Src/Public/New-Diagrammer.ps1

function New-Diagrammer {
    <#
    .SYNOPSIS
        Diagram the configuration of IT infrastructure in PDF/SVG/DOT/PNG formats using PSGraph and Graphviz.
 
    .DESCRIPTION
        This cmdlet generates diagrams of IT infrastructure configurations in various formats such as PDF, SVG, DOT, and PNG using PSGraph and Graphviz. It provides extensive customization options for diagram appearance, including font settings, colors, node and edge properties, and more.
 
    .PARAMETER InputObject
        The PSGraph input object (graph/subgraphs/nodes) used to generate the diagram.
 
    .PARAMETER Format
        Output format(s) for the diagram. Supported values: pdf, svg, png, dot, base64, jpg. Default: pdf.
 
    .PARAMETER Edgecolor
        Edge line color (RGB hex or color name). Default: '#71797E'.
 
    .PARAMETER EdgeArrowSize
        Size of edge arrows. Default: 1.
 
    .PARAMETER EdgeLineWidth
        Width (pen size) of edge lines. Default: 3.
 
    .PARAMETER Fontcolor
        Default graph font color (RGB hex or color name). Default: '#000000'.
 
    .PARAMETER Fontname
        Default graph font name. Default: 'Segoe Ui'.
 
    .PARAMETER MainDiagramLabelFontBold
        Switch to render the main diagram label in bold.
 
    .PARAMETER MainDiagramLabelFontItalic
        Switch to render the main diagram label in italic.
 
    .PARAMETER MainDiagramLabelFontUnderline
        Switch to render the main diagram label with underline.
 
    .PARAMETER MainDiagramLabelFontOverline
        Switch to render the main diagram label with overline.
 
    .PARAMETER MainDiagramLabelFontSubscript
        Switch to render part of the main diagram label as subscript.
 
    .PARAMETER MainDiagramLabelFontSuperscript
        Switch to render part of the main diagram label as superscript.
 
    .PARAMETER MainDiagramLabelFontStrikeThrough
        Switch to render the main diagram label with strikethrough.
 
    .PARAMETER NodeFontSize
        Font size used for node labels. Default: 14.
 
    .PARAMETER NodeFontcolor
        Node font color (RGB hex or color name). Default: 'Black'.
 
    .PARAMETER IconPath
        Path used to resolve icon/image names referenced in $ImagesObj. Default: Tools\Icons relative to the module.
 
    .PARAMETER ImagesObj
        Hashtable mapping image identifiers to filenames (IconName -> FileName). Defaults include Diagrammer.png and Diagrammer_footer.png.
 
    .PARAMETER Direction
        Layout direction for the graph. Valid values: 'left-to-right', 'top-to-bottom'. Default: 'top-to-bottom'.
 
    .PARAMETER OutputFolderPath
        Folder where exported diagram files will be written. Default: system temp folder.
 
    .PARAMETER SignatureLogo
        Path to a custom signature logo file.
 
    .PARAMETER SignatureLogoName
        Name (key in $ImagesObj) to use as the signature logo.
 
    .PARAMETER Logo
        Path to a custom main diagram logo file.
 
    .PARAMETER LogoName
        Name (key in $ImagesObj) to use as the main diagram logo.
 
    .PARAMETER Filename
        Base filename for exported diagrams (extension is appended per format).
 
    .PARAMETER EdgeType
        Controls how edges are drawn. Valid values: 'polyline', 'curved', 'ortho', 'line', 'spline'. Default: 'line'.
 
    .PARAMETER NodeSeparation
        Node separation setting (rank/node separation ratio). Accepts discrete values as configured. Default: 0.60.
 
    .PARAMETER SectionSeparation
        Section (subgraph) separation setting (rank separation ratio). Default: 0.75.
 
    .PARAMETER DraftMode
        Switch to enable debug visualization (styles/colors shown for subgraphs, nodes, and edges).
 
    .PARAMETER EnableErrorDebug
        Switch to enable verbose and debug output for troubleshooting.
 
    .PARAMETER DisableMainDiagramLogo
        Switch to disable rendering the main diagram logo.
 
    .PARAMETER AuthorName
        Author name used in the footer signature (required when -Signature is used).
 
    .PARAMETER CompanyName
        Company name used in the footer signature (required when -Signature is used).
 
    .PARAMETER Signature
        Switch to include a footer signature (requires AuthorName and CompanyName).
 
    .PARAMETER MainDiagramLabel
        Main label/title displayed at the top of the diagram.
 
    .PARAMETER MainDiagramLabelFontsize
        Font size for the main diagram label. Default: 24.
 
    .PARAMETER MainDiagramLabelFontname
        Font name for the main diagram label. Default: 'Segoe Ui'.
 
    .PARAMETER MainDiagramLabelFontColor
        Font color for the main diagram label (RGB hex or color name). Default: '#000000'.
 
    .PARAMETER MainGraphAttributes
        Hashtable of graph attributes to override or augment defaults (examples: fontname, fontcolor, imagepath, style, nodesep, ranksep, bgcolor).
 
    .PARAMETER WaterMarkColor
        Color used for watermark text. Default: 'DarkGray'.
 
    .PARAMETER WaterMarkText
        Text to render as a watermark on the exported diagram (empty to disable).
 
    .PARAMETER WaterMarkFontOpacity
        Opacity for the watermark text (0-100). Default: 30.
 
    .PARAMETER MainGraphBGColor
        Background color for the main graph. Default: 'White'.
 
    .PARAMETER MainGraphSize
        Graph size / image resolution hint (Graphviz size option), e.g. "8,11!" for specific sizing.
 
    .PARAMETER MainGraphLogoSizePercent
        Scale percent for the main logo when rendered in the diagram. Range: 1-100. Default: 100.
 
    .NOTES
        Version: 0.2.32
        Author(s): Jonathan Colon
        Bluesky: @jcolonfpr.bsky.social
        Github: rebelinux
        Credits: Kevin Marquette (@KevinMarquette) - PSGraph module
                        Prateek Singh (@PrateekKumarSingh) - AzViz module
 
    .LINK
        https://github.com/rebelinux/
        https://github.com/KevinMarquette/PSGraph
        https://github.com/PrateekKumarSingh/AzViz
    #>



    [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidUsingConvertToSecureStringWithPlainText", "", Scope = "Function")]
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidUsingUserNameAndPassWordParams", "", Scope = "Function")]
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidUsingPlainTextForPassword", "", Scope = "Function")]
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseShouldProcessForStateChangingFunctions", "", Scope = "Function")]

    [CmdletBinding(
        PositionalBinding = $false,
        DefaultParameterSetName = 'Credential'
    )]
    param (

        [Parameter(
            Position = 1,
            Mandatory = $true,
            HelpMessage = 'Please provide the psgraph input'
        )]
        $InputObject,

        [Parameter(
            Position = 2,
            Mandatory = $false,
            HelpMessage = 'Please provide the diagram output format'
        )]
        [ValidateNotNullOrEmpty()]
        [ValidateSet('pdf', 'svg', 'png', 'dot', 'base64', 'jpg')]
        [Array] $Format = 'pdf',

        [Parameter(
            Mandatory = $false,
            HelpMessage = 'Please provide the diagram font color in RGB format (Ex: #FFFFFF)'
        )]
        [ValidateNotNullOrEmpty()]
        [string] $Edgecolor = '#71797E',

        [Parameter(
            Mandatory = $false,
            HelpMessage = 'Please provide the edge arrow size (Int)'
        )]
        [ValidateNotNullOrEmpty()]
        [string] $EdgeArrowSize = 1,

        [Parameter(
            Mandatory = $false,
            HelpMessage = 'Please provide the edge line width (Int)'
        )]
        [ValidateNotNullOrEmpty()]
        [string] $EdgeLineWidth = 3,

        [Parameter(
            Mandatory = $false,
            HelpMessage = 'Please provide the diagram font color in RGB format (Ex: #FFFFFF) or color string'
        )]
        [ValidateNotNullOrEmpty()]
        [string] $Fontcolor = '#000000',

        [Parameter(
            Mandatory = $false,
            HelpMessage = 'Please provide the diagram font name'
        )]
        [ValidateNotNullOrEmpty()]
        [string] $Fontname = 'Segoe Ui',

        [Parameter(
            Mandatory = $false,
            HelpMessage = 'Allow to set the font bold'
        )]
        [switch] $MainDiagramLabelFontBold,

        [Parameter(
            Mandatory = $false,
            HelpMessage = 'Allow to set the font italic'
        )]
        [switch] $MainDiagramLabelFontItalic,

        [Parameter(
            Mandatory = $false,
            HelpMessage = 'Allow to set the font underline'
        )]
        [switch] $MainDiagramLabelFontUnderline,

        [Parameter(
            Mandatory = $false,
            HelpMessage = 'Allow to set the font overline'
        )]
        [switch] $MainDiagramLabelFontOverline,

        [Parameter(
            Mandatory = $false,
            HelpMessage = 'Allow to set the font subscript'
        )]
        [switch] $MainDiagramLabelFontSubscript,

        [Parameter(
            Mandatory = $false,
            HelpMessage = 'Allow to set the font superscript'
        )]
        [switch] $MainDiagramLabelFontSuperscript,

        [Parameter(
            Mandatory = $false,
            HelpMessage = 'Allow to set the font strikethrough'
        )]
        [switch] $MainDiagramLabelFontStrikeThrough,

        [Parameter(
            Mandatory = $false,
            HelpMessage = 'Please provide the node font size (Int)'
        )]
        [ValidateNotNullOrEmpty()]
        [string] $NodeFontSize = 14,

        [Parameter(
            Mandatory = $false,
            HelpMessage = 'Please provide the node font color in RGB format (Ex: #FFFFFF) or color string'
        )]
        [ValidateNotNullOrEmpty()]
        [string] $NodeFontcolor = 'Black',

        [Parameter(
            Position = 3,
            Mandatory = $false,
            HelpMessage = 'Please provide the diagram output format'
        )]
        [ValidateScript( {
                if (Test-Path -Path $_) {
                    $true
                } else {
                    throw "Path $_ not found!"
                }
            })]
        [System.IO.FileInfo] $IconPath = (Join-Path (Split-Path (Split-Path $PSScriptRoot -Parent) -Parent) 'Tools\Icons'),

        [Parameter(
            Position = 4,
            Mandatory = $false,
            HelpMessage = 'Please provide the icons hashtable'
        )]
        [ValidateNotNullOrEmpty()]
        [Hashtable] $ImagesObj = @{
            "Main_Logo" = "Diagrammer.png"
            "Logo_Footer" = "Diagrammer_footer.png"
        },

        [Parameter(
            Mandatory = $false,
            HelpMessage = 'Direction in which resource are plotted on the visualization'
        )]
        [ValidateSet('left-to-right', 'top-to-bottom')]
        [string] $Direction = 'top-to-bottom',

        [Parameter(
            Mandatory = $false,
            HelpMessage = 'Please provide the path to the diagram output file'
        )]
        [ValidateScript( {
                if (Test-Path -Path $_) {
                    $true
                } else {
                    throw "Path $_ not found!"
                }
            })]
        [string] $OutputFolderPath = [System.IO.Path]::GetTempPath(),

        [Parameter(
            Mandatory = $false,
            HelpMessage = 'Please provide the path to the custom logo used for Signature'
        )]
        [ValidateScript( {
                if (Test-Path -Path $_) {
                    $true
                } else {
                    throw "File $_ not found!"
                }
            })]
        [string] $SignatureLogo,

        [Parameter(
            Mandatory = $false,
            HelpMessage = 'Please provide the name of the signature logo (Must be defined in $ImageObj)'
        )]
        [string] $SignatureLogoName,

        [Parameter(
            Mandatory = $false,
            HelpMessage = 'Please provide the path to the custom logo'
        )]
        [ValidateScript( {
                if (Test-Path -Path $_) {
                    $true
                } else {
                    throw "File $_ not found!"
                }
            })]
        [string] $Logo,

        [Parameter(
            Mandatory = $false,
            HelpMessage = 'Please provide the name of the main diagram logo (Must be defined in $ImageObj)'
        )]
        [string] $LogoName,

        [Parameter(
            Mandatory = $false,
            HelpMessage = 'Specify the Diagram filename'
        )]
        [ValidateNotNullOrEmpty()]
        [String] $Filename,

        [Parameter(
            Mandatory = $false,
            HelpMessage = 'Controls how edges lines appear in visualization'
        )]
        [ValidateSet('polyline', 'curved', 'ortho', 'line', 'spline')]
        [string] $EdgeType = 'line',

        [Parameter(
            Mandatory = $false,
            HelpMessage = 'Controls Node separation ratio in visualization'
        )]
        [ValidateSet(0, 1, 2, 3)]
        [string] $NodeSeparation = .60,

        [Parameter(
            Mandatory = $false,
            HelpMessage = 'Controls Section (Subgraph) separation ratio in visualization'
        )]
        [ValidateSet(0, 1, 2, 3)]
        [string] $SectionSeparation = .75,

        [Parameter(
            Mandatory = $false,
            HelpMessage = 'Allow to enable visualization debugging of subgraph, edges and nodes'
        )]
        [Switch] $DraftMode = $false,

        [Parameter(
            Mandatory = $false,
            HelpMessage = 'Allow to enable error debugging'
        )]
        [Switch] $EnableErrorDebug = $false,

        [Parameter(
            Mandatory = $false,
            HelpMessage = 'Disable the Main Diagram Logo'
        )]
        [Switch] $DisableMainDiagramLogo,

        [Parameter(
            Mandatory = $false,
            HelpMessage = 'Allow to set footer signature Author Name'
        )]
        [string] $AuthorName,

        [Parameter(
            Mandatory = $false,
            HelpMessage = 'Allow to set footer signature Company Name'
        )]
        [string] $CompanyName,

        [Parameter(
            Mandatory = $false,
            HelpMessage = 'Allow the creation of footer signature'
        )]
        [Switch] $Signature = $false,

        [Parameter(
            Mandatory = $true,
            HelpMessage = 'Set the Main Label used at the top of the diagram'
        )]
        [string] $MainDiagramLabel,

        [Parameter(
            Mandatory = $false,
            HelpMessage = 'Set the Main Label font size used at the top of the diagram'
        )]
        [int] $MainDiagramLabelFontsize = 24,

        [Parameter(
            Mandatory = $false,
            HelpMessage = 'Set the Main Label font name used at the top of the diagram'
        )]
        [string] $MainDiagramLabelFontname = "Segoe Ui",

        [Parameter(
            Mandatory = $false,
            HelpMessage = 'Set the Main Label font color used at the top of the diagram'
        )]
        [string] $MainDiagramLabelFontColor = "#000000",

        [Parameter(
            Mandatory = $false,
            HelpMessage = 'Provide a Hashtable with general graph attributes (fontname,fontcolor,imagepath,style,imagepath)'
        )]
        [ValidateNotNullOrEmpty()]
        [Hashtable] $MainGraphAttributes,

        [Parameter(
            Mandatory = $false,
            HelpMessage = 'Provide a System.Drawing.Color compatible color for the WaterMark text (Ex. Red, Green, Blue, Black)'
        )]
        [ValidateNotNullOrEmpty()]
        [string] $WaterMarkColor = 'DarkGray',

        [Parameter(
            Mandatory = $false,
            HelpMessage = 'Allow the creation of a watermark in the diagram'
        )]
        [string] $WaterMarkText = '',

        [Parameter(
            Mandatory = $false,
            HelpMessage = 'Allow to set the font opacity of the watermark text (0 to 100)'
        )]
        [int] $WaterMarkFontOpacity = 30,

        [Parameter(
            Mandatory = $false,
            HelpMessage = 'Allow to set diagram backgroud color'
        )]
        [string] $MainGraphBGColor = 'White',

        [Parameter(
            Mandatory = $false,
            HelpMessage = 'Allow to set image resolution size (Ex: 8,11! = 800x1100 pixels)'
        )]
        [string] $MainGraphSize,

        [Parameter(
            Mandatory = $false,
            HelpMessage = 'Set the image size in percent (100% is default)'
        )]
        [ValidateRange(1, 100)]
        [int] $MainGraphLogoSizePercent = 100
    )

    begin {

        if ($EnableErrorDebug) {
            $global:VerbosePreference = 'Continue'
            $global:DebugPreference = 'Continue'
        } else {
            $global:VerbosePreference = 'SilentlyContinue'
            $global:DebugPreference = 'SilentlyContinue'
        }

        if (($Format -ne "base64") -and !(Test-Path $OutputFolderPath)) {
            Write-Error -Message "OutputFolderPath '$OutputFolderPath' is not a valid folder path."
            break
        }

        if ($Signature -and (([string]::IsNullOrEmpty($AuthorName)) -or ([string]::IsNullOrEmpty($CompanyName)))) {
            throw "New-Diagrammer: AuthorName and CompanyName must be defined if the Signature option is specified"
        }

        $IconDebug = $false

        if ($DraftMode) {
            $SubGraphDebug = @{style = 'dashed'; color = 'red' }
            $NodeDebug = @{color = 'black'; style = 'red'; shape = 'plain' }
            $NodeDebugEdge = @{color = 'black'; style = 'red'; shape = 'plain' }
            $EdgeDebug = @{style = 'filled'; color = 'red' }
            $IconDebug = $true
        } else {
            $SubGraphDebug = @{style = 'invis'; color = 'gray' }
            $NodeDebug = @{color = 'transparent'; style = 'transparent'; shape = 'point' }
            $NodeDebugEdge = @{color = 'transparent'; style = 'transparent'; shape = 'none' }
            $EdgeDebug = @{style = 'invis'; color = 'red' }
        }

        $Dir = switch ($Direction) {
            'top-to-bottom' { 'TB' }
            'left-to-right' { 'LR' }
        }

        # Validate Custom logo
        if ($Logo -and (-not $LogoName)) {
            $CustomLogo = Test-Logo -LogoPath (Get-ChildItem -Path $Logo).FullName -IconPath $IconPath -ImagesObj $ImagesObj
        } elseif ($LogoName) {
            $CustomLogo = $LogoName
        } else {
            $CustomLogo = "Diagrammer.png"
        }
        # Validate Custom Signature Logo
        if ($SignatureLogo -and (-not $SignatureLogoName )) {
            $CustomSignatureLogo = Test-Logo -LogoPath (Get-ChildItem -Path $SignatureLogo).FullName -IconPath $IconPath -ImagesObj $ImagesObj
        } elseif ($SignatureLogoName) {
            $CustomSignatureLogo = $SignatureLogoName
        } else {
            $CustomSignatureLogo = "Diagrammer.png"
        }

        $MainGraphAttributes = @{
            pad = 1
            rankdir = $Dir
            splines = $EdgeType
            penwidth = 1.5
            fontname = $Fontname
            fontcolor = $Fontcolor
            fontsize = 32
            style = "dashed"
            labelloc = 't'
            imagepath = $IconPath
            nodesep = $NodeSeparation
            ranksep = $SectionSeparation
            bgcolor = $MainGraphBGColor
            compound = $true
        }

        if ($MainGraphSize) {
            $MainGraphAttributes['size'] = $MainGraphSize
        }
    }

    process {

        # Graph default atrributes
        $script:Graph = Graph -Name Root -Attributes $MainGraphAttributes {
            # Node default theme
            Node @{
                # label = ''
                shape = 'none'
                labelloc = 't'
                style = 'filled'
                fillColor = '#71797E'
                fontsize = $NodeFontSize
                imagescale = $true
                fontcolor = $NodeFontcolor
            }
            # Edge default theme
            Edge @{
                style = 'dashed'
                dir = 'both'
                arrowtail = 'dot'
                color = $Edgecolor
                penwidth = $EdgeLineWidth
                arrowsize = $EdgeArrowSize
                fontcolor = $Edgecolor
            }

            # Signature Section
            if ($Signature) {
                Write-Verbose -Message "Generating diagram signature"
                if ($CustomSignatureLogo) {
                    $Signature = (Add-DiaHtmlSignatureTable -ImagesObj $ImagesObj -Rows "Author: $($AuthorName)", "Company: $($CompanyName)" -TableBorder 2 -CellBorder 0 -Align 'left' -Logo $CustomSignatureLogo -IconDebug $IconDebug)
                } else {
                    $Signature = (Add-DiaHtmlSignatureTable -ImagesObj $ImagesObj -Rows "Author: $($AuthorName)", "Company: $($CompanyName)" -TableBorder 2 -CellBorder 0 -Align 'left' -Logo "Logo_Footer" -IconDebug $IconDebug)
                }
            } else {
                Write-Verbose -Message "No diagram signature specified"
                $Signature = " "
            }

            #---------------------------------------------------------------------------------------------#
            # Graphviz Clusters (SubGraph) Section #
            # SubGraph can be use to bungle the Nodes together like a single entity #
            # SubGraph allow you to have a graph within a graph #
            # PSgraph: https://psgraph.readthedocs.io/en/latest/Command-SubGraph/ #
            # Graphviz: https://graphviz.org/docs/attrs/cluster/ #
            #---------------------------------------------------------------------------------------------#

            # Subgraph OUTERDRAWBOARD1 used to draw the footer signature (bottom-right corner)
            SubGraph OUTERDRAWBOARD1 -Attributes @{Label = $Signature; fontsize = 24; penwidth = 1.5; labelloc = 'b'; labeljust = "r"; style = $SubGraphDebug.style; color = $SubGraphDebug.color } {
                # Subgraph MainGraph used to draw the main drawboard.
                if ($DisableMainDiagramLogo) {
                    $FormatedMainLogo = ""
                } else {
                    $FormatedMainLogo = (Add-DiaHtmlLabel -ImagesObj $ImagesObj -Label $MainDiagramLabel -IconType $CustomLogo -IconDebug $IconDebug -ImageSizePercent $MainGraphLogoSizePercent -Fontsize $MainDiagramLabelFontsize -FontColor  $MainDiagramLabelFontColor -FontName $MainDiagramLabelFontname -FontBold:$MainDiagramLabelFontBold -FontItalic:$MainDiagramLabelFontItalic -FontUnderline:$MainDiagramLabelFontUnderline -FontOverline:$MainDiagramLabelFontOverline -FontSubscript:$MainDiagramLabelFontSubscript -FontSuperscript:$MainDiagramLabelFontSuperscript -FontStrikeThrough:$MainDiagramLabelFontStrikeThrough)
                }

                SubGraph MainGraph -Attributes @{Label = $FormatedMainLogo; fontsize = 24; penwidth = 0; labelloc = 't'; labeljust = "c" } {
                    $InputObject
                }
            }
        }
    }
    end {
        foreach ($OutputFormat in $Format) {
            #Export the Diagram
            if ($Graph) {
                Export-Diagrammer -GraphObj ($Graph | Select-String -Pattern '"\w+"\s\[label="";style="invis";shape="point";]' -NotMatch) -ErrorDebug $EnableErrorDebug -Format $OutputFormat -Filename "$Filename.$OutputFormat" -OutputFolderPath $OutputFolderPath -WaterMarkText $WaterMarkText -WaterMarkColor $WaterMarkColor -IconPath $IconPath -WaterMarkFontOpacity $WaterMarkFontOpacity
            } else {
                Write-Verbose -Message "No Graph object found. Disabling diagram section"
            }
        }
    }
}