diff --git a/AzureResourceInventory.ps1 b/AzureResourceInventory.ps1 index d3747dd..f21aa2e 100644 --- a/AzureResourceInventory.ps1 +++ b/AzureResourceInventory.ps1 @@ -2,9 +2,9 @@ # # # * Azure Resource Inventory ( ARI ) Report Generator * # # # -# Version: 3.1.08 # +# Version: 3.1.09 # # # -# Date: 08/31/2023 # +# Date: 11/06/2023 # # # ########################################################################################## <# @@ -71,7 +71,7 @@ param ($TenantID, $ManagementGroup, $Appid, $Secret, - $ResourceGroup, + [string[]]$ResourceGroup, $TagKey, $TagValue, [switch]$SkipAdvisory, @@ -113,7 +113,7 @@ param ($TenantID, Write-Host "" Write-Host " -TenantID : Specifies the Tenant to be inventoried. " Write-Host " -SubscriptionID : Specifies Subscription(s) to be inventoried. " - Write-Host " -ResourceGroup : Specifies one unique Resource Group to be inventoried, This parameter requires the -SubscriptionID to work. " + Write-Host " -ResourceGroup : Specifies one (or more) unique Resource Group to be inventoried, This parameter requires the -SubscriptionID to work. " Write-Host " -TagKey : Specifies the tag key to be inventoried, This parameter requires the -SubscriptionID to work. " Write-Host " -TagValue : Specifies the tag value be inventoried, This parameter requires the -SubscriptionID to work. " Write-Host " -SkipAdvisory : Do not collect Azure Advisory. " @@ -545,7 +545,7 @@ param ($TenantID, $Subscri = $SubscriptionID - $GraphQuery = "resources | where resourceGroup == '$ResourceGroup' and strlen(properties.definition.actions) < 123000 | summarize count()" + $GraphQuery = "resources | where resourceGroup in ('$([String]::Join("','",$ResourceGroup))') and strlen(properties.definition.actions) < 123000 | summarize count()" $EnvSize = az graph query -q $GraphQuery --subscriptions $Subscri --output json --only-show-errors | ConvertFrom-Json $EnvSizeNum = $EnvSize.data.'count_' @@ -556,7 +556,7 @@ param ($TenantID, $Limit = 0 while ($Looper -lt $Loop) { - $GraphQuery = "resources | where resourceGroup == '$ResourceGroup' and strlen(properties.definition.actions) < 123000 | project id,name,type,tenantId,kind,location,resourceGroup,subscriptionId,managedBy,sku,plan,properties,identity,zones,extendedLocation$($GraphQueryTags) | order by id asc" + $GraphQuery = "resources | where resourceGroup in ('$([String]::Join("','",$ResourceGroup))') and strlen(properties.definition.actions) < 123000 | project id,name,type,tenantId,kind,location,resourceGroup,subscriptionId,managedBy,sku,plan,properties,identity,zones,extendedLocation$($GraphQueryTags) | order by id asc" $Resource = (az graph query -q $GraphQuery --subscriptions $Subscri --skip $Limit --first 1000 --output json --only-show-errors).tolower() | ConvertFrom-Json $Global:Resources += $Resource.data @@ -784,7 +784,7 @@ param ($TenantID, $GraphQueryExtension = "| join kind=inner (resourcecontainers | where type == 'microsoft.resources/subscriptions' | mv-expand managementGroupParent = properties.managementGroupAncestorsChain | where managementGroupParent.name =~ '$ManagementGroup' | project subscriptionId, managanagementGroup = managementGroupParent.name) on subscriptionId" } if (![string]::IsNullOrEmpty($ResourceGroup)) { - $GraphQueryExtension = "$GraphQueryExtension | where resourceGroup == '$ResourceGroup'" + $GraphQueryExtension = "$GraphQueryExtension | where resourceGroup in ('$([String]::Join("','",$ResourceGroup))')" } $GraphQuery = "advisorresources $GraphQueryExtension | summarize count()" @@ -835,7 +835,7 @@ param ($TenantID, $GraphQueryExtension = "| join kind=inner (resourcecontainers | where type == 'microsoft.resources/subscriptions' | mv-expand managementGroupParent = properties.managementGroupAncestorsChain | where managementGroupParent.name =~ '$ManagementGroup' | project subscriptionId, managanagementGroup = managementGroupParent.name) on subscriptionId" } if (![string]::IsNullOrEmpty($ResourceGroup)) { - $GraphQueryExtension = "$GraphQueryExtension | where resourceGroup == '$ResourceGroup'" + $GraphQueryExtension = "$GraphQueryExtension | where resourceGroup in ('$([String]::Join("','",$ResourceGroup))')" } #$SecSize = az graph query -q "securityresources | where properties['status']['code'] == 'Unhealthy' | summarize count()" --subscriptions $Subscri --output json --only-show-errors | ConvertFrom-Json $SecSize = az graph query -q "securityresources $GraphQueryExtension | where properties['status']['code'] == 'Unhealthy' | summarize count()" --output json --only-show-errors | ConvertFrom-Json @@ -880,7 +880,7 @@ param ($TenantID, $GraphQueryExtension = "| join kind=inner (resourcecontainers | where type == 'microsoft.resources/subscriptions' | mv-expand managementGroupParent = properties.managementGroupAncestorsChain | where managementGroupParent.name =~ '$ManagementGroup' | project subscriptionId, managanagementGroup = managementGroupParent.name) on subscriptionId" } if (![string]::IsNullOrEmpty($ResourceGroup)) { - $GraphQueryExtension = "$GraphQueryExtension | where resourceGroup == '$ResourceGroup'" + $GraphQueryExtension = "$GraphQueryExtension | where resourceGroup in ('$([String]::Join("','",$ResourceGroup))')" } #$AVDSize = az graph query -q "desktopvirtualizationresources | summarize count()" --subscriptions $Subscri --output json --only-show-errors | ConvertFrom-Json $AVDSize = az graph query -q "desktopvirtualizationresources $GraphQueryExtension | summarize count()" --output json --only-show-errors | ConvertFrom-Json @@ -977,10 +977,8 @@ param ($TenantID, $ModuSeq = $ModuSeq0.ReadToEnd() $ModuSeq0.Dispose() } - - $ScriptBlock = [Scriptblock]::Create($ModuSeq) - - $DrawRun = ([PowerShell]::Create()).AddScript($ScriptBlock).AddArgument($($args[1])).AddArgument($($args[2] | ConvertFrom-Json)).AddArgument($($args[3])).AddArgument($($args[4])).AddArgument($($args[5])).AddArgument($($args[6])).AddArgument($($args[7])) + + $DrawRun = ([PowerShell]::Create()).AddScript($ModuSeq).AddArgument($($args[1])).AddArgument($($args[2] | ConvertFrom-Json)).AddArgument($($args[3])).AddArgument($($args[4])).AddArgument($($args[5])).AddArgument($($args[6])).AddArgument($($args[7])) $DrawJob = $DrawRun.BeginInvoke() @@ -1049,9 +1047,7 @@ param ($TenantID, $ModuSeq0.Dispose() } - $ScriptBlock = [Scriptblock]::Create($ModuSeq) - - $SecRun = ([PowerShell]::Create()).AddScript($ScriptBlock).AddArgument($($args[1])).AddArgument($($args[2])).AddArgument($($args[3])) + $SecRun = ([PowerShell]::Create()).AddScript($ModuSeq).AddArgument($($args[1])).AddArgument($($args[2])).AddArgument($($args[3])) $SecJob = $SecRun.BeginInvoke() @@ -1089,9 +1085,7 @@ param ($TenantID, $ModuSeq0.Dispose() } - $ScriptBlock = [Scriptblock]::Create($ModuSeq) - - $PolRun = ([PowerShell]::Create()).AddScript($ScriptBlock).AddArgument($($args[1])).AddArgument($($args[2])).AddArgument($($args[3])).AddArgument($($args[4])) + $PolRun = ([PowerShell]::Create()).AddScript($ModuSeq).AddArgument($($args[1])).AddArgument($($args[2])).AddArgument($($args[3])).AddArgument($($args[4])) $PolJob = $PolRun.BeginInvoke() @@ -1129,9 +1123,7 @@ param ($TenantID, $ModuSeq0.Dispose() } - $ScriptBlock = [Scriptblock]::Create($ModuSeq) - - $AdvRun = ([PowerShell]::Create()).AddScript($ScriptBlock).AddArgument($($args[1])).AddArgument($($args[2])).AddArgument($($args[3])) + $AdvRun = ([PowerShell]::Create()).AddScript($ModuSeq).AddArgument($($args[1])).AddArgument($($args[2])).AddArgument($($args[3])) $AdvJob = $AdvRun.BeginInvoke() @@ -1167,9 +1159,7 @@ param ($TenantID, $ModuSeq0.Dispose() } - $ScriptBlock = [Scriptblock]::Create($ModuSeq) - - $SubRun = ([PowerShell]::Create()).AddScript($ScriptBlock).AddArgument($($args[1])).AddArgument($($args[2])).AddArgument($($args[3])).AddArgument($($args[4])) + $SubRun = ([PowerShell]::Create()).AddScript($ModuSeq).AddArgument($($args[1])).AddArgument($($args[2])).AddArgument($($args[3])).AddArgument($($args[4])) $SubJob = $SubRun.BeginInvoke() @@ -1234,12 +1224,10 @@ param ($TenantID, $ModuSeq0.Dispose() } - $ScriptBlock = [Scriptblock]::Create($ModuSeq) - New-Variable -Name ('ModRun' + $ModName) New-Variable -Name ('ModJob' + $ModName) - Set-Variable -Name ('ModRun' + $ModName) -Value ([PowerShell]::Create()).AddScript($ScriptBlock).AddArgument($($args[1])).AddArgument($($args[2])).AddArgument($($args[3])).AddArgument($($args[4] | ConvertFrom-Json)).AddArgument($($args[5])).AddArgument($null).AddArgument($null).AddArgument($null).AddArgument($($args[12])) + Set-Variable -Name ('ModRun' + $ModName) -Value ([PowerShell]::Create()).AddScript($ModuSeq).AddArgument($($args[1])).AddArgument($($args[2])).AddArgument($($args[3])).AddArgument($($args[4] | ConvertFrom-Json)).AddArgument($($args[5])).AddArgument($null).AddArgument($null).AddArgument($null).AddArgument($($args[12])) Set-Variable -Name ('ModJob' + $ModName) -Value ((get-variable -name ('ModRun' + $ModName)).Value).BeginInvoke() @@ -1330,12 +1318,10 @@ param ($TenantID, $ModuSeq0.Dispose() } - $ScriptBlock = [Scriptblock]::Create($ModuSeq) - New-Variable -Name ('ModRun' + $ModName) New-Variable -Name ('ModJob' + $ModName) - Set-Variable -Name ('ModRun' + $ModName) -Value ([PowerShell]::Create()).AddScript($ScriptBlock).AddArgument($($args[1])).AddArgument($($args[2])).AddArgument($($args[3])).AddArgument($($args[4] | ConvertFrom-Json)).AddArgument($($args[5])).AddArgument($null).AddArgument($null).AddArgument($null).AddArgument($($args[12])) + Set-Variable -Name ('ModRun' + $ModName) -Value ([PowerShell]::Create()).AddScript($ModuSeq).AddArgument($($args[1])).AddArgument($($args[2])).AddArgument($($args[3])).AddArgument($($args[4] | ConvertFrom-Json)).AddArgument($($args[5])).AddArgument($null).AddArgument($null).AddArgument($null).AddArgument($($args[12])) Set-Variable -Name ('ModJob' + $ModName) -Value ((get-variable -name ('ModRun' + $ModName)).Value).BeginInvoke() @@ -1456,9 +1442,7 @@ param ($TenantID, Write-Debug "Running Module: '$Module'" - $ScriptBlock = [Scriptblock]::Create($ModuSeq) - - $ExcelRun = ([PowerShell]::Create()).AddScript($ScriptBlock).AddArgument($PSScriptRoot).AddArgument($null).AddArgument($InTag).AddArgument($null).AddArgument('Reporting').AddArgument($file).AddArgument($SmaResources).AddArgument($TableStyle).AddArgument($Unsupported) + $ExcelRun = ([PowerShell]::Create()).AddScript($ModuSeq).AddArgument($PSScriptRoot).AddArgument($null).AddArgument($InTag).AddArgument($null).AddArgument('Reporting').AddArgument($file).AddArgument($SmaResources).AddArgument($TableStyle).AddArgument($Unsupported) $ExcelJob = $ExcelRun.BeginInvoke() @@ -1506,9 +1490,7 @@ param ($TenantID, $ModuSeq0.Dispose() } - $ScriptBlock = [Scriptblock]::Create($ModuSeq) - - $QuotaRun = ([PowerShell]::Create()).AddScript($ScriptBlock).AddArgument($File).AddArgument($Global:AzQuota).AddArgument($TableStyle) + $QuotaRun = ([PowerShell]::Create()).AddScript($ModuSeq).AddArgument($File).AddArgument($Global:AzQuota).AddArgument($TableStyle) $QuotaJob = $QuotaRun.BeginInvoke() @@ -1559,9 +1541,7 @@ param ($TenantID, $ModuSeq0.Dispose() } - $ScriptBlock = [Scriptblock]::Create($ModuSeq) - - $SecExcelRun = ([PowerShell]::Create()).AddScript($ScriptBlock).AddArgument($null).AddArgument($null).AddArgument('Reporting').AddArgument($file).AddArgument($Sec).AddArgument($TableStyle) + $SecExcelRun = ([PowerShell]::Create()).AddScript($ModuSeq).AddArgument($null).AddArgument($null).AddArgument('Reporting').AddArgument($file).AddArgument($Sec).AddArgument($TableStyle) $SecExcelJob = $SecExcelRun.BeginInvoke() @@ -1610,9 +1590,7 @@ param ($TenantID, $ModuSeq0.Dispose() } - $ScriptBlock = [Scriptblock]::Create($ModuSeq) - - $PolExcelRun = ([PowerShell]::Create()).AddScript($ScriptBlock).AddArgument($null).AddArgument('Reporting').AddArgument($null).AddArgument($file).AddArgument($Pol).AddArgument($TableStyle) + $PolExcelRun = ([PowerShell]::Create()).AddScript($ModuSeq).AddArgument($null).AddArgument('Reporting').AddArgument($null).AddArgument($file).AddArgument($Pol).AddArgument($TableStyle) $PolExcelJob = $PolExcelRun.BeginInvoke() @@ -1661,9 +1639,7 @@ param ($TenantID, $ModuSeq0.Dispose() } - $ScriptBlock = [Scriptblock]::Create($ModuSeq) - - $AdvExcelRun = ([PowerShell]::Create()).AddScript($ScriptBlock).AddArgument($null).AddArgument('Reporting').AddArgument($file).AddArgument($Adv).AddArgument($TableStyle) + $AdvExcelRun = ([PowerShell]::Create()).AddScript($ModuSeq).AddArgument($null).AddArgument('Reporting').AddArgument($file).AddArgument($Adv).AddArgument($TableStyle) $AdvExcelJob = $AdvExcelRun.BeginInvoke() @@ -1699,9 +1675,7 @@ param ($TenantID, $ModuSeq0.Dispose() } - $ScriptBlock = [Scriptblock]::Create($ModuSeq) - - $SubsRun = ([PowerShell]::Create()).AddScript($ScriptBlock).AddArgument($null).AddArgument($null).AddArgument('Reporting').AddArgument($file).AddArgument($AzSubs).AddArgument($TableStyle) + $SubsRun = ([PowerShell]::Create()).AddScript($ModuSeq).AddArgument($null).AddArgument($null).AddArgument('Reporting').AddArgument($file).AddArgument($AzSubs).AddArgument($TableStyle) $SubsJob = $SubsRun.BeginInvoke() @@ -1740,11 +1714,9 @@ param ($TenantID, } - $ScriptBlock = [Scriptblock]::Create($ModuSeq) - Write-Progress -activity 'Azure Resource Inventory Reporting Charts' -Status "15% Complete." -PercentComplete 15 -CurrentOperation "Invoking Excel Chart's Thread." - $ChartsRun = ([PowerShell]::Create()).AddScript($ScriptBlock).AddArgument($file).AddArgument($TableStyle).AddArgument($Global:PlatOS).AddArgument($Global:Subscriptions).AddArgument($Global:Resources.Count).AddArgument($ExtractionRunTime).AddArgument($ReportingRunTime).AddArgument($RunLite) + $ChartsRun = ([PowerShell]::Create()).AddScript($ModuSeq).AddArgument($file).AddArgument($TableStyle).AddArgument($Global:PlatOS).AddArgument($Global:Subscriptions).AddArgument($Global:Resources.Count).AddArgument($ExtractionRunTime).AddArgument($ReportingRunTime).AddArgument($RunLite) $ChartsJob = $ChartsRun.BeginInvoke() @@ -1823,5 +1795,4 @@ if(!$SkipDiagram.IsPresent) Write-Host ('Draw.io Diagram file saved at: ') -NoNewline write-host $DDFile -ForegroundColor Cyan Write-Host '' - } - + } \ No newline at end of file diff --git a/Modules/Compute/VM.ps1 b/Modules/Compute/VM.ps1 index be33597..c782ef7 100644 --- a/Modules/Compute/VM.ps1 +++ b/Modules/Compute/VM.ps1 @@ -13,7 +13,7 @@ https://github.com/microsoft/ARI/Modules/Compute/VM.ps1 This powershell Module is part of Azure Resource Inventory (ARI) .NOTES -Version: 3.1.2 +Version: 3.1.3 First Release Date: 19th November, 2020 Authors: Claudio Merola and Renato Gregio @@ -160,25 +160,6 @@ If ($Task -eq 'Processing') $VNET = $vmnic.properties.ipConfigurations.properties.subnet.id.split('/')[8] $Subnet = $vmnic.properties.ipConfigurations.properties.subnet.id.split('/')[10] - $Relibility = '' - #Low Level Issues - if(![string]::IsNullOrEmpty($data.extended.instanceView) -and $data.extended.instanceView.replicationStat -ne 'Replicating'){$Relibility = 'VM-4'} - if($data.storageProfile.dataDisks.count -lt 1){$Relibility = 'VM-6'} - if(![string]::IsNullOrEmpty($vmnic.properties.dnsSettings.dnsServers)){$Relibility = 'VM-15'} - if([string]::IsNullOrEmpty($Azinsights)){$Relibility = 'VM-20'} - - #Medium Level Issues - if([string]::IsNullOrEmpty($data.backupProfile)){$Relibility = 'VM-7'} - if($vmnic.properties.enableAcceleratedNetworking -ne $true){$Relibility = 'VM-10'} - if(![string]::IsNullOrEmpty($vmnsg)){$Relibility = 'VM-13'} - if(![string]::IsNullOrEmpty($PIP)){$Relibility = 'VM-12'} - if($vmnic.properties.enableIPForwarding -ne $true){$Relibility = 'VM-14'} - - #High Level Issues - if([string]::IsNullOrEmpty($data.availabilitySetReference) -and [string]::IsNullOrEmpty($data.hardwareProfile.zone)){$Relibility = 'VM-1'} - if([string]::IsNullOrEmpty($1.zones)){$Relibility = 'VM-2'} - if([string]::IsNullOrEmpty($data.storageProfile.osDisk.managedDisk.id)){$Relibility = 'VM-5'} - foreach ($Tag in $Tags) @@ -191,7 +172,6 @@ If ($Task -eq 'Processing') 'Location' = $1.LOCATION; 'Zone' = [string]$1.ZONES; 'Availability Set' = $AVSET; - 'Reliability' = $Relibility; 'VM Size' = $data.hardwareProfile.vmSize; 'vCPUs' = $vmsizemap[$data.hardwareProfile.vmSize].CPU; 'RAM (GiB)' = $vmsizemap[$data.hardwareProfile.vmSize].RAM; @@ -248,26 +228,24 @@ else $StyleExt = New-ExcelStyle -HorizontalAlignment Left -Range AK:AK -Width 60 -WrapText $cond = @() - #Reliability - $cond += New-ConditionalText VM -Range I:I #Hybrid Benefit - $cond += New-ConditionalText None -Range P:P + $cond += New-ConditionalText None -Range O:O #NSG - $cond += New-ConditionalText None -Range AF:AF + $cond += New-ConditionalText None -Range AE:AE #Boot Diagnostics + $cond += New-ConditionalText falso -Range R:R + $cond += New-ConditionalText false -Range R:R + #Performance Agent $cond += New-ConditionalText falso -Range S:S $cond += New-ConditionalText false -Range S:S - #Performance Agent + #Azure Monitor $cond += New-ConditionalText falso -Range T:T $cond += New-ConditionalText false -Range T:T - #Azure Monitor - $cond += New-ConditionalText falso -Range U:U - $cond += New-ConditionalText false -Range U:U #Acelerated Network - $cond += New-ConditionalText false -Range AH:AH - $cond += New-ConditionalText falso -Range AH:AH + $cond += New-ConditionalText false -Range AG:AG + $cond += New-ConditionalText falso -Range AG:AG #Retirement - $cond += New-ConditionalText - -Range N:N -ConditionalType ContainsText + $cond += New-ConditionalText - -Range M:M -ConditionalType ContainsText $Exc = New-Object System.Collections.Generic.List[System.Object] $Exc.Add('Subscription') @@ -278,7 +256,6 @@ else $Exc.Add('RAM (GiB)') $Exc.Add('Location') $Exc.Add('OS Type') - $Exc.Add('Reliability') $Exc.Add('OS Name') $Exc.Add('OS Version') $Exc.Add('Image Reference') @@ -325,20 +302,18 @@ else $excel = Open-ExcelPackage -Path $File -KillExcel - $null = $excel.'Virtual Machines'.Cells["N1"].AddComment("It's important to be aware of upcoming Azure services and feature retirements to understand their impact on your workloads and plan migration.", "Azure Resource Inventory") - $excel.'Virtual Machines'.Cells["N1"].Hyperlink = 'https://learn.microsoft.com/en-us/azure/advisor/advisor-how-to-plan-migration-workloads-service-retirement' - $null = $excel.'Virtual Machines'.Cells["S1"].AddComment("Boot diagnostics is a debugging feature for Azure virtual machines (VM) that allows diagnosis of VM boot failures.", "Azure Resource Inventory") - $excel.'Virtual Machines'.Cells["S1"].Hyperlink = 'https://docs.microsoft.com/en-us/azure/virtual-machines/boot-diagnostics' - $null = $excel.'Virtual Machines'.Cells["I1"].AddComment("This column is for specific reliability recommendations for Virtual Machines, as well as detailed information on VM regional resiliency with availability zones and cross-region resiliency with disaster recovery.", "Azure Resource Inventory") - $excel.'Virtual Machines'.Cells["I1"].Hyperlink = 'https://learn.microsoft.com/en-us/azure/virtual-machines/reliability-virtual-machines' - $null = $excel.'Virtual Machines'.Cells["T1"].AddComment("Is recommended to install Performance Diagnostics Agent in every Azure Virtual Machine upfront. The agent is only used when triggered by the console and may save time in an event of performance struggling.", "Azure Resource Inventory") - $excel.'Virtual Machines'.Cells["T1"].Hyperlink = 'https://docs.microsoft.com/en-us/azure/virtual-machines/troubleshooting/performance-diagnostics' - $null = $excel.'Virtual Machines'.Cells["U1"].AddComment("We recommend that you use Azure Monitor to gain visibility into your resource's health.", "Azure Resource Inventory") - $excel.'Virtual Machines'.Cells["U1"].Hyperlink = 'https://docs.microsoft.com/en-us/azure/security/fundamentals/iaas#monitor-vm-performance' - $null = $excel.'Virtual Machines'.Cells["AF1"].AddComment("Use a network security group to protect against unsolicited traffic into Azure subnets. Network security groups are simple, stateful packet inspection devices that use the 5-tuple approach (source IP, source port, destination IP, destination port, and layer 4 protocol) to create allow/deny rules for network traffic.", "Azure Resource Inventory") - $excel.'Virtual Machines'.Cells["AF1"].Hyperlink = 'https://docs.microsoft.com/en-us/azure/security/fundamentals/network-best-practices#logically-segment-subnets' - $null = $excel.'Virtual Machines'.Cells["AH1"].AddComment("Accelerated networking enables single root I/O virtualization (SR-IOV) to a VM, greatly improving its networking performance. This high-performance path bypasses the host from the datapath, reducing latency, jitter, and CPU utilization.", "Azure Resource Inventory") - $excel.'Virtual Machines'.Cells["AH1"].Hyperlink = 'https://docs.microsoft.com/en-us/azure/virtual-network/create-vm-accelerated-networking-cli' + $null = $excel.'Virtual Machines'.Cells["M1"].AddComment("It's important to be aware of upcoming Azure services and feature retirements to understand their impact on your workloads and plan migration.", "Azure Resource Inventory") + $excel.'Virtual Machines'.Cells["M1"].Hyperlink = 'https://learn.microsoft.com/en-us/azure/advisor/advisor-how-to-plan-migration-workloads-service-retirement' + $null = $excel.'Virtual Machines'.Cells["R1"].AddComment("Boot diagnostics is a debugging feature for Azure virtual machines (VM) that allows diagnosis of VM boot failures.", "Azure Resource Inventory") + $excel.'Virtual Machines'.Cells["R1"].Hyperlink = 'https://docs.microsoft.com/en-us/azure/virtual-machines/boot-diagnostics' + $null = $excel.'Virtual Machines'.Cells["S1"].AddComment("Is recommended to install Performance Diagnostics Agent in every Azure Virtual Machine upfront. The agent is only used when triggered by the console and may save time in an event of performance struggling.", "Azure Resource Inventory") + $excel.'Virtual Machines'.Cells["S1"].Hyperlink = 'https://docs.microsoft.com/en-us/azure/virtual-machines/troubleshooting/performance-diagnostics' + $null = $excel.'Virtual Machines'.Cells["T1"].AddComment("We recommend that you use Azure Monitor to gain visibility into your resource's health.", "Azure Resource Inventory") + $excel.'Virtual Machines'.Cells["T1"].Hyperlink = 'https://docs.microsoft.com/en-us/azure/security/fundamentals/iaas#monitor-vm-performance' + $null = $excel.'Virtual Machines'.Cells["AE1"].AddComment("Use a network security group to protect against unsolicited traffic into Azure subnets. Network security groups are simple, stateful packet inspection devices that use the 5-tuple approach (source IP, source port, destination IP, destination port, and layer 4 protocol) to create allow/deny rules for network traffic.", "Azure Resource Inventory") + $excel.'Virtual Machines'.Cells["AE1"].Hyperlink = 'https://docs.microsoft.com/en-us/azure/security/fundamentals/network-best-practices#logically-segment-subnets' + $null = $excel.'Virtual Machines'.Cells["AG1"].AddComment("Accelerated networking enables single root I/O virtualization (SR-IOV) to a VM, greatly improving its networking performance. This high-performance path bypasses the host from the datapath, reducing latency, jitter, and CPU utilization.", "Azure Resource Inventory") + $excel.'Virtual Machines'.Cells["AG1"].Hyperlink = 'https://docs.microsoft.com/en-us/azure/virtual-network/create-vm-accelerated-networking-cli' Close-ExcelPackage $excel }