Table of contents
Several computing functions require the ability to uniquely identify a computer. These functions will generally rely on various signatures presented by hardware components. For a physical machine, it’s expected that these numbers will be constants. For virtual machines, almost nothing is constant. Nothing is truly unique, either, as duplicates can be made through simple file copy operations. Sometimes, items are duplicated that should be unique. All of these situations can be avoided by using Hyper-V’s export and import functionality, but, things happen. This post includes a free script that easily modifies the BIOS GUID of a virtual machine.
The particular field that this script modifies (VM BIOS GUID) is also known as the system’s UUID (universally unique identifier). I know that it is used when you attempt to PXE boot a computer. If you’re here, you likely already know the reason that you care about it, so I’m not going to spend a lot of time on that particular topic. If you want to see what a computer’s UUID is, open an elevated PowerShell prompt and run the following (this works on any Windows computer, whether virtual or physical):
gwmi Win32_ComputerSystem | select UUID
I used short-hand aliases since this is something you’re typing interactively. gwmi is short for Get-WMIObject, Win32_ComputerSystem is being given to the positional ClassName parameter, select is an alias for Select-Object, and UUID is being supplied to the positional Property parameter.
The output of the above PowerShell prompt is the computer’s UUID. On a virtual machine, it is also the BIOS GUID. That is the field that this script modifies.
Warning 1: Changes to this field are irreversible without restoring from backup. Modification of the field is likely to trigger software activation events. Other side effects, potentially damaging, may occur. Use this script at your own risk.
Warning 2: The BIOS GUID cannot be modified while the virtual machine is on. It must be in an Off state (not Saved or Paused). This script will turn off a running virtual machine (you are prompted first). It will not change anything on saved or paused VMs.
The following safety measures are in place:
- The script is marked as High impact, which means that it will prompt before doing anything unless you supply the -Force parameter or have your confirmation preference set to a dangerous level. It will prompt up to two times: once if the virtual machine is running (because the VM must be off before the change can occur) and when performing the change.
- The script will only accept a single virtual machine at a time. Of course, it can operate within a foreach block so this barrier can be overcome. The intent was to prevent severe accidents such as Get-VM | New-VMBIOSGUID.
- If a running virtual machine does not shut down within the allotted time, the script exits. The default wait time is 5 minutes, overridable by specifying the Timeout parameter. The timeout is measured in seconds. If the virtual machine’s guest shutdown process was properly triggered, it will continue to attempt to shut down and this script will not try to turn it back on.
- If a guest’s shutdown integration service does not respond (which includes guests that don’t have a shutdown integration service) the script will exit without making changes.
Tip: If you just want to know what a virtual machine’s BIOSGUID is, and the above PowerShell doesn’t work/isn’t what you want to do, use this function with the -WhatIf parameter. However, because the BIOSGUID is automatically generated each time the script is run, the one that WhatIf displays will not be the one that is actually applied should you run the script again. When running without WhatIf, the GUID shown in the confirmation prompt is the GUID that will be applied.
New-VMBIOSGUID
function New-VMBIOSGUID { <# .SYNOPSIS Changes the BIOSGUID for Hyper-V guests running on Hyper-V versions 8/2012 or later. .DESCRIPTION Changes the BIOSGUID for Hyper-V guests running on Hyper-V versions 8/2012 or later. A GUID can be supplied. If not, one is automatically generated. If the virtual machine is running, this script will attempt to shut it down prior to the operation. Once the replacement is complete, the virtual machine will be turned back on. .PARAMETER VM The name or virtual machine object (from Get-VM) of the virtual machine whose BIOSGUID is to be changed. .PARAMETER NewID The new GUID to assign to the virtual machine. If empty, a new GUID will be automatically generated. .PARAMETER ComputerName The Hyper-V host that owns the virtual machine to be modified. .PARAMETER Timeout Number of seconds to wait when shutting down the guest before assuming the shutdown failed and ending the script. Default is 300 (5 minutes). If the virtual machine is off, this parameter has no effect. .PARAMETER Force Suppresses prompts. If this parameter is not used, you will be prompted to shut down the virtual machine if it is running and you will be prompted to replace the BIOSGUID. Force can shut down a running virtual machine. It cannot affect a virtual machine that is saved or paused. .PARAMETER WhatIf Performs normal WhatIf operations by displaying the change that would be made. However, the new BIOSGUID is automatically generated on each run. The one that WhatIf displays will not be used. .NOTES Version 1.0 February 29, 2016 Author: Eric Siron (c) 2016 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 Microsoft.HyperV.PowerShell.VirtualMachine or System.String System.GUID .EXAMPLE New-VMBIOSGUID -VM svtest Replaces the BIOS GUID on the virtual machine named svtest with an automatically-generated ID. .EXAMPLE New-VMBIOSGUID svtest Exactly the same as example 1; uses positional parameter. .EXAMPLE Get-VM svtest | New-VMBIOSGUID Exactly the same as example 1 and 2; uses the pipeline. .EXAMPLE New-VMBIOSGUID svtest -Force Exactly the same as examples 1, 2, and 3; prompts suppressed. .EXAMPLE New-VMBIOSGUID svtest -NewID $Guid Replaces the BIOS GUID of svtest with the supplied ID. These IDs can be generated with [System.Guid]::NewGuid(). .EXAMPLE New-VMBIOSGUID svtest -WhatIf Shows how the BIOS GUID will be changed. TIP: Use this to view the current BIOS GUID without changing it. #> #requires -Version 4 #requires -Modules Hyper-V #requires -RunAsAdministrator [CmdletBinding(SupportsShouldProcess=$true, ConfirmImpact='High')] param ( [Parameter(Mandatory=$true, ValueFromPipeline=$true, Position=1)][PSObject]$VM, [Parameter()][System.GUID]$NewID, [Parameter()][String]$ComputerName = $env:COMPUTERNAME, [Parameter()][UInt32]$Timeout = 300, [Parameter()][Switch]$Force ) begin { <# adapted from http://blogs.msdn.com/b/taylorb/archive/2008/06/18/hyper-v-wmi-rich-error-messages-for-non-zero-returnvalue-no-more-32773-32768-32700.aspx #> function Process-WMIJob { param ( [Parameter(ValueFromPipeline=$true)][System.Management.ManagementBaseObject]$WmiResponse, [Parameter()][String]$WmiClassPath = $null, [Parameter()][String]$MethodName = $null, [Parameter()][String]$VMName, [Parameter()][String]$ComputerName ) process { $ErrorCode = 0 if($WmiResponse.ReturnValue -eq 4096) { $Job = [WMI]$WmiResponse.Job while ($Job.JobState -eq 4) { Write-Progress -Activity ('Modifying virtual machine {0}' -f $VMName, $ComputerName) -Status ('{0}% Complete' -f $Job.PercentComplete) -PercentComplete $Job.PercentComplete Start-Sleep -Milliseconds 100 $Job.PSBase.Get() } if($Job.JobState -ne 7) { if ($Job.ErrorDescription -ne "") { throw $Job.ErrorDescription } else { $ErrorCode = $Job.ErrorCode } Write-Progress $Job.Caption "Completed" -Completed $true } } elseif ($WmiResponse.ReturnValue -ne 0) { $ErrorCode = $WmiResponse.ReturnValue } if($ErrorCode -ne 0) { if($WmiClassPath -and $MethodName) { $PSWmiClass = [WmiClass]$WmiClassPath $PSWmiClass.PSBase.Options.UseAmendedQualifiers = $true $MethodQualifiers = $PSWmiClass.PSBase.Methods[$MethodName].Qualifiers $IndexOfError = [System.Array]::IndexOf($MethodQualifiers["ValueMap"].Value, [String]$ErrorCode) if($IndexOfError -ne "-1") { throw('Error Code: {0}, Method: {1}, Error: {2}' -f $ErrorCode, $MethodName, $MethodQualifiers["Values"].Value[$IndexOfError]) } else { throw('Error Code: {0}, Method: {1}, Error: Message Not Found' -f $ErrorCode, $MethodName) } } } } } } process { Write-Verbose -Message 'Validating input...' $VMName = '' $InputType = $VM.GetType() if($InputType.FullName -eq 'System.String') { $VMName = $VM } elseif($InputType.FullName -eq 'Microsoft.HyperV.PowerShell.VirtualMachine') { $VMName = $VM.Name $ComputerName = $VM.ComputerName } else { throw('You must supply a virtual machine name or object.') } if($NewID -ne $null) { try { $NewID = [System.Guid]::Parse($NewID) } catch { throw('Provided GUID cannot be parsed. Supply a valid GUID or leave empty to allow an ID to be automatically generated.') } } Write-Verbose -Message ('Establishing WMI connection to Virtual Machine Management Service on {0}...' -f $ComputerName) $VMMS = Get-WmiObject -Namespace rootvirtualizationv2 -Class Msvm_VirtualSystemManagementService -ComputerName $ComputerName Write-Verbose -Message 'Acquiring an empty paramater object for the ModifySystemSettings function...' $ModifySystemSettingsParams = $VMMS.GetMethodParameters('ModifySystemSettings') Write-Verbose -Message ('Establishing WMI connection to virtual machine {0}' -f $VMName) $VMObject = Get-WmiObject -Namespace rootvirtualizationv2 -Class Msvm_ComputerSystem -Filter "ElementName = '$VMName'" if($VMObject -eq $null) { throw('Virtual machine {0} not found on computer {1}' -f $VMName, $ComputerName) } Write-Verbose -Message ('Verifying that {0} is off...' -f $VMName) $OriginalState = $VMObject.EnabledState if($OriginalState -ne 3) { if($OriginalState -eq 2 -band ($Force.ToBool() -bor $PSCmdlet.ShouldProcess($VMName, 'Shut down'))) { $ShutdownComponent = $VMObject.GetRelated('Msvm_ShutdownComponent') Write-Verbose -Message 'Initiating shutdown...' Process-WMIJob -WmiResponse $ShutdownComponent.InitiateShutdown($true, 'Change BIOSGUID') -WmiClassPath $ShutdownComponent.ClassPath -MethodName 'InitiateShutdown' -VMName $VMName -ComputerName $ComputerName -ErrorAction Stop # the InitiateShutdown function completes as soon as the guest's integration services respond; it does not wait for the power state change to complete Write-Verbose -Message ('Waiting for virtual machine {0} to shut down...' -f $VMName) $TimeoutCounterStarted = [datetime]::Now $TimeoutExpiration = [datetime]::Now + [timespan]::FromSeconds($Timeout) while($VMObject.EnabledState -ne 3) { $ElapsedPercent = [UInt32]((([datetime]::Now - $TimeoutCounterStarted).TotalSeconds / $Timeout) * 100) if($ElapsedPercent -ge 100) { throw('Timeout waiting for virtual machine {0} to shut down' -f $VMName) } else { Write-Progress -Activity ('Waiting for virtual machine {0} on {1} to stop' -f $VMName, $ComputerName) -Status ('{0}% timeout expiration' -f ($ElapsedPercent)) -PercentComplete $ElapsedPercent Start-Sleep -Milliseconds 250 $VMObject.Get() } } } elseif($OriginalState -ne 2) { throw('Virtual machine must be turned off to replace the BIOS GUID. It is not in a state this script can work with.' -f $VMName) } } Write-Verbose -Message ('Retrieving all current settings for virtual machine {0}' -f $VMName) $CurrentSettingsDataCollection = $VMObject.GetRelated('Msvm_VirtualSystemSettingData') Write-Verbose -Message 'Extracting the settings data object from the settings data collection object...' $CurrentSettingsData = $null foreach($SettingsObject in $CurrentSettingsDataCollection) { $CurrentSettingsData = [System.Management.ManagementObject]($SettingsObject) } if($NewID -eq $null) { Write-Verbose 'Generating new GUID...' $NewID = [System.Guid]::NewGuid() } $OriginalGUID = $CurrentSettingsData.BIOSGUID Write-Verbose -Message ('Orginal BIOS GUID: {0}' -f $OriginalGUID) Write-Verbose -Message 'Changing BIOSGUID in data object...' $CurrentSettingsData['BIOSGUID'] = "{$($NewID.Guid.ToUpper())}" Write-Verbose -Message ('New BIOS GUID: {0}' -f $CurrentSettingsData.BIOSGUID) Write-Verbose -Message 'Assigning modified data object as parameter for ModifySystemSettings function...' $ModifySystemSettingsParams['SystemSettings'] = $CurrentSettingsData.GetText([System.Management.TextFormat]::CimDtd20) if($Force.ToBool() -bor $PSCmdlet.ShouldProcess($VMName, ('Change BIOSGUID from {0} to {1}' -f $OriginalGUID, "{$($NewID.Guid.ToUpper())}"))) { Write-Verbose -Message ('Instructing Virtual Machine Management Service to modify settings for virtual machine {0}' -f $VMName) Process-WMIJob -WmiClassPath $VMMS.ClassPath ($VMMS.InvokeMethod('ModifySystemSettings', $ModifySystemSettingsParams, $null)) Process-WMIJob -WmiResponse ($VMMS.InvokeMethod('ModifySystemSettings', $ModifySystemSettingsParams, $null)) -WmiClassPath $VMMS.ClassPath -MethodName 'ModifySystemSettings' -VMName $VMName -ComputerName $ComputerName } $VMObject.Get() if($OriginalState -ne $VMObject.EnabledState) { Write-Verbose -Message ('Returning {0} to its original running state.' -f $VMName) Process-WMIJob -WmiResponse $VMObject.RequestStateChange($OriginalState) -WmiClassPath $VMObject.ClassPath -MethodName 'RequestStateChange' -VMName $VMName -ComputerName $ComputerName -ErrorAction Stop } } }
Script Discussion
Changing the BIOSGUID has never been something that I needed to do, but I saw someone else requesting it. I noticed that all existing documentation was written for version 1 of the Hyper-V WMI namespace, which doesn’t exist as of 2012 R2. Whereas a number of WMI-based Hyper-V functions can be updated from the v1 namespace to the v2 namespace just by changing the -Namespace parameter from rootvirtualization to rootvirtualizationv2, these functions have completely different names and are not accessed through associators. So, none of those older guides will work for current virtual machines; mine will not work on 2008 R2 and earlier.
There aren’t any supported non-WMI techniques to change a Hyper-V virtual machine’s BIOSGUID/UUID. Up through 2012 R2, you can modify the .XML file directly. From what I’ve seen of the new virtual machine file format in 2016, you should be able to perform something similar using a binary file editor. However, if you don’t want to get on the bad side of Microsoft support, WMI is the way to go.
For a more detailed discussion of the BIOSGUID on Hyper-V, I found an article by John Howard. Most of the how-to there is useless because it’s for the older version, but you can learn more about the field itself than what I explained.