DSCResources/BMD_cWSManListener/BMD_cWSManListener.psm1

#######################################################################################
# cWSManListener : DSC Resource that will set/test/get the WS-Man Listerner on a
# specified port.
#######################################################################################
 
data LocalizedData
{
    # culture="en-US"
    ConvertFrom-StringData -StringData @'
GettingListenerMessage=Getting Listener.
ListenerExistsMessage={0} Listener exists.
ListenerDoesNotExistMessage={0} Listener does not exist.
SettingListenerMessage=Setting Listener.
EnsureListenerExistsMessage=Ensuring {0} Listener on port {1} exists.
EnsureListenerDoesNotExistMessage=Ensuring {0} Listener on port {1} does not exist.
ListenerExistsRemoveMessage={0} Listener on port {1} exists. Removing.
ListenerOnPortDoesNotExistMessage={0} Listener on port {1} does not exist.
CreatingListenerMessage=Creating {0} Listener on port {1}.
ListenerCreateFailNoCertError=Failed to create {0} Listener on port {1} because a applicable certificate could not be found.
TestingListenerMessage=Testing Listener.
ListenerOnWrongPortMessage={0} Listener is on port {1}, should be on {2}. Change required.
ListenerOnWrongAddressMessage={0} Listener is bound to {1}, should be {2}. Change required.
ListenerDoesNotExistButShouldMessage={0} Listener does not exist but should. Change required.
ListenerExistsButShouldNotMessage={0} Listener exists but should not. Change required.
ListenerDoesNotExistAndShouldNotMessage={0} Listener does not exist and should not. Change not required.
'@

}


######################################################################################
# The Get-TargetResource cmdlet.
# This function will return the details of a Listener on the specified Port.
######################################################################################
function Get-TargetResource
{
    [OutputType([Hashtable])]
    param
    (
        [parameter(Mandatory = $true)]
        [ValidateSet('HTTP','HTTPS')]
        [String]
        $Transport,

        [parameter(Mandatory = $true)]
        [ValidateSet('Present','Absent')]
        [String]
        $Ensure
    )
    
    Write-Verbose -Message ( @(
        "$($MyInvocation.MyCommand): "
        $($LocalizedData.GettingListenerMessage)
        ) -join '' )

    $returnValue = @{
        Transport = $Transport
    }

    # Lookup the existing Listener
    $Listeners = Get-Listener -Transport $Transport
    if ($Listeners) {
        Write-Verbose -Message ( @(
            "$($MyInvocation.MyCommand): "
            $($LocalizedData.ListenerExistsMessage) -f $Transport
            ) -join '' )
        $returnValue += @{
            Ensure = 'Present'
            Port = $Listeners.Port
            Address = $Listeners.Address
            HostName = $Listeners.HostName
            Enabled = $Listeners.Enabled
            URLPrefix = $Listeners.URLPrefix
            CertificateThumbprint = $Listeners.CertificateThumbprint
            }
    } Else {       
        Write-Verbose -Message ( @(
            "$($MyInvocation.MyCommand): "
            $($LocalizedData.ListenerDoesNotExistMessage) -f $Transport
            ) -join '' )
        $returnValue += @{ Ensure = 'Absent' }
    }

    $returnValue
} # Get-TargetResource

