WSManDsc.psm1

#Region '.\prefix.ps1' -1

using module .\Modules\DscResource.Base

# Import nested, 'DscResource.Common' module
$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' 8
#Region '.\Enum\005.WSManAuthCbtHardeningLevel.ps1' -1

<#
    .SYNOPSIS
        The possible states for the DSC resource parameter WSManAuthCbtHardeningLevel.
#>

enum WSManAuthCbtHardeningLevel
{
    Strict = 1
    Relaxed
    None
}
#EndRegion '.\Enum\005.WSManAuthCbtHardeningLevel.ps1' 11
#Region '.\Enum\005.WSManSubjectFormat.ps1' -1

<#
    .SYNOPSIS
        The possible states for the DSC resource parameter WSManSubjectFormat.
#>

enum WSManSubjectFormat
{
    Both = 1
    FQDNOnly
    NameOnly
}
#EndRegion '.\Enum\005.WSManSubjectFormat.ps1' 11
#Region '.\Enum\005.WSManTransport.ps1' -1

<#
    .SYNOPSIS
        The possible states for the DSC resource parameter WSManTransport.
#>

enum WSManTransport
{
    HTTP = 1
    HTTPS
}
#EndRegion '.\Enum\005.WSManTransport.ps1' 10
#Region '.\Classes\001.WSManReason.ps1' -1

<#
    .SYNOPSIS
        The reason a property of a DSC resource is not in desired state.
 
    .DESCRIPTION
        A DSC resource can have a read-only property `Reasons` that the compliance
        part (audit via Azure Policy) of Azure AutoManage Machine Configuration
        uses. The property Reasons holds an array of WSManReason. Each WSManReason
        explains why a property of a DSC resource is not in desired state.
#>


class WSManReason
{
    [DscProperty()]
    [System.String]
    $Code

    [DscProperty()]
    [System.String]
    $Phrase
}
#EndRegion '.\Classes\001.WSManReason.ps1' 22
#Region '.\Classes\010.WSManConfigBase.ps1' -1

<#
    .SYNOPSIS
        `WSManConfigBase` contains properties and methods which are shared across all WSMan*Config DSC Resources.
 
    .PARAMETER IsSingleInstance
        Specifies the resource is a single instance, the value must be 'Yes'.
 
    .PARAMETER Reasons
        Returns the reason a property is not in desired state.
#>


class WSManConfigBase : ResourceBase
{
    [DscProperty(Key)]
    [ValidateSet('Yes')]
    [System.String]
    $IsSingleInstance

    [DscProperty(NotConfigurable)]
    [WSManReason[]]
    $Reasons

    hidden [System.String] $ResourceURI
    hidden [System.Boolean] $HasAuthContainer = $false

    WSManConfigBase () : base ($PSScriptRoot)
    {
        # These properties will not be enforced.
        $this.ExcludeDscProperties = @(
            'IsSingleInstance'
        )
    }

    # Base method Get() call this method to get the current state as a Hashtable.
    [System.Collections.Hashtable] GetCurrentState([System.Collections.Hashtable] $properties)
    {
        $state = @{}

        # Get the properties that have a value. Assert has checked at least one property is set.
        $props = $this | Get-DscProperty -Attribute @('Optional') -HasValue

        $uri = 'WSMan:\{0}' -f $this.ResourceURI

        # Get the desired state, only check the properties that are set as some will be set to a default value.
        $currentState = [System.Collections.Generic.List[System.Object]]::new()

        $currentState.AddRange(
            @(Get-ChildItem -Path $uri).Where(
                { $_.Name -in $props.Keys -and $_.Type -ne 'Container' }
            )
        )

        if ($this.HasAuthContainer)
        {
            $childProperties = @(Get-ChildItem -Path ('{0}\Auth' -f $uri))
            $mappedProperties = @($this.MapFromAuthContainer($childProperties).Where(
                    { $_.Name -in $props.Keys }
                ))

            $currentState.AddRange($mappedProperties)
        }

        foreach ($property in $currentState)
        {
            $targetType = $this.($property.Name).GetType()
            if ($targetType -eq [System.Boolean])
            {
                # Parse string "true"/"false" correctly (case-insensitive)
                $state[$property.Name] = [System.Convert]::ToBoolean($property.Value)
                continue
            }

            $state[$property.Name] = [System.Management.Automation.LanguagePrimitives]::ConvertTo(
                $property.Value,
                $targetType
            )
        }

        return $state
    }

