Hashtable as resource parameter

This topic contains 5 replies, has 2 voices, and was last updated by Profile photo of Martin Nielsen Martin Nielsen 2 years, 3 months ago.

  • Author
    Posts
  • #17989
    Profile photo of Martin Nielsen
    Martin Nielsen
    Participant

    I've been squeezing my brain trying to figure out how to get *-TargetResource to accept a [hashtable] parameter. Can it be done?

    #psm1:
    param([hashtable]$AdvancedProperties)
    
    #schema:
    [Write] String AdvancedProperties;
    

    breaks with

    Write-NodeMOFFile : Invalid MOF definition for node 'localhost': Exception calling "ValidateInstanceText" with "1"
    argument(s): "Syntax error:
     At line:40, char:48
     Buffer:
     $MSFT_KeyValuePair1ref,^
       $M
    "
    At
    C:\windows\system32\windowspowershell\v1.0\Modules\PSDesiredStateConfiguration\PSDesiredStateConfiguration.psm1:1457
    char:17
    + ...             Write-NodeMOFFile $name $mofNode $Script:NodeInstanceAlia ...
    +                 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
        + CategoryInfo          : InvalidOperation: (:) [Write-Error], InvalidOperationException
        + FullyQualifiedErrorId : InvalidMOFDefinition,Write-NodeMOFFile
    

    So that's no good, obviously.

    #psm1:
    param([hashtable]$AdvancedProperties)
    
    #schema
    [write] Hashtable AdvancedProperties;
    

    is not recognized as a valid schema value at all, and the resource isn't registered with DSC.

    #psm1:
    param([hashtable]$AdvancedProperties)
    
    #schema
    [write, EmbeddedInstance("MSFT_KeyValuePair")] String AdvancedProperties;
    

    breaks with the following error:

    Write-NodeMOFFile : Invalid MOF definition for node 'localhost': Exception calling "ValidateInstanceText" with "1"
    argument(s): "Syntax error:
     At line:40, char:48
     Buffer:
     $MSFT_KeyValuePair1ref,^
       $M
    "
    At
    C:\windows\system32\windowspowershell\v1.0\Modules\PSDesiredStateConfiguration\PSDesiredStateConfiguration.psm1:1457
    char:17
    + ...             Write-NodeMOFFile $name $mofNode $Script:NodeInstanceAlia ...
    +                 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
        + CategoryInfo          : InvalidOperation: (:) [Write-Error], InvalidOperationException
        + FullyQualifiedErrorId : InvalidMOFDefinition,Write-NodeMOFFile
    

    And finally, I found a PowerShell Team person (http://blogs.msdn.com/b/powershell/archive/2013/11/19/resource-designer-tool-a-walkthrough-writing-a-dsc-resource.aspx) suggesting that it should be set up as such:

    #psm1
    param([Microsoft.Management.Infrastructure.CimInstance]$AdvancedProperties)
    
    #schema
    [write, EmbeddedInstance("MSFT_KeyValuePair")] String AdvancedProperties;
    

    but that also fails with the same error as previously

    Write-NodeMOFFile : Invalid MOF definition for node 'localhost': Exception calling "ValidateInstanceText" with "1"
    argument(s): "Syntax error:
     At line:25, char:48
     Buffer:
     $MSFT_KeyValuePair1ref,^
       $M
    "
    At
    C:\windows\system32\windowspowershell\v1.0\Modules\PSDesiredStateConfiguration\PSDesiredStateConfiguration.psm1:1457
    char:17
    + ...             Write-NodeMOFFile $name $mofNode $Script:NodeInstanceAlia ...
    +                 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
        + CategoryInfo          : InvalidOperation: (:) [Write-Error], InvalidOperationException
        + FullyQualifiedErrorId : InvalidMOFDefinition,Write-NodeMOFFile
    

    It breaks using both WMF4 and WMF5 Preview.

    This is the configuration file I'm using:

    configuration DSCModuleTest {
        param(
            [Parameter(Mandatory = $true)]
            [string]$ComputerName
        )
    
        Import-DscResource -ModuleName devNetAdapterModule
    
        Node $ComputerName {
            sNetAdapterAdvancedProperty Property {
                InterfaceAlias = 'Ethernet'
                AdvancedProperties = @{
                    'Jumbo Packet'         = '1514'
                    'Receive Side Scaling' = '1'
                }
            }
        }
    }
    
    DSCModuleTest -ComputerName 'localhost'
    

    and the associated DSCResource as it looks currently:

    
    
    ######################################################################################
    # The Get-TargetResource cmdlet.
    # This function will get the working interface and its bindings
    ######################################################################################
    function Get-TargetResource
    {
        [CmdletBinding()]
    	param
    	(		
    		[Parameter(Mandatory)]
    		[ValidateNotNullOrEmpty()]
            [string]$InterfaceAlias
    	)
    	
        $properties = GetNetAdapterAdvancedProperty -InterfaceAlias $InterfaceAlias -Formatted
        
        Write-Output $properties
    }
    
    ######################################################################################
    # The Set-TargetResource cmdlet.
    # This function will set the bindings of the selected interface
    ######################################################################################
    function Set-TargetResource
    {
        [CmdletBinding()]
    	param
    	(
    		[Parameter(Mandatory)]
    		[ValidateNotNullOrEmpty()]
            [string]$InterfaceAlias,
    
            [Parameter(Mandatory)]
            [hashtable]$AdvancedProperties
            #[Microsoft.Management.Infrastructure.CimInstance]$AdvancedProperties
    	)
    
        $properties = GetNetAdapterAdvancedProperty -InterfaceAlias $InterfaceAlias
    
        foreach($key in $AdvancedProperties.Keys) {
            Write-Verbose ('Processing property {0}' -f $key)
            
            $property = $properties | Where-Object DisplayName -eq $key
            if($property -eq $null) {
                throw "Property $key not found. Use the Get-NetAdapterAdvancedProperty cmdlet to see available properties for your interface, and configure DSC AdvancedProperties hashtable to use 'DisplayName' property as key, and 'RegistryValue' as value."
            }
            
            Write-Verbose ('Found and now setting property {0}' -f $property.DisplayName)
            $property | Set-NetAdapterAdvancedProperty -RegistryValue $AdvancedProperties.$key
        }
    }
    
    ######################################################################################
    # The Test-TargetResource cmdlet.
    # This will test if the interface bindings are as expected
    ######################################################################################
    function Test-TargetResource
    {
        [CmdletBinding()]
        param
    	(		
    		[Parameter(Mandatory)]
    		[ValidateNotNullOrEmpty()]
    		[string]$InterfaceAlias,
            
            [Parameter(Mandatory)]
            [hashtable]$AdvancedProperties
            #[Microsoft.Management.Infrastructure.CimInstance]$AdvancedProperties
    	)
    
        $properties = GetNetAdapterAdvancedProperty -InterfaceAlias $InterfaceAlias -Formatted
    
        foreach($key in $AdvancedProperties.Keys) {
            Write-Verbose ('Processing property {0}' -f $key)
            
            $property = $properties | Where-Object DisplayName -eq $key
            if($property -eq $null) {
                throw "Property $key not found. Use the Get-NetAdapterAdvancedProperty cmdlet to see available properties for your interface, and configure DSC AdvancedProperties hashtable to use 'DisplayName' property as key, and 'RegistryValue' as value."
            }
            
            Write-Verbose ('Found and now testing property {0}' -f $property.DisplayName)
            if($property.RegistryValue -ne $AdvancedProperties.$key) {
                Write-Verbose ('Property "{0}" with expected value "{1}" does not match set value "{2}"' -f $key, $AdvancedProperties.$key, $property.RegistryValue)
                return $false
            }
        }
    
        return $true
    }
    
    
    #######################################################################################
    #  Helper function that validates the IP Address properties. If the switch parameter
    # "Apply" is set, then it will set the properties after a test
    #######################################################################################
    function GetNetAdapterByAlias {
        [CmdletBinding()]
        param(
            [Parameter(Mandatory)]
            [string]$InterfaceAlias
        )
    
        Write-Verbose "Attempting to find NetAdapter using Alias $InterfaceAlias"
        $adapter = Get-NetAdapter -InterfaceAlias $InterfaceAlias
    
        if($adapter -ne $null -and @($adapter).Count -eq 1) {
            Write-Verbose ('Found {0}' -f $adapter.Name)
            return $adapter
        }
        else {
            if(@($adapter).Count -gt 1) { 
                throw ("Too many adapters found for Alias $InterfaceAlias ({0})" -f ($adapter.Name -join ', '))
            }
            elseif($adapter -eq $null) { 
                throw "No adapter found for Alias $InterfaceAlias"
            }
            else {
                throw ('Unexpected error? Adapter count: {0}. InterfaceAlias: {1}.' -f @($adapter).Count, $InterfaceAlias)
            }
        }
    }
    
    function GetNetAdapterAdvancedProperty {
        [CmdletBinding()]
        param(
            [Parameter(Mandatory)]
            [string]$InterfaceAlias,
    
            [switch]$Formatted
        )
    
        $adapter = GetNetAdapterByAlias -InterfaceAlias $InterfaceAlias
    
        Write-Verbose "Getting advanced properties"
        $properties = $adapter | Get-NetAdapterAdvancedProperty
        if($properties -eq $null) {
            throw ("Unable to acquire bindings for NetAdapter {0}" -f $adapter.Name)
        }
    
        if($Formatted) { 
            Write-Verbose "Formatting properties"
            $returnValue = foreach($property in $properties) {
                Write-Output @{
                    $property.DisplayName = $property.DisplayValue
                }
            }
    
    	    Write-Output $returnValue
        }
        else {
            Write-Output $properties
        }
    }
    
    
    #  FUNCTIONS TO BE EXPORTED 
    Export-ModuleMember -function Get-TargetResource, Set-TargetResource, Test-TargetResource
    
  • #17990
    Profile photo of Martin Nielsen
    Martin Nielsen
    Participant

    Oh, and running it with -Debug gives the following:

    DEBUG:    SHT_sNetAdapterAdvancedProperty: RESOURCE PROCESSING STARTED [KeywordName='sNetAdapterAdvancedProperty'] Function='devNetAdapterModule\sNetAdapterAdvancedProperty']
    DEBUG:    SHT_sNetAdapterAdvancedProperty: ResourceID = [sNetAdapterAdvancedProperty]Property
    DEBUG:    SHT_sNetAdapterAdvancedProperty:  Processing property 'DependsOn' [
    DEBUG:    SHT_sNetAdapterAdvancedProperty:        Canonicalized property 'DependsOn' = ''
    DEBUG:    SHT_sNetAdapterAdvancedProperty:    Processing completed 'DependsOn' ]
    DEBUG:    SHT_sNetAdapterAdvancedProperty:  Processing property 'InterfaceAlias' [
    DEBUG:    SHT_sNetAdapterAdvancedProperty:        Canonicalized property 'InterfaceAlias' = 'Ethernet'
    DEBUG:    SHT_sNetAdapterAdvancedProperty:    Processing completed 'InterfaceAlias' ]
    DEBUG:    SHT_sNetAdapterAdvancedProperty:  Processing property 'AdvancedProperties' [
    DEBUG:    SHT_sNetAdapterAdvancedProperty:        Canonicalized property 'AdvancedProperties' = 'System.Collections.Hashtable'
    DEBUG:    SHT_sNetAdapterAdvancedProperty:    Processing completed 'AdvancedProperties' ]
    DEBUG:    SHT_sNetAdapterAdvancedProperty: MOF alias for this resource is '$SHT_sNetAdapterAdvancedProperty1ref'
    DEBUG:    SHT_sNetAdapterAdvancedProperty: RESOURCE PROCESSING COMPLETED. TOTAL ERROR COUNT: 0
    Write-NodeMOFFile : Invalid MOF definition for node 'localhost': Exception calling "ValidateInstanceText" with "1" argument(s): "Syntax error: 
     At line:25, char:48
     Buffer:
     $MSFT_KeyValuePair1ref,^
       $M
    "
    At C:\Windows\system32\WindowsPowerShell\v1.0\Modules\PSDesiredStateConfiguration\PSDesiredStateConfiguration.psm1:1425 char:17
    +                 Write-NodeMOFFile $name $mofNode $Script:NodeInstanceAliases[$mo ...
    +                 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
        + CategoryInfo          : InvalidOperation: (:) [Write-Error], InvalidOperationException
        + FullyQualifiedErrorId : InvalidMOFDefinition,Write-NodeMOFFile
    

    and the mof.error file that's generated:

    /*
    @TargetNode='localhost'
    @GeneratedBy=mni
    @GenerationDate=08/13/2014 11:08:57
    @GenerationHost=MNI-PC
    */
    
    instance of MSFT_KeyValuePair as $MSFT_KeyValuePair1ref
    {
    Key = "Receive Side Scaling";
     Value = "1";
    
    };
    
    instance of MSFT_KeyValuePair as $MSFT_KeyValuePair2ref
    {
    Key = "Jumbo Packet";
     Value = "1514";
    
    };
    
    instance of SHT_sNetAdapterAdvancedProperty as $SHT_sNetAdapterAdvancedProperty1ref
    {
    ResourceID = "[sNetAdapterAdvancedProperty]Property";
     AdvancedProperties =    $MSFT_KeyValuePair1ref,
       $MSFT_KeyValuePair2ref
    ;
     SourceInfo = "C:\\DSC\\dsc.ps1::10::9::sNetAdapterAdvancedProperty";
     ModuleName = "devNetAdapterModule";
     InterfaceAlias = "Ethernet";
     ModuleVersion = "1.1";
    
    };
    
    instance of OMI_ConfigurationDocument
    {
     Version="1.0.0";
     Author="mni";
     GenerationDate="08/13/2014 11:08:57";
     GenerationHost="MNI-PC";
    };
    
  • #17991
    Profile photo of Martin Nielsen
    Martin Nielsen
    Participant

    Found something...

    Setting the schema property to

    [Write, EmbeddedInstance("MSFT_KeyValuePair")] String AdvancedProperties[];

    lets DSC read and generate a proper mof file if you can believe it.

    Only thing is now I'm getting

    PowerShell DSC resource SHT_sNetAdapterAdvancedProperty  failed to execute Test-TargetResource functionality with
    error message: Cannot process argument transformation on parameter 'AdvancedProperties'. Cannot convert the
    "Microsoft.Management.Infrastructure.CimInstance[]" value of type "Microsoft.Management.Infrastructure.CimInstance[]"
    to type "System.Collections.Generic.KeyValuePair`2[System.String,System.String]".
        + CategoryInfo          : InvalidOperation: (:) [], CimException
        + FullyQualifiedErrorId : ProviderOperationExecutionFailure
        + PSComputerName        : localhost
    

    if I set the param to either no type or KeyValuePair[[string],[string]].

    If I set the param to [hashtable], [Microsoft.Management.Infrastructure.CimInstance] or [Microsoft.Management.Infrastructure.CimInstance[]] I get

    PowerShell DSC resource SHT_sNetAdapterAdvancedProperty  failed to execute Test-TargetResource functionality with
    error message: Cannot process argument transformation on parameter 'AdvancedProperties'. Cannot convert the
    "Microsoft.Management.Infrastructure.CimInstance[]" value of type "Microsoft.Management.Infrastructure.CimInstance[]"
    to type "System.Collections.Hashtable".
        + CategoryInfo          : InvalidOperation: (:) [], CimException
        + FullyQualifiedErrorId : ProviderOperationExecutionFailure
        + PSComputerName        : localhost
    

    At least I'm getting somewhere though. I think.

  • #17993
    Profile photo of Martin Nielsen
    Martin Nielsen
    Participant

    Aha, success!

    #psm1
    param(
        [Microsoft.Management.Infrastructure.CimInstance[]]$AdvancedProperties
    )
    
    foreach($instance in $AdvancedProperties) {
        Write-Verbose ('Key: {0}, Value: {1}' -f $instance.Key, $instance.Value)
    }
    
    #schema
    [Write, EmbeddedInstance("MSFT_KeyValuePair")] String AdvancedProperties[];
    

    This actually works. Fantastic. It may be that it takes a Hashtable in the DSC configuration, but trying to access Hashtable properties like .Keys apparently makes PowerShell attempt an implicit conversion from KeyValuePair to Hashtable, and this breaks quite spectacularly.

    Now to somehow make Get-TargetResource to work...

  • #18047
    Profile photo of Dave Wyatt
    Dave Wyatt
    Moderator

    I wouldn't stress too much about Get-TargetResource at this point. As far as I know, nothing ever calls it (unless you use it internally as part of the resource's Test or Set functions). Until the DSC engine begins to rely on Get-TargetResource for something, you'd just be making guesses at what the output hashtable should look like when dealing with these embedded instances. (I suspect that it should contain arrays of MSFT_KeyValuePair objects, just as they're passed to Set-TargetResource, but who knows?)

  • #18051
    Profile photo of Martin Nielsen
    Martin Nielsen
    Participant

    I had a chance to test out an idea using the same principle you referred to in the other thread, and it was a great success.

    #schema
    {
      [key] String KeyVariable;
      [write, EmbeddedInstance("MSFT_KeyValuePair")] String AdvancedResource;
    };
    
    #Get-TargetResource
    return @{
            KeyVariable = $KeyVariable
            AdvancedResource = New-CimInstance -ClassName MSFT_KeyValuePair -Namespace root/microsoft/Windows/DesiredStateConfiguration -Property @{
                Key = 'Some key'
                Value = 'Some value'
            } -ClientOnly
        }
    
    #
    PS C:\DSC> $conf = Get-DscConfiguration
    PS C:\DSC> $conf
    
    AdvancedResource                        KeyVariable                             PSComputerName
    --                                      --                                      --
    MSFT_KeyValuePair (key = "Some key")    key variable here
    
    
    PS C:\DSC> $conf.AdvancedResource
    
    key                                     Value                                   PSComputerName
    --                                      --                                      --
    Some key                                Some value
    

You must be logged in to reply to this topic.