CloudConfigurationManager.psm1
function Get-CCMPropertiesToSend { [CmdletBinding()] [OutputType([System.Collections.Hashtable])] param( [Parameter(Mandatory = $true)] [System.Collections.Hashtable] $Instance, [Parameter()] [System.Collections.Hashtable] $Parameters ) # Clone the instance to avoid modifying the original object. $currentInstance = ([System.Collections.Hashtable]$instance).Clone() $ResourceName = $currentInstance.ResourceName $currentInstance.Remove('ResourceName') | Out-Null $ResourceInstanceName = $currentInstance.ResourceInstanceName $currentInstance.Remove('ResourceInstanceName') | Out-Null Write-Verbose -Message "[Get-CCMPropertiesToSend]: Calling Get-CCMPropertiesToSend for {$ResourceInstanceName}" $propertiesToSend = @{} $dscResourceInfo = foreach ($propertyName in $currentInstance.Keys) { # Retrieve the CIM Instance Property. $CimProperty = $currentInstance.$propertyName # If the current propertry is a CIMInstance if ($CimProperty.Keys -ne $null -and $CimProperty.Keys.Contains("CIMInstance")) { $cimResult = Expand-CCMCimProperty -CimInstanceValue $currentInstance.$propertyName if ($null -eq $cimResult) { throw "Failed to expand the CIMInstance property [$propertyName] for the resource [$ResourceName]" } else { $propertiesToSend.Add($propertyName, $cimResult) } } else { # Property is not a CIMInstance, therefore add it to the list. $propertyValue = $currentInstance.$propertyName # If the property contains a variable, check in the received parameters # to see if a replacement alternative was specified. if (-not [System.String]::IsNullOrEmpty($propertyValue) ` -and $propertyValue.GetType().Name -eq 'String' ` -and $propertyValue.Contains('$')) { foreach ($parameterSpecified in $Parameters.Keys) { if ($propertyValue.Contains("`$$parameterSpecified")) { if ($Parameters.$parameterSpecified.GetType().Name -eq 'String') { $propertyValue = $propertyValue.Replace("`$$parameterSpecified", $Parameters.$parameterSpecified) } else { $propertyValue = $Parameters.$parameterSpecified } break } } } elseif ($null -ne $propertyValue -and $propertyValue.GetType().Name -eq 'Object[]') { $newValue = @() foreach ($entry in $propertyValue) { $foundParameter = $false foreach ($parameterSpecified in $Parameters.Keys) { if ($null -ne $entry -and $entry.Contains("`$$parameterSpecified")) { $foundParameter = $true if ($Parameters.$parameterSpecified.GetType().Name -eq 'String') { $newValue += $propertyValue.Replace("`$$parameterSpecified", $Parameters.$parameterSpecified) } else { $newValue += $Parameters.$parameterSpecified } break } } if (-not $foundParameter) { $newValue += $entry } } $propertyValue = $newValue } $propertiesToSend.Add($propertyName, $propertyValue) } } return $propertiesToSend } function Expand-CCMCimProperty { [CmdletBinding()] [OutputType([Microsoft.Management.Infrastructure.CimInstance[]])] param( [Parameter(Mandatory = $true)] [System.Object] $CimInstanceValue ) $cimInstanceProperties = @{} if ($CimInstanceValue -notin [System.Array]) { $CimInstanceValue = @($CimInstanceValue) } $cimPropertyNameBlacklist = @( 'CIMInstance', 'ResourceName') $cimResults = @() #Iterate over each object within the CimInstanceValueArray foreach ($cimInstance in $CimInstanceValue) { $cimInstanceProperties = @{} # this is the current CIM Instance foreach ($cimSubPropertyName in $cimInstance.Keys) { if ($cimSubPropertyName -notin $cimPropertyNameBlacklist) { $cimSubPropertyValue = $cimInstance.$cimSubPropertyName if ($cimSubPropertyValue -isnot [System.Array]) { $cimSubPropertyValue = @($cimSubPropertyValue) } foreach ($cimSubPropertyValueItem in $cimSubPropertyValue) { if ($cimSubPropertyValueItem -is [System.Collections.Specialized.OrderedDictionary]) { $cimSubPropertyValueItem = Expand-CCMCimProperty -CimInstanceValue $cimSubPropertyValueItem if ($cimSubPropertyValueItem.Length -eq 1) { $cimSubPropertyValueItem = [Microsoft.Management.Infrastructure.CimInstance]$cimSubPropertyValueItem[0] } } else { $cimInstanceProperties.Add($cimSubPropertyName, $cimSubPropertyValue[0]) | Out-Null } } } } $newCIMInstanceObject = Get-CCMSubCIMInstances -cimInstanceProperties $cimInstanceProperties $cimResults += New-CimInstance -ClassName "$($cimInstance.CIMInstance)" ` -Property $newCimInstanceObject ` -ClientOnly } return [Microsoft.Management.Infrastructure.CimInstance[]]$cimResults } function Get-CCMSubCIMInstances { [CmdletBinding()] [OutputType([System.Collections.Hashtable])] param( [parameter(Mandatory = $true)] [System.Collections.Hashtable] $cimInstanceProperties ) # Scan through the properties of the current CIMInstance to see if there are nested CIMInstances $newCimInstanceObject = @{} foreach ($topKey in $cimInstanceProperties.Keys) { if ($cimInstanceProperties.$topKey.GetType().ToString() -eq 'System.Collections.Hashtable' -or ` $cimInstanceProperties.$topKey.GetType().ToString() -eq 'System.Object[]') { $newSubCim = @{} foreach ($subKey in $cimInstanceProperties.$topKey.Keys) { if (($cimInstanceProperties.$topKey.$subKey.GetType().ToString() -eq 'System.Collections.Hashtable' -and ` $cimInstanceProperties.$topKey.$subKey.ContainsKey('CIMInstance')) -or ` $cimInstanceProperties.$topKey.$subKey.GetType().ToString() -eq 'System.Object[]') { $curSubCim = Get-CCMSubCIMInstances -cimInstanceProperties $cimInstanceProperties.$topkey $curSubCimInstance = New-CIMInstance -ClassName $cimInstanceProperties.$topKey.CIMInstance ` -Property $curSubCim -ClientOnly $newSubCim.Add($subkey, $curSubCimInstance.$subKey) } elseif ($subKey -ne 'CIMInstance') { $newSubCim.Add($subKey, $cimInstanceProperties.$topKey.$subKey) } } $currentCIMInstance = New-CIMInstance -ClassName $cimInstanceProperties.$topKey.CIMInstance ` -Property $newSubCim ` -ClientOnly $newCimInstanceObject.Add($topKey, $currentCIMInstance) } elseif ($topKey -ne 'CIMInstance') { $newCimInstanceObject.Add($topKey, $cimInstanceProperties.$topKey) } } return $newCimInstanceObject } function Get-CCMParsedResources { [CmdletBinding()] [OutputType([Array])] param( [Parameter()] [System.String] $Path, [Parameter()] [System.String] $Content, [Parameter()] [System.String] $SchemaDefinition ) # Convert the DSC Resources into PowerShell Objects $resourceInstances = $null if (-not [System.String]::IsNullOrEmpty($Path) -and [System.String]::IsNullOrEmpty($Content)) { $Content = Get-Content $Path -Raw } $resourceInstances = ConvertTo-DSCObject -Content $Content ` -Schema $SchemaDefinition ` # This will fix an issue with single resource configurations as in this case # the return will be a single object. Therefore further processing of the object will fail. if ($resourceInstances -isnot [System.Array]) { $resourceInstances = @($resourceInstances) } return $resourceInstances } function Test-CCMConfiguration { [CmdletBinding()] [OutputType([System.Boolean])] param( [Parameter(ParameterSetName = 'Path')] [System.String] $Path, [Parameter(ParameterSetName = 'Content')] [System.String] $Content, [Parameter(ParameterSetName = 'PreParsed')] [Array] $ResourceInstances, [Parameter()] [System.Collections.Hashtable] $Parameters, [Parameter()] [System.String] $SchemaDefinition, [Parameter()] [System.String] $ModuleName ) $TestResult = $true $Global:CCMAllDrifts = @() if ($null -eq $ResourceInstances) { # Parse the content of the content of the configuration file into an array of PowerShell object. $ResourceInstances = Get-CCMParsedResources -Path $Path ` -SchemaDefinition $SchemaDefinition ` -Content $Content } # Loop through all resource instances in the parsed configuration file. $i = 1 $Global:CCMCurrentImportedModule = $null foreach ($instance in $resourceInstances) { $ResourceName = $instance.ResourceName $ResourceInstanceName = $instance.ResourceInstanceName if ($Global:CCMCurrentImportedModule -ne $ResourceName) { $ModulePath = (Get-Module $ModuleName -ListAvailable).ModuleBase $ResourcePath = Join-Path -Path $ModulePath -ChildPath "/DSCResources/MSFT_$ResourceName/MSFT_$ResourceName.psm1" Import-Module $ResourcePath -Force $Global:CCMCurrentImportedModule = $ResourceName } Write-Verbose -Message "[Test-CCMConfiguration]: Resource [$i/$($resourceInstances.Length)]" # Retrieve the Hashtable representing the parameters to be sent to the Test method. $propertiesToSend = Get-CCMPropertiesToSend -Instance $instance ` -Parameters $Parameters # Remove DSC specific properties $propertiesToSend.Remove('DependsOn') | Out-Null $propertiesToSend.Remove('PSDSCRunAsCredential') | Out-Null # Evaluate the properties of the current resource. Write-Verbose -Message "[Test-CCMConfiguration]: Calling Test-TargetResource for {$ResourceInstanceName}" $currentResult = Test-TargetResource @propertiesToSend Write-Verbose -Message "[Test-CCMConfiguration]: Test-TargetResource for {$ResourceInstanceName} returned {$currentResult}" # If a drift was detected, augment its related info with the name of the # current instance and collect it in the CCMAllDrifts Global Variable. if (-not $currentResult) { $TestResult = $false # If the the current resource's module implements the CCM Drift pattern, collect # and enrich the information related to the drift from the CCMCurrentDriftInfo Global variable. # This variable needs to be populated from the resource's module. if ($null -ne $Global:CCMCurrentDriftInfo) { $currentDrift = $Global:CCMCurrentDriftInfo if (-not $currentDrift.ContainsKey('InstanceName')) { $currentDrift.Add('InstanceName', $ResourceInstanceName) } $Global:CCMAllDrifts += $currentDrift } } $i++ } Write-Verbose -Message "[Test-CCMConfiguration]: Returned {$TestResult}" return $TestResult } function Start-CCMConfiguration { [CmdletBinding()] param( [Parameter(ParameterSetName = 'Path')] [System.String] $Path, [Parameter(ParameterSetName = 'Content')] [System.String] $Content, [Parameter()] [System.Collections.Hashtable] $Parameters, [Parameter()] [System.String] $SchemaDefinition, [Parameter()] [System.String] $ModuleName ) # Parse the content of the content of the configuration file into an array of PowerShell object. $resourceInstances = Get-CCMParsedResources -Path $Path ` -SchemaDefinition $SchemaDefinition ` -Content $Content # Loop through all resource instances in the parsed configuration file. $i = 1 foreach ($instance in $resourceInstances) { $ResourceName = $instance.ResourceName $ResourceInstanceName = $instance.ResourceInstanceName Write-Verbose -Message "[Start-CCMConfiguration]: Resource [$i/$($resourceInstances.Length)]" # Retrieve the Hashtable representing the parameters to be sent to the Test method. $propertiesToSend = Get-CCMPropertiesToSend -Instance $instance ` -Parameters $Parameters # Evaluate the properties of the current resource. Write-Verbose -Message "[Start-CCMConfiguration]: Calling Test-TargetResource for {$ResourceInstanceName}" $currentResult = Test-TargetResource @propertiesToSend Write-Verbose -Message "[Start-CCMConfiguration]: Test-TargetResource for {$ResourceInstanceName} returned {$currentResult}" # If a drift was detected, apply the defined configuration for the resource instance by # calling into the Set-TargetResource method of the resource. if (-not $currentResult) { Write-Verbose -Message "[Start-CCMConfiguration]: Calling Set-TargetResource for {$ResourceInstanceName}" Set-TargetResource @propertiesToSend Write-Verbose -Message "[Start-CCMConfiguration]: Configuration applied successfully for {$ResourceInstanceName}" } $i++ } } |