Summary: Microsoft Scripting Guy, Ed Wilson, talks about using Windows PowerShell to shut down all virtual machines on a server running Hyper-V prior to shutting down the server.
Hey, Scripting Guy! From time to time I need to shut down one of our servers that is running the Hyper-V role. The problem is that these servers have multiple virtual machines running, and I do not want to crash the virtual machines. So, right now, I use the Hyper-V Manager, target the server running Hyper-V, and right-click every running virtual machine and select Shut Down. I do not mind doing this, but some of the virtual machines are running things like Exchange and it takes them a long time to shut down. So, what should be a simple task of shutting down one of our Hyper-V servers ends up taking nearly an hour—an hour of very boring work, I might add.
—BB
Hello BB,
Microsoft Scripting Guy, Ed Wilson, is here. Well, the snow is all gone. Yep, that is right, we had snow in Charlotte, NC. I am sitting here, sipping a nice cup of tea. I have been experimenting a bit. Today my teapot contains the following recipe: 2 teaspoons (tsp.) of English Breakfast, 1 tsp. of generic green tea, ½ tsp. of organic orange peel, ½ tsp. of licorice root, 1 tsp. of lemon grass, and a crushed cinnamon stick. Let it steep for 5 minutes, and I have a very nice pot of tea. It is sweet enough that I feel it needs no sweetener whatsoever.
Note In this post, I am using the cmdlets from Windows Server 2012 and the Hyper-V module. I obtained this module on my computer running Windows 8 by downloading and installing the RSAT tools. The Windows 8 RSAT tools are available from the Microsoft Download Center.
Find all running virtual machines
BB, the first thing you need to do is to use the Get-VM cmdlet and find all virtual machines that are running on the remote host. To do this, use the Get-VM and pipe the results to the Where-Object cmdlet and filter out for a state that is equal to running. It is not as difficult as it may sound. The command is shown here.
$runningVM = Get-VM -ComputerName $vmhost| where state -eq 'running'
Because you more than likely have more than a single virtual machine running on your remote Hyper-V server, I use the ForEach language statement to walk through the collection of virtual machines that I store in the $RunningVM variable. Inside the loop, I create a WMI Event that uses the Win32_ComputerShutdownEvent WMI class to let me know when each virtual machine shuts down. This portion of the code is shown here.
foreach ($cn in $runningVM)
{
Write-Debug "registering shutdown event for $($cn.name)"
Register-WmiEvent -Class win32_ComputerShutdownEvent -ComputerName $cn.name `
-SourceIdentifier $cn.name.tostring()
Once I have registered the event, then I call the Stop-Computer cmdlet to shut down the virtual machine. This code is shown here.
Write-debug "Shutting down $($cn.name)"
Stop-Computer -ComputerName $cn.name -Force
Because I registered a win32_ComputerShutdownEvent for the virtual machine, an event triggers after the virtual machine shuts down. To pick up this event, I use the Wait-Event cmdlet. Once the computer shuts down, the event triggers. This code is shown here.
Write-Debug "Waiting for shutdown to complete"
Wait-Event -SourceIdentifier $cn.Name.ToString()}
After all of the virtual machines are shut down, it is time to shut down the Hyper-V host computer (the one that hosts all of the virtual machines). To do this, I use the Stop-Computer cmdlet. This is shown here.
Write-Debug "Shuting down $vmhost"
Stop-Computer -ComputerName $vmhost -Force
Monitor progress of the shutdown
Because I am watching the shutdown of the systems remotely, and I want to know what is happening, I decided to add a series of Write-Debug statements. This is extremely easy to use, and when the script runs without the –debug switch only the default output appears. But when the script runs with the –debug switch, it displays each statement and prompts for the action to take place. This is an interactive type of experience, and it may not be what you want. If you are just wanting more information about each statement without the prompt, then use the Write-Verbose cmdlet instead of Write-Debug. They both work the same—I get them for free as long as I add [cmdletbinding()]
Note During testing, I noticed that sometimes the script would appear to hang. This happens when the virtual machine stops more quickly than I am able to press “y” to confirm the next step, and therefore, the Wait-Event is waiting for an event that has already occurred.
After testing, I decided I was tired of typing “y” all the time, and so I did a global find and replace of Write-Debug with Write-Verbose. I also decided I needed to remove lingering event objects. So I added the following code.
Get-Event -SourceIdentifier $cn.name.Tostring() | Remove-Event
The revised script is shown here.
[cmdletbinding()]
Param($vmhost = 'hyperv2')
Write-Verbose "getting running VM's on $vmhost"
$runningVM = Get-VM -ComputerName $vmhost| where state -eq 'running'
foreach ($cn in $runningVM)
{
Write-Verbose "registering shutdown event for $($cn.name)"
Register-WmiEvent -Class win32_ComputerShutdownEvent -ComputerName $cn.name `
-SourceIdentifier $cn.name.tostring()
Write-Verbose "Shutting down $($cn.name)"
Stop-Computer -ComputerName $cn.name -Force
Write-Verbose "Waiting for shutdown to complete"
Wait-Event -SourceIdentifier $cn.Name.ToString()
Get-Event -SourceIdentifier $cn.name.Tostring() | Remove-Event}
Write-Verbose "Shuting down $vmhost"
Stop-Computer -ComputerName $vmhost -Force
When I run the script now, the following is shown.
BB, that is all there is to using Windows PowerShell to shut down your virtual machines and then to shut down your Hyper-V server. Join me tomorrow when I will talk about more cool stuff.
I invite you to follow me on Twitter and Facebook. If you have any questions, send email to me at scripter@microsoft.com, or post your questions on the Official Scripting Guys Forum. See you tomorrow. Until then, peace.
Ed Wilson, Microsoft Scripting Guy