Writing a Blueprint for Windows VM Automation

Last updated: March 4, 2026


Table of Contents


Why Windows VMs in a Blueprint?

When I started using NCM Self-Service on my CE cluster, most examples I found were Linux-focused: cloud-init, bash scripts, and SSH. But in my home lab I run several Windows workloads β€” a Windows Server 2022 domain controller, a SQL Server 2022 instance, and a Windows 10 test desktop for application testing.

Provisioning those by hand every time was painful. Windows has its own unattended setup mechanism (Sysprep + Autounattend.xml), its own remote execution protocol (WinRM), and its own scripting language (PowerShell). Getting a Blueprint to fully automate a Windows VM end-to-end took me longer than I expected, mostly because of Cloudbase-Init quirks. This article documents what actually works.


CE Prerequisites

Before writing a Windows Blueprint, confirm your CE environment has:

  • Prism Central deployed and registered to your cluster

  • NCM Self-Service enabled in Prism Central (Settings β†’ Enable App Management)

  • A Windows Server 2022 image uploaded to Prism Central Image Service (sysprepped, see below)

  • VirtIO drivers installed in the image (required for AHV disk and NIC recognition)

  • Cloudbase-Init installed in the image (the Windows equivalent of cloud-init)

  • A network with reachable DNS (WinRM requires name resolution to work reliably)

Enable NCM Self-Service on CE

Once enabled, the Services menu in Prism Central shows Calm (the NCM Self-Service icon).


Preparing the Windows Image

The Nutanix AHV hypervisor uses VirtIO for paravirtualized disk and NIC drivers. A standard Windows ISO does not include these β€” you must inject them before or during installation.

Step 1 – Download VirtIO ISO

Download the Nutanix-validated VirtIO ISO from:

Step 2 – Install Windows with VirtIO

During Windows Server 2022 installation:

  1. Attach both the Windows Server ISO and the VirtIO ISO as CD-ROMs in AHV

  2. When the installer asks "Where do you want to install Windows?" and shows no disks, click Load Driver

  3. Browse to the VirtIO CD β†’ viostor\2k22\amd64 β†’ load the SCSI driver

  4. Disk appears; proceed with installation

  5. After Windows boots, install remaining VirtIO drivers by running the VirtIO installer from the CD:

Step 3 – Install Cloudbase-Init

Cloudbase-Init reads metadata from the AHV hypervisor (via a config drive or HTTP metadata endpoint) and runs setup tasks on first boot.

Step 4 – Configure Cloudbase-Init

Edit C:\Program Files\Cloudbase Solutions\Cloudbase-Init\conf\cloudbase-init.conf:

Step 5 – Sysprep and Shutdown

Run Sysprep to generalize the image so new VMs get unique SIDs and machine names:

Minimal unattend.xml to skip OOBE prompts:

After Sysprep shuts down the VM, take a Nutanix VM snapshot from Prism Element. Then use that snapshot as the basis for an AHV disk image uploaded to the Image Service via acli:

Or upload directly via Prism Central β†’ Image Service β†’ Add Image β†’ From URL.


Sysprep and Cloudbase-Init Primer

When AHV creates a VM from a sysprepped image, the boot sequence is:

The Blueprint create action runs its Tasks after Cloudbase-Init completes, so by the time your shell scripts run, the VM is fully initialized with the hostname and credentials you specified.


Blueprint Structure for a Windows VM

A Windows single-VM Blueprint consists of:


Writing the Blueprint – Step by Step

Define the Service

  1. Click + Service on the canvas β†’ name it WindowsVM

  2. In the Service panel (right side):

    • Service Name: WindowsVM

    • Variable Name: WindowsVM (used in macros as @@{WindowsVM.address}@@)

Configure the Substrate

Under the Service β†’ VM tab:

Guest Customization Script (Sysprep XML injected via config drive)

In the Customization Script field, paste the unattend.xml with password macro:

@@{admin_password}@@ is a Blueprint variable marked as Secret / Hidden. The value is injected at deploy time.


Credentials and WinRM Configuration

Define Credentials

Enable WinRM in the Create Action

WinRM is not enabled by default on Windows Server. Your Blueprint's Create action must enable it before any subsequent Tasks can connect.

Add the first Task in the Create action as a Shell (Execute) task run locally (on the Blueprint engine, not via WinRM) β€” this task polls until WinRM is responsive:

Task: Wait for WinRM

Task: Enable WinRM (via Run Script Execute on VM)

Wait β€” before WinRM is up, how do I run a script on the VM? The answer is the AHV Guest Script mechanism. In Prism Central, you can inject a first-boot PowerShell script via user-data in the config drive. Add this to the Cloudbase-Init user-data section:

In Substrate β†’ Guest Customization β†’ User Data (separate from Sysprep):

This runs automatically on first boot via Cloudbase-Init before your Blueprint Tasks fire.


Install Tasks via PowerShell

Once WinRM is reachable, subsequent Tasks run over WinRM using the win-admin credential.

Task: Install IIS

Task: Configure IIS Site

Task: Set VM Output Variables

Set the VM's IP as an application output for downstream use:

Mark vm_ip as an output variable in the Task configuration. It becomes available in the Application summary after deployment.


Day 2 Actions for Windows VMs

Action: Windows Update

Action: Join Domain

Action: Snapshot Before Patch


Using calm-dsl to Define the Blueprint as Code

The GUI is fine for initial design, but I version-control all my Blueprints using calm-dsl.

Install calm-dsl

Windows VM Blueprint in Python

Push Blueprint to Prism Central


Publishing to the Marketplace

Once the Blueprint is tested, publish it so other users can launch Windows VMs without needing Blueprint edit permissions.

Users with the Consumer role can now launch a Windows Server VM from the Marketplace without touching the Blueprint itself.


Troubleshooting Common Windows Blueprint Issues

Symptom
Likely Cause
Fix

VM boots but Cloudbase-Init never runs

Sysprep not run before image creation, or Cloudbase-Init service disabled

Verify cloudbase-init service is set to Automatic in the template

WinRM connection refused

WinRM not enabled in user-data, or Windows Firewall blocking port 5985

Check user-data script executed; add firewall rule manually and test winrm id -remote:http://IP:5985

Blueprint Task fails: "Auth failed"

Password macro not resolving or wrong credential bound

Verify @@{admin_password}@@ variable is Secret, runtime=True, and mapped correctly

Hostname not set from macro

Cloudbase-Init SetHostNamePlugin not in plugin list

Add SetHostNamePlugin to cloudbase-init.conf plugins list

Sysprep error 0x800f0906

.NET Framework install failed during Sysprep generalize

Install all Windows Updates before Sysprep; avoid Sysprep while .NET is pending reboot

VM IP is 169.254.x.x (APIPA)

DHCP not assigned; VLAN misconfigured

Verify VM NIC is on correct VLAN, DHCP scope has available IPs

calm-dsl readiness probe times out

WinRM takes > 300 seconds to become ready

Increase connection_timeout in readiness probe spec, or add a longer Wait task


Next Steps


Tags: Nutanix, Blueprint, NCM Self-Service, Windows Server, calm-dsl, Cloudbase-Init, Sysprep, WinRM, PowerShell, Community Edition

Last updated