From 2ed8bdbcd085cfd815a0c1b07a8a16534eefda69 Mon Sep 17 00:00:00 2001 From: "G.Reijn" Date: Tue, 10 Jun 2025 05:22:40 +0200 Subject: [PATCH 1/6] Fix discovery of capabilities PSAdapter --- .../Tests/powershellgroup.resource.tests.ps1 | 1 + .../psDscAdapter/powershell.resource.ps1 | 3 ++ .../psDscAdapter/psDscAdapter.psm1 | 28 +++++++++++++++++-- 3 files changed, 30 insertions(+), 2 deletions(-) diff --git a/powershell-adapter/Tests/powershellgroup.resource.tests.ps1 b/powershell-adapter/Tests/powershellgroup.resource.tests.ps1 index c8be165e3..872262770 100644 --- a/powershell-adapter/Tests/powershellgroup.resource.tests.ps1 +++ b/powershell-adapter/Tests/powershellgroup.resource.tests.ps1 @@ -28,6 +28,7 @@ Describe 'PowerShell adapter resource tests' { $LASTEXITCODE | Should -Be 0 $resources = $r | ConvertFrom-Json ($resources | ? { $_.Type -eq 'TestClassResource/TestClassResource' }).Count | Should -Be 1 + ($resources | Where-Object -Property type -EQ 'PSClassResource/PSClassResource').capabilities | Should -BeIn @('get', 'set', 'test', 'export') } It 'Get works on class-based resource' { diff --git a/powershell-adapter/psDscAdapter/powershell.resource.ps1 b/powershell-adapter/psDscAdapter/powershell.resource.ps1 index d5417775e..32ec539d5 100644 --- a/powershell-adapter/psDscAdapter/powershell.resource.ps1 +++ b/powershell-adapter/psDscAdapter/powershell.resource.ps1 @@ -94,6 +94,9 @@ switch ($Operation) { if ($module.PrivateData.PSData.DscCapabilities) { $capabilities = $module.PrivateData.PSData.DscCapabilities } + elseif ($DscResourceInfo.Methods) { + $capabilities = $DscResourceInfo.Methods + } else { $capabilities = @('get', 'set', 'test') } diff --git a/powershell-adapter/psDscAdapter/psDscAdapter.psm1 b/powershell-adapter/psDscAdapter/psDscAdapter.psm1 index ffee99d94..65e9193a2 100644 --- a/powershell-adapter/psDscAdapter/psDscAdapter.psm1 +++ b/powershell-adapter/psDscAdapter/psDscAdapter.psm1 @@ -1,7 +1,7 @@ # Copyright (c) Microsoft Corporation. # Licensed under the MIT License. -$script:CurrentCacheSchemaVersion = 2 +$script:CurrentCacheSchemaVersion = 3 function Write-DscTrace { param( @@ -117,7 +117,7 @@ function FindAndParseResourceDefinitions { $typeDefinitions = $ast.FindAll( { $typeAst = $args[0] -as [System.Management.Automation.Language.TypeDefinitionAst] - return $typeAst -ne $null; + return $null -ne $typeAst; }, $false); @@ -139,6 +139,7 @@ function FindAndParseResourceDefinitions { $DscResourceInfo.Version = $moduleVersion $DscResourceInfo.Properties = [System.Collections.Generic.List[DscResourcePropertyInfo]]::new() + $DscResourceInfo.Methods = GetClassBasedCapabilities $typeDefinitionAst.Members Add-AstMembers $typeDefinitions $typeDefinitionAst $DscResourceInfo.Properties $resourceList.Add($DscResourceInfo) @@ -529,6 +530,28 @@ function GetTypeInstanceFromModule { return $instance } +function GetClassBasedCapabilities ($functionMemberAst) { + $capabilities = @() + # These are the methods that we can potentially expect in a class-based DSC resource. + $availableMethods = @('get', 'set', 'setHandlesExist', 'whatIf', 'test', 'delete', 'export') + $methods = $functionMemberAst | Where-Object { $_ -is [System.Management.Automation.Language.FunctionMemberAst] -and $_.Name -in $availableMethods } + + foreach ($method in $methods.Name) { + # We go through each method to properly case handle the method names. + switch ($method) { + 'Get' { $capabilities += 'get' } + 'Set' { $capabilities += 'set' } + 'Test' { $capabilities += 'test' } + 'WhatIf' { $capabilities += 'whatIf' } + 'SetHandlesExist' { $capabilities += 'setHandlesExist' } + 'Delete' { $capabilities += 'delete' } + 'Export' { $capabilities += 'export' } + } + } + + return ($capabilities | Select-Object -Unique) +} + # cached resource class dscResourceCacheEntry { [string] $Type @@ -578,4 +601,5 @@ class DscResourceInfo { [string] $ImplementedAs [string] $CompanyName [System.Collections.Generic.List[DscResourcePropertyInfo]] $Properties + [string[]] $Methods } From fafeb306dd09fbd02da6fdf7e1b25e895df3c321 Mon Sep 17 00:00:00 2001 From: "G.Reijn" Date: Tue, 10 Jun 2025 05:59:24 +0200 Subject: [PATCH 2/6] Fix test --- powershell-adapter/Tests/powershellgroup.resource.tests.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/powershell-adapter/Tests/powershellgroup.resource.tests.ps1 b/powershell-adapter/Tests/powershellgroup.resource.tests.ps1 index 872262770..296297425 100644 --- a/powershell-adapter/Tests/powershellgroup.resource.tests.ps1 +++ b/powershell-adapter/Tests/powershellgroup.resource.tests.ps1 @@ -28,7 +28,7 @@ Describe 'PowerShell adapter resource tests' { $LASTEXITCODE | Should -Be 0 $resources = $r | ConvertFrom-Json ($resources | ? { $_.Type -eq 'TestClassResource/TestClassResource' }).Count | Should -Be 1 - ($resources | Where-Object -Property type -EQ 'PSClassResource/PSClassResource').capabilities | Should -BeIn @('get', 'set', 'test', 'export') + ($resources | Where-Object -Property type -EQ 'TestClassResource/TestClassResource').capabilities | Should -BeIn @('get', 'set', 'test', 'export') } It 'Get works on class-based resource' { From 34197474918415b4e10200f5a8017dd02495ef92 Mon Sep 17 00:00:00 2001 From: "G.Reijn" Date: Tue, 10 Jun 2025 05:22:40 +0200 Subject: [PATCH 3/6] Fix discovery of capabilities PSAdapter --- .../Tests/powershellgroup.resource.tests.ps1 | 1 + .../psDscAdapter/powershell.resource.ps1 | 3 ++ .../psDscAdapter/psDscAdapter.psm1 | 28 +++++++++++++++++-- 3 files changed, 30 insertions(+), 2 deletions(-) diff --git a/powershell-adapter/Tests/powershellgroup.resource.tests.ps1 b/powershell-adapter/Tests/powershellgroup.resource.tests.ps1 index dbd205cec..c37a6ef45 100644 --- a/powershell-adapter/Tests/powershellgroup.resource.tests.ps1 +++ b/powershell-adapter/Tests/powershellgroup.resource.tests.ps1 @@ -28,6 +28,7 @@ Describe 'PowerShell adapter resource tests' { $LASTEXITCODE | Should -Be 0 $resources = $r | ConvertFrom-Json ($resources | ? { $_.Type -eq 'TestClassResource/TestClassResource' }).Count | Should -Be 1 + ($resources | Where-Object -Property type -EQ 'PSClassResource/PSClassResource').capabilities | Should -BeIn @('get', 'set', 'test', 'export') } It 'Get works on class-based resource' { diff --git a/powershell-adapter/psDscAdapter/powershell.resource.ps1 b/powershell-adapter/psDscAdapter/powershell.resource.ps1 index d5417775e..32ec539d5 100644 --- a/powershell-adapter/psDscAdapter/powershell.resource.ps1 +++ b/powershell-adapter/psDscAdapter/powershell.resource.ps1 @@ -94,6 +94,9 @@ switch ($Operation) { if ($module.PrivateData.PSData.DscCapabilities) { $capabilities = $module.PrivateData.PSData.DscCapabilities } + elseif ($DscResourceInfo.Methods) { + $capabilities = $DscResourceInfo.Methods + } else { $capabilities = @('get', 'set', 'test') } diff --git a/powershell-adapter/psDscAdapter/psDscAdapter.psm1 b/powershell-adapter/psDscAdapter/psDscAdapter.psm1 index ffee99d94..65e9193a2 100644 --- a/powershell-adapter/psDscAdapter/psDscAdapter.psm1 +++ b/powershell-adapter/psDscAdapter/psDscAdapter.psm1 @@ -1,7 +1,7 @@ # Copyright (c) Microsoft Corporation. # Licensed under the MIT License. -$script:CurrentCacheSchemaVersion = 2 +$script:CurrentCacheSchemaVersion = 3 function Write-DscTrace { param( @@ -117,7 +117,7 @@ function FindAndParseResourceDefinitions { $typeDefinitions = $ast.FindAll( { $typeAst = $args[0] -as [System.Management.Automation.Language.TypeDefinitionAst] - return $typeAst -ne $null; + return $null -ne $typeAst; }, $false); @@ -139,6 +139,7 @@ function FindAndParseResourceDefinitions { $DscResourceInfo.Version = $moduleVersion $DscResourceInfo.Properties = [System.Collections.Generic.List[DscResourcePropertyInfo]]::new() + $DscResourceInfo.Methods = GetClassBasedCapabilities $typeDefinitionAst.Members Add-AstMembers $typeDefinitions $typeDefinitionAst $DscResourceInfo.Properties $resourceList.Add($DscResourceInfo) @@ -529,6 +530,28 @@ function GetTypeInstanceFromModule { return $instance } +function GetClassBasedCapabilities ($functionMemberAst) { + $capabilities = @() + # These are the methods that we can potentially expect in a class-based DSC resource. + $availableMethods = @('get', 'set', 'setHandlesExist', 'whatIf', 'test', 'delete', 'export') + $methods = $functionMemberAst | Where-Object { $_ -is [System.Management.Automation.Language.FunctionMemberAst] -and $_.Name -in $availableMethods } + + foreach ($method in $methods.Name) { + # We go through each method to properly case handle the method names. + switch ($method) { + 'Get' { $capabilities += 'get' } + 'Set' { $capabilities += 'set' } + 'Test' { $capabilities += 'test' } + 'WhatIf' { $capabilities += 'whatIf' } + 'SetHandlesExist' { $capabilities += 'setHandlesExist' } + 'Delete' { $capabilities += 'delete' } + 'Export' { $capabilities += 'export' } + } + } + + return ($capabilities | Select-Object -Unique) +} + # cached resource class dscResourceCacheEntry { [string] $Type @@ -578,4 +601,5 @@ class DscResourceInfo { [string] $ImplementedAs [string] $CompanyName [System.Collections.Generic.List[DscResourcePropertyInfo]] $Properties + [string[]] $Methods } From 7047065879f9613eab9190befcfaaca1e7b3cbc4 Mon Sep 17 00:00:00 2001 From: "G.Reijn" Date: Tue, 10 Jun 2025 05:59:24 +0200 Subject: [PATCH 4/6] Fix test --- powershell-adapter/Tests/powershellgroup.resource.tests.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/powershell-adapter/Tests/powershellgroup.resource.tests.ps1 b/powershell-adapter/Tests/powershellgroup.resource.tests.ps1 index c37a6ef45..f6d2ec784 100644 --- a/powershell-adapter/Tests/powershellgroup.resource.tests.ps1 +++ b/powershell-adapter/Tests/powershellgroup.resource.tests.ps1 @@ -28,7 +28,7 @@ Describe 'PowerShell adapter resource tests' { $LASTEXITCODE | Should -Be 0 $resources = $r | ConvertFrom-Json ($resources | ? { $_.Type -eq 'TestClassResource/TestClassResource' }).Count | Should -Be 1 - ($resources | Where-Object -Property type -EQ 'PSClassResource/PSClassResource').capabilities | Should -BeIn @('get', 'set', 'test', 'export') + ($resources | Where-Object -Property type -EQ 'TestClassResource/TestClassResource').capabilities | Should -BeIn @('get', 'set', 'test', 'export') } It 'Get works on class-based resource' { From a3184dbeb1b1466d83e8a5c04e21204b8fb99927 Mon Sep 17 00:00:00 2001 From: "G.Reijn" Date: Sat, 19 Jul 2025 07:11:27 +0200 Subject: [PATCH 5/6] Resolve feedback --- .../Tests/powershellgroup.resource.tests.ps1 | 3 ++- .../psDscAdapter/powershell.resource.ps1 | 11 +++++----- .../psDscAdapter/psDscAdapter.psm1 | 22 +++++++++---------- 3 files changed, 18 insertions(+), 18 deletions(-) diff --git a/powershell-adapter/Tests/powershellgroup.resource.tests.ps1 b/powershell-adapter/Tests/powershellgroup.resource.tests.ps1 index f6d2ec784..ffe97c7e3 100644 --- a/powershell-adapter/Tests/powershellgroup.resource.tests.ps1 +++ b/powershell-adapter/Tests/powershellgroup.resource.tests.ps1 @@ -27,8 +27,9 @@ Describe 'PowerShell adapter resource tests' { $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 | Where-Object { $_.Type -eq 'TestClassResource/TestClassResource' }).Count | Should -Be 1 ($resources | Where-Object -Property type -EQ 'TestClassResource/TestClassResource').capabilities | Should -BeIn @('get', 'set', 'test', 'export') + ($resources | Where-Object -Property type -EQ 'TestClassResource/NoExport').capabilities | Should -BeIn @('get', 'set', 'test') } It 'Get works on class-based resource' { diff --git a/powershell-adapter/psDscAdapter/powershell.resource.ps1 b/powershell-adapter/psDscAdapter/powershell.resource.ps1 index 32ec539d5..42a8a7f35 100644 --- a/powershell-adapter/psDscAdapter/powershell.resource.ps1 +++ b/powershell-adapter/psDscAdapter/powershell.resource.ps1 @@ -91,13 +91,12 @@ switch ($Operation) { # 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) { + if ($DscResourceInfo.Capabilities) { + $capabilities = $DscResourceInfo.Capabilities + } elseif ($module.PrivateData.PSData.DscCapabilities) { + $capabilities = $module.PrivateData.PSData.DscCapabilities - } - elseif ($DscResourceInfo.Methods) { - $capabilities = $DscResourceInfo.Methods - } - else { + } else { $capabilities = @('get', 'set', 'test') } } diff --git a/powershell-adapter/psDscAdapter/psDscAdapter.psm1 b/powershell-adapter/psDscAdapter/psDscAdapter.psm1 index 65e9193a2..5180ed48a 100644 --- a/powershell-adapter/psDscAdapter/psDscAdapter.psm1 +++ b/powershell-adapter/psDscAdapter/psDscAdapter.psm1 @@ -60,7 +60,7 @@ function Add-AstMembers { foreach ($member in $TypeAst.Members) { $property = $member -as [System.Management.Automation.Language.PropertyMemberAst] - if (($property -eq $null) -or ($property.IsStatic)) { + if (($null -eq $property) -or ($property.IsStatic)) { continue; } $skipProperty = $true @@ -139,7 +139,7 @@ function FindAndParseResourceDefinitions { $DscResourceInfo.Version = $moduleVersion $DscResourceInfo.Properties = [System.Collections.Generic.List[DscResourcePropertyInfo]]::new() - $DscResourceInfo.Methods = GetClassBasedCapabilities $typeDefinitionAst.Members + $DscResourceInfo.Capabilities = GetClassBasedCapabilities $typeDefinitionAst.Members Add-AstMembers $typeDefinitions $typeDefinitionAst $DscResourceInfo.Properties $resourceList.Add($DscResourceInfo) @@ -531,7 +531,7 @@ function GetTypeInstanceFromModule { } function GetClassBasedCapabilities ($functionMemberAst) { - $capabilities = @() + $capabilities = [System.Collections.Generic.List[string[]]]::new() # These are the methods that we can potentially expect in a class-based DSC resource. $availableMethods = @('get', 'set', 'setHandlesExist', 'whatIf', 'test', 'delete', 'export') $methods = $functionMemberAst | Where-Object { $_ -is [System.Management.Automation.Language.FunctionMemberAst] -and $_.Name -in $availableMethods } @@ -539,13 +539,13 @@ function GetClassBasedCapabilities ($functionMemberAst) { foreach ($method in $methods.Name) { # We go through each method to properly case handle the method names. switch ($method) { - 'Get' { $capabilities += 'get' } - 'Set' { $capabilities += 'set' } - 'Test' { $capabilities += 'test' } - 'WhatIf' { $capabilities += 'whatIf' } - 'SetHandlesExist' { $capabilities += 'setHandlesExist' } - 'Delete' { $capabilities += 'delete' } - 'Export' { $capabilities += 'export' } + 'Get' { $capabilities.Add('get') } + 'Set' { $capabilities.Add('set') } + 'Test' { $capabilities.Add('test') } + 'WhatIf' { $capabilities.Add('whatIf') } + 'SetHandlesExist' { $capabilities.Add('setHandlesExist') } + 'Delete' { $capabilities.Add('delete') } + 'Export' { $capabilities.Add('export') } } } @@ -601,5 +601,5 @@ class DscResourceInfo { [string] $ImplementedAs [string] $CompanyName [System.Collections.Generic.List[DscResourcePropertyInfo]] $Properties - [string[]] $Methods + [string[]] $Capabilities } From b2713e4261532080192f490cc285295cb6c124f1 Mon Sep 17 00:00:00 2001 From: "G.Reijn" Date: Sun, 20 Jul 2025 05:36:11 +0200 Subject: [PATCH 6/6] Add comment --- powershell-adapter/psDscAdapter/powershell.resource.ps1 | 1 + powershell-adapter/psDscAdapter/psDscAdapter.psm1 | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/powershell-adapter/psDscAdapter/powershell.resource.ps1 b/powershell-adapter/psDscAdapter/powershell.resource.ps1 index 42a8a7f35..ccee164ae 100644 --- a/powershell-adapter/psDscAdapter/powershell.resource.ps1 +++ b/powershell-adapter/psDscAdapter/powershell.resource.ps1 @@ -91,6 +91,7 @@ switch ($Operation) { # 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 the DscResourceInfo does have capabilities, use them or else use the module's capabilities if ($DscResourceInfo.Capabilities) { $capabilities = $DscResourceInfo.Capabilities } elseif ($module.PrivateData.PSData.DscCapabilities) { diff --git a/powershell-adapter/psDscAdapter/psDscAdapter.psm1 b/powershell-adapter/psDscAdapter/psDscAdapter.psm1 index 5180ed48a..fe23327f3 100644 --- a/powershell-adapter/psDscAdapter/psDscAdapter.psm1 +++ b/powershell-adapter/psDscAdapter/psDscAdapter.psm1 @@ -326,7 +326,7 @@ function Invoke-DscCacheRefresh { # fill in resource files (and their last-write-times) that will be used for up-do-date checks $lastWriteTimes = @{} - Get-ChildItem -Recurse -File -Path $dscResource.ParentPath -Include "*.ps1", "*.psd1", "*.psm1", "*.mof" -ea Ignore | % { + Get-ChildItem -Recurse -File -Path $dscResource.ParentPath -Include "*.ps1", "*.psd1", "*.psm1", "*.mof" -ea Ignore | ForEach-Object { $lastWriteTimes.Add($_.FullName, $_.LastWriteTime) } @@ -339,7 +339,7 @@ function Invoke-DscCacheRefresh { [dscResourceCache]$cache = [dscResourceCache]::new() $cache.ResourceCache = $dscResourceCacheEntries - $m = $env:PSModulePath -split [IO.Path]::PathSeparator | % { Get-ChildItem -Directory -Path $_ -Depth 1 -ea SilentlyContinue } + $m = $env:PSModulePath -split [IO.Path]::PathSeparator | ForEach-Object { Get-ChildItem -Directory -Path $_ -Depth 1 -ea SilentlyContinue } $cache.PSModulePaths = $m.FullName $cache.CacheSchemaVersion = $script:CurrentCacheSchemaVersion