Overwrite MSI DisplayVersion with Custom String - wix

I have a .msi installer (via wix) for an application I'm working on but the application's version number doesn't fit the X.Y.Z version numbers required my MSI's registry Version so the version number is "mangled" into something that does fit and still increases with every release.
I'm okay with this.
msiexec, as part of it's final cleanup, converts this X.Y.Z integer-encoded version number into a string and dumps it into the DisplayVersion registry entry. What I'd like to do is overwrite that string with my own that contains the actual version number of my application.
This certainly seems possible. For example...
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Installer\UserData\S-1-5-18\Products\19BF4688EE4961F41A44D0282A2340D9\InstallProperties
DisplayName = (REG_SZ) "Configuration Manager Client"
LocalPackage = (REG_SZ) "C:\Windows\Installer\41202.msi"
DisplayVersion = (REG_SZ) "5.00.7958.1000"
Version = (REG_DWORD) 0x05001f16
The Version is the encoded value of "5.00.7958", so where did the rest of the DisplayVersion string come from?
How, using only wix/msi supported options, do I overwrite DisplayVersion in the registry with my own custom string?

Might be a larger change than what you're looking to make, but...
if you set ARPSYSTEMCOMPONENT=1 in your MSI it won't register an ARP entry for your product. Then you can create your own ARP entry for your product by populating the HKLM\Software\Microsoft\Windows\CurrentVersion\Uninstall\[ProductCode] keys in the Registry table of your MSI.

I eventually accomplished this by having the MSI launch a custom installer binary near the end of the installation procedure. That program forks a background copy of itself and exits so that the install can finish.
The background task sleeps for a while to let the installation complete and then directly alters the registry to set DisplayVersion to the desired string.
There's a race condition here but it hasn't been a problem and updating the string isn't essential.

Related

WiX - Dynamically changing name of installed product in Add/Remove Programs

I am working on an installer with WiX which takes a "name" input from the user using a textbox in a dialog. This name will be used to name the product that i am installing.
But, i am not able to set the product name dynamically during install. Even if i use a custom action, the registry entry is getting created with the static name that i have provided earlier. This is ultimately leading to inconsistency.
Can anyone please help me regarding this?
This cannot be done in a custom action. It's true that you can set the ProductName property in a custom action (such as a type 51) early in the install and that will indeed change the name in the UI, but it will NOT change the name of the installed product - it will remain the same as the original value. For example, enumerating installed products will return the original name.
So the only good way to do this is to modify the ProductName in the MSI file before you launch it. You would have a launching program that modifies the MSI file and then installs it. The MSI file is a database that can be modified in the Property table to change the value of ProductName. This example will give you the general idea:
How do I add/update a property inside an MSI from the command-line?
but basically you open the open the database (MsiOpenDatabase or equivalent) then MsiOpenView with a SQL such as:
"UPDATE Property SET Property.Value = 'Your variable' WHERE Property.Property = 'CurrentProductName' "
then MsiViewExecute and close handles etc. Details depend on your preferred coding language environment.
This is not an ideal solution because if your MSI file is digitally signed you have tampered with it and it is no longer correct.
Another way is to generate a transform file, based on altering a copy of the MSI file. If you make a copy of the MSI file, and then do the alteration of ProductName as above you can then do an MsiDatabaseGenerateTransform() call which will generate a transform file, a .mst file, the difference between the two MSI files. You then install the original unaltered MSI file with a command line that includes TRANSFORMS=[the .mst file] which will update the ProductName and start the install.
None of this is very easy because Windows Installer products are not designed to have dynamic product names. Maybe historically and before Windows Installer setups this was more practical, but not in MSI setups.

Making a registry entry post installation

