views:

1432

answers:

2

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?

A: 

I found an ugly solution. In the code above replace this line

properties.Add("@{N=\"Foo\";E={\"Bar\"}}");

with this block

Dictionary<string, object> ht = new Dictionary<string, object>();
ht.Add("N", "Foo");
RunspaceInvoke invoke = new RunspaceInvoke();
ScriptBlock scriptblock = invoke.Invoke("{\"Bar\"}")[0].BaseObject as ScriptBlock;
ht.Add("E", scriptblock);
properties.Add(ht);

Please submit something cleaner or more correct than this.

xcud
I am posting the answer now.
x0n
+8  A: 

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

x0n
type on 'Mananagement', also "error CS0117: 'System.Management.Automation.ScriptBlock' does not contain a definition for 'Create'" -- other than that, works great
xcud
ah, you must be using v1 powershell (the create static is on v2). sorry!
x0n
That's what I figured. Solution accepted anyway. I adapted what you provided to get what I needed and applied it to the real app. Works great. Best ever.
xcud