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:
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:
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:
Or, you could also pass the object to another command to complete the task:
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.
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:
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 (::
).
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:
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:
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.
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.
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:
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:
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:
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:
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).
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:
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:
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:
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.
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:
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:
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.
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:
In the previous command, a DirectorySearcher
object was created using the New-Object
cmdlet. The resulting object is then dumped into the $Searche
r 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:
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:
After the filter is defined, the next step is to execute the search using the FindAll
method:
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:
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:
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-Membe
r cmdlet, as shown in the following example:
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.
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:
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.
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:
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.
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:
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:
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.
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.
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:
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:
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.
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:
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
:
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
:
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:
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.
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.
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.
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:
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:
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.
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:
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:
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.
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.
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:
Or, to define an object that is typed as an integer, you use the following format:
To define an object that is typed as an array, you use the following format:
Lastly, to define an object that is typed as a hash table, you use the following format:
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:
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:
In cases where a type conversion cannot be performed, an error will always be thrown:
To declare a variable as a particular type, you place the type name, enclosed in brackets, in front of the variable name. For example:
Lastly, you can also use a type as an object itself, as shown in the following example:
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:
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:
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:
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.
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.