I am going to show you how to create a Hub-Spoke network configuration with Azure Firewall using PowerShell.
What is a Hub-Spoke network?
Think of the Hub-Spoke as two different networks, network1 is hub, network2 is spoke. Network1 acts as the central point of connectivity and perimeter for your network where all traffic has to enter and leave where it can be monitored before it reaches your network2 which is the spoke.
Why a Hub-Spoke network?
Various reasons let to this type of configuration, key areas as to why:-
- Cost effective:- Having a centralised-hub you will be saving cost on NVA’s such as Firewalls , third-party intrusion applications
- Deployment time:- No need to deploy multiple DNS servers/AD Controllers per vNET having these centralised will save time, money and even admin-overhead
- Azure specific:- Overcome some subscription limits by peering virtual networks from different subscriptions to the central hub
- Security:- Having a central hub where any incoming/out-going traffic is monitored before it has access to any of the spoke virtual networks
The environment setup

The PowerShell deployment will consist of:-
- Resource Group:- To hold all resources within this environment
- Virtual Network:- Hub vNET that will be the central hub for traffic attempting to access VM1 (VM2 is deployed but used as local access only)
- Virtual Network:- Spoke vNET (VM1/VM2 will be deployed to here)
- Azure Firewall:- Create Azure Firewall that will have a DNAT rule for RDP
- Network Security Group:- Created for inter VM traffic (not used in initial deployment)
- Application Security Group:- Created for inter VM traffic (not used in initial deployment)
- Virtual Machine Creation:- VM1 & VM2
- Peer Virtual Networks:- Peer Virtual networks between hub & spoke
- Route Table:- Routing only configured to cater for DNAT from Azure Firewall (additional route tables required if you want to restrict outbound access from VMs using Azure firewall)
PowerShell Variables
# Resource Group Name
$vnetresourcegroup = "tamops-network"
# Hub Virtual Network creation
$hubvnet = "tamopsHub-vNET"
$hubvnet1addressprefix = "10.0.0.0/16"
$hubvnet1subnet = "10.0.0.0/24"
$hubvnet1subnetname = "AzureFirewallSubnet"
# Spoke Virtual Network creation
$spokevnet1 = "tamopsspoke1-vNET"
$spokevnet1addressprefix = "192.168.0.0/24"
$spokevnet1subnet = "192.168.0.0/28"
$spokevnet1subnetname = "vm"
# Create Azure Firewall
$AzFirewallpipName = "tamopsfw-pip"
$AzFirewallName = "tamopsfw"
# Create Network Security Group (NSG)
$AzNsgName = "tamops-nsg"
$AzAsgName = "tamops-asg"
# Virtual Machine (VM) Creation
$tamOpsVMs= @(
@{
VmName = "tamops-vm1"
nicName = "tamops-vm1-nic"
ip = "192.168.0.4"
vmsize = "Standard_B2s"
},
@{
VmName = "tamops-vm2"
nicName = "tamops-vm2-nic"
ip = "192.168.0.5"
vmsize = "Standard_B2s"
}
)
$VMLocalAdminUser = "tamopsadmin"
$VMLocalAdminSecurePassword = ConvertTo-SecureString "ENTERPASSWORDHERE" -AsPlainText -Force
# Create a route table
$AzFirewallRouteTableName = "AzFirewallRouteTable"
PowerShell Deployment Breakdown
Resource Group:- tamops-network being created
# Create Resource Group for Test Environment
New-AzResourcegroup -name $vnetresourcegroup -Location uksouth
Hub Virtual Network:- tamopsHub-vNET deployed with Firewall Subnet
# Create Hub Virtual Network
$HubVirtualNetwork = New-AzVirtualNetwork `
-ResourceGroupName $vnetresourcegroup `
-Location uksouth `
-Name $hubvnet `
-AddressPrefix $hubvnet1addressprefix
# Create Firewall subnet
$subnetadd = Add-AzVirtualNetworkSubnetConfig `
-Name $hubvnet1subnetname `
-AddressPrefix $hubvnet1subnet `
-VirtualNetwork $HubVirtualNetwork
# Assign newly created subnet to Virtual Network
$HubVirtualNetwork | Set-AzVirtualNetwork
Spoke Virtual Network:- tamopsspoke1-vNET deployed with vm subnet
# Create Spoke Virtual Network
$SpokeVirtualNetwork = New-AzVirtualNetwork `
-ResourceGroupName $vnetresourcegroup `
-Location uksouth `
-Name $spokevnet1 `
-AddressPrefix $spokevnet1addressprefix
# Create vm subnet
$subnetadd = Add-AzVirtualNetworkSubnetConfig `
-Name $spokevnet1subnetname `
-AddressPrefix $spokevnet1subnet `
-VirtualNetwork $SpokeVirtualNetwork
# Assign newly created subnet to Virtual Network
$SpokeVirtualNetwork | Set-AzVirtualNetwork
Azure Firewall:- with Public IP
# Public IP Address creation for Azure Firewall
$AzFirewallpip = New-AzPublicIpAddress `
-Name $AzFirewallpipName `
-ResourceGroupName $vnetresourcegroup `
-Location uksouth `
-AllocationMethod Static `
-Sku Standard
#Create Azure Firewall
$AzFirewall = New-AzFirewall `
-Name $AzFirewallName `
-ResourceGroupName $vnetresourcegroup `
-Location uksouth `
-VirtualNetworkName $HubVirtualNetwork.name `
-PublicIpName $AzFirewallpip.name
Network Security Group & Application Security Group
# NSG Creation
$AzNsg = New-AzNetworkSecurityGroup `
-Name $AzNsgName `
-Location uksouth `
-ResourceGroupName $vnetresourcegroup
# ASG Creation
$AzAsg = New-AzApplicationSecurityGroup `
-Name $AzAsgName `
-Location uksouth `
-ResourceGroupName $vnetresourcegroup
Virtual Machine:- Deployed and assigned to vm subnet
# VM Creation
foreach($vm in $tamOpsVMs){
$Subnet = Get-AzVirtualNetwork -name $spokevnet1
$IPconfig = New-AzNetworkInterfaceIpConfig -Name "IPConfig1" -PrivateIpAddressVersion IPv4 -PrivateIpAddress $vm.ip -SubnetId $Subnet.subnets[0].id
$NIC = New-AzNetworkInterface -Name $vm.nicname -ResourceGroupName $vnetresourcegroup -Location uksouth -IpConfiguration $IPconfig
$Credential = New-Object System.Management.Automation.PSCredential ($VMLocalAdminUser, $VMLocalAdminSecurePassword);
$VirtualMachine = New-AzVMConfig -VMName $vm.VmName -VMSize $vm.vmsize
$VirtualMachine = Set-AzVMOperatingSystem -VM $VirtualMachine -Windows -ComputerName $vm.VmName -Credential $Credential -ProvisionVMAgent -EnableAutoUpdate
$VirtualMachine = Add-AzVMNetworkInterface -VM $VirtualMachine -Id $NIC.Id
$VirtualMachine = Set-AzVMSourceImage -VM $VirtualMachine -PublisherName 'MicrosoftWindowsServer' -Offer 'WindowsServer' -Skus '2012-R2-Datacenter' -Version latest
New-AzVM -ResourceGroupName $vnetresourcegroup -Location uksouth -VM $VirtualMachine -Verbose
}
Virtual Network Peerings:- Hub-spoke peering configuration
# Peer Virtual Networks
$HubVirtualNetwork = Get-AzVirtualNetwork -name $hubvnet
$SpokeVirtualNetwork = get-azvirtualnetwork -name $spokevnet1
# Peer Hub to Spoke
Add-AzVirtualNetworkPeering `
-Name "hub2spokepeer" `
-VirtualNetwork $HubVirtualNetwork `
-RemoteVirtualNetworkId $SpokeVirtualNetwork.Id
# Peer Spoke to Hub
Add-AzVirtualNetworkPeering `
-Name "spoke2hubpeer" `
-VirtualNetwork $SpokeVirtualNetwork `
-RemoteVirtualNetworkId $HubVirtualNetwork.Id
Route Table:- Creation and configured default route to be assigned to vm subnet
# Route Table creation
$AzFirewallRouteTable = New-AzRouteTable `
-Name $AzFirewallRouteTableName `
-ResourceGroupName $vnetresourcegroup `
-location uksouth
# Create a route (default route)
Get-AzRouteTable `
-ResourceGroupName $vnetresourcegroup `
-Name $AzFirewallRouteTableName `
| Add-AzRouteConfig `
-Name "Firewall-DR" `
-AddressPrefix 0.0.0.0/0 `
-NextHopType "VirtualAppliance" `
-NextHopIpAddress $AzFirewall.IpConfigurations.privateipaddress `
| Set-AzRouteTable
# Associate the route table to the VM subnet
Set-AzVirtualNetworkSubnetConfig `
-VirtualNetwork $SpokeVirtualNetwork `
-Name $spokevnet1subnetname `
-AddressPrefix $spokevnet1subnet `
-RouteTable $AzFirewallRouteTable | `
Set-AzVirtualNetwork
Azure Firewall NAT Rule:- To allow RDP from Firewall Public IP to tamops-vm1
# Create NAT Rule Collection for RDP on Azure Firewall
$AzFirewallPublicIP = Get-AzPublicIpAddress -name $AzFirewallpipName
$natrulerdp = `
New-AzFirewallNatRule `
-Name "rdp" `
-Protocol "TCP" `
-SourceAddress "*" `
-DestinationAddress $AzFirewallPublicIP.IpAddress `
-DestinationPort "3389" `
-TranslatedAddress $tamOpsVMs.ip[0] `
-TranslatedPort "3389"
$rdpcollectionrule = `
New-AzFirewallNatRuleCollection `
-Name "MyNatRuleCollection" `
-Priority 100 `
-Rule $natrulerdp
$AzFirewall.NatRuleCollections = $rdpcollectionrule
$AzFirewall | Set-AzFirewall
Network Security Group:- NSG rule creation to allow RDP from Azure Firewall and assign NSG to both VMs including ASG
# NSG Rule and assignment to VMs
$rdpallow = New-AzNetworkSecurityRuleConfig `
-Name RDP-Allow-AzFW `
-Access Allow `
-DestinationAddressPrefix $tamOpsVMs.ip[0] `
-DestinationPortRange 3389 `
-Direction Inbound `
-Priority 100 `
-Protocol tcp `
-SourceAddressPrefix $AzFirewallPublicIP.IpAddress `
-SourcePortRange *
$AzNsg.SecurityRules = $rdpallow
$AzNsg | Set-AzNetworkSecurityGroup
# Assign ASG & NSG to VMs
$Vms = Get-AzVM -ResourceGroupName $vnetresourcegroup
$AsgName = $AzAsgName
$NsgName = $AzNsgName
foreach ($vm in $vms) {
$nic = Get-AzNetworkInterface -ResourceId $Vm.NetworkProfile.NetworkInterfaces.id
$Asg = Get-AzApplicationSecurityGroup -Name $AsgName
$Nsg = Get-AzNetworkSecurityGroup -name $NsgName
$nic.IpConfigurations[0].ApplicationSecurityGroups = $Asg
$nic.NetworkSecurityGroup = $Nsg
$nic | Set-AzNetworkInterface
}
PowerShell Full Script
# Resource Group Name
$vnetresourcegroup = "tamops-network"
# Hub Virtual Network creation
$hubvnet = "tamopsHub-vNET"
$hubvnet1addressprefix = "10.0.0.0/16"
$hubvnet1subnet = "10.0.0.0/24"
$hubvnet1subnetname = "AzureFirewallSubnet"
# Spoke Virtual Network creation
$spokevnet1 = "tamopsspoke1-vNET"
$spokevnet1addressprefix = "192.168.0.0/24"
$spokevnet1subnet = "192.168.0.0/28"
$spokevnet1subnetname = "vm"
# Create Azure Firewall
$AzFirewallpipName = "tamopsfw-pip"
$AzFirewallName = "tamopsfw"
# Create Network Security Group (NSG)
$AzNsgName = "tamops-nsg"
$AzAsgName = "tamops-asg"
# Virtual Machine (VM) Creation
$tamOpsVMs= @(
@{
VmName = "tamops-vm1"
nicName = "tamops-vm1-nic"
ip = "192.168.0.4"
vmsize = "Standard_B2s"
},
@{
VmName = "tamops-vm2"
nicName = "tamops-vm2-nic"
ip = "192.168.0.5"
vmsize = "Standard_B2s"
}
)
$VMLocalAdminUser = "tamopsadmin"
$VMLocalAdminSecurePassword = ConvertTo-SecureString "ENTERPASSWORDHERE" -AsPlainText -Force
# Create a route table
$AzFirewallRouteTableName = "AzFirewallRouteTable"
# Create Resource Group for Test Environment
New-AzResourcegroup -name $vnetresourcegroup -Location uksouth
# Create Hub Virtual Network
$HubVirtualNetwork = New-AzVirtualNetwork `
-ResourceGroupName $vnetresourcegroup `
-Location uksouth `
-Name $hubvnet `
-AddressPrefix $hubvnet1addressprefix
# Create Firewall subnet
$subnetadd = Add-AzVirtualNetworkSubnetConfig `
-Name $hubvnet1subnetname `
-AddressPrefix $hubvnet1subnet `
-VirtualNetwork $HubVirtualNetwork
# Assign newly created subnet to Virtual Network
$HubVirtualNetwork | Set-AzVirtualNetwork
# Create Spoke Virtual Network
$SpokeVirtualNetwork = New-AzVirtualNetwork `
-ResourceGroupName $vnetresourcegroup `
-Location uksouth `
-Name $spokevnet1 `
-AddressPrefix $spokevnet1addressprefix
# Create vm subnet
$subnetadd = Add-AzVirtualNetworkSubnetConfig `
-Name $spokevnet1subnetname `
-AddressPrefix $spokevnet1subnet `
-VirtualNetwork $SpokeVirtualNetwork
# Assign newly created subnet to Virtual Network
$SpokeVirtualNetwork | Set-AzVirtualNetwork
# Public IP Address creation for Azure Firewall
$AzFirewallpip = New-AzPublicIpAddress `
-Name $AzFirewallpipName `
-ResourceGroupName $vnetresourcegroup `
-Location uksouth `
-AllocationMethod Static `
-Sku Standard
#Create Azure Firewall
$AzFirewall = New-AzFirewall `
-Name $AzFirewallName `
-ResourceGroupName $vnetresourcegroup `
-Location uksouth `
-VirtualNetworkName $HubVirtualNetwork.name `
-PublicIpName $AzFirewallpip.name
# NSG Creation
$AzNsg = New-AzNetworkSecurityGroup `
-Name $AzNsgName `
-Location uksouth `
-ResourceGroupName $vnetresourcegroup
# ASG Creation
$AzAsg = New-AzApplicationSecurityGroup `
-Name $AzAsgName `
-Location uksouth `
-ResourceGroupName $vnetresourcegroup
# VM Creation
foreach($vm in $tamOpsVMs){
$Subnet = Get-AzVirtualNetwork -name $spokevnet1
$IPconfig = New-AzNetworkInterfaceIpConfig -Name "IPConfig1" -PrivateIpAddressVersion IPv4 -PrivateIpAddress $vm.ip -SubnetId $Subnet.subnets[0].id
$NIC = New-AzNetworkInterface -Name $vm.nicname -ResourceGroupName $vnetresourcegroup -Location uksouth -IpConfiguration $IPconfig
$Credential = New-Object System.Management.Automation.PSCredential ($VMLocalAdminUser, $VMLocalAdminSecurePassword);
$VirtualMachine = New-AzVMConfig -VMName $vm.VmName -VMSize $vm.vmsize
$VirtualMachine = Set-AzVMOperatingSystem -VM $VirtualMachine -Windows -ComputerName $vm.VmName -Credential $Credential -ProvisionVMAgent -EnableAutoUpdate
$VirtualMachine = Add-AzVMNetworkInterface -VM $VirtualMachine -Id $NIC.Id
$VirtualMachine = Set-AzVMSourceImage -VM $VirtualMachine -PublisherName 'MicrosoftWindowsServer' -Offer 'WindowsServer' -Skus '2012-R2-Datacenter' -Version latest
New-AzVM -ResourceGroupName $vnetresourcegroup -Location uksouth -VM $VirtualMachine -Verbose
}
# Peer Virtual Networks
$HubVirtualNetwork = Get-AzVirtualNetwork -name $hubvnet
$SpokeVirtualNetwork = get-azvirtualnetwork -name $spokevnet1
# Peer Hub to Spoke
Add-AzVirtualNetworkPeering `
-Name "hub2spokepeer" `
-VirtualNetwork $HubVirtualNetwork `
-RemoteVirtualNetworkId $SpokeVirtualNetwork.Id
# Peer Spoke to Hub
Add-AzVirtualNetworkPeering `
-Name "spoke2hubpeer" `
-VirtualNetwork $SpokeVirtualNetwork `
-RemoteVirtualNetworkId $HubVirtualNetwork.Id
# Route Table creation
$AzFirewallRouteTable = New-AzRouteTable `
-Name $AzFirewallRouteTableName `
-ResourceGroupName $vnetresourcegroup `
-location uksouth
# Create a route (default route)
Get-AzRouteTable `
-ResourceGroupName $vnetresourcegroup `
-Name $AzFirewallRouteTableName `
| Add-AzRouteConfig `
-Name "Firewall-DR" `
-AddressPrefix 0.0.0.0/0 `
-NextHopType "VirtualAppliance" `
-NextHopIpAddress $AzFirewall.IpConfigurations.privateipaddress `
| Set-AzRouteTable
# Associate the route table to the VM subnet
Set-AzVirtualNetworkSubnetConfig `
-VirtualNetwork $SpokeVirtualNetwork `
-Name $spokevnet1subnetname `
-AddressPrefix $spokevnet1subnet `
-RouteTable $AzFirewallRouteTable | `
Set-AzVirtualNetwork
# Create NAT Rule Collection for RDP on Azure Firewall
$AzFirewallPublicIP = Get-AzPublicIpAddress -name $AzFirewallpipName
$natrulerdp = `
New-AzFirewallNatRule `
-Name "rdp" `
-Protocol "TCP" `
-SourceAddress "*" `
-DestinationAddress $AzFirewallPublicIP.IpAddress `
-DestinationPort "3389" `
-TranslatedAddress $tamOpsVMs.ip[0] `
-TranslatedPort "3389"
$rdpcollectionrule = `
New-AzFirewallNatRuleCollection `
-Name "MyNatRuleCollection" `
-Priority 100 `
-Rule $natrulerdp
$AzFirewall.NatRuleCollections = $rdpcollectionrule
$AzFirewall | Set-AzFirewall
# NSG Rule and assignment to VMs
$rdpallow = New-AzNetworkSecurityRuleConfig `
-Name RDP-Allow-AzFW `
-Access Allow `
-DestinationAddressPrefix $tamOpsVMs.ip[0] `
-DestinationPortRange 3389 `
-Direction Inbound `
-Priority 100 `
-Protocol tcp `
-SourceAddressPrefix $AzFirewallPublicIP.IpAddress `
-SourcePortRange *
$AzNsg.SecurityRules = $rdpallow
$AzNsg | Set-AzNetworkSecurityGroup
# Assign ASG & NSG to VMs
$Vms = Get-AzVM -ResourceGroupName $vnetresourcegroup
$AsgName = $AzAsgName
$NsgName = $AzNsgName
foreach ($vm in $vms) {
$nic = Get-AzNetworkInterface -ResourceId $Vm.NetworkProfile.NetworkInterfaces.id
$Asg = Get-AzApplicationSecurityGroup -Name $AsgName
$Nsg = Get-AzNetworkSecurityGroup -name $NsgName
$nic.IpConfigurations[0].ApplicationSecurityGroups = $Asg
$nic.NetworkSecurityGroup = $Nsg
$nic | Set-AzNetworkInterface
}
Deployment output

Test RDP
Test RDP via Azure Firewall Public IP, found:-
PS C:UsersThomasDocumentsHubspoke_networking> $AzFirewallPublicIP.IpAddress
51.132.162.35
A lengthy blog but a good introduction into hub-spoke networking!