Run a batch file on Windows 7 shutdown before closing programs (preferably on power button press)

lime picture lime · Jan 24, 2011 · Viewed 18.8k times · Source

I have a Windows 7 box with multiple VMWare Player machines that are started on boot. I use WMWare VIX and a batch file to shutdown the virtual machines like so:

"C:\path\to\VMWare VIX\vmrun.exe" -T player stop "C:\path\to\machine.vmx" soft

What I want to accomplish is to run these commands when the host machine is shut down, so that I don't need to shut down each VM separately. When deployed, shutdowns will likely be started with a short press of the ACPI power button, not from the Start menu. Ways I've tried that don't work (at least not well enough):

  1. Group Policies - The most obvious way to go, but in Windows 7, the option to run shutdown scripts 'asynchronously' is gone.
    The result is that Windows first tells all open windows to close, the VMs respond that they are in use and you get the 'Force close' dialog. Only after VMWare Player and everything else is closed are the scripts run, to no use.
    You'd think this could be changed, but I think I remember seeing some official MS note along the lines of "nope, sorry". Can't find the link though.

  2. Use one batch file that closes all VMs and then shutdowns the host as a desktop shortcut instead of the usual shutdown button. - Works, and that's about what I'm using right now while developing.
    But using the ACPI power button initiates a normal shutdown with the same result as earlier, and it would be better if the end-user who turns the machine on and off on a daily basis wouldn't need to use a monitor and mouse.
    So what I'm googling for at the moment is a way to modify the action called when pressing the physical power button. Windows allows you to choose between some different actions like Sleep, Hibernate, Restart etc, but could you change that into 'Run this .bat'? Or maybe change the behaviour of the shutdown command altogether?

  3. Programmatically intercept the shutdown message, abort shutdown, run batch file, re-initiate shutdown. There has been some discussion on intercepting shutdown e.g. here, here and here, but I'm still too much of a n00b in all languages except maybe Ruby or Java to really understand if and how it could be done in this case. If someone can clarify how to actually make this work (without getting stuck on the 'Force close' screen) then I'm eager to try out any language you offer.

Answer

lime picture lime · Mar 9, 2011

Okay, so i found a solution that worked for me; a tool called AutoHotkey_L and a script made according to these threads on the AutoHotkey forums.

This is the code I'm using, and I suggest reading up on AutoHotkey commands in the documentation. I'm tweaking the code as I learn what it's actually doing, but for now this works. :)

#NoEnv
#Persistent
SendMode Input
SetWorkingDir %A_ScriptDir%
SetTimer, RunBeforeShutdown, Off

Gui,+LastFound
hwnd:=WinExist()
DllCall("ShutdownBlockReasonCreate","Uint",hwnd,"Str","")
DllCall("kernel32.dll\SetProcessShutdownParameters", UInt, 0x4FF, UInt, 0)
;puts us first in line for getting the shutdown call, i guess?
OnMessage(0x11, "WM_QUERYENDSESSION")
Return

WM_QUERYENDSESSION(wParam, lParam)
{
    ENDSESSION_Logoff = 2147483648
    If (lParam == ENDSESSION_Logoff) {
        global EventType = "Logoff"
    } Else {
        global EventType = "Shutdown"
        ;no way to distinguish between shutdown and restart
    }

    SetTimer, RunBeforeShutdown, On
    Return false
}

runBeforeShutdown:
    SetTimer, RunBeforeShutdown, Off
    Sleep, 1000
    SendInput, {ENTER}  ; gets us past the 'Force shudown' screen
    Sleep, 1000
    #SingleInstance, Force
    DllCall("ShutdownBlockReasonDestroy","Uint",hwnd)
    ; **** Your commands go here ****
    RunWait shutdown.bat
    ; ********

    If (EventType == "Logoff") {
        Shutdown, 0
    } Else {
        Shutdown, 1
    }
    Reload
Return

So right now it only distinguishes between logoff and shutdown, but this post has a simple GUI in HTML that lets the user choose if they want to restart, hibernate etc.

In my case it's okay to interrupt shutdown and run the batch file regardless of whether VMware is running or not, but you can set a condition for it for example like so:

IfWinExist, ahk_class VMPlayerFrame {
    SetTimer, RunBeforeShutdown, On
    Return false
} Else { 
    Return true
}

I have already run into problems with this script, like when the host is so slowed down (memory leakage) that the "Force shudown" screen won't appear in time for the script to close it. And it would probably benefit from keeping track of the number of tries, so that it could shutdown forcibly if the first try fails.

Good enough for now at least. And I may not even need virtualization for my project after all, but hopefully it can help someone else. Alternative solutions are still very welcome.