Private/AzStackHci.Network.Helpers.ps1

# ////////////////////////////////////////////////////////////////////////////
# Test if proxy is enabled and return boolean
function Get-Proxy
{
    <#
    .SYNOPSIS
        Check if proxy is enabled and return boolean and proxy server uri
    #>


    begin {
        # Write-Debug "Get-Proxy: Beginning proxy detection process"
    }

    process {
        $line1, $line2, $line3, $JsonLines = netsh winhttp show advproxy
        $ProxyInformation = $JsonLines | ConvertFrom-Json -ErrorAction SilentlyContinue
        # Return True/False and the Proxy server uri as a PSObject
        $ProxyReturnVariable = New-Object PsObject -Property @{
            # True/False
            Enabled = [bool]$ProxyInformation.Proxy
            # Proxy server URI
            Server = $ProxyInformation.Proxy
            # Proxy bypass list
            # ProxyBypass = $proxy.Bypass
        }
        # Check if the Proxy is enabled and if the proxy server is set
        if($ProxyReturnVariable.Enabled) {
            if(-not($ProxyReturnVariable.Server)){
                # In case the proxy server is not set from netsh winhttp show advproxy output
                $proxyUri = [Uri]$null
                $ProxyTest = [System.Net.WebRequest]::GetSystemWebProxy()
                $ProxyTest.Credentials = [System.Net.CredentialCache]::DefaultCredentials
                # use a known URL to test the proxy server, but http, in case of Arc Gateway, to prevent "localhost" from being returned
                # This is a known URL that should be accessible from any network
                $TestUrl = [System.Uri]::new("http://oneocsp.microsoft.com")
                # Test the proxy server using a known URL
                $proxyUri = $ProxyTest.GetProxy($TestUrl)
                if($proxyUri -eq $TestUrl) {
                    # Proxy is not required, so do not use it
                    $ProxyReturnVariable.Enabled = $false
                    Write-HostAzS "`t`nNo proxy server detected, using direct connection...`n" -ForegroundColor Green
                } else {
                    # Proxy is required, so use it
                    $ProxyReturnVariable.Server = $proxyUri
                }
            }
            # Check if the proxy server string has a semi-colon which can be used for Arc Gateway
            if($ProxyReturnVariable.Server -match ";"){
                # In case the proxy server is set from netsh winhttp show advproxy output
                [array]$ProxyStrings = $ProxyReturnVariable.Server.ToString().Split(";")
            }
            if($ProxyStrings){
                # Check if the proxy server string has a semi-colon which can be used for Arc Gateway
                # Multiple proxy servers detected
                Write-HostAzS "`n`tArc Gateway scenario detected (possible), as multiple proxy servers detected ('netsh winhttp show advproxy')..."
                ForEach($ProxyServer in $ProxyStrings){
                    # Trim the proxy server string
                    $ProxyServer = $ProxyServer.Trim()
                    if($ProxyServer -like "http=*"){
                        # HTTP proxy server (should be customer proxy server)
                        # Check if the proxy server is set from netsh winhttp show advproxy output
                        Write-HostAzS "`tHTTP Proxy server detected, using proxy: $($ProxyServer)" -ForegroundColor Green    
                    } elseif($ProxyServer -like "https=*"){
                        # HTTPS proxy server (might be Arc Gateway Agent, "localhost")
                        # Check if the proxy server is set from netsh winhttp show advproxy output
                        Write-HostAzS "`tHTTPS Proxy server detected, using proxy: $($ProxyServer)" -ForegroundColor Green    
                    } else {
                        # Other proxy server detected
                        # Check if the proxy server is set from netsh winhttp show advproxy output
                        Write-HostAzS "`tProxy server detected, using proxy: $($ProxyServer)" -ForegroundColor Green    
                    }
                } # End ForEach
                Write-HostAzS ""
            } else {
                # Single proxy detected
                Write-HostAzS "`t`nProxy server detected, using proxy: $($ProxyReturnVariable.Server)`n" -ForegroundColor Green
            }

        } else {
            # Proxy is NOT enabled
            # No proxy server detected, so use direct connection
            Write-HostAzS "`t`nNo proxy server detected, using direct connection...`n" -ForegroundColor Green
        }

    } # End of process block

    end {
        # Write-Debug "Get-Proxy: Proxy detection process completed"

        # Return the ProxyReturnVariable object
        Return $ProxyReturnVariable
    }
} # End Function Get-Proxy


