Public/Import-WsusUpdate.ps1

<#
    .SYNOPSIS
    Imports updates from the Microsoft Update Catalog into your local WSUS server.
 
    .DESCRIPTION
    The Import-WsusUpdate cmdlet imports updates from the Microsoft Update Catalog into your local WSUS server.
 
    By default, Import-WsusUpdate will download the update files required for this update to a temporary folder in the TEMP folder (%TEMP%\Import-WsusUpdate) and import them into the WSUS server.
 
    The update files will be removed from the temporary folder after successfully importing the update into the WSUS server.
 
    The download path may be changed to a different location using the -DownloadPath parameter of the cmdlet. When using the -DownloadPath parameter, the update files in the folder will not be removed after import. This can be useful for downloading the update files once, and importing them on multiple WSUS servers.
 
    .EXAMPLE
    Import-WsusUpdate "0373ad3c-3f8f-4123-81f8-6d3d8cd86f6d"
    Description: Downloads the update files for the update and imports the metadata for the update into the local WSUS server.
 
    The update files will be stored on the local WSUS server (similar to how WSUS will download the files when updates are approved/required).
 
    The UpdateID of an update on the Microsoft Update Catalog can be found on the Update Details page for the update.
 
    .EXAMPLE
    Import-WsusUpdate "0373ad3c-3f8f-4123-81f8-6d3d8cd86f6d" -MetadataOnly
    Description: Imports only the metadata for the update into the local WSUS server.
 
    The update files will be downloaded later by WSUS based on the WSUS server's settings.
 
    The UpdateID of an update on the Microsoft Update Catalog can be found on the Update Details page for the update.
 
    .EXAMPLE
    Import-WsusUpdate -KB "KB5026454"
    Description: Searches the Microsoft Update Catalog for the update with KB number 5026454.
 
    If only one result (update) is found, downloads the update files for the update and imports the metadata for the update into the local WSUS server. The update files will be stored on the local WSUS server (similar to how WSUS will download the files when updates are approved/required).
 
    If more than one result (update) is found, the cmdlet will halt execution. The -Filter parameter will need to be used to further refine the search results, or one of the architecture parameters (-x86, -x64, -ARM64) may be used.
 
    Alternatively, the UpdateID can be used instead of filtering the results, and may be required if multiple updates with the exact same title have been released (e.g.: 2023-07 Cumulative Update Preview for .NET Framework 3.5 and 4.8.1 for Windows 11, version 22H2 for x64 (KB5028017)).
 
    .EXAMPLE
    Import-WsusUpdate -KB "KB5026454" -MetadataOnly
    Description: Searches the Microsoft Update Catalog for the update with KB number 5026454.
 
    If only one result (update) is found, imports only the metadata for the update into the local WSUS server. The update files will be downloaded later by WSUS based on the WSUS server's settings.
 
    If more than one result (update) is found, the cmdlet will halt execution. The -Filter parameter will need to be used to further refine the search results, or one of the architecture parameters (-x86, -x64, -ARM64) may be used.
 
    Alternatively, the UpdateID can be used instead of filtering the results, and may be required if multiple updates with the exact same title have been released (e.g.: 2023-07 Cumulative Update Preview for .NET Framework 3.5 and 4.8.1 for Windows 11, version 22H2 for x64 (KB5028017)).
 
    .EXAMPLE
    Import-WsusUpdate -KB "KB5029244" -MetadataOnly -x64 -Filter "21H2","-Dynamic"
    Description: Searches the Microsoft Update Catalog for the update with KB number KB5029244, and the filters "21H2", "-Dynamic" and "x64".
 
    Only the metadata for the update will be imported into the local WSUS server.
 
    The update files will be downloaded later by WSUS based on the WSUS server's settings.
 
    .EXAMPLE
    Import-WsusUpdate "0373ad3c-3f8f-4123-81f8-6d3d8cd86f6d" -DownloadPath "C:\MUCatalogUpdateFiles"
    Description: Downloads the update files for the update to C:\MUCatalogUpdateFiles and imports the metadata for the update into the local WSUS server.
 
    The update files will be stored on the local WSUS server (similar to how WSUS will download the files when updates are approved/required).
 
    If the update files are already present in the folder specified by the -DownloadPath parameter, the download(s) for the existing file(s) will be skipped. The update files will not be automatically removed after importing them into the WSUS server.
 
    The UpdateID of an update on the Microsoft Update Catalog can be found on the Update Details page for the update.
 
    .EXAMPLE
    Import-WsusUpdate "0373ad3c-3f8f-4123-81f8-6d3d8cd86f6d" -DownloadPath "C:\MUCatalogUpdateFiles" -DownloadInParallel
    Description: Downloads the update files for the update to C:\MUCatalogUpdateFiles and imports the metadata for the update into the local WSUS server.
 
    The update files will be stored on the local WSUS server (similar to how WSUS will download the files when updates are approved/required).
 
    If the update files are already present in the folder specified by the -DownloadPath parameter, the download(s) for the existing file(s) will be skipped. The update files will not be automatically removed after importing them into the WSUS server.
 
    The update files will be downloaded in parallel, with a maximum of 4 active downloads active at once.
 
    The UpdateID of an update on the Microsoft Update Catalog can be found on the Update Details page for the update.
 
    .NOTES
    The Import-WsusUpdate cmdlet requires an internet connection to import update metadata from the Microsoft Update upstream server.
 
    The Import-WsusUpdate cmdlet will not work on WSUS servers syncing from an upstream server other than Microsoft Update, unless the AllowCatalogImportOnDSS registry key has been set. This is a restriction from the WSUS API.
 
    If the WSUS server is configured to use a proxy, the Import-WsusUpdate cmdlet will respect the WSUS proxy configuration and use the proxy when connecting to the Internet and downloading update files when importing updates.
 
    Update files are downloaded one at a time by default, unless the -DownloadInParallel switch is passed.
 
    Currently remote servers are not supported, but this functionality is planned for subsequent releases.
 
    .INPUTS
    This cmdlet does not currently support piping input.
 
    .OUTPUTS
    Nothing.
