From 224bbf2cd8ea5b5a4cc7392894d06655cf98695a Mon Sep 17 00:00:00 2001 From: Andrew Date: Thu, 23 May 2024 22:51:55 -0700 Subject: [PATCH 1/5] Stage 1 --- .../psDscAdapter/powershell.resource.ps1 | 11 +- .../psDscAdapter/psDscAdapter.psm1 | 114 +++++++++++++++++- 2 files changed, 120 insertions(+), 5 deletions(-) diff --git a/powershell-adapter/psDscAdapter/powershell.resource.ps1 b/powershell-adapter/psDscAdapter/powershell.resource.ps1 index 2cb8d99c..7d2a6a38 100644 --- a/powershell-adapter/psDscAdapter/powershell.resource.ps1 +++ b/powershell-adapter/psDscAdapter/powershell.resource.ps1 @@ -28,7 +28,9 @@ if ('Validate' -ne $Operation) { } if ($jsonInput) { - $inputobj_pscustomobj = $jsonInput | ConvertFrom-Json + if ($jsonInput -ne '@{}') { + $inputobj_pscustomobj = $jsonInput | ConvertFrom-Json + } $new_psmodulepath = $inputobj_pscustomobj.psmodulepath if ($new_psmodulepath) { @@ -39,10 +41,11 @@ if ($jsonInput) { # process the operation requested to the script switch ($Operation) { 'List' { - $dscResourceCache = Invoke-DscCacheRefresh + Invoke-DscCacheRefresh + #$dscResourceCache = Invoke-DscCacheRefresh # cache was refreshed on script load - foreach ($dscResource in $dscResourceCache) { + <#foreach ($dscResource in $dscResourceCache) { # https://learn.microsoft.com/dotnet/api/system.management.automation.dscresourceinfo $DscResourceInfo = $dscResource.DscResourceInfo @@ -92,7 +95,7 @@ switch ($Operation) { requireAdapter = $requireAdapter description = $description } | ConvertTo-Json -Compress - } + }#> } { @('Get','Set','Test','Export') -contains $_ } { $desiredState = $psDscAdapter.invoke( { param($jsonInput) Get-DscResourceObject -jsonInput $jsonInput }, $jsonInput ) diff --git a/powershell-adapter/psDscAdapter/psDscAdapter.psm1 b/powershell-adapter/psDscAdapter/psDscAdapter.psm1 index 6bd234f2..9785d870 100644 --- a/powershell-adapter/psDscAdapter/psDscAdapter.psm1 +++ b/powershell-adapter/psDscAdapter/psDscAdapter.psm1 @@ -20,6 +20,118 @@ function Import-PSDSCModule { $PSDesiredStateConfiguration = Import-Module $m -Force -PassThru } +function Get-DSCResourceModules +{ + $listPSModuleFolders = $env:PSModulePath.Split([IO.Path]::PathSeparator) + $dscModulePsd1List = [System.Collections.Generic.HashSet[System.String]]::new() + foreach ($folder in $listPSModuleFolders) + { + if (!(Test-Path $folder)) + { + continue + } + + foreach($moduleFolder in Get-ChildItem $folder -Directory) + { + $addModule = $false + foreach($psd1 in Get-ChildItem -Recurse -Filter "$($moduleFolder.Name).psd1" -Path $moduleFolder.fullname -Depth 2) + { + $containsDSCResource = select-string -LiteralPath $psd1 -pattern '^[^#]*\bDscResourcesToExport\b.*' + if($null -ne $containsDSCResource) + { + $dscModulePsd1List.Add($psd1) | Out-Null + break + } + } + } + } + + return $dscModulePsd1List +} + +function FindAndParseResourceDefinitions +{ + [CmdletBinding(HelpUri = '')] + param( + [Parameter(Mandatory = $true)] + [string]$filePath + ) + + if (-not (Test-Path $filePath)) + { + return + } + + if (([System.IO.Path]::GetExtension($filePath) -ne ".psm1") -and ([System.IO.Path]::GetExtension($filePath) -ne ".ps1")) + { + return + } + + $filePath + + [System.Management.Automation.Language.Token[]] $tokens = $null + [System.Management.Automation.Language.ParseError[]] $errors = $null + $ast = [System.Management.Automation.Language.Parser]::ParseFile($filePath, [ref]$tokens, [ref]$errors) + $resourceDefinitions = $ast.FindAll( + { + $typeAst = $args[0] -as [System.Management.Automation.Language.TypeDefinitionAst] + if ($typeAst) + { + foreach($a in $typeAst.Attributes) + { + if ($a.TypeName.Name -eq 'DscResource') + { + return $true; + } + } + } + + return $false; + }, + $false); + + $resourceDefinitions.Name +} + +function LoadPowerShellClassResourcesFromModule +{ + [CmdletBinding(HelpUri = '')] + param( + [Parameter(Mandatory = $true)] + [PSModuleInfo]$moduleInfo + ) + + if ($moduleInfo.RootModule) + { + $scriptPath = Join-Path $moduleInfo.ModuleBase $moduleInfo.RootModule + } + else + { + $scriptPath = $moduleInfo.Path; + } + + FindAndParseResourceDefinitions $scriptPath + + if ($moduleInfo.NestedModules) + { + foreach ($nestedModule in $moduleInfo.NestedModules) + { + LoadPowerShellClassResourcesFromModule $nestedModule + } + } +} + +function Invoke-DscCacheRefresh { + + $dscResourceModulePsd1s = Get-DSCResourceModules + if($null -ne $dscResourceModulePsd1s) { + $modules = Get-Module -ListAvailable -Name ($dscResourceModulePsd1s) + foreach ($mod in $modules) + { + LoadPowerShellClassResourcesFromModule -moduleInfo $mod + } + } +} <# public function Invoke-DscCacheRefresh .SYNOPSIS This function caches the results of the Get-DscResource call to optimize performance. @@ -32,7 +144,7 @@ function Import-PSDSCModule { .EXAMPLE Invoke-DscCacheRefresh -Module "PSDesiredStateConfiguration" #> -function Invoke-DscCacheRefresh { +function Invoke-DscCacheRefresh_ORIGINAL { [CmdletBinding(HelpUri = '')] param( [Parameter(ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)] From d557c6d1b75431cf2e87b4d5a40f0c9f43ee3af1 Mon Sep 17 00:00:00 2001 From: Andrew Date: Fri, 24 May 2024 12:03:26 -0700 Subject: [PATCH 2/5] Stage 2 --- .../Tests/powershellgroup.resource.tests.ps1 | 3 +- .../psDscAdapter/powershell.resource.ps1 | 9 +- .../psDscAdapter/psDscAdapter.psm1 | 138 +++++++++++------- 3 files changed, 92 insertions(+), 58 deletions(-) diff --git a/powershell-adapter/Tests/powershellgroup.resource.tests.ps1 b/powershell-adapter/Tests/powershellgroup.resource.tests.ps1 index 5dd3dcb1..ba791e2f 100644 --- a/powershell-adapter/Tests/powershellgroup.resource.tests.ps1 +++ b/powershell-adapter/Tests/powershellgroup.resource.tests.ps1 @@ -23,13 +23,12 @@ Describe 'PowerShell adapter resource tests' { Remove-Item -Force -ea SilentlyContinue -Path $cacheFilePath } - It 'Discovery includes class-based and script-based resources ' -Skip:(!$IsWindows){ + It 'Discovery includes class-based resources' -Skip:(!$IsWindows){ $r = dsc resource list * -a Microsoft.DSC/PowerShell $LASTEXITCODE | Should -Be 0 $resources = $r | ConvertFrom-Json ($resources | ? {$_.Type -eq 'TestClassResource/TestClassResource'}).Count | Should -Be 1 - ($resources | ? {$_.Type -eq 'PSTestModule/TestPSRepository'}).Count | Should -Be 1 } It 'Get works on class-based resource' -Skip:(!$IsWindows){ diff --git a/powershell-adapter/psDscAdapter/powershell.resource.ps1 b/powershell-adapter/psDscAdapter/powershell.resource.ps1 index 7d2a6a38..53b3d327 100644 --- a/powershell-adapter/psDscAdapter/powershell.resource.ps1 +++ b/powershell-adapter/psDscAdapter/powershell.resource.ps1 @@ -41,11 +41,10 @@ if ($jsonInput) { # process the operation requested to the script switch ($Operation) { 'List' { - Invoke-DscCacheRefresh - #$dscResourceCache = Invoke-DscCacheRefresh + $dscResourceCache = Invoke-DscCacheRefresh # cache was refreshed on script load - <#foreach ($dscResource in $dscResourceCache) { + foreach ($dscResource in $dscResourceCache) { # https://learn.microsoft.com/dotnet/api/system.management.automation.dscresourceinfo $DscResourceInfo = $dscResource.DscResourceInfo @@ -85,7 +84,7 @@ switch ($Operation) { [resourceOutput]@{ type = $dscResource.Type kind = 'Resource' - version = $DscResourceInfo.version.ToString() + version = [string]$DscResourceInfo.version capabilities = $capabilities path = $DscResourceInfo.Path directory = $DscResourceInfo.ParentPath @@ -95,7 +94,7 @@ switch ($Operation) { requireAdapter = $requireAdapter description = $description } | ConvertTo-Json -Compress - }#> + } } { @('Get','Set','Test','Export') -contains $_ } { $desiredState = $psDscAdapter.invoke( { param($jsonInput) Get-DscResourceObject -jsonInput $jsonInput }, $jsonInput ) diff --git a/powershell-adapter/psDscAdapter/psDscAdapter.psm1 b/powershell-adapter/psDscAdapter/psDscAdapter.psm1 index 9785d870..6c64090a 100644 --- a/powershell-adapter/psDscAdapter/psDscAdapter.psm1 +++ b/powershell-adapter/psDscAdapter/psDscAdapter.psm1 @@ -66,9 +66,10 @@ function FindAndParseResourceDefinitions { return } - - $filePath - + + "Loading resources from '$filePath'" | Write-DscTrace -Operation Trace + #TODO: Handle class inheritance + #TODO: Ensure embedded instances in properties are working correctly [System.Management.Automation.Language.Token[]] $tokens = $null [System.Management.Automation.Language.ParseError[]] $errors = $null $ast = [System.Management.Automation.Language.Parser]::ParseFile($filePath, [ref]$tokens, [ref]$errors) @@ -90,7 +91,61 @@ function FindAndParseResourceDefinitions }, $false); - $resourceDefinitions.Name + $resourceList = [System.Collections.Generic.List[DscResourceInfo]]::new() + + foreach($typeDefinitionAst in $resourceDefinitions) + { + $DscResourceInfo = [DscResourceInfo]::new() + $DscResourceInfo.Name = $typeDefinitionAst.Name + $DscResourceInfo.ResourceType = $typeDefinitionAst.Name + $DscResourceInfo.FriendlyName = $typeDefinitionAst.Name + $DscResourceInfo.ImplementationDetail = 'ClassBased' + $DscResourceInfo.Module = $filePath + $DscResourceInfo.Path = $filePath + #TODO: ModuleName, Version and ParentPath should be taken from psd1 contents + $DscResourceInfo.ModuleName = [System.IO.Path]::GetFileNameWithoutExtension($filePath) + $DscResourceInfo.ParentPath = [System.IO.Path]::GetDirectoryName($filePath) + + $DscResourceInfo.Properties = [System.Collections.Generic.List[DscResourcePropertyInfo]]::new() + foreach ($member in $typeDefinitionAst.Members) + { + $property = $member -as [System.Management.Automation.Language.PropertyMemberAst] + if (($property -eq $null) -or ($property.IsStatic)) + { + continue; + } + $skipProperty = $true + $isKeyProperty = $false + foreach($attr in $property.Attributes) + { + if ($attr.TypeName.Name -eq 'DscProperty') + { + $skipProperty = $false + foreach($attrArg in $attr.NamedArguments) + { + if ($attrArg.ArgumentName -eq 'Key') + { + $isKeyProperty = $true + } + } + } + } + if ($skipProperty) + { + continue; + } + + [DscResourcePropertyInfo]$prop = [DscResourcePropertyInfo]::new() + $prop.Name = $property.Name + $prop.PropertyType = $property.PropertyType.TypeName.Name + $prop.IsMandatory = $isKeyProperty + $DscResourceInfo.Properties.Add($prop) + } + + $resourceList.Add($DscResourceInfo) + } + + return $resourceList } function LoadPowerShellClassResourcesFromModule @@ -110,28 +165,20 @@ function LoadPowerShellClassResourcesFromModule $scriptPath = $moduleInfo.Path; } - FindAndParseResourceDefinitions $scriptPath + $Resources = FindAndParseResourceDefinitions $scriptPath if ($moduleInfo.NestedModules) { foreach ($nestedModule in $moduleInfo.NestedModules) { - LoadPowerShellClassResourcesFromModule $nestedModule + $resourcesOfNestedModules = LoadPowerShellClassResourcesFromModule $nestedModule + $Resources.AddRange($resourcesOfNestedModules) } } -} -function Invoke-DscCacheRefresh { - - $dscResourceModulePsd1s = Get-DSCResourceModules - if($null -ne $dscResourceModulePsd1s) { - $modules = Get-Module -ListAvailable -Name ($dscResourceModulePsd1s) - foreach ($mod in $modules) - { - LoadPowerShellClassResourcesFromModule -moduleInfo $mod - } - } + return $Resources } + <# public function Invoke-DscCacheRefresh .SYNOPSIS This function caches the results of the Get-DscResource call to optimize performance. @@ -144,7 +191,7 @@ function Invoke-DscCacheRefresh { .EXAMPLE Invoke-DscCacheRefresh -Module "PSDesiredStateConfiguration" #> -function Invoke-DscCacheRefresh_ORIGINAL { +function Invoke-DscCacheRefresh { [CmdletBinding(HelpUri = '')] param( [Parameter(ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)] @@ -158,12 +205,8 @@ function Invoke-DscCacheRefresh_ORIGINAL { # PS 6+ on Windows Join-Path $env:LocalAppData "dsc\PSAdapterCache.json" } else { - # either WinPS or PS 6+ on Linux/Mac - if ($PSVersionTable.PSVersion.Major -le 5) { - Join-Path $env:LocalAppData "dsc\WindowsPSAdapterCache.json" - } else { - Join-Path $env:HOME ".dsc" "PSAdapterCache.json" - } + # PS 6+ on Linux/Mac + Join-Path $env:HOME ".dsc" "PSAdapterCache.json" } if (Test-Path $cacheFilePath) { @@ -225,33 +268,18 @@ function Invoke-DscCacheRefresh_ORIGINAL { # create a list object to store cache of Get-DscResource [dscResourceCacheEntry[]]$dscResourceCacheEntries = [System.Collections.Generic.List[Object]]::new() - Import-PSDSCModule - $DscResources = Get-DscResource - - foreach ($dscResource in $DscResources) { - # resources that shipped in Windows should only be used with Windows PowerShell - if ($dscResource.ParentPath -like "$env:windir\System32\*" -and $PSVersionTable.PSVersion.Major -gt 5) { - continue - } - - if ( $dscResource.ImplementationDetail ) { - # only support known dscResourceType - if ([dscResourceType].GetEnumNames() -notcontains $dscResource.ImplementationDetail) { - 'WARNING: implementation detail not found: ' + $dscResource.ImplementationDetail | Write-DscTrace - continue - } - } - - $DscResourceInfo = [DscResourceInfo]::new() - $dscResource.PSObject.Properties | ForEach-Object -Process { - if ($null -ne $_.Value) { - $DscResourceInfo.$($_.Name) = $_.Value - } - else { - $DscResourceInfo.$($_.Name) = '' - } + $DscResources = [System.Collections.Generic.List[DscResourceInfo]]::new() + $dscResourceModulePsd1s = Get-DSCResourceModules + if($null -ne $dscResourceModulePsd1s) { + $modules = Get-Module -ListAvailable -Name ($dscResourceModulePsd1s) + foreach ($mod in $modules) + { + [System.Collections.Generic.List[DscResourceInfo]]$r = LoadPowerShellClassResourcesFromModule -moduleInfo $mod + $DscResources.AddRange($r) } + } + foreach ($dscResource in $DscResources) { $moduleName = $dscResource.ModuleName # fill in resource files (and their last-write-times) that will be used for up-do-date checks @@ -262,7 +290,7 @@ function Invoke-DscCacheRefresh_ORIGINAL { $dscResourceCacheEntries += [dscResourceCacheEntry]@{ Type = "$moduleName/$($dscResource.Name)" - DscResourceInfo = $DscResourceInfo + DscResourceInfo = $dscResource LastWriteTimes = $lastWriteTimes } } @@ -454,6 +482,14 @@ enum dscResourceType { Composite } +class DscResourcePropertyInfo +{ + [string] $Name + [string] $PropertyType + [bool] $IsMandatory + [System.Collections.Generic.List[string]] $Values +} + # dsc resource type (settable clone) class DscResourceInfo { [dscResourceType] $ImplementationDetail @@ -467,5 +503,5 @@ class DscResourceInfo { [string] $ParentPath [string] $ImplementedAs [string] $CompanyName - [psobject[]] $Properties + [System.Collections.Generic.List[DscResourcePropertyInfo]] $Properties } From e07e5bdf9cdb0adbd86466fd1f1a0579d533b684 Mon Sep 17 00:00:00 2001 From: Andrew Date: Fri, 24 May 2024 12:47:26 -0700 Subject: [PATCH 3/5] Stage 3 --- .../Tests/powershellgroup.config.tests.ps1 | 14 ++++----- .../Tests/powershellgroup.resource.tests.ps1 | 14 ++++----- .../psDscAdapter/powershell.resource.ps1 | 31 ++++++++++++------- .../psDscAdapter/psDscAdapter.psm1 | 5 +++ 4 files changed, 38 insertions(+), 26 deletions(-) diff --git a/powershell-adapter/Tests/powershellgroup.config.tests.ps1 b/powershell-adapter/Tests/powershellgroup.config.tests.ps1 index 269b6d4e..2f4d5499 100644 --- a/powershell-adapter/Tests/powershellgroup.config.tests.ps1 +++ b/powershell-adapter/Tests/powershellgroup.config.tests.ps1 @@ -24,7 +24,7 @@ Describe 'PowerShell adapter resource tests' { Remove-Item -Force -ea SilentlyContinue -Path $cacheFilePath } - It 'Get works on config with class-based resources' -Skip:(!$IsWindows){ + It 'Get works on config with class-based resources' { $r = Get-Content -Raw $pwshConfigPath | dsc config get $LASTEXITCODE | Should -Be 0 @@ -33,7 +33,7 @@ Describe 'PowerShell adapter resource tests' { $res.results[0].result.actualState.result[0].properties.EnumProp | Should -BeExactly 'Expected' } - It 'Test works on config with class-based resources' -Skip:(!$IsWindows){ + It 'Test works on config with class-based resources' { $r = Get-Content -Raw $pwshConfigPath | dsc config test $LASTEXITCODE | Should -Be 0 @@ -41,7 +41,7 @@ Describe 'PowerShell adapter resource tests' { $res.results[0].result.actualState.result[0] | Should -Not -BeNull } - It 'Set works on config with class-based resources' -Skip:(!$IsWindows){ + It 'Set works on config with class-based resources' { $r = Get-Content -Raw $pwshConfigPath | dsc config set $LASTEXITCODE | Should -Be 0 @@ -49,7 +49,7 @@ Describe 'PowerShell adapter resource tests' { $res.results.result.afterState.result[0].type | Should -Be "TestClassResource/TestClassResource" } - It 'Export works on config with class-based resources' -Skip:(!$IsWindows){ + It 'Export works on config with class-based resources' { $yaml = @' $schema: https://raw.githubusercontent.com/PowerShell/DSC/main/schemas/2024/04/config/document.json @@ -71,7 +71,7 @@ Describe 'PowerShell adapter resource tests' { $res.resources[0].properties.result[0].Prop1 | Should -Be "Property of object1" } - It 'Custom psmodulepath in config works' -Skip:(!$IsWindows){ + It 'Custom psmodulepath in config works' { $OldPSModulePath = $env:PSModulePath Copy-Item -Recurse -Force -Path "$PSScriptRoot/TestClassResource" -Destination $TestDrive @@ -104,7 +104,7 @@ Describe 'PowerShell adapter resource tests' { } } - It 'DSCConfigRoot macro is working when config is from a file' -Skip:(!$IsWindows){ + It 'DSCConfigRoot macro is working when config is from a file' { $yaml = @" `$schema: https://raw.githubusercontent.com/PowerShell/DSC/main/schemas/2024/04/config/document.json @@ -129,7 +129,7 @@ Describe 'PowerShell adapter resource tests' { $res.results.result.actualState.result.properties.Prop1 | Should -Be $TestDrive } - It 'DSC_CONFIG_ROOT env var is cwd when config is piped from stdin' -Skip:(!$IsWindows){ + It 'DSC_CONFIG_ROOT env var is cwd when config is piped from stdin' { $yaml = @" `$schema: https://raw.githubusercontent.com/PowerShell/DSC/main/schemas/2024/04/config/document.json diff --git a/powershell-adapter/Tests/powershellgroup.resource.tests.ps1 b/powershell-adapter/Tests/powershellgroup.resource.tests.ps1 index ba791e2f..b189842e 100644 --- a/powershell-adapter/Tests/powershellgroup.resource.tests.ps1 +++ b/powershell-adapter/Tests/powershellgroup.resource.tests.ps1 @@ -23,7 +23,7 @@ Describe 'PowerShell adapter resource tests' { Remove-Item -Force -ea SilentlyContinue -Path $cacheFilePath } - It 'Discovery includes class-based resources' -Skip:(!$IsWindows){ + It 'Discovery includes class-based resources' { $r = dsc resource list * -a Microsoft.DSC/PowerShell $LASTEXITCODE | Should -Be 0 @@ -31,7 +31,7 @@ Describe 'PowerShell adapter resource tests' { ($resources | ? {$_.Type -eq 'TestClassResource/TestClassResource'}).Count | Should -Be 1 } - It 'Get works on class-based resource' -Skip:(!$IsWindows){ + It 'Get works on class-based resource' { $r = "{'Name':'TestClassResource1'}" | dsc resource get -r 'TestClassResource/TestClassResource' $LASTEXITCODE | Should -Be 0 @@ -39,7 +39,7 @@ Describe 'PowerShell adapter resource tests' { $res.actualState.result.properties.Prop1 | Should -BeExactly 'ValueForProp1' } - It 'Get uses enum names on class-based resource' -Skip:(!$IsWindows){ + It 'Get uses enum names on class-based resource' { $r = "{'Name':'TestClassResource1'}" | dsc resource get -r 'TestClassResource/TestClassResource' $LASTEXITCODE | Should -Be 0 @@ -47,7 +47,7 @@ Describe 'PowerShell adapter resource tests' { $res.actualState.result.properties.EnumProp | Should -BeExactly 'Expected' } - It 'Test works on class-based resource' -Skip:(!$IsWindows){ + It 'Test works on class-based resource' { $r = "{'Name':'TestClassResource1','Prop1':'ValueForProp1'}" | dsc resource test -r 'TestClassResource/TestClassResource' $LASTEXITCODE | Should -Be 0 @@ -55,7 +55,7 @@ Describe 'PowerShell adapter resource tests' { $res.actualState.result.properties.InDesiredState | Should -Be $True } - It 'Set works on class-based resource' -Skip:(!$IsWindows){ + It 'Set works on class-based resource' { $r = "{'Name':'TestClassResource1','Prop1':'ValueForProp1'}" | dsc resource set -r 'TestClassResource/TestClassResource' $LASTEXITCODE | Should -Be 0 @@ -63,7 +63,7 @@ Describe 'PowerShell adapter resource tests' { $res.afterState.result | Should -Not -BeNull } - It 'Export works on PS class-based resource' -Skip:(!$IsWindows){ + It 'Export works on PS class-based resource' { $r = dsc resource export -r TestClassResource/TestClassResource $LASTEXITCODE | Should -Be 0 @@ -73,7 +73,7 @@ Describe 'PowerShell adapter resource tests' { $res.resources[0].properties.result[0].Prop1 | Should -Be "Property of object1" } - It 'Get --all works on PS class-based resource' -Skip:(!$IsWindows){ + It 'Get --all works on PS class-based resource' { $r = dsc resource get --all -r TestClassResource/TestClassResource $LASTEXITCODE | Should -Be 0 diff --git a/powershell-adapter/psDscAdapter/powershell.resource.ps1 b/powershell-adapter/psDscAdapter/powershell.resource.ps1 index 53b3d327..6a9f6bce 100644 --- a/powershell-adapter/psDscAdapter/powershell.resource.ps1 +++ b/powershell-adapter/psDscAdapter/powershell.resource.ps1 @@ -9,6 +9,24 @@ param( [string]$jsonInput = '@{}' ) +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 = $Message } | ConvertTo-Json -Compress + $host.ui.WriteErrorLine($trace) +} + +# Adding some debug info to STDERR +'PSVersion=' + $PSVersionTable.PSVersion.ToString() | Write-DscTrace +'PSPath=' + $PSHome | Write-DscTrace +'PSModulePath=' + $env:PSModulePath | Write-DscTrace + if ('Validate' -ne $Operation) { # write $jsonInput to STDERR for debugging $trace = @{'Debug' = 'jsonInput=' + $jsonInput } | ConvertTo-Json -Compress @@ -21,7 +39,6 @@ if ('Validate' -ne $Operation) { else { $psDscAdapter = Import-Module "$PSScriptRoot/psDscAdapter.psd1" -Force -PassThru } - # initialize OUTPUT as array $result = [System.Collections.Generic.List[Object]]::new() @@ -50,6 +67,7 @@ switch ($Operation) { $DscResourceInfo = $dscResource.DscResourceInfo # Provide a way for existing resources to specify their capabilities, or default to Get, Set, Test + # TODO: for perf, it is better to take capabilities from psd1 in Invoke-DscCacheRefresh, not by extra call to Get-Module if ($DscResourceInfo.ModuleName) { $module = Get-Module -Name $DscResourceInfo.ModuleName -ListAvailable | Sort-Object -Property Version -Descending | Select-Object -First 1 if ($module.PrivateData.PSData.DscCapabilities) { @@ -161,14 +179,3 @@ class resourceOutput { [string] $requireAdapter [string] $description } - -# Adding some debug info to STDERR -$trace = @{'Debug' = 'PSVersion=' + $PSVersionTable.PSVersion.ToString() } | ConvertTo-Json -Compress -$host.ui.WriteErrorLine($trace) -$trace = @{'Debug' = 'PSPath=' + $PSHome } | ConvertTo-Json -Compress -$host.ui.WriteErrorLine($trace) -$m = Get-Command 'Get-DscResource' -$trace = @{'Debug' = 'Module=' + $m.Source.ToString() } | ConvertTo-Json -Compress -$host.ui.WriteErrorLine($trace) -$trace = @{'Debug' = 'PSModulePath=' + $env:PSModulePath } | ConvertTo-Json -Compress -$host.ui.WriteErrorLine($trace) \ No newline at end of file diff --git a/powershell-adapter/psDscAdapter/psDscAdapter.psm1 b/powershell-adapter/psDscAdapter/psDscAdapter.psm1 index 6c64090a..a4e5824e 100644 --- a/powershell-adapter/psDscAdapter/psDscAdapter.psm1 +++ b/powershell-adapter/psDscAdapter/psDscAdapter.psm1 @@ -73,6 +73,11 @@ function FindAndParseResourceDefinitions [System.Management.Automation.Language.Token[]] $tokens = $null [System.Management.Automation.Language.ParseError[]] $errors = $null $ast = [System.Management.Automation.Language.Parser]::ParseFile($filePath, [ref]$tokens, [ref]$errors) + foreach($e in $errors) + { + $e | Out-String | Write-DscTrace -Operation Error + } + $resourceDefinitions = $ast.FindAll( { $typeAst = $args[0] -as [System.Management.Automation.Language.TypeDefinitionAst] From 50d3e86fe301c84a70ed100254de7405016f770f Mon Sep 17 00:00:00 2001 From: Andrew Date: Fri, 24 May 2024 13:41:44 -0700 Subject: [PATCH 4/5] Stage 4 --- build.ps1 | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/build.ps1 b/build.ps1 index 4e5ba9c5..a69cb635 100644 --- a/build.ps1 +++ b/build.ps1 @@ -324,10 +324,13 @@ if (!$Clippy -and !$SkipBuild) { if ($Test) { $failed = $false - $FullyQualifiedName = @{ModuleName="PSDesiredStateConfiguration";ModuleVersion="2.0.7"} - if (-not(Get-Module -ListAvailable -FullyQualifiedName $FullyQualifiedName)) - { "Installing module PSDesiredStateConfiguration 2.0.7" - Install-PSResource -Name PSDesiredStateConfiguration -Version 2.0.7 -Repository PSGallery -TrustRepository + if ($IsWindows) { + # PSDesiredStateConfiguration module is needed for Microsoft.Windows/WindowsPowerShell adapter + $FullyQualifiedName = @{ModuleName="PSDesiredStateConfiguration";ModuleVersion="2.0.7"} + if (-not(Get-Module -ListAvailable -FullyQualifiedName $FullyQualifiedName)) + { "Installing module PSDesiredStateConfiguration 2.0.7" + Install-PSResource -Name PSDesiredStateConfiguration -Version 2.0.7 -Repository PSGallery -TrustRepository + } } if (-not(Get-Module -ListAvailable -Name Pester)) From cb17f138e65cb85d35caca3c9c573c6faebec466 Mon Sep 17 00:00:00 2001 From: Andrew Date: Fri, 24 May 2024 14:09:37 -0700 Subject: [PATCH 5/5] Stage 5 --- powershell-adapter/Tests/powershellgroup.config.tests.ps1 | 3 ++- powershell-adapter/Tests/powershellgroup.resource.tests.ps1 | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/powershell-adapter/Tests/powershellgroup.config.tests.ps1 b/powershell-adapter/Tests/powershellgroup.config.tests.ps1 index 2f4d5499..cd152d69 100644 --- a/powershell-adapter/Tests/powershellgroup.config.tests.ps1 +++ b/powershell-adapter/Tests/powershellgroup.config.tests.ps1 @@ -78,13 +78,14 @@ Describe 'PowerShell adapter resource tests' { Rename-Item -Path "$PSScriptRoot/TestClassResource" -NewName "_TestClassResource" try { + $psmp = "`$env:PSModulePath"+[System.IO.Path]::PathSeparator+$TestDrive $yaml = @" `$schema: https://raw.githubusercontent.com/PowerShell/DSC/main/schemas/2024/04/config/document.json resources: - name: Working with class-based resources type: Microsoft.DSC/PowerShell properties: - psmodulepath: `$env:PSModulePath;$TestDrive + psmodulepath: $psmp resources: - name: Class-resource Info type: TestClassResource/TestClassResource diff --git a/powershell-adapter/Tests/powershellgroup.resource.tests.ps1 b/powershell-adapter/Tests/powershellgroup.resource.tests.ps1 index b189842e..edf83910 100644 --- a/powershell-adapter/Tests/powershellgroup.resource.tests.ps1 +++ b/powershell-adapter/Tests/powershellgroup.resource.tests.ps1 @@ -25,7 +25,7 @@ Describe 'PowerShell adapter resource tests' { It 'Discovery includes class-based resources' { - $r = dsc resource list * -a Microsoft.DSC/PowerShell + $r = dsc resource list '*' -a Microsoft.DSC/PowerShell $LASTEXITCODE | Should -Be 0 $resources = $r | ConvertFrom-Json ($resources | ? {$_.Type -eq 'TestClassResource/TestClassResource'}).Count | Should -Be 1