powershellcastingclone

Cast $PSBoundParameters to [HashTable] to enable .Clone()


I have been adding $psBoundParameters to a hash table like this.

$seedTaskState = @{
   arguments = $PSBoundParameters
}

Later, I need to use those same arguments to call the next function, with one of the values changed. So I used this

$nestedTaskParameters = $seedTaskState.arguments

and then changed the one value I needed to change. Which... doesn't work because complex types are by reference so I was changing the original bound parameters, which causes all sorts of issues.

I can just initialize $nestedTaskParameters as a hash table and loop through the bound parameters and add them, like so.

$nestedTaskParameters = @{}
foreach ($key in $seedTaskState.arguments.Keys) {
    $nestedTaskParameters.Add($key, $seedTaskState.arguments.$key)
}

And I had thought that this might work, and be more elegant

$nestedTaskParameters = $seedTaskState.arguments.Clone()

but .Clone() is only available with a hashtable, not a bound parameters dictionary.

So I tried casting $seedTaskState.arguments to a hash table first, then cloning, like this.

$nestedTaskParameters = ([Hashtable]$seedTaskState.arguments).Clone()

That seems to work, but is also well outside my comfort zone, so I wonder if there is some sort of gotcha with this approach?


Solution

  • Splatting works with any dictionary-like type that implements the System.Collections.Generic.IDictionary`2 .NET interface or its non-generic counterpart, System.Collections.IDictionary.

    Caveat:

    # Sample function.
    function Foo {
    
      param(
        $bar,
        $baz
      )
      
      # Effectively copy the entries from $PSBoundParameters into a 
      # new hashtable.
      $hashtable = [hashtable] $PSBoundParameters
    
      # Add new entries to the hashtable.
      $hashtable.new1 = 'stuff1'
      $hashtable.new2 = 'stuff2'
      # Remove one.
      $hashtable.Remove('bar')
    
      # Modify the instance of the .NET reference type stored in the entry
      # with key 'baz'.
      # THIS AFFECTS THE ORIGINAL ENTRY IN $PSBoundParameters TOO.
      $hashtable.baz.prop = 2
    
      # Output the two dictionaries.
      '-- $PSBoundParameters'
      $PSBoundParameters
      "`n-- hashtable`n"
      $hashtable
    
    }
    
    # Call the function with an instance of a value type and a reference type.
    Foo 42 ([pscustomobject] @{ prop = 1 })
    

    The above yields the following, which shows that directly modifying the entry whose value is an instance of a .NET reference type affected both dictionaries, while adding and removing entries did not:

    -- $PSBoundParameters
    
    Key      Value
    ---      -----
    bar         42
    baz  @{prop=2}
    
    -- hashtable
    
    new2    stuff2
    new1    stuff1
    baz  @{prop=2}