From 314d40a07bfdba752cbec266757f1453d0988448 Mon Sep 17 00:00:00 2001 From: Dan Hughes <2237515+dan-hughes@users.noreply.github.com> Date: Thu, 24 Jul 2025 18:04:08 +0100 Subject: [PATCH 01/20] Remove MOF --- .../DSC_AdcsOnlineResponder.psm1 | 269 ------------ .../DSC_AdcsOnlineResponder.schema.mof | 7 - .../DSC_AdcsOnlineResponder/README.md | 7 - .../DSC_AdcsOnlineResponder.strings.psd1 | 13 - tests/Unit/DSC_AdcsOnlineResponder.Tests.ps1 | 398 ------------------ 5 files changed, 694 deletions(-) delete mode 100644 source/DSCResources/DSC_AdcsOnlineResponder/DSC_AdcsOnlineResponder.psm1 delete mode 100644 source/DSCResources/DSC_AdcsOnlineResponder/DSC_AdcsOnlineResponder.schema.mof delete mode 100644 source/DSCResources/DSC_AdcsOnlineResponder/README.md delete mode 100644 source/DSCResources/DSC_AdcsOnlineResponder/en-US/DSC_AdcsOnlineResponder.strings.psd1 delete mode 100644 tests/Unit/DSC_AdcsOnlineResponder.Tests.ps1 diff --git a/source/DSCResources/DSC_AdcsOnlineResponder/DSC_AdcsOnlineResponder.psm1 b/source/DSCResources/DSC_AdcsOnlineResponder/DSC_AdcsOnlineResponder.psm1 deleted file mode 100644 index bae7768..0000000 --- a/source/DSCResources/DSC_AdcsOnlineResponder/DSC_AdcsOnlineResponder.psm1 +++ /dev/null @@ -1,269 +0,0 @@ -$modulePath = Join-Path -Path (Split-Path -Path (Split-Path -Path $PSScriptRoot -Parent) -Parent) -ChildPath 'Modules' - -# Import the ADCS Deployment Resource Common Module. -Import-Module -Name (Join-Path -Path $modulePath ` - -ChildPath (Join-Path -Path 'ActiveDirectoryCSDsc.Common' ` - -ChildPath 'ActiveDirectoryCSDsc.Common.psm1')) - -Import-Module -Name (Join-Path -Path $modulePath -ChildPath 'DscResource.Common') - -# Import Localization Strings. -$script:localizedData = Get-LocalizedData -DefaultUICulture 'en-US' - -<# - .SYNOPSIS - Returns an object containing the current state information for the ADCS Online Responder. - - .PARAMETER IsSingleInstance - Specifies the resource is a single instance, the value must be 'Yes'. - - .PARAMETER Credential - If the Online Responder service is configured to use Standalone certification authority, - then an account that is a member of the local Administrators on the CA is required. If - the Online Responder service is configured to use an Enterprise CA, then an account that - is a member of Domain Admins is required. - - .PARAMETER Ensure - Specifies whether the Online Responder feature should be installed or uninstalled. - - .OUTPUTS - Returns an object containing the ADCS Online Responder state information. -#> -function Get-TargetResource -{ - [CmdletBinding()] - [OutputType([System.Collections.Hashtable])] - param - ( - [Parameter(Mandatory = $true)] - [ValidateSet('Yes')] - [System.String] - $IsSingleInstance, - - [Parameter(Mandatory = $true)] - [System.Management.Automation.PSCredential] - [System.Management.Automation.Credential()] - $Credential, - - [Parameter()] - [ValidateSet('Present', 'Absent')] - [System.String] - $Ensure = 'Present' - ) - - Write-Verbose -Message ( @( - "$($MyInvocation.MyCommand): " - $($script:localizedData.GettingAdcsOnlineResponderStatusMessage) - ) -join '' ) - - $adcsParameters = @{ } + $PSBoundParameters - $null = $adcsParameters.Remove('IsSingleInstance') - $null = $adcsParameters.Remove('Ensure') - $null = $adcsParameters.Remove('Debug') - $null = $adcsParameters.Remove('ErrorAction') - - try - { - $null = Install-AdcsOnlineResponder @adcsParameters -WhatIf - # CA is not installed - $Ensure = 'Absent' - } - catch [Microsoft.CertificateServices.Deployment.Common.OCSP.OnlineResponderSetupException] - { - # CA is already installed - $Ensure = 'Present' - } - catch - { - # Something else went wrong - throw $_ - } - - return @{ - Ensure = $Ensure - Credential = $Credential - } -} # function Get-TargetResource - -<# - .SYNOPSIS - Installs or uinstalls the ADCS Online Responder from the server. - - .PARAMETER IsSingleInstance - Specifies the resource is a single instance, the value must be 'Yes'. - - .PARAMETER Credential - If the Online Responder service is configured to use Standalone certification authority, - then an account that is a member of the local Administrators on the CA is required. If - the Online Responder service is configured to use an Enterprise CA, then an account that - is a member of Domain Admins is required. - - .PARAMETER Ensure - Specifies whether the Online Responder feature should be installed or uninstalled. -#> -function Set-TargetResource -{ - [CmdletBinding()] - param - ( - [Parameter(Mandatory = $true)] - [ValidateSet('Yes')] - [System.String] - $IsSingleInstance, - - [Parameter(Mandatory = $true)] - [System.Management.Automation.PSCredential] - [System.Management.Automation.Credential()] - $Credential, - - [Parameter()] - [ValidateSet('Present', 'Absent')] - [System.String] - $Ensure = 'Present' - ) - - Write-Verbose -Message ( @( - "$($MyInvocation.MyCommand): " - $($script:localizedData.SettingAdcsOnlineResponderStatusMessage) - ) -join '' ) - - $adcsParameters = @{ } + $PSBoundParameters - $null = $adcsParameters.Remove('IsSingleInstance') - $null = $adcsParameters.Remove('Ensure') - $null = $adcsParameters.Remove('Debug') - $null = $adcsParameters.Remove('ErrorAction') - - $errorMessage = '' - - if ($Ensure -eq 'Present') - { - Write-Verbose -Message ( @( - "$($MyInvocation.MyCommand): " - $($script:localizedData.InstallingAdcsOnlineResponderMessage) - ) -join '' ) - - $errorMessage = (Install-AdcsOnlineResponder @adcsParameters -Force).ErrorString - } - else - { - Write-Verbose -Message ( @( - "$($MyInvocation.MyCommand): " - $($script:localizedData.UninstallingAdcsOnlineResponderMessage) - ) -join '' ) - - $errorMessage = (Uninstall-AdcsOnlineResponder -Force).ErrorString - } - - if (-not [System.String]::IsNullOrEmpty($errorMessage)) - { - New-InvalidOperationException -Message $errorMessage - } -} # function Set-TargetResource - -<# - .SYNOPSIS - Tests is the ADCS Online Responder is in the desired state. - - .PARAMETER IsSingleInstance - Specifies the resource is a single instance, the value must be 'Yes'. - - .PARAMETER Credential - If the Online Responder service is configured to use Standalone certification authority, - then an account that is a member of the local Administrators on the CA is required. If - the Online Responder service is configured to use an Enterprise CA, then an account that - is a member of Domain Admins is required. - - .PARAMETER Ensure - Specifies whether the Online Responder feature should be installed or uninstalled. - - .OUTPUTS - Returns true if the ADCS Online Responder is in the desired state. -#> -function Test-TargetResource -{ - [CmdletBinding()] - [OutputType([System.Boolean])] - param - ( - [Parameter(Mandatory = $true)] - [ValidateSet('Yes')] - [System.String] - $IsSingleInstance, - - [Parameter(Mandatory = $true)] - [System.Management.Automation.PSCredential] - [System.Management.Automation.Credential()] - $Credential, - - [Parameter()] - [ValidateSet('Present', 'Absent')] - [System.String] - $Ensure = 'Present' - ) - - Write-Verbose -Message ( @( - "$($MyInvocation.MyCommand): " - $script:localizedData.TestingAdcsOnlineResponderStatusMessage - ) -join '' ) - - $adcsParameters = @{ } + $PSBoundParameters - $null = $adcsParameters.Remove('IsSingleInstance') - $null = $adcsParameters.Remove('Ensure') - $null = $adcsParameters.Remove('Debug') - $null = $adcsParameters.Remove('ErrorAction') - - try - { - $null = Install-AdcsOnlineResponder @adcsParameters -WhatIf - # Online Responder is not installed - if ($Ensure -eq 'Present') - { - # Online Responder is not installed but should be - change required - Write-Verbose -Message ( @( - "$($MyInvocation.MyCommand): " - $($script:localizedData.AdcsOnlineResponderNotInstalledButShouldBeMessage) - ) -join '' ) - - return $false - } - else - { - # Online Responder is not installed and should not be - change not required - Write-Verbose -Message ( @( - "$($MyInvocation.MyCommand): " - $($script:localizedData.AdcsOnlineResponderNotInstalledAndShouldNotBeMessage) - ) -join '' ) - - return $true - } - } - catch [Microsoft.CertificateServices.Deployment.Common.OCSP.OnlineResponderSetupException] - { - # Online Responder is already installed - if ($Ensure -eq 'Present') - { - # Online Responder is installed and should be - change not required - Write-Verbose -Message ( @( - "$($MyInvocation.MyCommand): " - $($script:localizedData.AdcsOnlineResponderInstalledAndShouldBeMessage) - ) -join '' ) - - return $true - } - else - { - # Online Responder is installed and should not be - change required - Write-Verbose -Message ( @( - "$($MyInvocation.MyCommand): " - $($script:localizedData.AdcsOnlineResponderInstalledButShouldNotBeMessage) - ) -join '' ) - - return $false - } - } - catch - { - # Something else went wrong - throw $_ - } # try -} # function Test-TargetResource diff --git a/source/DSCResources/DSC_AdcsOnlineResponder/DSC_AdcsOnlineResponder.schema.mof b/source/DSCResources/DSC_AdcsOnlineResponder/DSC_AdcsOnlineResponder.schema.mof deleted file mode 100644 index fbc89da..0000000 --- a/source/DSCResources/DSC_AdcsOnlineResponder/DSC_AdcsOnlineResponder.schema.mof +++ /dev/null @@ -1,7 +0,0 @@ -[ClassVersion("0.1.0.0"), FriendlyName("AdcsOnlineResponder")] -class DSC_AdcsOnlineResponder : OMI_BaseResource -{ - [Key, Description("Specifies the resource is a single instance, the value must be 'Yes'"), ValueMap{"Yes"}, Values{"Yes"}] String IsSingleInstance; - [Required, Description("If the Online Responder service is configured to use Standalone certification authority, then an account that is a member of the local Administrators on the CA is required. If the Online Responder service is configured to use an Enterprise CA, then an account that is a member of Domain Admins is required."), EmbeddedInstance("MSFT_Credential")] String Credential; - [Write, Description("Specifies whether the Online Responder feature should be installed or uninstalled."), ValueMap{"Present","Absent"}, Values{"Present","Absent"}] String Ensure; -}; diff --git a/source/DSCResources/DSC_AdcsOnlineResponder/README.md b/source/DSCResources/DSC_AdcsOnlineResponder/README.md deleted file mode 100644 index c3b93bb..0000000 --- a/source/DSCResources/DSC_AdcsOnlineResponder/README.md +++ /dev/null @@ -1,7 +0,0 @@ -# Description - -This resource can be used to install an ADCS Online Responder after the feature -has been installed on the server. -Using this DSC Resource to configure an ADCS Certificate Authority assumes that -the ```ADCS-Online-Responder``` feature has already been installed. -For more information on ADCS Online Responders, see [this article on TechNet](https://technet.microsoft.com/en-us/library/cc725958.aspx). diff --git a/source/DSCResources/DSC_AdcsOnlineResponder/en-US/DSC_AdcsOnlineResponder.strings.psd1 b/source/DSCResources/DSC_AdcsOnlineResponder/en-US/DSC_AdcsOnlineResponder.strings.psd1 deleted file mode 100644 index 4fd9406..0000000 --- a/source/DSCResources/DSC_AdcsOnlineResponder/en-US/DSC_AdcsOnlineResponder.strings.psd1 +++ /dev/null @@ -1,13 +0,0 @@ -# Localized resources for DSC_AdcsOnlineResponder - -ConvertFrom-StringData @' - GettingAdcsOnlineResponderStatusMessage = Getting ADCS Online Responder Status. - TestingAdcsOnlineResponderStatusMessage = Testing ADCS Online Responder Status. - AdcsOnlineResponderInstalledButShouldNotBeMessage = ADCS Online Responder is installed but should not be. Change required. - AdcsOnlineResponderInstalledAndShouldBeMessage = ADCS Online Responder is installed and should be. Change not required. - AdcsOnlineResponderNotInstalledButShouldBeMessage = ADCS Online Responder is not installed but should be. Change required. - AdcsOnlineResponderNotInstalledAndShouldNotBeMessage = ADCS Online Responder is not installed and should not be. Change not required. - SettingAdcsOnlineResponderStatusMessage = Setting ADCS Online Responder Status. - InstallingAdcsOnlineResponderMessage = Installing ADCS Online Responder. - UninstallingAdcsOnlineResponderMessage = Uninstalling ADCS Online Responder. -'@ diff --git a/tests/Unit/DSC_AdcsOnlineResponder.Tests.ps1 b/tests/Unit/DSC_AdcsOnlineResponder.Tests.ps1 deleted file mode 100644 index 8a92e80..0000000 --- a/tests/Unit/DSC_AdcsOnlineResponder.Tests.ps1 +++ /dev/null @@ -1,398 +0,0 @@ -<# - .SYNOPSIS - Unit test for DSC_AdcsOnlineResponder 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 - } - - # 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 = 'ActiveDirectoryCSDsc' - $script:dscResourceName = 'DSC_AdcsOnlineResponder' - - $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') - Import-Module -Name (Join-Path -Path $PSScriptRoot -ChildPath '.\Stubs\AdcsDeploymentStub.psm1') - - $PSDefaultParameterValues['InModuleScope:ModuleName'] = $script:dscResourceName - $PSDefaultParameterValues['Mock:ModuleName'] = $script:dscResourceName - $PSDefaultParameterValues['Should:ModuleName'] = $script:dscResourceName - - # Add Custom Type - if (-not ([System.Management.Automation.PSTypeName]'Microsoft.CertificateServices.Deployment.Common.OCSP.OnlineResponderSetupException').Type) - { - <# - Define the exception class: - Microsoft.CertificateServices.Deployment.Common.OCSP.OnlineResponderSetupException - so that unit tests can be run without ADCS being installed. - #> - - $ExceptionDefinition = @' -namespace Microsoft.CertificateServices.Deployment.Common.OCSP { - public class OnlineResponderSetupException: System.Exception { - } -} -'@ - Add-Type -TypeDefinition $ExceptionDefinition - } - - # Add Test Data - InModuleScope -ScriptBlock { - $DummyCredential = New-Object System.Management.Automation.PSCredential ('Administrator', (New-Object -Type SecureString)) - - $script:testParametersPresent = @{ - IsSingleInstance = 'Yes' - Ensure = 'Present' - Credential = $DummyCredential - Verbose = $false - } - - $script:TestParametersAbsent = @{ - IsSingleInstance = 'Yes' - Ensure = 'Absent' - Credential = $DummyCredential - Verbose = $false - } - } -} - -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 -Name AdcsDeploymentStub -Force - - # Remove module common test helper. - Get-Module -Name 'CommonTestHelper' -All | Remove-Module -Force -} - -Describe 'DSC_AdcsOnlineResponder\Get-TargetResource' -Tag 'Get' { - Context 'When the Online Responder is installed' { - BeforeAll { - Mock ` - -CommandName Install-AdcsOnlineResponder ` - -MockWith { - Throw (New-Object -TypeName 'Microsoft.CertificateServices.Deployment.Common.OCSP.OnlineResponderSetupException') - } -Verifiable - } - - - It 'Should return Ensure set to Present' { - InModuleScope -ScriptBlock { - Set-StrictMode -Version 1.0 - - $getTargetResourceResult = Get-TargetResource @testParametersPresent - $getTargetResourceResult.Ensure | Should -Be 'Present' - } - } - - It 'Should call expected mocks' { - Should -InvokeVerifiable - Should -Invoke ` - -CommandName Install-AdcsOnlineResponder ` - -Exactly ` - -Times 1 -Scope Context - } - } - - Context 'When the Online Responder is not installed' { - BeforeAll { - Mock -CommandName Install-AdcsOnlineResponder - } - - - It 'Should return Ensure set to Absent' { - InModuleScope -ScriptBlock { - Set-StrictMode -Version 1.0 - - $getTargetResourceResult = Get-TargetResource @testParametersPresent - $getTargetResourceResult.Ensure | Should -Be 'Absent' - } - } - - It 'Should call expected mocks' { - Should -Invoke ` - -CommandName Install-AdcsOnlineResponder ` - -Exactly ` - -Times 1 -Scope Context - } - } - - Context 'When there is an unexpected error' { - BeforeAll { - Mock ` - -CommandName Install-AdcsOnlineResponder ` - -MockWith { Throw (New-Object -TypeName 'System.Exception') } ` - -Verifiable - } - - It 'Should throw an exception' { - InModuleScope -ScriptBlock { - Set-StrictMode -Version 1.0 - - { Get-TargetResource @testParametersPresent } | Should -Throw - } - } - - It 'Should call expected mocks' { - Should -InvokeVerifiable - Should -Invoke ` - -CommandName Install-AdcsOnlineResponder ` - -Exactly ` - -Times 1 -Scope Context - } - } -} - -Describe 'DSC_AdcsOnlineResponder\Set-TargetResource' -Tag 'Set' { - Context 'When the Online Responder is not installed but should be' { - BeforeAll { - Mock -CommandName Install-AdcsOnlineResponder - Mock -CommandName Uninstall-AdcsOnlineResponder - } - - It 'Should not throw an exception' { - InModuleScope -ScriptBlock { - Set-StrictMode -Version 1.0 - - { Set-TargetResource @testParametersPresent } | Should -Not -Throw - } - } - - It 'Should call expected mocks' { - Should -Invoke ` - -CommandName Install-AdcsOnlineResponder ` - -Exactly ` - -Times 1 -Scope Context - - Should -Invoke ` - -CommandName Uninstall-AdcsOnlineResponder ` - -Exactly ` - -Times 0 -Scope Context - } - } - - Context 'When the Online Responder is not installed but should be but an error is thrown installing it' { - BeforeAll { - Mock -CommandName Install-AdcsOnlineResponder ` - -MockWith { [PSObject] @{ ErrorString = 'Something went wrong' } } - - Mock -CommandName Uninstall-AdcsOnlineResponder - } - - It 'Should throw an exception' { - InModuleScope -ScriptBlock { - Set-StrictMode -Version 1.0 - - $errorRecord = Get-InvalidOperationRecord -Message 'Something went wrong' - - { Set-TargetResource @testParametersPresent } | Should -Throw $errorRecord - } - } - - It 'Should call expected mocks' { - Should -Invoke ` - -CommandName Install-AdcsOnlineResponder ` - -Exactly ` - -Times 1 -Scope Context - - Should -Invoke ` - -CommandName Uninstall-AdcsOnlineResponder ` - -Exactly ` - -Times 0 -Scope Context - } - } - - Context 'When the Online Responder is installed but should not be' { - BeforeAll { - Mock -CommandName Install-AdcsOnlineResponder - Mock -CommandName Uninstall-AdcsOnlineResponder - } - - It 'Should not throw an exception' { - InModuleScope -ScriptBlock { - Set-StrictMode -Version 1.0 - - { Set-TargetResource @TestParametersAbsent } | Should -Not -Throw - } - } - - It 'Should call expected mocks' { - Should -Invoke ` - -CommandName Install-AdcsOnlineResponder ` - -Exactly ` - -Times 0 -Scope Context - - Should -Invoke ` - -CommandName Uninstall-AdcsOnlineResponder ` - -Exactly ` - -Times 1 -Scope Context - } - } -} - -Describe 'DSC_AdcsOnlineResponder\Test-TargetResource' -Tag 'Test' { - Context 'When the Online Responder is installed' { - Context 'When the Online Responder should be installed' { - BeforeAll { - Mock -CommandName Install-AdcsOnlineResponder ` - -MockWith { Throw (New-Object -TypeName 'Microsoft.CertificateServices.Deployment.Common.OCSP.OnlineResponderSetupException') } ` - -Verifiable - } - - It 'Should return true' { - InModuleScope -ScriptBlock { - Set-StrictMode -Version 1.0 - - $testTargetResourceResult = Test-TargetResource @testParametersPresent - $testTargetResourceResult | Should -BeTrue - } - } - - It 'Should call expected mocks' { - Should -InvokeVerifiable - Should -Invoke ` - -CommandName Install-AdcsOnlineResponder ` - -Exactly ` - -Times 1 -Scope Context - } - } - - Context 'When the Online Responder should not be installed' { - BeforeAll { - Mock -CommandName Install-AdcsOnlineResponder ` - -MockWith { Throw (New-Object -TypeName 'Microsoft.CertificateServices.Deployment.Common.OCSP.OnlineResponderSetupException') } ` - -Verifiable - } - - - It 'Should return false' { - InModuleScope -ScriptBlock { - Set-StrictMode -Version 1.0 - - $testTargetResourceResult = Test-TargetResource @TestParametersAbsent - $testTargetResourceResult | Should -BeFalse - } - } - - It 'Should call expected mocks' { - Should -InvokeVerifiable - Should -Invoke ` - -CommandName Install-AdcsOnlineResponder ` - -Exactly ` - -Times 1 -Scope Context - } - } - } - - Context 'When the Online Responder is not installed' { - Context 'When the Online Responder should be installed' { - BeforeAll { - Mock -CommandName Install-AdcsOnlineResponder ` - -Verifiable - } - - It 'Should return false' { - InModuleScope -ScriptBlock { - Set-StrictMode -Version 1.0 - - $testTargetResourceResult = Test-TargetResource @testParametersPresent - $testTargetResourceResult | Should -BeFalse - } - } - - It 'Should call expected mocks' { - Should -InvokeVerifiable - Should -Invoke ` - -CommandName Install-AdcsOnlineResponder ` - -Exactly ` - -Times 1 -Scope Context - } - } - - Context 'When the Online Responder should not be installed' { - BeforeAll { - Mock -CommandName Install-AdcsOnlineResponder ` - -Verifiable - } - - - It 'Should return true' { - InModuleScope -ScriptBlock { - Set-StrictMode -Version 1.0 - - $testTargetResourceResult = Test-TargetResource @TestParametersAbsent - $testTargetResourceResult | Should -BeTrue - } - } - - It 'Should call expected mocks' { - Should -InvokeVerifiable - Should -Invoke ` - -CommandName Install-AdcsOnlineResponder ` - -Exactly ` - -Times 1 -Scope Context - } - } - } - - Context 'Should throw on any other error' { - BeforeAll { - Mock -CommandName Install-AdcsOnlineResponder ` - -MockWith { Throw (New-Object -TypeName 'System.Exception') } ` - -Verifiable - } - - It 'Should throw an exception' { - InModuleScope -ScriptBlock { - Set-StrictMode -Version 1.0 - - { Test-TargetResource @testParametersPresent } | Should -Throw - } - } - - It 'Should call expected mocks' { - Should -InvokeVerifiable - Should -Invoke ` - -CommandName Install-AdcsOnlineResponder ` - -Exactly ` - -Times 1 -Scope Context - } - } -} From bf1c391016a53afcc6db0f4940c0c637f7ec952b Mon Sep 17 00:00:00 2001 From: Dan Hughes <2237515+dan-hughes@users.noreply.github.com> Date: Thu, 24 Jul 2025 18:04:20 +0100 Subject: [PATCH 02/20] Add class --- source/Classes/020.AdcsOnlineResponder.ps1 | 137 +++ source/en-US/AdcsOnlineResponder.strings.psd1 | 13 + .../Classes/AdcsOnlineResponder.Tests.ps1 | 897 ++++++++++++++++++ 3 files changed, 1047 insertions(+) create mode 100644 source/Classes/020.AdcsOnlineResponder.ps1 create mode 100644 source/en-US/AdcsOnlineResponder.strings.psd1 create mode 100644 tests/Unit/Classes/AdcsOnlineResponder.Tests.ps1 diff --git a/source/Classes/020.AdcsOnlineResponder.ps1 b/source/Classes/020.AdcsOnlineResponder.ps1 new file mode 100644 index 0000000..c5b0f17 --- /dev/null +++ b/source/Classes/020.AdcsOnlineResponder.ps1 @@ -0,0 +1,137 @@ +<# + .SYNOPSIS + The `AdcsOnlineResponder` DSC resource is used to configure the + ADCS Online Responder after the feature has been installed on the server. + + .DESCRIPTION + This resource can be used to install an ADCS Online Responder after the feature + has been installed on the server. + Using this DSC Resource to configure an ADCS Certificate Authority assumes that + the ```ADCS-Online-Responder``` feature has already been installed. + + .PARAMETER IsSingleInstance + Specifies the resource is a single instance, the value must be 'Yes'. + + .PARAMETER Credential + If the Online Responder service is configured to use Standalone certification authority, + then an account that is a member of the local Administrators on the CA is required. If + the Online Responder service is configured to use an Enterprise CA, then an account that + is a member of Domain Admins is required. + + .PARAMETER Ensure + Specifies whether the Online Responder feature should be installed or uninstalled. + + .PARAMETER Reasons + Returns the reason a property is not in desired state. + + .NOTES + Used Functions: + Name | Module + ----------------------------- |------------------- + Install-AdcsOnlineResponder | ADCSDeployment + Uninstall-AdcsOnlineResponder | ADCSDeployment + Assert-Module | DscResource.Common + New-InvalidOperationException | DscResource.Common +#> + +[DscResource()] +class AdcsOnlineResponder : ResourceBase +{ + [DscProperty(Key)] + [System.String] + $IsSingleInstance = 'Yes' + + [DscProperty(Mandatory)] + [System.Management.Automation.PSCredential] + [System.Management.Automation.Credential()] + $Credential + + [DscProperty()] + [Ensure] + $Ensure = [Ensure]::Present + + [DscProperty(NotConfigurable)] + [AdcsReason[]] + $Reasons + + AdcsOnlineResponder () : base ($PSScriptRoot) + { + # These properties will not be enforced. + $this.ExcludeDscProperties = @( + 'IsSingleInstance' + 'Credential' + ) + } + + [AdcsOnlineResponder] Get() + { + # Call the base method to return the properties. + return ([ResourceBase] $this).Get() + } + + # Base method Get() calls this method to get the current state as a Hashtable. + [System.Collections.Hashtable] GetCurrentState([System.Collections.Hashtable] $properties) + { + try + { + $null = Install-AdcsOnlineResponder -Credential $this.Credential -WhatIf + + return @{} + } + catch + { + return @{ + IsSingleInstance = 'Yes' + Credential = $this.Credential + } + } + # catch + # { + # New-InvalidOperationException -Message $this.localizedData.ErrorGetCurrentState -ErrorRecord $_ + # } + } + + [void] Set() + { + # Call the base method to enforce the properties. + ([ResourceBase] $this).Set() + } + + <# + Base method Set() call this method with the properties that should be + enforced and that are not in desired state. + #> + hidden [void] Modify([System.Collections.Hashtable] $properties) + { + $errorMessage = '' + + if ($properties.ContainsKey('Ensure') -and $properties.Ensure -eq [Ensure]::Absent) + { + $errorMessage = (Uninstall-AdcsOnlineResponder -Force).ErrorString + } + else + { + $errorMessage = (Install-AdcsOnlineResponder -Credential $this.Credential -Force).ErrorString + } + + if (-not [System.String]::IsNullOrEmpty($errorMessage)) + { + New-InvalidOperationException -Message $errorMessage + } + } + + [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) + { + Assert-Module -ModuleName 'ADCSDeployment' + } +} diff --git a/source/en-US/AdcsOnlineResponder.strings.psd1 b/source/en-US/AdcsOnlineResponder.strings.psd1 new file mode 100644 index 0000000..77ae2fc --- /dev/null +++ b/source/en-US/AdcsOnlineResponder.strings.psd1 @@ -0,0 +1,13 @@ +<# + .SYNOPSIS + The localized resource strings in English (en-US) for the + resource AdcsOnlineResponder. +#> + +ConvertFrom-StringData @' + ## Strings overrides for the ResourceBase's default strings. + # None + + ## Strings directly used by the derived class AdcsOnlineResponder. + # ErrorGetCurrentState = GetCurrentState() failed with an unexpected error. (ADCSOR0001) +'@ diff --git a/tests/Unit/Classes/AdcsOnlineResponder.Tests.ps1 b/tests/Unit/Classes/AdcsOnlineResponder.Tests.ps1 new file mode 100644 index 0000000..a642663 --- /dev/null +++ b/tests/Unit/Classes/AdcsOnlineResponder.Tests.ps1 @@ -0,0 +1,897 @@ +<# + .SYNOPSIS + Unit test for AdcsOnlineResponder 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 = 'ActiveDirectoryCSDsc' + + # Loading mocked exception class + # New-MockObject -Type Microsoft.CertificateServices.Deployment.Common.OCSP.OnlineResponderSetupException + + Import-Module -Name $script:dscModuleName + + # Load stub cmdlets and classes. + Import-Module -Name (Join-Path -Path $PSScriptRoot -ChildPath '..\Stubs\AdcsDeploymentStub.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 stub module + Remove-Module -Name AdcsDeploymentStub -Force + + # Unload the module being tested so that it doesn't impact any other tests. + Get-Module -Name $script:dscModuleName -All | Remove-Module -Force +} + +Describe 'AdcsOnlineResponder' { + Context 'When class is instantiated' { + It 'Should not throw an exception' { + InModuleScope -ScriptBlock { + Set-StrictMode -Version 1.0 + + { [AdcsOnlineResponder]::new() } | Should -Not -Throw + } + } + + It 'Should have a default or empty constructor' { + InModuleScope -ScriptBlock { + Set-StrictMode -Version 1.0 + + $instance = [AdcsOnlineResponder]::new() + $instance | Should -Not -BeNullOrEmpty + } + } + + It 'Should be the correct type' { + InModuleScope -ScriptBlock { + Set-StrictMode -Version 1.0 + + $instance = [AdcsOnlineResponder]::new() + $instance.GetType().Name | Should -Be 'AdcsOnlineResponder' + } + } + } +} + +Describe 'AdcsOnlineResponder\Get()' -Tag 'Get' { + Context 'When the system is in the desired state' { + Context 'When AdcsOnlineResponder is installed' { + BeforeAll { + InModuleScope -ScriptBlock { + Set-StrictMode -Version 1.0 + + $script:mockCredential = New-Object System.Management.Automation.PSCredential ('Administrator', (New-Object -Type SecureString)) + + $script:mockInstance = [AdcsOnlineResponder] @{ + IsSingleInstance = 'Yes' + Credential = $script:mockCredential + } + + <# + This mocks the method GetCurrentState(). + This mocks the method Assert(). + This mocks the method Normalize(). + + Method Get() will call the base method Get() which will + call back to the derived class methods. + #> + $script:mockInstance | + Add-Member -Force -MemberType 'ScriptMethod' -Name 'GetCurrentState' -Value { + return @{ + IsSingleInstance = 'Yes' + Credential = $script:mockCredential + } + } -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.Credential | Should -Be $script:mockCredential + $currentState.Ensure | Should -Be 'Present' + + $currentState.Reasons | Should -BeNullOrEmpty + } + } + } + + Context 'When AdcsOnlineResponder is not installed' { + BeforeAll { + InModuleScope -ScriptBlock { + Set-StrictMode -Version 1.0 + + $script:mockInstance = [AdcsOnlineResponder] @{ + IsSingleInstance = 'Yes' + Credential = New-Object System.Management.Automation.PSCredential ('Administrator', (New-Object -Type SecureString)) + Ensure = [Ensure]::Absent + } + + <# + This mocks the method GetCurrentState(). + This mocks the method Assert(). + This mocks the method Normalize(). + + Method Get() will call the base method Get() which will + call back to the derived class methods. + #> + $script:mockInstance | + Add-Member -Force -MemberType 'ScriptMethod' -Name 'GetCurrentState' -Value { + return @{} + } -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.Credential | Should -BeNullOrEmpty + $currentState.Ensure | Should -Be 'Absent' + + $currentState.Reasons | Should -HaveCount 0 + } + } + } + } + + Context 'When the system is not in the desired state' { + Context 'When AdcsOnlineResponder is installed' { + BeforeAll { + InModuleScope -ScriptBlock { + Set-StrictMode -Version 1.0 + + $script:mockCredential = New-Object System.Management.Automation.PSCredential ('Administrator', (New-Object -Type SecureString)) + + $script:mockInstance = [AdcsOnlineResponder] @{ + IsSingleInstance = 'Yes' + Credential = $script:mockCredential + Ensure = [Ensure]::Absent + } + + <# + This mocks the method GetCurrentState(). + This mocks the method Assert(). + This mocks the method Normalize(). + + Method Get() will call the base method Get() which will + call back to the derived class methods. + #> + $script:mockInstance | + Add-Member -Force -MemberType 'ScriptMethod' -Name 'GetCurrentState' -Value { + return @{ + IsSingleInstance = 'Yes' + Credential = $script:mockCredential + } + } -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.Credential | Should -Be $script:mockCredential + $currentState.Ensure | Should -Be 'Present' + + + $currentState.Reasons | Should -HaveCount 1 + $currentState.Reasons[0].Code | Should -Be 'AdcsOnlineResponder:AdcsOnlineResponder:Ensure' + $currentState.Reasons[0].Phrase | Should -Be 'The property Ensure should be "Absent", but was "Present"' + } + } + } + + Context 'When AdcsOnlineResponder is not installed' { + BeforeAll { + InModuleScope -ScriptBlock { + Set-StrictMode -Version 1.0 + + $script:mockCredential = New-Object System.Management.Automation.PSCredential ('Administrator', (New-Object -Type SecureString)) + + $script:mockInstance = [AdcsOnlineResponder] @{ + IsSingleInstance = 'Yes' + Credential = $script:mockCredential + } + + <# + This mocks the method GetCurrentState(). + This mocks the method Assert(). + This mocks the method Normalize(). + + Method Get() will call the base method Get() which will + call back to the derived class methods. + #> + $script:mockInstance | + Add-Member -Force -MemberType 'ScriptMethod' -Name 'GetCurrentState' -Value { + return @{} + } -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.Credential | Should -BeNullOrEmpty + $currentState.Ensure | Should -Be 'Absent' + + + $currentState.Reasons | Should -HaveCount 1 + $currentState.Reasons[0].Code | Should -Be 'AdcsOnlineResponder:AdcsOnlineResponder:Ensure' + $currentState.Reasons[0].Phrase | Should -Be 'The property Ensure should be "Present", but was "Absent"' + } + } + } + } +} + +Describe 'AdcsOnlineResponder\Set()' -Tag 'Set' -Skip:$true { + BeforeAll { + InModuleScope -ScriptBlock { + Set-StrictMode -Version 1.0 + + $script:mockInstance = [AdcsOnlineResponder] @{ + IsSingleInstance = 'Yes' + AiaUri = @('http://example.com/aia1') + OcspUri = @('http://example.com/ocsp1', 'http://example.com/ocsp2') + } | + # 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 + + $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 = 'AiaUri' + ExpectedValue = @('http://example.com/aia1') + ActualValue = @('http://example.com/aia1', 'http://example.com/aia2') + } + ) + } + } + + It 'Should call method Modify()' { + InModuleScope -ScriptBlock { + Set-StrictMode -Version 1.0 + + $script:mockInstance.Set() + + $script:methodTestCallCount | Should -Be 1 + $script:methodModifyCallCount | Should -Be 1 + } + } + } +} + +Describe 'AdcsOnlineResponder\Test()' -Tag 'Test' -Skip:$true { + BeforeAll { + InModuleScope -ScriptBlock { + Set-StrictMode -Version 1.0 + + $script:mockInstance = [AdcsOnlineResponder] @{ + IsSingleInstance = 'Yes' + AiaUri = @('http://example.com/aia1') + OcspUri = @('http://example.com/ocsp1', 'http://example.com/ocsp2') + } + } + } + + 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 = 'AiaUri' + ExpectedValue = @('http://example.com/aia1') + ActualValue = @('http://example.com/aia1', 'http://example.com/aia2') + } + ) + } + } + + It 'Should return $false' { + InModuleScope -ScriptBlock { + Set-StrictMode -Version 1.0 + + $script:mockInstance.Test() | Should -BeFalse + + $script:getMethodCallCount | Should -Be 1 + } + } + } +} + +Describe 'AdcsOnlineResponder\GetCurrentState()' -Tag 'HiddenMember' -Skip:$true { + Context 'When object is missing in the current state' { + BeforeAll { + InModuleScope -ScriptBlock { + Set-StrictMode -Version 1.0 + + $script:mockInstance = [AdcsOnlineResponder] @{ + IsSingleInstance = 'Yes' + AiaUri = @('http://example.com/aia1') + OcspUri = @('http://example.com/ocsp1', 'http://example.com/ocsp2') + } + } + + Mock -CommandName Get-CaAiaUriList -ParameterFilter { + $ExtensionType -eq 'AddToCertificateAia' + } + + Mock -CommandName Get-CaAiaUriList -ParameterFilter { + $ExtensionType -eq 'AddToCertificateOcsp' + } + } + + It 'Should return the correct values' { + InModuleScope -ScriptBlock { + Set-StrictMode -Version 1.0 + + $currentState = $script:mockInstance.GetCurrentState( + @{ + IsSingleInstance = 'Yes' + } + ) + + $currentState.IsSingleInstance | Should -BeNullOrEmpty + $currentState.AiaUri | Should -BeNullOrEmpty + $currentState.OcspUri | Should -BeNullOrEmpty + $currentState.AllowRestartService | Should -BeNullOrEmpty + } + + Should -Invoke -CommandName Get-CaAiaUriList -ParameterFilter { + $ExtensionType -eq 'AddToCertificateAia' + } -Exactly -Times 1 -Scope It + + Should -Invoke -CommandName Get-CaAiaUriList -ParameterFilter { + $ExtensionType -eq 'AddToCertificateOcsp' + } -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 = [AdcsOnlineResponder] @{ + IsSingleInstance = 'Yes' + AiaUri = @('http://example.com/aia1') + OcspUri = @('http://example.com/ocsp1', 'http://example.com/ocsp2') + } + } + + Mock -CommandName Get-CaAiaUriList -ParameterFilter { + $ExtensionType -eq 'AddToCertificateAia' + } -MockWith { + return @('http://example.com/aia1', 'http://example.com/aia2') + } + + Mock -CommandName Get-CaAiaUriList -ParameterFilter { + $ExtensionType -eq 'AddToCertificateOcsp' + } -MockWith { + return @('http://example.com/ocsp1', 'http://example.com/ocsp2') + } + } + + It 'Should return the correct values' { + InModuleScope -ScriptBlock { + Set-StrictMode -Version 1.0 + + $currentState = $script:mockInstance.GetCurrentState( + @{ + IsSingleInstance = 'Yes' + } + ) + + $currentState.IsSingleInstance | Should -Be 'Yes' + $currentState.AiaUri | Should -Be @('http://example.com/aia1', 'http://example.com/aia2') + $currentState.OcspUri | Should -Be @('http://example.com/ocsp1', 'http://example.com/ocsp2') + $currentState.AllowRestartService | Should -BeNullOrEmpty + } + + Should -Invoke -CommandName Get-CaAiaUriList -ParameterFilter { + $ExtensionType -eq 'AddToCertificateAia' + } -Exactly -Times 1 -Scope It + + Should -Invoke -CommandName Get-CaAiaUriList -ParameterFilter { + $ExtensionType -eq 'AddToCertificateOcsp' + } -Exactly -Times 1 -Scope It + } + } +} + +Describe 'AdcsOnlineResponder\Modify()' -Tag 'HiddenMember' -Skip:$true { + Context 'When the system is not in the desired state' { + BeforeAll { + InModuleScope -ScriptBlock { + Set-StrictMode -Version 1.0 + + $script:mockInstance = [AdcsOnlineResponder] @{ + IsSingleInstance = 'Yes' + AiaUri = @('http://example.com/aia1', 'http://example.com/aia2') + OcspUri = @('http://example.com/ocsp1', 'http://example.com/ocsp2') + } + + Mock -CommandName Add-CAAuthorityInformationAccess -ParameterFilter { + $AddToCertificateAia -eq $true + } + + Mock -CommandName Remove-CAAuthorityInformationAccess -ParameterFilter { + $AddToCertificateAia -eq $true + } + + Mock -CommandName Add-CAAuthorityInformationAccess -ParameterFilter { + $AddToCertificateOcsp -eq $true + } + + Mock -CommandName Remove-CAAuthorityInformationAccess -ParameterFilter { + $AddToCertificateOcsp -eq $true + } + + Mock -CommandName Restart-ServiceIfExists + } + } + + Context 'When the resource does not exist' { + It 'Should call the correct mocks' { + InModuleScope -ScriptBlock { + Set-StrictMode -Version 1.0 + + $mockProperties = @{ + AiaUri = @('http://example.com/aia1', 'http://example.com/aia2') + OcspUri = @('http://example.com/ocsp1', 'http://example.com/ocsp2') + } + + $script:mockInstance.PropertiesNotInDesiredState = @( + @{ + Property = 'AiaUri' + ExpectedValue = @('http://example.com/aia1', 'http://example.com/aia2') + ActualValue = @() + } + @{ + Property = 'OcspUri' + ExpectedValue = @('http://example.com/ocsp1', 'http://example.com/ocsp2') + ActualValue = @() + } + ) + + $script:mockInstance.Modify($mockProperties) + } + + Should -Invoke -CommandName Add-CAAuthorityInformationAccess -ParameterFilter { + $AddToCertificateAia -eq $true + } -Exactly -Times 2 -Scope It + + Should -Invoke -CommandName Add-CAAuthorityInformationAccess -ParameterFilter { + $AddToCertificateOcsp -eq $true + } -Exactly -Times 2 -Scope It + + Should -Invoke -CommandName Remove-CAAuthorityInformationAccess -ParameterFilter { + $AddToCertificateAia -eq $true + } -Exactly -Times 0 -Scope It + + Should -Invoke -CommandName Remove-CAAuthorityInformationAccess -ParameterFilter { + $AddToCertificateOcsp -eq $true + } -Exactly -Times 0 -Scope It + + Should -Invoke -CommandName Restart-ServiceIfExists -Exactly -Times 0 -Scope It + } + } + + Context 'When the AIA needs to be added' { + It 'Should call the correct mocks' { + InModuleScope -ScriptBlock { + Set-StrictMode -Version 1.0 + + $mockProperties = @{ + AiaUri = @('http://example.com/aia1', 'http://example.com/aia2') + } + + $script:mockInstance.PropertiesNotInDesiredState = @( + @{ + Property = 'AiaUri' + ExpectedValue = @('http://example.com/aia1', 'http://example.com/aia2') + ActualValue = @('http://example.com/aia1') + } + ) + + $script:mockInstance.Modify($mockProperties) + } + + Should -Invoke -CommandName Add-CAAuthorityInformationAccess -ParameterFilter { + $AddToCertificateAia -eq $true + } -Exactly -Times 1 -Scope It + + Should -Invoke -CommandName Add-CAAuthorityInformationAccess -ParameterFilter { + $AddToCertificateOcsp -eq $true + } -Exactly -Times 0 -Scope It + + Should -Invoke -CommandName Remove-CAAuthorityInformationAccess -ParameterFilter { + $AddToCertificateAia -eq $true + } -Exactly -Times 0 -Scope It + + Should -Invoke -CommandName Remove-CAAuthorityInformationAccess -ParameterFilter { + $AddToCertificateOcsp -eq $true + } -Exactly -Times 0 -Scope It + + Should -Invoke -CommandName Restart-ServiceIfExists -Exactly -Times 0 -Scope It + } + } + + Context 'When the OCSP needs to be added' { + It 'Should call the correct mocks' { + InModuleScope -ScriptBlock { + Set-StrictMode -Version 1.0 + + $mockProperties = @{ + OcspUri = @('http://example.com/ocsp1', 'http://example.com/ocsp2') + } + + $script:mockInstance.PropertiesNotInDesiredState = @( + @{ + Property = 'OcspUri' + ExpectedValue = @('http://example.com/ocsp1', 'http://example.com/ocsp2') + ActualValue = @('http://example.com/ocsp1') + } + ) + + $script:mockInstance.Modify($mockProperties) + } + + Should -Invoke -CommandName Add-CAAuthorityInformationAccess -ParameterFilter { + $AddToCertificateAia -eq $true + } -Exactly -Times 0 -Scope It + + Should -Invoke -CommandName Add-CAAuthorityInformationAccess -ParameterFilter { + $AddToCertificateOcsp -eq $true + } -Exactly -Times 1 -Scope It + + Should -Invoke -CommandName Remove-CAAuthorityInformationAccess -ParameterFilter { + $AddToCertificateAia -eq $true + } -Exactly -Times 0 -Scope It + + Should -Invoke -CommandName Remove-CAAuthorityInformationAccess -ParameterFilter { + $AddToCertificateOcsp -eq $true + } -Exactly -Times 0 -Scope It + + Should -Invoke -CommandName Restart-ServiceIfExists -Exactly -Times 0 -Scope It + } + } + + Context 'When the AIA needs to be removed' { + It 'Should call the correct mocks' { + InModuleScope -ScriptBlock { + Set-StrictMode -Version 1.0 + + $mockProperties = @{ + AiaUri = @('http://example.com/aia1', 'http://example.com/aia2') + } + + $script:mockInstance.PropertiesNotInDesiredState = @( + @{ + Property = 'AiaUri' + ExpectedValue = @('http://example.com/aia1') + ActualValue = @('http://example.com/aia1', 'http://example.com/aia2') + } + + ) + + $script:mockInstance.Modify($mockProperties) + } + + Should -Invoke -CommandName Add-CAAuthorityInformationAccess -ParameterFilter { + $AddToCertificateAia -eq $true + } -Exactly -Times 0 -Scope It + + Should -Invoke -CommandName Add-CAAuthorityInformationAccess -ParameterFilter { + $AddToCertificateOcsp -eq $true + } -Exactly -Times 0 -Scope It + + Should -Invoke -CommandName Remove-CAAuthorityInformationAccess -ParameterFilter { + $AddToCertificateAia -eq $true + } -Exactly -Times 1 -Scope It + + Should -Invoke -CommandName Remove-CAAuthorityInformationAccess -ParameterFilter { + $AddToCertificateOcsp -eq $true + } -Exactly -Times 0 -Scope It + + Should -Invoke -CommandName Restart-ServiceIfExists -Exactly -Times 0 -Scope It + } + } + + Context 'When the OCSP needs to be removed' { + It 'Should call the correct mocks' { + InModuleScope -ScriptBlock { + Set-StrictMode -Version 1.0 + + $mockProperties = @{ + OcspUri = @('http://example.com/ocsp1', 'http://example.com/ocsp2') + } + + $script:mockInstance.PropertiesNotInDesiredState = @( + @{ + Property = 'OcspUri' + ExpectedValue = @('http://example.com/ocsp1') + ActualValue = @('http://example.com/ocsp1', 'http://example.com/ocsp2') + } + ) + + $script:mockInstance.Modify($mockProperties) + } + + Should -Invoke -CommandName Add-CAAuthorityInformationAccess -ParameterFilter { + $AddToCertificateAia -eq $true + } -Exactly -Times 0 -Scope It + + Should -Invoke -CommandName Add-CAAuthorityInformationAccess -ParameterFilter { + $AddToCertificateOcsp -eq $true + } -Exactly -Times 0 -Scope It + + Should -Invoke -CommandName Remove-CAAuthorityInformationAccess -ParameterFilter { + $AddToCertificateAia -eq $true + } -Exactly -Times 0 -Scope It + + Should -Invoke -CommandName Remove-CAAuthorityInformationAccess -ParameterFilter { + $AddToCertificateOcsp -eq $true + } -Exactly -Times 1 -Scope It + + Should -Invoke -CommandName Restart-ServiceIfExists -Exactly -Times 0 -Scope It + } + } + + Context 'When the OCSP needs to be removed and AllowRestartService is set to $true' { + It 'Should call the correct mocks' { + InModuleScope -ScriptBlock { + Set-StrictMode -Version 1.0 + + $mockProperties = @{ + OcspUri = @('http://example.com/ocsp1', 'http://example.com/ocsp2') + } + + $script:mockInstance.PropertiesNotInDesiredState = @( + @{ + Property = 'OcspUri' + ExpectedValue = @('http://example.com/ocsp1') + ActualValue = @('http://example.com/ocsp1', 'http://example.com/ocsp2') + } + ) + + $script:mockInstance.AllowRestartService = $true + + $script:mockInstance.Modify($mockProperties) + } + + Should -Invoke -CommandName Add-CAAuthorityInformationAccess -ParameterFilter { + $AddToCertificateAia -eq $true + } -Exactly -Times 0 -Scope It + + Should -Invoke -CommandName Add-CAAuthorityInformationAccess -ParameterFilter { + $AddToCertificateOcsp -eq $true + } -Exactly -Times 0 -Scope It + + Should -Invoke -CommandName Remove-CAAuthorityInformationAccess -ParameterFilter { + $AddToCertificateAia -eq $true + } -Exactly -Times 0 -Scope It + + Should -Invoke -CommandName Remove-CAAuthorityInformationAccess -ParameterFilter { + $AddToCertificateOcsp -eq $true + } -Exactly -Times 1 -Scope It + + Should -Invoke -CommandName Restart-ServiceIfExists -Exactly -Times 1 -Scope It + } + } + } +} + +Describe 'AdcsOnlineResponder\AssertProperties()' -Tag 'AssertProperties' { + BeforeAll { + InModuleScope -ScriptBlock { + Set-StrictMode -Version 1.0 + + $script:mockInstance = [AdcsOnlineResponder] @{} + } + } + + Context 'When required module is missing' { + BeforeAll { + Mock -CommandName Assert-Module -MockWith { throw } + } + + It 'Should throw an error' { + InModuleScope -ScriptBlock { + Set-StrictMode -Version 1.0 + + $mockProperties = @{ + IsSingleInstance = 'Yes' + } + + { $mockInstance.AssertProperties($mockProperties) } | Should -Throw + } + } + } + + Context 'When required module is present' { + BeforeAll { + Mock -CommandName Assert-Module + } + + It 'Should throw an error' { + InModuleScope -ScriptBlock { + Set-StrictMode -Version 1.0 + + $mockProperties = @{ + IsSingleInstance = 'Yes' + } + + { $mockInstance.AssertProperties($mockProperties) } | Should -Not -Throw + } + } + } +} From 553587e8dfa7f1fb4c4cf73151d7c39fb3534cc1 Mon Sep 17 00:00:00 2001 From: Dan Hughes <2237515+dan-hughes@users.noreply.github.com> Date: Thu, 24 Jul 2025 18:04:26 +0100 Subject: [PATCH 03/20] Update changelog --- CHANGELOG.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0bc6e25..bddb131 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,7 +7,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed -- ActiveDirectoryCSDsc +- `ActiveDirectoryCSDsc` - Automatically publish documentation to GitHub Wiki - Fixes [Issue #122](https://github.com/dsccommunity/ActiveDirectoryCSDsc/issues/122). - Revert Pester to non-prerelease. - Use DscResource.Base pre-release. @@ -22,7 +22,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - `Tests` - Migrate all tests to Pester 5. - Include module file in code coverage. - Add RootModule to Module psd1. -- AdcsAuthorityInformationAccess +- `AdcsAuthorityInformationAccess` - Removed `AllowRestartService` parameter from compared settings, force `Get-CaAiaUriList` to return unmangled `System.String[]` with single values. Fixes [Issue #128](https://github.com/dsccommunity/ActiveDirectoryCSDsc/issues/128) @@ -35,6 +35,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Move to Private function. - `Restart-ServiceIfExists` - Change Write-Verbose to Write-Debug. +- `AdcsOnlineResponder` + - Convert to class-based resource [#155](https://github.com/dsccommunity/ActiveDirectoryCSDsc/issues/155). ### Added From 2e8e65b24642fcfd1c12f8f48fdce708b71f86dc Mon Sep 17 00:00:00 2001 From: Dan Hughes <2237515+dan-hughes@users.noreply.github.com> Date: Thu, 24 Jul 2025 18:22:09 +0100 Subject: [PATCH 04/20] Fix misplaced It block --- .../Classes/AdcsAuthorityInformationAccess.Tests.ps1 | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/tests/Unit/Classes/AdcsAuthorityInformationAccess.Tests.ps1 b/tests/Unit/Classes/AdcsAuthorityInformationAccess.Tests.ps1 index 74b8b45..c73d1e5 100644 --- a/tests/Unit/Classes/AdcsAuthorityInformationAccess.Tests.ps1 +++ b/tests/Unit/Classes/AdcsAuthorityInformationAccess.Tests.ps1 @@ -594,15 +594,15 @@ Describe 'AdcsAuthorityInformationAccess\Test()' -Tag 'Test' { $script:getMethodCallCount += 1 } } + } - It 'Should return $true' { - InModuleScope -ScriptBlock { - Set-StrictMode -Version 1.0 + It 'Should return $true' { + InModuleScope -ScriptBlock { + Set-StrictMode -Version 1.0 - $script:mockInstance.Test() | Should -BeTrue + $script:mockInstance.Test() | Should -BeTrue - $script:getMethodCallCount | Should -Be 1 - } + $script:getMethodCallCount | Should -Be 1 } } } From 51b7e02f3bc9b4bdfc4b667977175b3bbf7e4aa7 Mon Sep 17 00:00:00 2001 From: Dan Hughes <2237515+dan-hughes@users.noreply.github.com> Date: Thu, 24 Jul 2025 18:22:23 +0100 Subject: [PATCH 05/20] Enable Test and Set tests --- .../Classes/AdcsOnlineResponder.Tests.ps1 | 39 ++++++++++--------- 1 file changed, 21 insertions(+), 18 deletions(-) diff --git a/tests/Unit/Classes/AdcsOnlineResponder.Tests.ps1 b/tests/Unit/Classes/AdcsOnlineResponder.Tests.ps1 index a642663..e709da0 100644 --- a/tests/Unit/Classes/AdcsOnlineResponder.Tests.ps1 +++ b/tests/Unit/Classes/AdcsOnlineResponder.Tests.ps1 @@ -301,15 +301,16 @@ Describe 'AdcsOnlineResponder\Get()' -Tag 'Get' { } } -Describe 'AdcsOnlineResponder\Set()' -Tag 'Set' -Skip:$true { +Describe 'AdcsOnlineResponder\Set()' -Tag 'Set'{ BeforeAll { InModuleScope -ScriptBlock { Set-StrictMode -Version 1.0 + $script:mockCredential = New-Object System.Management.Automation.PSCredential ('Administrator', (New-Object -Type SecureString)) + $script:mockInstance = [AdcsOnlineResponder] @{ IsSingleInstance = 'Yes' - AiaUri = @('http://example.com/aia1') - OcspUri = @('http://example.com/ocsp1', 'http://example.com/ocsp2') + Credential = $script:mockCredential } | # Mock method Modify which is called by the case method Set(). Add-Member -Force -MemberType 'ScriptMethod' -Name 'Modify' -Value { @@ -368,9 +369,9 @@ Describe 'AdcsOnlineResponder\Set()' -Tag 'Set' -Skip:$true { $script:mockInstance.PropertiesNotInDesiredState = @( @{ - Property = 'AiaUri' - ExpectedValue = @('http://example.com/aia1') - ActualValue = @('http://example.com/aia1', 'http://example.com/aia2') + Property = 'Ensure' + ExpectedValue = 'Present' + ActualValue = 'Absent' } ) } @@ -389,15 +390,16 @@ Describe 'AdcsOnlineResponder\Set()' -Tag 'Set' -Skip:$true { } } -Describe 'AdcsOnlineResponder\Test()' -Tag 'Test' -Skip:$true { +Describe 'AdcsOnlineResponder\Test()' -Tag 'Test' { BeforeAll { InModuleScope -ScriptBlock { Set-StrictMode -Version 1.0 + $script:mockCredential = New-Object System.Management.Automation.PSCredential ('Administrator', (New-Object -Type SecureString)) + $script:mockInstance = [AdcsOnlineResponder] @{ IsSingleInstance = 'Yes' - AiaUri = @('http://example.com/aia1') - OcspUri = @('http://example.com/ocsp1', 'http://example.com/ocsp2') + Credential = $script:mockCredential } } } @@ -421,17 +423,18 @@ Describe 'AdcsOnlineResponder\Test()' -Tag 'Test' -Skip:$true { $script:getMethodCallCount += 1 } } + } - It 'Should return $true' { - InModuleScope -ScriptBlock { - Set-StrictMode -Version 1.0 + It 'Should return $true' { + InModuleScope -ScriptBlock { + Set-StrictMode -Version 1.0 - $script:mockInstance.Test() | Should -BeTrue + $script:mockInstance.Test() | Should -BeTrue - $script:getMethodCallCount | Should -Be 1 - } + $script:getMethodCallCount | Should -Be 1 } } + } Context 'When the system is not in the desired state' { @@ -447,9 +450,9 @@ Describe 'AdcsOnlineResponder\Test()' -Tag 'Test' -Skip:$true { $script:mockInstance.PropertiesNotInDesiredState = @( @{ - Property = 'AiaUri' - ExpectedValue = @('http://example.com/aia1') - ActualValue = @('http://example.com/aia1', 'http://example.com/aia2') + Property = 'Ensure' + ExpectedValue = 'Present' + ActualValue = 'Absent' } ) } From 5c28b65e09f19a2fc5643d1791a97f584f8ed3ca Mon Sep 17 00:00:00 2001 From: Dan Hughes <2237515+dan-hughes@users.noreply.github.com> Date: Fri, 25 Jul 2025 10:03:05 +0100 Subject: [PATCH 06/20] coderabbit fixes --- tests/Unit/Classes/AdcsOnlineResponder.Tests.ps1 | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/Unit/Classes/AdcsOnlineResponder.Tests.ps1 b/tests/Unit/Classes/AdcsOnlineResponder.Tests.ps1 index e709da0..9a038bb 100644 --- a/tests/Unit/Classes/AdcsOnlineResponder.Tests.ps1 +++ b/tests/Unit/Classes/AdcsOnlineResponder.Tests.ps1 @@ -301,7 +301,7 @@ Describe 'AdcsOnlineResponder\Get()' -Tag 'Get' { } } -Describe 'AdcsOnlineResponder\Set()' -Tag 'Set'{ +Describe 'AdcsOnlineResponder\Set()' -Tag 'Set' { BeforeAll { InModuleScope -ScriptBlock { Set-StrictMode -Version 1.0 @@ -885,7 +885,7 @@ Describe 'AdcsOnlineResponder\AssertProperties()' -Tag 'AssertProperties' { Mock -CommandName Assert-Module } - It 'Should throw an error' { + It 'Should not throw an error' { InModuleScope -ScriptBlock { Set-StrictMode -Version 1.0 From c200798b39fd7e87675f5f1160fbc836f7b8b58f Mon Sep 17 00:00:00 2001 From: Dan Hughes <2237515+dan-hughes@users.noreply.github.com> Date: Sat, 30 Aug 2025 19:12:42 +0100 Subject: [PATCH 07/20] Use preview common module --- RequiredModules.psd1 | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/RequiredModules.psd1 b/RequiredModules.psd1 index 4dfbf88..2a153c8 100644 --- a/RequiredModules.psd1 +++ b/RequiredModules.psd1 @@ -38,7 +38,12 @@ AllowPrerelease = $true } } - 'DscResource.Common' = 'latest' + 'DscResource.Common' = @{ + Version = 'latest' + Parameters = @{ + AllowPrerelease = $true + } + } # Analyzer rules 'DscResource.AnalyzerRules' = 'latest' From 43c519f7a618bad05fa73308e1971d8fc3d0f643 Mon Sep 17 00:00:00 2001 From: Dan Hughes <2237515+dan-hughes@users.noreply.github.com> Date: Sat, 30 Aug 2025 19:12:50 +0100 Subject: [PATCH 08/20] Enable string --- source/en-US/AdcsOnlineResponder.strings.psd1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/en-US/AdcsOnlineResponder.strings.psd1 b/source/en-US/AdcsOnlineResponder.strings.psd1 index 77ae2fc..000b069 100644 --- a/source/en-US/AdcsOnlineResponder.strings.psd1 +++ b/source/en-US/AdcsOnlineResponder.strings.psd1 @@ -9,5 +9,5 @@ ConvertFrom-StringData @' # None ## Strings directly used by the derived class AdcsOnlineResponder. - # ErrorGetCurrentState = GetCurrentState() failed with an unexpected error. (ADCSOR0001) + ErrorGetCurrentState = GetCurrentState() failed with an unexpected error. (ADCSOR0001) '@ From 343bca2f6ade00e5f5b41247bb3bae0e51dd8207 Mon Sep 17 00:00:00 2001 From: Dan Hughes <2237515+dan-hughes@users.noreply.github.com> Date: Sat, 30 Aug 2025 19:13:18 +0100 Subject: [PATCH 09/20] Work around type not existing --- source/Classes/020.AdcsOnlineResponder.ps1 | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/source/Classes/020.AdcsOnlineResponder.ps1 b/source/Classes/020.AdcsOnlineResponder.ps1 index c5b0f17..bff86ca 100644 --- a/source/Classes/020.AdcsOnlineResponder.ps1 +++ b/source/Classes/020.AdcsOnlineResponder.ps1 @@ -80,15 +80,16 @@ class AdcsOnlineResponder : ResourceBase } catch { - return @{ - IsSingleInstance = 'Yes' - Credential = $this.Credential + if ($_.Exception.ToString() -match 'OnlineResponderSetupException$') + { + return @{ + IsSingleInstance = $this.IsSingleInstance + Credential = $this.Credential + } } + + return New-InvalidOperationException -Message $this.localizedData.ErrorGetCurrentState -ErrorRecord $_ } - # catch - # { - # New-InvalidOperationException -Message $this.localizedData.ErrorGetCurrentState -ErrorRecord $_ - # } } [void] Set() From 16761afef7fcca41171c0c4f550b9803d7aa66fe Mon Sep 17 00:00:00 2001 From: Dan Hughes <2237515+dan-hughes@users.noreply.github.com> Date: Sat, 30 Aug 2025 19:13:50 +0100 Subject: [PATCH 10/20] Add GetCurrentState tests --- .../Classes/AdcsOnlineResponder.Tests.ps1 | 87 ++++++++++--------- 1 file changed, 44 insertions(+), 43 deletions(-) diff --git a/tests/Unit/Classes/AdcsOnlineResponder.Tests.ps1 b/tests/Unit/Classes/AdcsOnlineResponder.Tests.ps1 index 9a038bb..ad7b3d1 100644 --- a/tests/Unit/Classes/AdcsOnlineResponder.Tests.ps1 +++ b/tests/Unit/Classes/AdcsOnlineResponder.Tests.ps1 @@ -470,26 +470,19 @@ Describe 'AdcsOnlineResponder\Test()' -Tag 'Test' { } } -Describe 'AdcsOnlineResponder\GetCurrentState()' -Tag 'HiddenMember' -Skip:$true { - Context 'When object is missing in the current state' { +Describe 'AdcsOnlineResponder\GetCurrentState()' -Tag 'HiddenMember' { + Context 'When the OnlineResponder is not installed' { BeforeAll { InModuleScope -ScriptBlock { Set-StrictMode -Version 1.0 $script:mockInstance = [AdcsOnlineResponder] @{ IsSingleInstance = 'Yes' - AiaUri = @('http://example.com/aia1') - OcspUri = @('http://example.com/ocsp1', 'http://example.com/ocsp2') + Ensure = [Ensure]::Present } } - Mock -CommandName Get-CaAiaUriList -ParameterFilter { - $ExtensionType -eq 'AddToCertificateAia' - } - - Mock -CommandName Get-CaAiaUriList -ParameterFilter { - $ExtensionType -eq 'AddToCertificateOcsp' - } + Mock -CommandName Install-AdcsOnlineResponder } It 'Should return the correct values' { @@ -503,43 +496,25 @@ Describe 'AdcsOnlineResponder\GetCurrentState()' -Tag 'HiddenMember' -Skip:$true ) $currentState.IsSingleInstance | Should -BeNullOrEmpty - $currentState.AiaUri | Should -BeNullOrEmpty - $currentState.OcspUri | Should -BeNullOrEmpty - $currentState.AllowRestartService | Should -BeNullOrEmpty } - Should -Invoke -CommandName Get-CaAiaUriList -ParameterFilter { - $ExtensionType -eq 'AddToCertificateAia' - } -Exactly -Times 1 -Scope It - - Should -Invoke -CommandName Get-CaAiaUriList -ParameterFilter { - $ExtensionType -eq 'AddToCertificateOcsp' - } -Exactly -Times 1 -Scope It + Should -Invoke -CommandName Install-AdcsOnlineResponder -Exactly -Times 1 -Scope It } } - Context 'When the object is present in the current state' { + Context 'When the OnlineResponder is already installed' { BeforeAll { InModuleScope -ScriptBlock { Set-StrictMode -Version 1.0 $script:mockInstance = [AdcsOnlineResponder] @{ IsSingleInstance = 'Yes' - AiaUri = @('http://example.com/aia1') - OcspUri = @('http://example.com/ocsp1', 'http://example.com/ocsp2') + Ensure = [Ensure]::Present } } - Mock -CommandName Get-CaAiaUriList -ParameterFilter { - $ExtensionType -eq 'AddToCertificateAia' - } -MockWith { - return @('http://example.com/aia1', 'http://example.com/aia2') - } - - Mock -CommandName Get-CaAiaUriList -ParameterFilter { - $ExtensionType -eq 'AddToCertificateOcsp' - } -MockWith { - return @('http://example.com/ocsp1', 'http://example.com/ocsp2') + Mock -CommandName Install-AdcsOnlineResponder -MockWith { + throw 'Microsoft.CertificateServices.Deployment.Common.OCSP.OnlineResponderSetupException' } } @@ -554,18 +529,44 @@ Describe 'AdcsOnlineResponder\GetCurrentState()' -Tag 'HiddenMember' -Skip:$true ) $currentState.IsSingleInstance | Should -Be 'Yes' - $currentState.AiaUri | Should -Be @('http://example.com/aia1', 'http://example.com/aia2') - $currentState.OcspUri | Should -Be @('http://example.com/ocsp1', 'http://example.com/ocsp2') - $currentState.AllowRestartService | Should -BeNullOrEmpty } - Should -Invoke -CommandName Get-CaAiaUriList -ParameterFilter { - $ExtensionType -eq 'AddToCertificateAia' - } -Exactly -Times 1 -Scope It + Should -Invoke -CommandName Install-AdcsOnlineResponder -Exactly -Times 1 -Scope It + } + } + + Context 'When the OnlineResponder throws an unexpected exception' { + BeforeAll { + InModuleScope -ScriptBlock { + Set-StrictMode -Version 1.0 + + $script:mockInstance = [AdcsOnlineResponder] @{ + IsSingleInstance = 'Yes' + Ensure = [Ensure]::Present + } + } + + Mock -CommandName Install-AdcsOnlineResponder -MockWith { + throw 'Another exception' + } + } + + It 'Should return the correct values' { + InModuleScope -ScriptBlock { + Set-StrictMode -Version 1.0 + + $errorRecord = Get-InvalidOperationRecord -Message $script:mockInstance.localizedData.ErrorGetCurrentState + + { + $script:mockInstance.GetCurrentState( + @{ + IsSingleInstance = 'Yes' + } + ) + } | Should -Throw -ExpectedMessage ($errorRecord.Exception.Message + '*') + } - Should -Invoke -CommandName Get-CaAiaUriList -ParameterFilter { - $ExtensionType -eq 'AddToCertificateOcsp' - } -Exactly -Times 1 -Scope It + Should -Invoke -CommandName Install-AdcsOnlineResponder -Exactly -Times 1 -Scope It } } } From 05c2164101d5f937abd2b83940e3eeeca4a7dc05 Mon Sep 17 00:00:00 2001 From: Dan Hughes <2237515+dan-hughes@users.noreply.github.com> Date: Sat, 30 Aug 2025 19:31:40 +0100 Subject: [PATCH 11/20] Add setup exception stub --- tests/Unit/Stubs/AdcsDeploymentStub.psm1 | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/tests/Unit/Stubs/AdcsDeploymentStub.psm1 b/tests/Unit/Stubs/AdcsDeploymentStub.psm1 index f6724b5..a7ebffc 100644 --- a/tests/Unit/Stubs/AdcsDeploymentStub.psm1 +++ b/tests/Unit/Stubs/AdcsDeploymentStub.psm1 @@ -7,6 +7,15 @@ [CmdletBinding()] param () +Add-Type -TypeDefinition @' +namespace Microsoft.CertificateServices.Deployment.Common.OCSP { + public class OnlineResponderSetupException : System.Exception { + public OnlineResponderSetupException() {} + public OnlineResponderSetupException(string message) : base(message) {} + } +} +'@ + function Install-AdcsCertificationAuthority { <# From 3be68e89fdae532a15ce2b4ae8a4a89f6b7cfc1c Mon Sep 17 00:00:00 2001 From: Dan Hughes <2237515+dan-hughes@users.noreply.github.com> Date: Sat, 30 Aug 2025 19:50:42 +0100 Subject: [PATCH 12/20] Update Modify and GetCurrentState --- source/Classes/020.AdcsOnlineResponder.ps1 | 28 +++++++++++++--------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/source/Classes/020.AdcsOnlineResponder.ps1 b/source/Classes/020.AdcsOnlineResponder.ps1 index bff86ca..5632daa 100644 --- a/source/Classes/020.AdcsOnlineResponder.ps1 +++ b/source/Classes/020.AdcsOnlineResponder.ps1 @@ -80,11 +80,10 @@ class AdcsOnlineResponder : ResourceBase } catch { - if ($_.Exception.ToString() -match 'OnlineResponderSetupException$') + if ($_.Exception.GetType().FullName -eq 'Microsoft.CertificateServices.Deployment.Common.OCSP.OnlineResponderSetupException') { return @{ IsSingleInstance = $this.IsSingleInstance - Credential = $this.Credential } } @@ -104,20 +103,27 @@ class AdcsOnlineResponder : ResourceBase #> hidden [void] Modify([System.Collections.Hashtable] $properties) { - $errorMessage = '' - if ($properties.ContainsKey('Ensure') -and $properties.Ensure -eq [Ensure]::Absent) { - $errorMessage = (Uninstall-AdcsOnlineResponder -Force).ErrorString + try + { + Uninstall-AdcsOnlineResponder -Force + } + catch + { + New-InvalidOperationException -Message $_.Exception.Message -ErrorRecord $_ + } } else { - $errorMessage = (Install-AdcsOnlineResponder -Credential $this.Credential -Force).ErrorString - } - - if (-not [System.String]::IsNullOrEmpty($errorMessage)) - { - New-InvalidOperationException -Message $errorMessage + try + { + Install-AdcsOnlineResponder -Credential $this.Credential -Force + } + catch + { + New-InvalidOperationException -Message $_.Exception.Message -ErrorRecord $_ + } } } From 58f1c2fc22bf1be21fdd15f3089b1e9bb8c2692d Mon Sep 17 00:00:00 2001 From: Dan Hughes <2237515+dan-hughes@users.noreply.github.com> Date: Sat, 30 Aug 2025 19:50:53 +0100 Subject: [PATCH 13/20] Fix tests --- tests/Unit/Classes/AdcsOnlineResponder.Tests.ps1 | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/Unit/Classes/AdcsOnlineResponder.Tests.ps1 b/tests/Unit/Classes/AdcsOnlineResponder.Tests.ps1 index ad7b3d1..05335e0 100644 --- a/tests/Unit/Classes/AdcsOnlineResponder.Tests.ps1 +++ b/tests/Unit/Classes/AdcsOnlineResponder.Tests.ps1 @@ -514,7 +514,7 @@ Describe 'AdcsOnlineResponder\GetCurrentState()' -Tag 'HiddenMember' { } Mock -CommandName Install-AdcsOnlineResponder -MockWith { - throw 'Microsoft.CertificateServices.Deployment.Common.OCSP.OnlineResponderSetupException' + throw [Microsoft.CertificateServices.Deployment.Common.OCSP.OnlineResponderSetupException]::new() } } @@ -876,7 +876,7 @@ Describe 'AdcsOnlineResponder\AssertProperties()' -Tag 'AssertProperties' { IsSingleInstance = 'Yes' } - { $mockInstance.AssertProperties($mockProperties) } | Should -Throw + { $script:mockInstance.AssertProperties($mockProperties) } | Should -Throw } } } @@ -894,7 +894,7 @@ Describe 'AdcsOnlineResponder\AssertProperties()' -Tag 'AssertProperties' { IsSingleInstance = 'Yes' } - { $mockInstance.AssertProperties($mockProperties) } | Should -Not -Throw + { $script:mockInstance.AssertProperties($mockProperties) } | Should -Not -Throw } } } From bb1d9c7534fb83b3611394d7453d2d1fa6ed8dc0 Mon Sep 17 00:00:00 2001 From: Dan Hughes <2237515+dan-hughes@users.noreply.github.com> Date: Sun, 31 Aug 2025 12:39:29 +0100 Subject: [PATCH 14/20] Update Modify tests --- .../Classes/AdcsOnlineResponder.Tests.ps1 | 327 +++++------------- 1 file changed, 95 insertions(+), 232 deletions(-) diff --git a/tests/Unit/Classes/AdcsOnlineResponder.Tests.ps1 b/tests/Unit/Classes/AdcsOnlineResponder.Tests.ps1 index 05335e0..4b1c418 100644 --- a/tests/Unit/Classes/AdcsOnlineResponder.Tests.ps1 +++ b/tests/Unit/Classes/AdcsOnlineResponder.Tests.ps1 @@ -94,7 +94,7 @@ Describe 'AdcsOnlineResponder\Get()' -Tag 'Get' { InModuleScope -ScriptBlock { Set-StrictMode -Version 1.0 - $script:mockCredential = New-Object System.Management.Automation.PSCredential ('Administrator', (New-Object -Type SecureString)) + $script:mockCredential = [System.Management.Automation.PSCredential]::new('Administrator', ([System.Security.SecureString]::new())) $script:mockInstance = [AdcsOnlineResponder] @{ IsSingleInstance = 'Yes' @@ -148,7 +148,7 @@ Describe 'AdcsOnlineResponder\Get()' -Tag 'Get' { $script:mockInstance = [AdcsOnlineResponder] @{ IsSingleInstance = 'Yes' - Credential = New-Object System.Management.Automation.PSCredential ('Administrator', (New-Object -Type SecureString)) + Credential = [System.Management.Automation.PSCredential]::new('Administrator', ([System.Security.SecureString]::new())) Ensure = [Ensure]::Absent } @@ -196,7 +196,7 @@ Describe 'AdcsOnlineResponder\Get()' -Tag 'Get' { InModuleScope -ScriptBlock { Set-StrictMode -Version 1.0 - $script:mockCredential = New-Object System.Management.Automation.PSCredential ('Administrator', (New-Object -Type SecureString)) + $script:mockCredential = [System.Management.Automation.PSCredential]::new('Administrator', ([System.Security.SecureString]::new())) $script:mockInstance = [AdcsOnlineResponder] @{ IsSingleInstance = 'Yes' @@ -252,7 +252,7 @@ Describe 'AdcsOnlineResponder\Get()' -Tag 'Get' { InModuleScope -ScriptBlock { Set-StrictMode -Version 1.0 - $script:mockCredential = New-Object System.Management.Automation.PSCredential ('Administrator', (New-Object -Type SecureString)) + $script:mockCredential = [System.Management.Automation.PSCredential]::new('Administrator', ([System.Security.SecureString]::new())) $script:mockInstance = [AdcsOnlineResponder] @{ IsSingleInstance = 'Yes' @@ -306,7 +306,7 @@ Describe 'AdcsOnlineResponder\Set()' -Tag 'Set' { InModuleScope -ScriptBlock { Set-StrictMode -Version 1.0 - $script:mockCredential = New-Object System.Management.Automation.PSCredential ('Administrator', (New-Object -Type SecureString)) + $script:mockCredential = [System.Management.Automation.PSCredential]::new('Administrator', ([System.Security.SecureString]::new())) $script:mockInstance = [AdcsOnlineResponder] @{ IsSingleInstance = 'Yes' @@ -395,7 +395,7 @@ Describe 'AdcsOnlineResponder\Test()' -Tag 'Test' { InModuleScope -ScriptBlock { Set-StrictMode -Version 1.0 - $script:mockCredential = New-Object System.Management.Automation.PSCredential ('Administrator', (New-Object -Type SecureString)) + $script:mockCredential = [System.Management.Automation.PSCredential]::new('Administrator', ([System.Security.SecureString]::new())) $script:mockInstance = [AdcsOnlineResponder] @{ IsSingleInstance = 'Yes' @@ -571,285 +571,148 @@ Describe 'AdcsOnlineResponder\GetCurrentState()' -Tag 'HiddenMember' { } } -Describe 'AdcsOnlineResponder\Modify()' -Tag 'HiddenMember' -Skip:$true { - Context 'When the system is not in the desired state' { +Describe 'AdcsOnlineResponder\Modify()' -Tag 'HiddenMember' { + Context 'When the resource should be added' { BeforeAll { InModuleScope -ScriptBlock { Set-StrictMode -Version 1.0 $script:mockInstance = [AdcsOnlineResponder] @{ IsSingleInstance = 'Yes' - AiaUri = @('http://example.com/aia1', 'http://example.com/aia2') - OcspUri = @('http://example.com/ocsp1', 'http://example.com/ocsp2') } - Mock -CommandName Add-CAAuthorityInformationAccess -ParameterFilter { - $AddToCertificateAia -eq $true - } + Mock -CommandName Install-AdcsOnlineResponder + } + } - Mock -CommandName Remove-CAAuthorityInformationAccess -ParameterFilter { - $AddToCertificateAia -eq $true - } + It 'Should call the correct mocks' { + InModuleScope -ScriptBlock { + Set-StrictMode -Version 1.0 - Mock -CommandName Add-CAAuthorityInformationAccess -ParameterFilter { - $AddToCertificateOcsp -eq $true + $mockProperties = @{ + Ensure = [Ensure]::Present } - Mock -CommandName Remove-CAAuthorityInformationAccess -ParameterFilter { - $AddToCertificateOcsp -eq $true - } + $script:mockInstance.PropertiesNotInDesiredState = @( + @{ + Property = 'Ensure' + ExpectedValue = 'Present' + ActualValue = 'Absent' + } + ) - Mock -CommandName Restart-ServiceIfExists + { $script:mockInstance.Modify($mockProperties) } | Should -Not -Throw } - } - Context 'When the resource does not exist' { - It 'Should call the correct mocks' { - InModuleScope -ScriptBlock { - Set-StrictMode -Version 1.0 - - $mockProperties = @{ - AiaUri = @('http://example.com/aia1', 'http://example.com/aia2') - OcspUri = @('http://example.com/ocsp1', 'http://example.com/ocsp2') - } + Should -Invoke -CommandName Install-AdcsOnlineResponder -Exactly -Times 1 -Scope It + } + } - $script:mockInstance.PropertiesNotInDesiredState = @( - @{ - Property = 'AiaUri' - ExpectedValue = @('http://example.com/aia1', 'http://example.com/aia2') - ActualValue = @() - } - @{ - Property = 'OcspUri' - ExpectedValue = @('http://example.com/ocsp1', 'http://example.com/ocsp2') - ActualValue = @() - } - ) + Context 'When the resource should be added and fails' { + BeforeAll { + InModuleScope -ScriptBlock { + Set-StrictMode -Version 1.0 - $script:mockInstance.Modify($mockProperties) + $script:mockInstance = [AdcsOnlineResponder] @{ + IsSingleInstance = 'Yes' } - Should -Invoke -CommandName Add-CAAuthorityInformationAccess -ParameterFilter { - $AddToCertificateAia -eq $true - } -Exactly -Times 2 -Scope It - - Should -Invoke -CommandName Add-CAAuthorityInformationAccess -ParameterFilter { - $AddToCertificateOcsp -eq $true - } -Exactly -Times 2 -Scope It - - Should -Invoke -CommandName Remove-CAAuthorityInformationAccess -ParameterFilter { - $AddToCertificateAia -eq $true - } -Exactly -Times 0 -Scope It - - Should -Invoke -CommandName Remove-CAAuthorityInformationAccess -ParameterFilter { - $AddToCertificateOcsp -eq $true - } -Exactly -Times 0 -Scope It - - Should -Invoke -CommandName Restart-ServiceIfExists -Exactly -Times 0 -Scope It + Mock -CommandName Install-AdcsOnlineResponder -MockWith { throw } } } - Context 'When the AIA needs to be added' { - It 'Should call the correct mocks' { - InModuleScope -ScriptBlock { - Set-StrictMode -Version 1.0 - - $mockProperties = @{ - AiaUri = @('http://example.com/aia1', 'http://example.com/aia2') - } - - $script:mockInstance.PropertiesNotInDesiredState = @( - @{ - Property = 'AiaUri' - ExpectedValue = @('http://example.com/aia1', 'http://example.com/aia2') - ActualValue = @('http://example.com/aia1') - } - ) + It 'Should call the correct mocks' { + InModuleScope -ScriptBlock { + Set-StrictMode -Version 1.0 - $script:mockInstance.Modify($mockProperties) + $mockProperties = @{ + Ensure = [Ensure]::Present } - Should -Invoke -CommandName Add-CAAuthorityInformationAccess -ParameterFilter { - $AddToCertificateAia -eq $true - } -Exactly -Times 1 -Scope It - - Should -Invoke -CommandName Add-CAAuthorityInformationAccess -ParameterFilter { - $AddToCertificateOcsp -eq $true - } -Exactly -Times 0 -Scope It - - Should -Invoke -CommandName Remove-CAAuthorityInformationAccess -ParameterFilter { - $AddToCertificateAia -eq $true - } -Exactly -Times 0 -Scope It - - Should -Invoke -CommandName Remove-CAAuthorityInformationAccess -ParameterFilter { - $AddToCertificateOcsp -eq $true - } -Exactly -Times 0 -Scope It + $script:mockInstance.PropertiesNotInDesiredState = @( + @{ + Property = 'Ensure' + ExpectedValue = 'Present' + ActualValue = 'Absent' + } + ) - Should -Invoke -CommandName Restart-ServiceIfExists -Exactly -Times 0 -Scope It + { $script:mockInstance.Modify($mockProperties) } | Should -Throw } - } - Context 'When the OCSP needs to be added' { - It 'Should call the correct mocks' { - InModuleScope -ScriptBlock { - Set-StrictMode -Version 1.0 - - $mockProperties = @{ - OcspUri = @('http://example.com/ocsp1', 'http://example.com/ocsp2') - } + Should -Invoke -CommandName Install-AdcsOnlineResponder -Exactly -Times 1 -Scope It + } + } - $script:mockInstance.PropertiesNotInDesiredState = @( - @{ - Property = 'OcspUri' - ExpectedValue = @('http://example.com/ocsp1', 'http://example.com/ocsp2') - ActualValue = @('http://example.com/ocsp1') - } - ) + Context 'When the resource should be removed' { + BeforeAll { + InModuleScope -ScriptBlock { + Set-StrictMode -Version 1.0 - $script:mockInstance.Modify($mockProperties) + $script:mockInstance = [AdcsOnlineResponder] @{ + IsSingleInstance = 'Yes' } - Should -Invoke -CommandName Add-CAAuthorityInformationAccess -ParameterFilter { - $AddToCertificateAia -eq $true - } -Exactly -Times 0 -Scope It - - Should -Invoke -CommandName Add-CAAuthorityInformationAccess -ParameterFilter { - $AddToCertificateOcsp -eq $true - } -Exactly -Times 1 -Scope It - - Should -Invoke -CommandName Remove-CAAuthorityInformationAccess -ParameterFilter { - $AddToCertificateAia -eq $true - } -Exactly -Times 0 -Scope It - - Should -Invoke -CommandName Remove-CAAuthorityInformationAccess -ParameterFilter { - $AddToCertificateOcsp -eq $true - } -Exactly -Times 0 -Scope It - - Should -Invoke -CommandName Restart-ServiceIfExists -Exactly -Times 0 -Scope It + Mock -CommandName Uninstall-AdcsOnlineResponder } } - Context 'When the AIA needs to be removed' { - It 'Should call the correct mocks' { - InModuleScope -ScriptBlock { - Set-StrictMode -Version 1.0 - - $mockProperties = @{ - AiaUri = @('http://example.com/aia1', 'http://example.com/aia2') - } - - $script:mockInstance.PropertiesNotInDesiredState = @( - @{ - Property = 'AiaUri' - ExpectedValue = @('http://example.com/aia1') - ActualValue = @('http://example.com/aia1', 'http://example.com/aia2') - } - - ) + It 'Should call the correct mocks' { + InModuleScope -ScriptBlock { + Set-StrictMode -Version 1.0 - $script:mockInstance.Modify($mockProperties) + $mockProperties = @{ + Ensure = [Ensure]::Absent } - Should -Invoke -CommandName Add-CAAuthorityInformationAccess -ParameterFilter { - $AddToCertificateAia -eq $true - } -Exactly -Times 0 -Scope It - - Should -Invoke -CommandName Add-CAAuthorityInformationAccess -ParameterFilter { - $AddToCertificateOcsp -eq $true - } -Exactly -Times 0 -Scope It - - Should -Invoke -CommandName Remove-CAAuthorityInformationAccess -ParameterFilter { - $AddToCertificateAia -eq $true - } -Exactly -Times 1 -Scope It - - Should -Invoke -CommandName Remove-CAAuthorityInformationAccess -ParameterFilter { - $AddToCertificateOcsp -eq $true - } -Exactly -Times 0 -Scope It + $script:mockInstance.PropertiesNotInDesiredState = @( + @{ + Property = 'Ensure' + ExpectedValue = 'Absent' + ActualValue = 'Present' + } + ) - Should -Invoke -CommandName Restart-ServiceIfExists -Exactly -Times 0 -Scope It + { $script:mockInstance.Modify($mockProperties) } | Should -Not -Throw } - } - - Context 'When the OCSP needs to be removed' { - It 'Should call the correct mocks' { - InModuleScope -ScriptBlock { - Set-StrictMode -Version 1.0 - $mockProperties = @{ - OcspUri = @('http://example.com/ocsp1', 'http://example.com/ocsp2') - } + Should -Invoke -CommandName Uninstall-AdcsOnlineResponder -Exactly -Times 1 -Scope It + } + } - $script:mockInstance.PropertiesNotInDesiredState = @( - @{ - Property = 'OcspUri' - ExpectedValue = @('http://example.com/ocsp1') - ActualValue = @('http://example.com/ocsp1', 'http://example.com/ocsp2') - } - ) + Context 'When the resource should be removed but fails' { + BeforeAll { + InModuleScope -ScriptBlock { + Set-StrictMode -Version 1.0 - $script:mockInstance.Modify($mockProperties) + $script:mockInstance = [AdcsOnlineResponder] @{ + IsSingleInstance = 'Yes' } - Should -Invoke -CommandName Add-CAAuthorityInformationAccess -ParameterFilter { - $AddToCertificateAia -eq $true - } -Exactly -Times 0 -Scope It - - Should -Invoke -CommandName Add-CAAuthorityInformationAccess -ParameterFilter { - $AddToCertificateOcsp -eq $true - } -Exactly -Times 0 -Scope It - - Should -Invoke -CommandName Remove-CAAuthorityInformationAccess -ParameterFilter { - $AddToCertificateAia -eq $true - } -Exactly -Times 0 -Scope It - - Should -Invoke -CommandName Remove-CAAuthorityInformationAccess -ParameterFilter { - $AddToCertificateOcsp -eq $true - } -Exactly -Times 1 -Scope It - - Should -Invoke -CommandName Restart-ServiceIfExists -Exactly -Times 0 -Scope It + Mock -CommandName Uninstall-AdcsOnlineResponder -MockWith { throw } } } - Context 'When the OCSP needs to be removed and AllowRestartService is set to $true' { - It 'Should call the correct mocks' { - InModuleScope -ScriptBlock { - Set-StrictMode -Version 1.0 - - $mockProperties = @{ - OcspUri = @('http://example.com/ocsp1', 'http://example.com/ocsp2') - } - - $script:mockInstance.PropertiesNotInDesiredState = @( - @{ - Property = 'OcspUri' - ExpectedValue = @('http://example.com/ocsp1') - ActualValue = @('http://example.com/ocsp1', 'http://example.com/ocsp2') - } - ) - - $script:mockInstance.AllowRestartService = $true + It 'Should call the correct mocks' { + InModuleScope -ScriptBlock { + Set-StrictMode -Version 1.0 - $script:mockInstance.Modify($mockProperties) + $mockProperties = @{ + Ensure = [Ensure]::Absent } - Should -Invoke -CommandName Add-CAAuthorityInformationAccess -ParameterFilter { - $AddToCertificateAia -eq $true - } -Exactly -Times 0 -Scope It - - Should -Invoke -CommandName Add-CAAuthorityInformationAccess -ParameterFilter { - $AddToCertificateOcsp -eq $true - } -Exactly -Times 0 -Scope It - - Should -Invoke -CommandName Remove-CAAuthorityInformationAccess -ParameterFilter { - $AddToCertificateAia -eq $true - } -Exactly -Times 0 -Scope It - - Should -Invoke -CommandName Remove-CAAuthorityInformationAccess -ParameterFilter { - $AddToCertificateOcsp -eq $true - } -Exactly -Times 1 -Scope It + $script:mockInstance.PropertiesNotInDesiredState = @( + @{ + Property = 'Ensure' + ExpectedValue = 'Absent' + ActualValue = 'Present' + } + ) - Should -Invoke -CommandName Restart-ServiceIfExists -Exactly -Times 1 -Scope It + { $script:mockInstance.Modify($mockProperties) } | Should -Throw } + + Should -Invoke -CommandName Uninstall-AdcsOnlineResponder -Exactly -Times 1 -Scope It } } } From 8ad4cf007f9b4fbbdd876ddeb723f74697fdf0ad Mon Sep 17 00:00:00 2001 From: Dan Hughes <2237515+dan-hughes@users.noreply.github.com> Date: Sun, 31 Aug 2025 13:00:37 +0100 Subject: [PATCH 15/20] Fully qualify command names and remove used functions table --- source/Classes/020.AdcsOnlineResponder.ps1 | 25 +++++++--------------- 1 file changed, 8 insertions(+), 17 deletions(-) diff --git a/source/Classes/020.AdcsOnlineResponder.ps1 b/source/Classes/020.AdcsOnlineResponder.ps1 index 5632daa..d3da929 100644 --- a/source/Classes/020.AdcsOnlineResponder.ps1 +++ b/source/Classes/020.AdcsOnlineResponder.ps1 @@ -23,18 +23,9 @@ .PARAMETER Reasons Returns the reason a property is not in desired state. - - .NOTES - Used Functions: - Name | Module - ----------------------------- |------------------- - Install-AdcsOnlineResponder | ADCSDeployment - Uninstall-AdcsOnlineResponder | ADCSDeployment - Assert-Module | DscResource.Common - New-InvalidOperationException | DscResource.Common #> -[DscResource()] +[DscResource(RunAsCredential = 'NotSupported')] class AdcsOnlineResponder : ResourceBase { [DscProperty(Key)] @@ -74,7 +65,7 @@ class AdcsOnlineResponder : ResourceBase { try { - $null = Install-AdcsOnlineResponder -Credential $this.Credential -WhatIf + $null = ADCSDeployment\Install-AdcsOnlineResponder -Credential $this.Credential -WhatIf return @{} } @@ -87,7 +78,7 @@ class AdcsOnlineResponder : ResourceBase } } - return New-InvalidOperationException -Message $this.localizedData.ErrorGetCurrentState -ErrorRecord $_ + return DscResource.Common\New-InvalidOperationException -Message $this.localizedData.ErrorGetCurrentState -ErrorRecord $_ } } @@ -107,22 +98,22 @@ class AdcsOnlineResponder : ResourceBase { try { - Uninstall-AdcsOnlineResponder -Force + $null = ADCSDeployment\Uninstall-AdcsOnlineResponder -Force } catch { - New-InvalidOperationException -Message $_.Exception.Message -ErrorRecord $_ + DscResource.Common\New-InvalidOperationException -Message $_.Exception.Message -ErrorRecord $_ } } else { try { - Install-AdcsOnlineResponder -Credential $this.Credential -Force + $null = ADCSDeployment\Install-AdcsOnlineResponder -Credential $this.Credential -Force } catch { - New-InvalidOperationException -Message $_.Exception.Message -ErrorRecord $_ + DscResource.Common\New-InvalidOperationException -Message $_.Exception.Message -ErrorRecord $_ } } } @@ -139,6 +130,6 @@ class AdcsOnlineResponder : ResourceBase #> hidden [void] AssertProperties([System.Collections.Hashtable] $properties) { - Assert-Module -ModuleName 'ADCSDeployment' + DscResource.Common\Assert-Module -ModuleName 'ADCSDeployment' } } From 1001230e96a0e6b25b4d4b7ded2898dba0a100ec Mon Sep 17 00:00:00 2001 From: Dan Hughes <2237515+dan-hughes@users.noreply.github.com> Date: Sun, 31 Aug 2025 13:08:39 +0100 Subject: [PATCH 16/20] Remove fully qualified command names --- source/Classes/020.AdcsOnlineResponder.ps1 | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/source/Classes/020.AdcsOnlineResponder.ps1 b/source/Classes/020.AdcsOnlineResponder.ps1 index d3da929..3b0fb65 100644 --- a/source/Classes/020.AdcsOnlineResponder.ps1 +++ b/source/Classes/020.AdcsOnlineResponder.ps1 @@ -65,7 +65,7 @@ class AdcsOnlineResponder : ResourceBase { try { - $null = ADCSDeployment\Install-AdcsOnlineResponder -Credential $this.Credential -WhatIf + $null = Install-AdcsOnlineResponder -Credential $this.Credential -WhatIf return @{} } @@ -78,7 +78,7 @@ class AdcsOnlineResponder : ResourceBase } } - return DscResource.Common\New-InvalidOperationException -Message $this.localizedData.ErrorGetCurrentState -ErrorRecord $_ + return New-InvalidOperationException -Message $this.localizedData.ErrorGetCurrentState -ErrorRecord $_ } } @@ -98,22 +98,22 @@ class AdcsOnlineResponder : ResourceBase { try { - $null = ADCSDeployment\Uninstall-AdcsOnlineResponder -Force + $null = Uninstall-AdcsOnlineResponder -Force } catch { - DscResource.Common\New-InvalidOperationException -Message $_.Exception.Message -ErrorRecord $_ + New-InvalidOperationException -Message $_.Exception.Message -ErrorRecord $_ } } else { try { - $null = ADCSDeployment\Install-AdcsOnlineResponder -Credential $this.Credential -Force + $null = Install-AdcsOnlineResponder -Credential $this.Credential -Force } catch { - DscResource.Common\New-InvalidOperationException -Message $_.Exception.Message -ErrorRecord $_ + New-InvalidOperationException -Message $_.Exception.Message -ErrorRecord $_ } } } @@ -130,6 +130,6 @@ class AdcsOnlineResponder : ResourceBase #> hidden [void] AssertProperties([System.Collections.Hashtable] $properties) { - DscResource.Common\Assert-Module -ModuleName 'ADCSDeployment' + Assert-Module -ModuleName 'ADCSDeployment' } } From 43ad21fbd2e5203c859391545cbc5f0bc299c98f Mon Sep 17 00:00:00 2001 From: Dan Hughes <2237515+dan-hughes@users.noreply.github.com> Date: Sun, 31 Aug 2025 13:16:06 +0100 Subject: [PATCH 17/20] Improve Integration test reliability --- .../DSC_AdcsCertificationAuthority.Integration.Tests.ps1 | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/tests/Integration/DSC_AdcsCertificationAuthority.Integration.Tests.ps1 b/tests/Integration/DSC_AdcsCertificationAuthority.Integration.Tests.ps1 index 15fd8e6..b3e2ef7 100644 --- a/tests/Integration/DSC_AdcsCertificationAuthority.Integration.Tests.ps1 +++ b/tests/Integration/DSC_AdcsCertificationAuthority.Integration.Tests.ps1 @@ -245,6 +245,10 @@ Describe 'DSC_AdcsAuthorityInformationAccess_Integration' -Skip:$skipIntegration } } + AfterEach { + Wait-ForIdleLcm + } + It 'Should compile and apply the MOF without throwing' { { & 'DSC_AdcsAuthorityInformationAccess_Config' ` @@ -290,6 +294,10 @@ Describe 'DSC_AdcsAuthorityInformationAccess_Integration' -Skip:$skipIntegration } } + AfterEach { + Wait-ForIdleLcm + } + It 'Should compile and apply the MOF without throwing' { { & 'DSC_AdcsAuthorityInformationAccess_Config' ` From 4ba8b9a590861da2d76fc687da8634dd165dd653 Mon Sep 17 00:00:00 2001 From: Dan Hughes <2237515+dan-hughes@users.noreply.github.com> Date: Mon, 1 Sep 2025 09:47:13 +0100 Subject: [PATCH 18/20] Use preview DscResource.Test --- RequiredModules.psd1 | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/RequiredModules.psd1 b/RequiredModules.psd1 index 2a153c8..8c93f92 100644 --- a/RequiredModules.psd1 +++ b/RequiredModules.psd1 @@ -28,7 +28,12 @@ Sampler = 'latest' 'Sampler.GitHubTasks' = 'latest' MarkdownLinkCheck = 'latest' - 'DscResource.Test' = 'latest' + 'DscResource.Test' = @{ + Version = 'latest' + Parameters = @{ + AllowPrerelease = $true + } + } xDscResourceDesigner = 'latest' # Build dependencies needed for using the module From 0c42817b898e8f76ec0a63593619abe3b9057f02 Mon Sep 17 00:00:00 2001 From: Dan Hughes <2237515+dan-hughes@users.noreply.github.com> Date: Tue, 2 Sep 2025 12:25:30 +0100 Subject: [PATCH 19/20] Add HQRM fix --- source/ActiveDirectoryCSDsc.psd1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/ActiveDirectoryCSDsc.psd1 b/source/ActiveDirectoryCSDsc.psd1 index 745d0e1..6fbf6c5 100644 --- a/source/ActiveDirectoryCSDsc.psd1 +++ b/source/ActiveDirectoryCSDsc.psd1 @@ -27,7 +27,7 @@ FunctionsToExport = @() # Cmdlets to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no cmdlets to export. - CmdletsToExport = @() + CmdletsToExport = '*' # Variables to export from this module VariablesToExport = @() From b0ef088c64423db0b923abeaea738a183191cdc8 Mon Sep 17 00:00:00 2001 From: Dan Hughes <2237515+dan-hughes@users.noreply.github.com> Date: Tue, 2 Sep 2025 12:25:47 +0100 Subject: [PATCH 20/20] Move to latest DscResource.Test --- RequiredModules.psd1 | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/RequiredModules.psd1 b/RequiredModules.psd1 index 8c93f92..2a153c8 100644 --- a/RequiredModules.psd1 +++ b/RequiredModules.psd1 @@ -28,12 +28,7 @@ Sampler = 'latest' 'Sampler.GitHubTasks' = 'latest' MarkdownLinkCheck = 'latest' - 'DscResource.Test' = @{ - Version = 'latest' - Parameters = @{ - AllowPrerelease = $true - } - } + 'DscResource.Test' = 'latest' xDscResourceDesigner = 'latest' # Build dependencies needed for using the module