Last Wednesday, we started our deep dive into PowerShell. This week, we continue our exploration of Inline .NET code when scripting. This trick allows you to extend PowerShell and to create custom commands for your environment. If you are interested in other techniques like this, pick up a copy of PowerShell Deep Dives and start really exploring PowerShell!
Today’s tutorial focuses on creating a .NET class. This section was written by Richard Siddaway:
At this point you may be thinking that you can combine the two outputs into a single object. There are a number of ways of performing this task. Using New-Object to create a custom object is the most commonly used, but there’s an alternative: create and compile a .NET class as part of your script. This class becomes part of the .NET framework loaded into PowerShell and can be accessed and used like any other class. It may seem that this is an additional complication you don’t need—and in many cases, you would be correct. But using this technique offers some benefits:
- Class properties are strongly typed.
- The class has a unique name, which means it works easily with PowerShell’s format and type files.
Strongly typed is a way of saying that the value you pass to a property must be of the correct data type or be automatically convertible to the correct data type. If you define a property as an integer and try to pass it a string value, PowerShell attempts to convert the string to an integer. If the conversion works, everything is fine and processing continues. If the conversion fails, PowerShell throws an error.
So how do you get this to work? An example is shown in the next listing. The source code is supplied at the start of the script. Add-Type compiles the code, after which it can be accessed like any other .NET class.
$source = @" #1 public class MySystemData { public string ComputerName {get; set;} public string DNSHostName {get; set;} public string OSName {get; set;} public string OSArchitecture {get; set;} public string Manufacturer {get; set;} public string Model {get; set;} public int CountryCode {get; set;} public double Ram {get; set;} public System.DateTime BootUpTime {get; set;} } "@ #1 Add-Type -TypeDefinition $source -Language CSharpVersion3 #2 function get-computersystem { #3 [CmdletBinding()] param ( [Parameter(ValueFromPipeline=$True, ValueFromPipelineByPropertyName=$True)] [ValidateScript({Test-Connection -Computername $_ -Count 1 -Quiet})] [string]$computername = $env:COMPUTERNAME ) $os = Get-CimInstance -ClassName Win32_OperatingSystem ` #4 -ComputerName $computername | select CSName, Caption, OSArchitecture, LastBootUpTime, CountryCode $cs = Get-CimInstance -ClassName Win32_ComputerSystem ` #5 -ComputerName $computername| select DNSHostName, TotalPhysicalMemory, Manufacturer, Model, Domain $props = @{ #6 ComputerName = $os.CSName DNSHostName = $cs.DNSHostName OSName = $os.Caption OSArchitecture = $os.OSArchitecture Manufacturer = $cs.Manufacturer Model = $cs.Model CountryCode = $os.CountryCode Ram = [math]::round($($cs.TotalPhysicalMemory / 1GB), 2) BootUpTime = $os.LastBootUpTime } New-Object -TypeName MySystemData -Property $props #7 }
#1 Class definition
#2 Compile class
#3 Function definition
#4 Get operating system
#5 Get computer system
#6 Set property values
#7 Create object
The .NET code is defined (#1) using a here string. The class definition is supplied first. It looks like this:
public class MySystemData { }
The class keyword says you’re creating a class. It’s given a name: MySystemData. The public keyword indicates that the class is visible to PowerShell. Within the class definition are a number of property definitions of this form:
public string ComputerName {get; set;}
Public is again used to make the property visible. The data type is defined as a string. The property is named ComputerName. The final part of the definition makes the property readable (get) and writable (set). The common data types you’ll need are shown in the listing; other types can be found in the .NET documentation.
Add-Type (#2) is used to compile the code. The language is expected to be C# version 3, but you can use a different version of C#, Visual Basic, J#, F#, or another language if you have the appropriate compiler installed on your system.
NOTE You can’t unload a type or change it once you’ve successfully compiled the code. You have to change the type (class) name or start a new instance of PowerShell.
The code now reverts to the PowerShell you know and love. A function is defined (#3) with one parameter: a computer name that can be supplied by parameter or through the pipeline. Test-Connection is used as part of the validation process to ensure that the computer is reachable.
Calls to Get-CimInstance retrieve the operating system data (#4) and the computer hardware information (#5). You could substitute Get-WmiObject for Get-CimInstance, although I recommend moving to the CIM cmdlets for new work.
A hash table is used to create the property set (#6). Together with New-Object (#7) it creates an object using the class name used in the source code.
The function can be loaded as part of a module or script and is used exactly the same way as any other advanced function. Running it on my test machine produces these results:
PS> get-computersystem ComputerName : RSLAPTOP01 DNSHostName : RSLAPTOP01 OSName : Microsoft Windows 8 Enterprise OSArchitecture : 32-bit Manufacturer : Hewlett-Packard Model : HP G60 Notebook PC CountryCode : 44 Ram : 2.75 BootUpTime : 02/09/2012 19:27:41
One objection that many people have raised to using New-Object to create a custom object is that the order of the properties isn’t maintained. This is because of the way hash tables work. Compare the order of creation in the code and what is produced when you display the hash table:
PS> $props Name Value ---- ----- OSName Microsoft Windows 8 Enterprise Ram 2.75 Manufacturer Hewlett-Packard ComputerName RSLAPTOP01 CountryCode 44 BootUpTime 02/09/2012 19:27:41 OSArchitecture 32-bit DNSHostName RSLAPTOP01 Model HP G60 Notebook PC
Creating an object this way using .NET preserves the order of properties; compare the output with the source code.
The other advantage of creating objects like this is that you define the type:
PS> (get-computersystem).getType() IsPublic IsSerial Name BaseType -------- -------- ---- -------- True False MySystemData System.Object
You can then create new type and format definitions if required.
That concludes our look at using inline .NET code to create an object for output. The second major way to use .NET code is to create a class with methods you can call.