Skip to content
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

Class-based PowerShell DSC Resources include hidden properties in dsc config results #157

Closed
3 tasks done
michaeltlombardi opened this issue Aug 16, 2023 · 1 comment · Fixed by #556
Closed
3 tasks done
Assignees
Labels
Issue-Bug Something isn't working
Milestone

Comments

@michaeltlombardi
Copy link
Collaborator

michaeltlombardi commented Aug 16, 2023

Prerequisites

  • Write a descriptive title.
  • Make sure you are able to repro it on the latest version
  • Search the existing issues.

Steps to reproduce

  1. Create the files for a new PowerShell module

    mkdir ./dsc-repro/psdsc.repro
    touch ./dsc-repro/psdsc.repro/psdsc.repro.psd1
    touch ./dsc-repro/psdsc.repro/psdsc.repro.psm1
  2. Define the module manifest:

    @{
        RootModule           = 'psdsc.repro.psm1'
        ModuleVersion        = '0.0.1'
        GUID                 = '361fff1b-423a-423f-9e05-39f5ab94a437'
        Author               = 'Bug Reproducer'
        CompanyName          = 'Unknown'
        Copyright            = '(c) Bug Reproducer. All rights reserved.'
        FunctionsToExport    = '*'
        CmdletsToExport      = '*'
        VariablesToExport    = '*'
        AliasesToExport      = '*'
        DscResourcesToExport = @(
            'ReproResultData'
        )
        PrivateData          = @{ PSData = @{} }
    }
  3. Define the root module script:

    [DscResource()] class ReproResultData {
                [DscProperty(Key)]             [string] $KeyProperty
                [DscProperty()]                [string] $NormalProperty
                                               [string] $NonDscProperty
        hidden                                 [string] $HiddenNonDscProperty
        hidden  [DscProperty()]                [string] $HiddenDscProperty
        static  [DscProperty(NotConfigurable)] [string] $StaticDscProperty = @(
            'This property could be in results data,'
            'but is an anti-pattern unless the property'
            'is read-only.'
        ) -join ' '
        static                                 [string] $StaticNonDscProperty = @(
            "This property shouldn't be in results data,"
            "because it's not a DSC property."
        ) -join ' '
    
        ReproResultData() {
            $this.NormalProperty = 'Should be in results data.'
            $this.NonDscProperty = @(
                "This property shouldn't be in results data,"
                "because it's not a DSC property."
            ) -join ' '
            $this.HiddenNonDscProperty = @(
                "This property shouldn't be in results data,"
                "because it's hidden and not a DSC property."
            ) -join ' '
            $this.HiddenDscProperty = @(
                'This property should be in results data,'
                'but is an anti-pattern.'
            ) -join ' '
        }
    
        [ReproResultData] Get() {
            $Current             = [ReproResultData]::new()
            $Current.KeyProperty = $this.KeyProperty
    
            return $Current
        }
    
        [bool] Test() { return $true }
        [void] Set()  { }
    }
  4. Add the folder containing the repro module to the PSModulePath:

    $env:PSModulePath += [System.IO.Path]::PathSeparator + "$PWD/dsc-repro"
  5. Confirm you can get the ReproResultData dsc resource and see its properties:

    $Resource = Get-DscResource -Module psdsc.repro
    ImplementationDetail : ClassBased
    ResourceType         : ReproResultData
    Name                 : ReproResultData
    FriendlyName         :
    Module               : psdsc.repro
    ModuleName           : psdsc.repro
    Version              : 0.0.1
    Path                 : C:\code\dsc-repro\psdsc.repro\psdsc.repro.psd1
    ParentPath           : C:\code\dsc-repro\psdsc.repro
    ImplementedAs        : PowerShell
    CompanyName          : Unknown
    Properties           : {KeyProperty, DependsOn, HiddenDscProperty, NormalProperty…}
    $Resource | Select-Object -ExpandProperty Properties | Format-Table
    Name                 PropertyType   IsMandatory Values
    ----                 ------------   ----------- ------
    KeyProperty          [string]              True {}
    DependsOn            [string[]]           False {}
    HiddenDscProperty    [string]             False {}
    NormalProperty       [string]             False {}
    PsDscRunAsCredential [PSCredential]       False {}
    
    dsc --format yaml resource list psdsc.repro/ReproResultData
    # Formatted for easier reading
    type          : psdsc.repro/ReproResultData
    version       : 0.0.1
    path          : C:\code\dsc-repro\psdsc.repro\psdsc.repro.psd1
    description   : null
    directory     : C:\code\dsc-repro\psdsc.repro
    implementedAs : ClassBased
    author        : ''
    properties    : [
                        KeyProperty,
                        DependsOn,
                        HiddenDscProperty,
                        NormalProperty,
                        PsDscRunAsCredential
                    ]
    requires      : DSC/PowerShellGroup
    manifest      : null
  6. Invoke the resource with Invoke-DscResource

    $PSInvokeParams = @{
        Method   = 'Get'
        Module   = 'psdsc.repro'
        Name     = 'ReproResultData'
        Property = @{ KeyProperty = 'Repro Example (PSDSC)' }
    }
    $PSInvokeResult = Invoke-DscResource @PSInvokeParams
    $PSInvokeResult | Format-List
    KeyProperty    : Repro Example (PSDSC)
    NormalProperty : Should be in results data.
    NonDscProperty : This property shouldn't be in results data, because it's not a DSC property.
  7. Invoke the resource with dsc resource:

    $V3InvokeResult = '{ "KeyProperty": "Repro Example (DSCv3)" }' |
        dsc resource get -r psdsc.repro/ReproResultData |
        ConvertFrom-Json |
        Select-Object -ExpandProperty actualState
    $V3InvokeResult | Format-List
    KeyProperty          : Repro Example (DSCv3)
    NormalProperty       : Should be in results data.
    NonDscProperty       : This property shouldn't be in results data, because it's not a DSC property.
    HiddenNonDscProperty : This property shouldn't be in results data, because it's hidden and not a DSC property.
    HiddenDscProperty    : This property should be in results data, but is an anti-pattern.
  8. Compare the actual and preferred output statuses, with metadata:

    filter Test-HasAttribute($Property, $Attribute) {
        [bool]($Property.CustomAttributes.AttributeType -eq $Attribute)
    }
    filter Test-InPropertySet($Property, $InputObject) {
        $Set = if ($InputObject -is [Microsoft.PowerShell.DesiredStateConfiguration.DscResourceInfo]) {
            $InputObject.Properties
        } else {
            $InputObject.psobject.Properties
        }
        $Property.Name -in $Set.Name
    }
    filter Test-IsHidden ($p) {
        $a = [System.Management.Automation.HiddenAttribute]
        Test-HasAttribute -Property $p -Attribute $a
    }
    filter Test-IsDscProperty ($p) {
        $a = [System.Management.Automation.DscPropertyAttribute]
        Test-HasAttribute -Property $p -Attribute $a
    }
    filter Test-ShouldBeInResult ($p) {
        $IsDscProperty    = Test-IsDscProperty $_
        $IsHiddenProperty = Test-IsHidden      $_
    
        $IsDscProperty -and (-not $IsHiddenProperty)
    }
    $PSInvokeResult.GetType().GetProperties() | Format-Table -Property @(
        'Name'
        @{ Name = 'InResourceInfo'    ; Expression = { Test-InPropertySet $_ $Resource }}
        @{ Name = 'InPSDscResult'     ; Expression = { Test-InPropertySet $_ $PSInvokeResult }}
        @{ Name = 'InV3DscResult'     ; Expression = { Test-InPropertySet $_ $V3InvokeResult }}
        @{ Name = 'ShouldBeInResults' ; Expression = { Test-ShouldBeInResult $_ }}
        @{ Name = 'IsDscProperty'     ; Expression = { Test-IsDscProperty $_ }}
        @{ Name = 'IsHidden'          ; Expression = { Test-IsHidden $_ }}
        @{ Name = 'IsStatic'          ; Expression = { $_.GetGetMethod().IsStatic }}
    )
    Name                 InResourceInfo InPSDscResult InV3DscResult ShouldBeInResults IsDscProperty IsHidden IsStatic
    ----                 -------------- ------------- ------------- ----------------- ------------- -------- --------
    KeyProperty                    True          True          True             False          True    False    False
    NormalProperty                 True          True          True             False          True    False    False
    NonDscProperty                False          True          True             False         False    False    False
    HiddenNonDscProperty          False         False          True             False         False     True    False
    HiddenDscProperty              True         False          True             False          True     True    False
    StaticDscProperty             False         False         False             False          True    False     True
    StaticNonDscProperty          False         False         False             False         False    False     True
    

