Skip to main content

Shareable Link for Azure Bastion - Part 2

ยท 7 min read
Hasan Gural

Great to see you again! In the previous article, we tried to create a resource group for our lab environment. From now on, I will jump right into my script to create a lab environment. I will explain the script line by line. You can also find the script in end of the article and in my GitHub repository.

๐Ÿ’ป Configuration of Lab Environmentโ€‹

I tried creating a lab environment as close to a real-world scenario. PowerShell has an environmentMap complex variable containing all the lab environment information. You can see the environmentMap variable below.

$environmentMap = [ordered]@{

"vnt-0001" = @{
type = "VirtualNetwork" # Type of the resource. VirtualNetwork or VirtualMachine
AddressPrefix = ""
Subnets = @{

"AzureBastionSubnet" = @{ AddressPrefix = "" }
"subnet-0001" = @{ AddressPrefix = "" }
"subnet-0002" = @{ AddressPrefix = "" }
deployBastion = $true # Deploy Azure Bastion to the virtual network or not

"vm-01" = @{

"type" = "VirtualMachine"
"ResourceGroupName" = $rgDef.Name
"Location" = "uksouth"
"Size" = "Standard_B1s"
"Image" = "win2019datacenter"
"vNetName" = "vnt-0001"
"SubnetName" = "subnet-0001"
"UserName" = "yourUserName"
"Password" = "yourStrongPassword"


"vm-02" = @{

"type" = "VirtualMachine"
"ResourceGroupName" = $rgDef.Name
"Location" = "uksouth"
"Size" = "Standard_B1s"
"Image" = "win2019datacenter" # OS Image
"vNetName" = "vnt-0001"
"SubnetName" = "subnet-0001"
"UserName" = "yourUserName" # Username for the VM
"Password" = "yourStrongPassword" # Password for the VM


As you can see, we have a very complex environmentMap variable. It contains information about virtual networks, subnets, virtual machines, and Azure Bastion. I have a vnt-0001 virtual network that has three subnets. One of them is AzureBastionSubnet, which is mandatory for Azure Bastion. The other two subnets are subnet-0001 and subnet-0002. Each section of the environmentMap variable has a type property. This property is used to determine the type of resource that will be created. In the example above, we have two types of resources. VirtualNetwork and VirtualMachine. The deployBastion property is used to determine whether we will deploy Azure Bastion to the virtual network or not. You can carve out the resource names from the environmentMap variable. For example, the name of the virtual network is vnt-0001, and the virtual machine's name is vm-01. It is totally up to you to name your resources. I will now apply the naming convention above in my script.

EnvironmentMap variable contains information about the VMs. We have two VMs in the example above. The vm-01 and vm-02 VMs are in the same subnet. Variable can be extended to include more resources. For example, you might add more VMs and Subnets blocks and try to understand the behavior of Azure Bastion. I will let you decide what to do with the environmentMap variable.

๐Ÿš€ Provisioning of Lab Environment in ten minutesโ€‹

# Developed for creating lab environment for Azure Bastion Shareable Links feature
# Author: Hasan Gural | Microsoft MVP @ Azure
# Version: 1.0.0-beta

ForEach ($item in $environmentMap.Keys) {

if ($environmentMap.$item.type -eq "VirtualNetwork") {

Write-Output "[TASK-1.0] - Creating Virtual Network: $item"
Write-Output "[TASK-1.0] - Virtual Network adress space: $($environmentMap.$item.AddressPrefix)"
Write-Output "[TASK-1.0] - Virtual Network subnets: $($environmentMap.$item.Subnets.Keys)"

$vNetDef = @{

Name = $item # Name of the virtual network from the network map
AddressPrefix = $environmentMap.$item.AddressPrefix # Address prefix for the virtual network.
ResourceGroupName = $rgDef.Name # Resource group name
Location = "uksouth" # Location of the virtual network

Subnet = ForEach ($subnet in $environmentMap.$item.Subnets.Keys) {

Write-Information "Creating subnet configuration for subnet '$subnet', in network '$item'"
New-AzVirtualNetworkSubnetConfig -Name $subnet -AddressPrefix $environmentMap.$item.Subnets.$subnet.AddressPrefix

New-AzVirtualNetwork @vNetDef | Out-Null

Write-Output "[TASK-1.0] - Virtual Network has been created: $item"

if ($environmentMap.$item.deployBastion -eq $true) {

Write-Output "[TASK-1.1] - Creating Bastion Host: bastion-$item"

#Make sure the Bastion Host IP is available
$createPIP = New-AzPublicIpAddress -Name "bastion-$item-pip" -ResourceGroupName $rgDef.Name `
-Location "uksouth" -AllocationMethod Static -Sku Standard -Force

$bastionDef = @{

Name = "bastion-$item"
ResourceGroupName = $rgDef.Name
VirtualNetworkName = $item
VirtualNetworkRgName = $rgDef.Name
PublicIpAddressId = $createPIP.Id
ScaleUnit = 2
Sku = "Standard"


New-AzBastion @bastionDef -Asjob | Out-Null

Write-Output "[TASK-1.1] - Bastion Host is on the way: bastion-$item - Please wait for it to be created: bastion-$item"

# Region Bastion Feature - This is a workaround for the fact that the Bastion Host is not yet available in the Azure PowerShell module.
# Intention of this is to enable the shareable link feature for the Bastion Host. We will hit the REST API directly

Start-Job -ArgumentList $item, $rgDef, $subscriptionId -ScriptBlock {

param (



while ($true) {

$bastionStatus = Get-AzBastion -Name "bastion-$item" -ResourceGroupName $rgDef.Name -ErrorAction SilentlyContinue

if ($bastionStatus.ProvisioningState -eq "Succeeded") {

$header = @{

"Content-Type" = "application/json"
Authorization = ("Bearer " + (Get-AzAccessToken).Token)

$requestBody = @{

location = $rgDef.Location
properties = @{
enableShareableLink = $true
ipconfigurations = @(
name = "IpConf"
properties = @{
publicIPAddress = @{
id = "/subscriptions/$($subscriptionId)/resourceGroups/$($rgDef.Name)/providers/Microsoft.Network/publicIPAddresses/bastion-$($item)-pip"
subnet = @{
id = "/subscriptions/$($subscriptionId)/resourceGroups/$($rgDef.Name)/providers/Microsoft.Network/virtualNetworks/$($item)/subnets/AzureBastionSubnet"

$uri = "$($subscriptionId)/resourceGroups/$($rgDef.Name)/providers/Microsoft.Network/bastionHosts/bastion-vnt-0001?api-version=2022-07-01"

Invoke-RestMethod -Method Put -Uri $uri -Headers $header -Body (ConvertTo-Json $requestBody -Depth 10) | Out-Null
else {

Write-Output "Bastion Host is not ready yet. Waiting 30 seconds before checking again"
Start-Sleep -Seconds 30

} | Out-Null

#End Bastion Feature
elseif ($environmentMap.$item.type -eq "VirtualMachine") {

Write-Output "[TASK-2.0] - Creating Virtual Machine: $item"

$vmProps = @{

Name = $item
ResourceGroupName = $environmentMap.$item.ResourceGroupName
Location = $environmentMap.$item.Location
VirtualNetworkName = $environmentMap.$item.vNetName
SubnetName = $environmentMap.$item.SubnetName
Size = $environmentMap.$item.Size
Image = $environmentMap.$item.Image
SecurityGroupName = $item
OpenPorts = @("3389")
Credential = New-Object System.Management.Automation.PSCredential($environmentMap.$item.UserName, (ConvertTo-SecureString -String $environmentMap.$item.Password -AsPlainText -Force))


New-AzVM @vmProps -AsJob | Out-Null

Write-Output "[TASK-2.0] - Virtual Machine is on the way: $item - Please wait for the VM to be created."


The script will consume the environmentMap variable and create the resources. After the successful execution of the hand, you should see the following output from PowerShell Console.



PowerShell Script will finish in less than a minute. Script uses background jobs to create resources. Please wait at least 5 minutes for the resources to be made. Then you can check the status of the resources on the Azure Portal.

Script uses background jobs to create resources. If you want to understand more about running background jobs in PowerShell, please check the following article: How to run background jobs in PowerShell

๐Ÿ’ก Check the status of the resourcesโ€‹

In Azure Portal, you should see the following resources.


We now have Lab resources to test the Azure Bastion Shareable Links feature. For quick validation, we can jump one of the VMs using the Azure Bastion to confirm that the Bastion Host is working as expected. Go ahead to any VMs you would like to connect to and click on the Connect button on the top of the blade. This will prove that the Bastion Host is working as expected.

Please be sure that Azure Bastion is in the Succeeded state and that the Configuration section shows Enabled for the Shareable Link feature.


Brilliant! We now have Azure Bastion Host in place and are ready to proceed with the Shareable Links feature. In the next article, we will start to interact with an endpoint of Azure Management API to create the Shareable Links on demand.