Navigation Menu

Skip to content

Instantly share code, notes, and snippets.

@janis-veinbergs
Last active January 29, 2024 13:37
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save janis-veinbergs/bf2da138a2cea554f360443dbc700967 to your computer and use it in GitHub Desktop.
Save janis-veinbergs/bf2da138a2cea554f360443dbc700967 to your computer and use it in GitHub Desktop.
PowerShell Out-Tree: View object parent/child relationship within a hierarchy
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