functions/New-PSFormatXML.ps1


Function New-PSFormatXML {
    [cmdletbinding(SupportsShouldProcess)]
    [alias("nfx")]
    [Outputtype("None", "System.IO.FileInfo")]

    Param(
        [Parameter(Mandatory, ValueFromPipeline, HelpMessage = "Specify an object to analyze and generate or update a ps1xml file.")]
        [object]$InputObject,
        [Parameter(HelpMessage = "Enter a set of properties to include. The default is all.")]
        [string[]]$Properties,
        [Parameter(HelpMessage = "Specify the object typename. If you don't, then the command will use the detected object type from the Inputobject.")]
        [string]$Typename,
        [Parameter(HelpMessage = "Specify whether to create a table or list view")]
        [ValidateSet("Table", "List")]
        [string]$FormatType = "Table",
        [string]$ViewName = "default",
        [Parameter(Mandatory, HelpMessage = "Enter full filename and path for the format.ps1xml file.")]
        [ValidateNotNullOrEmpty()]
        [string]$Path,
        [switch]$Append,
        [switch]$Passthru
    )
    Begin {
        Write-Verbose "[$((Get-Date).TimeofDay) BEGIN ] Starting $($myinvocation.mycommand)"

        #convert the parent path into a real file system path
        $parent = Convert-Path -Path (Split-Path -Path $path)
        #reconstruct the path
        $realPath = Join-Path -Path $parent -ChildPath (Split-Path -Path $path -Leaf)

        if (-Not $Append) {
            Write-Verbose "[$((Get-Date).TimeofDay) BEGIN ] Initializing a new XML document"
            [xml]$Doc = New-Object System.Xml.XmlDocument

            #create declaration
            $dec = $Doc.CreateXmlDeclaration("1.0", "UTF-8", $null)
            #append to document
            [void]$doc.AppendChild($dec)

            $text = @"
 
format type data generated $(Get-Date)
by $env:USERDOMAIN\$env:username
 
"@


            [void]$doc.AppendChild($doc.CreateComment($text))

            #create Configuration Node
            $config = $doc.CreateNode("element", "Configuration", $null)
            $viewdef = $doc.CreateNode("element", "ViewDefinitions", $null)

        }
        elseif ($Append -AND (Test-Path -path $realPath)) {
            Write-Verbose "[$((Get-Date).TimeofDay) BEGIN ] Opening format document $RealPath"
            [xml]$Doc = Get-Content -Path $realPath
        }
        else {
            Throw "Failed to find $Path"
        }
        $view = $doc.CreateNode("element", "View", $null)
        [void]$view.AppendChild($doc.CreateComment("Created $(Get-Date) by $env:USERDOMAIN\$env:username"))
        $name = $doc.CreateElement("Name")
        $name.InnerText = $ViewName
        [void]$view.AppendChild($name)
        $select = $doc.createnode("element", "ViewSelectedBy", $null)

        if ($FormatType -eq 'Table') {
            $table = $doc.CreateNode("element", "TableControl", $null)
            $headers = $doc.CreateNode("element", "TableHeaders", $null)
            $TableRowEntries = $doc.CreateNode("element", "TableRowEntries", $null)
            $entry = $doc.CreateNode("element", "TableRowEntry", $null)
        }
        else {
            $list = $doc.CreateNode("element", "ListControl", $null)
            $ListEntries = $doc.CreateNode("element", "ListEntries", $null)
            $ListEntry = $doc.CreateNode("element", "ListEntry", $null)
        }
        $counter = 0
    } #begin

    Process {
        If ($counter -eq 0) {

            if ($Typename) {
                $tname = $TypeName
            }
            else {
                $tname = $Inputobject.psobject.typenames[0]
            }
            $tnameElement = $doc.CreateElement("TypeName")
            $tnameElement.InnerText = $tname
            [void]$select.AppendChild($tnameElement)
            [void]$view.AppendChild($select)

            Write-Verbose "[$((Get-Date).TimeofDay) PROCESS] Creating an format document for object type $tname "

            #get property members
            $objProperties = $Inputobject.psobject.properties
            $members = @()
            if ($properties) {
                foreach ($property in $properties) {
                    Write-Verbose "[$((Get-Date).TimeofDay) PROCESS] Validating property: $property"
                    $test = ($objProperties).where( {$_.name -like $property})
                    if ($test) {
                        $members += $test
                    }
                    else {
                        Write-Warning "Can't find a property called $property on this object. Did you enter it correctly?"
                    }
                }
                #$members = $members.where( {$properties -contains $_.name})
            }
            else {
                #use auto detected properties
                Write-Verbose "[$((Get-Date).TimeofDay) PROCESS] Auto detected $($objProperties.name.count) properties"
                $members = $objProperties
            }
            Write-Verbose "[$((Get-Date).TimeofDay) PROCESS] Processing $($members.name.count) properties"

            $comment = @"
 
            By default the entries use property names, but you can replace them with scriptblocks.
            <Scriptblock>`$_.foo /1mb -as [int]</Scriptblock>
 
"@

            if ($FormatType -eq 'Table') {

                $items = $doc.CreateNode("element", "TableColumnItems", $null)
                [void]$items.AppendChild($doc.CreateComment($comment))

                foreach ($member in $members) {
                    $th = $doc.createNode("element", "TableColumnHeader", $null)

                    Write-Verbose "[$((Get-Date).TimeofDay) PROCESS]... $($member.name)"
                    $label = $doc.CreateElement("Label")
                    $label.InnerText = $member.Name
                    [void]$th.AppendChild($label)
                    $width = $doc.CreateElement("Width")
                    <#
                        set initial width to value length + 3
                        Use the width of whichever is longer, the name or value
                    #>


                    $longest = $Member.value.tostring().length, $member.name.length | Sort-Object | Select-Object -last 1
                    $width.InnerText = $longest + 3
                    [void]$th.AppendChild($width)

                    $align = $doc.CreateElement("Alignment")
                    $align.InnerText = "left"
                    [void]$th.AppendChild($align)
                    [void]$headers.AppendChild($th)

                    $tci = $doc.CreateNode("element", "TableColumnItem", $null)
                    $prop = $doc.CreateElement("PropertyName")
                    $prop.InnerText = $member.name
                    [void]$tci.AppendChild($prop)
                    [void]$items.AppendChild($tci)
                }
            }
            else {
                #create a list
                $items = $doc.CreateNode("element", "ListItems", $null)
                [void]$items.AppendChild($doc.CreateComment($comment))
                foreach ($member in $members) {
                    $li = $doc.createNode("element", "ListItem", $null)

                    Write-Verbose "[$((Get-Date).TimeofDay) PROCESS]... $($member.name)"

                    $li = $doc.CreateNode("element", "ListItem", $null)
                    $label = $doc.CreateElement("Label")
                    $label.InnerText = $member.Name
                    [void]$li.AppendChild($label)
                    $prop = $doc.CreateElement("PropertyName")
                    $prop.InnerText = $member.name
                    [void]$li.AppendChild($prop)
                    [void]$items.AppendChild($li)
                }
            }
            $counter++
        }
        else {
            Write-Warning "Ignoring this object. I only need one instance of an object to create the ps1xml file. Your file will still be created."
        }
    } #process

    End {
        Write-Verbose "[$((Get-Date).TimeofDay) END ] Finalizing XML"
        #Add elements to each parent
        if ($FormatType -eq 'Table') {
            [void]$entry.AppendChild($items)
            [void]$TableRowEntries.AppendChild($entry)
            [void]$table.AppendChild($doc.CreateComment("Delete the AutoSize node if you want to use the defined widths."))
            $auto = $doc.CreateElement("AutoSize")
            [void]$table.AppendChild($auto)
            [void]$table.AppendChild($headers)
            [void]$table.AppendChild($TableRowEntries)

            [void]$view.AppendChild($table)
        }
        else {
            [void]$listentry.AppendChild($items)
            [void]$listentries.AppendChild($listentry)
            [void]$list.AppendChild($listentries)
            [void]$view.AppendChild($list)
        }

        if ($append) {
            Write-Verbose "[$((Get-Date).TimeofDay) END ] Appending to existing XML"
            [void]$doc.Configuration.ViewDefinitions.AppendChild($View)
        }
        else {
            [void]$viewdef.AppendChild($view)
            [void]$config.AppendChild($viewdef)
            [void]$doc.AppendChild($config)
        }

        Write-Verbose "[$((Get-Date).TimeofDay) END ] Saving to $realpath"
        if ($PSCmdlet.ShouldProcess($realPath, "Adding $formattype view $viewname")) {
            $doc.Save($realPath)
            if ($Passthru) {
                Get-Item $realPath
            }
        }

        Write-Verbose "[$((Get-Date).TimeofDay) END ] Ending $($myinvocation.mycommand)"

    } #end

} #close New-PSFormatXML