Skip to content

Instantly share code, notes, and snippets.

@mklement0
Last active September 22, 2024 04:40

Revisions

  1. mklement0 revised this gist Oct 25, 2022. No changes.
  2. mklement0 revised this gist Oct 25, 2022. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion Get-WinError.ps1
    Original file line number Diff line number Diff line change
    @@ -39,7 +39,7 @@ function Get-WinError {

    <#
    .SYNOPSIS
    Looks up Windows error numbers.
    Looks up information about Windows errors.
    .DESCRIPTION
    Looks up error numbers used by Windows APIs, including HRESULT
  3. mklement0 revised this gist Oct 25, 2022. 1 changed file with 1 addition and 0 deletions.
    1 change: 1 addition & 0 deletions Get-WinError.ps1
    Original file line number Diff line number Diff line change
    @@ -44,6 +44,7 @@ function Get-WinError {
    .DESCRIPTION
    Looks up error numbers used by Windows APIs, including HRESULT
    error / status codes.
    Alternatively, search by (part of) an error's symbolic name.
    On Windows, results are output directly by default, via the Err.exe
    Microsoft CLI that is downloaded on demand.
  4. mklement0 revised this gist Oct 25, 2022. No changes.
  5. mklement0 revised this gist Oct 25, 2022. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion Get-WinError.ps1
    Original file line number Diff line number Diff line change
    @@ -18,7 +18,7 @@
    DOWNLOAD ONLY:
    irm https://gist.github.com/mklement0/0fc086da1af9a72a94cbdb4a59d55230/raw > Get-WinError.ps1
    irm https://gist.github.com/mklement0/0fc086da1af9a72a94cbdb4a59d55230/raw | Set-Content Get-WinError.ps1 -Encoding utf8
    The above downloads to the specified file, which you then need to dot-source to make the function available
    in the current session (again, use 4>$null to silence the guidance information):
  6. mklement0 revised this gist Oct 25, 2022. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion Get-WinError.ps1
    Original file line number Diff line number Diff line change
    @@ -6,7 +6,7 @@
    DOWNLOAD and INSTANT DEFINITION OF THE FUNCTION:
    irm https://gist.github.com/mklement0/0fc086da1af9a72a94cbdb4a59d55230/Get-WinError.ps1 | iex
    irm https://gist.github.com/mklement0/0fc086da1af9a72a94cbdb4a59d55230/raw/Get-WinError.ps1 | iex
    The above directly defines the function below in your session and offers guidance for making it available in future
    sessions too. To silence the guidance information, append 4>$null
  7. mklement0 revised this gist Oct 25, 2022. 1 changed file with 3 additions and 3 deletions.
    6 changes: 3 additions & 3 deletions Get-WinError.ps1
    Original file line number Diff line number Diff line change
    @@ -6,7 +6,7 @@
    DOWNLOAD and INSTANT DEFINITION OF THE FUNCTION:
    irm ???this-Gist-Url/raw/???this-Gist-FileName.ps1 | iex
    irm https://gist.github.com/mklement0/0fc086da1af9a72a94cbdb4a59d55230/Get-WinError.ps1 | iex
    The above directly defines the function below in your session and offers guidance for making it available in future
    sessions too. To silence the guidance information, append 4>$null
    @@ -18,12 +18,12 @@
    DOWNLOAD ONLY:
    irm ???this-Gist-Url/raw > ???this-Gist-FileName.ps1
    irm https://gist.github.com/mklement0/0fc086da1af9a72a94cbdb4a59d55230/raw > Get-WinError.ps1
    The above downloads to the specified file, which you then need to dot-source to make the function available
    in the current session (again, use 4>$null to silence the guidance information):
    . ./???this-Gist-FileName.ps1
    . ./Get-WinError.ps1
    To learn what the function does:
    * see the next comment block
  8. mklement0 created this gist Oct 25, 2022.
    276 changes: 276 additions & 0 deletions Get-WinError.ps1
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,276 @@
    <#
    Prerequisites: Windows PowerShell v5.1 or PowerShell Core (v6+)
    License: MIT
    Author: Michael Klement <mklement0@gmail.com>
    DOWNLOAD and INSTANT DEFINITION OF THE FUNCTION:
    irm ???this-Gist-Url/raw/???this-Gist-FileName.ps1 | iex
    The above directly defines the function below in your session and offers guidance for making it available in future
    sessions too. To silence the guidance information, append 4>$null
    CAVEAT: If you run this command *from a script*, you'll get a spurious warning about dot-sourcing, which you can ignore
    and suppress by appending 3>$null. However, it's best to avoid calling this command from scripts, because later versions
    of this Gist aren't guaranteed to be backward-compatible; howevever, you can modify the command to lock in a
    *specific revision* of this Gist, which is guaranteed not to change: see the instructions at
    https://gist.github.com/mklement0/880624fd665073bb439dfff5d71da886?permalink_comment_id=4296669#gistcomment-4296669
    DOWNLOAD ONLY:
    irm ???this-Gist-Url/raw > ???this-Gist-FileName.ps1
    The above downloads to the specified file, which you then need to dot-source to make the function available
    in the current session (again, use 4>$null to silence the guidance information):
    . ./???this-Gist-FileName.ps1
    To learn what the function does:
    * see the next comment block
    * or, once downloaded and defined, invoke the function with -? or pass its name to Get-Help.
    To define an ALIAS for the function, (also) add something like the following to your $PROFILE:
    Set-Alias gwe Get-WinError
    #>

    function Get-WinError {

    <#
    .SYNOPSIS
    Looks up Windows error numbers.
    .DESCRIPTION
    Looks up error numbers used by Windows APIs, including HRESULT
    error / status codes.
    On Windows, results are output directly by default, via the Err.exe
    Microsoft CLI that is downloaded on demand.
    Use -Online to use the default web browers to show results, courtesy of
    https://hresult.info; implied on Unix-like platforms.
    CAVEATS:
    * Only HRESULT error values produce meaningful results with -Online;
    with simple numbers as such as 0x2, the true result will be buried
    among many false positives.
    The HRESULT equivalent of system error 0x2 (ERROR_FILE_NOT_FOUND) is
    0x80070002.
    * https://hresult.info recognizes FEWER errors than Err.exe
    .EXAMPLE
    Get-WinError -1073740791, 2, 0x80070002
    Looks up the given error numbers, both decimal and hex. forms
    are supported.
    .EXAMPLE
    -1073740791, 0x80070002 | Get-WinError -Online
    Looks up the given error numbers and opens a result page in the
    default web browswer for each.
    NOTE: With -Online, only HRESULT values produce meaningful results.
    .EXAMPLE
    Get-WinError BUFFER_OVER
    Looks for errors whose symbolic name contains substring "BUFFER_OVER".
    Also works with -Online.
    #>

    [CmdletBinding(PositionalBinding = $false)]
    param(
    [Parameter(ValueFromPipeline, Position = 0)]
    [string[]] $ErrorNumberOrName,
    [switch] $Online,
    [switch] $Force # download Err.exe without asking for confirmation, if necessary.
    )

    begin {
    Set-StrictMode -Version 1
    If ($env:OS -eq 'Windows_NT') {
    if (-not $Online) {
    # Make sure that `Err.exe` is in $env:PATH or in a custom location.
    # Otherwise, download on demand.
    $exeFileName = 'Err.exe'
    $customDir = "$HOME\bin"
    $customExePath = "$customDir\$exeFileName"
    $downloadURL = 'https://download.microsoft.com/download/4/3/2/432140e8-fb6c-4145-8192-25242838c542/Err_6.4.5/Err_6.4.5.exe'
    if (-not ($errExe = (Get-Command -ErrorAction Ignore $exeFileName).Path) -and -not ($errExe = (Get-Command -ErrorAction Ignore $customExePath).Path)) {
    # Err.exe must be downloaded.
    if ($Force) {
    $ProgressPreference = 'SilentlyContinue'
    }
    else {
    # Prompt user for permission to download.
    $confirmed = 0 -eq $host.ui.PromptForChoice("Download Required", "OK to download required utility '$exeFileName' to '$customDir', from '$downloadUrl'?", ('&Yes', '&No'), 1)
    if (-not $confirmed) {
    throw "Required download aborted. Use -Online for online lookups, which don't require a download."
    }
    }
    Write-Verbose "Required CLI 'Err.exe' not found; downloading on demand from '$downloadUrl', to '$customDir'."
    $null = New-Item -Type Directory -ErrorAction Stop -Force $customDir # make sure the custom dir. exists.
    Invoke-WebRequest -ErrorAction Stop -OutFile $customExePath $downloadURL
    $errExe = $customExePath
    }
    Write-Verbose "Using CLI '$errExe'"
    }
    }
    else {
    # Unix-like platforms: only online lookups supported.
    if (-not $Online) {
    $Online = $true
    Write-Warning "Non-Windows platform: Assuming -Online; CLI lookups not supported."
    }
    }

    }

    process {
    foreach ($e in $ErrorNumberOrName) {
    if ($Online) {
    # Note: Supports decimal and hex numbers, and symbolic name substrings.
    # !! Use "www.", as only then are query strings properly handled (as of 25 Oct 2022).
    Start-Process "https://www.hresult.info/Search?q=$e"
    }
    else {
    # If the argument cannot be parsed as an [int], assume it is
    # a symbolic name (substring), which requires prefixing with ":" for Err.exe
    # (While prefix "=" is also supported for matching *in full*, we don't bother surfacing
    # this functionality as well, given that it's unlikely that full symbolic names are substrings of others)
    if (-not ($e -as [int])) {
    $e = ':' + $e
    }
    # Sample XML to parse; there may be *multiple* <err> elements:
    # <?xml version="1.0" standalone="no"?>
    # <ErrV1>
    # <err n='0xc0000409' name='STATUS_STACK_BUFFER_OVERRUN' src='ntstatus.h'>The system detected an overrun of a stack-based buffer in this application. This overrun could potentially allow a malicious user to gain control of this application.</err>
    # </ErrV1>
    # If no info was found, there is *no* <err> element.
    $results = ([xml] (& $errExe /:xml $e)).SelectNodes('//err')

    if ($results.Count -eq 0) {
    # No results found.
    $PSCmdlet.WriteError(
    [System.Management.Automation.ErrorRecord]::new(
    "No information about error '$e' found.",
    'UnknownErrorNumber',
    'InvalidArgument',
    $e
    )
    )
    }
    else {
    # Transform the XML results to [pscustomobject] instances.
    foreach ($el in $results) {
    # Note:
    [pscustomobject] @{
    HexNumber = '0x{0:X}' -f [int] $el.n
    Name = $el.Name
    Source = $el.src
    Message = $el.InnerText -replace '\r?\n', ' ' # some messages contain newlines
    }
    }
    }
    }
    }

    }

    } # function Get-WinError


    # --------------------------------
    # GENERIC INSTALLATION HELPER CODE
    # --------------------------------
    # Provides guidance for making the function persistently available when
    # this script is either directly invoked from the originating Gist or
    # dot-sourced after download.
    # IMPORTANT:
    # * DO NOT USE `exit` in the code below, because it would exit
    # the calling shell when Invoke-Expression is used to directly
    # execute this script's content from GitHub.
    # * Because the typical invocation is DOT-SOURCED (via Invoke-Expression),
    # do not define variables or alter the session state via Set-StrictMode, ...
    # *except in child scopes*, via & { ... }
    if ($MyInvocation.Line -eq '') {
    # Most likely, this code is being executed via Invoke-Expression directly
    # from gist.github.com
    # To simulate for testing with a local script, use the following:
    # Note: Be sure to use a path and to use "/" as the separator.
    # iex (Get-Content -Raw ./script.ps1)
    # Derive the function name from the invocation command, via the enclosing
    # script name presumed to be contained in the URL.
    # NOTE: Unfortunately, when invoked via Invoke-Expression, $MyInvocation.MyCommand.ScriptBlock
    # with the actual script content is NOT available, so we cannot extract
    # the function name this way.
    & {

    param($invocationCmdLine)

    # Try to extract the function name from the URL.
    $funcName = $invocationCmdLine -replace '^.+/(.+?)(?:\.ps1).*$', '$1'
    if ($funcName -eq $invocationCmdLine) {
    # Function name could not be extracted, just provide a generic message.
    # Note: Hypothetically, we could try to extract the Gist ID from the URL
    # and use the REST API to determine the first filename.
    Write-Verbose -Verbose "Function is now defined in this session."
    }
    else {
    # Indicate that the function is now defined and also show how to
    # add it to the $PROFILE or convert it to a script file.
    Write-Verbose -Verbose @"
    Function `"$funcName`" is now defined in this session.
    * If you want to add this function to your `$PROFILE, run the following:
    "``nfunction $funcName {``n`${function:$funcName}``n}" | Add-Content `$PROFILE
    * If you want to convert this function into a script file that you can invoke
    directly, run:
    "`${function:$funcName}" | Set-Content $funcName.ps1 -Encoding $('utf8' + ('', 'bom')[[bool] (Get-Variable -ErrorAction Ignore IsCoreCLR -ValueOnly)])
    "@
    }
    } $MyInvocation.MyCommand.Definition # Pass the original invocation command line to the script block.
    }
    else {
    # Invocation presumably as a local file after manual download,
    # either dot-sourced (as it should be) or mistakenly directly.
    & {
    param($originalInvocation)
    # Parse this file to reliably extract the name of the embedded function,
    # irrespective of the name of the script file.
    $ast = $originalInvocation.MyCommand.ScriptBlock.Ast
    $funcName = $ast.Find( { $args[0] -is [System.Management.Automation.Language.FunctionDefinitionAst] }, $false).Name
    if ($originalInvocation.InvocationName -eq '.') {
    # Being dot-sourced as a file.

    # Provide a hint that the function is now loaded and provide
    # guidance for how to add it to the $PROFILE.
    Write-Verbose -Verbose @"
    Function `"$funcName`" is now defined in this session.
    If you want to add this function to your `$PROFILE, run the following:
    "``nfunction $funcName {``n`${function:$funcName}``n}" | Add-Content `$PROFILE
    "@
    }
    else {
    # Mistakenly directly invoked.
    # Issue a warning that the function definition didn't take effect and
    # provide guidance for reinvocation and adding to the $PROFILE.
    Write-Warning @"
    This script contains a definition for function "$funcName", but this definition
    only takes effect if you dot-source this script.
    To define this function for the current session, run:
    . "$($originalInvocation.MyCommand.Path)"
    "@
    }
    } $MyInvocation # Pass the original invocation info to the helper script block.
    }