Now that you’ve started using PowerShell as an interactive command line (if you’re not, see my last article for why you should), you’ll need to understand that there are some fundamental differences between how Cmd.exe and PowerShell process input and output. There are two fundamental PowerShell principles that you need to understand before you’re ready to leave batch files (Cmd.exe shell scripts) behind and move to PowerShell:

  1. PowerShell uses objects, not text, and
  2. Display output is not content.

Let’s take a closer look at both of these principles.

PowerShell Uses Objects, not Text

The first principle is that PowerShell processes objects, not text. You have probably heard this before, but you might need some time to adjust to this principle means if you have a background in Cmd.exe or other command shells.

Let’s look at a fairly simple Cmd.exe example. Suppose we have the directory C:\Scripts that contains some text files, and we want to create a comma-delimited list of these files and their last write times. Figure 1 shows a directory listing, the content of a shell script (batch file), and the script’s output.

Figure 1 - Getting file names and last write times in Cmd.exe


The output from the Listing1.cmd script in Figure 1 is text only. This means that if, for example, we want to extract the year of each file, we have to parse the date string by getting a substring starting at the seventh character and counting four characters. This is awkward and error-prone because different localization settings often use different date formats. (For example, what if your localization setting causes Cmd.exe to put the 4-digit year first in the date string?)

In PowerShell, files are file objects. Files have a Name property (a string) and a LastWriteTime property (a DateTime object). The PowerShell equivalent to the Listing1.cmd script in Figure 1 is the following:

Get-ChildItem *.txt | Select-Object Name,LastWriteTime

The pipe (|) character creates a PowerShell pipeline. I will give more detail about the pipeline next time. For purposes of this discussion, the important thing to understand is that the pipeline causes the output objects from one command to become the input to another command. In other words, the above command means “Take the objects from the Get-Childitem cmdlet and pass them as input to the Select-Object cmdlet.” The output of Select-Object is a list of objects containing only the requested properties (in this case, Name and LastWriteTime).

Figure 2 - PowerShell objects and properties


The first command in Figure 2 shows the properties that Get-ChildItem outputs by default. It looks a bit like the output from the first Dir command in Figure 1, but the important thing to understand is that PowerShell is really outputting objects, not a textual listing of file names. The second command in Figure 2 shows the output of the command when we pipe to Select-Object and choose only the Name and LastWriteTime properties. The arrows and text boxes show the relationships between the properties of the output objects from both commands.

This seeming disparity between what PowerShell’s output looks like (text) vs. what it actually is (objects) leads to the second principle: Display output is not content.

Display Output Is not Content

When PowerShell outputs objects, it uses a formatter to arrange the data for viewing. This means that the visible output in the PowerShell window is merely a textual representation of the objects. The first command in Figure 2 illustrates this principle: The output is a list of file objects, but file objects have more properties than just Mode (first column), LastWriteTime, Length, and Name. Those four properties just happen to be the ones the formatter displays.

Figure 2 also features the Select-Object cmdlet. The purpose of Select-Object is to output new objects containing only the properties selected from its input objects. In Figure 2, Select-Object chooses only the Name and LastWriteTime properties of the File objects it receives as input. The PowerShell formatter outputs these objects in columnar format on the screen, but again: These are objects, not text.

Selecting Parts of Properties

We can also select parts of individual properties using Select-Object. Going back to the example in Figure 2, suppose we want to get the file’s name and the creation year of each file. The following command will accomplish this:

Get-ChildItem *.txt | Select-Object Name,{$_.CreationTime.Year}

The second command in Figure 3 shows the command and the output.

Figure 3 - Selecting the creation year for each file


The $_ variable represents the current object in the pipeline (i.e., each file object). Each file object has a CreationTime property (which is a DateTime object), which has a Year property. Thus, $_.CreationTime.Year means “the year of the creation time of each file object.” The curly braces, { }, are required by PowerShell (without them, the $_ will be an empty object and the command will not work correctly).

Calculated Properties

The Year property name is ugly, but we can customize it by creating a calculated property. To create a calculated property, we need to specify a hashtable that contains two key-value pairs: Name and Expression. Name will be the property’s name, and Expression will be the property’s content. The PowerShell command looks like this:

Get-ChildItem *.txt | Select-Object Name,@{Name="Year"; Expression={$_.CreationTime.Year}}

Figure 4 shows the above command and the resulting output. The arrows indicate the relationship between the calculated property in the command and the effect on PowerShell’s output.

Figure 4 - Creating a calculated property


Note that we’re not concerning ourselves with the appearance of the output in the PowerShell window; we’re letting the PowerShell formatter output the objects according to its defaults.

Where’s My Output?

Sometimes a property or object doesn’t fit nicely in the PowerShell window, so PowerShell might occasionally truncate the display of the output to make it fit, as shown in Figure 5. 


Figure 5 - PowerShell truncation example


In Figure 6, the default formatter wraps the display of the Name property in the default Get-ChildItem output format, but it truncates the display of the Name property in the Select-Object output. This example shows how PowerShell only changes the display of data in the output.

Formatting the Output

PowerShell provides two cmdlets that can change the default output format: Format-Table and Format-List. Figure 6 shows example output from both cmdlets. (If you remove the -AutoSize parameter from the first command, you will get the same output as the second command in Figure 6.) 


Figure 6 - Format-Table and Format-List


Keep in mind that the Format-Table and Format-List cmdlets are convenient for viewing the output, but there is no need to trim spaces or blank lines from either kind of output because the outputs are objects, not text.

Filtering Objects

In addition to selecting object properties (Select-Object), PowerShell also provides the Where-Object cmdlet to filter objects based on any criteria you like. When you pipe to Where-Object, you need to specify an expression that tells PowerShell which objects you want. For example, suppose we want to find files that are less than 50 bytes in size. The PowerShell command is: 

Get-ChildItem *.txt | Where-Object { $_.Length -lt 50 } 

The second command in Figure 7 shows the output from this command. Just as with Select-Object, the $_ variable means “the current object in the pipeline.” 


Figure 7 - Where-Object example

Objects and Output

There is no need to parse PowerShell’s output to trim blank lines, trim spaces, extract the year from a string, etc. because PowerShell is object-oriented. You also don’t need to be concerned if PowerShell truncates output on occasion, because the appearance of the data in the output is not equivalent to its content. An understanding of these principles makes it easier to understand how PowerShell really works and makes it easier to leave batch behind. 

Up next: The pipeline is your next key to understanding the power of PowerShell. Stay tuned.