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 create the core WARA Action Plan Excel file.
 
.PARAMETER Debugging
Switch to enable debugging mode.
 
.PARAMETER Help
Switch to display help information.
 
.PARAMETER RepoUrl
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.
 
.EXAMPLE
.\2_wara_data_analyzer.ps1 -JSONFile 'C:\Temp\WARA_File_2024-04-01_10_01.json' -Debugging
 
.LINK
https://github.com/Azure/Azure-Proactive-Resiliency-Library-v2
#>


[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingWriteHost', '', Justification = 'False positive as Write-Host does not represent a security risk and this script will always run on host consoles')]
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSReviewUnusedParameter', '', Justification = 'False positive as parameters are not always required')]
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments','', Justification='Variable is reserved for future use')]
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidGlobalVars','', Justification='This will be fixed in refactor')]
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseSingularNouns','', Justification='This will be fixed in refactor')]
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseShouldProcessForStateChangingFunctions','', Justification='This will be fixed in refactor')]

Param(
  [switch]$Debugging,
  [switch]$Help,
  [string]$CustomRecommendationsYAMLPath,

  [ValidatePattern('^https:\/\/.+$')]
  [string]$RepoUrl = 'https://github.com/Azure/Azure-Proactive-Resiliency-Library-v2',

  [Parameter(mandatory = $true)]
  [string] $JSONFile)

# Checking the operating system running this script.
if (-not $IsWindows) {
  Write-Host 'This script only supports Windows operating systems currently. Please try to run with Windows operating systems.'
  Exit
}

if ($Debugging.IsPresent) { $DebugPreference = 'Continue' } else { $DebugPreference = 'silentlycontinue' }

$Script:FilterRecommendations = $true

$Script:Runtime = Measure-Command -Expression {

  function Get-HelpMessage {
    Write-Host ''
    Write-Host 'Parameters'
    Write-Host ''
    Write-Host " -JSONFile : Mandatory; WARA JSON file generated by '1_wara_collector.ps1' script. "
    Write-Host ' -Debugging : Switch; Writes Debugging information of the script during the execution. '
    Write-Host " -CustomRecommendationsYAMLPath : Optional; Path to a custom YAML file with recommendations. "
    Write-Host ' -RepoUrl : Optional; Specifies the git repository URL that contains APRL contents if you want to use non-standard APRL repository.'
    Write-Host ''
    Write-Host 'Examples: '
    Write-Host ' Run using JSON file'
    Write-Host " .\2_wara_analyzer.ps1 -JSONFile 'C:\Temp\WARA_File_2024-04-01_10_01.json'"
    Write-Host ''
    Write-Host ' Run using JSON file with Debugging details'
    Write-Host " .\2_wara_analyzer.ps1 -JSONFile 'C:\Temp\WARA_File_2024-04-01_10_01.json' -Debugging"
    Write-Host ''
    Write-Host ''
  }

  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 Get-RepoVersion {
    param(
      [Parameter(Mandatory = $true)]
      [string] $ClonePath
    )

    return Get-Content -Path "$ClonePath\tools\Version.json" -ErrorAction SilentlyContinue | ConvertFrom-Json
  }

  function Set-RecommendationControl {
    param (
        [string]$category
    )

    switch ($category) {
        'BusinessContinuity' { return 'Business Continuity' }
        'DisasterRecovery' { return 'Disaster Recovery' }
        'MonitoringAndAlerting' { return 'Monitoring And Alerting' }
        'ServiceUpgradeAndRetirement' { return 'Service Upgrade And Retirement' }
        'OtherBestPractices' { return 'Other Best Practices' }
        'HighAvailability' { return 'High Availability' }
        default { return $category }
    }
  }


  function Set-LocalFile {
    [CmdletBinding(SupportsShouldProcess, ConfirmImpact = 'Low')]
    param()

    if ($PSCmdlet.ShouldProcess('')) {
      # Clone the GitHub repository to a temporary folder

      # Define script path as the default path to save files
      $workingFolderPath = Get-Location
      Set-Location -Path $workingFolderPath;
      $Script:clonePath = "$workingFolderPath\Azure-Proactive-Resiliency-Library-v2"
      if ((Get-ChildItem -Path $Script:clonePath -Force -ErrorAction 'Ignore' | Measure-Object).Count -gt 0) {
        Write-Debug 'APRL Folder does exist. Reseting it...'
        Get-Item -Path $Script:clonePath | Remove-Item -Recurse -Force
        git clone $RepoUrl $Script:clonePath --quiet
      } else {
        git clone $RepoUrl $Script:clonePath --quiet
      }
      Write-Debug 'Checking the version of the script'
      $RepoVersion = Get-RepoVersion -ClonePath $Script:clonePath
      if ($Script:Version -ne $RepoVersion.Analyzer) {
        Write-Host 'This version of the script is outdated. ' -BackgroundColor DarkRed
        Write-Host 'Please use a more recent version of the script.' -BackgroundColor DarkRed
      } else {
        Write-Host 'This version of the script is current version. ' -BackgroundColor DarkGreen
      }
    }
  }

  function Convert-JSON {
    Write-Host 'Processing JSON File'

    # Load the JSON file from the collector script...
    $JSONFile = Get-Item -Path $JSONFile
    $JSONFile = $JSONFile.FullName
    $results = Get-Content -Path $JSONFile | ConvertFrom-Json

    $Script:AllResourceTypesOrdered = $results.ResourceType
    $Script:Outages = $results.Outages
    $Script:SupportTickets = $results.SupportTickets
    $Script:Retirements = $results.Retirements
    $Script:ServiceHealth = $results.ServiceHealth
    $Script:CollectorDetails = $results.ScriptDetails
    $Script:OutOfScope = $results.OutOfScope

    $RepoVersion = $RepoVersion = Get-RepoVersion -ClonePath $Script:clonePath

    if ($Script:CollectorDetails.Version -eq $RepoVersion.Collector) {
      Write-Host 'The JSON file was created by the current version of the Collector Script. ' -BackgroundColor DarkGreen
    } else {
      Write-Host "The JSON file was created by an outdated version ($($Script:CollectorDetails.Version)) of the Collector Script. The latest version is $($RepoVersion.Collector)" -BackgroundColor DarkRed
    }

    $CoreResources = $results.ImpactedResources
    $CoreAdvisories = $results.Advisory

    $Script:ServicesYAML = Get-ChildItem -Path ($Script:clonePath + '\azure-resources') -Filter 'recommendations.yaml' -Recurse
    $Script:WAFYAML = Get-ChildItem -Path ($Script:clonePath + '\azure-waf') -Filter 'recommendations.yaml' -Recurse

    if ($Script:CollectorDetails.SAP -eq 'True') {
      $Script:ServicesYAML += Get-ChildItem -Path ($Script:clonePath + '\azure-specialized-workloads\sap') -Filter 'recommendations.yaml' -Recurse
    }
    if ($Script:CollectorDetails.AVD -eq 'True') {
      $Script:ServicesYAML += Get-ChildItem -Path ($Script:clonePath + '\azure-specialized-workloads\avd') -Filter 'recommendations.yaml' -Recurse
    }
    if ($Script:CollectorDetails.AVS -eq 'True') {
      $Script:ServicesYAML += Get-ChildItem -Path ($Script:clonePath + '\azure-specialized-workloads\avs') -Filter 'recommendations.yaml' -Recurse
    }
    if ($Script:CollectorDetails.HPC -eq 'HPC') {
      $Script:ServicesYAML += Get-ChildItem -Path ($Script:clonePath + '\azure-specialized-workloads\hpc') -Filter 'recommendations.yaml' -Recurse
    }

    $Script:AdvisorContent = $CoreAdvisories | Select-Object -Property recommendationId, type, category, impact, description -Unique

    # Load custom YAML content if provided...
    # Custom YAML variable is always here regardless of whether a custom file is provided.
    $Script:CustomYAMLContent = @()

    # If a custom file is provided, load it and add it to the custom YAML content.
    if (![string]::IsNullOrWhiteSpace(($CustomRecommendationsYAMLPath))) {
      $Script:CustomYAMLContent = Get-Content -Path $CustomRecommendationsYAMLPath | ConvertFrom-Yaml
    }

    $Script:ServicesYAMLContent = @()
    foreach ($YAML in $Script:ServicesYAML) {
      if (![string]::IsNullOrEmpty($YAML)) {
        $Script:ServicesYAMLContent += Get-Content -Path $YAML | ConvertFrom-Yaml
      }
    }
    $Script:WAFYAMLContent = @()
    foreach ($YAML in $Script:WAFYAML) {
      if (![string]::IsNullOrEmpty($YAML)) {
        $Script:WAFYAMLContent += Get-Content -Path $YAML | ConvertFrom-Yaml
      }
    }

    $Script:MergedRecommendation = @()

    foreach ($Recom in $CoreResources | Where-Object { $_ -ne $null }) {
      if ($($Recom.checkName) -and $($Recom.selector)) {
        # This is a runbook recommendation...
        $recomContent = $Script:CustomYAMLContent `
        | Where-Object { ($_.aprlGuid -eq $Recom.recommendationId) -and ($_.checkName -eq $Recom.checkName) } `
        | Select-Object -First 1

        if (-not $recomContent) {
          # If we couldn't find a check-specific recommendation, try to find a generic one...
          $recomContent = $Script:CustomYAMLContent `
          | Where-Object { ($_.aprlGuid -eq $Recom.recommendationId) } `
          | Select-Object -First 1
        }

        if (-not $recomContent) {
          # If we couldn't find a check-specific recommendation, try to find a generic one...
          $recomContent = $Script:ServicesYAMLContent `
          | Where-Object { ($_.aprlGuid -eq $Recom.recommendationId) } `
          | Select-Object -First 1
        }

        if (-not $recomContent) {
          # If we still couldn't find a recommendation, create a default one..
          $recomContent = [pscustomobject]@{
            description                = [string]::Empty
            recommendationResourceType = 'Unknown'
            recommendationImpact       = 'Unknown'
          }
        }

        $tmp = @{
          'How was the resource/recommendation validated or what actions need to be taken?' = $Recom.validationAction;
          recommendationId                                                                  = $Recom.recommendationId;
          recommendationTitle                                                               = $recomContent.description;
          resourceType                                                                      = $recomContent.recommendationResourceType;
          impact                                                                            = $recomContent.recommendationImpact;
          subscriptionId                                                                    = $Recom.subscriptionId;
          resourceGroup                                                                     = $Recom.resourceGroup;
          name                                                                              = $Recom.name;
          id                                                                                = $Recom.id;
          location                                                                          = $Recom.location;
          param1                                                                            = $Recom.param1;
          param2                                                                            = $Recom.param2;
          param3                                                                            = $Recom.param3;
          param4                                                                            = $Recom.param4;
          param5                                                                            = $Recom.param5;
          supportTicketId                                                                   = [string]::Empty;
          source                                                                            = $Recom.selector;
          checkName                                                                         = $Recom.checkName;
          'WAF Pillar'                                                                      = 'Reliability';
          tagged                                                                            = $Recom.tagged
        }

        $Script:MergedRecommendation += $tmp

      } else {
        # This isn't a runbook recommendation...
        $RecomTitle = $Script:ServicesYAMLContent | Where-Object { $_.aprlGuid -eq $Recom.recommendationId }

        if ([string]::IsNullOrEmpty($RecomTitle.recommendationTypeId) -or (![string]::IsNullOrEmpty($RecomTitle.recommendationTypeId) -and $RecomTitle.recommendationTypeId -notin $CoreAdvisories.recommendationId)) {
          $Ticket = $Script:SupportTickets | Where-Object { $_.'Related Resource' -eq $Recom.id }
          if (($RecomTitle.recommendationMetadataState -eq 'Active') -or $Recom.validationAction -eq 'IMPORTANT - Recommendation cannot be validated with ARGs - Validate Resources manually' -or $Recom.validationAction -eq 'IMPORTANT - Query under development - Validate Resources manually' ) {
            $Tickets = if ($Ticket.'Ticket ID'.count -gt 1) { $Ticket.'Ticket ID' | ForEach-Object { $_ + ' /' } }else { $Ticket.'Ticket ID' }
            $Tickets = [string]$Tickets
            $Tickets = if ($Tickets -like '* /*') { $Tickets -replace '.$' }else { $Tickets }
            $tmp = @{
              'How was the resource/recommendation validated or what actions need to be taken?' = $Recom.validationAction;
              recommendationId                                                                  = $Recom.recommendationId;
              recommendationTitle                                                               = $RecomTitle.description;
              resourceType                                                                      = $RecomTitle.recommendationResourceType;
              impact                                                                            = $RecomTitle.recommendationImpact;
              subscriptionId                                                                    = $Recom.subscriptionId;
              resourceGroup                                                                     = $Recom.resourceGroup;
              name                                                                              = $Recom.name;
              id                                                                                = $Recom.id;
              location                                                                          = $Recom.location;
              param1                                                                            = $Recom.param1;
              param2                                                                            = $Recom.param2;
              param3                                                                            = $Recom.param3;
              param4                                                                            = $Recom.param4;
              param5                                                                            = $Recom.param5;
              supportTicketId                                                                   = $Tickets;
              source                                                                            = $Recom.selector;
              checkName                                                                         = $Recom.checkName;
              'WAF Pillar'                                                                      = 'Reliability';
              tagged                                                                            = $Recom.tagged
            }
            $Script:MergedRecommendation += $tmp
          } elseif ($Recom.validationAction -eq 'IMPORTANT - Resource Type is not available in either APRL or Advisor - Validate Resources manually if Applicable, if not Delete this line' ) {
            $tmp = @{
              'How was the resource/recommendation validated or what actions need to be taken?' = $Recom.validationAction;
              recommendationId                                                                  = '';
              recommendationTitle                                                               = $RecomTitle.description;
              resourceType                                                                      = $Recom.recommendationId;
              impact                                                                            = '';
              subscriptionId                                                                    = $Recom.subscriptionId;
              resourceGroup                                                                     = $Recom.resourceGroup;
              name                                                                              = $Recom.name;
              id                                                                                = $Recom.id;
              location                                                                          = $Recom.location;
              param1                                                                            = $Recom.param1;
              param2                                                                            = $Recom.param2;
              param3                                                                            = $Recom.param3;
              param4                                                                            = $Recom.param4;
              param5                                                                            = $Recom.param5;
              supportTicketId                                                                   = $Tickets;
              source                                                                            = $Recom.selector;
              checkName                                                                         = $Recom.checkName;
              'WAF Pillar'                                                                      = 'Reliability';
              tagged                                                                            = $Recom.tagged
            }
            $Script:MergedRecommendation += $tmp
          }
        }
      }
    }

    $Script:RecommendedAdv = @()
    foreach ($adv in $CoreAdvisories) {
      if (![string]::IsNullOrEmpty($adv.recommendationId)) {
        #$APRLADV = $Script:ServicesYAMLContent | Where-Object { $_.recommendationTypeId -eq $adv.recommendationId }

        #if ($APRLADV.recommendationTypeId -eq $adv.recommendationId ) {
          $Ticket = $Script:SupportTickets | Where-Object { $_.'Related Resource' -eq $adv.id }
          $Tickets = if ($Ticket.'Ticket ID'.count -gt 1) { $Ticket.'Ticket ID' | ForEach-Object { $_ + ' /' } }else { $Ticket.'Ticket ID' }
          $Tickets = [string]$Tickets
          $Tickets = if ($Tickets -like '* /*') { $Tickets -replace '.$' }else { $Tickets }
          $WAFPillar = if ($adv.category -eq 'HighAvailability') { 'Reliability' }else { $adv.category }
          $tmp = @{
            'How was the resource/recommendation validated or what actions need to be taken?' = 'Advisor - Queries';
            recommendationId                                                                  = $adv.recommendationId;
            recommendationTitle                                                               = $adv.description;
            impact                                                                            = $adv.impact;
            resourceType                                                                      = $adv.type;
            subscriptionId                                                                    = $adv.subscriptionId;
            resourceGroup                                                                     = $adv.resourceGroup;
            name                                                                              = $adv.name;
            id                                                                                = $adv.id;
            location                                                                          = $adv.location;
            param1                                                                            = '';
            param2                                                                            = '';
            param3                                                                            = '';
            param4                                                                            = '';
            param5                                                                            = '';
            supportTicketId                                                                   = $Tickets;
            source                                                                            = 'ADVISOR';
            checkName                                                                         = '';
            'WAF Pillar'                                                                      = $WAFPillar;
            tagged                                                                            = $true
          }
          $Script:MergedRecommendation += $tmp
          $Script:RecommendedAdv += $adv.recommendationId
        #}
      }
    }

    foreach ($WAF in $Script:WAFYAMLContent) {
      $tmp = @{
        'How was the resource/recommendation validated or what actions need to be taken?' = "IMPORTANT - Update this item based on Discovery Workshop Questionnaire";
        recommendationId                                                                  = [string]$WAF.aprlGuid;
        recommendationTitle                                                               = [string]$WAF.description;
        resourceType                                                                      = [string]$WAF.recommendationResourceType;
        impact                                                                            = [string]$WAF.recommendationImpact;
        subscriptionId                                                                    = '';
        resourceGroup                                                                     = '';
        name                                                                              = 'Entire Workload';
        id                                                                                = '';
        location                                                                          = '';
        param1                                                                            = '';
        param2                                                                            = '';
        param3                                                                            = '';
        param4                                                                            = '';
        param5                                                                            = '';
        supportTicketId                                                                   = '';
        source                                                                            = 'APRL';
        checkName                                                                         = ''
        'WAF Pillar'                                                                      = 'Reliability';
        tagged                                                                            = $true
      }
      $Script:MergedRecommendation += $tmp
    }
  }

  function Build-ExcelFile {

    Write-Host 'Starting Excel file Processing. '

    $TableStyle = 'Light19'
    $Script:Recommendations = @()

    # Defines the Excel file to be created in the root folder
    $Script:ExcelFile = ($(Get-Location).Path + '\WARA Action Plan ' + (Get-Date -Format 'yyyy-MM-dd-HH-mm') + '.xlsx')

    function Add-ImpactedResource {
      #################### Creates the first sheet (ImpactedResources)
      $Styles1 = @(
        New-ExcelStyle -HorizontalAlignment Center -FontName 'Calibri' -FontSize 11 -FontColor 'White' -Bold -BackgroundColor 'DarkSlateGray' -AutoSize -Range 'A1:S1'
        New-ExcelStyle -HorizontalAlignment Center -FontName 'Calibri' -FontSize 11 -AutoSize -NumberFormat '0' -Range 'A:B'
        New-ExcelStyle -HorizontalAlignment Center -FontName 'Calibri' -FontSize 11 -Width 100 -WrapText -NumberFormat '0' -Range 'C:C'
        New-ExcelStyle -HorizontalAlignment Center -FontName 'Calibri' -FontSize 11 -AutoSize -NumberFormat '0' -Range 'D:I'
        New-ExcelStyle -HorizontalAlignment Left -FontName 'Calibri' -FontSize 11 -Width 80 -Range 'J:J'
        New-ExcelStyle -HorizontalAlignment Center -FontName 'Calibri' -FontSize 11 -AutoSize -NumberFormat '0' -Range 'K:S'
      )

      $Styles2 = @(
        New-ExcelStyle -HorizontalAlignment Center -FontName 'Calibri' -FontSize 11 -FontColor 'White' -Bold -BackgroundColor 'DarkSlateGray' -AutoSize -Range 'A1:G1'
        New-ExcelStyle -HorizontalAlignment Center -FontName 'Calibri' -FontSize 11 -AutoSize -NumberFormat '0' -Range 'A:F'
        New-ExcelStyle -HorizontalAlignment Left -FontName 'Calibri' -FontSize 11 -Width 80 -Range 'G:G'
      )

      $cond = @()
      $cond += New-ConditionalText "IMPORTANT - Update this item based on Discovery Workshop Questionnaire" -Range A:A
      $cond += New-ConditionalText 'IMPORTANT' -Range A:A

      $cond2 = @()
      $cond2 += New-ConditionalText 'No Action Required' -Range A:A

      $ImpactedResourcesSheet = New-Object System.Collections.Generic.List[System.Object]
      $ImpactedResourcesSheet.Add('How was the resource/recommendation validated or what actions need to be taken?')
      $ImpactedResourcesSheet.Add('resourceType')
      $ImpactedResourcesSheet.Add('recommendationTitle')
      $ImpactedResourcesSheet.Add('recommendationId')
      $ImpactedResourcesSheet.Add('impact')
      $ImpactedResourcesSheet.Add('subscriptionId')
      $ImpactedResourcesSheet.Add('resourceGroup')
      $ImpactedResourcesSheet.Add('location')
      $ImpactedResourcesSheet.Add('name')
      $ImpactedResourcesSheet.Add('id')
      $ImpactedResourcesSheet.Add('param1')
      $ImpactedResourcesSheet.Add('param2')
      $ImpactedResourcesSheet.Add('param3')
      $ImpactedResourcesSheet.Add('param4')
      $ImpactedResourcesSheet.Add('param5')
      $ImpactedResourcesSheet.Add('supportTicketId')
      $ImpactedResourcesSheet.Add('source')
      $ImpactedResourcesSheet.Add('WAF Pillar')
      $ImpactedResourcesSheet.Add('checkName')

      $OutOfScopeSheet = New-Object System.Collections.Generic.List[System.Object]
      $OutOfScopeSheet.Add('description')
      $OutOfScopeSheet.Add('type')
      $OutOfScopeSheet.Add('subscriptionId')
      $OutOfScopeSheet.Add('resourceGroup')
      $OutOfScopeSheet.Add('name')
      $OutOfScopeSheet.Add('location')
      $OutOfScopeSheet.Add('id')


      $Script:MergedRecommendation | ForEach-Object { [PSCustomObject]$_ } | Select-Object $ImpactedResourcesSheet |
        Export-Excel -Path $ExcelFile -WorksheetName 'ImpactedResources' -TableName 'Table2' -ConditionalText $cond -AutoSize -TableStyle $TableStyle -Style $Styles1

      $Script:OutOfScope | ForEach-Object { [PSCustomObject]$_ } | Select-Object $OutOfScopeSheet |
        Export-Excel -Path $ExcelFile -WorksheetName 'Other-OutOfScope' -TableName 'UnTagTable' -ConditionalText $cond2 -AutoSize -TableStyle $TableStyle -Style $Styles2
    }

    function Add-ResourceType {
      #################### Creates the second sheet (ResourceTypes)
      $ResourceTypeSheet = New-Object System.Collections.Generic.List[System.Object]
      $ResourceTypeSheet.Add('Resource Type')
      $ResourceTypeSheet.Add('Number of Resources')
      $ResourceTypeSheet.Add('Available in APRL/ADVISOR?')
      $ResourceTypeSheet.Add('Assessment Owner')
      $ResourceTypeSheet.Add('Status')
      $ResourceTypeSheet.Add('Notes')

      $TypeStyle = @(
        New-ExcelStyle -HorizontalAlignment Center -FontName 'Calibri' -FontSize 11 -FontColor 'White' -Bold -BackgroundColor 'DarkSlateGray' -AutoSize -Range 'A1:F1'
        New-ExcelStyle -HorizontalAlignment Center -FontName 'Calibri' -FontSize 11 -AutoSize -NumberFormat '0' -Range 'A:F'
      )

      $Script:AllResourceTypesOrdered | ForEach-Object { [PSCustomObject]$_ } | Select-Object $ResourceTypeSheet |
        Export-Excel -Path $ExcelFile -WorksheetName 'ResourceTypes' -TableName 'TableTypes' -AutoSize -TableStyle $TableStyle -Style $TypeStyle

    }

    function Add-Outage {
      #################### Creates the Outages sheet
      $Script:OutagesSheet = @()
      $RealOutages = $Script:Outages | Where-Object { $_.properties.description -like '*How can customers make incidents like this less impactful?*' -and $_.properties.impactStartTime -gt ((Get-Date).AddMonths(-3)) }
      foreach ($Outage in  $RealOutages) {
        if (![string]::IsNullOrEmpty($Outage.name)) {

          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 = ""
          }

          $OutProps = $Outage.properties
          $tmp = @{
            'Tracking ID'                                                         = [string]$Outage.name;
            'Event Type'                                                          = [string]$OutProps.eventType;
            'Event Source'                                                        = [string]$OutProps.eventSource;
            'Status'                                                              = [string]$OutProps.status;
            'Title'                                                               = [string]$OutProps.title;
            'Level'                                                               = [string]$OutProps.level;
            'Event Level'                                                         = [string]$OutProps.eventLevel;
            'Start Time'                                                          = [string]$OutProps.impactStartTime;
            'Mitigation Time'                                                     = [string]$OutProps.impactMitigationTime;
            'Impacted Service'                                                    = [string]$OutProps.impact.impactedService;
            'What happened'                                                       = $whathap;
            'What went wrong and why'                                             = $whatwent;
            'How did we respond'                                                  = $howdid;
            'How are we making incidents like this less likely or less impactful' = $howarewe;
            'How can customers make incidents like this less impactful'           = $howcan;
          }
          $Script:OutagesSheet += $tmp
        }
      }


      $Styles3 = @(
        New-ExcelStyle -HorizontalAlignment Center -FontName 'Calibri' -FontSize 11 -FontColor 'White' -VerticalAlignment Center -Bold -WrapText -BackgroundColor 'DarkSlateGray' -Width 14 -Range 'A1:B1'
        New-ExcelStyle -HorizontalAlignment Center -FontName 'Calibri' -FontSize 11 -FontColor 'White' -VerticalAlignment Center -Bold -WrapText -BackgroundColor 'DarkSlateGray' -Width 18 -Range 'C1'
        New-ExcelStyle -HorizontalAlignment Center -FontName 'Calibri' -FontSize 11 -FontColor 'White' -VerticalAlignment Center -Bold -WrapText -BackgroundColor 'DarkSlateGray' -Width 20 -Range 'D1'
        New-ExcelStyle -HorizontalAlignment Center -FontName 'Calibri' -FontSize 11 -FontColor 'White' -VerticalAlignment Center -Bold -WrapText -BackgroundColor 'DarkSlateGray' -Width 55 -Range 'E1'
        New-ExcelStyle -HorizontalAlignment Center -FontName 'Calibri' -FontSize 11 -FontColor 'White' -VerticalAlignment Center -Bold -WrapText -BackgroundColor 'DarkSlateGray' -Width 20 -Range 'F1:I1'
        New-ExcelStyle -HorizontalAlignment Center -FontName 'Calibri' -FontSize 11 -FontColor 'White' -VerticalAlignment Center -Bold -WrapText -BackgroundColor 'DarkSlateGray' -Width 25 -Range 'J1'
        New-ExcelStyle -HorizontalAlignment Center -FontName 'Calibri' -FontSize 11 -FontColor 'White' -VerticalAlignment Center -Bold -WrapText -BackgroundColor 'DarkSlateGray' -Width 80 -Range 'K1:O1'
        New-ExcelStyle -HorizontalAlignment Center -FontName 'Calibri' -FontSize 11 -VerticalAlignment Center -WrapText -Range 'A:O'
      )

      # Configure the array of fields to be used in the Recommendations sheet
      $OutagesWorksheet = New-Object System.Collections.Generic.List[System.Object]
      $OutagesWorksheet.Add('Tracking ID')
      $OutagesWorksheet.Add('Event Type')
      $OutagesWorksheet.Add('Event Source')
      $OutagesWorksheet.Add('Status')
      $OutagesWorksheet.Add('Title')
      $OutagesWorksheet.Add('Level')
      $OutagesWorksheet.Add('Event Level')
      $OutagesWorksheet.Add('Start Time')
      $OutagesWorksheet.Add('Mitigation Time')
      $OutagesWorksheet.Add('Impacted Service')
      $OutagesWorksheet.Add('What happened')
      $OutagesWorksheet.Add('What went wrong and why')
      $OutagesWorksheet.Add('How did we respond')
      $OutagesWorksheet.Add('How are we making incidents like this less likely or less impactful')
      $OutagesWorksheet.Add('How can customers make incidents like this less impactful')


      if (![string]::IsNullOrEmpty($Script:OutagesSheet)) {
        $Script:OutagesSheet | ForEach-Object { [PSCustomObject]$_ } | Select-Object $OutagesWorksheet |
          Export-Excel -Path $ExcelFile -WorksheetName 'Outages' -TableName 'TableOutage' -AutoSize -TableStyle $tableStyle -Style $Styles3
      }

    }

    function Add-Retirement {
      #################### Creates the Retirement sheet
      $Script:RetirementSheet = @()
      foreach ($Retires in $Script:Retirements) {
        if (![string]::IsNullOrEmpty($Retires)) {
          $HTML = New-Object -Com 'HTMLFile'
          $HTML.write([ref]$Retires.Summary)
          $RetirementSummary = $Html.body.innerText

          try {
            $HTML = New-Object -Com 'HTMLFile'
            $HTML.write([ref]$Retires.Description)
            $RetirementDescriptionFull = $Html.body.innerText
            $SplitDescription = $RetirementDescriptionFull.split('Help and support').split('Required action')
          } catch {
            $SplitDescription = ' ', ' '
          }
          $tmp = @{
            'Subscription'     = [string]$Retires.Subscription;
            'Tracking ID'      = [string]$Retires.TrackingId;
            'Status'           = [string]$Retires.Status;
            'Last Update Time' = [string]$Retires.LastUpdateTime;
            'End Time'         = [string]$Retires.Endtime;
            'Impacted Service' = [string]$Retires.ImpactedService;
            'Title'            = [string]$Retires.Title;
            'Summary'          = [string]$RetirementSummary;
            'Required Action'  = [string]$SplitDescription[1];
            'Details'          = [string]$SplitDescription[0]
          }
          $Script:RetirementSheet += $tmp
        }
      }

      $Styles4 = @(
        New-ExcelStyle -HorizontalAlignment Center -FontName 'Calibri' -FontSize 11 -FontColor 'White' -VerticalAlignment Center -Bold -WrapText -BackgroundColor 'DarkSlateGray' -Width 50 -Range 'A1'
        New-ExcelStyle -HorizontalAlignment Center -FontName 'Calibri' -FontSize 11 -FontColor 'White' -VerticalAlignment Center -Bold -WrapText -BackgroundColor 'DarkSlateGray' -Width 25 -Range 'B1:E1'
        New-ExcelStyle -HorizontalAlignment Center -FontName 'Calibri' -FontSize 11 -FontColor 'White' -VerticalAlignment Center -Bold -WrapText -BackgroundColor 'DarkSlateGray' -Width 30 -Range 'F1'
        New-ExcelStyle -HorizontalAlignment Center -FontName 'Calibri' -FontSize 11 -FontColor 'White' -VerticalAlignment Center -Bold -WrapText -BackgroundColor 'DarkSlateGray' -Width 70 -Range 'G1'
        New-ExcelStyle -HorizontalAlignment Center -FontName 'Calibri' -FontSize 11 -FontColor 'White' -VerticalAlignment Center -Bold -WrapText -BackgroundColor 'DarkSlateGray' -Width 80 -Range 'H1'
        New-ExcelStyle -HorizontalAlignment Center -FontName 'Calibri' -FontSize 11 -FontColor 'White' -VerticalAlignment Center -Bold -WrapText -BackgroundColor 'DarkSlateGray' -Width 90 -Range 'I1:J1'
        New-ExcelStyle -HorizontalAlignment Center -FontName 'Calibri' -FontSize 11 -VerticalAlignment Center -WrapText -Range 'A:J'
      )

      # Configure the array of fields to be used in the Retirement sheet
      $RetirementWorksheet = New-Object System.Collections.Generic.List[System.Object]
      $RetirementWorksheet.Add('Subscription')
      $RetirementWorksheet.Add('Tracking ID')
      $RetirementWorksheet.Add('Status')
      $RetirementWorksheet.Add('Last Update Time')
      $RetirementWorksheet.Add('End Time')
      $RetirementWorksheet.Add('Impacted Service')
      $RetirementWorksheet.Add('Title')
      $RetirementWorksheet.Add('Summary')
      $RetirementWorksheet.Add('Details')
      $RetirementWorksheet.Add('Required Action')

      if (![string]::IsNullOrEmpty($Script:RetirementSheet)) {
        $Script:RetirementSheet | ForEach-Object { [PSCustomObject]$_ } | Select-Object $RetirementWorksheet |
          Export-Excel -Path $ExcelFile -WorksheetName 'Retirements' -TableName 'TableRetires' -AutoSize -TableStyle $tableStyle -Style $Styles4
      }

    }

    function Add-SupportTicket {
      #################### Creates the Tickets sheet
      $Script:TicketsSheet = @()
      foreach ($Ticket in $Script:SupportTickets) {
        if (![string]::IsNullOrEmpty($Ticket)) {
          $tmp = @{
            'Ticket ID'         = [string]$Ticket.'Ticket ID';
            'Severity'          = [string]$Ticket.'Severity';
            'Status'            = [string]$Ticket.'Status';
            'Support Plan Type' = [string]$Ticket.'Support Plan Type';
            'Creation Date'     = [string]$Ticket.'Creation Date';
            'Modified Date'     = [string]$Ticket.'Modified Date';
            'Title'             = [string]$Ticket.'Title';
            'Related Resource'  = [string]$Ticket.'Related Resource'
          }
          $Script:TicketsSheet += $tmp
        }
      }

      $Styles5 = @(
        New-ExcelStyle -HorizontalAlignment Center -FontName 'Calibri' -FontSize 11 -FontColor 'White' -VerticalAlignment Center -Bold -WrapText -BackgroundColor 'DarkSlateGray' -Width 20 -Range 'A1'
        New-ExcelStyle -HorizontalAlignment Center -FontName 'Calibri' -FontSize 11 -FontColor 'White' -VerticalAlignment Center -Bold -WrapText -BackgroundColor 'DarkSlateGray' -Width 15 -Range 'B1:C1'
        New-ExcelStyle -HorizontalAlignment Center -FontName 'Calibri' -FontSize 11 -FontColor 'White' -VerticalAlignment Center -Bold -WrapText -BackgroundColor 'DarkSlateGray' -Width 35 -Range 'D1'
        New-ExcelStyle -HorizontalAlignment Center -FontName 'Calibri' -FontSize 11 -FontColor 'White' -VerticalAlignment Center -Bold -WrapText -BackgroundColor 'DarkSlateGray' -Width 20 -Range 'E1:F1'
        New-ExcelStyle -HorizontalAlignment Center -FontName 'Calibri' -FontSize 11 -FontColor 'White' -VerticalAlignment Center -Bold -WrapText -BackgroundColor 'DarkSlateGray' -Width 50 -Range 'G1'
        New-ExcelStyle -HorizontalAlignment Center -FontName 'Calibri' -FontSize 11 -FontColor 'White' -VerticalAlignment Center -Bold -WrapText -BackgroundColor 'DarkSlateGray' -Width 120 -Range 'H1'
        New-ExcelStyle -HorizontalAlignment Center -FontName 'Calibri' -FontSize 11 -VerticalAlignment Center -WrapText -Range 'A:G'
      )

      # Configure the array of fields to be used in the Tickets sheet
      $TicketWorksheet = New-Object System.Collections.Generic.List[System.Object]
      $TicketWorksheet.Add('Ticket ID')
      $TicketWorksheet.Add('Severity')
      $TicketWorksheet.Add('Status')
      $TicketWorksheet.Add('Support Plan Type')
      $TicketWorksheet.Add('Creation Date')
      $TicketWorksheet.Add('Modified Date')
      $TicketWorksheet.Add('Title')
      $TicketWorksheet.Add('Related Resource')

      if (![string]::IsNullOrEmpty($Script:TicketsSheet)) {
        $Script:TicketsSheet | ForEach-Object { [PSCustomObject]$_ } | Select-Object $TicketWorksheet |
          Export-Excel -Path $ExcelFile -WorksheetName 'Support Tickets' -TableName 'TableTickets' -AutoSize -TableStyle $tableStyle -Style $Styles5 -NoNumberConversion *
      }
    }

    function Add-ServiceHealth {
      #################### Creates the Service Health sheet
      $Script:ServiceHealthSheet = @()
      foreach ($Alert in $Script:ServiceHealth) {
        if (![string]::IsNullOrEmpty($Alert)) {
          $Service = if ($Alert.Services.count -gt 1) { $Alert.Services | ForEach-Object { $_ + ' /' } }else { $Alert.Services }
          $Service = [string]$Service
          $Service = if ($Service -like '* /*') { $Service -replace '.$' }else { $Service }
          $EventT = if ($Alert.EventType.count -gt 1) { $Alert.EventType | ForEach-Object { $_ + ' /' } }else { $Alert.EventType }
          $EventT = [string]$EventT
          $EventT = if ($EventT -like '* /*') { $EventT -replace '.$' }else { $EventT }
          $Region = if ($Alert.Regions.count -gt 1) { $Alert.Regions | ForEach-Object { $_ + ' /' } }else { $Alert.Regions }
          $Region = [string]$Region
          $Region = if ($Region -like '* /*') { $Region -replace '.$' }else { $Region }
          $Action = if ($Alert.ActionGroup.count -gt 1) { $Alert.ActionGroup | ForEach-Object { $_ + ' /' } }else { $Alert.ActionGroup }
          $Action = [string]$Action
          $Action = if ($Action -like '* /*') { $Action -replace '.$' }else { $Action }
          $tmp = @{
            'Name'         = [string]$Alert.Name;
            'Enabled'      = [string]$Alert.Enabled;
            'Subscription' = [string]$Alert.Subscription;
            'Services'     = $Service;
            'Event Type'   = $EventT;
            'Regions'      = $Region;
            'Action Group' = $Action
          }
          $Script:ServiceHealthSheet += $tmp
        }
      }

      $Styles6 = @(
        New-ExcelStyle -HorizontalAlignment Center -FontName 'Calibri' -FontSize 11 -FontColor 'White' -VerticalAlignment Center -Bold -WrapText -BackgroundColor 'DarkSlateGray' -Width 25 -NumberFormat '0' -Range 'A1'
        New-ExcelStyle -HorizontalAlignment Center -FontName 'Calibri' -FontSize 11 -FontColor 'White' -VerticalAlignment Center -Bold -WrapText -BackgroundColor 'DarkSlateGray' -Width 15 -NumberFormat '0' -Range 'B1'
        New-ExcelStyle -HorizontalAlignment Center -FontName 'Calibri' -FontSize 11 -FontColor 'White' -VerticalAlignment Center -Bold -WrapText -BackgroundColor 'DarkSlateGray' -Width 35 -NumberFormat '0' -Range 'C1:G1'
        New-ExcelStyle -HorizontalAlignment Center -FontName 'Calibri' -FontSize 11 -VerticalAlignment Center -WrapText -NumberFormat '0' -Range 'A:G'
      )

      # Configure the array of fields to be used in the Tickets sheet
      $ServiceHealthtWorksheet = New-Object System.Collections.Generic.List[System.Object]
      $ServiceHealthtWorksheet.Add('Name')
      $ServiceHealthtWorksheet.Add('Enabled')
      $ServiceHealthtWorksheet.Add('Subscription')
      $ServiceHealthtWorksheet.Add('Services')
      $ServiceHealthtWorksheet.Add('Event Type')
      $ServiceHealthtWorksheet.Add('Regions')
      $ServiceHealthtWorksheet.Add('Action Group')

      if (![string]::IsNullOrEmpty($Script:ServiceHealthSheet)) {
        $Script:ServiceHealthSheet | ForEach-Object { [PSCustomObject]$_ } | Select-Object $ServiceHealthtWorksheet |
          Export-Excel -Path $ExcelFile -WorksheetName 'Health Alerts' -TableName 'TableAlerts' -AutoSize -TableStyle $tableStyle -Style $Styles6
      }
    }

    function Add-Recommendation {
      #################### Starts to process the main sheet

      foreach ($customRec in $Script:CustomYAMLContent) {
        $countFormula = 'COUNTIFS(ImpactedResources!D:D,"' + $customRec.aprlGuid + '",ImpactedResources!S:S,"' + $customRec.checkName + '")'
        $compliantFormula = 'IF((' + $countFormula + ')>0,"No","Yes")'

        $ExcelCat = Set-RecommendationControl -category $customRec.recommendationControl

        $Script:Recommendations += @{
          'Implemented?Yes/No' = "=$compliantFormula"
          'Number of Impacted Resources?' = "=$countFormula"
          'Azure Service / Well-Architected' = 'Custom'
          'Recommendation Source' = 'Custom'
          'Resiliency Category' = $ExcelCat
          'Azure Service Category / Well-Architected Area' = 'Custom'
          'Azure Service / Well-Architected Topic' = 'Custom'
          'Recommendation Title' = [string]$customRec.description
          'Impact' = [string]$customRec.recommendationImpact
          'Best Practices Guidance' = [string]$customRec.longDescription
          'Read More' = [string]$customRec.learnMoreLink.url
          'Potential Benefits' = [string]$customRec.potentialBenefits
          'Add associated Outage TrackingID and/or Support Request # and/or Service Retirement TrackingID' = ''
          'Observation / Annotation' = ''
          'Recommendation Id' = $customRec.aprlGuid
        }
      }

      # Build the APRL Recommendations
      foreach ($Service in $Script:ServicesYAMLContent) {
        if ($Service.recommendationMetadataState -eq 'Active')
          {
            if (($Service.recommendationResourceType -like 'Specialized.Workload/*' -or $Service.recommendationResourceType -eq 'Microsoft.Subscription/Subscriptions' -or $Service.recommendationResourceType -in $Script:AllResourceTypesOrdered.'Resource Type' -or $Script:FilterRecommendations -eq $false) -and ([string]::IsNullOrEmpty($Service.recommendationTypeId) -or (![string]::IsNullOrEmpty($Service.recommendationTypeId) -and $Service.recommendationTypeId -notin $Script:RecommendedAdv))) {
              $ID = $Service.aprlGuid
              $resourceType = $Service.recommendationResourceType

              $ExcelCat = Set-RecommendationControl -category $Service.recommendationControl

              $tmp = @{
                'Implemented?Yes/No'                                                                             = ('=IF((COUNTIF(ImpactedResources!D:D,"' + $ID + '")=0),"Yes","No")');
                'Number of Impacted Resources?'                                                                  = ('=COUNTIF(ImpactedResources!D:D,"' + $ID + '")');
                'Azure Service / Well-Architected'                                                               = 'Azure Service';
                'Recommendation Source'                                                                          = 'APRL';
                'Resiliency Category'                                                                            = $ExcelCat;
                'Azure Service Category / Well-Architected Area'                                                 = if ($resourceType -like 'Specialized.Workload/*') { $resourceType }else { ($resourceType.split('/')[0]) };
                'Azure Service / Well-Architected Topic'                                                         = if ($resourceType -like 'Specialized.Workload/*') { $resourceType }else { ($resourceType.split('/')[1]) };
                'Recommendation Title'                                                                           = $Service.description;
                'Impact'                                                                                         = $Service.recommendationImpact;
                'Best Practices Guidance'                                                                        = [string]$Service.longDescription;
                'Read More'                                                                                      = [string]($Service.learnMoreLink.url -join "`n");
                'Potential Benefits'                                                                             = [string]$Service.potentialBenefits;
                'Add associated Outage TrackingID and/or Support Request # and/or Service Retirement TrackingID' = '';
                'Observation / Annotation'                                                                       = '';
                'Recommendation Id'                                                                              = [string]$Service.aprlGuid
              }
              $Script:Recommendations += $tmp
            }
          }
      }

      # Builds the Advisor recommendations
      foreach ($advisor in $Script:AdvisorContent) {
        if ($advisor.recommendationId -in $Script:RecommendedAdv) {
          $ID = $advisor.recommendationId
          $resourceType = $advisor.type.ToLower()
          $ExcelCat = Set-RecommendationControl -category $advisor.category

          $tmp = @{
            'Implemented?Yes/No'                                                                             = ('=IF((COUNTIF(ImpactedResources!D:D,"' + $ID + '")=0),"Yes","No")');
            'Number of Impacted Resources?'                                                                  = ('=COUNTIF(ImpactedResources!D:D,"' + $ID + '")');
            'Azure Service / Well-Architected'                                                               = 'Azure Service';
            'Recommendation Source'                                                                          = 'ADVISOR';
            'Resiliency Category'                                                                            = $ExcelCat;
            'Azure Service Category / Well-Architected Area'                                                 = ($resourceType.split('/')[0]);
            'Azure Service / Well-Architected Topic'                                                         = ($resourceType.split('/')[1]);
            'Recommendation Title'                                                                           = $advisor.description;
            'Impact'                                                                                         = $advisor.impact;
            'Best Practices Guidance'                                                                        = $advisor.description;
            'Read More'                                                                                      = '';
            'Potential Benefits'                                                                             = '';
            'Add associated Outage TrackingID and/or Support Request # and/or Service Retirement TrackingID' = '';
            'Observation / Annotation'                                                                       = '';
            'Recommendation Id'                                                                              = [string]$advisor.recommendationId
          }
          $Script:Recommendations += $tmp
        }
      }

      # Builds the WAF recommendations
      foreach ($WAFYAML in $Script:WAFYAMLContent) {
        $resourceType = $WAFYAML.recommendationResourceType
        $ID = $WAFYAML.aprlGuid

        $ExcelCat = Set-RecommendationControl -category $WAFYAML.recommendationControl

        $tmp = @{
          'Implemented?Yes/No'                                                                             = ('=IF((COUNTIF(ImpactedResources!D:D,"' + $ID + '")=0),"Yes","No")');
          'Number of Impacted Resources?'                                                                  = ('=COUNTIF(ImpactedResources!D:D,"' + $ID + '")');
          'Azure Service / Well-Architected'                                                               = 'Well Architected';
          'Recommendation Source'                                                                          = 'APRL';
          'Resiliency Category'                                                                            = $ExcelCat;
          'Azure Service Category / Well-Architected Area'                                                 = ($resourceType.split('/')[0]);
          'Azure Service / Well-Architected Topic'                                                         = ($resourceType.split('/')[1]);
          'Recommendation Title'                                                                           = $WAFYAML.description;
          'Impact'                                                                                         = $WAFYAML.recommendationImpact;
          'Best Practices Guidance'                                                                        = [string]$WAFYAML.longDescription;
          'Read More'                                                                                      = [string]($WAFYAML.learnMoreLink.url -join "`n");
          'Potential Benefits'                                                                             = [string]$WAFYAML.potentialBenefits;
          'Add associated Outage TrackingID and/or Support Request # and/or Service Retirement TrackingID' = '';
          'Observation / Annotation'                                                                       = '';
          'Recommendation Id'                                                                              = [string]$WAFYAML.aprlGuid
        }
        $Script:Recommendations += $tmp
      }

      $columnCommonStyle = @{
        FontName = 'Calibri'
        FontSize = 11
        WrapText = $true
      }

      $headerCommonStyle = @{
        FontName            = 'Calibri'
        FontSize            = 11
        FontColor           = 'White'
        Bold                = $true
        BackgroundColor     = 'DarkSlateGray'
        HorizontalAlignment = 'Center'
        VerticalAlignment   = 'Center'
        WrapText            = $true
      }

      $Styles2 = @(
        # Apply the style to the columns.
        New-ExcelStyle @columnCommonStyle -Range 'A:G' -HorizontalAlignment Center -VerticalAlignment Center
        New-ExcelStyle @columnCommonStyle -Range 'H:H' -HorizontalAlignment Left -VerticalAlignment Center
        New-ExcelStyle @columnCommonStyle -Range 'I:I' -HorizontalAlignment Center -VerticalAlignment Center
        New-ExcelStyle @columnCommonStyle -Range 'J:K' -HorizontalAlignment Left -VerticalAlignment Top
        New-ExcelStyle @columnCommonStyle -Range 'L:L' -HorizontalAlignment Center -VerticalAlignment Center
        New-ExcelStyle @columnCommonStyle -Range 'M:M' -HorizontalAlignment Left -VerticalAlignment Top
        New-ExcelStyle @columnCommonStyle -Range 'N:N' -HorizontalAlignment Center -VerticalAlignment Center

        # Apply the style to the header row.
        New-ExcelStyle @headerCommonStyle -Range 'A1:B1' -Width 14
        New-ExcelStyle @headerCommonStyle -Range 'C1' -Width 18
        New-ExcelStyle @headerCommonStyle -Range 'D1' -Width 20
        New-ExcelStyle @headerCommonStyle -Range 'E1:F1' -Width 35
        New-ExcelStyle @headerCommonStyle -Range 'G1' -Width 20
        New-ExcelStyle @headerCommonStyle -Range 'H1' -Width 55
        New-ExcelStyle @headerCommonStyle -Range 'I1' -Width 10
        New-ExcelStyle @headerCommonStyle -Range 'J1' -Width 90
        New-ExcelStyle @headerCommonStyle -Range 'K1' -Width 45
        New-ExcelStyle @headerCommonStyle -Range 'L1:M1' -Width 35
        New-ExcelStyle @headerCommonStyle -Range 'N1' -Width 45
      )

      # Configure the array of fields to be used in the Recommendations sheet
      $FinalWorksheet = New-Object System.Collections.Generic.List[System.Object]
      $FinalWorksheet.Add('Implemented?Yes/No')
      $FinalWorksheet.Add('Number of Impacted Resources?')
      $FinalWorksheet.Add('Azure Service / Well-Architected')
      $FinalWorksheet.Add('Recommendation Source')
      $FinalWorksheet.Add('Azure Service Category / Well-Architected Area')
      $FinalWorksheet.Add('Azure Service / Well-Architected Topic')
      $FinalWorksheet.Add('Resiliency Category')
      $FinalWorksheet.Add('Recommendation Title')
      $FinalWorksheet.Add('Impact')
      $FinalWorksheet.Add('Best Practices Guidance')
      $FinalWorksheet.Add('Read More')
      $FinalWorksheet.Add('Add associated Outage TrackingID and/or Support Request # and/or Service Retirement TrackingID')
      $FinalWorksheet.Add('Observation / Annotation')
      $FinalWorksheet.Add('Recommendation Id')

      #################### Creates the recommendations sheet in Excel
      $Script:Recommendations | ForEach-Object { [PSCustomObject]$_ } | Select-Object $FinalWorksheet |
        Export-Excel -Path $ExcelFile -WorksheetName 'Recommendations' -TableName 'Table1' -AutoSize -TableStyle $tableStyle -Style $Styles2 -MoveToStart

    }

    function Build-PivotTable {
      #################### Creates the empty PivotTable sheet to be used later
      '' | Export-Excel -Path $ExcelFile -WorksheetName 'PivotTable'

      #################### Creates the Charts sheet and already add the first line with the yellow background
      $StyleOver = New-ExcelStyle -Range A1:G1 -Bold -FontSize 11 -BackgroundColor ([System.Drawing.Color]::Yellow) -Merge -HorizontalAlignment Left
      'Copy the Charts below to your Word and Powerpoint Documents' | Export-Excel -Path $ExcelFile -WorksheetName 'Charts' -Style $StyleOver

      # Open the Excel file to add the Pivot Tables and Charts
      $Excel = Open-ExcelPackage -Path $ExcelFile

      $PTParams = @{
        PivotTableName    = 'P0'
        Address           = $Excel.PivotTable.cells['A3']
        SourceWorkSheet   = $Excel.Recommendations
        PivotRows         = @('Azure Service / Well-Architected', 'Azure Service / Well-Architected Topic')
        PivotColumns      = @('Impact')
        PivotData         = @{'Azure Service Category / Well-Architected Area' = 'Count' }
        PivotTableStyle   = 'Medium8'
        Activate          = $true
        PivotFilter       = 'Implemented?Yes/No'
        ShowPercent       = $true
        IncludePivotChart = $true
        #ShowCategory = $true
        ChartType         = 'BarClustered'
        ChartRow          = 80
        ChartColumn       = 3
        NoLegend          = $false
        ChartTitle        = 'Recommendations per Services/Well-Architected Area'
        ChartHeight       = 696
        ChartWidth        = 450
      }
      Add-PivotTable @PTParams


      $PTParams = @{
        PivotTableName    = 'P1'
        Address           = $Excel.PivotTable.cells['H3']
        SourceWorkSheet   = $Excel.Recommendations
        PivotRows         = @('Resiliency Category')
        PivotColumns      = @('Impact')
        PivotData         = @{'Resiliency Category' = 'Count' }
        PivotTableStyle   = 'Medium9'
        Activate          = $true
        PivotFilter       = 'Implemented?Yes/No'
        ShowPercent       = $true
        IncludePivotChart = $true
        ChartType         = 'BarClustered'
        ChartRow          = 80
        ChartColumn       = 30
        NoLegend          = $false
        ChartTitle        = 'Recommendations per Resiliency Category'
        ChartHeight       = 569
        ChartWidth        = 462
      }
      Add-PivotTable @PTParams

      Close-ExcelPackage $Excel
    }

    function Invoke-ExcelAPI {
      Write-Host 'Opening Excel...'
      $Script:ExcelApplication = New-Object -ComObject Excel.Application
      Start-Sleep 2
      Write-Host 'Customizing Excel Charts. '
      # Open the Excel using the API to move the charts from the PivotTable sheet to the Charts sheet and change chart style, font, etc..
      if ($Script:ExcelApplication) {
        try {
          Write-Debug 'Opening Excel File'
          $Ex = $ExcelApplication.Workbooks.Open($ExcelFile)
          Start-Sleep -Seconds 2
          Write-Debug 'Opening Excel Sheets'
          $WS = $ex.Worksheets | Where-Object { $_.Name -eq 'PivotTable' }
          $WS2 = $ex.Worksheets | Where-Object { $_.Name -eq 'Charts' }
          Write-Debug 'Moving Charts to Chart sheet'
          ($WS.Shapes | Where-Object { $_.name -eq 'ChartP0' }).DrawingObject.Cut()
          $WS2.Paste()
          ($WS.Shapes | Where-Object { $_.name -eq 'ChartP1' }).DrawingObject.Cut()
          $WS2.Paste()

          Write-Debug 'Reloading Excel Chart Sheet'
          $WS2 = $ex.Worksheets | Where-Object { $_.Name -eq 'Charts' }

          Write-Debug 'Editing ChartP0'
          ($WS2.Shapes | Where-Object { $_.name -eq 'ChartP0' }).DrawingObject.Chart.ChartStyle = 222
          ($WS2.Shapes | Where-Object { $_.name -eq 'ChartP0' }).DrawingObject.Chart.ChartArea.Font.Name = 'Segoe UI'
          ($WS2.Shapes | Where-Object { $_.name -eq 'ChartP0' }).DrawingObject.Chart.ChartArea.Font.Size = 9
          ($WS2.Shapes | Where-Object { $_.name -eq 'ChartP0' }).DrawingObject.Chart.ChartArea.Left = 18
          ($WS2.Shapes | Where-Object { $_.name -eq 'ChartP0' }).DrawingObject.Chart.ChartArea.Top = 40

          Write-Debug 'Editing ChartP1'
          ($WS2.Shapes | Where-Object { $_.name -eq 'ChartP1' }).DrawingObject.Chart.ChartStyle = 222
          ($WS2.Shapes | Where-Object { $_.name -eq 'ChartP1' }).DrawingObject.Chart.ChartArea.Font.Name = 'Segoe UI'
          ($WS2.Shapes | Where-Object { $_.name -eq 'ChartP1' }).DrawingObject.Chart.ChartArea.Font.Size = 9
          ($WS2.Shapes | Where-Object { $_.name -eq 'ChartP1' }).DrawingObject.Chart.ChartArea.Left = 555
          ($WS2.Shapes | Where-Object { $_.name -eq 'ChartP1' }).DrawingObject.Chart.ChartArea.Top = 40

          Write-Debug 'Editing Pivot Filters'
          $WS.Range('B1').Formula = 'No'
          $WS.Range('I1').Formula = 'No'

          Write-Debug 'Saving File'
          $Ex.Save()
          Write-Debug 'Closing Excel Application'
          $Ex.Close()
          $ExcelApplication.Quit()
          # Ensures the Excel process opened by the API is closed
          Write-Debug 'Ensuring Excel Process is Closed.'
          Get-Process -Name 'excel' -ErrorAction Ignore | Where-Object { $_.CommandLine -like '*/automation*' } | Stop-Process
        } catch {
          Write-Host 'Error during the PivotTable + Charts customization' -BackgroundColor DarkRed
        }
      }

    }

    Add-ImpactedResource
    Add-ResourceType
    Add-Outage
    Add-Retirement
    Add-SupportTicket
    Add-ServiceHealth
    Add-Recommendation
    Build-PivotTable
    Invoke-ExcelAPI

  }

  #Call the functions
  $Script:Version = '2.1.18'
  Write-Host 'Version: ' -NoNewline
  Write-Host $Script:Version -ForegroundColor DarkBlue

  if ($Help.IsPresent) {
    Get-HelpMessage
    Exit
  }

  Write-Debug 'Calling Function: Test-Requirement'
  Test-Requirement

  Write-Debug 'Calling Function: Set-LocalFile'
  Set-LocalFile

  Write-Debug 'Calling Function: Convert-JSON'
  Convert-JSON

  Write-Debug 'Calling Function: Build-ExcelFile'
  Build-ExcelFile
}

$TotalTime = $Script:Runtime.Totalminutes.ToString('#######.##')

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 $Script:ExcelFile -ForegroundColor Blue
Write-Host '---------------------------------------------------------------------'