PSGraphPlus.psm1

Write-Verbose 'Importing from [C:\projects\psgraphplus\PSGraphPlus\private]'
# .\PSGraphPlus\private\Add-DebugNote.ps1
function Add-DebugNote
{
    param($Id, $Message)
    if ($script:DebugAST)
    {
        $debugID = New-Guid
        node -Name $debugID @{label = $Message; shape = 'plaintext'}
        edge -From $debugID -To $Id @{style = 'dotted'; arrowhead = 'none'}
    }
}
# .\PSGraphPlus\private\Get-AstMap.ps1
function Get-AstMap
{
    [cmdletbinding()]
    param (
        [Parameter(
            ValueFromPipeline,
            Position = 0
        )]
        [AllowNull()]
        $Ast,

        $ParentID = $null,

        [ref]$ChildId = [ref]$null
    )

    process
    {
        $lastId = $null
        $rank = [System.Collections.Generic.List[System.Object]]::new()
        :node foreach ($node in $Ast)
        {
            $type = $node.GetType().Name
            $id = $node.gethashcode()

            $rule = Get-AstRule -Name $type
            if ($rule)
            {
                if ($rule.Visible -or $Script:DebugAST -eq $true)
                {
                    Add-DebugNote -Id $id -Message $type
                    $ChildId.Value = $id
                    if ( $rule.Container )
                    {
                        throw 'not supported, subgraphs are buggy with these types of graphs'
                        subgraph -Name $id -Attributes @{label = $rule.Label; labeljust = 'l'} -scriptblock {
                            Get-AstMap -AST $node.$($rule.Container)
                        }
                    }
                    else
                    {
                        $node | node -NodeScript {$id} -Attributes @{label = $rule.Label}
                    }
                    foreach ( $property in $rule.ChildProperty )
                    {
                        Get-AstMap -AST $node.$property -ParentID $id
                    }
                }
                else
                {
                    $ChildId.Value = $id
                    foreach ( $property in $rule.ChildProperty )
                    {
                        Get-AstMap -AST $node.$property -ParentID $ParentID -ChildId $ChildId
                    }
                    continue node
                }
            }
            else
            {
                # hand crafted rules
                switch ( $type )
                {
                    'ForStatementAst'
                    {
                        $node
                    }
                    'IfStatementAST'
                    {
                        Add-DebugNote -Id $id -Message $type
                        $ChildId.Value = $id
                        node -Name $id @{label = "IF (...)"; color = 'blue'}
                        $conditionID = New-Guid
                        $conditionTrueID = new-guid
                        node -Name $conditionID @{label = '( CONDITION )'; color = 'blue'; }
                        Edge $id -To $conditionID
                        Get-AstMap -AST $node.Clauses[0].Item1 -ParentID $conditionID
                        node -Name $conditionTrueID @{label = 'IF TRUE'; color = 'blue'; shape = 'diamond'}
                        Edge $id -To $conditionTrueID
                        Get-AstMap -AST $node.Clauses[0].Item2 -ParentID $conditionTrueID

                        $NextParent = $id
                        $list = $node.Clauses

                        for ($index = 1; $index -lt $list.count; $index++ )
                        {
                            $child = $node.Clauses[$index]
                            $ifElseID = New-Guid
                            $conditionID = New-Guid
                            $conditionTrueID = new-guid
                            node -Name $ifElseID @{label = "IFELSE (...)"; color = 'blue'}
                            edge $NextParent -To $ifElseID
                            node -Name $conditionID @{label = '( CONDITION )'; color = 'blue'; }
                            Edge $ifElseID -To $conditionID

                            Get-AstMap -AST $child.Item1 -ParentID $conditionID
                            node -Name $conditionTrueID @{label = 'IF TRUE'; color = 'blue'; shape = 'diamond'}
                            Edge $ifElseID -To $conditionTrueID

                            Get-AstMap -AST $child.Item2 -ParentID $conditionTrueID

                            $NextParent = $ifElseID
                        }

                        $child = $node.ElseClause
                        if ( $child )
                        {
                            $elseId = New-Guid
                            $conditionID = New-Guid
                            $conditionTrueID = new-guid
                            node -Name $elseId @{label = "ELSE"; color = 'blue'; shape = 'diamond'}
                            edge $NextParent -To $elseId
                            Get-AstMap -AST $child -ParentID $elseId
                        }
                        #continue node
                    }
                    'HashtableAst'
                    {
                        Add-DebugNote -Id $id -Message $type
                        $ChildId.Value = $id
                        node -Name $id @{label = '@{...}'}
                        foreach ($child in $node.KeyValuePairs)
                        {
                            $NextParent = 0
                            Get-AstMap -AST $child.Item1 -ParentID $id -ChildId ([ref]$NextParent)
                            Get-AstMap -AST $child.Item2 -ParentID $NextParent
                        }
                        #$node
                    }
                    'CommandAst'
                    {
                        #CommandElements
                        $property = 'CommandElements'
                        if ($node.$($property).count )
                        {
                            $ChildId.Value = $id
                            $child = $node.$($property)[0]
                            Get-AstMap -AST $child -ParentID $ParentId -ChildId $ChildId
                            Add-DebugNote -Id $ChildId.Value -Message $type
                        }

                        $command = Get-Command -Name $child.Value -ErrorAction Ignore

                        $PrimaryParent = $ChildId.Value
                        $NewParent = $ChildId.Value
                        $NextParent = $ChildId.Value
                        $list = $node.$($property)

                        for ($index = 1; $index -lt $list.count; $index++ )
                        {
                            $child = $node.$($property)[$index]
                            Get-AstMap -AST $child -ParentID $NewParent -ChildId ([ref]$NextParent)

                            $NewParent = $PrimaryParent
                            if ( $child.GetType().name -eq 'CommandParameterAst' -and
                                -not $command.Parameters.$($child.ParameterName).SwitchParameter
                            )
                            {
                                $NewParent = $NextParent
                            }
                        }
                        continue node
                    }
                    'PipelineAst'
                    {
                        $NextParent = $ParentID
                        if ($Script:DebugAST)
                        {
                            $ChildId.Value = $id
                            node -Name $id @{label = 'PipelineAst[]'}
                            edge $ParentID -To $id
                            $NextParent = $id
                        }

                        $ChildId.Value = $id
                        Get-AstMap -AST $node.PipelineElements[0] -ParentID $NextParent -ChildId $ChildId
                        Add-DebugNote -Id $ChildId.Value -Message $type

                        $NewParent = $ChildId.Value
                        $NextParent = $ChildId.Value
                        $list = $node.PipelineElements
                        for ($index = 1; $index -lt $list.count; $index++ )
                        {
                            if ( $index -lt $list.count )
                            {
                                $guid = New-Guid
                                node -Name $guid @{label = "|"}
                                edge $NewParent -To $guid
                                $NewParent = $guid
                            }
                            Get-AstMap -AST $node.PipelineElements[$index] -ParentID $NewParent -ChildId ([ref]$NextParent)
                            Add-DebugNote -Id $NextParent -Message $type

                            $NewParent = $NextParent
                        }

                        continue node
                    }

                    default
                    {
                        $ChildId.Value = $id
                        node -Name $id @{label = $type; color = 'red'}
                        $guid = New-Guid
                        node -Name $guid @{label = $node.extent.tostring()}
                        edge $id -to $guid
                        #Write-Host "Skipping type [$PSItem]"
                        #continue node
                    }
                }
            }
            if ($null -ne $lastId)
            {
                #edge $lastId -to $id
            }
            $rank.Add($id)
            $lastId = $id
            if ( $null -ne $ParentId )
            {
                edge $ParentId -to $id
            }
        }
        if ($rank.Count -gt 1)
        {
            #edge -From $rank -LiteralAttribute '[style="invis"]'
            rank -Nodes $rank
        }
    }
}

