diff --git a/src/api/wix/WixToolset.Data/Symbols/WixProductSearchSymbol.cs b/src/api/wix/WixToolset.Data/Symbols/WixProductSearchSymbol.cs index 99360da59..38f2af889 100644 --- a/src/api/wix/WixToolset.Data/Symbols/WixProductSearchSymbol.cs +++ b/src/api/wix/WixToolset.Data/Symbols/WixProductSearchSymbol.cs @@ -46,6 +46,7 @@ public enum WixProductSearchType Language, State, Assignment, + Exists, } public class WixProductSearchSymbol : IntermediateSymbol diff --git a/src/burn/engine/search.cpp b/src/burn/engine/search.cpp index b52186009..1f128e957 100644 --- a/src/burn/engine/search.cpp +++ b/src/burn/engine/search.cpp @@ -334,6 +334,10 @@ extern "C" HRESULT SearchesParseFromXml( { pSearch->MsiProductSearch.Type = BURN_MSI_PRODUCT_SEARCH_TYPE_ASSIGNMENT; } + else if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, 0, scz, -1, L"exists", -1)) + { + pSearch->MsiProductSearch.Type = BURN_MSI_PRODUCT_SEARCH_TYPE_EXISTS; + } else { ExitWithRootFailure(hr, E_INVALIDARG, "Invalid value for @Type: %ls", scz); @@ -1144,6 +1148,7 @@ static HRESULT MsiProductSearch( case BURN_MSI_PRODUCT_SEARCH_TYPE_LANGUAGE: wzProperty = INSTALLPROPERTY_LANGUAGE; break; + case BURN_MSI_PRODUCT_SEARCH_TYPE_EXISTS: __fallthrough; case BURN_MSI_PRODUCT_SEARCH_TYPE_STATE: wzProperty = INSTALLPROPERTY_PRODUCTSTATE; break; @@ -1218,6 +1223,7 @@ static HRESULT MsiProductSearch( case BURN_MSI_PRODUCT_SEARCH_TYPE_LANGUAGE: // is supposed to remain empty break; + case BURN_MSI_PRODUCT_SEARCH_TYPE_EXISTS: __fallthrough; case BURN_MSI_PRODUCT_SEARCH_TYPE_STATE: value.Type = BURN_VARIANT_TYPE_NUMERIC; value.llValue = INSTALLSTATE_ABSENT; @@ -1237,6 +1243,7 @@ static HRESULT MsiProductSearch( case BURN_MSI_PRODUCT_SEARCH_TYPE_LANGUAGE: type = BURN_VARIANT_TYPE_STRING; break; + case BURN_MSI_PRODUCT_SEARCH_TYPE_EXISTS: __fallthrough; case BURN_MSI_PRODUCT_SEARCH_TYPE_STATE: __fallthrough; case BURN_MSI_PRODUCT_SEARCH_TYPE_ASSIGNMENT: type = BURN_VARIANT_TYPE_NUMERIC; @@ -1245,6 +1252,13 @@ static HRESULT MsiProductSearch( hr = BVariantChangeType(&value, type); ExitOnFailure(hr, "Failed to change value type."); + // When testing if a product exists, replace the value with a numeric "true" or "false" + // based on the calculated install state. + if (BURN_MSI_PRODUCT_SEARCH_TYPE_EXISTS == pSearch->MsiProductSearch.Type) + { + value.llValue = (value.llValue == INSTALLSTATE_ABSENT) ? 0 : 1; + } + // Set variable. hr = VariableSetVariant(pVariables, pSearch->sczVariable, &value); ExitOnFailure(hr, "Failed to set variable."); diff --git a/src/burn/engine/search.h b/src/burn/engine/search.h index 341fe1aa9..e70645c59 100644 --- a/src/burn/engine/search.h +++ b/src/burn/engine/search.h @@ -58,6 +58,7 @@ enum BURN_MSI_PRODUCT_SEARCH_TYPE BURN_MSI_PRODUCT_SEARCH_TYPE_LANGUAGE, BURN_MSI_PRODUCT_SEARCH_TYPE_STATE, BURN_MSI_PRODUCT_SEARCH_TYPE_ASSIGNMENT, + BURN_MSI_PRODUCT_SEARCH_TYPE_EXISTS, }; enum BURN_MSI_PRODUCT_SEARCH_GUID_TYPE diff --git a/src/burn/test/BurnUnitTest/SearchTest.cpp b/src/burn/test/BurnUnitTest/SearchTest.cpp index a8e397c22..e3c714cb4 100644 --- a/src/burn/test/BurnUnitTest/SearchTest.cpp +++ b/src/burn/test/BurnUnitTest/SearchTest.cpp @@ -410,6 +410,8 @@ namespace Bootstrapper L" " L" " L" " + L" " + L" " L""; // load XML document @@ -429,6 +431,8 @@ namespace Bootstrapper Assert::Equal(5ll, VariableGetNumericHelper(&variables, L"Variable4")); Assert::Equal(1ll, VariableGetNumericHelper(&variables, L"Variable5")); Assert::Equal(gcnew String(L"1.0.0.0"), VariableGetVersionHelper(&variables, L"Variable6")); + Assert::Equal(1ll, VariableGetNumericHelper(&variables, L"Variable7")); + Assert::Equal(0ll, VariableGetNumericHelper(&variables, L"Variable8")); } finally { diff --git a/src/ext/Util/wixext/UtilCompiler.cs b/src/ext/Util/wixext/UtilCompiler.cs index 1ad273221..919cc49b2 100644 --- a/src/ext/Util/wixext/UtilCompiler.cs +++ b/src/ext/Util/wixext/UtilCompiler.cs @@ -2736,8 +2736,11 @@ private void ParseProductSearchElement(Intermediate intermediate, IntermediateSe case "assignment": type = WixProductSearchType.Assignment; break; + case "exists": + type = WixProductSearchType.Exists; + break; default: - this.Messaging.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, attrib.Parent.Name.LocalName, attrib.Name.LocalName, result, "version", "language", "state", "assignment")); + this.Messaging.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, attrib.Parent.Name.LocalName, attrib.Name.LocalName, result, "version", "language", "state", "assignment", "exists")); break; } break; diff --git a/src/wix/WixToolset.Core.Burn/Bind/LegacySearchFacade.cs b/src/wix/WixToolset.Core.Burn/Bind/LegacySearchFacade.cs index c9301fdc4..885f307e5 100644 --- a/src/wix/WixToolset.Core.Burn/Bind/LegacySearchFacade.cs +++ b/src/wix/WixToolset.Core.Burn/Bind/LegacySearchFacade.cs @@ -135,6 +135,9 @@ private void WriteProductSearchXml(XmlTextWriter writer, WixProductSearchSymbol case WixProductSearchType.Assignment: writer.WriteAttributeString("Type", "assignment"); break; + case WixProductSearchType.Exists: + writer.WriteAttributeString("Type", "exists"); + break; default: throw new NotImplementedException(); } diff --git a/src/xsd/util.xsd b/src/xsd/util.xsd index c0a9e44ae..99a1b58ca 100644 --- a/src/xsd/util.xsd +++ b/src/xsd/util.xsd @@ -1344,6 +1344,14 @@ Saves the version of a matching product if found; 0.0.0.0 otherwise. This is the default. + + + + [WiX v7 and later] + Saves true if a matching product entry is found (non-absent); false otherwise. + + +