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) $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 } |