While I was searching for the answers I found that the registry entries are made during installation only. In my installer once the installation is done then there comes a dialogue box which contains one checkbox. Based upon the checkbox value I want to make the registry entry.
Please suggest.
All system modifications (such as file installation or registry modifications) should occur DURING installation. There is a REASON for that - to make installation transactional (all or nothing), and to allow clean uninstall and repair.
Suggestion: if you want to put some user setting (a-la 'user agreed to receive marketing emails'), better do it yourself (as a custom action for example). Or better yet, in your own program (do not put it in the MSI installer)
A custom action in principle can be run in any execution sequence (including UI sequence), so you will be able to run it any time (i.e. even after that dialog)
Another option is to collect data (checkbox value) BEFORE install. Then you can include it as part of normal install sequence (as registry element)

How can the contents of an installed file marked Permanent="No" be preserved during an upgrade?

Installers of previous versions of our software include a Component File that was NOT marked with Permanent="Yes". Now, we wish to read the pre-upgrade contents of this file during the upgrade process, which will overwrite the file with different contents. Is there a good way to do this?
It would help if you said exactly what you were doing that would cause the file to be overwritten. Some major upgrades (is that what you're doing?) will do a complete uninstall of the product first, followed by a complete install of the newer product. If that's the situation then use a custom action sequenced before RemoveExistingProducts to back up the file somewhere so that your application can retrieve the content, or get the content you need before it's ovewritten.
If you are doing a major upgrade sequenced later (such as afterInstallExecute) or you are doing a patch then it is by no means certain that the file will be overwritten because file overwrite rules will not replace a file that has been updated since it was installed. If the application altered the file then this type of upgrade will not overwrite it:
https://msdn.microsoft.com/en-us/library/windows/desktop/aa370531(v=vs.85).aspx
Or in the case that the file is unchanged since it was installed, change the dates so it appears to be modified, as described here:
https://blogs.msdn.microsoft.com/astebner/2013/05/23/updating-the-last-modified-time-to-prevent-windows-installer-from-updating-an-unversioned-file/
It's also not clear that Permanent=yes is what you want anyway - that would glue the file to the system forever. You may be thinking of NeverOverwrite, but it's typically not required if the app changes the files, and easier to decide at upgrade time (by changing dates) instead of committing to NeverOverwrite when it's sometimes unclear what the product may need in the future.
A comment refers to retrieving the previous version of the product during the upgrade. There are a number of ways to do this:
If you know the ProductCode of the previous version, MsiGetProductInfo (and equivalents in script etc) will return product version values or strings:
https://msdn.microsoft.com/en-us/library/aa370130(v=vs.85).aspx
Or if you'd rather not hardcode the value, MsiEnumProducts passing the UpgradeCode will return a list of installed ProductCodes. This technique is most useful if you have your own bootstrapper or UI where you want to show the user the current installed version.
In a WiX major upgrade the associated property (WIX_UPGRADE_DETECTED) is a list of the ProductCodes detected (usually a list of one) so you can use that to get the version of the product being upgraded. In a small vbscript example, something like:
set installer = CreateObject("WindowsInstaller.Installer")
and:
prodversionstring = installer.productinfo(WIX_UPGRADE_DETECTED, "VersionString")
will get you close.
Assuming this file is a configuration file such as an XML file, I find this is just a tough area of Windows Installer. You ship file version 1, the end user modifies certain attributes and then you ship file version 2 to which you want to preserve those customizations.
The problem is this is a very complex merge. It works somewhat OK if you only care about 1-2 attributes but if the answer is I need to preserve all of it then you are stuck between losing all the customizations or not getting the changes from version 2 of the file.
You could write extensive custom actions to do all this during the installer but I propose there is a better way: Have 2 files.
1 file that is owned by the installer and can always be safely overwritten and 1 file that is owned by the application that overrides are stored in. Think of it like a transformation file. The installer doesn't know about this file so it never overwrites or deletes it. (The very definition of user data from an MSI perspective.)
For example the .NET framework Web.Config schema AppSettings element has a file attribute that was designed to support this nicely.
Specifies a relative path to an external file containing custom
application configuration settings. The specified file contains the
same kind of settings that are specified in the , , and
elements and uses the same key/value pair format as those
elements. The path specified is relative to the main configuration
file. For a Windows Forms application, this would be the binary folder
(such as /bin/debug), not the location of the application
configuration file. For Web Forms applications, the path is relative
to the application root, where the web.config file is located.
Note that the runtime ignores the attribute if the specified file can
not be found.

Short filenames in LocalServer32 entries

I have a Component in an installer Project (created with an old Installshield Version).
The Class table entry in the MSI is created correct. But when the installer runs the entry in the registry (LocalServer32) is created with a short 8.3 Name.
What can I do so that the entries in the registry are made with the full 32bit long filename?
The problem behind it:
My component tries to locate localized DLLs with the filename. But when the component is launched with the 8.3 filename the fielname returned by GetModuleFilenameis also in 8.3 format. So when it just appends DEU to the name and changes the extension to DLL to locate the localized DLLs this sometimes fails. And I can not modify this component. (I.e. CompenentName.exe tries to find CompenentNameDEU.dll)
When I register the component manually (ComponentName.exe -register) the entries are made with the full long filename and everything works perfect.
A way around your problem is to use the GetLongPathName() API to convert the path to the long version of the filename.
This should work regardless of whether the argument is a shortened 8.3 path or whether it's already a long path.
Look at the rest of the registry entries that are created. I suspect you'll find something like a LocalServer32 data item (not a key) with apparent garbage in it. If so, what happens is that the 8.3 name is not used to locate the COM server. That "garbage" contains an encoding of ProductCode and Component guid that are used with MSI APIs to locate the target file, invoking repair if necessary.
So if this is what you see, the short answer is "don't use the Class table" because it creates an MSI links to find the target, not a file path.

Upgrade built by InstallShield 2012 reports installed version in bad format

I am seeing the following messages during an upgrade using an installer built with InstallShield 2012 Spring:
The InstallShield Wizard will update the installed version (9.01.005) of <our product> to version 9.2.0.53.
The InstallShield Wizard is updating (9.01.005) of <our product> to version 9.2.0.53.
These are messages are based on string resources IDS_IFX_SDWELCOMMESSAGE_UPDATE_WELCOME and IDS_IFX_STATUSEX_STATICTEXT_UPDATEUI. I believe the un-altered string resources would look like this:
The InstallShield Wizard will update the installed version (%VI) of %P to version %VS. To continue, click Next.
The InstallShield Wizard is updating (%VI) of %P to version %VS.
although some resources indicate that the second string resource is "The InstallShield Wizard is updated %VI of %P to version %VS."
The problem is that the old (installed) version number is supposed to be a display string like "9.1.5.2" instead of a formatted version of the internal version number like "9.01.005". As I understand it SdSubstituteProductInfo uses IFX_INSTALLED_DISPLAY_VERSION to populate the %VI substitution in OnUpdateUIBefore's default InstallScript code. And IFX_INSTALLED_DISPLAY_VERSION defaults from IFX_INSTALLED_VERSION, which explains why the preferred display string is not appearing where we would want it to. And I assume SdWelcome (or any dialog or code similarly referencing IFX_INSTALLED_DISPLAY_VERSION), also displays the badly formatted version.
Despite all my searching on the web about the functions, strings, and string IDs mentioned above, I have not found the ideal solution to displaying the properly formatted old version number (DisplayVersion) in the correct format. Some articles suggest hard coding a format string that skips the old version number instead of using a string resource that includes %VI. Others suggest manually formatting the version number as desired and putting the result into IFX_INSTALLED_DISPLAY_VERSION. I know there's a better answer, and I want it to be easier to find next time I or anyone else looks for it.
On the OnBegin InstallScript function, add the following line of InstallScript code as the first line after begin:
RegDBGetItem(REGDB_UNINSTALL_DISPLAY_VERSION, IFX_INSTALLED_DISPLAY_VERSION);
Performing this in OnBegin also ensures that another message not mentioned in the question uses the correct format:
The setup has detected that version %VI of %P is already installed.
This setup installs an earlier version of %P (%VS).
You will have to uninstall the previous version before installing this version.