SChannelDsc.psm1

#Region '.\prefix.ps1' -1

$script:dscResourceCommonModulePath = Join-Path -Path $PSScriptRoot -ChildPath 'Modules/DscResource.Common'
Import-Module -Name $script:dscResourceCommonModulePath

$script:localizedData = Get-LocalizedData -DefaultUICulture 'en-US'
#EndRegion '.\prefix.ps1' 5
#Region '.\Private\ConvertTo-TlsProtocolRegistryKeyName.ps1' -1

<#
    .SYNOPSIS
        Converts a protocol identifier to the SCHANNEL registry key name.
 
    .DESCRIPTION
        Maps protocol values from the `[System.Security.Authentication.SslProtocols]`
        enum to the actual SCHANNEL registry key names (e.g. 'TLS 1.2').
 
    .PARAMETER Protocol
        The protocol value from the `[System.Security.Authentication.SslProtocols]`
        enum, e.g. `Tls12`, `Ssl3`, `Tls`.
 
    .OUTPUTS
        System.String
 
    .EXAMPLE
        ConvertTo-TlsProtocolRegistryKeyName -Protocol Tls12
 
        Returns the string 'TLS 1.2'.
#>

function ConvertTo-TlsProtocolRegistryKeyName
{
    [CmdletBinding()]
    [OutputType([System.String])]
    param
    (
        [Parameter(Mandatory = $true)]
        [System.Security.Authentication.SslProtocols]
        $Protocol
    )

    $protocolRegistryKeyName = switch ($Protocol)
    {
        ([System.Security.Authentication.SslProtocols]::Ssl2)
        {
            'SSL 2.0'
        }

        ([System.Security.Authentication.SslProtocols]::Ssl3)
        {
            'SSL 3.0'
        }

        ([System.Security.Authentication.SslProtocols]::Tls)
        {
            'TLS 1.0'
        }

        ([System.Security.Authentication.SslProtocols]::Tls11)
        {
            'TLS 1.1'
        }

        ([System.Security.Authentication.SslProtocols]::Tls12)
        {
            'TLS 1.2'
        }

        # A guard to check if Tls13 is defined in the enum (it may not be in older .NET versions)
        { [System.Enum]::GetNames([System.Security.Authentication.SslProtocols]) -contains 'Tls13' -and $_ -eq [System.Security.Authentication.SslProtocols]::Tls13 }
        {
            'TLS 1.3'
        }

        default
        {
            $errorMessage = $script:localizedData.ConvertTo_TlsProtocolRegistryKeyName_UnknownProtocol -f $Protocol
            $exception = New-Exception -Message $errorMessage
            $errorRecord = New-ErrorRecord -Exception $exception -ErrorId 'CTTPRKN0001' -ErrorCategory ([System.Management.Automation.ErrorCategory]::InvalidArgument) -TargetObject $Protocol
            $PSCmdlet.ThrowTerminatingError($errorRecord)
        }
    }

    return $protocolRegistryKeyName
}
#EndRegion '.\Private\ConvertTo-TlsProtocolRegistryKeyName.ps1' 76
#Region '.\Private\Get-TlsProtocolRegistryPath.ps1' -1

<#
    .SYNOPSIS
        Returns the SCHANNEL registry path for a given protocol and target.
 
    .DESCRIPTION
        Builds the registry path under
        HKLM:\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Protocols
        for the provided friendly protocol name and selects the `Server` or
        `Client` subkey depending on the `-Client` switch.
 
    .PARAMETER Protocol
        The protocol identifier, e.g. 'Tls12'.
 
    .PARAMETER Client
        When specified, return the path for the `Client` subkey, otherwise
        return the `Server` subkey path.
 
    .INPUTS
        None.
 
    .OUTPUTS
        System.String
 
    .EXAMPLE
        Get-TlsProtocolRegistryPath -Protocol Tls12
 
        Returns the string:
        'HKLM:\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Protocols\TLS 1.2\Server'.
 
    .EXAMPLE
        Get-TlsProtocolRegistryPath -Protocol Tls13 -Client
 
        Returns the string:
        'HKLM:\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Protocols\TLS 1.3\Client'.
#>

