Functions/Show-Object.ps1

Function Show-Object {
<#
.SYNOPSIS
    Takes an object and displays a new window containing the object, and you can drill down on its properties.
.DESCRIPTION
    Takes an object and displays a new window containing the object, and you can drill down on its properties.
.EXAMPLE
    Show-Object (Get-Date)
.NOTES
    #############################################################################
    ##
    ## Show-Object
    ##
    ## From Windows PowerShell Cookbook (O'Reilly)
    ## by Lee Holmes (http://www.leeholmes.com/guide)
    ##
    ##############################################################################
#>


    [CmdletBinding(ConfirmImpact='Low')]
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidAssignmentToAutomaticVariable','')]
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingInvokeExpression','')]
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSReviewUnusedParameter','')]
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseProcessBlockForPipelineCommand','')]
    param(
        [Parameter(ValueFromPipeline,Position=0)]
        $InputObject
    )

    Set-StrictMode -Version 3

    Add-Type -AssemblyName System.Windows.Forms

    ## Figure out the variable name to use when displaying the
    ## object navigation syntax. To do this, we look through all
    ## of the variables for the one with the same object identifier.
    $rootVariableName = get-childitem -path variable:\* -Exclude InputObject,Args |
        Where-Object {
            $_.Value -and
            ($_.Value.GetType() -eq $InputObject.GetType()) -and
            ($_.Value.GetHashCode() -eq $InputObject.GetHashCode())
    }

    ## If we got multiple, pick the first
    $rootVariableName = $rootVariableName| foreach-object {$_.name}  | Select-object -First 1

    ## If we didn't find one, use a default name
    if(-not $rootVariableName)
    {
        $rootVariableName = 'InputObject'
    }

    ## A function to add an object to the display tree
    function PopulateNode
    {
        <#
                .SYNOPSIS
                Describe purpose of "PopulateNode" in 1-2 sentences.
 
                .DESCRIPTION
                Add a more complete description of what the function does.
 
                .PARAMETER node
                Describe parameter -node.
 
                .PARAMETER object
                Describe parameter -object.
 
                .EXAMPLE
                PopulateNode -node Value -object Value
                Describe what this call does
 
                .NOTES
                Place additional notes here.
 
                .LINK
                URLs to related sites
                The first link is opened by Get-Help -Online PopulateNode
 
                .INPUTS
                List of input types that are accepted by this function.
 
                .OUTPUTS
                List of output types produced by this function.
        #>


        ## If we've been asked to add a NULL object, just return

        [CmdletBinding()]
        param
        (
            $node,

            $object
        )

        if(-not $object) {
            write-output -InputObject $null
        }

        ## If the object is a collection, then we need to add multiple
        ## children to the node
        if([System.Management.Automation.LanguagePrimitives]::GetEnumerator($object))
        {
            ## Some very rare collections don't support indexing (i.e.: $foo[0]).
            ## In this situation, PowerShell returns the parent object back when you
            ## try to access the [0] property.
            $isOnlyEnumerable = $object.GetHashCode() -eq $object[0].GetHashCode()

            ## Go through all the items
            $count = 0
            foreach($childObjectValue in $object)
            {
                ## Create the new node to add, with the node text of the item and
                ## value, along with its type
                $newChildNode = New-Object -TypeName Windows.Forms.TreeNode
                $newChildNode.Text = "$($node.Name)[$count] = $childObjectValue : " +
                    $childObjectValue.GetType()

                ## Use the node name to keep track of the actual property name
                ## and syntax to access that property.
                ## If we can't use the index operator to access children, add
                ## a special tag that we'll handle specially when displaying
                ## the node names.
                if($isOnlyEnumerable)
                {
                    $newChildNode.Name = '@'
                }

                $newChildNode.Name += "[$count]"
                $null = $node.Nodes.Add($newChildNode)

                ## If this node has children or properties, add a placeholder
                ## node underneath so that the node shows a '+' sign to be
                ## expanded.
                AddPlaceholderIfRequired -node $newChildNode -object $childObjectValue

                $count++
            }
        }
        else
        {
            ## If the item was not a collection, then go through its
            ## properties
            foreach($child in $object.PSObject.Properties)
            {
                ## Figure out the value of the property, along with
                ## its type.
                $childObject = $child.Value
                $childObjectType = $null
                if($childObject)
                {
                    $childObjectType = $childObject.GetType()
                }

                ## Create the new node to add, with the node text of the item and
                ## value, along with its type
                $childNode = New-Object -TypeName Windows.Forms.TreeNode
                $childNode.Text = $child.Name + " = $childObject : $childObjectType"
                $childNode.Name = $child.Name
                $null = $node.Nodes.Add($childNode)

                ## If this node has children or properties, add a placeholder
                ## node underneath so that the node shows a '+' sign to be
                ## expanded.
                AddPlaceholderIfRequired -node $childNode -object $childObject
            }
        }
    }

    ## A function to add a placeholder if required to a node.
    ## If there are any properties or children for this object, make a temporary
    ## node with the text "..." so that the node shows a '+' sign to be
    ## expanded.
    function AddPlaceholderIfRequired
    {
        <#
                .SYNOPSIS
                Describe purpose of "AddPlaceholderIfRequired" in 1-2 sentences.
 
                .DESCRIPTION
                Add a more complete description of what the function does.
 
                .PARAMETER node
                Describe parameter -node.
 
                .PARAMETER object
                Describe parameter -object.
 
                .EXAMPLE
                AddPlaceholderIfRequired -node Value -object Value
                Describe what this call does
 
                .NOTES
                Place additional notes here.
 
                .LINK
                URLs to related sites
                The first link is opened by Get-Help -Online AddPlaceholderIfRequired
 
                .INPUTS
                List of input types that are accepted by this function.
 
                .OUTPUTS
                List of output types produced by this function.
        #>


        [CmdletBinding()]
        param
        (
            $node,

            $object
        )
        if(-not $object) { write-output -InputObject $null }

        if([System.Management.Automation.LanguagePrimitives]::GetEnumerator($object) -or
            @($object.PSObject.Properties))
        {
            $null = $node.Nodes.Add( (New-Object -TypeName Windows.Forms.TreeNode -ArgumentList '...') )
        }
    }

    ## A function invoked when a node is selected.
    function OnAfterSelect
    {
        <#
                .SYNOPSIS
                Describe purpose of "OnAfterSelect" in 1-2 sentences.
 
                .DESCRIPTION
                Add a more complete description of what the function does.
 
                .PARAMETER Sender
                Describe parameter -Sender.
 
                .PARAMETER TreeViewEventArgs
                Describe parameter -TreeViewEventArgs.
 
                .EXAMPLE
                OnAfterSelect -Sender Value -TreeViewEventArgs Value
                Describe what this call does
 
                .NOTES
                Place additional notes here.
 
                .LINK
                URLs to related sites
                The first link is opened by Get-Help -Online OnAfterSelect
 
                .INPUTS
                List of input types that are accepted by this function.
 
                .OUTPUTS
                List of output types produced by this function.
        #>


        [CmdletBinding()]
        param($Sender, $TreeViewEventArgs)

        ## Determine the selected node
        $nodeSelected = $Sender.SelectedNode

        ## Walk through its parents, creating the virtual
        ## PowerShell syntax to access this property.
        $nodePath = GetPathForNode -Node $nodeSelected

        ## Now, invoke that PowerShell syntax to retrieve
        ## the value of the property.
        $resultObject = Invoke-Expression -Command $nodePath
        $outputPane.Text = $nodePath

        ## If we got some output, put the object's member
        ## information in the text box.
        if($resultObject)
        {
            $members = Get-Member -InputObject $resultObject | Out-String
            $outputPane.Text += "`n" + $members
        }
    }

    ## A function invoked when the user is about to expand a node
    function OnBeforeExpand
    {
        <#
                .SYNOPSIS
                Describe purpose of "OnBeforeExpand" in 1-2 sentences.
 
                .DESCRIPTION
                Add a more complete description of what the function does.
 
                .PARAMETER Sender
                Describe parameter -Sender.
 
                .PARAMETER TreeViewCancelEventArgs
                Describe parameter -TreeViewCancelEventArgs.
 
                .EXAMPLE
                OnBeforeExpand -Sender Value -TreeViewCancelEventArgs Value
                Describe what this call does
 
                .NOTES
                Place additional notes here.
 
                .LINK
                URLs to related sites
                The first link is opened by Get-Help -Online OnBeforeExpand
 
                .INPUTS
                List of input types that are accepted by this function.
 
                .OUTPUTS
                List of output types produced by this function.
        #>


        [CmdletBinding()]
        param($Sender, $TreeViewCancelEventArgs)

        ## Determine the selected node
        $selectedNode = $TreeViewCancelEventArgs.Node

        ## If it has a child node that is the placeholder, clear
        ## the placeholder node.
        if($selectedNode.FirstNode -and
            ($selectedNode.FirstNode.Text -eq '...'))
        {
            $selectedNode.Nodes.Clear()
        }
        else
        {
            write-output -InputObject $null
        }

        ## Walk through its parents, creating the virtual
        ## PowerShell syntax to access this property.
        $nodePath = GetPathForNode -Node $selectedNode

        ## Now, invoke that PowerShell syntax to retrieve
        ## the value of the property.
        Invoke-Expression -Command "`$resultObject = $nodePath"

        ## And populate the node with the result object.
        PopulateNode -node $selectedNode -object $resultObject
    } #endfunction OnBeforeExpand

    ## A function to handle keypresses on the form.
    ## In this case, we capture ^C to copy the path of
    ## the object property that we're currently viewing.
    function OnKeyPress
    {
        <#
                .SYNOPSIS
                Describe purpose of "OnKeyPress" in 1-2 sentences.
 
                .DESCRIPTION
                Add a more complete description of what the function does.
 
                .PARAMETER Sender
                Describe parameter -Sender.
 
                .PARAMETER KeyPressEventArgs
                Describe parameter -KeyPressEventArgs.
 
                .EXAMPLE
                OnKeyPress -Sender Value -KeyPressEventArgs Value
                Describe what this call does
 
                .NOTES
                Place additional notes here.
 
                .LINK
                URLs to related sites
                The first link is opened by Get-Help -Online OnKeyPress
 
                .INPUTS
                List of input types that are accepted by this function.
 
                .OUTPUTS
                List of output types produced by this function.
        #>


        [CmdletBinding()]
        param($Sender, $KeyPressEventArgs)

        ## [Char] 3 = Control-C
        if($KeyPressEventArgs.KeyChar -eq 3)
        {
            $KeyPressEventArgs.Handled = $true

            ## Get the object path, and set it on the clipboard
            $node = $Sender.SelectedNode
            $nodePath = GetPathForNode -Node $node
            [System.Windows.Forms.Clipboard]::SetText($nodePath)

            $form.Close()
        }
    } #endfunction OnKeyPress

    ## A function to walk through the parents of a node,
    ## creating virtual PowerShell syntax to access this property.
    function GetPathForNode
    {
        <#
                .SYNOPSIS
                Describe purpose of "GetPathForNode" in 1-2 sentences.
 
                .DESCRIPTION
                Add a more complete description of what the function does.
 
                .PARAMETER Node
                Describe parameter -Node.
 
                .EXAMPLE
                GetPathForNode -Node Value
                Describe what this call does
 
                .NOTES
                Place additional notes here.
 
                .LINK
                URLs to related sites
                The first link is opened by Get-Help -Online GetPathForNode
 
                .INPUTS
                List of input types that are accepted by this function.
 
                .OUTPUTS
                List of output types produced by this function.
        #>


        [CmdletBinding()]
        param($Node)

        $nodeElements = @()

        ## Go through all the parents, adding them so that
        ## $nodeElements is in order.
        while($Node)
        {
            $nodeElements = ,$Node + $nodeElements
            $Node = $Node.Parent
        }

        ## Now go through the node elements
        $nodePath = ''
        foreach($Node in $nodeElements)
        {
            $nodeName = $Node.Name

            ## If it was a node that PowerShell is able to enumerate
            ## (but not index), wrap it in the array cast operator.
            if($nodeName.StartsWith('@'))
            {
                $nodeName = $nodeName.Substring(1)
                $nodePath = '@(' + $nodePath + ')'
            }
            elseif($nodeName.StartsWith('['))
            {
                ## If it's a child index, we don't need to
                ## add the dot for property access
            }
            elseif($nodePath)
            {
                ## Otherwise, we're accessing a property. Add a dot.
                $nodePath += '.'
            }

            ## Append the node name to the path
            $nodePath += $nodeName
        }

        ## And return the result
        $nodePath
    } # endfunction GetPathForNode
    ## Create the TreeView, which will hold our object navigation
    ## area.
    $treeView = New-Object -TypeName Windows.Forms.TreeView
    $treeView.Dock = 'Top'
    $treeView.Height = 500
    $treeView.PathSeparator = '.'
    $treeView.Add_AfterSelect( { OnAfterSelect @args } )
    $treeView.Add_BeforeExpand( { OnBeforeExpand @args } )
    $treeView.Add_KeyPress( { OnKeyPress @args } )

    ## Create the output pane, which will hold our object
    ## member information.
    $outputPane = New-Object -TypeName System.Windows.Forms.TextBox
    $outputPane.Multiline = $true
    $outputPane.ScrollBars = 'Vertical'
    $outputPane.Font = 'Consolas'
    $outputPane.Dock = 'Top'
    $outputPane.Height = 300

    ## Create the root node, which represents the object
    ## we are trying to show.
    $root = New-Object -TypeName Windows.Forms.TreeNode
    $root.Text = "$InputObject : " + $InputObject.GetType()
    $root.Name = '$' + $rootVariableName
    $root.Expand()
    $null = $treeView.Nodes.Add($root)

    ## And populate the initial information into the tree
    ## view.
    PopulateNode -node $root -object $InputObject

    ## Finally, create the main form and show it.
    $form = New-Object -TypeName Windows.Forms.Form
    $form.Text = 'Browsing ' + $root.Text
    $form.Width = 1000
    $form.Height = 800
    $form.Controls.Add($outputPane)
    $form.Controls.Add($treeView)
    $null = $form.ShowDialog()
    $form.Dispose()
}