commit 888431f972ee803560cd7fa90ae99422f1599acb parent 76e1f563018c963e55362145a4529d04da74ea22 Author: yao-msft <50888816+yao-msft@users.noreply.github.com> Date: Wed, 22 Jan 2020 15:21:27 -0800 Add basic flow to install package from a given manifest (#20) * First iter. Install works * Refactor and add sha256 * Basic ui * Improvements * Fix test * PR comments * Add tests * PR comments * PR comments * PR comments Diffstat:
59 files changed, 1956 insertions(+), 136 deletions(-)
diff --git a/src/AppInstallerCLI.sln b/src/AppInstallerCLI.sln @@ -29,6 +29,8 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "AppInstallerCommonCore", "A EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "AppInstallerSQLiteIndexUtil", "AppInstallerSQLiteIndexUtil\AppInstallerSQLiteIndexUtil.vcxproj", "{FB313532-38B0-4676-9303-AB200AA13576}" EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "AppInstallerTestExeInstaller", "AppInstallerTestExeInstaller\AppInstallerTestExeInstaller.vcxproj", "{6CB84692-5994-407D-B9BD-9216AF77FE83}" +EndProject Global GlobalSection(SharedMSBuildProjectFiles) = preSolution catch2\catch2.vcxitems*{5295e21e-9868-4de2-a177-fbb97b36579b}*SharedItemsImports = 9 @@ -177,6 +179,18 @@ Global {FB313532-38B0-4676-9303-AB200AA13576}.Release|x64.Build.0 = Release|x64 {FB313532-38B0-4676-9303-AB200AA13576}.Release|x86.ActiveCfg = Release|Win32 {FB313532-38B0-4676-9303-AB200AA13576}.Release|x86.Build.0 = Release|Win32 + {6CB84692-5994-407D-B9BD-9216AF77FE83}.Debug|ARM.ActiveCfg = Debug|Win32 + {6CB84692-5994-407D-B9BD-9216AF77FE83}.Debug|ARM64.ActiveCfg = Debug|Win32 + {6CB84692-5994-407D-B9BD-9216AF77FE83}.Debug|x64.ActiveCfg = Debug|x64 + {6CB84692-5994-407D-B9BD-9216AF77FE83}.Debug|x64.Build.0 = Debug|x64 + {6CB84692-5994-407D-B9BD-9216AF77FE83}.Debug|x86.ActiveCfg = Debug|Win32 + {6CB84692-5994-407D-B9BD-9216AF77FE83}.Debug|x86.Build.0 = Debug|Win32 + {6CB84692-5994-407D-B9BD-9216AF77FE83}.Release|ARM.ActiveCfg = Release|Win32 + {6CB84692-5994-407D-B9BD-9216AF77FE83}.Release|ARM64.ActiveCfg = Release|Win32 + {6CB84692-5994-407D-B9BD-9216AF77FE83}.Release|x64.ActiveCfg = Release|x64 + {6CB84692-5994-407D-B9BD-9216AF77FE83}.Release|x64.Build.0 = Release|x64 + {6CB84692-5994-407D-B9BD-9216AF77FE83}.Release|x86.ActiveCfg = Release|Win32 + {6CB84692-5994-407D-B9BD-9216AF77FE83}.Release|x86.Build.0 = Release|Win32 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/src/AppInstallerCLI/AppInstallerCLI.vcxproj b/src/AppInstallerCLI/AppInstallerCLI.vcxproj @@ -130,6 +130,7 @@ <Link> <SubSystem>Console</SubSystem> <GenerateWindowsMetadata>false</GenerateWindowsMetadata> + <AdditionalDependencies Condition="'$(Configuration)'=='Debug'">wininet.lib;shell32.lib;%(AdditionalDependencies)</AdditionalDependencies> </Link> </ItemDefinitionGroup> <ItemDefinitionGroup Condition="'$(Platform)'=='Win32'"> @@ -154,6 +155,7 @@ <EnableCOMDATFolding>true</EnableCOMDATFolding> <OptimizeReferences>true</OptimizeReferences> <GenerateWindowsMetadata>false</GenerateWindowsMetadata> + <AdditionalDependencies Condition="'$(Configuration)'=='Release'">wininet.lib;shell32.lib;%(AdditionalDependencies)</AdditionalDependencies> </Link> </ItemDefinitionGroup> <ItemGroup> diff --git a/src/AppInstallerCLICore/AppInstallerCLICore.vcxproj b/src/AppInstallerCLICore/AppInstallerCLICore.vcxproj @@ -122,9 +122,9 @@ <ClCompile> <Optimization>Disabled</Optimization> <PreprocessorDefinitions>_DEBUG;%(PreprocessorDefinitions);CLICOREDLLBUILD</PreprocessorDefinitions> - <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Debug|ARM'">$(ProjectDir);$(ProjectDir)..\AppInstallerCommonCore\Public;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> - <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">$(ProjectDir);$(ProjectDir)..\AppInstallerCommonCore\Public;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> - <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">$(ProjectDir);$(ProjectDir)..\AppInstallerCommonCore\Public;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> + <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Debug|ARM'">$(ProjectDir);$(ProjectDir)\Public;$(ProjectDir)..\AppInstallerRepositoryCore;$(ProjectDir)..\AppInstallerCommonCore\Public;$(ProjectDir)..\YamlCppLib\yaml-cpp\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> + <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">$(ProjectDir);$(ProjectDir)\Public;$(ProjectDir)..\AppInstallerRepositoryCore;$(ProjectDir)..\AppInstallerCommonCore\Public;$(ProjectDir)..\YamlCppLib\yaml-cpp\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> + <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">$(ProjectDir);$(ProjectDir)\Public;$(ProjectDir)..\AppInstallerRepositoryCore;$(ProjectDir)..\AppInstallerCommonCore\Public;$(ProjectDir)..\YamlCppLib\yaml-cpp\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> </ClCompile> <Link> <GenerateWindowsMetadata>false</GenerateWindowsMetadata> @@ -136,7 +136,7 @@ <ItemDefinitionGroup Condition="'$(Platform)'=='Win32'"> <ClCompile> <PreprocessorDefinitions>WIN32;%(PreprocessorDefinitions);CLICOREDLLBUILD</PreprocessorDefinitions> - <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">$(ProjectDir);$(ProjectDir)..\AppInstallerCommonCore\Public;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> + <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">$(ProjectDir);$(ProjectDir)\Public;$(ProjectDir)..\AppInstallerRepositoryCore;$(ProjectDir)..\AppInstallerCommonCore\Public;$(ProjectDir)..\YamlCppLib\yaml-cpp\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> </ClCompile> <Link> <SubSystem Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">Windows</SubSystem> @@ -148,10 +148,10 @@ <FunctionLevelLinking>true</FunctionLevelLinking> <IntrinsicFunctions>true</IntrinsicFunctions> <PreprocessorDefinitions>NDEBUG;%(PreprocessorDefinitions);CLICOREDLLBUILD</PreprocessorDefinitions> - <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Release|ARM'">$(ProjectDir);$(ProjectDir)..\AppInstallerCommonCore\Public;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> - <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'">$(ProjectDir);$(ProjectDir)..\AppInstallerCommonCore\Public;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> - <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">$(ProjectDir);$(ProjectDir)..\AppInstallerCommonCore\Public;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> - <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Release|x64'">$(ProjectDir);$(ProjectDir)..\AppInstallerCommonCore\Public;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> + <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Release|ARM'">$(ProjectDir);$(ProjectDir)\Public;$(ProjectDir)..\AppInstallerRepositoryCore;$(ProjectDir)..\AppInstallerCommonCore\Public;$(ProjectDir)..\YamlCppLib\yaml-cpp\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> + <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'">$(ProjectDir);$(ProjectDir)\Public;$(ProjectDir)..\AppInstallerRepositoryCore;$(ProjectDir)..\AppInstallerCommonCore\Public;$(ProjectDir)..\YamlCppLib\yaml-cpp\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> + <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">$(ProjectDir);$(ProjectDir)\Public;$(ProjectDir)..\AppInstallerRepositoryCore;$(ProjectDir)..\AppInstallerCommonCore\Public;$(ProjectDir)..\YamlCppLib\yaml-cpp\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> + <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Release|x64'">$(ProjectDir);$(ProjectDir)\Public;$(ProjectDir)..\AppInstallerRepositoryCore;$(ProjectDir)..\AppInstallerCommonCore\Public;$(ProjectDir)..\YamlCppLib\yaml-cpp\include;$(ProjectDir)..\YamlCppLib\yaml-cpp\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> </ClCompile> <Link> <EnableCOMDATFolding>true</EnableCOMDATFolding> @@ -165,15 +165,19 @@ </ItemDefinitionGroup> <ItemGroup> <ClInclude Include="Command.h" /> + <ClInclude Include="Commands\Common.h" /> <ClInclude Include="Commands\DescribeCommand.h" /> <ClInclude Include="Commands\InstallCommand.h" /> <ClInclude Include="Commands\RootCommand.h" /> - <ClInclude Include="Common.h" /> + <ClInclude Include="Workflows\Common.h" /> + <ClInclude Include="Workflows\InstallFlow.h" /> <ClInclude Include="Invocation.h" /> <ClInclude Include="Localization.h" /> <ClInclude Include="pch.h" /> <ClInclude Include="Public\AppInstallerCLICore.h" /> <ClInclude Include="Search\Search.h" /> + <ClInclude Include="Workflows\ManifestComparator.h" /> + <ClInclude Include="Workflows\WorkflowReporter.h" /> </ItemGroup> <ItemGroup> <ClCompile Include="Command.cpp" /> @@ -181,14 +185,28 @@ <ClCompile Include="Commands\InstallCommand.cpp" /> <ClCompile Include="Commands\RootCommand.cpp" /> <ClCompile Include="Core.cpp" /> + <ClCompile Include="Workflows\ManifestComparator.cpp" /> + <ClCompile Include="Workflows\InstallFlow.cpp" /> <ClCompile Include="pch.cpp"> <PrecompiledHeader>Create</PrecompiledHeader> </ClCompile> + <ClCompile Include="Workflows\WorkflowReporter.cpp" /> </ItemGroup> <ItemGroup> <None Include="packages.config" /> <None Include="PropertySheet.props" /> </ItemGroup> + <ItemGroup> + <ProjectReference Include="..\AppInstallerCommonCore\AppInstallerCommonCore.vcxproj"> + <Project>{5890d6ed-7c3b-40f3-b436-b54f640d9e65}</Project> + </ProjectReference> + <ProjectReference Include="..\AppInstallerRepositoryCore\AppInstallerRepositoryCore.vcxproj"> + <Project>{5eb88068-5fb9-4e69-89b2-72dbc5e068f9}</Project> + </ProjectReference> + <ProjectReference Include="..\YamlCppLib\YamlCppLib.vcxproj"> + <Project>{8bb94bb8-374f-4294-bca1-c7811514a6b7}</Project> + </ProjectReference> + </ItemGroup> <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" /> <ImportGroup Label="ExtensionTargets"> <Import Project="$(SolutionDir)\packages\Microsoft.Windows.CppWinRT.2.0.191111.2\build\native\Microsoft.Windows.CppWinRT.targets" Condition="Exists('$(SolutionDir)\packages\Microsoft.Windows.CppWinRT.2.0.191111.2\build\native\Microsoft.Windows.CppWinRT.targets')" /> diff --git a/src/AppInstallerCLICore/AppInstallerCLICore.vcxproj.filters b/src/AppInstallerCLICore/AppInstallerCLICore.vcxproj.filters @@ -19,6 +19,9 @@ <Filter Include="Public"> <UniqueIdentifier>{00cc3138-2893-4fc4-8595-d3cf9d26be1c}</UniqueIdentifier> </Filter> + <Filter Include="Workflows"> + <UniqueIdentifier>{fdeb940e-93e2-4bb0-a59c-1e2d1c0588d1}</UniqueIdentifier> + </Filter> </ItemGroup> <ItemGroup> <ClInclude Include="pch.h"> @@ -42,15 +45,27 @@ <ClInclude Include="Invocation.h"> <Filter>Header Files</Filter> </ClInclude> - <ClInclude Include="Common.h"> - <Filter>Header Files</Filter> - </ClInclude> <ClInclude Include="Search\Search.h"> <Filter>Header Files</Filter> </ClInclude> <ClInclude Include="Public\AppInstallerCLICore.h"> <Filter>Public</Filter> </ClInclude> + <ClInclude Include="Workflows\InstallFlow.h"> + <Filter>Workflows</Filter> + </ClInclude> + <ClInclude Include="Workflows\ManifestComparator.h"> + <Filter>Workflows</Filter> + </ClInclude> + <ClInclude Include="Workflows\WorkflowReporter.h"> + <Filter>Workflows</Filter> + </ClInclude> + <ClInclude Include="Commands\Common.h"> + <Filter>Commands</Filter> + </ClInclude> + <ClInclude Include="Workflows\Common.h"> + <Filter>Workflows</Filter> + </ClInclude> </ItemGroup> <ItemGroup> <ClCompile Include="pch.cpp"> @@ -71,6 +86,15 @@ <ClCompile Include="Core.cpp"> <Filter>Source Files</Filter> </ClCompile> + <ClCompile Include="Workflows\InstallFlow.cpp"> + <Filter>Workflows</Filter> + </ClCompile> + <ClCompile Include="Workflows\ManifestComparator.cpp"> + <Filter>Workflows</Filter> + </ClCompile> + <ClCompile Include="Workflows\WorkflowReporter.cpp"> + <Filter>Workflows</Filter> + </ClCompile> </ItemGroup> <ItemGroup> <None Include="PropertySheet.props" /> diff --git a/src/AppInstallerCLICore/Command.cpp b/src/AppInstallerCLICore/Command.cpp @@ -174,7 +174,7 @@ namespace AppInstaller::CLI } } - void Command::Execute(Invocation& inv, std::ostream& out) const + void Command::Execute(Invocation& inv, std::ostream& out, std::istream& in) const { AICLI_LOG(CLI, Info, << "Executing command: " << Name()); if (inv.Contains(APPINSTALLER_CLI_HELP_ARGUMENT_TEXT_STRING)) @@ -183,11 +183,11 @@ namespace AppInstaller::CLI } else { - ExecuteInternal(inv, out); + ExecuteInternal(inv, out, in); } } - void Command::ExecuteInternal(Invocation&, std::ostream& out) const + void Command::ExecuteInternal(Invocation&, std::ostream& out, std::istream& in) const { out << LOCME("Oops, we forgot to do this...") << std::endl; THROW_HR(E_NOTIMPL); diff --git a/src/AppInstallerCLICore/Command.h b/src/AppInstallerCLICore/Command.h @@ -8,7 +8,6 @@ #include <type_traits> #include <vector> -#include "Common.h" #include "Invocation.h" #define APPINSTALLER_CLI_ARGUMENT_IDENTIFIER_CHAR '-' @@ -21,14 +20,14 @@ namespace AppInstaller::CLI { struct CommandException { - CommandException(std::string message, std::string param) : m_message(message), m_param(param) {} + CommandException(std::string message, std::string_view param) : m_message(message), m_param(param) {} const std::string& Message() const { return m_message; } - const std::string& Param() const { return m_param; } + const std::string_view Param() const { return m_param; } private: std::string m_message; - std::string m_param; + std::string_view m_param; }; enum class ArgumentType @@ -43,16 +42,16 @@ namespace AppInstaller::CLI struct Argument { - Argument(StringLiteralPtr name, std::string desc) : + Argument(std::string_view name, std::string desc) : m_name(name), m_desc(std::move(desc)) {} - Argument(StringLiteralPtr name, std::string desc, bool required) : + Argument(std::string_view name, std::string desc, bool required) : m_name(name), m_desc(std::move(desc)), m_required(required) {} - Argument(StringLiteralPtr name, std::string desc, ArgumentType type) : + Argument(std::string_view name, std::string desc, ArgumentType type) : m_name(name), m_desc(std::move(desc)), m_type(type) {} - Argument(StringLiteralPtr name, std::string desc, ArgumentType type, bool required) : + Argument(std::string_view name, std::string desc, ArgumentType type, bool required) : m_name(name), m_desc(std::move(desc)), m_type(type), m_required(required) {} ~Argument() = default; @@ -63,14 +62,14 @@ namespace AppInstaller::CLI Argument(Argument&&) = default; Argument& operator=(Argument&&) = default; - StringLiteralPtrRef Name() const { return m_name; } + std::string_view Name() const { return m_name; } std::string Description() const { return m_desc; } bool Required() const { return m_required; } ArgumentType Type() const { return m_type; } size_t Limit() const { return m_countLimit; } private: - StringLiteralPtrRef m_name; + std::string_view m_name; std::string m_desc; bool m_required = false; ArgumentType m_type = ArgumentType::Standard; @@ -79,7 +78,7 @@ namespace AppInstaller::CLI struct Command { - Command(StringLiteralPtr name) : m_name(name) {} + Command(std::string_view name) : m_name(name) {} virtual ~Command() = default; Command(const Command&) = default; @@ -88,7 +87,7 @@ namespace AppInstaller::CLI Command(Command&&) = default; Command& operator=(Command&&) = default; - StringLiteralPtrRef Name() const { return m_name; } + std::string_view Name() const { return m_name; } virtual std::vector<std::unique_ptr<Command>> GetCommands() const { return {}; } virtual std::vector<Argument> GetArguments() const { return {}; } @@ -103,13 +102,13 @@ namespace AppInstaller::CLI virtual void ParseArguments(Invocation& inv) const; virtual void ValidateArguments(Invocation& inv) const; - virtual void Execute(Invocation& inv, std::ostream& out) const; + virtual void Execute(Invocation& inv, std::ostream& out, std::istream& in) const; protected: - virtual void ExecuteInternal(Invocation& inv, std::ostream& out) const; + virtual void ExecuteInternal(Invocation& inv, std::ostream& out, std::istream& in) const; private: - StringLiteralPtrRef m_name; + std::string_view m_name; }; template <typename Container> diff --git a/src/AppInstallerCLICore/Commands/Common.h b/src/AppInstallerCLICore/Commands/Common.h @@ -0,0 +1,13 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +#pragma once + +#include "pch.h" +using namespace std::string_view_literals; + +namespace AppInstaller::CLI +{ + static constexpr std::string_view ARG_APPLICATION = "application"sv; + static constexpr std::string_view ARG_MANIFEST = "manifest"sv; +}+ \ No newline at end of file diff --git a/src/AppInstallerCLICore/Commands/DescribeCommand.cpp b/src/AppInstallerCLICore/Commands/DescribeCommand.cpp @@ -1,6 +1,7 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. #include "pch.h" +#include "Common.h" #include "DescribeCommand.h" #include "Localization.h" @@ -9,8 +10,8 @@ namespace AppInstaller::CLI std::vector<Argument> DescribeCommand::GetArguments() const { return { - Argument{ "application", LOCME("The name of the application to install"), ArgumentType::Positional, true }, - Argument{ "manifest", LOCME("If specified, output the full manifest file"), ArgumentType::Flag }, + Argument{ ARG_APPLICATION, LOCME("The name of the application to install"), ArgumentType::Positional, true }, + Argument{ ARG_MANIFEST, LOCME("If specified, output the full manifest file"), ArgumentType::Flag }, }; } diff --git a/src/AppInstallerCLICore/Commands/InstallCommand.cpp b/src/AppInstallerCLICore/Commands/InstallCommand.cpp @@ -1,15 +1,22 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. #include "pch.h" +#include "Common.h" #include "InstallCommand.h" #include "Localization.h" +#include "Manifest\Manifest.h" +#include "Workflows\InstallFlow.h" + +using namespace AppInstaller::Manifest; +using namespace AppInstaller::Workflow; namespace AppInstaller::CLI { std::vector<Argument> InstallCommand::GetArguments() const { return { - Argument{ "application", LOCME("The name of the application to install"), ArgumentType::Positional, true }, + 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 }, }; } @@ -24,4 +31,29 @@ namespace AppInstaller::CLI LOCME("Installs the given application"), }; } + + void InstallCommand::ExecuteInternal(Invocation& inv, std::ostream& out, std::istream& in) const + { + if (inv.Contains(ARG_MANIFEST)) + { + std::string manifest = *(inv.GetArg(ARG_MANIFEST)); + Manifest::Manifest packageManifest = Manifest::Manifest::CreateFromPath(manifest); + InstallFlow packageInstall(packageManifest, out, in); + packageInstall.Install(); + } + else + { + out << "Not supported!" << std::endl; + } + } + + void InstallCommand::ValidateArguments(Invocation& inv) const + { + Command::ValidateArguments(inv); + + if (!inv.Contains(ARG_APPLICATION) && !inv.Contains(ARG_MANIFEST)) + { + throw CommandException(LOCME("Required argument not provided"), ARG_APPLICATION); + } + } } diff --git a/src/AppInstallerCLICore/Commands/InstallCommand.h b/src/AppInstallerCLICore/Commands/InstallCommand.h @@ -9,9 +9,13 @@ namespace AppInstaller::CLI { InstallCommand() : Command("install") {} - virtual std::vector<Argument> GetArguments() const override; + std::vector<Argument> GetArguments() const override; - virtual std::string ShortDescription() const override; - virtual std::vector<std::string> GetLongDescription() const override; + std::string ShortDescription() const override; + std::vector<std::string> GetLongDescription() const override; + + protected: + void ExecuteInternal(Invocation& inv, std::ostream& out, std::istream& in) const override; + void ValidateArguments(Invocation& inv) const override; }; } diff --git a/src/AppInstallerCLICore/Commands/RootCommand.cpp b/src/AppInstallerCLICore/Commands/RootCommand.cpp @@ -25,7 +25,7 @@ namespace AppInstaller::CLI }; } - void RootCommand::ExecuteInternal(Invocation&, std::ostream& out) const + void RootCommand::ExecuteInternal(Invocation&, std::ostream& out, std::istream& in) const { OutputHelp(out); } diff --git a/src/AppInstallerCLICore/Commands/RootCommand.h b/src/AppInstallerCLICore/Commands/RootCommand.h @@ -14,6 +14,6 @@ namespace AppInstaller::CLI virtual std::vector<std::string> GetLongDescription() const override; protected: - virtual void ExecuteInternal(Invocation& inv, std::ostream& out) const; + virtual void ExecuteInternal(Invocation& inv, std::ostream& out, std::istream& in) const; }; } diff --git a/src/AppInstallerCLICore/Common.h b/src/AppInstallerCLICore/Common.h @@ -1,9 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once - -namespace AppInstaller::CLI -{ - using StringLiteralPtr = char const* const; - using StringLiteralPtrRef = char const*; -} diff --git a/src/AppInstallerCLICore/Core.cpp b/src/AppInstallerCLICore/Core.cpp @@ -66,7 +66,7 @@ namespace AppInstaller::CLI try { - commandToExecute->Execute(invocation, std::cout); + commandToExecute->Execute(invocation, std::cout, std::cin); } // Exceptions that may occur in the process of executing an arbitrary command catch (const winrt::hresult_error& hre) diff --git a/src/AppInstallerCLICore/Invocation.h b/src/AppInstallerCLICore/Invocation.h @@ -4,8 +4,6 @@ #include <map> #include <vector> -#include "Common.h" - namespace AppInstaller::CLI { struct Invocation @@ -43,24 +41,37 @@ namespace AppInstaller::CLI iterator end() { return { m_args.size(), m_args }; } void consume(const iterator& i) { m_currentFirstArg = i.index() + 1; } - bool Contains(StringLiteralPtrRef name) const { return (m_parsedArgs.count(name) != 0); } - const std::vector<std::string>* GetArgs(StringLiteralPtrRef name) const + bool Contains(std::string_view name) const { return (m_parsedArgs.count(name) != 0); } + const std::vector<std::string>* GetArgs(std::string_view name) const { auto itr = m_parsedArgs.find(name); return (itr == m_parsedArgs.end() ? nullptr : &(itr->second)); } - size_t GetCount(StringLiteralPtrRef name) const + + const std::string* GetArg(std::string_view name) const + { + auto itr = m_parsedArgs.find(name); + + if (itr == m_parsedArgs.end()) + { + return nullptr; + } + + return &(itr->second[0]); + } + + size_t GetCount(std::string_view name) const { auto args = GetArgs(name); return (args ? args->size() : 0); } - bool AddArg(StringLiteralPtrRef name) + bool AddArg(std::string_view name) { AICLI_LOG(CLI, Verbose, << "Found flag: " << name); return m_parsedArgs[name].empty(); } - void AddArg(StringLiteralPtrRef name, std::string value) + void AddArg(std::string_view name, std::string value) { AICLI_LOG(CLI, Verbose, << "Found argument with value: " << name << " => " << value); m_parsedArgs[name].emplace_back(std::move(value)); @@ -70,6 +81,6 @@ namespace AppInstaller::CLI std::vector<std::string> m_args; size_t m_currentFirstArg = 0; - std::map<std::string, std::vector<std::string>> m_parsedArgs; + std::map<std::string_view, std::vector<std::string>> m_parsedArgs; }; } diff --git a/src/AppInstallerCLICore/Public/AppInstallerCLICore.h b/src/AppInstallerCLICore/Public/AppInstallerCLICore.h @@ -7,6 +7,11 @@ #define CLICORE_ERROR_INTERNAL_ERROR 0x8A150001 #define CLICORE_ERROR_INVALID_CL_ARGUMENTS 0x8A150002 #define CLICORE_ERROR_COMMAND_FAILED 0x8A150003 +#define CLICORE_ERROR_MANIFEST_FAILED 0x8A150004 +#define CLICORE_ERROR_WORKFLOW_FAILED 0x8A150005 +#define CLICORE_ERROR_INSTALLFLOW_FAILED 0x8A150006 +#define CLICORE_ERROR_RUNTIME_ERROR 0x8A150007 +#define CLICORE_ERROR_DOWNLOAD_FAILED 0x8A150008 namespace AppInstaller::CLI { diff --git a/src/AppInstallerCLICore/Workflows/Common.h b/src/AppInstallerCLICore/Workflows/Common.h @@ -0,0 +1,13 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +#pragma once + +namespace AppInstaller::Workflow +{ + class WorkflowException : public wil::ResultException + { + public: + WorkflowException(HRESULT hr) : wil::ResultException(hr) {} + }; +}+ \ No newline at end of file diff --git a/src/AppInstallerCLICore/Workflows/InstallFlow.cpp b/src/AppInstallerCLICore/Workflows/InstallFlow.cpp @@ -0,0 +1,169 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +#include "pch.h" +#include "InstallFlow.h" +#include "AppInstallerDownloader.h" +#include "ManifestComparator.h" + +using namespace AppInstaller::Utility; +using namespace AppInstaller::Manifest; + +namespace AppInstaller::Workflow { + + void InstallFlow::Install() + { + ManifestComparator manifestComparator(m_packageManifest, m_reporter); + + m_selectedLocalization = manifestComparator.GetPreferredLocalization(std::locale("")); + + m_reporter.ShowPackageInfo( + m_packageManifest.Name, + m_packageManifest.Version, + m_packageManifest.Author, + m_selectedLocalization.Description, + m_selectedLocalization.Homepage, + m_selectedLocalization.LicenseUrl + ); + + m_selectedInstaller = manifestComparator.GetPreferredInstaller(std::locale("")); + + DownloadInstaller(); + ExecuteInstaller(); + } + + void InstallFlow::DownloadInstaller() + { + // Todo: Rework the path logic. The new path logic should work with MOTW. + std::filesystem::path tempInstallerPath = Runtime::GetPathToTemp(); + tempInstallerPath /= m_packageManifest.Id + '_' + m_packageManifest.Version + '.' + m_selectedInstaller.InstallerType; + + AICLI_LOG(CLI, Info, << "Generated temp download path: " << tempInstallerPath); + + auto downloader = Downloader::StartDownloadAsync( + m_selectedInstaller.Url, + tempInstallerPath, + true, + &m_reporter.GetDownloaderCallback()); + + auto downloadResult = downloader->Wait(); + + if (downloadResult == DownloaderResult::Failed) + { + m_reporter.ShowMsg(WorkflowReporter::Level::Error, "Package download failed."); + THROW_EXCEPTION_MSG(WorkflowException(CLICORE_ERROR_INSTALLFLOW_FAILED), "Package download failed"); + } + else if (downloadResult == DownloaderResult::Canceled) + { + m_reporter.ShowMsg(WorkflowReporter::Level::Info, "Package download canceled."); + THROW_EXCEPTION_MSG(WorkflowException(CLICORE_ERROR_INSTALLFLOW_FAILED), "Package download canceled"); + } + + if (!std::equal( + m_selectedInstaller.Sha256.begin(), + m_selectedInstaller.Sha256.end(), + downloader->GetDownloadHash().begin())) + { + AICLI_LOG(CLI, Error, + << "Package hash verification failed. SHA256 in manifest: " + << SHA256::ConvertToString(m_selectedInstaller.Sha256) + << "SHA256 from download: " + << SHA256::ConvertToString(downloader->GetDownloadHash())); + + if (!m_reporter.PromptForBoolResponse(WorkflowReporter::Level::Warning, "Package hash verification failed. Continue?")) + { + m_reporter.ShowMsg(WorkflowReporter::Level::Error, "Canceled. Package hash mismatch."); + THROW_EXCEPTION_MSG(WorkflowException(CLICORE_ERROR_INSTALLFLOW_FAILED), "Package installation canceled"); + } + } + else + { + AICLI_LOG(CLI, Info, << "Downloaded package hash verified"); + m_reporter.ShowMsg(WorkflowReporter::Level::Info, "Successfully verified SHA256."); + } + + m_downloadedInstaller = tempInstallerPath; + } + + void InstallFlow::ExecuteInstaller() + { + if (m_downloadedInstaller.empty()) + { + THROW_EXCEPTION_MSG(WorkflowException(CLICORE_ERROR_INSTALLFLOW_FAILED), "Installer not downloaded yet"); + } + + m_reporter.ShowMsg(WorkflowReporter::Level::Info, "Installing package ..."); + + std::string installerArgs = GetInstallerArgs(); + AICLI_LOG(CLI, Info, << "Installer args: " << installerArgs); + + // Todo: add support for other installer types + std::future<DWORD> installTask; + if (Utility::ToLower(m_selectedInstaller.InstallerType) == "exe") + { + installTask = ExecuteExeInstallerAsync(m_downloadedInstaller, installerArgs); + } + else + { + m_reporter.ShowMsg(WorkflowReporter::Level::Error, "Installer type not supported."); + THROW_EXCEPTION_MSG(WorkflowException(CLICORE_ERROR_INSTALLFLOW_FAILED), "Installer type not supported"); + } + + m_reporter.ShowIndefiniteSpinner(true); + + installTask.wait(); + + m_reporter.ShowIndefiniteSpinner(false); + + auto installResult = installTask.get(); + + if (installResult != 0) + { + m_reporter.ShowMsg(WorkflowReporter::Level::Error, "Install failed. Exit code: " + std::to_string(installResult)); + + THROW_EXCEPTION_MSG(WorkflowException(CLICORE_ERROR_INSTALLFLOW_FAILED), + "Install failed. Installer task returned: %u", installResult); + } + + m_reporter.ShowMsg(WorkflowReporter::Level::Info, "Successfully installed!"); + } + + std::future<DWORD> InstallFlow::ExecuteExeInstallerAsync(const std::filesystem::path& filePath, const std::string& args) + { + AICLI_LOG(CLI, Info, << "Staring EXE installer. Path: " << filePath); + return std::async(std::launch::async, [&filePath, &args] { + + SHELLEXECUTEINFOA execInfo = { 0 }; + execInfo.cbSize = sizeof(SHELLEXECUTEINFO); + execInfo.fMask = SEE_MASK_NOCLOSEPROCESS; + execInfo.lpFile = Utility::ConvertToUTF8(filePath.c_str()).c_str(); + execInfo.lpParameters = args.c_str(); + execInfo.nShow = SW_SHOW; + if (!ShellExecuteExA(&execInfo) || !execInfo.hProcess) + { + return GetLastError(); + } + // Wait for installation to finish + WaitForSingleObject(execInfo.hProcess, INFINITE); + + // Get exe exit code + DWORD exitCode; + GetExitCodeProcess(execInfo.hProcess, &exitCode); + + CloseHandle(execInfo.hProcess); + + return exitCode; + }); + } + + std::string InstallFlow::GetInstallerArgs() + { + // Todo: Implement arg selection logic. + if (m_selectedInstaller.Switches.has_value()) + { + return m_selectedInstaller.Switches.value().Default; + } + + return ""; + } +}+ \ No newline at end of file diff --git a/src/AppInstallerCLICore/Workflows/InstallFlow.h b/src/AppInstallerCLICore/Workflows/InstallFlow.h @@ -0,0 +1,31 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +#pragma once +#include "Common.h" +#include "WorkflowReporter.h" + +namespace AppInstaller::Workflow { + + class InstallFlow + { + public: + InstallFlow(AppInstaller::Manifest::Manifest manifest, std::ostream& outStream, std::istream& inStream) : + m_packageManifest(manifest), m_reporter(outStream, inStream) {} + + void Install(); + + protected: + AppInstaller::Manifest::Manifest m_packageManifest; + AppInstaller::Manifest::ManifestInstaller m_selectedInstaller; + AppInstaller::Manifest::ManifestLocalization m_selectedLocalization; + std::filesystem::path m_downloadedInstaller; + WorkflowReporter m_reporter; + + virtual void DownloadInstaller(); + virtual void ExecuteInstaller(); + std::string GetInstallerArgs(); + + std::future<DWORD> ExecuteExeInstallerAsync(const std::filesystem::path& filePath, const std::string& args); + }; +}+ \ No newline at end of file diff --git a/src/AppInstallerCLICore/Workflows/ManifestComparator.cpp b/src/AppInstallerCLICore/Workflows/ManifestComparator.cpp @@ -0,0 +1,92 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +#include "pch.h" +#include "Common.h" +#include "WorkflowReporter.h" +#include "ManifestComparator.h" + +using namespace AppInstaller::Manifest; + +namespace AppInstaller::Workflow +{ + bool InstallerComparator::operator() (const ManifestInstaller& installer1, const ManifestInstaller& installer2) + { + // Todo: Comapre only architecture for now. Need more work and spec. + if (Utility::IsApplicableArchitecture(installer1.Arch) < Utility::IsApplicableArchitecture(installer2.Arch)) + { + return false; + } + + return true; + } + + bool LocalizationComparator::operator() (const ManifestLocalization& loc1, const ManifestLocalization& loc2) + { + // Todo: Compare simple language for now. Need more work and spec. + std::string userPreferredLocale = std::locale("").name(); + + auto found = userPreferredLocale.find(loc1.Language); + + if (found != std::string::npos) + { + return false; + } + + return true; + } + + ManifestInstaller ManifestComparator::GetPreferredInstaller(const std::locale& preferredLocale) + { + AICLI_LOG(CLI, Info, << "Starting installer selection."); + + // Sorting the list of availlable installers according to rules defined in InstallerComparator. + std::sort(m_manifestRef.Installers.begin(), m_manifestRef.Installers.end(), InstallerComparator()); + + // If the first one is inapplicable, then no installer is applicable. + if (Utility::IsApplicableArchitecture(m_manifestRef.Installers[0].Arch) == -1) + { + m_reporterRef.ShowMsg(WorkflowReporter::Level::Error, "No applicable installer found."); + THROW_EXCEPTION_MSG(WorkflowException(CLICORE_ERROR_WORKFLOW_FAILED), "No installer with applicable architecture found."); + } + + ManifestInstaller selectedInstaller = m_manifestRef.Installers[0]; + + AICLI_LOG(CLI, Info, << "Completed installer selection."); + AICLI_LOG(CLI, Verbose, << "Selected installer arch: " << (int)selectedInstaller.Arch); + AICLI_LOG(CLI, Verbose, << "Selected installer url: " << selectedInstaller.Url); + AICLI_LOG(CLI, Verbose, << "Selected installer InstallerType: " << selectedInstaller.InstallerType); + AICLI_LOG(CLI, Verbose, << "Selected installer scope: " << selectedInstaller.Scope); + AICLI_LOG(CLI, Verbose, << "Selected installer language: " << selectedInstaller.Language); + + return selectedInstaller; + } + + ManifestLocalization ManifestComparator::GetPreferredLocalization(const std::locale& preferredLocale) + { + AICLI_LOG(CLI, Info, << "Starting localization selection."); + + ManifestLocalization selectedLocalization; + + // Sorting the list of availlable localizations according to rules defined in LocalizationComparator. + if (!m_manifestRef.Localization.empty()) + { + std::sort(m_manifestRef.Localization.begin(), m_manifestRef.Localization.end(), LocalizationComparator()); + + // TODO: needs to check language applicability here + + selectedLocalization = m_manifestRef.Localization[0]; + } + else + { + // Pupulate default from package manifest + selectedLocalization.Description = m_manifestRef.Description; + selectedLocalization.Homepage = m_manifestRef.Homepage; + selectedLocalization.LicenseUrl = m_manifestRef.LicenseUrl; + } + + AICLI_LOG(CLI, Info, << "Completed localization selection. Selected localization language: " << selectedLocalization.Language); + + return selectedLocalization; + } +}+ \ No newline at end of file diff --git a/src/AppInstallerCLICore/Workflows/ManifestComparator.h b/src/AppInstallerCLICore/Workflows/ManifestComparator.h @@ -0,0 +1,38 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +#pragma once + +namespace AppInstaller::Workflow +{ + // This is used in sorting the list of available installers to get the best match. + struct InstallerComparator + { + bool operator() ( + const AppInstaller::Manifest::ManifestInstaller& installer1, + const AppInstaller::Manifest::ManifestInstaller& installer2); + }; + + // This is used in sorting the list of available localizations to get the best match. + struct LocalizationComparator + { + bool operator() ( + const AppInstaller::Manifest::ManifestLocalization& loc1, + const AppInstaller::Manifest::ManifestLocalization& loc2); + }; + + // Class in charge of comparing manifest entries + class ManifestComparator + { + public: + ManifestComparator(AppInstaller::Manifest::Manifest& manifest, WorkflowReporter& reporter) : m_manifestRef(manifest), m_reporterRef(reporter) {} + + AppInstaller::Manifest::ManifestInstaller GetPreferredInstaller(const std::locale& preferredLocale); + AppInstaller::Manifest::ManifestLocalization GetPreferredLocalization(const std::locale& preferredLocale); + + private: + AppInstaller::Manifest::Manifest& m_manifestRef; + WorkflowReporter& m_reporterRef; + }; + +}+ \ No newline at end of file diff --git a/src/AppInstallerCLICore/Workflows/WorkflowReporter.cpp b/src/AppInstallerCLICore/Workflows/WorkflowReporter.cpp @@ -0,0 +1,116 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +#include "pch.h" +#include "WorkflowReporter.h" + +namespace AppInstaller::Workflow +{ + void DownloaderCallback::OnStarted() + { + out << "Starting package download ..." << std::endl; + } + + void DownloaderCallback::OnProgress(LONGLONG progress, LONGLONG downloadSize) + { + out << "\rDownloading " << progress << '/' << downloadSize; + + if (progress == downloadSize) + { + out << std::endl; + } + } + + void DownloaderCallback::OnCanceled() + { + out << "Package download canceled ..." << std::endl; + } + + void DownloaderCallback::OnCompleted() + { + out << "Package download completed ..." << std::endl; + } + + void WorkflowReporter::ShowPackageInfo( + const std::string& name, + const std::string& version, + const std::string& author, + const std::string& description, + const std::string& homepage, + const std::string& licenceUrl + ) + { + out << "Name: " << name << std::endl; + out << "Version: " << version << std::endl; + out << "Author: " << author << std::endl; + out << "Description: " << description << std::endl; + out << "Homepage: " << homepage << std::endl; + out << "Licence: " << licenceUrl << std::endl; + } + + bool WorkflowReporter::PromptForBoolResponse(Level level, const std::string& msg) + { + out << msg << " (Y|N)" << std::endl; + + char response; + in.get(response); + + return tolower(response) == 'y'; + } + + void WorkflowReporter::ShowMsg(Level level, const std::string& msg) + { + // Todo: color output using level and possibly other factors. + out << msg << std::endl; + } + + void WorkflowReporter::ShowIndefiniteSpinner(bool running) + { + if (running) + { + m_spinner.ShowSpinner(); + } + else + { + m_spinner.StopSpinner(); + } + } + + void IndefiniteSpinner::ShowSpinner() + { + if (!m_spinnerJob.valid() && !m_spinnerRunning && !m_canceled) + { + m_spinnerRunning = true; + m_spinnerJob = std::async(std::launch::async, &IndefiniteSpinner::ShowSpinnerInternal, this); + } + } + + void IndefiniteSpinner::ShowSpinnerInternal() + { + char spinnerChars[] = { '-', '\\', '|', '/' }; + + for (int i = 0; !m_canceled; i++) { + out << '\b' << spinnerChars[i] << std::flush; + + if (i == 3) + { + i = -1; + } + + Sleep(300); + } + + out << '\b'; + m_canceled = false; + m_spinnerRunning = false; + } + + void IndefiniteSpinner::StopSpinner() + { + if (!m_canceled && m_spinnerJob.valid() && m_spinnerRunning) + { + m_canceled = true; + m_spinnerJob.wait(); + } + } +}+ \ No newline at end of file diff --git a/src/AppInstallerCLICore/Workflows/WorkflowReporter.h b/src/AppInstallerCLICore/Workflows/WorkflowReporter.h @@ -0,0 +1,83 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +#pragma once + +#include "AppInstallerDownloader.h" + +namespace AppInstaller::Workflow +{ + // This will be triggered by file downloader to get download progress + class DownloaderCallback : public AppInstaller::Utility::IDownloaderCallback + { + public: + DownloaderCallback(std::ostream& stream) : out(stream) {}; + + void OnStarted() override; + void OnProgress(LONGLONG progress, LONGLONG downloadSize) override; + void OnCanceled() override; + void OnCompleted() override; + + private: + std::ostream& out; + }; + + // Class to print the in progress spinner + class IndefiniteSpinner + { + public: + IndefiniteSpinner(std::ostream& stream) : out(stream) {}; + + void ShowSpinner(); + void StopSpinner(); + + private: + std::atomic<bool> m_canceled = false; + std::atomic<bool> m_spinnerRunning = false; + std::future<void> m_spinnerJob; + std::ostream& out; + + void ShowSpinnerInternal(); + }; + + // WorkflowReporter should be the central place to show workflow status to user. + // Todo: need to implement actual console output to show color, progress bar, etc + class WorkflowReporter + { + public: + + enum class Level + { + Verbose, + Info, + Warning, + Error, + }; + + WorkflowReporter(std::ostream& outStream, std::istream& inStream) : + out(outStream), in(inStream), m_downloaderCallback(outStream), m_spinner(outStream) {}; + + void ShowPackageInfo( + const std::string& name, + const std::string& version, + const std::string& author, + const std::string& description, + const std::string& homepage, + const std::string& licenceUrl); + + bool PromptForBoolResponse(Level level, const std::string& msg); + + void ShowMsg(Level level, const std::string& msg); + + // running: shows the spinner if set to true, stops the spinner if set to false + void ShowIndefiniteSpinner(bool running); + + DownloaderCallback& GetDownloaderCallback() { return m_downloaderCallback; } + + private: + std::ostream& out; + std::istream& in; + DownloaderCallback m_downloaderCallback; + IndefiniteSpinner m_spinner; + }; +}+ \ No newline at end of file diff --git a/src/AppInstallerCLICore/pch.h b/src/AppInstallerCLICore/pch.h @@ -3,14 +3,28 @@ #pragma once #include <windows.h> -#include "AppInstallerLogging.h" -#include "AppInstallerTelemetry.h" -#include "AppInstallerStrings.h" +#include <WinInet.h> #include <winrt/Windows.Foundation.h> #include <winrt/Windows.Foundation.Collections.h> +#include <wil/result_macros.h> + #include <iostream> +#include <fstream> #include <memory> #include <sstream> #include <vector> +#include <future> +#include <string_view> + +#include <yaml-cpp\yaml.h> + +#include "AppInstallerLogging.h" +#include "AppInstallerTelemetry.h" +#include "AppInstallerStrings.h" +#include "AppInstallerRuntime.h" +#include "AppInstallerSHA256.h" +#include "AppInstallerDownloader.h" +#include "Manifest/ManifestInstaller.h" +#include "Manifest/Manifest.h" diff --git a/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj b/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj @@ -108,21 +108,21 @@ <ClCompile> <Optimization>Disabled</Optimization> <PreprocessorDefinitions>_DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions> - <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">$(MSBuildThisFileDirectory)..\AppInstallerCommonCore;$(MSBuildThisFileDirectory)..\AppInstallerRepositoryCore;%(AdditionalIncludeDirectories);$(MSBuildThisFileDirectory)..\YamlCppLib\yaml-cpp\include</AdditionalIncludeDirectories> + <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">$(MSBuildThisFileDirectory)..\AppInstallerCommonCore;$(MSBuildThisFileDirectory)..\AppInstallerRepositoryCore;$(MSBuildThisFileDirectory)..\AppInstallerCommonCore\Public;$(MSBuildThisFileDirectory)..\AppInstallerCLICore\Public;$(MSBuildThisFileDirectory)..\YamlCppLib\yaml-cpp\include;$(MSBuildThisFileDirectory)..\AppInstallerCLICore;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> </ClCompile> <Link> <SubSystem>Console</SubSystem> <GenerateWindowsMetadata>false</GenerateWindowsMetadata> - <AdditionalDependencies Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">winsqlite3.lib;%(AdditionalDependencies)</AdditionalDependencies> + <AdditionalDependencies Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">wininet.lib;shell32.lib;winsqlite3.lib;%(AdditionalDependencies)</AdditionalDependencies> </Link> </ItemDefinitionGroup> <ItemDefinitionGroup Condition="'$(Platform)'=='Win32'"> <ClCompile> <PreprocessorDefinitions>WIN32;%(PreprocessorDefinitions)</PreprocessorDefinitions> - <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">$(MSBuildThisFileDirectory)..\AppInstallerCommonCore;$(MSBuildThisFileDirectory)..\AppInstallerRepositoryCore;%(AdditionalIncludeDirectories);$(MSBuildThisFileDirectory)..\YamlCppLib\yaml-cpp\include</AdditionalIncludeDirectories> + <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">$(MSBuildThisFileDirectory)..\AppInstallerCommonCore;$(MSBuildThisFileDirectory)..\AppInstallerRepositoryCore;$(MSBuildThisFileDirectory)..\AppInstallerCommonCore\Public;$(MSBuildThisFileDirectory)..\AppInstallerCLICore\Public;$(MSBuildThisFileDirectory)..\YamlCppLib\yaml-cpp\include;$(MSBuildThisFileDirectory)..\AppInstallerCLICore;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> </ClCompile> <Link> - <AdditionalDependencies Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">winsqlite3.lib;%(AdditionalDependencies)</AdditionalDependencies> + <AdditionalDependencies Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">wininet.lib;shell32.lib;winsqlite3.lib;%(AdditionalDependencies)</AdditionalDependencies> </Link> </ItemDefinitionGroup> <ItemDefinitionGroup Condition="'$(Configuration)'=='Release'"> @@ -131,16 +131,16 @@ <FunctionLevelLinking>true</FunctionLevelLinking> <IntrinsicFunctions>true</IntrinsicFunctions> <PreprocessorDefinitions>NDEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions> - <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">$(MSBuildThisFileDirectory)..\AppInstallerCommonCore;$(MSBuildThisFileDirectory)..\AppInstallerRepositoryCore;%(AdditionalIncludeDirectories);$(MSBuildThisFileDirectory)..\YamlCppLib\yaml-cpp\include</AdditionalIncludeDirectories> - <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Release|x64'">$(MSBuildThisFileDirectory)..\AppInstallerCommonCore;$(MSBuildThisFileDirectory)..\AppInstallerRepositoryCore;%(AdditionalIncludeDirectories);$(MSBuildThisFileDirectory)..\YamlCppLib\yaml-cpp\include</AdditionalIncludeDirectories> + <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">$(MSBuildThisFileDirectory)..\AppInstallerCommonCore;$(MSBuildThisFileDirectory)..\AppInstallerRepositoryCore;$(MSBuildThisFileDirectory)..\AppInstallerCommonCore\Public;$(MSBuildThisFileDirectory)..\AppInstallerCLICore\Public;$(MSBuildThisFileDirectory)..\YamlCppLib\yaml-cpp\include;$(MSBuildThisFileDirectory)..\AppInstallerCLICore;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> + <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Release|x64'">$(MSBuildThisFileDirectory)..\AppInstallerCommonCore;$(MSBuildThisFileDirectory)..\AppInstallerRepositoryCore;$(MSBuildThisFileDirectory)..\AppInstallerCommonCore\Public;$(MSBuildThisFileDirectory)..\AppInstallerCLICore\Public;$(MSBuildThisFileDirectory)..\YamlCppLib\yaml-cpp\include;$(MSBuildThisFileDirectory)..\AppInstallerCLICore;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> </ClCompile> <Link> <SubSystem>Console</SubSystem> <EnableCOMDATFolding>true</EnableCOMDATFolding> <OptimizeReferences>true</OptimizeReferences> <GenerateWindowsMetadata>false</GenerateWindowsMetadata> - <AdditionalDependencies Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">winsqlite3.lib;%(AdditionalDependencies)</AdditionalDependencies> - <AdditionalDependencies Condition="'$(Configuration)|$(Platform)'=='Release|x64'">winsqlite3.lib;%(AdditionalDependencies)</AdditionalDependencies> + <AdditionalDependencies Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">wininet.lib;shell32.lib;winsqlite3.lib;%(AdditionalDependencies)</AdditionalDependencies> + <AdditionalDependencies Condition="'$(Configuration)|$(Platform)'=='Release|x64'">wininet.lib;shell32.lib;winsqlite3.lib;%(AdditionalDependencies)</AdditionalDependencies> </Link> </ItemDefinitionGroup> <ItemGroup> @@ -148,6 +148,8 @@ <ClInclude Include="TestCommon.h" /> </ItemGroup> <ItemGroup> + <ClCompile Include="Downloader.cpp" /> + <ClCompile Include="InstallFlow.cpp" /> <ClCompile Include="main.cpp"> <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">NotUsing</PrecompiledHeader> <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">NotUsing</PrecompiledHeader> @@ -169,10 +171,19 @@ <CopyFileToFolders Include="TestData\BadManifest-MissingName.yml"> <DeploymentContent>true</DeploymentContent> </CopyFileToFolders> + <CopyFileToFolders Include="TestData\InstallFlowTest.yml"> + <DeploymentContent>true</DeploymentContent> + </CopyFileToFolders> + <CopyFileToFolders Include="TestData\InstallFlowTest_NoApplicableArchitecture.yml"> + <DeploymentContent>true</DeploymentContent> + </CopyFileToFolders> <None Include="packages.config" /> <None Include="PropertySheet.props" /> </ItemGroup> <ItemGroup> + <ProjectReference Include="..\AppInstallerCLICore\AppInstallerCLICore.vcxproj"> + <Project>{1c6e0108-2860-4b17-9f7e-fa5c6c1f3d3d}</Project> + </ProjectReference> <ProjectReference Include="..\AppInstallerCommonCore\AppInstallerCommonCore.vcxproj"> <Project>{5890d6ed-7c3b-40f3-b436-b54f640d9e65}</Project> </ProjectReference> diff --git a/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj.filters b/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj.filters @@ -44,6 +44,12 @@ <ClCompile Include="YamlManifest.cpp"> <Filter>Source Files</Filter> </ClCompile> + <ClCompile Include="Downloader.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="InstallFlow.cpp"> + <Filter>Source Files</Filter> + </ClCompile> </ItemGroup> <ItemGroup> <None Include="PropertySheet.props" /> @@ -56,5 +62,11 @@ <CopyFileToFolders Include="TestData\GoodManifest.yml"> <Filter>TestData</Filter> </CopyFileToFolders> + <CopyFileToFolders Include="TestData\InstallFlowTest.yml"> + <Filter>TestData</Filter> + </CopyFileToFolders> + <CopyFileToFolders Include="TestData\InstallFlowTest_NoApplicableArchitecture.yml"> + <Filter>TestData</Filter> + </CopyFileToFolders> </ItemGroup> </Project> \ No newline at end of file diff --git a/src/AppInstallerCLITests/Downloader.cpp b/src/AppInstallerCLITests/Downloader.cpp @@ -0,0 +1,65 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "TestCommon.h" +#include "AppInstallerDownloader.h" +#include "AppInstallerSHA256.h" + +using namespace AppInstaller::Utility; + +TEST_CASE("DownloadValidFileAndVerifyHash", "[Downloader]") +{ + TestCommon::TempFile tempFile("downloader_test", ".test"); + INFO("Using temporary file named: " << tempFile.GetPath()); + + // Todo: point to files from our repo when the repo goes public + auto downloader = Downloader::StartDownloadAsync("https://raw.githubusercontent.com/microsoft/msix-packaging/master/LICENSE", tempFile.GetPath(), true); + + auto result = downloader->Wait(); + + REQUIRE(result == DownloaderResult::Success); + + auto expectedHash = SHA256::ConvertToBytes("d2a45116709136462ee7a1c42f0e75f0efa258fe959b1504dc8ea4573451b759"); + REQUIRE(std::equal( + expectedHash.begin(), + expectedHash.end(), + downloader->GetDownloadHash().begin())); + + REQUIRE(std::filesystem::file_size(tempFile.GetPath()) > 0); +} + +TEST_CASE("DownloadValidFileAndCancel", "[Downloader]") +{ + TestCommon::TempFile tempFile("downloader_test", ".test"); + INFO("Using temporary file named: " << tempFile.GetPath()); + + auto downloader = Downloader::StartDownloadAsync("https://aka.ms/win32-x64-user-stable", tempFile.GetPath(), true); + + DownloaderResult waitResult; + std::thread waitThread([&downloader, &waitResult] { waitResult = downloader->Wait(); }); + + DownloaderResult cancelResult; + std::thread cancelThread([&downloader, &cancelResult] { cancelResult = downloader->Cancel();}); + + waitThread.join(); + cancelThread.join(); + + REQUIRE(waitResult == cancelResult); + REQUIRE(waitResult == DownloaderResult::Canceled); + + REQUIRE_THROWS(downloader->GetDownloadHash()); +} + +TEST_CASE("DownloadUnreachableUrl", "[Downloader]") +{ + TestCommon::TempFile tempFile("downloader_test", ".test"); + INFO("Using temporary file named: " << tempFile.GetPath()); + + auto downloader = Downloader::StartDownloadAsync("https://does_not_exist.com/", tempFile.GetPath(), true); + + auto result = downloader->Wait(); + + REQUIRE(result == DownloaderResult::Failed); + + REQUIRE_THROWS(downloader->GetDownloadHash()); +}+ \ No newline at end of file diff --git a/src/AppInstallerCLITests/InstallFlow.cpp b/src/AppInstallerCLITests/InstallFlow.cpp @@ -0,0 +1,59 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +#include "pch.h" +#include "Manifest/Manifest.h" +#include "AppInstallerDownloader.h" +#include "Workflows/InstallFlow.h" + +using namespace AppInstaller::Workflow; +using namespace AppInstaller::Utility; +using namespace AppInstaller::Manifest; + +class InstallFlowTest : public InstallFlow +{ +public: + InstallFlowTest(Manifest manifest, std::ostream& outStream, std::istream& inStream) : + InstallFlow(manifest, outStream, inStream) {} + +protected: + void DownloadInstaller() override + { + this->m_downloadedInstaller = std::filesystem::current_path().append("AppInstallerTestExeInstaller.exe"); + } +}; + +TEST_CASE("InstallFlowWithTestManifest", "[InstallFlow]") +{ + auto installResultPath = std::filesystem::current_path().append("TestExeInstalled.txt"); + + std::filesystem::remove(installResultPath); + + auto manifest = Manifest::CreateFromPath("InstallFlowTest.yml"); + + InstallFlowTest testFlow(manifest, std::cout, std::cin); + testFlow.Install(); + + // Verify Installer is called and parameters are passed in. + REQUIRE(std::filesystem::exists(installResultPath)); + std::ifstream installResultFile(installResultPath); + REQUIRE(installResultFile.is_open()); + std::string installResultStr; + std::getline(installResultFile, installResultStr); + REQUIRE(installResultStr.find("/default") != std::string::npos); +} + +TEST_CASE("InstallFlowWithNonApplicableArchitecture", "[InstallFlow]") +{ + auto installResultPath = std::filesystem::current_path().append("TestExeInstalled.txt"); + + std::filesystem::remove(installResultPath); + + auto manifest = Manifest::CreateFromPath("InstallFlowTest_NoApplicableArchitecture.yml"); + + InstallFlowTest testFlow(manifest, std::cout, std::cin); + REQUIRE_THROWS_WITH(testFlow.Install(), Catch::Contains("No installer with applicable architecture found.")); + + // Verify Installer is called and parameters are passed in. + REQUIRE(!std::filesystem::exists(installResultPath)); +}+ \ No newline at end of file diff --git a/src/AppInstallerCLITests/TestData/InstallFlowTest.yml b/src/AppInstallerCLITests/TestData/InstallFlowTest.yml @@ -0,0 +1,14 @@ +Id: AppInstallerCliTest.TestInstaller +Version: 1.0.0.0 +Name: AppInstaller Test Installer +Publisher: Microsoft Corporation +AppMoniker: AICLITestExe +Switches: + Verbose: /verbose + Default: /default + Silent: /silence +Installers: + - Arch: x64 + Url: https://ThisIsNotUsed + InstallerType: exe + Sha256: 65DB2F2AC2686C7F2FD69D4A4C6683B888DC55BFA20A0E32CA9F838B51689A3B diff --git a/src/AppInstallerCLITests/TestData/InstallFlowTest_NoApplicableArchitecture.yml b/src/AppInstallerCLITests/TestData/InstallFlowTest_NoApplicableArchitecture.yml @@ -0,0 +1,14 @@ +Id: AppInstallerCliTest.TestInstaller +Version: 1.0.0.0 +Name: AppInstaller Test Installer +Publisher: Microsoft Corporation +AppMoniker: AICLITestExe +Switches: + Verbose: /verbose + Default: /default + Silent: /silence +Installers: + - Arch: unknown + Url: https://ThisIsNotUsed + InstallerType: exe + Sha256: 65DB2F2AC2686C7F2FD69D4A4C6683B888DC55BFA20A0E32CA9F838B51689A3B+ \ No newline at end of file diff --git a/src/AppInstallerCLITests/YamlManifest.cpp b/src/AppInstallerCLITests/YamlManifest.cpp @@ -2,8 +2,10 @@ // Licensed under the MIT License. #include "pch.h" #include "Manifest/Manifest.h" +#include "AppInstallerSHA256.h" using namespace AppInstaller::Manifest; +using namespace AppInstaller::Utility; TEST_CASE("ReadGoodManifestAndVerifyContents", "[PackageManifestHelper]") { @@ -37,9 +39,9 @@ TEST_CASE("ReadGoodManifestAndVerifyContents", "[PackageManifestHelper]") // installers REQUIRE(manifest.Installers.size() == 2); ManifestInstaller installer1 = manifest.Installers.at(0); - REQUIRE(installer1.Arch == "x86"); + REQUIRE(installer1.Arch == Architecture::X86); REQUIRE(installer1.Url == "https://rubengustorage.blob.core.windows.net/publiccontainer/msixsdkx86.zip"); - REQUIRE(installer1.Sha256 == "69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8C82"); + REQUIRE(installer1.Sha256 == SHA256::ConvertToBytes("69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8C82")); REQUIRE(installer1.Language == "en-US"); REQUIRE(installer1.InstallerType == "Zip"); REQUIRE(installer1.Scope == "user"); @@ -51,14 +53,19 @@ TEST_CASE("ReadGoodManifestAndVerifyContents", "[PackageManifestHelper]") REQUIRE(installer1Switches.Default == "/d"); ManifestInstaller installer2 = manifest.Installers.at(1); - REQUIRE(installer2.Arch == "x64"); + REQUIRE(installer2.Arch == Architecture::X64); REQUIRE(installer2.Url == "https://rubengustorage.blob.core.windows.net/publiccontainer/msixsdkx64.zip"); - REQUIRE(installer2.Sha256 == "69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF0000"); + REQUIRE(installer2.Sha256 == SHA256::ConvertToBytes("69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF0000")); REQUIRE(installer2.Language == "en-US"); REQUIRE(installer2.InstallerType == "Zip"); REQUIRE(installer2.Scope == "user"); - REQUIRE_FALSE(installer2.Switches.has_value()); + // 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"); // Localization REQUIRE(manifest.Localization.size() == 1); @@ -71,5 +78,5 @@ TEST_CASE("ReadGoodManifestAndVerifyContents", "[PackageManifestHelper]") TEST_CASE("ReadBadManifestAndVerifyThrow", "[PackageManifestHelper]") { - REQUIRE_THROWS_WITH(Manifest::CreateFromPath("BadManifest-MissingName.yml"), "invalid node; first invalid key: \"Name\""); + REQUIRE_THROWS_WITH(Manifest::CreateFromPath("BadManifest-MissingName.yml"), Catch::Contains("invalid node; first invalid key: \"Name\"")); } diff --git a/src/AppInstallerCLITests/pch.h b/src/AppInstallerCLITests/pch.h @@ -3,14 +3,20 @@ #pragma once #define NOMINMAX #include <Windows.h> +#include <WinInet.h> #include <catch.hpp> #include <winrt/Windows.Foundation.h> #include <winrt/Windows.Foundation.Collections.h> +#include <wil/result_macros.h> + #include <iostream> #include <sstream> #include <utility> +#include <filesystem> +#include <future> +#include <fstream> #include <yaml-cpp/yaml.h> \ No newline at end of file diff --git a/src/AppInstallerCommonCore/AppInstallerCommonCore.vcxproj b/src/AppInstallerCommonCore/AppInstallerCommonCore.vcxproj @@ -122,9 +122,9 @@ <ClCompile> <Optimization>Disabled</Optimization> <PreprocessorDefinitions>_DEBUG;%(PreprocessorDefinitions);CLICOREDLLBUILD</PreprocessorDefinitions> - <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Debug|ARM'">$(ProjectDir);$(ProjectDir)Telemetry;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> - <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">$(ProjectDir);$(ProjectDir)Telemetry;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> - <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">$(ProjectDir);$(ProjectDir)Telemetry;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> + <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Debug|ARM'">$(ProjectDir);$(ProjectDir)Telemetry;$(ProjectDir)..\AppInstallerCliCore\Public;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> + <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">$(ProjectDir);$(ProjectDir)Telemetry;$(ProjectDir)..\AppInstallerCliCore\Public;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> + <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">$(ProjectDir);$(ProjectDir)Telemetry;$(ProjectDir)..\AppInstallerCliCore\Public;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> </ClCompile> <Link> <GenerateWindowsMetadata>false</GenerateWindowsMetadata> @@ -136,7 +136,7 @@ <ItemDefinitionGroup Condition="'$(Platform)'=='Win32'"> <ClCompile> <PreprocessorDefinitions>WIN32;%(PreprocessorDefinitions);CLICOREDLLBUILD</PreprocessorDefinitions> - <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">$(ProjectDir);$(ProjectDir)Telemetry;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> + <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">$(ProjectDir);$(ProjectDir)Telemetry;$(ProjectDir)..\AppInstallerCliCore\Public;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> </ClCompile> <Link> <SubSystem Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">Windows</SubSystem> @@ -148,10 +148,10 @@ <FunctionLevelLinking>true</FunctionLevelLinking> <IntrinsicFunctions>true</IntrinsicFunctions> <PreprocessorDefinitions>NDEBUG;%(PreprocessorDefinitions);CLICOREDLLBUILD</PreprocessorDefinitions> - <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Release|ARM'">$(ProjectDir);$(ProjectDir)Telemetry;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> - <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'">$(ProjectDir);$(ProjectDir)Telemetry;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> - <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">$(ProjectDir);$(ProjectDir)Telemetry;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> - <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Release|x64'">$(ProjectDir);$(ProjectDir)Telemetry;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> + <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Release|ARM'">$(ProjectDir);$(ProjectDir)Telemetry;$(ProjectDir)..\AppInstallerCliCore\Public;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> + <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'">$(ProjectDir);$(ProjectDir)Telemetry;$(ProjectDir)..\AppInstallerCliCore\Public;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> + <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">$(ProjectDir);$(ProjectDir)Telemetry;$(ProjectDir)..\AppInstallerCliCore\Public;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> + <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Release|x64'">$(ProjectDir);$(ProjectDir)Telemetry;$(ProjectDir)..\AppInstallerCliCore\Public;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> </ClCompile> <Link> <EnableCOMDATFolding>true</EnableCOMDATFolding> @@ -167,10 +167,13 @@ <ClInclude Include="DateTime.h" /> <ClInclude Include="FileLogger.h" /> <ClInclude Include="pch.h" /> + <ClInclude Include="Public\AppInstallerDownloader.h" /> <ClInclude Include="Public\AppInstallerRuntime.h" /> + <ClInclude Include="Public\AppInstallerSHA256.h" /> <ClInclude Include="Public\AppInstallerStrings.h" /> <ClInclude Include="Public\AppInstallerTelemetry.h" /> <ClInclude Include="Public\AppInstallerLogging.h" /> + <ClInclude Include="Public\AppInstallerArchitecture.h" /> <ClInclude Include="Telemetry\MicrosoftTelemetry.h" /> <ClInclude Include="Telemetry\TraceLogging.h" /> <ClInclude Include="Telemetry\WinEventLogLevels.h" /> @@ -179,13 +182,16 @@ <ClCompile Include="AppInstallerLogging.cpp" /> <ClCompile Include="AppInstallerStrings.cpp" /> <ClCompile Include="DateTime.cpp" /> + <ClCompile Include="Downloader.cpp" /> <ClCompile Include="FileLogger.cpp" /> <ClCompile Include="Runtime.cpp" /> <ClCompile Include="pch.cpp"> <PrecompiledHeader>Create</PrecompiledHeader> </ClCompile> <ClCompile Include="AppInstallerTelemetry.cpp" /> + <ClCompile Include="SHA256.cpp" /> <ClCompile Include="Telemetry\TraceLogging.cpp" /> + <ClCompile Include="Architecture.cpp" /> </ItemGroup> <ItemGroup> <None Include="packages.config" /> diff --git a/src/AppInstallerCommonCore/AppInstallerCommonCore.vcxproj.filters b/src/AppInstallerCommonCore/AppInstallerCommonCore.vcxproj.filters @@ -51,6 +51,15 @@ <ClInclude Include="Public\AppInstallerRuntime.h"> <Filter>Public</Filter> </ClInclude> + <ClInclude Include="Public\AppInstallerSHA256.h"> + <Filter>Public</Filter> + </ClInclude> + <ClInclude Include="Public\AppInstallerDownloader.h"> + <Filter>Public</Filter> + </ClInclude> + <ClInclude Include="Public\AppInstallerArchitecture.h"> + <Filter>Public</Filter> + </ClInclude> </ItemGroup> <ItemGroup> <ClCompile Include="pch.cpp"> @@ -77,6 +86,15 @@ <ClCompile Include="Runtime.cpp"> <Filter>Source Files</Filter> </ClCompile> + <ClCompile Include="SHA256.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="Downloader.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="Architecture.cpp"> + <Filter>Source Files</Filter> + </ClCompile> </ItemGroup> <ItemGroup> <None Include="PropertySheet.props" /> diff --git a/src/AppInstallerCommonCore/AppInstallerStrings.cpp b/src/AppInstallerCommonCore/AppInstallerStrings.cpp @@ -3,7 +3,6 @@ #include "pch.h" #include "Public/AppInstallerStrings.h" - namespace AppInstaller::Utility { std::string ConvertToUTF8(std::wstring_view input) @@ -35,4 +34,12 @@ namespace AppInstaller::Utility return result; } + + std::string ToLower(const std::string& in) + { + std::string result(in); + std::transform(result.begin(), result.end(), result.begin(), + [](unsigned char c) { return std::tolower(c); }); + return result; + } } diff --git a/src/AppInstallerCommonCore/Architecture.cpp b/src/AppInstallerCommonCore/Architecture.cpp @@ -0,0 +1,89 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "Public/AppInstallerArchitecture.h" +#include "Public/AppInstallerLogging.h" +#include "Public/AppInstallerRuntime.h" +#include "Public/AppInstallerStrings.h" + +namespace AppInstaller::Utility +{ + Architecture ConvertToArchitectureEnum(const std::string& archStr) + { + if (ToLower(archStr) == "x86") + { + return Architecture::X86; + } + else if (ToLower(archStr) == "x64") + { + return Architecture::X64; + } + if (ToLower(archStr) == "arm") + { + return Architecture::Arm; + } + else if (ToLower(archStr) == "arm64") + { + return Architecture::Arm64; + } + else if (ToLower(archStr) == "neutral") + { + return Architecture::Neutral; + } + + AICLI_LOG(YAML, Info, << "Convert to architecture enum. Unknown architecture: " << archStr); + return Architecture::Unknown; + } + + std::vector<Architecture> GetApplicableArchitectures() + { + static std::vector<Architecture> applicableArchs; + + if (!applicableArchs.empty()) + { + return applicableArchs; + } + + switch (Runtime::GetSystemArchitecture()) + { + case Architecture::Arm64: + applicableArchs.push_back(Architecture::Arm64); + applicableArchs.push_back(Architecture::Neutral); + applicableArchs.push_back(Architecture::Arm); + applicableArchs.push_back(Architecture::X86); + break; + case Architecture::Arm: + applicableArchs.push_back(Architecture::Arm); + applicableArchs.push_back(Architecture::Neutral); + break; + case Architecture::X86: + applicableArchs.push_back(Architecture::X86); + applicableArchs.push_back(Architecture::Neutral); + break; + case Architecture::X64: + applicableArchs.push_back(Architecture::X64); + applicableArchs.push_back(Architecture::Neutral); + applicableArchs.push_back(Architecture::X86); + break; + default: + applicableArchs.push_back(Architecture::Neutral); + } + + return applicableArchs; + } + + int IsApplicableArchitecture(Architecture arch) + { + std::vector<Architecture> applicableArchs = GetApplicableArchitectures(); + auto it = std::find(applicableArchs.begin(), applicableArchs.end(), arch); + + if (it != applicableArchs.end()) + { + return std::distance(it, applicableArchs.end()); + } + else + { + return -1; + } + } +}+ \ No newline at end of file diff --git a/src/AppInstallerCommonCore/Downloader.cpp b/src/AppInstallerCommonCore/Downloader.cpp @@ -0,0 +1,199 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +#include "pch.h" +#include "Public/AppInstallerRuntime.h" +#include "Public/AppInstallerDownloader.h" +#include "Public/AppInstallerSHA256.h" +#include "Public/AppInstallerStrings.h" +#include "Public/AppInstallerLogging.h" + +using namespace AppInstaller::Runtime; + +namespace AppInstaller::Utility +{ + std::unique_ptr<Downloader> Downloader::StartDownloadAsync( + const std::string& url, + const std::filesystem::path& dest, + bool computeHash, + IDownloaderCallback* callback) + { + // std::make_unique cannot access private constructor + auto downloader = std::unique_ptr<Downloader>(new Downloader()); + + downloader->m_downloadTask = std::async(std::launch::async, &Downloader::DownloadInternal, downloader.get(), url, dest, computeHash, callback); + + return downloader; + } + + DownloaderResult Downloader::DownloadInternal( + const std::string& url, + const std::filesystem::path& dest, + bool computeHash, + IDownloaderCallback* callback) + { + try + { + AICLI_LOG(CLI, Info, << "Downloading url: " << url << " , dest: " << dest); + + wil::unique_hinternet session(InternetOpenA( + "appinstaller-cli", + INTERNET_OPEN_TYPE_PRECONFIG, + NULL, + NULL, + 0)); + THROW_LAST_ERROR_IF_NULL_MSG(session, "InternetOpen() failed."); + + wil::unique_hinternet urlFile(InternetOpenUrlA( + session.get(), + url.c_str(), + NULL, + 0, + INTERNET_FLAG_IGNORE_REDIRECT_TO_HTTPS, // This allows http->https redirection + 0)); + THROW_LAST_ERROR_IF_NULL_MSG(urlFile, "InternetOpenUrl() failed."); + + // Check http return status + DWORD requestStatus = 0; + DWORD cbRequestStatus = sizeof(requestStatus); + + THROW_LAST_ERROR_IF_MSG(!HttpQueryInfoA(urlFile.get(), + HTTP_QUERY_STATUS_CODE | HTTP_QUERY_FLAG_NUMBER, + &requestStatus, + &cbRequestStatus, + nullptr), "Query download request status failed."); + + if (requestStatus != HTTP_STATUS_OK) + { + AICLI_LOG(CLI, Error, << "Download request failed. Returned status: " << requestStatus); + THROW_HR_MSG(MAKE_HRESULT(SEVERITY_ERROR, FACILITY_HTTP, requestStatus), "Download request status is not success."); + } + + AICLI_LOG(CLI, Verbose, << "Download request status success."); + + // Get content length. Don't fail the download if failed. + LONGLONG contentLength = 0; + DWORD cbContentLength = sizeof(contentLength); + + HttpQueryInfoA( + urlFile.get(), + HTTP_QUERY_CONTENT_LENGTH | HTTP_QUERY_FLAG_NUMBER64, + &contentLength, + &cbContentLength, + nullptr); + AICLI_LOG(CLI, Verbose, << "Download size: " << contentLength); + + std::ofstream outfile(dest, std::ofstream::binary); + + // Setup hash engine + SHA256 hashEngine; + std::string contentHash; + + const int bufferSize = 1024 * 1024; // 1MB + auto buffer = std::make_unique<BYTE[]>(bufferSize); + + BOOL readSuccess = true; + DWORD bytesRead = 0; + LONGLONG progress = 0; + + if (callback) + { + callback->OnStarted(); + } + + do + { + if (m_cancelled) + { + if (callback) + { + callback->OnCanceled(); + } + + return DownloaderResult::Canceled; + } + + readSuccess = InternetReadFile(urlFile.get(), buffer.get(), bufferSize, &bytesRead); + + THROW_LAST_ERROR_IF_MSG(!readSuccess, "InternetReadFile() failed."); + + if (computeHash) + { + hashEngine.Add(buffer.get(), bytesRead); + } + + outfile.write((char*)buffer.get(), bytesRead); + + progress += bytesRead; + + if (callback && bytesRead != 0) + { + callback->OnProgress(progress, contentLength); + } + + } while (bytesRead != 0); + + outfile.flush(); + + if (computeHash) + { + m_downloadHash = hashEngine.Get(); + AICLI_LOG(CLI, Info, << "Download hash: " << SHA256::ConvertToString(m_downloadHash)); + } + + if (callback) + { + callback->OnCompleted(); + } + + AICLI_LOG(CLI, Info, << "Download completed."); + + return DownloaderResult::Success; + } + catch (const wil::ResultException& e) + { + AICLI_LOG(Fail, Error, << "Download failed. HResult: " << e.GetErrorCode() << " Reason: " << e.what()); + return DownloaderResult::Failed; + } + catch (const std::exception& e) + { + AICLI_LOG(Fail, Error, << "Download failed. Reason: " << e.what()); + return DownloaderResult::Failed; + } + } + + DownloaderResult Downloader::Cancel() + { + if (!m_downloadTask.valid()) + { + THROW_HR_MSG(E_UNEXPECTED, "No active download found. Cancel failed."); + } + + if (!m_cancelled) + { + m_cancelled = true; + } + + return m_downloadTask.get(); + } + + DownloaderResult Downloader::Wait() + { + if (!m_downloadTask.valid()) + { + THROW_HR_MSG(E_UNEXPECTED, "No active download found. Wait failed."); + } + + return m_downloadTask.get(); + } + + std::vector<BYTE> Downloader::GetDownloadHash() + { + if (m_downloadHash.size() == 0) + { + THROW_HR_MSG(E_UNEXPECTED, "Invalid sha256 length. Download in progress or hash calculation not requested."); + } + + return m_downloadHash; + }; +}+ \ No newline at end of file diff --git a/src/AppInstallerCommonCore/Public/AppInstallerArchitecture.h b/src/AppInstallerCommonCore/Public/AppInstallerArchitecture.h @@ -0,0 +1,30 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +#pragma once + +#include <vector> + +namespace AppInstaller::Utility +{ + enum class Architecture + { + Unknown = -1, + Neutral, + X86, + X64, + Arm, + Arm64 + }; + + // Converts a string to corresponding enum + Architecture ConvertToArchitectureEnum(const std::string& archStr); + + // Gets a set of architectures that are applicable to the current system + std::vector<Architecture> GetApplicableArchitectures(); + + // Gets if an architecture is applicable to the system + // Returns the priority in the applicable architecture list if the architecture is applicable. 0 has lowest priority. + // Returns -1 if the architecture is not applicable. + int IsApplicableArchitecture(Architecture arch); +}+ \ No newline at end of file diff --git a/src/AppInstallerCommonCore/Public/AppInstallerDownloader.h b/src/AppInstallerCommonCore/Public/AppInstallerDownloader.h @@ -0,0 +1,68 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +#pragma once + +namespace AppInstaller::Utility +{ + // Enum used by Downloader to report download result + enum class DownloaderResult + { + Success = 0, + Failed, + Canceled + }; + + // Callback interface that can be passed in to downloader to get download updates. + class IDownloaderCallback + { + public: + virtual void OnStarted() = 0; + + virtual void OnProgress(LONGLONG progress, LONGLONG downloadSize) = 0; + + virtual void OnCanceled() = 0; + + virtual void OnCompleted() = 0; + }; + + // Downloader class to handle 1 file download per instance. The Downloader class supports + // SHA 256 calculation as downloading happens. + class Downloader + { + public: + // This is the only method to get a Downloader instance. + // url: The url to be downloaded from. http->https redirection is allowed. + // dest: The path to local file to be downloaded to. + // computeHash: Optional. Indicates if SHA256 hash should be calculated when downloading. + // callback: Optional. Pass in an object implementing IDownloaderCallback to receive download updates. + static std::unique_ptr<Downloader> StartDownloadAsync( + const std::string& url, + const std::filesystem::path& dest, + bool computeHash = false, + IDownloaderCallback* callback = nullptr); + + // Cancel the download. + DownloaderResult Cancel(); + + // Wait for the download to finish. + DownloaderResult Wait(); + + // Get download content hash only if download is success and hash calculation is requested. + std::vector<BYTE> GetDownloadHash(); + + private: + std::shared_future<DownloaderResult> m_downloadTask; + std::atomic<bool> m_cancelled = false; + std::vector<BYTE> m_downloadHash; + + Downloader() {}; + + // The internal method which does actual downloading. + DownloaderResult DownloadInternal( + const std::string& url, + const std::filesystem::path& dest, + bool computeHash, + IDownloaderCallback* callback); + }; +}+ \ No newline at end of file diff --git a/src/AppInstallerCommonCore/Public/AppInstallerRuntime.h b/src/AppInstallerCommonCore/Public/AppInstallerRuntime.h @@ -3,6 +3,8 @@ #pragma once #include <filesystem> +#include "AppInstallerArchitecture.h" +#include "AppInstallerCLICore.h" namespace AppInstaller::Runtime { @@ -11,4 +13,7 @@ namespace AppInstaller::Runtime // Gets the path to the temp location. std::filesystem::path GetPathToTemp(); + + // Gets the system's architecture as Architecture enum + AppInstaller::Utility::Architecture GetSystemArchitecture(); } diff --git a/src/AppInstallerCommonCore/Public/AppInstallerSHA256.h b/src/AppInstallerCommonCore/Public/AppInstallerSHA256.h @@ -0,0 +1,61 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once + +#include <memory> +#include <string> +#include <vector> +#include <stdexcept> + +namespace AppInstaller::Utility { + + // Forward declaration of type defined within PAL + struct SHA256Context; + + // Class used to compute SHA256 hashes over various sets of data. + // Create one and Add data to it if the data is not all available, + // or simply call ComputeHash if the data is all in memory. + class SHA256 + { + public: + using HashBuffer = std::vector<uint8_t>; + + SHA256(); + + // Adds the next chunk of data to the hash. + void Add(const uint8_t* buffer, size_t cbBuffer); + + inline void Add(const std::vector<std::uint8_t>& buffer) + { + Add(buffer.data(), buffer.size()); + } + + // Gets the hash of the data. This is a destructive action; the accumulated hash + // value will be returned and the object can no longer be used. + void Get(HashBuffer& hash); + + inline HashBuffer Get() + { + HashBuffer result{}; + Get(result); + return result; + } + + // Computes the hash of the given buffer immediately. + static bool ComputeHash(uint8_t* buffer, std::uint32_t cbBuffer, HashBuffer& hash); + + static std::string ConvertToString(const HashBuffer& hashBuffer); + + static HashBuffer ConvertToBytes(const std::string& hashStr); + + private: + void EnsureNotFinished() const; + + struct SHA256ContextDeleter + { + void operator()(SHA256Context* context); + }; + + std::unique_ptr<SHA256Context, SHA256ContextDeleter> context; + }; +}+ \ No newline at end of file diff --git a/src/AppInstallerCommonCore/Public/AppInstallerStrings.h b/src/AppInstallerCommonCore/Public/AppInstallerStrings.h @@ -12,4 +12,7 @@ namespace AppInstaller::Utility // Converts the given UTF8 string to UTF16 std::wstring ConvertToUTF16(std::string_view input); + + // Get the lower case version of the given string + std::string ToLower(const std::string& in); } diff --git a/src/AppInstallerCommonCore/Runtime.cpp b/src/AppInstallerCommonCore/Runtime.cpp @@ -1,10 +1,12 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. #include "pch.h" +#include "Public/AppInstallerArchitecture.h" #include "Public/AppInstallerRuntime.h" - #include <winrt/Windows.Storage.h> +using namespace AppInstaller::Utility; + namespace AppInstaller::Runtime { namespace @@ -36,4 +38,32 @@ namespace AppInstaller::Runtime return { std::wstring_view{ tempPath, static_cast<size_t>(tempChars) } }; } } + + Architecture GetSystemArchitecture() + { + Architecture systemArchitecture = Architecture::Unknown; + + SYSTEM_INFO systemInfo; + ZeroMemory(&systemInfo, sizeof(SYSTEM_INFO)); + GetNativeSystemInfo(&systemInfo); + + switch (systemInfo.wProcessorArchitecture) + { + case PROCESSOR_ARCHITECTURE_AMD64: + case PROCESSOR_ARCHITECTURE_IA64: + systemArchitecture = Architecture::X64; + break; + case PROCESSOR_ARCHITECTURE_ARM: + systemArchitecture = Architecture::Arm; + break; + case PROCESSOR_ARCHITECTURE_ARM64: + systemArchitecture = Architecture::Arm64; + break; + case PROCESSOR_ARCHITECTURE_INTEL: + systemArchitecture = Architecture::X86; + break; + } + + return systemArchitecture; + } } diff --git a/src/AppInstallerCommonCore/SHA256.cpp b/src/AppInstallerCommonCore/SHA256.cpp @@ -0,0 +1,153 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +#include <pch.h> +#define WIN32_NO_STATUS +#include <bcrypt.h> +#include "Public/AppInstallerSHA256.h" +#include "Public/AppInstallerRuntime.h" + +using namespace AppInstaller::Runtime; + +namespace AppInstaller::Utility { + + struct SHA256Context + { + wil::unique_bcrypt_algorithm algHandle; + wil::unique_bcrypt_hash hashHandle; + DWORD hashLength = 0; + }; + + SHA256::SHA256() : context(new SHA256Context{}) + { + BCRYPT_ALG_HANDLE algHandleT{}; + BCRYPT_HASH_HANDLE hashHandleT; + DWORD hashLength = 0; + DWORD resultLength = 0; + + // Open an algorithm handle + THROW_IF_NTSTATUS_FAILED_MSG(BCryptOpenAlgorithmProvider( + &algHandleT, // Alg Handle pointer + BCRYPT_SHA256_ALGORITHM, // Cryptographic Algorithm name (null terminated unicode string) + nullptr, // Provider name; if null, the default provider is loaded + 0), // Flags + "failed opening SHA256 algorithm provider"); + context->algHandle.reset(algHandleT); + + // Obtain the length of the hash + THROW_IF_NTSTATUS_FAILED_MSG(BCryptGetProperty( + context->algHandle.get(), // Handle to a CNG object + BCRYPT_HASH_LENGTH, // Property name (null terminated unicode string) + (PBYTE) & (context->hashLength), // Address of the output buffer which receives the property value + sizeof(context->hashLength), // Size of the buffer in bytes + &resultLength, // Number of bytes that were copied into the buffer + 0), // Flags + "failed getting SHA256 hash length"); + + if (resultLength != sizeof(context->hashLength)) + { + THROW_HR_MSG(E_UNEXPECTED, "failed getting SHA256 hash length"); + } + + // Create a hash handle + THROW_IF_NTSTATUS_FAILED_MSG(BCryptCreateHash( + context->algHandle.get(), // Handle to an algorithm provider + &hashHandleT, // A pointer to a hash handle - can be a hash or hmac object + nullptr, // Pointer to the buffer that receives the hash/hmac object + 0, // Size of the buffer in bytes + nullptr, // A pointer to a key to use for the hash or MAC + 0, // Size of the key in bytes + 0), // Flags + "failed creating SHA256 hash object"); + context->hashHandle.reset(hashHandleT); + } + + void SHA256::Add(const uint8_t* buffer, size_t cbBuffer) + { + EnsureNotFinished(); + + // Add the data + THROW_IF_NTSTATUS_FAILED_MSG( + BCryptHashData(context->hashHandle.get(), const_cast<PUCHAR>(buffer), static_cast<ULONG>(cbBuffer), 0), + "failed adding SHA256 data"); + } + + void SHA256::Get(HashBuffer& hash) + { + EnsureNotFinished(); + + // Size the hash buffer appropriately + hash.resize(context->hashLength); + + // Obtain the hash of the message(s) into the hash buffer + THROW_IF_NTSTATUS_FAILED_MSG(BCryptFinishHash( + context->hashHandle.get(), // Handle to the hash or MAC object + hash.data(), // A pointer to a buffer that receives the hash or MAC value + context->hashLength, // Size of the buffer in bytes + 0), // Flags + "failed getting SHA256 hash"); + + context.reset(); + } + + std::string SHA256::ConvertToString(const HashBuffer& hashBuffer) + { + if (hashBuffer.size() != 32) + { + THROW_HR_MSG(E_INVALIDARG, "Invalid SHA256 size when SHA256::ConvertToString() is called."); + } + + char resultBuffer[65]; + + for (int i = 0; i < hashBuffer.size(); i++) + { + sprintf(resultBuffer + i * 2, "%02x", resultBuffer[i]); + } + + resultBuffer[64] = '\0'; + + return std::string(resultBuffer); + } + + std::vector<uint8_t> SHA256::ConvertToBytes(const std::string& hashStr) + { + if (hashStr.size() != 64) + { + THROW_HR_MSG(E_INVALIDARG, "Invalid SHA256 size when SHA256::ConvertToBytes() is called."); + } + + auto hashCStr = hashStr.c_str(); + std::vector<uint8_t> resultBuffer; + + resultBuffer.resize(32); + + for (int i = 0; i < 32; i++) + { + sscanf(hashCStr + 2 * i, "%02x", &resultBuffer[i]); + } + + return resultBuffer; + } + + bool SHA256::ComputeHash(std::uint8_t* buffer, std::uint32_t cbBuffer, HashBuffer& hash) + { + SHA256 hasher; + hasher.Add(buffer, cbBuffer); + hasher.Get(hash); + + return true; + } + + void SHA256::SHA256ContextDeleter::operator()(SHA256Context* context) + { + delete context; + } + + void SHA256::EnsureNotFinished() const + { + if (!context) + { + THROW_HR_MSG(E_UNEXPECTED, "The hash is already finished"); + } + } +}+ \ No newline at end of file diff --git a/src/AppInstallerCommonCore/pch.h b/src/AppInstallerCommonCore/pch.h @@ -5,13 +5,16 @@ #define NOMINMAX #include <Windows.h> #include <appmodel.h> +#include <WinInet.h> #include "TraceLogging.h" #include <wil/result_macros.h> #include <wil/safecast.h> +#include <wil/resource.h> #include <chrono> +#include <filesystem> #include <fstream> #include <iomanip> #include <limits> @@ -21,3 +24,4 @@ #include <string_view> #include <type_traits> #include <vector> +#include <future>+ \ No newline at end of file diff --git a/src/AppInstallerRepositoryCore/AppInstallerRepositoryCore.vcxproj b/src/AppInstallerRepositoryCore/AppInstallerRepositoryCore.vcxproj @@ -122,9 +122,9 @@ <ClCompile> <Optimization>Disabled</Optimization> <PreprocessorDefinitions>_DEBUG;%(PreprocessorDefinitions);CLICOREDLLBUILD</PreprocessorDefinitions> - <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Debug|ARM'">$(ProjectDir);$(ProjectDir)..\AppInstallerCommonCore\Public;$(ProjectDir)..\YamlCppLib\yaml-cpp\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> - <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">$(ProjectDir);$(ProjectDir)..\AppInstallerCommonCore\Public;$(ProjectDir)..\YamlCppLib\yaml-cpp\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> - <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">$(ProjectDir);$(ProjectDir)..\AppInstallerCommonCore\Public;$(ProjectDir)..\YamlCppLib\yaml-cpp\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> + <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Debug|ARM'">$(ProjectDir);$(ProjectDir)..\AppInstallerCommonCore\Public;$(ProjectDir)..\YamlCppLib\yaml-cpp\include;$(ProjectDir)..\AppInstallerCLICore\Public;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> + <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">$(ProjectDir);$(ProjectDir)..\AppInstallerCommonCore\Public;$(ProjectDir)..\YamlCppLib\yaml-cpp\include;$(ProjectDir)..\AppInstallerCLICore\Public;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> + <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">$(ProjectDir);$(ProjectDir)..\AppInstallerCommonCore\Public;$(ProjectDir)..\YamlCppLib\yaml-cpp\include;$(ProjectDir)..\AppInstallerCLICore\Public;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> </ClCompile> <Link> <GenerateWindowsMetadata>false</GenerateWindowsMetadata> @@ -136,7 +136,7 @@ <ItemDefinitionGroup Condition="'$(Platform)'=='Win32'"> <ClCompile> <PreprocessorDefinitions>WIN32;%(PreprocessorDefinitions);CLICOREDLLBUILD</PreprocessorDefinitions> - <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">$(ProjectDir);$(ProjectDir)..\AppInstallerCommonCore\Public;$(ProjectDir)..\YamlCppLib\yaml-cpp\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> + <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">$(ProjectDir);$(ProjectDir)..\AppInstallerCommonCore\Public;$(ProjectDir)..\YamlCppLib\yaml-cpp\include;$(ProjectDir)..\AppInstallerCLICore\Public;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> </ClCompile> <Link> <SubSystem Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">Windows</SubSystem> @@ -148,10 +148,10 @@ <FunctionLevelLinking>true</FunctionLevelLinking> <IntrinsicFunctions>true</IntrinsicFunctions> <PreprocessorDefinitions>NDEBUG;%(PreprocessorDefinitions);CLICOREDLLBUILD</PreprocessorDefinitions> - <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Release|ARM'">$(ProjectDir);$(ProjectDir)..\AppInstallerCommonCore\Public;$(ProjectDir)..\YamlCppLib\yaml-cpp\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> - <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'">$(ProjectDir);$(ProjectDir)..\AppInstallerCommonCore\Public;$(ProjectDir)..\YamlCppLib\yaml-cpp\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> - <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">$(ProjectDir);$(ProjectDir)..\AppInstallerCommonCore\Public;$(ProjectDir)..\YamlCppLib\yaml-cpp\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> - <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Release|x64'">$(ProjectDir);$(ProjectDir)..\AppInstallerCommonCore\Public;$(ProjectDir)..\YamlCppLib\yaml-cpp\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> + <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Release|ARM'">$(ProjectDir);$(ProjectDir)..\AppInstallerCommonCore\Public;$(ProjectDir)..\YamlCppLib\yaml-cpp\include;$(ProjectDir)..\AppInstallerCLICore\Public;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> + <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'">$(ProjectDir);$(ProjectDir)..\AppInstallerCommonCore\Public;$(ProjectDir)..\YamlCppLib\yaml-cpp\include;$(ProjectDir)..\AppInstallerCLICore\Public;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> + <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">$(ProjectDir);$(ProjectDir)..\AppInstallerCommonCore\Public;$(ProjectDir)..\YamlCppLib\yaml-cpp\include;$(ProjectDir)..\AppInstallerCLICore\Public;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> + <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Release|x64'">$(ProjectDir);$(ProjectDir)..\AppInstallerCommonCore\Public;$(ProjectDir)..\YamlCppLib\yaml-cpp\include;$(ProjectDir)..\AppInstallerCLICore\Public;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> </ClCompile> <Link> <EnableCOMDATFolding>true</EnableCOMDATFolding> diff --git a/src/AppInstallerRepositoryCore/Manifest/InstallerSwitches.cpp b/src/AppInstallerRepositoryCore/Manifest/InstallerSwitches.cpp @@ -3,13 +3,22 @@ #include "pch.h" #include "InstallerSwitches.h" +#include "Manifest.h" namespace AppInstaller::Manifest { - void InstallerSwitches::PopulateSwitchesFields(const YAML::Node& switchesNode) + void InstallerSwitches::PopulateSwitchesFields(const YAML::Node& switchesNode, const InstallerSwitches* defaultSwitches) { - this->Default = switchesNode["Default"] ? switchesNode["Default"].as<std::string>() : ""; - this->Silent = switchesNode["Silent"] ? switchesNode["Silent"].as<std::string>() : ""; - this->Verbose = switchesNode["Verbose"] ? switchesNode["Verbose"].as<std::string>() : ""; + 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 @@ -16,6 +16,8 @@ namespace AppInstaller::Manifest std::string Verbose; - void PopulateSwitchesFields(const YAML::Node& switchesNode); + // 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 @@ -29,40 +29,62 @@ namespace AppInstaller::Manifest this->Homepage = rootNode["Homepage"] ? rootNode["Homepage"].as<std::string>() : ""; this->LicenseUrl = rootNode["LicenseUrl"] ? rootNode["LicenseUrl"].as<std::string>() : ""; + if (rootNode["Switches"]) + { + YAML::Node switchesNode = rootNode["Switches"]; + InstallerSwitches switches; + switches.PopulateSwitchesFields(switchesNode); + this->Switches.emplace(std::move(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()); + } + YAML::Node installersNode = rootNode["Installers"]; for (std::size_t i = 0; i < installersNode.size(); i++) { YAML::Node installerNode = installersNode[i]; ManifestInstaller installer; - installer.PopulateInstallerFields(installerNode); + installer.PopulateInstallerFields(installerNode, defaultInstaller); this->Installers.emplace_back(std::move(installer)); } + // Create default ManifestLocalization to be used to populate default value when optional fields are not found. + ManifestLocalization defaultLocalization; + defaultLocalization.Description = this->Description; + defaultLocalization.Homepage = this->Homepage; + defaultLocalization.LicenseUrl = this->LicenseUrl; + if (rootNode["Localization"]) { YAML::Node localizationsNode = rootNode["Localization"]; for (std::size_t i = 0; i < localizationsNode.size(); i++) { YAML::Node localizationNode = localizationsNode[i]; ManifestLocalization localization; - localization.PopulateLocalizationFields(localizationNode); + localization.PopulateLocalizationFields(localizationNode, defaultLocalization); this->Localization.emplace_back(std::move(localization)); } } - - if (rootNode["Switches"]) - { - YAML::Node switchesNode = rootNode["Switches"]; - InstallerSwitches switches; - switches.PopulateSwitchesFields(switchesNode); - this->Switches.emplace(std::move(switches)); - } } Manifest Manifest::CreateFromPath(const std::filesystem::path& inputFile) { - YAML::Node rootNode = YAML::LoadFile(inputFile.u8string()); - Manifest manifest; - manifest.PopulateManifestFields(rootNode); + + try + { + YAML::Node rootNode = YAML::LoadFile(inputFile.u8string()); + manifest.PopulateManifestFields(rootNode); + } + catch (std::runtime_error& e) + { + AICLI_LOG(YAML, Error, << "Failed to create manifest from file: " << inputFile.u8string()); + THROW_EXCEPTION_MSG(ManifestException(), e.what()); + } return manifest; } @@ -76,15 +98,12 @@ namespace AppInstaller::Manifest YAML::Node rootNode = YAML::Load(input); manifest.PopulateManifestFields(rootNode); } - catch (std::exception&) + catch (std::runtime_error& e) { - // TODO: Log theinput string when read manifest failure - throw; + AICLI_LOG(YAML, Error, << "Failed to create manifest: " << input); + THROW_EXCEPTION_MSG(ManifestException(), e.what()); } return manifest; } - - } - diff --git a/src/AppInstallerRepositoryCore/Manifest/Manifest.h b/src/AppInstallerRepositoryCore/Manifest/Manifest.h @@ -1,6 +1,8 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. #pragma once + +#include "AppInstallerCLICore.h" #include "ManifestInstaller.h" #include "ManifestLocalization.h" @@ -11,6 +13,11 @@ namespace AppInstaller::Manifest { + struct ManifestException : public wil::ResultException + { + ManifestException() : wil::ResultException(CLICORE_ERROR_MANIFEST_FAILED) {} + }; + // Our representation of the parsed manifest file. struct Manifest { diff --git a/src/AppInstallerRepositoryCore/Manifest/ManifestInstaller.cpp b/src/AppInstallerRepositoryCore/Manifest/ManifestInstaller.cpp @@ -6,24 +6,32 @@ namespace AppInstaller::Manifest { - void ManifestInstaller::PopulateInstallerFields(const YAML::Node& installerNode) + void ManifestInstaller::PopulateInstallerFields(const YAML::Node& installerNode, const ManifestInstaller& defaultInstaller) { // Required fields - this->Arch = installerNode["Arch"].as<std::string>(); + this->Arch = Utility::ConvertToArchitectureEnum(installerNode["Arch"].as<std::string>()); this->Url = installerNode["Url"].as<std::string>(); - this->Sha256 = installerNode["Sha256"].as<std::string>(); + this->Sha256 = Utility::SHA256::ConvertToBytes(installerNode["Sha256"].as<std::string>()); // Optional fields. this->Language = installerNode["Language"] ? installerNode["Language"].as<std::string>() : ""; this->Scope = installerNode["Scope"] ? installerNode["Scope"].as<std::string>() : ""; - this->InstallerType = installerNode["InstallerType"] ? installerNode["InstallerType"].as<std::string>() : ""; + + this->InstallerType = installerNode["InstallerType"] ? + installerNode["InstallerType"].as<std::string>() : + defaultInstaller.InstallerType; if (installerNode["Switches"]) { YAML::Node switchesNode = installerNode["Switches"]; InstallerSwitches switches; - switches.PopulateSwitchesFields(switchesNode); + switches.PopulateSwitchesFields(switchesNode, + defaultInstaller.Switches.has_value() ? &(defaultInstaller.Switches.value()) : nullptr); this->Switches.emplace(std::move(switches)); } + else if (defaultInstaller.Switches.has_value()) + { + this->Switches.emplace(defaultInstaller.Switches.value()); + } } } diff --git a/src/AppInstallerRepositoryCore/Manifest/ManifestInstaller.h b/src/AppInstallerRepositoryCore/Manifest/ManifestInstaller.h @@ -3,6 +3,7 @@ #pragma once #include <string> #include <optional> +#include <AppInstallerArchitecture.h> #include "InstallerSwitches.h" namespace AppInstaller::Manifest @@ -11,13 +12,13 @@ namespace AppInstaller::Manifest { public: // Required. Values: x86, x64, arm, arm64, all. - std::string Arch; + AppInstaller::Utility::Architecture Arch; // Required std::string Url; // Required - std::string Sha256; + std::vector<BYTE> Sha256; // Empty means default std::string Language; @@ -31,6 +32,8 @@ namespace AppInstaller::Manifest // If present, has more presedence than root std::optional<InstallerSwitches> Switches; - void PopulateInstallerFields(const YAML::Node& installerNode); + // 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); }; } \ No newline at end of file diff --git a/src/AppInstallerRepositoryCore/Manifest/ManifestLocalization.cpp b/src/AppInstallerRepositoryCore/Manifest/ManifestLocalization.cpp @@ -6,14 +6,22 @@ namespace AppInstaller::Manifest { - void ManifestLocalization::PopulateLocalizationFields(const YAML::Node& localizationNode) + void ManifestLocalization::PopulateLocalizationFields(const YAML::Node& localizationNode, const ManifestLocalization& defaultLocalization) { // Required this->Language = localizationNode["Language"].as<std::string>(); // Optional - this->Description = localizationNode["Description"] ? localizationNode["Description"].as<std::string>() : ""; - this->Homepage = localizationNode["Homepage"] ? localizationNode["Homepage"].as<std::string>() : ""; - this->LicenseUrl = localizationNode["LicenseUrl"] ? localizationNode["LicenseUrl"].as<std::string>() : ""; + this->Description = localizationNode["Description"] ? + localizationNode["Description"].as<std::string>() : + defaultLocalization.Description; + + this->Homepage = localizationNode["Homepage"] ? + localizationNode["Homepage"].as<std::string>() : + defaultLocalization.Homepage; + + this->LicenseUrl = localizationNode["LicenseUrl"] ? + localizationNode["LicenseUrl"].as<std::string>() : + defaultLocalization.LicenseUrl; } } diff --git a/src/AppInstallerRepositoryCore/Manifest/ManifestLocalization.h b/src/AppInstallerRepositoryCore/Manifest/ManifestLocalization.h @@ -19,6 +19,8 @@ namespace AppInstaller::Manifest std::string LicenseUrl; - void PopulateLocalizationFields(const YAML::Node& localizationNode); + // Populates ManifestLocalization + // defaultLocalization: if an optional field is not found in the YAML node, the field will be populated with value from defaultLocalization. + void PopulateLocalizationFields(const YAML::Node& localizationNode, const ManifestLocalization& defaultLocalization); }; } \ No newline at end of file diff --git a/src/AppInstallerRepositoryCore/pch.h b/src/AppInstallerRepositoryCore/pch.h @@ -6,6 +6,7 @@ #include <windows.h> #include <AppInstallerLogging.h> +#include <AppInstallerSHA256.h> #include <yaml-cpp/yaml.h> #include <wil/result_macros.h> 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;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> - <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">$(ProjectDir);$(ProjectDir)..\AppInstallerCommonCore;$(ProjectDir)..\AppInstallerRepositoryCore;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> - <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">$(ProjectDir);$(ProjectDir)..\AppInstallerCommonCore;$(ProjectDir)..\AppInstallerRepositoryCore;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> + <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Debug|ARM'">$(ProjectDir);$(ProjectDir)..\AppInstallerCommonCore;$(ProjectDir)..\AppInstallerRepositoryCore;$(ProjectDir)..\AppInstallerCommonCore\Public;$(ProjectDir)..\AppInstallerCLICore\Public;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> + <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">$(ProjectDir);$(ProjectDir)..\AppInstallerCommonCore;$(ProjectDir)..\AppInstallerRepositoryCore;$(ProjectDir)..\AppInstallerCommonCore\Public;$(ProjectDir)..\AppInstallerCLICore\Public;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> + <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">$(ProjectDir);$(ProjectDir)..\AppInstallerCommonCore;$(ProjectDir)..\AppInstallerRepositoryCore;$(ProjectDir)..\AppInstallerCommonCore\Public;$(ProjectDir)..\AppInstallerCLICore\Public;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> </ClCompile> <Link> <GenerateWindowsMetadata>false</GenerateWindowsMetadata> @@ -142,7 +142,7 @@ <ItemDefinitionGroup Condition="'$(Platform)'=='Win32'"> <ClCompile> <PreprocessorDefinitions>WIN32;%(PreprocessorDefinitions);CLICOREDLLBUILD</PreprocessorDefinitions> - <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">$(ProjectDir);$(ProjectDir)..\AppInstallerCommonCore;$(ProjectDir)..\AppInstallerRepositoryCore;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> + <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">$(ProjectDir);$(ProjectDir)..\AppInstallerCommonCore;$(ProjectDir)..\AppInstallerRepositoryCore;$(ProjectDir)..\AppInstallerCommonCore\Public;$(ProjectDir)..\AppInstallerCLICore\Public;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> </ClCompile> <Link> <SubSystem Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">Windows</SubSystem> @@ -156,10 +156,10 @@ <FunctionLevelLinking>true</FunctionLevelLinking> <IntrinsicFunctions>true</IntrinsicFunctions> <PreprocessorDefinitions>NDEBUG;%(PreprocessorDefinitions);CLICOREDLLBUILD</PreprocessorDefinitions> - <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Release|ARM'">$(ProjectDir);$(ProjectDir)..\AppInstallerCommonCore;$(ProjectDir)..\AppInstallerRepositoryCore;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> - <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'">$(ProjectDir);$(ProjectDir)..\AppInstallerCommonCore;$(ProjectDir)..\AppInstallerRepositoryCore;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> - <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">$(ProjectDir);$(ProjectDir)..\AppInstallerCommonCore;$(ProjectDir)..\AppInstallerRepositoryCore;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> - <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Release|x64'">$(ProjectDir);$(ProjectDir)..\AppInstallerCommonCore;$(ProjectDir)..\AppInstallerRepositoryCore;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> + <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Release|ARM'">$(ProjectDir);$(ProjectDir)..\AppInstallerCommonCore;$(ProjectDir)..\AppInstallerRepositoryCore;$(ProjectDir)..\AppInstallerCommonCore\Public;$(ProjectDir)..\AppInstallerCLICore\Public;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> + <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'">$(ProjectDir);$(ProjectDir)..\AppInstallerCommonCore;$(ProjectDir)..\AppInstallerRepositoryCore;$(ProjectDir)..\AppInstallerCommonCore\Public;$(ProjectDir)..\AppInstallerCLICore\Public;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> + <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">$(ProjectDir);$(ProjectDir)..\AppInstallerCommonCore;$(ProjectDir)..\AppInstallerRepositoryCore;$(ProjectDir)..\AppInstallerCommonCore\Public;$(ProjectDir)..\AppInstallerCLICore\Public;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> + <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Release|x64'">$(ProjectDir);$(ProjectDir)..\AppInstallerCommonCore;$(ProjectDir)..\AppInstallerRepositoryCore;$(ProjectDir)..\AppInstallerCommonCore\Public;$(ProjectDir)..\AppInstallerCLICore\Public;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> </ClCompile> <Link> <EnableCOMDATFolding>true</EnableCOMDATFolding> diff --git a/src/AppInstallerTestExeInstaller/AppInstallerTestExeInstaller.vcxproj b/src/AppInstallerTestExeInstaller/AppInstallerTestExeInstaller.vcxproj @@ -0,0 +1,171 @@ +<?xml version="1.0" encoding="utf-8"?> +<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> + <PropertyGroup Label="Globals"> + <VCProjectVersion>15.0</VCProjectVersion> + <ProjectGuid>{6CB84692-5994-407D-B9BD-9216AF77FE83}</ProjectGuid> + <Keyword>Win32Proj</Keyword> + <RootNamespace>AppInstallerTestExeInstaller</RootNamespace> + <WindowsTargetPlatformVersion>10.0</WindowsTargetPlatformVersion> + </PropertyGroup> + <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" /> + <ItemGroup Label="ProjectConfigurations"> + <ProjectConfiguration Include="Debug|Win32"> + <Configuration>Debug</Configuration> + <Platform>Win32</Platform> + </ProjectConfiguration> + <ProjectConfiguration Include="Release|Win32"> + <Configuration>Release</Configuration> + <Platform>Win32</Platform> + </ProjectConfiguration> + <ProjectConfiguration Include="Debug|x64"> + <Configuration>Debug</Configuration> + <Platform>x64</Platform> + </ProjectConfiguration> + <ProjectConfiguration Include="Release|x64"> + <Configuration>Release</Configuration> + <Platform>x64</Platform> + </ProjectConfiguration> + </ItemGroup> + <PropertyGroup Label="Configuration"> + <ConfigurationType>Application</ConfigurationType> + <PlatformToolset>v140</PlatformToolset> + <PlatformToolset Condition="'$(VisualStudioVersion)' == '15.0'">v141</PlatformToolset> + <PlatformToolset Condition="'$(VisualStudioVersion)' == '16.0'">v142</PlatformToolset> + <CharacterSet>Unicode</CharacterSet> + </PropertyGroup> + <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'"> + <LinkIncremental>true</LinkIncremental> + <OutDir>$(SolutionDir)$(Platform)\$(Configuration)\$(ProjectName)\</OutDir> + </PropertyGroup> + <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|ARM'"> + <LinkIncremental>true</LinkIncremental> + <OutDir>$(SolutionDir)$(Platform)\$(Configuration)\$(ProjectName)\</OutDir> + </PropertyGroup> + <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'"> + <LinkIncremental>true</LinkIncremental> + <OutDir>$(SolutionDir)$(Platform)\$(Configuration)\$(ProjectName)\</OutDir> + </PropertyGroup> + <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'"> + <LinkIncremental>true</LinkIncremental> + <OutDir>$(SolutionDir)x86\$(Configuration)\$(ProjectName)\</OutDir> + </PropertyGroup> + <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'"> + <LinkIncremental>false</LinkIncremental> + <OutDir>$(SolutionDir)x86\$(Configuration)\$(ProjectName)\</OutDir> + </PropertyGroup> + <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'"> + <LinkIncremental>false</LinkIncremental> + <OutDir>$(SolutionDir)$(Platform)\$(Configuration)\$(ProjectName)\</OutDir> + </PropertyGroup> + <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|ARM'"> + <LinkIncremental>false</LinkIncremental> + <OutDir>$(SolutionDir)$(Platform)\$(Configuration)\$(ProjectName)\</OutDir> + </PropertyGroup> + <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'"> + <LinkIncremental>false</LinkIncremental> + <OutDir>$(SolutionDir)$(Platform)\$(Configuration)\$(ProjectName)\</OutDir> + </PropertyGroup> + <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" /> + <ImportGroup Label="ExtensionSettings"> + </ImportGroup> + <ImportGroup Label="Shared"> + </ImportGroup> + <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'"> + <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" /> + </ImportGroup> + <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'"> + <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" /> + </ImportGroup> + <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|x64'"> + <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" /> + </ImportGroup> + <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|x64'"> + <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" /> + </ImportGroup> + <PropertyGroup Label="UserMacros" /> + <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'"> + <LinkIncremental>false</LinkIncremental> + </PropertyGroup> + <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'"> + <LinkIncremental>true</LinkIncremental> + </PropertyGroup> + <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'"> + <LinkIncremental>true</LinkIncremental> + </PropertyGroup> + <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'"> + <LinkIncremental>false</LinkIncremental> + </PropertyGroup> + <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'"> + <ClCompile> + <PrecompiledHeader> + </PrecompiledHeader> + <WarningLevel>Level3</WarningLevel> + <FunctionLevelLinking>true</FunctionLevelLinking> + <IntrinsicFunctions>true</IntrinsicFunctions> + <SDLCheck>true</SDLCheck> + <PreprocessorDefinitions>NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions> + <ConformanceMode>true</ConformanceMode> + </ClCompile> + <Link> + <SubSystem>Console</SubSystem> + <EnableCOMDATFolding>true</EnableCOMDATFolding> + <OptimizeReferences>true</OptimizeReferences> + <GenerateDebugInformation>true</GenerateDebugInformation> + </Link> + </ItemDefinitionGroup> + <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'"> + <ClCompile> + <PrecompiledHeader> + </PrecompiledHeader> + <WarningLevel>Level3</WarningLevel> + <SDLCheck>true</SDLCheck> + <PreprocessorDefinitions>WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions> + <ConformanceMode>true</ConformanceMode> + </ClCompile> + <Link> + <SubSystem>Console</SubSystem> + <GenerateDebugInformation>true</GenerateDebugInformation> + </Link> + </ItemDefinitionGroup> + <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'"> + <ClCompile> + <PrecompiledHeader> + </PrecompiledHeader> + <WarningLevel>Level3</WarningLevel> + <SDLCheck>true</SDLCheck> + <PreprocessorDefinitions>_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions> + <ConformanceMode>true</ConformanceMode> + </ClCompile> + <Link> + <SubSystem>Console</SubSystem> + <GenerateDebugInformation>true</GenerateDebugInformation> + </Link> + </ItemDefinitionGroup> + <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'"> + <ClCompile> + <PrecompiledHeader> + </PrecompiledHeader> + <WarningLevel>Level3</WarningLevel> + <FunctionLevelLinking>true</FunctionLevelLinking> + <IntrinsicFunctions>true</IntrinsicFunctions> + <SDLCheck>true</SDLCheck> + <PreprocessorDefinitions>WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions> + <ConformanceMode>true</ConformanceMode> + </ClCompile> + <Link> + <SubSystem>Console</SubSystem> + <EnableCOMDATFolding>true</EnableCOMDATFolding> + <OptimizeReferences>true</OptimizeReferences> + <GenerateDebugInformation>true</GenerateDebugInformation> + </Link> + </ItemDefinitionGroup> + <ItemGroup> + <ClCompile Include="main.cpp" /> + </ItemGroup> + <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" /> + <ImportGroup Label="ExtensionTargets"> + </ImportGroup> + <Target Name="AfterBuild"> + <Copy SourceFiles="$(OutDir)\$(TargetFileName)" DestinationFolder="$(OutDir)\..\AppInstallerCliTests" ContinueOnError="true" /> + </Target> +</Project>+ \ No newline at end of file diff --git a/src/AppInstallerTestExeInstaller/AppInstallerTestExeInstaller.vcxproj.filters b/src/AppInstallerTestExeInstaller/AppInstallerTestExeInstaller.vcxproj.filters @@ -0,0 +1,22 @@ +<?xml version="1.0" encoding="utf-8"?> +<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> + <ItemGroup> + <Filter Include="Source Files"> + <UniqueIdentifier>{4FC737F1-C7A5-4376-A066-2A32D752A2FF}</UniqueIdentifier> + <Extensions>cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx</Extensions> + </Filter> + <Filter Include="Header Files"> + <UniqueIdentifier>{93995380-89BD-4b04-88EB-625FBE52EBFB}</UniqueIdentifier> + <Extensions>h;hh;hpp;hxx;hm;inl;inc;ipp;xsd</Extensions> + </Filter> + <Filter Include="Resource Files"> + <UniqueIdentifier>{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}</UniqueIdentifier> + <Extensions>rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms</Extensions> + </Filter> + </ItemGroup> + <ItemGroup> + <ClCompile Include="main.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + </ItemGroup> +</Project>+ \ No newline at end of file diff --git a/src/AppInstallerTestExeInstaller/main.cpp b/src/AppInstallerTestExeInstaller/main.cpp @@ -0,0 +1,17 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +#include <iostream> +#include <fstream> + +int main(int argc, const char** argv) +{ + std::ofstream file("TestExeInstalled.txt", std::ofstream::out); + + for (int i = 1; i < argc; i++) + { + file << argv[i] << ' '; + } + + file.close(); +}