MAKEMSI quickly and reliably creates MSI files in a non-programmatic way
Have your say! Join the MAKEMSI discussion list or view archive! Suggest improvements. No question too simple or too complex.
[Bottom][Contents][Prev]: Error Dialog - Improve[Next]: Licence Dialog Enabling
Have your say! Join the MAKEMSI discussion list or view archive! Suggest improvements. No question too simple or too complex.
\->Tips and Tricks->User Interface Tips->FilesInUse Dialog - Display if Program Running

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. 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. You should also be aware of the "pending restart bugs".

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.

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" 3
   ;---1. Pgm Name (RegExp)  2. Command Line (RegExp)     3. User Friendly Name --------------
          ".*Excel.EXE"         ".*"                         "Microsoft Excel"
          ".*NotePad.EXE"       ".* 1.txt"                   "The notepad editor editing 1.txt"
#data
<$ShowFilesInUseForExecutingPrograms "Need2Stop">

WINXP onwards supports command line checking, where not possible the code will just ignore the command line mask (the 2nd parameter), the code will always first attempt to match the executable name. Case insensitive regular expressions are used to match the program name and command line.

EXAMPLE - Reusable Code in Macro

This is some reusable code which should be placed into one of your header files 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.CountOfProgramOccurances         ;;Version 10.051

       '============================================
       function CountOfProgramOccurances(RegExp4ProgramName, RegExp4CommandLine, 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 ---------------------------------------------------
           CountOfProgramOccurances = 0
           CaDebug 0, "CountOfProgramOccurances() : " & UserFriendlyName
           VbsCaLogInc 1
           CaDebug 0, "RegExp4ProgramName = " & RegExp4ProgramName
           CaDebug 0, "RegExp4CommandLine = " & RegExp4CommandLine

           ;--- Run the query -----------------------------------------------
           dim oWMI    : set oWMI = GetObject("winmgmts:")
           dim Query   : Query    = "select * from Win32_Process"
           CaDebug 0, "Executing QUERY: " & Query
           dim oPrograms : set oPrograms = oWmi.ExecQuery(Query)
           CaDebug 0, "Found " & oPrograms.Count & " programs running, we will compare each against the regular expressions..."
           dim oProgram
           VbsCaLogInc 1
              for each oProgram in oPrograms
                  ;--- Get program details ---------------------------------
                  on error resume next
                  dim PgmName : PgmName = ""
                  dim PgmCl   : PgmCl   = ""
                  if varType(oProgram.ExecutablePath) = vbNULL then
                     PgmName = oProgram.Caption
                  else
                     PgmName = oProgram.ExecutablePath
                  end if
                  if varType(oProgram.CommandLine) <> vbNULL then
                     PgmCl = oProgram.CommandLine          'Should at least contain the program name
                  end if
                  on error goto 0

                  ;--- No compare ------------------------------------------
                  CaDebug 0, "Check Program """ & PgmName & """, with command line: " & PgmCl
                  VbsCaLogInc 1
                     dim Matches
                     oRE.Pattern = RegExp4ProgramName
                     Matches = oRE.test(PgmName)
                     if Matches then
                        CaDebug 0, "Matched program name..."
                        if PgmCl <> "" then
                           ;--- Command line unknown so probably WIN2000 or earlier ---
                           CaDebug 0, "Can check command line so we will..."
                           oRE.Pattern = RegExp4CommandLine
                           Matches = oRE.test(PgmCl)
                        end if
                     end if
                     CaDebug 0, "Matched: " & Matches & ", against: " & oRE.Pattern
                     if Matches then
                        CountOfProgramOccurances = CountOfProgramOccurances + 1
                     end if
                  VbsCaLogInc -1
              next
           VbsCaLogInc -1


           ;--- Set return code and log exit message ------------------------
           If CountOfProgramOccurances > 0 Then
              CaDebug 0, """" & UserFriendlyName & """ is running (" & CountOfProgramOccurances & " instances)"
           else
              CaDebug 0, """" & UserFriendlyName & """ is NOT running"
           end if
           VbsCaLogInc -1
       end function
#)
#(  '<?NewLine>'
    ;--- Define a reusable macro --------------------------------------------
    #define ShowFilesInUseForExecutingPrograms               ;;Version 10.051

    ;--- 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 oRE      : set oRE = Nothing
       dim InUseCnt : InUseCnt = 0
       dim ProgramNames(), FriendlyNames()

       ;--- Entry Point -----------------------------------------------------
       <$VbsCaEntry "Install">
           ;--- Create Regular Expression Object ---
           CaDebug 1, "Creating Regular Expression Object..."
           set oRE        = new RegExp
           oRE.IgnoreCase = true

           ;--- Capture state and display if required -----------------------
           CaDebug 1, "Check for running programs and display ""FilesInUse"" dialog if required..."
           CaptureInUseStateOfSpecifiedPrograms()
           <$VbsCaEntryName> = HandleRunningPrograms()
       <$/VbsCaEntry>


       <?NewLine><?NewLine>
       '============================================
       sub CaptureInUseStateOfSpecifiedPrograms()
       '============================================
           CaDebug 1, "Looking for executing programs and need to be stopped..."
           VbsCaLogInc 1
               InUseCnt = 0
               #{ FOR @@X = 1 to <?Data:{$#1}.?>        ;;Perform the checks specified by the #data structure
                  IfProgramExecutingThenAddToList "<?Data:{$#1}.@@X.1>", "<?Data:{$#1}.@@X.2>", "<?Data:{$#1}.@@X.3>"
               #}
           VbsCaLogInc -1
       end sub

       <?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
                CaDebug 0, "HandleRunningPrograms() : No running programs that need to be stopped."
                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(RegExpTextProgramName, RegExpTextCommandLine, ByVal 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() : " & UserFriendlyName
           VbsCaLogInc 1

           ;--- Is it running? ----------------------------------------------
           dim  TotalInstances : TotalInstances = CountOfProgramOccurances(RegExpTextProgramName, RegExpTextCommandLine, UserFriendlyName)
           if   TotalInstances <> 0 then
                if TotalInstances > 1 then UserFriendlyName = UserFriendlyName & " (" & TotalInstances & " instances)"
                redim preserve ProgramNames(InUseCnt)
                redim preserve FriendlyNames(InUseCnt)
                ProgramNames(InUseCnt)  = RegExpTextProgramName         'TODO: Forgotten what this is for...
                FriendlyNames(InUseCnt) = UserFriendlyName
                InUseCnt                = InUseCnt + 1
           end if

           ;--- Log Exit ----------------------------------------------------
           CaDebug 0, "IfProgramExecutingThenAddToList() finished..."
           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

           ;--- Display "FilesInUse" dialog if program running --------------
           CaDebug 0, "Displaying FilesInUse dialog for " & InUseCnt & " programs."
           dim DialogResponse
           do
              ;--- 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 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

              ;--- Well must be retrying then :-) -------------------------------
              CaDebug 0, "User wants to RETRY, so should have stopped programs, we'll see..."
              VbsCaLogInc 1
                 CaptureInUseStateOfSpecifiedPrograms()
                 if   InUseCnt = 0 then
                      CaDebug 0, "After retry there are no running programs that need to be stopped :-)"
                      VbsCaLogInc -1
                      exit do
                 end if
              VbsCaLogInc -1
           loop

           ;--- Log Exit --------------------------------------------------------
           CaDebug 0, "Finished DisplayFilesInUseDialog() : RC = " & DisplayFilesInUseDialog
           VbsCaLogInc -1
       end function

       ;--- Function defined above ------------------------------------------
       <?NewLine><?NewLine>
       <$VBSFunction.CountOfProgramOccurances>
    <$/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>~}^>
#)


Microsoft awarded me an MVP (Most Valuable Professional award) in 2004, 2005, 2006, 2007, 2008 & 2009 for the Windows SDK (Windows Installer) area.Please email me any feedback, additional information or corrections.
See this page online (look for updates)

[Top][Contents][Prev]: Error Dialog - Improve[Next]: Licence Dialog Enabling


MAKEMSI© is (C)opyright Dennis Bareis 2003-2008 (All rights reserved).
Saturday May 28 2022 at 3:11pm
Visit MAKEMSI's Home Page
Microsoft awarded me an MVP (Most Valuable Professional award) in 2004, 2005, 2006, 2007, 2008 & 2009 for the Windows SDK (Windows Installer) area.