Automatically deploy Hyper-V machines with PowerShell

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'
view raw generalize.ps1 hosted with ❤ by GitHub

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
view raw variables.ps1 hosted with ❤ by GitHub

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
}
}
view raw VMSizes.ps1 hosted with ❤ by GitHub

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
view raw StartVM.ps1 hosted with ❤ by GitHub

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
view raw CreateVM.ps1 hosted with ❤ by GitHub
Get Search Result from SharePoint with PowerShell

Get Search Result from SharePoint with PowerShell

Attach / combine CSVs to one CSV for data collection

Attach / combine CSVs to one CSV for data collection