HOWTO: Check return values, boot status and (de)install status of MSI installations (Was: Problem with VBScript SUB for uninstalling .MSI packages) 
Author Message
 HOWTO: Check return values, boot status and (de)install status of MSI installations (Was: Problem with VBScript SUB for uninstalling .MSI packages)

Hi all!

I have not yet found/seen any good, comprehensibly description/solution
of this subject, so I will try to describe it the best I can based on my
experience and research. A lot of people asks for bits and pieces around
this subject, I will try to give you a overview the best I can and also
give you some code examples.  The documentation from Microsoft how to do
this is scarce and spread around (and too often undocumented :-(.

Comments, clarifications  and corrections are welcome! (My native tongue
is Norwegian, so please bear over me with some of the bad English here
and there)

KEYWORDS: MSI installations, Microsoft Installer, MSIEXEC.EXE, command
line, MIF,  ISMIF32.DLL, check get return value, boot status, install
status, deinstall status, suppress reboot, restart, WSH, VBScript,
Installer object, silent install, modal dialogue box

OS TYPE: Windows 95, 98, ME, NT4 and 2000.

 <<<<<<<<<<<<<<<<<  >>>>>>>>>>>>>>>>>>>>>

There are two main "methods" of starting a MSI installation as I know
of:

A. Using the Installer object from a script/programming environment

OR

B. Run MSIEXEC.EXE from the command line (directly, or indirectly
through a setup.exe wrapper)

I will mainly focus on the command line options method, as I find it the
most easy, versatile, flexible and readable way of installing and
deinstalling a MSI package. Sometimes you also want to or have to use a
setup.exe wrapper, my description will apply to it as long you can pass
MSIEXEC command line parameters to it. Some of the things I mention
under the command line options method can be transferred to the
Installer object method (eg. input of property values to the
installation and some of the return values maybe. If you also can sent a
MIF file parameter to the installer object method, you can use that part
to)

 <<<<<<<<<<<<<<<<<  >>>>>>>>>>>>>>>>>>>>>

A. Using the Installer object from a script/programming environment

 Pros: Direct access to "everything" if that is a prerequisite

 Cons: You can drown in details
  No UNinstall method! (or can ConfigureProduct be used?)

 Object:  WindowsInstaller.Installer
 Method: InstallProduct
 Method: ConfigureProduct ?
 Method: LastErrorRecord ?

Q: Can LastErrorRecord be used to get error status/messages after a
failed installation or especially  if the user cancelled? When looking
at the designated functions that generate an error record (listed in the
MSI SDK) I didn't get overly optimistic.

When I played a little with this way of installing a MSI file, I just
got the error "Msi API Error 80004005" when pressing cancel.
If LastErrorRecord is usable for getting status after a failed
installation or especially  if the user cancelled, then this definitely
belongs under Pros!

  <<<<<<<<<<<<<<<<<  >>>>>>>>>>>>>>>>>>>>>

B. Run MSIEXEC.EXE from the command line (directly, or indirectly
through a setup.exe wrapper)

One prerequisite for the Pros-list to be correct: ISMIF32.dll must be
present in the path (more details further down)

 Pros: Full control over both MSIEXEC.EXE return codes and also MSI
"internal" errors
  Can detect that the user choose Cancel if you have a UI.
  Consistent way of handling reboot/install status
  Command line parameters are documented and easy to understand
  Supports a setup wrapper if it can transfer command line parameters in
to MSIEXEC.EXE
  Supports UNinstall in the same way as install/reinstall

 Cons: No access to the "inner life" of MSI

Example on a setup wrapper input parameter syntax (InstallShield):
 Setup.exe /s /v" /qb- /lv c:\install.log" ' parameters after /v is
passed to MSIEXEC.EXE

See also
http://www.*-*-*.com/
for command line options to MSIEXEC.EXE.

  <<<<<<<<<<<<<<<<<  >>>>>>>>>>>>>>>>>>>>>

The rest of the document will refer to the command line method, but a
lot of it should be transferable to the Installer object method.

I will go through the following:

1. Reboot status and handling
2. Checking installation/uninstallation  status.
3. List of errors  returned by MSIEXEC.EXE and in a mif file
4. Code examples

  <<<<<<<<<<<<<<<<<  >>>>>>>>>>>>>>>>>>>>>

1. Reboot status and handling

Preventing reboot:
When running silent install (/q /qn /qb /qb-), MSI will default reboot
the PC without warning if it "wants" to. A "strong" reboot, bye bye to
any unsaved data if I remember correctly. This can be prevented (in most
cases anyway, there are exceptions eg. NAV 7.5 CE) by setting the REBOOT
property.

The REBOOT property can have three different values:
Suppress
 Suppresses all reboots eg. caused "file in use". Will work in mostly
all situations

ReallySuppress
 As Suppress but also suppresses a ForceReboot action (NB! When a MSI
installation uses the ForceReboot action the installation will always
continue after the reboot (using RunOnce in registry)

Force
 If you really want to reboot every time, go ahead ;-)

Ex.: MSIEXEC.EXE /i <msifile> /qb- ALLUSERS=2 REBOOT=ReallySuppress
NB! always enter property names in CAPITAL letters.

Detecting reboot status:

Check the return value of MSIEXEC.EXE (1641 and 3010)

NB! If you use a setup.exe wrapper that doesn't relay the return values
of MSIEXEC.EXE (eg. InstallShield, the previous version anyway), bad for
you!
Consider to drop the wrapper if possible and use MSIEXEC.EXE directly.

------------------------------------------------------------------------------

1641 ERROR_SUCCESS_REBOOT_INITIATED  ' The installer has started a
reboot (ForceReboot action).
What it is really saying: Going down "RIGHT" now. By by to all unsaved
data if my memory serves me right!. The MSI installation will continue
after the reboot (from RunOnce in registry). Suppress with
REBOOT=ReallySuppress (the boot that is, not the install that will
continue after the boot ;-)

TIP: If you have used REBOOT=Suppress and gets this return code, you
should quit your script (WSH: Wscript.Quit) because it can take some
time before the PC actually gets around to restart, this to avoid being
interrupted in the middle of something.

TIP: If it is important for you to do something after the MSI install is
finished in an 1641 situation (eg. after the reboot), put a script in
Run in registry before you start the MSI installation (and let the
script delete itself from Run as the first thing it does). Do NOT put it
in RunOnce, most likely your script will be run before the MSI
installation because the run sequence for values in RunOnce and Run is
the same as the creation sequence. Also everything in RunOnce is started
before anything is started in Run.

TIP: In RunOnce and Run: The alphabetically listed keys you see in
REGEDIT.EXE is not the actual run sequence. To see it, export the values
to a registry file. The order in the registry file reflects the run
order when the user logs on.

TIP: To be sure of the run sequence in RunOnce or Run, you can
(programmable) delete all necessary values, and then recreate them in
the desired run order.

Registry paths:
Run = HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Run
RunOnce = HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\RunOnce
------------------------------------------------------------------------------

3010 ERROR_SUCCESS_REBOOT_REQUIRED ' A reboot is required to complete
the install. This does not include installs where the
ForceReboot action is run (=1641)
------------------------------------------------------------------------------

You should at one point sooner or later give the user an option to
reboot, but it's all up to you ;-)

Example of a check on the return value of MSIEXEC.EXE

Set oShell = CreateObject("WScript.Shell")

strLaunchCmd = "MSIEXEC.EXE /i <msifile> /qb- ALLUSERS=2
REBOOT=Suppress"
intRetVal = oShell.Run(strLaunchCmd,1,True)

If intRetVal = "3010" Then
 Set bolBootNeeded = "True"
 MsgBox "TEST: Installation was successfull, reboot needed"
ElseIf intRetVal = "1641" Then
 Set bolBootNeeded = "True"
 MsgBox "TEST: Installation will continue after boot, reboot started"
 WScript.Quit
 ' If REBOOT=ReallySuppress, disable the two previous lines and enable
the following line
 ' MsgBox "TEST: Installation will continue after boot, reboot needed"
ElseIf intRetVal = "0" Then
 ' A better solution: Check the mif file instead to be completly sure!
 MsgBox "TEST: Installation was successfull (probably!)"
Else
 ' A better solution: Check the mif file ALSO to be completly sure!
 ' If the MIF file has status "Success", it is no error!
 ' If the MIF file is missing, there is definitive a error!
 MsgBox  "TEST: Installation failed (probably!) - Error #" & intRetVal
End if

  <<<<<<<<<<<<<<<<<  >>>>>>>>>>>>>>>>>>>>>

2. Checking installation/uninstallation status.

Return values from MSIEXEC.EXE isn't always to useful here. 0 should
equal "Success", right. Nope! If you run some UI, like a progress bar
only (/qb-) the user can press Cancel and the installation is aborted.
MSIEXEC.EXE still returns 0. I guess that there are a lot of other
situations that give the same behaviour.

But of course, you can (should?) test for the return code anyway.
Looking through the list of error codes (see link further down), it
looks like that you could trigger a error handling if return value is >
0, with an exception of 1641 and 3010 that indicates a reboot situation
and not an error!

But how can you test for the user cancellation and other more subtle
error situations? The answer is a MIF file, well known for most people
that have used Systems Management Server (SMS) I presume. I have never
seen/used SMS, but I think the MIF file originated from it.

But now you may say "But we do not use SMS, so this is not for me?"
Wrong!
One very overlooked feature of MSI is the capability to create a MIF
status file at the end of the installation (without SMS :-). It gives
you a guaranteed way of testing the install/deinstall status and also
often a  "detailed" message in case of error.

There are some rules that MUST be obeyed to get this to work!

A: ISMIF32.dll must be in the path

B: MIF filename must NOT be > 8 characters

C: MIF filename must NOT include a path

D: Do NOT enter a MIF filename extension

Is B -> D documented anywhere? I have not found it to be, it's the good
ool' trial and error method that applies ;-)

The MIF file will be placed in the %WinDir% folder (with a .mif filename
extension). I have seen some documentation that says that it can end up
in the %temp% folder too, but I think that applies maybe to a SMS
installation only and not to a MSI installation. But anyhow, it doesn't
hurt to check both places.

See the code section for a VBScript sub (TestMIFStatus) that interprets
a MIF file and returns install status and error messages if any. This is
not so easy to implement as you should believe, the MIF file has a kind
of XML syntax.

TIP: You can use the MIF file to test if the MSI installation is
finished. The file is not created before the absolute end of the
installation. This is VERY useful if you have a setup.exe wrapper that
spawns a new install process and immediate "commits suicide" and let the
child to the dirty work. Eg. the Installshield version have this
"problem" (but I have seen rumours that the new version has a "wait"
switch on the command line?).

The following is an example of the command-line syntax used to generate
a status MIF file:

Install:
msiexec.exe /I <Package> /m MIFfilename

Uninstall:
msiexec.exe /x <Package|ProductCode> /m MIFfilename

When the /m command-line option is used to install software to a
computer with ISMIF32.dll present in the path (preferable in Windows
system folder?), Windows Installer generates an install status MIF file
that includes the following summary properties that correspond to the
MIF fields:

<quote>
Command line documentation on MSIEXEC.EXE for /m filename

Generates a Systems Management Server (SMS) status .mif file. Must be
used with the install (-i), remove (-x), administrative installation
(-a), or reinstall (-f) option. The Ismif32.dll is installed as part of
SMS and must be on the path.
The fields of the status .mif file are filled with the following
information:

----------------------------------------------------------------------------------------------------

Windows Installer MIF File Data
----------------------------------------------------------------------------------------------------

 MIF Field --> Windows Installer Summary Property
----------------------------------------------------------------------------------------------------

Manufacturer --> Author (manufacturer)
Product  --> Revision number (package code)
Version  --> Subject (product name)
Locale  --> Template
Serial Number --> Not set
Installation --> Set by Ismif32.dll to "DateTime"
InstallStatus --> "Success" or "Failed"
Description --> Blank if successful. Error message if failed. *)

*) Description - Error messages in the following order: 1) Error
messages generated by installer; 2) Resource from Msi.dll if
installation could not commence or user exits; 3) System error message
file; 4) Formatted message: "Installer error %i", where %i is the error
returned from Msi.dll
----------------------------------------------------------------------------------------------------

