|
![]() |
| FilesInUse Dialog - Display if Program Running |
This is a User Interface Tips example to demonstrate how you can use a custom action to detect running programs and add these to any "FilesInUse" dialog.
Note that I don't try to terminate the programs although WMI is perfectly capable of doing this as we have no idea of the impact of doing so, for example the user may be editing a critical document which will be lost if the task is simply killed.
I have broken this example into two components, a reusable macro which can be used in other scripts and the code which invokes this macro for this specific script.
Smart people will NEVER duplicate effort, having the logic in one central place minimises testing and effort if (or much more likely "when") an improvement or bug fix is required.
| EXAMPLE - Script Code |
This code uses a "#data" structure to supply the information about the programs which we wish to look for and then passes this to the macro (shown below).
#data "Need2Stop" 2
;---1. Pgm Name 2.User Friendly Name --------------
"Excel.EXE" "Microsoft Excel"
"NotePad.EXE" "The notepad editor"
#data
<$ShowFilesInUseForExecutingPrograms "Need2Stop">
| EXAMPLE - Reusable Code in Macro |
This is some reusable code which should be placed into one of your headers so that it is never repeated. This ensures that you can make all improvements or fixes in the one central location. As all your MSIs reuse the same code the total testing required is reduced.
The "IsProgramRunning()" function uses WMI, you may need to rewrite this function for more general use (its OK as is if it is deployed to a managed environment where WMI is expected to exist). There are a number of suitable command line tools which will report running tasks, you might also be able to detect the Window by title with other techniques.
If WMI is not available the script will not successfully complete.
Please note that this example doesn't do anything if the install is silent, it should perhaps invoke the "ForceReboot" action and use the "AFTERREBOOT" property (let me know if you make this changes - thanks). I am trying to get information on exactly how Windows Installer handles files that are in use. This is another example of why it is a good idea to centralise code, it allows you to do the best possible job in the time available and as time or information becomes available it is simple to update.
#( '<?NewLine>'
;--- You may wish to replace contents of macro with your own code ----
#define? VBSFunction.IsProgramRunning
'============================================
function IsProgramRunning(ProgramName, UserFriendlyName)
'
' This function encapsulates the code required to test
' whether a program is executing.
' Note that WMI is used, this may not work on all
' operating systems or boxes (its probably best used
' in a managed environment).
' Replace the logic in this function if required.
'============================================
;--- Log entry ---------------------------------------------------
CaDebug 0, "IsProgramRunning() : " & ProgramName
VbsCaLogInc 1
CaDebug 2, "Checking: " & UserFriendlyName
;--- Run the query -----------------------------------------------
dim oWMI : set oWMI = GetObject("winmgmts:")
dim Query : Query = "select * from Win32_Process where name='" & ProgramName & "'"
CaDebug 0, "Executing QUERY: " & Query
dim oResult : set oResult = oWmi.Execquery(Query)
CaDebug 0, "Found " & oResult.Count & " programs running."
;--- Set return code and log exit message ------------------------
If oResult.Count > 0 Then
IsProgramRunning = true
CaDebug 0, "Program """ & ProgramName & """ is running"
else
IsProgramRunning = false
CaDebug 0, "Program """ & ProgramName & """ is NOT running"
end if
VbsCaLogInc -1
end function
#)
#( '<?NewLine>'
;--- Define a reusable macro --------------------------------------------
#define ShowFilesInUseForExecutingPrograms
;--- Do some parameter validations --------------------------------------
{$!KEYWORDS} ;;Don't Expect any keywords!
{$!:#1,SEQ,SEQTABLE,CONDITION} ;;List all valid parameters
;--- Build the Custom Action --------------------------------------------
<$VbsCa Binary="PopulateFilesInUseDialog.vbs">
;--- Define global variables -----------------------------------------
dim ExitRc : ExitRc = 0
dim InUseCnt : InUseCnt = 0
dim ProgramNames(), FriendlyNames()
;--- Entry Point -----------------------------------------------------
<$VbsCaEntry "Install">
;--- Update progress dialogs message -----------------------------
CaDebug 1, "Looking for executing programs and need to be stopped..."
;--- Perform the checks specified by the #data structure ---------
#{ FOR @@X = 1 to <?Data:{$#1}.?>
IfProgramExecutingThenAddToList "<?Data:{$#1}.@@X.1>", "<?Data:{$#1}.@@X.2>"
#}
;--- Now Do whatever is required ---------------------------------
<$VbsCaEntryName> = HandleRunningPrograms()
<$/VbsCaEntry>
<?NewLine><?NewLine>
'============================================
function HandleRunningPrograms()
'
' Processes the list of running programs we have created.
' At the moment it simply displays the file in use dialog,
' in future it may do more (need to do anything for silent install?).
'============================================
;--- Need to do anything? ----------------------------------------
HandleRunningPrograms = 0
if InUseCnt = 0 then
exit function
end if
;--- Log entry ---------------------------------------------------
CaDebug 0, "HandleRunningPrograms() : We found " & InUseCnt & " programs that need to be stopped."
VbsCaLogInc 1
;--- For now only handle UI --------------------------------------
HandleRunningPrograms = DisplayFilesInUseDialog()
;--- Log Exit ----------------------------------------------------
CaDebug 0, "Finished HandleRunningPrograms()"
VbsCaLogInc -1
end function
<?NewLine><?NewLine>
'============================================
sub IfProgramExecutingThenAddToList(ProgramName, UserFriendlyName)
'
' As its name implies checks to see if a program is running,
' if it is it is added to a list.
'============================================
;--- Log entry ---------------------------------------------------
CaDebug 0, "IfProgramExecutingThenAddToList() : " & ProgramName
VbsCaLogInc 1
;--- Is it running? ----------------------------------------------
if IsProgramRunning(ProgramName, UserFriendlyName) then
redim preserve ProgramNames(InUseCnt)
redim preserve FriendlyNames(InUseCnt)
ProgramNames(InUseCnt) = ProgramName
FriendlyNames(InUseCnt) = UserFriendlyName
InUseCnt = InUseCnt + 1
end if
;--- Log Exit ----------------------------------------------------
CaDebug 0, "IfProgramExecutingThenAddToList()"
VbsCaLogInc -1
end sub
<?NewLine><?NewLine>
'============================================
function DisplayFilesInUseDialog()
'
' This must only be called sometime after "PrepareDlg",
' it (Windows Installer helps here again...) will hang if
' called at the wrong time (needs improving - loop limit?).
'============================================
;--- Log entry ---------------------------------------------------
CaDebug 0, "DisplayFilesInUseDialog()"
VbsCaLogInc 1
DisplayFilesInUseDialog = 0
;--- Prepare Basic UI message ------------------------------------
dim BasicUiMsg : BasicUiMsg = "The following programs need to be stopped:" & vbCRLF
dim i
for i = 0 to InUseCnt-1
BasicUiMsg = BasicUiMsg & vbCRLF & FriendlyNames(i)
next
;--- Prepare Record ----------------------------------------------
CaDebug 0, "Preparing record for " & InUseCnt & " programs."
dim oMsgRec : set oMsgRec = Installer.CreateRecord(1 + (InUseCnt*2))
oMsgRec.StringData(0) = BasicUiMsg
for i = 0 to InUseCnt-1
oMsgRec.StringData(i*2 + 1) = ProgramNames(i)
oMsgRec.StringData(i*2 + 2) = FriendlyNames(i)
next
;--- Display "FilesInUse" dialog if program running --------------
CaDebug 0, "Displaying FilesInUse dialog for " & InUseCnt & " programs."
dim DialogResponse
do
;--- Display the dialog, capture the return code/response -----
DialogResponse = session.message(msiMessageTypeFilesInUse, oMsgRec)
VbsCaLogInc 1
CaDebug 0, "FileInUse DIALOG RC = " & DialogResponse
VbsCaLogInc -1
;--- User wants to abort? -------------------------------------
if DialogResponse = msiMessageStatusCancel then
DisplayFilesInUseDialog = ERROR_INSTALL_USEREXIT
CaDebug 0, "User wants to cancel the install!"
exit do
end if
;--- Exit if we want to ignore ------------------------------------
if DialogResponse = msiMessageStatusIgnore or DialogResponse = msiMessageStatusOK then
CaDebug 0, "User wants to ignore this issue!" ;;If you don't want remove/disable dialog button
exit do
end if
loop
;--- Log Exit --------------------------------------------------------
CaDebug 0, "Finished DisplayFilesInUseDialog() : RC = " & DisplayFilesInUseDialog
VbsCaLogInc -1
end function
;--- Function defined above ------------------------------------------
<?NewLine><?NewLine>
<$VBSFunction.IsProgramRunning>
<$/VbsCa>
;--- Schedule somewhere after "PrepareDlg" (or dialog won't display) ----
<$VbsCaSetup Binary="PopulateFilesInUseDialog.vbs" Entry="Install" Type="IMMEDIATE" Seq="{$Seq=^MigrateFeatureStates-^}" SeqTable="{$SeqTable=^InstallUISequence^}" CONDITION=^{$Condition=~<$CONDITION_EXCEPT_UNINSTALL>~}^>
#)
![]() | ![]() |