Posey's Tips & Tricks
Using PowerShell to Start Hyper-V VMs in Sequence
Here's how to streamline virtual machine launches with PowerShell scripts for efficient and sequential booting.
Recently, I have been hard at work building the lab environment that will be used for a video training course that I am about to create. Like many of the lab environments that I have used in the past, this one involves a number of different Hyper-V virtual machines, and those VMs must be started is a specific sequence. While I could manually start each individual virtual machine, it is a lot easier to simply script the virtual machine boot process.
Hyper-V includes a PowerShell cmdlet called Start-VM that you can use to boot a virtual machine. All you have to do is append the name of the virtual machine that you want to boot. With that in mind, it's theoretically possible to start a collection of virtual machines in sequence by using something as simple as:
Start-VM VM1
Start-VM VM2
Start-VM VM3
Start-VM VM4
Start-VM VM5
The problem with this approach is that as soon as the first virtual machine is powered on, PowerShell powers on the second VM. The script listed above does not give the virtual machine time to boot before powering up the other virtual machines.
One technique that I have occasionally seen used is to insert a time delay between each Start-VM command. You can accomplish this by using the Start-Sleep command. This command simply forces PowerShell to wait for a specified amount of time before executing the next command in the script. If for example, you wanted to have a two minute delay between each VM being started, then you could do something like this:
Start-VM VM1
Start-Sleep -Seconds 120
Start-VM VM2
Start-Sleep -Seconds 120
Start-VM VM3
Start-Sleep -Seconds 120
Start-VM VM4
Start-Sleep -Seconds 120
Start-VM VM5
This approach undoubtedly works better than the previous script, but it still leaves a lot to chance. Suppose, for example, that one of the virtual machines fails to boot. The script has no way to detect the failure, and it will simply go on booting VMs whether previously started VMs are running or not.
Even if you aren't anticipating a failure, something as simple as installing an update can cause a virtual machine to take longer than normal to boot, potentially exceeding the scripted time delay.
A better approach is to use while loops within your script and use some sort of test to check and see if a virtual machine is running. For the purposes of this article, I will use a ping test, but you could check for just about anything (such as a system service running or a process running).
To show you how this technique works, imagine that I want to write a script that starts a VM named VM1. I then want the script to start VM2, but only after confirming that Windows is able to ping VM1. Here is what such a script might look like:
Start-VM VM1
while (-not (Test-Connection -ComputerName VM1 -Count 1 -Quiet)) {
Start-Sleep -Seconds 30
}
Start-VM VM2
The script shown above starts out by using the Start-VM cmdlet to boot a virtual machine named VM1. Once that command has been processed, PowerShell launches into a While loop. The loop is based around the Test-Connection cmdlet.
If you are not familiar with the Test-Connection cmdlet, it is the PowerShell equivalent to the PING command. In this case, I am using three cmdlet parameters. The first of these parameters is the virtual machine name (VM1, in this case). I could have just as easily used an IP address if the VM is equipped with a static IP.
The second parameter is Count. Normally, the PING command sends four echo requests. In order for this loop to work properly, we need to limit the ping to a single echo request. The Count parameter simply tells the Test-Connection cmdlet how many echo requests to send (in this case, one).
The third parameter being used is Quiet. This simply suppresses the Test-Connection cmdlet's output so that you don't end up with a bunch of failed ICMP echo requests being displayed while you are waiting for the VM to finish booting.
The really important thing to pay attention to is that the While loop uses the -Not parameter. This parameter causes the loop to look for ping failures, not ping successes. In other words, each time the specified virtual machine fails to respond to an echo request, the while loop executes the loop body. The loop body contains a single cmdlet, which causes the script to pause for 30 seconds. At the end of the 30 second sleep cycle, the loop sends another echo request. The loop will continue until the VM eventually responds to the request, at which point it will start the next VM in the sequence.
One thing to keep in mind is that by default, the Windows Firewall blocks ICMP requests. Therefore, you will need to configure your virtual machines to allow ICMP echo requests before you will be able to use this technique to check to see if a virtual machine is running. If you forget to allow ICMP traffic to pass through the firewall, then the script will be caught in an infinite loop even if the VM is running.
About the Author
Brien Posey is a 22-time Microsoft MVP with decades of IT experience. As a freelance writer, Posey has written thousands of articles and contributed to several dozen books on a wide variety of IT topics. Prior to going freelance, Posey was a CIO for a national chain of hospitals and health care facilities. He has also served as a network administrator for some of the country's largest insurance companies and for the Department of Defense at Fort Knox. In addition to his continued work in IT, Posey has spent the last several years actively training as a commercial scientist-astronaut candidate in preparation to fly on a mission to study polar mesospheric clouds from space. You can follow his spaceflight training on his Web site.