commit 45e2dcf91302f6eb7bfb071a6df5171852cc7d36 parent 0f07ab5de5ae4226d2be4d91244827d8c504f807 Author: yao-msft <50888816+yao-msft@users.noreply.github.com> Date: Fri, 14 Feb 2020 12:29:03 -0800 Add support for more installer types and implement installer args logic (#35) * Manifest modified, other types added * Installer args logic works. * Fix existing tests * Added installer args tests * Minor tweaks * PR comments * One more PR fix * PR comments Diffstat:
35 files changed, 590 insertions(+), 179 deletions(-)
diff --git a/src/AppInstallerCLICore/Commands/Common.h b/src/AppInstallerCLICore/Commands/Common.h @@ -10,4 +10,10 @@ namespace AppInstaller::CLI { static constexpr std::string_view ARG_APPLICATION = "application"sv; static constexpr std::string_view ARG_MANIFEST = "manifest"sv; + static constexpr std::string_view ARG_INTERACTIVE = "interactive"sv; + static constexpr std::string_view ARG_SILENT = "silent"sv; + static constexpr std::string_view ARG_LANGUAGE = "language"sv; + static constexpr std::string_view ARG_LOG = "log"sv; + static constexpr std::string_view ARG_OVERRIDE = "override"sv; + static constexpr std::string_view ARG_INSTALLLOCATION = "installlocation"sv; } \ No newline at end of file diff --git a/src/AppInstallerCLICore/Commands/InstallCommand.cpp b/src/AppInstallerCLICore/Commands/InstallCommand.cpp @@ -17,6 +17,11 @@ namespace AppInstaller::CLI return { Argument{ ARG_APPLICATION, LOCME("The name of the application to install"), ArgumentType::Positional, false }, Argument{ ARG_MANIFEST, LOCME("The path to the manifest of the application to install"), ArgumentType::Standard, false }, + Argument{ ARG_INTERACTIVE, LOCME("The application installation is interactive. User input is needed."), ArgumentType::Flag, false }, + Argument{ ARG_SILENT, LOCME("The application installation is silent."), ArgumentType::Flag, false }, + Argument{ ARG_LANGUAGE, LOCME("Preferred language if application installation supports multiple languages."), ArgumentType::Standard, false }, + Argument{ ARG_LOG, LOCME("Preferred log location if application installation supports custom log path."), ArgumentType::Standard, false }, + Argument{ ARG_OVERRIDE, LOCME("Override switches to be passed on to application installer."), ArgumentType::Standard, false }, }; } @@ -41,7 +46,7 @@ namespace AppInstaller::CLI Logging::Telemetry().LogManifestFields(packageManifest.Name, packageManifest.Version); - InstallFlow packageInstall(packageManifest, out, in); + InstallFlow packageInstall(packageManifest, inv, out, in); packageInstall.Install(); } else @@ -58,5 +63,10 @@ namespace AppInstaller::CLI { throw CommandException(LOCME("Required argument not provided"), ARG_APPLICATION); } + + if (inv.Contains(ARG_SILENT) && inv.Contains(ARG_INTERACTIVE)) + { + throw CommandException(LOCME("More than one install behavior argument provided"), ARG_APPLICATION); + } } } diff --git a/src/AppInstallerCLICore/Workflows/InstallFlow.cpp b/src/AppInstallerCLICore/Workflows/InstallFlow.cpp @@ -47,9 +47,14 @@ namespace AppInstaller::Workflow { switch (m_selectedInstaller.InstallerType) { case ManifestInstaller::InstallerTypeEnum::Exe: - return std::make_unique<ShellExecuteInstallerHandler>(m_selectedInstaller, m_reporter); + case ManifestInstaller::InstallerTypeEnum::Burn: + case ManifestInstaller::InstallerTypeEnum::Inno: + case ManifestInstaller::InstallerTypeEnum::Msi: + case ManifestInstaller::InstallerTypeEnum::Nullsoft: + case ManifestInstaller::InstallerTypeEnum::Wix: + return std::make_unique<ShellExecuteInstallerHandler>(m_selectedInstaller, m_argsRef, m_reporter); case ManifestInstaller::InstallerTypeEnum::Msix: - return std::make_unique<MsixInstallerHandler>(m_selectedInstaller, m_reporter); + return std::make_unique<MsixInstallerHandler>(m_selectedInstaller, m_argsRef, m_reporter); default: THROW_HR(HRESULT_FROM_WIN32(ERROR_NOT_SUPPORTED)); } diff --git a/src/AppInstallerCLICore/Workflows/InstallFlow.h b/src/AppInstallerCLICore/Workflows/InstallFlow.h @@ -3,6 +3,7 @@ #pragma once #include "Common.h" +#include "Invocation.h" #include "InstallerHandlerBase.h" #include "WorkflowReporter.h" @@ -11,8 +12,8 @@ namespace AppInstaller::Workflow class InstallFlow { public: - InstallFlow(AppInstaller::Manifest::Manifest manifest, std::ostream& outStream, std::istream& inStream) : - m_packageManifest(manifest), m_reporter(outStream, inStream) {} + InstallFlow(AppInstaller::Manifest::Manifest manifest, const AppInstaller::CLI::Invocation& args, std::ostream& outStream, std::istream& inStream) : + m_packageManifest(manifest), m_reporter(outStream, inStream), m_argsRef(args) {} void Install(); @@ -21,6 +22,7 @@ namespace AppInstaller::Workflow AppInstaller::Manifest::ManifestInstaller m_selectedInstaller; AppInstaller::Manifest::ManifestLocalization m_selectedLocalization; WorkflowReporter m_reporter; + const AppInstaller::CLI::Invocation& m_argsRef; virtual void ProcessManifest(); diff --git a/src/AppInstallerCLICore/Workflows/InstallerHandlerBase.cpp b/src/AppInstallerCLICore/Workflows/InstallerHandlerBase.cpp @@ -45,7 +45,7 @@ namespace AppInstaller::Workflow AICLI_LOG(CLI, Error, << "Package hash verification failed. SHA256 in manifest: " << SHA256::ConvertToString(m_manifestInstallerRef.Sha256) - << "SHA256 from download: " + << " SHA256 from download: " << SHA256::ConvertToString(downloader->GetDownloadHash())); if (!m_reporterRef.PromptForBoolResponse(WorkflowReporter::Level::Warning, "Package hash verification failed. Continue?")) @@ -63,27 +63,55 @@ namespace AppInstaller::Workflow m_downloadedInstaller = tempInstallerPath; } - void InstallerHandlerBase::DownloaderCallback::OnStarted() + void InstallerHandlerBase::DownloaderCallback::OnStarted(LONGLONG totalBytes) { - m_reporterRef.ShowMsg(WorkflowReporter::Level::Info, "Starting package download ..."); - m_reporterRef.ShowProgress(true, 0); + m_reporterRef.ShowMsg(WorkflowReporter::Level::Info, "Starting installer download ..."); + m_useProgressBar = totalBytes > 0; + + if (m_useProgressBar) + { + m_reporterRef.ShowProgress(true, 0); + } + else + { + m_reporterRef.ShowIndefiniteProgress(true); + } } - void InstallerHandlerBase::DownloaderCallback::OnProgress(LONGLONG progress, LONGLONG downloadSize) + void InstallerHandlerBase::DownloaderCallback::OnProgress(LONGLONG bytesDownloaded, LONGLONG totalBytes) { - int progressPercent = static_cast<int>(100 * progress / downloadSize); - m_reporterRef.ShowProgress(true, progressPercent); + if (m_useProgressBar) + { + int progressPercent = static_cast<int>(100 * bytesDownloaded / totalBytes); + m_reporterRef.ShowProgress(true, progressPercent); + } } void InstallerHandlerBase::DownloaderCallback::OnCanceled() { - m_reporterRef.ShowProgress(false, 0); - m_reporterRef.ShowMsg(WorkflowReporter::Level::Warning, "Package download canceled."); + if (m_useProgressBar) + { + m_reporterRef.ShowProgress(false, 0); + } + else + { + m_reporterRef.ShowIndefiniteProgress(false); + } + + m_reporterRef.ShowMsg(WorkflowReporter::Level::Warning, "Installer download canceled."); } void InstallerHandlerBase::DownloaderCallback::OnCompleted() { - m_reporterRef.ShowProgress(false, 0); - m_reporterRef.ShowMsg(WorkflowReporter::Level::Error, "Package download completed."); + if (m_useProgressBar) + { + m_reporterRef.ShowProgress(false, 0); + } + else + { + m_reporterRef.ShowIndefiniteProgress(false); + } + + m_reporterRef.ShowMsg(WorkflowReporter::Level::Error, "Installer download completed."); } } \ No newline at end of file diff --git a/src/AppInstallerCLICore/Workflows/InstallerHandlerBase.h b/src/AppInstallerCLICore/Workflows/InstallerHandlerBase.h @@ -3,10 +3,17 @@ #pragma once #include "pch.h" +#include "Invocation.h" #include "WorkflowReporter.h" namespace AppInstaller::Workflow { + using namespace std::string_view_literals; + + // Token specified in installer args will be replaced by proper value. + static constexpr std::string_view ARG_TOKEN_LOGPATH = "<LOGPATH>"sv; + static constexpr std::string_view ARG_TOKEN_INSTALLPATH = "<INSTALLPATH>"sv; + // This is the base class for installer handlers. Individual installer handler should override // member methods to do appropriate work on different installers. class InstallerHandlerBase @@ -29,19 +36,26 @@ namespace AppInstaller::Workflow public: DownloaderCallback(WorkflowReporter& reporter) : m_reporterRef(reporter) {}; - void OnStarted() override; - void OnProgress(LONGLONG progress, LONGLONG downloadSize) override; + void OnStarted(LONGLONG totalBytes) override; + void OnProgress(LONGLONG bytesDownloaded, LONGLONG totalBytes) override; void OnCanceled() override; void OnCompleted() override; private: WorkflowReporter& m_reporterRef; + + // This determines if definite progress bar or indefinite progress bar should be shown. + bool m_useProgressBar = true; }; - InstallerHandlerBase(const Manifest::ManifestInstaller& manifestInstaller, WorkflowReporter& reporter) : - m_manifestInstallerRef(manifestInstaller), m_reporterRef(reporter), m_downloaderCallback(reporter) {}; + InstallerHandlerBase( + const Manifest::ManifestInstaller& manifestInstaller, + const CLI::Invocation& args, + WorkflowReporter& reporter) : + m_manifestInstallerRef(manifestInstaller), m_reporterRef(reporter), m_downloaderCallback(reporter), m_argsRef(args) {}; const Manifest::ManifestInstaller& m_manifestInstallerRef; + const CLI::Invocation& m_argsRef; WorkflowReporter& m_reporterRef; std::filesystem::path m_downloadedInstaller; DownloaderCallback m_downloaderCallback; diff --git a/src/AppInstallerCLICore/Workflows/MsixInstallerHandler.cpp b/src/AppInstallerCLICore/Workflows/MsixInstallerHandler.cpp @@ -12,17 +12,6 @@ using namespace AppInstaller::Manifest; namespace AppInstaller::Workflow { - MsixInstallerHandler::MsixInstallerHandler( - const Manifest::ManifestInstaller& manifestInstaller, - WorkflowReporter& reporter) : - InstallerHandlerBase(manifestInstaller, reporter) - { - if (manifestInstaller.InstallerType != ManifestInstaller::InstallerTypeEnum::Msix) - { - THROW_HR_MSG(E_UNEXPECTED, "Installer type not supported."); - } - } - void MsixInstallerHandler::Download() { if (m_manifestInstallerRef.SignatureSha256.empty()) diff --git a/src/AppInstallerCLICore/Workflows/MsixInstallerHandler.h b/src/AppInstallerCLICore/Workflows/MsixInstallerHandler.h @@ -12,7 +12,9 @@ namespace AppInstaller::Workflow public: MsixInstallerHandler( const Manifest::ManifestInstaller& manifestInstaller, - WorkflowReporter& reporter); + const CLI::Invocation& args, + WorkflowReporter& reporter) : + InstallerHandlerBase(manifestInstaller, args, reporter) {} // Download method just checks installer signature hash if signature hash // is provided in the manifest. Otherwise, Download will download the whole diff --git a/src/AppInstallerCLICore/Workflows/ShellExecuteInstallerHandler.cpp b/src/AppInstallerCLICore/Workflows/ShellExecuteInstallerHandler.cpp @@ -3,6 +3,7 @@ #include "pch.h" #include "Common.h" +#include "Commands/Common.h" #include "ShellExecuteInstallerHandler.h" using namespace AppInstaller::Utility; @@ -10,19 +11,6 @@ using namespace AppInstaller::Manifest; namespace AppInstaller::Workflow { - ShellExecuteInstallerHandler::ShellExecuteInstallerHandler( - const Manifest::ManifestInstaller& manifestInstaller, - WorkflowReporter& reporter) : - InstallerHandlerBase(manifestInstaller, reporter) - { - // Todo: add support for other installer types. - // This Installer Handler should support Inno, Wix, Nullsoft, Msi and Exe. - if (manifestInstaller.InstallerType != ManifestInstaller::InstallerTypeEnum::Exe) - { - THROW_HR_MSG(E_UNEXPECTED, "Installer type not supported."); - } - } - void ShellExecuteInstallerHandler::Install() { if (m_downloadedInstaller.empty()) @@ -60,7 +48,7 @@ namespace AppInstaller::Workflow std::future<DWORD> ShellExecuteInstallerHandler::ExecuteInstallerAsync(const std::filesystem::path& filePath, const std::string& args) { AICLI_LOG(CLI, Info, << "Staring installer. Path: " << filePath); - return std::async(std::launch::async, [&filePath, &args] + return std::async(std::launch::async, [this, filePath, args] { SHELLEXECUTEINFOA execInfo = { 0 }; execInfo.cbSize = sizeof(SHELLEXECUTEINFO); @@ -68,7 +56,7 @@ namespace AppInstaller::Workflow std::string filePathUTF8Str = Utility::ConvertToUTF8(filePath.c_str()); execInfo.lpFile = filePathUTF8Str.c_str(); execInfo.lpParameters = args.c_str(); - execInfo.nShow = SW_SHOW; + execInfo.nShow = m_argsRef.Contains(CLI::ARG_INTERACTIVE) ? SW_SHOW : SW_HIDE; if (!ShellExecuteExA(&execInfo) || !execInfo.hProcess) { return GetLastError(); @@ -87,24 +75,103 @@ namespace AppInstaller::Workflow }); } + std::string ShellExecuteInstallerHandler::GetInstallerArgsTemplate() + { + std::string installerArgs = ""; + const std::map<ManifestInstaller::InstallerSwitchType, std::string>& installerSwitches = m_manifestInstallerRef.Switches; + + // Construct install experience arg. + if (m_argsRef.Contains(CLI::ARG_SILENT) && installerSwitches.find(ManifestInstaller::InstallerSwitchType::Silent) != installerSwitches.end()) + { + installerArgs += installerSwitches.at(ManifestInstaller::InstallerSwitchType::Silent); + } + else if (m_argsRef.Contains(CLI::ARG_INTERACTIVE) && installerSwitches.find(ManifestInstaller::InstallerSwitchType::Interactive) != installerSwitches.end()) + { + installerArgs += installerSwitches.at(ManifestInstaller::InstallerSwitchType::Interactive); + } + else if (installerSwitches.find(ManifestInstaller::InstallerSwitchType::SilentWithProgress) != installerSwitches.end()) + { + installerArgs += installerSwitches.at(ManifestInstaller::InstallerSwitchType::SilentWithProgress); + } + + // Construct language arg if necessary. + if (m_argsRef.Contains(CLI::ARG_LANGUAGE) && installerSwitches.find(ManifestInstaller::InstallerSwitchType::Language) != installerSwitches.end()) + { + installerArgs += ' ' + installerSwitches.at(ManifestInstaller::InstallerSwitchType::Language); + } + + // Construct install location arg if necessary. + if (m_argsRef.Contains(CLI::ARG_INSTALLLOCATION) && installerSwitches.find(ManifestInstaller::InstallerSwitchType::InstallLocation) != installerSwitches.end()) + { + installerArgs += ' ' + installerSwitches.at(ManifestInstaller::InstallerSwitchType::InstallLocation); + } + + // Construct log path arg. + if (installerSwitches.find(ManifestInstaller::InstallerSwitchType::Log) != installerSwitches.end()) + { + installerArgs += ' ' + installerSwitches.at(ManifestInstaller::InstallerSwitchType::Log); + } + + // Construct custom arg. + if (installerSwitches.find(ManifestInstaller::InstallerSwitchType::Custom) != installerSwitches.end()) + { + installerArgs += ' ' + installerSwitches.at(ManifestInstaller::InstallerSwitchType::Custom); + } + + return installerArgs; + } + + void ShellExecuteInstallerHandler::PopulateInstallerArgsTemplate(std::string& installerArgs) + { + // Populate <LogPath> with value from command line or temp path. + std::string logPath; + if (m_argsRef.Contains(CLI::ARG_LOG)) + { + logPath = *m_argsRef.GetArg(CLI::ARG_LOG); + } + else + { + logPath = Utility::ConvertToUTF8(m_downloadedInstaller.c_str()) + ".log"; + } + Utility::FindAndReplace(installerArgs, std::string(ARG_TOKEN_LOGPATH), logPath); + + // Populate <InstallPath> with value from command line or current path. + Utility::FindAndReplace(installerArgs, std::string(ARG_TOKEN_INSTALLPATH), *m_argsRef.GetArg(CLI::ARG_INSTALLLOCATION)); + + // Todo: language token support will be implemented later + } + std::string ShellExecuteInstallerHandler::GetInstallerArgs() { - // Todo: Implement arg selection logic. - if (m_manifestInstallerRef.Switches.has_value()) + // If override switch is specified, use the override value as installer args. + if (m_argsRef.Contains(CLI::ARG_OVERRIDE)) { - return m_manifestInstallerRef.Switches.value().Default; + return *m_argsRef.GetArg(CLI::ARG_OVERRIDE); } - return ""; + std::string installerArgs = GetInstallerArgsTemplate(); + + PopulateInstallerArgsTemplate(installerArgs); + + return installerArgs; } void ShellExecuteInstallerHandler::RenameDownloadedInstaller() { std::filesystem::path renamedDownloadedInstaller(m_downloadedInstaller); - if (m_manifestInstallerRef.InstallerType == ManifestInstaller::InstallerTypeEnum::Exe) + switch(m_manifestInstallerRef.InstallerType) { + case ManifestInstaller::InstallerTypeEnum::Burn: + case ManifestInstaller::InstallerTypeEnum::Exe: + case ManifestInstaller::InstallerTypeEnum::Inno: + case ManifestInstaller::InstallerTypeEnum::Nullsoft: renamedDownloadedInstaller += L".exe"; + break; + case ManifestInstaller::InstallerTypeEnum::Msi: + case ManifestInstaller::InstallerTypeEnum::Wix: + renamedDownloadedInstaller += L".msi"; + break; } std::filesystem::rename(m_downloadedInstaller, renamedDownloadedInstaller); diff --git a/src/AppInstallerCLICore/Workflows/ShellExecuteInstallerHandler.h b/src/AppInstallerCLICore/Workflows/ShellExecuteInstallerHandler.h @@ -13,13 +13,27 @@ namespace AppInstaller::Workflow public: ShellExecuteInstallerHandler( const Manifest::ManifestInstaller& manifestInstaller, - WorkflowReporter& reporter); + const CLI::Invocation& args, + WorkflowReporter& reporter) : + InstallerHandlerBase(manifestInstaller, args, reporter) {}; // Install is done though invoking SheelExecute on downloaded installer. void Install() override; protected: std::future<DWORD> ExecuteInstallerAsync(const std::filesystem::path& filePath, const std::string& args); + + // The known default arg format if the corresponding arg is not specified in the manifest + // i.e. If silent switch is not specified in manifest and installer type is msi, /quiet will be returned. + std::string GetDefaultArg(std::string_view argType); + + // Construct the installer arg string from appropriate source(known args, manifest) according to command line args. + // Token is not replaced with actual values yet. + std::string GetInstallerArgsTemplate(); + + // Replace tokens in the installer arg string with appropriate values. + void PopulateInstallerArgsTemplate(std::string& installerArgs); + std::string GetInstallerArgs(); // This method appends appropriate extension to the downloaded installer. diff --git a/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj b/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj @@ -188,6 +188,18 @@ </CopyFileToFolders> <None Include="packages.config" /> <None Include="PropertySheet.props" /> + <CopyFileToFolders Include="TestData\InstallerArgTest_Inno_NoSwitches.yml"> + <DeploymentContent>true</DeploymentContent> + </CopyFileToFolders> + <CopyFileToFolders Include="TestData\InstallerArgTest_Inno_WithSwitches.yml"> + <DeploymentContent>true</DeploymentContent> + </CopyFileToFolders> + <CopyFileToFolders Include="TestData\InstallerArgTest_Msi_NoSwitches.yml"> + <DeploymentContent>true</DeploymentContent> + </CopyFileToFolders> + <CopyFileToFolders Include="TestData\InstallerArgTest_Msi_WithSwitches.yml"> + <DeploymentContent>true</DeploymentContent> + </CopyFileToFolders> </ItemGroup> <ItemGroup> <ProjectReference Include="..\AppInstallerCLICore\AppInstallerCLICore.vcxproj"> diff --git a/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj.filters b/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj.filters @@ -57,6 +57,9 @@ <ItemGroup> <None Include="PropertySheet.props" /> <None Include="packages.config" /> + <None Include="TestData\InstallerArgTest_Msi_WithSwitches.yml"> + <Filter>TestData</Filter> + </None> </ItemGroup> <ItemGroup> <CopyFileToFolders Include="TestData\BadManifest-MissingName.yml"> @@ -77,5 +80,14 @@ <CopyFileToFolders Include="TestData\InstallFlowTest_Msix_StreamingFlow.yml"> <Filter>TestData</Filter> </CopyFileToFolders> + <CopyFileToFolders Include="TestData\InstallerArgTest_Msi_NoSwitches.yml"> + <Filter>TestData</Filter> + </CopyFileToFolders> + <CopyFileToFolders Include="TestData\InstallerArgTest_Inno_NoSwitches.yml"> + <Filter>TestData</Filter> + </CopyFileToFolders> + <CopyFileToFolders Include="TestData\InstallerArgTest_Inno_WithSwitches.yml"> + <Filter>TestData</Filter> + </CopyFileToFolders> </ItemGroup> </Project> \ No newline at end of file diff --git a/src/AppInstallerCLITests/InstallFlow.cpp b/src/AppInstallerCLITests/InstallFlow.cpp @@ -2,6 +2,8 @@ // Licensed under the MIT License. #include "pch.h" #include "TestCommon.h" +#include "Commands/Common.h" +#include "AppInstallerLogging.h" #include "Manifest/Manifest.h" #include "AppInstallerDownloader.h" #include "AppInstallerStrings.h" @@ -21,7 +23,8 @@ class MsixInstallerHandlerTest : public MsixInstallerHandler public: MsixInstallerHandlerTest( const ManifestInstaller& manifestInstaller, - WorkflowReporter& reporter) : MsixInstallerHandler(manifestInstaller, reporter) {}; + const AppInstaller::CLI::Invocation& args, + WorkflowReporter& reporter) : MsixInstallerHandler(manifestInstaller, args, reporter) {}; protected: @@ -44,7 +47,8 @@ class ShellExecuteInstallerHandlerTest : public ShellExecuteInstallerHandler public: ShellExecuteInstallerHandlerTest( const ManifestInstaller& manifestInstaller, - WorkflowReporter& reporter) : ShellExecuteInstallerHandler(manifestInstaller, reporter) {}; + const AppInstaller::CLI::Invocation& args, + WorkflowReporter& reporter) : ShellExecuteInstallerHandler(manifestInstaller, args, reporter) {}; void Download() override { @@ -52,13 +56,19 @@ public: } void RenameDownloadedInstaller() override {}; + + std::string TestInstallerArgs() + { + Download(); + return ShellExecuteInstallerHandler::GetInstallerArgs(); + } }; class InstallFlowTest : public InstallFlow { public: - InstallFlowTest(Manifest manifest, std::ostream& outStream, std::istream& inStream) : - InstallFlow(manifest, outStream, inStream) {} + InstallFlowTest(Manifest manifest, const AppInstaller::CLI::Invocation& args, std::ostream& outStream, std::istream& inStream) : + InstallFlow(manifest, args, outStream, inStream) {} protected: std::unique_ptr<InstallerHandlerBase> GetInstallerHandler() override @@ -66,9 +76,9 @@ protected: switch (m_selectedInstaller.InstallerType) { case ManifestInstaller::InstallerTypeEnum::Exe: - return std::make_unique<ShellExecuteInstallerHandlerTest>(m_selectedInstaller, m_reporter); + return std::make_unique<ShellExecuteInstallerHandlerTest>(m_selectedInstaller, m_argsRef, m_reporter); case ManifestInstaller::InstallerTypeEnum::Msix: - return std::make_unique<MsixInstallerHandlerTest>(m_selectedInstaller, m_reporter); + return std::make_unique<MsixInstallerHandlerTest>(m_selectedInstaller, m_argsRef, m_reporter); default: THROW_HR(HRESULT_FROM_WIN32(ERROR_NOT_SUPPORTED)); } @@ -82,7 +92,8 @@ TEST_CASE("ExeInstallFlowWithTestManifest", "[InstallFlow]") auto manifest = Manifest::CreateFromPath(TestDataFile("InstallFlowTest_Exe.yml")); std::ostringstream installOutput; - InstallFlowTest testFlow(manifest, installOutput, std::cin); + AppInstaller::CLI::Invocation inv{ {""} }; + InstallFlowTest testFlow(manifest, inv, installOutput, std::cin); testFlow.Install(); INFO(installOutput.str()); @@ -92,7 +103,8 @@ TEST_CASE("ExeInstallFlowWithTestManifest", "[InstallFlow]") REQUIRE(installResultFile.is_open()); std::string installResultStr; std::getline(installResultFile, installResultStr); - REQUIRE(installResultStr.find("/default") != std::string::npos); + REQUIRE(installResultStr.find("/custom") != std::string::npos); + REQUIRE(installResultStr.find("/silentwithprogress") != std::string::npos); } TEST_CASE("InstallFlowWithNonApplicableArchitecture", "[InstallFlow]") @@ -102,7 +114,8 @@ TEST_CASE("InstallFlowWithNonApplicableArchitecture", "[InstallFlow]") auto manifest = Manifest::CreateFromPath(TestDataFile("InstallFlowTest_NoApplicableArchitecture.yml")); std::ostringstream installOutput; - InstallFlowTest testFlow(manifest, installOutput, std::cin); + AppInstaller::CLI::Invocation inv{ {""} }; + InstallFlowTest testFlow(manifest, inv, installOutput, std::cin); REQUIRE_THROWS_WITH(testFlow.Install(), Catch::Contains("No installer with applicable architecture found.")); INFO(installOutput.str()); @@ -118,7 +131,8 @@ TEST_CASE("MsixInstallFlow_DownloadFlow", "[InstallFlow]") auto manifest = Manifest::CreateFromPath(TestDataFile("InstallFlowTest_Msix_DownloadFlow.yml")); std::ostringstream installOutput; - InstallFlowTest testFlow(manifest, installOutput, std::cin); + AppInstaller::CLI::Invocation inv{ {""} }; + InstallFlowTest testFlow(manifest, inv, installOutput, std::cin); testFlow.Install(); INFO(installOutput.str()); @@ -139,15 +153,113 @@ TEST_CASE("MsixInstallFlow_StreamingFlow", "[InstallFlow]") auto manifest = Manifest::CreateFromPath(TestDataFile("InstallFlowTest_Msix_StreamingFlow.yml")); std::ostringstream installOutput; - InstallFlowTest testFlow(manifest, installOutput, std::cin); + AppInstaller::CLI::Invocation inv{ {""} }; + InstallFlowTest testFlow(manifest, inv, installOutput, std::cin); testFlow.Install(); INFO(installOutput.str()); - // Verify Installer is called and a local file is used as package Uri. + // Verify Installer is called and a http address is used as package Uri. REQUIRE(std::filesystem::exists(installResultPath.GetPath())); std::ifstream installResultFile(installResultPath.GetPath()); REQUIRE(installResultFile.is_open()); std::string installResultStr; std::getline(installResultFile, installResultStr); REQUIRE(installResultStr.find("https://") != std::string::npos); +} + +TEST_CASE("ShellExecuteHandlerInstallerArgs", "[InstallFlow]") +{ + std::ostringstream installOutput; + WorkflowReporter reporter(installOutput, std::cin); + + { + // Default Msi type with no args passed in, no switches specified in manifest + auto manifest = Manifest::CreateFromPath(TestDataFile("InstallerArgTest_Msi_NoSwitches.yml")); + AppInstaller::CLI::Invocation inv{ {""} }; + ShellExecuteInstallerHandlerTest testhandler(manifest.Installers.at(0), inv, reporter); + std::string installerArgs = testhandler.TestInstallerArgs(); + REQUIRE(installerArgs.find("/passive") != std::string::npos); + REQUIRE(installerArgs.find("AppInstallerTestExeInstaller.exe.log") != std::string::npos); + } + + { + // Msi type with /silent and /log and /custom and /installlocation, no switches specified in manifest + auto manifest = Manifest::CreateFromPath(TestDataFile("InstallerArgTest_Msi_NoSwitches.yml")); + AppInstaller::CLI::Invocation inv{ {""} }; + inv.AddArg(AppInstaller::CLI::ARG_SILENT); + inv.AddArg(AppInstaller::CLI::ARG_LOG, "MyLog.log"); + inv.AddArg(AppInstaller::CLI::ARG_INSTALLLOCATION, "MyDir"); + ShellExecuteInstallerHandlerTest testhandler(manifest.Installers.at(0), inv, reporter); + std::string installerArgs = testhandler.TestInstallerArgs(); + REQUIRE(installerArgs.find("/quiet") != std::string::npos); + REQUIRE(installerArgs.find("/log \"MyLog.log\"") != std::string::npos); + REQUIRE(installerArgs.find("TARGETDIR=\"MyDir\"") != std::string::npos); + } + + { + // Msi type with /silent and /log and /custom and /installlocation, switches specified in manifest + auto manifest = Manifest::CreateFromPath(TestDataFile("InstallerArgTest_Msi_WithSwitches.yml")); + AppInstaller::CLI::Invocation inv{ {""} }; + inv.AddArg(AppInstaller::CLI::ARG_SILENT); + inv.AddArg(AppInstaller::CLI::ARG_LOG, "MyLog.log"); + inv.AddArg(AppInstaller::CLI::ARG_INSTALLLOCATION, "MyDir"); + ShellExecuteInstallerHandlerTest testhandler(manifest.Installers.at(0), inv, reporter); + std::string installerArgs = testhandler.TestInstallerArgs(); + REQUIRE(installerArgs.find("/mysilent") != std::string::npos); // Use declaration in manifest + REQUIRE(installerArgs.find("/mylog=\"MyLog.log\"") != std::string::npos); // Use declaration in manifest + REQUIRE(installerArgs.find("/mycustom") != std::string::npos); // Use declaration in manifest + REQUIRE(installerArgs.find("/myinstalldir=\"MyDir\"") != std::string::npos); // Use declaration in manifest + } + + { + // Default Inno type with no args passed in, no switches specified in manifest + auto manifest = Manifest::CreateFromPath(TestDataFile("InstallerArgTest_Inno_NoSwitches.yml")); + AppInstaller::CLI::Invocation inv{ {""} }; + ShellExecuteInstallerHandlerTest testhandler(manifest.Installers.at(0), inv, reporter); + std::string installerArgs = testhandler.TestInstallerArgs(); + REQUIRE(installerArgs.find("/SILENT") != std::string::npos); + REQUIRE(installerArgs.find("AppInstallerTestExeInstaller.exe.log") != std::string::npos); + } + + { + // Inno type with /silent and /log and /custom and /installlocation, no switches specified in manifest + auto manifest = Manifest::CreateFromPath(TestDataFile("InstallerArgTest_Inno_NoSwitches.yml")); + AppInstaller::CLI::Invocation inv{ {""} }; + inv.AddArg(AppInstaller::CLI::ARG_SILENT); + inv.AddArg(AppInstaller::CLI::ARG_LOG, "MyLog.log"); + inv.AddArg(AppInstaller::CLI::ARG_INSTALLLOCATION, "MyDir"); + ShellExecuteInstallerHandlerTest testhandler(manifest.Installers.at(0), inv, reporter); + std::string installerArgs = testhandler.TestInstallerArgs(); + REQUIRE(installerArgs.find("/VERYSILENT") != std::string::npos); + REQUIRE(installerArgs.find("/LOG=\"MyLog.log\"") != std::string::npos); + REQUIRE(installerArgs.find("/DIR=\"MyDir\"") != std::string::npos); + } + + { + // Inno type with /silent and /log and /custom and /installlocation, switches specified in manifest + auto manifest = Manifest::CreateFromPath(TestDataFile("InstallerArgTest_Inno_WithSwitches.yml")); + AppInstaller::CLI::Invocation inv{ {""} }; + inv.AddArg(AppInstaller::CLI::ARG_SILENT); + inv.AddArg(AppInstaller::CLI::ARG_LOG, "MyLog.log"); + inv.AddArg(AppInstaller::CLI::ARG_INSTALLLOCATION, "MyDir"); + ShellExecuteInstallerHandlerTest testhandler(manifest.Installers.at(0), inv, reporter); + std::string installerArgs = testhandler.TestInstallerArgs(); + REQUIRE(installerArgs.find("/mysilent") != std::string::npos); // Use declaration in manifest + REQUIRE(installerArgs.find("/mylog=\"MyLog.log\"") != std::string::npos); // Use declaration in manifest + REQUIRE(installerArgs.find("/mycustom") != std::string::npos); // Use declaration in manifest + REQUIRE(installerArgs.find("/myinstalldir=\"MyDir\"") != std::string::npos); // Use declaration in manifest + } + + { + // Override switch specified. The whole arg passed to installer is overrided. + auto manifest = Manifest::CreateFromPath(TestDataFile("InstallerArgTest_Inno_WithSwitches.yml")); + AppInstaller::CLI::Invocation inv{ {""} }; + inv.AddArg(AppInstaller::CLI::ARG_SILENT); + inv.AddArg(AppInstaller::CLI::ARG_LOG, "MyLog.log"); + inv.AddArg(AppInstaller::CLI::ARG_INSTALLLOCATION, "MyDir"); + inv.AddArg(AppInstaller::CLI::ARG_OVERRIDE, "/OverrideEverything"); + ShellExecuteInstallerHandlerTest testhandler(manifest.Installers.at(0), inv, reporter); + std::string installerArgs = testhandler.TestInstallerArgs(); + REQUIRE(installerArgs == "/OverrideEverything"); // Use value specified in override switch + } } \ No newline at end of file diff --git a/src/AppInstallerCLITests/TestData/GoodManifest.yml b/src/AppInstallerCLITests/TestData/GoodManifest.yml @@ -18,9 +18,13 @@ FileExtensions: "appx,appxbundle,msix,msixbundle" # on the root. An installer can override them. InstallerType: Zip Switches: - Verbose: /verbose - Default: /default + Custom: /custom + SilentWithProgress: /silentwithprogress Silent: /silence + Interactive: /interactive + Language: /en-us + Log: /log=<LOGPATH> + InstallLocation: /dir=<INSTALLPATH> Installers: - Arch: x86 Url: https://rubengustorage.blob.core.windows.net/publiccontainer/msixsdkx86.zip @@ -29,9 +33,13 @@ Installers: InstallerType: Zip Scope: user Switches: - Verbose: /v - Default: /d + Custom: /c + SilentWithProgress: /sp Silent: /s + Interactive: /i + Language: /en + Log: /l=<LOGPATH> + InstallLocation: /d=<INSTALLPATH> - Arch: x64 Url: https://rubengustorage.blob.core.windows.net/publiccontainer/msixsdkx64.zip Sha256: 69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF0000 diff --git a/src/AppInstallerCLITests/TestData/InstallFlowTest_Exe.yml b/src/AppInstallerCLITests/TestData/InstallFlowTest_Exe.yml @@ -4,8 +4,8 @@ Name: AppInstaller Test Installer Publisher: Microsoft Corporation AppMoniker: AICLITestExe Switches: - Verbose: /verbose - Default: /default + Custom: /custom + SilentWithProgress: /silentwithprogress Silent: /silence Installers: - Arch: x64 diff --git a/src/AppInstallerCLITests/TestData/InstallFlowTest_NoApplicableArchitecture.yml b/src/AppInstallerCLITests/TestData/InstallFlowTest_NoApplicableArchitecture.yml @@ -4,8 +4,8 @@ Name: AppInstaller Test Installer Publisher: Microsoft Corporation AppMoniker: AICLITestExe Switches: - Verbose: /verbose - Default: /default + Custom: /custom + SilentWithProgress: /silentwithprogress Silent: /silence Installers: - Arch: unknown diff --git a/src/AppInstallerCLITests/TestData/InstallerArgTest_Inno_NoSwitches.yml b/src/AppInstallerCLITests/TestData/InstallerArgTest_Inno_NoSwitches.yml @@ -0,0 +1,11 @@ +Id: AppInstallerCliTest.TestInstaller +Version: 1.0.0.0 +Name: AppInstaller Test Installer +Publisher: Microsoft Corporation +AppMoniker: AICLITest +Installers: + - Arch: x64 + Url: https://ThisIsNotUsed + InstallerType: inno + Sha256: 65DB2F2AC2686C7F2FD69D4A4C6683B888DC55BFA20A0E32CA9F838B51689A3B + diff --git a/src/AppInstallerCLITests/TestData/InstallerArgTest_Inno_WithSwitches.yml b/src/AppInstallerCLITests/TestData/InstallerArgTest_Inno_WithSwitches.yml @@ -0,0 +1,16 @@ +Id: AppInstallerCliTest.TestInstaller +Version: 1.0.0.0 +Name: AppInstaller Test Installer +Publisher: Microsoft Corporation +AppMoniker: AICLITest +Installers: + - Arch: x64 + Url: https://ThisIsNotUsed + InstallerType: inno + Sha256: 65DB2F2AC2686C7F2FD69D4A4C6683B888DC55BFA20A0E32CA9F838B51689A3B + Switches: + Custom: /mycustom + SilentWithProgress: /mysilentwithprogress + Silent: /mysilent + Log: /mylog="<LOGPATH>" + InstallLocation: /myinstalldir="<INSTALLPATH>" diff --git a/src/AppInstallerCLITests/TestData/InstallerArgTest_Msi_NoSwitches.yml b/src/AppInstallerCLITests/TestData/InstallerArgTest_Msi_NoSwitches.yml @@ -0,0 +1,10 @@ +Id: AppInstallerCliTest.TestInstaller +Version: 1.0.0.0 +Name: AppInstaller Test Installer +Publisher: Microsoft Corporation +AppMoniker: AICLITest +Installers: + - Arch: x64 + Url: https://ThisIsNotUsed + InstallerType: msi + Sha256: 65DB2F2AC2686C7F2FD69D4A4C6683B888DC55BFA20A0E32CA9F838B51689A3B diff --git a/src/AppInstallerCLITests/TestData/InstallerArgTest_Msi_WithSwitches.yml b/src/AppInstallerCLITests/TestData/InstallerArgTest_Msi_WithSwitches.yml @@ -0,0 +1,16 @@ +Id: AppInstallerCliTest.TestInstaller +Version: 1.0.0.0 +Name: AppInstaller Test Installer +Publisher: Microsoft Corporation +AppMoniker: AICLITest +Installers: + - Arch: x64 + Url: https://ThisIsNotUsed + InstallerType: msi + Sha256: 65DB2F2AC2686C7F2FD69D4A4C6683B888DC55BFA20A0E32CA9F838B51689A3B + Switches: + Custom: /mycustom + SilentWithProgress: /mysilentwithprogress + Silent: /mysilent + Log: /mylog="<LOGPATH>" + InstallLocation: /myinstalldir="<INSTALLPATH>" diff --git a/src/AppInstallerCLITests/YamlManifest.cpp b/src/AppInstallerCLITests/YamlManifest.cpp @@ -51,11 +51,14 @@ TEST_CASE("ReadGoodManifestAndVerifyContents", "[PackageManifestHelper]") REQUIRE(manifest.InstallerType == ManifestInstaller::InstallerTypeEnum::Zip); // default switches - REQUIRE(manifest.Switches.has_value()); - InstallerSwitches switches = manifest.Switches.value(); - REQUIRE(switches.Verbose == "/verbose"); - REQUIRE(switches.Silent == "/silence"); - REQUIRE(switches.Default == "/default"); + auto switches = manifest.Switches; + REQUIRE(switches.at(ManifestInstaller::InstallerSwitchType::Custom) == "/custom"); + REQUIRE(switches.at(ManifestInstaller::InstallerSwitchType::SilentWithProgress) == "/silentwithprogress"); + REQUIRE(switches.at(ManifestInstaller::InstallerSwitchType::Silent) == "/silence"); + REQUIRE(switches.at(ManifestInstaller::InstallerSwitchType::Interactive) == "/interactive"); + REQUIRE(switches.at(ManifestInstaller::InstallerSwitchType::Language) == "/en-us"); + REQUIRE(switches.at(ManifestInstaller::InstallerSwitchType::Log) == "/log=<LOGPATH>"); + REQUIRE(switches.at(ManifestInstaller::InstallerSwitchType::InstallLocation) == "/dir=<INSTALLPATH>"); // installers REQUIRE(manifest.Installers.size() == 2); @@ -67,11 +70,14 @@ TEST_CASE("ReadGoodManifestAndVerifyContents", "[PackageManifestHelper]") REQUIRE(installer1.InstallerType == ManifestInstaller::InstallerTypeEnum::Zip); REQUIRE(installer1.Scope == "user"); - REQUIRE(installer1.Switches.has_value()); - InstallerSwitches installer1Switches = installer1.Switches.value(); - REQUIRE(installer1Switches.Verbose == "/v"); - REQUIRE(installer1Switches.Silent == "/s"); - REQUIRE(installer1Switches.Default == "/d"); + auto installer1Switches = installer1.Switches; + REQUIRE(installer1Switches.at(ManifestInstaller::InstallerSwitchType::Custom) == "/c"); + REQUIRE(installer1Switches.at(ManifestInstaller::InstallerSwitchType::SilentWithProgress) == "/sp"); + REQUIRE(installer1Switches.at(ManifestInstaller::InstallerSwitchType::Silent) == "/s"); + REQUIRE(installer1Switches.at(ManifestInstaller::InstallerSwitchType::Interactive) == "/i"); + REQUIRE(installer1Switches.at(ManifestInstaller::InstallerSwitchType::Language) == "/en"); + REQUIRE(installer1Switches.at(ManifestInstaller::InstallerSwitchType::Log) == "/l=<LOGPATH>"); + REQUIRE(installer1Switches.at(ManifestInstaller::InstallerSwitchType::InstallLocation) == "/d=<INSTALLPATH>"); ManifestInstaller installer2 = manifest.Installers.at(1); REQUIRE(installer2.Arch == Architecture::X64); @@ -82,11 +88,14 @@ TEST_CASE("ReadGoodManifestAndVerifyContents", "[PackageManifestHelper]") REQUIRE(installer2.Scope == "user"); // Installer2 does not declare switches, it inherits switches from package default. - REQUIRE(installer2.Switches.has_value()); - InstallerSwitches installer2Switches = installer2.Switches.value(); - REQUIRE(installer2Switches.Verbose == "/verbose"); - REQUIRE(installer2Switches.Silent == "/silence"); - REQUIRE(installer2Switches.Default == "/default"); + auto installer2Switches = installer2.Switches; + REQUIRE(installer2Switches.at(ManifestInstaller::InstallerSwitchType::Custom) == "/custom"); + REQUIRE(installer2Switches.at(ManifestInstaller::InstallerSwitchType::SilentWithProgress) == "/silentwithprogress"); + REQUIRE(installer2Switches.at(ManifestInstaller::InstallerSwitchType::Silent) == "/silence"); + REQUIRE(installer2Switches.at(ManifestInstaller::InstallerSwitchType::Interactive) == "/interactive"); + REQUIRE(installer2Switches.at(ManifestInstaller::InstallerSwitchType::Language) == "/en-us"); + REQUIRE(installer2Switches.at(ManifestInstaller::InstallerSwitchType::Log) == "/log=<LOGPATH>"); + REQUIRE(installer2Switches.at(ManifestInstaller::InstallerSwitchType::InstallLocation) == "/dir=<INSTALLPATH>"); // Localization REQUIRE(manifest.Localization.size() == 1); diff --git a/src/AppInstallerCommonCore/AppInstallerStrings.cpp b/src/AppInstallerCommonCore/AppInstallerStrings.cpp @@ -63,4 +63,14 @@ namespace AppInstaller::Utility return nonWhitespaceNotFound; } + + void FindAndReplace(std::string& inputStr, const std::string& token, const std::string& value) + { + std::string::size_type pos = 0u; + while ((pos = inputStr.find(token, pos)) != std::string::npos) + { + inputStr.replace(pos, token.length(), value); + pos += value.length(); + } + } } diff --git a/src/AppInstallerCommonCore/Downloader.cpp b/src/AppInstallerCommonCore/Downloader.cpp @@ -94,11 +94,11 @@ namespace AppInstaller::Utility BOOL readSuccess = true; DWORD bytesRead = 0; - LONGLONG progress = 0; + LONGLONG bytesDownloaded = 0; if (callback) { - callback->OnStarted(); + callback->OnStarted(contentLength); } do @@ -124,11 +124,11 @@ namespace AppInstaller::Utility outfile.write((char*)buffer.get(), bytesRead); - progress += bytesRead; + bytesDownloaded += bytesRead; if (callback && bytesRead != 0) { - callback->OnProgress(progress, contentLength); + callback->OnProgress(bytesDownloaded, contentLength); } } while (bytesRead != 0); diff --git a/src/AppInstallerCommonCore/Public/AppInstallerDownloader.h b/src/AppInstallerCommonCore/Public/AppInstallerDownloader.h @@ -17,9 +17,9 @@ namespace AppInstaller::Utility class IDownloaderCallback { public: - virtual void OnStarted() = 0; + virtual void OnStarted(LONGLONG totalBytes) = 0; - virtual void OnProgress(LONGLONG progress, LONGLONG downloadSize) = 0; + virtual void OnProgress(LONGLONG bytesDownloaded, LONGLONG totalBytes) = 0; virtual void OnCanceled() = 0; diff --git a/src/AppInstallerCommonCore/Public/AppInstallerStrings.h b/src/AppInstallerCommonCore/Public/AppInstallerStrings.h @@ -21,4 +21,7 @@ namespace AppInstaller::Utility // Checks if the input string is empty or whitespace bool IsEmptyOrWhitespace(std::wstring_view str); + + // Find token in the input string and replace with value. + void FindAndReplace(std::string& inputStr, const std::string& token, const std::string& value); } diff --git a/src/AppInstallerRepositoryCore/AppInstallerRepositoryCore.vcxproj b/src/AppInstallerRepositoryCore/AppInstallerRepositoryCore.vcxproj @@ -168,7 +168,6 @@ </Link> </ItemDefinitionGroup> <ItemGroup> - <ClInclude Include="Manifest\InstallerSwitches.h" /> <ClInclude Include="Manifest\Manifest.h" /> <ClInclude Include="Manifest\ManifestInstaller.h" /> <ClInclude Include="Manifest\ManifestLocalization.h" /> @@ -197,7 +196,6 @@ <ClInclude Include="SQLiteWrapper.h" /> </ItemGroup> <ItemGroup> - <ClCompile Include="Manifest\InstallerSwitches.cpp" /> <ClCompile Include="Manifest\Manifest.cpp" /> <ClCompile Include="Manifest\ManifestInstaller.cpp" /> <ClCompile Include="Manifest\ManifestLocalization.cpp" /> diff --git a/src/AppInstallerRepositoryCore/AppInstallerRepositoryCore.vcxproj.filters b/src/AppInstallerRepositoryCore/AppInstallerRepositoryCore.vcxproj.filters @@ -51,9 +51,6 @@ <ClInclude Include="Microsoft\Schema\1_0\Interface.h"> <Filter>Microsoft\Schema\1_0</Filter> </ClInclude> - <ClInclude Include="Manifest\InstallerSwitches.h"> - <Filter>Manifest</Filter> - </ClInclude> <ClInclude Include="Manifest\Manifest.h"> <Filter>Manifest</Filter> </ClInclude> @@ -131,9 +128,6 @@ <ClCompile Include="Microsoft\Schema\1_0\Interface.cpp"> <Filter>Microsoft\Schema\1_0</Filter> </ClCompile> - <ClCompile Include="Manifest\InstallerSwitches.cpp"> - <Filter>Manifest</Filter> - </ClCompile> <ClCompile Include="Manifest\Manifest.cpp"> <Filter>Manifest</Filter> </ClCompile> diff --git a/src/AppInstallerRepositoryCore/Manifest/InstallerSwitches.cpp b/src/AppInstallerRepositoryCore/Manifest/InstallerSwitches.cpp @@ -1,24 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -#include "pch.h" -#include "InstallerSwitches.h" -#include "Manifest.h" - -namespace AppInstaller::Manifest -{ - void InstallerSwitches::PopulateSwitchesFields(const YAML::Node& switchesNode, const InstallerSwitches* defaultSwitches) - { - this->Default = switchesNode["Default"] ? - switchesNode["Default"].as<std::string>() : - defaultSwitches ? defaultSwitches->Default : ""; - - this->Silent = switchesNode["Silent"] ? - switchesNode["Silent"].as<std::string>() : - defaultSwitches ? defaultSwitches->Silent : ""; - - this->Verbose = switchesNode["Verbose"] ? - switchesNode["Verbose"].as<std::string>() : - defaultSwitches ? defaultSwitches->Verbose : ""; - } -} diff --git a/src/AppInstallerRepositoryCore/Manifest/InstallerSwitches.h b/src/AppInstallerRepositoryCore/Manifest/InstallerSwitches.h @@ -1,23 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include <string> - -namespace YAML { class Node; } - -namespace AppInstaller::Manifest -{ - class InstallerSwitches - { - public: - std::string Default; - - std::string Silent; - - std::string Verbose; - - // Populates InstallerSwitches - // defaultSwitches: if an optional field is not found in the YAML node, the field will be populated with value from defaultSwitches. - void PopulateSwitchesFields(const YAML::Node& switchesNode, const InstallerSwitches* defaultSwitches = nullptr); - }; -}- \ No newline at end of file diff --git a/src/AppInstallerRepositoryCore/Manifest/Manifest.cpp b/src/AppInstallerRepositoryCore/Manifest/Manifest.cpp @@ -64,18 +64,13 @@ namespace AppInstaller::Manifest if (rootNode["Switches"]) { YAML::Node switchesNode = rootNode["Switches"]; - InstallerSwitches switches; - switches.PopulateSwitchesFields(switchesNode); - this->Switches.emplace(std::move(switches)); + ManifestInstaller::PopulateSwitchesFields(&switchesNode, this->Switches); } // Create default ManifestInstaller to be used to populate default value when optional fields are not found. ManifestInstaller defaultInstaller; defaultInstaller.InstallerType = this->InstallerType; - if (this->Switches.has_value()) - { - defaultInstaller.Switches.emplace(this->Switches.value()); - } + defaultInstaller.Switches = this->Switches; YAML::Node installersNode = rootNode["Installers"]; for (std::size_t i = 0; i < installersNode.size(); i++) { diff --git a/src/AppInstallerRepositoryCore/Manifest/Manifest.h b/src/AppInstallerRepositoryCore/Manifest/Manifest.h @@ -60,7 +60,7 @@ namespace AppInstaller::Manifest ManifestInstaller::InstallerTypeEnum InstallerType; - std::optional<InstallerSwitches> Switches; + std::map<ManifestInstaller::InstallerSwitchType, std::string> Switches; std::string Description; diff --git a/src/AppInstallerRepositoryCore/Manifest/ManifestInstaller.cpp b/src/AppInstallerRepositoryCore/Manifest/ManifestInstaller.cpp @@ -24,22 +24,91 @@ namespace AppInstaller::Manifest this->InstallerType = installerNode["InstallerType"] ? ConvertToInstallerTypeEnum(installerNode["InstallerType"].as<std::string>()) : - InstallerTypeEnum::Unknown; + defaultInstaller.InstallerType; + + std::map<InstallerSwitchType, std::string> defaultKnownSwitches = GetDefaultKnownSwitches(this->InstallerType); if (installerNode["Switches"]) { YAML::Node switchesNode = installerNode["Switches"]; - InstallerSwitches switches; - switches.PopulateSwitchesFields(switchesNode, - defaultInstaller.Switches.has_value() ? &(defaultInstaller.Switches.value()) : nullptr); - this->Switches.emplace(std::move(switches)); + PopulateSwitchesFields(&switchesNode, this->Switches, &(defaultInstaller.Switches), &defaultKnownSwitches); + } + else + { + PopulateSwitchesFields(nullptr, this->Switches, &(defaultInstaller.Switches), &defaultKnownSwitches); + } + } + + void ManifestInstaller::PopulateSwitchesFields( + const YAML::Node* switchesNode, + std::map<InstallerSwitchType, std::string>& switches, + const std::map<InstallerSwitchType, std::string>* manifestRootSwitches, + const std::map<InstallerSwitchType, std::string>* defaultKnownSwitches) + { + PopulateOneSwitchField(switchesNode, "Custom", InstallerSwitchType::Custom, switches, manifestRootSwitches, defaultKnownSwitches); + PopulateOneSwitchField(switchesNode, "Silent", InstallerSwitchType::Silent, switches, manifestRootSwitches, defaultKnownSwitches); + PopulateOneSwitchField(switchesNode, "SilentWithProgress", InstallerSwitchType::SilentWithProgress, switches, manifestRootSwitches, defaultKnownSwitches); + PopulateOneSwitchField(switchesNode, "Interactive", InstallerSwitchType::Interactive, switches, manifestRootSwitches, defaultKnownSwitches); + PopulateOneSwitchField(switchesNode, "Language", InstallerSwitchType::Language, switches, manifestRootSwitches, defaultKnownSwitches); + PopulateOneSwitchField(switchesNode, "Log", InstallerSwitchType::Log, switches, manifestRootSwitches, defaultKnownSwitches); + PopulateOneSwitchField(switchesNode, "InstallLocation", InstallerSwitchType::InstallLocation, switches, manifestRootSwitches, defaultKnownSwitches); + } + + void ManifestInstaller::PopulateOneSwitchField( + const YAML::Node* switchesNode, + const std::string& switchName, + InstallerSwitchType switchType, + std::map<InstallerSwitchType, std::string>& switches, + const std::map<InstallerSwitchType, std::string>* manifestRootSwitches, + const std::map<InstallerSwitchType, std::string>* defaultKnownSwitches) + { + if (switchesNode && (*switchesNode)[switchName]) + { + switches.emplace(switchType, (*switchesNode)[switchName].as<std::string>()); + } + else if (manifestRootSwitches && manifestRootSwitches->find(switchType) != manifestRootSwitches->end()) + { + switches.emplace(switchType, manifestRootSwitches->at(switchType)); } - else if (defaultInstaller.Switches.has_value()) + else if (defaultKnownSwitches && defaultKnownSwitches->find(switchType) != defaultKnownSwitches->end()) { - this->Switches.emplace(defaultInstaller.Switches.value()); + switches.emplace(switchType, defaultKnownSwitches->at(switchType)); } } + std::map<ManifestInstaller::InstallerSwitchType, std::string> ManifestInstaller::GetDefaultKnownSwitches(InstallerTypeEnum installerType) + { + switch (installerType) + { + case ManifestInstaller::InstallerTypeEnum::Burn: + case ManifestInstaller::InstallerTypeEnum::Wix: + case ManifestInstaller::InstallerTypeEnum::Msi: + return + { + {InstallerSwitchType::Silent, "/quiet"}, + {InstallerSwitchType::SilentWithProgress, "/passive"}, + {InstallerSwitchType::Log, "/log \"" + std::string(ARG_TOKEN_LOGPATH) + "\""}, + {InstallerSwitchType::InstallLocation, "TARGETDIR=\"" + std::string(ARG_TOKEN_INSTALLPATH) + "\""} + }; + case ManifestInstaller::InstallerTypeEnum::Nullsoft: + return + { + {InstallerSwitchType::Silent, "/S"}, + {InstallerSwitchType::SilentWithProgress, "/S"}, + {InstallerSwitchType::InstallLocation, "/D=\"" + std::string(ARG_TOKEN_INSTALLPATH) + "\""} + }; + case ManifestInstaller::InstallerTypeEnum::Inno: + return + { + {InstallerSwitchType::Silent, "/VERYSILENT"}, + {InstallerSwitchType::SilentWithProgress, "/SILENT"}, + {InstallerSwitchType::Log, "/LOG=\"" + std::string(ARG_TOKEN_LOGPATH) + "\""}, + {InstallerSwitchType::InstallLocation, "/DIR=\"" + std::string(ARG_TOKEN_INSTALLPATH) + "\""} + }; + } + return {}; + } + ManifestInstaller::InstallerTypeEnum ManifestInstaller::ConvertToInstallerTypeEnum(const std::string& in) { std::string inStrLower = Utility::ToLower(in); @@ -73,6 +142,10 @@ namespace AppInstaller::Manifest { result = InstallerTypeEnum::Exe; } + else if (inStrLower == "burn") + { + result = InstallerTypeEnum::Burn; + } return result; } @@ -102,6 +175,9 @@ namespace AppInstaller::Manifest case ManifestInstaller::InstallerTypeEnum::Zip: out << "Zip"; break; + case ManifestInstaller::InstallerTypeEnum::Burn: + out << "Burn"; + break; default: out << "Unknown"; } diff --git a/src/AppInstallerRepositoryCore/Manifest/ManifestInstaller.h b/src/AppInstallerRepositoryCore/Manifest/ManifestInstaller.h @@ -2,12 +2,17 @@ // Licensed under the MIT License. #pragma once #include <string> -#include <optional> +#include <map> #include <AppInstallerArchitecture.h> -#include "InstallerSwitches.h" namespace AppInstaller::Manifest { + using namespace std::string_view_literals; + + // Token specified in installer args will be replaced by proper value. + static constexpr std::string_view ARG_TOKEN_LOGPATH = "<LOGPATH>"sv; + static constexpr std::string_view ARG_TOKEN_INSTALLPATH = "<INSTALLPATH>"sv; + class ManifestInstaller { public: @@ -21,9 +26,21 @@ namespace AppInstaller::Manifest Zip, Msix, Exe, + Burn, Unknown }; + enum class InstallerSwitchType + { + Custom, + Silent, + SilentWithProgress, + Interactive, + Language, + Log, + InstallLocation, + }; + // Required. Values: x86, x64, arm, arm64, all. AppInstaller::Utility::Architecture Arch; @@ -43,14 +60,34 @@ namespace AppInstaller::Manifest // Name TBD std::string Scope; - // If present, has more presedence than root + // If present, has more precedence than root InstallerTypeEnum InstallerType; - // If present, has more presedence than root - std::optional<InstallerSwitches> Switches; + // If present, has more precedence than root + std::map<InstallerSwitchType, std::string> Switches; static InstallerTypeEnum ConvertToInstallerTypeEnum(const std::string& in); + static std::map<InstallerSwitchType, std::string> GetDefaultKnownSwitches(InstallerTypeEnum installerType); + + // Populates InstallerSwitches + // The value declared in the manifest takes precedence, then value in the manifest root, then default known values. + static void PopulateSwitchesFields( + const YAML::Node* switchesNode, + std::map<InstallerSwitchType, std::string>& switches, + const std::map<InstallerSwitchType, std::string>* manifestRootSwitches = nullptr, + const std::map<InstallerSwitchType, std::string>* defaultKnownSwitches = nullptr); + + // Populates one Installer Switch + // The value declared in the manifest takes precedence, then value in the manifest root, then default known values. + static void PopulateOneSwitchField( + const YAML::Node* switchesNode, + const std::string& switchName, + InstallerSwitchType switchType, + std::map<InstallerSwitchType, std::string>& switches, + const std::map<InstallerSwitchType, std::string>* manifestRootSwitches, + const std::map<InstallerSwitchType, std::string>* defaultKnownSwitches); + // Populates ManifestInstaller // defaultInstaller: if an optional field is not found in the YAML node, the field will be populated with value from defaultInstaller. void PopulateInstallerFields(const YAML::Node& installerNode, const ManifestInstaller& defaultInstaller); diff --git a/src/AppInstallerSQLiteIndexUtil/AppInstallerSQLiteIndexUtil.vcxproj b/src/AppInstallerSQLiteIndexUtil/AppInstallerSQLiteIndexUtil.vcxproj @@ -122,9 +122,9 @@ <ClCompile> <Optimization>Disabled</Optimization> <PreprocessorDefinitions>_DEBUG;%(PreprocessorDefinitions);CLICOREDLLBUILD</PreprocessorDefinitions> - <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Debug|ARM'">$(ProjectDir);$(ProjectDir)..\AppInstallerCommonCore;$(ProjectDir)..\AppInstallerRepositoryCore;$(ProjectDir)..\AppInstallerCommonCore\Public;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> - <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">$(ProjectDir);$(ProjectDir)..\AppInstallerCommonCore;$(ProjectDir)..\AppInstallerRepositoryCore;$(ProjectDir)..\AppInstallerCommonCore\Public;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> - <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">$(ProjectDir);$(ProjectDir)..\AppInstallerCommonCore;$(ProjectDir)..\AppInstallerRepositoryCore;$(ProjectDir)..\AppInstallerCommonCore\Public;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> + <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Debug|ARM'">$(ProjectDir);$(ProjectDir)..\AppInstallerCommonCore;$(ProjectDir)..\AppInstallerRepositoryCore;$(ProjectDir)..\AppInstallerCommonCore\Public;$(ProjectDir)..\YamlCppLib\yaml-cpp\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> + <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">$(ProjectDir);$(ProjectDir)..\AppInstallerCommonCore;$(ProjectDir)..\AppInstallerRepositoryCore;$(ProjectDir)..\AppInstallerCommonCore\Public;$(ProjectDir)..\YamlCppLib\yaml-cpp\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> + <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">$(ProjectDir);$(ProjectDir)..\AppInstallerCommonCore;$(ProjectDir)..\AppInstallerRepositoryCore;$(ProjectDir)..\AppInstallerCommonCore\Public;$(ProjectDir)..\YamlCppLib\yaml-cpp\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> <TreatWarningAsError Condition="'$(Configuration)|$(Platform)'=='Debug|ARM'">true</TreatWarningAsError> <TreatWarningAsError Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">true</TreatWarningAsError> <TreatWarningAsError Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">true</TreatWarningAsError> @@ -145,7 +145,7 @@ <ItemDefinitionGroup Condition="'$(Platform)'=='Win32'"> <ClCompile> <PreprocessorDefinitions>WIN32;%(PreprocessorDefinitions);CLICOREDLLBUILD</PreprocessorDefinitions> - <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">$(ProjectDir);$(ProjectDir)..\AppInstallerCommonCore;$(ProjectDir)..\AppInstallerRepositoryCore;$(ProjectDir)..\AppInstallerCommonCore\Public;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> + <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">$(ProjectDir);$(ProjectDir)..\AppInstallerCommonCore;$(ProjectDir)..\AppInstallerRepositoryCore;$(ProjectDir)..\AppInstallerCommonCore\Public;$(ProjectDir)..\YamlCppLib\yaml-cpp\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> <TreatWarningAsError Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">true</TreatWarningAsError> </ClCompile> <Link> @@ -160,10 +160,10 @@ <FunctionLevelLinking>true</FunctionLevelLinking> <IntrinsicFunctions>true</IntrinsicFunctions> <PreprocessorDefinitions>NDEBUG;%(PreprocessorDefinitions);CLICOREDLLBUILD</PreprocessorDefinitions> - <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Release|ARM'">$(ProjectDir);$(ProjectDir)..\AppInstallerCommonCore;$(ProjectDir)..\AppInstallerRepositoryCore;$(ProjectDir)..\AppInstallerCommonCore\Public;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> - <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'">$(ProjectDir);$(ProjectDir)..\AppInstallerCommonCore;$(ProjectDir)..\AppInstallerRepositoryCore;$(ProjectDir)..\AppInstallerCommonCore\Public;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> - <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">$(ProjectDir);$(ProjectDir)..\AppInstallerCommonCore;$(ProjectDir)..\AppInstallerRepositoryCore;$(ProjectDir)..\AppInstallerCommonCore\Public;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> - <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Release|x64'">$(ProjectDir);$(ProjectDir)..\AppInstallerCommonCore;$(ProjectDir)..\AppInstallerRepositoryCore;$(ProjectDir)..\AppInstallerCommonCore\Public;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> + <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Release|ARM'">$(ProjectDir);$(ProjectDir)..\AppInstallerCommonCore;$(ProjectDir)..\AppInstallerRepositoryCore;$(ProjectDir)..\AppInstallerCommonCore\Public;$(ProjectDir)..\YamlCppLib\yaml-cpp\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> + <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'">$(ProjectDir);$(ProjectDir)..\AppInstallerCommonCore;$(ProjectDir)..\AppInstallerRepositoryCore;$(ProjectDir)..\AppInstallerCommonCore\Public;$(ProjectDir)..\YamlCppLib\yaml-cpp\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> + <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">$(ProjectDir);$(ProjectDir)..\AppInstallerCommonCore;$(ProjectDir)..\AppInstallerRepositoryCore;$(ProjectDir)..\AppInstallerCommonCore\Public;$(ProjectDir)..\YamlCppLib\yaml-cpp\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> + <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Release|x64'">$(ProjectDir);$(ProjectDir)..\AppInstallerCommonCore;$(ProjectDir)..\AppInstallerRepositoryCore;$(ProjectDir)..\AppInstallerCommonCore\Public;$(ProjectDir)..\YamlCppLib\yaml-cpp\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> </ClCompile> <Link> <EnableCOMDATFolding>true</EnableCOMDATFolding> diff --git a/src/AppInstallerSQLiteIndexUtil/pch.h b/src/AppInstallerSQLiteIndexUtil/pch.h @@ -5,6 +5,8 @@ #define NOMINMAX #include <Windows.h> +#include <yaml-cpp/yaml.h> + #include <Public/AppInstallerFileLogger.h> #include <Public/AppInstallerStrings.h> #include <Public/AppInstallerLogging.h> @@ -16,4 +18,4 @@ #include <filesystem> #include <memory> #include <mutex> -#include <string> +#include <string>+ \ No newline at end of file