# .\PSGraphPlus\private\Get-AstRule.ps1
function Get-AstRule
{
    <#
        .Synopsis
        Gets a rendering rule for a specific AST object

        .Example
        Get-AstRule -Name 'IfStatementAst'

        .Notes
        Most AST items have very generic rules.
    #>

    [cmdletbinding()]
    param(
        # AST object type name
        [Parameter(
            Mandatory,
            Position = 0,
            ValueFromPipelineByPropertyName
        )]
        [ValidateNotNullOrEmpty()]
        [String]
        $Name
    )

    begin
    {
        $astRules = @{
            ScriptBlockExpressionAst      = @{
                ChildProperty = 'ScriptBlock'
                Label         = {'ScriptBlock'}
                Visible       = $false
            }
            CommandExpressionAst          = @{
                ChildProperty = 'Expression'
                Label         = {'CommandExpression'}
                Visible       = $false
            }
            ConstantExpressionAst         = @{
                Visible = $true
                Label   = {$_.Value}
            }
            NamedBlockAst                 = @{
                Visible       = $true
                ChildProperty = 'Statements'
                Label         = {'{0} Block' -f $_.BlockKind}
            }
            VariableExpressionAst         = @{
                Visible = $true
                Label   = {$_.extent.tostring()}
            }
            AssignmentStatementAst        = @{
                Visible       = $true
                Label         = {("{0} {1}" -f $_.left.ToString(), $_.Operator)}
                ChildProperty = 'Right'
            }
            UnaryExpressionAst            = @{
                Visible = $true
                Label   = {$_.extent.tostring()}
            }
            CommandParameterAst           = @{
                Visible = $true
                Label   = {"-$($_.ParameterName)"}
            }
            ScriptBlockAst                = @{
                Visible       = $false
                ChildProperty = 'ParamBlock', 'BeginBlock', 'ProcessBlock', 'EndBlock'
                Label         = {'ScriptBlock'}
            }
            MemberExpressionAst           = @{
                Visible = $true
                Label   = {$_.extent.tostring()}
            }
            InvokeMemberExpressionAst     = @{
                Visible = $true
                Label   = {$_.extent.tostring()}
            }
            ArrayExpressionAst            = @{
                Visible       = $false
                Label         = '[System.Object[]]::New()@{'
                ChildProperty = 'SubExpression'
            }
            StringConstantExpressionAst   = @{
                Visible = $true
                Label   = { "{0}" -f $_.extent.tostring() }
            }
            ExpandableStringExpressionAst = @{
                Visible       = $true
                Label         = {$_.extent.tostring()}
                ChildProperty = 'NestedExpressions'
            }
            IndexExpressionAst            = @{
                Visible = $true
                Label   = {$_.extent.tostring()}
            }
            ThrowStatementAst             = @{
                Visible = $true
                Label   = {$_.extent.tostring()}
            }
            CmdletInfo                    = {
                Visible = $true
                Label = {$_.Name}
            }
            _HashtableAst                 = @{
                Visible       = $true
                Label         = {'Hashtable'}
                ChildProperty = 'KeyValuePairs'
            }
            'Tuple`2'                     = @{
                Visible       = $true
                Label         = {$_.Item1}
                ChildProperty = 'Item1', 'Item2'
            }
            ParenExpressionAst            = @{
                Visible       = $false
                Label         = {'ParenExpression'}
                ChildProperty = 'Pipeline'
            }
            _IfStatementAST               = @{
                Visible       = $true
                Label         = 'IF'
                ChildProperty = 'Clauses', 'ElseClause'
            }
            StatementBlockAst             = @{
                Visible       = $false
                Label         = 'StatementBlock'
                ChildProperty = 'Statements'
            }
            BinaryExpressionAST           = @{
                Visible       = $true
                Label         = {$_.Operator}
                ChildProperty = 'Left', 'Right'
            }
            ForEachStatementAst           = @{
                Visible       = $true
                Label         = {'{0} foreach ( {1} in {2} )' -f $_.Label, $_.Variable, $_.Condition}
                ChildProperty = 'Condition', 'Body'
                #Container = 'Condition'
            }
            FunctionDefinitionAST         = @{
                Visible       = $true
                Label         = {$_.Name}
                ChildProperty = 'Parameters', 'Body'
            }
            SwitchStatementAST            = @{
                Visible       = $true
                Label         = {'{0} Switch ( {1} )' -f $_.Label, $_.Condition}
                ChildProperty = 'Condition', 'Clauses', 'Default'
            }
            TryStatementAST               = @{
                Visible       = $true
                Label         = 'TRY'
                ChildProperty = 'Body', 'CatchClauses', 'Finally'
            }
            CatchClauseAst                = @{
                Visible       = $true
                Label         = 'CATCH'
                ChildProperty = 'CatchTypes', 'Body'
            }
            DoUntilStatementAst           = @{
                Visible       = $true
                Label         = {'{0} DO UNTIL ( {1} )' -f $_.label, $_.Condition}
                ChildProperty = 'Condition', 'Body'
            }
            ReturnStatementAst            = @{
                Visible       = $true
                Label         = 'RETURN'
                ChildProperty = 'Pipeline'
            }
            SubExpressionAst              = @{
                Visible       = $false
                Label         = 'SubExpression'
                ChildProperty = 'SubExpression'
            }
            ArrayLiteralAst               = @{
                Visible = $true
                Label   = {$_.extent.tostring()}
                #ChildProperty = 'Elements'
            }
            ContinueStatementAst          = @{
                Visible = $true
                Label   = {$_.extent.tostring()}
            }
            BreakStatementAst             = @{
                Visible = $true
                Label   = {$_.extent.tostring()}
            }
            ConvertExpressionAst          = @{
                Visible       = $true
                Label         = {$_.type.extent.tostring()}
                ChildProperty = 'Child'
            }
            WhileStatementAst             = @{
                Visible       = $true
                Label         = {'{0} WHILE ( {1} )' -f $_.Label, $_.Condition}
                ChildProperty = 'Condition', 'Body'
            }
            TypeDefinitionAst             = @{
                Visible       = $true
                Label         = {'{0} {1}' -f $_.TypeAttributes, $_.Name}
                ChildProperty = 'Attributes', 'Members'
            }
            PropertyMemberAst             = @{
                Visible       = $true
                Label         = {$_.extent.tostring()}
                ChildProperty = 'InitialValue'
            }
            TypeConstraintAst             = @{
                Visible       = $true
                Label         = {$_.extent.tostring()}
                ChildProperty = 'InitialValue'
            }
            ParameterAst                  = @{
                Visible       = $true
                Label         = {$_.Name}
                ChildProperty = 'Attributes', 'DefaultValue'
            }
            AttributeAst                  = @{
                Visible       = $true
                Label         = {'[{0}( ... )]' -f $_.TypeName}
                ChildProperty = 'NamedArguments', 'PositionalArguments'
            }
            NamedAttributeArgumentAst     = @{
                Visible       = $true
                Label         = {$_.ArgumentName}
                ChildProperty = 'Argument'
            }
            FunctionMemberAst             = @{
                Visible       = $true
                Label         = {'{0} {1}' -f $_.ReturnType, $_.Name}
                ChildProperty = 'Attributes', 'Parameters', 'Body'
            }
            ParamBlockAst                 = @{
                Visible       = $true
                Label         = 'Param (...)'
                ChildProperty = 'Parameters'
            }
            ForStatementAst               = @{
                Visible       = $true
                Label         = {'{0} FOR ( {1}; {2}; {3})' -f $_.Label, $_.Initializer, $_.Condition, $_.Iterator}
                ChildProperty = 'Condition', 'Body'
            }
        }
    }

    process
    {
        try
        {
            $astRules[$Name]
        }
        catch
        {
            $PSCmdlet.ThrowTerminatingError( $PSItem )
        }
    }
}

