Last active
January 29, 2024 13:37
-
-
Save janis-veinbergs/bf2da138a2cea554f360443dbc700967 to your computer and use it in GitHub Desktop.
PowerShell Out-Tree: View object parent/child relationship within a hierarchy
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Set-StrictMode -Version 3.0 | |
function Out-Tree { | |
<# | |
.SYNOPSIS | |
View object parent/child relationship within a hierarchy | |
.DESCRIPTION | |
Outputs objects as a hierarchy, outputted as string. It is important that properties are sorted. | |
It is very important to sort list before Out-Tree cmdlet by ParentValueProperty. Because if objects come in NOT within sequence, the hierarchy will be "interrupted" and restart | |
.PARAMETER Property | |
Which property to use as item name. If you want to have some sort of concatenated string that conveys multiple fields, use PropertyExpression | |
.PARAMETER PropertyExpression | |
Scriptblock to construct item display value. Access InputObject with $_ | |
.PARAMETER ParentProperty | |
Specify property name that stores values like directory name, some parent id, etc. | |
.PARAMETER ParentValueProperty | |
ParentValueProperty contains actual value that points to its parent specified in ParentProperty. If a match means item goes "under" that item. Equals method is used to compare whether properties match. | |
.PARAMETER MaxDepth | |
Maximum number of indentations that will be outputted. This is NOT a filter, just that if you pass 0 for MaxDepth, output will be flat list. | |
.PARAMETER VerticalSpacer | |
Choose how to format vertical line within hierarchy. | |
.PARAMETER HorizontalSpacer | |
Choose how to format prefix for indentations. The "Prefix" will be (VerticalSpacer + HorizontalSpacer) * Depth | |
.EXAMPLE | |
@{'SomeProp'='One'; 'Parent'=$null},` | |
@{'SomeProp'='Two'; 'Parent'='One'},` | |
@{'SomeProp'='Three'; 'Parent'='One'},` | |
@{'SomeProp'='Four'; 'Parent'='Two'} | ` | |
% { New-Object -TypeName PSObject -Property $_} | Out-Tree -Property "SomeProp" -ParentProperty Parent ParentValueProperty 'SomeProp' | |
Property ordering is important | |
Outputs: | |
One | |
+--Two | |
+--Three | |
Four | |
Notice how four is not "Under" two, because it doesn't follow Two | |
.EXAMPLE | |
@{'SomeProp'='One'; 'Parent'=$null},` | |
@{'SomeProp'='Two'; 'Parent'='One'},` | |
@{'SomeProp'='Three'; 'Parent'='Two'}| ` | |
% { New-Object -TypeName PSObject -Property $_} | Out-Tree -Property "SomeProp" -ParentProperty Parent ParentValueProperty 'SomeProp' | |
Correct order | |
Outputs: | |
One | |
+--Two | |
+--+--Three | |
.EXAMPLE | |
@{'SomeProp'='One'; 'Parent'=$null},` | |
@{'SomeProp'='Two'; 'Parent'='One'},` | |
@{'SomeProp'='Four'; 'Parent'='Two'}| ` | |
% { New-Object -TypeName PSObject -Property $_} | Out-Tree -Property "SomeProp" -ParentProperty Parent ParentValueProperty 'SomeProp' -VerticalSpacer "|" -HorizontalSpacer " " | |
Custom formatting | |
Outputs: | |
One | |
| Two | |
| | Four | |
.EXAMPLE | |
gci -Recurse ~ | select -first 100 | sort pspath | Out-Tree -Property Name -ParentProperty PSParentPath ParentValueProperty PSPath | |
View filesystem hierarchically | |
.EXAMPLE | |
@{'SomeProp'='One'; 'Parent'=$null},` | |
@{'SomeProp'='Two'; 'Parent'='One'} | ` | |
% { New-Object -TypeName PSObject -Property $_} | Out-Tree -PropertyExpression { "$($_.SomeProp) Look, my parent is: $(if ($_.Parent -eq $null) {"(None)"} else {$_.Parent})" } -ParentProperty Parent -ParentValueProperty 'SomeProp' | |
Output name expression | |
Output: | |
One Look, my parent is: (None) | |
+--Two Look, my parent is: One | |
.LINK | |
https://gist.github.com/janis-veinbergs/bf2da138a2cea554f360443dbc700967 | |
#> | |
[OutputType([string])] | |
[CmdletBinding(DefaultParameterSetName = 'Property')] | |
param( | |
[Parameter(Mandatory=$true, Position=0, ParameterSetName = 'Property')] | |
[string]$Property, | |
[Parameter(Mandatory=$true, Position=0, ParameterSetName = 'PropertyExpression')] | |
[ScriptBlock]$PropertyExpression, | |
[Parameter(Mandatory=$true, Position=1)] | |
[string]$ParentProperty, | |
[Parameter(Mandatory=$true, Position=2)] | |
[string]$ParentValueProperty, | |
[Parameter(Mandatory=$true, Position=3, ValueFromPipeline)] | |
[psobject]$InputObject, | |
[int]$MaxDepth = [int]::MaxValue, | |
[string]$VerticalSpacer = '+', | |
[string]$HorizontalSpacer = '--' | |
) | |
begin { | |
[object[]]$breadcrumbs = @() | |
} | |
process { | |
$parentPropertyValue = Select-Object -ExpandProperty $ParentProperty -InputObject $InputObject | |
$parentValuePropertyValue = Select-Object -ExpandProperty $ParentValueProperty -InputObject $InputObject | |
if ($null -eq $parentValuePropertyValue) { | |
$breadcrumbs = @($parentPropertyValue) | |
$depth = 0 | |
} else { | |
#LastIndexOf - The elements are compared to the specified value using the Object.Equals method. If the element type is a nonintrinsic (user-defined) type, the Equals implementation of that type is used. | |
$depth = [array]::LastIndexOf($breadcrumbs, $parentPropertyValue) + 1; | |
if ($depth -eq 0) { | |
# Reset | |
$breadcrumbs = @() | |
} elseif ($depth -ge 1) { | |
# Step-up | |
$breadcrumbs = $breadcrumbs[0..($depth-1)] | |
} | |
$breadcrumbs += $parentValuePropertyValue | |
# Update depth after breadcrumbs modifications | |
$depth = [array]::LastIndexOf($breadcrumbs, $parentPropertyValue) + 1; | |
} | |
if ($PropertyExpression -ne $null) { | |
$propertyValue = $PropertyExpression.Invoke($InputObject) | |
} else { | |
$propertyValue = Select-Object -ExpandProperty $Property -InputObject $InputObject | |
} | |
$prefix = "$VerticalSpacer$HorizontalSpacer" | |
$realDepth = [Math]::Min($depth, $MaxDepth); | |
Write-Host "$($prefix*$realDepth)$propertyValue" | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment