DscBuildHelpers.psm1
#Region '.\Private\Assert-DscModuleResourceIsValid.ps1' -1 function Assert-DscModuleResourceIsValid { [CmdletBinding()] param ( [Parameter(ValueFromPipeline = $true)] [Microsoft.PowerShell.DesiredStateConfiguration.DscResourceInfo] $DscResources ) begin { Write-Verbose 'Testing for valid resources.' $failedDscResources = @() } process { foreach ($DscResource in $DscResources) { $failedDscResources += Get-FailedDscResource -DscResource $DscResource } } end { if ($failedDscResources.Count -gt 0) { Write-Verbose 'Found failed resources.' foreach ($resource in $failedDscResources) { Write-Warning "`t`tFailed Resource - $($resource.Name) ($($resource.Version))" } throw 'One or more resources is invalid.' } } } #EndRegion '.\Private\Assert-DscModuleResourceIsValid.ps1' 38 #Region '.\Private\Get-CimType.ps1' -1 function Get-CimType { <# .SYNOPSIS Retrieves the CIM type for a specified DSC resource property. .DESCRIPTION The Get-CimType function retrieves the CIM (Common Information Model) type for a specified property of a DSC (Desired State Configuration) resource. If the property is not a CIM type, it returns null and writes a verbose message. .PARAMETER DscResourceName The name of the DSC resource. .PARAMETER PropertyName The name of the property for which to retrieve the CIM type. .EXAMPLE $cimType = Get-CimType -DscResourceName 'MyDscResource' -PropertyName 'MyProperty' This example retrieves the CIM type for the 'MyProperty' property of the 'MyDscResource' DSC resource. .OUTPUTS System.Object The CIM type of the specified property, or null if the property is not a CIM type. .NOTES This function relies on a global variable named $allDscResourcePropertiesTable to retrieve the CIM type. Ensure that this variable is properly initialized and populated before calling this function. #> [CmdletBinding()] [OutputType([object])] param ( [Parameter(Mandatory = $true)] [string] $DscResourceName, [Parameter(Mandatory = $true)] [string] $PropertyName ) $cimType = $allDscResourcePropertiesTable."$ResourceName-$PropertyName" if ($null -eq $cimType) { Write-Verbose "The CIM Type for DSC resource '$DscResourceName' with the name '$PropertyName'. It is not a CIM type." return } return $cimType } #EndRegion '.\Private\Get-CimType.ps1' 52 #Region '.\Private\Get-DscCimInstanceReference.ps1' -1 function Get-DscCimInstanceReference { <# .SYNOPSIS Retrieves a scriptblock for a CIM instance reference of a DSC resource property. .DESCRIPTION The Get-DscCimInstanceReference function retrieves a scriptblock for a CIM (Common Information Model) instance reference of a specified property of a Desired State Configuration (DSC) resource. It uses the metadata information initialized by the Initialize-DscResourceMetaInfo function to find the type constraint of the property and generates the corresponding scriptblock. .PARAMETER ResourceName The name of the DSC resource. .PARAMETER ParameterName The name of the parameter for which to retrieve the CIM instance reference. .PARAMETER Data The data to be used for the CIM instance reference. .EXAMPLE $data = @{ Property1 = 'Value1' Property2 = 'Value2' } $scriptblock = Get-DscCimInstanceReference -ResourceName 'MyResource' -ParameterName 'MyParameter' -Data $data $scriptblock.Invoke($data) This example retrieves a scriptblock for the 'MyParameter' parameter of the 'MyResource' DSC resource and invokes it with the specified data. .NOTES This function relies on the metadata information initialized by the Initialize-DscResourceMetaInfo function. Ensure that Initialize-DscResourceMetaInfo is called before using this function. .LINK Initialize-DscResourceMetaInfo Get-DscSplattedResource #> [CmdletBinding()] [OutputType([ScriptBlock])] [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingWriteHost', '', Justification = 'For debugging purposes')] param ( [Parameter(Mandatory = $true)] [string] $ResourceName, [Parameter(Mandatory = $true)] [string] $ParameterName, [Parameter()] [object] $Data ) if ($Script:allDscResourcePropertiesTable) { if ($allDscResourcePropertiesTable.ContainsKey("$($ResourceName)-$($ParameterName)")) { $property = $allDscResourcePropertiesTable."$($ResourceName)-$($ParameterName)" $typeConstraint = $property.TypeConstraint -replace '\[\]', '' Get-DscSplattedResource -ResourceName $typeConstraint -Properties $Data -NoInvoke } } else { Write-Host "No DSC Resource Properties metadata was found, cannot translate CimInstance parameters. Call 'Initialize-DscResourceMetaInfo' first is this is needed." } } #EndRegion '.\Private\Get-DscCimInstanceReference.ps1' 69 #Region '.\Private\Get-DscResourceProperty.ps1' -1 function Get-DscResourceProperty { <# .SYNOPSIS Retrieves the properties of a specified DSC resource. .DESCRIPTION The Get-DscResourceProperty function retrieves the properties of a specified DSC (Desired State Configuration) resource. It imports the module containing the DSC resource and loads the CIM (Common Information Model) keywords and class resources. The function returns a collection of properties for the specified DSC resource. .PARAMETER ModuleInfo The PSModuleInfo object representing the module containing the DSC resource. This parameter is mandatory if ModuleName is not specified. .PARAMETER ModuleName The name of the module containing the DSC resource. This parameter is mandatory if ModuleInfo is not specified. .PARAMETER ResourceName The name of the DSC resource for which to retrieve the properties. .EXAMPLE $properties = Get-DscResourceProperty -ModuleName 'MyDscModule' -ResourceName 'MyDscResource' This example retrieves the properties of the 'MyDscResource' DSC resource from the 'MyDscModule' module. .EXAMPLE $module = Get-Module -Name 'MyDscModule' -ListAvailable $properties = Get-DscResourceProperty -ModuleInfo $module -ResourceName 'MyDscResource' This example retrieves the properties of the 'MyDscResource' DSC resource from the 'MyDscModule' module using the PSModuleInfo object. .OUTPUTS System.Collections.Generic.Dictionary[string, object] A collection of properties for the specified DSC resource. .NOTES This function relies on the Microsoft.PowerShell.DesiredStateConfiguration.Internal.DscClassCache class to load CIM keywords and class resources. Ensure that the module containing the DSC resource is available and can be imported. #> [CmdletBinding()] [OutputType([pscustomobject])] param ( [Parameter(Mandatory = $true, ParameterSetName = 'ModuleInfo')] [System.Management.Automation.PSModuleInfo] $ModuleInfo, [Parameter(Mandatory = $true, ParameterSetName = 'ModuleName')] [string] $ModuleName, [Parameter(Mandatory = $true)] [string] $ResourceName ) $ModuleInfo = if ($ModuleName) { Import-Module -Name $ModuleName -PassThru -Force } else { Import-Module -Name $ModuleInfo.Name -PassThru -Force } [Microsoft.PowerShell.DesiredStateConfiguration.Internal.DscClassCache]::ClearCache() $functionsToDefine = New-Object -TypeName 'System.Collections.Generic.Dictionary[string,ScriptBlock]'([System.StringComparer]::OrdinalIgnoreCase) [Microsoft.PowerShell.DesiredStateConfiguration.Internal.DscClassCache]::LoadDefaultCimKeywords($functionsToDefine) $schemaFilePath = $null $keywordErrors = New-Object -TypeName 'System.Collections.ObjectModel.Collection[System.Exception]' $foundCimSchema = [Microsoft.PowerShell.DesiredStateConfiguration.Internal.DscClassCache]::ImportCimKeywordsFromModule($ModuleInfo, $ResourceName, [ref] $SchemaFilePath, $functionsToDefine, $keywordErrors) if ($foundCimSchema) { [void][Microsoft.PowerShell.DesiredStateConfiguration.Internal.DscClassCache]::ImportScriptKeywordsFromModule($ModuleInfo, $ResourceName, [ref] $SchemaFilePath, $functionsToDefine) } else { [System.Collections.Generic.List[string]]$resourceNameAsList = $ResourceName [void][Microsoft.PowerShell.DesiredStateConfiguration.Internal.DscClassCache]::ImportClassResourcesFromModule($ModuleInfo, $resourceNameAsList, $functionsToDefine) } $resourceProperties = ([System.Management.Automation.Language.DynamicKeyword]::GetKeyword($ResourceName)).Properties foreach ($key in $resourceProperties.Keys) { $resourceProperty = $resourceProperties.$key $dscClassParameterInfo = & $ModuleInfo { param ( [Parameter(Mandatory = $true)] [string]$TypeName ) $result = @{ ElementType = $null Type = $null IsArray = $false } $result.Type = $TypeName -as [type] if ($null -eq $result.Type) { Write-Verbose "The type '$TypeName' could not be resolved." } if ($result.Type.IsArray) { $result.ElementType = $result.Type.GetElementType().FullName $result.IsArray = $true } return $result } $resourceProperty.TypeConstraint $isArrayType = if ($null -ne $dscClassParameterInfo.Type) { $dscClassParameterInfo.IsArray } else { $resourceProperty.TypeConstraint -match '.+\[\]' } [PSCustomObject]@{ Name = $resourceProperty.Name ModuleName = $ModuleInfo.Name ResourceName = $ResourceName TypeConstraint = $resourceProperty.TypeConstraint Attributes = $resourceProperty.Attributes Values = $resourceProperty.Values ValueMap = $resourceProperty.ValueMap Mandatory = $resourceProperty.Mandatory IsKey = $resourceProperty.IsKey Range = $resourceProperty.Range IsArray = $isArrayType ElementType = $dscClassParameterInfo.ElementType Type = $dscClassParameterInfo.Type } } } #EndRegion '.\Private\Get-DscResourceProperty.ps1' 148 #Region '.\Private\Get-DynamicTypeObject.ps1' -1 function Get-DynamicTypeObject { <# .SYNOPSIS Retrieves the dynamic type of a given object. .DESCRIPTION The Get-DynamicTypeObject function returns the dynamic type of a given object. It checks for various properties (ElementType, PropertyType, Type) to determine the type of the object. .PARAMETER Object The object for which to retrieve the dynamic type. .EXAMPLE $type = Get-DynamicTypeObject -Object $myObject This example retrieves the dynamic type of the object stored in the $myObject variable. .OUTPUTS System.Type The dynamic type of the specified object. .NOTES This function is useful for dynamically determining the type of an object, especially in scenarios where the type may not be known at design time. #> [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [object] $Object ) if ($Object.ElementType) { return $Object.Type.GetElementType() } elseif ($Object.PropertyType) { return $Object.PropertyType } elseif ($Object.Type) { return $Object.Type } else { return $Object } } #EndRegion '.\Private\Get-DynamicTypeObject.ps1' 50 #Region '.\Private\Get-PropertiesData.ps1' -1 function Get-PropertiesData { <# .SYNOPSIS Retrieves the value of a specified property path from a global properties variable. .DESCRIPTION The Get-PropertiesData function retrieves the value of a specified property path from a global properties variable. It constructs the path dynamically and uses it to get and return the value. This function is useful for accessing nested properties in a dynamic and flexible manner. .PARAMETER Path An array of strings representing the property path to retrieve the value from. .EXAMPLE $value = Get-PropertiesData -Path 'Property1', 'SubProperty' This example retrieves the value of 'SubProperty' under 'Property1' from the global properties variable. .EXAMPLE $value = Get-PropertiesData -Path 'Settings', 'Database', 'ConnectionString' This example retrieves the value of 'ConnectionString' under 'Settings -> Database' from the global properties variable. .OUTPUTS System.Object The value of the specified property path, or null if the path does not exist. .NOTES This function relies on a global variable named $Properties to retrieve the data. Ensure that the $Properties variable is properly initialized and populated before calling this function. #> [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [string[]] $Path ) $paths = foreach ($p in $Path) { "['$p']" } $pathValue = try { [ScriptBlock]::Create("`$Properties$($paths -join '')").Invoke() } catch { $null } return $pathValue } #EndRegion '.\Private\Get-PropertiesData.ps1' 57 #Region '.\Private\Get-RequiredModulesFromMOF.ps1' -1 #author Iain Brighton, from here: https://gist.github.com/iainbrighton/9d3dd03630225ee44126769c5d9c50a9 # Not sure that takes all possibilities into account: # i.e. when using Import-DscResource -Name ResourceName #even if it's bad practice # Also need to return PSModuleInfo, instead of @{ModuleName='<version>'} # Then probably worth promoting to public function Get-RequiredModulesFromMOF { <# .SYNOPSIS Scans a Desired State Configuration .mof file and returns the declared/ required modules. #> [CmdletBinding()] [OutputType([hashtable])] param ( [Parameter(Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)] [System.String] $Path ) process { $modules = @{} $moduleName = $null $moduleVersion = $null Get-Content -Path $Path -Encoding Unicode | ForEach-Object { $line = $_ if ($line -match '^\s?Instance of') { ## We have a new instance so write the existing one if (($null -ne $moduleName) -and ($null -ne $moduleVersion)) { $modules[$moduleName] = $moduleVersion $moduleName = $null $moduleVersion = $null Write-Verbose "Module Instance found: '$moduleName $moduleVersion'." } } elseif ($line -match '(?<=^\s?ModuleName\s?=\s?")\S+(?=";)') { ## Ignore the default PSDesiredStateConfiguration module if ($Matches[0] -notmatch 'PSDesiredStateConfiguration') { $moduleName = $Matches[0] Write-Verbose "Found Module Name '$modulename'." } else { Write-Verbose 'Excluding PSDesiredStateConfiguration module' } } elseif ($line -match '(?<=^\s?ModuleVersion\s?=\s?")\S+(?=";)') { $moduleVersion = $Matches[0] -as [System.Version] Write-Verbose "Module version = '$moduleVersion'." } } $modules } } #EndRegion '.\Private\Get-RequiredModulesFromMOF.ps1' 64 #Region '.\Private\Get-StandardCimType.ps1' -1 function Get-StandardCimType { <# .SYNOPSIS Retrieves the standard CIM types and their corresponding .NET types. .DESCRIPTION The Get-StandardCimType function retrieves a hashtable of standard Common Information Model (CIM) types and their corresponding .NET types. This function is useful for mapping CIM types to .NET types when working with DSC resources. .EXAMPLE $cimTypes = Get-StandardCimType This example retrieves the standard CIM types and their corresponding .NET types. .OUTPUTS System.Collections.Hashtable A hashtable containing the standard CIM types as keys and their corresponding .NET types as values. .NOTES This function is used internally by other functions such as Get-CimType and Write-CimProperty to map CIM types to .NET types. #> [CmdletBinding()] [OutputType([pscustomobject])] param () $types = @{ Boolean = 'System.Boolean' UInt8 = 'System.Byte' SInt8 = 'System.SByte' UInt16 = 'System.UInt16' SInt16 = 'System.Int16' UInt32 = 'System.UInt32' SInt32 = 'System.Int32' UInt64 = 'System.UInt64' SInt64 = 'System.Int64' Real32 = 'System.Single' Real64 = 'System.Double' Char16 = 'System.Char' DateTime = 'System.DateTime' String = 'System.String' Reference = 'Microsoft.Management.Infrastructure.CimInstance' Instance = 'Microsoft.Management.Infrastructure.CimInstance' BooleanArray = 'System.Boolean[]' UInt8Array = 'System.Byte[]' SInt8Array = 'System.SByte[]' UInt16Array = 'System.UInt16[]' SInt16Array = 'System.Int16[]' UInt32Array = 'System.UInt32[]' SInt32Array = 'System.Int32[]' UInt64Array = 'System.UInt64[]' SInt64Array = 'System.Int64[]' Real32Array = 'System.Single[]' Real64Array = 'System.Double[]' Char16Array = 'System.Char[]' DateTimeArray = 'System.DateTime[]' StringArray = 'System.String[]' MSFT_Credential = 'System.Management.Automation.PSCredential' 'MSFT_KeyValuePair[]' = 'System.Collections.Hashtable' MSFT_KeyValuePair = 'System.Collections.Hashtable' } $types.GetEnumerator() | ForEach-Object { $type = $_.Value -as [type] if ($null -eq $type) { Write-Error -Message "Failed to load CIM Types. The type '$($_.Value)' could not be resolved." } [PSCustomObject]@{ CimType = $_.Key DotNetType = $_.Value } } } #EndRegion '.\Private\Get-StandardCimType.ps1' 78 #Region '.\Private\Resolve-ModuleMetadataFile.ps1' -1 function Resolve-ModuleMetadataFile { [CmdletBinding(DefaultParameterSetName = 'ByDirectoryInfo')] param ( [Parameter(ParameterSetName = 'ByPath', Mandatory = $true, ValueFromPipelineByPropertyName = $true)] [string] $Path, [Parameter(ParameterSetName = 'ByDirectoryInfo', Mandatory = $true, ValueFromPipeline = $true)] [System.IO.DirectoryInfo] $InputObject ) process { $metadataFileFound = $true $metadataFilePath = '' Write-Verbose "Using Parameter set - $($PSCmdlet.ParameterSetName)." switch ($PSCmdlet.ParameterSetName) { 'ByPath' { Write-Verbose "Testing Path - $path." if (Test-Path -Path $Path) { Write-Verbose "`tFound $path." $item = (Get-Item -Path $Path) if ($item.PSIsContainer) { Write-Verbose "`t`tIt is a folder." $moduleName = Split-Path $Path -Leaf $metadataFilePath = Join-Path -Path $Path -ChildPath "$moduleName.psd1" $metadataFileFound = Test-Path -Path $metadataFilePath } else { if ($item.Extension -like '.psd1') { Write-Verbose "`t`tIt is a module metadata file." $metadataFilePath = $item.FullName $metadataFileFound = $true } else { $modulePath = Split-Path -Path $Path Write-Verbose "`t`tSearching for module metadata folder in '$ModulePath'." $moduleName = Split-Path $modulePath -Leaf Write-Verbose "`t`tModule name is '$moduleName'." $metadataFilePath = Join-Path -Path $ModulePath -ChildPath "$ModuleName.psd1" Write-Verbose "`t`tChecking for '$metadataFilePath'." $metadataFileFound = Test-Path -Path $metadataFilePath } } } else { $metadataFileFound = $false } } 'ByDirectoryInfo' { $moduleName = $InputObject.Name $metadataFilePath = Join-Path -Path $InputObject.FullName -ChildPath "$moduleName.psd1" $metadataFileFound = Test-Path -Path $metadataFilePath } } if ($metadataFileFound -and (-not [string]::IsNullOrEmpty($metadataFilePath))) { Write-Verbose "Found a module metadata file at '$metadataFilePath'." Convert-Path -Path $metadataFilePath } else { Write-Error "Failed to find a module metadata file at '$metadataFilePath'." } } } #EndRegion '.\Private\Resolve-ModuleMetadataFile.ps1' 80 #Region '.\Private\Write-CimProperty.ps1' -1 function Write-CimProperty { <# .SYNOPSIS Writes the CIM property definition to a StringBuilder object. .DESCRIPTION The Write-CimProperty function appends the definition of a CIM (Common Information Model) property to a StringBuilder object. It handles both single properties and arrays, and recursively writes nested properties if necessary. This function is useful for dynamically constructing DSC (Desired State Configuration) resource blocks. .PARAMETER StringBuilder The StringBuilder object to which the CIM property definition will be appended. .PARAMETER CimProperty The CIM property object containing the property definition. .PARAMETER Path An array of strings representing the property path. .PARAMETER ResourceName The name of the DSC resource. .EXAMPLE $stringBuilder = [System.Text.StringBuilder]::new() Write-CimProperty -StringBuilder $stringBuilder -CimProperty $cimProperty -Path 'Property1' -ResourceName 'MyResource' This example appends the definition of the 'Property1' CIM property of the 'MyResource' DSC resource to the StringBuilder object. .OUTPUTS None. The function modifies the StringBuilder object in place. .NOTES This function relies on the Get-PropertiesData and Write-CimPropertyValue functions to retrieve property values and write nested properties. Ensure that these functions are available in the same scope. #> [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [System.Text.StringBuilder] $StringBuilder, [Parameter(Mandatory = $true)] [object] $CimProperty, [Parameter(Mandatory = $true)] [string[]] $Path, [Parameter(Mandatory = $true)] [string] $ResourceName ) $null = $StringBuilder.Append("$($CimProperty.Name) = ") if ($CimProperty.IsArray -or $CimProperty.PropertyType.IsArray -or $CimProperty.CimType -eq 'InstanceArray') { $null = $StringBuilder.Append("@(`n") $pathValue = Get-PropertiesData -Path $Path $i = 0 foreach ($element in $pathValue) { $p = $Path + $i Write-CimPropertyValue -StringBuilder $StringBuilder -CimProperty $CimProperty -Path $p -ResourceName $ResourceName $i++ } $null = $StringBuilder.Append(")`n") } else { Write-CimPropertyValue -StringBuilder $StringBuilder -CimProperty $CimProperty -Path $Path -ResourceName $ResourceName } } #EndRegion '.\Private\Write-CimProperty.ps1' 79 #Region '.\Private\Write-CimPropertyValue.ps1' -1 function Write-CimPropertyValue { <# .SYNOPSIS Writes the value of a CIM property to a StringBuilder object. .DESCRIPTION The Write-CimPropertyValue function appends the value of a CIM (Common Information Model) property to a StringBuilder object. It handles both single properties and arrays, and recursively writes nested properties if necessary. This function is useful for dynamically constructing DSC (Desired State Configuration) resource blocks. .PARAMETER StringBuilder The StringBuilder object to which the CIM property value will be appended. .PARAMETER CimProperty The CIM property object containing the property value. .PARAMETER Path An array of strings representing the property path. .PARAMETER ResourceName The name of the DSC resource. .EXAMPLE $stringBuilder = [System.Text.StringBuilder]::new() Write-CimPropertyValue -StringBuilder $stringBuilder -CimProperty $cimProperty -Path 'Property1' -ResourceName 'MyResource' This example appends the value of the 'Property1' CIM property of the 'MyResource' DSC resource to the StringBuilder object. .NOTES This function relies on the Get-PropertiesData and Get-DynamicTypeObject functions to retrieve property values and determine the type of the CIM property. Ensure that these functions are available in the same scope. #> [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [System.Text.StringBuilder] $StringBuilder, [Parameter(Mandatory = $true)] [object] $CimProperty, [Parameter(Mandatory = $true)] [string[]] $Path, [Parameter(Mandatory = $true)] [string] $ResourceName ) $type = Get-DynamicTypeObject -Object $CimProperty if ($type.IsArray) { if ($type -is [System.Management.Automation.PSCustomObject]) { $typeName = $type.TypeConstraint -replace '\[\]', '' $typeProperties = ($allDscSchemaClasses.Where({ $_.CimClassName -eq $typeName -and $_.ResourceName -eq $ResourceName })).CimClassProperties } else { $typeName = $type.Name -replace '\[\]', '' $typeProperties = $type.GetElementType().GetProperties().Where({ $_.CustomAttributes.AttributeType.Name -eq 'DscPropertyAttribute' }) } } else { if ($type -is [System.Management.Automation.PSCustomObject]) { $typeName = $type.TypeConstraint $typeProperties = ($allDscSchemaClasses.Where({ $_.CimClassName -eq $typeName -and $_.ResourceName -eq $ResourceName })).CimClassProperties } elseif ($type -is [type]) { $typeName = $type.Name $typeProperties = $type.GetProperties().Where({ $_.CustomAttributes.AttributeType.Name -eq 'DscPropertyAttribute' }) } elseif ($type.GetType().FullName -eq 'Microsoft.Management.Infrastructure.Internal.Data.CimClassPropertyOfClass') { $typeName = $type.ReferenceClassName $typeProperties = ($allDscSchemaClasses.Where({ $_.CimClassName -eq $typeName -and $_.ResourceName -eq $ResourceName })).CimClassProperties } } $null = $StringBuilder.AppendLine($typeName) $null = $StringBuilder.AppendLine('{') foreach ($property in $typeProperties) { $isCimProperty = if ($property.GetType().Name -eq 'CimClassPropertyOfClass') { if ($property.CimType -in 'Instance', 'InstanceArray') { $true } else { $property.CimType -notin $script:standardCimTypes.CimType } } else { $property.PropertyType.FullName -notin $script:standardCimTypes.DotNetType -and $property.PropertyType.BaseType -ne [System.Enum] } $pathValue = Get-PropertiesData -Path ($Path + $property.Name) if ($null -ne $pathValue) { if ($isCimProperty) { Write-CimProperty -StringBuilder $StringBuilder -CimProperty $property -Path ($Path + $property.Name) -ResourceName $ResourceName } else { $paths = foreach ($p in $Path) { "['$p']" } $null = $StringBuilder.AppendLine("$($property.Name) = `$Parameters$($paths -join '')['$($property.Name)']") } } } $null = $StringBuilder.AppendLine('}') } #EndRegion '.\Private\Write-CimPropertyValue.ps1' 129 #Region '.\Public\Clear-CachedDscResource.ps1' -1 function Clear-CachedDscResource { [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingWMICmdlet', '', Justification = 'Not possible via CIM')] [CmdletBinding(SupportsShouldProcess = $true)] param () if ($pscmdlet.ShouldProcess($env:computername)) { Write-Verbose 'Stopping any existing WMI processes to clear cached resources.' ### find the process that is hosting the DSC engine $dscProcessID = Get-WmiObject msft_providers | Where-Object { $_.provider -like 'dsccore' } | Select-Object -ExpandProperty HostProcessIdentifier ### Stop the process if ($dscProcessID -and $PSCmdlet.ShouldProcess('DSC Process')) { Get-Process -Id $dscProcessID | Stop-Process } else { Write-Verbose 'Skipping killing the DSC Process' } Write-Verbose 'Clearing out any tmp WMI classes from tested resources.' Get-DscResourceWmiClass -Class tmp* | Remove-DscResourceWmiClass } } #EndRegion '.\Public\Clear-CachedDscResource.ps1' 30 #Region '.\Public\Compress-DscResourceModule.ps1' -1 function Compress-DscResourceModule { [CmdletBinding(SupportsShouldProcess = $true)] param ( [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [String] $DscBuildOutputModules, [Parameter(Mandatory = $true, ValueFromPipeline = $true)] [AllowNull()] [PSModuleInfo[]] $Modules ) begin { if (-not (Test-Path -Path $DscBuildOutputModules)) { mkdir -Path $DscBuildOutputModules -Force } } process { foreach ($module in $Modules) { if ($PSCmdlet.ShouldProcess("Compress $Module $($Module.Version) from $(Split-Path -Parent $Module.Path) to $DscBuildOutputModules")) { Write-Verbose "Publishing Module $(Split-Path -Parent $Module.Path) to $DscBuildOutputModules" $destinationPath = Join-Path -Path $DscBuildOutputModules -ChildPath "$($module.Name)_$($module.Version).zip" Compress-Archive -Path "$($module.ModuleBase)\*" -DestinationPath $destinationPath (Get-FileHash -Path $destinationPath).Hash | Set-Content -Path "$destinationPath.checksum" -NoNewline } } } } #EndRegion '.\Public\Compress-DscResourceModule.ps1' 39 #Region '.\Public\Find-ModuleToPublish.ps1' -1 function Find-ModuleToPublish { [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [String[]] $DscBuildSourceResources, [Parameter()] [ValidateNotNullOrEmpty()] [Microsoft.PowerShell.Commands.ModuleSpecification[]] $ExcludedModules = $null, [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] $DscBuildOutputModules ) $modulesAvailable = Get-ModuleFromFolder -ModuleFolder $DscBuildSourceResources -ExcludedModules $ExcludedModules foreach ($module in $modulesAvailable) { $publishTargetZip = [System.IO.Path]::Combine( $DscBuildOutputModules, "$($module.Name)_$($module.version).zip" ) $publishTargetZipCheckSum = [System.IO.Path]::Combine( $DscBuildOutputModules, "$($module.Name)_$($module.version).zip.checksum" ) $zipExists = Test-Path -Path $publishTargetZip $checksumExists = Test-Path -Path $publishTargetZipCheckSum if (-not ($zipExists -and $checksumExists)) { Write-Debug "ZipExists = $zipExists; CheckSum exists = $checksumExists" Write-Verbose -Message "Adding $($Module.Name)_$($Module.Version) to the Modules To Publish" Write-Output -InputObject $Module } else { Write-Verbose -Message "$($Module.Name) does not need to be published" } } } #EndRegion '.\Public\Find-ModuleToPublish.ps1' 48 #Region '.\Public\Get-DscFailedResource.ps1' -1 function Get-DscFailedResource { [CmdletBinding()] param ( [Parameter(Mandatory = $true, ValueFromPipeline = $true)] [Microsoft.PowerShell.DesiredStateConfiguration.DscResourceInfo[]] $DscResource ) process { foreach ($resource in $DscResource) { if ($resource.Path) { $resourceNameOrPath = Split-Path $resource.Path -Parent } else { $resourceNameOrPath = $resource.Name } if (-not (Test-xDscResource -Name $resourceNameOrPath)) { Write-Warning "`tResources $($_.name) is invalid." $resource } else { Write-Verbose ('DSC Resource Name {0} {1} is Valid' -f $resource.Name, $resource.Version) } } } } #EndRegion '.\Public\Get-DscFailedResource.ps1' 35 #Region '.\Public\Get-DscResourceFromModuleInFolder.ps1' -1 function Get-DscResourceFromModuleInFolder { [CmdletBinding()] [OutputType([object[]])] param ( [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true)] [ValidateNotNullOrEmpty()] [string] $ModuleFolder, [Parameter(Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)] [ValidateNotNullOrEmpty()] [System.Management.Automation.PSModuleInfo[]] $Modules ) begin { $oldPSModulePath = $env:PSModulePath $env:PSModulePath = $ModuleFolder Write-Verbose "Retrieving all resources for '$ModuleFolder'." $dscResources = Get-DscResource $env:PSModulePath = $oldPSModulePath $result = @() } process { Write-Verbose "Filtering the $($dscResources.Count) resources." Write-Debug ($dscResources | Format-Table -AutoSize | Out-String) foreach ($dscResource in $dscResources) { if ($null -eq $dscResource.Module) { Write-Debug "Excluding resource '$($dscResource.Name) - $($dscResource.Version)', it is not part of a module." continue } foreach ($module in $Modules) { if (-not (Compare-Object -ReferenceObject $dscResource.Module -DifferenceObject $Module -Property ModuleType, Version, Name)) { Write-Debug "Resource $($dscResource.Name) matches one of the supplied Modules." Write-Debug "`tIncluding $($dscResource.Name) $($dscResource.Version)" $result += $dscResource } } } } end { $result } } #EndRegion '.\Public\Get-DscResourceFromModuleInFolder.ps1' 60 #Region '.\Public\Get-DscResourceWmiClass.ps1' -1 function Get-DscResourceWmiClass { <# .Synopsis Retrieves WMI classes from the DSC namespace. .Description Retrieves WMI classes from the DSC namespace. .Example Get-DscResourceWmiClass -Class tmp* .Example Get-DscResourceWmiClass -Class 'MSFT_UserResource' #> [CmdletBinding()] [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingWMICmdlet', '', Justification = 'Not possible via CIM')] param ( #The WMI Class name search for. Supports wildcards. [Parameter(ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)] [Alias('Name')] [string] $Class ) begin { $dscNamespace = 'root/Microsoft/Windows/DesiredStateConfiguration' } process { Get-WmiObject -Namespace $dscNamespace -List @PSBoundParameters } } #EndRegion '.\Public\Get-DscResourceWmiClass.ps1' 33 #Region '.\Public\Get-DscSplattedResource.ps1' -1 function Get-DscSplattedResource { <# .SYNOPSIS Generates a scriptblock for a DSC resource with splatted properties. .DESCRIPTION The Get-DscSplattedResource function generates a scriptblock for a Desired State Configuration (DSC) resource with splatted properties. It constructs the resource block dynamically based on the provided properties and optionally executes it. This function is useful for dynamically constructing and invoking DSC resource blocks. .PARAMETER ResourceName The name of the DSC resource. .PARAMETER ExecutionName The execution name of the DSC resource. .PARAMETER Properties A hashtable containing the properties to be splatted into the DSC resource block. .PARAMETER NoInvoke If specified, the function returns the scriptblock without invoking it. .EXAMPLE $properties = @{ Property1 = 'Value1' Property2 = 'Value2' } $scriptblock = Get-DscSplattedResource -ResourceName 'MyResource' -Properties $properties -NoInvoke $scriptblock.Invoke($properties) This example generates a scriptblock for the 'MyResource' DSC resource with the specified properties and invokes it. .NOTES This function relies on the Get-CimType and Write-CimProperty functions to retrieve CIM types and write nested properties. Ensure that these functions are available in the same scope. .LINK Get-CimType Write-CimProperty #> [CmdletBinding()] [OutputType([scriptblock])] param ( [Parameter(Mandatory = $true)] [String] $ResourceName, [Parameter()] [String] $ExecutionName, [Parameter()] [hashtable] $Properties, [Parameter()] [switch] $NoInvoke ) if (-not $script:allDscResourcePropertiesTable -and -not $script:allDscResourcePropertiesTableWarningShown) { Write-Warning -Message "The 'allDscResourcePropertiesTable' is not defined. This will be an expensive operation. Resources with MOF sub-types are only supported when calling 'Initialize-DscResourceMetaInfo' once before starting the compilation process." $script:allDscResourcePropertiesTableWarningShown = $true } # Remove Case Sensitivity of ordered Dictionary or Hashtables $Properties = @{} + $Properties $stringBuilder = [System.Text.StringBuilder]::new() $null = $stringBuilder.AppendLine("Param([hashtable]`$Parameters)") $null = $stringBuilder.AppendLine() if ($ExecutionName) { $null = $stringBuilder.AppendLine("$ResourceName '$ExecutionName' {") } else { $null = $stringBuilder.AppendLine("$ResourceName {") } foreach ($propertyName in $Properties.Keys) { $cimProperty = Get-CimType -DscResourceName $ResourceName -PropertyName $propertyName if ($cimProperty) { Write-CimProperty -StringBuilder $stringBuilder -CimProperty $cimProperty -Path $propertyName -ResourceName $ResourceName } else { $null = $stringBuilder.AppendLine("$propertyName = `$Parameters['$propertyName']") } } $null = $stringBuilder.AppendLine('}') Write-Debug -Message ('Generated Resource Block = {0}' -f $stringBuilder.ToString()) if ($NoInvoke) { [scriptblock]::Create($stringBuilder.ToString()) } else { if ($Properties) { [scriptblock]::Create($stringBuilder.ToString()).Invoke($Properties) } else { [scriptblock]::Create($stringBuilder.ToString()).Invoke() } } } Set-Alias -Name x -Value Get-DscSplattedResource -Scope Global #EndRegion '.\Public\Get-DscSplattedResource.ps1' 118 #Region '.\Public\Get-ModuleFromFolder.ps1' -1 function Get-ModuleFromFolder { [CmdletBinding()] [OutputType([System.Management.Automation.PSModuleInfo[]])] param ( [Parameter(Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)] [ValidateNotNullOrEmpty()] [System.IO.DirectoryInfo[]] $ModuleFolder, [Parameter()] [AllowNull()] [Microsoft.PowerShell.Commands.ModuleSpecification[]] $ExcludedModules ) begin { $allModulesInFolder = @() } process { foreach ($folder in $ModuleFolder) { Write-Debug -Message "Replacing Module path with $folder" $oldPSModulePath = $env:PSModulePath $env:PSModulePath = $folder Write-Debug -Message 'Discovering modules from folder' $allModulesInFolder += Get-Module -Refresh -ListAvailable Write-Debug -Message 'Reverting PSModulePath' $env:PSModulePath = $oldPSModulePath } } end { $allModulesInFolder | Where-Object { $source = $_ Write-Debug -Message "Checking if module '$source' is sxcluded." $isExcluded = foreach ($excludedModule in $ExcludedModules) { Write-Debug "`t Excluded module '$ExcludedModule'" if (($excludedModule.Name -and $excludedModule.Name -eq $source.Name) -and ( (-not $excludedModule.Version -and -not $excludedModule.Guid -and -not $excludedModule.MaximumVersion -and -not $excludedModule.RequiredVersion ) -or ($excludedModule.Version -and $excludedModule.Version -eq $source.Version) -or ($excludedModule.Guid -and $excludedModule.Guid -ne $source.Guid) -or ($excludedModule.MaximumVersion -and $excludedModule.MaximumVersion -ge $source.Version) -or ($excludedModule.RequiredVersion -and $excludedModule.RequiredVersion -eq $source.Version) ) ) { Write-Debug ('Skipping {0} {1} {2}' -f $source.Name, $source.Version, $source.Guid) return $false } } if (-not $isExcluded) { return $true } } } } #EndRegion '.\Public\Get-ModuleFromFolder.ps1' 70 #Region '.\Public\Initialize-DscResourceMetaInfo.ps1' -1 function Initialize-DscResourceMetaInfo { <# .SYNOPSIS Initializes the metadata information for DSC resources. .DESCRIPTION The Initialize-DscResourceMetaInfo function initializes the metadata information for Desired State Configuration (DSC) resources. It retrieves the properties and schema classes for DSC resources from the specified module path and stores them in a global variable. This function is useful for preparing DSC resource metadata for further processing or validation. .PARAMETER ModulePath The path to the module containing the DSC resources. .PARAMETER ReturnAllProperties If specified, all properties of the DSC resources will be returned, including standard CIM types. .PARAMETER Force If specified, the metadata information will be re-initialized even if it has already been initialized. .PARAMETER PassThru If specified, the function will return the metadata information as an output. .EXAMPLE Initialize-DscResourceMetaInfo -ModulePath 'C:\Modules\DscResources' This example initializes the metadata information for DSC resources located in the 'C:\Modules\DscResources' path. .EXAMPLE $metadata = Initialize-DscResourceMetaInfo -ModulePath 'C:\Modules\DscResources' -PassThru This example initializes the metadata information for DSC resources and returns it as an output. .NOTES This function relies on the Get-ModuleFromFolder, Get-DscResourceFromModuleInFolder, and Get-DscResourceProperty functions to retrieve module and resource information. Ensure that these functions are available in the same scope. .LINK Get-ModuleFromFolder Get-DscResourceFromModuleInFolder Get-DscResourceProperty #> param ( [Parameter(Mandatory = $true)] [string] $ModulePath, [Parameter()] [switch] $ReturnAllProperties, [Parameter()] [switch] $Force, [Parameter()] [switch] $PassThru ) if ($script:allDscResourcePropertiesTable.Count -ne 0 -and -not $Force) { if ($PassThru) { return $script:allDscResourcePropertiesTable } else { return } } $allModules = Get-ModuleFromFolder -ModuleFolder $ModulePath $allDscResources = Get-DscResourceFromModuleInFolder -ModuleFolder $ModulePath -Modules $allModules $modulesWithDscResources = $allDscResources | Select-Object -ExpandProperty ModuleName -Unique $modulesWithDscResources = $allModules | Where-Object Name -In $modulesWithDscResources $script:standardCimTypes = Get-StandardCimType $script:allDscResourcePropertiesTable = @{} $script:allDscSchemaClasses = @() $script:allDscResourceProperties = foreach ($dscResource in $allDscResources) { $moduleInfo = $modulesWithDscResources | Where-Object { $_.Name -EQ $dscResource.ModuleName -and $_.Version -eq $dscResource.Version } $dscModule = [System.Tuple]::Create($dscResource.Module.Name, [System.Version]$dscResource.Version) $exceptionCollection = [System.Collections.ObjectModel.Collection[System.Exception]]::new() $schemaMofFile = [System.IO.Path]::ChangeExtension($dscResource.Path, 'schema.mof') if (Test-Path -Path $schemaMofFile) { $dscSchemaClasses = [Microsoft.PowerShell.DesiredStateConfiguration.Internal.DscClassCache]::ImportClasses($schemaMofFile, $dscModule, $exceptionCollection) foreach ($dscSchemaClass in $dscSchemaClasses) { $dscSchemaClass | Add-Member -Name ModuleName -MemberType NoteProperty -Value $dscResource.ModuleName $dscSchemaClass | Add-Member -Name ModuleVersion -MemberType NoteProperty -Value $dscResource.Version $dscSchemaClass | Add-Member -Name ResourceName -MemberType NoteProperty -Value $dscResource.Name } $script:allDscSchemaClasses += $dscSchemaClasses } $cimProperties = if ($ReturnAllProperties) { Get-DscResourceProperty -ModuleInfo $moduleInfo -ResourceName $dscResource.Name } else { Get-DscResourceProperty -ModuleInfo $moduleInfo -ResourceName $dscResource.Name | Where-Object TypeConstraint -NotIn $script:standardCimTypes.CimType } foreach ($cimProperty in $cimProperties) { [PSCustomObject]@{ Name = $cimProperty.Name TypeConstraint = $cimProperty.TypeConstraint IsKey = $cimProperty.IsKey Mandatory = $cimProperty.Mandatory Values = $cimProperty.Values Range = $cimProperty.Range ModuleName = $dscResource.ModuleName ResourceName = $dscResource.Name } $script:allDscResourcePropertiesTable."$($dscResource.Name)-$($cimProperty.Name)" = $cimProperty } } if ($PassThru) { $script:allDscResourcePropertiesTable } } #EndRegion '.\Public\Initialize-DscResourceMetaInfo.ps1' 135 #Region '.\Public\Publish-DscConfiguration.ps1' -1 function Publish-DscConfiguration { [CmdletBinding(SupportsShouldProcess = $true)] param ( [Parameter(Mandatory = $true)] [string] $DscBuildOutputConfigurations, [Parameter()] [string] $PullServerWebConfig = "$env:SystemDrive\inetpub\wwwroot\PSDSCPullServer\web.config" ) process { Write-Verbose "Publishing Configuration MOFs from '$DscBuildOutputConfigurations'." Get-ChildItem -Path (Join-Path -Path $DscBuildOutputConfigurations -ChildPath '*.mof') | ForEach-Object { if (-not (Test-Path -Path $PullServerWebConfig)) { Write-Warning "The Pull Server configg '$PullServerWebConfig' cannot be found." Write-Warning "`t Skipping Publishing Configuration MOFs" } elseif ($PSCmdlet.ShouldProcess($_.BaseName)) { Write-Verbose -Message "Publishing $($_.Name)" Publish-MofToPullServer -FullName $_.FullName -PullServerWebConfig $PullServerWebConfig } } } } #EndRegion '.\Public\Publish-DscConfiguration.ps1' 33 #Region '.\Public\Publish-DscResourceModule.ps1' -1 function Publish-DscResourceModule { [CmdletBinding(SupportsShouldProcess = $true)] param ( [Parameter(Mandatory = $true)] [string] $DscBuildOutputModules, [Parameter()] [System.IO.FileInfo] $PullServerWebConfig = "$env:SystemDrive\inetpub\wwwroot\PSDSCPullServer\web.config" ) begin { if (-not (Test-Path $PullServerWebConfig)) { if ($PSBoundParameters['ErrorAction'] -eq 'SilentlyContinue') { Write-Warning -Message "Could not find the Web.config of the pull Server at '$PullServerWebConfig'." } else { throw "Could not find the Web.config of the pull Server at '$PullServerWebConfig'." } return } else { $webConfigXml = [xml](Get-Content -Raw -Path $PullServerWebConfig) $configXElement = $webConfigXml.SelectNodes("//appSettings/add[@key = 'ConfigurationPath']") $OutputFolderPath = $configXElement.Value } } process { if ($OutputFolderPath) { Write-Verbose 'Moving Processed Resource Modules from' Write-Verbose "`t$DscBuildOutputModules to" Write-Verbose "`t$OutputFolderPath" if ($PSCmdlet.ShouldProcess("copy '$DscBuildOutputModules' to '$OutputFolderPath'")) { Get-ChildItem -Path $DscBuildOutputModules -Include @('*.zip', '*.checksum') | Copy-Item -Destination $OutputFolderPath -Force } } } } #EndRegion '.\Public\Publish-DscResourceModule.ps1' 53 #Region '.\Public\Push-DscConfiguration.ps1' -1 function Push-DscConfiguration { [CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = 'High')] [OutputType([void])] param ( [Parameter(Mandatory = $true, Position = 0, ValueFromPipelineByPropertyName)] [ValidateNotNullOrEmpty()] [System.Management.Automation.Runspaces.PSSession] $Session, [Parameter()] [Alias('MOF', 'Path')] [System.IO.FileInfo] $ConfigurationDocument, [Parameter()] [System.Management.Automation.PSModuleInfo[]] $WithModule, [Parameter(ValueFromPipelineByPropertyName = $true, ValueFromRemainingArguments = $true, Position = 1)] [Alias('DscBuildOutputModules')] $StagingFolderPath = "$Env:TMP\DSC\BuildOutput\modules\", [Parameter(ValueFromPipelineByPropertyName = $true, ValueFromRemainingArguments = $true, Position = 3)] $RemoteStagingPath = '$Env:TMP\DSC\modules\', [Parameter(ValueFromPipelineByPropertyName = $true, ValueFromRemainingArguments = $true, Position = 4)] [switch] $Force ) process { if ($PSCmdlet.ShouldProcess($Session.ComputerName, "Applying MOF '$ConfigurationDocument'")) { if ($WithModule) { Push-DscModuleToNode -Module $WithModule -StagingFolderPath $StagingFolderPath -RemoteStagingPath $RemoteStagingPath -Session $Session } Write-Verbose 'Removing previously pushed configuration documents' $resolvedRemoteStagingPath = Invoke-Command -Session $Session -ScriptBlock { $resolvedStagingPath = $ExecutionContext.InvokeCommand.ExpandString($Using:RemoteStagingPath) $null = Get-Item "$resolvedStagingPath\*.mof" | Remove-Item -Force -ErrorAction SilentlyContinue if (-not (Test-Path $resolvedStagingPath)) { mkdir -Force $resolvedStagingPath -ErrorAction Stop } $resolvedStagingPath } -ErrorAction Stop $remoteConfigDocumentPath = [System.IO.Path]::Combine($ResolvedRemoteStagingPath, 'localhost.mof') Copy-Item -ToSession $Session -Path $ConfigurationDocument -Destination $remoteConfigDocumentPath -Force -ErrorAction Stop Write-Verbose "Attempting to apply '$remoteConfigDocumentPath' on '$($session.ComputerName)'" Invoke-Command -Session $Session -ScriptBlock { Start-DscConfiguration -Wait -Force -Path $Using:resolvedRemoteStagingPath -Verbose -ErrorAction Stop } } } } #EndRegion '.\Public\Push-DscConfiguration.ps1' 63 #Region '.\Public\Push-DscModuleToNode.ps1' -1 <# .SYNOPSIS Injects Modules via PS Session. .DESCRIPTION Injects the missing modules on a remote node via a PSSession. The module list is checked again the available modules from the remote computer, Any missing version is then zipped up and sent over the PS session, before being extracted in the root PSModulePath folder of the remote node. .PARAMETER Module A list of Modules required on the remote node. Those missing will be packaged based on their Path. .PARAMETER StagingFolderPath Staging folder where the modules are being zipped up locally before being sent accross. .PARAMETER Session Session to use to gather the missing modules and to copy the modules to. .PARAMETER RemoteStagingPath Path on the remote Node where the modules will be copied before extraction. .PARAMETER Force Force all modules to be re-zipped, re-sent, and re-extracted to the target node. .EXAMPLE Push-DscModuleToNode -Module (Get-ModuleFromFolder C:\src\SampleKitchen\modules) -Session $RemoteSession -StagingFolderPath "C:\BuildOutput" #> function Push-DscModuleToNode { [CmdletBinding()] [OutputType([void])] param ( # Param1 help description [Parameter(Mandatory = $true, Position = 0, ValueFromPipelineByPropertyName = $true, ValueFromRemainingArguments = $true)] [Alias('ModuleInfo')] [System.Management.Automation.PSModuleInfo[]] $Module, [Parameter(Position = 1, ValueFromPipelineByPropertyName = $true, ValueFromRemainingArguments = $true)] [Alias('DscBuildOutputModules')] $StagingFolderPath = "$Env:TMP\DSC\BuildOutput\modules\", [Parameter(Mandatory = $true, Position = 2, ValueFromPipelineByPropertyName = $true, ValueFromRemainingArguments = $true)] [System.Management.Automation.Runspaces.PSSession] $Session, [Parameter(Position = 3, ValueFromPipelineByPropertyName = $true, ValueFromRemainingArguments = $true)] $RemoteStagingPath = '$Env:TMP\DSC\modules\', [Parameter(Position = 4, ValueFromPipelineByPropertyName = $true, ValueFromRemainingArguments = $true)] [switch] $Force ) process { # Find the modules already available remotely if (-not $Force) { $remoteModuleAvailable = Invoke-Command -Session $Session -ScriptBlock { Get-Module -ListAvailable } } $resolvedRemoteStagingPath = Invoke-Command -Session $Session -ScriptBlock { $ResolvedStagingPath = $ExecutionContext.InvokeCommand.ExpandString($using:RemoteStagingPath) if (-not (Test-Path $ResolvedStagingPath)) { mkdir -Force $ResolvedStagingPath } $resolvedStagingPath } # Find the modules missing on remote node $missingModules = $Module.Where{ $matchingModule = foreach ($remoteModule in $RemoteModuleAvailable) { if ( $remoteModule.Name -eq $_.Name -and $remoteModule.Version -eq $_.Version -and $remoteModule.Guid -eq $_.Guid ) { Write-Verbose "Module match: '$($remoteModule.Name)'." $remoteModule } } if (-not $matchingModule) { Write-Verbose "Module not found: '$($_.Name)'." $_ } } Write-Verbose "The Missing modules are '$($MissingModules.Name -join ', ')'." # Find the missing modules from the staging folder # and publish it there Write-Verbose "Looking for missing zip modules in '$($StagingFolderPath)'." $missingModules.Where{ -not (Test-Path -Path "$StagingFolderPath\$($_.Name)_$($_.version).zip") } | Compress-DscResourceModule -DscBuildOutputModules $StagingFolderPath # Copy missing modules to remote node if not present already foreach ($module in $missingModules) { $fileName = "$($StagingFolderPath)/$($module.Name)_$($module.Version).zip" $testPathResult = Invoke-Command -Session $Session -ScriptBlock { param ( [Parameter(Mandatory = $true)] [string] $FileName ) Test-Path -Path $FileName } -ArgumentList $fileName if ($Force -or -not ($testPathResult)) { Write-Verbose "Copying '$fileName*' to '$ResolvedRemoteStagingPath'." Invoke-Command -Session $Session -ScriptBlock { param ( [Parameter(Mandatory = $true)] [string] $PathToZips ) if (-not (Test-Path -Path $PathToZips)) { mkdir -Path $PathToZips -Force } } -ArgumentList $resolvedRemoteStagingPath $param = @{ ToSession = $Session Path = "$($StagingFolderPath)/$($module.Name)_$($module.Version)*" Destination = $ResolvedRemoteStagingPath Force = $true } Copy-Item @param | Out-Null } else { Write-Verbose 'The File is already present remotely.' } } # Extract missing modules on remote node to PSModulePath Write-Verbose "Expanding '$resolvedRemoteStagingPath/*.zip' to '$env:CommonProgramW6432\WindowsPowerShell\Modules\$($Module.Name)\$($module.version)'." Invoke-Command -Session $Session -ScriptBlock { param ( [Parameter()] $MissingModules, [Parameter()] $PathToZips ) foreach ($module in $MissingModules) { $fileName = "$($module.Name)_$($module.version).zip" Write-Verbose "Expanding '$PathToZips/$fileName' to '$Env:CommonProgramW6432\WindowsPowerShell\Modules\$($Module.Name)\$($module.version)'." Expand-Archive -Path "$PathToZips/$fileName" -DestinationPath "$Env:ProgramW6432\WindowsPowerShell\Modules\$($Module.Name)\$($module.version)" -Force } } -ArgumentList $missingModules, $resolvedRemoteStagingPath } } #EndRegion '.\Public\Push-DscModuleToNode.ps1' 165 #Region '.\Public\Remove-DscResourceWmiClass.ps1' -1 <# .Synopsis Removes a WMI class from the DSC namespace. .Description Removes a WMI class from the DSC namespace. .Example Get-DscResourceWmiClass -Class tmp* | Remove-DscResourceWmiClass .Example Remove-DscResourceWmiClass -Class 'tmpD460' #> function Remove-DscResourceWmiClass { [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingWMICmdlet', '', Justification = 'Not possible via CIM')] [CmdletBinding()] param ( #The WMI Class name to remove. Supports wildcards. [Parameter(ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)] [Alias('Name')] [string] $ResourceType ) begin { $dscNamespace = 'root/Microsoft/Windows/DesiredStateConfiguration' } process { #Have to use WMI here because I can't find how to delete a WMI instance via the CIM cmdlets. (Get-WmiObject -Namespace $dscNamespace -List -Class $ResourceType).psbase.Delete() } } #EndRegion '.\Public\Remove-DscResourceWmiClass.ps1' 34 #Region '.\Public\Test-DscResourceFromModuleInFolderIsValid.ps1' -1 function Test-DscResourceFromModuleInFolderIsValid { [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [System.IO.DirectoryInfo] $ModuleFolder, [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true, ValueFromPipeline = $true)] [System.Management.Automation.PSModuleInfo[]] [AllowNull()] $Modules ) process { foreach ($module in $Modules) { $Resources = Get-DscResourceFromModuleInFolder -ModuleFolder $ModuleFolder -Modules $module $Resources.Where{ $_.ImplementedAs -eq 'PowerShell' } | Assert-DscModuleResourceIsValid } } } #EndRegion '.\Public\Test-DscResourceFromModuleInFolderIsValid.ps1' 26 |