Write-Verbose 'Importing from [C:\projects\psgraphplus\PSGraphPlus\public]'
# .\PSGraphPlus\public\Show-AstCommandGraph.ps1

Function Show-AstCommandGraph
{
    <#
    .SYNOPSIS
    Generates a graph of the commands called in a script

    .DESCRIPTION
    Generates a graph of the commands called in a script

    .PARAMETER ScriptBlock
    a scriptblock to process

    .PARAMETER ScriptText
    The raw text of a script to process

    .PARAMETER Path
    The path to a script to process

    .PARAMETER AllCommands
    Show commands called that are not part of the script or module

    .PARAMETER AllCalls
    Will show a line for each time a function is called

    .PARAMETER Raw
    Produces a raw dot file.

    .EXAMPLE
    $script = {
        function test-function () {
            Write-Output 'test'
        }

        function other-function () {
            test-function
        }
    }

    $script | Show-AstCommandGraph

    .NOTES
    The core powershell cmdlets are filtered out of the graph.
    commands like foreach-object, write-verbose, ect just add noise

    #>

    [cmdletbinding(DefaultParameterSetName = 'ScriptBlock')]
    param(
        [parameter(
            ParameterSetName = 'ScriptBlock',
            ValueFromPipeline
        )]
        [ValidateNotNullOrEmpty()]
        [scriptblock]
        $ScriptBlock,

        [parameter(
            ParameterSetName = 'ScriptText'
        )]
        [ValidateNotNullOrEmpty()]
        [string]
        $ScriptText,

        [parameter(
            ParameterSetName = 'Path',
            ValueFromPipeline,
            Position = 0
        )]
        [ValidateNotNullOrEmpty()]
        [string]
        $Path,

        [switch]
        $AllCommands,

        [switch]
        $AllCalls,

        [switch]
        $Raw
    )

    process
    {
        if (![string]::IsNullOrWhiteSpace($Path))
        {
            $ScriptText = Get-Content $Path -Raw -ErrorAction Stop
        }
        if (![string]::IsNullOrWhiteSpace($ScriptText))
        {
            $ScriptBlock = [scriptblock]::Create($ScriptText)
        }

        $functions = $ScriptBlock.Ast | Select-AST -Type FunctionDefinitionAst
        $names = $functions.Name
        $commands = $names

        if ($AllCommands)
        {
            $commands = (Get-Command | Where-Object source -notlike Microsoft.PowerShell.* | Select-Object -ExpandProperty Name) + $names
            $commands = $commands | Select-Object -Unique
        }

        if ($null -ne $names)
        {

            $graph = Graph {
                node @{shape = 'box'}
                node $names

                foreach ($function in $functions)
                {
                    $calls = $function.Body | Select-Ast -Type CommandAst
                    $uniquecalls = $calls.commandelements |
                        Where-Object StringConstantType -eq 'BareWord' |
                        Select-Object -ExpandProperty Value -Unique:(-Not $AllCalls) |
                        ForEach-Object {$commands -eq $_ }

                    if ($uniquecalls)
                    {
                        edge $function.Name -To $uniquecalls
                    }
                }
            }
        }

        if ($Raw)
        {
            $graph
        }
        else
        {
            $graph | Export-PSGraph -ShowGraph
        }
    }
}