    <#
        Base method Set() calls this method with the properties that should be
        enforced and that are not in desired state.
    #>

    hidden [void] Modify([System.Collections.Hashtable] $properties)
    {
        $baseUri = 'WSMan:\{0}' -f $this.ResourceURI

        foreach ($property in $properties.GetEnumerator())
        {
            if ($property.Name.StartsWith('Auth'))
            {
                $property.Name = $property.Name -replace '^Auth', ''
                Set-Item -Path ('{0}\Auth\{1}' -f $baseUri, $property.Name) -Value $property.Value -Force
                continue
            }

            Set-Item -Path ('{0}\{1}' -f $baseUri, $property.Name) -Value $property.Value -Force
        }
    }

    hidden [System.Object[]] MapFromAuthContainer([System.Object[]] $properties)
    {
        foreach ($property in $properties)
        {
            # Need to add auth to the beginning.
            $property.Name = 'Auth{0}' -f $property.Name
        }

        return $properties
    }
}
#EndRegion '.\Classes\010.WSManConfigBase.ps1' 114
#Region '.\Classes\020.WSManConfig.ps1' -1

<#
    .SYNOPSIS
        The `WSManConfig` DSC resource is used to configure general WS-Man settings.
 
    .DESCRIPTION
        This resource is used to edit WS-Management configuration.
 
    .PARAMETER MaxEnvelopeSizekb
        Specifies the WS-Man maximum envelope size in KB. The minimum value is 32 and the maximum is 4294967295.
 
    .PARAMETER MaxTimeoutms
        Specifies the WS-Man maximum timeout in milliseconds. The minimum value is 500 and the maximum is 4294967295.
 
    .PARAMETER MaxBatchItems
        Specifies the WS-Man maximum batch items. The minimum value is 1 and the maximum is 4294967295.
#>


[DscResource()]
class WSManConfig : WSManConfigBase
{
    [DscProperty()]
    [ValidateRange(32, 4294967295)]
    [Nullable[System.Uint32]]
    $MaxEnvelopeSizekb

    [DscProperty()]
    [ValidateRange(500, 4294967295)]
    [Nullable[System.Uint32]]
    $MaxTimeoutms

    [DscProperty()]
    [ValidateRange(1, 4294967295)]
    [Nullable[System.Uint32]]
    $MaxBatchItems

    WSManConfig () : base ()
    {
        $this.ResourceURI = 'localhost'
    }

    [WSManConfig] Get()
    {
        # Call the base method to return the properties.
        return ([ResourceBase] $this).Get()
    }

    [void] Set()
    {
        # Call the base method to enforce the properties.
        ([ResourceBase] $this).Set()
    }

    [System.Boolean] Test()
    {
        # Call the base method to test all of the properties that should be enforced.
        return ([ResourceBase] $this).Test()
    }

    <#
        Base method Assert() call this method with the properties that was assigned
        a value.
    #>

    hidden [void] AssertProperties([System.Collections.Hashtable] $properties)
    {
        $assertBoundParameterParameters = @{
            BoundParameterList = $properties
            RequiredParameter  = @(
                'MaxEnvelopeSizekb'
                'MaxTimeoutms'
                'MaxBatchItems'
            )
            RequiredBehavior   = 'Any'
        }

        Assert-BoundParameter @assertBoundParameterParameters
    }
}
#EndRegion '.\Classes\020.WSManConfig.ps1' 78
#Region '.\Classes\020.WSManListener.ps1' -1

