Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to capture a Powershell CmdLet's verbose output when the CmdLet is programmatically Invoked from C#

BACKGROUND

  • I am using Powershell 2.0 on Windows 7.
  • I am writing a cmdlet in a Powershell module ("module" is new to Powershell 2.0).
  • To test the cmdlet I am writing Unit tests in Visual Studio 2008 that programmatically invoke the cmdlet.

REFERENCE

  • This Article on MSDN called "How to Invoke a Cmdlet from Within a Cmdlet" shows how to call a cmdlet from C#.

THE SOURCE CODE

  • This is a distilled version of my actual code — I've made it as small as possible so that you can see the problem I am having clearly:

    using System;
    using System.Management.Automation;   
    
    namespace DemoCmdLet1
    {
        class Program
        {
            static void Main(string[] args)
            {
                var cmd = new GetColorsCommand();
    
                    foreach ( var i in cmd.Invoke<string>())
                    {
                       Console.WriteLine("- " + i );   
                    } 
               } 
           } 
    
        [Cmdlet("Get", "Colors")]
        public class GetColorsCommand : Cmdlet
        {
            protected override void ProcessRecord()
            {
                this.WriteObject("Hello");
                this.WriteVerbose("World");
            }
    
        }
    }
    

COMMENTS

  • I understand how to enable and capture verbose output from the Powershell command line; that's not the problem.
  • In this case I am programmatically invoking the cmdlet from C#.
  • Nothing I've found addresses my specific scenario. Some articles suggest I should implement my own PSHost, but seems expensive and also it seems like a have to call the cmdlet as text, which I would like to avoid because that is not as strongly typed.

UPDATE ON 2009-07-20

Here is is the source code based on the answer below.

Some things are still not clear to me: * How to call the "Get-Colors" cmdlet (ideally without having to pass it as a string to the ps objet) * How to get the verbose output as it is generated instead of getting an collection of them at the end.

    using System;
    using System.Management.Automation;   

    namespace DemoCmdLet1
    {
        class Program
        {
            static void Main(string[] args)
            {
                var ps = System.Management.Automation.PowerShell.Create();

                ps.Commands.AddScript("$verbosepreference='continue'; write-verbose 42");

                foreach ( var i in ps.Invoke<string>())
                {
                   Console.WriteLine("normal output: {0}" , i );   
                }
                foreach (var i in ps.Streams.Verbose)
                {
                    Console.WriteLine("verbose output: {0}" , i);
                }

            }
        }

        [Cmdlet("Get", "Colors")]
        public class GetColorsCommand : Cmdlet
        {
            protected override void ProcessRecord()
            {
                this.WriteObject("Red");
                this.WriteVerbose("r");
                this.WriteObject("Green");
                this.WriteVerbose("g");
                this.WriteObject("Blue");
                this.WriteVerbose("b");

            }

        }
    }

The code above generates this output:

d:\DemoCmdLet1\DemoCmdLet1>bin\Debug\DemoCmdLet1.exe
verbose output: 42

UPDATE ON 2010-01-16

by using the Powershell class (found in System.Management.Automation but only in the version of the assembly that comes with the powershell 2.0 SDK, not what comes out-of-the-box on Windows 7) I can programmatically call the cmdlet and get the verbose output. The remaining part is to actually add a custom cmdlet to that powershell instance - because that was my original goal - to unit test my cmdlets not those that come with powershell.

class Program
{
    static void Main(string[] args)
    {
        var ps = System.Management.Automation.PowerShell.Create();
        ps.AddCommand("Get-Process");
        ps.AddParameter("Verbose");
        ps.Streams.Verbose.DataAdded += Verbose_DataAdded;
        foreach (PSObject result in ps.Invoke())
        {
            Console.WriteLine(
                    "output: {0,-24}{1}",
                    result.Members["ProcessName"].Value,
                    result.Members["Id"].Value);
        } 
        Console.ReadKey();
    }

    static void Verbose_DataAdded(object sender, DataAddedEventArgs e)
    {
        Console.WriteLine( "verbose output: {0}", e.Index);
    }
}


[Cmdlet("Get", "Colors")]
public class GetColorsCommand : Cmdlet
{
    protected override void ProcessRecord()
    {
        this.WriteObject("Hello");
        this.WriteVerbose("World");
    }
}
like image 542
namenlos Avatar asked Jul 18 '09 02:07

namenlos


People also ask

What is Commandlets in PowerShell?

A cmdlet is a lightweight command that is used in the PowerShell environment. The PowerShell runtime invokes these cmdlets within the context of automation scripts that are provided at the command line. The PowerShell runtime also invokes them programmatically through PowerShell APIs.

What is the structure of a cmdlet?

PowerShell Commands, also called Commandlets or Cmdlets, are the smallest unit of the PowerShell scripting language, consisting of a verb, a noun and a parameter list. PowerShell Commands are based on structural verb-noun name conventions, same goes for their derived Microsoft .

What are two important characteristics of a cmdlet?

The building blocks of Powershell: cmdlets The three most important characteristics are: Cmdlets output results as an object or as an array of objects. Cmdlets can get data for analysis or transfer data to another cmdlet using pipes. Cmdlets are case-insensitive.


1 Answers

  • Verbose output is not actually output unless $VerbosePreference is set at least to "Continue."
  • Use the PowerShell type to run your cmdlet, and read VerboseRecord instances from the Streams.Verbose propery

Example in powershell script:

ps> $ps = [powershell]::create()
ps> $ps.Commands.AddScript("`$verbosepreference='continue'; write-verbose 42")
ps> $ps.invoke()
ps> $ps.streams.verbose
Message   InvocationInfo                          PipelineIterationInfo
-------   --------------                          ---------------------
42        System.Management.Automation.Invocat... {0, 0}

This should be easy to translate into C#.

like image 177
x0n Avatar answered Sep 28 '22 06:09

x0n