ConvertTo-FlatObject
Some cmdlets, like Export-Csv
and ConvertTo-Csv
(described as: “Converts objects into a series of comma-separated value (CSV) variable-length strings.“) expect flat file as input. Meaning, if you supply any complex object like an embedded array, hash table or object, it will output nothing more than:
PS C:\> [PSCustomObject]@{
Array = @("a", "b")
Hashtable = @{a = "b"}
PSCustomObject = ([PSCustomObject]@{a = "b"})
Process = (Get-Process | Select -First 1)
} | ConvertTo-Csv -NoTypeInfo
"Array","Hashtable","PSCustomObject","Process"
"System.Object[]","System.Collections.Hashtable","@{a=b}","System.Diagnostics.Process (ApplicationFrameHost)"
Which is is quiet useless and probably not what you expect. Something similar will also happen if you group objects (using Group-Object
) as the group properties are embedded in a deeper Group object.
This cmdlet might help to resolve this as it recursively flattens every property of any embedded array, hash table or object to property at root of the object.
Cmdlet
Function Flatten ConvertTo-FlatObject { # https://powersnippets.com/flatten-object/
[CmdletBinding()]Param ( # Version 02.00.16, by iRon
[Parameter(ValueFromPipeLine = $True)][Object[]]$Objects,
[String]$Separator = ".", [ValidateSet("", 0, 1)]$Base = 1, [Int]$Depth = 5, [Int]$Uncut = 1,
[String[]]$ToString = ([String], [DateTime], [TimeSpan], [Version], [Enum]), [String[]]$Path = @()
)
$PipeLine = $Input | ForEach {$_}; If ($PipeLine) {$Objects = $PipeLine}
If (@(Get-PSCallStack)[1].Command -eq $MyInvocation.MyCommand.Name -or @(Get-PSCallStack)[1].Command -eq "<position>") {
$Object = @($Objects)[0]; $Iterate = New-Object System.Collections.Specialized.OrderedDictionary
If ($ToString | Where {$Object -is $_}) {$Object = $Object.ToString()}
ElseIf ($Depth) {$Depth--
If ($Object.GetEnumerator.OverloadDefinitions -match "[\W]IDictionaryEnumerator[\W]") {
$Iterate = $Object
} ElseIf ($Object.GetEnumerator.OverloadDefinitions -match "[\W]IEnumerator[\W]") {
$Object.GetEnumerator() | ForEach -Begin {$i = $Base} {$Iterate.($i) = $_; $i += 1}
} Else {
$Names = If ($Uncut) {$Uncut--} Else {$Object.PSStandardMembers.DefaultDisplayPropertySet.ReferencedPropertyNames}
If (!$Names) {$Names = $Object.PSObject.Properties | Where {$_.IsGettable} | Select -Expand Name}
If ($Names) {$Names | ForEach {$Iterate.$_ = $Object.$_}}
}
}
If (@($Iterate.Keys).Count) {
$Iterate.Keys | ForEach {
Flatten-Object @(,$Iterate.$_) $Separator $Base $Depth $Uncut $ToString ($Path + $_)
}
} Else {$Property.(($Path | Where {$_}) -Join $Separator) = $Object}
} ElseIf ($Objects -ne $Null) {
@($Objects) | ForEach -Begin {$Output = @(); $Names = @()} {
New-Variable -Force -Option AllScope -Name Property -Value (New-Object System.Collections.Specialized.OrderedDictionary)
Flatten-Object @(,$_) $Separator $Base $Depth $Uncut $ToString $Path
$Output += New-Object PSObject -Property $Property
$Names += $Output[-1].PSObject.Properties | Select -Expand Name
}
$Output | Select ([String[]]($Names | Select -Unique))
}
}; Set-Alias Flatten ConvertTo-FlatObject
Syntax
<Object[]> Flatten-Object [-Separator <String>] [-Base "" | 0 | 1] [-Depth <Int>] [-Uncut<Int>] [ToString <Type[]>]
Flatten-Object <Object[]> [[-Separator] <String>] [[-Base] "" | 0 | 1] [[-Depth] <Int>] [[-Uncut] <Int>] [[ToString] <Type[]>]
Parameters
-Object[] <Object[]>
The object (or objects) to be flatten.
-Separator <String>
(default: .
)
The separator used between the recursive property names. .
-Depth <Int>
(default: 5
)
The maximal depth of flattening a recursive property. Any negative value will result in an unlimited depth and could cause a infinitive loop.
-Uncut <Int>
(default: 1
)
The number of object iterations that will left uncut. Further object properties will be limited to just the DefaultDisplayPropertySet
. Any negative value will reveal all properties of all objects.
-Base "" | 0 | 1
(default: 1
)
The first index name of an embedded array:
1
, arrays will be 1 based:<Parent>.1
,<Parent>.2
,<Parent>.3
, …0
, arrays will be 0 based:<Parent>.0
,<Parent>.1
,<Parent>.2
, …""
, the first item in an array will be unnamed and than followed with 1:<Parent>
,<Parent>.1
,<Parent>.2
, …
-ToString <Type[]= [String], [DateTime], [TimeSpan]>
(default: [String], [DateTime], [TimeSpan]
)
A list of value types that will be converted to string rather the further flattened. E.g. a [DateTime]
could be flattened with additional properties like Date
, Day
, DayOfWeek
, etc. but will be converted to a single (String) property instead.
The parameter
-Path
is for internal use but could but used to prefix property names.
Examples
PS C:\> New-Object PSObject @{
String = [String]"Text"
Char = [Char]65
Byte = [Byte]66
Int = [Int]67
Long = [Long]68
Null = $Null
Booleans = $False, $True
Decimal = [Decimal]69
Single = [Single]70
Double = [Double]71
Array = @("One", "Two", @("Three", "Four"), "Five")
HashTable = @{city="New York"; currency="Dollar"; postalCode=10021; Etc = @("Three", "Four", "Five")}
Object = New-Object PSObject -Property @{Name = "One"; Value = 1; Text = @("First", "1st")}
} | Flatten
Double : 71
Decimal : 69
Long : 68
Array.1 : One
Array.2 : Two
Array.3.1 : Three
Array.3.2 : Four
Array.4 : Five
Object.Name : One
Object.Value : 1
Object.Text.1 : First
Object.Text.2 : 1st
Int : 67
Byte : 66
HashTable.postalCode : 10021
HashTable.currency : Dollar
HashTable.Etc.1 : Three
HashTable.Etc.2 : Four
HashTable.Etc.3 : Five
HashTable.city : New York
Booleans.1 : False
Booleans.2 : True
String : Text
Char : A
Single : 70
Null :
Flatten grouped objects:
$csv | Group Name | Flatten | Format-Table # https://stackoverflow.com/a/47409634/1701026
Flatten common objects:
$MyInvocation | Flatten-Object
Get-Process | Select -First 1 | Flatten-Object
Or a list (array) of system objects:
Get-Service | Flatten-Object -Depth 3 | Export-CSV Service.csv
Note that a command as below could take hours to complete:
Get-Process | Flatten-Object | Export-CSV Process.csv
Why? because it results in a table with a few hundred rows and several thousand columns. So if you if would like to use this for flatting process, you beter limit the number of rows (using the Where-Object
cmdlet) or the number of columns (using the Select-Object
cmdlet).
The Flatten-Object
cmdlet was originally published at StackOverflow