Summary: Attach a VHD to a new virtual machine and modify the operating system for additional customizations.

Hey, Scripting Guy! Question Hey, Scripting Guy!

With that prepped VHD file, how much work will it take to create an actual virtual machine? Possibly with some customizations?

—MM

Hey, Scripting Guy! Answer Hello MM,

Honorary Scripting Guy, Sean Kearney, is here to raise a little Shell!

     Note This post is the fourth in a five-part series. To catch up, read: 

As you read in the previous posts, we have a bootable VHD hot and ready to go! Now we want to do a few more things. My goal for you is not only to easily spin up a bootable virtual machine. Even though that, by itself, would be cool.

I want you to have the ability to spin up a private lab, including your own basic domain controller. My goal is that if a team wants a private lab, you can spin up the resources as a script by using a CSV or XML file, and you’ll have an environment guaranteed to operate and self-timeout. This script is meant to be simple enough that a non-administrator could spin it up.

On to the easiest part…

We need to create a virtual machine with the name $VMName and some additional parameters such as Memory and Network Name, and we want to attach the VHD that we created earlier. We’ll add some additional parameters to the top of the script:

$Ram=1024MB

$NetworkSwitch=”EOT-External”

I’m adding some defaults such as my particular network switch name in Hyper-V. You can use your own. For simplicity, I’ve gone with a static ram configuration for the lab environment. You can always tweak these things later and improve them.

So we create the new virtual machine in Hyper-V, attach the virtual hard disk, and connect to our virtual switch:

New-VM -name $VMName -MemoryStartupBytes $Ram

Add-VMHardDiskDrive -VMName $VMName

Connect-VMNetworkAdapter -VMName $VMName -SwitchName $NetworkSwitch

If we started it now, we would have an operational virtual machine. However, I’d like to bring forth some additional options to make your life easier. We’re going to create a subfolder to contain a potential script to run automatically. We also need an entry in the new Windows VHD to allow the script to run automatically—even if a user does not sign in.

This will all happen just before we create the virtual machine in our script because we need to be able to access the VHD directly.

So after we copy the Unattend.xml into the virtual machine, we’ll create folder on the remote VHD file, and plant a Windows PowerShell script. To allow us to run things seamlessly, I’ve decided to name the source file for the Windows PowerShell script with the same name as the virtual machine name.

For our current environment, we only need one customized script, but if we had three or four machines, we could have the scripts ready for each machine in the same folder.

I’m going make a subfolder under the virtual machine’s \ProgramData\ folder called Scripts, and place whatever I need to fire up there.

$ScriptFolder=$DriveLetter+"\ProgramData\Scripts\"

$Scriptname=$Scriptfolder+"Firstrun.ps1"

$SourceStartup=$SourcePath+"\"+$VMName+".PS1"

New-Item $ScriptFolder -ItemType Directory

Copy-Item "$SourcePath\FirstStart.CMD" $ScriptFolder

Copy-Item $SourceStartup $Scriptname

Copy-Item $UnattendXML $Destination

Now for the tricky part: accessing the registry in the VHD file. For this answer, I did a little research in Bing and found a great Blog post from @jrich523, a winner of the Microsoft Community Contributor Award in 2011: PowerShell: Loading and Unloading Registry Hives. He wrote a really cool and simple method to load a registry file by leveraging the old Reg.exe and Windows PowerShell.

To access the RunOnce key, we load the software registry hive:

$RemoteReg=$DriveLetter+"\Windows\System32\config\Software"

REG LOAD 'HKLM\REMOTEPC' $RemoteReg

When we have access to the file, we create a single entry into RunOnce called PoshStart to launch a CMD file. This file will set the Windows PowerShell Execution Policy on the server and launch the first Windows PowerShell script, which can do as little or as much as you want.

NEW-ITEMPROPERTY "HKLM:REMOTEPC\Microsoft\Windows\CurrentVersion\RunOnce\" -Name "PoshStart" -Value "C:\ProgramData\Scripts\FirstStart.CMD"

REG UNLOAD 'HKLM\REMOTEPC'

The FirstStart.cmd script will look like is this:

powershell.exe -command { set-executionpolicy -executionpolicy Bypass -force }

powershell.exe -file c:\programdata\scripts\firstrun.ps1 -executionpolicy Bypass

I will place this script in the same source folder as all the rest of our files.

Here is our current script in the ISO folder to build a virtual machine from a virtual machine template. On my computer, I named it New-VMfromTemplate.ps1.

param(

$VMName,

#$ProductKey='',

$Organization='Energized About Technology',

$Owner='Energized About Technology',

$TimeZone='Eastern Standard Time',

$AdminPassword='P@ssw0rd',

$Edition="WindowsServer2012R2ServerStandard",

$Ram=1024MB,

$SourcePath="C:\iso",

$NetworkSwitch="ContosoPrivate"

)

#

# Edit unattend.xml template

