On this weblog publish, you’ll discover ways to configure an Azure digital machine (VM) as an Azure Arc-enabled server for studying, coaching, and testing functions, as I stroll you thru the straightforward setup course of utilizing Azure PowerShell, a PowerShell script, and the free Azure Bastion Developer SKU.
⚠️ Take into account that this weblog publish and the steps to configure an Azure VM as an Arc-enabled server are meant completely for testing and analysis. They need to by no means be utilized in a manufacturing setting or on important Azure VMs.
In any regular situation, you may’t join an Azure VM as an Azure Arc-enabled server as a result of it’s already represented in Azure Useful resource Supervisor, with all native Azure options and capabilities simply accessible if wanted.
Nonetheless, if you happen to try and onboard any of your Azure VMs utilizing the OnboardingScript.ps1, you’ll obtain an error message stating that it’s unsupported, as proven within the screenshot under.

So, as you may see, by default, this isn’t doable. Nonetheless, there are just a few steps you may take as a workaround to onboard an Azure VM working Home windows Server as an Arc-enabled server. This may be significantly helpful for testing or studying functions if you don’t have entry to any on-premises machines.
On this weblog publish, I’ll stroll you thru the straightforward setup course of utilizing Azure PowerShell, a PowerShell script, and the free Azure Bastion Developer SKU.
Desk of Contents
Azure conditions
- An Azure subscription is required for this course of. Because the VM(s) will likely be used solely for coaching and testing, it’s advisable to make use of a sandboxed Azure subscription.
- An Azure Administrator account with the suitable RBAC roles, equivalent to Proprietor or Contributor on the subscription or useful resource group stage.
- An present Azure Arc OnboardingScript.ps1 file.



Deploying the Azure VM and different required assets with an Azure PowerShell script
To get began, we’ll first provision a brand new Azure VM in a devoted useful resource group inside the sandboxed subscription. You possibly can both use the Azure Portal or, for a sooner setup, run the Azure PowerShell script under.
This script will create a digital community (VNet) in a specified useful resource group, deploy an Azure VM working Home windows Server 2022 inside the similar useful resource group, and connect the VM to a subnet within the VNet.
To make use of the script, begin by saving a replica as “Create-Azure-Home windows-VM-with-VNet-and-RG-for-Azure-Arc-learning.ps1” or downloading it immediately from GitHub. Alter the dynamic and different variables to suit your particular wants, after which run the script utilizing Home windows Terminal, Visible Studio Code, or Home windows PowerShell.

Earlier than working the script, be certain that to sign up with the Join-AzAccount cmdlet to hyperlink your Azure account. In case you have a number of Azure tenants, use the Set-AzContext -tenantID cmdlet to pick out the proper tenant earlier than executing the script.

You possibly can then run the script, with the required parameters.
.Create-Azure-Home windows-VM-with-VNet-and-RG-for-Azure-Arc-Studying.ps1 -SubscriptionName

If you wish to use a special VM dimension than the default “Standard_B2ms“, run the script with the next parameters.
.Create-Azure-Home windows-VM-with-VNet-and-RG-for-Azure-Arc-Studying.ps1 -SubscriptionName -vmSize