Expected behavior

$Invoking = @{
    Method   = 'Get'
    Module   = 'psdsc.repro'
    Name     = 'ReproResultData'
    Property = @{ KeyProperty = 'Repro Example' }
}
Invoke-DscResource @Invoking | Format-List
KeyProperty    : Repro Example
NormalProperty : Should be in results data.
$invoking.Property |
    ConvertTo-Json |
    dsc resource get -r "$($i.Module)/$($i.Name)" |
    ConvertFrom-Json |
    Select-Object -ExpandProperty actualState |
    Format-List
KeyProperty          : Repro Example
NormalProperty       : Should be in results data.

Actual behavior

$Invoking = @{
    Method   = 'Get'
    Module   = 'psdsc.repro'
    Name     = 'ReproResultData'
    Property = @{ KeyProperty = 'Repro Example' }
}
Invoke-DscResource @Invoking | Format-List
KeyProperty    : Repro Example
NormalProperty : Should be in results data.
NonDscProperty : This property shouldn't be in results data, because it's not a DSC property.
$invoking.Property |
    ConvertTo-Json |
    dsc resource get -r "$($i.Module)/$($i.Name)" |
    ConvertFrom-Json |
    Select-Object -ExpandProperty actualState |
    Format-List
KeyProperty          : Repro Example
NormalProperty       : Should be in results data.
NonDscProperty       : This property shouldn't be in results data, because it's not a DSC property.
HiddenNonDscProperty : This property shouldn't be in results data, because it's hidden and not a DSC property.
HiddenDscProperty    : This property should be in results data, but is an anti-pattern.

