analyzer/2_wara_data_analyzer.ps1
#Requires -Version 7 <# .SYNOPSIS Well-Architected Reliability Assessment Script .DESCRIPTION The script "2_wara_data_analyzer" will process the JSON file created by the "1_wara_collector" script and will edit the Expert-Analysis Excel file. .PARAMETER Help Switch to display help information. .PARAMETER RepositoryUrl Specifies the git repository URL that contains APRL contents if you want to use custom APRL repository. .PARAMETER JSONFile Path to the JSON file created by the "1_wara_collector" script. .PARAMETER ExpertAnalysisFile Path to the Expert-Analysis file to be customized by script. .EXAMPLE .\2_wara_data_analyzer.ps1 -JSONFile 'C:\Temp\WARA_File_2024-04-01_10_01.json' -ExpertAnalysisFile 'C:\Temp\Expert-Analysis-v1.xlsx' .LINK https://github.com/Azure/Azure-Proactive-Resiliency-Library-v2 #> Param( [ValidatePattern('^https:\/\/.+$')] [string] $RecommendationsUrl = 'https://azure.github.io/WARA-Build/objects/recommendations.json', [Parameter(mandatory = $true)] [string] $JSONFile, [string] $ExpertAnalysisFile ) # WARA In Scope Resource Types CSV File $RecommendationResourceTypesUri = 'https://raw.githubusercontent.com/Azure/Azure-Proactive-Resiliency-Library-v2/refs/heads/main/tools/WARAinScopeResTypes.csv' # Check if the Expert-Analysis file exists $ExpertAnalysisPath = $PSScriptRoot + '\Expert-Analysis-v1.xlsx' if (!$ExpertAnalysisFile) { Write-Debug ((get-date -Format 'yyyy-MM-dd HH:mm:ss') + (' - Testing: ' + './Expert-Analysis-v1.xlsx')) if ((Test-Path -Path ($ExpertAnalysisPath) -PathType Leaf) -eq $true) { $ExpertAnalysisFile = $ExpertAnalysisPath } } else { Throw "Error locating the Expert-Analysis file. Please provide a valid path to the Expert-Analysis file or reinstall the WARA Module." Exit } if ((Test-Path -Path $ExpertAnalysisFile -PathType Leaf) -eq $true) { $ExpertAnalysisFile = (Resolve-Path -Path $ExpertAnalysisFile).Path } else { Throw "The Expert-Analysis file does not exist. Please provide a valid path to the Expert-Analysis file." Exit } # Check if the JSON file exists if ((Test-Path -Path $JSONFile -PathType Leaf) -eq $true) { $JSONFile = (Resolve-Path -Path $JSONFile).Path } else { Throw "JSON file not found. Please provide a valid path to the JSON file." Exit } $TableStyle = 'Light19' # Classes Class ImpactedResourceObj { [string] $ValidationMSG [string] $ValidationCategory [string] $ResourceType [string] $SubscriptionId [string] $ResourceGroup [string] $Location [string] $Name [string] $Id [string] $Custom1 [string] $Custom2 [string] $Custom3 [string] $Custom4 [string] $Custom5 [string] $RecommendationTitle [string] $Impact [string] $RecommendationControl [string] $PotentialBenefit [string] $LearnMoreLink [string] $LongDescription [string] $Guid [string] $Category [string] $Source [string] $WAFPillar [string] $PlatformIssueTrackingId [string] $RetirementTrackingId [string] $SupportRequestNumber [string] $Notes [string] $CheckName } Class AnalysisPlanningObj { [string] $Category [string] $ResourceType = 'N/A' [string] $NumberOfResources = 'n/a' [string] $ImpactedResources = 'n/a' [string] $HasRecommendationsInAPRLAdvisor = 'Yes' [string] $AssessmentStatus = 'Pending' } Class OutagesObj { [string] $OutageMSG [string] $TrackingID [string] $EventType [string] $EventSource [string] $Status [string] $Title [string] $Level [string] $EventLevel [string] $StartTime [string] $MitigationTime [string] $ImpactedService [string] $WhatHappened [string] $WhatWentWrongAndWhy [string] $HowDidWeRespond [string] $HowAreWeMakingIncidentsLessLikely [string] $HowCanCustomersMakeIncidentsLessImpactful } Class WorkLoadInvObj { [string] $subscriptionId [string] $resourceGroup [string] $type [string] $location [string] $name [string] $id [string] $tenantId [string] $kind [string] $managedBy [string] $sku [string] $plan [string] $zones } # function validate if the required modules are installed function Test-Requirement { # Install required modules Write-Host 'Validating ' -NoNewline Write-Host 'ImportExcel' -ForegroundColor Cyan -NoNewline Write-Host ' Module..' $ImportExcel = Get-Module -Name ImportExcel -ListAvailable -ErrorAction silentlycontinue if ($null -eq $ImportExcel) { Write-Host 'Installing ImportExcel Module' -ForegroundColor Yellow Install-Module -Name ImportExcel -Force -SkipPublisherCheck } <# Write-Host 'Validating ' -NoNewline Write-Host 'Powershell-YAML' -ForegroundColor Cyan -NoNewline Write-Host ' Module..' $AzModules = Get-Module -Name powershell-yaml -ListAvailable -ErrorAction silentlycontinue if ($null -eq $AzModules) { Write-Host 'Installing Az Modules' -ForegroundColor Yellow Install-Module -Name powershell-yaml -SkipPublisherCheck -InformationAction SilentlyContinue } #> <# Write-Host 'Validating ' -NoNewline Write-Host 'Git' -ForegroundColor Cyan -NoNewline Write-Host ' Installation..' $GitVersion = git --version if ($null -eq $GitVersion) { Write-Host 'Missing Git' -ForegroundColor Red Exit } #> } # function to read the JSON file function Read-JSONFile { Param ( [Parameter(Mandatory = $true)] [string]$JSONFile ) Write-Debug ((get-date -Format 'yyyy-MM-dd HH:mm:ss') + ' - Starting to Read the JSON file') $JSONResources = Get-Item -Path $JSONFile $JSONResources = $JSONResources.FullName $JSONContent = Get-Content -Path $JSONResources | ConvertFrom-Json Write-Debug ((get-date -Format 'yyyy-MM-dd HH:mm:ss') + ' - JSON File Created with version: ' + $JSONContent.ScriptDetails.Version) Write-Debug ((get-date -Format 'yyyy-MM-dd HH:mm:ss') + ' - Raw ImpactedResources found: ' + $JSONContent.ImpactedResources.Count) Write-Debug ((get-date -Format 'yyyy-MM-dd HH:mm:ss') + ' - Raw PlatformIssues found: ' + $JSONContent.Outages.Count) Write-Debug ((get-date -Format 'yyyy-MM-dd HH:mm:ss') + ' - Raw SupportTickets found: ' + $JSONContent.SupportTickets.Count) Write-Debug ((get-date -Format 'yyyy-MM-dd HH:mm:ss') + ' - Raw Workload Inventory found: ' + $JSONContent.InScopeResources.Count) return $JSONContent } function Save-WARAExcelFile { Param( [string]$ExpertAnalysisFile ) $workingFolderPath = Get-Location $workingFolderPath = $workingFolderPath.Path $ExcelPkg = Open-ExcelPackage -Path $ExpertAnalysisFile $NewExpertAnalysisFile = ($workingFolderPath + '\Expert-Analysis-v1-' + (Get-Date -Format 'yyyy-MM-dd-HH-mm') + '.xlsx') Close-ExcelPackage -ExcelPackage $ExcelPkg -SaveAs $NewExpertAnalysisFile return $NewExpertAnalysisFile } # function responsible to import recommendations from the JSON file function Get-WARARecommendationList { Param( [Parameter(Mandatory = $true)] [string]$RecommendationsUrl ) Write-Debug ((get-date -Format 'yyyy-MM-dd HH:mm:ss') + ' - Processing Recommendations from JSON file.') # Get Recommendation Objects $RecommendationDataUri = $RecommendationsUrl $RecommendationObject = Invoke-RestMethod $RecommendationDataUri return $RecommendationObject } # function to process the standard WARA message in the column A function Get-WARAMessage { Param($Message) if ($Message -eq 'ImpactedResources_Type') { $WARAMessage = @" REQUIRED ACTIONS: ResourceType not available in APRL/Advisor. This Resource Type does not have recommendations in APRL or Advisor, then follow these steps: 1. Manually validate this resource and create your own resiliency and/or reliability-related recommendations, if applicable. 2. When creating a new recommendation, ensure the following fields are populated: Title, Impact, Potential Benefit, Description, ResourceType, and Learn More Link. 3. Duplicate this row if you need to create more than one recommendation for the same resource. 4. If the resource is not compliant with your recommendation, update this cell to "Reviewed". or 5. Delete this row if this resource is irrelevant or is already compliant with your recommendation. "@ } if ($Message -eq 'ImpactedResources_Unavailable') { $WARAMessage = @" REQUIRED ACTIONS: Recommendation does not have automated validation. This recommendation does not have automated validation. Since it cannot be validated automatically, follow these steps: 1. Review the Recommendation Title and Description to understand the recommendation. Use the "Read More" links for additional information. 2. Open the Azure Portal and locate the potentially impacted resource using its Resource Name or ResourceId. 3. Manually validate the resource. 4. If the resource is not compliant with the recommendation, update this cell to "Reviewed". or 5. Delete this row if the resource is irrelevant or already compliant with the recommendation. "@ } if ($Message -eq 'ImpactedResources_ServiceRetirement') { $WARAMessage = @" REQUIRED ACTIONS: Azure Service Health - Service Retirements. This Service Health Retirement Notification was automatically imported to your workload review according to the Subscriptions being assessed and Services being used. 1. Review and if necessary summarize the cell "Recommendation Title." 2. Retrieve the Resource Name and Resource ID of the impacted resources from the Azure Portal. 3. If more resources are associated with the same Service Retirement Notification, duplicate this row and update it with the Resource Name and Resource ID for all applicable resources. 4. Once completed, update this cell to "Reviewed". or 5. If this Service Retirement Notification is not relevant, delete this row. "@ } if ($Message -eq 'ImpactedResources_Architecture') { $WARAMessage = @" REQUIRED ACTIONS: Architectural and Reliability Design Patterns Recommendations This row is just an example. Modify it and create your own personalized recommendations for architecture and/or reliability design patterns. 1. Create new custom recommendations based on the Discovery Workshop Questionnaire. 2. Associate it with the SubscriptionId and "Microsoft.Subscription/Subscriptions" as the associated "resourceType", or any resource and ResourceType that you consider applicable. 3. Once completed, update this cell to "Reviewed". or 4. Delete this row if no personalized/custom recommendations will be provided. "@ } if ($Message -eq 'ImpactedResources_WAF') { $WARAMessage = @" REQUIRED ACTIONS: Well-Architected Framework. This is a generic recommendation from the Well-Architected Framework - Reliability pillar. It cannot be validated automatically and need information from the Discovery Workshop session. 1. Review and update this row based on the Discovery Workshop Questionnaire. 2. If this recommendation is applicable, update the "Impact" column according to the importance for the Workload and then update this cell to "Reviewed". or 3. If this recommendation is not relevant, delete this row. "@ } if ($Message -eq 'PlatformIssues_Standard') { $WARAMessage = @" REQUIRED ACTIONS: Review Platform Issue and create recommendations. This Platform Issue may have affected the workload, since it cannot be validated automatically, follow these steps: 1. Review all information about the Platform Issue. 2. Discuss with the Account Team/Workload Owner if this Platform Issue affected the workload and how, you can also check if there are associated Support Requests. 3. If this issue affected the workload, create recommendations in the "3.ImpactedResources" worksheet based on the "How can customers make incidents like this less impactful" field. You can associate the recommendation(s) with the Workload or individual resources. 4. If the recommendation(s) already exists, simply add the TrackingID to the respective column of the associated Recommendation(s). or 5. Delete this row if the workload was not affected by this Platform Issue. "@ } if ($Message -eq 'SupportTickets_Standard') { $WARAMessage = @" REQUIRED ACTIONS: Review Customer Support Requests and create recommendations. This Customer Support Request is associated with the Subscription of the workload, since it cannot be validated automatically, follow these steps: 1. Review all information about the Support Request in the Azure Portal. 2. Discuss with Workload Owner if this Support Request affected the workload and how. 3. If this Support Request was relevant for the workload, create recommendations in the "3.ImpactedResources" worksheet based on it was resolved. You can associate the recommendation(s) with the Workload or individual resources. The goal is to make sure other resources or services follow the Microsoft recommendation/solution and prevent this incident from happening again. 4. If the recommendation(s) already exists, simply add the TicketID to the respective column of the associated Recommendation(s). or 5. Delete this row if the workload was not affected, associated with this Support Request. "@ } return $WARAMessage } <############################## Impacted Resources #########################################> function Initialize-WARAImpactedResources { Param( [Parameter(mandatory = $true)] $ImpactedResources, [Parameter(mandatory = $true)] $Advisory, [Parameter(mandatory = $true)] $Retirements, [Parameter(mandatory = $true)] $ScriptDetails, [Parameter(mandatory = $true)] $RecommendationsUrl ) Write-Debug ((get-date -Format 'yyyy-MM-dd HH:mm:ss') + ' - Getting All Recommendations') $RecommendationObject = Get-WARARecommendationList -RecommendationsUrl $RecommendationsUrl Write-Debug ((get-date -Format 'yyyy-MM-dd HH:mm:ss') + ' - Getting Recommendations from the standard Resources') $ResourceRecommendations = $RecommendationObject | Where-Object {[string]::IsNullOrEmpty($_.tags) -and $_.description -notlike 'RE:*'} Write-Debug ((get-date -Format 'yyyy-MM-dd HH:mm:ss') + ' - Getting Recommendations from the standard Azure-WAF') $WAFRecommendations = $RecommendationObject | Where-Object { $_.description -like 'RE:*'} if ($ScriptDetails.SAP -eq 'True') { Write-Debug ((get-date -Format 'yyyy-MM-dd HH:mm:ss') + ' - Getting Recommendations from SAP') $ResourceRecommendations += $RecommendationObject | Where-Object {$_.tags -like 'SAP'} } if ($ScriptDetails.AVD -eq 'True') { Write-Debug ((get-date -Format 'yyyy-MM-dd HH:mm:ss') + ' - Getting Recommendations from AVD') $ResourceRecommendations += $RecommendationObject | Where-Object {$_.tags -like 'AVD'} } if ($ScriptDetails.AVS -eq 'True') { Write-Debug ((get-date -Format 'yyyy-MM-dd HH:mm:ss') + ' - Getting Recommendations from AVS') $ResourceRecommendations += $RecommendationObject | Where-Object {$_.tags -like 'AVS'} } if ($ScriptDetails.HPC -eq 'True') { Write-Debug ((get-date -Format 'yyyy-MM-dd HH:mm:ss') + ' - Getting Recommendations from HPC') $ResourceRecommendations += $RecommendationObject | Where-Object {$_.tags -like 'HPC'} } if ($ScriptDetails.AI -eq 'True') { Write-Debug ((get-date -Format 'yyyy-MM-dd HH:mm:ss') + ' - Getting Recommendations from AI') $ResourceRecommendations += $RecommendationObject | Where-Object {$_.tags -like 'AI'} } Write-Debug ((get-date -Format 'yyyy-MM-dd HH:mm:ss') + ' - Overall Recommendations found: ' + [string]$ResourceRecommendations.Count) # Filtering the recommendations to get only the active ones and the ones that are not already in the advisories list Write-Debug ((get-date -Format 'yyyy-MM-dd HH:mm:ss') + ' - Filtering Active Recommendations and Recommendations not in Advisories') $RecommendationContent = $ResourceRecommendations | Where-Object {($_.recommendationMetadataState -eq 'Active' -and $_.recommendationTypeId -notin $JSONContent.Advisory.recommendationId) } Write-Debug ((get-date -Format 'yyyy-MM-dd HH:mm:ss') + ' - Recommendations After Filtering: ' + [string]$RecommendationContent.Count) $tmp = @() # First loop through the recommendations to get the impacted resources foreach ($Recom in $RecommendationContent) { # Getting the impacted resources for the recommendation and validating if the recommendation is a Custom Recommendation $Resources = $ImpactedResources | Where-Object {($_.recommendationId -eq $Recom.aprlGuid) -and ($_.checkName -eq $Recom.checkName) } # If the recommendation is not a Custom Recommendation, we need to validate if the resources are not already in the tmp array (from a previous loop of a Custom Recommendation) if ([string]::IsNullOrEmpty($Resources) -and $Recom.aprlGuid -notin $tmp.Guid) { $Resources = $ImpactedResources| Where-Object {($_.recommendationId -eq $Recom.aprlGuid) } } foreach ($Resource in $Resources) { $ValidationMSG = if($Resource.validationAction -eq 'IMPORTANT - Query under development - Validate Resources manually') { Get-WARAMessage -Message 'ImpactedResources_Unavailable' } else { if ($Resource.validationAction -eq 'IMPORTANT - Resource Type is not available in either APRL or Advisor - Validate Resources manually if Applicable, if not Delete this line') { Get-WARAMessage -Message 'ImpactedResources_Type' } else { 'Pending' } } $ResObj = [ImpactedResourceObj]::new() $ResObj.ValidationMSG = $ValidationMSG $ResObj.ValidationCategory = 'Resource' $ResObj.ResourceType = $Resource.type $ResObj.SubscriptionId = $Resource.subscriptionId $ResObj.ResourceGroup = $Resource.resourceGroup $ResObj.Location = $Resource.location $ResObj.Name = $Resource.name $ResObj.Id = $Resource.id $ResObj.Custom1 = $Resource.param1 $ResObj.Custom2 = $Resource.param2 $ResObj.Custom3 = $Resource.param3 $ResObj.Custom4 = $Resource.param4 $ResObj.Custom5 = $Resource.param5 $ResObj.RecommendationTitle = $Recom.description $ResObj.Impact = $Recom.recommendationImpact $ResObj.RecommendationControl = ($Recom.recommendationControl -csplit '(?=[A-Z])' -ne '' -join ' ') $ResObj.PotentialBenefit = $Recom.potentialBenefits $ResObj.LearnMoreLink = ($Recom.learnMoreLink.url -join " `n") $ResObj.LongDescription = $Recom.longDescription $ResObj.Guid = $Recom.aprlGuid $ResObj.Category = 'Azure Service' $ResObj.Source = $Resource.selector $ResObj.WAFPillar = 'Reliability' $ResObj.CheckName = $Resource.checkName $tmp += $ResObj } } # Second loop through the advisories to get the impacted resources $ADVMessage = Get-WARAMessage -Message 'ImpactedResources_Unavailable' foreach ($adv in $Advisory) { if (![string]::IsNullOrEmpty($adv)) { $ADVobj = [impactedResourceObj]::new() $ADVobj.ValidationMSG = $ADVMessage $ADVobj.ValidationCategory = 'Resource' $ADVobj.ResourceType = $adv.type $ADVobj.SubscriptionId = $adv.subscriptionId $ADVobj.ResourceGroup = $adv.resourceGroup $ADVobj.Location = $adv.location $ADVobj.Name = $adv.name $ADVobj.Id = $adv.id $ADVobj.RecommendationTitle = $adv.description $ADVobj.Impact = $adv.impact $ADVobj.RecommendationControl = ($adv.category -csplit '(?=[A-Z])' -ne '' -join ' ') $ADVobj.Guid = $adv.recommendationId $ADVobj.Source = 'ADVISOR' $ADVobj.WAFPillar = 'Reliability' $tmp += $ADVobj } } # Third loop through the retirements $ServiceRetirementMSG = Get-WARAMessage -Message 'ImpactedResources_ServiceRetirement' foreach ($Retirement in $Retirements) { if (![string]::IsNullOrEmpty($Retirement)) { $RetirementType = $RootTypes | Where-Object {$_.FriendlyName -eq $Retirement.ImpactedService} $RetirementType = if(![string]::IsNullOrEmpty($RetirementType)) { $RetirementType.ResourceType } else { $Retirement.ImpactedService } try { $HTML = New-Object -Com 'HTMLFile' $HTML.write([ref]$Retirement.Description) $RetirementDescriptionFull = $Html.body.innerText $SplitDescription = $RetirementDescriptionFull.split('Help and support').split('Required action') } catch { $SplitDescription = ' ', ' ' } $RetObj = [ImpactedResourceObj]::new() $RetObj.ValidationMSG = $ServiceRetirementMSG $RetObj.ValidationCategory = 'Retirements' $RetObj.ResourceType = $RetirementType $RetObj.SubscriptionId = $Retirement.Subscription $RetObj.ResourceGroup = 'RG name not needed' $RetObj.Location = 'Location not needed' $RetObj.Name = 'Get ResourceName from Azure Portal' $RetObj.Id = 'Get ResourceID from Azure Portal' $RetObj.RecommendationTitle = $Retirement.Title $RetObj.Impact = 'Medium' $RetObj.LongDescription = [string]$SplitDescription[0] $RetObj.Guid = 'GUID not needed' $RetObj.Source = 'Azure Service Health - Service Retirements' $RetObj.WAFPillar = 'Reliability' $RetObj.RetirementTrackingId = $Retirement.TrackingId $tmp += $RetObj } } # Fourth loop through the WAF recommendations $WAFMSG = Get-WARAMessage -Message 'ImpactedResources_WAF' foreach ($waf in $WAFRecommendations) { if (![string]::IsNullOrEmpty($waf)) { $WAFObj = [ImpactedResourceObj]::new() $WAFObj.ValidationMSG = $WAFMSG $WAFObj.ValidationCategory = 'WAF' $WAFObj.ResourceType = $waf.recommendationResourceType $WAFObj.Name = 'Entire Workload' $WAFObj.RecommendationTitle = $waf.description $WAFObj.Impact = $waf.recommendationImpact $WAFObj.RecommendationControl = ($waf.recommendationControl -csplit '(?=[A-Z])' -ne '' -join ' ') $WAFObj.PotentialBenefit = $waf.potentialBenefits $WAFObj.LearnMoreLink = ($Recom.learnMoreLink.url -join " `n") $WAFObj.LongDescription = $waf.longDescription $WAFObj.Guid = $waf.aprlGuid $WAFObj.Category = 'Well Architected' $tmp += $WAFObj } } # Standard Architecture and Reliability Design Patterns Recommendations $ArchtectureMSG = Get-WARAMessage -Message 'ImpactedResources_Architecture' $ARCHObj = [ImpactedResourceObj]::new() $ARCHObj.ValidationMSG = $ArchtectureMSG $ARCHObj.ValidationCategory = 'Architectural' $ARCHObj.ResourceType = 'Microsoft.Subscription/Subscriptions' $tmp += $ARCHObj $ImpactedResourcesFormatted = foreach ($line in $tmp) { [PSCustomObject]@{ 'REQUIRED ACTIONS / REVIEW STATUS' = $line.ValidationMSG 'ValidationCategory' = $line.ValidationCategory 'Resource Type' = $line.ResourceType 'subscriptionId' = $line.SubscriptionId 'resourceGroup' = $line.ResourceGroup 'location' = $line.Location 'name' = $line.Name 'id' = $line.Id 'custom1' = $line.Custom1 'custom2' = $line.Custom2 'custom3' = $line.Custom3 'custom4' = $line.Custom4 'custom5' = $line.Custom5 'Recommendation Title' = $line.RecommendationTitle 'Impact' = $line.Impact 'Recommendation Control' = $line.RecommendationControl 'Potential Benefit' = $line.PotentialBenefit 'Learn More Link' = $line.LearnMoreLink 'Long Description' = $line.LongDescription 'Guid' = $line.Guid 'Category' = $line.Category 'Source' = $line.Source 'WAF Pillar' = $line.WAFPillar 'Platform Issue TrackingId' = $line.PlatformIssueTrackingId 'Retirement TrackingId' = $line.RetirementTrackingId 'Support Request Number' = $line.SupportRequestNumber 'Notes' = $line.Notes 'checkName' = $line.CheckName } } # Returns the array with all the recommendations already formatted to be exported to Excel return $ImpactedResourcesFormatted } function Export-WARAImpactedResources { Param($ImpactedResourcesFormatted) $Style = @() $Style += New-ExcelStyle -HorizontalAlignment Left -WrapText -Range A:A $Style += New-ExcelStyle -HorizontalAlignment Center -Range A12:A12 $Style += New-ExcelStyle -HorizontalAlignment Center -Range B:F $Style += New-ExcelStyle -HorizontalAlignment Left -Range G:L $Style += New-ExcelStyle -HorizontalAlignment Center -Range G12:L12 $Style += New-ExcelStyle -HorizontalAlignment Center -WrapText -Range M:P $Style += New-ExcelStyle -HorizontalAlignment Center -WrapText -Range R:R $Style += New-ExcelStyle -HorizontalAlignment Center -Range S:Z $Style += New-ExcelStyle -VerticalAlignment Center -Range A:Z $ImpactedResourcesSheet = New-Object System.Collections.Generic.List[System.Object] $ImpactedResourcesSheet.Add('REQUIRED ACTIONS / REVIEW STATUS') $ImpactedResourcesSheet.Add('ValidationCategory') $ImpactedResourcesSheet.Add('Resource Type') $ImpactedResourcesSheet.Add('subscriptionId') $ImpactedResourcesSheet.Add('resourceGroup') $ImpactedResourcesSheet.Add('location') $ImpactedResourcesSheet.Add('name') $ImpactedResourcesSheet.Add('id') $ImpactedResourcesSheet.Add('custom1') $ImpactedResourcesSheet.Add('custom2') $ImpactedResourcesSheet.Add('custom3') $ImpactedResourcesSheet.Add('custom4') $ImpactedResourcesSheet.Add('custom5') $ImpactedResourcesSheet.Add('Recommendation Title') $ImpactedResourcesSheet.Add('Impact') $ImpactedResourcesSheet.Add('Recommendation Control') $ImpactedResourcesSheet.Add('Potential Benefit') $ImpactedResourcesSheet.Add('Learn More Link') $ImpactedResourcesSheet.Add('Long Description') $ImpactedResourcesSheet.Add('Guid') $ImpactedResourcesSheet.Add('Category') $ImpactedResourcesSheet.Add('Source') $ImpactedResourcesSheet.Add('WAF Pillar') $ImpactedResourcesSheet.Add('Platform Issue TrackingId') $ImpactedResourcesSheet.Add('Retirement TrackingId') $ImpactedResourcesSheet.Add('Support Request Number') $ImpactedResourcesSheet.Add('Notes') $ImpactedResourcesSheet.Add('checkName') Write-Debug ((get-date -Format 'yyyy-MM-dd HH:mm:ss') + ' - Exporting Impacted Resources to Excel') $ImpactedResourcesFormatted | ForEach-Object { [PSCustomObject]$_ } | Select-Object $ImpactedResourcesSheet | Export-Excel -Path $NewExpertAnalysisFile -WorksheetName $ImpactedResourcesSheetRef -TableName 'impactedresources' -TableStyle $TableStyle -Style $Style -StartRow 12 } <############################## Analysis Planning #########################################> function Initialize-WARAAnalysisPlanning { Param( $InScopeResources ) Write-Debug ((get-date -Format 'yyyy-MM-dd HH:mm:ss') + ' - Grouping InScope Resources by Resource Type') $ResourceTypes = $InScopeResources | Group-Object -Property type # Formula has to be extracted from the Excel file to be in this specific standard used by Excel, otherwise it will not work $ImpactedResourcesFormula = @" =IF(COUNTIF($ImpactedResourcesSheetRef!C:C, TableTypes8[[#This Row],[Resource Type]])=0, 0, COUNTA(_xlfn.UNIQUE(_xlfn._xlws.FILTER($ImpactedResourcesSheetRef!H:H, ($ImpactedResourcesSheetRef!C:C=TableTypes8[[#This Row],[Resource Type]]) * ($ImpactedResourcesSheetRef!H:H<>"Get ResourceID from Azure Portal"))))) "@ $ReviewedFormula = @" =IF(OR(AND(TableTypes8[[#This Row],[Category]]="Support Requests", COUNTIFS($SupportRequestsSheetRef!A:A, "<>Reviewed")=0),AND(TableTypes8[[#This Row],[Category]]="Platform Issues", COUNTIFS($PlatformIssuesSheetRef!A:A, "<>Reviewed")=0),AND(TableTypes8[[#This Row],[Category]]="Impacted Resources", COUNTIFS($ImpactedResourcesSheetRef!A:A, "<>Reviewed", $ImpactedResourcesSheetRef!C:C, TableTypes8[[#This Row],[Resource Type]], $ImpactedResourcesSheetRef!O:O, "<>Low")=0)), "Reviewed", "Pending") "@ $tmp = @() $Counter = 11 foreach ($ResourceType in $ResourceTypes) { $RootType = "" $RootType = $RootTypes | Where-Object {$_.ResourceType -eq $ResourceType.Name} $APRLOrAdv = if($RootType.WARAinScope -eq 'yes' -and $RootType.InAprlAndOrAdvisor -eq 'yes') { 'Yes' } else { 'No' } $ResTypeObj = [AnalysisPlanningObj]::new() $ResTypeObj.Category = 'Impacted Resources' $ResTypeObj.ResourceType = $ResourceType.Name $ResTypeObj.NumberOfResources = $ResourceType.'Count' $ResTypeObj.ImpactedResources = $ImpactedResourcesFormula $ResTypeObj.HasRecommendationsInAPRLAdvisor = $APRLOrAdv $ResTypeObj.AssessmentStatus = $ReviewedFormula $Counter ++ $tmp += $ResTypeObj } $SupObj = [AnalysisPlanningObj]::new() $SupObj.Category = 'Support Requests' $tmp += $SupObj $PlatObj = [AnalysisPlanningObj]::new() $PlatObj.Category = 'Platform Issues' $tmp += $PlatObj $AnalysisPlanningFormatted = foreach ($line in $tmp) { [PSCustomObject]@{ 'Category' = $line.Category 'Resource Type' = $line.ResourceType 'Number of Resources' = $line.NumberOfResources 'Impacted Resources' = $line.ImpactedResources 'Has Recommendations_x000a_in APRL/Advisor' = $line.HasRecommendationsInAPRLAdvisor 'Assessment Status' = $line.AssessmentStatus } } return $AnalysisPlanningFormatted } function Export-WARAAnalysisPlanning { Param($AnalysisPlanningFormatted) $Style = @() $Style += New-ExcelStyle -HorizontalAlignment Center -Range A:H $Style = @() $Style += New-ExcelStyle -HorizontalAlignment Center -Range A:H $AnalysesPlanning = New-Object System.Collections.Generic.List[System.Object] $AnalysesPlanning.Add('Category') $AnalysesPlanning.Add('Resource Type') $AnalysesPlanning.Add('Number of Resources') $AnalysesPlanning.Add('Impacted Resources') $AnalysesPlanning.Add('Has Recommendations_x000a_in APRL/Advisor') $AnalysesPlanning.Add('Assessment Owner') $AnalysesPlanning.Add('Assessment Status') $AnalysesPlanning.Add('Notes') Write-Debug ((get-date -Format 'yyyy-MM-dd HH:mm:ss') + ' - Exporting Analysis Planning to Excel') $AnalysisPlanningFormatted | ForEach-Object { [PSCustomObject]$_ } | Select-Object $AnalysesPlanning | Export-Excel -Path $NewExpertAnalysisFile -WorksheetName $AnalysisPlanningSheetRef -TableName 'TableTypes8' -TableStyle $TableStyle -Style $Style -StartRow 10 } <############################## Platform Issues #########################################> function Initialize-WARAPlatformIssues { Param( $PlatformIssues ) $OutagesMSG = Get-WARAMessage -Message 'PlatformIssues_Standard' Write-Debug ((get-date -Format 'yyyy-MM-dd HH:mm:ss') + ' - Formatting Platform Issues') # Just getting a sum of the total outages that will be included in the Excel file, in case there are no outages, a row will be added with empty values later $TotalOutages = ($PlatformIssues | Where-Object {$_.properties.description -like '*How can customers make incidents like this less impactful?*'}).count $tmp = @() foreach ($Outage in $PlatformIssues) { if ($Outage.properties.description -like '*How can customers make incidents like this less impactful?*') { try { $HTML = New-Object -Com 'HTMLFile' $HTML.write([ref]$Outage.properties.description) $OutageDescription = $Html.body.innerText $SplitDescription = $OutageDescription.split('How can we make our incident communications more useful?').split('How can customers make incidents like this less impactful?').split('How are we making incidents like this less likely or less impactful?').split('How did we respond?').split('What went wrong and why?').split('What happened?') $whathap = ($SplitDescription[1]).Split([Environment]::NewLine)[1] $whatwent = ($SplitDescription[2]).Split([Environment]::NewLine)[1] $howdid = ($SplitDescription[3]).Split([Environment]::NewLine)[1] $howarewe = ($SplitDescription[4]).Split([Environment]::NewLine)[1] $howcan = ($SplitDescription[5]).Split([Environment]::NewLine)[1] } catch { $whathap = "" $whatwent = "" $howdid = "" $howarewe = "" $howcan = "" } $ImpactedSvc = if ($Outage.properties.impact.impactedService.count -gt 1) { $Outage.properties.impact.impactedService | ForEach-Object { $_ + ' ,' } }else { $Outage.properties.impact.impactedService} $ImpactedSvc = [string]$ImpactedSvc $ImpactedSvc = if ($ImpactedSvc -like '* ,*') { $ImpactedSvc -replace ".$" }else { $ImpactedSvc } $OutageObj = [OutagesObj]::new() $OutageObj.OutageMSG = $OutagesMSG $OutageObj.TrackingID = $Outage.name $OutageObj.EventType = $Outage.properties.eventType $OutageObj.EventSource = $Outage.properties.eventSource $OutageObj.Status = $Outage.properties.status $OutageObj.Title = $Outage.properties.title $OutageObj.Level = $Outage.properties.level $OutageObj.EventLevel = $Outage.properties.eventLevel $OutageObj.StartTime = $Outage.properties.impactStartTime $OutageObj.MitigationTime = $Outage.properties.impactMitigationTime $OutageObj.ImpactedService = $ImpactedSvc $OutageObj.WhatHappened = $whathap $OutageObj.WhatWentWrongAndWhy = $whatwent $OutageObj.HowDidWeRespond = $howdid $OutageObj.HowAreWeMakingIncidentsLessLikely = $howarewe $OutageObj.HowCanCustomersMakeIncidentsLessImpactful = $howcan $tmp += $OutageObj } } # If there are no outages, a row will be added with empty values if ($TotalOutages -eq 0) { $ZeroOutageObj = [OutagesObj]::new() $ZeroOutageObj.OutageMSG = $OutagesMSG $tmp += $ZeroOutageObj } $PlatformIssuesFormatted = foreach ($line in $tmp) { [PSCustomObject]@{ 'REQUIRED ACTIONS / REVIEW STATUS' = $line.OutageMSG 'Tracking ID' = $line.TrackingID 'Event Type' = $line.EventType 'Event Source' = $line.EventSource 'Status' = $line.Status 'Title' = $line.Title 'Level' = $line.Level 'Event Level' = $line.EventLevel 'Start Time' = $line.StartTime 'Mitigation Time' = $line.MitigationTime 'Impacted Service' = $line.ImpactedService 'What happened' = $line.WhatHappened 'What went wrong and why' = $line.WhatWentWrongAndWhy 'How did we respond' = $line.HowDidWeRespond 'How are we making incidents like this less likely or less impactful' = $line.HowAreWeMakingIncidentsLessLikely 'How can customers make incidents like this less impactful' = $line.HowCanCustomersMakeIncidentsLessImpactful } $obj } return $PlatformIssuesFormatted } function Export-WARAPlatformIssues { Param($PlatformIssuesFormatted) $Style = @() $Style += New-ExcelStyle -HorizontalAlignment Left -WrapText -Range A:A $Style += New-ExcelStyle -HorizontalAlignment Center -Range A12:A12 $Style += New-ExcelStyle -HorizontalAlignment Center -Range B:K $Style += New-ExcelStyle -HorizontalAlignment Center -WrapText -Range L:P $Style += New-ExcelStyle -VerticalAlignment Center -Range B:P $PlatformIssuesSheet = New-Object System.Collections.Generic.List[System.Object] $PlatformIssuesSheet.Add('REQUIRED ACTIONS / REVIEW STATUS') $PlatformIssuesSheet.Add('Tracking ID') $PlatformIssuesSheet.Add('Event Type') $PlatformIssuesSheet.Add('Event Source') $PlatformIssuesSheet.Add('Status') $PlatformIssuesSheet.Add('Title') $PlatformIssuesSheet.Add('Level') $PlatformIssuesSheet.Add('Event Level') $PlatformIssuesSheet.Add('Start Time') $PlatformIssuesSheet.Add('Mitigation Time') $PlatformIssuesSheet.Add('Impacted Service') $PlatformIssuesSheet.Add('What happened') $PlatformIssuesSheet.Add('What went wrong and why') $PlatformIssuesSheet.Add('How did we respond') $PlatformIssuesSheet.Add('How are we making incidents like this less likely or less impactful') $PlatformIssuesSheet.Add('How can customers make incidents like this less impactful') Write-Debug ((get-date -Format 'yyyy-MM-dd HH:mm:ss') + ' - Exporting Platform Issues to Excel') $PlatformIssuesFormatted | ForEach-Object { [PSCustomObject]$_ } | Select-Object $PlatformIssuesSheet | Export-Excel -Path $NewExpertAnalysisFile -WorksheetName $PlatformIssuesSheetRef -TableName 'platformIssues' -TableStyle $TableStyle -Style $Style -StartRow 12 } <############################## Support Requests #########################################> function Initialize-WARASupportTicket { Param( $SupportTickets ) $SupportTicketsMSG = Get-WARAMessage -Message 'SupportTickets_Standard' Write-Debug ((get-date -Format 'yyyy-MM-dd HH:mm:ss') + ' - Formatting Support Tickets') # Just getting a sum of the total support tickets that will be included in the Excel file, in case there are no support tickets, a row will be added with empty values later $TotalSupportTickets = ($SupportTickets | Where-Object {![string]::IsNullOrEmpty($_.title)}).count $tmp = @() foreach ($Ticket in $SupportTickets) { if (![string]::IsNullOrEmpty($Ticket.title)) { $obj = @{ 'REQUIRED ACTIONS / REVIEW STATUS' = $SupportTicketsMSG; 'Ticket ID' = $Ticket.'Ticket ID'; 'Severity' = $Ticket.Severity; 'Status' = $Ticket.Status; 'Support Plan Type' = $Ticket.'Support Plan Type'; 'Creation Date' = $Ticket.'Creation Date'; 'Modified Date' = $Ticket.'Modified Date'; 'Title' = $Ticket.Title; 'Related Resource' = $Ticket.'Related Resource' } $tmp += $obj } } # If there are no support tickets, a row will be added with empty values if ($TotalSupportTickets -eq 0) { $obj = @{ 'REQUIRED ACTIONS / REVIEW STATUS' = $SupportTicketsMSG; 'Ticket ID' = ''; 'Severity' = ''; 'Status' = ''; 'Support Plan Type' = ''; 'Creation Date' = ''; 'Modified Date' = ''; 'Title' = ''; 'Related Resource' = '' } $tmp += $obj } return $tmp } function Export-WARASupportTicket { Param($SupportTicketsFormatted) $Style = @() $Style += New-ExcelStyle -HorizontalAlignment Left -WrapText -Range A:A $Style += New-ExcelStyle -HorizontalAlignment Center -Range A12:A12 $Style += New-ExcelStyle -HorizontalAlignment Center -Range B:I $Style += New-ExcelStyle -VerticalAlignment Center -Range B:I $SupportTicketsSheet = New-Object System.Collections.Generic.List[System.Object] $SupportTicketsSheet.Add('REQUIRED ACTIONS / REVIEW STATUS') $SupportTicketsSheet.Add('Ticket ID') $SupportTicketsSheet.Add('Severity') $SupportTicketsSheet.Add('Status') $SupportTicketsSheet.Add('Support Plan Type') $SupportTicketsSheet.Add('Creation Date') $SupportTicketsSheet.Add('Modified Date') $SupportTicketsSheet.Add('Title') $SupportTicketsSheet.Add('Related Resource') Write-Debug ((get-date -Format 'yyyy-MM-dd HH:mm:ss') + ' - Exporting Support Tickets to Excel') $SupportTicketsFormatted | ForEach-Object { [PSCustomObject]$_ } | Select-Object $SupportTicketsSheet | Export-Excel -Path $NewExpertAnalysisFile -WorksheetName $SupportRequestsSheetRef -TableName 'supportRequests' -TableStyle $TableStyle -Style $Style -StartRow 12 -NoNumberConversion "Ticket ID" } <############################## Workload Inventory #########################################> function Initialize-WARAWorkloadInventory { Param( $InScopeResources, $TenantID ) # Just getting a sum of the total in scope resources that will be included in the Excel file, in case there are no in scope resources, a row will be added with empty values later $TotalInScope = ($InScopeResources | Where-Object {![string]::IsNullOrEmpty($_.id)}).count Write-Debug ((get-date -Format 'yyyy-MM-dd HH:mm:ss') + ' - Formatting Workload Inventory') $tmp = @() foreach ($resource in $InScopeResources) { if (![string]::IsNullOrEmpty($resource.id)) { $ResourceObj = [WorkLoadInvObj]::new() $ResourceObj.subscriptionId = $resource.subscriptionId $ResourceObj.resourceGroup = $resource.resourceGroup $ResourceObj.type = $resource.type $ResourceObj.location = $resource.location $ResourceObj.name = $resource.name $ResourceObj.id = $resource.id $ResourceObj.tenantId = $TenantID $ResourceObj.kind = $resource.kind $ResourceObj.managedBy = $resource.managedBy $ResourceObj.sku = [string]$resource.sku $ResourceObj.plan = $resource.plan $ResourceObj.zones = [string]$resource.zones $tmp += $ResourceObj } } if ($TotalInScope -eq 0) { $ResourceObj = [WorkLoadInvObj]::new() $tmp += $ResourceObj } return $tmp } function Export-WARAWorkloadInventory { Param($WorkloadInventoryFormatted) $Style = @() $Style += New-ExcelStyle -HorizontalAlignment Center $InScopeSheet = New-Object System.Collections.Generic.List[System.Object] $InScopeSheet.Add('id') $InScopeSheet.Add('name') $InScopeSheet.Add('type') $InScopeSheet.Add('tenantId') $InScopeSheet.Add('kind') $InScopeSheet.Add('location') $InScopeSheet.Add('resourceGroup') $InScopeSheet.Add('subscriptionId') $InScopeSheet.Add('managedBy') $InScopeSheet.Add('sku') $InScopeSheet.Add('plan') $InScopeSheet.Add('zones') Write-Debug ((get-date -Format 'yyyy-MM-dd HH:mm:ss') + ' - Exporting Workload Inventory to Excel') $WorkloadInventoryFormatted | ForEach-Object { [PSCustomObject]$_ } | Select-Object $InScopeSheet | Export-Excel -Path $NewExpertAnalysisFile -WorksheetName $WorkloadInventorySheetRef -TableName 'InScopeResources' -TableStyle $TableStyle -Style $Style -StartRow 12 } <############################## Extra Configurations #########################################> function Set-ExpertAnalysisFile { [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [string]$NewExpertAnalysisFile ) Write-Debug ((get-date -Format 'yyyy-MM-dd HH:mm:ss') + ' - Openning Excel File for Last Customization') $Excel = Open-ExcelPackage -path $NewExpertAnalysisFile $sheet = $Excel.Workbook.Worksheets[$ImpactedResourcesSheetRef] Write-Debug ((get-date -Format 'yyyy-MM-dd HH:mm:ss') + ' - Adding Conditional Formatting to Impacted Resources Sheet') Add-ConditionalFormatting -WorkSheet $sheet -RuleType ContainsText -ConditionValue "REQUIRED ACTIONS: Azure Service Health - Service Retirements" -Address A:A -BackgroundColor ([System.Drawing.Color]::fromArgb(0xd87406)) Add-ConditionalFormatting -WorkSheet $sheet -RuleType ContainsText -ConditionValue "REQUIRED ACTIONS: Azure Service Health - Platform Issues" -Address A:A -BackgroundColor ([System.Drawing.Color]::fromArgb(0xee9432)) Add-ConditionalFormatting -WorkSheet $sheet -RuleType ContainsText -ConditionValue "REQUIRED ACTIONS: Well-Architected Framework" -Address A:A -BackgroundColor ([System.Drawing.Color]::fromArgb(0xfbe757)) Add-ConditionalFormatting -WorkSheet $sheet -RuleType ContainsText -ConditionValue "REQUIRED ACTIONS: ResourceType not available in APRL/Advisor" -Address A:A -BackgroundColor ([System.Drawing.Color]::fromArgb(0xfa7a06)) Add-ConditionalFormatting -WorkSheet $sheet -RuleType ContainsText -ConditionValue "REQUIRED ACTIONS: Recommendation does not have automated validation." -Address A:A -BackgroundColor ([System.Drawing.Color]::fromArgb(0xffa500)) Add-ConditionalFormatting -WorkSheet $sheet -RuleType ContainsText -ConditionValue "REQUIRED ACTIONS: Architectural and Reliability Design Patterns Recommendations" -Address A:A -BackgroundColor ([System.Drawing.Color]::fromArgb(0x92d050)) $sheet = $Excel.Workbook.Worksheets[$PlatformIssuesSheetRef] Write-Debug ((get-date -Format 'yyyy-MM-dd HH:mm:ss') + ' - Adding Conditional Formatting to Platform Issues Sheet') Add-ConditionalFormatting -WorkSheet $sheet -RuleType ContainsText -ConditionValue "REQUIRED ACTIONS: Review Platform Issue and create recommendations" -Address A:A -BackgroundColor ([System.Drawing.Color]::fromArgb(0xee9432)) $sheet = $Excel.Workbook.Worksheets[$SupportRequestsSheetRef] Write-Debug ((get-date -Format 'yyyy-MM-dd HH:mm:ss') + ' - Adding Conditional Formatting to Support Requests Sheet') Add-ConditionalFormatting -WorkSheet $sheet -RuleType ContainsText -ConditionValue "REQUIRED ACTIONS: Review Customer Support Requests and create recommendations" -Address A:A -BackgroundColor ([System.Drawing.Color]::fromArgb(0xFA7A06)) Write-Debug ((get-date -Format 'yyyy-MM-dd HH:mm:ss') + ' - Closing Excel File') Close-ExcelPackage -ExcelPackage $Excel -Calculate } # Start the stopwatch $Runtime = [System.Diagnostics.Stopwatch]::StartNew() # Excel Sheet Reference $ImpactedResourcesSheetRef = '4.ImpactedResourcesAnalysis' $AnalysisPlanningSheetRef = '3.AnalysisPlanning' $PlatformIssuesSheetRef = '5.PlatformIssuesAnalysis' $SupportRequestsSheetRef = '6.SupportRequestsAnalysis' $WorkloadInventorySheetRef = '2.WorkloadInventory' Write-Debug (' ---------------------------------- STARTING DATA ANALYZER SCRIPT --------------------------------------- ') #Call the functions $Version = '2.2.0' Write-Host 'Version: ' -NoNewline Write-Host $Version -ForegroundColor DarkBlue Write-Host "Starting the " -NoNewline Write-Host "WARA Analyzer" -ForegroundColor Magenta Write-Debug ((get-date -Format 'yyyy-MM-dd HH:mm:ss') + ' - Invoking Function: Read-JSONFile') Test-Requirement Write-Debug ((get-date -Format 'yyyy-MM-dd HH:mm:ss') + ' - Invoking Function: Read-JSONFile') $JSONContent = Read-JSONFile -JSONFile $JSONFile Write-Debug ((get-date -Format 'yyyy-MM-dd HH:mm:ss') + ' - Importing Supported Types') # Importing the CSV files to get the supported types and the friendly names for the resource types in the Retirements $RootTypes = Invoke-RestMethod $RecommendationResourceTypesUri | ConvertFrom-Csv $RootTypes = $RootTypes | Where-Object {$_.InAprlAndOrAdvisor -eq 'yes'} Write-Host 'Analysing Excel File Template' $NewExpertAnalysisFile = Save-WARAExcelFile -ExpertAnalysisFile $ExpertAnalysisFile Write-Debug ((get-date -Format 'yyyy-MM-dd HH:mm:ss') + ' - Invoking Function: Initialize-WARAImpactedResources') # Creating the Array with the Impacted Resources to be added to the Excel file $ImpactedResources = Initialize-WARAImpactedResources -ImpactedResources $JSONContent.ImpactedResources -Advisory $JSONContent.Advisory -Retirements $JSONContent.Retirements -ScriptDetails $JSONContent.ScriptDetails -RecommendationsUrl $RecommendationsUrl Write-Host $ImpactedResourcesSheetRef -NoNewline -ForegroundColor Green Write-Host ': ' -NoNewline $ImpactResCount = $ImpactedResources | Measure-Object Write-Host ([string]$ImpactResCount.count) -NoNewline -ForegroundColor Cyan Write-Host ' (Lines to be added to the new Excel file)' Write-Debug ((get-date -Format 'yyyy-MM-dd HH:mm:ss') + ' - Invoking Function: Export-WARAImpactedResources') # Adding the Impacted Resources to the Excel file Export-WARAImpactedResources -ImpactedResourcesFormatted $ImpactedResources Write-Debug ((get-date -Format 'yyyy-MM-dd HH:mm:ss') + ' - Invoking Function: Initialize-WARAPlatformIssues') # Creating the Array with the Platform Issues to be added to the Excel file $PlatformIssues = Initialize-WARAPlatformIssues -PlatformIssues $JSONContent.Outages Write-Host $PlatformIssuesSheetRef -NoNewline -ForegroundColor Green Write-Host ': ' -NoNewline $PlatissuesCount = $PlatformIssues | Measure-Object Write-Host ([string]$PlatissuesCount.Count) -NoNewline -ForegroundColor Cyan Write-Host ' (Lines to be added to the new Excel file)' Write-Debug ((get-date -Format 'yyyy-MM-dd HH:mm:ss') + ' - Invoking Function: Export-WaraPlatformIssues') # Adding the Platform Issues to the Excel file Export-WARAPlatformIssues -PlatformIssuesFormatted $PlatformIssues Write-Debug ((get-date -Format 'yyyy-MM-dd HH:mm:ss') + ' - Invoking Function: Initialize-WARASupportTicket') # Creating the Array with the Support Tickets to be added to the Excel file $SupportTickets = Initialize-WARASupportTicket -SupportTickets $JSONContent.SupportTickets Write-Host $SupportRequestsSheetRef -NoNewline -ForegroundColor Green Write-Host ': ' -NoNewline $SuppTicketsCount = $SupportTickets | Measure-Object Write-Host ([string]$SuppTicketsCount.count) -NoNewline -ForegroundColor Cyan Write-Host ' (Lines to be added to the new Excel file)' Write-Debug ((get-date -Format 'yyyy-MM-dd HH:mm:ss') + ' - Invoking Function: Export-WaraSupportTicket') # Adding the Support Tickets to the Excel file Export-WARASupportTicket -SupportTicketsFormatted $SupportTickets Write-Debug ((get-date -Format 'yyyy-MM-dd HH:mm:ss') + ' - Invoking Function: Initialize-WARAAnalysisPlanning') # Creating the Array with the Analysis Planning to be added to the Excel file $AnalysisPlanning = Initialize-WARAAnalysisPlanning -InScopeResources $JSONContent.resourceInventory Write-Host $AnalysisPlanningSheetRef -NoNewline -ForegroundColor Green Write-Host ': ' -NoNewline $AnalysisPlanningCount = $AnalysisPlanning | Measure-Object Write-Host ([string]$AnalysisPlanningCount.count) -NoNewline -ForegroundColor Cyan Write-Host ' (Lines to be added to the new Excel file)' Write-Debug ((get-date -Format 'yyyy-MM-dd HH:mm:ss') + ' - Invoking Function: Export-WaraAnalysisPlanning') # Adding the Analysis Planning to the Excel file Export-WARAAnalysisPlanning -AnalysisPlanningFormatted $AnalysisPlanning Write-Debug ((get-date -Format 'yyyy-MM-dd HH:mm:ss') + ' - Invoking Function: Initialize-WARAWorkloadInventory') # Creating the Array with the Workload Inventory to be added to the Excel file $WorkloadInventory = Initialize-WARAWorkloadInventory -InScopeResources $JSONContent.resourceInventory -TenantID $JSONContent.ScriptDetails.TenantId Write-Host $WorkloadInventorySheetRef -NoNewline -ForegroundColor Green Write-Host ': ' -NoNewline $WorkloadInvCount = $WorkloadInventory | Measure-Object Write-Host ([string]$WorkloadInvCount.count) -NoNewline -ForegroundColor Cyan Write-Host ' (Lines to be added to the new Excel file)' # Adding the Workload Inventory to the Excel file Write-Debug ((get-date -Format 'yyyy-MM-dd HH:mm:ss') + ' - Invoking Function: Export-WaraWorkloadInventory') Export-WARAWorkloadInventory -WorkloadInventoryFormatted $WorkloadInventory Write-Host 'Overall Excel File' -NoNewline -ForegroundColor Green Write-Host ': ' -NoNewline Write-Host 'Extra Excel Customization' -ForegroundColor Cyan # Setting the Excel file with the extra configurations like the conditional formatting Write-Debug ((get-date -Format 'yyyy-MM-dd HH:mm:ss') + ' - Invoking Function: Set-ExpertAnalysisFile') Set-ExpertAnalysisFile -NewExpertAnalysisFile $NewExpertAnalysisFile $Runtime.Stop() $TotalTime = $Runtime.Elapsed.toString('hh\:mm\:ss') Write-Host '---------------------------------------------------------------------' Write-Host ('Execution Complete. Total Runtime was: ') -NoNewline Write-Host $TotalTime -NoNewline -ForegroundColor Cyan Write-Host (' Minutes') Write-Host 'Excel File: ' -NoNewline Write-Host $NewExpertAnalysisFile -ForegroundColor Blue Write-Host '---------------------------------------------------------------------' |