DSC icon indicating copy to clipboard operation
DSC copied to clipboard

PSDSC Resource data in `Set` and `Test` break naive expectations

Open michaeltlombardi opened this issue 2 years ago • 0 comments

Prerequisites

  • [X] Write a descriptive title.
  • [X] Make sure you are able to repro it on the latest version
  • [X] 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] $ValueProperty
    
        [ReproResultData] Get() {
            $Current               = [ReproResultData]::new()
            $Current.KeyProperty   = $this.KeyProperty
            $Current.ValueProperty = "Default"
    
            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, EnumProperty, PsDscRunAsCredential}
    
    $Resource | Select-Object -ExpandProperty Properties | Format-Table
    
    Name                 PropertyType   IsMandatory Values
    ----                 ------------   ----------- ------
    KeyProperty          [string]              True {}
    DependsOn            [string[]]           False {}
    PsDscRunAsCredential [PSCredential]       False {}
    ValueProperty        [string]             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,
                        PsDscRunAsCredential,
                        ValueProperty
                    ]
    requires      : DSC/PowerShellGroup
    manifest      : null
    
  6. Invoke the resource with Invoke-DscResource

    $PSInvokeParams = @{
        Module   = 'psdsc.repro'
        Name     = 'ReproResultData'
        Property = @{
            KeyProperty   = 'Repro Example (PSDSC)'
            ValueProperty = 'Desired'
        }
    }
    'Get', 'Test', 'Set' | ForEach-Object {
        Invoke-DscResource -Method $_  @PSInvokeParams | Format-List
    }
    
    KeyProperty   : Repro Example (PSDSC)
    ValueProperty : Default
    ---
    InDesiredState : False
    ---
    RebootRequired : False
    
  7. Invoke the resource with dsc resource:

    foreach ($Method in @('get', 'test', 'set')) {
         $InputJson = @{
             KeyProperty   = 'Repro Example (DSCv3)'
             ValueProperty = 'Desired'
         } | ConvertTo-Json
         $Arguments = @(
             $Method
             '-r', 'psdsc.repro/ReproResultData'
             '-i', $InputJson
         )
         dsc resource @Arguments
    }
    
    actualState:
      KeyProperty: Repro Example (DSCv3)
      ValueProperty: Default
    ---
    desiredState:
      KeyProperty: Repro Example (DSCv3)
      ValueProperty: Desired
      type: psdsc.repro/ReproResultData
    actualState:
      InDesiredState: false
    inDesiredState: false
    differingProperties:
    - KeyProperty
    - ValueProperty
    - type
    ---
    beforeState:
      KeyProperty: Repro Example (DSCv3)
      ValueProperty: Default
    afterState:
      RebootRequired: false
    changedProperties:
    - RebootRequired
    

Expected behavior

$PSInvokeParams = @{
    Module   = 'psdsc.repro'
    Name     = 'ReproResultData'
    Property = @{
        KeyProperty   = 'Repro Example (PSDSC)'
        ValueProperty = 'Desired'
    }
}
'Get', 'Test', 'Set' | ForEach-Object {
    Invoke-DscResource -Method $_  @PSInvokeParams | Format-List
}
KeyProperty   : Repro Example (PSDSC)
ValueProperty : Default
---
InDesiredState : False
---
RebootRequired : False
foreach ($Method in @('get', 'test', 'set')) {
    $InputJson = @{
        KeyProperty   = 'Repro Example (DSCv3)'
        ValueProperty = 'Desired'
    } | ConvertTo-Json
    $Arguments = @(
        $Method
        '-r', 'psdsc.repro/ReproResultData'
        '-i', $InputJson
    )
    dsc resource @Arguments
}
actualState:
  KeyProperty:   Repro Example (PSDSC)
  ValueProperty: Default
---
desiredState:
  KeyProperty:   Repro Example (PSDSC)
  ValueProperty: Desired
actualState:
  KeyProperty:    Repro Example (PSDSC)
  ValueProperty:  Default
  InDesiredState: false # should be ignored in property comparison
inDesiredState: false
differingProperties:
- ValueProperty
---
beforeState:
  KeyProperty:   Repro Example (PSDSC)
  ValueProperty: Default
afterState:
  KeyProperty:    Repro Example (PSDSC)
  ValueProperty:  Default
  RebootRequired: false # should be ignored in property comparison
changedProperties: []   # empty because set isn't implemented

Actual behavior

$PSInvokeParams = @{
    Module   = 'psdsc.repro'
    Name     = 'ReproResultData'
    Property = @{
        KeyProperty   = 'Repro Example (PSDSC)'
        ValueProperty = 'Desired'
    }
}
'Get', 'Test', 'Set' | ForEach-Object {
    Invoke-DscResource -Method $_  @PSInvokeParams | Format-List
}
KeyProperty   : Repro Example (PSDSC)
ValueProperty : Default
---
InDesiredState : False
---
RebootRequired : False
foreach ($Method in @('get', 'test', 'set')) {
    $InputJson = @{
        KeyProperty   = 'Repro Example (DSCv3)'
        ValueProperty = 'Desired'
    } | ConvertTo-Json
    $Arguments = @(
        $Method
        '-r', 'psdsc.repro/ReproResultData'
        '-i', $InputJson
    )
    dsc resource @Arguments
}
actualState:
  KeyProperty:   Repro Example (PSDSC)
  ValueProperty: Default
---
desiredState:
  KeyProperty:   Repro Example (PSDSC)
  ValueProperty: Desired
  type:          psdsc.repro/ReproResultData
actualState:
  InDesiredState: false
inDesiredState: false
differingProperties:
- KeyProperty
- ValueProperty
- type
---
beforeState:
  KeyProperty:   Repro Example (PSDSC)
  ValueProperty: Default
afterState:
  RebootRequired: false
changedProperties:
- RebootRequired

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

Fix Proposal

The DSC/PowerShellGroup resource provider should handle munging the data before returning it to dsc - currently, it just passes through the JSONified output from Invoke-DscResource - this breaks the expectations for dsc resource test and dsc resource set, particularly for integrating tools and users unfamiliar with what's happening.

The resource provider could invoke the get method before test/set calls, caching the before state. For Set calls, it could call get again after the set operation.

We need further thought and discussion on how to handle the InDesiredState and RebootRequired values from PSDSC Resources.

michaeltlombardi avatar Aug 16 '23 19:08 michaeltlombardi