By now, you should have picked up on the fact that Windows PowerShell is all about the objects. Fortunately, PowerShell has supplied a library of cmdlets to work with objects. Although there are some object-specific cmdlets (e.g., Stop-Service), when it comes to more generalized tasks such as sorting and measuring, PowerShell's cmdlets are one size fits all. This means that once you learn how to use the Sort-Object and Measure-Object cmdlets, you can use the same techniques in just about any PowerShell code. It doesn't matter if you're working with services, processes, files, or user accounts.
Using the Sort-Object Cmdlet
Sorting is probably one of the most common tasks you'll undertake in PowerShell. In the days of VBScript, sorting was usually a cumbersome process. But that's not the case in PowerShell because it has the Sort-Object cmdlet, which has an alias of sort. The typical way to sort is to pipe a command's output to Sort-Object and specify an object property to sort on. Take, for example, the command:
Get-Process | sort -Property WorkingSet
This command pipes the Get-Process cmdlet's output to Select-Object, which sorts it by the WorkingSet property. The -Property parameter is positional, meaning you don't have to specify the parameter name (-Property) as long as the parameter is in the expected position. Most people don't include the parameter name for basic sorts like this.
The default sort order is ascending. However, you can reverse the sort order using the -Descending parameter. For example, the following command sorts the Get-Process cmdlet's output in descending order:
Get-Process | sort WorkingSet -Descending | select -First 5
When you sort, the object isn't changed, so you can pipe it to another cmdlet. In this case, the sorted output is piped to the Select-Object cmdlet (which has the alias select) to select the top five processes with the greatest WorkingSet values, as shown in Figure 1.
You can sort on any object property. To discover an object's properties, you can pipe the object to the Get-Member cmdlet. You can also sort on multiple properties. You simply need to separate them with commas:
Get-Service | sort Status,DisplayName
The sort order will be the same for all the specified properties (either all ascending or all descending). In this instance, all the properties will be sorted in ascending order, with the Status property sorted first. Figure 2 shows the results, which might surprise you. You might have been expecting to see the running services listed first. However, in this particular case, the Status property is actually a numeric enumeration. PowerShell put a "text face" on the values because you probably wouldn't know that a status of 4 indicates a running service and a value of 1 indicates stopped service. Sorts occur on the "true" underlying value of a property.
Another way to use the Sort-Object cmdlet is to sort on a property using the -Unique parameter to eliminate duplicates and return a set of unique values. This works well with numbers:
14,14,5,6,7,1,54,4,4,5,6,1,0,33,3,5 | sort -Unique
As you can see in Figure 3, this command returns a set of unique numbers sorted in ascending order. Integers really don't have properties, so Sort-Object sorted on them as input objects.
Here's another example of how to use the -Unique parameter:
Get-EventLog System -Newest 100 | sort Source -Unique
This command gets the newest 100 entries in the System event log, sorts them by the Source property, and selects a unique object for each Source property. In essence, this command provides a snapshot of the different types of entries being recorded for the different sources, as Figure 4 shows.
One final important point to know about Sort-Object is that sorting is case insensitive by default. Case-sensitive sorting isn't used too often, but I want to demonstrate how it works in case the need arises. The following command returns a unique list of service account names, not taking into account the case used for those names:
Get-WmiObject Win32_Service | select Startname |
sort Startname -Unique
If you want to force the sort to be case sensitive, you need to include the -CaseSensitive parameter:
Get-WmiObject Win32_Service | select Startname |
sort Startname -Unique -CaseSensitive
Figure 5 shows the results of both commands.
There are some advanced ways to use the Sort-Object cmdlet, but for most PowerShell work, what I've demonstrated here should suffice. If you're interested in the advanced usages, see the PowerShell Help files.
Using the Measure-Object Cmdlet
Another common task in PowerShell is measuring a group of objects based on some numeric property. The cmdlet to use for this task is Measure-Object, which has an alias of measure. All you need to do is specify the property name and the type of measurement to perform. The cmdlet has parameters for adding values (-Sum), calculating the average value (-Average), and finding the minimum value (-Minimum) and maximum value (-Maximum).
For example, here's a command that sums the VirtualMemorySize property values for all the process objects:
Get-Process | measure VirtualMemorySize -Sum
Figure 6 shows the results. Notice the Count entry at the top. You'll always get the Count property when measuring numeric values. The other properties (e.g., Average) are populated when requested, which I'll demonstrate in a moment. The value for Sum is in whatever unit of measurement the property uses, which in this case, is bytes.
Measure-Object writes a new object to the pipeline, so you no longer have process objects. If you pipe the expression to the Get-Member cmdlet like this
Get-Process | measure VirtualMemorySize -Sum | Get-Member
you'll see that the new object is a Microsoft.PowerShell.Commands.GenericMeasureInfo object with six properties (Average, Count, Maximum, Minimum, Property, and Sum) and four methods (Equals, GetHashCode, GetType, and ToString).
Here's a more complex example of how to use Measure-Object to check the size of the files in a directory:
dir C:\Scripts -File -Recurse |
measure Length -Sum -Average -Minimum -Maximum
In this command, I'm using the Get-ChildItem cmdlet (which has the alias dir) with the -File and -Recurse parameters to retrieve all the files contained in my C:\Scripts directory. (Note that the Get-ChildItem cmdlet's -File parameter is new to PowerShell 3.0.) I then pipe the files to the Measure-Object cmdlet, specifying the Length property and the four measurement parameters. As you can see in Figure 7, the results show how many files are in my C:\Scripts directory, their total size, the average size, the largest file, and the smallest file. It looks like I have at least one 0 length file to find and remove.
If I only want to see the total size of all the .ps1 files in my Scripts folder, I can use the command:
dir C:\Scripts -Filter *.ps1 -Recurse |
measure Length -Sum | select Count,Sum,Property
In this case, I didn't care about the average, minimum, or maximum size, so I selected only the Count, Sum, and Property properties. Figure 8 shows the results.
You can also use the Measure-Object cmdlet in a command like this:
(dir $env:temp -Recurse -File | measure Length -sum).sum/1mb
This command displays the total size of the temp folder in megabytes.
Most people use Measure-Object for this kind of numeric analysis. However, you can also use it to count the number of lines, words, and characters in a text file by using the -Line, -Word, and -Character parameters, respectively. For example, the following command uses the Get-Content cmdlet to retrieve the content of the file named Jeff.txt, then uses the Measure-Object cmdlet to count the number of lines, words, and characters in it:
Get-Content C:\Jeff.txt | measure -Character -Word -Line
As you see in Figure 9, the output includes the Property column, even though strings don't really have a property other than Length, which is irrelevant here.
Putting It All Together
Let's wrap up with a few practical examples that put the Sort-Object and Measure-Object cmdlets to work. Here's a command that goes through the C:\Scripts folder, sorts the files by length in descending order, selects the first 10 files (i.e., the 10 largest files), then displays their total size in bytes:
dir C:\Scripts -File | sort Length -Descending |
select -First 10 | measure Length -Sum |
Select -ExpandProperty Sum
Here's another example:
dir 'C:\Program Files' -Directory |
$stat = dir $_.FullName -File -Recurse |
measure Length -Sum
$_ | Add-Member -MemberType NoteProperty `
-Name FileCount -Value $stat.count
$_ | Add-Member -MemberType NoteProperty `
-Name FileSize -Value $stat.sum -PassThru
} | Sort FileSize | Select Name,FileSize,FileCount |
Out-GridView -Title "Folder Report"
This code is a bit more complicated and something you're more likely to use in a script. It's getting all the top-level folders in C:\Program Files, and for each folder, it's measuring the size of the files. Using the Add-Member cmdlet, it then adds some new folder object properties, which are then sorted and selected. The output is sent to the Out-GridView cmdlet, which displays it in a grid view window, as you can see in Figure 10. The grid view window has a sorting capability, so you can further analyze the data.
Sorting and measuring are common PowerShell tasks, so you'll probably use the Sort-Object and Measure-Object cmdlets all the time. And once you're comfortable using the cmdlets, you'll find all sorts of creative uses that will offer insights into your Windows systems that you never thought possible.