Skip to content
Menu
The Lonely Administrator
  • PowerShell Tips & Tricks
  • Books & Training
  • Essential PowerShell Learning Resources
  • Privacy Policy
  • About Me
The Lonely Administrator

PowerShell Pivot Tables

Posted on July 5, 2012August 9, 2013

I was working on a question in the Ask Don and Jeff forum about multidimensional arrays. The ultimate goal of sorts was to create an HTML report that contained in essence a pivot table. This device allows you to slice and dice data so that you can identify trends or patterns. In PowerShell, there aren't too many tools that allow you to "re-organize" an object. For example, say you have a variable that contains the results of querying a few computers for some critical services. But you want a table that shows you the computername as the Y-Axis and each service name as the header on the X axis. For each computer, the intersection should be the status of said property. In other words, a result like this:

Manage and Report Active Directory, Exchange and Microsoft 365 with
ManageEngine ADManager Plus - Download Free Trial

Exclusive offer on ADManager Plus for US and UK regions. Claim now!
Computername Lanmanserver Wuauserv    Bits Spooler Audiosrv
------------ ------------ --------    ---- ------- --------
serenity          Running  Running Running Running  Running
quark                      Stopped Running Running  Running
jdhit-dc01        Running  Running Running Stopped         

At a glance I can see where there might be problems. Blanks indicate the service doesn't exist on that computer. In this case, you might be able to get similar results with some grouping and Format-Table tricks. But more than likely you wouldn't have something you could export to XML or convert to HTML.
Or say you have a directory listing for a handful of file extensions and you want to see a count of each extension for each folder, perhaps something like this:

Directory                                        .ZIP .BAT .PS1 .TXT
---------                                        ---- ---- ---- ----
C:\scripts\AD-Old\New                               0    0    1    1
C:\scripts\AD-Old                                   1    0   82    1
C:\scripts\ADTFM-Scripts\LocalUsersGroups           0    0    8    0
C:\scripts\ADTFM-Scripts                            0    0   55    3
C:\scripts\en-US                                    0    0    1    0
C:\scripts\GPAE                                     0    0    8    3
C:\scripts\modhelp                                  1    0    0    0
C:\scripts\PowerShellBingo                          0    0    4    0
C:\scripts\PS-TFM                                   1    0   69    2
C:\scripts\PSVirtualBox                             0    0    0    1
C:\scripts\quark                                    0    0    0    1
C:\scripts\TechEd2012                               1    0   11    3
C:\scripts\Toolmaking\old                           0    0   10    0
C:\scripts\Toolmaking                               0    0   48    0
C:\scripts                                         55   13 1133  305

That last row is not a sum, but rather the count of each of those file types in the root of C:\Scripts. I did all of this with a function I call New-PSPivotTable.

