Skip to content

Fix property discovery on class-based resources WinPS adapter #879

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions powershell-adapter/Tests/powershellgroup.resource.tests.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,22 @@ Describe 'PowerShell adapter resource tests' {
$res.actualState.EnumProp | Should -BeExactly 'Expected'
}

It 'Get should return the correct properties on class-based resource' {
$r = "{'Name':'TestClassResource1'}" | dsc resource get -r 'TestClassResource/TestClassResource' -f -
$LASTEXITCODE | Should -Be 0
$res = $r | ConvertFrom-Json -AsHashtable
$res.actualState.ContainsKey('Name') | Should -Be $True
$res.actualState.ContainsKey('Prop1') | Should -Be $True
$res.actualState.ContainsKey('HashTableProp') | Should -Be $True
$res.actualState.ContainsKey('EnumProp') | Should -Be $True
$res.actualState.ContainsKey('Credential') | Should -Be $True
$res.actualState.ContainsKey('Ensure') | Should -Be $True
$res.actualState.ContainsKey('BaseProperty') | Should -Be $True
$res.actualState.ContainsKey('HiddenDscProperty') | Should -Be $True
$res.actualState.ContainsKey('NonDscProperty') | Should -Be $False
$res.actualState.ContainsKey('HiddenNonDscProperty') | Should -Be $False
}

It 'Test works on class-based resource' {

$r = "{'Name':'TestClassResource1','Prop1':'ValueForProp1'}" | dsc resource test -r 'TestClassResource/TestClassResource' -f -
Expand Down
85 changes: 81 additions & 4 deletions powershell-adapter/psDscAdapter/win_psDscAdapter.psm1
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,10 @@ function Invoke-DscCacheRefresh {
if ($classBased -and ($classBased.CustomAttributes.AttributeType.Name -eq 'DscResourceAttribute')) {
"Detected class-based resource: $($dscResource.Name) => Type: $($classBased.BaseType.FullName)" | Write-DscTrace
$dscResourceInfo.ImplementationDetail = 'ClassBased'
$properties = GetClassBasedProperties -filePath $dscResource.Path -className $dscResource.Name
if ($null -ne $properties) {
$DscResourceInfo.Properties = $properties
}
}

# fill in resource files (and their last-write-times) that will be used for up-do-date checks
Expand All @@ -239,10 +243,10 @@ function Invoke-DscCacheRefresh {
}

$dscResourceCacheEntries.Add([dscResourceCacheEntry]@{
Type = "$moduleName/$($dscResource.Name)"
DscResourceInfo = $DscResourceInfo
LastWriteTimes = $lastWriteTimes
})
Type = "$moduleName/$($dscResource.Name)"
DscResourceInfo = $DscResourceInfo
LastWriteTimes = $lastWriteTimes
})
}

if ($namedModules.Count -gt 0) {
Expand Down Expand Up @@ -584,6 +588,72 @@ function ValidateMethod {
return $method
}

function GetClassBasedProperties {
param (
[Parameter(Mandatory = $true)]
[string] $filePath,

[Parameter(Mandatory = $true)]
[string] $className
)

if (".psd1" -notcontains ([System.IO.Path]::GetExtension($filePath))) {
return @('get', 'set', 'test')
}

$module = $filePath.Replace('.psd1', '.psm1')

$properties = [System.Collections.Generic.List[DscResourcePropertyInfo]]::new()

if (Test-Path $module -ErrorAction Ignore) {
[System.Management.Automation.Language.Token[]] $tokens = $null
[System.Management.Automation.Language.ParseError[]] $errors = $null
$ast = [System.Management.Automation.Language.Parser]::ParseFile($module, [ref]$tokens, [ref]$errors)
foreach ($e in $errors) {
$e | Out-String | Write-DscTrace -Operation Error
}

$typeDefinitions = $ast.FindAll(
{
$typeAst = $args[0] -as [System.Management.Automation.Language.TypeDefinitionAst]
return $null -ne $typeAst;
},
$false);

$typeDefinition = $typeDefinitions | Where-Object -Property Name -EQ $className

foreach ($member in $typeDefinition.Members) {
$property = $member -as [System.Management.Automation.Language.PropertyMemberAst]
if (($null -eq $property) -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
break
}
}
}
}
if ($skipProperty) {
continue;
}

[DscResourcePropertyInfo]$prop = [DscResourcePropertyInfo]::new()
$prop.Name = $property.Name
$prop.PropertyType = $property.PropertyType.TypeName.Name
$prop.IsMandatory = $isKeyProperty
$properties.Add($prop)
}
return $properties
}
}

# cached resource
class dscResourceCacheEntry {
[string] $Type
Expand Down Expand Up @@ -612,6 +682,13 @@ 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
Expand Down
Loading