######################################################################################
# The Set-TargetResource cmdlet.
# This function will configure (or remove) a WS-Man Listener on the specified port
######################################################################################
function Set-TargetResource
{
    param
    (
        [parameter(Mandatory = $true)]
        [ValidateSet('HTTP','HTTPS')]
        [String]
        $Transport,

        [parameter(Mandatory = $true)]
        [ValidateSet('Present','Absent')]
        [String]
        $Ensure,

        [UInt16]
        $Port,

        [String]
        $Address = '*',

        [String]
        $Issuer,

        [ValidateSet('Both','FQDNOnly','NameOnly')]
        [String]
        $SubjectFormat = 'Both',

        [Boolean]
        $MatchAlternate
    )

    Write-Verbose -Message ( @(
        "$($MyInvocation.MyCommand): "
        $($LocalizedData.SettingListenerMessage)
        ) -join '' )

    # Lookup the existing Listener
    $Listeners = Get-Listener -Transport $Transport

    # Get the default port for the transport if none was provided
    $Port = Get-DefaultPort -Transport $Transport -Port $Port

    if ($Ensure -eq 'Present') {
        # The listener should exist
        Write-Verbose -Message ( @(
            "$($MyInvocation.MyCommand): "
            $($LocalizedData.EnsureListenerExistsMessage) -f $Transport,$Port
            ) -join '' )
        if ($Listeners) {
            # The Listener exists already - delete it
            Write-Verbose -Message ( @(
                "$($MyInvocation.MyCommand): "
                $($LocalizedData.ListenerExistsRemoveMessage) -f $Transport,$Port
                ) -join '' )
            Remove-WSManInstance `
                -ResourceURI winrm/config/Listener `
                -SelectorSet @{ Transport=$Listeners.Transport;Address=$Listeners.Address }
        } else {
            Write-Verbose -Message ( @(
                "$($MyInvocation.MyCommand): "
                $($LocalizedData.ListenerOnPortDoesNotExistMessage) -f $Transport,$Port
                ) -join '' )
            # Ths listener doesn't exist - do nothing
        }
        # Create the listener
        if ($Transport -eq 'HTTPS') {
            [String] $Thumbprint = ''
            # First try and find a certificate that is used to the FQDN of the machine
            if ($SubjectFormat -in 'Both','FQDNOnly') {
                [String] $HostName = [System.Net.Dns]::GetHostByName($ENV:computerName).Hostname
                if ($MatchAlternate) {
                    $Thumbprint = (Get-ChildItem -Path Cert:\localmachine\my | Where-Object { 
                            ($_.Extensions.EnhancedKeyUsages.FriendlyName -contains 'Server Authentication') -and
                            ($_.Issuer -eq $Issuer) -and
                            ($HostName -in $_.DNSNameList.Unicode) -and
                            ($_.Subject -eq "CN=$HostName") } | Select-Object -First 1
                        ).Thumbprint
                } else {
                    $Thumbprint = (Get-ChildItem -Path Cert:\localmachine\my | Where-Object { 
                            ($_.Extensions.EnhancedKeyUsages.FriendlyName -contains 'Server Authentication') -and
                            ($_.Issuer -eq $Issuer) -and
                            ($_.Subject -eq "CN=$HostName") } | Select-Object -First 1
                        ).Thumbprint    
                } # if
            }
            if (($SubjectFormat -in 'Both','NameOnly') -and -not $Thumbprint) {
                # If could not find an FQDN cert, try for one issued to the computer name
                [String] $HostName = $ENV:ComputerName
                if ($MatchAlternate) {
                    $Thumbprint = (Get-ChildItem -Path Cert:\localmachine\my | Where-Object { 
                            ($_.Extensions.EnhancedKeyUsages.FriendlyName -contains 'Server Authentication') -and
                            ($_.Issuer -eq $Issuer) -and
                            ($HostName -in $_.DNSNameList.Unicode) -and
                            ($_.Subject -eq "CN=$HostName") } | Select-Object -First 1
                        ).Thumbprint
                } else {
                    $Thumbprint = (Get-ChildItem -Path Cert:\localmachine\my | Where-Object { 
                            ($_.Extensions.EnhancedKeyUsages.FriendlyName -contains 'Server Authentication') -and
                            ($_.Issuer -eq $Issuer) -and
                            ($_.Subject -eq "CN=$HostName") } | Select-Object -First 1
                        ).Thumbprint    
                } # if
            } # if
            if ($Thumbprint) {
                # A certificate was found, so use it to enable the HTTPS WinRM listener
                Write-Verbose -Message ( @(
                    "$($MyInvocation.MyCommand): "
                    $($LocalizedData.CreatingListenerMessage) -f $Transport,$Port
                    ) -join '' )
                New-WSManInstance `
                    -ResourceURI winrm/config/Listener `
                    -SelectorSet @{Address=$Address;Transport=$Transport} `
                    -ValueSet @{Hostname=$HostName;CertificateThumbprint=$Thumbprint;Port=$Port} `
                    -ErrorAction Stop
            } else {
                Write-Verbose -Message ( @(
                    "$($MyInvocation.MyCommand): "
                    $($LocalizedData.ListenerCreateFailNoCertError) -f $Transport,$Port
                    ) -join '' )
            } # if
        } else {
            Write-Verbose -Message ( @(
                "$($MyInvocation.MyCommand): "
                $($LocalizedData.CreatingListenerMessage) -f $Transport,$Port
                ) -join '' )
            New-WSManInstance `
                -ResourceURI winrm/config/Listener `
                -SelectorSet @{Address=$Address;Transport=$Transport} `
                -ValueSet @{Port=$Port} `
                -ErrorAction Stop
        }
    } else {
        # The listener should not exist
        Write-Verbose -Message ( @(
            "$($MyInvocation.MyCommand): "
            $($LocalizedData.EnsureListenerDoesNotExistMessage) -f $Transport,$Port
            ) -join '' )
        if ($Listeners) {
            Write-Verbose -Message ( @(
                "$($MyInvocation.MyCommand): "
                $($LocalizedData.ListenerExistsRemoveMessage) -f $Transport,$Port
                ) -join '' )
            Remove-WSManInstance `
                -ResourceURI winrm/config/Listener `
                -SelectorSet @{ Transport=$Listeners.Transport;Address=$Listeners.Address }
        }
    } # if
} # Set-TargetResource

