Skip to main content
Version: Next

Active Directory Authentication - ARM Template Deployment

Runbook Azure

DOCUMENT CATEGORY: Runbook SCOPE: ARM template-based cluster deployment with Active Directory PURPOSE: Automated and repeatable deployment via infrastructure-as-code MASTER REFERENCE: Microsoft Learn - Deploy via ARM Template

Status: Active Estimated Time: 2–3 hours Last Updated: 2026-03-09


Overview

This guide walks through deploying an Azure Local cluster with Active Directory authentication using ARM templates. It is structured as a sequential runbook — complete each task in order from start to finish.

AD authentication requires:

  • A pre-existing Active Directory domain accessible from the cluster nodes
  • An organizational unit (OU) for Azure Local computer objects
  • A deployment service account with OU GenericAll permissions

The Microsoft official ARM template (azuredeploy.json, apiVersion 2025-09-15-preview) accepts 54 parameters. Azure Local Cloud provides parameter template files and a generation script that populates all 54 from variables.yml. See the Reference section at the bottom for the full parameter catalog.


Before You Begin

Prerequisites

RequirementDescriptionValidation
Azure CLI or PowerShellAz module installedGet-Module -Name Az
Subscription accessContributor + User Access AdministratorVerify in IAM
Resource groupCreated for cluster deploymentGet-AzResourceGroup
Platform Key VaultStores deployment credentials (local admin, LCM passwords)Secrets populated in prior phases
Arc-registered nodesAll nodes registered in Azure ArcVerify in Azure Portal
Identical local credentialsSame admin password on all nodesVerify login via WinRM
Active DirectoryDomain reachable from all nodesTest-ComputerSecureChannel
OU for Azure LocalDedicated OU in ADGet-ADOrganizationalUnit
LCM service accountDomain account with OU GenericAllVerify delegation

What You Need Ready

Before starting the deployment procedure, confirm each item:

  1. variables.yml fully populated — all 13 sections complete, including cluster_arm_deployment, accounts, network_intents, and compute.cluster_nodes
  2. Authenticated to Azure — run Connect-AzAccount (PowerShell) or az login (CLI) on the machine where you will execute commands
  3. powershell-yaml module installed — required by the generation script: Install-Module powershell-yaml -Scope CurrentUser
  4. Arc nodes registered — Phase 04 complete; arc_node_resource_ids populated in variables.yml
  5. Platform Key Vault secrets storedlocal-admin-password and lcm-deployment-password secrets exist in the platform Key Vault (from prior phases)
  6. AD infrastructure ready — domain reachable, OU created, LCM service account has GenericAll delegation on the OU
Where to Run From

All commands in this runbook run from the azl-toolkit repo root on a management/jump box authenticated to Azure. The toolkit scripts expect variables.yml at config/variables.yml relative to the repo root.

Key Vault Architecture

Two Key Vaults are involved in Azure Local deployment:

  • Platform Key Vault — Already exists from prior phases. Stores deployment credentials (local admin password, LCM domain service account password). Referenced in the parameter file via Key Vault references. No action needed here.
  • Cluster Key Vault — Created automatically by the ARM template when createNewKeyVault is true. The name (keyVaultName) and other settings are already defined in variables.yml.

Variables from variables.yml

PathTypeDescription
compute.arm_deployment.cluster_namestringCluster name
compute.arm_deployment.cluster_resource_groupstringCluster resource group
compute.cluster_nodes[*].ipv4_addressstringNode IP addresses
compute.cluster_nodes[*].arc_node_resource_idsstringArc resource IDs
cluster_arm_deployment.domain_ou_pathstringAD OU distinguished name
accounts.lcm_admin.usernamestringLCM admin username
cluster_arm_deployment.resource_provider_object_idstringHCI RP Object ID
security.keyvault.*objectKey Vault references
networking.*objectNetwork intents and IP allocation

Deployment Procedure

Task 1: Verify Active Directory Prerequisites

Confirm that each cluster node can reach the domain, the OU exists, and the LCM service account has the required delegation.

When to use: You want to verify each check individually using standard AD PowerShell commands.