function Get-TlsProtocolRegistryPath
{
    [CmdletBinding()]
    [OutputType([System.String])]
    param
    (
        [Parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [System.Security.Authentication.SslProtocols]
        $Protocol,

        [Parameter()]
        [System.Management.Automation.SwitchParameter]
        $Client
    )

    $protocolKeyName = ConvertTo-TlsProtocolRegistryKeyName -Protocol $Protocol

    $target = Get-TlsProtocolTargetRegistryName -Client:$Client

    return ('HKLM:\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Protocols\{0}\{1}' -f $protocolKeyName, $target)
}
#EndRegion '.\Private\Get-TlsProtocolRegistryPath.ps1' 58
#Region '.\Private\Get-TlsProtocolTargetRegistryName.ps1' -1

<#
    .SYNOPSIS
        Returns the SCHANNEL protocol target name for registry keys.
 
    .DESCRIPTION
        Returns either 'Server' or 'Client' depending on the provided
        `-Client` switch. This centralizes the logic used by public commands
        for choosing the registry subkey name.
 
    .PARAMETER Client
        When specified, return 'Client', otherwise return 'Server'.
 
    .OUTPUTS
        System.String
 
    .EXAMPLE
        Get-TlsProtocolTargetRegistryName
 
        Returns the string 'Server'.
 
    .EXAMPLE
        Get-TlsProtocolTargetRegistryName -Client
 
        Returns the string 'Client'.
#>

function Get-TlsProtocolTargetRegistryName
{
    [CmdletBinding()]
    [OutputType([System.String])]
    param
    (
        [Parameter()]
        [System.Management.Automation.SwitchParameter]
        $Client
    )

    if ($Client.IsPresent)
    {
        return 'Client'
    }

    return 'Server'
}
#EndRegion '.\Private\Get-TlsProtocolTargetRegistryName.ps1' 44
#Region '.\Private\Set-TlsProtocolRegistryValue.ps1' -1

<#
    .SYNOPSIS
        Sets TLS/SSL protocol registry values for enabling or disabling protocols.
 
    .DESCRIPTION
        Internal helper function that writes SCHANNEL protocol registry values
        under HKLM:\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Protocols.
        This function creates the registry key if it does not exist and sets
        the Enabled DWORD value. Optionally sets the DisabledByDefault value.
 
        The function handles ShouldProcess confirmation using the caller's
        PSCmdlet object.
 
    .PARAMETER Protocol
        One or more protocol names to set registry values for. Accepts values
        from the `[System.Security.Authentication.SslProtocols]` enum such as
        `Ssl2`, `Ssl3`, `Tls`, `Tls11`, `Tls12`, `Tls13`.
 
    .PARAMETER Enable
        Enables the protocol by setting Enabled to 1 and DisabledByDefault to 0.
 
    .PARAMETER Disable
        Disables the protocol by setting Enabled to 0 and DisabledByDefault to 1.
 
    .PARAMETER Client
        When specified, operate on the protocol `Client` registry key instead of
        the default `Server` key.
 
    .PARAMETER SetDisabledByDefault
        When specified, also set the DisabledByDefault registry value.
 
    .PARAMETER Force
        When specified, bypasses confirmation prompts and suppresses
        ShouldProcess confirmations.
 
    .PARAMETER Cmdlet
        The PSCmdlet object from the calling command, used to perform
        ShouldProcess confirmation.
 
    .INPUTS
        None.
 
    .OUTPUTS
        None.
 
    .EXAMPLE
        Set-TlsProtocolRegistryValue -Protocol Tls12 -Enable -SetDisabledByDefault
 
        Enables TLS 1.2 for server-side connections by setting the Enabled
        registry value to 1 and DisabledByDefault to 0.
 
    .EXAMPLE
        Set-TlsProtocolRegistryValue -Protocol Tls12, Tls13 -Enable
 
        Enables TLS 1.2 and TLS 1.3 for server-side connections.
 
    .EXAMPLE
        Set-TlsProtocolRegistryValue -Protocol Ssl3 -Disable -Client
 
        Disables SSL 3.0 for client-side connections by setting the Enabled
        registry value to 0.
#>

function Set-TlsProtocolRegistryValue
{
    [CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = 'Medium', DefaultParameterSetName = 'Enable')]
    [OutputType()]
    param
    (
        [Parameter(Mandatory = $true)]
        [System.Security.Authentication.SslProtocols[]]
        $Protocol,

        [Parameter(Mandatory = $true, ParameterSetName = 'Enable')]
        [System.Management.Automation.SwitchParameter]
        $Enable,

        [Parameter(Mandatory = $true, ParameterSetName = 'Disable')]
        [System.Management.Automation.SwitchParameter]
        $Disable,

        [Parameter()]
        [System.Management.Automation.SwitchParameter]
        $Client,

        [Parameter()]
        [System.Management.Automation.SwitchParameter]
        $SetDisabledByDefault,

        [Parameter()]
        [System.Management.Automation.SwitchParameter]
        $Force
    )

    # Need to do this check with Get-Variable instead of $Confirm due to strict mode.
    if ($Force.IsPresent -and -not (Get-Variable -Name 'Confirm' -ValueOnly -ErrorAction SilentlyContinue))
    {
        $ConfirmPreference = 'None'
    }

    foreach ($currentProtocol in $Protocol)
    {
        $protocolKeyName = ConvertTo-TlsProtocolRegistryKeyName -Protocol $currentProtocol
        $target = Get-TlsProtocolTargetRegistryName -Client:$Client

        if ($Enable.IsPresent)
        {
            $enabledValue = 1
            $disabledByDefaultValue = 0
            $descriptionMessage = $script:localizedData.Set_TlsProtocolRegistryValue_Enable_ShouldProcessDescription -f $protocolKeyName, $target
            $confirmationMessage = $script:localizedData.Set_TlsProtocolRegistryValue_Enable_ShouldProcessConfirmation -f $protocolKeyName
            $captionMessage = $script:localizedData.Set_TlsProtocolRegistryValue_Enable_ShouldProcessCaption
            $errorMessage = $script:localizedData.Set_TlsProtocolRegistryValue_FailedToEnable
            $errorId = 'STPRV0001'
        }
        else
        {
            $enabledValue = 0
            $disabledByDefaultValue = 1
            $descriptionMessage = $script:localizedData.Set_TlsProtocolRegistryValue_Disable_ShouldProcessDescription -f $protocolKeyName, $target
            $confirmationMessage = $script:localizedData.Set_TlsProtocolRegistryValue_Disable_ShouldProcessConfirmation -f $protocolKeyName
            $captionMessage = $script:localizedData.Set_TlsProtocolRegistryValue_Disable_ShouldProcessCaption
            $errorMessage = $script:localizedData.Set_TlsProtocolRegistryValue_FailedToDisable
            $errorId = 'STPRV0002'
        }

        if ($PSCmdlet.ShouldProcess($descriptionMessage, $confirmationMessage, $captionMessage))
        {
            $regPath = Get-TlsProtocolRegistryPath -Protocol $currentProtocol -Client:$Client

            try
            {
                $null = New-Item -Path $regPath -Force -ErrorAction 'Stop'
                $null = New-ItemProperty -Path $regPath -Name 'Enabled' -Value $enabledValue -PropertyType DWord -Force -ErrorAction 'Stop'

                if ($SetDisabledByDefault.IsPresent)
                {
                    $null = New-ItemProperty -Path $regPath -Name 'DisabledByDefault' -Value $disabledByDefaultValue -PropertyType DWord -Force -ErrorAction 'Stop'
                }
            }
            catch
            {
                $errorMessage = $errorMessage -f $currentProtocol

                $exception = New-Exception -Message $errorMessage -ErrorRecord $_
                $errorRecord = New-ErrorRecord -Exception $exception -ErrorId $errorId -ErrorCategory 'InvalidOperation' -TargetObject $currentProtocol
                $PSCmdlet.ThrowTerminatingError($errorRecord)
            }
        }
    }
}
#EndRegion '.\Private\Set-TlsProtocolRegistryValue.ps1' 151
#Region '.\Public\Assert-TlsProtocol.ps1' -1