#>

function Import-WsusUpdate {
    [CmdletBinding(DefaultParameterSetName = "UpdateID")]
    param (
        [Parameter(Mandatory, ParameterSetName = "UpdateID", Position = 0,
            HelpMessage = "The UpdateID (GUID) of the Microsoft Update Catalog update to import.")]
        [ValidatePattern("[a-zA-Z\d]{8}-(?:[a-zA-Z\d]{4}-){3}[a-zA-Z\d]{12}")]
        [string]
        $UpdateID,

        [Parameter(Mandatory, ParameterSetName = "KB",
            HelpMessage = "The KB of the update to download. Since multiple updates may share the same KB (e.g.: 64-bit and 32-bit release of the same KB) additional parameters may need to be used to narrow down the search.")]
        [ValidatePattern("^KB\d{7}$")]
        [string]
        $KB,

        [Parameter(Mandatory, ParameterSetName = "Uri",
            HelpMessage = "The URI in the address bar for this update. It must contain the following text: ScopedViewInline.aspx?updateid=")]
        [ValidatePattern(".*?ScopedViewInline\.aspx\?updateid=([a-zA-Z\d]{8}-(?:[a-zA-Z\d]{4}-){3}[a-zA-Z\d]{12}).*")]
        [string]
        $Uri,

        [Parameter(ParameterSetName = "KB",
            HelpMessage = "Any additional keywords to include in the search to filter matching updates (e.g.: 'x86-based')")]
        [AllowNull()]
        [AllowEmptyCollection()]
        [string[]]
        $Filter,

        [Parameter(ParameterSetName = "KB",
            HelpMessage = "Adds 'x64' to the list of filters used when searching for updates to filter out non-64bit updates. This is the equivalent of adding 'x64' to -Filter.")]
        [Alias("64Bit")]
        [switch]
        $x64,

        [Parameter(ParameterSetName = "KB",
            HelpMessage = "Adds 'x86' to the list of filters used when searching for updates to filter out non-32bit updates. This is the equivalent of adding 'x86' to -Filter.")]
        [Alias("32Bit")]
        [switch]
        $x86,

        [Parameter(ParameterSetName = "KB",
            HelpMessage = "Adds 'ARM64' to the list of filters used when searching for updates to filter out non-ARM64 updates. This is the equivalent of adding 'ARM64' to -Filter.")]
        [switch]
        $ARM64,

        [Parameter(HelpMessage = "The path to save the update files required by the update. When specified, the downloaded update files will not be removed after being successfully imported into the WSUS server, allowing you to import the files on another WSUS server without re-downloading the update files.")]
        [string]
        $DownloadPath,

        [Parameter(HelpMessage = "If update files should be downloaded simultaneously. By default update files are downloaded one at a time.")]
        [switch]
        $DownloadInParallel,

        [Parameter(HelpMessage = "If only the metadata of the update should be imported into the WSUS server. The WSUS server will determine when to download the files required for this file (e.g.: when the update is approved).")]
        [Alias("SkipDownload")]
        [switch]
        $MetadataOnly,

        [Parameter(HelpMessage = "For diagnosing errors. A PowerShell transcript will be started for troubleshooting, and relevant WSUS/system information will be displayed at the end of the script's execution.")]
        [switch]
        $HelpMe,

        [Parameter(HelpMessage = "Removes the larger AJ Tek logo from the script's output.")]
        [switch]
        $NoLogo
    )

    if ($PSVersionTable.PSEdition -eq "Core") {
        Write-Error -Message "PowerShell Core is not supported. Please use Windows PowerShell to run this cmdlet." `
            -Category InvalidOperation `
            -ErrorId "PowerShellCoreIsNotSupported"
        return
    }

    if (!$Script:WarnedPSVersion -and $PSVersionTable.PSVersion.Major -lt 4) {
        Write-Warning "Only PowerShell 4.0 and PowerShell 5.1 are officially supported."
        Write-Warning "It is possible that the cmdlet will work on other PowerShell versions, but it is highly recommended that you use one of the officially supported versions."

        # We shouldn't pester the user - only warn them once.
        $Script:WarnedPSVersion = $true
    }

    if ([System.Environment]::Is64BitOperatingSystem -and ![System.Environment]::Is64BitProcess) {
        Write-Warning "Running in a 32-bit process on a 64-bit machine. If you encounter errors, please try using the 64-bit version of PowerShell to run the cmdlet."
    }

    try {
        $wsus = $null
        $wsusConfig = $null
        $removeFilesAfterImport = $false

        if ($HelpMe) {
            if ([string]::IsNullOrWhiteSpace($env:TEMP)) {
                Write-Warning "Environmental TEMP variable was empty: starting HelpMe transcript in its default location."
                Start-Transcript -ErrorAction Stop
            } else {
                Start-Transcript -Path "${env:TEMP}\Import-WsusUpdate-$(Get-Date -f "yyyy.MM.dd-HH.mm.ss")-HelpMe.txt" -ErrorAction Stop
            }

            $VerbosePreference = "Continue"
            Write-Verbose "Verbose output will be displayed since we are in a HelpMe context."
        }

        try {
            # Ensure we are executing from an elevated process.
            if (!(TestElevated)) {
                Write-Error -Message "This cmdlet must be ran as an administrator." `
                    -Category PermissionDenied `
                    -ErrorId "CmdletRequiresElevation" `
                    -RecommendedAction "Run the command again from an elevated PowerShell window."
                return
            }

            if ($NoLogo) {
                Write-Output "Brought to you by: AJ Tek Corporation"
            } else {
                Write-Output "============================="
                Write-Output "Brought to you by:"
                Write-Output "AJ Tek Corporation"
                Write-Output "https://www.ajtek.ca"
                Write-Output "============================="
            }

            if (!$MetadataOnly) {
                # Ensure that we have a valid download path to save the update files to.
                if (!$PSBoundParameters.ContainsKey("DownloadPath")) {
                    $removeFilesAfterImport = $true
                    Write-Verbose "Using temporary folder to store update files since user didn't provide a download path."
                    $tempPath = $env:TEMP
                    $DownloadPath = Resolve-Path -Path $tempPath -ErrorAction "SilentlyContinue"

                    if ([string]::IsNullOrWhiteSpace($DownloadPath)) {
                        Write-Error -Message "The -DownloadPath parameter was not passed and the environmental TEMP variable ($tempPath) didn't point to an existing folder. Please use the -DownloadPath parameter and provide a path to a folder instead." `
                            -Category "ObjectNotFound" `
                            -ErrorId "EnvTempFolderNotFound" `
                            -TargetObject $tempPath
                        return
                    }

                    $DownloadPath = Join-Path $DownloadPath "Import-WsusUpdate"
                }

                if (!(Test-Path $DownloadPath)) {
                    Write-Verbose "Download path doesn't exist; creating it now..."

                    try {
                        [void](New-Item -Path $DownloadPath -ItemType Directory -ErrorAction Stop)
                    } catch {
                        Write-Warning "Couldn't create the download folder at $DownloadPath!"
                        Write-Warning "Please manually create this folder."
                        throw
                    }
                }

                Write-Output "Update files will be saved in $DownloadPath."
            }
        } catch {
            Write-Warning "Failed cmdlet startup with the following error:"
            throw
        }

        try {
            # Connect to the local WSUS server and retrieve its configuration settings.
            Write-Output "Connecting to the local WSUS server..."

            if ($null -ne (Get-Command -Name "Get-WsusServer" -ErrorAction Ignore)) {
                Write-Verbose "Connecting to the local WSUS server via the Get-WsusServer cmdlet."
                $wsus = Get-WsusServer -ErrorAction Stop
            } else {
                Write-Verbose "Get-WsusServer cmdlet was not available. Connecting to the local WSUS server via AdminProxy::GetUpdateServer."
                [void]([System.Reflection.Assembly]::LoadWithPartialName("Microsoft.UpdateServices.Administration"))
                $wsus = [Microsoft.UpdateServices.Administration.AdminProxy]::GetUpdateServer()
            }

            Write-Verbose "Retrieving WSUS configuration..."
            $wsusConfig = $wsus.GetConfiguration()

            if (!$wsusConfig.SyncFromMicrosoftUpdate) {
                $allowCatalogImportOnDSS = Get-ItemProperty -Path "HKLM:\Software\Microsoft\Update Services\Server\Setup" `
                    -Name "AllowCatalogImportOnDSS" `
                    -ErrorAction SilentlyContinue
                $allowCatalogImportOnDSS32 = Get-ItemProperty -Path "HKLM:\Software\Wow6432Node\Microsoft\Update Services\Server\Setup" `
                    -Name "AllowCatalogImportOnDSS" `
                    -ErrorAction SilentlyContinue

                if ($null -eq $allowCatalogImportOnDSS -and $null -eq $allowCatalogImportOnDSS32) {
                    Write-Error -Message "Cannot import updates as WSUS is syncing from an upstream server other than Microsoft Update." `
                        -Category InvalidOperation `
                        -ErrorId "CouldNotImportUpdateUSSNotMicrosoftUpdate"
                    return
                } else {
                    Write-Verbose "AllowCatalogImportOnDSS registry key exists; ignoring the fact that WSUS is set to sync from an upstream other than Microsoft Update."
                }
            }

            if (!(InitializeProxySettings -WsusConfig $wsusConfig -ErrorAction Stop)) {
                Write-Error -Message "WSUS proxy settings are set to an unsupported configuration." `
                    -Category InvalidOperation `
                    -ErrorId "CouldNotValidateProxySettings"
                return
            }
        } catch {
            Write-Warning "Failed establishing a valid connection to the WSUS server!"
            throw
        }

        $warnSchUseStrongCrypto = $false
        $warnSchUseStrongCrypto32 = $false
        $schUseStrongCrypto = Get-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\.NETFramework\v4.0.30319" -Name "SchUseStrongCrypto" -ErrorAction Ignore

        if ($null -eq $schUseStrongCrypto -or $schUseStrongCrypto.SchUseStrongCrypto -ne "1") {
            Write-Verbose "SchUseStrongCrypto registry entry was missing or not set to its recommended value."
            $warnSchUseStrongCrypto = $true
        }

        if (Test-Path "HKLM:\SOFTWARE\Wow6432Node") {
            $schUseStrongCrypto32 = Get-ItemProperty -Path "HKLM:\SOFTWARE\Wow6432Node\Microsoft\.NetFramework\v4.0.30319" -Name "SchUseStrongCrypto" -ErrorAction Ignore

            if ($null -eq $schUseStrongCrypto32 -or $schUseStrongCrypto32.SchUseStrongCrypto -ne "1") {
                Write-Verbose "SchUseStrongCrypto registry entry (32-bit) was missing or not set to its recommended value."
                $warnSchUseStrongCrypto32 = $true
            }
        }

        if ($warnSchUseStrongCrypto -or $warnSchUseStrongCrypto32) {
            if ($warnSchUseStrongCrypto -and $warnSchUseStrongCrypto32) {
                $warnSchUseStrongCryptoWording = "both the 64-bit and 32-bit .NET Framework registry keys"
            } elseif ($warnSchUseStrongCrypto32) {
                $warnSchUseStrongCryptoWording = "the 32-bit .NET Framework registry key"
            } else {
                $warnSchUseStrongCryptoWording = "the .NET Framework registry key"
            }

            Write-Warning "The SchUseStrongCrypto registry entry was missing in $warnSchUseStrongCryptoWording. This may cause networking-related issues when executing the script, and it is recommended that you set the SchUseStrongCrypto registry value to 1."
            Write-Warning "For WAM users, this can be accomplished by opening the WAM Shell and running: Set-SchUseStrongCrypto -Value 1"
        }

        $resolvedUpdateId = ""
        $updateFilePaths = New-Object -TypeName "System.Collections.Generic.List[string]"

        if ($PSBoundParameters.ContainsKey("KB")) {
            try {
                $mergedFilterList = New-Object -TypeName "System.Collections.Generic.List[string]"

                if ($x86) { [void]($mergedFilterList.Add("x86")) }
                if ($x64) { [void]($mergedFilterList.Add("x64")) }
                if ($ARM64) { [void]($mergedFilterList.Add("ARM64")) }
                if ($Filter) { $mergedFilterList.AddRange($Filter) }

                if ($mergedFilterList) {
                    $filtersUsed = " using the filters: $( [string]::Join(", ", $mergedFilterList) )"
                } else {
                    $filtersUsed = ""
                }

                Write-Verbose "Searching the Microsoft Update Catalog for the UpdateID of KB `"$KB`"${filtersUsed}."
                $resolvedUpdateId = GetUpdateIDFromKB -KB $KB -Filter $mergedFilterList.ToArray() -ErrorAction Stop
                Write-Verbose "KB $KB resolved to UpdateID $resolvedUpdateID."
            } catch {
                if ($_.FullyQualifiedErrorId -eq "MultipleUpdatesFound,GetUpdateIDFromKB") {
                    Write-Warning "Found multiple updates while searching for KB $KB."
                    Write-Warning "Please use the -Filter parameter to narrow your search, or use the update's UpdateID with the -UpdateID parameter of the cmdlet."
                } else {
                    Write-Warning "Couldn't find UpdateID from KB: $_"
                }

                throw
            }
        } elseif ($PSBoundParameters.ContainsKey("Uri")) {
            if ($Uri -notmatch ".*updateid=([a-zA-Z\d]{8}-(?:[a-zA-Z\d]{4}-){3}[a-zA-Z\d]{12}).*") {
                throw "-Uri parameter didn't match the expected format and the UpdateID could not be resolved. Please find the UpdateID manually, or use the -KB switch."
            }

            $resolvedUpdateId = $Matches[1]
        } else {
            $resolvedUpdateId = $UpdateID
        }

        if (!$MetadataOnly) {
            Write-Output "Getting information about the update we're going to import from the Microsoft Update Catalog..."

            if ($PSBoundParameters.ContainsKey("Uri")) {
                $targetUri = $Uri
            } else {
                $targetUri = "https://www.catalog.update.microsoft.com/ScopedViewInline.aspx?updateid=$resolvedUpdateId"
            }

            try {
                $updateInfo = GetMUCatalogUpdateInfo -Uri $targetUri -ErrorAction Stop
            } catch {
                Write-Warning "Failed to retrieve update information:"
                throw
            }

            $resolvedUpdateId = $updateInfo["UpdateID"]
            $parsedFileInfoList = $updateInfo["Files"]
            $totalUpdateFiles = $parsedFileInfoList.Count
            Write-Output "Found $totalUpdateFiles file$( if ($totalUpdateFiles -gt 1) { "s" } ) to download."
            Write-Output "Checking to ensure all files are available for download..."

            # Check to ensure that all update files are available before attempting to download them.
            foreach ($parsedFileInfo in $parsedFileInfoList) {
                $fileDownloadUri = $parsedFileInfo["Uri"]
                $fileName = $parsedFileInfo["FileName"]
                Write-Verbose "Checking to see if update file $fileName at $fileDownloadUri is available..."
                $available, $responseError = TestDownloadAvailable -Uri $fileDownloadUri

                if (!$available) {
                    Write-Error -Message "File $fileName (URI $fileDownloadUri) was not available for download: $responseError" `
                        -Category "ResourceUnavailable" `
                        -ErrorId "UpdateFileDownloadNotAvailable" `
                        -TargetObject $fileDownloadUri
                    return
                }
            }

            try {
                Write-Verbose "Downloading update files..."
                $downloadFilesParams = @{
                    "FileInfoList" = $parsedFileInfoList
                    "OutputPath" = $DownloadPath
                }

                if ($DownloadInParallel) {
                    DownloadFilesInParallel @downloadFilesParams -ErrorAction Stop
                } else {
                    DownloadFilesSequentially @downloadFilesParams -ErrorAction Stop
                }
            } catch {
                Write-Warning "Failed downloading update files with the following error: $_"
                throw
            }

            foreach ($parsedFileInfo in $parsedFileInfoList) {
                $fileDownloadPath = Join-Path $DownloadPath $parsedFileInfo["FileName"] -ErrorAction Stop
                [void]($updateFilePaths.Add($fileDownloadPath))
            }
        }

        try {
            $importedFiles = $false

            if ($MetadataOnly) {
                Write-Output "Importing metadata for update with ID $resolvedUpdateId."
            } else {
                Write-Output "Importing downloaded update into the local WSUS server."
            }

            $wsus.ImportUpdateFromCatalogSite($resolvedUpdateId, $updateFilePaths.ToArray())
            Write-Output "Successfully imported update $resolvedUpdateId into the local WSUS server."
            $importedFiles = $true
        } catch {
            Write-Warning "Failed importing the update into the local WSUS server."
            Write-Warning "Please note that due to WSUS importing update metadata from the upstream server, the import will likely fail on disconnected systems syncing from Microsoft Update."
            Write-Warning "The error was:"
            $_ | Out-String | Write-Error
            $confirmation = Read-Host -Prompt "Would you like to open the SoftwareDistribution.log file for manual error checking? (Y/n)"

            if ([string]::IsNullOrWhiteSpace($confirmation) -or $confirmation.Trim() -eq "y") {
                Start-Process -FilePath "${env:ProgramFiles}\Update Services\LogFiles\SoftwareDistribution.log" -ErrorAction Continue
            }
        } finally {
            if (!$MetadataOnly -and $removeFilesAfterImport) {
                if ($importedFiles) {
                    # TODO: CTRL-C abort Write-Output seems wack, Write-Host instead?
                    Write-Output "Removing downloaded update files in $DownloadPath since they have been copied to the WSUSContent folder..."
                    $updateFilePaths | ForEach-Object {
                        Write-Verbose "Deleting file $_..."
                        Remove-Item -Path $_ -ErrorAction Continue
                    }
                } else {
                    Write-Warning "The downloaded update files will remain in the download folder $DownloadPath until they are successfully imported or manually removed."
                }
            }
        }
    } finally {
        if ($HelpMe) {
            Write-Output ""
            Write-Output "============================="
            Write-Output "Is this a 64 Bit Process? $([Environment]::Is64BitProcess)"
            Write-Output "============================="

            Write-Output ""
            Write-Output "============================="
            Write-Output "Operating System Information"
            Write-Output "============================="
            $CimInstanceOperatingSystem = (Get-CimInstance Win32_OperatingSystem -Verbose:$false) | Select-Object *
            ($CimInstanceOperatingSystem | Out-String).Trim()

            Write-Output ""
            Write-Output "============================="
            Write-Output "Computer System Information"
            Write-Output "============================="
            $CimInstanceComputerSystem = (Get-CimInstance Win32_ComputerSystem -Verbose:$false) | Select-Object *
            ($CimInstanceComputerSystem | Out-String).Trim()

            Write-Output ""
            Write-Output "============================="
            Write-Output "WSUS Configuration"
            Write-Output "============================="

            if ($null -ne $wsusConfig) {
                $wsusConfig | Select-Object * -ExcludeProperty ProxyPassword | Format-List -Force | Out-String -Stream | Sort-Object | Where-Object { ![string]::IsNullOrWhiteSpace($_) }
            } else {
                Write-Output "Not available (WSUS configuration wasn't retrieved)."
            }

            Write-Output ""
            Write-Output "============================="
            Write-Output "Proxy Information"
            Write-Output "============================="
            Write-Output "Proxy: $(netsh winhttp show proxy)"
            $proxyEnable = Get-ItemProperty -Path 'HKCU:\Software\Microsoft\Windows\CurrentVersion\Internet Settings' -Name 'ProxyEnable' -ErrorAction SilentlyContinue

            if ($proxyEnable) {
                if (($proxyEnable | Select-Object -ExpandProperty 'ProxyEnable') -eq '1') {
                    try {
                        Get-ItemProperty -Path 'HKCU:\Software\Microsoft\Windows\CurrentVersion\Internet Settings' -Name 'ProxyOverride' -ErrorAction SilentlyContinue | Select-Object -ExpandProperty 'ProxyOverride'
                    } catch {
                        Write-Output "Bypass proxy server for local addresses is turned off."
                    }
                } else {
                    Write-Output "A proxy server has not been enabled on this user."
                }
            } else {
                Write-Output "ProxyEnable registry key didn't exist. A proxy server has not been enabled on this user."
            }

            Stop-Transcript -ErrorAction Continue
        }
    }
}