#

$UnattendTemplate="Unattend-template.xml"

$Unattendfile=New-Object XML

$Unattendfile.Load($SourcePath+"\"+$UnattendTemplate)

$Unattendfile.unattend.settings.component[0].ComputerName=$VMName

#$Unattendfile.unattend.settings.component[0].ProductKey=$ProductKey

$Unattendfile.unattend.settings.component[0].RegisteredOrganization=$Organization

$Unattendfile.unattend.settings.component[0].RegisteredOwner=$Owner

$Unattendfile.unattend.settings.component[0].TimeZone=$TimeZone

$Unattendfile.unattend.settings.Component[1].RegisteredOrganization=$Organization

$Unattendfile.unattend.settings.Component[1].RegisteredOwner=$Owner

$UnattendFile.unattend.settings.component[1].UserAccounts.AdministratorPassword.Value=$AdminPassword

$UnattendFile.unattend.settings.component[1].autologon.password.value=$AdminPassword

 

$UnattendXML=$SourcePath+"\"+$VMName+".xml"

 

$Unattendfile.save($UnattendXML)

 

#

# Create Virtual Machine

#

$VHDPath=(Get-ItemProperty -Path 'HKLM:\Software\Microsoft\Windows NT\CurrentVersion\Virtualization' -Name 'DefaultVirtualHardDiskPath').DefaultVirtualHardDiskPath

$VMPath=(Get-ItemProperty -Path 'HKLM:\Software\Microsoft\Windows NT\CurrentVersion\Virtualization' -Name 'DefaultExternalDataRoot').DefaultExternalDataRoot

 

$SourceData=$SourcePath+"\"+$Edition+".vhd"

$TargetData=$VHDPath+$VMName+"-"+$Edition+"-c.vhd"

 

New-VM -name $VMName -MemoryStartupBytes $Ram

Add-VMHardDiskDrive -VMName $VMName

 

#

# Grab VHD file from Template

#

Copy-Item $SourceData $TargetData

 

#

# Inject XML file into Virtual Machine

#

Mount-diskimage $TargetData

$DriveLetter=((Get-DiskImage $TargetData | get-disk | get-partition).DriveLetter)+":"

 

$Destination=$Driveletter+"\Windows\System32\Sysprep\unattend.xml"

 

$ScriptFolder=$DriveLetter+"\ProgramData\Scripts\"

$Scriptname=$Scriptfolder+"Firstrun.ps1"

$SourceStartup=$SourcePath+"\"+$VMName+".PS1"

 

New-Item $ScriptFolder -ItemType Directory

Copy-Item "$SourcePath\FirstStart.CMD" $ScriptFolder

Copy-Item $SourceStartup $Scriptname

Copy-Item $UnattendXML $Destination

 

$RemoteReg=$DriveLetter+"\Windows\System32\config\Software"

REG LOAD 'HKLM\REMOTEPC' $RemoteReg

 

NEW-ITEMPROPERTY "HKLM:REMOTEPC\Microsoft\Windows\CurrentVersion\RunOnce\" -Name "PoshStart" -Value "C:\ProgramData\Scripts\FirstStart.CMD"

 

REG UNLOAD 'HKLM\REMOTEPC'

 

dismount-diskimage $TargetData

 

#

# Attach Drive

#

Set-VMHardDiskDrive -VMName $VMName -Path $TargetData

 

#

# Connect to default Switch

#

Connect-VMNetworkAdapter -VMName $VMName -SwitchName $NetworkSwitch

 

START-VM $VMName

If you successfully created the VHD template files from Set Up a Lab with Windows PowerShell and Free Microsoft Software: Part 1, you will be able to create a new virtual machine by using this line (if you want all the default settings):

NEW-VMFromTemplate.PS1 –VMName ContosoServer

Or you could adjust parameters as follows for more RAM and a different Hyper-V network name:

NEW-VMFromTemplate.PS1 –VMName ContosoServer –Ram 2048MB –NetworkSwitch ‘FabrikamPublic’

You’ll also notice that there is a parameter called $ProductKey that has been commented out. You can uncomment this and leverage the ProductKey field in Unattend.xml to pass alternate product keys. This is good if you’re using this solution with the full licensed copy of Windows Server 2012 R2.

Now our script has the ability to have a virtual machine that spins up prenamed. We also have a Windows PowerShell script to launch, and we can make as many changes as we need!

Tune in tomorrow when I will show you how to build that external Windows Powershell script to turn this new virtual machine into a domain controller, including the creation of a DHCP server.

I invite you to follow The Scripting Guys on Twitter and Facebook. If you have any questions, send an email to The Scripting Guys at scripter@microsoft.com, or post your questions on the Official Scripting Guys Forum. See you tomorrow. Until then remember eat your Cmdlets each and every day with a taste dash of Creativity.

Sean Kearney, Windows PowerShell MVP and Honorary Scripting Guy