Src/Public/Invoke-AsBuiltReport.Microsoft.Azure.ps1
|
function Invoke-AsBuiltReport.Microsoft.Azure { <# .SYNOPSIS PowerShell script to document the configuration of Microsoft Azure in Word/HTML/Text formats .DESCRIPTION Documents the configuration of Microsoft Azure in Word/HTML/Text formats using PScribo. .NOTES Version: 0.2.0 Author: Tim Carman Twitter: @tpcarman Github: @tpcarman Credits: Iain Brighton (@iainbrighton) - PScribo module .EXAMPLE PS C:\> New-AsBuiltReport -Report Microsoft.Azure -Target 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx' -Credential $Credential -Format Html -OutputFolderPath 'C:\Reports' Generates an Azure report in HTML format for the specified tenant using credentials. .EXAMPLE PS C:\> New-AsBuiltReport -Report Microsoft.Azure -Target 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx' -UseInteractiveAuth -Format Word -OutputFolderPath 'C:\Reports' Generates an Azure report in Word format for the specified tenant using interactive authentication (MFA). .EXAMPLE PS C:\> New-AsBuiltReport -Report Microsoft.Azure -Target 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx' -UseInteractiveAuth -Format Html,Word -OutputFolderPath 'C:\Reports' -ReportConfigFilePath 'C:\Config\AsBuiltReport.Microsoft.Azure.json' Generates an Azure report in both HTML and Word formats using a custom configuration file. .EXAMPLE PS C:\> $Token = (Get-AzAccessToken).Token PS C:\> New-AsBuiltReport -Report Microsoft.Azure -Target 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx' -Token $Token -TokenParameters @{AccountId='user@domain.com'} -Format Html -OutputFolderPath 'C:\Reports' Generates an Azure report using token-based authentication. .LINK https://github.com/AsBuiltReport/AsBuiltReport.Microsoft.Azure #> [CmdletBinding()] # Do not remove or add to these parameters param ( [String[]] $Target, [PSCredential] $Credential, [Switch] $UseInteractiveAuth, [String] $Token, [String] $AccountId # Passed via TokenParameters hashtable from Core ) # Check for required modules Get-RequiredModule -Name 'Az' -Version '16.0.0' # Display report module information using Core function Write-ReportModuleInfo -ModuleName 'Microsoft.Azure' # Import Report Configuration $Report = $ReportConfig.Report $Filter = $ReportConfig.Filter $InfoLevel = $ReportConfig.InfoLevel $Options = $ReportConfig.Options $SectionOrder = $Options.SectionOrder $LocalizedData = $reportTranslate.InvokeAsBuiltReportMicrosoftAzure # Used to set values to TitleCase where required $TextInfo = (Get-Culture).TextInfo # Define default section order if not specified in config $DefaultSectionOrder = @( "ApplicationGateway", "StorageAccount", "KeyVault", "LogAnalyticsWorkspace", "DataCollectionRule", "DiagnosticSetting", "LoadBalancer", "ExpressRoute", "VirtualNetworkGateway", "DdosProtectionPlan", "VirtualNetwork", "PublicIpAddress", "NetworkWatcher", "NetworkSecurityGroup", "PrivateEndpoint", "IpGroup", "DnsPrivateResolver", "DnsForwardingRuleset", "PrivateDnsZone", "Bastion", "Policy", "UserAssignedManagedIdentity", "Firewall", "FirewallPolicy", "RouteTable", "VirtualMachine", "AvailabilitySet", "VmScaleSet", "MaintenanceConfiguration", "AutomationAccount", "RecoveryServicesVault", "SiteRecovery", "AsrPolicy", "AsrRecoveryPlan", "AsrNetworkMapping", "DesktopVirtualization", "NetAppFiles" ) # Use custom section order if provided, otherwise use default if (-not $SectionOrder -or $SectionOrder.Count -eq 0) { Write-PScriboMessage -Plugin "Module" -Message $LocalizedData.DefaultOrder $SectionOrder = $DefaultSectionOrder } else { Write-PScriboMessage -Plugin "Module" -Message $LocalizedData.CustomOrder } # Resource type mapping for per-subscription existence checks $ResourceTypeMap = @{ 'ApplicationGateway' = 'Microsoft.Network/applicationGateways' 'AutomationAccount' = 'Microsoft.Automation/automationAccounts' 'AvailabilitySet' = 'Microsoft.Compute/availabilitySets' 'VmScaleSet' = 'Microsoft.Compute/virtualMachineScaleSets' 'MaintenanceConfiguration' = 'Microsoft.Maintenance/maintenanceConfigurations' 'Bastion' = 'Microsoft.Network/bastionHosts' 'DataCollectionRule' = 'Microsoft.Insights/dataCollectionRules' 'DdosProtectionPlan' = 'Microsoft.Network/ddosProtectionPlans' 'DesktopVirtualization' = 'Microsoft.DesktopVirtualization/hostpools' 'DnsPrivateResolver' = 'Microsoft.Network/dnsResolvers' 'DnsForwardingRuleset' = 'Microsoft.Network/dnsForwardingRulesets' 'ExpressRoute' = 'Microsoft.Network/expressRouteCircuits' 'ExpressRouteCircuit' = 'Microsoft.Network/expressRouteCircuits' 'Firewall' = 'Microsoft.Network/azureFirewalls' 'FirewallPolicy' = 'Microsoft.Network/firewallPolicies' 'IpGroup' = 'Microsoft.Network/ipGroups' 'KeyVault' = 'Microsoft.KeyVault/vaults' 'LoadBalancer' = 'Microsoft.Network/loadBalancers' 'LogAnalyticsWorkspace' = 'Microsoft.OperationalInsights/workspaces' 'NetAppFiles' = 'Microsoft.NetApp/netAppAccounts' 'NetworkSecurityGroup' = 'Microsoft.Network/networkSecurityGroups' 'DiagnosticSetting' = $null 'Policy' = $null 'UserAssignedManagedIdentity' = 'Microsoft.ManagedIdentity/userAssignedIdentities' 'PrivateDnsZone' = 'Microsoft.Network/privateDnsZones' 'PrivateEndpoint' = 'Microsoft.Network/privateEndpoints' 'NetworkWatcher' = 'Microsoft.Network/networkWatchers' 'PublicIpAddress' = 'Microsoft.Network/publicIPAddresses' 'RecoveryServicesVault' = 'Microsoft.RecoveryServices/vaults' 'RouteTable' = 'Microsoft.Network/routeTables' 'SiteRecovery' = 'Microsoft.RecoveryServices/vaults' 'AsrPolicy' = 'Microsoft.RecoveryServices/vaults' 'AsrRecoveryPlan' = 'Microsoft.RecoveryServices/vaults' 'AsrNetworkMapping' = 'Microsoft.RecoveryServices/vaults' 'StorageAccount' = 'Microsoft.Storage/storageAccounts' 'VirtualMachine' = 'Microsoft.Compute/virtualMachines' 'VirtualNetwork' = 'Microsoft.Network/virtualNetworks' 'VirtualNetworkGateway' = 'Microsoft.Network/virtualNetworkGateways' } # Function mapping for section names to function names $SectionFunctionMap = @{ "ApplicationGateway" = "Get-AbrAzApplicationGateway" "AutomationAccount" = "Get-AbrAzAutomationAccount" "AvailabilitySet" = "Get-AbrAzAvailabilitySet" "VmScaleSet" = "Get-AbrAzVmScaleSet" "MaintenanceConfiguration" = "Get-AbrAzMaintenanceConfiguration" "Bastion" = "Get-AbrAzBastion" "DataCollectionRule" = "Get-AbrAzDataCollectionRule" "DdosProtectionPlan" = "Get-AbrAzDdosProtectionPlan" "DnsPrivateResolver" = "Get-AbrAzDnsPrivateResolver" "DnsForwardingRuleset" = "Get-AbrAzDnsForwardingRuleset" "ExpressRouteCircuit" = "Get-AbrAzExpressRouteCircuit" "ExpressRoute" = "Get-AbrAzExpressRouteCircuit" # Alias for backward compatibility "Firewall" = "Get-AbrAzFirewall" "FirewallPolicy" = "Get-AbrAzFirewallPolicy" "IpGroup" = "Get-AbrAzIpGroup" "KeyVault" = "Get-AbrAzKeyVault" "LogAnalyticsWorkspace" = "Get-AbrAzLogAnalyticsWorkspace" "LoadBalancer" = "Get-AbrAzLoadBalancer" "VirtualNetwork" = "Get-AbrAzVirtualNetwork" "NetworkSecurityGroup" = "Get-AbrAzNetworkSecurityGroup" "DiagnosticSetting" = "Get-AbrAzDiagnosticSetting" "Policy" = "Get-AbrAzPolicy" "UserAssignedManagedIdentity" = "Get-AbrAzUserAssignedManagedIdentity" "RouteTable" = "Get-AbrAzRouteTable" "NetworkVirtualAppliance" = "Get-AbrAzNetworkVirtualAppliance" "VirtualMachine" = "Get-AbrAzVirtualMachine" "VirtualNetworkGateway" = "Get-AbrAzVirtualNetworkGateway" "RecoveryServicesVault" = "Get-AbrAzRecoveryServicesVault" "SiteRecovery" = "Get-AbrAsrProtectedItems" "AsrPolicy" = "Get-AbrAzAsrPolicy" "AsrRecoveryPlan" = "Get-AbrAzAsrRecoveryPlan" "AsrNetworkMapping" = "Get-AbrAzAsrNetworkMapping" "StorageAccount" = "Get-AbrAzStorageAccount" "PrivateDnsZone" = "Get-AbrAzPrivateDnsZone" "PrivateEndpoint" = "Get-AbrAzPrivateEndpoint" "NetworkWatcher" = "Get-AbrAzNetworkWatcher" "PublicIpAddress" = "Get-AbrAzPublicIpAddress" "DesktopVirtualization" = "Get-AbrAzDesktopVirtualization" "NetAppFiles" = "Get-AbrAzNetAppFiles" } #region foreach loop foreach ($TenantId in $Target) { try { Write-PScriboMessage -Plugin "Module" -Message ($LocalizedData.Connecting -f $TenantId) if ($UseInteractiveAuth -or (-not $Token -and -not $Credential)) { # Use interactive auth if explicitly requested OR if no other auth method provided Clear-AzContext -Scope Process -Force -ErrorAction SilentlyContinue $AzAccount = Connect-AzAccount -TenantId $TenantId -ErrorAction Stop } elseif ($Token) { # Validate AccountId is provided via TokenParameters if (-not $AccountId) { Write-Error ($LocalizedData.TokenAccountIdRequired -f 'New-AsBuiltReport', 'TokenParameters') throw "Azure token authentication requires AccountId. Please use: -TokenParameters @{AccountId='user@domain.com'}" } Write-PScriboMessage -Plugin "Module" -Message ($LocalizedData.ConnectingWithToken -f $AccountId, $TenantId) $AzAccount = Connect-AzAccount -TenantId $TenantId -AccessToken $Token -AccountId $AccountId -ErrorAction Stop } else { Clear-AzContext -Scope Process -Force -ErrorAction SilentlyContinue $AzAccount = Connect-AzAccount -Credential $Credential -TenantId $TenantId -ErrorAction Stop } } catch { Write-Error $_ } if ($AzAccount) { try { $AzTenant = Get-AzTenant -TenantId $TenantId -ErrorAction Stop $AzLocations = Get-AzLocation -ErrorAction Stop } catch { Write-PScriboMessage -IsWarning ($LocalizedData.AzureApiError -f $_.Exception.Message) continue } $AzLocationLookup = @{} foreach ($AzLocation in $AzLocations) { $AzLocationLookup.($AzLocation.Location) = $AzLocation.DisplayName } if ($AzTenant) { # Create a Lookup Hashtable for all Azure Subscriptions try { $AzSubscriptions = Get-AzSubscription -TenantId $TenantId -ErrorAction Stop | Sort-Object Name } catch { Write-PScriboMessage -IsWarning ($LocalizedData.AzureApiError -f $_.Exception.Message) continue } $AzSubscriptionLookup = @{} foreach ($AzSubscription in $AzSubscriptions) { $AzSubscriptionLookup.($AzSubscription.SubscriptionId) = $AzSubscription.Name } # Filter Subscriptions if ($Filter.Subscription -ne "*") { try { $AzSubscriptions = foreach ($AzSubscription in $Filter.Subscription) { Get-AzSubscription -TenantId $TenantId -SubscriptionId $AzSubscription -ErrorAction Stop | Sort-Object Name } } catch { Write-PScriboMessage -IsWarning ($LocalizedData.AzureApiError -f $_.Exception.Message) continue } } Section -Style Heading1 $($AzTenant.Name) { Get-AbrAzTenant Get-AbrAzManagementGroup Section -Style Heading2 $LocalizedData.Subscriptions { Get-AbrAzSubscription if (Test-AbrAzInfoLevelEnabled -SectionOrder $SectionOrder -InfoLevel $InfoLevel) { foreach ($AzSubscription in $AzSubscriptions) { Write-PScriboMessage ($LocalizedData.SubscriptionID -f $($AzSubscription.Id)) $AzContext = Set-AzContext -Subscription $AzSubscription.Id -Tenant $TenantId $SubHasResources = $false foreach ($SectionName in ($SectionOrder | Where-Object { Test-AbrAzInfoLevelEnabled -SectionOrder @($_) -InfoLevel $InfoLevel })) { if (-not $ResourceTypeMap.ContainsKey($SectionName)) { $SubHasResources = $true break } $ResourceType = $ResourceTypeMap[$SectionName] if ($null -eq $ResourceType) { continue } if (Get-AzResource -ResourceType $ResourceType -ErrorAction SilentlyContinue | Select-Object -First 1) { $SubHasResources = $true break } } if ($SubHasResources) { Section -Style Heading3 $($AzSubscription.Name) { # Process sections in the order specified by SectionOrder foreach ($SectionName in $SectionOrder) { try { # Get the info level for this section $level = if ($InfoLevel.PSObject.Properties.Name -contains $SectionName) { $InfoLevel.$SectionName } elseif ($InfoLevel.ContainsKey($SectionName)) { $InfoLevel[$SectionName] } else { Write-PScriboMessage ($LocalizedData.InfoLevelNotFound -f $SectionName) continue } # Determine if section is enabled and execute function $enabled = switch ($level) { { $_ -is [hashtable] -or $_ -is [PSCustomObject] } { # For complex objects, sum all property values $sum = if ($_ -is [hashtable]) { ($_.Values | Measure-Object -Sum).Sum } else { ($_.PSObject.Properties.Value | ForEach-Object { [int]$_ } | Measure-Object -Sum).Sum } $sum -gt 0 } default { # For simple types (int, int64, string), convert to int and check if > 0 try { [int]$_ -gt 0 } catch { $false } } } if ($enabled) { $functionName = $SectionFunctionMap[$SectionName] if ($functionName -and (Get-Command $functionName -ErrorAction SilentlyContinue)) { & $functionName } else { Write-PScriboMessage ($LocalizedData.FunctionNotFound -f $functionName, $SectionName) } } } catch { Write-PScriboMessage ($LocalizedData.ErrorProcessing -f $SectionName, $_) } } } } } } } } } else { Write-PScriboMessage ($LocalizedData.TenantNotFound -f $TenantId) } Disconnect-AzAccount $AzAccount | Out-Null } } #endregion foreach loop } |