Includes/PwSh.Fw.Object.psm1
$Script:NS = "PwSh.Object" <# .SYNOPSIS Convert an XML content to a PowerShell Object .DESCRIPTION Convert any XML content to a PSCustomObject. It can then be manipulated like any object. It handles array and nested XML content. It is usefull for example to convert an XML object to a JSON object .PARAMETER InputObject XML object to convert .EXAMPLE [XML]$xml = Get-Content /path/to/file.xml $obj = $xml | ConvertFrom-Xml $json = $obj | ConvertTo-Json .NOTES #> function ConvertFrom-Xml { [CmdletBinding()]Param ( [Parameter(Mandatory = $true,ValueFromPipeLine = $true)][System.Object]$InputObject ) Begin { # eenter($Script:NS + "\" + $MyInvocation.MyCommand) } Process { $OutputObject = New-Object PSObject if ($null -ne $InputObject) { if ($InputObject.HasAttributes) { ForEach ($attr in $InputObject.Attributes) { $OutputObject | Add-Member -MemberType NoteProperty -Name $attr.Name -Value $attr.Value } } if ($InputObject.HasChildNodes) { ForEach ($child in $InputObject.ChildNodes) { $OutputObject | Add-Member -MemberType NoteProperty -Name $child.LocalName -Value @() -ErrorAction SilentlyContinue $OutputObject.($child.LocalName) += ($child | ConvertFrom-Xml) } } } return $OutputObject } End { # eleave($Script:NS + "\" + $MyInvocation.MyCommand) } } <# .SYNOPSIS List object's properties. .DESCRIPTION Get properties of the type of an object. It can be used to filter out default object's type properties. .PARAMETER obj Object of reference .EXAMPLE $s = "this is a test" $s | Get-ObjectProperties .NOTES #> function Get-ObjectProperties { [CmdletBinding()]Param ( [Parameter(Mandatory = $true,ValueFromPipeLine = $true)][System.object]$obj, [string[]]$Exclude ) Begin { # eenter($Script:NS + "\" + $MyInvocation.MyCommand) } Process { if (!$obj) { return } if ($null -eq $obj) { return } Try { $DefaultTypeProps = @( $obj.gettype().GetProperties() | Where-Object { $_.Name -notin $Exclude } | Select-Object -ExpandProperty Name -ErrorAction Stop ) if ($DefaultTypeProps.count -gt 0) { # edevel("Excluding default properties for $($obj.gettype().Fullname):") # edevel($($DefaultTypeProps | Out-String)) } } Catch { ewarn("Failed to extract properties from $($obj.gettype().Fullname): $_") $DefaultTypeProps = @() } @( $DefaultTypeProps ) | Select-Object -Unique } End { # eleave($Script:NS + "\" + $MyInvocation.MyCommand) } } <# .SYNOPSIS Get the usefull properties of an object. .DESCRIPTION Get the properties of an object minus the default object properties. It is the opposite of Get-ObjectProperties. .PARAMETER InputObject Object to inspect .PARAMETER Include For inclusion of a default property that would otherwise been stripped out. .EXAMPLE $obj | Get-CustomObjectProperties .EXAMPLE $obj | Get-CustomObjectProperties -Include Name .NOTES General notes #> function Get-CustomObjectProperties { [CmdletBinding()]Param ( [Parameter(Mandatory = $true,ValueFromPipeLine = $true)][Object]$InputObject, [string[]]$Include ) Begin { # eenter($Script:NS + '\' + $MyInvocation.MyCommand) } Process { $excludeProps = $InputObject | Get-ObjectProperties | Where-Object { $_ -notin $Include } Try { $DefaultTypeProps = @($InputObject.gettype().GetProperties() | Where-Object { $_.Name -notin $excludeProps } | Select-Object -ExpandProperty Name -ErrorAction Stop ) if ($DefaultTypeProps.count -gt 0) { # edevel($($DefaultTypeProps | Out-String)) } } Catch { ewarn("Failed to extract properties from $($obj.gettype().Fullname): $_") $DefaultTypeProps = @() } return @($DefaultTypeProps) | Sort-Object -Unique } End { # eleave($Script:NS + '\' + $MyInvocation.MyCommand) } } <# .SYNOPSIS Merge two objects .DESCRIPTION The `Merge-Object` merge two objects into one. If keys are found in both objects, the keys from InputObject1 are overriden with the values of InputObject2. Keep that in mind to order your objects appropriately. .PARAMETER InputObject1 1st object to merge .PARAMETER InputObject2 2nd object to merge .EXAMPLE Merge-Object -InputObject (Get-Process)[0] -InputObject2 (Get-Item ./file) .NOTES General notes .LINK http://powershelldistrict.com/how-to-combine-powershell-objects/ #> function Merge-Object { [CmdletBinding()]Param ( [Parameter(Mandatory = $true,ValueFromPipeLine = $true)][Object]$InputObject1, [Parameter(Mandatory = $true,ValueFromPipeLine = $true)][Object]$InputObject2 ) Begin { # eenter($Script:NS + '\' + $MyInvocation.MyCommand) } Process { $arguments = @{} foreach ($Property in $InputObject1.psobject.Properties) { $arguments += @{ $Property.Name = $Property.value } } foreach ($Property in $InputObject2.psobject.Properties) { # this syntax avoir duplicate keys $arguments.$($Property.Name) = $Property.value } $OutputObject = [Pscustomobject]$arguments return $OutputObject } End { # eleave($Script:NS + '\' + $MyInvocation.MyCommand) } } <# .SYNOPSIS Merge two or more hashtables .DESCRIPTION Merge multiple hashtables into one. For this cmdlet you can use several syntaxes and you are not limited to two input tables: Using the pipeline: $h1, $h2, $h3 | Merge-Hashtables Using arguments: Merge-Hashtables $h1 $h2 $h3 Or a combination: $h1 | Merge-Hashtables $h2 $h3 .EXAMPLE $h1, $h2, $h3 | Merge-Hashtables .EXAMPLE Merge-Hashtables $h1 $h2 $h3 .EXAMPLE $h1 | Merge-Hashtables $h2 $h3 .NOTES https://stackoverflow.com/questions/8800375/merging-hashtables-in-powershell-how #> Function Merge-Hashtables { $Output = @{} ForEach ($Hashtable in ($Input + $Args)) { If ($Hashtable -is [Hashtable]) { ForEach ($Key in $Hashtable.Keys) { $Output.$Key = $Hashtable.$Key } } } $Output } <# .SYNOPSIS Sort a hashtable .DESCRIPTION Sort a hashtable by Name .PARAMETER InputObject Hashtable to sort .EXAMPLE $h = @{ "this" = "is"; "a" = "test"} $h | Sort-HashTable .NOTES I know Sort is not an approved verb but hey, this function actually DOES sort a hashtable .OUTPUTS The sorted hashtable #> function Sort-HashTable { [CmdletBinding()]Param ( [Parameter(Mandatory = $true, ValueFromPipeLine = $true)][hashtable]$InputObject ) Begin { # eenter($Script:NS + '\' + $MyInvocation.MyCommand) } Process { # return $InputObject.GetEnumerator() | Sort-Object -Property Name $hReturn = [ordered]@{} $InputObject.GetEnumerator() | Sort-Object -Property Name | ForEach-Object { $hReturn.Add($_.Name, $_.Value) } return $hReturn } End { # eleave($Script:NS + '\' + $MyInvocation.MyCommand) } } <# .SYNOPSIS Convert a XML Plist to a PowerShell object .DESCRIPTION Converts an XML PList (property list) in to a usable object in PowerShell. Properties will be converted in to ordered hashtables, the values of each property may be integer, double, date/time, boolean, string, or hashtables, arrays of any these, or arrays of bytes. .PARAMETER plist The property list as an [XML] document object, to be processed. This parameter is mandatory and is accepted from the pipeline. .EXAMPLE $pList = [xml](Get-Content 'somefile.plist') | ConvertFrom-Plist .INPUTS system.xml.document .OUTPUTS system.object .NOTES Original Script / Function / Class assembled by Carl Morris, Morris Softronics, Hooper, NE, USA Initial release - Aug 27, 2018 Rewritten without the use of class .LINK https://github.com/msftrncs/PwshReadXmlPList .FUNCTIONALITY data format conversion #> function ConvertFrom-Plist { [CmdletBinding()]Param ( [Parameter(Mandatory = $true, ValueFromPipeLine = $true)][xml]$plist ) Begin { # eenter($Script:NS + '\' + $MyInvocation.MyCommand) } Process { if ($null -eq $plist.item('plist')) { return $null } else { return (Read-PlistNode -Node $plist.item('plist').FirstChild) } } End { # eleave($Script:NS + '\' + $MyInvocation.MyCommand) } } function Read-PlistNode { [CmdletBinding()][OutputType([System.Object])]Param ( [Parameter(Mandatory = $true, ValueFromPipeLine = $true)][System.Xml.XmlElement]$node ) Begin { # eenter($Script:NS + '\' + $MyInvocation.MyCommand) } Process { if ($node.HasChildNodes) { # edevel "$($node.name) - $($node.'#text')" switch ($node.Name) { array { # for arrays, recurse each node in the subtree, returning an array (forced) , @($node.ChildNodes.foreach{ (Read-PlistNode -Node $_) }) continue } date { # must be a date-time type value element, return its value $node.InnerText -as [datetime] continue } data { # must be a data block value element, return its value as [byte[]] # [convert]::FromBase64String((Read-PlistNode -Node $node.InnerText)) $node.InnerText continue } dict { # for dictionary, return the subtree as a ordered hashtable, with possible recursion of additional arrays or dictionaries $collection = [ordered]@{} $currnode = $node.FirstChild # start at the first child node of the dictionary while ($null -ne $currnode) { if ($currnode.Name -eq 'key') { # edevel "$($currnode.name) - $($currnode.'#text')" # a key in a dictionary, add it to a collection if ($null -ne $currnode.NextSibling) { # edevel "$($currnode.NextSibling.name) - $($currnode.NextSibling.'#text')" # note: keys are forced to [string], insures a $null key is accepted # $collection[$currnode.InnerText] = (Read-PlistNode -Node $currnode.NextSibling) $collection.Add($currnode.InnerText, (Read-PlistNode -Node $currnode.NextSibling)) $currnode = $currnode.NextSibling.NextSibling # skip the next sibling because it was the value of the property } else { throw "Dictionary property value missing!" } } else { throw "Non 'key' element found in dictionary: <$($currnode.Name)>!" } } # return the collected hash table $collection continue } integer { # must be an integer type value element, return its value $node.InnerText -as [int] continue } real { $node.InnerText -as [double] continue } string { # for string, return the value, with possible recursion and # collection $node.InnerText continue } default { # we didn't recognize the element type! throw "Unhandled PLIST property type <$($node.Name)>!" } } } else { # return simple element value (need to check for Boolean datatype, and process value accordingly) switch ($node.Name) { true { $true; continue } # return a Boolean TRUE value false { $false; continue } # return a Boolean FALSE value # default { $node.Value } # return the element value } } } End { # eleave($Script:NS + '\' + $MyInvocation.MyCommand) } } function ConvertTo-CamelCase { [CmdletBinding()]Param ( [Parameter(Mandatory=$true, ValueFromPipeline=$true)] [AllowNull()][AllowEmptyString()] [string]$String ) Begin { # eenter($MyInvocation.MyCommand) } Process { # convert to Title Case $camelCase = (get-culture).TextInfo.ToTitleCase($String) # transforme accent to normal letters $camelCase = $camelCase | Remove-StringLatinCharacters # remove non alphanumeric characters $camelCase = $camelCase -replace '[^a-zA-Z0-9]', '' # convert 1st letter to lowercase $camelCase = $camelCase.Substring(0,1).ToLower() + $camelCase.Substring(1) return $camelCase } End { # eleave($MyInvocation.MyCommand) } } <# .SYNOPSIS Serialize an object to a single string .DESCRIPTION Convert an object to a single string to ease display and debug .PARAMETER InputObject an object (currently, only hashtable are supported) .EXAMPLE $ht = @{'key'="value";'key2'="value2"} This defines a hastable .EXAMPLE $ht | ConvertTo-SingleString This example convert the previously created hastable into a single, serialized string .NOTES General notes #> function ConvertTo-SingleString { [CmdletBinding()]param( [parameter(Mandatory,ValueFromPipeline = $True)] $InputObject ) Begin { } Process { # $InputObject.GetTYpe() switch ($InputObject.GetTYpe()) { 'Hashtable' { # $serialized = ($InputObject.GetEnumerator() | % { "'$($_.Key)'=`"$($_.Value)`"" }) -join ';' $serialized = Foreach ($k in $InputObject.GetEnumerator()) { switch ($k.Value.GetType()) { 'datetime' { "'$($k.Key)'=[System.DateTime]`"$($k.Value)`"" } default { "'$($k.Key)'=`"$($k.Value)`"" } } } $serialized = $serialized -join(';') } default { eerror("Object type '" + $InputObject.GetTYpe() + "' not supported yet.") return $false } } return "@{" + $serialized + "}" } End { } } <# .LINK http://www.lazywinadmin.com/2015/05/powershell-remove-diacritics-accents.html #> function Remove-StringLatinCharacters { PARAM ( [parameter(ValueFromPipeline = $true)] [string]$String ) PROCESS { [Text.Encoding]::ASCII.GetString([Text.Encoding]::GetEncoding("Cyrillic").GetBytes($String)) } } <# .SYNOPSIS Resolve boolean well-known values .DESCRIPTION Boolean are not just (true | false) value. It can by yes/no or 0/1. Resolve-Boolean handle all of this. .PARAMETER var The variable name to check .EXAMPLE true | Resolve-Boolean .EXAMPLE 0 | Resolve-Boolean .EXAMPLE if ((Resolve-Boolean -var "yes") -eq $true) { echo "yes is true" } .NOTES General notes #> function Resolve-Boolean { [CmdletBinding()][OutputType([boolean])]Param ( [Parameter(Mandatory,ValueFromPipeLine = $true)]$var ) Begin { # eenter($MyInvocation.MyCommand) } Process { switch -regex ($var.GetType()) { 'bool*' { return $var } 'int*' { switch ($var) { 0 { return $false } 1 { return $true } } } 'string' { switch -wildcard ($var) { 'false' { return $false } 'true' { return $true } 'n*' { return $false } 'y*' { return $true } } } } return $false } End { # eleave($MyInvocation.MyCommand) } } Set-Alias -Force -Name Resolve-Bool -Value Resolve-Boolean |