Src/Private/Get-AbrVb365Diagram.ps1

function Get-AbrVb365Diagram {
    <#
    .SYNOPSIS
        Diagram the configuration of Veeam Backup for Microsoft 365 infrastructure in PDF/SVG/DOT/PNG formats using PSGraph and Graphviz.
    .DESCRIPTION
        Diagram the configuration of Veeam Backup for Microsoft 365 infrastructure in PDF/SVG/DOT/PNG formats using PSGraph and Graphviz.
    .NOTES
        Version: 0.3.5
        Author(s): Jonathan Colon
        Twitter: @jcolonfzenpr
        Github: rebelinux
        Credits: Kevin Marquette (@KevinMarquette) - PSGraph module
        Credits: Prateek Singh (@PrateekKumarSingh) - AzViz module
    .LINK
        https://github.com/rebelinux/
        https://github.com/KevinMarquette/PSGraph
        https://github.com/PrateekKumarSingh/AzViz
    #>



    begin {
        # Used for DiagramDebug
        if ($Options.EnableDiagramDebug) {
            $EdgeDebug = @{style = 'filled'; color = 'red' }
            $SubGraphDebug = @{style = 'dashed'; color = 'red' }
            $NodeDebug = @{color = 'black'; style = 'red'; shape = 'plain' }
            $NodeDebugEdge = @{color = 'black'; style = 'red'; shape = 'plain' }
            $IconDebug = $true
        } else {
            $EdgeDebug = @{style = 'invis'; color = 'red' }
            $SubGraphDebug = @{style = 'invis'; color = 'gray' }
            $NodeDebug = @{color = 'transparent'; style = 'transparent'; shape = 'point' }
            $NodeDebugEdge = @{color = 'transparent'; style = 'transparent'; shape = 'none' }
            $IconDebug = $false
        }
    }

    process {

        #-----------------------------------------------------------------------------------------------#
        # Graphviz Node Section #
        # Nodes are Graphviz elements used to define a object entity #
        # Nodes can have attribues like Shape, HTML Labels, Styles etc.. #
        # PSgraph: https://psgraph.readthedocs.io/en/latest/Command-Node/ #
        # Graphviz: https://graphviz.org/doc/info/shapes.html #
        #-----------------------------------------------------------------------------------------------#

        $ServerInfo = @{
            'Version' = Switch ([string]::IsNullOrEmpty((Get-VBOVersion).ProductVersion)) {
                $true { 'Unknown' }
                $false { (Get-VBOVersion).ProductVersion }
                default { 'Unknown' }
            }
        }

        if ($ServerConfigRestAPI.IsServiceEnabled) {
            $ServerInfo.Add('RestAPI Port', $ServerConfigRestAPI.HTTPSPort)
        }

        # VB365 Server Object
        Node VB365Server @{Label = Get-DiaNodeIcon -Rows $ServerInfo -ImagesObj $Images -Name $VeeamBackupServer -IconType "VB365_Server" -Align "Center" -IconDebug $IconDebug; shape = 'plain'; fillColor = 'transparent'; fontsize = 14 }

        if ($RestorePortal.IsServiceEnabled) {
            $RestorePortalURL = @{
                'Portal URI' = $RestorePortal.PortalUri
            }
            Node VB365RestorePortal @{Label = Get-DiaNodeIcon -Rows $RestorePortalURL -ImagesObj $Images -Name 'Self-Service Portal' -IconType "VB365_Restore_Portal" -Align "Center" -IconDebug $IconDebug; shape = 'plain'; fillColor = 'transparent'; fontsize = 14 }
        }

        # Proxy Graphviz Cluster
        if ($Proxies) {
            $ProxiesInfo = @()

            $Proxies | ForEach-Object {
                $inobj = @{
                    'Type' = $_.Type
                    'Port' = "TCP/$($_.Port)"
                    'OS' = $_.OperatingSystemKind
                }
                $ProxiesInfo += $inobj
            }

            SubGraph ProxyServer -Attributes @{Label = (Get-DiaHTMLLabel -ImagesObj $Images -Label "Backup Proxies" -IconType "VB365_Proxy" -SubgraphLabel -IconDebug $IconDebug); fontsize = 18; penwidth = 1.5; labelloc = 'b'; style = 'dashed,rounded' } {

                Node Proxies @{Label = (Get-DiaHTMLNodeTable -ImagesObj $Images -inputObject ($Proxies.HostName | ForEach-Object { $_.split('.')[0] }) -Align "Center" -iconType "VB365_Proxy_Server" -columnSize 3 -IconDebug $IconDebug -MultiIcon -AditionalInfo $ProxiesInfo); shape = 'plain'; fillColor = 'transparent'; fontsize = 14; fontname = "Tahoma" }
            }
        } else {
            SubGraph ProxyServer -Attributes @{Label = (Get-DiaHTMLLabel -ImagesObj $Images -Label "Backup Proxies" -IconType "VB365_Proxy" -SubgraphLabel -IconDebug $IconDebug); fontsize = 18; penwidth = 1.5; labelloc = 'b'; style = 'dashed,rounded' } {

                Node -Name Proxies -Attributes @{Label = 'No Backup Proxies'; shape = "rectangle"; labelloc = 'c'; fixedsize = $true; width = "3"; height = "2"; fillColor = 'transparent'; penwidth = 0 }
            }
        }

        # Proxy Pools Graphviz Cluster
        if ($ProxyPools) {

            SubGraph ProxyPools -Attributes @{Label = (Get-DiaHTMLLabel -ImagesObj $Images -Label "Backup Proxy Pools" -IconType "VB365_Proxy" -SubgraphLabel -IconDebug $IconDebug); fontsize = 18; penwidth = 1.5; labelloc = 'b'; style = 'dashed,rounded' } {

                $PoolNumber = 0

                foreach ($ProxyPool in $ProxyPools) {
                    $SubGraphName = Remove-SpecialChar -String $ProxyPool.Name -SpecialChars '\-. '

                    SubGraph $SubGraphName -Attributes @{Label = (Get-DiaHTMLLabel -ImagesObj $Images -Label $ProxyPool.Name -IconType "VB365_Proxy_Server" -SubgraphLabel -IconDebug $IconDebug); fontsize = 18; penwidth = 1.5; labelloc = 'b'; style = 'dashed,rounded' } {

                        Node "ProxyPool$($PoolNumber)" @{Label = (Get-DiaHTMLTable -ImagesObj $Images -Rows $ProxyPool.Proxies.Hostname.Split('.')[0] -MultiColunms -ColumnSize 2 -Align 'Center' -IconDebug $IconDebug); shape = 'plain'; fillColor = 'transparent'; fontsize = 14; fontname = "Tahoma" }

                    }
                    $PoolNumber++
                }
            }
        } else {
            SubGraph ProxyPools -Attributes @{Label = (Get-DiaHTMLLabel -ImagesObj $Images -Label "Backup Proxy Pools" -IconType "VB365_Proxy" -SubgraphLabel -IconDebug $IconDebug); fontsize = 18; penwidth = 1.5; labelloc = 'b'; style = 'dashed,rounded' } {

                Node -Name Proxies -Attributes @{Label = 'No Backup Proxy Pools'; shape = "rectangle"; labelloc = 'c'; fixedsize = $true; width = "3"; height = "2"; fillColor = 'transparent'; penwidth = 0 }
            }
        }

        # Restore Operator Graphviz Cluster
        if ($RestoreOperators) {
            $RestoreOperatorsInfo = @()

            $RestoreOperators | ForEach-Object {
                $OrgId = $_.OrganizationId
                $inobj = @{
                    'Organization' = Switch ([string]::IsNullOrEmpty(($Organizations | Where-Object { $_.Id -eq $OrgId }))) {
                        $true { 'Unknown' }
                        $false { ($Organizations | Where-Object { $_.Id -eq $OrgId }).Name }
                        default { 'Unknown' }
                    }
                }
                $RestoreOperatorsInfo += $inobj
            }

            SubGraph RestoreOp -Attributes @{Label = (Get-DiaHTMLLabel -ImagesObj $Images -Label "Restore Operators" -IconType "VB365_User_Group" -SubgraphLabel -IconDebug $IconDebug); fontsize = 18; penwidth = 1.5; labelloc = 'b'; style = 'dashed,rounded' } {

                Node RestoreOperators @{Label = (Get-DiaHTMLNodeTable -ImagesObj $Images -inputObject $RestoreOperators.Name -Align "Center" -iconType "VB365_User" -columnSize 3 -IconDebug $IconDebug -MultiIcon -AditionalInfo $RestoreOperatorsInfo); shape = 'plain'; fillColor = 'transparent'; fontsize = 14; fontname = "Tahoma" }
            }
        } else {
            SubGraph RestoreOp -Attributes @{Label = (Get-DiaHTMLLabel -ImagesObj $Images -Label "Restore Operators" -IconType "VB365_User_Group" -SubgraphLabel -IconDebug $IconDebug); fontsize = 18; penwidth = 1.5; labelloc = 'b'; style = 'dashed,rounded' } {

                Node -Name RestoreOperators -Attributes @{Label = 'No Restore Operators'; shape = "rectangle"; labelloc = 'c'; fixedsize = $true; width = "3"; height = "2"; fillColor = 'transparent'; penwidth = 0 }
            }
        }

        # Repositories Graphviz Cluster
        if ($Repositories) {
            $RepositoriesInfo = @()

            foreach ($Repository in $Repositories) {
                if ($Repository.ObjectStorageRepository.Name) {
                    $ObjStorage = $Repository.ObjectStorageRepository.Name
                } else {
                    $ObjStorage = 'None'
                }
                $inobj = [ordered] @{
                    # 'Path' = $Repository.Path
                    'Capacity' = ConvertTo-FileSizeString $Repository.Capacity
                    'Free Space' = ConvertTo-FileSizeString $Repository.FreeSpace
                    'ObjectStorage' = $ObjStorage
                }
                $RepositoriesInfo += $inobj
            }

            SubGraph Repos -Attributes @{Label = (Get-DiaHTMLLabel -ImagesObj $Images -Label "Backup Repositories" -IconType "Veeam_Repository" -SubgraphLabel -IconDebug $IconDebug); fontsize = 18; penwidth = 1.5; labelloc = 'b'; style = 'dashed,rounded' } {

                Node Repositories @{Label = (Get-DiaHTMLNodeTable -ImagesObj $Images -inputObject $Repositories.Name -Align "Center" -iconType "VB365_Repository" -columnSize 3 -IconDebug $IconDebug -MultiIcon -AditionalInfo $RepositoriesInfo); shape = 'plain'; fillColor = 'transparent'; fontsize = 14; fontname = "Tahoma" }
            }
        } else {
            SubGraph Repos -Attributes @{Label = (Get-DiaHTMLLabel -ImagesObj $Images -Label "Backup Repositories" -IconType "Veeam_Repository" -SubgraphLabel -IconDebug $IconDebug); fontsize = 18; penwidth = 1.5; labelloc = 'b'; style = 'dashed,rounded' } {

                Node -Name Repositories -Attributes @{Label = 'No Backup Repositories'; shape = "rectangle"; labelloc = 'c'; fixedsize = $true; width = "3"; height = "2"; fillColor = 'transparent'; penwidth = 0 }
            }
        }
        # Object Repositories Graphviz Cluster
        if ($ObjectRepositories) {

            $ObjectRepositoriesInfo = @()
            $ORIconType = @()

            $ObjectRepositories | ForEach-Object {
                $inobj = @{
                    'Type' = $_.Type
                    'Folder' = $_.Folder
                    'Immutability' = ConvertTo-TextYN $_.EnableImmutability
                }
                $ObjectRepositoriesInfo += $inobj
                $ORIconType += Switch ($_.Type) {
                    'AmazonS3' { 'VBR365_Amazon_S3' }
                    'AmazonS3Compatible' { 'VBR365_Amazon_S3_Compatible' }
                    'AzureBlob' { 'VBR365_Azure_Blob' }
                }
            }

            SubGraph ObjectRepos -Attributes @{Label = (Get-DiaHTMLLabel -ImagesObj $Images -Label "Object Repositories" -IconType "VB365_Object_Support" -SubgraphLabel -IconDebug $IconDebug); fontsize = 18; penwidth = 1.5; labelloc = 't'; style = 'dashed,rounded' } {

                Node ObjectRepositories @{Label = (Get-DiaHTMLNodeTable -ImagesObj $Images -inputObject $ObjectRepositories.Name -Align "Center" -iconType $ORIconType -columnSize 3 -IconDebug $IconDebug -MultiIcon -AditionalInfo $ObjectRepositoriesInfo); shape = 'plain'; fillColor = 'transparent'; fontsize = 14; fontname = "Tahoma" }
            }
        } else {
            SubGraph ObjectRepos -Attributes @{Label = (Get-DiaHTMLLabel -ImagesObj $Images -Label "Object Repositories" -IconType "VB365_Object_Support" -SubgraphLabel -IconDebug $IconDebug); fontsize = 18; penwidth = 1.5; labelloc = 't'; style = 'dashed,rounded' } {

                Node -Name ObjectRepositories -Attributes @{Label = 'No Object Repositories'; shape = "rectangle"; labelloc = 'c'; fixedsize = $true; width = "3"; height = "2"; fillColor = 'transparent'; penwidth = 0 }
            }
        }

        # Organization Graphviz Cluster
        SubGraph Organizations -Attributes @{Label = (Get-DiaHTMLLabel -ImagesObj $Images -Label "Organizations" -IconType "VB365_On_Premises" -SubgraphLabel -IconDebug $IconDebug); fontsize = 18; penwidth = 1.5; labelloc = 't'; style = 'dashed,rounded' } {

            # On-Premises Organization Graphviz Cluster
            if (($Organizations | Where-Object { $_.Type -eq 'OnPremises' })) {
                $OrganizationsInfo = @()

                ($Organizations | Where-Object { $_.Type -eq 'OnPremises' }) | ForEach-Object {
                    $inobj = @{
                        'Users' = "Licensed: $($_.LicensingOptions.LicensedUsersCount) - Trial: $($_.LicensingOptions.TrialUsersCount)"
                        'BackedUp' = ConvertTo-TextYN $_.IsBackedUp
                    }
                    $OrganizationsInfo += $inobj
                }

                SubGraph OnPremise -Attributes @{Label = (Get-DiaHTMLLabel -ImagesObj $Images -Label "On-premises" -IconType "VB365_On_Premises" -SubgraphLabel -IconDebug $IconDebug); fontsize = 18; penwidth = 1.5; labelloc = 't'; style = 'dashed,rounded' } {

                    Node OnpremisesOrg @{Label = (Get-DiaHTMLNodeTable -ImagesObj $Images -inputObject ($Organizations | Where-Object { $_.Type -eq 'OnPremises' }).Name -Align "Center" -iconType "Datacenter" -columnSize 3 -IconDebug $IconDebug -MultiIcon -AditionalInfo $OrganizationsInfo); shape = 'plain'; fillColor = 'transparent'; fontsize = 14; fontname = "Tahoma" }
                }
            }

            # Microsoft 365 Organization Graphviz Cluster
            if ($Organizations | Where-Object { $_.Type -eq 'Office365' }) {
                $OrganizationsInfo = @()

                ($Organizations | Where-Object { $_.Type -eq 'Office365' }) | ForEach-Object {
                    $inobj = @{
                        'Users' = "Licensed: $($_.LicensingOptions.LicensedUsersCount) - Trial: $($_.LicensingOptions.TrialUsersCount)"
                        'BackedUp' = ConvertTo-TextYN $_.IsBackedUp
                    }
                    $OrganizationsInfo += $inobj
                }
                SubGraph Microsoft365 -Attributes @{Label = (Get-DiaHTMLLabel -ImagesObj $Images -Label "Microsoft 365" -IconType "VB365_Microsoft_365" -SubgraphLabel -IconDebug $IconDebug); fontsize = 18; penwidth = 1.5; labelloc = 't'; style = 'dashed,rounded' } {

                    Node Microsoft365Org @{Label = (Get-DiaHTMLNodeTable -ImagesObj $Images -inputObject ($Organizations | Where-Object { $_.Type -eq 'Office365' }).Name -Align "Center" -iconType "Microsoft_365" -columnSize 3 -IconDebug $IconDebug -MultiIcon -AditionalInfo $OrganizationsInfo); shape = 'plain'; fillColor = 'transparent'; fontsize = 14; fontname = "Tahoma" }
                }
            }
        }

        # Veeam VB365 elements point of connection (Dummy Nodes!)
        $Node = @('VB365ServerPointSpace', 'VB365ProxyPoint', 'VB365ProxyPointSpace', 'VB365RepoPoint')
        Node $Node -NodeScript { $_ } @{Label = { $_ } ; fontcolor = $NodeDebug.color; fillColor = $NodeDebug.style; shape = $NodeDebug.shape }

        $NodeStartEnd = @('VB365StartPoint', 'VB365EndPointSpace')
        Node $NodeStartEnd -NodeScript { $_ } @{Label = { $_ } ; fontcolor = $NodeDebug.color; shape = 'point'; fixedsize = 'true'; width = .2 ; height = .2 }

        #---------------------------------------------------------------------------------------------#
        # Graphviz Rank Section #
        # Rank allow to put Nodes on the same group level #
        # PSgraph: https://psgraph.readthedocs.io/en/stable/Command-Rank-Advanced/ #
        # Graphviz: https://graphviz.org/docs/attrs/rank/ #
        #---------------------------------------------------------------------------------------------#

        # Put the dummy node in the same rank to be able to create a horizontal line
        Rank VB365ServerPointSpace, VB365ProxyPoint, VB365ProxyPointSpace, VB365RepoPoint, VB365StartPoint, VB365EndPointSpace

        if ($RestorePortal.IsServiceEnabled) {
            # Put the VB365Server and the VB365RestorePortal in the same level to align it horizontally
            Rank VB365RestorePortal, VB365Server
        }

        #---------------------------------------------------------------------------------------------#
        # Graphviz Edge Section #
        # Edges are Graphviz elements use to interconnect Nodes #
        # Edges can have attribues like Shape, Size, Styles etc.. #
        # PSgraph: https://psgraph.readthedocs.io/en/latest/Command-Edge/ #
        # Graphviz: https://graphviz.org/docs/edges/ #
        #---------------------------------------------------------------------------------------------#

        # Connect the Dummy Node in a straight line
        # VB365StartPoint --- VB365ServerPointSpace --- VB365ProxyPoint --- VB365ProxyPointSpace --- VB365RepoPoint --- VB365EndPointSpace
        Edge -From VB365StartPoint -To VB365ServerPointSpace @{minlen = 10; arrowtail = 'none'; arrowhead = 'none'; style = 'filled' }
        Edge -From VB365ServerPointSpace -To VB365ProxyPoint @{minlen = 10; arrowtail = 'none'; arrowhead = 'none'; style = 'filled' }
        Edge -From VB365ProxyPoint -To VB365ProxyPointSpace @{minlen = 10; arrowtail = 'none'; arrowhead = 'none'; style = 'filled' }
        Edge -From VB365ProxyPointSpace -To VB365RepoPoint @{minlen = 10; arrowtail = 'none'; arrowhead = 'none'; style = 'filled' }
        Edge -From VB365RepoPoint -To VB365EndPointSpace @{minlen = 10; arrowtail = 'none'; arrowhead = 'none'; style = 'filled' }

        # Connect Veeam Backup server to the Dummy line
        Edge -From VB365Server -To VB365ServerPointSpace @{minlen = 2; arrowtail = 'dot'; arrowhead = 'none'; style = 'dashed' }

        # Connect Veeam Backup server to RetorePortal
        if ($RestorePortal.IsServiceEnabled) {
            Edge -From VB365RestorePortal -To VB365Server @{minlen = 2; arrowtail = 'dot'; arrowhead = 'normal'; style = 'dashed'; color = '#DF8c42' }
        }
        # Connect Veeam Backup Server to Organization Graphviz Cluster
        if ($Organizations | Where-Object { $_.Type -eq 'OnPremises' }) {
            Edge -To VB365Server -From OnpremisesOrg @{minlen = 2; arrowtail = 'dot'; arrowhead = 'normal'; style = 'dashed'; color = '#DF8c42' }
        } elseif ($Organizations | Where-Object { $_.Type -eq 'Office365' }) {
            Edge -To VB365Server -From Microsoft365Org @{minlen = 2; arrowtail = 'dot'; arrowhead = 'normal'; style = 'dashed'; color = '#DF8c42' }
        } else {
            SubGraph Organizations -Attributes @{Label = (Get-DiaHTMLLabel -ImagesObj $Images -Label "Organizations" -IconType "VB365_On_Premises" -SubgraphLabel -IconDebug $IconDebug); fontsize = 18; penwidth = 1.5; labelloc = 't'; style = 'dashed,rounded' } {
                Node -Name DummyNoOrganization -Attributes @{Label = 'No Organization'; shape = "rectangle"; labelloc = 'c'; fixedsize = $true; width = "3"; height = "2"; fillColor = 'transparent'; penwidth = 0 }
            }
            Edge -To VB365Server -From DummyNoOrganization @{minlen = 2; arrowtail = 'dot'; arrowhead = 'normal'; style = 'dashed'; color = '#DF8c42' }
        }

        # Connect Veeam RestorePortal to the Restore Operators
        Edge -From VB365ServerPointSpace -To RestoreOperators @{minlen = 2; arrowtail = 'none'; arrowhead = 'dot'; style = 'dashed' }

        # Connect Veeam Proxies Server to the Dummy line
        Edge -From VB365ProxyPoint -To Proxies @{minlen = 2; arrowtail = 'none'; arrowhead = 'dot'; style = 'dashed' }

        if ($ProxyPools) {
            Edge -From Proxies -To ProxyPool0 @{minlen = 1; arrowtail = 'none'; arrowhead = 'dot'; style = 'dashed' }
        }

        # Connect Veeam Repository to the Dummy line
        Edge -From VB365RepoPoint -To Repositories @{minlen = 2; arrowtail = 'none'; arrowhead = 'dot'; style = 'dashed' }

        # Connect Veeam Object Repository to the Dummy line
        Edge -To VB365RepoPoint -From ObjectRepositories @{minlen = 2; arrowtail = 'dot'; arrowhead = 'none'; style = 'dashed' }

        # End results example
        #
        #------------------------------------------------------------------------------------------------------------------------------------
        #
        #------------------------------------------------------------------------------------------------------------------------------------
        # |---------------------------------------------------| ^
        # | |---------------------------------------------| | |
        # | | Subgraph Logo | Organization | | |
        # | |---------------------------------------------| | MainGraph Cluster Board
        # ----------------------o| | Onpremise Table | Microsoft 365 Table | |
        # | | |---------------------------------------------| |
        # | |---------------------------------------------------|
        # | Organization Graphviz Cluster
        # |
        # \-/
        # |
        # |--------------|
        # | ICON |
        # |--------------|
        # | VB365 Server | <--- Graphviz Node Example
        # |--------------|
        # | Version: |
        # |--------------|
        # O Dummy Nodes
        # | |
        # | |
        # | \|/
        # VB365StartPoint --- VB365ServerPointSpace --- VB365ProxyPoint --- VB365ProxyPointSpace --- VB365RepoPoint --- VB365EndPointSpace
        # |
        # | <--- Graphviz Edge Example
        # |
        # O
        # |------------------------------------|
        # | |------------------------------| |
        # | | ICON | ICON | |
        # | |------------------------------| |
        # | | Proxy Server | Proxy Server | | <--- Graphviz Cluster Example
        # | |------------------------------| |
        # | | Subgraph Logo | Backup Proxy | |
        # | |------------------------------| |
        # |------------------------------------|
        # Proxy Graphviz Cluster
        #
        #--------------------------------------------------------------------------------------------------------------------------------------
        # |---------------------------|
        # |--------- |
        # | | Author Name |
        # Signature -----> | Logo | |
        # | | Company Name |
        # |--------- |
        # |---------------------------|
        #--------------------------------------------------------------------------------------------------------------------------------------
        # ^
        # |
        # |
        # OUTERDRAWBOARD1 Cluster Board
    }
}