######################################################################################
# The Test-TargetResource cmdlet.
# This function will detect if any changes need to be made on the listener on the
# specified port.
######################################################################################
function Test-TargetResource
{
    [OutputType([System.Boolean])]
    param
    (
        [parameter(Mandatory = $true)]
        [ValidateSet('HTTP','HTTPS')]
        [String]
        $Transport,

        [parameter(Mandatory = $true)]
        [ValidateSet('Present','Absent')]
        [String]
        $Ensure,

        [UInt16]
        $Port,

        [String]
        $Address = '*',

        [String]
        $Issuer,

        [ValidateSet('Both','FQDNOnly','NameOnly')]
        [String]
        $SubjectFormat = 'Both',

        [Boolean]
        $MatchAlternate
    )

    # Flag to signal whether settings are correct
    [Boolean] $desiredConfigurationMatch = $true

    Write-Verbose -Message ( @(
        "$($MyInvocation.MyCommand): "
        $($LocalizedData.TestingListenerMessage)
        ) -join '' )

    # Lookup the existing Listener
    $Listeners = Get-Listener -Transport $Transport
    
    # Get the default port for the transport if none was provided
    $Port = Get-DefaultPort -Transport $Transport -Port $Port

    if ($Ensure -eq 'Present') {
        # The listener should exist
        if ($Listeners) {
            # The Listener exists already
            Write-Verbose -Message ( @(
                "$($MyInvocation.MyCommand): "
                $($LocalizedData.ListenerExistsMessage)
                ) -join '' )
            # Check it is setup as per parameters
            if ($Listeners.Port -ne $Port) {
                Write-Verbose -Message ( @(
                    "$($MyInvocation.MyCommand): "
                    $($LocalizedData.ListenerOnWrongPortMessage) -f $Transport,$Listeners.Port,$Port
                    ) -join '' )
                $desiredConfigurationMatch = $false                
            }
            if ($Listeners.Address -ne $Address) {
                Write-Verbose -Message ( @(
                    "$($MyInvocation.MyCommand): "
                    $($LocalizedData.ListenerOnWrongAddressMessage) -f $Transport,$Listeners.Address,$Address
                    ) -join '' )
                $desiredConfigurationMatch = $false                
            }
        } else {
            # Ths listener doesn't exist but should
            Write-Verbose -Message ( @(
                "$($MyInvocation.MyCommand): "
                 $($LocalizedData.ListenerDoesNotExistButShouldMessage) -f $Transport
                ) -join '' )
            $desiredConfigurationMatch = $false
        }
    } else {
        # The listener should not exist
        if ($Listeners) {
            # The listener exists but should not
            Write-Verbose -Message ( @(
                "$($MyInvocation.MyCommand): "
                 $($LocalizedData.ListenerExistsButShouldNotMessage) -f $Transport
                ) -join '' )
            $desiredConfigurationMatch = $false
        } else {
            # The listener does not exist and should not
            Write-Verbose -Message ( @(
                "$($MyInvocation.MyCommand): "
                $($LocalizedData.ListenerDoesNotExistAndShouldNotMessage) -f $Transport
                ) -join '' )
        }
    } # if
    return $desiredConfigurationMatch
} # Test-TargetResource

######################################################################################
# Helpers
######################################################################################
function Get-Listener
{
    [OutputType([Hashtable])]
    param
    (
        [parameter(Mandatory = $true)]
        [ValidateSet('HTTP','HTTPS')]
        [String]
        $Transport
    )
    
    $Listeners = @(Get-WSManInstance `
        -ResourceURI winrm/config/Listener `
        -Enumerate)
    if ($Listeners) {
        
        return $Listeners.Where( {$_.Transport -eq $Transport } )
    }

} # Get-Listener

######################################################################################
function Get-DefaultPort
{
    [OutputType([UInt16])]
    param
    (
        [parameter(Mandatory = $true)]
        [ValidateSet('HTTP','HTTPS')]
        [String]
        $Transport,

        [UInt16]
        $Port
    )

    if (-not $Port) {
        # Set the default port because none was provided
        if ($Transport -eq 'HTTP') {
            $Port = 5985
        } else {
            $Port = 5986
        }
    }
    return $Port
}

######################################################################################
Export-ModuleMember -Function *-TargetResource