-vmSize
Instance 1: .Create-Azure-Home windows-VM-with-VNet-and-RG-for-Azure-Arc-Studying.ps1 -SubscriptionName "sub-tst-myh-sandbox-01"
Instance 2: .Create-Azure-Home windows-VM-with-VNet-and-RG-for-Azure-Arc-Studying.ps1 -SubscriptionName "sub-tst-myh-sandbox-01" -vmSize "Standard__D2s_v3"
.LINK
Azure Arc: Using an Azure Windows VM as an Arc-enabled server for learning and training
#>
## ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
## Parameters
param(
# $subscriptionName -> Title of the Azure Subscription
[parameter(Mandatory=$true)][ValidateNotNullOrEmpty()][string] $subscriptionName,
# $vmSize -> Specifies the VM dimension.
[parameter(Mandatory=$false)][ValidateNotNullOrEmpty()][string] $vmSize = "Standard_B2ms"
)
## ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
## Variables
# Naming conference: instance - rg-tst-arc-01 (useful resource kind - spoke - goal - stock quantity)
# Dynamic variables - Please change the values to if required.
$spoke = "tst" # Abbreviation for the spoke (e.g., 'tst' for check, 'poc' for proof of idea). Change based mostly in your setting.
$goal = "arc" # Specifies the meant use of the useful resource group or service (e.g., 'arc' for Azure Arc). Replace if completely different.
$addressPrefix = "10.23.1." # Deal with prefix for the digital community subnets. Replace if completely different.
# Different Configuration Variables
$inventoryNumbering = 1 # Stock quantity for the useful resource (e.g., 1, 2, 3, and so on. to distinguish assets).
$area = "westeurope" # Azure area (e.g., 'westeurope', 'eastus'). Substitute together with your present area if wanted.
$regionShort = "we" # Brief abbreviation for the Azure area (e.g., 'we' for West Europe, 'eus' for East US). Replace if completely different.
# Useful resource teams Arc assets
$rgNameArcTst = "rg" + "-" + $spoke + "-" + $goal + "-" + $inventoryNumbering.ToString("D2")
# Networking assets
$networkWatcherName = "nw" + "-" + $spoke + "-" + $regionShort + "-" + $inventoryNumbering.ToString("D2")
$vnetName = "vnet" + "-" + $spoke + "-" + $regionShort + "-" + $inventoryNumbering.ToString("D2")
$subnetNameGateway = "GatewaySubnet"
$subnetNameAzureBastion = "AzureBastionSubnet"
$subnetNameVm = "snet" + "-" + $spoke + "-" + "vm" + "-" + $inventoryNumbering.ToString("D2")
$subnetAddressPrefixGateway = $addressPrefix + "0/26"
$subnetAddressPrefixAzureBastion = $addressPrefix + "64/26"
$subnetAddressPrefixVm = $addressPrefix + "128/26"
$vnetAddressPrefix = $addressPrefix + "0/24"
# VM assets
$vmName01 = "swt" + $goal + $inventoryNumbering.ToString("D3") # Specifies theVM title. Replace if completely different.
$userName = "loc_arc" # Specifies the username for the VM. Replace if completely different.
$password = "ArcF@lcon_7632" # Specifies the password for the VM. Replace if completely different.
$osSKU01 = "2022-Datacenter" # Specifies the OS SKU for the VM. Replace if completely different.
$osDiskNameVM01 = $vmName01 + "-" + "c" # Specifies the OS disk title for the VM. Replace if completely different.
$osDiskSizeInGB = "127" # Specifies the OS disk dimension in GB. Replace if completely different.
$diskStorageAccountType = "StandardSSD_LRS" # Premium_LRS = Premium SSD; StandardSSD_LRS = Customary SSD; Standard_LRS = Customary HHD
$nicNameVM01 = "nic" + "-" + "01" + "-" + $vmName01 # Specifies the NIC title for the VM. Replace if completely different.
# Tags
$tagSpokeName = "Env" # The setting tag title you need to use.
$tagSpokeValue = "$($spoke[0].ToString().ToUpper())$($spoke.SubString(1))"
$tagOSVersionName = "OperatingSystem" # The working system tag title you need to use.
# Different variables
Set-PSBreakpoint -Variable currenttime -Mode Learn -Motion {$international:currenttime = Get-Date -Format "dddd MM/dd/yyyy HH:mm"} | Out-Null
$foregroundColor1 = "Inexperienced"
$foregroundColor2 = "Yellow"
$foregroundColor3 = "Purple"
$writeEmptyLine = "`n"
$writeSeperatorSpaces = " - "
## ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
## Suppress Azure PowerShell breaking change warning messages to keep away from pointless output.
Set-Merchandise -Path Env:SuppressAzurePowerShellBreakingChangeWarnings -Worth $true | Out-Null
Replace-AzConfig -DisplayBreakingChangeWarning $false | Out-Null
Replace-AzConfig -DisplayRegionIdentified $false | Out-Null
$warningPreference = "SilentlyContinue"
## ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
## Write script began.
Write-Host ($writeEmptyLine + "# Script began. With out errors, it could actually take as much as 4 minutes to finish" + $writeSeperatorSpaces + $currentTime)`
-foregroundcolor $foregroundColor1 $writeEmptyLine
## ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
## Change the present context to the desired subscription.
$subName = Get-AzSubscription | The place-Object {$_.Title -like $subscriptionName}
Set-AzContext -SubscriptionId $subName.SubscriptionId | Out-Null
Write-Host ($writeEmptyLine + "# Specified subscription in present tenant chosen" + $writeSeperatorSpaces + $currentTime)`
-foregroundcolor $foregroundColor2 $writeEmptyLine
## ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
## Retailer the desired set of tags in a hash desk.
$tags = @{$tagSpokeName=$tagSpokeValue}
Write-Host ($writeEmptyLine + "# Specified set of tags obtainable so as to add" + $writeSeperatorSpaces + $currentTime)`
-foregroundcolor $foregroundColor2 $writeEmptyLine
## ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
## Create a useful resource group for Azure Arc testing functions, if it not already exists, and apply specified tags.
strive Out-Null
catch Out-Null
# Set tags Azure Arc-enabled servers useful resource group
Set-AzResourceGroup -Title $rgNameArcTst -Tag $tags | Out-Null
Write-Host ($writeEmptyLine + "# Useful resource group $rgNameArcTst obtainable" + $writeSeperatorSpaces + $currentTime)`
-foregroundcolor $foregroundColor2 $writeEmptyLine
## ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
## Create Community Watcher, if it not already exists, and apply specified tags.
strive Out-Null
catch Out-Null
Write-Host ($writeEmptyLine + "# Community watcher $rgNameArcTst created" + $writeSeperatorSpaces + $currentTime)`
-foregroundcolor $foregroundColor2 $writeEmptyLine
## ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
## Create Subnets, in the event that they not exist already.
$gatewaySubnet = New-AzVirtualNetworkSubnetConfig -Title $subnetNameGateway -AddressPrefix $subnetAddressPrefixGateway
$azureBastionSubnet = New-AzVirtualNetworkSubnetConfig -Title $subnetNameAzureBastion -AddressPrefix $subnetAddressPrefixAzureBastion
$vmSubnet = New-AzVirtualNetworkSubnetConfig -Title $subnetNameVm -AddressPrefix $subnetAddressPrefixVm
Write-Host ($writeEmptyLine + "# Digital community subnet configurations $vnetName created" + $writeSeperatorSpaces + $currentTime)`
-foregroundcolor $foregroundColor2 $writeEmptyLine
## ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
## Create Digital Community, if it not already exists, and apply specified tags.
$subnets = $gatewaySubnet,$azureBastionSubnet,$vmSubnet
strive Out-Null
catch Out-Null
# Set tags VNet
$vnet = Get-AzVirtualNetwork -Title $vnetName -ResourceGroupname $rgNameArcTst
$vnet.Tag = $tags
Set-AzVirtualNetwork -VirtualNetwork $vnet | Out-Null
Write-Host ($writeEmptyLine + "# VNet $vnetName created and configured" + $writeSeperatorSpaces + $currentTime)`
-foregroundcolor $foregroundColor2 $writeEmptyLine
## ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
## Create the NIC the VM, if it not already exists, and apply specified tags.
# Get the VNet to which to attach the NIC
$vnet = Get-AzVirtualNetwork -Title $vnetName -ResourceGroupname $rgNameArcTst
# Get the Subnet ID to which to attach the NIC
$subnetID = (Get-AzVirtualNetworkSubnetConfig -Title $subnetNameVm -VirtualNetwork $vnet).Id
# Create dynamic NIC VM 1
strive Out-Null
catch Out-Null
# Retailer NIC VM 1 in a variable
$nicVM01 = Get-AzNetworkInterface -ResourceGroupName $rgNameArcTst -Title $nicNameVM01
# Set non-public IP tackle NIC VM 1 to static
$nicVM01.IpConfigurations[0].PrivateIpAllocationMethod = "Static"
Set-AzNetworkInterface -NetworkInterface $nicVM01 | Out-Null
# Set tags on NIC VM1
$nicVM01.Tag = $tags
Set-AzNetworkInterface -NetworkInterface $nicVM01 | Out-Null
Write-Host ($writeEmptyLine + "# NIC $nicNameVM01 created" + $writeSeperatorSpaces + $currentTime)`
-foregroundcolor $foregroundColor2 $writeEmptyLine
## ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
## Specify the native administrator account.
$passwordSec = convertto-securestring $password -asplaintext -force
$creds = New-Object System.Administration.Automation.PSCredential($userName,$passwordSec)
## ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
## Get the newest Azure Market VMImage for Home windows Server 2022 that isn't deprecated
$images01 = Get-AzVMImage -Location $area -PublisherName "MicrosoftWindowsServer" -Supply "WindowsServer" -Skus $osSKU01 |
The place-Object { $_.ReplicationStatus -ne "Deprecated" } |
Kind-Object -Descending -Property PublishedDate
# Verify if any legitimate photos can be found
if (-not $images01) {
throw "No legitimate VM photos discovered for the desired OS SKU: $osSKU01. Be certain that non-deprecated photos can be found."
}
Write-Host ($writeEmptyLine + "# Newest non-deprecated Azure Market VM picture chosen" + $writeSeperatorSpaces + $currentTime)`
-foregroundcolor $foregroundColor2 $writeEmptyLine
## ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
## Create VM 1 if it not already exists, and apply specified tags.
Write-Host ($writeEmptyLine + "# Creating VM $vmName01 in useful resource group $rgNameArcTst" + $writeSeperatorSpaces + $currentTime) -ForegroundColor $foregroundColor2 $writeEmptyLine
strive {
if (-not (Get-AzVM -ResourceGroupName $rgNameArcTst -Title $vmName01 -ErrorAction SilentlyContinue)) {
# Create a configurable VM object
$vm01 = New-AzVMConfig -Title $vmName01.ToLower() -VMSize $vmSize
# Add the NIC
Add-AzVMNetworkInterface -VM $vm01 -Id $nicVM01.Id | Out-Null
# Specify the picture
if (-not $images01) {
throw "No VM photos discovered for the desired OS SKU: $osSKU01"
}
Set-AzVMSourceImage -VM $vm01 -PublisherName $images01[0].PublisherName -Supply $images01[0].Supply -Skus $images01[0].Skus -Model $images01[0].Model | Out-Null
# Set OS properties
Set-AzVMOperatingSystem -VM $vm01 -Home windows -ProvisionVMAgent -EnableAutoUpdate -Credential $creds -ComputerName $vmName01 | Out-Null
# Set OS disk properties
Set-AzVMOSDisk -VM $vm01 -name $osDiskNameVM01 -CreateOption fromImage -DiskSizeInGB $osDiskSizeInGB -StorageAccountType $diskStorageAccountType -Home windows | Out-Null
# Disable boot diagnostics
$vm01.DiagnosticsProfile = [Microsoft.Azure.Management.Compute.Models.DiagnosticsProfile]@{
BootDiagnostics = [Microsoft.Azure.Management.Compute.Models.BootDiagnostics]@{
Enabled = $false
}
}
# Create VM
New-AzVM -ResourceGroupName $rgNameArcTst -Location $area -VM $vm01 -OSDiskDeleteOption Delete -Verify:$false | Out-Null
}
} catch {
Write-Host "Error creating VM: $_" -ForegroundColor $foregroundColor3
throw
}
# Set tags on VM1
$vm01 = Get-AzVM -ResourceGroupName $rgNameArcTst -Title $vmName01
Replace-AzTag -Tag $tags -ResourceId $vm01.Id -Operation Merge | Out-Null
# Get OS model VM1
$osVersion = $vm01.StorageProfile.ImageReference.Supply + " $($vm01.StorageProfile.ImageReference.Sku)"
# Add OS tag to VM1
$osTag = @{$tagOSVersionName = $osVersion}
Replace-AzTag -ResourceId $vm01.Id -Tag $osTag -Operation Merge | Out-Null
Write-Host ($writeEmptyLine + "# VM $vmName01 created" + $writeSeperatorSpaces + $currentTime)`
-foregroundcolor $foregroundColor2 $writeEmptyLine
## ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
## Set tags on all disks within the useful resource group.
strive {
Get-AzDisk -ResourceGroupName $resourceGroupName -DiskName $diskNames | ForEach-Object {New-AzTag -ResourceId $_.Id -Tag $tags} | Out-Null
Write-Host ($writeEmptyLine + "# Tags set to all disks within the useful resource group $rgVMSpoke" + $writeSeperatorSpaces + $currentTime)`
-foregroundcolor $foregroundColor2 $writeEmptyLine
} catch {
Write-Host "Error tagging disks: $_" -ForegroundColor $foregroundColor3
}
## ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
## Write script accomplished.
Write-Host ($writeEmptyLine + "# Script accomplished" + $writeSeperatorSpaces + $currentTime)`
-foregroundcolor $foregroundColor1 $writeEmptyLine
## ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------



Deploy Azure Bastion Developer
To organize the Azure VM for onboarding to Azure Arc, we have to join and log in to it so as to carry out the mandatory steps for configuration.
I choose utilizing Azure Bastion for this, and happily, for Dev/Take a look at functions, now you can simply arrange an Azure Bastion Developer, which is a free, light-weight model of the Azure Bastion service.
Presently, deploying this Bastion SKU with Azure PowerShell is just not doable. To set it up, log in to the Azure Portal, and within the international search bar, kind in “bastion”. Then click on on Bastions.

Then, click on on + Create.

Subsequent, fill in all of the fields on the Fundamentals web page. Choose the proper subscription and useful resource group, present a reputation, and select the suitable area. Most significantly, choose Developer because the Tier and specify the newly created VNet. Then, click on Subsequent: Superior >.

Then, specify the tags you need to use and click on Subsequent: Overview + create.

Then, click on Create to deploy your Bastion Developer host.


Put together the Azure VM for onboarding to Azure Arc
To handle your Azure VM as an Azure Arc-enabled server, you first have to make just a few modifications to the VM earlier than putting in and configuring it. All these steps will likely be defined on this part.
First, all VM extensions deployed to the Azure VM ought to be eliminated. If you happen to adopted the earlier steps to deploy a brand new VM, no extensions, such because the Azure Monitor agent, ought to be put in. The one extension current ought to be the default BGInfo extension, which shouldn’t be eliminated.

The following step is to set the setting variable to override the ARC set up on the Azure VM, then disable the Azure VM Visitor Agent. Lastly, you’ll have to block entry to the Azure IMDS endpoint.
To make these steps simpler, you may both copy the PowerShell script under or obtain it from GitHub.
## ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
## Variables
Set-PSBreakpoint -Variable currenttime -Mode Learn -Motion {$international:currenttime = Get-Date -Format "dddd MM/dd/yyyy HH:mm"} | Out-Null
$foregroundColor1 = "Inexperienced"
$foregroundColor2 = "Yellow"
$writeEmptyLine = "`n"
$writeSeperatorSpaces = " - "
## ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
## Write script began.
Write-Host ($writeEmptyLine + "# Script began. With out errors, it could actually take as much as 2 minutes to finish" + $writeSeperatorSpaces + $currentTime)`
-foregroundcolor $foregroundColor1 $writeEmptyLine
## ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
## Put together Azure VM for onboarding to Azure Arc
# Set the setting variable to override the ARC on an Azure VM set up
[System.Environment]::SetEnvironmentVariable("MSFT_ARC_TEST",'true', [System.EnvironmentVariableTarget]::Machine)
# Disable the Azure VM Visitor Agent
Set-Service WindowsAzureGuestAgent -StartupType Disabled -Verbose
Cease-Service WindowsAzureGuestAgent -Drive -Verbose
# Block entry to Azure IMDS endpoint
New-NetFirewallRule -Title BlockAzureIMDS -DisplayName "Block entry to Azure IMDS" -Enabled True -Profile Any -Path Outbound -Motion Block -RemoteAddress 169.254.169.254
Write-Host ($writeEmptyLine + "# Azure VM is now prepared for onboarding to Azure Arc" + $writeSeperatorSpaces + $currentTime)`
-foregroundcolor $foregroundColor2 $writeEmptyLine
## ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
## Write script accomplished.
Write-Host ($writeEmptyLine + "# Script accomplished" + $writeSeperatorSpaces + $currentTime)`
-foregroundcolor $foregroundColor1 $writeEmptyLine
## ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
After copying the script or downloading it from GitHub, open it in Visible Studio Code or your most well-liked editor.
Then, go to the Azure Portal and navigate to the Azure VM you deployed or need to onboard to Azure Arc. Within the VM blade, scroll all the way down to the Join part and click on Bastion.

On the Bastion web page, enter the Username and VM Password, then click on Join.
💡 On this display, you may as well alter the Keyboard Language to match your keyboard settings.

If you happen to’ve entered the proper username and password, the connection to the VM by way of Bastion Developer will open immediately within the Azure portal (utilizing HTML5) over port 443 with the Bastion service, and it’ll open in a brand new tab.
When prompted for clipboard permissions, choose Permit. This can allow you to make use of the distant clipboard and the arrows on the left facet of the display.

On the VM, open Home windows PowerShell ISE. Then, in a brand new script file, copy the content material of the script you copied above or downloaded from GitHub. After that, click on the Run Script button to execute the script.



Onboard the Azure VM as an Azure Arc-enabled server
The ultimate step is to onboard the Azure VM as an Azure Arc-enabled server. To do that, copy the content material of the OnboardingScript.ps1 into a brand new script file in PowerShell ISE on the VM, after which run the script.






Conclusion
Whereas it’s strongly not suggested to put in Azure Arc-enabled servers on an Azure VM for manufacturing eventualities, it’s doable to configure Azure Arc-enabled servers on an Azure VM for coaching and testing functions solely.
On this weblog publish, I’ll present you how you can accomplish this utilizing Azure PowerShell, a typical PowerShell script, and the Azure Bastion Developer host to configure all the things in a simple and free method.
In case you have any questions or solutions concerning this weblog publish and the use scripts, be at liberty to achieve out to me by way of my X deal with (@wmatthyssen) or go away a remark under. I’m completely happy to assist!