I'd like to pass an associative array from C# to Powershell. As an example I'd like to execute this powershell line of code:
PS C:\> get-command | select name, @{N="Foo";E={"Bar"}} -first 3
Name Foo
---- ---
Add-Content Bar
Add-History Bar
Add-Member Bar
I'd like to do this via a Pipeline of distinct Commands as opposed to a single command marked as a script. Here's the code:
Runspace runspace = RunspaceFactory.CreateRunspace();
runspace.Open();
Pipeline pipeline = runspace.CreatePipeline();
pipeline.Commands.Add("get-command");
Command c = new Command("select-object");
List properties = new List();
properties.Add("name");
properties.Add("@{N=\"Foo\";E={\"Bar\"}}");
c.Parameters.Add("Property", properties.ToArray());
c.Parameters.Add("First", 3);
pipeline.Commands.Add(c);
pipeline.Commands.Add("Out-String");
Collection retval = pipeline.Invoke();
runspace.Close();
StringBuilder stringBuilder = new StringBuilder();
foreach (PSObject obj in retval)
Console.WriteLine(obj.ToString());
But that associative array being passed in as a parameter to Select-Object isn't being parsed correctly. This is what comes out the other side:
PS C:\test> c:\test\Bin\Debug\test.exe
Name @{N="Foo";E={"Bar"}}
---- --------------------
Add-Content
Add-History
Add-Member
What's wrong with how I'm setting up the Select-Object command parameters?
You can only pass associative arrays by name. It's better (more efficient) to pass regular arrays by name also. You would do something like eval echo "\${$1[$key]}" in the function, and pass in the name of the variable, without the $ .
Associative arrays are used to represent collections of data elements that can be retrieved by specifying a name called a key. D associative array keys are formed by a list of scalar expression values called a tuple.
The elements of an associative array can only be accessed by the corresponding keys. As there is not strict indexing between the keys, accessing the elements normally by integer index is not possible in PHP. Although the array_keys() function can be used to get an indexed array of keys for an associative array.
Associative arrays are used to store key value pairs. For example, to store the marks of different subject of a student in an array, a numerically indexed array would not be the best choice.
Creating a pipeline through c# and creating a pipeline with native powershell script have one major difference that is actually quite subtle: the parameter binder.
if I write a version of your code in pure script, I will get the same error: the hashtable literal is treated as a string value.
ps> $ps = $ps.Commands.Add("get-process")
ps> $ps = $ps.Commands.Add("select-object")
ps> $ps.Commands[1].Parameters.Add("Property", @("Name", '@{N="Foo";E={"Bar"}}'))
In this case, the command receives an array of two strings, the "name" and the hashtable literal string. This will be broken in exactly the same way as your C#. Now, take a look at the right way to do it (in script) - let me rewrite line 3:
ps> $ps.Commands[1].Parameters.Add("Property", @("Name", @{N="Foo";E={"Bar"}}))
So what changed? I removed the quotes around the hashtable -- I am passing a hashtable as the 2nd element of the object array! So, to get your C# example to work, you need to do what the parameter binder does for us at the command line (which is quite a lot!). Replace:
properties.Add("@{N=\"Foo\";E={\"Bar\"}}");
with
properties.Add(
new Hashtable {
{"N", "Foo"},
{"E", System.Mananagement.Automation.ScriptBlock.Create("\"Foo\"")}
}
);
I hope this clears it up for you. The parameter binder is probably the least visible but most powerful part of the powershell experience.
-Oisin
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With