Application Security Testing: An Integral Part of DevOps
Windows PowerShell not only offers a powerful shell and scripting language, but also an easily extensible interface. PowerShell introduced an object-oriented perspective, specifically a .NET perspective, to shells and scripting. Because PowerShell is deeply integrated with the .NET Framework, it is relatively simple to extend the shell and language by creating custom cmdlets.
In this article, I am going to explore how to create a custom PowerShell cmdlet using Visual Basic 2005.
What Makes a Cmdlet?
Cmdlets are .NET classes that derive from the Cmdlet base class in the the PowerShell Software Developers Kit (SDK). In keeping with the PowerShell way of naming commands, cmdlets should always have names of the form Verb-Noun.
Which .NET Language?
Most custom cmdlet examples are written in C#. I suppose this is because PowerShell was written in C#, has a similar grammar, and many programmers who want to write cmdlets prefer C#. I searched the Internet extensively, but I could not find a single cmdlet written in Visual Basic. Now that I have said that, I am sure someone will send me a link to a VB cmdlet example. That said, in the remainder of this article, I will attempt to create a basic PowerShell cmdlet using Visual Basic.
Naming a Custom Cmdlet
For my simple example, I am going to create a cmdlet that gets the amount of total physical memory in the computer and returns the value in bytes. Now that I know what my cmdlet will do, I need to decide on an appropriate name.
A cmdlet name should use the Verb-Noun form, where Verb describes the action the cmdlet performs and Noun describes the object the cmdlet is acting upon. PowerShell has a list of approved cmdlet verb names that should be used when choosing a cmdlet name. In rare cases, a verb not on the approved list might be needed, but in most cases the cmdlet verb should come from the approved list. By selecting verbs from the approved list, consistency is maintained across native PowerShell commands and other custom cmdlets. After examining the list, "Get" seems to be the most appropriate verb for my cmdlet.
The Noun portion of the name should be as specific as possible. Because I want to return the amount of total physical memory, "TotalPhysicalMemory" is a good noun. Putting this with the verb I selected yields "Get-TotalPhysicalMemory" for the cmdlet name.
Creating the Cmdlet Class
Now that I have a name for my cmdlet, I need to create a VB class to implement it. The code for a cmdlet is amazingly small and simple. Here is the entire code for my cmdlet class:
<Cmdlet(VerbsCommon.Get, "TotalPhysicalMemory")> _ Public Class GetTotalPhysicalMemoryCommand Inherits Cmdlet Protected Overrides Sub EndProcessing() WriteObject(My.Computer.Info.TotalPhysicalMemory, False) End Sub End Class
A total of eight lines, and one is blank! Now, admittedly this is a very simple cmdlet that just return the number of bytes of total physical memory. My cmdlet does not accept any input from the pipeline, so I do not have to process any incoming objects. I merely return the number of bytes of total physical memory; this is easily retrieved from the Visual Basic My namespace.
The first line is the Cmdlet attribute that identifies this class as a cmdlet and specifies the verb and noun for my cmdlet. The Cmdlet attribute is required because it enables the PowerShell runtime to call the cmdlet correctly.
Next is the actual class definition. Microsoft recommends reflecting the cmdlet name in the class name using the the form "VerbNounCommand", replacing "Verb" and "Noun" with the verb and noun from the cmdlet name. Therefore, my cmdlet class name is "GetTotalPhysicalMemoryCommand".
All cmdlets inherit from the Cmdlet or PSCmdlet base class. Custom cmdlets that derive from Cmdlet do not depend on the PowerShell runtime and can be used directly from a .NET language. PSCmdlet is the base class for derived cmdlets that depend on the Windows PowerShell runtime. My cmdlet does not depend on anything in the PowerShell runtime, so my class inherits from the Cmdlet base class.
The Cmdlet base class provides three input processing methods, and a custom cmdlet must override at least one of them. The three input processing methods are: BeginProcessing, ProcessRecord, and EndProcessing. BeginProcessing should be overridden if the custom cmdlet needs to perform some preprocessing or setup. ProcessRecord must be overridden if the custom cmdlet needs to accept input from the pipeline. Finally, EndProcessing should be overridden if the cmdlet does not accept input from the pipeline. Because my cmdlet does not have any preprocessing or setup to perform and takes no input from the pipeline, I only need to override the EndProcessing method.
When a cmdlet needs to produce output, the WriteObject method should be used. The WriteObject method takes two parameters: an object to be outputted and a boolean that specifies whether the object should be enumerated if it is a collection. My cmdlet is just returning a single number, therefore my call to WriteObject specifies the object as My.Computer.Info.TotalPhysicalMemory and I pass "False" because it is not a collection.
Now that I have created my cmdlet, I need to implement a PowerShell snap-in to provide access to my cmdlet in PowerShell. The PowerShell SDK provides two base classes for snap-ins, the PSSnapIn class and the CustomPSSnapIn class. A snap-in derived from PSSnapIn registers all the cmdlets and providers in an assembly, and a snap-in derived from CustomPSSnapIn can be used to register a specific list of cmdlets and providers. For my purposes, a class derived from the PSSnapIn class is fine.
I am not going to delve into the details of the PSnapIn class, but more information is available.
Installing the Snap-In
The first thing I need to do is compile the code. This produces a file named GetSystemInfoPSSnapIn.dll.
Now that I have the .dll file, I need to install and register the snap-in in the PowerShell runtime. To install the GetSystemInfoPSSnapIn.dll file, I use the .NET Framework Installation utility. This utility is located in the C:\Windows\Microsoft.NET\Framework\v2.0.50727 folder. You can use the installation utility to install the custom snap-in through the PowerShell prompt: "InstallUtil.exe GetSystemInfoPSSnapIn.dll", as seen in Figure 1.
A bunch of text will scroll by, but the last few lines are where you want to look. If the installation completed successfully, you should see something similar to Figure 2.
To verify the snap-in was successfully registered, you can run the Get-PSSnapin command and use the -Registered parameter to only list snap-ins that are not installed with PowerShell. Figure 3 shows the Get-PSnapIn command and the results of running it.
Figure 3: Get-PSSnapin -Registered
Adding the Snap-In to the Shell
We now have the snap-in installed and registered with PowerShell, but to actually use the cmdlets we need to add the snap-in to the shell. The Add-PSSnapin command will do this. Figure 4 shows how to use the Add-PSSnapin command.
Figure 4: Add-PSSNapin GetSystemInfoPSSnapIn
Using the Cmdlet
Finally, you get to actually run the cmdlet. Remember, I named the cmdlet Get-TotalPhysicalMemory, so to run it you can type Get-TotalPhysicalMemory at the PowerShell prompt. You could also type Get-T and press and the the shell will automatically expand the cmdlet name. Figure 5 shows running the cmdlet and the output from it.
Removing and Uninstalling the Snap-In
Removal of the snap-in is just as easy as the installation. First, you need to remove the snap-in from the the shell by using the Remove-PSSnapin command. Figure 6 shows the Remove-PSSnapin command and the results of running it.
Figure 6: Remove-PSSnapin GetSystemInfoPSSnapIn
The Remove-PSSnapin command removed the snap-in from the shell, but you still need to uninstall the .dll file to complete the removal. Use the .NET Framework Installation utility with the "-u" parameter to uninstall the .dll file. Figure 7 and Figure 8 show the InstallUtil utility and the results of running it with the "-u" parameter.
Figure 8: InstallUtil.exe -u GetSystemInfoPSSnapIn.dll
When I began researching this article, I was afraid that creating a custom cmdlet was going to be a difficult process. Much to my surprise, it was very simple and required only a small amount of code. Given the dearth of VB sample code that utilizes PowerShell, I hope that this article and my custom cmdlet will assist other VB programmers in creating some great custom PowerShell cmdlets.
About the Author
Josh Fitzgerald is an applications development group leader for a large medical device company in Warsaw, Indiana. Designing and developing Visual Basic .NET applications is only one of his responsibilities, but it is his favorite part of his job. You can reach Josh at firstname.lastname@example.org.