<#
    .SYNOPSIS
        The `WSManListener` DSC resource is used to create, modify, or remove
        WSMan listeners.
 
    .DESCRIPTION
        This resource is used to create, edit or remove WS-Management HTTP/HTTPS listeners.
 
        ### SubjectFormat Parameter Notes
 
        The subject format is used to determine how the certificate for the listener
        will be identified. It must be one of the following:
 
        - **Both**: Look for a certificate with a subject matching the computer FQDN.
            If one can't be found the flat computer name will be used. If neither
            can be found then the listener will not be created.
        - **FQDN**: Look for a certificate with a subject matching the computer FQDN
            only. If one can't be found then the listener will not be created.
        - **ComputerName**: Look for a certificate with a subject matching the computer
        FQDN only. If one can't be found then the listener will not be created.
 
    .PARAMETER Transport
        The transport type of WS-Man Listener.
 
    .PARAMETER Ensure
        Specifies whether the WS-Man Listener should exist.
 
    .PARAMETER Port
        The port the WS-Man Listener should use. Defaults to 5985 for HTTP and 5986 for HTTPS listeners.
 
    .PARAMETER Address
        The Address that the WS-Man Listener will be bound to. The default is * (any address).
 
    .PARAMETER Issuer
        The Issuer of the certificate to use for the HTTPS WS-Man Listener if a thumbprint is
        not specified.
 
    .PARAMETER SubjectFormat
        The format used to match the certificate subject to use for an HTTPS WS-Man Listener
        if a thumbprint is not specified.
 
    .PARAMETER MatchAlternate
        Should the FQDN/Name be used to also match the certificate alternate subject for an HTTPS WS-Man
        Listener if a thumbprint is not specified.
 
    .PARAMETER BaseDN
        This is the BaseDN (path part of the full Distinguished Name) used to identify the certificate
        to use for the HTTPS WS-Man Listener if a thumbprint is not specified.
 
    .PARAMETER CertificateThumbprint
        The Thumbprint of the certificate to use for the HTTPS WS-Man Listener.
 
    .PARAMETER HostName
        The HostName of WS-Man Listener.
 
    .PARAMETER Enabled
        Returns true if the existing WS-Man Listener is enabled.
 
    .PARAMETER URLPrefix
        The URL Prefix of the existing WS-Man Listener.
 
    .PARAMETER Reasons
        Returns the reason a property is not in desired state.
#>


[DscResource()]
class WSManListener : ResourceBase
{
    [DscProperty(Key)]
    [WSManTransport]
    $Transport

    [DscProperty(Mandatory)]
    [Ensure]
    $Ensure

    [DscProperty()]
    [ValidateRange(0, 65535)]
    [Nullable[System.UInt16]]
    $Port

    [DscProperty()]
    [System.String]
    $Address

    [DscProperty()]
    [System.String]
    $Issuer

    [DscProperty()]
    [WSManSubjectFormat]
    $SubjectFormat

    [DscProperty()]
    [Nullable[System.Boolean]]
    $MatchAlternate

    [DscProperty()]
    [System.String]
    $BaseDN

    [DscProperty()]
    [System.String]
    $CertificateThumbprint

    [DscProperty()]
    [System.String]
    $HostName

    [DscProperty(NotConfigurable)]
    [System.Boolean]
    $Enabled

    [DscProperty(NotConfigurable)]
    [System.String]
    $URLPrefix

    [DscProperty(NotConfigurable)]
    [WSManReason[]]
    $Reasons

    WSManListener () : base ($PSScriptRoot)
    {
        # These properties will not be enforced.
        $this.ExcludeDscProperties = @(
            'Issuer'
            'SubjectFormat'
            'MatchAlternate'
            'BaseDN'
        )
    }

    [WSManListener] Get()
    {
        # Call the base method to return the properties.
        return ([ResourceBase] $this).Get()
    }

    # Base method Get() call this method to get the current state as a Hashtable.
    [System.Collections.Hashtable] GetCurrentState([System.Collections.Hashtable] $properties)
    {
        $getParameters = @{
            Transport = $properties.Transport
        }

        $state = @{}

        $getCurrentStateResult = Get-Listener @getParameters

        if ($getCurrentStateResult)
        {
            $state = @{
                Transport             = [WSManTransport] $getCurrentStateResult.Transport
                Port                  = [System.UInt16] $getCurrentStateResult.Port
                Address               = $getCurrentStateResult.Address

                CertificateThumbprint = $getCurrentStateResult.CertificateThumbprint
                Hostname              = $getCurrentStateResult.Hostname

                Enabled               = $getCurrentStateResult.Enabled
                URLPrefix             = $getCurrentStateResult.URLPrefix
            }

            if ($getCurrentStateResult.CertificateThumbprint)
            {
                $state.Issuer = (Find-Certificate -CertificateThumbprint $getCurrentStateResult.CertificateThumbprint).Issuer
            }
        }

        return $state
    }