<#
    .SYNOPSIS
        Asserts that the specified TLS/SSL protocols are enabled or disabled.
 
    .DESCRIPTION
        Calls Test-TlsProtocol for the specified protocol(s) and throws a
        terminating error if the assertion fails. By default, the command
        asserts that the protocol(s) are enabled for server-side connections.
        Use the `-Client` switch to assert the `Client` key instead of the
        default `Server` key. Use the `-Disabled` switch to assert that the
        protocol(s) are disabled instead of enabled.
 
    .PARAMETER Protocol
        One or more protocol names to assert. Accepts values from the
        `[System.Security.Authentication.SslProtocols]` enum such as `Ssl2`,
        `Ssl3`, `Tls`, `Tls11`, `Tls12`, `Tls13`.
 
    .PARAMETER Client
        When specified, assert the protocol in the `Client` registry key
        instead of the default `Server` key.
 
    .PARAMETER Disabled
        When specified, asserts that the protocol(s) are disabled. By default
        the command asserts that the protocol(s) are enabled.
 
    .INPUTS
        None.
 
    .OUTPUTS
        None.
 
    .EXAMPLE
        Assert-TlsProtocol -Protocol Tls12
 
        Asserts that TLS 1.2 is enabled for server-side connections. Throws a
        terminating error if TLS 1.2 is not enabled.
 
    .EXAMPLE
        Assert-TlsProtocol -Protocol Tls12 -Client
 
        Asserts that TLS 1.2 is enabled for client-side connections.
 
    .EXAMPLE
        Assert-TlsProtocol -Protocol Tls12 -Disabled
 
        Asserts that TLS 1.2 is disabled for server-side connections. Throws a
        terminating error if TLS 1.2 is still enabled.
 
    .EXAMPLE
        Assert-TlsProtocol -Protocol Ssl3, Tls -Disabled
 
        Asserts that both SSL 3.0 and TLS 1.0 are disabled for server-side
        connections.
#>

function Assert-TlsProtocol
{
    [CmdletBinding()]
    [OutputType()]
    param
    (
        [Parameter(Mandatory = $true)]
        [System.Security.Authentication.SslProtocols[]]
        $Protocol,

        [Parameter()]
        [System.Management.Automation.SwitchParameter]
        $Client,

        [Parameter()]
        [System.Management.Automation.SwitchParameter]
        $Disabled
    )

    $result = Test-TlsProtocol -Protocol $Protocol -Client:$Client -Disabled:$Disabled

    if (-not $result)
    {
        if ($Disabled)
        {
            $message = ($script:localizedData.Assert_TlsProtocol_NotDisabled -f ($Protocol -join ', '))
        }
        else
        {
            $message = ($script:localizedData.Assert_TlsProtocol_NotEnabled -f ($Protocol -join ', '))
        }

        $exception = New-Exception -Message $message
        $errorRecord = New-ErrorRecord -Exception $exception -ErrorId 'ATP0001' -ErrorCategory 'InvalidOperation' -TargetObject $Protocol
        $PSCmdlet.ThrowTerminatingError($errorRecord)
    }
}
#EndRegion '.\Public\Assert-TlsProtocol.ps1' 92
#Region '.\Public\Disable-TlsProtocol.ps1' -1

