Private/ConvertTo-StmResultMessage.ps1
|
function ConvertTo-StmResultMessage { <# .SYNOPSIS Converts a Task Scheduler result code to a human-readable message. .DESCRIPTION The ConvertTo-StmResultMessage function translates numeric result codes from Windows Task Scheduler into human-readable messages. It uses a three-tier translation approach: 1. Task Scheduler-specific codes (SCHED_S_*, SCHED_E_*) - highest confidence 2. HRESULT decoding (parses structure, extracts Win32 code) - medium-high confidence 3. Direct Win32/application codes - medium confidence 4. Unknown codes - returns original with hex representation For ambiguous codes that may have multiple meanings, all possible interpretations are returned in the Meanings array. .PARAMETER ResultCode The result code to translate. Accepts integer, decimal string, or hex string (with 0x prefix). .EXAMPLE ConvertTo-StmResultMessage -ResultCode 0 Translates the success code 0 to "The operation completed successfully". .EXAMPLE ConvertTo-StmResultMessage -ResultCode 267009 Translates SCHED_S_TASK_RUNNING to "The task is currently running". .EXAMPLE ConvertTo-StmResultMessage -ResultCode '0x8004131F' Translates SCHED_E_ALREADY_RUNNING from hex format. .EXAMPLE ConvertTo-StmResultMessage -ResultCode 2147942402 Translates the HRESULT 0x80070002 to "The system cannot find the file specified". .INPUTS System.Object Accepts result codes as integers, strings (decimal), or hex strings. .OUTPUTS PSCustomObject Returns an object with translation details including ResultCode, HexCode, Message, Source, ConstantName, IsSuccess, Facility, FacilityCode, and Meanings array. .NOTES This function is used internally by Get-StmResultCodeMessage and Get-StmScheduledTaskRun. Task Scheduler codes are sourced from Microsoft documentation: https://learn.microsoft.com/en-us/windows/win32/taskschd/task-scheduler-error-and-success-constants #> [CmdletBinding()] [OutputType([PSCustomObject])] param( [Parameter(Mandatory = $true, ValueFromPipeline = $true)] [AllowNull()] [object] $ResultCode ) begin { # Task Scheduler-specific codes from Microsoft documentation # Source: https://learn.microsoft.com/en-us/windows/win32/taskschd/task-scheduler-error-and-success-constants $script:TaskSchedulerCodes = @{ # Success codes (SCHED_S_*) # Hex 0x00041300 = decimal 267008 267008 = @{ Name = 'SCHED_S_TASK_READY' Message = 'The task is ready to run at its next scheduled time' IsSuccess = $true } # Hex 0x00041301 = decimal 267009 267009 = @{ Name = 'SCHED_S_TASK_RUNNING' Message = 'The task is currently running' IsSuccess = $true } # Hex 0x00041302 = decimal 267010 267010 = @{ Name = 'SCHED_S_TASK_DISABLED' Message = 'The task will not run at the scheduled times because it has been disabled' IsSuccess = $true } # Hex 0x00041303 = decimal 267011 267011 = @{ Name = 'SCHED_S_TASK_HAS_NOT_RUN' Message = 'The task has not yet run' IsSuccess = $true } # Hex 0x00041304 = decimal 267012 267012 = @{ Name = 'SCHED_S_TASK_NO_MORE_RUNS' Message = 'There are no more runs scheduled for this task' IsSuccess = $true } # Hex 0x00041305 = decimal 267013 267013 = @{ Name = 'SCHED_S_TASK_NOT_SCHEDULED' Message = 'One or more of the properties needed to run this task on a schedule have not been set' IsSuccess = $true } # Hex 0x00041306 = decimal 267014 267014 = @{ Name = 'SCHED_S_TASK_TERMINATED' Message = 'The last run of the task was terminated by the user' IsSuccess = $true } # Hex 0x00041307 = decimal 267015 267015 = @{ Name = 'SCHED_S_TASK_NO_VALID_TRIGGERS' Message = 'Either the task has no triggers or the existing triggers are disabled or not set' IsSuccess = $true } # Hex 0x00041308 = decimal 267016 267016 = @{ Name = 'SCHED_S_EVENT_TRIGGER' Message = 'Event triggers do not have set run times' IsSuccess = $true } # Hex 0x0004131B = decimal 267035 267035 = @{ Name = 'SCHED_S_SOME_TRIGGERS_FAILED' Message = 'The task is registered, but not all specified triggers will start the task' IsSuccess = $true } # Hex 0x0004131C = decimal 267036 267036 = @{ Name = 'SCHED_S_BATCH_LOGON_PROBLEM' Message = 'The task is registered, but may fail to start. Batch logon privilege needs to be enabled for the task principal' IsSuccess = $true } # Hex 0x00041325 = decimal 267045 267045 = @{ Name = 'SCHED_S_TASK_QUEUED' Message = 'The Task Scheduler service has asked the task to run' IsSuccess = $true } # Error codes (SCHED_E_*) 6200 = @{ Name = 'SCHED_E_SERVICE_NOT_LOCALSYSTEM' Message = 'The Task Scheduler service must be configured to run in the System account to function properly' IsSuccess = $false } 2147750665 = @{ Name = 'SCHED_E_TRIGGER_NOT_FOUND' Message = 'Trigger not found' IsSuccess = $false } 2147750666 = @{ Name = 'SCHED_E_TASK_NOT_READY' Message = 'One or more of the properties needed to run this task have not been set' IsSuccess = $false } 2147750667 = @{ Name = 'SCHED_E_TASK_NOT_RUNNING' Message = 'There is no running instance of the task' IsSuccess = $false } 2147750668 = @{ Name = 'SCHED_E_SERVICE_NOT_INSTALLED' Message = 'The Task Scheduler Service is not installed on this computer' IsSuccess = $false } 2147750669 = @{ Name = 'SCHED_E_CANNOT_OPEN_TASK' Message = 'The task object could not be opened' IsSuccess = $false } 2147750670 = @{ Name = 'SCHED_E_INVALID_TASK' Message = 'The object is either an invalid task object or is not a task object' IsSuccess = $false } 2147750671 = @{ Name = 'SCHED_E_ACCOUNT_INFORMATION_NOT_SET' Message = 'No account information could be found in the Task Scheduler security database for the task indicated' IsSuccess = $false } 2147750672 = @{ Name = 'SCHED_E_ACCOUNT_NAME_NOT_FOUND' Message = 'Unable to establish existence of the account specified' IsSuccess = $false } 2147750673 = @{ Name = 'SCHED_E_ACCOUNT_DBASE_CORRUPT' Message = 'Corruption was detected in the Task Scheduler security database; the database has been reset' IsSuccess = $false } 2147750674 = @{ Name = 'SCHED_E_NO_SECURITY_SERVICES' Message = 'Task Scheduler security services are available only on Windows NT' IsSuccess = $false } 2147750675 = @{ Name = 'SCHED_E_UNKNOWN_OBJECT_VERSION' Message = 'The task object version is either unsupported or invalid' IsSuccess = $false } 2147750676 = @{ Name = 'SCHED_E_UNSUPPORTED_ACCOUNT_OPTION' Message = 'The task has been configured with an unsupported combination of account settings and run time options' IsSuccess = $false } 2147750677 = @{ Name = 'SCHED_E_SERVICE_NOT_RUNNING' Message = 'The Task Scheduler Service is not running' IsSuccess = $false } 2147750678 = @{ Name = 'SCHED_E_UNEXPECTEDNODE' Message = 'The task XML contains an unexpected node' IsSuccess = $false } 2147750679 = @{ Name = 'SCHED_E_NAMESPACE' Message = 'The task XML contains an element or attribute from an unexpected namespace' IsSuccess = $false } 2147750680 = @{ Name = 'SCHED_E_INVALIDVALUE' Message = 'The task XML contains a value which is incorrectly formatted or out of range' IsSuccess = $false } 2147750681 = @{ Name = 'SCHED_E_MISSINGNODE' Message = 'The task XML is missing a required element or attribute' IsSuccess = $false } 2147750682 = @{ Name = 'SCHED_E_MALFORMEDXML' Message = 'The task XML is malformed' IsSuccess = $false } 2147750685 = @{ Name = 'SCHED_E_TOO_MANY_NODES' Message = 'The task XML contains too many nodes of the same type' IsSuccess = $false } 2147750686 = @{ Name = 'SCHED_E_PAST_END_BOUNDARY' Message = 'The task cannot be started after the trigger end boundary' IsSuccess = $false } 2147750687 = @{ Name = 'SCHED_E_ALREADY_RUNNING' Message = 'An instance of this task is already running' IsSuccess = $false } 2147750688 = @{ Name = 'SCHED_E_USER_NOT_LOGGED_ON' Message = 'The task will not run because the user is not logged on' IsSuccess = $false } 2147750689 = @{ Name = 'SCHED_E_INVALID_TASK_HASH' Message = 'The task image is corrupt or has been tampered with' IsSuccess = $false } 2147750690 = @{ Name = 'SCHED_E_SERVICE_NOT_AVAILABLE' Message = 'The Task Scheduler service is not available' IsSuccess = $false } 2147750691 = @{ Name = 'SCHED_E_SERVICE_TOO_BUSY' Message = 'The Task Scheduler service is too busy to handle your request. Please try again later' IsSuccess = $false } 2147750692 = @{ Name = 'SCHED_E_TASK_ATTEMPTED' Message = 'The Task Scheduler service attempted to run the task, but the task did not run due to one of the constraints in the task definition' IsSuccess = $false } 2147750694 = @{ Name = 'SCHED_E_TASK_DISABLED' Message = 'The task is disabled' IsSuccess = $false } 2147750695 = @{ Name = 'SCHED_E_TASK_NOT_V1_COMPAT' Message = 'The task has properties that are not compatible with previous versions of Windows' IsSuccess = $false } 2147750696 = @{ Name = 'SCHED_E_START_ON_DEMAND' Message = 'The task settings do not allow the task to start on demand' IsSuccess = $false } 2147750697 = @{ Name = 'SCHED_E_TASK_NOT_UBPM_COMPAT' Message = 'The combination of properties that task is using is not compatible with the scheduling engine' IsSuccess = $false } 2147750704 = @{ Name = 'SCHED_E_DEPRECATED_FEATURE_USED' Message = 'The task definition uses a deprecated feature' IsSuccess = $false } # Common COM/OLE errors seen in task results (FACILITY_ITF) # These are not Task Scheduler specific but commonly appear when tasks fail 2147746065 = @{ Name = 'CLASS_E_CLASSNOTAVAILABLE' Message = 'ClassFactory cannot supply requested class' IsSuccess = $false } 2147746132 = @{ Name = 'REGDB_E_CLASSNOTREG' Message = 'Class not registered' IsSuccess = $false } } # Common HRESULT facility codes $script:FacilityCodes = @{ 0 = 'FACILITY_NULL' 1 = 'FACILITY_RPC' 2 = 'FACILITY_DISPATCH' 3 = 'FACILITY_STORAGE' 4 = 'FACILITY_ITF' 7 = 'FACILITY_WIN32' 8 = 'FACILITY_WINDOWS' 9 = 'FACILITY_SECURITY' 10 = 'FACILITY_CONTROL' 11 = 'FACILITY_CERT' 12 = 'FACILITY_INTERNET' 13 = 'FACILITY_MEDIASERVER' 14 = 'FACILITY_MSMQ' 15 = 'FACILITY_SETUPAPI' 16 = 'FACILITY_SCARD' 17 = 'FACILITY_COMPLUS' 18 = 'FACILITY_AAF' 19 = 'FACILITY_URT' 20 = 'FACILITY_ACS' 21 = 'FACILITY_DPLAY' 22 = 'FACILITY_UMI' 23 = 'FACILITY_SXS' 24 = 'FACILITY_WINDOWS_CE' 25 = 'FACILITY_HTTP' 26 = 'FACILITY_USERMODE_COMMONLOG' 31 = 'FACILITY_USERMODE_FILTER_MANAGER' 32 = 'FACILITY_BACKGROUNDCOPY' 33 = 'FACILITY_CONFIGURATION' 34 = 'FACILITY_STATE_MANAGEMENT' 35 = 'FACILITY_METADIRECTORY' 36 = 'FACILITY_WINDOWSUPDATE' 37 = 'FACILITY_DIRECTORYSERVICE' 38 = 'FACILITY_GRAPHICS' 39 = 'FACILITY_SHELL' 40 = 'FACILITY_TPM_SERVICES' 41 = 'FACILITY_TPM_SOFTWARE' 48 = 'FACILITY_PLA' 49 = 'FACILITY_FVE' 50 = 'FACILITY_FWP' 51 = 'FACILITY_WINRM' 52 = 'FACILITY_NDIS' 53 = 'FACILITY_USERMODE_HYPERVISOR' 54 = 'FACILITY_CMI' 55 = 'FACILITY_USERMODE_VIRTUALIZATION' 56 = 'FACILITY_USERMODE_VOLMGR' 57 = 'FACILITY_BCD' 58 = 'FACILITY_USERMODE_VHD' 60 = 'FACILITY_SDIAG' 61 = 'FACILITY_WEBSERVICES' 80 = 'FACILITY_WINDOWS_DEFENDER' 81 = 'FACILITY_OPC' } } process { # Handle null or empty input if ($null -eq $ResultCode -or ($ResultCode -is [string] -and [string]::IsNullOrWhiteSpace($ResultCode))) { Write-Verbose 'ResultCode is null or empty, returning null' return $null } # Parse the input to get an integer value [int64]$codeValue = 0 $parseSuccess = $false if ($ResultCode -is [int] -or $ResultCode -is [int64] -or $ResultCode -is [uint32]) { $codeValue = [int64]$ResultCode $parseSuccess = $true Write-Verbose "Parsed integer input: $codeValue" } elseif ($ResultCode -is [string]) { $stringValue = $ResultCode.Trim() # Check for hex format (0x or 0X prefix) if ($stringValue -match '^0[xX]([0-9a-fA-F]+)$') { try { $codeValue = [Convert]::ToInt64($Matches[1], 16) $parseSuccess = $true Write-Verbose "Parsed hex string input '$stringValue' to: $codeValue" } catch { Write-Verbose "Failed to parse hex string '$stringValue': $_" } } else { # Try to parse as decimal if ([int64]::TryParse($stringValue, [ref]$codeValue)) { $parseSuccess = $true Write-Verbose "Parsed decimal string input '$stringValue' to: $codeValue" } else { Write-Verbose "Failed to parse decimal string '$stringValue'" } } } else { # Try to convert other types try { $codeValue = [int64]$ResultCode $parseSuccess = $true Write-Verbose "Converted input type '$($ResultCode.GetType().Name)' to: $codeValue" } catch { Write-Verbose "Failed to convert input type '$($ResultCode.GetType().Name)': $_" } } if (-not $parseSuccess) { Write-Warning "Unable to parse result code: $ResultCode" return [PSCustomObject]@{ ResultCode = $ResultCode HexCode = $null Message = 'Unable to parse result code' Source = 'Unknown' ConstantName = $null IsSuccess = $null Facility = $null FacilityCode = $null Meanings = @() } } # Convert to unsigned for hex display (handle negative values properly) if ($codeValue -lt 0) { if ($codeValue -ge [int]::MinValue -and $codeValue -le [int]::MaxValue) { # Value fits in int32, use BitConverter for proper signed-to-unsigned conversion $bytes = [System.BitConverter]::GetBytes([int32]$codeValue) $unsignedValue = [System.BitConverter]::ToUInt32($bytes, 0) $hexCode = '0x{0:X8}' -f $unsignedValue } else { # Large negative value outside int32 range, display as int64 hex $hexCode = '0x{0:X16}' -f $codeValue } } else { $hexCode = '0x{0:X8}' -f $codeValue } Write-Verbose "Processing result code: $codeValue ($hexCode)" # Build the meanings array $meanings = [System.Collections.Generic.List[PSCustomObject]]::new() # Tier 1: Check Task Scheduler lookup table first # Try int64 lookup first (for SCHED_E_* codes with values > int32 max) # Then try int32 lookup (for SCHED_S_* codes stored as int32 keys) $taskSchedulerMatch = $script:TaskSchedulerCodes[[int64]$codeValue] if (-not $taskSchedulerMatch -and $codeValue -ge [int]::MinValue -and $codeValue -le [int]::MaxValue) { $taskSchedulerMatch = $script:TaskSchedulerCodes[[int]$codeValue] } if ($taskSchedulerMatch) { Write-Verbose "Found Task Scheduler code: $($taskSchedulerMatch.Name)" $meanings.Add([PSCustomObject]@{ Source = 'TaskScheduler' ConstantName = $taskSchedulerMatch.Name Message = $taskSchedulerMatch.Message IsSuccess = $taskSchedulerMatch.IsSuccess }) } # Parse HRESULT structure for additional information # HRESULT structure: # Bit 31: Severity (1=failure, 0=success) # Bit 30: Reserved # Bit 29: Customer (1=customer-defined, 0=Microsoft-defined) # Bits 16-28: Facility code (13 bits) # Bits 0-15: Error code (16 bits) $isFailure = ($codeValue -band 0x80000000) -ne 0 $facilityCode = [int](($codeValue -shr 16) -band 0x1FFF) $errorCode = [int]($codeValue -band 0xFFFF) $facilityName = $script:FacilityCodes[$facilityCode] if (-not $facilityName) { $facilityName = "FACILITY_$facilityCode" } Write-Verbose "HRESULT: IsFailure=$isFailure, Facility=$facilityName ($facilityCode), ErrorCode=$errorCode" # Tier 2: HRESULT decoding - translate Win32 error code if applicable if ($facilityCode -eq 7) { # FACILITY_WIN32 - extract and translate the Win32 error code Write-Verbose "Detected FACILITY_WIN32, extracting error code: $errorCode" $win32Message = Get-StmWin32ErrorMessage -ErrorCode $errorCode if ($null -ne $win32Message) { # Only add if different from Task Scheduler message (avoid duplicates) $isDuplicate = $meanings | Where-Object { $_.Message -eq $win32Message } if (-not $isDuplicate) { $meanings.Add([PSCustomObject]@{ Source = 'Win32' ConstantName = $null Message = $win32Message IsSuccess = -not $isFailure }) Write-Verbose "Added Win32 translation: $win32Message" } } } elseif ($codeValue -ne 0 -and -not $taskSchedulerMatch -and $codeValue -gt 0 -and $codeValue -le 65535) { # Tier 3: Small positive integers - try direct Win32 translation Write-Verbose "Trying direct Win32 translation for small code: $codeValue" $win32Message = Get-StmWin32ErrorMessage -ErrorCode ([int]$codeValue) # Check if translation succeeded and message is not just the number if ($null -ne $win32Message -and $win32Message -ne $codeValue.ToString()) { $meanings.Add([PSCustomObject]@{ Source = 'Win32' ConstantName = $null Message = $win32Message IsSuccess = $false }) Write-Verbose "Added direct Win32 translation: $win32Message" } } # Special case: code 0 is always success if ($codeValue -eq 0 -and $meanings.Count -eq 0) { Write-Verbose 'Adding success message for code 0' $meanings.Add([PSCustomObject]@{ Source = 'Win32' ConstantName = 'ERROR_SUCCESS' Message = 'The operation completed successfully' IsSuccess = $true }) } # Build the result object $primaryMeaning = $meanings | Select-Object -First 1 if ($primaryMeaning) { $result = [PSCustomObject]@{ ResultCode = $codeValue HexCode = $hexCode Message = $primaryMeaning.Message Source = $primaryMeaning.Source ConstantName = $primaryMeaning.ConstantName IsSuccess = $primaryMeaning.IsSuccess Facility = $facilityName FacilityCode = $facilityCode Meanings = $meanings.ToArray() } } else { # Tier 4: Unknown code Write-Verbose "No translation found, returning as Unknown" $result = [PSCustomObject]@{ ResultCode = $codeValue HexCode = $hexCode Message = "Unknown result code: $hexCode" Source = 'Unknown' ConstantName = $null IsSuccess = -not $isFailure Facility = $facilityName FacilityCode = $facilityCode Meanings = @() } } return $result } } |