James Whitlow wrote:
> Does anyone in the group know if it is possible to detect whether the
> computer is shutting down or restarting from a VBScript running as a
> shutdown script?
>
> hi James,
There is an answer, but you are not going to like it.
When the system is ready to shutdown (or logoff?), it sends
a "WM_QUERYENDSESSION" message to all the running apps, i.e.,
it sends the message to all the top-level windows. The purpose
of this message is to warn the apps a shutdown is coming, and
to set their affairs in order. For example, in the case of
text editors, the app will ask the user whether he/she wants
to save their work. Also, it is possible for any app to DELAY
the shutdown, depending on the way they respond to the message.
This doesn't work well for scripters, as there is no way to
detect the shutdown message without subclassing, and that means
using a 3rd-party control. And, if you are willing to use a
3rd-party control, then what window do you subclass? WScript
does have a very obscure window (no title, and not visible)
which it uses to intercept broadcast system messages (like the
shutdown message), but WScript is not going to tell you when it
gets such a message. It seems to me that WScript just terminates
the script, but I can't offer any proof-positive for this
assertion.
I have done some subclassing in script, using a proprietary
3rd-party control, and subclassing a window (vb form) that I
had been using as a gui. But this would get awkward for you
because both using windows (vb forms) and subclassing are
not native to WScript.
I have another suggestion, use another language which DOES
allow for intercepting system messages. Write yourself a
script (in this other language) which just sits around
waiting for shutdown, and then calls your shutdown script.
One such language is FBSL (for Freestyle Basic Scripting
Language), found here:
http://www.fbsl.net/
I have attached an fbsl script (a template script) which
shows how to detect a shutdown (look for the "message loop"
near the end of the script. As a template, it does not
contain the code you might use, but it would be easy to
either call your script or to embed your script in the
fbsl script (using the ms script control).
And yes, if you have a vb compiler, you could just as well
write the code in vb (i.e., stay comfortably within the
trustworthy-and-reliable ms family of products).
cheers, jw
____________________________________________________________
You got questions? WE GOT ANSWERS!!! ..(but, no guarantee
the answers will be applicable to the questions)
' fbsl_Template_wStatusBar Script, jw 27Jan09
'
' --- description block --------------------------
'
' Title: fbsl Template Script (with Statusbar)...
'
' Description: This is the starting point for most any fbsl script
' you write, and contains the "bare minimum" of features
' to get you going (namely a debug msg window)...
'
' Author: mr_unreliable
' Website: none at present...
'
' Usage: Use at you own risk, tested on win98se...
'
' --- revision history ---------------------------
' 27Jan09: initial attempt, based on fbsl_Template...
' --- end of description block -------------------
#Uses "@|WIN32"
#AppType GUI ' hide the (fbsl) launcher console...
#Option Strict
#DllDeclare user32("UpdateWindow", "GetDlgItem")
#DllDeclare Gdi32("CreateSolidBrush", "SetBkColor")
#DllDeclare kernel32("RtlMoveMemory" As CopyMemory, "GetTimeFormat")
Macro dbMsg(sMsg) = SendMessage(hDBMsgs, LB_ADDSTRING, 0, sMsg)
Macro ScrollDn() = SendMessage(hDBMsgs, WM_VSCROLL, SB_BOTTOM, 0)
' testing more-than-one stmt in a macro, it works -- BUT msgbox's displayed
' in REVERSE order(?). o.k. so it doesn't work, the second msgbox is
' apparently regarded as not-in-the-macro, and so displays immediately...
' Macro Test2Msg() = MsgBox(ME, "MB_1", "Test2Msg", MB_OK) : MsgBox(ME, "MB_2", "Test2Msg", MB_OK)
#Define Long Integer ' define "Long" as (fbsl) Integer
#Define LOCALE_USER_DEFAULT 0x0400 ' for formatting time
#Define GTF_LOCALE_DEFAULT_FORMAT 0
Type Byte ' used in string allocation (locally formatted time)
Default %w * 8
End Type
'
Dim FBScript as clsFBScript ' as Object, (i.e., my WScript clone)
'
' --- dialog configuration constants -------------
Dim hDBMsgs as Integer, hStatusBar as Integer
Dim lbStyle as Integer = WS_CHILD BOr WS_VISIBLE BOr WS_VSCROLL BOr LBS_NOINTEGRALHEIGHT
'
Dim IDC_DBMSTATICCTRL as Long = 1000 ' static control id
' initialize dialog layout...
Const wdDlg = 500, htDlg = 350
Dim wdDBM = wdDlg - 30
Dim htDBM = htDlg - 30 - 20 - 35 ' - caption - static - statusbar
Const sMe = "[main], "
Const sEvnt = "[event], "
'
Const cbTime = 32 ' allocated bytes
Dim sFormattedTime[cbTime] as Byte
' colorization...
Dim crAliceBlue = RGB(0xF0,0xF0,0xFF)
Dim crGainsboro = RGB(0xDC,0xDC,0xDC)
Dim frmBackcolor = crGainsboro
Const crNavy = RGB(0,0,0x80)
Dim crStaticText = crNavy
' create a brush for colorizing the static control (don't forget to release it)...
Dim m_frmColorBrush = CreateSolidBrush(frmBackcolor)
'
Dim sMsePos as String
' --- end of declarations and constants ----------
' ================================================
' === MAIN LINE CODE BEGINS HERE =================
' ================================================
Const sMe = "[main], "
Set FBScript = New clsFBScript ' used later for script directory/name
ReSize(ME, 100,100, wdDlg,htDlg) ' setwindowpos
FBSL_Settext(ME," " & FBScript.ScriptName() & " Script Dialog.. ")
Fbsl_SetFormColor(ME, frmBackcolor)
FBSL_Control("Static", ME, "debugging messages...", IDC_DBMSTATICCTRL, 10,10, 200,17, WS_CHILD BOr WS_VISIBLE, 0)
hDBMsgs = FBSL_Control("ListBox", ME, "", 0, 10,28, wdDBM,htDBM, lbStyle, WS_EX_STATICEDGE)
hStatusBar = FBSL_control("msctls_statusbar32",ME,"",0, 0,0, 0,0, WS_VISIBLE + WS_CHILD,0)
' partition the status bar...
Dim aryPart[1] as Long ' array of RIGHT coordinates (-1 => rt edge)
aryPart[0] = wdDlg - 130 : aryPart[1] = %-1 ' leave ~130px for the time
Dim nSBParts = 2
SendMessage(hStatusBar, SB_SETPARTS, nSBParts, @aryPart)
' want time in the statusbar, but fbsl defaults to "European Time" (24-hr clock),
' and so, go get your "locale" (i.e., the USA) formatted time...
GetTimeFormat(LOCALE_USER_DEFAULT, 0, 0, "hh':'mm':'ss tt", @sFormattedTime, 32)
' dbPrint(sMe & "Formatted Time: " & sFormattedTime)
SendMessage(hStatusBar, SB_SETTEXT, nSBParts -1, " Time: " & sFormattedTime)
ModStyle(ME, 0, WS_SIZEBOX, 0) ' remove sizebox
SetTimer(ME, NULL, 1000 - 5) ' to update clock (every second)
' Center(ME)
Show(ME)
' Test2Msg()
' dbPrint (sMe & "Message test dbPrint.. ")
dbPrint (sMe & "fbsl dialog open for business... ")
Begin Events
Select Case CBMSG
Case WM_MOUSEMOVE
' this seems to get msemov events only when the mse is over the FORM...
sMsePos = LPad(LoWord(CBLPARAM),4,"0") & "/" & LPad(HiWord(CBLPARAM),4,"0")
SendMessage(hStatusBar, SB_SETTEXT, 0, "Mouse (x/y): " & sMsePos)
Case WM_TIMER
GetTimeFormat(LOCALE_USER_DEFAULT, 0, 0, "hh':'mm':'ss tt", @sFormattedTime, 32)
SendMessage(hStatusBar, SB_SETTEXT, nSBParts -1, " Time: " & sFormattedTime)
' dbMsg(sEvnt & " ..updating the clock at: " & sFormattedTime)
' ScrollDn()
Case WM_CTLCOLORSTATIC ' set the static background color...
' dbPrint "[event], ctlcolorstatic msg rcvd.. "
' uh-oh. Can't use dbPrint in message loop, it throws off event processing.
dbMsg("[event], ctlcolorstatic msg rcvd.. ")
' the aim here is to set the static control's backcolor to be the same as the
' form's backcolor. Unfortunately, a static control has no background color
' property, and so if you wish to set the backcolor you must respond to the
' wm_ctlcolorstatic message. Also, the process for handling the colorstatic
' message must be rigidly adhered to. In addition to setting the control's
' background color, you must also set both the TEXT fore and background colors...
'
' the wm_ctlcolorstatic message provides hDC = CBWPARAM and hWnd = CBLPARAM
If (GetDlgItem(ME, IDC_DBMSTATICCTRL) = CBLPARAM) Then ' check which static
SetTextColor(CBWPARAM, crStaticText) ' set text color
SetBkColor(CBWPARAM, frmBackcolor) ' set text background color
Return m_frmColorBrush ' return control background color
End If
Case WM_QUERYENDSESSION ' check for system shut-down
MsgBox(ME, "Your " & FBScript.ScriptName & " Script will terminate now.. ", _
"Shutdown Event Detected", MB_OK)
Hide(ME)
DeleteObject(m_frmColorBrush)' return any open system objects (pens and brushes)...
Delete FBScript ' delete any open class objects
ExitProgram(0)
Case WM_CLOSE
dbMsg("") ' space
dbMsg(" ====================================")
dbMsg(sEvnt & " ..THIS DIALOG CLOSED BY USER")
dbMsg(" ====================================")
dbMsg("") ' space
ScrollDn()
UpdateWindow(hDBMsgs)
Sleep(1000) ' allow time to read
Hide(ME)
DeleteObject(m_frmColorBrush)' return any open system objects (pens and brushes)...
Delete FBScript ' delete any open class objects
ExitProgram(0)
Case WM_SYSCOMMAND
If SC_MINIMIZE = CBWPARAM Then Hide(ME) ' test one-line if stmt...
' Hide(ME)
' End If
End Select ' cbmsg
End Events
Sub BugAssert(byRef bTest, byRef sMsg)
if (bTest) then Exit Sub ' assume assertion is TRUE
' throw an error if assertion is NOT TRUE...
MsgBox(0, sMsg, "TrayLauncher Program Error", MB_ICONERROR BOr MB_OK)
ExitProgram(0)
End Sub
Sub dbPrint(byRef sMsg)
BugAssert ((GetType(sMsg) = FBVT_STRING), "uh-oh. dbPrint parameter is NOT a STRING! ")
SendMessage(hDBMsgs, LB_ADDSTRING, 0, sMsg)
' also scroll down (to keep most recent message in view)...
SendMessage(hDBMsgs, WM_VSCROLL, SB_BOTTOM, 0)
UpdateWindow(hDBMsgs) ' this doesn't appear to be necessary...
End Sub
' ------------------------------------------------
' --- SCRIPT-RELATED CHARACTERISTICS (CLASS) -----
' ------------------------------------------------
' Note: for the inspiration to this, see vbScript's WScript object.
Class clsFBScript
' class "local variables" (apparently no need for "dim" them)...
$sMe, $sScriptDir, $sScriptName
$sCompilerPart, $sScriptFileSpec, $sScriptFileExt
%iLastBackSlash
Private:
Method Initialize()
sMe = "[clsFBScript], "
' the intention here is to verify the command line. It is expected to
' contain the fbsl (compiler) exe, and then the FileSpec for THIS script.
' note: command(-1) retrieves the ENTIRE command line. To get the directory
' of this script, use Command(1) -- the script full path -- and then remove
' the script filename.ext from the path...
' verify that command(0) actually contains the fbsl compiler...
sCompilerPart = Right(Command(0), 8) ' expect "fbsl.exe"
BugAssert((LCase(sCompilerPart) = "fbsl.exe"), "uh-oh, sCompiler FileSpec looks wrong.. ")
' verify that command(1) actually contains a script filespec...
sScriptFileSpec = Command(1)
sScriptFileExt = Right(sScriptFileSpec, 3)
' dbPrint(sMe & "sScriptFileExt: " & sScriptFileExt)
BugAssert((LCase(sScriptFileExt) = "fbs"), "uh-oh, sScriptFileSpec has the WROMG EXT.. ")
' get the directory containing THIS script...
iLastBackSlash = InStrRev(sScriptFileSpec, "\")
sScriptDir = Left(sScriptFileSpec, iLastBackSlash) ' includes "\"
' get the script file name...
sScriptName = Right(sScriptFileSpec, Len(sScriptFileSpec) - iLastBackSlash)
' MsgBox(ME, sScriptName, "Interim Reporting", MB_OK)
End Method
Public:
Function ScriptDirectory()
' dbMsg(sMe & sScriptDir)
Return sScriptDir
End Function
Function ScriptName()
' MsgBox(ME, sScriptName, "Interim Reporting", MB_OK)
Return sScriptName
End Function
End Class ' clsApplication