Step 1 — Define variables from variables.yml
# Replace placeholders with values from your variables.yml
$nodeIPs = @("<NODE_IP_1>", "<NODE_IP_2>") # compute.cluster_nodes[*].ipv4_address
$ouPath = "<DOMAIN_OU_PATH>" # cluster_arm_deployment.domain_ou_path
$lcmUser = "<LCM_USERNAME>" # accounts.lcm_admin.username
Step 2 — Verify domain connectivity from each node
# Expected: True for every node
Invoke-Command -ComputerName $nodeIPs -ScriptBlock {
[pscustomobject]@{
Node = $env:COMPUTERNAME
SecureChannel = (Test-ComputerSecureChannel)
}
}

Expected output — every node returns SecureChannel = True:

Node SecureChannel
---- -------------
iic-01-n01 True
iic-01-n02 True
Step 3 — Verify OU exists
# Expected: Returns the OU object without error
Get-ADOrganizationalUnit -Identity $ouPath | Format-List Name, DistinguishedName
Step 4 — Verify LCM account has GenericAll on OU
$acl = Get-Acl "AD:\$ouPath"
$delegation = $acl.Access | Where-Object {
$_.IdentityReference -like "*$lcmUser*" -and
$_.ActiveDirectoryRights -match "GenericAll"
}

if ($delegation) {
Write-Host "PASS: $lcmUser has GenericAll on $ouPath" -ForegroundColor Green
$delegation | Format-Table IdentityReference, ActiveDirectoryRights
} else {
Write-Host "FAIL: $lcmUser does NOT have GenericAll on $ouPath" -ForegroundColor Red
}
Do Not Proceed If Checks Fail

If domain connectivity, OU existence, or LCM delegation checks fail, fix the AD configuration before continuing. Deployment will fail if these prerequisites are not met.


Task 2: Get HCI Resource Provider Object ID

The hciResourceProviderObjectID parameter requires the tenant-specific Object ID of the Microsoft.AzureStackHCI resource provider service principal. This GUID is unique per Entra ID tenant and must be looked up once, then stored in variables.yml at cluster_arm_deployment.resource_provider_object_id.

When to use: Quick one-time lookup.

PowerShell — look up the Object ID
$hciRP = Get-AzADServicePrincipal -DisplayName "Microsoft.AzureStackHCI Resource Provider"
$hciRP.Id
Azure CLI alternative
az ad sp list --display-name "Microsoft.AzureStackHCI Resource Provider" --query "[0].id" -o tsv

Expected output: A single GUID, e.g. ab12cd34-ef56-7890-ab12-cd34ef567890

Next: Open variables.yml, find cluster_arm_deployment.resource_provider_object_id, and paste the GUID as the value.


Task 3: Generate Parameter File

Create the deployment-ready JSON parameter file that maps all 54 ARM template parameters. This step produces the file used by Tasks 4 and 5.

When to use: You want to build the parameter file by hand or use a pre-built example as a starting point.

  1. Copy the template: configs/azure/arm-templates/04-cluster-deployment/azuredeploy.parameters.ad.json
  2. Replace all {{VARIABLE}} placeholders with values from your variables.yml

Key placeholders to replace (most common errors come from these):

PlaceholderYAML Source PathDescription
{{CLUSTER_NAME}}cluster_arm_deployment.cluster_nameCluster resource name
{{RESOURCE_GROUP}}azure.resource_groupTarget resource group
{{LOCATION}}azure.locationAzure region
{{KEY_VAULT_NAME}}cluster_arm_deployment.key_vault_nameCluster KV (created by ARM)
{{DIAGNOSTIC_STORAGE}}cluster_arm_deployment.arm_diagnostic_storage_account_nameKV diagnostics storage
{{WITNESS_STORAGE}}cluster_arm_deployment.cloud_witness_storage_accountCloud witness storage
{{LOCAL_ADMIN_USER}}accounts.local_admin.usernameLocal admin on all nodes
{{LCM_ADMIN_USER}}accounts.lcm_admin.usernameLCM sAMAccountName (no @domain)
{{HCI_RP_OBJECT_ID}}cluster_arm_deployment.resource_provider_object_idFrom Task 2
{{DOMAIN_FQDN}}cluster_arm_deployment.domain_fqdnAD domain FQDN
{{ADOU_PATH}}cluster_arm_deployment.domain_ou_pathOU distinguished name
{{NAMING_PREFIX}}cluster_arm_deployment.naming_prefixResource prefix