<#
    .SYNOPSIS
        Disables specified TLS/SSL protocols by writing SCHANNEL registry values.
 
    .DESCRIPTION
        Disables SCHANNEL protocol keys under
        HKLM:\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Protocols
        for the server-side `Server` key by default. Use the `-Client` switch to
        operate on the `Client` key instead. The command will create the target
        key if it does not exist and set the `Enabled` DWORD to `0`.
 
        Optionally, when `-SetDisabledByDefault` is specified the command will
        also write `DisabledByDefault = 1` (opt-in only).
 
    .PARAMETER Protocol
        One or more protocol names to disable. Accepts values from the
        `[System.Security.Authentication.SslProtocols]` enum such as `Ssl2`,
        `Ssl3`, `Tls`, `Tls11`, `Tls12`, `Tls13`.
 
    .PARAMETER Client
        When specified, operate on the protocol `Client` registry key instead of
        the default `Server` key.
 
    .PARAMETER SetDisabledByDefault
        When specified, also set the `DisabledByDefault` DWORD to 1. This is an
        opt-in behavior to avoid unintentionally changing additional registry
        values.
 
    .PARAMETER Force
        Suppresses confirmation prompts.
 
    .INPUTS
        None.
 
    .OUTPUTS
        None.
 
    .EXAMPLE
        Disable-TlsProtocol -Protocol Ssl3
 
        Disables SSL 3.0 for server-side connections by setting the `Enabled`
        registry value to 0.
 
    .EXAMPLE
        Disable-TlsProtocol -Protocol Tls -Client
 
        Disables TLS 1.0 for client-side connections.
 
    .EXAMPLE
        Disable-TlsProtocol -Protocol Ssl2, Ssl3 -SetDisabledByDefault
 
        Disables SSL 2.0 and SSL 3.0 for server-side connections and also sets
        the `DisabledByDefault` registry value to 1.
 
    .EXAMPLE
        Disable-TlsProtocol -Protocol Tls -Force
 
        Disables TLS 1.0 for server-side connections without prompting for
        confirmation.
#>

function Disable-TlsProtocol
{
    [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '', Justification = 'Because ShouldProcess is used in the called function Set-TlsProtocolRegistryValue')]
    [CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = 'Medium')]
    [OutputType()]
    param
    (
        [Parameter(Mandatory = $true)]
        [System.Security.Authentication.SslProtocols[]]
        $Protocol,

        [Parameter()]
        [System.Management.Automation.SwitchParameter]
        $Client,

        [Parameter()]
        [System.Management.Automation.SwitchParameter]
        $SetDisabledByDefault,

        [Parameter()]
        [System.Management.Automation.SwitchParameter]
        $Force
    )

    # ShouldProcess is handled in Set-TlsProtocolRegistryValue
    Set-TlsProtocolRegistryValue @PSBoundParameters -Disable
}
#EndRegion '.\Public\Disable-TlsProtocol.ps1' 88
#Region '.\Public\Enable-TlsProtocol.ps1' -1

<#
    .SYNOPSIS
        Enables specified TLS/SSL protocols by writing SCHANNEL registry values.
 
    .DESCRIPTION
        Enables SCHANNEL protocol keys under
        HKLM:\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Protocols
        for the server-side `Server` key by default. Use the `-Client` switch to
        operate on the `Client` key instead. The command will create the target
        key if it does not exist and set the `Enabled` DWORD to `1`.
 
        Optionally, when `-SetDisabledByDefault` is specified the command will
        also write `DisabledByDefault = 0` (opt-in only).
 
    .PARAMETER Protocol
        One or more protocol names to enable. Accepts values from the
        `[System.Security.Authentication.SslProtocols]` enum such as `Ssl2`,
        `Ssl3`, `Tls`, `Tls11`, `Tls12`, `Tls13`.
 
    .PARAMETER Client
        When specified, operate on the protocol `Client` registry key instead of
        the default `Server` key.
 
    .PARAMETER SetDisabledByDefault
        When specified, also set the `DisabledByDefault` DWORD to 0. This is an
        opt-in behavior to avoid unintentionally changing additional registry
        values.
 
    .PARAMETER Force
        Suppresses confirmation prompts.
 
    .INPUTS
        None.
 
    .OUTPUTS
        None.
 
    .EXAMPLE
        Enable-TlsProtocol -Protocol Tls12
 
        Enables TLS 1.2 for server-side connections by setting the `Enabled`
        registry value to 1.
 
    .EXAMPLE
        Enable-TlsProtocol -Protocol Tls13 -Client
 
        Enables TLS 1.3 for client-side connections.
 
    .EXAMPLE
        Enable-TlsProtocol -Protocol Tls12, Tls13 -SetDisabledByDefault
 
        Enables TLS 1.2 and TLS 1.3 for server-side connections and also sets
        the `DisabledByDefault` registry value to 0.
 
    .EXAMPLE
        Enable-TlsProtocol -Protocol Tls12 -Force
 
        Enables TLS 1.2 for server-side connections without prompting for
        confirmation.
#>

