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.WSManSubjectFormat.ps1' -1 <# .SYNOPSIS The possible states for the DSC resource parameter WSManSubjectFormat. #> enum WSManSubjectFormat { Both = 1 FQDNOnly NameOnly } #EndRegion '.\Enum\005.WSManSubjectFormat.ps1' 12 #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' 11 #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\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) { # Enable use of Enums as optional properties $this.FeatureOptionalEnums = $true # 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-InvalidArgumentException -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' 310 #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 |