From 9bdf3730cd43e1af8a4ea9be6cf2fba77fcff2d2 Mon Sep 17 00:00:00 2001 From: Sean Hall Date: Thu, 1 Jul 2021 09:30:10 -0500 Subject: Add bundle option for command line variables to always be uppercase. Fixes #3777 --- .../WixToolset.Mba.Core/IOverridableVariables.cs | 20 +++++++++ src/api/burn/WixToolset.Mba.Core/MbaCommand.cs | 6 ++- .../WixToolset.Mba.Core/OverridableVariables.cs | 30 +++++++++++++ src/api/burn/balutil/balinfo.cpp | 40 +++++++++++++++++ src/api/burn/balutil/inc/balinfo.h | 7 +++ .../WixToolsetTest.Mba.Core.csproj | 1 + .../wix/WixToolset.Data/Symbols/WixBundleSymbol.cs | 14 ++++++ src/burn/engine/core.cpp | 9 +++- src/burn/engine/variable.cpp | 28 ++++++++++++ src/burn/engine/variable.h | 7 +++ src/burn/test/BurnUnitTest/ManifestTest.cpp | 1 + .../BasicFunctionality_BundleA_manifest.xml | 2 +- .../PlanTest/MsiTransaction_BundleAv1_manifest.xml | 2 +- .../PlanTest/Slipstream_BundleA_manifest.xml | 2 +- src/burn/test/BurnUnitTest/VariableTest.cpp | 3 ++ .../test/WixToolsetTest.Bal/BalExtensionFixture.cs | 27 ++++++++++- .../TestData/Overridable/Bundle.wxs | 2 +- .../TestData/Overridable/WrongCaseBundle.wxs | 13 ++++++ .../WixToolsetTest.Bal/WixToolsetTest.Bal.csproj | 6 +-- src/ext/Bal/wixext/BalBurnBackendExtension.cs | 21 +++++++++ src/ext/Bal/wixext/BalErrors.cs | 6 +++ .../TestData/VariableTests/BundleA/BundleA.wixproj | 17 +++++++ .../TestData/VariableTests/BundleA/BundleA.wxs | 17 +++++++ .../burn/TestData/VariableTests/BundleB/Bundle.wxs | 40 +++++++++++++++++ .../TestData/VariableTests/BundleB/BundleB.wixproj | 14 ++++++ .../TestData/VariableTests/BundleB/BundleB.wxs | 17 +++++++ .../VariableTests/PackageA/PackageA.wixproj | 9 ++++ .../burn/WixToolsetTest.BurnE2E/VariableTests.cs | 52 ++++++++++++++++++++++ ...CreateBootstrapperApplicationManifestCommand.cs | 19 ++++++++ .../Bundles/CreateBurnManifestCommand.cs | 14 ++++++ src/wix/WixToolset.Core/Compiler_Bundle.cs | 17 +++++++ .../BundleFixture.cs | 4 ++ 32 files changed, 454 insertions(+), 13 deletions(-) create mode 100644 src/ext/Bal/test/WixToolsetTest.Bal/TestData/Overridable/WrongCaseBundle.wxs create mode 100644 src/test/burn/TestData/VariableTests/BundleA/BundleA.wixproj create mode 100644 src/test/burn/TestData/VariableTests/BundleA/BundleA.wxs create mode 100644 src/test/burn/TestData/VariableTests/BundleB/Bundle.wxs create mode 100644 src/test/burn/TestData/VariableTests/BundleB/BundleB.wixproj create mode 100644 src/test/burn/TestData/VariableTests/BundleB/BundleB.wxs create mode 100644 src/test/burn/TestData/VariableTests/PackageA/PackageA.wixproj create mode 100644 src/test/burn/WixToolsetTest.BurnE2E/VariableTests.cs (limited to 'src') diff --git a/src/api/burn/WixToolset.Mba.Core/IOverridableVariables.cs b/src/api/burn/WixToolset.Mba.Core/IOverridableVariables.cs index 3944913b..1ffe50e4 100644 --- a/src/api/burn/WixToolset.Mba.Core/IOverridableVariables.cs +++ b/src/api/burn/WixToolset.Mba.Core/IOverridableVariables.cs @@ -4,11 +4,31 @@ namespace WixToolset.Mba.Core { using System.Collections.Generic; + /// + /// The case sensitivity of variables from the command line. + /// + public enum VariableCommandLineType + { + /// + /// Similar to Windows Installer, all variable names specified on the command line are automatically converted to upper case. + /// + UpperCase, + /// + /// All variable names specified on the command line must match the case specified when building the bundle. + /// + CaseSensitive, + } + /// /// Overridable variable information from the BA manifest. /// public interface IOverridableVariables { + /// + /// The of the bundle. + /// + VariableCommandLineType CommandLineType { get; } + /// /// Variable Dictionary of variable name to . /// diff --git a/src/api/burn/WixToolset.Mba.Core/MbaCommand.cs b/src/api/burn/WixToolset.Mba.Core/MbaCommand.cs index e7e49607..424cde63 100644 --- a/src/api/burn/WixToolset.Mba.Core/MbaCommand.cs +++ b/src/api/burn/WixToolset.Mba.Core/MbaCommand.cs @@ -19,9 +19,11 @@ namespace WixToolset.Mba.Core { foreach (var kvp in this.Variables) { - if (!overridableVariables.Variables.TryGetValue(kvp.Key, out var overridableVariable)) + var key = overridableVariables.CommandLineType == VariableCommandLineType.UpperCase ? kvp.Key.ToUpperInvariant() : kvp.Key; + + if (!overridableVariables.Variables.TryGetValue(key, out var overridableVariable)) { - engine.Log(LogLevel.Error, string.Format("Ignoring attempt to set non-overridable variable: '{0}'.", kvp.Key)); + engine.Log(LogLevel.Error, string.Format("Ignoring attempt to set non-overridable variable: '{0}'.", key)); } else { diff --git a/src/api/burn/WixToolset.Mba.Core/OverridableVariables.cs b/src/api/burn/WixToolset.Mba.Core/OverridableVariables.cs index 855ce9a9..148acb34 100644 --- a/src/api/burn/WixToolset.Mba.Core/OverridableVariables.cs +++ b/src/api/burn/WixToolset.Mba.Core/OverridableVariables.cs @@ -12,6 +12,9 @@ namespace WixToolset.Mba.Core /// public class OverridableVariablesInfo : IOverridableVariables { + /// + public VariableCommandLineType CommandLineType { get; internal set; } + /// public IDictionary Variables { get; internal set; } @@ -26,9 +29,36 @@ namespace WixToolset.Mba.Core { XmlNamespaceManager namespaceManager = new XmlNamespaceManager(root.NameTable); namespaceManager.AddNamespace("p", BootstrapperApplicationData.XMLNamespace); + XPathNavigator commandLineNode = root.SelectSingleNode("/p:BootstrapperApplicationData/p:CommandLine", namespaceManager); XPathNodeIterator nodes = root.Select("/p:BootstrapperApplicationData/p:WixStdbaOverridableVariable", namespaceManager); var overridableVariables = new OverridableVariablesInfo(); + + if (commandLineNode == null) + { + throw new Exception("Failed to select command line information."); + } + + string variablesValue = BootstrapperApplicationData.GetAttribute(commandLineNode, "Variables"); + + if (variablesValue == null) + { + throw new Exception("Failed to get command line variable type."); + } + + if (variablesValue.Equals("upperCase", StringComparison.InvariantCulture)) + { + overridableVariables.CommandLineType = VariableCommandLineType.UpperCase; + } + else if (variablesValue.Equals("caseSensitive", StringComparison.InvariantCulture)) + { + overridableVariables.CommandLineType = VariableCommandLineType.CaseSensitive; + } + else + { + throw new Exception(string.Format("Unknown command line variable type: '{0}'", variablesValue)); + } + overridableVariables.Variables = new Dictionary(); foreach (XPathNavigator node in nodes) diff --git a/src/api/burn/balutil/balinfo.cpp b/src/api/burn/balutil/balinfo.cpp index 5927ef72..f71784a5 100644 --- a/src/api/burn/balutil/balinfo.cpp +++ b/src/api/burn/balutil/balinfo.cpp @@ -292,6 +292,7 @@ DAPI_(HRESULT) BalSetOverridableVariablesFromEngine( ) { HRESULT hr = S_OK; + LPWSTR sczKey = NULL; BAL_INFO_OVERRIDABLE_VARIABLE* pOverridableVariable = NULL; for (DWORD i = 0; i < pCommand->cVariables; ++i) @@ -299,6 +300,14 @@ DAPI_(HRESULT) BalSetOverridableVariablesFromEngine( LPCWSTR wzVariableName = pCommand->rgVariableNames[i]; LPCWSTR wzVariableValue = pCommand->rgVariableValues[i]; + if (BAL_INFO_VARIABLE_COMMAND_LINE_TYPE_UPPER_CASE == pOverridableVariables->commandLineType) + { + hr = StrAllocStringToUpperInvariant(&sczKey, wzVariableName, 0); + ExitOnFailure(hr, "Failed to upper case variable name."); + + wzVariableName = sczKey; + } + hr = DictGetValue(pOverridableVariables->sdVariables, wzVariableName, reinterpret_cast(&pOverridableVariable)); if (E_NOTFOUND == hr) { @@ -313,6 +322,8 @@ DAPI_(HRESULT) BalSetOverridableVariablesFromEngine( } LExit: + ReleaseStr(sczKey); + return hr; } @@ -527,10 +538,37 @@ static HRESULT ParseOverridableVariablesFromXml( ) { HRESULT hr = S_OK; + IXMLDOMNode* pCommandLineNode = NULL; + LPWSTR scz = NULL; IXMLDOMNode* pNode = NULL; IXMLDOMNodeList* pNodes = NULL; BAL_INFO_OVERRIDABLE_VARIABLE* pOverridableVariable = NULL; + hr = XmlSelectSingleNode(pixdManifest, L"/BootstrapperApplicationData/CommandLine", &pCommandLineNode); + if (S_FALSE == hr) + { + hr = E_NOTFOUND; + } + ExitOnFailure(hr, "Failed to select command line information."); + + // @Variables + hr = XmlGetAttributeEx(pCommandLineNode, L"Variables", &scz); + ExitOnFailure(hr, "Failed to get command line variable type."); + + if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, 0, scz, -1, L"upperCase", -1)) + { + pOverridableVariables->commandLineType = BAL_INFO_VARIABLE_COMMAND_LINE_TYPE_UPPER_CASE; + } + else if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, 0, scz, -1, L"caseSensitive", -1)) + { + pOverridableVariables->commandLineType = BAL_INFO_VARIABLE_COMMAND_LINE_TYPE_CASE_SENSITIVE; + } + else + { + hr = E_INVALIDARG; + ExitOnFailure(hr, "Invalid value for CommandLine/@Variables: %ls", scz); + } + // Get the list of variables users can override on the command line. hr = XmlSelectNodes(pixdManifest, L"/BootstrapperApplicationData/WixStdbaOverridableVariable", &pNodes); if (S_FALSE == hr) @@ -570,6 +608,8 @@ static HRESULT ParseOverridableVariablesFromXml( } LExit: + ReleaseStr(scz); + ReleaseObject(pCommandLineNode); ReleaseObject(pNode); ReleaseObject(pNodes); return hr; diff --git a/src/api/burn/balutil/inc/balinfo.h b/src/api/burn/balutil/inc/balinfo.h index 0fce35ec..07a1cbb7 100644 --- a/src/api/burn/balutil/inc/balinfo.h +++ b/src/api/burn/balutil/inc/balinfo.h @@ -18,6 +18,12 @@ typedef enum BAL_INFO_PACKAGE_TYPE BAL_INFO_PACKAGE_TYPE_BUNDLE_PATCH, } BAL_INFO_PACKAGE_TYPE; +typedef enum _BAL_INFO_VARIABLE_COMMAND_LINE_TYPE +{ + BAL_INFO_VARIABLE_COMMAND_LINE_TYPE_UPPER_CASE, + BAL_INFO_VARIABLE_COMMAND_LINE_TYPE_CASE_SENSITIVE, +} BAL_INFO_VARIABLE_COMMAND_LINE_TYPE; + typedef struct _BAL_INFO_PACKAGE { @@ -58,6 +64,7 @@ typedef struct _BAL_INFO_OVERRIDABLE_VARIABLES BAL_INFO_OVERRIDABLE_VARIABLE* rgVariables; DWORD cVariables; STRINGDICT_HANDLE sdVariables; + BAL_INFO_VARIABLE_COMMAND_LINE_TYPE commandLineType; } BAL_INFO_OVERRIDABLE_VARIABLES; diff --git a/src/api/burn/test/WixToolsetTest.Mba.Core/WixToolsetTest.Mba.Core.csproj b/src/api/burn/test/WixToolsetTest.Mba.Core/WixToolsetTest.Mba.Core.csproj index bdb6a829..8d68546f 100644 --- a/src/api/burn/test/WixToolsetTest.Mba.Core/WixToolsetTest.Mba.Core.csproj +++ b/src/api/burn/test/WixToolsetTest.Mba.Core/WixToolsetTest.Mba.Core.csproj @@ -14,6 +14,7 @@ + diff --git a/src/api/wix/WixToolset.Data/Symbols/WixBundleSymbol.cs b/src/api/wix/WixToolset.Data/Symbols/WixBundleSymbol.cs index 9724cbd7..de5646d3 100644 --- a/src/api/wix/WixToolset.Data/Symbols/WixBundleSymbol.cs +++ b/src/api/wix/WixToolset.Data/Symbols/WixBundleSymbol.cs @@ -33,6 +33,7 @@ namespace WixToolset.Data new IntermediateFieldDefinition(nameof(WixBundleSymbolFields.BundleId), IntermediateFieldType.String), new IntermediateFieldDefinition(nameof(WixBundleSymbolFields.ProviderKey), IntermediateFieldType.String), new IntermediateFieldDefinition(nameof(WixBundleSymbolFields.InProgressName), IntermediateFieldType.String), + new IntermediateFieldDefinition(nameof(WixBundleSymbolFields.CommandLineVariables), IntermediateFieldType.String), }, typeof(WixBundleSymbol)); } @@ -67,6 +68,7 @@ namespace WixToolset.Data.Symbols BundleId, ProviderKey, InProgressName, + CommandLineVariables, } [Flags] @@ -79,6 +81,12 @@ namespace WixToolset.Data.Symbols PerMachine = 0x8, } + public enum WixBundleCommandLineVariables + { + UpperCase, + CaseSensitive, + } + public class WixBundleSymbol : IntermediateSymbol { public WixBundleSymbol() : base(SymbolDefinitions.WixBundle, null, null) @@ -229,6 +237,12 @@ namespace WixToolset.Data.Symbols set => this.Set((int)WixBundleSymbolFields.InProgressName, value); } + public WixBundleCommandLineVariables CommandLineVariables + { + get => (WixBundleCommandLineVariables)this.Fields[(int)WixBundleSymbolFields.CommandLineVariables].AsNumber(); + set => this.Set((int)WixBundleSymbolFields.CommandLineVariables, (int)value); + } + public PackagingType DefaultPackagingType => (this.Compressed.HasValue && !this.Compressed.Value) ? PackagingType.External : PackagingType.Embedded; public bool DisableModify => (this.Attributes & WixBundleAttributes.DisableModify) == WixBundleAttributes.DisableModify; diff --git a/src/burn/engine/core.cpp b/src/burn/engine/core.cpp index ca613dc5..8b97952c 100644 --- a/src/burn/engine/core.cpp +++ b/src/burn/engine/core.cpp @@ -1699,7 +1699,14 @@ static HRESULT GetSanitizedCommandLine( const wchar_t* pwc = wcschr(argv[i], L'='); if (pwc) { - hr = StrAllocString(&sczVariableName, argv[i], pwc - argv[i]); + if (BURN_VARIABLE_COMMAND_LINE_TYPE_UPPER_CASE == pVariables->commandLineType) + { + hr = StrAllocStringToUpperInvariant(&sczVariableName, argv[i], pwc - argv[i]); + } + else + { + hr = StrAllocString(&sczVariableName, argv[i], pwc - argv[i]); + } ExitOnFailure(hr, "Failed to copy variable name."); hr = VariableIsHidden(pVariables, sczVariableName, &fHidden); diff --git a/src/burn/engine/variable.cpp b/src/burn/engine/variable.cpp index 6f818ff3..e2b1f1f2 100644 --- a/src/burn/engine/variable.cpp +++ b/src/burn/engine/variable.cpp @@ -294,6 +294,7 @@ extern "C" HRESULT VariablesParseFromXml( ) { HRESULT hr = S_OK; + IXMLDOMNode* pixnCommandLine = NULL; IXMLDOMNodeList* pixnNodes = NULL; IXMLDOMNode* pixnNode = NULL; DWORD cNodes = 0; @@ -307,6 +308,32 @@ extern "C" HRESULT VariablesParseFromXml( ::EnterCriticalSection(&pVariables->csAccess); + // select registration node + hr = XmlSelectSingleNode(pixnBundle, L"CommandLine", &pixnCommandLine); + if (S_FALSE == hr) + { + hr = E_NOTFOUND; + } + ExitOnFailure(hr, "Failed to select CommandLine node."); + + // @Variables + hr = XmlGetAttributeEx(pixnCommandLine, L"Variables", &scz); + ExitOnFailure(hr, "Failed to get CommandLine/@Variables."); + + if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, 0, scz, -1, L"upperCase", -1)) + { + pVariables->commandLineType = BURN_VARIABLE_COMMAND_LINE_TYPE_UPPER_CASE; + } + else if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, 0, scz, -1, L"caseSensitive", -1)) + { + pVariables->commandLineType = BURN_VARIABLE_COMMAND_LINE_TYPE_CASE_SENSITIVE; + } + else + { + hr = E_INVALIDARG; + ExitOnFailure(hr, "Invalid value for CommandLine/@Variables: %ls", scz); + } + // select variable nodes hr = XmlSelectNodes(pixnBundle, L"Variable", &pixnNodes); ExitOnFailure(hr, "Failed to select variable nodes."); @@ -434,6 +461,7 @@ extern "C" HRESULT VariablesParseFromXml( LExit: ::LeaveCriticalSection(&pVariables->csAccess); + ReleaseObject(pixnCommandLine); ReleaseObject(pixnNodes); ReleaseObject(pixnNode); ReleaseStr(scz); diff --git a/src/burn/engine/variable.h b/src/burn/engine/variable.h index a38c9daa..9ed86ca7 100644 --- a/src/burn/engine/variable.h +++ b/src/burn/engine/variable.h @@ -25,6 +25,12 @@ typedef HRESULT (*PFN_INITIALIZEVARIABLE)( // constants +enum BURN_VARIABLE_COMMAND_LINE_TYPE +{ + BURN_VARIABLE_COMMAND_LINE_TYPE_UPPER_CASE, + BURN_VARIABLE_COMMAND_LINE_TYPE_CASE_SENSITIVE, +}; + enum BURN_VARIABLE_INTERNAL_TYPE { BURN_VARIABLE_INTERNAL_TYPE_NORMAL, // the BA can set this variable. @@ -54,6 +60,7 @@ typedef struct _BURN_VARIABLES DWORD dwMaxVariables; DWORD cVariables; BURN_VARIABLE* rgVariables; + BURN_VARIABLE_COMMAND_LINE_TYPE commandLineType; } BURN_VARIABLES; diff --git a/src/burn/test/BurnUnitTest/ManifestTest.cpp b/src/burn/test/BurnUnitTest/ManifestTest.cpp index 963be156..345ddfd9 100644 --- a/src/burn/test/BurnUnitTest/ManifestTest.cpp +++ b/src/burn/test/BurnUnitTest/ManifestTest.cpp @@ -37,6 +37,7 @@ namespace Bootstrapper " " "