For localAdminPassword and AzureStackLCMAdminPassword, use Key Vault references (not plain text):

Key Vault reference format (points to platform Key Vault)
"localAdminPassword": {
"reference": {
"keyVault": {
"id": "/subscriptions/<SUB_ID>/resourceGroups/<RG>/providers/Microsoft.KeyVault/vaults/<PLATFORM_KV_NAME>"
},
"secretName": "local-admin-password"
}
}
  1. Verify all {{ placeholders are replaced: search the file for {{ — there should be zero matches
  2. Save as azuredeploy.parameters.ad.json in your working directory
Use an Example as Reference

Validated IIC examples exist for each networking pattern in configs/azure/arm-templates/examples/. Compare your file against the matching pattern — see the Example Parameter Files collapsible in the Reference section below.


Task 4: Validate Deployment

Run the ARM template in Validate mode first. Validation executes the full deployment pipeline — environment checks, connectivity tests, credential validation — but stops before provisioning. Fix all errors before proceeding to Deploy.

When to use: You built the parameter file manually in Task 3 and want to run validation directly.

Define deployment variables
# Values from variables.yml
$subscriptionId = "<SUB_ID>" # azure.subscription_id
$resourceGroup = "<RESOURCE_GROUP>" # azure.resource_group
$parametersFile = ".\azuredeploy.parameters.ad.json" # File from Task 3
$templateUri = "https://raw.githubusercontent.com/Azure/azure-quickstart-templates/master/quickstarts/microsoft.azurestackhci/create-cluster/azuredeploy.json"

# Set subscription context
Set-AzContext -SubscriptionId $subscriptionId
Ensure deploymentMode is set to Validate
# Check current mode in the parameters file
$json = Get-Content $parametersFile | ConvertFrom-Json
$currentMode = $json.parameters.deploymentMode.value
Write-Host "Current deploymentMode: $currentMode"

# If not "Validate", update it:
if ($currentMode -ne "Validate") {
$json.parameters.deploymentMode.value = "Validate"
$json | ConvertTo-Json -Depth 20 | Set-Content $parametersFile
Write-Host "Updated deploymentMode to Validate" -ForegroundColor Yellow
}
Run validation
$validateResult = New-AzResourceGroupDeployment `
-Name "azl-validate-$(Get-Date -Format 'yyyyMMddHHmmss')" `
-ResourceGroupName $resourceGroup `
-TemplateUri $templateUri `
-TemplateParameterFile $parametersFile `
-Verbose

$validateResult | Select-Object DeploymentName, ProvisioningState, Timestamp

Expected output for a successful validation:

DeploymentName ProvisioningState Timestamp
-------------- ----------------- ---------
azl-validate-20260309142055 Succeeded 3/9/2026 2:45:30 PM

If validation fails, check the error output against the Troubleshooting table.

Run the Validation Monitor

While validation runs, start Monitor-Validation.ps1 to see live step status and EnvironmentValidatorFull log output. It auto-exits when validation completes.

Validate First — Always

Validation runs the full deployment pipeline but stops before provisioning. Never skip validation. Fix all errors before switching to Deploy mode.


Task 5: Deploy Cluster

After validation passes, switch to Deploy mode and run the deployment. This provisions the cluster — the process takes 1.5–3 hours depending on node count.

When to use: You ran validation manually in Task 4 and want to continue with direct commands.

Step 1 — Switch deploymentMode to Deploy
$parametersFile = ".\azuredeploy.parameters.ad.json"

$json = Get-Content $parametersFile | ConvertFrom-Json
$json.parameters.deploymentMode.value = "Deploy"
$json | ConvertTo-Json -Depth 20 | Set-Content $parametersFile
Write-Host "Updated deploymentMode to Deploy" -ForegroundColor Green
Step 2 — Run deployment
$templateUri = "https://raw.githubusercontent.com/Azure/azure-quickstart-templates/master/quickstarts/microsoft.azurestackhci/create-cluster/azuredeploy.json"
$resourceGroup = "<RESOURCE_GROUP>" # azure.resource_group
$deploymentName = "azl-deploy-$(Get-Date -Format 'yyyyMMddHHmmss')"

$deployment = New-AzResourceGroupDeployment `
-Name $deploymentName `
-ResourceGroupName $resourceGroup `
-TemplateUri $templateUri `
-TemplateParameterFile $parametersFile `
-Verbose

$deployment | Select-Object DeploymentName, ProvisioningState, Timestamp
This is a Long-Running Operation

The New-AzResourceGroupDeployment command blocks until deployment completes (1.5–3 hours). Do not close the terminal. Use the monitoring script in a separate terminal to track progress.

Run the Deployment Monitor

After deployment starts, open a second terminal and run Monitor-Deployment.ps1 to track hierarchical step progress and stream OrchestratorFull logs in real time. Press Ctrl+C to exit at any time.

Expected Duration:

Node CountApproximate Time
1-node~1.5 hours
2-node~2.5 hours
4-node~3 hours

Task 6: Verify Deployment

After deployment completes successfully, verify the cluster is healthy, all nodes are online, storage is operational, and domain join is correct.

When to use: Verify the deployment step-by-step using standard commands.

Step 1 — Verify cluster resource in Azure
$resourceGroup = "<RESOURCE_GROUP>" # azure.resource_group
Get-AzResource -ResourceGroupName $resourceGroup `
-ResourceType "Microsoft.AzureStackHCI/clusters" |
Format-Table Name, Location, ProvisioningState

Expected: Cluster shows ProvisioningState = Succeeded

Step 2 — Connect to a node and verify cluster health
$nodeIP = "<NODE_IP>" # compute.cluster_nodes[0].ipv4_address
$cred = Get-Credential -Message "Enter local admin credentials"

Invoke-Command -ComputerName $nodeIP -Credential $cred -ScriptBlock {
Write-Host "=== Cluster Status ===" -ForegroundColor Cyan
Get-Cluster | Format-List Name, SharedVolumesRoot

Write-Host "`n=== Node Status ===" -ForegroundColor Cyan
Get-ClusterNode | Format-Table Name, State, StatusInformation
# Expected: All nodes State = Up

Write-Host "`n=== Storage Pool ===" -ForegroundColor Cyan
Get-StoragePool | Where-Object IsPrimordial -eq $false |
Format-Table FriendlyName, HealthStatus, OperationalStatus
# Expected: HealthStatus = Healthy

Write-Host "`n=== Domain Status ===" -ForegroundColor Cyan
Get-ClusterNode | ForEach-Object {
[pscustomobject]@{
Node = $_.Name
Domain = (Get-WmiObject -ComputerName $_.Name Win32_ComputerSystem).Domain
Secure = (Test-ComputerSecureChannel -Server $_.Name -ErrorAction SilentlyContinue)
}
} | Format-Table
# Expected: All nodes joined to correct domain, SecureChannel = True
}

Healthy cluster indicators:

CheckExpected Result
Azure resource stateProvisioningState = Succeeded
All cluster nodesState = Up
Storage poolHealthStatus = Healthy
Domain membershipAll nodes joined to correct domain
Secure channelTrue for every node

Troubleshooting

IssueCauseResolution
Key Vault access deniedMissing RBAC on platform KVGrant Key Vault Secrets User to deployment identity on the platform Key Vault
Domain join failsLCM account lacks OU permissionsVerify GenericAll delegation on the OU
Credential mismatchDifferent local passwords across nodesReset to identical password on all nodes
adouPath format errorIncorrect DN syntaxMust be full DN: OU=...,DC=...,DC=...
Validation fails on hciResourceProviderObjectIDWrong Object IDRe-run Get-AzADServicePrincipal (Task 2)
networkingPattern errorIntent layout doesn't match patternVerify intent count and traffic types match pattern
Deployment timeoutNetwork or Azure service issueCheck node Azure endpoint connectivity
Arc extension failedNode not reachable from AzureVerify WinRM/HTTPS and Arc agent status
deploymentMode stuck on ValidateForgot to switch modeRe-run the script with -DeploymentMode Deploy or edit the JSON
Parameter file has {{ placeholdersIncomplete placeholder replacementSearch file for {{ and replace all remaining

Deployment Logs

Check deployment logs on a cluster node
# Azure-side deployment operations
Get-AzResourceGroupDeploymentOperation `
-ResourceGroupName $resourceGroup `
-DeploymentName $deploymentName |
Where-Object { $_.ProvisioningState -ne "Succeeded" } |
Select-Object OperationId, ProvisioningState, @{N="Error";E={$_.StatusMessage}} |
Format-List

# Node-side logs
Invoke-Command -ComputerName $nodeIP -Credential $cred -ScriptBlock {
Get-ChildItem "C:\CloudDeployment\Logs" -Recurse |
Sort-Object LastWriteTime -Descending |
Select-Object -First 10 FullName, LastWriteTime
}

Next Steps

Deployment StatusNext Action
✅ Validation passedSwitch to Deploy mode (Task 5)
✅ Deployment succeededRun verification (Task 6), then proceed to Phase 06: Post-Deployment
❌ FailedReview Troubleshooting table and deployment logs

Reference

The sections below provide detailed reference material for the ARM template parameters, networking patterns, and examples. Expand each section as needed.

ARM Template Parameters — Complete Reference (54 parameters)

The Microsoft official ARM template (azuredeploy.json) contains 54 parameters organized into 10 functional groups. Required parameters (no defaultValue in the template) are marked with ⚠️.

Key Vault & Diagnostics

ParameterTypeRequiredDescriptionExample
keyVaultNamestring⚠️Cluster deployment Key Vault namekv-iic-deploy-01
createNewKeyVaultboolCreate KV or use existingtrue
softDeleteRetentionDaysintKV soft-delete retention (7–90)30
diagnosticStorageAccountNamestring⚠️Storage for KV audit logsstiicdiag01
logsRetentionInDaysintKV log retention days30
storageAccountTypestringKV diagnostic storage SKUStandard_LRS

Cluster Identity

ParameterTypeRequiredDescriptionExample
clusterNamestring⚠️Azure Local cluster resource nameiic-clus01
locationstringAzure regioneastus
tenantIdstringEntra ID tenant GUIDa1b2c3d4-...
deploymentModestringValidate first, then DeployValidate

Cluster Witness

ParameterTypeRequiredDescriptionExample
witnessTypestringCloud (recommended)Cloud
clusterWitnessStorageAccountNamestringCloud witness storage accountstiicwitness01

Credentials & Admin Accounts

ParameterTypeRequiredDescriptionExample
localAdminUserNamestring⚠️Local admin on all nodesosfadmin
localAdminPasswordsecurestring⚠️Key Vault reference for password(KV ref)
AzureStackLCMAdminUsernamestring⚠️LCM admin — sAMAccountName only, no @domain (domain comes from domainFqdn)svc-azlocal-lcm
AzureStackLCMAdminPasswordsecurestring⚠️Key Vault reference for LCM pwd(KV ref)
hciResourceProviderObjectIDstring⚠️Microsoft.AzureStackHCI RP Object IDabcdef01-...

Active Directory Authentication

ParameterTypeRequiredDescriptionAD Value
domainFqdnstringAD domain FQDNad.azurelocal.cloud
adouPathstringOU distinguished nameOU=AzureLocal,OU=Servers,DC=ad,DC=azurelocal,DC=cloud
namingPrefixstringResource naming prefixiic-01
AD-Specific Parameters
  • domainFqdn must be the FQDN (not NetBIOS)
  • adouPath must be the full distinguished name
  • AzureStackLCMAdminUsername must be the sAMAccountName only (e.g., svc-azlocal-lcm) — do NOT pass the full UPN with @domain. The domain is already provided via domainFqdn. Azure Local's Initialize-CloudDeployment.ps1 rejects usernames containing @ or \.
  • The LCM service account needs GenericAll on the specified OU

Arc Integration

ParameterTypeRequiredDescriptionExample
arcNodeResourceIdsarrayArc-registered node resource IDs(array of resource ID strings)

Security Settings

ParameterTypeDefaultDescription
securityLevelstringRecommendedRecommended or Customized
driftControlEnforcedbooltrueReapply security defaults regularly
credentialGuardEnforcedbooltrueVBS credential protection
smbSigningEnforcedbooltrueSMB traffic signing
smbClusterEncryptionboolfalseSMB in-transit encryption
bitlockerBootVolumebooltrueBoot volume encryption
bitlockerDataVolumesbooltrueData volume encryption
wdacEnforcedbooltrueWindows Defender Application Control

Telemetry & Observability

ParameterTypeDefaultDescription
streamingDataClientbooltrueTelemetry streaming to Microsoft
euLocationboolfalseEU data residency compliance
episodicDataUploadbooltrueCrash dumps and diagnostics

Storage Configuration

ParameterTypeDefaultDescription
configurationModestringExpressExpress (auto) or InfraOnly

Networking

ParameterTypeDescriptionExample
subnetMaskstringManagement subnet mask255.255.255.0
defaultGatewaystringManagement gateway192.168.100.1
startingIPAddressstringCluster IP pool start192.168.100.30
endingIPAddressstringCluster IP pool end192.168.100.39
dnsServersarrayDNS servers["192.168.100.10"]
useDhcpboolUse DHCP for managementfalse
networkingTypestringDeployment topologyswitchedMultiServerDeployment
networkingPatternstringIntent layoutconvergedManagementCompute
intentListarrayNetwork intent definitions(see examples)
storageNetworkListarrayStorage VLAN mappings(see examples)
storageConnectivitySwitchlessboolSwitchless storagefalse
enableStorageAutoIpboolAuto-IP for storagetrue
physicalNodesSettingsarrayNode name + IP list(see examples)

SBE (Solution Builder Extension)

ParameterTypeDescriptionExample
sbeVersionstringSBE package version4.1.2411.1
sbeFamilystringSBE family/categoryAzureLocal
sbePublisherstringSBE vendorDell
sbeManifestSourcestringManifest URL""
sbeManifestCreationDatestringManifest date""
partnerPropertiesarrayPartner key-value pairs[]
partnerCredentiallistarrayPartner credentials[]

Other

ParameterTypeDescriptionExample
customLocationstringArc custom location resource ID""
Networking Pattern & Type Reference

networkingPattern Values

PatternIntentsDescriptionUse Case
hyperConverged1All traffic (Mgmt+Compute+Storage) on one adapter pairSmall clusters, 2 NICs/node
convergedManagementCompute2Mgmt+Compute shared; Storage dedicatedStandard production (4 NICs/node)
convergedComputeStorage2Compute+Storage shared; Mgmt dedicatedRare; management-isolated
custom3Fully disaggregated (Mgmt / Compute / Storage)Large clusters, 6 NICs/node

networkingType Values

TypeNode CountStorage Connectivity
switchedMultiServerDeployment2+ nodesTop-of-rack switches
switchlessMultiServerDeployment2–3 nodesDirect-connect (no TOR for storage)
singleServerDeployment1 nodeN/A
Key Vault Reference Syntax

Sensitive parameters (localAdminPassword, AzureStackLCMAdminPassword) use Key Vault references that point to the platform Key Vault — the vault provisioned in prior phases where deployment credentials are already stored:

Key Vault reference pointing to platform Key Vault
"localAdminPassword": {
"reference": {
"keyVault": {
"id": "/subscriptions/<SUB_ID>/resourceGroups/<RG>/providers/Microsoft.KeyVault/vaults/<PLATFORM_KV_NAME>"
},
"secretName": "local-admin-password"
}
}

This is not the cluster Key Vault (keyVaultName) — that vault is created by the ARM template during deployment.

Never Store Passwords in Plain Text

Always use Key Vault references to the platform Key Vault for sensitive parameters.

Example Parameter Files by Networking Pattern

Azure Local Cloud maintains validated IIC examples for each networking pattern in: azl-toolkit/configs/azure/arm-templates/examples/

Layout: All traffic on 2 × 25 Gbps adapters per node Pattern: hyperConverged

NIC1 (25 Gbps) ──┐
├── Management + Compute + Storage
NIC2 (25 Gbps) ──┘

See: examples/single-intent-converged/azuredeploy.parameters.ad.json


References: