Zachary Loeber

The personal website of Zachary Loeber.

Powershell Tip: Save and Load Calculated Property Definitions

Using Export-CliXML and Import-CliXML (as well as some custom code) you can save calculated properties in a file for later use. Although the need for something like this is rather infrequent the exercise can better familiarize you with multiple Powershell techniques and concepts.

Intro

I’ve picked up quite a bit of PowerShell know how over the course of the last few years. I’m going to try and round back to some of the more interesting tricks I’ve picked up and share them in a series of quick tip articles.  This first one I recently discovered as a method to store, then later load, calculated property definitions.

Why would I want to do this? Well a few of my recent projects have involved gathering a large amount of data from either computers or AD and then regurgitating the data into one of several reports. So the same data element may be split by a carriage return for an excel report or a
for an html report (as a small example). In order to accommodate this functionality I chose to bundle in all the report element output in one big hash which also includes the report section data and several other bits of logic.

Here is an example of one report section where the same data is used for two different types of reports.

$Report = @{
  'ReportTypes' = @{
       'ExcelExport' = @{ 
         'ContainerType' = 'Full' 
         'SectionOverride' = $false 
         'TableType' = 'Horizontal' 
         'Properties' = 
              @{n='Name';e={$_.virtualserver}}, 
              @{n='Address';e={$_.address}}, 
              @{n='Port';e={$_.port}}, 
              @{n='Pool';e={$_.pool}}, 
              @{n='Enabled';e={$_.enabled}}, 
              @{n='Availability';e={$_.availability}}, 
              @{n='Persistence Profile';e={[string]$_.persistenceprofile -replace ' ', "`n`r"}}, 
              @{n='Rules';e={[string]$_.rules -replace ' ', "`n`r"}} 
       } 
       'FullDocumentation' = @{ 
          'ContainerType' = 'Full' 
          'SectionOverride' = $false 
          'TableType' = 'Horizontal' 
          'Properties' = 
              @{n='Name';e={'<a id="{0}">{0}</a>' -f $_.virtualserver}}, 
              @{n='Persistence Profile';e={[string]$_.persistenceprofile -replace ' ', "<br />`n`r"}}, 
              @{n='Rules';e={[string]$_.rules -replace ' ', "<br />`n`r"}} 
        } 
   }
}

Later on I select the report section data using the calculated properties for the report I want to create like this:

$SectionData = $ReportData | Select $Report[‘ReportType’][‘FullDocumentation’][‘Properties’]

Calculated Properties

Calculated properties are simply two element hashes with a label and an expression (technically you only need the expression but that expression becomes the element label and that’s ugly). The label key can be “Name”,”Label”,”n”, or “l” and the value is a string. The expression key can be “Expression” or just “e” and its value is a scriptblock (so it must be surrounded by curly braces).

It should be noted that there is nothing special about calculated properties when you define them in separate variables. You are simply defining an array of hashes. In the example code above I could have also written the calculated properties like this:

'Properties' = @(@{n='Name';e={'<a id="{0}">{0}</a>' -f $_.virtualserver}},
@{n='Persistence Profile';e={[string]$_.persistenceprofile -replace ' ', "<br />`n`r"}},
@{n='Rules';e={[string]$_.rules -replace ' ', "<br />`n`r"}})

Getting To The Point

Ok, so how do we save calculated property definitions into an external file? With most data exports that involve basic arrays or psobjects I’d use Export-CSV  Whenever you are looking to save hash data into files your go to command will be Export-CliXML. Export-CliXML will export hash information into an XML format which represents the hash for an almost perfect re-import with Import-CliXML.

Notice I said, “almost perfect”. Export-CliXML is not going to recognize the expression as the scriptblock it is meant to be. Instead it will save it as a plain string. When you re-import the calculated properties you need to convert all the expressions back to a scriptblock otherwise when you use them you will simply get empty results. Here is the example code I put together which shows the top 5 memory consuming processes and the percentage they are utilizing. I create a separate variable which holds the calculated properties, export it to a save file, then import the file into a different variable, update each expression in the resulting array of hashes to be scriptblocks, and then finish by using the resulting variable.

$SaveFile = 'C:\Temp\saveprops.xml' 
$TotalMem = (Get-WMIObject -class 'Win32_ComputerSystem').TotalPhysicalMemory 
$MemUsageProp = @{n='Name';e={$_.ProcessName}}, 
@{n='MemoryUsage';e={[math]::Round((($_.WS/$TotalMem)*100),2)}}
Export-Clixml -InputObject $MemUsageProp -Path $SaveFile
$MemUsageProp2 = Import-Clixml -Path $SaveFile
$MemUsageProp2 | ForEach { 
   $_['e'] = [Scriptblock]::Create($_['e']) 
}
Get-Process | 
  Sort-Object -Property WS -Descending | 
  Select-Object $MemUsageProp2 -First 5

This is a pretty simple example but if we were embedding the calculated properties with several other elements you would have to customize this to fit your needs. Also notice that I only target the ‘e’ element for converting into a scriptblock. It should be a nominal effort to update this to include the ‘expression’ element as well.

comments powered by Disqus