function Enable-TlsProtocol
{
    [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '', Justification = 'Because ShouldProcess is used in the called function Set-TlsProtocolRegistryValue')]
    [CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = 'High')]
    [OutputType()]
    param
    (
        [Parameter(Mandatory = $true)]
        [System.Security.Authentication.SslProtocols[]]
        $Protocol,

        [Parameter()]
        [System.Management.Automation.SwitchParameter]
        $Client,

        [Parameter()]
        [System.Management.Automation.SwitchParameter]
        $SetDisabledByDefault,

        [Parameter()]
        [System.Management.Automation.SwitchParameter]
        $Force
    )

    # ShouldProcess is handled in Set-TlsProtocolRegistryValue
    Set-TlsProtocolRegistryValue @PSBoundParameters -Enable
}
#EndRegion '.\Public\Enable-TlsProtocol.ps1' 88
#Region '.\Public\Get-TlsProtocol.ps1' -1

<#
    .SYNOPSIS
        Returns configured SCHANNEL protocol settings for Server or Client.
 
    .DESCRIPTION
        Reads the `Enabled` and `DisabledByDefault` values for one or more
        SCHANNEL protocol keys and returns a PSCustomObject with the results.
        By default, all supported protocols are queried if no specific protocol
        is specified.
 
    .PARAMETER Protocol
        One or more protocol names. Accepts values from the
        `[System.Security.Authentication.SslProtocols]` enum such as `Ssl2`,
        `Ssl3`, `Tls`, `Tls11`, `Tls12`, `Tls13`. If not specified, all
        supported protocols are returned.
 
    .PARAMETER Client
        When specified, reads the `Client` key. By default the `Server` key is used.
 
    .INPUTS
        None.
 
    .OUTPUTS
        `System.Management.Automation.PSCustomObject`
 
        Returns one or more objects with the following properties: Protocol,
        Target, Enabled, DisabledByDefault, and RegistryPath.
 
    .EXAMPLE
        Get-TlsProtocol
 
        Returns the SCHANNEL protocol settings for all supported protocols for
        server-side connections.
 
    .EXAMPLE
        Get-TlsProtocol -Protocol Tls12
 
        Returns the SCHANNEL protocol settings for TLS 1.2 for server-side
        connections.
 
    .EXAMPLE
        Get-TlsProtocol -Protocol Tls12, Tls13 -Client
 
        Returns the SCHANNEL protocol settings for TLS 1.2 and TLS 1.3 for
        client-side connections.
 
    .EXAMPLE
        Get-TlsProtocol | Format-Table -AutoSize
 
        Returns all protocol settings and displays them in a formatted table.
#>

function Get-TlsProtocol
{
    [CmdletBinding()]
    [OutputType([System.Management.Automation.PSCustomObject])]
    param
    (
        [Parameter()]
        [System.Security.Authentication.SslProtocols[]]
        $Protocol,

        [Parameter()]
        [System.Management.Automation.SwitchParameter]
        $Client
    )

    if (-not $PSBoundParameters.ContainsKey('Protocol'))
    {
        $Protocol = [System.Collections.ArrayList] @(
            [System.Security.Authentication.SslProtocols]::Ssl2,
            [System.Security.Authentication.SslProtocols]::Ssl3,
            [System.Security.Authentication.SslProtocols]::Tls,
            [System.Security.Authentication.SslProtocols]::Tls11,
            [System.Security.Authentication.SslProtocols]::Tls12,
            [System.Security.Authentication.SslProtocols]::Tls13
        )

        if ([System.Enum]::GetNames([System.Security.Authentication.SslProtocols]) -notcontains 'Tls13')
        {
            $Protocol.Remove([System.Security.Authentication.SslProtocols]::Tls13)
        }
    }

    foreach ($currentProtocol in $Protocol)
    {
        $regPath = Get-TlsProtocolRegistryPath -Protocol $currentProtocol -Client:$Client

        $protocolEnabled = Get-RegistryPropertyValue -Path $regPath -Name 'Enabled' -ErrorAction SilentlyContinue
        $protocolDisabled = Get-RegistryPropertyValue -Path $regPath -Name 'DisabledByDefault' -ErrorAction SilentlyContinue

        $protocolEnabled = if ($null -ne $protocolEnabled)
        {
            [System.UInt32] $protocolEnabled
        }
        else
        {
            $null
        }

        $protocolDisabled = if ($null -ne $protocolDisabled)
        {
            [System.UInt32] $protocolDisabled
        }
        else
        {
            $null
        }

        [PSCustomObject] @{
            Protocol          = $currentProtocol
            Target            = Get-TlsProtocolTargetRegistryName -Client:$Client
            Enabled           = $protocolEnabled
            DisabledByDefault = $protocolDisabled
            RegistryPath      = $regPath
        }
    }
}
#EndRegion '.\Public\Get-TlsProtocol.ps1' 118
#Region '.\Public\Reset-TlsProtocol.ps1' -1

