Powershell splatting a nested hash table

1.6k views Asked by At

I have a function that returns a complex nested hash table data structure, where part of it forms the arguments for a further function call, and part of it is strings for reporting errors that result in the arguments not being populated. Ideally I would like to splat just the arguments hash table, but I am starting to think that can't be done. So, an example of the basic problem looks like this...

function Test {
    param (
        [String]$A,
        [String]$B,
        [String]$C
    )
    Write-Host "A: $A"
    Write-Host "B: $B"
    Write-Host "C: $C"
}

$data = @{
    arguments = @{
        A = 'A string'
        B = 'Another string'
        C = 'The last string'
    }
    kruft = 'A string that doesn not need to get passed'
}

Ideally I want to splat $data.arguments, so something like this... Test @data.arguments But that doesn't work, resulting in the error

The splatting operator '@' cannot be used to reference variables in an expression. '@data' can be used only as an argument to a command. To 
reference variables in an expression use '$data'.

So I tried... Test @(data.arguments) Which results in the error

data.arguments : The term 'data.arguments' is not recognized as the name of a cmdlet, function, script file, or operable program. Check the 
spelling of the name, or if a path was included, verify that the path is correct and try again.

I also tried... Test @($data.arguments) Which results in the whole hash table being passed as a single argument and the output is

A: System.Collections.Hashtable
B: 
C:

What DOES work is...

$arguments = $data.arguments
Test @arguments

Which has me thinking you really cannot splat anything but a simple variable that is an appropriate hash table. But, I am hoping someone can verify that is indeed true, or point out the solution I haven't come up with yet. The actual code requires 5 arguments, with somewhat verbose names because I prefer descriptive names, so splatting is very much an appropriate solution. Needing to make a new variable with just the hash table to be passed isn't an issue, just wondering if it really is the only option.

3

There are 3 answers

1
swbbl On BEST ANSWER

That's not possible.

Any variable (and only variables) provided with "@" instead of "$" for a function parameter is declared as TokenKind "SplattedVariable".

See: https://learn.microsoft.com/en-us/dotnet/api/system.management.automation.language.tokenkind?view=powershellsdk-7.0.0

PS: Some quick tests from my side which could have succeed (apart from PS design):

Write-Host 'Test 1' -ForegroundColor Yellow
Test @$data.arguments

Write-Host 'Test 2' -ForegroundColor Yellow
Test @$($bla = $data.arguments)

Write-Host 'Test 3' -ForegroundColor Yellow
Test @$bla = $data.arguments

Write-Host 'Test 4' -ForegroundColor Yellow
Test @$bla = $data.arguments.GetEnumerator()

Write-Host 'Test 5' -ForegroundColor Yellow
Test @$($data.arguments.GetEnumerator())

... but they didn't.

0
arco444 On

I cannot claim to fully understand what you're attempting, but here are a couple of solutions that might help.

Your Test function is expecting three strings, but I don't see anything in your example that satisfies that. I would either change it to accept a Hashtable (which is the data type in question) or have it accept a string array and pass $data.arguments.values

Using a Hashtable:

function Test {
  param (
    [Hashtable]$hash
  )

  write-host $hash.A
  write-host $hash.B
  write-host $hash.C
}

Test $data.arguments

Using a String array:

function Test {
  param (
    [String[]]$a
  )

  $a | % { write-host $_ }
}

Test $data.arguments.values
0
Mark Schultheiss On

This is a totally artificial way (more of a hack) of doing this but you could pass it as a hash table then self-call with the arguments splatted

function Test {
    param (
    [Parameter()]
        [hashtable]$ht,
        [String]$A,
        [String]$B,
        [String]$C
    )
    if($null -eq $ht){
        Write-Host "A: $a" 
        Write-Host "B: $B" 
        Write-Host "C: $C" 
    }
    else
    {
        Write-Host "ht.A: $($ht.a)" 
        Test -a $($ht.A+ " new blob")  -b $ht.B -c $ht.C
        Test @ht
    }
}

$tdata = @{
    arguments = @{
        A = 'A string'
        B = 'Another string'
        C = 'The last string'
    }
    kruft = 'A string that doesn not need to get passed'
}

Write-Host 'Test ht' -ForegroundColor Yellow
Test -ht $tdata.arguments