Blocking older MSI setups

From NSIS Wiki

Author: MathiasSimmack (talk, contrib)


Description

You may have read these two articles about removing MSI installations:

Uninstalling a previous MSI
Removing MSI packages with MSI related functions

Okay the MSI version is gone. But the user could reinstall it. You should make sure that this is not possible if your NSIS version is newer.

Method 1 (simple but nasty)

The Microsoft Installer remembers all installed products in the registry (HKLM\Software\Classes\Installer\Products). You need the original ProductCode of your MSI setup. You cannot find it in the registry? It's because the code is saved in a "twisted" format. A ProductCode is a GUID and looks like this

{12345678-1234-1234-1234-123456798901}

What's to do? The first three field must be written with the last character first (1234 -> 4321) and the last two fields must be exchanged byte-wise (1234 = 12 34 -> 21 43 = 2143). Then remove the parenthesises and hyphens and you will get something like this

87654321432143212143214365979810

You should find this code if the MSI product is installed. Now remove the old version and create a registry key named after this "twisted" ProductCode (s. above). If you then try to reinstall the older MSI version you should see an error message that this is not possible.

And yes: if you want to block different MSI releases you need all ProductCodes and you have to create a key for each version.

Method 2 (better)

If you were clever you included an upgrade check in your MSI file. What does it mean? Before installing, the MSI setup is looking for other related products. And it should cancel if it finds the same or a newer version already installed.

Did you do something like this? Perfect. Then you first need a new fresh ProductCode. This is absolutely different from method 1 (s. above). You MUST NOT use a code, another MSI version used before. (Or you're back at method 1.) So please create a new GUID and remove the parenthesises and hyphens.

Then you need the original UpgradeCode that you used in your older MSI setups. It's another GUID and it should be the same in your product family (1.0 -> 1.x -> 2.0 -> ...). You also have to "distort" it that it looks like the ProductCode (s. method 1 on how to do it).

Now call the macro "WRITE_FAKE_MSICOMPONENTS" and set the ProductCode, the UpgradeCode and the 4 digits of your current version number (Major.Minor.Release.Build) as parameters. For example:

!insertmacro WRITE_FAKE_MSICOMPONENTS \
  "3DD65B80F4E711D7A48800E07DA59103" \
  "18AF4D6DB8206D1139D10000C1108ABC" \
  1 2 3 4

Your current version number must be higher than all older releases or the MSI upgrade check won't work. Here is the macro code:

!macro BUILD_MSICOMPATIBLE_VERNUM MAJOR MINOR RELEASE BUILD RESULT
  StrCpy ${RESULT} "0"
 
  IntOp $R0 ${MAJOR} * 0x1000000
  IntOp $R1 ${MINOR} * 0x10000
  IntOp $R2 ${RELEASE} * 0x100
 
  IntOp ${RESULT} ${RESULT} + $R0
  IntOp ${RESULT} ${RESULT} + $R1
  IntOp ${RESULT} ${RESULT} + $R2
  IntOp ${RESULT} ${RESULT} + ${BUILD}
!macroend
 
!macro WRITE_FAKE_MSICOMPONENTS TURNPCODE TURNUCODE MAJ MIN REL BUILD
  !insertmacro BUILD_MSICOMPATIBLE_VERNUM ${MAJ} ${MIN} ${REL} ${BUILD} $0
  WriteRegDword HKEY_LOCAL_MACHINE "Software\Classes\Installer\Products\${TURNPCODE}" "Version" $0
  WriteRegDword HKEY_LOCAL_MACHINE "Software\Classes\Installer\Products\${TURNPCODE}" "Assignment" 0x1
  WriteRegStr HKEY_LOCAL_MACHINE "Software\Classes\Installer\UpgradeCodes\${TURNUCODE}" "${TURNPCODE}" ""
!macroend

As you can see it just creates a MSI compatible version number and saves it in the registry, where all installed MSI products are saved. The UpgradeCode is required to find the installed product: it just has one single string value named after the ProductCode.

If you find it does not work, make sure your codes are valid. One way to do this is to use Process Monitor with a Process Name filter on msiexec.exe. I found it was reading the correct keys but still not detecting a new version as installed. However changing the Assignment value to 0x2 fixed it.

That's it. If you now try to install the older MSI version, it will detect the UpgradeCode. Then it jumps to the written ProductCode and compares the version number with its own version. If the saved number is equal or higher, you should see a message that a newer version is already installed. What the setup really says to you depends on what you wrote into. ;-)

Please use the next macro called "REMOVE_FAKE_MSICOMPONENTS" to remove these MSI entries. Set the ProductCode and UpgradeCode as parameters

!insertmacro REMOVE_FAKE_MSICOMPONENTS \
  "3DD65B80F4E711D7A48800E07DA59103" \
  "18AF4D6DB8206D1139D10000C1108ABC"

And here is the code:

!macro REMOVE_FAKE_MSICOMPONENTS TURNPCODE TURNUCODE
  DeleteRegKey HKEY_LOCAL_MACHINE "Software\Classes\Installer\Products\${TURNPCODE}"
  DeleteRegKey HKEY_LOCAL_MACHINE "Software\Classes\Installer\UpgradeCodes\${TURNUCODE}"
!macroend

Mathias.

(btw: I'm sorry but my English is not as good as it could be. I hope this article is understandable.)

Personal tools
donate