<#
    .SYNOPSIS
        Resets specified TLS/SSL protocols by removing SCHANNEL registry keys.
 
    .DESCRIPTION
        Removes SCHANNEL protocol registry keys under
        HKLM:\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Protocols
        for the server-side `Server` key by default. Use the `-Client` switch to
        operate on the `Client` key instead. This resets the protocol configuration
        to the Windows default behavior.
 
        If no protocol is specified, all supported protocols are reset.
 
    .PARAMETER Protocol
        One or more protocol names to reset. Accepts values from the
        `[System.Security.Authentication.SslProtocols]` enum such as `Ssl2`,
        `Ssl3`, `Tls`, `Tls11`, `Tls12`, `Tls13`. If not specified, all
        supported protocols are reset.
 
    .PARAMETER Client
        When specified, operate on the protocol `Client` registry key instead of
        the default `Server` key.
 
    .PARAMETER Force
        Suppresses confirmation prompts.
 
    .INPUTS
        None.
 
    .OUTPUTS
        None.
 
    .EXAMPLE
        Reset-TlsProtocol
 
        Resets all supported TLS/SSL protocols for server-side connections by
        removing the corresponding registry keys, restoring Windows default
        behavior.
 
    .EXAMPLE
        Reset-TlsProtocol -Protocol Tls12
 
        Resets TLS 1.2 for server-side connections by removing the corresponding
        registry key, restoring Windows default behavior.
 
    .EXAMPLE
        Reset-TlsProtocol -Protocol Tls12 -Client
 
        Resets TLS 1.2 for client-side connections.
 
    .EXAMPLE
        Reset-TlsProtocol -Protocol Ssl2, Ssl3
 
        Resets SSL 2.0 and SSL 3.0 for server-side connections.
 
    .EXAMPLE
        Reset-TlsProtocol -Client -Force
 
        Resets all supported TLS/SSL protocols for client-side connections
        without prompting for confirmation.
 
    .EXAMPLE
        Reset-TlsProtocol -Protocol Tls -Force
 
        Resets TLS 1.0 for server-side connections without prompting for
        confirmation.
#>

function Reset-TlsProtocol
{
    [CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = 'High')]
    [OutputType()]
    param
    (
        [Parameter()]
        [System.Security.Authentication.SslProtocols[]]
        $Protocol,

        [Parameter()]
        [System.Management.Automation.SwitchParameter]
        $Client,

        [Parameter()]
        [System.Management.Automation.SwitchParameter]
        $Force
    )

    # Need to do this check with Get-Variable instead of $Confirm due to strict mode.
    if ($Force.IsPresent -and -not $Confirm)
    {
        $ConfirmPreference = 'None'
    }

    if (-not $PSBoundParameters.ContainsKey('Protocol'))
    {
        $Protocol = (Get-TlsProtocol -Client:$Client).Protocol
    }

    foreach ($currentProtocol in $Protocol)
    {
        $protocolKeyName = ConvertTo-TlsProtocolRegistryKeyName -Protocol $currentProtocol
        $target = Get-TlsProtocolTargetRegistryName -Client:$Client
        $regPath = Get-TlsProtocolRegistryPath -Protocol $currentProtocol -Client:$Client

        $descriptionMessage = $script:localizedData.Reset_TlsProtocol_ShouldProcessDescription -f $protocolKeyName, $target
        $confirmationMessage = $script:localizedData.Reset_TlsProtocol_ShouldProcessConfirmation -f $protocolKeyName
        $captionMessage = $script:localizedData.Reset_TlsProtocol_ShouldProcessCaption

        if ($PSCmdlet.ShouldProcess($descriptionMessage, $confirmationMessage, $captionMessage))
        {
            if (Test-Path -Path $regPath)
            {
                try
                {
                    Remove-Item -Path $regPath -Force -ErrorAction 'Stop'

                    # Remove the parent protocol key if it has no remaining child items
                    $parentPath = Split-Path -Path $regPath -Parent

                    if ((Test-Path -Path $parentPath) -and -not (Get-ChildItem -Path $parentPath))
                    {
                        Remove-Item -Path $parentPath -Force -ErrorAction 'Stop'
                    }
                }
                catch
                {
                    $errorMessage = $script:localizedData.Reset_TlsProtocol_FailedToReset -f $currentProtocol

                    $exception = New-Exception -Message $errorMessage -ErrorRecord $_
                    $errorRecord = New-ErrorRecord -Exception $exception -ErrorId 'RTP0001' -ErrorCategory 'InvalidOperation' -TargetObject $currentProtocol
                    $PSCmdlet.ThrowTerminatingError($errorRecord)
                }
            }
        }
    }
}
#EndRegion '.\Public\Reset-TlsProtocol.ps1' 136
#Region '.\Public\Test-TlsNegotiation.ps1' -1

