Automating a DC deployment in Hyper-V

7 min readOct 6, 2022

TL;DR how to automatically deploy a DC in Hypver-V and some tweaks that were required.

Welcome to Part V of our Back to the Basics Series!

Part I: NTDS.dit vs SAM

Part II: Ownership Matters

Part III: Recovering from a Crash

Part IV: Setting up a Simple Honeypot Account

Part V: Automating DC Deployment

Part VI: Sometimes it’s the dumbest thing

Part VII: Merry Christmas, macros, & Base64

Part VIII: Why old 0 Days make great teaching tools

Part IX: PowerShell & PS1s without PowerShell.exe

Part X: Ownership & so called “effective permissions”

Part XI: Windows Event Forwarding & SACLs

Part XII: Poorly planned honeypots & other Bad Ideas

Part XIII: Setting up a simple honey token

Part XIV: Smartcards and Pass-the-Hash

Part XV: Forwarding logs to Sentinel & basic alerts


We covered migrating a DC and the FSMO roles awhile back. More recently we covered recovering from a crash of the PDC. This is my notes from automating deployment of a DC while following along with the book PowerShell for Sysadmins. I had to

  • Download the newest copy of Convert-WindowsImage.ps1
  • Tweak the parameters being passed to it
  • Create a new answer file
  • Figure out why Hyper-V wouldn’t connect the VM to the outside world

Hence I ended up making a bunch of notes and figured I might as well post them here in case it helps anyone else.

This process was done on a member server in test.local running Hyper-V.

Provisioning the VM

Once I got it working this takes an ISO, answer file, and some parameters and creates a Hyper-V VM that is all ready to boot up and start using. An answer file is basically an XML that contains the answers to all the questions that Windows asks during a standard OS installation. You can create one using the Windows System Installation Manager. There is a step by step walkthrough of that process here.

I had to download the newest copy of Convert-WindowsImage.ps1 from the PowerShell gallery. I then had to tweak the author’s VM setup as the newer conversion took different parameters. I simply commented out the parts I had to change and put my tweak on the line below it.

To confirm the exact value to pass to the Edition parameter:

Mount-DiskImage -ImagePath “<path to ISO>"dism /Get-WimInfo /WimFile:F:\sources\install.wim /index:4


