From 7073cc4793cc59921f89bb6200517ba437791086 Mon Sep 17 00:00:00 2001 From: Daniel Hughes Date: Wed, 18 Sep 2024 12:26:18 +0100 Subject: [PATCH 001/134] Update Build files for Classes --- RequiredModules.psd1 | 3 ++- Resolve-Dependency.psd1 | 2 +- build.yaml | 8 +++++--- 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/RequiredModules.psd1 b/RequiredModules.psd1 index 119ddc9..321d0a5 100644 --- a/RequiredModules.psd1 +++ b/RequiredModules.psd1 @@ -1,5 +1,5 @@ @{ - PSDependOptions = @{ + PSDependOptions = @{ AddToPath = $true Target = 'output\RequiredModules' Parameters = @{ @@ -21,6 +21,7 @@ # Build dependencies needed for using the module 'DscResource.Common' = 'latest' + 'DscResource.Base' = 'latest' # Analyzer rules 'DscResource.AnalyzerRules' = 'latest' diff --git a/Resolve-Dependency.psd1 b/Resolve-Dependency.psd1 index 07945f8..bbf34e1 100644 --- a/Resolve-Dependency.psd1 +++ b/Resolve-Dependency.psd1 @@ -3,7 +3,7 @@ AllowPrerelease = $false WithYAML = $true - #UseModuleFast = $true + UseModuleFast = $true #ModuleFastVersion = '0.1.2' #ModuleFastBleedingEdge = $true diff --git a/build.yaml b/build.yaml index e11f517..a8e4b40 100644 --- a/build.yaml +++ b/build.yaml @@ -80,11 +80,15 @@ NestedModule: Path: ./output/RequiredModules/DscResource.Common AddToManifest: false Exclude: PSGetModuleInfo.xml + DscResource.Base: + CopyOnly: true + Path: ./output/RequiredModules/DscResource.Base + AddToManifest: false + Exclude: PSGetModuleInfo.xml #################################################### # Pester Configuration (Sampler) # #################################################### - Pester: Configuration: Run: @@ -105,8 +109,6 @@ Pester: ExcludeFromCodeCoverage: - Modules/DscResource.Common - Modules/DscResource.Base - - prefix.ps1 - - WSManDsc.psm1 #################################################### # Pester Configuration (DscResource.Test) # From 50648450f37041de6b3919396a51346a523a5439 Mon Sep 17 00:00:00 2001 From: Daniel Hughes Date: Wed, 18 Sep 2024 13:19:54 +0100 Subject: [PATCH 002/134] Tidy up module psd1 and, remove ref to suffix --- build.yaml | 1 - source/WSManDsc.psd1 | 24 +++--------------------- 2 files changed, 3 insertions(+), 22 deletions(-) diff --git a/build.yaml b/build.yaml index a8e4b40..4dd1f0f 100644 --- a/build.yaml +++ b/build.yaml @@ -45,7 +45,6 @@ CopyPaths: - en-US - DSCResources Prefix: prefix.ps1 -Suffix: suffix.ps1 Encoding: UTF8 VersionedOutputDirectory: true BuiltModuleSubdirectory: builtModule diff --git a/source/WSManDsc.psd1 b/source/WSManDsc.psd1 index f405952..d9a0eec 100644 --- a/source/WSManDsc.psd1 +++ b/source/WSManDsc.psd1 @@ -1,6 +1,6 @@ @{ # Script module or binary module file associated with this manifest. - # RootModule = '' + RootModule = 'WSManDsc.psm1' # Version number of this module. ModuleVersion = '0.0.1' @@ -21,16 +21,7 @@ Description = 'DSC resources for configuring WS-Man.' # Minimum version of the Windows PowerShell engine required by this module - PowerShellVersion = '4.0' - - # Name of the Windows PowerShell host required by this module - # PowerShellHostName = '' - - # Minimum version of the Windows PowerShell host required by this module - # PowerShellHostVersion = '' - - # Minimum version of Microsoft .NET Framework required by this module - # DotNetFrameworkVersion = '' + PowerShellVersion = '5.0' # Minimum version of the common language runtime (CLR) required by this module CLRVersion = '4.0' @@ -53,9 +44,6 @@ # Format files (.ps1xml) to be loaded when importing this module FormatsToProcess = @() - # Modules to import as nested modules of the module specified in RootModule/ModuleToProcess - # NestedModules = @() - # Functions to export from this module FunctionsToExport = @() @@ -69,7 +57,7 @@ AliasesToExport = @() # DSC resources to export from this module - DscResourcesToExport = @('WSManListener', 'WSManConfig', 'WSManServiceConfig') + DscResourcesToExport = @() # List of all modules packaged with this module # ModuleList = @() @@ -101,10 +89,4 @@ } # End of PSData hashtable } # End of PrivateData hashtable - - # HelpInfo URI of this module - # HelpInfoURI = '' - - # Default prefix for commands exported from this module. Override the default prefix using Import-Module -Prefix. - # DefaultCommandPrefix = '' } From c81f4a552692c215e9c3d9be200a518152f692c2 Mon Sep 17 00:00:00 2001 From: Daniel Hughes Date: Wed, 18 Sep 2024 13:20:03 +0100 Subject: [PATCH 003/134] Add prefix --- source/prefix.ps1 | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 source/prefix.ps1 diff --git a/source/prefix.ps1 b/source/prefix.ps1 new file mode 100644 index 0000000..168dd74 --- /dev/null +++ b/source/prefix.ps1 @@ -0,0 +1,7 @@ +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' From cc1ac2c2d41b79853cc8a77dc30c14338e5f0263 Mon Sep 17 00:00:00 2001 From: Daniel Hughes Date: Wed, 18 Sep 2024 13:32:04 +0100 Subject: [PATCH 004/134] Move Get-DefaultPort out of the DscResource --- source/Private/Get-DefaultPort.ps1 | 44 ++++++ tests/Unit/Private/Get-DefaultPort.Tests.ps1 | 136 +++++++++++++++++++ 2 files changed, 180 insertions(+) create mode 100644 source/Private/Get-DefaultPort.ps1 create mode 100644 tests/Unit/Private/Get-DefaultPort.Tests.ps1 diff --git a/source/Private/Get-DefaultPort.ps1 b/source/Private/Get-DefaultPort.ps1 new file mode 100644 index 0000000..711b38f --- /dev/null +++ b/source/Private/Get-DefaultPort.ps1 @@ -0,0 +1,44 @@ +<# + .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)] + [ValidateSet('HTTP', 'HTTPS')] + [System.String] + $Transport, + + [Parameter()] + [System.UInt16] + $Port + ) + + process + { + if (-not $Port) + { + # Set the default port because none was provided + if ($Transport -eq 'HTTP') + { + $Port = 5985 + } + else + { + $Port = 5986 + } + } + + return $Port + } +} diff --git a/tests/Unit/Private/Get-DefaultPort.Tests.ps1 b/tests/Unit/Private/Get-DefaultPort.Tests.ps1 new file mode 100644 index 0000000..687a04c --- /dev/null +++ b/tests/Unit/Private/Get-DefaultPort.Tests.ps1 @@ -0,0 +1,136 @@ +[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '')] +param () + +BeforeDiscovery { + try + { + if (-not (Get-Module -Name 'DscResource.Test')) + { + # Assumes dependencies has been resolved, so if this module is not available, run 'noop' task. + if (-not (Get-Module -Name 'DscResource.Test' -ListAvailable)) + { + # Redirect all streams to $null, except the error stream (stream 2) + & "$PSScriptRoot/../../../build.ps1" -Tasks 'noop' 2>&1 4>&1 5>&1 6>&1 > $null + } + + # If the dependencies has not been resolved, this will throw an error. + Import-Module -Name 'DscResource.Test' -Force -ErrorAction 'Stop' + } + } + catch [System.IO.FileNotFoundException] + { + throw 'DscResource.Test module dependency not found. Please run ".\build.ps1 -ResolveDependency -Tasks build" first.' + } +} + +BeforeAll { + $script:dscModuleName = 'WSManDsc' + + Import-Module -Name $script:dscModuleName + + $PSDefaultParameterValues['InModuleScope:ModuleName'] = $script:dscModuleName + $PSDefaultParameterValues['Mock:ModuleName'] = $script:dscModuleName + $PSDefaultParameterValues['Should:ModuleName'] = $script:dscModuleName +} + +AfterAll { + $PSDefaultParameterValues.Remove('InModuleScope:ModuleName') + $PSDefaultParameterValues.Remove('Mock:ModuleName') + $PSDefaultParameterValues.Remove('Should:ModuleName') + + # Unload the module being tested so that it doesn't impact any other tests. + Get-Module -Name $script:dscModuleName -All | Remove-Module -Force +} + +Describe 'Get-DefaultPort' -Tag 'Private' { + Context 'When a port is not provided' { + BeforeDiscovery { + $testCases = @( + @{ + Transport = 'HTTP' + ExpectedValue = 5985 + } + @{ + Transport = 'HTTPS' + ExpectedValue = 5986 + } + ) + } + + It 'Should return '''' for '''' transport' -ForEach $testCases { + InModuleScope -Parameters $_ -ScriptBlock { + Set-StrictMode -Version 1.0 + + $mockParams = @{ + Transport = $Transport + } + + $result = Get-DefaultPort @mockParams + + $result | Should -Be $ExpectedValue + $result | Should -BeOfType System.UInt16 + } + } + } + + Context 'When a port is provided' { + BeforeDiscovery { + $testCases = @( + @{ + Transport = 'HTTP' + Port = 1000 + } + @{ + Transport = 'HTTPS' + Port = 2000 + } + ) + } + + It 'Should return '''' for '''' transport' -ForEach $testCases { + InModuleScope -Parameters $_ -ScriptBlock { + Set-StrictMode -Version 1.0 + + $mockParams = @{ + Transport = $Transport + Port = $Port + } + + $result = Get-DefaultPort @mockParams + + $result | Should -Be $Port + $result | Should -BeOfType System.UInt16 + } + } + } + + Context 'When Transport is not ''HTTP'' or ''HTTPS''' { + It 'Should throw the correct exception' { + InModuleScope -ScriptBlock { + Set-StrictMode -Version 1.0 + + $mockParams = @{ + Transport = $null + Port = 9999 + } + + { Get-DefaultPort @mockParams } | Should -Throw + } + } + } + + Context 'When Port is not valid' { + It 'Should throw the correct exception' { + InModuleScope -ScriptBlock { + Set-StrictMode -Version 1.0 + + $mockParams = @{ + Transport = 'HTTP' + Port = 'Something' + } + + { Get-DefaultPort @mockParams } | Should -Not -Throw + } + } + } +} From cce3026cf754616b8f59a9aae7b3c063fc5adfc1 Mon Sep 17 00:00:00 2001 From: Daniel Hughes Date: Wed, 18 Sep 2024 13:34:05 +0100 Subject: [PATCH 005/134] Add WSManReason --- source/Classes/001.WSManReason.ps1 | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 source/Classes/001.WSManReason.ps1 diff --git a/source/Classes/001.WSManReason.ps1 b/source/Classes/001.WSManReason.ps1 new file mode 100644 index 0000000..6b939ee --- /dev/null +++ b/source/Classes/001.WSManReason.ps1 @@ -0,0 +1,21 @@ +<# + .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 +} From 8d86381da6bfdc4493cb0bb8f0f4daef314e2439 Mon Sep 17 00:00:00 2001 From: Daniel Hughes Date: Wed, 18 Sep 2024 15:21:03 +0100 Subject: [PATCH 006/134] `Get-InvalidOperationRecord` now provided by `DscResource.Test` --- tests/TestHelpers/CommonTestHelper.psm1 | 40 +------------------------ 1 file changed, 1 insertion(+), 39 deletions(-) diff --git a/tests/TestHelpers/CommonTestHelper.psm1 b/tests/TestHelpers/CommonTestHelper.psm1 index cdc4a0a..0542934 100644 --- a/tests/TestHelpers/CommonTestHelper.psm1 +++ b/tests/TestHelpers/CommonTestHelper.psm1 @@ -43,44 +43,6 @@ function Get-InvalidArgumentRecord .PARAMETER ErrorRecord The error record containing the exception that is causing this terminating error #> -function Get-InvalidOperationRecord -{ - [CmdletBinding()] - param - ( - [ValidateNotNullOrEmpty()] - [System.String] - $Message, - - [ValidateNotNull()] - [System.Management.Automation.ErrorRecord] - $ErrorRecord - ) - - if ($null -eq $Message) - { - $invalidOperationException = New-Object -TypeName 'InvalidOperationException' - } - elseif ($null -eq $ErrorRecord) - { - $invalidOperationException = - New-Object -TypeName 'InvalidOperationException' -ArgumentList @( $Message ) - } - else - { - $invalidOperationException = - New-Object -TypeName 'InvalidOperationException' -ArgumentList @( $Message, - $ErrorRecord.Exception ) - } - - $newObjectParams = @{ - TypeName = 'System.Management.Automation.ErrorRecord' - ArgumentList = @( $invalidOperationException.ToString(), 'MachineStateIncorrect', - 'InvalidOperation', $null ) - } - return New-Object @newObjectParams -} Export-ModuleMember -Function ` - Get-InvalidArgumentRecord, ` - Get-InvalidOperationRecord + Get-InvalidArgumentRecord From 3e7cdc7bbd39706221f2fd9f0e962dec2a5b4e69 Mon Sep 17 00:00:00 2001 From: Daniel Hughes Date: Wed, 18 Sep 2024 16:19:10 +0100 Subject: [PATCH 007/134] Remove Mof WSManListener --- .../DSC_WSManListener/DSC_WSManListener.psm1 | 783 ---------- .../DSC_WSManListener.schema.mof | 18 - .../DSCResources/DSC_WSManListener/README.md | 16 - .../en-US/DSC_WSManListener.strings.psd1 | 25 - tests/Unit/DSC_WSManListener.Tests.ps1 | 1326 ----------------- 5 files changed, 2168 deletions(-) delete mode 100644 source/DSCResources/DSC_WSManListener/DSC_WSManListener.psm1 delete mode 100644 source/DSCResources/DSC_WSManListener/DSC_WSManListener.schema.mof delete mode 100644 source/DSCResources/DSC_WSManListener/README.md delete mode 100644 source/DSCResources/DSC_WSManListener/en-US/DSC_WSManListener.strings.psd1 delete mode 100644 tests/Unit/DSC_WSManListener.Tests.ps1 diff --git a/source/DSCResources/DSC_WSManListener/DSC_WSManListener.psm1 b/source/DSCResources/DSC_WSManListener/DSC_WSManListener.psm1 deleted file mode 100644 index 045e878..0000000 --- a/source/DSCResources/DSC_WSManListener/DSC_WSManListener.psm1 +++ /dev/null @@ -1,783 +0,0 @@ -$modulePath = Join-Path -Path (Split-Path -Path (Split-Path -Path $PSScriptRoot -Parent) -Parent) -ChildPath 'Modules' - -Import-Module -Name (Join-Path -Path $modulePath -ChildPath 'DscResource.Common') - -# Import Localization Strings -$script:localizedData = Get-LocalizedData -DefaultUICulture 'en-US' - - -# Standard Transport Ports -$Default_HTTP_Port = 5985 -$Default_HTTPS_Port = 5986 - -<# - .SYNOPSIS - Returns the current WS-Man Listener details. - - .PARAMETER Transport - The transport type of WS-Man Listener. - - .PARAMETER Ensure - Specifies whether the WS-Man Listener should exist. -#> -function Get-TargetResource -{ - [CmdletBinding()] - [OutputType([Hashtable])] - param - ( - [Parameter(Mandatory = $true)] - [ValidateSet('HTTP', 'HTTPS')] - [System.String] - $Transport, - - [Parameter(Mandatory = $true)] - [ValidateSet('Present', 'Absent')] - [System.String] - $Ensure - ) - - Write-Verbose -Message ( @( - "$($MyInvocation.MyCommand): " - $($script:localizedData.GettingListenerMessage) - ) -join '' ) - - $returnValue = @{ - Transport = $Transport - } - - # Lookup the existing Listener - $listener = Get-Listener -Transport $Transport - - if ($listener) - { - $certificate = '' - - # An existing listener matching the transport was found - Write-Verbose -Message ( @( - "$($MyInvocation.MyCommand): " - $($script:localizedData.ListenerExistsMessage) ` - -f $Transport - ) -join '' ) - - if ($listener.CertificateThumbprint) - { - $certificate = Find-Certificate -CertificateThumbprint $listener.CertificateThumbprint - } - - $returnValue += @{ - Ensure = 'Present' - Port = $listener.Port - Address = $listener.Address - Issuer = $certificate.Issuer - SubjectFormat = $null - MatchAlternate = $null - DN = $null - Hostname = $listener.Hostname - Enabled = $listener.Enabled - URLPrefix = $listener.URLPrefix - CertificateThumbprint = $listener.CertificateThumbprint - } - } - else - { - # An existing listener matching the transport was not found - Write-Verbose -Message ( @( - "$($MyInvocation.MyCommand): " - $($script:localizedData.ListenerDoesNotExistMessage) ` - -f $Transport - ) -join '' ) - - $returnValue += @{ - Ensure = 'Absent' - Port = $null - Address = $null - Issuer = $null - SubjectFormat = $null - MatchAlternate = $null - DN = $null - Hostname = $null - Enabled = $null - URLPrefix = $null - CertificateThumbprint = $null - } - } # if - - return $returnValue -} # Get-TargetResource - -<# - .SYNOPSIS - Sets the state of a WS-Man Listener. - - .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 DN - This is a Distinguished Name component that will be 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 Set-TargetResource -{ - [CmdletBinding()] - param - ( - [Parameter(Mandatory = $true)] - [ValidateSet('HTTP', 'HTTPS')] - [System.String] - $Transport, - - [Parameter(Mandatory = $true)] - [ValidateSet('Present', 'Absent')] - [System.String] - $Ensure, - - [Parameter()] - [System.UInt16] - $Port, - - [Parameter()] - [System.String] - $Address = '*', - - [Parameter()] - [System.String] - $Issuer, - - [Parameter()] - [ValidateSet('Both', 'FQDNOnly', 'NameOnly')] - [System.String] - $SubjectFormat = 'Both', - - [Parameter()] - [System.Boolean] - $MatchAlternate, - - [Parameter()] - [System.String] - $DN, - - [Parameter()] - [System.String] - $CertificateThumbprint, - - [Parameter()] - [System.String] - $Hostname - ) - - Write-Verbose -Message ( @( - "$($MyInvocation.MyCommand): " - $($script:localizedData.SettingListenerMessage) - ) -join '' ) - - # Lookup the existing Listener - $listener = 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): " - $($script:localizedData.EnsureListenerExistsMessage) ` - -f $Transport, $Port - ) -join '' ) - - if ($listener) - { - # The Listener exists already - delete it - Write-Verbose -Message ( @( - "$($MyInvocation.MyCommand): " - $($script:localizedData.ListenerExistsRemoveMessage) ` - -f $Transport, $Port - ) -join '' ) - - Remove-WSManInstance ` - -ResourceURI 'winrm/config/Listener' ` - -SelectorSet @{ - Transport = $listener.Transport - Address = $listener.Address - } - } - else - { - # Ths listener doesn't exist - do nothing - Write-Verbose -Message ( @( - "$($MyInvocation.MyCommand): " - $($script:localizedData.ListenerOnPortDoesNotExistMessage) ` - -f $Transport, $Port - ) -join '' ) - } - - # Create the listener - if ($Transport -eq 'HTTPS') - { - # Find the certificate to use for the HTTPS Listener - $null = $PSBoundParameters.Remove('Transport') - $null = $PSBoundParameters.Remove('Ensure') - $null = $PSBoundParameters.Remove('Port') - $null = $PSBoundParameters.Remove('Address') - - $certificate = Find-Certificate @PSBoundParameters - [System.String] $thumbprint = $certificate.thumbprint - - if ($thumbprint) - { - # A certificate was found, so use it to enable the HTTPS WinRM listener - Write-Verbose -Message ( @( - "$($MyInvocation.MyCommand): " - $($script:localizedData.CreatingListenerMessage) ` - -f $Transport, $Port - ) -join '' ) - - if ([System.String]::IsNullOrEmpty($Hostname)) - { - $Hostname = [System.Net.Dns]::GetHostByName($ENV:computerName).Hostname - } - - New-WSManInstance ` - -ResourceURI 'winrm/config/Listener' ` - -SelectorSet @{ - Address = $Address - Transport = $Transport - } ` - -ValueSet @{ - Hostname = $Hostname - CertificateThumbprint = $thumbprint - Port = $Port - } ` - -ErrorAction Stop - } - else - { - # A certificate could not be found to use for the HTTPS listener - New-InvalidArgumentException ` - -Message ($script:localizedData.ListenerCreateFailNoCertError -f ` - $Transport, $Port) ` - -Argument 'Issuer' - } # if - } - else - { - # Create a plain HTTP listener - Write-Verbose -Message ( @( - "$($MyInvocation.MyCommand): " - $($script: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): " - $($script:localizedData.EnsureListenerDoesNotExistMessage) ` - -f $Transport, $Port - ) -join '' ) - - if ($listener) - { - # The listener does exist - so delete it - Write-Verbose -Message ( @( - "$($MyInvocation.MyCommand): " - $($script:localizedData.ListenerExistsRemoveMessage) ` - -f $Transport, $Port - ) -join '' ) - - Remove-WSManInstance ` - -ResourceURI 'winrm/config/Listener' ` - -SelectorSet @{ - Transport = $listener.Transport - Address = $listener.Address - } - } - } # if -} # Set-TargetResource - -<# - .SYNOPSIS - Tests the state of a WS-Man Listener. - - .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 DN - This is a Distinguished Name component that will be 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 Test-TargetResource -{ - [CmdletBinding()] - [OutputType([System.Boolean])] - param - ( - [Parameter(Mandatory = $true)] - [ValidateSet('HTTP', 'HTTPS')] - [System.String] - $Transport, - - [Parameter(Mandatory = $true)] - [ValidateSet('Present', 'Absent')] - [System.String] - $Ensure, - - [Parameter()] - [System.UInt16] - $Port, - - [Parameter()] - [System.String] - $Address = '*', - - [Parameter()] - [System.String] - $Issuer, - - [Parameter()] - [ValidateSet('Both', 'FQDNOnly', 'NameOnly')] - [System.String] - $SubjectFormat = 'Both', - - [Parameter()] - [System.Boolean] - $MatchAlternate, - - [Parameter()] - [System.String] - $DN, - - [Parameter()] - [System.String] - $CertificateThumbprint, - - [Parameter()] - [System.String] - $Hostname - ) - - # Flag to signal whether settings are correct - $desiredConfigurationMatch = $true - - Write-Verbose -Message ( @( - "$($MyInvocation.MyCommand): " - $($script:localizedData.TestingListenerMessage) - ) -join '' ) - - # Lookup the existing Listener - $listener = 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 ($listener) - { - # The Listener exists already - Write-Verbose -Message ( @( - "$($MyInvocation.MyCommand): " - $($script:localizedData.ListenerExistsMessage) ` - -f $Transport - ) -join '' ) - - # Check it is setup as per parameters - if ($listener.Port -ne $Port) - { - Write-Verbose -Message ( @( - "$($MyInvocation.MyCommand): " - $($script:localizedData.ListenerOnWrongPortMessage) ` - -f $Transport, $listener.Port, $Port - ) -join '' ) - $desiredConfigurationMatch = $false - } - - if ($listener.Address -ne $Address) - { - Write-Verbose -Message ( @( - "$($MyInvocation.MyCommand): " - $($script:localizedData.ListenerOnWrongAddressMessage) ` - -f $Transport, $listener.Address, $Address - ) -join '' ) - $desiredConfigurationMatch = $false - } - - if ($PSBoundParameters.ContainsKey('Hostname') -and $listener.Hostname -ne $Hostname) - { - Write-Verbose -Message ( @( - "$($MyInvocation.MyCommand): " - $($script:localizedData.ListenerOnWrongHostnameMessage) ` - -f $Transport, $listener.Hostname, $Hostname - ) -join '' ) - $desiredConfigurationMatch = $false - } - - if ($PSBoundParameters.ContainsKey('CertificateThumbprint') -and $listener.CertificateThumbprint -ne $CertificateThumbprint) - { - Write-Verbose -Message ( @( - "$($MyInvocation.MyCommand): " - $($script:localizedData.ListenerOnWrongCertificateThumbprintMessage) ` - -f $Transport, $listener.CertificateThumbprint, $CertificateThumbprint - ) -join '' ) - $desiredConfigurationMatch = $false - } - } - else - { - # Ths listener doesn't exist but should - Write-Verbose -Message ( @( - "$($MyInvocation.MyCommand): " - $($script:localizedData.ListenerDoesNotExistButShouldMessage) ` - -f $Transport - ) -join '' ) - $desiredConfigurationMatch = $false - } - } - else - { - # The listener should not exist - if ($listener) - { - # The listener exists but should not - Write-Verbose -Message ( @( - "$($MyInvocation.MyCommand): " - $($script:localizedData.ListenerExistsButShouldNotMessage) ` - -f $Transport - ) -join '' ) - $desiredConfigurationMatch = $false - } - else - { - # The listener does not exist and should not - Write-Verbose -Message ( @( - "$($MyInvocation.MyCommand): " - $($script:localizedData.ListenerDoesNotExistAndShouldNotMessage) ` - -f $Transport - ) -join '' ) - } - } # if - - return $desiredConfigurationMatch -} # Test-TargetResource - -<# - .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([Hashtable])] - param - ( - [Parameter(Mandatory = $true)] - [ValidateSet('HTTP', 'HTTPS')] - [System.String] - $Transport - ) - - $listeners = @(Get-WSManInstance ` - -ResourceURI 'winrm/config/Listener' ` - -Enumerate) - - if ($listeners) - { - return $listeners.Where( { ($_.Transport -eq $Transport) ` - -and ($_.Source -ne 'Compatibility') } ) - } -} # Get-Listener - -<# - .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)] - [ValidateSet('HTTP', 'HTTPS')] - [System.String] - $Transport, - - [Parameter()] - [System.UInt16] - $Port - ) - - if (-not $Port) - { - # Set the default port because none was provided - if ($Transport -eq 'HTTP') - { - $Port = $Default_HTTP_Port - } - else - { - $Port = $Default_HTTPS_Port - } - } - return $Port -} - -<# - .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 DN - This is a Distinguished Name component that will be 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()] - [ValidateSet('Both', 'FQDNOnly', 'NameOnly')] - [System.String] - $SubjectFormat = 'Both', - - [Parameter()] - [System.Boolean] - $MatchAlternate, - - [Parameter()] - [System.String] - $DN, - - [Parameter()] - [System.String] - $CertificateThumbprint, - - [Parameter()] - [System.String] - $Hostname - ) - - if ($PSBoundParameters.ContainsKey('CertificateThumbprint')) - { - Write-Verbose -Message ( @( - "$($MyInvocation.MyCommand): " - $($script:localizedData.FindCertificateByThumbprintMessage) ` - -f $CertificateThumbprint - ) -join '' ) - - $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 'Both', 'FQDNOnly') - { - # Lookup the certificate using the FQDN of the machine - if ([System.String]::IsNullOrEmpty($Hostname)) - { - $Hostname = [System.Net.Dns]::GetHostByName($ENV:computerName).Hostname - } - $Subject = "CN=$Hostname" - - if ($PSBoundParameters.ContainsKey('DN')) - { - $Subject = "$Subject, $DN" - } # if - - if ($MatchAlternate) - { - # Try and lookup the certificate using the subject and the alternate name - Write-Verbose -Message ( @( - "$($MyInvocation.MyCommand): " - $($script:localizedData.FindCertificateAlternateMessage) ` - -f $Subject, $Issuer, $Hostname - ) -join '' ) - - $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 ( @( - "$($MyInvocation.MyCommand): " - $($script:localizedData.FindCertificateMessage) ` - -f $Subject, $Issuer - ) -join '' ) - - $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 'Both', 'NameOnly')) - { - # If could not find an FQDN cert, try for one issued to the computer name - [System.String] $Hostname = $ENV:ComputerName - [System.String] $Subject = "CN=$Hostname" - - if ($PSBoundParameters.ContainsKey('DN')) - { - $Subject = "$Subject, $DN" - } # if - - if ($MatchAlternate) - { - # Try and lookup the certificate using the subject and the alternate name - Write-Verbose -Message ( @( - "$($MyInvocation.MyCommand): " - $($script:localizedData.FindCertificateAlternateMessage) ` - -f $Subject, $Issuer, $Hostname - ) -join '' ) - - $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 ( @( - "$($MyInvocation.MyCommand): " - $($script:localizedData.FindCertificateMessage) ` - -f $Subject, $Issuer - ) -join '' ) - - $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 ( @( - "$($MyInvocation.MyCommand): " - $($script:localizedData.CertificateFoundMessage) ` - -f $certificate.thumbprint - ) -join '' ) - } - else - { - Write-Verbose -Message ( @( - "$($MyInvocation.MyCommand): " - $($script:localizedData.CertificateNotFoundMessage) ` - ) -join '' ) - } # if - - return $certificate -} # Find-Certificate - -Export-ModuleMember -Function *-TargetResource diff --git a/source/DSCResources/DSC_WSManListener/DSC_WSManListener.schema.mof b/source/DSCResources/DSC_WSManListener/DSC_WSManListener.schema.mof deleted file mode 100644 index 52cbeea..0000000 --- a/source/DSCResources/DSC_WSManListener/DSC_WSManListener.schema.mof +++ /dev/null @@ -1,18 +0,0 @@ -[ClassVersion("1.0.0.0"), FriendlyName("WSManListener")] -class DSC_WSManListener : OMI_BaseResource -{ - [Key, Description("The transport type of WS-Man Listener."), ValueMap{"HTTP","HTTPS"}, Values{"HTTP","HTTPS"}] String Transport; - [Required, Description("Specifies whether the WS-Man Listener should exist."), ValueMap{"Present","Absent"}, Values{"Present","Absent"}] String Ensure; - [Write, Description("The port the WS-Man Listener should use. Defaults to 5985 for HTTP and 5986 for HTTPS listeners.")] uint16 Port; - [Write, Description("The Address that the WS-Man Listener will be bound to. The default is * (any address).")] String Address; - [Write, Description("The Issuer of the certificate to use for the HTTPS WS-Man Listener if a thumbprint is not specified.")] String Issuer; - [Write, Description("The format used to match the certificate subject to use for an HTTPS WS-Man Listener if a thumbprint is not specified."), ValueMap{"Both","FQDNOnly","NameOnly"}, Values{"Both","FQDNOnly","NameOnly"}] String SubjectFormat; - [Write, Description("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.")] Boolean MatchAlternate; - [Write, Description("This is a Distinguished Name component that will be used to identify the certificate to use for the HTTPS WS-Man Listener if a thumbprint is not specified.")] String DN; - [Write, Description("The host name that a HTTPS WS-Man Listener will be bound to. If not specified it will default to the computer name of the node.")] String Hostname; - [Read, Description("Returns true if the existing WS-Man Listener is enabled.")] Boolean Enabled; - [Read, Description("The URL Prefix of the existing WS-Man Listener.")] String URLPrefix; - [Write, Description("The Thumbprint of the certificate to use for the HTTPS WS-Man Listener.")] String CertificateThumbprint; -}; - - diff --git a/source/DSCResources/DSC_WSManListener/README.md b/source/DSCResources/DSC_WSManListener/README.md deleted file mode 100644 index a63b4e0..0000000 --- a/source/DSCResources/DSC_WSManListener/README.md +++ /dev/null @@ -1,16 +0,0 @@ -# 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. diff --git a/source/DSCResources/DSC_WSManListener/en-US/DSC_WSManListener.strings.psd1 b/source/DSCResources/DSC_WSManListener/en-US/DSC_WSManListener.strings.psd1 deleted file mode 100644 index a28a7b1..0000000 --- a/source/DSCResources/DSC_WSManListener/en-US/DSC_WSManListener.strings.psd1 +++ /dev/null @@ -1,25 +0,0 @@ -ConvertFrom-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 an 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. - ListenerOnWrongHostnameMessage = {0} Listener Hostname is {1}, should be {2}. Change required. - ListenerOnWrongCertificateThumbprintMessage = {0} Listener Certificate Thumbprint is {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. - FindCertificateAlternateMessage = Looking for machine server certificate with subject '{0}' issued by '{1}' and DNS name '{2}'. - FindCertificateMessage = Looking for machine server certificate with subject '{0}' issued by '{1}'. - FindCertificateByThumbprintMessage = Looking for machine server certificate with thumbprint '{0}'. - CertificateFoundMessage = Certificate found with thumbprint '{0}' to use for HTTPS Listener. - CertificateNotFoundMessage = Certificate not found. -'@ diff --git a/tests/Unit/DSC_WSManListener.Tests.ps1 b/tests/Unit/DSC_WSManListener.Tests.ps1 deleted file mode 100644 index 776f354..0000000 --- a/tests/Unit/DSC_WSManListener.Tests.ps1 +++ /dev/null @@ -1,1326 +0,0 @@ -<# - .SYNOPSIS - Unit test for DSC_WSManListener DSC resource. - - .NOTES -#> - -# Suppressing this rule because Script Analyzer does not understand Pester's syntax. -[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '')] -param () - -BeforeDiscovery { - try - { - if (-not (Get-Module -Name 'DscResource.Test')) - { - # Assumes dependencies has been resolved, so if this module is not available, run 'noop' task. - if (-not (Get-Module -Name 'DscResource.Test' -ListAvailable)) - { - # Redirect all streams to $null, except the error stream (stream 2) - & "$PSScriptRoot/../../build.ps1" -Tasks 'noop' 2>&1 4>&1 5>&1 6>&1 > $null - } - - # This will throw an error if the dependencies have not been resolved. - Import-Module -Name 'DscResource.Test' -Force -ErrorAction 'Stop' - } - } - catch [System.IO.FileNotFoundException] - { - throw 'DscResource.Test module dependency not found. Please run ".\build.ps1 -ResolveDependency -Tasks build" first.' - } - - $script:dscResourceName = 'DSC_WSManListener' -} - -BeforeAll { - $script:dscModuleName = 'WSManDsc' - $script:dscResourceName = 'DSC_WSManListener' - - $script:testEnvironment = Initialize-TestEnvironment ` - -DSCModuleName $script:dscModuleName ` - -DSCResourceName $script:dscResourceName ` - -ResourceType 'Mof' ` - -TestType 'Unit' - - Import-Module -Name (Join-Path -Path $PSScriptRoot -ChildPath '..\TestHelpers\CommonTestHelper.psm1') - - $PSDefaultParameterValues['InModuleScope:ModuleName'] = $script:dscResourceName - $PSDefaultParameterValues['Mock:ModuleName'] = $script:dscResourceName - $PSDefaultParameterValues['Should:ModuleName'] = $script:dscResourceName - - # Create the Mock Objects that will be used for running tests - $script:mockCertificateThumbprint = '74FA31ADEA7FDD5333CED10910BFA6F665A1F2FC' - $mockFQDN = 'SERVER1.CONTOSO.COM' - $mockHostName = $([System.Net.Dns]::GetHostByName($ENV:computerName).Hostname) - $script:mockIssuer = 'CN=CONTOSO.COM Issuing CA, DC=CONTOSO, DC=COM' - $script:mockDN = 'O=Contoso Inc, S=Pennsylvania, C=US' - $mockCertificate = @{ - Thumbprint = $mockCertificateThumbprint - Subject = "CN=$mockHostName" - Issuer = $mockIssuer - Extensions = @{ EnhancedKeyUsages = @{ FriendlyName = 'Server Authentication' } } - DNSNameList = @{ Unicode = $mockHostName } - } - - $script:mockCertificateDN = @{ - Thumbprint = $mockCertificateThumbprint - Subject = "CN=$mockHostName, $mockDN" - Issuer = $mockIssuer - Extensions = @{ EnhancedKeyUsages = @{ FriendlyName = 'Server Authentication' } } - DNSNameList = @{ Unicode = $mockHostName } - } - - $script:mockListenerHTTP = @{ - cfg = 'http://schemas.microsoft.com/wbem/wsman/1/config/listener' - xsi = 'http://www.w3.org/2001/XMLSchema-instance' - lang = 'en-US' - Address = '*' - Transport = 'HTTP' - Port = 5985 - Hostname = '' - Enabled = 'true' - URLPrefix = 'wsman' - CertificateThumbprint = '' - } - - $script:mockListenerHTTPS = @{ - cfg = 'http://schemas.microsoft.com/wbem/wsman/1/config/listener' - xsi = 'http://www.w3.org/2001/XMLSchema-instance' - lang = 'en-US' - Address = '*' - Transport = 'HTTPS' - Port = 5986 - Hostname = $mockFQDN - Enabled = 'true' - URLPrefix = 'wsman' - CertificateThumbprint = $mockCertificateThumbprint - } -} - -AfterAll { - $PSDefaultParameterValues.Remove('InModuleScope:ModuleName') - $PSDefaultParameterValues.Remove('Mock:ModuleName') - $PSDefaultParameterValues.Remove('Should:ModuleName') - - Restore-TestEnvironment -TestEnvironment $script:testEnvironment - - # Unload the module being tested so that it doesn't impact any other tests. - Get-Module -Name $script:dscResourceName -All | Remove-Module -Force - - # Remove module common test helper. - Get-Module -Name 'CommonTestHelper' -All | Remove-Module -Force -} - -Describe "$($script:dscResourceName)\Get-TargetResource" -Tag 'Get' { - Context 'No listeners exist' { - BeforeAll { - Mock -CommandName Get-WSManInstance - } - - It 'Should return absent listener' { - InModuleScope -ScriptBlock { - Set-StrictMode -Version 1.0 - - $result = Get-TargetResource ` - -Transport HTTP ` - -Ensure Present ` - -Verbose:$VerbosePreference - $result.Ensure | Should -Be 'Absent' - } - } - - It 'Should call Get-WSManInstance once' { - Should -Invoke ` - -CommandName Get-WSManInstance ` - -Exactly -Times 1 ` - -Scope Context - } - } - - Context 'Requested listener does not exist' { - BeforeAll { - Mock -CommandName Get-WSManInstance -MockWith { - @($mockListenerHTTP) - } - } - - It 'Should return absent listener' { - InModuleScope -Parameters @{ - mockListenerHTTPS = $script:mockListenerHTTPS - } -ScriptBlock { - Set-StrictMode -Version 1.0 - - $result = Get-TargetResource ` - -Transport $mockListenerHTTPS.Transport ` - -Ensure Present ` - -Verbose:$VerbosePreference - - $result.Ensure | Should -Be 'Absent' - } - } - - It 'Should call Get-WSManInstance once' { - Should -Invoke ` - -CommandName Get-WSManInstance ` - -Exactly -Times 1 ` - -Scope Context - } - } - - Context 'Requested listener does exist' { - BeforeAll { - Mock -CommandName Get-WSManInstance -MockWith { - @($mockListenerHTTP) - } - } - - It 'Should return correct listener' { - InModuleScope -Parameters @{ - mockListenerHTTP = $script:mockListenerHTTP - } -ScriptBlock { - Set-StrictMode -Version 1.0 - - $result = Get-TargetResource ` - -Transport $mockListenerHTTP.Transport ` - -Ensure Present ` - -Verbose:$VerbosePreference - - $result.Ensure | Should -Be 'Present' - $result.Port | Should -Be $mockListenerHTTP.Port - $result.Address | Should -Be $mockListenerHTTP.Address - $result.HostName | Should -Be $mockListenerHTTP.HostName - $result.Enabled | Should -Be $mockListenerHTTP.Enabled - $result.URLPrefix | Should -Be $mockListenerHTTP.URLPrefix - $result.CertificateThumbprint | Should -Be $mockListenerHTTP.CertificateThumbprint - } - } - - It 'Should call Get-WSManInstance once' { - Should -Invoke ` - -CommandName Get-WSManInstance ` - -Exactly -Times 1 ` - -Scope Context - } - } -} - -Describe "$($script:dscResourceName)\Set-TargetResource" -Tag 'Set' { - Context 'HTTP Listener does not exist but should' { - BeforeAll { - Mock -CommandName Get-WSManInstance - Mock -CommandName Remove-WSManInstance - Mock -CommandName New-WSManInstance - Mock -CommandName Find-Certificate - } - - It 'Should not throw error' { - InModuleScope -Parameters @{ - mockListenerHTTP = $script:mockListenerHTTP - } -ScriptBlock { - Set-StrictMode -Version 1.0 - - { Set-TargetResource ` - -Transport $mockListenerHTTP.Transport ` - -Ensure 'Present' ` - -Verbose:$VerbosePreference } | Should -Not -Throw - } - } - - It 'Should call expected Mocks' { - Should -Invoke ` - -CommandName Get-WSManInstance ` - -Exactly -Times 1 ` - -Scope Context - Should -Invoke ` - -CommandName Remove-WSManInstance ` - -Exactly -Times 0 ` - -Scope Context - Should -Invoke ` - -CommandName New-WSManInstance ` - -Exactly -Times 1 ` - -Scope Context - Should -Invoke ` - -CommandName Find-Certificate ` - -Exactly -Times 0 ` - -Scope Context - } - } - - Context 'HTTPS Listener does not exist but should' { - BeforeAll { - Mock -CommandName Get-WSManInstance - Mock -CommandName Remove-WSManInstance - Mock -CommandName New-WSManInstance - Mock -CommandName Find-Certificate -MockWith { - return $mockCertificate - } - } - - It 'Should not throw error' { - InModuleScope -Parameters @{ - mockListenerHTTPS = $script:mockListenerHTTPS - mockIssuer = $script:mockIssuer - } -ScriptBlock { - Set-StrictMode -Version 1.0 - - { Set-TargetResource ` - -Transport $mockListenerHTTPS.Transport ` - -Ensure 'Present' ` - -Issuer $mockIssuer ` - -Verbose:$VerbosePreference } | Should -Not -Throw - } - } - - It 'Should call expected Mocks' { - Should -Invoke ` - -CommandName Get-WSManInstance ` - -Exactly -Times 1 ` - -Scope Context - Should -Invoke ` - -CommandName Remove-WSManInstance ` - -Exactly -Times 0 ` - -Scope Context - Should -Invoke ` - -CommandName New-WSManInstance ` - -Exactly -Times 1 ` - -Scope Context - Should -Invoke ` - -CommandName Find-Certificate ` - -Exactly -Times 1 ` - -Scope Context - } - } - - Context 'HTTPS Listener does not exist but should but certificate missing' { - BeforeAll { - Mock -CommandName Get-WSManInstance - Mock -CommandName Remove-WSManInstance - Mock -CommandName New-WSManInstance - Mock -CommandName Find-Certificate - } - - It 'Should throw error' { - InModuleScope -Parameters @{ - mockListenerHTTPS = $script:mockListenerHTTPS - mockIssuer = $script:mockIssuer - } -ScriptBlock { - Set-StrictMode -Version 1.0 - - $errorRecord = Get-InvalidArgumentRecord ` - -Message ($script:localizedData.ListenerCreateFailNoCertError -f ` - $mockListenerHTTPS.Transport, '5986') ` - -ArgumentName 'Issuer' - - { Set-TargetResource ` - -Transport $mockListenerHTTPS.Transport ` - -Ensure 'Present' ` - -Issuer $mockIssuer ` - -Verbose:$VerbosePreference } | Should -Throw $errorRecord - } - } - - It 'Should call expected Mocks' { - Should -Invoke ` - -CommandName Get-WSManInstance ` - -Exactly -Times 1 ` - -Scope Context - Should -Invoke ` - -CommandName Remove-WSManInstance ` - -Exactly -Times 0 ` - -Scope Context - Should -Invoke ` - -CommandName New-WSManInstance ` - -Exactly -Times 0 ` - -Scope Context - Should -Invoke ` - -CommandName Find-Certificate ` - -Exactly -Times 1 ` - -Scope Context - } - } - - Context 'HTTP Listener exists but should not' { - BeforeAll { - Mock -CommandName Get-WSManInstance -MockWith { - @($mockListenerHTTP) - } - Mock -CommandName Remove-WSManInstance - Mock -CommandName New-WSManInstance - Mock -CommandName Find-Certificate - } - - It 'Should not throw error' { - InModuleScope -Parameters @{ - mockListenerHTTP = $script:mockListenerHTTP - } -ScriptBlock { - Set-StrictMode -Version 1.0 - - { Set-TargetResource ` - -Transport $mockListenerHTTP.Transport ` - -Ensure 'Absent' ` - -Verbose:$VerbosePreference } | Should -Not -Throw - } - } - - It 'Should call expected Mocks' { - Should -Invoke ` - -CommandName Get-WSManInstance ` - -Exactly -Times 1 ` - -Scope Context - Should -Invoke ` - -CommandName Remove-WSManInstance ` - -Exactly -Times 1 ` - -Scope Context - Should -Invoke ` - -CommandName New-WSManInstance ` - -Exactly -Times 0 ` - -Scope Context - Should -Invoke ` - -CommandName Find-Certificate ` - -Exactly -Times 0 ` - -Scope Context - } - } - - Context 'HTTP Listener exists and should' { - BeforeAll { - Mock -CommandName Get-WSManInstance -MockWith { - @($mockListenerHTTP) - } - Mock -CommandName Remove-WSManInstance - Mock -CommandName New-WSManInstance - Mock -CommandName Find-Certificate - } - - It 'Should not throw error' { - InModuleScope -Parameters @{ - mockListenerHTTP = $script:mockListenerHTTP - mockIssuer = $script:mockIssuer - } -ScriptBlock { - Set-StrictMode -Version 1.0 - - { Set-TargetResource ` - -Transport $mockListenerHTTP.Transport ` - -Ensure 'Present' ` - -Issuer $mockIssuer ` - -Verbose:$VerbosePreference } | Should -Not -Throw - } - } - - It 'Should call expected Mocks' { - Should -Invoke ` - -CommandName Get-WSManInstance ` - -Exactly -Times 1 ` - -Scope Context - Should -Invoke ` - -CommandName Remove-WSManInstance ` - -Exactly -Times 1 ` - -Scope Context - Should -Invoke ` - -CommandName New-WSManInstance ` - -Exactly -Times 1 ` - -Scope Context - Should -Invoke ` - -CommandName Find-Certificate ` - -Exactly -Times 0 ` - -Scope Context - } - } - - Context 'HTTP Listener exists and HTTPS is required' { - BeforeAll { - Mock -CommandName Get-WSManInstance -MockWith { - @($mockListenerHTTP) - } - Mock -CommandName Remove-WSManInstance - Mock -CommandName New-WSManInstance - Mock -CommandName Find-Certificate -MockWith { - $mockCertificate - } - } - - It 'Should not throw error' { - InModuleScope -Parameters @{ - mockListenerHTTPS = $script:mockListenerHTTPS - mockIssuer = $script:mockIssuer - } -ScriptBlock { - Set-StrictMode -Version 1.0 - - { Set-TargetResource ` - -Transport $mockListenerHTTPS.Transport ` - -Ensure 'Present' ` - -Issuer $mockIssuer ` - -Verbose:$VerbosePreference } | Should -Not -Throw - } - } - - It 'Should call expected Mocks' { - Should -Invoke ` - -CommandName Get-WSManInstance ` - -Exactly -Times 1 ` - -Scope Context - Should -Invoke ` - -CommandName Remove-WSManInstance ` - -Exactly -Times 0 ` - -Scope Context - Should -Invoke ` - -CommandName New-WSManInstance ` - -Exactly -Times 1 ` - -Scope Context - Should -Invoke ` - -CommandName Find-Certificate ` - -Exactly -Times 1 ` - -Scope Context - } - } - - Context 'HTTPS Listener exists and HTTP is required' { - BeforeAll { - Mock -CommandName Get-WSManInstance -MockWith { - @($mockListenerHTTPS) - } - Mock -CommandName Remove-WSManInstance - Mock -CommandName New-WSManInstance - Mock -CommandName Find-Certificate - } - - It 'Should not throw error' { - InModuleScope -Parameters @{ - mockListenerHTTP = $script:mockListenerHTTP - mockIssuer = $script:mockIssuer - } -ScriptBlock { - Set-StrictMode -Version 1.0 - - { Set-TargetResource ` - -Transport $mockListenerHTTP.Transport ` - -Ensure 'Present' ` - -Issuer $mockIssuer ` - -Verbose:$VerbosePreference } | Should -Not -Throw - } - } - - It 'Should call expected Mocks' { - Should -Invoke ` - -CommandName Get-WSManInstance ` - -Exactly -Times 1 ` - -Scope Context - Should -Invoke ` - -CommandName Remove-WSManInstance ` - -Exactly -Times 0 ` - -Scope Context - Should -Invoke ` - -CommandName New-WSManInstance ` - -Exactly -Times 1 ` - -Scope Context - Should -Invoke ` - -CommandName Find-Certificate ` - -Exactly -Times 0 ` - -Scope Context - } - } - - Context 'Both Listeners exist and HTTPS is required' { - BeforeAll { - Mock -CommandName Get-WSManInstance -MockWith { - @($mockListenerHTTP, $mockListenerHTTPS) - } - Mock -CommandName Remove-WSManInstance - Mock -CommandName New-WSManInstance - Mock -CommandName Find-Certificate -MockWith { - $mockCertificate - } - } - - It 'Should not throw error' { - InModuleScope -Parameters @{ - mockListenerHTTPS = $script:mockListenerHTTPS - mockIssuer = $script:mockIssuer - } -ScriptBlock { - Set-StrictMode -Version 1.0 - - { Set-TargetResource ` - -Transport $mockListenerHTTPS.Transport ` - -Ensure 'Present' ` - -Issuer $mockIssuer ` - -Verbose:$VerbosePreference } | Should -Not -Throw - } - } - - It 'Should call expected Mocks' { - Should -Invoke ` - -CommandName Get-WSManInstance ` - -Exactly -Times 1 ` - -Scope Context - Should -Invoke ` - -CommandName Remove-WSManInstance ` - -Exactly -Times 1 ` - -Scope Context - Should -Invoke ` - -CommandName New-WSManInstance ` - -Exactly -Times 1 ` - -Scope Context - Should -Invoke ` - -CommandName Find-Certificate ` - -Exactly -Times 1 ` - -Scope Context - } - } - - Context 'Both Listeners exist and HTTP is required' { - BeforeAll { - Mock -CommandName Get-WSManInstance -MockWith { - @($mockListenerHTTP, $mockListenerHTTPS) - } - Mock -CommandName Remove-WSManInstance - Mock -CommandName New-WSManInstance - Mock -CommandName Find-Certificate - } - - It 'Should not throw error' { - InModuleScope -Parameters @{ - mockListenerHTTP = $script:mockListenerHTTP - mockIssuer = $script:mockIssuer - } -ScriptBlock { - Set-StrictMode -Version 1.0 - - { Set-TargetResource ` - -Transport $mockListenerHTTP.Transport ` - -Ensure 'Present' ` - -Issuer $mockIssuer ` - -Verbose:$VerbosePreference } | Should -Not -Throw - } - } - - It 'Should call expected Mocks' { - Should -Invoke ` - -CommandName Get-WSManInstance ` - -Exactly -Times 1 ` - -Scope Context - Should -Invoke ` - -CommandName Remove-WSManInstance ` - -Exactly -Times 1 ` - -Scope Context - Should -Invoke ` - -CommandName New-WSManInstance ` - -Exactly -Times 1 ` - -Scope Context - Should -Invoke ` - -CommandName Find-Certificate ` - -Exactly -Times 0 ` - -Scope Context - } - } -} - -Describe "$($script:dscResourceName)\Test-TargetResource" -Tag 'Test' { - Context 'HTTP Listener does not exist but should' { - BeforeAll { - Mock -CommandName Get-WSManInstance - } - - It 'Should return false' { - InModuleScope -Parameters @{ - mockListenerHTTP = $script:mockListenerHTTP - } -ScriptBlock { - Set-StrictMode -Version 1.0 - - Test-TargetResource ` - -Transport $mockListenerHTTP.Transport ` - -Ensure 'Present' ` - -Verbose:$VerbosePreference | Should -BeFalse - } - } - - It 'Should call expected Mocks' { - Should -Invoke ` - -CommandName Get-WSManInstance ` - -Exactly -Times 1 ` - -Scope Context - } - } - - Context 'HTTP Listener does not exist and should not' { - BeforeAll { - Mock -CommandName Get-WSManInstance - } - - It 'Should return false' { - InModuleScope -Parameters @{ - mockListenerHTTP = $script:mockListenerHTTP - } -ScriptBlock { - Set-StrictMode -Version 1.0 - - Test-TargetResource ` - -Transport $mockListenerHTTP.Transport ` - -Ensure 'Absent' ` - -Verbose:$VerbosePreference | Should -BeTrue - } - } - - It 'Should call expected Mocks' { - Should -Invoke ` - -CommandName Get-WSManInstance ` - -Exactly -Times 1 ` - -Scope Context - } - } - - Context 'HTTPS Listener does not exist but should' { - BeforeAll { - Mock -CommandName Get-WSManInstance - } - - It 'Should return false' { - InModuleScope -Parameters @{ - mockListenerHTTPS = $script:mockListenerHTTPS - mockIssuer = $script:mockIssuer - } -ScriptBlock { - Set-StrictMode -Version 1.0 - - Test-TargetResource ` - -Transport $mockListenerHTTPS.Transport ` - -Ensure 'Present' ` - -Issuer $mockIssuer ` - -Verbose:$VerbosePreference | Should -BeFalse - } - } - - It 'Should call expected Mocks' { - Should -Invoke ` - -CommandName Get-WSManInstance ` - -Exactly -Times 1 ` - -Scope Context - } - } - - Context 'HTTP Listener exists but should not' { - BeforeAll { - Mock -CommandName Get-WSManInstance -MockWith { - @($mockListenerHTTP) - } - } - - It 'Should return false' { - InModuleScope -Parameters @{ - mockListenerHTTP = $script:mockListenerHTTP - } -ScriptBlock { - Set-StrictMode -Version 1.0 - - Test-TargetResource ` - -Transport $mockListenerHTTP.Transport ` - -Ensure 'Absent' ` - -Verbose:$VerbosePreference | Should -BeFalse - } - } - - It 'Should call expected Mocks' { - Should -Invoke ` - -CommandName Get-WSManInstance ` - -Exactly -Times 1 ` - -Scope Context - } - } - - Context 'HTTPS Listener exists but should not' { - BeforeAll { - Mock -CommandName Get-WSManInstance -MockWith { - @($mockListenerHTTPS) - } - } - - It 'Should return false' { - InModuleScope -Parameters @{ - mockListenerHTTPS = $script:mockListenerHTTPS - } -ScriptBlock { - Set-StrictMode -Version 1.0 - - Test-TargetResource ` - -Transport $mockListenerHTTPS.Transport ` - -Ensure 'Absent' ` - -Verbose:$VerbosePreference | Should -BeFalse - } - } - - It 'Should call expected Mocks' { - Should -Invoke ` - -CommandName Get-WSManInstance ` - -Exactly -Times 1 ` - -Scope Context - } - } - - Context 'HTTP Listener exists and should' { - BeforeAll { - Mock -CommandName Get-WSManInstance -MockWith { - @($mockListenerHTTP) - } - } - - It 'Should return true' { - InModuleScope -Parameters @{ - mockListenerHTTP = $script:mockListenerHTTP - } -ScriptBlock { - Set-StrictMode -Version 1.0 - - Test-TargetResource ` - -Transport $mockListenerHTTP.Transport ` - -Ensure 'Present' ` - -Verbose:$VerbosePreference | Should -BeTrue - } - } - - It 'Should call expected Mocks' { - Should -Invoke ` - -CommandName Get-WSManInstance ` - -Exactly -Times 1 ` - -Scope Context - } - } - - Context 'HTTPS Listener exists and should' { - BeforeAll { - Mock -CommandName Get-WSManInstance -MockWith { - @($mockListenerHTTPS) - } - } - - It 'Should return true' { - InModuleScope -Parameters @{ - mockListenerHTTPS = $script:mockListenerHTTPS - } -ScriptBlock { - Set-StrictMode -Version 1.0 - - Test-TargetResource ` - -Transport $mockListenerHTTPS.Transport ` - -Ensure 'Present' ` - -Verbose:$VerbosePreference | Should -BeTrue - } - } - - It 'Should call expected Mocks' { - Should -Invoke ` - -CommandName Get-WSManInstance ` - -Exactly -Times 1 ` - -Scope Context - } - } - - Context 'HTTP Listener exists but port is incorrect' { - BeforeAll { - Mock -CommandName Get-WSManInstance -MockWith { - @($mockListenerHTTP) - } - } - - It 'Should return true' { - InModuleScope -Parameters @{ - mockListenerHTTP = $script:mockListenerHTTP - } -ScriptBlock { - Set-StrictMode -Version 1.0 - - Test-TargetResource ` - -Transport $mockListenerHTTP.Transport ` - -Port 9999 ` - -Ensure 'Present' ` - -Verbose:$VerbosePreference | Should -BeFalse - } - } - - It 'Should call expected Mocks' { - Should -Invoke ` - -CommandName Get-WSManInstance ` - -Exactly -Times 1 ` - -Scope Context - } - } - - Context 'HTTP Listener exists but address is incorrect' { - BeforeAll { - Mock -CommandName Get-WSManInstance -MockWith { - @($mockListenerHTTP) - } - } - - It 'Should return true' { - InModuleScope -Parameters @{ - mockListenerHTTP = $script:mockListenerHTTP - } -ScriptBlock { - Set-StrictMode -Version 1.0 - - Test-TargetResource ` - -Transport $mockListenerHTTP.Transport ` - -Address '192.168.1.1' ` - -Ensure 'Present' ` - -Verbose:$VerbosePreference | Should -BeFalse - } - } - - It 'Should call expected Mocks' { - Should -Invoke ` - -CommandName Get-WSManInstance ` - -Exactly -Times 1 ` - -Scope Context - } - } - - Context 'HTTP Listener exists but hostname is incorrect' { - BeforeAll { - Mock -CommandName Get-WSManInstance -MockWith { - @($mockListenerHTTPS) - } - } - - It 'Should return true' { - InModuleScope -Parameters @{ - mockListenerHTTPS = $script:mockListenerHTTPS - } -ScriptBlock { - Set-StrictMode -Version 1.0 - - Test-TargetResource ` - -Transport $mockListenerHTTPS.Transport ` - -Hostname 'thewronghostname.example.local' ` - -Ensure 'Present' ` - -Verbose:$VerbosePreference | Should -BeFalse - } - } - - It 'Should call expected Mocks' { - Should -Invoke ` - -CommandName Get-WSManInstance ` - -Exactly -Times 1 ` - -Scope Context - } - } - - Context 'HTTP Listener exists but CertificateThumbprint is incorrect' { - BeforeAll { - Mock -CommandName Get-WSManInstance -MockWith { - @($mockListenerHTTPS) - } - } - - It 'Should return true' { - InModuleScope -Parameters @{ - mockListenerHTTPS = $script:mockListenerHTTPS - } -ScriptBlock { - Set-StrictMode -Version 1.0 - - Test-TargetResource ` - -Transport $mockListenerHTTPS.Transport ` - -CertificateThumbprint '' ` - -Ensure 'Present' ` - -Verbose:$VerbosePreference | Should -BeFalse - } - } - - It 'Should call expected Mocks' { - Should -Invoke ` - -CommandName Get-WSManInstance ` - -Exactly -Times 1 ` - -Scope Context - } - } - - Context 'Both Listeners exists and HTTPS should' { - BeforeAll { - Mock -CommandName Get-WSManInstance -MockWith { - @($mockListenerHTTP, $mockListenerHTTPS) - } - } - - It 'Should return true' { - InModuleScope -Parameters @{ - mockListenerHTTPS = $script:mockListenerHTTPS - } -ScriptBlock { - Set-StrictMode -Version 1.0 - - Test-TargetResource ` - -Transport $mockListenerHTTPS.Transport ` - -Ensure 'Present' ` - -Verbose:$VerbosePreference | Should -BeTrue - } - } - - It 'Should call expected Mocks' { - Should -Invoke ` - -CommandName Get-WSManInstance ` - -Exactly -Times 1 ` - -Scope Context - } - } -} - -Describe "$($script:dscResourceName)\Find-Certificate" -Tag 'Private' { - Context 'CertificateThumbprint is passed but does not exist' { - BeforeAll { - Mock -CommandName Get-ChildItem - } - - It 'Should not throw error' { - InModuleScope -Parameters @{ - mockCertificateThumbprint = $script:mockCertificateThumbprint - } -ScriptBlock { - Set-StrictMode -Version 1.0 - - { $script:returnedCertificate = Find-Certificate ` - -CertificateThumbprint $mockCertificateThumbprint ` - -Verbose:$VerbosePreference } | Should -Not -Throw - } - } - - It 'Should return null' { - InModuleScope -ScriptBlock { - Set-StrictMode -Version 1.0 - - $script:returnedCertificate | Should -BeNullOrEmpty - } - } - - It 'Should call expected Mocks' { - Should -Invoke ` - -CommandName Get-ChildItem ` - -Exactly -Times 1 ` - -Scope Context - } - } - - Context 'CertificateThumbprint is passed and does exist' { - BeforeAll { - Mock -CommandName Get-ChildItem -MockWith { - $mockCertificateDN - } - } - - It 'Should not throw error' { - InModuleScope -Parameters @{ - mockCertificateThumbprint = $script:mockCertificateThumbprint - } -ScriptBlock { - Set-StrictMode -Version 1.0 - - { $script:returnedCertificate = Find-Certificate ` - -CertificateThumbprint $mockCertificateThumbprint ` - -Verbose:$VerbosePreference } | Should -Not -Throw - } - } - - It 'Should return expected certificate' { - InModuleScope -Parameters @{ - mockCertificateThumbprint = $script:mockCertificateThumbprint - } -ScriptBlock { - Set-StrictMode -Version 1.0 - - $script:returnedCertificate.Thumbprint | Should -Be $mockCertificateThumbprint - } - } - - It 'Should call expected Mocks' { - Should -Invoke ` - -CommandName Get-ChildItem ` - -Exactly -Times 1 ` - -Scope Context - } - } - - Context 'SubjectFormat is Both, Certificate does not exist, DN passed' { - BeforeAll { - Mock -CommandName Get-ChildItem - } - - It 'Should not throw error' { - InModuleScope -Parameters @{ - mockIssuer = $script:mockIssuer - mockDN = $script:mockDN - } -ScriptBlock { - Set-StrictMode -Version 1.0 - - { $script:returnedCertificate = Find-Certificate ` - -Issuer $mockIssuer ` - -SubjectFormat 'Both' ` - -MatchAlternate $true ` - -DN $mockDN ` - -Verbose:$VerbosePreference } | Should -Not -Throw - } - } - - It 'Should return null' { - InModuleScope -ScriptBlock { - Set-StrictMode -Version 1.0 - - $script:returnedCertificate | Should -BeNullOrEmpty - } - } - - It 'Should call expected Mocks' { - Should -Invoke ` - -CommandName Get-ChildItem ` - -Exactly -Times 2 ` - -Scope Context - } - } - - Context 'SubjectFormat is Both, Certificate with DN Exists, DN passed' { - BeforeAll { - Mock -CommandName Get-ChildItem -MockWith { - $mockCertificateDN - } - } - - It 'Should not throw error' { - InModuleScope -Parameters @{ - mockIssuer = $script:mockIssuer - mockDN = $script:mockDN - } -ScriptBlock { - Set-StrictMode -Version 1.0 - - { $script:returnedCertificate = Find-Certificate ` - -Issuer $mockIssuer ` - -SubjectFormat 'Both' ` - -MatchAlternate $true ` - -DN $mockDN ` - -Verbose:$VerbosePreference } | Should -Not -Throw - } - } - - It 'Should return expected certificate' { - InModuleScope -Parameters @{ - mockCertificateThumbprint = $script:mockCertificateThumbprint - } -ScriptBlock { - Set-StrictMode -Version 1.0 - - $script:returnedCertificate.Thumbprint | Should -Be $mockCertificateThumbprint - } - } - - It 'Should call expected Mocks' { - Should -Invoke ` - -CommandName Get-ChildItem ` - -Exactly -Times 1 ` - -Scope Context - } - } - - Context 'SubjectFormat is Both, Certificate without DN Exists, DN passed' { - BeforeAll { - Mock -CommandName Get-ChildItem -MockWith { - $mockCertificate - } - } - - It 'Should not throw error' { - InModuleScope -Parameters @{ - mockIssuer = $script:mockIssuer - mockDN = $script:mockDN - } -ScriptBlock { - Set-StrictMode -Version 1.0 - - { $script:returnedCertificate = Find-Certificate ` - -Issuer $mockIssuer ` - -SubjectFormat 'Both' ` - -MatchAlternate $true ` - -DN $mockDN ` - -Verbose:$VerbosePreference } | Should -Not -Throw - } - } - - It 'Should return null' { - InModuleScope -ScriptBlock { - Set-StrictMode -Version 1.0 - - $script:returnedCertificate | Should -BeNullOrEmpty - } - } - - It 'Should call expected Mocks' { - Should -Invoke ` - -CommandName Get-ChildItem ` - -Exactly -Times 2 ` - -Scope Context - } - } - - Context 'SubjectFormat is Both, Certificate does not exist, DN not passed' { - BeforeAll { - Mock -CommandName Get-ChildItem - } - - It 'Should not throw error' { - InModuleScope -Parameters @{ - mockIssuer = $script:mockIssuer - } -ScriptBlock { - Set-StrictMode -Version 1.0 - - { $script:returnedCertificate = Find-Certificate ` - -Issuer $mockIssuer ` - -SubjectFormat 'Both' ` - -MatchAlternate $true ` - -Verbose:$VerbosePreference } | Should -Not -Throw - } - } - - It 'Should return null' { - InModuleScope -ScriptBlock { - Set-StrictMode -Version 1.0 - - $script:returnedCertificate | Should -BeNullOrEmpty - } - } - - It 'Should call expected Mocks' { - Should -Invoke ` - -CommandName Get-ChildItem ` - -Exactly -Times 2 ` - -Scope Context - } - } - - Context 'SubjectFormat is Both, Certificate with DN Exists, DN not passed' { - BeforeAll { - Mock -CommandName Get-ChildItem -MockWith { - $mockCertificateDN - } - } - - It 'Should not throw error' { - InModuleScope -Parameters @{ - mockIssuer = $script:mockIssuer - } -ScriptBlock { - Set-StrictMode -Version 1.0 - - { $script:returnedCertificate = Find-Certificate ` - -Issuer $mockIssuer ` - -SubjectFormat 'Both' ` - -MatchAlternate $true ` - -Verbose:$VerbosePreference } | Should -Not -Throw - } - } - - It 'Should return null' { - InModuleScope -ScriptBlock { - Set-StrictMode -Version 1.0 - - $script:returnedCertificate | Should -BeNullOrEmpty - } - } - - It 'Should call expected Mocks' { - Should -Invoke ` - -CommandName Get-ChildItem ` - -Exactly -Times 2 ` - -Scope Context - } - } - - Context 'SubjectFormat is Both, Certificate without DN Exists, DN not passed' { - BeforeAll { - Mock -CommandName Get-ChildItem -MockWith { - $mockCertificate - } - } - - It 'Should not throw error' { - InModuleScope -Parameters @{ - mockIssuer = $script:mockIssuer - } -ScriptBlock { - Set-StrictMode -Version 1.0 - - { $script:returnedCertificate = Find-Certificate ` - -Issuer $mockIssuer ` - -SubjectFormat 'Both' ` - -MatchAlternate $true ` - -Verbose:$VerbosePreference } | Should -Not -Throw - } - } - - It 'Should return expected certificate' { - InModuleScope -Parameters @{ - mockCertificateThumbprint = $script:mockCertificateThumbprint - } -ScriptBlock { - Set-StrictMode -Version 1.0 - - $script:returnedCertificate.Thumbprint | Should -Be $mockCertificateThumbprint - } - } - - It 'Should call expected Mocks' { - Should -Invoke ` - -CommandName Get-ChildItem ` - -Exactly -Times 1 ` - -Scope Context - } - } - - Context 'SubjectFormat is Both, Certificate does not exist, DN not passed, MatchAlternate is false' { - BeforeAll { - Mock -CommandName Get-ChildItem - } - - It 'Should not throw error' { - InModuleScope -Parameters @{ - mockIssuer = $script:mockIssuer - } -ScriptBlock { - Set-StrictMode -Version 1.0 - - { $script:returnedCertificate = Find-Certificate ` - -Issuer $mockIssuer ` - -SubjectFormat 'Both' ` - -MatchAlternate $false ` - -Verbose:$VerbosePreference } | Should -Not -Throw - } - } - - It 'Should return null' { - InModuleScope -ScriptBlock { - Set-StrictMode -Version 1.0 - - $script:returnedCertificate | Should -BeNullOrEmpty - } - } - - It 'Should call expected Mocks' { - Should -Invoke ` - -CommandName Get-ChildItem ` - -Exactly -Times 2 ` - -Scope Context - } - } - - Context 'SubjectFormat is Both, Certificate without DN Exists, DN not passed, MatchAlternate is false' { - BeforeAll { - Mock -CommandName Get-ChildItem -MockWith { - $mockCertificate - } - } - - It 'Should not throw error' { - InModuleScope -Parameters @{ - mockIssuer = $script:mockIssuer - } -ScriptBlock { - Set-StrictMode -Version 1.0 - - { $script:returnedCertificate = Find-Certificate ` - -Issuer $mockIssuer ` - -SubjectFormat 'Both' ` - -MatchAlternate $false ` - -Verbose:$VerbosePreference } | Should -Not -Throw - } - } - - It 'Should return expected certificate' { - InModuleScope -Parameters @{ - mockCertificateThumbprint = $script:mockCertificateThumbprint - } -ScriptBlock { - Set-StrictMode -Version 1.0 - - $script:returnedCertificate.Thumbprint | Should -Be $mockCertificateThumbprint - } - } - - It 'Should call expected Mocks' { - Should -Invoke ` - -CommandName Get-ChildItem ` - -Exactly -Times 1 ` - -Scope Context - } - } -} From 4a6f35c0fc6fccdd7b411b04d2ee4d4e7730822d Mon Sep 17 00:00:00 2001 From: Daniel Hughes Date: Wed, 18 Sep 2024 16:20:08 +0100 Subject: [PATCH 008/134] Add class WSManListener --- source/Classes/020.WSManListener.ps1 | 218 +++++++++++++++++ source/en-US/WSManListener.strings.psd1 | 14 ++ tests/Unit/Classes/WSManListener.Tests.ps1 | 268 +++++++++++++++++++++ 3 files changed, 500 insertions(+) create mode 100644 source/Classes/020.WSManListener.ps1 create mode 100644 source/en-US/WSManListener.strings.psd1 create mode 100644 tests/Unit/Classes/WSManListener.Tests.ps1 diff --git a/source/Classes/020.WSManListener.ps1 b/source/Classes/020.WSManListener.ps1 new file mode 100644 index 0000000..b4fa37e --- /dev/null +++ b/source/Classes/020.WSManListener.ps1 @@ -0,0 +1,218 @@ +<# + .SYNOPSIS + The `WSManListener` DSC resource is used to create, modify, or remove + WSMan listeners. + + .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)] + [ValidateSet('HTTP', 'HTTPS')] + [System.String] + $Transport + + [DscProperty(Mandatory = $true)] + [Ensure] + $Ensure + + [DscProperty()] + [ValidateRange(0, 65535)] + [Nullable[System.UInt16]] + $Port + + [DscProperty()] + [System.String] + $Address = '*' + + [DscProperty()] + [System.String] + $Issuer + + [DscProperty()] + [ValidateSet('Both', 'FQDNOnly', 'NameOnly')] + [System.String] + $SubjectFormat = 'Both' + + [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' + + ) + + # Get the port if it's not provided + if ($this.Port) + { + $this.Port = Get-DefaultPort -Transport $this.Transport -Port $this.Port + } + } + + [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 + } + + $getCurrentStateResult = Get-Listener @getParameters + + $currentCertificate = '' + + if ($getCurrentStateResult.CertificateThumbprint) + { + $currentCertificate = Find-Certificate -CertificateThumbprint $getCurrentStateResult.CertificateThumbprint + } + + $state = @{ + Transport = $properties.Transport + Port = [System.UInt16] $getCurrentStateResult.Port + Address = $getCurrentStateResult.Address + Enabled = $getCurrentStateResult.Enabled + URLPrefix = $getCurrentStateResult.URLPrefix + Issuer = $currentCertificate.Issuer + SubjectFormat = $properties.SubjectFormat + MatchAlternate = $null + BaseDN = $null + CertificateThumbprint = $getCurrentStateResult.CertificateThumbprint + Hostname = $getCurrentStateResult.HostName + } + + 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. + #> + [void] Modify([System.Collections.Hashtable] $properties) + { + # <# + # If the property 'EnablePollutionProtection' was present and not in desired state, + # then the property name must be change for the cmdlet Set-DnsServerCache. In the + # cmdlet Get-DnsServerCache the property name is 'EnablePollutionProtection', but + # in the cmdlet Set-DnsServerCache the parameter is 'PollutionProtection'. + # #> + # if ($properties.ContainsKey('EnablePollutionProtection')) + # { + # $properties['PollutionProtection'] = $properties.EnablePollutionProtection + + # $properties.Remove('EnablePollutionProtection') + # } + + # Set-DnsServerCache @properties + } + + [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) + { + # # The properties MaximumFiles and MaximumRolloverFiles are mutually exclusive. + # $assertBoundParameterParameters = @{ + # BoundParameterList = $properties + # MutuallyExclusiveList1 = @( + # 'MaximumFiles' + # ) + # MutuallyExclusiveList2 = @( + # 'MaximumRolloverFiles' + # ) + # } + + # Assert-BoundParameter @assertBoundParameterParameters + } +} diff --git a/source/en-US/WSManListener.strings.psd1 b/source/en-US/WSManListener.strings.psd1 new file mode 100644 index 0000000..f187454 --- /dev/null +++ b/source/en-US/WSManListener.strings.psd1 @@ -0,0 +1,14 @@ +<# + .SYNOPSIS + The localized resource strings in English (en-US) for the + resource WSManListener module. This file should only contain + localized strings for private functions, public command, and + classes (that are not a DSC resource). +#> + +ConvertFrom-StringData @' + ## Strings overrides for the ResourceBase's default strings. + # None + + ## Strings directly used by the derived class WSManListener. +'@ diff --git a/tests/Unit/Classes/WSManListener.Tests.ps1 b/tests/Unit/Classes/WSManListener.Tests.ps1 new file mode 100644 index 0000000..05a94ed --- /dev/null +++ b/tests/Unit/Classes/WSManListener.Tests.ps1 @@ -0,0 +1,268 @@ +<# + .SYNOPSIS + Unit test for DSC_WSManListener DSC resource. +#> + +# Suppressing this rule because Script Analyzer does not understand Pester's syntax. +[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '')] +param () + +BeforeDiscovery { + try + { + if (-not (Get-Module -Name 'DscResource.Test')) + { + # Assumes dependencies has been resolved, so if this module is not available, run 'noop' task. + if (-not (Get-Module -Name 'DscResource.Test' -ListAvailable)) + { + # Redirect all streams to $null, except the error stream (stream 2) + & "$PSScriptRoot/../../../build.ps1" -Tasks 'noop' 2>&1 4>&1 5>&1 6>&1 > $null + } + + # If the dependencies has not been resolved, this will throw an error. + Import-Module -Name 'DscResource.Test' -Force -ErrorAction 'Stop' + } + } + catch [System.IO.FileNotFoundException] + { + throw 'DscResource.Test module dependency not found. Please run ".\build.ps1 -ResolveDependency -Tasks build" first.' + } +} + +BeforeAll { + $script:dscModuleName = 'WSManDsc' + + Import-Module -Name $script:dscModuleName + + Import-Module -Name (Join-Path -Path $PSScriptRoot -ChildPath '../../TestHelpers/CommonTestHelper.psm1') + + $PSDefaultParameterValues['InModuleScope:ModuleName'] = $script:dscModuleName + $PSDefaultParameterValues['Mock:ModuleName'] = $script:dscModuleName + $PSDefaultParameterValues['Should:ModuleName'] = $script:dscModuleName +} + +AfterAll { + $PSDefaultParameterValues.Remove('InModuleScope:ModuleName') + $PSDefaultParameterValues.Remove('Mock:ModuleName') + $PSDefaultParameterValues.Remove('Should:ModuleName') + + # Unload the module being tested so that it doesn't impact any other tests. + Get-Module -Name $script:dscModuleName -All | Remove-Module -Force + + # Remove module common test helper. + Get-Module -Name 'CommonTestHelper' -All | Remove-Module -Force +} + +Describe 'WSManListener' { + Context 'When class is instantiated' { + It 'Should not throw an exception' { + InModuleScope -ScriptBlock { + Set-StrictMode -Version 1.0 + + { [WSManListener]::new() } | Should -Not -Throw + } + } + + It 'Should have a default or empty constructor' { + InModuleScope -ScriptBlock { + Set-StrictMode -Version 1.0 + + $instance = [WSManListener]::new() + $instance | Should -Not -BeNullOrEmpty + } + } + + It 'Should be the correct type' { + InModuleScope -ScriptBlock { + Set-StrictMode -Version 1.0 + + $instance = [WSManListener]::new() + $instance.GetType().Name | Should -Be 'WSManListener' + } + } + } +} + +Describe 'WSManListener\Get()' -Tag 'Get' { + Context 'When the system is in the desired state' { + Context 'When getting a HTTP listener' { + BeforeAll { + InModuleScope -ScriptBlock { + Set-StrictMode -Version 1.0 + + $script:mockWSManListenerInstance = [WSManListener] @{ + Transport = 'HTTP' + Ensure = 'Present' + } + + <# + This mocks the method GetCurrentState(). + + Method Get() will call the base method Get() which will + call back to the derived class method GetCurrentState() + to get the result to return from the derived method Get(). + #> + $script:mockWSManListenerInstance | + Add-Member -Force -MemberType 'ScriptMethod' -Name 'GetCurrentState' -Value { + return [System.Collections.Hashtable] @{ + Transport = 'HTTP' + Port = 5985 + Address = '*' + Enabled = 'true' + URLPrefix = 'wsman' + Issuer = $null + SubjectFormat = 'Both' + MatchAlternate = $null + BaseDN = $null + CertificateThumbprint = $null + Hostname = $null + } + } -PassThru | + Add-Member -Force -MemberType 'ScriptMethod' -Name 'AssertProperties' -Value { + return + } + } + } + + It 'Should return the correct values' { + InModuleScope -ScriptBlock { + Set-StrictMode -Version 1.0 + + $currentState = $script:mockWSManListenerInstance.Get() + + $currentState.Transport | Should -Be 'HTTP' + $currentState.Port | Should -Be 5985 + $currentState.Port | Should -BeOfType System.UInt16 + $currentState.Address | Should -Be '*' + $currentState.Enabled | Should -Be $true + $currentState.URLPrefix | Should -Be 'wsman' + + $currentState.Issuer | Should -BeNullOrEmpty + $currentState.SubjectFormat | Should -Be 'Both' + $currentState.MatchAlternate | Should -BeNullOrEmpty + $currentState.BaseDN | Should -BeNullOrEmpty + $currentState.CertificateThumbprint | Should -BeNullOrEmpty + $currentState.Hostname | Should -BeNullOrEmpty + + $currentState.Ensure | Should -Be 'Present' + $currentState.Reasons | Should -BeNullOrEmpty + } + } + } + + Context 'When getting a HTTPS listener' { + BeforeAll { + InModuleScope -ScriptBlock { + Set-StrictMode -Version 1.0 + + $script:mockWSManListenerInstance = [WSManListener] @{ + Transport = 'HTTPS' + Ensure = 'Present' + } + + <# + This mocks the method GetCurrentState(). + + Method Get() will call the base method Get() which will + call back to the derived class method GetCurrentState() + to get the result to return from the derived method Get(). + #> + $script:mockWSManListenerInstance | + Add-Member -Force -MemberType 'ScriptMethod' -Name 'GetCurrentState' -Value { + return [System.Collections.Hashtable] @{ + Transport = 'HTTPS' + Port = 5986 + Address = '*' + Enabled = 'true' + URLPrefix = 'wsman' + Issuer = $null + SubjectFormat = 'Both' + MatchAlternate = $null + BaseDN = $null + CertificateThumbprint = $null + Hostname = $null + } + } -PassThru | + Add-Member -Force -MemberType 'ScriptMethod' -Name 'AssertProperties' -Value { + return + } + } + } + + It 'Should return the correct values' { + InModuleScope -ScriptBlock { + Set-StrictMode -Version 1.0 + + $currentState = $script:mockWSManListenerInstance.Get() + + $currentState.Transport | Should -Be 'HTTPS' + $currentState.Port | Should -Be 5986 + $currentState.Port | Should -BeOfType System.UInt16 + $currentState.Address | Should -Be '*' + $currentState.Enabled | Should -Be $true + $currentState.URLPrefix | Should -Be 'wsman' + + $currentState.Issuer | Should -BeNullOrEmpty + $currentState.SubjectFormat | Should -Be 'Both' + $currentState.MatchAlternate | Should -BeNullOrEmpty + $currentState.BaseDN | Should -BeNullOrEmpty + $currentState.CertificateThumbprint | Should -BeNullOrEmpty + $currentState.Hostname | Should -BeNullOrEmpty + + $currentState.Ensure | Should -Be 'Present' + $currentState.Reasons | Should -BeNullOrEmpty + } + } + } + } + + # Context 'When the system is not in the desired state' { + # Context 'When property Path have the wrong value for a File audit' { + # BeforeAll { + # InModuleScope -ScriptBlock { + # $script:mockSqlAuditInstance = [SqlAudit] @{ + # Name = 'MockAuditName' + # InstanceName = 'NamedInstance' + # Path = 'C:\NewFolder' + # } + + # <# + # This mocks the method GetCurrentState(). + + # Method Get() will call the base method Get() which will + # call back to the derived class method GetCurrentState() + # to get the result to return from the derived method Get(). + # #> + # $script:mockSqlAuditInstance | + # Add-Member -Force -MemberType 'ScriptMethod' -Name 'GetCurrentState' -Value { + # return [System.Collections.Hashtable] @{ + # Name = 'MockAuditName' + # InstanceName = 'NamedInstance' + # Path = 'C:\Temp' + # } + # } -PassThru | + # Add-Member -Force -MemberType 'ScriptMethod' -Name 'AssertProperties' -Value { + # return + # } + # } + # } + + # It 'Should return the correct values' { + # InModuleScope -ScriptBlock { + # $currentState = $script:mockSqlAuditInstance.Get() + + # $currentState.InstanceName | Should -Be 'NamedInstance' + # $currentState.Name | Should -Be 'MockAuditName' + # $currentState.ServerName | Should -Be (Get-ComputerName) + # $currentState.Credential | Should -BeNullOrEmpty + + # $currentState.Path | Should -Be 'C:\Temp' + + # $currentState.Reasons | Should -HaveCount 1 + # $currentState.Reasons[0].Code | Should -Be 'SqlAudit:SqlAudit:Path' + # $currentState.Reasons[0].Phrase | Should -Be 'The property Path should be "C:\NewFolder", but was "C:\Temp"' + # } + # } + # } + # } +} From 12a469730f0fbe83defc34bfef18a9cdfb355ee5 Mon Sep 17 00:00:00 2001 From: Daniel Hughes Date: Wed, 18 Sep 2024 16:20:27 +0100 Subject: [PATCH 009/134] Move Get-Listener to Private --- source/Private/Get-Listener.ps1 | 28 +++++ tests/Unit/Private/Get-Listener.Tests.ps1 | 141 ++++++++++++++++++++++ 2 files changed, 169 insertions(+) create mode 100644 source/Private/Get-Listener.ps1 create mode 100644 tests/Unit/Private/Get-Listener.Tests.ps1 diff --git a/source/Private/Get-Listener.ps1 b/source/Private/Get-Listener.ps1 new file mode 100644 index 0000000..380d856 --- /dev/null +++ b/source/Private/Get-Listener.ps1 @@ -0,0 +1,28 @@ +<# + .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)] + [ValidateSet('HTTP', 'HTTPS')] + [System.String] + $Transport + ) + + $listeners = @(Get-WSManInstance -ResourceURI 'winrm/config/Listener' -Enumerate) + + if ($listeners) + { + return $listeners.Where( + { ($_.Transport -eq $Transport) -and ($_.Source -ne 'Compatibility') } + ) + } +} diff --git a/tests/Unit/Private/Get-Listener.Tests.ps1 b/tests/Unit/Private/Get-Listener.Tests.ps1 new file mode 100644 index 0000000..34ea025 --- /dev/null +++ b/tests/Unit/Private/Get-Listener.Tests.ps1 @@ -0,0 +1,141 @@ +[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '')] +param () + +BeforeDiscovery { + try + { + if (-not (Get-Module -Name 'DscResource.Test')) + { + # Assumes dependencies has been resolved, so if this module is not available, run 'noop' task. + if (-not (Get-Module -Name 'DscResource.Test' -ListAvailable)) + { + # Redirect all streams to $null, except the error stream (stream 2) + & "$PSScriptRoot/../../../build.ps1" -Tasks 'noop' 2>&1 4>&1 5>&1 6>&1 > $null + } + + # If the dependencies has not been resolved, this will throw an error. + Import-Module -Name 'DscResource.Test' -Force -ErrorAction 'Stop' + } + } + catch [System.IO.FileNotFoundException] + { + throw 'DscResource.Test module dependency not found. Please run ".\build.ps1 -ResolveDependency -Tasks build" first.' + } +} + +BeforeAll { + $script:dscModuleName = 'WSManDsc' + + Import-Module -Name $script:dscModuleName + + $PSDefaultParameterValues['InModuleScope:ModuleName'] = $script:dscModuleName + $PSDefaultParameterValues['Mock:ModuleName'] = $script:dscModuleName + $PSDefaultParameterValues['Should:ModuleName'] = $script:dscModuleName +} + +AfterAll { + $PSDefaultParameterValues.Remove('InModuleScope:ModuleName') + $PSDefaultParameterValues.Remove('Mock:ModuleName') + $PSDefaultParameterValues.Remove('Should:ModuleName') + + # Unload the module being tested so that it doesn't impact any other tests. + Get-Module -Name $script:dscModuleName -All | Remove-Module -Force +} + +Describe 'Get-Listener' -Tag 'Private' { + Context 'When the listener exists' { + BeforeAll { + Mock -CommandName Get-WSManInstance -MockWith { + @{ + Transport = 'HTTP' + Port = 5985 + Source = 'Compatibility' + } + @{ + Transport = 'HTTPS' + Port = 5986 + Source = 'Compatibility' + } + @{ + Transport = 'HTTP' + Port = 5985 + Source = 'NotCompatibility' + } + @{ + Transport = 'HTTPS' + Port = 5986 + Source = 'NotCompatibility' + } + } + } + + BeforeDiscovery { + $testCases = @( + @{ + Transport = 'HTTP' + ExpectedPort = 5985 + } + @{ + Transport = 'HTTPS' + ExpectedPort = 5986 + } + ) + } + + It 'Should return a listener for ''''' -ForEach $testCases { + InModuleScope -Parameters $_ -ScriptBlock { + Set-StrictMode -Version 1.0 + + $result = Get-Listener -Transport $Transport + + $result.Transport | Should -Be $Transport + $result.Port | Should -Be $ExpectedPort + } + + Should -Invoke -CommandName Get-WSManInstance -Exactly -Times 1 + } + } + + Context 'When the listener does not exist' { + BeforeAll { + Mock -CommandName Get-WSManInstance -MockWith { + @{ + Transport = 'HTTP' + Port = 5985 + Source = 'NotCompatibility' + } + @{ + Transport = 'HTTPS' + Port = 5986 + Source = 'NotCompatibility' + } + } + } + + BeforeDiscovery { + $testCases = @( + @{ + Transport = 'HTTP' + ExpectedPort = 5985 + } + @{ + Transport = 'HTTPS' + ExpectedPort = 5986 + } + ) + } + + It 'Should return a listener for ''''' -ForEach $testCases { + InModuleScope -Parameters $_ -ScriptBlock { + Set-StrictMode -Version 1.0 + + $result = Get-Listener -Transport $Transport + + $result | Should -HaveCount 1 + $result | Should -BeOfType System.Collections.Hashtable + } + + Should -Invoke -CommandName Get-WSManInstance -Exactly -Times 1 + } + } +} From a4c3080fa005257049c536337fe8b2869aefcd13 Mon Sep 17 00:00:00 2001 From: Daniel Hughes Date: Wed, 18 Sep 2024 16:20:42 +0100 Subject: [PATCH 010/134] Fix Test --- tests/Unit/Private/Get-DefaultPort.Tests.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Unit/Private/Get-DefaultPort.Tests.ps1 b/tests/Unit/Private/Get-DefaultPort.Tests.ps1 index 687a04c..a73428b 100644 --- a/tests/Unit/Private/Get-DefaultPort.Tests.ps1 +++ b/tests/Unit/Private/Get-DefaultPort.Tests.ps1 @@ -129,7 +129,7 @@ Describe 'Get-DefaultPort' -Tag 'Private' { Port = 'Something' } - { Get-DefaultPort @mockParams } | Should -Not -Throw + { Get-DefaultPort @mockParams } | Should -Throw } } } From 532366e9350e504c91bb1fa5d165b23829368d47 Mon Sep 17 00:00:00 2001 From: Daniel Hughes Date: Wed, 18 Sep 2024 16:52:23 +0100 Subject: [PATCH 011/134] Update DN to BaseDN --- ...nfig.ps1 => 3-WSManListener_HTTPS_WithBaseDN_Config.ps1} | 6 +++--- tests/Integration/DSC_WSManListener.Integration.Tests.ps1 | 6 +++--- tests/Integration/DSC_WSManListener_Add_HTTPS.config.ps1 | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) rename source/Examples/Resources/WSManListener/{3-WSManListener_HTTPS_WithDN_Config.ps1 => 3-WSManListener_HTTPS_WithBaseDN_Config.ps1} (88%) diff --git a/source/Examples/Resources/WSManListener/3-WSManListener_HTTPS_WithDN_Config.ps1 b/source/Examples/Resources/WSManListener/3-WSManListener_HTTPS_WithBaseDN_Config.ps1 similarity index 88% rename from source/Examples/Resources/WSManListener/3-WSManListener_HTTPS_WithDN_Config.ps1 rename to source/Examples/Resources/WSManListener/3-WSManListener_HTTPS_WithBaseDN_Config.ps1 index bd6b21e..55a79b9 100644 --- a/source/Examples/Resources/WSManListener/3-WSManListener_HTTPS_WithDN_Config.ps1 +++ b/source/Examples/Resources/WSManListener/3-WSManListener_HTTPS_WithBaseDN_Config.ps1 @@ -19,11 +19,11 @@ <# .DESCRIPTION - Create an HTTPS Listener using a LocalMachine certificate containing a DN matching + Create an HTTPS Listener using a LocalMachine certificate containing a BaseDN matching 'O=Contoso Inc, S=Pennsylvania, C=US' that is installed and issued by 'CN=CONTOSO.COM Issuing CA, DC=CONTOSO, DC=COM' on port 5986. #> -Configuration WSManListener_HTTPS_WithDN_Config +Configuration WSManListener_HTTPS_WithBaseDN_Config { Import-DscResource -Module WSManDsc @@ -34,7 +34,7 @@ Configuration WSManListener_HTTPS_WithDN_Config Transport = 'HTTPS' Ensure = 'Present' Issuer = 'CN=CONTOSO.COM Issuing CA, DC=CONTOSO, DC=COM' - DN = 'O=Contoso Inc, S=Pennsylvania, C=US' + BaseDN = 'O=Contoso Inc, S=Pennsylvania, C=US' } # End of WSManListener Resource } # End of Node } # End of Configuration diff --git a/tests/Integration/DSC_WSManListener.Integration.Tests.ps1 b/tests/Integration/DSC_WSManListener.Integration.Tests.ps1 index 69e0174..f1fa5d8 100644 --- a/tests/Integration/DSC_WSManListener.Integration.Tests.ps1 +++ b/tests/Integration/DSC_WSManListener.Integration.Tests.ps1 @@ -63,8 +63,8 @@ BeforeAll { Remove-Item -Force $script:Hostname = ([System.Net.Dns]::GetHostByName($ENV:computerName).Hostname) - $script:DN = 'O=Contoso Inc, S=Pennsylvania, C=US' - $script:Issuer = "CN=$Hostname, $DN" + $script:BaseDN = 'O=Contoso Inc, S=Pennsylvania, C=US' + $script:Issuer = "CN=$Hostname, $BaseDN" # Create the certificate if ([System.Environment]::OSVersion.Version.Major -ge 10) @@ -190,7 +190,7 @@ Describe "$($script:dscResourceName)_Integration_Add_HTTPS" { Issuer = $Issuer SubjectFormat = 'Both' MatchAlternate = $false - DN = $DN + BaseDN = $BaseDN Hostname = $Hostname } ) diff --git a/tests/Integration/DSC_WSManListener_Add_HTTPS.config.ps1 b/tests/Integration/DSC_WSManListener_Add_HTTPS.config.ps1 index d7d0eb6..edc59a4 100644 --- a/tests/Integration/DSC_WSManListener_Add_HTTPS.config.ps1 +++ b/tests/Integration/DSC_WSManListener_Add_HTTPS.config.ps1 @@ -10,7 +10,7 @@ Configuration DSC_WSManListener_Config_Add_HTTPS { Issuer = $Node.Issuer SubjectFormat = $Node.SubjectFormat MatchAlternate = $Node.MatchAlternate - DN = $Node.DN + BaseDN = $Node.BaseDN } } } From 3b2b455a466eb2883057f1d2eba80f5605e8ead6 Mon Sep 17 00:00:00 2001 From: Daniel Hughes Date: Wed, 18 Sep 2024 17:06:40 +0100 Subject: [PATCH 012/134] Update Changelog --- CHANGELOG.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6272d11..7ffffc2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 for the DSC resource. - Added build task `Generate_Wiki_Content` to generate the wiki content that can be used to update the GitHub Wiki. +- `WSManReason` + - Used in Class Resources ### Changed @@ -60,12 +62,22 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - `DSC_WSManServiceConfig` - Refactor `Test-TargetResource` to use `Test-DscParameterState`. - Remove unused strings +- `DSC_WSManListener` + - Converted to Class Resource + - Extracted private functions to own files + - BREAKING: Renamed parameter `DN` to `BaseDN` - fixes [Issue #89](https://github.com/dsccommunity/WSManDsc/issues/89). +- `RequiredModules` + - Added `DscResource.Base` class ### Fixed - Fixed pipeline by replacing the GitVersion task in the `azure-pipelines.yml` with a script. +### Removed +- `Get-InvalidOperationRecord` + - This is now provided by `DscResource.Test` + ## [3.1.1] - 2020-01-31 ### Changed From 8f02f2c47f5aac0b7f03903ac3d17bc7784a8ac7 Mon Sep 17 00:00:00 2001 From: Daniel Hughes Date: Wed, 18 Sep 2024 17:18:28 +0100 Subject: [PATCH 013/134] Add Find-Certificate --- source/Private/Find-Certificate.ps1 | 185 ++++++++ tests/Unit/Private/Find-Certificate.Tests.ps1 | 417 ++++++++++++++++++ 2 files changed, 602 insertions(+) create mode 100644 source/Private/Find-Certificate.ps1 create mode 100644 tests/Unit/Private/Find-Certificate.Tests.ps1 diff --git a/source/Private/Find-Certificate.ps1 b/source/Private/Find-Certificate.ps1 new file mode 100644 index 0000000..167dd72 --- /dev/null +++ b/source/Private/Find-Certificate.ps1 @@ -0,0 +1,185 @@ +<# + .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 DN + This is a Distinguished Name component that will be 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()] + [ValidateSet('Both', 'FQDNOnly', 'NameOnly')] + [System.String] + $SubjectFormat = 'Both', + + [Parameter()] + [System.Boolean] + $MatchAlternate, + + [Parameter()] + [System.String] + $DN, + + [Parameter()] + [System.String] + $CertificateThumbprint, + + [Parameter()] + [System.String] + $Hostname + ) + + if ($PSBoundParameters.ContainsKey('CertificateThumbprint')) + { + Write-Verbose -Message ( @( + "$($MyInvocation.MyCommand): " + $($script:localizedData.FindCertificateByThumbprintMessage) ` + -f $CertificateThumbprint + ) -join '' ) + + $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 'Both', 'FQDNOnly') + { + # Lookup the certificate using the FQDN of the machine + if ([System.String]::IsNullOrEmpty($Hostname)) + { + $Hostname = [System.Net.Dns]::GetHostByName($ENV:computerName).Hostname + } + $Subject = "CN=$Hostname" + + if ($PSBoundParameters.ContainsKey('DN')) + { + $Subject = "$Subject, $DN" + } # if + + if ($MatchAlternate) + { + # Try and lookup the certificate using the subject and the alternate name + Write-Verbose -Message ( @( + "$($MyInvocation.MyCommand): " + $($script:localizedData.FindCertificateAlternateMessage) ` + -f $Subject, $Issuer, $Hostname + ) -join '' ) + + $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 ( @( + "$($MyInvocation.MyCommand): " + $($script:localizedData.FindCertificateMessage) ` + -f $Subject, $Issuer + ) -join '' ) + + $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 'Both', 'NameOnly')) + { + # If could not find an FQDN cert, try for one issued to the computer name + [System.String] $Hostname = $ENV:ComputerName + [System.String] $Subject = "CN=$Hostname" + + if ($PSBoundParameters.ContainsKey('DN')) + { + $Subject = "$Subject, $DN" + } # if + + if ($MatchAlternate) + { + # Try and lookup the certificate using the subject and the alternate name + Write-Verbose -Message ( @( + "$($MyInvocation.MyCommand): " + $($script:localizedData.FindCertificateAlternateMessage) ` + -f $Subject, $Issuer, $Hostname + ) -join '' ) + + $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 ( @( + "$($MyInvocation.MyCommand): " + $($script:localizedData.FindCertificateMessage) ` + -f $Subject, $Issuer + ) -join '' ) + + $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 ( @( + "$($MyInvocation.MyCommand): " + $($script:localizedData.CertificateFoundMessage) ` + -f $certificate.thumbprint + ) -join '' ) + } + else + { + Write-Verbose -Message ( @( + "$($MyInvocation.MyCommand): " + $($script:localizedData.CertificateNotFoundMessage) ` + ) -join '' ) + } # if + + return $certificate +} diff --git a/tests/Unit/Private/Find-Certificate.Tests.ps1 b/tests/Unit/Private/Find-Certificate.Tests.ps1 new file mode 100644 index 0000000..61c4202 --- /dev/null +++ b/tests/Unit/Private/Find-Certificate.Tests.ps1 @@ -0,0 +1,417 @@ +[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '')] +param () + +BeforeDiscovery { + try + { + if (-not (Get-Module -Name 'DscResource.Test')) + { + # Assumes dependencies has been resolved, so if this module is not available, run 'noop' task. + if (-not (Get-Module -Name 'DscResource.Test' -ListAvailable)) + { + # Redirect all streams to $null, except the error stream (stream 2) + & "$PSScriptRoot/../../../build.ps1" -Tasks 'noop' 2>&1 4>&1 5>&1 6>&1 > $null + } + + # If the dependencies has not been resolved, this will throw an error. + Import-Module -Name 'DscResource.Test' -Force -ErrorAction 'Stop' + } + } + catch [System.IO.FileNotFoundException] + { + throw 'DscResource.Test module dependency not found. Please run ".\build.ps1 -ResolveDependency -Tasks build" first.' + } +} + +BeforeAll { + $script:dscModuleName = 'WSManDsc' + + Import-Module -Name $script:dscModuleName + + $PSDefaultParameterValues['InModuleScope:ModuleName'] = $script:dscModuleName + $PSDefaultParameterValues['Mock:ModuleName'] = $script:dscModuleName + $PSDefaultParameterValues['Should:ModuleName'] = $script:dscModuleName +} + +AfterAll { + $PSDefaultParameterValues.Remove('InModuleScope:ModuleName') + $PSDefaultParameterValues.Remove('Mock:ModuleName') + $PSDefaultParameterValues.Remove('Should:ModuleName') + + # Unload the module being tested so that it doesn't impact any other tests. + Get-Module -Name $script:dscModuleName -All | Remove-Module -Force +} + +Describe 'Find-Certificate' -Tag 'Private' { + # Context 'CertificateThumbprint is passed but does not exist' { + # BeforeAll { + # Mock -CommandName Get-ChildItem + # } + + # It 'Should not throw error' { + # InModuleScope -Parameters @{ + # mockCertificateThumbprint = $script:mockCertificateThumbprint + # } -ScriptBlock { + # Set-StrictMode -Version 1.0 + + # { $script:returnedCertificate = Find-Certificate ` + # -CertificateThumbprint $mockCertificateThumbprint ` + # -Verbose:$VerbosePreference } | Should -Not -Throw + # } + # } + + # It 'Should return null' { + # InModuleScope -ScriptBlock { + # Set-StrictMode -Version 1.0 + + # $script:returnedCertificate | Should -BeNullOrEmpty + # } + # } + + # It 'Should call expected Mocks' { + # Should -Invoke ` + # -CommandName Get-ChildItem ` + # -Exactly -Times 1 ` + # -Scope Context + # } + # } + + # Context 'CertificateThumbprint is passed and does exist' { + # BeforeAll { + # Mock -CommandName Get-ChildItem -MockWith { + # $mockCertificateDN + # } + # } + + # It 'Should not throw error' { + # InModuleScope -Parameters @{ + # mockCertificateThumbprint = $script:mockCertificateThumbprint + # } -ScriptBlock { + # Set-StrictMode -Version 1.0 + + # { $script:returnedCertificate = Find-Certificate ` + # -CertificateThumbprint $mockCertificateThumbprint ` + # -Verbose:$VerbosePreference } | Should -Not -Throw + # } + # } + + # It 'Should return expected certificate' { + # InModuleScope -Parameters @{ + # mockCertificateThumbprint = $script:mockCertificateThumbprint + # } -ScriptBlock { + # Set-StrictMode -Version 1.0 + + # $script:returnedCertificate.Thumbprint | Should -Be $mockCertificateThumbprint + # } + # } + + # It 'Should call expected Mocks' { + # Should -Invoke ` + # -CommandName Get-ChildItem ` + # -Exactly -Times 1 ` + # -Scope Context + # } + # } + + # Context 'SubjectFormat is Both, Certificate does not exist, DN passed' { + # BeforeAll { + # Mock -CommandName Get-ChildItem + # } + + # It 'Should not throw error' { + # InModuleScope -Parameters @{ + # mockIssuer = $script:mockIssuer + # mockDN = $script:mockDN + # } -ScriptBlock { + # Set-StrictMode -Version 1.0 + + # { $script:returnedCertificate = Find-Certificate ` + # -Issuer $mockIssuer ` + # -SubjectFormat 'Both' ` + # -MatchAlternate $true ` + # -DN $mockDN ` + # -Verbose:$VerbosePreference } | Should -Not -Throw + # } + # } + + # It 'Should return null' { + # InModuleScope -ScriptBlock { + # Set-StrictMode -Version 1.0 + + # $script:returnedCertificate | Should -BeNullOrEmpty + # } + # } + + # It 'Should call expected Mocks' { + # Should -Invoke ` + # -CommandName Get-ChildItem ` + # -Exactly -Times 2 ` + # -Scope Context + # } + # } + + # Context 'SubjectFormat is Both, Certificate with DN Exists, DN passed' { + # BeforeAll { + # Mock -CommandName Get-ChildItem -MockWith { + # $mockCertificateDN + # } + # } + + # It 'Should not throw error' { + # InModuleScope -Parameters @{ + # mockIssuer = $script:mockIssuer + # mockDN = $script:mockDN + # } -ScriptBlock { + # Set-StrictMode -Version 1.0 + + # { $script:returnedCertificate = Find-Certificate ` + # -Issuer $mockIssuer ` + # -SubjectFormat 'Both' ` + # -MatchAlternate $true ` + # -DN $mockDN ` + # -Verbose:$VerbosePreference } | Should -Not -Throw + # } + # } + + # It 'Should return expected certificate' { + # InModuleScope -Parameters @{ + # mockCertificateThumbprint = $script:mockCertificateThumbprint + # } -ScriptBlock { + # Set-StrictMode -Version 1.0 + + # $script:returnedCertificate.Thumbprint | Should -Be $mockCertificateThumbprint + # } + # } + + # It 'Should call expected Mocks' { + # Should -Invoke ` + # -CommandName Get-ChildItem ` + # -Exactly -Times 1 ` + # -Scope Context + # } + # } + + # Context 'SubjectFormat is Both, Certificate without DN Exists, DN passed' { + # BeforeAll { + # Mock -CommandName Get-ChildItem -MockWith { + # $mockCertificate + # } + # } + + # It 'Should not throw error' { + # InModuleScope -Parameters @{ + # mockIssuer = $script:mockIssuer + # mockDN = $script:mockDN + # } -ScriptBlock { + # Set-StrictMode -Version 1.0 + + # { $script:returnedCertificate = Find-Certificate ` + # -Issuer $mockIssuer ` + # -SubjectFormat 'Both' ` + # -MatchAlternate $true ` + # -DN $mockDN ` + # -Verbose:$VerbosePreference } | Should -Not -Throw + # } + # } + + # It 'Should return null' { + # InModuleScope -ScriptBlock { + # Set-StrictMode -Version 1.0 + + # $script:returnedCertificate | Should -BeNullOrEmpty + # } + # } + + # It 'Should call expected Mocks' { + # Should -Invoke ` + # -CommandName Get-ChildItem ` + # -Exactly -Times 2 ` + # -Scope Context + # } + # } + + # Context 'SubjectFormat is Both, Certificate does not exist, DN not passed' { + # BeforeAll { + # Mock -CommandName Get-ChildItem + # } + + # It 'Should not throw error' { + # InModuleScope -Parameters @{ + # mockIssuer = $script:mockIssuer + # } -ScriptBlock { + # Set-StrictMode -Version 1.0 + + # { $script:returnedCertificate = Find-Certificate ` + # -Issuer $mockIssuer ` + # -SubjectFormat 'Both' ` + # -MatchAlternate $true ` + # -Verbose:$VerbosePreference } | Should -Not -Throw + # } + # } + + # It 'Should return null' { + # InModuleScope -ScriptBlock { + # Set-StrictMode -Version 1.0 + + # $script:returnedCertificate | Should -BeNullOrEmpty + # } + # } + + # It 'Should call expected Mocks' { + # Should -Invoke ` + # -CommandName Get-ChildItem ` + # -Exactly -Times 2 ` + # -Scope Context + # } + # } + + # Context 'SubjectFormat is Both, Certificate with DN Exists, DN not passed' { + # BeforeAll { + # Mock -CommandName Get-ChildItem -MockWith { + # $mockCertificateDN + # } + # } + + # It 'Should not throw error' { + # InModuleScope -Parameters @{ + # mockIssuer = $script:mockIssuer + # } -ScriptBlock { + # Set-StrictMode -Version 1.0 + + # { $script:returnedCertificate = Find-Certificate ` + # -Issuer $mockIssuer ` + # -SubjectFormat 'Both' ` + # -MatchAlternate $true ` + # -Verbose:$VerbosePreference } | Should -Not -Throw + # } + # } + + # It 'Should return null' { + # InModuleScope -ScriptBlock { + # Set-StrictMode -Version 1.0 + + # $script:returnedCertificate | Should -BeNullOrEmpty + # } + # } + + # It 'Should call expected Mocks' { + # Should -Invoke ` + # -CommandName Get-ChildItem ` + # -Exactly -Times 2 ` + # -Scope Context + # } + # } + + # Context 'SubjectFormat is Both, Certificate without DN Exists, DN not passed' { + # BeforeAll { + # Mock -CommandName Get-ChildItem -MockWith { + # $mockCertificate + # } + # } + + # It 'Should not throw error' { + # InModuleScope -Parameters @{ + # mockIssuer = $script:mockIssuer + # } -ScriptBlock { + # Set-StrictMode -Version 1.0 + + # { $script:returnedCertificate = Find-Certificate ` + # -Issuer $mockIssuer ` + # -SubjectFormat 'Both' ` + # -MatchAlternate $true ` + # -Verbose:$VerbosePreference } | Should -Not -Throw + # } + # } + + # It 'Should return expected certificate' { + # InModuleScope -Parameters @{ + # mockCertificateThumbprint = $script:mockCertificateThumbprint + # } -ScriptBlock { + # Set-StrictMode -Version 1.0 + + # $script:returnedCertificate.Thumbprint | Should -Be $mockCertificateThumbprint + # } + # } + + # It 'Should call expected Mocks' { + # Should -Invoke ` + # -CommandName Get-ChildItem ` + # -Exactly -Times 1 ` + # -Scope Context + # } + # } + + # Context 'SubjectFormat is Both, Certificate does not exist, DN not passed, MatchAlternate is false' { + # BeforeAll { + # Mock -CommandName Get-ChildItem + # } + + # It 'Should not throw error' { + # InModuleScope -Parameters @{ + # mockIssuer = $script:mockIssuer + # } -ScriptBlock { + # Set-StrictMode -Version 1.0 + + # { $script:returnedCertificate = Find-Certificate ` + # -Issuer $mockIssuer ` + # -SubjectFormat 'Both' ` + # -MatchAlternate $false ` + # -Verbose:$VerbosePreference } | Should -Not -Throw + # } + # } + + # It 'Should return null' { + # InModuleScope -ScriptBlock { + # Set-StrictMode -Version 1.0 + + # $script:returnedCertificate | Should -BeNullOrEmpty + # } + # } + + # It 'Should call expected Mocks' { + # Should -Invoke ` + # -CommandName Get-ChildItem ` + # -Exactly -Times 2 ` + # -Scope Context + # } + # } + + # Context 'SubjectFormat is Both, Certificate without DN Exists, DN not passed, MatchAlternate is false' { + # BeforeAll { + # Mock -CommandName Get-ChildItem -MockWith { + # $mockCertificate + # } + # } + + # It 'Should not throw error' { + # InModuleScope -Parameters @{ + # mockIssuer = $script:mockIssuer + # } -ScriptBlock { + # Set-StrictMode -Version 1.0 + + # { $script:returnedCertificate = Find-Certificate ` + # -Issuer $mockIssuer ` + # -SubjectFormat 'Both' ` + # -MatchAlternate $false ` + # -Verbose:$VerbosePreference } | Should -Not -Throw + # } + # } + + # It 'Should return expected certificate' { + # InModuleScope -Parameters @{ + # mockCertificateThumbprint = $script:mockCertificateThumbprint + # } -ScriptBlock { + # Set-StrictMode -Version 1.0 + + # $script:returnedCertificate.Thumbprint | Should -Be $mockCertificateThumbprint + # } + # } + + # It 'Should call expected Mocks' { + # Should -Invoke ` + # -CommandName Get-ChildItem ` + # -Exactly -Times 1 ` + # -Scope Context + # } + # } +} From 145c4787f7c2ed6aa7df9a62572edcbe8189e603 Mon Sep 17 00:00:00 2001 From: Daniel Hughes Date: Wed, 18 Sep 2024 17:26:33 +0100 Subject: [PATCH 014/134] Do not use modulefast --- Resolve-Dependency.psd1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Resolve-Dependency.psd1 b/Resolve-Dependency.psd1 index bbf34e1..07945f8 100644 --- a/Resolve-Dependency.psd1 +++ b/Resolve-Dependency.psd1 @@ -3,7 +3,7 @@ AllowPrerelease = $false WithYAML = $true - UseModuleFast = $true + #UseModuleFast = $true #ModuleFastVersion = '0.1.2' #ModuleFastBleedingEdge = $true From b4da7c639a1c70458668647a1dcdc5d50363fb54 Mon Sep 17 00:00:00 2001 From: Daniel Hughes Date: Wed, 18 Sep 2024 18:32:02 +0100 Subject: [PATCH 015/134] Add WSManReason Tests --- tests/Unit/Classes/WSManReason.Tests.ps1 | 76 ++++++++++++++++++++++++ 1 file changed, 76 insertions(+) create mode 100644 tests/Unit/Classes/WSManReason.Tests.ps1 diff --git a/tests/Unit/Classes/WSManReason.Tests.ps1 b/tests/Unit/Classes/WSManReason.Tests.ps1 new file mode 100644 index 0000000..878a7d5 --- /dev/null +++ b/tests/Unit/Classes/WSManReason.Tests.ps1 @@ -0,0 +1,76 @@ +[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '')] +param () + +BeforeDiscovery { + try + { + if (-not (Get-Module -Name 'DscResource.Test')) + { + # Assumes dependencies has been resolved, so if this module is not available, run 'noop' task. + if (-not (Get-Module -Name 'DscResource.Test' -ListAvailable)) + { + # Redirect all streams to $null, except the error stream (stream 2) + & "$PSScriptRoot/../../../build.ps1" -Tasks 'noop' 2>&1 4>&1 5>&1 6>&1 > $null + } + + # If the dependencies has not been resolved, this will throw an error. + Import-Module -Name 'DscResource.Test' -Force -ErrorAction 'Stop' + } + } + catch [System.IO.FileNotFoundException] + { + throw 'DscResource.Test module dependency not found. Please run ".\build.ps1 -ResolveDependency -Tasks build" first.' + } +} + +BeforeAll { + $script:dscModuleName = 'WSManDsc' + + Import-Module -Name $script:dscModuleName + + $PSDefaultParameterValues['InModuleScope:ModuleName'] = $script:dscModuleName + $PSDefaultParameterValues['Mock:ModuleName'] = $script:dscModuleName + $PSDefaultParameterValues['Should:ModuleName'] = $script:dscModuleName +} + +AfterAll { + $PSDefaultParameterValues.Remove('InModuleScope:ModuleName') + $PSDefaultParameterValues.Remove('Mock:ModuleName') + $PSDefaultParameterValues.Remove('Should:ModuleName') + + # Unload the module being tested so that it doesn't impact any other tests. + Get-Module -Name $script:dscModuleName -All | Remove-Module -Force +} + +Describe 'WSManReason' -Tag 'WSManReason' { + Context 'When instantiating the class' { + It 'Should not throw an error' { + $script:mockWSManReasonInstance = InModuleScope -ScriptBlock { + [WSManReason]::new() + } + } + + It 'Should be of the correct type' { + $mockWSManReasonInstance | Should -Not -BeNullOrEmpty + $mockWSManReasonInstance.GetType().Name | Should -Be 'WSManReason' + } + } + + Context 'When setting an reading values' { + It 'Should be able to set value in instance' { + $script:mockWSManReasonInstance = InModuleScope -ScriptBlock { + $WSManReasonInstance = [WSManReason]::new() + + $WSManReasonInstance.Code = 'WSManReason:WSManReason:Ensure' + $WSManReasonInstance.Phrase = 'The property Ensure should be "Present", but was "Absent"' + + return $WSManReasonInstance + } + } + + It 'Should be able read the values from instance' { + $mockWSManReasonInstance.Code | Should -Be 'WSManReason:WSManReason:Ensure' + $mockWSManReasonInstance.Phrase = 'The property Ensure should be "Present", but was "Absent"' + } + } +} From eb9d9f6d18e0bed96c890dc42e4f436f4db4bea1 Mon Sep 17 00:00:00 2001 From: Daniel Hughes Date: Wed, 18 Sep 2024 19:32:58 +0100 Subject: [PATCH 016/134] Update tests --- source/Classes/020.WSManListener.ps1 | 26 ++++- tests/Unit/Classes/WSManListener.Tests.ps1 | 121 ++++++++++++--------- 2 files changed, 93 insertions(+), 54 deletions(-) diff --git a/source/Classes/020.WSManListener.ps1 b/source/Classes/020.WSManListener.ps1 index b4fa37e..6e39ec2 100644 --- a/source/Classes/020.WSManListener.ps1 +++ b/source/Classes/020.WSManListener.ps1 @@ -116,11 +116,11 @@ class WSManListener : ResourceBase ) - # Get the port if it's not provided - if ($this.Port) - { - $this.Port = Get-DefaultPort -Transport $this.Transport -Port $this.Port - } + # # Set subject format to default value + # if (-not $this.SubjectFormat) + # { + # $this.SubjectFormat = 'Both' + # } } [WSManListener] Get() @@ -136,6 +136,12 @@ class WSManListener : ResourceBase Transport = $properties.Transport } + # Get the port if it's not provided + if (-not $properties.Port) + { + $this.Port = Get-DefaultPort -Transport $properties.Transport + } + $getCurrentStateResult = Get-Listener @getParameters $currentCertificate = '' @@ -202,6 +208,16 @@ class WSManListener : ResourceBase #> hidden [void] AssertProperties([System.Collections.Hashtable] $properties) { + # if ($null -ne $properties.SubjectFormat) + # { + # $errorMessage = $this.localizedData.SubjectFormatMustBeValid + + # if ($properties.SubjectFormat -inotin ('Both', 'FQDNOnly', 'NameOnly')) + # { + # New-InvalidArgumentException -ArgumentName 'SubjectFormat' -Message $errorMessage + # } + # } + # # The properties MaximumFiles and MaximumRolloverFiles are mutually exclusive. # $assertBoundParameterParameters = @{ # BoundParameterList = $properties diff --git a/tests/Unit/Classes/WSManListener.Tests.ps1 b/tests/Unit/Classes/WSManListener.Tests.ps1 index 05a94ed..dab01ea 100644 --- a/tests/Unit/Classes/WSManListener.Tests.ps1 +++ b/tests/Unit/Classes/WSManListener.Tests.ps1 @@ -216,53 +216,76 @@ Describe 'WSManListener\Get()' -Tag 'Get' { } } - # Context 'When the system is not in the desired state' { - # Context 'When property Path have the wrong value for a File audit' { - # BeforeAll { - # InModuleScope -ScriptBlock { - # $script:mockSqlAuditInstance = [SqlAudit] @{ - # Name = 'MockAuditName' - # InstanceName = 'NamedInstance' - # Path = 'C:\NewFolder' - # } - - # <# - # This mocks the method GetCurrentState(). - - # Method Get() will call the base method Get() which will - # call back to the derived class method GetCurrentState() - # to get the result to return from the derived method Get(). - # #> - # $script:mockSqlAuditInstance | - # Add-Member -Force -MemberType 'ScriptMethod' -Name 'GetCurrentState' -Value { - # return [System.Collections.Hashtable] @{ - # Name = 'MockAuditName' - # InstanceName = 'NamedInstance' - # Path = 'C:\Temp' - # } - # } -PassThru | - # Add-Member -Force -MemberType 'ScriptMethod' -Name 'AssertProperties' -Value { - # return - # } - # } - # } - - # It 'Should return the correct values' { - # InModuleScope -ScriptBlock { - # $currentState = $script:mockSqlAuditInstance.Get() - - # $currentState.InstanceName | Should -Be 'NamedInstance' - # $currentState.Name | Should -Be 'MockAuditName' - # $currentState.ServerName | Should -Be (Get-ComputerName) - # $currentState.Credential | Should -BeNullOrEmpty - - # $currentState.Path | Should -Be 'C:\Temp' - - # $currentState.Reasons | Should -HaveCount 1 - # $currentState.Reasons[0].Code | Should -Be 'SqlAudit:SqlAudit:Path' - # $currentState.Reasons[0].Phrase | Should -Be 'The property Path should be "C:\NewFolder", but was "C:\Temp"' - # } - # } - # } - # } + Context 'When the system is not in the desired state' { + Context 'When property ''Port'' has the wrong value' { + BeforeAll { + InModuleScope -ScriptBlock { + Set-StrictMode -Version 1.0 + + $script:mockWSManListenerInstance = [WSManListener] @{ + Transport = 'HTTPS' + Port = 5986 + Ensure = 'Present' + } + + <# + This mocks the method GetCurrentState(). + + Method Get() will call the base method Get() which will + call back to the derived class method GetCurrentState() + to get the result to return from the derived method Get(). + #> + $script:mockWSManListenerInstance | + Add-Member -Force -MemberType 'ScriptMethod' -Name 'GetCurrentState' -Value { + return [System.Collections.Hashtable] @{ + Transport = 'HTTPS' + Port = 6000 + Address = '*' + Enabled = 'true' + URLPrefix = 'wsman' + Issuer = $null + SubjectFormat = 'Both' + MatchAlternate = $null + BaseDN = $null + CertificateThumbprint = $null + Hostname = $null + } + } -PassThru | + Add-Member -Force -MemberType 'ScriptMethod' -Name 'AssertProperties' -Value { + return + } + } + } + + It 'Should return the correct values' { + InModuleScope -ScriptBlock { + Set-StrictMode -Version 1.0 + + $currentState = $script:mockWSManListenerInstance.Get() + + $currentState.Transport | Should -Be 'HTTPS' + + $currentState.Port | Should -Be 6000 + $currentState.Port | Should -BeOfType System.UInt16 + + $currentState.Address | Should -Be '*' + $currentState.Enabled | Should -Be $true + $currentState.URLPrefix | Should -Be 'wsman' + + $currentState.Issuer | Should -BeNullOrEmpty + $currentState.SubjectFormat | Should -Be 'Both' + $currentState.MatchAlternate | Should -BeNullOrEmpty + $currentState.BaseDN | Should -BeNullOrEmpty + $currentState.CertificateThumbprint | Should -BeNullOrEmpty + $currentState.Hostname | Should -BeNullOrEmpty + + $currentState.Ensure | Should -Be 'Present' + + $currentState.Reasons | Should -HaveCount 1 + $currentState.Reasons[0].Code | Should -Be 'WSManListener:WSManListener:Port' + $currentState.Reasons[0].Phrase | Should -Be 'The property Port should be 5986, but was 6000' + } + } + } + } } From 6a5e74d49959d062c3ac9b1632f18a7e84f9ec8e Mon Sep 17 00:00:00 2001 From: Daniel Hughes Date: Wed, 18 Sep 2024 20:04:50 +0100 Subject: [PATCH 017/134] UPdate Find Certificate --- source/Private/Find-Certificate.ps1 | 60 +- source/en-US/WSManDsc.strings.psd1 | 17 + tests/Unit/Private/Find-Certificate.Tests.ps1 | 587 +++++++++--------- 3 files changed, 328 insertions(+), 336 deletions(-) create mode 100644 source/en-US/WSManDsc.strings.psd1 diff --git a/source/Private/Find-Certificate.ps1 b/source/Private/Find-Certificate.ps1 index 167dd72..ac67b53 100644 --- a/source/Private/Find-Certificate.ps1 +++ b/source/Private/Find-Certificate.ps1 @@ -14,9 +14,9 @@ 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 DN - This is a Distinguished Name component that will be used to identify the certificate to use - for the 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. @@ -42,7 +42,7 @@ function Find-Certificate [Parameter()] [System.String] - $DN, + $BaseDN, [Parameter()] [System.String] @@ -55,11 +55,7 @@ function Find-Certificate if ($PSBoundParameters.ContainsKey('CertificateThumbprint')) { - Write-Verbose -Message ( @( - "$($MyInvocation.MyCommand): " - $($script:localizedData.FindCertificateByThumbprintMessage) ` - -f $CertificateThumbprint - ) -join '' ) + Write-Verbose -Message ($script:localizedData.FindCertificate_ByThumbprintMessage -f $CertificateThumbprint) $certificate = Get-ChildItem -Path Cert:\localmachine\my | Where-Object -FilterScript { ($_.Thumbprint -eq $CertificateThumbprint) @@ -77,19 +73,15 @@ function Find-Certificate } $Subject = "CN=$Hostname" - if ($PSBoundParameters.ContainsKey('DN')) + if ($PSBoundParameters.ContainsKey('BaseDN')) { - $Subject = "$Subject, $DN" + $Subject = "$Subject, $BaseDN" } # if if ($MatchAlternate) { # Try and lookup the certificate using the subject and the alternate name - Write-Verbose -Message ( @( - "$($MyInvocation.MyCommand): " - $($script:localizedData.FindCertificateAlternateMessage) ` - -f $Subject, $Issuer, $Hostname - ) -join '' ) + Write-Verbose -Message ($script:localizedData.FindCertificate_AlternateMessage -f $Subject, $Issuer, $Hostname) $certificate = (Get-ChildItem -Path Cert:\localmachine\my | Where-Object -FilterScript { ($_.Extensions.EnhancedKeyUsages.FriendlyName ` @@ -102,11 +94,7 @@ function Find-Certificate else { # Try and lookup the certificate using the subject name - Write-Verbose -Message ( @( - "$($MyInvocation.MyCommand): " - $($script:localizedData.FindCertificateMessage) ` - -f $Subject, $Issuer - ) -join '' ) + Write-Verbose -Message ($script:localizedData.FindCertificate_Message - $Subject, $Issuer) $certificate = Get-ChildItem -Path Cert:\localmachine\my | Where-Object -FilterScript { ($_.Extensions.EnhancedKeyUsages.FriendlyName ` @@ -117,26 +105,21 @@ function Find-Certificate } # if } - if (-not $certificate ` - -and ($SubjectFormat -in 'Both', 'NameOnly')) + if (-not $certificate -and ($SubjectFormat -in 'Both', 'NameOnly')) { # If could not find an FQDN cert, try for one issued to the computer name [System.String] $Hostname = $ENV:ComputerName [System.String] $Subject = "CN=$Hostname" - if ($PSBoundParameters.ContainsKey('DN')) + if ($PSBoundParameters.ContainsKey('BaseDN')) { - $Subject = "$Subject, $DN" + $Subject = "$Subject, $BaseDN" } # if if ($MatchAlternate) { # Try and lookup the certificate using the subject and the alternate name - Write-Verbose -Message ( @( - "$($MyInvocation.MyCommand): " - $($script:localizedData.FindCertificateAlternateMessage) ` - -f $Subject, $Issuer, $Hostname - ) -join '' ) + Write-Verbose -Message ($script:localizedData.FindCertificate_AlternateMessage -f $Subject, $Issuer, $Hostname) $certificate = Get-ChildItem -Path Cert:\localmachine\my | Where-Object -FilterScript { ($_.Extensions.EnhancedKeyUsages.FriendlyName ` @@ -149,11 +132,7 @@ function Find-Certificate else { # Try and lookup the certificate using the subject name - Write-Verbose -Message ( @( - "$($MyInvocation.MyCommand): " - $($script:localizedData.FindCertificateMessage) ` - -f $Subject, $Issuer - ) -join '' ) + Write-Verbose -Message ($script:localizedData.FindCertificate_Message - $Subject, $Issuer) $certificate = Get-ChildItem -Path Cert:\localmachine\my | Where-Object -FilterScript { ($_.Extensions.EnhancedKeyUsages.FriendlyName ` @@ -167,18 +146,11 @@ function Find-Certificate if ($certificate) { - Write-Verbose -Message ( @( - "$($MyInvocation.MyCommand): " - $($script:localizedData.CertificateFoundMessage) ` - -f $certificate.thumbprint - ) -join '' ) + Write-Verbose -Message ($script:localizedData.FindCertificate_FoundMessage -f $certificate.thumbprint) } else { - Write-Verbose -Message ( @( - "$($MyInvocation.MyCommand): " - $($script:localizedData.CertificateNotFoundMessage) ` - ) -join '' ) + Write-Verbose -Message ($script:localizedData.FindCertificate_NotFoundMessage) } # if return $certificate diff --git a/source/en-US/WSManDsc.strings.psd1 b/source/en-US/WSManDsc.strings.psd1 new file mode 100644 index 0000000..274cb4f --- /dev/null +++ b/source/en-US/WSManDsc.strings.psd1 @@ -0,0 +1,17 @@ +<# + .SYNOPSIS + The localized resource strings in English (en-US) for the + resource WSManDsc module. This file should only contain + localized strings for private functions, public command, and + classes (that are not a DSC resource). +#> + +ConvertFrom-StringData @' + + ## Find-Certificate + FindCertificate_ByThumbprintMessage = Looking for machine server certificate with thumbprint '{0}'. + FindCertificate_AlternateMessage = Looking for machine server certificate with subject '{0}' issued by '{1}' and DNS name '{2}'. + FindCertificate_Message = Looking for machine server certificate with subject '{0}' issued by '{1}'. + FindCertificate_FoundMessage = Certificate found with thumbprint '{0}' to use for HTTPS Listener. + FindCertificate_NotFoundMessage = Certificate not found. +'@ diff --git a/tests/Unit/Private/Find-Certificate.Tests.ps1 b/tests/Unit/Private/Find-Certificate.Tests.ps1 index 61c4202..70518b7 100644 --- a/tests/Unit/Private/Find-Certificate.Tests.ps1 +++ b/tests/Unit/Private/Find-Certificate.Tests.ps1 @@ -43,375 +43,378 @@ AfterAll { } Describe 'Find-Certificate' -Tag 'Private' { - # Context 'CertificateThumbprint is passed but does not exist' { - # BeforeAll { - # Mock -CommandName Get-ChildItem - # } + BeforeAll { + InModuleScope -ScriptBlock { + $script:mockCertificateThumbprint = '74FA31ADEA7FDD5333CED10910BFA6F665A1F2FC' + } + } + Context 'CertificateThumbprint is passed but does not exist' { + BeforeAll { + Mock -CommandName Get-ChildItem + } - # It 'Should not throw error' { - # InModuleScope -Parameters @{ - # mockCertificateThumbprint = $script:mockCertificateThumbprint - # } -ScriptBlock { - # Set-StrictMode -Version 1.0 + It 'Should not throw error' { + InModuleScope -ScriptBlock { + Set-StrictMode -Version 1.0 - # { $script:returnedCertificate = Find-Certificate ` - # -CertificateThumbprint $mockCertificateThumbprint ` - # -Verbose:$VerbosePreference } | Should -Not -Throw - # } - # } + $findParameters = @{ + CertificateThumbprint = $script:mockCertificateThumbprint + Verbose = $VerbosePreference + } - # It 'Should return null' { - # InModuleScope -ScriptBlock { - # Set-StrictMode -Version 1.0 + $script:returnedCertificate = Find-Certificate @findParameters - # $script:returnedCertificate | Should -BeNullOrEmpty - # } - # } + { $script:returnedCertificate } | Should -Not -Throw + } + } - # It 'Should call expected Mocks' { - # Should -Invoke ` - # -CommandName Get-ChildItem ` - # -Exactly -Times 1 ` - # -Scope Context - # } - # } + It 'Should return null' { + InModuleScope -ScriptBlock { + Set-StrictMode -Version 1.0 - # Context 'CertificateThumbprint is passed and does exist' { - # BeforeAll { - # Mock -CommandName Get-ChildItem -MockWith { - # $mockCertificateDN - # } - # } + $script:returnedCertificate | Should -BeNullOrEmpty + } - # It 'Should not throw error' { - # InModuleScope -Parameters @{ - # mockCertificateThumbprint = $script:mockCertificateThumbprint - # } -ScriptBlock { - # Set-StrictMode -Version 1.0 + Should -Invoke -CommandName Get-ChildItem -Exactly -Times 1 -Scope Context + } + } - # { $script:returnedCertificate = Find-Certificate ` - # -CertificateThumbprint $mockCertificateThumbprint ` - # -Verbose:$VerbosePreference } | Should -Not -Throw - # } - # } + Context 'CertificateThumbprint is passed and does exist' { + BeforeAll { + Mock -CommandName Get-ChildItem -MockWith { + @{ + Thumbprint = '74FA31ADEA7FDD5333CED10910BFA6F665A1F2FC' + Subject = "CN=$([System.Net.Dns]::GetHostByName($ENV:computerName).Hostname), O=Contoso Inc, S=Pennsylvania, C=US" + Issuer = 'CN=CONTOSO.COM Issuing CA, DC=CONTOSO, DC=COM' + Extensions = @{ EnhancedKeyUsages = @{ FriendlyName = 'Server Authentication' } } + DNSNameList = @{ Unicode = $([System.Net.Dns]::GetHostByName($ENV:computerName).Hostname) } + } + } + } - # It 'Should return expected certificate' { - # InModuleScope -Parameters @{ - # mockCertificateThumbprint = $script:mockCertificateThumbprint - # } -ScriptBlock { - # Set-StrictMode -Version 1.0 + It 'Should not throw error' { + InModuleScope -ScriptBlock { + Set-StrictMode -Version 1.0 - # $script:returnedCertificate.Thumbprint | Should -Be $mockCertificateThumbprint - # } - # } + $findParameters = @{ + CertificateThumbprint = $script:mockCertificateThumbprint + Verbose = $VerbosePreference + } - # It 'Should call expected Mocks' { - # Should -Invoke ` - # -CommandName Get-ChildItem ` - # -Exactly -Times 1 ` - # -Scope Context - # } - # } + $script:returnedCertificate = Find-Certificate @findParameters - # Context 'SubjectFormat is Both, Certificate does not exist, DN passed' { - # BeforeAll { - # Mock -CommandName Get-ChildItem - # } + { $script:returnedCertificate } | Should -Not -Throw + } + } - # It 'Should not throw error' { - # InModuleScope -Parameters @{ - # mockIssuer = $script:mockIssuer - # mockDN = $script:mockDN - # } -ScriptBlock { - # Set-StrictMode -Version 1.0 - - # { $script:returnedCertificate = Find-Certificate ` - # -Issuer $mockIssuer ` - # -SubjectFormat 'Both' ` - # -MatchAlternate $true ` - # -DN $mockDN ` - # -Verbose:$VerbosePreference } | Should -Not -Throw - # } - # } + It 'Should return expected certificate' { + InModuleScope -ScriptBlock { + Set-StrictMode -Version 1.0 - # It 'Should return null' { - # InModuleScope -ScriptBlock { - # Set-StrictMode -Version 1.0 + $script:returnedCertificate.Thumbprint | Should -Be $script:mockCertificateThumbprint + } - # $script:returnedCertificate | Should -BeNullOrEmpty + Should -Invoke -CommandName Get-ChildItem -Exactly -Times 1 -Scope Context + } + } + + # Context 'When SubjectFormat is ''Both''' { + # Context 'Certificate does not exist, DN passed' { + # BeforeAll { + # Mock -CommandName Get-ChildItem # } - # } - # It 'Should call expected Mocks' { - # Should -Invoke ` - # -CommandName Get-ChildItem ` - # -Exactly -Times 2 ` - # -Scope Context - # } - # } + # It 'Should not throw error' { + # InModuleScope -Parameters @{ + # mockIssuer = $script:mockIssuer + # mockDN = $script:mockDN + # } -ScriptBlock { + # Set-StrictMode -Version 1.0 + + # { $script:returnedCertificate = Find-Certificate ` + # -Issuer $mockIssuer ` + # -SubjectFormat 'Both' ` + # -MatchAlternate $true ` + # -DN $mockDN ` + # -Verbose:$VerbosePreference } | Should -Not -Throw + # } + # } + + # It 'Should return null' { + # InModuleScope -ScriptBlock { + # Set-StrictMode -Version 1.0 - # Context 'SubjectFormat is Both, Certificate with DN Exists, DN passed' { - # BeforeAll { - # Mock -CommandName Get-ChildItem -MockWith { - # $mockCertificateDN + # $script:returnedCertificate | Should -BeNullOrEmpty + # } # } - # } - # It 'Should not throw error' { - # InModuleScope -Parameters @{ - # mockIssuer = $script:mockIssuer - # mockDN = $script:mockDN - # } -ScriptBlock { - # Set-StrictMode -Version 1.0 - - # { $script:returnedCertificate = Find-Certificate ` - # -Issuer $mockIssuer ` - # -SubjectFormat 'Both' ` - # -MatchAlternate $true ` - # -DN $mockDN ` - # -Verbose:$VerbosePreference } | Should -Not -Throw + # It 'Should call expected Mocks' { + # Should -Invoke ` + # -CommandName Get-ChildItem ` + # -Exactly -Times 2 ` + # -Scope Context # } # } - # It 'Should return expected certificate' { - # InModuleScope -Parameters @{ - # mockCertificateThumbprint = $script:mockCertificateThumbprint - # } -ScriptBlock { - # Set-StrictMode -Version 1.0 + # Context 'Certificate with DN Exists, DN passed' { + # BeforeAll { + # Mock -CommandName Get-ChildItem -MockWith { + # $mockCertificateDN + # } + # } - # $script:returnedCertificate.Thumbprint | Should -Be $mockCertificateThumbprint + # It 'Should not throw error' { + # InModuleScope -Parameters @{ + # mockIssuer = $script:mockIssuer + # mockDN = $script:mockDN + # } -ScriptBlock { + # Set-StrictMode -Version 1.0 + + # { $script:returnedCertificate = Find-Certificate ` + # -Issuer $mockIssuer ` + # -SubjectFormat 'Both' ` + # -MatchAlternate $true ` + # -DN $mockDN ` + # -Verbose:$VerbosePreference } | Should -Not -Throw + # } # } - # } - # It 'Should call expected Mocks' { - # Should -Invoke ` - # -CommandName Get-ChildItem ` - # -Exactly -Times 1 ` - # -Scope Context - # } - # } + # It 'Should return expected certificate' { + # InModuleScope -Parameters @{ + # mockCertificateThumbprint = $script:mockCertificateThumbprint + # } -ScriptBlock { + # Set-StrictMode -Version 1.0 - # Context 'SubjectFormat is Both, Certificate without DN Exists, DN passed' { - # BeforeAll { - # Mock -CommandName Get-ChildItem -MockWith { - # $mockCertificate + # $script:returnedCertificate.Thumbprint | Should -Be $mockCertificateThumbprint + # } # } - # } - # It 'Should not throw error' { - # InModuleScope -Parameters @{ - # mockIssuer = $script:mockIssuer - # mockDN = $script:mockDN - # } -ScriptBlock { - # Set-StrictMode -Version 1.0 - - # { $script:returnedCertificate = Find-Certificate ` - # -Issuer $mockIssuer ` - # -SubjectFormat 'Both' ` - # -MatchAlternate $true ` - # -DN $mockDN ` - # -Verbose:$VerbosePreference } | Should -Not -Throw + # It 'Should call expected Mocks' { + # Should -Invoke ` + # -CommandName Get-ChildItem ` + # -Exactly -Times 1 ` + # -Scope Context # } # } - # It 'Should return null' { - # InModuleScope -ScriptBlock { - # Set-StrictMode -Version 1.0 + # Context 'Certificate without DN Exists, DN passed' { + # BeforeAll { + # Mock -CommandName Get-ChildItem -MockWith { + # $mockCertificate + # } + # } - # $script:returnedCertificate | Should -BeNullOrEmpty + # It 'Should not throw error' { + # InModuleScope -Parameters @{ + # mockIssuer = $script:mockIssuer + # mockDN = $script:mockDN + # } -ScriptBlock { + # Set-StrictMode -Version 1.0 + + # { $script:returnedCertificate = Find-Certificate ` + # -Issuer $mockIssuer ` + # -SubjectFormat 'Both' ` + # -MatchAlternate $true ` + # -DN $mockDN ` + # -Verbose:$VerbosePreference } | Should -Not -Throw + # } # } - # } - # It 'Should call expected Mocks' { - # Should -Invoke ` - # -CommandName Get-ChildItem ` - # -Exactly -Times 2 ` - # -Scope Context - # } - # } + # It 'Should return null' { + # InModuleScope -ScriptBlock { + # Set-StrictMode -Version 1.0 - # Context 'SubjectFormat is Both, Certificate does not exist, DN not passed' { - # BeforeAll { - # Mock -CommandName Get-ChildItem - # } + # $script:returnedCertificate | Should -BeNullOrEmpty + # } + # } - # It 'Should not throw error' { - # InModuleScope -Parameters @{ - # mockIssuer = $script:mockIssuer - # } -ScriptBlock { - # Set-StrictMode -Version 1.0 - - # { $script:returnedCertificate = Find-Certificate ` - # -Issuer $mockIssuer ` - # -SubjectFormat 'Both' ` - # -MatchAlternate $true ` - # -Verbose:$VerbosePreference } | Should -Not -Throw + # It 'Should call expected Mocks' { + # Should -Invoke ` + # -CommandName Get-ChildItem ` + # -Exactly -Times 2 ` + # -Scope Context # } # } - # It 'Should return null' { - # InModuleScope -ScriptBlock { - # Set-StrictMode -Version 1.0 + # Context 'Certificate does not exist, DN not passed' { + # BeforeAll { + # Mock -CommandName Get-ChildItem + # } - # $script:returnedCertificate | Should -BeNullOrEmpty + # It 'Should not throw error' { + # InModuleScope -Parameters @{ + # mockIssuer = $script:mockIssuer + # } -ScriptBlock { + # Set-StrictMode -Version 1.0 + + # { $script:returnedCertificate = Find-Certificate ` + # -Issuer $mockIssuer ` + # -SubjectFormat 'Both' ` + # -MatchAlternate $true ` + # -Verbose:$VerbosePreference } | Should -Not -Throw + # } # } - # } - # It 'Should call expected Mocks' { - # Should -Invoke ` - # -CommandName Get-ChildItem ` - # -Exactly -Times 2 ` - # -Scope Context - # } - # } + # It 'Should return null' { + # InModuleScope -ScriptBlock { + # Set-StrictMode -Version 1.0 - # Context 'SubjectFormat is Both, Certificate with DN Exists, DN not passed' { - # BeforeAll { - # Mock -CommandName Get-ChildItem -MockWith { - # $mockCertificateDN + # $script:returnedCertificate | Should -BeNullOrEmpty + # } # } - # } - # It 'Should not throw error' { - # InModuleScope -Parameters @{ - # mockIssuer = $script:mockIssuer - # } -ScriptBlock { - # Set-StrictMode -Version 1.0 - - # { $script:returnedCertificate = Find-Certificate ` - # -Issuer $mockIssuer ` - # -SubjectFormat 'Both' ` - # -MatchAlternate $true ` - # -Verbose:$VerbosePreference } | Should -Not -Throw + # It 'Should call expected Mocks' { + # Should -Invoke ` + # -CommandName Get-ChildItem ` + # -Exactly -Times 2 ` + # -Scope Context # } # } - # It 'Should return null' { - # InModuleScope -ScriptBlock { - # Set-StrictMode -Version 1.0 + # Context 'Certificate with DN Exists, DN not passed' { + # BeforeAll { + # Mock -CommandName Get-ChildItem -MockWith { + # $mockCertificateDN + # } + # } - # $script:returnedCertificate | Should -BeNullOrEmpty + # It 'Should not throw error' { + # InModuleScope -Parameters @{ + # mockIssuer = $script:mockIssuer + # } -ScriptBlock { + # Set-StrictMode -Version 1.0 + + # { $script:returnedCertificate = Find-Certificate ` + # -Issuer $mockIssuer ` + # -SubjectFormat 'Both' ` + # -MatchAlternate $true ` + # -Verbose:$VerbosePreference } | Should -Not -Throw + # } # } - # } - # It 'Should call expected Mocks' { - # Should -Invoke ` - # -CommandName Get-ChildItem ` - # -Exactly -Times 2 ` - # -Scope Context - # } - # } + # It 'Should return null' { + # InModuleScope -ScriptBlock { + # Set-StrictMode -Version 1.0 - # Context 'SubjectFormat is Both, Certificate without DN Exists, DN not passed' { - # BeforeAll { - # Mock -CommandName Get-ChildItem -MockWith { - # $mockCertificate + # $script:returnedCertificate | Should -BeNullOrEmpty + # } # } - # } - # It 'Should not throw error' { - # InModuleScope -Parameters @{ - # mockIssuer = $script:mockIssuer - # } -ScriptBlock { - # Set-StrictMode -Version 1.0 - - # { $script:returnedCertificate = Find-Certificate ` - # -Issuer $mockIssuer ` - # -SubjectFormat 'Both' ` - # -MatchAlternate $true ` - # -Verbose:$VerbosePreference } | Should -Not -Throw + # It 'Should call expected Mocks' { + # Should -Invoke ` + # -CommandName Get-ChildItem ` + # -Exactly -Times 2 ` + # -Scope Context # } # } - # It 'Should return expected certificate' { - # InModuleScope -Parameters @{ - # mockCertificateThumbprint = $script:mockCertificateThumbprint - # } -ScriptBlock { - # Set-StrictMode -Version 1.0 + # Context 'Certificate without DN Exists, DN not passed' { + # BeforeAll { + # Mock -CommandName Get-ChildItem -MockWith { + # $mockCertificate + # } + # } - # $script:returnedCertificate.Thumbprint | Should -Be $mockCertificateThumbprint + # It 'Should not throw error' { + # InModuleScope -Parameters @{ + # mockIssuer = $script:mockIssuer + # } -ScriptBlock { + # Set-StrictMode -Version 1.0 + + # { $script:returnedCertificate = Find-Certificate ` + # -Issuer $mockIssuer ` + # -SubjectFormat 'Both' ` + # -MatchAlternate $true ` + # -Verbose:$VerbosePreference } | Should -Not -Throw + # } # } - # } - # It 'Should call expected Mocks' { - # Should -Invoke ` - # -CommandName Get-ChildItem ` - # -Exactly -Times 1 ` - # -Scope Context - # } - # } + # It 'Should return expected certificate' { + # InModuleScope -Parameters @{ + # mockCertificateThumbprint = $script:mockCertificateThumbprint + # } -ScriptBlock { + # Set-StrictMode -Version 1.0 - # Context 'SubjectFormat is Both, Certificate does not exist, DN not passed, MatchAlternate is false' { - # BeforeAll { - # Mock -CommandName Get-ChildItem - # } + # $script:returnedCertificate.Thumbprint | Should -Be $mockCertificateThumbprint + # } + # } - # It 'Should not throw error' { - # InModuleScope -Parameters @{ - # mockIssuer = $script:mockIssuer - # } -ScriptBlock { - # Set-StrictMode -Version 1.0 - - # { $script:returnedCertificate = Find-Certificate ` - # -Issuer $mockIssuer ` - # -SubjectFormat 'Both' ` - # -MatchAlternate $false ` - # -Verbose:$VerbosePreference } | Should -Not -Throw + # It 'Should call expected Mocks' { + # Should -Invoke ` + # -CommandName Get-ChildItem ` + # -Exactly -Times 1 ` + # -Scope Context # } # } - # It 'Should return null' { - # InModuleScope -ScriptBlock { - # Set-StrictMode -Version 1.0 + # Context 'Certificate does not exist, DN not passed, MatchAlternate is false' { + # BeforeAll { + # Mock -CommandName Get-ChildItem + # } - # $script:returnedCertificate | Should -BeNullOrEmpty + # It 'Should not throw error' { + # InModuleScope -Parameters @{ + # mockIssuer = $script:mockIssuer + # } -ScriptBlock { + # Set-StrictMode -Version 1.0 + + # $findParameters = @{ + # Issuer = $mockIssuer + # SubjectFormat = 'Both' + # MatchAlternate = $false + # Verbose = $VerbosePreference + # } + + # { $script:returnedCertificate = Find-Certificate @findParameters } | Should -Not -Throw + # } # } - # } - # It 'Should call expected Mocks' { - # Should -Invoke ` - # -CommandName Get-ChildItem ` - # -Exactly -Times 2 ` - # -Scope Context - # } - # } + # It 'Should return null' { + # InModuleScope -ScriptBlock { + # Set-StrictMode -Version 1.0 - # Context 'SubjectFormat is Both, Certificate without DN Exists, DN not passed, MatchAlternate is false' { - # BeforeAll { - # Mock -CommandName Get-ChildItem -MockWith { - # $mockCertificate - # } - # } + # $script:returnedCertificate | Should -BeNullOrEmpty + # } - # It 'Should not throw error' { - # InModuleScope -Parameters @{ - # mockIssuer = $script:mockIssuer - # } -ScriptBlock { - # Set-StrictMode -Version 1.0 - - # { $script:returnedCertificate = Find-Certificate ` - # -Issuer $mockIssuer ` - # -SubjectFormat 'Both' ` - # -MatchAlternate $false ` - # -Verbose:$VerbosePreference } | Should -Not -Throw + # Should -Invoke -CommandName Get-ChildItem -Exactly -Times 2 -Scope It # } # } - # It 'Should return expected certificate' { - # InModuleScope -Parameters @{ - # mockCertificateThumbprint = $script:mockCertificateThumbprint - # } -ScriptBlock { - # Set-StrictMode -Version 1.0 + # Context 'Certificate without DN Exists, DN not passed, MatchAlternate is false' { + # BeforeAll { + # Mock -CommandName Get-ChildItem -MockWith { + # $mockCertificate + # } + # } - # $script:returnedCertificate.Thumbprint | Should -Be $mockCertificateThumbprint + # It 'Should not throw error' { + # InModuleScope -Parameters @{ + # mockIssuer = $script:mockIssuer + # } -ScriptBlock { + # Set-StrictMode -Version 1.0 + + # $findParameters = @{ + # Issuer = $mockIssuer + # SubjectFormat = 'Both' + # MatchAlternate = $false + # Verbose = $VerbosePreference + # } + + # { $script:returnedCertificate = Find-Certificate @findParameters } | Should -Not -Throw + # } # } - # } - # It 'Should call expected Mocks' { - # Should -Invoke ` - # -CommandName Get-ChildItem ` - # -Exactly -Times 1 ` - # -Scope Context + # It 'Should return expected certificate' { + # InModuleScope -Parameters @{ + # mockCertificateThumbprint = $script:mockCertificateThumbprint + # } -ScriptBlock { + # Set-StrictMode -Version 1.0 + + # $script:returnedCertificate.Thumbprint | Should -Be $mockCertificateThumbprint + # } + + # Should -Invoke -CommandName Get-ChildItem -Exactly -Times 1 -Scope It + # } # } # } } From b41d127ed6b2c08b577eae78fad762a220fe5b66 Mon Sep 17 00:00:00 2001 From: Daniel Hughes Date: Thu, 19 Sep 2024 13:42:31 +0100 Subject: [PATCH 018/134] Move away from ValidateSet and update Modify --- source/Classes/020.WSManListener.ps1 | 190 ++++++++++++++++++--- source/Enum/005.WSManSubjectFormat.ps1 | 11 ++ source/Enum/005.WSManTransport.ps1 | 10 ++ source/en-US/WSManListener.strings.psd1 | 3 + tests/Unit/Classes/WSManListener.Tests.ps1 | 11 +- 5 files changed, 195 insertions(+), 30 deletions(-) create mode 100644 source/Enum/005.WSManSubjectFormat.ps1 create mode 100644 source/Enum/005.WSManTransport.ps1 diff --git a/source/Classes/020.WSManListener.ps1 b/source/Classes/020.WSManListener.ps1 index 6e39ec2..38786a0 100644 --- a/source/Classes/020.WSManListener.ps1 +++ b/source/Classes/020.WSManListener.ps1 @@ -51,11 +51,12 @@ class WSManListener : ResourceBase { [DscProperty(Key)] - [ValidateSet('HTTP', 'HTTPS')] - [System.String] + #[ValidateSet('HTTP', 'HTTPS')] + #[System.String] + [WSManTransport] $Transport - [DscProperty(Mandatory = $true)] + [DscProperty(Mandatory)] [Ensure] $Ensure @@ -73,9 +74,10 @@ class WSManListener : ResourceBase $Issuer [DscProperty()] - [ValidateSet('Both', 'FQDNOnly', 'NameOnly')] - [System.String] - $SubjectFormat = 'Both' + #[ValidateSet('Both', 'FQDNOnly', 'NameOnly')] + #[System.String] + [WSManSubjectFormat] + $SubjectFormat = [WSManSubjectFormat]::Both [DscProperty()] [Nullable[System.Boolean]] @@ -152,17 +154,16 @@ class WSManListener : ResourceBase } $state = @{ - Transport = $properties.Transport + Transport = $getCurrentStateResult.Transport Port = [System.UInt16] $getCurrentStateResult.Port Address = $getCurrentStateResult.Address - Enabled = $getCurrentStateResult.Enabled - URLPrefix = $getCurrentStateResult.URLPrefix + Issuer = $currentCertificate.Issuer - SubjectFormat = $properties.SubjectFormat - MatchAlternate = $null - BaseDN = $null CertificateThumbprint = $getCurrentStateResult.CertificateThumbprint Hostname = $getCurrentStateResult.HostName + + Enabled = $getCurrentStateResult.Enabled + URLPrefix = $getCurrentStateResult.URLPrefix } return $state @@ -178,22 +179,161 @@ class WSManListener : ResourceBase Base method Set() call this method with the properties that should be enforced and that are not in desired state. #> - [void] Modify([System.Collections.Hashtable] $properties) + hidden [void] Modify([System.Collections.Hashtable] $properties) { - # <# - # If the property 'EnablePollutionProtection' was present and not in desired state, - # then the property name must be change for the cmdlet Set-DnsServerCache. In the - # cmdlet Get-DnsServerCache the property name is 'EnablePollutionProtection', but - # in the cmdlet Set-DnsServerCache the parameter is 'PollutionProtection'. - # #> - # if ($properties.ContainsKey('EnablePollutionProtection')) - # { - # $properties['PollutionProtection'] = $properties.EnablePollutionProtection - # $properties.Remove('EnablePollutionProtection') - # } + $remove = $false + $create = $false + + if ($properties.ContainsKey('Ensure') -and $properties.Ensure -eq [Ensure]::Absent -and $this.Ensure -eq [Ensure]::Absent) + { + # Ensure was no in desired state so the resource should be removed + $remove = $true - # Set-DnsServerCache @properties + } + 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 + $create = $true + } + else + { + # Resource exists but one or more properties are not in the desired state + $remove = $true + $create = $true + } + + if ($remove) + { + Write-Verbose -Message ($this.localizedData.ListenerExistsRemoveMessage -f $properties.Transport, $properties.Port) + + Remove-WSManInstance -ResourceURI 'winrm/config/Listener' -SelectorSet @{ + Transport = $properties.Transport + Address = $properties.Address + } + } + + if ($create) + { + $selectorSet = @{ + Transport = $properties.Transport + Address = $properties.Address + } + $valueSet = @{ + Port = $properties.Port + } + + switch ($properties.Transport) + { + HTTPS + { + $findCertificateParams = Get-DscProperty -Attribute @('Optional') -ExcludeName @('Port', 'Address') + + $certificate = Find-Certificate @findCertificateParams + [System.String] $thumbprint = $certificate.thumbprint + + if ($thumbprint) + { + $valueSet.CertificateThumbprint = $thumbprint + + if ([System.String]::IsNullOrEmpty($properties.Hostname)) + { + $valueSet.HostName = [System.Net.Dns]::GetHostByName($env:COMPUTERNAME).Hostname + } + else + { + $valueSet.HostName = $properties.HostName + } + } + else + { + # TODO: Extract this to assert or into a parameter set + # A certificate could not be found to use for the HTTPS listener + New-InvalidArgumentException ` + -Message ($this.localizedData.ListenerCreateFailNoCertError -f $properties.Transport, $properties.Port) ` + -Argument 'Issuer' + } # if + } + Default + { + } + } + + Write-Verbose -Message ($this.localizedData.CreatingListenerMessage -f $properties.Transport, $properties.Port) + + New-WSManInstance -ResourceURI 'winrm/config/Listener' -SelectorSet @selectorSet -ValueSet @valueSet -ErrorAction Stop + + #End + + # if ($Transport -eq 'HTTPS') + # { + # # Find the certificate to use for the HTTPS Listener + # $null = $PSBoundParameters.Remove('Transport') + # $null = $PSBoundParameters.Remove('Ensure') + # $null = $PSBoundParameters.Remove('Port') + # $null = $PSBoundParameters.Remove('Address') + + # $certificate = Find-Certificate @PSBoundParameters + # [System.String] $thumbprint = $certificate.thumbprint + + # if ($thumbprint) + # { + # # A certificate was found, so use it to enable the HTTPS WinRM listener + # Write-Verbose -Message ( @( + # "$($MyInvocation.MyCommand): " + # $($script:localizedData.CreatingListenerMessage) ` + # -f $Transport, $Port + # ) -join '' ) + + # if ([System.String]::IsNullOrEmpty($Hostname)) + # { + # $Hostname = [System.Net.Dns]::GetHostByName($ENV:computerName).Hostname + # } + + # New-WSManInstance ` + # -ResourceURI 'winrm/config/Listener' ` + # -SelectorSet @{ + # Address = $Address + # Transport = $Transport + # } ` + # -ValueSet @{ + # Hostname = $Hostname + # CertificateThumbprint = $thumbprint + # Port = $Port + # } ` + # -ErrorAction Stop + # } + # else + # { + # # A certificate could not be found to use for the HTTPS listener + # New-InvalidArgumentException ` + # -Message ($script:localizedData.ListenerCreateFailNoCertError -f ` + # $Transport, $Port) ` + # -Argument 'Issuer' + # } # if + # } + # else + # { + # # Create a plain HTTP listener + # Write-Verbose -Message ( @( + # "$($MyInvocation.MyCommand): " + # $($script:localizedData.CreatingListenerMessage) ` + # -f $Transport, $Port + # ) -join '' ) + + # New-WSManInstance ` + # -ResourceURI 'winrm/config/Listener' ` + # -SelectorSet @{ + # Address = $Address + # Transport = $Transport + # } ` + # -ValueSet @{ + # Port = $Port + # } ` + # -ErrorAction Stop + # } + # } + } } [System.Boolean] Test() diff --git a/source/Enum/005.WSManSubjectFormat.ps1 b/source/Enum/005.WSManSubjectFormat.ps1 new file mode 100644 index 0000000..ecf7c15 --- /dev/null +++ b/source/Enum/005.WSManSubjectFormat.ps1 @@ -0,0 +1,11 @@ +<# + .SYNOPSIS + The possible states for the DSC resource parameter WSManSubjectFormat. +#> + +enum WSManSubjectFormat +{ + Both + FQDNOnly + NameOnly +} diff --git a/source/Enum/005.WSManTransport.ps1 b/source/Enum/005.WSManTransport.ps1 new file mode 100644 index 0000000..17a828f --- /dev/null +++ b/source/Enum/005.WSManTransport.ps1 @@ -0,0 +1,10 @@ +<# + .SYNOPSIS + The possible states for the DSC resource parameter WSManTransport. +#> + +enum WSManTransport +{ + HTTP + HTTPS +} diff --git a/source/en-US/WSManListener.strings.psd1 b/source/en-US/WSManListener.strings.psd1 index f187454..3120c46 100644 --- a/source/en-US/WSManListener.strings.psd1 +++ b/source/en-US/WSManListener.strings.psd1 @@ -11,4 +11,7 @@ ConvertFrom-StringData @' # None ## Strings directly used by the derived class WSManListener. + ListenerExistsRemoveMessage = Removing {0} Listener on port {1} (WSML0001). + ListenerCreateFailNoCertError = Failed to create {0} Listener on port {1} because an applicable certificate could not be found (WSM0002). + CreatingListenerMessage = Creating {0} Listener on port {1} (WSML0003). '@ diff --git a/tests/Unit/Classes/WSManListener.Tests.ps1 b/tests/Unit/Classes/WSManListener.Tests.ps1 index dab01ea..ec0c616 100644 --- a/tests/Unit/Classes/WSManListener.Tests.ps1 +++ b/tests/Unit/Classes/WSManListener.Tests.ps1 @@ -105,7 +105,7 @@ Describe 'WSManListener\Get()' -Tag 'Get' { $script:mockWSManListenerInstance | Add-Member -Force -MemberType 'ScriptMethod' -Name 'GetCurrentState' -Value { return [System.Collections.Hashtable] @{ - Transport = 'HTTP' + Transport = [WSManTransport]::HTTP Port = 5985 Address = '*' Enabled = 'true' @@ -134,6 +134,7 @@ Describe 'WSManListener\Get()' -Tag 'Get' { $currentState.Port | Should -Be 5985 $currentState.Port | Should -BeOfType System.UInt16 $currentState.Address | Should -Be '*' + $currentState.Enabled | Should -Be $true $currentState.URLPrefix | Should -Be 'wsman' @@ -170,7 +171,7 @@ Describe 'WSManListener\Get()' -Tag 'Get' { $script:mockWSManListenerInstance | Add-Member -Force -MemberType 'ScriptMethod' -Name 'GetCurrentState' -Value { return [System.Collections.Hashtable] @{ - Transport = 'HTTPS' + Transport = [WSManTransport]::HTTPS Port = 5986 Address = '*' Enabled = 'true' @@ -224,7 +225,7 @@ Describe 'WSManListener\Get()' -Tag 'Get' { $script:mockWSManListenerInstance = [WSManListener] @{ Transport = 'HTTPS' - Port = 5986 + Port = 5986 Ensure = 'Present' } @@ -238,7 +239,7 @@ Describe 'WSManListener\Get()' -Tag 'Get' { $script:mockWSManListenerInstance | Add-Member -Force -MemberType 'ScriptMethod' -Name 'GetCurrentState' -Value { return [System.Collections.Hashtable] @{ - Transport = 'HTTPS' + Transport = [WSManTransport]::HTTPS Port = 6000 Address = '*' Enabled = 'true' @@ -263,7 +264,7 @@ Describe 'WSManListener\Get()' -Tag 'Get' { $currentState = $script:mockWSManListenerInstance.Get() - $currentState.Transport | Should -Be 'HTTPS' + $currentState.Transport | Should -Be HTTPS $currentState.Port | Should -Be 6000 $currentState.Port | Should -BeOfType System.UInt16 From e3cc85db980d9641bfdf9753016846e5135369e1 Mon Sep 17 00:00:00 2001 From: Daniel Hughes Date: Thu, 19 Sep 2024 15:22:07 +0100 Subject: [PATCH 019/134] Cast enum correctly --- source/Classes/020.WSManListener.ps1 | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/source/Classes/020.WSManListener.ps1 b/source/Classes/020.WSManListener.ps1 index 38786a0..9ddf822 100644 --- a/source/Classes/020.WSManListener.ps1 +++ b/source/Classes/020.WSManListener.ps1 @@ -135,13 +135,13 @@ class WSManListener : ResourceBase [System.Collections.Hashtable] GetCurrentState([System.Collections.Hashtable] $properties) { $getParameters = @{ - Transport = $properties.Transport + Transport = [WSManTransport].GetEnumName($properties.Transport) } # Get the port if it's not provided if (-not $properties.Port) { - $this.Port = Get-DefaultPort -Transport $properties.Transport + $this.Port = Get-DefaultPort @getParameters } $getCurrentStateResult = Get-Listener @getParameters @@ -154,7 +154,7 @@ class WSManListener : ResourceBase } $state = @{ - Transport = $getCurrentStateResult.Transport + Transport = [WSManTransport] $getCurrentStateResult.Transport Port = [System.UInt16] $getCurrentStateResult.Port Address = $getCurrentStateResult.Address @@ -185,6 +185,11 @@ class WSManListener : ResourceBase $remove = $false $create = $false + $selectorSet = @{ + Transport = [WSManTransport].GetEnumName($properties.Transport) + Address = $properties.Address + } + if ($properties.ContainsKey('Ensure') -and $properties.Ensure -eq [Ensure]::Absent -and $this.Ensure -eq [Ensure]::Absent) { # Ensure was no in desired state so the resource should be removed @@ -207,18 +212,11 @@ class WSManListener : ResourceBase { Write-Verbose -Message ($this.localizedData.ListenerExistsRemoveMessage -f $properties.Transport, $properties.Port) - Remove-WSManInstance -ResourceURI 'winrm/config/Listener' -SelectorSet @{ - Transport = $properties.Transport - Address = $properties.Address - } + Remove-WSManInstance -ResourceURI 'winrm/config/Listener' -SelectorSet @selectorSet } if ($create) { - $selectorSet = @{ - Transport = $properties.Transport - Address = $properties.Address - } $valueSet = @{ Port = $properties.Port } From 200e3a033ba2428e585b8c693320ae924910e241 Mon Sep 17 00:00:00 2001 From: Daniel Hughes Date: Fri, 20 Sep 2024 17:11:29 +0100 Subject: [PATCH 020/134] Move back to enum and return empty state as required --- source/Classes/020.WSManListener.ps1 | 179 ++++++----------- tests/Unit/Classes/WSManListener.Tests.ps1 | 216 ++++++++++++++++++--- 2 files changed, 247 insertions(+), 148 deletions(-) diff --git a/source/Classes/020.WSManListener.ps1 b/source/Classes/020.WSManListener.ps1 index 9ddf822..e4e39bf 100644 --- a/source/Classes/020.WSManListener.ps1 +++ b/source/Classes/020.WSManListener.ps1 @@ -51,8 +51,6 @@ class WSManListener : ResourceBase { [DscProperty(Key)] - #[ValidateSet('HTTP', 'HTTPS')] - #[System.String] [WSManTransport] $Transport @@ -67,17 +65,15 @@ class WSManListener : ResourceBase [DscProperty()] [System.String] - $Address = '*' + $Address [DscProperty()] [System.String] $Issuer [DscProperty()] - #[ValidateSet('Both', 'FQDNOnly', 'NameOnly')] - #[System.String] [WSManSubjectFormat] - $SubjectFormat = [WSManSubjectFormat]::Both + $SubjectFormat [DscProperty()] [Nullable[System.Boolean]] @@ -135,37 +131,49 @@ class WSManListener : ResourceBase [System.Collections.Hashtable] GetCurrentState([System.Collections.Hashtable] $properties) { $getParameters = @{ - Transport = [WSManTransport].GetEnumName($properties.Transport) + Transport = $properties.Transport } - # Get the port if it's not provided - if (-not $properties.Port) + # Get the port if it's not provided and resource should exist + if (-not $this.Port -and $this.Ensure -eq [Ensure]::Present) { $this.Port = Get-DefaultPort @getParameters } - $getCurrentStateResult = Get-Listener @getParameters + # Get the Address if it's not provided and resource should exist + if (-not $this.Address -and $this.Ensure -eq [Ensure]::Present) + { + $this.Address = '*' + } - $currentCertificate = '' - if ($getCurrentStateResult.CertificateThumbprint) + $getCurrentStateResult = Get-Listener @getParameters + + if ($getCurrentStateResult) { - $currentCertificate = Find-Certificate -CertificateThumbprint $getCurrentStateResult.CertificateThumbprint - } + $state = @{ + Transport = [WSManTransport] $getCurrentStateResult.Transport + Port = [System.UInt16] $getCurrentStateResult.Port + Address = $getCurrentStateResult.Address - $state = @{ - Transport = [WSManTransport] $getCurrentStateResult.Transport - Port = [System.UInt16] $getCurrentStateResult.Port - Address = $getCurrentStateResult.Address + CertificateThumbprint = $getCurrentStateResult.CertificateThumbprint + Hostname = $getCurrentStateResult.Hostname - Issuer = $currentCertificate.Issuer - CertificateThumbprint = $getCurrentStateResult.CertificateThumbprint - Hostname = $getCurrentStateResult.HostName + Enabled = $getCurrentStateResult.Enabled + URLPrefix = $getCurrentStateResult.URLPrefix + } - Enabled = $getCurrentStateResult.Enabled - URLPrefix = $getCurrentStateResult.URLPrefix + if ($getCurrentStateResult.CertificateThumbprint) + { + $state.Issuer = (Find-Certificate -CertificateThumbprint $getCurrentStateResult.CertificateThumbprint).Issuer + } + } + else + { + $state = @{} } + return $state } @@ -186,13 +194,17 @@ class WSManListener : ResourceBase $create = $false $selectorSet = @{ - Transport = [WSManTransport].GetEnumName($properties.Transport) + Transport = $properties.Transport Address = $properties.Address } + Write-Verbose ('$properties.ContainsKey(''Ensure'') = {0}' -f $properties.ContainsKey('Ensure')) + Write-Verbose ('$properties.Ensure = {0}' -f $properties.Ensure) + Write-Verbose ('$this.Ensure = {0}' -f $this.Ensure) + if ($properties.ContainsKey('Ensure') -and $properties.Ensure -eq [Ensure]::Absent -and $this.Ensure -eq [Ensure]::Absent) { - # Ensure was no in desired state so the resource should be removed + # Ensure was not in desired state so the resource should be removed $remove = $true } @@ -221,116 +233,41 @@ class WSManListener : ResourceBase Port = $properties.Port } - switch ($properties.Transport) + + if ($properties.Transport -eq [WSManTransport]::HTTPS) { - HTTPS - { - $findCertificateParams = Get-DscProperty -Attribute @('Optional') -ExcludeName @('Port', 'Address') + $findCertificateParams = Get-DscProperty -Attribute @('Optional') -ExcludeName @('Port', 'Address') - $certificate = Find-Certificate @findCertificateParams - [System.String] $thumbprint = $certificate.thumbprint + $certificate = Find-Certificate @findCertificateParams + [System.String] $thumbprint = $certificate.thumbprint - if ($thumbprint) + if ($thumbprint) + { + $valueSet.CertificateThumbprint = $thumbprint + + if ([System.String]::IsNullOrEmpty($properties.Hostname)) { - $valueSet.CertificateThumbprint = $thumbprint - - if ([System.String]::IsNullOrEmpty($properties.Hostname)) - { - $valueSet.HostName = [System.Net.Dns]::GetHostByName($env:COMPUTERNAME).Hostname - } - else - { - $valueSet.HostName = $properties.HostName - } + $valueSet.HostName = [System.Net.Dns]::GetHostByName($env:COMPUTERNAME).Hostname } else { - # TODO: Extract this to assert or into a parameter set - # A certificate could not be found to use for the HTTPS listener - New-InvalidArgumentException ` - -Message ($this.localizedData.ListenerCreateFailNoCertError -f $properties.Transport, $properties.Port) ` - -Argument 'Issuer' - } # if + $valueSet.HostName = $properties.HostName + } } - Default + else { - } + # TODO: Extract this to assert or into a parameter set + # A certificate could not be found to use for the HTTPS listener + New-InvalidArgumentException ` + -Message ($this.localizedData.ListenerCreateFailNoCertError -f $properties.Transport, $properties.Port) ` + -Argument 'Issuer' + } # if } + Write-Verbose -Message ($this.localizedData.CreatingListenerMessage -f $properties.Transport, $properties.Port) New-WSManInstance -ResourceURI 'winrm/config/Listener' -SelectorSet @selectorSet -ValueSet @valueSet -ErrorAction Stop - - #End - - # if ($Transport -eq 'HTTPS') - # { - # # Find the certificate to use for the HTTPS Listener - # $null = $PSBoundParameters.Remove('Transport') - # $null = $PSBoundParameters.Remove('Ensure') - # $null = $PSBoundParameters.Remove('Port') - # $null = $PSBoundParameters.Remove('Address') - - # $certificate = Find-Certificate @PSBoundParameters - # [System.String] $thumbprint = $certificate.thumbprint - - # if ($thumbprint) - # { - # # A certificate was found, so use it to enable the HTTPS WinRM listener - # Write-Verbose -Message ( @( - # "$($MyInvocation.MyCommand): " - # $($script:localizedData.CreatingListenerMessage) ` - # -f $Transport, $Port - # ) -join '' ) - - # if ([System.String]::IsNullOrEmpty($Hostname)) - # { - # $Hostname = [System.Net.Dns]::GetHostByName($ENV:computerName).Hostname - # } - - # New-WSManInstance ` - # -ResourceURI 'winrm/config/Listener' ` - # -SelectorSet @{ - # Address = $Address - # Transport = $Transport - # } ` - # -ValueSet @{ - # Hostname = $Hostname - # CertificateThumbprint = $thumbprint - # Port = $Port - # } ` - # -ErrorAction Stop - # } - # else - # { - # # A certificate could not be found to use for the HTTPS listener - # New-InvalidArgumentException ` - # -Message ($script:localizedData.ListenerCreateFailNoCertError -f ` - # $Transport, $Port) ` - # -Argument 'Issuer' - # } # if - # } - # else - # { - # # Create a plain HTTP listener - # Write-Verbose -Message ( @( - # "$($MyInvocation.MyCommand): " - # $($script:localizedData.CreatingListenerMessage) ` - # -f $Transport, $Port - # ) -join '' ) - - # New-WSManInstance ` - # -ResourceURI 'winrm/config/Listener' ` - # -SelectorSet @{ - # Address = $Address - # Transport = $Transport - # } ` - # -ValueSet @{ - # Port = $Port - # } ` - # -ErrorAction Stop - # } - # } } } diff --git a/tests/Unit/Classes/WSManListener.Tests.ps1 b/tests/Unit/Classes/WSManListener.Tests.ps1 index ec0c616..8621af6 100644 --- a/tests/Unit/Classes/WSManListener.Tests.ps1 +++ b/tests/Unit/Classes/WSManListener.Tests.ps1 @@ -105,17 +105,11 @@ Describe 'WSManListener\Get()' -Tag 'Get' { $script:mockWSManListenerInstance | Add-Member -Force -MemberType 'ScriptMethod' -Name 'GetCurrentState' -Value { return [System.Collections.Hashtable] @{ - Transport = [WSManTransport]::HTTP - Port = 5985 + Transport = [WSManTransport] 'HTTP' + Port = [System.UInt16] 5985 Address = '*' Enabled = 'true' URLPrefix = 'wsman' - Issuer = $null - SubjectFormat = 'Both' - MatchAlternate = $null - BaseDN = $null - CertificateThumbprint = $null - Hostname = $null } } -PassThru | Add-Member -Force -MemberType 'ScriptMethod' -Name 'AssertProperties' -Value { @@ -135,7 +129,7 @@ Describe 'WSManListener\Get()' -Tag 'Get' { $currentState.Port | Should -BeOfType System.UInt16 $currentState.Address | Should -Be '*' - $currentState.Enabled | Should -Be $true + $currentState.Enabled | Should -BeTrue $currentState.URLPrefix | Should -Be 'wsman' $currentState.Issuer | Should -BeNullOrEmpty @@ -171,17 +165,11 @@ Describe 'WSManListener\Get()' -Tag 'Get' { $script:mockWSManListenerInstance | Add-Member -Force -MemberType 'ScriptMethod' -Name 'GetCurrentState' -Value { return [System.Collections.Hashtable] @{ - Transport = [WSManTransport]::HTTPS - Port = 5986 + Transport = [WSManTransport] 'HTTPS' + Port = [System.UInt16] 5986 Address = '*' Enabled = 'true' URLPrefix = 'wsman' - Issuer = $null - SubjectFormat = 'Both' - MatchAlternate = $null - BaseDN = $null - CertificateThumbprint = $null - Hostname = $null } } -PassThru | Add-Member -Force -MemberType 'ScriptMethod' -Name 'AssertProperties' -Value { @@ -200,7 +188,7 @@ Describe 'WSManListener\Get()' -Tag 'Get' { $currentState.Port | Should -Be 5986 $currentState.Port | Should -BeOfType System.UInt16 $currentState.Address | Should -Be '*' - $currentState.Enabled | Should -Be $true + $currentState.Enabled | Should -BeTrue $currentState.URLPrefix | Should -Be 'wsman' $currentState.Issuer | Should -BeNullOrEmpty @@ -215,6 +203,61 @@ Describe 'WSManListener\Get()' -Tag 'Get' { } } } + + Context 'When no listener should exist' { + BeforeAll { + InModuleScope -ScriptBlock { + Set-StrictMode -Version 1.0 + + $script:mockWSManListenerInstance = [WSManListener] @{ + Transport = 'HTTP' + Ensure = 'Absent' + } + + <# + This mocks the method GetCurrentState(). + + Method Get() will call the base method Get() which will + call back to the derived class method GetCurrentState() + to get the result to return from the derived method Get(). + #> + $script:mockWSManListenerInstance | + Add-Member -Force -MemberType 'ScriptMethod' -Name 'GetCurrentState' -Value { + return [System.Collections.Hashtable] @{} + } -PassThru | + Add-Member -Force -MemberType 'ScriptMethod' -Name 'AssertProperties' -Value { + return + } + } + } + + It 'Should return the correct values' { + InModuleScope -ScriptBlock { + Set-StrictMode -Version 1.0 + + $currentState = $script:mockWSManListenerInstance.Get() + + $currentState.Transport | Should -Be 'HTTP' + $currentState.Port | Should -BeNullOrEmpty + $currentState.Address | Should -BeNullOrEmpty + + $currentState.Enabled | Should -BeFalse + $currentState.URLPrefix | Should -BeNullOrEmpty + + $currentState.Issuer | Should -BeNullOrEmpty + $currentState.SubjectFormat | Should -Be 'Both' + $currentState.MatchAlternate | Should -BeNullOrEmpty + $currentState.BaseDN | Should -BeNullOrEmpty + $currentState.CertificateThumbprint | Should -BeNullOrEmpty + $currentState.Hostname | Should -BeNullOrEmpty + + $currentState.Ensure | Should -Be 'Absent' + $currentState.Reasons | Should -HaveCount 1 + $currentState.Reasons[0].Code | Should -Be 'WSManListener:WSManListener:Transport' + $currentState.Reasons[0].Phrase | Should -Be 'The property Transport should be "HTTP", but was null' + } + } + } } Context 'When the system is not in the desired state' { @@ -239,17 +282,11 @@ Describe 'WSManListener\Get()' -Tag 'Get' { $script:mockWSManListenerInstance | Add-Member -Force -MemberType 'ScriptMethod' -Name 'GetCurrentState' -Value { return [System.Collections.Hashtable] @{ - Transport = [WSManTransport]::HTTPS - Port = 6000 + Transport = [WSManTransport] 'HTTPS' + Port = [System.UInt16] 6000 Address = '*' Enabled = 'true' URLPrefix = 'wsman' - Issuer = $null - SubjectFormat = 'Both' - MatchAlternate = $null - BaseDN = $null - CertificateThumbprint = $null - Hostname = $null } } -PassThru | Add-Member -Force -MemberType 'ScriptMethod' -Name 'AssertProperties' -Value { @@ -264,7 +301,7 @@ Describe 'WSManListener\Get()' -Tag 'Get' { $currentState = $script:mockWSManListenerInstance.Get() - $currentState.Transport | Should -Be HTTPS + $currentState.Transport | Should -Be 'HTTPS' $currentState.Port | Should -Be 6000 $currentState.Port | Should -BeOfType System.UInt16 @@ -288,5 +325,130 @@ Describe 'WSManListener\Get()' -Tag 'Get' { } } } + + Context 'When the listener exists' { + BeforeAll { + InModuleScope -ScriptBlock { + Set-StrictMode -Version 1.0 + + $script:mockWSManListenerInstance = [WSManListener] @{ + Transport = 'HTTPS' + Ensure = 'Present' + } + + <# + This mocks the method GetCurrentState(). + + Method Get() will call the base method Get() which will + call back to the derived class method GetCurrentState() + to get the result to return from the derived method Get(). + #> + $script:mockWSManListenerInstance | + Add-Member -Force -MemberType 'ScriptMethod' -Name 'GetCurrentState' -Value { + return [System.Collections.Hashtable] @{} + } -PassThru | + Add-Member -Force -MemberType 'ScriptMethod' -Name 'AssertProperties' -Value { + return + } + } + } + + It 'Should return the correct values' { + InModuleScope -ScriptBlock { + Set-StrictMode -Version 1.0 + + $currentState = $script:mockWSManListenerInstance.Get() + + $currentState.Transport | Should -Be 'HTTPS' + $currentState.Port | Should -BeNullOrEmpty + + $currentState.Address | Should -BeNullOrEmpty + $currentState.Enabled | Should -BeFalse + $currentState.URLPrefix | Should -BeNullOrEmpty + + $currentState.Issuer | Should -BeNullOrEmpty + $currentState.SubjectFormat | Should -Be 'Both' + $currentState.MatchAlternate | Should -BeNullOrEmpty + $currentState.BaseDN | Should -BeNullOrEmpty + $currentState.CertificateThumbprint | Should -BeNullOrEmpty + $currentState.Hostname | Should -BeNullOrEmpty + + $currentState.Ensure | Should -Be 'Absent' + + $currentState.Reasons | Should -HaveCount 2 + $currentState.Reasons[0].Code | Should -Be 'WSManListener:WSManListener:Ensure' + $currentState.Reasons[0].Phrase | Should -Be 'The property Ensure should be "Present", but was "Absent"' + $currentState.Reasons[1].Code | Should -Be 'WSManListener:WSManListener:Transport' + $currentState.Reasons[1].Phrase | Should -Be 'The property Transport should be "HTTPS", but was null' + } + } + } + + Context 'When the listener exists but should not' { + BeforeAll { + InModuleScope -ScriptBlock { + Set-StrictMode -Version 1.0 + + $script:mockWSManListenerInstance = [WSManListener] @{ + Transport = 'HTTP' + Ensure = 'Absent' + } + + <# + This mocks the method GetCurrentState(). + + Method Get() will call the base method Get() which will + call back to the derived class method GetCurrentState() + to get the result to return from the derived method Get(). + #> + $script:mockWSManListenerInstance | + Add-Member -Force -MemberType 'ScriptMethod' -Name 'GetCurrentState' -Value { + return [System.Collections.Hashtable] @{ + Transport = [WSManTransport] 'HTTP' + Port = [System.UInt16] 5985 + Address = '*' + Enabled = 'true' + URLPrefix = 'wsman' + Issuer = $null + MatchAlternate = $null + BaseDN = $null + CertificateThumbprint = $null + Hostname = $null + } + } -PassThru | + Add-Member -Force -MemberType 'ScriptMethod' -Name 'AssertProperties' -Value { + return + } + } + } + + It 'Should return the correct values' { + InModuleScope -ScriptBlock { + Set-StrictMode -Version 1.0 + + $currentState = $script:mockWSManListenerInstance.Get() + + $currentState.Transport | Should -Be 'HTTP' + $currentState.Port | Should -Be 5985 + + $currentState.Address | Should -Be '*' + $currentState.Enabled | Should -BeTrue + $currentState.URLPrefix | Should -Be 'wsman' + + $currentState.Issuer | Should -BeNullOrEmpty + $currentState.SubjectFormat | Should -Be 'Both' + $currentState.MatchAlternate | Should -BeNullOrEmpty + $currentState.BaseDN | Should -BeNullOrEmpty + $currentState.CertificateThumbprint | Should -BeNullOrEmpty + $currentState.Hostname | Should -BeNullOrEmpty + + $currentState.Ensure | Should -Be 'Present' + + $currentState.Reasons | Should -HaveCount 1 + $currentState.Reasons[0].Code | Should -Be 'WSManListener:WSManListener:Ensure' + $currentState.Reasons[0].Phrase | Should -Be 'The property Ensure should be "Absent", but was "Present"' + } + } + } } } From 608485b1fa8b393d422c7d906a972fbcc2760e58 Mon Sep 17 00:00:00 2001 From: Daniel Hughes Date: Fri, 20 Sep 2024 17:44:54 +0100 Subject: [PATCH 021/134] Fix tests --- source/Classes/020.WSManListener.ps1 | 13 ++++++------- tests/Unit/Classes/WSManListener.Tests.ps1 | 10 +++++----- 2 files changed, 11 insertions(+), 12 deletions(-) diff --git a/source/Classes/020.WSManListener.ps1 b/source/Classes/020.WSManListener.ps1 index e4e39bf..304091a 100644 --- a/source/Classes/020.WSManListener.ps1 +++ b/source/Classes/020.WSManListener.ps1 @@ -194,8 +194,8 @@ class WSManListener : ResourceBase $create = $false $selectorSet = @{ - Transport = $properties.Transport - Address = $properties.Address + Transport = $this.Transport + Address = $this.Address } Write-Verbose ('$properties.ContainsKey(''Ensure'') = {0}' -f $properties.ContainsKey('Ensure')) @@ -206,7 +206,6 @@ class WSManListener : ResourceBase { # Ensure was not in desired state so the resource should be removed $remove = $true - } elseif ($properties.ContainsKey('Ensure') -and $properties.Ensure -eq [Ensure]::Present -and $this.Ensure -eq [Ensure]::Present) { @@ -222,7 +221,7 @@ class WSManListener : ResourceBase if ($remove) { - Write-Verbose -Message ($this.localizedData.ListenerExistsRemoveMessage -f $properties.Transport, $properties.Port) + Write-Verbose -Message ($this.localizedData.ListenerExistsRemoveMessage -f $this.Transport, $this.Port) Remove-WSManInstance -ResourceURI 'winrm/config/Listener' -SelectorSet @selectorSet } @@ -234,7 +233,7 @@ class WSManListener : ResourceBase } - if ($properties.Transport -eq [WSManTransport]::HTTPS) + if ($this.Transport -eq [WSManTransport]::HTTPS) { $findCertificateParams = Get-DscProperty -Attribute @('Optional') -ExcludeName @('Port', 'Address') @@ -259,13 +258,13 @@ class WSManListener : ResourceBase # TODO: Extract this to assert or into a parameter set # A certificate could not be found to use for the HTTPS listener New-InvalidArgumentException ` - -Message ($this.localizedData.ListenerCreateFailNoCertError -f $properties.Transport, $properties.Port) ` + -Message ($this.localizedData.ListenerCreateFailNoCertError -f $this.Transport, $this.Port) ` -Argument 'Issuer' } # if } - Write-Verbose -Message ($this.localizedData.CreatingListenerMessage -f $properties.Transport, $properties.Port) + Write-Verbose -Message ($this.localizedData.CreatingListenerMessage -f $this.Transport, $this.Port) New-WSManInstance -ResourceURI 'winrm/config/Listener' -SelectorSet @selectorSet -ValueSet @valueSet -ErrorAction Stop } diff --git a/tests/Unit/Classes/WSManListener.Tests.ps1 b/tests/Unit/Classes/WSManListener.Tests.ps1 index 8621af6..4aed740 100644 --- a/tests/Unit/Classes/WSManListener.Tests.ps1 +++ b/tests/Unit/Classes/WSManListener.Tests.ps1 @@ -254,7 +254,7 @@ Describe 'WSManListener\Get()' -Tag 'Get' { $currentState.Ensure | Should -Be 'Absent' $currentState.Reasons | Should -HaveCount 1 $currentState.Reasons[0].Code | Should -Be 'WSManListener:WSManListener:Transport' - $currentState.Reasons[0].Phrase | Should -Be 'The property Transport should be "HTTP", but was null' + $currentState.Reasons[0].Phrase | Should -Be 'The property Transport should be "HTTP", but was ""' } } } @@ -376,10 +376,10 @@ Describe 'WSManListener\Get()' -Tag 'Get' { $currentState.Ensure | Should -Be 'Absent' $currentState.Reasons | Should -HaveCount 2 - $currentState.Reasons[0].Code | Should -Be 'WSManListener:WSManListener:Ensure' - $currentState.Reasons[0].Phrase | Should -Be 'The property Ensure should be "Present", but was "Absent"' - $currentState.Reasons[1].Code | Should -Be 'WSManListener:WSManListener:Transport' - $currentState.Reasons[1].Phrase | Should -Be 'The property Transport should be "HTTPS", but was null' + $currentState.Reasons[1].Code | Should -Be 'WSManListener:WSManListener:Ensure' + $currentState.Reasons[1].Phrase | Should -Be 'The property Ensure should be "Present", but was "Absent"' + $currentState.Reasons[0].Code | Should -Be 'WSManListener:WSManListener:Transport' + $currentState.Reasons[0].Phrase | Should -Be 'The property Transport should be "HTTPS", but was ""' } } } From dbdd0c3be51e80ce2822db5a6e0a6b6e6bca9de6 Mon Sep 17 00:00:00 2001 From: Daniel Hughes Date: Thu, 26 Sep 2024 17:11:46 +0100 Subject: [PATCH 022/134] Refactor tests --- tests/Unit/Private/Find-Certificate.Tests.ps1 | 705 ++++++++++-------- 1 file changed, 385 insertions(+), 320 deletions(-) diff --git a/tests/Unit/Private/Find-Certificate.Tests.ps1 b/tests/Unit/Private/Find-Certificate.Tests.ps1 index 70518b7..25aaa03 100644 --- a/tests/Unit/Private/Find-Certificate.Tests.ps1 +++ b/tests/Unit/Private/Find-Certificate.Tests.ps1 @@ -13,7 +13,7 @@ BeforeDiscovery { & "$PSScriptRoot/../../../build.ps1" -Tasks 'noop' 2>&1 4>&1 5>&1 6>&1 > $null } - # If the dependencies has not been resolved, this will throw an error. + # This will throw an error if the dependencies have not been resolved. Import-Module -Name 'DscResource.Test' -Force -ErrorAction 'Stop' } } @@ -31,6 +31,54 @@ BeforeAll { $PSDefaultParameterValues['InModuleScope:ModuleName'] = $script:dscModuleName $PSDefaultParameterValues['Mock:ModuleName'] = $script:dscModuleName $PSDefaultParameterValues['Should:ModuleName'] = $script:dscModuleName + + # Create the Mock Objects that will be used for running tests + $script:mockCertificateThumbprint = '74FA31ADEA7FDD5333CED10910BFA6F665A1F2FC' + $mockFQDN = 'SERVER1.CONTOSO.COM' + $mockHostName = $([System.Net.Dns]::GetHostByName($ENV:computerName).Hostname) + $script:mockIssuer = 'CN=CONTOSO.COM Issuing CA, DC=CONTOSO, DC=COM' + $script:mockDN = 'O=Contoso Inc, S=Pennsylvania, C=US' + $mockCertificate = @{ + Thumbprint = $mockCertificateThumbprint + Subject = "CN=$mockHostName" + Issuer = $mockIssuer + Extensions = @{ EnhancedKeyUsages = @{ FriendlyName = 'Server Authentication' } } + DNSNameList = @{ Unicode = $mockHostName } + } + + $script:mockCertificateDN = @{ + Thumbprint = $mockCertificateThumbprint + Subject = "CN=$mockHostName, $mockDN" + Issuer = $mockIssuer + Extensions = @{ EnhancedKeyUsages = @{ FriendlyName = 'Server Authentication' } } + DNSNameList = @{ Unicode = $mockHostName } + } + + $script:mockListenerHTTP = @{ + cfg = 'http://schemas.microsoft.com/wbem/wsman/1/config/listener' + xsi = 'http://www.w3.org/2001/XMLSchema-instance' + lang = 'en-US' + Address = '*' + Transport = 'HTTP' + Port = 5985 + Hostname = '' + Enabled = 'true' + URLPrefix = 'wsman' + CertificateThumbprint = '' + } + + $script:mockListenerHTTPS = @{ + cfg = 'http://schemas.microsoft.com/wbem/wsman/1/config/listener' + xsi = 'http://www.w3.org/2001/XMLSchema-instance' + lang = 'en-US' + Address = '*' + Transport = 'HTTPS' + Port = 5986 + Hostname = $mockFQDN + Enabled = 'true' + URLPrefix = 'wsman' + CertificateThumbprint = $mockCertificateThumbprint + } } AfterAll { @@ -43,26 +91,23 @@ AfterAll { } Describe 'Find-Certificate' -Tag 'Private' { - BeforeAll { - InModuleScope -ScriptBlock { - $script:mockCertificateThumbprint = '74FA31ADEA7FDD5333CED10910BFA6F665A1F2FC' - } - } Context 'CertificateThumbprint is passed but does not exist' { BeforeAll { Mock -CommandName Get-ChildItem } It 'Should not throw error' { - InModuleScope -ScriptBlock { + InModuleScope -Parameters @{ + mockCertificateThumbprint = $script:mockCertificateThumbprint + } -ScriptBlock { Set-StrictMode -Version 1.0 - $findParameters = @{ - CertificateThumbprint = $script:mockCertificateThumbprint + $findCertificateParams = @{ + CertificateThumbprint = $mockCertificateThumbprint Verbose = $VerbosePreference } - $script:returnedCertificate = Find-Certificate @findParameters + $script:returnedCertificate = Find-Certificate @findCertificateParams { $script:returnedCertificate } | Should -Not -Throw } @@ -74,7 +119,9 @@ Describe 'Find-Certificate' -Tag 'Private' { $script:returnedCertificate | Should -BeNullOrEmpty } + } + It 'Should call expected Mocks' { Should -Invoke -CommandName Get-ChildItem -Exactly -Times 1 -Scope Context } } @@ -82,339 +129,357 @@ Describe 'Find-Certificate' -Tag 'Private' { Context 'CertificateThumbprint is passed and does exist' { BeforeAll { Mock -CommandName Get-ChildItem -MockWith { - @{ - Thumbprint = '74FA31ADEA7FDD5333CED10910BFA6F665A1F2FC' - Subject = "CN=$([System.Net.Dns]::GetHostByName($ENV:computerName).Hostname), O=Contoso Inc, S=Pennsylvania, C=US" - Issuer = 'CN=CONTOSO.COM Issuing CA, DC=CONTOSO, DC=COM' - Extensions = @{ EnhancedKeyUsages = @{ FriendlyName = 'Server Authentication' } } - DNSNameList = @{ Unicode = $([System.Net.Dns]::GetHostByName($ENV:computerName).Hostname) } + $mockCertificateDN + } + } + + It 'Should not throw error' { + InModuleScope -Parameters @{ + mockCertificateThumbprint = $script:mockCertificateThumbprint + } -ScriptBlock { + Set-StrictMode -Version 1.0 + + $findCertificateParams = @{ + CertificateThumbprint = $mockCertificateThumbprint + Verbose = $VerbosePreference } + + $script:returnedCertificate = Find-Certificate @findCertificateParams + + { $script:returnedCertificate } | Should -Not -Throw + } + } + + It 'Should return expected certificate' { + InModuleScope -Parameters @{ + mockCertificateThumbprint = $script:mockCertificateThumbprint + } -ScriptBlock { + Set-StrictMode -Version 1.0 + + $script:returnedCertificate.Thumbprint | Should -Be $mockCertificateThumbprint } } + It 'Should call expected Mocks' { + Should -Invoke -CommandName Get-ChildItem -Exactly -Times 1 -Scope Context + } + } + + Context 'SubjectFormat is Both, Certificate does not exist, DN passed' { + BeforeAll { + Mock -CommandName Get-ChildItem + } + It 'Should not throw error' { + InModuleScope -Parameters @{ + mockIssuer = $script:mockIssuer + mockDN = $script:mockDN + } -ScriptBlock { + Set-StrictMode -Version 1.0 + + $findCertificateParams = @{ + Issuer = $mockIssuer + SubjectFormat = 'Both' + MatchAlternate = $true + BaseDN = $mockDN + Verbose = $VerbosePreference + } + + $script:returnedCertificate = Find-Certificate @findCertificateParams + + { $script:returnedCertificate } | Should -Not -Throw + } + } + + It 'Should return null' { InModuleScope -ScriptBlock { Set-StrictMode -Version 1.0 - $findParameters = @{ - CertificateThumbprint = $script:mockCertificateThumbprint - Verbose = $VerbosePreference + $script:returnedCertificate | Should -BeNullOrEmpty + } + } + + It 'Should call expected Mocks' { + Should -Invoke -CommandName Get-ChildItem -Exactly -Times 2 -Scope Context + } + } + + Context 'SubjectFormat is Both, Certificate with DN Exists, DN passed' { + BeforeAll { + Mock -CommandName Get-ChildItem -MockWith { + $mockCertificateDN + } + } + + It 'Should not throw error' { + InModuleScope -Parameters @{ + mockIssuer = $script:mockIssuer + mockDN = $script:mockDN + } -ScriptBlock { + Set-StrictMode -Version 1.0 + + $findCertificateParams = @{ + Issuer = $mockIssuer + SubjectFormat = 'Both' + MatchAlternate = $true + BaseDN = $mockDN + Verbose = $VerbosePreference } - $script:returnedCertificate = Find-Certificate @findParameters + $script:returnedCertificate = Find-Certificate @findCertificateParams { $script:returnedCertificate } | Should -Not -Throw } } It 'Should return expected certificate' { + InModuleScope -Parameters @{ + mockCertificateThumbprint = $script:mockCertificateThumbprint + } -ScriptBlock { + Set-StrictMode -Version 1.0 + + $script:returnedCertificate.Thumbprint | Should -Be $mockCertificateThumbprint + } + } + + It 'Should call expected Mocks' { + Should -Invoke -CommandName Get-ChildItem -Exactly -Times 1 -Scope Context + } + } + + Context 'SubjectFormat is Both, Certificate without DN Exists, DN passed' { + BeforeAll { + Mock -CommandName Get-ChildItem -MockWith { + $mockCertificate + } + } + + It 'Should not throw error' { + InModuleScope -Parameters @{ + mockIssuer = $script:mockIssuer + mockDN = $script:mockDN + } -ScriptBlock { + Set-StrictMode -Version 1.0 + + $findCertificateParams = @{ + Issuer = $mockIssuer + SubjectFormat = 'Both' + MatchAlternate = $true + BaseDN = $mockDN + Verbose = $VerbosePreference + } + + $script:returnedCertificate = Find-Certificate @findCertificateParams + + { $script:returnedCertificate } | Should -Not -Throw + } + } + + It 'Should return null' { InModuleScope -ScriptBlock { Set-StrictMode -Version 1.0 - $script:returnedCertificate.Thumbprint | Should -Be $script:mockCertificateThumbprint + $script:returnedCertificate | Should -BeNullOrEmpty + } + } + + It 'Should call expected Mocks' { + Should -Invoke -CommandName Get-ChildItem -Exactly -Times 2 -Scope Context + } + } + + Context 'SubjectFormat is Both, Certificate does not exist, DN not passed' { + BeforeAll { + Mock -CommandName Get-ChildItem + } + + It 'Should not throw error' { + InModuleScope -Parameters @{ + mockIssuer = $script:mockIssuer + } -ScriptBlock { + Set-StrictMode -Version 1.0 + + $findCertificateParams = @{ + Issuer = $mockIssuer + SubjectFormat = 'Both' + MatchAlternate = $true + Verbose = $VerbosePreference + } + + $script:returnedCertificate = Find-Certificate @findCertificateParams + + { $script:returnedCertificate } | Should -Not -Throw } + } + It 'Should return null' { + InModuleScope -ScriptBlock { + Set-StrictMode -Version 1.0 + + $script:returnedCertificate | Should -BeNullOrEmpty + } + } + + It 'Should call expected Mocks' { + Should -Invoke -CommandName Get-ChildItem -Exactly -Times 2 -Scope Context + } + } + + Context 'SubjectFormat is Both, Certificate with DN Exists, DN not passed' { + BeforeAll { + Mock -CommandName Get-ChildItem -MockWith { + $mockCertificateDN + } + } + + It 'Should not throw error' { + InModuleScope -Parameters @{ + mockIssuer = $script:mockIssuer + } -ScriptBlock { + Set-StrictMode -Version 1.0 + + $findCertificateParams = @{ + Issuer = $mockIssuer + SubjectFormat = 'Both' + MatchAlternate = $true + Verbose = $VerbosePreference + } + + $script:returnedCertificate = Find-Certificate @findCertificateParams + + { $script:returnedCertificate } | Should -Not -Throw + } + } + + It 'Should return null' { + InModuleScope -ScriptBlock { + Set-StrictMode -Version 1.0 + + $script:returnedCertificate | Should -BeNullOrEmpty + } + } + + It 'Should call expected Mocks' { + Should -Invoke -CommandName Get-ChildItem -Exactly -Times 2 -Scope Context + } + } + + Context 'SubjectFormat is Both, Certificate without DN Exists, DN not passed' { + BeforeAll { + Mock -CommandName Get-ChildItem -MockWith { + $mockCertificate + } + } + + It 'Should not throw error' { + InModuleScope -Parameters @{ + mockIssuer = $script:mockIssuer + } -ScriptBlock { + Set-StrictMode -Version 1.0 + + $findCertificateParams = @{ + Issuer = $mockIssuer + SubjectFormat = 'Both' + MatchAlternate = $true + Verbose = $VerbosePreference + } + + $script:returnedCertificate = Find-Certificate @findCertificateParams + + { $script:returnedCertificate } | Should -Not -Throw + } + } + + It 'Should return expected certificate' { + InModuleScope -Parameters @{ + mockCertificateThumbprint = $script:mockCertificateThumbprint + } -ScriptBlock { + Set-StrictMode -Version 1.0 + + $script:returnedCertificate.Thumbprint | Should -Be $mockCertificateThumbprint + } + } + + It 'Should call expected Mocks' { Should -Invoke -CommandName Get-ChildItem -Exactly -Times 1 -Scope Context } } - # Context 'When SubjectFormat is ''Both''' { - # Context 'Certificate does not exist, DN passed' { - # BeforeAll { - # Mock -CommandName Get-ChildItem - # } - - # It 'Should not throw error' { - # InModuleScope -Parameters @{ - # mockIssuer = $script:mockIssuer - # mockDN = $script:mockDN - # } -ScriptBlock { - # Set-StrictMode -Version 1.0 - - # { $script:returnedCertificate = Find-Certificate ` - # -Issuer $mockIssuer ` - # -SubjectFormat 'Both' ` - # -MatchAlternate $true ` - # -DN $mockDN ` - # -Verbose:$VerbosePreference } | Should -Not -Throw - # } - # } - - # It 'Should return null' { - # InModuleScope -ScriptBlock { - # Set-StrictMode -Version 1.0 - - # $script:returnedCertificate | Should -BeNullOrEmpty - # } - # } - - # It 'Should call expected Mocks' { - # Should -Invoke ` - # -CommandName Get-ChildItem ` - # -Exactly -Times 2 ` - # -Scope Context - # } - # } - - # Context 'Certificate with DN Exists, DN passed' { - # BeforeAll { - # Mock -CommandName Get-ChildItem -MockWith { - # $mockCertificateDN - # } - # } - - # It 'Should not throw error' { - # InModuleScope -Parameters @{ - # mockIssuer = $script:mockIssuer - # mockDN = $script:mockDN - # } -ScriptBlock { - # Set-StrictMode -Version 1.0 - - # { $script:returnedCertificate = Find-Certificate ` - # -Issuer $mockIssuer ` - # -SubjectFormat 'Both' ` - # -MatchAlternate $true ` - # -DN $mockDN ` - # -Verbose:$VerbosePreference } | Should -Not -Throw - # } - # } - - # It 'Should return expected certificate' { - # InModuleScope -Parameters @{ - # mockCertificateThumbprint = $script:mockCertificateThumbprint - # } -ScriptBlock { - # Set-StrictMode -Version 1.0 - - # $script:returnedCertificate.Thumbprint | Should -Be $mockCertificateThumbprint - # } - # } - - # It 'Should call expected Mocks' { - # Should -Invoke ` - # -CommandName Get-ChildItem ` - # -Exactly -Times 1 ` - # -Scope Context - # } - # } - - # Context 'Certificate without DN Exists, DN passed' { - # BeforeAll { - # Mock -CommandName Get-ChildItem -MockWith { - # $mockCertificate - # } - # } - - # It 'Should not throw error' { - # InModuleScope -Parameters @{ - # mockIssuer = $script:mockIssuer - # mockDN = $script:mockDN - # } -ScriptBlock { - # Set-StrictMode -Version 1.0 - - # { $script:returnedCertificate = Find-Certificate ` - # -Issuer $mockIssuer ` - # -SubjectFormat 'Both' ` - # -MatchAlternate $true ` - # -DN $mockDN ` - # -Verbose:$VerbosePreference } | Should -Not -Throw - # } - # } - - # It 'Should return null' { - # InModuleScope -ScriptBlock { - # Set-StrictMode -Version 1.0 - - # $script:returnedCertificate | Should -BeNullOrEmpty - # } - # } - - # It 'Should call expected Mocks' { - # Should -Invoke ` - # -CommandName Get-ChildItem ` - # -Exactly -Times 2 ` - # -Scope Context - # } - # } - - # Context 'Certificate does not exist, DN not passed' { - # BeforeAll { - # Mock -CommandName Get-ChildItem - # } - - # It 'Should not throw error' { - # InModuleScope -Parameters @{ - # mockIssuer = $script:mockIssuer - # } -ScriptBlock { - # Set-StrictMode -Version 1.0 - - # { $script:returnedCertificate = Find-Certificate ` - # -Issuer $mockIssuer ` - # -SubjectFormat 'Both' ` - # -MatchAlternate $true ` - # -Verbose:$VerbosePreference } | Should -Not -Throw - # } - # } - - # It 'Should return null' { - # InModuleScope -ScriptBlock { - # Set-StrictMode -Version 1.0 - - # $script:returnedCertificate | Should -BeNullOrEmpty - # } - # } - - # It 'Should call expected Mocks' { - # Should -Invoke ` - # -CommandName Get-ChildItem ` - # -Exactly -Times 2 ` - # -Scope Context - # } - # } - - # Context 'Certificate with DN Exists, DN not passed' { - # BeforeAll { - # Mock -CommandName Get-ChildItem -MockWith { - # $mockCertificateDN - # } - # } - - # It 'Should not throw error' { - # InModuleScope -Parameters @{ - # mockIssuer = $script:mockIssuer - # } -ScriptBlock { - # Set-StrictMode -Version 1.0 - - # { $script:returnedCertificate = Find-Certificate ` - # -Issuer $mockIssuer ` - # -SubjectFormat 'Both' ` - # -MatchAlternate $true ` - # -Verbose:$VerbosePreference } | Should -Not -Throw - # } - # } - - # It 'Should return null' { - # InModuleScope -ScriptBlock { - # Set-StrictMode -Version 1.0 - - # $script:returnedCertificate | Should -BeNullOrEmpty - # } - # } - - # It 'Should call expected Mocks' { - # Should -Invoke ` - # -CommandName Get-ChildItem ` - # -Exactly -Times 2 ` - # -Scope Context - # } - # } - - # Context 'Certificate without DN Exists, DN not passed' { - # BeforeAll { - # Mock -CommandName Get-ChildItem -MockWith { - # $mockCertificate - # } - # } - - # It 'Should not throw error' { - # InModuleScope -Parameters @{ - # mockIssuer = $script:mockIssuer - # } -ScriptBlock { - # Set-StrictMode -Version 1.0 - - # { $script:returnedCertificate = Find-Certificate ` - # -Issuer $mockIssuer ` - # -SubjectFormat 'Both' ` - # -MatchAlternate $true ` - # -Verbose:$VerbosePreference } | Should -Not -Throw - # } - # } - - # It 'Should return expected certificate' { - # InModuleScope -Parameters @{ - # mockCertificateThumbprint = $script:mockCertificateThumbprint - # } -ScriptBlock { - # Set-StrictMode -Version 1.0 - - # $script:returnedCertificate.Thumbprint | Should -Be $mockCertificateThumbprint - # } - # } - - # It 'Should call expected Mocks' { - # Should -Invoke ` - # -CommandName Get-ChildItem ` - # -Exactly -Times 1 ` - # -Scope Context - # } - # } - - # Context 'Certificate does not exist, DN not passed, MatchAlternate is false' { - # BeforeAll { - # Mock -CommandName Get-ChildItem - # } - - # It 'Should not throw error' { - # InModuleScope -Parameters @{ - # mockIssuer = $script:mockIssuer - # } -ScriptBlock { - # Set-StrictMode -Version 1.0 - - # $findParameters = @{ - # Issuer = $mockIssuer - # SubjectFormat = 'Both' - # MatchAlternate = $false - # Verbose = $VerbosePreference - # } - - # { $script:returnedCertificate = Find-Certificate @findParameters } | Should -Not -Throw - # } - # } - - # It 'Should return null' { - # InModuleScope -ScriptBlock { - # Set-StrictMode -Version 1.0 - - # $script:returnedCertificate | Should -BeNullOrEmpty - # } - - # Should -Invoke -CommandName Get-ChildItem -Exactly -Times 2 -Scope It - # } - # } - - # Context 'Certificate without DN Exists, DN not passed, MatchAlternate is false' { - # BeforeAll { - # Mock -CommandName Get-ChildItem -MockWith { - # $mockCertificate - # } - # } - - # It 'Should not throw error' { - # InModuleScope -Parameters @{ - # mockIssuer = $script:mockIssuer - # } -ScriptBlock { - # Set-StrictMode -Version 1.0 - - # $findParameters = @{ - # Issuer = $mockIssuer - # SubjectFormat = 'Both' - # MatchAlternate = $false - # Verbose = $VerbosePreference - # } - - # { $script:returnedCertificate = Find-Certificate @findParameters } | Should -Not -Throw - # } - # } - - # It 'Should return expected certificate' { - # InModuleScope -Parameters @{ - # mockCertificateThumbprint = $script:mockCertificateThumbprint - # } -ScriptBlock { - # Set-StrictMode -Version 1.0 - - # $script:returnedCertificate.Thumbprint | Should -Be $mockCertificateThumbprint - # } - - # Should -Invoke -CommandName Get-ChildItem -Exactly -Times 1 -Scope It - # } - # } - # } + Context 'SubjectFormat is Both, Certificate does not exist, DN not passed, MatchAlternate is false' { + BeforeAll { + Mock -CommandName Get-ChildItem + } + + It 'Should not throw error' { + InModuleScope -Parameters @{ + mockIssuer = $script:mockIssuer + } -ScriptBlock { + Set-StrictMode -Version 1.0 + + $findCertificateParams = @{ + Issuer = $mockIssuer + SubjectFormat = 'Both' + MatchAlternate = $false + Verbose = $VerbosePreference + } + + $script:returnedCertificate = Find-Certificate @findCertificateParams + + { $script:returnedCertificate } | Should -Not -Throw + } + } + + It 'Should return null' { + InModuleScope -ScriptBlock { + Set-StrictMode -Version 1.0 + + $script:returnedCertificate | Should -BeNullOrEmpty + } + } + + It 'Should call expected Mocks' { + Should -Invoke -CommandName Get-ChildItem -Exactly -Times 2 -Scope Context + } + } + + Context 'SubjectFormat is Both, Certificate without DN Exists, DN not passed, MatchAlternate is false' { + BeforeAll { + Mock -CommandName Get-ChildItem -MockWith { + $mockCertificate + } + } + + It 'Should not throw error' { + InModuleScope -Parameters @{ + mockIssuer = $script:mockIssuer + } -ScriptBlock { + Set-StrictMode -Version 1.0 + + $findCertificateParams = @{ + Issuer = $mockIssuer + SubjectFormat = 'Both' + MatchAlternate = $false + Verbose = $VerbosePreference + } + + $script:returnedCertificate = Find-Certificate @findCertificateParams + + { $script:returnedCertificate } | Should -Not -Throw + } + } + + It 'Should return expected certificate' { + InModuleScope -Parameters @{ + mockCertificateThumbprint = $script:mockCertificateThumbprint + } -ScriptBlock { + Set-StrictMode -Version 1.0 + + $script:returnedCertificate.Thumbprint | Should -Be $mockCertificateThumbprint + } + } + + It 'Should call expected Mocks' { + Should -Invoke -CommandName Get-ChildItem -Exactly -Times 1 -Scope Context + } + } } From 25771637a20306f23123e02abfe905213ef5e65d Mon Sep 17 00:00:00 2001 From: Daniel Hughes Date: Thu, 26 Sep 2024 17:11:57 +0100 Subject: [PATCH 023/134] Fix missing `f` --- source/Private/Find-Certificate.ps1 | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/source/Private/Find-Certificate.ps1 b/source/Private/Find-Certificate.ps1 index ac67b53..6d6096e 100644 --- a/source/Private/Find-Certificate.ps1 +++ b/source/Private/Find-Certificate.ps1 @@ -94,7 +94,7 @@ function Find-Certificate else { # Try and lookup the certificate using the subject name - Write-Verbose -Message ($script:localizedData.FindCertificate_Message - $Subject, $Issuer) + Write-Verbose -Message ($script:localizedData.FindCertificate_Message -f $Subject, $Issuer) $certificate = Get-ChildItem -Path Cert:\localmachine\my | Where-Object -FilterScript { ($_.Extensions.EnhancedKeyUsages.FriendlyName ` @@ -132,7 +132,7 @@ function Find-Certificate else { # Try and lookup the certificate using the subject name - Write-Verbose -Message ($script:localizedData.FindCertificate_Message - $Subject, $Issuer) + Write-Verbose -Message ($script:localizedData.FindCertificate_Message -f $Subject, $Issuer) $certificate = Get-ChildItem -Path Cert:\localmachine\my | Where-Object -FilterScript { ($_.Extensions.EnhancedKeyUsages.FriendlyName ` From a28b4ef03ac7a112357be064b2910c1b431fd8a6 Mon Sep 17 00:00:00 2001 From: Daniel Hughes Date: Thu, 26 Sep 2024 17:55:17 +0100 Subject: [PATCH 024/134] Remove parenthesis --- source/Private/Find-Certificate.ps1 | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/source/Private/Find-Certificate.ps1 b/source/Private/Find-Certificate.ps1 index 6d6096e..22a7c8a 100644 --- a/source/Private/Find-Certificate.ps1 +++ b/source/Private/Find-Certificate.ps1 @@ -135,10 +135,9 @@ function Find-Certificate 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) + $_.Extensions.EnhancedKeyUsages.FriendlyName -contains 'Server Authentication' + -and $_.Issuer -eq $Issuer + -and $_.Subject -eq $Subject } | Select-Object -First 1 } # if } # if From c1a50ea4f38960a428e509b0c43cdebd30865f44 Mon Sep 17 00:00:00 2001 From: Daniel Hughes Date: Thu, 26 Sep 2024 17:55:26 +0100 Subject: [PATCH 025/134] Add more tests --- tests/Unit/Classes/WSManListener.Tests.ps1 | 384 ++++++++++++++++----- 1 file changed, 291 insertions(+), 93 deletions(-) diff --git a/tests/Unit/Classes/WSManListener.Tests.ps1 b/tests/Unit/Classes/WSManListener.Tests.ps1 index 4aed740..a629025 100644 --- a/tests/Unit/Classes/WSManListener.Tests.ps1 +++ b/tests/Unit/Classes/WSManListener.Tests.ps1 @@ -90,7 +90,7 @@ Describe 'WSManListener\Get()' -Tag 'Get' { InModuleScope -ScriptBlock { Set-StrictMode -Version 1.0 - $script:mockWSManListenerInstance = [WSManListener] @{ + $script:mockInstance = [WSManListener] @{ Transport = 'HTTP' Ensure = 'Present' } @@ -102,14 +102,14 @@ Describe 'WSManListener\Get()' -Tag 'Get' { call back to the derived class method GetCurrentState() to get the result to return from the derived method Get(). #> - $script:mockWSManListenerInstance | + $script:mockInstance | Add-Member -Force -MemberType 'ScriptMethod' -Name 'GetCurrentState' -Value { - return [System.Collections.Hashtable] @{ - Transport = [WSManTransport] 'HTTP' - Port = [System.UInt16] 5985 - Address = '*' - Enabled = 'true' - URLPrefix = 'wsman' + return @{ + Transport = [WSManTransport] 'HTTP' + Port = [System.UInt16] 5985 + Address = '*' + Enabled = 'true' + URLPrefix = 'wsman' } } -PassThru | Add-Member -Force -MemberType 'ScriptMethod' -Name 'AssertProperties' -Value { @@ -122,7 +122,7 @@ Describe 'WSManListener\Get()' -Tag 'Get' { InModuleScope -ScriptBlock { Set-StrictMode -Version 1.0 - $currentState = $script:mockWSManListenerInstance.Get() + $currentState = $script:mockInstance.Get() $currentState.Transport | Should -Be 'HTTP' $currentState.Port | Should -Be 5985 @@ -150,7 +150,7 @@ Describe 'WSManListener\Get()' -Tag 'Get' { InModuleScope -ScriptBlock { Set-StrictMode -Version 1.0 - $script:mockWSManListenerInstance = [WSManListener] @{ + $script:mockInstance = [WSManListener] @{ Transport = 'HTTPS' Ensure = 'Present' } @@ -162,14 +162,14 @@ Describe 'WSManListener\Get()' -Tag 'Get' { call back to the derived class method GetCurrentState() to get the result to return from the derived method Get(). #> - $script:mockWSManListenerInstance | + $script:mockInstance | Add-Member -Force -MemberType 'ScriptMethod' -Name 'GetCurrentState' -Value { - return [System.Collections.Hashtable] @{ - Transport = [WSManTransport] 'HTTPS' - Port = [System.UInt16] 5986 - Address = '*' - Enabled = 'true' - URLPrefix = 'wsman' + return @{ + Transport = [WSManTransport] 'HTTPS' + Port = [System.UInt16] 5986 + Address = '*' + Enabled = 'true' + URLPrefix = 'wsman' } } -PassThru | Add-Member -Force -MemberType 'ScriptMethod' -Name 'AssertProperties' -Value { @@ -182,7 +182,7 @@ Describe 'WSManListener\Get()' -Tag 'Get' { InModuleScope -ScriptBlock { Set-StrictMode -Version 1.0 - $currentState = $script:mockWSManListenerInstance.Get() + $currentState = $script:mockInstance.Get() $currentState.Transport | Should -Be 'HTTPS' $currentState.Port | Should -Be 5986 @@ -209,7 +209,7 @@ Describe 'WSManListener\Get()' -Tag 'Get' { InModuleScope -ScriptBlock { Set-StrictMode -Version 1.0 - $script:mockWSManListenerInstance = [WSManListener] @{ + $script:mockInstance = [WSManListener] @{ Transport = 'HTTP' Ensure = 'Absent' } @@ -221,9 +221,9 @@ Describe 'WSManListener\Get()' -Tag 'Get' { call back to the derived class method GetCurrentState() to get the result to return from the derived method Get(). #> - $script:mockWSManListenerInstance | + $script:mockInstance | Add-Member -Force -MemberType 'ScriptMethod' -Name 'GetCurrentState' -Value { - return [System.Collections.Hashtable] @{} + return @{} } -PassThru | Add-Member -Force -MemberType 'ScriptMethod' -Name 'AssertProperties' -Value { return @@ -235,7 +235,7 @@ Describe 'WSManListener\Get()' -Tag 'Get' { InModuleScope -ScriptBlock { Set-StrictMode -Version 1.0 - $currentState = $script:mockWSManListenerInstance.Get() + $currentState = $script:mockInstance.Get() $currentState.Transport | Should -Be 'HTTP' $currentState.Port | Should -BeNullOrEmpty @@ -254,7 +254,7 @@ Describe 'WSManListener\Get()' -Tag 'Get' { $currentState.Ensure | Should -Be 'Absent' $currentState.Reasons | Should -HaveCount 1 $currentState.Reasons[0].Code | Should -Be 'WSManListener:WSManListener:Transport' - $currentState.Reasons[0].Phrase | Should -Be 'The property Transport should be "HTTP", but was ""' + $currentState.Reasons[0].Phrase | Should -Be 'The property Transport should be "HTTP", but was null' } } } @@ -266,7 +266,7 @@ Describe 'WSManListener\Get()' -Tag 'Get' { InModuleScope -ScriptBlock { Set-StrictMode -Version 1.0 - $script:mockWSManListenerInstance = [WSManListener] @{ + $script:mockInstance = [WSManListener] @{ Transport = 'HTTPS' Port = 5986 Ensure = 'Present' @@ -279,14 +279,14 @@ Describe 'WSManListener\Get()' -Tag 'Get' { call back to the derived class method GetCurrentState() to get the result to return from the derived method Get(). #> - $script:mockWSManListenerInstance | + $script:mockInstance | Add-Member -Force -MemberType 'ScriptMethod' -Name 'GetCurrentState' -Value { - return [System.Collections.Hashtable] @{ - Transport = [WSManTransport] 'HTTPS' - Port = [System.UInt16] 6000 - Address = '*' - Enabled = 'true' - URLPrefix = 'wsman' + return @{ + Transport = [WSManTransport] 'HTTPS' + Port = [System.UInt16] 6000 + Address = '*' + Enabled = 'true' + URLPrefix = 'wsman' } } -PassThru | Add-Member -Force -MemberType 'ScriptMethod' -Name 'AssertProperties' -Value { @@ -299,7 +299,7 @@ Describe 'WSManListener\Get()' -Tag 'Get' { InModuleScope -ScriptBlock { Set-StrictMode -Version 1.0 - $currentState = $script:mockWSManListenerInstance.Get() + $currentState = $script:mockInstance.Get() $currentState.Transport | Should -Be 'HTTPS' @@ -326,70 +326,12 @@ Describe 'WSManListener\Get()' -Tag 'Get' { } } - Context 'When the listener exists' { - BeforeAll { - InModuleScope -ScriptBlock { - Set-StrictMode -Version 1.0 - - $script:mockWSManListenerInstance = [WSManListener] @{ - Transport = 'HTTPS' - Ensure = 'Present' - } - - <# - This mocks the method GetCurrentState(). - - Method Get() will call the base method Get() which will - call back to the derived class method GetCurrentState() - to get the result to return from the derived method Get(). - #> - $script:mockWSManListenerInstance | - Add-Member -Force -MemberType 'ScriptMethod' -Name 'GetCurrentState' -Value { - return [System.Collections.Hashtable] @{} - } -PassThru | - Add-Member -Force -MemberType 'ScriptMethod' -Name 'AssertProperties' -Value { - return - } - } - } - - It 'Should return the correct values' { - InModuleScope -ScriptBlock { - Set-StrictMode -Version 1.0 - - $currentState = $script:mockWSManListenerInstance.Get() - - $currentState.Transport | Should -Be 'HTTPS' - $currentState.Port | Should -BeNullOrEmpty - - $currentState.Address | Should -BeNullOrEmpty - $currentState.Enabled | Should -BeFalse - $currentState.URLPrefix | Should -BeNullOrEmpty - - $currentState.Issuer | Should -BeNullOrEmpty - $currentState.SubjectFormat | Should -Be 'Both' - $currentState.MatchAlternate | Should -BeNullOrEmpty - $currentState.BaseDN | Should -BeNullOrEmpty - $currentState.CertificateThumbprint | Should -BeNullOrEmpty - $currentState.Hostname | Should -BeNullOrEmpty - - $currentState.Ensure | Should -Be 'Absent' - - $currentState.Reasons | Should -HaveCount 2 - $currentState.Reasons[1].Code | Should -Be 'WSManListener:WSManListener:Ensure' - $currentState.Reasons[1].Phrase | Should -Be 'The property Ensure should be "Present", but was "Absent"' - $currentState.Reasons[0].Code | Should -Be 'WSManListener:WSManListener:Transport' - $currentState.Reasons[0].Phrase | Should -Be 'The property Transport should be "HTTPS", but was ""' - } - } - } - Context 'When the listener exists but should not' { BeforeAll { InModuleScope -ScriptBlock { Set-StrictMode -Version 1.0 - $script:mockWSManListenerInstance = [WSManListener] @{ + $script:mockInstance = [WSManListener] @{ Transport = 'HTTP' Ensure = 'Absent' } @@ -401,7 +343,7 @@ Describe 'WSManListener\Get()' -Tag 'Get' { call back to the derived class method GetCurrentState() to get the result to return from the derived method Get(). #> - $script:mockWSManListenerInstance | + $script:mockInstance | Add-Member -Force -MemberType 'ScriptMethod' -Name 'GetCurrentState' -Value { return [System.Collections.Hashtable] @{ Transport = [WSManTransport] 'HTTP' @@ -426,7 +368,7 @@ Describe 'WSManListener\Get()' -Tag 'Get' { InModuleScope -ScriptBlock { Set-StrictMode -Version 1.0 - $currentState = $script:mockWSManListenerInstance.Get() + $currentState = $script:mockInstance.Get() $currentState.Transport | Should -Be 'HTTP' $currentState.Port | Should -Be 5985 @@ -452,3 +394,259 @@ Describe 'WSManListener\Get()' -Tag 'Get' { } } } + +Describe 'WSManListener\Set()' -Tag 'Set' { + BeforeAll { + InModuleScope -ScriptBlock { + Set-StrictMode -Version 1.0 + + $script:mockInstance = [WSManListener] @{ + Transport = 'HTTP' + Port = 5000 + Ensure = 'Present' + } | + # Mock method Modify which is called by the case method Set(). + Add-Member -Force -MemberType 'ScriptMethod' -Name 'Modify' -Value { + $script:methodModifyCallCount += 1 + } -PassThru + } + } + + BeforeEach { + InModuleScope -ScriptBlock { + Set-StrictMode -Version 1.0 + + $script:methodModifyCallCount = 0 + } + } + + Context 'When the system is in the desired state' { + BeforeAll { + InModuleScope -ScriptBlock { + Set-StrictMode -Version 1.0 + + $script:mockInstance | + # Mock method Compare() which is called by the base method Set() + Add-Member -Force -MemberType 'ScriptMethod' -Name 'Compare' -Value { + return $null + } -PassThru | + Add-Member -Force -MemberType 'ScriptMethod' -Name 'AssertProperties' -Value { + return + } + } + } + + It 'Should not call method Modify()' { + InModuleScope -ScriptBlock { + Set-StrictMode -Version 1.0 + + $script:mockInstance.Set() + + $script:methodModifyCallCount | Should -Be 0 + } + } + } + + Context 'When the system is not in the desired state' { + BeforeAll { + InModuleScope -ScriptBlock { + Set-StrictMode -Version 1.0 + + $script:mockInstance | + # Mock method Compare() which is called by the base method Set() + Add-Member -Force -MemberType 'ScriptMethod' -Name 'Compare' -Value { + return @( + @{ + Property = 'Port' + ExpectedValue = 5000 + ActualValue = 5985 + } + ) + } -PassThru | + Add-Member -Force -MemberType 'ScriptMethod' -Name 'AssertProperties' -Value { + return + } + } + } + + It 'Should call method Modify()' { + InModuleScope -ScriptBlock { + Set-StrictMode -Version 1.0 + + $script:mockInstance.Set() + + $script:methodModifyCallCount | Should -Be 1 + } + } + } +} + +Describe 'WSManListener\Test()' -Tag 'Test' { + BeforeAll { + InModuleScope -ScriptBlock { + Set-StrictMode -Version 1.0 + + $script:mockInstance = [WSManListener] @{ + Transport = 'HTTPS' + Port = 5986 + CertificateThumbprint = '74FA31ADEA7FDD5333CED10910BFA6F665A1F2FC' + HostName = $([System.Net.Dns]::GetHostByName($ENV:computerName).Hostname) + Ensure = 'Present' + } + } + } + + Context 'When the system is in the desired state' { + BeforeAll { + InModuleScope -ScriptBlock { + Set-StrictMode -Version 1.0 + + $script:mockInstance | + # Mock method Compare() which is called by the base method Set() + Add-Member -Force -MemberType 'ScriptMethod' -Name 'Compare' -Value { + return $null + } -PassThru | + Add-Member -Force -MemberType 'ScriptMethod' -Name 'AssertProperties' -Value { + return + } + } + + It 'Should return $true' { + InModuleScope -ScriptBlock { + Set-StrictMode -Version 1.0 + + $script:mockInstance.Test() | Should -BeTrue + } + } + } + } + + Context 'When the system is not in the desired state' { + BeforeAll { + InModuleScope -ScriptBlock { + Set-StrictMode -Version 1.0 + + $script:mockInstance | + # Mock method Compare() which is called by the base method Set() + Add-Member -Force -MemberType 'ScriptMethod' -Name 'Compare' -Value { + return @( + @{ + Property = 'Port' + ExpectedValue = 5986 + ActualValue = 443 + }) + } -PassThru | + Add-Member -Force -MemberType 'ScriptMethod' -Name 'AssertProperties' -Value { + return + } + } + } + + It 'Should return $false' { + InModuleScope -ScriptBlock { + Set-StrictMode -Version 1.0 + + $script:mockInstance.Test() | Should -BeFalse + } + } + } +} + +Describe 'WSManListener\GetCurrentState()' -Tag 'HiddenMember' { + Context 'When object is missing in the current state' { + BeforeAll { + InModuleScope -ScriptBlock { + Set-StrictMode -Version 1.0 + + # $script:mockInstance = [DnsServerCache] @{ + # DnsServer = 'localhost' + # } + } + + # Mock -CommandName Get-DnsServerCache + } + } + + It 'Should return the correct values' { + InModuleScope -ScriptBlock { + Set-StrictMode -Version 1.0 + + # $currentState = $script:mockInstance.GetCurrentState( + # @{ + # DnsServer = 'localhost' + # } + # ) + + # $currentState.DnsServer | Should -Be 'localhost' + # $currentState.IgnorePolicies | Should -BeFalse + # $currentState.LockingPercent | Should -Be 0 + # $currentState.MaxKBSize | Should -Be 0 + # $currentState.MaxNegativeTtl | Should -BeNullOrEmpty + # $currentState.MaxTtl | Should -BeNullOrEmpty + # $currentState.EnablePollutionProtection | Should -BeFalse + # $currentState.StoreEmptyAuthenticationResponse | Should -BeFalse + } + + #Should -Invoke -CommandName Get-DnsServerCache -Exactly -Times 1 -Scope It + } + + Context 'When the object is present in the current state' { + BeforeAll { + InModuleScope -ScriptBlock { + Set-StrictMode -Version 1.0 + + # $script:mockInstance = [DnsServerCache] @{ + # DnsServer = 'SomeHost' + # } + # } + + # Mock -CommandName Get-DnsServerCache -MockWith { + # return New-CimInstance -ClassName 'DnsServerCache' -Namespace 'root/Microsoft/Windows/DNS' -ClientOnly -Property @{ + # IgnorePolicies = $true + # LockingPercent = 100 + # MaxKBSize = 0 + # MaxNegativeTtl = '00:15:00' + # MaxTtl = '1.00:00:00' + # EnablePollutionProtection = $true + # StoreEmptyAuthenticationResponse = $true + # } + # } + } + } + + It 'Should return the correct values' { + InModuleScope -ScriptBlock { + Set-StrictMode -Version 1.0 + + # $currentState = $script:mockInstance.GetCurrentState( + # @{ + # DnsServer = 'SomeHost' + # } + # ) + + # $currentState.DnsServer | Should -Be 'SomeHost' + # $currentState.IgnorePolicies | Should -BeTrue + # $currentState.LockingPercent | Should -Be 100 + # $currentState.MaxKBSize | Should -Be 0 + # $currentState.MaxNegativeTtl | Should -Be '00:15:00' + # $currentState.MaxTtl | Should -Be '1.00:00:00' + # $currentState.EnablePollutionProtection | Should -BeTrue + # $currentState.StoreEmptyAuthenticationResponse | Should -BeTrue + } + + #Should -Invoke -CommandName Get-DnsServerCache -Exactly -Times 1 -Scope It + } + } +} + +Describe 'WSManListener\Modify()' -Tag 'HiddenMember' { + Context 'When the system is not in the desired state' { + BeforeAll { + InModuleScope -ScriptBlock { + Set-StrictMode -Version 1.0 + + + } + } + } +} From 8222cbca60debbf235c3d04541fa714eb2006382 Mon Sep 17 00:00:00 2001 From: Daniel Hughes Date: Fri, 27 Sep 2024 17:10:09 +0100 Subject: [PATCH 026/134] Update modify to separate functions --- source/Classes/020.WSManListener.ps1 | 151 +++++++++++++-------------- 1 file changed, 72 insertions(+), 79 deletions(-) diff --git a/source/Classes/020.WSManListener.ps1 b/source/Classes/020.WSManListener.ps1 index 304091a..2b9c3a6 100644 --- a/source/Classes/020.WSManListener.ps1 +++ b/source/Classes/020.WSManListener.ps1 @@ -89,7 +89,7 @@ class WSManListener : ResourceBase [DscProperty()] [System.String] - $Hostname + $HostName [DscProperty(NotConfigurable)] [System.Boolean] @@ -189,120 +189,113 @@ class WSManListener : ResourceBase #> hidden [void] Modify([System.Collections.Hashtable] $properties) { - - $remove = $false - $create = $false - - $selectorSet = @{ - Transport = $this.Transport - Address = $this.Address - } - - Write-Verbose ('$properties.ContainsKey(''Ensure'') = {0}' -f $properties.ContainsKey('Ensure')) - Write-Verbose ('$properties.Ensure = {0}' -f $properties.Ensure) - Write-Verbose ('$this.Ensure = {0}' -f $this.Ensure) + #Write-Verbose ('$properties.ContainsKey(''Ensure'') = {0}' -f $properties.ContainsKey('Ensure')) + #Write-Verbose ('$properties.Ensure = {0}' -f $properties.Ensure) + #Write-Verbose ('$this.Ensure = {0}' -f $this.Ensure) 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 - $remove = $true + Write-Verbose -Message ($this.localizedData.ListenerExistsRemoveMessage -f $this.Transport, $this.Port) + + $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 - $create = $true + Write-Verbose -Message ($this.localizedData.CreatingListenerMessage -f $this.Transport, $this.Port) + + $this.NewInstance() } else { # Resource exists but one or more properties are not in the desired state - $remove = $true - $create = $true + Write-Verbose -Message ($this.localizedData.ModifyingListenerMessage -f $this.Transport, $this.Port) + + $this.SetInstance($properties) } + } - if ($remove) - { - Write-Verbose -Message ($this.localizedData.ListenerExistsRemoveMessage -f $this.Transport, $this.Port) + [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) + {} + + hidden [void] NewInstance() + { + $selectorSet = @{ + Transport = $this.Transport + Address = $this.Address + } - Remove-WSManInstance -ResourceURI 'winrm/config/Listener' -SelectorSet @selectorSet + $valueSet = @{ + Port = $this.Port } - if ($create) + + if ($this.Transport -eq [WSManTransport]::HTTPS) { - $valueSet = @{ - Port = $properties.Port - } + $findCertificateParams = $this | Get-DscProperty -Attribute @('Optional') -ExcludeName @('Port', 'Address') + $certificate = Find-Certificate @findCertificateParams + [System.String] $thumbprint = $certificate.Thumbprint - if ($this.Transport -eq [WSManTransport]::HTTPS) + if ($thumbprint) { - $findCertificateParams = Get-DscProperty -Attribute @('Optional') -ExcludeName @('Port', 'Address') + $valueSet.CertificateThumbprint = $thumbprint - $certificate = Find-Certificate @findCertificateParams - [System.String] $thumbprint = $certificate.thumbprint - - if ($thumbprint) + if ([System.String]::IsNullOrEmpty($this.Hostname)) { - $valueSet.CertificateThumbprint = $thumbprint - - if ([System.String]::IsNullOrEmpty($properties.Hostname)) - { - $valueSet.HostName = [System.Net.Dns]::GetHostByName($env:COMPUTERNAME).Hostname - } - else - { - $valueSet.HostName = $properties.HostName - } + $valueSet.HostName = [System.Net.Dns]::GetHostByName($env:COMPUTERNAME).Hostname } else { - # TODO: Extract this to assert or into a parameter set - # 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 + $valueSet.HostName = $this.HostName + } } - - - Write-Verbose -Message ($this.localizedData.CreatingListenerMessage -f $this.Transport, $this.Port) - - New-WSManInstance -ResourceURI 'winrm/config/Listener' -SelectorSet @selectorSet -ValueSet @valueSet -ErrorAction Stop + 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 } - [System.Boolean] Test() + hidden [void] RemoveInstance() { - # Call the base method to test all of the properties that should be enforced. - return ([ResourceBase] $this).Test() + $selectorSet = @{ + Transport = $this.Transport + Address = $this.Address + } + + Remove-WSManInstance -ResourceURI 'winrm/config/Listener' -SelectorSet $selectorSet } - <# - Base method Assert() call this method with the properties that was assigned - a value. - #> - hidden [void] AssertProperties([System.Collections.Hashtable] $properties) + hidden [void] SetInstance([System.Collections.Hashtable] $properties) { - # if ($null -ne $properties.SubjectFormat) - # { - # $errorMessage = $this.localizedData.SubjectFormatMustBeValid + $selectorSet = @{ + Transport = $this.Transport + Address = $this.Address + } - # if ($properties.SubjectFormat -inotin ('Both', 'FQDNOnly', 'NameOnly')) - # { - # New-InvalidArgumentException -ArgumentName 'SubjectFormat' -Message $errorMessage - # } - # } + $valueSet = @{} - # # The properties MaximumFiles and MaximumRolloverFiles are mutually exclusive. - # $assertBoundParameterParameters = @{ - # BoundParameterList = $properties - # MutuallyExclusiveList1 = @( - # 'MaximumFiles' - # ) - # MutuallyExclusiveList2 = @( - # 'MaximumRolloverFiles' - # ) - # } + foreach ($property in $properties) { + $valueSet.$property.Key = $property.Value + } - # Assert-BoundParameter @assertBoundParameterParameters + Set-WSManInstance -ResourceURI 'winrm/config/Listener' -SelectorSet $selectorSet -ValueSet $valueSet } } From 9bc57ee35a946ddbfba54b5ae34c99b939b12e5d Mon Sep 17 00:00:00 2001 From: Daniel Hughes Date: Fri, 27 Sep 2024 17:10:20 +0100 Subject: [PATCH 027/134] Add new string --- source/en-US/WSManListener.strings.psd1 | 1 + 1 file changed, 1 insertion(+) diff --git a/source/en-US/WSManListener.strings.psd1 b/source/en-US/WSManListener.strings.psd1 index 3120c46..48597d1 100644 --- a/source/en-US/WSManListener.strings.psd1 +++ b/source/en-US/WSManListener.strings.psd1 @@ -14,4 +14,5 @@ ConvertFrom-StringData @' ListenerExistsRemoveMessage = Removing {0} Listener on port {1} (WSML0001). ListenerCreateFailNoCertError = Failed to create {0} Listener on port {1} because an applicable certificate could not be found (WSM0002). CreatingListenerMessage = Creating {0} Listener on port {1} (WSML0003). + ModifyingListenerMessage = Modifying {0} Listener on port {1} (WSML0004). '@ From 4dfb52a28bdfe7b4f40633d670a897966aa57b76 Mon Sep 17 00:00:00 2001 From: Daniel Hughes Date: Fri, 27 Sep 2024 17:10:26 +0100 Subject: [PATCH 028/134] Update tests --- tests/Unit/Classes/WSManListener.Tests.ps1 | 469 ++++++++++++++++++--- 1 file changed, 412 insertions(+), 57 deletions(-) diff --git a/tests/Unit/Classes/WSManListener.Tests.ps1 b/tests/Unit/Classes/WSManListener.Tests.ps1 index a629025..f45123a 100644 --- a/tests/Unit/Classes/WSManListener.Tests.ps1 +++ b/tests/Unit/Classes/WSManListener.Tests.ps1 @@ -254,7 +254,16 @@ Describe 'WSManListener\Get()' -Tag 'Get' { $currentState.Ensure | Should -Be 'Absent' $currentState.Reasons | Should -HaveCount 1 $currentState.Reasons[0].Code | Should -Be 'WSManListener:WSManListener:Transport' - $currentState.Reasons[0].Phrase | Should -Be 'The property Transport should be "HTTP", but was null' + + # PS6+ treats empty enum as null + if ($PSVersionTable.PSVersion.Major -gt 5) + { + $currentState.Reasons[0].Phrase | Should -Be 'The property Transport should be "HTTP", but was null' + } + else + { + $currentState.Reasons[0].Phrase | Should -Be 'The property Transport should be "HTTP", but was ""' + } } } } @@ -490,7 +499,7 @@ Describe 'WSManListener\Test()' -Tag 'Test' { Transport = 'HTTPS' Port = 5986 CertificateThumbprint = '74FA31ADEA7FDD5333CED10910BFA6F665A1F2FC' - HostName = $([System.Net.Dns]::GetHostByName($ENV:computerName).Hostname) + Hostname = $([System.Net.Dns]::GetHostByName($ENV:computerName).Hostname) Ensure = 'Present' } } @@ -558,95 +567,441 @@ Describe 'WSManListener\GetCurrentState()' -Tag 'HiddenMember' { InModuleScope -ScriptBlock { Set-StrictMode -Version 1.0 - # $script:mockInstance = [DnsServerCache] @{ - # DnsServer = 'localhost' - # } + $script:mockInstance = [WSManListener] @{ + Transport = 'HTTP' + Port = 5985 + Address = '*' + Ensure = 'Present' + } } - # Mock -CommandName Get-DnsServerCache + Mock -CommandName Get-Listener + } + + It 'Should return the correct values' { + InModuleScope -ScriptBlock { + Set-StrictMode -Version 1.0 + + $currentState = $script:mockInstance.GetCurrentState( + @{ + Transport = 'HTTP' + Ensure = [Ensure]::Present + } + ) + + $currentState.Transport | Should -BeNullOrEmpty + $currentState.Port | Should -BeNullOrEmpty + $currentState.Address | Should -BeNullOrEmpty + $currentState.Issuer | Should -BeNullOrEmpty + $currentState.CertificateThumbprint | Should -BeNullOrEmpty + $currentState.Hostname | Should -BeNullOrEmpty + $currentState.Enabled | Should -BeFalse + $currentState.URLPrefix | Should -BeNullOrEmpty + } + + Should -Invoke -CommandName Get-Listener -Exactly -Times 1 -Scope It } } - It 'Should return the correct values' { - InModuleScope -ScriptBlock { - Set-StrictMode -Version 1.0 + Context 'When the object is present in the current state' { + Context 'When ''Port'' and ''Address'' are supplied for HTTP Transport' { + BeforeAll { + InModuleScope -ScriptBlock { + Set-StrictMode -Version 1.0 + + $script:mockInstance = [WSManListener] @{ + Transport = 'HTTP' + Port = 5985 + Address = '*' + Ensure = 'Present' + } + } + + Mock -CommandName Get-Listener -MockWith { + return @{ + Transport = 'HTTP' + Port = [System.UInt16] 5985 + Address = '*' + + CertificateThumbprint = $null + Hostname = $([System.Net.Dns]::GetHostByName($ENV:computerName).Hostname) + + Enabled = $true + URLPrefix = 'wsman' + } + } + } + + It 'Should return the correct values' { + InModuleScope -ScriptBlock { + Set-StrictMode -Version 1.0 + + $currentState = $script:mockInstance.GetCurrentState( + @{ + Transport = 'HTTP' + Ensure = [Ensure]::Present + } + ) + + $currentState.Transport | Should -Be 'HTTP' + $currentState.Port | Should -Be 5985 + $currentState.Address | Should -Be '*' + $currentState.Issuer | Should -BeNullOrEmpty + $currentState.CertificateThumbprint | Should -BeNullOrEmpty + $currentState.Hostname | Should -Be $([System.Net.Dns]::GetHostByName($ENV:computerName).Hostname) + $currentState.Enabled | Should -BeTrue + $currentState.URLPrefix | Should -Be 'wsman' + } + + Should -Invoke -CommandName Get-Listener -Exactly -Times 1 -Scope It + } + } + + Context 'When ''Port'' and ''Address'' are not supplied for HTTP Transport' { + BeforeAll { + InModuleScope -ScriptBlock { + Set-StrictMode -Version 1.0 + + $script:mockInstance = [WSManListener] @{ + Transport = 'HTTP' + Ensure = 'Present' + } + } + + Mock -CommandName Get-Listener -MockWith { + return @{ + Transport = 'HTTP' + Port = [System.UInt16] 5985 + Address = '*' + + CertificateThumbprint = $null + Hostname = $([System.Net.Dns]::GetHostByName($ENV:computerName).Hostname) + + Enabled = $true + URLPrefix = 'wsman' + } + } - # $currentState = $script:mockInstance.GetCurrentState( - # @{ - # DnsServer = 'localhost' - # } - # ) + Mock -CommandName Get-DefaultPort -MockWith { return [System.UInt16] 5985 } + } + + It 'Should return the correct values' { + InModuleScope -ScriptBlock { + Set-StrictMode -Version 1.0 + + $currentState = $script:mockInstance.GetCurrentState( + @{ + Transport = 'HTTP' + Ensure = [Ensure]::Present + } + ) - # $currentState.DnsServer | Should -Be 'localhost' - # $currentState.IgnorePolicies | Should -BeFalse - # $currentState.LockingPercent | Should -Be 0 - # $currentState.MaxKBSize | Should -Be 0 - # $currentState.MaxNegativeTtl | Should -BeNullOrEmpty - # $currentState.MaxTtl | Should -BeNullOrEmpty - # $currentState.EnablePollutionProtection | Should -BeFalse - # $currentState.StoreEmptyAuthenticationResponse | Should -BeFalse + $currentState.Transport | Should -Be 'HTTP' + $currentState.Port | Should -Be 5985 + $currentState.Address | Should -Be '*' + $currentState.Issuer | Should -BeNullOrEmpty + $currentState.CertificateThumbprint | Should -BeNullOrEmpty + $currentState.Hostname | Should -Be $([System.Net.Dns]::GetHostByName($ENV:computerName).Hostname) + $currentState.Enabled | Should -BeTrue + $currentState.URLPrefix | Should -Be 'wsman' + } + + Should -Invoke -CommandName Get-Listener -Exactly -Times 1 -Scope It + Should -Invoke -CommandName Get-DefaultPort -Exactly -Times 1 -Scope It + } } - #Should -Invoke -CommandName Get-DnsServerCache -Exactly -Times 1 -Scope It + Context 'When ''Port'' and ''Address'' are supplied for HTTPS Transport' { + BeforeAll { + InModuleScope -ScriptBlock { + Set-StrictMode -Version 1.0 + + $script:mockInstance = [WSManListener] @{ + Transport = 'HTTPS' + Port = 5986 + Address = '*' + Ensure = 'Present' + } + } + + Mock -CommandName Get-Listener -MockWith { + return @{ + Transport = 'HTTPS' + Port = [System.UInt16] 5986 + Address = '*' + + CertificateThumbprint = '74FA31ADEA7FDD5333CED10910BFA6F665A1F2FC' + Hostname = $([System.Net.Dns]::GetHostByName($ENV:computerName).Hostname) + + Enabled = $true + URLPrefix = 'wsman' + } + } + + Mock -CommandName Find-Certificate -MockWith { return @{ Issuer = 'CN=CONTOSO.COM Issuing CA, DC=CONTOSO, DC=COM' } } + } + + It 'Should return the correct values' { + InModuleScope -ScriptBlock { + Set-StrictMode -Version 1.0 + + $currentState = $script:mockInstance.GetCurrentState( + @{ + Transport = 'HTTPS' + Ensure = [Ensure]::Present + } + ) + + $currentState.Transport | Should -Be 'HTTPS' + $currentState.Port | Should -Be 5986 + $currentState.Address | Should -Be '*' + $currentState.Issuer | Should -Be 'CN=CONTOSO.COM Issuing CA, DC=CONTOSO, DC=COM' + $currentState.CertificateThumbprint | Should -Be '74FA31ADEA7FDD5333CED10910BFA6F665A1F2FC' + $currentState.Hostname | Should -Be $([System.Net.Dns]::GetHostByName($ENV:computerName).Hostname) + $currentState.Enabled | Should -BeTrue + $currentState.URLPrefix | Should -Be 'wsman' + } + + Should -Invoke -CommandName Get-Listener -Exactly -Times 1 -Scope It + Should -Invoke -CommandName Find-Certificate -Exactly -Times 1 -Scope It + } + } } +} - Context 'When the object is present in the current state' { +Describe 'WSManListener\Modify()' -Tag 'HiddenMember' { + Context 'When the system is not in the desired state' { BeforeAll { InModuleScope -ScriptBlock { Set-StrictMode -Version 1.0 - # $script:mockInstance = [DnsServerCache] @{ - # DnsServer = 'SomeHost' - # } - # } - - # Mock -CommandName Get-DnsServerCache -MockWith { - # return New-CimInstance -ClassName 'DnsServerCache' -Namespace 'root/Microsoft/Windows/DNS' -ClientOnly -Property @{ - # IgnorePolicies = $true - # LockingPercent = 100 - # MaxKBSize = 0 - # MaxNegativeTtl = '00:15:00' - # MaxTtl = '1.00:00:00' - # EnablePollutionProtection = $true - # StoreEmptyAuthenticationResponse = $true - # } - # } + $script:mockInstance = [WSManListener] @{ + Transport = 'HTTP' + Ensure = 'Present' + } | + # Mock method NewInstance which is called by the case method Modify(). + Add-Member -Force -MemberType 'ScriptMethod' -Name 'NewInstance' -Value { + $script:methodNewInstanceCallCount += 1 + } -PassThru + | + # Mock method RemoveInstance which is called by the case method Modify(). + Add-Member -Force -MemberType 'ScriptMethod' -Name 'RemoveInstance' -Value { + $script:methodRemoveInstanceCallCount += 1 + } -PassThru + | + # Mock method SetInstance which is called by the case method Modify(). + Add-Member -Force -MemberType 'ScriptMethod' -Name 'SetInstance' -Value { + $script:methodSetInstanceCallCount += 1 + } -PassThru } } - It 'Should return the correct values' { + BeforeEach { InModuleScope -ScriptBlock { Set-StrictMode -Version 1.0 - # $currentState = $script:mockInstance.GetCurrentState( - # @{ - # DnsServer = 'SomeHost' - # } - # ) + $script:methodNewInstanceCallCount = 0 + $script:methodRemoveInstanceCallCount = 0 + $script:methodSetInstanceCallCount = 0 + } + } + + Context 'When the resource does not exist' { + It 'Should call method NewInstance()' { + InModuleScope -ScriptBlock { + Set-StrictMode -Version 1.0 - # $currentState.DnsServer | Should -Be 'SomeHost' - # $currentState.IgnorePolicies | Should -BeTrue - # $currentState.LockingPercent | Should -Be 100 - # $currentState.MaxKBSize | Should -Be 0 - # $currentState.MaxNegativeTtl | Should -Be '00:15:00' - # $currentState.MaxTtl | Should -Be '1.00:00:00' - # $currentState.EnablePollutionProtection | Should -BeTrue - # $currentState.StoreEmptyAuthenticationResponse | Should -BeTrue + $mockProperties = @{ + Transport = 'HTTP' + Ensure = 'Present' + } + + $script:mockInstance.Modify($mockProperties) + + $script:methodNewInstanceCallCount | Should -Be 1 + } } + } + + Context 'When the resource does exist' { + It 'Should call method RemoveInstance()' { + InModuleScope -ScriptBlock { + Set-StrictMode -Version 1.0 - #Should -Invoke -CommandName Get-DnsServerCache -Exactly -Times 1 -Scope It + $script:mockInstance.Ensure = 'Absent' + + $mockProperties = @{ + Transport = 'HTTP' + Ensure = 'Absent' + } + + $script:mockInstance.Modify($mockProperties) + + $script:methodRemoveInstanceCallCount | Should -Be 1 + } + } + } + + Context 'When the resource does exist but properties are incorrect' { + It 'Should call method SetInstance()' { + InModuleScope -ScriptBlock { + Set-StrictMode -Version 1.0 + + $script:mockInstance.Ensure = 'Absent' + + $mockProperties = @{ + Transport = 'HTTP' + Port = 5000 + } + + $script:mockInstance.Modify($mockProperties) + + $script:methodSetInstanceCallCount | Should -Be 1 + } + } } } } -Describe 'WSManListener\Modify()' -Tag 'HiddenMember' { - Context 'When the system is not in the desired state' { +Describe 'WSManListener\NewInstance()' -Tag 'HiddenMember' { + BeforeAll { + Mock -CommandName New-WSManInstance + } + + Context 'When creating a HTTP Transport' { + BeforeAll { + InModuleScope -ScriptBlock { + Set-StrictMode -Version 1.0 + + $script:mockInstance = [WSManListener] @{ + Transport = 'HTTP' + Port = 5985 + Address = '*' + Ensure = 'Present' + } + } + } + + It 'Should call the correct mock' { + InModuleScope -ScriptBlock { + Set-StrictMode -Version 1.0 + + $script:mockInstance.NewInstance() + } + + Should -Invoke -CommandName New-WSManInstance -Exactly -Times 1 -Scope It + } + } + + Context 'When creating a HTTPS Transport' { BeforeAll { + Mock -CommandName Get-DscProperty + } + + BeforeEach { InModuleScope -ScriptBlock { Set-StrictMode -Version 1.0 + $script:mockInstance = [WSManListener] @{ + Transport = 'HTTPS' + Port = 5986 + Address = '*' + Ensure = 'Present' + } + } + } + Context 'When the certificate thumbprint exists' { + BeforeAll { + Mock -CommandName Find-Certificate -MockWith { + @{ Thumbprint = '74FA31ADEA7FDD5333CED10910BFA6F665A1F2FC' } + } + } + + Context 'When the hostname is provided' { + It 'Should call the correct mocks' { + InModuleScope -ScriptBlock { + Set-StrictMode -Version 1.0 + + $script:mockInstance.HostName = 'somehost' + + $script:mockInstance.NewInstance() + } + + Should -Invoke -CommandName Get-DscProperty -Exactly -Times 1 -Scope It + Should -Invoke -CommandName Find-Certificate -Exactly -Times 1 -Scope It + Should -Invoke -CommandName New-WSManInstance -ParameterFilter { + $ValueSet.HostName -eq 'somehost' + } -Exactly -Times 1 -Scope It + } + } + + Context 'When the hostname is not provided' { + It 'Should call the correct mock' { + InModuleScope -ScriptBlock { + Set-StrictMode -Version 1.0 + + $script:mockInstance.NewInstance() + } + + Should -Invoke -CommandName Get-DscProperty -Exactly -Times 1 -Scope It + Should -Invoke -CommandName Find-Certificate -Exactly -Times 1 -Scope It + Should -Invoke -CommandName New-WSManInstance -ParameterFilter { + $ValueSet.HostName -eq [System.Net.Dns]::GetHostByName($env:COMPUTERNAME).Hostname + } -Exactly -Times 1 -Scope It + } } } + + Context 'When the certificate thumbprint does not exist' { + BeforeAll { + Mock -CommandName Find-Certificate + } + + It 'Should throw the correct exception' { + InModuleScope -ScriptBlock { + Set-StrictMode -Version 1.0 + + $mockErrorMessage = Get-InvalidArgumentRecord -Message ( + $script:mockInstance.localizedData.ListenerCreateFailNoCertError -f $script:mockInstance.Transport, $script:mockInstance.Port + ) -Argument 'Issuer' + + { $script:mockInstance.NewInstance() } | Should -Throw -ExpectedMessage $mockErrorMessage.Exception.Message + } + + Should -Invoke -CommandName New-WSManInstance -Exactly -Times 0 -Scope It + Should -Invoke -CommandName Get-DscProperty -Exactly -Times 1 -Scope It + Should -Invoke -CommandName Find-Certificate -Exactly -Times 1 -Scope It + } + } + } +} + +Describe 'WSManListener\RemoveInstance()' -Tag 'HiddenMember' { + BeforeAll { + InModuleScope -ScriptBlock { + Set-StrictMode -Version 1.0 + + $script:mockInstance = [WSManListener] @{ + Transport = 'HTTPS' + Port = 5986 + Address = '*' + Ensure = 'Present' + } + } + + Mock -CommandName Remove-WSManInstance + } + + It 'Should call the correct mock' { + InModuleScope -ScriptBlock { + Set-StrictMode -Version 1.0 + + $script:mockInstance.RemoveInstance() + } + + Should -Invoke -CommandName Remove-WSManInstance -Exactly -Times 1 -Scope It } } + +#Describe 'WSManListener\SetInstance()' -Tag 'HiddenMember' {} From 336625c190062a648f660282a5b9884547a18775 Mon Sep 17 00:00:00 2001 From: Daniel Hughes Date: Fri, 27 Sep 2024 17:16:24 +0100 Subject: [PATCH 029/134] Update syntax error --- tests/Unit/Classes/WSManListener.Tests.ps1 | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/tests/Unit/Classes/WSManListener.Tests.ps1 b/tests/Unit/Classes/WSManListener.Tests.ps1 index f45123a..84c3a6e 100644 --- a/tests/Unit/Classes/WSManListener.Tests.ps1 +++ b/tests/Unit/Classes/WSManListener.Tests.ps1 @@ -782,13 +782,11 @@ Describe 'WSManListener\Modify()' -Tag 'HiddenMember' { # Mock method NewInstance which is called by the case method Modify(). Add-Member -Force -MemberType 'ScriptMethod' -Name 'NewInstance' -Value { $script:methodNewInstanceCallCount += 1 - } -PassThru - | + } -PassThru | # Mock method RemoveInstance which is called by the case method Modify(). Add-Member -Force -MemberType 'ScriptMethod' -Name 'RemoveInstance' -Value { $script:methodRemoveInstanceCallCount += 1 - } -PassThru - | + } -PassThru | # Mock method SetInstance which is called by the case method Modify(). Add-Member -Force -MemberType 'ScriptMethod' -Name 'SetInstance' -Value { $script:methodSetInstanceCallCount += 1 @@ -897,7 +895,7 @@ Describe 'WSManListener\NewInstance()' -Tag 'HiddenMember' { BeforeAll { Mock -CommandName Get-DscProperty } - + BeforeEach { InModuleScope -ScriptBlock { Set-StrictMode -Version 1.0 From a9fc7939312ad60f7f62de89392355f7832cce52 Mon Sep 17 00:00:00 2001 From: Daniel Hughes Date: Fri, 27 Sep 2024 17:35:19 +0100 Subject: [PATCH 030/134] Remove empty values --- source/Classes/020.WSManListener.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/Classes/020.WSManListener.ps1 b/source/Classes/020.WSManListener.ps1 index 2b9c3a6..15a23c2 100644 --- a/source/Classes/020.WSManListener.ps1 +++ b/source/Classes/020.WSManListener.ps1 @@ -243,7 +243,7 @@ class WSManListener : ResourceBase if ($this.Transport -eq [WSManTransport]::HTTPS) { - $findCertificateParams = $this | Get-DscProperty -Attribute @('Optional') -ExcludeName @('Port', 'Address') + $findCertificateParams = $this | Get-DscProperty -Attribute @('Optional') -ExcludeName @('Port', 'Address') -HasValue $certificate = Find-Certificate @findCertificateParams [System.String] $thumbprint = $certificate.Thumbprint From f1f4a074cabda8d7b18affa95a7ae2676c78d350 Mon Sep 17 00:00:00 2001 From: Daniel Hughes Date: Thu, 24 Oct 2024 15:44:47 +0100 Subject: [PATCH 031/134] Use Transport Enum --- source/Private/Get-DefaultPort.ps1 | 3 +-- source/Private/Get-Listener.ps1 | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/source/Private/Get-DefaultPort.ps1 b/source/Private/Get-DefaultPort.ps1 index 711b38f..50a498d 100644 --- a/source/Private/Get-DefaultPort.ps1 +++ b/source/Private/Get-DefaultPort.ps1 @@ -15,8 +15,7 @@ function Get-DefaultPort param ( [Parameter(Mandatory = $true)] - [ValidateSet('HTTP', 'HTTPS')] - [System.String] + [WSManTransport] $Transport, [Parameter()] diff --git a/source/Private/Get-Listener.ps1 b/source/Private/Get-Listener.ps1 index 380d856..172d85e 100644 --- a/source/Private/Get-Listener.ps1 +++ b/source/Private/Get-Listener.ps1 @@ -12,8 +12,7 @@ function Get-Listener param ( [Parameter(Mandatory = $true)] - [ValidateSet('HTTP', 'HTTPS')] - [System.String] + [WSManTransport] $Transport ) From 968d2c2c0b8d11643d6e9221174da787888c797a Mon Sep 17 00:00:00 2001 From: Daniel Hughes Date: Thu, 24 Oct 2024 15:45:00 +0100 Subject: [PATCH 032/134] Remove build.psd1 --- source/Build.psd1 | 5 ----- 1 file changed, 5 deletions(-) delete mode 100644 source/Build.psd1 diff --git a/source/Build.psd1 b/source/Build.psd1 deleted file mode 100644 index 48730bf..0000000 --- a/source/Build.psd1 +++ /dev/null @@ -1,5 +0,0 @@ -@{ - Path = 'WSManDsc.psd1' -} -# Waiting for ModuleBuilder to do away with this file -# when all parameters are provided to the function From 0bcf61a867481ad182f3abd2cd2ddf86ef563b3a Mon Sep 17 00:00:00 2001 From: Daniel Hughes Date: Thu, 24 Oct 2024 15:45:35 +0100 Subject: [PATCH 033/134] Remove backtick --- source/Private/Find-Certificate.ps1 | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/source/Private/Find-Certificate.ps1 b/source/Private/Find-Certificate.ps1 index 22a7c8a..ba8cb9e 100644 --- a/source/Private/Find-Certificate.ps1 +++ b/source/Private/Find-Certificate.ps1 @@ -122,8 +122,7 @@ function Find-Certificate 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 + ($_.Extensions.EnhancedKeyUsages.FriendlyName -contains 'Server Authentication') -and ($_.Issuer -eq $Issuer) -and ($Hostname -in $_.DNSNameList.Unicode) -and ($_.Subject -eq $Subject) From 54aa751d23cdba66a54494bde642ec6e3d8a9c56 Mon Sep 17 00:00:00 2001 From: Daniel Hughes Date: Thu, 24 Oct 2024 16:13:51 +0100 Subject: [PATCH 034/134] Use Enum in compare --- source/Private/Get-DefaultPort.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/Private/Get-DefaultPort.ps1 b/source/Private/Get-DefaultPort.ps1 index 50a498d..15073f2 100644 --- a/source/Private/Get-DefaultPort.ps1 +++ b/source/Private/Get-DefaultPort.ps1 @@ -28,7 +28,7 @@ function Get-DefaultPort if (-not $Port) { # Set the default port because none was provided - if ($Transport -eq 'HTTP') + if ($Transport -eq [WSManTransport]::HTTP) { $Port = 5985 } From 0bb576603c8c3476f0464b24f164b14ce5a21433 Mon Sep 17 00:00:00 2001 From: Daniel Hughes Date: Thu, 24 Oct 2024 16:14:18 +0100 Subject: [PATCH 035/134] Remove unused localized data --- source/en-US/WSManListener.strings.psd1 | 1 - 1 file changed, 1 deletion(-) diff --git a/source/en-US/WSManListener.strings.psd1 b/source/en-US/WSManListener.strings.psd1 index 48597d1..3120c46 100644 --- a/source/en-US/WSManListener.strings.psd1 +++ b/source/en-US/WSManListener.strings.psd1 @@ -14,5 +14,4 @@ ConvertFrom-StringData @' ListenerExistsRemoveMessage = Removing {0} Listener on port {1} (WSML0001). ListenerCreateFailNoCertError = Failed to create {0} Listener on port {1} because an applicable certificate could not be found (WSM0002). CreatingListenerMessage = Creating {0} Listener on port {1} (WSML0003). - ModifyingListenerMessage = Modifying {0} Listener on port {1} (WSML0004). '@ From 8a248aeda1efb1757052d3fe8533d882fcfc6ac6 Mon Sep 17 00:00:00 2001 From: Daniel Hughes Date: Thu, 24 Oct 2024 16:14:39 +0100 Subject: [PATCH 036/134] Revert to previous logic --- source/Classes/020.WSManListener.ps1 | 37 ++++++---------------- tests/Unit/Classes/WSManListener.Tests.ps1 | 7 ++-- 2 files changed, 12 insertions(+), 32 deletions(-) diff --git a/source/Classes/020.WSManListener.ps1 b/source/Classes/020.WSManListener.ps1 index 15a23c2..d807879 100644 --- a/source/Classes/020.WSManListener.ps1 +++ b/source/Classes/020.WSManListener.ps1 @@ -189,30 +189,21 @@ class WSManListener : ResourceBase #> hidden [void] Modify([System.Collections.Hashtable] $properties) { - #Write-Verbose ('$properties.ContainsKey(''Ensure'') = {0}' -f $properties.ContainsKey('Ensure')) - #Write-Verbose ('$properties.Ensure = {0}' -f $properties.Ensure) - #Write-Verbose ('$this.Ensure = {0}' -f $this.Ensure) - 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 - Write-Verbose -Message ($this.localizedData.ListenerExistsRemoveMessage -f $this.Transport, $this.Port) - $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 - Write-Verbose -Message ($this.localizedData.CreatingListenerMessage -f $this.Transport, $this.Port) - $this.NewInstance() } else { # Resource exists but one or more properties are not in the desired state - Write-Verbose -Message ($this.localizedData.ModifyingListenerMessage -f $this.Transport, $this.Port) - - $this.SetInstance($properties) + $this.RemoveInstance() + $this.NewInstance() } } @@ -227,10 +218,14 @@ class WSManListener : ResourceBase a value. #> hidden [void] AssertProperties([System.Collections.Hashtable] $properties) - {} + { + #TODO: if HTTPS, CertificateThumbprint and Issuer, SubjectFormat, MatchAlternate, BaseDN are mutually exclusive + } hidden [void] NewInstance() { + Write-Verbose -Message ($this.localizedData.CreatingListenerMessage -f $this.Transport, $this.Port) + $selectorSet = @{ Transport = $this.Transport Address = $this.Address @@ -275,27 +270,13 @@ class WSManListener : ResourceBase hidden [void] RemoveInstance() { - $selectorSet = @{ - Transport = $this.Transport - Address = $this.Address - } - - Remove-WSManInstance -ResourceURI 'winrm/config/Listener' -SelectorSet $selectorSet - } + Write-Verbose -Message ($this.localizedData.ListenerExistsRemoveMessage -f $this.Transport, $this.Port) - hidden [void] SetInstance([System.Collections.Hashtable] $properties) - { $selectorSet = @{ Transport = $this.Transport Address = $this.Address } - $valueSet = @{} - - foreach ($property in $properties) { - $valueSet.$property.Key = $property.Value - } - - Set-WSManInstance -ResourceURI 'winrm/config/Listener' -SelectorSet $selectorSet -ValueSet $valueSet + Remove-WSManInstance -ResourceURI 'winrm/config/Listener' -SelectorSet $selectorSet } } diff --git a/tests/Unit/Classes/WSManListener.Tests.ps1 b/tests/Unit/Classes/WSManListener.Tests.ps1 index 84c3a6e..d5f6954 100644 --- a/tests/Unit/Classes/WSManListener.Tests.ps1 +++ b/tests/Unit/Classes/WSManListener.Tests.ps1 @@ -841,7 +841,7 @@ Describe 'WSManListener\Modify()' -Tag 'HiddenMember' { } Context 'When the resource does exist but properties are incorrect' { - It 'Should call method SetInstance()' { + It 'Should call method RemoveInstance() and NewInstance()' { InModuleScope -ScriptBlock { Set-StrictMode -Version 1.0 @@ -854,7 +854,8 @@ Describe 'WSManListener\Modify()' -Tag 'HiddenMember' { $script:mockInstance.Modify($mockProperties) - $script:methodSetInstanceCallCount | Should -Be 1 + $script:methodRemoveInstanceCallCount | Should -Be 1 + $script:methodNewInstanceCallCount | Should -Be 1 } } } @@ -1001,5 +1002,3 @@ Describe 'WSManListener\RemoveInstance()' -Tag 'HiddenMember' { Should -Invoke -CommandName Remove-WSManInstance -Exactly -Times 1 -Scope It } } - -#Describe 'WSManListener\SetInstance()' -Tag 'HiddenMember' {} From 828e05ff7c91e0846ba6215a47e8d29e0adccaa1 Mon Sep 17 00:00:00 2001 From: Daniel Hughes Date: Thu, 24 Oct 2024 16:31:39 +0100 Subject: [PATCH 037/134] Use correct DocGenerator Key --- build.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.yaml b/build.yaml index 4dd1f0f..33df85f 100644 --- a/build.yaml +++ b/build.yaml @@ -170,7 +170,7 @@ DscResource.DocGenerator: - '\*(.+?)\*' # Match Italic (asterisk) Publish_GitHub_Wiki_Content: Debug: false - Generate_Wiki_Content: + Generate_Markdown_For_DSC_Resources: MofResourceMetadata: Type: MofResource Category: Resources From 25fbebbf7686858f023b49b0720d6b2d63e2ef5b Mon Sep 17 00:00:00 2001 From: Daniel Hughes Date: Thu, 24 Oct 2024 16:43:48 +0100 Subject: [PATCH 038/134] Use SubjectFormatEnum --- source/Private/Find-Certificate.ps1 | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/source/Private/Find-Certificate.ps1 b/source/Private/Find-Certificate.ps1 index ba8cb9e..0cdbfb8 100644 --- a/source/Private/Find-Certificate.ps1 +++ b/source/Private/Find-Certificate.ps1 @@ -32,9 +32,8 @@ function Find-Certificate $Issuer, [Parameter()] - [ValidateSet('Both', 'FQDNOnly', 'NameOnly')] - [System.String] - $SubjectFormat = 'Both', + [WSManSubjectFormat] + $SubjectFormat = [WSManSubjectFormat]::Both, [Parameter()] [System.Boolean] @@ -64,7 +63,7 @@ function Find-Certificate else { # First try and find a certificate that is used to the FQDN of the machine - if ($SubjectFormat -in 'Both', 'FQDNOnly') + if ($SubjectFormat -in [WSManSubjectFormat]::Both, [WSManSubjectFormat]::FQDNOnly) { # Lookup the certificate using the FQDN of the machine if ([System.String]::IsNullOrEmpty($Hostname)) @@ -105,7 +104,7 @@ function Find-Certificate } # if } - if (-not $certificate -and ($SubjectFormat -in 'Both', 'NameOnly')) + 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 = $ENV:ComputerName From 84001195e987ee21c312759ba88d68e3244c246f Mon Sep 17 00:00:00 2001 From: Daniel Hughes Date: Thu, 24 Oct 2024 17:11:15 +0100 Subject: [PATCH 039/134] Set subjectFormat back to previous default --- source/Classes/020.WSManListener.ps1 | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/source/Classes/020.WSManListener.ps1 b/source/Classes/020.WSManListener.ps1 index d807879..8a13f07 100644 --- a/source/Classes/020.WSManListener.ps1 +++ b/source/Classes/020.WSManListener.ps1 @@ -111,14 +111,13 @@ class WSManListener : ResourceBase 'SubjectFormat' 'MatchAlternate' 'BaseDN' - ) - # # Set subject format to default value - # if (-not $this.SubjectFormat) - # { - # $this.SubjectFormat = 'Both' - # } + # Set subject format to default value + if (-not $this.SubjectFormat) + { + $this.SubjectFormat = [WSManSubjectFormat]::Both + } } [WSManListener] Get() From 11956cbac969c0e734db245f8c8aa756cababf1c Mon Sep 17 00:00:00 2001 From: Daniel Hughes Date: Thu, 24 Oct 2024 17:14:51 +0100 Subject: [PATCH 040/134] Match Mof resource defaults --- source/Classes/020.WSManListener.ps1 | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/source/Classes/020.WSManListener.ps1 b/source/Classes/020.WSManListener.ps1 index 8a13f07..2d5ac92 100644 --- a/source/Classes/020.WSManListener.ps1 +++ b/source/Classes/020.WSManListener.ps1 @@ -73,7 +73,7 @@ class WSManListener : ResourceBase [DscProperty()] [WSManSubjectFormat] - $SubjectFormat + $SubjectFormat = [WSManSubjectFormat]::Both [DscProperty()] [Nullable[System.Boolean]] @@ -112,12 +112,6 @@ class WSManListener : ResourceBase 'MatchAlternate' 'BaseDN' ) - - # Set subject format to default value - if (-not $this.SubjectFormat) - { - $this.SubjectFormat = [WSManSubjectFormat]::Both - } } [WSManListener] Get() From a45970c958b84d9be8567277c51dcb8d8f840d04 Mon Sep 17 00:00:00 2001 From: Daniel Hughes Date: Tue, 12 Nov 2024 13:35:20 +0000 Subject: [PATCH 041/134] Update formatting --- source/Private/Find-Certificate.ps1 | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/source/Private/Find-Certificate.ps1 b/source/Private/Find-Certificate.ps1 index 0cdbfb8..096875c 100644 --- a/source/Private/Find-Certificate.ps1 +++ b/source/Private/Find-Certificate.ps1 @@ -83,8 +83,7 @@ function Find-Certificate 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 + ($_.Extensions.EnhancedKeyUsages.FriendlyName -contains 'Server Authentication') -and ($_.Issuer -eq $Issuer) -and ($Hostname -in $_.DNSNameList.Unicode) -and ($_.Subject -eq $Subject) @@ -96,8 +95,7 @@ function Find-Certificate 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 + ($_.Extensions.EnhancedKeyUsages.FriendlyName -contains 'Server Authentication') -and ($_.Issuer -eq $Issuer) -and ($_.Subject -eq $Subject) } | Select-Object -First 1 @@ -133,9 +131,9 @@ function Find-Certificate 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 + ($_.Extensions.EnhancedKeyUsages.FriendlyName -contains 'Server Authentication') -and + $_.Issuer -eq $Issuer -and + $_.Subject -eq $Subject } | Select-Object -First 1 } # if } # if From 2566d065df1677b12db696ee43ce93587b6fc53e Mon Sep 17 00:00:00 2001 From: Daniel Hughes Date: Tue, 12 Nov 2024 13:35:55 +0000 Subject: [PATCH 042/134] Update string as port is not available --- source/en-US/WSManListener.strings.psd1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/en-US/WSManListener.strings.psd1 b/source/en-US/WSManListener.strings.psd1 index 3120c46..80e652d 100644 --- a/source/en-US/WSManListener.strings.psd1 +++ b/source/en-US/WSManListener.strings.psd1 @@ -11,7 +11,7 @@ ConvertFrom-StringData @' # None ## Strings directly used by the derived class WSManListener. - ListenerExistsRemoveMessage = Removing {0} Listener on port {1} (WSML0001). + ListenerExistsRemoveMessage = Removing {0} Listener on address {1} (WSML0001). ListenerCreateFailNoCertError = Failed to create {0} Listener on port {1} because an applicable certificate could not be found (WSM0002). CreatingListenerMessage = Creating {0} Listener on port {1} (WSML0003). '@ From 375f7232e73a95c8ebc47560d5baacf960e12ea3 Mon Sep 17 00:00:00 2001 From: Daniel Hughes Date: Tue, 12 Nov 2024 13:37:22 +0000 Subject: [PATCH 043/134] Update resource from testing --- source/Classes/020.WSManListener.ps1 | 49 +++++++++++++++++----- tests/Unit/Classes/WSManListener.Tests.ps1 | 20 ++++++--- 2 files changed, 53 insertions(+), 16 deletions(-) diff --git a/source/Classes/020.WSManListener.ps1 b/source/Classes/020.WSManListener.ps1 index 2d5ac92..d1a2942 100644 --- a/source/Classes/020.WSManListener.ps1 +++ b/source/Classes/020.WSManListener.ps1 @@ -3,6 +3,22 @@ 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. @@ -139,6 +155,7 @@ class WSManListener : ResourceBase $this.Address = '*' } + $state = @{} $getCurrentStateResult = Get-Listener @getParameters @@ -161,11 +178,6 @@ class WSManListener : ResourceBase $state.Issuer = (Find-Certificate -CertificateThumbprint $getCurrentStateResult.CertificateThumbprint).Issuer } } - else - { - $state = @{} - } - return $state } @@ -192,7 +204,7 @@ class WSManListener : ResourceBase # Ensure was not in the desired state so the resource should be created $this.NewInstance() } - else + elseif ($this.Ensure -eq [Ensure]::Present) { # Resource exists but one or more properties are not in the desired state $this.RemoveInstance() @@ -212,7 +224,19 @@ class WSManListener : ResourceBase #> hidden [void] AssertProperties([System.Collections.Hashtable] $properties) { - #TODO: if HTTPS, CertificateThumbprint and Issuer, SubjectFormat, MatchAlternate, BaseDN are mutually exclusive + $assertBoundParameterParameters = @{ + BoundParameterList = $properties + MutuallyExclusiveList1 = @( + 'Issuer' + 'BaseDN' + ) + MutuallyExclusiveList2 = @( + 'CertificateThumbprint' + 'Hostname' + ) + } + + Assert-BoundParameter @assertBoundParameterParameters } hidden [void] NewInstance() @@ -263,11 +287,16 @@ class WSManListener : ResourceBase hidden [void] RemoveInstance() { - Write-Verbose -Message ($this.localizedData.ListenerExistsRemoveMessage -f $this.Transport, $this.Port) + Write-Verbose -Message ($this.localizedData.ListenerExistsRemoveMessage -f $this.Transport, $this.Address) $selectorSet = @{ - Transport = $this.Transport - Address = $this.Address + Transport = [System.String] $this.Transport + Address = '*' + } + + if ($this.Address) + { + $selectorSet.Address = $this.Address } Remove-WSManInstance -ResourceURI 'winrm/config/Listener' -SelectorSet $selectorSet diff --git a/tests/Unit/Classes/WSManListener.Tests.ps1 b/tests/Unit/Classes/WSManListener.Tests.ps1 index d5f6954..53ff61e 100644 --- a/tests/Unit/Classes/WSManListener.Tests.ps1 +++ b/tests/Unit/Classes/WSManListener.Tests.ps1 @@ -786,10 +786,6 @@ Describe 'WSManListener\Modify()' -Tag 'HiddenMember' { # Mock method RemoveInstance which is called by the case method Modify(). Add-Member -Force -MemberType 'ScriptMethod' -Name 'RemoveInstance' -Value { $script:methodRemoveInstanceCallCount += 1 - } -PassThru | - # Mock method SetInstance which is called by the case method Modify(). - Add-Member -Force -MemberType 'ScriptMethod' -Name 'SetInstance' -Value { - $script:methodSetInstanceCallCount += 1 } -PassThru } } @@ -800,7 +796,6 @@ Describe 'WSManListener\Modify()' -Tag 'HiddenMember' { $script:methodNewInstanceCallCount = 0 $script:methodRemoveInstanceCallCount = 0 - $script:methodSetInstanceCallCount = 0 } } @@ -845,7 +840,7 @@ Describe 'WSManListener\Modify()' -Tag 'HiddenMember' { InModuleScope -ScriptBlock { Set-StrictMode -Version 1.0 - $script:mockInstance.Ensure = 'Absent' + $script:mockInstance.Ensure = 'Present' $mockProperties = @{ Transport = 'HTTP' @@ -1002,3 +997,16 @@ Describe 'WSManListener\RemoveInstance()' -Tag 'HiddenMember' { Should -Invoke -CommandName Remove-WSManInstance -Exactly -Times 1 -Scope It } } + +# Describe 'WSManListener\AssertProperties()' -Tag 'AssertProperties' { +# Context 'When passing mutually exclusive parameters' { +# Context 'When passing ' +# BeforeAll { +# InModuleScope -ScriptBlock { +# $script:mockInstance = [WSManListener] @{ + +# } +# } +# } +# } +# } From 04aeeec6b772efe5e3589ff58e784429e8bf0a0b Mon Sep 17 00:00:00 2001 From: Dan Hughes <2237515+dan-hughes@users.noreply.github.com> Date: Sun, 17 Nov 2024 16:58:48 +0000 Subject: [PATCH 044/134] Update BeforeDiscovery stream redirection --- tests/Integration/DSC_WSManConfig.Integration.Tests.ps1 | 2 +- tests/Integration/DSC_WSManListener.Integration.Tests.ps1 | 2 +- tests/Integration/DSC_WSManServiceConfig.Integration.Tests.ps1 | 2 +- tests/Unit/Classes/WSManListener.Tests.ps1 | 2 +- tests/Unit/Classes/WSManReason.Tests.ps1 | 2 +- tests/Unit/DSC_WSManConfig.Tests.ps1 | 2 +- tests/Unit/DSC_WSManServiceConfig.Tests.ps1 | 2 +- tests/Unit/Private/Find-Certificate.Tests.ps1 | 2 +- tests/Unit/Private/Get-DefaultPort.Tests.ps1 | 2 +- tests/Unit/Private/Get-Listener.Tests.ps1 | 2 +- 10 files changed, 10 insertions(+), 10 deletions(-) diff --git a/tests/Integration/DSC_WSManConfig.Integration.Tests.ps1 b/tests/Integration/DSC_WSManConfig.Integration.Tests.ps1 index e02554c..efeff88 100644 --- a/tests/Integration/DSC_WSManConfig.Integration.Tests.ps1 +++ b/tests/Integration/DSC_WSManConfig.Integration.Tests.ps1 @@ -18,7 +18,7 @@ BeforeDiscovery { if (-not (Get-Module -Name 'DscResource.Test' -ListAvailable)) { # Redirect all streams to $null, except the error stream (stream 2) - & "$PSScriptRoot/../../build.ps1" -Tasks 'noop' 2>&1 4>&1 5>&1 6>&1 > $null + & "$PSScriptRoot/../../build.ps1" -Tasks 'noop' 3>&1 4>&1 5>&1 6>&1 > $null } # This will throw an error if the dependencies have not been resolved. diff --git a/tests/Integration/DSC_WSManListener.Integration.Tests.ps1 b/tests/Integration/DSC_WSManListener.Integration.Tests.ps1 index f1fa5d8..c460702 100644 --- a/tests/Integration/DSC_WSManListener.Integration.Tests.ps1 +++ b/tests/Integration/DSC_WSManListener.Integration.Tests.ps1 @@ -18,7 +18,7 @@ BeforeDiscovery { if (-not (Get-Module -Name 'DscResource.Test' -ListAvailable)) { # Redirect all streams to $null, except the error stream (stream 2) - & "$PSScriptRoot/../../build.ps1" -Tasks 'noop' 2>&1 4>&1 5>&1 6>&1 > $null + & "$PSScriptRoot/../../build.ps1" -Tasks 'noop' 3>&1 4>&1 5>&1 6>&1 > $null } # This will throw an error if the dependencies have not been resolved. diff --git a/tests/Integration/DSC_WSManServiceConfig.Integration.Tests.ps1 b/tests/Integration/DSC_WSManServiceConfig.Integration.Tests.ps1 index 61f1a62..4397300 100644 --- a/tests/Integration/DSC_WSManServiceConfig.Integration.Tests.ps1 +++ b/tests/Integration/DSC_WSManServiceConfig.Integration.Tests.ps1 @@ -18,7 +18,7 @@ BeforeDiscovery { if (-not (Get-Module -Name 'DscResource.Test' -ListAvailable)) { # Redirect all streams to $null, except the error stream (stream 2) - & "$PSScriptRoot/../../build.ps1" -Tasks 'noop' 2>&1 4>&1 5>&1 6>&1 > $null + & "$PSScriptRoot/../../build.ps1" -Tasks 'noop' 3>&1 4>&1 5>&1 6>&1 > $null } # This will throw an error if the dependencies have not been resolved. diff --git a/tests/Unit/Classes/WSManListener.Tests.ps1 b/tests/Unit/Classes/WSManListener.Tests.ps1 index 53ff61e..f671c54 100644 --- a/tests/Unit/Classes/WSManListener.Tests.ps1 +++ b/tests/Unit/Classes/WSManListener.Tests.ps1 @@ -16,7 +16,7 @@ BeforeDiscovery { if (-not (Get-Module -Name 'DscResource.Test' -ListAvailable)) { # Redirect all streams to $null, except the error stream (stream 2) - & "$PSScriptRoot/../../../build.ps1" -Tasks 'noop' 2>&1 4>&1 5>&1 6>&1 > $null + & "$PSScriptRoot/../../../build.ps1" -Tasks 'noop' 3>&1 4>&1 5>&1 6>&1 > $null } # If the dependencies has not been resolved, this will throw an error. diff --git a/tests/Unit/Classes/WSManReason.Tests.ps1 b/tests/Unit/Classes/WSManReason.Tests.ps1 index 878a7d5..46db53d 100644 --- a/tests/Unit/Classes/WSManReason.Tests.ps1 +++ b/tests/Unit/Classes/WSManReason.Tests.ps1 @@ -10,7 +10,7 @@ BeforeDiscovery { if (-not (Get-Module -Name 'DscResource.Test' -ListAvailable)) { # Redirect all streams to $null, except the error stream (stream 2) - & "$PSScriptRoot/../../../build.ps1" -Tasks 'noop' 2>&1 4>&1 5>&1 6>&1 > $null + & "$PSScriptRoot/../../../build.ps1" -Tasks 'noop' 3>&1 4>&1 5>&1 6>&1 > $null } # If the dependencies has not been resolved, this will throw an error. diff --git a/tests/Unit/DSC_WSManConfig.Tests.ps1 b/tests/Unit/DSC_WSManConfig.Tests.ps1 index b7b2c6c..c5ba2bb 100644 --- a/tests/Unit/DSC_WSManConfig.Tests.ps1 +++ b/tests/Unit/DSC_WSManConfig.Tests.ps1 @@ -18,7 +18,7 @@ BeforeDiscovery { if (-not (Get-Module -Name 'DscResource.Test' -ListAvailable)) { # Redirect all streams to $null, except the error stream (stream 2) - & "$PSScriptRoot/../../build.ps1" -Tasks 'noop' 2>&1 4>&1 5>&1 6>&1 > $null + & "$PSScriptRoot/../../build.ps1" -Tasks 'noop' 3>&1 4>&1 5>&1 6>&1 > $null } # This will throw an error if the dependencies have not been resolved. diff --git a/tests/Unit/DSC_WSManServiceConfig.Tests.ps1 b/tests/Unit/DSC_WSManServiceConfig.Tests.ps1 index 86ff539..71a0abe 100644 --- a/tests/Unit/DSC_WSManServiceConfig.Tests.ps1 +++ b/tests/Unit/DSC_WSManServiceConfig.Tests.ps1 @@ -18,7 +18,7 @@ BeforeDiscovery { if (-not (Get-Module -Name 'DscResource.Test' -ListAvailable)) { # Redirect all streams to $null, except the error stream (stream 2) - & "$PSScriptRoot/../../build.ps1" -Tasks 'noop' 2>&1 4>&1 5>&1 6>&1 > $null + & "$PSScriptRoot/../../build.ps1" -Tasks 'noop' 3>&1 4>&1 5>&1 6>&1 > $null } # This will throw an error if the dependencies have not been resolved. diff --git a/tests/Unit/Private/Find-Certificate.Tests.ps1 b/tests/Unit/Private/Find-Certificate.Tests.ps1 index 25aaa03..243c0a9 100644 --- a/tests/Unit/Private/Find-Certificate.Tests.ps1 +++ b/tests/Unit/Private/Find-Certificate.Tests.ps1 @@ -10,7 +10,7 @@ BeforeDiscovery { if (-not (Get-Module -Name 'DscResource.Test' -ListAvailable)) { # Redirect all streams to $null, except the error stream (stream 2) - & "$PSScriptRoot/../../../build.ps1" -Tasks 'noop' 2>&1 4>&1 5>&1 6>&1 > $null + & "$PSScriptRoot/../../../build.ps1" -Tasks 'noop' 3>&1 4>&1 5>&1 6>&1 > $null } # This will throw an error if the dependencies have not been resolved. diff --git a/tests/Unit/Private/Get-DefaultPort.Tests.ps1 b/tests/Unit/Private/Get-DefaultPort.Tests.ps1 index a73428b..ffb0a52 100644 --- a/tests/Unit/Private/Get-DefaultPort.Tests.ps1 +++ b/tests/Unit/Private/Get-DefaultPort.Tests.ps1 @@ -10,7 +10,7 @@ BeforeDiscovery { if (-not (Get-Module -Name 'DscResource.Test' -ListAvailable)) { # Redirect all streams to $null, except the error stream (stream 2) - & "$PSScriptRoot/../../../build.ps1" -Tasks 'noop' 2>&1 4>&1 5>&1 6>&1 > $null + & "$PSScriptRoot/../../../build.ps1" -Tasks 'noop' 3>&1 4>&1 5>&1 6>&1 > $null } # If the dependencies has not been resolved, this will throw an error. diff --git a/tests/Unit/Private/Get-Listener.Tests.ps1 b/tests/Unit/Private/Get-Listener.Tests.ps1 index 34ea025..b2cb079 100644 --- a/tests/Unit/Private/Get-Listener.Tests.ps1 +++ b/tests/Unit/Private/Get-Listener.Tests.ps1 @@ -10,7 +10,7 @@ BeforeDiscovery { if (-not (Get-Module -Name 'DscResource.Test' -ListAvailable)) { # Redirect all streams to $null, except the error stream (stream 2) - & "$PSScriptRoot/../../../build.ps1" -Tasks 'noop' 2>&1 4>&1 5>&1 6>&1 > $null + & "$PSScriptRoot/../../../build.ps1" -Tasks 'noop' 3>&1 4>&1 5>&1 6>&1 > $null } # If the dependencies has not been resolved, this will throw an error. From d8c1396f8d2a86b851481855321e90f977bb21ef Mon Sep 17 00:00:00 2001 From: Dan Hughes <2237515+dan-hughes@users.noreply.github.com> Date: Sun, 17 Nov 2024 16:59:13 +0000 Subject: [PATCH 045/134] Add assert properties tests --- tests/Unit/Classes/WSManListener.Tests.ps1 | 46 ++++++++++++++++------ 1 file changed, 34 insertions(+), 12 deletions(-) diff --git a/tests/Unit/Classes/WSManListener.Tests.ps1 b/tests/Unit/Classes/WSManListener.Tests.ps1 index f671c54..f878fea 100644 --- a/tests/Unit/Classes/WSManListener.Tests.ps1 +++ b/tests/Unit/Classes/WSManListener.Tests.ps1 @@ -998,15 +998,37 @@ Describe 'WSManListener\RemoveInstance()' -Tag 'HiddenMember' { } } -# Describe 'WSManListener\AssertProperties()' -Tag 'AssertProperties' { -# Context 'When passing mutually exclusive parameters' { -# Context 'When passing ' -# BeforeAll { -# InModuleScope -ScriptBlock { -# $script:mockInstance = [WSManListener] @{ - -# } -# } -# } -# } -# } +Describe 'WSManListener\AssertProperties()' -Tag 'AssertProperties' { + BeforeAll { + InModuleScope -ScriptBlock { + $script:mockInstance = [WSManListener] @{} + } + } + Context 'When passing mutually exclusive parameters' { + Context 'When passing Issuer and Hostname' { + It 'Should throw the correct error' { + InModuleScope -ScriptBlock { + { + $mockInstance.AssertProperties(@{ + Issuer = 'SomeIssuer' + HostName = 'TheHostname' + }) + } | Should -Throw -ExpectedMessage '*DRC0010*' + } + } + } + + } + Context 'When passing BaseDN and CertificateThumbprint' { + It 'Should throw the correct error' { + InModuleScope -ScriptBlock { + { + $mockInstance.AssertProperties(@{ + BaseDN = 'SomeBaseDN' + CertificateThumbprint = 'certificateThumbprint' + }) + } | Should -Throw -ExpectedMessage '*DRC0010*' + } + } + } +} From 807bdc9f7737319c71cde1cb737da0db2e37a360 Mon Sep 17 00:00:00 2001 From: Dan Hughes <2237515+dan-hughes@users.noreply.github.com> Date: Sun, 17 Nov 2024 17:13:44 +0000 Subject: [PATCH 046/134] Remove Export-ModuleMember --- source/DSCResources/DSC_WSManConfig/DSC_WSManConfig.psm1 | 2 -- .../DSC_WSManServiceConfig/DSC_WSManServiceConfig.psm1 | 2 -- 2 files changed, 4 deletions(-) diff --git a/source/DSCResources/DSC_WSManConfig/DSC_WSManConfig.psm1 b/source/DSCResources/DSC_WSManConfig/DSC_WSManConfig.psm1 index a86fd3f..ce54a24 100644 --- a/source/DSCResources/DSC_WSManConfig/DSC_WSManConfig.psm1 +++ b/source/DSCResources/DSC_WSManConfig/DSC_WSManConfig.psm1 @@ -191,5 +191,3 @@ function Test-TargetResource -ExcludeProperties @('IsSingleInstance') ` -Verbose:$VerbosePreference } # Test-TargetResource - -Export-ModuleMember -Function *-TargetResource diff --git a/source/DSCResources/DSC_WSManServiceConfig/DSC_WSManServiceConfig.psm1 b/source/DSCResources/DSC_WSManServiceConfig/DSC_WSManServiceConfig.psm1 index cda9f2c..8ea1bd3 100644 --- a/source/DSCResources/DSC_WSManServiceConfig/DSC_WSManServiceConfig.psm1 +++ b/source/DSCResources/DSC_WSManServiceConfig/DSC_WSManServiceConfig.psm1 @@ -343,5 +343,3 @@ function Test-TargetResource -ExcludeProperties @('IsSingleInstance') ` -Verbose:$VerbosePreference } # Test-TargetResource - -Export-ModuleMember -Function *-TargetResource From 6157ba1c31ef12612f01c40a6d775ce6972cccbd Mon Sep 17 00:00:00 2001 From: Dan Hughes <2237515+dan-hughes@users.noreply.github.com> Date: Sun, 17 Nov 2024 17:17:23 +0000 Subject: [PATCH 047/134] Update changelog --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7ffffc2..d197c37 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -59,9 +59,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - `DSC_WSManConfig` - Refactor `Test-TargetResource` to use `Test-DscParameterState`. - Remove unused strings. + - Removed Export-ModuleMember. - `DSC_WSManServiceConfig` - Refactor `Test-TargetResource` to use `Test-DscParameterState`. - Remove unused strings + - Removed Export-ModuleMember. - `DSC_WSManListener` - Converted to Class Resource - Extracted private functions to own files From 5baa147e37a57b545d3fd0c2274046ebee26d3e1 Mon Sep 17 00:00:00 2001 From: Dan Hughes <2237515+dan-hughes@users.noreply.github.com> Date: Sun, 17 Nov 2024 17:31:05 +0000 Subject: [PATCH 048/134] Fix newlines and add strictmode --- tests/Unit/Classes/WSManListener.Tests.ps1 | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/tests/Unit/Classes/WSManListener.Tests.ps1 b/tests/Unit/Classes/WSManListener.Tests.ps1 index f878fea..20351e1 100644 --- a/tests/Unit/Classes/WSManListener.Tests.ps1 +++ b/tests/Unit/Classes/WSManListener.Tests.ps1 @@ -1001,13 +1001,18 @@ Describe 'WSManListener\RemoveInstance()' -Tag 'HiddenMember' { Describe 'WSManListener\AssertProperties()' -Tag 'AssertProperties' { BeforeAll { InModuleScope -ScriptBlock { + Set-StrictMode -Version 1.0 + $script:mockInstance = [WSManListener] @{} } } + Context 'When passing mutually exclusive parameters' { Context 'When passing Issuer and Hostname' { It 'Should throw the correct error' { InModuleScope -ScriptBlock { + Set-StrictMode -Version 1.0 + { $mockInstance.AssertProperties(@{ Issuer = 'SomeIssuer' @@ -1017,11 +1022,13 @@ Describe 'WSManListener\AssertProperties()' -Tag 'AssertProperties' { } } } - } + Context 'When passing BaseDN and CertificateThumbprint' { It 'Should throw the correct error' { InModuleScope -ScriptBlock { + Set-StrictMode -Version 1.0 + { $mockInstance.AssertProperties(@{ BaseDN = 'SomeBaseDN' From 514c668d3a651a2ab0c70d310b4acd6d9fe1c9dc Mon Sep 17 00:00:00 2001 From: Daniel Hughes Date: Mon, 9 Dec 2024 10:22:05 +0000 Subject: [PATCH 049/134] Use correct error code prefix --- source/en-US/WSManListener.strings.psd1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/en-US/WSManListener.strings.psd1 b/source/en-US/WSManListener.strings.psd1 index 80e652d..8abb4c4 100644 --- a/source/en-US/WSManListener.strings.psd1 +++ b/source/en-US/WSManListener.strings.psd1 @@ -12,6 +12,6 @@ ConvertFrom-StringData @' ## Strings directly used by the derived class WSManListener. ListenerExistsRemoveMessage = Removing {0} Listener on address {1} (WSML0001). - ListenerCreateFailNoCertError = Failed to create {0} Listener on port {1} because an applicable certificate could not be found (WSM0002). + ListenerCreateFailNoCertError = Failed to create {0} Listener on port {1} because an applicable certificate could not be found (WSML0002). CreatingListenerMessage = Creating {0} Listener on port {1} (WSML0003). '@ From a6c859a9923e82d7bed220ae7c08e1d4261c0b24 Mon Sep 17 00:00:00 2001 From: Dan Hughes <2237515+dan-hughes@users.noreply.github.com> Date: Sun, 12 Jan 2025 14:36:27 +0000 Subject: [PATCH 050/134] Use Get-ComputerName from DscResource.Common --- source/Classes/020.WSManListener.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/Classes/020.WSManListener.ps1 b/source/Classes/020.WSManListener.ps1 index d1a2942..637a709 100644 --- a/source/Classes/020.WSManListener.ps1 +++ b/source/Classes/020.WSManListener.ps1 @@ -266,7 +266,7 @@ class WSManListener : ResourceBase if ([System.String]::IsNullOrEmpty($this.Hostname)) { - $valueSet.HostName = [System.Net.Dns]::GetHostByName($env:COMPUTERNAME).Hostname + $valueSet.HostName = Get-ComputerName } else { From 12a06d65a6d64833f0275bf3e250ba8a63668a50 Mon Sep 17 00:00:00 2001 From: Dan Hughes <2237515+dan-hughes@users.noreply.github.com> Date: Sun, 12 Jan 2025 14:38:07 +0000 Subject: [PATCH 051/134] Add SubjectFormat and MatchAlternate to bound parameter checks --- source/Classes/020.WSManListener.ps1 | 2 ++ 1 file changed, 2 insertions(+) diff --git a/source/Classes/020.WSManListener.ps1 b/source/Classes/020.WSManListener.ps1 index 637a709..d640666 100644 --- a/source/Classes/020.WSManListener.ps1 +++ b/source/Classes/020.WSManListener.ps1 @@ -229,6 +229,8 @@ class WSManListener : ResourceBase MutuallyExclusiveList1 = @( 'Issuer' 'BaseDN' + 'SubjectFormat' + 'MatchAlternate' ) MutuallyExclusiveList2 = @( 'CertificateThumbprint' From 0aadf22b7ed1e3968e582342d9e4ed4efdf76227 Mon Sep 17 00:00:00 2001 From: Dan Hughes <2237515+dan-hughes@users.noreply.github.com> Date: Fri, 17 Jan 2025 12:21:00 +0000 Subject: [PATCH 052/134] Expand AssertProperties tests --- tests/Unit/Classes/WSManListener.Tests.ps1 | 85 +++++++++++++++++----- 1 file changed, 65 insertions(+), 20 deletions(-) diff --git a/tests/Unit/Classes/WSManListener.Tests.ps1 b/tests/Unit/Classes/WSManListener.Tests.ps1 index 20351e1..7d93feb 100644 --- a/tests/Unit/Classes/WSManListener.Tests.ps1 +++ b/tests/Unit/Classes/WSManListener.Tests.ps1 @@ -1008,33 +1008,78 @@ Describe 'WSManListener\AssertProperties()' -Tag 'AssertProperties' { } Context 'When passing mutually exclusive parameters' { - Context 'When passing Issuer and Hostname' { - It 'Should throw the correct error' { - InModuleScope -ScriptBlock { - Set-StrictMode -Version 1.0 - - { - $mockInstance.AssertProperties(@{ - Issuer = 'SomeIssuer' - HostName = 'TheHostname' - }) - } | Should -Throw -ExpectedMessage '*DRC0010*' + BeforeDiscovery { + $testCases = @( + @{ + Issuer = 'SomeIssuer' + HostName = 'TheHostname' + } + @{ + Issuer = 'SomeIssuer' + CertificateThumbprint = 'certificateThumbprint' + } + @{ + BaseDN = 'SomeBaseDN' + HostName = 'TheHostname' + } + @{ + BaseDN = 'SomeBaseDN' + CertificateThumbprint = 'certificateThumbprint' + } + @{ + SubjectFormat = 'SubjectFormat' + HostName = 'TheHostname' + } + @{ + SubjectFormat = 'SubjectFormat' + CertificateThumbprint = 'certificateThumbprint' + } + @{ + MatchAlternate = 'MatchAlternate' + HostName = 'TheHostname' } + @{ + MatchAlternate = 'MatchAlternate' + CertificateThumbprint = 'certificateThumbprint' + } + ) + } + + It 'Should throw the correct error' -ForEach $testCases { + InModuleScope -Parameters @{ + mockProperties = $_ + } -ScriptBlock { + Set-StrictMode -Version 1.0 + + { $mockInstance.AssertProperties($mockProperties) } | Should -Throw -ExpectedMessage '*DRC0010*' } } } - Context 'When passing BaseDN and CertificateThumbprint' { - It 'Should throw the correct error' { - InModuleScope -ScriptBlock { + Context 'When passing mutually inclusive parameters' { + BeforeDiscovery { + $testCases = @( + @{ + Issuer = 'SomeIssuer' + BaseDN = 'SomeBaseDN' + SubjectFormat = 'SubjectFormat' + MatchAlternate = 'MatchAlternate' + } + @{ + + HostName = 'TheHostname' + CertificateThumbprint = 'certificateThumbprint' + } + ) + } + + It 'Should not throw an error' -ForEach $testCases { + InModuleScope -Parameters @{ + mockProperties = $_ + } -ScriptBlock { Set-StrictMode -Version 1.0 - { - $mockInstance.AssertProperties(@{ - BaseDN = 'SomeBaseDN' - CertificateThumbprint = 'certificateThumbprint' - }) - } | Should -Throw -ExpectedMessage '*DRC0010*' + { $mockInstance.AssertProperties($mockProperties) } | Should -Not -Throw } } } From ae0ee81161a45e3fe76f1d7ecb04d9be1ea787c4 Mon Sep 17 00:00:00 2001 From: Dan Hughes <2237515+dan-hughes@users.noreply.github.com> Date: Fri, 17 Jan 2025 12:42:25 +0000 Subject: [PATCH 053/134] Remove remaning $ENV:computerName --- source/Private/Find-Certificate.ps1 | 2 +- .../DSC_WSManListener.Integration.Tests.ps1 | 2 +- tests/Unit/Classes/WSManListener.Tests.ps1 | 16 ++++++++-------- tests/Unit/Private/Find-Certificate.Tests.ps1 | 2 +- 4 files changed, 11 insertions(+), 11 deletions(-) diff --git a/source/Private/Find-Certificate.ps1 b/source/Private/Find-Certificate.ps1 index 096875c..21d44d9 100644 --- a/source/Private/Find-Certificate.ps1 +++ b/source/Private/Find-Certificate.ps1 @@ -68,7 +68,7 @@ function Find-Certificate # Lookup the certificate using the FQDN of the machine if ([System.String]::IsNullOrEmpty($Hostname)) { - $Hostname = [System.Net.Dns]::GetHostByName($ENV:computerName).Hostname + $Hostname = Get-ComputerName } $Subject = "CN=$Hostname" diff --git a/tests/Integration/DSC_WSManListener.Integration.Tests.ps1 b/tests/Integration/DSC_WSManListener.Integration.Tests.ps1 index c460702..faa2e3b 100644 --- a/tests/Integration/DSC_WSManListener.Integration.Tests.ps1 +++ b/tests/Integration/DSC_WSManListener.Integration.Tests.ps1 @@ -62,7 +62,7 @@ BeforeAll { Where-Object -Property FriendlyName -EQ $CertFriendlyName | Remove-Item -Force - $script:Hostname = ([System.Net.Dns]::GetHostByName($ENV:computerName).Hostname) + $script:Hostname = Get-ComputerName $script:BaseDN = 'O=Contoso Inc, S=Pennsylvania, C=US' $script:Issuer = "CN=$Hostname, $BaseDN" diff --git a/tests/Unit/Classes/WSManListener.Tests.ps1 b/tests/Unit/Classes/WSManListener.Tests.ps1 index 7d93feb..6f1d9e3 100644 --- a/tests/Unit/Classes/WSManListener.Tests.ps1 +++ b/tests/Unit/Classes/WSManListener.Tests.ps1 @@ -499,7 +499,7 @@ Describe 'WSManListener\Test()' -Tag 'Test' { Transport = 'HTTPS' Port = 5986 CertificateThumbprint = '74FA31ADEA7FDD5333CED10910BFA6F665A1F2FC' - Hostname = $([System.Net.Dns]::GetHostByName($ENV:computerName).Hostname) + Hostname = Get-ComputerName Ensure = 'Present' } } @@ -624,7 +624,7 @@ Describe 'WSManListener\GetCurrentState()' -Tag 'HiddenMember' { Address = '*' CertificateThumbprint = $null - Hostname = $([System.Net.Dns]::GetHostByName($ENV:computerName).Hostname) + Hostname = Get-ComputerName Enabled = $true URLPrefix = 'wsman' @@ -648,7 +648,7 @@ Describe 'WSManListener\GetCurrentState()' -Tag 'HiddenMember' { $currentState.Address | Should -Be '*' $currentState.Issuer | Should -BeNullOrEmpty $currentState.CertificateThumbprint | Should -BeNullOrEmpty - $currentState.Hostname | Should -Be $([System.Net.Dns]::GetHostByName($ENV:computerName).Hostname) + $currentState.Hostname | Should -Be (Get-ComputerName) $currentState.Enabled | Should -BeTrue $currentState.URLPrefix | Should -Be 'wsman' } @@ -675,7 +675,7 @@ Describe 'WSManListener\GetCurrentState()' -Tag 'HiddenMember' { Address = '*' CertificateThumbprint = $null - Hostname = $([System.Net.Dns]::GetHostByName($ENV:computerName).Hostname) + Hostname = Get-ComputerName Enabled = $true URLPrefix = 'wsman' @@ -701,7 +701,7 @@ Describe 'WSManListener\GetCurrentState()' -Tag 'HiddenMember' { $currentState.Address | Should -Be '*' $currentState.Issuer | Should -BeNullOrEmpty $currentState.CertificateThumbprint | Should -BeNullOrEmpty - $currentState.Hostname | Should -Be $([System.Net.Dns]::GetHostByName($ENV:computerName).Hostname) + $currentState.Hostname | Should -Be (Get-ComputerName) $currentState.Enabled | Should -BeTrue $currentState.URLPrefix | Should -Be 'wsman' } @@ -731,7 +731,7 @@ Describe 'WSManListener\GetCurrentState()' -Tag 'HiddenMember' { Address = '*' CertificateThumbprint = '74FA31ADEA7FDD5333CED10910BFA6F665A1F2FC' - Hostname = $([System.Net.Dns]::GetHostByName($ENV:computerName).Hostname) + Hostname = Get-ComputerName Enabled = $true URLPrefix = 'wsman' @@ -757,7 +757,7 @@ Describe 'WSManListener\GetCurrentState()' -Tag 'HiddenMember' { $currentState.Address | Should -Be '*' $currentState.Issuer | Should -Be 'CN=CONTOSO.COM Issuing CA, DC=CONTOSO, DC=COM' $currentState.CertificateThumbprint | Should -Be '74FA31ADEA7FDD5333CED10910BFA6F665A1F2FC' - $currentState.Hostname | Should -Be $([System.Net.Dns]::GetHostByName($ENV:computerName).Hostname) + $currentState.Hostname | Should -Be (Get-ComputerName) $currentState.Enabled | Should -BeTrue $currentState.URLPrefix | Should -Be 'wsman' } @@ -941,7 +941,7 @@ Describe 'WSManListener\NewInstance()' -Tag 'HiddenMember' { Should -Invoke -CommandName Get-DscProperty -Exactly -Times 1 -Scope It Should -Invoke -CommandName Find-Certificate -Exactly -Times 1 -Scope It Should -Invoke -CommandName New-WSManInstance -ParameterFilter { - $ValueSet.HostName -eq [System.Net.Dns]::GetHostByName($env:COMPUTERNAME).Hostname + $ValueSet.HostName -eq (Get-ComputerName) } -Exactly -Times 1 -Scope It } } diff --git a/tests/Unit/Private/Find-Certificate.Tests.ps1 b/tests/Unit/Private/Find-Certificate.Tests.ps1 index 243c0a9..ee0875d 100644 --- a/tests/Unit/Private/Find-Certificate.Tests.ps1 +++ b/tests/Unit/Private/Find-Certificate.Tests.ps1 @@ -35,7 +35,7 @@ BeforeAll { # Create the Mock Objects that will be used for running tests $script:mockCertificateThumbprint = '74FA31ADEA7FDD5333CED10910BFA6F665A1F2FC' $mockFQDN = 'SERVER1.CONTOSO.COM' - $mockHostName = $([System.Net.Dns]::GetHostByName($ENV:computerName).Hostname) + $mockHostName = Get-ComputerName $script:mockIssuer = 'CN=CONTOSO.COM Issuing CA, DC=CONTOSO, DC=COM' $script:mockDN = 'O=Contoso Inc, S=Pennsylvania, C=US' $mockCertificate = @{ From 5545a3c327cad198d2726aa7e981db9d9b3b7d67 Mon Sep 17 00:00:00 2001 From: Dan Hughes <2237515+dan-hughes@users.noreply.github.com> Date: Fri, 17 Jan 2025 13:01:31 +0000 Subject: [PATCH 054/134] Make SubjectFormat nullable to correct bound parameter checks --- source/Classes/020.WSManListener.ps1 | 4 ++-- tests/Unit/Classes/WSManListener.Tests.ps1 | 10 +++++----- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/source/Classes/020.WSManListener.ps1 b/source/Classes/020.WSManListener.ps1 index d640666..1506c63 100644 --- a/source/Classes/020.WSManListener.ps1 +++ b/source/Classes/020.WSManListener.ps1 @@ -88,8 +88,8 @@ class WSManListener : ResourceBase $Issuer [DscProperty()] - [WSManSubjectFormat] - $SubjectFormat = [WSManSubjectFormat]::Both + [Nullable[WSManSubjectFormat]] + $SubjectFormat [DscProperty()] [Nullable[System.Boolean]] diff --git a/tests/Unit/Classes/WSManListener.Tests.ps1 b/tests/Unit/Classes/WSManListener.Tests.ps1 index 6f1d9e3..3620c4e 100644 --- a/tests/Unit/Classes/WSManListener.Tests.ps1 +++ b/tests/Unit/Classes/WSManListener.Tests.ps1 @@ -133,7 +133,7 @@ Describe 'WSManListener\Get()' -Tag 'Get' { $currentState.URLPrefix | Should -Be 'wsman' $currentState.Issuer | Should -BeNullOrEmpty - $currentState.SubjectFormat | Should -Be 'Both' + $currentState.SubjectFormat | Should -BeNullOrEmpty $currentState.MatchAlternate | Should -BeNullOrEmpty $currentState.BaseDN | Should -BeNullOrEmpty $currentState.CertificateThumbprint | Should -BeNullOrEmpty @@ -192,7 +192,7 @@ Describe 'WSManListener\Get()' -Tag 'Get' { $currentState.URLPrefix | Should -Be 'wsman' $currentState.Issuer | Should -BeNullOrEmpty - $currentState.SubjectFormat | Should -Be 'Both' + $currentState.SubjectFormat | Should -BeNullOrEmpty $currentState.MatchAlternate | Should -BeNullOrEmpty $currentState.BaseDN | Should -BeNullOrEmpty $currentState.CertificateThumbprint | Should -BeNullOrEmpty @@ -245,7 +245,7 @@ Describe 'WSManListener\Get()' -Tag 'Get' { $currentState.URLPrefix | Should -BeNullOrEmpty $currentState.Issuer | Should -BeNullOrEmpty - $currentState.SubjectFormat | Should -Be 'Both' + $currentState.SubjectFormat | Should -BeNullOrEmpty $currentState.MatchAlternate | Should -BeNullOrEmpty $currentState.BaseDN | Should -BeNullOrEmpty $currentState.CertificateThumbprint | Should -BeNullOrEmpty @@ -320,7 +320,7 @@ Describe 'WSManListener\Get()' -Tag 'Get' { $currentState.URLPrefix | Should -Be 'wsman' $currentState.Issuer | Should -BeNullOrEmpty - $currentState.SubjectFormat | Should -Be 'Both' + $currentState.SubjectFormat | Should -BeNullOrEmpty $currentState.MatchAlternate | Should -BeNullOrEmpty $currentState.BaseDN | Should -BeNullOrEmpty $currentState.CertificateThumbprint | Should -BeNullOrEmpty @@ -387,7 +387,7 @@ Describe 'WSManListener\Get()' -Tag 'Get' { $currentState.URLPrefix | Should -Be 'wsman' $currentState.Issuer | Should -BeNullOrEmpty - $currentState.SubjectFormat | Should -Be 'Both' + $currentState.SubjectFormat | Should -BeNullOrEmpty $currentState.MatchAlternate | Should -BeNullOrEmpty $currentState.BaseDN | Should -BeNullOrEmpty $currentState.CertificateThumbprint | Should -BeNullOrEmpty From 3b8588e97857302ac7175a41b62a359d1651bc2d Mon Sep 17 00:00:00 2001 From: Dan Hughes <2237515+dan-hughes@users.noreply.github.com> Date: Fri, 17 Jan 2025 13:12:21 +0000 Subject: [PATCH 055/134] Cannot use nullable enums --- source/Classes/020.WSManListener.ps1 | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/source/Classes/020.WSManListener.ps1 b/source/Classes/020.WSManListener.ps1 index 1506c63..586e292 100644 --- a/source/Classes/020.WSManListener.ps1 +++ b/source/Classes/020.WSManListener.ps1 @@ -88,7 +88,8 @@ class WSManListener : ResourceBase $Issuer [DscProperty()] - [Nullable[WSManSubjectFormat]] + [ValidateSet('Both', 'FQDNOnly', 'NameOnly')] + [System.String] $SubjectFormat [DscProperty()] From 423ba17a1e9321a938e324f573a8fc1c8c2bb82f Mon Sep 17 00:00:00 2001 From: Dan Hughes <2237515+dan-hughes@users.noreply.github.com> Date: Fri, 17 Jan 2025 13:48:08 +0000 Subject: [PATCH 056/134] Enum use starting value of 1 --- source/Classes/020.WSManListener.ps1 | 3 +-- source/Enum/005.WSManSubjectFormat.ps1 | 2 +- source/Enum/005.WSManTransport.ps1 | 2 +- tests/Unit/Classes/WSManListener.Tests.ps1 | 10 +++++----- 4 files changed, 8 insertions(+), 9 deletions(-) diff --git a/source/Classes/020.WSManListener.ps1 b/source/Classes/020.WSManListener.ps1 index 586e292..be7f0fe 100644 --- a/source/Classes/020.WSManListener.ps1 +++ b/source/Classes/020.WSManListener.ps1 @@ -88,8 +88,7 @@ class WSManListener : ResourceBase $Issuer [DscProperty()] - [ValidateSet('Both', 'FQDNOnly', 'NameOnly')] - [System.String] + [WSManSubjectFormat] $SubjectFormat [DscProperty()] diff --git a/source/Enum/005.WSManSubjectFormat.ps1 b/source/Enum/005.WSManSubjectFormat.ps1 index ecf7c15..13bf44a 100644 --- a/source/Enum/005.WSManSubjectFormat.ps1 +++ b/source/Enum/005.WSManSubjectFormat.ps1 @@ -5,7 +5,7 @@ enum WSManSubjectFormat { - Both + Both = 1 FQDNOnly NameOnly } diff --git a/source/Enum/005.WSManTransport.ps1 b/source/Enum/005.WSManTransport.ps1 index 17a828f..fc199fc 100644 --- a/source/Enum/005.WSManTransport.ps1 +++ b/source/Enum/005.WSManTransport.ps1 @@ -5,6 +5,6 @@ enum WSManTransport { - HTTP + HTTP = 1 HTTPS } diff --git a/tests/Unit/Classes/WSManListener.Tests.ps1 b/tests/Unit/Classes/WSManListener.Tests.ps1 index 3620c4e..280c744 100644 --- a/tests/Unit/Classes/WSManListener.Tests.ps1 +++ b/tests/Unit/Classes/WSManListener.Tests.ps1 @@ -133,7 +133,7 @@ Describe 'WSManListener\Get()' -Tag 'Get' { $currentState.URLPrefix | Should -Be 'wsman' $currentState.Issuer | Should -BeNullOrEmpty - $currentState.SubjectFormat | Should -BeNullOrEmpty + $currentState.SubjectFormat | Should -Be 0 $currentState.MatchAlternate | Should -BeNullOrEmpty $currentState.BaseDN | Should -BeNullOrEmpty $currentState.CertificateThumbprint | Should -BeNullOrEmpty @@ -192,7 +192,7 @@ Describe 'WSManListener\Get()' -Tag 'Get' { $currentState.URLPrefix | Should -Be 'wsman' $currentState.Issuer | Should -BeNullOrEmpty - $currentState.SubjectFormat | Should -BeNullOrEmpty + $currentState.SubjectFormat | Should -Be 0 $currentState.MatchAlternate | Should -BeNullOrEmpty $currentState.BaseDN | Should -BeNullOrEmpty $currentState.CertificateThumbprint | Should -BeNullOrEmpty @@ -245,7 +245,7 @@ Describe 'WSManListener\Get()' -Tag 'Get' { $currentState.URLPrefix | Should -BeNullOrEmpty $currentState.Issuer | Should -BeNullOrEmpty - $currentState.SubjectFormat | Should -BeNullOrEmpty + $currentState.SubjectFormat | Should -Be 0 $currentState.MatchAlternate | Should -BeNullOrEmpty $currentState.BaseDN | Should -BeNullOrEmpty $currentState.CertificateThumbprint | Should -BeNullOrEmpty @@ -320,7 +320,7 @@ Describe 'WSManListener\Get()' -Tag 'Get' { $currentState.URLPrefix | Should -Be 'wsman' $currentState.Issuer | Should -BeNullOrEmpty - $currentState.SubjectFormat | Should -BeNullOrEmpty + $currentState.SubjectFormat | Should -Be 0 $currentState.MatchAlternate | Should -BeNullOrEmpty $currentState.BaseDN | Should -BeNullOrEmpty $currentState.CertificateThumbprint | Should -BeNullOrEmpty @@ -387,7 +387,7 @@ Describe 'WSManListener\Get()' -Tag 'Get' { $currentState.URLPrefix | Should -Be 'wsman' $currentState.Issuer | Should -BeNullOrEmpty - $currentState.SubjectFormat | Should -BeNullOrEmpty + $currentState.SubjectFormat | Should -Be 0 $currentState.MatchAlternate | Should -BeNullOrEmpty $currentState.BaseDN | Should -BeNullOrEmpty $currentState.CertificateThumbprint | Should -BeNullOrEmpty From f406a91ab741962a919cbc1de89e83b57895d2f6 Mon Sep 17 00:00:00 2001 From: Dan Hughes <2237515+dan-hughes@users.noreply.github.com> Date: Fri, 17 Jan 2025 16:47:53 +0000 Subject: [PATCH 057/134] Correct casing --- source/Classes/020.WSManListener.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/Classes/020.WSManListener.ps1 b/source/Classes/020.WSManListener.ps1 index be7f0fe..b04a2df 100644 --- a/source/Classes/020.WSManListener.ps1 +++ b/source/Classes/020.WSManListener.ps1 @@ -234,7 +234,7 @@ class WSManListener : ResourceBase ) MutuallyExclusiveList2 = @( 'CertificateThumbprint' - 'Hostname' + 'HostName' ) } From 375fb4d5b02394704b23b814cbc6f7ef5e57afd4 Mon Sep 17 00:00:00 2001 From: Dan Hughes <2237515+dan-hughes@users.noreply.github.com> Date: Fri, 17 Jan 2025 16:50:47 +0000 Subject: [PATCH 058/134] Remove subjectformat temporarily --- source/Classes/020.WSManListener.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/Classes/020.WSManListener.ps1 b/source/Classes/020.WSManListener.ps1 index b04a2df..5bf72fb 100644 --- a/source/Classes/020.WSManListener.ps1 +++ b/source/Classes/020.WSManListener.ps1 @@ -229,7 +229,7 @@ class WSManListener : ResourceBase MutuallyExclusiveList1 = @( 'Issuer' 'BaseDN' - 'SubjectFormat' + #'SubjectFormat' 'MatchAlternate' ) MutuallyExclusiveList2 = @( From 94c053e0ac32fdd5825c19602174c7b2dc749b54 Mon Sep 17 00:00:00 2001 From: Dan Hughes <2237515+dan-hughes@users.noreply.github.com> Date: Fri, 17 Jan 2025 16:51:35 +0000 Subject: [PATCH 059/134] Update unit tests for subjectformat using enum --- tests/Unit/Classes/WSManListener.Tests.ps1 | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/tests/Unit/Classes/WSManListener.Tests.ps1 b/tests/Unit/Classes/WSManListener.Tests.ps1 index 280c744..8f6d3c8 100644 --- a/tests/Unit/Classes/WSManListener.Tests.ps1 +++ b/tests/Unit/Classes/WSManListener.Tests.ps1 @@ -1027,11 +1027,11 @@ Describe 'WSManListener\AssertProperties()' -Tag 'AssertProperties' { CertificateThumbprint = 'certificateThumbprint' } @{ - SubjectFormat = 'SubjectFormat' + SubjectFormat = 1 HostName = 'TheHostname' } @{ - SubjectFormat = 'SubjectFormat' + SubjectFormat = 1 CertificateThumbprint = 'certificateThumbprint' } @{ @@ -1051,7 +1051,12 @@ Describe 'WSManListener\AssertProperties()' -Tag 'AssertProperties' { } -ScriptBlock { Set-StrictMode -Version 1.0 - { $mockInstance.AssertProperties($mockProperties) } | Should -Throw -ExpectedMessage '*DRC0010*' + if ($mockProperties.SubjectFormat) + { + $mockProperties.SubjectFormat = [WSManSubjectFormat]$mockProperties.SubjectFormat + } + + { $mockInstance.AssertProperties($mockProperties) } | Should -Throw -ExpectedMessage ('*' + 'DRC0010' + '*') } } } @@ -1062,7 +1067,7 @@ Describe 'WSManListener\AssertProperties()' -Tag 'AssertProperties' { @{ Issuer = 'SomeIssuer' BaseDN = 'SomeBaseDN' - SubjectFormat = 'SubjectFormat' + SubjectFormat = 0 MatchAlternate = 'MatchAlternate' } @{ @@ -1079,6 +1084,11 @@ Describe 'WSManListener\AssertProperties()' -Tag 'AssertProperties' { } -ScriptBlock { Set-StrictMode -Version 1.0 + if ($mockProperties.SubjectFormat) + { + $mockProperties.SubjectFormat = [WSManSubjectFormat]$mockProperties.SubjectFormat + } + { $mockInstance.AssertProperties($mockProperties) } | Should -Not -Throw } } From 61b8e276b0ed7ca9d3dc830dd4d30145b000ac51 Mon Sep 17 00:00:00 2001 From: Dan Hughes <2237515+dan-hughes@users.noreply.github.com> Date: Fri, 17 Jan 2025 16:53:14 +0000 Subject: [PATCH 060/134] Disable subjectformat tests --- tests/Unit/Classes/WSManListener.Tests.ps1 | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/tests/Unit/Classes/WSManListener.Tests.ps1 b/tests/Unit/Classes/WSManListener.Tests.ps1 index 8f6d3c8..eb80ad4 100644 --- a/tests/Unit/Classes/WSManListener.Tests.ps1 +++ b/tests/Unit/Classes/WSManListener.Tests.ps1 @@ -1026,14 +1026,14 @@ Describe 'WSManListener\AssertProperties()' -Tag 'AssertProperties' { BaseDN = 'SomeBaseDN' CertificateThumbprint = 'certificateThumbprint' } - @{ - SubjectFormat = 1 - HostName = 'TheHostname' - } - @{ - SubjectFormat = 1 - CertificateThumbprint = 'certificateThumbprint' - } + # @{ + # SubjectFormat = 1 + # HostName = 'TheHostname' + # } + # @{ + # SubjectFormat = 1 + # CertificateThumbprint = 'certificateThumbprint' + # } @{ MatchAlternate = 'MatchAlternate' HostName = 'TheHostname' From ebf394ef229de494b5ed6f8261bd7d8d109a9161 Mon Sep 17 00:00:00 2001 From: Dan Hughes <2237515+dan-hughes@users.noreply.github.com> Date: Fri, 17 Jan 2025 17:31:30 +0000 Subject: [PATCH 061/134] Add boundparameter filter for enum default values e.g. 0 --- source/Classes/020.WSManListener.ps1 | 17 +++++++++++++++-- tests/Unit/Classes/WSManListener.Tests.ps1 | 16 ++++++++-------- 2 files changed, 23 insertions(+), 10 deletions(-) diff --git a/source/Classes/020.WSManListener.ps1 b/source/Classes/020.WSManListener.ps1 index 5bf72fb..792f814 100644 --- a/source/Classes/020.WSManListener.ps1 +++ b/source/Classes/020.WSManListener.ps1 @@ -224,12 +224,25 @@ class WSManListener : ResourceBase #> hidden [void] AssertProperties([System.Collections.Hashtable] $properties) { + # Filter Out Keys that are enums AND have value of 0 (assumed default) + # https://learn.microsoft.com/en-us/powershell/dsc/concepts/class-based-resources?view=dsc-2.0#use-enums-instead-of-validateset + $FilteredBoundParameters = @{} + foreach ($key in $properties.Keys) + { + $value = $properties.$key + if ($value -is [System.Enum] -and [System.Int32]$value.value__ -eq 0) + { + continue + } + $FilteredBoundParameters.Add($key, $properties.$key) + } + $assertBoundParameterParameters = @{ - BoundParameterList = $properties + BoundParameterList = $FilteredBoundParameters MutuallyExclusiveList1 = @( 'Issuer' 'BaseDN' - #'SubjectFormat' + 'SubjectFormat' 'MatchAlternate' ) MutuallyExclusiveList2 = @( diff --git a/tests/Unit/Classes/WSManListener.Tests.ps1 b/tests/Unit/Classes/WSManListener.Tests.ps1 index eb80ad4..8f6d3c8 100644 --- a/tests/Unit/Classes/WSManListener.Tests.ps1 +++ b/tests/Unit/Classes/WSManListener.Tests.ps1 @@ -1026,14 +1026,14 @@ Describe 'WSManListener\AssertProperties()' -Tag 'AssertProperties' { BaseDN = 'SomeBaseDN' CertificateThumbprint = 'certificateThumbprint' } - # @{ - # SubjectFormat = 1 - # HostName = 'TheHostname' - # } - # @{ - # SubjectFormat = 1 - # CertificateThumbprint = 'certificateThumbprint' - # } + @{ + SubjectFormat = 1 + HostName = 'TheHostname' + } + @{ + SubjectFormat = 1 + CertificateThumbprint = 'certificateThumbprint' + } @{ MatchAlternate = 'MatchAlternate' HostName = 'TheHostname' From 452d1009cd11883e1fdb3b781e00688d98d5ab77 Mon Sep 17 00:00:00 2001 From: Dan Hughes <2237515+dan-hughes@users.noreply.github.com> Date: Mon, 20 Jan 2025 14:17:58 +0000 Subject: [PATCH 062/134] Revert "Remove remaning $ENV:computerName" This reverts commit ae0ee81161a45e3fe76f1d7ecb04d9be1ea787c4. --- source/Private/Find-Certificate.ps1 | 2 +- .../DSC_WSManListener.Integration.Tests.ps1 | 2 +- tests/Unit/Classes/WSManListener.Tests.ps1 | 16 ++++++++-------- tests/Unit/Private/Find-Certificate.Tests.ps1 | 2 +- 4 files changed, 11 insertions(+), 11 deletions(-) diff --git a/source/Private/Find-Certificate.ps1 b/source/Private/Find-Certificate.ps1 index 21d44d9..096875c 100644 --- a/source/Private/Find-Certificate.ps1 +++ b/source/Private/Find-Certificate.ps1 @@ -68,7 +68,7 @@ function Find-Certificate # Lookup the certificate using the FQDN of the machine if ([System.String]::IsNullOrEmpty($Hostname)) { - $Hostname = Get-ComputerName + $Hostname = [System.Net.Dns]::GetHostByName($ENV:computerName).Hostname } $Subject = "CN=$Hostname" diff --git a/tests/Integration/DSC_WSManListener.Integration.Tests.ps1 b/tests/Integration/DSC_WSManListener.Integration.Tests.ps1 index faa2e3b..c460702 100644 --- a/tests/Integration/DSC_WSManListener.Integration.Tests.ps1 +++ b/tests/Integration/DSC_WSManListener.Integration.Tests.ps1 @@ -62,7 +62,7 @@ BeforeAll { Where-Object -Property FriendlyName -EQ $CertFriendlyName | Remove-Item -Force - $script:Hostname = Get-ComputerName + $script:Hostname = ([System.Net.Dns]::GetHostByName($ENV:computerName).Hostname) $script:BaseDN = 'O=Contoso Inc, S=Pennsylvania, C=US' $script:Issuer = "CN=$Hostname, $BaseDN" diff --git a/tests/Unit/Classes/WSManListener.Tests.ps1 b/tests/Unit/Classes/WSManListener.Tests.ps1 index 8f6d3c8..c21432e 100644 --- a/tests/Unit/Classes/WSManListener.Tests.ps1 +++ b/tests/Unit/Classes/WSManListener.Tests.ps1 @@ -499,7 +499,7 @@ Describe 'WSManListener\Test()' -Tag 'Test' { Transport = 'HTTPS' Port = 5986 CertificateThumbprint = '74FA31ADEA7FDD5333CED10910BFA6F665A1F2FC' - Hostname = Get-ComputerName + Hostname = $([System.Net.Dns]::GetHostByName($ENV:computerName).Hostname) Ensure = 'Present' } } @@ -624,7 +624,7 @@ Describe 'WSManListener\GetCurrentState()' -Tag 'HiddenMember' { Address = '*' CertificateThumbprint = $null - Hostname = Get-ComputerName + Hostname = $([System.Net.Dns]::GetHostByName($ENV:computerName).Hostname) Enabled = $true URLPrefix = 'wsman' @@ -648,7 +648,7 @@ Describe 'WSManListener\GetCurrentState()' -Tag 'HiddenMember' { $currentState.Address | Should -Be '*' $currentState.Issuer | Should -BeNullOrEmpty $currentState.CertificateThumbprint | Should -BeNullOrEmpty - $currentState.Hostname | Should -Be (Get-ComputerName) + $currentState.Hostname | Should -Be $([System.Net.Dns]::GetHostByName($ENV:computerName).Hostname) $currentState.Enabled | Should -BeTrue $currentState.URLPrefix | Should -Be 'wsman' } @@ -675,7 +675,7 @@ Describe 'WSManListener\GetCurrentState()' -Tag 'HiddenMember' { Address = '*' CertificateThumbprint = $null - Hostname = Get-ComputerName + Hostname = $([System.Net.Dns]::GetHostByName($ENV:computerName).Hostname) Enabled = $true URLPrefix = 'wsman' @@ -701,7 +701,7 @@ Describe 'WSManListener\GetCurrentState()' -Tag 'HiddenMember' { $currentState.Address | Should -Be '*' $currentState.Issuer | Should -BeNullOrEmpty $currentState.CertificateThumbprint | Should -BeNullOrEmpty - $currentState.Hostname | Should -Be (Get-ComputerName) + $currentState.Hostname | Should -Be $([System.Net.Dns]::GetHostByName($ENV:computerName).Hostname) $currentState.Enabled | Should -BeTrue $currentState.URLPrefix | Should -Be 'wsman' } @@ -731,7 +731,7 @@ Describe 'WSManListener\GetCurrentState()' -Tag 'HiddenMember' { Address = '*' CertificateThumbprint = '74FA31ADEA7FDD5333CED10910BFA6F665A1F2FC' - Hostname = Get-ComputerName + Hostname = $([System.Net.Dns]::GetHostByName($ENV:computerName).Hostname) Enabled = $true URLPrefix = 'wsman' @@ -757,7 +757,7 @@ Describe 'WSManListener\GetCurrentState()' -Tag 'HiddenMember' { $currentState.Address | Should -Be '*' $currentState.Issuer | Should -Be 'CN=CONTOSO.COM Issuing CA, DC=CONTOSO, DC=COM' $currentState.CertificateThumbprint | Should -Be '74FA31ADEA7FDD5333CED10910BFA6F665A1F2FC' - $currentState.Hostname | Should -Be (Get-ComputerName) + $currentState.Hostname | Should -Be $([System.Net.Dns]::GetHostByName($ENV:computerName).Hostname) $currentState.Enabled | Should -BeTrue $currentState.URLPrefix | Should -Be 'wsman' } @@ -941,7 +941,7 @@ Describe 'WSManListener\NewInstance()' -Tag 'HiddenMember' { Should -Invoke -CommandName Get-DscProperty -Exactly -Times 1 -Scope It Should -Invoke -CommandName Find-Certificate -Exactly -Times 1 -Scope It Should -Invoke -CommandName New-WSManInstance -ParameterFilter { - $ValueSet.HostName -eq (Get-ComputerName) + $ValueSet.HostName -eq [System.Net.Dns]::GetHostByName($env:COMPUTERNAME).Hostname } -Exactly -Times 1 -Scope It } } diff --git a/tests/Unit/Private/Find-Certificate.Tests.ps1 b/tests/Unit/Private/Find-Certificate.Tests.ps1 index ee0875d..243c0a9 100644 --- a/tests/Unit/Private/Find-Certificate.Tests.ps1 +++ b/tests/Unit/Private/Find-Certificate.Tests.ps1 @@ -35,7 +35,7 @@ BeforeAll { # Create the Mock Objects that will be used for running tests $script:mockCertificateThumbprint = '74FA31ADEA7FDD5333CED10910BFA6F665A1F2FC' $mockFQDN = 'SERVER1.CONTOSO.COM' - $mockHostName = Get-ComputerName + $mockHostName = $([System.Net.Dns]::GetHostByName($ENV:computerName).Hostname) $script:mockIssuer = 'CN=CONTOSO.COM Issuing CA, DC=CONTOSO, DC=COM' $script:mockDN = 'O=Contoso Inc, S=Pennsylvania, C=US' $mockCertificate = @{ From 478a2c534f5fb0139efd9a44a2fd458ccf443e5c Mon Sep 17 00:00:00 2001 From: Dan Hughes <2237515+dan-hughes@users.noreply.github.com> Date: Mon, 20 Jan 2025 14:18:20 +0000 Subject: [PATCH 063/134] Revert "Use Get-ComputerName from DscResource.Common" This reverts commit a6c859a9923e82d7bed220ae7c08e1d4261c0b24. --- source/Classes/020.WSManListener.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/Classes/020.WSManListener.ps1 b/source/Classes/020.WSManListener.ps1 index 792f814..41acdad 100644 --- a/source/Classes/020.WSManListener.ps1 +++ b/source/Classes/020.WSManListener.ps1 @@ -281,7 +281,7 @@ class WSManListener : ResourceBase if ([System.String]::IsNullOrEmpty($this.Hostname)) { - $valueSet.HostName = Get-ComputerName + $valueSet.HostName = [System.Net.Dns]::GetHostByName($env:COMPUTERNAME).Hostname } else { From dcf799af3611e432e15a4ad8ce513e1ce3836365 Mon Sep 17 00:00:00 2001 From: Dan Hughes <2237515+dan-hughes@users.noreply.github.com> Date: Mon, 20 Jan 2025 14:30:04 +0000 Subject: [PATCH 064/134] GetHostByName is deprecated, use Get-ComuterName instead of $env:COMPUTERNAME --- source/Classes/020.WSManListener.ps1 | 2 +- source/Private/Find-Certificate.ps1 | 4 ++-- .../DSC_WSManListener.Integration.Tests.ps1 | 2 +- tests/Unit/Classes/WSManListener.Tests.ps1 | 16 ++++++++-------- tests/Unit/Private/Find-Certificate.Tests.ps1 | 2 +- 5 files changed, 13 insertions(+), 13 deletions(-) diff --git a/source/Classes/020.WSManListener.ps1 b/source/Classes/020.WSManListener.ps1 index 41acdad..4b79ace 100644 --- a/source/Classes/020.WSManListener.ps1 +++ b/source/Classes/020.WSManListener.ps1 @@ -281,7 +281,7 @@ class WSManListener : ResourceBase if ([System.String]::IsNullOrEmpty($this.Hostname)) { - $valueSet.HostName = [System.Net.Dns]::GetHostByName($env:COMPUTERNAME).Hostname + $valueSet.HostName = [System.Net.Dns]::GetHostEntry((Get-ComputerName)).Hostname } else { diff --git a/source/Private/Find-Certificate.ps1 b/source/Private/Find-Certificate.ps1 index 096875c..1b47d36 100644 --- a/source/Private/Find-Certificate.ps1 +++ b/source/Private/Find-Certificate.ps1 @@ -68,7 +68,7 @@ function Find-Certificate # Lookup the certificate using the FQDN of the machine if ([System.String]::IsNullOrEmpty($Hostname)) { - $Hostname = [System.Net.Dns]::GetHostByName($ENV:computerName).Hostname + $Hostname = [System.Net.Dns]::GetHostEntry((Get-ComputerName)).Hostname } $Subject = "CN=$Hostname" @@ -105,7 +105,7 @@ function Find-Certificate 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 = $ENV:ComputerName + [System.String] $Hostname = Get-ComputerName [System.String] $Subject = "CN=$Hostname" if ($PSBoundParameters.ContainsKey('BaseDN')) diff --git a/tests/Integration/DSC_WSManListener.Integration.Tests.ps1 b/tests/Integration/DSC_WSManListener.Integration.Tests.ps1 index c460702..a166be9 100644 --- a/tests/Integration/DSC_WSManListener.Integration.Tests.ps1 +++ b/tests/Integration/DSC_WSManListener.Integration.Tests.ps1 @@ -62,7 +62,7 @@ BeforeAll { Where-Object -Property FriendlyName -EQ $CertFriendlyName | Remove-Item -Force - $script:Hostname = ([System.Net.Dns]::GetHostByName($ENV:computerName).Hostname) + $script:Hostname = [System.Net.Dns]::GetHostEntry((Get-ComputerName)).Hostname $script:BaseDN = 'O=Contoso Inc, S=Pennsylvania, C=US' $script:Issuer = "CN=$Hostname, $BaseDN" diff --git a/tests/Unit/Classes/WSManListener.Tests.ps1 b/tests/Unit/Classes/WSManListener.Tests.ps1 index c21432e..253ff5a 100644 --- a/tests/Unit/Classes/WSManListener.Tests.ps1 +++ b/tests/Unit/Classes/WSManListener.Tests.ps1 @@ -499,7 +499,7 @@ Describe 'WSManListener\Test()' -Tag 'Test' { Transport = 'HTTPS' Port = 5986 CertificateThumbprint = '74FA31ADEA7FDD5333CED10910BFA6F665A1F2FC' - Hostname = $([System.Net.Dns]::GetHostByName($ENV:computerName).Hostname) + Hostname = [System.Net.Dns]::GetHostEntry((Get-ComputerName)).Hostname Ensure = 'Present' } } @@ -624,7 +624,7 @@ Describe 'WSManListener\GetCurrentState()' -Tag 'HiddenMember' { Address = '*' CertificateThumbprint = $null - Hostname = $([System.Net.Dns]::GetHostByName($ENV:computerName).Hostname) + Hostname = [System.Net.Dns]::GetHostEntry((Get-ComputerName)).Hostname Enabled = $true URLPrefix = 'wsman' @@ -648,7 +648,7 @@ Describe 'WSManListener\GetCurrentState()' -Tag 'HiddenMember' { $currentState.Address | Should -Be '*' $currentState.Issuer | Should -BeNullOrEmpty $currentState.CertificateThumbprint | Should -BeNullOrEmpty - $currentState.Hostname | Should -Be $([System.Net.Dns]::GetHostByName($ENV:computerName).Hostname) + $currentState.Hostname | Should -Be ([System.Net.Dns]::GetHostEntry((Get-ComputerName)).Hostname) $currentState.Enabled | Should -BeTrue $currentState.URLPrefix | Should -Be 'wsman' } @@ -675,7 +675,7 @@ Describe 'WSManListener\GetCurrentState()' -Tag 'HiddenMember' { Address = '*' CertificateThumbprint = $null - Hostname = $([System.Net.Dns]::GetHostByName($ENV:computerName).Hostname) + Hostname = [System.Net.Dns]::GetHostEntry((Get-ComputerName)).Hostname Enabled = $true URLPrefix = 'wsman' @@ -701,7 +701,7 @@ Describe 'WSManListener\GetCurrentState()' -Tag 'HiddenMember' { $currentState.Address | Should -Be '*' $currentState.Issuer | Should -BeNullOrEmpty $currentState.CertificateThumbprint | Should -BeNullOrEmpty - $currentState.Hostname | Should -Be $([System.Net.Dns]::GetHostByName($ENV:computerName).Hostname) + $currentState.Hostname | Should -Be ([System.Net.Dns]::GetHostEntry((Get-ComputerName)).Hostname) $currentState.Enabled | Should -BeTrue $currentState.URLPrefix | Should -Be 'wsman' } @@ -731,7 +731,7 @@ Describe 'WSManListener\GetCurrentState()' -Tag 'HiddenMember' { Address = '*' CertificateThumbprint = '74FA31ADEA7FDD5333CED10910BFA6F665A1F2FC' - Hostname = $([System.Net.Dns]::GetHostByName($ENV:computerName).Hostname) + Hostname = ([System.Net.Dns]::GetHostEntry((Get-ComputerName)).Hostname) Enabled = $true URLPrefix = 'wsman' @@ -757,7 +757,7 @@ Describe 'WSManListener\GetCurrentState()' -Tag 'HiddenMember' { $currentState.Address | Should -Be '*' $currentState.Issuer | Should -Be 'CN=CONTOSO.COM Issuing CA, DC=CONTOSO, DC=COM' $currentState.CertificateThumbprint | Should -Be '74FA31ADEA7FDD5333CED10910BFA6F665A1F2FC' - $currentState.Hostname | Should -Be $([System.Net.Dns]::GetHostByName($ENV:computerName).Hostname) + $currentState.Hostname | Should -Be ([System.Net.Dns]::GetHostEntry((Get-ComputerName)).Hostname) $currentState.Enabled | Should -BeTrue $currentState.URLPrefix | Should -Be 'wsman' } @@ -941,7 +941,7 @@ Describe 'WSManListener\NewInstance()' -Tag 'HiddenMember' { Should -Invoke -CommandName Get-DscProperty -Exactly -Times 1 -Scope It Should -Invoke -CommandName Find-Certificate -Exactly -Times 1 -Scope It Should -Invoke -CommandName New-WSManInstance -ParameterFilter { - $ValueSet.HostName -eq [System.Net.Dns]::GetHostByName($env:COMPUTERNAME).Hostname + $ValueSet.HostName -eq [System.Net.Dns]::GetHostEntry((Get-ComputerName)).Hostname } -Exactly -Times 1 -Scope It } } diff --git a/tests/Unit/Private/Find-Certificate.Tests.ps1 b/tests/Unit/Private/Find-Certificate.Tests.ps1 index 243c0a9..be01b16 100644 --- a/tests/Unit/Private/Find-Certificate.Tests.ps1 +++ b/tests/Unit/Private/Find-Certificate.Tests.ps1 @@ -35,7 +35,7 @@ BeforeAll { # Create the Mock Objects that will be used for running tests $script:mockCertificateThumbprint = '74FA31ADEA7FDD5333CED10910BFA6F665A1F2FC' $mockFQDN = 'SERVER1.CONTOSO.COM' - $mockHostName = $([System.Net.Dns]::GetHostByName($ENV:computerName).Hostname) + $mockHostName = ([System.Net.Dns]::GetHostEntry((Get-ComputerName)).Hostname) $script:mockIssuer = 'CN=CONTOSO.COM Issuing CA, DC=CONTOSO, DC=COM' $script:mockDN = 'O=Contoso Inc, S=Pennsylvania, C=US' $mockCertificate = @{ From e6922a9bf099240af096e5b2e88319f5da08de73 Mon Sep 17 00:00:00 2001 From: Daniel Hughes Date: Wed, 18 Sep 2024 12:26:18 +0100 Subject: [PATCH 065/134] Update Build files for Classes --- RequiredModules.psd1 | 3 ++- Resolve-Dependency.psd1 | 2 +- build.yaml | 8 +++++--- 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/RequiredModules.psd1 b/RequiredModules.psd1 index 119ddc9..321d0a5 100644 --- a/RequiredModules.psd1 +++ b/RequiredModules.psd1 @@ -1,5 +1,5 @@ @{ - PSDependOptions = @{ + PSDependOptions = @{ AddToPath = $true Target = 'output\RequiredModules' Parameters = @{ @@ -21,6 +21,7 @@ # Build dependencies needed for using the module 'DscResource.Common' = 'latest' + 'DscResource.Base' = 'latest' # Analyzer rules 'DscResource.AnalyzerRules' = 'latest' diff --git a/Resolve-Dependency.psd1 b/Resolve-Dependency.psd1 index 07945f8..bbf34e1 100644 --- a/Resolve-Dependency.psd1 +++ b/Resolve-Dependency.psd1 @@ -3,7 +3,7 @@ AllowPrerelease = $false WithYAML = $true - #UseModuleFast = $true + UseModuleFast = $true #ModuleFastVersion = '0.1.2' #ModuleFastBleedingEdge = $true diff --git a/build.yaml b/build.yaml index e11f517..a8e4b40 100644 --- a/build.yaml +++ b/build.yaml @@ -80,11 +80,15 @@ NestedModule: Path: ./output/RequiredModules/DscResource.Common AddToManifest: false Exclude: PSGetModuleInfo.xml + DscResource.Base: + CopyOnly: true + Path: ./output/RequiredModules/DscResource.Base + AddToManifest: false + Exclude: PSGetModuleInfo.xml #################################################### # Pester Configuration (Sampler) # #################################################### - Pester: Configuration: Run: @@ -105,8 +109,6 @@ Pester: ExcludeFromCodeCoverage: - Modules/DscResource.Common - Modules/DscResource.Base - - prefix.ps1 - - WSManDsc.psm1 #################################################### # Pester Configuration (DscResource.Test) # From 259cca237f16ad9b3e92c52a138ff63c8ec6ce5b Mon Sep 17 00:00:00 2001 From: Daniel Hughes Date: Wed, 18 Sep 2024 13:19:54 +0100 Subject: [PATCH 066/134] Tidy up module psd1 and, remove ref to suffix --- build.yaml | 1 - source/WSManDsc.psd1 | 24 +++--------------------- 2 files changed, 3 insertions(+), 22 deletions(-) diff --git a/build.yaml b/build.yaml index a8e4b40..4dd1f0f 100644 --- a/build.yaml +++ b/build.yaml @@ -45,7 +45,6 @@ CopyPaths: - en-US - DSCResources Prefix: prefix.ps1 -Suffix: suffix.ps1 Encoding: UTF8 VersionedOutputDirectory: true BuiltModuleSubdirectory: builtModule diff --git a/source/WSManDsc.psd1 b/source/WSManDsc.psd1 index f405952..d9a0eec 100644 --- a/source/WSManDsc.psd1 +++ b/source/WSManDsc.psd1 @@ -1,6 +1,6 @@ @{ # Script module or binary module file associated with this manifest. - # RootModule = '' + RootModule = 'WSManDsc.psm1' # Version number of this module. ModuleVersion = '0.0.1' @@ -21,16 +21,7 @@ Description = 'DSC resources for configuring WS-Man.' # Minimum version of the Windows PowerShell engine required by this module - PowerShellVersion = '4.0' - - # Name of the Windows PowerShell host required by this module - # PowerShellHostName = '' - - # Minimum version of the Windows PowerShell host required by this module - # PowerShellHostVersion = '' - - # Minimum version of Microsoft .NET Framework required by this module - # DotNetFrameworkVersion = '' + PowerShellVersion = '5.0' # Minimum version of the common language runtime (CLR) required by this module CLRVersion = '4.0' @@ -53,9 +44,6 @@ # Format files (.ps1xml) to be loaded when importing this module FormatsToProcess = @() - # Modules to import as nested modules of the module specified in RootModule/ModuleToProcess - # NestedModules = @() - # Functions to export from this module FunctionsToExport = @() @@ -69,7 +57,7 @@ AliasesToExport = @() # DSC resources to export from this module - DscResourcesToExport = @('WSManListener', 'WSManConfig', 'WSManServiceConfig') + DscResourcesToExport = @() # List of all modules packaged with this module # ModuleList = @() @@ -101,10 +89,4 @@ } # End of PSData hashtable } # End of PrivateData hashtable - - # HelpInfo URI of this module - # HelpInfoURI = '' - - # Default prefix for commands exported from this module. Override the default prefix using Import-Module -Prefix. - # DefaultCommandPrefix = '' } From f15b94fbd21f1b67ddc94e0c681060f8eca00cc9 Mon Sep 17 00:00:00 2001 From: Daniel Hughes Date: Wed, 18 Sep 2024 13:20:03 +0100 Subject: [PATCH 067/134] Add prefix --- source/prefix.ps1 | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 source/prefix.ps1 diff --git a/source/prefix.ps1 b/source/prefix.ps1 new file mode 100644 index 0000000..168dd74 --- /dev/null +++ b/source/prefix.ps1 @@ -0,0 +1,7 @@ +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' From 376d23951a72b8449c4fc57d61ca854c6a728a8d Mon Sep 17 00:00:00 2001 From: Daniel Hughes Date: Wed, 18 Sep 2024 13:32:04 +0100 Subject: [PATCH 068/134] Move Get-DefaultPort out of the DscResource --- source/Private/Get-DefaultPort.ps1 | 44 ++++++ tests/Unit/Private/Get-DefaultPort.Tests.ps1 | 136 +++++++++++++++++++ 2 files changed, 180 insertions(+) create mode 100644 source/Private/Get-DefaultPort.ps1 create mode 100644 tests/Unit/Private/Get-DefaultPort.Tests.ps1 diff --git a/source/Private/Get-DefaultPort.ps1 b/source/Private/Get-DefaultPort.ps1 new file mode 100644 index 0000000..711b38f --- /dev/null +++ b/source/Private/Get-DefaultPort.ps1 @@ -0,0 +1,44 @@ +<# + .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)] + [ValidateSet('HTTP', 'HTTPS')] + [System.String] + $Transport, + + [Parameter()] + [System.UInt16] + $Port + ) + + process + { + if (-not $Port) + { + # Set the default port because none was provided + if ($Transport -eq 'HTTP') + { + $Port = 5985 + } + else + { + $Port = 5986 + } + } + + return $Port + } +} diff --git a/tests/Unit/Private/Get-DefaultPort.Tests.ps1 b/tests/Unit/Private/Get-DefaultPort.Tests.ps1 new file mode 100644 index 0000000..687a04c --- /dev/null +++ b/tests/Unit/Private/Get-DefaultPort.Tests.ps1 @@ -0,0 +1,136 @@ +[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '')] +param () + +BeforeDiscovery { + try + { + if (-not (Get-Module -Name 'DscResource.Test')) + { + # Assumes dependencies has been resolved, so if this module is not available, run 'noop' task. + if (-not (Get-Module -Name 'DscResource.Test' -ListAvailable)) + { + # Redirect all streams to $null, except the error stream (stream 2) + & "$PSScriptRoot/../../../build.ps1" -Tasks 'noop' 2>&1 4>&1 5>&1 6>&1 > $null + } + + # If the dependencies has not been resolved, this will throw an error. + Import-Module -Name 'DscResource.Test' -Force -ErrorAction 'Stop' + } + } + catch [System.IO.FileNotFoundException] + { + throw 'DscResource.Test module dependency not found. Please run ".\build.ps1 -ResolveDependency -Tasks build" first.' + } +} + +BeforeAll { + $script:dscModuleName = 'WSManDsc' + + Import-Module -Name $script:dscModuleName + + $PSDefaultParameterValues['InModuleScope:ModuleName'] = $script:dscModuleName + $PSDefaultParameterValues['Mock:ModuleName'] = $script:dscModuleName + $PSDefaultParameterValues['Should:ModuleName'] = $script:dscModuleName +} + +AfterAll { + $PSDefaultParameterValues.Remove('InModuleScope:ModuleName') + $PSDefaultParameterValues.Remove('Mock:ModuleName') + $PSDefaultParameterValues.Remove('Should:ModuleName') + + # Unload the module being tested so that it doesn't impact any other tests. + Get-Module -Name $script:dscModuleName -All | Remove-Module -Force +} + +Describe 'Get-DefaultPort' -Tag 'Private' { + Context 'When a port is not provided' { + BeforeDiscovery { + $testCases = @( + @{ + Transport = 'HTTP' + ExpectedValue = 5985 + } + @{ + Transport = 'HTTPS' + ExpectedValue = 5986 + } + ) + } + + It 'Should return '''' for '''' transport' -ForEach $testCases { + InModuleScope -Parameters $_ -ScriptBlock { + Set-StrictMode -Version 1.0 + + $mockParams = @{ + Transport = $Transport + } + + $result = Get-DefaultPort @mockParams + + $result | Should -Be $ExpectedValue + $result | Should -BeOfType System.UInt16 + } + } + } + + Context 'When a port is provided' { + BeforeDiscovery { + $testCases = @( + @{ + Transport = 'HTTP' + Port = 1000 + } + @{ + Transport = 'HTTPS' + Port = 2000 + } + ) + } + + It 'Should return '''' for '''' transport' -ForEach $testCases { + InModuleScope -Parameters $_ -ScriptBlock { + Set-StrictMode -Version 1.0 + + $mockParams = @{ + Transport = $Transport + Port = $Port + } + + $result = Get-DefaultPort @mockParams + + $result | Should -Be $Port + $result | Should -BeOfType System.UInt16 + } + } + } + + Context 'When Transport is not ''HTTP'' or ''HTTPS''' { + It 'Should throw the correct exception' { + InModuleScope -ScriptBlock { + Set-StrictMode -Version 1.0 + + $mockParams = @{ + Transport = $null + Port = 9999 + } + + { Get-DefaultPort @mockParams } | Should -Throw + } + } + } + + Context 'When Port is not valid' { + It 'Should throw the correct exception' { + InModuleScope -ScriptBlock { + Set-StrictMode -Version 1.0 + + $mockParams = @{ + Transport = 'HTTP' + Port = 'Something' + } + + { Get-DefaultPort @mockParams } | Should -Not -Throw + } + } + } +} From c453b389a7efb9b8cc0c026d4a7474f33008d805 Mon Sep 17 00:00:00 2001 From: Daniel Hughes Date: Wed, 18 Sep 2024 13:34:05 +0100 Subject: [PATCH 069/134] Add WSManReason --- source/Classes/001.WSManReason.ps1 | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 source/Classes/001.WSManReason.ps1 diff --git a/source/Classes/001.WSManReason.ps1 b/source/Classes/001.WSManReason.ps1 new file mode 100644 index 0000000..6b939ee --- /dev/null +++ b/source/Classes/001.WSManReason.ps1 @@ -0,0 +1,21 @@ +<# + .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 +} From 388e631a36d49791163ae2c19a6110cb74d1eeed Mon Sep 17 00:00:00 2001 From: Daniel Hughes Date: Wed, 18 Sep 2024 15:21:03 +0100 Subject: [PATCH 070/134] `Get-InvalidOperationRecord` now provided by `DscResource.Test` --- tests/TestHelpers/CommonTestHelper.psm1 | 40 +------------------------ 1 file changed, 1 insertion(+), 39 deletions(-) diff --git a/tests/TestHelpers/CommonTestHelper.psm1 b/tests/TestHelpers/CommonTestHelper.psm1 index cdc4a0a..0542934 100644 --- a/tests/TestHelpers/CommonTestHelper.psm1 +++ b/tests/TestHelpers/CommonTestHelper.psm1 @@ -43,44 +43,6 @@ function Get-InvalidArgumentRecord .PARAMETER ErrorRecord The error record containing the exception that is causing this terminating error #> -function Get-InvalidOperationRecord -{ - [CmdletBinding()] - param - ( - [ValidateNotNullOrEmpty()] - [System.String] - $Message, - - [ValidateNotNull()] - [System.Management.Automation.ErrorRecord] - $ErrorRecord - ) - - if ($null -eq $Message) - { - $invalidOperationException = New-Object -TypeName 'InvalidOperationException' - } - elseif ($null -eq $ErrorRecord) - { - $invalidOperationException = - New-Object -TypeName 'InvalidOperationException' -ArgumentList @( $Message ) - } - else - { - $invalidOperationException = - New-Object -TypeName 'InvalidOperationException' -ArgumentList @( $Message, - $ErrorRecord.Exception ) - } - - $newObjectParams = @{ - TypeName = 'System.Management.Automation.ErrorRecord' - ArgumentList = @( $invalidOperationException.ToString(), 'MachineStateIncorrect', - 'InvalidOperation', $null ) - } - return New-Object @newObjectParams -} Export-ModuleMember -Function ` - Get-InvalidArgumentRecord, ` - Get-InvalidOperationRecord + Get-InvalidArgumentRecord From 5ca1705e982e8f1f77d6db5c6f2a92a42bcf9cde Mon Sep 17 00:00:00 2001 From: Daniel Hughes Date: Wed, 18 Sep 2024 16:19:10 +0100 Subject: [PATCH 071/134] Remove Mof WSManListener --- .../DSC_WSManListener/DSC_WSManListener.psm1 | 783 ---------- .../DSC_WSManListener.schema.mof | 18 - .../DSCResources/DSC_WSManListener/README.md | 16 - .../en-US/DSC_WSManListener.strings.psd1 | 25 - tests/Unit/DSC_WSManListener.Tests.ps1 | 1326 ----------------- 5 files changed, 2168 deletions(-) delete mode 100644 source/DSCResources/DSC_WSManListener/DSC_WSManListener.psm1 delete mode 100644 source/DSCResources/DSC_WSManListener/DSC_WSManListener.schema.mof delete mode 100644 source/DSCResources/DSC_WSManListener/README.md delete mode 100644 source/DSCResources/DSC_WSManListener/en-US/DSC_WSManListener.strings.psd1 delete mode 100644 tests/Unit/DSC_WSManListener.Tests.ps1 diff --git a/source/DSCResources/DSC_WSManListener/DSC_WSManListener.psm1 b/source/DSCResources/DSC_WSManListener/DSC_WSManListener.psm1 deleted file mode 100644 index 045e878..0000000 --- a/source/DSCResources/DSC_WSManListener/DSC_WSManListener.psm1 +++ /dev/null @@ -1,783 +0,0 @@ -$modulePath = Join-Path -Path (Split-Path -Path (Split-Path -Path $PSScriptRoot -Parent) -Parent) -ChildPath 'Modules' - -Import-Module -Name (Join-Path -Path $modulePath -ChildPath 'DscResource.Common') - -# Import Localization Strings -$script:localizedData = Get-LocalizedData -DefaultUICulture 'en-US' - - -# Standard Transport Ports -$Default_HTTP_Port = 5985 -$Default_HTTPS_Port = 5986 - -<# - .SYNOPSIS - Returns the current WS-Man Listener details. - - .PARAMETER Transport - The transport type of WS-Man Listener. - - .PARAMETER Ensure - Specifies whether the WS-Man Listener should exist. -#> -function Get-TargetResource -{ - [CmdletBinding()] - [OutputType([Hashtable])] - param - ( - [Parameter(Mandatory = $true)] - [ValidateSet('HTTP', 'HTTPS')] - [System.String] - $Transport, - - [Parameter(Mandatory = $true)] - [ValidateSet('Present', 'Absent')] - [System.String] - $Ensure - ) - - Write-Verbose -Message ( @( - "$($MyInvocation.MyCommand): " - $($script:localizedData.GettingListenerMessage) - ) -join '' ) - - $returnValue = @{ - Transport = $Transport - } - - # Lookup the existing Listener - $listener = Get-Listener -Transport $Transport - - if ($listener) - { - $certificate = '' - - # An existing listener matching the transport was found - Write-Verbose -Message ( @( - "$($MyInvocation.MyCommand): " - $($script:localizedData.ListenerExistsMessage) ` - -f $Transport - ) -join '' ) - - if ($listener.CertificateThumbprint) - { - $certificate = Find-Certificate -CertificateThumbprint $listener.CertificateThumbprint - } - - $returnValue += @{ - Ensure = 'Present' - Port = $listener.Port - Address = $listener.Address - Issuer = $certificate.Issuer - SubjectFormat = $null - MatchAlternate = $null - DN = $null - Hostname = $listener.Hostname - Enabled = $listener.Enabled - URLPrefix = $listener.URLPrefix - CertificateThumbprint = $listener.CertificateThumbprint - } - } - else - { - # An existing listener matching the transport was not found - Write-Verbose -Message ( @( - "$($MyInvocation.MyCommand): " - $($script:localizedData.ListenerDoesNotExistMessage) ` - -f $Transport - ) -join '' ) - - $returnValue += @{ - Ensure = 'Absent' - Port = $null - Address = $null - Issuer = $null - SubjectFormat = $null - MatchAlternate = $null - DN = $null - Hostname = $null - Enabled = $null - URLPrefix = $null - CertificateThumbprint = $null - } - } # if - - return $returnValue -} # Get-TargetResource - -<# - .SYNOPSIS - Sets the state of a WS-Man Listener. - - .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 DN - This is a Distinguished Name component that will be 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 Set-TargetResource -{ - [CmdletBinding()] - param - ( - [Parameter(Mandatory = $true)] - [ValidateSet('HTTP', 'HTTPS')] - [System.String] - $Transport, - - [Parameter(Mandatory = $true)] - [ValidateSet('Present', 'Absent')] - [System.String] - $Ensure, - - [Parameter()] - [System.UInt16] - $Port, - - [Parameter()] - [System.String] - $Address = '*', - - [Parameter()] - [System.String] - $Issuer, - - [Parameter()] - [ValidateSet('Both', 'FQDNOnly', 'NameOnly')] - [System.String] - $SubjectFormat = 'Both', - - [Parameter()] - [System.Boolean] - $MatchAlternate, - - [Parameter()] - [System.String] - $DN, - - [Parameter()] - [System.String] - $CertificateThumbprint, - - [Parameter()] - [System.String] - $Hostname - ) - - Write-Verbose -Message ( @( - "$($MyInvocation.MyCommand): " - $($script:localizedData.SettingListenerMessage) - ) -join '' ) - - # Lookup the existing Listener - $listener = 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): " - $($script:localizedData.EnsureListenerExistsMessage) ` - -f $Transport, $Port - ) -join '' ) - - if ($listener) - { - # The Listener exists already - delete it - Write-Verbose -Message ( @( - "$($MyInvocation.MyCommand): " - $($script:localizedData.ListenerExistsRemoveMessage) ` - -f $Transport, $Port - ) -join '' ) - - Remove-WSManInstance ` - -ResourceURI 'winrm/config/Listener' ` - -SelectorSet @{ - Transport = $listener.Transport - Address = $listener.Address - } - } - else - { - # Ths listener doesn't exist - do nothing - Write-Verbose -Message ( @( - "$($MyInvocation.MyCommand): " - $($script:localizedData.ListenerOnPortDoesNotExistMessage) ` - -f $Transport, $Port - ) -join '' ) - } - - # Create the listener - if ($Transport -eq 'HTTPS') - { - # Find the certificate to use for the HTTPS Listener - $null = $PSBoundParameters.Remove('Transport') - $null = $PSBoundParameters.Remove('Ensure') - $null = $PSBoundParameters.Remove('Port') - $null = $PSBoundParameters.Remove('Address') - - $certificate = Find-Certificate @PSBoundParameters - [System.String] $thumbprint = $certificate.thumbprint - - if ($thumbprint) - { - # A certificate was found, so use it to enable the HTTPS WinRM listener - Write-Verbose -Message ( @( - "$($MyInvocation.MyCommand): " - $($script:localizedData.CreatingListenerMessage) ` - -f $Transport, $Port - ) -join '' ) - - if ([System.String]::IsNullOrEmpty($Hostname)) - { - $Hostname = [System.Net.Dns]::GetHostByName($ENV:computerName).Hostname - } - - New-WSManInstance ` - -ResourceURI 'winrm/config/Listener' ` - -SelectorSet @{ - Address = $Address - Transport = $Transport - } ` - -ValueSet @{ - Hostname = $Hostname - CertificateThumbprint = $thumbprint - Port = $Port - } ` - -ErrorAction Stop - } - else - { - # A certificate could not be found to use for the HTTPS listener - New-InvalidArgumentException ` - -Message ($script:localizedData.ListenerCreateFailNoCertError -f ` - $Transport, $Port) ` - -Argument 'Issuer' - } # if - } - else - { - # Create a plain HTTP listener - Write-Verbose -Message ( @( - "$($MyInvocation.MyCommand): " - $($script: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): " - $($script:localizedData.EnsureListenerDoesNotExistMessage) ` - -f $Transport, $Port - ) -join '' ) - - if ($listener) - { - # The listener does exist - so delete it - Write-Verbose -Message ( @( - "$($MyInvocation.MyCommand): " - $($script:localizedData.ListenerExistsRemoveMessage) ` - -f $Transport, $Port - ) -join '' ) - - Remove-WSManInstance ` - -ResourceURI 'winrm/config/Listener' ` - -SelectorSet @{ - Transport = $listener.Transport - Address = $listener.Address - } - } - } # if -} # Set-TargetResource - -<# - .SYNOPSIS - Tests the state of a WS-Man Listener. - - .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 DN - This is a Distinguished Name component that will be 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 Test-TargetResource -{ - [CmdletBinding()] - [OutputType([System.Boolean])] - param - ( - [Parameter(Mandatory = $true)] - [ValidateSet('HTTP', 'HTTPS')] - [System.String] - $Transport, - - [Parameter(Mandatory = $true)] - [ValidateSet('Present', 'Absent')] - [System.String] - $Ensure, - - [Parameter()] - [System.UInt16] - $Port, - - [Parameter()] - [System.String] - $Address = '*', - - [Parameter()] - [System.String] - $Issuer, - - [Parameter()] - [ValidateSet('Both', 'FQDNOnly', 'NameOnly')] - [System.String] - $SubjectFormat = 'Both', - - [Parameter()] - [System.Boolean] - $MatchAlternate, - - [Parameter()] - [System.String] - $DN, - - [Parameter()] - [System.String] - $CertificateThumbprint, - - [Parameter()] - [System.String] - $Hostname - ) - - # Flag to signal whether settings are correct - $desiredConfigurationMatch = $true - - Write-Verbose -Message ( @( - "$($MyInvocation.MyCommand): " - $($script:localizedData.TestingListenerMessage) - ) -join '' ) - - # Lookup the existing Listener - $listener = 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 ($listener) - { - # The Listener exists already - Write-Verbose -Message ( @( - "$($MyInvocation.MyCommand): " - $($script:localizedData.ListenerExistsMessage) ` - -f $Transport - ) -join '' ) - - # Check it is setup as per parameters - if ($listener.Port -ne $Port) - { - Write-Verbose -Message ( @( - "$($MyInvocation.MyCommand): " - $($script:localizedData.ListenerOnWrongPortMessage) ` - -f $Transport, $listener.Port, $Port - ) -join '' ) - $desiredConfigurationMatch = $false - } - - if ($listener.Address -ne $Address) - { - Write-Verbose -Message ( @( - "$($MyInvocation.MyCommand): " - $($script:localizedData.ListenerOnWrongAddressMessage) ` - -f $Transport, $listener.Address, $Address - ) -join '' ) - $desiredConfigurationMatch = $false - } - - if ($PSBoundParameters.ContainsKey('Hostname') -and $listener.Hostname -ne $Hostname) - { - Write-Verbose -Message ( @( - "$($MyInvocation.MyCommand): " - $($script:localizedData.ListenerOnWrongHostnameMessage) ` - -f $Transport, $listener.Hostname, $Hostname - ) -join '' ) - $desiredConfigurationMatch = $false - } - - if ($PSBoundParameters.ContainsKey('CertificateThumbprint') -and $listener.CertificateThumbprint -ne $CertificateThumbprint) - { - Write-Verbose -Message ( @( - "$($MyInvocation.MyCommand): " - $($script:localizedData.ListenerOnWrongCertificateThumbprintMessage) ` - -f $Transport, $listener.CertificateThumbprint, $CertificateThumbprint - ) -join '' ) - $desiredConfigurationMatch = $false - } - } - else - { - # Ths listener doesn't exist but should - Write-Verbose -Message ( @( - "$($MyInvocation.MyCommand): " - $($script:localizedData.ListenerDoesNotExistButShouldMessage) ` - -f $Transport - ) -join '' ) - $desiredConfigurationMatch = $false - } - } - else - { - # The listener should not exist - if ($listener) - { - # The listener exists but should not - Write-Verbose -Message ( @( - "$($MyInvocation.MyCommand): " - $($script:localizedData.ListenerExistsButShouldNotMessage) ` - -f $Transport - ) -join '' ) - $desiredConfigurationMatch = $false - } - else - { - # The listener does not exist and should not - Write-Verbose -Message ( @( - "$($MyInvocation.MyCommand): " - $($script:localizedData.ListenerDoesNotExistAndShouldNotMessage) ` - -f $Transport - ) -join '' ) - } - } # if - - return $desiredConfigurationMatch -} # Test-TargetResource - -<# - .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([Hashtable])] - param - ( - [Parameter(Mandatory = $true)] - [ValidateSet('HTTP', 'HTTPS')] - [System.String] - $Transport - ) - - $listeners = @(Get-WSManInstance ` - -ResourceURI 'winrm/config/Listener' ` - -Enumerate) - - if ($listeners) - { - return $listeners.Where( { ($_.Transport -eq $Transport) ` - -and ($_.Source -ne 'Compatibility') } ) - } -} # Get-Listener - -<# - .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)] - [ValidateSet('HTTP', 'HTTPS')] - [System.String] - $Transport, - - [Parameter()] - [System.UInt16] - $Port - ) - - if (-not $Port) - { - # Set the default port because none was provided - if ($Transport -eq 'HTTP') - { - $Port = $Default_HTTP_Port - } - else - { - $Port = $Default_HTTPS_Port - } - } - return $Port -} - -<# - .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 DN - This is a Distinguished Name component that will be 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()] - [ValidateSet('Both', 'FQDNOnly', 'NameOnly')] - [System.String] - $SubjectFormat = 'Both', - - [Parameter()] - [System.Boolean] - $MatchAlternate, - - [Parameter()] - [System.String] - $DN, - - [Parameter()] - [System.String] - $CertificateThumbprint, - - [Parameter()] - [System.String] - $Hostname - ) - - if ($PSBoundParameters.ContainsKey('CertificateThumbprint')) - { - Write-Verbose -Message ( @( - "$($MyInvocation.MyCommand): " - $($script:localizedData.FindCertificateByThumbprintMessage) ` - -f $CertificateThumbprint - ) -join '' ) - - $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 'Both', 'FQDNOnly') - { - # Lookup the certificate using the FQDN of the machine - if ([System.String]::IsNullOrEmpty($Hostname)) - { - $Hostname = [System.Net.Dns]::GetHostByName($ENV:computerName).Hostname - } - $Subject = "CN=$Hostname" - - if ($PSBoundParameters.ContainsKey('DN')) - { - $Subject = "$Subject, $DN" - } # if - - if ($MatchAlternate) - { - # Try and lookup the certificate using the subject and the alternate name - Write-Verbose -Message ( @( - "$($MyInvocation.MyCommand): " - $($script:localizedData.FindCertificateAlternateMessage) ` - -f $Subject, $Issuer, $Hostname - ) -join '' ) - - $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 ( @( - "$($MyInvocation.MyCommand): " - $($script:localizedData.FindCertificateMessage) ` - -f $Subject, $Issuer - ) -join '' ) - - $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 'Both', 'NameOnly')) - { - # If could not find an FQDN cert, try for one issued to the computer name - [System.String] $Hostname = $ENV:ComputerName - [System.String] $Subject = "CN=$Hostname" - - if ($PSBoundParameters.ContainsKey('DN')) - { - $Subject = "$Subject, $DN" - } # if - - if ($MatchAlternate) - { - # Try and lookup the certificate using the subject and the alternate name - Write-Verbose -Message ( @( - "$($MyInvocation.MyCommand): " - $($script:localizedData.FindCertificateAlternateMessage) ` - -f $Subject, $Issuer, $Hostname - ) -join '' ) - - $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 ( @( - "$($MyInvocation.MyCommand): " - $($script:localizedData.FindCertificateMessage) ` - -f $Subject, $Issuer - ) -join '' ) - - $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 ( @( - "$($MyInvocation.MyCommand): " - $($script:localizedData.CertificateFoundMessage) ` - -f $certificate.thumbprint - ) -join '' ) - } - else - { - Write-Verbose -Message ( @( - "$($MyInvocation.MyCommand): " - $($script:localizedData.CertificateNotFoundMessage) ` - ) -join '' ) - } # if - - return $certificate -} # Find-Certificate - -Export-ModuleMember -Function *-TargetResource diff --git a/source/DSCResources/DSC_WSManListener/DSC_WSManListener.schema.mof b/source/DSCResources/DSC_WSManListener/DSC_WSManListener.schema.mof deleted file mode 100644 index 52cbeea..0000000 --- a/source/DSCResources/DSC_WSManListener/DSC_WSManListener.schema.mof +++ /dev/null @@ -1,18 +0,0 @@ -[ClassVersion("1.0.0.0"), FriendlyName("WSManListener")] -class DSC_WSManListener : OMI_BaseResource -{ - [Key, Description("The transport type of WS-Man Listener."), ValueMap{"HTTP","HTTPS"}, Values{"HTTP","HTTPS"}] String Transport; - [Required, Description("Specifies whether the WS-Man Listener should exist."), ValueMap{"Present","Absent"}, Values{"Present","Absent"}] String Ensure; - [Write, Description("The port the WS-Man Listener should use. Defaults to 5985 for HTTP and 5986 for HTTPS listeners.")] uint16 Port; - [Write, Description("The Address that the WS-Man Listener will be bound to. The default is * (any address).")] String Address; - [Write, Description("The Issuer of the certificate to use for the HTTPS WS-Man Listener if a thumbprint is not specified.")] String Issuer; - [Write, Description("The format used to match the certificate subject to use for an HTTPS WS-Man Listener if a thumbprint is not specified."), ValueMap{"Both","FQDNOnly","NameOnly"}, Values{"Both","FQDNOnly","NameOnly"}] String SubjectFormat; - [Write, Description("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.")] Boolean MatchAlternate; - [Write, Description("This is a Distinguished Name component that will be used to identify the certificate to use for the HTTPS WS-Man Listener if a thumbprint is not specified.")] String DN; - [Write, Description("The host name that a HTTPS WS-Man Listener will be bound to. If not specified it will default to the computer name of the node.")] String Hostname; - [Read, Description("Returns true if the existing WS-Man Listener is enabled.")] Boolean Enabled; - [Read, Description("The URL Prefix of the existing WS-Man Listener.")] String URLPrefix; - [Write, Description("The Thumbprint of the certificate to use for the HTTPS WS-Man Listener.")] String CertificateThumbprint; -}; - - diff --git a/source/DSCResources/DSC_WSManListener/README.md b/source/DSCResources/DSC_WSManListener/README.md deleted file mode 100644 index a63b4e0..0000000 --- a/source/DSCResources/DSC_WSManListener/README.md +++ /dev/null @@ -1,16 +0,0 @@ -# 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. diff --git a/source/DSCResources/DSC_WSManListener/en-US/DSC_WSManListener.strings.psd1 b/source/DSCResources/DSC_WSManListener/en-US/DSC_WSManListener.strings.psd1 deleted file mode 100644 index a28a7b1..0000000 --- a/source/DSCResources/DSC_WSManListener/en-US/DSC_WSManListener.strings.psd1 +++ /dev/null @@ -1,25 +0,0 @@ -ConvertFrom-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 an 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. - ListenerOnWrongHostnameMessage = {0} Listener Hostname is {1}, should be {2}. Change required. - ListenerOnWrongCertificateThumbprintMessage = {0} Listener Certificate Thumbprint is {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. - FindCertificateAlternateMessage = Looking for machine server certificate with subject '{0}' issued by '{1}' and DNS name '{2}'. - FindCertificateMessage = Looking for machine server certificate with subject '{0}' issued by '{1}'. - FindCertificateByThumbprintMessage = Looking for machine server certificate with thumbprint '{0}'. - CertificateFoundMessage = Certificate found with thumbprint '{0}' to use for HTTPS Listener. - CertificateNotFoundMessage = Certificate not found. -'@ diff --git a/tests/Unit/DSC_WSManListener.Tests.ps1 b/tests/Unit/DSC_WSManListener.Tests.ps1 deleted file mode 100644 index 776f354..0000000 --- a/tests/Unit/DSC_WSManListener.Tests.ps1 +++ /dev/null @@ -1,1326 +0,0 @@ -<# - .SYNOPSIS - Unit test for DSC_WSManListener DSC resource. - - .NOTES -#> - -# Suppressing this rule because Script Analyzer does not understand Pester's syntax. -[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '')] -param () - -BeforeDiscovery { - try - { - if (-not (Get-Module -Name 'DscResource.Test')) - { - # Assumes dependencies has been resolved, so if this module is not available, run 'noop' task. - if (-not (Get-Module -Name 'DscResource.Test' -ListAvailable)) - { - # Redirect all streams to $null, except the error stream (stream 2) - & "$PSScriptRoot/../../build.ps1" -Tasks 'noop' 2>&1 4>&1 5>&1 6>&1 > $null - } - - # This will throw an error if the dependencies have not been resolved. - Import-Module -Name 'DscResource.Test' -Force -ErrorAction 'Stop' - } - } - catch [System.IO.FileNotFoundException] - { - throw 'DscResource.Test module dependency not found. Please run ".\build.ps1 -ResolveDependency -Tasks build" first.' - } - - $script:dscResourceName = 'DSC_WSManListener' -} - -BeforeAll { - $script:dscModuleName = 'WSManDsc' - $script:dscResourceName = 'DSC_WSManListener' - - $script:testEnvironment = Initialize-TestEnvironment ` - -DSCModuleName $script:dscModuleName ` - -DSCResourceName $script:dscResourceName ` - -ResourceType 'Mof' ` - -TestType 'Unit' - - Import-Module -Name (Join-Path -Path $PSScriptRoot -ChildPath '..\TestHelpers\CommonTestHelper.psm1') - - $PSDefaultParameterValues['InModuleScope:ModuleName'] = $script:dscResourceName - $PSDefaultParameterValues['Mock:ModuleName'] = $script:dscResourceName - $PSDefaultParameterValues['Should:ModuleName'] = $script:dscResourceName - - # Create the Mock Objects that will be used for running tests - $script:mockCertificateThumbprint = '74FA31ADEA7FDD5333CED10910BFA6F665A1F2FC' - $mockFQDN = 'SERVER1.CONTOSO.COM' - $mockHostName = $([System.Net.Dns]::GetHostByName($ENV:computerName).Hostname) - $script:mockIssuer = 'CN=CONTOSO.COM Issuing CA, DC=CONTOSO, DC=COM' - $script:mockDN = 'O=Contoso Inc, S=Pennsylvania, C=US' - $mockCertificate = @{ - Thumbprint = $mockCertificateThumbprint - Subject = "CN=$mockHostName" - Issuer = $mockIssuer - Extensions = @{ EnhancedKeyUsages = @{ FriendlyName = 'Server Authentication' } } - DNSNameList = @{ Unicode = $mockHostName } - } - - $script:mockCertificateDN = @{ - Thumbprint = $mockCertificateThumbprint - Subject = "CN=$mockHostName, $mockDN" - Issuer = $mockIssuer - Extensions = @{ EnhancedKeyUsages = @{ FriendlyName = 'Server Authentication' } } - DNSNameList = @{ Unicode = $mockHostName } - } - - $script:mockListenerHTTP = @{ - cfg = 'http://schemas.microsoft.com/wbem/wsman/1/config/listener' - xsi = 'http://www.w3.org/2001/XMLSchema-instance' - lang = 'en-US' - Address = '*' - Transport = 'HTTP' - Port = 5985 - Hostname = '' - Enabled = 'true' - URLPrefix = 'wsman' - CertificateThumbprint = '' - } - - $script:mockListenerHTTPS = @{ - cfg = 'http://schemas.microsoft.com/wbem/wsman/1/config/listener' - xsi = 'http://www.w3.org/2001/XMLSchema-instance' - lang = 'en-US' - Address = '*' - Transport = 'HTTPS' - Port = 5986 - Hostname = $mockFQDN - Enabled = 'true' - URLPrefix = 'wsman' - CertificateThumbprint = $mockCertificateThumbprint - } -} - -AfterAll { - $PSDefaultParameterValues.Remove('InModuleScope:ModuleName') - $PSDefaultParameterValues.Remove('Mock:ModuleName') - $PSDefaultParameterValues.Remove('Should:ModuleName') - - Restore-TestEnvironment -TestEnvironment $script:testEnvironment - - # Unload the module being tested so that it doesn't impact any other tests. - Get-Module -Name $script:dscResourceName -All | Remove-Module -Force - - # Remove module common test helper. - Get-Module -Name 'CommonTestHelper' -All | Remove-Module -Force -} - -Describe "$($script:dscResourceName)\Get-TargetResource" -Tag 'Get' { - Context 'No listeners exist' { - BeforeAll { - Mock -CommandName Get-WSManInstance - } - - It 'Should return absent listener' { - InModuleScope -ScriptBlock { - Set-StrictMode -Version 1.0 - - $result = Get-TargetResource ` - -Transport HTTP ` - -Ensure Present ` - -Verbose:$VerbosePreference - $result.Ensure | Should -Be 'Absent' - } - } - - It 'Should call Get-WSManInstance once' { - Should -Invoke ` - -CommandName Get-WSManInstance ` - -Exactly -Times 1 ` - -Scope Context - } - } - - Context 'Requested listener does not exist' { - BeforeAll { - Mock -CommandName Get-WSManInstance -MockWith { - @($mockListenerHTTP) - } - } - - It 'Should return absent listener' { - InModuleScope -Parameters @{ - mockListenerHTTPS = $script:mockListenerHTTPS - } -ScriptBlock { - Set-StrictMode -Version 1.0 - - $result = Get-TargetResource ` - -Transport $mockListenerHTTPS.Transport ` - -Ensure Present ` - -Verbose:$VerbosePreference - - $result.Ensure | Should -Be 'Absent' - } - } - - It 'Should call Get-WSManInstance once' { - Should -Invoke ` - -CommandName Get-WSManInstance ` - -Exactly -Times 1 ` - -Scope Context - } - } - - Context 'Requested listener does exist' { - BeforeAll { - Mock -CommandName Get-WSManInstance -MockWith { - @($mockListenerHTTP) - } - } - - It 'Should return correct listener' { - InModuleScope -Parameters @{ - mockListenerHTTP = $script:mockListenerHTTP - } -ScriptBlock { - Set-StrictMode -Version 1.0 - - $result = Get-TargetResource ` - -Transport $mockListenerHTTP.Transport ` - -Ensure Present ` - -Verbose:$VerbosePreference - - $result.Ensure | Should -Be 'Present' - $result.Port | Should -Be $mockListenerHTTP.Port - $result.Address | Should -Be $mockListenerHTTP.Address - $result.HostName | Should -Be $mockListenerHTTP.HostName - $result.Enabled | Should -Be $mockListenerHTTP.Enabled - $result.URLPrefix | Should -Be $mockListenerHTTP.URLPrefix - $result.CertificateThumbprint | Should -Be $mockListenerHTTP.CertificateThumbprint - } - } - - It 'Should call Get-WSManInstance once' { - Should -Invoke ` - -CommandName Get-WSManInstance ` - -Exactly -Times 1 ` - -Scope Context - } - } -} - -Describe "$($script:dscResourceName)\Set-TargetResource" -Tag 'Set' { - Context 'HTTP Listener does not exist but should' { - BeforeAll { - Mock -CommandName Get-WSManInstance - Mock -CommandName Remove-WSManInstance - Mock -CommandName New-WSManInstance - Mock -CommandName Find-Certificate - } - - It 'Should not throw error' { - InModuleScope -Parameters @{ - mockListenerHTTP = $script:mockListenerHTTP - } -ScriptBlock { - Set-StrictMode -Version 1.0 - - { Set-TargetResource ` - -Transport $mockListenerHTTP.Transport ` - -Ensure 'Present' ` - -Verbose:$VerbosePreference } | Should -Not -Throw - } - } - - It 'Should call expected Mocks' { - Should -Invoke ` - -CommandName Get-WSManInstance ` - -Exactly -Times 1 ` - -Scope Context - Should -Invoke ` - -CommandName Remove-WSManInstance ` - -Exactly -Times 0 ` - -Scope Context - Should -Invoke ` - -CommandName New-WSManInstance ` - -Exactly -Times 1 ` - -Scope Context - Should -Invoke ` - -CommandName Find-Certificate ` - -Exactly -Times 0 ` - -Scope Context - } - } - - Context 'HTTPS Listener does not exist but should' { - BeforeAll { - Mock -CommandName Get-WSManInstance - Mock -CommandName Remove-WSManInstance - Mock -CommandName New-WSManInstance - Mock -CommandName Find-Certificate -MockWith { - return $mockCertificate - } - } - - It 'Should not throw error' { - InModuleScope -Parameters @{ - mockListenerHTTPS = $script:mockListenerHTTPS - mockIssuer = $script:mockIssuer - } -ScriptBlock { - Set-StrictMode -Version 1.0 - - { Set-TargetResource ` - -Transport $mockListenerHTTPS.Transport ` - -Ensure 'Present' ` - -Issuer $mockIssuer ` - -Verbose:$VerbosePreference } | Should -Not -Throw - } - } - - It 'Should call expected Mocks' { - Should -Invoke ` - -CommandName Get-WSManInstance ` - -Exactly -Times 1 ` - -Scope Context - Should -Invoke ` - -CommandName Remove-WSManInstance ` - -Exactly -Times 0 ` - -Scope Context - Should -Invoke ` - -CommandName New-WSManInstance ` - -Exactly -Times 1 ` - -Scope Context - Should -Invoke ` - -CommandName Find-Certificate ` - -Exactly -Times 1 ` - -Scope Context - } - } - - Context 'HTTPS Listener does not exist but should but certificate missing' { - BeforeAll { - Mock -CommandName Get-WSManInstance - Mock -CommandName Remove-WSManInstance - Mock -CommandName New-WSManInstance - Mock -CommandName Find-Certificate - } - - It 'Should throw error' { - InModuleScope -Parameters @{ - mockListenerHTTPS = $script:mockListenerHTTPS - mockIssuer = $script:mockIssuer - } -ScriptBlock { - Set-StrictMode -Version 1.0 - - $errorRecord = Get-InvalidArgumentRecord ` - -Message ($script:localizedData.ListenerCreateFailNoCertError -f ` - $mockListenerHTTPS.Transport, '5986') ` - -ArgumentName 'Issuer' - - { Set-TargetResource ` - -Transport $mockListenerHTTPS.Transport ` - -Ensure 'Present' ` - -Issuer $mockIssuer ` - -Verbose:$VerbosePreference } | Should -Throw $errorRecord - } - } - - It 'Should call expected Mocks' { - Should -Invoke ` - -CommandName Get-WSManInstance ` - -Exactly -Times 1 ` - -Scope Context - Should -Invoke ` - -CommandName Remove-WSManInstance ` - -Exactly -Times 0 ` - -Scope Context - Should -Invoke ` - -CommandName New-WSManInstance ` - -Exactly -Times 0 ` - -Scope Context - Should -Invoke ` - -CommandName Find-Certificate ` - -Exactly -Times 1 ` - -Scope Context - } - } - - Context 'HTTP Listener exists but should not' { - BeforeAll { - Mock -CommandName Get-WSManInstance -MockWith { - @($mockListenerHTTP) - } - Mock -CommandName Remove-WSManInstance - Mock -CommandName New-WSManInstance - Mock -CommandName Find-Certificate - } - - It 'Should not throw error' { - InModuleScope -Parameters @{ - mockListenerHTTP = $script:mockListenerHTTP - } -ScriptBlock { - Set-StrictMode -Version 1.0 - - { Set-TargetResource ` - -Transport $mockListenerHTTP.Transport ` - -Ensure 'Absent' ` - -Verbose:$VerbosePreference } | Should -Not -Throw - } - } - - It 'Should call expected Mocks' { - Should -Invoke ` - -CommandName Get-WSManInstance ` - -Exactly -Times 1 ` - -Scope Context - Should -Invoke ` - -CommandName Remove-WSManInstance ` - -Exactly -Times 1 ` - -Scope Context - Should -Invoke ` - -CommandName New-WSManInstance ` - -Exactly -Times 0 ` - -Scope Context - Should -Invoke ` - -CommandName Find-Certificate ` - -Exactly -Times 0 ` - -Scope Context - } - } - - Context 'HTTP Listener exists and should' { - BeforeAll { - Mock -CommandName Get-WSManInstance -MockWith { - @($mockListenerHTTP) - } - Mock -CommandName Remove-WSManInstance - Mock -CommandName New-WSManInstance - Mock -CommandName Find-Certificate - } - - It 'Should not throw error' { - InModuleScope -Parameters @{ - mockListenerHTTP = $script:mockListenerHTTP - mockIssuer = $script:mockIssuer - } -ScriptBlock { - Set-StrictMode -Version 1.0 - - { Set-TargetResource ` - -Transport $mockListenerHTTP.Transport ` - -Ensure 'Present' ` - -Issuer $mockIssuer ` - -Verbose:$VerbosePreference } | Should -Not -Throw - } - } - - It 'Should call expected Mocks' { - Should -Invoke ` - -CommandName Get-WSManInstance ` - -Exactly -Times 1 ` - -Scope Context - Should -Invoke ` - -CommandName Remove-WSManInstance ` - -Exactly -Times 1 ` - -Scope Context - Should -Invoke ` - -CommandName New-WSManInstance ` - -Exactly -Times 1 ` - -Scope Context - Should -Invoke ` - -CommandName Find-Certificate ` - -Exactly -Times 0 ` - -Scope Context - } - } - - Context 'HTTP Listener exists and HTTPS is required' { - BeforeAll { - Mock -CommandName Get-WSManInstance -MockWith { - @($mockListenerHTTP) - } - Mock -CommandName Remove-WSManInstance - Mock -CommandName New-WSManInstance - Mock -CommandName Find-Certificate -MockWith { - $mockCertificate - } - } - - It 'Should not throw error' { - InModuleScope -Parameters @{ - mockListenerHTTPS = $script:mockListenerHTTPS - mockIssuer = $script:mockIssuer - } -ScriptBlock { - Set-StrictMode -Version 1.0 - - { Set-TargetResource ` - -Transport $mockListenerHTTPS.Transport ` - -Ensure 'Present' ` - -Issuer $mockIssuer ` - -Verbose:$VerbosePreference } | Should -Not -Throw - } - } - - It 'Should call expected Mocks' { - Should -Invoke ` - -CommandName Get-WSManInstance ` - -Exactly -Times 1 ` - -Scope Context - Should -Invoke ` - -CommandName Remove-WSManInstance ` - -Exactly -Times 0 ` - -Scope Context - Should -Invoke ` - -CommandName New-WSManInstance ` - -Exactly -Times 1 ` - -Scope Context - Should -Invoke ` - -CommandName Find-Certificate ` - -Exactly -Times 1 ` - -Scope Context - } - } - - Context 'HTTPS Listener exists and HTTP is required' { - BeforeAll { - Mock -CommandName Get-WSManInstance -MockWith { - @($mockListenerHTTPS) - } - Mock -CommandName Remove-WSManInstance - Mock -CommandName New-WSManInstance - Mock -CommandName Find-Certificate - } - - It 'Should not throw error' { - InModuleScope -Parameters @{ - mockListenerHTTP = $script:mockListenerHTTP - mockIssuer = $script:mockIssuer - } -ScriptBlock { - Set-StrictMode -Version 1.0 - - { Set-TargetResource ` - -Transport $mockListenerHTTP.Transport ` - -Ensure 'Present' ` - -Issuer $mockIssuer ` - -Verbose:$VerbosePreference } | Should -Not -Throw - } - } - - It 'Should call expected Mocks' { - Should -Invoke ` - -CommandName Get-WSManInstance ` - -Exactly -Times 1 ` - -Scope Context - Should -Invoke ` - -CommandName Remove-WSManInstance ` - -Exactly -Times 0 ` - -Scope Context - Should -Invoke ` - -CommandName New-WSManInstance ` - -Exactly -Times 1 ` - -Scope Context - Should -Invoke ` - -CommandName Find-Certificate ` - -Exactly -Times 0 ` - -Scope Context - } - } - - Context 'Both Listeners exist and HTTPS is required' { - BeforeAll { - Mock -CommandName Get-WSManInstance -MockWith { - @($mockListenerHTTP, $mockListenerHTTPS) - } - Mock -CommandName Remove-WSManInstance - Mock -CommandName New-WSManInstance - Mock -CommandName Find-Certificate -MockWith { - $mockCertificate - } - } - - It 'Should not throw error' { - InModuleScope -Parameters @{ - mockListenerHTTPS = $script:mockListenerHTTPS - mockIssuer = $script:mockIssuer - } -ScriptBlock { - Set-StrictMode -Version 1.0 - - { Set-TargetResource ` - -Transport $mockListenerHTTPS.Transport ` - -Ensure 'Present' ` - -Issuer $mockIssuer ` - -Verbose:$VerbosePreference } | Should -Not -Throw - } - } - - It 'Should call expected Mocks' { - Should -Invoke ` - -CommandName Get-WSManInstance ` - -Exactly -Times 1 ` - -Scope Context - Should -Invoke ` - -CommandName Remove-WSManInstance ` - -Exactly -Times 1 ` - -Scope Context - Should -Invoke ` - -CommandName New-WSManInstance ` - -Exactly -Times 1 ` - -Scope Context - Should -Invoke ` - -CommandName Find-Certificate ` - -Exactly -Times 1 ` - -Scope Context - } - } - - Context 'Both Listeners exist and HTTP is required' { - BeforeAll { - Mock -CommandName Get-WSManInstance -MockWith { - @($mockListenerHTTP, $mockListenerHTTPS) - } - Mock -CommandName Remove-WSManInstance - Mock -CommandName New-WSManInstance - Mock -CommandName Find-Certificate - } - - It 'Should not throw error' { - InModuleScope -Parameters @{ - mockListenerHTTP = $script:mockListenerHTTP - mockIssuer = $script:mockIssuer - } -ScriptBlock { - Set-StrictMode -Version 1.0 - - { Set-TargetResource ` - -Transport $mockListenerHTTP.Transport ` - -Ensure 'Present' ` - -Issuer $mockIssuer ` - -Verbose:$VerbosePreference } | Should -Not -Throw - } - } - - It 'Should call expected Mocks' { - Should -Invoke ` - -CommandName Get-WSManInstance ` - -Exactly -Times 1 ` - -Scope Context - Should -Invoke ` - -CommandName Remove-WSManInstance ` - -Exactly -Times 1 ` - -Scope Context - Should -Invoke ` - -CommandName New-WSManInstance ` - -Exactly -Times 1 ` - -Scope Context - Should -Invoke ` - -CommandName Find-Certificate ` - -Exactly -Times 0 ` - -Scope Context - } - } -} - -Describe "$($script:dscResourceName)\Test-TargetResource" -Tag 'Test' { - Context 'HTTP Listener does not exist but should' { - BeforeAll { - Mock -CommandName Get-WSManInstance - } - - It 'Should return false' { - InModuleScope -Parameters @{ - mockListenerHTTP = $script:mockListenerHTTP - } -ScriptBlock { - Set-StrictMode -Version 1.0 - - Test-TargetResource ` - -Transport $mockListenerHTTP.Transport ` - -Ensure 'Present' ` - -Verbose:$VerbosePreference | Should -BeFalse - } - } - - It 'Should call expected Mocks' { - Should -Invoke ` - -CommandName Get-WSManInstance ` - -Exactly -Times 1 ` - -Scope Context - } - } - - Context 'HTTP Listener does not exist and should not' { - BeforeAll { - Mock -CommandName Get-WSManInstance - } - - It 'Should return false' { - InModuleScope -Parameters @{ - mockListenerHTTP = $script:mockListenerHTTP - } -ScriptBlock { - Set-StrictMode -Version 1.0 - - Test-TargetResource ` - -Transport $mockListenerHTTP.Transport ` - -Ensure 'Absent' ` - -Verbose:$VerbosePreference | Should -BeTrue - } - } - - It 'Should call expected Mocks' { - Should -Invoke ` - -CommandName Get-WSManInstance ` - -Exactly -Times 1 ` - -Scope Context - } - } - - Context 'HTTPS Listener does not exist but should' { - BeforeAll { - Mock -CommandName Get-WSManInstance - } - - It 'Should return false' { - InModuleScope -Parameters @{ - mockListenerHTTPS = $script:mockListenerHTTPS - mockIssuer = $script:mockIssuer - } -ScriptBlock { - Set-StrictMode -Version 1.0 - - Test-TargetResource ` - -Transport $mockListenerHTTPS.Transport ` - -Ensure 'Present' ` - -Issuer $mockIssuer ` - -Verbose:$VerbosePreference | Should -BeFalse - } - } - - It 'Should call expected Mocks' { - Should -Invoke ` - -CommandName Get-WSManInstance ` - -Exactly -Times 1 ` - -Scope Context - } - } - - Context 'HTTP Listener exists but should not' { - BeforeAll { - Mock -CommandName Get-WSManInstance -MockWith { - @($mockListenerHTTP) - } - } - - It 'Should return false' { - InModuleScope -Parameters @{ - mockListenerHTTP = $script:mockListenerHTTP - } -ScriptBlock { - Set-StrictMode -Version 1.0 - - Test-TargetResource ` - -Transport $mockListenerHTTP.Transport ` - -Ensure 'Absent' ` - -Verbose:$VerbosePreference | Should -BeFalse - } - } - - It 'Should call expected Mocks' { - Should -Invoke ` - -CommandName Get-WSManInstance ` - -Exactly -Times 1 ` - -Scope Context - } - } - - Context 'HTTPS Listener exists but should not' { - BeforeAll { - Mock -CommandName Get-WSManInstance -MockWith { - @($mockListenerHTTPS) - } - } - - It 'Should return false' { - InModuleScope -Parameters @{ - mockListenerHTTPS = $script:mockListenerHTTPS - } -ScriptBlock { - Set-StrictMode -Version 1.0 - - Test-TargetResource ` - -Transport $mockListenerHTTPS.Transport ` - -Ensure 'Absent' ` - -Verbose:$VerbosePreference | Should -BeFalse - } - } - - It 'Should call expected Mocks' { - Should -Invoke ` - -CommandName Get-WSManInstance ` - -Exactly -Times 1 ` - -Scope Context - } - } - - Context 'HTTP Listener exists and should' { - BeforeAll { - Mock -CommandName Get-WSManInstance -MockWith { - @($mockListenerHTTP) - } - } - - It 'Should return true' { - InModuleScope -Parameters @{ - mockListenerHTTP = $script:mockListenerHTTP - } -ScriptBlock { - Set-StrictMode -Version 1.0 - - Test-TargetResource ` - -Transport $mockListenerHTTP.Transport ` - -Ensure 'Present' ` - -Verbose:$VerbosePreference | Should -BeTrue - } - } - - It 'Should call expected Mocks' { - Should -Invoke ` - -CommandName Get-WSManInstance ` - -Exactly -Times 1 ` - -Scope Context - } - } - - Context 'HTTPS Listener exists and should' { - BeforeAll { - Mock -CommandName Get-WSManInstance -MockWith { - @($mockListenerHTTPS) - } - } - - It 'Should return true' { - InModuleScope -Parameters @{ - mockListenerHTTPS = $script:mockListenerHTTPS - } -ScriptBlock { - Set-StrictMode -Version 1.0 - - Test-TargetResource ` - -Transport $mockListenerHTTPS.Transport ` - -Ensure 'Present' ` - -Verbose:$VerbosePreference | Should -BeTrue - } - } - - It 'Should call expected Mocks' { - Should -Invoke ` - -CommandName Get-WSManInstance ` - -Exactly -Times 1 ` - -Scope Context - } - } - - Context 'HTTP Listener exists but port is incorrect' { - BeforeAll { - Mock -CommandName Get-WSManInstance -MockWith { - @($mockListenerHTTP) - } - } - - It 'Should return true' { - InModuleScope -Parameters @{ - mockListenerHTTP = $script:mockListenerHTTP - } -ScriptBlock { - Set-StrictMode -Version 1.0 - - Test-TargetResource ` - -Transport $mockListenerHTTP.Transport ` - -Port 9999 ` - -Ensure 'Present' ` - -Verbose:$VerbosePreference | Should -BeFalse - } - } - - It 'Should call expected Mocks' { - Should -Invoke ` - -CommandName Get-WSManInstance ` - -Exactly -Times 1 ` - -Scope Context - } - } - - Context 'HTTP Listener exists but address is incorrect' { - BeforeAll { - Mock -CommandName Get-WSManInstance -MockWith { - @($mockListenerHTTP) - } - } - - It 'Should return true' { - InModuleScope -Parameters @{ - mockListenerHTTP = $script:mockListenerHTTP - } -ScriptBlock { - Set-StrictMode -Version 1.0 - - Test-TargetResource ` - -Transport $mockListenerHTTP.Transport ` - -Address '192.168.1.1' ` - -Ensure 'Present' ` - -Verbose:$VerbosePreference | Should -BeFalse - } - } - - It 'Should call expected Mocks' { - Should -Invoke ` - -CommandName Get-WSManInstance ` - -Exactly -Times 1 ` - -Scope Context - } - } - - Context 'HTTP Listener exists but hostname is incorrect' { - BeforeAll { - Mock -CommandName Get-WSManInstance -MockWith { - @($mockListenerHTTPS) - } - } - - It 'Should return true' { - InModuleScope -Parameters @{ - mockListenerHTTPS = $script:mockListenerHTTPS - } -ScriptBlock { - Set-StrictMode -Version 1.0 - - Test-TargetResource ` - -Transport $mockListenerHTTPS.Transport ` - -Hostname 'thewronghostname.example.local' ` - -Ensure 'Present' ` - -Verbose:$VerbosePreference | Should -BeFalse - } - } - - It 'Should call expected Mocks' { - Should -Invoke ` - -CommandName Get-WSManInstance ` - -Exactly -Times 1 ` - -Scope Context - } - } - - Context 'HTTP Listener exists but CertificateThumbprint is incorrect' { - BeforeAll { - Mock -CommandName Get-WSManInstance -MockWith { - @($mockListenerHTTPS) - } - } - - It 'Should return true' { - InModuleScope -Parameters @{ - mockListenerHTTPS = $script:mockListenerHTTPS - } -ScriptBlock { - Set-StrictMode -Version 1.0 - - Test-TargetResource ` - -Transport $mockListenerHTTPS.Transport ` - -CertificateThumbprint '' ` - -Ensure 'Present' ` - -Verbose:$VerbosePreference | Should -BeFalse - } - } - - It 'Should call expected Mocks' { - Should -Invoke ` - -CommandName Get-WSManInstance ` - -Exactly -Times 1 ` - -Scope Context - } - } - - Context 'Both Listeners exists and HTTPS should' { - BeforeAll { - Mock -CommandName Get-WSManInstance -MockWith { - @($mockListenerHTTP, $mockListenerHTTPS) - } - } - - It 'Should return true' { - InModuleScope -Parameters @{ - mockListenerHTTPS = $script:mockListenerHTTPS - } -ScriptBlock { - Set-StrictMode -Version 1.0 - - Test-TargetResource ` - -Transport $mockListenerHTTPS.Transport ` - -Ensure 'Present' ` - -Verbose:$VerbosePreference | Should -BeTrue - } - } - - It 'Should call expected Mocks' { - Should -Invoke ` - -CommandName Get-WSManInstance ` - -Exactly -Times 1 ` - -Scope Context - } - } -} - -Describe "$($script:dscResourceName)\Find-Certificate" -Tag 'Private' { - Context 'CertificateThumbprint is passed but does not exist' { - BeforeAll { - Mock -CommandName Get-ChildItem - } - - It 'Should not throw error' { - InModuleScope -Parameters @{ - mockCertificateThumbprint = $script:mockCertificateThumbprint - } -ScriptBlock { - Set-StrictMode -Version 1.0 - - { $script:returnedCertificate = Find-Certificate ` - -CertificateThumbprint $mockCertificateThumbprint ` - -Verbose:$VerbosePreference } | Should -Not -Throw - } - } - - It 'Should return null' { - InModuleScope -ScriptBlock { - Set-StrictMode -Version 1.0 - - $script:returnedCertificate | Should -BeNullOrEmpty - } - } - - It 'Should call expected Mocks' { - Should -Invoke ` - -CommandName Get-ChildItem ` - -Exactly -Times 1 ` - -Scope Context - } - } - - Context 'CertificateThumbprint is passed and does exist' { - BeforeAll { - Mock -CommandName Get-ChildItem -MockWith { - $mockCertificateDN - } - } - - It 'Should not throw error' { - InModuleScope -Parameters @{ - mockCertificateThumbprint = $script:mockCertificateThumbprint - } -ScriptBlock { - Set-StrictMode -Version 1.0 - - { $script:returnedCertificate = Find-Certificate ` - -CertificateThumbprint $mockCertificateThumbprint ` - -Verbose:$VerbosePreference } | Should -Not -Throw - } - } - - It 'Should return expected certificate' { - InModuleScope -Parameters @{ - mockCertificateThumbprint = $script:mockCertificateThumbprint - } -ScriptBlock { - Set-StrictMode -Version 1.0 - - $script:returnedCertificate.Thumbprint | Should -Be $mockCertificateThumbprint - } - } - - It 'Should call expected Mocks' { - Should -Invoke ` - -CommandName Get-ChildItem ` - -Exactly -Times 1 ` - -Scope Context - } - } - - Context 'SubjectFormat is Both, Certificate does not exist, DN passed' { - BeforeAll { - Mock -CommandName Get-ChildItem - } - - It 'Should not throw error' { - InModuleScope -Parameters @{ - mockIssuer = $script:mockIssuer - mockDN = $script:mockDN - } -ScriptBlock { - Set-StrictMode -Version 1.0 - - { $script:returnedCertificate = Find-Certificate ` - -Issuer $mockIssuer ` - -SubjectFormat 'Both' ` - -MatchAlternate $true ` - -DN $mockDN ` - -Verbose:$VerbosePreference } | Should -Not -Throw - } - } - - It 'Should return null' { - InModuleScope -ScriptBlock { - Set-StrictMode -Version 1.0 - - $script:returnedCertificate | Should -BeNullOrEmpty - } - } - - It 'Should call expected Mocks' { - Should -Invoke ` - -CommandName Get-ChildItem ` - -Exactly -Times 2 ` - -Scope Context - } - } - - Context 'SubjectFormat is Both, Certificate with DN Exists, DN passed' { - BeforeAll { - Mock -CommandName Get-ChildItem -MockWith { - $mockCertificateDN - } - } - - It 'Should not throw error' { - InModuleScope -Parameters @{ - mockIssuer = $script:mockIssuer - mockDN = $script:mockDN - } -ScriptBlock { - Set-StrictMode -Version 1.0 - - { $script:returnedCertificate = Find-Certificate ` - -Issuer $mockIssuer ` - -SubjectFormat 'Both' ` - -MatchAlternate $true ` - -DN $mockDN ` - -Verbose:$VerbosePreference } | Should -Not -Throw - } - } - - It 'Should return expected certificate' { - InModuleScope -Parameters @{ - mockCertificateThumbprint = $script:mockCertificateThumbprint - } -ScriptBlock { - Set-StrictMode -Version 1.0 - - $script:returnedCertificate.Thumbprint | Should -Be $mockCertificateThumbprint - } - } - - It 'Should call expected Mocks' { - Should -Invoke ` - -CommandName Get-ChildItem ` - -Exactly -Times 1 ` - -Scope Context - } - } - - Context 'SubjectFormat is Both, Certificate without DN Exists, DN passed' { - BeforeAll { - Mock -CommandName Get-ChildItem -MockWith { - $mockCertificate - } - } - - It 'Should not throw error' { - InModuleScope -Parameters @{ - mockIssuer = $script:mockIssuer - mockDN = $script:mockDN - } -ScriptBlock { - Set-StrictMode -Version 1.0 - - { $script:returnedCertificate = Find-Certificate ` - -Issuer $mockIssuer ` - -SubjectFormat 'Both' ` - -MatchAlternate $true ` - -DN $mockDN ` - -Verbose:$VerbosePreference } | Should -Not -Throw - } - } - - It 'Should return null' { - InModuleScope -ScriptBlock { - Set-StrictMode -Version 1.0 - - $script:returnedCertificate | Should -BeNullOrEmpty - } - } - - It 'Should call expected Mocks' { - Should -Invoke ` - -CommandName Get-ChildItem ` - -Exactly -Times 2 ` - -Scope Context - } - } - - Context 'SubjectFormat is Both, Certificate does not exist, DN not passed' { - BeforeAll { - Mock -CommandName Get-ChildItem - } - - It 'Should not throw error' { - InModuleScope -Parameters @{ - mockIssuer = $script:mockIssuer - } -ScriptBlock { - Set-StrictMode -Version 1.0 - - { $script:returnedCertificate = Find-Certificate ` - -Issuer $mockIssuer ` - -SubjectFormat 'Both' ` - -MatchAlternate $true ` - -Verbose:$VerbosePreference } | Should -Not -Throw - } - } - - It 'Should return null' { - InModuleScope -ScriptBlock { - Set-StrictMode -Version 1.0 - - $script:returnedCertificate | Should -BeNullOrEmpty - } - } - - It 'Should call expected Mocks' { - Should -Invoke ` - -CommandName Get-ChildItem ` - -Exactly -Times 2 ` - -Scope Context - } - } - - Context 'SubjectFormat is Both, Certificate with DN Exists, DN not passed' { - BeforeAll { - Mock -CommandName Get-ChildItem -MockWith { - $mockCertificateDN - } - } - - It 'Should not throw error' { - InModuleScope -Parameters @{ - mockIssuer = $script:mockIssuer - } -ScriptBlock { - Set-StrictMode -Version 1.0 - - { $script:returnedCertificate = Find-Certificate ` - -Issuer $mockIssuer ` - -SubjectFormat 'Both' ` - -MatchAlternate $true ` - -Verbose:$VerbosePreference } | Should -Not -Throw - } - } - - It 'Should return null' { - InModuleScope -ScriptBlock { - Set-StrictMode -Version 1.0 - - $script:returnedCertificate | Should -BeNullOrEmpty - } - } - - It 'Should call expected Mocks' { - Should -Invoke ` - -CommandName Get-ChildItem ` - -Exactly -Times 2 ` - -Scope Context - } - } - - Context 'SubjectFormat is Both, Certificate without DN Exists, DN not passed' { - BeforeAll { - Mock -CommandName Get-ChildItem -MockWith { - $mockCertificate - } - } - - It 'Should not throw error' { - InModuleScope -Parameters @{ - mockIssuer = $script:mockIssuer - } -ScriptBlock { - Set-StrictMode -Version 1.0 - - { $script:returnedCertificate = Find-Certificate ` - -Issuer $mockIssuer ` - -SubjectFormat 'Both' ` - -MatchAlternate $true ` - -Verbose:$VerbosePreference } | Should -Not -Throw - } - } - - It 'Should return expected certificate' { - InModuleScope -Parameters @{ - mockCertificateThumbprint = $script:mockCertificateThumbprint - } -ScriptBlock { - Set-StrictMode -Version 1.0 - - $script:returnedCertificate.Thumbprint | Should -Be $mockCertificateThumbprint - } - } - - It 'Should call expected Mocks' { - Should -Invoke ` - -CommandName Get-ChildItem ` - -Exactly -Times 1 ` - -Scope Context - } - } - - Context 'SubjectFormat is Both, Certificate does not exist, DN not passed, MatchAlternate is false' { - BeforeAll { - Mock -CommandName Get-ChildItem - } - - It 'Should not throw error' { - InModuleScope -Parameters @{ - mockIssuer = $script:mockIssuer - } -ScriptBlock { - Set-StrictMode -Version 1.0 - - { $script:returnedCertificate = Find-Certificate ` - -Issuer $mockIssuer ` - -SubjectFormat 'Both' ` - -MatchAlternate $false ` - -Verbose:$VerbosePreference } | Should -Not -Throw - } - } - - It 'Should return null' { - InModuleScope -ScriptBlock { - Set-StrictMode -Version 1.0 - - $script:returnedCertificate | Should -BeNullOrEmpty - } - } - - It 'Should call expected Mocks' { - Should -Invoke ` - -CommandName Get-ChildItem ` - -Exactly -Times 2 ` - -Scope Context - } - } - - Context 'SubjectFormat is Both, Certificate without DN Exists, DN not passed, MatchAlternate is false' { - BeforeAll { - Mock -CommandName Get-ChildItem -MockWith { - $mockCertificate - } - } - - It 'Should not throw error' { - InModuleScope -Parameters @{ - mockIssuer = $script:mockIssuer - } -ScriptBlock { - Set-StrictMode -Version 1.0 - - { $script:returnedCertificate = Find-Certificate ` - -Issuer $mockIssuer ` - -SubjectFormat 'Both' ` - -MatchAlternate $false ` - -Verbose:$VerbosePreference } | Should -Not -Throw - } - } - - It 'Should return expected certificate' { - InModuleScope -Parameters @{ - mockCertificateThumbprint = $script:mockCertificateThumbprint - } -ScriptBlock { - Set-StrictMode -Version 1.0 - - $script:returnedCertificate.Thumbprint | Should -Be $mockCertificateThumbprint - } - } - - It 'Should call expected Mocks' { - Should -Invoke ` - -CommandName Get-ChildItem ` - -Exactly -Times 1 ` - -Scope Context - } - } -} From ffeee6a2b10f2ce023038d4f69e10ce3082b5a9b Mon Sep 17 00:00:00 2001 From: Daniel Hughes Date: Wed, 18 Sep 2024 16:20:08 +0100 Subject: [PATCH 072/134] Add class WSManListener --- source/Classes/020.WSManListener.ps1 | 218 +++++++++++++++++ source/en-US/WSManListener.strings.psd1 | 14 ++ tests/Unit/Classes/WSManListener.Tests.ps1 | 268 +++++++++++++++++++++ 3 files changed, 500 insertions(+) create mode 100644 source/Classes/020.WSManListener.ps1 create mode 100644 source/en-US/WSManListener.strings.psd1 create mode 100644 tests/Unit/Classes/WSManListener.Tests.ps1 diff --git a/source/Classes/020.WSManListener.ps1 b/source/Classes/020.WSManListener.ps1 new file mode 100644 index 0000000..b4fa37e --- /dev/null +++ b/source/Classes/020.WSManListener.ps1 @@ -0,0 +1,218 @@ +<# + .SYNOPSIS + The `WSManListener` DSC resource is used to create, modify, or remove + WSMan listeners. + + .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)] + [ValidateSet('HTTP', 'HTTPS')] + [System.String] + $Transport + + [DscProperty(Mandatory = $true)] + [Ensure] + $Ensure + + [DscProperty()] + [ValidateRange(0, 65535)] + [Nullable[System.UInt16]] + $Port + + [DscProperty()] + [System.String] + $Address = '*' + + [DscProperty()] + [System.String] + $Issuer + + [DscProperty()] + [ValidateSet('Both', 'FQDNOnly', 'NameOnly')] + [System.String] + $SubjectFormat = 'Both' + + [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' + + ) + + # Get the port if it's not provided + if ($this.Port) + { + $this.Port = Get-DefaultPort -Transport $this.Transport -Port $this.Port + } + } + + [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 + } + + $getCurrentStateResult = Get-Listener @getParameters + + $currentCertificate = '' + + if ($getCurrentStateResult.CertificateThumbprint) + { + $currentCertificate = Find-Certificate -CertificateThumbprint $getCurrentStateResult.CertificateThumbprint + } + + $state = @{ + Transport = $properties.Transport + Port = [System.UInt16] $getCurrentStateResult.Port + Address = $getCurrentStateResult.Address + Enabled = $getCurrentStateResult.Enabled + URLPrefix = $getCurrentStateResult.URLPrefix + Issuer = $currentCertificate.Issuer + SubjectFormat = $properties.SubjectFormat + MatchAlternate = $null + BaseDN = $null + CertificateThumbprint = $getCurrentStateResult.CertificateThumbprint + Hostname = $getCurrentStateResult.HostName + } + + 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. + #> + [void] Modify([System.Collections.Hashtable] $properties) + { + # <# + # If the property 'EnablePollutionProtection' was present and not in desired state, + # then the property name must be change for the cmdlet Set-DnsServerCache. In the + # cmdlet Get-DnsServerCache the property name is 'EnablePollutionProtection', but + # in the cmdlet Set-DnsServerCache the parameter is 'PollutionProtection'. + # #> + # if ($properties.ContainsKey('EnablePollutionProtection')) + # { + # $properties['PollutionProtection'] = $properties.EnablePollutionProtection + + # $properties.Remove('EnablePollutionProtection') + # } + + # Set-DnsServerCache @properties + } + + [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) + { + # # The properties MaximumFiles and MaximumRolloverFiles are mutually exclusive. + # $assertBoundParameterParameters = @{ + # BoundParameterList = $properties + # MutuallyExclusiveList1 = @( + # 'MaximumFiles' + # ) + # MutuallyExclusiveList2 = @( + # 'MaximumRolloverFiles' + # ) + # } + + # Assert-BoundParameter @assertBoundParameterParameters + } +} diff --git a/source/en-US/WSManListener.strings.psd1 b/source/en-US/WSManListener.strings.psd1 new file mode 100644 index 0000000..f187454 --- /dev/null +++ b/source/en-US/WSManListener.strings.psd1 @@ -0,0 +1,14 @@ +<# + .SYNOPSIS + The localized resource strings in English (en-US) for the + resource WSManListener module. This file should only contain + localized strings for private functions, public command, and + classes (that are not a DSC resource). +#> + +ConvertFrom-StringData @' + ## Strings overrides for the ResourceBase's default strings. + # None + + ## Strings directly used by the derived class WSManListener. +'@ diff --git a/tests/Unit/Classes/WSManListener.Tests.ps1 b/tests/Unit/Classes/WSManListener.Tests.ps1 new file mode 100644 index 0000000..05a94ed --- /dev/null +++ b/tests/Unit/Classes/WSManListener.Tests.ps1 @@ -0,0 +1,268 @@ +<# + .SYNOPSIS + Unit test for DSC_WSManListener DSC resource. +#> + +# Suppressing this rule because Script Analyzer does not understand Pester's syntax. +[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '')] +param () + +BeforeDiscovery { + try + { + if (-not (Get-Module -Name 'DscResource.Test')) + { + # Assumes dependencies has been resolved, so if this module is not available, run 'noop' task. + if (-not (Get-Module -Name 'DscResource.Test' -ListAvailable)) + { + # Redirect all streams to $null, except the error stream (stream 2) + & "$PSScriptRoot/../../../build.ps1" -Tasks 'noop' 2>&1 4>&1 5>&1 6>&1 > $null + } + + # If the dependencies has not been resolved, this will throw an error. + Import-Module -Name 'DscResource.Test' -Force -ErrorAction 'Stop' + } + } + catch [System.IO.FileNotFoundException] + { + throw 'DscResource.Test module dependency not found. Please run ".\build.ps1 -ResolveDependency -Tasks build" first.' + } +} + +BeforeAll { + $script:dscModuleName = 'WSManDsc' + + Import-Module -Name $script:dscModuleName + + Import-Module -Name (Join-Path -Path $PSScriptRoot -ChildPath '../../TestHelpers/CommonTestHelper.psm1') + + $PSDefaultParameterValues['InModuleScope:ModuleName'] = $script:dscModuleName + $PSDefaultParameterValues['Mock:ModuleName'] = $script:dscModuleName + $PSDefaultParameterValues['Should:ModuleName'] = $script:dscModuleName +} + +AfterAll { + $PSDefaultParameterValues.Remove('InModuleScope:ModuleName') + $PSDefaultParameterValues.Remove('Mock:ModuleName') + $PSDefaultParameterValues.Remove('Should:ModuleName') + + # Unload the module being tested so that it doesn't impact any other tests. + Get-Module -Name $script:dscModuleName -All | Remove-Module -Force + + # Remove module common test helper. + Get-Module -Name 'CommonTestHelper' -All | Remove-Module -Force +} + +Describe 'WSManListener' { + Context 'When class is instantiated' { + It 'Should not throw an exception' { + InModuleScope -ScriptBlock { + Set-StrictMode -Version 1.0 + + { [WSManListener]::new() } | Should -Not -Throw + } + } + + It 'Should have a default or empty constructor' { + InModuleScope -ScriptBlock { + Set-StrictMode -Version 1.0 + + $instance = [WSManListener]::new() + $instance | Should -Not -BeNullOrEmpty + } + } + + It 'Should be the correct type' { + InModuleScope -ScriptBlock { + Set-StrictMode -Version 1.0 + + $instance = [WSManListener]::new() + $instance.GetType().Name | Should -Be 'WSManListener' + } + } + } +} + +Describe 'WSManListener\Get()' -Tag 'Get' { + Context 'When the system is in the desired state' { + Context 'When getting a HTTP listener' { + BeforeAll { + InModuleScope -ScriptBlock { + Set-StrictMode -Version 1.0 + + $script:mockWSManListenerInstance = [WSManListener] @{ + Transport = 'HTTP' + Ensure = 'Present' + } + + <# + This mocks the method GetCurrentState(). + + Method Get() will call the base method Get() which will + call back to the derived class method GetCurrentState() + to get the result to return from the derived method Get(). + #> + $script:mockWSManListenerInstance | + Add-Member -Force -MemberType 'ScriptMethod' -Name 'GetCurrentState' -Value { + return [System.Collections.Hashtable] @{ + Transport = 'HTTP' + Port = 5985 + Address = '*' + Enabled = 'true' + URLPrefix = 'wsman' + Issuer = $null + SubjectFormat = 'Both' + MatchAlternate = $null + BaseDN = $null + CertificateThumbprint = $null + Hostname = $null + } + } -PassThru | + Add-Member -Force -MemberType 'ScriptMethod' -Name 'AssertProperties' -Value { + return + } + } + } + + It 'Should return the correct values' { + InModuleScope -ScriptBlock { + Set-StrictMode -Version 1.0 + + $currentState = $script:mockWSManListenerInstance.Get() + + $currentState.Transport | Should -Be 'HTTP' + $currentState.Port | Should -Be 5985 + $currentState.Port | Should -BeOfType System.UInt16 + $currentState.Address | Should -Be '*' + $currentState.Enabled | Should -Be $true + $currentState.URLPrefix | Should -Be 'wsman' + + $currentState.Issuer | Should -BeNullOrEmpty + $currentState.SubjectFormat | Should -Be 'Both' + $currentState.MatchAlternate | Should -BeNullOrEmpty + $currentState.BaseDN | Should -BeNullOrEmpty + $currentState.CertificateThumbprint | Should -BeNullOrEmpty + $currentState.Hostname | Should -BeNullOrEmpty + + $currentState.Ensure | Should -Be 'Present' + $currentState.Reasons | Should -BeNullOrEmpty + } + } + } + + Context 'When getting a HTTPS listener' { + BeforeAll { + InModuleScope -ScriptBlock { + Set-StrictMode -Version 1.0 + + $script:mockWSManListenerInstance = [WSManListener] @{ + Transport = 'HTTPS' + Ensure = 'Present' + } + + <# + This mocks the method GetCurrentState(). + + Method Get() will call the base method Get() which will + call back to the derived class method GetCurrentState() + to get the result to return from the derived method Get(). + #> + $script:mockWSManListenerInstance | + Add-Member -Force -MemberType 'ScriptMethod' -Name 'GetCurrentState' -Value { + return [System.Collections.Hashtable] @{ + Transport = 'HTTPS' + Port = 5986 + Address = '*' + Enabled = 'true' + URLPrefix = 'wsman' + Issuer = $null + SubjectFormat = 'Both' + MatchAlternate = $null + BaseDN = $null + CertificateThumbprint = $null + Hostname = $null + } + } -PassThru | + Add-Member -Force -MemberType 'ScriptMethod' -Name 'AssertProperties' -Value { + return + } + } + } + + It 'Should return the correct values' { + InModuleScope -ScriptBlock { + Set-StrictMode -Version 1.0 + + $currentState = $script:mockWSManListenerInstance.Get() + + $currentState.Transport | Should -Be 'HTTPS' + $currentState.Port | Should -Be 5986 + $currentState.Port | Should -BeOfType System.UInt16 + $currentState.Address | Should -Be '*' + $currentState.Enabled | Should -Be $true + $currentState.URLPrefix | Should -Be 'wsman' + + $currentState.Issuer | Should -BeNullOrEmpty + $currentState.SubjectFormat | Should -Be 'Both' + $currentState.MatchAlternate | Should -BeNullOrEmpty + $currentState.BaseDN | Should -BeNullOrEmpty + $currentState.CertificateThumbprint | Should -BeNullOrEmpty + $currentState.Hostname | Should -BeNullOrEmpty + + $currentState.Ensure | Should -Be 'Present' + $currentState.Reasons | Should -BeNullOrEmpty + } + } + } + } + + # Context 'When the system is not in the desired state' { + # Context 'When property Path have the wrong value for a File audit' { + # BeforeAll { + # InModuleScope -ScriptBlock { + # $script:mockSqlAuditInstance = [SqlAudit] @{ + # Name = 'MockAuditName' + # InstanceName = 'NamedInstance' + # Path = 'C:\NewFolder' + # } + + # <# + # This mocks the method GetCurrentState(). + + # Method Get() will call the base method Get() which will + # call back to the derived class method GetCurrentState() + # to get the result to return from the derived method Get(). + # #> + # $script:mockSqlAuditInstance | + # Add-Member -Force -MemberType 'ScriptMethod' -Name 'GetCurrentState' -Value { + # return [System.Collections.Hashtable] @{ + # Name = 'MockAuditName' + # InstanceName = 'NamedInstance' + # Path = 'C:\Temp' + # } + # } -PassThru | + # Add-Member -Force -MemberType 'ScriptMethod' -Name 'AssertProperties' -Value { + # return + # } + # } + # } + + # It 'Should return the correct values' { + # InModuleScope -ScriptBlock { + # $currentState = $script:mockSqlAuditInstance.Get() + + # $currentState.InstanceName | Should -Be 'NamedInstance' + # $currentState.Name | Should -Be 'MockAuditName' + # $currentState.ServerName | Should -Be (Get-ComputerName) + # $currentState.Credential | Should -BeNullOrEmpty + + # $currentState.Path | Should -Be 'C:\Temp' + + # $currentState.Reasons | Should -HaveCount 1 + # $currentState.Reasons[0].Code | Should -Be 'SqlAudit:SqlAudit:Path' + # $currentState.Reasons[0].Phrase | Should -Be 'The property Path should be "C:\NewFolder", but was "C:\Temp"' + # } + # } + # } + # } +} From 5862f2615e53708ab797c3287bc5dcbf28ff5298 Mon Sep 17 00:00:00 2001 From: Daniel Hughes Date: Wed, 18 Sep 2024 16:20:27 +0100 Subject: [PATCH 073/134] Move Get-Listener to Private --- source/Private/Get-Listener.ps1 | 28 +++++ tests/Unit/Private/Get-Listener.Tests.ps1 | 141 ++++++++++++++++++++++ 2 files changed, 169 insertions(+) create mode 100644 source/Private/Get-Listener.ps1 create mode 100644 tests/Unit/Private/Get-Listener.Tests.ps1 diff --git a/source/Private/Get-Listener.ps1 b/source/Private/Get-Listener.ps1 new file mode 100644 index 0000000..380d856 --- /dev/null +++ b/source/Private/Get-Listener.ps1 @@ -0,0 +1,28 @@ +<# + .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)] + [ValidateSet('HTTP', 'HTTPS')] + [System.String] + $Transport + ) + + $listeners = @(Get-WSManInstance -ResourceURI 'winrm/config/Listener' -Enumerate) + + if ($listeners) + { + return $listeners.Where( + { ($_.Transport -eq $Transport) -and ($_.Source -ne 'Compatibility') } + ) + } +} diff --git a/tests/Unit/Private/Get-Listener.Tests.ps1 b/tests/Unit/Private/Get-Listener.Tests.ps1 new file mode 100644 index 0000000..34ea025 --- /dev/null +++ b/tests/Unit/Private/Get-Listener.Tests.ps1 @@ -0,0 +1,141 @@ +[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '')] +param () + +BeforeDiscovery { + try + { + if (-not (Get-Module -Name 'DscResource.Test')) + { + # Assumes dependencies has been resolved, so if this module is not available, run 'noop' task. + if (-not (Get-Module -Name 'DscResource.Test' -ListAvailable)) + { + # Redirect all streams to $null, except the error stream (stream 2) + & "$PSScriptRoot/../../../build.ps1" -Tasks 'noop' 2>&1 4>&1 5>&1 6>&1 > $null + } + + # If the dependencies has not been resolved, this will throw an error. + Import-Module -Name 'DscResource.Test' -Force -ErrorAction 'Stop' + } + } + catch [System.IO.FileNotFoundException] + { + throw 'DscResource.Test module dependency not found. Please run ".\build.ps1 -ResolveDependency -Tasks build" first.' + } +} + +BeforeAll { + $script:dscModuleName = 'WSManDsc' + + Import-Module -Name $script:dscModuleName + + $PSDefaultParameterValues['InModuleScope:ModuleName'] = $script:dscModuleName + $PSDefaultParameterValues['Mock:ModuleName'] = $script:dscModuleName + $PSDefaultParameterValues['Should:ModuleName'] = $script:dscModuleName +} + +AfterAll { + $PSDefaultParameterValues.Remove('InModuleScope:ModuleName') + $PSDefaultParameterValues.Remove('Mock:ModuleName') + $PSDefaultParameterValues.Remove('Should:ModuleName') + + # Unload the module being tested so that it doesn't impact any other tests. + Get-Module -Name $script:dscModuleName -All | Remove-Module -Force +} + +Describe 'Get-Listener' -Tag 'Private' { + Context 'When the listener exists' { + BeforeAll { + Mock -CommandName Get-WSManInstance -MockWith { + @{ + Transport = 'HTTP' + Port = 5985 + Source = 'Compatibility' + } + @{ + Transport = 'HTTPS' + Port = 5986 + Source = 'Compatibility' + } + @{ + Transport = 'HTTP' + Port = 5985 + Source = 'NotCompatibility' + } + @{ + Transport = 'HTTPS' + Port = 5986 + Source = 'NotCompatibility' + } + } + } + + BeforeDiscovery { + $testCases = @( + @{ + Transport = 'HTTP' + ExpectedPort = 5985 + } + @{ + Transport = 'HTTPS' + ExpectedPort = 5986 + } + ) + } + + It 'Should return a listener for ''''' -ForEach $testCases { + InModuleScope -Parameters $_ -ScriptBlock { + Set-StrictMode -Version 1.0 + + $result = Get-Listener -Transport $Transport + + $result.Transport | Should -Be $Transport + $result.Port | Should -Be $ExpectedPort + } + + Should -Invoke -CommandName Get-WSManInstance -Exactly -Times 1 + } + } + + Context 'When the listener does not exist' { + BeforeAll { + Mock -CommandName Get-WSManInstance -MockWith { + @{ + Transport = 'HTTP' + Port = 5985 + Source = 'NotCompatibility' + } + @{ + Transport = 'HTTPS' + Port = 5986 + Source = 'NotCompatibility' + } + } + } + + BeforeDiscovery { + $testCases = @( + @{ + Transport = 'HTTP' + ExpectedPort = 5985 + } + @{ + Transport = 'HTTPS' + ExpectedPort = 5986 + } + ) + } + + It 'Should return a listener for ''''' -ForEach $testCases { + InModuleScope -Parameters $_ -ScriptBlock { + Set-StrictMode -Version 1.0 + + $result = Get-Listener -Transport $Transport + + $result | Should -HaveCount 1 + $result | Should -BeOfType System.Collections.Hashtable + } + + Should -Invoke -CommandName Get-WSManInstance -Exactly -Times 1 + } + } +} From d4f8ba1edd4ead3327ed6bcfb41fb811102bc7ee Mon Sep 17 00:00:00 2001 From: Daniel Hughes Date: Wed, 18 Sep 2024 16:20:42 +0100 Subject: [PATCH 074/134] Fix Test --- tests/Unit/Private/Get-DefaultPort.Tests.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Unit/Private/Get-DefaultPort.Tests.ps1 b/tests/Unit/Private/Get-DefaultPort.Tests.ps1 index 687a04c..a73428b 100644 --- a/tests/Unit/Private/Get-DefaultPort.Tests.ps1 +++ b/tests/Unit/Private/Get-DefaultPort.Tests.ps1 @@ -129,7 +129,7 @@ Describe 'Get-DefaultPort' -Tag 'Private' { Port = 'Something' } - { Get-DefaultPort @mockParams } | Should -Not -Throw + { Get-DefaultPort @mockParams } | Should -Throw } } } From e7eb8d9bfd2654ab1536e4ca972b185cc84fb9ee Mon Sep 17 00:00:00 2001 From: Daniel Hughes Date: Wed, 18 Sep 2024 16:52:23 +0100 Subject: [PATCH 075/134] Update DN to BaseDN --- ...nfig.ps1 => 3-WSManListener_HTTPS_WithBaseDN_Config.ps1} | 6 +++--- tests/Integration/DSC_WSManListener.Integration.Tests.ps1 | 6 +++--- tests/Integration/DSC_WSManListener_Add_HTTPS.config.ps1 | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) rename source/Examples/Resources/WSManListener/{3-WSManListener_HTTPS_WithDN_Config.ps1 => 3-WSManListener_HTTPS_WithBaseDN_Config.ps1} (88%) diff --git a/source/Examples/Resources/WSManListener/3-WSManListener_HTTPS_WithDN_Config.ps1 b/source/Examples/Resources/WSManListener/3-WSManListener_HTTPS_WithBaseDN_Config.ps1 similarity index 88% rename from source/Examples/Resources/WSManListener/3-WSManListener_HTTPS_WithDN_Config.ps1 rename to source/Examples/Resources/WSManListener/3-WSManListener_HTTPS_WithBaseDN_Config.ps1 index bd6b21e..55a79b9 100644 --- a/source/Examples/Resources/WSManListener/3-WSManListener_HTTPS_WithDN_Config.ps1 +++ b/source/Examples/Resources/WSManListener/3-WSManListener_HTTPS_WithBaseDN_Config.ps1 @@ -19,11 +19,11 @@ <# .DESCRIPTION - Create an HTTPS Listener using a LocalMachine certificate containing a DN matching + Create an HTTPS Listener using a LocalMachine certificate containing a BaseDN matching 'O=Contoso Inc, S=Pennsylvania, C=US' that is installed and issued by 'CN=CONTOSO.COM Issuing CA, DC=CONTOSO, DC=COM' on port 5986. #> -Configuration WSManListener_HTTPS_WithDN_Config +Configuration WSManListener_HTTPS_WithBaseDN_Config { Import-DscResource -Module WSManDsc @@ -34,7 +34,7 @@ Configuration WSManListener_HTTPS_WithDN_Config Transport = 'HTTPS' Ensure = 'Present' Issuer = 'CN=CONTOSO.COM Issuing CA, DC=CONTOSO, DC=COM' - DN = 'O=Contoso Inc, S=Pennsylvania, C=US' + BaseDN = 'O=Contoso Inc, S=Pennsylvania, C=US' } # End of WSManListener Resource } # End of Node } # End of Configuration diff --git a/tests/Integration/DSC_WSManListener.Integration.Tests.ps1 b/tests/Integration/DSC_WSManListener.Integration.Tests.ps1 index 69e0174..f1fa5d8 100644 --- a/tests/Integration/DSC_WSManListener.Integration.Tests.ps1 +++ b/tests/Integration/DSC_WSManListener.Integration.Tests.ps1 @@ -63,8 +63,8 @@ BeforeAll { Remove-Item -Force $script:Hostname = ([System.Net.Dns]::GetHostByName($ENV:computerName).Hostname) - $script:DN = 'O=Contoso Inc, S=Pennsylvania, C=US' - $script:Issuer = "CN=$Hostname, $DN" + $script:BaseDN = 'O=Contoso Inc, S=Pennsylvania, C=US' + $script:Issuer = "CN=$Hostname, $BaseDN" # Create the certificate if ([System.Environment]::OSVersion.Version.Major -ge 10) @@ -190,7 +190,7 @@ Describe "$($script:dscResourceName)_Integration_Add_HTTPS" { Issuer = $Issuer SubjectFormat = 'Both' MatchAlternate = $false - DN = $DN + BaseDN = $BaseDN Hostname = $Hostname } ) diff --git a/tests/Integration/DSC_WSManListener_Add_HTTPS.config.ps1 b/tests/Integration/DSC_WSManListener_Add_HTTPS.config.ps1 index d7d0eb6..edc59a4 100644 --- a/tests/Integration/DSC_WSManListener_Add_HTTPS.config.ps1 +++ b/tests/Integration/DSC_WSManListener_Add_HTTPS.config.ps1 @@ -10,7 +10,7 @@ Configuration DSC_WSManListener_Config_Add_HTTPS { Issuer = $Node.Issuer SubjectFormat = $Node.SubjectFormat MatchAlternate = $Node.MatchAlternate - DN = $Node.DN + BaseDN = $Node.BaseDN } } } From b10f76aff65b8b3827b5edf470b7d58a0104d96c Mon Sep 17 00:00:00 2001 From: Daniel Hughes Date: Wed, 18 Sep 2024 17:06:40 +0100 Subject: [PATCH 076/134] Update Changelog --- CHANGELOG.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9f3a582..56794a7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 for the DSC resource. - Added build task `Generate_Wiki_Content` to generate the wiki content that can be used to update the GitHub Wiki. +- `WSManReason` + - Used in Class Resources ### Changed @@ -62,12 +64,22 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - `DSC_WSManServiceConfig` - Refactor `Test-TargetResource` to use `Test-DscParameterState`. - Remove unused strings +- `DSC_WSManListener` + - Converted to Class Resource + - Extracted private functions to own files + - BREAKING: Renamed parameter `DN` to `BaseDN` - fixes [Issue #89](https://github.com/dsccommunity/WSManDsc/issues/89). +- `RequiredModules` + - Added `DscResource.Base` class ### Fixed - Fixed pipeline by replacing the GitVersion task in the `azure-pipelines.yml` with a script. +### Removed +- `Get-InvalidOperationRecord` + - This is now provided by `DscResource.Test` + ## [3.1.1] - 2020-01-31 ### Changed From 0dd676dca32add244287b442ac429dc2d1ebdc08 Mon Sep 17 00:00:00 2001 From: Daniel Hughes Date: Wed, 18 Sep 2024 17:18:28 +0100 Subject: [PATCH 077/134] Add Find-Certificate --- source/Private/Find-Certificate.ps1 | 185 ++++++++ tests/Unit/Private/Find-Certificate.Tests.ps1 | 417 ++++++++++++++++++ 2 files changed, 602 insertions(+) create mode 100644 source/Private/Find-Certificate.ps1 create mode 100644 tests/Unit/Private/Find-Certificate.Tests.ps1 diff --git a/source/Private/Find-Certificate.ps1 b/source/Private/Find-Certificate.ps1 new file mode 100644 index 0000000..167dd72 --- /dev/null +++ b/source/Private/Find-Certificate.ps1 @@ -0,0 +1,185 @@ +<# + .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 DN + This is a Distinguished Name component that will be 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()] + [ValidateSet('Both', 'FQDNOnly', 'NameOnly')] + [System.String] + $SubjectFormat = 'Both', + + [Parameter()] + [System.Boolean] + $MatchAlternate, + + [Parameter()] + [System.String] + $DN, + + [Parameter()] + [System.String] + $CertificateThumbprint, + + [Parameter()] + [System.String] + $Hostname + ) + + if ($PSBoundParameters.ContainsKey('CertificateThumbprint')) + { + Write-Verbose -Message ( @( + "$($MyInvocation.MyCommand): " + $($script:localizedData.FindCertificateByThumbprintMessage) ` + -f $CertificateThumbprint + ) -join '' ) + + $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 'Both', 'FQDNOnly') + { + # Lookup the certificate using the FQDN of the machine + if ([System.String]::IsNullOrEmpty($Hostname)) + { + $Hostname = [System.Net.Dns]::GetHostByName($ENV:computerName).Hostname + } + $Subject = "CN=$Hostname" + + if ($PSBoundParameters.ContainsKey('DN')) + { + $Subject = "$Subject, $DN" + } # if + + if ($MatchAlternate) + { + # Try and lookup the certificate using the subject and the alternate name + Write-Verbose -Message ( @( + "$($MyInvocation.MyCommand): " + $($script:localizedData.FindCertificateAlternateMessage) ` + -f $Subject, $Issuer, $Hostname + ) -join '' ) + + $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 ( @( + "$($MyInvocation.MyCommand): " + $($script:localizedData.FindCertificateMessage) ` + -f $Subject, $Issuer + ) -join '' ) + + $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 'Both', 'NameOnly')) + { + # If could not find an FQDN cert, try for one issued to the computer name + [System.String] $Hostname = $ENV:ComputerName + [System.String] $Subject = "CN=$Hostname" + + if ($PSBoundParameters.ContainsKey('DN')) + { + $Subject = "$Subject, $DN" + } # if + + if ($MatchAlternate) + { + # Try and lookup the certificate using the subject and the alternate name + Write-Verbose -Message ( @( + "$($MyInvocation.MyCommand): " + $($script:localizedData.FindCertificateAlternateMessage) ` + -f $Subject, $Issuer, $Hostname + ) -join '' ) + + $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 ( @( + "$($MyInvocation.MyCommand): " + $($script:localizedData.FindCertificateMessage) ` + -f $Subject, $Issuer + ) -join '' ) + + $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 ( @( + "$($MyInvocation.MyCommand): " + $($script:localizedData.CertificateFoundMessage) ` + -f $certificate.thumbprint + ) -join '' ) + } + else + { + Write-Verbose -Message ( @( + "$($MyInvocation.MyCommand): " + $($script:localizedData.CertificateNotFoundMessage) ` + ) -join '' ) + } # if + + return $certificate +} diff --git a/tests/Unit/Private/Find-Certificate.Tests.ps1 b/tests/Unit/Private/Find-Certificate.Tests.ps1 new file mode 100644 index 0000000..61c4202 --- /dev/null +++ b/tests/Unit/Private/Find-Certificate.Tests.ps1 @@ -0,0 +1,417 @@ +[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '')] +param () + +BeforeDiscovery { + try + { + if (-not (Get-Module -Name 'DscResource.Test')) + { + # Assumes dependencies has been resolved, so if this module is not available, run 'noop' task. + if (-not (Get-Module -Name 'DscResource.Test' -ListAvailable)) + { + # Redirect all streams to $null, except the error stream (stream 2) + & "$PSScriptRoot/../../../build.ps1" -Tasks 'noop' 2>&1 4>&1 5>&1 6>&1 > $null + } + + # If the dependencies has not been resolved, this will throw an error. + Import-Module -Name 'DscResource.Test' -Force -ErrorAction 'Stop' + } + } + catch [System.IO.FileNotFoundException] + { + throw 'DscResource.Test module dependency not found. Please run ".\build.ps1 -ResolveDependency -Tasks build" first.' + } +} + +BeforeAll { + $script:dscModuleName = 'WSManDsc' + + Import-Module -Name $script:dscModuleName + + $PSDefaultParameterValues['InModuleScope:ModuleName'] = $script:dscModuleName + $PSDefaultParameterValues['Mock:ModuleName'] = $script:dscModuleName + $PSDefaultParameterValues['Should:ModuleName'] = $script:dscModuleName +} + +AfterAll { + $PSDefaultParameterValues.Remove('InModuleScope:ModuleName') + $PSDefaultParameterValues.Remove('Mock:ModuleName') + $PSDefaultParameterValues.Remove('Should:ModuleName') + + # Unload the module being tested so that it doesn't impact any other tests. + Get-Module -Name $script:dscModuleName -All | Remove-Module -Force +} + +Describe 'Find-Certificate' -Tag 'Private' { + # Context 'CertificateThumbprint is passed but does not exist' { + # BeforeAll { + # Mock -CommandName Get-ChildItem + # } + + # It 'Should not throw error' { + # InModuleScope -Parameters @{ + # mockCertificateThumbprint = $script:mockCertificateThumbprint + # } -ScriptBlock { + # Set-StrictMode -Version 1.0 + + # { $script:returnedCertificate = Find-Certificate ` + # -CertificateThumbprint $mockCertificateThumbprint ` + # -Verbose:$VerbosePreference } | Should -Not -Throw + # } + # } + + # It 'Should return null' { + # InModuleScope -ScriptBlock { + # Set-StrictMode -Version 1.0 + + # $script:returnedCertificate | Should -BeNullOrEmpty + # } + # } + + # It 'Should call expected Mocks' { + # Should -Invoke ` + # -CommandName Get-ChildItem ` + # -Exactly -Times 1 ` + # -Scope Context + # } + # } + + # Context 'CertificateThumbprint is passed and does exist' { + # BeforeAll { + # Mock -CommandName Get-ChildItem -MockWith { + # $mockCertificateDN + # } + # } + + # It 'Should not throw error' { + # InModuleScope -Parameters @{ + # mockCertificateThumbprint = $script:mockCertificateThumbprint + # } -ScriptBlock { + # Set-StrictMode -Version 1.0 + + # { $script:returnedCertificate = Find-Certificate ` + # -CertificateThumbprint $mockCertificateThumbprint ` + # -Verbose:$VerbosePreference } | Should -Not -Throw + # } + # } + + # It 'Should return expected certificate' { + # InModuleScope -Parameters @{ + # mockCertificateThumbprint = $script:mockCertificateThumbprint + # } -ScriptBlock { + # Set-StrictMode -Version 1.0 + + # $script:returnedCertificate.Thumbprint | Should -Be $mockCertificateThumbprint + # } + # } + + # It 'Should call expected Mocks' { + # Should -Invoke ` + # -CommandName Get-ChildItem ` + # -Exactly -Times 1 ` + # -Scope Context + # } + # } + + # Context 'SubjectFormat is Both, Certificate does not exist, DN passed' { + # BeforeAll { + # Mock -CommandName Get-ChildItem + # } + + # It 'Should not throw error' { + # InModuleScope -Parameters @{ + # mockIssuer = $script:mockIssuer + # mockDN = $script:mockDN + # } -ScriptBlock { + # Set-StrictMode -Version 1.0 + + # { $script:returnedCertificate = Find-Certificate ` + # -Issuer $mockIssuer ` + # -SubjectFormat 'Both' ` + # -MatchAlternate $true ` + # -DN $mockDN ` + # -Verbose:$VerbosePreference } | Should -Not -Throw + # } + # } + + # It 'Should return null' { + # InModuleScope -ScriptBlock { + # Set-StrictMode -Version 1.0 + + # $script:returnedCertificate | Should -BeNullOrEmpty + # } + # } + + # It 'Should call expected Mocks' { + # Should -Invoke ` + # -CommandName Get-ChildItem ` + # -Exactly -Times 2 ` + # -Scope Context + # } + # } + + # Context 'SubjectFormat is Both, Certificate with DN Exists, DN passed' { + # BeforeAll { + # Mock -CommandName Get-ChildItem -MockWith { + # $mockCertificateDN + # } + # } + + # It 'Should not throw error' { + # InModuleScope -Parameters @{ + # mockIssuer = $script:mockIssuer + # mockDN = $script:mockDN + # } -ScriptBlock { + # Set-StrictMode -Version 1.0 + + # { $script:returnedCertificate = Find-Certificate ` + # -Issuer $mockIssuer ` + # -SubjectFormat 'Both' ` + # -MatchAlternate $true ` + # -DN $mockDN ` + # -Verbose:$VerbosePreference } | Should -Not -Throw + # } + # } + + # It 'Should return expected certificate' { + # InModuleScope -Parameters @{ + # mockCertificateThumbprint = $script:mockCertificateThumbprint + # } -ScriptBlock { + # Set-StrictMode -Version 1.0 + + # $script:returnedCertificate.Thumbprint | Should -Be $mockCertificateThumbprint + # } + # } + + # It 'Should call expected Mocks' { + # Should -Invoke ` + # -CommandName Get-ChildItem ` + # -Exactly -Times 1 ` + # -Scope Context + # } + # } + + # Context 'SubjectFormat is Both, Certificate without DN Exists, DN passed' { + # BeforeAll { + # Mock -CommandName Get-ChildItem -MockWith { + # $mockCertificate + # } + # } + + # It 'Should not throw error' { + # InModuleScope -Parameters @{ + # mockIssuer = $script:mockIssuer + # mockDN = $script:mockDN + # } -ScriptBlock { + # Set-StrictMode -Version 1.0 + + # { $script:returnedCertificate = Find-Certificate ` + # -Issuer $mockIssuer ` + # -SubjectFormat 'Both' ` + # -MatchAlternate $true ` + # -DN $mockDN ` + # -Verbose:$VerbosePreference } | Should -Not -Throw + # } + # } + + # It 'Should return null' { + # InModuleScope -ScriptBlock { + # Set-StrictMode -Version 1.0 + + # $script:returnedCertificate | Should -BeNullOrEmpty + # } + # } + + # It 'Should call expected Mocks' { + # Should -Invoke ` + # -CommandName Get-ChildItem ` + # -Exactly -Times 2 ` + # -Scope Context + # } + # } + + # Context 'SubjectFormat is Both, Certificate does not exist, DN not passed' { + # BeforeAll { + # Mock -CommandName Get-ChildItem + # } + + # It 'Should not throw error' { + # InModuleScope -Parameters @{ + # mockIssuer = $script:mockIssuer + # } -ScriptBlock { + # Set-StrictMode -Version 1.0 + + # { $script:returnedCertificate = Find-Certificate ` + # -Issuer $mockIssuer ` + # -SubjectFormat 'Both' ` + # -MatchAlternate $true ` + # -Verbose:$VerbosePreference } | Should -Not -Throw + # } + # } + + # It 'Should return null' { + # InModuleScope -ScriptBlock { + # Set-StrictMode -Version 1.0 + + # $script:returnedCertificate | Should -BeNullOrEmpty + # } + # } + + # It 'Should call expected Mocks' { + # Should -Invoke ` + # -CommandName Get-ChildItem ` + # -Exactly -Times 2 ` + # -Scope Context + # } + # } + + # Context 'SubjectFormat is Both, Certificate with DN Exists, DN not passed' { + # BeforeAll { + # Mock -CommandName Get-ChildItem -MockWith { + # $mockCertificateDN + # } + # } + + # It 'Should not throw error' { + # InModuleScope -Parameters @{ + # mockIssuer = $script:mockIssuer + # } -ScriptBlock { + # Set-StrictMode -Version 1.0 + + # { $script:returnedCertificate = Find-Certificate ` + # -Issuer $mockIssuer ` + # -SubjectFormat 'Both' ` + # -MatchAlternate $true ` + # -Verbose:$VerbosePreference } | Should -Not -Throw + # } + # } + + # It 'Should return null' { + # InModuleScope -ScriptBlock { + # Set-StrictMode -Version 1.0 + + # $script:returnedCertificate | Should -BeNullOrEmpty + # } + # } + + # It 'Should call expected Mocks' { + # Should -Invoke ` + # -CommandName Get-ChildItem ` + # -Exactly -Times 2 ` + # -Scope Context + # } + # } + + # Context 'SubjectFormat is Both, Certificate without DN Exists, DN not passed' { + # BeforeAll { + # Mock -CommandName Get-ChildItem -MockWith { + # $mockCertificate + # } + # } + + # It 'Should not throw error' { + # InModuleScope -Parameters @{ + # mockIssuer = $script:mockIssuer + # } -ScriptBlock { + # Set-StrictMode -Version 1.0 + + # { $script:returnedCertificate = Find-Certificate ` + # -Issuer $mockIssuer ` + # -SubjectFormat 'Both' ` + # -MatchAlternate $true ` + # -Verbose:$VerbosePreference } | Should -Not -Throw + # } + # } + + # It 'Should return expected certificate' { + # InModuleScope -Parameters @{ + # mockCertificateThumbprint = $script:mockCertificateThumbprint + # } -ScriptBlock { + # Set-StrictMode -Version 1.0 + + # $script:returnedCertificate.Thumbprint | Should -Be $mockCertificateThumbprint + # } + # } + + # It 'Should call expected Mocks' { + # Should -Invoke ` + # -CommandName Get-ChildItem ` + # -Exactly -Times 1 ` + # -Scope Context + # } + # } + + # Context 'SubjectFormat is Both, Certificate does not exist, DN not passed, MatchAlternate is false' { + # BeforeAll { + # Mock -CommandName Get-ChildItem + # } + + # It 'Should not throw error' { + # InModuleScope -Parameters @{ + # mockIssuer = $script:mockIssuer + # } -ScriptBlock { + # Set-StrictMode -Version 1.0 + + # { $script:returnedCertificate = Find-Certificate ` + # -Issuer $mockIssuer ` + # -SubjectFormat 'Both' ` + # -MatchAlternate $false ` + # -Verbose:$VerbosePreference } | Should -Not -Throw + # } + # } + + # It 'Should return null' { + # InModuleScope -ScriptBlock { + # Set-StrictMode -Version 1.0 + + # $script:returnedCertificate | Should -BeNullOrEmpty + # } + # } + + # It 'Should call expected Mocks' { + # Should -Invoke ` + # -CommandName Get-ChildItem ` + # -Exactly -Times 2 ` + # -Scope Context + # } + # } + + # Context 'SubjectFormat is Both, Certificate without DN Exists, DN not passed, MatchAlternate is false' { + # BeforeAll { + # Mock -CommandName Get-ChildItem -MockWith { + # $mockCertificate + # } + # } + + # It 'Should not throw error' { + # InModuleScope -Parameters @{ + # mockIssuer = $script:mockIssuer + # } -ScriptBlock { + # Set-StrictMode -Version 1.0 + + # { $script:returnedCertificate = Find-Certificate ` + # -Issuer $mockIssuer ` + # -SubjectFormat 'Both' ` + # -MatchAlternate $false ` + # -Verbose:$VerbosePreference } | Should -Not -Throw + # } + # } + + # It 'Should return expected certificate' { + # InModuleScope -Parameters @{ + # mockCertificateThumbprint = $script:mockCertificateThumbprint + # } -ScriptBlock { + # Set-StrictMode -Version 1.0 + + # $script:returnedCertificate.Thumbprint | Should -Be $mockCertificateThumbprint + # } + # } + + # It 'Should call expected Mocks' { + # Should -Invoke ` + # -CommandName Get-ChildItem ` + # -Exactly -Times 1 ` + # -Scope Context + # } + # } +} From 87b9fdb6aba5df8d5a78e9d5f4ea4c5f981438e5 Mon Sep 17 00:00:00 2001 From: Daniel Hughes Date: Wed, 18 Sep 2024 17:26:33 +0100 Subject: [PATCH 078/134] Do not use modulefast --- Resolve-Dependency.psd1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Resolve-Dependency.psd1 b/Resolve-Dependency.psd1 index bbf34e1..07945f8 100644 --- a/Resolve-Dependency.psd1 +++ b/Resolve-Dependency.psd1 @@ -3,7 +3,7 @@ AllowPrerelease = $false WithYAML = $true - UseModuleFast = $true + #UseModuleFast = $true #ModuleFastVersion = '0.1.2' #ModuleFastBleedingEdge = $true From 2e6032b71f8f63121ea002d447da7174ef0520ad Mon Sep 17 00:00:00 2001 From: Daniel Hughes Date: Wed, 18 Sep 2024 18:32:02 +0100 Subject: [PATCH 079/134] Add WSManReason Tests --- tests/Unit/Classes/WSManReason.Tests.ps1 | 76 ++++++++++++++++++++++++ 1 file changed, 76 insertions(+) create mode 100644 tests/Unit/Classes/WSManReason.Tests.ps1 diff --git a/tests/Unit/Classes/WSManReason.Tests.ps1 b/tests/Unit/Classes/WSManReason.Tests.ps1 new file mode 100644 index 0000000..878a7d5 --- /dev/null +++ b/tests/Unit/Classes/WSManReason.Tests.ps1 @@ -0,0 +1,76 @@ +[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '')] +param () + +BeforeDiscovery { + try + { + if (-not (Get-Module -Name 'DscResource.Test')) + { + # Assumes dependencies has been resolved, so if this module is not available, run 'noop' task. + if (-not (Get-Module -Name 'DscResource.Test' -ListAvailable)) + { + # Redirect all streams to $null, except the error stream (stream 2) + & "$PSScriptRoot/../../../build.ps1" -Tasks 'noop' 2>&1 4>&1 5>&1 6>&1 > $null + } + + # If the dependencies has not been resolved, this will throw an error. + Import-Module -Name 'DscResource.Test' -Force -ErrorAction 'Stop' + } + } + catch [System.IO.FileNotFoundException] + { + throw 'DscResource.Test module dependency not found. Please run ".\build.ps1 -ResolveDependency -Tasks build" first.' + } +} + +BeforeAll { + $script:dscModuleName = 'WSManDsc' + + Import-Module -Name $script:dscModuleName + + $PSDefaultParameterValues['InModuleScope:ModuleName'] = $script:dscModuleName + $PSDefaultParameterValues['Mock:ModuleName'] = $script:dscModuleName + $PSDefaultParameterValues['Should:ModuleName'] = $script:dscModuleName +} + +AfterAll { + $PSDefaultParameterValues.Remove('InModuleScope:ModuleName') + $PSDefaultParameterValues.Remove('Mock:ModuleName') + $PSDefaultParameterValues.Remove('Should:ModuleName') + + # Unload the module being tested so that it doesn't impact any other tests. + Get-Module -Name $script:dscModuleName -All | Remove-Module -Force +} + +Describe 'WSManReason' -Tag 'WSManReason' { + Context 'When instantiating the class' { + It 'Should not throw an error' { + $script:mockWSManReasonInstance = InModuleScope -ScriptBlock { + [WSManReason]::new() + } + } + + It 'Should be of the correct type' { + $mockWSManReasonInstance | Should -Not -BeNullOrEmpty + $mockWSManReasonInstance.GetType().Name | Should -Be 'WSManReason' + } + } + + Context 'When setting an reading values' { + It 'Should be able to set value in instance' { + $script:mockWSManReasonInstance = InModuleScope -ScriptBlock { + $WSManReasonInstance = [WSManReason]::new() + + $WSManReasonInstance.Code = 'WSManReason:WSManReason:Ensure' + $WSManReasonInstance.Phrase = 'The property Ensure should be "Present", but was "Absent"' + + return $WSManReasonInstance + } + } + + It 'Should be able read the values from instance' { + $mockWSManReasonInstance.Code | Should -Be 'WSManReason:WSManReason:Ensure' + $mockWSManReasonInstance.Phrase = 'The property Ensure should be "Present", but was "Absent"' + } + } +} From 6d8b4345fecebffbbef612fc4524a11d07099a88 Mon Sep 17 00:00:00 2001 From: Daniel Hughes Date: Wed, 18 Sep 2024 19:32:58 +0100 Subject: [PATCH 080/134] Update tests --- source/Classes/020.WSManListener.ps1 | 26 ++++- tests/Unit/Classes/WSManListener.Tests.ps1 | 121 ++++++++++++--------- 2 files changed, 93 insertions(+), 54 deletions(-) diff --git a/source/Classes/020.WSManListener.ps1 b/source/Classes/020.WSManListener.ps1 index b4fa37e..6e39ec2 100644 --- a/source/Classes/020.WSManListener.ps1 +++ b/source/Classes/020.WSManListener.ps1 @@ -116,11 +116,11 @@ class WSManListener : ResourceBase ) - # Get the port if it's not provided - if ($this.Port) - { - $this.Port = Get-DefaultPort -Transport $this.Transport -Port $this.Port - } + # # Set subject format to default value + # if (-not $this.SubjectFormat) + # { + # $this.SubjectFormat = 'Both' + # } } [WSManListener] Get() @@ -136,6 +136,12 @@ class WSManListener : ResourceBase Transport = $properties.Transport } + # Get the port if it's not provided + if (-not $properties.Port) + { + $this.Port = Get-DefaultPort -Transport $properties.Transport + } + $getCurrentStateResult = Get-Listener @getParameters $currentCertificate = '' @@ -202,6 +208,16 @@ class WSManListener : ResourceBase #> hidden [void] AssertProperties([System.Collections.Hashtable] $properties) { + # if ($null -ne $properties.SubjectFormat) + # { + # $errorMessage = $this.localizedData.SubjectFormatMustBeValid + + # if ($properties.SubjectFormat -inotin ('Both', 'FQDNOnly', 'NameOnly')) + # { + # New-InvalidArgumentException -ArgumentName 'SubjectFormat' -Message $errorMessage + # } + # } + # # The properties MaximumFiles and MaximumRolloverFiles are mutually exclusive. # $assertBoundParameterParameters = @{ # BoundParameterList = $properties diff --git a/tests/Unit/Classes/WSManListener.Tests.ps1 b/tests/Unit/Classes/WSManListener.Tests.ps1 index 05a94ed..dab01ea 100644 --- a/tests/Unit/Classes/WSManListener.Tests.ps1 +++ b/tests/Unit/Classes/WSManListener.Tests.ps1 @@ -216,53 +216,76 @@ Describe 'WSManListener\Get()' -Tag 'Get' { } } - # Context 'When the system is not in the desired state' { - # Context 'When property Path have the wrong value for a File audit' { - # BeforeAll { - # InModuleScope -ScriptBlock { - # $script:mockSqlAuditInstance = [SqlAudit] @{ - # Name = 'MockAuditName' - # InstanceName = 'NamedInstance' - # Path = 'C:\NewFolder' - # } - - # <# - # This mocks the method GetCurrentState(). - - # Method Get() will call the base method Get() which will - # call back to the derived class method GetCurrentState() - # to get the result to return from the derived method Get(). - # #> - # $script:mockSqlAuditInstance | - # Add-Member -Force -MemberType 'ScriptMethod' -Name 'GetCurrentState' -Value { - # return [System.Collections.Hashtable] @{ - # Name = 'MockAuditName' - # InstanceName = 'NamedInstance' - # Path = 'C:\Temp' - # } - # } -PassThru | - # Add-Member -Force -MemberType 'ScriptMethod' -Name 'AssertProperties' -Value { - # return - # } - # } - # } - - # It 'Should return the correct values' { - # InModuleScope -ScriptBlock { - # $currentState = $script:mockSqlAuditInstance.Get() - - # $currentState.InstanceName | Should -Be 'NamedInstance' - # $currentState.Name | Should -Be 'MockAuditName' - # $currentState.ServerName | Should -Be (Get-ComputerName) - # $currentState.Credential | Should -BeNullOrEmpty - - # $currentState.Path | Should -Be 'C:\Temp' - - # $currentState.Reasons | Should -HaveCount 1 - # $currentState.Reasons[0].Code | Should -Be 'SqlAudit:SqlAudit:Path' - # $currentState.Reasons[0].Phrase | Should -Be 'The property Path should be "C:\NewFolder", but was "C:\Temp"' - # } - # } - # } - # } + Context 'When the system is not in the desired state' { + Context 'When property ''Port'' has the wrong value' { + BeforeAll { + InModuleScope -ScriptBlock { + Set-StrictMode -Version 1.0 + + $script:mockWSManListenerInstance = [WSManListener] @{ + Transport = 'HTTPS' + Port = 5986 + Ensure = 'Present' + } + + <# + This mocks the method GetCurrentState(). + + Method Get() will call the base method Get() which will + call back to the derived class method GetCurrentState() + to get the result to return from the derived method Get(). + #> + $script:mockWSManListenerInstance | + Add-Member -Force -MemberType 'ScriptMethod' -Name 'GetCurrentState' -Value { + return [System.Collections.Hashtable] @{ + Transport = 'HTTPS' + Port = 6000 + Address = '*' + Enabled = 'true' + URLPrefix = 'wsman' + Issuer = $null + SubjectFormat = 'Both' + MatchAlternate = $null + BaseDN = $null + CertificateThumbprint = $null + Hostname = $null + } + } -PassThru | + Add-Member -Force -MemberType 'ScriptMethod' -Name 'AssertProperties' -Value { + return + } + } + } + + It 'Should return the correct values' { + InModuleScope -ScriptBlock { + Set-StrictMode -Version 1.0 + + $currentState = $script:mockWSManListenerInstance.Get() + + $currentState.Transport | Should -Be 'HTTPS' + + $currentState.Port | Should -Be 6000 + $currentState.Port | Should -BeOfType System.UInt16 + + $currentState.Address | Should -Be '*' + $currentState.Enabled | Should -Be $true + $currentState.URLPrefix | Should -Be 'wsman' + + $currentState.Issuer | Should -BeNullOrEmpty + $currentState.SubjectFormat | Should -Be 'Both' + $currentState.MatchAlternate | Should -BeNullOrEmpty + $currentState.BaseDN | Should -BeNullOrEmpty + $currentState.CertificateThumbprint | Should -BeNullOrEmpty + $currentState.Hostname | Should -BeNullOrEmpty + + $currentState.Ensure | Should -Be 'Present' + + $currentState.Reasons | Should -HaveCount 1 + $currentState.Reasons[0].Code | Should -Be 'WSManListener:WSManListener:Port' + $currentState.Reasons[0].Phrase | Should -Be 'The property Port should be 5986, but was 6000' + } + } + } + } } From 9bb0f439c78869ad976cdbeb30c6b8d7b851f8d7 Mon Sep 17 00:00:00 2001 From: Daniel Hughes Date: Wed, 18 Sep 2024 20:04:50 +0100 Subject: [PATCH 081/134] UPdate Find Certificate --- source/Private/Find-Certificate.ps1 | 60 +- source/en-US/WSManDsc.strings.psd1 | 17 + tests/Unit/Private/Find-Certificate.Tests.ps1 | 587 +++++++++--------- 3 files changed, 328 insertions(+), 336 deletions(-) create mode 100644 source/en-US/WSManDsc.strings.psd1 diff --git a/source/Private/Find-Certificate.ps1 b/source/Private/Find-Certificate.ps1 index 167dd72..ac67b53 100644 --- a/source/Private/Find-Certificate.ps1 +++ b/source/Private/Find-Certificate.ps1 @@ -14,9 +14,9 @@ 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 DN - This is a Distinguished Name component that will be used to identify the certificate to use - for the 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. @@ -42,7 +42,7 @@ function Find-Certificate [Parameter()] [System.String] - $DN, + $BaseDN, [Parameter()] [System.String] @@ -55,11 +55,7 @@ function Find-Certificate if ($PSBoundParameters.ContainsKey('CertificateThumbprint')) { - Write-Verbose -Message ( @( - "$($MyInvocation.MyCommand): " - $($script:localizedData.FindCertificateByThumbprintMessage) ` - -f $CertificateThumbprint - ) -join '' ) + Write-Verbose -Message ($script:localizedData.FindCertificate_ByThumbprintMessage -f $CertificateThumbprint) $certificate = Get-ChildItem -Path Cert:\localmachine\my | Where-Object -FilterScript { ($_.Thumbprint -eq $CertificateThumbprint) @@ -77,19 +73,15 @@ function Find-Certificate } $Subject = "CN=$Hostname" - if ($PSBoundParameters.ContainsKey('DN')) + if ($PSBoundParameters.ContainsKey('BaseDN')) { - $Subject = "$Subject, $DN" + $Subject = "$Subject, $BaseDN" } # if if ($MatchAlternate) { # Try and lookup the certificate using the subject and the alternate name - Write-Verbose -Message ( @( - "$($MyInvocation.MyCommand): " - $($script:localizedData.FindCertificateAlternateMessage) ` - -f $Subject, $Issuer, $Hostname - ) -join '' ) + Write-Verbose -Message ($script:localizedData.FindCertificate_AlternateMessage -f $Subject, $Issuer, $Hostname) $certificate = (Get-ChildItem -Path Cert:\localmachine\my | Where-Object -FilterScript { ($_.Extensions.EnhancedKeyUsages.FriendlyName ` @@ -102,11 +94,7 @@ function Find-Certificate else { # Try and lookup the certificate using the subject name - Write-Verbose -Message ( @( - "$($MyInvocation.MyCommand): " - $($script:localizedData.FindCertificateMessage) ` - -f $Subject, $Issuer - ) -join '' ) + Write-Verbose -Message ($script:localizedData.FindCertificate_Message - $Subject, $Issuer) $certificate = Get-ChildItem -Path Cert:\localmachine\my | Where-Object -FilterScript { ($_.Extensions.EnhancedKeyUsages.FriendlyName ` @@ -117,26 +105,21 @@ function Find-Certificate } # if } - if (-not $certificate ` - -and ($SubjectFormat -in 'Both', 'NameOnly')) + if (-not $certificate -and ($SubjectFormat -in 'Both', 'NameOnly')) { # If could not find an FQDN cert, try for one issued to the computer name [System.String] $Hostname = $ENV:ComputerName [System.String] $Subject = "CN=$Hostname" - if ($PSBoundParameters.ContainsKey('DN')) + if ($PSBoundParameters.ContainsKey('BaseDN')) { - $Subject = "$Subject, $DN" + $Subject = "$Subject, $BaseDN" } # if if ($MatchAlternate) { # Try and lookup the certificate using the subject and the alternate name - Write-Verbose -Message ( @( - "$($MyInvocation.MyCommand): " - $($script:localizedData.FindCertificateAlternateMessage) ` - -f $Subject, $Issuer, $Hostname - ) -join '' ) + Write-Verbose -Message ($script:localizedData.FindCertificate_AlternateMessage -f $Subject, $Issuer, $Hostname) $certificate = Get-ChildItem -Path Cert:\localmachine\my | Where-Object -FilterScript { ($_.Extensions.EnhancedKeyUsages.FriendlyName ` @@ -149,11 +132,7 @@ function Find-Certificate else { # Try and lookup the certificate using the subject name - Write-Verbose -Message ( @( - "$($MyInvocation.MyCommand): " - $($script:localizedData.FindCertificateMessage) ` - -f $Subject, $Issuer - ) -join '' ) + Write-Verbose -Message ($script:localizedData.FindCertificate_Message - $Subject, $Issuer) $certificate = Get-ChildItem -Path Cert:\localmachine\my | Where-Object -FilterScript { ($_.Extensions.EnhancedKeyUsages.FriendlyName ` @@ -167,18 +146,11 @@ function Find-Certificate if ($certificate) { - Write-Verbose -Message ( @( - "$($MyInvocation.MyCommand): " - $($script:localizedData.CertificateFoundMessage) ` - -f $certificate.thumbprint - ) -join '' ) + Write-Verbose -Message ($script:localizedData.FindCertificate_FoundMessage -f $certificate.thumbprint) } else { - Write-Verbose -Message ( @( - "$($MyInvocation.MyCommand): " - $($script:localizedData.CertificateNotFoundMessage) ` - ) -join '' ) + Write-Verbose -Message ($script:localizedData.FindCertificate_NotFoundMessage) } # if return $certificate diff --git a/source/en-US/WSManDsc.strings.psd1 b/source/en-US/WSManDsc.strings.psd1 new file mode 100644 index 0000000..274cb4f --- /dev/null +++ b/source/en-US/WSManDsc.strings.psd1 @@ -0,0 +1,17 @@ +<# + .SYNOPSIS + The localized resource strings in English (en-US) for the + resource WSManDsc module. This file should only contain + localized strings for private functions, public command, and + classes (that are not a DSC resource). +#> + +ConvertFrom-StringData @' + + ## Find-Certificate + FindCertificate_ByThumbprintMessage = Looking for machine server certificate with thumbprint '{0}'. + FindCertificate_AlternateMessage = Looking for machine server certificate with subject '{0}' issued by '{1}' and DNS name '{2}'. + FindCertificate_Message = Looking for machine server certificate with subject '{0}' issued by '{1}'. + FindCertificate_FoundMessage = Certificate found with thumbprint '{0}' to use for HTTPS Listener. + FindCertificate_NotFoundMessage = Certificate not found. +'@ diff --git a/tests/Unit/Private/Find-Certificate.Tests.ps1 b/tests/Unit/Private/Find-Certificate.Tests.ps1 index 61c4202..70518b7 100644 --- a/tests/Unit/Private/Find-Certificate.Tests.ps1 +++ b/tests/Unit/Private/Find-Certificate.Tests.ps1 @@ -43,375 +43,378 @@ AfterAll { } Describe 'Find-Certificate' -Tag 'Private' { - # Context 'CertificateThumbprint is passed but does not exist' { - # BeforeAll { - # Mock -CommandName Get-ChildItem - # } + BeforeAll { + InModuleScope -ScriptBlock { + $script:mockCertificateThumbprint = '74FA31ADEA7FDD5333CED10910BFA6F665A1F2FC' + } + } + Context 'CertificateThumbprint is passed but does not exist' { + BeforeAll { + Mock -CommandName Get-ChildItem + } - # It 'Should not throw error' { - # InModuleScope -Parameters @{ - # mockCertificateThumbprint = $script:mockCertificateThumbprint - # } -ScriptBlock { - # Set-StrictMode -Version 1.0 + It 'Should not throw error' { + InModuleScope -ScriptBlock { + Set-StrictMode -Version 1.0 - # { $script:returnedCertificate = Find-Certificate ` - # -CertificateThumbprint $mockCertificateThumbprint ` - # -Verbose:$VerbosePreference } | Should -Not -Throw - # } - # } + $findParameters = @{ + CertificateThumbprint = $script:mockCertificateThumbprint + Verbose = $VerbosePreference + } - # It 'Should return null' { - # InModuleScope -ScriptBlock { - # Set-StrictMode -Version 1.0 + $script:returnedCertificate = Find-Certificate @findParameters - # $script:returnedCertificate | Should -BeNullOrEmpty - # } - # } + { $script:returnedCertificate } | Should -Not -Throw + } + } - # It 'Should call expected Mocks' { - # Should -Invoke ` - # -CommandName Get-ChildItem ` - # -Exactly -Times 1 ` - # -Scope Context - # } - # } + It 'Should return null' { + InModuleScope -ScriptBlock { + Set-StrictMode -Version 1.0 - # Context 'CertificateThumbprint is passed and does exist' { - # BeforeAll { - # Mock -CommandName Get-ChildItem -MockWith { - # $mockCertificateDN - # } - # } + $script:returnedCertificate | Should -BeNullOrEmpty + } - # It 'Should not throw error' { - # InModuleScope -Parameters @{ - # mockCertificateThumbprint = $script:mockCertificateThumbprint - # } -ScriptBlock { - # Set-StrictMode -Version 1.0 + Should -Invoke -CommandName Get-ChildItem -Exactly -Times 1 -Scope Context + } + } - # { $script:returnedCertificate = Find-Certificate ` - # -CertificateThumbprint $mockCertificateThumbprint ` - # -Verbose:$VerbosePreference } | Should -Not -Throw - # } - # } + Context 'CertificateThumbprint is passed and does exist' { + BeforeAll { + Mock -CommandName Get-ChildItem -MockWith { + @{ + Thumbprint = '74FA31ADEA7FDD5333CED10910BFA6F665A1F2FC' + Subject = "CN=$([System.Net.Dns]::GetHostByName($ENV:computerName).Hostname), O=Contoso Inc, S=Pennsylvania, C=US" + Issuer = 'CN=CONTOSO.COM Issuing CA, DC=CONTOSO, DC=COM' + Extensions = @{ EnhancedKeyUsages = @{ FriendlyName = 'Server Authentication' } } + DNSNameList = @{ Unicode = $([System.Net.Dns]::GetHostByName($ENV:computerName).Hostname) } + } + } + } - # It 'Should return expected certificate' { - # InModuleScope -Parameters @{ - # mockCertificateThumbprint = $script:mockCertificateThumbprint - # } -ScriptBlock { - # Set-StrictMode -Version 1.0 + It 'Should not throw error' { + InModuleScope -ScriptBlock { + Set-StrictMode -Version 1.0 - # $script:returnedCertificate.Thumbprint | Should -Be $mockCertificateThumbprint - # } - # } + $findParameters = @{ + CertificateThumbprint = $script:mockCertificateThumbprint + Verbose = $VerbosePreference + } - # It 'Should call expected Mocks' { - # Should -Invoke ` - # -CommandName Get-ChildItem ` - # -Exactly -Times 1 ` - # -Scope Context - # } - # } + $script:returnedCertificate = Find-Certificate @findParameters - # Context 'SubjectFormat is Both, Certificate does not exist, DN passed' { - # BeforeAll { - # Mock -CommandName Get-ChildItem - # } + { $script:returnedCertificate } | Should -Not -Throw + } + } - # It 'Should not throw error' { - # InModuleScope -Parameters @{ - # mockIssuer = $script:mockIssuer - # mockDN = $script:mockDN - # } -ScriptBlock { - # Set-StrictMode -Version 1.0 - - # { $script:returnedCertificate = Find-Certificate ` - # -Issuer $mockIssuer ` - # -SubjectFormat 'Both' ` - # -MatchAlternate $true ` - # -DN $mockDN ` - # -Verbose:$VerbosePreference } | Should -Not -Throw - # } - # } + It 'Should return expected certificate' { + InModuleScope -ScriptBlock { + Set-StrictMode -Version 1.0 - # It 'Should return null' { - # InModuleScope -ScriptBlock { - # Set-StrictMode -Version 1.0 + $script:returnedCertificate.Thumbprint | Should -Be $script:mockCertificateThumbprint + } - # $script:returnedCertificate | Should -BeNullOrEmpty + Should -Invoke -CommandName Get-ChildItem -Exactly -Times 1 -Scope Context + } + } + + # Context 'When SubjectFormat is ''Both''' { + # Context 'Certificate does not exist, DN passed' { + # BeforeAll { + # Mock -CommandName Get-ChildItem # } - # } - # It 'Should call expected Mocks' { - # Should -Invoke ` - # -CommandName Get-ChildItem ` - # -Exactly -Times 2 ` - # -Scope Context - # } - # } + # It 'Should not throw error' { + # InModuleScope -Parameters @{ + # mockIssuer = $script:mockIssuer + # mockDN = $script:mockDN + # } -ScriptBlock { + # Set-StrictMode -Version 1.0 + + # { $script:returnedCertificate = Find-Certificate ` + # -Issuer $mockIssuer ` + # -SubjectFormat 'Both' ` + # -MatchAlternate $true ` + # -DN $mockDN ` + # -Verbose:$VerbosePreference } | Should -Not -Throw + # } + # } + + # It 'Should return null' { + # InModuleScope -ScriptBlock { + # Set-StrictMode -Version 1.0 - # Context 'SubjectFormat is Both, Certificate with DN Exists, DN passed' { - # BeforeAll { - # Mock -CommandName Get-ChildItem -MockWith { - # $mockCertificateDN + # $script:returnedCertificate | Should -BeNullOrEmpty + # } # } - # } - # It 'Should not throw error' { - # InModuleScope -Parameters @{ - # mockIssuer = $script:mockIssuer - # mockDN = $script:mockDN - # } -ScriptBlock { - # Set-StrictMode -Version 1.0 - - # { $script:returnedCertificate = Find-Certificate ` - # -Issuer $mockIssuer ` - # -SubjectFormat 'Both' ` - # -MatchAlternate $true ` - # -DN $mockDN ` - # -Verbose:$VerbosePreference } | Should -Not -Throw + # It 'Should call expected Mocks' { + # Should -Invoke ` + # -CommandName Get-ChildItem ` + # -Exactly -Times 2 ` + # -Scope Context # } # } - # It 'Should return expected certificate' { - # InModuleScope -Parameters @{ - # mockCertificateThumbprint = $script:mockCertificateThumbprint - # } -ScriptBlock { - # Set-StrictMode -Version 1.0 + # Context 'Certificate with DN Exists, DN passed' { + # BeforeAll { + # Mock -CommandName Get-ChildItem -MockWith { + # $mockCertificateDN + # } + # } - # $script:returnedCertificate.Thumbprint | Should -Be $mockCertificateThumbprint + # It 'Should not throw error' { + # InModuleScope -Parameters @{ + # mockIssuer = $script:mockIssuer + # mockDN = $script:mockDN + # } -ScriptBlock { + # Set-StrictMode -Version 1.0 + + # { $script:returnedCertificate = Find-Certificate ` + # -Issuer $mockIssuer ` + # -SubjectFormat 'Both' ` + # -MatchAlternate $true ` + # -DN $mockDN ` + # -Verbose:$VerbosePreference } | Should -Not -Throw + # } # } - # } - # It 'Should call expected Mocks' { - # Should -Invoke ` - # -CommandName Get-ChildItem ` - # -Exactly -Times 1 ` - # -Scope Context - # } - # } + # It 'Should return expected certificate' { + # InModuleScope -Parameters @{ + # mockCertificateThumbprint = $script:mockCertificateThumbprint + # } -ScriptBlock { + # Set-StrictMode -Version 1.0 - # Context 'SubjectFormat is Both, Certificate without DN Exists, DN passed' { - # BeforeAll { - # Mock -CommandName Get-ChildItem -MockWith { - # $mockCertificate + # $script:returnedCertificate.Thumbprint | Should -Be $mockCertificateThumbprint + # } # } - # } - # It 'Should not throw error' { - # InModuleScope -Parameters @{ - # mockIssuer = $script:mockIssuer - # mockDN = $script:mockDN - # } -ScriptBlock { - # Set-StrictMode -Version 1.0 - - # { $script:returnedCertificate = Find-Certificate ` - # -Issuer $mockIssuer ` - # -SubjectFormat 'Both' ` - # -MatchAlternate $true ` - # -DN $mockDN ` - # -Verbose:$VerbosePreference } | Should -Not -Throw + # It 'Should call expected Mocks' { + # Should -Invoke ` + # -CommandName Get-ChildItem ` + # -Exactly -Times 1 ` + # -Scope Context # } # } - # It 'Should return null' { - # InModuleScope -ScriptBlock { - # Set-StrictMode -Version 1.0 + # Context 'Certificate without DN Exists, DN passed' { + # BeforeAll { + # Mock -CommandName Get-ChildItem -MockWith { + # $mockCertificate + # } + # } - # $script:returnedCertificate | Should -BeNullOrEmpty + # It 'Should not throw error' { + # InModuleScope -Parameters @{ + # mockIssuer = $script:mockIssuer + # mockDN = $script:mockDN + # } -ScriptBlock { + # Set-StrictMode -Version 1.0 + + # { $script:returnedCertificate = Find-Certificate ` + # -Issuer $mockIssuer ` + # -SubjectFormat 'Both' ` + # -MatchAlternate $true ` + # -DN $mockDN ` + # -Verbose:$VerbosePreference } | Should -Not -Throw + # } # } - # } - # It 'Should call expected Mocks' { - # Should -Invoke ` - # -CommandName Get-ChildItem ` - # -Exactly -Times 2 ` - # -Scope Context - # } - # } + # It 'Should return null' { + # InModuleScope -ScriptBlock { + # Set-StrictMode -Version 1.0 - # Context 'SubjectFormat is Both, Certificate does not exist, DN not passed' { - # BeforeAll { - # Mock -CommandName Get-ChildItem - # } + # $script:returnedCertificate | Should -BeNullOrEmpty + # } + # } - # It 'Should not throw error' { - # InModuleScope -Parameters @{ - # mockIssuer = $script:mockIssuer - # } -ScriptBlock { - # Set-StrictMode -Version 1.0 - - # { $script:returnedCertificate = Find-Certificate ` - # -Issuer $mockIssuer ` - # -SubjectFormat 'Both' ` - # -MatchAlternate $true ` - # -Verbose:$VerbosePreference } | Should -Not -Throw + # It 'Should call expected Mocks' { + # Should -Invoke ` + # -CommandName Get-ChildItem ` + # -Exactly -Times 2 ` + # -Scope Context # } # } - # It 'Should return null' { - # InModuleScope -ScriptBlock { - # Set-StrictMode -Version 1.0 + # Context 'Certificate does not exist, DN not passed' { + # BeforeAll { + # Mock -CommandName Get-ChildItem + # } - # $script:returnedCertificate | Should -BeNullOrEmpty + # It 'Should not throw error' { + # InModuleScope -Parameters @{ + # mockIssuer = $script:mockIssuer + # } -ScriptBlock { + # Set-StrictMode -Version 1.0 + + # { $script:returnedCertificate = Find-Certificate ` + # -Issuer $mockIssuer ` + # -SubjectFormat 'Both' ` + # -MatchAlternate $true ` + # -Verbose:$VerbosePreference } | Should -Not -Throw + # } # } - # } - # It 'Should call expected Mocks' { - # Should -Invoke ` - # -CommandName Get-ChildItem ` - # -Exactly -Times 2 ` - # -Scope Context - # } - # } + # It 'Should return null' { + # InModuleScope -ScriptBlock { + # Set-StrictMode -Version 1.0 - # Context 'SubjectFormat is Both, Certificate with DN Exists, DN not passed' { - # BeforeAll { - # Mock -CommandName Get-ChildItem -MockWith { - # $mockCertificateDN + # $script:returnedCertificate | Should -BeNullOrEmpty + # } # } - # } - # It 'Should not throw error' { - # InModuleScope -Parameters @{ - # mockIssuer = $script:mockIssuer - # } -ScriptBlock { - # Set-StrictMode -Version 1.0 - - # { $script:returnedCertificate = Find-Certificate ` - # -Issuer $mockIssuer ` - # -SubjectFormat 'Both' ` - # -MatchAlternate $true ` - # -Verbose:$VerbosePreference } | Should -Not -Throw + # It 'Should call expected Mocks' { + # Should -Invoke ` + # -CommandName Get-ChildItem ` + # -Exactly -Times 2 ` + # -Scope Context # } # } - # It 'Should return null' { - # InModuleScope -ScriptBlock { - # Set-StrictMode -Version 1.0 + # Context 'Certificate with DN Exists, DN not passed' { + # BeforeAll { + # Mock -CommandName Get-ChildItem -MockWith { + # $mockCertificateDN + # } + # } - # $script:returnedCertificate | Should -BeNullOrEmpty + # It 'Should not throw error' { + # InModuleScope -Parameters @{ + # mockIssuer = $script:mockIssuer + # } -ScriptBlock { + # Set-StrictMode -Version 1.0 + + # { $script:returnedCertificate = Find-Certificate ` + # -Issuer $mockIssuer ` + # -SubjectFormat 'Both' ` + # -MatchAlternate $true ` + # -Verbose:$VerbosePreference } | Should -Not -Throw + # } # } - # } - # It 'Should call expected Mocks' { - # Should -Invoke ` - # -CommandName Get-ChildItem ` - # -Exactly -Times 2 ` - # -Scope Context - # } - # } + # It 'Should return null' { + # InModuleScope -ScriptBlock { + # Set-StrictMode -Version 1.0 - # Context 'SubjectFormat is Both, Certificate without DN Exists, DN not passed' { - # BeforeAll { - # Mock -CommandName Get-ChildItem -MockWith { - # $mockCertificate + # $script:returnedCertificate | Should -BeNullOrEmpty + # } # } - # } - # It 'Should not throw error' { - # InModuleScope -Parameters @{ - # mockIssuer = $script:mockIssuer - # } -ScriptBlock { - # Set-StrictMode -Version 1.0 - - # { $script:returnedCertificate = Find-Certificate ` - # -Issuer $mockIssuer ` - # -SubjectFormat 'Both' ` - # -MatchAlternate $true ` - # -Verbose:$VerbosePreference } | Should -Not -Throw + # It 'Should call expected Mocks' { + # Should -Invoke ` + # -CommandName Get-ChildItem ` + # -Exactly -Times 2 ` + # -Scope Context # } # } - # It 'Should return expected certificate' { - # InModuleScope -Parameters @{ - # mockCertificateThumbprint = $script:mockCertificateThumbprint - # } -ScriptBlock { - # Set-StrictMode -Version 1.0 + # Context 'Certificate without DN Exists, DN not passed' { + # BeforeAll { + # Mock -CommandName Get-ChildItem -MockWith { + # $mockCertificate + # } + # } - # $script:returnedCertificate.Thumbprint | Should -Be $mockCertificateThumbprint + # It 'Should not throw error' { + # InModuleScope -Parameters @{ + # mockIssuer = $script:mockIssuer + # } -ScriptBlock { + # Set-StrictMode -Version 1.0 + + # { $script:returnedCertificate = Find-Certificate ` + # -Issuer $mockIssuer ` + # -SubjectFormat 'Both' ` + # -MatchAlternate $true ` + # -Verbose:$VerbosePreference } | Should -Not -Throw + # } # } - # } - # It 'Should call expected Mocks' { - # Should -Invoke ` - # -CommandName Get-ChildItem ` - # -Exactly -Times 1 ` - # -Scope Context - # } - # } + # It 'Should return expected certificate' { + # InModuleScope -Parameters @{ + # mockCertificateThumbprint = $script:mockCertificateThumbprint + # } -ScriptBlock { + # Set-StrictMode -Version 1.0 - # Context 'SubjectFormat is Both, Certificate does not exist, DN not passed, MatchAlternate is false' { - # BeforeAll { - # Mock -CommandName Get-ChildItem - # } + # $script:returnedCertificate.Thumbprint | Should -Be $mockCertificateThumbprint + # } + # } - # It 'Should not throw error' { - # InModuleScope -Parameters @{ - # mockIssuer = $script:mockIssuer - # } -ScriptBlock { - # Set-StrictMode -Version 1.0 - - # { $script:returnedCertificate = Find-Certificate ` - # -Issuer $mockIssuer ` - # -SubjectFormat 'Both' ` - # -MatchAlternate $false ` - # -Verbose:$VerbosePreference } | Should -Not -Throw + # It 'Should call expected Mocks' { + # Should -Invoke ` + # -CommandName Get-ChildItem ` + # -Exactly -Times 1 ` + # -Scope Context # } # } - # It 'Should return null' { - # InModuleScope -ScriptBlock { - # Set-StrictMode -Version 1.0 + # Context 'Certificate does not exist, DN not passed, MatchAlternate is false' { + # BeforeAll { + # Mock -CommandName Get-ChildItem + # } - # $script:returnedCertificate | Should -BeNullOrEmpty + # It 'Should not throw error' { + # InModuleScope -Parameters @{ + # mockIssuer = $script:mockIssuer + # } -ScriptBlock { + # Set-StrictMode -Version 1.0 + + # $findParameters = @{ + # Issuer = $mockIssuer + # SubjectFormat = 'Both' + # MatchAlternate = $false + # Verbose = $VerbosePreference + # } + + # { $script:returnedCertificate = Find-Certificate @findParameters } | Should -Not -Throw + # } # } - # } - # It 'Should call expected Mocks' { - # Should -Invoke ` - # -CommandName Get-ChildItem ` - # -Exactly -Times 2 ` - # -Scope Context - # } - # } + # It 'Should return null' { + # InModuleScope -ScriptBlock { + # Set-StrictMode -Version 1.0 - # Context 'SubjectFormat is Both, Certificate without DN Exists, DN not passed, MatchAlternate is false' { - # BeforeAll { - # Mock -CommandName Get-ChildItem -MockWith { - # $mockCertificate - # } - # } + # $script:returnedCertificate | Should -BeNullOrEmpty + # } - # It 'Should not throw error' { - # InModuleScope -Parameters @{ - # mockIssuer = $script:mockIssuer - # } -ScriptBlock { - # Set-StrictMode -Version 1.0 - - # { $script:returnedCertificate = Find-Certificate ` - # -Issuer $mockIssuer ` - # -SubjectFormat 'Both' ` - # -MatchAlternate $false ` - # -Verbose:$VerbosePreference } | Should -Not -Throw + # Should -Invoke -CommandName Get-ChildItem -Exactly -Times 2 -Scope It # } # } - # It 'Should return expected certificate' { - # InModuleScope -Parameters @{ - # mockCertificateThumbprint = $script:mockCertificateThumbprint - # } -ScriptBlock { - # Set-StrictMode -Version 1.0 + # Context 'Certificate without DN Exists, DN not passed, MatchAlternate is false' { + # BeforeAll { + # Mock -CommandName Get-ChildItem -MockWith { + # $mockCertificate + # } + # } - # $script:returnedCertificate.Thumbprint | Should -Be $mockCertificateThumbprint + # It 'Should not throw error' { + # InModuleScope -Parameters @{ + # mockIssuer = $script:mockIssuer + # } -ScriptBlock { + # Set-StrictMode -Version 1.0 + + # $findParameters = @{ + # Issuer = $mockIssuer + # SubjectFormat = 'Both' + # MatchAlternate = $false + # Verbose = $VerbosePreference + # } + + # { $script:returnedCertificate = Find-Certificate @findParameters } | Should -Not -Throw + # } # } - # } - # It 'Should call expected Mocks' { - # Should -Invoke ` - # -CommandName Get-ChildItem ` - # -Exactly -Times 1 ` - # -Scope Context + # It 'Should return expected certificate' { + # InModuleScope -Parameters @{ + # mockCertificateThumbprint = $script:mockCertificateThumbprint + # } -ScriptBlock { + # Set-StrictMode -Version 1.0 + + # $script:returnedCertificate.Thumbprint | Should -Be $mockCertificateThumbprint + # } + + # Should -Invoke -CommandName Get-ChildItem -Exactly -Times 1 -Scope It + # } # } # } } From 3949bf2bd7e5fca0bf639f86efa39c6443762422 Mon Sep 17 00:00:00 2001 From: Daniel Hughes Date: Thu, 19 Sep 2024 13:42:31 +0100 Subject: [PATCH 082/134] Move away from ValidateSet and update Modify --- source/Classes/020.WSManListener.ps1 | 190 ++++++++++++++++++--- source/Enum/005.WSManSubjectFormat.ps1 | 11 ++ source/Enum/005.WSManTransport.ps1 | 10 ++ source/en-US/WSManListener.strings.psd1 | 3 + tests/Unit/Classes/WSManListener.Tests.ps1 | 11 +- 5 files changed, 195 insertions(+), 30 deletions(-) create mode 100644 source/Enum/005.WSManSubjectFormat.ps1 create mode 100644 source/Enum/005.WSManTransport.ps1 diff --git a/source/Classes/020.WSManListener.ps1 b/source/Classes/020.WSManListener.ps1 index 6e39ec2..38786a0 100644 --- a/source/Classes/020.WSManListener.ps1 +++ b/source/Classes/020.WSManListener.ps1 @@ -51,11 +51,12 @@ class WSManListener : ResourceBase { [DscProperty(Key)] - [ValidateSet('HTTP', 'HTTPS')] - [System.String] + #[ValidateSet('HTTP', 'HTTPS')] + #[System.String] + [WSManTransport] $Transport - [DscProperty(Mandatory = $true)] + [DscProperty(Mandatory)] [Ensure] $Ensure @@ -73,9 +74,10 @@ class WSManListener : ResourceBase $Issuer [DscProperty()] - [ValidateSet('Both', 'FQDNOnly', 'NameOnly')] - [System.String] - $SubjectFormat = 'Both' + #[ValidateSet('Both', 'FQDNOnly', 'NameOnly')] + #[System.String] + [WSManSubjectFormat] + $SubjectFormat = [WSManSubjectFormat]::Both [DscProperty()] [Nullable[System.Boolean]] @@ -152,17 +154,16 @@ class WSManListener : ResourceBase } $state = @{ - Transport = $properties.Transport + Transport = $getCurrentStateResult.Transport Port = [System.UInt16] $getCurrentStateResult.Port Address = $getCurrentStateResult.Address - Enabled = $getCurrentStateResult.Enabled - URLPrefix = $getCurrentStateResult.URLPrefix + Issuer = $currentCertificate.Issuer - SubjectFormat = $properties.SubjectFormat - MatchAlternate = $null - BaseDN = $null CertificateThumbprint = $getCurrentStateResult.CertificateThumbprint Hostname = $getCurrentStateResult.HostName + + Enabled = $getCurrentStateResult.Enabled + URLPrefix = $getCurrentStateResult.URLPrefix } return $state @@ -178,22 +179,161 @@ class WSManListener : ResourceBase Base method Set() call this method with the properties that should be enforced and that are not in desired state. #> - [void] Modify([System.Collections.Hashtable] $properties) + hidden [void] Modify([System.Collections.Hashtable] $properties) { - # <# - # If the property 'EnablePollutionProtection' was present and not in desired state, - # then the property name must be change for the cmdlet Set-DnsServerCache. In the - # cmdlet Get-DnsServerCache the property name is 'EnablePollutionProtection', but - # in the cmdlet Set-DnsServerCache the parameter is 'PollutionProtection'. - # #> - # if ($properties.ContainsKey('EnablePollutionProtection')) - # { - # $properties['PollutionProtection'] = $properties.EnablePollutionProtection - # $properties.Remove('EnablePollutionProtection') - # } + $remove = $false + $create = $false + + if ($properties.ContainsKey('Ensure') -and $properties.Ensure -eq [Ensure]::Absent -and $this.Ensure -eq [Ensure]::Absent) + { + # Ensure was no in desired state so the resource should be removed + $remove = $true - # Set-DnsServerCache @properties + } + 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 + $create = $true + } + else + { + # Resource exists but one or more properties are not in the desired state + $remove = $true + $create = $true + } + + if ($remove) + { + Write-Verbose -Message ($this.localizedData.ListenerExistsRemoveMessage -f $properties.Transport, $properties.Port) + + Remove-WSManInstance -ResourceURI 'winrm/config/Listener' -SelectorSet @{ + Transport = $properties.Transport + Address = $properties.Address + } + } + + if ($create) + { + $selectorSet = @{ + Transport = $properties.Transport + Address = $properties.Address + } + $valueSet = @{ + Port = $properties.Port + } + + switch ($properties.Transport) + { + HTTPS + { + $findCertificateParams = Get-DscProperty -Attribute @('Optional') -ExcludeName @('Port', 'Address') + + $certificate = Find-Certificate @findCertificateParams + [System.String] $thumbprint = $certificate.thumbprint + + if ($thumbprint) + { + $valueSet.CertificateThumbprint = $thumbprint + + if ([System.String]::IsNullOrEmpty($properties.Hostname)) + { + $valueSet.HostName = [System.Net.Dns]::GetHostByName($env:COMPUTERNAME).Hostname + } + else + { + $valueSet.HostName = $properties.HostName + } + } + else + { + # TODO: Extract this to assert or into a parameter set + # A certificate could not be found to use for the HTTPS listener + New-InvalidArgumentException ` + -Message ($this.localizedData.ListenerCreateFailNoCertError -f $properties.Transport, $properties.Port) ` + -Argument 'Issuer' + } # if + } + Default + { + } + } + + Write-Verbose -Message ($this.localizedData.CreatingListenerMessage -f $properties.Transport, $properties.Port) + + New-WSManInstance -ResourceURI 'winrm/config/Listener' -SelectorSet @selectorSet -ValueSet @valueSet -ErrorAction Stop + + #End + + # if ($Transport -eq 'HTTPS') + # { + # # Find the certificate to use for the HTTPS Listener + # $null = $PSBoundParameters.Remove('Transport') + # $null = $PSBoundParameters.Remove('Ensure') + # $null = $PSBoundParameters.Remove('Port') + # $null = $PSBoundParameters.Remove('Address') + + # $certificate = Find-Certificate @PSBoundParameters + # [System.String] $thumbprint = $certificate.thumbprint + + # if ($thumbprint) + # { + # # A certificate was found, so use it to enable the HTTPS WinRM listener + # Write-Verbose -Message ( @( + # "$($MyInvocation.MyCommand): " + # $($script:localizedData.CreatingListenerMessage) ` + # -f $Transport, $Port + # ) -join '' ) + + # if ([System.String]::IsNullOrEmpty($Hostname)) + # { + # $Hostname = [System.Net.Dns]::GetHostByName($ENV:computerName).Hostname + # } + + # New-WSManInstance ` + # -ResourceURI 'winrm/config/Listener' ` + # -SelectorSet @{ + # Address = $Address + # Transport = $Transport + # } ` + # -ValueSet @{ + # Hostname = $Hostname + # CertificateThumbprint = $thumbprint + # Port = $Port + # } ` + # -ErrorAction Stop + # } + # else + # { + # # A certificate could not be found to use for the HTTPS listener + # New-InvalidArgumentException ` + # -Message ($script:localizedData.ListenerCreateFailNoCertError -f ` + # $Transport, $Port) ` + # -Argument 'Issuer' + # } # if + # } + # else + # { + # # Create a plain HTTP listener + # Write-Verbose -Message ( @( + # "$($MyInvocation.MyCommand): " + # $($script:localizedData.CreatingListenerMessage) ` + # -f $Transport, $Port + # ) -join '' ) + + # New-WSManInstance ` + # -ResourceURI 'winrm/config/Listener' ` + # -SelectorSet @{ + # Address = $Address + # Transport = $Transport + # } ` + # -ValueSet @{ + # Port = $Port + # } ` + # -ErrorAction Stop + # } + # } + } } [System.Boolean] Test() diff --git a/source/Enum/005.WSManSubjectFormat.ps1 b/source/Enum/005.WSManSubjectFormat.ps1 new file mode 100644 index 0000000..ecf7c15 --- /dev/null +++ b/source/Enum/005.WSManSubjectFormat.ps1 @@ -0,0 +1,11 @@ +<# + .SYNOPSIS + The possible states for the DSC resource parameter WSManSubjectFormat. +#> + +enum WSManSubjectFormat +{ + Both + FQDNOnly + NameOnly +} diff --git a/source/Enum/005.WSManTransport.ps1 b/source/Enum/005.WSManTransport.ps1 new file mode 100644 index 0000000..17a828f --- /dev/null +++ b/source/Enum/005.WSManTransport.ps1 @@ -0,0 +1,10 @@ +<# + .SYNOPSIS + The possible states for the DSC resource parameter WSManTransport. +#> + +enum WSManTransport +{ + HTTP + HTTPS +} diff --git a/source/en-US/WSManListener.strings.psd1 b/source/en-US/WSManListener.strings.psd1 index f187454..3120c46 100644 --- a/source/en-US/WSManListener.strings.psd1 +++ b/source/en-US/WSManListener.strings.psd1 @@ -11,4 +11,7 @@ ConvertFrom-StringData @' # None ## Strings directly used by the derived class WSManListener. + ListenerExistsRemoveMessage = Removing {0} Listener on port {1} (WSML0001). + ListenerCreateFailNoCertError = Failed to create {0} Listener on port {1} because an applicable certificate could not be found (WSM0002). + CreatingListenerMessage = Creating {0} Listener on port {1} (WSML0003). '@ diff --git a/tests/Unit/Classes/WSManListener.Tests.ps1 b/tests/Unit/Classes/WSManListener.Tests.ps1 index dab01ea..ec0c616 100644 --- a/tests/Unit/Classes/WSManListener.Tests.ps1 +++ b/tests/Unit/Classes/WSManListener.Tests.ps1 @@ -105,7 +105,7 @@ Describe 'WSManListener\Get()' -Tag 'Get' { $script:mockWSManListenerInstance | Add-Member -Force -MemberType 'ScriptMethod' -Name 'GetCurrentState' -Value { return [System.Collections.Hashtable] @{ - Transport = 'HTTP' + Transport = [WSManTransport]::HTTP Port = 5985 Address = '*' Enabled = 'true' @@ -134,6 +134,7 @@ Describe 'WSManListener\Get()' -Tag 'Get' { $currentState.Port | Should -Be 5985 $currentState.Port | Should -BeOfType System.UInt16 $currentState.Address | Should -Be '*' + $currentState.Enabled | Should -Be $true $currentState.URLPrefix | Should -Be 'wsman' @@ -170,7 +171,7 @@ Describe 'WSManListener\Get()' -Tag 'Get' { $script:mockWSManListenerInstance | Add-Member -Force -MemberType 'ScriptMethod' -Name 'GetCurrentState' -Value { return [System.Collections.Hashtable] @{ - Transport = 'HTTPS' + Transport = [WSManTransport]::HTTPS Port = 5986 Address = '*' Enabled = 'true' @@ -224,7 +225,7 @@ Describe 'WSManListener\Get()' -Tag 'Get' { $script:mockWSManListenerInstance = [WSManListener] @{ Transport = 'HTTPS' - Port = 5986 + Port = 5986 Ensure = 'Present' } @@ -238,7 +239,7 @@ Describe 'WSManListener\Get()' -Tag 'Get' { $script:mockWSManListenerInstance | Add-Member -Force -MemberType 'ScriptMethod' -Name 'GetCurrentState' -Value { return [System.Collections.Hashtable] @{ - Transport = 'HTTPS' + Transport = [WSManTransport]::HTTPS Port = 6000 Address = '*' Enabled = 'true' @@ -263,7 +264,7 @@ Describe 'WSManListener\Get()' -Tag 'Get' { $currentState = $script:mockWSManListenerInstance.Get() - $currentState.Transport | Should -Be 'HTTPS' + $currentState.Transport | Should -Be HTTPS $currentState.Port | Should -Be 6000 $currentState.Port | Should -BeOfType System.UInt16 From a61251e0f9b944df751f700a5f36575b34331645 Mon Sep 17 00:00:00 2001 From: Daniel Hughes Date: Thu, 19 Sep 2024 15:22:07 +0100 Subject: [PATCH 083/134] Cast enum correctly --- source/Classes/020.WSManListener.ps1 | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/source/Classes/020.WSManListener.ps1 b/source/Classes/020.WSManListener.ps1 index 38786a0..9ddf822 100644 --- a/source/Classes/020.WSManListener.ps1 +++ b/source/Classes/020.WSManListener.ps1 @@ -135,13 +135,13 @@ class WSManListener : ResourceBase [System.Collections.Hashtable] GetCurrentState([System.Collections.Hashtable] $properties) { $getParameters = @{ - Transport = $properties.Transport + Transport = [WSManTransport].GetEnumName($properties.Transport) } # Get the port if it's not provided if (-not $properties.Port) { - $this.Port = Get-DefaultPort -Transport $properties.Transport + $this.Port = Get-DefaultPort @getParameters } $getCurrentStateResult = Get-Listener @getParameters @@ -154,7 +154,7 @@ class WSManListener : ResourceBase } $state = @{ - Transport = $getCurrentStateResult.Transport + Transport = [WSManTransport] $getCurrentStateResult.Transport Port = [System.UInt16] $getCurrentStateResult.Port Address = $getCurrentStateResult.Address @@ -185,6 +185,11 @@ class WSManListener : ResourceBase $remove = $false $create = $false + $selectorSet = @{ + Transport = [WSManTransport].GetEnumName($properties.Transport) + Address = $properties.Address + } + if ($properties.ContainsKey('Ensure') -and $properties.Ensure -eq [Ensure]::Absent -and $this.Ensure -eq [Ensure]::Absent) { # Ensure was no in desired state so the resource should be removed @@ -207,18 +212,11 @@ class WSManListener : ResourceBase { Write-Verbose -Message ($this.localizedData.ListenerExistsRemoveMessage -f $properties.Transport, $properties.Port) - Remove-WSManInstance -ResourceURI 'winrm/config/Listener' -SelectorSet @{ - Transport = $properties.Transport - Address = $properties.Address - } + Remove-WSManInstance -ResourceURI 'winrm/config/Listener' -SelectorSet @selectorSet } if ($create) { - $selectorSet = @{ - Transport = $properties.Transport - Address = $properties.Address - } $valueSet = @{ Port = $properties.Port } From 4a8db6e278288fa1fc5740167ad8f23f125c1499 Mon Sep 17 00:00:00 2001 From: Daniel Hughes Date: Fri, 20 Sep 2024 17:11:29 +0100 Subject: [PATCH 084/134] Move back to enum and return empty state as required --- source/Classes/020.WSManListener.ps1 | 179 ++++++----------- tests/Unit/Classes/WSManListener.Tests.ps1 | 216 ++++++++++++++++++--- 2 files changed, 247 insertions(+), 148 deletions(-) diff --git a/source/Classes/020.WSManListener.ps1 b/source/Classes/020.WSManListener.ps1 index 9ddf822..e4e39bf 100644 --- a/source/Classes/020.WSManListener.ps1 +++ b/source/Classes/020.WSManListener.ps1 @@ -51,8 +51,6 @@ class WSManListener : ResourceBase { [DscProperty(Key)] - #[ValidateSet('HTTP', 'HTTPS')] - #[System.String] [WSManTransport] $Transport @@ -67,17 +65,15 @@ class WSManListener : ResourceBase [DscProperty()] [System.String] - $Address = '*' + $Address [DscProperty()] [System.String] $Issuer [DscProperty()] - #[ValidateSet('Both', 'FQDNOnly', 'NameOnly')] - #[System.String] [WSManSubjectFormat] - $SubjectFormat = [WSManSubjectFormat]::Both + $SubjectFormat [DscProperty()] [Nullable[System.Boolean]] @@ -135,37 +131,49 @@ class WSManListener : ResourceBase [System.Collections.Hashtable] GetCurrentState([System.Collections.Hashtable] $properties) { $getParameters = @{ - Transport = [WSManTransport].GetEnumName($properties.Transport) + Transport = $properties.Transport } - # Get the port if it's not provided - if (-not $properties.Port) + # Get the port if it's not provided and resource should exist + if (-not $this.Port -and $this.Ensure -eq [Ensure]::Present) { $this.Port = Get-DefaultPort @getParameters } - $getCurrentStateResult = Get-Listener @getParameters + # Get the Address if it's not provided and resource should exist + if (-not $this.Address -and $this.Ensure -eq [Ensure]::Present) + { + $this.Address = '*' + } - $currentCertificate = '' - if ($getCurrentStateResult.CertificateThumbprint) + $getCurrentStateResult = Get-Listener @getParameters + + if ($getCurrentStateResult) { - $currentCertificate = Find-Certificate -CertificateThumbprint $getCurrentStateResult.CertificateThumbprint - } + $state = @{ + Transport = [WSManTransport] $getCurrentStateResult.Transport + Port = [System.UInt16] $getCurrentStateResult.Port + Address = $getCurrentStateResult.Address - $state = @{ - Transport = [WSManTransport] $getCurrentStateResult.Transport - Port = [System.UInt16] $getCurrentStateResult.Port - Address = $getCurrentStateResult.Address + CertificateThumbprint = $getCurrentStateResult.CertificateThumbprint + Hostname = $getCurrentStateResult.Hostname - Issuer = $currentCertificate.Issuer - CertificateThumbprint = $getCurrentStateResult.CertificateThumbprint - Hostname = $getCurrentStateResult.HostName + Enabled = $getCurrentStateResult.Enabled + URLPrefix = $getCurrentStateResult.URLPrefix + } - Enabled = $getCurrentStateResult.Enabled - URLPrefix = $getCurrentStateResult.URLPrefix + if ($getCurrentStateResult.CertificateThumbprint) + { + $state.Issuer = (Find-Certificate -CertificateThumbprint $getCurrentStateResult.CertificateThumbprint).Issuer + } + } + else + { + $state = @{} } + return $state } @@ -186,13 +194,17 @@ class WSManListener : ResourceBase $create = $false $selectorSet = @{ - Transport = [WSManTransport].GetEnumName($properties.Transport) + Transport = $properties.Transport Address = $properties.Address } + Write-Verbose ('$properties.ContainsKey(''Ensure'') = {0}' -f $properties.ContainsKey('Ensure')) + Write-Verbose ('$properties.Ensure = {0}' -f $properties.Ensure) + Write-Verbose ('$this.Ensure = {0}' -f $this.Ensure) + if ($properties.ContainsKey('Ensure') -and $properties.Ensure -eq [Ensure]::Absent -and $this.Ensure -eq [Ensure]::Absent) { - # Ensure was no in desired state so the resource should be removed + # Ensure was not in desired state so the resource should be removed $remove = $true } @@ -221,116 +233,41 @@ class WSManListener : ResourceBase Port = $properties.Port } - switch ($properties.Transport) + + if ($properties.Transport -eq [WSManTransport]::HTTPS) { - HTTPS - { - $findCertificateParams = Get-DscProperty -Attribute @('Optional') -ExcludeName @('Port', 'Address') + $findCertificateParams = Get-DscProperty -Attribute @('Optional') -ExcludeName @('Port', 'Address') - $certificate = Find-Certificate @findCertificateParams - [System.String] $thumbprint = $certificate.thumbprint + $certificate = Find-Certificate @findCertificateParams + [System.String] $thumbprint = $certificate.thumbprint - if ($thumbprint) + if ($thumbprint) + { + $valueSet.CertificateThumbprint = $thumbprint + + if ([System.String]::IsNullOrEmpty($properties.Hostname)) { - $valueSet.CertificateThumbprint = $thumbprint - - if ([System.String]::IsNullOrEmpty($properties.Hostname)) - { - $valueSet.HostName = [System.Net.Dns]::GetHostByName($env:COMPUTERNAME).Hostname - } - else - { - $valueSet.HostName = $properties.HostName - } + $valueSet.HostName = [System.Net.Dns]::GetHostByName($env:COMPUTERNAME).Hostname } else { - # TODO: Extract this to assert or into a parameter set - # A certificate could not be found to use for the HTTPS listener - New-InvalidArgumentException ` - -Message ($this.localizedData.ListenerCreateFailNoCertError -f $properties.Transport, $properties.Port) ` - -Argument 'Issuer' - } # if + $valueSet.HostName = $properties.HostName + } } - Default + else { - } + # TODO: Extract this to assert or into a parameter set + # A certificate could not be found to use for the HTTPS listener + New-InvalidArgumentException ` + -Message ($this.localizedData.ListenerCreateFailNoCertError -f $properties.Transport, $properties.Port) ` + -Argument 'Issuer' + } # if } + Write-Verbose -Message ($this.localizedData.CreatingListenerMessage -f $properties.Transport, $properties.Port) New-WSManInstance -ResourceURI 'winrm/config/Listener' -SelectorSet @selectorSet -ValueSet @valueSet -ErrorAction Stop - - #End - - # if ($Transport -eq 'HTTPS') - # { - # # Find the certificate to use for the HTTPS Listener - # $null = $PSBoundParameters.Remove('Transport') - # $null = $PSBoundParameters.Remove('Ensure') - # $null = $PSBoundParameters.Remove('Port') - # $null = $PSBoundParameters.Remove('Address') - - # $certificate = Find-Certificate @PSBoundParameters - # [System.String] $thumbprint = $certificate.thumbprint - - # if ($thumbprint) - # { - # # A certificate was found, so use it to enable the HTTPS WinRM listener - # Write-Verbose -Message ( @( - # "$($MyInvocation.MyCommand): " - # $($script:localizedData.CreatingListenerMessage) ` - # -f $Transport, $Port - # ) -join '' ) - - # if ([System.String]::IsNullOrEmpty($Hostname)) - # { - # $Hostname = [System.Net.Dns]::GetHostByName($ENV:computerName).Hostname - # } - - # New-WSManInstance ` - # -ResourceURI 'winrm/config/Listener' ` - # -SelectorSet @{ - # Address = $Address - # Transport = $Transport - # } ` - # -ValueSet @{ - # Hostname = $Hostname - # CertificateThumbprint = $thumbprint - # Port = $Port - # } ` - # -ErrorAction Stop - # } - # else - # { - # # A certificate could not be found to use for the HTTPS listener - # New-InvalidArgumentException ` - # -Message ($script:localizedData.ListenerCreateFailNoCertError -f ` - # $Transport, $Port) ` - # -Argument 'Issuer' - # } # if - # } - # else - # { - # # Create a plain HTTP listener - # Write-Verbose -Message ( @( - # "$($MyInvocation.MyCommand): " - # $($script:localizedData.CreatingListenerMessage) ` - # -f $Transport, $Port - # ) -join '' ) - - # New-WSManInstance ` - # -ResourceURI 'winrm/config/Listener' ` - # -SelectorSet @{ - # Address = $Address - # Transport = $Transport - # } ` - # -ValueSet @{ - # Port = $Port - # } ` - # -ErrorAction Stop - # } - # } } } diff --git a/tests/Unit/Classes/WSManListener.Tests.ps1 b/tests/Unit/Classes/WSManListener.Tests.ps1 index ec0c616..8621af6 100644 --- a/tests/Unit/Classes/WSManListener.Tests.ps1 +++ b/tests/Unit/Classes/WSManListener.Tests.ps1 @@ -105,17 +105,11 @@ Describe 'WSManListener\Get()' -Tag 'Get' { $script:mockWSManListenerInstance | Add-Member -Force -MemberType 'ScriptMethod' -Name 'GetCurrentState' -Value { return [System.Collections.Hashtable] @{ - Transport = [WSManTransport]::HTTP - Port = 5985 + Transport = [WSManTransport] 'HTTP' + Port = [System.UInt16] 5985 Address = '*' Enabled = 'true' URLPrefix = 'wsman' - Issuer = $null - SubjectFormat = 'Both' - MatchAlternate = $null - BaseDN = $null - CertificateThumbprint = $null - Hostname = $null } } -PassThru | Add-Member -Force -MemberType 'ScriptMethod' -Name 'AssertProperties' -Value { @@ -135,7 +129,7 @@ Describe 'WSManListener\Get()' -Tag 'Get' { $currentState.Port | Should -BeOfType System.UInt16 $currentState.Address | Should -Be '*' - $currentState.Enabled | Should -Be $true + $currentState.Enabled | Should -BeTrue $currentState.URLPrefix | Should -Be 'wsman' $currentState.Issuer | Should -BeNullOrEmpty @@ -171,17 +165,11 @@ Describe 'WSManListener\Get()' -Tag 'Get' { $script:mockWSManListenerInstance | Add-Member -Force -MemberType 'ScriptMethod' -Name 'GetCurrentState' -Value { return [System.Collections.Hashtable] @{ - Transport = [WSManTransport]::HTTPS - Port = 5986 + Transport = [WSManTransport] 'HTTPS' + Port = [System.UInt16] 5986 Address = '*' Enabled = 'true' URLPrefix = 'wsman' - Issuer = $null - SubjectFormat = 'Both' - MatchAlternate = $null - BaseDN = $null - CertificateThumbprint = $null - Hostname = $null } } -PassThru | Add-Member -Force -MemberType 'ScriptMethod' -Name 'AssertProperties' -Value { @@ -200,7 +188,7 @@ Describe 'WSManListener\Get()' -Tag 'Get' { $currentState.Port | Should -Be 5986 $currentState.Port | Should -BeOfType System.UInt16 $currentState.Address | Should -Be '*' - $currentState.Enabled | Should -Be $true + $currentState.Enabled | Should -BeTrue $currentState.URLPrefix | Should -Be 'wsman' $currentState.Issuer | Should -BeNullOrEmpty @@ -215,6 +203,61 @@ Describe 'WSManListener\Get()' -Tag 'Get' { } } } + + Context 'When no listener should exist' { + BeforeAll { + InModuleScope -ScriptBlock { + Set-StrictMode -Version 1.0 + + $script:mockWSManListenerInstance = [WSManListener] @{ + Transport = 'HTTP' + Ensure = 'Absent' + } + + <# + This mocks the method GetCurrentState(). + + Method Get() will call the base method Get() which will + call back to the derived class method GetCurrentState() + to get the result to return from the derived method Get(). + #> + $script:mockWSManListenerInstance | + Add-Member -Force -MemberType 'ScriptMethod' -Name 'GetCurrentState' -Value { + return [System.Collections.Hashtable] @{} + } -PassThru | + Add-Member -Force -MemberType 'ScriptMethod' -Name 'AssertProperties' -Value { + return + } + } + } + + It 'Should return the correct values' { + InModuleScope -ScriptBlock { + Set-StrictMode -Version 1.0 + + $currentState = $script:mockWSManListenerInstance.Get() + + $currentState.Transport | Should -Be 'HTTP' + $currentState.Port | Should -BeNullOrEmpty + $currentState.Address | Should -BeNullOrEmpty + + $currentState.Enabled | Should -BeFalse + $currentState.URLPrefix | Should -BeNullOrEmpty + + $currentState.Issuer | Should -BeNullOrEmpty + $currentState.SubjectFormat | Should -Be 'Both' + $currentState.MatchAlternate | Should -BeNullOrEmpty + $currentState.BaseDN | Should -BeNullOrEmpty + $currentState.CertificateThumbprint | Should -BeNullOrEmpty + $currentState.Hostname | Should -BeNullOrEmpty + + $currentState.Ensure | Should -Be 'Absent' + $currentState.Reasons | Should -HaveCount 1 + $currentState.Reasons[0].Code | Should -Be 'WSManListener:WSManListener:Transport' + $currentState.Reasons[0].Phrase | Should -Be 'The property Transport should be "HTTP", but was null' + } + } + } } Context 'When the system is not in the desired state' { @@ -239,17 +282,11 @@ Describe 'WSManListener\Get()' -Tag 'Get' { $script:mockWSManListenerInstance | Add-Member -Force -MemberType 'ScriptMethod' -Name 'GetCurrentState' -Value { return [System.Collections.Hashtable] @{ - Transport = [WSManTransport]::HTTPS - Port = 6000 + Transport = [WSManTransport] 'HTTPS' + Port = [System.UInt16] 6000 Address = '*' Enabled = 'true' URLPrefix = 'wsman' - Issuer = $null - SubjectFormat = 'Both' - MatchAlternate = $null - BaseDN = $null - CertificateThumbprint = $null - Hostname = $null } } -PassThru | Add-Member -Force -MemberType 'ScriptMethod' -Name 'AssertProperties' -Value { @@ -264,7 +301,7 @@ Describe 'WSManListener\Get()' -Tag 'Get' { $currentState = $script:mockWSManListenerInstance.Get() - $currentState.Transport | Should -Be HTTPS + $currentState.Transport | Should -Be 'HTTPS' $currentState.Port | Should -Be 6000 $currentState.Port | Should -BeOfType System.UInt16 @@ -288,5 +325,130 @@ Describe 'WSManListener\Get()' -Tag 'Get' { } } } + + Context 'When the listener exists' { + BeforeAll { + InModuleScope -ScriptBlock { + Set-StrictMode -Version 1.0 + + $script:mockWSManListenerInstance = [WSManListener] @{ + Transport = 'HTTPS' + Ensure = 'Present' + } + + <# + This mocks the method GetCurrentState(). + + Method Get() will call the base method Get() which will + call back to the derived class method GetCurrentState() + to get the result to return from the derived method Get(). + #> + $script:mockWSManListenerInstance | + Add-Member -Force -MemberType 'ScriptMethod' -Name 'GetCurrentState' -Value { + return [System.Collections.Hashtable] @{} + } -PassThru | + Add-Member -Force -MemberType 'ScriptMethod' -Name 'AssertProperties' -Value { + return + } + } + } + + It 'Should return the correct values' { + InModuleScope -ScriptBlock { + Set-StrictMode -Version 1.0 + + $currentState = $script:mockWSManListenerInstance.Get() + + $currentState.Transport | Should -Be 'HTTPS' + $currentState.Port | Should -BeNullOrEmpty + + $currentState.Address | Should -BeNullOrEmpty + $currentState.Enabled | Should -BeFalse + $currentState.URLPrefix | Should -BeNullOrEmpty + + $currentState.Issuer | Should -BeNullOrEmpty + $currentState.SubjectFormat | Should -Be 'Both' + $currentState.MatchAlternate | Should -BeNullOrEmpty + $currentState.BaseDN | Should -BeNullOrEmpty + $currentState.CertificateThumbprint | Should -BeNullOrEmpty + $currentState.Hostname | Should -BeNullOrEmpty + + $currentState.Ensure | Should -Be 'Absent' + + $currentState.Reasons | Should -HaveCount 2 + $currentState.Reasons[0].Code | Should -Be 'WSManListener:WSManListener:Ensure' + $currentState.Reasons[0].Phrase | Should -Be 'The property Ensure should be "Present", but was "Absent"' + $currentState.Reasons[1].Code | Should -Be 'WSManListener:WSManListener:Transport' + $currentState.Reasons[1].Phrase | Should -Be 'The property Transport should be "HTTPS", but was null' + } + } + } + + Context 'When the listener exists but should not' { + BeforeAll { + InModuleScope -ScriptBlock { + Set-StrictMode -Version 1.0 + + $script:mockWSManListenerInstance = [WSManListener] @{ + Transport = 'HTTP' + Ensure = 'Absent' + } + + <# + This mocks the method GetCurrentState(). + + Method Get() will call the base method Get() which will + call back to the derived class method GetCurrentState() + to get the result to return from the derived method Get(). + #> + $script:mockWSManListenerInstance | + Add-Member -Force -MemberType 'ScriptMethod' -Name 'GetCurrentState' -Value { + return [System.Collections.Hashtable] @{ + Transport = [WSManTransport] 'HTTP' + Port = [System.UInt16] 5985 + Address = '*' + Enabled = 'true' + URLPrefix = 'wsman' + Issuer = $null + MatchAlternate = $null + BaseDN = $null + CertificateThumbprint = $null + Hostname = $null + } + } -PassThru | + Add-Member -Force -MemberType 'ScriptMethod' -Name 'AssertProperties' -Value { + return + } + } + } + + It 'Should return the correct values' { + InModuleScope -ScriptBlock { + Set-StrictMode -Version 1.0 + + $currentState = $script:mockWSManListenerInstance.Get() + + $currentState.Transport | Should -Be 'HTTP' + $currentState.Port | Should -Be 5985 + + $currentState.Address | Should -Be '*' + $currentState.Enabled | Should -BeTrue + $currentState.URLPrefix | Should -Be 'wsman' + + $currentState.Issuer | Should -BeNullOrEmpty + $currentState.SubjectFormat | Should -Be 'Both' + $currentState.MatchAlternate | Should -BeNullOrEmpty + $currentState.BaseDN | Should -BeNullOrEmpty + $currentState.CertificateThumbprint | Should -BeNullOrEmpty + $currentState.Hostname | Should -BeNullOrEmpty + + $currentState.Ensure | Should -Be 'Present' + + $currentState.Reasons | Should -HaveCount 1 + $currentState.Reasons[0].Code | Should -Be 'WSManListener:WSManListener:Ensure' + $currentState.Reasons[0].Phrase | Should -Be 'The property Ensure should be "Absent", but was "Present"' + } + } + } } } From f316f45aee26880ab7b9c6df8aa3ffabaf737018 Mon Sep 17 00:00:00 2001 From: Daniel Hughes Date: Fri, 20 Sep 2024 17:44:54 +0100 Subject: [PATCH 085/134] Fix tests --- source/Classes/020.WSManListener.ps1 | 13 ++++++------- tests/Unit/Classes/WSManListener.Tests.ps1 | 10 +++++----- 2 files changed, 11 insertions(+), 12 deletions(-) diff --git a/source/Classes/020.WSManListener.ps1 b/source/Classes/020.WSManListener.ps1 index e4e39bf..304091a 100644 --- a/source/Classes/020.WSManListener.ps1 +++ b/source/Classes/020.WSManListener.ps1 @@ -194,8 +194,8 @@ class WSManListener : ResourceBase $create = $false $selectorSet = @{ - Transport = $properties.Transport - Address = $properties.Address + Transport = $this.Transport + Address = $this.Address } Write-Verbose ('$properties.ContainsKey(''Ensure'') = {0}' -f $properties.ContainsKey('Ensure')) @@ -206,7 +206,6 @@ class WSManListener : ResourceBase { # Ensure was not in desired state so the resource should be removed $remove = $true - } elseif ($properties.ContainsKey('Ensure') -and $properties.Ensure -eq [Ensure]::Present -and $this.Ensure -eq [Ensure]::Present) { @@ -222,7 +221,7 @@ class WSManListener : ResourceBase if ($remove) { - Write-Verbose -Message ($this.localizedData.ListenerExistsRemoveMessage -f $properties.Transport, $properties.Port) + Write-Verbose -Message ($this.localizedData.ListenerExistsRemoveMessage -f $this.Transport, $this.Port) Remove-WSManInstance -ResourceURI 'winrm/config/Listener' -SelectorSet @selectorSet } @@ -234,7 +233,7 @@ class WSManListener : ResourceBase } - if ($properties.Transport -eq [WSManTransport]::HTTPS) + if ($this.Transport -eq [WSManTransport]::HTTPS) { $findCertificateParams = Get-DscProperty -Attribute @('Optional') -ExcludeName @('Port', 'Address') @@ -259,13 +258,13 @@ class WSManListener : ResourceBase # TODO: Extract this to assert or into a parameter set # A certificate could not be found to use for the HTTPS listener New-InvalidArgumentException ` - -Message ($this.localizedData.ListenerCreateFailNoCertError -f $properties.Transport, $properties.Port) ` + -Message ($this.localizedData.ListenerCreateFailNoCertError -f $this.Transport, $this.Port) ` -Argument 'Issuer' } # if } - Write-Verbose -Message ($this.localizedData.CreatingListenerMessage -f $properties.Transport, $properties.Port) + Write-Verbose -Message ($this.localizedData.CreatingListenerMessage -f $this.Transport, $this.Port) New-WSManInstance -ResourceURI 'winrm/config/Listener' -SelectorSet @selectorSet -ValueSet @valueSet -ErrorAction Stop } diff --git a/tests/Unit/Classes/WSManListener.Tests.ps1 b/tests/Unit/Classes/WSManListener.Tests.ps1 index 8621af6..4aed740 100644 --- a/tests/Unit/Classes/WSManListener.Tests.ps1 +++ b/tests/Unit/Classes/WSManListener.Tests.ps1 @@ -254,7 +254,7 @@ Describe 'WSManListener\Get()' -Tag 'Get' { $currentState.Ensure | Should -Be 'Absent' $currentState.Reasons | Should -HaveCount 1 $currentState.Reasons[0].Code | Should -Be 'WSManListener:WSManListener:Transport' - $currentState.Reasons[0].Phrase | Should -Be 'The property Transport should be "HTTP", but was null' + $currentState.Reasons[0].Phrase | Should -Be 'The property Transport should be "HTTP", but was ""' } } } @@ -376,10 +376,10 @@ Describe 'WSManListener\Get()' -Tag 'Get' { $currentState.Ensure | Should -Be 'Absent' $currentState.Reasons | Should -HaveCount 2 - $currentState.Reasons[0].Code | Should -Be 'WSManListener:WSManListener:Ensure' - $currentState.Reasons[0].Phrase | Should -Be 'The property Ensure should be "Present", but was "Absent"' - $currentState.Reasons[1].Code | Should -Be 'WSManListener:WSManListener:Transport' - $currentState.Reasons[1].Phrase | Should -Be 'The property Transport should be "HTTPS", but was null' + $currentState.Reasons[1].Code | Should -Be 'WSManListener:WSManListener:Ensure' + $currentState.Reasons[1].Phrase | Should -Be 'The property Ensure should be "Present", but was "Absent"' + $currentState.Reasons[0].Code | Should -Be 'WSManListener:WSManListener:Transport' + $currentState.Reasons[0].Phrase | Should -Be 'The property Transport should be "HTTPS", but was ""' } } } From 3cd8a723bb38785f5c6fd90081c69ffb217c43e7 Mon Sep 17 00:00:00 2001 From: Daniel Hughes Date: Thu, 26 Sep 2024 17:11:46 +0100 Subject: [PATCH 086/134] Refactor tests --- tests/Unit/Private/Find-Certificate.Tests.ps1 | 705 ++++++++++-------- 1 file changed, 385 insertions(+), 320 deletions(-) diff --git a/tests/Unit/Private/Find-Certificate.Tests.ps1 b/tests/Unit/Private/Find-Certificate.Tests.ps1 index 70518b7..25aaa03 100644 --- a/tests/Unit/Private/Find-Certificate.Tests.ps1 +++ b/tests/Unit/Private/Find-Certificate.Tests.ps1 @@ -13,7 +13,7 @@ BeforeDiscovery { & "$PSScriptRoot/../../../build.ps1" -Tasks 'noop' 2>&1 4>&1 5>&1 6>&1 > $null } - # If the dependencies has not been resolved, this will throw an error. + # This will throw an error if the dependencies have not been resolved. Import-Module -Name 'DscResource.Test' -Force -ErrorAction 'Stop' } } @@ -31,6 +31,54 @@ BeforeAll { $PSDefaultParameterValues['InModuleScope:ModuleName'] = $script:dscModuleName $PSDefaultParameterValues['Mock:ModuleName'] = $script:dscModuleName $PSDefaultParameterValues['Should:ModuleName'] = $script:dscModuleName + + # Create the Mock Objects that will be used for running tests + $script:mockCertificateThumbprint = '74FA31ADEA7FDD5333CED10910BFA6F665A1F2FC' + $mockFQDN = 'SERVER1.CONTOSO.COM' + $mockHostName = $([System.Net.Dns]::GetHostByName($ENV:computerName).Hostname) + $script:mockIssuer = 'CN=CONTOSO.COM Issuing CA, DC=CONTOSO, DC=COM' + $script:mockDN = 'O=Contoso Inc, S=Pennsylvania, C=US' + $mockCertificate = @{ + Thumbprint = $mockCertificateThumbprint + Subject = "CN=$mockHostName" + Issuer = $mockIssuer + Extensions = @{ EnhancedKeyUsages = @{ FriendlyName = 'Server Authentication' } } + DNSNameList = @{ Unicode = $mockHostName } + } + + $script:mockCertificateDN = @{ + Thumbprint = $mockCertificateThumbprint + Subject = "CN=$mockHostName, $mockDN" + Issuer = $mockIssuer + Extensions = @{ EnhancedKeyUsages = @{ FriendlyName = 'Server Authentication' } } + DNSNameList = @{ Unicode = $mockHostName } + } + + $script:mockListenerHTTP = @{ + cfg = 'http://schemas.microsoft.com/wbem/wsman/1/config/listener' + xsi = 'http://www.w3.org/2001/XMLSchema-instance' + lang = 'en-US' + Address = '*' + Transport = 'HTTP' + Port = 5985 + Hostname = '' + Enabled = 'true' + URLPrefix = 'wsman' + CertificateThumbprint = '' + } + + $script:mockListenerHTTPS = @{ + cfg = 'http://schemas.microsoft.com/wbem/wsman/1/config/listener' + xsi = 'http://www.w3.org/2001/XMLSchema-instance' + lang = 'en-US' + Address = '*' + Transport = 'HTTPS' + Port = 5986 + Hostname = $mockFQDN + Enabled = 'true' + URLPrefix = 'wsman' + CertificateThumbprint = $mockCertificateThumbprint + } } AfterAll { @@ -43,26 +91,23 @@ AfterAll { } Describe 'Find-Certificate' -Tag 'Private' { - BeforeAll { - InModuleScope -ScriptBlock { - $script:mockCertificateThumbprint = '74FA31ADEA7FDD5333CED10910BFA6F665A1F2FC' - } - } Context 'CertificateThumbprint is passed but does not exist' { BeforeAll { Mock -CommandName Get-ChildItem } It 'Should not throw error' { - InModuleScope -ScriptBlock { + InModuleScope -Parameters @{ + mockCertificateThumbprint = $script:mockCertificateThumbprint + } -ScriptBlock { Set-StrictMode -Version 1.0 - $findParameters = @{ - CertificateThumbprint = $script:mockCertificateThumbprint + $findCertificateParams = @{ + CertificateThumbprint = $mockCertificateThumbprint Verbose = $VerbosePreference } - $script:returnedCertificate = Find-Certificate @findParameters + $script:returnedCertificate = Find-Certificate @findCertificateParams { $script:returnedCertificate } | Should -Not -Throw } @@ -74,7 +119,9 @@ Describe 'Find-Certificate' -Tag 'Private' { $script:returnedCertificate | Should -BeNullOrEmpty } + } + It 'Should call expected Mocks' { Should -Invoke -CommandName Get-ChildItem -Exactly -Times 1 -Scope Context } } @@ -82,339 +129,357 @@ Describe 'Find-Certificate' -Tag 'Private' { Context 'CertificateThumbprint is passed and does exist' { BeforeAll { Mock -CommandName Get-ChildItem -MockWith { - @{ - Thumbprint = '74FA31ADEA7FDD5333CED10910BFA6F665A1F2FC' - Subject = "CN=$([System.Net.Dns]::GetHostByName($ENV:computerName).Hostname), O=Contoso Inc, S=Pennsylvania, C=US" - Issuer = 'CN=CONTOSO.COM Issuing CA, DC=CONTOSO, DC=COM' - Extensions = @{ EnhancedKeyUsages = @{ FriendlyName = 'Server Authentication' } } - DNSNameList = @{ Unicode = $([System.Net.Dns]::GetHostByName($ENV:computerName).Hostname) } + $mockCertificateDN + } + } + + It 'Should not throw error' { + InModuleScope -Parameters @{ + mockCertificateThumbprint = $script:mockCertificateThumbprint + } -ScriptBlock { + Set-StrictMode -Version 1.0 + + $findCertificateParams = @{ + CertificateThumbprint = $mockCertificateThumbprint + Verbose = $VerbosePreference } + + $script:returnedCertificate = Find-Certificate @findCertificateParams + + { $script:returnedCertificate } | Should -Not -Throw + } + } + + It 'Should return expected certificate' { + InModuleScope -Parameters @{ + mockCertificateThumbprint = $script:mockCertificateThumbprint + } -ScriptBlock { + Set-StrictMode -Version 1.0 + + $script:returnedCertificate.Thumbprint | Should -Be $mockCertificateThumbprint } } + It 'Should call expected Mocks' { + Should -Invoke -CommandName Get-ChildItem -Exactly -Times 1 -Scope Context + } + } + + Context 'SubjectFormat is Both, Certificate does not exist, DN passed' { + BeforeAll { + Mock -CommandName Get-ChildItem + } + It 'Should not throw error' { + InModuleScope -Parameters @{ + mockIssuer = $script:mockIssuer + mockDN = $script:mockDN + } -ScriptBlock { + Set-StrictMode -Version 1.0 + + $findCertificateParams = @{ + Issuer = $mockIssuer + SubjectFormat = 'Both' + MatchAlternate = $true + BaseDN = $mockDN + Verbose = $VerbosePreference + } + + $script:returnedCertificate = Find-Certificate @findCertificateParams + + { $script:returnedCertificate } | Should -Not -Throw + } + } + + It 'Should return null' { InModuleScope -ScriptBlock { Set-StrictMode -Version 1.0 - $findParameters = @{ - CertificateThumbprint = $script:mockCertificateThumbprint - Verbose = $VerbosePreference + $script:returnedCertificate | Should -BeNullOrEmpty + } + } + + It 'Should call expected Mocks' { + Should -Invoke -CommandName Get-ChildItem -Exactly -Times 2 -Scope Context + } + } + + Context 'SubjectFormat is Both, Certificate with DN Exists, DN passed' { + BeforeAll { + Mock -CommandName Get-ChildItem -MockWith { + $mockCertificateDN + } + } + + It 'Should not throw error' { + InModuleScope -Parameters @{ + mockIssuer = $script:mockIssuer + mockDN = $script:mockDN + } -ScriptBlock { + Set-StrictMode -Version 1.0 + + $findCertificateParams = @{ + Issuer = $mockIssuer + SubjectFormat = 'Both' + MatchAlternate = $true + BaseDN = $mockDN + Verbose = $VerbosePreference } - $script:returnedCertificate = Find-Certificate @findParameters + $script:returnedCertificate = Find-Certificate @findCertificateParams { $script:returnedCertificate } | Should -Not -Throw } } It 'Should return expected certificate' { + InModuleScope -Parameters @{ + mockCertificateThumbprint = $script:mockCertificateThumbprint + } -ScriptBlock { + Set-StrictMode -Version 1.0 + + $script:returnedCertificate.Thumbprint | Should -Be $mockCertificateThumbprint + } + } + + It 'Should call expected Mocks' { + Should -Invoke -CommandName Get-ChildItem -Exactly -Times 1 -Scope Context + } + } + + Context 'SubjectFormat is Both, Certificate without DN Exists, DN passed' { + BeforeAll { + Mock -CommandName Get-ChildItem -MockWith { + $mockCertificate + } + } + + It 'Should not throw error' { + InModuleScope -Parameters @{ + mockIssuer = $script:mockIssuer + mockDN = $script:mockDN + } -ScriptBlock { + Set-StrictMode -Version 1.0 + + $findCertificateParams = @{ + Issuer = $mockIssuer + SubjectFormat = 'Both' + MatchAlternate = $true + BaseDN = $mockDN + Verbose = $VerbosePreference + } + + $script:returnedCertificate = Find-Certificate @findCertificateParams + + { $script:returnedCertificate } | Should -Not -Throw + } + } + + It 'Should return null' { InModuleScope -ScriptBlock { Set-StrictMode -Version 1.0 - $script:returnedCertificate.Thumbprint | Should -Be $script:mockCertificateThumbprint + $script:returnedCertificate | Should -BeNullOrEmpty + } + } + + It 'Should call expected Mocks' { + Should -Invoke -CommandName Get-ChildItem -Exactly -Times 2 -Scope Context + } + } + + Context 'SubjectFormat is Both, Certificate does not exist, DN not passed' { + BeforeAll { + Mock -CommandName Get-ChildItem + } + + It 'Should not throw error' { + InModuleScope -Parameters @{ + mockIssuer = $script:mockIssuer + } -ScriptBlock { + Set-StrictMode -Version 1.0 + + $findCertificateParams = @{ + Issuer = $mockIssuer + SubjectFormat = 'Both' + MatchAlternate = $true + Verbose = $VerbosePreference + } + + $script:returnedCertificate = Find-Certificate @findCertificateParams + + { $script:returnedCertificate } | Should -Not -Throw } + } + It 'Should return null' { + InModuleScope -ScriptBlock { + Set-StrictMode -Version 1.0 + + $script:returnedCertificate | Should -BeNullOrEmpty + } + } + + It 'Should call expected Mocks' { + Should -Invoke -CommandName Get-ChildItem -Exactly -Times 2 -Scope Context + } + } + + Context 'SubjectFormat is Both, Certificate with DN Exists, DN not passed' { + BeforeAll { + Mock -CommandName Get-ChildItem -MockWith { + $mockCertificateDN + } + } + + It 'Should not throw error' { + InModuleScope -Parameters @{ + mockIssuer = $script:mockIssuer + } -ScriptBlock { + Set-StrictMode -Version 1.0 + + $findCertificateParams = @{ + Issuer = $mockIssuer + SubjectFormat = 'Both' + MatchAlternate = $true + Verbose = $VerbosePreference + } + + $script:returnedCertificate = Find-Certificate @findCertificateParams + + { $script:returnedCertificate } | Should -Not -Throw + } + } + + It 'Should return null' { + InModuleScope -ScriptBlock { + Set-StrictMode -Version 1.0 + + $script:returnedCertificate | Should -BeNullOrEmpty + } + } + + It 'Should call expected Mocks' { + Should -Invoke -CommandName Get-ChildItem -Exactly -Times 2 -Scope Context + } + } + + Context 'SubjectFormat is Both, Certificate without DN Exists, DN not passed' { + BeforeAll { + Mock -CommandName Get-ChildItem -MockWith { + $mockCertificate + } + } + + It 'Should not throw error' { + InModuleScope -Parameters @{ + mockIssuer = $script:mockIssuer + } -ScriptBlock { + Set-StrictMode -Version 1.0 + + $findCertificateParams = @{ + Issuer = $mockIssuer + SubjectFormat = 'Both' + MatchAlternate = $true + Verbose = $VerbosePreference + } + + $script:returnedCertificate = Find-Certificate @findCertificateParams + + { $script:returnedCertificate } | Should -Not -Throw + } + } + + It 'Should return expected certificate' { + InModuleScope -Parameters @{ + mockCertificateThumbprint = $script:mockCertificateThumbprint + } -ScriptBlock { + Set-StrictMode -Version 1.0 + + $script:returnedCertificate.Thumbprint | Should -Be $mockCertificateThumbprint + } + } + + It 'Should call expected Mocks' { Should -Invoke -CommandName Get-ChildItem -Exactly -Times 1 -Scope Context } } - # Context 'When SubjectFormat is ''Both''' { - # Context 'Certificate does not exist, DN passed' { - # BeforeAll { - # Mock -CommandName Get-ChildItem - # } - - # It 'Should not throw error' { - # InModuleScope -Parameters @{ - # mockIssuer = $script:mockIssuer - # mockDN = $script:mockDN - # } -ScriptBlock { - # Set-StrictMode -Version 1.0 - - # { $script:returnedCertificate = Find-Certificate ` - # -Issuer $mockIssuer ` - # -SubjectFormat 'Both' ` - # -MatchAlternate $true ` - # -DN $mockDN ` - # -Verbose:$VerbosePreference } | Should -Not -Throw - # } - # } - - # It 'Should return null' { - # InModuleScope -ScriptBlock { - # Set-StrictMode -Version 1.0 - - # $script:returnedCertificate | Should -BeNullOrEmpty - # } - # } - - # It 'Should call expected Mocks' { - # Should -Invoke ` - # -CommandName Get-ChildItem ` - # -Exactly -Times 2 ` - # -Scope Context - # } - # } - - # Context 'Certificate with DN Exists, DN passed' { - # BeforeAll { - # Mock -CommandName Get-ChildItem -MockWith { - # $mockCertificateDN - # } - # } - - # It 'Should not throw error' { - # InModuleScope -Parameters @{ - # mockIssuer = $script:mockIssuer - # mockDN = $script:mockDN - # } -ScriptBlock { - # Set-StrictMode -Version 1.0 - - # { $script:returnedCertificate = Find-Certificate ` - # -Issuer $mockIssuer ` - # -SubjectFormat 'Both' ` - # -MatchAlternate $true ` - # -DN $mockDN ` - # -Verbose:$VerbosePreference } | Should -Not -Throw - # } - # } - - # It 'Should return expected certificate' { - # InModuleScope -Parameters @{ - # mockCertificateThumbprint = $script:mockCertificateThumbprint - # } -ScriptBlock { - # Set-StrictMode -Version 1.0 - - # $script:returnedCertificate.Thumbprint | Should -Be $mockCertificateThumbprint - # } - # } - - # It 'Should call expected Mocks' { - # Should -Invoke ` - # -CommandName Get-ChildItem ` - # -Exactly -Times 1 ` - # -Scope Context - # } - # } - - # Context 'Certificate without DN Exists, DN passed' { - # BeforeAll { - # Mock -CommandName Get-ChildItem -MockWith { - # $mockCertificate - # } - # } - - # It 'Should not throw error' { - # InModuleScope -Parameters @{ - # mockIssuer = $script:mockIssuer - # mockDN = $script:mockDN - # } -ScriptBlock { - # Set-StrictMode -Version 1.0 - - # { $script:returnedCertificate = Find-Certificate ` - # -Issuer $mockIssuer ` - # -SubjectFormat 'Both' ` - # -MatchAlternate $true ` - # -DN $mockDN ` - # -Verbose:$VerbosePreference } | Should -Not -Throw - # } - # } - - # It 'Should return null' { - # InModuleScope -ScriptBlock { - # Set-StrictMode -Version 1.0 - - # $script:returnedCertificate | Should -BeNullOrEmpty - # } - # } - - # It 'Should call expected Mocks' { - # Should -Invoke ` - # -CommandName Get-ChildItem ` - # -Exactly -Times 2 ` - # -Scope Context - # } - # } - - # Context 'Certificate does not exist, DN not passed' { - # BeforeAll { - # Mock -CommandName Get-ChildItem - # } - - # It 'Should not throw error' { - # InModuleScope -Parameters @{ - # mockIssuer = $script:mockIssuer - # } -ScriptBlock { - # Set-StrictMode -Version 1.0 - - # { $script:returnedCertificate = Find-Certificate ` - # -Issuer $mockIssuer ` - # -SubjectFormat 'Both' ` - # -MatchAlternate $true ` - # -Verbose:$VerbosePreference } | Should -Not -Throw - # } - # } - - # It 'Should return null' { - # InModuleScope -ScriptBlock { - # Set-StrictMode -Version 1.0 - - # $script:returnedCertificate | Should -BeNullOrEmpty - # } - # } - - # It 'Should call expected Mocks' { - # Should -Invoke ` - # -CommandName Get-ChildItem ` - # -Exactly -Times 2 ` - # -Scope Context - # } - # } - - # Context 'Certificate with DN Exists, DN not passed' { - # BeforeAll { - # Mock -CommandName Get-ChildItem -MockWith { - # $mockCertificateDN - # } - # } - - # It 'Should not throw error' { - # InModuleScope -Parameters @{ - # mockIssuer = $script:mockIssuer - # } -ScriptBlock { - # Set-StrictMode -Version 1.0 - - # { $script:returnedCertificate = Find-Certificate ` - # -Issuer $mockIssuer ` - # -SubjectFormat 'Both' ` - # -MatchAlternate $true ` - # -Verbose:$VerbosePreference } | Should -Not -Throw - # } - # } - - # It 'Should return null' { - # InModuleScope -ScriptBlock { - # Set-StrictMode -Version 1.0 - - # $script:returnedCertificate | Should -BeNullOrEmpty - # } - # } - - # It 'Should call expected Mocks' { - # Should -Invoke ` - # -CommandName Get-ChildItem ` - # -Exactly -Times 2 ` - # -Scope Context - # } - # } - - # Context 'Certificate without DN Exists, DN not passed' { - # BeforeAll { - # Mock -CommandName Get-ChildItem -MockWith { - # $mockCertificate - # } - # } - - # It 'Should not throw error' { - # InModuleScope -Parameters @{ - # mockIssuer = $script:mockIssuer - # } -ScriptBlock { - # Set-StrictMode -Version 1.0 - - # { $script:returnedCertificate = Find-Certificate ` - # -Issuer $mockIssuer ` - # -SubjectFormat 'Both' ` - # -MatchAlternate $true ` - # -Verbose:$VerbosePreference } | Should -Not -Throw - # } - # } - - # It 'Should return expected certificate' { - # InModuleScope -Parameters @{ - # mockCertificateThumbprint = $script:mockCertificateThumbprint - # } -ScriptBlock { - # Set-StrictMode -Version 1.0 - - # $script:returnedCertificate.Thumbprint | Should -Be $mockCertificateThumbprint - # } - # } - - # It 'Should call expected Mocks' { - # Should -Invoke ` - # -CommandName Get-ChildItem ` - # -Exactly -Times 1 ` - # -Scope Context - # } - # } - - # Context 'Certificate does not exist, DN not passed, MatchAlternate is false' { - # BeforeAll { - # Mock -CommandName Get-ChildItem - # } - - # It 'Should not throw error' { - # InModuleScope -Parameters @{ - # mockIssuer = $script:mockIssuer - # } -ScriptBlock { - # Set-StrictMode -Version 1.0 - - # $findParameters = @{ - # Issuer = $mockIssuer - # SubjectFormat = 'Both' - # MatchAlternate = $false - # Verbose = $VerbosePreference - # } - - # { $script:returnedCertificate = Find-Certificate @findParameters } | Should -Not -Throw - # } - # } - - # It 'Should return null' { - # InModuleScope -ScriptBlock { - # Set-StrictMode -Version 1.0 - - # $script:returnedCertificate | Should -BeNullOrEmpty - # } - - # Should -Invoke -CommandName Get-ChildItem -Exactly -Times 2 -Scope It - # } - # } - - # Context 'Certificate without DN Exists, DN not passed, MatchAlternate is false' { - # BeforeAll { - # Mock -CommandName Get-ChildItem -MockWith { - # $mockCertificate - # } - # } - - # It 'Should not throw error' { - # InModuleScope -Parameters @{ - # mockIssuer = $script:mockIssuer - # } -ScriptBlock { - # Set-StrictMode -Version 1.0 - - # $findParameters = @{ - # Issuer = $mockIssuer - # SubjectFormat = 'Both' - # MatchAlternate = $false - # Verbose = $VerbosePreference - # } - - # { $script:returnedCertificate = Find-Certificate @findParameters } | Should -Not -Throw - # } - # } - - # It 'Should return expected certificate' { - # InModuleScope -Parameters @{ - # mockCertificateThumbprint = $script:mockCertificateThumbprint - # } -ScriptBlock { - # Set-StrictMode -Version 1.0 - - # $script:returnedCertificate.Thumbprint | Should -Be $mockCertificateThumbprint - # } - - # Should -Invoke -CommandName Get-ChildItem -Exactly -Times 1 -Scope It - # } - # } - # } + Context 'SubjectFormat is Both, Certificate does not exist, DN not passed, MatchAlternate is false' { + BeforeAll { + Mock -CommandName Get-ChildItem + } + + It 'Should not throw error' { + InModuleScope -Parameters @{ + mockIssuer = $script:mockIssuer + } -ScriptBlock { + Set-StrictMode -Version 1.0 + + $findCertificateParams = @{ + Issuer = $mockIssuer + SubjectFormat = 'Both' + MatchAlternate = $false + Verbose = $VerbosePreference + } + + $script:returnedCertificate = Find-Certificate @findCertificateParams + + { $script:returnedCertificate } | Should -Not -Throw + } + } + + It 'Should return null' { + InModuleScope -ScriptBlock { + Set-StrictMode -Version 1.0 + + $script:returnedCertificate | Should -BeNullOrEmpty + } + } + + It 'Should call expected Mocks' { + Should -Invoke -CommandName Get-ChildItem -Exactly -Times 2 -Scope Context + } + } + + Context 'SubjectFormat is Both, Certificate without DN Exists, DN not passed, MatchAlternate is false' { + BeforeAll { + Mock -CommandName Get-ChildItem -MockWith { + $mockCertificate + } + } + + It 'Should not throw error' { + InModuleScope -Parameters @{ + mockIssuer = $script:mockIssuer + } -ScriptBlock { + Set-StrictMode -Version 1.0 + + $findCertificateParams = @{ + Issuer = $mockIssuer + SubjectFormat = 'Both' + MatchAlternate = $false + Verbose = $VerbosePreference + } + + $script:returnedCertificate = Find-Certificate @findCertificateParams + + { $script:returnedCertificate } | Should -Not -Throw + } + } + + It 'Should return expected certificate' { + InModuleScope -Parameters @{ + mockCertificateThumbprint = $script:mockCertificateThumbprint + } -ScriptBlock { + Set-StrictMode -Version 1.0 + + $script:returnedCertificate.Thumbprint | Should -Be $mockCertificateThumbprint + } + } + + It 'Should call expected Mocks' { + Should -Invoke -CommandName Get-ChildItem -Exactly -Times 1 -Scope Context + } + } } From 2911de48949f970b23987311bb4362e6e94c3305 Mon Sep 17 00:00:00 2001 From: Daniel Hughes Date: Thu, 26 Sep 2024 17:11:57 +0100 Subject: [PATCH 087/134] Fix missing `f` --- source/Private/Find-Certificate.ps1 | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/source/Private/Find-Certificate.ps1 b/source/Private/Find-Certificate.ps1 index ac67b53..6d6096e 100644 --- a/source/Private/Find-Certificate.ps1 +++ b/source/Private/Find-Certificate.ps1 @@ -94,7 +94,7 @@ function Find-Certificate else { # Try and lookup the certificate using the subject name - Write-Verbose -Message ($script:localizedData.FindCertificate_Message - $Subject, $Issuer) + Write-Verbose -Message ($script:localizedData.FindCertificate_Message -f $Subject, $Issuer) $certificate = Get-ChildItem -Path Cert:\localmachine\my | Where-Object -FilterScript { ($_.Extensions.EnhancedKeyUsages.FriendlyName ` @@ -132,7 +132,7 @@ function Find-Certificate else { # Try and lookup the certificate using the subject name - Write-Verbose -Message ($script:localizedData.FindCertificate_Message - $Subject, $Issuer) + Write-Verbose -Message ($script:localizedData.FindCertificate_Message -f $Subject, $Issuer) $certificate = Get-ChildItem -Path Cert:\localmachine\my | Where-Object -FilterScript { ($_.Extensions.EnhancedKeyUsages.FriendlyName ` From 1c31e940980ddaea0cb5c71214c040f6cb9b4b71 Mon Sep 17 00:00:00 2001 From: Daniel Hughes Date: Thu, 26 Sep 2024 17:55:17 +0100 Subject: [PATCH 088/134] Remove parenthesis --- source/Private/Find-Certificate.ps1 | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/source/Private/Find-Certificate.ps1 b/source/Private/Find-Certificate.ps1 index 6d6096e..22a7c8a 100644 --- a/source/Private/Find-Certificate.ps1 +++ b/source/Private/Find-Certificate.ps1 @@ -135,10 +135,9 @@ function Find-Certificate 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) + $_.Extensions.EnhancedKeyUsages.FriendlyName -contains 'Server Authentication' + -and $_.Issuer -eq $Issuer + -and $_.Subject -eq $Subject } | Select-Object -First 1 } # if } # if From 9563056d25cc5b528b53c9a8e4a4e35edb830e39 Mon Sep 17 00:00:00 2001 From: Daniel Hughes Date: Thu, 26 Sep 2024 17:55:26 +0100 Subject: [PATCH 089/134] Add more tests --- tests/Unit/Classes/WSManListener.Tests.ps1 | 384 ++++++++++++++++----- 1 file changed, 291 insertions(+), 93 deletions(-) diff --git a/tests/Unit/Classes/WSManListener.Tests.ps1 b/tests/Unit/Classes/WSManListener.Tests.ps1 index 4aed740..a629025 100644 --- a/tests/Unit/Classes/WSManListener.Tests.ps1 +++ b/tests/Unit/Classes/WSManListener.Tests.ps1 @@ -90,7 +90,7 @@ Describe 'WSManListener\Get()' -Tag 'Get' { InModuleScope -ScriptBlock { Set-StrictMode -Version 1.0 - $script:mockWSManListenerInstance = [WSManListener] @{ + $script:mockInstance = [WSManListener] @{ Transport = 'HTTP' Ensure = 'Present' } @@ -102,14 +102,14 @@ Describe 'WSManListener\Get()' -Tag 'Get' { call back to the derived class method GetCurrentState() to get the result to return from the derived method Get(). #> - $script:mockWSManListenerInstance | + $script:mockInstance | Add-Member -Force -MemberType 'ScriptMethod' -Name 'GetCurrentState' -Value { - return [System.Collections.Hashtable] @{ - Transport = [WSManTransport] 'HTTP' - Port = [System.UInt16] 5985 - Address = '*' - Enabled = 'true' - URLPrefix = 'wsman' + return @{ + Transport = [WSManTransport] 'HTTP' + Port = [System.UInt16] 5985 + Address = '*' + Enabled = 'true' + URLPrefix = 'wsman' } } -PassThru | Add-Member -Force -MemberType 'ScriptMethod' -Name 'AssertProperties' -Value { @@ -122,7 +122,7 @@ Describe 'WSManListener\Get()' -Tag 'Get' { InModuleScope -ScriptBlock { Set-StrictMode -Version 1.0 - $currentState = $script:mockWSManListenerInstance.Get() + $currentState = $script:mockInstance.Get() $currentState.Transport | Should -Be 'HTTP' $currentState.Port | Should -Be 5985 @@ -150,7 +150,7 @@ Describe 'WSManListener\Get()' -Tag 'Get' { InModuleScope -ScriptBlock { Set-StrictMode -Version 1.0 - $script:mockWSManListenerInstance = [WSManListener] @{ + $script:mockInstance = [WSManListener] @{ Transport = 'HTTPS' Ensure = 'Present' } @@ -162,14 +162,14 @@ Describe 'WSManListener\Get()' -Tag 'Get' { call back to the derived class method GetCurrentState() to get the result to return from the derived method Get(). #> - $script:mockWSManListenerInstance | + $script:mockInstance | Add-Member -Force -MemberType 'ScriptMethod' -Name 'GetCurrentState' -Value { - return [System.Collections.Hashtable] @{ - Transport = [WSManTransport] 'HTTPS' - Port = [System.UInt16] 5986 - Address = '*' - Enabled = 'true' - URLPrefix = 'wsman' + return @{ + Transport = [WSManTransport] 'HTTPS' + Port = [System.UInt16] 5986 + Address = '*' + Enabled = 'true' + URLPrefix = 'wsman' } } -PassThru | Add-Member -Force -MemberType 'ScriptMethod' -Name 'AssertProperties' -Value { @@ -182,7 +182,7 @@ Describe 'WSManListener\Get()' -Tag 'Get' { InModuleScope -ScriptBlock { Set-StrictMode -Version 1.0 - $currentState = $script:mockWSManListenerInstance.Get() + $currentState = $script:mockInstance.Get() $currentState.Transport | Should -Be 'HTTPS' $currentState.Port | Should -Be 5986 @@ -209,7 +209,7 @@ Describe 'WSManListener\Get()' -Tag 'Get' { InModuleScope -ScriptBlock { Set-StrictMode -Version 1.0 - $script:mockWSManListenerInstance = [WSManListener] @{ + $script:mockInstance = [WSManListener] @{ Transport = 'HTTP' Ensure = 'Absent' } @@ -221,9 +221,9 @@ Describe 'WSManListener\Get()' -Tag 'Get' { call back to the derived class method GetCurrentState() to get the result to return from the derived method Get(). #> - $script:mockWSManListenerInstance | + $script:mockInstance | Add-Member -Force -MemberType 'ScriptMethod' -Name 'GetCurrentState' -Value { - return [System.Collections.Hashtable] @{} + return @{} } -PassThru | Add-Member -Force -MemberType 'ScriptMethod' -Name 'AssertProperties' -Value { return @@ -235,7 +235,7 @@ Describe 'WSManListener\Get()' -Tag 'Get' { InModuleScope -ScriptBlock { Set-StrictMode -Version 1.0 - $currentState = $script:mockWSManListenerInstance.Get() + $currentState = $script:mockInstance.Get() $currentState.Transport | Should -Be 'HTTP' $currentState.Port | Should -BeNullOrEmpty @@ -254,7 +254,7 @@ Describe 'WSManListener\Get()' -Tag 'Get' { $currentState.Ensure | Should -Be 'Absent' $currentState.Reasons | Should -HaveCount 1 $currentState.Reasons[0].Code | Should -Be 'WSManListener:WSManListener:Transport' - $currentState.Reasons[0].Phrase | Should -Be 'The property Transport should be "HTTP", but was ""' + $currentState.Reasons[0].Phrase | Should -Be 'The property Transport should be "HTTP", but was null' } } } @@ -266,7 +266,7 @@ Describe 'WSManListener\Get()' -Tag 'Get' { InModuleScope -ScriptBlock { Set-StrictMode -Version 1.0 - $script:mockWSManListenerInstance = [WSManListener] @{ + $script:mockInstance = [WSManListener] @{ Transport = 'HTTPS' Port = 5986 Ensure = 'Present' @@ -279,14 +279,14 @@ Describe 'WSManListener\Get()' -Tag 'Get' { call back to the derived class method GetCurrentState() to get the result to return from the derived method Get(). #> - $script:mockWSManListenerInstance | + $script:mockInstance | Add-Member -Force -MemberType 'ScriptMethod' -Name 'GetCurrentState' -Value { - return [System.Collections.Hashtable] @{ - Transport = [WSManTransport] 'HTTPS' - Port = [System.UInt16] 6000 - Address = '*' - Enabled = 'true' - URLPrefix = 'wsman' + return @{ + Transport = [WSManTransport] 'HTTPS' + Port = [System.UInt16] 6000 + Address = '*' + Enabled = 'true' + URLPrefix = 'wsman' } } -PassThru | Add-Member -Force -MemberType 'ScriptMethod' -Name 'AssertProperties' -Value { @@ -299,7 +299,7 @@ Describe 'WSManListener\Get()' -Tag 'Get' { InModuleScope -ScriptBlock { Set-StrictMode -Version 1.0 - $currentState = $script:mockWSManListenerInstance.Get() + $currentState = $script:mockInstance.Get() $currentState.Transport | Should -Be 'HTTPS' @@ -326,70 +326,12 @@ Describe 'WSManListener\Get()' -Tag 'Get' { } } - Context 'When the listener exists' { - BeforeAll { - InModuleScope -ScriptBlock { - Set-StrictMode -Version 1.0 - - $script:mockWSManListenerInstance = [WSManListener] @{ - Transport = 'HTTPS' - Ensure = 'Present' - } - - <# - This mocks the method GetCurrentState(). - - Method Get() will call the base method Get() which will - call back to the derived class method GetCurrentState() - to get the result to return from the derived method Get(). - #> - $script:mockWSManListenerInstance | - Add-Member -Force -MemberType 'ScriptMethod' -Name 'GetCurrentState' -Value { - return [System.Collections.Hashtable] @{} - } -PassThru | - Add-Member -Force -MemberType 'ScriptMethod' -Name 'AssertProperties' -Value { - return - } - } - } - - It 'Should return the correct values' { - InModuleScope -ScriptBlock { - Set-StrictMode -Version 1.0 - - $currentState = $script:mockWSManListenerInstance.Get() - - $currentState.Transport | Should -Be 'HTTPS' - $currentState.Port | Should -BeNullOrEmpty - - $currentState.Address | Should -BeNullOrEmpty - $currentState.Enabled | Should -BeFalse - $currentState.URLPrefix | Should -BeNullOrEmpty - - $currentState.Issuer | Should -BeNullOrEmpty - $currentState.SubjectFormat | Should -Be 'Both' - $currentState.MatchAlternate | Should -BeNullOrEmpty - $currentState.BaseDN | Should -BeNullOrEmpty - $currentState.CertificateThumbprint | Should -BeNullOrEmpty - $currentState.Hostname | Should -BeNullOrEmpty - - $currentState.Ensure | Should -Be 'Absent' - - $currentState.Reasons | Should -HaveCount 2 - $currentState.Reasons[1].Code | Should -Be 'WSManListener:WSManListener:Ensure' - $currentState.Reasons[1].Phrase | Should -Be 'The property Ensure should be "Present", but was "Absent"' - $currentState.Reasons[0].Code | Should -Be 'WSManListener:WSManListener:Transport' - $currentState.Reasons[0].Phrase | Should -Be 'The property Transport should be "HTTPS", but was ""' - } - } - } - Context 'When the listener exists but should not' { BeforeAll { InModuleScope -ScriptBlock { Set-StrictMode -Version 1.0 - $script:mockWSManListenerInstance = [WSManListener] @{ + $script:mockInstance = [WSManListener] @{ Transport = 'HTTP' Ensure = 'Absent' } @@ -401,7 +343,7 @@ Describe 'WSManListener\Get()' -Tag 'Get' { call back to the derived class method GetCurrentState() to get the result to return from the derived method Get(). #> - $script:mockWSManListenerInstance | + $script:mockInstance | Add-Member -Force -MemberType 'ScriptMethod' -Name 'GetCurrentState' -Value { return [System.Collections.Hashtable] @{ Transport = [WSManTransport] 'HTTP' @@ -426,7 +368,7 @@ Describe 'WSManListener\Get()' -Tag 'Get' { InModuleScope -ScriptBlock { Set-StrictMode -Version 1.0 - $currentState = $script:mockWSManListenerInstance.Get() + $currentState = $script:mockInstance.Get() $currentState.Transport | Should -Be 'HTTP' $currentState.Port | Should -Be 5985 @@ -452,3 +394,259 @@ Describe 'WSManListener\Get()' -Tag 'Get' { } } } + +Describe 'WSManListener\Set()' -Tag 'Set' { + BeforeAll { + InModuleScope -ScriptBlock { + Set-StrictMode -Version 1.0 + + $script:mockInstance = [WSManListener] @{ + Transport = 'HTTP' + Port = 5000 + Ensure = 'Present' + } | + # Mock method Modify which is called by the case method Set(). + Add-Member -Force -MemberType 'ScriptMethod' -Name 'Modify' -Value { + $script:methodModifyCallCount += 1 + } -PassThru + } + } + + BeforeEach { + InModuleScope -ScriptBlock { + Set-StrictMode -Version 1.0 + + $script:methodModifyCallCount = 0 + } + } + + Context 'When the system is in the desired state' { + BeforeAll { + InModuleScope -ScriptBlock { + Set-StrictMode -Version 1.0 + + $script:mockInstance | + # Mock method Compare() which is called by the base method Set() + Add-Member -Force -MemberType 'ScriptMethod' -Name 'Compare' -Value { + return $null + } -PassThru | + Add-Member -Force -MemberType 'ScriptMethod' -Name 'AssertProperties' -Value { + return + } + } + } + + It 'Should not call method Modify()' { + InModuleScope -ScriptBlock { + Set-StrictMode -Version 1.0 + + $script:mockInstance.Set() + + $script:methodModifyCallCount | Should -Be 0 + } + } + } + + Context 'When the system is not in the desired state' { + BeforeAll { + InModuleScope -ScriptBlock { + Set-StrictMode -Version 1.0 + + $script:mockInstance | + # Mock method Compare() which is called by the base method Set() + Add-Member -Force -MemberType 'ScriptMethod' -Name 'Compare' -Value { + return @( + @{ + Property = 'Port' + ExpectedValue = 5000 + ActualValue = 5985 + } + ) + } -PassThru | + Add-Member -Force -MemberType 'ScriptMethod' -Name 'AssertProperties' -Value { + return + } + } + } + + It 'Should call method Modify()' { + InModuleScope -ScriptBlock { + Set-StrictMode -Version 1.0 + + $script:mockInstance.Set() + + $script:methodModifyCallCount | Should -Be 1 + } + } + } +} + +Describe 'WSManListener\Test()' -Tag 'Test' { + BeforeAll { + InModuleScope -ScriptBlock { + Set-StrictMode -Version 1.0 + + $script:mockInstance = [WSManListener] @{ + Transport = 'HTTPS' + Port = 5986 + CertificateThumbprint = '74FA31ADEA7FDD5333CED10910BFA6F665A1F2FC' + HostName = $([System.Net.Dns]::GetHostByName($ENV:computerName).Hostname) + Ensure = 'Present' + } + } + } + + Context 'When the system is in the desired state' { + BeforeAll { + InModuleScope -ScriptBlock { + Set-StrictMode -Version 1.0 + + $script:mockInstance | + # Mock method Compare() which is called by the base method Set() + Add-Member -Force -MemberType 'ScriptMethod' -Name 'Compare' -Value { + return $null + } -PassThru | + Add-Member -Force -MemberType 'ScriptMethod' -Name 'AssertProperties' -Value { + return + } + } + + It 'Should return $true' { + InModuleScope -ScriptBlock { + Set-StrictMode -Version 1.0 + + $script:mockInstance.Test() | Should -BeTrue + } + } + } + } + + Context 'When the system is not in the desired state' { + BeforeAll { + InModuleScope -ScriptBlock { + Set-StrictMode -Version 1.0 + + $script:mockInstance | + # Mock method Compare() which is called by the base method Set() + Add-Member -Force -MemberType 'ScriptMethod' -Name 'Compare' -Value { + return @( + @{ + Property = 'Port' + ExpectedValue = 5986 + ActualValue = 443 + }) + } -PassThru | + Add-Member -Force -MemberType 'ScriptMethod' -Name 'AssertProperties' -Value { + return + } + } + } + + It 'Should return $false' { + InModuleScope -ScriptBlock { + Set-StrictMode -Version 1.0 + + $script:mockInstance.Test() | Should -BeFalse + } + } + } +} + +Describe 'WSManListener\GetCurrentState()' -Tag 'HiddenMember' { + Context 'When object is missing in the current state' { + BeforeAll { + InModuleScope -ScriptBlock { + Set-StrictMode -Version 1.0 + + # $script:mockInstance = [DnsServerCache] @{ + # DnsServer = 'localhost' + # } + } + + # Mock -CommandName Get-DnsServerCache + } + } + + It 'Should return the correct values' { + InModuleScope -ScriptBlock { + Set-StrictMode -Version 1.0 + + # $currentState = $script:mockInstance.GetCurrentState( + # @{ + # DnsServer = 'localhost' + # } + # ) + + # $currentState.DnsServer | Should -Be 'localhost' + # $currentState.IgnorePolicies | Should -BeFalse + # $currentState.LockingPercent | Should -Be 0 + # $currentState.MaxKBSize | Should -Be 0 + # $currentState.MaxNegativeTtl | Should -BeNullOrEmpty + # $currentState.MaxTtl | Should -BeNullOrEmpty + # $currentState.EnablePollutionProtection | Should -BeFalse + # $currentState.StoreEmptyAuthenticationResponse | Should -BeFalse + } + + #Should -Invoke -CommandName Get-DnsServerCache -Exactly -Times 1 -Scope It + } + + Context 'When the object is present in the current state' { + BeforeAll { + InModuleScope -ScriptBlock { + Set-StrictMode -Version 1.0 + + # $script:mockInstance = [DnsServerCache] @{ + # DnsServer = 'SomeHost' + # } + # } + + # Mock -CommandName Get-DnsServerCache -MockWith { + # return New-CimInstance -ClassName 'DnsServerCache' -Namespace 'root/Microsoft/Windows/DNS' -ClientOnly -Property @{ + # IgnorePolicies = $true + # LockingPercent = 100 + # MaxKBSize = 0 + # MaxNegativeTtl = '00:15:00' + # MaxTtl = '1.00:00:00' + # EnablePollutionProtection = $true + # StoreEmptyAuthenticationResponse = $true + # } + # } + } + } + + It 'Should return the correct values' { + InModuleScope -ScriptBlock { + Set-StrictMode -Version 1.0 + + # $currentState = $script:mockInstance.GetCurrentState( + # @{ + # DnsServer = 'SomeHost' + # } + # ) + + # $currentState.DnsServer | Should -Be 'SomeHost' + # $currentState.IgnorePolicies | Should -BeTrue + # $currentState.LockingPercent | Should -Be 100 + # $currentState.MaxKBSize | Should -Be 0 + # $currentState.MaxNegativeTtl | Should -Be '00:15:00' + # $currentState.MaxTtl | Should -Be '1.00:00:00' + # $currentState.EnablePollutionProtection | Should -BeTrue + # $currentState.StoreEmptyAuthenticationResponse | Should -BeTrue + } + + #Should -Invoke -CommandName Get-DnsServerCache -Exactly -Times 1 -Scope It + } + } +} + +Describe 'WSManListener\Modify()' -Tag 'HiddenMember' { + Context 'When the system is not in the desired state' { + BeforeAll { + InModuleScope -ScriptBlock { + Set-StrictMode -Version 1.0 + + + } + } + } +} From a8c067b51c78f55aeda104baf112a17cb0cc357a Mon Sep 17 00:00:00 2001 From: Daniel Hughes Date: Fri, 27 Sep 2024 17:10:09 +0100 Subject: [PATCH 090/134] Update modify to separate functions --- source/Classes/020.WSManListener.ps1 | 151 +++++++++++++-------------- 1 file changed, 72 insertions(+), 79 deletions(-) diff --git a/source/Classes/020.WSManListener.ps1 b/source/Classes/020.WSManListener.ps1 index 304091a..2b9c3a6 100644 --- a/source/Classes/020.WSManListener.ps1 +++ b/source/Classes/020.WSManListener.ps1 @@ -89,7 +89,7 @@ class WSManListener : ResourceBase [DscProperty()] [System.String] - $Hostname + $HostName [DscProperty(NotConfigurable)] [System.Boolean] @@ -189,120 +189,113 @@ class WSManListener : ResourceBase #> hidden [void] Modify([System.Collections.Hashtable] $properties) { - - $remove = $false - $create = $false - - $selectorSet = @{ - Transport = $this.Transport - Address = $this.Address - } - - Write-Verbose ('$properties.ContainsKey(''Ensure'') = {0}' -f $properties.ContainsKey('Ensure')) - Write-Verbose ('$properties.Ensure = {0}' -f $properties.Ensure) - Write-Verbose ('$this.Ensure = {0}' -f $this.Ensure) + #Write-Verbose ('$properties.ContainsKey(''Ensure'') = {0}' -f $properties.ContainsKey('Ensure')) + #Write-Verbose ('$properties.Ensure = {0}' -f $properties.Ensure) + #Write-Verbose ('$this.Ensure = {0}' -f $this.Ensure) 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 - $remove = $true + Write-Verbose -Message ($this.localizedData.ListenerExistsRemoveMessage -f $this.Transport, $this.Port) + + $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 - $create = $true + Write-Verbose -Message ($this.localizedData.CreatingListenerMessage -f $this.Transport, $this.Port) + + $this.NewInstance() } else { # Resource exists but one or more properties are not in the desired state - $remove = $true - $create = $true + Write-Verbose -Message ($this.localizedData.ModifyingListenerMessage -f $this.Transport, $this.Port) + + $this.SetInstance($properties) } + } - if ($remove) - { - Write-Verbose -Message ($this.localizedData.ListenerExistsRemoveMessage -f $this.Transport, $this.Port) + [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) + {} + + hidden [void] NewInstance() + { + $selectorSet = @{ + Transport = $this.Transport + Address = $this.Address + } - Remove-WSManInstance -ResourceURI 'winrm/config/Listener' -SelectorSet @selectorSet + $valueSet = @{ + Port = $this.Port } - if ($create) + + if ($this.Transport -eq [WSManTransport]::HTTPS) { - $valueSet = @{ - Port = $properties.Port - } + $findCertificateParams = $this | Get-DscProperty -Attribute @('Optional') -ExcludeName @('Port', 'Address') + $certificate = Find-Certificate @findCertificateParams + [System.String] $thumbprint = $certificate.Thumbprint - if ($this.Transport -eq [WSManTransport]::HTTPS) + if ($thumbprint) { - $findCertificateParams = Get-DscProperty -Attribute @('Optional') -ExcludeName @('Port', 'Address') + $valueSet.CertificateThumbprint = $thumbprint - $certificate = Find-Certificate @findCertificateParams - [System.String] $thumbprint = $certificate.thumbprint - - if ($thumbprint) + if ([System.String]::IsNullOrEmpty($this.Hostname)) { - $valueSet.CertificateThumbprint = $thumbprint - - if ([System.String]::IsNullOrEmpty($properties.Hostname)) - { - $valueSet.HostName = [System.Net.Dns]::GetHostByName($env:COMPUTERNAME).Hostname - } - else - { - $valueSet.HostName = $properties.HostName - } + $valueSet.HostName = [System.Net.Dns]::GetHostByName($env:COMPUTERNAME).Hostname } else { - # TODO: Extract this to assert or into a parameter set - # 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 + $valueSet.HostName = $this.HostName + } } - - - Write-Verbose -Message ($this.localizedData.CreatingListenerMessage -f $this.Transport, $this.Port) - - New-WSManInstance -ResourceURI 'winrm/config/Listener' -SelectorSet @selectorSet -ValueSet @valueSet -ErrorAction Stop + 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 } - [System.Boolean] Test() + hidden [void] RemoveInstance() { - # Call the base method to test all of the properties that should be enforced. - return ([ResourceBase] $this).Test() + $selectorSet = @{ + Transport = $this.Transport + Address = $this.Address + } + + Remove-WSManInstance -ResourceURI 'winrm/config/Listener' -SelectorSet $selectorSet } - <# - Base method Assert() call this method with the properties that was assigned - a value. - #> - hidden [void] AssertProperties([System.Collections.Hashtable] $properties) + hidden [void] SetInstance([System.Collections.Hashtable] $properties) { - # if ($null -ne $properties.SubjectFormat) - # { - # $errorMessage = $this.localizedData.SubjectFormatMustBeValid + $selectorSet = @{ + Transport = $this.Transport + Address = $this.Address + } - # if ($properties.SubjectFormat -inotin ('Both', 'FQDNOnly', 'NameOnly')) - # { - # New-InvalidArgumentException -ArgumentName 'SubjectFormat' -Message $errorMessage - # } - # } + $valueSet = @{} - # # The properties MaximumFiles and MaximumRolloverFiles are mutually exclusive. - # $assertBoundParameterParameters = @{ - # BoundParameterList = $properties - # MutuallyExclusiveList1 = @( - # 'MaximumFiles' - # ) - # MutuallyExclusiveList2 = @( - # 'MaximumRolloverFiles' - # ) - # } + foreach ($property in $properties) { + $valueSet.$property.Key = $property.Value + } - # Assert-BoundParameter @assertBoundParameterParameters + Set-WSManInstance -ResourceURI 'winrm/config/Listener' -SelectorSet $selectorSet -ValueSet $valueSet } } From ce21fbeff26d0fd0e065088e9b7b4de06429abb5 Mon Sep 17 00:00:00 2001 From: Daniel Hughes Date: Fri, 27 Sep 2024 17:10:20 +0100 Subject: [PATCH 091/134] Add new string --- source/en-US/WSManListener.strings.psd1 | 1 + 1 file changed, 1 insertion(+) diff --git a/source/en-US/WSManListener.strings.psd1 b/source/en-US/WSManListener.strings.psd1 index 3120c46..48597d1 100644 --- a/source/en-US/WSManListener.strings.psd1 +++ b/source/en-US/WSManListener.strings.psd1 @@ -14,4 +14,5 @@ ConvertFrom-StringData @' ListenerExistsRemoveMessage = Removing {0} Listener on port {1} (WSML0001). ListenerCreateFailNoCertError = Failed to create {0} Listener on port {1} because an applicable certificate could not be found (WSM0002). CreatingListenerMessage = Creating {0} Listener on port {1} (WSML0003). + ModifyingListenerMessage = Modifying {0} Listener on port {1} (WSML0004). '@ From 8c753b69a5b484e023c87c500d9546d3549a6129 Mon Sep 17 00:00:00 2001 From: Daniel Hughes Date: Fri, 27 Sep 2024 17:10:26 +0100 Subject: [PATCH 092/134] Update tests --- tests/Unit/Classes/WSManListener.Tests.ps1 | 469 ++++++++++++++++++--- 1 file changed, 412 insertions(+), 57 deletions(-) diff --git a/tests/Unit/Classes/WSManListener.Tests.ps1 b/tests/Unit/Classes/WSManListener.Tests.ps1 index a629025..f45123a 100644 --- a/tests/Unit/Classes/WSManListener.Tests.ps1 +++ b/tests/Unit/Classes/WSManListener.Tests.ps1 @@ -254,7 +254,16 @@ Describe 'WSManListener\Get()' -Tag 'Get' { $currentState.Ensure | Should -Be 'Absent' $currentState.Reasons | Should -HaveCount 1 $currentState.Reasons[0].Code | Should -Be 'WSManListener:WSManListener:Transport' - $currentState.Reasons[0].Phrase | Should -Be 'The property Transport should be "HTTP", but was null' + + # PS6+ treats empty enum as null + if ($PSVersionTable.PSVersion.Major -gt 5) + { + $currentState.Reasons[0].Phrase | Should -Be 'The property Transport should be "HTTP", but was null' + } + else + { + $currentState.Reasons[0].Phrase | Should -Be 'The property Transport should be "HTTP", but was ""' + } } } } @@ -490,7 +499,7 @@ Describe 'WSManListener\Test()' -Tag 'Test' { Transport = 'HTTPS' Port = 5986 CertificateThumbprint = '74FA31ADEA7FDD5333CED10910BFA6F665A1F2FC' - HostName = $([System.Net.Dns]::GetHostByName($ENV:computerName).Hostname) + Hostname = $([System.Net.Dns]::GetHostByName($ENV:computerName).Hostname) Ensure = 'Present' } } @@ -558,95 +567,441 @@ Describe 'WSManListener\GetCurrentState()' -Tag 'HiddenMember' { InModuleScope -ScriptBlock { Set-StrictMode -Version 1.0 - # $script:mockInstance = [DnsServerCache] @{ - # DnsServer = 'localhost' - # } + $script:mockInstance = [WSManListener] @{ + Transport = 'HTTP' + Port = 5985 + Address = '*' + Ensure = 'Present' + } } - # Mock -CommandName Get-DnsServerCache + Mock -CommandName Get-Listener + } + + It 'Should return the correct values' { + InModuleScope -ScriptBlock { + Set-StrictMode -Version 1.0 + + $currentState = $script:mockInstance.GetCurrentState( + @{ + Transport = 'HTTP' + Ensure = [Ensure]::Present + } + ) + + $currentState.Transport | Should -BeNullOrEmpty + $currentState.Port | Should -BeNullOrEmpty + $currentState.Address | Should -BeNullOrEmpty + $currentState.Issuer | Should -BeNullOrEmpty + $currentState.CertificateThumbprint | Should -BeNullOrEmpty + $currentState.Hostname | Should -BeNullOrEmpty + $currentState.Enabled | Should -BeFalse + $currentState.URLPrefix | Should -BeNullOrEmpty + } + + Should -Invoke -CommandName Get-Listener -Exactly -Times 1 -Scope It } } - It 'Should return the correct values' { - InModuleScope -ScriptBlock { - Set-StrictMode -Version 1.0 + Context 'When the object is present in the current state' { + Context 'When ''Port'' and ''Address'' are supplied for HTTP Transport' { + BeforeAll { + InModuleScope -ScriptBlock { + Set-StrictMode -Version 1.0 + + $script:mockInstance = [WSManListener] @{ + Transport = 'HTTP' + Port = 5985 + Address = '*' + Ensure = 'Present' + } + } + + Mock -CommandName Get-Listener -MockWith { + return @{ + Transport = 'HTTP' + Port = [System.UInt16] 5985 + Address = '*' + + CertificateThumbprint = $null + Hostname = $([System.Net.Dns]::GetHostByName($ENV:computerName).Hostname) + + Enabled = $true + URLPrefix = 'wsman' + } + } + } + + It 'Should return the correct values' { + InModuleScope -ScriptBlock { + Set-StrictMode -Version 1.0 + + $currentState = $script:mockInstance.GetCurrentState( + @{ + Transport = 'HTTP' + Ensure = [Ensure]::Present + } + ) + + $currentState.Transport | Should -Be 'HTTP' + $currentState.Port | Should -Be 5985 + $currentState.Address | Should -Be '*' + $currentState.Issuer | Should -BeNullOrEmpty + $currentState.CertificateThumbprint | Should -BeNullOrEmpty + $currentState.Hostname | Should -Be $([System.Net.Dns]::GetHostByName($ENV:computerName).Hostname) + $currentState.Enabled | Should -BeTrue + $currentState.URLPrefix | Should -Be 'wsman' + } + + Should -Invoke -CommandName Get-Listener -Exactly -Times 1 -Scope It + } + } + + Context 'When ''Port'' and ''Address'' are not supplied for HTTP Transport' { + BeforeAll { + InModuleScope -ScriptBlock { + Set-StrictMode -Version 1.0 + + $script:mockInstance = [WSManListener] @{ + Transport = 'HTTP' + Ensure = 'Present' + } + } + + Mock -CommandName Get-Listener -MockWith { + return @{ + Transport = 'HTTP' + Port = [System.UInt16] 5985 + Address = '*' + + CertificateThumbprint = $null + Hostname = $([System.Net.Dns]::GetHostByName($ENV:computerName).Hostname) + + Enabled = $true + URLPrefix = 'wsman' + } + } - # $currentState = $script:mockInstance.GetCurrentState( - # @{ - # DnsServer = 'localhost' - # } - # ) + Mock -CommandName Get-DefaultPort -MockWith { return [System.UInt16] 5985 } + } + + It 'Should return the correct values' { + InModuleScope -ScriptBlock { + Set-StrictMode -Version 1.0 + + $currentState = $script:mockInstance.GetCurrentState( + @{ + Transport = 'HTTP' + Ensure = [Ensure]::Present + } + ) - # $currentState.DnsServer | Should -Be 'localhost' - # $currentState.IgnorePolicies | Should -BeFalse - # $currentState.LockingPercent | Should -Be 0 - # $currentState.MaxKBSize | Should -Be 0 - # $currentState.MaxNegativeTtl | Should -BeNullOrEmpty - # $currentState.MaxTtl | Should -BeNullOrEmpty - # $currentState.EnablePollutionProtection | Should -BeFalse - # $currentState.StoreEmptyAuthenticationResponse | Should -BeFalse + $currentState.Transport | Should -Be 'HTTP' + $currentState.Port | Should -Be 5985 + $currentState.Address | Should -Be '*' + $currentState.Issuer | Should -BeNullOrEmpty + $currentState.CertificateThumbprint | Should -BeNullOrEmpty + $currentState.Hostname | Should -Be $([System.Net.Dns]::GetHostByName($ENV:computerName).Hostname) + $currentState.Enabled | Should -BeTrue + $currentState.URLPrefix | Should -Be 'wsman' + } + + Should -Invoke -CommandName Get-Listener -Exactly -Times 1 -Scope It + Should -Invoke -CommandName Get-DefaultPort -Exactly -Times 1 -Scope It + } } - #Should -Invoke -CommandName Get-DnsServerCache -Exactly -Times 1 -Scope It + Context 'When ''Port'' and ''Address'' are supplied for HTTPS Transport' { + BeforeAll { + InModuleScope -ScriptBlock { + Set-StrictMode -Version 1.0 + + $script:mockInstance = [WSManListener] @{ + Transport = 'HTTPS' + Port = 5986 + Address = '*' + Ensure = 'Present' + } + } + + Mock -CommandName Get-Listener -MockWith { + return @{ + Transport = 'HTTPS' + Port = [System.UInt16] 5986 + Address = '*' + + CertificateThumbprint = '74FA31ADEA7FDD5333CED10910BFA6F665A1F2FC' + Hostname = $([System.Net.Dns]::GetHostByName($ENV:computerName).Hostname) + + Enabled = $true + URLPrefix = 'wsman' + } + } + + Mock -CommandName Find-Certificate -MockWith { return @{ Issuer = 'CN=CONTOSO.COM Issuing CA, DC=CONTOSO, DC=COM' } } + } + + It 'Should return the correct values' { + InModuleScope -ScriptBlock { + Set-StrictMode -Version 1.0 + + $currentState = $script:mockInstance.GetCurrentState( + @{ + Transport = 'HTTPS' + Ensure = [Ensure]::Present + } + ) + + $currentState.Transport | Should -Be 'HTTPS' + $currentState.Port | Should -Be 5986 + $currentState.Address | Should -Be '*' + $currentState.Issuer | Should -Be 'CN=CONTOSO.COM Issuing CA, DC=CONTOSO, DC=COM' + $currentState.CertificateThumbprint | Should -Be '74FA31ADEA7FDD5333CED10910BFA6F665A1F2FC' + $currentState.Hostname | Should -Be $([System.Net.Dns]::GetHostByName($ENV:computerName).Hostname) + $currentState.Enabled | Should -BeTrue + $currentState.URLPrefix | Should -Be 'wsman' + } + + Should -Invoke -CommandName Get-Listener -Exactly -Times 1 -Scope It + Should -Invoke -CommandName Find-Certificate -Exactly -Times 1 -Scope It + } + } } +} - Context 'When the object is present in the current state' { +Describe 'WSManListener\Modify()' -Tag 'HiddenMember' { + Context 'When the system is not in the desired state' { BeforeAll { InModuleScope -ScriptBlock { Set-StrictMode -Version 1.0 - # $script:mockInstance = [DnsServerCache] @{ - # DnsServer = 'SomeHost' - # } - # } - - # Mock -CommandName Get-DnsServerCache -MockWith { - # return New-CimInstance -ClassName 'DnsServerCache' -Namespace 'root/Microsoft/Windows/DNS' -ClientOnly -Property @{ - # IgnorePolicies = $true - # LockingPercent = 100 - # MaxKBSize = 0 - # MaxNegativeTtl = '00:15:00' - # MaxTtl = '1.00:00:00' - # EnablePollutionProtection = $true - # StoreEmptyAuthenticationResponse = $true - # } - # } + $script:mockInstance = [WSManListener] @{ + Transport = 'HTTP' + Ensure = 'Present' + } | + # Mock method NewInstance which is called by the case method Modify(). + Add-Member -Force -MemberType 'ScriptMethod' -Name 'NewInstance' -Value { + $script:methodNewInstanceCallCount += 1 + } -PassThru + | + # Mock method RemoveInstance which is called by the case method Modify(). + Add-Member -Force -MemberType 'ScriptMethod' -Name 'RemoveInstance' -Value { + $script:methodRemoveInstanceCallCount += 1 + } -PassThru + | + # Mock method SetInstance which is called by the case method Modify(). + Add-Member -Force -MemberType 'ScriptMethod' -Name 'SetInstance' -Value { + $script:methodSetInstanceCallCount += 1 + } -PassThru } } - It 'Should return the correct values' { + BeforeEach { InModuleScope -ScriptBlock { Set-StrictMode -Version 1.0 - # $currentState = $script:mockInstance.GetCurrentState( - # @{ - # DnsServer = 'SomeHost' - # } - # ) + $script:methodNewInstanceCallCount = 0 + $script:methodRemoveInstanceCallCount = 0 + $script:methodSetInstanceCallCount = 0 + } + } + + Context 'When the resource does not exist' { + It 'Should call method NewInstance()' { + InModuleScope -ScriptBlock { + Set-StrictMode -Version 1.0 - # $currentState.DnsServer | Should -Be 'SomeHost' - # $currentState.IgnorePolicies | Should -BeTrue - # $currentState.LockingPercent | Should -Be 100 - # $currentState.MaxKBSize | Should -Be 0 - # $currentState.MaxNegativeTtl | Should -Be '00:15:00' - # $currentState.MaxTtl | Should -Be '1.00:00:00' - # $currentState.EnablePollutionProtection | Should -BeTrue - # $currentState.StoreEmptyAuthenticationResponse | Should -BeTrue + $mockProperties = @{ + Transport = 'HTTP' + Ensure = 'Present' + } + + $script:mockInstance.Modify($mockProperties) + + $script:methodNewInstanceCallCount | Should -Be 1 + } } + } + + Context 'When the resource does exist' { + It 'Should call method RemoveInstance()' { + InModuleScope -ScriptBlock { + Set-StrictMode -Version 1.0 - #Should -Invoke -CommandName Get-DnsServerCache -Exactly -Times 1 -Scope It + $script:mockInstance.Ensure = 'Absent' + + $mockProperties = @{ + Transport = 'HTTP' + Ensure = 'Absent' + } + + $script:mockInstance.Modify($mockProperties) + + $script:methodRemoveInstanceCallCount | Should -Be 1 + } + } + } + + Context 'When the resource does exist but properties are incorrect' { + It 'Should call method SetInstance()' { + InModuleScope -ScriptBlock { + Set-StrictMode -Version 1.0 + + $script:mockInstance.Ensure = 'Absent' + + $mockProperties = @{ + Transport = 'HTTP' + Port = 5000 + } + + $script:mockInstance.Modify($mockProperties) + + $script:methodSetInstanceCallCount | Should -Be 1 + } + } } } } -Describe 'WSManListener\Modify()' -Tag 'HiddenMember' { - Context 'When the system is not in the desired state' { +Describe 'WSManListener\NewInstance()' -Tag 'HiddenMember' { + BeforeAll { + Mock -CommandName New-WSManInstance + } + + Context 'When creating a HTTP Transport' { + BeforeAll { + InModuleScope -ScriptBlock { + Set-StrictMode -Version 1.0 + + $script:mockInstance = [WSManListener] @{ + Transport = 'HTTP' + Port = 5985 + Address = '*' + Ensure = 'Present' + } + } + } + + It 'Should call the correct mock' { + InModuleScope -ScriptBlock { + Set-StrictMode -Version 1.0 + + $script:mockInstance.NewInstance() + } + + Should -Invoke -CommandName New-WSManInstance -Exactly -Times 1 -Scope It + } + } + + Context 'When creating a HTTPS Transport' { BeforeAll { + Mock -CommandName Get-DscProperty + } + + BeforeEach { InModuleScope -ScriptBlock { Set-StrictMode -Version 1.0 + $script:mockInstance = [WSManListener] @{ + Transport = 'HTTPS' + Port = 5986 + Address = '*' + Ensure = 'Present' + } + } + } + Context 'When the certificate thumbprint exists' { + BeforeAll { + Mock -CommandName Find-Certificate -MockWith { + @{ Thumbprint = '74FA31ADEA7FDD5333CED10910BFA6F665A1F2FC' } + } + } + + Context 'When the hostname is provided' { + It 'Should call the correct mocks' { + InModuleScope -ScriptBlock { + Set-StrictMode -Version 1.0 + + $script:mockInstance.HostName = 'somehost' + + $script:mockInstance.NewInstance() + } + + Should -Invoke -CommandName Get-DscProperty -Exactly -Times 1 -Scope It + Should -Invoke -CommandName Find-Certificate -Exactly -Times 1 -Scope It + Should -Invoke -CommandName New-WSManInstance -ParameterFilter { + $ValueSet.HostName -eq 'somehost' + } -Exactly -Times 1 -Scope It + } + } + + Context 'When the hostname is not provided' { + It 'Should call the correct mock' { + InModuleScope -ScriptBlock { + Set-StrictMode -Version 1.0 + + $script:mockInstance.NewInstance() + } + + Should -Invoke -CommandName Get-DscProperty -Exactly -Times 1 -Scope It + Should -Invoke -CommandName Find-Certificate -Exactly -Times 1 -Scope It + Should -Invoke -CommandName New-WSManInstance -ParameterFilter { + $ValueSet.HostName -eq [System.Net.Dns]::GetHostByName($env:COMPUTERNAME).Hostname + } -Exactly -Times 1 -Scope It + } } } + + Context 'When the certificate thumbprint does not exist' { + BeforeAll { + Mock -CommandName Find-Certificate + } + + It 'Should throw the correct exception' { + InModuleScope -ScriptBlock { + Set-StrictMode -Version 1.0 + + $mockErrorMessage = Get-InvalidArgumentRecord -Message ( + $script:mockInstance.localizedData.ListenerCreateFailNoCertError -f $script:mockInstance.Transport, $script:mockInstance.Port + ) -Argument 'Issuer' + + { $script:mockInstance.NewInstance() } | Should -Throw -ExpectedMessage $mockErrorMessage.Exception.Message + } + + Should -Invoke -CommandName New-WSManInstance -Exactly -Times 0 -Scope It + Should -Invoke -CommandName Get-DscProperty -Exactly -Times 1 -Scope It + Should -Invoke -CommandName Find-Certificate -Exactly -Times 1 -Scope It + } + } + } +} + +Describe 'WSManListener\RemoveInstance()' -Tag 'HiddenMember' { + BeforeAll { + InModuleScope -ScriptBlock { + Set-StrictMode -Version 1.0 + + $script:mockInstance = [WSManListener] @{ + Transport = 'HTTPS' + Port = 5986 + Address = '*' + Ensure = 'Present' + } + } + + Mock -CommandName Remove-WSManInstance + } + + It 'Should call the correct mock' { + InModuleScope -ScriptBlock { + Set-StrictMode -Version 1.0 + + $script:mockInstance.RemoveInstance() + } + + Should -Invoke -CommandName Remove-WSManInstance -Exactly -Times 1 -Scope It } } + +#Describe 'WSManListener\SetInstance()' -Tag 'HiddenMember' {} From 2cd02e22a82db7ae10487d6dfa1a356cbf3c85c4 Mon Sep 17 00:00:00 2001 From: Daniel Hughes Date: Fri, 27 Sep 2024 17:16:24 +0100 Subject: [PATCH 093/134] Update syntax error --- tests/Unit/Classes/WSManListener.Tests.ps1 | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/tests/Unit/Classes/WSManListener.Tests.ps1 b/tests/Unit/Classes/WSManListener.Tests.ps1 index f45123a..84c3a6e 100644 --- a/tests/Unit/Classes/WSManListener.Tests.ps1 +++ b/tests/Unit/Classes/WSManListener.Tests.ps1 @@ -782,13 +782,11 @@ Describe 'WSManListener\Modify()' -Tag 'HiddenMember' { # Mock method NewInstance which is called by the case method Modify(). Add-Member -Force -MemberType 'ScriptMethod' -Name 'NewInstance' -Value { $script:methodNewInstanceCallCount += 1 - } -PassThru - | + } -PassThru | # Mock method RemoveInstance which is called by the case method Modify(). Add-Member -Force -MemberType 'ScriptMethod' -Name 'RemoveInstance' -Value { $script:methodRemoveInstanceCallCount += 1 - } -PassThru - | + } -PassThru | # Mock method SetInstance which is called by the case method Modify(). Add-Member -Force -MemberType 'ScriptMethod' -Name 'SetInstance' -Value { $script:methodSetInstanceCallCount += 1 @@ -897,7 +895,7 @@ Describe 'WSManListener\NewInstance()' -Tag 'HiddenMember' { BeforeAll { Mock -CommandName Get-DscProperty } - + BeforeEach { InModuleScope -ScriptBlock { Set-StrictMode -Version 1.0 From e1dfbb2080716ab002d4994196e932de48d56bbd Mon Sep 17 00:00:00 2001 From: Daniel Hughes Date: Fri, 27 Sep 2024 17:35:19 +0100 Subject: [PATCH 094/134] Remove empty values --- source/Classes/020.WSManListener.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/Classes/020.WSManListener.ps1 b/source/Classes/020.WSManListener.ps1 index 2b9c3a6..15a23c2 100644 --- a/source/Classes/020.WSManListener.ps1 +++ b/source/Classes/020.WSManListener.ps1 @@ -243,7 +243,7 @@ class WSManListener : ResourceBase if ($this.Transport -eq [WSManTransport]::HTTPS) { - $findCertificateParams = $this | Get-DscProperty -Attribute @('Optional') -ExcludeName @('Port', 'Address') + $findCertificateParams = $this | Get-DscProperty -Attribute @('Optional') -ExcludeName @('Port', 'Address') -HasValue $certificate = Find-Certificate @findCertificateParams [System.String] $thumbprint = $certificate.Thumbprint From 8f2a0dccb560e9ab65898cff9670dfe519885f2f Mon Sep 17 00:00:00 2001 From: Daniel Hughes Date: Thu, 24 Oct 2024 15:44:47 +0100 Subject: [PATCH 095/134] Use Transport Enum --- source/Private/Get-DefaultPort.ps1 | 3 +-- source/Private/Get-Listener.ps1 | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/source/Private/Get-DefaultPort.ps1 b/source/Private/Get-DefaultPort.ps1 index 711b38f..50a498d 100644 --- a/source/Private/Get-DefaultPort.ps1 +++ b/source/Private/Get-DefaultPort.ps1 @@ -15,8 +15,7 @@ function Get-DefaultPort param ( [Parameter(Mandatory = $true)] - [ValidateSet('HTTP', 'HTTPS')] - [System.String] + [WSManTransport] $Transport, [Parameter()] diff --git a/source/Private/Get-Listener.ps1 b/source/Private/Get-Listener.ps1 index 380d856..172d85e 100644 --- a/source/Private/Get-Listener.ps1 +++ b/source/Private/Get-Listener.ps1 @@ -12,8 +12,7 @@ function Get-Listener param ( [Parameter(Mandatory = $true)] - [ValidateSet('HTTP', 'HTTPS')] - [System.String] + [WSManTransport] $Transport ) From c51495d3d317a76b4113983a500462b285d1f9ed Mon Sep 17 00:00:00 2001 From: Daniel Hughes Date: Thu, 24 Oct 2024 15:45:00 +0100 Subject: [PATCH 096/134] Remove build.psd1 --- source/Build.psd1 | 5 ----- 1 file changed, 5 deletions(-) delete mode 100644 source/Build.psd1 diff --git a/source/Build.psd1 b/source/Build.psd1 deleted file mode 100644 index 48730bf..0000000 --- a/source/Build.psd1 +++ /dev/null @@ -1,5 +0,0 @@ -@{ - Path = 'WSManDsc.psd1' -} -# Waiting for ModuleBuilder to do away with this file -# when all parameters are provided to the function From 6b6d5b0ac1b68a1bd70767a90f05a4be3c49c4d7 Mon Sep 17 00:00:00 2001 From: Daniel Hughes Date: Thu, 24 Oct 2024 15:45:35 +0100 Subject: [PATCH 097/134] Remove backtick --- source/Private/Find-Certificate.ps1 | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/source/Private/Find-Certificate.ps1 b/source/Private/Find-Certificate.ps1 index 22a7c8a..ba8cb9e 100644 --- a/source/Private/Find-Certificate.ps1 +++ b/source/Private/Find-Certificate.ps1 @@ -122,8 +122,7 @@ function Find-Certificate 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 + ($_.Extensions.EnhancedKeyUsages.FriendlyName -contains 'Server Authentication') -and ($_.Issuer -eq $Issuer) -and ($Hostname -in $_.DNSNameList.Unicode) -and ($_.Subject -eq $Subject) From 3f673c57c16c901ef6916dcac2f85ed2ca170574 Mon Sep 17 00:00:00 2001 From: Daniel Hughes Date: Thu, 24 Oct 2024 16:13:51 +0100 Subject: [PATCH 098/134] Use Enum in compare --- source/Private/Get-DefaultPort.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/Private/Get-DefaultPort.ps1 b/source/Private/Get-DefaultPort.ps1 index 50a498d..15073f2 100644 --- a/source/Private/Get-DefaultPort.ps1 +++ b/source/Private/Get-DefaultPort.ps1 @@ -28,7 +28,7 @@ function Get-DefaultPort if (-not $Port) { # Set the default port because none was provided - if ($Transport -eq 'HTTP') + if ($Transport -eq [WSManTransport]::HTTP) { $Port = 5985 } From 30c7a027d43b3e2853a0edc93a2eb4cf39d52bf0 Mon Sep 17 00:00:00 2001 From: Daniel Hughes Date: Thu, 24 Oct 2024 16:14:18 +0100 Subject: [PATCH 099/134] Remove unused localized data --- source/en-US/WSManListener.strings.psd1 | 1 - 1 file changed, 1 deletion(-) diff --git a/source/en-US/WSManListener.strings.psd1 b/source/en-US/WSManListener.strings.psd1 index 48597d1..3120c46 100644 --- a/source/en-US/WSManListener.strings.psd1 +++ b/source/en-US/WSManListener.strings.psd1 @@ -14,5 +14,4 @@ ConvertFrom-StringData @' ListenerExistsRemoveMessage = Removing {0} Listener on port {1} (WSML0001). ListenerCreateFailNoCertError = Failed to create {0} Listener on port {1} because an applicable certificate could not be found (WSM0002). CreatingListenerMessage = Creating {0} Listener on port {1} (WSML0003). - ModifyingListenerMessage = Modifying {0} Listener on port {1} (WSML0004). '@ From 6d3c4e99a609268fea92d58a39df7a7c1f01ad45 Mon Sep 17 00:00:00 2001 From: Daniel Hughes Date: Thu, 24 Oct 2024 16:14:39 +0100 Subject: [PATCH 100/134] Revert to previous logic --- source/Classes/020.WSManListener.ps1 | 37 ++++++---------------- tests/Unit/Classes/WSManListener.Tests.ps1 | 7 ++-- 2 files changed, 12 insertions(+), 32 deletions(-) diff --git a/source/Classes/020.WSManListener.ps1 b/source/Classes/020.WSManListener.ps1 index 15a23c2..d807879 100644 --- a/source/Classes/020.WSManListener.ps1 +++ b/source/Classes/020.WSManListener.ps1 @@ -189,30 +189,21 @@ class WSManListener : ResourceBase #> hidden [void] Modify([System.Collections.Hashtable] $properties) { - #Write-Verbose ('$properties.ContainsKey(''Ensure'') = {0}' -f $properties.ContainsKey('Ensure')) - #Write-Verbose ('$properties.Ensure = {0}' -f $properties.Ensure) - #Write-Verbose ('$this.Ensure = {0}' -f $this.Ensure) - 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 - Write-Verbose -Message ($this.localizedData.ListenerExistsRemoveMessage -f $this.Transport, $this.Port) - $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 - Write-Verbose -Message ($this.localizedData.CreatingListenerMessage -f $this.Transport, $this.Port) - $this.NewInstance() } else { # Resource exists but one or more properties are not in the desired state - Write-Verbose -Message ($this.localizedData.ModifyingListenerMessage -f $this.Transport, $this.Port) - - $this.SetInstance($properties) + $this.RemoveInstance() + $this.NewInstance() } } @@ -227,10 +218,14 @@ class WSManListener : ResourceBase a value. #> hidden [void] AssertProperties([System.Collections.Hashtable] $properties) - {} + { + #TODO: if HTTPS, CertificateThumbprint and Issuer, SubjectFormat, MatchAlternate, BaseDN are mutually exclusive + } hidden [void] NewInstance() { + Write-Verbose -Message ($this.localizedData.CreatingListenerMessage -f $this.Transport, $this.Port) + $selectorSet = @{ Transport = $this.Transport Address = $this.Address @@ -275,27 +270,13 @@ class WSManListener : ResourceBase hidden [void] RemoveInstance() { - $selectorSet = @{ - Transport = $this.Transport - Address = $this.Address - } - - Remove-WSManInstance -ResourceURI 'winrm/config/Listener' -SelectorSet $selectorSet - } + Write-Verbose -Message ($this.localizedData.ListenerExistsRemoveMessage -f $this.Transport, $this.Port) - hidden [void] SetInstance([System.Collections.Hashtable] $properties) - { $selectorSet = @{ Transport = $this.Transport Address = $this.Address } - $valueSet = @{} - - foreach ($property in $properties) { - $valueSet.$property.Key = $property.Value - } - - Set-WSManInstance -ResourceURI 'winrm/config/Listener' -SelectorSet $selectorSet -ValueSet $valueSet + Remove-WSManInstance -ResourceURI 'winrm/config/Listener' -SelectorSet $selectorSet } } diff --git a/tests/Unit/Classes/WSManListener.Tests.ps1 b/tests/Unit/Classes/WSManListener.Tests.ps1 index 84c3a6e..d5f6954 100644 --- a/tests/Unit/Classes/WSManListener.Tests.ps1 +++ b/tests/Unit/Classes/WSManListener.Tests.ps1 @@ -841,7 +841,7 @@ Describe 'WSManListener\Modify()' -Tag 'HiddenMember' { } Context 'When the resource does exist but properties are incorrect' { - It 'Should call method SetInstance()' { + It 'Should call method RemoveInstance() and NewInstance()' { InModuleScope -ScriptBlock { Set-StrictMode -Version 1.0 @@ -854,7 +854,8 @@ Describe 'WSManListener\Modify()' -Tag 'HiddenMember' { $script:mockInstance.Modify($mockProperties) - $script:methodSetInstanceCallCount | Should -Be 1 + $script:methodRemoveInstanceCallCount | Should -Be 1 + $script:methodNewInstanceCallCount | Should -Be 1 } } } @@ -1001,5 +1002,3 @@ Describe 'WSManListener\RemoveInstance()' -Tag 'HiddenMember' { Should -Invoke -CommandName Remove-WSManInstance -Exactly -Times 1 -Scope It } } - -#Describe 'WSManListener\SetInstance()' -Tag 'HiddenMember' {} From ee0741b1caf7e2feb7940b96e6a031c9f83800e4 Mon Sep 17 00:00:00 2001 From: Daniel Hughes Date: Thu, 24 Oct 2024 16:31:39 +0100 Subject: [PATCH 101/134] Use correct DocGenerator Key --- build.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.yaml b/build.yaml index 4dd1f0f..33df85f 100644 --- a/build.yaml +++ b/build.yaml @@ -170,7 +170,7 @@ DscResource.DocGenerator: - '\*(.+?)\*' # Match Italic (asterisk) Publish_GitHub_Wiki_Content: Debug: false - Generate_Wiki_Content: + Generate_Markdown_For_DSC_Resources: MofResourceMetadata: Type: MofResource Category: Resources From ea71a890d632c2eade9fff9310f1271f3ef8f93a Mon Sep 17 00:00:00 2001 From: Daniel Hughes Date: Thu, 24 Oct 2024 16:43:48 +0100 Subject: [PATCH 102/134] Use SubjectFormatEnum --- source/Private/Find-Certificate.ps1 | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/source/Private/Find-Certificate.ps1 b/source/Private/Find-Certificate.ps1 index ba8cb9e..0cdbfb8 100644 --- a/source/Private/Find-Certificate.ps1 +++ b/source/Private/Find-Certificate.ps1 @@ -32,9 +32,8 @@ function Find-Certificate $Issuer, [Parameter()] - [ValidateSet('Both', 'FQDNOnly', 'NameOnly')] - [System.String] - $SubjectFormat = 'Both', + [WSManSubjectFormat] + $SubjectFormat = [WSManSubjectFormat]::Both, [Parameter()] [System.Boolean] @@ -64,7 +63,7 @@ function Find-Certificate else { # First try and find a certificate that is used to the FQDN of the machine - if ($SubjectFormat -in 'Both', 'FQDNOnly') + if ($SubjectFormat -in [WSManSubjectFormat]::Both, [WSManSubjectFormat]::FQDNOnly) { # Lookup the certificate using the FQDN of the machine if ([System.String]::IsNullOrEmpty($Hostname)) @@ -105,7 +104,7 @@ function Find-Certificate } # if } - if (-not $certificate -and ($SubjectFormat -in 'Both', 'NameOnly')) + 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 = $ENV:ComputerName From b230808642e39c1bf816e16d8c95ece533d77d49 Mon Sep 17 00:00:00 2001 From: Daniel Hughes Date: Thu, 24 Oct 2024 17:11:15 +0100 Subject: [PATCH 103/134] Set subjectFormat back to previous default --- source/Classes/020.WSManListener.ps1 | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/source/Classes/020.WSManListener.ps1 b/source/Classes/020.WSManListener.ps1 index d807879..8a13f07 100644 --- a/source/Classes/020.WSManListener.ps1 +++ b/source/Classes/020.WSManListener.ps1 @@ -111,14 +111,13 @@ class WSManListener : ResourceBase 'SubjectFormat' 'MatchAlternate' 'BaseDN' - ) - # # Set subject format to default value - # if (-not $this.SubjectFormat) - # { - # $this.SubjectFormat = 'Both' - # } + # Set subject format to default value + if (-not $this.SubjectFormat) + { + $this.SubjectFormat = [WSManSubjectFormat]::Both + } } [WSManListener] Get() From b1b9ecbe637bea5617e779763944c10092d6f6ea Mon Sep 17 00:00:00 2001 From: Daniel Hughes Date: Thu, 24 Oct 2024 17:14:51 +0100 Subject: [PATCH 104/134] Match Mof resource defaults --- source/Classes/020.WSManListener.ps1 | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/source/Classes/020.WSManListener.ps1 b/source/Classes/020.WSManListener.ps1 index 8a13f07..2d5ac92 100644 --- a/source/Classes/020.WSManListener.ps1 +++ b/source/Classes/020.WSManListener.ps1 @@ -73,7 +73,7 @@ class WSManListener : ResourceBase [DscProperty()] [WSManSubjectFormat] - $SubjectFormat + $SubjectFormat = [WSManSubjectFormat]::Both [DscProperty()] [Nullable[System.Boolean]] @@ -112,12 +112,6 @@ class WSManListener : ResourceBase 'MatchAlternate' 'BaseDN' ) - - # Set subject format to default value - if (-not $this.SubjectFormat) - { - $this.SubjectFormat = [WSManSubjectFormat]::Both - } } [WSManListener] Get() From c7feeb584af41a9eea3f04a5b9f27a7d6d6d6101 Mon Sep 17 00:00:00 2001 From: Daniel Hughes Date: Tue, 12 Nov 2024 13:35:20 +0000 Subject: [PATCH 105/134] Update formatting --- source/Private/Find-Certificate.ps1 | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/source/Private/Find-Certificate.ps1 b/source/Private/Find-Certificate.ps1 index 0cdbfb8..096875c 100644 --- a/source/Private/Find-Certificate.ps1 +++ b/source/Private/Find-Certificate.ps1 @@ -83,8 +83,7 @@ function Find-Certificate 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 + ($_.Extensions.EnhancedKeyUsages.FriendlyName -contains 'Server Authentication') -and ($_.Issuer -eq $Issuer) -and ($Hostname -in $_.DNSNameList.Unicode) -and ($_.Subject -eq $Subject) @@ -96,8 +95,7 @@ function Find-Certificate 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 + ($_.Extensions.EnhancedKeyUsages.FriendlyName -contains 'Server Authentication') -and ($_.Issuer -eq $Issuer) -and ($_.Subject -eq $Subject) } | Select-Object -First 1 @@ -133,9 +131,9 @@ function Find-Certificate 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 + ($_.Extensions.EnhancedKeyUsages.FriendlyName -contains 'Server Authentication') -and + $_.Issuer -eq $Issuer -and + $_.Subject -eq $Subject } | Select-Object -First 1 } # if } # if From a7950d4f49780605baca1a48cbaaa48cce0050d0 Mon Sep 17 00:00:00 2001 From: Daniel Hughes Date: Tue, 12 Nov 2024 13:35:55 +0000 Subject: [PATCH 106/134] Update string as port is not available --- source/en-US/WSManListener.strings.psd1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/en-US/WSManListener.strings.psd1 b/source/en-US/WSManListener.strings.psd1 index 3120c46..80e652d 100644 --- a/source/en-US/WSManListener.strings.psd1 +++ b/source/en-US/WSManListener.strings.psd1 @@ -11,7 +11,7 @@ ConvertFrom-StringData @' # None ## Strings directly used by the derived class WSManListener. - ListenerExistsRemoveMessage = Removing {0} Listener on port {1} (WSML0001). + ListenerExistsRemoveMessage = Removing {0} Listener on address {1} (WSML0001). ListenerCreateFailNoCertError = Failed to create {0} Listener on port {1} because an applicable certificate could not be found (WSM0002). CreatingListenerMessage = Creating {0} Listener on port {1} (WSML0003). '@ From 6ab891ce1a527de61ead25c4ff38281f4614b0f0 Mon Sep 17 00:00:00 2001 From: Daniel Hughes Date: Tue, 12 Nov 2024 13:37:22 +0000 Subject: [PATCH 107/134] Update resource from testing --- source/Classes/020.WSManListener.ps1 | 49 +++++++++++++++++----- tests/Unit/Classes/WSManListener.Tests.ps1 | 20 ++++++--- 2 files changed, 53 insertions(+), 16 deletions(-) diff --git a/source/Classes/020.WSManListener.ps1 b/source/Classes/020.WSManListener.ps1 index 2d5ac92..d1a2942 100644 --- a/source/Classes/020.WSManListener.ps1 +++ b/source/Classes/020.WSManListener.ps1 @@ -3,6 +3,22 @@ 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. @@ -139,6 +155,7 @@ class WSManListener : ResourceBase $this.Address = '*' } + $state = @{} $getCurrentStateResult = Get-Listener @getParameters @@ -161,11 +178,6 @@ class WSManListener : ResourceBase $state.Issuer = (Find-Certificate -CertificateThumbprint $getCurrentStateResult.CertificateThumbprint).Issuer } } - else - { - $state = @{} - } - return $state } @@ -192,7 +204,7 @@ class WSManListener : ResourceBase # Ensure was not in the desired state so the resource should be created $this.NewInstance() } - else + elseif ($this.Ensure -eq [Ensure]::Present) { # Resource exists but one or more properties are not in the desired state $this.RemoveInstance() @@ -212,7 +224,19 @@ class WSManListener : ResourceBase #> hidden [void] AssertProperties([System.Collections.Hashtable] $properties) { - #TODO: if HTTPS, CertificateThumbprint and Issuer, SubjectFormat, MatchAlternate, BaseDN are mutually exclusive + $assertBoundParameterParameters = @{ + BoundParameterList = $properties + MutuallyExclusiveList1 = @( + 'Issuer' + 'BaseDN' + ) + MutuallyExclusiveList2 = @( + 'CertificateThumbprint' + 'Hostname' + ) + } + + Assert-BoundParameter @assertBoundParameterParameters } hidden [void] NewInstance() @@ -263,11 +287,16 @@ class WSManListener : ResourceBase hidden [void] RemoveInstance() { - Write-Verbose -Message ($this.localizedData.ListenerExistsRemoveMessage -f $this.Transport, $this.Port) + Write-Verbose -Message ($this.localizedData.ListenerExistsRemoveMessage -f $this.Transport, $this.Address) $selectorSet = @{ - Transport = $this.Transport - Address = $this.Address + Transport = [System.String] $this.Transport + Address = '*' + } + + if ($this.Address) + { + $selectorSet.Address = $this.Address } Remove-WSManInstance -ResourceURI 'winrm/config/Listener' -SelectorSet $selectorSet diff --git a/tests/Unit/Classes/WSManListener.Tests.ps1 b/tests/Unit/Classes/WSManListener.Tests.ps1 index d5f6954..53ff61e 100644 --- a/tests/Unit/Classes/WSManListener.Tests.ps1 +++ b/tests/Unit/Classes/WSManListener.Tests.ps1 @@ -786,10 +786,6 @@ Describe 'WSManListener\Modify()' -Tag 'HiddenMember' { # Mock method RemoveInstance which is called by the case method Modify(). Add-Member -Force -MemberType 'ScriptMethod' -Name 'RemoveInstance' -Value { $script:methodRemoveInstanceCallCount += 1 - } -PassThru | - # Mock method SetInstance which is called by the case method Modify(). - Add-Member -Force -MemberType 'ScriptMethod' -Name 'SetInstance' -Value { - $script:methodSetInstanceCallCount += 1 } -PassThru } } @@ -800,7 +796,6 @@ Describe 'WSManListener\Modify()' -Tag 'HiddenMember' { $script:methodNewInstanceCallCount = 0 $script:methodRemoveInstanceCallCount = 0 - $script:methodSetInstanceCallCount = 0 } } @@ -845,7 +840,7 @@ Describe 'WSManListener\Modify()' -Tag 'HiddenMember' { InModuleScope -ScriptBlock { Set-StrictMode -Version 1.0 - $script:mockInstance.Ensure = 'Absent' + $script:mockInstance.Ensure = 'Present' $mockProperties = @{ Transport = 'HTTP' @@ -1002,3 +997,16 @@ Describe 'WSManListener\RemoveInstance()' -Tag 'HiddenMember' { Should -Invoke -CommandName Remove-WSManInstance -Exactly -Times 1 -Scope It } } + +# Describe 'WSManListener\AssertProperties()' -Tag 'AssertProperties' { +# Context 'When passing mutually exclusive parameters' { +# Context 'When passing ' +# BeforeAll { +# InModuleScope -ScriptBlock { +# $script:mockInstance = [WSManListener] @{ + +# } +# } +# } +# } +# } From 7428d81507422c35b2d7518c96a51929e5eb12ea Mon Sep 17 00:00:00 2001 From: Dan Hughes <2237515+dan-hughes@users.noreply.github.com> Date: Sun, 17 Nov 2024 16:58:48 +0000 Subject: [PATCH 108/134] Update BeforeDiscovery stream redirection --- tests/Integration/DSC_WSManConfig.Integration.Tests.ps1 | 2 +- tests/Integration/DSC_WSManListener.Integration.Tests.ps1 | 2 +- tests/Integration/DSC_WSManServiceConfig.Integration.Tests.ps1 | 2 +- tests/Unit/Classes/WSManListener.Tests.ps1 | 2 +- tests/Unit/Classes/WSManReason.Tests.ps1 | 2 +- tests/Unit/DSC_WSManConfig.Tests.ps1 | 2 +- tests/Unit/DSC_WSManServiceConfig.Tests.ps1 | 2 +- tests/Unit/Private/Find-Certificate.Tests.ps1 | 2 +- tests/Unit/Private/Get-DefaultPort.Tests.ps1 | 2 +- tests/Unit/Private/Get-Listener.Tests.ps1 | 2 +- 10 files changed, 10 insertions(+), 10 deletions(-) diff --git a/tests/Integration/DSC_WSManConfig.Integration.Tests.ps1 b/tests/Integration/DSC_WSManConfig.Integration.Tests.ps1 index e02554c..efeff88 100644 --- a/tests/Integration/DSC_WSManConfig.Integration.Tests.ps1 +++ b/tests/Integration/DSC_WSManConfig.Integration.Tests.ps1 @@ -18,7 +18,7 @@ BeforeDiscovery { if (-not (Get-Module -Name 'DscResource.Test' -ListAvailable)) { # Redirect all streams to $null, except the error stream (stream 2) - & "$PSScriptRoot/../../build.ps1" -Tasks 'noop' 2>&1 4>&1 5>&1 6>&1 > $null + & "$PSScriptRoot/../../build.ps1" -Tasks 'noop' 3>&1 4>&1 5>&1 6>&1 > $null } # This will throw an error if the dependencies have not been resolved. diff --git a/tests/Integration/DSC_WSManListener.Integration.Tests.ps1 b/tests/Integration/DSC_WSManListener.Integration.Tests.ps1 index f1fa5d8..c460702 100644 --- a/tests/Integration/DSC_WSManListener.Integration.Tests.ps1 +++ b/tests/Integration/DSC_WSManListener.Integration.Tests.ps1 @@ -18,7 +18,7 @@ BeforeDiscovery { if (-not (Get-Module -Name 'DscResource.Test' -ListAvailable)) { # Redirect all streams to $null, except the error stream (stream 2) - & "$PSScriptRoot/../../build.ps1" -Tasks 'noop' 2>&1 4>&1 5>&1 6>&1 > $null + & "$PSScriptRoot/../../build.ps1" -Tasks 'noop' 3>&1 4>&1 5>&1 6>&1 > $null } # This will throw an error if the dependencies have not been resolved. diff --git a/tests/Integration/DSC_WSManServiceConfig.Integration.Tests.ps1 b/tests/Integration/DSC_WSManServiceConfig.Integration.Tests.ps1 index 61f1a62..4397300 100644 --- a/tests/Integration/DSC_WSManServiceConfig.Integration.Tests.ps1 +++ b/tests/Integration/DSC_WSManServiceConfig.Integration.Tests.ps1 @@ -18,7 +18,7 @@ BeforeDiscovery { if (-not (Get-Module -Name 'DscResource.Test' -ListAvailable)) { # Redirect all streams to $null, except the error stream (stream 2) - & "$PSScriptRoot/../../build.ps1" -Tasks 'noop' 2>&1 4>&1 5>&1 6>&1 > $null + & "$PSScriptRoot/../../build.ps1" -Tasks 'noop' 3>&1 4>&1 5>&1 6>&1 > $null } # This will throw an error if the dependencies have not been resolved. diff --git a/tests/Unit/Classes/WSManListener.Tests.ps1 b/tests/Unit/Classes/WSManListener.Tests.ps1 index 53ff61e..f671c54 100644 --- a/tests/Unit/Classes/WSManListener.Tests.ps1 +++ b/tests/Unit/Classes/WSManListener.Tests.ps1 @@ -16,7 +16,7 @@ BeforeDiscovery { if (-not (Get-Module -Name 'DscResource.Test' -ListAvailable)) { # Redirect all streams to $null, except the error stream (stream 2) - & "$PSScriptRoot/../../../build.ps1" -Tasks 'noop' 2>&1 4>&1 5>&1 6>&1 > $null + & "$PSScriptRoot/../../../build.ps1" -Tasks 'noop' 3>&1 4>&1 5>&1 6>&1 > $null } # If the dependencies has not been resolved, this will throw an error. diff --git a/tests/Unit/Classes/WSManReason.Tests.ps1 b/tests/Unit/Classes/WSManReason.Tests.ps1 index 878a7d5..46db53d 100644 --- a/tests/Unit/Classes/WSManReason.Tests.ps1 +++ b/tests/Unit/Classes/WSManReason.Tests.ps1 @@ -10,7 +10,7 @@ BeforeDiscovery { if (-not (Get-Module -Name 'DscResource.Test' -ListAvailable)) { # Redirect all streams to $null, except the error stream (stream 2) - & "$PSScriptRoot/../../../build.ps1" -Tasks 'noop' 2>&1 4>&1 5>&1 6>&1 > $null + & "$PSScriptRoot/../../../build.ps1" -Tasks 'noop' 3>&1 4>&1 5>&1 6>&1 > $null } # If the dependencies has not been resolved, this will throw an error. diff --git a/tests/Unit/DSC_WSManConfig.Tests.ps1 b/tests/Unit/DSC_WSManConfig.Tests.ps1 index b7b2c6c..c5ba2bb 100644 --- a/tests/Unit/DSC_WSManConfig.Tests.ps1 +++ b/tests/Unit/DSC_WSManConfig.Tests.ps1 @@ -18,7 +18,7 @@ BeforeDiscovery { if (-not (Get-Module -Name 'DscResource.Test' -ListAvailable)) { # Redirect all streams to $null, except the error stream (stream 2) - & "$PSScriptRoot/../../build.ps1" -Tasks 'noop' 2>&1 4>&1 5>&1 6>&1 > $null + & "$PSScriptRoot/../../build.ps1" -Tasks 'noop' 3>&1 4>&1 5>&1 6>&1 > $null } # This will throw an error if the dependencies have not been resolved. diff --git a/tests/Unit/DSC_WSManServiceConfig.Tests.ps1 b/tests/Unit/DSC_WSManServiceConfig.Tests.ps1 index 86ff539..71a0abe 100644 --- a/tests/Unit/DSC_WSManServiceConfig.Tests.ps1 +++ b/tests/Unit/DSC_WSManServiceConfig.Tests.ps1 @@ -18,7 +18,7 @@ BeforeDiscovery { if (-not (Get-Module -Name 'DscResource.Test' -ListAvailable)) { # Redirect all streams to $null, except the error stream (stream 2) - & "$PSScriptRoot/../../build.ps1" -Tasks 'noop' 2>&1 4>&1 5>&1 6>&1 > $null + & "$PSScriptRoot/../../build.ps1" -Tasks 'noop' 3>&1 4>&1 5>&1 6>&1 > $null } # This will throw an error if the dependencies have not been resolved. diff --git a/tests/Unit/Private/Find-Certificate.Tests.ps1 b/tests/Unit/Private/Find-Certificate.Tests.ps1 index 25aaa03..243c0a9 100644 --- a/tests/Unit/Private/Find-Certificate.Tests.ps1 +++ b/tests/Unit/Private/Find-Certificate.Tests.ps1 @@ -10,7 +10,7 @@ BeforeDiscovery { if (-not (Get-Module -Name 'DscResource.Test' -ListAvailable)) { # Redirect all streams to $null, except the error stream (stream 2) - & "$PSScriptRoot/../../../build.ps1" -Tasks 'noop' 2>&1 4>&1 5>&1 6>&1 > $null + & "$PSScriptRoot/../../../build.ps1" -Tasks 'noop' 3>&1 4>&1 5>&1 6>&1 > $null } # This will throw an error if the dependencies have not been resolved. diff --git a/tests/Unit/Private/Get-DefaultPort.Tests.ps1 b/tests/Unit/Private/Get-DefaultPort.Tests.ps1 index a73428b..ffb0a52 100644 --- a/tests/Unit/Private/Get-DefaultPort.Tests.ps1 +++ b/tests/Unit/Private/Get-DefaultPort.Tests.ps1 @@ -10,7 +10,7 @@ BeforeDiscovery { if (-not (Get-Module -Name 'DscResource.Test' -ListAvailable)) { # Redirect all streams to $null, except the error stream (stream 2) - & "$PSScriptRoot/../../../build.ps1" -Tasks 'noop' 2>&1 4>&1 5>&1 6>&1 > $null + & "$PSScriptRoot/../../../build.ps1" -Tasks 'noop' 3>&1 4>&1 5>&1 6>&1 > $null } # If the dependencies has not been resolved, this will throw an error. diff --git a/tests/Unit/Private/Get-Listener.Tests.ps1 b/tests/Unit/Private/Get-Listener.Tests.ps1 index 34ea025..b2cb079 100644 --- a/tests/Unit/Private/Get-Listener.Tests.ps1 +++ b/tests/Unit/Private/Get-Listener.Tests.ps1 @@ -10,7 +10,7 @@ BeforeDiscovery { if (-not (Get-Module -Name 'DscResource.Test' -ListAvailable)) { # Redirect all streams to $null, except the error stream (stream 2) - & "$PSScriptRoot/../../../build.ps1" -Tasks 'noop' 2>&1 4>&1 5>&1 6>&1 > $null + & "$PSScriptRoot/../../../build.ps1" -Tasks 'noop' 3>&1 4>&1 5>&1 6>&1 > $null } # If the dependencies has not been resolved, this will throw an error. From 5c86af984eadbbeee0727bc53df3a9d4cc7b0447 Mon Sep 17 00:00:00 2001 From: Dan Hughes <2237515+dan-hughes@users.noreply.github.com> Date: Sun, 17 Nov 2024 16:59:13 +0000 Subject: [PATCH 109/134] Add assert properties tests --- tests/Unit/Classes/WSManListener.Tests.ps1 | 46 ++++++++++++++++------ 1 file changed, 34 insertions(+), 12 deletions(-) diff --git a/tests/Unit/Classes/WSManListener.Tests.ps1 b/tests/Unit/Classes/WSManListener.Tests.ps1 index f671c54..f878fea 100644 --- a/tests/Unit/Classes/WSManListener.Tests.ps1 +++ b/tests/Unit/Classes/WSManListener.Tests.ps1 @@ -998,15 +998,37 @@ Describe 'WSManListener\RemoveInstance()' -Tag 'HiddenMember' { } } -# Describe 'WSManListener\AssertProperties()' -Tag 'AssertProperties' { -# Context 'When passing mutually exclusive parameters' { -# Context 'When passing ' -# BeforeAll { -# InModuleScope -ScriptBlock { -# $script:mockInstance = [WSManListener] @{ - -# } -# } -# } -# } -# } +Describe 'WSManListener\AssertProperties()' -Tag 'AssertProperties' { + BeforeAll { + InModuleScope -ScriptBlock { + $script:mockInstance = [WSManListener] @{} + } + } + Context 'When passing mutually exclusive parameters' { + Context 'When passing Issuer and Hostname' { + It 'Should throw the correct error' { + InModuleScope -ScriptBlock { + { + $mockInstance.AssertProperties(@{ + Issuer = 'SomeIssuer' + HostName = 'TheHostname' + }) + } | Should -Throw -ExpectedMessage '*DRC0010*' + } + } + } + + } + Context 'When passing BaseDN and CertificateThumbprint' { + It 'Should throw the correct error' { + InModuleScope -ScriptBlock { + { + $mockInstance.AssertProperties(@{ + BaseDN = 'SomeBaseDN' + CertificateThumbprint = 'certificateThumbprint' + }) + } | Should -Throw -ExpectedMessage '*DRC0010*' + } + } + } +} From 946daf147ae9d4ffac13e412f15d0d732a9173c2 Mon Sep 17 00:00:00 2001 From: Dan Hughes <2237515+dan-hughes@users.noreply.github.com> Date: Sun, 17 Nov 2024 17:13:44 +0000 Subject: [PATCH 110/134] Remove Export-ModuleMember --- source/DSCResources/DSC_WSManConfig/DSC_WSManConfig.psm1 | 2 -- .../DSC_WSManServiceConfig/DSC_WSManServiceConfig.psm1 | 2 -- 2 files changed, 4 deletions(-) diff --git a/source/DSCResources/DSC_WSManConfig/DSC_WSManConfig.psm1 b/source/DSCResources/DSC_WSManConfig/DSC_WSManConfig.psm1 index a86fd3f..ce54a24 100644 --- a/source/DSCResources/DSC_WSManConfig/DSC_WSManConfig.psm1 +++ b/source/DSCResources/DSC_WSManConfig/DSC_WSManConfig.psm1 @@ -191,5 +191,3 @@ function Test-TargetResource -ExcludeProperties @('IsSingleInstance') ` -Verbose:$VerbosePreference } # Test-TargetResource - -Export-ModuleMember -Function *-TargetResource diff --git a/source/DSCResources/DSC_WSManServiceConfig/DSC_WSManServiceConfig.psm1 b/source/DSCResources/DSC_WSManServiceConfig/DSC_WSManServiceConfig.psm1 index cda9f2c..8ea1bd3 100644 --- a/source/DSCResources/DSC_WSManServiceConfig/DSC_WSManServiceConfig.psm1 +++ b/source/DSCResources/DSC_WSManServiceConfig/DSC_WSManServiceConfig.psm1 @@ -343,5 +343,3 @@ function Test-TargetResource -ExcludeProperties @('IsSingleInstance') ` -Verbose:$VerbosePreference } # Test-TargetResource - -Export-ModuleMember -Function *-TargetResource From 11c9019008183ce8119349816dbb70342d7ff9f9 Mon Sep 17 00:00:00 2001 From: Dan Hughes <2237515+dan-hughes@users.noreply.github.com> Date: Sun, 17 Nov 2024 17:17:23 +0000 Subject: [PATCH 111/134] Update changelog --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 56794a7..a12eb2e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -61,9 +61,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - `DSC_WSManConfig` - Refactor `Test-TargetResource` to use `Test-DscParameterState`. - Remove unused strings. + - Removed Export-ModuleMember. - `DSC_WSManServiceConfig` - Refactor `Test-TargetResource` to use `Test-DscParameterState`. - Remove unused strings + - Removed Export-ModuleMember. - `DSC_WSManListener` - Converted to Class Resource - Extracted private functions to own files From fb08dd865f0f823bba17987d262a732a156a870d Mon Sep 17 00:00:00 2001 From: Dan Hughes <2237515+dan-hughes@users.noreply.github.com> Date: Sun, 17 Nov 2024 17:31:05 +0000 Subject: [PATCH 112/134] Fix newlines and add strictmode --- tests/Unit/Classes/WSManListener.Tests.ps1 | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/tests/Unit/Classes/WSManListener.Tests.ps1 b/tests/Unit/Classes/WSManListener.Tests.ps1 index f878fea..20351e1 100644 --- a/tests/Unit/Classes/WSManListener.Tests.ps1 +++ b/tests/Unit/Classes/WSManListener.Tests.ps1 @@ -1001,13 +1001,18 @@ Describe 'WSManListener\RemoveInstance()' -Tag 'HiddenMember' { Describe 'WSManListener\AssertProperties()' -Tag 'AssertProperties' { BeforeAll { InModuleScope -ScriptBlock { + Set-StrictMode -Version 1.0 + $script:mockInstance = [WSManListener] @{} } } + Context 'When passing mutually exclusive parameters' { Context 'When passing Issuer and Hostname' { It 'Should throw the correct error' { InModuleScope -ScriptBlock { + Set-StrictMode -Version 1.0 + { $mockInstance.AssertProperties(@{ Issuer = 'SomeIssuer' @@ -1017,11 +1022,13 @@ Describe 'WSManListener\AssertProperties()' -Tag 'AssertProperties' { } } } - } + Context 'When passing BaseDN and CertificateThumbprint' { It 'Should throw the correct error' { InModuleScope -ScriptBlock { + Set-StrictMode -Version 1.0 + { $mockInstance.AssertProperties(@{ BaseDN = 'SomeBaseDN' From f4cb0b3ea4a97e138ade4d4de19b7fbb4d3222a3 Mon Sep 17 00:00:00 2001 From: Daniel Hughes Date: Mon, 9 Dec 2024 10:22:05 +0000 Subject: [PATCH 113/134] Use correct error code prefix --- source/en-US/WSManListener.strings.psd1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/en-US/WSManListener.strings.psd1 b/source/en-US/WSManListener.strings.psd1 index 80e652d..8abb4c4 100644 --- a/source/en-US/WSManListener.strings.psd1 +++ b/source/en-US/WSManListener.strings.psd1 @@ -12,6 +12,6 @@ ConvertFrom-StringData @' ## Strings directly used by the derived class WSManListener. ListenerExistsRemoveMessage = Removing {0} Listener on address {1} (WSML0001). - ListenerCreateFailNoCertError = Failed to create {0} Listener on port {1} because an applicable certificate could not be found (WSM0002). + ListenerCreateFailNoCertError = Failed to create {0} Listener on port {1} because an applicable certificate could not be found (WSML0002). CreatingListenerMessage = Creating {0} Listener on port {1} (WSML0003). '@ From 1ec4a55a9e6e6f15458d223989938182744c3345 Mon Sep 17 00:00:00 2001 From: Dan Hughes <2237515+dan-hughes@users.noreply.github.com> Date: Sun, 12 Jan 2025 14:36:27 +0000 Subject: [PATCH 114/134] Use Get-ComputerName from DscResource.Common --- source/Classes/020.WSManListener.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/Classes/020.WSManListener.ps1 b/source/Classes/020.WSManListener.ps1 index d1a2942..637a709 100644 --- a/source/Classes/020.WSManListener.ps1 +++ b/source/Classes/020.WSManListener.ps1 @@ -266,7 +266,7 @@ class WSManListener : ResourceBase if ([System.String]::IsNullOrEmpty($this.Hostname)) { - $valueSet.HostName = [System.Net.Dns]::GetHostByName($env:COMPUTERNAME).Hostname + $valueSet.HostName = Get-ComputerName } else { From b55001d438a50000e97843fc563f0cb8f6b722b3 Mon Sep 17 00:00:00 2001 From: Dan Hughes <2237515+dan-hughes@users.noreply.github.com> Date: Sun, 12 Jan 2025 14:38:07 +0000 Subject: [PATCH 115/134] Add SubjectFormat and MatchAlternate to bound parameter checks --- source/Classes/020.WSManListener.ps1 | 2 ++ 1 file changed, 2 insertions(+) diff --git a/source/Classes/020.WSManListener.ps1 b/source/Classes/020.WSManListener.ps1 index 637a709..d640666 100644 --- a/source/Classes/020.WSManListener.ps1 +++ b/source/Classes/020.WSManListener.ps1 @@ -229,6 +229,8 @@ class WSManListener : ResourceBase MutuallyExclusiveList1 = @( 'Issuer' 'BaseDN' + 'SubjectFormat' + 'MatchAlternate' ) MutuallyExclusiveList2 = @( 'CertificateThumbprint' From d1543fe872afc4ea5640d1fce3fc587cbfc9c8f9 Mon Sep 17 00:00:00 2001 From: Dan Hughes <2237515+dan-hughes@users.noreply.github.com> Date: Fri, 17 Jan 2025 12:21:00 +0000 Subject: [PATCH 116/134] Expand AssertProperties tests --- tests/Unit/Classes/WSManListener.Tests.ps1 | 85 +++++++++++++++++----- 1 file changed, 65 insertions(+), 20 deletions(-) diff --git a/tests/Unit/Classes/WSManListener.Tests.ps1 b/tests/Unit/Classes/WSManListener.Tests.ps1 index 20351e1..7d93feb 100644 --- a/tests/Unit/Classes/WSManListener.Tests.ps1 +++ b/tests/Unit/Classes/WSManListener.Tests.ps1 @@ -1008,33 +1008,78 @@ Describe 'WSManListener\AssertProperties()' -Tag 'AssertProperties' { } Context 'When passing mutually exclusive parameters' { - Context 'When passing Issuer and Hostname' { - It 'Should throw the correct error' { - InModuleScope -ScriptBlock { - Set-StrictMode -Version 1.0 - - { - $mockInstance.AssertProperties(@{ - Issuer = 'SomeIssuer' - HostName = 'TheHostname' - }) - } | Should -Throw -ExpectedMessage '*DRC0010*' + BeforeDiscovery { + $testCases = @( + @{ + Issuer = 'SomeIssuer' + HostName = 'TheHostname' + } + @{ + Issuer = 'SomeIssuer' + CertificateThumbprint = 'certificateThumbprint' + } + @{ + BaseDN = 'SomeBaseDN' + HostName = 'TheHostname' + } + @{ + BaseDN = 'SomeBaseDN' + CertificateThumbprint = 'certificateThumbprint' + } + @{ + SubjectFormat = 'SubjectFormat' + HostName = 'TheHostname' + } + @{ + SubjectFormat = 'SubjectFormat' + CertificateThumbprint = 'certificateThumbprint' + } + @{ + MatchAlternate = 'MatchAlternate' + HostName = 'TheHostname' } + @{ + MatchAlternate = 'MatchAlternate' + CertificateThumbprint = 'certificateThumbprint' + } + ) + } + + It 'Should throw the correct error' -ForEach $testCases { + InModuleScope -Parameters @{ + mockProperties = $_ + } -ScriptBlock { + Set-StrictMode -Version 1.0 + + { $mockInstance.AssertProperties($mockProperties) } | Should -Throw -ExpectedMessage '*DRC0010*' } } } - Context 'When passing BaseDN and CertificateThumbprint' { - It 'Should throw the correct error' { - InModuleScope -ScriptBlock { + Context 'When passing mutually inclusive parameters' { + BeforeDiscovery { + $testCases = @( + @{ + Issuer = 'SomeIssuer' + BaseDN = 'SomeBaseDN' + SubjectFormat = 'SubjectFormat' + MatchAlternate = 'MatchAlternate' + } + @{ + + HostName = 'TheHostname' + CertificateThumbprint = 'certificateThumbprint' + } + ) + } + + It 'Should not throw an error' -ForEach $testCases { + InModuleScope -Parameters @{ + mockProperties = $_ + } -ScriptBlock { Set-StrictMode -Version 1.0 - { - $mockInstance.AssertProperties(@{ - BaseDN = 'SomeBaseDN' - CertificateThumbprint = 'certificateThumbprint' - }) - } | Should -Throw -ExpectedMessage '*DRC0010*' + { $mockInstance.AssertProperties($mockProperties) } | Should -Not -Throw } } } From d32a551cd01af75d63f432b6b356564abc414165 Mon Sep 17 00:00:00 2001 From: Dan Hughes <2237515+dan-hughes@users.noreply.github.com> Date: Fri, 17 Jan 2025 12:42:25 +0000 Subject: [PATCH 117/134] Remove remaning $ENV:computerName --- source/Private/Find-Certificate.ps1 | 2 +- .../DSC_WSManListener.Integration.Tests.ps1 | 2 +- tests/Unit/Classes/WSManListener.Tests.ps1 | 16 ++++++++-------- tests/Unit/Private/Find-Certificate.Tests.ps1 | 2 +- 4 files changed, 11 insertions(+), 11 deletions(-) diff --git a/source/Private/Find-Certificate.ps1 b/source/Private/Find-Certificate.ps1 index 096875c..21d44d9 100644 --- a/source/Private/Find-Certificate.ps1 +++ b/source/Private/Find-Certificate.ps1 @@ -68,7 +68,7 @@ function Find-Certificate # Lookup the certificate using the FQDN of the machine if ([System.String]::IsNullOrEmpty($Hostname)) { - $Hostname = [System.Net.Dns]::GetHostByName($ENV:computerName).Hostname + $Hostname = Get-ComputerName } $Subject = "CN=$Hostname" diff --git a/tests/Integration/DSC_WSManListener.Integration.Tests.ps1 b/tests/Integration/DSC_WSManListener.Integration.Tests.ps1 index c460702..faa2e3b 100644 --- a/tests/Integration/DSC_WSManListener.Integration.Tests.ps1 +++ b/tests/Integration/DSC_WSManListener.Integration.Tests.ps1 @@ -62,7 +62,7 @@ BeforeAll { Where-Object -Property FriendlyName -EQ $CertFriendlyName | Remove-Item -Force - $script:Hostname = ([System.Net.Dns]::GetHostByName($ENV:computerName).Hostname) + $script:Hostname = Get-ComputerName $script:BaseDN = 'O=Contoso Inc, S=Pennsylvania, C=US' $script:Issuer = "CN=$Hostname, $BaseDN" diff --git a/tests/Unit/Classes/WSManListener.Tests.ps1 b/tests/Unit/Classes/WSManListener.Tests.ps1 index 7d93feb..6f1d9e3 100644 --- a/tests/Unit/Classes/WSManListener.Tests.ps1 +++ b/tests/Unit/Classes/WSManListener.Tests.ps1 @@ -499,7 +499,7 @@ Describe 'WSManListener\Test()' -Tag 'Test' { Transport = 'HTTPS' Port = 5986 CertificateThumbprint = '74FA31ADEA7FDD5333CED10910BFA6F665A1F2FC' - Hostname = $([System.Net.Dns]::GetHostByName($ENV:computerName).Hostname) + Hostname = Get-ComputerName Ensure = 'Present' } } @@ -624,7 +624,7 @@ Describe 'WSManListener\GetCurrentState()' -Tag 'HiddenMember' { Address = '*' CertificateThumbprint = $null - Hostname = $([System.Net.Dns]::GetHostByName($ENV:computerName).Hostname) + Hostname = Get-ComputerName Enabled = $true URLPrefix = 'wsman' @@ -648,7 +648,7 @@ Describe 'WSManListener\GetCurrentState()' -Tag 'HiddenMember' { $currentState.Address | Should -Be '*' $currentState.Issuer | Should -BeNullOrEmpty $currentState.CertificateThumbprint | Should -BeNullOrEmpty - $currentState.Hostname | Should -Be $([System.Net.Dns]::GetHostByName($ENV:computerName).Hostname) + $currentState.Hostname | Should -Be (Get-ComputerName) $currentState.Enabled | Should -BeTrue $currentState.URLPrefix | Should -Be 'wsman' } @@ -675,7 +675,7 @@ Describe 'WSManListener\GetCurrentState()' -Tag 'HiddenMember' { Address = '*' CertificateThumbprint = $null - Hostname = $([System.Net.Dns]::GetHostByName($ENV:computerName).Hostname) + Hostname = Get-ComputerName Enabled = $true URLPrefix = 'wsman' @@ -701,7 +701,7 @@ Describe 'WSManListener\GetCurrentState()' -Tag 'HiddenMember' { $currentState.Address | Should -Be '*' $currentState.Issuer | Should -BeNullOrEmpty $currentState.CertificateThumbprint | Should -BeNullOrEmpty - $currentState.Hostname | Should -Be $([System.Net.Dns]::GetHostByName($ENV:computerName).Hostname) + $currentState.Hostname | Should -Be (Get-ComputerName) $currentState.Enabled | Should -BeTrue $currentState.URLPrefix | Should -Be 'wsman' } @@ -731,7 +731,7 @@ Describe 'WSManListener\GetCurrentState()' -Tag 'HiddenMember' { Address = '*' CertificateThumbprint = '74FA31ADEA7FDD5333CED10910BFA6F665A1F2FC' - Hostname = $([System.Net.Dns]::GetHostByName($ENV:computerName).Hostname) + Hostname = Get-ComputerName Enabled = $true URLPrefix = 'wsman' @@ -757,7 +757,7 @@ Describe 'WSManListener\GetCurrentState()' -Tag 'HiddenMember' { $currentState.Address | Should -Be '*' $currentState.Issuer | Should -Be 'CN=CONTOSO.COM Issuing CA, DC=CONTOSO, DC=COM' $currentState.CertificateThumbprint | Should -Be '74FA31ADEA7FDD5333CED10910BFA6F665A1F2FC' - $currentState.Hostname | Should -Be $([System.Net.Dns]::GetHostByName($ENV:computerName).Hostname) + $currentState.Hostname | Should -Be (Get-ComputerName) $currentState.Enabled | Should -BeTrue $currentState.URLPrefix | Should -Be 'wsman' } @@ -941,7 +941,7 @@ Describe 'WSManListener\NewInstance()' -Tag 'HiddenMember' { Should -Invoke -CommandName Get-DscProperty -Exactly -Times 1 -Scope It Should -Invoke -CommandName Find-Certificate -Exactly -Times 1 -Scope It Should -Invoke -CommandName New-WSManInstance -ParameterFilter { - $ValueSet.HostName -eq [System.Net.Dns]::GetHostByName($env:COMPUTERNAME).Hostname + $ValueSet.HostName -eq (Get-ComputerName) } -Exactly -Times 1 -Scope It } } diff --git a/tests/Unit/Private/Find-Certificate.Tests.ps1 b/tests/Unit/Private/Find-Certificate.Tests.ps1 index 243c0a9..ee0875d 100644 --- a/tests/Unit/Private/Find-Certificate.Tests.ps1 +++ b/tests/Unit/Private/Find-Certificate.Tests.ps1 @@ -35,7 +35,7 @@ BeforeAll { # Create the Mock Objects that will be used for running tests $script:mockCertificateThumbprint = '74FA31ADEA7FDD5333CED10910BFA6F665A1F2FC' $mockFQDN = 'SERVER1.CONTOSO.COM' - $mockHostName = $([System.Net.Dns]::GetHostByName($ENV:computerName).Hostname) + $mockHostName = Get-ComputerName $script:mockIssuer = 'CN=CONTOSO.COM Issuing CA, DC=CONTOSO, DC=COM' $script:mockDN = 'O=Contoso Inc, S=Pennsylvania, C=US' $mockCertificate = @{ From 34b71da6768919e754ecb525291247b6fd8c5898 Mon Sep 17 00:00:00 2001 From: Dan Hughes <2237515+dan-hughes@users.noreply.github.com> Date: Fri, 17 Jan 2025 13:01:31 +0000 Subject: [PATCH 118/134] Make SubjectFormat nullable to correct bound parameter checks --- source/Classes/020.WSManListener.ps1 | 4 ++-- tests/Unit/Classes/WSManListener.Tests.ps1 | 10 +++++----- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/source/Classes/020.WSManListener.ps1 b/source/Classes/020.WSManListener.ps1 index d640666..1506c63 100644 --- a/source/Classes/020.WSManListener.ps1 +++ b/source/Classes/020.WSManListener.ps1 @@ -88,8 +88,8 @@ class WSManListener : ResourceBase $Issuer [DscProperty()] - [WSManSubjectFormat] - $SubjectFormat = [WSManSubjectFormat]::Both + [Nullable[WSManSubjectFormat]] + $SubjectFormat [DscProperty()] [Nullable[System.Boolean]] diff --git a/tests/Unit/Classes/WSManListener.Tests.ps1 b/tests/Unit/Classes/WSManListener.Tests.ps1 index 6f1d9e3..3620c4e 100644 --- a/tests/Unit/Classes/WSManListener.Tests.ps1 +++ b/tests/Unit/Classes/WSManListener.Tests.ps1 @@ -133,7 +133,7 @@ Describe 'WSManListener\Get()' -Tag 'Get' { $currentState.URLPrefix | Should -Be 'wsman' $currentState.Issuer | Should -BeNullOrEmpty - $currentState.SubjectFormat | Should -Be 'Both' + $currentState.SubjectFormat | Should -BeNullOrEmpty $currentState.MatchAlternate | Should -BeNullOrEmpty $currentState.BaseDN | Should -BeNullOrEmpty $currentState.CertificateThumbprint | Should -BeNullOrEmpty @@ -192,7 +192,7 @@ Describe 'WSManListener\Get()' -Tag 'Get' { $currentState.URLPrefix | Should -Be 'wsman' $currentState.Issuer | Should -BeNullOrEmpty - $currentState.SubjectFormat | Should -Be 'Both' + $currentState.SubjectFormat | Should -BeNullOrEmpty $currentState.MatchAlternate | Should -BeNullOrEmpty $currentState.BaseDN | Should -BeNullOrEmpty $currentState.CertificateThumbprint | Should -BeNullOrEmpty @@ -245,7 +245,7 @@ Describe 'WSManListener\Get()' -Tag 'Get' { $currentState.URLPrefix | Should -BeNullOrEmpty $currentState.Issuer | Should -BeNullOrEmpty - $currentState.SubjectFormat | Should -Be 'Both' + $currentState.SubjectFormat | Should -BeNullOrEmpty $currentState.MatchAlternate | Should -BeNullOrEmpty $currentState.BaseDN | Should -BeNullOrEmpty $currentState.CertificateThumbprint | Should -BeNullOrEmpty @@ -320,7 +320,7 @@ Describe 'WSManListener\Get()' -Tag 'Get' { $currentState.URLPrefix | Should -Be 'wsman' $currentState.Issuer | Should -BeNullOrEmpty - $currentState.SubjectFormat | Should -Be 'Both' + $currentState.SubjectFormat | Should -BeNullOrEmpty $currentState.MatchAlternate | Should -BeNullOrEmpty $currentState.BaseDN | Should -BeNullOrEmpty $currentState.CertificateThumbprint | Should -BeNullOrEmpty @@ -387,7 +387,7 @@ Describe 'WSManListener\Get()' -Tag 'Get' { $currentState.URLPrefix | Should -Be 'wsman' $currentState.Issuer | Should -BeNullOrEmpty - $currentState.SubjectFormat | Should -Be 'Both' + $currentState.SubjectFormat | Should -BeNullOrEmpty $currentState.MatchAlternate | Should -BeNullOrEmpty $currentState.BaseDN | Should -BeNullOrEmpty $currentState.CertificateThumbprint | Should -BeNullOrEmpty From a6fa777ef02ede28377a42ac02a41b10ae9d09d9 Mon Sep 17 00:00:00 2001 From: Dan Hughes <2237515+dan-hughes@users.noreply.github.com> Date: Fri, 17 Jan 2025 13:12:21 +0000 Subject: [PATCH 119/134] Cannot use nullable enums --- source/Classes/020.WSManListener.ps1 | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/source/Classes/020.WSManListener.ps1 b/source/Classes/020.WSManListener.ps1 index 1506c63..586e292 100644 --- a/source/Classes/020.WSManListener.ps1 +++ b/source/Classes/020.WSManListener.ps1 @@ -88,7 +88,8 @@ class WSManListener : ResourceBase $Issuer [DscProperty()] - [Nullable[WSManSubjectFormat]] + [ValidateSet('Both', 'FQDNOnly', 'NameOnly')] + [System.String] $SubjectFormat [DscProperty()] From 725e05e33caf7b196629983c6ea8a6d0703a959b Mon Sep 17 00:00:00 2001 From: Dan Hughes <2237515+dan-hughes@users.noreply.github.com> Date: Fri, 17 Jan 2025 13:48:08 +0000 Subject: [PATCH 120/134] Enum use starting value of 1 --- source/Classes/020.WSManListener.ps1 | 3 +-- source/Enum/005.WSManSubjectFormat.ps1 | 2 +- source/Enum/005.WSManTransport.ps1 | 2 +- tests/Unit/Classes/WSManListener.Tests.ps1 | 10 +++++----- 4 files changed, 8 insertions(+), 9 deletions(-) diff --git a/source/Classes/020.WSManListener.ps1 b/source/Classes/020.WSManListener.ps1 index 586e292..be7f0fe 100644 --- a/source/Classes/020.WSManListener.ps1 +++ b/source/Classes/020.WSManListener.ps1 @@ -88,8 +88,7 @@ class WSManListener : ResourceBase $Issuer [DscProperty()] - [ValidateSet('Both', 'FQDNOnly', 'NameOnly')] - [System.String] + [WSManSubjectFormat] $SubjectFormat [DscProperty()] diff --git a/source/Enum/005.WSManSubjectFormat.ps1 b/source/Enum/005.WSManSubjectFormat.ps1 index ecf7c15..13bf44a 100644 --- a/source/Enum/005.WSManSubjectFormat.ps1 +++ b/source/Enum/005.WSManSubjectFormat.ps1 @@ -5,7 +5,7 @@ enum WSManSubjectFormat { - Both + Both = 1 FQDNOnly NameOnly } diff --git a/source/Enum/005.WSManTransport.ps1 b/source/Enum/005.WSManTransport.ps1 index 17a828f..fc199fc 100644 --- a/source/Enum/005.WSManTransport.ps1 +++ b/source/Enum/005.WSManTransport.ps1 @@ -5,6 +5,6 @@ enum WSManTransport { - HTTP + HTTP = 1 HTTPS } diff --git a/tests/Unit/Classes/WSManListener.Tests.ps1 b/tests/Unit/Classes/WSManListener.Tests.ps1 index 3620c4e..280c744 100644 --- a/tests/Unit/Classes/WSManListener.Tests.ps1 +++ b/tests/Unit/Classes/WSManListener.Tests.ps1 @@ -133,7 +133,7 @@ Describe 'WSManListener\Get()' -Tag 'Get' { $currentState.URLPrefix | Should -Be 'wsman' $currentState.Issuer | Should -BeNullOrEmpty - $currentState.SubjectFormat | Should -BeNullOrEmpty + $currentState.SubjectFormat | Should -Be 0 $currentState.MatchAlternate | Should -BeNullOrEmpty $currentState.BaseDN | Should -BeNullOrEmpty $currentState.CertificateThumbprint | Should -BeNullOrEmpty @@ -192,7 +192,7 @@ Describe 'WSManListener\Get()' -Tag 'Get' { $currentState.URLPrefix | Should -Be 'wsman' $currentState.Issuer | Should -BeNullOrEmpty - $currentState.SubjectFormat | Should -BeNullOrEmpty + $currentState.SubjectFormat | Should -Be 0 $currentState.MatchAlternate | Should -BeNullOrEmpty $currentState.BaseDN | Should -BeNullOrEmpty $currentState.CertificateThumbprint | Should -BeNullOrEmpty @@ -245,7 +245,7 @@ Describe 'WSManListener\Get()' -Tag 'Get' { $currentState.URLPrefix | Should -BeNullOrEmpty $currentState.Issuer | Should -BeNullOrEmpty - $currentState.SubjectFormat | Should -BeNullOrEmpty + $currentState.SubjectFormat | Should -Be 0 $currentState.MatchAlternate | Should -BeNullOrEmpty $currentState.BaseDN | Should -BeNullOrEmpty $currentState.CertificateThumbprint | Should -BeNullOrEmpty @@ -320,7 +320,7 @@ Describe 'WSManListener\Get()' -Tag 'Get' { $currentState.URLPrefix | Should -Be 'wsman' $currentState.Issuer | Should -BeNullOrEmpty - $currentState.SubjectFormat | Should -BeNullOrEmpty + $currentState.SubjectFormat | Should -Be 0 $currentState.MatchAlternate | Should -BeNullOrEmpty $currentState.BaseDN | Should -BeNullOrEmpty $currentState.CertificateThumbprint | Should -BeNullOrEmpty @@ -387,7 +387,7 @@ Describe 'WSManListener\Get()' -Tag 'Get' { $currentState.URLPrefix | Should -Be 'wsman' $currentState.Issuer | Should -BeNullOrEmpty - $currentState.SubjectFormat | Should -BeNullOrEmpty + $currentState.SubjectFormat | Should -Be 0 $currentState.MatchAlternate | Should -BeNullOrEmpty $currentState.BaseDN | Should -BeNullOrEmpty $currentState.CertificateThumbprint | Should -BeNullOrEmpty From d9c87a46e6b9370ee805c6af8dc5c8fb453c4046 Mon Sep 17 00:00:00 2001 From: Dan Hughes <2237515+dan-hughes@users.noreply.github.com> Date: Fri, 17 Jan 2025 16:47:53 +0000 Subject: [PATCH 121/134] Correct casing --- source/Classes/020.WSManListener.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/Classes/020.WSManListener.ps1 b/source/Classes/020.WSManListener.ps1 index be7f0fe..b04a2df 100644 --- a/source/Classes/020.WSManListener.ps1 +++ b/source/Classes/020.WSManListener.ps1 @@ -234,7 +234,7 @@ class WSManListener : ResourceBase ) MutuallyExclusiveList2 = @( 'CertificateThumbprint' - 'Hostname' + 'HostName' ) } From 9747472524811290a521ea75db584c709aa14df8 Mon Sep 17 00:00:00 2001 From: Dan Hughes <2237515+dan-hughes@users.noreply.github.com> Date: Fri, 17 Jan 2025 16:50:47 +0000 Subject: [PATCH 122/134] Remove subjectformat temporarily --- source/Classes/020.WSManListener.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/Classes/020.WSManListener.ps1 b/source/Classes/020.WSManListener.ps1 index b04a2df..5bf72fb 100644 --- a/source/Classes/020.WSManListener.ps1 +++ b/source/Classes/020.WSManListener.ps1 @@ -229,7 +229,7 @@ class WSManListener : ResourceBase MutuallyExclusiveList1 = @( 'Issuer' 'BaseDN' - 'SubjectFormat' + #'SubjectFormat' 'MatchAlternate' ) MutuallyExclusiveList2 = @( From 28776f51a62d8bc3ad90e38370524ce66973a4ff Mon Sep 17 00:00:00 2001 From: Dan Hughes <2237515+dan-hughes@users.noreply.github.com> Date: Fri, 17 Jan 2025 16:51:35 +0000 Subject: [PATCH 123/134] Update unit tests for subjectformat using enum --- tests/Unit/Classes/WSManListener.Tests.ps1 | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/tests/Unit/Classes/WSManListener.Tests.ps1 b/tests/Unit/Classes/WSManListener.Tests.ps1 index 280c744..8f6d3c8 100644 --- a/tests/Unit/Classes/WSManListener.Tests.ps1 +++ b/tests/Unit/Classes/WSManListener.Tests.ps1 @@ -1027,11 +1027,11 @@ Describe 'WSManListener\AssertProperties()' -Tag 'AssertProperties' { CertificateThumbprint = 'certificateThumbprint' } @{ - SubjectFormat = 'SubjectFormat' + SubjectFormat = 1 HostName = 'TheHostname' } @{ - SubjectFormat = 'SubjectFormat' + SubjectFormat = 1 CertificateThumbprint = 'certificateThumbprint' } @{ @@ -1051,7 +1051,12 @@ Describe 'WSManListener\AssertProperties()' -Tag 'AssertProperties' { } -ScriptBlock { Set-StrictMode -Version 1.0 - { $mockInstance.AssertProperties($mockProperties) } | Should -Throw -ExpectedMessage '*DRC0010*' + if ($mockProperties.SubjectFormat) + { + $mockProperties.SubjectFormat = [WSManSubjectFormat]$mockProperties.SubjectFormat + } + + { $mockInstance.AssertProperties($mockProperties) } | Should -Throw -ExpectedMessage ('*' + 'DRC0010' + '*') } } } @@ -1062,7 +1067,7 @@ Describe 'WSManListener\AssertProperties()' -Tag 'AssertProperties' { @{ Issuer = 'SomeIssuer' BaseDN = 'SomeBaseDN' - SubjectFormat = 'SubjectFormat' + SubjectFormat = 0 MatchAlternate = 'MatchAlternate' } @{ @@ -1079,6 +1084,11 @@ Describe 'WSManListener\AssertProperties()' -Tag 'AssertProperties' { } -ScriptBlock { Set-StrictMode -Version 1.0 + if ($mockProperties.SubjectFormat) + { + $mockProperties.SubjectFormat = [WSManSubjectFormat]$mockProperties.SubjectFormat + } + { $mockInstance.AssertProperties($mockProperties) } | Should -Not -Throw } } From c0960b40e8b31a3d5bd0febb9e2974f2a8a76ab4 Mon Sep 17 00:00:00 2001 From: Dan Hughes <2237515+dan-hughes@users.noreply.github.com> Date: Fri, 17 Jan 2025 16:53:14 +0000 Subject: [PATCH 124/134] Disable subjectformat tests --- tests/Unit/Classes/WSManListener.Tests.ps1 | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/tests/Unit/Classes/WSManListener.Tests.ps1 b/tests/Unit/Classes/WSManListener.Tests.ps1 index 8f6d3c8..eb80ad4 100644 --- a/tests/Unit/Classes/WSManListener.Tests.ps1 +++ b/tests/Unit/Classes/WSManListener.Tests.ps1 @@ -1026,14 +1026,14 @@ Describe 'WSManListener\AssertProperties()' -Tag 'AssertProperties' { BaseDN = 'SomeBaseDN' CertificateThumbprint = 'certificateThumbprint' } - @{ - SubjectFormat = 1 - HostName = 'TheHostname' - } - @{ - SubjectFormat = 1 - CertificateThumbprint = 'certificateThumbprint' - } + # @{ + # SubjectFormat = 1 + # HostName = 'TheHostname' + # } + # @{ + # SubjectFormat = 1 + # CertificateThumbprint = 'certificateThumbprint' + # } @{ MatchAlternate = 'MatchAlternate' HostName = 'TheHostname' From cdacf22b56306744b402f5c45f820abec28be277 Mon Sep 17 00:00:00 2001 From: Dan Hughes <2237515+dan-hughes@users.noreply.github.com> Date: Fri, 17 Jan 2025 17:31:30 +0000 Subject: [PATCH 125/134] Add boundparameter filter for enum default values e.g. 0 --- source/Classes/020.WSManListener.ps1 | 17 +++++++++++++++-- tests/Unit/Classes/WSManListener.Tests.ps1 | 16 ++++++++-------- 2 files changed, 23 insertions(+), 10 deletions(-) diff --git a/source/Classes/020.WSManListener.ps1 b/source/Classes/020.WSManListener.ps1 index 5bf72fb..792f814 100644 --- a/source/Classes/020.WSManListener.ps1 +++ b/source/Classes/020.WSManListener.ps1 @@ -224,12 +224,25 @@ class WSManListener : ResourceBase #> hidden [void] AssertProperties([System.Collections.Hashtable] $properties) { + # Filter Out Keys that are enums AND have value of 0 (assumed default) + # https://learn.microsoft.com/en-us/powershell/dsc/concepts/class-based-resources?view=dsc-2.0#use-enums-instead-of-validateset + $FilteredBoundParameters = @{} + foreach ($key in $properties.Keys) + { + $value = $properties.$key + if ($value -is [System.Enum] -and [System.Int32]$value.value__ -eq 0) + { + continue + } + $FilteredBoundParameters.Add($key, $properties.$key) + } + $assertBoundParameterParameters = @{ - BoundParameterList = $properties + BoundParameterList = $FilteredBoundParameters MutuallyExclusiveList1 = @( 'Issuer' 'BaseDN' - #'SubjectFormat' + 'SubjectFormat' 'MatchAlternate' ) MutuallyExclusiveList2 = @( diff --git a/tests/Unit/Classes/WSManListener.Tests.ps1 b/tests/Unit/Classes/WSManListener.Tests.ps1 index eb80ad4..8f6d3c8 100644 --- a/tests/Unit/Classes/WSManListener.Tests.ps1 +++ b/tests/Unit/Classes/WSManListener.Tests.ps1 @@ -1026,14 +1026,14 @@ Describe 'WSManListener\AssertProperties()' -Tag 'AssertProperties' { BaseDN = 'SomeBaseDN' CertificateThumbprint = 'certificateThumbprint' } - # @{ - # SubjectFormat = 1 - # HostName = 'TheHostname' - # } - # @{ - # SubjectFormat = 1 - # CertificateThumbprint = 'certificateThumbprint' - # } + @{ + SubjectFormat = 1 + HostName = 'TheHostname' + } + @{ + SubjectFormat = 1 + CertificateThumbprint = 'certificateThumbprint' + } @{ MatchAlternate = 'MatchAlternate' HostName = 'TheHostname' From 4ae14ea34df75ea293ff34d6cfcfad831e49aefc Mon Sep 17 00:00:00 2001 From: Dan Hughes <2237515+dan-hughes@users.noreply.github.com> Date: Mon, 20 Jan 2025 14:17:58 +0000 Subject: [PATCH 126/134] Revert "Remove remaning $ENV:computerName" This reverts commit ae0ee81161a45e3fe76f1d7ecb04d9be1ea787c4. --- source/Private/Find-Certificate.ps1 | 2 +- .../DSC_WSManListener.Integration.Tests.ps1 | 2 +- tests/Unit/Classes/WSManListener.Tests.ps1 | 16 ++++++++-------- tests/Unit/Private/Find-Certificate.Tests.ps1 | 2 +- 4 files changed, 11 insertions(+), 11 deletions(-) diff --git a/source/Private/Find-Certificate.ps1 b/source/Private/Find-Certificate.ps1 index 21d44d9..096875c 100644 --- a/source/Private/Find-Certificate.ps1 +++ b/source/Private/Find-Certificate.ps1 @@ -68,7 +68,7 @@ function Find-Certificate # Lookup the certificate using the FQDN of the machine if ([System.String]::IsNullOrEmpty($Hostname)) { - $Hostname = Get-ComputerName + $Hostname = [System.Net.Dns]::GetHostByName($ENV:computerName).Hostname } $Subject = "CN=$Hostname" diff --git a/tests/Integration/DSC_WSManListener.Integration.Tests.ps1 b/tests/Integration/DSC_WSManListener.Integration.Tests.ps1 index faa2e3b..c460702 100644 --- a/tests/Integration/DSC_WSManListener.Integration.Tests.ps1 +++ b/tests/Integration/DSC_WSManListener.Integration.Tests.ps1 @@ -62,7 +62,7 @@ BeforeAll { Where-Object -Property FriendlyName -EQ $CertFriendlyName | Remove-Item -Force - $script:Hostname = Get-ComputerName + $script:Hostname = ([System.Net.Dns]::GetHostByName($ENV:computerName).Hostname) $script:BaseDN = 'O=Contoso Inc, S=Pennsylvania, C=US' $script:Issuer = "CN=$Hostname, $BaseDN" diff --git a/tests/Unit/Classes/WSManListener.Tests.ps1 b/tests/Unit/Classes/WSManListener.Tests.ps1 index 8f6d3c8..c21432e 100644 --- a/tests/Unit/Classes/WSManListener.Tests.ps1 +++ b/tests/Unit/Classes/WSManListener.Tests.ps1 @@ -499,7 +499,7 @@ Describe 'WSManListener\Test()' -Tag 'Test' { Transport = 'HTTPS' Port = 5986 CertificateThumbprint = '74FA31ADEA7FDD5333CED10910BFA6F665A1F2FC' - Hostname = Get-ComputerName + Hostname = $([System.Net.Dns]::GetHostByName($ENV:computerName).Hostname) Ensure = 'Present' } } @@ -624,7 +624,7 @@ Describe 'WSManListener\GetCurrentState()' -Tag 'HiddenMember' { Address = '*' CertificateThumbprint = $null - Hostname = Get-ComputerName + Hostname = $([System.Net.Dns]::GetHostByName($ENV:computerName).Hostname) Enabled = $true URLPrefix = 'wsman' @@ -648,7 +648,7 @@ Describe 'WSManListener\GetCurrentState()' -Tag 'HiddenMember' { $currentState.Address | Should -Be '*' $currentState.Issuer | Should -BeNullOrEmpty $currentState.CertificateThumbprint | Should -BeNullOrEmpty - $currentState.Hostname | Should -Be (Get-ComputerName) + $currentState.Hostname | Should -Be $([System.Net.Dns]::GetHostByName($ENV:computerName).Hostname) $currentState.Enabled | Should -BeTrue $currentState.URLPrefix | Should -Be 'wsman' } @@ -675,7 +675,7 @@ Describe 'WSManListener\GetCurrentState()' -Tag 'HiddenMember' { Address = '*' CertificateThumbprint = $null - Hostname = Get-ComputerName + Hostname = $([System.Net.Dns]::GetHostByName($ENV:computerName).Hostname) Enabled = $true URLPrefix = 'wsman' @@ -701,7 +701,7 @@ Describe 'WSManListener\GetCurrentState()' -Tag 'HiddenMember' { $currentState.Address | Should -Be '*' $currentState.Issuer | Should -BeNullOrEmpty $currentState.CertificateThumbprint | Should -BeNullOrEmpty - $currentState.Hostname | Should -Be (Get-ComputerName) + $currentState.Hostname | Should -Be $([System.Net.Dns]::GetHostByName($ENV:computerName).Hostname) $currentState.Enabled | Should -BeTrue $currentState.URLPrefix | Should -Be 'wsman' } @@ -731,7 +731,7 @@ Describe 'WSManListener\GetCurrentState()' -Tag 'HiddenMember' { Address = '*' CertificateThumbprint = '74FA31ADEA7FDD5333CED10910BFA6F665A1F2FC' - Hostname = Get-ComputerName + Hostname = $([System.Net.Dns]::GetHostByName($ENV:computerName).Hostname) Enabled = $true URLPrefix = 'wsman' @@ -757,7 +757,7 @@ Describe 'WSManListener\GetCurrentState()' -Tag 'HiddenMember' { $currentState.Address | Should -Be '*' $currentState.Issuer | Should -Be 'CN=CONTOSO.COM Issuing CA, DC=CONTOSO, DC=COM' $currentState.CertificateThumbprint | Should -Be '74FA31ADEA7FDD5333CED10910BFA6F665A1F2FC' - $currentState.Hostname | Should -Be (Get-ComputerName) + $currentState.Hostname | Should -Be $([System.Net.Dns]::GetHostByName($ENV:computerName).Hostname) $currentState.Enabled | Should -BeTrue $currentState.URLPrefix | Should -Be 'wsman' } @@ -941,7 +941,7 @@ Describe 'WSManListener\NewInstance()' -Tag 'HiddenMember' { Should -Invoke -CommandName Get-DscProperty -Exactly -Times 1 -Scope It Should -Invoke -CommandName Find-Certificate -Exactly -Times 1 -Scope It Should -Invoke -CommandName New-WSManInstance -ParameterFilter { - $ValueSet.HostName -eq (Get-ComputerName) + $ValueSet.HostName -eq [System.Net.Dns]::GetHostByName($env:COMPUTERNAME).Hostname } -Exactly -Times 1 -Scope It } } diff --git a/tests/Unit/Private/Find-Certificate.Tests.ps1 b/tests/Unit/Private/Find-Certificate.Tests.ps1 index ee0875d..243c0a9 100644 --- a/tests/Unit/Private/Find-Certificate.Tests.ps1 +++ b/tests/Unit/Private/Find-Certificate.Tests.ps1 @@ -35,7 +35,7 @@ BeforeAll { # Create the Mock Objects that will be used for running tests $script:mockCertificateThumbprint = '74FA31ADEA7FDD5333CED10910BFA6F665A1F2FC' $mockFQDN = 'SERVER1.CONTOSO.COM' - $mockHostName = Get-ComputerName + $mockHostName = $([System.Net.Dns]::GetHostByName($ENV:computerName).Hostname) $script:mockIssuer = 'CN=CONTOSO.COM Issuing CA, DC=CONTOSO, DC=COM' $script:mockDN = 'O=Contoso Inc, S=Pennsylvania, C=US' $mockCertificate = @{ From 48e8b3bba5f431d4528beda31a9ea2a96ec80c61 Mon Sep 17 00:00:00 2001 From: Dan Hughes <2237515+dan-hughes@users.noreply.github.com> Date: Mon, 20 Jan 2025 14:18:20 +0000 Subject: [PATCH 127/134] Revert "Use Get-ComputerName from DscResource.Common" This reverts commit a6c859a9923e82d7bed220ae7c08e1d4261c0b24. --- source/Classes/020.WSManListener.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/Classes/020.WSManListener.ps1 b/source/Classes/020.WSManListener.ps1 index 792f814..41acdad 100644 --- a/source/Classes/020.WSManListener.ps1 +++ b/source/Classes/020.WSManListener.ps1 @@ -281,7 +281,7 @@ class WSManListener : ResourceBase if ([System.String]::IsNullOrEmpty($this.Hostname)) { - $valueSet.HostName = Get-ComputerName + $valueSet.HostName = [System.Net.Dns]::GetHostByName($env:COMPUTERNAME).Hostname } else { From dc26b6b58191ae8d464d0c850f491ef238ea5349 Mon Sep 17 00:00:00 2001 From: Dan Hughes <2237515+dan-hughes@users.noreply.github.com> Date: Mon, 20 Jan 2025 14:30:04 +0000 Subject: [PATCH 128/134] GetHostByName is deprecated, use Get-ComuterName instead of $env:COMPUTERNAME --- source/Classes/020.WSManListener.ps1 | 2 +- source/Private/Find-Certificate.ps1 | 4 ++-- .../DSC_WSManListener.Integration.Tests.ps1 | 2 +- tests/Unit/Classes/WSManListener.Tests.ps1 | 16 ++++++++-------- tests/Unit/Private/Find-Certificate.Tests.ps1 | 2 +- 5 files changed, 13 insertions(+), 13 deletions(-) diff --git a/source/Classes/020.WSManListener.ps1 b/source/Classes/020.WSManListener.ps1 index 41acdad..4b79ace 100644 --- a/source/Classes/020.WSManListener.ps1 +++ b/source/Classes/020.WSManListener.ps1 @@ -281,7 +281,7 @@ class WSManListener : ResourceBase if ([System.String]::IsNullOrEmpty($this.Hostname)) { - $valueSet.HostName = [System.Net.Dns]::GetHostByName($env:COMPUTERNAME).Hostname + $valueSet.HostName = [System.Net.Dns]::GetHostEntry((Get-ComputerName)).Hostname } else { diff --git a/source/Private/Find-Certificate.ps1 b/source/Private/Find-Certificate.ps1 index 096875c..1b47d36 100644 --- a/source/Private/Find-Certificate.ps1 +++ b/source/Private/Find-Certificate.ps1 @@ -68,7 +68,7 @@ function Find-Certificate # Lookup the certificate using the FQDN of the machine if ([System.String]::IsNullOrEmpty($Hostname)) { - $Hostname = [System.Net.Dns]::GetHostByName($ENV:computerName).Hostname + $Hostname = [System.Net.Dns]::GetHostEntry((Get-ComputerName)).Hostname } $Subject = "CN=$Hostname" @@ -105,7 +105,7 @@ function Find-Certificate 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 = $ENV:ComputerName + [System.String] $Hostname = Get-ComputerName [System.String] $Subject = "CN=$Hostname" if ($PSBoundParameters.ContainsKey('BaseDN')) diff --git a/tests/Integration/DSC_WSManListener.Integration.Tests.ps1 b/tests/Integration/DSC_WSManListener.Integration.Tests.ps1 index c460702..a166be9 100644 --- a/tests/Integration/DSC_WSManListener.Integration.Tests.ps1 +++ b/tests/Integration/DSC_WSManListener.Integration.Tests.ps1 @@ -62,7 +62,7 @@ BeforeAll { Where-Object -Property FriendlyName -EQ $CertFriendlyName | Remove-Item -Force - $script:Hostname = ([System.Net.Dns]::GetHostByName($ENV:computerName).Hostname) + $script:Hostname = [System.Net.Dns]::GetHostEntry((Get-ComputerName)).Hostname $script:BaseDN = 'O=Contoso Inc, S=Pennsylvania, C=US' $script:Issuer = "CN=$Hostname, $BaseDN" diff --git a/tests/Unit/Classes/WSManListener.Tests.ps1 b/tests/Unit/Classes/WSManListener.Tests.ps1 index c21432e..253ff5a 100644 --- a/tests/Unit/Classes/WSManListener.Tests.ps1 +++ b/tests/Unit/Classes/WSManListener.Tests.ps1 @@ -499,7 +499,7 @@ Describe 'WSManListener\Test()' -Tag 'Test' { Transport = 'HTTPS' Port = 5986 CertificateThumbprint = '74FA31ADEA7FDD5333CED10910BFA6F665A1F2FC' - Hostname = $([System.Net.Dns]::GetHostByName($ENV:computerName).Hostname) + Hostname = [System.Net.Dns]::GetHostEntry((Get-ComputerName)).Hostname Ensure = 'Present' } } @@ -624,7 +624,7 @@ Describe 'WSManListener\GetCurrentState()' -Tag 'HiddenMember' { Address = '*' CertificateThumbprint = $null - Hostname = $([System.Net.Dns]::GetHostByName($ENV:computerName).Hostname) + Hostname = [System.Net.Dns]::GetHostEntry((Get-ComputerName)).Hostname Enabled = $true URLPrefix = 'wsman' @@ -648,7 +648,7 @@ Describe 'WSManListener\GetCurrentState()' -Tag 'HiddenMember' { $currentState.Address | Should -Be '*' $currentState.Issuer | Should -BeNullOrEmpty $currentState.CertificateThumbprint | Should -BeNullOrEmpty - $currentState.Hostname | Should -Be $([System.Net.Dns]::GetHostByName($ENV:computerName).Hostname) + $currentState.Hostname | Should -Be ([System.Net.Dns]::GetHostEntry((Get-ComputerName)).Hostname) $currentState.Enabled | Should -BeTrue $currentState.URLPrefix | Should -Be 'wsman' } @@ -675,7 +675,7 @@ Describe 'WSManListener\GetCurrentState()' -Tag 'HiddenMember' { Address = '*' CertificateThumbprint = $null - Hostname = $([System.Net.Dns]::GetHostByName($ENV:computerName).Hostname) + Hostname = [System.Net.Dns]::GetHostEntry((Get-ComputerName)).Hostname Enabled = $true URLPrefix = 'wsman' @@ -701,7 +701,7 @@ Describe 'WSManListener\GetCurrentState()' -Tag 'HiddenMember' { $currentState.Address | Should -Be '*' $currentState.Issuer | Should -BeNullOrEmpty $currentState.CertificateThumbprint | Should -BeNullOrEmpty - $currentState.Hostname | Should -Be $([System.Net.Dns]::GetHostByName($ENV:computerName).Hostname) + $currentState.Hostname | Should -Be ([System.Net.Dns]::GetHostEntry((Get-ComputerName)).Hostname) $currentState.Enabled | Should -BeTrue $currentState.URLPrefix | Should -Be 'wsman' } @@ -731,7 +731,7 @@ Describe 'WSManListener\GetCurrentState()' -Tag 'HiddenMember' { Address = '*' CertificateThumbprint = '74FA31ADEA7FDD5333CED10910BFA6F665A1F2FC' - Hostname = $([System.Net.Dns]::GetHostByName($ENV:computerName).Hostname) + Hostname = ([System.Net.Dns]::GetHostEntry((Get-ComputerName)).Hostname) Enabled = $true URLPrefix = 'wsman' @@ -757,7 +757,7 @@ Describe 'WSManListener\GetCurrentState()' -Tag 'HiddenMember' { $currentState.Address | Should -Be '*' $currentState.Issuer | Should -Be 'CN=CONTOSO.COM Issuing CA, DC=CONTOSO, DC=COM' $currentState.CertificateThumbprint | Should -Be '74FA31ADEA7FDD5333CED10910BFA6F665A1F2FC' - $currentState.Hostname | Should -Be $([System.Net.Dns]::GetHostByName($ENV:computerName).Hostname) + $currentState.Hostname | Should -Be ([System.Net.Dns]::GetHostEntry((Get-ComputerName)).Hostname) $currentState.Enabled | Should -BeTrue $currentState.URLPrefix | Should -Be 'wsman' } @@ -941,7 +941,7 @@ Describe 'WSManListener\NewInstance()' -Tag 'HiddenMember' { Should -Invoke -CommandName Get-DscProperty -Exactly -Times 1 -Scope It Should -Invoke -CommandName Find-Certificate -Exactly -Times 1 -Scope It Should -Invoke -CommandName New-WSManInstance -ParameterFilter { - $ValueSet.HostName -eq [System.Net.Dns]::GetHostByName($env:COMPUTERNAME).Hostname + $ValueSet.HostName -eq [System.Net.Dns]::GetHostEntry((Get-ComputerName)).Hostname } -Exactly -Times 1 -Scope It } } diff --git a/tests/Unit/Private/Find-Certificate.Tests.ps1 b/tests/Unit/Private/Find-Certificate.Tests.ps1 index 243c0a9..be01b16 100644 --- a/tests/Unit/Private/Find-Certificate.Tests.ps1 +++ b/tests/Unit/Private/Find-Certificate.Tests.ps1 @@ -35,7 +35,7 @@ BeforeAll { # Create the Mock Objects that will be used for running tests $script:mockCertificateThumbprint = '74FA31ADEA7FDD5333CED10910BFA6F665A1F2FC' $mockFQDN = 'SERVER1.CONTOSO.COM' - $mockHostName = $([System.Net.Dns]::GetHostByName($ENV:computerName).Hostname) + $mockHostName = ([System.Net.Dns]::GetHostEntry((Get-ComputerName)).Hostname) $script:mockIssuer = 'CN=CONTOSO.COM Issuing CA, DC=CONTOSO, DC=COM' $script:mockDN = 'O=Contoso Inc, S=Pennsylvania, C=US' $mockCertificate = @{ From e6a7becb6325de7c76d552c3ff47b001b0e3821c Mon Sep 17 00:00:00 2001 From: Dan Hughes <2237515+dan-hughes@users.noreply.github.com> Date: Sun, 2 Feb 2025 18:06:05 +0000 Subject: [PATCH 129/134] Enable optional enum feature in base class --- source/Classes/020.WSManListener.ps1 | 18 ++++-------------- 1 file changed, 4 insertions(+), 14 deletions(-) diff --git a/source/Classes/020.WSManListener.ps1 b/source/Classes/020.WSManListener.ps1 index 4b79ace..a468047 100644 --- a/source/Classes/020.WSManListener.ps1 +++ b/source/Classes/020.WSManListener.ps1 @@ -121,6 +121,9 @@ class WSManListener : ResourceBase WSManListener () : base ($PSScriptRoot) { + # Enable use of Enums as optional properties + $this.FeatureOptionalEnums = $true + # These properties will not be enforced. $this.ExcludeDscProperties = @( 'Issuer' @@ -224,21 +227,8 @@ class WSManListener : ResourceBase #> hidden [void] AssertProperties([System.Collections.Hashtable] $properties) { - # Filter Out Keys that are enums AND have value of 0 (assumed default) - # https://learn.microsoft.com/en-us/powershell/dsc/concepts/class-based-resources?view=dsc-2.0#use-enums-instead-of-validateset - $FilteredBoundParameters = @{} - foreach ($key in $properties.Keys) - { - $value = $properties.$key - if ($value -is [System.Enum] -and [System.Int32]$value.value__ -eq 0) - { - continue - } - $FilteredBoundParameters.Add($key, $properties.$key) - } - $assertBoundParameterParameters = @{ - BoundParameterList = $FilteredBoundParameters + BoundParameterList = $properties MutuallyExclusiveList1 = @( 'Issuer' 'BaseDN' From 3a1f6a094836f4ca3e9e6f9349da439dd9f5dfd7 Mon Sep 17 00:00:00 2001 From: Dan Hughes <2237515+dan-hughes@users.noreply.github.com> Date: Sun, 2 Feb 2025 18:25:13 +0000 Subject: [PATCH 130/134] Fix changelog --- CHANGELOG.md | 39 +++++++++++++++++++++++++-------------- 1 file changed, 25 insertions(+), 14 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a12eb2e..68cff17 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,31 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Added + +- `WSManReason` + - Used in Class Resources +- `WSManSubjectFormat` Enum +- `WSManTransport` Enum +- `RequiredModules` + - Added `DscResource.Base` class. + +### Changed + +- `DSC_WSManListener` + - Converted to Class Resource. + - Extracted private functions to individual files. + - BREAKING: Renamed parameter `DN` to `BaseDN` - fixes [Issue #89](https://github.com/dsccommunity/WSManDsc/issues/89). +- `DSC_WSManConfig` + - Removed Export-ModuleMember. +- `DSC_WSManServiceConfig` + - Removed Export-ModuleMember. + +### Removed + +- `Get-InvalidOperationRecord` + - This is now provided by `DscResource.Test` + ## [3.2.0] - 2025-01-19 ### Added @@ -13,8 +38,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 for the DSC resource. - Added build task `Generate_Wiki_Content` to generate the wiki content that can be used to update the GitHub Wiki. -- `WSManReason` - - Used in Class Resources ### Changed @@ -61,27 +84,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - `DSC_WSManConfig` - Refactor `Test-TargetResource` to use `Test-DscParameterState`. - Remove unused strings. - - Removed Export-ModuleMember. - `DSC_WSManServiceConfig` - Refactor `Test-TargetResource` to use `Test-DscParameterState`. - Remove unused strings - - Removed Export-ModuleMember. -- `DSC_WSManListener` - - Converted to Class Resource - - Extracted private functions to own files - - BREAKING: Renamed parameter `DN` to `BaseDN` - fixes [Issue #89](https://github.com/dsccommunity/WSManDsc/issues/89). -- `RequiredModules` - - Added `DscResource.Base` class ### Fixed - Fixed pipeline by replacing the GitVersion task in the `azure-pipelines.yml` with a script. -### Removed -- `Get-InvalidOperationRecord` - - This is now provided by `DscResource.Test` - ## [3.1.1] - 2020-01-31 ### Changed From 36e0eca532196a905b9093dd7da9a37dc4e470a5 Mon Sep 17 00:00:00 2001 From: Dan Hughes <2237515+dan-hughes@users.noreply.github.com> Date: Sun, 2 Feb 2025 20:08:39 +0000 Subject: [PATCH 131/134] Remove CommonTestHelper as function now in DscResource.Test --- tests/TestHelpers/CommonTestHelper.psm1 | 48 --------------------- tests/Unit/Classes/WSManListener.Tests.ps1 | 5 --- tests/Unit/DSC_WSManConfig.Tests.ps1 | 5 --- tests/Unit/DSC_WSManServiceConfig.Tests.ps1 | 5 --- 4 files changed, 63 deletions(-) delete mode 100644 tests/TestHelpers/CommonTestHelper.psm1 diff --git a/tests/TestHelpers/CommonTestHelper.psm1 b/tests/TestHelpers/CommonTestHelper.psm1 deleted file mode 100644 index 0542934..0000000 --- a/tests/TestHelpers/CommonTestHelper.psm1 +++ /dev/null @@ -1,48 +0,0 @@ -<# - .SYNOPSIS - Returns an invalid argument exception object - - .PARAMETER Message - The message explaining why this error is being thrown - - .PARAMETER ArgumentName - The name of the invalid argument that is causing this error to be thrown -#> -function Get-InvalidArgumentRecord -{ - [CmdletBinding()] - param - ( - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [System.String] - $Message, - - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [System.String] - $ArgumentName - ) - - $argumentException = New-Object -TypeName 'ArgumentException' -ArgumentList @( $Message, - $ArgumentName ) - $newObjectParams = @{ - TypeName = 'System.Management.Automation.ErrorRecord' - ArgumentList = @( $argumentException, $ArgumentName, 'InvalidArgument', $null ) - } - return New-Object @newObjectParams -} - -<# - .SYNOPSIS - Returns an invalid operation exception object - - .PARAMETER Message - The message explaining why this error is being thrown - - .PARAMETER ErrorRecord - The error record containing the exception that is causing this terminating error -#> - -Export-ModuleMember -Function ` - Get-InvalidArgumentRecord diff --git a/tests/Unit/Classes/WSManListener.Tests.ps1 b/tests/Unit/Classes/WSManListener.Tests.ps1 index 253ff5a..3d4d879 100644 --- a/tests/Unit/Classes/WSManListener.Tests.ps1 +++ b/tests/Unit/Classes/WSManListener.Tests.ps1 @@ -34,8 +34,6 @@ BeforeAll { Import-Module -Name $script:dscModuleName - Import-Module -Name (Join-Path -Path $PSScriptRoot -ChildPath '../../TestHelpers/CommonTestHelper.psm1') - $PSDefaultParameterValues['InModuleScope:ModuleName'] = $script:dscModuleName $PSDefaultParameterValues['Mock:ModuleName'] = $script:dscModuleName $PSDefaultParameterValues['Should:ModuleName'] = $script:dscModuleName @@ -48,9 +46,6 @@ AfterAll { # Unload the module being tested so that it doesn't impact any other tests. Get-Module -Name $script:dscModuleName -All | Remove-Module -Force - - # Remove module common test helper. - Get-Module -Name 'CommonTestHelper' -All | Remove-Module -Force } Describe 'WSManListener' { diff --git a/tests/Unit/DSC_WSManConfig.Tests.ps1 b/tests/Unit/DSC_WSManConfig.Tests.ps1 index c5ba2bb..96d2db3 100644 --- a/tests/Unit/DSC_WSManConfig.Tests.ps1 +++ b/tests/Unit/DSC_WSManConfig.Tests.ps1 @@ -52,8 +52,6 @@ BeforeAll { -ResourceType 'Mof' ` -TestType 'Unit' - Import-Module -Name (Join-Path -Path $PSScriptRoot -ChildPath '..\TestHelpers\CommonTestHelper.psm1') - $PSDefaultParameterValues['InModuleScope:ModuleName'] = $script:dscResourceName $PSDefaultParameterValues['Mock:ModuleName'] = $script:dscResourceName $PSDefaultParameterValues['Should:ModuleName'] = $script:dscResourceName @@ -77,9 +75,6 @@ AfterAll { # Unload the module being tested so that it doesn't impact any other tests. Get-Module -Name $script:dscResourceName -All | Remove-Module -Force - - # Remove module common test helper. - Get-Module -Name 'CommonTestHelper' -All | Remove-Module -Force } Describe "$($script:dscResourceName)\Get-TargetResource" { diff --git a/tests/Unit/DSC_WSManServiceConfig.Tests.ps1 b/tests/Unit/DSC_WSManServiceConfig.Tests.ps1 index 71a0abe..a8f77a1 100644 --- a/tests/Unit/DSC_WSManServiceConfig.Tests.ps1 +++ b/tests/Unit/DSC_WSManServiceConfig.Tests.ps1 @@ -53,8 +53,6 @@ BeforeAll { -ResourceType 'Mof' ` -TestType 'Unit' - Import-Module -Name (Join-Path -Path $PSScriptRoot -ChildPath '..\TestHelpers\CommonTestHelper.psm1') - $PSDefaultParameterValues['InModuleScope:ModuleName'] = $script:dscResourceName $PSDefaultParameterValues['Mock:ModuleName'] = $script:dscResourceName $PSDefaultParameterValues['Should:ModuleName'] = $script:dscResourceName @@ -79,9 +77,6 @@ AfterAll { # Unload the module being tested so that it doesn't impact any other tests. Get-Module -Name $script:dscResourceName -All | Remove-Module -Force - - # Remove module common test helper. - Get-Module -Name 'CommonTestHelper' -All | Remove-Module -Force } Describe "$($script:dscResourceName)\Get-TargetResource" -Tag 'Get' { From efca43162291cf479ff29f4d3c588e93a92a84d5 Mon Sep 17 00:00:00 2001 From: Dan Hughes <2237515+dan-hughes@users.noreply.github.com> Date: Sun, 2 Feb 2025 20:09:21 +0000 Subject: [PATCH 132/134] Update changelog --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 68cff17..2184538 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -27,7 +27,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Removed -- `Get-InvalidOperationRecord` +- `CommonTestHelper` - This is now provided by `DscResource.Test` ## [3.2.0] - 2025-01-19 From 3d7e440628333bce028977222748bb44c48b4468 Mon Sep 17 00:00:00 2001 From: Dan Hughes <2237515+dan-hughes@users.noreply.github.com> Date: Mon, 3 Feb 2025 18:02:10 +0000 Subject: [PATCH 133/134] Use preview doc generator for class updates --- RequiredModules.psd1 | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/RequiredModules.psd1 b/RequiredModules.psd1 index 321d0a5..3b19580 100644 --- a/RequiredModules.psd1 +++ b/RequiredModules.psd1 @@ -28,6 +28,11 @@ 'Indented.ScriptAnalyzerRules' = 'latest' # Prerequisite modules for documentation. - 'DscResource.DocGenerator' = 'latest' + 'DscResource.DocGenerator' = @{ + Version = 'latest' + Parameters = @{ + AllowPrerelease = $true + } + } PlatyPS = 'latest' } From 58171a0879fd53cde1389203778a4e9c442cca66 Mon Sep 17 00:00:00 2001 From: Dan Hughes <2237515+dan-hughes@users.noreply.github.com> Date: Sun, 16 Feb 2025 20:08:48 +0000 Subject: [PATCH 134/134] Move setting Port and Address out of GetCurrentState --- source/Classes/020.WSManListener.ps1 | 24 +++--- tests/Unit/Classes/WSManListener.Tests.ps1 | 92 ++++++++-------------- 2 files changed, 46 insertions(+), 70 deletions(-) diff --git a/source/Classes/020.WSManListener.ps1 b/source/Classes/020.WSManListener.ps1 index a468047..ff030e7 100644 --- a/source/Classes/020.WSManListener.ps1 +++ b/source/Classes/020.WSManListener.ps1 @@ -146,18 +146,6 @@ class WSManListener : ResourceBase Transport = $properties.Transport } - # Get the port if it's not provided and resource should exist - if (-not $this.Port -and $this.Ensure -eq [Ensure]::Present) - { - $this.Port = Get-DefaultPort @getParameters - } - - # Get the Address if it's not provided and resource should exist - if (-not $this.Address -and $this.Ensure -eq [Ensure]::Present) - { - $this.Address = '*' - } - $state = @{} $getCurrentStateResult = Get-Listener @getParameters @@ -246,6 +234,18 @@ class WSManListener : ResourceBase 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 = @{ diff --git a/tests/Unit/Classes/WSManListener.Tests.ps1 b/tests/Unit/Classes/WSManListener.Tests.ps1 index 3d4d879..6fc8e25 100644 --- a/tests/Unit/Classes/WSManListener.Tests.ps1 +++ b/tests/Unit/Classes/WSManListener.Tests.ps1 @@ -599,7 +599,7 @@ Describe 'WSManListener\GetCurrentState()' -Tag 'HiddenMember' { } Context 'When the object is present in the current state' { - Context 'When ''Port'' and ''Address'' are supplied for HTTP Transport' { + Context 'When getting a HTTP Transport' { BeforeAll { InModuleScope -ScriptBlock { Set-StrictMode -Version 1.0 @@ -652,61 +652,7 @@ Describe 'WSManListener\GetCurrentState()' -Tag 'HiddenMember' { } } - Context 'When ''Port'' and ''Address'' are not supplied for HTTP Transport' { - BeforeAll { - InModuleScope -ScriptBlock { - Set-StrictMode -Version 1.0 - - $script:mockInstance = [WSManListener] @{ - Transport = 'HTTP' - Ensure = 'Present' - } - } - - Mock -CommandName Get-Listener -MockWith { - return @{ - Transport = 'HTTP' - Port = [System.UInt16] 5985 - Address = '*' - - CertificateThumbprint = $null - Hostname = [System.Net.Dns]::GetHostEntry((Get-ComputerName)).Hostname - - Enabled = $true - URLPrefix = 'wsman' - } - } - - Mock -CommandName Get-DefaultPort -MockWith { return [System.UInt16] 5985 } - } - - It 'Should return the correct values' { - InModuleScope -ScriptBlock { - Set-StrictMode -Version 1.0 - - $currentState = $script:mockInstance.GetCurrentState( - @{ - Transport = 'HTTP' - Ensure = [Ensure]::Present - } - ) - - $currentState.Transport | Should -Be 'HTTP' - $currentState.Port | Should -Be 5985 - $currentState.Address | Should -Be '*' - $currentState.Issuer | Should -BeNullOrEmpty - $currentState.CertificateThumbprint | Should -BeNullOrEmpty - $currentState.Hostname | Should -Be ([System.Net.Dns]::GetHostEntry((Get-ComputerName)).Hostname) - $currentState.Enabled | Should -BeTrue - $currentState.URLPrefix | Should -Be 'wsman' - } - - Should -Invoke -CommandName Get-Listener -Exactly -Times 1 -Scope It - Should -Invoke -CommandName Get-DefaultPort -Exactly -Times 1 -Scope It - } - } - - Context 'When ''Port'' and ''Address'' are supplied for HTTPS Transport' { + Context 'When getting a HTTPS Transport' { BeforeAll { InModuleScope -ScriptBlock { Set-StrictMode -Version 1.0 @@ -726,14 +672,16 @@ Describe 'WSManListener\GetCurrentState()' -Tag 'HiddenMember' { Address = '*' CertificateThumbprint = '74FA31ADEA7FDD5333CED10910BFA6F665A1F2FC' - Hostname = ([System.Net.Dns]::GetHostEntry((Get-ComputerName)).Hostname) + Hostname = [System.Net.Dns]::GetHostEntry((Get-ComputerName)).Hostname Enabled = $true URLPrefix = 'wsman' } } - Mock -CommandName Find-Certificate -MockWith { return @{ Issuer = 'CN=CONTOSO.COM Issuing CA, DC=CONTOSO, DC=COM' } } + Mock -CommandName Find-Certificate -MockWith { + return @{ Issuer = 'CN=CONTOSO.COM Issuing CA, DC=CONTOSO, DC=COM' } + } } It 'Should return the correct values' { @@ -964,6 +912,34 @@ Describe 'WSManListener\NewInstance()' -Tag 'HiddenMember' { } } } + + Context 'When the parameters ''Port'' and ''Address'' is not set' { + BeforeAll { + InModuleScope -ScriptBlock { + Set-StrictMode -Version 1.0 + + $script:mockInstance = [WSManListener] @{ + Transport = 'HTTP' + Ensure = 'Present' + } + } + + Mock -CommandName Get-DefaultPort -MockWith { + return [System.UInt16] 5985 + } + } + + It 'Should create the listener correctly' { + InModuleScope -ScriptBlock { + Set-StrictMode -Version 1.0 + + $script:mockInstance.NewInstance() + } + + Should -Invoke -CommandName New-WSManInstance -Exactly -Times 1 -Scope It + Should -Invoke -CommandName Get-DefaultPort -Exactly -Times 1 -Scope It + } + } } Describe 'WSManListener\RemoveInstance()' -Tag 'HiddenMember' {