Archive for the “V2 CTP2” Category

One feature of PowerShell V2 that is being touted by many developers and hard core scripters is Script Cmdlets. This is essentially the ability to build full blown cmdlets natively in PowerShell. You will no longer have to venture into the world of C# or VB .NET to build a  real Cmdlet. I’ve been working on a Cmdlet in C# over the last week or so and up until then I don’t think I was able to fully appreciate the power that is coming with Script Cmdlets. Now that I have seen what you can do in C# and really how ridiculously easy it is to do, I can’t wait to start developing Cmdlets in PowerShell.

One of the most frustrating problems in Version 1 was having to deal with parameters. Specifically, figuring out how to get input from the pipeline if its available, and from specifying the parameter name directly. Script Cmdlets solve this problem beautifully!

When developing a cmdlet in C#, you use something called attributes that get tacked on to the parameter that is being declared. Just as an example, lets say we were writing a cmdlet to create a new Hyper-V Virtual Machine. When you create a new VM, you want to be able to specify the number of CPU’s.

You can do this with the following code:

[Parameter(
            Position = 2,
            Mandatory = false,
            ValueFromPipeline = true,
            ValueFromPipelineByPropertyName = true,
            HelpMessage = "Please enter the number of Processors to add to the VM (1-4)"
            )]
        [Alias("CPU")]
        [ValidateRange(1, 4)] //can only have up to 4 processors
        public int NumberOfCpus { get; set; }

In this code, there is a bunch of things going on, but they are fairly self-explanatory if you dig in to them. First of all, the actual parameter is just an integer named NumberOfCpus that is being declared at the very bottom. All the attributes are declared above the object that they are tied to.

In this case I have added the Parameter attribute with a few options. The first is that this is this parameter is the second parameter if no name is specified. The second is saying that this parameter is not required, by declaring "Mandatory" to be false.

The next two options are the ones that solve the pipeline problem. I can’t tell you how great this will be in V2. Just by declaring these options, you can grab objects from the pipeline and inject them into the argument of your parameter.

There is a reason there are two options here. The first one is used for basic pipeline input. Take, for example, get-content servers.txt | new-vm. If there was a parameter called Name that accepted values from the pipeline, that would work.

The ValueFromPipelineByPropertyName is a little more tricky. This is the reason this command works

get-process notepad | get-childitem

image

 

This is because get-process notepad produces a "Process Object" which has a property called path. When that object gets sent to get-childitem, get-childitem has a parameter called path and PowerShell links the two up and moves on.

The last couple options are pretty self explanatory, but worth mentioning. You get interactive help for free, just by declaring the HelpMessage string, and you can do all kinds of validations on the input. Here I have specified a range of valid numbers for the Number of CPU’s, so someone doesn’t try to create a VM with 197 processors.

It is truly amazing how much plumbing that the PowerShell engine does that we can use for free. Its gonna be awesome. Oh yeah, and then distributing and sharing these things within the community is going to be orders of magnitude easier with the advent of modules.

Comments 1 Comment »

I have been using CTP2 on my computers for sometime and I recently came across a situation where I needed a function that would easily accept parameters from the pipeline and also as a standard parameter. This was the perfect excuse to start playing with Script Cmdlets. These things have all kinds of cool attributes that you can use to make your scripts easier to use, which is all wonderfulness.

So I cracked open Powershell and started playing. It took me a little while but I figured out how to convert my function over to a ScriptCmdlet.

But there was a problem. I had to read lots of documentation to figure it out. What I love about Powershell is how it is so discoverable with use of get-member.

Buried in Powershell is something called a CommandInfo object. This object describes a command and what it can do. Wouldn’t it be cool if these objects had information about the parameters, whether or not they accepted values from the pipeline, and what position they were in. The list of options goes on and on.

