If you've worked with Windows PowerShell for any length of time, you're probably aware that PowerShell, unlike the Windows Cmd.exe command shell, is based on the concept of processing objects. Cmdlets, functions, and scripts output objects (even if the output looks like plain text), and you can use these output objects as input for other cmdlets, functions, and scripts. The vertical bar (|) character in the PowerShell command line instructs the command on the right side of the | character to get its input from the command on the left side of the | character. (In this context, the term command refers to a cmdlet, command-line executable, function, or script.) The term pipeline is used to describe the concept of supplying one command's output as the input for another command. In PowerShell, the pipeline works with objects rather than strings of text.

Most of the objects you work with in PowerShell are defined somewhere in the Microsoft .NET Framework itself, a PowerShell module, or a PowerShell snap-in. For example, if you use the Get-Item cmdlet to retrieve a file, the object is a FileInfo object. You can get an object's type by calling its GetType method. Take, for example, the commands:

$item = Get-Item $ENV:SystemRoot\system32\winver.exe
$item.GetType()

The first command retrieves a FileInfo object, and the second command outputs the object's type (FileInfo). You can see the properties and methods of an object by piping the object to the Get-Member cmdlet:

$item | Get-Member

A property is an attribute of an object (something the object is), and a method is executable code associated with the object (something the object does). The Get-Member cmdlet outputs both the object's properties and methods. For example, you can see the size of the file by outputting its Length property:

$item.Length

But what if you want to output only certain properties of an object? Or what if you want to collect data from different objects and output a brand-new object containing only the properties you want? In both cases, you can create a custom object. Let's take a look at both scenarios.

Creating a Custom Object That Outputs Only Certain Properties of an Object

PowerShell makes it easy to output only certain properties of an object, as this is the purpose of the Select-Object cmdlet. This cmdlet uses an object as input. You specify the properties you want as output, and Select-Object outputs a custom object containing only those properties. For example, suppose you want to see a list of running processes and their process IDs. You can produce this list as follows:

Get-Process | Select-Object ProcessName,Id

The output from this command is a list of custom objects containing only the ProcessName and Id properties for the processes running on the current computer.

Select-Object also lets you create calculated properties. A calculated property is a property that's generated on demand when Select-Object outputs the object. To create a calculated property, you need to supply Select-Object with a hash table containing two key-value pairs: Name (the name of the property) and Expression (a script block that tells Select-Object how to calculate the property). You can supply multiple calculated properties by specifying multiple hash tables.

For example, suppose you want to see the day of the week on which comma-separated value (CSV) files in the current directory were last modified. To do this, you can use this command:

Get-ChildItem *.csv | Select-Object Name,LastWriteTime,
  @{Name="DayOfWeek"; Expression=
  {$_.LastWriteTime.DayofWeek}}

In this example, the Get-ChildItem cmdlet retrieves the file objects and Select-Object selects the Name and LastWriteTime properties from each one. The hash table after the LastWriteTime property in this command is a calculated property that retrieves the DayOfWeek property of each file object's LastWriteTime property. In the Expression script block, $_ refers to the current object from the pipeline.

Creating a Custom Object That Combines Properties of Different Objects

PowerShell lets you combine properties of different objects. For example, suppose you want to find out the process name for all running services on the current computer. The WMI Win32_Service class can retrieve the process ID and service name for all running services:

Get-WmiObject Win32_Service -Filter "State='Running'"

You can pipe this output to the Get-Process cmdlet to retrieve the process information for each running service:

Get-WmiObject Win32_Service -Filter "State='Running'" |
ForEach-Object {
  Get-Process -Id $_.ProcessId
}

This code will output a process object for each running service. However, you've lost the display name for each service because you're outputting process objects, not WMI Win32_Service objects. To solve this problem, you can use the New-Object cmdlet to create a custom object inside the ForEach-Object loop as follows:

Get-WmiObject Win32_Service -Filter "State='Running'" |
ForEach-Object {
  $process = Get-Process -Id $_.ProcessId
  New-Object PSCustomObject -Property @{
    "DisplayName" = $_.DisplayName
    "Process" = $process.Name
  }
}

This code creates the $process variable inside the ForEach-Object loop so that you can retrieve the name of the process. The New-Object cmdlet creates a PSCustomObject object and sets its properties (the -Property parameter) to a hash table. The hash table's key names are the property names, and its values are the property values. The end result is a list of custom objects containing the display name and process name for all running services on the current computer.

By definition, hash tables aren't ordered. Thus, when you use the New-Object cmdlet with a hash table (as was done here), the output might not be exactly what you want because the properties of the new custom object can be in any order. You can specify an order for your new object's properties by piping the New-Object cmdlet's output to Select-Object, like this:

Get-WmiObject Win32_Service -Filter "State='Running'" |
ForEach-Object {
  $process = Get-Process -Id $_.ProcessId
  New-Object PSCustomObject -Property @{
    "DisplayName" = $_.DisplayName
    "Process" = $process.Name
  } | Select-Object DisplayName,Process
}