Function New-PSPivotTable {

# comment based help omitted here 

[cmdletbinding(DefaultParameterSetName="Property")]

Param(
[Parameter(Position=0,Mandatory=$True)]
[object]$Inputobject,
[Parameter()]
[String]$yLabel,
[Parameter(Mandatory=$True)]
[String]$yProperty,
[Parameter(Mandatory=$True)]
[string]$xLabel,
[Parameter(ParameterSetName="Property")]
[string]$xProperty,
[Parameter(ParameterSetName="Count")]
[switch]$Count,
[Parameter(ParameterSetName="Sum")]
[string]$Sum,
[Parameter(ParameterSetName="Sum")]
[ValidateSet("None","KB","MB","GB","TB")]
[string]$Format="None",
[Parameter(ParameterSetName="Sum")]
[ValidateScript({$_ -gt 0})]
[int]$Round
)

Begin {
    Write-Verbose "Starting $($myinvocation.mycommand)"
    $Activity="PS Pivot Table"
    $status="Creating new table"
    Write-Progress -Activity $Activity -Status $Status
    #initialize an array to hold results
    $result=@()
    #if no yLabel then use yProperty name
    if (-Not $yLabel) {
        $yLabel=$yProperty
    }
    Write-Verbose "Vertical axis label is $ylabel"
}
Process {    
    Write-Progress -Activity $Activity -status "Pre-Processing"
    if ($Count -or $Sum) {
        #create an array of all unique property names so that if one isn't 
        #found we can set a value of 0
        Write-Verbose "Creating a unique list based on $xLabel"
        <#
          Filter out blanks. Uniqueness is case sensitive so we first do a 
          quick filtering with Select-Object, then turn each of them to upper
          case and finally get unique uppercase items. 
        #>
        $unique=$inputobject | Where {$_.$xlabel} | 
         Select-Object -ExpandProperty $xLabel -unique | foreach {
           $_.ToUpper()} | Select-Object -unique
         
        Write-Verbose ($unique -join  ',' | out-String).Trim()
      
    } 
    else {
     Write-Verbose "Processing $xLabel for $xProperty"    
    }
    
    Write-Verbose "Grouping objects on $yProperty"
    Write-Progress -Activity $Activity -status "Pre-Processing" -CurrentOperation "Grouping by $yProperty"
    $grouped=$Inputobject | Group -Property $yProperty
    $status="Analyzing data"  
    $i=0
    $groupcount=($grouped | measure).count
    foreach ($item in $grouped ) {
      Write-Verbose "Item $($item.name)"
      $i++
      #calculate what percentage is complete for Write-Progress
      $percent=($i/$groupcount)*100
      Write-Progress -Activity $Activity -Status $Status -CurrentOperation $($item.Name) -PercentComplete $percent
      $obj=new-object psobject -property @{$yLabel=$item.name}   
      #process each group
        #Calculate value depending on parameter set
        Switch ($pscmdlet.parametersetname) {
        
        "Property" {
                    <#
                      take each property name from the horizontal axis and make 
                      it a property name. Use the grouped property value as the 
                      new value
                    #>
                     $item.group | foreach {
                        $obj | Add-member Noteproperty -name "$($_.$xLabel)" -value $_.$xProperty
                      } #foreach
                    }
        "Count"  {
                    Write-Verbose "Calculating count based on $xLabel"
                     $labelGroup=$item.group | Group-Object -Property $xLabel 
                     #find non-matching labels and set count to 0
                     Write-Verbose "Finding 0 count entries"
                     #make each name upper case
                     $diff=$labelGroup | Select-Object -ExpandProperty Name -unique | 
                     Foreach { $_.ToUpper()} |Select-Object -unique
                     
                     #compare the master list of unique labels with what is in this group
                     Compare-Object -ReferenceObject $Unique -DifferenceObject $diff | 
                     Select-Object -ExpandProperty inputobject | foreach {
                        #add each item and set the value to 0
                        Write-Verbose "Setting $_ to 0"
                        $obj | Add-member Noteproperty -name $_ -value 0
                     }
                     
                     Write-Verbose "Counting entries"
                     $labelGroup | foreach {
                        $n=($_.name).ToUpper()
                        write-verbose $n
                        $obj | Add-member Noteproperty -name $n -value $_.Count -force
                    } #foreach
                 }
         "Sum"  {
                    Write-Verbose "Calculating sum based on $xLabel using $sum"
                    $labelGroup=$item.group | Group-Object -Property $xLabel 
                 
                     #find non-matching labels and set count to 0
                     Write-Verbose "Finding 0 count entries"
                     #make each name upper case
                     $diff=$labelGroup | Select-Object -ExpandProperty Name -unique | 
                     Foreach { $_.ToUpper()} |Select-Object -unique
                     
                     #compare the master list of unique labels with what is in this group
                     Compare-Object -ReferenceObject $Unique -DifferenceObject $diff | 
                     Select-Object -ExpandProperty inputobject | foreach {
                        #add each item and set the value to 0
                        Write-Verbose "Setting $_ sum to 0"
                        $obj | Add-member Noteproperty -name $_ -value 0
                     }
                     
                     Write-Verbose "Measuring entries"
                     $labelGroup | foreach {
                        $n=($_.name).ToUpper()
                        write-verbose "Measuring $n"
                        
                        $measure= $_.Group | Measure-Object -Property $Sum -sum
                        if ($Format -eq "None") {
                            $value=$measure.sum
                        }
                        else {
                            Write-Verbose "Formatting to $Format"
                             $value=$measure.sum/"1$Format"
                            }
                        if ($Round) {
                            Write-Verbose "Rounding to $Round places"
                            $Value=[math]::Round($value,$round)
                        }
                        $obj | Add-member Noteproperty -name $n -value $value -force
                    } #foreach
                }        
        } #switch

        #add each object to the results array
      $result+=$obj
    } #foreach item
} #process
End {
    Write-Verbose "Writing results to the pipeline"
    $result
    Write-Verbose "Ending $($myinvocation.mycommand)"
    Write-Progress -Completed -Activity $Activity -Status "Ending"
}
} #end function

