diff --git a/CHANGELOG.md b/CHANGELOG.md index 5011a8e..dbb8b8a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Changed + +- `Assert-BoundParameter` + - Enhanced parameter `IfParameterPresent` to support both string arrays and + hashtables for conditional assertion logic. + - Added alias `IfEqualParameterList` to parameter `IfParameterPresent` for + backward compatibility. + ## [0.24.0] - 2025-08-26 ### Added @@ -18,18 +26,18 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Added parameter `IfEqualParameterList` to conditionally perform assertions only when specified parameters have exact values [#160](https://github.com/dsccommunity/DscResource.Common/issues/160). - `Format-Path` - - Added parameter `ExpandEnvironmentVariable` fixes [#147](https://github.com/dsccommunity/DscResource.Common/issues/147). - - Added support to `Compare-DscParameterState` for comparing large hashtables - that contain lists of elements. + - Added parameter `ExpandEnvironmentVariable` (issue [#147](https://github.com/dsccommunity/DscResource.Common/issues/147)). +- `Compare-DscParameterState` + - Added support for comparing large hashtables that contain lists of elements. ### Changed - `Get-ComputerName` - - Replaced platform-specific logic with cross-platform implementation using + - Replaced platform-specific logic with cross-platform implementation using `[System.Environment]::MachineName` for consistent short name behavior. - - Enhanced FQDN functionality to use `[System.Net.Dns]::GetHostByName()` for + - Enhanced FQDN functionality to use `[System.Net.Dns]::GetHostEntry()` for proper domain name resolution on Windows, Linux, and macOS. - - Improved error handling to gracefully fallback to short name when DNS + - Improved error handling to gracefully fallback to short name when DNS resolution fails. ## [0.22.0] - 2025-04-25 diff --git a/source/Private/Assert-RequiredCommandParameter.ps1 b/source/Private/Assert-RequiredCommandParameter.ps1 index c915778..2cca6ea 100644 --- a/source/Private/Assert-RequiredCommandParameter.ps1 +++ b/source/Private/Assert-RequiredCommandParameter.ps1 @@ -26,7 +26,7 @@ Throws an exception if either of the two parameters are not specified. .EXAMPLE - Assert-RequiredCommandParameter -BoundParameter $PSBoundParameters -RequiredParameter @('PBStartPortRange', 'PBEndPortRange') -RequiredBehavior 'AtLeastOnce' + Assert-RequiredCommandParameter -BoundParameter $PSBoundParameters -RequiredParameter @('PBStartPortRange', 'PBEndPortRange') -RequiredBehavior 'Any' Throws an exception if at least one of the two parameters are not specified. @@ -84,7 +84,6 @@ function Assert-RequiredCommandParameter { $errorMessage = if ($PSBoundParameters.ContainsKey('IfParameterPresent')) { - $script:localizedData.RequiredCommandParameter_SpecificParametersMustAllBeSetWhenParameterExist -f ($RequiredParameter -join ''', '''), ($IfParameterPresent -join ''', ''') } else @@ -116,7 +115,6 @@ function Assert-RequiredCommandParameter { $errorMessage = if ($PSBoundParameters.ContainsKey('IfParameterPresent')) { - $script:localizedData.RequiredCommandParameter_SpecificParametersAtLeastOneMustBeSetWhenParameterExist -f ($RequiredParameter -join ''', '''), ($IfParameterPresent -join ''', ''') } else @@ -137,6 +135,5 @@ function Assert-RequiredCommandParameter break } } - } } diff --git a/source/Public/Assert-BoundParameter.ps1 b/source/Public/Assert-BoundParameter.ps1 index 280323d..ec516ce 100644 --- a/source/Public/Assert-BoundParameter.ps1 +++ b/source/Public/Assert-BoundParameter.ps1 @@ -52,10 +52,9 @@ If neither of the parameter names has been specified the evaluation of required parameters are not made. - .PARAMETER IfEqualParameterList - A hashtable of parameter names and their expected values. The assertion will - only be performed if all the specified parameters in the BoundParameterList - have the exact values specified in this hashtable. + This parameter can also accept a hashtable of parameter names and their expected + values. The assertion will only be performed if all the specified parameters in + the BoundParameterList have the exact values specified in this hashtable. .PARAMETER AtLeastOneList An array of parameter names where at least one must be bound. @@ -110,7 +109,7 @@ MutuallyExclusiveList2 = @( 'MessageId' ) - IfEqualParameterList = @{ + IfParameterPresent = @{ Ensure = 'Present' } } @@ -121,13 +120,13 @@ the value `Present`. .EXAMPLE - Assert-BoundParameter -BoundParameterList $PSBoundParameters -RequiredParameter @('Property2', 'Property3') -IfEqualParameterList @{ Property1 = 'SpecificValue' } + Assert-BoundParameter -BoundParameterList $PSBoundParameters -RequiredParameter @('Property2', 'Property3') -IfParameterPresent @{ Property1 = 'SpecificValue' } Throws an exception if the parameter 'Property1' has the value 'SpecificValue' and either of the required parameters are not specified. .EXAMPLE - Assert-BoundParameter -BoundParameterList $PSBoundParameters -AtLeastOneList @('Severity', 'MessageId') -IfEqualParameterList @{ Ensure = 'Present' } + Assert-BoundParameter -BoundParameterList $PSBoundParameters -AtLeastOneList @('Severity', 'MessageId') -IfParameterPresent @{ Ensure = 'Present' } Throws an exception if the parameter 'Ensure' has the value 'Present' and none of the parameters 'Severity' or 'MessageId' are specified. @@ -159,26 +158,37 @@ function Assert-BoundParameter $RequiredBehavior = [BoundParameterBehavior]::All, [Parameter(ParameterSetName = 'RequiredParameter')] - [System.String[]] - $IfParameterPresent, - [Parameter(ParameterSetName = 'MutuallyExclusiveParameters')] - [Parameter(ParameterSetName = 'RequiredParameter')] [Parameter(ParameterSetName = 'AtLeastOne')] - [System.Collections.Hashtable] - $IfEqualParameterList, + [Alias('IfEqualParameterList')] + [System.Object] + $IfParameterPresent, [Parameter(ParameterSetName = 'AtLeastOne', Mandatory = $true)] [System.String[]] $AtLeastOneList ) - # Early return if IfEqualParameterList conditions are not met - if ($PSBoundParameters.ContainsKey('IfEqualParameterList')) + # Early return if IfParameterPresent conditions are not met + if ($PSBoundParameters.ContainsKey('IfParameterPresent')) { - foreach ($parameterName in $IfEqualParameterList.Keys) + if ($IfParameterPresent -is [System.Collections.Hashtable]) { - if (-not $BoundParameterList.ContainsKey($parameterName) -or $BoundParameterList[$parameterName] -ne $IfEqualParameterList[$parameterName]) + # Handle hashtable case (original IfEqualParameterList behavior) + foreach ($parameterName in $IfParameterPresent.Keys) + { + if (-not $BoundParameterList.ContainsKey($parameterName) -or $BoundParameterList[$parameterName] -ne $IfParameterPresent[$parameterName]) + { + return + } + } + } + else + { + # Handle string array case (original IfParameterPresent behavior) + $hasIfParameterPresent = $BoundParameterList.Keys.Where( { $_ -in $IfParameterPresent } ) + + if (-not $hasIfParameterPresent) { return } @@ -207,17 +217,29 @@ function Assert-BoundParameter 'RequiredParameter' { - if ($PSBoundParameters.ContainsKey('IfEqualParameterList')) - { - $PSBoundParameters.Remove('IfEqualParameterList') + $assertRequiredCommandParameterParams = @{ + BoundParameterList = $BoundParameterList + RequiredParameter = $RequiredParameter + RequiredBehavior = $RequiredBehavior } - if (-not $PSBoundParameters.ContainsKey('RequiredBehavior')) + # Pass IfParameterPresent to Assert-RequiredCommandParameter for better error messages + if ($PSBoundParameters.ContainsKey('IfParameterPresent')) { - $PSBoundParameters.RequiredBehavior = $RequiredBehavior + if ($IfParameterPresent -is [System.Collections.Hashtable]) + { + # For hashtable case, pass the keys as IfParameterPresent + $assertRequiredCommandParameterParams.IfParameterPresent = $IfParameterPresent.Keys + } + else + { + # For string array case, pass as-is + $assertRequiredCommandParameterParams.IfParameterPresent = $IfParameterPresent + } } - Assert-RequiredCommandParameter @PSBoundParameters + Assert-RequiredCommandParameter @assertRequiredCommandParameterParams + break } diff --git a/tests/Integration/Commands/Assert-BoundParameter.Integration.Tests.ps1 b/tests/Integration/Commands/Assert-BoundParameter.Integration.Tests.ps1 index 450c913..3b88222 100644 --- a/tests/Integration/Commands/Assert-BoundParameter.Integration.Tests.ps1 +++ b/tests/Integration/Commands/Assert-BoundParameter.Integration.Tests.ps1 @@ -24,9 +24,9 @@ BeforeDiscovery { } BeforeAll { - $script:dscModuleName = 'DscResource.Common' + $script:moduleName = 'DscResource.Common' - Import-Module -Name $script:dscModuleName -Force -ErrorAction 'Stop' + Import-Module -Name $script:moduleName -Force -ErrorAction 'Stop' } Describe 'Assert-BoundParameter Integration Tests' -Tag 'AssertBoundParameterIntegration' { @@ -693,29 +693,6 @@ Describe 'Assert-BoundParameter Integration Tests' -Tag 'AssertBoundParameterInt Assert-BoundParameter @assertBoundParameterParameters } | Should -Not -Throw } - - It 'Should work with multiple conditions and IfParameterPresent together' { - { - $assertBoundParameterParameters = @{ - BoundParameterList = @{ - ConfigType = 'Advanced' - Environment = 'Production' - TriggerParam = 'Present' - Param1 = 'Value1' - Param2 = 'Value2' - } - RequiredParameter = @('Param1', 'Param2') - IfParameterPresent = @('TriggerParam') - IfEqualParameterList = @{ - ConfigType = 'Advanced' - Environment = 'Production' - } - ErrorAction = 'Stop' - } - - Assert-BoundParameter @assertBoundParameterParameters - } | Should -Not -Throw - } } Context 'When the IfEqualParameterList condition is not met' { diff --git a/tests/Integration/Commands/Get-ComputerName.Integration.Tests.ps1 b/tests/Integration/Commands/Get-ComputerName.Integration.Tests.ps1 index cb19366..c55c0a4 100644 --- a/tests/Integration/Commands/Get-ComputerName.Integration.Tests.ps1 +++ b/tests/Integration/Commands/Get-ComputerName.Integration.Tests.ps1 @@ -24,9 +24,9 @@ BeforeDiscovery { } BeforeAll { - $script:dscModuleName = 'DscResource.Common' + $script:moduleName = 'DscResource.Common' - Import-Module -Name $script:dscModuleName -Force -ErrorAction 'Stop' + Import-Module -Name $script:moduleName -Force -ErrorAction 'Stop' } Describe 'Get-ComputerName' -Tag 'GetComputerName' { diff --git a/tests/Integration/Commands/Set-DscMachineRebootRequired.Integration.Tests.ps1 b/tests/Integration/Commands/Set-DscMachineRebootRequired.Integration.Tests.ps1 index ecd4cff..36ee25e 100644 --- a/tests/Integration/Commands/Set-DscMachineRebootRequired.Integration.Tests.ps1 +++ b/tests/Integration/Commands/Set-DscMachineRebootRequired.Integration.Tests.ps1 @@ -24,9 +24,9 @@ BeforeDiscovery { } BeforeAll { - $script:dscModuleName = 'DscResource.Common' + $script:moduleName = 'DscResource.Common' - Import-Module -Name $script:dscModuleName -Force -ErrorAction 'Stop' + Import-Module -Name $script:moduleName -Force -ErrorAction 'Stop' } Describe 'Set-DscMachineRebootRequired' -Tag 'Set-DscMachineRebootRequired' { diff --git a/tests/Integration/Commands/Set-PSModulePath.Integration.Tests.ps1 b/tests/Integration/Commands/Set-PSModulePath.Integration.Tests.ps1 index e7efb72..62ca4ce 100644 --- a/tests/Integration/Commands/Set-PSModulePath.Integration.Tests.ps1 +++ b/tests/Integration/Commands/Set-PSModulePath.Integration.Tests.ps1 @@ -36,9 +36,9 @@ BeforeDiscovery { } BeforeAll { - $script:dscModuleName = 'DscResource.Common' + $script:moduleName = 'DscResource.Common' - Import-Module -Name $script:dscModuleName -Force -ErrorAction 'Stop' + Import-Module -Name $script:moduleName -Force -ErrorAction 'Stop' } Describe 'Set-PSModulePath' -Tag 'SetPSModulePath' { diff --git a/tests/Unit/Private/Assert-RequiredCommandParameter.Tests.ps1 b/tests/Unit/Private/Assert-RequiredCommandParameter.Tests.ps1 index b6c2827..19e28df 100644 --- a/tests/Unit/Private/Assert-RequiredCommandParameter.Tests.ps1 +++ b/tests/Unit/Private/Assert-RequiredCommandParameter.Tests.ps1 @@ -29,7 +29,7 @@ BeforeAll { # Make sure there are not other modules imported that will conflict with mocks. Get-Module -Name $script:moduleName -All | Remove-Module -Force - Import-Module -Name $script:moduleName + Import-Module -Name $script:moduleName -Force -ErrorAction 'Stop' $PSDefaultParameterValues['InModuleScope:ModuleName'] = $script:moduleName $PSDefaultParameterValues['Mock:ModuleName'] = $script:moduleName @@ -144,7 +144,7 @@ Describe 'Assert-RequiredCommandParameter' -Tag 'Private' { } Context 'When the parameters in IfParameterPresent is present and required parameters are present' { - It 'Should throw the correct error' { + It 'Should not throw an error' { InModuleScope -ScriptBlock { Set-StrictMode -Version 1.0 @@ -240,7 +240,7 @@ Describe 'Assert-RequiredCommandParameter' -Tag 'Private' { } Context 'When the parameters in IfParameterPresent is present and one of the required parameters are present' { - It 'Should throw the correct error' { + It 'Should not throw an error' { InModuleScope -ScriptBlock { Set-StrictMode -Version 1.0 diff --git a/tests/Unit/Private/Clear-ZeroedEnumPropertyValue.Tests.ps1 b/tests/Unit/Private/Clear-ZeroedEnumPropertyValue.Tests.ps1 index c4b3cb7..45cf728 100644 --- a/tests/Unit/Private/Clear-ZeroedEnumPropertyValue.Tests.ps1 +++ b/tests/Unit/Private/Clear-ZeroedEnumPropertyValue.Tests.ps1 @@ -29,7 +29,7 @@ BeforeAll { # Make sure there are not other modules imported that will conflict with mocks. Get-Module -Name $script:moduleName -All | Remove-Module -Force - Import-Module -Name $script:moduleName + Import-Module -Name $script:moduleName -Force -ErrorAction 'Stop' $PSDefaultParameterValues['InModuleScope:ModuleName'] = $script:moduleName $PSDefaultParameterValues['Mock:ModuleName'] = $script:moduleName diff --git a/tests/Unit/Private/Test-DscPropertyIsAssigned.Tests.ps1 b/tests/Unit/Private/Test-DscPropertyIsAssigned.Tests.ps1 index aaebd57..92efd43 100644 --- a/tests/Unit/Private/Test-DscPropertyIsAssigned.Tests.ps1 +++ b/tests/Unit/Private/Test-DscPropertyIsAssigned.Tests.ps1 @@ -24,13 +24,13 @@ BeforeDiscovery { } BeforeAll { - $script:dscModuleName = 'DscResource.Common' + $script:moduleName = 'DscResource.Common' - Import-Module -Name $script:dscModuleName + Import-Module -Name $script:moduleName -Force -ErrorAction 'Stop' - $PSDefaultParameterValues['InModuleScope:ModuleName'] = $script:dscModuleName - $PSDefaultParameterValues['Mock:ModuleName'] = $script:dscModuleName - $PSDefaultParameterValues['Should:ModuleName'] = $script:dscModuleName + $PSDefaultParameterValues['InModuleScope:ModuleName'] = $script:moduleName + $PSDefaultParameterValues['Mock:ModuleName'] = $script:moduleName + $PSDefaultParameterValues['Should:ModuleName'] = $script:moduleName } AfterAll { @@ -39,7 +39,7 @@ AfterAll { $PSDefaultParameterValues.Remove('Should:ModuleName') # Unload the module being tested so that it doesn't impact any other tests. - Get-Module -Name $script:dscModuleName -All | Remove-Module -Force + Get-Module -Name $script:moduleName -All | Remove-Module -Force } Describe 'Test-DscPropertyIsAssigned' -Tag 'Private' { diff --git a/tests/Unit/Public/Assert-BoundParameter.Tests.ps1 b/tests/Unit/Public/Assert-BoundParameter.Tests.ps1 index b34f572..82c3ee6 100644 --- a/tests/Unit/Public/Assert-BoundParameter.Tests.ps1 +++ b/tests/Unit/Public/Assert-BoundParameter.Tests.ps1 @@ -50,17 +50,17 @@ Describe 'Assert-BoundParameter' -Tag 'AssertBoundParameter' { @{ MockParameterSetName = 'MutuallyExclusiveParameters' # cSpell: disable-next - MockExpectedParameters = '-BoundParameterList -MutuallyExclusiveList1 -MutuallyExclusiveList2 [-IfEqualParameterList ] []' + MockExpectedParameters = '-BoundParameterList -MutuallyExclusiveList1 -MutuallyExclusiveList2 [-IfParameterPresent ] []' } @{ MockParameterSetName = 'RequiredParameter' # cSpell: disable-next - MockExpectedParameters = '-BoundParameterList -RequiredParameter [-RequiredBehavior ] [-IfParameterPresent ] [-IfEqualParameterList ] []' + MockExpectedParameters = '-BoundParameterList -RequiredParameter [-RequiredBehavior ] [-IfParameterPresent ] []' } @{ MockParameterSetName = 'AtLeastOne' # cSpell: disable-next - MockExpectedParameters = '-BoundParameterList -AtLeastOneList [-IfEqualParameterList ] []' + MockExpectedParameters = '-BoundParameterList -AtLeastOneList [-IfParameterPresent ] []' } ) { InModuleScope -Parameters $_ -ScriptBlock { @@ -314,9 +314,9 @@ Describe 'Assert-BoundParameter' -Tag 'AssertBoundParameter' { } } - Context 'When using IfEqualParameterList parameter' { + Context 'When using alias IfEqualParameterList parameter' { Context 'When using MutuallyExclusiveParameters parameter set' { - Context 'When the IfEqualParameterList condition is met' { + Context 'When the alias IfEqualParameterList condition is met' { It 'Should throw an error when mutually exclusive parameters are both present' { $errorMessage = InModuleScope -ScriptBlock { $script:localizedData.ParameterUsageWrong @@ -361,7 +361,7 @@ Describe 'Assert-BoundParameter' -Tag 'AssertBoundParameter' { } } - Context 'When the IfEqualParameterList condition is not met' { + Context 'When the alias IfEqualParameterList condition is not met' { It 'Should not throw an error even when mutually exclusive parameters are both present' { { $assertBoundParameterParameters = @{ @@ -400,7 +400,7 @@ Describe 'Assert-BoundParameter' -Tag 'AssertBoundParameter' { } } - Context 'When multiple conditions in IfEqualParameterList must match' { + Context 'When multiple conditions in alias IfEqualParameterList must match' { It 'Should throw an error when all conditions are met and mutually exclusive parameters are present' { $errorMessage = InModuleScope -ScriptBlock { $script:localizedData.ParameterUsageWrong @@ -456,7 +456,7 @@ Describe 'Assert-BoundParameter' -Tag 'AssertBoundParameter' { Mock -CommandName Assert-RequiredCommandParameter } - Context 'When the IfEqualParameterList condition is met' { + Context 'When the alias IfEqualParameterList condition is met' { It 'Should call Assert-RequiredCommandParameter when condition is met' { InModuleScope -ScriptBlock { $testParams = @{ @@ -477,7 +477,7 @@ Describe 'Assert-BoundParameter' -Tag 'AssertBoundParameter' { } } - Context 'When the IfEqualParameterList condition is not met' { + Context 'When the alias IfEqualParameterList condition is not met' { It 'Should not call Assert-RequiredCommandParameter when condition is not met' { InModuleScope -ScriptBlock { $testParams = @{ @@ -497,10 +497,102 @@ Describe 'Assert-BoundParameter' -Tag 'AssertBoundParameter' { Should -Invoke -CommandName Assert-RequiredCommandParameter -Exactly -Times 0 -Scope It } } + + Context 'When the IfParameterPresent condition is met using string value' { + It 'Should call Assert-RequiredCommandParameter when parameter is present' { + InModuleScope -ScriptBlock { + $testParams = @{ + BoundParameterList = @{ + Property1 = 'SomeValue' + Property2 = 'Value2' + } + RequiredParameter = @('Property2', 'Property3') + IfParameterPresent = 'Property1' + } + + Assert-BoundParameter @testParams + } + + Should -Invoke -CommandName Assert-RequiredCommandParameter -Exactly -Times 1 -Scope It + } + } + + Context 'When the IfParameterPresent condition is not met using string value' { + It 'Should not call Assert-RequiredCommandParameter when parameter is not present' { + InModuleScope -ScriptBlock { + $testParams = @{ + BoundParameterList = @{ + Property2 = 'Value2' + } + RequiredParameter = @('Property2', 'Property3') + IfParameterPresent = 'Property1' + } + + Assert-BoundParameter @testParams + } + + Should -Invoke -CommandName Assert-RequiredCommandParameter -Exactly -Times 0 -Scope It + } + } + + Context 'When the IfParameterPresent condition is met using string array with two items' { + It 'Should call Assert-RequiredCommandParameter when any parameter from array is present' { + InModuleScope -ScriptBlock { + $testParams = @{ + BoundParameterList = @{ + Property1 = 'SomeValue' + Property3 = 'Value3' + } + RequiredParameter = @('Property3', 'Property4') + IfParameterPresent = @('Property1', 'Property2') + } + + Assert-BoundParameter @testParams + } + + Should -Invoke -CommandName Assert-RequiredCommandParameter -Exactly -Times 1 -Scope It + } + + It 'Should call Assert-RequiredCommandParameter when second parameter from array is present' { + InModuleScope -ScriptBlock { + $testParams = @{ + BoundParameterList = @{ + Property2 = 'SomeValue' + Property3 = 'Value3' + } + RequiredParameter = @('Property3', 'Property4') + IfParameterPresent = @('Property1', 'Property2') + } + + Assert-BoundParameter @testParams + } + + Should -Invoke -CommandName Assert-RequiredCommandParameter -Exactly -Times 1 -Scope It + } + } + + Context 'When the IfParameterPresent condition is not met using string array with two items' { + It 'Should not call Assert-RequiredCommandParameter when none of the parameters from array are present' { + InModuleScope -ScriptBlock { + $testParams = @{ + BoundParameterList = @{ + Property3 = 'Value3' + Property4 = 'Value4' + } + RequiredParameter = @('Property3', 'Property4') + IfParameterPresent = @('Property1', 'Property2') + } + + Assert-BoundParameter @testParams + } + + Should -Invoke -CommandName Assert-RequiredCommandParameter -Exactly -Times 0 -Scope It + } + } } Context 'When using AtLeastOne parameter set' { - Context 'When the IfEqualParameterList condition is met' { + Context 'When the alias IfEqualParameterList condition is met' { It 'Should throw an error when none of the required parameters are present' { $errorMessage = InModuleScope -ScriptBlock { $script:localizedData.Assert_BoundParameter_AtLeastOneParameterMustBeSet @@ -542,7 +634,7 @@ Describe 'Assert-BoundParameter' -Tag 'AssertBoundParameter' { } } - Context 'When the IfEqualParameterList condition is not met' { + Context 'When the alias IfEqualParameterList condition is not met' { It 'Should not throw an error even when none of the required parameters are present' { { $assertBoundParameterParameters = @{