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 |