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 = "10.10.10.0/24"
Subnets = @{
"AzureBastionSubnet" = @{ AddressPrefix = "10.10.10.0/27" }
"subnet-0001" = @{ AddressPrefix = "10.10.10.32/27" }
"subnet-0002" = @{ AddressPrefix = "10.10.10.64/27" }
}
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 {
[CmdletBinding()]
param (
[Parameter()]
$item,
[Parameter()]
$rgDef,
[Parameter()]
$subscriptionId
)
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 = "https://management.azure.com/subscriptions/$($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.