Using Dynamics To Call PowerShell in C#

In PowerShell 3.0 all PSObjects are now based on the dynamic language runtimeDynamic Runtime Library. Joel Bennett has a great article about what that means to PowerShell. For a C# developer this is also a pretty neat. In the past this is how PSObjects were accessed in C#.

foreach(var process in PowerShell.Create().AddCommand("Get-Process").Invoke())
{
Console.WriteLine(process.Properties["ProcessName"].Value);
}

This is pretty straight forward, but it not very elegant code. I took some time to study what it meant to be able to access objects, generated by PowerShell, using the DLR in C#. The above C# code could be modified as follows.

foreach(dynamic process in PowerShell.Create().AddCommand("Get-Process").Invoke())
{
Console.WriteLine(process.ProcessName);
}

Executing the above code using the PowerShell 3 CTP2 run time will yield the same result as the first sample. I wanted to take it a step further and make PowerShell “feel” a little bit more integrated into C#. I created a new class that extends DynamicObject. With a couple tricky operator overloads I can use the DLR to offer syntax like this.

dynamic ps = new PowerShellDynamicPipeline();

foreach (var process in ps.Get_Process | ps.Where_Object.Property("StartTime").Gt().Value(DateTime.Now.AddMinutes(-600)) | ps.Measure_Object)
{
       Console.WriteLine(process.Count);
}

Rather than using strings to specify the cmdlet names and parameters, we can instead utilize the dynamic class to offer a bit more of a “typed” feel to the pipeline. It also enables the use of the pipeline operator (“|”).  In addition to iterating over objects written to the pipeline, it is also possible to simply execute the pipeline without expecting objects returned.

dynamic ps = new PowerShellDynamicPipeline();
ps.Start_Process.FilePath("Notepad").ExecutePipeline();

The PowerShellDynamicPipeline is a pretty simple class. I’ve included the contents of it below. It currently doesn’t handle errors well and could be extended to be more robust. I’m just exited this this type of thing is now possible! It’s starting to make it much easier to work with PowerShell in the CLR.

using System.Collections;
using System.Collections.Generic;
using System.Dynamic;
using System.Linq;
using System.Management.Automation;
using System.Management.Automation.Runspaces;

namespace PowerShellIntegration
{
    class PowerShellDynamicPipeline : DynamicObject, IEnumerable
    {
        private Runspace r;
        private string _currentCmdlet;
        private IDictionary<string, IDictionary<string, object>> pipeLine;

        public PowerShellDynamicPipeline()
        {
            pipeLine= new Dictionary<string, IDictionary<string, object>>();
            r = RunspaceFactory.CreateRunspace();
        }

        public override bool TryGetMember(GetMemberBinder binder, out object result)
        {
            _currentCmdlet = binder.Name.Replace("_", "-");
            pipeLine.Add(_currentCmdlet, new Dictionary<string, object>());

            result = this;
            return true;
        }

        public override bool TryInvokeMember(InvokeMemberBinder binder, object[] args, out object result)
        {
            if (binder.Name == "GetEnumerator")
            {
                IEnumerable<dynamic> val = ExecutePipeline();
                result = val.GetEnumerator();
                return true;
            }

            pipeLine[_currentCmdlet].Add(binder.Name, args.FirstOrDefault());
            result = this;
            return true;
        }

        public IEnumerable<dynamic> ExecutePipeline()
        {
            var ps = PowerShell.Create();

            if (r.RunspaceStateInfo.State == RunspaceState.BeforeOpen)
            {
                r.Open();
            }

            ps.Runspace = r;

            foreach (var cmdlet in pipeLine)
            {
                ps.AddCommand(cmdlet.Key);

                foreach (var argument in cmdlet.Value)
                {
                    if (argument.Value != null)
                    {
                        ps.AddParameter(argument.Key, argument.Value);    
                    }
                    else
                    {
                        ps.AddParameter(argument.Key);    
                    }
                    
                }
            }

            pipeLine = new Dictionary<string, IDictionary<string, object>>();

            IEnumerable<dynamic> val = ps.Invoke();
            return val;
        }

        public IEnumerator GetEnumerator()
        {
            IEnumerable<dynamic> val = ExecutePipeline();
            return val.GetEnumerator();
        }

        public static PowerShellDynamicPipeline operator |(PowerShellDynamicPipeline c1, PowerShellDynamicPipeline c2)
        {
            return c1;
        }
    }
}
You can leave a response, or trackback from your own site.

