Installed Checks using the version comparison types seem to be ambiguous.

Mar 5, 2010 at 11:37 AM
Edited Mar 5, 2010 at 11:42 AM

Not sure if this is a bug or intended behaviour.

I'm trying to schedule running a patch (MSP) if the user has a previous version of one of my installers on their system. I've tried using the product code installed check by supplying the upgrade code & checking the VersionString. This works fine if the user has any version of the installer in question on their system, if they have an old one it runs the patch as expected, if they have the current version it's detected as being installed. The problem is when they have nothing installed, it's still trying to schedule the patch which fails because there's nothing to patch (in this situation I have it schedule installation of the latest version before the patch is scheduled  which again makes the patch fail because it's already at the latest version when it tries to run the patch).

The code I've used is very simple

<installedcheck id="my-upgrade-guid" id_type="upgradecode" propertyname="VersionString" propertyvalue="1.3.0" comparison="version" type="check_product" description="Installed Check" />

It appears that the version check is returning false if the value is less than the value specified but also if the value is null (as in the upgrade code doesn't exist on the machine at all).

This seems to be replicated for Registry Checks too. I've also tried the following:

<installedcheck path="SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\{productcodev1.1}" fieldname="DisplayVersion" fieldvalue="1.3.0" fieldtype="REG_SZ" comparison="version" rootkey="HKEY_LOCAL_MACHINE" wowoption="WOW64_32" type="check_registry_value" description="Installed Check" />

<installedcheck path="SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\{productcodev1.2}" fieldname="DisplayVersion" fieldvalue="1.3.0" fieldtype="REG_SZ" comparison="version" rootkey="HKEY_LOCAL_MACHINE" wowoption="WOW64_32" type="check_registry_value" description="Installed Check" />

 both together as above, individually and with an installedcheckoperator between them with its type set to both "And" & "Or". Everything exhibits the same behaviour in all my test cases, when the installer in question doesn't exist on the machine both the full installer & the patch are scheduled.

If you've any help on how to get this working it'd be most appreciated & if you want me to file a bug for this the let me know. Obviously in the above examples I've removed my specific guids but in the actual code they are present & correct.

Coordinator
Mar 5, 2010 at 12:17 PM

What is the version of the patch and of the product? If I understand, the patch is exactly the same version as your currently installed product?

Mar 5, 2010 at 1:39 PM

The problem scenario is when no product is installed so the version of the product should be irrelevant in that case.

The new version is 1.3.0 and there are 3 versions of the product in the wild, 1.0.0, 1.1.0 & 1.2.0. My MSP & MSIs work fine in all cases where they're manually run.

The issue is that if a user has nothing installed on their system, my bootstrapper will first try to install v1.3.0 of the product using the MSI and then run the v1.3 MSP which will error. I don't want this to happen, I want it to run the MSI only & not schedule the MSP & can't figure out some logic to make the install checks operate in this manner.

I need to schedule the MSP as users could have v1.0, v1.1 or v1.2 of this product. This product is essentially a large set of support files which the main product needs as a pre-requisite but since they don't change very often (if at all) they're separated from the main product so users can update the main product regularly using smaller downloads & faster installations. To check whether the support product is installed or not I use the following code:

 <installedcheck path="SOFTWARE\My Company\My Product" fieldname="InstallLocation" fieldvalue="1" fieldtype="REG_SZ" comparison="exists" rootkey="HKEY_LOCAL_MACHINE" wowoption="WOW64_32" type="check_registry_value" description="Installed Check" />

This registry key is written by the support product MSI during installation (obviously slightly obfuscated the registry key) & used by the main product to get the location of the support files. This works as expected, if any version of the support  product is installed the bootstrapper doesn't attempt to install it. I've tried using that installedcheck with installedcheckoperators set to "Not" preceding it & one set to "Or" along with the id check in my first post but again nothing works as I would like.

Coordinator
Mar 5, 2010 at 2:56 PM

I see. Am I correct that the MSP needs to run before your 1.3?

Basically you have these paths:

  • 1.0->MSP->1.3
  • 1.1->MSP->1.3
  • 1.2->MSP->1.3
  • 1.3

Or I don't understand how MSP works... In the case above you just need to condition the MSP to run only when you have 1.0, 1.1 or 1.2. So it would appear above the 1.3 installer and the condition for installing 1.3 and the MSP should be identical (default InstalledCheckRegistry, type=version, fieldvalue=1.3.0).

Mar 5, 2010 at 4:01 PM

No I need an either or situation. It doesn't matter which way round the MSI or MSP are scheduled to run. They are mutally exclusive. I just prefer to have the MSI before the MSP in the UI list as it gives better feedback on the state of the installation that way.

Either the user has nothing & the MSI installs or the user has an old version and the MSP installs as per the paths you describe. Scheduling only the MSP when the user has 1.0-1.2 but not 1.3 I can do with the installcheck id in my first post. That's never been a problem.

If I use the install check code you suggest both the MSI & MSP will try to install when the user has nothing. If the MSP goes first in that scenario it fails when it's launched because there's nothing for it to update (1.0-1.2 don't exist). If the MSI goes first then the MSP fails when it's launched because there's nothing for it to update (1.3 is already installed). Hence I need to create some combination of install checks which are mutually exclusive because I really don't want users seeing errors during the installation unless it's an actual error which this isn't.

Essentially I'm trying to do something I think isn't possible with the current code. My main problem is that a version check returns a false on a null as well as when the value is less than the given value.

Coordinator
Mar 5, 2010 at 5:30 PM
Edited Mar 5, 2010 at 5:31 PM

Okay, you're right. I couldn't get it to work either. But I think it's just a bug.

When the comparison is "version", and the product doesn't exist, the response shouldn't be "false" for the check in all cases.

  • match: the key doesn't exist, so we don't have a match, false
  • version: the value installed (ie. none) isn't newer than our value, false
  • version_eq: the value installed (ie. none) == our value? false unless fieldvalue is blank
  • version_lt: the value installed (ie. none) is less than our value? true unless fieldvalue is blank
  • version_le: a blank value is smaller or equal than anything: true
  • version_gt: a blank value is never greater than anything: false
  • version_ge: a blank value is only greater or equal to another blank value: true if fieldvalue is blank
  • exists: doesn't exist: false
	if (! DVLib::RegistryKeyExists(DVLib::wstring2HKEY(rootkey), path, fieldname, dwKeyOption))
	{
		LOG(L"*** No registry key found: " << keypath);

		if (comparison == L"match")
			return false;
		else if (comparison == L"version")
			return false;
		else if (comparison == L"version_eq")
			return (fieldvalue == L"");
		else if (comparison == L"version_lt")
			return (fieldvalue != L"");
		else if (comparison == L"version_le")
			return true;
		else if (comparison == L"version_gt")
			return false;
		else if (comparison == L"version_ge")
			return (fieldvalue == L"");
		else if (comparison == L"exists")
			return false;
		else
		{
			THROW_EX("Invalid comparison type: " << comparison);
		}
Similar problem in file version check. If the file doesn't exist and the check is not "exists":
	// file doesn't exists or has no version
	if (comparison == TEXT("match"))
		return false;
	else if (comparison == TEXT("version"))
		return (fileversion.GetValue() != L"");
	else if (comparison == TEXT("version_eq"))
		return (fileversion.GetValue() == L"");
	else if (comparison == TEXT("version_gt"))
		return false;
	else if (comparison == TEXT("version_ge"))
		return (fileversion.GetValue() == L"");
	else if (comparison == TEXT("version_lt"))
		return (fileversion.GetValue() != L"");
	else if (comparison == TEXT("version_le"))
		return true;
	else
	{
		THROW_EX(L"Invalid comparison type \"" << comparison << L"\"");
	}

Thoughts? Comments?
Coordinator
Mar 5, 2010 at 5:42 PM
Edited Mar 5, 2010 at 5:50 PM

... but this still doesn't resolve your problem I think. Maybe there's a place for a new version comparison type? version_patch, ie. it would say: "I am installed (don't install me) when there's no product or its version is newer than fieldvalue."?

Update: alternatively I can just add a default value to return when the registry key doesn't exist.

In all of this, I am just not set of what the theoretical "correct" story would be.

Coordinator
Mar 5, 2010 at 11:21 PM

I thought about this some more and tried the simplest route. I think it's the most elegant.

I've uploaded build 1.9.4402.0, adding defaultvalue options to registry and file installed checks. The default value is used when the bootstrapper does not find the registry key or file, ie. it cannot decide whether the component is installed based on the specifics of the check (eg. version comparison with a value that doesn't exist is not meaningful).

See if it works for you. For the patch install check you would just flip the defaultvalue to true and make it a standard version check. Let me know.

Mar 8, 2010 at 9:28 AM

That sounds perfect, will give the beta a go & see how it goes.

Mar 8, 2010 at 11:02 AM
Edited Mar 8, 2010 at 11:03 AM

Works perfectly. The only thing I could ask for is that you make the same change to the Product Code installedcheck type too as it would save me having to add extra registry checks for every version in the future & I could simply use an upgradecode check with this logic but for now this is all good.

I used the 2 registry install checks in my first post above with

defaultvalue="True"

set on both (no other installedcheck Elements under the MSP component). I left the MSI's installcheck unchanged (pasted in my second post above).

When nothing is installed only the MSI is scheduled (MSP shows as "Installed" but people can learn to live with that).

When any of 1.0, 1.1 or 1.2 are installed only the MSP is scheduled (MSI shows as Installed) as before.

When 1.3 is installed both the MSI & MSP show as Installed as before.

Thanks for all the time & effort spent helping me with this. I'll be 'pimping' dotnetinstaller on the WiX Users discussion list even more than before!

Also thanks for fixing the command parsing bug I reported so promptly. That had been annoying me for a while but I kept forgetting to come register here so I could report it.

Coordinator
Mar 8, 2010 at 6:44 PM

Added defaultvalue to InstalledCheckProduct in build 1.9.4704.0. Try it.

I wrote a unit test today that checks that all the configuration parameters written are read back. It found another one of those with uninstall_command_basic in the cmd component. These have been annoying me for a long time too, hopefuly this is the end of it :)

I'll add an MSP component next, it's pretty trivial and I got a long subway ride home today.

Coordinator
Mar 9, 2010 at 2:30 PM

MSP components added in 1.9.4817.0. Please try it and let me know if it works.

Mar 9, 2010 at 4:17 PM

Both changes seem perfectly fine to me. Tested all 5 of my possible scenarios after changing the MSP's installed check to an upgrade id check as above & all work as before. Tested installing the MSP using full & basic UI levels in the bootstrapper, all works as expected.