Error details

No response

Environment data

Name                           Value
----                           -----
PSVersion                      7.3.6
PSEdition                      Core
GitCommitId                    7.3.6
OS                             Microsoft Windows 10.0.22621
Platform                       Win32NT
PSCompatibleVersions           {1.0, 2.0, 3.0, 4.0…}
PSRemotingProtocolVersion      2.3
SerializationVersion           1.1.0.1
WSManStackVersion              3.0

Version

DSC (build from main), PSDSC v2.0.7

Visuals

No response

Notes

While both PSDSC and DSCv3 do show the NonDscProperty in the results, that seems like a bug in PSDesiredStateConfiguration. Similarly, the non-inclusion of StaticDscProperty is (arguably) a bug in PSDesiredStateConfiguration.

However, the return data handling by the DSC/PowerShellGroup resource provider is to just convert the result data to JSON:

$result | ConvertTo-Json

This runs into PowerShell/PowerShell#9847, where the serialization unexpectedly includes hidden properties (compared to ConvertTo-Csv and Select-Object).

Fix Proposal

Filter the returned properties to only include properties with the DscProperty attribute, so that the schema surface for the resources matches with Get-DscResource and the contract that the class definition implements.

@michaeltlombardi michaeltlombardi added the Issue-Bug Something isn't working label Aug 30, 2023
@SteveL-MSFT
Copy link
Member

I believe #363 will address this

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Issue-Bug Something isn't working
Projects
Status: Done
Development

Successfully merging a pull request may close this issue.

3 participants