From 364f21759a3802838927401dd2cf4f84895da6e5 Mon Sep 17 00:00:00 2001 From: Dan Hughes <2237515+dan-hughes@users.noreply.github.com> Date: Thu, 1 May 2025 17:53:01 +0100 Subject: [PATCH 1/8] Update Changelog --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index ab2cfd5..02e731a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Public command: - `Get-FileProductVersion` - Get the product version of a file. - `Test-PendingRestart` - Test if a pending restart is required. + - Private command + - `Clear-ZeroedEnumPropertyValue` from `DscResource.Base`. +- `Get-DscProperty` + - Add optional parameter `IgnoreZeroEnumValue`. ### Fixed From 9787c67995b4d71c00bc27f5c98ef93820e5e61f Mon Sep 17 00:00:00 2001 From: Dan Hughes <2237515+dan-hughes@users.noreply.github.com> Date: Thu, 1 May 2025 17:53:17 +0100 Subject: [PATCH 2/8] Add function from DscResource.Base --- .../Private/Clear-ZeroedEnumPropertyValue.ps1 | 47 ++++++ .../Clear-ZeroedEnumPropertyValue.Tests.ps1 | 154 ++++++++++++++++++ 2 files changed, 201 insertions(+) create mode 100644 source/Private/Clear-ZeroedEnumPropertyValue.ps1 create mode 100644 tests/Unit/Private/Clear-ZeroedEnumPropertyValue.Tests.ps1 diff --git a/source/Private/Clear-ZeroedEnumPropertyValue.ps1 b/source/Private/Clear-ZeroedEnumPropertyValue.ps1 new file mode 100644 index 0000000..20b31aa --- /dev/null +++ b/source/Private/Clear-ZeroedEnumPropertyValue.ps1 @@ -0,0 +1,47 @@ +<# + .SYNOPSIS + Removes any properties from a hashable which have values that are + type [System.Enum] and have an [System.Int32] value of 0. + + .DESCRIPTION + Removes any properties from a hashable which have values that are + type [System.Enum] and have an [System.Int32] value of 0. + + .PARAMETER InputObject + The hashtable to be checked. + + .EXAMPLE + Clear-ZeroedEnumPropertyValue -InputObject $ht + + .OUTPUTS + [System.Collections.Hashtable] +#> + +function Clear-ZeroedEnumPropertyValue +{ + [CmdletBinding()] + [OutputType([System.Collections.Hashtable])] + param ( + [Parameter(Mandatory = $true, ValueFromPipeline = $true)] + [System.Collections.Hashtable] + $InputObject + ) + + process + { + $result = @{} + + foreach ($property in $InputObject.Keys) + { + $value = $InputObject.$property + if ($value -is [System.Enum] -and [System.Int32]$value.value__ -eq 0) + { + continue + } + + $result.$property = $value + } + + return $result + } +} diff --git a/tests/Unit/Private/Clear-ZeroedEnumPropertyValue.Tests.ps1 b/tests/Unit/Private/Clear-ZeroedEnumPropertyValue.Tests.ps1 new file mode 100644 index 0000000..c4b3cb7 --- /dev/null +++ b/tests/Unit/Private/Clear-ZeroedEnumPropertyValue.Tests.ps1 @@ -0,0 +1,154 @@ +[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:moduleName = 'DscResource.Common' + + # 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 + + $PSDefaultParameterValues['InModuleScope:ModuleName'] = $script:moduleName + $PSDefaultParameterValues['Mock:ModuleName'] = $script:moduleName + $PSDefaultParameterValues['Should:ModuleName'] = $script:moduleName +} + +AfterAll { + $PSDefaultParameterValues.Remove('InModuleScope:ModuleName') + $PSDefaultParameterValues.Remove('Mock:ModuleName') + $PSDefaultParameterValues.Remove('Should:ModuleName') + + # Unload the module being tested so that it doesn't impact any other tests. + Get-Module -Name $script:moduleName -All | Remove-Module -Force +} + +Describe 'Clear-ZeroedEnumPropertyValue' -Tag 'Private' { + Context 'When the hashtable does not contain zeroed Enum properties' { + Context 'When input is passed as a named variable' { + It 'Should return the same amount of values' { + InModuleScope -ScriptBlock { + Set-StrictMode -Version 1.0 + + $testParams = @{ + Variable1 = 'SomeString' + Variable2 = [System.Int32] 10 + Variable3 = $true + Variable4 = New-TimeSpan -Days 8 + } + + $result = Clear-ZeroedEnumPropertyValue -InputObject $testParams + + $result.Count | Should -Be $testParams.Count + } + } + } + + Context 'When input is passed via pipeline' { + It 'Should return the same amount of values' { + InModuleScope -ScriptBlock { + Set-StrictMode -Version 1.0 + + $testParams = @{ + Variable1 = 'SomeString' + Variable2 = [System.Int32] 10 + Variable3 = $true + Variable4 = New-TimeSpan -Days 8 + } + + $result = $testParams | Clear-ZeroedEnumPropertyValue + + $result.Count | Should -Be $testParams.Count + } + } + } + } + + Context 'When the hashtable does contain zeroed Enum properties' { + Context 'When input is passed as a named variable' { + It 'Should return the same amount of values' { + InModuleScope -ScriptBlock { + Set-StrictMode -Version 1.0 + + enum MyMockEnum + { + Value1 = 1 + Value2 + Value3 + Value4 + Value5 + } + + $testParams = @{ + Variable1 = 'SomeString' + Variable2 = [System.Int32] 10 + Variable3 = $true + Variable4 = New-TimeSpan -Days 8 + Variable5 = [MyMockEnum]::Value1 + Variable6 = [MyMockEnum]::new() + Variable7 = [MyMockEnum]::Value3 + Variable8 = [MyMockEnum]::new() + } + + $result = Clear-ZeroedEnumPropertyValue -InputObject $testParams + + $result.Count | Should -Be ($testParams.Count - 2) + } + } + } + + Context 'When input is passed via pipeline' { + It 'Should return the same amount of values' { + InModuleScope -ScriptBlock { + Set-StrictMode -Version 1.0 + + enum MyMockEnum + { + Value1 = 1 + Value2 + Value3 + Value4 + Value5 + } + + $testParams = @{ + Variable1 = 'SomeString' + Variable2 = [System.Int32] 10 + Variable3 = $true + Variable4 = New-TimeSpan -Days 8 + Variable5 = [MyMockEnum]::Value1 + Variable6 = [MyMockEnum]::new() + Variable7 = [MyMockEnum]::Value3 + Variable8 = [MyMockEnum]::new() + } + + $result = $testParams | Clear-ZeroedEnumPropertyValue + + $result.Count | Should -Be ($testParams.Count - 2) + } + } + } + } +} From 56da3db65d6c050f0e02c0df19ae209b072e3c21 Mon Sep 17 00:00:00 2001 From: Dan Hughes <2237515+dan-hughes@users.noreply.github.com> Date: Thu, 1 May 2025 18:06:09 +0100 Subject: [PATCH 3/8] Add new parameter --- source/Public/Get-DscProperty.ps1 | 14 ++++- tests/Unit/Public/Get-DscProperty.Tests.ps1 | 63 +++++++++++++++++++++ 2 files changed, 76 insertions(+), 1 deletion(-) diff --git a/source/Public/Get-DscProperty.ps1 b/source/Public/Get-DscProperty.ps1 index d9693f4..2b74d6b 100644 --- a/source/Public/Get-DscProperty.ps1 +++ b/source/Public/Get-DscProperty.ps1 @@ -26,6 +26,9 @@ If left out all properties are returned regardless if there is a value assigned or not. + .PARAMETER IgnoreZeroEnumValue + Specifies to return only Enum properties that has been assigned a non zero value. + .OUTPUTS System.Collections.Hashtable @@ -88,7 +91,11 @@ function Get-DscProperty [Parameter()] [System.Management.Automation.SwitchParameter] - $HasValue + $HasValue, + + [Parameter()] + [System.Management.Automation.SwitchParameter] + $IgnoreZeroEnumValue ) process @@ -167,6 +174,11 @@ function Get-DscProperty $getPropertyResult.$currentProperty = $InputObject.$currentProperty } + if ($IgnoreZeroEnumValue.IsPresent) + { + $getPropertyResult = $getPropertyResult | Clear-ZeroedEnumPropertyValue + } + return $getPropertyResult } } diff --git a/tests/Unit/Public/Get-DscProperty.Tests.ps1 b/tests/Unit/Public/Get-DscProperty.Tests.ps1 index 9d90f82..5a565db 100644 --- a/tests/Unit/Public/Get-DscProperty.Tests.ps1 +++ b/tests/Unit/Public/Get-DscProperty.Tests.ps1 @@ -625,6 +625,69 @@ Describe 'Get-DscProperty' -Tag 'Public' { } } + Context 'When using parameter IgnoreZeroEnumValue' { + Context 'When getting all optional properties' { + BeforeAll { + enum MyEnum + { + MyValue1 = 1 + MyValue2 + MyValue3 + } + + class MyMockResource + { + [DscProperty(Key)] + [System.String] + $MyResourceKeyProperty1 + + [DscProperty(Key)] + [System.String] + $MyResourceKeyProperty2 + + [DscProperty(Mandatory)] + [System.String] + $MyResourceMandatoryProperty + + [DscProperty()] + [MyEnum] + $MyResourceProperty1 + + [DscProperty()] + [MyEnum] + $MyResourceProperty2 + + [DscProperty(NotConfigurable)] + [System.String] + $MyResourceReadProperty + } + + $script:mockResourceBaseInstance = [MyMockResource]::new() + $script:mockResourceBaseInstance.MyResourceKeyProperty1 = 'MockValue1' + $script:mockResourceBaseInstance.MyResourceKeyProperty2 = 'MockValue2' + $script:mockResourceBaseInstance.MyResourceMandatoryProperty = 'MockValue3' + $script:mockResourceBaseInstance.MyResourceProperty1 = [MyEnum]::new() + $script:mockResourceBaseInstance.MyResourceProperty2 = [MyEnum]::MyValue3 + } + + It 'Should return the correct value' { + $result = Get-DscProperty -Attribute 'Optional' -HasValue -InputObject $script:mockResourceBaseInstance -IgnoreZeroEnumValue + + $result | Should -BeOfType [System.Collections.Hashtable] + + $result.Keys | Should -Not -Contain 'MyResourceMandatoryProperty' -Because 'mandatory properties should not be part of the collection' + $result.Keys | Should -Not -Contain 'MyResourceKeyProperty1' -Because 'key properties should not be part of the collection' + $result.Keys | Should -Not -Contain 'MyResourceKeyProperty2' -Because 'key properties should not be part of the collection' + $result.Keys | Should -Not -Contain 'MyResourceReadProperty' -Because 'read properties should not be part of the collection' + + $result.Keys | Should -Not -Contain 'MyResourceProperty1' -Because 'the enum property has a zero value' + + $result.Keys | Should -Contain 'MyResourceProperty2' -Because 'the enum property has a non zero value' + $result.MyResourceProperty2 | Should -Be MyValue3 + } + } + } + Context 'When getting specific named properties' { BeforeAll { class MyMockResource From 260cc1fa79cdb1081ac3901256cae8ba489ec345 Mon Sep 17 00:00:00 2001 From: Dan Hughes <2237515+dan-hughes@users.noreply.github.com> Date: Thu, 1 May 2025 18:09:20 +0100 Subject: [PATCH 4/8] Update example --- source/Public/Get-DscProperty.ps1 | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/source/Public/Get-DscProperty.ps1 b/source/Public/Get-DscProperty.ps1 index 2b74d6b..2531eb1 100644 --- a/source/Public/Get-DscProperty.ps1 +++ b/source/Public/Get-DscProperty.ps1 @@ -58,6 +58,12 @@ Returns the DSC resource properties that has the specified attributes and has a non-null value assigned. + .EXAMPLE + Get-DscProperty -InputObject $this -Attribute @('Optional') -HasValue -IgnoreZeroEnumValue + + Returns the DSC resource properties that has the specified attributes and + has a non-null value assigned, and any Enum properties that has a non-zero value. + .OUTPUTS [System.Collections.Hashtable] From 277cd496ea3a1ea9f2f42bbe1b2252a3760907d2 Mon Sep 17 00:00:00 2001 From: Dan Hughes <2237515+dan-hughes@users.noreply.github.com> Date: Fri, 2 May 2025 11:00:59 +0100 Subject: [PATCH 5/8] Add parameterset and make HasValue mandatory with IgnoreZeroEnumValue --- source/Public/Get-DscProperty.ps1 | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/source/Public/Get-DscProperty.ps1 b/source/Public/Get-DscProperty.ps1 index 2531eb1..8359d72 100644 --- a/source/Public/Get-DscProperty.ps1 +++ b/source/Public/Get-DscProperty.ps1 @@ -96,10 +96,11 @@ function Get-DscProperty $Attribute, [Parameter()] + [Parameter(ParameterSetName = 'IgnoreZeroEnumValue', Mandatory = $true)] [System.Management.Automation.SwitchParameter] $HasValue, - [Parameter()] + [Parameter(ParameterSetName = 'IgnoreZeroEnumValue')] [System.Management.Automation.SwitchParameter] $IgnoreZeroEnumValue ) From ad6de661a9a506cd778ad2af77d6ea5609dbc63d Mon Sep 17 00:00:00 2001 From: Dan Hughes <2237515+dan-hughes@users.noreply.github.com> Date: Fri, 2 May 2025 12:43:32 +0100 Subject: [PATCH 6/8] Add two named parameter sets --- source/Public/Get-DscProperty.ps1 | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/source/Public/Get-DscProperty.ps1 b/source/Public/Get-DscProperty.ps1 index 8359d72..af798ce 100644 --- a/source/Public/Get-DscProperty.ps1 +++ b/source/Public/Get-DscProperty.ps1 @@ -73,29 +73,33 @@ #> function Get-DscProperty { - [CmdletBinding()] + [CmdletBinding(DefaultParameterSetName = 'BaseSet')] [OutputType([System.Collections.Hashtable])] param ( - [Parameter(Mandatory = $true, ValueFromPipeline = $true)] + [Parameter(Mandatory = $true, ValueFromPipeline = $true, ParameterSetName = 'BaseSet')] + [Parameter(Mandatory = $true, ValueFromPipeline = $true, ParameterSetName = 'IgnoreZeroEnumValue')] [PSObject] $InputObject, - [Parameter()] + [Parameter(ParameterSetName = 'BaseSet')] + [Parameter(ParameterSetName = 'IgnoreZeroEnumValue')] [System.String[]] $Name, - [Parameter()] + [Parameter(ParameterSetName = 'BaseSet')] + [Parameter(ParameterSetName = 'IgnoreZeroEnumValue')] [System.String[]] $ExcludeName, - [Parameter()] + [Parameter(ParameterSetName = 'BaseSet')] + [Parameter(ParameterSetName = 'IgnoreZeroEnumValue')] [ValidateSet('Key', 'Mandatory', 'NotConfigurable', 'Optional')] [Alias('Type')] [System.String[]] $Attribute, - [Parameter()] + [Parameter(ParameterSetName = 'BaseSet')] [Parameter(ParameterSetName = 'IgnoreZeroEnumValue', Mandatory = $true)] [System.Management.Automation.SwitchParameter] $HasValue, From d5b0e9aea1994964022b2e927c221e1a84c71d8b Mon Sep 17 00:00:00 2001 From: Dan Hughes <2237515+dan-hughes@users.noreply.github.com> Date: Fri, 2 May 2025 12:46:44 +0100 Subject: [PATCH 7/8] Optimise --- source/Public/Get-DscProperty.ps1 | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/source/Public/Get-DscProperty.ps1 b/source/Public/Get-DscProperty.ps1 index af798ce..6ef71e7 100644 --- a/source/Public/Get-DscProperty.ps1 +++ b/source/Public/Get-DscProperty.ps1 @@ -77,23 +77,19 @@ function Get-DscProperty [OutputType([System.Collections.Hashtable])] param ( - [Parameter(Mandatory = $true, ValueFromPipeline = $true, ParameterSetName = 'BaseSet')] - [Parameter(Mandatory = $true, ValueFromPipeline = $true, ParameterSetName = 'IgnoreZeroEnumValue')] + [Parameter(Mandatory = $true, ValueFromPipeline = $true)] [PSObject] $InputObject, - [Parameter(ParameterSetName = 'BaseSet')] - [Parameter(ParameterSetName = 'IgnoreZeroEnumValue')] + [Parameter()] [System.String[]] $Name, - [Parameter(ParameterSetName = 'BaseSet')] - [Parameter(ParameterSetName = 'IgnoreZeroEnumValue')] + [Parameter()] [System.String[]] $ExcludeName, - [Parameter(ParameterSetName = 'BaseSet')] - [Parameter(ParameterSetName = 'IgnoreZeroEnumValue')] + [Parameter()] [ValidateSet('Key', 'Mandatory', 'NotConfigurable', 'Optional')] [Alias('Type')] [System.String[]] From 73734c6915cb323ee83fd1a3f86d34925c471273 Mon Sep 17 00:00:00 2001 From: Dan Hughes <2237515+dan-hughes@users.noreply.github.com> Date: Fri, 2 May 2025 13:19:51 +0100 Subject: [PATCH 8/8] Clean up parametersets --- source/Public/Get-DscProperty.ps1 | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/source/Public/Get-DscProperty.ps1 b/source/Public/Get-DscProperty.ps1 index 6ef71e7..96d59c7 100644 --- a/source/Public/Get-DscProperty.ps1 +++ b/source/Public/Get-DscProperty.ps1 @@ -73,34 +73,37 @@ #> function Get-DscProperty { - [CmdletBinding(DefaultParameterSetName = 'BaseSet')] + [CmdletBinding(DefaultParameterSetName = 'Default')] [OutputType([System.Collections.Hashtable])] param ( - [Parameter(Mandatory = $true, ValueFromPipeline = $true)] + [Parameter(Mandatory = $true, ValueFromPipeline = $true, ParameterSetName = 'Default')] + [Parameter(Mandatory = $true, ValueFromPipeline = $true, ParameterSetName = 'HasValue')] [PSObject] $InputObject, - [Parameter()] + [Parameter(ParameterSetName = 'Default')] + [Parameter(ParameterSetName = 'HasValue')] [System.String[]] $Name, - [Parameter()] + [Parameter(ParameterSetName = 'Default')] + [Parameter(ParameterSetName = 'HasValue')] [System.String[]] $ExcludeName, - [Parameter()] + [Parameter(ParameterSetName = 'Default')] + [Parameter(ParameterSetName = 'HasValue')] [ValidateSet('Key', 'Mandatory', 'NotConfigurable', 'Optional')] [Alias('Type')] [System.String[]] $Attribute, - [Parameter(ParameterSetName = 'BaseSet')] - [Parameter(ParameterSetName = 'IgnoreZeroEnumValue', Mandatory = $true)] + [Parameter(ParameterSetName = 'HasValue', Mandatory = $true)] [System.Management.Automation.SwitchParameter] $HasValue, - [Parameter(ParameterSetName = 'IgnoreZeroEnumValue')] + [Parameter(ParameterSetName = 'HasValue')] [System.Management.Automation.SwitchParameter] $IgnoreZeroEnumValue )