PSDSC.psm1
#Region '.\prefix.ps1' -1 $Script:IsPowerShellCore = $PSVersionTable.PSEdition -eq 'Core' if ($Script:IsPowerShellCore -and $IsWindows) { Import-Module -Name 'PSDesiredStateConfiguration' -MinimumVersion 2.0.7 -Prefix 'Pwsh' -ErrorAction SilentlyContinue } #EndRegion '.\prefix.ps1' 7 #Region '.\Classes\10.ManifestFile.ps1' -1 class ResourceManifest { [System.String] $type [System.String] $description [System.Version] $version [System.Array] $resourceInput ResourceManifest () { } ResourceManifest ([string] $type, [string] $description, [version] $version) { $this.type = $type $this.description = $description $this.version = $version } ResourceManifest ([string] $type, [string] $description, [version] $version, [array] $resourceInput) { $this.type = $type $this.description = $description $this.version = $version $this.resourceInput = $resourceInput } } #EndRegion '.\Classes\10.ManifestFile.ps1' 27 #Region '.\Classes\DscConfigCompleters.ps1' -1 class DscConfigCompleter : System.Management.Automation.IArgumentCompleter { [System.Collections.Generic.IEnumerable[System.Management.Automation.CompletionResult]] CompleteArgument( [string] $CommandName, [string] $ParameterName, [string] $wordToComplete, [System.Management.Automation.Language.CommandAst] $CommandAst, [Collections.IDictionary] $fakeBoundParameters ) { $exe = ResolveDscExe -ErrorAction SilentlyContinue $list = [System.Collections.Generic.List[System.Management.Automation.CompletionResult]]::new() if ($exe) { $resources = GetDscResourceDetail -Exclude @{kind = 'Group' } # don't include Group resources foreach ($resource in $resources) { $CompletionText = $resource $ListItemText = $resource $ResultType = [System.Management.Automation.CompletionResultType]::ParameterValue $ToolTip = $resource $obj = [System.Management.Automation.CompletionResult]::new($CompletionText, $ListItemText, $ResultType, $Tooltip) $list.add($obj) } return $list } else { return $list } } } class DscConfigInputCompleter : System.Management.Automation.IArgumentCompleter { [System.Collections.Generic.IEnumerable[System.Management.Automation.CompletionResult]] CompleteArgument( [string] $CommandName, [string] $ParameterName, [string] $wordToComplete, [System.Management.Automation.Language.CommandAst] $CommandAst, [Collections.IDictionary] $fakeBoundParameters ) { if ($fakeBoundParameters.ContainsKey('ResourceName')) { [array]$Resources = GetDscRequiredKey -BuildHashTable | Where-Object { $_.type -eq $fakeBoundParameters.ResourceName } | Select-Object -ExpandProperty resourceInput -Unique | Sort-Object } else { [array]$Resources = @() } $list = [System.Collections.Generic.List[System.Management.Automation.CompletionResult]]::new() foreach ($Resource in $Resources) { $CompletionText = $Resource $ListItemText = $Resource $ResultType = [System.Management.Automation.CompletionResultType]::ParameterValue $ToolTip = '{0}' -f $fakeBoundParameters.ResourceName $obj = [System.Management.Automation.CompletionResult]::new($CompletionText, $ListItemText, $ResultType, $Tooltip) $list.add($obj) } return $list } } #EndRegion '.\Classes\DscConfigCompleters.ps1' 76 #Region '.\Classes\DscResourceCompleters.ps1' -1 class DscResourceCompleter : System.Management.Automation.IArgumentCompleter { [System.Collections.Generic.IEnumerable[System.Management.Automation.CompletionResult]] CompleteArgument( [string] $CommandName, [string] $ParameterName, [string] $wordToComplete, [System.Management.Automation.Language.CommandAst] $CommandAst, [Collections.IDictionary] $fakeBoundParameters ) { $exe = ResolveDscExe -ErrorAction SilentlyContinue $list = [System.Collections.Generic.List[System.Management.Automation.CompletionResult]]::new() if ($exe) { $manifestFiles = Get-ChildItem -Path (Split-Path $exe -Parent) -Depth 1 -Filter "*.dsc.resource.json" foreach ($manifest in $manifestFiles) { $typeName = (Get-Content $manifest | ConvertFrom-Json -ErrorAction SilentlyContinue).type $CompletionText = $typeName $ListItemText = $typeName $ResultType = [System.Management.Automation.CompletionResultType]::ParameterValue $ToolTip = $typeName $obj = [System.Management.Automation.CompletionResult]::new($CompletionText, $ListItemText, $ResultType, $Tooltip) $list.add($obj) } # section to include PSTypes data $psTypes = ReadDscPsAdapterSchema -ReturnTypeInfo $psTypes | ForEach-Object { $list.Add($_) } return $list } else { return $list } } } class DscResourceInputCompleter : System.Management.Automation.IArgumentCompleter { [System.Collections.Generic.IEnumerable[System.Management.Automation.CompletionResult]] CompleteArgument( [string] $CommandName, [string] $ParameterName, [string] $wordToComplete, [System.Management.Automation.Language.CommandAst] $CommandAst, [Collections.IDictionary] $fakeBoundParameters ) { if ($fakeBoundParameters.ContainsKey('ResourceName')) { [array]$Resources = GetDscRequiredKey | Where-Object { $_.type -eq $fakeBoundParameters.ResourceName } | Select-Object -ExpandProperty resourceInput -Unique | Sort-Object } else { [array]$Resources = @() } $list = [System.Collections.Generic.List[System.Management.Automation.CompletionResult]]::new() foreach ($Resource in $Resources) { $CompletionText = "'$Resource'" $ListItemText = "'$Resource'" $ResultType = [System.Management.Automation.CompletionResultType]::ParameterValue $ToolTip = '{0}' -f $fakeBoundParameters.ResourceName $obj = [System.Management.Automation.CompletionResult]::new($CompletionText, $ListItemText, $ResultType, $Tooltip) $list.add($obj) } return $list } } #EndRegion '.\Classes\DscResourceCompleters.ps1' 82 #Region '.\Classes\DscVersionCompleters.ps1' -1 class DscVersionCompleter : System.Management.Automation.IArgumentCompleter { [System.Collections.Generic.IEnumerable[System.Management.Automation.CompletionResult]] CompleteArgument( [string] $CommandName, [string] $ParameterName, [string] $wordToComplete, [System.Management.Automation.Language.CommandAst] $CommandAst, [Collections.IDictionary] $fakeBoundParameters ) { [array]$DscVersions = (GetDscVersion -UseGitHub) | Where-Object { $_ -like "$wordToComplete*" } $list = [System.Collections.Generic.List[System.Management.Automation.CompletionResult]]::new() foreach ($DscVersion in $DscVersions) { $CompletionText = $DscVersion $ListItemText = $DscVersion $ResultType = [System.Management.Automation.CompletionResultType]::ParameterValue $ToolTip = $DscVersion $obj = [System.Management.Automation.CompletionResult]::new($CompletionText, $ListItemText, $ResultType, $Tooltip) $list.add($obj) } return $list } } #EndRegion '.\Classes\DscVersionCompleters.ps1' 30 #Region '.\Private\DSC\BuildDscConfigDocument.ps1' -1 function BuildDscConfigDocument { <# .SYNOPSIS Build DSC configuration document .DESCRIPTION The function BuildDscConfigDocument builds a Desired State Configuration version 3 document. .PARAMETER Path The path to a valid Configuration Document. .PARAMETER Content The content to a valid DSC Configuration Document. .EXAMPLE PS C:\> $path = 'myConfig.ps1' PS C:\> BuildDscConfigDocument -Path $path .NOTES For more details, go to module repository at: https://github.com/Gijsreyn/PSDSC. #> [CmdletBinding(DefaultParameterSetName = 'Path')] [OutputType([System.Collections.Specialized.OrderedDictionary])] param ( [Parameter(Mandatory = $true, ParameterSetName = 'Path')] [ValidateScript({ if (-Not ($_ | Test-Path) ) { throw "File or folder does not exist" } if (-Not ($_ | Test-Path -PathType Leaf) ) { throw "The Path argument must be a file. Folder paths are not allowed." } return $true })] [System.String] $Path, [Parameter(Mandatory = $true, ParameterSetName = 'Content')] [System.String] $Content ) # create configuration document resource class (can be re-used) $configurationDocument = [PSCustomObject]@{ name = $null type = $null properties = @{} } # convert object $rs = ConvertToDscObject @PSBoundParameters -ErrorAction SilentlyContinue $configurationDocument.name = ($rs | Select-Object -First 1 -ExpandProperty ConfigurationName) $configurationDocument.type = ($rs | Select-Object -First 1 -ExpandProperty Type) # bag to hold resources $resourceProps = [System.Collections.Generic.List[object]]::new() foreach ($resource in $rs) { # props $properties = @{} # TODO: make the dependsOn key using resourceId() function $resource.GetEnumerator() | ForEach-Object { if ($_.Key -notin @('ResourceInstanceName', 'ResourceName', 'ModuleName', 'DependsOn', 'ConfigurationName', 'Type')) { $properties.Add($_.Key, $_.Value) } } # build the module $inputObject = [PSCustomObject]@{ name = $resource.ResourceInstanceName type = ("{0}/{1}" -f $resource.ModuleName, $resource.ResourceName) properties = $properties } # add to bag $resourceProps.Add($inputObject) } # add all the resources $configurationDocument.properties = @{ resources = $resourceProps } # TODO: get schema information from GitHub $configurationDocument = [ordered]@{ "`$schema" = "https://raw.githubusercontent.com/PowerShell/DSC/main/schemas/2024/04/config/document.json" resources = @($configurationDocument) } return $configurationDocument } #EndRegion '.\Private\DSC\BuildDscConfigDocument.ps1' 100 #Region '.\Private\DSC\BuildDscInput.ps1' -1 function BuildDscInput { <# .SYNOPSIS Build the Desired State Configuration input string. .DESCRIPTION The function BuildDscInput builds the argument input string to pass to 'dsc.exe'. .PARAMETER Command The sub command to run e.g. config .PARAMETER Operation The operation to run e.g. Set .PARAMETER ResourceName The resource name to execute. .PARAMETER ResourceInput The resource input to provide. Supports JSON, YAML path and PowerShell hash table. .PARAMETER Parameter Optionally, the parameter input to provide. .EXAMPLE PS C:\> BuildDscInput -Arguments config -Operation get -ResourceInput myconfig.dsc.config.yaml .NOTES For more details, go to module repository at: https://github.com/Gijsreyn/PSDSC. #> [CmdletBinding()] [OutputType([System.String])] param ( [Parameter(Mandatory = $true)] [ValidateSet("config", "resource")] [System.String] $Command, [Parameter(Mandatory = $true)] [ValidateSet('list', 'get', 'set', 'test', 'delete', 'export')] [System.String] $Operation, [Parameter(Mandatory = $false)] [Alias('Name')] [System.String] $ResourceName, [Parameter(Mandatory = $false)] [AllowNull()] [object] $ResourceInput, [Parameter(Mandatory = $false)] [AllowNull()] [object] $Parameter ) # string to build $sb = [System.Text.StringBuilder]::new($Command) if ($Command -eq 'config' -and $Operation -in @('list', 'delete')) { # TODO: helpful documentation to use _exist for set operation Throw "Operation not valid when running 'dsc config'. Please use different combination." } switch ($Command) { 'config' { # start validating parameters first $paramValue = ConfirmDscInput -Command $Command -ParameterInput $Parameter if ($paramValue) { Write-Debug -Message "Appending '$ParamValue'" $sb.Append(" $paramValue") | Out-Null } $sb.Append(" $Operation") | Out-Null $inputValue = ConfirmDscInput -Command $Command -ResourceInput $ResourceInput if ($inputValue) { $sb.Append(" $inputValue") | Out-Null } } 'resource' { # operation comes first $sb.Append(" $Operation") | Out-Null $inputValue = ConfirmDscInput -Command $Command -ResourceInput $ResourceInput if ($Operation -ne 'List' -and ([string]::IsNullOrEmpty($ResourceName))) { Throw ("You are attempting to run 'dsc resource' using operation '{0}' without resource name. Please specify the resource name using -ResourceName" -f $Operation) } if ($Operation -eq 'List') { $inputValue = $null $string = " --adapter $ResourceName" # TODO: validate the resource name should be an adapter } if ($Operation -in @('get', 'set', 'test', 'delete', 'export')) { $string = " --resource $ResourceName" } $sb.Append($string) | Out-Null if ($inputValue) { $sb.Append(" $inputValue") | Out-Null } } Default { } } return $sb.ToString() } #EndRegion '.\Private\DSC\BuildDscInput.ps1' 131 #Region '.\Private\DSC\ConfirmDscInput.ps1' -1 function ConfirmDscInput { <# .SYNOPSIS Confirm input and builds arguments required for 'dsc.exe' .DESCRIPTION The function ConfirmDscInput confirms the input that is passed from higher-level functions. It returns the arguments for building the System.Diagnostics.Process object. .PARAMETER Command The command to run e.g. config .PARAMETER ResourceInput The resource input to provide. Supports JSON, YAML path and PowerShell hash table. .PARAMETER ParameterInput Optionally, the parameter input to provide. .EXAMPLE PS C:\> ConfirmDscInput -Command resource -ResourceInput @{keyPath = 'HKCU\1'} .NOTES For more details, go to module repository at: https://github.com/Gijsreyn/PSDSC. #> [CmdletBinding(DefaultParameterSetName = 'ByInput')] [OutputType([System.String])] param ( [Parameter(Mandatory = $true)] [ValidateSet('config', 'resource')] [System.String] $Command, [Parameter(Mandatory = $false, ParameterSetName = 'ByInput')] [System.Object] [AllowNull()] $ResourceInput, [Parameter(Mandatory = $false, ParameterSetName = 'ByParameter')] [System.Object] [AllowNull()] $ParameterInput ) $Path = $false if ([string]::IsNullOrEmpty($ResourceInput) -and [string]::IsNullOrEmpty($ParameterInput)) { return } if ($PSCmdlet.ParameterSetName -eq 'ByInput' -and $ResourceInput) { # check the type of ResourceInput and process accordingly if ($ResourceInput -is [string] -and (Test-Path -Path $ResourceInput -ErrorAction SilentlyContinue)) { $extension = (Get-Item $ResourceInput -ErrorAction SilentlyContinue).Extension if ($extension -in @('.json', '.yaml', '.yml')) { Write-Debug -Message "The '$ResourceInput' is a valid path string." $out = $ResourceInput # set variable $Path = $true } if ($extension -eq '.ps1' -and $Command -eq 'config') { Write-Debug -Message "The '$ResourceInput' is a PowerShell (.ps1) script. Converting..." $out = ConvertTo-DscJson -Path $ResourceInput Write-Debug -Message "The converted JSON is:" Write-Debug -Message $out } } elseif ($ResourceInput -is [hashtable]) { # resourceInput is a hashtable $json = $ResourceInput | ConvertTo-Json -Depth 10 -Compress $out = $json } elseif ($ResourceInput -is [string]) { try { $json = $ResourceInput | ConvertFrom-Json $out = $json | ConvertTo-Json -Depth 10 -Compress } catch { Write-Debug -Message "The '$ResourceInput' is not a valid JSON string. Please make sure the input is valid JSON." } } else { # TODO: check if YAML can be used to convert with ConvertFrom-Yaml } } # process ParameterInput if provided if ($PSCmdlet.ParameterSetName -eq 'ByParameter' -and $ParameterInput) { if ($ParameterInput -is [string] -and (Test-Path -Path $ParameterInput -ErrorAction SilentlyContinue)) { $extension = (Get-Item $ParameterInput -ErrorAction SilentlyContinue).Extension if ($extension -in @('.json', '.yaml', '.yml')) { Write-Debug -Message "The '$ParameterInput' is a valid path string." $out = $ParameterInput # set variable $Path = $true } } elseif ($ParameterInput -is [hashtable]) { $json = $ParameterInput | ConvertTo-Json -Compress $out = $json } elseif ($ParameterInput -is [string]) { try { $json = $ParameterInput | ConvertFrom-Json $out = $json | ConvertTo-Json -Depth 10 -Compress } catch { Write-Debug -Message "The '$ParameterInput' is not a valid JSON string. Please make sure the input is valid JSON." } } } switch ($Command) { 'config' { if ($Path -and $PSCmdlet.ParameterSetName -eq 'ByInput') { $string = "--path $out" } elseif ($PSCmdlet.ParameterSetName -eq 'ByInput') { $string = ("--document {0}" -f ($out | ConvertTo-Json) -replace "\\\\", "\") } elseif ($Path -and $PSCmdlet.ParameterSetName -eq 'ByParameter') { $string = "--parameters-file $out" } else { $string = ("--parameters $(($out | ConvertTo-Json) -replace "\\\\", "\")" -replace "`r`n", "") } } 'resource' { if (-not $extension) { $string = ("--input {0}" -f ($out | ConvertTo-Json) -replace "\\\\", "\") } else { $string = "--path $out" } } } return $string } #EndRegion '.\Private\DSC\ConfirmDscInput.ps1' 170 #Region '.\Private\DSC\ConvertToDscObject.ps1' -1 function ConvertToDscObject { <# .SYNOPSIS Convert DSC configuration documents to object .DESCRIPTION The function ConvertToDscObject converts Configuration Document(s) to an hashtable object .PARAMETER Path The path to a valid Configuration Document. .PARAMETER Content The content to a valid DSC Configuration Document. .EXAMPLE PS C:\> $path = 'myConfig.ps1' PS C:\> ConvertToDscObject -Path $path .NOTES Credits to: https://github.com/microsoft/DSCParser/tree/master #> [CmdletBinding(DefaultParameterSetName = 'Path')] [OutputType([System.Array])] [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingInvokeExpression', '', Justification = 'Function converted from Microsoft.')] param ( [Parameter(Mandatory = $true, ParameterSetName = 'Path')] [ValidateScript({ if (-Not ($_ | Test-Path) ) { throw "File or folder does not exist" } if (-Not ($_ | Test-Path -PathType Leaf) ) { throw "The Path argument must be a file. Folder paths are not allowed." } if (($_ | Get-Item).Extension -ne '.ps1' ) { throw "The Path argument must be a file and end with '.ps1'. Folder paths are not allowed." } return $true })] [System.String] $Path, [Parameter(Mandatory = $true, ParameterSetName = 'Content')] [System.String] $Content ) $result = @() $Tokens = $null $ParseErrors = $null # Use the AST to parse the DSC configuration if (-not [System.String]::IsNullOrEmpty($Path) -and [System.String]::IsNullOrEmpty($Content)) { if (-not ([System.IO.Path]::GetExtension($Path))) { Throw "The file must end with .ps1." } $Content = Get-Content $Path -Raw } # Remove the module version information. $start = $Content.ToLower().IndexOf('import-dscresource') if ($start -ge 0) { $end = $Content.IndexOf("`n", $start) if ($end -gt $start) { $start = $Content.ToLower().IndexOf("-moduleversion", $start) if ($start -ge 0 -and $start -lt $end) { $Content = $Content.Remove($start, $end - $start) } } } # Rename the configuration node to ensure a valid name is used. $start = $Content.ToLower().IndexOf("`nconfiguration") if ($start -lt 0) { $start = $Content.ToLower().IndexOf(' configuration ') } if ($start -ge 0) { $end = $Content.IndexOf("`n", $start) if ($end -gt $start) { $start = $Content.ToLower().IndexOf(' ', $start + 1) if ($start -ge 0 -and $start -lt $end) { $Content = $Content.Remove($start, $end - $start) $Content = $Content.Insert($start, " TempDSCParserConfiguration") } } } $AST = [System.Management.Automation.Language.Parser]::ParseInput($Content, [ref]$Tokens, [ref]$ParseErrors) # Look up the Configuration definition ("") $Config = $AST.Find({ $Args[0].GetType().Name -eq 'ConfigurationDefinitionAst' }, $False) # Retrieve information about the DSC Modules imported in the config # and get the list of their associated resources. $ModulesToLoad = @() foreach ($statement in $config.body.ScriptBlock.EndBlock.Statements) { if ($null -ne $statement.CommandElements -and $null -ne $statement.CommandElements[0].Value -and ` $statement.CommandElements[0].Value -eq 'Import-DSCResource') { $currentModule = @{} for ($i = 0; $i -le $statement.CommandElements.Count; $i++) { if ($statement.CommandElements[$i].ParameterName -eq 'ModuleName' -and ` ($i + 1) -lt $statement.CommandElements.Count) { $moduleName = $statement.CommandElements[$i + 1].Value $currentModule.Add('ModuleName', $moduleName) } elseif ($statement.CommandElements[$i].ParameterName -eq 'ModuleVersion' -and ` ($i + 1) -lt $statement.CommandElements.Count) { $moduleVersion = $statement.CommandElements[$i + 1].Value $currentModule.Add('ModuleVersion', $moduleVersion) } } $ModulesToLoad += $currentModule } } $DSCResources = @() foreach ($moduleToLoad in $ModulesToLoad) { $loadedModuleTest = Get-Module -Name $moduleToLoad.ModuleName -ListAvailable | Where-Object -FilterScript { $_.Version -eq $moduleToLoad.ModuleVersion } if ($null -eq $loadedModuleTest -and -not [System.String]::IsNullOrEmpty($moduleToLoad.ModuleVersion)) { throw "Module {$($moduleToLoad.ModuleName)} version {$($moduleToLoad.ModuleVersion)} specified in the configuration isn't installed on the machine/agent. Install it by running: Install-Module -Name '$($moduleToLoad.ModuleName)' -RequiredVersion '$($moduleToLoad.ModuleVersion)'" } else { Write-Verbose -Message ("Retrieving module: {0}" -f $moduleToLoad.ModuleName) if ($Script:IsPowerShellCore) { $currentResources = Get-PwshDscResource -Module $moduleToLoad.ModuleName } else { $currentResources = Get-DSCResource -Module $moduleToLoad.ModuleName } if (-not [System.String]::IsNullOrEmpty($moduleToLoad.ModuleVersion)) { $currentResources = $currentResources | Where-Object -FilterScript { $_.Version -eq $moduleToLoad.ModuleVersion } } $DSCResources += $currentResources } } # Drill down # Body.ScriptBlock is the part after "Configuration <InstanceName> {" # EndBlock is the actual code within that Configuration block # Find the first DynamicKeywordStatement that has a word "Node" in it, find all "NamedBlockAst" elements, these are the DSC resource definitions try { $resourceInstances = $Config.Body.ScriptBlock.EndBlock.Statements.Find({ $Args[0].GetType().Name -eq 'DynamicKeywordStatementAst' -and $Args[0].CommandElements[0].StringConstantType -eq 'BareWord' -and $Args[0].CommandElements[0].Value -eq 'Node' }, $False).commandElements[2].ScriptBlock.Find({ $Args[0].GetType().Name -eq 'NamedBlockAst' }, $False).Statements } catch { $resourceInstances = $Config.Body.ScriptBlock.EndBlock.Statements | Where-Object -FilterScript { $null -ne $_.CommandElements -and $_.CommandElements[0].Value -ne 'Import-DscResource' } } # Get the name of the configuration. $configurationName = $Config.InstanceName.Value $totalCount = 1 foreach ($resource in $resourceInstances) { $currentResourceInfo = @{} # CommandElements # 0 - Resource Type # 1 - Resource Instance Name # 2 - Key/Pair Value list of parameters. $resourceType = $resource.CommandElements[0].Value $resourceInstanceName = $resource.CommandElements[1].Value $percent = ($totalCount / ($resourceInstances.Count) * 100) Write-Progress -Status "[$totalCount/$($resourceInstances.Count)] $resourceType - $resourceInstanceName" ` -PercentComplete $percent ` -Activity "Parsing Resources" $currentResourceInfo.Add("ResourceName", $resourceType) $currentResourceInfo.Add("ResourceInstanceName", $resourceInstanceName) $currentResourceInfo.Add("ModuleName", $ModulesToLoad.ModuleName) $currentResourceInfo.Add("ConfigurationName", $configurationName) $adapter = 'Microsoft.DSC/PowerShell' if ($PSVersionTable.PSEdition -ne 'Core') { $adapter = 'Microsoft.Windows/WindowsPowerShell' } $currentResourceInfo.Add("Type", $adapter) # Get a reference to the current resource. $currentResource = $DSCResources | Where-Object -FilterScript { $_.Name -eq $resourceType } # Loop through all the key/pair value foreach ($keyValuePair in $resource.CommandElements[2].KeyValuePairs) { $isVariable = $false $key = $keyValuePair.Item1.Value if ($null -ne $keyValuePair.Item2.PipelineElements) { if ($null -eq $keyValuePair.Item2.PipelineElements.Expression.Value) { if ($null -ne $keyValuePair.Item2.PipelineElements.Expression) { if ($keyValuePair.Item2.PipelineElements.Expression.StaticType.Name -eq 'Object[]') { $value = $keyValuePair.Item2.PipelineElements.Expression.SubExpression $newValue = @() foreach ($expression in $value.Statements.PipelineElements.Expression) { if ($null -ne $expression.Elements) { foreach ($element in $expression.Elements) { if ($null -ne $element.VariablePath) { $newValue += "`$" + $element.VariablePath.ToString() } elseif ($null -ne $element.Value) { $newValue += $element.Value } } } else { $newValue += $expression.Value } } $value = $newValue } else { $value = $keyValuePair.Item2.PipelineElements.Expression.ToString() } } else { $value = $keyValuePair.Item2.PipelineElements.Parent.ToString() } if ($value.GetType().Name -eq 'String' -and $value.StartsWith('$')) { $isVariable = $true } } else { $value = $keyValuePair.Item2.PipelineElements.Expression.Value } } # Retrieve the current property's type based on the resource's schema. $currentPropertyInResourceSchema = $currentResource.Properties | Where-Object -FilterScript { $_.Name -eq $key } $valueType = $currentPropertyInResourceSchema.PropertyType # If the value type is null, then the parameter doesn't exist # in the resource's schema and we throw a warning $propertyFound = $true if ($null -eq $valueType) { $propertyFound = $false Write-Warning "Defined property {$key} was not found in resource {$resourceType}" } if ($propertyFound) { # If the current property is not a CIMInstance if (-not $valueType.StartsWith('[MSFT_') -and ` $valueType -ne '[string]' -and ` $valueType -ne '[string[]]' -and ` -not $isVariable) { # Try to parse the value based on the retrieved type. $scriptBlock = @" `$typeStaticMethods = $valueType | gm -static if (`$typeStaticMethods.Name.Contains('TryParse')) { $valueType::TryParse(`$value, [ref]`$value) | Out-Null } "@ Invoke-Expression -Command $scriptBlock | Out-Null } elseif ($valueType -eq '[String]' -or $isVariable) { if ($isVariable -and [Boolean]::TryParse($value.TrimStart('$'), [ref][Boolean])) { if ($value -eq "`$true") { $value = $true } else { $value = $false } } else { $value = $value } } elseif ($valueType -eq '[string[]]') { # If the property is an array but there's only one value # specified as a string (not specifying the @()) then # we need to create the array. if ($value.GetType().Name -eq 'String' -and -not $value.StartsWith('@(')) { $value = @($value) } } else { $isArray = $false if ($keyValuePair.Item2.ToString().StartsWith('@(')) { $isArray = $true } if ($isArray) { $value = @($value) } } $currentResourceInfo.Add($key, $value) | Out-Null } } $result += $currentResourceInfo $totalCount++ } Write-Progress -Completed ` -Activity "Parsing Resources" return [System.Array]$result } #EndRegion '.\Private\DSC\ConvertToDscObject.ps1' 354 #Region '.\Private\DSC\ExportDscResourceCommand.ps1' -1 function ExportDscResourceCommand { <# .SYNOPSIS Run export operation against DSC resource .DESCRIPTION The function FindDscResourceCommand exports a DSC resource / adapter .PARAMETER ResourceName The resource name to export against. .EXAMPLE PS C:\> FindDscResourceCommand -ResourceName Microsoft.VSCode.Dsc/VSCodeExtension .NOTES For more details, go to module repository at: https://github.com/Gijsreyn/PSDSC. #> [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [System.String] $ResourceName ) begin { $commandData = GetDscCommandIndex -CommandName $MyInvocation.MyCommand.Name $arguments = BuildDscInput -Command $commandData.Command -Operation $commandData.Operation -ResourceName $ResourceName # TODO: we can still make a call to the resource manifest and see if input is required } process { # get the System.Diagnostics.Process object $process = GetNetProcessObject -Arguments $arguments # start the process $inputObject = StartNetProcessObject -Process $process } end { # return $inputObject } } #EndRegion '.\Private\DSC\ExportDscResourceCommand.ps1' 51 #Region '.\Private\DSC\FindDscResourceCommand.ps1' -1 function FindDscResourceCommand { <# .SYNOPSIS Run list operation against DSC resource .DESCRIPTION The function FindDscResourceCommand lists a DSC resource / adapter .PARAMETER ResourceName The resource name to get against. .EXAMPLE PS C:\> FindDscResourceCommand -ResourceName Microsoft.PowerShell/DSC .NOTES For more details, go to module repository at: https://github.com/Gijsreyn/PSDSC. #> [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [System.String] $ResourceName ) begin { $commandData = GetDscCommandIndex -CommandName $MyInvocation.MyCommand.Name $arguments = BuildDscInput -Command $commandData.Command -Operation $commandData.Operation -ResourceName $ResourceName # TODO: we can still make a call to the resource manifest and see if input is required } process { # get the System.Diagnostics.Process object $process = GetNetProcessObject -Arguments $arguments # start the process $inputObject = StartNetProcessObject -Process $process } end { # return $inputObject } } #EndRegion '.\Private\DSC\FindDscResourceCommand.ps1' 51 #Region '.\Private\DSC\GetDscCommandIndex.ps1' -1 function GetDscCommandIndex { <# .SYNOPSIS Get command index data. .DESCRIPTION The function GetDscCommandIndex gets the command index data for commands created. .PARAMETER CommandName The command name to return data for. .PARAMETER IncludeCommand Include the sub command as StringBuilder .EXAMPLE PS C:\> GetDscCommandIndex -CommandName GetDscResourceCommand Returns: Name SubCommand ---- ---------- {[SubCommand, resource get]} .NOTES For more details, go to module repository at: https://github.com/Gijsreyn/PSDSC. #> [CmdletBinding()] [OutputType([pscustomobject])] param ( [Parameter(Mandatory = $false)] [System.String] $CommandName ) # TODO: When version information is available, we can get it using Get-Item and use ResolveDscExe # $resolveExe = ResolveDscExe if (-not $script:version) { $versionProc = GetNetProcessObject $script:version = ((StartNetProcessObject -Process $versionProc).Output.Trim() -split "-") | Select-Object -Last 1 } # TODO: can add without version later $cmdData = @{ 'GetDscResourceCommand' = @{ 'preview.11' = @{ SubCommand = @('resource', 'get') } } 'SetDscResourceCommand' = @{ 'preview.11' = @{ SubCommand = @('resource', 'set') } } 'TestDscResourceCommand' = @{ 'preview.11' = @{ SubCommand = @('resource', 'test') } } 'RemoveDscResourceCommand' = @{ 'preview.11' = @{ SubCommand = @('resource', 'delete') } } 'ExportDscResourceCommand' = @{ 'preview.11' = @{ SubCommand = @('resource', 'export') } } 'FindDscResourceCommand' = @{ 'preview.11' = @{ SubCommand = @('resource', 'list') } } 'GetDscConfigCommand' = @{ 'preview.11' = @{ SubCommand = @('config', 'get') } } 'SetDscConfigCommand' = @{ 'preview.11' = @{ SubCommand = @('config', 'set') } } 'TestDscConfigCommand' = @{ 'preview.11' = @{ SubCommand = @('config', 'test') } } 'RemoveDscConfigCommand' = @{ 'preview.11' = @{ SubCommand = @('config', 'delete') } } } $keyData = $cmdData.$CommandName.$Version if (-not $keyData) { Throw "Command '$CommandName' not implemented for version: $version." } Write-Verbose -Message "Selected data for '$CommandName'" return ([PSCustomObject]@{ Command = $keyData.SubCommand[0] Operation = $keyData.SubCommand[1] }) } #EndRegion '.\Private\DSC\GetDscCommandIndex.ps1' 111 #Region '.\Private\DSC\GetDscConfigCommand.ps1' -1 function GetDscConfigCommand { <# .SYNOPSIS Run get config against DSC resource .DESCRIPTION The function GetDscResourceCommand gets a DSC config .PARAMETER ResourceInput The resource input to provide. Supports JSON, path and PowerShell scripts. .PARAMETER Parameter Optionally, the parameter input to provide. .EXAMPLE PS C:\> GetDscConfigCommand -ResourceInput myconfig.dsc.config.yaml -Parameter myconfig.dsc.config.parameters.yaml .NOTES For more details, go to module repository at: https://github.com/Gijsreyn/PSDSC. #> [CmdletBinding()] param ( [Parameter(Mandatory = $false, ValueFromPipeline = $true)] [AllowNull()] [object] $ResourceInput, [Parameter(Mandatory = $false)] [AllowNull()] [object] $Parameter ) begin { # collect the command from index $commandData = GetDscCommandIndex -CommandName $MyInvocation.MyCommand.Name # build the input string for arguments $arguments = BuildDscInput -Command $commandData.Command -Operation $commandData.Operation -ResourceInput $ResourceInput -Parameter $Parameter } process { # get the System.Diagnostics.Process object $process = GetNetProcessObject -Arguments $arguments # start the process $inputObject = StartNetProcessObject -Process $process } end { # return $inputObject } } #EndRegion '.\Private\DSC\GetDscConfigCommand.ps1' 60 #Region '.\Private\DSC\GetDscRequiredKey.ps1' -1 function GetDscRequiredKey { <# .SYNOPSIS Get the required DSC key(s) from Resource Manifest(s). .DESCRIPTION The function GetDscRequiredKey gets all the required keys located in 'dsc.exe' installation location. It reads all resource manifest to build the object and captures the resource input if possible. .PARAMETER Path The path to the 'dsc.exe' installation location. .PARAMETER BuildHashTable A switch parameter to indicate if the output should be a hashtable. .EXAMPLE PS C:\> $resolvedPath = Split-Path (Get-Command 'dsc').Source PS C:\> GetDscRequiredKey -Path $resolvedPath Returns ResourceManifest object .NOTES For more details, go to module repository at: https://github.com/Gijsreyn/PSDSC. #> [CmdletBinding()] param ( [Parameter(Mandatory = $false)] [System.String] $Path, [Parameter()] [System.Management.Automation.SwitchParameter]$BuildHashTable ) if (-not $PSBoundParameters.ContainsKey('Path')) { $Path = Split-Path (ResolveDscExe) -Parent } $mFiles = Get-ChildItem -Path $Path -Depth 1 -Filter "*.dsc.resource.json" if ($mFiles) { $p = @{ ResourceManifest = $mFiles BuildHashTable = $BuildHashTable.IsPresent } $inputObject = ReadDscRequiredKey @p } $inputObject } #EndRegion '.\Private\DSC\GetDscRequiredKey.ps1' 56 #Region '.\Private\DSC\GetDscResourceCommand.ps1' -1 function GetDscResourceCommand { <# .SYNOPSIS Run get operation against DSC resource .DESCRIPTION The function GetDscResourceCommand gets a DSC resource .PARAMETER ResourceName The resource name to get against. .PARAMETER ResourceInput The resource input to provide. Supports JSON, path and PowerShell scripts. .EXAMPLE PS C:\> GetDscResourceCommand -ResourceName Microsoft.Windows/Registry -ResourceInput @{keyPath = 'HKCU\Microsoft'} .NOTES For more details, go to module repository at: https://github.com/Gijsreyn/PSDSC. #> [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [System.String] $ResourceName, [Parameter(Mandatory = $false)] [AllowNull()] [object] $ResourceInput ) begin { $commandData = GetDscCommandIndex -CommandName $MyInvocation.MyCommand.Name $arguments = BuildDscInput -Command $commandData.Command -Operation $commandData.Operation -ResourceInput $ResourceInput -ResourceName $ResourceName # TODO: we can still make a call to the resource manifest and see if input is required } process { # get the System.Diagnostics.Process object $process = GetNetProcessObject -Arguments $arguments # start the process $inputObject = StartNetProcessObject -Process $process } end { # return $inputObject } } #EndRegion '.\Private\DSC\GetDscResourceCommand.ps1' 58 #Region '.\Private\DSC\GetDscResourceDetail.ps1' -1 function GetDscResourceDetail { <# .SYNOPSIS Get the resource detail from *.dsc.resource.json file. .DESCRIPTION The function GetDscResourceDetail gets the resource details from the *.dsc.resource.json file(s) and returns the type of the resource. .PARAMETER Path The path that locates 'dsc.exe' file. If not provided, the function will resolve the path to 'dsc.exe'. .PARAMETER Exclude The hashtable that contains the key-value pair to exclude the JSON content. The key is the property name and the value is the property value. For example: @{kind = 'Group'} excludes all resource(s) that have the kind of 'Group'. .EXAMPLE PS C:\> GetDscResourceDetail Returns all resources from the *.dsc.resource.json file(s). .EXAMPLE PS C:\> GetDscResourceDetail -Exclude @{kind = 'Adapter'} Returns all resources from the *.dsc.resource.json file(s) excluding the resources that have the kind of 'Adapter'. .OUTPUTS System.String .NOTES For more details, go to module repository at: https://github.com/Gijsreyn/PSDSC. #> [CmdletBinding()] [OutputType([System.Collections.Generic.List[System.String]])] [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingPositionalParameters', '', Justification = 'PowerShell 7+.')] param ( [Parameter(Mandatory = $false)] [System.String] $Path = (ResolveDscExe), [Parameter(Mandatory = $false)] [System.Collections.Hashtable] [AllowNull()] $Exclude ) if (-not (Test-Path $Path -ErrorAction SilentlyContinue)) { Throw "Path does not exist: $Path. Please ensure the path is correct to 'dsc.exe'." } # Get the current directory 'dsc.exe' sits in $current = Split-Path $Path -Parent # Get all JSON files that match the pattern *.dsc.resource.json $jsonFiles = Get-ChildItem -Path $current -Filter '*.dsc.resource.json' # Initialize an array to hold the results $results = [System.Collections.Generic.List[System.String]]::new() foreach ($file in $jsonFiles) { try { # Read the content of the JSON file $jsonContent = Get-Content -Path $file.FullName -Raw | ConvertFrom-Json if (-not ([string]::IsNullOrEmpty($Exclude))) { if ($jsonContent.$($Exclude.Keys) -eq $Exclude.Values) { # Set to null as we are not adding any excludes $jsonContent = $null } } # Filter the JSON content based on type if ($null -ne $jsonContent.type -and $jsonContent.kind -ne 'Adapter') { $results.Add($jsonContent.type) } if ($jsonContent.kind -eq 'Adapter' -and $jsonContent.type -eq 'Microsoft.DSC/PowerShell') { if ($IsLinux -or $IsMacOS) { $cacheFilePath = Join-Path $env:HOME ".dsc" "PSAdapterCache.json" } else { $cacheFilePath = Join-Path $env:LocalAppData "dsc" "PSAdapterCache.json" } if (Test-Path $cacheFilePath) { try { $cacheContent = Get-Content -Path $cacheFilePath -Raw | ConvertFrom-Json $cacheContent.ResourceCache.Type | ForEach-Object { $results.Add($_) } } catch { Write-Debug "Failed to read or parse cache file: $cacheFilePath. Exception: $_" } } else { Write-Debug -Message ("'PSAdapterCache.json' file not found at: $cacheFilePath. Please run './powershell.resource.ps1 List' to generate the cache file found at '{0}'." -f (Join-Path $current 'psDscAdapter')) } } if ($jsonContent.kind -eq 'Adapter' -and $jsonContent.type -eq 'Microsoft.Windows/WindowsPowerShell') { if ($PSVersionTable.PSVersion.Major -le 5) { Join-Path $env:LocalAppData "dsc\WindowsPSAdapterCache.json" } else { Join-Path $env:HOME ".dsc" "PSAdapterCache.json" } if (Test-Path $cacheFilePath) { $cacheContent = Get-Content -Path $cacheFilePath -Raw | ConvertFrom-Json $cacheContent.ResourceCache.Type | ForEach-Object { $results.Add($_) } } else { Write-Debug -Message ("'WindowsPSAdapterCache' file not found at: $cacheFilePath. Please run './powershell.resource.ps1 List' to generate the cache file found at '{0}'." -f (Join-Path $current 'psDscAdapter')) } } } catch { Write-Debug "Failed to process file: $($file.FullName). Exception: $_" } } # Return the filtered results return $results } #EndRegion '.\Private\DSC\GetDscResourceDetail.ps1' 149 #Region '.\Private\DSC\GetDscVersion.ps1' -1 function GetDscVersion { <# .SYNOPSIS Get DSC version available. .DESCRIPTION The function GetDscVersion gets the DSC version available locally or on GitHub. .PARAMETER UseGitHub Switch to search for using GitHub. .EXAMPLE PS C:\> GetDscVersion -UseGitHub .NOTES For more details, go to module repository at: https://github.com/Gijsreyn/PSDSC. #> [CmdletBinding()] [OutputType([System.String])] param ( [Parameter()] [System.Management.Automation.SwitchParameter] $UseGitHub ) if ($UseGitHub.IsPresent) { if ($null -eq $script:availableDscVersions) { try { $script:availableDscVersions = GetGithubReleaseVersion -Organization 'PowerShell' -Repository 'DSC' -ErrorAction Stop } catch { $script:availableDscVersions = @() Write-Verbose -Message "Failed to retrieve all available versions with error: $_" } } return $script:availableDscVersions } if (TestDsc) { # do it like this to avoid circular dependencies because of WinGet $process = GetNetProcessObject -Arguments '--version' -ExePath (Get-Command -Name 'dsc.exe').Source $version = ((StartNetProcessObject -Process $process).Output -split "-")[-1] return $version } else { return "" } } #EndRegion '.\Private\DSC\GetDscVersion.ps1' 58 #Region '.\Private\DSC\ReadDscPsAdapterSchema.ps1' -1 function ReadDscPsAdapterSchema { <# .SYNOPSIS Read the Desired State Configuration PowerShell adapter cache. .DESCRIPTION The function ReadDscPsAdapterSchema reads the PowerShell adapter cache both for Windows PowerShell and PowerShell. It builds upon the work of Andrew Menagarishvili, one of the core members on the DSC project, and reads the required files generated by 'powershell.resource.ps1'. .PARAMETER ReturnTypeInfo Switch parameter to only return the type name(s). .PARAMETER BuildHashTable A switch parameter to indicate if the output should be a hashtable. .EXAMPLE PS C:\> ReadDscAdapterSchema Returns: Type Description Version ResourceInput ---- ----------- ------- ------------- .NOTES For more details, go to module repository at: https://github.com/Gijsreyn/PSDSC. #> [CmdletBinding()] [OutputType([System.Array])] param ( [System.Management.Automation.SwitchParameter] $ReturnTypeInfo, [Parameter(Mandatory = $false)] [System.Management.Automation.SwitchParameter] $BuildHashTable ) begin { Write-Verbose -Message ("Starting: {0}" -f $MyInvocation.MyCommand.Name) } process { $cacheFilePath = if ($IsWindows) { # PS 6+ on Windows Join-Path -Path $env:LocalAppData "dsc\PSAdapterCache.json" } else { # either WinPS or PS 6+ on Linux/Mac if ($PSVersionTable.PSVersion.Major -le 5) { Join-Path -Path $env:LocalAppData "dsc\WindowsPSAdapterCache.json" } else { Join-Path -Path $env:HOME ".dsc" "PSAdapterCache.json" } } if (-not (Test-Path $cacheFilePath)) { return } Write-Verbose -Message ("Retrieving cache content from: '{0}'" -f $cacheFilePath) $cacheContent = Get-Content $cacheFilePath | ConvertFrom-Json $objectBag = foreach ($resource in $cacheContent.ResourceCache) { # create manifest object $resourceObject = [PSCustomObject]@{ Type = $resource.type Description = $resource.DscResourceInfo.FriendlyName Version = $resource.DscResourceInfo.Version ResourceInput = $null } # add the example code $pParams = @{ properties = $resource.DscResourceInfo.Properties BuildHashTable = $BuildHashTable.IsPresent } $exampleCode = @((ReadDscPsAdapterSchemaProperty @pParams)) $resourceObject.resourceInput = $exampleCode $resourceObject } } end { Write-Verbose -Message ("Ended: {0}" -f $MyInvocation.MyCommand.Name) if ($ReturnTypeInfo) { return ($objectBag.Type) } return $objectBag } } #EndRegion '.\Private\DSC\ReadDscPsAdapterSchema.ps1' 105 #Region '.\Private\DSC\ReadDscPsAdapterSchemaProperty.ps1' -1 function ReadDscPsAdapterSchemaProperty { <# .SYNOPSIS Reads the schema properties of a DSC PowerShell adapter. .DESCRIPTION The function ReadDscPsAdapterSchemaProperty processes the properties of a DSC PowerShell adapter and returns them in a specified format. It can return the properties as a JSON string or as a hash table string. .PARAMETER Properties The properties of the DSC PowerShell adapter to be processed. This parameter is mandatory. .PARAMETER BuildHashTable A switch parameter that indicates whether to build the output as a hash table string. If not specified, the output will be in JSON format. .OUTPUTS System.Collections.Generic.List[System.String] Returns a list of strings containing the processed properties in the specified format. .EXAMPLE PS C:\> $properties = @( [PSCustomObject]@{ Name = 'Property1'; PropertyType = '[string]'; IsMandatory = $true }, [PSCustomObject]@{ Name = 'Property2'; PropertyType = '[int]'; IsMandatory = $false } ) PS C:\> ReadDscPsAdapterSchemaProperty -Properties $properties Returns: This example processes the given properties and returns them in JSON format. .EXAMPLE PS C:\> $properties = @( [PSCustomObject]@{ Name = 'Property1'; PropertyType = '[string]'; IsMandatory = $true }, [PSCustomObject]@{ Name = 'Property2'; PropertyType = '[int]'; IsMandatory = $false } ) PS C:\> ReadDscPsAdapterSchemaProperty -Properties $properties -BuildHashTable This example processes the given properties and returns them as a hash table string. .NOTES For more details, go to module repository at: https://github.com/Gijsreyn/PSDSC. #> [CmdletBinding()] [OutputType([System.Collections.Generic.List[System.String]])] param ( [Parameter(Mandatory = $true)] [PSObject] $Properties, [Parameter(Mandatory = $false)] [System.Management.Automation.SwitchParameter] $BuildHashTable ) $resourceInput = [System.Collections.Generic.List[System.String]]::new() $inputObject = [ordered]@{} $mandatory = [ordered]@{} foreach ($property in $Properties) { $typeName = $property.PropertyType.Split(".")[-1].TrimEnd("]").Replace("[", "") $inputObject[$property.Name] = "<$typeName>" if ($property.IsMandatory -eq $true) { $mandatory[$property.Name] = "<$typeName>" } } if ($BuildHashTable.IsPresent) { $resourceInput.Add((ConvertToHashString -HashTable $mandatory)) $resourceInput.Add((ConvertToHashString -HashTable $inputObject)) } else { $resourceInput.Add(($mandatory | ConvertTo-Json -Depth 10 -Compress)) $resourceInput.Add(($inputObject | ConvertTo-Json -Depth 10 -Compress)) } return $resourceInput } #EndRegion '.\Private\DSC\ReadDscPsAdapterSchemaProperty.ps1' 82 #Region '.\Private\DSC\ReadDscRequiredKey.ps1' -1 function ReadDscRequiredKey { <# .SYNOPSIS Read DSC keys from Resource Manifest. .DESCRIPTION The function ReadDscRequiredKey reads the DSC keys from Resource Manifest. .PARAMETER ResourceManifest The resource manifest to read. .PARAMETER BuildHashTable A switch parameter to indicate if the output should be a hashtable. .EXAMPLE PS C:\> ReadDscRequiredKey -Path "$env:ProgramFiles\DSC\registry.dsc.resource.json" Returns type description version resourceInput ---- ----------- ------- ------------- Microsoft.Windows/Registry Manage Windows Registry keys and values 0.1.0 .NOTES For more details, go to module repository at: https://github.com/Gijsreyn/PSDSC. #> [CmdletBinding()] [OutputType([System.Collections.Generic.List[ResourceManifest]])] param ( [Parameter(Mandatory = $false)] [System.IO.FileInfo[]] $ResourceManifest, [Parameter(Mandatory = $false)] [System.Management.Automation.SwitchParameter] $BuildHashTable ) begin { Write-Verbose -Message ("Starting: {0}" -f $MyInvocation.MyCommand.Name) } process { $objectBag = [System.Collections.Generic.List[ResourceManifest]]::new() foreach ($manifest in $ResourceManifest) { try { Write-Verbose -Message ("Reading file: '{0}'" -f $manifest.Name) $ctx = Get-Content $manifest | ConvertFrom-Json -ErrorAction SilentlyContinue # expect to be command-based if (-not $ctx.kind -and $ctx.schema) { # initialize $inputObject = [ResourceManifest]::new($ctx.type, $ctx.description, $ctx.version) $schemaParams = @{ Schema = $ctx.schema BuildHashTable = $BuildHashTable.IsPresent } # grab both embedded and schema key using ReadDscSchema $resourceInput = ReadDscSchema @schemaParams $inputObject.resourceInput = $resourceInput Write-Verbose -Message "Adding '$($inputObject.type)'" $objectBag.Add($inputObject) } if ($ctx.kind -eq 'Adapter' -and $ctx.type -in @('Microsoft.DSC/PowerShell', 'Microsoft.Windows/WindowsPowerShell')) { $cacheRefresh = ReadDscPsAdapterSchema -BuildHashTable:$BuildHashTable.IsPresent if ($cacheRefresh) { $cacheRefresh | ForEach-Object { $m = [ResourceManifest]::new($_.Type, $_.Description, $_.Version, $_.ResourceInput) $objectBag.Add($m) } } } } catch { # change to warning to debug argumentcompleter Write-Debug -Message ("'{0}' file could not be converted to JSON object. Error: {1}" -f $manifest.Name, $PSItem.Exception.Message) } } } end { Write-Verbose -Message ("Ended: {0}" -f $MyInvocation.MyCommand.Name) # return $objectBag } } #EndRegion '.\Private\DSC\ReadDscRequiredKey.ps1' 105 #Region '.\Private\DSC\ReadDscSchema.ps1' -1 function ReadDscSchema { <# .SYNOPSIS Read DSC schema information. .DESCRIPTION The function ReadDscSchema reads the DSC schema information and builds the resource input required. .PARAMETER Schema The schema information to read. .PARAMETER BuildHashTable A switch parameter to indicate if the output should be a hashtable. .EXAMPLE PS C:\> $ctx = (Get-Content "$env:ProgramFiles\DSC\registry.dsc.resource.json" | ConvertFrom-Json) PS C:\> ReadDscSchema -Schema $ctx.schema .NOTES For more details, go to module repository at: https://github.com/Gijsreyn/PSDSC. #> [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [psobject] $Schema, [Parameter(Mandatory = $false)] [System.Management.Automation.SwitchParameter] $BuildHashTable ) begin { Write-Verbose -Message ("Starting: {0}" -f $MyInvocation.MyCommand.Name) } process { $exampleCode = @() $schemaParams = @{ schemaObject = $null BuildHashTable = $BuildHashTable.IsPresent } if ($Schema.command) { # use resource name because WinGet does not allow direct resource to be called $resourceName = $ctx.type $process = GetNetProcessObject -Arguments "resource schema -r $resourceName" $out = StartNetProcessObject -Process $process if ($out.ExitCode -eq 0) { $schemaParams.schemaObject = $out.Output | ConvertFrom-Json $exampleCode = ReadDscSchemaProperty @schemaParams } } if ($Schema.embedded) { $schemaParams.schemaObject = $Schema.embedded $exampleCode = ReadDscSchemaProperty @schemaParams } } end { Write-Verbose -Message ("Ended: {0}" -f $MyInvocation.MyCommand.Name) # return return $exampleCode } } #EndRegion '.\Private\DSC\ReadDscSchema.ps1' 76 #Region '.\Private\DSC\ReadDscSchemaProperty.ps1' -1 function ReadDscSchemaProperty { <# .SYNOPSIS Read DSC schema property information. .DESCRIPTION The function ReadDscSchemaProperty reads the DSC schema property information and builds the resource input required. .PARAMETER SchemaObject The schema object to pass in. .PARAMETER BuildHashTable A switch parameter to indicate if the output should be a hashtable. .EXAMPLE PS C:\> $ctx = (Get-Content "$env:ProgramFiles\DSC\registry.dsc.resource.json" | ConvertFrom-Json) PS C:\> $fullExePath = [System.String]::Concat("$(Split-Path -Path $exePath)\", $ctx.schema.command.executable, '.exe') PS C:\> $process = GetNetProcessObject -Arguments "$($ctx.schema.command.args)" -ExePath $fullExePath PS C:\> $schema = (StartNetProcessObject -Process $process).Output | ConvertFrom-Json PS C:\> ReadDscSchemaProperty -SchemaObject $schema Returns: {"keyPath":"<keyPath>"} {"valueName":"<valueName>","_exist":"<_exist>","keyPath":"<keyPath>","valueData":"<valueData>","_metadata":"<_metadata>"} .NOTES For more details, go to module repository at: https://github.com/Gijsreyn/PSDSC. #> [CmdletBinding()] [OutputType([System.String])] param ( [Parameter(Mandatory = $true)] [PSCustomObject]$SchemaObject, [Parameter()] [System.Management.Automation.SwitchParameter]$BuildHashTable ) $exampleCode = @() # Add the required keys as example $exampleCode += $SchemaObject.required.ForEach({ if ($BuildHashTable.IsPresent) { ConvertToHashString -HashTable ([ordered]@{ $_ = "<$($_)>" }) } else { [ordered]@{ $_ = "<$($_)>" } | ConvertTo-Json -Depth 10 -Compress } }) # Go through optional keys as example $props = $SchemaObject.properties.PSObject.Properties | Where-Object { $_.MemberType -eq 'NoteProperty' } | Select-Object -ExpandProperty Name $hash = [ordered]@{} foreach ($prop in $props) { $hash.Add($prop, "<$prop>") } if ($BuildHashTable.IsPresent) { $exampleCode += ConvertToHashString -HashTable $hash } else { if ($hash.Count -ne 0) { $exampleCode += ($hash | ConvertTo-Json -Compress) } else { $exampleCode += @() } } return $exampleCode } #EndRegion '.\Private\DSC\ReadDscSchemaProperty.ps1' 81 #Region '.\Private\DSC\RemoveDscConfigCommand.ps1' -1 function RemoveDscConfigCommand { <# .SYNOPSIS Run delete config against DSC resource .DESCRIPTION The function RemoveDscResourceCommand deletes a DSC config .PARAMETER ResourceInput The resource input to provide. Supports JSON, path and PowerShell scripts. .PARAMETER Parameter Optionally, the parameter input to provide. .EXAMPLE PS C:\> RemoveDscConfigCommand -ResourceInput myconfig.dsc.config.yaml -Parameter myconfig.dsc.config.parameters.yaml .NOTES For more details, go to module repository at: https://github.com/Gijsreyn/PSDSC. #> [CmdletBinding()] param ( [Parameter(Mandatory = $false, ValueFromPipeline = $true)] [AllowNull()] [object] $ResourceInput, [Parameter(Mandatory = $false)] [AllowNull()] [object] $Parameter ) begin { # collect the command from index $commandData = GetDscCommandIndex -CommandName $MyInvocation.MyCommand.Name # build the input string for arguments $arguments = BuildDscInput -Command $commandData.Command -Operation $commandData.Operation -ResourceInput $ResourceInput -Parameter $Parameter } process { # get the System.Diagnostics.Process object $process = GetNetProcessObject -Arguments $arguments # start the process $inputObject = StartNetProcessObject -Process $process } end { # return $inputObject } } #EndRegion '.\Private\DSC\RemoveDscConfigCommand.ps1' 60 #Region '.\Private\DSC\RemoveDscResourceCommand.ps1' -1 function RemoveDscResourceCommand { <# .SYNOPSIS Run delete operation against DSC resource .DESCRIPTION The function RemoveDscResourceCommand deletes a DSC resource .PARAMETER ResourceName The resource name to delete against. .PARAMETER ResourceInput The resource input to provide. Supports JSON, path and PowerShell scripts. .EXAMPLE PS C:\> RemoveDscResourceCommand -ResourceName Microsoft.Windows/Registry -ResourceInput @{keyPath = 'HKCU\1'} .NOTES For more details, go to module repository at: https://github.com/Gijsreyn/PSDSC. #> [CmdletBinding(SupportsShouldProcess = $true)] param ( [Parameter(Mandatory = $true)] [System.String] $ResourceName, [Parameter(Mandatory = $false)] [AllowNull()] [object] $ResourceInput ) begin { $commandData = GetDscCommandIndex -CommandName $MyInvocation.MyCommand.Name $arguments = BuildDscInput -Command $commandData.Command -Operation $commandData.Operation -ResourceInput $ResourceInput -ResourceName $ResourceName # TODO: we can still make a call to the resource manifest and see if input is required } process { # get the System.Diagnostics.Process object $process = GetNetProcessObject -Arguments $arguments # start the process if ($PSCmdlet.ShouldProcess(("'{0}' with input: [{1}]" -f $ResourceName, $resourceInput))) { $inputObject = StartNetProcessObject -Process $process } } end { # return $inputObject } } #EndRegion '.\Private\DSC\RemoveDscResourceCommand.ps1' 62 #Region '.\Private\DSC\ResolveDscExe.ps1' -1 function ResolveDscExe { <# .SYNOPSIS Resolve the location of 'dsc.exe'. .DESCRIPTION The function ResolveDscExe searches for: * DSC_RESOURCE_PATH environment variable * PATH environment variable * Custom path specified using -Path .PARAMETER Path Optional parameter to directly specify 'dsc.exe' .PARAMETER Scope The scope to search for e.g. Machine, User, or Process .EXAMPLE PS C:\> $Exe = ResolveDscExe .NOTES For more details, go to module repository at: https://github.com/Gijsreyn/PSDSC. #> [OutputType([System.String])] [CmdletBinding()] [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingPositionalParameters', '', Justification = 'PowerShell module is 7+).')] Param ( [Parameter(Mandatory = $false)] [AllowNull()] [System.IO.FileInfo] $Path, [Parameter(Mandatory = $false)] [ValidateSet('Machine', 'User', 'Process')] [System.String] $Scope = 'Machine' ) if ($PSBoundParameters.ContainsKey('Path') -and ($Path.Name -ne 'dsc.exe')) { Throw "No file found at path '$Path'. Please specify the file path to 'dsc.exe'" } if ($IsWindows) { $dscResourceVar = GetEnvironmentVariable -Name 'DSC_RESOURCE_PATH' -Scope $Scope -Expanded if ($dscResourceVar.Length -ne 0) { $dscExePath = Join-Path -Path $dscResourceVar -ChildPath 'dsc.exe' if (Test-Path $dscExePath) { Write-Verbose -Message "Returning DSC executable from DSC_RESOURCE_PATH environment variable: $dscExePath." return $dscExePath } } $elevated = TestAdministrator $dscExePath = if ($elevated) { Join-Path $env:ProgramFiles 'dsc' 'dsc.exe' } else { Join-Path $env:LOCALAPPDATA 'dsc' 'dsc.exe' } if (Test-Path $dscexePath) { Write-Verbose -Message "Returning DSC executable from default installation path: $dscExePath." return $dscExePath } if (TestWinGetModule) { # TODO: life is difficult with WinGet $version = (GetDscVersion) -replace "preview.", "" $architecture = ([System.Runtime.InteropServices.RuntimeInformation]::OSArchitecture).ToString().ToLower() $dscExePath = Join-Path $env:ProgramFiles 'WindowsApps' "Microsoft.DesiredStateConfiguration-Preview_3.0.$version.0_$architecture`__8wekyb3d8bbwe" 'dsc.exe' if (Test-Path $dscExePath) { Write-Verbose -Message "Returning DSC executable from WindowsApps: $dscExePath." return $dscExePath } } $dscExePath = (Get-Command -Name 'dsc.exe' -ErrorAction SilentlyContinue).Source if ($dscExePath) { Write-Verbose -Message "Returning DSC executable from PATH: $dscExePath." return $dscExePath } else { Throw "Could not locate 'dsc.exe'. Please make sure it can be found through the PATH or DSC_RESOURCE_PATH environment variable." } } elseif ($IsLinux) { $dscExePath = Join-Path '/opt/' 'microsoft' 'dsc' 'dsc.exe' if (Test-Path $dscExePath) { Write-Verbose -Message "Returning DSC executable from default installation path: $dscExePath." return $dscExePath } $dscExePath = (Get-Command -Name 'dsc.exe' -ErrorAction SilentlyContinue).Source if ($dscExePath) { Write-Verbose -Message "Returning DSC executable from PATH: $dscExePath." return $dscExePath } else { Throw "Could not locate 'dsc.exe'. Please make sure it can be found through the PATH or DSC_RESOURCE_PATH environment variable." } } elseif ($IsMacOs) { } } #EndRegion '.\Private\DSC\ResolveDscExe.ps1' 127 #Region '.\Private\DSC\SetDscConfigCommand.ps1' -1 function SetDscConfigCommand { <# .SYNOPSIS Run set config against DSC resource .DESCRIPTION The function SetDscResourceCommand sets a DSC config .PARAMETER ResourceInput The resource input to provide. Supports JSON, path and PowerShell scripts. .PARAMETER Parameter Optionally, the parameter input to provide. .EXAMPLE PS C:\> SetDscConfigCommand -ResourceInput myconfig.dsc.config.yaml -Parameter myconfig.dsc.config.parameters.yaml .NOTES For more details, go to module repository at: https://github.com/Gijsreyn/PSDSC. #> [CmdletBinding()] param ( [Parameter(Mandatory = $false, ValueFromPipeline = $true)] [AllowNull()] [object] $ResourceInput, [Parameter(Mandatory = $false)] [AllowNull()] [object] $Parameter ) begin { # collect the command from index $commandData = GetDscCommandIndex -CommandName $MyInvocation.MyCommand.Name # build the input string for arguments $arguments = BuildDscInput -Command $commandData.Command -Operation $commandData.Operation -ResourceInput $ResourceInput -Parameter $Parameter } process { # get the System.Diagnostics.Process object $process = GetNetProcessObject -Arguments $arguments # start the process $inputObject = StartNetProcessObject -Process $process } end { # return $inputObject } } #EndRegion '.\Private\DSC\SetDscConfigCommand.ps1' 60 #Region '.\Private\DSC\SetDscResourceCommand.ps1' -1 function SetDscResourceCommand { <# .SYNOPSIS Run set operation against DSC resource .DESCRIPTION The function SetDscResourceCommand sets a DSC resource .PARAMETER ResourceName The resource name to set against. .PARAMETER ResourceInput The resource input to provide. Supports JSON, path and PowerShell scripts. .EXAMPLE PS C:\> SetDscResourceCommand -ResourceName Microsoft.Windows/Registry -ResourceInput @{keyPath = 'HKCU\Microsoft'} .NOTES For more details, go to module repository at: https://github.com/Gijsreyn/PSDSC. #> [CmdletBinding(SupportsShouldProcess = $true)] param ( [Parameter(Mandatory = $true)] [System.String] $ResourceName, [Parameter(Mandatory = $false)] [AllowNull()] [object] $ResourceInput ) begin { $commandData = GetDscCommandIndex -CommandName $MyInvocation.MyCommand.Name $arguments = BuildDscInput -Command $commandData.Command -Operation $commandData.Operation -ResourceInput $ResourceInput -ResourceName $ResourceName # TODO: we can still make a call to the resource manifest and see if input is required } process { # get the System.Diagnostics.Process object $process = GetNetProcessObject -Arguments $arguments # start the process if ($PSCmdlet.ShouldProcess(("'{0}' with input: [{1}]" -f $ResourceName, $resourceInput))) { $inputObject = StartNetProcessObject -Process $process } } end { # return $inputObject } } #EndRegion '.\Private\DSC\SetDscResourceCommand.ps1' 62 #Region '.\Private\DSC\TestDsc.ps1' -1 function TestDsc { <# .SYNOPSIS Check if dsc is installed. .DESCRIPTION Check if dsc is installed. Returns either true or false. .EXAMPLE PS C:\> TestDsc .NOTES For more details, go to module repository at: https://github.com/Gijsreyn/PSDSC. #> [CmdletBinding()] [OutputType([System.Boolean])] param () $dsc = (Get-Command dsc -ErrorAction SilentlyContinue) if ($dsc) { $true } else { $false } } #EndRegion '.\Private\DSC\TestDsc.ps1' 30 #Region '.\Private\DSC\TestDscConfigCommand.ps1' -1 function TestDscConfigCommand { <# .SYNOPSIS Run test config against DSC resource .DESCRIPTION The function TestDscResourceCommand tests a DSC config .PARAMETER ResourceInput The resource input to provide. Supports JSON, path and PowerShell scripts. .PARAMETER Parameter Optionally, the parameter input to provide. .EXAMPLE PS C:\> TestDscConfigCommand -ResourceInput myconfig.dsc.config.yaml -Parameter myconfig.dsc.config.parameters.yaml .NOTES For more details, go to module repository at: https://github.com/Gijsreyn/PSDSC. #> [CmdletBinding()] param ( [Parameter(Mandatory = $false, ValueFromPipeline = $true)] [AllowNull()] [object] $ResourceInput, [Parameter(Mandatory = $false)] [AllowNull()] [object] $Parameter ) begin { # collect the command from index $commandData = GetDscCommandIndex -CommandName $MyInvocation.MyCommand.Name # build the input string for arguments $arguments = BuildDscInput -Command $commandData.Command -Operation $commandData.Operation -ResourceInput $ResourceInput -Parameter $Parameter } process { # get the System.Diagnostics.Process object $process = GetNetProcessObject -Arguments $arguments # start the process $inputObject = StartNetProcessObject -Process $process } end { # return $inputObject } } #EndRegion '.\Private\DSC\TestDscConfigCommand.ps1' 60 #Region '.\Private\DSC\TestDscResourceCommand.ps1' -1 function TestDscResourceCommand { <# .SYNOPSIS Run test operation against DSC resource .DESCRIPTION The function TestDscResourceCommand tests a DSC resource .PARAMETER ResourceName The resource name to test against. .PARAMETER ResourceInput The resource input to provide. Supports JSON, path and PowerShell scripts. .EXAMPLE PS C:\> TestDscResourceCommand -ResourceName Microsoft.Windows/Registry -ResourceInput @{keyPath = 'HKCU\Microsoft'} .NOTES For more details, go to module repository at: https://github.com/Gijsreyn/PSDSC. #> [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [System.String] $ResourceName, [Parameter(Mandatory = $false)] [AllowNull()] [object] $ResourceInput ) begin { $commandData = GetDscCommandIndex -CommandName $MyInvocation.MyCommand.Name $arguments = BuildDscInput -Command $commandData.Command -Operation $commandData.Operation -ResourceInput $ResourceInput -ResourceName $ResourceName # TODO: we can still make a call to the resource manifest and see if input is required } process { # get the System.Diagnostics.Process object $process = GetNetProcessObject -Arguments $arguments # start the process $inputObject = StartNetProcessObject -Process $process } end { # return $inputObject } } #EndRegion '.\Private\DSC\TestDscResourceCommand.ps1' 59 #Region '.\Private\GitHub\GetGitHubReleaseVersion.ps1' -1 function GetGithubReleaseVersion { <# .SYNOPSIS Get GitHub release version using API. .DESCRIPTION The function GetGithubReleaseVersion gets a GitHub release using the API. .PARAMETER Organization The organization name to look for. .PARAMETER Repository The repository name to look for in the organization. .PARAMETER Latest Switch to grab latest version if available. .EXAMPLE PS C:\> GetGitHubReleaseVersion -Organization PowerShell -Repository DSC Returns a list of available versions for DSC repository .NOTES For more details, go to module repository at: https://github.com/Gijsreyn/PSDSC. #> [CmdletBinding()] [OutputType([System.String])] param( [Parameter(Mandatory = $true)] [System.String] $Organization, [Parameter(Mandatory = $true)] [System.String] $Repository, [System.Management.Automation.SwitchParameter] $Latest ) $Url = 'https://api.github.com/repos/{0}/{1}/releases' -f $Organization, $Repository if ($Latest.IsPresent) { $Url = '{0}/latest' -f $Url } try { $Versions = Invoke-RestMethod -Uri $Url -ErrorAction 'Stop' # TODO: when versions become version, change to system.version return ($versions.tag_name | Foreach-Object -Process { $_.TrimStart("v") -as [System.String] }) } catch { Write-Error -Message "Could not get version of $Organization/$Repository from GitHub. $_" -Category ObjectNotFound } } #EndRegion '.\Private\GitHub\GetGitHubReleaseVersion.ps1' 57 #Region '.\Private\Path\AddToPath.ps1' -1 function AddToPath { <# .SYNOPSIS Adds the specified path to PATH env variable .DESCRIPTION Assumes that paths on PATH are separated by `;` .PARAMETER Path path to add to the list. .PARAMETER Persistent save the variable in machine scope. .PARAMETER First preppend the value instead of appending. .PARAMETER User save to user scope. use only when needed. .EXAMPLE PS C:\> $exePath | AddToPath -Persistent:$true .NOTES Site: https://github.com/qbikez/ps-pathutils/tree/master #> [CmdletBinding()] [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidMultipleTypeAttributes', '', Justification = 'Not my store.')] param ( [Parameter(ValueFromPipeline = $true, mandatory = $true)] [System.String] $Path, [Alias("p")] [System.Management.Automation.SwitchParameter] $Persistent, [System.Management.Automation.SwitchParameter] $First, [System.Management.Automation.SwitchParameter] [System.Boolean] $User ) process { if ($null -eq $path) { throw [System.ArgumentNullException]"path" } if ($User) { $p = GetPathEnv -User } elseif ($persistent) { $p = GetPathEnv -Machine } else { $p = GetPathEnv -Current } $p = $p | ForEach-Object { $_.trimend("\") } $p = @($p) $paths = @($path) $paths | ForEach-Object { $path = $_.trimend("\") Write-Verbose "adding $path to PATH" if ($first) { if ($p.length -eq 0 -or $p[0] -ine $path) { $p = @($path) + $p } } else { if ($path -inotin $p) { $p += $path } } } if ($User) { Write-Verbose "saving user PATH and adding to current proc" [System.Environment]::SetEnvironmentVariable("PATH", [string]::Join(";", $p), [System.EnvironmentVariableTarget]::User); #add also to process PATH AddToPath $path -persistent:$false -first:$first } elseif ($persistent) { Write-Verbose "Saving to global machine PATH variable" [System.Environment]::SetEnvironmentVariable("PATH", [string]::Join(";", $p), [System.EnvironmentVariableTarget]::Machine); #add also to process PATH AddToPath $path -persistent:$false -first:$first } else { $env:path = [string]::Join(";", $p); [System.Environment]::SetEnvironmentVariable("PATH", $env:path, [System.EnvironmentVariableTarget]::Process); } } } #EndRegion '.\Private\Path\AddToPath.ps1' 109 #Region '.\Private\Path\GetEnvironmentPath.ps1' -1 function GetEnvironmentPath { <# .SYNOPSIS Get the environment path. .DESCRIPTION The function GetEnvironmentPath gets the environment path. .PARAMETER Scope [System.EnvironmentVariableTarget], [String] Specify the scope to search for the target Environment Variable. Process : Environment Variables in the running process. User : Environment Variables in the User Scope affect the Global Environment Variables for the current user. Machine : Environment Variables in the Machine Scope change the settings in the registry for all users. .EXAMPLE PS C:\> GetEnvironmentPath -Scope Machine .NOTES https://github.com/rbleattler/xEnvironmentVariables #> param ( [Parameter(Mandatory)] [System.EnvironmentVariableTarget] $Scope ) switch ($Scope) { "Process" { "Env:" } "User" { "Registry::HKEY_CURRENT_USER\Environment" } "Machine" { "Registry::HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\Environment" } Default { $null } } } #EndRegion '.\Private\Path\GetEnvironmentPath.ps1' 51 #Region '.\Private\Path\GetEnvironmentVariable.ps1' -1 function GetEnvironmentVariable { <# .SYNOPSIS A PowerShell module to handle environment variables, supporting variable expansion. This function handles GETTING environment variables. .DESCRIPTION This module is capable of Retrieving Environment Variables in any scope (Process, User, Machine). It will return the value of the Envrionment Variable. .PARAMETER Name [String] Specify the name of the Environment Variable to retrieve. .PARAMETER Scope [System.EnvironmentVariableTarget], [String] Specify the scope to search for the target Environment Variable. Process : Environment Variables in the running process. User : Environment Variables in the User Scope affect the Global Environment Variables for the current user. Machine : Environment Variables in the Machine Scope change the settings in the registry for all users. .PARAMETER Expanded [Switch] If enabled any Environment Variables in the output of the command will be expanded. String : A simple string value ExpandString : A string value which contains unexpanded environment variables in the syntax of %VARIABLENAME%. These variables will NOT be expanded when the value is read. .PARAMETER ShowProperties [Switch] If enabled this parameter will show the Name, Value, Scope, ValueType and (if a String containing an unexpanded Environment Variable) the BeforeExpansion properties and their respective values. Otherwise only the Value will be output as a string. .EXAMPLE PS C:\> Get-EnvironmentVariable -name TestVar -Scope Machine -ShowProperties Name : TestVar Value : TestValue Scope : Machine ValueType : String BeforeExpansion : TestValue .EXAMPLE PS C:\> Get-EnvironmentVariable -name TestPathVar -Scope Machine -ShowProperties Name : TestPathVar Value : C:\Users\rblea\AppData\Local\Temp\TestValue2 Scope : Machine ValueType : String BeforeExpansion : %TEMP%\TestValue2 .EXAMPLE PS C:\> Get-EnvironmentVariable -name TestPathVar -Scope Machine C:\Users\USER\AppData\Local\Temp\TestValue2 .EXAMPLE PS C:\> Get-EnvironmentVariable -name TestPathVar -Scope Machine -Expanded %TEMP%\TestValue2 .INPUTS [String],[System.EnvironmentVariableTarget],[Boolean] .OUTPUTS [Hashtable],[String] .NOTES .LINK https://github.com/rbleattler/xEnvironmentVariables #> [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [String] $Name, [Parameter()] [System.EnvironmentVariableTarget] $Scope = [System.EnvironmentVariableTarget]::Process, [Parameter()] [Switch] $Expanded, [Parameter()] [Switch] $ShowProperties ) $Getter = [System.Environment]::GetEnvironmentVariable($Name, $Scope) if ($null -eq $Getter) { $RawValue = $null $GetterType = $null } else { if ($Scope -ne "Process") { if (!$Expanded) { $AllEnvironmentVariables = Get-Item -Path (GetEnvironmentPath -Scope $Scope) $GetterType = $AllEnvironmentVariables.GetValueKind($Name) } else { $AllEnvironmentVariables = [System.Environment]::GetEnvironmentVariables($Scope) $GetterType = $Getter.GetTypeCode() } if ($GetterType -eq "ExpandString") { $RawValue = $AllEnvironmentVariables.GetValue( $Name, $null, 'DoNotExpandEnvironmentNames' ) } elseif ($GetterType -eq "String") { $RawValue = $Getter if ($Expanded) { $Getter = [System.Environment]::ExpandEnvironmentVariables($Getter) } } else { # inappropriate kind (dword, bytes, ...) $RawValue = $null $GetterType = $null } } else { # $Scope -eq "Process" $RawValue = $null $GetterType = "String" } } $params = @{ Name = $Name Value = $Getter Scope = $Scope ValueType = $GetterType BeforeExpansion = $RawValue } $null = NewEnvironmentVariableObject @params | Set-Variable -Name NewEnvVar if ($ShowProperties) { $NewEnvVar | Add-Member ScriptMethod ToString { $this.Value } -Force -PassThru } else { if (!$Expanded) { $NewEnvVar | Add-Member ScriptMethod ToString { $this.Value } -Force -PassThru | Select-Object -ExpandProperty BeforeExpansion } else { $NewEnvVar | Add-Member ScriptMethod ToString { $this.Value } -Force -PassThru | Select-Object -ExpandProperty Value } } } #EndRegion '.\Private\Path\GetEnvironmentVariable.ps1' 164 #Region '.\Private\Path\GetEnvVar.ps1' -1 function GetEnvVar { <# .SYNOPSIS Get Environment variable value. .DESCRIPTION Get Environment variable value. To see more details, check out the .NOTES section for link. .PARAMETER Name The variable name to look for. .PARAMETER User get variable from user scope (persistent) .PARAMETER Machine get variable from machine scope (persistent) .PARAMETER Current (default=true) get variable from current process scope .EXAMPLE PS C:\> $machinepath = getenvvar "PATH" -machine .NOTES Site: https://github.com/qbikez/ps-pathutils/tree/master #> [CmdletBinding()] [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingInvokeExpression', '', Justification = 'Not my store.')] param ( [Parameter(Mandatory = $true)] [System.String] $Name, [System.Management.Automation.SwitchParameter] $User, [System.Management.Automation.SwitchParameter] $Machine, [System.Management.Automation.SwitchParameter] $Current ) $val = @() if ($user) { $val += [System.Environment]::GetEnvironmentVariable($name, [System.EnvironmentVariableTarget]::User); } if ($machine) { $val += [System.Environment]::GetEnvironmentVariable($name, [System.EnvironmentVariableTarget]::Machine); } if (!$user.IsPresent -and !$machine.IsPresent) { $current = $true } if ($current) { $val = invoke-expression "`$env:$name" } if ($null -ne $val) { $p = $val -Split ";" } else { $p = @() } return $p } #EndRegion '.\Private\Path\GetEnvVar.ps1' 74 #Region '.\Private\Path\GetPathEnv.ps1' -1 function GetPathEnv { <# .SYNOPSIS Gets PATH env variable .DESCRIPTION Gets PATH env variable. To see more details, check out the .NOTES section for link. .PARAMETER User Get the value from user scope .PARAMETER Machine (default) Get the value from machine scope .PARAMETER Current Get the value from process scope .PARAMETER All Return values for each scope .EXAMPLE PS C:\> $p = GetPathEnv -Machine .NOTES Site: https://github.com/qbikez/ps-pathutils/tree/master #> [CmdLetBinding(DefaultParameterSetName = "scoped")] [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseOutputTypeCorrectly', '', Justification = 'Not my store.')] param ( [Parameter(ParameterSetName = "scoped")] [System.Management.Automation.SwitchParameter] $User, [Parameter(ParameterSetName = "scoped")] [System.Management.Automation.SwitchParameter] $Machine, [Alias("process")] [Parameter(ParameterSetName = "scoped")] [System.Management.Automation.SwitchParameter] $Current, [Parameter(ParameterSetName = "all")] [System.Management.Automation.SwitchParameter] $All ) $scopespecified = $user.IsPresent -or $machine.IsPresent -or $current.IsPresent $path = @() $userpath = getenvvar "PATH" -user if ($user) { $path += $userpath } $machinepath = getenvvar "PATH" -machine if ($machine -or !$scopespecified) { $path += $machinepath } if (!$user.IsPresent -and !$machine.IsPresent) { $current = $true } $currentPath = getenvvar "PATH" -current if ($current) { $path = $currentPath } if ($all) { $h = @{ user = $userpath machine = $machinepath process = $currentPath } return @( "`r`n USER", " -----------", $h.user, "`r`n MACHINE", " -----------", $h.machine, "`r`n PROCESS", " -----------", $h.process ) } return $path } #EndRegion '.\Private\Path\GetPathEnv.ps1' 95 #Region '.\Private\Path\NewEnvironmentVariableObject.ps1' -1 function NewEnvironmentVariableObject { <# .SYNOPSIS A PowerShell module to handle environment variables, supporting variable expansion. This function handles GETTING environment variables. .DESCRIPTION This module is capable of Retrieving Environment Variables in any scope (Process, User, Machine). It will return the value of the Envrionment Variable. .PARAMETER Name [String] Specify the name of the Environment Variable to retrieve. .PARAMETER Value What is the value to be set. .PARAMETER Scope [System.EnvironmentVariableTarget], [String] Specify the scope to search for the target Environment Variable. Process : Environment Variables in the running process. User : Environment Variables in the User Scope affect the Global Environment Variables for the current user. Machine : Environment Variables in the Machine Scope change the settings in the registry for all users. .PARAMETER ValueType The value type e.g. String. .PARAMETER BeforeExpansion The value before it got expanded. .EXAMPLE PS C:\> NewEnvironmentVariableObject .NOTES Site: https://github.com/rbleattler/xEnvironmentVariables #> param ( [OutputType([HashTable])] [Parameter(Mandatory)] [ValidatePattern("[^=]+")] $Name, [Parameter()] [AllowNull()] [String] $Value, [Parameter(Mandatory)] [System.EnvironmentVariableTarget] $Scope, [Parameter()] [AllowNull()] [ValidateSet("String", "ExpandString", $null)] [String] $ValueType, [Parameter()] [AllowNull()] [String] $BeforeExpansion ) $OutPut = [PSCustomObject]@{ Name = $Name Value = $Value Scope = $Scope ValueType = $ValueType BeforeExpansion = $BeforeExpansion } $OutPut } #EndRegion '.\Private\Path\NewEnvironmentVariableObject.ps1' 71 #Region '.\Private\Process\GetNetProcessObject.ps1' -1 function GetNetProcessObject { <# .SYNOPSIS Produce .NET System.Diagnostics.Process object. .DESCRIPTION The function GetNetProcessObject instantiaties a .NET process object to be returned. .PARAMETER Arguments The arguments to add to the run. .PARAMETER ExePath The executable to start. Defaults to 'dsc.exe' .EXAMPLE PS C:\> GetNetProcessObject -Arguments 'resource get --resource Microsoft.Windows/RebootPending' .NOTES For more details, go to module repository at: https://github.com/Gijsreyn/PSDSC. #> [CmdletBinding()] [OutputType([System.Diagnostics.Process])] param ( [Parameter(Mandatory = $false)] [AllowNull()] [System.String] $Arguments, [Parameter(Mandatory = $false)] [AllowNull()] [System.String] $ExePath = (ResolveDscExe) ) # use System.Diagnostics.Process instead of & or Invoke-Expression $proc = [System.Diagnostics.Process]::new() $arguments = (-not [string]::IsNullOrEmpty($Arguments)) ? $Arguments : '--version' # build process start info with redirects $procStartinfo = [System.Diagnostics.ProcessStartInfo]::new($ExePath, $arguments) $procStartinfo.UseShellExecute = $false $procStartinfo.RedirectStandardOutput = $true $procStartinfo.RedirectStandardError = $true $proc.StartInfo = $procStartinfo # return object return $proc } #EndRegion '.\Private\Process\GetNetProcessObject.ps1' 52 #Region '.\Private\Process\StartNetProcessObject.ps1' -1 function StartNetProcessObject { <# .SYNOPSIS Start process using .NET. .DESCRIPTION The function StartNetProcessObject starts a process using .NET instead of Start-Process cmdlet. .PARAMETER Process The process object to start. .EXAMPLE PS C:\> $Process = [System.Diagnostics.Process]::new() PS C:\> $ProcStartinfo = [System.Diagnostics.ProcessStartInfo]::new('dsc.exe', '--version') PS C:\> $Process.StartInfo = $ProcStartInfo PS C:\> StartNetProcessObject -Process $Process Runs dsc --version .NOTES For more details, go to module repository at: https://github.com/Gijsreyn/PSDSC. #> [CmdletBinding()] param ( [Parameter(Mandatory = $false)] [AllowNull()] [System.Diagnostics.Process] $Process ) Write-Debug -Message ("Starting '{0}' with arguments: [{1}]" -f $Process.StartInfo.FileName, $Process.StartInfo.Arguments) $Process.Start() | Out-Null $output = [System.Collections.Generic.List[string]]::new() if ($Process.StartInfo.RedirectStandardOutput) { while ($null -ne ($line = $Process.StandardOutput.ReadLine())) { if (-not [string]::IsNullOrEmpty($line)) { Write-Debug "Adding: $line" $output.Add($line) } } } if ($Process.StartInfo.RedirectStandardError) { $standardError = $Process.StandardError.ReadToEnd() } $Process.WaitForExit() $inputObject = New-Object -TypeName PSObject -Property ([Ordered]@{ Executable = $Process.StartInfo.FileName Arguments = $Process.StartInfo.Arguments ExitCode = $Process.ExitCode Output = $output Error = $standardError }) $inputObject } #EndRegion '.\Private\Process\StartNetProcessObject.ps1' 67 #Region '.\Private\Util\ConvertToHashString.ps1' -1 Function ConvertToHashString { <# .SYNOPSIS Convert hashtable to string. .DESCRIPTION The function ConvertToHashString converts a hashtable to a string. .PARAMETER HashTable The hashtable to convert. .EXAMPLE PS C:\> ConvertToHashString -HashTable @{ 'key' = 'value' } Returns: @{key = value} .NOTES For more details, go to module repository at: https://github.com/Gijsreyn/PSDSC. #> [CmdletBinding()] [OutputType([System.String])] param ( [Parameter(Mandatory = $true)] [System.Collections.Specialized.OrderedDictionary]$HashTable ) $first = $true foreach ($pair in $HashTable.GetEnumerator()) { if ($first) { $first = $false } else { $output += ';' } $output += "'{0}' = '{1}'" -f $($pair.key), $($pair.Value) } $output = [System.String]::Concat('@{', $output, '}') return $output } #EndRegion '.\Private\Util\ConvertToHashString.ps1' 48 #Region '.\Private\Util\GetBoundParameters.ps1' -1 function GetBoundParameters { <# .SYNOPSIS Helper function that returns a hashtable of the GOOD bound parameters needed for calling other function .DESCRIPTION The function GetBoundParameters helps to create a hash table used for other functions .PARAMETER BoundParameters The bound parameters to check .PARAMETER GoodKeys The good keys to check in bound parameters .EXAMPLE PS C:\> GetBoundParameters -BoundParameters $PSBoundParameters -GoodKeys @("ResourceName", "ResourceInput") .NOTES Tags: Parameters #> [OutputType([hashtable])] [CmdletBinding()] Param ( [AllowNull()] [object] $BoundParameters, [System.Array] $GoodKeys ) $ConfigurationHelper = @{} foreach ($Key in $BoundParameters.Keys) { if ($GoodKeys -contains $Key) { $ConfigurationHelper[$Key] = $BoundParameters[$Key] } } return $ConfigurationHelper } #EndRegion '.\Private\Util\GetBoundParameters.ps1' 46 #Region '.\Private\Util\TestAdministrator.ps1' -1 function TestAdministrator { <# .SYNOPSIS Test if user is currently running elevated or not. .DESCRIPTION Test if user is currently running elevated or not. .EXAMPLE TestAdministrator .NOTES For more details, go to module repository at: https://github.com/Gijsreyn/PSDSC. #> $user = [Security.Principal.WindowsIdentity]::GetCurrent(); (New-Object Security.Principal.WindowsPrincipal $user).IsInRole([Security.Principal.WindowsBuiltinRole]::Administrator) } #EndRegion '.\Private\Util\TestAdministrator.ps1' 20 #Region '.\Private\Util\TestWinGetModule.ps1' -1 function TestWinGetModule { <# .SYNOPSIS Checks if the specified PowerShell module is installed. .DESCRIPTION The TestWinGetModule function checks if a specified PowerShell module is installed on the system by using the Get-Module cmdlet with the -ListAvailable parameter. By default, it checks for the "Microsoft.WinGet.Dsc" module, but you can specify a different module name if needed. .PARAMETER ModuleName The name of the module to check for. The default value is "Microsoft.WinGet.Dsc". .EXAMPLE TestWinGetModule This example checks if the "Microsoft.WinGet.Dsc" module is installed. .EXAMPLE PS C:\> TestWinGetModule -ModuleName "Pester" This example checks if the "Pester" module is installed. .OUTPUTS System.Boolean Returns $true if the module is installed, otherwise returns $false. #> [CmdletBinding()] [OutputType([System.Boolean])] param ( [Parameter(Mandatory = $false)] [string]$ModuleName = "Microsoft.WinGet.Dsc" ) process { $module = Get-Module -Name $ModuleName -ListAvailable if ($module) { return $true } else { return $false } } } #EndRegion '.\Private\Util\TestWinGetModule.ps1' 46 #Region '.\Private\Util\TestYamlModule.ps1' -1 function TestYamlModule { <# .SYNOPSIS Test if ConvertTo-Yaml is installed from Yayaml .DESCRIPTION Test if ConvertTo-Yaml is installed from Yayaml .EXAMPLE TestYamlModule .NOTES For more details, go to module repository at: https://github.com/Gijsreyn/PSDSC. #> if (-not (Get-Module -ListAvailable yayaml -ErrorAction SilentlyContinue) -or (Get-Module -ListAvailable powershell-yaml)) { return $false } return $true } #EndRegion '.\Private\Util\TestYamlModule.ps1' 24 #Region '.\Public\ConvertTo-DscJson.ps1' -1 function ConvertTo-DscJson { <# .SYNOPSIS Convert DSC Configuration (v1/v2) Document to JSON. .DESCRIPTION The function ConvertTo-DscJson converts a DSC Configuration Document (v1/v2) to JSON. .PARAMETER Path The file path to a valid DSC Configuration Document. .PARAMETER Content The content to a valid DSC Configuration Document. .EXAMPLE PS C:\> $path = 'myConfig.ps1' PS C:\> ConvertTo-DscJson -Path $path .INPUTS Input a valid DSC Configuration Document configuration MyConfiguration { Import-DscResource -ModuleName PSDesiredStateConfiguration Node localhost { Environment CreatePathEnvironmentVariable { Name = 'TestPathEnvironmentVariable' Value = 'TestValue' Ensure = 'Present' Path = $true Target = @('Process') } } } .OUTPUTS Returns a JSON string { "$schema": "https://raw.githubusercontent.com/PowerShell/DSC/main/schemas/2024/04/config/document.json", "resources": { "name": "MyConfiguration node", "type": "Microsoft.DSC/PowerShell", "properties": { "resources": [ { "name": "CreatePathEnvironmentVariable", "type": "PSDscResources/Environment", "properties": { "Value": "TestValue", "Path": true, "Name": "TestPathEnvironmentVariable", "Ensure": "Present", "Target": [ "Process" ] } } ] } } } #> [CmdletBinding(DefaultParameterSetName = 'Path')] [OutputType([System.String])] param ( [Parameter(Mandatory = $true, ParameterSetName = 'Path')] [ValidateScript({ if (-Not ($_ | Test-Path) ) { throw "File or folder does not exist" } if (-Not ($_ | Test-Path -PathType Leaf) ) { throw "The Path argument must be a file. Folder paths are not allowed." } return $true })] [System.String] $Path, [Parameter(Mandatory = $true, ParameterSetName = 'Content')] [System.String] $Content ) begin { Write-Verbose -Message ("Starting: {0}" -f $MyInvocation.MyCommand.Name) } process { $configurationDocument = BuildDscConfigDocument @PSBoundParameters } end { Write-Verbose ("Ended: {0}" -f $MyInvocation.MyCommand.Name) return ($configurationDocument | ConvertTo-Json -Depth 10 -Compress) } } #EndRegion '.\Public\ConvertTo-DscJson.ps1' 108 #Region '.\Public\ConvertTo-DscYaml.ps1' -1 function ConvertTo-DscYaml { <# .SYNOPSIS Convert DSC Configuration (v1/v2) Document to YAML. .DESCRIPTION The function ConvertTo-DscYaml converts a DSC Configuration Document (v1/v2) to YAML. .PARAMETER Path The file path to a valid DSC Configuration Document. .PARAMETER Content The content to a valid DSC Configuration Document. .EXAMPLE PS C:\> $path = 'myConfig.ps1' PS C:\> ConvertTo-DscYaml -Path $path .INPUTS Input a valid DSC Configuration Document configuration MyConfiguration { Import-DscResource -ModuleName PSDesiredStateConfiguration Node localhost { Environment CreatePathEnvironmentVariable { Name = 'TestPathEnvironmentVariable' Value = 'TestValue' Ensure = 'Present' Path = $true Target = @('Process') } } } .OUTPUTS Returns a YAML string $schema: https://raw.githubusercontent.com/PowerShell/DSC/main/schemas/2024/04/config/document.json resources: name: MyConfiguration type: Microsoft.DSC/PowerShell properties: resources: - name: CreatePathEnvironmentVariable type: PSDscResources/Environment properties: Value: TestValue Path: true Name: TestPathEnvironmentVariable Ensure: Present Target: - Process #> [CmdletBinding(DefaultParameterSetName = 'Path')] [OutputType([System.String])] param ( [Parameter(Mandatory = $true, ParameterSetName = 'Path')] [ValidateScript({ if (-Not ($_ | Test-Path) ) { throw "File or folder does not exist" } if (-Not ($_ | Test-Path -PathType Leaf) ) { throw "The Path argument must be a file. Folder paths are not allowed." } return $true })] [System.String] $Path, [Parameter(Mandatory = $true, ParameterSetName = 'Content')] [System.String] $Content ) begin { Write-Verbose -Message ("Starting: {0}" -f $MyInvocation.MyCommand.Name) } process { $configurationDocument = BuildDscConfigDocument @PSBoundParameters } end { Write-Verbose ("Ended: {0}" -f $MyInvocation.MyCommand.Name) if (TestYamlModule) { $inputObject = ConvertTo-Yaml -InputObject $configurationDocument -Depth 10 } return $inputObject } } #EndRegion '.\Public\ConvertTo-DscYaml.ps1' 102 #Region '.\Public\Get-PsDscResourceSchema.ps1' -1 <# .SYNOPSIS Retrieves the schema for a specified DSC resource. .DESCRIPTION The function Get-PsDscResourceSchema function retrieves the schema for a specified Desired State Configuration (DSC) resource. It can optionally include the properties of the resource in the output. .PARAMETER ResourceName The name of the DSC resource for which to retrieve the schema. .PARAMETER IncludeProperty A switch parameter that, when specified, includes the properties of the DSC resource in the output. .EXAMPLE PS C:\> Get-PsDscResourceSchema -ResourceName "Microsoft.Windows/Registry" Retrieves the schema for the "Microsoft.Windows/Registry" DSC resource. .EXAMPLE PS C:\> Get-PsDscResourceSchema -ResourceName "Microsoft.WinGet.DSC/WinGetPackage" -IncludeProperty Retrieves the schema for the "Microsoft.WinGet.DSC/WinGetPackage" DSC resource and includes its properties in the output only. .NOTES For more details, refer to the module documentation. #> function Get-PsDscResourceSchema { [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [Alias('Name')] [ArgumentCompleter([DscResourceCompleter])] [System.String] $ResourceName, [Parameter(Mandatory = $false)] [System.Management.Automation.SwitchParameter] $IncludeProperty ) begin { Write-Verbose -Message ("Starting: {0}" -f $MyInvocation.MyCommand.Name) } process { $process = GetNetProcessObject -Arguments "resource schema --resource $ResourceName" $inputObject = StartNetProcessObject -Process $process if (-not ([string]::IsNullOrEmpty($inputObject.Output))) { $out = $inputObject.Output | ConvertFrom-Json if ($IncludeProperty) { $hash = @{} $out.properties.PSObject.Members | ForEach-Object { if ($_.MemberType -eq 'NoteProperty ') { $hash[$_.Name] = $null } } $inputObject = $hash } } } end { Write-Verbose ("Ended: {0}" -f $MyInvocation.MyCommand.Name) return $inputObject } } #EndRegion '.\Public\Get-PsDscResourceSchema.ps1' 78 #Region '.\Public\Initialize-PsDscConfigDocument.ps1' -1 function Initialize-PsDscConfigDocument { <# .SYNOPSIS Initialize a DSC configuration document. .DESCRIPTION The function Initialize-PsDscConfigDocument initializes a DSC configuration document. It can pick up 'resource' and 'adapter' from PowerShell or Windows PowerShell. .PARAMETER ResourceName The resource name to execute. .PARAMETER ResourceInput The resource input to provide. Supports PowerShell hash table. .PARAMETER ResourceDescription The resource description to provide. .PARAMETER IsPwsh Switch to indicate if the resource is using PowerShell. Adds 'Microsoft.DSC/PowerShell' type. .PARAMETER IsWindowsPowerShell Switch to indicate if the resource is using Windows PowerShell. Adds 'Microsoft.Windows/WindowsPowerShell' type. .EXAMPLE PS C:\> Initialize-PsDscConfigDocument -ResourceName 'Microsoft.Windows/Registry' -ResourceInput @{'keyPath' = 'HKCU\1'} -ResourceDescription 'Registry' Returns: {"$schema":"https://raw.githubusercontent.com/PowerShell/DSC/main/schemas/2024/04/config/document.json","resources":[{"name":"Registry","type":"Microsoft.Windows/Registry","properties":"@{keyPath=HKCU\\1}"}]} .EXAMPLE PS C:\> Init-PsDscConfigDocument -ResourceName Microsoft.WinGet.DSC/WinGetPackage -IsPwsh -ResourceInput @{ Id = 'Microsoft.PowerShell.Preview'} -ResourceDescription 'WinGetPackage' Returns: {"$schema":"https://raw.githubusercontent.com/PowerShell/DSC/main/schemas/2024/04/config/document.json","resources":[{"name":"Using Pwsh","type":"Microsoft.DSC/PowerShell","properties":"@{resources=}"}]} .OUTPUTS System.String .NOTES For more details, go to module repository at: https://github.com/Gijsreyn/PSDSC. #> [CmdletBinding()] [Alias('Init-PsDscConfigDocument')] [OutputType([System.String])] param ( [Parameter(Mandatory = $true)] [ArgumentCompleter([DscConfigCompleter])] [string]$ResourceName, [Parameter(Mandatory = $false)] [ArgumentCompleter([DscConfigInputCompleter])] [AllowNull()] [hashtable]$ResourceInput, [Parameter(Mandatory = $false)] [string]$ResourceDescription, [Parameter(Mandatory = $false)] [System.Management.Automation.SwitchParameter] $IsPwsh, [Parameter(Mandatory = $false)] [System.Management.Automation.SwitchParameter] $IsWindowsPowerShell ) if ([string]::IsNullOrEmpty($ResourceDescription)) { $ResourceDescription = $ResourceName } $resources = [ordered]@{ name = $ResourceDescription type = $ResourceName properties = $ResourceInput } if ($IsPwsh.IsPresent) { $resources = [ordered]@{ name = 'Using Pwsh' type = 'Microsoft.DSC/PowerShell' properties = [ordered]@{ resources = $resources } } } if ($IsWindowsPowerShell.IsPresent) { $resources = [ordered]@{ name = 'Using Pwsh' type = 'Microsoft.Windows/WindowsPowerShell' properties = [ordered]@{ resources = $resources } } } $configDocument = [ordered]@{ '$schema' = 'https://raw.githubusercontent.com/PowerShell/DSC/main/schemas/2024/04/config/document.json' resources = @($resources) } return $configDocument | ConvertTo-Json -Depth 10 } #EndRegion '.\Public\Initialize-PsDscConfigDocument.ps1' 111 #Region '.\Public\Install-DscCLI.ps1' -1 function Install-DscCLI { <# .SYNOPSIS Install DSC CLI (Windows only). .DESCRIPTION The function Install-DscCLI installs Desired State Configuration version 3 executablle. .PARAMETER Force This switch will force DSC to be installed, even if another installation is already in place. .PARAMETER Version The version of DSC to install. .PARAMETER UseWinGet Use the Windows Package Manager to install DSC. .PARAMETER UseGitHub Use GitHub to install DSC. .EXAMPLE PS C:\> Install-DscCli Install the latest version of DSC .EXAMPLE PS C:\> Install-DscCli -Force Install DSC and forces the installed if there is already a version installed. .NOTES For more details, go to module repository at: https://github.com/Gijsreyn/PSDSC. #> [CmdletBinding()] [OutputType([System.Boolean])] param ( [Parameter()] [ArgumentCompleter([DscVersionCompleter])] [System.String] $Version, [Parameter(Mandatory = $false)] [System.Management.Automation.SwitchParameter]$UseWinGet, [Parameter(Mandatory = $false)] [System.Management.Automation.SwitchParameter]$UseGitHub, [Parameter()] [System.Management.Automation.SwitchParameter] $Force ) Write-Verbose -Message ("Starting: {0}" -f $MyInvocation.MyCommand.Name) $base = 'https://api.github.com/repos/PowerShell/DSC/releases' if ($PSBoundParameters.ContainsKey('Version')) { $releaseUrl = ('{0}/tags/v{1}' -f $base, $Version) $UseVersion = $true } else { # TODO: no latest tag because no official release $releaseUrl = $base } $releases = Invoke-RestMethod -Uri $releaseUrl # TODO: remove after latest is known if ($releases.Count -gt 1) { $releases = $releases | Sort-Object -Descending | Select-Object -First 1 } if ($IsWindows) { if ($UseWinGet.IsPresent) { $winGetModuleInstalled = TestWinGetModule if (-not $winGetModuleInstalled) { throw "This function requires the 'Microsoft.WinGet.Dsc' module to be installed. Please install it using 'Install-PSResource -Name Microsoft.WinGet.Dsc'." } $hashArgs = @{ Id = '9PCX3HX4HZ0Z' MatchOption = 'EqualsCaseInsensitive' } $versionPackage = Get-WinGetPackage @hashArgs if (-not $versionPackage) { $dscInstalled = Install-WinGetPackage @hashArgs if ($dscInstalled.Status -eq 'Ok') { $version = GetDscVersion Write-Verbose -Message "DSC successfully installed with version $version" # return return $true } } if ($versionPackage) { $versions = Find-WinGetPackage @hashArgs | Select-Object -First 1 # TODO: validate if multiple version are available if ($versionPackage.Version -le $versions.Version -and $PSBoundParameters.ContainsKey('Force')) { Write-Verbose -Message "Updating DSC version: '$($versionPackage.Version)' to '$($versions.Version)'" $dscInstalled = Update-WinGetPackage @hashArgs if ($dscInstalled.Status -eq 'Ok' -or $dscInstalled.Status -eq 'NoApplicableUpgrade') { $version = GetDscVersion Write-Verbose -Message "DSC successfully updated with version $version" # return return $true } } if ($versionPackage.Version -le $versions.Version) { Write-Warning -Message "DSC is already installed with version $($versionPackage.Version), but you have not specified the -Force switch to update it. If you want to update it, please add the -Force parameter." # return return $false } else { Write-Verbose -Message "DSC is already installed with version $($versionPackage.Version)." # return return $true } } } if ($UseGitHub.IsPresent) { $fileName = 'DSC-3.0.0-*-x86_64-pc-windows-msvc.zip' # get latest asset to be downloaded $asset = $releases.assets | Where-Object -Property Name -Like $fileName # download the installer $tmpdir = [System.IO.Path]::GetTempPath() $fileName = $asset.name $installerPath = [System.IO.Path]::Combine($tmpDir, $fileName) (New-Object Net.WebClient).DownloadFileAsync($asset.browser_download_url, $installerPath) Write-Verbose "Downloading $($asset.browser_download_url) to location $installerPath" do { $PercentComplete = [math]::Round((Get-Item $installerPath).Length / $asset.size * 100) Write-Progress -Activity 'Downloading DSC' -PercentComplete $PercentComplete start-sleep 1 } while ((Get-Item $installerPath).Length -lt $asset.size) # expand the installer to directory $elevated = TestAdministrator $exePath = if ($elevated) { Join-Path $env:ProgramFiles 'dsc' } else { Join-Path $env:LOCALAPPDATA 'dsc' } Write-Verbose -Message ("Expanding '{0}' to '{1}'" -f $installerPath, $exePath) $null = Expand-Archive -LiteralPath $installerPath -DestinationPath $exePath -Force if ($elevated) { $exePath | AddToPath -Persistent:$true } else { $exePath | AddToPath -User } if (TestDsc) { $dsc = GetDscVersion Write-Verbose -Message "DSC successfully installed with version $dsc" return $true } } } elseif ($IsLinux) { if ($UseVersion) { $filePath = '/tmp/DSC-' + $Version + '-x86_64-unknown-linux-gnu.tar.gz' $uri = "https://github.com/PowerShell/DSC/releases/download/v$Version/DSC-$Version-x86_64-unknown-linux-gnu.tar.gz" } else { $filePath = '/tmp/DSC-3.0.0-x86_64-unknown-linux-gnu.tar.gz' $fileName = 'DSC-3.0.0-*-x86_64-unknown-linux-gnu.tar.gz' $uri = ($releases.assets | Where-Object -Property Name -Like $fileName).browser_download_url } Write-Verbose -Message "Using URI: $uri on path: $filePath" curl -L -o $filePath $uri # Create the target folder where powershell will be placed sudo mkdir -p /opt/microsoft/dsc # Expand powershell to the target folder sudo tar zxf $filePath -C /opt/microsoft/dsc # Set execute permissions sudo chmod +x /opt/microsoft/dsc # Create the symbolic link that points to pwsh sudo ln -s /opt/microsoft/dsc /usr/bin/dsc # Add to path $env:PATH += [System.IO.Path]::PathSeparator + "/usr/bin/dsc" # Call version dsc --version } } #EndRegion '.\Public\Install-DscCLI.ps1' 231 #Region '.\Public\Invoke-PsDscConfig.ps1' -1 function Invoke-PsDscConfig { <# .SYNOPSIS Invoke DSC version 3 config using the command-line utility .DESCRIPTION The function Invoke-PsDscConfig invokes Desired State Configuration version 3 configuration documents calling 'dsc.exe'. .PARAMETER ResourceInput The resource input to provide. Supports: - JSON (string and path) - YAML (string and path) - PowerShell hash table - PowerShell configuration document script (.ps1) .PARAMETER Operation The operation capability to execute e.g. 'Set'. .PARAMETER Parameter Optionally, the parameter input to provide. .EXAMPLE PS C:\> Invoke-PsDscConfig -ResourceInput myconfig.dsc.config.yaml -Parameter myconfig.dsc.config.parameters.yaml .EXAMPLE PS C:\> Invoke-PsDscConfig -ResourceInput @{keyPath = 'HKCU\1'} -Operation Set .EXAMPLE PS C:\> $script = @' configuration WinGet { Import-DscResource -ModuleName 'Microsoft.WinGet.DSC' node localhost { WinGetPackage WinGetPackageExample { Id = 'Microsoft.PowerShell.Preview' Ensure = 'Present' } } } '@ PS C:\> Set-Content -Path 'winget.powershell.dsc.config.ps1' -Value $script PS C:\> Invoke-PsDscConfig -ResourceInput 'winget.powershell.dsc.config.ps1' -Operation Set .NOTES For more details, go to module repository at: https://github.com/Gijsreyn/PSDSC. #> [CmdletBinding(SupportsShouldProcess)] [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '', Justification = 'Required for down-level function(s).')] param ( [Parameter(Mandatory = $false)] [AllowNull()] [object] $ResourceInput, [Parameter(Mandatory = $true)] [ValidateSet('Get', 'Set', 'Test', 'Export')] [System.String] $Operation, [Parameter(Mandatory = $false)] [AllowNull()] [object] $Parameter ) begin { Write-Verbose -Message ("Starting: {0}" -f $MyInvocation.MyCommand.Name) $boundParameters = GetBoundParameters -BoundParameters $PSBoundParameters -GoodKeys @('ResourceInput', 'Parameter') Write-Verbose ($boundParameters | ConvertTo-Json | Out-String) } process { switch ($Operation) { 'Get' { $inputObject = GetDscConfigCommand @boundParameters } 'Set' { $inputObject = SetDscConfigCommand @boundParameters } 'Test' { $inputobject = TestDscConfigCommand @boundParameters } 'Delete' { $inputobject = RemoveDscConfigCommand @boundParameters } } } end { Write-Verbose ("Ended: {0}" -f $MyInvocation.MyCommand.Name) # return return $inputObject } } #EndRegion '.\Public\Invoke-PsDscConfig.ps1' 110 #Region '.\Public\Invoke-PsDscResource.ps1' -1 function Invoke-PsDscResource { <# .SYNOPSIS Invoke DSC version 3 resource using the command-line utility .DESCRIPTION The function Invoke-PsDscResource invokes Desired State Configuration version 3 resources calling the executable. .PARAMETER ResourceName The resource name to execute. .PARAMETER Operation The operation capability to execute e.g. 'Set'. .PARAMETER ResourceInput The resource input to provide. Supports JSON, YAML path and PowerShell hash table. .EXAMPLE PS C:\> Invoke-PsDscResource -ResourceName Microsoft.Windows/RebootPending -Operation Get Execute Microsoft.Windows/RebootPending resource on Windows system to check if there is a pending reboot .NOTES For more details, go to module repository at: https://github.com/Gijsreyn/PSDSC. #> [CmdletBinding(SupportsShouldProcess)] [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '', Justification = 'Required for down-level function(s).')] param ( [Parameter(Mandatory = $true)] [Alias('Name')] [ArgumentCompleter([DscResourceCompleter])] [System.String] $ResourceName, [Parameter(Mandatory = $false)] [ValidateSet('Get', 'Set', 'Test', 'Delete', 'Export')] [System.String] $Operation, [Parameter(Mandatory = $false)] [ArgumentCompleter([DscResourceInputCompleter])] [AllowNull()] [object] $ResourceInput ) begin { Write-Verbose -Message ("Starting: {0}" -f $MyInvocation.MyCommand.Name) # get the bound parameters without common $boundParameters = GetBoundParameters -BoundParameters $PSBoundParameters @("ResourceName", "ResourceInput") Write-Verbose ($boundParameters | ConvertTo-Json | Out-String) } process { if (-not $PSBoundParameters.ContainsKey('Operation') -and $PSBoundParameters.ContainsKey('ResourceInput')) { $boundParameters.Remove('ResourceInput') } switch ($Operation) { 'Get' { $inputObject = GetDscResourceCommand @boundParameters } 'Set' { $inputObject = SetDscResourceCommand @boundParameters } 'Test' { $inputobject = TestDscResourceCommand @boundParameters } 'Delete' { $inputobject = RemoveDscResourceCommand @boundParameters } 'Export' { $inputobject = ExportDscResourceCommand @boundParameters } default { $inputObject = FindDscResourceCommand @boundParameters } } } end { Write-Verbose ("Ended: {0}" -f $MyInvocation.MyCommand.Name) $inputObject } } #EndRegion '.\Public\Invoke-PsDscResource.ps1' 101 #Region '.\Public\New-PsDscVsCodeSettingsFile.ps1' -1 function New-PsDscVsCodeSettingsFile { <# .SYNOPSIS Simple function to add schema definitions to VSCode settings file. .DESCRIPTION The function New-PsDscVsCodeSettingsFile adds schema definitions to the 'settings.json' file to help author DSC Configuration Documents. .PARAMETER Path The path to the VSCode settings file. Defaults to $Home\AppData\Roaming\Code\User\settings.json .EXAMPLE PS C:\> New-PsDscVsCodeSettingsFile .EXAMPLE PS C:\> New-PsDscVsCodeSettingsFile -Path customsettingsfile.json .OUTPUTS System.String .NOTES For more details, go to module repository at: https://github.com/Gijsreyn/PSDSC. #> [CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = 'High')] [OutputType([System.String])] param ( [Parameter(Mandatory = $false)] [AllowNull()] $Path = "$Home\AppData\Roaming\Code\User\settings.json" ) $schema = "https://raw.githubusercontent.com/PowerShell/DSC/main/schemas/2024/04/bundled/config/document.vscode.json" $settings = @" { "json.schemas": [ { "fileMatch": ["**/*.dsc.config.json"], "url": "$schema" } ], "yaml.schemas": { "$schema": "**/*.dsc.config.yaml" }, "yaml.completion": true } "@ $params = @{ Path = $Path Encoding = 'utf8' Value = $settings } if (-not (Test-Path $Path -ErrorAction SilentlyContinue)) { Write-Verbose -Message ("Creating new file: '$Path' with") Write-Verbose -Message $settings Set-Content @params } else { try { $current = Get-Content $Path | ConvertFrom-Json -ErrorAction Stop $reference = ($current | ConvertTo-Json | ConvertFrom-Json) # schema object $yamlObject = [PSCustomObject]@{ $schema = '**/*.dsc.config.yaml' } $jsonObject = [PSCustomObject]@{ fileMatch = @('**/*.dsc.config.json') url = $schema } # add to current $current | Add-Member -NotePropertyName 'yaml.schemas' -TypeName NoteProperty -NotePropertyValue $yamlObject -Force $current | Add-Member -NotePropertyName 'json.schemas' -TypeName NoteProperty -NotePropertyValue @($jsonObject) -Force $settings = $current | ConvertTo-Json -Depth 10 Write-Verbose -Message "Previous settings file:" Write-Verbose -Message ($reference | ConvertTo-Json -Depth 5 | Out-String) $params.Value = $settings if ($PSCmdlet.ShouldProcess($Path, 'overwrite')) { Set-Content @params } } catch { Throw ("'$Path' is not a valid .JSON file. Error: {0}" -f $PSItem.Exception.Message) } } return $settings } #EndRegion '.\Public\New-PsDscVsCodeSettingsFile.ps1' 104 |