    [void] Set()
    {
        # Call the base method to enforce the properties.
        ([ResourceBase] $this).Set()
    }

    <#
        Base method Set() call this method with the properties that should be
        enforced and that are not in desired state.
    #>

    hidden [void] Modify([System.Collections.Hashtable] $properties)
    {
        if ($properties.ContainsKey('Ensure') -and $properties.Ensure -eq [Ensure]::Absent -and $this.Ensure -eq [Ensure]::Absent)
        {
            # Ensure was not in desired state so the resource should be removed
            $this.RemoveInstance()
        }
        elseif ($properties.ContainsKey('Ensure') -and $properties.Ensure -eq [Ensure]::Present -and $this.Ensure -eq [Ensure]::Present)
        {
            # Ensure was not in the desired state so the resource should be created
            $this.NewInstance()
        }
        elseif ($this.Ensure -eq [Ensure]::Present)
        {
            # Resource exists but one or more properties are not in the desired state
            $this.RemoveInstance()
            $this.NewInstance()
        }
    }

    [System.Boolean] Test()
    {
        # Call the base method to test all of the properties that should be enforced.
        return ([ResourceBase] $this).Test()
    }

    <#
        Base method Assert() call this method with the properties that was assigned
        a value.
    #>

    hidden [void] AssertProperties([System.Collections.Hashtable] $properties)
    {
        $assertBoundParameterParameters = @{
            BoundParameterList     = $properties
            MutuallyExclusiveList1 = @(
                'Issuer'
                'BaseDN'
                'SubjectFormat'
                'MatchAlternate'
            )
            MutuallyExclusiveList2 = @(
                'CertificateThumbprint'
                'HostName'
            )
        }

        Assert-BoundParameter @assertBoundParameterParameters
    }

    hidden [void] NewInstance()
    {
        # Get the port if it's not provided
        if (-not $this.Port)
        {
            $this.Port = Get-DefaultPort -Transport $this.Transport
        }

        # Get the Address if it's not provided
        if (-not $this.Address)
        {
            $this.Address = '*'
        }

        Write-Verbose -Message ($this.localizedData.CreatingListenerMessage -f $this.Transport, $this.Port)

        $selectorSet = @{
            Transport = $this.Transport
            Address   = $this.Address
        }

        $valueSet = @{
            Port = $this.Port
        }


        if ($this.Transport -eq [WSManTransport]::HTTPS)
        {
            $findCertificateParams = $this | Get-DscProperty -Attribute @('Optional') -ExcludeName @('Port', 'Address') -HasValue

            $certificate = Find-Certificate @findCertificateParams
            [System.String] $thumbprint = $certificate.Thumbprint

            if ($thumbprint)
            {
                $valueSet.CertificateThumbprint = $thumbprint

                if ([System.String]::IsNullOrEmpty($this.Hostname))
                {
                    $valueSet.HostName = [System.Net.Dns]::GetHostEntry((Get-ComputerName)).Hostname
                }
                else
                {
                    $valueSet.HostName = $this.HostName
                }
            }
            else
            {
                # A certificate could not be found to use for the HTTPS listener
                New-ArgumentException -Message (
                    $this.localizedData.ListenerCreateFailNoCertError -f $this.Transport, $this.Port
                ) -Argument 'Issuer'
            } # if
        }

        New-WSManInstance -ResourceURI 'winrm/config/Listener' -SelectorSet $selectorSet -ValueSet $valueSet -ErrorAction Stop
    }

    hidden [void] RemoveInstance()
    {
        Write-Verbose -Message ($this.localizedData.ListenerExistsRemoveMessage -f $this.Transport, $this.Address)

        $selectorSet = @{
            Transport = [System.String] $this.Transport
            Address   = '*'
        }

        if ($this.Address)
        {
            $selectorSet.Address = $this.Address
        }

        Remove-WSManInstance -ResourceURI 'winrm/config/Listener' -SelectorSet $selectorSet
    }
}
#EndRegion '.\Classes\020.WSManListener.ps1' 307
#Region '.\Classes\020.WSManServiceConfig.ps1' -1

