Power$nippets

Boost your shell with reusable code stripped to its essentials

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

Last updated on 29 Jun 2020
Published on 6 Nov 2017