<#
    .SYNOPSIS
        Tests which TLS/SSL protocols can be negotiated with a target host/port.
 
    .DESCRIPTION
        Test-TlsNegotiation attempts to establish a TCP connection to the
        specified HostName and Port, then performs a TLS/SSL handshake using
        each protocol provided in `-Protocol`.
 
        It uses System.Net.Security.SslStream and ignores certificate validation
        errors on purpose (the goal is to test protocol support, not certificate
        trust).
 
        For each attempted protocol, the function returns an object indicating
        whether the handshake succeeded, and if so, which protocol and cipher
        suite were negotiated.
 
    .PARAMETER HostName
        The DNS name or IP address of the target host. Default is 'localhost'.
 
    .PARAMETER Port
        The TCP port to connect to. Default is 443.
 
    .PARAMETER Protocol
        One or more protocol names to attempt. Accepts values from the
        `[System.Security.Authentication.SslProtocols]` enum such as `Ssl2`,
        `Ssl3`, `Tls`, `Tls11`, `Tls12`, `Tls13`. If not specified, all
        supported protocols are attempted.
 
    .PARAMETER TimeoutSeconds
        Connection timeout in seconds for the TCP connect attempt. Default is 5.
 
    .INPUTS
        None.
 
    .OUTPUTS
        `System.Management.Automation.PSCustomObject`
 
        Each output object contains: HostName, Port, AttemptedProtocol, Success,
        NegotiatedProtocol, NegotiatedCipherSuite, Error, and InnerError.
 
 
        .EXAMPLE
        Test-TlsNegotiation -HostName localhost
 
        Attempts each protocol against localhost using default port 443 and returns
        the results.
 
    .EXAMPLE
        Test-TlsNegotiation -HostName localhost -Port 1433
 
        Attempts each protocol against localhost:1433 and returns the results.
 
    .EXAMPLE
        Test-TlsNegotiation -HostName localhost -Port 1433 | Format-Table -AutoSize
 
        Attempts each protocol against localhost:1433 and displays results in a
        formatted table.
 
    .EXAMPLE
        Test-TlsNegotiation -HostName sql01.contoso.com -Port 1433 -Verbose
 
        Tests protocol negotiation against sql01.contoso.com:1433 and prints
        each attempt via -Verbose.
 
    .EXAMPLE
        Test-TlsNegotiation -HostName webserver.contoso.com -Port 443 -Protocol Tls12, Tls13
 
        Tests only TLS 1.2 and TLS 1.3 negotiation against a web server on
        port 443.
 
    .NOTES
        Certificate validation is intentionally bypassed to focus solely on
        protocol support. TLS 1.3 availability depends on OS and .NET runtime.
#>

function Test-TlsNegotiation
{
    [CmdletBinding()]
    [OutputType([System.Management.Automation.PSCustomObject])]
    param
    (
        [Parameter(Position = 0)]
        [ValidateNotNullOrEmpty()]
        [System.String]
        $HostName = 'localhost',

        [Parameter(Position = 1)]
        [ValidateRange(1, 65535)]
        [System.UInt16]
        $Port = 443,

        [Parameter()]
        [System.Security.Authentication.SslProtocols[]]
        $Protocol,

        [Parameter()]
        [ValidateRange(1, 600)]
        [System.UInt32]
        $TimeoutSeconds = 5
    )

    if (-not $PSBoundParameters.ContainsKey('Protocol'))
    {
        $Protocol = @(
            [System.Security.Authentication.SslProtocols]::Ssl2,
            [System.Security.Authentication.SslProtocols]::Ssl3,
            [System.Security.Authentication.SslProtocols]::Tls,
            [System.Security.Authentication.SslProtocols]::Tls11,
            [System.Security.Authentication.SslProtocols]::Tls12,
            [System.Security.Authentication.SslProtocols]::Tls13
        )
    }

    # Equivalent to: (sender, certificate, chain, sslPolicyErrors) => true
    $certValidationCallback = [System.Net.Security.RemoteCertificateValidationCallback] {
        param
        (
            [Parameter()]
            $sender,

            [Parameter()]
            $certificate,

            [Parameter()]
            $chain,

            [Parameter()]
            $sslPolicyErrors
        )

        return $true
    }

    foreach ($currentProtocol in $Protocol)
    {
        Write-Verbose -Message ($script:localizedData.Test_TlsNegotiation_TryingProtocol -f $currentProtocol)

        $client = $null
        $sslStream = $null

        try
        {
            $client = [System.Net.Sockets.TcpClient]::new()

            # Timeout logic (TcpClient.Connect() has no built-in timeout)
            $iar = $client.BeginConnect($HostName, $Port, $null, $null)

            try
            {
                if (-not $iar.AsyncWaitHandle.WaitOne([System.TimeSpan]::FromSeconds($TimeoutSeconds), $false))
                {
                    $message = $script:localizedData.Test_TlsNegotiation_ConnectTimeout -f $TimeoutSeconds

                    $exception = New-Exception -Message $message
                    $errorRecord = New-ErrorRecord -Exception $exception -ErrorId 'TTN0002' -ErrorCategory 'OperationTimeout' -TargetObject $HostName
                    $PSCmdlet.ThrowTerminatingError($errorRecord)
                }
            }
            finally
            {
                $iar.AsyncWaitHandle.Dispose()
            }

            $client.EndConnect($iar)

            $sslStream = [System.Net.Security.SslStream]::new(
                $client.GetStream(),
                $false,
                $certValidationCallback,
                $null
            )

            # Equivalent to SslClientAuthenticationOptions { TargetHost = host; EnabledSslProtocols = protocol }
            $opts = [System.Net.Security.SslClientAuthenticationOptions]::new()

            $opts.TargetHost = $HostName
            $opts.EnabledSslProtocols = $currentProtocol

            $sslStream.AuthenticateAsClient($opts)

            [PSCustomObject] @{
                HostName              = $HostName
                Port                  = $Port
                AttemptedProtocol     = $currentProtocol
                Success               = $true
                NegotiatedProtocol    = $sslStream.SslProtocol
                NegotiatedCipherSuite = $sslStream.NegotiatedCipherSuite
                Error                 = $null
                InnerError            = $null
            }
        }
        catch
        {
            $innerExceptionMessage = if ($_.Exception.InnerException)
            {
                $_.Exception.InnerException.Message
            }
            else
            {
                $null
            }

            [PSCustomObject] @{
                HostName              = $HostName
                Port                  = $Port
                AttemptedProtocol     = $currentProtocol
                Success               = $false
                NegotiatedProtocol    = $null
                NegotiatedCipherSuite = $null
                Error                 = $_.Exception.Message
                InnerError            = $innerExceptionMessage
            }
        }
        finally
        {
            if ($sslStream)
            {
                $sslStream.Dispose()
            }

            if ($client)
            {
                $client.Close()
                $client.Dispose()
            }
        }
    }
}
#EndRegion '.\Public\Test-TlsNegotiation.ps1' 229
#Region '.\Public\Test-TlsProtocol.ps1' -1