<#
    .SYNOPSIS
        The `WSManServiceConfig` DSC resource is used to configure WS-Man service specific settings.
 
    .DESCRIPTION
        This resource is used configure WS-Man Service settings.
 
    .PARAMETER RootSDDL
        Specifies the security descriptor that controls remote access to the listener.
 
    .PARAMETER MaxConnections
        Specifies the maximum number of active requests that the service can process simultaneously.
 
    .PARAMETER MaxConcurrentOperationsPerUser
        Specifies the maximum number of concurrent operations that any user can remotely open on the
        same system.
 
    .PARAMETER EnumerationTimeoutMS
        Specifies the idle time-out in milliseconds between Pull messages.
 
    .PARAMETER MaxPacketRetrievalTimeSeconds
        Specifies the maximum length of time, in seconds, the WinRM service takes to retrieve a packet.
 
    .PARAMETER AllowUnencrypted
        Allows the client computer to request unencrypted traffic.
 
    .PARAMETER AuthBasic
        Allows the WinRM service to use Basic authentication.
 
    .PARAMETER AuthKerberos
        Allows the WinRM service to use Kerberos authentication.
 
    .PARAMETER AuthNegotiate
        Allows the WinRM service to use Negotiate authentication.
 
    .PARAMETER AuthCertificate
        Allows the WinRM service to use client certificate-based authentication.
 
    .PARAMETER AuthCredSSP
        Allows the WinRM service to use Credential Security Support Provider (CredSSP) authentication.
 
    .PARAMETER AuthCbtHardeningLevel
        Specifies the hardening level for Channel Binding Tokens (CBT) used in authentication.
 
    .PARAMETER EnableCompatibilityHttpListener
        Specifies whether the compatibility HTTP listener is enabled.
 
    .PARAMETER EnableCompatibilityHttpsListener
        Specifies whether the compatibility HTTPS listener is enabled.
#>


[DscResource(RunAsCredential = 'Optional')]
class WSManServiceConfig : WSManConfigBase
{
    [DscProperty()]
    [System.String]
    $RootSDDL

    [DscProperty()]
    [Nullable[System.Uint32]]
    $MaxConnections

    [DscProperty()]
    [Nullable[System.Uint32]]
    $MaxConcurrentOperationsPerUser

    [DscProperty()]
    [Nullable[System.Uint32]]
    $EnumerationTimeoutMS

    [DscProperty()]
    [Nullable[System.Uint32]]
    $MaxPacketRetrievalTimeSeconds

    [DscProperty()]
    [Nullable[System.Boolean]]
    $AllowUnencrypted

    [DscProperty()]
    [Nullable[System.Boolean]]
    $AuthBasic

    [DscProperty()]
    [Nullable[System.Boolean]]
    $AuthKerberos

    [DscProperty()]
    [Nullable[System.Boolean]]
    $AuthNegotiate

    [DscProperty()]
    [Nullable[System.Boolean]]
    $AuthCertificate

    [DscProperty()]
    [Nullable[System.Boolean]]
    $AuthCredSSP

    [DscProperty()]
    [WSManAuthCbtHardeningLevel]
    $AuthCbtHardeningLevel

    [DscProperty()]
    [Nullable[System.Boolean]]
    $EnableCompatibilityHttpListener

    [DscProperty()]
    [Nullable[System.Boolean]]
    $EnableCompatibilityHttpsListener

    WSManServiceConfig () : base ()
    {
        $this.ResourceURI = 'localhost\Service'
        $this.HasAuthContainer = $true
    }

    [WSManServiceConfig] Get()
    {
        # Call the base method to return the properties.
        return ([ResourceBase] $this).Get()
    }

    [void] Set()
    {
        # Call the base method to enforce the properties.
        ([ResourceBase] $this).Set()
    }

    [System.Boolean] Test()
    {
        # Call the base method to test all of the properties that should be enforced.
        return ([ResourceBase] $this).Test()
    }

    <#
        Base method Assert() call this method with the properties that was assigned
        a value.
    #>

