Public/Get-ScheduledTaskAudit.ps1
|
using namespace System using namespace System.IO using namespace System.Security.Cryptography using namespace System.Collections.Immutable Set-StrictMode -Version Latest #### <h2 style="color: #DCA657;">Get-ScheduledTaskAudit</h2> #### function Get-ScheduledTaskAudit { #### Return one flat row per scheduled task. #### #### <b style="color: #D2A8FF;">Parameters</b> #### [CmdletBinding()] param( #### - `[string]`: __TaskPath__ #### - *Task folder to enumerate. Defaults to every folder.* [Parameter()][string]$TaskPath = '\*' ) #### Warned, not thrown. Stopping here would deny a non-admin an audit that is #### still worth having, but silence would present two thirds of the machine as #### the whole of it. if (-not (Test-Administrator)) { Write-Warning 'Get-ScheduledTaskAudit is not elevated. Enumeration is incomplete and omits SYSTEM context tasks.' } foreach ($task in Get-ScheduledTask -TaskPath $TaskPath -ErrorAction SilentlyContinue) { #### LastRunTime, LastTaskResult and NextRunTime live on a different cmdlet. #### Get-ScheduledTask alone never tells you whether a task actually ran. $info = $task | Get-ScheduledTaskInfo -ErrorAction SilentlyContinue #### A principal is either a UserId or a GroupId, never both. Reading only #### UserId leaves every group principal blank, which is a third of the tasks #### on a stock Windows install. That failure is silent, unlike the COM action #### one above, which is why it is easy to miss. $isGroupPrincipal = [string]::IsNullOrWhiteSpace($task.Principal.UserId) $principal = if ($isGroupPrincipal) { $task.Principal.GroupId } else { $task.Principal.UserId } #### Most non-zero results are not failures. The 0x000413xx range is the #### scheduler's own informational set, and "the task has not yet run" alone #### accounts for the overwhelming majority of them. The HRESULT severity bit #### is what separates a real failure from a status report. $resultCode = [uint32]0 if ($null -ne $info -and $null -ne $info.LastTaskResult) { $resultCode = [uint32]($info.LastTaskResult -band 0xFFFFFFFFL) } $resultKind = if ($resultCode -eq 0) { 'Success' } elseif ($resultCode -band 0x80000000) { 'Failure' } else { 'Informational' } $runs = [System.Collections.Generic.List[string]]::new() foreach ($action in @($task.Actions)) { switch ($action.CimClass.CimClassName) { 'MSFT_TaskExecAction' { $runs.Add(($action.Execute, $action.Arguments -join ' ').Trim()) } 'MSFT_TaskComHandlerAction' { $runs.Add("COM:$($action.ClassId)") } default { $runs.Add("OTHER:$($action.CimClass.CimClassName)") } } } #### #### <b style="color: #369FFF;">Returns</b> #### #### - `[PSCustomObject]` #### - `[string]`: __TaskName__ / __TaskPath__ / __State__ / __Author__ #### - *Identity and current state.* #### - `[string]`: __RunAs__ #### - *The principal, resolved from `UserId` or `GroupId`.* #### - `[string]`: __PrincipalKind__ #### - *`User` or `Group`, naming which one produced `RunAs`.* #### - `[string]`: __LogonType__ / __RunLevel__ #### - *How it logs on, and whether it runs at `Highest`.* #### - `[string]`: __ActionKind__ #### - *`Exec`, `ComHandler`, or a comma joined mix.* #### - `[string]`: __Runs__ #### - *Command line per exec action, or `COM:<ClassId>`.* #### - `[datetime]`: __LastRunTime__ / __NextRunTime__ #### - *From `Get-ScheduledTaskInfo`, not from the task itself.* #### - `[int]`: __LastTaskResult__ #### - *Raw result code.* #### - `[string]`: __LastResultHex__ #### - *The same code as `0xXXXXXXXX`, which is the readable form.* #### - `[string]`: __LastResultKind__ #### - *`Success`, `Informational`, or `Failure`, by HRESULT severity bit.* [PSCustomObject]@{ TaskName = $task.TaskName TaskPath = $task.TaskPath State = [string]$task.State Author = $task.Author RunAs = $principal PrincipalKind = if ($isGroupPrincipal) { 'Group' } else { 'User' } LogonType = [string]$task.Principal.LogonType RunLevel = [string]$task.Principal.RunLevel ActionKind = (@($task.Actions | ForEach-Object { $_.CimClass.CimClassName -replace '^MSFT_Task|Action$' }) -join ',') Runs = ($runs -join ' | ') LastRunTime = $info.LastRunTime LastTaskResult = $info.LastTaskResult LastResultHex = '0x{0:X8}' -f $resultCode LastResultKind = $resultKind NextRunTime = $info.NextRunTime } } } #### #### --- #### |