<#
    .SYNOPSIS
        Tests if specified TLS/SSL protocols are enabled on the local machine.
 
    .DESCRIPTION
        Tests one or more SCHANNEL protocol keys under
        HKLM:\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Protocols
        to determine whether the protocol is enabled or disabled for server-side
        or client-side connections. Returns `$true` if all specified protocols
        match the expected state, or `$false` if any do not.
 
    .PARAMETER Protocol
        One or more protocol names to check. Accepts values from the
        `[System.Security.Authentication.SslProtocols]` enum such as `Ssl2`,
        `Ssl3`, `Tls`, `Tls11`, `Tls12`, `Tls13`.
 
    .PARAMETER Client
        When specified, checks the protocol `Client` registry key instead of the
        default `Server` key.
 
    .PARAMETER Disabled
        When specified, tests that the protocol(s) are disabled. By default the
        command tests that the protocol(s) are enabled.
 
    .INPUTS
        None.
 
    .OUTPUTS
        `System.Boolean`
 
        Returns `$true` if all specified protocols match the expected state,
        `$false` otherwise.
 
    .EXAMPLE
        Test-TlsProtocol -Protocol Tls12
 
        Tests if TLS 1.2 is enabled for server-side connections.
 
    .EXAMPLE
        Test-TlsProtocol -Protocol Tls13 -Client
 
        Tests if TLS 1.3 is enabled for client-side connections.
 
    .EXAMPLE
        Test-TlsProtocol -Protocol Tls12 -Disabled
 
        Tests if TLS 1.2 is disabled for server-side connections.
 
    .EXAMPLE
        Test-TlsProtocol -Protocol Ssl2, Ssl3 -Disabled
 
        Tests if both SSL 2.0 and SSL 3.0 are disabled for server-side
        connections. Returns `$true` only if both protocols are disabled.
 
    .EXAMPLE
        Test-TlsProtocol -Protocol Tls12 -Client -Disabled
 
        Tests if TLS 1.2 is disabled for client-side connections.
#>

function Test-TlsProtocol
{
    [CmdletBinding()]
    [OutputType([System.Boolean])]
    param
    (
        [Parameter(Mandatory = $true)]
        [System.Security.Authentication.SslProtocols[]]
        $Protocol,

        [Parameter()]
        [System.Management.Automation.SwitchParameter]
        $Client,

        [Parameter()]
        [System.Management.Automation.SwitchParameter]
        $Disabled
    )

    foreach ($currentProtocol in $Protocol)
    {
        $regPath = Get-TlsProtocolRegistryPath -Protocol $currentProtocol -Client:$Client
        $protocolEnabled = Get-RegistryPropertyValue -Path $regPath -Name 'Enabled' -ErrorAction SilentlyContinue
        $protocolDisabled = Get-RegistryPropertyValue -Path $regPath -Name 'DisabledByDefault' -ErrorAction SilentlyContinue
        $protocolEnabled = if ($null -ne $protocolEnabled)
        {
            [System.Int32] $protocolEnabled
        }
        else
        {
            $null
        }

        $protocolDisabled = if ($null -ne $protocolDisabled)
        {
            [System.Int32] $protocolDisabled
        }
        else
        {
            $null
        }
        if ($Disabled.IsPresent)
        {
            # Missing keys imply the protocol is enabled by default, so -Disabled should fail
            if ($null -eq $protocolEnabled -and $null -eq $protocolDisabled)
            {
                return $false
            }

            # Consider protocol disabled when Enabled != 1 or DisabledByDefault == 1
            if (($null -ne $protocolEnabled -and $protocolEnabled -ne 1) -or ($null -ne $protocolDisabled -and $protocolDisabled -eq 1))
            {
                continue
            }
            else
            {
                return $false
            }
        }
        else
        {
            if ($null -eq $protocolEnabled -and $null -eq $protocolDisabled)
            {
                continue
            }

            if ((($protocolEnabled -eq 1 -and ($protocolDisabled -eq 0 -or $null -eq $protocolDisabled)) -or ($null -eq $protocolEnabled -and $protocolDisabled -eq 0)))
            {
                continue
            }
            else
            {
                return $false
            }
        }
    }

    return $true
}
#EndRegion '.\Public\Test-TlsProtocol.ps1' 139