There is a lot going on here but I've tried to use comments and Write-Verbose statements to make it easier to track. In short, the function takes a collection of data and builds a table based on the property names and values you specify. You can build a table on a property like I did with the services. You can create a table based on count or on size. For the latter you need to also specify what property name to measure to calculate the size.

The function is writing an object to the pipeline which means you can transform the output however you need it.

PS C:\> $files=dir c:\scripts -include *.ps1,*.txt,*.zip,*.bat -recurse
PS C:\> $pivot=New-PSPivotTable $files -yProperty Directory -xlabel Extension -sum length -round 2 -format kb
PS C:\> $pivot[0] | format-table -auto

Directory             .ZIP .BAT .PS1 .TXT
---------             ---- ---- ---- ----
C:\scripts\AD-Old\New    0    0 2.32 1.49
PS C:\> $pivot | convertto-html -Title "Extension Pivot" | out-file extpivot.htm

Honestly, I think the best thing is for you to download New-PSPivotTable and try it out. The download has full help and examples which you should read. I really hope you find it useful and if you run into some scenarios where this doesn't work the way you think it should, please let me know.


Behind the PowerShell Pipeline

Share this:

  • Click to share on X (Opens in new window) X
  • Click to share on Facebook (Opens in new window) Facebook
  • Click to share on Mastodon (Opens in new window) Mastodon
  • Click to share on LinkedIn (Opens in new window) LinkedIn
  • Click to share on Pocket (Opens in new window) Pocket
  • Click to share on Reddit (Opens in new window) Reddit
  • Click to print (Opens in new window) Print
  • Click to email a link to a friend (Opens in new window) Email

Like this:

Like Loading...

Related

4 thoughts on “PowerShell Pivot Tables”

  1. Pingback: PowerShell – Diversas « Alex Souza
  2. Aman Dhally says:
    July 6, 2012 at 6:03 am

    Liked it…keep it up the Good Work..

    thanks
    aman

  3. alzdba says:
    July 9, 2012 at 3:53 am

    Thank you for the function.
    I noticed it produced uncorrect results in my tests.
    I first noticed it didn’t report on my top-level folder, then missing result values (0.00).
    The problem was it needs a sort on fullname to produce the currect results.

    $files = dir c:\temp -include *.ps1,*.txt,*.zip,*.csv -recurse | sort fullname

    1. Jeffery Hicks says:
      July 9, 2012 at 7:33 am

      I need to see how you used New-PSPivotTable so see if I can duplicate your problem.

Comments are closed.

reports

Powered by Buttondown.

Join me on Mastodon

The PowerShell Practice Primer
Learn PowerShell in a Month of Lunches Fourth edition


Get More PowerShell Books

Other Online Content

github



PluralSightAuthor

Active Directory ADSI Automation Backup Books CIM CLI conferences console Friday Fun FridayFun Function functions Get-WMIObject GitHub hashtable HTML Hyper-V Iron Scripter ISE Measure-Object module modules MrRoboto new-object objects Out-Gridview Pipeline PowerShell PowerShell ISE Profile prompt Registry Regular Expressions remoting SAPIEN ScriptBlock Scripting Techmentor Training VBScript WMI WPF Write-Host xml

©2025 The Lonely Administrator | Powered by SuperbThemes!
%d