Azure NAT gateways: export subnet associations
Overview
This PowerShell script searches all enabled Azure subscriptions available to the current account, inventories every NAT Gateway, and maps each one to the virtual networks and subnets associated with it.
It exports both:
- NAT Gateways that are attached to subnets.
- NAT Gateways that exist but have no associated subnet.
This is useful when you need to quickly identify:
- Which NAT Gateways are deployed in each subscription.
- Which VNets and subnets are using each NAT Gateway.
- Which NAT Gateways are currently orphaned.
- Which public IPs or public IP prefixes are attached to each NAT Gateway.
What the script does
- Retrieves enabled subscriptions with
Get-AzSubscription. - Switches context into each subscription with
Set-AzContext. - Reads NAT Gateways with
Get-AzNatGateway. - Reads VNets with
Get-AzVirtualNetwork. - Checks each subnet for an associated NAT Gateway.
- Builds a lookup by NAT Gateway resource ID to match subnet associations reliably.
- Exports associated and unassociated NAT Gateways to CSV.
- Prints a summary table in the terminal.
Prerequisites
Install the Azure PowerShell modules if needed:
Install-Module Az -Scope CurrentUserConnect to Azure before running the script:
Connect-AzAccountThe account must have permission to read NAT Gateways, virtual networks, and subnets in the subscriptions being checked.
Output
The CSV is written to:
$HOME/nat-gateway-inventory.csvEach row includes:
SubscriptionNameSubscriptionIdNatGatewayNameNatGatewayResourceGroupNatGatewayLocationNatGatewaySkuNatGatewayIdVNetNameVNetResourceGroupVNetLocationVNetAddressSpaceVNetIdSubnetNameSubnetAddressSpaceSubnetIdPublicIpIdsPublicIpPrefixIds
For NAT Gateways with no subnet association, the script writes NO ASSOCIATED SUBNET in SubnetName.
PowerShell script
$OutputFile = "$HOME/nat-gateway-inventory.csv"
$Results = @()
$Subscriptions = Get-AzSubscription |
Where-Object { $_.State -eq "Enabled" }
foreach ($Subscription in $Subscriptions) {
Write-Host ""
Write-Host "Processing subscription: $($Subscription.Name) [$($Subscription.Id)]" -ForegroundColor Cyan
try {
Set-AzContext -SubscriptionId $Subscription.Id -ErrorAction Stop | Out-Null
}
catch {
Write-Warning "Unable to switch to subscription $($Subscription.Name)"
continue
}
try {
$NatGateways = @(Get-AzNatGateway -ErrorAction Stop)
}
catch {
Write-Warning "Unable to retrieve NAT Gateways: $($_.Exception.Message)"
continue
}
if ($NatGateways.Count -eq 0) {
Write-Host " No NAT Gateways found." -ForegroundColor DarkGray
continue
}
Write-Host " NAT Gateways found: $($NatGateways.Count)" -ForegroundColor Green
$NatGatewayLookup = @{}
foreach ($NatGateway in $NatGateways) {
$NormalizedId = $NatGateway.Id.ToLowerInvariant()
$NatGatewayLookup[$NormalizedId] = $NatGateway
Write-Host " $($NatGateway.Name)" -ForegroundColor Yellow
}
try {
$VNets = @(Get-AzVirtualNetwork -ErrorAction Stop)
}
catch {
Write-Warning "Unable to retrieve VNets: $($_.Exception.Message)"
continue
}
$AssociatedNatGatewayIds = @{}
foreach ($VNet in $VNets) {
$VNetAddressSpace = ""
if ($VNet.AddressSpace.AddressPrefixes) {
$VNetAddressSpace = (
$VNet.AddressSpace.AddressPrefixes |
Sort-Object
) -join "; "
}
foreach ($Subnet in $VNet.Subnets) {
$SubnetNatGatewayId = $null
if ($Subnet.NatGateway) {
if ($Subnet.NatGateway.Id) {
$SubnetNatGatewayId = $Subnet.NatGateway.Id
}
elseif ($Subnet.NatGateway -is [string]) {
$SubnetNatGatewayId = $Subnet.NatGateway
}
}
if ([string]::IsNullOrWhiteSpace($SubnetNatGatewayId)) {
continue
}
$NormalizedNatId = $SubnetNatGatewayId.ToLowerInvariant()
if (-not $NatGatewayLookup.ContainsKey($NormalizedNatId)) {
Write-Warning "Subnet NAT Gateway was not found in lookup: $SubnetNatGatewayId"
continue
}
$NatGateway = $NatGatewayLookup[$NormalizedNatId]
$AssociatedNatGatewayIds[$NormalizedNatId] = $true
$SubnetAddressSpace = ""
if ($Subnet.AddressPrefixes -and $Subnet.AddressPrefixes.Count -gt 0) {
$SubnetAddressSpace = (
$Subnet.AddressPrefixes |
Sort-Object
) -join "; "
}
elseif ($Subnet.AddressPrefix) {
$SubnetAddressSpace = $Subnet.AddressPrefix
}
Write-Host " $($NatGateway.Name) -> $($VNet.Name)/$($Subnet.Name)" -ForegroundColor Green
$PublicIpIds = ""
if ($NatGateway.PublicIpAddresses) {
$PublicIpIds = (
$NatGateway.PublicIpAddresses |
ForEach-Object { $_.Id }
) -join "; "
}
$PublicIpPrefixIds = ""
if ($NatGateway.PublicIpPrefixes) {
$PublicIpPrefixIds = (
$NatGateway.PublicIpPrefixes |
ForEach-Object { $_.Id }
) -join "; "
}
$Results += [PSCustomObject]@{
SubscriptionName = $Subscription.Name
SubscriptionId = $Subscription.Id
NatGatewayName = $NatGateway.Name
NatGatewayResourceGroup = $NatGateway.ResourceGroupName
NatGatewayLocation = $NatGateway.Location
NatGatewaySku = $NatGateway.Sku.Name
NatGatewayId = $NatGateway.Id
VNetName = $VNet.Name
VNetResourceGroup = $VNet.ResourceGroupName
VNetLocation = $VNet.Location
VNetAddressSpace = $VNetAddressSpace
VNetId = $VNet.Id
SubnetName = $Subnet.Name
SubnetAddressSpace = $SubnetAddressSpace
SubnetId = $Subnet.Id
PublicIpIds = $PublicIpIds
PublicIpPrefixIds = $PublicIpPrefixIds
}
}
}
foreach ($NatGateway in $NatGateways) {
$NormalizedId = $NatGateway.Id.ToLowerInvariant()
if (-not $AssociatedNatGatewayIds.ContainsKey($NormalizedId)) {
Write-Host " $($NatGateway.Name) -> NO ASSOCIATED SUBNET" -ForegroundColor DarkYellow
$PublicIpIds = ""
if ($NatGateway.PublicIpAddresses) {
$PublicIpIds = (
$NatGateway.PublicIpAddresses |
ForEach-Object { $_.Id }
) -join "; "
}
$PublicIpPrefixIds = ""
if ($NatGateway.PublicIpPrefixes) {
$PublicIpPrefixIds = (
$NatGateway.PublicIpPrefixes |
ForEach-Object { $_.Id }
) -join "; "
}
$Results += [PSCustomObject]@{
SubscriptionName = $Subscription.Name
SubscriptionId = $Subscription.Id
NatGatewayName = $NatGateway.Name
NatGatewayResourceGroup = $NatGateway.ResourceGroupName
NatGatewayLocation = $NatGateway.Location
NatGatewaySku = $NatGateway.Sku.Name
NatGatewayId = $NatGateway.Id
VNetName = ""
VNetResourceGroup = ""
VNetLocation = ""
VNetAddressSpace = ""
VNetId = ""
SubnetName = "NO ASSOCIATED SUBNET"
SubnetAddressSpace = ""
SubnetId = ""
PublicIpIds = $PublicIpIds
PublicIpPrefixIds = $PublicIpPrefixIds
}
}
}
}
if ($Results.Count -gt 0) {
$Results |
Sort-Object SubscriptionName, NatGatewayName, VNetName, SubnetName |
Export-Csv `
-Path $OutputFile `
-NoTypeInformation `
-Encoding UTF8
Write-Host ""
Write-Host "==============================================" -ForegroundColor Green
Write-Host "EXPORT COMPLETE" -ForegroundColor Green
Write-Host "==============================================" -ForegroundColor Green
Write-Host "Rows exported : $($Results.Count)"
Write-Host "File : $OutputFile"
Write-Host ""
$Results |
Sort-Object SubscriptionName, NatGatewayName, VNetName, SubnetName |
Format-Table `
SubscriptionName,
NatGatewayName,
VNetName,
VNetAddressSpace,
SubnetName,
SubnetAddressSpace `
-AutoSize
}
else {
Write-Warning "No NAT Gateway records were found. No CSV was created."
}Notes
- The script matches subnet associations by normalized resource ID, which is safer than matching only by name.
- It records orphaned NAT Gateways explicitly so the export doubles as a cleanup inventory.
- If a subscription context switch or resource query fails, the script warns and continues with the next subscription.
- If you want a reusable variant, move the output path into a parameter.