</quote>

Where can the ISMIF32.dll be found?

From MS DLL Help Database at
http://www.*-*-*.com/ :

PRODUCT  SIZE MOD DATE CAB/IEXPRESSRELATIVE PATH
SMS 1.2  9,488 7/27/1996 \smssetup\mips\intnl
SMS 1.2  6,976 7/23/1996 \sp3\smssetup\x86\intnl
SMS 1.2  6,976 7/23/1996 \sp4\smssetup\x86\intnl
SMS 1.2  6,976 7/27/1996 \smssetup\x86\intnl
Visual Basic 6.0  6,976 7/26/1996 \disk 2\boffice
Visual J++ 6.0  6,976 7/26/1996 \boffice
Visual Studio 6.0 6,976 7/26/1996 \disk2\boffice

  <<<<<<<<<<<<<<<<<  >>>>>>>>>>>>>>>>>>>>>

3. List of errors  returned by MSIEXEC.EXE and in a mif file

This one list error codes < 2000  and also 3010 =
ERROR_SUCCESS_REBOOT_REQUIRED (Success is an error ;-)
http://www.*-*-*.com/

This one also list error codes > 2000
http://www.*-*-*.com/

"The error codes above 2000 are internal errors and do not have authored
strings, but these can occur if the installation package has been
incorrectly authored."  (minus error 3010 if I am not mistaken ;-)

