Working with .NET Framework objects - Advanced PowerShell - PowerShell in Depth, Second Edition (2015)

PowerShell in Depth, Second Edition (2015)

Part 4. Advanced PowerShell

Chapter 35. Working with .NET Framework objects

This chapter covers

· Understanding .NET terminology

· Using .NET classes

· Creating .NET instances

One of the most powerful aspects of PowerShell is the fact that it’s built on the .NET Framework and that it can access all of the underlying .NET capabilities. The .NET Framework is huge, and it’s a good bet that you can find something to do what you need in the event no cmdlet is available for the task at hand.

We have to issue a warning here, though: We’re crossing the line. You’re no longer using PowerShell as PowerShell; you’re diving into the world of .NET programming, and you just happen to be using PowerShell as a way of getting to .NET. You’ll need to use programming-style structures, rather than commands. Though this is something PowerShell can do, it isn’t something PowerShell necessarily excels at; if you’re getting into complex scripts that use .NET heavily, you might have a better experience getting a copy of Visual Studio, which is designed specifically for .NET programming.

Tip

PowerShell syntax was created to be deliberately similar to C#. That was done to provide as smooth a transition as possible for the situation where you need to use a .NET language to get your job done. Having said that, we don’t expect many IT pros to progress down that path. It’s an option, not a necessity.

We also have to set some scope for this chapter. The Framework is truly massive, and there’s no way we can cover all of it. No single book could, let alone a chapter. Our job here is to help you understand some of .NET’s terminology, show you how PowerShell accesses .NET, and point you to Microsoft’s online documentation for .NET. Beyond that, you’re on your own.

35.1. Classes, instances, and members

A class is an abstract definition of some functional unit. You’ve used Get-Service for simple examples throughout this book; it produces objects of the type System.Service-Process.ServiceController. That’s the type name of the class. The class is, by and large, just a definition of what such a thing would look like.

An instance is some actual, running occurrence of a class. If your machine has 100 services, then it has 100 instances of the System.ServiceProcess.ServiceController class. In the case of that particular class, you usually work with the instances that are already running. For other classes, you might first have to create, or instantiate, an instance of the class in order to have something to work with. Technically, you have to ask the class to create a new instance of itself, a process called constructing. Classes offer one or more constructors for this purpose, which are essentially a special method that returns a new instance of the class. Some constructors require no extra information in order to get the new instance up and running; other constructors may require additional arguments in order to complete the task.

Part of what the class defines is its members. These include its properties, its methods, and the events that it can support. You’ve seen these before by piping objects to Get-Member; now you know why the cmdlet uses the noun “Member”.

Typing a class name into your favorite search engine will, often as not, yield Microsoft’s documentation for that class. In figure 35.1, we’ve located the documentation for the System.ServiceProcess.ServiceController, which outlines the class’s members. You can also see the constructors for the class, which is what you’d call to create a new instance of the class. You can see, for example, that there are three constructors, two of which require additional information in order to execute. The MSDN documentation for .NET shows the latest version by default. You can use the Other Versions (immediately below the class name) dropdown to access the documentation for earlier versions of .NET. Appendix C contains information on the .NET version each version of PowerShell expects to work with.

Figure 35.1. The MSDN website contains the documentation for .NET’s classes.

Classes can have two kinds of members. An instance member is one that can be accessed only from an instance of the class, and they’re the most common types of members. For example, to retrieve the name of a service, you have to have an actual instance of a service to work with. There are also class members, also called static members, which don’t need an instance of the class. For example, .NET’s Math class offers static methods for a number of math operations. You don’t have to create an instance of the class in order to use them; they’re just available to you all the time.

35.2. .NET Framework syntax in PowerShell

PowerShell has specific syntax for working with Framework classes:

· The New-Object cmdlet creates new instances of classes. The –TypeName parameter accepts the type name of the desired class, and –ArgumentList enables you to pass arguments to the class’s constructor. Based on the number and data type of the arguments you provide, .NET will automatically select the correct constructor.

Note

This “automatic selection” is a standard .NET feature called overloading. The arguments accepted by a method, including constructors, collectively form a signature: “This method needs two strings, then a number, then a Boolean value,” for example. No constructor can have the same signature as another. Thus, by looking at the values you provide, .NET can figure out which constructor you were trying to run.

· When referring to a class’s type name, such as to execute static methods, put the class name in square brackets [].

· Use a period (.) to access the members of a class or instance.

· Follow a class name, in square brackets, with two colons (::) to access static members of the class.

You’ll see examples of all of these in the next couple of sections.

35.3. .NET support in PowerShell

