PSPivotTable.psm1
#requires -version 4.0 Function New-PSPivotTable { <# .SYNOPSIS Create a pivot table in the PowerShell console. .DESCRIPTION This command takes the result of a PowerShell expression and creates a pivot table object. You can use this object to analyze data patterns. For example, you could get a directory listing and then prepare a table showing the size of different file extensions for each folder. .PARAMETER Data This is the collection of data object to analyze. You must enter a parameter value. See help examples. .PARAMETER yLabel This is an alternative value for the the "Y-Axis". If you don't specify a value then the yProperty value will be used. Use this parameter when you want to rename a property value such as Machinename to Computername. .PARAMETER yProperty The property name to pivot on. This is the "Y-Axis" of the pivot table. .PARAMETER xLabel The property name that you want to pivot on horizontally. The value of each corresponding object property becomes the label on the "X-Axis". For example, if the Data is a collection of service objects and xLabel is Name, each column will be labeled with the name of a service object, e.g. Alerter or BITS. See help examples. .PARAMETER xProperty The property name that you want to analyze for each object. This will be used for calculating table values. See help examples. .PARAMETER Count Instead of getting a property for each xLabel value, return a total count of each. .PARAMETER Sum Instead of getting a property for each xLabel value, return a total sum of each. The parameter value is the object property to measure. .PARAMETER Format If using -Sum the default output is typically bytes, depending on the object. Use KB, MB, GB or TB to reformat the sum accordingly. .PARAMETER Round Use this value to round a sum, especially if you are formatting it to something like KB. .PARAMETER Sort You have the option of sorting results when using -Count or -Sum. You can sort on the value, i.e. count or sum, or on the property name. The default sort option is none but you can specify Ascending or Descending. .PARAMETER SortKey Specify if you want to sort on the value or property name. The default is Value. This parameter has no effect unless you also use -Sort. .EXAMPLE PS C:\> $svc="Lanmanserver","Wuauserv","DNS","ADWS" PS C:\> $computers="chi-dc01","chi-dc02","chi-dc04" PS C:\> $data = Get-Service -name $svc -ComputerName $computers PS C:\> New-PSPivotTable $data -ylabel Computername -yProperty Machinename -xlabel Name -xproperty Status -verbose | format-table -autosize Computername ADWS DNS Lanmanserver Wuauserv ------------ ---- --- ------------ -------- chi-dc01 Running Running Running Running chi-dc02 Running Stopped Running Running chi-dc04 Running Running Running Stopped Create a table that shows the status of each service on each computer. The yLabel parameter renames the property so that instead of Machinename it shows Computername. The xLabel is the property name to analyze, in this case the service name. The xProperty value of each service becomes the table value. .EXAMPLE PS C:\> $files = dir c:\scripts -include *.ps1,*.txt,*.zip,*.bat -recurse PS C:\> New-PSPivotTable $files -yProperty Directory -xLabel Extension -count | format-table -auto 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\Toolmaking 0 0 48 0 C:\scripts 55 13 1133 305 Display a table report that shows the count of each file type in each directory. PS C:\> New-PSPivotTable $files -yProperty Directory -xLabel Extension -count | ConvertTo-HTML -title "Script Report" -CssUri C:\scripts\blue.css -PreContent "<H3>C:\Scripts</H3>" -PostContent "<H6>$(Get-Date)</H6>" | Out-File C:\work\Scripts.htm -Encoding ascii Create a pivot table similar to the example above and create an HTML report. .EXAMPLE PS C:\> $files = dir -path c:\scripts\*.ps*,*.txt,*.zip,*.bat PS C:\> New-PSPivotTable $files -yProperty Directory -xlabel Extension -Sum Length -round 2 -format kb | format-table -auto Directory .PS1 .PSM1 .PS1XML .PSSC .PSD1 .TXT .ZIP .BAT --------- ---- ----- ------- ----- ----- ---- ---- ---- C:\scripts 8542 500.88 137.82 11.95 9.16 22473.86 2402.63 26.32 Analyse files by extension, measuring the total size of each extension. The value is formatted as KB to 2 decimal points. .EXAMPLE PS C:\> New-PSPivotTable $files -yProperty Directory -xLabel Extension -Count -Sort Ascending Directory : C:\scripts PSSC : 3 PSD1 : 7 BAT : 17 PS1XML : 24 PSM1 : 50 ZIP : 74 TXT : 443 PS1 : 2077 Process the collection of script files and analyze by the count of each file type. The result is sorted by the count value in ascending order. Note that the actual output would include the period as part of the extension. .EXAMPLE PS C:\> $path = "\\chi-fp02\shared" Define a variable for a path to be analyzed. PS C:\> $files = dir $path -recurse -File | Select *, @{Name="Age";Expression={(Get-Date)-$_.LastWriteTime}}, @{Name="Bucket";Expression={ Switch([int]((Get-Date)-$_.LastWriteTime).TotalDays) { {$_ -gt 365} {'365Plus' ; Break} {$_ -gt 180 -AND $_ -le 365} {'1Yr' ; Break} {$_ -gt 90 -AND $_ -le 180} {'6Mo' ; Break} {$_ -gt 30 -AND $_ -le 90} {'3Mo' ; Break} {$_ -gt 7 -AND $_ -le 30} { '1Mo'; Break } {$_ -gt 0 -AND $_ -le 7} { '1Wk' ; Break } Default { 'Today' } } #switch }} Get all files and include some aging information based on the last write time. PS C:\> New-PSPivotTable $files -yProperty Directory -xLabel Bucket -count | Out-GridView -title "File Aging" Create a pivot table on the directory and aging buckets and display results with Out-Gridview. .EXAMPLE PS C:\> $data = get-eventlog system -newest 1000 Get 1000 recent events from the System eventlog. PS C:\> New-PSPivotTable -Data $data -Count -yProperty EntryType -xLabel Source | Out-Gridview -title "System Sources" Create a pivot table with a Y column of Entry Type and the X axis labels of the different sources. The value under each column will be the total count of entries by source. The results are piped to Out-Gridview for viewing and further sorting or filtering. PS C:\> $e = ($data).Where({$_.EntryType -eq 'error'}) Create a variable with only error entries. PS C:\> New-PSPivotTable $e -yProperty EntryType -xLabel Source -count -sort Descending EntryType : Error SCHANNEL : 36 DCOM : 23 NTFS : 5 KERBEROS : 1 DISK : 1 Create a pivot table on the error source, sorted by count in descending order. PS C:\> $k = ($data).Where({$_.source -match 'kernel'}) Create a variable of all entries where the source includes 'kernel' in the name. PS C:\> New-PSPivotTable $k -yProperty EntryType -xLabel Source -count -sort Ascending -SortKey Name EntryType : Warning MICROSOFT-WINDOWS-KERNEL-BOOT : 0 MICROSOFT-WINDOWS-KERNEL-GENERAL : 0 MICROSOFT-WINDOWS-KERNEL-PNP : 36 MICROSOFT-WINDOWS-KERNEL-POWER : 0 MICROSOFT-WINDOWS-KERNEL-PROCESSOR-POWER : 48 EntryType : Information MICROSOFT-WINDOWS-KERNEL-BOOT : 49 MICROSOFT-WINDOWS-KERNEL-GENERAL : 42 MICROSOFT-WINDOWS-KERNEL-PNP : 0 MICROSOFT-WINDOWS-KERNEL-POWER : 10 MICROSOFT-WINDOWS-KERNEL-PROCESSOR-POWER : 28 Create a pivot table for each entry type showing the count of each source. The results are sorted by the source name. .NOTES NAME: New-PSPivotTable AUTHOR: Jeffery Hicks (@JeffHicks) VERSION: 2.1.2 LASTEDIT: 30 March 2016 This function was first published and described at http://jdhitsolutions.com/blog/powershell/2434/powershell-pivot-tables/ Learn more about PowerShell: http://jdhitsolutions.com/blog/essential-powershell-resources/ **************************************************************** * DO NOT USE IN A PRODUCTION ENVIRONMENT UNTIL YOU HAVE TESTED * * THOROUGHLY IN A LAB ENVIRONMENT. USE AT YOUR OWN RISK. IF * * YOU DO NOT UNDERSTAND WHAT THIS SCRIPT DOES OR HOW IT WORKS, * * DO NOT USE IT OUTSIDE OF A SECURE, TEST SETTING. * **************************************************************** Thanks to kdoblosky for contributing to this module. .LINK Measure-Object Group-Object Select-Object #> [cmdletbinding(DefaultParameterSetName = "Property")] Param( [Parameter( Position = 0, Mandatory, HelpMessage = "What is the data to analyze?" )] [ValidateNotNullorEmpty()] [object]$Data, [Parameter()] [String]$yLabel, [Parameter( Mandatory, HelpMessage = "What is the Y axis property?" )] [ValidateNotNullorEmpty()] [String]$yProperty, [Parameter( Mandatory, HelpMessage = "What is the X axis label?" )] [ValidateNotNullorEmpty()] [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","PB")] [string]$Format = "None", [Parameter( ParameterSetName = "Sum" )] [ValidateScript({$_ -gt 0})] [int]$Round, [Parameter( ParameterSetName = "Sum" )] [Parameter( ParameterSetName = "Count" )] [ValidateSet("None","Ascending","Descending")] [string]$Sort = "None", [Parameter( ParameterSetName = "Sum" )] [Parameter( ParameterSetName = "Count" )] [ValidateSet("Name","Value")] [string]$SortKey = "Value" ) Begin { Write-Verbose "Starting $($myinvocation.mycommand)" #define some values for Write-Progress $Activity = "PowerShell 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" } #begin Process { Write-Progress -Activity $Activity -status "Pre-Processing" Write-Verbose "Creating a unique list based on $xLabel" <# Filter out null values, but not 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 = $Data | Where {$_.$xlabel -ne $Null} | Select-Object -ExpandProperty $xLabel -unique | foreach { $_.ToUpper()} | Select-Object -unique Write-Verbose ($unique -join ',' | out-String).Trim() Write-Verbose "Grouping objects on $yProperty" Write-Progress -Activity $Activity -status "Pre-Processing" -CurrentOperation "Grouping by $yProperty" $grouped = $Data | 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 $hash = [ordered]@{} #process each group #Calculate value depending on parameter set Switch ($pscmdlet.parametersetname) { "Property" { Write-Verbose "Processing $xLabel for $xProperty" <# take each property name from the horizontal axis and make it a property name. Use the grouped property value as the new value #> #find non-matching labels and set value to Null #make each name upper case $labelGroup = $item.group | Group-Object -Property $xLabel $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 null Write-Verbose "Setting $_ to null" $hash.Add($_,$null) } $item.group | foreach { $v = $_.$xProperty Write-Verbose "Adding $($_.$xLabel) with a value of $v" $hash.Add($($_.$xLabel),$v) } #foreach } #property "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" # Account for blank entries If ([String]::IsNullOrEmpty($_)) { $_ = "[NONE]" } $hash.add($_,0) } Write-Verbose "Counting entries" $labelGroup | foreach { $n = ($_.name).ToUpper() Write-Verbose "$n = $($_.count)" # Account for blank names If ([String]::IsNullOrEmpty($n)) { $n = "[NONE]" } $hash.Add($n,$_.count) } #foreach } #count "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" # Account for blank entries If ([String]::IsNullOrEmpty($_)) { $_ = "[NONE]" } $hash.add($_,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) } # Account for blank names If ([String]::IsNullOrEmpty($n)) { $n = "[NONE]" } $hash.add($n,$value) } #foreach } #Sum } #switch #sort as necessary if ($Sort -ne "None") { Write-Verbose "Sorting order $sort" Write-Verbose "Sorting on $sortkey" #define a hashtable of parameters for Sort-Object $sortParams = @{Property=$SortKey} if ($sort -eq "Descending") { $sortParams.Add("Descending",$True) } $sorted = $hash.GetEnumerator() | Sort-Object @sortParams #rebuild the hash table based on sorting $sorted | foreach -Begin {$hash = [ordered]@{}} -process { $hash.add($_.name,$_.value)} } #add ylabel $hash.Insert(0,$yLabel,$item.name) #add each object to the results array Write-Verbose "Adding object to the results array" $result += [pscustomobject]$hash } #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 } #end function #define an optional alias Set-Alias -Name npt -Value New-PSPivotTable Export-ModuleMember -Function * -Alias * |