    hidden [void] AssertProperties([System.Collections.Hashtable] $properties)
    {
        $assertBoundParameterParameters = @{
            BoundParameterList = $properties
            RequiredParameter  = @(
                'RootSDDL'
                'MaxConnections'
                'EnumerationTimeoutMS'
                'MaxPacketRetrievalTimeSeconds'
                'AllowUnencrypted'
                'AuthBasic'
                'AuthKerberos'
                'AuthNegotiate'
                'AuthCertificate'
                'AuthCredSSP'
                'AuthCbtHardeningLevel'
                'EnableCompatibilityHttpListener'
                'EnableCompatibilityHttpsListener'
            )
            RequiredBehavior   = 'Any'
        }

        Assert-BoundParameter @assertBoundParameterParameters
    }
}
#EndRegion '.\Classes\020.WSManServiceConfig.ps1' 164
#Region '.\Private\Find-Certificate.ps1' -1

<#
    .SYNOPSIS
        Finds the certificate to use for the HTTPS WS-Man Listener
 
    .PARAMETER Issuer
        The Issuer of the certificate to use for the HTTPS WS-Man Listener if a thumbprint is
        not specified.
 
    .PARAMETER SubjectFormat
        The format used to match the certificate subject to use for an HTTPS WS-Man Listener
        if a thumbprint is not specified.
 
    .PARAMETER MatchAlternate
        Should the FQDN/Name be used to also match the certificate alternate subject for an HTTPS WS-Man
        Listener if a thumbprint is not specified.
 
    .PARAMETER BaseDN
        This is the BaseDN (path part of the full Distinguished Name) used to identify the certificate
        to use for the HTTPS WS-Man Listener if a thumbprint is not specified.
 
    .PARAMETER CertificateThumbprint
        The Thumbprint of the certificate to use for the HTTPS WS-Man Listener.
#>

function Find-Certificate
{
    [CmdletBinding()]
    [OutputType([System.Security.Cryptography.X509Certificates.X509Certificate2])]
    param
    (
        [Parameter()]
        [System.String]
        $Issuer,

        [Parameter()]
        [WSManSubjectFormat]
        $SubjectFormat = [WSManSubjectFormat]::Both,

        [Parameter()]
        [System.Boolean]
        $MatchAlternate,

        [Parameter()]
        [System.String]
        $BaseDN,

        [Parameter()]
        [System.String]
        $CertificateThumbprint,

        [Parameter()]
        [System.String]
        $Hostname
    )

    if ($PSBoundParameters.ContainsKey('CertificateThumbprint'))
    {
        Write-Verbose -Message ($script:localizedData.FindCertificate_ByThumbprintMessage -f $CertificateThumbprint)

        $certificate = Get-ChildItem -Path Cert:\localmachine\my | Where-Object -FilterScript {
                ($_.Thumbprint -eq $CertificateThumbprint)
        } | Select-Object -First 1
    }
    else
    {
        # First try and find a certificate that is used to the FQDN of the machine
        if ($SubjectFormat -in [WSManSubjectFormat]::Both, [WSManSubjectFormat]::FQDNOnly)
        {
            # Lookup the certificate using the FQDN of the machine
            if ([System.String]::IsNullOrEmpty($Hostname))
            {
                $Hostname = [System.Net.Dns]::GetHostEntry((Get-ComputerName)).Hostname
            }
            $Subject = "CN=$Hostname"

            if ($PSBoundParameters.ContainsKey('BaseDN'))
            {
                $Subject = "$Subject, $BaseDN"
            } # if

            if ($MatchAlternate)
            {
                # Try and lookup the certificate using the subject and the alternate name
                Write-Verbose -Message ($script:localizedData.FindCertificate_AlternateMessage -f $Subject, $Issuer, $Hostname)

                $certificate = (Get-ChildItem -Path Cert:\localmachine\my | Where-Object -FilterScript {
                        ($_.Extensions.EnhancedKeyUsages.FriendlyName -contains 'Server Authentication') -and
                        ($_.Issuer -eq $Issuer) -and
                        ($Hostname -in $_.DNSNameList.Unicode) -and
                        ($_.Subject -eq $Subject)
                    } | Select-Object -First 1)
            }
            else
            {
                # Try and lookup the certificate using the subject name
                Write-Verbose -Message ($script:localizedData.FindCertificate_Message -f $Subject, $Issuer)

                $certificate = Get-ChildItem -Path Cert:\localmachine\my | Where-Object -FilterScript {
                        ($_.Extensions.EnhancedKeyUsages.FriendlyName -contains 'Server Authentication') -and
                        ($_.Issuer -eq $Issuer) -and
                        ($_.Subject -eq $Subject)
                } | Select-Object -First 1
            } # if
        }

        if (-not $certificate -and ($SubjectFormat -in [WSManSubjectFormat]::Both, [WSManSubjectFormat]::NameOnly))
        {
            # If could not find an FQDN cert, try for one issued to the computer name
            [System.String] $Hostname = Get-ComputerName
            [System.String] $Subject = "CN=$Hostname"

            if ($PSBoundParameters.ContainsKey('BaseDN'))
            {
                $Subject = "$Subject, $BaseDN"
            } # if

            if ($MatchAlternate)
            {
                # Try and lookup the certificate using the subject and the alternate name
                Write-Verbose -Message ($script:localizedData.FindCertificate_AlternateMessage -f $Subject, $Issuer, $Hostname)

                $certificate = Get-ChildItem -Path Cert:\localmachine\my | Where-Object -FilterScript {
                        ($_.Extensions.EnhancedKeyUsages.FriendlyName -contains 'Server Authentication') -and
                        ($_.Issuer -eq $Issuer) -and
                        ($Hostname -in $_.DNSNameList.Unicode) -and
                        ($_.Subject -eq $Subject)
                } | Select-Object -First 1
            }
            else
            {
                # Try and lookup the certificate using the subject name
                Write-Verbose -Message ($script:localizedData.FindCertificate_Message -f $Subject, $Issuer)

                $certificate = Get-ChildItem -Path Cert:\localmachine\my | Where-Object -FilterScript {
                    ($_.Extensions.EnhancedKeyUsages.FriendlyName -contains 'Server Authentication') -and
                    $_.Issuer -eq $Issuer -and
                    $_.Subject -eq $Subject
                } | Select-Object -First 1
            } # if
        } # if
    } # if

    if ($certificate)
    {
        Write-Verbose -Message ($script:localizedData.FindCertificate_FoundMessage -f $certificate.thumbprint)
    }
    else
    {
        Write-Verbose -Message ($script:localizedData.FindCertificate_NotFoundMessage)
    } # if

    return $certificate
}
#EndRegion '.\Private\Find-Certificate.ps1' 153
#Region '.\Private\Get-DefaultPort.ps1' -1

