Private/Code.ps1
|
# What a code means. # # A status or an error code is never shown to a Technician bare. The code stays - it is # what gets looked up - and what it means stands beside it. These are the decoders the # Kinds share: the error numbers of Windows, HTTP statuses, the sign-in statuses of # Windows and of Microsoft Entra ID. A code none of them knows comes back with nothing, # and is then shown as it is: that is a gap to close and not a reason to hide the code. # # All pure. Where a table here is not Microsoft's own documentation, it says where it is from. function ConvertTo-CodeNumber { <# .SYNOPSIS A code as a number, however it was written: 1067, '0x5', 'C000006D'. $null for what is none. Pure. #> [CmdletBinding()] [OutputType([long])] param([AllowNull()]$Code, [switch]$Hex) $text = "$Code".Trim() if (-not $text) { return $null } $number = [long]0 if ($text -match '^(?i)0x([0-9a-f]+)$') { if ([long]::TryParse($Matches[1], [Globalization.NumberStyles]::HexNumber, [cultureinfo]::InvariantCulture, [ref]$number)) { return $number } return $null } if ($Hex -and $text -match '^(?i)[0-9a-f]{8}$') { if ([long]::TryParse($text, [Globalization.NumberStyles]::HexNumber, [cultureinfo]::InvariantCulture, [ref]$number)) { return $number } } if ([long]::TryParse($text, [Globalization.NumberStyles]::Integer, [cultureinfo]::InvariantCulture, [ref]$number)) { return $number } $null } function Get-Win32ErrorText { <# .SYNOPSIS What Windows itself says of one of its error numbers, in the language Windows runs in. Nothing for zero and for a number it has no words for. Pure. .DESCRIPTION The exit code of a service, the error of a Group Policy run, the low word of an HRESULT that begins 0x8007. Windows carries the sentences; a number it does not know comes back as "Unknown error (0x...)", which is no meaning and is dropped. #> [CmdletBinding()] [OutputType([string])] param([AllowNull()]$Code) $number = ConvertTo-CodeNumber -Code $Code if ($null -eq $number -or $number -le 0 -or $number -gt [int]::MaxValue) { return '' } # A number Windows has no sentence for and the field knows well. 283 is 0x11B: what # "Windows cannot connect to the printer, 0x0000011b" shows, since the print spooler # update of September 2021 (CVE-2021-1678) made client and print server agree on how # the connection is secured. Not a table of Microsoft's. if ($number -eq 283) { return Get-Text 'Value.Code.Win32.283' } try { $message = "$((New-Object System.ComponentModel.Win32Exception ([int]$number)).Message)".Trim() if ($message -match '(?i)^(Unknown error|Unbekannter Fehler)' -or $message -match '\(0x[0-9a-f]+\)$') { return '' } $message } catch { '' } } function Get-WindowsMessageText { <# .SYNOPSIS The sentence a library of Windows carries for one of its codes, in the language Windows runs in. Nothing for a code it has none for. Reads Windows; changes nothing. .DESCRIPTION ntdll.dll carries one for every status of Windows (NTSTATUS), winhttp.dll for every WinHTTP error; Get-Win32ErrorText does not reach either. A sentence with a place left open for a value ("%hs") has [...] there; a title in braces before it is kept as its beginning; of a long one the first sentence is kept. #> [CmdletBinding()] [OutputType([string])] param([Parameter(Mandatory)][ValidateSet('ntdll.dll', 'winhttp.dll')][string]$Library, [AllowNull()]$Id) if ($null -eq $Id) { return '' } try { if (-not ('Gutcheck.WindowsMessage' -as [type])) { Add-Type -Namespace Gutcheck -Name WindowsMessage -MemberDefinition @' [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)] public static extern IntPtr LoadLibraryEx(string file, IntPtr reserved, uint flags); [DllImport("kernel32.dll")] public static extern bool FreeLibrary(IntPtr module); [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)] public static extern int FormatMessage(uint flags, IntPtr source, uint id, uint language, System.Text.StringBuilder buffer, int size, IntPtr arguments); '@ } $number = [uint32]([long]$Id -band 0xFFFFFFFFL) # As a data file only: nothing of the library is run. $module = [Gutcheck.WindowsMessage]::LoadLibraryEx($Library, [IntPtr]::Zero, 0x2) if ($module -eq [IntPtr]::Zero) { return '' } try { $buffer = New-Object System.Text.StringBuilder 2048 # From the module, inserts left as they are. $length = [Gutcheck.WindowsMessage]::FormatMessage(0x800 -bor 0x200, $module, $number, 0, $buffer, $buffer.Capacity, [IntPtr]::Zero) if ($length -le 0) { return '' } $text = $buffer.ToString() } finally { [void][Gutcheck.WindowsMessage]::FreeLibrary($module) } # Line by line: a title in braces becomes the beginning, and a line that ends # without a full stop gets one, so that the lines do not run into each other. $lines = @($text -split '\r?\n' | ForEach-Object { $_.Trim() } | Where-Object { $_ } | ForEach-Object { $line = $_ -replace '^\{([^}]*)\}$', '$1:' -replace '^\{([^}]*)\}\s*', '$1: ' if ($line -notmatch '[.:!?,;]$') { $line + '.' } else { $line } }) $text = $lines -join ' ' # A place left open for a value: [...] stands where the value would. $text = $text -replace '"?(0x)?%[0-9]*!?[a-zA-Z]+!?"?', '[...]' -replace '%[0-9]+', '[...]' $text = ($text -replace '\s+', ' ').Trim() if ($text.Length -gt 160) { $end = $text.IndexOf('. ') if ($end -gt 20) { $text = $text.Substring(0, $end + 1) } } $text.TrimEnd('.') } catch { '' } } function Get-HttpStatusText { <# .SYNOPSIS What an HTTP status means, in a Technician's words: the status's own where this has them, else what its class means. Nothing for what is no HTTP status. Pure. .DESCRIPTION 0 is no answer at all: a client writes it where the connection failed before the server said anything. -Exact asks for a status's own words only, and not its class's: Format-HttpStatus puts the reason a server gave before the class. #> [CmdletBinding()] [OutputType([string])] param([AllowNull()]$Status, [switch]$Exact) $number = ConvertTo-CodeNumber -Code $Status if ($null -eq $number) { return '' } switch ([int]$number) { 0 { return Get-Text 'Value.Code.Http.0' } 200 { return Get-Text 'Value.Code.Http.200' } 204 { return Get-Text 'Value.Code.Http.204' } 301 { return Get-Text 'Value.Code.Http.301' } 302 { return Get-Text 'Value.Code.Http.302' } 307 { return Get-Text 'Value.Code.Http.302' } 400 { return Get-Text 'Value.Code.Http.400' } 401 { return Get-Text 'Value.Code.Http.401' } 403 { return Get-Text 'Value.Code.Http.403' } 404 { return Get-Text 'Value.Code.Http.404' } 407 { return Get-Text 'Value.Code.Http.407' } 408 { return Get-Text 'Value.Code.Http.408' } 429 { return Get-Text 'Value.Code.Http.429' } 500 { return Get-Text 'Value.Code.Http.500' } 502 { return Get-Text 'Value.Code.Http.502' } 503 { return Get-Text 'Value.Code.Http.503' } 504 { return Get-Text 'Value.Code.Http.504' } } if ($Exact) { return '' } if ($number -ge 100 -and $number -lt 200) { return Get-Text 'Value.Code.Http.1xx' } if ($number -ge 200 -and $number -lt 300) { return Get-Text 'Value.Code.Http.2xx' } if ($number -ge 300 -and $number -lt 400) { return Get-Text 'Value.Code.Http.3xx' } if ($number -ge 400 -and $number -lt 500) { return Get-Text 'Value.Code.Http.4xx' } if ($number -ge 500 -and $number -lt 600) { return Get-Text 'Value.Code.Http.5xx' } '' } function Format-CodeWithMeaning { <# .SYNOPSIS A code with what it means behind it; the code alone where nothing is known. Pure. #> [CmdletBinding()] [OutputType([string])] param([AllowNull()][AllowEmptyString()][string]$Code, [AllowNull()][AllowEmptyString()][string]$Meaning) if (-not "$Code".Trim()) { return '' } if (-not "$Meaning".Trim()) { return "$Code".Trim() } (Get-Text 'Value.Code.WithMeaning') -f "$Code".Trim(), "$Meaning".Trim() } function Format-HttpStatus { <# .SYNOPSIS An HTTP status with what it means behind it, "404 (nicht gefunden)": never the number alone. Nothing for no status. Pure. .DESCRIPTION The one way every Kind says a status. Its own words where this has them; else the reason the server gave with it (-Reason), "423 (Locked)"; else what its class means, "418 (Fehler der Anfrage)". #> [CmdletBinding()] [OutputType([string])] param([AllowNull()]$Status, [AllowNull()][AllowEmptyString()][string]$Reason) $number = ConvertTo-CodeNumber -Code $Status if ($null -eq $number) { return (Format-CodeWithMeaning -Code "$Status" -Meaning '') } $meaning = Get-HttpStatusText -Status $number -Exact if (-not $meaning) { $meaning = "$Reason".Trim() } if (-not $meaning) { $meaning = Get-HttpStatusText -Status $number } Format-CodeWithMeaning -Code "$number" -Meaning $meaning } function Get-NtStatusText { <# .SYNOPSIS What a sign-in status of Windows (NTSTATUS) means. Nothing for one not known here. Pure. .DESCRIPTION The ones a sign-in ends in, as Microsoft documents them for logon events (event 4625, "Status" and "Sub Status"), and the ones Microsoft's article on troubleshooting the primary refresh token names for the "Attempt Status" of dsregcmd /status: the network was not there, or the logon session was gone. Any other status is said as Windows says it, unless -KnownHere asks for the ones worded here only. #> [CmdletBinding()] [OutputType([string])] param([AllowNull()]$Code, [switch]$KnownHere) $number = ConvertTo-CodeNumber -Code $Code -Hex if ($null -eq $number) { return '' } switch ('{0:X8}' -f ($number -band 0xFFFFFFFFL)) { 'C000006D' { return Get-Text 'Value.Code.NtStatus.LogonFailure' } 'C000006A' { return Get-Text 'Value.Code.NtStatus.WrongPassword' } 'C0000064' { return Get-Text 'Value.Code.NtStatus.NoSuchUser' } 'C0000234' { return Get-Text 'Value.Code.NtStatus.LockedOut' } 'C0000072' { return Get-Text 'Value.Code.NtStatus.Disabled' } 'C0000071' { return Get-Text 'Value.Code.NtStatus.PasswordExpired' } 'C0000224' { return Get-Text 'Value.Code.NtStatus.PasswordMustChange' } 'C0000193' { return Get-Text 'Value.Code.NtStatus.AccountExpired' } 'C000006F' { return Get-Text 'Value.Code.NtStatus.LogonHours' } 'C0000070' { return Get-Text 'Value.Code.NtStatus.Workstation' } 'C000005E' { return Get-Text 'Value.Code.NtStatus.NoLogonServers' } 'C0000133' { return Get-Text 'Value.Code.NtStatus.TimeDifference' } 'C000015B' { return Get-Text 'Value.Code.NtStatus.LogonType' } 'C000005F' { return Get-Text 'Value.Code.NtStatus.NoLogonSession' } 'C00000BE' { return Get-Text 'Value.Code.NtStatus.BadNetworkPath' } 'C00000C4' { return Get-Text 'Value.Code.NtStatus.UnexpectedNetworkError' } 'C00000D0' { return Get-Text 'Value.Code.NtStatus.RequestNotAccepted' } 'C000023C' { return Get-Text 'Value.Code.NtStatus.NetworkUnreachable' } # What a share is refused with (SMBClient 31010): Windows' own sentence is three # lines for two words. 'C0000022' { return Get-Text 'Value.Code.NtStatus.AccessDenied' } 'C00000CC' { return Get-Text 'Value.Code.NtStatus.BadNetworkName' } } if ($KnownHere) { return '' } # Not one of those: Windows has a sentence for every status it knows. Get-WindowsMessageText -Library 'ntdll.dll' -Id $number } function Get-HResultText { <# .SYNOPSIS What an HRESULT means: a Win32 error it wraps, a sign-in status of Windows, or one of the sign-in broker's own. Nothing for one not known here. Pure. .DESCRIPTION 0x8007nnnn is the Win32 error nnnn. 0xCAAnnnnn are the codes of the Microsoft account and work account broker (AAD/WAM) that Office logs; the ones named here are those Microsoft's troubleshooting articles for Office sign-in name, and 0xCAA82EEn carry a WinHTTP error in their low word (12002 timeout, 12007 name not resolved, 12029 cannot connect, 12030 connection aborted, 12175 no secure connection). The same WinHTTP errors wrapped as 0x80072nnn are named here too: Windows keeps their sentences in winhttp.dll and not where Get-Win32ErrorText reads, so it has none for them. The errors of AppX deployment (0x80073CFn) need no table: they are Win32 errors Windows has words for. #> [CmdletBinding()] [OutputType([string])] param([AllowNull()]$Code) $number = ConvertTo-CodeNumber -Code $Code -Hex if ($null -eq $number) { return '' } $hex = '{0:X8}' -f ($number -band 0xFFFFFFFFL) switch ($hex) { '80072EE2' { return Get-Text 'Value.Code.WinHttp.Timeout' } '80072EE7' { return Get-Text 'Value.Code.WinHttp.NameNotResolved' } '80072EFD' { return Get-Text 'Value.Code.WinHttp.CannotConnect' } '80072EFE' { return Get-Text 'Value.Code.WinHttp.ConnectionAborted' } '80072F8F' { return Get-Text 'Value.Code.WinHttp.SecureFailure' } 'CAA82EFE' { return Get-Text 'Value.Code.WinHttp.ConnectionAborted' } 'CAA82F8F' { return Get-Text 'Value.Code.WinHttp.SecureFailure' } } # Any other WinHTTP error (12000 to 12999), wrapped either way: winhttp.dll has it. if ($hex -match '^(8007|CAA8)(2[EF][0-9A-F]{2}|3[0-2][0-9A-F]{2})$') { $low = $number -band 0xFFFF if ($low -ge 12000 -and $low -le 12999) { $said = Get-WindowsMessageText -Library 'winhttp.dll' -Id $low if ($said) { return $said } } } if ($hex.StartsWith('8007')) { return Get-Win32ErrorText -Code ($number -band 0xFFFF) } switch ($hex) { # These three as the event of the Windows sign-in log words them beside the code # (read off Microsoft-Windows-AAD/Operational 1098 on a Windows 11 machine): # "A login hint was sent that doesn't match any WebAccount in the system", # "Token broker operation failed", and invalid_request. 'CAA100D8' { return Get-Text 'Value.Code.Wam.LoginHint' } 'CAA5001C' { return Get-Text 'Value.Code.Wam.BrokerFailed' } 'CAA20002' { return Get-Text 'Value.Code.Wam.InvalidRequest' } 'CAA20003' { return Get-Text 'Value.Code.Wam.InvalidGrant' } 'CAA2000C' { return Get-Text 'Value.Code.Wam.InteractionRequired' } 'CAA70004' { return Get-Text 'Value.Code.Wam.ServerNotFound' } 'CAA70007' { return Get-Text 'Value.Code.Wam.ServerNotResponding' } 'CAA82EE2' { return Get-Text 'Value.Code.Wam.Timeout' } 'CAA82EE7' { return Get-Text 'Value.Code.Wam.NameNotResolved' } 'CAA82EFD' { return Get-Text 'Value.Code.Wam.CannotConnect' } } switch ($hex) { # OneDrive's own sign-in errors, as Microsoft's troubleshooting articles for each # describe them (docs/research/onedrive-sign-in.md): 0x8004DE40 and 0x8004DE88 # "having trouble connecting to the cloud", 0x8004DE80 and 0x8004DE86 a problem of # the account or its configuration, 0x8004DEC5 "multiple cached identities", # 0x8004DEB4 after an Office update, worked around by turning on modern sign-in. '8004DE40' { return Get-Text 'Value.Code.OneDrive.CannotConnect' } '8004DE88' { return Get-Text 'Value.Code.OneDrive.CannotConnect' } '8004DE80' { return Get-Text 'Value.Code.OneDrive.Account' } '8004DE86' { return Get-Text 'Value.Code.OneDrive.Account' } '8004DEC5' { return Get-Text 'Value.Code.OneDrive.Identities' } '8004DEB4' { return Get-Text 'Value.Code.OneDrive.ModernAuth' } } # Another of OneDrive's: whose code it is. Every code those articles name is 0x8004DEnn. if ($hex.StartsWith('8004DE')) { return Get-Text 'Value.Code.OneDrive.Other' } # One of the broker's own that is not named above: at least whose code it is. A # Technician who knows that it is the sign-in, and not the network or the program, # knows where to look and what to search for. if ($hex.StartsWith('CAA')) { return Get-Text 'Value.Code.Wam.Other' } # An HRESULT of another facility can share its number with no status of Windows, # and must not borrow one's sentence: only a plain status (0xC000....) is asked for. if ($hex.StartsWith('C000')) { return Get-NtStatusText -Code $number } Get-NtStatusText -Code $number -KnownHere } function Get-BitsHttpStatus { <# .SYNOPSIS The HTTP status a BITS result carries - 401 of 0x80190191 - or nothing for any other result. Pure. .DESCRIPTION BG_E_HTTP_ERROR_xxx is 0x8019nnnn with the status in its low word. The one rule Format-BitsResult says a result by and a Judge marks a refusal by. #> [CmdletBinding()] [OutputType([int])] param([AllowNull()]$Code) $number = ConvertTo-CodeNumber -Code $Code -Hex if ($null -eq $number) { return } if (('{0:X8}' -f ($number -band 0xFFFFFFFFL)).StartsWith('8019')) { $status = [int]($number -band 0xFFFF) if ($status -ge 100 -and $status -le 599) { return $status } } } function Format-BitsResult { <# .SYNOPSIS The result of a BITS transfer with what it means: the HTTP status it carries, a reason of BITS's own, or any other HRESULT as Get-HResultText reads it. Nothing for success. Pure. .DESCRIPTION From Microsoft's BITS Return Values. BG_E_HTTP_ERROR_xxx is 0x8019nnnn with the HTTP status in its low word, 0x80190191 for 401 and 0x801901F7 for 503: said as the status, as Format-HttpStatus says every status, "0x80190191: HTTP-Status 401 (Anmeldung verlangt)". The reasons of BITS (0x8020nnnn) named here are those a transfer ends in on the network; another one at least says whose it is. A transfer also ends in a WinHTTP or a Win32 error, 0x80072EE7 for a name that did not resolve, which Get-HResultText knows. The code is written in hexadecimal, as it is looked up, however it came: the event carries 0x80190191 as 2149122449. What is no number is given back as it is. #> [CmdletBinding()] [OutputType([string])] param([AllowNull()]$Code) $number = ConvertTo-CodeNumber -Code $Code -Hex if ($null -eq $number) { return "$Code".Trim() } if ($number -eq 0) { return '' } $hex = '{0:X8}' -f ($number -band 0xFFFFFFFFL) $shown = '0x' + $hex $status = Get-BitsHttpStatus -Code $number if ($null -ne $status) { return ((Get-Text 'Value.Code.Bits.Http') -f $shown, (Format-HttpStatus -Status $status)) } Format-CodeWithMeaning -Code $shown -Meaning (Get-BitsResultText -Code $number) } function Get-BitsResultText { <# .SYNOPSIS What a result of a BITS transfer that carries no HTTP status means: a reason of BITS's own, else any other HRESULT as Get-HResultText reads it. Nothing where nothing is known. Pure. #> [CmdletBinding()] [OutputType([string])] param([AllowNull()]$Code) $number = ConvertTo-CodeNumber -Code $Code -Hex if ($null -eq $number -or $number -eq 0) { return '' } $hex = '{0:X8}' -f ($number -band 0xFFFFFFFFL) $meaning = switch ($hex) { '80200010' { Get-Text 'Value.Code.Bits.NetworkDisconnected' } '80200011' { Get-Text 'Value.Code.Bits.MissingFileSize' } '80200012' { Get-Text 'Value.Code.Bits.HttpVersion' } '80200013' { Get-Text 'Value.Code.Bits.Range' } } if (-not $meaning -and $hex.StartsWith('8020')) { $meaning = Get-Text 'Value.Code.Bits.Other' } if (-not $meaning) { $meaning = Get-HResultText -Code $number } "$meaning" } function Get-AadStsText { <# .SYNOPSIS What an error number of Microsoft Entra ID (AADSTS) means. Nothing for one not known here. Pure. .DESCRIPTION The ones a user's sign-in ends in, as Microsoft documents them under "Microsoft Entra authentication and authorization error codes". 70044 is not in that table: it is the session's counterpart of 70043, as the sign-in itself words it ("The session has expired or is invalid due to sign-in frequency checks by Conditional Access"). #> [CmdletBinding()] [OutputType([string])] param([AllowNull()]$Code) $number = ConvertTo-CodeNumber -Code ("$Code" -replace '(?i)^\s*AADSTS', '') if ($null -eq $number) { return '' } switch ([long]$number) { 50020 { return Get-Text 'Value.Code.Aad.50020' } 50034 { return Get-Text 'Value.Code.Aad.50034' } 50053 { return Get-Text 'Value.Code.Aad.50053' } 50055 { return Get-Text 'Value.Code.Aad.50055' } 50057 { return Get-Text 'Value.Code.Aad.50057' } 50058 { return Get-Text 'Value.Code.Aad.50058' } 50074 { return Get-Text 'Value.Code.Aad.50074' } 50076 { return Get-Text 'Value.Code.Aad.50076' } 50079 { return Get-Text 'Value.Code.Aad.50079' } 50097 { return Get-Text 'Value.Code.Aad.50097' } 50105 { return Get-Text 'Value.Code.Aad.50105' } 50126 { return Get-Text 'Value.Code.Aad.50126' } 50133 { return Get-Text 'Value.Code.Aad.50133' } 50155 { return Get-Text 'Value.Code.Aad.50155' } 50158 { return Get-Text 'Value.Code.Aad.50158' } 50173 { return Get-Text 'Value.Code.Aad.50173' } 53000 { return Get-Text 'Value.Code.Aad.53000' } 53001 { return Get-Text 'Value.Code.Aad.53001' } 53003 { return Get-Text 'Value.Code.Aad.53003' } 65001 { return Get-Text 'Value.Code.Aad.65001' } 70008 { return Get-Text 'Value.Code.Aad.70008' } 70043 { return Get-Text 'Value.Code.Aad.70043' } 70044 { return Get-Text 'Value.Code.Aad.70043' } 135011 { return Get-Text 'Value.Code.Aad.135011' } 700016 { return Get-Text 'Value.Code.Aad.700016' } 700082 { return Get-Text 'Value.Code.Aad.700082' } } '' } function Get-OAuthErrorText { <# .SYNOPSIS What an OAuth error word of Microsoft Entra ID means (invalid_grant and the like). Nothing for one not known here. Pure. .DESCRIPTION The words the token endpoint answers with, as Microsoft documents them for the OAuth 2.0 authorization code flow ("Error codes for token endpoint errors"). The AAD log and dsregcmd /status carry them beside the AADSTS number, which says more. #> [CmdletBinding()] [OutputType([string])] param([AllowNull()][AllowEmptyString()][string]$Code) switch ("$Code".Trim().ToLowerInvariant()) { 'invalid_grant' { return Get-Text 'Value.Code.Wam.InvalidGrant' } 'interaction_required' { return Get-Text 'Value.Code.Wam.InteractionRequired' } 'consent_required' { return Get-Text 'Value.Code.Aad.65001' } 'invalid_client' { return Get-Text 'Value.Code.OAuth.InvalidClient' } 'unauthorized_client' { return Get-Text 'Value.Code.OAuth.UnauthorizedClient' } 'temporarily_unavailable' { return Get-Text 'Value.Code.OAuth.TemporarilyUnavailable' } } '' } function Get-MicrosoftAppText { <# .SYNOPSIS Which program an application id of Microsoft's is, as a Technician knows it. One it does not know is said to be another, with the id; none is said to be none. Pure. .DESCRIPTION Every program that signs in to Microsoft 365 does so as an application with an id of its own, and the sign-in log of Windows names that id with each error. The ids here are the ones that sign in on a Windows PC, as Microsoft's own sign-in documentation and the directory itself name them (collected in the public list merill/microsoft-info, MicrosoftApps.csv, read 2026-10-10; the ones marked there as contributed by the community are Edge, the Office app from the Store, the company portal, the accounts control and the device management client). Outlook as part of Office has no id of its own: it signs in as "Microsoft Office", as Word and Excel do. Which of them it was the log does not say. #> [CmdletBinding()] [OutputType([string])] param([AllowNull()][AllowEmptyString()][string]$ClientId) $id = "$ClientId".Trim().ToLowerInvariant() if (-not $id) { return Get-Text 'Value.Code.App.NotNamed' } switch ($id) { 'd3590ed6-52b3-4102-aeff-aad2292ab01c' { return Get-Text 'Value.Code.App.Office' } '5d661950-3475-41cd-a2c3-d671a3162bc1' { return Get-Text 'Value.Code.App.Outlook' } '27922004-5251-4030-b22d-91ecd9a37ea4' { return Get-Text 'Value.Code.App.OutlookMobile' } 'ab9b8c07-8f02-4f72-87fa-80105867a763' { return Get-Text 'Value.Code.App.OneDrive' } '6a9b9266-8161-4a7b-913a-a9eda19da220' { return Get-Text 'Value.Code.App.OneDrive' } 'b26aadf8-566f-4478-926f-589f601d9c74' { return Get-Text 'Value.Code.App.OneDrive' } 'ecd6b820-32c2-49b6-98a6-444530e5a77a' { return Get-Text 'Value.Code.App.Edge' } 'd7b530a4-7680-4c23-a8bf-c52c121d2e87' { return Get-Text 'Value.Code.App.EdgeNewTab' } '1fec8e78-bce4-4aaf-ab1b-5451cc387264' { return Get-Text 'Value.Code.App.Teams' } '5e3ce6c0-2b1f-4285-8d4b-75ee78787346' { return Get-Text 'Value.Code.App.TeamsWeb' } '29d9ed98-a469-4536-ade2-f981bc1d605e' { return Get-Text 'Value.Code.App.Broker' } 'a40d7d7d-59aa-447e-a655-679a4107e548' { return Get-Text 'Value.Code.App.AccountsControl' } '38aa3b87-a06d-4817-b275-7a316988d93b' { return Get-Text 'Value.Code.App.Hello' } '26a7ee05-5602-4d76-a7ba-eae8b7b67941' { return Get-Text 'Value.Code.App.Search' } '1b3c667f-cde3-4090-b60b-3d2abd0117f0' { return Get-Text 'Value.Code.App.Spotlight' } '4765445b-32c6-49b0-83e6-1d93765276ca' { return Get-Text 'Value.Code.App.OfficeHome' } '0ec893e0-5785-4de6-99da-4ed124e5296c' { return Get-Text 'Value.Code.App.OfficeStore' } 'c0ab8ce9-e9a0-42e7-b064-33d422df41f1' { return Get-Text 'Value.Code.App.Copilot' } 'cf36b471-5b44-428c-9ce7-313bf84528de' { return Get-Text 'Value.Code.App.Bing' } '9ba1a5c7-f17a-4de9-a1f1-6178c8d51223' { return Get-Text 'Value.Code.App.CompanyPortal' } 'fc0f3af4-6835-4174-b806-f7db311fd2f3' { return Get-Text 'Value.Code.App.IntuneAgent' } 'de50c81f-5f80-4771-b66b-cebd28ccdfc1' { return Get-Text 'Value.Code.App.DeviceManagement' } } (Get-Text 'Value.Code.App.Other') -f $id } function Get-RasErrorText { <# .SYNOPSIS What Windows itself says of an error number of its dial-up and VPN networking, in the language Windows runs in. Nothing for a number it has no words for. Reads Windows; changes nothing. .DESCRIPTION The numbers 600 to 999 are not the error numbers of Windows: 809 there is about file metadata, and here it is a VPN server that does not answer. Windows keeps their sentences apart (RasGetErrorString). A VPN also fails with numbers outside that range - 13801, an IKE error - and those are Windows' own. Of a long answer the first sentence is kept. #> [CmdletBinding()] [OutputType([string])] param([AllowNull()]$Code) $number = ConvertTo-CodeNumber -Code $Code if ($null -eq $number -or $number -le 0 -or $number -gt [int]::MaxValue) { return '' } if ($number -lt 600 -or $number -gt 999) { return (Get-Win32ErrorText -Code $number) } try { if (-not ('Gutcheck.RasMessage' -as [type])) { Add-Type -Namespace Gutcheck -Name RasMessage -MemberDefinition @" [DllImport("rasapi32.dll", CharSet = CharSet.Unicode)] public static extern uint RasGetErrorString(uint id, System.Text.StringBuilder buffer, uint size); "@ } $buffer = New-Object System.Text.StringBuilder 1024 if ([Gutcheck.RasMessage]::RasGetErrorString([uint32]$number, $buffer, [uint32]$buffer.Capacity) -ne 0) { return '' } $text = ($buffer.ToString() -replace '\s+', ' ').Trim() if ($text.Length -gt 160) { $end = $text.IndexOf('. ') if ($end -gt 20) { $text = $text.Substring(0, $end + 1) } } $text.TrimEnd('.') } catch { '' } } function Get-TlsAlertText { <# .SYNOPSIS What a TLS alert means, by its number. Nothing for a number not in the standard's list. Pure. .DESCRIPTION Schannel logs "a fatal alert was received: 40" and leaves the forty to the reader. The numbers are those of the TLS standard (RFC 8446, section 6, and the IANA registry of TLS alerts), the same in every version of TLS. #> [CmdletBinding()] [OutputType([string])] param([AllowNull()]$Alert) $number = ConvertTo-CodeNumber -Code $Alert if ($null -eq $number) { return '' } switch ([int]$number) { 10 { return (Get-Text 'Value.Code.TlsAlert.10') } 20 { return (Get-Text 'Value.Code.TlsAlert.20') } 40 { return (Get-Text 'Value.Code.TlsAlert.40') } 42 { return (Get-Text 'Value.Code.TlsAlert.42') } 43 { return (Get-Text 'Value.Code.TlsAlert.43') } 44 { return (Get-Text 'Value.Code.TlsAlert.44') } 45 { return (Get-Text 'Value.Code.TlsAlert.45') } 46 { return (Get-Text 'Value.Code.TlsAlert.46') } 47 { return (Get-Text 'Value.Code.TlsAlert.47') } 48 { return (Get-Text 'Value.Code.TlsAlert.48') } 49 { return (Get-Text 'Value.Code.TlsAlert.49') } 50 { return (Get-Text 'Value.Code.TlsAlert.50') } 51 { return (Get-Text 'Value.Code.TlsAlert.51') } 70 { return (Get-Text 'Value.Code.TlsAlert.70') } 71 { return (Get-Text 'Value.Code.TlsAlert.71') } 80 { return (Get-Text 'Value.Code.TlsAlert.80') } 86 { return (Get-Text 'Value.Code.TlsAlert.86') } 90 { return (Get-Text 'Value.Code.TlsAlert.90') } 112 { return (Get-Text 'Value.Code.TlsAlert.112') } 116 { return (Get-Text 'Value.Code.TlsAlert.116') } 120 { return (Get-Text 'Value.Code.TlsAlert.120') } } '' } function Format-CodeInText { <# .SYNOPSIS A text as an event wrote it, with every error code in it ("0x80200011") followed by what it means, as Format-BitsResult says it. Pure. .DESCRIPTION The message of an event is shown as Windows wrote it, and Windows writes the code alone: "... mit folgendem Fehler: 0x80200011". Only a code shaped as an error is read - an HRESULT or a status of Windows, whose high bit is set: 0x8..., 0xC..., 0xD... - and only where its meaning is known: the time stamp, the offset and the address of a crash are eight hex digits as well, and 0x00000000 is success. The code stays as it was written; one already followed by the very meaning it would get is left as it is. #> [CmdletBinding()] [OutputType([string])] param([AllowNull()][AllowEmptyString()][string]$Text) if (-not $Text) { return "$Text" } [regex]::Replace($Text, '(?<![0-9A-Za-z])0x[89CDcd][0-9A-Fa-f]{7}(?![0-9A-Za-z])', { param($Match) $code = $Match.Value $status = Get-BitsHttpStatus -Code $code $said = $(if ($null -ne $status) { (Get-Text 'Value.Code.Bits.Http') -f $code, (Format-HttpStatus -Status $status) } else { Format-CodeWithMeaning -Code $code -Meaning (Get-BitsResultText -Code $code) }) # Said already: what follows the code is what would be added. $after = $Text.Substring($Match.Index) if ($after.StartsWith($said, [StringComparison]::OrdinalIgnoreCase)) { return $code } $said }) } function Get-DotNetExceptionText { <# .SYNOPSIS What a .NET exception type means, by its name with or without its namespace: "EndpointNotFoundException" or "System.ServiceModel.EndpointNotFoundException". ".NET-Ausnahme" for one not known here; nothing for what is no exception's name. Pure. .DESCRIPTION An application's own log writes the type as the error of a line, and the type alone is a code: a Technician reads "EndpointNotFoundException" and learns no more than that something went wrong. The ones named here are those of the network, the files and the common programming faults, which say where to look. #> [CmdletBinding()] [OutputType([string])] param([AllowNull()][AllowEmptyString()][string]$Name) $short = @("$Name".Trim() -split '[.+]')[-1] if ($short -notmatch '^\w+Exception$') { return '' } switch ($short) { 'EndpointNotFoundException' { return Get-Text 'Value.Code.DotNet.EndpointNotFound' } 'CommunicationException' { return Get-Text 'Value.Code.DotNet.Communication' } 'CommunicationObjectFaultedException' { return Get-Text 'Value.Code.DotNet.CommunicationFaulted' } 'FaultException' { return Get-Text 'Value.Code.DotNet.Fault' } 'ServerTooBusyException' { return Get-Text 'Value.Code.DotNet.ServerTooBusy' } 'SecurityNegotiationException' { return Get-Text 'Value.Code.DotNet.SecurityNegotiation' } 'MessageSecurityException' { return Get-Text 'Value.Code.DotNet.MessageSecurity' } 'AuthenticationException' { return Get-Text 'Value.Code.DotNet.Authentication' } 'WebException' { return Get-Text 'Value.Code.DotNet.Web' } 'HttpRequestException' { return Get-Text 'Value.Code.DotNet.HttpRequest' } 'SocketException' { return Get-Text 'Value.Code.DotNet.Socket' } 'TimeoutException' { return Get-Text 'Value.Code.DotNet.Timeout' } 'TaskCanceledException' { return Get-Text 'Value.Code.DotNet.TaskCanceled' } 'OperationCanceledException' { return Get-Text 'Value.Code.DotNet.TaskCanceled' } 'IOException' { return Get-Text 'Value.Code.DotNet.IO' } 'FileNotFoundException' { return Get-Text 'Value.Code.DotNet.FileNotFound' } 'DirectoryNotFoundException' { return Get-Text 'Value.Code.DotNet.DirectoryNotFound' } 'UnauthorizedAccessException' { return Get-Text 'Value.Code.DotNet.UnauthorizedAccess' } 'SqlException' { return Get-Text 'Value.Code.DotNet.Sql' } 'OutOfMemoryException' { return Get-Text 'Value.Code.DotNet.OutOfMemory' } 'NullReferenceException' { return Get-Text 'Value.Code.DotNet.NullReference' } 'InvalidOperationException' { return Get-Text 'Value.Code.DotNet.InvalidOperation' } 'ObjectDisposedException' { return Get-Text 'Value.Code.DotNet.ObjectDisposed' } 'ArgumentException' { return Get-Text 'Value.Code.DotNet.Argument' } 'ArgumentNullException' { return Get-Text 'Value.Code.DotNet.Argument' } 'COMException' { return Get-Text 'Value.Code.DotNet.Com' } } Get-Text 'Value.Code.DotNet.Unknown' } function Format-DotNetException { <# .SYNOPSIS A .NET exception type with what it means behind it, "EndpointNotFoundException (unter der Adresse antwortet kein Dienst)"; what is no exception's name as it is. Pure. #> [CmdletBinding()] [OutputType([string])] param([AllowNull()][AllowEmptyString()][string]$Name) Format-CodeWithMeaning -Code "$Name".Trim() -Meaning (Get-DotNetExceptionText -Name $Name) } |