This heading might seem like an odd one because PowerShell is built from .NET and uses .NET objects (more or less). What this heading means is that a certain amount of the .NET Framework is loaded by default when PowerShell is started. If you need anything else, you must explicitly load it.

If you want to discover the .NET assemblies (often roughly equivalent to namespaces) that are loaded, you can perform this trick:

PS C:\> [appdomain]::CurrentDomain.GetAssemblies() | foreach

{$_.Fullname.Split(",")[0]} | Sort

Anonymously Hosted DynamicMethods Assembly

Microsoft.CSharp

Microsoft.Management.Infrastructure

Microsoft.Management.Infrastructure.Native

Microsoft.Management.Infrastructure.UserFilteredExceptionHandling

Microsoft.PowerShell.Cmdletization.GeneratedTypes

Microsoft.PowerShell.Commands.Management

Microsoft.PowerShell.Commands.Utility

Microsoft.PowerShell.ConsoleHost

Microsoft.PowerShell.Security

mscorlib

PSEventHandler

System

System.Configuration

System.Configuration.Install

System.Core

System.Data

System.DirectoryServices

System.Management

System.Management.Automation

System.Numerics

System.Transactions

System.Xml

This list is taken from a newly opened PowerShell v4 console on Windows 8.1. Loading other modules will alter the list of loaded assemblies. Other versions of PowerShell on other versions of Windows will give slightly different results.

The Appdomain class is a member of the System namespace. You should use [System .AppDomain] to be 100% correct, but because the System namespace is loaded you can omit using System. We recommend using the full name, apart from a few well-known classes such asPSObject and Math. If you’re in doubt about whether some part of the .NET Framework is available, you can use this technique to test what’s been loaded.

35.4. Accessing static members

The -Math class is one you’ll commonly see people working with when it comes to static members. Technically, its class name is System.Math, although PowerShell lets you get away with omitting System in class names, because that’s one of the default top-level namespaces that PowerShell loads, as you’ve just seen.

Note

A namespace is simply a means of categorizing similar classes. System is one of the top-level categories.

Math doesn’t have a constructor, so you’re not meant to instantiate it. Instead, you use its many static members, documented at http://msdn.microsoft.com/en-us/library/system.math.aspx. Or you can pipe [math] to Get-Member. When you access a class, you need to put the name inside brackets, so you’d use [Math] rather than Math.

For example, to get the absolute value of a number:

PS C:\> [system.math]::Abs(-100)

100

PS C:\> [math]::Abs(-100)

100

Here, you’re performing the same operation twice, just to demonstrate that both the full class name [System.Math] and the shorter [Math] work identically. As a rule of thumb if the class name starts with System, you can omit it. Abs() is a static method of the [Math] class; methods always include parentheses immediately after the method name. In this case, the method accepts one numeric parameter, which you’ve given as –100.

Note

Get-Member has a number of parameters to help you track down the members you need. –Static will return only static methods The –MemberType property can be used to refine your search.

Math also has some static properties, which are referred to as fields. These contain constant (unchanging) values. For example:

PS C:\> [math]::pi

3.14159265358979

You can visually differentiate between a method and a property (or field) because methods always have the opening and closing parentheses; properties (and fields) don’t.

PS C:\> $r=Read-Host "Enter a radius"

Enter a radius: 5

PS C:\> ([math]::Pow($r,2))*[math]::pi

78.5398163397448

35.5. Finding the right framework bits

Let’s work through an example. Say you’d like to find a way to resolve a hostname into one or more IP addresses by using DNS. This task is certainly possible without resorting to .NET, but it’ll make a good exercise for figuring out how to use .NET from within PowerShell.

The toughest aspect of using .NET is finding the part you need, not using it. Fortunately, .NET is well documented, and the internal search engine on http://msdn.microsoft.com and the various public search engines all do a good job. Add “msdn” or “.net” to any search in your favorite search engine to return more Framework-centric results. For example, to find our DNS example, we started with a Bing search for “msdn system.network,” guessing that the high-level System.Network namespace would be a good starting point (being able to make good guesses definitely shortens the searching process). That search got us the System.Net Namespace page, at http://msdn.microsoft.com/en-us/library/system.net(v=vs.110).aspx, shown in figure 35.2. Note that our guess, System.Network, wasn’t entirely accurate; the namespace is called System.Net. But search engines are often “fuzzy” enough to let a good first guess turn up the right result.

Figure 35.2. Reviewing the classes in the System.Net namespace

From there, we spotted the Dns class, which looked promising.

