9

I'm trying to run PowerShell scripts from my C# code, that will use custom Cmdlets from the assembly that runs them. Here is the code:

using System;
using System.Management.Automation;

[Cmdlet(VerbsCommon.Get,"Hello")]
public class GetHelloCommand:Cmdlet
{
    protected override void EndProcessing()
    {
        WriteObject("Hello",true);
    }
}

class MainClass
{
    public static void Main(string[] args)
    {
        PowerShell powerShell=PowerShell.Create();
        powerShell.AddCommand("Get-Hello");
        foreach(string str in powerShell.AddCommand("Out-String").Invoke<string>())
            Console.WriteLine(str);
    }
}

When I try to run it, I get a CommandNotFoundException. Did I write my Cmdlet wrong? Is there something I need to do to register my Cmdlet in PowerShell or in the Runspace or something?

x0n
  • 51,312
  • 7
  • 89
  • 111
Idan Arye
  • 12,402
  • 5
  • 49
  • 68

3 Answers3

9

The easiest way to do this with your current code snippet is like this:

using System; 
using System.Management.Automation; 

[Cmdlet(VerbsCommon.Get,"Hello")] 
public class GetHelloCommand:Cmdlet 
{ 
    protected override void EndProcessing() 
    { 
        WriteObject("Hello",true); 
    } 
} 

class MainClass 
{ 
    public static void Main(string[] args) 
    { 
        PowerShell powerShell=PowerShell.Create();

        // import commands from the current executing assembly
        powershell.AddCommand("Import-Module")
            .AddParameter("Assembly",
                  System.Reflection.Assembly.GetExecutingAssembly())
        powershell.Invoke()
        powershell.Commands.Clear()

        powershell.AddCommand("Get-Hello"); 
        foreach(string str in powerShell.AddCommand("Out-String").Invoke<string>()) 
            Console.WriteLine(str); 
    } 
} 

This assumes PowerShell v2.0 (you can check in your console with $psversiontable or by the copyright date which should be 2009.) If you're on win7, you are on v2.

x0n
  • 51,312
  • 7
  • 89
  • 111
5

Yet another simple way is to register cmdlets in a runspace configuration, create a runspace with this configuration, and use that runspace.

using System;
using System.Management.Automation;
using System.Management.Automation.Runspaces;

[Cmdlet(VerbsCommon.Get, "Hello")]
public class GetHelloCommand : Cmdlet
{
    protected override void EndProcessing()
    {
        WriteObject("Hello", true);
    }
}

class MainClass
{
    public static void Main(string[] args)
    {
        PowerShell powerShell = PowerShell.Create();
        var configuration = RunspaceConfiguration.Create();
        configuration.Cmdlets.Append(new CmdletConfigurationEntry[] { new CmdletConfigurationEntry("Get-Hello", typeof(GetHelloCommand), "") });
        powerShell.Runspace = RunspaceFactory.CreateRunspace(configuration);
        powerShell.Runspace.Open();

        powerShell.AddCommand("Get-Hello");
        foreach (string str in powerShell.AddCommand("Out-String").Invoke<string>())
            Console.WriteLine(str);
    }
}

Just in case, with this approach cmdlet classes do not have to be public.

Roman Kuzmin
  • 40,627
  • 11
  • 95
  • 117
  • Thanks. Is there a way to do this from inside the PowerShell? That is, to load a DLL using [Reflection.Assembly]::LoadFrom, and then call a function that loads the Cmdlets to the current session of PowerShell? – Idan Arye Sep 27 '11 at 11:57
  • I did not try that (just because did not have to). In PowerShell you can access the configuration of the default (more or less *current* but it depends) runspace as: `[System.Management.Automation.Runspaces.Runspace]::DefaultRunspace.RunspaceConfiguration`. Then try to call `.Cmdlets.Append(...)` on it. It would be nice if you try this and tell us the results. – Roman Kuzmin Sep 27 '11 at 12:22
  • 1
    I can't get it to work. It seems the Runspace won't load Cmdlets that way unless it's in the BeforeOpen state. Any way to load Cmdlets to an alreay open Runspace? – Idan Arye Sep 27 '11 at 19:56
1

You do need to register your cmdlet before you can use it in a Powershell session. You'll generally do that by way of a Powershell Snap-In. Here's a high-level overview of the process:

  • Create custom cmdlets (you've already done this part)
  • Create a Powershell Snap-in which contains and describes your cmdlets
  • Install the new Snap-in on your system using Installutil
  • Add the Snap-in to a specific Powershell session using Add-PSSnapin

There are a couple useful articles on MSDN that explain the process pretty thoroughly:

There's also a 2-part series on ByteBlocks that discusses writing custom cmdlets. That series may be your best bet, since you seem to have done the equivalent of part 1 already. You may be able to just use part 2 as a quick reference and be good to go.

ajk
  • 4,473
  • 2
  • 19
  • 24
  • 1
    A SnapIn is the powershell v1 way of doing things and is discouraged these days as it requires administrative privileges to register them. He should be using powershell v2 modules. – x0n Sep 26 '11 at 21:30
  • Doesn't really answer the question for what he is trying to do. – manojlds Sep 26 '11 at 21:30
  • @manojlds It does answer the question, but is definitely not as up to date or elegant as x0n's answer. – ajk Sep 26 '11 at 21:41
  • @x0n I upvoted your answer since it's much more elegant and has the added benefit of being self-contained. – ajk Sep 26 '11 at 21:42