Chapter 3 Advanced PowerShell Concepts

IN THIS CHAPTER

Most shells operate in a text-based environment. Being text-based, these types of shells require an administrator to manipulate the text-based data from commands to complete an automation task. For example, if you need to pipe data from one command to the next, usually the output from the first command must be reformatted to meet the second command’s requirements. While this method does work, and has so for years, it means that working with text-based shells can be difficult, time-consuming, and frustrating without the use of custom tools to complete an automation task (awk is a good example of this).

Luckily, Microsoft took note of this frustration when it set out to develop the next generation command-line interface (CLI) and scripting language (PowerShell). In doing so, it made an architectural decision to build PowerShell on top of the .NET Framework. The result from this decision is that PowerShell retrieves data from commands in the form of .NET Framework objects. Treating data as objects is different than most other shells that operate purely in a text-based environment. Instead of transporting data as plain text, an object allows commands (or cmdlets) to access object properties and methods directly, simplifying an administrator’s interaction with the shell and scripting language. Instead of modifying text data, you can just refer to the required data by name. Similarly, instead of writing code or using a tool to transform data into a usable format, you can simply refer to objects and manipulate them as needed. For example:

image

PS > get-service

Status                  Name               DisplayName
------                     ----                     -----------
Running      AeLookupSvc        Application Experience
Stopped        ALG                        Application Layer Gateway Service
Stopped        Appinfo                   Application Information
Stopped        AppMgmt               Application Management
Running     Ati External Ev...   Ati External Event Utility
Running     AudioEndpointBu... Windows Audio Endpoint Builder
...

image

When the Get-Service cmdlet is executed it collects information about all of the services that exist on the local computer. Each service is then passed as an object to the next cmdlet in the pipeline. In this case, another cmdlet is not waiting in the pipeline to process the stream of objects; instead, information is just redirected to the console. The result is a list of services where each row in the displayed table directly relates to an object that was created and passed through the pipeline by PowerShell.

Because the Get-Service cmdlet creates a collection of objects, you can just as easily put the members of these objects to work. For example:

image

PS > (get-service)[0].Status
Running

image

In the previous command, the Status property of the first object that is returned by the Get-Service cmdlet is examined. If you wanted to, you could also use the object’s methods to complete a task, such as:

image

PS > (get-service)[0].Stop()
PS > (get-service)[0].Status
Stopped

image

Or, you could also pass the object to another command to complete the task:

image

PS > (get-service)[0] | Start-Service
PS > (get-service)[0].Status
Running

image

In either case, there is an object, and that object is accessed and manipulated directly from the command line. In many respects, interfacing with an object in this manner is the power that the .NET Framework brings to PowerShell. Additionally, because PowerShell is based on the .NET self-describing object model, you also have access to the extensive libraries that are available in the .NET Framework. Just imagine, you now have easy access to all of the pre-coded goodies that programmers have had for years. However, to truly appreciate what using a self-describing object model can bring to your command line and script activities, you need to first understand how easy it is to explore and use the objects that are created in PowerShell.

Working with the .NET Framework

Although it was touched upon briefly in the beginning of this chapter, the importance of Powershell’s .NET Framework integration cannot be stressed enough. By using PowerShell, an administrator now has direct CLI access to the .NET Framework’s thousands and thousands of classes, interfaces, and value types. By definition, each of these .NET Framework “types” is an abstract description of a programmable component that can be used to complete a wide range of automation tasks. In other words, directly from the CLI, an administrator now has access to the same type of reusable programmatic objects that programmers have enjoyed for years.

To an administrator, the obvious question might be: “Why is this so exciting?” Simply put, the answer is that access to the .NET Framework’s class library has the power to make administrators extremely productive in relation to the management tasks that they need to complete. Examples of tasks (but not limited to) that fall under this category are as follows:

• Retrieving data from a Web service or centralized data store

• Interacting with users using Windows Forms without creating an application

• Managing Active Directory (configuration) via a reusable script

• Automating complex account provisioning and deprovisioning activities

• Remotely changing local administrator passwords on thousands of machines

• Synchronizing information between Active Directory and Active Directory Application Mode (ADAM)

• Performing online Volume Shadow Copy Service (VSS) snap-shots of data across many systems

To better understand how these benefits are derived, you must first remember that most of the power is realized through the usage of .NET objects. At a basic level, PowerShell uses a .NET object to drastically reduce the amount of effort to complete a task. This reduction may be in the form of fewer commands issued in the CLI or in a script.

However, to truly gain insight into how a class and its resulting .NET object can provide these benefits, it helps to first investigate what comprises these somewhat abstract entities.

To start this explorative process, you need to first understand that a .NET Framework type contains a group of members. These members consist of properties, methods, fields, events, and constructors. An explanation for each member type is as follows:

Property and Field—Both are attributes that describe something about the object. For example, a System.String typed object has a Length property. As its name suggests, examining this property will tell you the length of the string.

Method—An action that an object can complete. An example of a method is the ToUpper method for System.String typed object. You can use this method to convert a string to uppercase characters.

