Creating virtual machines using Hyper-V console is good but when it comes to creation of many VM’s, console is time consuming.

Virtual Machine (VM) and Guest OS Configuration using PowerShell and PowerShell direct

I wanted to demonstrate VM deployment and guest OS configuration by using powershell.

Note: Both Host and/or guest should be Windows server 2016 or Windows 10

Pre-Requisites:

  • Windows Server 2016 or Windows 10
  • Sysprepped VHDx with an embedded unattend.xml answer file

Outline:

The script created an empty generation 2 VM and copies the sysprepped VHDX file to the location. The script add network adapter, VHDx, Fibre channel adapter etc. to the VM and power it up.

When powered on, the unattended file does the initial configuration such as setting up local administrator ID, keyboard settings etc.

The script then connects to the VM with powershell direct to configure the rest such as IP Address, computer name, join to domain etc.

Script:

Import-Module BitsTransfer

#Basic Information regarding the VM
$hostname = "NEWHOSTNAME"
$Source = "\\FileShareServerName\VMTemplate\"
$CSV_Location = "C:\ClusterStorage\Volume2"
$VHDDestination = "$CSV_Location$hostname$hostname\Virtual Hard Disks"
$Image = "Win2016Template.vhdx"

#VM Specs
$vCPU = 3
$vRAM = 16gb
$vLAN = 1

#VM Network Information
$Eth0_IP = "192.168.1.143"
$Eth0_Mask = 24     #Subnet Mask
$Eth0_GW = "192.168.1.1"
$DNS1 = "192.168.1.15"
$DNS2 = "192.168.1.15"

#Domain and VM's local credentials for configuration
$domain_user = "domainname\adminuser1"
$domain_password = "password"  | ConvertTo-SecureString  -AsPlainText -Force
$domain = "domainname.local"

#local admin id in VM
$user = "administrator"
$password = "password"  | ConvertTo-SecureString  -AsPlainText -Force

#Creating new folder to keep all VM files
mkdir -Path $CSV_Location -Name $hostname

#create new VM
New-VM -Name $hostname -SwitchName Production -NoVHD -Path $CSV_Location$hostname -Generation 2 -BootDevice VHD -MemoryStartupBytes $vRAM
Set-VMProcessor -VMName $hostname -Count $vCPU
Set-VMNetworkAdapterVlan -VMName $hostname -VMNetworkAdapterName "Network Adapter" -VlanId $vLAN -Access

#copying VHD file from share path to destination
mkdir -Path $CSV_Location$hostname$hostname -Name "Virtual Hard Disks"

Start-BitsTransfer -Source $Source$Image -Destination $VHDDestination -Description "VHDX" -DisplayName "Disk Image" -TransferPolicy Unrestricted -Priority High

#Converting or renaming the VHD to desired name as per requirement
#Convert-VHD -Path "$CSV_Location$hostname$hostname\Virtual Hard Disks$Image" -DestinationPath "$CSV_Location$hostname$hostname\Virtual Hard Disks$hostname.vhdx" -VHDType Fixed
Rename-Item -Path "$CSV_Location$hostname$hostname\Virtual Hard Disks$Image" -NewName "$hostname.vhdx"
#removing the copied image file if VHD is converted to a different format
#Remove-Item "$CSV_Location$hostname$hostname\Virtual Hard Disks$Image"

#Attaching the VHDx to VM
Add-VMHardDiskDrive -VMName $hostname -ControllerType SCSI -Path "$CSV_Location$hostname$hostname\Virtual Hard Disks$hostname.vhdx"
#If required to resize the VHDx file
#Resize-VHD -Path "$CSV_Location$hostname$hostname\Virtual Hard Disks$hostname.vhdx" -SizeBytes 130gb

#Modifying VM Security parameter to attach vSAN adapter
Set-VMSecurity -VMName $hostname -VirtualizationBasedSecurityOptOut $true

#Adding HBA card with auto generated WWPN
Add-VMFibreChannelHba -VMName $hostname -GenerateWwn -SanName vSAN-A
Add-VMFibreChannelHba -VMName $hostname -GenerateWwn -SanName vSAN-B

#This will display WWPN of the VM
Get-VMFibreChannelHba -VMName $hostname | Select SanName, WorldWideNodeNameSetA, WorldWidePortNameSetA, WorldWideNodeNameSetB, WorldWidePortNameSetB

#Setting VHD as the boot drive
$vhds = Get-VMHardDiskDrive -VMName $hostname
Set-VMFirmware -VMName $hostname -FirstBootDevice $vhds

#Setting VM integration services and processor settings
Set-VM -VMName $hostname -AutomaticStopAction Save
Disable-VMIntegrationService -VMName $hostname -Name "Time Synchronization"
Set-VMProcessor -VMName $hostname -CompatibilityForMigrationEnabled $true

#Power on the VM
Start-VM $hostname

############Refer below for Guest OS configuration script############

At this stage, VM will power on and self configure based on the unattended answer file. Once the configuration is completed, the script uses PowerShell direct to configure the guest VM.

###############Guest OS Configuration##############
#Make sure that the image is prepared with the answer file with pre defined local administrator user and password, Once VM boots and configure itself with the answer file, we can leverage the power of PowerShell direct
#VM with vHBA takes more time to boot, so we set a delay of 100 seconds.

Start-Sleep -Seconds 100

#Create local credential variable
$credential = New-Object System.Management.Automation.PsCredential($user,$password)

#Rename the VM and wait for the VM to restart 
Invoke-Command -ScriptBlock {Rename-Computer -NewName $args[0] -Restart} -VMName $hostname -Credential $credential -ArgumentList $hostname

Start-Sleep -Seconds 100

#Set the IP address and disk drive size(if required)
Invoke-Command -VMName $hostname -Credential $credential -ScriptBlock {
    $Eth0_INT = (Get-NetAdapter -Name "Ethernet*" ).ifIndex
    New-NetIPAddress -InterfaceIndex $Eth0_INT -IPAddress $Using:Eth0_IP -PrefixLength $Using:Eth0_Mask -DefaultGateway $Using:Eth0_GW  -AddressFamily IPv4
    Set-DnsClientServerAddress -InterfaceIndex $Eth0_INT -ServerAddresses $Using:DNS1, $Using:DNS2
} 

#Resizing the partition size (if required)
Invoke-Command -VMName $hostname -Credential $credential -ScriptBlock {
    Get-Partition -DriveLetter C | Resize-Partition -Size (Get-PartitionSupportedSize -DriveLetter C).SizeMax
} 


#Join the VM to domain and wait for it to restart
Invoke-Command -VMName $hostname -Credential $credential -ScriptBlock {
    $domain_credential = New-Object System.Management.Automation.PsCredential($Using:domain_user,$Using:domain_password)
    Add-Computer -DomainName $Using:domain -Restart -Credential $domain_credential 
} 

Start-Sleep -Seconds 100

#once the VM reboots, add the desired domain users to local administrator group
Invoke-Command -VMName $hostname -Credential $credential -ScriptBlock {
    Add-LocalGroupMember -Group administrators -Member "domainname\adminuser1"
} 

If you wish to hide your password for the above script or for any other scripts. Please have a look at https://tekcookie.com/avoid-hardcode-password-in-powershell-script/

 

Yeah, finally we have automated the deployment of VM and guest OS configuration.

Hope this script is useful to you. Thank you for reading.