Wednesday, October 29, 2014

PowerShell - Create CmdLet Hello World

How to create PowerShell CmdLet ?

What is PowerShell CmdLet learn at Microsoft site for CmdLet .

Create class library project.

Reference System.Management.Automation from  PowerShell folder.

Typically  
C:\Program Files (x86)\Reference Assemblies\Microsoft\WindowsPowerShell\3.0\System.Management.Automation.dll on x64 system

CmdLet is class inherited from PSCmdlet class  (System.Management.Automation namespace).

We need some attributes to describe our CmdLet, at least something like:

[Cmdlet(VerbsCommon.Remove, "Controller")]

Decorating class with this attribute will create Cmdlet which can be invoked by typing

Remove-Controller.

PowerShell CmdLet common Verbs and custom Verb

VerbsCommon class from System.Management.Automation contains set of common verbs i.e. Add, Clear,Get,Set and if you want to use verb which is not defined, attribute declaration will look like:

[Cmdlet("Specify", "Controller")]

This will create Cmdlet which can be invoked by typing

Specify-Controller.

CmdLet parameters

If you need parameters in your Cmdlet you will declare them as class properties decorated with Parameter attribute i.e.:

[Parameter(Mandatory = true)]
public int ControllerId {get;set;}

CmdLet ProcessRecord method

In order to perform some work when CmdLet is invoked we need to override method:

protected virtual void ProcessRecord(); 

and we will do it like:

protected override void ProcessRecord()
{
    WriteObject(String.Format("You have entered {0}", ControllerId));
    base.ProcessRecord();
}

Importing PowerShell Module

Start Windows PowerShell and import module by issuing command:

Import-Module pathtodll

If you used non-common verb (like in our example) you will receive warning

WARNING: The names of some imported commands from the module 'PowershellPlayground' include unapproved verbs that might  make them less discoverable. To find the commands with unapproved verbs, run the Import-Module command again with the Verbose parameter. For a list of approved verbs, type Get-Verb.

Your CmdLet is now available in this specific workspace. If you close Windows PowerShell window and open new, module will not be imported.

Let's run it by typing in PowerShell

Specify-Controller

As we marked ControllerId mandatory PowerShell will prompt:

cmdlet Specify-Controller at command pipeline position 1
Supply values for the following parameters:
ControllerId:

After you enter value you will have your value printed.

Optional CmdLet parameters

Let's change decoration of our parameter:

[Parameter(Mandatory = false)]
public int ControllerId { get; set; }

Rebuild. Import module to workspace and run Specify-Controller.

PowerShell will not prompt to enter parameter value and will print:

You have entered 0

Let's play but more and change parameter decoration once more:

[Parameter(Mandatory = false)]
public int? ControllerId { get; set; }

Rebuild, import module and run

Specify-Controller

Have in mind that rebuild will fail if you have PowerShell workspace with imported dll as PowerShell locks imported file.

PowerShell will print

You have entered

Let's run command with parameter specified:

Specify-Controller -ControllerId 2

Powershell will print:

You have entered 2

Write object from CmdLet

Let's create new class in our project

    public class Response
    {
        public int Number { get; set; }
        public DateTime TimeProcessed { get; set; }
        public string Machine { get; set; }
    }

and change ProcessRecord implementation

protected override void ProcessRecord()
        {
            var response = new Response
            {
                Number = ControllerId,
                TimeProcessed = DateTime.Now,
                Machine = Environment.MachineName
            };
            WriteObject(response);
            base.ProcessRecord();
        }

Rebuild, import and run

Specify-Controller -ControllerId 4

Response will be

Number       TimeProcessed                           Machine
------            -------------                                   -------
     4            29.10.2014 9:39:43                      LIGHTNING

Let's change our Response class

     public class Response
    {
        [DisplayName("Time processed")]
        public DateTime TimeProcessed { get; set; }
        public string Machine { get; set; }
        public int? Number { get; set; }
        public string Property1 { get; set; }
        public string Property2 { get; set; }
        public string Property3 { get; set; }
        public string Property4 { get; set; }
        public string Property5 { get; set; }
        public string Property6 { get; set; }
        public string Property7 { get; set; }
        public string Property8 { get; set; }
        public string Property9 { get; set; }
        public string Property10 { get; set; }
    }

Rebuild, import module and run

Specify-Controller -ControllerId 4

Result is:

