Building Script Cmdlets as objects

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 are closed