20 Responses to “Using Dynamics To Call PowerShell in C#”

  1. [...] Using Dynamics To Call PowerShell in C# (Adam Driscoll) [...]

  2. James Manning says:

    WRT:

    … In PowerShell 3.0 all PSObjects are now based on the Dynamic Runtime Library …

    Was that meant to be Dynamic Language Runtime? Or is there a particular library you’re referring to outside of the DLR?

  3. James Manning says:

    One more typo: Joel’s last name ends with 2 t’s :)

    http://huddledmasses.org/author/jaykul/

    Joel ‘Jaykul’ Bennett

    • adamdriscoll says:

      Thanks again!

      I need to be more careful with peoples’ names. I’m going to get myself in trouble. :P I accidentally wrote “Jeff Wilson” once when I had intended to mention Jeff Hicks and Ed Wilson…

  4. Chris Morrow says:

    Hi Adam,

    I’m enjoying the article, and kicking the tires. One thing I’m having issues with is importing a module at the right time.

    I’ve added an import-module … script and invoked it before any of the other stuff on the powershell object runs, but nothing will return after that. No errors or exceptions just no output.

    Have you tried any modules that are not native to windows by default.

    Thanks in advance.
    Chris

  5. adamdriscoll says:

    Hey Chris,

    You’re going to want to check the error stream. It is part of the PowerShell class. If you look in the ExecutePipeline method, right before you return you could do something like:

    foreach(var error in ps.Streams.Error)
    {
    Console.WriteLine(error.ToString());
    }

    This should give you a bit more information into why your module isn’t loading and why commands no longer work.

    Hope this helps,

    Adam

    • Chris Morrow says:

      Hi Adam,

      I forgot to reply. It has something to do with the module I am trying to import, and has some issues around execution policy which is odd.

      I’m working through it with the provider.

      After I peeled back the layers and started working with the base powershell classes it was much easier than I thought, and to your point the errors are sitting in the pipeline.

      Thanks for the help, and have a great day.
      Chris

  6. Andy Grogan says:

    Hiya Adam, this is really useful thank you for taking the time to publish it. I was wondering how I can check for a null value using:

    foreach (var exName in ps.Get_MailboxServer | ps.Where_Object.Property(“DatabaseAvailabilityGroup”).ne().Value(null)){
    // Do something

    }

    I have tried variations of null and “$null” but still no joy – I think that I am missing something very simple but for the life of me I don’t know what. Would you be able to help?
    Thanks
    Andy

    • adamdriscoll says:

      That’s interesting. I hadn’t dealt with that when I was toying with this. Let me crack open the project tonight and see if I can figure out anything.

      • Andy Grogan says:

        Nice one – thanks Adam! I have found a work around in the code that I was writing, but it is not as elegant as being able to check for a null result in the pipeline so anything that you can offer would be good. Just for information – I have double checked in Native PowerShell that the actual test returns a $null – by using the simple cmdlet Get-MailboxServer | Where {$_.DatabaseAvailabiltyGroup -ne $null} which returns the Server Names that I would expect.

        Thanks
        A

        • adamdriscoll says:

          Hey Andy,

          The trick is to adjust the dynamic object. It’s keying off the fact that the value is null. If it is, it doesn’t set the parameter’s value. Here are the changes I made to make it work.

          public IEnumerable ExecutePipeline()
          {
          var ps = PowerShell.Create();

          if (r.RunspaceStateInfo.State == RunspaceState.BeforeOpen)
          {
          r.Open();
          }

          ps.Runspace = r;

          foreach (var cmdlet in pipeLine)
          {
          ps.AddCommand(cmdlet.Key);

          foreach (var argument in cmdlet.Value)
          {
          if (argument.Value == “null”)
          {
          ps.AddParameter(argument.Key, null);
          }
          else if (argument.Value != null)
          {
          ps.AddParameter(argument.Key, argument.Value);
          }
          else
          {
          ps.AddParameter(argument.Key);
          }

          }
          }

          pipeLine = new Dictionary>();

          IEnumerable val = ps.Invoke();
          return val;
          }

          I then invoked it like this.

          dynamic ps = new PowerShellDynamicPipeline();
          foreach (var exName in ps.Get_Variable | ps.Where_Object.Property(“Value”).eq().Value(“null”))
          {
          Console.WriteLine(exName.Name);
          }

          I hope that helps!

          Adam

          • Andy Grogan says:

            Adam, sorry for the delay in getting back to you. I just wanted to say that I really appreciate your efforts above. Works perfectly. Once again, thank you for a brilliant and really useful post.
            Many thanks
            A

          • adamdriscoll says:

            Great! Glad it worked!

  7. Andy Grogan says:

    Hiya Adam, one final question if you have time. I am trying to select multiple property values using the pipeline – for example;

    foreach (var entry in dagNetPipe.Get_DatabaseAvailabilityGroupNetwork | dagNetPipe.Select.ExpandProperty(“Subnets Name”))

    Which corresponds to the following PowerShell example;

    Get-DatabaseAvailabilityGroupNetwork | select -expandProperty Subnets Name

    The dynamic pipeline does not seem to like it – I have been scratching my head for a few days on this – so any suggestions would be really cool, and I promise that I will leave you alone.
    Cheers
    A

    • adamdriscoll says:

      Hey Andy,

      I think you might be running into problems because ExpandProperty is only intended to take a single property. It seems to work fine for me if I use the Property parameter.

      foreach (var exName in ps.Get_Process | ps.Select_Object.Property(new [] {“ProcessName”, “Id”}))
      {
      Console.WriteLine(exName.ProcessName);
      }

      Don’t worry about leaving me alone. I love this stuff!

      Adam

  8. Andy Grogan says:

    Hiya Adam, many thanks again for the response – appreciated again. I have to confess I ended up looking at the problem from a different angle and managed to get what I needed using the pipeline as provided. What threw me was that in the Exchange Management Shell I was able to get both the name and the expanded property set.
    Anyhow – thanks for the assistance.
    Cheers
    A

  9. A greate job and idea!
    So cool it is!
    In fact, I firstly learn DyanmicObject from your blog!
    Thanks!

  10. herb says:

    i’m needing to use PowerShell for a project am working on but i’m not seeing the big picture yet.
    I saw your posting and thought this would be helpful. do you have a sample that could use your class? i’m working with “failovercluster” and i’m at the beginning of my project and can’t get my head around what is going on.

Leave a Reply

In an effort to prevent automatic filling, you should perform a task displayed below.



eight + = 17