As you can see if you visit the DNS class’ page, at http://msdn.microsoft.com/en-us/library/system.net.dns(v=vs.110).aspx, there’s no constructor, meaning we expect all of the members in this class to be static. That’s confirmed by the method list, which shows each method with a big red “S” icon, indicating static methods. A bunch of these methods are listed as “Obsolete,” which suggests they came from earlier versions of .NET and that we should stay away from them. Fine—that eliminates about half of what’s on the page! We eventually found GetHostAddresses, which is what we were trying to do. GetHostByName was actually our first hit, but it’s obsolete, so we kept looking. Clicking on the method name took us to http://msdn.microsoft.com/en-us/library/system.net.dns.gethostaddresses.aspx, shown in figure 35.3.

Figure 35.3. Reviewing the Dns class’s members

That took us to the GetHostAddress method at http://msdn.microsoft.com/en-us/library/system.net.dns.gethostaddresses(v=vs.110).aspx, as shown in figure 35.4.

Figure 35.4. Reviewing the GetHostAddresses() method

Note that the method name is shown as GetHostAddresses, not GetHostAddresses() with parentheses. Adding the parentheses is something you just have to know to do when you’re working from within PowerShell; not every .NET language requires them, but PowerShell does because of its C# background.

This documentation is telling us that the method accepts one argument, a hostname or address, which is a String, and is the hostname or IP address to resolve. It returns an object of System.Net.IPAddress. Actually, it says it returns an array of such objects, meaning there’s the potential for there to be more than one. So before we try this method, we want to see what this System.Net.IPAddress looks like. Clicking on that class name under “Return Value” took us to http://msdn.microsoft.com/en-us/library/system.net.ipaddress.aspx, which is shown in figure 35.5.

Figure 35.5. Reviewing the System.Net.IPAddress class documentation

Constructors are shown here, but we don’t need them. We’re not going to be creating an instance of this class ourselves; we’re going to be getting an instance that was created by our Dns method.

The documentation indicates that each IPAddress object has an Address property but that it’s obsolete. Bummer. Scrolling down a bit, we see that there’s a ToString() instance method, which “Converts an Internet Address to its standard notation.” That sounds like what we’re after, and the good news is that PowerShell will automatically call ToString() when it needs to render a human-readable representation of an object. All objects have a ToString() method; you’ve probably seen them in Get-Member output. With all that in mind, let’s give this a whirl:

PS C:\> [System.Net.Dns]::GetHostAddresses('bing.com')

Address : 3368374220

AddressFamily : InterNetwork

ScopeId :

IsIPv6Multicast : False

IsIPv6LinkLocal : False

IsIPv6SiteLocal : False

IsIPv6Teredo : False

IsIPv4MappedToIPv6 : False

IPAddressToString : 204.79.197.200

Cool. That IPAddressToString is even better—it’s exactly what we wanted. But it’s not listed in the MSDN web page—where did it come from? A quick pipe to Get-Member reveals the truth:

PS C:\> [System.Net.Dns]::GetHostAddresses('bing.com') | get-member

TypeName: System.Net.IPAddress

Name MemberType Definition

---- ---------- ----------

Equals Method bool Equals(System.Object comparand)

GetAddressBytes Method byte[] GetAddressBytes()

GetHashCode Method int GetHashCode()

GetType Method type GetType()

MapToIPv4 Method ipaddress MapToIPv4()

MapToIPv6 Method ipaddress MapToIPv6()

ToString Method string ToString()

Address Property long Address {get;set;}

AddressFamily Property System.Net.Sockets.AddressFamily Addr...

IsIPv4MappedToIPv6 Property bool IsIPv4MappedToIPv6 {get;}

IsIPv6LinkLocal Property bool IsIPv6LinkLocal {get;}

IsIPv6Multicast Property bool IsIPv6Multicast {get;}

IsIPv6SiteLocal Property bool IsIPv6SiteLocal {get;}

IsIPv6Teredo Property bool IsIPv6Teredo {get;}

ScopeId Property long ScopeId {get;set;}