# .\PSGraphPlus\public\Show-AstGraph.ps1
function Show-AstGraph
{
    <#
    .Synopsis
    Creates a full AST diagram

    .Description
    Parses a script for all the AST elements and builds a graph out of them.

    .PARAMETER ScriptBlock
    a scriptblock to process

    .PARAMETER ScriptText
    The raw text of a script to process

    .PARAMETER Path
    The path to a script to process

    .PARAMETER Annotate
    Expand the graph and show AST object types

    .PARAMETER Raw
    Produces a raw dot file.

    .EXAMPLE
    $script = {
        function test-function () {
            Write-Output 'test'
        }

        function other-function () {
            test-function
        }
    }

    $script | Show-AstGraph

    .NOTES

    #>

    [cmdletbinding(DefaultParameterSetName = 'ScriptBlock')]
    param(
        [parameter(
            ParameterSetName = 'ScriptBlock',
            ValueFromPipeline
        )]
        [ValidateNotNullOrEmpty()]
        [scriptblock]
        $ScriptBlock,

        [parameter(
            ParameterSetName = 'ScriptText'
        )]
        [ValidateNotNullOrEmpty()]
        [string]
        $ScriptText,

        [parameter(
            ParameterSetName = 'Path',
            ValueFromPipeline,
            Position = 0
        )]
        [ValidateNotNullOrEmpty()]
        [string]
        $Path,

        [switch]
        $Annotate,

        [switch]
        $Raw
    )

    process
    {
        if (![string]::IsNullOrWhiteSpace($Path))
        {
            $ScriptText = Get-Content $Path -Raw -ErrorAction Stop
        }
        if (![string]::IsNullOrWhiteSpace($ScriptText))
        {
            $ScriptBlock = [scriptblock]::Create($ScriptText)
        }

        $script:DebugAst = $Annotate
        $ast = $ScriptBlock.Ast
        $nodesAndEdges = Get-AstMap -Ast $ast

        $options = @{
            rankdir = 'LR'
            splines = 'true'
            nodesep = '0.6'
        }
        $graph = graph $options {
            node @{shape = 'box'}
            $nodesAndEdges
        }

        if ($Raw)
        {
            $graph
        }
        else
        {
            $graph | Export-PSGraph -ShowGraph
        }
    }
}