<#
    .SYNOPSIS
        Returns the port to use for the listener based on the transport and port.
 
    .PARAMETER Transport
        The transport type of WS-Man Listener.
 
    .PARAMETER Port
        The port the WS-Man Listener should use. Defaults to 5985 for HTTP and 5986 for HTTPS listeners.
#>

function Get-DefaultPort
{
    [CmdletBinding()]
    [OutputType([System.UInt16])]
    param
    (
        [Parameter(Mandatory = $true)]
        [WSManTransport]
        $Transport,

        [Parameter()]
        [System.UInt16]
        $Port
    )

    process
    {
        if (-not $Port)
        {
            # Set the default port because none was provided
            if ($Transport -eq [WSManTransport]::HTTP)
            {
                $Port = 5985
            }
            else
            {
                $Port = 5986
            }
        }

        return $Port
    }
}
#EndRegion '.\Private\Get-DefaultPort.ps1' 44
#Region '.\Private\Get-Listener.ps1' -1

<#
    .SYNOPSIS
        Looks up a WS-Man listener on the machine and returns the details.
 
    .PARAMETER Transport
        The transport type of WS-Man Listener.
#>

function Get-Listener
{
    [CmdletBinding()]
    [OutputType([System.Collections.Hashtable])]
    param
    (
        [Parameter(Mandatory = $true)]
        [WSManTransport]
        $Transport
    )

    $listeners = @(Get-WSManInstance -ResourceURI 'winrm/config/Listener' -Enumerate)

    if ($listeners)
    {
        return $listeners.Where(
            { ($_.Transport -eq $Transport) -and ($_.Source -ne 'Compatibility') }
        )
    }
}
#EndRegion '.\Private\Get-Listener.ps1' 28