Interesting return codes/message in MIF file when user cancels:

User cancel:
1602           Are you sure you want to cancel?
(2322           User cancelled)
(2367           User Abort. )

  <<<<<<<<<<<<<<<<<  >>>>>>>>>>>>>>>>>>>>>

4. Code examples

************************************************************************************

' Example on how to use the function MIFSSuccess

Set oFSO = CreateObject("Scripting.FileSystemObject")

strMyMifFile = <some MIF file>"

If oFSO.FileExists(strMyMifFile) Then
 bolMIFSSuccess = MIFSSuccess (strMyMifFile, strMIFErrMsg,
strMIF_MSI_Msg)

 If Not TypeName (bolMIFSSuccess) = "Boolean" Then
  ' There is something fishy here (eg. missing MIF file or invalid data
in MIF file)
  MsgBox bolMIFSSuccess
 ElseIf bolMIFSSuccess Then
  ' (un)Installation succeded
  MsgBox "(un)Installation succeded!" & vbCR & strMIF_MSI_Msg
 Else
  ' (un)Installation failed
  MsgBox strMIFErrMsg & vbCR & strMIF_MSI_Msg
 End if
End if

Function MIFSSuccess (strMIFfile, strMIFErrMsg, strMIF_MSI_Msg)

' Author: Unni Boge and Torgeir Bakken
' Date: July 10, 2001

