diff --git a/source/Public/Compare-DscParameterState.ps1 b/source/Public/Compare-DscParameterState.ps1 new file mode 100644 index 0000000..e69f065 --- /dev/null +++ b/source/Public/Compare-DscParameterState.ps1 @@ -0,0 +1,499 @@ +<# + .SYNOPSIS + This method is used to compare current and desired values for any DSC resource. + + .DESCRIPTION + This function compare the parameter status of DSC resource parameters against + the current values present on the system, and return a hashtable with the metadata + from the comparison. + + .PARAMETER CurrentValues + A hashtable with the current values on the system, obtained by e.g. + Get-TargetResource. + + .PARAMETER DesiredValues + The hashtable of desired values. For example $PSBoundParameters with the + desired values. + + .PARAMETER Properties + This is a list of properties in the desired values list should be checked. + If this is empty then all values in DesiredValues are checked. + + .PARAMETER ExcludeProperties + This is a list of which properties in the desired values list should be checked. + If this is empty then all values in DesiredValues are checked. + + .PARAMETER TurnOffTypeChecking + Indicates that the type of the parameter should not be checked. + + .PARAMETER ReverseCheck + Indicates that a reverse check should be done. The current and desired state + are swapped for another test. + + .PARAMETER SortArrayValues + If the sorting of array values does not matter, values are sorted internally + before doing the comparison. + + .EXAMPLE + + + $currentState = Get-TargetResource @PSBoundParameters + + $returnValue = Compare-DscParameterState -CurrentValues $currentState -DesiredValues $PSBoundParameters + + The function Get-TargetResource is called first using all bound parameters + to get the values in the current state. The result is then compared to the + desired state by calling `Compare-DscParameterState`. + + .EXAMPLE + $getTargetResourceParameters = @{ + ServerName = $ServerName + InstanceName = $InstanceName + Name = $Name + } + + $returnValue = Compare-DscParameterState ` + -CurrentValues (Get-TargetResource @getTargetResourceParameters) ` + -DesiredValues $PSBoundParameters ` + -ExcludeProperties @( + 'FailsafeOperator' + 'NotificationMethod' + ) + + This compares the values in the current state against the desires state. + The function Get-TargetResource is called using just the required parameters + to get the values in the current state. The parameter 'ExcludeProperties' + is used to exclude the properties 'FailsafeOperator' and + 'NotificationMethod' from the comparison. + + .EXAMPLE + $getTargetResourceParameters = @{ + ServerName = $ServerName + InstanceName = $InstanceName + Name = $Name + } + + $returnValue = Compare-DscParameterState ` + -CurrentValues (Get-TargetResource @getTargetResourceParameters) ` + -DesiredValues $PSBoundParameters ` + -Properties ServerName, Name + + This compares the values in the current state against the desires state. + The function Get-TargetResource is called using just the required parameters + to get the values in the current state. The 'Properties' parameter is used + to to only compare the properties 'ServerName' and 'Name'. +#> +function Compare-DscParameterState +{ + [CmdletBinding()] + param + ( + [Parameter(Mandatory = $true)] + [System.Object] + $CurrentValues, + + [Parameter(Mandatory = $true)] + [System.Object] + $DesiredValues, + + [Parameter()] + [System.String[]] + [Alias('ValuesToCheck')] + $Properties, + + [Parameter()] + [System.String[]] + $ExcludeProperties, + + [Parameter()] + [System.Management.Automation.SwitchParameter] + $TurnOffTypeChecking, + + [Parameter()] + [System.Management.Automation.SwitchParameter] + $ReverseCheck, + + [Parameter()] + [System.Management.Automation.SwitchParameter] + $SortArrayValues + ) + + $returnValue = @() + #region ConvertCIm to Hashtable + if ($CurrentValues -is [Microsoft.Management.Infrastructure.CimInstance] -or + $CurrentValues -is [Microsoft.Management.Infrastructure.CimInstance[]]) + { + $CurrentValues = ConvertTo-HashTable -CimInstance $CurrentValues + } + + if ($DesiredValues -is [Microsoft.Management.Infrastructure.CimInstance] -or + $DesiredValues -is [Microsoft.Management.Infrastructure.CimInstance[]]) + { + $DesiredValues = ConvertTo-HashTable -CimInstance $DesiredValues + } + #endregion Endofconverion + #region CheckType of object + $types = 'System.Management.Automation.PSBoundParametersDictionary', + 'System.Collections.Hashtable', + 'Microsoft.Management.Infrastructure.CimInstance' # but why if you convert it before ? + + if ($DesiredValues.GetType().FullName -notin $types) + { + New-InvalidArgumentException ` + -Message ($script:localizedData.InvalidDesiredValuesError -f $DesiredValues.GetType().FullName) ` + -ArgumentName 'DesiredValues' + } + + if ($CurrentValues.GetType().FullName -notin $types) + { + New-InvalidArgumentException ` + -Message ($script:localizedData.InvalidCurrentValuesError -f $CurrentValues.GetType().FullName) ` + -ArgumentName 'CurrentValues' + } + #endregion checktype + #region check if CimInstance and not have properties in parameters invoke exception + if ($DesiredValues -is [Microsoft.Management.Infrastructure.CimInstance] -and -not $Properties) + { + New-InvalidArgumentException ` + -Message $script:localizedData.InvalidPropertiesError ` + -ArgumentName Properties + } + #endregion check cim and properties + #Clean value if there are a common parameters provide from Test/Get-TargetResource parameter + $desiredValuesClean = Remove-CommonParameter -Hashtable $DesiredValues + #region generate keyList based on $Properties and $excludeProperties value + if (-not $Properties) + { + $keyList = $desiredValuesClean.Keys + } + else + { + $keyList = $Properties + } + if ($ExcludeProperties) + { + $keyList = $keyList | Where-Object -FilterScript { $_ -notin $ExcludeProperties } + } + #endregion + #region enumerate of each key in list + foreach ($key in $keyList) + { + #generate default value + $complianceTable = @{ + Property = $key + Compliance = $true + } + $returnValue += $complianceTable + #get value of each key + $desiredValue = $desiredValuesClean.$key + $currentValue = $CurrentValues.$key + + #region convert to hashtable if value of key is CimInstance + if ($desiredValue -is [Microsoft.Management.Infrastructure.CimInstance] -or + $desiredValue -is [Microsoft.Management.Infrastructure.CimInstance[]]) + { + $desiredValue = ConvertTo-HashTable -CimInstance $desiredValue + } + if ($currentValue -is [Microsoft.Management.Infrastructure.CimInstance] -or + $currentValue -is [Microsoft.Management.Infrastructure.CimInstance[]]) + { + $currentValue = ConvertTo-HashTable -CimInstance $currentValue + } + #endregion converttohashtable + #region gettype of value to check if they are the same. + if ($null -ne $desiredValue) + { + $desiredType = $desiredValue.GetType() + } + else + { + $desiredType = @{ + Name = 'Unknown' + } + } + + if ($null -ne $currentValue) + { + $currentType = $currentValue.GetType() + } + else + { + $currentType = @{ + Name = 'Unknown' + } + } + #endregion + #region check if the desiredtype if a credential object. Only if the current type isn't unknown. + if ($currentType.Name -ne 'Unknown' -and $desiredType.Name -eq 'PSCredential') + { + # This is a credential object. Compare only the user name + if ($currentType.Name -eq 'PSCredential' -and $currentValue.UserName -eq $desiredValue.UserName) + { + Write-Verbose -Message ($script:localizedData.MatchPsCredentialUsernameMessage -f $currentValue.UserName, $desiredValue.UserName) + continue # pass to the next key + } + else + { + Write-Verbose -Message ($script:localizedData.NoMatchPsCredentialUsernameMessage -f $currentValue.UserName, $desiredValue.UserName) + $complianceTable.Compliance = $false + } + + # Assume the string is our username when the matching desired value is actually a credential + if ($currentType.Name -eq 'string' -and $currentValue -eq $desiredValue.UserName) + { + Write-Verbose -Message ($script:localizedData.MatchPsCredentialUsernameMessage -f $currentValue, $desiredValue.UserName) + continue # pass to the next key + } + else + { + Write-Verbose -Message ($script:localizedData.NoMatchPsCredentialUsernameMessage -f $currentValue, $desiredValue.UserName) + $complianceTable.Compliance = $false + } + } + #endregion test credential + #region Test type of object. And if they're not compliance, generate en exception + if (-not $TurnOffTypeChecking) + { + if (($desiredType.Name -ne 'Unknown' -and $currentType.Name -ne 'Unknown') -and + $desiredType.FullName -ne $currentType.FullName) + { + Write-Verbose -Message ($script:localizedData.NoMatchTypeMismatchMessage -f $key, $currentType.FullName, $desiredType.FullName) + $complianceTable.Compliance = $false + continue # pass to the next key + } + } + #endregion TestType + #region Check if the value of Current and desired state is the same but only if they are not an array + if ($currentValue -eq $desiredValue -and -not $desiredType.IsArray) + { + Write-Verbose -Message ($script:localizedData.MatchValueMessage -f $desiredType.FullName, $key, $currentValue, $desiredValue) + continue # pass to the next key + } + #endregion check same value + #region Check if the DesiredValuesClean has the key and if it don't have, it's not necessary to check his value + if ($desiredValuesClean.GetType().Name -in 'HashTable', 'PSBoundParametersDictionary') + { + $checkDesiredValue = $desiredValuesClean.ContainsKey($key) + } + else + { + $checkDesiredValue = Test-DscObjectHasProperty -Object $desiredValuesClean -PropertyName $key + } + # if there no key, don't need to check + if (-not $checkDesiredValue) + { + Write-Verbose -Message ($script:localizedData.MatchValueMessage -f $desiredType.FullName, $key, $currentValue, $desiredValue) + continue # pass to the next key + } + #endregion + #region Check if desired type is array, ifno Hashtable and currenttype hashtable to + if ($desiredType.IsArray) + { + Write-Verbose -Message ($script:localizedData.TestDscParameterCompareMessage -f $key, $desiredType.FullName) + # Check if the currentValues and desiredValue are empty array. + if (-not $currentValue -and -not $desiredValue) + { + Write-Verbose -Message ($script:localizedData.MatchValueMessage -f $desiredType.FullName, $key, 'empty array', 'empty array') + continue + } + elseif (-not $currentValue) + { + #If only currentvalue is empty, the configuration isn't compliant. + Write-Verbose -Message ($script:localizedData.NoMatchValueMessage -f $desiredType.FullName, $key, $currentValue, $desiredValue) + $complianceTable.Compliance = $false + continue + } + elseif ($currentValue.Count -ne $desiredValue.Count) + { + #If there is a difference between the number of objects in arrays, this isn't compliant. + Write-Verbose -Message ($script:localizedData.NoMatchValueDifferentCountMessage -f $desiredType.FullName, $key, $currentValue.Count, $desiredValue.Count) + $complianceTable.Compliance = $false + continue + } + else + { + $desiredArrayValues = $desiredValue + $currentArrayValues = $currentValue + # if the sortArrayValues parameter is using, sort value of array + if ($SortArrayValues) + { + $desiredArrayValues = @($desiredArrayValues | Sort-Object) + $currentArrayValues = @($currentArrayValues | Sort-Object) + } + <# + for all object in collection, check their type.ConvertoString if they are script block. + + #> + for ($i = 0; $i -lt $desiredArrayValues.Count; $i++) + { + if ($desiredArrayValues[$i]) + { + $desiredType = $desiredArrayValues[$i].GetType() + } + else + { + $desiredType = @{ + Name = 'Unknown' + } + } + + if ($currentArrayValues[$i]) + { + $currentType = $currentArrayValues[$i].GetType() + } + else + { + $currentType = @{ + Name = 'Unknown' + } + } + + if (-not $TurnOffTypeChecking) + { + if (($desiredType.Name -ne 'Unknown' -and $currentType.Name -ne 'Unknown') -and + $desiredType.FullName -ne $currentType.FullName) + { + Write-Verbose -Message ($script:localizedData.NoMatchElementTypeMismatchMessage -f $key, $i, $currentType.FullName, $desiredType.FullName) + $complianceTable.Compliance = $false + continue + } + } + + <# + Convert a scriptblock into a string as scriptblocks are not comparable + if currentvalue is scriptblock and if desired value is string, + we invoke the result of script block. Ifno, we convert to string. + if Desired value + #> + + $wasCurrentArrayValuesConverted = $false + if ($currentArrayValues[$i] -is [scriptblock]) + { + $currentArrayValues[$i] = if ($desiredArrayValues[$i] -is [string]) + { + $currentArrayValues[$i] = $currentArrayValues[$i].Invoke() + } + else + { + $currentArrayValues[$i].ToString() + } + $wasCurrentArrayValuesConverted = $true + } + if ($desiredArrayValues[$i] -is [scriptblock]) + { + $desiredArrayValues[$i] = if ($currentArrayValues[$i] -is [string] -and -not $wasCurrentArrayValuesConverted) + { + $desiredArrayValues[$i].Invoke() + } + else + { + $desiredArrayValues[$i].ToString() + } + } + + if ($desiredType -eq [System.Collections.Hashtable] -and $currentType -eq [System.Collections.Hashtable]) + { + $param = $PSBoundParameters + $param.CurrentValues = $currentArrayValues[$i] + $param.DesiredValues = $desiredArrayValues[$i] + + if ($complianceTable.Compliance) + { + $complianceTable.Compliance = Test-DscParameterState @param + } + else + { + Test-DscParameterState @param | Out-Null + } + continue + } + + if ($desiredArrayValues[$i] -ne $currentArrayValues[$i]) + { + Write-Verbose -Message ($script:localizedData.NoMatchElementValueMismatchMessage -f $i, $desiredType.FullName, $key, $currentArrayValues[$i], $desiredArrayValues[$i]) + $complianceTable.Compliance = $false + continue + } + else + { + Write-Verbose -Message ($script:localizedData.MatchElementValueMessage -f $i, $desiredType.FullName, $key, $currentArrayValues[$i], $desiredArrayValues[$i]) + continue + } + } + + } + } + elseif ($desiredType -eq [System.Collections.Hashtable] -and $currentType -eq [System.Collections.Hashtable]) + { + $param = $PSBoundParameters + $param.CurrentValues = $currentValue + $param.DesiredValues = $desiredValue + + if ($complianceTable.Compliance) + { + $complianceTable.Compliance = Test-DscParameterState @param + } + else + { + Test-DscParameterState @param | Out-Null + } + continue + } + else + { + #Convert a scriptblock into a string as scriptblocks are not comparable + $wasCurrentValue = $false + if ($currentValue -is [scriptblock]) + { + $currentValue = if ($desiredValue -is [string]) + { + $currentValue = $currentValue.Invoke() + } + else + { + $currentValue.ToString() + } + $wasCurrentValue = $true + } + if ($desiredValue -is [scriptblock]) + { + $desiredValue = if ($currentValue -is [string] -and -not $wasCurrentValue) + { + $desiredValue.Invoke() + } + else + { + $desiredValue.ToString() + } + } + + if ($desiredValue -ne $currentValue) + { + Write-Verbose -Message ($script:localizedData.NoMatchValueMessage -f $desiredType.FullName, $key, $currentValue, $desiredValue) + $complianceTable.Compliance = $false + } + } + #endregion check type + } + #endregion end of enumeration + if ($ReverseCheck) + { + Write-Verbose -Message $script:localizedData.StartingReverseCheck + $reverseCheckParameters = $PSBoundParameters + $reverseCheckParameters.CurrentValues = $DesiredValues + $reverseCheckParameters.DesiredValues = $CurrentValues + $null = $reverseCheckParameters.Remove('ReverseCheck') + + if ($returnValue) + { + $returnValue = Compare-DscParameterState @reverseCheckParameters + } + else + { + $null = Compare-DscParameterState @reverseCheckParameters + } + } + + Write-Verbose -Message ($script:localizedData.TestDscParameterResultMessage -f $returnValue) + return $returnValue +} diff --git a/tests/Unit/Public/Compare-DscParameterState.Tests.ps1 b/tests/Unit/Public/Compare-DscParameterState.Tests.ps1 new file mode 100644 index 0000000..94173b2 --- /dev/null +++ b/tests/Unit/Public/Compare-DscParameterState.Tests.ps1 @@ -0,0 +1,1373 @@ +$ProjectPath = "$PSScriptRoot\..\..\.." | Convert-Path +$ProjectName = ((Get-ChildItem -Path $ProjectPath\*\*.psd1).Where{ + ($_.Directory.Name -match 'source|src' -or $_.Directory.Name -eq $_.BaseName) -and + $(try + { + Test-ModuleManifest $_.FullName -ErrorAction Stop + } + catch + { + $false + } ) + }).BaseName + +Import-Module $ProjectName -Force + +InModuleScope $ProjectName { + Describe 'ComputerManagementDsc.Common\Compare-DscParameterState' { + BeforeAll { + $verbose = $true + } + + Context 'When testing single values' { + BeforeAll{ + $currentValues = @{ + String = 'a string' + Bool = $true + Int = 99 + Array = 'a', 'b', 'c' + Hashtable = @{ + k1 = 'Test' + k2 = 123 + k3 = 'v1', 'v2', 'v3' + } + ScriptBlock = { Get-Date } + } + } + Context 'When all values match' { + BeforeAll{ + $desiredValues = [PSObject] @{ + String = 'a string' + Bool = $true + Int = 99 + Array = 'a', 'b', 'c' + Hashtable = @{ + k1 = 'Test' + k2 = 123 + k3 = 'v1', 'v2', 'v3' + } + ScriptBlock = { Get-Date } + } + } + It 'Should not throw exception' { + { $script:result = Compare-DscParameterState ` + -CurrentValues $currentValues ` + -DesiredValues $desiredValues ` + -Verbose:$verbose } | Should -Not -Throw + } + + It 'Should return all compliance in $true' { + $script:result.Compliance | Should -Not -Contain $false + } + } + + Context 'When a string is mismatched' { + BeforeAll { + $desiredValues = [PSObject] @{ + String = 'different string' + Bool = $true + Int = 99 + Array = 'a', 'b', 'c' + Hashtable = @{ + k1 = 'Test' + k2 = 123 + k3 = 'v1', 'v2', 'v3' + } + } + } + It 'Should not throw exception' { + { $script:result = Compare-DscParameterState ` + -CurrentValues $currentValues ` + -DesiredValues $desiredValues ` + -Verbose:$verbose } | Should -Not -Throw + } + + It 'Should return $false for String compliance' { + $script:result.where({$_.Property -eq 'String'}).Compliance | Should -BeFalse + } + + It 'Should return all compliance (without String property) in $true' { + $script:result.where({$_.Property -ne 'String'}).Compliance | Should -Not -Contain $false + } + } + + Context 'When a boolean is mismatched' { + BeforeAll{ + $desiredValues = [PSObject] @{ + String = 'a string' + Bool = $false + Int = 99 + Array = 'a', 'b', 'c' + Hashtable = @{ + k1 = 'Test' + k2 = 123 + k3 = 'v1', 'v2', 'v3' + } + } + } + + It 'Should not throw exception' { + { $script:result = Compare-DscParameterState ` + -CurrentValues $currentValues ` + -DesiredValues $desiredValues ` + -Verbose:$verbose } | Should -Not -Throw + } + + It 'Should return $false for Bool compliance' { + $script:result.where({$_.Property -eq 'Bool'}).Compliance | Should -BeFalse + } + + It 'Should return all compliance (without Bool property) in $true' { + $script:result.where({$_.Property -ne 'Bool'}).Compliance | Should -Not -Contain $false + } + } + + Context 'When an int is mismatched' { + BeforeAll { + $desiredValues = [PSObject] @{ + String = 'a string' + Bool = $true + Int = 1 + Array = 'a', 'b', 'c' + Hashtable = @{ + k1 = 'Test' + k2 = 123 + k3 = 'v1', 'v2', 'v3' + } + } + } + It 'Should not throw exception' { + { $script:result = Compare-DscParameterState ` + -CurrentValues $currentValues ` + -DesiredValues $desiredValues ` + -Verbose:$verbose } | Should -Not -Throw + } + + It 'Should return $false for Int compliance' { + $script:result.where({$_.Property -eq 'Int'}).Compliance | Should -BeFalse + } + + It 'Should return all compliance (without Int property) in $true' { + $script:result.where({$_.Property -ne 'Int'}).Compliance | Should -Not -Contain $false + } + } + + Context 'When an scriptblock is mismatched' { + BeforeAll { + $desiredValues = [PSObject] @{ + String = 'a string' + Bool = $true + Int = 99 + Array = 'a', 'b', 'c' + Hashtable = @{ + k1 = 'Test' + k2 = 123 + k3 = 'v1', 'v2', 'v3' + } + ScriptBlock = { Get-Process } + } + } + + It 'Should not throw exception' { + { $script:result = Compare-DscParameterState ` + -CurrentValues $currentValues ` + -DesiredValues $desiredValues ` + -Verbose:$verbose } | Should -Not -Throw + } + + It 'Should return $false for ScriptBlock compliance' { + $script:result.where({$_.Property -eq 'ScriptBlock'}).Compliance | Should -BeFalse + } + + It 'Should return all compliance (without ScriptBlock property) in $true' { + $script:result.where({$_.Property -ne 'ScriptBlock'}).Compliance | Should -Not -Contain $false + } + } + + Context 'When an int is mismatched without ScriptBlock' { + BeforeAll { + $desiredValues = [PSObject] @{ + String = 'a string' + Bool = $true + Int = 1 + Array = 'a', 'b', 'c' + Hashtable = @{ + k1 = 'Test' + k2 = 123 + k3 = 'v1', 'v2', 'v3' + } + } + } + + It 'Should not throw exception' { + { $script:result = Compare-DscParameterState ` + -CurrentValues $currentValues ` + -DesiredValues $desiredValues ` + -Verbose:$verbose } | Should -Not -Throw + } + + It 'Should return $false for Int compliance' { + $script:result.where({$_.Property -eq 'Int'}).Compliance | Should -BeFalse + } + + It 'Should return all compliance (without Int property) in $true' { + $script:result.where({$_.Property -ne 'Int'}).Compliance | Should -Not -Contain $false + } + + It 'Should not return property with ScriptBlock in value' { + $script:result.where({$_.Property -eq 'ScriptBlock'}) | Should -BeNullOrEmpty + } + } + + Context 'When a type is mismatched' { + BeforeAll{ + $desiredValues = [PSObject] @{ + String = 'a string' + Bool = $true + Int = '99' + Array = 'a', 'b', 'c' + } + } + + It 'Should not throw exception' { + { $script:result = Compare-DscParameterState ` + -CurrentValues $currentValues ` + -DesiredValues $desiredValues ` + -Verbose:$verbose } | Should -Not -Throw + } + + It 'Should return $false for Int compliance' { + $script:result.where({$_.Property -eq 'Int'}).Compliance | Should -BeFalse + } + + It 'Should return all compliance (without Int property) in $true' { + $script:result.where({$_.Property -ne 'Int'}).Compliance | Should -Not -Contain $false + } + } + + Context 'When a type is mismatched but TurnOffTypeChecking is used' { + BeforeAll{ + $desiredValues = [PSObject] @{ + String = 'a string' + Bool = $true + Int = '99' + Array = 'a', 'b', 'c' + } + } + + It 'Should not throw exception' { + { $script:result = Compare-DscParameterState ` + -CurrentValues $currentValues ` + -DesiredValues $desiredValues ` + -TurnOffTypeChecking ` + -Verbose:$verbose } | Should -Not -Throw + } + + It 'Should return $true for Int compliance' { + $script:result.where({$_.Property -eq 'Int'}).Compliance | Should -BeTrue + } + + It 'Should return all compliance in $true' { + $script:result.Compliance | Should -Not -Contain $false + } + } + + Context 'When a value is mismatched but ExcludeProperties is used to exclude then' { + BeforeAll{ + $desiredValues = @{ + String = 'some other string' + Bool = $true + Int = 99 + Array = 'a', 'b', 'c' + Hashtable = @{ + k1 = 'Test' + k2 = 123 + k3 = 'v1', 'v2', 'v3' + } + ScriptBlock = { Get-Date } + } + } + + $excludeProperties = @( + 'String' + ) + + It 'Should not throw exception' { + { $script:result = Compare-DscParameterState ` + -CurrentValues $currentValues ` + -DesiredValues $desiredValues ` + -ExcludeProperties $excludeProperties ` + -Verbose:$verbose } | Should -Not -Throw + } + + It 'Should return all compliance in $true' { + $script:result.Compliance | Should -Not -Contain $false + } + + It 'Should not return property with String in value' { + $script:result.where({$_.Property -eq 'String'}) | Should -BeNullOrEmpty + } + } + Context 'When a value is mismatched but it is not in Properties then' { + BeforeAll{ + $desiredValues = @{ + String = 'some other string' + Bool = $true + Int = 99 + Array = 'a', 'b', 'c' + Hashtable = @{ + k1 = 'Test' + k2 = 123 + k3 = 'v1', 'v2', 'v3' + } + ScriptBlock = { Get-Date } + } + + $properties = @( + 'Bool' + 'Int' + 'Array' + 'Hashtable' + 'ScriptBlock' + ) + } + + Context 'When using the alias ValuesToCheck' { + It 'Should not throw exception' { + { $script:result = Compare-DscParameterState ` + -CurrentValues $currentValues ` + -DesiredValues $desiredValues ` + -ValuesToCheck $properties ` + -Verbose:$verbose } | Should -Not -Throw + } + + It 'Should return all compliance in $true' { + $script:result.Compliance | Should -Not -Contain $false + } + + It 'Should return all property in $properties' { + $script:result.Property.Count | Should -Be $Properties.Count + foreach ($Property in $Properties){ + $property | Should -BeIn $script:result.Property + } + } + } + + It 'Should not throw exception' { + { $script:result = Compare-DscParameterState ` + -CurrentValues $currentValues ` + -DesiredValues $desiredValues ` + -Properties $properties ` + -Verbose:$verbose } | Should -Not -Throw + } + + It 'Should return all compliance in $true' { + $script:result.Compliance | Should -Not -Contain $false + } + + It 'Should return all property in $properties' { + $script:result.Property.Count | Should -Be $Properties.Count + foreach ($Property in $Properties){ + $property | Should -BeIn $script:result.Property + } + } + + Context 'When a value is mismatched but it is in Properties then' { + BeforeAll{ + $properties = @( + 'String' + 'Bool' + 'Int' + 'Array' + 'Hashtable' + ) + } + It 'Should not throw exception' { + { $script:result = Compare-DscParameterState ` + -CurrentValues $currentValues ` + -DesiredValues $desiredValues ` + -Properties $properties ` + -Verbose:$verbose } | Should -Not -Throw + } + + It 'Should return $false for String compliance' { + $script:result.where({$_.Property -eq 'String'}).Compliance | Should -BeFalse + } + + It 'Should return all compliance (without String property) in $true' { + $script:result.where({$_.Property -ne 'String'}).Compliance | Should -Not -Contain $false + } + + It 'Should return all property in $properties' { + $script:result.Property.Count | Should -Be $Properties.Count + foreach ($Property in $Properties){ + $property | Should -BeIn $script:result.Property + } + } + + It 'Should not return property with ScriptBlock in value' { + $script:result.where({$_.Property -eq 'ScriptBlock'}) | Should -BeNullOrEmpty + } + } + } + } + + Context 'When testing array values' { + BeforeAll { + $currentValues = @{ + String = 'a string' + Bool = $true + Int = 99 + Array = 'a', 'b', 'c', 1 + Hashtable = @{ + k1 = 'Test' + k2 = 123 + k3 = 'v1', 'v2', 'v3' + } + } + } + + Context 'When array is missing a value' { + BeforeAll { + $desiredValues = [PSObject] @{ + String = 'a string' + Bool = $true + Int = 99 + Array = 'a', 'b', 'c' + Hashtable = @{ + k1 = 'Test' + k2 = 123 + k3 = 'v1', 'v2', 'v3' + } + } + } + + It 'Should not throw exception' { + { $script:result = Compare-DscParameterState ` + -CurrentValues $currentValues ` + -DesiredValues $desiredValues ` + -Verbose:$verbose } | Should -Not -Throw + } + + It 'Should return $false for Array compliance' { + $script:result.where({$_.Property -eq 'Array'}).Compliance | Should -BeFalse + } + + It 'Should return all compliance (without Array property) in $true' { + $script:result.where({$_.Property -ne 'Array'}).Compliance | Should -Not -Contain $false + } + } + + Context 'When array has an additional value' { + BeforeAll { + $desiredValues = [PSObject] @{ + String = 'a string' + Bool = $true + Int = 99 + Array = 'a', 'b', 'c', 1, 2 + } + } + + It 'Should not throw exception' { + { $script:result = Compare-DscParameterState ` + -CurrentValues $currentValues ` + -DesiredValues $desiredValues ` + -Verbose:$verbose } | Should -Not -Throw + } + + It 'Should return $false for Array compliance' { + $script:result.where({$_.Property -eq 'Array'}).Compliance | Should -BeFalse + } + + It 'Should return all compliance (without Array property) in $true' { + $script:result.where({$_.Property -ne 'Array'}).Compliance | Should -Not -Contain $false + } + } + + Context 'When array has a different value' { + BeforeAll { + $desiredValues = [PSObject] @{ + String = 'a string' + Bool = $true + Int = 99 + Array = 'a', 'x', 'c', 1 + } + } + + It 'Should not throw exception' { + { $script:result = Compare-DscParameterState ` + -CurrentValues $currentValues ` + -DesiredValues $desiredValues ` + -Verbose:$verbose } | Should -Not -Throw + } + + It 'Should return $false for Array compliance' { + $script:result.where({$_.Property -eq 'Array'}).Compliance | Should -BeFalse + } + + It 'Should return all compliance (without Array property) in $true' { + $script:result.where({$_.Property -ne 'Array'}).Compliance | Should -Not -Contain $false + } + } + + Context 'When array has different order' { + BeforeAll { + $desiredValues = [PSObject] @{ + String = 'a string' + Bool = $true + Int = 99 + Array = 'c', 'b', 'a', 1 + } + } + + It 'Should not throw exception' { + { $script:result = Compare-DscParameterState ` + -CurrentValues $currentValues ` + -DesiredValues $desiredValues ` + -Verbose:$verbose } | Should -Not -Throw + } + + It 'Should return $false for Array compliance' { + $script:result.where({$_.Property -eq 'Array'}).Compliance | Should -BeFalse + } + + It 'Should return all compliance (without Array property) in $true' { + $script:result.where({$_.Property -ne 'Array'}).Compliance | Should -Not -Contain $false + } + } + + Context 'When array has different order but SortArrayValues is used' { + BeforeAll { + $desiredValues = [PSObject] @{ + String = 'a string' + Bool = $true + Int = 99 + Array = 'c', 'b', 'a', 1 + } + } + + It 'Should not throw exception' { + { $script:result = Compare-DscParameterState ` + -CurrentValues $currentValues ` + -DesiredValues $desiredValues ` + -SortArrayValues ` + -Verbose:$verbose } | Should -Not -Throw + } + + It 'Should return $true for Array compliance' { + $script:result.where({$_.Property -eq 'Array'}).Compliance | Should -BeTrue + } + + It 'Should return all compliance (without Array property) in $true' { + $script:result.where({$_.Property -ne 'Array'}).Compliance | Should -Not -Contain $false + } + } + + Context 'When array has a value with a different type' { + BeforeAll { + $desiredValues = [PSObject] @{ + String = 'a string' + Bool = $true + Int = 99 + Array = 'a', 'b', 'c', '1' + } + } + + It 'Should not throw exception' { + { $script:result = Compare-DscParameterState ` + -CurrentValues $currentValues ` + -DesiredValues $desiredValues ` + -Verbose:$verbose } | Should -Not -Throw + } + + It 'Should return $false for Array compliance' { + $script:result.where({$_.Property -eq 'Array'}).Compliance | Should -BeFalse + } + + It 'Should return all compliance (without Array property) in $true' { + $script:result.where({$_.Property -ne 'Array'}).Compliance | Should -Not -Contain $false + } + } + + Context 'When array has a value with a different type but TurnOffTypeChecking is used' { + BeforeAll { + $desiredValues = [PSObject] @{ + String = 'a string' + Bool = $true + Int = 99 + Array = 'a', 'b', 'c', '1' + } + } + + It 'Should not throw exception' { + { $script:result = Compare-DscParameterState ` + -CurrentValues $currentValues ` + -DesiredValues $desiredValues ` + -TurnOffTypeChecking ` + -Verbose:$verbose } | Should -Not -Throw + } + + It 'Should return $true for Array compliance' { + $script:result.where({$_.Property -eq 'Array'}).Compliance | Should -BeTrue + } + + It 'Should return all compliance (without Array property) in $true' { + $script:result.where({$_.Property -ne 'Array'}).Compliance | Should -Not -Contain $false + } + } + + Context 'When both arrays are empty' { + BeforeAll { + $currentValues = @{ + String = 'a string' + Bool = $true + Int = 99 + Array = @() + Hashtable = @{ + k1 = 'Test' + k2 = 123 + k3 = @() + } + } + + $desiredValues = [PSObject] @{ + String = 'a string' + Bool = $true + Int = 99 + Array = @() + Hashtable = @{ + k1 = 'Test' + k2 = 123 + k3 = @() + } + } + } + + It 'Should not throw exception' { + { $script:result = Compare-DscParameterState ` + -CurrentValues $currentValues ` + -DesiredValues $desiredValues ` + -Verbose:$verbose } | Should -Not -Throw + } + + It 'Should return $true for Array compliance' { + $script:result.where({$_.Property -eq 'Array'}).Compliance | Should -BeTrue + } + + It 'Should return all compliance (without Array property) in $true' { + $script:result.where({$_.Property -ne 'Array'}).Compliance | Should -Not -Contain $false + } + } + + Context 'When a current value array is empty' { + BeforeAll { + $currentValues = @{ + String = 'a string' + Bool = $true + Int = 99 + Array = @('a','b','c') + Hashtable = @{ + k1 = 'Test' + k2 = 123 + k3 = @() + } + } + + $desiredValues = [PSObject] @{ + String = 'a string' + Bool = $true + Int = 99 + Array = @() + Hashtable = @{ + k1 = 'Test' + k2 = 123 + k3 = @() + } + } + } + + It 'Should not throw exception' { + { $script:result = Compare-DscParameterState ` + -CurrentValues $currentValues ` + -DesiredValues $desiredValues ` + -Verbose:$verbose } | Should -Not -Throw + } + + It 'Should return $false for Array compliance' { + $script:result.where({$_.Property -eq 'Array'}).Compliance | Should -BeFalse + } + + It 'Should return all compliance (without Array property) in $true' { + $script:result.where({$_.Property -ne 'Array'}).Compliance | Should -Not -Contain $false + } + } + + Context 'When a desired value array is empty' { + BeforeAll { + $currentValues = @{ + String = 'a string' + Bool = $true + Int = 99 + Array = @() + Hashtable = @{ + k1 = 'Test' + k2 = 123 + k3 = @() + } + } + + $desiredValues = [PSObject] @{ + String = 'a string' + Bool = $true + Int = 99 + Array = @('a','b','c') + Hashtable = @{ + k1 = 'Test' + k2 = 123 + k3 = @() + } + } + } + + It 'Should not throw exception' { + { $script:result = Compare-DscParameterState ` + -CurrentValues $currentValues ` + -DesiredValues $desiredValues ` + -Verbose:$verbose } | Should -Not -Throw + } + + It 'Should return $false for Array compliance' { + $script:result.where({$_.Property -eq 'Array'}).Compliance | Should -BeFalse + } + + It 'Should return all compliance (without Array property) in $true' { + $script:result.where({$_.Property -ne 'Array'}).Compliance | Should -Not -Contain $false + } + } + } + + Context 'When testing hashtables' { + BeforeAll { + $currentValues = @{ + String = 'a string' + Bool = $true + Int = 99 + Array = 'a', 'b', 'c' + Hashtable = @{ + k1 = 'Test' + k2 = 123 + k3 = 'v1', 'v2', 'v3', 99 + } + } + } + + Context 'When hashtable is missing a value' { + BeforeAll { + $desiredValues = [PSObject] @{ + String = 'a string' + Bool = $true + Int = 99 + Array = 'a', 'b', 'c' + Hashtable = @{ + k1 = 'Test' + k2 = 123 + k3 = 'v1', 'v2', 'v3' + } + } + } + + It 'Should not throw exception' { + { $script:result = Compare-DscParameterState ` + -CurrentValues $currentValues ` + -DesiredValues $desiredValues ` + -Verbose:$verbose } | Should -Not -Throw + } + + It 'Should return $false for Hashtable compliance' { + $script:result.where({$_.Property -eq 'Hashtable'}).Compliance | Should -BeFalse + } + + It 'Should return all compliance (without Hashtable property) in $true' { + $script:result.where({$_.Property -ne 'Hashtable'}).Compliance | Should -Not -Contain $false + } + } + + Context 'When hashtable has an additional value' { + BeforeAll { + $desiredValues = [PSObject] @{ + String = 'a string' + Bool = $true + Int = 99 + Array = 'a', 'b', 'c' + Hashtable = @{ + k1 = 'Test' + k2 = 123 + k3 = 'v1', 'v2', 'v3', 99, 100 + } + } + } + + It 'Should not throw exception' { + { $script:result = Compare-DscParameterState ` + -CurrentValues $currentValues ` + -DesiredValues $desiredValues ` + -Verbose:$verbose } | Should -Not -Throw + } + + It 'Should return $false for Hashtable compliance' { + $script:result.where({$_.Property -eq 'Hashtable'}).Compliance | Should -BeFalse + } + + It 'Should return all compliance (without Hashtable property) in $true' { + $script:result.where({$_.Property -ne 'Hashtable'}).Compliance | Should -Not -Contain $false + } + } + + Context 'When hashtable has a different value' { + BeforeAll { + $desiredValues = [PSObject] @{ + String = 'a string' + Bool = $true + Int = 99 + Array = 'a', 'b', 'c' + Hashtable = @{ + k1 = 'Test' + k2 = 123 + k3 = 'xx', 'v2', 'v3', 99 + } + } + } + + It 'Should not throw exception' { + { $script:result = Compare-DscParameterState ` + -CurrentValues $currentValues ` + -DesiredValues $desiredValues ` + -Verbose:$verbose } | Should -Not -Throw + } + + It 'Should return $false for Hashtable compliance' { + $script:result.where({$_.Property -eq 'Hashtable'}).Compliance | Should -BeFalse + } + + It 'Should return all compliance (without Hashtable property) in $true' { + $script:result.where({$_.Property -ne 'Hashtable'}).Compliance | Should -Not -Contain $false + } + } + + Context 'When an array in hashtable has different order' { + BeforeAll { + $desiredValues = [PSObject] @{ + String = 'a string' + Bool = $true + Int = 99 + Array = 'a', 'b', 'c' + Hashtable = @{ + k1 = 'Test' + k2 = 123 + k3 = 'v3', 'v2', 'v1', 99 + } + } + } + + It 'Should not throw exception' { + { $script:result = Compare-DscParameterState ` + -CurrentValues $currentValues ` + -DesiredValues $desiredValues ` + -Verbose:$verbose } | Should -Not -Throw + } + + It 'Should return $false for Hashtable compliance' { + $script:result.where({$_.Property -eq 'Hashtable'}).Compliance | Should -BeFalse + } + + It 'Should return all compliance (without Hashtable property) in $true' { + $script:result.where({$_.Property -ne 'Hashtable'}).Compliance | Should -Not -Contain $false + } + } + + Context 'When an array in hashtable has different order but SortArrayValues is used' { + BeforeAll { + $desiredValues = [PSObject] @{ + String = 'a string' + Bool = $true + Int = 99 + Array = 'a', 'b', 'c' + Hashtable = @{ + k1 = 'Test' + k2 = 123 + k3 = 'v3', 'v2', 'v1', 99 + } + } + } + + It 'Should not throw exception' { + { $script:result = Compare-DscParameterState ` + -CurrentValues $currentValues ` + -DesiredValues $desiredValues ` + -SortArrayValues ` + -Verbose:$verbose } | Should -Not -Throw + } + + It 'Should return $true for Hashtable compliance' { + $script:result.where({$_.Property -eq 'Hashtable'}).Compliance | Should -BeTrue + } + + It 'Should return all compliance (without Hashtable property) in $true' { + $script:result.where({$_.Property -ne 'Hashtable'}).Compliance | Should -Not -Contain $false + } + } + + Context 'When hashtable has a value with a different type' { + BeforeAll { + $desiredValues = [PSObject] @{ + String = 'a string' + Bool = $true + Int = 99 + Array = 'a', 'b', 'c' + Hashtable = @{ + k1 = 'Test' + k2 = 123 + k3 = 'v1', 'v2', 'v3', '99' + } + } + } + + It 'Should not throw exception' { + { $script:result = Compare-DscParameterState ` + -CurrentValues $currentValues ` + -DesiredValues $desiredValues ` + -Verbose:$verbose } | Should -Not -Throw + } + + It 'Should return $false for Hashtable compliance' { + $script:result.where({$_.Property -eq 'Hashtable'}).Compliance | Should -BeFalse + } + + It 'Should return all compliance (without Hashtable property) in $true' { + $script:result.where({$_.Property -ne 'Hashtable'}).Compliance | Should -Not -Contain $false + } + } + + Context 'When hashtable has a value with a different type but TurnOffTypeChecking is used' { + BeforeAll { + $desiredValues = [PSObject] @{ + String = 'a string' + Bool = $true + Int = 99 + Array = 'a', 'b', 'c' + Hashtable = @{ + k1 = 'Test' + k2 = 123 + k3 = 'v1', 'v2', 'v3', 99 + } + } + } + + It 'Should not throw exception' { + { $script:result = Compare-DscParameterState ` + -CurrentValues $currentValues ` + -DesiredValues $desiredValues ` + -TurnOffTypeChecking ` + -Verbose:$verbose } | Should -Not -Throw + } + + It 'Should return $true for Hashtable compliance' { + $script:result.where({$_.Property -eq 'Hashtable'}).Compliance | Should -BeTrue + } + + It 'Should return all compliance (without Hashtable property) in $true' { + $script:result.where({$_.Property -ne 'Hashtable'}).Compliance | Should -Not -Contain $false + } + } + } + + Context 'When reverse checking' { + BeforeAll { + $currentValues = @{ + String = 'a string' + Bool = $true + Int = 99 + Array = 'a', 'b', 'c', 1 + Hashtable = @{ + k1 = 'Test' + k2 = 123 + k3 = 'v1', 'v2', 'v3' + } + } + } + + Context 'When even if missing property in the desired state' { + BeforeAll { + $desiredValues = [PSObject] @{ + Array = 'a', 'b', 'c', 1 + Hashtable = @{ + k1 = 'Test' + k2 = 123 + k3 = 'v1', 'v2', 'v3' + } + } + } + + It 'Should not throw exception' { + { $script:result = Compare-DscParameterState ` + -CurrentValues $currentValues ` + -DesiredValues $desiredValues ` + -Verbose:$verbose } | Should -Not -Throw + } + + It 'Should return $false for all compliance' { + $script:result.Compliance | Should -Not -Contain $false + } + } + + Context 'When missing property in the desired state' { + BeforeAll { + $currentValues = @{ + String = 'a string' + Bool = $true + } + + $desiredValues = [PSObject] @{ + String = 'a string' + } + } + + It 'Should not throw exception' { + { $script:result = Compare-DscParameterState ` + -CurrentValues $currentValues ` + -DesiredValues $desiredValues ` + -ReverseCheck ` + -Verbose:$verbose } | Should -Not -Throw + } + + It 'Should return $false for missed property (Bool)' { + $script:result.where({$_.Property -eq 'Bool'}).Compliance | Should -BeFalse + } + + It 'Should return all compliance (without Bool property) in $true' { + $script:result.where({$_.Property -ne 'Bool'}).Compliance | Should -Not -Contain $false + } + } + } + + Context 'When testing parameter types' { + Context 'When desired value is of the wrong type' { + BeforeAll { + $currentValues = @{ + String = 'a string' + } + + $desiredValues = 1, 2, 3 + } + + It 'Should throw exception' { + { $script:result = Compare-DscParameterState ` + -CurrentValues $currentValues ` + -DesiredValues $desiredValues ` + -Verbose:$verbose } | Should -Throw + } + } + + Context 'When current value is of the wrong type' { + BeforeAll { + $currentValues = 1, 2, 3 + + $desiredValues = @{ + String = 'a string' + } + } + + It 'Should throw exception' { + { $script:result = Compare-DscParameterState ` + -CurrentValues $currentValues ` + -DesiredValues $desiredValues ` + -Verbose:$verbose } | Should -Throw + } + } + } + + # macOS and Linux does not support CimInstance. + if ($isWindows -or $PSEdition -eq 'Desktop') + { + Context 'When testing CimInstances / hashtables' { + BeforeAll { + $currentValues = @{ + String = 'a string' + Bool = $true + Int = 99 + Array = 'a', 'b', 'c' + Hashtable = @{ + k1 = 'Test' + k2 = 123 + k3 = 'v1', 'v2', 'v3', 99 + } + + CimInstances = [Microsoft.Management.Infrastructure.CimInstance[]] ( + ConvertTo-CimInstance -Hashtable @{ + String = 'a string' + Bool = $true + Int = 99 + Array = 'a, b, c' + } + ) + } + } + + Context 'When everything matches' { + BeforeAll { + $desiredValues = [PSObject]@{ + String = 'a string' + Bool = $true + Int = 99 + Array = 'a', 'b', 'c' + Hashtable = @{ + k1 = 'Test' + k2 = 123 + k3 = 'v1', 'v2', 'v3', 99 + } + + CimInstances = [Microsoft.Management.Infrastructure.CimInstance[]] ( + ConvertTo-CimInstance -Hashtable @{ + String = 'a string' + Bool = $true + Int = 99 + Array = 'a, b, c' + } + ) + } + } + + It 'Should not throw exception' { + { $script:result = Compare-DscParameterState ` + -CurrentValues $currentValues ` + -DesiredValues $desiredValues ` + -Verbose:$verbose } | Should -Not -Throw + } + + It 'Should return all compliance in $true' { + $script:result.Compliance | Should -Not -Contain $false + } + } + + Context 'When CimInstances missing a value in the desired state (not recognized)' { + BeforeAll { + $desiredValues = [PSObject]@{ + String = 'a string' + Bool = $true + Int = 99 + Array = 'a', 'b', 'c' + Hashtable = @{ + k1 = 'Test' + k2 = 123 + k3 = 'v1', 'v2', 'v3', 99 + } + CimInstances = @{ + String = 'a string' + Bool = $true + Array = 'a, b, c' + } + } + } + + It 'Should not throw exception' { + { $script:result = Compare-DscParameterState ` + -CurrentValues $currentValues ` + -DesiredValues $desiredValues ` + -Verbose:$verbose } | Should -Not -Throw + } + + It 'Should return all compliance in $true' { + $script:result.Compliance | Should -Not -Contain $false + } + } + + Context 'When CimInstances missing a value in the desired state (recognized using ReverseCheck)' { + BeforeAll { + $desiredValues = [PSObject] @{ + String = 'a string' + Bool = $true + Int = 99 + Array = 'a', 'b', 'c' + Hashtable = @{ + k1 = 'Test' + k2 = 123 + k3 = 'v1', 'v2', 'v3', 99 + } + CimInstances = @{ + String = 'a string' + Bool = $true + Array = 'a, b, c' + } + } + } + + It 'Should not throw exception' { + { $script:result = Compare-DscParameterState ` + -CurrentValues $currentValues ` + -DesiredValues $desiredValues ` + -ReverseCheck ` + -Verbose:$verbose } | Should -Not -Throw + } + + It 'Should return $false for CimInstances compliance' { + $script:result.where({$_.Property -eq 'CimInstances'}).Compliance | Should -BeFalse + } + + It 'Should return all compliance (without CimInstances property) in $true' { + $script:result.where({$_.Property -ne 'CimInstances'}).Compliance | Should -Not -Contain $false + } + } + + Context 'When CimInstances have an additional value' { + BeforeAll { + $desiredValues = [PSObject] @{ + String = 'a string' + Bool = $true + Int = 99 + Array = 'a', 'b', 'c' + Hashtable = @{ + k1 = 'Test' + k2 = 123 + k3 = 'v1', 'v2', 'v3', 99 + } + CimInstances = @{ + String = 'a string' + Bool = $true + Int = 99 + Array = 'a, b, c' + Test = 'Some string' + } + } + } + + It 'Should not throw exception' { + { $script:result = Compare-DscParameterState ` + -CurrentValues $currentValues ` + -DesiredValues $desiredValues ` + -Verbose:$verbose } | Should -Not -Throw + } + + It 'Should return $false for CimInstances compliance' { + $script:result.where({$_.Property -eq 'CimInstances'}).Compliance | Should -BeFalse + } + + It 'Should return all compliance (without CimInstances property) in $true' { + $script:result.where({$_.Property -ne 'CimInstances'}).Compliance | Should -Not -Contain $false + } + } + + Context 'When CimInstances have a different value' { + BeforeAll { + $desiredValues = [PSObject] @{ + String = 'a string' + Bool = $true + Int = 99 + Array = 'a', 'b', 'c' + Hashtable = @{ + k1 = 'Test' + k2 = 123 + k3 = 'v1', 'v2', 'v3', 99 + } + CimInstances = @{ + String = 'some other string' + Bool = $true + Int = 99 + Array = 'a, b, c' + } + } + } + + It 'Should not throw exception' { + { $script:result = Compare-DscParameterState ` + -CurrentValues $currentValues ` + -DesiredValues $desiredValues ` + -Verbose:$verbose } | Should -Not -Throw + } + + It 'Should return $false for CimInstances compliance' { + $script:result.where({$_.Property -eq 'CimInstances'}).Compliance | Should -BeFalse + } + + It 'Should return all compliance (without CimInstances property) in $true' { + $script:result.where({$_.Property -ne 'CimInstances'}).Compliance | Should -Not -Contain $false + } + } + + Context 'When CimInstances have a value with a different type' { + BeforeAll { + $desiredValues = [PSObject] @{ + String = 'a string' + Bool = $true + Int = 99 + Array = 'a', 'b', 'c' + Hashtable = @{ + k1 = 'Test' + k2 = 123 + k3 = 'v1', 'v2', 'v3', 99 + } + CimInstances = @{ + String = 'a string' + Bool = $true + Int = '99' + Array = 'a, b, c' + } + } + } + + It 'Should not throw exception' { + { $script:result = Compare-DscParameterState ` + -CurrentValues $currentValues ` + -DesiredValues $desiredValues ` + -Verbose:$verbose } | Should -Not -Throw + } + + It 'Should return $false for CimInstances compliance' { + $script:result.where({$_.Property -eq 'CimInstances'}).Compliance | Should -BeFalse + } + + It 'Should return all compliance (without CimInstances property) in $true' { + $script:result.where({$_.Property -ne 'CimInstances'}).Compliance | Should -Not -Contain $false + } + } + + Context 'When CimInstances have a value with a different type but TurnOffTypeChecking is used' { + BeforeAll { + $desiredValues = [PSObject] @{ + String = 'a string' + Bool = $true + Int = 99 + Array = 'a', 'b', 'c' + Hashtable = @{ + k1 = 'Test' + k2 = 123 + k3 = 'v1', 'v2', 'v3', 99 + } + CimInstances = @{ + String = 'a string' + Bool = $true + Int = '99' + Array = 'a, b, c' + } + } + } + + It 'Should not throw exception' { + { $script:result = Compare-DscParameterState ` + -CurrentValues $currentValues ` + -DesiredValues $desiredValues ` + -TurnOffTypeChecking ` + -Verbose:$verbose } | Should -Not -Throw + } + + It 'Should return $true for CimInstances compliance' { + $script:result.where({$_.Property -eq 'CimInstances'}).Compliance | Should -BeTrue + } + + It 'Should return all compliance (without CimInstances property) in $true' { + $script:result.where({$_.Property -ne 'CimInstances'}).Compliance | Should -Not -Contain $false + } + } + } + } + } +}