diff --git a/CHANGELOG.md b/CHANGELOG.md index 68c0e26..f2903c7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Added `DscResource.Base` class. - `WSManConfigBase` - Base class for WSMan*Config Resources fixes [#44](https://github.com/dsccommunity/WSManDsc/issues/44). +- `WSManClientConfig` resource. Fixes [#41](https://github.com/dsccommunity/WSManDsc/issues/41). ### Changed 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/source/Classes/010.WSManConfigBase.ps1 b/source/Classes/010.WSManConfigBase.ps1 index 9f6ca53..496ace4 100644 --- a/source/Classes/010.WSManConfigBase.ps1 +++ b/source/Classes/010.WSManConfigBase.ps1 @@ -70,6 +70,12 @@ class WSManConfigBase : ResourceBase continue } + if ($targetType -eq [System.String[]]) + { + $state[$property.Name] = $property.Value -split ',' + continue + } + $state[$property.Name] = [System.Management.Automation.LanguagePrimitives]::ConvertTo( $property.Value, $targetType @@ -89,14 +95,19 @@ class WSManConfigBase : ResourceBase foreach ($property in $properties.GetEnumerator()) { + $uri = $baseUri if ($property.Name.StartsWith('Auth')) { $property.Name = $property.Name -replace '^Auth', '' - Set-Item -Path ('{0}\Auth\{1}' -f $baseUri, $property.Name) -Value $property.Value -Force - continue + $uri = ('{0}\Auth' -f $baseUri) + } + + if ($property.Value -is [System.String[]]) + { + $property.Value = $property.Value -join ',' } - Set-Item -Path ('{0}\{1}' -f $baseUri, $property.Name) -Value $property.Value -Force + Set-Item -Path ('{0}\{1}' -f $uri, $property.Name) -Value $property.Value -Force } } diff --git a/source/Classes/020.WSManClientConfig.ps1 b/source/Classes/020.WSManClientConfig.ps1 new file mode 100644 index 0000000..131dc75 --- /dev/null +++ b/source/Classes/020.WSManClientConfig.ps1 @@ -0,0 +1,131 @@ +<# + .SYNOPSIS + The `WSManClientConfig` DSC resource is used to configure WS-Man client specific settings. + + .DESCRIPTION + This resource is used configure WS-Man Client settings. + + .PARAMETER NetworkDelayms + Specifies the extra time in milliseconds that the client computer waits to accommodate for network delay time. + + .PARAMETER URLPrefix + Specifies a URL prefix on which to accept HTTP or HTTPS requests. The default URL prefix is wsman. + + .PARAMETER AllowUnencrypted + Allows the client computer to request unencrypted traffic. + + .PARAMETER TrustedHosts + Specifies the list of remote computers that are trusted. + + .PARAMETER AuthBasic + Allows the WinRM client to use Basic authentication. + + .PARAMETER AuthDigest + Allows the WinRM client to use Digest authentication. + + .PARAMETER AuthCertificate + Allows the WinRM client to use client certificate-based authentication. + + .PARAMETER AuthKerberos + Allows the WinRM client to use Kerberos authentication. + + .PARAMETER AuthNegotiate + Allows the WinRM client to use Negotiate authentication. + + .PARAMETER AuthCredSSP + Allows the WinRM client to use Credential Security Support Provider (CredSSP) authentication. +#> + +[DscResource()] +class WSManClientConfig : WSManConfigBase +{ + [DscProperty()] + [Nullable[System.UInt32]] + $NetworkDelayms + + [DscProperty()] + [System.String] + $URLPrefix + + [DscProperty()] + [Nullable[System.Boolean]] + $AllowUnencrypted + + [DscProperty()] + [System.String[]] + $TrustedHosts + + [DscProperty()] + [Nullable[System.Boolean]] + $AuthBasic + + [DscProperty()] + [Nullable[System.Boolean]] + $AuthDigest + + [DscProperty()] + [Nullable[System.Boolean]] + $AuthCertificate + + [DscProperty()] + [Nullable[System.Boolean]] + $AuthKerberos + + [DscProperty()] + [Nullable[System.Boolean]] + $AuthNegotiate + + [DscProperty()] + [Nullable[System.Boolean]] + $AuthCredSSP + + WSManClientConfig () : base () + { + $this.ResourceURI = 'localhost\Client' + $this.HasAuthContainer = $true + } + + [WSManClientConfig] Get() + { + # Call the base method to return the properties. + return ([ResourceBase] $this).Get() + } + + [void] Set() + { + # Call the base method to enforce the properties. + ([ResourceBase] $this).Set() + } + + [System.Boolean] Test() + { + # Call the base method to test all of the properties that should be enforced. + return ([ResourceBase] $this).Test() + } + + <# + Base method Assert() call this method with the properties that was assigned + a value. + #> + hidden [void] AssertProperties([System.Collections.Hashtable] $properties) + { + $assertBoundParameterParameters = @{ + BoundParameterList = $properties + RequiredParameter = @( + 'NetworkDelayms' + 'URLPrefix' + 'AllowUnencrypted' + 'TrustedHosts' + 'AuthBasic' + 'AuthDigest' + 'AuthCertificate' + 'AuthKerberos' + 'AuthNegotiate' + 'AuthCredSSP' + ) + RequiredBehavior = 'Any' + } + + Assert-BoundParameter @assertBoundParameterParameters + } +} diff --git a/source/Examples/Resources/WSManClientConfig/1-WSManClientConfig_Config.ps1 b/source/Examples/Resources/WSManClientConfig/1-WSManClientConfig_Config.ps1 new file mode 100644 index 0000000..db0be53 --- /dev/null +++ b/source/Examples/Resources/WSManClientConfig/1-WSManClientConfig_Config.ps1 @@ -0,0 +1,40 @@ +<#PSScriptInfo +.VERSION 1.0.0 +.GUID 5eb95759-9a02-4121-bfca-bba124bfc4f8 +.AUTHOR DSC Community +.COMPANYNAME DSC Community +.COPYRIGHT Copyright the DSC Community contributors. All rights reserved. +.TAGS DSCConfiguration +.LICENSEURI https://github.com/dsccommunity/WSManDsc/blob/main/LICENSE +.PROJECTURI https://github.com/dsccommunity/WSManDsc +.ICONURI +.EXTERNALMODULEDEPENDENCIES +.REQUIREDSCRIPTS +.EXTERNALSCRIPTDEPENDENCIES +.RELEASENOTES First version. +.PRIVATEDATA 2016-Datacenter,2016-Datacenter-Server-Core +#> + +#Requires -module WSManDsc + +<# + .DESCRIPTION + Set the WS-Man client to disallow unencrypted traffic, + disable Basic authentication and set TrustedHosts to '*'. +#> +Configuration WSManClientConfig_Config +{ + Import-DscResource -Module WSManDsc + + Node localhost + { + WSManClientConfig ClientConfig + { + IsSingleInstance = 'Yes' + AllowUnencrypted = $false + AuthBasic = $false + TrustedHosts = '*' + + } # End of WSManClientConfig Resource + } # End of Node +} # End of Configuration diff --git a/source/en-US/WSManClientConfig.strings.psd1 b/source/en-US/WSManClientConfig.strings.psd1 new file mode 100644 index 0000000..c4b5c7b --- /dev/null +++ b/source/en-US/WSManClientConfig.strings.psd1 @@ -0,0 +1,14 @@ +<# + .SYNOPSIS + The localized resource strings in English (en-US) for the + resource WSManClientConfig. 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 WSManClientConfig. +'@ diff --git a/source/en-US/WSManConfig.strings.psd1 b/source/en-US/WSManConfig.strings.psd1 index 92f30e2..72ee36c 100644 --- a/source/en-US/WSManConfig.strings.psd1 +++ b/source/en-US/WSManConfig.strings.psd1 @@ -1,7 +1,7 @@ <# .SYNOPSIS The localized resource strings in English (en-US) for the - resource WSManConfig module. This file should only contain + resource WSManConfig. This file should only contain localized strings for private functions, public command, and classes (that are not a DSC resource). #> diff --git a/source/en-US/WSManListener.strings.psd1 b/source/en-US/WSManListener.strings.psd1 index 8abb4c4..3898a1a 100644 --- a/source/en-US/WSManListener.strings.psd1 +++ b/source/en-US/WSManListener.strings.psd1 @@ -1,7 +1,7 @@ <# .SYNOPSIS The localized resource strings in English (en-US) for the - resource WSManListener module. This file should only contain + resource WSManListener. This file should only contain localized strings for private functions, public command, and classes (that are not a DSC resource). #> diff --git a/source/en-US/WSManServiceConfig.strings.psd1 b/source/en-US/WSManServiceConfig.strings.psd1 index f04e364..e76373f 100644 --- a/source/en-US/WSManServiceConfig.strings.psd1 +++ b/source/en-US/WSManServiceConfig.strings.psd1 @@ -1,7 +1,7 @@ <# .SYNOPSIS The localized resource strings in English (en-US) for the - resource WSManServiceConfig module. This file should only contain + resource WSManServiceConfig. This file should only contain localized strings for private functions, public command, and classes (that are not a DSC resource). #> diff --git a/tests/Integration/DSC_WSManClientConfig.Integration.Tests.ps1 b/tests/Integration/DSC_WSManClientConfig.Integration.Tests.ps1 new file mode 100644 index 0000000..52493ef --- /dev/null +++ b/tests/Integration/DSC_WSManClientConfig.Integration.Tests.ps1 @@ -0,0 +1,236 @@ +<# + .SYNOPSIS + Integration test for DSC_WSManClientConfig 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' 3>&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.' + } + + $dscResourceName = 'DSC_WSManClientConfig' + + $ParameterList = @( + @{ + Name = 'NetworkDelayms' + Path = 'NetworkDelayms' + Type = 'UInt32' + Default = 5000 + TestVal = 5001 + IntTest = $true + }, + @{ + Name = 'URLPrefix' + Path = 'URLPrefix' + Type = 'String' + Default = 'wsman' + TestVal = 'wsmanTest' + IntTest = $true + }, + @{ + Name = 'AllowUnencrypted' + Path = 'AllowUnencrypted' + Type = 'Boolean' + Default = $false + TestVal = $true + IntTest = $true + }, + @{ + Name = 'TrustedHosts' + Path = 'TrustedHosts' + Type = 'String[]' + Default = '' + TestVal = @('testserver1.contoso.com','testserver2.contoso.com') + IntTest = $true + }, + @{ + Name = 'AuthBasic' + Path = 'Auth\Basic' + Type = 'Boolean' + Default = $false + TestVal = $true + IntTest = $false + }, + @{ + Name = 'AuthDigest' + Path = 'Auth\Digest' + Type = 'Boolean' + Default = $false + TestVal = $true + IntTest = $false + }, + @{ + Name = 'AuthKerberos' + Path = 'Auth\Kerberos' + Type = 'Boolean' + Default = $true + TestVal = $false + IntTest = $false + }, + @{ + Name = 'AuthNegotiate' + Path = 'Auth\Negotiate' + Type = 'Boolean' + Default = $true + TestVal = $false + IntTest = $false + }, + @{ + Name = 'AuthCertificate' + Path = 'Auth\Certificate' + Type = 'Boolean' + Default = $false + TestVal = $true + IntTest = $true + }, + @{ + Name = 'AuthCredSSP' + Path = 'Auth\CredSSP' + Type = 'Boolean' + Default = $false + TestVal = $true + IntTest = $true + } + ) + + $script:wsmanClientConfigParameterList = $ParameterList.Where({ $_.IntTest } ) +} + +BeforeAll { + $script:dscModuleName = 'WSManDsc' + $script:dscResourceName = 'DSC_WSManClientConfig' + + $script:testEnvironment = Initialize-TestEnvironment ` + -DSCModuleName $script:dscModuleName ` + -DSCResourceName $script:dscResourceName ` + -ResourceType 'Mof' ` + -TestType 'Integration' + + # Backup the existing settings + $currentWsManClientConfig = @{} + + foreach ($parameter in $wsmanClientConfigParameterList) + { + $parameterPath = Join-Path ` + -Path 'WSMan:\Localhost\Client\' ` + -ChildPath $parameter.Path + $currentWsManClientConfig.$($Parameter.Name) = (Get-Item -Path $parameterPath).Value + } # foreach + + # Make sure WS-Man is enabled + if (-not (Get-PSProvider -PSProvider WSMan -ErrorAction SilentlyContinue)) + { + $null = Enable-PSRemoting ` + -SkipNetworkProfileCheck ` + -Force ` + -ErrorAction Stop + } # if + + # Set the Service Config to default settings + foreach ($parameter in $wsmanClientConfigParameterList) + { + $parameterPath = Join-Path ` + -Path 'WSMan:\Localhost\Client\' ` + -ChildPath $parameter.Path + + Set-Item -Path $parameterPath -Value $($parameter.Default) -Force + } # foreach +} + +AfterAll { + # Clean up by restoring all parameters + foreach ($parameter in $wsmanClientConfigParameterList) + { + $parameterPath = Join-Path ` + -Path 'WSMan:\Localhost\Client\' ` + -ChildPath $parameter.Path + Set-Item -Path $parameterPath -Value $currentWsManClientConfig.$($parameter.Name) -Force + } # foreach + + Restore-TestEnvironment -TestEnvironment $script:testEnvironment +} + +Describe "$($script:dscResourceName)_Integration" { + BeforeAll { + $ConfigFile = Join-Path -Path $PSScriptRoot -ChildPath "$($script:dscResourceName).config.ps1" + . $ConfigFile + + $configData = @{ + AllNodes = @( + @{ + NodeName = 'localhost' + } + ) + } + foreach ($parameter in $wsmanClientConfigParameterList) + { + $configData.AllNodes[0] += @{ + $($parameter.Name) = $($parameter.TestVal) + } + } # foreach + } + + AfterEach { + Wait-ForIdleLcm + } + + It 'Should compile without throwing' { + { + & "$($script:dscResourceName)_Config" ` + -OutputPath $TestDrive ` + -ConfigurationData $configData + + Write-Verbose "TestDrive = $($TestDrive)" + + $startDscConfigurationParameters = @{ + Path = $TestDrive + ComputerName = 'localhost' + Wait = $true + Verbose = $true + Force = $true + ErrorAction = 'Stop' + } + + Start-DscConfiguration @startDscConfigurationParameters + } | Should -Not -Throw + } + + It 'Should be able to call Get-DscConfiguration without throwing' { + { Get-DscConfiguration -Verbose -ErrorAction Stop } | Should -Not -Throw + } + + It 'Should have set the resource and all the parameters should match' -ForEach $wsmanClientConfigParameterList { + $parameterPath = Join-Path ` + -Path 'WSMan:\Localhost\Client\' ` + -ChildPath $Path + + if ($Type -eq 'String[]') + { + $TestVal = $TestVal -join ',' + } + + (Get-Item -Path $parameterPath).Value | Should -Be $TestVal + } +} diff --git a/tests/Integration/DSC_WSManClientConfig.config.ps1 b/tests/Integration/DSC_WSManClientConfig.config.ps1 new file mode 100644 index 0000000..94fc1fa --- /dev/null +++ b/tests/Integration/DSC_WSManClientConfig.config.ps1 @@ -0,0 +1,25 @@ +# Integration Test Config Template Version: 1.0.0 +configuration DSC_WSManClientConfig_Config { + Import-DscResource -ModuleName WSManDsc + + node $AllNodes.NodeName { + WSManClientConfig Integration_Test + { + IsSingleInstance = 'Yes' + NetworkDelayms = $Node.NetworkDelayms + URLPrefix = $Node.URLPrefix + AllowUnencrypted = $Node.AllowUnencrypted + TrustedHosts = $Node.TrustedHosts + <# +Integration testing these values can result in difficult to reverse damage to the test server. +So these tests are disabled. Only perform them on a disposable test server. + AuthBasic = $Node.AuthBasic + AuthDigest = $Node.AuthDigest + AuthKerberos = $Node.AuthKerberos + AuthNegotiate = $Node.AuthNegotiate +#> + AuthCertificate = $Node.AuthCertificate + AuthCredSSP = $Node.AuthCredSSP + } + } +} diff --git a/tests/Integration/DSC_WSManConfig.Integration.Tests.ps1 b/tests/Integration/DSC_WSManConfig.Integration.Tests.ps1 index 4b0552d..c7b60a5 100644 --- a/tests/Integration/DSC_WSManConfig.Integration.Tests.ps1 +++ b/tests/Integration/DSC_WSManConfig.Integration.Tests.ps1 @@ -134,6 +134,10 @@ Describe "$($script:dscResourceName)_Integration" { } # foreach } + AfterEach { + Wait-ForIdleLcm + } + It 'Should compile without throwing' { { & "$($script:dscResourceName)_Config" ` diff --git a/tests/Unit/Classes/WSManClientConfig.Tests.ps1 b/tests/Unit/Classes/WSManClientConfig.Tests.ps1 new file mode 100644 index 0000000..da66506 --- /dev/null +++ b/tests/Unit/Classes/WSManClientConfig.Tests.ps1 @@ -0,0 +1,501 @@ +<# + .SYNOPSIS + Unit test for WSManClientConfig 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' 3>&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 'WSManClientConfig' { + Context 'When class is instantiated' { + It 'Should not throw an exception' { + InModuleScope -ScriptBlock { + Set-StrictMode -Version 1.0 + + { [WSManClientConfig]::new() } | Should -Not -Throw + } + } + + It 'Should have a default or empty constructor' { + InModuleScope -ScriptBlock { + Set-StrictMode -Version 1.0 + + $instance = [WSManClientConfig]::new() + $instance | Should -Not -BeNullOrEmpty + } + } + + It 'Should be the correct type' { + InModuleScope -ScriptBlock { + Set-StrictMode -Version 1.0 + + $instance = [WSManClientConfig]::new() + $instance.GetType().Name | Should -Be 'WSManClientConfig' + } + } + } +} + +Describe 'WSManClientConfig\Get()' -Tag 'Get' { + Context 'When the system is in the desired state' { + BeforeAll { + InModuleScope -ScriptBlock { + Set-StrictMode -Version 1.0 + + $script:mockInstance = [WSManClientConfig] @{ + IsSingleInstance = 'Yes' + NetworkDelayms = 5000 + URLPrefix = 'wsmclient' + AllowUnencrypted = $false + TrustedHosts = @() + AuthBasic = $false + AuthDigest = $false + AuthCertificate = $true + AuthKerberos = $true + AuthNegotiate = $true + AuthCredSSP = $false + } + + <# + 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:mockInstance | + Add-Member -Force -MemberType 'ScriptMethod' -Name 'GetCurrentState' -Value { + return @{ + IsSingleInstance = 'Yes' + NetworkDelayms = [System.UInt32] 5000 + URLPrefix = 'wsmclient' + AllowUnencrypted = $false + TrustedHosts = [System.String[]] @() + AuthBasic = $false + AuthDigest = $false + AuthCertificate = $true + AuthKerberos = $true + AuthNegotiate = $true + AuthCredSSP = $false + } + } -PassThru | + Add-Member -Force -MemberType 'ScriptMethod' -Name 'Assert' -Value { + return + } -PassThru | + Add-Member -Force -MemberType 'ScriptMethod' -Name 'Normalize' -Value { + return + } -PassThru + } + } + + It 'Should return the correct values' { + InModuleScope -ScriptBlock { + Set-StrictMode -Version 1.0 + + $currentState = $script:mockInstance.Get() + + $currentState.IsSingleInstance | Should -Be 'Yes' + + $currentState.NetworkDelayms | Should -Be 5000 + $currentState.NetworkDelayms | Should -BeOfType System.UInt32 + + $currentState.URLPrefix | Should -Be 'wsmclient' + $currentState.URLPrefix | Should -BeOfType System.String + + $currentState.TrustedHosts | Should -BeNullOrEmpty + + $currentState.AuthBasic | Should -BeFalse + $currentState.AuthBasic | Should -BeOfType System.Boolean + + $currentState.AuthDigest | Should -BeFalse + $currentState.AuthDigest | Should -BeOfType System.Boolean + + $currentState.AuthKerberos | Should -BeTrue + $currentState.AuthKerberos | Should -BeOfType System.Boolean + + $currentState.AuthNegotiate | Should -BeTrue + $currentState.AuthNegotiate | Should -BeOfType System.Boolean + + $currentState.AuthCertificate | Should -BeTrue + $currentState.AuthCertificate | Should -BeOfType System.Boolean + + $currentState.AuthCredSSP | Should -BeFalse + $currentState.AuthCredSSP | Should -BeOfType System.Boolean + + $currentState.Reasons | Should -BeNullOrEmpty + } + } + } + + Context 'When the system is not in the desired state' { + Context 'When property ''MaxConnections'' has the wrong value' { + BeforeAll { + InModuleScope -ScriptBlock { + Set-StrictMode -Version 1.0 + + $script:mockInstance = [WSManClientConfig] @{ + IsSingleInstance = 'Yes' + URLPrefix = 'wsmclient' + } + + <# + 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:mockInstance | + Add-Member -Force -MemberType 'ScriptMethod' -Name 'GetCurrentState' -Value { + return @{ + IsSingleInstance = 'Yes' + URLPrefix = 'wsmclient4' + } + } -PassThru | + Add-Member -Force -MemberType 'ScriptMethod' -Name 'Assert' -Value { + return + } -PassThru | + Add-Member -Force -MemberType 'ScriptMethod' -Name 'Normalize' -Value { + return + } -PassThru + } + } + + It 'Should return the correct values' { + InModuleScope -ScriptBlock { + Set-StrictMode -Version 1.0 + + $currentState = $script:mockInstance.Get() + + $currentState.IsSingleInstance | Should -Be 'Yes' + + $currentState.URLPrefix | Should -Be 'wsmclient4' + $currentState.URLPrefix | Should -BeOfType System.String + + $currentState.Reasons | Should -HaveCount 1 + $currentState.Reasons[0].Code | Should -Be 'WSManClientConfig:WSManClientConfig:URLPrefix' + $currentState.Reasons[0].Phrase | Should -Be 'The property URLPrefix should be "wsmclient", but was "wsmclient4"' + } + } + } + } +} + +Describe 'WSManClientConfig\Set()' -Tag 'Set' { + BeforeAll { + InModuleScope -ScriptBlock { + Set-StrictMode -Version 1.0 + + $script:mockInstance = [WSManClientConfig] @{ + IsSingleInstance = 'Yes' + NetworkDelayms = 5000 + URLPrefix = 'wsmclient' + AllowUnencrypted = $false + TrustedHosts = @() + AuthBasic = $false + AuthDigest = $false + AuthCertificate = $true + AuthKerberos = $true + AuthNegotiate = $true + AuthCredSSP = $false + } | + # 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:methodTestCallCount = 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 Test() which is called by the base method Set() + Add-Member -Force -MemberType 'ScriptMethod' -Name 'Test' -Value { + $script:methodTestCallCount += 1 + return $true + } + } + } + + It 'Should not call method Modify()' { + InModuleScope -ScriptBlock { + Set-StrictMode -Version 1.0 + + $null = $script:mockInstance.Set() + + $script:methodTestCallCount | Should -Be 1 + $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 Test() which is called by the base method Set() + Add-Member -Force -MemberType 'ScriptMethod' -Name 'Test' -Value { + $script:methodTestCallCount += 1 + return $false + } + + $script:mockInstance.PropertiesNotInDesiredState = @( + @{ + Property = 'AuthKerberos' + ExpectedValue = $true + ActualValue = $false + } + ) + } + } + + It 'Should call method Modify()' { + InModuleScope -ScriptBlock { + Set-StrictMode -Version 1.0 + + $null = $script:mockInstance.Set() + + $script:methodTestCallCount | Should -Be 1 + $script:methodModifyCallCount | Should -Be 1 + } + } + } +} + +Describe 'WSManClientConfig\Test()' -Tag 'Test' { + BeforeAll { + InModuleScope -ScriptBlock { + Set-StrictMode -Version 1.0 + + $script:mockInstance = [WSManClientConfig] @{ + IsSingleInstance = 'Yes' + NetworkDelayms = 5000 + URLPrefix = 'wsmclient' + AllowUnencrypted = $false + TrustedHosts = @() + AuthBasic = $false + AuthDigest = $false + AuthCertificate = $true + AuthKerberos = $true + AuthNegotiate = $true + AuthCredSSP = $false + } + } + } + + BeforeEach { + InModuleScope -ScriptBlock { + Set-StrictMode -Version 1.0 + + $script:getMethodCallCount = 0 + } + } + + Context 'When the system is in the desired state' { + BeforeAll { + InModuleScope -ScriptBlock { + Set-StrictMode -Version 1.0 + + $script:mockInstance | + # Mock method Get() which is called by the base method Test() + Add-Member -Force -MemberType 'ScriptMethod' -Name 'Get' -Value { + $script:getMethodCallCount += 1 + } + } + } + + It 'Should return $true' { + InModuleScope -ScriptBlock { + Set-StrictMode -Version 1.0 + + $script:mockInstance.Test() | Should -BeTrue + + $script:getMethodCallCount | Should -Be 1 + } + } + } + + Context 'When the system is not in the desired state' { + BeforeAll { + InModuleScope -ScriptBlock { + Set-StrictMode -Version 1.0 + + $script:mockInstance | + # Mock method Get() which is called by the base method Test() + Add-Member -Force -MemberType 'ScriptMethod' -Name 'Get' -Value { + $script:getMethodCallCount += 1 + } + + $script:mockInstance.PropertiesNotInDesiredState = @( + @{ + Property = 'AuthCredSSP' + ExpectedValue = $false + ActualValue = $true + } + ) + } + } + + It 'Should return $false' { + InModuleScope -ScriptBlock { + Set-StrictMode -Version 1.0 + + $script:mockInstance.Test() | Should -BeFalse + + $script:getMethodCallCount | Should -Be 1 + } + } + } +} + +Describe 'WSManClientConfig\AssertProperties()' -Tag 'AssertProperties' { + BeforeAll { + InModuleScope -ScriptBlock { + Set-StrictMode -Version 1.0 + + $script:mockInstance = [WSManClientConfig] @{} + } + } + + Context 'When required parameters are missing' { + BeforeDiscovery { + $testCases = @( + @{ + IsSingleInstance = 'Yes' + } + ) + } + + It 'Should throw the correct error' -ForEach $testCases { + InModuleScope -Parameters @{ + mockProperties = $_ + } -ScriptBlock { + Set-StrictMode -Version 1.0 + + { + $mockInstance.AssertProperties($mockProperties) + } | Should -Throw -ExpectedMessage ('*' + 'DRC0050' + '*') + } + } + } + + Context 'When passing required parameters' { + BeforeDiscovery { + $testCases = @( + @{ + URLPrefix = 'wsmclient' + NetworkDelayms = 5000 + AllowUnencrypted = $false + TrustedHosts = @() + AuthBasic = $false + AuthDigest = $false + AuthCertificate = $true + AuthKerberos = $true + AuthNegotiate = $true + AuthCredSSP = $false + } + @{ + NetworkDelayms = 5000 + AllowUnencrypted = $false + TrustedHosts = @() + AuthBasic = $false + AuthDigest = $false + AuthCertificate = $true + AuthKerberos = $true + AuthNegotiate = $true + AuthCredSSP = $false + } + @{ + TrustedHosts = @() + AuthBasic = $false + AuthDigest = $false + AuthCertificate = $true + AuthKerberos = $true + AuthNegotiate = $true + AuthCredSSP = $false + } + @{ + AuthBasic = $false + AuthDigest = $false + AuthCertificate = $true + AuthKerberos = $true + AuthNegotiate = $true + AuthCredSSP = $false + } + @{ + AuthDigest = $false + AuthCertificate = $true + AuthKerberos = $true + AuthNegotiate = $true + AuthCredSSP = $false + } + ) + } + + It 'Should not throw an error' -ForEach $testCases { + InModuleScope -Parameters @{ + mockProperties = $_ + } -ScriptBlock { + Set-StrictMode -Version 1.0 + + { $mockInstance.AssertProperties($mockProperties) } | Should -Not -Throw + } + } + } +}