' Checks (un)install status from a MIF file

' Function returns:
' True ==> (boolean) Successful (un)installation
' False ==> (boolean) Failed (un)installation
' "Missing MIF input file: " & strMIFfile ==> (string) Selfdocumenting
;-)
' "Invalid data in MIF file " & strMIFfile ==> (string) Selfdocumenting
;-)

' Input parameters:
'   BstrMIFfile ==> (string) Full Path/Filename to input file to read
the status from

' Output parameters:
' Set at both success and failure:
'   strMIF_MSI_Msg ==> (string) <msg from MSI>   ' mostly "" when
success

' Set only at failure:
'   strMIFErrMsg ==> (string) "Installation of <strMSI_ProdVersion>
failed"

 Dim bolInstFound, bolFailedFound, bolDescrFound, bolVersionFound
 Dim strMSI_ProdVersion, fMifFile, strMifLine, iPos, iMyLen

 bolInstFound = False
 bolFailedFound = False
 bolDescrFound = False
 bolVersionFound = False

 If oFSO.FileExists(strMIFfile) Then
  ' Init value
  MIFSSuccess = "Invalid data in MIF file " & strMIFfile
  Set fMifFile = oFSO.OpenTextFile(strMIFfile)
  Do Until fMifFile.atEndOfStream
      strMifLine = fMifFile.ReadLine
      If InStr(1,strMifLine,"VALUE = ",vbTextCompare) > 0 Then
       If bolInstFound Then
        bolInstFound = False
        If InStr(1,strMifLine,"Success",vbTextCompare) > 0 Then
         MIFSSuccess = True
        End if
        If InStr(1,strMifLine,"Failed",vbTextCompare)  > 0  Then
         MIFSSuccess = False
      bolFailedFound = True
        End if
       End if
       If bolVersionFound Then
        bolVersionFound = False
        iPos = InStr(1,strMifLine,"=",vbTextCompare)
        iMyLen = Len(strMifLine)
        iMyLen = iMyLen - iPos
        strMSI_ProdVersion = trim (right(strMifLine, iMyLen))
        ' getting rid of the "" that is read from the file
        strMSI_ProdVersion = Replace(strMSI_ProdVersion, """", "", 1,
-1, vbTextCompare)
       End if
       If bolDescrFound Then
        bolDescrFound = False
     iPos = InStr(1,strMifLine,"=",vbTextCompare)
        iMyLen = Len(strMifLine)
        iMyLen = iMyLen - iPos
        strMIF_MSI_Msg = trim (right(strMifLine, iMyLen))
        ' getting rid of the "" that is read from the file
        strMIF_MSI_Msg = Replace(strMIF_MSI_Msg, """", "", 1, -1,
vbTextCompare)
     If bolFailedFound Then
         strMIFErrMsg = "Installation of " & strMSI_ProdVersion & "