Event—An occurrence that can be reacted to. For example, in the System.Process class, there is an event named Exited. By using this event, you can monitor for when a process has shut down and exited.

Constructer—A unique member in that it is used to create a new object. For example, when PowerShell creates a new instance of an object based on a .NET class, it uses the Constructor method to complete this task.

These members consist of two basic types: static or instance. Static members are shared across all instances of a class and don’t require a typed object to be created before being used. Instead, static members are accessed simply by referring to the class name as if it were the name of the object followed by the static operator (::). For example:

image

PS > [System.DirectoryServices.ActiveDirectory.Forest]::GetCurrentForest()

image

In the previous example, the DirectoryServices.ActiveDirectory.Forest class is used to retrieve information about the current forest. To complete this task the class name is enclosed within the two square brackets ([...]). Then the GetCurrentForest method is invoked by using the static operator (::).

NOTE

To retrieve a list of static members for a class, use the Get-Member cmdlet: get-member -inputObject ([System.String]) -static. The Get-Member cmdlet is explained in more detail later in this chapter.

The second member type, instance, is a member that can be accessed only when an instance of a class already exists. This means that you need to first create the object instance before an instance member can be put to use. For example:

image

PS > $ps = new-object System.Diagnostics.Process

image

In the preceding command, the New-Object cmdlet is used to create an instance of the System.Diagnostics.Process class. The resulting Process object, which resides in the $PS variable, consists of instance members from the System.Diagnostics.Process class. In addition to using the New-Object cmdlet, as previously shown, an object instance or collection that is created using a cmdlet also consists of instance members that are based on a .NET class. For example:

image

PS > $ps = get-process powershell

image

The resulting object from the Get-Process cmdlet consists of the same instance members found with the object that was generated using the New-Object cmdlet. In other words, the object is a .NET object that is based on the System.Diagnostics.Process class and inherits instance members from that type. However, in this case, the cmdlet not only creates a System.Diagnostics.Process typed object, but also binds that object to the powershell process.

NOTE

Throughout the remainder of this chapter, you will see the usage of the word “type” in relation to the usage of the word “object.” In object-oriented programming (OOP), a type refers to the structure that makes up an object. Thus, a type determines the standard set of properties, methods, and so on an object is to consist of after it is created.

Using the New-Object Cmdlet

The New-Object cmdlet is used to both create .NET and COM objects. To create an instance of a .NET object, you simply provide the fully qualified name of the .NET class you want to use, as shown here:

image

PS > $Ping = new-object Net.NetworkInformation.Ping

image

By using the New-Object cmdlet, you now have an instance of the Ping class that enables you to detect whether a remote computer can be reached via Internet Control Message Protocol (ICMP). Therefore, you have an object-based version of the Ping.exe command-line tool, which can be manipulated just like any other PowerShell object. For example:

image

PS > $Ping.Send("sc1-dc01")

Status                  : Success
Address               : 192.168.0.11
RoundtripTime  : 2 ms
BufferSize          : 32
Options                : TTL=128, DontFragment=False

image

To create an instance of a COM object, the comObject switch parameter is used. To use this parameter, define its argument as the COM object’s programmatic identifier (ProgID), as shown here:

image

PS > $IE = new-object -comObject InternetExplorer.Application

image

In the previous example, the comObject switch parameter is used to create an instance of Internet Explorer which is then dumped into the $IE parameter. Then, like the $Ping example, the $IE COM instance can be manipulated just like any other PowerShell object:

image

PS > $IE.Visible=$True
PS > $IE.Navigate("www.cnn.com")

image

Understanding Assemblies

The physical manifestation of the .NET Framework is called a .NET assembly. A .NET assembly is a partially compiled code library that can be used for execution. By using this library, developers are able to quickly create and deploy applications without impacting other applications that reside on a system. In addition, a .NET assembly can easily be shared among other applications, reducing the amount of effort needed to manage and deploy code across applications.

The assembly itself can consist of either a dynamic link library (DLL) or an EXE file. Additionally, in some cases, an assembly may consist of many files: code, resources, images, and so on, making it a multifile assembly. In either case, all assemblies can consist of the following four elements:

• An assembly manifest, which contains metadata describing the assembly: name, files, security requirements, version information, references to resources, and so on.

• Type metadata (describes the types that are implemented).

• Partially compiled code in the MS intermediate language (MISL). This code is used to implement the defined types in an assembly.

• Any related resources (images and other nonexecutable data).

NOTE

Oddly enough, the only required element is the assembly manifest. All other elements are optional depending on what the assembly is being used for.

PowerShell is, by definition, a .NET application. Being a .NET application, it is linked to a number of different common .NET assemblies. As such, most of the common classes (types) are readily available from within a PowerShell session. However, there might be cases where certain assemblies are not loaded into PowerShell’s AppDomain and thus not available. For example, if you try to create a Windows Forms object using the New-Object cmdlet, you get the following error:

image

PS > $Form = new-object System.Windows.Forms.Form
New-Object : Cannot find type [System.Windows.Forms.Form]: make sure
the assembly containing this type is loaded.
At line:1 char:19
+ $Form = new-object  <<<< System.Windows.Forms.Form

