From 5ae4e8e9109de5855f703afb2b9b86e0c401ecd5 Mon Sep 17 00:00:00 2001 From: GijsR Date: Thu, 31 Jul 2025 13:45:53 +0200 Subject: [PATCH 01/41] Implement PowerShell import extension --- build.ps1 | 5 + extensions/powershell/convert-resource.ps1 | 28 ++ extensions/powershell/convertDscResource.psd1 | 47 +++ extensions/powershell/convertDscResource.psm1 | 385 ++++++++++++++++++ extensions/powershell/copy_files.txt | 4 + .../powershell/powershell.dsc.extension.json | 22 + 6 files changed, 491 insertions(+) create mode 100644 extensions/powershell/convert-resource.ps1 create mode 100644 extensions/powershell/convertDscResource.psd1 create mode 100644 extensions/powershell/convertDscResource.psm1 create mode 100644 extensions/powershell/copy_files.txt create mode 100644 extensions/powershell/powershell.dsc.extension.json diff --git a/build.ps1 b/build.ps1 index 0afe46305..4e09d7b2c 100755 --- a/build.ps1 +++ b/build.ps1 @@ -43,6 +43,9 @@ $filesForWindowsPackage = @( 'appx.dsc.extension.json', 'appx-discover.ps1', 'bicep.dsc.extension.json', + 'convert-resource.ps1', + 'convertDscResource.psd1', + 'convertDscResource.psm1', 'dsc.exe', 'dsc_default.settings.json', 'dsc.settings.json', @@ -55,6 +58,7 @@ $filesForWindowsPackage = @( 'osinfo.exe', 'osinfo.dsc.resource.json', 'powershell.dsc.resource.json', + 'powershell.dsc.extension.json' 'psDscAdapter/', 'psscript.ps1', 'psscript.dsc.resource.json', @@ -311,6 +315,7 @@ if (!$SkipBuild) { "dsc", "dscecho", "extensions/bicep", + "extensions/powershell" "osinfo", "powershell-adapter", 'resources/PSScript', diff --git a/extensions/powershell/convert-resource.ps1 b/extensions/powershell/convert-resource.ps1 new file mode 100644 index 000000000..11b780bf6 --- /dev/null +++ b/extensions/powershell/convert-resource.ps1 @@ -0,0 +1,28 @@ +[CmdletBinding()] +param ( + [Parameter(ValueFromPipeline = $true)] + [string]$stringInput +) + +return "{}" + +# begin { +# $lines = [System.Collections.Generic.List[string]]::new() + +# $scriptModule = Import-Module "$PSScriptRoot/convertDscResource.psd1" -Force -PassThru +# } + +# process { +# # Process each line of input +# foreach ($line in $stringInput) { +# $lines.Add($line) +# } +# } + +# end { +# if ($lines.Count -ne 0) { +# $result = $scriptModule.invoke( { param($lines) Build-DscConfigDocument -Content $lines }, ($lines | Out-String) ) + +# return ($result | ConvertTo-Json -Depth 10) +# } +# } \ No newline at end of file diff --git a/extensions/powershell/convertDscResource.psd1 b/extensions/powershell/convertDscResource.psd1 new file mode 100644 index 000000000..3cf481384 --- /dev/null +++ b/extensions/powershell/convertDscResource.psd1 @@ -0,0 +1,47 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +@{ + +# Script module or binary module file associated with this manifest. +RootModule = 'convertDscResource.psm1' + +# Version number of this module. +moduleVersion = '0.0.1' + +# ID used to uniquely identify this module +GUID = '95f93fd3-34ff-417e-80e4-f0112918a0bd' + +# Author of this module +Author = 'Microsoft Corporation' + +# Company or vendor of this module +CompanyName = 'Microsoft Corporation' + +# Copyright statement for this module +Copyright = '(c) Microsoft Corporation. All rights reserved.' + +# Description of the functionality provided by this module +Description = 'PowerShell Desired State Configuration Module for converting DSC Resources to JSON format' + +# Functions to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no functions to export. +FunctionsToExport = @( + 'Write-DscTrace' + 'Build-DscConfigDocument' + ) + +# Cmdlets to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no cmdlets to export. +CmdletsToExport = @() + +# Variables to export from this module +VariablesToExport = @() + +# Aliases to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no aliases to export. +AliasesToExport = @() + +PrivateData = @{ + PSData = @{ + ProjectUri = 'https://github.com/PowerShell/dsc' + } +} +} diff --git a/extensions/powershell/convertDscResource.psm1 b/extensions/powershell/convertDscResource.psm1 new file mode 100644 index 000000000..5d2696236 --- /dev/null +++ b/extensions/powershell/convertDscResource.psm1 @@ -0,0 +1,385 @@ +function Write-DscTrace { + param( + [Parameter(Mandatory = $false)] + [ValidateSet('Error', 'Warn', 'Info', 'Debug', 'Trace')] + [string]$Operation = 'Debug', + [Parameter(Mandatory = $true, ValueFromPipeline = $true)] + [string]$Message + ) + + $trace = @{$Operation.ToLower() = $Message } | ConvertTo-Json -Compress + $host.ui.WriteLine($trace) +} + +function Build-DscConfigDocument +{ + [CmdletBinding()] + [OutputType([System.Collections.Specialized.OrderedDictionary])] + param + ( + [Parameter(Mandatory = $true)] + [System.String] + $Content + ) + + # declare configuration document + $configurationDocument = [ordered]@{ + "`$schema" = "https://aka.ms/dsc/schemas/v3/bundled/config/document.json" + resources = @() + } + + # convert object to hashtable(s) + $dscObjects = ConvertTo-DscObject @PSBoundParameters -ErrorAction SilentlyContinue + + if (-not $dscObjects -or $dscObjects.Count -eq 0) + { + "No DSC objects found in the provided content." | Write-DscTrace -Operation Error + exit 1 + } + + # store all resources in variables + $resources = [System.Collections.Generic.List[object]]::new() + + foreach ($dscObject in $dscObjects) + { + $resource = [PSCustomObject]@{ + name = $dscObject.ResourceInstanceName + type = ("{0}/{1}" -f $dscObject.ModuleName, $dscObject.ResourceName) + properties = @() + } + + $properties = [ordered]@{} + + foreach ($dscObjectProperty in $dscObject.GetEnumerator()) + { + if ($dscObjectProperty.Key -notin @('ResourceInstanceName', 'ResourceName', 'ModuleName', 'DependsOn', 'ConfigurationName', 'Type')) + { + $properties.Add($dscObjectProperty.Key, $dscObjectProperty.Value) + } + } + + # add properties + $resource.properties = $properties + + if ($dscObject.ContainsKey('DependsOn') -and $dscObject.DependsOn) + { + $dependsOnKeys = $dscObject.DependsOn.Split("]").Replace("[", "") + + $previousGroupHash = $dscObjects | Where-Object { $_.ResourceName -eq $dependsOnKeys[0] -and $_.ResourceInstanceName -eq $dependsOnKeys[1] } + if ($previousGroupHash) + { + $dependsOnString = "[resourceId('$("{0}/{1}" -f $previousGroupHash.ModuleName, $previousGroupHash.ResourceName)','$($previousGroupHash.ResourceInstanceName)')]" + + Write-Verbose -Message "Found '$dependsOnstring' for resource: $($dscObject.ResourceInstanceName)" + # add it to the object + $resource | Add-Member -MemberType NoteProperty -Name 'dependsOn' -Value @($dependsOnString) + } + } + + $resources.Add($resource) + } + + $configurationDocument.resources = $resources + + return $configurationDocument +} + +function ConvertTo-DscObject +{ + [CmdletBinding()] + param + ( + [Parameter(Mandatory = $true)] + [System.String] + $Content + ) + + $result = @() + $Tokens = $null + $ParseErrors = $null + + # 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 'Module' -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 + { + "Retrieving module: '$($moduleToLoad.ModuleName)'" | Write-DscTrace -Operation Debug + 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 {" + # 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) + + # 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 + "Defined property {$key} was not found in resource {$resourceType}" | Write-DscTrace -Operation Warn + } + + 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 +} \ No newline at end of file diff --git a/extensions/powershell/copy_files.txt b/extensions/powershell/copy_files.txt new file mode 100644 index 000000000..96dcb7468 --- /dev/null +++ b/extensions/powershell/copy_files.txt @@ -0,0 +1,4 @@ +powershell.dsc.extension.json +convert-resource.ps1 +convertDscResource.psd1 +convertDscResource.psm1 \ No newline at end of file diff --git a/extensions/powershell/powershell.dsc.extension.json b/extensions/powershell/powershell.dsc.extension.json new file mode 100644 index 000000000..9b0781fe7 --- /dev/null +++ b/extensions/powershell/powershell.dsc.extension.json @@ -0,0 +1,22 @@ +{ + "$schema": "https://aka.ms/dsc/schemas/v3/bundled/resource/manifest.json", + "type": "Microsoft.DSC.Extension/PowerShell", + "version": "0.1.0", + "description": "Enable passing PowerShell v2 configuration document file directly to DSC, but requires pwsh executable to be available.", + "import": { + "fileExtensions": ["ps1"], + "executable": "pwsh", + "args": [ + "-NoLogo", + "-NonInteractive", + "-NoProfile", + "-ExecutionPolicy", + "Bypass", + "-Command", + { + "fileArg": "Get-Content" + }, + "$args | ./convert-resource.ps1 $args" + ] + } +} From d032063597998b4be90a64e7aa1ce14de5561668 Mon Sep 17 00:00:00 2001 From: GijsR Date: Thu, 31 Jul 2025 13:51:51 +0200 Subject: [PATCH 02/41] Uncomment section --- extensions/powershell/convert-resource.ps1 | 35 ++++++++++------------ 1 file changed, 16 insertions(+), 19 deletions(-) diff --git a/extensions/powershell/convert-resource.ps1 b/extensions/powershell/convert-resource.ps1 index 11b780bf6..c9e851c74 100644 --- a/extensions/powershell/convert-resource.ps1 +++ b/extensions/powershell/convert-resource.ps1 @@ -1,28 +1,25 @@ [CmdletBinding()] param ( [Parameter(ValueFromPipeline = $true)] - [string]$stringInput + [string[]]$stringInput ) -return "{}" +begin { + $lines = [System.Collections.Generic.List[string]]::new() -# begin { -# $lines = [System.Collections.Generic.List[string]]::new() + $scriptModule = Import-Module "$PSScriptRoot/convertDscResource.psd1" -Force -PassThru +} -# $scriptModule = Import-Module "$PSScriptRoot/convertDscResource.psd1" -Force -PassThru -# } +process { + foreach ($line in $stringInput) { + $lines.Add($line) + } +} -# process { -# # Process each line of input -# foreach ($line in $stringInput) { -# $lines.Add($line) -# } -# } +end { + if ($lines.Count -ne 0) { + $result = $scriptModule.invoke( { param($lines) Build-DscConfigDocument -Content $lines }, ($lines | Out-String) ) -# end { -# if ($lines.Count -ne 0) { -# $result = $scriptModule.invoke( { param($lines) Build-DscConfigDocument -Content $lines }, ($lines | Out-String) ) - -# return ($result | ConvertTo-Json -Depth 10) -# } -# } \ No newline at end of file + return ($result | ConvertTo-Json -Depth 10 -Compress) + } +} \ No newline at end of file From 68f6c83f2b3f9b0bd98d3eeab3c1bda01ab7d7c7 Mon Sep 17 00:00:00 2001 From: "G.Reijn" Date: Sat, 2 Aug 2025 08:35:38 +0200 Subject: [PATCH 03/41] Add tests --- dsc/examples/variable.dsc.ps1 | 13 ++++++ extensions/powershell/convert-resource.ps1 | 2 +- extensions/powershell/convertDscResource.psm1 | 22 ++++++++-- .../powershell/powershell.dsc.extension.json | 5 ++- extensions/powershell/powershell.tests.ps1 | 41 +++++++++++++++++++ 5 files changed, 77 insertions(+), 6 deletions(-) create mode 100644 dsc/examples/variable.dsc.ps1 create mode 100644 extensions/powershell/powershell.tests.ps1 diff --git a/dsc/examples/variable.dsc.ps1 b/dsc/examples/variable.dsc.ps1 new file mode 100644 index 000000000..e76d89230 --- /dev/null +++ b/dsc/examples/variable.dsc.ps1 @@ -0,0 +1,13 @@ +configuration VariableConfiguration { + Import-DscResource -ModuleName PSDesiredStateConfiguration + Node localhost + { + Environment PathEnvironmentVariable { + Name = 'TestPathEnvironmentVariable' + Value = 'TestValue' + Ensure = 'Present' + Path = $true + Target = @('Process') + } + } +} \ No newline at end of file diff --git a/extensions/powershell/convert-resource.ps1 b/extensions/powershell/convert-resource.ps1 index c9e851c74..2793f2e61 100644 --- a/extensions/powershell/convert-resource.ps1 +++ b/extensions/powershell/convert-resource.ps1 @@ -19,7 +19,7 @@ process { end { if ($lines.Count -ne 0) { $result = $scriptModule.invoke( { param($lines) Build-DscConfigDocument -Content $lines }, ($lines | Out-String) ) - + return ($result | ConvertTo-Json -Depth 10 -Compress) } } \ No newline at end of file diff --git a/extensions/powershell/convertDscResource.psm1 b/extensions/powershell/convertDscResource.psm1 index 5d2696236..7af841d22 100644 --- a/extensions/powershell/convertDscResource.psm1 +++ b/extensions/powershell/convertDscResource.psm1 @@ -1,3 +1,14 @@ +$Script:IsPowerShellCore = $PSVersionTable.PSEdition -eq 'Core' + +if ($Script:IsPowerShellCore) +{ + if ($IsWindows) + { + Import-Module -Name 'PSDesiredStateConfiguration' -RequiredVersion 1.1 -UseWindowsPowerShell -WarningAction SilentlyContinue + } + Import-Module -Name 'PSDesiredStateConfiguration' -MinimumVersion 2.0.7 -Prefix 'Pwsh' +} + function Write-DscTrace { param( [Parameter(Mandatory = $false)] @@ -178,11 +189,11 @@ function ConvertTo-DscObject 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)'" + "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)'" | Write-DscTrace -Operation Error + exit 1 } else { - "Retrieving module: '$($moduleToLoad.ModuleName)'" | Write-DscTrace -Operation Debug if ($Script:IsPowerShellCore) { $currentResources = Get-PwshDscResource -Module $moduleToLoad.ModuleName @@ -200,6 +211,12 @@ function ConvertTo-DscObject } } + if ($DSCResources.Count -eq 0) + { + "No DSC resources found in the imported modules." | Write-DscTrace -Operation Error + exit 1 + } + # Drill down # Body.ScriptBlock is the part after "Configuration {" # EndBlock is the actual code within that Configuration block @@ -310,7 +327,6 @@ function ConvertTo-DscObject if ($null -eq $valueType) { $propertyFound = $false - "Defined property {$key} was not found in resource {$resourceType}" | Write-DscTrace -Operation Warn } if ($propertyFound) diff --git a/extensions/powershell/powershell.dsc.extension.json b/extensions/powershell/powershell.dsc.extension.json index 9b0781fe7..36f6d32b8 100644 --- a/extensions/powershell/powershell.dsc.extension.json +++ b/extensions/powershell/powershell.dsc.extension.json @@ -13,10 +13,11 @@ "-ExecutionPolicy", "Bypass", "-Command", + "Get-Content", { - "fileArg": "Get-Content" + "fileArg": "" }, - "$args | ./convert-resource.ps1 $args" + "| ./convert-resource.ps1" ] } } diff --git a/extensions/powershell/powershell.tests.ps1 b/extensions/powershell/powershell.tests.ps1 new file mode 100644 index 000000000..0d0f40a04 --- /dev/null +++ b/extensions/powershell/powershell.tests.ps1 @@ -0,0 +1,41 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +BeforeDiscovery { + if ($IsWindows) { + $identity = [System.Security.Principal.WindowsIdentity]::GetCurrent() + $principal = [System.Security.Principal.WindowsPrincipal]::new($identity) + $isElevated = $principal.IsInRole([System.Security.Principal.WindowsBuiltInRole]::Administrator) + } +} + +Describe 'PowerShell extension tests' { + It 'Example PowerShell file should work' -Skip:(!$isElevated) { + $psFile = Resolve-Path -Path "$PSScriptRoot\..\..\dsc\examples\variable.dsc.ps1" + $out = dsc -l trace config get -f $psFile 2>$TestDrive/error.log | ConvertFrom-Json + $LASTEXITCODE | Should -Be 0 -Because (Get-Content -Path $TestDrive/error.log -Raw | Out-String) + $out.results[0].result.actualState.Ensure | Should -Be 'Absent' + (Get-Content -Path $TestDrive/error.log -Raw) | Should -Match "Importing file '$psFile' with extension 'Microsoft.DSC.Extension/PowerShell'" + } + + It 'Invalid PowerShell configuration document file returns error' { + $psFile = "$TestDrive/invalid.ps1" + Set-Content -Path $psFile -Value @" +configuration InvalidConfiguration { + Import-DscResource -ModuleName InvalidModule + Node localhost + { + Test Invalid { + Name = 'InvalidTest' + Ensure = 'Present' + } + } +} +"@ + $out = dsc -l trace config get -f $psFile 2>$TestDrive/error.log | ConvertFrom-Json + $LASTEXITCODE | Should -Be 2 -Because (Get-Content -Path $TestDrive/error.log -Raw | Out-String) + $content = (Get-Content -Path $TestDrive/error.log -Raw) + $content | Should -BeLike "*Importing file '$psFile' with extension 'Microsoft.DSC.Extension/PowerShell'*" + $content | Should -Match "No DSC resources found in the imported modules." + } +} From ba03899c931bf25a8b1ae131aba7273737defb67 Mon Sep 17 00:00:00 2001 From: "G.Reijn" Date: Sat, 2 Aug 2025 10:16:30 +0200 Subject: [PATCH 04/41] Copy items for other OS --- build.ps1 | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/build.ps1 b/build.ps1 index 4e09d7b2c..6809bfe79 100755 --- a/build.ps1 +++ b/build.ps1 @@ -83,9 +83,12 @@ $filesForWindowsPackage = @( $filesForLinuxPackage = @( 'bicep.dsc.extension.json', + 'convert-resource.ps1', + 'convertDscResource.psd1', + 'convertDscResource.psm1', 'dsc', 'dsc_default.settings.json', - 'dsc.settings.json' + 'dsc.settings.json', 'dscecho', 'echo.dsc.resource.json', 'assertion.dsc.resource.json', @@ -97,6 +100,7 @@ $filesForLinuxPackage = @( 'osinfo', 'osinfo.dsc.resource.json', 'powershell.dsc.resource.json', + 'powershell.dsc.extension.json' 'psDscAdapter/', 'psscript.ps1', 'psscript.dsc.resource.json', @@ -108,9 +112,12 @@ $filesForLinuxPackage = @( $filesForMacPackage = @( 'bicep.dsc.extension.json', + 'convert-resource.ps1', + 'convertDscResource.psd1', + 'convertDscResource.psm1', 'dsc', 'dsc_default.settings.json', - 'dsc.settings.json' + 'dsc.settings.json', 'dscecho', 'echo.dsc.resource.json', 'assertion.dsc.resource.json', @@ -122,6 +129,7 @@ $filesForMacPackage = @( 'osinfo', 'osinfo.dsc.resource.json', 'powershell.dsc.resource.json', + 'powershell.dsc.extension.json' 'psDscAdapter/', 'psscript.ps1', 'psscript.dsc.resource.json', From 0682b3917c6f7867c8b96b6432866f577cd9cda8 Mon Sep 17 00:00:00 2001 From: "G.Reijn" Date: Sun, 3 Aug 2025 05:18:50 +0200 Subject: [PATCH 05/41] Update to Windows PowerShell --- build.ps1 | 8 -------- .../{powershell.tests.ps1 => win_powershell.tests.ps1} | 4 ++-- ...xtension.json => windowspowershell.dsc.extension.json} | 4 ++-- 3 files changed, 4 insertions(+), 12 deletions(-) rename extensions/powershell/{powershell.tests.ps1 => win_powershell.tests.ps1} (94%) rename extensions/powershell/{powershell.dsc.extension.json => windowspowershell.dsc.extension.json} (75%) diff --git a/build.ps1 b/build.ps1 index 6809bfe79..f424002db 100755 --- a/build.ps1 +++ b/build.ps1 @@ -83,9 +83,6 @@ $filesForWindowsPackage = @( $filesForLinuxPackage = @( 'bicep.dsc.extension.json', - 'convert-resource.ps1', - 'convertDscResource.psd1', - 'convertDscResource.psm1', 'dsc', 'dsc_default.settings.json', 'dsc.settings.json', @@ -100,7 +97,6 @@ $filesForLinuxPackage = @( 'osinfo', 'osinfo.dsc.resource.json', 'powershell.dsc.resource.json', - 'powershell.dsc.extension.json' 'psDscAdapter/', 'psscript.ps1', 'psscript.dsc.resource.json', @@ -112,9 +108,6 @@ $filesForLinuxPackage = @( $filesForMacPackage = @( 'bicep.dsc.extension.json', - 'convert-resource.ps1', - 'convertDscResource.psd1', - 'convertDscResource.psm1', 'dsc', 'dsc_default.settings.json', 'dsc.settings.json', @@ -129,7 +122,6 @@ $filesForMacPackage = @( 'osinfo', 'osinfo.dsc.resource.json', 'powershell.dsc.resource.json', - 'powershell.dsc.extension.json' 'psDscAdapter/', 'psscript.ps1', 'psscript.dsc.resource.json', diff --git a/extensions/powershell/powershell.tests.ps1 b/extensions/powershell/win_powershell.tests.ps1 similarity index 94% rename from extensions/powershell/powershell.tests.ps1 rename to extensions/powershell/win_powershell.tests.ps1 index 0d0f40a04..a37c6fe88 100644 --- a/extensions/powershell/powershell.tests.ps1 +++ b/extensions/powershell/win_powershell.tests.ps1 @@ -10,7 +10,7 @@ BeforeDiscovery { } Describe 'PowerShell extension tests' { - It 'Example PowerShell file should work' -Skip:(!$isElevated) { + It 'Example PowerShell file should work' -Skip:(!$IsWindows -or !$isElevated) { $psFile = Resolve-Path -Path "$PSScriptRoot\..\..\dsc\examples\variable.dsc.ps1" $out = dsc -l trace config get -f $psFile 2>$TestDrive/error.log | ConvertFrom-Json $LASTEXITCODE | Should -Be 0 -Because (Get-Content -Path $TestDrive/error.log -Raw | Out-String) @@ -18,7 +18,7 @@ Describe 'PowerShell extension tests' { (Get-Content -Path $TestDrive/error.log -Raw) | Should -Match "Importing file '$psFile' with extension 'Microsoft.DSC.Extension/PowerShell'" } - It 'Invalid PowerShell configuration document file returns error' { + It 'Invalid PowerShell configuration document file returns error' -Skip:(!$IsWindows) { $psFile = "$TestDrive/invalid.ps1" Set-Content -Path $psFile -Value @" configuration InvalidConfiguration { diff --git a/extensions/powershell/powershell.dsc.extension.json b/extensions/powershell/windowspowershell.dsc.extension.json similarity index 75% rename from extensions/powershell/powershell.dsc.extension.json rename to extensions/powershell/windowspowershell.dsc.extension.json index 36f6d32b8..dd3a4a3f7 100644 --- a/extensions/powershell/powershell.dsc.extension.json +++ b/extensions/powershell/windowspowershell.dsc.extension.json @@ -2,10 +2,10 @@ "$schema": "https://aka.ms/dsc/schemas/v3/bundled/resource/manifest.json", "type": "Microsoft.DSC.Extension/PowerShell", "version": "0.1.0", - "description": "Enable passing PowerShell v2 configuration document file directly to DSC, but requires pwsh executable to be available.", + "description": "Enable passing PowerShell v1 configuration document file directly to DSC. Works only on Windows.", "import": { "fileExtensions": ["ps1"], - "executable": "pwsh", + "executable": "powershell", "args": [ "-NoLogo", "-NonInteractive", From fb8ba6ce2ab54fdb4eec8ef2e0f33b51a4b271f6 Mon Sep 17 00:00:00 2001 From: GijsR Date: Thu, 31 Jul 2025 13:45:53 +0200 Subject: [PATCH 06/41] Implement PowerShell import extension --- build.ps1 | 5 + extensions/powershell/convert-resource.ps1 | 28 ++ extensions/powershell/convertDscResource.psd1 | 47 +++ extensions/powershell/convertDscResource.psm1 | 385 ++++++++++++++++++ extensions/powershell/copy_files.txt | 4 + .../powershell/powershell.dsc.extension.json | 22 + 6 files changed, 491 insertions(+) create mode 100644 extensions/powershell/convert-resource.ps1 create mode 100644 extensions/powershell/convertDscResource.psd1 create mode 100644 extensions/powershell/convertDscResource.psm1 create mode 100644 extensions/powershell/copy_files.txt create mode 100644 extensions/powershell/powershell.dsc.extension.json diff --git a/build.ps1 b/build.ps1 index 5cb6b3922..27b9dfd81 100755 --- a/build.ps1 +++ b/build.ps1 @@ -48,6 +48,9 @@ $filesForWindowsPackage = @( 'appx.dsc.extension.json', 'appx-discover.ps1', 'bicep.dsc.extension.json', + 'convert-resource.ps1', + 'convertDscResource.psd1', + 'convertDscResource.psm1', 'dsc.exe', 'dsc_default.settings.json', 'dsc.settings.json', @@ -60,6 +63,7 @@ $filesForWindowsPackage = @( 'osinfo.exe', 'osinfo.dsc.resource.json', 'powershell.dsc.resource.json', + 'powershell.dsc.extension.json' 'psDscAdapter/', 'psscript.ps1', 'psscript.dsc.resource.json', @@ -316,6 +320,7 @@ if (!$SkipBuild) { "dsc", "dscecho", "extensions/bicep", + "extensions/powershell" "osinfo", "powershell-adapter", 'resources/PSScript', diff --git a/extensions/powershell/convert-resource.ps1 b/extensions/powershell/convert-resource.ps1 new file mode 100644 index 000000000..11b780bf6 --- /dev/null +++ b/extensions/powershell/convert-resource.ps1 @@ -0,0 +1,28 @@ +[CmdletBinding()] +param ( + [Parameter(ValueFromPipeline = $true)] + [string]$stringInput +) + +return "{}" + +# begin { +# $lines = [System.Collections.Generic.List[string]]::new() + +# $scriptModule = Import-Module "$PSScriptRoot/convertDscResource.psd1" -Force -PassThru +# } + +# process { +# # Process each line of input +# foreach ($line in $stringInput) { +# $lines.Add($line) +# } +# } + +# end { +# if ($lines.Count -ne 0) { +# $result = $scriptModule.invoke( { param($lines) Build-DscConfigDocument -Content $lines }, ($lines | Out-String) ) + +# return ($result | ConvertTo-Json -Depth 10) +# } +# } \ No newline at end of file diff --git a/extensions/powershell/convertDscResource.psd1 b/extensions/powershell/convertDscResource.psd1 new file mode 100644 index 000000000..3cf481384 --- /dev/null +++ b/extensions/powershell/convertDscResource.psd1 @@ -0,0 +1,47 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +@{ + +# Script module or binary module file associated with this manifest. +RootModule = 'convertDscResource.psm1' + +# Version number of this module. +moduleVersion = '0.0.1' + +# ID used to uniquely identify this module +GUID = '95f93fd3-34ff-417e-80e4-f0112918a0bd' + +# Author of this module +Author = 'Microsoft Corporation' + +# Company or vendor of this module +CompanyName = 'Microsoft Corporation' + +# Copyright statement for this module +Copyright = '(c) Microsoft Corporation. All rights reserved.' + +# Description of the functionality provided by this module +Description = 'PowerShell Desired State Configuration Module for converting DSC Resources to JSON format' + +# Functions to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no functions to export. +FunctionsToExport = @( + 'Write-DscTrace' + 'Build-DscConfigDocument' + ) + +# Cmdlets to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no cmdlets to export. +CmdletsToExport = @() + +# Variables to export from this module +VariablesToExport = @() + +# Aliases to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no aliases to export. +AliasesToExport = @() + +PrivateData = @{ + PSData = @{ + ProjectUri = 'https://github.com/PowerShell/dsc' + } +} +} diff --git a/extensions/powershell/convertDscResource.psm1 b/extensions/powershell/convertDscResource.psm1 new file mode 100644 index 000000000..5d2696236 --- /dev/null +++ b/extensions/powershell/convertDscResource.psm1 @@ -0,0 +1,385 @@ +function Write-DscTrace { + param( + [Parameter(Mandatory = $false)] + [ValidateSet('Error', 'Warn', 'Info', 'Debug', 'Trace')] + [string]$Operation = 'Debug', + [Parameter(Mandatory = $true, ValueFromPipeline = $true)] + [string]$Message + ) + + $trace = @{$Operation.ToLower() = $Message } | ConvertTo-Json -Compress + $host.ui.WriteLine($trace) +} + +function Build-DscConfigDocument +{ + [CmdletBinding()] + [OutputType([System.Collections.Specialized.OrderedDictionary])] + param + ( + [Parameter(Mandatory = $true)] + [System.String] + $Content + ) + + # declare configuration document + $configurationDocument = [ordered]@{ + "`$schema" = "https://aka.ms/dsc/schemas/v3/bundled/config/document.json" + resources = @() + } + + # convert object to hashtable(s) + $dscObjects = ConvertTo-DscObject @PSBoundParameters -ErrorAction SilentlyContinue + + if (-not $dscObjects -or $dscObjects.Count -eq 0) + { + "No DSC objects found in the provided content." | Write-DscTrace -Operation Error + exit 1 + } + + # store all resources in variables + $resources = [System.Collections.Generic.List[object]]::new() + + foreach ($dscObject in $dscObjects) + { + $resource = [PSCustomObject]@{ + name = $dscObject.ResourceInstanceName + type = ("{0}/{1}" -f $dscObject.ModuleName, $dscObject.ResourceName) + properties = @() + } + + $properties = [ordered]@{} + + foreach ($dscObjectProperty in $dscObject.GetEnumerator()) + { + if ($dscObjectProperty.Key -notin @('ResourceInstanceName', 'ResourceName', 'ModuleName', 'DependsOn', 'ConfigurationName', 'Type')) + { + $properties.Add($dscObjectProperty.Key, $dscObjectProperty.Value) + } + } + + # add properties + $resource.properties = $properties + + if ($dscObject.ContainsKey('DependsOn') -and $dscObject.DependsOn) + { + $dependsOnKeys = $dscObject.DependsOn.Split("]").Replace("[", "") + + $previousGroupHash = $dscObjects | Where-Object { $_.ResourceName -eq $dependsOnKeys[0] -and $_.ResourceInstanceName -eq $dependsOnKeys[1] } + if ($previousGroupHash) + { + $dependsOnString = "[resourceId('$("{0}/{1}" -f $previousGroupHash.ModuleName, $previousGroupHash.ResourceName)','$($previousGroupHash.ResourceInstanceName)')]" + + Write-Verbose -Message "Found '$dependsOnstring' for resource: $($dscObject.ResourceInstanceName)" + # add it to the object + $resource | Add-Member -MemberType NoteProperty -Name 'dependsOn' -Value @($dependsOnString) + } + } + + $resources.Add($resource) + } + + $configurationDocument.resources = $resources + + return $configurationDocument +} + +function ConvertTo-DscObject +{ + [CmdletBinding()] + param + ( + [Parameter(Mandatory = $true)] + [System.String] + $Content + ) + + $result = @() + $Tokens = $null + $ParseErrors = $null + + # 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 'Module' -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 + { + "Retrieving module: '$($moduleToLoad.ModuleName)'" | Write-DscTrace -Operation Debug + 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 {" + # 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) + + # 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 + "Defined property {$key} was not found in resource {$resourceType}" | Write-DscTrace -Operation Warn + } + + 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 +} \ No newline at end of file diff --git a/extensions/powershell/copy_files.txt b/extensions/powershell/copy_files.txt new file mode 100644 index 000000000..96dcb7468 --- /dev/null +++ b/extensions/powershell/copy_files.txt @@ -0,0 +1,4 @@ +powershell.dsc.extension.json +convert-resource.ps1 +convertDscResource.psd1 +convertDscResource.psm1 \ No newline at end of file diff --git a/extensions/powershell/powershell.dsc.extension.json b/extensions/powershell/powershell.dsc.extension.json new file mode 100644 index 000000000..9b0781fe7 --- /dev/null +++ b/extensions/powershell/powershell.dsc.extension.json @@ -0,0 +1,22 @@ +{ + "$schema": "https://aka.ms/dsc/schemas/v3/bundled/resource/manifest.json", + "type": "Microsoft.DSC.Extension/PowerShell", + "version": "0.1.0", + "description": "Enable passing PowerShell v2 configuration document file directly to DSC, but requires pwsh executable to be available.", + "import": { + "fileExtensions": ["ps1"], + "executable": "pwsh", + "args": [ + "-NoLogo", + "-NonInteractive", + "-NoProfile", + "-ExecutionPolicy", + "Bypass", + "-Command", + { + "fileArg": "Get-Content" + }, + "$args | ./convert-resource.ps1 $args" + ] + } +} From e3e43b31d01e5b8399c3d84a751b8a77113cc0bc Mon Sep 17 00:00:00 2001 From: GijsR Date: Thu, 31 Jul 2025 13:51:51 +0200 Subject: [PATCH 07/41] Uncomment section --- extensions/powershell/convert-resource.ps1 | 35 ++++++++++------------ 1 file changed, 16 insertions(+), 19 deletions(-) diff --git a/extensions/powershell/convert-resource.ps1 b/extensions/powershell/convert-resource.ps1 index 11b780bf6..c9e851c74 100644 --- a/extensions/powershell/convert-resource.ps1 +++ b/extensions/powershell/convert-resource.ps1 @@ -1,28 +1,25 @@ [CmdletBinding()] param ( [Parameter(ValueFromPipeline = $true)] - [string]$stringInput + [string[]]$stringInput ) -return "{}" +begin { + $lines = [System.Collections.Generic.List[string]]::new() -# begin { -# $lines = [System.Collections.Generic.List[string]]::new() + $scriptModule = Import-Module "$PSScriptRoot/convertDscResource.psd1" -Force -PassThru +} -# $scriptModule = Import-Module "$PSScriptRoot/convertDscResource.psd1" -Force -PassThru -# } +process { + foreach ($line in $stringInput) { + $lines.Add($line) + } +} -# process { -# # Process each line of input -# foreach ($line in $stringInput) { -# $lines.Add($line) -# } -# } +end { + if ($lines.Count -ne 0) { + $result = $scriptModule.invoke( { param($lines) Build-DscConfigDocument -Content $lines }, ($lines | Out-String) ) -# end { -# if ($lines.Count -ne 0) { -# $result = $scriptModule.invoke( { param($lines) Build-DscConfigDocument -Content $lines }, ($lines | Out-String) ) - -# return ($result | ConvertTo-Json -Depth 10) -# } -# } \ No newline at end of file + return ($result | ConvertTo-Json -Depth 10 -Compress) + } +} \ No newline at end of file From 0a3b5fd0237d8c138e3cffb561f48497be152cb6 Mon Sep 17 00:00:00 2001 From: "G.Reijn" Date: Sat, 2 Aug 2025 08:35:38 +0200 Subject: [PATCH 08/41] Add tests --- dsc/examples/variable.dsc.ps1 | 13 ++++++ extensions/powershell/convert-resource.ps1 | 2 +- extensions/powershell/convertDscResource.psm1 | 22 ++++++++-- .../powershell/powershell.dsc.extension.json | 5 ++- extensions/powershell/powershell.tests.ps1 | 41 +++++++++++++++++++ 5 files changed, 77 insertions(+), 6 deletions(-) create mode 100644 dsc/examples/variable.dsc.ps1 create mode 100644 extensions/powershell/powershell.tests.ps1 diff --git a/dsc/examples/variable.dsc.ps1 b/dsc/examples/variable.dsc.ps1 new file mode 100644 index 000000000..e76d89230 --- /dev/null +++ b/dsc/examples/variable.dsc.ps1 @@ -0,0 +1,13 @@ +configuration VariableConfiguration { + Import-DscResource -ModuleName PSDesiredStateConfiguration + Node localhost + { + Environment PathEnvironmentVariable { + Name = 'TestPathEnvironmentVariable' + Value = 'TestValue' + Ensure = 'Present' + Path = $true + Target = @('Process') + } + } +} \ No newline at end of file diff --git a/extensions/powershell/convert-resource.ps1 b/extensions/powershell/convert-resource.ps1 index c9e851c74..2793f2e61 100644 --- a/extensions/powershell/convert-resource.ps1 +++ b/extensions/powershell/convert-resource.ps1 @@ -19,7 +19,7 @@ process { end { if ($lines.Count -ne 0) { $result = $scriptModule.invoke( { param($lines) Build-DscConfigDocument -Content $lines }, ($lines | Out-String) ) - + return ($result | ConvertTo-Json -Depth 10 -Compress) } } \ No newline at end of file diff --git a/extensions/powershell/convertDscResource.psm1 b/extensions/powershell/convertDscResource.psm1 index 5d2696236..7af841d22 100644 --- a/extensions/powershell/convertDscResource.psm1 +++ b/extensions/powershell/convertDscResource.psm1 @@ -1,3 +1,14 @@ +$Script:IsPowerShellCore = $PSVersionTable.PSEdition -eq 'Core' + +if ($Script:IsPowerShellCore) +{ + if ($IsWindows) + { + Import-Module -Name 'PSDesiredStateConfiguration' -RequiredVersion 1.1 -UseWindowsPowerShell -WarningAction SilentlyContinue + } + Import-Module -Name 'PSDesiredStateConfiguration' -MinimumVersion 2.0.7 -Prefix 'Pwsh' +} + function Write-DscTrace { param( [Parameter(Mandatory = $false)] @@ -178,11 +189,11 @@ function ConvertTo-DscObject 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)'" + "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)'" | Write-DscTrace -Operation Error + exit 1 } else { - "Retrieving module: '$($moduleToLoad.ModuleName)'" | Write-DscTrace -Operation Debug if ($Script:IsPowerShellCore) { $currentResources = Get-PwshDscResource -Module $moduleToLoad.ModuleName @@ -200,6 +211,12 @@ function ConvertTo-DscObject } } + if ($DSCResources.Count -eq 0) + { + "No DSC resources found in the imported modules." | Write-DscTrace -Operation Error + exit 1 + } + # Drill down # Body.ScriptBlock is the part after "Configuration {" # EndBlock is the actual code within that Configuration block @@ -310,7 +327,6 @@ function ConvertTo-DscObject if ($null -eq $valueType) { $propertyFound = $false - "Defined property {$key} was not found in resource {$resourceType}" | Write-DscTrace -Operation Warn } if ($propertyFound) diff --git a/extensions/powershell/powershell.dsc.extension.json b/extensions/powershell/powershell.dsc.extension.json index 9b0781fe7..36f6d32b8 100644 --- a/extensions/powershell/powershell.dsc.extension.json +++ b/extensions/powershell/powershell.dsc.extension.json @@ -13,10 +13,11 @@ "-ExecutionPolicy", "Bypass", "-Command", + "Get-Content", { - "fileArg": "Get-Content" + "fileArg": "" }, - "$args | ./convert-resource.ps1 $args" + "| ./convert-resource.ps1" ] } } diff --git a/extensions/powershell/powershell.tests.ps1 b/extensions/powershell/powershell.tests.ps1 new file mode 100644 index 000000000..0d0f40a04 --- /dev/null +++ b/extensions/powershell/powershell.tests.ps1 @@ -0,0 +1,41 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +BeforeDiscovery { + if ($IsWindows) { + $identity = [System.Security.Principal.WindowsIdentity]::GetCurrent() + $principal = [System.Security.Principal.WindowsPrincipal]::new($identity) + $isElevated = $principal.IsInRole([System.Security.Principal.WindowsBuiltInRole]::Administrator) + } +} + +Describe 'PowerShell extension tests' { + It 'Example PowerShell file should work' -Skip:(!$isElevated) { + $psFile = Resolve-Path -Path "$PSScriptRoot\..\..\dsc\examples\variable.dsc.ps1" + $out = dsc -l trace config get -f $psFile 2>$TestDrive/error.log | ConvertFrom-Json + $LASTEXITCODE | Should -Be 0 -Because (Get-Content -Path $TestDrive/error.log -Raw | Out-String) + $out.results[0].result.actualState.Ensure | Should -Be 'Absent' + (Get-Content -Path $TestDrive/error.log -Raw) | Should -Match "Importing file '$psFile' with extension 'Microsoft.DSC.Extension/PowerShell'" + } + + It 'Invalid PowerShell configuration document file returns error' { + $psFile = "$TestDrive/invalid.ps1" + Set-Content -Path $psFile -Value @" +configuration InvalidConfiguration { + Import-DscResource -ModuleName InvalidModule + Node localhost + { + Test Invalid { + Name = 'InvalidTest' + Ensure = 'Present' + } + } +} +"@ + $out = dsc -l trace config get -f $psFile 2>$TestDrive/error.log | ConvertFrom-Json + $LASTEXITCODE | Should -Be 2 -Because (Get-Content -Path $TestDrive/error.log -Raw | Out-String) + $content = (Get-Content -Path $TestDrive/error.log -Raw) + $content | Should -BeLike "*Importing file '$psFile' with extension 'Microsoft.DSC.Extension/PowerShell'*" + $content | Should -Match "No DSC resources found in the imported modules." + } +} From 71d475fa486975efc6bcf1af093c79e88345fbc1 Mon Sep 17 00:00:00 2001 From: "G.Reijn" Date: Sat, 2 Aug 2025 10:16:30 +0200 Subject: [PATCH 09/41] Copy items for other OS --- build.ps1 | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/build.ps1 b/build.ps1 index 27b9dfd81..6441fa82c 100755 --- a/build.ps1 +++ b/build.ps1 @@ -88,9 +88,12 @@ $filesForWindowsPackage = @( $filesForLinuxPackage = @( 'bicep.dsc.extension.json', + 'convert-resource.ps1', + 'convertDscResource.psd1', + 'convertDscResource.psm1', 'dsc', 'dsc_default.settings.json', - 'dsc.settings.json' + 'dsc.settings.json', 'dscecho', 'echo.dsc.resource.json', 'assertion.dsc.resource.json', @@ -102,6 +105,7 @@ $filesForLinuxPackage = @( 'osinfo', 'osinfo.dsc.resource.json', 'powershell.dsc.resource.json', + 'powershell.dsc.extension.json' 'psDscAdapter/', 'psscript.ps1', 'psscript.dsc.resource.json', @@ -113,9 +117,12 @@ $filesForLinuxPackage = @( $filesForMacPackage = @( 'bicep.dsc.extension.json', + 'convert-resource.ps1', + 'convertDscResource.psd1', + 'convertDscResource.psm1', 'dsc', 'dsc_default.settings.json', - 'dsc.settings.json' + 'dsc.settings.json', 'dscecho', 'echo.dsc.resource.json', 'assertion.dsc.resource.json', @@ -127,6 +134,7 @@ $filesForMacPackage = @( 'osinfo', 'osinfo.dsc.resource.json', 'powershell.dsc.resource.json', + 'powershell.dsc.extension.json' 'psDscAdapter/', 'psscript.ps1', 'psscript.dsc.resource.json', From 5fdc34d80368f095ff6aba7028a62aa0ada74bfe Mon Sep 17 00:00:00 2001 From: "G.Reijn" Date: Sun, 3 Aug 2025 05:18:50 +0200 Subject: [PATCH 10/41] Update to Windows PowerShell --- build.ps1 | 8 -------- .../{powershell.tests.ps1 => win_powershell.tests.ps1} | 4 ++-- ...xtension.json => windowspowershell.dsc.extension.json} | 4 ++-- 3 files changed, 4 insertions(+), 12 deletions(-) rename extensions/powershell/{powershell.tests.ps1 => win_powershell.tests.ps1} (94%) rename extensions/powershell/{powershell.dsc.extension.json => windowspowershell.dsc.extension.json} (75%) diff --git a/build.ps1 b/build.ps1 index 6441fa82c..51eb446e9 100755 --- a/build.ps1 +++ b/build.ps1 @@ -88,9 +88,6 @@ $filesForWindowsPackage = @( $filesForLinuxPackage = @( 'bicep.dsc.extension.json', - 'convert-resource.ps1', - 'convertDscResource.psd1', - 'convertDscResource.psm1', 'dsc', 'dsc_default.settings.json', 'dsc.settings.json', @@ -105,7 +102,6 @@ $filesForLinuxPackage = @( 'osinfo', 'osinfo.dsc.resource.json', 'powershell.dsc.resource.json', - 'powershell.dsc.extension.json' 'psDscAdapter/', 'psscript.ps1', 'psscript.dsc.resource.json', @@ -117,9 +113,6 @@ $filesForLinuxPackage = @( $filesForMacPackage = @( 'bicep.dsc.extension.json', - 'convert-resource.ps1', - 'convertDscResource.psd1', - 'convertDscResource.psm1', 'dsc', 'dsc_default.settings.json', 'dsc.settings.json', @@ -134,7 +127,6 @@ $filesForMacPackage = @( 'osinfo', 'osinfo.dsc.resource.json', 'powershell.dsc.resource.json', - 'powershell.dsc.extension.json' 'psDscAdapter/', 'psscript.ps1', 'psscript.dsc.resource.json', diff --git a/extensions/powershell/powershell.tests.ps1 b/extensions/powershell/win_powershell.tests.ps1 similarity index 94% rename from extensions/powershell/powershell.tests.ps1 rename to extensions/powershell/win_powershell.tests.ps1 index 0d0f40a04..a37c6fe88 100644 --- a/extensions/powershell/powershell.tests.ps1 +++ b/extensions/powershell/win_powershell.tests.ps1 @@ -10,7 +10,7 @@ BeforeDiscovery { } Describe 'PowerShell extension tests' { - It 'Example PowerShell file should work' -Skip:(!$isElevated) { + It 'Example PowerShell file should work' -Skip:(!$IsWindows -or !$isElevated) { $psFile = Resolve-Path -Path "$PSScriptRoot\..\..\dsc\examples\variable.dsc.ps1" $out = dsc -l trace config get -f $psFile 2>$TestDrive/error.log | ConvertFrom-Json $LASTEXITCODE | Should -Be 0 -Because (Get-Content -Path $TestDrive/error.log -Raw | Out-String) @@ -18,7 +18,7 @@ Describe 'PowerShell extension tests' { (Get-Content -Path $TestDrive/error.log -Raw) | Should -Match "Importing file '$psFile' with extension 'Microsoft.DSC.Extension/PowerShell'" } - It 'Invalid PowerShell configuration document file returns error' { + It 'Invalid PowerShell configuration document file returns error' -Skip:(!$IsWindows) { $psFile = "$TestDrive/invalid.ps1" Set-Content -Path $psFile -Value @" configuration InvalidConfiguration { diff --git a/extensions/powershell/powershell.dsc.extension.json b/extensions/powershell/windowspowershell.dsc.extension.json similarity index 75% rename from extensions/powershell/powershell.dsc.extension.json rename to extensions/powershell/windowspowershell.dsc.extension.json index 36f6d32b8..dd3a4a3f7 100644 --- a/extensions/powershell/powershell.dsc.extension.json +++ b/extensions/powershell/windowspowershell.dsc.extension.json @@ -2,10 +2,10 @@ "$schema": "https://aka.ms/dsc/schemas/v3/bundled/resource/manifest.json", "type": "Microsoft.DSC.Extension/PowerShell", "version": "0.1.0", - "description": "Enable passing PowerShell v2 configuration document file directly to DSC, but requires pwsh executable to be available.", + "description": "Enable passing PowerShell v1 configuration document file directly to DSC. Works only on Windows.", "import": { "fileExtensions": ["ps1"], - "executable": "pwsh", + "executable": "powershell", "args": [ "-NoLogo", "-NonInteractive", From 1129edc6ae29dabdd3a5c371097767bd7f9206b6 Mon Sep 17 00:00:00 2001 From: "G.Reijn" Date: Fri, 8 Aug 2025 09:33:19 +0200 Subject: [PATCH 11/41] Fix tests --- build.ps1 | 2 +- extensions/powershell/convert-resource.ps1 | 4 ++-- extensions/powershell/convertDscResource.psd1 | 2 +- extensions/powershell/convertDscResource.psm1 | 13 +------------ extensions/powershell/copy_files.txt | 2 +- extensions/powershell/win_powershell.tests.ps1 | 2 +- .../powershell/windowspowershell.dsc.extension.json | 4 ++-- 7 files changed, 9 insertions(+), 20 deletions(-) diff --git a/build.ps1 b/build.ps1 index 51eb446e9..c5dbcffd4 100755 --- a/build.ps1 +++ b/build.ps1 @@ -63,7 +63,7 @@ $filesForWindowsPackage = @( 'osinfo.exe', 'osinfo.dsc.resource.json', 'powershell.dsc.resource.json', - 'powershell.dsc.extension.json' + 'windowspowershell.dsc.extension.json' 'psDscAdapter/', 'psscript.ps1', 'psscript.dsc.resource.json', diff --git a/extensions/powershell/convert-resource.ps1 b/extensions/powershell/convert-resource.ps1 index 2793f2e61..55bf3af39 100644 --- a/extensions/powershell/convert-resource.ps1 +++ b/extensions/powershell/convert-resource.ps1 @@ -7,7 +7,7 @@ param ( begin { $lines = [System.Collections.Generic.List[string]]::new() - $scriptModule = Import-Module "$PSScriptRoot/convertDscResource.psd1" -Force -PassThru + $scriptModule = Import-Module "$PSScriptRoot/convertDscResource.psd1" -Force -PassThru -ErrorAction Ignore } process { @@ -19,7 +19,7 @@ process { end { if ($lines.Count -ne 0) { $result = $scriptModule.invoke( { param($lines) Build-DscConfigDocument -Content $lines }, ($lines | Out-String) ) - + return ($result | ConvertTo-Json -Depth 10 -Compress) } } \ No newline at end of file diff --git a/extensions/powershell/convertDscResource.psd1 b/extensions/powershell/convertDscResource.psd1 index 3cf481384..ac0a1db93 100644 --- a/extensions/powershell/convertDscResource.psd1 +++ b/extensions/powershell/convertDscResource.psd1 @@ -41,7 +41,7 @@ AliasesToExport = @() PrivateData = @{ PSData = @{ - ProjectUri = 'https://github.com/PowerShell/dsc' + ProjectUri = 'https://github.com/PowerShell/DSC' } } } diff --git a/extensions/powershell/convertDscResource.psm1 b/extensions/powershell/convertDscResource.psm1 index 7af841d22..a9907090d 100644 --- a/extensions/powershell/convertDscResource.psm1 +++ b/extensions/powershell/convertDscResource.psm1 @@ -1,14 +1,3 @@ -$Script:IsPowerShellCore = $PSVersionTable.PSEdition -eq 'Core' - -if ($Script:IsPowerShellCore) -{ - if ($IsWindows) - { - Import-Module -Name 'PSDesiredStateConfiguration' -RequiredVersion 1.1 -UseWindowsPowerShell -WarningAction SilentlyContinue - } - Import-Module -Name 'PSDesiredStateConfiguration' -MinimumVersion 2.0.7 -Prefix 'Pwsh' -} - function Write-DscTrace { param( [Parameter(Mandatory = $false)] @@ -19,7 +8,7 @@ function Write-DscTrace { ) $trace = @{$Operation.ToLower() = $Message } | ConvertTo-Json -Compress - $host.ui.WriteLine($trace) + $host.ui.WriteErrorLine($trace) } function Build-DscConfigDocument diff --git a/extensions/powershell/copy_files.txt b/extensions/powershell/copy_files.txt index 96dcb7468..9ab85b46b 100644 --- a/extensions/powershell/copy_files.txt +++ b/extensions/powershell/copy_files.txt @@ -1,4 +1,4 @@ -powershell.dsc.extension.json +windowspowershell.dsc.extension.json convert-resource.ps1 convertDscResource.psd1 convertDscResource.psm1 \ No newline at end of file diff --git a/extensions/powershell/win_powershell.tests.ps1 b/extensions/powershell/win_powershell.tests.ps1 index a37c6fe88..57cf71392 100644 --- a/extensions/powershell/win_powershell.tests.ps1 +++ b/extensions/powershell/win_powershell.tests.ps1 @@ -32,7 +32,7 @@ configuration InvalidConfiguration { } } "@ - $out = dsc -l trace config get -f $psFile 2>$TestDrive/error.log | ConvertFrom-Json + dsc -l trace config get -f $psFile 2>$TestDrive/error.log $LASTEXITCODE | Should -Be 2 -Because (Get-Content -Path $TestDrive/error.log -Raw | Out-String) $content = (Get-Content -Path $TestDrive/error.log -Raw) $content | Should -BeLike "*Importing file '$psFile' with extension 'Microsoft.DSC.Extension/PowerShell'*" diff --git a/extensions/powershell/windowspowershell.dsc.extension.json b/extensions/powershell/windowspowershell.dsc.extension.json index dd3a4a3f7..375520a56 100644 --- a/extensions/powershell/windowspowershell.dsc.extension.json +++ b/extensions/powershell/windowspowershell.dsc.extension.json @@ -1,8 +1,8 @@ { "$schema": "https://aka.ms/dsc/schemas/v3/bundled/resource/manifest.json", - "type": "Microsoft.DSC.Extension/PowerShell", + "type": "Microsoft.DSC.Extension/WindowsPowerShell", "version": "0.1.0", - "description": "Enable passing PowerShell v1 configuration document file directly to DSC. Works only on Windows.", + "description": "Enable passing Windows PowerShell v1 configuration document file directly to DSC. Works only on Windows.", "import": { "fileExtensions": ["ps1"], "executable": "powershell", From 55d4424beb101e79739a1ab8abc600ee749fc333 Mon Sep 17 00:00:00 2001 From: "G.Reijn" Date: Fri, 8 Aug 2025 10:53:36 +0200 Subject: [PATCH 12/41] Update test --- dsc/tests/dsc_extension_discover.tests.ps1 | 14 +++++++++----- extensions/powershell/convertDscResource.psm1 | 6 ++++++ 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/dsc/tests/dsc_extension_discover.tests.ps1 b/dsc/tests/dsc_extension_discover.tests.ps1 index d37455465..d033c1c70 100644 --- a/dsc/tests/dsc_extension_discover.tests.ps1 +++ b/dsc/tests/dsc_extension_discover.tests.ps1 @@ -24,19 +24,23 @@ Describe 'Discover extension tests' { $out = dsc extension list | ConvertFrom-Json $LASTEXITCODE | Should -Be 0 if ($IsWindows) { - $out.Count | Should -Be 3 -Because ($out | Out-String) + $out.Count | Should -Be 4 -Because ($out | Out-String) $out[0].type | Should -Be 'Microsoft.DSC.Extension/Bicep' $out[0].version | Should -Be '0.1.0' $out[0].capabilities | Should -BeExactly @('import') $out[0].manifest | Should -Not -BeNullOrEmpty - $out[1].type | Should -Be 'Microsoft.Windows.Appx/Discover' + $out[1].type | Should -Be 'Microsoft.DSC.Extension/WindowsPowerShell' $out[1].version | Should -Be '0.1.0' - $out[1].capabilities | Should -BeExactly @('discover') + $out[1].capabilities | Should -BeExactly @('import') $out[1].manifest | Should -Not -BeNullOrEmpty - $out[2].type | Should -BeExactly 'Test/Discover' - $out[2].version | Should -BeExactly '0.1.0' + $out[2].type | Should -Be 'Microsoft.Windows.Appx/Discover' + $out[2].version | Should -Be '0.1.0' $out[2].capabilities | Should -BeExactly @('discover') $out[2].manifest | Should -Not -BeNullOrEmpty + $out[3].type | Should -BeExactly 'Test/Discover' + $out[3].version | Should -BeExactly '0.1.0' + $out[3].capabilities | Should -BeExactly @('discover') + $out[3].manifest | Should -Not -BeNullOrEmpty } else { $out.Count | Should -Be 2 -Because ($out | Out-String) $out[0].type | Should -Be 'Microsoft.DSC.Extension/Bicep' diff --git a/extensions/powershell/convertDscResource.psm1 b/extensions/powershell/convertDscResource.psm1 index a9907090d..2a6926cc6 100644 --- a/extensions/powershell/convertDscResource.psm1 +++ b/extensions/powershell/convertDscResource.psm1 @@ -98,6 +98,12 @@ function ConvertTo-DscObject $Tokens = $null $ParseErrors = $null + # Load the PSDesiredStateConfiguration module + Import-Module -Name 'PSDesiredStateConfiguration' -RequiredVersion '1.1' -Force -ErrorAction stop -ErrorVariable $importModuleError + if (-not [string]::IsNullOrEmpty($importModuleError)) { + 'Could not import PSDesiredStateConfiguration 1.1 in Windows PowerShell. ' + $importModuleError | Write-DscTrace -Operation Error + } + # Remove the module version information. $start = $Content.ToLower().IndexOf('import-dscresource') if ($start -ge 0) From 2860da79b67e700c24c58182d44634406c071a55 Mon Sep 17 00:00:00 2001 From: "G.Reijn" Date: Fri, 8 Aug 2025 11:53:13 +0200 Subject: [PATCH 13/41] Debug agent --- extensions/powershell/convertDscResource.psm1 | 2 ++ 1 file changed, 2 insertions(+) diff --git a/extensions/powershell/convertDscResource.psm1 b/extensions/powershell/convertDscResource.psm1 index 2a6926cc6..6987812bb 100644 --- a/extensions/powershell/convertDscResource.psm1 +++ b/extensions/powershell/convertDscResource.psm1 @@ -98,10 +98,12 @@ function ConvertTo-DscObject $Tokens = $null $ParseErrors = $null + Get-ChildItem "C:\Windows\System32\WindowsPowerShell\v1.0\Modules" | Select-Object Name | Write-DscTrace -Operation Trace # Load the PSDesiredStateConfiguration module Import-Module -Name 'PSDesiredStateConfiguration' -RequiredVersion '1.1' -Force -ErrorAction stop -ErrorVariable $importModuleError if (-not [string]::IsNullOrEmpty($importModuleError)) { 'Could not import PSDesiredStateConfiguration 1.1 in Windows PowerShell. ' + $importModuleError | Write-DscTrace -Operation Error + exit 1 } # Remove the module version information. From 4183c2785b4d4e2e8947d1cdec3e4584a9709a0d Mon Sep 17 00:00:00 2001 From: "G.Reijn" Date: Fri, 8 Aug 2025 13:16:21 +0200 Subject: [PATCH 14/41] Revert to old change --- extensions/powershell/convertDscResource.psm1 | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/extensions/powershell/convertDscResource.psm1 b/extensions/powershell/convertDscResource.psm1 index 6987812bb..4079846c8 100644 --- a/extensions/powershell/convertDscResource.psm1 +++ b/extensions/powershell/convertDscResource.psm1 @@ -98,9 +98,8 @@ function ConvertTo-DscObject $Tokens = $null $ParseErrors = $null - Get-ChildItem "C:\Windows\System32\WindowsPowerShell\v1.0\Modules" | Select-Object Name | Write-DscTrace -Operation Trace # Load the PSDesiredStateConfiguration module - Import-Module -Name 'PSDesiredStateConfiguration' -RequiredVersion '1.1' -Force -ErrorAction stop -ErrorVariable $importModuleError + Import-Module -Name 'PSDesiredStateConfiguration' -RequiredVersion 1.1 -UseWindowsPowerShell -WarningAction SilentlyContinue -ErrorVariable $importModuleError if (-not [string]::IsNullOrEmpty($importModuleError)) { 'Could not import PSDesiredStateConfiguration 1.1 in Windows PowerShell. ' + $importModuleError | Write-DscTrace -Operation Error exit 1 From 7342f164d217e82a3c76f7bbdbe5839b23cb0582 Mon Sep 17 00:00:00 2001 From: "G.Reijn" Date: Fri, 8 Aug 2025 15:20:34 +0200 Subject: [PATCH 15/41] Find PSDesiredStateConfiguration --- extensions/powershell/convertDscResource.psm1 | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/extensions/powershell/convertDscResource.psm1 b/extensions/powershell/convertDscResource.psm1 index 4079846c8..ced9b0ae4 100644 --- a/extensions/powershell/convertDscResource.psm1 +++ b/extensions/powershell/convertDscResource.psm1 @@ -98,8 +98,10 @@ function ConvertTo-DscObject $Tokens = $null $ParseErrors = $null + (Get-Module -Name 'PSDesiredStateConfiguration' | ConvertTo-Json) | Write-DscTrace Debug + # Load the PSDesiredStateConfiguration module - Import-Module -Name 'PSDesiredStateConfiguration' -RequiredVersion 1.1 -UseWindowsPowerShell -WarningAction SilentlyContinue -ErrorVariable $importModuleError + Import-Module -Name 'PSDesiredStateConfiguration' -RequiredVersion '1.1' -Force -ErrorAction stop -ErrorVariable $importModuleError if (-not [string]::IsNullOrEmpty($importModuleError)) { 'Could not import PSDesiredStateConfiguration 1.1 in Windows PowerShell. ' + $importModuleError | Write-DscTrace -Operation Error exit 1 From 29da6d704897f7c8cf7b4a5d74a8c3e7b6bf4328 Mon Sep 17 00:00:00 2001 From: "G.Reijn" Date: Fri, 8 Aug 2025 15:59:04 +0200 Subject: [PATCH 16/41] Find PSDesiredStateConfiguration --- extensions/powershell/convertDscResource.psm1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extensions/powershell/convertDscResource.psm1 b/extensions/powershell/convertDscResource.psm1 index ced9b0ae4..7784dd23a 100644 --- a/extensions/powershell/convertDscResource.psm1 +++ b/extensions/powershell/convertDscResource.psm1 @@ -98,7 +98,7 @@ function ConvertTo-DscObject $Tokens = $null $ParseErrors = $null - (Get-Module -Name 'PSDesiredStateConfiguration' | ConvertTo-Json) | Write-DscTrace Debug + (Get-Module -Name 'PSDesiredStateConfiguration' -ListAvailable | ConvertTo-Json) | Write-DscTrace Debug # Load the PSDesiredStateConfiguration module Import-Module -Name 'PSDesiredStateConfiguration' -RequiredVersion '1.1' -Force -ErrorAction stop -ErrorVariable $importModuleError From d9ccec478b509018227db5518d1c15d420d1bfec Mon Sep 17 00:00:00 2001 From: "G.Reijn" Date: Sun, 10 Aug 2025 05:14:38 +0200 Subject: [PATCH 17/41] Debug PSDesiredStateConfiguration --- build.ps1 | 7 +++++- extensions/powershell/convertDscResource.psm1 | 23 ++++++++++++++----- .../powershell/win_powershell.tests.ps1 | 4 ++++ 3 files changed, 27 insertions(+), 7 deletions(-) diff --git a/build.ps1 b/build.ps1 index c5dbcffd4..80d93f2f3 100755 --- a/build.ps1 +++ b/build.ps1 @@ -579,7 +579,12 @@ if ($Test) { (Get-Module -Name Pester -ListAvailable).Path } - Invoke-Pester -Output Detailed -ErrorAction Stop + if ($IsWindows) { + $file = Get-ChildItem -Filter win_powershell.tests.ps1 -Recurse + Invoke-Pester -Path $file -Output Detailed -ErrorAction Stop + } else { + Invoke-Pester -Output Detailed -ErrorAction Stop + } } function Find-MakeAppx() { diff --git a/extensions/powershell/convertDscResource.psm1 b/extensions/powershell/convertDscResource.psm1 index 7784dd23a..525974bef 100644 --- a/extensions/powershell/convertDscResource.psm1 +++ b/extensions/powershell/convertDscResource.psm1 @@ -1,3 +1,14 @@ +$Script:IsPowerShellCore = $PSVersionTable.PSEdition -eq 'Core' + +if ($Script:IsPowerShellCore) +{ + if ($IsWindows) + { + Import-Module -Name 'PSDesiredStateConfiguration' -RequiredVersion 1.1 -UseWindowsPowerShell -WarningAction SilentlyContinue + } + Import-Module -Name 'PSDesiredStateConfiguration' -MinimumVersion 2.0.7 -Prefix 'Pwsh' +} + function Write-DscTrace { param( [Parameter(Mandatory = $false)] @@ -100,12 +111,12 @@ function ConvertTo-DscObject (Get-Module -Name 'PSDesiredStateConfiguration' -ListAvailable | ConvertTo-Json) | Write-DscTrace Debug - # Load the PSDesiredStateConfiguration module - Import-Module -Name 'PSDesiredStateConfiguration' -RequiredVersion '1.1' -Force -ErrorAction stop -ErrorVariable $importModuleError - if (-not [string]::IsNullOrEmpty($importModuleError)) { - 'Could not import PSDesiredStateConfiguration 1.1 in Windows PowerShell. ' + $importModuleError | Write-DscTrace -Operation Error - exit 1 - } + # # Load the PSDesiredStateConfiguration module + # Import-Module -Name 'PSDesiredStateConfiguration' -RequiredVersion '1.1' -Force -ErrorAction stop -ErrorVariable $importModuleError + # if (-not [string]::IsNullOrEmpty($importModuleError)) { + # 'Could not import PSDesiredStateConfiguration 1.1 in Windows PowerShell. ' + $importModuleError | Write-DscTrace -Operation Error + # exit 1 + # } # Remove the module version information. $start = $Content.ToLower().IndexOf('import-dscresource') diff --git a/extensions/powershell/win_powershell.tests.ps1 b/extensions/powershell/win_powershell.tests.ps1 index 57cf71392..ecbff7ac7 100644 --- a/extensions/powershell/win_powershell.tests.ps1 +++ b/extensions/powershell/win_powershell.tests.ps1 @@ -11,6 +11,10 @@ BeforeDiscovery { Describe 'PowerShell extension tests' { It 'Example PowerShell file should work' -Skip:(!$IsWindows -or !$isElevated) { + Write-Verbose -Message $env:PSModulePath -Verbose + + $names = Get-ChildItem "$env:SystemRoot\System32\WindowsPowerShell\v1.0\Modules" | ForEach-Object {Write-Verbose "Found module: $($_.Name)"} -Verbose + $psFile = Resolve-Path -Path "$PSScriptRoot\..\..\dsc\examples\variable.dsc.ps1" $out = dsc -l trace config get -f $psFile 2>$TestDrive/error.log | ConvertFrom-Json $LASTEXITCODE | Should -Be 0 -Because (Get-Content -Path $TestDrive/error.log -Raw | Out-String) From 526ca6c5da25142c290fd33e4e1f64f11a393187 Mon Sep 17 00:00:00 2001 From: "G.Reijn" Date: Sun, 10 Aug 2025 05:46:20 +0200 Subject: [PATCH 18/41] Output available modules --- extensions/powershell/win_powershell.tests.ps1 | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/extensions/powershell/win_powershell.tests.ps1 b/extensions/powershell/win_powershell.tests.ps1 index ecbff7ac7..136fc5754 100644 --- a/extensions/powershell/win_powershell.tests.ps1 +++ b/extensions/powershell/win_powershell.tests.ps1 @@ -13,8 +13,11 @@ Describe 'PowerShell extension tests' { It 'Example PowerShell file should work' -Skip:(!$IsWindows -or !$isElevated) { Write-Verbose -Message $env:PSModulePath -Verbose - $names = Get-ChildItem "$env:SystemRoot\System32\WindowsPowerShell\v1.0\Modules" | ForEach-Object {Write-Verbose "Found module: $($_.Name)"} -Verbose - + $names = Get-ChildItem "$env:SystemRoot\System32\WindowsPowerShell\v1.0\Modules" + foreach ($name in $names) { + Write-Verbose -Message "Found module: $($name.Name)" -Verbose + } + $psFile = Resolve-Path -Path "$PSScriptRoot\..\..\dsc\examples\variable.dsc.ps1" $out = dsc -l trace config get -f $psFile 2>$TestDrive/error.log | ConvertFrom-Json $LASTEXITCODE | Should -Be 0 -Because (Get-Content -Path $TestDrive/error.log -Raw | Out-String) From f71d987a9a4a4e42c0fa9be1009a41c3d71209c8 Mon Sep 17 00:00:00 2001 From: "G.Reijn" Date: Sun, 10 Aug 2025 05:53:47 +0200 Subject: [PATCH 19/41] Update PSModulePath during discovery --- .../powershell/win_powershell.tests.ps1 | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/extensions/powershell/win_powershell.tests.ps1 b/extensions/powershell/win_powershell.tests.ps1 index 136fc5754..8168fcd36 100644 --- a/extensions/powershell/win_powershell.tests.ps1 +++ b/extensions/powershell/win_powershell.tests.ps1 @@ -6,6 +6,27 @@ BeforeDiscovery { $identity = [System.Security.Principal.WindowsIdentity]::GetCurrent() $principal = [System.Security.Principal.WindowsPrincipal]::new($identity) $isElevated = $principal.IsInRole([System.Security.Principal.WindowsBuiltInRole]::Administrator) + + if ($env:GITHUB_ACTION) { + Write-Verbose -Message "Running in GitHub Actions" -Verbose + # Uninstall the PSDesiredStateConfiguration module as this requires v1.1 and the build script installs it + Uninstall-PSResource -Name 'PSDesiredStateConfiguration' -Version 2.0.7 -ErrorAction Stop + # Get current PSModulePath and exclude PowerShell 7 paths + $currentPaths = $env:PSModulePath -split ';' | Where-Object { + $_ -notmatch 'PowerShell[\\/]7' -and + $_ -notmatch 'Program Files[\\/]PowerShell[\\/]' -and + $_ -notmatch 'Documents[\\/]PowerShell[\\/]' + } + + # Check if Windows PowerShell modules path exists + $windowsPSPath = "$env:SystemRoot\System32\WindowsPowerShell\v1.0\Modules" + if ($windowsPSPath -notin $currentPaths) { + $currentPaths += $windowsPSPath + } + + # Update PSModulePath + $env:PSModulePath = $currentPaths -join ';' + } } } From 2d106025b9691c5680605793aadd19440ced4e3b Mon Sep 17 00:00:00 2001 From: "G.Reijn" Date: Sun, 10 Aug 2025 06:25:01 +0200 Subject: [PATCH 20/41] Set verbosePreference --- extensions/powershell/convertDscResource.psm1 | 12 +++--------- extensions/powershell/win_powershell.tests.ps1 | 5 ----- 2 files changed, 3 insertions(+), 14 deletions(-) diff --git a/extensions/powershell/convertDscResource.psm1 b/extensions/powershell/convertDscResource.psm1 index 525974bef..3bd49d69c 100644 --- a/extensions/powershell/convertDscResource.psm1 +++ b/extensions/powershell/convertDscResource.psm1 @@ -9,6 +9,8 @@ if ($Script:IsPowerShellCore) Import-Module -Name 'PSDesiredStateConfiguration' -MinimumVersion 2.0.7 -Prefix 'Pwsh' } +$VerbosePreference = 'Continue' + function Write-DscTrace { param( [Parameter(Mandatory = $false)] @@ -109,15 +111,6 @@ function ConvertTo-DscObject $Tokens = $null $ParseErrors = $null - (Get-Module -Name 'PSDesiredStateConfiguration' -ListAvailable | ConvertTo-Json) | Write-DscTrace Debug - - # # Load the PSDesiredStateConfiguration module - # Import-Module -Name 'PSDesiredStateConfiguration' -RequiredVersion '1.1' -Force -ErrorAction stop -ErrorVariable $importModuleError - # if (-not [string]::IsNullOrEmpty($importModuleError)) { - # 'Could not import PSDesiredStateConfiguration 1.1 in Windows PowerShell. ' + $importModuleError | Write-DscTrace -Operation Error - # exit 1 - # } - # Remove the module version information. $start = $Content.ToLower().IndexOf('import-dscresource') if ($start -ge 0) @@ -209,6 +202,7 @@ function ConvertTo-DscObject } else { + Write-Verbose -Message "Loading DSC resources from module '$($moduleToLoad.ModuleName)'" -Verbose $currentResources = Get-DSCResource -Module $moduleToLoad.ModuleName } diff --git a/extensions/powershell/win_powershell.tests.ps1 b/extensions/powershell/win_powershell.tests.ps1 index 8168fcd36..f45b20944 100644 --- a/extensions/powershell/win_powershell.tests.ps1 +++ b/extensions/powershell/win_powershell.tests.ps1 @@ -33,11 +33,6 @@ BeforeDiscovery { Describe 'PowerShell extension tests' { It 'Example PowerShell file should work' -Skip:(!$IsWindows -or !$isElevated) { Write-Verbose -Message $env:PSModulePath -Verbose - - $names = Get-ChildItem "$env:SystemRoot\System32\WindowsPowerShell\v1.0\Modules" - foreach ($name in $names) { - Write-Verbose -Message "Found module: $($name.Name)" -Verbose - } $psFile = Resolve-Path -Path "$PSScriptRoot\..\..\dsc\examples\variable.dsc.ps1" $out = dsc -l trace config get -f $psFile 2>$TestDrive/error.log | ConvertFrom-Json From 6496881a413cad9425b6607a611871f1840d5dd8 Mon Sep 17 00:00:00 2001 From: "G.Reijn" Date: Sun, 10 Aug 2025 06:59:02 +0200 Subject: [PATCH 21/41] Return result --- extensions/powershell/convert-resource.ps1 | 2 ++ extensions/powershell/convertDscResource.psm1 | 2 -- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/extensions/powershell/convert-resource.ps1 b/extensions/powershell/convert-resource.ps1 index 55bf3af39..b6efebc29 100644 --- a/extensions/powershell/convert-resource.ps1 +++ b/extensions/powershell/convert-resource.ps1 @@ -20,6 +20,8 @@ end { if ($lines.Count -ne 0) { $result = $scriptModule.invoke( { param($lines) Build-DscConfigDocument -Content $lines }, ($lines | Out-String) ) + ($result | ConvertTo-Json -Depth 10 -Compress) | Write-DscTrace Debug + return ($result | ConvertTo-Json -Depth 10 -Compress) } } \ No newline at end of file diff --git a/extensions/powershell/convertDscResource.psm1 b/extensions/powershell/convertDscResource.psm1 index 3bd49d69c..ad738e54b 100644 --- a/extensions/powershell/convertDscResource.psm1 +++ b/extensions/powershell/convertDscResource.psm1 @@ -9,8 +9,6 @@ if ($Script:IsPowerShellCore) Import-Module -Name 'PSDesiredStateConfiguration' -MinimumVersion 2.0.7 -Prefix 'Pwsh' } -$VerbosePreference = 'Continue' - function Write-DscTrace { param( [Parameter(Mandatory = $false)] From fe1a7993d44ecefc838b8fddd29a50b21f75e696 Mon Sep 17 00:00:00 2001 From: "G.Reijn" Date: Sun, 10 Aug 2025 07:38:34 +0200 Subject: [PATCH 22/41] Fix test --- build.ps1 | 7 +------ extensions/powershell/convertDscResource.psm1 | 4 +--- extensions/powershell/win_powershell.tests.ps1 | 9 +++++++++ 3 files changed, 11 insertions(+), 9 deletions(-) diff --git a/build.ps1 b/build.ps1 index 80d93f2f3..97534a3c1 100755 --- a/build.ps1 +++ b/build.ps1 @@ -579,12 +579,7 @@ if ($Test) { (Get-Module -Name Pester -ListAvailable).Path } - if ($IsWindows) { - $file = Get-ChildItem -Filter win_powershell.tests.ps1 -Recurse - Invoke-Pester -Path $file -Output Detailed -ErrorAction Stop - } else { - Invoke-Pester -Output Detailed -ErrorAction Stop - } + Invoke-Pester -Output Detailed -ErrorAction Stop } function Find-MakeAppx() { diff --git a/extensions/powershell/convertDscResource.psm1 b/extensions/powershell/convertDscResource.psm1 index ad738e54b..fc06aa048 100644 --- a/extensions/powershell/convertDscResource.psm1 +++ b/extensions/powershell/convertDscResource.psm1 @@ -6,7 +6,7 @@ if ($Script:IsPowerShellCore) { Import-Module -Name 'PSDesiredStateConfiguration' -RequiredVersion 1.1 -UseWindowsPowerShell -WarningAction SilentlyContinue } - Import-Module -Name 'PSDesiredStateConfiguration' -MinimumVersion 2.0.7 -Prefix 'Pwsh' + Import-Module -Name 'PSDesiredStateConfiguration' -MinimumVersion 2.0.7 -Prefix 'Pwsh' -WarningAction SilentlyContinue } function Write-DscTrace { @@ -81,7 +81,6 @@ function Build-DscConfigDocument { $dependsOnString = "[resourceId('$("{0}/{1}" -f $previousGroupHash.ModuleName, $previousGroupHash.ResourceName)','$($previousGroupHash.ResourceInstanceName)')]" - Write-Verbose -Message "Found '$dependsOnstring' for resource: $($dscObject.ResourceInstanceName)" # add it to the object $resource | Add-Member -MemberType NoteProperty -Name 'dependsOn' -Value @($dependsOnString) } @@ -200,7 +199,6 @@ function ConvertTo-DscObject } else { - Write-Verbose -Message "Loading DSC resources from module '$($moduleToLoad.ModuleName)'" -Verbose $currentResources = Get-DSCResource -Module $moduleToLoad.ModuleName } diff --git a/extensions/powershell/win_powershell.tests.ps1 b/extensions/powershell/win_powershell.tests.ps1 index f45b20944..b126821a4 100644 --- a/extensions/powershell/win_powershell.tests.ps1 +++ b/extensions/powershell/win_powershell.tests.ps1 @@ -8,6 +8,7 @@ BeforeDiscovery { $isElevated = $principal.IsInRole([System.Security.Principal.WindowsBuiltInRole]::Administrator) if ($env:GITHUB_ACTION) { + $script:currentModulePaths = $env:PSModulePath Write-Verbose -Message "Running in GitHub Actions" -Verbose # Uninstall the PSDesiredStateConfiguration module as this requires v1.1 and the build script installs it Uninstall-PSResource -Name 'PSDesiredStateConfiguration' -Version 2.0.7 -ErrorAction Stop @@ -62,3 +63,11 @@ configuration InvalidConfiguration { $content | Should -Match "No DSC resources found in the imported modules." } } + +AfterAll { + if ($IsWindows -and $env:GITHUB_ACTION) { + Install-PSResource -Name 'PSDesiredStateConfiguration' -Version 2.0.7 -ErrorAction Stop -TrustRepository -Reinstall + } + + $env:PSModulePath = $script:currentModulePaths +} \ No newline at end of file From 490402b504f19aeb628c2d95a1d0a299c52029f1 Mon Sep 17 00:00:00 2001 From: "G.Reijn" Date: Sun, 10 Aug 2025 07:39:15 +0200 Subject: [PATCH 23/41] Remove whitespace --- build.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.ps1 b/build.ps1 index 97534a3c1..c5dbcffd4 100755 --- a/build.ps1 +++ b/build.ps1 @@ -579,7 +579,7 @@ if ($Test) { (Get-Module -Name Pester -ListAvailable).Path } - Invoke-Pester -Output Detailed -ErrorAction Stop + Invoke-Pester -Output Detailed -ErrorAction Stop } function Find-MakeAppx() { From f470c1da876163aa13450b339335e28942fa70b9 Mon Sep 17 00:00:00 2001 From: "G.Reijn" Date: Sun, 10 Aug 2025 08:32:18 +0200 Subject: [PATCH 24/41] Run test with verbose --- extensions/powershell/convert-resource.ps1 | 2 -- extensions/powershell/win_powershell.tests.ps1 | 6 +++--- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/extensions/powershell/convert-resource.ps1 b/extensions/powershell/convert-resource.ps1 index b6efebc29..55bf3af39 100644 --- a/extensions/powershell/convert-resource.ps1 +++ b/extensions/powershell/convert-resource.ps1 @@ -20,8 +20,6 @@ end { if ($lines.Count -ne 0) { $result = $scriptModule.invoke( { param($lines) Build-DscConfigDocument -Content $lines }, ($lines | Out-String) ) - ($result | ConvertTo-Json -Depth 10 -Compress) | Write-DscTrace Debug - return ($result | ConvertTo-Json -Depth 10 -Compress) } } \ No newline at end of file diff --git a/extensions/powershell/win_powershell.tests.ps1 b/extensions/powershell/win_powershell.tests.ps1 index b126821a4..6cece6dc6 100644 --- a/extensions/powershell/win_powershell.tests.ps1 +++ b/extensions/powershell/win_powershell.tests.ps1 @@ -33,8 +33,6 @@ BeforeDiscovery { Describe 'PowerShell extension tests' { It 'Example PowerShell file should work' -Skip:(!$IsWindows -or !$isElevated) { - Write-Verbose -Message $env:PSModulePath -Verbose - $psFile = Resolve-Path -Path "$PSScriptRoot\..\..\dsc\examples\variable.dsc.ps1" $out = dsc -l trace config get -f $psFile 2>$TestDrive/error.log | ConvertFrom-Json $LASTEXITCODE | Should -Be 0 -Because (Get-Content -Path $TestDrive/error.log -Raw | Out-String) @@ -59,7 +57,7 @@ configuration InvalidConfiguration { dsc -l trace config get -f $psFile 2>$TestDrive/error.log $LASTEXITCODE | Should -Be 2 -Because (Get-Content -Path $TestDrive/error.log -Raw | Out-String) $content = (Get-Content -Path $TestDrive/error.log -Raw) - $content | Should -BeLike "*Importing file '$psFile' with extension 'Microsoft.DSC.Extension/PowerShell'*" + $content | Should -BeLike "*Importing file '$psFile' with extension 'Microsoft.DSC.Extension/WindowsPowerShell'*" $content | Should -Match "No DSC resources found in the imported modules." } } @@ -69,5 +67,7 @@ AfterAll { Install-PSResource -Name 'PSDesiredStateConfiguration' -Version 2.0.7 -ErrorAction Stop -TrustRepository -Reinstall } + Write-Verbose -Message "Restoring original PSModulePath" -Verbose + Write-Verbose -Message ($script:currentModulePaths) -Verbose $env:PSModulePath = $script:currentModulePaths } \ No newline at end of file From 03ac34c0fab3c254703a22a9564d4f92cf512f74 Mon Sep 17 00:00:00 2001 From: "G.Reijn" Date: Sun, 10 Aug 2025 11:27:13 +0200 Subject: [PATCH 25/41] Disable test --- .../powershell/win_powershell.tests.ps1 | 130 +++++++++--------- 1 file changed, 65 insertions(+), 65 deletions(-) diff --git a/extensions/powershell/win_powershell.tests.ps1 b/extensions/powershell/win_powershell.tests.ps1 index 6cece6dc6..3b08cd25b 100644 --- a/extensions/powershell/win_powershell.tests.ps1 +++ b/extensions/powershell/win_powershell.tests.ps1 @@ -1,73 +1,73 @@ -# Copyright (c) Microsoft Corporation. -# Licensed under the MIT License. +# # Copyright (c) Microsoft Corporation. +# # Licensed under the MIT License. -BeforeDiscovery { - if ($IsWindows) { - $identity = [System.Security.Principal.WindowsIdentity]::GetCurrent() - $principal = [System.Security.Principal.WindowsPrincipal]::new($identity) - $isElevated = $principal.IsInRole([System.Security.Principal.WindowsBuiltInRole]::Administrator) +# BeforeDiscovery { +# if ($IsWindows) { +# $identity = [System.Security.Principal.WindowsIdentity]::GetCurrent() +# $principal = [System.Security.Principal.WindowsPrincipal]::new($identity) +# $isElevated = $principal.IsInRole([System.Security.Principal.WindowsBuiltInRole]::Administrator) - if ($env:GITHUB_ACTION) { - $script:currentModulePaths = $env:PSModulePath - Write-Verbose -Message "Running in GitHub Actions" -Verbose - # Uninstall the PSDesiredStateConfiguration module as this requires v1.1 and the build script installs it - Uninstall-PSResource -Name 'PSDesiredStateConfiguration' -Version 2.0.7 -ErrorAction Stop - # Get current PSModulePath and exclude PowerShell 7 paths - $currentPaths = $env:PSModulePath -split ';' | Where-Object { - $_ -notmatch 'PowerShell[\\/]7' -and - $_ -notmatch 'Program Files[\\/]PowerShell[\\/]' -and - $_ -notmatch 'Documents[\\/]PowerShell[\\/]' - } +# if ($env:GITHUB_ACTION) { +# $script:currentModulePaths = $env:PSModulePath +# Write-Verbose -Message "Running in GitHub Actions" -Verbose +# # Uninstall the PSDesiredStateConfiguration module as this requires v1.1 and the build script installs it +# Uninstall-PSResource -Name 'PSDesiredStateConfiguration' -Version 2.0.7 -ErrorAction Stop +# # Get current PSModulePath and exclude PowerShell 7 paths +# $currentPaths = $env:PSModulePath -split ';' | Where-Object { +# $_ -notmatch 'PowerShell[\\/]7' -and +# $_ -notmatch 'Program Files[\\/]PowerShell[\\/]' -and +# $_ -notmatch 'Documents[\\/]PowerShell[\\/]' +# } - # Check if Windows PowerShell modules path exists - $windowsPSPath = "$env:SystemRoot\System32\WindowsPowerShell\v1.0\Modules" - if ($windowsPSPath -notin $currentPaths) { - $currentPaths += $windowsPSPath - } +# # Check if Windows PowerShell modules path exists +# $windowsPSPath = "$env:SystemRoot\System32\WindowsPowerShell\v1.0\Modules" +# if ($windowsPSPath -notin $currentPaths) { +# $currentPaths += $windowsPSPath +# } - # Update PSModulePath - $env:PSModulePath = $currentPaths -join ';' - } - } -} +# # Update PSModulePath +# $env:PSModulePath = $currentPaths -join ';' +# } +# } +# } -Describe 'PowerShell extension tests' { - It 'Example PowerShell file should work' -Skip:(!$IsWindows -or !$isElevated) { - $psFile = Resolve-Path -Path "$PSScriptRoot\..\..\dsc\examples\variable.dsc.ps1" - $out = dsc -l trace config get -f $psFile 2>$TestDrive/error.log | ConvertFrom-Json - $LASTEXITCODE | Should -Be 0 -Because (Get-Content -Path $TestDrive/error.log -Raw | Out-String) - $out.results[0].result.actualState.Ensure | Should -Be 'Absent' - (Get-Content -Path $TestDrive/error.log -Raw) | Should -Match "Importing file '$psFile' with extension 'Microsoft.DSC.Extension/PowerShell'" - } +# Describe 'PowerShell extension tests' { +# It 'Example PowerShell file should work' -Skip:(!$IsWindows -or !$isElevated) { +# $psFile = Resolve-Path -Path "$PSScriptRoot\..\..\dsc\examples\variable.dsc.ps1" +# $out = dsc -l trace config get -f $psFile 2>$TestDrive/error.log | ConvertFrom-Json +# $LASTEXITCODE | Should -Be 0 -Because (Get-Content -Path $TestDrive/error.log -Raw | Out-String) +# $out.results[0].result.actualState.Ensure | Should -Be 'Absent' +# (Get-Content -Path $TestDrive/error.log -Raw) | Should -Match "Importing file '$psFile' with extension 'Microsoft.DSC.Extension/PowerShell'" +# } - It 'Invalid PowerShell configuration document file returns error' -Skip:(!$IsWindows) { - $psFile = "$TestDrive/invalid.ps1" - Set-Content -Path $psFile -Value @" -configuration InvalidConfiguration { - Import-DscResource -ModuleName InvalidModule - Node localhost - { - Test Invalid { - Name = 'InvalidTest' - Ensure = 'Present' - } - } -} -"@ - dsc -l trace config get -f $psFile 2>$TestDrive/error.log - $LASTEXITCODE | Should -Be 2 -Because (Get-Content -Path $TestDrive/error.log -Raw | Out-String) - $content = (Get-Content -Path $TestDrive/error.log -Raw) - $content | Should -BeLike "*Importing file '$psFile' with extension 'Microsoft.DSC.Extension/WindowsPowerShell'*" - $content | Should -Match "No DSC resources found in the imported modules." - } -} +# It 'Invalid PowerShell configuration document file returns error' -Skip:(!$IsWindows) { +# $psFile = "$TestDrive/invalid.ps1" +# Set-Content -Path $psFile -Value @" +# configuration InvalidConfiguration { +# Import-DscResource -ModuleName InvalidModule +# Node localhost +# { +# Test Invalid { +# Name = 'InvalidTest' +# Ensure = 'Present' +# } +# } +# } +# "@ +# dsc -l trace config get -f $psFile 2>$TestDrive/error.log +# $LASTEXITCODE | Should -Be 2 -Because (Get-Content -Path $TestDrive/error.log -Raw | Out-String) +# $content = (Get-Content -Path $TestDrive/error.log -Raw) +# $content | Should -BeLike "*Importing file '$psFile' with extension 'Microsoft.DSC.Extension/WindowsPowerShell'*" +# $content | Should -Match "No DSC resources found in the imported modules." +# } +# } -AfterAll { - if ($IsWindows -and $env:GITHUB_ACTION) { - Install-PSResource -Name 'PSDesiredStateConfiguration' -Version 2.0.7 -ErrorAction Stop -TrustRepository -Reinstall - } +# AfterAll { +# if ($IsWindows -and $env:GITHUB_ACTION) { +# Install-PSResource -Name 'PSDesiredStateConfiguration' -Version 2.0.7 -ErrorAction Stop -TrustRepository -Reinstall +# } - Write-Verbose -Message "Restoring original PSModulePath" -Verbose - Write-Verbose -Message ($script:currentModulePaths) -Verbose - $env:PSModulePath = $script:currentModulePaths -} \ No newline at end of file +# Write-Verbose -Message "Restoring original PSModulePath" -Verbose +# Write-Verbose -Message ($script:currentModulePaths) -Verbose +# $env:PSModulePath = $script:currentModulePaths +# } \ No newline at end of file From a0a4fbaedfdfd71627163ff7a0d26a1ab9d01521 Mon Sep 17 00:00:00 2001 From: "G.Reijn" Date: Sun, 10 Aug 2025 12:05:57 +0200 Subject: [PATCH 26/41] logging --- .../powershell/win_powershell.tests.ps1 | 109 +++++++----------- .../Tests/win_powershell_cache.tests.ps1 | 5 +- 2 files changed, 43 insertions(+), 71 deletions(-) diff --git a/extensions/powershell/win_powershell.tests.ps1 b/extensions/powershell/win_powershell.tests.ps1 index 3b08cd25b..a73ea9c8b 100644 --- a/extensions/powershell/win_powershell.tests.ps1 +++ b/extensions/powershell/win_powershell.tests.ps1 @@ -1,73 +1,44 @@ -# # Copyright (c) Microsoft Corporation. -# # Licensed under the MIT License. +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. -# BeforeDiscovery { -# if ($IsWindows) { -# $identity = [System.Security.Principal.WindowsIdentity]::GetCurrent() -# $principal = [System.Security.Principal.WindowsPrincipal]::new($identity) -# $isElevated = $principal.IsInRole([System.Security.Principal.WindowsBuiltInRole]::Administrator) +BeforeDiscovery { + if ($IsWindows) { + $identity = [System.Security.Principal.WindowsIdentity]::GetCurrent() + $principal = [System.Security.Principal.WindowsPrincipal]::new($identity) + $isElevated = $principal.IsInRole([System.Security.Principal.WindowsBuiltInRole]::Administrator) -# if ($env:GITHUB_ACTION) { -# $script:currentModulePaths = $env:PSModulePath -# Write-Verbose -Message "Running in GitHub Actions" -Verbose -# # Uninstall the PSDesiredStateConfiguration module as this requires v1.1 and the build script installs it -# Uninstall-PSResource -Name 'PSDesiredStateConfiguration' -Version 2.0.7 -ErrorAction Stop -# # Get current PSModulePath and exclude PowerShell 7 paths -# $currentPaths = $env:PSModulePath -split ';' | Where-Object { -# $_ -notmatch 'PowerShell[\\/]7' -and -# $_ -notmatch 'Program Files[\\/]PowerShell[\\/]' -and -# $_ -notmatch 'Documents[\\/]PowerShell[\\/]' -# } - -# # Check if Windows PowerShell modules path exists -# $windowsPSPath = "$env:SystemRoot\System32\WindowsPowerShell\v1.0\Modules" -# if ($windowsPSPath -notin $currentPaths) { -# $currentPaths += $windowsPSPath -# } - -# # Update PSModulePath -# $env:PSModulePath = $currentPaths -join ';' -# } -# } -# } + Write-Verbose -Message $env:Path -Verbose + Write-Verbose -Message $env:Path -Verbose + } +} -# Describe 'PowerShell extension tests' { -# It 'Example PowerShell file should work' -Skip:(!$IsWindows -or !$isElevated) { -# $psFile = Resolve-Path -Path "$PSScriptRoot\..\..\dsc\examples\variable.dsc.ps1" -# $out = dsc -l trace config get -f $psFile 2>$TestDrive/error.log | ConvertFrom-Json -# $LASTEXITCODE | Should -Be 0 -Because (Get-Content -Path $TestDrive/error.log -Raw | Out-String) -# $out.results[0].result.actualState.Ensure | Should -Be 'Absent' -# (Get-Content -Path $TestDrive/error.log -Raw) | Should -Match "Importing file '$psFile' with extension 'Microsoft.DSC.Extension/PowerShell'" -# } +Describe 'PowerShell extension tests' { + It 'Example PowerShell file should work' -Skip:(!$IsWindows -or !$isElevated) { + $psFile = Resolve-Path -Path "$PSScriptRoot\..\..\dsc\examples\variable.dsc.ps1" + $out = dsc -l trace config get -f $psFile 2>$TestDrive/error.log | ConvertFrom-Json + $LASTEXITCODE | Should -Be 0 -Because (Get-Content -Path $TestDrive/error.log -Raw | Out-String) + $out.results[0].result.actualState.Ensure | Should -Be 'Absent' + (Get-Content -Path $TestDrive/error.log -Raw) | Should -Match "Importing file '$psFile' with extension 'Microsoft.DSC.Extension/PowerShell'" + } -# It 'Invalid PowerShell configuration document file returns error' -Skip:(!$IsWindows) { -# $psFile = "$TestDrive/invalid.ps1" -# Set-Content -Path $psFile -Value @" -# configuration InvalidConfiguration { -# Import-DscResource -ModuleName InvalidModule -# Node localhost -# { -# Test Invalid { -# Name = 'InvalidTest' -# Ensure = 'Present' -# } -# } -# } -# "@ -# dsc -l trace config get -f $psFile 2>$TestDrive/error.log -# $LASTEXITCODE | Should -Be 2 -Because (Get-Content -Path $TestDrive/error.log -Raw | Out-String) -# $content = (Get-Content -Path $TestDrive/error.log -Raw) -# $content | Should -BeLike "*Importing file '$psFile' with extension 'Microsoft.DSC.Extension/WindowsPowerShell'*" -# $content | Should -Match "No DSC resources found in the imported modules." -# } -# } - -# AfterAll { -# if ($IsWindows -and $env:GITHUB_ACTION) { -# Install-PSResource -Name 'PSDesiredStateConfiguration' -Version 2.0.7 -ErrorAction Stop -TrustRepository -Reinstall -# } - -# Write-Verbose -Message "Restoring original PSModulePath" -Verbose -# Write-Verbose -Message ($script:currentModulePaths) -Verbose -# $env:PSModulePath = $script:currentModulePaths -# } \ No newline at end of file + It 'Invalid PowerShell configuration document file returns error' -Skip:(!$IsWindows) { + $psFile = "$TestDrive/invalid.ps1" + Set-Content -Path $psFile -Value @" +configuration InvalidConfiguration { + Import-DscResource -ModuleName InvalidModule + Node localhost + { + Test Invalid { + Name = 'InvalidTest' + Ensure = 'Present' + } + } +} +"@ + dsc -l trace config get -f $psFile 2>$TestDrive/error.log + $LASTEXITCODE | Should -Be 2 -Because (Get-Content -Path $TestDrive/error.log -Raw | Out-String) + $content = (Get-Content -Path $TestDrive/error.log -Raw) + $content | Should -BeLike "*Importing file '$psFile' with extension 'Microsoft.DSC.Extension/WindowsPowerShell'*" + $content | Should -Match "No DSC resources found in the imported modules." + } +} \ No newline at end of file diff --git a/powershell-adapter/Tests/win_powershell_cache.tests.ps1 b/powershell-adapter/Tests/win_powershell_cache.tests.ps1 index 8f2ca4b58..c53f5cdfb 100644 --- a/powershell-adapter/Tests/win_powershell_cache.tests.ps1 +++ b/powershell-adapter/Tests/win_powershell_cache.tests.ps1 @@ -48,10 +48,11 @@ Describe 'WindowsPowerShell adapter resource tests - requires elevated permissio } It 'Get works on Binary "File" resource' { - + Write-Verbose -Message $env:Path -Verbose + Write-Verbose -Message $env:PSModulePath -Verbose $testFile = "$testdrive\test.txt" 'test' | Set-Content -Path $testFile -Force - $r = '{"DestinationPath":"' + $testFile.replace('\', '\\') + '"}' | dsc resource get -r 'PSDesiredStateConfiguration/File' -f - + $r = '{"DestinationPath":"' + $testFile.replace('\', '\\') + '"}' | dsc -l trace resource get -r 'PSDesiredStateConfiguration/File' -f - $LASTEXITCODE | Should -Be 0 $res = $r | ConvertFrom-Json $res.actualState.DestinationPath | Should -Be "$testFile" From bd99f632328f00df6bf2de7eea5d0fb0e6227da8 Mon Sep 17 00:00:00 2001 From: "G.Reijn" Date: Sun, 10 Aug 2025 13:06:19 +0200 Subject: [PATCH 27/41] Import module --- extensions/powershell/convert-resource.ps1 | 2 +- extensions/powershell/convertDscResource.psm1 | 204 ++++++------------ .../powershell/win_powershell.tests.ps1 | 5 +- .../Tests/win_powershell_cache.tests.ps1 | 4 +- 4 files changed, 68 insertions(+), 147 deletions(-) diff --git a/extensions/powershell/convert-resource.ps1 b/extensions/powershell/convert-resource.ps1 index 55bf3af39..633a2ac4d 100644 --- a/extensions/powershell/convert-resource.ps1 +++ b/extensions/powershell/convert-resource.ps1 @@ -7,7 +7,7 @@ param ( begin { $lines = [System.Collections.Generic.List[string]]::new() - $scriptModule = Import-Module "$PSScriptRoot/convertDscResource.psd1" -Force -PassThru -ErrorAction Ignore + $scriptModule = Import-Module "$PSScriptRoot/convertDscResource.psd1" -Force -PassThru -WarningAction SilentlyContinue -ErrorAction Stop } process { diff --git a/extensions/powershell/convertDscResource.psm1 b/extensions/powershell/convertDscResource.psm1 index fc06aa048..c7113fd7d 100644 --- a/extensions/powershell/convertDscResource.psm1 +++ b/extensions/powershell/convertDscResource.psm1 @@ -1,12 +1,12 @@ $Script:IsPowerShellCore = $PSVersionTable.PSEdition -eq 'Core' -if ($Script:IsPowerShellCore) -{ - if ($IsWindows) - { +if ($Script:IsPowerShellCore) { + if ($IsWindows) { Import-Module -Name 'PSDesiredStateConfiguration' -RequiredVersion 1.1 -UseWindowsPowerShell -WarningAction SilentlyContinue } Import-Module -Name 'PSDesiredStateConfiguration' -MinimumVersion 2.0.7 -Prefix 'Pwsh' -WarningAction SilentlyContinue +} else { + Import-Module -Name 'PSDesiredStateConfiguration' -RequiredVersion 1.1 -WarningAction SilentlyContinue } function Write-DscTrace { @@ -22,8 +22,7 @@ function Write-DscTrace { $host.ui.WriteErrorLine($trace) } -function Build-DscConfigDocument -{ +function Build-DscConfigDocument { [CmdletBinding()] [OutputType([System.Collections.Specialized.OrderedDictionary])] param @@ -42,8 +41,7 @@ function Build-DscConfigDocument # convert object to hashtable(s) $dscObjects = ConvertTo-DscObject @PSBoundParameters -ErrorAction SilentlyContinue - if (-not $dscObjects -or $dscObjects.Count -eq 0) - { + if (-not $dscObjects -or $dscObjects.Count -eq 0) { "No DSC objects found in the provided content." | Write-DscTrace -Operation Error exit 1 } @@ -51,8 +49,7 @@ function Build-DscConfigDocument # store all resources in variables $resources = [System.Collections.Generic.List[object]]::new() - foreach ($dscObject in $dscObjects) - { + foreach ($dscObject in $dscObjects) { $resource = [PSCustomObject]@{ name = $dscObject.ResourceInstanceName type = ("{0}/{1}" -f $dscObject.ModuleName, $dscObject.ResourceName) @@ -61,10 +58,8 @@ function Build-DscConfigDocument $properties = [ordered]@{} - foreach ($dscObjectProperty in $dscObject.GetEnumerator()) - { - if ($dscObjectProperty.Key -notin @('ResourceInstanceName', 'ResourceName', 'ModuleName', 'DependsOn', 'ConfigurationName', 'Type')) - { + foreach ($dscObjectProperty in $dscObject.GetEnumerator()) { + if ($dscObjectProperty.Key -notin @('ResourceInstanceName', 'ResourceName', 'ModuleName', 'DependsOn', 'ConfigurationName', 'Type')) { $properties.Add($dscObjectProperty.Key, $dscObjectProperty.Value) } } @@ -72,13 +67,11 @@ function Build-DscConfigDocument # add properties $resource.properties = $properties - if ($dscObject.ContainsKey('DependsOn') -and $dscObject.DependsOn) - { + if ($dscObject.ContainsKey('DependsOn') -and $dscObject.DependsOn) { $dependsOnKeys = $dscObject.DependsOn.Split("]").Replace("[", "") $previousGroupHash = $dscObjects | Where-Object { $_.ResourceName -eq $dependsOnKeys[0] -and $_.ResourceInstanceName -eq $dependsOnKeys[1] } - if ($previousGroupHash) - { + if ($previousGroupHash) { $dependsOnString = "[resourceId('$("{0}/{1}" -f $previousGroupHash.ModuleName, $previousGroupHash.ResourceName)','$($previousGroupHash.ResourceInstanceName)')]" # add it to the object @@ -94,8 +87,7 @@ function Build-DscConfigDocument return $configurationDocument } -function ConvertTo-DscObject -{ +function ConvertTo-DscObject { [CmdletBinding()] param ( @@ -110,14 +102,11 @@ function ConvertTo-DscObject # Remove the module version information. $start = $Content.ToLower().IndexOf('import-dscresource') - if ($start -ge 0) - { + if ($start -ge 0) { $end = $Content.IndexOf("`n", $start) - if ($end -gt $start) - { + if ($end -gt $start) { $start = $Content.ToLower().IndexOf("-moduleversion", $start) - if ($start -ge 0 -and $start -lt $end) - { + if ($start -ge 0 -and $start -lt $end) { $Content = $Content.Remove($start, $end - $start) } } @@ -125,18 +114,14 @@ function ConvertTo-DscObject # Rename the configuration node to ensure a valid name is used. $start = $Content.ToLower().IndexOf("`nconfiguration") - if ($start -lt 0) - { + if ($start -lt 0) { $start = $Content.ToLower().IndexOf(' configuration ') } - if ($start -ge 0) - { + if ($start -ge 0) { $end = $Content.IndexOf("`n", $start) - if ($end -gt $start) - { + if ($end -gt $start) { $start = $Content.ToLower().IndexOf(' ', $start + 1) - if ($start -ge 0 -and $start -lt $end) - { + if ($start -ge 0 -and $start -lt $end) { $Content = $Content.Remove($start, $end - $start) $Content = $Content.Insert($start, " TempDSCParserConfiguration") } @@ -151,29 +136,21 @@ function ConvertTo-DscObject # 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) - { + 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') - { + $statement.CommandElements[0].Value -eq 'Import-DSCResource') { $currentModule = @{} - for ($i = 0; $i -le $statement.CommandElements.Count; $i++) - { + for ($i = 0; $i -le $statement.CommandElements.Count; $i++) { if ($statement.CommandElements[$i].ParameterName -eq 'ModuleName' -and ` - ($i + 1) -lt $statement.CommandElements.Count) - { + ($i + 1) -lt $statement.CommandElements.Count) { $moduleName = $statement.CommandElements[$i + 1].Value $currentModule.Add('ModuleName', $moduleName) - } - elseif ($statement.CommandElements[$i].ParameterName -eq 'Module' -and ` - ($i + 1) -lt $statement.CommandElements.Count) - { + } elseif ($statement.CommandElements[$i].ParameterName -eq 'Module' -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) - { + } elseif ($statement.CommandElements[$i].ParameterName -eq 'ModuleVersion' -and ` + ($i + 1) -lt $statement.CommandElements.Count) { $moduleVersion = $statement.CommandElements[$i + 1].Value $currentModule.Add('ModuleVersion', $moduleVersion) } @@ -182,36 +159,27 @@ function ConvertTo-DscObject } } $DSCResources = @() - foreach ($moduleToLoad in $ModulesToLoad) - { + 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)) - { + if ($null -eq $loadedModuleTest -and -not [System.String]::IsNullOrEmpty($moduleToLoad.ModuleVersion)) { "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)'" | Write-DscTrace -Operation Error exit 1 - } - else - { - if ($Script:IsPowerShellCore) - { + } else { + if ($Script:IsPowerShellCore) { $currentResources = Get-PwshDscResource -Module $moduleToLoad.ModuleName - } - else - { + } else { $currentResources = Get-DSCResource -Module $moduleToLoad.ModuleName } - if (-not [System.String]::IsNullOrEmpty($moduleToLoad.ModuleVersion)) - { + if (-not [System.String]::IsNullOrEmpty($moduleToLoad.ModuleVersion)) { $currentResources = $currentResources | Where-Object -FilterScript { $_.Version -eq $moduleToLoad.ModuleVersion } } $DSCResources += $currentResources } } - if ($DSCResources.Count -eq 0) - { + if ($DSCResources.Count -eq 0) { "No DSC resources found in the imported modules." | Write-DscTrace -Operation Error exit 1 } @@ -220,12 +188,9 @@ function ConvertTo-DscObject # Body.ScriptBlock is the part after "Configuration {" # 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 - { + 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 - { + } catch { $resourceInstances = $Config.Body.ScriptBlock.EndBlock.Statements | Where-Object -FilterScript { $null -ne $_.CommandElements -and $_.CommandElements[0].Value -ne 'Import-DscResource' } } @@ -233,8 +198,7 @@ function ConvertTo-DscObject $configurationName = $Config.InstanceName.Value $totalCount = 1 - foreach ($resource in $resourceInstances) - { + foreach ($resource in $resourceInstances) { $currentResourceInfo = @{} # CommandElements @@ -257,61 +221,41 @@ function ConvertTo-DscObject $currentResource = $DSCResources | Where-Object -FilterScript { $_.Name -eq $resourceType } # Loop through all the key/pair value - foreach ($keyValuePair in $resource.CommandElements[2].KeyValuePairs) - { + 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[]') - { + 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) - { + 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) - { + } elseif ($null -ne $element.Value) { $newValue += $element.Value } } - } - else - { + } else { $newValue += $expression.Value } } $value = $newValue - } - else - { + } else { $value = $keyValuePair.Item2.PipelineElements.Expression.ToString() } - } - else - { + } else { $value = $keyValuePair.Item2.PipelineElements.Parent.ToString() } - if ($value.GetType().Name -eq 'String' -and $value.StartsWith('$')) - { + if ($value.GetType().Name -eq 'String' -and $value.StartsWith('$')) { $isVariable = $true } - } - else - { + } else { $value = $keyValuePair.Item2.PipelineElements.Expression.Value } } @@ -323,19 +267,16 @@ function ConvertTo-DscObject # 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) - { + if ($null -eq $valueType) { $propertyFound = $false } - if ($propertyFound) - { + 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) - { + -not $isVariable) { # Try to parse the value based on the retrieved type. $scriptBlock = @" `$typeStaticMethods = $valueType | gm -static @@ -345,44 +286,29 @@ function ConvertTo-DscObject } "@ 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") - { + } elseif ($valueType -eq '[String]' -or $isVariable) { + if ($isVariable -and [Boolean]::TryParse($value.TrimStart('$'), [ref][Boolean])) { + if ($value -eq "`$true") { $value = $true - } - else - { + } else { $value = $false } - } - else - { + } else { $value = $value } - } - elseif ($valueType -eq '[string[]]') - { + } 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('@(')) - { + if ($value.GetType().Name -eq 'String' -and -not $value.StartsWith('@(')) { $value = @($value) } - } - else - { + } else { $isArray = $false - if ($keyValuePair.Item2.ToString().StartsWith('@(')) - { + if ($keyValuePair.Item2.ToString().StartsWith('@(')) { $isArray = $true } - if ($isArray) - { + if ($isArray) { $value = @($value) } } diff --git a/extensions/powershell/win_powershell.tests.ps1 b/extensions/powershell/win_powershell.tests.ps1 index a73ea9c8b..0c8ea664d 100644 --- a/extensions/powershell/win_powershell.tests.ps1 +++ b/extensions/powershell/win_powershell.tests.ps1 @@ -6,9 +6,6 @@ BeforeDiscovery { $identity = [System.Security.Principal.WindowsIdentity]::GetCurrent() $principal = [System.Security.Principal.WindowsPrincipal]::new($identity) $isElevated = $principal.IsInRole([System.Security.Principal.WindowsBuiltInRole]::Administrator) - - Write-Verbose -Message $env:Path -Verbose - Write-Verbose -Message $env:Path -Verbose } } @@ -41,4 +38,4 @@ configuration InvalidConfiguration { $content | Should -BeLike "*Importing file '$psFile' with extension 'Microsoft.DSC.Extension/WindowsPowerShell'*" $content | Should -Match "No DSC resources found in the imported modules." } -} \ No newline at end of file +} diff --git a/powershell-adapter/Tests/win_powershell_cache.tests.ps1 b/powershell-adapter/Tests/win_powershell_cache.tests.ps1 index c53f5cdfb..94e8d65d9 100644 --- a/powershell-adapter/Tests/win_powershell_cache.tests.ps1 +++ b/powershell-adapter/Tests/win_powershell_cache.tests.ps1 @@ -48,11 +48,9 @@ Describe 'WindowsPowerShell adapter resource tests - requires elevated permissio } It 'Get works on Binary "File" resource' { - Write-Verbose -Message $env:Path -Verbose - Write-Verbose -Message $env:PSModulePath -Verbose $testFile = "$testdrive\test.txt" 'test' | Set-Content -Path $testFile -Force - $r = '{"DestinationPath":"' + $testFile.replace('\', '\\') + '"}' | dsc -l trace resource get -r 'PSDesiredStateConfiguration/File' -f - + $r = '{"DestinationPath":"' + $testFile.replace('\', '\\') + '"}' | dsc resource get -r 'PSDesiredStateConfiguration/File' -f - $LASTEXITCODE | Should -Be 0 $res = $r | ConvertFrom-Json $res.actualState.DestinationPath | Should -Be "$testFile" From 246d471a62a7832f62ab5fd5d77bf17ff5ca282e Mon Sep 17 00:00:00 2001 From: "G.Reijn" Date: Sun, 10 Aug 2025 13:44:54 +0200 Subject: [PATCH 28/41] Trace one more time --- extensions/powershell/convertDscResource.psd1 | 55 ++++++++++--------- extensions/powershell/convertDscResource.psm1 | 3 +- 2 files changed, 31 insertions(+), 27 deletions(-) diff --git a/extensions/powershell/convertDscResource.psd1 b/extensions/powershell/convertDscResource.psd1 index ac0a1db93..1efd7026a 100644 --- a/extensions/powershell/convertDscResource.psd1 +++ b/extensions/powershell/convertDscResource.psd1 @@ -3,45 +3,48 @@ @{ -# Script module or binary module file associated with this manifest. -RootModule = 'convertDscResource.psm1' + # Script module or binary module file associated with this manifest. + RootModule = 'convertDscResource.psm1' -# Version number of this module. -moduleVersion = '0.0.1' + # Version number of this module. + moduleVersion = '0.0.1' -# ID used to uniquely identify this module -GUID = '95f93fd3-34ff-417e-80e4-f0112918a0bd' + # ID used to uniquely identify this module + GUID = '95f93fd3-34ff-417e-80e4-f0112918a0bd' -# Author of this module -Author = 'Microsoft Corporation' + # Author of this module + Author = 'Microsoft Corporation' -# Company or vendor of this module -CompanyName = 'Microsoft Corporation' + # Company or vendor of this module + CompanyName = 'Microsoft Corporation' -# Copyright statement for this module -Copyright = '(c) Microsoft Corporation. All rights reserved.' + # Copyright statement for this module + Copyright = '(c) Microsoft Corporation. All rights reserved.' -# Description of the functionality provided by this module -Description = 'PowerShell Desired State Configuration Module for converting DSC Resources to JSON format' + # Description of the functionality provided by this module + Description = 'PowerShell Desired State Configuration Module for converting DSC Resources to JSON format' -# Functions to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no functions to export. -FunctionsToExport = @( + # Minimum version of the Windows PowerShell engine required by this module + PowerShellVersion = '5.1' + + # Functions to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no functions to export. + FunctionsToExport = @( 'Write-DscTrace' 'Build-DscConfigDocument' ) -# Cmdlets to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no cmdlets to export. -CmdletsToExport = @() + # Cmdlets to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no cmdlets to export. + CmdletsToExport = @() -# Variables to export from this module -VariablesToExport = @() + # Variables to export from this module + VariablesToExport = @() -# Aliases to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no aliases to export. -AliasesToExport = @() + # Aliases to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no aliases to export. + AliasesToExport = @() -PrivateData = @{ - PSData = @{ - ProjectUri = 'https://github.com/PowerShell/DSC' + PrivateData = @{ + PSData = @{ + ProjectUri = 'https://github.com/PowerShell/DSC' + } } } -} diff --git a/extensions/powershell/convertDscResource.psm1 b/extensions/powershell/convertDscResource.psm1 index c7113fd7d..0e9133cbb 100644 --- a/extensions/powershell/convertDscResource.psm1 +++ b/extensions/powershell/convertDscResource.psm1 @@ -5,7 +5,7 @@ if ($Script:IsPowerShellCore) { Import-Module -Name 'PSDesiredStateConfiguration' -RequiredVersion 1.1 -UseWindowsPowerShell -WarningAction SilentlyContinue } Import-Module -Name 'PSDesiredStateConfiguration' -MinimumVersion 2.0.7 -Prefix 'Pwsh' -WarningAction SilentlyContinue -} else { +} else { Import-Module -Name 'PSDesiredStateConfiguration' -RequiredVersion 1.1 -WarningAction SilentlyContinue } @@ -22,6 +22,7 @@ function Write-DscTrace { $host.ui.WriteErrorLine($trace) } +"The current module paths: $env:PSModulePath" | Write-DscTrace -Operation Trace function Build-DscConfigDocument { [CmdletBinding()] [OutputType([System.Collections.Specialized.OrderedDictionary])] From c7a2aa36f6599e90db4e235e5f7dbb153e03b0fe Mon Sep 17 00:00:00 2001 From: "G.Reijn" Date: Sun, 10 Aug 2025 14:19:13 +0200 Subject: [PATCH 29/41] Trace second time --- extensions/powershell/convert-resource.ps1 | 2 ++ extensions/powershell/convertDscResource.psm1 | 1 - 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/extensions/powershell/convert-resource.ps1 b/extensions/powershell/convert-resource.ps1 index 633a2ac4d..3b681c5e3 100644 --- a/extensions/powershell/convert-resource.ps1 +++ b/extensions/powershell/convert-resource.ps1 @@ -8,6 +8,8 @@ begin { $lines = [System.Collections.Generic.List[string]]::new() $scriptModule = Import-Module "$PSScriptRoot/convertDscResource.psd1" -Force -PassThru -WarningAction SilentlyContinue -ErrorAction Stop + + "The current module paths: $env:PSModulePath" | Write-DscTrace -Operation Trace } process { diff --git a/extensions/powershell/convertDscResource.psm1 b/extensions/powershell/convertDscResource.psm1 index 0e9133cbb..9f374afba 100644 --- a/extensions/powershell/convertDscResource.psm1 +++ b/extensions/powershell/convertDscResource.psm1 @@ -22,7 +22,6 @@ function Write-DscTrace { $host.ui.WriteErrorLine($trace) } -"The current module paths: $env:PSModulePath" | Write-DscTrace -Operation Trace function Build-DscConfigDocument { [CmdletBinding()] [OutputType([System.Collections.Specialized.OrderedDictionary])] From a5371c8b7c2416b478e4ff62dff8da19291f2fa6 Mon Sep 17 00:00:00 2001 From: "G.Reijn" Date: Sun, 10 Aug 2025 16:54:52 +0200 Subject: [PATCH 30/41] Run line --- extensions/powershell/convert-resource.ps1 | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/extensions/powershell/convert-resource.ps1 b/extensions/powershell/convert-resource.ps1 index 3b681c5e3..db93b35ae 100644 --- a/extensions/powershell/convert-resource.ps1 +++ b/extensions/powershell/convert-resource.ps1 @@ -6,10 +6,9 @@ param ( begin { $lines = [System.Collections.Generic.List[string]]::new() + "The current module paths: $env:PSModulePath" | Write-DscTrace -Operation Trace $scriptModule = Import-Module "$PSScriptRoot/convertDscResource.psd1" -Force -PassThru -WarningAction SilentlyContinue -ErrorAction Stop - - "The current module paths: $env:PSModulePath" | Write-DscTrace -Operation Trace } process { From 70ab41b8b306dddcd5e318764881a4ac86a85db3 Mon Sep 17 00:00:00 2001 From: "G.Reijn" Date: Sun, 10 Aug 2025 16:55:46 +0200 Subject: [PATCH 31/41] Import error --- extensions/powershell/convert-resource.ps1 | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/extensions/powershell/convert-resource.ps1 b/extensions/powershell/convert-resource.ps1 index db93b35ae..5564ad960 100644 --- a/extensions/powershell/convert-resource.ps1 +++ b/extensions/powershell/convert-resource.ps1 @@ -6,7 +6,10 @@ param ( begin { $lines = [System.Collections.Generic.List[string]]::new() - "The current module paths: $env:PSModulePath" | Write-DscTrace -Operation Trace + $Operation = 'Trace' + + $trace = @{$Operation.ToLower() = "The current module paths: $env:PSModulePath" } | ConvertTo-Json -Compress + $host.ui.WriteErrorLine($trace) $scriptModule = Import-Module "$PSScriptRoot/convertDscResource.psd1" -Force -PassThru -WarningAction SilentlyContinue -ErrorAction Stop } From 52738e6284d36c115a05f26fd99041ebabbf4c45 Mon Sep 17 00:00:00 2001 From: "G.Reijn" Date: Sun, 10 Aug 2025 17:18:48 +0200 Subject: [PATCH 32/41] Remove all PowerShell modules from module path --- extensions/powershell/convert-resource.ps1 | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/extensions/powershell/convert-resource.ps1 b/extensions/powershell/convert-resource.ps1 index 5564ad960..98f4c9af9 100644 --- a/extensions/powershell/convert-resource.ps1 +++ b/extensions/powershell/convert-resource.ps1 @@ -6,10 +6,22 @@ param ( begin { $lines = [System.Collections.Generic.List[string]]::new() - $Operation = 'Trace' + + if ($PSVersionTable.PSEdition -ne 'Core') { + # Remove all PowerShell paths + $env:PSModulePath = ($env:PSModulePath -split ';' | Where-Object { + $_ -notmatch 'PowerShell[\\/]7' -and + $_ -notmatch 'Program Files[\\/]PowerShell[\\/]' -and + $_ -notmatch 'Documents[\\/]PowerShell[\\/]' + }) -join ';' - $trace = @{$Operation.ToLower() = "The current module paths: $env:PSModulePath" } | ConvertTo-Json -Compress - $host.ui.WriteErrorLine($trace) + # Make sure the default path is Windows PowerShell is included + $winPsPath = "$env:windir\System32\WindowsPowerShell\v1.0\Modules" + if ($env:PSModulePath -notmatch [regex]::Escape($winPsPath)) { + # Separator is already at the end + $env:PSModulePath = $env:PSModulePath + $winPsPath + } + } $scriptModule = Import-Module "$PSScriptRoot/convertDscResource.psd1" -Force -PassThru -WarningAction SilentlyContinue -ErrorAction Stop } From 71f31cca91f043349ad4bbc87dbfccf2fc27753f Mon Sep 17 00:00:00 2001 From: "G.Reijn" Date: Sun, 10 Aug 2025 17:56:58 +0200 Subject: [PATCH 33/41] Debug lines --- extensions/powershell/convertDscResource.psm1 | 24 ++++++++++--------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/extensions/powershell/convertDscResource.psm1 b/extensions/powershell/convertDscResource.psm1 index 9f374afba..f69f11f7a 100644 --- a/extensions/powershell/convertDscResource.psm1 +++ b/extensions/powershell/convertDscResource.psm1 @@ -1,14 +1,3 @@ -$Script:IsPowerShellCore = $PSVersionTable.PSEdition -eq 'Core' - -if ($Script:IsPowerShellCore) { - if ($IsWindows) { - Import-Module -Name 'PSDesiredStateConfiguration' -RequiredVersion 1.1 -UseWindowsPowerShell -WarningAction SilentlyContinue - } - Import-Module -Name 'PSDesiredStateConfiguration' -MinimumVersion 2.0.7 -Prefix 'Pwsh' -WarningAction SilentlyContinue -} else { - Import-Module -Name 'PSDesiredStateConfiguration' -RequiredVersion 1.1 -WarningAction SilentlyContinue -} - function Write-DscTrace { param( [Parameter(Mandatory = $false)] @@ -22,6 +11,19 @@ function Write-DscTrace { $host.ui.WriteErrorLine($trace) } +$Script:IsPowerShellCore = $PSVersionTable.PSEdition -eq 'Core' + +if ($Script:IsPowerShellCore) { + if ($IsWindows) { + Import-Module -Name 'PSDesiredStateConfiguration' -RequiredVersion 1.1 -UseWindowsPowerShell -WarningAction SilentlyContinue + } + Import-Module -Name 'PSDesiredStateConfiguration' -MinimumVersion 2.0.7 -Prefix 'Pwsh' -WarningAction SilentlyContinue +} else { + "Loading module: 'PSDesiredStateConfiguration" | Write-DscTrace -Operation Trace + "The PSModulePaths: $env:PSModulePath" | Write-DscTrace -Operation Trace + Import-Module -Name 'PSDesiredStateConfiguration' -RequiredVersion 1.1 -WarningAction SilentlyContinue +} + function Build-DscConfigDocument { [CmdletBinding()] [OutputType([System.Collections.Specialized.OrderedDictionary])] From 1d30a2fcbb3cb7c6d0ff2652159d21af9fd6b9b6 Mon Sep 17 00:00:00 2001 From: "G.Reijn" Date: Mon, 11 Aug 2025 00:40:04 +0200 Subject: [PATCH 34/41] Debug lines --- docs/reference/schemas/config/functions/union.md | 0 extensions/powershell/convert-resource.ps1 | 3 +-- 2 files changed, 1 insertion(+), 2 deletions(-) create mode 100644 docs/reference/schemas/config/functions/union.md diff --git a/docs/reference/schemas/config/functions/union.md b/docs/reference/schemas/config/functions/union.md new file mode 100644 index 000000000..e69de29bb diff --git a/extensions/powershell/convert-resource.ps1 b/extensions/powershell/convert-resource.ps1 index 98f4c9af9..36b76e925 100644 --- a/extensions/powershell/convert-resource.ps1 +++ b/extensions/powershell/convert-resource.ps1 @@ -18,8 +18,7 @@ begin { # Make sure the default path is Windows PowerShell is included $winPsPath = "$env:windir\System32\WindowsPowerShell\v1.0\Modules" if ($env:PSModulePath -notmatch [regex]::Escape($winPsPath)) { - # Separator is already at the end - $env:PSModulePath = $env:PSModulePath + $winPsPath + $env:PSModulePath = $env:PSModulePath + [System.IO.Path]::PathSeparator + $winPsPath } } From 87da4d098d3c8fb46034c01541f311bb5081de42 Mon Sep 17 00:00:00 2001 From: "G.Reijn" Date: Mon, 11 Aug 2025 01:23:48 +0200 Subject: [PATCH 35/41] Cast tests to string --- .../schemas/config/functions/union.md | 0 extensions/powershell/convertDscResource.psm1 | 24 +++++++++---------- .../powershell/win_powershell.tests.ps1 | 6 +++-- .../Tests/win_powershell_cache.tests.ps1 | 1 + 4 files changed, 16 insertions(+), 15 deletions(-) delete mode 100644 docs/reference/schemas/config/functions/union.md diff --git a/docs/reference/schemas/config/functions/union.md b/docs/reference/schemas/config/functions/union.md deleted file mode 100644 index e69de29bb..000000000 diff --git a/extensions/powershell/convertDscResource.psm1 b/extensions/powershell/convertDscResource.psm1 index f69f11f7a..9f374afba 100644 --- a/extensions/powershell/convertDscResource.psm1 +++ b/extensions/powershell/convertDscResource.psm1 @@ -1,3 +1,14 @@ +$Script:IsPowerShellCore = $PSVersionTable.PSEdition -eq 'Core' + +if ($Script:IsPowerShellCore) { + if ($IsWindows) { + Import-Module -Name 'PSDesiredStateConfiguration' -RequiredVersion 1.1 -UseWindowsPowerShell -WarningAction SilentlyContinue + } + Import-Module -Name 'PSDesiredStateConfiguration' -MinimumVersion 2.0.7 -Prefix 'Pwsh' -WarningAction SilentlyContinue +} else { + Import-Module -Name 'PSDesiredStateConfiguration' -RequiredVersion 1.1 -WarningAction SilentlyContinue +} + function Write-DscTrace { param( [Parameter(Mandatory = $false)] @@ -11,19 +22,6 @@ function Write-DscTrace { $host.ui.WriteErrorLine($trace) } -$Script:IsPowerShellCore = $PSVersionTable.PSEdition -eq 'Core' - -if ($Script:IsPowerShellCore) { - if ($IsWindows) { - Import-Module -Name 'PSDesiredStateConfiguration' -RequiredVersion 1.1 -UseWindowsPowerShell -WarningAction SilentlyContinue - } - Import-Module -Name 'PSDesiredStateConfiguration' -MinimumVersion 2.0.7 -Prefix 'Pwsh' -WarningAction SilentlyContinue -} else { - "Loading module: 'PSDesiredStateConfiguration" | Write-DscTrace -Operation Trace - "The PSModulePaths: $env:PSModulePath" | Write-DscTrace -Operation Trace - Import-Module -Name 'PSDesiredStateConfiguration' -RequiredVersion 1.1 -WarningAction SilentlyContinue -} - function Build-DscConfigDocument { [CmdletBinding()] [OutputType([System.Collections.Specialized.OrderedDictionary])] diff --git a/extensions/powershell/win_powershell.tests.ps1 b/extensions/powershell/win_powershell.tests.ps1 index 0c8ea664d..2b19fbd43 100644 --- a/extensions/powershell/win_powershell.tests.ps1 +++ b/extensions/powershell/win_powershell.tests.ps1 @@ -15,7 +15,8 @@ Describe 'PowerShell extension tests' { $out = dsc -l trace config get -f $psFile 2>$TestDrive/error.log | ConvertFrom-Json $LASTEXITCODE | Should -Be 0 -Because (Get-Content -Path $TestDrive/error.log -Raw | Out-String) $out.results[0].result.actualState.Ensure | Should -Be 'Absent' - (Get-Content -Path $TestDrive/error.log -Raw) | Should -Match "Importing file '$psFile' with extension 'Microsoft.DSC.Extension/PowerShell'" + $psFile = $psFile.ToString().Replace('\', '\\') + (Get-Content -Path $TestDrive/error.log -Raw) | Should -Match "Importing file '$psFile' with extension 'Microsoft.DSC.Extension/WindowsPowerShell'" } It 'Invalid PowerShell configuration document file returns error' -Skip:(!$IsWindows) { @@ -35,7 +36,8 @@ configuration InvalidConfiguration { dsc -l trace config get -f $psFile 2>$TestDrive/error.log $LASTEXITCODE | Should -Be 2 -Because (Get-Content -Path $TestDrive/error.log -Raw | Out-String) $content = (Get-Content -Path $TestDrive/error.log -Raw) - $content | Should -BeLike "*Importing file '$psFile' with extension 'Microsoft.DSC.Extension/WindowsPowerShell'*" + $psFile = $psFile.ToString().Replace('\', '\\') + $content | Should -Match "Importing file '$psFile' with extension 'Microsoft.DSC.Extension/WindowsPowerShell'" $content | Should -Match "No DSC resources found in the imported modules." } } diff --git a/powershell-adapter/Tests/win_powershell_cache.tests.ps1 b/powershell-adapter/Tests/win_powershell_cache.tests.ps1 index 94e8d65d9..6ed7bec1c 100644 --- a/powershell-adapter/Tests/win_powershell_cache.tests.ps1 +++ b/powershell-adapter/Tests/win_powershell_cache.tests.ps1 @@ -48,6 +48,7 @@ Describe 'WindowsPowerShell adapter resource tests - requires elevated permissio } It 'Get works on Binary "File" resource' { + $testFile = "$testdrive\test.txt" 'test' | Set-Content -Path $testFile -Force $r = '{"DestinationPath":"' + $testFile.replace('\', '\\') + '"}' | dsc resource get -r 'PSDesiredStateConfiguration/File' -f - From 75aebd717d2146013f8c11b8d4e1903cc8712481 Mon Sep 17 00:00:00 2001 From: "G.Reijn" Date: Mon, 11 Aug 2025 01:49:26 +0200 Subject: [PATCH 36/41] Include Microsoft.DSC.Extension/PowerShell --- build.ps1 | 11 +++- extensions/powershell/convertDscResource.psd1 | 3 - extensions/powershell/convertDscResource.psm1 | 8 +-- extensions/powershell/copy_files.txt | 1 + .../powershell/powershell.dsc.extension.json | 23 ++++++++ extensions/powershell/powershell.tests.ps1 | 56 +++++++++++++++++++ .../powershell/win_powershell.tests.ps1 | 4 +- .../windowspowershell.dsc.extension.json | 4 +- 8 files changed, 98 insertions(+), 12 deletions(-) create mode 100644 extensions/powershell/powershell.dsc.extension.json create mode 100644 extensions/powershell/powershell.tests.ps1 diff --git a/build.ps1 b/build.ps1 index c5dbcffd4..9d416cc2c 100755 --- a/build.ps1 +++ b/build.ps1 @@ -63,7 +63,7 @@ $filesForWindowsPackage = @( 'osinfo.exe', 'osinfo.dsc.resource.json', 'powershell.dsc.resource.json', - 'windowspowershell.dsc.extension.json' + 'powershell.dsc.extension.json', 'psDscAdapter/', 'psscript.ps1', 'psscript.dsc.resource.json', @@ -77,6 +77,7 @@ $filesForWindowsPackage = @( 'sshdconfig.exe', 'sshd-windows.dsc.resource.json', 'sshd_config.dsc.resource.json', + 'windowspowershell.dsc.extension.json', 'windowspowershell.dsc.resource.json', 'wmi.dsc.resource.json', 'wmi.resource.ps1', @@ -88,6 +89,9 @@ $filesForWindowsPackage = @( $filesForLinuxPackage = @( 'bicep.dsc.extension.json', + 'convert-resource.ps1', + 'convertDscResource.psd1', + 'convertDscResource.psm1', 'dsc', 'dsc_default.settings.json', 'dsc.settings.json', @@ -102,6 +106,7 @@ $filesForLinuxPackage = @( 'osinfo', 'osinfo.dsc.resource.json', 'powershell.dsc.resource.json', + 'powershell.dsc.extension.json', 'psDscAdapter/', 'psscript.ps1', 'psscript.dsc.resource.json', @@ -113,6 +118,9 @@ $filesForLinuxPackage = @( $filesForMacPackage = @( 'bicep.dsc.extension.json', + 'convert-resource.ps1', + 'convertDscResource.psd1', + 'convertDscResource.psm1', 'dsc', 'dsc_default.settings.json', 'dsc.settings.json', @@ -127,6 +135,7 @@ $filesForMacPackage = @( 'osinfo', 'osinfo.dsc.resource.json', 'powershell.dsc.resource.json', + 'powershell.dsc.extension.json', 'psDscAdapter/', 'psscript.ps1', 'psscript.dsc.resource.json', diff --git a/extensions/powershell/convertDscResource.psd1 b/extensions/powershell/convertDscResource.psd1 index 1efd7026a..bb3aebf9b 100644 --- a/extensions/powershell/convertDscResource.psd1 +++ b/extensions/powershell/convertDscResource.psd1 @@ -24,9 +24,6 @@ # Description of the functionality provided by this module Description = 'PowerShell Desired State Configuration Module for converting DSC Resources to JSON format' - # Minimum version of the Windows PowerShell engine required by this module - PowerShellVersion = '5.1' - # Functions to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no functions to export. FunctionsToExport = @( 'Write-DscTrace' diff --git a/extensions/powershell/convertDscResource.psm1 b/extensions/powershell/convertDscResource.psm1 index 9f374afba..ef82ba2d3 100644 --- a/extensions/powershell/convertDscResource.psm1 +++ b/extensions/powershell/convertDscResource.psm1 @@ -2,11 +2,11 @@ $Script:IsPowerShellCore = $PSVersionTable.PSEdition -eq 'Core' if ($Script:IsPowerShellCore) { if ($IsWindows) { - Import-Module -Name 'PSDesiredStateConfiguration' -RequiredVersion 1.1 -UseWindowsPowerShell -WarningAction SilentlyContinue + Import-Module -Name 'PSDesiredStateConfiguration' -RequiredVersion 1.1 -UseWindowsPowerShell -WarningAction SilentlyContinue -ErrorAction Stop } - Import-Module -Name 'PSDesiredStateConfiguration' -MinimumVersion 2.0.7 -Prefix 'Pwsh' -WarningAction SilentlyContinue -} else { - Import-Module -Name 'PSDesiredStateConfiguration' -RequiredVersion 1.1 -WarningAction SilentlyContinue + Import-Module -Name 'PSDesiredStateConfiguration' -MinimumVersion 2.0.7 -Prefix 'Pwsh' -WarningAction SilentlyContinue -ErrorAction Stop +} else { + Import-Module -Name 'PSDesiredStateConfiguration' -RequiredVersion 1.1 -WarningAction SilentlyContinue -ErrorAction Stop } function Write-DscTrace { diff --git a/extensions/powershell/copy_files.txt b/extensions/powershell/copy_files.txt index 9ab85b46b..ae0ed7751 100644 --- a/extensions/powershell/copy_files.txt +++ b/extensions/powershell/copy_files.txt @@ -1,3 +1,4 @@ +powershell.dsc.extension.json windowspowershell.dsc.extension.json convert-resource.ps1 convertDscResource.psd1 diff --git a/extensions/powershell/powershell.dsc.extension.json b/extensions/powershell/powershell.dsc.extension.json new file mode 100644 index 000000000..b1f10900f --- /dev/null +++ b/extensions/powershell/powershell.dsc.extension.json @@ -0,0 +1,23 @@ +{ + "$schema": "https://aka.ms/dsc/schemas/v3/bundled/resource/manifest.json", + "type": "Microsoft.DSC.Extension/PowerShell", + "version": "0.1.0", + "description": "Enable passing PowerShell v2 configuration document file directly to DSC. Requires PSDesiredStateConfiguration v2.0.7.", + "import": { + "fileExtensions": ["ps1"], + "executable": "pwsh", + "args": [ + "-NoLogo", + "-NonInteractive", + "-NoProfile", + "-ExecutionPolicy", + "Bypass", + "-Command", + "Get-Content", + { + "fileArg": "" + }, + "| ./convert-resource.ps1" + ] + } +} diff --git a/extensions/powershell/powershell.tests.ps1 b/extensions/powershell/powershell.tests.ps1 new file mode 100644 index 000000000..1a255515e --- /dev/null +++ b/extensions/powershell/powershell.tests.ps1 @@ -0,0 +1,56 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +BeforeDiscovery { + if ($IsWindows) { + $identity = [System.Security.Principal.WindowsIdentity]::GetCurrent() + $principal = [System.Security.Principal.WindowsPrincipal]::new($identity) + $isElevated = $principal.IsInRole([System.Security.Principal.WindowsBuiltInRole]::Administrator) + } +} + +Describe 'PowerShell extension tests' { + It 'Example PowerShell file should work' -Skip:(!$IsWindows -or !$isElevated) { + $powerShellConfiguration = @" +configuration TestClassConfiguration { + Import-DscResource -ModuleName TestClassResource + Node localhost + { + TestClassResource TestClass { + Name = 'Test' + } + } +} +"@ + $config_path = "$TestDrive/testclass.ps1" + $powerShellConfiguration | Set-Content -Path $config_path + $out = dsc -l trace config get -f $config_path 2>$TestDrive/error.log | ConvertFrom-Json + $LASTEXITCODE | Should -Be 0 -Because (Get-Content -Path $TestDrive/error.log -Raw | Out-String) + $out.results[0].result.actualState.Ensure | Should -Be 'Present' + $out.results[0].result.actualState.Name | Should -Be 'Test' + $config_path = $config_path.ToString().Replace('\', '\\') + (Get-Content -Path $TestDrive/error.log -Raw) | Should -Match "Importing file '$config_path' with extension 'Microsoft.DSC.Extension/PowerShell'" + } + + It 'Invalid PowerShell configuration document file returns error' -Skip:(!$IsWindows) { + $psFile = "$TestDrive/invalid.ps1" + Set-Content -Path $psFile -Value @" +configuration InvalidConfiguration { + Import-DscResource -ModuleName InvalidModule + Node localhost + { + Test Invalid { + Name = 'InvalidTest' + Ensure = 'Present' + } + } +} +"@ + dsc -l trace config get -f $psFile 2>$TestDrive/error.log + $LASTEXITCODE | Should -Be 2 -Because (Get-Content -Path $TestDrive/error.log -Raw | Out-String) + $content = (Get-Content -Path $TestDrive/error.log -Raw) + $psFile = $psFile.ToString().Replace('\', '\\') + $content | Should -Match "Importing file '$psFile' with extension 'Microsoft.DSC.Extension/PowerShell'" + $content | Should -Match "No DSC resources found in the imported modules." + } +} diff --git a/extensions/powershell/win_powershell.tests.ps1 b/extensions/powershell/win_powershell.tests.ps1 index 2b19fbd43..bbb7d1363 100644 --- a/extensions/powershell/win_powershell.tests.ps1 +++ b/extensions/powershell/win_powershell.tests.ps1 @@ -16,7 +16,7 @@ Describe 'PowerShell extension tests' { $LASTEXITCODE | Should -Be 0 -Because (Get-Content -Path $TestDrive/error.log -Raw | Out-String) $out.results[0].result.actualState.Ensure | Should -Be 'Absent' $psFile = $psFile.ToString().Replace('\', '\\') - (Get-Content -Path $TestDrive/error.log -Raw) | Should -Match "Importing file '$psFile' with extension 'Microsoft.DSC.Extension/WindowsPowerShell'" + (Get-Content -Path $TestDrive/error.log -Raw) | Should -Match "Importing file '$psFile' with extension 'Microsoft.Windows.Extension/WindowsPowerShell'" } It 'Invalid PowerShell configuration document file returns error' -Skip:(!$IsWindows) { @@ -37,7 +37,7 @@ configuration InvalidConfiguration { $LASTEXITCODE | Should -Be 2 -Because (Get-Content -Path $TestDrive/error.log -Raw | Out-String) $content = (Get-Content -Path $TestDrive/error.log -Raw) $psFile = $psFile.ToString().Replace('\', '\\') - $content | Should -Match "Importing file '$psFile' with extension 'Microsoft.DSC.Extension/WindowsPowerShell'" + $content | Should -Match "Importing file '$psFile' with extension 'Microsoft.Windows.Extension/WindowsPowerShell'" $content | Should -Match "No DSC resources found in the imported modules." } } diff --git a/extensions/powershell/windowspowershell.dsc.extension.json b/extensions/powershell/windowspowershell.dsc.extension.json index 375520a56..d7613a935 100644 --- a/extensions/powershell/windowspowershell.dsc.extension.json +++ b/extensions/powershell/windowspowershell.dsc.extension.json @@ -1,8 +1,8 @@ { "$schema": "https://aka.ms/dsc/schemas/v3/bundled/resource/manifest.json", - "type": "Microsoft.DSC.Extension/WindowsPowerShell", + "type": "Microsoft.Windows.Extension/WindowsPowerShell", "version": "0.1.0", - "description": "Enable passing Windows PowerShell v1 configuration document file directly to DSC. Works only on Windows.", + "description": "Enable passing Windows PowerShell v1 configuration document file directly to DSC. Works only on Windows and leverages the built-in PSDesiredStateConfiguration module.", "import": { "fileExtensions": ["ps1"], "executable": "powershell", From 4cca19701accd738913e44dad7f8ce16bbc687db Mon Sep 17 00:00:00 2001 From: "G.Reijn" Date: Mon, 11 Aug 2025 02:24:23 +0200 Subject: [PATCH 37/41] Fix discovery test --- dsc/tests/dsc_extension_discover.tests.ps1 | 28 ++++++++++++------- extensions/powershell/convertDscResource.psm1 | 1 + extensions/powershell/powershell.tests.ps1 | 4 +-- 3 files changed, 21 insertions(+), 12 deletions(-) diff --git a/dsc/tests/dsc_extension_discover.tests.ps1 b/dsc/tests/dsc_extension_discover.tests.ps1 index d033c1c70..26cad7047 100644 --- a/dsc/tests/dsc_extension_discover.tests.ps1 +++ b/dsc/tests/dsc_extension_discover.tests.ps1 @@ -24,33 +24,41 @@ Describe 'Discover extension tests' { $out = dsc extension list | ConvertFrom-Json $LASTEXITCODE | Should -Be 0 if ($IsWindows) { - $out.Count | Should -Be 4 -Because ($out | Out-String) + $out.Count | Should -Be 5 -Because ($out | Out-String) $out[0].type | Should -Be 'Microsoft.DSC.Extension/Bicep' $out[0].version | Should -Be '0.1.0' $out[0].capabilities | Should -BeExactly @('import') $out[0].manifest | Should -Not -BeNullOrEmpty - $out[1].type | Should -Be 'Microsoft.DSC.Extension/WindowsPowerShell' + $out[1].type | Should -Be 'Microsoft.DSC.Extension/PowerShell' $out[1].version | Should -Be '0.1.0' $out[1].capabilities | Should -BeExactly @('import') $out[1].manifest | Should -Not -BeNullOrEmpty - $out[2].type | Should -Be 'Microsoft.Windows.Appx/Discover' + $out[2].type | Should -Be 'Microsoft.DSC.Extension/WindowsPowerShell' $out[2].version | Should -Be '0.1.0' - $out[2].capabilities | Should -BeExactly @('discover') + $out[2].capabilities | Should -BeExactly @('import') $out[2].manifest | Should -Not -BeNullOrEmpty - $out[3].type | Should -BeExactly 'Test/Discover' - $out[3].version | Should -BeExactly '0.1.0' + $out[3].type | Should -Be 'Microsoft.Windows.Appx/Discover' + $out[3].version | Should -Be '0.1.0' $out[3].capabilities | Should -BeExactly @('discover') $out[3].manifest | Should -Not -BeNullOrEmpty + $out[4].type | Should -BeExactly 'Test/Discover' + $out[4].version | Should -BeExactly '0.1.0' + $out[4].capabilities | Should -BeExactly @('discover') + $out[4].manifest | Should -Not -BeNullOrEmpty } else { - $out.Count | Should -Be 2 -Because ($out | Out-String) + $out.Count | Should -Be 3 -Because ($out | Out-String) $out[0].type | Should -Be 'Microsoft.DSC.Extension/Bicep' $out[0].version | Should -Be '0.1.0' $out[0].capabilities | Should -BeExactly @('import') $out[0].manifest | Should -Not -BeNullOrEmpty - $out[1].type | Should -BeExactly 'Test/Discover' - $out[1].version | Should -BeExactly '0.1.0' - $out[1].capabilities | Should -BeExactly @('discover') + $out[1].type | Should -Be 'Microsoft.DSC.Extension/PowerShell' + $out[1].version | Should -Be '0.1.0' + $out[1].capabilities | Should -BeExactly @('import') $out[1].manifest | Should -Not -BeNullOrEmpty + $out[2].type | Should -BeExactly 'Test/Discover' + $out[2].version | Should -BeExactly '0.1.0' + $out[2].capabilities | Should -BeExactly @('discover') + $out[2].manifest | Should -Not -BeNullOrEmpty } } diff --git a/extensions/powershell/convertDscResource.psm1 b/extensions/powershell/convertDscResource.psm1 index ef82ba2d3..236c63e69 100644 --- a/extensions/powershell/convertDscResource.psm1 +++ b/extensions/powershell/convertDscResource.psm1 @@ -6,6 +6,7 @@ if ($Script:IsPowerShellCore) { } Import-Module -Name 'PSDesiredStateConfiguration' -MinimumVersion 2.0.7 -Prefix 'Pwsh' -WarningAction SilentlyContinue -ErrorAction Stop } else { + "Loaded module paths: $env:PSModulePath" | Write-DscTrace -Operation Trace Import-Module -Name 'PSDesiredStateConfiguration' -RequiredVersion 1.1 -WarningAction SilentlyContinue -ErrorAction Stop } diff --git a/extensions/powershell/powershell.tests.ps1 b/extensions/powershell/powershell.tests.ps1 index 1a255515e..5b7514cf7 100644 --- a/extensions/powershell/powershell.tests.ps1 +++ b/extensions/powershell/powershell.tests.ps1 @@ -10,7 +10,7 @@ BeforeDiscovery { } Describe 'PowerShell extension tests' { - It 'Example PowerShell file should work' -Skip:(!$IsWindows -or !$isElevated) { + It 'Example PowerShell file should work' -Skip:(!$isElevated) { $powerShellConfiguration = @" configuration TestClassConfiguration { Import-DscResource -ModuleName TestClassResource @@ -32,7 +32,7 @@ configuration TestClassConfiguration { (Get-Content -Path $TestDrive/error.log -Raw) | Should -Match "Importing file '$config_path' with extension 'Microsoft.DSC.Extension/PowerShell'" } - It 'Invalid PowerShell configuration document file returns error' -Skip:(!$IsWindows) { + It 'Invalid PowerShell configuration document file returns error' { $psFile = "$TestDrive/invalid.ps1" Set-Content -Path $psFile -Value @" configuration InvalidConfiguration { From 6bdf5593f894cfb8a219685c9715b53ff5b405e6 Mon Sep 17 00:00:00 2001 From: "G.Reijn" Date: Mon, 11 Aug 2025 02:30:00 +0200 Subject: [PATCH 38/41] Not working with index --- dsc/tests/dsc_extension_discover.tests.ps1 | 58 +++++++++------------- 1 file changed, 23 insertions(+), 35 deletions(-) diff --git a/dsc/tests/dsc_extension_discover.tests.ps1 b/dsc/tests/dsc_extension_discover.tests.ps1 index 26cad7047..df04e825e 100644 --- a/dsc/tests/dsc_extension_discover.tests.ps1 +++ b/dsc/tests/dsc_extension_discover.tests.ps1 @@ -23,42 +23,30 @@ Describe 'Discover extension tests' { It 'Discover extensions' { $out = dsc extension list | ConvertFrom-Json $LASTEXITCODE | Should -Be 0 - if ($IsWindows) { - $out.Count | Should -Be 5 -Because ($out | Out-String) - $out[0].type | Should -Be 'Microsoft.DSC.Extension/Bicep' - $out[0].version | Should -Be '0.1.0' - $out[0].capabilities | Should -BeExactly @('import') - $out[0].manifest | Should -Not -BeNullOrEmpty - $out[1].type | Should -Be 'Microsoft.DSC.Extension/PowerShell' - $out[1].version | Should -Be '0.1.0' - $out[1].capabilities | Should -BeExactly @('import') - $out[1].manifest | Should -Not -BeNullOrEmpty - $out[2].type | Should -Be 'Microsoft.DSC.Extension/WindowsPowerShell' - $out[2].version | Should -Be '0.1.0' - $out[2].capabilities | Should -BeExactly @('import') - $out[2].manifest | Should -Not -BeNullOrEmpty - $out[3].type | Should -Be 'Microsoft.Windows.Appx/Discover' - $out[3].version | Should -Be '0.1.0' - $out[3].capabilities | Should -BeExactly @('discover') - $out[3].manifest | Should -Not -BeNullOrEmpty - $out[4].type | Should -BeExactly 'Test/Discover' - $out[4].version | Should -BeExactly '0.1.0' - $out[4].capabilities | Should -BeExactly @('discover') - $out[4].manifest | Should -Not -BeNullOrEmpty + $expectedExtensions = if ($IsWindows) { + @( + @{ type = 'Microsoft.DSC.Extension/Bicep'; version = '0.1.0'; capabilities = @('import') } + @{ type = 'Microsoft.DSC.Extension/PowerShell'; version = '0.1.0'; capabilities = @('import') } + @{ type = 'Microsoft.DSC.Extension/WindowsPowerShell'; version = '0.1.0'; capabilities = @('import') } + @{ type = 'Microsoft.Windows.Appx/Discover'; version = '0.1.0'; capabilities = @('discover') } + @{ type = 'Test/Discover'; version = '0.1.0'; capabilities = @('discover') } + ) } else { - $out.Count | Should -Be 3 -Because ($out | Out-String) - $out[0].type | Should -Be 'Microsoft.DSC.Extension/Bicep' - $out[0].version | Should -Be '0.1.0' - $out[0].capabilities | Should -BeExactly @('import') - $out[0].manifest | Should -Not -BeNullOrEmpty - $out[1].type | Should -Be 'Microsoft.DSC.Extension/PowerShell' - $out[1].version | Should -Be '0.1.0' - $out[1].capabilities | Should -BeExactly @('import') - $out[1].manifest | Should -Not -BeNullOrEmpty - $out[2].type | Should -BeExactly 'Test/Discover' - $out[2].version | Should -BeExactly '0.1.0' - $out[2].capabilities | Should -BeExactly @('discover') - $out[2].manifest | Should -Not -BeNullOrEmpty + @( + @{ type = 'Microsoft.DSC.Extension/Bicep'; version = '0.1.0'; capabilities = @('import') } + @{ type = 'Microsoft.DSC.Extension/PowerShell'; version = '0.1.0'; capabilities = @('import') } + @{ type = 'Test/Discover'; version = '0.1.0'; capabilities = @('discover') } + ) + } + + $out.Count | Should -Be $expectedExtensions.Count -Because ($out | Out-String) + + foreach ($expected in $expectedExtensions) { + $extension = $out | Where-Object { $_.type -eq $expected.type } + $extension | Should -Not -BeNullOrEmpty -Because "Extension $($expected.type) should exist" + $extension.version | Should -BeExactly $expected.version + $extension.capabilities | Should -BeExactly $expected.capabilities + $extension.manifest | Should -Not -BeNullOrEmpty } } From f7069cb76b53cbaaa83f6761c0d2f7a314cd5233 Mon Sep 17 00:00:00 2001 From: "G.Reijn" Date: Mon, 11 Aug 2025 04:34:46 +0200 Subject: [PATCH 39/41] Revert changes pwsh --- dsc/tests/dsc_extension_discover.tests.ps1 | 48 ++++++++----------- .../powershell/win_powershell.tests.ps1 | 4 +- .../windowspowershell.dsc.extension.json | 4 +- .../Tests/win_powershell_cache.tests.ps1 | 1 - 4 files changed, 25 insertions(+), 32 deletions(-) diff --git a/dsc/tests/dsc_extension_discover.tests.ps1 b/dsc/tests/dsc_extension_discover.tests.ps1 index d033c1c70..59a5f46fc 100644 --- a/dsc/tests/dsc_extension_discover.tests.ps1 +++ b/dsc/tests/dsc_extension_discover.tests.ps1 @@ -23,34 +23,28 @@ Describe 'Discover extension tests' { It 'Discover extensions' { $out = dsc extension list | ConvertFrom-Json $LASTEXITCODE | Should -Be 0 - if ($IsWindows) { - $out.Count | Should -Be 4 -Because ($out | Out-String) - $out[0].type | Should -Be 'Microsoft.DSC.Extension/Bicep' - $out[0].version | Should -Be '0.1.0' - $out[0].capabilities | Should -BeExactly @('import') - $out[0].manifest | Should -Not -BeNullOrEmpty - $out[1].type | Should -Be 'Microsoft.DSC.Extension/WindowsPowerShell' - $out[1].version | Should -Be '0.1.0' - $out[1].capabilities | Should -BeExactly @('import') - $out[1].manifest | Should -Not -BeNullOrEmpty - $out[2].type | Should -Be 'Microsoft.Windows.Appx/Discover' - $out[2].version | Should -Be '0.1.0' - $out[2].capabilities | Should -BeExactly @('discover') - $out[2].manifest | Should -Not -BeNullOrEmpty - $out[3].type | Should -BeExactly 'Test/Discover' - $out[3].version | Should -BeExactly '0.1.0' - $out[3].capabilities | Should -BeExactly @('discover') - $out[3].manifest | Should -Not -BeNullOrEmpty + $expectedExtensions = if ($IsWindows) { + @( + @{ type = 'Microsoft.DSC.Extension/Bicep'; version = '0.1.0'; capabilities = @('import') } + @{ type = 'Microsoft.DSC.Transitional/PSDesiredStateConfiguration'; version = '0.1.0'; capabilities = @('import') } + @{ type = 'Microsoft.Windows.Appx/Discover'; version = '0.1.0'; capabilities = @('discover') } + @{ type = 'Test/Discover'; version = '0.1.0'; capabilities = @('discover') } + ) } else { - $out.Count | Should -Be 2 -Because ($out | Out-String) - $out[0].type | Should -Be 'Microsoft.DSC.Extension/Bicep' - $out[0].version | Should -Be '0.1.0' - $out[0].capabilities | Should -BeExactly @('import') - $out[0].manifest | Should -Not -BeNullOrEmpty - $out[1].type | Should -BeExactly 'Test/Discover' - $out[1].version | Should -BeExactly '0.1.0' - $out[1].capabilities | Should -BeExactly @('discover') - $out[1].manifest | Should -Not -BeNullOrEmpty + @( + @{ type = 'Microsoft.DSC.Extension/Bicep'; version = '0.1.0'; capabilities = @('import') } + @{ type = 'Test/Discover'; version = '0.1.0'; capabilities = @('discover') } + ) + } + + $out.Count | Should -Be $expectedExtensions.Count -Because ($out | Out-String) + + foreach ($expected in $expectedExtensions) { + $extension = $out | Where-Object { $_.type -eq $expected.type } + $extension | Should -Not -BeNullOrEmpty -Because "Extension $($expected.type) should exist" + $extension.version | Should -BeExactly $expected.version + $extension.capabilities | Should -BeExactly $expected.capabilities + $extension.manifest | Should -Not -BeNullOrEmpty } } diff --git a/extensions/powershell/win_powershell.tests.ps1 b/extensions/powershell/win_powershell.tests.ps1 index 2b19fbd43..a9a886491 100644 --- a/extensions/powershell/win_powershell.tests.ps1 +++ b/extensions/powershell/win_powershell.tests.ps1 @@ -16,7 +16,7 @@ Describe 'PowerShell extension tests' { $LASTEXITCODE | Should -Be 0 -Because (Get-Content -Path $TestDrive/error.log -Raw | Out-String) $out.results[0].result.actualState.Ensure | Should -Be 'Absent' $psFile = $psFile.ToString().Replace('\', '\\') - (Get-Content -Path $TestDrive/error.log -Raw) | Should -Match "Importing file '$psFile' with extension 'Microsoft.DSC.Extension/WindowsPowerShell'" + (Get-Content -Path $TestDrive/error.log -Raw) | Should -Match "Importing file '$psFile' with extension 'Microsoft.DSC.Transitional/PSDesiredStateConfiguration'" } It 'Invalid PowerShell configuration document file returns error' -Skip:(!$IsWindows) { @@ -37,7 +37,7 @@ configuration InvalidConfiguration { $LASTEXITCODE | Should -Be 2 -Because (Get-Content -Path $TestDrive/error.log -Raw | Out-String) $content = (Get-Content -Path $TestDrive/error.log -Raw) $psFile = $psFile.ToString().Replace('\', '\\') - $content | Should -Match "Importing file '$psFile' with extension 'Microsoft.DSC.Extension/WindowsPowerShell'" + $content | Should -Match "Importing file '$psFile' with extension 'Microsoft.DSC.Transitional/PSDesiredStateConfiguration'" $content | Should -Match "No DSC resources found in the imported modules." } } diff --git a/extensions/powershell/windowspowershell.dsc.extension.json b/extensions/powershell/windowspowershell.dsc.extension.json index 375520a56..d4b35fd61 100644 --- a/extensions/powershell/windowspowershell.dsc.extension.json +++ b/extensions/powershell/windowspowershell.dsc.extension.json @@ -1,8 +1,8 @@ { "$schema": "https://aka.ms/dsc/schemas/v3/bundled/resource/manifest.json", - "type": "Microsoft.DSC.Extension/WindowsPowerShell", + "type": "Microsoft.DSC.Transitional/PSDesiredStateConfiguration", "version": "0.1.0", - "description": "Enable passing Windows PowerShell v1 configuration document file directly to DSC. Works only on Windows.", + "description": "Enable passing Windows PowerShell v1 configuration document file directly to DSC. Works only on Windows and leverages the built-in PSDesiredStateConfiguration module.", "import": { "fileExtensions": ["ps1"], "executable": "powershell", diff --git a/powershell-adapter/Tests/win_powershell_cache.tests.ps1 b/powershell-adapter/Tests/win_powershell_cache.tests.ps1 index 6ed7bec1c..94e8d65d9 100644 --- a/powershell-adapter/Tests/win_powershell_cache.tests.ps1 +++ b/powershell-adapter/Tests/win_powershell_cache.tests.ps1 @@ -48,7 +48,6 @@ Describe 'WindowsPowerShell adapter resource tests - requires elevated permissio } It 'Get works on Binary "File" resource' { - $testFile = "$testdrive\test.txt" 'test' | Set-Content -Path $testFile -Force $r = '{"DestinationPath":"' + $testFile.replace('\', '\\') + '"}' | dsc resource get -r 'PSDesiredStateConfiguration/File' -f - From d46d0b27e234bff9f08ce9ec564681dbc3834aef Mon Sep 17 00:00:00 2001 From: "G.Reijn" Date: Mon, 11 Aug 2025 06:44:23 +0200 Subject: [PATCH 40/41] Wrong commit id --- build.ps1 | 12 ---- extensions/powershell/convertDscResource.psm1 | 1 - extensions/powershell/copy_files.txt | 1 - .../powershell/powershell.dsc.extension.json | 23 -------- extensions/powershell/powershell.tests.ps1 | 56 ------------------- 5 files changed, 93 deletions(-) delete mode 100644 extensions/powershell/powershell.dsc.extension.json delete mode 100644 extensions/powershell/powershell.tests.ps1 diff --git a/build.ps1 b/build.ps1 index 9d416cc2c..063af8294 100755 --- a/build.ps1 +++ b/build.ps1 @@ -48,9 +48,6 @@ $filesForWindowsPackage = @( 'appx.dsc.extension.json', 'appx-discover.ps1', 'bicep.dsc.extension.json', - 'convert-resource.ps1', - 'convertDscResource.psd1', - 'convertDscResource.psm1', 'dsc.exe', 'dsc_default.settings.json', 'dsc.settings.json', @@ -63,7 +60,6 @@ $filesForWindowsPackage = @( 'osinfo.exe', 'osinfo.dsc.resource.json', 'powershell.dsc.resource.json', - 'powershell.dsc.extension.json', 'psDscAdapter/', 'psscript.ps1', 'psscript.dsc.resource.json', @@ -89,9 +85,6 @@ $filesForWindowsPackage = @( $filesForLinuxPackage = @( 'bicep.dsc.extension.json', - 'convert-resource.ps1', - 'convertDscResource.psd1', - 'convertDscResource.psm1', 'dsc', 'dsc_default.settings.json', 'dsc.settings.json', @@ -106,7 +99,6 @@ $filesForLinuxPackage = @( 'osinfo', 'osinfo.dsc.resource.json', 'powershell.dsc.resource.json', - 'powershell.dsc.extension.json', 'psDscAdapter/', 'psscript.ps1', 'psscript.dsc.resource.json', @@ -118,9 +110,6 @@ $filesForLinuxPackage = @( $filesForMacPackage = @( 'bicep.dsc.extension.json', - 'convert-resource.ps1', - 'convertDscResource.psd1', - 'convertDscResource.psm1', 'dsc', 'dsc_default.settings.json', 'dsc.settings.json', @@ -135,7 +124,6 @@ $filesForMacPackage = @( 'osinfo', 'osinfo.dsc.resource.json', 'powershell.dsc.resource.json', - 'powershell.dsc.extension.json', 'psDscAdapter/', 'psscript.ps1', 'psscript.dsc.resource.json', diff --git a/extensions/powershell/convertDscResource.psm1 b/extensions/powershell/convertDscResource.psm1 index 236c63e69..ef82ba2d3 100644 --- a/extensions/powershell/convertDscResource.psm1 +++ b/extensions/powershell/convertDscResource.psm1 @@ -6,7 +6,6 @@ if ($Script:IsPowerShellCore) { } Import-Module -Name 'PSDesiredStateConfiguration' -MinimumVersion 2.0.7 -Prefix 'Pwsh' -WarningAction SilentlyContinue -ErrorAction Stop } else { - "Loaded module paths: $env:PSModulePath" | Write-DscTrace -Operation Trace Import-Module -Name 'PSDesiredStateConfiguration' -RequiredVersion 1.1 -WarningAction SilentlyContinue -ErrorAction Stop } diff --git a/extensions/powershell/copy_files.txt b/extensions/powershell/copy_files.txt index ae0ed7751..9ab85b46b 100644 --- a/extensions/powershell/copy_files.txt +++ b/extensions/powershell/copy_files.txt @@ -1,4 +1,3 @@ -powershell.dsc.extension.json windowspowershell.dsc.extension.json convert-resource.ps1 convertDscResource.psd1 diff --git a/extensions/powershell/powershell.dsc.extension.json b/extensions/powershell/powershell.dsc.extension.json deleted file mode 100644 index b1f10900f..000000000 --- a/extensions/powershell/powershell.dsc.extension.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "$schema": "https://aka.ms/dsc/schemas/v3/bundled/resource/manifest.json", - "type": "Microsoft.DSC.Extension/PowerShell", - "version": "0.1.0", - "description": "Enable passing PowerShell v2 configuration document file directly to DSC. Requires PSDesiredStateConfiguration v2.0.7.", - "import": { - "fileExtensions": ["ps1"], - "executable": "pwsh", - "args": [ - "-NoLogo", - "-NonInteractive", - "-NoProfile", - "-ExecutionPolicy", - "Bypass", - "-Command", - "Get-Content", - { - "fileArg": "" - }, - "| ./convert-resource.ps1" - ] - } -} diff --git a/extensions/powershell/powershell.tests.ps1 b/extensions/powershell/powershell.tests.ps1 deleted file mode 100644 index 5b7514cf7..000000000 --- a/extensions/powershell/powershell.tests.ps1 +++ /dev/null @@ -1,56 +0,0 @@ -# Copyright (c) Microsoft Corporation. -# Licensed under the MIT License. - -BeforeDiscovery { - if ($IsWindows) { - $identity = [System.Security.Principal.WindowsIdentity]::GetCurrent() - $principal = [System.Security.Principal.WindowsPrincipal]::new($identity) - $isElevated = $principal.IsInRole([System.Security.Principal.WindowsBuiltInRole]::Administrator) - } -} - -Describe 'PowerShell extension tests' { - It 'Example PowerShell file should work' -Skip:(!$isElevated) { - $powerShellConfiguration = @" -configuration TestClassConfiguration { - Import-DscResource -ModuleName TestClassResource - Node localhost - { - TestClassResource TestClass { - Name = 'Test' - } - } -} -"@ - $config_path = "$TestDrive/testclass.ps1" - $powerShellConfiguration | Set-Content -Path $config_path - $out = dsc -l trace config get -f $config_path 2>$TestDrive/error.log | ConvertFrom-Json - $LASTEXITCODE | Should -Be 0 -Because (Get-Content -Path $TestDrive/error.log -Raw | Out-String) - $out.results[0].result.actualState.Ensure | Should -Be 'Present' - $out.results[0].result.actualState.Name | Should -Be 'Test' - $config_path = $config_path.ToString().Replace('\', '\\') - (Get-Content -Path $TestDrive/error.log -Raw) | Should -Match "Importing file '$config_path' with extension 'Microsoft.DSC.Extension/PowerShell'" - } - - It 'Invalid PowerShell configuration document file returns error' { - $psFile = "$TestDrive/invalid.ps1" - Set-Content -Path $psFile -Value @" -configuration InvalidConfiguration { - Import-DscResource -ModuleName InvalidModule - Node localhost - { - Test Invalid { - Name = 'InvalidTest' - Ensure = 'Present' - } - } -} -"@ - dsc -l trace config get -f $psFile 2>$TestDrive/error.log - $LASTEXITCODE | Should -Be 2 -Because (Get-Content -Path $TestDrive/error.log -Raw | Out-String) - $content = (Get-Content -Path $TestDrive/error.log -Raw) - $psFile = $psFile.ToString().Replace('\', '\\') - $content | Should -Match "Importing file '$psFile' with extension 'Microsoft.DSC.Extension/PowerShell'" - $content | Should -Match "No DSC resources found in the imported modules." - } -} From d353b7380bca34a1e8d60cda65f73c1c62030d52 Mon Sep 17 00:00:00 2001 From: "G.Reijn" Date: Mon, 11 Aug 2025 07:42:41 +0200 Subject: [PATCH 41/41] Revert file --- powershell-adapter/Tests/win_powershell_cache.tests.ps1 | 1 + 1 file changed, 1 insertion(+) diff --git a/powershell-adapter/Tests/win_powershell_cache.tests.ps1 b/powershell-adapter/Tests/win_powershell_cache.tests.ps1 index 94e8d65d9..6ed7bec1c 100644 --- a/powershell-adapter/Tests/win_powershell_cache.tests.ps1 +++ b/powershell-adapter/Tests/win_powershell_cache.tests.ps1 @@ -48,6 +48,7 @@ Describe 'WindowsPowerShell adapter resource tests - requires elevated permissio } It 'Get works on Binary "File" resource' { + $testFile = "$testdrive\test.txt" 'test' | Set-Content -Path $testFile -Force $r = '{"DestinationPath":"' + $testFile.replace('\', '\\') + '"}' | dsc resource get -r 'PSDesiredStateConfiguration/File' -f -