TimeProcessed : 29.10.2014 9:45:41
Machine       : LIGHTNING
Number        : 4
Property1     :
Property2     :
Property3     :
Property4     :
Property5     :
Property6     :
Property7     :
Property8     :
Property9     :
Property10    :

We can see that display name is not affecting how PowerShell prints object. We can also see that order of properties will be same as order in which they are specified in class.

CmdLet Parameter from Pipeline

Let's create another Cmdlet

using System.Management.Automation;

namespace PowershellPlayground
{
    [Cmdlet(VerbsCommon.Get,"CalculatedValue")]
    public class ExampleCmdLet2 : PSCmdlet
    {
        [Parameter(Mandatory = true, Position = 1, ValueFromPipeline = true)]
        public Response response { get; set; }

        protected override void ProcessRecord()
        {
            response.Number += 10;
            WriteObject(response);
            base.ProcessRecord();
        }
    }  
}

We have defined one parameter of Response type, mandatory and we have indicated that we will be able to receive this value from pipeline. Rebuild, import module and run:

Specify-Controller -ControllerId 5

Response will be

TimeProcessed : 29.10.2014 9:54:55
Machine       : LIGHTNING
Number        : 5

Now run

Specify-Controller -ControllerId 5 | Get-CalculatedValue

Response will be:

TimeProcessed : 29.10.2014 9:55:11
Machine       : LIGHTNING
Number        : 15

Create PowerShell Snap-in

To create PowerShell Snap-in we need to add extra class to our project. Here's example:

using System.ComponentModel;
using System.Management.Automation;

namespace PowershellPlayground
{
    [RunInstaller(true)]
    public class PowerShellPlayground : PSSnapIn
    {
        public override string Description
        {
            get { return "Playing with PowerShell"; }
        }

        public override string Name
        {
            get { return "PlayingWithPowerShell"; }
        }

        public override string Vendor
        {
            get { return "MCo"; }
        }
    }
}

This class inherits abstract class PSSnapIn and overrides few properties talking about our Snap-in. Class is actual installer so it needs to be marked with RunInstaller(true).

Rebuild solution.

Installing PowerShell Snapin

In order to make PowerShell aware of our Snap-in we need to install it using installutil:

From PowerShell:

Set-Alias installutil C:\windows\Microsoft.NET\Framework64\v4.0.30319\InstallUtil.exe
installutil pathtodll 

Uninstalling PowerShell Snapin

From PowerShell:

Set-Alias installutil C:\windows\Microsoft.NET\Framework64\v4.0.30319\InstallUtil.exe
installutil -u pathtodll 

Check that Snap-In is installed

Execute PowerShell CmdLet

Get-PSSnapin -Registered

You should be able to see following entry

Name        : PlayingWithPowerShell
PSVersion   : 3.0
Description : Playing with PowerShell

Add PSSnapin to workspace

Execute PowerShell CmdLet:

Add-PSSnapin PlayingWithPowerShell

List commands available in CmdLet

Execute PowerShell CmdLet:

Get-Command -PSSnapin PlayingWithPowerShell

Get all command with specific verb

Execute PowerShell CmdLet:
Get-Command -Verb Specify

Read more about Get-Command at Microsoft site.

Get detailed information on CmdLet usage


Execute PowerShell CmdLet:

Get-Help Specify-Controller

Response will be:

NAME
    Specify-Controller

SYNTAX
    Specify-Controller [-ControllerId <int>]  [<CommonParameters>]


ALIASES
    None


REMARKS
    None

Override PowerShell CmdLet

You can override CmdLet by crating one with same name. We will override Get-WSManInstance CmdLet.

Let's create our CmdLet

using System.Management.Automation;

namespace PowershellPlayground
{
    [Cmdlet(VerbsCommon.Get, "WSManInstance")]
    public class GetWsManInstance : PSCmdlet
    {
     
        protected override void ProcessRecord()
        {          
            WriteObject("Hello World!");
            base.ProcessRecord();
        }
    }  
}

Rebuild but do not import.

Start PowerShell and run

Get-WSManInstance

PowerShell will respond with

cmdlet Get-WSManInstance at command pipeline position 1
Supply values for the following parameters:
ResourceURI: 

This is "default" implementation from Microsoft.WSMan.Management module.

Lets import our module:

Import-Module pathtodll

Run


Get-WSManInstance

And PowerShell will respond with

Hello World!

If you have multiple overrides of same CmdLet imported in PowerShell workspace last CmdLet imported will be one that will be executed.

No comments:

Post a Comment