I think the best way to explain is to show some code that could exist.

   1: # Build Up A parameter object
   2: $param.Name = "File"
   3: $param.HelpMessage = "Please Enter a file name"
   4: $param.acceptsValueFromPipeline = $TRUE
   5:  
   6: # Build a block that will go into the processBlock of the ScriptCmdlet
   7: $scriptblock = "Get-Content `$File"
   8:  
   9: #Build The CmdLet
  10: $cmdlet.verb = "Get"
  11: $cmdlet.noun = "DemoTextFile"
  12: $cmdlet.p1 = $param # p1 is short for parameter1 in the scriptCmdlet
  13: $cmdlet.beginBlock = "write-host `"Beginning`""
  14: $cmdlet.processBlock = $scriptblock
  15: $cmdlet.endBlock = "write-host `"Ending`""
  16: $cmdlet.Write()

At the very end, the $cmdlet.write() method would write the code for you.

Well, now here comes the fun part (With warnings)

This code was written hacked together and is absolutely not guaranteed to work and I cannot be held liable if it eats your cat or kills your computer. The purpose here is to really see if there would be any interest in furthering this. Eventually i would like to see these properties get added to the System.Management.Automation.FunctionInfo class, or create a new class that inherits from FunctionInfo.

First, we can use Add-Type to create new types with inline C#

   1: Add-Type @"
   2: namespace Getpowershell {
   3:     public class Scriptcmdlet {
   4:         public string noun;
   5:         public string verb;
   6:         public string processBlock;
   7:         public string beginBlock;
   8:         public string endBlock;
   9:         public Getpowershell.Parameter p1;
  10:         public Getpowershell.Parameter p2;
  11:  
  12:         public string Write() {
  13:  
  14:             System.Text.StringBuilder sb = new System.Text.StringBuilder();
  15:             sb.Append("Cmdlet " + this.verb + "-" + this.noun + " { \n");
  16:             sb.Append("param (\n");
  17:             sb.Append("[Parameter(\n");
  18:             if (this.p1.acceptsValueFromPipeline == true) { sb.Append("ValueFromPipeline=`$true," +"\n"); }
  19:             if (this.p1.Mandatory == true) { sb.Append("Mandatory,\n"); }
  20:             sb.Append("HelpMessage=\"" + this.p1.HelpMessage + "\"]\n");
  21:             sb.Append("$" + this.p1.Name);
  22:             sb.Append(")\n");
  23:             sb.Append("Begin { \n" + this.beginBlock + "\n}\n");
  24:             sb.Append("Process { \n" + this.processBlock + "\n}\n");
  25:             sb.Append("End { \n" + this.endBlock + "\n}\n}\n");
  26:             return sb.ToString();
  27:         }
  28:     }
  29:     public class Parameter {
  30:             public string Name;
  31:             public bool acceptsValueFromPipeline;
  32:             public string HelpMessage;
  33:             public bool Mandatory;
  34:         }
  35:     
  36: }
  37: "@

Now I can do this code over again but I need to set up some objects with my new types first.

   1: $cmdlet = New-Object Getpowershell.Scriptcmdlet
   2: $param = New-Object Getpowershell.Parameter
   3:  
   4: # Build Up A parameter object
   5: $param.Name = "File"
   6: $param.HelpMessage = "Please Enter a file name"
   7: $param.acceptsValueFromPipeline = $TRUE
   8:  
   9: # Build a block that will go into the processBlock of the ScriptCmdlet
  10: $scriptblock = "Get-Content `$File"
  11:  
  12: #Build The CmdLet
  13: $cmdlet.verb = "Get"
  14: $cmdlet.noun = "DemoTextFile"
  15: $cmdlet.p1 = $param # p1 is short for parameter1 in the scriptCmdlet
  16: $cmdlet.beginBlock = "write-host `"Beginning`""
  17: $cmdlet.processBlock = $scriptblock
  18: $cmdlet.endBlock = "write-host `"Ending`""
  19: $cmdlet.Write()

In theory, you can run $cmdlet.write() and it will echo out the text for a new Script Cmdlet..

