Automatically deploy Hyper-V machines with PowerShell
Before we can start with PowerShell, you will have to install Windows 10 or a Windows Server in a Hyper-V environment. We will be using this image to create new VMs. It is important to note, that this will make it possible to have one default image that your are using for every environment. You will also have to keep this image up to date. We will also use certain Hyper-V machine sizes, which makes it easier if you want to provide a service, like server hosting, to customers. This makes handling and planning of these environments easier.
Also make sure that you shutdown the image in a generalized state:
Start-Process -FilePath C:\Windows\System32\Sysprep\Sysprep.exe -ArgumentList '/generalize /oobe /shutdown /quiet' |
If you want to try this on your Windows 10 Machine, you will need to install Hyper-V.
Enable-WindowsOptionalFeature -Online -FeatureName Microsoft-Hyper-V-Management-PowerShell |
First, we need to create a lot of variables. And I mean A LOT. There are things like $environmentName, $type, $OS that I define so that I see what kind of machine that is without checking a document. I also want the last 3 digits of the IP to be part of the name.
And then there are the paths. Your will need to define this!
$Path
Path where the VM is gonna be stored$ParentVHDOS
Path where the Parent OS, which we will use to create the machines, is stored$VHDPathOS
Path where the VHD with the OS will be stored$VHDPath
Path where the additional D drive is stored
Also don’t forget to change $VMHost, because this needs to be your Hyper-V.
$environmentName = "Example" # CustomerName | |
$type = "S" # Client (C) or Server (S) | |
$OS = "16" # Windows Server 16 (16), Win Server 19 (19), Win10 (10) | |
$IP = "192.168.0.15" # provide the IP Address | |
$ipString = $IP.split(".") | |
$ipString = $ipString[7] # last three digits of the IP | |
$VMName = $environmentName + $type + $OS + $ipString # generate name | |
$VMSize = "S1" # S1, M1, L1, XXL1 | |
$Path = "C:\Hyper-V\"+$VMName #Path to store VMs and stuff, depending on customer? | |
$ParentVHDOS = "C:\Hyper-V\ParentOS\ParentOS\Virtual Hard Disks\ParentOS.vhdx" #"Z:\parentos.vhdx" #Define parent vhd path to create new disks from | |
$VHDPathOS = $Path+"\ParentOS.vhdx" | |
$VHDPath = $Path+"\ddrive.vhdx" | |
$VMHost = "DESKTOP-0JB2P4J" #Change to correct Hyper-V VM Host |
Next we build the credentials to log onto the machine.
# Build VM Credentials | |
$username = "" | |
$pw = "" | |
$secpassword = ConvertTo-SecureString $pw -AsPlainText -Force | |
$VMCredentials= New-Object System.Management.Automation.PSCredential ($username, $secpassword) |
Because I want to provide set systems, I will define certain VM sizes which a customer can choose from.
switch($VMSize) | |
{ | |
"S1" | |
{ | |
$MemoryStartupBytes = 1GB | |
$VHDSize = 10GB | |
$ProcCount = 1 | |
$MemoryMinimumBytes = 512MB | |
$MemoryMaximumBytes = 1GB | |
$Generation = "2" | |
} | |
"M1" | |
{ | |
$MemoryStartupBytes = 1GB | |
$VHDSize = 50GB | |
$ProcCount = 1 | |
$MemoryMinimumBytes = 1GB | |
$MemoryMaximumBytes = 1GB | |
$Generation = 2 | |
} | |
"L1" | |
{ | |
$MemoryStartupBytes = 4GB | |
$VHDSize = 120GB | |
$ProcCount = 2 | |
$MemoryMinimumBytes = 4GB | |
$MemoryMaximumBytes = 4GB | |
$Generation = 2 | |
} | |
"XXL1" | |
{ | |
$MemoryStartupBytes = 8GB | |
$VHDSize = 160GB | |
$ProcCount = 4 | |
$MemoryMinimumBytes = 8GB | |
$MemoryMaximumBytes = 8GB | |
$Generation = 2 | |
} | |
} |
In the next step we will create the VM. About time I would say! In the first try/catch we will copy the parent OS HDD to our directory. In the second try/catch we are creating the VM. And in the third one, we are updating some settings. During the VM creating and the updating of settings, we are using the VM sizes defined in our switch block above.
# Create new VM and basic VHD | |
try | |
{ | |
# "Copy" HDD of the Parent OS | |
New-VHD -Path $VHDPathOS -ParentPath $ParentVHDOS #-Differencing | |
} | |
catch{ "An error occured during the creation of of VHD OS in Path "+$VHDPathOS } | |
try | |
{ | |
# Create VM according to the VM sizes chosen above | |
New-VM -Name $VMName -MemoryStartupBytes $MemoryStartupBytes -VHDPath $VHDPathOS -Generation $Generation -ComputerName $VMHost -Path $Path | |
} | |
catch { "An error occured during the creation of the VM "+$VMName} | |
try | |
{ | |
# Change some settings according to the chosen VM size | |
Set-VM -Name $VMName -ProcessorCount $ProcCount -MemoryMinimumBytes $MemoryMinimumBytes -MemoryMaximumBytes $MemoryMaximumBytes | |
} | |
catch{ "An error occured during the definition of the VM "+$VMName} |
Now we can update the VM again and attach a second HDD.
# Add second VHD | |
try | |
{ | |
New-VHD -Path $VHDPath -SizeBytes $VHDSize | |
} | |
catch{ "An error occured during the creation of the second VHD in Path "+$VHDPath} | |
try | |
{ | |
Add-VMHardDiskDrive -VMName $VMName -ControllerType SCSI -Path $VHDPath | |
} | |
catch{ "An error occured druing the attach-process of the second VHD to VM "+$VMName} |
Finally we can start the VM!
# Start Hyper-V VM | |
Start-VM –ComputerName $VMHost –Name $VMName |
Finally we will set the IP of the new VM. For this, we need the machine to be powered on, but there is now way to tell if the OS is already up and running. That’s why we are using do/while and a little bit of trickery. After changing the IP we will restart the machine and you can log onto a brand new VM.
# Waits till VM is running | |
do{ | |
$RunningVM = Get-VM -ComputerName $VMHost -Name $VMName | |
$state = $RunningVM.State | |
}while($state -ne "Running") | |
# Break to get OS Version to ensure that Windows is up | |
do{ | |
try{ | |
Get-WMIObject -Class Win32_OperatingSystem -ComputerName $VMName -Credential $VMCredentials | Select-Object *Version -ExpandProperty Version* | |
$OSRunning = $true | |
} | |
catch{$OSRunning = $false} | |
}while($OSRunning -ne $true) | |
Write-Host "System is available." | |
# Set IP Address | |
do{ | |
try | |
{ | |
Invoke-Command -VMName $VMName -ScriptBlock { Set-NetIPAddress -InterfaceIndex 12 -IPAddress $IP } | |
$IPSet = $true | |
} | |
catch{ "An error occured during the process of setting an IP address in VM "+$VMName} | |
}while($IPSet -ne $true) | |
Write-Host "IP is set and will be restarted." | |
Restart-VM -Name $VMName -ComputerName $VMHost |
And we are done. You can find the script below or in the Github repo.
### | |
# Danny Davis | |
# Create VM in Hyper-V | |
# Created: 04/23/19 | |
# Modified: 06/12/19 | |
### | |
$environmentName = "Example" # CustomerName | |
$type = "S" # Client (C) or Server (S) | |
$OS = "16" # Windows Server 16 (16), Win Server 19 (19), Win10 (10) | |
$IP = "192.168.0.15" # provide the IP Address | |
$ipString = $IP.split(".") | |
$ipString = $ipString[7] # last three digits of the IP | |
$VMName = $environmentName + $type + $OS + $ipString # generate name | |
$VMSize = "S1" # S1, M1, L1, XXL1 | |
$Path = "C:\Hyper-V\"+$VMName #Path to store VMs and stuff | |
$ParentVHDOS = "C:\Hyper-V\ParentOS\ParentOS\Virtual Hard Disks\ParentOS.vhdx" #Define parent vhd path to create new disks from | |
$VHDPathOS = $Path+"\ParentOS.vhdx" | |
$VHDPath = $Path+"\ddrive.vhdx" | |
$VMHost = "DESKTOP-0JB2P4J" #Change to correct Hyper-V VM Host | |
# Build VM Credentials | |
$username = "" | |
$pw = "" | |
$secpassword = ConvertTo-SecureString $pw -AsPlainText -Force | |
$VMCredentials= New-Object System.Management.Automation.PSCredential ($username, $secpassword) | |
switch($VMSize) | |
{ | |
"S1" | |
{ | |
$MemoryStartupBytes = 1GB | |
$VHDSize = 10GB | |
$ProcCount = 1 | |
$MemoryMinimumBytes = 512MB | |
$MemoryMaximumBytes = 1GB | |
$Generation = "2" | |
} | |
"M1" | |
{ | |
$MemoryStartupBytes = 1GB | |
$VHDSize = 50GB | |
$ProcCount = 1 | |
$MemoryMinimumBytes = 1GB | |
$MemoryMaximumBytes = 1GB | |
$Generation = 2 | |
} | |
"L1" | |
{ | |
$MemoryStartupBytes = 4GB | |
$VHDSize = 120GB | |
$ProcCount = 2 | |
$MemoryMinimumBytes = 4GB | |
$MemoryMaximumBytes = 4GB | |
$Generation = 2 | |
} | |
"XXL1" | |
{ | |
$MemoryStartupBytes = 8GB | |
$VHDSize = 160GB | |
$ProcCount = 4 | |
$MemoryMinimumBytes = 8GB | |
$MemoryMaximumBytes = 8GB | |
$Generation = 2 | |
} | |
} | |
# Create new VM and basic VHD | |
try | |
{ | |
# "Copy" HDD of the Parent OS | |
New-VHD -Path $VHDPathOS -ParentPath $ParentVHDOS #-Differencing | |
} | |
catch{ "An error occured during the creation of of VHD OS in Path "+$VHDPathOS } | |
try | |
{ | |
# Create VM according to the VM sizes chosen above | |
New-VM -Name $VMName -MemoryStartupBytes $MemoryStartupBytes -VHDPath $VHDPathOS -Generation $Generation -ComputerName $VMHost -Path $Path | |
} | |
catch { "An error occured during the creation of the VM "+$VMName} | |
try | |
{ | |
# Change some settings according to the chosen VM size | |
Set-VM -Name $VMName -ProcessorCount $ProcCount -MemoryMinimumBytes $MemoryMinimumBytes -MemoryMaximumBytes $MemoryMaximumBytes | |
} | |
catch{ "An error occured during the definition of the VM "+$VMName} | |
# Add second VHD | |
try | |
{ | |
New-VHD -Path $VHDPath -SizeBytes $VHDSize | |
} | |
catch{ "An error occured during the creation of the second VHD in Path "+$VHDPath} | |
try | |
{ | |
Add-VMHardDiskDrive -VMName $VMName -ControllerType SCSI -Path $VHDPath | |
} | |
catch{ "An error occured druing the attach-process of the second VHD to VM "+$VMName} | |
# Start Hyper-V VM | |
Start-VM –ComputerName $VMHost –Name $VMName | |
# Waits till VM is running | |
do{ | |
$RunningVM = Get-VM -ComputerName $VMHost -Name $VMName | |
$state = $RunningVM.State | |
}while($state -ne "Running") | |
# Break to get OS Version to ensure that Windows is up | |
do{ | |
try{ | |
Get-WMIObject -Class Win32_OperatingSystem -ComputerName $VMName -Credential $VMCredentials | Select-Object *Version -ExpandProperty Version* | |
$OSRunning = $true | |
} | |
catch{$OSRunning = $false} | |
}while($OSRunning -ne $true) | |
Write-Host "System is available." | |
# Set IP Address | |
do{ | |
try | |
{ | |
Invoke-Command -VMName $VMName -ScriptBlock { Set-NetIPAddress -InterfaceIndex 12 -IPAddress $IP } | |
$IPSet = $true | |
} | |
catch{ "An error occured during the process of setting an IP address in VM "+$VMName} | |
}while($IPSet -ne $true) | |
Write-Host "IP is set and will be restarted." | |
Restart-VM -Name $VMName -ComputerName $VMHost |