In our corporate environment, our Windows 7 workstations can be powered off or restarted remotely in order to deploy updates, patches, or new software. For those of us running virtual machines in VMware Workstation, this means a running guest operating system would experience an abrupt power-off as the host machine is reset. At the very minimum, this causes the ‘Windows was not shut down properly’ message to appear when the guest OS is powered on, and it may cause serious problems with the integrity of the guest OS or the virtual machine files.
I wanted to improve the situation through the use of shutdown/logoff and startup/logon scripts on the host and the vmrun
command line utility that ships with VMware Workstation and VMware Server, and I had three goals in mind.
- Any running guest OS would be allowed to shut down or suspend before the host powered off
- An event would be written to the Application log on the host for each guest that was shut down or suspended
- A complementary process would start or resume each guest that was running when the host restarted
The VBScripts are written for use on a 64-bit Windows 7 host.
The challenge of correct timing
I soon ran into a problem when trying to use Local Group Policy to deploy the shutdown/logoff script on my Windows 7 host. The order of events is such that the shutdown/logoff process is halted by the still-running vmware.exe process (the VMware Workstation UI). I’ve added some notes about this behavior to the bottom of the post, but I have not yet solved this problem.
A word about networking
If the network adapter in the guest OS is not reconnected upon resuming from suspend (in Windows, this can be resolved with ipconfig /renew
), it may be that the VMware Tools scripts are not running at start up/resume. Disconnecting from the network is a normal process when the VM receives a suspend
command with a soft
parameter. I have found that I can ensure that the network adapter is reconnected upon resuming by changing the Power Options for the VM to use “Start Up Guest” instead of “Power On”.
The shutdown/logoff script
This is what I came up with for the shutdown/logoff script.
' This script passes the suspend command to each running VMware virtual machine, allowing it to gracefully sleep/hibernate ' It also saves the list of running VMs to a text file in %TEMP%, which may be parsed by a startup/logon script to resume the VMs ' It can be used as a shutdown/logoff script ' //ardamis.com/2012/03/08/managing-vmware-workstation-virtual-machines-with-vbscript/ Option Explicit Dim objShell, objScriptExec, objFSO, WshShell, strRunCmd Dim TEMP, strFileName, vmList, objFile, ForWriting, result, textLines, textLine, isFirstLine 'Initialize the objShell Set objShell = CreateObject("WScript.Shell") 'Execute vmrun and create the list of running virtual machines Set objScriptExec = objShell.Exec("""C:\Program Files (x86)\VMware\VMware Workstation\vmrun.exe"" list") 'Write the list to a variable vmList = objScriptExec.StdOut.ReadAll() 'Debug 'WScript.Echo vmList 'Initialize the wshShell Set WshShell = WScript.CreateObject("WSCript.shell") TEMP = WshShell.ExpandEnvironmentStrings("%TEMP%") 'Enter the path to the file that will hold the names of the running VMs strFileName = TEMP & "\vms.txt" 'Debug 'WScript.Echo strFileName 'Initialize the objFSO Set objFSO = CreateObject("Scripting.FileSystemObject") 'Create the file Set objFile = objFSO.CreateTextFile(strFileName) 'Write the list to the file objFile.Write vmList 'Close the file objFile.Close 'Split the list into lines textLines = Split(vmList,vbCrLf) 'Loop through the lines For Each textLine in textLines 'Compare the first line in the file to the text "Total running VMs:" isFirstLine = StrComp(Mid(textLine, 1, 18), "Total running VMs:") 'If the line has more than 0 character (is not blank) and is not the first line If Len(textLine) > 0 And isFirstLine <> 0 Then 'Write to the application log WshShell.LogEvent 4, "Event: VMware is attempting to suspend the VM at " & textLine 'Save the command as a variable strRunCmd = """C:\Program Files (x86)\VMware\VMware Workstation\vmrun.exe"" -T ws suspend """ & textLine & """ soft" 'Run the command result = WshShell.Run(strRunCmd, 0, True) 'Write to the application log If result = 0 Then WshShell.LogEvent 4, "Event: VMware successfully suspended the VM at " & textLine Else WshShell.LogEvent 1, "Event: VMware was unable to suspend the VM at " & textLine End If 'Debug 'WScript.Echo result 'Debug 'WScript.Echo textLine End If Next
The vms.txt file that the script creates will contain something like the following, if it finds a running VM:
Total running VMs: 1 C:\Virtual Machines\Windows XP Professional\Windows XP Professional.vmx
I have chosen to suspend the virtual machine, rather than shut it down, because I don’t want to lose any work that may be unsaved. The official explanation of the suspend
power command from VMware:
Suspends a virtual machine (
.vmx
file) or team (.vmtm
) without shutting down, so local work can resume later. Thesoft
option suspends the guest after running system scripts. On Windows guests, these scripts release the IP address. On Linux guests, the scripts suspend networking. Thehard
option suspends the guest without running the scripts. The default is to use thepowerType
value specified in the.vmx
file, if present.
To resume virtual machine operation aftersuspend
, use thestart
command. On Windows, the IP address is retrieved. On Linux, networking is restarted.
http://www.vmware.com/support/developer/vix-api/vix110_vmrun_command.pdf
The startup/logon script
This is the startup/logo script that compliments the shutdown/logoff script.
' This script reads a list of VMware virtual machines from a text file and passes the start command to each VM, allowing it to resume from sleep/hibernate/shutdown ' It can be used as a startup/logon script ' //ardamis.com/2012/03/08/managing-vmware-workstation-virtual-machines-with-vbscript/ Option Explicit Dim objFSO, WshShell, strRunCmd Dim TEMP, strFileName, objTextStream, vmList, ForReading, result, textLines, textLine, isFirstLine 'Initialize the wshShell Set WshShell = WScript.CreateObject("WSCript.shell") TEMP = WshShell.ExpandEnvironmentStrings("%TEMP%") 'Enter the path to the text file that will hold the names of the running VMs strFileName = TEMP & "\vms.txt" WshShell.LogEvent 4, "Event: VMware is attempting to find a list of VMs to restart in " & strFileName 'Initialize the objFSO Set objFSO = CreateObject("Scripting.FileSystemObject") 'Check to see if the text file exists If objFSO.FileExists(strFileName) Then 'Open the text file Set objTextStream = objFSO.OpenTextFile(strFileName, 1) 'Read the contents into a variable vmList = objTextStream.ReadAll() 'Debug 'WScript.Echo vmList 'Close the text file objTextStream.Close 'Split the list into lines textLines = Split(vmList,vbCrLf) 'Loop through the lines For Each textLine in textLines 'Compare the first line in the file to the text "Total running VMs: 0" isFirstLine = StrComp(Mid(textLine, 1, 20), "Total running VMs: 0") 'Check to see if the first line of the text file reports 0 running VMs If isFirstLine = 0 Then 'Write to the application log WshShell.LogEvent 4, "Event: VMware found no running VMs were enumerated in " & strFileName End If 'Compare the first line in the file to the text "Total running VMs:" isFirstLine = StrComp(Mid(textLine, 1, 18), "Total running VMs:") 'If the line has more than 0 character (is not blank) and is not the first line If Len(textLine) > 0 And isFirstLine <> 0 Then 'Write to the application log WshShell.LogEvent 4, "Event: VMware is attempting to start the VM at " & textLine 'Save the command as a variable strRunCmd = """C:\Program Files (x86)\VMware\VMware Workstation\vmrun.exe"" -T ws start """ & textLine 'Run the command result = WshShell.Run(strRunCmd, 0, True) 'Write to the application log If result = 0 Then WshShell.LogEvent 4, "Event: VMware successfully started the VM at " & textLine Else WshShell.LogEvent 1, "Event: VMware was unable to start the VM at " & textLine End If 'Debug 'WScript.Echo result 'Debug 'WScript.Echo textLine End If Next Else WshShell.LogEvent 4, "Event: VMware did not find a list of VMs to restart at " & strFileName End If
This script starts/resumes the virtual machine and launches the Workstation user interface.
Timing of the shutdown/logoff events
Using Group Policy shutdown/logoff scripts seemed a natural way to power off and resume the virtual machines, but there is a timing problem that prevents this from working as desired. Instead of running any logoff scripts immediately when the user chooses to log off, Windows first tries to close any open applications by ending running processes. When it encounters vmware.exe, which is the VMware Workstation GUI, it pauses the log off process and asks the user whether the log off should force the applications to close, or if the log off should be cancelled.
On Windows 7, the screen will dim and the programs that are preventing Windows from logging off the user or shutting down are listed.
1 program still needs to close:
(Waiting for) [VM name] – VMware Workstation
1 virtual machine is in use.To close the program that is preventing Windows from logging off, click Cancel, and then close the program.
[Force log off] [Cancel]
As pointed out on the vmware.com community forums, this only happens when the Workstation UI process is running at the time.
We don’t support running Workstation a service. I assume you’re using some third-party tool for that?
Anyway, that error only appears if the Workstation UI is running when you try to log off. If you kill the UI process (vmware.exe) and let the VM run in the background, you shouldn’t get that. Alternatively you could try running VMware Player instead of VMware Workstation a service.
http://communities.vmware.com/message/1189261
Quitting the Workstation process and allowing the scripts to close out the actual VMs seemed like an acceptable compromise. It still required some user interaction on the host to prepare the guest to be powered off, but I figured that there may be ways to end the Workstation UI programatically prior to the logoff.
I decided to consult the Workstation 7.1 manual:
You can set a virtual machine that is powered on to continue running in the background when you close a virtual machine or team tab, or when you exit Workstation. You can still interact with it through VNC or another service.
From the VMware Workstation menu bar, choose Edit > Preferences. On the Workspace tab, select Keep VMs running after Workstation closes and click OK.
http://www.vmware.com/pdf/ws71_manual.pdf
I found that if the VMware Workstation application is already closed, the shutdown/logoff proceeds smoothly and the scripts fire. But there is another problem. By the time the logoff script runs, the vmware-vmx.exe process (the actual virtual machine) has already been quit, so the vmrun list
command finds no running VMs and you end up with a vms.txt file that contains this:
Total running VMs: 0
At this point, running VMware Player like a service logged on as the Local System account, which presumably will allow the VMs to continue running even while users on the host log out, becomes the best solution, as it theoretically avoids the problem of a) requiring the user to close the UI and b) the vmware-vmx.exe process being ended as the user logs off. VMware Player is included with Workstation, but we’re not quite out of the woods yet. According to another VMware employee:
VMware Player is not built to run as a service. However, there are different discussions and possible solutions using srvany.exe.
If you google for site:vmware.com srvany player you will find some interesting posts for this issue.
http://communities.vmware.com/message/1595588
I followed through on this suggestion, and while it didn’t solve my problem, I’m including some detail here in the hopes that it will further someone else’s exploration.
To run an application as though it were a service, you need two executables from the Windows Server 2003 Resource Kit Tools:
- Instsrv.exe: Service Installer
- Srvany.exe: Applications as Services Utility
The Windows Server 2003 Resource Kit Tools are not officially supported on Windows 7, and in fact the installer will cause the Program Compatibility Assistant to warn that “This program has known compatibility issues”, but my observations seems to support other people’s reports that they work fine.
A Windows .Net magazine article from 2004 referencing Workstation 4.0 is still a good guide to follow in setting this up. My adjustments for using Player are below:
- Install the Windows Server 2003 Resource Kit Tools and reboot
- Locate srvany.exe (the default location is C:\Program Files (x86)\Windows Resource Kits\Tools\srvany.exe)
- Open an elevated command prompt and enter instsrv [service name] [srvany.exe location], using anything you want for the service name (ex: instsrv vmplayer “C:\Program Files (x86)\Windows Resource Kits\Tools\srvany.exe”)
- Open an elevated instance of the Windows Services snap-in (services.msc), right-click the newly created service, choose the Log On tab, and check the box next to “Allow service to interact with desktop”
- Open an elevated instance of Registry Editor (regedit.exe)
- Locate vmplayer.exe (the default location is C:\Program Files (x86)\VMware\VMware Workstation\vmplayer.exe)
- Navigate to your service’s key at HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\services\[service name]
- Create a new subkey named Parameters under your service’s key
- Create a new String Value named Application under the Parameters key
- Double-click the Application value and enter the path to vmplayer.exe as the value’s data
You should now be able to start the vmplayer service (or whatever you chose to name it) from the Services snap-in.
But, we’re still not home free. This doesn’t magically allow any instance of VMware Player to persist through a user logoff (which is really what I was hoping to get).
The harsh reality set in when I came across this thread, wherein continuum (a guy with incredible insight into VMware) bursts the vm-as-a-service balloon:
there are 2 ways to run the service …
– run it with “local system account” plus “allow to interact …” checked
– run it as a user – needs the password of this userIn first case the VM starts after a user is logged in – the VM is visible and you can interact with it but you can NOT log off.
It use process vmplayer.exe plus vmware-vmx.exe.In second case the VM is invisible and only process vmware-vmx.exe runs but no user has to be logged in
http://communities.vmware.com/message/1471897#1471897
What I need is the best of both worlds: a VMware GUI environment, be it Workstation or Player, that is able to load a VM when a user logs into the host, and at the same time is able to keep the VM running while that user logs off.
Ultimately, I’m left with the same feelings as expressed toward the end of the thread at http://communities.vmware.com/message/1402590: why should useful and highly sought-after functionality that is present in the free but no-longer-actively developed Server product be absent from the non-free and actively developed Workstation product?
The answer, if there is one, may be that VMware doesn’t want to get involved.
One of the nastier corner cases is, what happens if there is a failure suspending the VM? Do we decide the user really wanted to log off and forcibly kill the VM, or do we veto the log-off and go back to the user for input (which, if you are using a laptop, means closing the lid leaves the VM running and kills the battery)? What if the VM process crashes during this – who initiates the log-off then? What if the VM is busy doing something expensive (like disk consolidation) and cannot suspend? Getting involved in the log-off path is, realistically, just a mess of bugs.
http://communities.vmware.com/thread/233117
Final thoughts
As with pretty much anything I do, this is far from finished. I’m not ready to give up on the goal of using scripts to start and suspend VMs without any user interaction. But it seems that it’s going to be much more difficult than one might reasonably expect.
As for the scripts themselves, I’m slightly bothered by the empty command prompt window that is opened momentarily by objShell.Exec
. I’m not sure that I like saving the list of running VMs to %TEMP%
, where it may be deleted by other processes that clean that location at login/logout. But they are a good start, and they seem to serve their purpose.
Just found these scripts and started using them. Thank you!
Made a minor modification to the original to pause the startup of the next VM guest if there is a dependency on the prior VM guest to be running. This change requires the VM Tools be installed on the guests (which is usually done) and will wait until the VM Tools start and can get the IP Address of the prior VM Guest before starting the next one.
This is helpful if the first VM is a domain controller and the other VMs depend on the DC being up before starting, or in my case, FreeNAS running an iSCSI target with two windows servers in a cluster depending on the iSCSI connection.
Here is the dif of my changes to the shutdown/logoff script:
Comparing files ORIG\shutdown-logoff.vbs and SHUTDOWN-LOGOFF.VBS
***** ORIG\shutdown-logoff.vbs
TEMP = WshShell.ExpandEnvironmentStrings(“%TEMP%”)
***** SHUTDOWN-LOGOFF.VBS
‘changed the location of the vms.txt file so it can be preserved and not accidentally deleted
TEMP = WshShell.ExpandEnvironmentStrings(“%USERPROFILE%”)
*****
Here are the changes I made to the startup/logon script:
Comparing files ORIG\startup-logon.vbs and STARTUP-LOGON.VBS
***** ORIG\startup-logon.vbs
Dim TEMP, strFileName, objTextStream, vmList, ForReading, result, textLines, textLine, isFirstLine
***** STARTUP-LOGON.VBS
Dim TEMP, strFileName, objTextStream, vmList, ForReading, result, textLines, textLine, isFirstLine
Dim textLineArr, bolHaveOption, bolVMUp
*****
***** ORIG\startup-logon.vbs
TEMP = WshShell.ExpandEnvironmentStrings(“%TEMP%”)
***** STARTUP-LOGON.VBS
‘to make it easier to save and find vms.txt, changed the location from %TEMP% to %USERPROFILE%
TEMP = WshShell.ExpandEnvironmentStrings(“%USERPROFILE%”)
*****
***** ORIG\startup-logon.vbs
‘Enter the path to the text file that will hold the names of the running VMs
strFileName = TEMP & “\vms.txt”
***** STARTUP-LOGON.VBS
‘Enter the path to the text file that will hold the names of the running VMs
‘I know this breaks the sharing of this file between this script’s siste, but
‘this way it preserves any processing options setup to start the VM Guests
‘with dependencies
strFileName = TEMP & “\vmstart.txt”
*****
***** ORIG\startup-logon.vbs
‘Write to the application log
WshShell.LogEvent 4, “Event: VMware is attempting to start the VM at ” & textLine
***** STARTUP-LOGON.VBS
‘the line in textLine could have ,wait on it to pause the starup of the next VM Guest
‘parse the data in textLine to check for the presence of the wait option
textLineArr = Split(textLine,”,”)
‘checking the length of the array will determine if the presence of the wait option
‘set a boolean to be used laster to tell of there is a wait option, the coding below
‘provides for other options to be allowed in the future, right now wait is the only
‘option I need
bolHaveOption = False
if UBound(textLineArr) > 0 then
if Len(textLineArr(UBound(textLineArr)) > 0 then
bolHaveOption = True
end if
end if
‘Write to the application log
WshShell.LogEvent 4, “Event: VMware is attempting to start the VM at ” & textLineArr(0)
*****
***** ORIG\startup-logon.vbs
‘Save the command as a variable
strRunCmd = “””C:\Program Files (x86)\VMware\VMware Workstation\vmrun.exe”” -T ws start “”” & textLine
***** STARTUP-LOGON.VBS
‘Save the command as a variable
‘want to use vmplayer instead of vm workstation
strRunCmd = “””C:\Program Files (x86)\VMware\VMware Workstation\vmrun.exe”” -T player start “”” & textL
ineArr(0)
*****
***** ORIG\startup-logon.vbs
If result = 0 Then
WshShell.LogEvent 4, “Event: VMware successfully started the VM at ” & textLine
Else
WshShell.LogEvent 1, “Event: VMware was unable to start the VM at ” & textLine
End If
***** STARTUP-LOGON.VBS
If result = 0 Then
WshShell.LogEvent 4, “Event: VMware successfully started the VM at ” & textLineArr(0)
Else
WshShell.LogEvent 1, “Event: VMware was unable to start the VM at ” & textLineArr(0)
End If
*****
***** ORIG\startup-logon.vbs
End If
Next
Else
WshShell.LogEvent 4, “Event: VMware did not find a list of VMs to restart at ” & strFileName
End If
***** STARTUP-LOGON.VBS
‘check to see if there is a processing option, then do what that option
‘requies. right now “wait” is the only one.
if bolHaveOption then
Select Case LCase(textLineArr(UBound(textLineArr)))
Case “wait”
‘set a boolean to false to denote the VM Guest is not up yet
bolVMUp = False
*****
***** ORIG\startup-logon.vbs
***** STARTUP-LOGON.VBS
‘setup the command to query the VM Guest via VMware Tools for the IP Address
‘which will be available once the VMware Tools service(s) start
strRunCmd = “””C:\Program Files (x86)\VMware\VMware Workstation\vmrun.exe”” -T
player getGuestIPAddress “”” & textLineArr(0)
‘perform this loop until the VM Guest is up
Do Until bolVMUp
‘run the command
result = WshShell.Run(strRunCmd, 0, True)
‘result will be -1 until the VM Tools start and returns the IP Adress
‘at which time result will equal 0, so break out of the loop and
‘continue processing
if result = 0 then
bolVMUp = True
end if
Loop
End Select
end if
End If
Next
Else
WshShell.LogEvent 4, “Event: VMware did not find a list of VMs to restart at ” & strFileName
End If
*****
I’m at a complete loss with vbs. How may I modify these to simply suspend or start a specific vmx and have this run hidden?