🖇️ Creating VNet Links to Azure Private DNS Zones

Use Azure PowerShell to Mass Update or Create VNet Links on Azure Private DNS Zones

Summary

A PowerShell script that creates a Virtual Network (VNet) link for each VNet within a subscription and associates it with all the private DNS zones available in a tenant. This solution is recommended for a centralized Azure Private DNS setup.

Result

Create or update a VNet link for an Azure Virtual Network (VNet) on multiple Azure Private DNS Zones.

This example uses one Azure subscription, one Azure Virtual Network, and three Azure Private DNS Zones.

⚠️ Note that this example has been pared down for simplicity.

.\Set-AzPrivateDnsZoneVNetLink.ps1 -ResourceGroupName "rg-PrivateDNSZones" -SubscriptionName "Management" -TenantId "x0000000-x00x-0000-xx00-00xxxxxxx0x0"

Prerequisites

  • Azure PowerShell

  • Azure Subscription

  • Azure Virtual Network

  • At least one Azure Private DNS Zone

Workflow

The below outlines creating the script.

The Script in Detail

The parameters:

  • PrivateDnzZones: This parameter holds an array of Private DNS Zone names. If not specified, the default will be to pull the Private DNS Zone name from the subscription specified in the SubscriptionName parameter.

  • ResourceGroupName (Required): This parameter holds the name of the resource group with the Private DNS Zones.

  • SubscriptionName (Required): This parameter holds the subscription name with the Private DNS Zones.

  • TenantId (Required): This parameter holds the Azure tenant Id.

  • ExcludeSubscription: This parameter holds an array of subscription names to exclude.

  • ExcludePrivateDnsZone: This parameter holds an array of Private DNS Zone names to exclude.

  • ExcludeVNetName: This parameter holds an array of VNet names to exclude.

[CmdletBinding(SupportsShouldProcess)]
param(
    [Parameter(HelpMessage="Enter Private DNS Zone names.")]
    [String[]]
    $PrivateDnsZones,

    [Parameter(Mandatory, HelpMessage="Enter the resource group name where the private dns zones reside.")]
    [String]
    $ResourceGroupName,

    [Parameter(Mandatory, HelpMessage="Enter the subscription name where the private dns zones reside.")]
    [String]
    $SubscriptionName,

    [Parameter(Mandatory, HelpMessage="Enter the Azure tenant Id.")]
    [String]
    $TenantId,

    # Subscriptions to exclude from discovery.
    [Parameter(HelpMessage="Enter subscription names that can be excluded from the subscription name discovery.")]
    [String[]]
    $ExcludeSubscription,

    [Parameter(HelpMessage="Enter Private DNS Zone names that can be excluded from the private dns zone names discovery.")]
    [String[]]
    $ExcludePrivateDnsZone,

    [Parameter(HelpMessage="Enter Virtual Network names that can be excluded.")]
    [String[]]
    $ExcludeVNetName
)

I am using the PowerShell Trap statement to handle all errors within this script. For the Trap statement to work, the -ErrorAction Stop must be added to each PowerShell cmdlet.

# Script error handling.
trap [System.Exception] {
    "An unexpected error has occurred! $($_)"
    break
}

Use Connect-AzAccount to connect to Azure. The Tenant ID is required. This helped in my scenario because I manage multiple tenants, and adding the Tenant ID ensures the script pulls the right credentials for the correct Tenant.

# Login into Azure
Connect-AzAccount -Tenant $TenantId -WarningAction Ignore -ErrorAction Stop | Out-Null

Use Set-AzContext to set the subscription context for the subscription that contains the centralized private DNS zones.

# Set context to the subscription that holds the private dns zones.
Set-AzContext $SubscriptionName -WarningAction Ignore -ErrorAction Stop | Out-Null

Get all Azure Private DNS Zone names.

Write-Verbose "Get all Private DNS Zone names"
if($PrivateDnsZones) {
    $AllPrivateDnsZoneNames = $PrivateDnsZones
} else {
    $AllPrivateDnsZoneNames = $(Get-AzPrivateDnsZone -ErrorAction Stop | Where-Object { $_.Name -notin $ExcludePrivateDnsZone }).Name
}

Get all Azure Subscription names. The Get-AzSubscription gets all of the subscriptions for the specified tenant.

Write-Verbose "Get all subscription names"
$allSubscriptions = Get-AzSubscription -WarningAction Ignore -ErrorAction Stop | Where-Object { $_.Name -notin $ExcludeSubscription }

A subscription could have multiple VNets. Initialize a collection list variable to hold the information for each VNet in the subscription.

$vnetProperties = New-Object System.Collections.Generic.List[PSObject]

Iterate through each subscription to pull and store the VNet information.

This is done by iterating through the $AllSubscriptions array. Within the ForEach loop, the context is set to the current subscription item, and the VNet information is pulled and stored in the $vnetProperties object one VNet at a time.

$allSubscriptions | ForEach-Object {

    $subName = $_.Name

    Set-AzContext $subName -WarningAction Ignore -ErrorAction Stop | Out-Null

    $VNET = Get-AzVirtualNetwork

    if($VNET) {
        $VNET | ForEach-Object {
            if($_.Name -notin $ExcludeVNetName) {
                Write-Verbose "`nAdding entry"
                Write-Verbose "Subscription: $($subName)"
                Write-Verbose "VNet: $($_.Name)"
                Write-Verbose "VNetId: $($_.Id)"

                $vnetProperties.Add(
                [PSCustomObject]@{
                    subscriptionName = $subName
                    vnetName = $_.Name
                    vnetId = $_.Id

                })
            }
        }
    }
}

Change the subscription context to the subscription that holds the centralized Private DNS zones.

Write-Verbose "Context changed to $($SubscriptionName)"
Set-AzContext $SubscriptionName -WarningAction Ignore -ErrorAction Stop | Out-Null

Iterate through the stored VNets. For each VNet, add a link to each Private DNS Zone. If a link already exists, remove it and recreate it.

This is completed by iterating through the $vnetProperties array. Within the ForEach loop, the subscription name, VNet name, and VNet resourceId are stored in a variable.

The $AllPrivateDnsZoneNames are iterated using a ForEach loop. Within this ForEach, if a VNet link to the zone exists, the established link is removed first, then the link is recreated. If a link does not already exist, a link is created.

$vnetProperties | ForEach-Object {
    $sub = $_.subscriptionName
    $Name = $_.vnetName
    $Id = $_.vnetId

    $linkName = "link-to-$($Name)"
    $vNetId = $Id

    $AllPrivateDnsZoneNames | ForEach-Object {

        Write-Host "`nZoneName: $($_)"

        $Existing = Get-AzPrivateDnsVirtualNetworkLink -ResourceGroupName $ResourceGroupName -ZoneName $_  -ErrorAction SilentlyContinue | Where-Object { $_.VirtualNetworkId -eq $vNetId }

        if($Existing) {
            Write-Host "Removing the existing link."
            Remove-AzPrivateDnsVirtualNetworkLink -ResourceId $($Existing).ResourceId -ErrorAction Stop
        }

        Write-Host "Subscription: $($sub)"
        Write-Host "Link Name: $($linkName) & ID: $($vNetId)"
        New-AzPrivateDnsVirtualNetworkLink -ZoneName $_ -ResourceGroupName $ResourceGroupName -Name $linkName -VirtualNetworkId $vNetId -ErrorAction Stop | Out-Null
    }

}

⬇️Get the Script

The entire script can be downloaded from GitHub/jameswassinger

If you enjoyed this article, please show your support.