Modules/ActiveDirectoryCSDsc.Common/ActiveDirectoryCSDsc.Common.psm1
<#
.SYNOPSIS Removes common parameters from a hashtable .DESCRIPTION This function serves the purpose of removing common parameters and option common parameters from a parameter hashtable .PARAMETER Hashtable The parameter hashtable that should be pruned #> function Remove-CommonParameter { [OutputType([System.Collections.Hashtable])] [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [System.Collections.Hashtable] $Hashtable ) $inputClone = $Hashtable.Clone() $commonParameters = [System.Management.Automation.PSCmdlet]::CommonParameters $commonParameters += [System.Management.Automation.PSCmdlet]::OptionalCommonParameters $Hashtable.Keys | Where-Object -FilterScript { $_ -in $commonParameters } | ForEach-Object -Process { $inputClone.Remove($_) } return $inputClone } <# .SYNOPSIS Tests the status of DSC resource parameters. .DESCRIPTION This function tests the parameter status of DSC resource parameters against the current values present on the system. .PARAMETER CurrentValues A hashtable with the current values on the system, obtained by e.g. Get-TargetResource. .PARAMETER DesiredValues The hashtable of desired values. .PARAMETER ValuesToCheck The values to check if not all values should be 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. #> function Test-DscParameterState { [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [System.Object] $CurrentValues, [Parameter(Mandatory = $true)] [System.Object] $DesiredValues, [Parameter()] [System.String[]] $ValuesToCheck, [Parameter()] [switch] $TurnOffTypeChecking, [Parameter()] [switch] $ReverseCheck, [Parameter()] [switch] $SortArrayValues ) $returnValue = $true 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 } $types = 'System.Management.Automation.PSBoundParametersDictionary', 'System.Collections.Hashtable', 'Microsoft.Management.Infrastructure.CimInstance' 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' } if ($DesiredValues -is [Microsoft.Management.Infrastructure.CimInstance] -and -not $ValuesToCheck) { New-InvalidArgumentException ` -Message $script:localizedData.InvalidValuesToCheckError ` -ArgumentName 'ValuesToCheck' } $desiredValuesClean = Remove-CommonParameter -Hashtable $DesiredValues if (-not $ValuesToCheck) { $keyList = $desiredValuesClean.Keys } else { $keyList = $ValuesToCheck } foreach ($key in $keyList) { $desiredValue = $desiredValuesClean.$key $currentValue = $CurrentValues.$key 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 } if ($null -ne $desiredValue) { $desiredType = $desiredValue.GetType() } else { $desiredType = @{ Name = 'Unknown' } } if ($null -ne $currentValue) { $currentType = $currentValue.GetType() } else { $currentType = @{ Name = '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 } else { Write-Verbose -Message ($script:localizedData.NoMatchPsCredentialUsernameMessage -f $currentValue.UserName, $desiredValue.UserName) $returnValue = $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 } else { Write-Verbose -Message ($script:localizedData.NoMatchPsCredentialUsernameMessage -f $currentValue, $desiredValue.UserName) $returnValue = $false } } 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) $returnValue = $false continue } } if ($currentValue -eq $desiredValue -and -not $desiredType.IsArray) { Write-Verbose -Message ($script:localizedData.MatchValueMessage -f $desiredType.FullName, $key, $currentValue, $desiredValue) continue } if ($desiredValuesClean.GetType().Name -in 'HashTable', 'PSBoundParametersDictionary') { $checkDesiredValue = $desiredValuesClean.ContainsKey($key) } else { $checkDesiredValue = Test-DscObjectHasProperty -Object $desiredValuesClean -PropertyName $key } if (-not $checkDesiredValue) { Write-Verbose -Message ($script:localizedData.MatchValueMessage -f $desiredType.FullName, $key, $currentValue, $desiredValue) continue } if ($desiredType.IsArray) { Write-Verbose -Message ($script:localizedData.TestDscParameterCompareMessage -f $key, $desiredType.FullName) if (-not $currentValue -and -not $desiredValue) { Write-Verbose -Message ($script:localizedData.MatchValueMessage -f $desiredType.FullName, $key, 'empty array', 'empty array') continue } elseif (-not $currentValue) { Write-Verbose -Message ($script:localizedData.NoMatchValueMessage -f $desiredType.FullName, $key, $currentValue, $desiredValue) $returnValue = $false continue } elseif ($currentValue.Count -ne $desiredValue.Count) { Write-Verbose -Message ($script:localizedData.NoMatchValueDifferentCountMessage -f $desiredType.FullName, $key, $currentValue.Count, $desiredValue.Count) $returnValue = $false continue } else { $desiredArrayValues = $desiredValue $currentArrayValues = $currentValue if ($SortArrayValues) { $desiredArrayValues = $desiredArrayValues | Sort-Object $currentArrayValues = $currentArrayValues | Sort-Object } for ($i = 0; $i -lt $desiredArrayValues.Count; $i++) { if ($null -ne $desiredArrayValues[$i]) { $desiredType = $desiredArrayValues[$i].GetType() } else { $desiredType = @{ Name = 'Unknown' } } if ($null -ne $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) $returnValue = $false continue } } if ($desiredArrayValues[$i] -ne $currentArrayValues[$i]) { Write-Verbose -Message ($script:localizedData.NoMatchElementValueMismatchMessage -f $i, $desiredType.FullName, $key, $currentArrayValues[$i], $desiredArrayValues[$i]) $returnValue = $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 $null = $param.Remove('ValuesToCheck') if ($returnValue) { $returnValue = Test-DscParameterState @param } else { Test-DscParameterState @param | Out-Null } continue } else { if ($desiredValue -ne $currentValue) { Write-Verbose -Message ($script:localizedData.NoMatchValueMessage -f $desiredType.FullName, $key, $currentValue, $desiredValue) $returnValue = $false } } } if ($ReverseCheck) { Write-Verbose -Message $script:localizedData.StartingReverseCheck $reverseCheckParameters = $PSBoundParameters $reverseCheckParameters.CurrentValues = $DesiredValues $reverseCheckParameters.DesiredValues = $CurrentValues $null = $reverseCheckParameters.Remove('ReverseCheck') if ($returnValue) { $returnValue = Test-DscParameterState @reverseCheckParameters } else { $null = Test-DscParameterState @reverseCheckParameters } } Write-Verbose -Message ($script:localizedData.TestDscParameterResultMessage -f $returnValue) return $returnValue } <# .SYNOPSIS Tests of an object has a property .PARAMETER Object The object to test .PARAMETER PropertyName The property name #> function Test-DscObjectHasProperty { [CmdletBinding()] [OutputType([System.Boolean])] param ( [Parameter(Mandatory = $true)] [System.Object] $Object, [Parameter(Mandatory = $true)] [System.String] $PropertyName ) if ($Object.PSObject.Properties.Name -contains $PropertyName) { return [System.Boolean] $Object.$PropertyName } return $false } <# .SYNOPSIS Converts a hashtable into a CimInstance array. .DESCRIPTION This function is used to convert a hashtable into MSFT_KeyValuePair objects. These are stored as an CimInstance array. DSC cannot handle hashtables but CimInstances arrays storing MSFT_KeyValuePair. .PARAMETER Hashtable A hashtable with the values to convert. .OUTPUTS An object array with CimInstance objects. #> function ConvertTo-CimInstance { [CmdletBinding()] [OutputType([System.Object[]])] param ( [Parameter(Mandatory = $true, ValueFromPipeline = $true)] [System.Collections.Hashtable] $Hashtable ) process { foreach ($item in $Hashtable.GetEnumerator()) { New-CimInstance -ClassName MSFT_KeyValuePair -Namespace root/microsoft/Windows/DesiredStateConfiguration -Property @{ Key = $item.Key Value = if ($item.Value -is [array]) { $item.Value -join ',' } else { $item.Value } } -ClientOnly } } } <# .SYNOPSIS Converts CimInstances into a hashtable. .DESCRIPTION This function is used to convert a CimInstance array containing MSFT_KeyValuePair objects into a hashtable. .PARAMETER CimInstance An array of CimInstances or a single CimInstance object to convert. .OUTPUTS Hashtable #> function ConvertTo-HashTable { [CmdletBinding()] [OutputType([System.Collections.Hashtable])] param ( [Parameter(Mandatory = $true, ValueFromPipeline = $true)] [AllowEmptyCollection()] [Microsoft.Management.Infrastructure.CimInstance[]] $CimInstance ) begin { $result = @{ } } process { foreach ($ci in $CimInstance) { $result.Add($ci.Key, $ci.Value) } } end { $result } } <# .SYNOPSIS Retrieves the localized string data based on the machine's culture. Falls back to en-US strings if the machine's culture is not supported. .PARAMETER ResourceName The name of the resource as it appears before '.strings.psd1' of the localized string file. For example: For WindowsOptionalFeature: MSFT_WindowsOptionalFeature For Service: MSFT_ServiceResource For Registry: MSFT_RegistryResource For Helper: SqlServerDscHelper .PARAMETER ScriptRoot Optional. The root path where to expect to find the culture folder. This is only needed for localization in helper modules. This should not normally be used for resources. .NOTES To be able to use localization in the helper function, this function must be first in the file, before Get-LocalizedData is used by itself to load localized data for this helper module (see directly after this function). #> function Get-LocalizedData { [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [System.String] $ResourceName, [Parameter()] [ValidateNotNullOrEmpty()] [System.String] $ScriptRoot ) if (-not $ScriptRoot) { $dscResourcesFolder = Join-Path -Path (Split-Path -Path (Split-Path -Path $PSScriptRoot -Parent) -Parent) -ChildPath 'DSCResources' $resourceDirectory = Join-Path -Path $dscResourcesFolder -ChildPath $ResourceName } else { $resourceDirectory = $ScriptRoot } $localizedStringFileLocation = Join-Path -Path $resourceDirectory -ChildPath $PSUICulture if (-not (Test-Path -Path $localizedStringFileLocation)) { # Fallback to en-US $localizedStringFileLocation = Join-Path -Path $resourceDirectory -ChildPath 'en-US' } Import-LocalizedData ` -BindingVariable 'localizedData' ` -FileName "$ResourceName.strings.psd1" ` -BaseDirectory $localizedStringFileLocation return $localizedData } <# .SYNOPSIS Creates and throws an invalid argument exception. .PARAMETER Message The message explaining why this error is being thrown. .PARAMETER ArgumentName The name of the invalid argument that is causing this error to be thrown. #> function New-InvalidArgumentException { [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [System.String] $Message, [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [System.String] $ArgumentName ) $argumentException = New-Object -TypeName 'ArgumentException' ` -ArgumentList @($Message, $ArgumentName) $newObjectParameters = @{ TypeName = 'System.Management.Automation.ErrorRecord' ArgumentList = @($argumentException, $ArgumentName, 'InvalidArgument', $null) } $errorRecord = New-Object @newObjectParameters throw $errorRecord } <# .SYNOPSIS Creates and throws an invalid operation exception. .PARAMETER Message The message explaining why this error is being thrown. .PARAMETER ErrorRecord The error record containing the exception that is causing this terminating error. #> function New-InvalidOperationException { [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [System.String] $Message, [Parameter()] [ValidateNotNull()] [System.Management.Automation.ErrorRecord] $ErrorRecord ) if ($null -eq $ErrorRecord) { $invalidOperationException = New-Object -TypeName 'InvalidOperationException' ` -ArgumentList @($Message) } else { $invalidOperationException = New-Object -TypeName 'InvalidOperationException' ` -ArgumentList @($Message, $ErrorRecord.Exception) } $newObjectParameters = @{ TypeName = 'System.Management.Automation.ErrorRecord' ArgumentList = @( $invalidOperationException.ToString(), 'MachineStateIncorrect', 'InvalidOperation', $null ) } $errorRecordToThrow = New-Object @newObjectParameters throw $errorRecordToThrow } <# .SYNOPSIS Creates and throws an object not found exception. .PARAMETER Message The message explaining why this error is being thrown. .PARAMETER ErrorRecord The error record containing the exception that is causing this terminating error. #> function New-ObjectNotFoundException { [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [System.String] $Message, [Parameter()] [ValidateNotNull()] [System.Management.Automation.ErrorRecord] $ErrorRecord ) if ($null -eq $ErrorRecord) { $exception = New-Object -TypeName 'System.Exception' ` -ArgumentList @($Message) } else { $exception = New-Object -TypeName 'System.Exception' ` -ArgumentList @($Message, $ErrorRecord.Exception) } $newObjectParameters = @{ TypeName = 'System.Management.Automation.ErrorRecord' ArgumentList = @( $exception.ToString(), 'MachineStateIncorrect', 'ObjectNotFound', $null ) } $errorRecordToThrow = New-Object @newObjectParameters throw $errorRecordToThrow } <# .SYNOPSIS Creates and throws an invalid result exception. .PARAMETER Message The message explaining why this error is being thrown. .PARAMETER ErrorRecord The error record containing the exception that is causing this terminating error. #> function New-InvalidResultException { [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [System.String] $Message, [Parameter()] [ValidateNotNull()] [System.Management.Automation.ErrorRecord] $ErrorRecord ) if ($null -eq $ErrorRecord) { $exception = New-Object -TypeName 'System.Exception' ` -ArgumentList @($Message) } else { $exception = New-Object -TypeName 'System.Exception' ` -ArgumentList @($Message, $ErrorRecord.Exception) } $newObjectParameters = @{ TypeName = 'System.Management.Automation.ErrorRecord' ArgumentList = @( $exception.ToString(), 'MachineStateIncorrect', 'InvalidResult', $null ) } $errorRecordToThrow = New-Object @newObjectParameters throw $errorRecordToThrow } <# .SYNOPSIS Creates and throws a not implemented exception. .PARAMETER Message The message explaining why this error is being thrown. .PARAMETER ErrorRecord The error record containing the exception that is causing this terminating error. #> function New-NotImplementedException { [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [System.String] $Message, [Parameter()] [ValidateNotNull()] [System.Management.Automation.ErrorRecord] $ErrorRecord ) if ($null -eq $ErrorRecord) { $invalidOperationException = New-Object -TypeName 'NotImplementedException' ` -ArgumentList @($Message) } else { $invalidOperationException = New-Object -TypeName 'NotImplementedException' ` -ArgumentList @($Message, $ErrorRecord.Exception) } $newObjectParameters = @{ TypeName = 'System.Management.Automation.ErrorRecord' ArgumentList = @( $invalidOperationException.ToString(), 'MachineStateIncorrect', 'NotImplemented', $null ) } $errorRecordToThrow = New-Object @newObjectParameters throw $errorRecordToThrow } <# .SYNOPSIS Restarts a System Service .PARAMETER Name Name of the service to be restarted. #> function Restart-ServiceIfExists { [CmdletBinding()] param ( [Parameter()] [System.String] $Name ) Write-Verbose -Message ($script:localizedData.GetServiceInformation -f $Name) -Verbose $servicesService = Get-Service @PSBoundParameters -ErrorAction Continue if ($servicesService) { Write-Verbose -Message ($script:localizedData.RestartService -f $Name) -Verbose $servicesService | Restart-Service -Force -ErrorAction Stop -Verbose } else { Write-Verbose -Message ($script:localizedData.UnknownService -f $Name) -Verbose } } $script:localizedData = Get-LocalizedData -ResourceName 'ActiveDirectoryCSDsc.Common' -ScriptRoot $PSScriptRoot Export-ModuleMember -Function @( 'Test-DscParameterState', 'New-InvalidArgumentException', 'New-InvalidOperationException', 'New-ObjectNotFoundException', 'New-InvalidResultException', 'New-NotImplementedException', 'Get-LocalizedData', 'Restart-ServiceIfExists' ) |