Save to My DOJO
For quite some time, I’ve been wanting to write an article on the wonders of Windows Deployment Services (WDS) and Hyper-V. Most of the other techniques that we use to deploy hosts and virtual machines are largely junk. WDS’s learning curve is short, and it doesn’t require many resources to stand up, operate, or maintain. It’s one of those technologies that you didn’t know that you couldn’t live without until the first time you see it in action.
This article is not the article that explains all of that to you. This article is the one that knocks out the most persistent annoyance to fully using WDS.
Two related newer tools:
- A script that updates even more advanced virtual machine settings
- A binary executable with GUI that updates advanced virtual machine settings (installable or portable)
What is Windows Deployment Services?
Before I can explain why you want this script, I need to explain Windows Deployment Services (WDS). If you’re already familiar with WDS, I assume that you’re already familiar with your scroll wheel as well.
WDS is a small service that sits on your network listening for PXE boot requests. When it intercepts one, it ships a boot image to the requesting machine. From there, it can start an operating system (think diskless workstations) or it can fire up an operating system installer. I use it for the latter. The best part is that WDS is highly Active Directory integrated. Not only will it install the operating system for you, it will automatically place it in the directory. Even better, you can take a tiny snip of information from the target computer and place it into a particular attribute of an Active Directory computer object. WDS will match the newly installed computer to the AD object, so it will start life with the computer name, OU, and group policies that you want.
That tiny little piece of information needed for WDS to match the computer to the object is the tiny annoyance that I spoke of earlier. You must boot the machine in PXE mode and capture its GUID:
Modern server systems can take a very long time to boot, if you miss it then you must start over, and you must manually transcribe the digits. Fun, right?
Well, virtual machines don’t have any of those problems. Extracting the BIOS GUID still isn’t the most pleasant thing that you’ll ever do, though. It’s worse if you don’t know how to use CIM and/or WMI. It’s almost easier just to boot a VM just like you would a physical machine. That’s where this script comes in.
Script Prerequisites
The script itself has these requirements:
- PowerShell version 4 or later
- The Active Directory PowerShell module. You could run it from a domain controller, although that’s about as smart as starting a land war in Asia. Use the Remote Server Administration Tools instead.
- Must be run from a domain member. I’m not sure if the AD PS module would work otherwise anyway.
There is no dependency on the Hyper-V PowerShell module. I specifically built it to work with native CIM cmdlets since I’m already forcing you to use the AD module.
I tested from a Windows 10 system against a Hyper-V Server 2016 system. I tested from a Windows Server 2012 R2 system still using PowerShell 4.0 against a Hyper-V Server 2012 R2 system. All machines are in the same domain, which is 2012 R2 forest and domain functional level.
The Script
The script is displayed below. Copy/paste into your PowerShell editor of choice and save it to a system that has the Active Directory cmdlet module installed.
Parameters:
- VM: This will accept a string (name), a Hyper-V VM object (from Get-VM, etc.), a WMI object of type Msvm_ComputerSystem, or a CIM object of type Msvm_ComputerSystem.
- ComputerName: The name of the Hyper-V host for the VM that you want to work with. This field is only used if the VM is specified as a string. For any of the other types, the computer name is extracted from the passed-in object.
- ADObjectName: If the Active Directory object name is different from the VM’s name, this parameter will be used to name the AD object. If not specified, the VM’s name will be used.
- Create: A switch that indicates that you wish to create an AD object if one does not exist. If you don’t specify this, the script will error if it can’t find a matching AD object. The object will be created in the default OU. Can be used with CreateInOU, but it’s not necessary to use both.
- CreateInOU: An Active Directory OU object where you wish to create the computer. This must be a true object; use Get-ADOrganizationalUnit to generate it. This can be used with the Create parameter, but it’s not necessary to use both.
- What If: Shows you what will happen, as usual. Useful if you just want to see what the VM’s BIOS GUID is without learning WMI/CIM or going through the hassle of booting it up.
The script includes complete support for Get-Help. It contains numerous examples to help you get started. If you’re uncertain, leverage -WhatIf until things look as you expect.
#function Set-VMWDSInfo #uncomment this line, the second line, and the last line to use as a dot-sourced/profile function #{ #uncomment this line, the first line, and the last line to use as a dot-sourced/profile function <# .SYNOPSIS Applies the virtual machine's BIOS GUID to the matching Active Directory computer object so that it can be recognized by WDS. .DESCRIPTION Applies the virtual machine's BIOS GUID to the matching Active Directory computer object so that it can be recognized by WDS. .PARAMETER VM The name or virtual machine object (see input types) of the virtual machine whose BIOSGUID is to be changed. .PARAMETER ComputerName The Hyper-V host that owns the virtual machine to be modified. Ignored if VM is not of type System.String. .PARAMETER ADObjectName The name of the computer object in Active Directory. If not specified, will use the virtual machine's name. .PARAMETER Create If this parameter is specified and a matching computer object does not exist, it will be created in the default OU. This parameter is implied if you specify a value for "CreateInOU". You do not need to specify both. If you do not specify this parameter and/or the "CreateInOU" parameter and the target computer object does not exist, the script will exit with an error. .PARAMETER CreateInOU If this parameter is specified, the computer object will be created in the specified OU *if it does not exist*. If the computer object already exists, the value of this parameter is ignored. You must supply an object of type ADOrganizationalUnit (output from Get-ADOrganizationalUnit). If blank, the computer object will be created in the default OU. If you do not specify this parameter and/or the "Create" parameter and the target computer object does not exist, the script will exit with an error. .NOTES Version 1.0 February 16, 2017 Author: Eric Siron (c) 2017 Altaro Software This script comes with no warranty, express or implied. Neither Altaro Software nor Eric Siron are liable for any damages, intentional or otherwise, that arise from its use in any capacity. .INPUTS VM: Microsoft.HyperV.PowerShell.VirtualMachine or System.String or System.Management.ManagementObject or Microsoft.Management.Infrastructure.CimInstance CreateInOU: Microsoft.ActiveDirectory.Management.ADOrganizationalUnit .EXAMPLE Set-VMWDSInfo -VM MyVM The Active Directory object for the virtual machine named "My VM" on the local computer is updated with its BIOS GUID. .EXAMPLE Set-VMWDSInfo -VM MyVM -Computer svhv1 The Active Directory object for the virtual machine named "My VM" on the Hyper-V host "svhv1" is updated with its BIOS GUID. .EXAMPLE Set-VMWDSInfo -VM MyVM -Computer svhv1 -ADObjectName VMinAD The Active Directory object named "VMinAD" that represents the virtual machine named "My VM" on the Hyper-V host "svhv1" is updated with its BIOS GUID. .EXAMPLE Set-VMWDSInfo -VM MyVM -Computer svhv1 -ADObjectName VMinAD An Active Directory object named "VMinAD" is created to represent the virtual machine named "My VM" on the Hyper-V host "svhv1", and is set with the VM's BIOS GUID. .EXAMPLE $OU = Get-ADOrganizationalUnit -Identity 'HVGuests' Set-VMWDSInfo -VM MyVM -Computer svhv1 -ADObjectName VMinAD -CreateInOU $OU An Active Directory object named "VMinAD" is created to represent the virtual machine named "My VM" on the Hyper-V host "svhv1". The new object is moved to the OU named "HVGuests" and is set with the VM's BIOS GUID. .EXAMPLE $VM = New-VM -PassThru... Set-VM -VM -$VM... $OU = Get-ADOrganizationalUnit -Identity 'HVGuests' Set-VMWDSInfo -VM $VM -Computer svhv1 -CreateInOU $OU A new virtual machine is created and set to your specifications. An object is created for it in Active Directory and moved to the "HVGuests" OU. The object is assigned the BIOSGUID of the new virtual machine. #> #requires -Version 4 #requires -Modules ActiveDirectory param ( [Parameter(Mandatory=$true, ValueFromPipeline=$true, Position=1)][PSObject]$VM, [Parameter()][String]$ComputerName = $env:COMPUTERNAME, [Parameter()][String]$ADObjectName = [String]::Empty, [Parameter()][Switch]$Create, [Parameter()][Microsoft.ActiveDirectory.Management.ADOrganizationalUnit]$CreateInOU, [Parameter()][Switch]$WhatIf ) process { Write-Verbose -Message 'Validating input...' $VMName = [String]::Empty $VMID = [String]::Empty $VMCIM = $null $InputType = $VM.GetType() if($InputType.FullName -eq 'System.String') { $VMCIM = Get-CimInstance -ComputerName $ComputerName -Namespace root/virtualization/v2 -ClassName Msvm_ComputerSystem -Filter ('ElementName="{0}"' -f $VM) } elseif($InputType.FullName -eq 'Microsoft.HyperV.PowerShell.VirtualMachine') { $VMCIM = Get-CimInstance -ComputerName $VM.ComputerName -Namespace root/virtualization/v2 -ClassName Msvm_ComputerSystem -Filter ('Name="{0}"' -f $VM.Id.ToString()) } elseif($InputType.FullName -eq 'System.Management.ManagementObject' -and $VM.CreationClassName -eq 'Msvm_ComputerSystem') { $VMCIM = Get-CimInstance -ComputerName $VM.__SERVER -Namespace root/virtualization/v2 -ClassName Msvm_ComputerSystem -Filter ('Name="{0}"' -f $VM.Name) } elseif($InputType.FullName -eq 'Microsoft.Management.Infrastructure.CimInstance' -and $VM.CreationClassName -eq 'Msvm_ComputerSystem') { $VMCIM = $VM } if (!$VMCIM) { throw('You must supply a valid virtual machine name or object.') } $VMName = $VMCIM.ElementName if(!$ADObjectName) { $ADObjectName = $VMName } Write-Verbose -Message ('Retrieving settings for VM {0}...' -f $VMName) $VMSettings = Get-CimAssociatedInstance -InputObject $VMCim -ResultClassName Msvm_VirtualSystemSettingData -ErrorAction Stop $BIOSGUIDString = $VMSettings[0].BIOSGUID if($VMSettings.Count) { foreach($VMSetting in $VMSettings) { if($VMSetting.BIOSGUID -ne $BIOSGUIDString) { throw('Virtual machine {0} has a BIOSGUID that does not match the BIOSGUID of at least one of its checkpoints. This tool will not function unless all IDs match.') } } } $BIOSGUID = [System.Guid]::Parse($BIOSGUIDString) if($WhatIf) { Write-Host -ForegroundColor Yellow -Object ('What If: Assigning GUID {0} to Active Directory object "{1}" for VM "{2}"' -f $BIOSGUIDString, $ADObjectName, $VMName) exit 0 } Write-Verbose -Message ('Locating a matching object in the directory') try { $ADCO = Get-ADComputer -Identity $ADObjectName -ErrorAction Stop } catch [Microsoft.ActiveDirectory.Management.ADIdentityNotFoundException] { if($Create -or $CreateInOU) { Write-Progress -Activity ('Adding computer {0} to domain' -f $ADObjectName) -Status 'Creating computer object' $NewComputer = New-ADComputer -Name $ADObjectName -PassThru -ErrorAction Stop if($CreateInOU) { Write-Progress -Activity ('Adding computer {0} to domain' -f $ADObjectName) -Status 'Moving to target OU' Move-ADObject -Identity $NewComputer -TargetPath $CreateInOU -ErrorAction Stop } Write-Progress -Activity ('Adding computer {0} to domain' -f $ADObjectName) -Completed } else { throw('No computer object matching the name "{0}" could be found in the directory and no create parameter was supplied' -f $ADObjectName) } } catch { Write-Warning -Message ('An unexpected error occurred while querying Active Directory for the existence of an object named "{0}"' -f $ADObjectName) throw $_ } Set-ADComputer -Identity $ADObjectName -Replace @{netbootGUID=$BIOSGUID.ToByteArray()} } #} #uncomment this line and the first two lines to use as a dot-sourced/profile function
Script Discussion
Once the script completes, its results should be instantly viewable in the WDS console:
There are a few additional things to note.
Potential for Data Loss
This script executes a Replace function on the netbootGUID property of an Active Directory computer object. Target wisely.
I always set my WDS server to require users to press F12 before an image is pushed. If you’re not doing that, then the next time a configured VM starts and contacts the PXE server, it will drop right into setup. If you’ve got all the scaffolding set up for it to jump straight into unattend mode… Well, just be careful.
Other WDS-Related Fields
I elected to only set the BIOS GUID because that is the toughest part. It would be possible to set other WDS-related items, such as the WDS server, but that would have made the script quite a bit more complicated. I am using the Active Directory “Replace” function to place the BIOS GUID. I could easily slip in a few other fields, but you’d be required to specify them each time or any existing settings would be wiped out. The scaffolding necessary to adequately control that behavior would be significant. It would be easier to write other scripts that were similar in build to this one to adjust other fields.
Further Work
I still have it in my to-do list to work up a good article on Windows Deployment Services with Hyper-V. It’s not a complicated technology, so I encourage any and all self-starters to spin up a WDS system and start poking around. It’s nice to never need to scrounge for install disks/ISOs or dig for USB keys or bother with templates. I’ve also got my WDS system integrated with the automated WSUS script that I wrote earlier, so I know that my deployment images are up to date. These are all tools that can make your life much easier. I’ll do my best to get that article out soon, but I’m encouraging you to get started right away anyway.
Not a DOJO Member yet?
Join thousands of other IT pros and receive a weekly roundup email with the latest content & updates!
2 thoughts on "Free Hyper-V Script: Update WDS Boot ID for a Virtual Machine"
Hi,
Do you know of any way to get the VM serial or UUID when there is no OS in it? What i’d use this for is automating the deployment from WDS (passing off to MDT to follow the task sequence), i can do the actual deployment automatically once i have populated the MDT DB with a record for the VM serial or UUID, but i have to manually get that information before i can use it, i could script the creation of the VM, then i have to manually get the serial/uuid, then im thinking (not sure yet) that i’d be able to script the creation of an MDT DB record.
It doesn’t need to have an OS for the UUID or any serials. This script with -WhatIf will show the UUID. All of these items are in the Msvm_VirtualSystemSettingData object attached to the VM.