#VHDPartitionStyle parameter does not exist in the newest Convert-WindowsImage.ps1 from PowerShell Gallery.
#$isoFilePath = ‘C:\PowerLab\ISOs\en_windows_server_2016_x64_dvd_9718492.iso’
$isoFilePath = “C:\PowerLab\ISOs\Windows_Server_2016_Datacenter_EVAL_en-us_14393_refresh.ISO”#$answerFilePath = ‘C:\PowerShellForSysAdmins\Part II\Automating Operating System Installs\LABDC.xml’
$answerFilePath = “C:\PowerLab\autounattend.xml”
$convertParams = @{
SourcePath = $isoFilePath
SizeBytes = 40GB
#Edition = ‘ServerStandardCore’
Edition = ‘Windows Server 2016 Datacenter Evaluation (Desktop Experience)’
VHDFormat = ‘VHDX’
VHDPath = ‘C:\PowerLab\VMs\LABDC\LABDC.vhdx’
DiskLayout = ‘UEFI’
#VHDType = ‘Dynamic’
#VHDPartitionStyle = ‘GPT’
UnattendPath = $answerFilePath
#. ‘C:\PowerShellForSysAdmins\Part II\Automating Operating System Installs\Convert-WindowsImage.ps1’Set-ExecutionPolicy -ExecutionPolicy Bypass -Scope CurrentUser
. ‘C:\PowerShellForSysAdmins\Convert-WindowsImage (from PS Gallery)\Convert-WindowsImage.ps1’
Convert-WindowsImage @convertParamsNew-VMSwitch -Name ‘PowerLab’ -NetAdapterName “Ethernet0”
New-VM -Name “LABDC” -Path “C:\PowerLab\VMs\LABDC” -MemoryStartupBytes 4GB -Generation 2
Connect-VMNetworkAdapter -VMName LABDC -Name “Network Adapter” -SwitchName PowerLab
$vm = Get-Vm -Name ‘LABDC’
$vm | Add-VMHardDiskDrive -Path ‘C:\PowerLab\VMs\LABDC\LABDC.vhdx’
$bootOrder = ($vm | Get-VMFirmware).Bootorder
if ($bootOrder[0].BootType -ne ‘Drive’) {
$vm | Set-VMFirmware -FirstBootDevice $vm.HardDrives[0]

Configure the VM’s NIC

Get-Credential | Export-Clixml -Path “C:\PowerLab\DomainCredential.xml”
$cred = Import-Clixml -Path “C:\PowerLab\DomainCredential.xml”
#verify the interface’s name
Invoke-Command -VMName LABDC -ScriptBlock {Get-NetAdapter} -Credential $cred
#set a static IP, subnet, & gateway
Invoke-Command -VMName LABDC -ScriptBlock {New-NetIPAddress –InterfaceAlias “Ethernet 2” –IPAddress “” –PrefixLength 24 -DefaultGateway “”} -Credential $cred
#set the DNS order to itself, TestDC, ISP
Invoke-Command -VMName LABDC -ScriptBlock {Set-DNSClientServerAddress -InterfaceAlias “Ethernet 2” -ServerAddresses (“”, “<ISP's DNS>”, “<ISP's DNS>”)} -Credential $cred
Invoke-Command -VMName LABDC -ScriptBlock {ping} -Credential $cred

Storing credentials in a variable is a nifty feature I had not used before. My home lab is a ‘flat domain’ with SSO, so Mishka can just login to a workstation and go. However here we are working with a Hyper-V host that is a member server on test.local and running a VM that is the PDC for powerlab.local. There is no trust relationship between the two domains. Therefore to use PowerShell Direct, PSSession, Invoke-Command, or really any method one has to authenticate all over again.

This method stores the credentials in an encrypted format in XML, so only the user who stored them can use them. If anyone else opens the XML they’ll just see gibberish.

Going into promiscuous mode

I ran into an issue at this point. The Hyper-V VM could ping the host OS, a Windows Server 2019 VM running in ESXi, but could not reach anything else. Luckily a wise individual on Reddit had the answer. It turns out that you have to set the ESXi virtual switch that the Hyper-V host is using to allow

  • Promiscuous mode
  • MAC address changes
  • Forged transmits

Set Hyper-V to allow MAC address spoofing.

Make the VM a DC

$cred = Import-Clixml -Path “C:\PowerLab\DomainCredential.xml”Invoke-Command -VMName LABDC -ScriptBlock {Install-WindowsFeature -Name AD-Domain-Services} -Credential $cred#$DSRMpwd = Import-Clixml -Path “C:\PowerLab\DSRMpwd.xml”
#-SafeModeAdministratorPassword “$DSRMpwd”
Invoke-Command -VMName LABDC -ScriptBlock {Install-ADDSForest -DomainName ‘powerlab.local’ -DomainMode ‘WinThreshold’ -ForestMode ‘WinThreshold’} -Credential $cred#verify, user powerlab.local credentials
Invoke-Command -VMName LABDC -ScriptBlock {Get-ADUser -Filter *} -Credential “ “

If you are making the DC a backup in an existing domain, for example test.local, then simply change that to:

Add-Computer -DomainName test.local -Credential <domain\username> -Restart -ForceInstall-ADDSDomainController -DomainName “<FQDN>” -InstallDns:$true -Credential <domain>\<username>

An astute reader will notice that we did not change the default ComputerName before promoting it to a DC. The book left that part out since it was focused on teaching PowerShell Direct. If this was more than just IaC practice then you would obviously do:

Rename-Computer -NewName <name> -LocalCredential Administrator -PassThru -Restart -Force

Populating the domain

Obviously this part is not applicable to adding a backup DC to an existing domain. However if you are creating the initial DC in a new forest then it is quite handy. We covered adding bulk users from a CSV file here. However this pertains to adding bulk users to a Hyper-V VM using PowerShell Direct.

The first thing you will want to do is add the AD and ImportExcel modules to the system that you are using to run code on the Hyper-V VM from via PowerShell Direct.

Install-Module -Name ImportExcel
Add-WindowsFeature -Name RSAT
Import-Module ActiveDirectory

ImportExcel is an incredibly useful module that allows one to import and export Excel spreadsheets without requiring Excel itself on the system processing the data.

We are pulling the data from an Excel spreadsheet on the Hyper-V host, then using that data on the VM. This works via the nifty PowerShell feature ‘$using:<variable>’.

Get-Credential | Export-Clixml -Path “C:\PowerLab\powerlab.xml”
$cred = Import-Clixml -Path “C:\PowerLab\powerlab.xml”
$users = Import-Excel -Path “C:\PowerShellForSysAdmins\Part III\Creating an Active Directory Forest\ActiveDirectoryObjects.xlsx” -WorksheetName Users$groups = Import-Excel -Path “C:\PowerShellForSysAdmins\Part III\Creating an Active Directory Forest\ActiveDirectoryObjects.xlsx” -WorksheetName GroupsForEach ($group in $groups) {Invoke-Command -VMName LABDC -ScriptBlock {if (-not (Get-ADOrganizationalUnit -Filter “Name -eq ‘$($using:group.OUName)’”)) {
New-ADOrganizationalUnit -Name $using:group.OUName
if (-not (Get-ADGroup -Filter “Name -eq ‘$($using:group.GroupName)’”)) {
New-ADGroup -Name $using:group.GroupName -GroupScope Global -Path “OU=$($using:group.OUName),dc=powerlab,dc=local”
} -Credential $cred
ForEach ($user in $users) {Invoke-Command -VMName LABDC -ScriptBlock {if (-not (Get-ADOrganizationalUnit -Filter “Name -eq ‘$($using:user.OUName)’”)) {
New-ADOrganizationalUnit -Name $using:user.OUName
if (-not (Get-ADUser -Filter “Name -eq ‘$($using:user.UserName)’”)) {
New-ADUser -Name $using:user.UserName -Path “OU=$($using:user.OUName),dc=powerlab,dc=local”
if ($using:user.UserName -notin (Get-ADGroupMember -Identity $using:user.MemberOf).Name) {
Add-ADGroupMember -Identity $using:user.MemberOf -Members $using:user.UserName
} -Credential $cred

This method is not the most efficient, but it works. A perceptive reader will notice that this does not enable the accounts it creates, set an initial password on them, or even set basic attributes like SurName or GivenName. That’s because this was done while following along with a book that is teaching how to use PowerShell Direct. We have covered creating bulk accounts from a CSV with account attributes before here.


Overall I thought PowerShell for Sysadmins is a great book and very well put together. It stresses actually doing stuff instead of talking about theory. My only complaint would be that the author kind of blew past the security concerns of using CredSSP, but then this isn’t a security book. Security folks and auditors working in a Windows domain environment should already know about CredSSP. The author briefly mentioned the credential theft concern of using it for Sysadmin types who aren’t all that into security.

The book is two years old now though and some changes have taken place. Hence I had to tweak a fair amount to get the IaC working, took a bunch of notes on what I did, and figured I would post them here in case it helps anyone else.

There is also a good chance that I will be reading this myself in one to two years when I am trying to do something similar.


How to create an answer file:

ImportExcel from PowerShell Gallery:

Using variables over PowerShell Direct:

CredSSP security issues:




I work various IT jobs & like Windows domain security as a hobby. Most of what’s here is my notes from auditing or the lab.