# ////////////////////////////////////////////////////////////////////////////
# This function retrieves the SSL certificate chain from a remote HTTPS endpoint
# It uses the System.Net.Http.HttpClient class to make the request and capture the certificate chain
function Get-SslCertificateChain
{
    <#
    .SYNOPSIS
        Retrieve remote ssl certificate & chain from https endpoint for Desktop and Core
    .NOTES
        Credit: https://github.com/markekraus
    #>

    [CmdletBinding()]
    param (
        [system.uri]
        $url,

        [Parameter()]
        [bool]
        $AllowAutoRedirect,

        [Parameter()]
        [string]
        $Proxy
    )

    begin {
        # Write-Debug "Get-SslCertificateChain: Beginning SSL certificate chain retrieval for '$url'"
    }

    process {
    try
    {
        $cs = @'
    using System;
    using System.Collections.Generic;
    using System.Net.Http;
    using System.Net.Security;
    using System.Security.Cryptography.X509Certificates;
 
    namespace CertificateCapture
    {
        public class Utility
        {
            public static Func<HttpRequestMessage,X509Certificate2,X509Chain,SslPolicyErrors,Boolean> ValidationCallback =
                (message, cert, chain, errors) => {
                    CapturedCertificates.Clear();
                    var newCert = new X509Certificate2(cert);
                    var newChain = new X509Chain();
                    newChain.Build(newCert);
                    CapturedCertificates.Add(new CapturedCertificate(){
                        Certificate = newCert,
                        CertificateChain = newChain,
                        PolicyErrors = errors,
                        URI = message.RequestUri
                    });
                    return true;
                };
            public static List<CapturedCertificate> CapturedCertificates = new List<CapturedCertificate>();
        }
 
        public class CapturedCertificate
        {
            public X509Certificate2 Certificate { get; set; }
            public X509Chain CertificateChain { get; set; }
            public SslPolicyErrors PolicyErrors { get; set; }
            public Uri URI { get; set; }
        }
    }
'@


        try
        {
            if (-not ('CertificateCapture.Utility' -as [type]))
            {
                if ($PSEdition -ne 'Core')
                {
                    Add-Type -AssemblyName System.Net.Http
                    Add-Type $cs -ReferencedAssemblies System.Net.Http
                }
                else
                {
                    Add-Type $cs
                }
            }
        }
        catch
        {
            if ($_.Exception.Message -notmatch 'Definition of new types is not supported in this language mode')
            {
                throw "Language mode does not allow this test Error: $_"
            }
        }

        # Reset variables, in case cached.
        Remove-Variable Certs, Handler, Client, Request -ErrorAction SilentlyContinue

        # Create a new list to hold captured certificates.
        $Certs = [CertificateCapture.Utility]::CapturedCertificates
        # Clear any previously captured certificates.
        $Certs.Clear()
        # Create the HttpClientHandler.
        $Handler = [System.Net.Http.HttpClientHandler]::new()
        # This is important to capture the certificate chain of the first endpoint, not redirected endpoints.
        if($AllowAutoRedirect -eq $false)
        {
            # Set the handler to not allow auto redirects
            # https://learn.microsoft.com/en-us/dotnet/api/system.net.http.httpclienthandler.allowautoredirect
            $Handler.AllowAutoRedirect = $false
        }
        if ($Proxy)
        {
            $Handler.Proxy = New-Object System.Net.WebProxy($proxy)
        }
        # Set the ServerCertificateCustomValidationCallback to our custom callback.
        $Handler.ServerCertificateCustomValidationCallback = [CertificateCapture.Utility]::ValidationCallback
        # Create the HttpClient with the handler
        $Client = [System.Net.Http.HttpClient]::new($Handler)
        # Set a timeout to 15 seconds (reduced from 60s to limit impact of unresponsive backend pool servers):
        $Client.Timeout = 150000000
        try {
            # Setup the request to the URL.
            $Request = $Client.GetAsync($url)
            # Wait for the request to complete calling the Result method.
            $Null = $Request.Result
            # Check if the request is completed and that certificates were captured.
            if(($Request.IsCompleted) -and $Certs){
                # Return the captured certificate chain
                Write-Debug "Successfully obtained SSL certificate chain from endpoint: '$url'"
                return $Certs.CertificateChain
            } else {
                # Return null if no certificates were captured.
                Write-Debug "Failed to obtain SSL certificate chain from endpoint: '$url'"
                return $null
            }
        }
        finally {
            # Dispose IDisposable resources to prevent socket/handle leaks
            if ($Request) { $Request.Dispose() }
            if ($Client)  { $Client.Dispose() }
            if ($Handler) { $Handler.Dispose() }
        }

    }
    catch
    {
        throw $_
    }
    } # End of process block

    end {
        # Write-Debug "Get-SslCertificateChain: SSL certificate chain retrieval completed"
    }
} # End Function Get-SslCertificateChain