diff --git a/src/api/burn/WixToolset.BootstrapperApplicationApi/IPackageInfo.cs b/src/api/burn/WixToolset.BootstrapperApplicationApi/IPackageInfo.cs index e2512584a..9b5e74cb4 100644 --- a/src/api/burn/WixToolset.BootstrapperApplicationApi/IPackageInfo.cs +++ b/src/api/burn/WixToolset.BootstrapperApplicationApi/IPackageInfo.cs @@ -27,6 +27,11 @@ public interface IPackageInfo /// string DisplayInternalUICondition { get; } + /// + /// The authored bal:DisplayFilesInUseDialogCondition. + /// + string DisplayFilesInUseDialogCondition { get; } + /// /// The package's display name. /// diff --git a/src/api/burn/WixToolset.BootstrapperApplicationApi/PackageInfo.cs b/src/api/burn/WixToolset.BootstrapperApplicationApi/PackageInfo.cs index e835f9ea2..81a8869f9 100644 --- a/src/api/burn/WixToolset.BootstrapperApplicationApi/PackageInfo.cs +++ b/src/api/burn/WixToolset.BootstrapperApplicationApi/PackageInfo.cs @@ -120,6 +120,9 @@ public class PackageInfo : IPackageInfo /// public string DisplayInternalUICondition { get; internal set; } + /// + public string DisplayFilesInUseDialogCondition { get; internal set; } + /// public string ProductCode { get; internal set; } @@ -363,6 +366,7 @@ internal static void ParseBalPackageInfoFromXml(XPathNavigator root, XmlNamespac var package = (PackageInfo)ipackage; package.DisplayInternalUICondition = BootstrapperApplicationData.GetAttribute(node, "DisplayInternalUICondition"); + package.DisplayFilesInUseDialogCondition = BootstrapperApplicationData.GetAttribute(node, "DisplayFilesInUseDialogCondition"); } nodes = root.Select("/p:BootstrapperApplicationData/p:WixPrereqInformation", namespaceManager); diff --git a/src/api/burn/balutil/balinfo.cpp b/src/api/burn/balutil/balinfo.cpp index 6f609a400..85d8deecf 100644 --- a/src/api/burn/balutil/balinfo.cpp +++ b/src/api/burn/balutil/balinfo.cpp @@ -291,6 +291,7 @@ DAPI_(void) BalInfoUninitialize( ReleaseStr(pBundle->packages.rgPackages[i].sczDescription); ReleaseStr(pBundle->packages.rgPackages[i].sczId); ReleaseStr(pBundle->packages.rgPackages[i].sczDisplayInternalUICondition); + ReleaseStr(pBundle->packages.rgPackages[i].sczDisplayFilesInUseDialogCondition); ReleaseStr(pBundle->packages.rgPackages[i].sczProductCode); ReleaseStr(pBundle->packages.rgPackages[i].sczUpgradeCode); ReleaseStr(pBundle->packages.rgPackages[i].sczVersion); @@ -517,6 +518,9 @@ static HRESULT ParseBalPackageInfoFromXml( hr = XmlGetAttributeEx(pNode, L"DisplayInternalUICondition", &pPackage->sczDisplayInternalUICondition); ExitOnOptionalXmlQueryFailure(hr, fXmlFound, "Failed to get DisplayInternalUICondition setting for package."); + hr = XmlGetAttributeEx(pNode, L"DisplayFilesInUseDialogCondition", &pPackage->sczDisplayFilesInUseDialogCondition); + ExitOnOptionalXmlQueryFailure(hr, fXmlFound, "Failed to get DisplayFilesInUseDialogCondition setting for package."); + hr = XmlGetAttributeEx(pNode, L"PrimaryPackageType", &scz); ExitOnOptionalXmlQueryFailure(hr, fXmlFound, "Failed to get PrimaryPackageType setting for package."); diff --git a/src/api/burn/balutil/inc/balinfo.h b/src/api/burn/balutil/inc/balinfo.h index 234284f6f..8baee8445 100644 --- a/src/api/burn/balutil/inc/balinfo.h +++ b/src/api/burn/balutil/inc/balinfo.h @@ -54,6 +54,7 @@ typedef struct _BAL_INFO_PACKAGE BOOL fPermanent; BOOL fVital; LPWSTR sczDisplayInternalUICondition; + LPWSTR sczDisplayFilesInUseDialogCondition; LPWSTR sczProductCode; LPWSTR sczUpgradeCode; LPWSTR sczVersion; diff --git a/src/ext/Bal/stdbas/WixStandardBootstrapperApplication.cpp b/src/ext/Bal/stdbas/WixStandardBootstrapperApplication.cpp index c86a4a1de..daa9582d1 100644 --- a/src/ext/Bal/stdbas/WixStandardBootstrapperApplication.cpp +++ b/src/ext/Bal/stdbas/WixStandardBootstrapperApplication.cpp @@ -1231,34 +1231,52 @@ class CWixStandardBootstrapperApplication : public CBootstrapperApplicationBase __inout int* pResult ) { + HRESULT hr = S_OK; + BAL_INFO_PACKAGE* pPackage = NULL; + BOOL fShowFilesInUseDialog = TRUE; if (!m_fShowingInternalUiThisPackage && wzPackageId && *wzPackageId) { BalLog(BOOTSTRAPPER_LOG_LEVEL_VERBOSE, "Package %ls has %d applications holding files in use.", wzPackageId, cFiles); - switch (source) + hr = BalInfoFindPackageById(&m_Bundle.packages, wzPackageId, &pPackage); + if (SUCCEEDED(hr) && pPackage->sczDisplayFilesInUseDialogCondition) { - case BOOTSTRAPPER_FILES_IN_USE_TYPE_MSI: - if (m_fShowStandardFilesInUse) - { - return ShowMsiFilesInUse(cFiles, rgwzFiles, source, pResult); - } - break; - case BOOTSTRAPPER_FILES_IN_USE_TYPE_MSI_RM: - if (m_fShowRMFilesInUse) - { - return ShowMsiFilesInUse(cFiles, rgwzFiles, source, pResult); - } - break; - case BOOTSTRAPPER_FILES_IN_USE_TYPE_NETFX: - if (m_fShowNetfxFilesInUse) + hr = BalEvaluateCondition(pPackage->sczDisplayFilesInUseDialogCondition, &fShowFilesInUseDialog); + BalExitOnFailure(hr, "Failed to evaluate condition for package '%ls': %ls", wzPackageId, pPackage->sczDisplayFilesInUseDialogCondition); + } + + if (fShowFilesInUseDialog) + { + switch (source) { - return ShowNetfxFilesInUse(cFiles, rgwzFiles, pResult); + case BOOTSTRAPPER_FILES_IN_USE_TYPE_MSI: + if (m_fShowStandardFilesInUse) + { + return ShowMsiFilesInUse(cFiles, rgwzFiles, source, pResult); + } + break; + case BOOTSTRAPPER_FILES_IN_USE_TYPE_MSI_RM: + if (m_fShowRMFilesInUse) + { + return ShowMsiFilesInUse(cFiles, rgwzFiles, source, pResult); + } + break; + case BOOTSTRAPPER_FILES_IN_USE_TYPE_NETFX: + if (m_fShowNetfxFilesInUse) + { + return ShowNetfxFilesInUse(cFiles, rgwzFiles, pResult); + } + break; } - break; + } + else + { + *pResult = IDIGNORE; } } + LExit: return __super::OnExecuteFilesInUse(wzPackageId, cFiles, rgwzFiles, nRecommendation, source, pResult); } diff --git a/src/ext/Bal/test/WixToolsetTest.BootstrapperApplications/BalExtensionFixture.cs b/src/ext/Bal/test/WixToolsetTest.BootstrapperApplications/BalExtensionFixture.cs index a9460008c..d7528698c 100644 --- a/src/ext/Bal/test/WixToolsetTest.BootstrapperApplications/BalExtensionFixture.cs +++ b/src/ext/Bal/test/WixToolsetTest.BootstrapperApplications/BalExtensionFixture.cs @@ -51,6 +51,44 @@ public void CanBuildUsingDisplayInternalUICondition() } } + [Fact] + public void CanBuildUsingDisplayFilesInUseDialogCondition() + { + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var bundleFile = Path.Combine(baseFolder, "bin", "test.exe"); + var bundleSourceFolder = TestData.Get(@"TestData\WixStdBa"); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var baFolderPath = Path.Combine(baseFolder, "ba"); + var extractFolderPath = Path.Combine(baseFolder, "extract"); + + var compileResult = WixRunner.Execute(new[] + { + "build", + Path.Combine(bundleSourceFolder, "DisplayFilesInUseDialogConditionBundle.wxs"), + "-ext", TestData.Get(@"WixToolset.BootstrapperApplications.wixext.dll"), + "-intermediateFolder", intermediateFolder, + "-bindpath", Path.Combine(bundleSourceFolder, "data"), + "-o", bundleFile, + }); + compileResult.AssertSuccess(); + + Assert.True(File.Exists(bundleFile)); + + var extractResult = BundleExtractor.ExtractBAContainer(null, bundleFile, baFolderPath, extractFolderPath); + extractResult.AssertSuccess(); + + var balPackageInfos = extractResult.GetBADataTestXmlLines("/ba:BootstrapperApplicationData/ba:WixBalPackageInfo"); + WixAssert.CompareLineByLine(new string[] + { + "", + }, balPackageInfos); + + Assert.True(File.Exists(Path.Combine(baFolderPath, "thm.wxl"))); + } + } + [Fact] public void CanBuildUsingOverridable() { @@ -253,6 +291,7 @@ public void CannotBuildUsingOverridableWrongCase() { "bal:Condition/@Condition contains the built-in Variable 'WixBundleAction', which is not available when it is evaluated. (Unavailable Variables are: 'WixBundleAction'.). Rewrite the condition to avoid Variables that are never valid during its evaluation.", "Overridable variable 'TEST1' collides with 'Test1' with Bundle/@CommandLineVariables value 'caseInsensitive'.", + "The *Package/@bal:DisplayFilesInUseDialogCondition attribute's value '=' is not a valid bundle condition.", "The *Package/@bal:DisplayInternalUICondition attribute's value '=' is not a valid bundle condition.", "The location of the Variable related to the previous error.", }, messages.ToArray()); diff --git a/src/ext/Bal/test/WixToolsetTest.BootstrapperApplications/InternalUIBAFixture.cs b/src/ext/Bal/test/WixToolsetTest.BootstrapperApplications/InternalUIBAFixture.cs index 58d5d551a..706dccc27 100644 --- a/src/ext/Bal/test/WixToolsetTest.BootstrapperApplications/InternalUIBAFixture.cs +++ b/src/ext/Bal/test/WixToolsetTest.BootstrapperApplications/InternalUIBAFixture.cs @@ -167,6 +167,7 @@ public void CanBuildUsingWixIuiBaWithWarnings() "WixInternalUIBootstrapperApplication does not support the value of 'force' for Cache on prereq packages. Prereq packages are only cached when they need to be installed.", "WixInternalUIBootstrapperApplication ignores InstallCondition for the primary package so that the MSI UI is always shown.", "WixInternalUIBootstrapperApplication ignores DisplayInternalUICondition for the primary package so that the MSI UI is always shown.", + "WixInternalUIBootstrapperApplication ignores DisplayFilesInUseDialogCondition for the primary package so that the MSI UI is always shown.", "When using WixInternalUIBootstrapperApplication, all prereq packages should be before the primary package in the chain. The prereq packages are always installed before the primary package.", }, compileResult.Messages.Select(m => m.ToString()).ToArray()); @@ -180,7 +181,7 @@ public void CanBuildUsingWixIuiBaWithWarnings() var balPackageInfos = extractResult.GetBADataTestXmlLines("/ba:BootstrapperApplicationData/ba:WixBalPackageInfo"); WixAssert.CompareLineByLine(new string[] { - "", + "", }, balPackageInfos); var mbaPrereqInfos = extractResult.GetBADataTestXmlLines("/ba:BootstrapperApplicationData/ba:WixPrereqInformation"); diff --git a/src/ext/Bal/test/WixToolsetTest.BootstrapperApplications/TestData/Overridable/WrongCaseBundle.wxs b/src/ext/Bal/test/WixToolsetTest.BootstrapperApplications/TestData/Overridable/WrongCaseBundle.wxs index 67dfc5894..33b2d64cf 100644 --- a/src/ext/Bal/test/WixToolsetTest.BootstrapperApplications/TestData/Overridable/WrongCaseBundle.wxs +++ b/src/ext/Bal/test/WixToolsetTest.BootstrapperApplications/TestData/Overridable/WrongCaseBundle.wxs @@ -9,7 +9,7 @@ - + diff --git a/src/ext/Bal/test/WixToolsetTest.BootstrapperApplications/TestData/WixIuiBa/IuibaWarnings.wxs b/src/ext/Bal/test/WixToolsetTest.BootstrapperApplications/TestData/WixIuiBa/IuibaWarnings.wxs index 2cf9787df..9c9aa0f87 100644 --- a/src/ext/Bal/test/WixToolsetTest.BootstrapperApplications/TestData/WixIuiBa/IuibaWarnings.wxs +++ b/src/ext/Bal/test/WixToolsetTest.BootstrapperApplications/TestData/WixIuiBa/IuibaWarnings.wxs @@ -6,7 +6,7 @@ - + diff --git a/src/ext/Bal/test/WixToolsetTest.BootstrapperApplications/TestData/WixStdBa/DisplayFilesInUseDialogConditionBundle.wxs b/src/ext/Bal/test/WixToolsetTest.BootstrapperApplications/TestData/WixStdBa/DisplayFilesInUseDialogConditionBundle.wxs new file mode 100644 index 000000000..1041eb39a --- /dev/null +++ b/src/ext/Bal/test/WixToolsetTest.BootstrapperApplications/TestData/WixStdBa/DisplayFilesInUseDialogConditionBundle.wxs @@ -0,0 +1,12 @@ + + + + + + + + + + + diff --git a/src/ext/Bal/wixext/BalBurnBackendExtension.cs b/src/ext/Bal/wixext/BalBurnBackendExtension.cs index 821955496..5831bb8a4 100644 --- a/src/ext/Bal/wixext/BalBurnBackendExtension.cs +++ b/src/ext/Bal/wixext/BalBurnBackendExtension.cs @@ -51,6 +51,11 @@ public override bool TryProcessSymbol(IntermediateSection section, IntermediateS writer.WriteAttributeString("DisplayInternalUICondition", balPackageInfoSymbol.DisplayInternalUICondition); } + if (balPackageInfoSymbol.DisplayFilesInUseDialogCondition != null) + { + writer.WriteAttributeString("DisplayFilesInUseDialogCondition", balPackageInfoSymbol.DisplayFilesInUseDialogCondition); + } + if (balPackageInfoSymbol.PrimaryPackageType != BalPrimaryPackageType.None) { writer.WriteAttributeString("PrimaryPackageType", balPackageInfoSymbol.PrimaryPackageType.ToString().ToLower()); @@ -104,6 +109,7 @@ public override void SymbolsFinalized(IntermediateSection section) this.VerifyBalConditions(section); this.VerifyDisplayInternalUICondition(section); + this.VerifyDisplayFilesInUseDialogCondition(section); this.VerifyOverridableVariables(section); var balBaSymbol = section.Symbols.OfType().SingleOrDefault(); @@ -195,6 +201,17 @@ private void VerifyDisplayInternalUICondition(IntermediateSection section) } } + private void VerifyDisplayFilesInUseDialogCondition(IntermediateSection section) + { + foreach (var balPackageInfoSymbol in section.Symbols.OfType().ToList()) + { + if (balPackageInfoSymbol.DisplayFilesInUseDialogCondition != null) + { + this.BackendHelper.ValidateBundleCondition(balPackageInfoSymbol.SourceLineNumbers, "*Package", "bal:DisplayFilesInUseDialogCondition", balPackageInfoSymbol.DisplayFilesInUseDialogCondition, BundleConditionPhase.Plan); + } + } + } + private void VerifyPrimaryPackages(IntermediateSection section, SourceLineNumber baSourceLineNumbers) { WixBalPackageInfoSymbol defaultPrimaryPackage = null; @@ -420,6 +437,11 @@ private void VerifyIuibaPrimaryPackage(WixBundlePackageSymbol packageSymbol, Wix { this.Messaging.Write(BalWarnings.IuibaPrimaryPackageDisplayInternalUICondition(packageSymbol.SourceLineNumbers)); } + + if (balPackageInfoSymbol.DisplayFilesInUseDialogCondition != null) + { + this.Messaging.Write(BalWarnings.IuibaPrimaryPackageDisplayFilesInUseDialogCondition(packageSymbol.SourceLineNumbers)); + } } private void VerifyOverridableVariables(IntermediateSection section) diff --git a/src/ext/Bal/wixext/BalCompiler.cs b/src/ext/Bal/wixext/BalCompiler.cs index 35c86233c..b7d5f679f 100644 --- a/src/ext/Bal/wixext/BalCompiler.cs +++ b/src/ext/Bal/wixext/BalCompiler.cs @@ -200,6 +200,20 @@ public override void ParseAttribute(Intermediate intermediate, IntermediateSecti break; } break; + case "DisplayFilesInUseDialogCondition": + switch (parentElement.Name.LocalName) + { + case "MsiPackage": + case "MspPackage": + var displayFilesInUseDialogCondition = this.ParseHelper.GetAttributeValue(sourceLineNumbers, attribute); + var packageInfo = this.GetBalPackageInfoSymbol(section, sourceLineNumbers, packageId); + packageInfo.DisplayFilesInUseDialogCondition = displayFilesInUseDialogCondition; + break; + default: + this.ParseHelper.UnexpectedAttribute(parentElement, attribute); + break; + } + break; case "PrimaryPackageType": { var primaryPackageType = BalPrimaryPackageType.None; diff --git a/src/ext/Bal/wixext/BalWarnings.cs b/src/ext/Bal/wixext/BalWarnings.cs index 8c5d892f1..f86837f9f 100644 --- a/src/ext/Bal/wixext/BalWarnings.cs +++ b/src/ext/Bal/wixext/BalWarnings.cs @@ -23,6 +23,11 @@ public static Message IuibaPrimaryPackageDisplayInternalUICondition(SourceLineNu return Message(sourceLineNumbers, Ids.IuibaPrimaryPackageDisplayInternalUICondition, "WixInternalUIBootstrapperApplication ignores DisplayInternalUICondition for the primary package so that the MSI UI is always shown."); } + public static Message IuibaPrimaryPackageDisplayFilesInUseDialogCondition(SourceLineNumber sourceLineNumbers) + { + return Message(sourceLineNumbers, Ids.IuibaPrimaryPackageDisplayFilesInUseDialogCondition, "WixInternalUIBootstrapperApplication ignores DisplayFilesInUseDialogCondition for the primary package so that the MSI UI is always shown."); + } + public static Message IuibaPrimaryPackageInstallCondition(SourceLineNumber sourceLineNumbers) { return Message(sourceLineNumbers, Ids.IuibaPrimaryPackageInstallCondition, "WixInternalUIBootstrapperApplication ignores InstallCondition for the primary package so that the MSI UI is always shown."); @@ -56,6 +61,7 @@ public enum Ids IuibaPrimaryPackageDisplayInternalUICondition = 6504, IuibaPrereqPackageAfterPrimaryPackage = 6505, DeprecatedBAFactoryAssemblyAttribute = 6506, + IuibaPrimaryPackageDisplayFilesInUseDialogCondition = 6507, } } } diff --git a/src/ext/Bal/wixext/Symbols/WixBalPackageInfoSymbol.cs b/src/ext/Bal/wixext/Symbols/WixBalPackageInfoSymbol.cs index e2636d33e..9ac97ee91 100644 --- a/src/ext/Bal/wixext/Symbols/WixBalPackageInfoSymbol.cs +++ b/src/ext/Bal/wixext/Symbols/WixBalPackageInfoSymbol.cs @@ -13,6 +13,7 @@ public static partial class BalSymbolDefinitions { new IntermediateFieldDefinition(nameof(WixBalPackageInfoSymbolFields.PackageId), IntermediateFieldType.String), new IntermediateFieldDefinition(nameof(WixBalPackageInfoSymbolFields.DisplayInternalUICondition), IntermediateFieldType.String), + new IntermediateFieldDefinition(nameof(WixBalPackageInfoSymbolFields.DisplayFilesInUseDialogCondition), IntermediateFieldType.String), new IntermediateFieldDefinition(nameof(WixBalPackageInfoSymbolFields.PrimaryPackageType), IntermediateFieldType.Number), }, typeof(WixBalPackageInfoSymbol)); @@ -27,6 +28,7 @@ public enum WixBalPackageInfoSymbolFields { PackageId, DisplayInternalUICondition, + DisplayFilesInUseDialogCondition, PrimaryPackageType, } @@ -63,6 +65,12 @@ public string DisplayInternalUICondition set => this.Set((int)WixBalPackageInfoSymbolFields.DisplayInternalUICondition, value); } + public string DisplayFilesInUseDialogCondition + { + get => this.Fields[(int)WixBalPackageInfoSymbolFields.DisplayFilesInUseDialogCondition].AsString(); + set => this.Set((int)WixBalPackageInfoSymbolFields.DisplayFilesInUseDialogCondition, value); + } + public BalPrimaryPackageType PrimaryPackageType { get => (BalPrimaryPackageType)this.Fields[(int)WixBalPackageInfoSymbolFields.PrimaryPackageType].AsNumber();