| name | automatedlab-deployment |
| compatibility | Requires Windows with Hyper-V enabled, the AutomatedLab PowerShell module, and operating-system ISO images under LabSources. |
| description | Build and manage Hyper-V lab environments with AutomatedLab (AL): installation, lab definitions, roles (AD, File Server, Routing, PKI, SQL), networking (incl. NAT), post-deploy configuration, VM status, snapshots, file transfer, DSC, certificates, firewall, and teardown. USE FOR: AutomatedLab, Hyper-V lab, deploy/create lab, domain controller, lab VMs, ISO images, LabSources, Install-Lab, New-LabDefinition, Add-LabMachineDefinition, Remove-Lab, Get-LabVM*, Wait-LabVM, Wait-LabADReady, Restart-LabVM, Save-LabVM, Copy-LabFileItem, Get-LabInternetFile, Invoke-LabDscConfiguration, Test-LabMachineInternetConnectivity, Join-LabVMDomain, Enable/Disable-LabVMFirewallGroup, Add-LabVMUserRight, Request-LabCertificate, Mount-LabIsoImage, Connect-Lab, Enable-LabInternalRouting, Add-LabVirtualNetworkDefinition -UseNat, NetNat, HyperVUseNAT, CredSSP double-hop, Enable-LabHostRemoting. DO NOT USE FOR: AutomatedLab on Proxmox/QEMU (use automatedlab-proxmox), Azure VM deployment, production infrastructure. |
AutomatedLab — Hyper-V Lab Deployment
Skill for creating, configuring, and managing local Hyper-V lab environments
using AutomatedLab.
When to Use
- Creating a new Hyper-V lab with domain controllers, member servers, or clients
- Adding roles (AD, File Server, Routing, PKI, SQL, Exchange, etc.)
- Configuring lab networking, internet connectivity, or multi-site topologies
- Post-deployment tasks (shares, software installation, DSC)
- Troubleshooting lab deployment issues
- Tearing down and recreating labs
For Proxmox/QEMU-backed AutomatedLab failures, use
automatedlab-proxmox; recipes in this skill assume Hyper-V.
Pre-flight Checks
AutomatedLab requires local administrator privileges. Every generated
deployment script must verify admin rights before calling any AL cmdlet.
Admin-rights guard (mandatory in every script)
$currentIdentity = [Security.Principal.WindowsIdentity]::GetCurrent()
$principal = [Security.Principal.WindowsPrincipal]$currentIdentity
if (-not $principal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)) {
throw 'This script must run in an elevated (Run as Administrator) PowerShell session.'
}
Place this as the first executable statement in the script, before
New-LabDefinition or any other AL call. Using #Requires -RunAsAdministrator
is also acceptable but the runtime check above gives a clearer error message
when invoked from non-interactive contexts (e.g., CI/CD pipelines, VS Code
terminals).
Installation
# Install the module from PSGallery (elevated PowerShell required)
Install-PackageProvider -Name Nuget -Force
Install-Module -Name AutomatedLab -SkipPublisherCheck -Force
# One-time host preparation
Enable-LabHostRemoting -Force
# Create the LabSources folder structure (adjust drive letter as needed)
New-LabSourcesFolder -DriveLetter C
# Opt out of telemetry (optional)
[Environment]::SetEnvironmentVariable('AUTOMATEDLAB_TELEMETRY_OPTIN', 'false', 'Machine')
$env:AUTOMATEDLAB_TELEMETRY_OPTIN = 'false'
ISO Images
Place ISO files in <LabSources>\ISOs\. The LabSources folder location
varies per installation — always resolve it dynamically:
$labSources = Get-LabSourcesLocation # e.g. C:\LabSources, E:\LabSources, etc.
Never hardcode C:\LabSources. Use Get-LabSourcesLocation in scripts and
commands so they work on any host regardless of where LabSources was created.
Download evaluation ISOs from:
Verify detected OS names:
Get-LabAvailableOperatingSystem -Path (Get-LabSourcesLocation) | Select-Object OperatingSystemName
VM Storage Path Detection
Before creating a new lab, check if an AutomatedLab-VMs folder already
exists on any drive. If found, use that location for the new lab's VM
disks. Do not create a second AutomatedLab-VMs folder on another drive.
# Scan all fixed drives for an existing AutomatedLab-VMs folder
$existingVmPath = Get-CimInstance -ClassName Win32_LogicalDisk -Filter "DriveType = 3" |
ForEach-Object {
$candidate = Join-Path $_.DeviceID 'AutomatedLab-VMs'
if (Test-Path -Path $candidate) { $candidate }
} | Select-Object -First 1
if ($existingVmPath) {
Write-Host "Reusing existing VM path: $existingVmPath"
New-LabDefinition -Name 'MyLab' -DefaultVirtualizationEngine HyperV `
-VmPath $existingVmPath
} else {
# Let AL use its default location
New-LabDefinition -Name 'MyLab' -DefaultVirtualizationEngine HyperV
}
Why this matters: Hyper-V hosts often have a dedicated data disk for
VM storage. If an operator has already set up D:\AutomatedLab-VMs or
E:\AutomatedLab-VMs, creating VMs on a different drive wastes space
and causes confusion.
VM Name Collision Avoidance
Before deploying a lab, check that none of the planned VM names collide
with VMs defined in other existing labs. AutomatedLab will fail if a VM
with the same name already exists.
This check must be engine-agnostic — it must work for Hyper-V, Azure,
and any future engine (e.g. Proxmox). Do not use Get-VM (Hyper-V
only). Instead, use Import-Lab + Get-LabVM to query each existing
lab — this works for all engines because AL stores lab metadata locally.
Pre-flight: detect collisions (mandatory in every deployment script)
# List of planned VM names for this lab
$plannedNames = @('CL1', 'CL2') # ← adjust to match your lab machines
# Collect all machine names from existing AL labs (engine-agnostic)
$existingNames = [System.Collections.Generic.List[string]]::new()
foreach ($lab in (Get-Lab -List)) {
Import-Lab -Name $lab -NoValidation -ErrorAction SilentlyContinue
(Get-LabVM).Name | ForEach-Object { $existingNames.Add($_) }
}
$collisions = $plannedNames | Where-Object { $_ -in $existingNames }
if ($collisions) {
Write-Warning "VM name collision detected: $($collisions -join ', ')"
Write-Warning 'These names are already used in existing AutomatedLab labs.'
# Auto-generate a short unique prefix (2 uppercase letters) to avoid collisions
do {
$prefix = -join ((65..90) | Get-Random -Count 2 | ForEach-Object { [char]$_ })
$prefixedNames = $plannedNames | ForEach-Object { "$prefix$_" }
$stillCollides = $prefixedNames | Where-Object { $_ -in $existingNames }
} while ($stillCollides)
Write-Host "Using name prefix '$prefix' to avoid collisions (e.g. $($prefixedNames[0]))"
Set-LabGlobalNamePrefix -Name $prefix
}
Why this matters: Common names like DC1, CL1, FS1 are reused
across labs. If a previous lab was not fully cleaned up, or another lab
is still running, the deployment will fail with a name collision error.
Why not Get-VM? Get-VM is a Hyper-V cmdlet. It does not work
for Azure labs, and will not work for future engines like Proxmox.
Import-Lab + Get-LabVM works for all engines because AL stores
lab metadata locally regardless of the virtualisation backend.
Note: Import-Lab requires elevation and overwrites the "current
lab" global state, but deployment scripts already require elevation
and New-LabDefinition (called afterward) resets the lab context.
The collision check must run before New-LabDefinition.
Set-LabGlobalNamePrefix prepends the prefix to every VM name
defined afterward.
Core Workflow
Every AutomatedLab deployment follows this pattern:
# 0. Pre-flight — verify local administrator rights
$currentIdentity = [Security.Principal.WindowsIdentity]::GetCurrent()
$principal = [Security.Principal.WindowsPrincipal]$currentIdentity
if (-not $principal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)) {
throw 'This script must run in an elevated (Run as Administrator) PowerShell session.'
}
# 1. Define the lab
New-LabDefinition -Name 'MyLab' -DefaultVirtualizationEngine HyperV
# 2. Set credentials & domain
Set-LabInstallationCredential -Username 'Install' -Password 'Somepass1'
Add-LabDomainDefinition -Name 'contoso.com' -AdminUser 'Install' -AdminPassword 'Somepass1'
# 3. Define network(s)
Add-LabVirtualNetworkDefinition -Name 'LabNet' -AddressSpace '192.168.100.0/24'
# 4. Define machines with roles
Add-LabMachineDefinition -Name 'DC1' -OperatingSystem 'Windows Server 2025 Standard (Desktop Experience)' `
-Roles RootDC -DomainName 'contoso.com' -Network 'LabNet'
# 5. Deploy (use -NoValidation to bypass known validator bugs — see Troubleshooting)
Install-Lab -NoValidation
# 6. Review
Show-LabDeploymentSummary
Operating System Name Strings
The -OperatingSystem parameter must match exactly what AL detects on the ISO.
Common values (may vary by ISO edition):
| OS | Typical String |
|---|
| Server 2025 Standard (GUI) | Windows Server 2025 Standard (Desktop Experience) |
| Server 2025 Datacenter (GUI) | Windows Server 2025 Datacenter (Desktop Experience) |
| Server 2022 Standard (GUI) | Windows Server 2022 Standard (Desktop Experience) |
| Server 2022 Datacenter (GUI) | Windows Server 2022 Datacenter (Desktop Experience) |
| Windows 11 Enterprise | Windows 11 Enterprise |
| Windows 11 Pro | Windows 11 Pro |
| Windows 10 Enterprise | Windows 10 Enterprise |
Consumer vs. Evaluation ISOs: Windows 11 consumer ISOs contain Pro/Home
editions but not Enterprise. If you downloaded a consumer ISO, use
'Windows 11 Pro'. Enterprise requires the separate evaluation or VLSC ISO.
Always verify with Get-LabAvailableOperatingSystem — edition strings change
between ISOs and evaluation vs. retail media.
Networking
Virtual network definitions, switch types (External/Internal/Private), NAT setup, Enable-LabInternalRouting, Add-LabVirtualNetworkDefinition -UseNat, HyperVUseNAT notes, and DNS/IP-range planning — read references/networking.md.
Roles, File Server, Machine Definitions, Internet, Chocolatey
Active Directory roles, File Server role, Add-LabMachineDefinition parameters, post-deployment internet connectivity fix, and Chocolatey-based software install — read references/roles-and-services.md.
Post-Deployment Operations
Invoke-LabCommand, scriptblock vs file modes, -PassThru, -NoDisplay, -AsJob, -ComputerName patterns, CredSSP for double-hop, and elevated-context recipes — read references/post-deployment-operations.md.
VM Operations & Lifecycle
Get-LabVM* queries, Wait-LabVM / Wait-LabADReady operations, restart/save/snapshot lifecycle, Copy-LabFileItem/Get-LabInternetFile, and PSSession management — read references/vm-operations.md.
Lab Management
DSC configuration, testing & validation, domain operations (Join-LabVMDomain), firewall, PKI/certs (Request-LabCertificate), disk/ISO management (Mount-LabIsoImage), lab data paths, pre/post-install activities, base-image caching, and lab lifecycle — read references/lab-management.md.
Retrieving Lab Metadata After Import
After Import-Lab, use Get-Lab to retrieve the lab object. This is the
correct way to obtain the domain name, network info, and other lab-level
configuration. Never hardcode domain names — always derive them from
the lab definition so scripts work regardless of how the lab was deployed.
Import-Lab -Name 'MyLab' -NoValidation
# Lab object contains domains, networks, machines, and metadata
$lab = Get-Lab
# Domain FQDN (e.g. 'contoso.com')
$domainName = $lab.Domains[0].Name
# Derive DN and NetBIOS from the FQDN
$domainDN = ($domainName -split '\.' | ForEach-Object { "DC=$_" }) -join ','
$domainNetBIOS = ($domainName -split '\.')[0]
# Resolve VM names (accounts for global name prefix)
$labVMs = Get-LabVM
$dc1Name = ($labVMs | Where-Object { $_.Name -like '*DC1' }).Name
Why this matters: Deployment scripts accept -DomainName as a
parameter (e.g. contoso.com, mylab.local). Post-deployment and
break/fix scripts that hardcode a specific domain name will fail
when the lab was deployed with a different domain. Always use
Get-Lab to retrieve the actual domain.
Useful Get-Lab properties:
| Property | Example Value | Description |
|---|
$lab.Domains[0].Name | contoso.com | Primary domain FQDN |
$lab.Domains[0].Administrator | contoso\Install | Domain admin account |
$lab.Name | MyLab | Lab name |
$lab.DefaultVirtualizationEngine | HyperV | Virtualisation engine |
Pass derived values into Invoke-LabCommand scriptblocks using $using::
Invoke-LabCommand -ComputerName $dc1Name -ScriptBlock {
$zone = "_msdcs.$($using:domainName)"
Get-DnsServerResourceRecord -ZoneName $zone -RRType SRV
}
Reference Lab: Domain + File Server + Client + Internet
A complete lab script is maintained at the project root:
- Script:
Deploy-AutomatedLab.ps1
- Machines: DC1 (RootDC), DC2 (DC), FS1 (FileServer), RTR1 (Routing), CL1 (Win11 client)
- Domain:
contoso.com
- Network:
192.168.100.0/24 internal + Default Switch (External) for internet
- Post-deploy: Creates
\\FS1\Data share, takes a baseline snapshot
Troubleshooting
Hyper-V symptoms, WinRM/CredSSP failures, AD-readiness races, ISO mount issues, base-image corruption, and post-install hangs — read references/troubleshooting.md.
Common Roles Quick Reference
| Role | Purpose |
|---|
RootDC | Forest root domain controller |
FirstChildDC | Child or tree domain controller |
DC | Additional domain controller |
ADDS | AD DS without automatic promotion |
FileServer | Windows File Server role |
WebServer | IIS Web Server |
DHCP | DHCP Server |
Routing | RRAS router / NAT gateway |
CaRoot | Enterprise Root CA |
CaSubordinate | Subordinate CA |
SQLServer2022 | SQL Server 2022 (needs ISO) |
SQLServer2019 | SQL Server 2019 (needs ISO) |
SQLServer2017 | SQL Server 2017 (needs ISO) |
SQLServer | Generic SQL Server role |
Exchange2019 | Exchange Server (needs ISO + prereqs) |
DSCPullServer | DSC Pull Server with SQL reporting |
WindowsAdminCenter | WAC portal |
HyperV | Nested Hyper-V role |
FailoverNode | Failover cluster member |
FailoverStorage | Failover cluster shared storage |
ADFS | Active Directory Federation Services |
ADFSWAP | ADFS Web Application Proxy |
Office2013 | Office 2013 deployment |
Office2016 | Office 2016 deployment |
AzDevOps | Azure DevOps Server (formerly TFS) |
Tfs2018 | Team Foundation Server 2018 |
TfsBuildWorker | TFS/Azure DevOps build agent |
Cmdlet Quick Reference
Complete table of AutomatedLab cmdlets grouped by area (definition, deployment, query, lifecycle, command execution, network, disk, file transfer) — read references/cmdlet-reference.md.
Links