Here is some output from get-member on $cmdlet and $param

   1: PS C:\Users\andys\Desktop> $cmdlet | fl *
   2:  
   3:  
   4: noun         : DemoTextFile
   5: verb         : Get
   6: processBlock : Get-Content $File
   7: beginBlock   : write-host "Beginning"
   8: endBlock     : write-host "Ending"
   9: p1           : Getpowershell.Parameter
  10: p2           :
  11:  
  12:  
  13:  
  14: PS C:\Users\andys\Desktop> $param | fl *
  15:  
  16:  
  17: Name                     : File
  18: acceptsValueFromPipeline : True
  19: HelpMessage              : Please Enter a file name
  20: Mandatory                : False
  21:  
  22:  
  23:  
  24: PS C:\Users\andys\Desktop> $cmdlet.Write()
  25: Cmdlet Get-DemoTextFile {
  26: param (
  27: [Parameter(
  28: ValueFromPipeline=$true,
  29: HelpMe
ssage="Please Enter a file name"]
  30: $File)
  31: Begin {
  32: write-host "Beginning"
  33: }
  34: Process {
  35: Get-Content $File
  36: }
  37: End {
  38: write-host "Ending"
  39: }
  40: }
  41:  
  42:  
  43: PS C:\Users\andys\Desktop>

Known issues:

I have no idea if the Mandatory option will produce proper code for the Cmdlet

Right now it will only support adding one parameter which is the p1 property of the $cmdlet object

Comments No Comments »

 

With CTP2, one of the biggest features is remoting. This works really well when you are running native cmdlets and scriptblocks in runspaces with multiple computers.

For example

function q {$args}
$rs = New-Runspace (q powershell-dev1 powershell-dev2 powershell-dev3)
Invoke-Command -ScriptBlock {hostname} -Runspace $rs

# This will create the following output
PS C:\Users\andys> Invoke-Command -ScriptBlock {hostname} -Runspace $rs
powershell-dev2
powershell-dev3
powershell-dev1

Pretty cool, and you can use the computername property to find out which computer returned which object.

But with commands like ipconfig or netsh that are not native to Powershell, just tacking on the computername property is a little difficult. When you pipe everthing to select-object, format-table, or format-list, which properties do you select.

Under normal circumstances, with say something like Get-Process, you could pipe it to Format-Table Id, Name, WorkingSet.

But with native commands, there are no properties. So here’s what you can do.

You can pipe it to select-object and select the whole object using $_ and then also select  $_.ComputerName. The only trick is you have to pass the properties in as scriptblocks,


PS C:\Users\andys> Invoke-Command -ScriptBlock {ipconfig} -Runspace $rs |

 select {$_} ,{$_.ComputerName}

$_                                                     $_.ComputerName
--                                                     ---------------
                                                       powershell-dev3
Windows IP Configuration                               powershell-dev3
                                                       powershell-dev3
                                                       powershell-dev3
Ethernet adapter Local Area Connection:                powershell-dev3
                                                       powershell-dev3
   Connection-specific DNS Suffix  . : example.com.... powershell-dev3
   Link-local IPv6 Address . . . . . : fe80::4c04:b... powershell-dev3
   IPv4 Address. . . . . . . . . . . : 10.2.31.214     powershell-dev3
   Subnet Mask . . . . . . . . . . . : 255.255.254.0   powershell-dev3
   Default Gateway . . . . . . . . . : 10.2.30.1       powershell-dev3
                                                       powershell-dev3
Tunnel adapter Local Area Connection* 8:               powershell-dev3
                                                       powershell-dev3
   Media State . . . . . . . . . . . : Media discon... powershell-dev3
   Connection-specific DNS Suffix  . : corp.avanade... powershell-dev3
                                                       powershell-dev2
Windows IP Configuration                               powershell-dev2
                                                       powershell-dev2
                                                       powershell-dev2
Ethernet adapter Local Area Connection:                powershell-dev2

The output is not incredibly wonderful but you can at least easily know which remote computer returned which line of text from the command you executed.

Comments No Comments »