Modules/Build-AzureResourceReport.psm1

<#
.Synopsis
Main module for Excel Report Building
 
.DESCRIPTION
This module is the main module for building the Excel Report.
 
.Link
https://github.com/microsoft/ARI/Modules/Build-AzureResourceReport.psm1
 
.COMPONENT
This powershell Module is part of Azure Resource Inventory (ARI)
 
.NOTES
Version: 4.0.1
First Release Date: 15th Oct, 2024
Authors: Claudio Merola
 
#>

Function Build-AzureResourceReport {
    Param($Subscriptions, $ExtractionRuntime, $Resources, $ResourceContainers, $Policies, $Advisories, $Security, $File, $DDFile, $RunOnline, $Repo, $RawRepo, $SkipDiagram, $RunLite, $FullEnv, $DiagramCache, $PlatOS, $InTag, $SkipPolicy, $SkipAdvisory, $Debug)

    if ($Debug.IsPresent)
        {
            $DebugPreference = 'Continue'
            $ErrorActionPreference = 'Continue'
        }
    else
        {
            $ErrorActionPreference = "silentlycontinue"
        }

    $ReportingRunTime = Measure-Command -Expression {

    #### Generic Conditional Text rules, Excel style specifications for the spreadsheets and tables:
    $TableStyle = "Light19"
    Write-Debug ((get-date -Format 'yyyy-MM-dd_HH_mm_ss')+' - '+'Excel Table Style used: ' + $TableStyle)

    Write-Progress -activity 'Azure Inventory' -Status "21% Complete." -PercentComplete 21 -CurrentOperation "Starting to process extraction data.."


    <######################################################### IMPORT UNSUPPORTED VERSION LIST ######################################################################>

    Write-Debug ((get-date -Format 'yyyy-MM-dd_HH_mm_ss')+' - '+'Importing List of Unsupported Versions.')
        if($PSScriptRoot -like '*\*')
            {
                Write-Debug ((get-date -Format 'yyyy-MM-dd_HH_mm_ss')+' - '+'Looking for the following file: '+$PSScriptRoot + '\Extras\Support.json')
                $ModuSeq0 = New-Object System.IO.StreamReader($PSScriptRoot + '\Extras\Support.json')
            }
        else
            {
                Write-Debug ((get-date -Format 'yyyy-MM-dd_HH_mm_ss')+' - '+'Looking for the following file: '+$PSScriptRoot + '/Extras/Support.json')
                $ModuSeq0 = New-Object System.IO.StreamReader($PSScriptRoot + '/Extras/Support.json')
            }
        $ModuSeq = $ModuSeq0.ReadToEnd()
        $ModuSeq0.Dispose()

    $Unsupported = $ModuSeq | ConvertFrom-Json

    $DataActive = ('Azure Resource Inventory Reporting (' + ($Resources.count) + ') Resources')

    <######################################################### DRAW.IO DIAGRAM JOB ######################################################################>

    Write-Debug ((get-date -Format 'yyyy-MM-dd_HH_mm_ss')+' - '+'Checking if Draw.io Diagram Job Should be Run.')
    if (!$SkipDiagram.IsPresent) {
        Write-Debug ((get-date -Format 'yyyy-MM-dd_HH_mm_ss')+' - '+'Starting Draw.io Diagram Processing Job.')
        Start-job -Name 'DrawDiagram' -ScriptBlock {

            Import-Module AzureResourceInventory

            $DiagramCache = $($args[5])

            $TempPath = $DiagramCache.split("DiagramCache\")[0]

            $Logfile = ($TempPath+'DiagramLogFile.log')

            ('DrawIOCoreJob - '+(get-date -Format 'yyyy-MM-dd_HH_mm_ss')+' - Starting Draw.IO Job') | Out-File -FilePath $LogFile -Append

            ('DrawIOCoreJob - '+(get-date -Format 'yyyy-MM-dd_HH_mm_ss')+' - Calling Draw.IO Thread') | Out-File -FilePath $LogFile -Append
            try
                {
                    Invoke-ARIDrawIODiagram -Subscriptions $($args[0]) -Resources $($args[1]) -Advisories $($args[2]) -DDFile $($args[3]) -DiagramCache $($args[4]) -FullEnvironment $($args[5]) -ResourceContainers $($args[6])

                }
            catch
                {
                    ('DrawIOCoreJob - '+(get-date -Format 'yyyy-MM-dd_HH_mm_ss')+' - '+$_.Exception.Message) | Out-File -FilePath $LogFile -Append
                }
            ('DrawIOCoreJob - '+(get-date -Format 'yyyy-MM-dd_HH_mm_ss')+' - Draw.IO Ended.') | Out-File -FilePath $LogFile -Append

        } -ArgumentList $Subscriptions, $Resources, $Advisories, $DDFile, $DiagramCache, $FullEnv, $ResourceContainers | Out-Null
    }

    <######################################################### VISIO DIAGRAM JOB ######################################################################>
    <#
    Write-Debug ((get-date -Format 'yyyy-MM-dd_HH_mm_ss')+' - '+'Checking if Visio Diagram Job Should be Run.')
    if ($Diagram.IsPresent) {
        Write-Debug ((get-date -Format 'yyyy-MM-dd_HH_mm_ss')+' - '+'Starting Visio Diagram Processing Job.')
        Start-job -Name 'VisioDiagram' -ScriptBlock {
 
            If ($($args[5]) -eq $true) {
                $ModuSeq = (New-Object System.Net.WebClient).DownloadString($($args[7]) + '/Extras/VisioDiagram.ps1')
            }
            Else {
                $ModuSeq0 = New-Object System.IO.StreamReader($($args[0]) + '\Extras\VisioDiagram.ps1')
                $ModuSeq = $ModuSeq0.ReadToEnd()
                $ModuSeq0.Dispose()
            }
 
            $ScriptBlock = [Scriptblock]::Create($ModuSeq)
 
            $VisioRun = ([PowerShell]::Create()).AddScript($ScriptBlock).AddArgument($($args[1])).AddArgument($($args[2])).AddArgument($($args[3])).AddArgument($($args[4]))
 
            $VisioJob = $VisioRun.BeginInvoke()
 
            while ($VisioJob.IsCompleted -contains $false) {}
 
            $VisioRun.EndInvoke($VisioJob)
 
            $VisioRun.Dispose()
 
        } -ArgumentList $PSScriptRoot, $Subscriptions, $Resources, $Advisories, $DFile, $RunOnline, $Repo, $RawRepo | Out-Null
    }
    #>


    <######################################################### SECURITY CENTER JOB ######################################################################>

    Write-Debug ((get-date -Format 'yyyy-MM-dd_HH_mm_ss')+' - '+'Checking If Should Run Security Center Job.')
    if ($SecurityCenter.IsPresent) {
        Write-Debug ((get-date -Format 'yyyy-MM-dd_HH_mm_ss')+' - '+'Starting Security Job.')
        Start-Job -Name 'Security' -ScriptBlock {

            Import-Module AzureResourceInventory

            $SecResult = Invoke-ARISecCenterProcessing -Subscriptions $Subscriptions -Security $Security

            $SecResult

        } -ArgumentList $Subscriptions , $Security | Out-Null
    }

    <######################################################### POLICY JOB ######################################################################>

    Write-Debug ((get-date -Format 'yyyy-MM-dd_HH_mm_ss')+' - '+'Checking If Should Run Policy Job.')
    if (!$SkipPolicy.IsPresent) {
        Write-Debug ((get-date -Format 'yyyy-MM-dd_HH_mm_ss')+' - '+'Starting Policy Processing Job.')
        Start-Job -Name 'Policy' -ScriptBlock {

            Import-Module AzureResourceInventory

            $PolResult = Invoke-ARIPolicyProcessing -Subscriptions $($args[0]) -Policies $($args[1])

            $PolResult

        } -ArgumentList $Subscriptions, $Policies | Out-Null
    }

    <######################################################### ADVISORY JOB ######################################################################>

    Write-Debug ((get-date -Format 'yyyy-MM-dd_HH_mm_ss')+' - '+'Checking If Should Run Advisory Job.')
    if (!$SkipAdvisory.IsPresent) {
        Write-Debug ((get-date -Format 'yyyy-MM-dd_HH_mm_ss')+' - '+'Starting Advisory Processing Job.')
        Start-Job -Name 'Advisory' -ScriptBlock {

            Import-Module AzureResourceInventory

            $AdvResult = Invoke-ARIAdvisoryProcessing -Advisories $($args[0])

            $AdvResult

        } -ArgumentList $Advisories | Out-Null
    }

    <######################################################### SUBSCRIPTIONS JOB ######################################################################>

    Write-Debug ((get-date -Format 'yyyy-MM-dd_HH_mm_ss')+' - '+'Starting Subscriptions job.')
    Start-Job -Name 'Subscriptions' -ScriptBlock {

        Import-Module AzureResourceInventory

        $SubResult = Invoke-ARISubsProcessing -Subscriptions $($args[0]) -Resources $($args[1])

        $SubResult

    } -ArgumentList $Subscriptions, $Resources | Out-Null

    <######################################################### RESOURCE GROUP JOB ######################################################################>

    switch ($Resources.count)
        {
            {$_ -le 1000}
                {
                    $EnvSizeLooper = 1000
                    $DebugEnvSize = 'Small'
                }
            {$_ -gt 1000 -and $_ -le 30000}
                {
                    $EnvSizeLooper = 5000
                    $DebugEnvSize = 'Medium'
                }
            {$_ -gt 30000 -and $_ -le 60000}
                {
                    $EnvSizeLooper = 10000
                    $DebugEnvSize = 'Large'
                    Write-Host $DebugEnvSize -NoNewline -ForegroundColor Green
                    Write-Host (' Size Environment Identified.')
                    Write-Host ('Jobs will be run in batches to avoid CPU Overload.')
                }
            {$_ -gt 60000}
                {
                    $EnvSizeLooper = 5000
                    $DebugEnvSize = 'Enormous'
                    Write-Host $DebugEnvSize -NoNewline -ForegroundColor Green
                    Write-Host (' Size Environment Identified.')
                    Write-Host ('Jobs will be run in batches to prevent CPU Overload.')
                }
        }
        Write-Debug ((get-date -Format 'yyyy-MM-dd_HH_mm_ss')+' - '+'Starting Processing Jobs in '+ $DebugEnvSize +' Mode.')

        $Loop = $Resources.count / $EnvSizeLooper
        $Loop = [math]::ceiling($Loop)
        $Looper = 0
        $Limit = 0
        $JobLoop = 1

        $ResourcesCount = [string]$Resources.count
        Write-Debug ((get-date -Format 'yyyy-MM-dd_HH_mm_ss')+' - '+'Total Resources Being Processed: '+ $ResourcesCount)

        while ($Looper -lt $Loop) {
            $Looper ++

            $Resource = $Resources | Select-Object -First $EnvSizeLooper -Skip $Limit

            $ResourceCount = [string]$Resource.count
            $LoopCountStr = [string]$Looper
            Write-Debug ((get-date -Format 'yyyy-MM-dd_HH_mm_ss')+' - '+'Resources Being Processed in ResourceJob_'+ $LoopCountStr + ': ' + $ResourceCount)

            Start-Job -Name ('ResourceJob_'+$Looper) -ScriptBlock {

                $Job = @()

                $Subscriptions = $($args[2])
                $InTag = $($args[3])
                $Resource = $($args[4])
                $Task = $($args[5])
                $Unsupported = $($args[9])

                if($($args[1]) -like '*\*')
                    {
                        $Modules = Get-ChildItem -Path ($($args[1]) + '\Scripts\*.ps1') -Recurse
                    }
                else
                    {
                        $Modules = Get-ChildItem -Path ($($args[1]) + '/Scripts/*.ps1') -Recurse
                    }

                $job = @()

                $Modules | ForEach-Object {
                    $ModName = $_.Name.replace(".ps1","")
                    $ModuSeq0 = New-Object System.IO.StreamReader($_.FullName)
                    $ModuSeq = $ModuSeq0.ReadToEnd()
                    $ModuSeq0.Dispose()
                    Start-Sleep -Milliseconds 250

                    New-Variable -Name ('ModRun' + $ModName)
                    New-Variable -Name ('ModJob' + $ModName)

                    Set-Variable -Name ('ModRun' + $ModName) -Value ([PowerShell]::Create()).AddScript($ModuSeq).AddArgument($PSScriptRoot).AddArgument($Subscriptions).AddArgument($InTag).AddArgument($Resource).AddArgument($Task).AddArgument($null).AddArgument($null).AddArgument($null).AddArgument($Unsupported)

                    Set-Variable -Name ('ModJob' + $ModName) -Value ((get-variable -name ('ModRun' + $ModName)).Value).BeginInvoke()

                    $job += (get-variable -name ('ModJob' + $ModName)).Value
                    Start-Sleep -Milliseconds 250
                    Clear-Variable -Name ModName
                }

                while ($Job.Runspace.IsCompleted -contains $false) { Start-Sleep -Milliseconds 1000 }

                $Modules | ForEach-Object {
                    $ModName = $_.Name.replace(".ps1","")
                    Start-Sleep -Milliseconds 250

                    New-Variable -Name ('ModValue' + $ModName)
                    Set-Variable -Name ('ModValue' + $ModName) -Value (((get-variable -name ('ModRun' + $ModName)).Value).EndInvoke((get-variable -name ('ModJob' + $ModName)).Value))

                    Clear-Variable -Name ('ModRun' + $ModName)
                    Clear-Variable -Name ('ModJob' + $ModName)
                    Start-Sleep -Milliseconds 250
                    Clear-Variable -Name ModName
                }

                [System.GC]::GetTotalMemory($true) | out-null

                $Hashtable = New-Object System.Collections.Hashtable

                $Modules | ForEach-Object {
                    $ModName = $_.Name.replace(".ps1","")
                    Start-Sleep -Milliseconds 250

                    $Hashtable["$ModName"] = (get-variable -name ('ModValue' + $ModName)).Value

                    Clear-Variable -Name ('ModValue' + $ModName)
                    Start-Sleep -Milliseconds 100

                    Clear-Variable -Name ModName
                }

                [System.GC]::GetTotalMemory($true) | out-null

            $Hashtable
            } -ArgumentList $null, $PSScriptRoot, $Subscriptions, $InTag, $Resource, 'Processing', $null, $null, $null, $Unsupported | Out-Null
            $Limit = $Limit + $EnvSizeLooper
            Start-Sleep -Milliseconds 250
            if($DebugEnvSize -in ('Large','Enormous') -and $JobLoop -eq 5)
                {
                    Write-Debug ((get-date -Format 'yyyy-MM-dd_HH_mm_ss')+' - '+'Waiting Batch of Jobs to Complete.')

                    $coun = 0

                    $InterJobNames = (Get-Job | Where-Object {$_.name -like 'ResourceJob_*' -and $_.State -eq 'Running'}).Name

                    while (get-job -Name $InterJobNames | Where-Object { $_.State -eq 'Running' }) {
                        $jb = get-job -Name $InterJobNames
                        $c = (((($jb.count - ($jb | Where-Object { $_.State -eq 'Running' }).Count)) / $jb.Count) * 100)
                        Write-Debug ((get-date -Format 'yyyy-MM-dd_HH_mm_ss')+' - '+'initial Jobs Running: '+[string]($jb | Where-Object { $_.State -eq 'Running' }).count)
                        $c = [math]::Round($coun)
                        Write-Progress -Id 1 -activity "Processing Initial Resource Jobs" -Status "$coun% Complete." -PercentComplete $coun
                        Start-Sleep -Seconds 15
                    }
                    $JobLoop = 0
                }
            $JobLoop ++
            [System.GC]::GetTotalMemory($true) | out-null
        }

    <############################################################## RESOURCES LOOP CREATION #############################################################>


    $ResourcesCount = $Resources.Count

    if($DebugEnvSize -in ('Large','Enormous'))
        {
            Clear-Variable Resources
            [System.GC]::GetTotalMemory($true) | out-null
        }

    Write-Debug ((get-date -Format 'yyyy-MM-dd_HH_mm_ss')+' - '+'Starting Jobs Collector.')
    Write-Progress -activity $DataActive -Status "Processing Inventory" -PercentComplete 0
    $c = 0

    $JobNames = (Get-Job | Where-Object {$_.name -like 'ResourceJob_*'}).Name

    while (get-job -Name $JobNames | Where-Object { $_.State -eq 'Running' }) {
        $jb = get-job -Name $JobNames
        $c = (((($jb.count - ($jb | Where-Object { $_.State -eq 'Running' }).Count)) / $jb.Count) * 100)
        Write-Debug ((get-date -Format 'yyyy-MM-dd_HH_mm_ss')+' - '+'Jobs Still Running: '+[string]($jb | Where-Object { $_.State -eq 'Running' }).count)
        $c = [math]::Round($c)
        Write-Progress -Id 1 -activity "Processing Resource Jobs" -Status "$c% Complete." -PercentComplete $c
        Start-Sleep -Seconds 5
    }
    Write-Progress -Id 1 -activity "Processing Resource Jobs" -Status "100% Complete." -Completed

    Write-Debug ((get-date -Format 'yyyy-MM-dd_HH_mm_ss')+' - '+'Jobs Compleated.')

    $AzSubs = Receive-Job -Name 'Subscriptions'

    $SmaResources = @()

    Foreach ($Job in $JobNames)
        {
            $TempJob = Receive-Job -Name $Job
            Write-Debug ((get-date -Format 'yyyy-MM-dd_HH_mm_ss')+' - '+'Job '+ $Job +' Returned: ' + ($TempJob.values | Where-Object {$_ -ne $null}).Count + ' Resource Types.')
            $SmaResources += $TempJob
        }

    <############################################################## REPORTING ###################################################################>

    Write-Debug ((get-date -Format 'yyyy-MM-dd_HH_mm_ss')+' - '+'Starting Reporting Phase.')
    Write-Progress -activity $DataActive -Status "Processing Inventory" -PercentComplete 50

    Write-Debug ((get-date -Format 'yyyy-MM-dd_HH_mm_ss')+' - '+'Running Offline, Gathering List Of Modules.')
    if($PSScriptRoot -like '*\*')
        {
            $Modules = Get-ChildItem -Path ($PSScriptRoot + '\Scripts\*.ps1') -Recurse
        }
    else
        {
            $Modules = Get-ChildItem -Path ($PSScriptRoot + '/Scripts/*.ps1') -Recurse
        }

    Write-Debug ((get-date -Format 'yyyy-MM-dd_HH_mm_ss')+' - '+'Modules Found: ' + $Modules.Count)
    $Lops = $Modules.count
    $ReportCounter = 0

    foreach ($Module in $Modules) {

        $c = (($ReportCounter / $Lops) * 100)
        $c = [math]::Round($c)
        Write-Progress -Id 1 -activity "Building Report" -Status "$c% Complete." -PercentComplete $c

        $ModuSeq0 = New-Object System.IO.StreamReader($Module.FullName)
        $ModuSeq = $ModuSeq0.ReadToEnd()
        $ModuSeq0.Dispose()
        Start-Sleep -Milliseconds 50
        $ModuleName = $Module.name.replace('.ps1','')

        $ModuleResourceCount = $SmaResources.$ModuleName.count

        if ($ModuleResourceCount -gt 0)
            {
                Start-Sleep -Milliseconds 100
                Write-Debug ((get-date -Format 'yyyy-MM-dd_HH_mm_ss')+' - '+"Running Module: '$ModuleName'. Resources Count: $ModuleResourceCount")

                $ExcelRun = ([PowerShell]::Create()).AddScript($ModuSeq).AddArgument($PSScriptRoot).AddArgument($null).AddArgument($InTag).AddArgument($null).AddArgument('Reporting').AddArgument($file).AddArgument($SmaResources).AddArgument($TableStyle).AddArgument($Unsupported)

                $ExcelJob = $ExcelRun.BeginInvoke()

                while ($ExcelJob.IsCompleted -contains $false) { Start-Sleep -Milliseconds 100 }

                $ExcelRun.EndInvoke($ExcelJob)

                $ExcelRun.Dispose()

                [System.GC]::GetTotalMemory($true) | out-null
            }

        $ReportCounter ++

    }

    if($DebugEnvSize -in ('Large','Enormous'))
        {
            Clear-Variable SmaResources -Scope Global
            [System.GC]::GetTotalMemory($true) | out-null
        }

    Write-Debug ((get-date -Format 'yyyy-MM-dd_HH_mm_ss')+' - '+'Resource Reporting Phase Done.')

    <################################################################### QUOTAS ###################################################################>

    if($QuotaUsage.IsPresent)
        {
            get-job -Name 'Quota Usage' | Wait-Job

            $AzQuota = Receive-Job -Name 'Quota Usage'

            Write-Debug ((get-date -Format 'yyyy-MM-dd_HH_mm_ss')+' - '+'Generating Quota Usage sheet for: ' + $AzQuota.count + ' Subscriptions/Regions.')

            Write-Progress -activity 'Azure Resource Inventory Quota Usage' -Status "50% Complete." -PercentComplete 50 -CurrentOperation "Building Quota Sheet"

            Build-ARIQuotaReport -File $File -AzQuota $AzQuota -TableStyle $TableStyle

            Write-Progress -activity 'Azure Resource Inventory Quota Usage' -Status "100% Complete." -Completed
        }


    <################################################ SECURITY CENTER #######################################################>
    #### Security Center worksheet is generated apart

    Write-Debug ((get-date -Format 'yyyy-MM-dd_HH_mm_ss')+' - '+'Checking if Should Generate Security Center Sheet.')
    if ($SecurityCenter.IsPresent) {
        Write-Debug ((get-date -Format 'yyyy-MM-dd_HH_mm_ss')+' - '+'Generating Security Center Sheet.')
        $Secadvco = $Security.Count

        Write-Progress -activity $DataActive -Status "Building Security Center Report" -PercentComplete 0 -CurrentOperation "Considering $Secadvco Security Advisories"

        while (get-job -Name 'Security' | Where-Object { $_.State -eq 'Running' }) {
            Write-Progress -Id 1 -activity 'Processing Security Center Advisories' -Status "50% Complete." -PercentComplete 50
            Start-Sleep -Seconds 2
        }
        Write-Progress -Id 1 -activity 'Processing Security Center Advisories'  -Status "100% Complete." -Completed

        $Sec = Receive-Job -Name 'Security'

        Build-ARISecCenterReport -File $File -Sec $Sec -TableStyle $TableStyle

    }


    <################################################ POLICY #######################################################>
    #### Policy worksheet is generated apart from the resources
    Write-Debug ((get-date -Format 'yyyy-MM-dd_HH_mm_ss')+' - '+'Checking if Should Generate Policy Sheet.')
    if (!$SkipPolicy.IsPresent) {
        Write-Debug ((get-date -Format 'yyyy-MM-dd_HH_mm_ss')+' - '+'Generating Policy Sheet.')
        $polco = $Policies.count

        Write-Progress -activity $DataActive -Status "Building Policy Report" -PercentComplete 0 -CurrentOperation "Considering $polco Policies"

        while (get-job -Name 'Policy' | Where-Object { $_.State -eq 'Running' }) {
            Write-Progress -Id 1 -activity 'Processing Policies' -Status "50% Complete." -PercentComplete 50
            Write-Debug ((get-date -Format 'yyyy-MM-dd_HH_mm_ss')+' - '+'Policy Job is: '+(get-job -Name 'Policy').State)
            Start-Sleep -Seconds 2
        }
        Write-Progress -Id 1 -activity 'Processing Policies'  -Status "100% Complete." -Completed

        $Pol = Receive-Job -Name 'Policy'

        Build-ARIPolicyReport -File $File -Pol $Pol -TableStyle $TableStyle

    }


    <################################################ ADVISOR #######################################################>
    #### Advisor worksheet is generated apart from the resources
    Write-Debug ((get-date -Format 'yyyy-MM-dd_HH_mm_ss')+' - '+'Checking if Should Generate Advisory Sheet.')
    if (!$SkipAdvisory.IsPresent) {
        Write-Debug ((get-date -Format 'yyyy-MM-dd_HH_mm_ss')+' - '+'Generating Advisor Sheet.')
        $advco = $Advisories.count

        Write-Progress -activity $DataActive -Status "Building Advisories Report" -PercentComplete 0 -CurrentOperation "Considering $advco Advisories"

        while (get-job -Name 'Advisory' | Where-Object { $_.State -eq 'Running' }) {
            Write-Progress -Id 1 -activity 'Processing Advisories' -Status "50% Complete." -PercentComplete 50
            Write-Debug ((get-date -Format 'yyyy-MM-dd_HH_mm_ss')+' - '+'Advisory Job is: '+(get-job -Name 'Advisory').State)
            Start-Sleep -Seconds 2
        }
        Write-Progress -Id 1 -activity 'Processing Advisories'  -Status "100% Complete." -Completed

        $Adv = Receive-Job -Name 'Advisory'

        Build-ARIAdvisoryReport -File $File -Adv $Adv -TableStyle $TableStyle

    }

    <################################################################### SUBSCRIPTIONS ###################################################################>

    Write-Debug ((get-date -Format 'yyyy-MM-dd_HH_mm_ss')+' - '+'Generating Subscription sheet for: ' + $Subscriptions.count + ' Subscriptions.')

    Write-Progress -activity 'Azure Resource Inventory Subscriptions' -Status "50% Complete." -PercentComplete 50 -CurrentOperation "Building Subscriptions Sheet"

    Build-ARISubsReport -File $File -Sub $AzSubs -TableStyle $TableStyle

    [System.GC]::GetTotalMemory($true) | out-null

    Write-Progress -activity 'Azure Resource Inventory Subscriptions' -Status "100% Complete." -Completed

    <################################################################### CHARTS ###################################################################>

    Write-Debug ((get-date -Format 'yyyy-MM-dd_HH_mm_ss')+' - '+'Generating Overview sheet (Charts).')

    Write-Progress -activity 'Azure Resource Inventory Reporting Charts' -Status "10% Complete." -PercentComplete 10 -CurrentOperation "Starting Excel Chart's Thread."

}

    Write-Progress -activity 'Azure Resource Inventory Reporting Charts' -Status "15% Complete." -PercentComplete 15 -CurrentOperation "Invoking Excel Chart's Thread."

    Build-ARIExcelChart -File $File -TableStyle $TableStyle -PlatOS $PlatOS -Subscriptions $Subscriptions -Resources $Resources -ExtractionRunTime $ExtractionRuntime -ReportingRunTime $ReportingRunTime -RunLite $RunLite

    [System.GC]::GetTotalMemory($true) | out-null

    Write-Debug ((get-date -Format 'yyyy-MM-dd_HH_mm_ss')+' - '+'Finished Charts Phase.')

    Write-Progress -activity 'Azure Resource Inventory Reporting Charts' -Status "100% Complete." -Completed

    if(!$SkipDiagram.IsPresent)
    {
    Write-Progress -activity 'Diagrams' -Status "Completing Diagram" -PercentComplete 70 -CurrentOperation "Consolidating Diagram"

        while (get-job -Name 'DrawDiagram' | Where-Object { $_.State -eq 'Running' }) {
            Write-Progress -Id 1 -activity 'Processing Diagrams' -Status "50% Complete." -PercentComplete 50
            Start-Sleep -Seconds 2
        }
        Write-Progress -Id 1 -activity 'Processing Diagrams'  -Status "100% Complete." -Completed

    Write-Progress -activity 'Diagrams' -Status "Closing Diagram File" -Completed
    }

    Get-Job | Wait-Job | Remove-Job
}