Task 02: Register Cluster Nodes with Azure Arc
Status: Active | Estimated Time: 20-60 minutes (includes OEM updates) | Last Updated: 2026-03-15
Overview
Register all cluster nodes with Azure Arc using Invoke-AzStackHciArcInitialization. Arc registration is a mandatory prerequisite for Azure Local cloud deployment — without it, the deployment orchestrator cannot communicate with the cluster.
Arc registration requires authenticating to Azure (not the node OS). Two methods are available:
- Service principal (SPN) — Production default. SPN secret resolved from Key Vault or prompted securely. Used by both tabs and by the
Invoke-script. - Device code — Lab/test alternative. Interactive browser-based login via
Connect-AzAccount -DeviceCode. Available in the Direct tab.
Node access (RDP for Direct, WinRM for Orchestrated) is separate — it uses the management server session context or your RDP session. No node credentials are passed to Azure.
When nodes ship with a preinstalled or outdated Azure Stack HCI OS image (Dell, HPE, Lenovo, etc.), Invoke-AzStackHciArcInitialization triggers an automatic OS update cycle:
- Scan — detect current OS version vs. latest baseline
- Download — download update package (~40-60 min depending on network)
- Install — apply the OS update
- Reboot — node reboots to complete the update (WinRM session disconnects)
- Resume — bootstrap service resumes Arc registration using cached SPN token
This is expected behavior. The Invoke-AzStackHciArcInitialization cmdlet manages the full update + reboot + resume cycle internally. The synchronous Invoke-Command session stays alive through reboots — no manual intervention required. Run Task 03 in a separate window to monitor OEM update progress.
See: Handle preinstalled or outdated OS images during Azure Arc registration
Prerequisites
| Requirement | Details |
|---|---|
| Task 01 passed | Connectivity validation succeeded on all nodes |
| Service principal | Created with Contributor on the resource group |
| SPN credentials | App ID and secret available (Key Vault or secure store) |
| Node access | RDP/console (Direct) or WinRM from mgmt server (Orchestrated) |
Variables from variables.yml
| Path | Type | Description |
|---|---|---|
azure_platform.tenant.id | string | Azure Tenant ID |
azure_platform.subscriptions.lab.id | string | Azure Subscription ID |
compute.azure_local.arc_resource_group | string | Resource group for Arc resources |
azure_platform.region | string | Azure region |
compute.azure_local.arc_gateway_id | string | Arc Gateway resource ID |
identity.service_principal.client_id | string | SPN Application ID |
compute.cluster_nodes[].management_ip | string | Node management IPs |
security.keyvault.kv_azl.kv_azl_name | string | Key Vault name |
Execution
- Direct (On Node)
- Orchestrated (Mgmt Server)
Run on each node individually via RDP or console.
Option A: Service Principal Authentication (Production)
# Task 02 - Register Node with Azure Arc (SPN auth, run on each node)
$TenantId = "REPLACE_TENANT_ID"
$SubscriptionId = "REPLACE_SUBSCRIPTION_ID"
$ResourceGroup = "REPLACE_RESOURCE_GROUP"
$Region = "REPLACE_REGION"
$Cloud = "AzureCloud"
$ArcGatewayId = "REPLACE_ARC_GATEWAY_ID"
$SpnId = "REPLACE_SPN_ID"
if ($TenantId -match '^REPLACE_') { throw 'Edit the REPLACE_ variables before running.' }
# Prompt for SPN secret securely (never hardcode secrets in scripts)
$SpnSecure = Read-Host "Enter SPN secret" -AsSecureString
$SpnSecret = [System.Net.NetworkCredential]::new('', $SpnSecure).Password
# Authenticate with service principal
$SecurePassword = ConvertTo-SecureString $SpnSecret -AsPlainText -Force
$Credential = New-Object System.Management.Automation.PSCredential($SpnId, $SecurePassword)
Connect-AzAccount -ServicePrincipal -TenantId $TenantId -Credential $Credential -SubscriptionId $SubscriptionId
# Get ARM access token
$ArmToken = (Get-AzAccessToken).Token
$AccountId = $SpnId
# Register this node with Arc
Invoke-AzStackHciArcInitialization `
-SubscriptionID $SubscriptionId `
-ResourceGroup $ResourceGroup `
-TenantID $TenantId `
-Region $Region `
-Cloud $Cloud `
-ArmAccessToken $ArmToken `
-AccountID $AccountId `
-ArcGatewayID $ArcGatewayId
Option B: Device Code Authentication (Lab/Test)
# Task 02 - Register Node with Azure Arc (device code auth, run on each node)
$TenantId = "REPLACE_TENANT_ID"
$SubscriptionId = "REPLACE_SUBSCRIPTION_ID"
$ResourceGroup = "REPLACE_RESOURCE_GROUP"
$Region = "REPLACE_REGION"
$Cloud = "AzureCloud"
$ArcGatewayId = "REPLACE_ARC_GATEWAY_ID"
if ($TenantId -match '^REPLACE_') { throw 'Edit the REPLACE_ variables before running.' }
# Authenticate interactively — opens a browser prompt via device code
Connect-AzAccount -TenantId $TenantId -SubscriptionId $SubscriptionId -DeviceCode
# Get ARM access token
$ArmToken = (Get-AzAccessToken).Token
$AccountId = (Get-AzContext).Account.Id
# Register this node with Arc
Invoke-AzStackHciArcInitialization `
-SubscriptionID $SubscriptionId `
-ResourceGroup $ResourceGroup `
-TenantID $TenantId `
-Region $Region `
-Cloud $Cloud `
-ArmAccessToken $ArmToken `
-AccountID $AccountId `
-ArcGatewayID $ArcGatewayId
Run from management server against all nodes via PowerShell remoting.
For production deployments, use Invoke-ArcRegistration-Orchestrated.ps1 from the azl-toolkit repo. Defaults to SPN authentication (secret resolved from Key Vault), but supports bypassing SPN via -SpnAppId / -SpnSecret overrides or by pre-authenticating with Connect-AzAccount before running. Also supports -WhatIf and -TargetNode.
The script uses a synchronous Invoke-Command -ComputerName ($ServerList) call that fans out to all nodes in parallel. Each PSRemoting session stays alive through OEM reboots — the cmdlet manages the full update + reboot + resume cycle internally. Run Task 03 in a separate window to monitor OEM update progress, or use the wrapper script to launch both together.
The inline code below uses Select-String regex to extract values from YAML, which is fragile and may match incorrect values in a deeply nested YAML structure. The Invoke- script above uses powershell-yaml for proper config parsing.
# Task 02 - Register Nodes with Azure Arc (orchestrated, all nodes)
# variables.yml variables (schema v4.0.0):
# azure_platform.tenant.id -> $TenantId
# azure_platform.subscriptions.lab.id -> $SubscriptionId
# compute.azure_local.arc_resource_group -> $ResourceGroup
# azure_platform.region -> $Region
# compute.azure_local.arc_gateway_id -> $ArcGatewayId
# identity.service_principal.client_id -> $SpnId
# compute.cluster_nodes[].management_ip -> $ServerList
$ConfigPath = ".\config\variables.yml"
$cfg = Get-Content $ConfigPath
$TenantId = ($cfg | Select-String 'tenant_id:\s+"?([^"'' ]+)' | ForEach-Object { $_.Matches[0].Groups[1].Value.Trim() }) | Select-Object -First 1
$SubscriptionId = ($cfg | Select-String 'subscription_id:\s+"?([^"'' ]+)' | ForEach-Object { $_.Matches[0].Groups[1].Value.Trim() }) | Select-Object -First 1
$ResourceGroup = ($cfg | Select-String 'resource_group:\s+"?([^"'' ]+)' | ForEach-Object { $_.Matches[0].Groups[1].Value.Trim() }) | Select-Object -First 1
$Region = ($cfg | Select-String 'region:\s+"?([^"'' ]+)' | ForEach-Object { $_.Matches[0].Groups[1].Value.Trim() }) | Select-Object -First 1
$ArcGatewayId = ($cfg | Select-String 'arc_gateway_id:\s+"?([^"'' ]+)' | ForEach-Object { $_.Matches[0].Groups[1].Value.Trim() }) | Select-Object -First 1
$SpnId = ($cfg | Select-String 'spn_id:\s+"?([^"'' ]+)' | ForEach-Object { $_.Matches[0].Groups[1].Value.Trim() }) | Select-Object -First 1
$ServerList = ($cfg | Select-String 'management_ip:\s+"?([^"'' ]+)' | ForEach-Object { $_.Matches[0].Groups[1].Value.Trim() })
$Cloud = "AzureCloud"
# SPN secret — resolved from Key Vault (not stored in YAML)
$VaultName = ($cfg | Select-String 'key_vault_name:\s+"?([^"'' ]+)' | ForEach-Object { $_.Matches[0].Groups[1].Value.Trim() }) | Select-Object -First 1
$SecretName = ($cfg | Select-String 'spn_secret_name:\s+"?([^"'' ]+)' | ForEach-Object { $_.Matches[0].Groups[1].Value.Trim() }) | Select-Object -First 1
$SpnSecret = (Get-AzKeyVaultSecret -VaultName $VaultName -Name $SecretName -AsPlainText)
# Authenticate with service principal
$SecurePassword = ConvertTo-SecureString $SpnSecret -AsPlainText -Force
$SpnCredential = New-Object System.Management.Automation.PSCredential($SpnId, $SecurePassword)
Connect-AzAccount -ServicePrincipal -TenantId $TenantId -Credential $SpnCredential -SubscriptionId $SubscriptionId
$ArmToken = (Get-AzAccessToken).Token
$AccountId = $SpnId
# Register each node
Invoke-Command ($ServerList) {
param($Sub, $RG, $Tenant, $Reg, $Cld, $Token, $Acct, $GwId)
Invoke-AzStackHciArcInitialization `
-SubscriptionID $Sub `
-ResourceGroup $RG `
-TenantID $Tenant `
-Region $Reg `
-Cloud $Cld `
-ArmAccessToken $Token `
-AccountID $Acct `
-ArcGatewayID $GwId
} -ArgumentList $SubscriptionId, $ResourceGroup, $TenantId, $Region, $Cloud, $ArmToken, $AccountId, $ArcGatewayId |
Sort -Property PsComputerName
Alternative: Configurator App (Preview)
Microsoft provides a GUI wizard — the Azure Stack HCI Configurator App — that simplifies Arc registration for smaller or first-time deployments.
| Setting | Value | Source |
|---|---|---|
| Authentication | Interactive or Service Principal | User preference |
| Tenant ID | From variables.yml | azure_platform.tenant.id |
| Subscription ID | From variables.yml | azure_platform.subscriptions.lab.id |
| Resource Group | From variables.yml | compute.azure_local.arc_resource_group |
| Region | From variables.yml | azure_platform.region |
| Arc Gateway | Enabled | compute.azure_local.arc_gateway_id |
- Download from aka.ms/ashciconfigurator
- Launch → Register with Azure Arc
- Fill in settings from the table above
- Authenticate (device code or SPN)
- Click Register and wait for completion
Reference: Microsoft Learn — Use Configurator for Arc Registration
Combined Registration + Monitor (Wrapper Script)
For OEM image scenarios where nodes will reboot during registration, use the wrapper script to launch both registration and monitoring together:
# Launch monitor in a new window + run registration in current window
.\scripts\deploy\04-cluster-deployment\phase-04-arc-registration\task-02-register-cluster-nodes-with-azure-arc\powershell\Start-ArcRegistrationWithMonitor.ps1 `
-ConfigPath .\configs\infrastructure-azl-lab.yml
The wrapper:
- Launches
Invoke-BootstrapMonitor-Orchestrated.ps1in a new PowerShell window (background) - Runs
Invoke-ArcRegistration-Orchestrated.ps1in the current window (foreground) - The monitor tracks all OEM update phases while registration runs synchronously
| Parameter | Default | Description |
|---|---|---|
-ConfigPath | (required) | Path to variables.yml |
-TargetNode | all nodes | Target specific node(s) — passed to both scripts |
-WhatIf | — | Dry-run registration only, no monitor window |
-PollIntervalSeconds | 60 | Monitor poll interval (passed to monitor) |
-NoMonitor | — | Skip launching the monitor window |
-SpnSecret | — | SPN secret override (passed to registration) |
-NoArcGateway | — | Skip Arc Gateway requirement (registration without gateway) |
Use Start-ArcRegistrationWithMonitor.ps1 when deploying nodes with OEM images that may trigger automatic OS updates and reboots. For fresh installs or nodes already at baseline OS version, Invoke-ArcRegistration-Orchestrated.ps1 alone is sufficient.
Expected Outcomes
After registration completes on each node, confirm:
# Quick check — run on any node
Get-Service himds | Select-Object Name, Status
& "$env:ProgramFiles\AzureConnectedMachineAgent\azcmagent.exe" show
| Check | Expected |
|---|---|
himds service | Running |
azcmagent show status | Connected |
| Azure Portal → Arc servers | All nodes visible |
Troubleshooting
| Issue | Resolution |
|---|---|
| SPN auth fails | Verify App ID/Secret, confirm Contributor role on RG |
| Token expired mid-run | Re-run Connect-AzAccount and get a fresh token |
| WinRM denied (orchestrated) | Confirm WinRM enabled from Phase 03 and session context (domain trust) is valid — use -Credential override if needed |
| Arc init timeout | Check node internet connectivity (Task 01) |
| Bootstrap reports InProgress | OEM updates or registration still running — run Task 03 to monitor |
| Bootstrap reports Failed (401) | SPN token expired or session lost — re-run registration with fresh auth |
Navigation
← Task 01: Pre-Registration Validation · Task 03: Monitor Bootstrap →