How to change output format in PowerShell

I was looking around the Net today and stumbled on the post in Scott Hanselman's blog, where he talks about technical and marketing aspects of product naming in Microsoft. There he shows display format for IIS application pools that may be relevant to servers, where admins keep multiple versions of CLR and run applications in various modes. This output includes managed runtime version for the pool and mode (integrated or classical) for the pool. By default IIS provider shows only name, state and list of applications, assigned to this pool. It is pretty easy to change this output to what Scott prefers.

Open file iisprovider.format.ps1xml (name could be changed in next release) and find view, defined there for application pool. This will be piece of XML tagged by type name "Microsoft.IIs.PowerShell.Provider.NavigationNode#AppPool". This typename means that as soon as PowerShell will find object in the output stream, which has this type name in its typenames collection, it will use this view. View selected once for the first object in output stream -- this is pretty annoying shortcoming in PowerShell because in IIS configuration it is common to have objects of multiple types in output.

OK, now you have to create new XML file with the name you like, and extension "format.ps1xml", say PoolModeView.format.ps1xml. Then add there header and footer:

<?xml version="1.0" encoding="utf-8"?>
<Configuration>
  <ViewDefinitions>
  </ViewDefinitions>
</Configuration>

Select whole section with pool view in original file and copy it into your new file. We will modify this view to add new columns. We have to change name of the view first. Let's name it "appPoolModeView". Then add new column definitions after the State column:

<TableColumnHeader>
  <Label>State</Label>
  <Width>12</Width>
  <Alignment>left</Alignment>
</TableColumnHeader>
<TableColumnHeader>
  <Label>CLR Version</Label>
  <Width>12</Width>
  <Alignment>left</Alignment>
</TableColumnHeader>
<TableColumnHeader>
  <Label>Mode</Label>
  <Width>12</Width>
  <Alignment>left</Alignment>
</TableColumnHeader>

That will be our column headers. We have to define, what to show in these columns. After section TableColumnItem for State, add the following:

<TableColumnItem>
  <PropertyName>State</PropertyName>
</TableColumnItem>
<TableColumnItem>
  <PropertyName>managedRuntimeVersion</PropertyName>
</TableColumnItem>
<TableColumnItem>
  <PropertyName>managedPipelineMode</PropertyName>
</TableColumnItem>

We will display two properties that identify CLR runtime and mode. Now we could save our file. In PowerShell window run the following command:

PS C:\ #> Update-FormatData -prependpath <path-to-your-file>

Depending on your settings in PowerShell you may see an error, because new file is not signed. As a long term solution you will need to sign this file the same way you are signing scripts, for now you could relax execution policy to RemoteSigned.

Let's see what we get:

PS C:\ #> cd iis:\apppools

PS IIS:\AppPools #> dir

Name                  State     CLR Version  Mode         Applications
----                  -----     -----------  ----         ------------
DefaultAppPool        Started   v2.0         Integrated   Default Web Sit
                                                          e
                                                          mysite
                                                          /appDefault
                                                          /myapp
Classic .NET AppPool  Started   v2.0         Integrated
newPool               Started   v2.0         Integrated

If you want to have this view, but don't like to use it by default, run update-formatdata without -prependpath. PowerShell will add your view to the internal tables, but still will use the old one. To enable new output format, add "format-table -view apppoolmodeview" to your output command, and you will get this view.

PS IIS:\AppPools #> dir | ft -view appPoolModeView

XPath in output formatting

While we discussing output formatting, let me show how you could extend output for any object by adding pieces of script into the view. This script block could get any data and add it to your output. Look how I did it for application pool view. The last column shows applications, assigned to this pool. But pool structure doesn't include any indication about applications. To find which applications belong to the pool, we have to look through all applications and select those having property "applicationPool" set to the name of our pool. Let's take a look how we form column for applications (I inserted back ticks into long lines to fit them on screen):

<TableColumnItem>
  <ScriptBlock>
    $pn = $_.Name
    $sites = get-webconfigurationproperty `
      "/system.applicationHost/sites/site/`
      application[@applicationPool=`'$pn`'and `
      @path='/']/parent::*" `
      machine/webroot/apphost -name name
    $apps = get-webconfigurationproperty `
      "/system.applicationHost/sites/site/`
      application[@applicationPool=`'$pn`'and `
      @path!='/']" `
      machine/webroot/apphost -name path
    $out = ""
    foreach ($s in $sites + $apps) {$out = $out + $s + "`n"}
    $out = $out.Substring(0, $out.Length - 1)
    $out
  </ScriptBlock>
</TableColumnItem>

In our namespace we are hiding root applications and root virtual directories, and moved their properties to sites. That's another XPath query, but in namespace definition for site. Because we need both sites and applications, scriptblock runs two queries. First it selects all root applications (with path'/') that are assigned to our pool (with name $pn). For each path query selects parent node for application, i.e. site, and returns name of this site. Second query selects names of all non-root applications with the same setting. Next line does the trick that shows all entries inside of column -- it appends each name from combined list to the string variable, and adds symbol <CR> after that. Then script removes the last <CR> and outputs the resulting string. When output module in PowerShell formats the view, it wraps strings inside of column, and inserted <CR> symbols point to end of each row.

Certainly, one could write script block that doesn't use XPath, but this would be much longer code with multiple loops and conditions. XPath takes one query string and returns result with minimal overhead.

Enjoy,

Sergei Antonov

No Comments