Home > Guides > Operating Systems, Server

Precisely Formatting PowerShell Output for a Log File

Last updated Nov 26, 2008.

Every PowerShell cmdlet outputs an object that is represented as a table. It may be a table of one item with one property label, but it’s still a table. For the sake of expediency, that table contains everything that falls within the criteria of the instruction, and if you didn’t specify any criteria, that means your table of everything could become extremely long. And even if you do use criteria to filter certain results, the amount of data that table returns may still be too much information for what you need.

Or put another way, PowerShell tables in their native formats don’t make great log file contents. More often, you’ll want to be able to disassemble a table so that your log file contains precisely the data you need.

Specifying Table Contents Column-by-Column

Back in the early days of Microsoft Level II BASIC for the TRS-80, there was an instruction called PRINT USING. Unlike the ordinary PRINT statement, it took a parameter that contained placeholder characters that represented the character position, on a monospaced display or printer page, where digits or letters would appear. In a more simplistic era of layout, it was the way you got all your columns to line up with one another.

That parameter for the PRINT USING statement was a string of alphanumeric characters. PowerShell borrows one more idea from the 1970s that works well in this context: For custom output, you create a string that acts as a template for where you want the output to appear. Since in the modern era we can refer to content elements by name, you don’t have to use placeholder characters to say “text goes here” or “digits go here.” Instead, you assemble the order of columns in the table, along with the custom labels you want attached above each column.

The way PowerShell goes about this is unique, but workable. Assume that there’s an input table containing data that PowerShell has already retrieved. Each column in that input table already has its own label. What you are formatting is an output table, and you’re creating a formatting string that maps the columns from the input table to the data in the output table.

Now, think of each column as a chain of elements, with each element separated by commas, that are assigned to an arbitrarily-named string variable, such as $table. Each element in that string is comprised of three properties:

  • The Expression which maps to the value from the input table, perhaps adjusted or changed for the sake of the output table or perhaps left just the way it is;
  • The Label which goes above each column that explains the meaning of each Expression;
  • The Width of each column in evenly-spaced characters.

These property triplets are expressed within a pair of {curly braces}, with properties separated from each other by semicolons, with each enclosure preceded with a @ character, and with enclosures separated from one another by commas. It’s a tricky formation, as you’ll see, because it manages to embed curly braces within curly braces—a formation which, in other programming languages, means something else entirely.

The get-process command produces an extensive list of how much time and resources each active process in memory is using or has used already. For the sake of logging whether Windows Media Player has been running during a given time (a project begun in the previous segment), we may only want a running log to include a few of these elements: for instance, the amount of virtual memory consumed, the number of seconds of CPU time the process has already usurped, and the time in which the sample was processed—something the get-process table doesn’t actually contain. We don’t need to see the name of the process itself, since we’re only testing for Media Player.

The formatting string looks more like a pain to produce than it actually is. For this example, we only need three columns. Here’s how the string is generated:

$table = @{Expression={$_.VM};Label="Memory";Width=10},@{Expression={$_.CPU};Label="Uptime";Width=10}

Now, it may not be obvious from this instruction alone, but what you’re actually doing is creating a table. As you may have learned from earlier segments, a newly created PowerShell variable with an arbitrary name (like $table) takes the type of whatever you assign to it. This instruction actually creates a table with two rows (each @ enclosure is a row) and three columns. If you don’t believe me, you can ask PowerShell yourself:

PS C:\PowerShell> $table

Name              Value
----              -----
Label             Memory
Width             10
Expression           $_.VM
Label             Uptime
Width             10
Expression           $_.CPU

The precise name for this type of generated table is a hash table. The table formatting template uses $_ as a placeholder to represent “this.” Obviously, there’s no “this” yet; but that’s okay, because PowerShell hasn’t processed the template yet. It will when you present the variable with the template as a parameter, with this instruction:

get-process | where {$_.ProcessName -match "wmplayer"} | format-table $table | '
 out-file "mediaplayer.log" -append

The results of the initial get-process instruction are first filtered through the where clause, and then pared to size with the format-table cmdlet. That’s the cmdlet that uses the $table variable as a template. The result, for now, is attached to the end of the running log.

At this point, it’s not quite useful enough. What’s lacking is any mention of the time in which the measurement is taken. We can add that by adjusting the table template.

$table = @{Expression={$_.VM};Label="Memory";width=10},@{Expression={$_.CPU};label="Uptime";width=10}, '
 @{Expression={get-date};Label="Time";width=24}

What that accomplishes is add a third column to the end:

  Memory   Uptime Time
  ------   ------ ----
 113012736 10.640625 11/16/2008 9:57:53 PM

Scheduling Automatic Script Runs

This may seem counter-intuitive, but for now, the principal way you get a PowerShell script to be executed periodically is outside of PowerShell—specifically, using Windows Task Scheduler.

First, you need to save your script (for our above example, all two instructions of it) in a text file with the extension .PS1. (After PowerShell version 2 comes out, certainly that will change.) Next, you write—of all things—a batch file that runs in the regular CMD.EXE command shell. Task Scheduler cannot yet automatically execute PowerShell scripts, so the next best thing it can do is run a DOS batch file that triggers PowerShell, and that in turn loads and executes the script.

Suppose our two-instruction script above was saved to a file called MPMonitor.PS1. Because PowerShell requires explicit references to script files for safety, you need to give a fully qualified patch to the file. So the batch file for running it might look like this:

Powershell.exe -command c:\psscripts\MPMonitor.PS1

You save that line as a batch file that Task Scheduler will recognize—for example, MPMonitor.BAT—and from there, you use Task Scheduler as you normally would to set up the batch file to run every hour or half-hour, or some reasonable interval.

Books and E-books

Online Resources

Discussions

Root Domain Redundancy
Posted Jun 12, 2008 05:16 PM by tommy58673
0 Replies
NAT
Posted Apr 22, 2008 04:39 PM by v-rathim
0 Replies
the topic is very useful
Posted Mar 10, 2008 02:27 AM by wghanem57957
0 Replies

Make a New Comment

You must log in in order to post a comment.

Related Resources

Jennifer  BortelWin FREE iPhone Developer Books and Videos- Introducing @InformIT Giveaways
By Jennifer BortelFebruary 5, 2010 No Comments

Apples’s recent iPad announcement made our hearts flutter so we couldn’t resist making an announcement of our own!

Today marks the first ever @InformIT Giveaway!

We’ll regularly post a video like this one profiling spectacular prizes we’re giving away—from books and videos to T-shirts and other exciting stuff. Check out the video below to see the giveaways for today, and then scroll down for more prize details and instructions on how to win them!

So Far So Good
By John TraenkenschuhFebruary 2, 2010 No Comments

So far, Win 7 is making a thoroughbred of what has been a plough mule laptop

Dustin Sullivan"Every OSX developer should have this book on their desk."
By Dustin SullivanFebruary 1, 2010 No Comments

That was the sentence Mike Riley ended his recent Dr Dobb's CodeTalk review of Cocoa Programming Developer's Handbook with.

See More Blogs

Informit Network