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.
+
+
+