If you have PowerShell 3.0 or later, you can specify the order of the attributes in a hash table by using the [Ordered] attribute for the hash table. (Technically, this creates an OrderedDictionary object instead of a Hashtable object, but they work exactly the same way.) So, if you have PowerShell 3.0, you can use the [Ordered] attribute as follows:

Get-WmiObject Win32_Service -Filter "State='Running'" |
ForEach-Object {
  $process = Get-Process -Id $_.ProcessId
  New-Object PSCustomObject -Property ([Ordered] @{
    "DisplayName" = $_.DisplayName
    "Process" = $process.Name
  })
}

Note that you if you use the [Ordered] attribute, you need to enclose the entire value of the -Property parameter's argument (i.e., [Ordered] followed by the hash table) in parentheses to tell PowerShell it's a single argument.

Another way to create a custom object is to use the Select-Object cmdlet. This technique outputs a "dummy" object (I usually use an empty string) and uses a list of calculated properties to define the output object. (This technique was popular before PowerShell 2.0, when the New-Object cmdlet didn't have a -Property parameter.) You could rewrite the previous example using the Select-Object technique as follows:

Get-WmiObject Win32_Service -Filter "State='Running'" |
ForEach-Object {
  $service = $_
  $process = Get-Process -Id $_.ProcessId
  "" | Select-Object @{Name = "DisplayName";
    Expression = {$service.DisplayName}},
    @{Name = "Process"; Expression = {$process.Name}}
}

This code saves the Win32_Service object in the $service variable and the process object in the $process variable. Next, it outputs a blank string and pipes it to Select-Object (because Select-Object requires an input object). Lastly, it provides two hash tables, separated by a comma, that describe each output object's property names and values. This technique is a bit arcane and harder to understand than if you use New-Object, but I wanted to include it because you might encounter this technique. If you do, it's helpful to know how it works.

Combining CSV Files—Real-World Example

Let's look at a real-world example. Suppose you have two separate CSV files that contain a common property, and you want to combine them into a single CSV file. For example, Table 1 (Data1.csv) contains user IDs and display names.

ID displayName Property
Table 1: Data1.csv
fgarvin Garvin, Fred
pflynn Flynn, Phineas
gbates Bates, Gil

Table 2 (Data2.csv) contains the same user IDs and the users' email addresses. You want to combine these two CSV files into a new CSV file (Data3.csv) that contains all three properties (user ID, display name, and email address).

ID mail Property
Table 2: Data2.csv
fgarvin fred.garvin@contoso.com
pflynn phineas.flynn@contoso.com
gbates gil.bates@contoso.com

The Import-Csv cmdlet imports a CSV file and outputs custom objects based on the content of the CSV file. Figure 1 shows the effects of Import-Csv for both Data1.csv and Data2.csv.

Figure 1: Creating Custom Objects from the Contents of Data1.csv and Data2.csv

The Combine-CsvFiles.ps1 script in Listing 1 demonstrates how you can combine these two CSV files into a new CSV file that contains all three properties. (You can download this script by clicking the Download the Code button near the top of the page.) First, the script reads the CSV files into the $table1 and $table2 variables. Next, it iterates each row in Data2.csv (the "inner" foreach loop) for each row in Data1.csv (the "outer" foreach loop). If the ID property of the current row from both CSV files is equal, the script outputs a custom object containing the ID, displayName, and mail properties from both CSV files, as shown in callout A.

Listing 1: Combine-CsvFiles.ps1
$table1 = Import-Csv "Data1.csv"
$table2 = Import-Csv "Data2.csv"
foreach ( $row1 in $table1 ) {
  foreach ( $row2 in $table2 ) {
    if ( $row1.ID -eq $row2.ID ) {
      # BEGIN CALLOUT A
      New-Object PSCustomObject -Property @{
        "ID" = $row1.ID
        "displayName" = $row1.displayName
        "mail" = $row2.mail
      } | Select-Object ID,displayName,mail
      # END CALLOUT A
    }
  }
}

To create a new CSV file containing the combined data, you can pipe the script's output to the Export-Csv cmdlet and write its output to the Data3.csv file, like this:

.\Combine-CsvFiles.ps1 |
  Export-Csv Data3.csv -NoTypeInformation

If you want to see the contents of the Data3.csv file, you can use the Get-Content or Import-Csv cmdlet. If you want to use Get-Content, you'd run the command:

Get-Content Data3.csv

If you want to use Import-Csv, you'd run the command:

Import-Csv Data3.csv

Figure 2 shows the result from both commands.

Figure 2: Getting the Contents of the Data3.csv File

Create Your Own Custom Objects

The ability to create your own custom objects on demand containing the properties you need is one of PowerShell's most useful features. I've only scratched the surface of the capabilities of custom objects, but it should be enough to whet your appetite for more.

For more information on custom objects in Windows Powershell, see Robert Sheldon's "Powershell Basics: Custom Objects."