Each Hyper-V virtual machine sports a number of settings that can be changed, but not by any sanctioned GUI tools. If you’re familiar with WMI, these properties are part of the Msvm_VirtualSystemSettingData class. Whether you’re familiar with WMI or not, these properties are not simple to change. I previously created a script that modifies the BIOS GUID setting, but that left out all the other available fields. So, I took that script back into the workshop and rewired it to increase its reach.

If you’re fairly new to using PowerShell as a scripting language and use other people’s scripts to learn, there are some additional notes after the script contents that you might be interested in.

What this Script Does

This script can be used to modify the following properties of a Hyper-V virtual machine:

  • BIOS GUID: The BIOS of every modern computer should contain a Universally Unique Identifier (UUID). Microsoft’s implementation of the standard UUID is called a Globally Unique Identifier (GUID).  You can see it on any Windows computer with gwmi Win32_ComputerSystem | select UUID. On physical machines, I don’t believe that it’s possible to change the UUID. On virtual machines, it is possible and even necessary at times. You can provide your own GUID or specify a new one.
  • Baseboard serial number.
  • BIOS serial number.
  • Chassis asset tag.
  • Chassis serial number.

There are other modifiable fields on this particular WMI class, but these are the only fields that I’m certain have no effect on the way that Hyper-V handles the virtual machine.

Warning 1: Changes to these fields are irreversible without restoring from backup. Modification of the BIOS GUID field is likely to trigger software activation events. Other side effects, potentially damaging, may occur. Any of these fields may be tracked and used by software inside the virtual machine. Any of these fields may be tracked and used by third-party software that manipulates the virtual machine. Use this script at your own risk.

Warning 2: These settings 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.

Warning 3: Only the active virtual machine is impacted. If the virtual machine has any checkpoints, they are left as-is. That means that if you delete the checkpoints, the new settings will be retained. If you apply or revert to a checkpoint, the old settings will be restored. I made the assumption that this behavior would be expected.

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 accidents.
  • 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. If you’re really intent on making changes no matter what, I’d use the built-in Stop-VM cmdlet first.

Script Requirements

The script is designed to operate via WMI, so the Hyper-V PowerShell module is not required. However, it can accept output from Get-VM for its own VM parameter.

You must have administrative access on the target Hyper-V host. The script does not check for this status because you might be targeting a remote computer. I did not test the script with membership in “Hyper-V Administrators”. That group does not always behave as expected, but it might work.

Set-VMAdvancedSettings

Copy/paste the contents of the code block to a text editor on your system and save the file as Set-VMAdvancedSettings.ps1. As-is, you call the script directly. If you uncomment the first two lines and the last line, you will convert the script to an advanced function that can be dot-sourced or added to your profile.

Script Notes for Other Scripters

I hope that at least some of you are using my scripts to advance your own PowerShell knowledge. This script shows two internal functions used for two separate purposes.

Code/Script Reusability

Sometimes, you’ll find yourself needing the same functionality in multiple scripts. You could rewrite that functionality each time, but I’m sure you don’t need me to tell you what’s wrong with that. You could put that functionality into another script and dot-source it into each of the others that rely on it. That’s a perfectly viable approach, but I don’t use it in my public scripts because I can’t guarantee what scripts will be on readers’ systems and I want to provide one-stop wherever possible. That leads to the third option, which is reusability.

Find the line that says function Process-WmiJob. As the comments say, I have adapted that from someone else’s work. I commonly need to use its functionality in many of my WMI-based scripts. So, I modularized it to use a universal set of parameters. Now I just copy/paste it into any other script that uses WMI jobs.

Making your code/script reusable can save you a great deal of time. Reused blocks have predictable results. The more you use them, the more likely you are to work out long-standing bugs and fine-tune them.

The problem with reused blocks is that they become disjointed. If I fix a problem that I find in the function within this script, I might or might not go back and update it everywhere else that I use it. In your own local scripting, you can address that problem by having a single copy of a script dot-sourced into all of your others. However, if each script file has its own copy of the function, it’s easier to customize it when necessary.

There’s no “right” answer or approach when it comes to code/script reusability. The overall goal is to reduce duplication of work, not to make reusable blocks. Never be so bound up in making a block reusable that you end up doing more overall work.

Don’t Repeat Yourself

A lofty goal related to code/script reusability is the Don’t Repeat Yourself principle (DRY). As I was reworking the original version of this script, I found that I was essentially taking the script block for the previous virtual machine property, copy/pasting it, and then updating the property and variable names. There was a tiny bit of customization on a few of them, but overall the blocks were syntactically identical. The script worked, and it was easy to follow along, but that’s not really an efficient way to write a script. Computers are quite skilled at repeating tasks. Humans, on the contrary, quickly tire of repetition. Therefore, it only makes sense to let the computer do whatever you’d rather skip.

DRY also addresses the issue of tiny mistakes. Let’s say that I duplicated the script for changing the ChassisSerialNumber, but left in the property name for the ChassisAssetTag. That would mean that your update would result in the ChassisAssetTag that you specified being applied to the ChassisAssetTag field and the ChassisSerialNumber. These types of errors are extremely common when you copy/paste/modify blocks.

Look at the line that says Change-VMSetting. That contains a fairly short bit of script that changes the properties of an object. I won’t dive too deeply into the details; the important part is that this particular function might be called up to five times during each iteration of the script. It’s only typed once, though. If I (or you) find a bug in it, there’s only place that I need to make corrections.

Internal Functions in Your Own Scripts

Notice that I put my functions into a begin {} block. If this script is on the right side of a pipe, then its process {} block might be called multiple times. The functions only need to be defined once. Leaving them in the begin block provides a minor performance boost because that part of the script won’t need to be parsed on each pass.

I also chose to use non-standard verbs “Process” and “Change” for the functions. That’s because I can never be entirely certain about function names that might already be in the global namespace or that might be in other scripts that include mine. Programming languages tend to implement namespaces to avoid such naming collisions, but PowerShell does not have that level of namespace support just yet. Keep that problem in mind when writing your own internal functions.