Assets/Get-OpsJob.ps1
|
<# .SYNOPSIS Gets the status of Ops Jobs. .DESCRIPTION Reports each job's schedule state plus the script it runs and the arguments it runs it with, recovered from the Base64 payload New-OpsJob stored on the scheduled task's action. ScriptArguments comes back as an ordered hashtable, so it can be splatted straight back into New-OpsJob to re-register a job with the same arguments. .PARAMETER JobName Optional specific job name. Supports wildcards. #> param ( [string]$JobName = "*" ) function Get-OpsJobPayload { param([string]$Arguments) # The payload is Base64 of UTF-8 JSON and therefore a single unquoted token; # matching the alphabet is enough to isolate it from the surrounding switches. if ($Arguments -notmatch '-PayloadBase64\s+([A-Za-z0-9+/=]+)') { return $null } try { $Json = [System.Text.Encoding]::UTF8.GetString([Convert]::FromBase64String($Matches[1])) return $Json | ConvertFrom-Json } catch { Write-Warning "Could not decode the payload of a scheduled task: $($_.Exception.Message)" return $null } } function ConvertTo-OpsArgumentTable { param($ScriptArguments) if ($null -eq $ScriptArguments) { return $null } # ConvertFrom-Json turns a JSON object into a PSCustomObject. A hashtable is # what New-OpsJob and JobRunner both expect, so hand back something the # caller can splat rather than something they have to convert first. if ($ScriptArguments -is [System.Management.Automation.PSCustomObject]) { $Table = [ordered]@{} foreach ($Property in $ScriptArguments.PSObject.Properties) { $Table[$Property.Name] = $Property.Value } return $Table } return @($ScriptArguments) } $Tasks = @(Get-ScheduledTask -TaskName "OpsJob-$JobName" -ErrorAction SilentlyContinue) $Results = [System.Collections.Generic.List[object]]::new() foreach ($Task in $Tasks) { $Info = $Task | Get-ScheduledTaskInfo -ErrorAction SilentlyContinue $ScriptPath = $null $ScriptArguments = $null foreach ($Action in @($Task.Actions)) { $Arguments = [string]$Action.Arguments if ([string]::IsNullOrWhiteSpace($Arguments)) { continue } $Payload = Get-OpsJobPayload -Arguments $Arguments if ($null -ne $Payload) { $ScriptPath = $Payload.ScriptPath $ScriptArguments = ConvertTo-OpsArgumentTable -ScriptArguments $Payload.ScriptArguments break } # Jobs registered before the payload change passed -ScriptPath on the # command line. Their arguments cannot be recovered the same way, so # only the path is reported and ScriptArguments stays $null rather than # being guessed at. if ($Arguments -match '-ScriptPath\s+(?:"([^"]+)"|([^\s"]+))') { $ScriptPath = if ($Matches[1]) { $Matches[1] } else { $Matches[2] } break } } $Results.Add([PSCustomObject]@{ # Anchored: an unanchored -replace would also strip the prefix from the # middle of a job name such as "Sync-OpsJob-Nightly". JobName = ($Task.TaskName -replace '^OpsJob-', '') State = $Task.State ScriptPath = $ScriptPath ScriptArguments = $ScriptArguments LastRunTime = $Info.LastRunTime LastTaskResult = $Info.LastTaskResult NextRunTime = $Info.NextRunTime }) } return $Results.ToArray() |