failed"
        End if
       End if
      End if
   If InStr(1,strMifLine,"Version",vbTextCompare)> 0 Then
    If InStr(1,strMifLine,"NAME = ",vbTextCompare)> 0 Then
     bolVersionFound = True
       End if
   End if
   If InStr(1,strMifLine,"InstallStatus",vbTextCompare)> 0 Then
    If InStr(1,strMifLine,"NAME = ",vbTextCompare)> 0 Then
     bolInstFound = True
       End if
   End if
   If InStr(1,strMifLine,"Description",vbTextCompare)> 0 Then
    If InStr(1,strMifLine,"NAME = ",vbTextCompare)> 0 Then
     bolDescrFound = True
       End if
   End if
  Loop
 Else
  MIFSSuccess = "Missing MIF input file: " & strMIFfile
 End if
End Function

************************************************************************************

Custum action that can be used in a MSI file:

Function ChkBoot
 Set oShell = CreateObject("WScript.Shell")
 If (Session.Mode(6)) Then
  oShell.RegWrite "HKLM\SOFTWARE\<somewhere>\Bootneeded","True"
 Else
  oShell.RegWrite "HKLM\SOFTWARE\<somewhere>\Bootneeded","False"
 End If
End Function

************************************************************************************

An example on how to create a MSIEXEC command that uninstalls
applications silently and uninstall applications on a per-user basis,
see MSIREMOV.vbs at
http://www.*-*-*.com/
(Deploying Windows Installer Setup Packages with Systems Management
Server 2.0). Here is an extract of the description:

<quote>
The MSIREMOV.vbs script extracts the package code from a specified
Windows Installer package and automatically creates a registry file
(.reg) that includes the necessary registry key entries to both
uninstall applications silently and uninstall applications on a per-user
basis.
</quote>

************************************************************************************

Puhhh, finished, and I hope someone will find this information useful
:-)

Regards,
Torgeir



Mon, 19 Jan 2004 10:06:21 GMT  
 
 [ 1 post ] 

 Relevant Pages 

1. HOWTO: Check return values, boot status and (de)install status of MSI installations (Was: Problem with VBScript SUB for uninstalling .MSI packages)

2. Client side status bar implementation via JavaScript - HOWTO?

3. Return status from a SQL Server stored procedure

4. upload file and return status

5. Return status from a SQL Server stored procedure

6. Checking window status

7. Security check on user status - in ASP/vbscript

8. Can I Check Status Of Browser Online ??

9. Using VBscipt to check Network status....

10. Check status of Performance Logs and Alerts

11. Check the Status of a disk (Dynamic or Basic)

12. How to check network connection status.

 

 
Powered by phpBB® Forum Software