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?
If your intent is simply to use the arguments
for argument splatting,
it is fine to copy the entries of the automatic $PSBoundParameters
variable - whose type is derived from a generic dictionary (System.Collections.Generic.Dictionary`2
) - into a hash table ([hashtable]
) by way of a cast (which calls the appropriate constructor behind the scenes).
.Clone()
on it.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:
The resulting hashtable is a collection of entries that is separate from $PSBoundParameters
, i.e. you can add or remove entries or assign new values to its entries independently, without affecting $PSBoundParameters
.
However, if $PSBoundParameters
entry values happen to be instances of .NET reference types, modifying these values from either dictionary affects both dictionaries, because the corresponding entries in both dictionaries "point to" (reference) the very same object instance. That is, what the [hashtable]
constructor created were what are called shallow copies of the entries in $PSBoundParameters
, and the same would apply to calling .Clone()
on an existing hashtable.
If you need to avoid that, you'll have to manually create deep copies of such values, which may be nontrivial; to determine whether a given value is an instance of .NET reference type, use -not $someValue.GetType().IsValueType
See this answer for more information.
# 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}