# Copyright (c) 2023 AJ Tek Corporation. All Rights Reserved.

# SIG # Begin signature block
# MIIVmwYJKoZIhvcNAQcCoIIVjDCCFYgCAQExCzAJBgUrDgMCGgUAMGkGCisGAQQB
# gjcCAQSgWzBZMDQGCisGAQQBgjcCAR4wJgIDAQAABBAfzDtgWUsITrck0sYpfvNR
# AgEAAgEAAgEAAgEAAgEAMCEwCQYFKw4DAhoFAAQUskcJGMNAD98tsQ22VmldxQn4
# StegghH7MIIFbzCCBFegAwIBAgIQSPyTtGBVlI02p8mKidaUFjANBgkqhkiG9w0B
# AQwFADB7MQswCQYDVQQGEwJHQjEbMBkGA1UECAwSR3JlYXRlciBNYW5jaGVzdGVy
# MRAwDgYDVQQHDAdTYWxmb3JkMRowGAYDVQQKDBFDb21vZG8gQ0EgTGltaXRlZDEh
# MB8GA1UEAwwYQUFBIENlcnRpZmljYXRlIFNlcnZpY2VzMB4XDTIxMDUyNTAwMDAw
# MFoXDTI4MTIzMTIzNTk1OVowVjELMAkGA1UEBhMCR0IxGDAWBgNVBAoTD1NlY3Rp
# Z28gTGltaXRlZDEtMCsGA1UEAxMkU2VjdGlnbyBQdWJsaWMgQ29kZSBTaWduaW5n
# IFJvb3QgUjQ2MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAjeeUEiIE
# JHQu/xYjApKKtq42haxH1CORKz7cfeIxoFFvrISR41KKteKW3tCHYySJiv/vEpM7
# fbu2ir29BX8nm2tl06UMabG8STma8W1uquSggyfamg0rUOlLW7O4ZDakfko9qXGr
# YbNzszwLDO/bM1flvjQ345cbXf0fEj2CA3bm+z9m0pQxafptszSswXp43JJQ8mTH
# qi0Eq8Nq6uAvp6fcbtfo/9ohq0C/ue4NnsbZnpnvxt4fqQx2sycgoda6/YDnAdLv
# 64IplXCN/7sVz/7RDzaiLk8ykHRGa0c1E3cFM09jLrgt4b9lpwRrGNhx+swI8m2J
# mRCxrds+LOSqGLDGBwF1Z95t6WNjHjZ/aYm+qkU+blpfj6Fby50whjDoA7NAxg0P
# OM1nqFOI+rgwZfpvx+cdsYN0aT6sxGg7seZnM5q2COCABUhA7vaCZEao9XOwBpXy
# bGWfv1VbHJxXGsd4RnxwqpQbghesh+m2yQ6BHEDWFhcp/FycGCvqRfXvvdVnTyhe
# Be6QTHrnxvTQ/PrNPjJGEyA2igTqt6oHRpwNkzoJZplYXCmjuQymMDg80EY2NXyc
# uu7D1fkKdvp+BRtAypI16dV60bV/AK6pkKrFfwGcELEW/MxuGNxvYv6mUKe4e7id
# FT/+IAx1yCJaE5UZkADpGtXChvHjjuxf9OUCAwEAAaOCARIwggEOMB8GA1UdIwQY
# MBaAFKARCiM+lvEH7OKvKe+CpX/QMKS0MB0GA1UdDgQWBBQy65Ka/zWWSC8oQEJw
# IDaRXBeF5jAOBgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zATBgNVHSUE
# DDAKBggrBgEFBQcDAzAbBgNVHSAEFDASMAYGBFUdIAAwCAYGZ4EMAQQBMEMGA1Ud
# HwQ8MDowOKA2oDSGMmh0dHA6Ly9jcmwuY29tb2RvY2EuY29tL0FBQUNlcnRpZmlj
# YXRlU2VydmljZXMuY3JsMDQGCCsGAQUFBwEBBCgwJjAkBggrBgEFBQcwAYYYaHR0
# cDovL29jc3AuY29tb2RvY2EuY29tMA0GCSqGSIb3DQEBDAUAA4IBAQASv6Hvi3Sa
# mES4aUa1qyQKDKSKZ7g6gb9Fin1SB6iNH04hhTmja14tIIa/ELiueTtTzbT72ES+
# BtlcY2fUQBaHRIZyKtYyFfUSg8L54V0RQGf2QidyxSPiAjgaTCDi2wH3zUZPJqJ8
# ZsBRNraJAlTH/Fj7bADu/pimLpWhDFMpH2/YGaZPnvesCepdgsaLr4CnvYFIUoQx
# 2jLsFeSmTD1sOXPUC4U5IOCFGmjhp0g4qdE2JXfBjRkWxYhMZn0vY86Y6GnfrDyo
# XZ3JHFuu2PMvdM+4fvbXg50RlmKarkUT2n/cR/vfw1Kf5gZV6Z2M8jpiUbzsJA8p
# 1FiAhORFe1rYMIIGGjCCBAKgAwIBAgIQYh1tDFIBnjuQeRUgiSEcCjANBgkqhkiG
# 9w0BAQwFADBWMQswCQYDVQQGEwJHQjEYMBYGA1UEChMPU2VjdGlnbyBMaW1pdGVk
# MS0wKwYDVQQDEyRTZWN0aWdvIFB1YmxpYyBDb2RlIFNpZ25pbmcgUm9vdCBSNDYw
# HhcNMjEwMzIyMDAwMDAwWhcNMzYwMzIxMjM1OTU5WjBUMQswCQYDVQQGEwJHQjEY
# MBYGA1UEChMPU2VjdGlnbyBMaW1pdGVkMSswKQYDVQQDEyJTZWN0aWdvIFB1Ymxp
# YyBDb2RlIFNpZ25pbmcgQ0EgUjM2MIIBojANBgkqhkiG9w0BAQEFAAOCAY8AMIIB
# igKCAYEAmyudU/o1P45gBkNqwM/1f/bIU1MYyM7TbH78WAeVF3llMwsRHgBGRmxD
# eEDIArCS2VCoVk4Y/8j6stIkmYV5Gej4NgNjVQ4BYoDjGMwdjioXan1hlaGFt4Wk
# 9vT0k2oWJMJjL9G//N523hAm4jF4UjrW2pvv9+hdPX8tbbAfI3v0VdJiJPFy/7Xw
# iunD7mBxNtecM6ytIdUlh08T2z7mJEXZD9OWcJkZk5wDuf2q52PN43jc4T9OkoXZ
# 0arWZVeffvMr/iiIROSCzKoDmWABDRzV/UiQ5vqsaeFaqQdzFf4ed8peNWh1OaZX
# nYvZQgWx/SXiJDRSAolRzZEZquE6cbcH747FHncs/Kzcn0Ccv2jrOW+LPmnOyB+t
# AfiWu01TPhCr9VrkxsHC5qFNxaThTG5j4/Kc+ODD2dX/fmBECELcvzUHf9shoFvr
# n35XGf2RPaNTO2uSZ6n9otv7jElspkfK9qEATHZcodp+R4q2OIypxR//YEb3fkDn
# 3UayWW9bAgMBAAGjggFkMIIBYDAfBgNVHSMEGDAWgBQy65Ka/zWWSC8oQEJwIDaR
# XBeF5jAdBgNVHQ4EFgQUDyrLIIcouOxvSK4rVKYpqhekzQwwDgYDVR0PAQH/BAQD
# AgGGMBIGA1UdEwEB/wQIMAYBAf8CAQAwEwYDVR0lBAwwCgYIKwYBBQUHAwMwGwYD
# VR0gBBQwEjAGBgRVHSAAMAgGBmeBDAEEATBLBgNVHR8ERDBCMECgPqA8hjpodHRw
# Oi8vY3JsLnNlY3RpZ28uY29tL1NlY3RpZ29QdWJsaWNDb2RlU2lnbmluZ1Jvb3RS
# NDYuY3JsMHsGCCsGAQUFBwEBBG8wbTBGBggrBgEFBQcwAoY6aHR0cDovL2NydC5z
# ZWN0aWdvLmNvbS9TZWN0aWdvUHVibGljQ29kZVNpZ25pbmdSb290UjQ2LnA3YzAj
# BggrBgEFBQcwAYYXaHR0cDovL29jc3Auc2VjdGlnby5jb20wDQYJKoZIhvcNAQEM
# BQADggIBAAb/guF3YzZue6EVIJsT/wT+mHVEYcNWlXHRkT+FoetAQLHI1uBy/YXK
# ZDk8+Y1LoNqHrp22AKMGxQtgCivnDHFyAQ9GXTmlk7MjcgQbDCx6mn7yIawsppWk
# vfPkKaAQsiqaT9DnMWBHVNIabGqgQSGTrQWo43MOfsPynhbz2Hyxf5XWKZpRvr3d
# MapandPfYgoZ8iDL2OR3sYztgJrbG6VZ9DoTXFm1g0Rf97Aaen1l4c+w3DC+IkwF
# kvjFV3jS49ZSc4lShKK6BrPTJYs4NG1DGzmpToTnwoqZ8fAmi2XlZnuchC4NPSZa
# PATHvNIzt+z1PHo35D/f7j2pO1S8BCysQDHCbM5Mnomnq5aYcKCsdbh0czchOm8b
# kinLrYrKpii+Tk7pwL7TjRKLXkomm5D1Umds++pip8wH2cQpf93at3VDcOK4N7Ew
# oIJB0kak6pSzEu4I64U6gZs7tS/dGNSljf2OSSnRr7KWzq03zl8l75jy+hOds9TW
# SenLbjBQUGR96cFr6lEUfAIEHVC1L68Y1GGxx4/eRI82ut83axHMViw1+sVpbPxg
# 51Tbnio1lB93079WPFnYaOvfGAA0e0zcfF/M9gXr+korwQTh2Prqooq2bYNMvUoU
# KD85gnJ+t0smrWrb8dee2CvYZXD5laGtaAxOfy/VKNmwuWuAh9kcMIIGZjCCBM6g
# AwIBAgIRAMyLU7NDPs1zQXTEeYo/k2YwDQYJKoZIhvcNAQEMBQAwVDELMAkGA1UE
# BhMCR0IxGDAWBgNVBAoTD1NlY3RpZ28gTGltaXRlZDErMCkGA1UEAxMiU2VjdGln
# byBQdWJsaWMgQ29kZSBTaWduaW5nIENBIFIzNjAeFw0yMjA2MzAwMDAwMDBaFw0y
# NTA2MjkyMzU5NTlaMFkxCzAJBgNVBAYTAkNBMRAwDgYDVQQIDAdPbnRhcmlvMRsw
# GQYDVQQKDBJBSiBUZWsgQ29ycG9yYXRpb24xGzAZBgNVBAMMEkFKIFRlayBDb3Jw
# b3JhdGlvbjCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAOLV0fXklSI7
# zs4Awbc5rzQGG/XJaEgZLEXFVvzqKWlApLYSdXMLTzW14/IEoVMmi+qunmgIF98c
# 3gUFm6dTSkf+ccSIWgHwjD/pPMX4leIO1Qen+OBup4p33XRNqHsWLzBqlWmKRcLW
# 5cyRDRXedgZDvZozhxTqLhk0tFdTcBoGULA13Z/TfotbwNCx05W304VgluUogNX2
# 4yv80QYmHa6667ewXuAPNiPWQgFE4fyXOvFsRUFEtllRZmUpOPqZtGURw2ZYd+cZ
# UFn4QrC9DzlESFEjQzMMF5iUjrKCI25jq53MkTZHnOjO9KGH5SvcLo5CbgNptpzr
# d1P7AsoHFedvErNbp6YOhKrkv7F0ksVgqBh1v2Yc1ShpzZBEJ6pL+QsdnWzl6Wzb
# B8xEYEdbNF0A0/kyFHm2tYSxxCsVO4caoL2CcsA3q/1h73MBWEcNT2fV6o25L+LY
# sNUkFRc6ZRqOQ/ub9na8jCOwuhxi8MXTGo22Crinp5bbzYA/liNm97G77mJe59za
# aaoulTWzk5y06k7VgLDYtR7IgZ47Aytnj/jr7S5/P/1F2K3lbRUdGyKdifubSgjk
# m1gubK0cm3Y6L7Ar2X1nBm+PpAZqA54jQBs9cV03MVDjZb3AvjooCi9uOCJ4998U
# TV9Sn2873/GUXE6dRB+w0fD/FcUxX2pjAgMBAAGjggGsMIIBqDAfBgNVHSMEGDAW
# gBQPKssghyi47G9IritUpimqF6TNDDAdBgNVHQ4EFgQU0JE9KqcvG1A1pTeA+vcd
# t1flqK0wDgYDVR0PAQH/BAQDAgeAMAwGA1UdEwEB/wQCMAAwEwYDVR0lBAwwCgYI
# KwYBBQUHAwMwSgYDVR0gBEMwQTA1BgwrBgEEAbIxAQIBAwIwJTAjBggrBgEFBQcC
# ARYXaHR0cHM6Ly9zZWN0aWdvLmNvbS9DUFMwCAYGZ4EMAQQBMEkGA1UdHwRCMEAw
# PqA8oDqGOGh0dHA6Ly9jcmwuc2VjdGlnby5jb20vU2VjdGlnb1B1YmxpY0NvZGVT
# aWduaW5nQ0FSMzYuY3JsMHkGCCsGAQUFBwEBBG0wazBEBggrBgEFBQcwAoY4aHR0
# cDovL2NydC5zZWN0aWdvLmNvbS9TZWN0aWdvUHVibGljQ29kZVNpZ25pbmdDQVIz
# Ni5jcnQwIwYIKwYBBQUHMAGGF2h0dHA6Ly9vY3NwLnNlY3RpZ28uY29tMCEGA1Ud
# EQQaMBiBFmFqLmFjY291bnRpbmdAYWp0ZWsuY2EwDQYJKoZIhvcNAQEMBQADggGB
# ACa/QJzMPVDohIgQtL4/Yr7z7DlaybhjLQ2gEx7pU6G/hNQ+kClqLR3hykkJ7fwZ
# 4cYX1TF4JWnkmQ0GZwj5bk5RaSyDatQVVIW8AQNMIEEhUftHJn1REz16CzXxlgzd
# d+GTqYmwommBa6DlFO88fwF0FL3KJgvNguQGSU9sGIGUWyQuxqFUwXRgsXpwQbpR
# 1H6qsViLN1SGPXO+iqUSyejmd9mIbc8b0IxuR6rDtU2PIcU8XzwsJv8M/L1paQut
# 4m7dNw54gsoRVp+KJWawkkEVM6xvszCo8VIk8ZGetRCBT+ZSunIb/LFripb++lR8
# tIBkq8zYEhEp9U8GQZbKNZzfelix4kRt5wUq39rpBU8aHoU4GRXFs571jb/qBz/x
# AVckN5cosppJxe+AW/TR9qKrL8uKOJ9cCLRXjPdLGSmHA5XMN6ecgRE+yfLMOauX
# pUo33dCGh36TZuSi7P4uz2trEEUfmaQlr/TEp0xtbgUxTopdUYh9xagN4bbkRmoB
# izGCAwowggMGAgEBMGkwVDELMAkGA1UEBhMCR0IxGDAWBgNVBAoTD1NlY3RpZ28g
# TGltaXRlZDErMCkGA1UEAxMiU2VjdGlnbyBQdWJsaWMgQ29kZSBTaWduaW5nIENB
# IFIzNgIRAMyLU7NDPs1zQXTEeYo/k2YwCQYFKw4DAhoFAKB4MBgGCisGAQQBgjcC
# AQwxCjAIoAKAAKECgAAwGQYJKoZIhvcNAQkDMQwGCisGAQQBgjcCAQQwHAYKKwYB
# BAGCNwIBCzEOMAwGCisGAQQBgjcCARUwIwYJKoZIhvcNAQkEMRYEFP2QQHwnr1C9
# S48OWCX/UvaNrhRAMA0GCSqGSIb3DQEBAQUABIICAGrHmvwGnMGyvWFBkUzkWUwf
# 1e6fNchFFSqQmCdD1l7MTGVbWUwhCBuJ2HFjXLbE7g59bJCXNpI76v84dljsTICk
# Fed18JOzJ4PMI9mCXHmVOE/vbhanveL/Sw2Y2CfYYd/3f8O/euHUaSWBVajAjyoY
# HAFUtEpZYW1aDPfp0psrtu8F3DJk3vPJ0CFVN3O59wZ8hPen4mla4hpbIrw5Uoyy
# 3AbCDPfJHgWndF/n/gA6b/4tCSVtH9hlJsDgKjAPIFJVp9VnyhPkp2L7EhhiJFKQ
# DIBNil3ysjBcLFR4Lo7QZBQJO0en/mqZCl8eAfSuEVAqdWF+HWcCG5R0OFrIfGlR
# Sy0cSiSUma3pFu2xQdfZohVnhJiW2nc6s1G7MsN/EHTrGug4IosO3neJ2DhXQZvX
# Dwku+nwp0NYT6lPR4FHKbaQUE9m6v63YpcISuoBxPwYX2jAOxGmv1arV++faWJ/F
# 4nJvN3sVOJ1dClJCaxGT1SXB86ZMRx6e2gMOFcUYVA3XHFKIFokXBcz4Oqq46ilI
# J/w83PWmZtuuGyX7V6JbsVzC3S4a4u8uTfB5gp57/NM9UuAiyzW0eRbldz+FClDQ
# cCxJvMgyItMo6vp4K4UKwo6YQ5uiuUBUFvHb4Dl0Yw7nzP+r08v6RHf14aGN078q
# tYt2A8cxT48FJcauNwqe
# SIG # End signature block