# .\PSGraphPlus\public\Show-GitGraph.ps1
enum Direction
{
    BottomToTop
    TopToBottom
    RightToLeft
    LeftToRight
}

function Show-GitGraph
{
    <#
    .SYNOPSIS
    Gets a graph of the git history

    .DESCRIPTION
    This will generate a graph showing the recent histroy of a project with the branches.

    .PARAMETER Path
    Local location of the Git repository

    .PARAMETER HistoryDepth
    How far back into history to show

    .PARAMETER Uri
    Allows the injection of a base URL for github projects

    .PARAMETER ShowCommitMessage
    This will show the git commit instead of the hash

    .PARAMETER Raw
    Output the raw graph without generating the image or showing it. Useful for testing.

    .PARAMETER Direction
    This sets the direction of the chart.

    .EXAMPLE
    Show-GitGraph

    .EXAMPLE
    Show-GitGraph -HistoryDepth 30


    .EXAMPLE
    Show-GitGraph -Path c:\workspace\project -ShowCommitMessage

    .NOTES

    #>

    [CmdletBinding()]
    param(
        $Path = $PWD,
        [alias('Depth')]
        $HistoryDepth = 15,
        $Uri = 'https://github.com/KevinMarquette/PSGraph',
        [switch]
        $ShowCommitMessage,
        [switch]
        $Raw,
        [Direction]
        $Direction = [Direction]::LeftToRight
    )

    begin
    {
        $directionMap = @{
            [Direction]::TopToBottom = 'TB'
            [Direction]::BottomToTop = 'BT'
            [Direction]::LeftToRight = 'LR'
            [Direction]::RightToLeft = 'RL'
        }
    }
    process
    {
        Push-Location $Path
        # Git history with branch details
        $git = git log --format="%h|%p|%s" -n $HistoryDepth --branches=* | Select-Object -SkipLast 1
        $HASH = 0
        $PARENT = 1
        $SUBJECT = 2
        $branches = git branch -a -v
        $tagList = git show-ref --abbrev=7 --tags
        $current = git log -1 --pretty=format:"%h"

        $tagLookup = @{}
        foreach ($tag in $tagList)
        {
            $tagHash, $tagName = $tag -split ' '

            if (-not $tagLookup.ContainsKey($tagHash))
            {
                $tagLookup[$tagHash] = @()
            }
            $tagLookup[$tagHash] += $tagName.replace('refs/tags/', '')

        }

        $commits = @()
        $graph = graph git  @{ rankdir = $directionMap[$Direction]; label = [regex]::Escape( $PWD); pack = 'true' } {
            Node @{shape = 'box'}
            foreach ($line in $git)
            {
                $data = $line.split('|')
                #$label = $data[$HASH]
                if ($ShowCommitMessage)
                {
                    #$label = '{0}\n{1}' -f $data[$SUBJECT], $data[$HASH]
                    $commitID = 'commit' + $data[$HASH]
                    Node $commitID @{label = $data[$SUBJECT]; shape = 'plaintext'}

                    Rank $commitID, $data[$HASH]
                    Edge -From $commitID -To $data[$HASH] @{style = 'dotted'; arrowhead = 'none'}
                    $commits = @($commitID) + @($commits)
                }

                Node -Name $data[$HASH] @{
                    URL = "{0}/commit/{1}" -f $Uri, $data[$HASH]
                }
                Edge -From $data[$PARENT].split(' ') -To $data[$HASH]

                #add tags
                if ($tagLookup.ContainsKey($data[$HASH]))
                {
                    Node $tagLookup[$data[$HASH]] @{fillcolor = 'yellow'; style = 'filled'}
                    Edge -From $tagLookup[$data[$HASH]] -To $data[$HASH]
                }
            }
            if ($commits.Count)
            {
                Edge $commits @{style = 'invis'}
            }

            # branches
            Node @{shape = 'box'; fillcolor = 'green'; style = 'filled'}
            foreach ($line in $branches)
            {
                if ($line -match '(?<branch>[\w/-]+)\s+(?<hash>\w+) (.+)')
                {
                    Node $Matches.branch
                    Edge $Matches.branch -To $Matches.hash
                }
            }

            # current commit
            Node $current @{fillcolor = 'gray'; style = 'filled'}
        }

        if ($Raw)
        {
            $graph
        }
        else
        {
            $graph | Export-PSGraph -ShowGraph
        }

        Pop-Location
    }
}
# .\PSGraphPlus\public\Show-NetworkConnectionGraph.ps1
function Show-NetworkConnectionGraph
{
    <#
    .SYNOPSIS
    Generates a map of network connections

    .Description
    This graph will show the source and target IP addresses with each edge showing the ports

    .EXAMPLE
    Show-NetworkConnectionGraph

    .Example
    Show-NetworkConnectionGraph -ComputerName $server -Credential $Credential

    .NOTES

    #>

    [CmdletBinding( DefaultParameterSetName = 'Default' )]
    param(
        # Remote computer name
        [Parameter( ParameterSetName = 'Default' )]
        [string[]]
        $ComputerName,

        # Credential for authorization
        [Parameter( ParameterSetName = 'Default' )]
        [pscredential]
        $Credential,

        # Outputs the raw dot graph (for testing
        [switch]
        $Raw
    )

    process
    {
        $session = @{}
        if ( $null -ne $ComputerName )
        {
            $PSBoundParameters.Remove('Raw')
            $session = @{
                CimSession = New-CimSession @PSBoundParameters
            }
        }
        elseif ( $CimSession )
        {
            $session = @{
                CimSession = $CimSession
            }
        }

        $netstat = Get-NetTCPConnection -State Established, TimeWait -ErrorAction SilentlyContinue @session
        $netstat = $netstat | Where-Object LocalAddress -NotMatch ':'
        $dns = Get-DnsClientCache @session | Where-Object data -in $netstat.RemoteAddress

        $graph = graph network @{rankdir = 'LR'; label = 'Network Connections'} {
            Node @{shape = 'rect'}

            $EdgeParam = @{
                Node       = $netstat
                FromScript = {$_.LocalAddress}
                ToScript   = {$_.RemoteAddress}
                Attributes = @{label = {'{0}:{1}' -f $_.LocalPort, $_.RemotePort}}
            }
            Edge @EdgeParam

            Node $dns -NodeScript {$_.data} @{label = {'{0}\n{1}' -f $_.entry, $_.data}}

        }

        if ($Raw)
        {
            $graph
        }
        else
        {
            $graph | Export-PSGraph -ShowGraph
        }
    }
}
# .\PSGraphPlus\public\Show-ProcessConnectionGraph.ps1
function Show-ProcessConnectionGraph
{
    <#
    .SYNOPSIS
    Generates a map of network connections

    .Description
    This graph will show the source and target IP addresses with each edge showing the ports

    .EXAMPLE
    Show-NetworkConnectionGraph

    .Example
    Show-NetworkConnectionGraph -ComputerName $server -Credential $Credential

    .NOTES

    #>

    [CmdletBinding( DefaultParameterSetName = 'Default' )]
    param(
        # Remote computer name
        [Parameter( ParameterSetName = 'Default' )]
        [string[]]
        $ComputerName,

        # Credential for authorization
        [Parameter( ParameterSetName = 'Default' )]
        [pscredential]
        $Credential,

        # Outputs the raw dot graph (for testing)
        [switch]
        $Raw
    )

    process
    {
        $session = @{}
        if ( $null -ne $ComputerName )
        {
            $PSBoundParameters.Remove('Raw')
            $session = @{
                CimSession = New-CimSession @PSBoundParameters
            }
        }

        $netstat = Get-NetTCPConnection -State Established, TimeWait -ErrorAction SilentlyContinue @session
        $netstat = $netstat | Where-Object LocalAddress -NotMatch ':'
        $dns = Get-DnsClientCache @session | Where-Object data -in $netstat.RemoteAddress

        $process = Get-CIMInstance -ClassName CIM_Process @session | Where-Object ProcessId -in $netstat.OwningProcess

        $graph = graph network @{rankdir = 'LR'; label = 'Process Network Connections'} {
            Node @{shape = 'rect'}
            Node $process -NodeScript {$_.ProcessID} @{label = {'{0}\n{1}' -f $_.ProcessName, $_.ProcessID}}

            $EdgeParam = @{
                Node       = $netstat
                FromScript = {$_.OwningProcess}
                ToScript   = {$_.RemoteAddress}
                Attributes = @{label = {'{0}:{1}' -f $_.LocalPort, $_.RemotePort}}
            }
            Edge @EdgeParam

            Node $dns -NodeScript {$_.data} @{label = {'{0}\n{1}' -f $_.entry, $_.data}}
        }

        if ($Raw)
        {
            $graph
        }
        else
        {
            $graph | Export-PSGraph -ShowGraph
        }
    }
}
# .\PSGraphPlus\public\Show-ServiceDependencyGraph.ps1
function Show-ServiceDependencyGraph
{
    <#
    .SYNOPSIS
    Show the process dependency graph

    .DESCRIPTION
    Loads all processes and maps out the dependencies

    .EXAMPLE
    Show-ProcessDependencyGraph

    .NOTES
    General notes
    #>

    [CmdletBinding()]
    param(
        # Service Name
        [Parameter()]
        [string[]]
        $Name,

        # Remote computer name
        [Parameter()]
        [string[]]
        $ComputerName,

        # Credential for authorization
        [Parameter()]
        [pscredential]
        $Credential,

        # Outputs the raw dot graph (for testing)
        [switch]
        $Raw
    )

    process
    {
        if ( $null -ne $ComputerName )
        {
            Write-Verbose 'Connecting to remote system'
            $PSBoundParameters.Remove('Raw')
            $services = Invoke-Command @PSBoundParameters -ScriptBlock {Get-Service -Include *}
        }
        else
        {
            $services = Get-Service -Include *
        }

        if ($null -ne $Name)
        {
            Write-Verbose ( 'Filtering on name [{0}]' -f ( $Name -join ',' ) )
            $services = foreach ($node in $services)
            {
                if ($node.Name -in $Name)
                {
                    $node
                    continue
                }
                foreach ($dependency in $node.ServicesDependedOn.Name)
                {
                    if ( $dependency -in $Name)
                    {
                        $node
                        continue
                    }
                }
            }
        }

        if ( $null -eq $services ) { return }

        Set-NodeFormatScript {$_.tolower()}
        $graph = graph services  @{rankdir = 'LR'; pack = 'true'} {
            Node @{shape = 'box'}

            Node $services -NodeScript {$_.name} @{
                label = {'{0}\n{1}' -f $_.DisplayName, $_.Name}
                color = {If ($_.Status -eq 'Running') {'blue'}else {'red'}}
            }
            $linkedServices = $services | Where-Object {$_.ServicesDependedOn}
            Edge $linkedServices -FromScript {$_.Name} -ToScript {$_.ServicesDependedOn.Name}
        }
        Set-NodeFormatScript

        if ($Raw)
        {
            $graph
        }
        else
        {
            $graph | Export-PSGraph -ShowGraph
        }
    }
}
Write-Verbose 'Importing from [C:\projects\psgraphplus\PSGraphPlus\classes]'