From 8cbfc326cccf8d9b3b63cb6f752fc770f7dee0fc Mon Sep 17 00:00:00 2001 From: Sean Hall Date: Tue, 29 Jun 2021 19:14:02 -0500 Subject: Expose overridable variable APIs in balutil and Mba.Core. Fixes #4777 --- .../WixToolset.Mba.Core/BootstrapperCommand.cs | 52 +++++- src/api/burn/WixToolset.Mba.Core/BundleInfo.cs | 5 + .../WixToolset.Mba.Core/IBootstrapperCommand.cs | 16 +- src/api/burn/WixToolset.Mba.Core/IBundleInfo.cs | 5 + src/api/burn/WixToolset.Mba.Core/IMbaCommand.cs | 30 +++ .../IOverridableVariableInfo.cs | 15 ++ .../WixToolset.Mba.Core/IOverridableVariables.cs | 17 ++ src/api/burn/WixToolset.Mba.Core/MbaCommand.cs | 33 ++++ .../WixToolset.Mba.Core/OverridableVariableInfo.cs | 15 ++ .../WixToolset.Mba.Core/OverridableVariables.cs | 51 ++++++ src/api/burn/balutil/balinfo.cpp | 203 +++++++++++++++++++++ src/api/burn/balutil/inc/balinfo.h | 55 ++++++ src/api/burn/balutil/precomp.h | 1 + .../BaseBootstrapperApplicationFactoryFixture.cs | 13 +- .../WixStandardBootstrapperApplication.cpp | 131 +++---------- src/test/burn/TestBA/TestBA.cs | 6 +- .../burn/WixToolset.WixBA/InstallationViewModel.cs | 12 +- 17 files changed, 539 insertions(+), 121 deletions(-) create mode 100644 src/api/burn/WixToolset.Mba.Core/IMbaCommand.cs create mode 100644 src/api/burn/WixToolset.Mba.Core/IOverridableVariableInfo.cs create mode 100644 src/api/burn/WixToolset.Mba.Core/IOverridableVariables.cs create mode 100644 src/api/burn/WixToolset.Mba.Core/MbaCommand.cs create mode 100644 src/api/burn/WixToolset.Mba.Core/OverridableVariableInfo.cs create mode 100644 src/api/burn/WixToolset.Mba.Core/OverridableVariables.cs (limited to 'src') diff --git a/src/api/burn/WixToolset.Mba.Core/BootstrapperCommand.cs b/src/api/burn/WixToolset.Mba.Core/BootstrapperCommand.cs index 65dde0f4..345e0448 100644 --- a/src/api/burn/WixToolset.Mba.Core/BootstrapperCommand.cs +++ b/src/api/burn/WixToolset.Mba.Core/BootstrapperCommand.cs @@ -3,6 +3,7 @@ namespace WixToolset.Mba.Core { using System; + using System.Collections.Generic; using System.ComponentModel; using System.Runtime.InteropServices; @@ -11,8 +12,6 @@ namespace WixToolset.Mba.Core /// public sealed class BootstrapperCommand : IBootstrapperCommand { - private readonly string commandLine; - /// /// /// @@ -45,7 +44,7 @@ namespace WixToolset.Mba.Core this.Action = action; this.Display = display; this.Restart = restart; - this.commandLine = commandLine; + this.CommandLine = commandLine; this.CmdShow = cmdShow; this.Resume = resume; this.SplashScreen = splashScreen; @@ -66,7 +65,7 @@ namespace WixToolset.Mba.Core public Restart Restart { get; } /// - public string[] CommandLineArgs => GetCommandLineArgs(this.commandLine); + public string CommandLine { get; } /// public int CmdShow { get; } @@ -92,6 +91,49 @@ namespace WixToolset.Mba.Core /// public string BootstrapperApplicationDataPath { get; } + /// + public IMbaCommand ParseCommandLine() + { + var args = ParseCommandLineToArgs(this.CommandLine); + var unknownArgs = new List(); + var variables = new List>(); + + foreach (var arg in args) + { + var unknownArg = false; + + if (arg[0] == '-' || arg[0] == '/') + { + unknownArg = true; + } + else + { + var index = arg.IndexOf('='); + if (index == -1) + { + unknownArg = true; + } + else + { + var name = arg.Substring(0, index); + var value = arg.Substring(index + 1); + variables.Add(new KeyValuePair(name, value)); + } + } + + if (unknownArg) + { + unknownArgs.Add(arg); + } + } + + return new MbaCommand + { + UnknownCommandLineArgs = unknownArgs.ToArray(), + Variables = variables.ToArray(), + }; + } + /// /// Gets the command line arguments as a string array. /// @@ -102,7 +144,7 @@ namespace WixToolset.Mba.Core /// /// This method uses the same parsing as the operating system which handles quotes and spaces correctly. /// - public static string[] GetCommandLineArgs(string commandLine) + public static string[] ParseCommandLineToArgs(string commandLine) { if (null == commandLine) { diff --git a/src/api/burn/WixToolset.Mba.Core/BundleInfo.cs b/src/api/burn/WixToolset.Mba.Core/BundleInfo.cs index 3d5d535d..4a533bf9 100644 --- a/src/api/burn/WixToolset.Mba.Core/BundleInfo.cs +++ b/src/api/burn/WixToolset.Mba.Core/BundleInfo.cs @@ -22,6 +22,9 @@ namespace WixToolset.Mba.Core /// public string LogVariable { get; internal set; } + /// + public IOverridableVariables OverridableVariables { get; internal set; } + /// public IDictionary Packages { get; internal set; } @@ -78,6 +81,8 @@ namespace WixToolset.Mba.Core bundle.LogVariable = BootstrapperApplicationData.GetAttribute(bundleNode, "LogPathVariable"); + bundle.OverridableVariables = OverridableVariablesInfo.ParseFromXml(root); + bundle.Packages = PackageInfo.ParsePackagesFromXml(root); return bundle; diff --git a/src/api/burn/WixToolset.Mba.Core/IBootstrapperCommand.cs b/src/api/burn/WixToolset.Mba.Core/IBootstrapperCommand.cs index e861813f..b7baa55c 100644 --- a/src/api/burn/WixToolset.Mba.Core/IBootstrapperCommand.cs +++ b/src/api/burn/WixToolset.Mba.Core/IBootstrapperCommand.cs @@ -25,13 +25,12 @@ namespace WixToolset.Mba.Core Restart Restart { get; } /// - /// Gets the command line arguments as a string array. + /// Gets the command line arguments. /// /// - /// Array of command line arguments not handled by the engine. + /// Command line arguments not handled by the engine. /// - /// The command line could not be parsed into an array. - string[] CommandLineArgs { get; } + string CommandLine { get; } /// /// Hint for the initial visibility of the window. @@ -72,5 +71,14 @@ namespace WixToolset.Mba.Core /// Gets path to BootstrapperApplicationData.xml. /// string BootstrapperApplicationDataPath { get; } + + /// + /// Parses the command line arguments into an . + /// + /// + /// The parsed information. + /// + /// The command line could not be parsed. + IMbaCommand ParseCommandLine(); } } diff --git a/src/api/burn/WixToolset.Mba.Core/IBundleInfo.cs b/src/api/burn/WixToolset.Mba.Core/IBundleInfo.cs index f4a82f36..35decc88 100644 --- a/src/api/burn/WixToolset.Mba.Core/IBundleInfo.cs +++ b/src/api/burn/WixToolset.Mba.Core/IBundleInfo.cs @@ -19,6 +19,11 @@ namespace WixToolset.Mba.Core /// string Name { get; } + /// + /// + /// + IOverridableVariables OverridableVariables { get; } + /// /// /// diff --git a/src/api/burn/WixToolset.Mba.Core/IMbaCommand.cs b/src/api/burn/WixToolset.Mba.Core/IMbaCommand.cs new file mode 100644 index 00000000..a3ad68d8 --- /dev/null +++ b/src/api/burn/WixToolset.Mba.Core/IMbaCommand.cs @@ -0,0 +1,30 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. + +namespace WixToolset.Mba.Core +{ + using System.Collections.Generic; + + /// + /// Command information parsed from the command line. + /// + public interface IMbaCommand + { + /// + /// The command line arguments not parsed into or . + /// + string[] UnknownCommandLineArgs { get; } + + /// + /// The variables that were parsed from the command line. + /// Key = variable name, Value = variable value. + /// + KeyValuePair[] Variables { get; } + + /// + /// Sets overridable variables from the command line. + /// + /// The overridable variable information from . + /// The engine. + void SetOverridableVariables(IOverridableVariables overridableVariables, IEngine engine); + } +} diff --git a/src/api/burn/WixToolset.Mba.Core/IOverridableVariableInfo.cs b/src/api/burn/WixToolset.Mba.Core/IOverridableVariableInfo.cs new file mode 100644 index 00000000..d01dead3 --- /dev/null +++ b/src/api/burn/WixToolset.Mba.Core/IOverridableVariableInfo.cs @@ -0,0 +1,15 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. + +namespace WixToolset.Mba.Core +{ + /// + /// Overridable variable from the BA manifest. + /// + public interface IOverridableVariableInfo + { + /// + /// The Variable name. + /// + string Name { get; } + } +} \ No newline at end of file diff --git a/src/api/burn/WixToolset.Mba.Core/IOverridableVariables.cs b/src/api/burn/WixToolset.Mba.Core/IOverridableVariables.cs new file mode 100644 index 00000000..3944913b --- /dev/null +++ b/src/api/burn/WixToolset.Mba.Core/IOverridableVariables.cs @@ -0,0 +1,17 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. + +namespace WixToolset.Mba.Core +{ + using System.Collections.Generic; + + /// + /// Overridable variable information from the BA manifest. + /// + public interface IOverridableVariables + { + /// + /// Variable Dictionary of variable name to . + /// + IDictionary Variables { get; } + } +} \ No newline at end of file diff --git a/src/api/burn/WixToolset.Mba.Core/MbaCommand.cs b/src/api/burn/WixToolset.Mba.Core/MbaCommand.cs new file mode 100644 index 00000000..e7e49607 --- /dev/null +++ b/src/api/burn/WixToolset.Mba.Core/MbaCommand.cs @@ -0,0 +1,33 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. + +namespace WixToolset.Mba.Core +{ + using System.Collections.Generic; + + /// + /// Default implementation of . + /// + internal sealed class MbaCommand : IMbaCommand + { + public string[] UnknownCommandLineArgs { get; internal set; } + + public KeyValuePair[] Variables { get; internal set; } + + internal MbaCommand() { } + + public void SetOverridableVariables(IOverridableVariables overridableVariables, IEngine engine) + { + foreach (var kvp in this.Variables) + { + if (!overridableVariables.Variables.TryGetValue(kvp.Key, out var overridableVariable)) + { + engine.Log(LogLevel.Error, string.Format("Ignoring attempt to set non-overridable variable: '{0}'.", kvp.Key)); + } + else + { + engine.SetVariableString(overridableVariable.Name, kvp.Value, false); + } + } + } + } +} diff --git a/src/api/burn/WixToolset.Mba.Core/OverridableVariableInfo.cs b/src/api/burn/WixToolset.Mba.Core/OverridableVariableInfo.cs new file mode 100644 index 00000000..b8e85c34 --- /dev/null +++ b/src/api/burn/WixToolset.Mba.Core/OverridableVariableInfo.cs @@ -0,0 +1,15 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. + +namespace WixToolset.Mba.Core +{ + /// + /// Default implementation of . + /// + internal class OverridableVariableInfo : IOverridableVariableInfo + { + /// + public string Name { get; internal set; } + + internal OverridableVariableInfo() { } + } +} diff --git a/src/api/burn/WixToolset.Mba.Core/OverridableVariables.cs b/src/api/burn/WixToolset.Mba.Core/OverridableVariables.cs new file mode 100644 index 00000000..855ce9a9 --- /dev/null +++ b/src/api/burn/WixToolset.Mba.Core/OverridableVariables.cs @@ -0,0 +1,51 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. + +namespace WixToolset.Mba.Core +{ + using System; + using System.Collections.Generic; + using System.Xml; + using System.Xml.XPath; + + /// + /// Default implementation of . + /// + public class OverridableVariablesInfo : IOverridableVariables + { + /// + public IDictionary Variables { get; internal set; } + + internal OverridableVariablesInfo() { } + + /// + /// Parses the overridable variable info from the BA manifest. + /// + /// XML root + /// The parsed information. + public static IOverridableVariables ParseFromXml(XPathNavigator root) + { + XmlNamespaceManager namespaceManager = new XmlNamespaceManager(root.NameTable); + namespaceManager.AddNamespace("p", BootstrapperApplicationData.XMLNamespace); + XPathNodeIterator nodes = root.Select("/p:BootstrapperApplicationData/p:WixStdbaOverridableVariable", namespaceManager); + + var overridableVariables = new OverridableVariablesInfo(); + overridableVariables.Variables = new Dictionary(); + + foreach (XPathNavigator node in nodes) + { + var variable = new OverridableVariableInfo(); + + string name = BootstrapperApplicationData.GetAttribute(node, "Name"); + if (name == null) + { + throw new Exception("Failed to get name for overridable variable."); + } + variable.Name = name; + + overridableVariables.Variables.Add(variable.Name, variable); + } + + return overridableVariables; + } + } +} diff --git a/src/api/burn/balutil/balinfo.cpp b/src/api/burn/balutil/balinfo.cpp index 3abb9286..5927ef72 100644 --- a/src/api/burn/balutil/balinfo.cpp +++ b/src/api/burn/balutil/balinfo.cpp @@ -11,7 +11,88 @@ static HRESULT ParseBalPackageInfoFromXml( __in BAL_INFO_PACKAGES* pPackages, __in IXMLDOMDocument* pixdManifest ); +static HRESULT ParseOverridableVariablesFromXml( + __in BAL_INFO_OVERRIDABLE_VARIABLES* pOverridableVariables, + __in IXMLDOMDocument* pixdManifest + ); + + +DAPI_(HRESULT) BalInfoParseCommandLine( + __in BAL_INFO_COMMAND* pCommand, + __in LPCWSTR wzCommandLine + ) +{ + HRESULT hr = S_OK; + int argc = 0; + LPWSTR* argv = NULL; + BOOL fUnknownArg = FALSE; + + BalInfoUninitializeCommandLine(pCommand); + + if (!wzCommandLine || !*wzCommandLine) + { + ExitFunction(); + } + + hr = AppParseCommandLine(wzCommandLine, &argc, &argv); + ExitOnFailure(hr, "Failed to parse command line."); + for (int i = 0; i < argc; ++i) + { + fUnknownArg = FALSE; + + if (argv[i][0] == L'-' || argv[i][0] == L'/') + { + fUnknownArg = TRUE; + } + else + { + const wchar_t* pwc = wcschr(argv[i], L'='); + if (!pwc) + { + fUnknownArg = TRUE; + } + else + { + hr = MemEnsureArraySizeForNewItems(reinterpret_cast(&pCommand->rgVariableNames), pCommand->cVariables, 1, sizeof(LPWSTR), 5); + ExitOnFailure(hr, "Failed to ensure size for variable names."); + + hr = MemEnsureArraySizeForNewItems(reinterpret_cast(&pCommand->rgVariableValues), pCommand->cVariables, 1, sizeof(LPWSTR), 5); + ExitOnFailure(hr, "Failed to ensure size for variable values."); + + LPWSTR* psczVariableName = pCommand->rgVariableNames + pCommand->cVariables; + LPWSTR* psczVariableValue = pCommand->rgVariableValues + pCommand->cVariables; + pCommand->cVariables += 1; + + hr = StrAllocString(psczVariableName, argv[i], pwc - argv[i]); + BalExitOnFailure(hr, "Failed to copy variable name."); + + hr = StrAllocString(psczVariableValue, ++pwc, 0); + BalExitOnFailure(hr, "Failed to copy variable value."); + } + } + + if (fUnknownArg) + { + hr = MemEnsureArraySizeForNewItems(reinterpret_cast(&pCommand->rgUnknownArgs), pCommand->cUnknownArgs, 1, sizeof(LPWSTR), 5); + BalExitOnFailure(hr, "Failed to ensure size for unknown args."); + + LPWSTR* psczArg = pCommand->rgUnknownArgs + pCommand->cUnknownArgs; + pCommand->cUnknownArgs += 1; + + StrAllocString(psczArg, argv[i], 0); + BalExitOnFailure(hr, "Failed to copy unknown arg."); + } + } + +LExit: + if (argv) + { + AppFreeCommandLineArgs(argv); + } + + return hr; +} DAPI_(HRESULT) BalInfoParseFromXml( __in BAL_INFO_BUNDLE* pBundle, @@ -45,6 +126,9 @@ DAPI_(HRESULT) BalInfoParseFromXml( } } + hr = ParseOverridableVariablesFromXml(&pBundle->overridableVariables, pixdManifest); + BalExitOnFailure(hr, "Failed to parse overridable variables from bootstrapper application data."); + hr = ParsePackagesFromXml(&pBundle->packages, pixdManifest); BalExitOnFailure(hr, "Failed to parse package information from bootstrapper application data."); @@ -163,12 +247,76 @@ DAPI_(void) BalInfoUninitialize( ReleaseMem(pBundle->packages.rgPackages); + for (DWORD i = 0; i < pBundle->overridableVariables.cVariables; ++i) + { + ReleaseStr(pBundle->overridableVariables.rgVariables[i].sczName); + } + + ReleaseMem(pBundle->overridableVariables.rgVariables); + ReleaseDict(pBundle->overridableVariables.sdVariables); + ReleaseStr(pBundle->sczName); ReleaseStr(pBundle->sczLogVariable); memset(pBundle, 0, sizeof(BAL_INFO_BUNDLE)); } +DAPI_(void) BalInfoUninitializeCommandLine( + __in BAL_INFO_COMMAND* pCommand + ) +{ + for (DWORD i = 0; i < pCommand->cUnknownArgs; ++i) + { + ReleaseNullStrSecure(pCommand->rgUnknownArgs[i]); + } + + ReleaseMem(pCommand->rgUnknownArgs); + + for (DWORD i = 0; i < pCommand->cVariables; ++i) + { + ReleaseNullStrSecure(pCommand->rgVariableNames[i]); + ReleaseNullStrSecure(pCommand->rgVariableValues[i]); + } + + ReleaseMem(pCommand->rgVariableNames); + ReleaseMem(pCommand->rgVariableValues); + + memset(pCommand, 0, sizeof(BAL_INFO_COMMAND)); +} + + +DAPI_(HRESULT) BalSetOverridableVariablesFromEngine( + __in BAL_INFO_OVERRIDABLE_VARIABLES* pOverridableVariables, + __in BAL_INFO_COMMAND* pCommand, + __in IBootstrapperEngine* pEngine + ) +{ + HRESULT hr = S_OK; + BAL_INFO_OVERRIDABLE_VARIABLE* pOverridableVariable = NULL; + + for (DWORD i = 0; i < pCommand->cVariables; ++i) + { + LPCWSTR wzVariableName = pCommand->rgVariableNames[i]; + LPCWSTR wzVariableValue = pCommand->rgVariableValues[i]; + + hr = DictGetValue(pOverridableVariables->sdVariables, wzVariableName, reinterpret_cast(&pOverridableVariable)); + if (E_NOTFOUND == hr) + { + BalLog(BOOTSTRAPPER_LOG_LEVEL_ERROR, "Ignoring attempt to set non-overridable variable: '%ls'.", wzVariableName); + hr = S_OK; + continue; + } + BalExitOnFailure(hr, "Failed to check the dictionary of overridable variables."); + + hr = pEngine->SetVariableString(pOverridableVariable->sczName, wzVariableValue, FALSE); + BalExitOnFailure(hr, "Failed to set variable: '%ls'.", pOverridableVariable->sczName); + } + +LExit: + return hr; +} + + static HRESULT ParsePackagesFromXml( __in BAL_INFO_PACKAGES* pPackages, __in IXMLDOMDocument* pixdManifest @@ -371,3 +519,58 @@ LExit: return hr; } + + +static HRESULT ParseOverridableVariablesFromXml( + __in BAL_INFO_OVERRIDABLE_VARIABLES* pOverridableVariables, + __in IXMLDOMDocument* pixdManifest + ) +{ + HRESULT hr = S_OK; + IXMLDOMNode* pNode = NULL; + IXMLDOMNodeList* pNodes = NULL; + BAL_INFO_OVERRIDABLE_VARIABLE* pOverridableVariable = NULL; + + // Get the list of variables users can override on the command line. + hr = XmlSelectNodes(pixdManifest, L"/BootstrapperApplicationData/WixStdbaOverridableVariable", &pNodes); + if (S_FALSE == hr) + { + ExitFunction1(hr = S_OK); + } + ExitOnFailure(hr, "Failed to select overridable variable nodes."); + + hr = pNodes->get_length(reinterpret_cast(&pOverridableVariables->cVariables)); + ExitOnFailure(hr, "Failed to get overridable variable node count."); + + if (pOverridableVariables->cVariables) + { + hr = DictCreateWithEmbeddedKey(&pOverridableVariables->sdVariables, pOverridableVariables->cVariables, reinterpret_cast(&pOverridableVariables->rgVariables), offsetof(BAL_INFO_OVERRIDABLE_VARIABLE, sczName), DICT_FLAG_NONE); + ExitOnFailure(hr, "Failed to create the overridable variables string dictionary."); + + hr = MemAllocArray(reinterpret_cast(&pOverridableVariables->rgVariables), sizeof(pOverridableVariable), pOverridableVariables->cVariables); + ExitOnFailure(hr, "Failed to create the overridable variables array."); + + for (DWORD i = 0; i < pOverridableVariables->cVariables; ++i) + { + pOverridableVariable = pOverridableVariables->rgVariables + i; + + hr = XmlNextElement(pNodes, &pNode, NULL); + ExitOnFailure(hr, "Failed to get next node."); + + // @Name + hr = XmlGetAttributeEx(pNode, L"Name", &pOverridableVariable->sczName); + ExitOnFailure(hr, "Failed to get name for overridable variable."); + + hr = DictAddValue(pOverridableVariables->sdVariables, pOverridableVariable); + ExitOnFailure(hr, "Failed to add \"%ls\" to the string dictionary.", pOverridableVariable->sczName); + + // prepare next iteration + ReleaseNullObject(pNode); + } + } + +LExit: + 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 8c2155e9..0fce35ec 100644 --- a/src/api/burn/balutil/inc/balinfo.h +++ b/src/api/burn/balutil/inc/balinfo.h @@ -47,15 +47,50 @@ typedef struct _BAL_INFO_PACKAGES } BAL_INFO_PACKAGES; +typedef struct _BAL_INFO_OVERRIDABLE_VARIABLE +{ + LPWSTR sczName; +} BAL_INFO_OVERRIDABLE_VARIABLE; + + +typedef struct _BAL_INFO_OVERRIDABLE_VARIABLES +{ + BAL_INFO_OVERRIDABLE_VARIABLE* rgVariables; + DWORD cVariables; + STRINGDICT_HANDLE sdVariables; +} BAL_INFO_OVERRIDABLE_VARIABLES; + + typedef struct _BAL_INFO_BUNDLE { BOOL fPerMachine; LPWSTR sczName; LPWSTR sczLogVariable; BAL_INFO_PACKAGES packages; + BAL_INFO_OVERRIDABLE_VARIABLES overridableVariables; } BAL_INFO_BUNDLE; +typedef struct _BAL_INFO_COMMAND +{ + DWORD cUnknownArgs; + LPWSTR* rgUnknownArgs; + DWORD cVariables; + LPWSTR* rgVariableNames; + LPWSTR* rgVariableValues; +} BAL_INFO_COMMAND; + + +/******************************************************************* + BalInfoParseCommandLine - parses wzCommandLine from BOOTSTRAPPER_COMMAND. + +********************************************************************/ +HRESULT DAPI BalInfoParseCommandLine( + __in BAL_INFO_COMMAND* pCommand, + __in LPCWSTR wzCommandLine + ); + + /******************************************************************* BalInfoParseFromXml - loads the bundle and package info from the UX manifest. @@ -100,6 +135,26 @@ DAPI_(void) BalInfoUninitialize( ); +/******************************************************************* + BalInfoUninitializeCommandLine - uninitializes BAL_INFO_COMMAND. + +********************************************************************/ +void DAPI BalInfoUninitializeCommandLine( + __in BAL_INFO_COMMAND* pCommand +); + + +/******************************************************************* + BalInfoSetOverridableVariablesFromEngine - sets overridable variables from command line. + + ********************************************************************/ +HRESULT DAPI BalSetOverridableVariablesFromEngine( + __in BAL_INFO_OVERRIDABLE_VARIABLES* pOverridableVariables, + __in BAL_INFO_COMMAND* pCommand, + __in IBootstrapperEngine* pEngine + ); + + #ifdef __cplusplus } #endif diff --git a/src/api/burn/balutil/precomp.h b/src/api/burn/balutil/precomp.h index 15142210..64d4a6cf 100644 --- a/src/api/burn/balutil/precomp.h +++ b/src/api/burn/balutil/precomp.h @@ -16,6 +16,7 @@ #include #include +#include #include #include #include diff --git a/src/api/burn/test/WixToolsetTest.Mba.Core/BaseBootstrapperApplicationFactoryFixture.cs b/src/api/burn/test/WixToolsetTest.Mba.Core/BaseBootstrapperApplicationFactoryFixture.cs index aaf5ee29..b49b3927 100644 --- a/src/api/burn/test/WixToolsetTest.Mba.Core/BaseBootstrapperApplicationFactoryFixture.cs +++ b/src/api/burn/test/WixToolsetTest.Mba.Core/BaseBootstrapperApplicationFactoryFixture.cs @@ -3,6 +3,7 @@ namespace WixToolsetTest.Mba.Core { using System; + using System.Collections.Generic; using System.Runtime.InteropServices; using WixToolset.Mba.Core; using Xunit; @@ -17,7 +18,7 @@ namespace WixToolsetTest.Mba.Core action = LaunchAction.Install, cbSize = Marshal.SizeOf(typeof(TestCommand)), display = Display.Full, - wzCommandLine = "this \"is a\" test", + wzCommandLine = "this \"is a\" test VariableA=AVariable =EmptyName EmptyValue=", }; var pCommand = Marshal.AllocHGlobal(command.cbSize); try @@ -42,7 +43,15 @@ namespace WixToolsetTest.Mba.Core Assert.Equal(baFactory.BA, createResults.pBA); Assert.Equal(baFactory.BA.Command.Action, command.action); Assert.Equal(baFactory.BA.Command.Display, command.display); - Assert.Equal(baFactory.BA.Command.CommandLineArgs, new string[] { "this", "is a", "test" }); + + var mbaCommand = baFactory.BA.Command.ParseCommandLine(); + Assert.Equal(mbaCommand.UnknownCommandLineArgs, new string[] { "this", "is a", "test" }); + Assert.Equal(mbaCommand.Variables, new KeyValuePair[] + { + new KeyValuePair("VariableA", "AVariable"), + new KeyValuePair("", "EmptyName"), + new KeyValuePair("EmptyValue", ""), + }); } finally { diff --git a/src/ext/Bal/wixstdba/WixStandardBootstrapperApplication.cpp b/src/ext/Bal/wixstdba/WixStandardBootstrapperApplication.cpp index 62cdc70a..9f0b0082 100644 --- a/src/ext/Bal/wixstdba/WixStandardBootstrapperApplication.cpp +++ b/src/ext/Bal/wixstdba/WixStandardBootstrapperApplication.cpp @@ -2095,8 +2095,8 @@ private: // privates hr = BalManifestLoad(m_hModule, &pixdManifest); BalExitOnFailure(hr, "Failed to load bootstrapper application manifest."); - hr = ParseOverridableVariablesFromXml(pixdManifest); - BalExitOnFailure(hr, "Failed to read overridable variables."); + hr = BalInfoParseFromXml(&m_Bundle, pixdManifest); + BalExitOnFailure(hr, "Failed to load bundle information."); hr = ProcessCommandLine(&m_sczLanguage); ExitOnFailure(hr, "Unknown commandline parameters."); @@ -2110,9 +2110,6 @@ private: // privates hr = LoadTheme(sczModulePath, m_sczLanguage); ExitOnFailure(hr, "Failed to load theme."); - hr = BalInfoParseFromXml(&m_Bundle, pixdManifest); - BalExitOnFailure(hr, "Failed to load bundle information."); - hr = BalConditionsParseFromXml(&m_Conditions, pixdManifest, m_pWixLoc); BalExitOnFailure(hr, "Failed to load conditions from XML."); @@ -2173,16 +2170,20 @@ private: // privates HRESULT hr = S_OK; int argc = 0; LPWSTR* argv = NULL; - LPWSTR sczVariableName = NULL; - LPWSTR sczVariableValue = NULL; + BOOL fUnknownArg = FALSE; - if (m_command.wzCommandLine && *m_command.wzCommandLine) - { - hr = AppParseCommandLine(m_command.wzCommandLine, &argc, &argv); - ExitOnFailure(hr, "Failed to parse command line."); + hr = BalInfoParseCommandLine(&m_BalInfoCommand, m_command.wzCommandLine); + BalExitOnFailure(hr, "Failed to parse command line with balutil."); + + argc = m_BalInfoCommand.cUnknownArgs; + argv = m_BalInfoCommand.rgUnknownArgs; + if (argc) + { for (int i = 0; i < argc; ++i) { + fUnknownArg = FALSE; + if (argv[i][0] == L'-' || argv[i][0] == L'/') { if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, NORM_IGNORECASE, &argv[i][1], -1, L"lang", -1)) @@ -2198,51 +2199,31 @@ private: // privates hr = StrAllocString(psczLanguage, &argv[i][0], 0); BalExitOnFailure(hr, "Failed to copy language."); } - } - else if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, NORM_IGNORECASE, &argv[i][1], -1, L"cache", -1)) - { - m_plannedAction = BOOTSTRAPPER_ACTION_CACHE; - } - else if (m_sdOverridableVariables) - { - const wchar_t* pwc = wcschr(argv[i], L'='); - if (pwc) + else if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, NORM_IGNORECASE, &argv[i][1], -1, L"cache", -1)) { - hr = StrAllocString(&sczVariableName, argv[i], pwc - argv[i]); - BalExitOnFailure(hr, "Failed to copy variable name."); - - hr = DictKeyExists(m_sdOverridableVariables, sczVariableName); - if (E_NOTFOUND == hr) - { - BalLog(BOOTSTRAPPER_LOG_LEVEL_ERROR, "Ignoring attempt to set non-overridable variable: '%ls'.", sczVariableName); - hr = S_OK; - continue; - } - ExitOnFailure(hr, "Failed to check the dictionary of overridable variables."); - - hr = StrAllocString(&sczVariableValue, ++pwc, 0); - BalExitOnFailure(hr, "Failed to copy variable value."); - - hr = m_pEngine->SetVariableString(sczVariableName, sczVariableValue, FALSE); - BalExitOnFailure(hr, "Failed to set variable."); + m_plannedAction = BOOTSTRAPPER_ACTION_CACHE; } else { - BalLog(BOOTSTRAPPER_LOG_LEVEL_STANDARD, "Ignoring unknown argument: %ls", argv[i]); + fUnknownArg = TRUE; } } - } - } + else + { + fUnknownArg = TRUE; + } - LExit: - if (argv) - { - AppFreeCommandLineArgs(argv); + if (fUnknownArg) + { + BalLog(BOOTSTRAPPER_LOG_LEVEL_STANDARD, "Ignoring unknown argument: %ls", argv[i]); + } + } } - ReleaseStr(sczVariableName); - ReleaseStr(sczVariableValue); + hr = BalSetOverridableVariablesFromEngine(&m_Bundle.overridableVariables, &m_BalInfoCommand, m_pEngine); + BalExitOnFailure(hr, "Failed to set overridable variables from the command line."); + LExit: return hr; } @@ -2323,57 +2304,6 @@ private: // privates } - HRESULT ParseOverridableVariablesFromXml( - __in IXMLDOMDocument* pixdManifest - ) - { - HRESULT hr = S_OK; - IXMLDOMNode* pNode = NULL; - IXMLDOMNodeList* pNodes = NULL; - DWORD cNodes = 0; - LPWSTR scz = NULL; - - // Get the list of variables users can override on the command line. - hr = XmlSelectNodes(pixdManifest, L"/BootstrapperApplicationData/WixStdbaOverridableVariable", &pNodes); - if (S_FALSE == hr) - { - ExitFunction1(hr = S_OK); - } - ExitOnFailure(hr, "Failed to select overridable variable nodes."); - - hr = pNodes->get_length((long*)&cNodes); - ExitOnFailure(hr, "Failed to get overridable variable node count."); - - if (cNodes) - { - hr = DictCreateStringList(&m_sdOverridableVariables, 32, DICT_FLAG_NONE); - ExitOnFailure(hr, "Failed to create the string dictionary."); - - for (DWORD i = 0; i < cNodes; ++i) - { - hr = XmlNextElement(pNodes, &pNode, NULL); - ExitOnFailure(hr, "Failed to get next node."); - - // @Name - hr = XmlGetAttributeEx(pNode, L"Name", &scz); - ExitOnFailure(hr, "Failed to get @Name."); - - hr = DictAddKey(m_sdOverridableVariables, scz); - ExitOnFailure(hr, "Failed to add \"%ls\" to the string dictionary.", scz); - - // prepare next iteration - ReleaseNullObject(pNode); - } - } - - LExit: - ReleaseObject(pNode); - ReleaseObject(pNodes); - ReleaseStr(scz); - return hr; - } - - HRESULT InitializePackageInfo() { HRESULT hr = S_OK; @@ -3951,6 +3881,7 @@ public: m_pWixLoc = NULL; memset(&m_Bundle, 0, sizeof(m_Bundle)); + memset(&m_BalInfoCommand, 0, sizeof(m_BalInfoCommand)); memset(&m_Conditions, 0, sizeof(m_Conditions)); m_sczConfirmCloseMessage = NULL; m_sczFailedMessage = NULL; @@ -3977,7 +3908,6 @@ public: m_fSuppressRepair = FALSE; m_fSupportCacheOnly = FALSE; - m_sdOverridableVariables = NULL; m_pTaskbarList = NULL; m_uTaskbarButtonCreatedMessage = UINT_MAX; m_fTaskbarButtonOK = FALSE; @@ -4013,11 +3943,11 @@ public: } ::DeleteCriticalSection(&m_csShowingInternalUiThisPackage); - ReleaseDict(m_sdOverridableVariables); ReleaseStr(m_sczFailedMessage); ReleaseStr(m_sczConfirmCloseMessage); BalConditionsUninitialize(&m_Conditions); BalInfoUninitialize(&m_Bundle); + BalInfoUninitializeCommandLine(&m_BalInfoCommand); LocFree(m_pWixLoc); ReleaseStr(m_sczLanguage); @@ -4050,6 +3980,7 @@ private: WIX_LOCALIZATION* m_pWixLoc; BAL_INFO_BUNDLE m_Bundle; + BAL_INFO_COMMAND m_BalInfoCommand; BAL_CONDITIONS m_Conditions; LPWSTR m_sczFailedMessage; LPWSTR m_sczConfirmCloseMessage; @@ -4080,8 +4011,6 @@ private: BOOL m_fSuppressRepair; BOOL m_fSupportCacheOnly; - STRINGDICT_HANDLE m_sdOverridableVariables; - BOOL m_fPrereq; BOOL m_fPrereqInstalled; BOOL m_fPrereqAlreadyInstalled; diff --git a/src/test/burn/TestBA/TestBA.cs b/src/test/burn/TestBA/TestBA.cs index 09378bc5..5c70253d 100644 --- a/src/test/burn/TestBA/TestBA.cs +++ b/src/test/burn/TestBA/TestBA.cs @@ -74,8 +74,6 @@ namespace WixToolset.Test.BA return; } - base.OnStartup(args); - this.action = this.Command.Action; this.TestVariables(); @@ -84,7 +82,7 @@ namespace WixToolset.Test.BA List verifyArguments = this.ReadVerifyArguments(); - foreach (string arg in this.Command.CommandLineArgs) + foreach (string arg in BootstrapperCommand.ParseCommandLineToArgs(this.Command.CommandLine)) { // If we're not in the update already, process the updatebundle. if (this.Command.Relation != RelationType.Update && arg.StartsWith("-updatebundle:", StringComparison.OrdinalIgnoreCase)) @@ -116,6 +114,8 @@ namespace WixToolset.Test.BA return; } + base.OnStartup(args); + int redetectCount; string redetect = this.ReadPackageAction(null, "RedetectCount"); if (String.IsNullOrEmpty(redetect) || !Int32.TryParse(redetect, out redetectCount)) diff --git a/src/test/burn/WixToolset.WixBA/InstallationViewModel.cs b/src/test/burn/WixToolset.WixBA/InstallationViewModel.cs index 410ab110..14ea561f 100644 --- a/src/test/burn/WixToolset.WixBA/InstallationViewModel.cs +++ b/src/test/burn/WixToolset.WixBA/InstallationViewModel.cs @@ -256,7 +256,7 @@ namespace WixToolset.WixBA public bool IsComplete { - get { return IsSuccessfulCompletion || IsFailedCompletion; } + get { return this.IsSuccessfulCompletion || this.IsFailedCompletion; } } public bool IsSuccessfulCompletion @@ -358,7 +358,7 @@ namespace WixToolset.WixBA { this.root.Canceled = false; WixBA.Plan(WixBA.Model.PlannedAction); - }, param => IsFailedCompletion); + }, param => this.IsFailedCompletion); } return this.tryAgainCommand; @@ -456,7 +456,7 @@ namespace WixToolset.WixBA // Force all commands to reevaluate CanExecute. // InvalidateRequerySuggested must be run on the UI thread. - root.Dispatcher.Invoke(new Action(CommandManager.InvalidateRequerySuggested)); + this.root.Dispatcher.Invoke(new Action(CommandManager.InvalidateRequerySuggested)); } private void PlanPackageBegin(object sender, PlanPackageBeginEventArgs e) @@ -639,7 +639,7 @@ namespace WixToolset.WixBA WixBA.Dispatcher.BeginInvoke(new Action(WixBA.View.Close)); return; } - else if (root.AutoClose) + else if (this.root.AutoClose) { // Automatically closing since the user clicked the X button. WixBA.Dispatcher.BeginInvoke(new Action(WixBA.View.Close)); @@ -648,13 +648,13 @@ namespace WixToolset.WixBA // Force all commands to reevaluate CanExecute. // InvalidateRequerySuggested must be run on the UI thread. - root.Dispatcher.Invoke(new Action(CommandManager.InvalidateRequerySuggested)); + this.root.Dispatcher.Invoke(new Action(CommandManager.InvalidateRequerySuggested)); } private void ParseCommandLine() { // Get array of arguments based on the system parsing algorithm. - string[] args = WixBA.Model.Command.CommandLineArgs; + string[] args = BootstrapperCommand.ParseCommandLineToArgs(WixBA.Model.Command.CommandLine); for (int i = 0; i < args.Length; ++i) { if (args[i].StartsWith("InstallFolder=", StringComparison.InvariantCultureIgnoreCase)) -- cgit v1.2.3-55-g6feb