PSGraphPlus.psm1

Write-Verbose 'Importing from [C:\projects\psgraphplus\PSGraphPlus\private]'
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-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]'