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 '' }
    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-HttpStatusText {
    <#
    .SYNOPSIS
        What an HTTP status means, in a Technician's words. Nothing for one this has no
        word for and whose class says nothing either. Pure.
    #>

    [CmdletBinding()]
    [OutputType([string])]
    param([AllowNull()]$Status)

    $number = ConvertTo-CodeNumber -Code $Status
    if ($null -eq $number) { return '' }
    switch ([int]$number) {
        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 ($number -ge 300 -and $number -lt 400) { return Get-Text 'Value.Code.Http.3xx' }
    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. Pure.
    #>

    [CmdletBinding()]
    [OutputType([string])]
    param([AllowNull()]$Status)

    Format-CodeWithMeaning -Code "$Status" -Meaning (Get-HttpStatusText -Status $Status)
}

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.
    #>

    [CmdletBinding()]
    [OutputType([string])]
    param([AllowNull()]$Code)

    $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' }
    }
    ''
}

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' }
    }
    if ($hex.StartsWith('8007')) { return Get-Win32ErrorText -Code ($number -band 0xFFFF) }
    switch ($hex) {
        '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' }
    }
    # 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' }
    Get-NtStatusText -Code $number
}

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".
    #>

    [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.50076' }
        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' }
    }
    ''
}