image

If you encounter this error, there is no reason to be alarmed. The error is PowerShell’s subtle way of informing the operator about a failed attempt to use an assembly that has not been loaded. To load additional assemblies into a PowerShell session, use the System.Reflection.Assembly class shown here:

image

PS > [System.Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms")

image

In the previous example, the LoadWithPartialName method is used to load the System.Windows.Forms assembly from the Global Assembly Cache (GAC) using what is called a partial name (or simple text name). Using a partial name is an easy way to load additional .NET assemblies; however, Microsoft has now made the LoadWithPartialName method obsolete. The replacement is the Load method, which is meant to prevent partial binds when .NET assemblies are loaded. Using the Load method requires more work. However, if you don’t mind the implications of a partial bind (such as your script failing), you can continue using the LoadWithPartialName method until it is removed from the .NET Framework.

To use the Load method, you need the full name (also known as a strong name) of the assembly that is loaded. An assembly’s full name consists of its simple text name, the version number, its culture definition, and its public key. For example:

image

System.Windows.Forms, Version=2.0.0.0, Culture=neutral,
PublicKeyToken=b77a5c561934e089

image

To get this information, you can look at the assembly’s properties using Windows Explorer. By default all .NET assemblies are located in the %systemroot%assembly folder. Or, you can use a third-party tool such as GACView to review the assemblies loaded into the GAC.

NOTE

The Global Assembly Cache (GAC) in the .NET Framework acts as the central place for registered assemblies. As mentioned, the %systemroot%assembly folder contains all globally available assemblies. These assemblies have mangled filenames so that the version and public key tokens can be included. To view and manipulate the contents of the global assembly cache using Windows Explorer, a shell extension (Assembly Cache Viewer–Shfusion.dll) is added when the .NET Framework is installed.

In addition to the LoadWithPartialName and Load methods, you can also load assemblies using the LoadFrom and LoadFile methods. Typically, these methods are used for .NET-based DLLs included with Microsoft SDKs, from third-party vendors, or your own custom assemblies. For example, say you develop a .NET assembly to manage xyz application. To load that assembly into a PowerShell session, you might use the following command:

image

PS > [System.Reflection.Assembly]::LoadFrom("C:Really_Cool_Stuff
myfirstdll.dll")

image

Understanding Reflection

Reflection is a feature in the .NET Framework that enables developers to examine objects and retrieve their supported methods, properties, fields, and so on. Because PowerShell is built on the .NET Framework, it provides this feature, too, with the Get-Member cmdlet. This cmdlet analyzes an object or collection of objects you pass to it via the pipeline. For example, the following command analyzes the objects returned from the Get-Process cmdlet and displays their associated properties and methods:

image

PS > get-service | get-member -MemberType property

image

Developers often refer to this process as “interrogating” an object. This method of accessing and retrieving information about an object can be useful in understanding its methods and properties without referring to Microsoft’s MSDN documentation, or searching the Internet.

Using the Get-Member Cmdlet

The only way to truly understand the power of the Get-Member cmdlet is to use it to discover what an object can do. In this section, you step through the process of using the Get-Member cmdlet to discover how a DirectorySearcher object can be used to search for objects in Active Directory. The first step in this process is to create a DirectorySearcher object using the following command:

image

PS > $Searcher = new-object System.DirectoryServices.DirectorySearcher

image

In the previous command, a DirectorySearcher object was created using the New-Object cmdlet. The resulting object is then dumped into the $Searcher variable. The goal of using the DirectorySearcher class is to retrieve user information from Active Directory, but you do not yet know what members the returned object supports. To retrieve this information, run the Get-Member cmdlet against a variable containing your mystery object. For example:

image

PS > $Searcher | get-member

   TypeName: System.DirectoryServices.DirectorySearcher

Name                 MemberType             Definition
----                            ----------                    ----------
add_Disposed      Method     System.Void add_Disposed(EventHandler
                                                  value)
CreateObjRef      Method     System.Runtime.Remoting.ObjRef
                                                  CreateObjRef(Type requestedType)
Dispose                 Method     System.Void Dispose()
Equals                   Method     System.Boolean Equals(Object obj)
FindAll                 Method     System.DirectoryServices.
                                                 SearchResultCollection FindAll()
FindOne               Method     System.DirectoryServices.SearchResult
                                                  FindOne()
...

image

The information that is returned by the Get-Member cmdlet is a list of members that the object supports. Within that list, you will find that the DirectorySearcher object has a number of methods and properties that will allow you to conduct a search against an Active Directory domain. To conduct a search, the first step is to define the filter that will be used to limit information that is returned from Active Directory to just the objects you want. To do this, use the DirectorySearcher’s Filter property; this takes a Lightweight Directory Access Protocol (LDAP) search filter statement. For example, if you wanted to search only for user accounts that are disabled, the following filter statement would be used:

image

PS > $Searcher.Filter = `
"(&((sAMAccountType=805306368)(userAccountControl:1.2.840.113556.1.4.8
03:=2)))"

image

After the filter is defined, the next step is to execute the search using the FindAll method:

image

PS > $Searcher.FindAll()

Path                                                                                                     Properties
----                                                                                                        ----------
LDAP://CN=Erica,OU=Sales,DC=companyabc,DC=com
{samaccounttype, objectsid, whencreated, primarygroupid...}
LDAP://CN=Bill,OU=IT,DC=companyabc,DC=com            {samaccounttype,
objectsid, whencreated, primarygroupid...}
LDAP://CN=Miro,OU=Spore,DC=companyabc,DC=com
{samaccounttype, objectsid, whencreated, primarygroupid...}
LDAP://CN=Yelena,OU=Spore,DC=companyabc,DC=com
{samaccounttype, objectsid, whencreated, primarygroupid...}
...

image

In the results that are returned from the search, you will find a collection of user accounts that are disabled. Using this collection, you can then further explore what members the resulting objects support by again using the Get-Member cmdlet. For example, to return the methods supported by the first object in the collection, use the following command:

image

PS > ($Searcher.FindAll())[0] | get-member -MemberType method

image

Or if you wanted, you could dump the entire collection into a variable, and then interrogate it using the Get-Member cmdlet to understand what properties are supported. For example:

image

PS > $Users = $Searcher.FindAll()
PS > $Users | gm -MemberType property

   TypeName: System.DirectoryServices.SearchResult

Name           MemberType            Definition
----                  ----------                       ----------
Path                 Property         System.String Path {get;}
Properties     Property          System.DirectoryServices.
                                                   ResultPropertyCollection Properties {get;}

image

Either way, based on the results from the Get-Member cmdlet, you gain insight into what an object or collection of objects can be used for. In the previous example, the Get-Member cmdlet provided you with information about the properties that are supported. In this case, the object collection supports the Properties and Path properties. The Properties property contains a list of attributes for each user account, whereas the Path property contains the ADsPath. Using the ADsPath, you can then use the [ADSI] type accelerator (explained later in this chapter) to bind to a user account and continue your exploration using the Get-Member cmdlet, as shown in the following example:

image

PS > [ADSI]$Users[1].Path | gm

   TypeName: System.DirectoryServices.DirectoryEntry

Name               MemberType               Definition
----                        ----------                         ----------
accountExpires     Property   System.DirectoryServices.
                                                     PropertyValueCollection accountExpires {get;set;}
adminCount           Property   System.DirectoryServices.PropertyValueCollection
                                                     adminCount {get;set;}
cn                             Property   System.DirectoryServices.PropertyValueCollection
                                                      cn {get;set;}
...

image

At this point, you should now have an appreciation for how the Get-Member cmdlet works. Using this cmdlet, you can understand how a DirectorySearcher object can be used to conduct an Active Directory search. After completing the search, you used the ADsPath that was returned within the search results to bind to an Active Directory object. Once again, you then used the Get-Member cmdlet to gain insight into what members are suppored by the resulting DirectoryEntry object. In both cases, you learned to use the resulting object(s) from the .NET Framework DirectorySearcher and DirectoryEntry classes without referring to any documention. Instead, you used Reflection and the Get-Member cmdlet to gather the information that you needed to complete the automation task on hand. Together, these are two powerful tools, and by now, having a working knowledge of these tools, your PowerShell sessions should be many times more productive.

Understanding the Pipeline

The use of objects gives you a more robust method for dealing with data. In the past, data was transferred from one command to the next by using the pipeline, which makes it possible to string a series of commands together to gather information from a system. However, as mentioned previously, most shells have a major disadvantage: The information gathered from commands is text-based. In such cases, raw text needs to be parsed (transformed) into a format the next command can understand before being piped. To see how this parsing works, review the following Bash shell example:

image

$ ps -ef | grep "bash" | cut -f2

image

The goal in the previous example is to get the process ID (PID) for the bash process. To complete this task, a list of currently running processes is gathered with the ps command and then piped to the grep command and filtered on the string bash. Next, the remaining information is piped to the cut command, which returns the second field containing the PID based on a tab delimiter.

NOTE

A delimiter is a character used to separate data fields. The default delimiter for the cut command is a tab. If you want to use a different delimiter, use the -d parameter.

Based on the man information for the grep and cut commands, it seems as though the ps command should work. However, the PID isn’t returned or displayed in the correct format.

The command doesn’t work because the Bash shell requires you to manipulate text data to display the PID. The output of the ps command is text-based, so transforming the text into a more usable format requires a series of other commands, such as grep and cut. Manipulating text data makes this task more complicated. For example, to retrieve the PID from the data piped from the grep command, you need to provide the field location and the delimiter for separating text information to the cut command. To find this information, run the first part of the ps command:

image

$ ps -ef | grep "bash"
   bob    3628       1 con  16:52:46 /usr/bin/bash

image

The field you need is the second one, 3628. Notice that the ps command doesn’t use a tab delimiter to separate columns in the output; instead, it uses a variable number of spaces or a whitespace delimiter, between fields.

NOTE

A whitespace delimiter consists of characters, such as spaces or tabs, that equate to a blank space.

The cut command has no way to tell that variable number spaces should be used as a field separator, which is why the command doesn’t work. To get the PID, you need to use the awk scripting language. The command and output in that language would look like this:

image

$ ps -ef | grep "bash" | awk '{print $2}'
3628

image

The point is that although most UNIX and Linux shell commands are powerful, using them can be complicated and frustrating. Because these shells are text-based, often commands lack functionality or require using additional commands or tools to perform tasks. To address the differences in text output from shell commands, many utilities and scripting languages have been developed to parse text.

The result of all this parsing is a tree of commands and tools that make working with shells unwieldy and time-consuming, which is one reason for the proliferation of management interfaces that rely on GUIs. This trend can be seen among tools Windows administrators use, too, as Microsoft has focused on enhancing the management GUI at the expense of the CLI.

Luckily, for Windows administrators, this trend is now changing with the introduction of PowerShell. They now not only have access to the same automation capabilities as their UNIX and Linux counterparts, but PowerShell and its use of objects fill the automation need Windows administrators have had since the days of batch scripting and WSH in a more usable and less parsing intense manner. To illustrate this, the following command completes the same type of task that you reviewed in the Bash shell example:

image

PS > get-process powershell | format-table id -AutoSize

  Id
  --
3628

image

On the surface, the command doesn’t necessarily look any shorter or less complex then the Bash shell example. But, underneath something special is happening in the PowerShell pipeline. When the command is executed, a pipeline processor is generated to manage the entire pipeline execution. To start things off, the pipeline processor binds values to the parameters as command-line input for the first cmdlet in the pipeline. In this case, the value is powershell, which is being bound to the Get-Process name parameter by its position within the command. Next, the pipeline processor starts the command processing, gathers the results as a base .NET object, wraps that object within a specifically typed PowerShell object called a PSObject (explained later in this chapter), and then finally passes that object to the next cmdlet in the pipeline.

Like the first cmdlet, the pipeline processor again performs the value to parameter binding. However, in this case, the pipeline processor attempts to perform the binding to parameters that accept pipeline input. For example, if you were to review the full help information for the Format-Table cmdlet, you will find that the inputObject parameter accepts pipeline input.

image

PS > get-help format-table
-inputObject <psobject>
...
     Accept pipeline input?       true (ByValue)
...

image

After the passed value (PSObject) has been bound to the correct parameter, the pipeline processor again starts the command processing, gathers the results as a base .NET object, wraps that object in a PSObject, and then finally passes that object to the next cmdlet in the pipeline. This process then continues until the end of the pipeline has been reached. The pipeline processor then (by default) pipes the resulting object to the Out-Default cmdlet. The Out-Default cmdlet is a special non-interactive cmdlet that is responsible for figuring out how to format and output the object to the PowerShell Console. All pipelines, by default, end with this cmdlet.

In summary, a PowerShell pipeline is a command-processing factory. In this factory, there are a series of commands (cmdlets) that are separated by the pipe operator (“|”). When the factory is put to work, PowerShell attempts to ensure that data from one command is piped correctly to the next command without the need to perform any type of parsing. An example of this is shown in Figure 3.1.

Figure 3.1. A simplistic view of the PowerShell pipeline

Image

The previously shown figure is a simplistic view of the PowerShell pipeline. If this was a book written purely for an audience of developers, there would be number of additional concepts that would need to be discussed about the PowerShell pipeline. Most important of these concepts is the fact that the previous example explains only the pipeline in reference to a single object being passed through. As such, the question must be asked, “What happens when there are numerous objects that are returned by the first cmdlet?” An example of this is shown in the following command:

image

PS > get-process | format-table name, id -AutoSize

image

Here, the Get-Process cmdlet returns a collection of objects. This is different from the first pipeline example where the Get-Process cmdlet returned only one object (unless you had multiple PowerShell Console sessions running at once). If PowerShell were like a conventional programming environment, the results from the pipeline would be returned to the console session only after the entire object collection had finished being processed by each cmdlet. However, like most other shell languages, PowerShell processes objects one at a time within the pipeline. By streaming the objects, PowerShell is able to start outputting data from the pipeline as soon as it is available.

As you might have guessed, being able to stream results back to the interactive console as soon as they are available is important. If this didn’t happen, and the command was data-intensive, then you might be sitting with a blank console session for some time until the pipeline has finished executing. A perfect example of where streaming might be beneficial is shown with the following command:

image

PS > get-childitem -Path hklm: -Recurse

image

Here, the command issued is a recursive dump of the entire HKLM PSDrive. If PowerShell didn’t perform pipeline streaming, then it might take minutes before any information is returned to the console session. Instead, when the command is issued, results are displayed almost immediately within the console.

Powerful One-Liners

At this point, you may still be wondering what makes the PowerShell pipeline so powerful. While reading through the previous examples, you might have seen several clues. However, it isn’t until you have put these clues together can you then gain a full appreciation for what can be accomplished with PowerShell from the CLI. The following is a summary of some key concepts that have been reviewed so far:

• PowerShell is an object-based shell.

• Data in PowerShell is in the form of .NET objects that are then encapsulated in a generic PSObject.

• The PowerShell pipeline processor attempts to ensure that objects in the pipeline can be passed successfully to the next cmdlet.

• The pipeline processor also streams objects through the pipeline.

• Pipelines, by default, end with the Out-Default cmdlet, which figures out how to format the output to the console.

Putting these concepts all together, you are empowered with the tools needed to construct a complex pipeline that can complete any number of automation tasks. This concept of completing tasks via a single complex pipeline is often referred to as a PowerShell one-liner. To illustrate this concept, the three following examples show a PowerShell one-liner in increasing complexity. In the first example, the object collection returned by the Get-Process cmdlet is piped into the Where-Object cmdlet. Then, by using the Match conditional operator, the object collection is filtered so that only objects with a value that contains Microsoft in the Company property are returned:

image

PS > get-process | where-object {$_.Company -match ".*Microsoft*"}

image

In the next example, the one-liner is expanded such that the Select-Object is used to return only a process’s Name, ID, and Path:

image

PS > get-process | where-object {$_.Company -match ".*Microsoft*"} |
select Name, ID, Path

image

In the last example, the resulting object collection from the Select-Object cmdlet is then piped to the ConvertTo-Html cmdlet. The HTML that is generated from this cmdlet is then finally redirected into a file named report.html:

image

PS > get-process | where-object {$_.Company -match ".*Microsoft*"} |
select Name, ID, Path | convertto-html > report.html

image

While stepping through these examples, you can see how the pipeline continues to become more and more complex. In the first example, the results from the pipeline just dumped Microsoft-related process information to the Out-Default cmdlet. By the third example, the results were refined and exported into an HTML-based report. The entire automation task for creating this report is done using a single line of code. In comparison, if you were to use WSH to complete this task, it would take many lines of code and several different automation interfaces.

To further demonstrate this, here is a more compelling example of a complex one-liner:

image

PS > import-csv .users.csv | foreach {get-adobject -filter
"(&(samAccountName=$($_.employeeID)))"} | select
@{name='Name';Expression={$_.cn} },@{ name='Accountname';
Expression={$_.sAMAccountName} },@{ name='Mail'; Expression={$_.mail}}
| export-csv results.csv

image

This one-liner makes use of the Get-ADObject cmdlet, which is available after installing the PowerShell Community Extensions package. By utilizing this cmdlet, the one-liner is able to complete an Active Directory search for users based on information that is imported from a CSV file. Then, using the Select-Object cmdlet, information about each user found is exported to a results CSV file. In other words, a user report that would have taken many lines of code to complete using WSH is instead completed using one long line of code (a one-liner). Needless to say, one-liners can be powerful to an administrator who seeks to perform complex automation tasks.

NOTE

PowerShell Community Extensions (PSCX) is a community-based project that is focused on providing a number of different useful cmdlets, providers, aliases, filters, functions, and scripts. This package can be freely downloaded from the following site: www.codeplex.com/PowerShellCX.

The Extended Type System (ETS)

For the most part, PowerShell is a typeless language in that an administrator rarely needs to specify or think about the type of object that they happen to be working with. However, just because you don’t see it, doesn’t mean PowerShell is typeless. Instead PowerShell is type-driven. After all, it is object-based, and it has to interface with many different types of objects from the less than perfect .NET to Windows Management Instrumentation (WMI), Component Object Model (COM), ActiveX Data Objects (ADO), Active Directory Service Interfaces (ADSI), Extensible Markup Language (XML), and even custom objects. To handle these many different types of objects (both programmatically and logically) while ensuring that they can be passed through the Pipeline, PowerShell adapts to different object types and then produces its interpretation of an object for you. To do this, PowerShell uses the Extended Type System (ETS), which provides an interface that enables administrators to manipulate and change objects as needed.

The ETS was designed to overcome three basic problems that are encountered in PowerShell. First, there can be cases where a .NET object doesn’t behave in a default manner, which then can prevent data from flowing between cmdlets. For example, when working with WMI, ADO, and XML objects, their members describe only the data that they contain. Nonetheless, the goal of PowerShell is to work with the data that is referenced by these objects. To get around this predicament, the ETS provides the notion of adapters, which allow an underlying object to be adapted to meet the expected default semantics. Secondly, there might be cases where a .NET object just doesn’t provide the capabilities that are required to complete the task at hand. Using the ETS, these objects can be extended with additional members. Lastly, PowerShell attempts to always interact with an operator by presenting itself as a typeless scripting language. In other words, a variable rarely needs to be declared as a particular type despite the fact that PowerShell is type-driven.

To accomplish these feats the ETS uses a common object that has the capability to state its type dynamically, add members dynamically, and make interactions with all objects consistent. The name for this abstraction layer is the PSObject, a common object used for all object access in PowerShell. The PSObject can encapsulate any base object (.NET, custom, and so on), any instance members, and implicit or explicit access to adapted and type-based extended members, depending on the type of base object.

By wrapping an existing object (base object), the PSObject can be used to show an adapted view of an object or as a vehicle to extend that object. An adapted view of an object will expose only a view of an object that contains a selected set of members that can then be directly accessible. To better understand the relationship between a base object and the resulting PSObject, refer to Figure 3.2.

Figure 3.2. How an object is adapted and the resulting PSObject

Image

In the event that the resulting PSObject does not expose the needed functionality, PowerShell has several different object views that can be used to access blocked members from the base object and gain insight into how the PSObject was constructed. These views are explained as follows:

PSBase—Used to access the base object (a list of the original properties of the .NET object without extension or adaptation).

PSAdapted—A view of the adapted object.

PSExtended—Provides a list of extended elements (properties and methods that were added in the Types.ps1xml files or by using the Add-Member cmdlet).

PSObject—Describes the adapter that converts the base object to a PSObject.

PSTypeNames—A list of object types that describe an object.

In certain cases, you might encounter issues with the behavior of a cmdlet’s interpretation of an object. A perfect example of such a scenario can be found with how PowerShell interacts with the DirectoryEntry class. Details about this interaction are explained in Chapter 13, “PowerShell as a Management Interface.” Many of the more useful base object members are not exposed in a PowerShell-based DirectoryEntry object. When this type of scenario is encountered, you can use PSBase view to review the base object and make use of its members. For example:

image

PS > $OU = new-object
DirectoryServices.DirectoryEntry("LDAP://OU=Accounts,DC=companyabc,DC=com")
PS > $OU.PSBase | gm

image

By default, the Get-Member cmdlet returns only information from the PSObject, PSExtended, and PSTypeNames views. As shown in the previous example, the PSBase view is used in conjunction with the Get-Member cmdlet to expose the members of the base object. To interact with the base object’s members, you use the PSBase view and PowerShell’s “.” notation, as shown in the following command:

image

PS > $OU.PSBase.Properties

image

After the previous command is issued, you are presented with a formatted table that contains all of the specified Active Directory object’s attributes and their associated values.

Understanding the Add-Member Cmdlet

As mentioned before, the ETS can also be used to extend objects. So, in addition to PSObject blocking useful members, there might be cases where you will want to extend an object to include additional members. To extend objects in PowerShell, there are two different interfaces. The first interface is to programmatically extend an object by using a custom Types.ps1xml file. The second interface is to use the Add-Member cmdlet and extend the object directly from the command line. For example:

image

PS > $ProcList = get-process "Power*"
PS > $ProcList | add-member -Type scriptProperty "RunTime" {return
((date) - ($this.starttime))}

image

In the preceding example, a scriptProperty is added to a collection of process objects. In the property that is added, a ScriptBlock is defined that is used to calculate the time in minutes that the process has been running. The formatted results from the added scriptPoperty are shown in the next example:

image

PS > $Proclist | select Name, @{name='RunTime';
Expression={"{0:n0}" -f $_.RunTime.TotalMinutes}}

Name                                                        RunTime
----                                                                -------
powershell                                                  63

image

In the examples that you have just reviewed, it is important to understand that two object collections are actually created. The first object collection is generated when the Get-Process cmdlet is executed. The second object collection is generated when the Add-Member cmdlet is executed. Why does this happen? Well, when the Add-Member cmdlet is used, it actually wraps the original object in a new PSObject instance.

Understanding the types.ps1xml File

Be default, all object types that are used by PowerShell cmdlets, providers, and the powershell.exe are defined via an XML file named types.ps1xml. This file is part of the default PowerShell installation. However, the framework within this file represents the method by which you can define a new object type or extend an existing object type. In other words, you create your own types.ps1xml file and then load it into your PowerShell session using the Update-TypeData cmdlet.

NOTE

Do not modify the default types.ps1xml file because doing so may put PowerShell into an unusable state.

Working with Types

In PowerShell, there are several different ways to cast an object as a certain type. The first method is to use PowerShell’s built-in capability to determine basic types: strings, numbers, arrays, and hash tables. To use these basic types, you just define the data as it might be when typed. For example, to define an object that is typed as a string, you use the following format:

image

PS > $mystring = "This is a string"
PS > $mystring.GetType()

IsPublic     IsSerial     Name                                     BaseType
--------         --------             ----                                         --------
True             True     String                              System.Object

PS >

image

Or, to define an object that is typed as an integer, you use the following format:

image

PS > $mynumber = 123456
PS > $mynumber.GetType()

IsPublic     IsSerial     Name                                BaseType
--------         --------         ----                                        --------
True             True         Int32                               System.ValueType

PS >

image

To define an object that is typed as an array, you use the following format:

image

PS > $myarray = "1","2","3","4"
PS > $myarray.GetType()

IsPublic     IsSerial     Name                                BaseType
--------         --------         ----                                    --------
True             True     Object[]                            System.Array

PS >

image

Lastly, to define an object that is typed as a hash table, you use the following format:

image

PS > $mytable= @{name = "Bob Barker"; job = "TV Guy"}
PS > $mytable.GetType()

IsPublic     IsSerial     Name                                BaseType
--------         --------         ----                                        --------
True             True     Hashtable                           System.Object

PS >

image

The other method to define a type in PowerShell is to use something that is called a literal type. As the name suggests, a literal type is used in PowerShell to specify a particular type either during a cast, during the declaration of a variable, or as the object itself. The following command shows how to case series of numbers into an array:

image

PS > $myarray = [System.Array]"1","2","3"

image

In the previous example, you see a string that is enclosed within a set of square brackets ([]). When used, square brackets ([]) denote that the term enclosed is a .NET Framework reference. A reference can be:

A Fully Qualified Class Name—[System.Diagnostics.Process]

A Class under the System Namespace—[Diagnostics.Process]

A Type Accelerator—[ADSI], [WMI], [Regex], and so on

In this case, the string that is enclosed is a full type name (Fully Qualified Class Name) that denotes that the resulting object is to be of the specific type (System.Array). Keep in mind, however, that when working with classes under the System namespace, you don’t need to include “System” in the full typed name. Instead, in an effort to reduce typing, PowerShell always appends “System” to a type name if it’s invalid and tries again to perform the type conversion. For example:

image

PS > $myarray = [Array]"1","2","3"

image

In cases where a type conversion cannot be performed, an error will always be thrown:

image

PS > $mynumber = [int]"this will not work"
Cannot convert value "this will not work" to type "System.Int32".
Error: "Input string was not in a correct format."
At line:1 char:18
+ $mynumber = [int]" <<<< this will not work"

image

To declare a variable as a particular type, you place the type name, enclosed in brackets, in front of the variable name. For example:

image

PS > [array]$myarray = "1","2","3"

image

Lastly, you can also use a type as an object itself, as shown in the following example:

image

PS > [System.Array] | gm –static

   TypeName: System.Array

Name                    MemberType             Definition
----            ---------- ----------
AsReadOnly          Method         static  AsReadOnly(T[] array)
BinarySearch        Method         static System.Int32 BinarySearch(T[] array,
                                                       T value), static System.Int32
                                                       BinarySearch(T[...
Clear                       Method         static System.Void Clear(Array array, Int32
                                                       index, Int32 length)
ConstrainedCopy Method         static System.Void ConstrainedCopy(Array
                                                       sourceArray, Int32 sourceIndex, Array
                                                       destinatio...
...
Reverse                 Method         static System.Void Reverse(Array array),
                                                       static System.Void Reverse(Array array,
                                                       Int32 in...
...

image

In the previous example, you use the Get-Member cmdlet to retrieve the methods that are supported by an object that is typed as a System.Array. To actually use one of these static methods, you need to use the “::” operator, as shown in the next example:

image

PS > [System.Array]::Reverse($myarray)
PS > $myarray
3
2
1
PS > [System.Array]::Reverse($myarray)
PS > $myarray
1
2
3

image

Type Accelerators

A type accelerator, also known as a type shortcut, is simply an alias for specifying a .NET type. Without a type accelerator, defining a variable type requires entering a fully qualified class name, as shown here:

image

PS > $User = [System.DirectoryServices.DirectoryEntry]"LDAP://CN=Fujio
Saitoh,OU=Accounts,OU=Managed Objects,DC=companyabc,DC=com"
PS > $User

distinguishedName
-----------------
{CN=Fujio Saitoh,OU=Accounts,OU=Managed Objects,DC=companyabc,DC=com}
...

image

Instead of typing the entire class name, you can just use the [ADSI] type accelerator to define the variable type, as shown in the following example:

image

PS > $User = [ADSI]"LDAP://CN=Fujio Saitoh,OU=Accounts,OU=Managed
Objects,DC=companyabc,DC=com"

image

Type accelerators have been included in PowerShell mainly to cut down on the amount of typing to define an object type. However, for some reason, a definitive list of supported type accelerators isn’t provided within the PowerShell documentation, even though the [WMI], [ADSI], and other common type accelerators are referenced on numerous Web blogs. Regardless of the lack of documentation, type accelerators are a fairly useful feature of PowerShell. Table 3.1 lists some of the more commonly used type accelerators.

Table 3.1. Type Accelerators in PowerShell

Image

Summary

This chapter’s primary focus was to delve deeper into what PowerShell is and how it works. Among the many key concepts that were reviewed, the most important of these is to understand that PowerShell is an object-based shell that is built on top of the .NET Framework. In addition, because of PowerShell’s relationship with the .NET Framework, it exposes the power of that framework’s object model and a .NET feature called Reflection directly from within a command-line console (or script file).

While an object-based shell is not necessarily a new concept, the PowerShell product team wanted to design a shell that would empower its users, not frustrate them. The end result is a shell that meets this objective by attempting to abstract all objects into a common form (PSObject) that can be used without modification (parsing) in your commands and scripts. This feature combined with a robust command pipeline, the concept of cmdlets, and the capability to be adapted as needed, make PowerShell a tool that facilitates the ability to complete tasks.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset