Export-AzureFirewallRule.ps1
|
<#PSScriptInfo .VERSION 1.0.1 .GUID e7affeab-f70a-45e6-8d40-3d1e4f750812 .AUTHOR Chendrayan Venkatesan .COMPANYNAME Freelancer .COPYRIGHT .TAGS AzureFirewallRule ExportAzureFirewallRule ExportAzureFirewallRuleCSV .LICENSEURI .PROJECTURI .ICONURI .EXTERNALMODULEDEPENDENCIES .REQUIREDSCRIPTS .EXTERNALSCRIPTDEPENDENCIES .RELEASENOTES .PRIVATEDATA #> <# .DESCRIPTION PowerShell Script to export Azure Firewall rules in CSV format #> param( [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [string]$OutputCsvPath, [string[]]$SubscriptionIds, [ValidateRange(1, 1000)] [int]$PageSize = 1000 ) Set-StrictMode -Version Latest $ErrorActionPreference = 'Stop' function Test-RequiredModules { if (-not (Get-Module -ListAvailable -Name Az.Accounts)) { throw "Az.Accounts is not installed. Install-Module Az.Accounts -Scope CurrentUser" } if (-not (Get-Module -ListAvailable -Name Az.ResourceGraph)) { throw "Az.ResourceGraph is not installed. Install-Module Az.ResourceGraph -Scope CurrentUser" } if (-not (Get-Module -ListAvailable -Name Az.Network)) { throw "Az.Network is not installed. Install-Module Az.Network -Scope CurrentUser" } Import-Module Az.Accounts -ErrorAction Stop Import-Module Az.ResourceGraph -ErrorAction Stop Import-Module Az.Network -ErrorAction Stop } function Test-AzLogin { try { $null = Get-AzContext -ErrorAction Stop } catch { throw "No Azure login found. Run Connect-AzAccount first." } } function Test-OutputDirectory { param( [Parameter(Mandatory = $true)] [string]$Path ) $parent = Split-Path -Path $Path -Parent if (-not [string]::IsNullOrWhiteSpace($parent) -and -not (Test-Path -LiteralPath $parent)) { New-Item -Path $parent -ItemType Directory -Force | Out-Null } } function Invoke-AzGraphPagedQuery { param( [Parameter(Mandatory = $true)] [string]$Query, [string[]]$SubscriptionIds, [ValidateRange(1, 1000)] [int]$PageSize = 1000 ) $rows = [System.Collections.Generic.List[object]]::new() $skipToken = $null $page = 0 do { $page++ $params = @{ Query = $Query First = $PageSize } if ($SubscriptionIds -and $SubscriptionIds.Count -gt 0) { $params.Subscription = $SubscriptionIds } if (-not [string]::IsNullOrWhiteSpace($skipToken)) { $params.SkipToken = $skipToken } Write-Host ("Fetching Resource Graph page {0}..." -f $page) -ForegroundColor Cyan $response = Search-AzGraph @params -ErrorAction Stop if ($response.Data) { $rows.AddRange($response.Data) Write-Host (" Retrieved {0} rows" -f $response.Data.Count) -ForegroundColor Green } $skipToken = $response.SkipToken } while (-not [string]::IsNullOrWhiteSpace($skipToken)) return $rows } function ConvertFrom-AzureResourceId { param( [Parameter(Mandatory = $true)] [string]$ResourceId ) $parts = $ResourceId.Trim('/') -split '/' $result = [ordered]@{ SubscriptionId = $null ResourceGroupName = $null ProviderNamespace = $null ResourceType = $null Name = $null } for ($i = 0; $i -lt $parts.Length; $i++) { switch -Regex ($parts[$i].ToLowerInvariant()) { '^subscriptions$' { if ($i + 1 -lt $parts.Length) { $result.SubscriptionId = $parts[$i + 1] } } '^resourcegroups$' { if ($i + 1 -lt $parts.Length) { $result.ResourceGroupName = $parts[$i + 1] } } '^providers$' { if ($i + 1 -lt $parts.Length) { $result.ProviderNamespace = $parts[$i + 1] } if ($i + 2 -lt $parts.Length) { $result.ResourceType = $parts[$i + 2] } if ($i + 3 -lt $parts.Length) { $result.Name = $parts[$i + 3] } } } } [pscustomobject]$result } function Resolve-SourceIpGroupDetails { param( [Parameter(Mandatory = $true)] [string[]]$IpGroupIds ) $cache = @{} foreach ($ipGroupId in ($IpGroupIds | Where-Object { -not [string]::IsNullOrWhiteSpace($_) } | Sort-Object -Unique)) { try { $parsed = ConvertFrom-AzureResourceId -ResourceId $ipGroupId if (-not $parsed.SubscriptionId -or -not $parsed.ResourceGroupName -or -not $parsed.Name) { Write-Warning "Could not parse IP Group resource ID: $ipGroupId" $cache[$ipGroupId.ToLowerInvariant()] = [pscustomobject]@{ SourceIpGroupName = $null SourceIpGroupAddresses = $null } continue } $currentContext = Get-AzContext if (-not $currentContext -or $currentContext.Subscription.Id -ne $parsed.SubscriptionId) { Set-AzContext -SubscriptionId $parsed.SubscriptionId -ErrorAction Stop | Out-Null } $ipGroup = Get-AzIpGroup -ResourceGroupName $parsed.ResourceGroupName -Name $parsed.Name -ErrorAction Stop $addresses = @() if ($ipGroup.IpAddresses) { $addresses = @($ipGroup.IpAddresses) } $cache[$ipGroupId.ToLowerInvariant()] = [pscustomobject]@{ SourceIpGroupName = $ipGroup.Name SourceIpGroupAddresses = ($addresses -join ', ') } } catch { Write-Warning ("Failed to resolve IP Group '{0}': {1}" -f $ipGroupId, $_.Exception.Message) $cache[$ipGroupId.ToLowerInvariant()] = [pscustomobject]@{ SourceIpGroupName = $null SourceIpGroupAddresses = $null } } } return $cache } try { Test-RequiredModules Test-AzLogin Test-OutputDirectory -Path $OutputCsvPath $query = @" networkresources | where type =~ 'microsoft.network/firewallpolicies/rulecollectiongroups' | extend firewallPolicyId = tostring(split(id, '/ruleCollectionGroups/')[0]), ruleCollectionGroupName = name, ruleCollectionGroupPriority = toint(properties.priority), ruleCollections = properties.ruleCollections | mv-expand ruleCollection = ruleCollections | extend ruleCollectionName = tostring(ruleCollection.name), ruleCollectionPriority = toint(ruleCollection.priority), ruleCollectionType = tostring(ruleCollection.ruleCollectionType), actionType = tostring(ruleCollection.action.type), rules = ruleCollection.rules | mv-expand rule = rules | project subscriptionId, resourceGroup, location, firewallPolicyId, ruleCollectionGroupName, ruleCollectionGroupPriority, ruleCollectionName, ruleCollectionPriority, ruleCollectionType, actionType, ruleName = tostring(rule.name), ruleType = tostring(rule.ruleType), sourceAddresses = strcat_array(coalesce(rule.sourceAddresses, dynamic([])), ', '), sourceIpGroups = strcat_array(coalesce(rule.sourceIpGroups, dynamic([])), ', '), sourceIpGroupCount = array_length(coalesce(rule.sourceIpGroups, dynamic([]))), destinationAddresses = strcat_array(coalesce(rule.destinationAddresses, dynamic([])), ', '), destinationIpGroups = strcat_array(coalesce(rule.destinationIpGroups, dynamic([])), ', '), destinationFqdns = strcat_array(coalesce(rule.destinationFqdns, dynamic([])), ', '), targetFqdns = strcat_array(coalesce(rule.targetFqdns, dynamic([])), ', '), fqdnTags = strcat_array(coalesce(rule.fqdnTags, dynamic([])), ', '), ipProtocols = strcat_array(coalesce(rule.ipProtocols, dynamic([])), ', '), protocols = tostring(rule.protocols), destinationPorts = strcat_array(coalesce(rule.destinationPorts, dynamic([])), ', '), translatedAddress = tostring(rule.translatedAddress), translatedPort = tostring(rule.translatedPort), webCategories = strcat_array(coalesce(rule.webCategories, dynamic([])), ', '), terminateTls = tostring(rule.terminateTLS), id | order by firewallPolicyId asc, ruleCollectionGroupPriority asc, ruleCollectionPriority asc, ruleName asc "@ $results = Invoke-AzGraphPagedQuery -Query $query -SubscriptionIds $SubscriptionIds -PageSize $PageSize if (-not $results -or $results.Count -eq 0) { throw "Query completed but returned no rows." } $allIpGroupIds = New-Object System.Collections.Generic.List[string] foreach ($row in $results) { if (-not [string]::IsNullOrWhiteSpace($row.sourceIpGroups)) { $ids = $row.sourceIpGroups -split '\s*,\s*' | Where-Object { -not [string]::IsNullOrWhiteSpace($_) } foreach ($id in $ids) { $allIpGroupIds.Add($id) } } } Write-Host ("Resolving {0} unique source IP Group IDs..." -f (($allIpGroupIds | Sort-Object -Unique).Count)) -ForegroundColor Cyan $ipGroupLookup = Resolve-SourceIpGroupDetails -IpGroupIds $allIpGroupIds $final = foreach ($row in $results) { $resolvedNames = New-Object System.Collections.Generic.List[string] $resolvedAddresses = New-Object System.Collections.Generic.List[string] if (-not [string]::IsNullOrWhiteSpace($row.sourceIpGroups)) { $ids = $row.sourceIpGroups -split '\s*,\s*' | Where-Object { -not [string]::IsNullOrWhiteSpace($_) } foreach ($id in $ids) { $key = $id.ToLowerInvariant() if ($ipGroupLookup.ContainsKey($key)) { $entry = $ipGroupLookup[$key] if (-not [string]::IsNullOrWhiteSpace($entry.SourceIpGroupName)) { $resolvedNames.Add($entry.SourceIpGroupName) } if (-not [string]::IsNullOrWhiteSpace($entry.SourceIpGroupAddresses)) { $resolvedAddresses.Add($entry.SourceIpGroupAddresses) } } } } [pscustomobject]@{ subscriptionId = $row.subscriptionId resourceGroup = $row.resourceGroup location = $row.location firewallPolicyId = $row.firewallPolicyId ruleCollectionGroupName = $row.ruleCollectionGroupName ruleCollectionGroupPriority = $row.ruleCollectionGroupPriority ruleCollectionName = $row.ruleCollectionName ruleCollectionPriority = $row.ruleCollectionPriority ruleCollectionType = $row.ruleCollectionType actionType = $row.actionType ruleName = $row.ruleName ruleType = $row.ruleType sourceAddresses = $row.sourceAddresses sourceIpGroups = $row.sourceIpGroups sourceIpGroupNames = (($resolvedNames | Sort-Object -Unique) -join ', ') sourceIpGroupAddresses = (($resolvedAddresses | Sort-Object -Unique) -join ' | ') sourceIpGroupCount = $row.sourceIpGroupCount destinationAddresses = $row.destinationAddresses destinationIpGroups = $row.destinationIpGroups destinationFqdns = $row.destinationFqdns targetFqdns = $row.targetFqdns fqdnTags = $row.fqdnTags ipProtocols = $row.ipProtocols # protocols = $row.protocols protocols = $(if (-not [string]::IsNullOrWhiteSpace($row.protocols)) { ($row.protocols | ConvertFrom-Json | ForEach-Object { "{0} ({1})" -f $_.protocolType, $_.port }) -join ', ' } else { $null }) destinationPorts = $row.destinationPorts translatedAddress = $row.translatedAddress translatedPort = $row.translatedPort webCategories = $row.webCategories terminateTls = $row.terminateTls id = $row.id } } $final | Export-Csv -LiteralPath $OutputCsvPath -NoTypeInformation -Encoding UTF8 -Force Write-Host ("Exported {0} rows to {1}" -f $final.Count, $OutputCsvPath) -ForegroundColor Green } catch { Write-Error ("Script failed: {0}" -f $_.Exception.Message) exit 1 } |