IPAddressToString ScriptProperty System.Object IPAddressToString {get=...

Ah, it’s a ScriptProperty. That’s something added by PowerShell. So the clever PowerShell team, or someone at Microsoft, suspected we might be using this class method and added a handy conversion that gets us the IP address in a string format. So we can just select it to get the address:

PS C:\> [System.Net.Dns]::GetHostAddresses('bing.com') | Select -Property

IPAddressToString

IPAddressToString

-----------------

204.79.197.200

Or, if we just want the string all by itself:

PS C:\> [System.Net.Dns]::GetHostAddresses('bing.com') | Select

-ExpandProperty IPAddressToString

204.79.197.200

DNSClient module

We used the [System.Net] class for demonstration purposes only. There’s a DNSClient module you can use to do things like resolving hostnames. As a rule, you should always look for a cmdlet before turning to a .NET class.

PS C:\> Resolve-DnsName -Name bing.com | Format-List

Name : bing.com

Type : A

TTL : 82

DataLength : 4

Section : Answer

IPAddress : 204.79.197.200

The DNSClient module is available in PowerShell v3 and v4 but only on Windows 8/Windows Server 2012 and Windows 8.1/Windows Server 2012 R2, respectively. It’s a CDXML-based module (see chapter 39) that relies on WMI classes that aren’t available on legacy versions of Windows.

Using .NET as we’ve shown here is your get-out-of-jail card when working on older versions of Windows.

35.6. Creating and working with instances

That’s a great static method example—but what about classes that require you to create an instance? We struggled a bit to find something that wasn’t covered by a PowerShell command, and in the end we decided to show you how to access the properties of a Windows event log. Yes, you can do this with cmdlets already, but it’s still a good example of the process you’d use for other .NET stuff. And, because all Windows computers have event logs, anyone can test this out and get used to it.

We needed to start by finding the necessary class. A Bing search for “.NET event log” turned up hits for the EventLog class; switching the search to “.NET eventlog class” got us to http://msdn.microsoft.com/en-us/library/system.diagnostics.eventlog.aspx, which is shown in figure 35.6.

Figure 35.6. Reviewing the EventLog class documentation

Looking at the constructors, we see that the second one accepts a string, which is the name of an event log on the computer. The third constructor does the same thing while also accepting a remote computer name, which is useful to know about—we’ll file that away for later. We can also see that the complete type name for the class is System.Diagnostics.EventLog. So here goes:

PS C:\> $log = New-Object -TypeName System.Diagnostics.EventLog

-ArgumentList 'Security'

PS C:\> $log

Max(K) Retain OverflowAction Entries Log

------ ------ -------------- ------- ---

20,480 0 OverwriteAsNeeded 7,448 Security

Cool. So our $log variable now contains a reference to the Security log. It’s common to store object instances in a variable, as we’ve done, so that you can easily refer to the instance later. The MSDN web page shows several properties, a few of which were output by default when we displayed $log. There are also methods, including the Clear() method:

PS C:\> $log.clear()

No errors usually means it works—and hope we meant to do that! Note that methods have no –confirm or –whatIf parameter, as a cmdlet would, so you’d better be careful with what you try.

.NET limitations

If you’ve ever wondered why there isn’t a PowerShell cmdlet to back up an event log before clearing it, the answer is that the underlying .NET class doesn’t supply a method to perform backups.

PS C:\> Get-EventLog -List | gm

TypeName: System.Diagnostics.EventLog

This result indicates that the PowerShell event log cmdlets use the same .NET class we’ve shown you here, which if you check the documentation doesn’t have a backup method.

If you want to back up an event log, you need to use WMI.

We’ve often heard comments like “PowerShell should be able to do X and it can’t.” It’s usually not a PowerShell limitation but a limitation of the underlying .NET class—and that’s something much harder to get changed.

Honestly, that’s about it. There’s no specific need to “release” the $log variable, although we can certainly do so if we’re done with it:

PS C:\> del variable:log

or use Remove-Variable, which does the same thing:

PS C:\> remove-variable log

Deleting it from the VARIABLE: drive releases its resources and will free up some memory (although not necessarily right away; .NET itself will take care of the memory management when it feels the need and has a spare moment, in a process called garbage collection).

35.7. Summary

Working with the .NET Framework from within PowerShell is easy—once you know what part of .NET you want and how to use it. As with everything else in PowerShell, knowing how to do what you want is the tough challenge; getting PowerShell to do it is a bit easier. But just because you can doesn’t always mean you should. Wherever possible, look for cmdlets that you can work with and use the “raw” .NET Framework to fill in the gaps.

Remember in chapter 21 we showed you how to create your own .NET class for the objects that you need to output from your scripts. That chapter would be worth rereading in the light of what you’ve learned here. We also recommend chapter 19 of PowerShell Deep Dives (Manning, 2013) if you want to learn more about using .NET code with PowerShell.

We’d love to direct you to a book that’s just about .NET, but there aren’t any. Most will include .NET in the context of a language, like C# or Visual Basic. If you decide to pick one up, go with C#, as its syntax is closest to PowerShell, but understand that you won’t be able to run code examples from the book as is. Microsoft’s own MDSN Library (start in http://msdn.microsoft.com/en-us/library/190bkk9s.aspx) might be the best starting point to help you become more familiar with what .NET has to offer.