commit f1627af1073b99a3021384190de4a83d3443a0aa parent 890e667e5c5907a93593b6c2ac1d09934a4c91d9 Author: Ryan <69221034+ryfu-msft@users.noreply.github.com> Date: Tue, 1 Aug 2023 15:57:56 -0700 Generate manifest for Winget Download (#3448) Diffstat:
24 files changed, 1292 insertions(+), 163 deletions(-)
diff --git a/src/AppInstallerCLICore/Workflows/DownloadFlow.cpp b/src/AppInstallerCLICore/Workflows/DownloadFlow.cpp @@ -3,10 +3,11 @@ #include "pch.h" #include "DownloadFlow.h" #include <winget/Filesystem.h> +#include <AppInstallerDownloader.h> #include <AppInstallerRuntime.h> #include <AppInstallerMsixInfo.h> #include <winget/AdminSettings.h> -#include <AppInstallerDownloader.h> +#include <winget/ManifestYamlWriter.h> namespace AppInstaller::CLI::Workflow { @@ -90,7 +91,7 @@ namespace AppInstaller::CLI::Workflow } // Gets the file name for the downloaded installer in the format of {id}_{version}_{architecture}_{scope}_{installerType}_{locale}. - std::filesystem::path GetInstallerDownloadOnlyFileName(Execution::Context& context) + std::filesystem::path GetInstallerDownloadOnlyFileName(Execution::Context& context, const std::wstring_view& extension = {}) { const auto& manifest = context.Get<Execution::Data::Manifest>(); const auto& installer = context.Get<Execution::Data::Installer>().value(); @@ -120,7 +121,15 @@ namespace AppInstaller::CLI::Workflow } std::filesystem::path fileNamePath = Utility::ConvertToUTF16(fileName); - fileNamePath += GetInstallerFileExtension(context); + + if (!extension.empty()) + { + fileNamePath += extension; + } + else + { + fileNamePath += GetInstallerFileExtension(context); + } // Make file name suitable for file system path fileNamePath = Utility::ConvertToUTF16(Utility::MakeSuitablePathPart(fileNamePath.u8string())); @@ -183,6 +192,8 @@ namespace AppInstaller::CLI::Workflow return; } + bool installerDownloadOnly = WI_IsFlagSet(context.GetFlags(), Execution::ContextFlag::InstallerDownloadOnly); + // CheckForExistingInstaller will set the InstallerPath if found if (!context.Contains(Execution::Data::InstallerPath)) { @@ -200,7 +211,7 @@ namespace AppInstaller::CLI::Workflow context << DownloadInstallerFile; break; case InstallerTypeEnum::Msix: - if (installer.SignatureSha256.empty() || WI_IsFlagSet(context.GetFlags(), Execution::ContextFlag::InstallerDownloadOnly)) + if (installer.SignatureSha256.empty() || installerDownloadOnly) { // If InstallerDownloadOnly flag is set, always download the installer file. context << DownloadInstallerFile; @@ -212,7 +223,7 @@ namespace AppInstaller::CLI::Workflow } break; case InstallerTypeEnum::MSStore: - if (WI_IsFlagSet(context.GetFlags(), Execution::ContextFlag::InstallerDownloadOnly)) + if (installerDownloadOnly) { THROW_HR(HRESULT_FROM_WIN32(ERROR_NOT_SUPPORTED)); } @@ -230,6 +241,11 @@ namespace AppInstaller::CLI::Workflow VerifyInstallerHash << UpdateInstallerFileMotwIfApplicable << RenameDownloadedInstaller; + + if (installerDownloadOnly) + { + context << ExportManifest; + } } void CheckForExistingInstaller(Execution::Context& context) @@ -563,4 +579,16 @@ namespace AppInstaller::CLI::Workflow context.Add<Execution::Data::DownloadDirectory>(downloadsDirectory / packageDownloadFolderName); } } -} + + void ExportManifest(Execution::Context& context) + { + const auto& downloadDirectory = context.Get<Execution::Data::DownloadDirectory>(); + const auto& manifest = context.Get<Execution::Data::Manifest>(); + const auto& installer = context.Get<Execution::Data::Installer>(); + + std::filesystem::path manifestFileName = GetInstallerDownloadOnlyFileName(context, L".yaml"); + auto manifestDownloadPath = downloadDirectory / manifestFileName; + YamlWriter::OutputYamlFile(manifest, installer.value(), manifestDownloadPath); + AICLI_LOG(CLI, Info, << "Successfully generated manifest yaml. Path: " << manifestDownloadPath); + } +}+ \ No newline at end of file diff --git a/src/AppInstallerCLICore/Workflows/DownloadFlow.h b/src/AppInstallerCLICore/Workflows/DownloadFlow.h @@ -72,4 +72,10 @@ namespace AppInstaller::CLI::Workflow // Inputs: Manifest // Outputs: None void SetDownloadDirectory(Execution::Context& context); + + // Exports the manifest yaml file for the downloaded package installer. Only applies to the 'winget download' command. + // Required Args: None + // Inputs: Manifest, Installer, DownloadDirectory + // Outputs: None + void ExportManifest(Execution::Context& context); } diff --git a/src/AppInstallerCLIE2ETests/Helpers/TestCommon.cs b/src/AppInstallerCLIE2ETests/Helpers/TestCommon.cs @@ -552,7 +552,7 @@ namespace AppInstallerCLIE2ETests.Helpers } /// <summary> - /// Verify installer downloaded correctly and cleanup. + /// Verify installer and manifest downloaded correctly and cleanup. /// </summary> /// <param name="downloadDir">Download directory.</param> /// <param name="name">Package name.</param> @@ -589,14 +589,14 @@ namespace AppInstallerCLIE2ETests.Helpers expectedFileName += $"_{locale}"; } - string extension; + string installerExtension; if (isArchive) { - extension = ".zip"; + installerExtension = ".zip"; } else { - extension = installerType switch + installerExtension = installerType switch { PackageInstallerType.Msi => ".msi", PackageInstallerType.Msix => ".msix", @@ -604,18 +604,19 @@ namespace AppInstallerCLIE2ETests.Helpers }; } - expectedFileName += extension; - string installerDownloadPath = Path.Combine(downloadDir, expectedFileName); + string installerDownloadPath = Path.Combine(downloadDir, expectedFileName + installerExtension); + string manifestDownloadPath = Path.Combine(downloadDir, expectedFileName + ".yaml"); bool downloadResult = false; - if (Directory.Exists(downloadDir) && File.Exists(installerDownloadPath)) + if (Directory.Exists(downloadDir) && File.Exists(installerDownloadPath) && File.Exists(manifestDownloadPath)) { downloadResult = true; if (cleanup) { File.Delete(installerDownloadPath); + File.Delete(manifestDownloadPath); Directory.Delete(downloadDir, true); } } diff --git a/src/AppInstallerCLITests/TestData/ManifestV1-Singleton.yaml b/src/AppInstallerCLITests/TestData/ManifestV1-Singleton.yaml @@ -109,6 +109,7 @@ Installers: - Preview VC Runtime PackageDependencies: - PackageIdentifier: Microsoft.MsixSdkDepPreview + MinimumVersion: 1.0.0 ExternalDependencies: - Preview Outside dependencies PackageFamilyName: Microsoft.DesktopAppInstallerPreview_8wekyb3d8bbwe diff --git a/src/AppInstallerCLITests/TestData/ManifestV1_1-Singleton.yaml b/src/AppInstallerCLITests/TestData/ManifestV1_1-Singleton.yaml @@ -135,6 +135,7 @@ Installers: - Preview VC Runtime PackageDependencies: - PackageIdentifier: Microsoft.MsixSdkDepPreview + MinimumVersion: 1.0.0 ExternalDependencies: - Preview Outside dependencies PackageFamilyName: Microsoft.DesktopAppInstallerPreview_8wekyb3d8bbwe diff --git a/src/AppInstallerCLITests/TestData/ManifestV1_2-Singleton.yaml b/src/AppInstallerCLITests/TestData/ManifestV1_2-Singleton.yaml @@ -144,6 +144,7 @@ Installers: - Preview VC Runtime PackageDependencies: - PackageIdentifier: Microsoft.MsixSdkDepPreview + MinimumVersion: 1.0.0 ExternalDependencies: - Preview Outside dependencies PackageFamilyName: Microsoft.DesktopAppInstallerPreview_8wekyb3d8bbwe diff --git a/src/AppInstallerCLITests/TestData/ManifestV1_4-Singleton.yaml b/src/AppInstallerCLITests/TestData/ManifestV1_4-Singleton.yaml @@ -156,6 +156,7 @@ Installers: - Preview VC Runtime PackageDependencies: - PackageIdentifier: Microsoft.MsixSdkDepPreview + MinimumVersion: 1.0.0 ExternalDependencies: - Preview Outside dependencies PackageFamilyName: Microsoft.DesktopAppInstallerPreview_8wekyb3d8bbwe diff --git a/src/AppInstallerCLITests/TestData/ManifestV1_5-Singleton.yaml b/src/AppInstallerCLITests/TestData/ManifestV1_5-Singleton.yaml @@ -162,6 +162,7 @@ Installers: - Preview VC Runtime PackageDependencies: - PackageIdentifier: Microsoft.MsixSdkDepPreview + MinimumVersion: 1.0.0 ExternalDependencies: - Preview Outside dependencies PackageFamilyName: Microsoft.DesktopAppInstallerPreview_8wekyb3d8bbwe diff --git a/src/AppInstallerCLITests/TestData/ManifestV1_6-Singleton.yaml b/src/AppInstallerCLITests/TestData/ManifestV1_6-Singleton.yaml @@ -162,6 +162,7 @@ Installers: - Preview VC Runtime PackageDependencies: - PackageIdentifier: Microsoft.MsixSdkDepPreview + MinimumVersion: 1.0.0 ExternalDependencies: - Preview Outside dependencies PackageFamilyName: Microsoft.DesktopAppInstallerPreview_8wekyb3d8bbwe diff --git a/src/AppInstallerCLITests/TestData/MultiFileManifestV1/ManifestV1-MultiFile-Installer.yaml b/src/AppInstallerCLITests/TestData/MultiFileManifestV1/ManifestV1-MultiFile-Installer.yaml @@ -91,6 +91,7 @@ Installers: - Preview VC Runtime PackageDependencies: - PackageIdentifier: Microsoft.MsixSdkDepPreview + MinimumVersion: 1.0.0 ExternalDependencies: - Preview Outside dependencies PackageFamilyName: Microsoft.DesktopAppInstallerPreview_8wekyb3d8bbwe diff --git a/src/AppInstallerCLITests/TestData/MultiFileManifestV1_1/ManifestV1_1-MultiFile-Installer.yaml b/src/AppInstallerCLITests/TestData/MultiFileManifestV1_1/ManifestV1_1-MultiFile-Installer.yaml @@ -111,6 +111,7 @@ Installers: - Preview VC Runtime PackageDependencies: - PackageIdentifier: Microsoft.MsixSdkDepPreview + MinimumVersion: 1.0.0 ExternalDependencies: - Preview Outside dependencies PackageFamilyName: Microsoft.DesktopAppInstallerPreview_8wekyb3d8bbwe diff --git a/src/AppInstallerCLITests/TestData/MultiFileManifestV1_2/ManifestV1_2-MultiFile-Installer.yaml b/src/AppInstallerCLITests/TestData/MultiFileManifestV1_2/ManifestV1_2-MultiFile-Installer.yaml @@ -115,6 +115,7 @@ Installers: - Preview VC Runtime PackageDependencies: - PackageIdentifier: Microsoft.MsixSdkDepPreview + MinimumVersion: 1.0.0 ExternalDependencies: - Preview Outside dependencies PackageFamilyName: Microsoft.DesktopAppInstallerPreview_8wekyb3d8bbwe diff --git a/src/AppInstallerCLITests/TestData/MultiFileManifestV1_4/ManifestV1_4-MultiFile-Installer.yaml b/src/AppInstallerCLITests/TestData/MultiFileManifestV1_4/ManifestV1_4-MultiFile-Installer.yaml @@ -127,6 +127,7 @@ Installers: - Preview VC Runtime PackageDependencies: - PackageIdentifier: Microsoft.MsixSdkDepPreview + MinimumVersion: 1.0.0 ExternalDependencies: - Preview Outside dependencies PackageFamilyName: Microsoft.DesktopAppInstallerPreview_8wekyb3d8bbwe diff --git a/src/AppInstallerCLITests/TestData/MultiFileManifestV1_5/ManifestV1_5-MultiFile-Installer.yaml b/src/AppInstallerCLITests/TestData/MultiFileManifestV1_5/ManifestV1_5-MultiFile-Installer.yaml @@ -127,6 +127,7 @@ Installers: - Preview VC Runtime PackageDependencies: - PackageIdentifier: Microsoft.MsixSdkDepPreview + MinimumVersion: 1.0.0 ExternalDependencies: - Preview Outside dependencies PackageFamilyName: Microsoft.DesktopAppInstallerPreview_8wekyb3d8bbwe diff --git a/src/AppInstallerCLITests/TestData/MultiFileManifestV1_6/ManifestV1_6-MultiFile-Installer.yaml b/src/AppInstallerCLITests/TestData/MultiFileManifestV1_6/ManifestV1_6-MultiFile-Installer.yaml @@ -127,6 +127,7 @@ Installers: - Preview VC Runtime PackageDependencies: - PackageIdentifier: Microsoft.MsixSdkDepPreview + MinimumVersion: 1.0.0 ExternalDependencies: - Preview Outside dependencies PackageFamilyName: Microsoft.DesktopAppInstallerPreview_8wekyb3d8bbwe diff --git a/src/AppInstallerCLITests/YamlManifest.cpp b/src/AppInstallerCLITests/YamlManifest.cpp @@ -4,11 +4,13 @@ #include "TestCommon.h" #include <AppInstallerSHA256.h> #include <winget/ManifestYamlParser.h> +#include <winget/ManifestYamlWriter.h> #include <winget/Yaml.h> using namespace TestCommon; using namespace AppInstaller::Manifest; using namespace AppInstaller::Manifest::YamlParser; +using namespace AppInstaller::Manifest::YamlWriter; using namespace AppInstaller::Utility; using namespace AppInstaller::YAML; @@ -392,7 +394,7 @@ void CopyTestDataFilesToFolder(const std::vector<std::string>& testDataFiles, co } } -void VerifyV1ManifestContent(const Manifest& manifest, bool isSingleton, ManifestVer manifestVer = { s_ManifestVersionV1 }) +void VerifyV1ManifestContent(const Manifest& manifest, bool isSingleton, ManifestVer manifestVer = { s_ManifestVersionV1 }, bool isExported = false) { REQUIRE(manifest.Id == "microsoft.msixsdk"); REQUIRE(manifest.Version == "1.7.32"); @@ -442,84 +444,87 @@ void VerifyV1ManifestContent(const Manifest& manifest, bool isSingleton, Manifes REQUIRE(manifest.DefaultLocalization.Get<Localization::Icons>().at(0).Sha256 == SHA256::ConvertToBytes("69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8123")); } - REQUIRE(manifest.DefaultInstallerInfo.Locale == "en-US"); - REQUIRE(manifest.DefaultInstallerInfo.Platform == std::vector<PlatformEnum>{ PlatformEnum::Desktop, PlatformEnum::Universal }); - REQUIRE(manifest.DefaultInstallerInfo.MinOSVersion == "10.0.0.0"); - REQUIRE(manifest.DefaultInstallerInfo.BaseInstallerType == InstallerTypeEnum::Exe); - REQUIRE(manifest.DefaultInstallerInfo.Scope == ScopeEnum::Machine); - REQUIRE(manifest.DefaultInstallerInfo.InstallModes == std::vector<InstallModeEnum>{ InstallModeEnum::Interactive, InstallModeEnum::Silent, InstallModeEnum::SilentWithProgress }); - - auto defaultSwitches = manifest.DefaultInstallerInfo.Switches; - REQUIRE(defaultSwitches.at(InstallerSwitchType::Custom) == "/custom"); - REQUIRE(defaultSwitches.at(InstallerSwitchType::SilentWithProgress) == "/silentwithprogress"); - REQUIRE(defaultSwitches.at(InstallerSwitchType::Silent) == "/silence"); - REQUIRE(defaultSwitches.at(InstallerSwitchType::Interactive) == "/interactive"); - REQUIRE(defaultSwitches.at(InstallerSwitchType::Log) == "/log=<LOGPATH>"); - REQUIRE(defaultSwitches.at(InstallerSwitchType::InstallLocation) == "/dir=<INSTALLPATH>"); - REQUIRE(defaultSwitches.at(InstallerSwitchType::Update) == "/upgrade"); - - REQUIRE(manifest.DefaultInstallerInfo.InstallerSuccessCodes == std::vector<DWORD>{ 1, static_cast<DWORD>(0x80070005) }); - REQUIRE(manifest.DefaultInstallerInfo.UpdateBehavior == UpdateBehaviorEnum::UninstallPrevious); - REQUIRE(manifest.DefaultInstallerInfo.Commands == MultiValue{ "makemsix", "makeappx" }); - REQUIRE(manifest.DefaultInstallerInfo.Protocols == MultiValue{ "protocol1", "protocol2" }); - REQUIRE(manifest.DefaultInstallerInfo.FileExtensions == MultiValue{ "appx", "msix", "appxbundle", "msixbundle" }); - - auto dependencies = manifest.DefaultInstallerInfo.Dependencies; - REQUIRE(dependencies.HasExactDependency(DependencyType::WindowsFeature, "IIS")); - REQUIRE(dependencies.HasExactDependency(DependencyType::WindowsLibrary, "VC Runtime")); - REQUIRE(dependencies.HasExactDependency(DependencyType::Package, "Microsoft.MsixSdkDep", "1.0.0")); - REQUIRE(dependencies.HasExactDependency(DependencyType::External, "Outside dependencies")); - REQUIRE(dependencies.Size() == 4); - - REQUIRE(manifest.DefaultInstallerInfo.Capabilities == MultiValue{ "internetClient" }); - REQUIRE(manifest.DefaultInstallerInfo.RestrictedCapabilities == MultiValue{ "runFullTrust" }); - REQUIRE(manifest.DefaultInstallerInfo.PackageFamilyName == "Microsoft.DesktopAppInstaller_8wekyb3d8bbwe"); - REQUIRE(manifest.DefaultInstallerInfo.ProductCode == "{Foo}"); - - if (manifestVer >= ManifestVer{ s_ManifestVersionV1_1 }) + if (!isExported) { - REQUIRE(manifest.DefaultInstallerInfo.ReleaseDate == "2021-01-01"); - REQUIRE(manifest.DefaultInstallerInfo.InstallerAbortsTerminal); - REQUIRE(manifest.DefaultInstallerInfo.InstallLocationRequired); - REQUIRE(manifest.DefaultInstallerInfo.RequireExplicitUpgrade); - REQUIRE(manifest.DefaultInstallerInfo.ElevationRequirement == ElevationRequirementEnum::ElevatesSelf); - REQUIRE(manifest.DefaultInstallerInfo.UnsupportedOSArchitectures.size() == 1); - REQUIRE(manifest.DefaultInstallerInfo.UnsupportedOSArchitectures.at(0) == Architecture::Arm); - REQUIRE(manifest.DefaultInstallerInfo.AppsAndFeaturesEntries.size() == 1); - REQUIRE(manifest.DefaultInstallerInfo.AppsAndFeaturesEntries.at(0).DisplayName == "DisplayName"); - REQUIRE(manifest.DefaultInstallerInfo.AppsAndFeaturesEntries.at(0).DisplayVersion == "DisplayVersion"); - REQUIRE(manifest.DefaultInstallerInfo.AppsAndFeaturesEntries.at(0).Publisher == "Publisher"); - REQUIRE(manifest.DefaultInstallerInfo.AppsAndFeaturesEntries.at(0).ProductCode == "ProductCode"); - REQUIRE(manifest.DefaultInstallerInfo.AppsAndFeaturesEntries.at(0).UpgradeCode == "UpgradeCode"); - REQUIRE(manifest.DefaultInstallerInfo.AppsAndFeaturesEntries.at(0).InstallerType == InstallerTypeEnum::Exe); - REQUIRE(manifest.DefaultInstallerInfo.Markets.AllowedMarkets.size() == 1); - REQUIRE(manifest.DefaultInstallerInfo.Markets.AllowedMarkets.at(0) == "US"); - REQUIRE(manifest.DefaultInstallerInfo.ExpectedReturnCodes.size() == 1); - REQUIRE(manifest.DefaultInstallerInfo.ExpectedReturnCodes.at(10).ReturnResponseEnum == ExpectedReturnCodeEnum::PackageInUse); - } + REQUIRE(manifest.DefaultInstallerInfo.Locale == "en-US"); + REQUIRE(manifest.DefaultInstallerInfo.Platform == std::vector<PlatformEnum>{ PlatformEnum::Desktop, PlatformEnum::Universal }); + REQUIRE(manifest.DefaultInstallerInfo.MinOSVersion == "10.0.0.0"); + REQUIRE(manifest.DefaultInstallerInfo.BaseInstallerType == InstallerTypeEnum::Exe); + REQUIRE(manifest.DefaultInstallerInfo.Scope == ScopeEnum::Machine); + REQUIRE(manifest.DefaultInstallerInfo.InstallModes == std::vector<InstallModeEnum>{ InstallModeEnum::Interactive, InstallModeEnum::Silent, InstallModeEnum::SilentWithProgress }); + + auto defaultSwitches = manifest.DefaultInstallerInfo.Switches; + REQUIRE(defaultSwitches.at(InstallerSwitchType::Custom) == "/custom"); + REQUIRE(defaultSwitches.at(InstallerSwitchType::SilentWithProgress) == "/silentwithprogress"); + REQUIRE(defaultSwitches.at(InstallerSwitchType::Silent) == "/silence"); + REQUIRE(defaultSwitches.at(InstallerSwitchType::Interactive) == "/interactive"); + REQUIRE(defaultSwitches.at(InstallerSwitchType::Log) == "/log=<LOGPATH>"); + REQUIRE(defaultSwitches.at(InstallerSwitchType::InstallLocation) == "/dir=<INSTALLPATH>"); + REQUIRE(defaultSwitches.at(InstallerSwitchType::Update) == "/upgrade"); + + REQUIRE(manifest.DefaultInstallerInfo.InstallerSuccessCodes == std::vector<DWORD>{ 1, static_cast<DWORD>(0x80070005) }); + REQUIRE(manifest.DefaultInstallerInfo.UpdateBehavior == UpdateBehaviorEnum::UninstallPrevious); + REQUIRE(manifest.DefaultInstallerInfo.Commands == MultiValue{ "makemsix", "makeappx" }); + REQUIRE(manifest.DefaultInstallerInfo.Protocols == MultiValue{ "protocol1", "protocol2" }); + REQUIRE(manifest.DefaultInstallerInfo.FileExtensions == MultiValue{ "appx", "msix", "appxbundle", "msixbundle" }); + + auto dependencies = manifest.DefaultInstallerInfo.Dependencies; + REQUIRE(dependencies.HasExactDependency(DependencyType::WindowsFeature, "IIS")); + REQUIRE(dependencies.HasExactDependency(DependencyType::WindowsLibrary, "VC Runtime")); + REQUIRE(dependencies.HasExactDependency(DependencyType::Package, "Microsoft.MsixSdkDep", "1.0.0")); + REQUIRE(dependencies.HasExactDependency(DependencyType::External, "Outside dependencies")); + REQUIRE(dependencies.Size() == 4); + + REQUIRE(manifest.DefaultInstallerInfo.Capabilities == MultiValue{ "internetClient" }); + REQUIRE(manifest.DefaultInstallerInfo.RestrictedCapabilities == MultiValue{ "runFullTrust" }); + REQUIRE(manifest.DefaultInstallerInfo.PackageFamilyName == "Microsoft.DesktopAppInstaller_8wekyb3d8bbwe"); + REQUIRE(manifest.DefaultInstallerInfo.ProductCode == "{Foo}"); - if (manifestVer >= ManifestVer{ s_ManifestVersionV1_2 }) - { - REQUIRE(manifest.DefaultInstallerInfo.DisplayInstallWarnings); - REQUIRE(manifest.DefaultInstallerInfo.UnsupportedArguments.size() == 1); - REQUIRE(manifest.DefaultInstallerInfo.UnsupportedArguments.at(0) == UnsupportedArgumentEnum::Log); - } + if (manifestVer >= ManifestVer{ s_ManifestVersionV1_1 }) + { + REQUIRE(manifest.DefaultInstallerInfo.ReleaseDate == "2021-01-01"); + REQUIRE(manifest.DefaultInstallerInfo.InstallerAbortsTerminal); + REQUIRE(manifest.DefaultInstallerInfo.InstallLocationRequired); + REQUIRE(manifest.DefaultInstallerInfo.RequireExplicitUpgrade); + REQUIRE(manifest.DefaultInstallerInfo.ElevationRequirement == ElevationRequirementEnum::ElevatesSelf); + REQUIRE(manifest.DefaultInstallerInfo.UnsupportedOSArchitectures.size() == 1); + REQUIRE(manifest.DefaultInstallerInfo.UnsupportedOSArchitectures.at(0) == Architecture::Arm); + REQUIRE(manifest.DefaultInstallerInfo.AppsAndFeaturesEntries.size() == 1); + REQUIRE(manifest.DefaultInstallerInfo.AppsAndFeaturesEntries.at(0).DisplayName == "DisplayName"); + REQUIRE(manifest.DefaultInstallerInfo.AppsAndFeaturesEntries.at(0).DisplayVersion == "DisplayVersion"); + REQUIRE(manifest.DefaultInstallerInfo.AppsAndFeaturesEntries.at(0).Publisher == "Publisher"); + REQUIRE(manifest.DefaultInstallerInfo.AppsAndFeaturesEntries.at(0).ProductCode == "ProductCode"); + REQUIRE(manifest.DefaultInstallerInfo.AppsAndFeaturesEntries.at(0).UpgradeCode == "UpgradeCode"); + REQUIRE(manifest.DefaultInstallerInfo.AppsAndFeaturesEntries.at(0).InstallerType == InstallerTypeEnum::Exe); + REQUIRE(manifest.DefaultInstallerInfo.Markets.AllowedMarkets.size() == 1); + REQUIRE(manifest.DefaultInstallerInfo.Markets.AllowedMarkets.at(0) == "US"); + REQUIRE(manifest.DefaultInstallerInfo.ExpectedReturnCodes.size() == 1); + REQUIRE(manifest.DefaultInstallerInfo.ExpectedReturnCodes.at(10).ReturnResponseEnum == ExpectedReturnCodeEnum::PackageInUse); + } - if (manifestVer >= ManifestVer{ s_ManifestVersionV1_4 }) - { - REQUIRE(manifest.DefaultInstallerInfo.NestedInstallerType == InstallerTypeEnum::Msi); - REQUIRE(manifest.DefaultInstallerInfo.NestedInstallerFiles.size() == 1); - REQUIRE(manifest.DefaultInstallerInfo.NestedInstallerFiles.at(0).RelativeFilePath == "RelativeFilePath"); - REQUIRE(manifest.DefaultInstallerInfo.NestedInstallerFiles.at(0).PortableCommandAlias == "PortableCommandAlias"); - REQUIRE(manifest.DefaultInstallerInfo.InstallationMetadata.DefaultInstallLocation == "%ProgramFiles%\\TestApp"); - REQUIRE(manifest.DefaultInstallerInfo.InstallationMetadata.Files.size() == 1); - REQUIRE(manifest.DefaultInstallerInfo.InstallationMetadata.Files.at(0).RelativeFilePath == "main.exe"); - REQUIRE(manifest.DefaultInstallerInfo.InstallationMetadata.Files.at(0).FileType == InstalledFileTypeEnum::Launch); - REQUIRE(manifest.DefaultInstallerInfo.InstallationMetadata.Files.at(0).FileSha256 == SHA256::ConvertToBytes("69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8C82")); - REQUIRE(manifest.DefaultInstallerInfo.InstallationMetadata.Files.at(0).InvocationParameter == "/arg"); + if (manifestVer >= ManifestVer{ s_ManifestVersionV1_2 }) + { + REQUIRE(manifest.DefaultInstallerInfo.DisplayInstallWarnings); + REQUIRE(manifest.DefaultInstallerInfo.UnsupportedArguments.size() == 1); + REQUIRE(manifest.DefaultInstallerInfo.UnsupportedArguments.at(0) == UnsupportedArgumentEnum::Log); + } + + if (manifestVer >= ManifestVer{ s_ManifestVersionV1_4 }) + { + REQUIRE(manifest.DefaultInstallerInfo.NestedInstallerType == InstallerTypeEnum::Msi); + REQUIRE(manifest.DefaultInstallerInfo.NestedInstallerFiles.size() == 1); + REQUIRE(manifest.DefaultInstallerInfo.NestedInstallerFiles.at(0).RelativeFilePath == "RelativeFilePath"); + REQUIRE(manifest.DefaultInstallerInfo.NestedInstallerFiles.at(0).PortableCommandAlias == "PortableCommandAlias"); + REQUIRE(manifest.DefaultInstallerInfo.InstallationMetadata.DefaultInstallLocation == "%ProgramFiles%\\TestApp"); + REQUIRE(manifest.DefaultInstallerInfo.InstallationMetadata.Files.size() == 1); + REQUIRE(manifest.DefaultInstallerInfo.InstallationMetadata.Files.at(0).RelativeFilePath == "main.exe"); + REQUIRE(manifest.DefaultInstallerInfo.InstallationMetadata.Files.at(0).FileType == InstalledFileTypeEnum::Launch); + REQUIRE(manifest.DefaultInstallerInfo.InstallationMetadata.Files.at(0).FileSha256 == SHA256::ConvertToBytes("69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8C82")); + REQUIRE(manifest.DefaultInstallerInfo.InstallationMetadata.Files.at(0).InvocationParameter == "/arg"); + } } - if (isSingleton) + if (isSingleton || isExported) { REQUIRE(manifest.Installers.size() == 1); } @@ -568,7 +573,7 @@ void VerifyV1ManifestContent(const Manifest& manifest, bool isSingleton, Manifes auto installer1Dependencies = installer1.Dependencies; REQUIRE(installer1Dependencies.HasExactDependency(DependencyType::WindowsFeature, "PreviewIIS")); REQUIRE(installer1Dependencies.HasExactDependency(DependencyType::WindowsLibrary, "Preview VC Runtime")); - REQUIRE(installer1Dependencies.HasExactDependency(DependencyType::Package, "Microsoft.MsixSdkDepPreview")); + REQUIRE(installer1Dependencies.HasExactDependency(DependencyType::Package, "Microsoft.MsixSdkDepPreview", "1.0.0")); REQUIRE(installer1Dependencies.HasExactDependency(DependencyType::External, "Preview Outside dependencies")); REQUIRE(installer1Dependencies.Size() == 4); @@ -618,72 +623,75 @@ void VerifyV1ManifestContent(const Manifest& manifest, bool isSingleton, Manifes if (!isSingleton) { - ManifestInstaller installer2 = manifest.Installers.at(1); - REQUIRE(installer2.BaseInstallerType == InstallerTypeEnum::Exe); - REQUIRE(installer2.Arch == Architecture::X64); - REQUIRE(installer2.Url == "https://www.microsoft.com/msixsdk/msixsdkx64.exe"); - REQUIRE(installer2.Sha256 == SHA256::ConvertToBytes("69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8C82")); - REQUIRE(installer2.ProductCode == "{Bar}"); - - if (manifestVer >= ManifestVer{ s_ManifestVersionV1_1 }) - { - REQUIRE(installer2.ReleaseDate == "2021-01-01"); - REQUIRE(installer2.InstallerAbortsTerminal); - REQUIRE(installer2.InstallLocationRequired); - REQUIRE(installer2.RequireExplicitUpgrade); - REQUIRE(installer2.ElevationRequirement == ElevationRequirementEnum::ElevatesSelf); - REQUIRE(installer2.UnsupportedOSArchitectures.size() == 1); - REQUIRE(installer2.UnsupportedOSArchitectures.at(0) == Architecture::Arm); - REQUIRE(installer2.AppsAndFeaturesEntries.size() == 1); - REQUIRE(installer2.AppsAndFeaturesEntries.at(0).DisplayName == "DisplayName"); - REQUIRE(installer2.AppsAndFeaturesEntries.at(0).DisplayVersion == "DisplayVersion"); - REQUIRE(installer2.AppsAndFeaturesEntries.at(0).Publisher == "Publisher"); - REQUIRE(installer2.AppsAndFeaturesEntries.at(0).ProductCode == "ProductCode"); - REQUIRE(installer2.AppsAndFeaturesEntries.at(0).UpgradeCode == "UpgradeCode"); - REQUIRE(installer2.AppsAndFeaturesEntries.at(0).InstallerType == InstallerTypeEnum::Exe); - REQUIRE(installer2.Markets.AllowedMarkets.size() == 1); - REQUIRE(installer2.Markets.AllowedMarkets.at(0) == "US"); - REQUIRE(installer2.ExpectedReturnCodes.size() == 1); - REQUIRE(installer2.ExpectedReturnCodes.at(10).ReturnResponseEnum == ExpectedReturnCodeEnum::PackageInUse); - } - - if (manifestVer >= ManifestVer{ s_ManifestVersionV1_2 }) - { - ManifestInstaller installer3 = manifest.Installers.at(2); - REQUIRE(installer3.BaseInstallerType == InstallerTypeEnum::Portable); - REQUIRE(installer3.Arch == Architecture::X86); - REQUIRE(installer3.Url == "https://www.microsoft.com/msixsdk/msixsdkx86.exe"); - REQUIRE(installer3.Sha256 == SHA256::ConvertToBytes("69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8C82")); - REQUIRE(installer3.Commands == MultiValue{ "standalone" }); - REQUIRE(installer3.ExpectedReturnCodes.size() == 1); - REQUIRE(installer3.ExpectedReturnCodes.at(11).ReturnResponseEnum == ExpectedReturnCodeEnum::Custom); - REQUIRE(installer3.ExpectedReturnCodes.at(11).ReturnResponseUrl == "https://defaultReturnResponseUrl.com"); - REQUIRE_FALSE(installer3.DisplayInstallWarnings); - REQUIRE(installer3.UnsupportedArguments.size() == 1); - REQUIRE(installer3.UnsupportedArguments.at(0) == UnsupportedArgumentEnum::Log); - } - - if (manifestVer >= ManifestVer{ s_ManifestVersionV1_4 }) + if (!isExported) { - ManifestInstaller installer4 = manifest.Installers.at(3); - REQUIRE(installer4.BaseInstallerType == InstallerTypeEnum::Zip); - REQUIRE(installer4.Arch == Architecture::X64); - REQUIRE(installer4.Url == "https://www.microsoft.com/msixsdk/msixsdkx64.exe"); - REQUIRE(installer4.Sha256 == SHA256::ConvertToBytes("69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8C82")); - REQUIRE(installer4.ProductCode == "{Foo}"); - REQUIRE(installer4.NestedInstallerType == InstallerTypeEnum::Portable); - REQUIRE(installer4.NestedInstallerFiles.size() == 2); - REQUIRE(installer4.NestedInstallerFiles.at(0).RelativeFilePath == "relativeFilePath1"); - REQUIRE(installer4.NestedInstallerFiles.at(0).PortableCommandAlias == "portableAlias1"); - REQUIRE(installer4.NestedInstallerFiles.at(1).RelativeFilePath == "relativeFilePath2"); - REQUIRE(installer4.NestedInstallerFiles.at(1).PortableCommandAlias == "portableAlias2"); - REQUIRE(installer4.InstallationMetadata.DefaultInstallLocation == "%ProgramFiles%\\TestApp2"); - REQUIRE(installer4.InstallationMetadata.Files.size() == 1); - REQUIRE(installer4.InstallationMetadata.Files.at(0).RelativeFilePath == "main2.exe"); - REQUIRE(installer4.InstallationMetadata.Files.at(0).FileType == InstalledFileTypeEnum::Other); - REQUIRE(installer4.InstallationMetadata.Files.at(0).FileSha256 == SHA256::ConvertToBytes("69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8C82")); - REQUIRE(installer4.InstallationMetadata.Files.at(0).InvocationParameter == "/arg2"); - REQUIRE(installer4.InstallationMetadata.Files.at(0).DisplayName == "DisplayName2"); + ManifestInstaller installer2 = manifest.Installers.at(1); + REQUIRE(installer2.BaseInstallerType == InstallerTypeEnum::Exe); + REQUIRE(installer2.Arch == Architecture::X64); + REQUIRE(installer2.Url == "https://www.microsoft.com/msixsdk/msixsdkx64.exe"); + REQUIRE(installer2.Sha256 == SHA256::ConvertToBytes("69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8C82")); + REQUIRE(installer2.ProductCode == "{Bar}"); + + if (manifestVer >= ManifestVer{ s_ManifestVersionV1_1 }) + { + REQUIRE(installer2.ReleaseDate == "2021-01-01"); + REQUIRE(installer2.InstallerAbortsTerminal); + REQUIRE(installer2.InstallLocationRequired); + REQUIRE(installer2.RequireExplicitUpgrade); + REQUIRE(installer2.ElevationRequirement == ElevationRequirementEnum::ElevatesSelf); + REQUIRE(installer2.UnsupportedOSArchitectures.size() == 1); + REQUIRE(installer2.UnsupportedOSArchitectures.at(0) == Architecture::Arm); + REQUIRE(installer2.AppsAndFeaturesEntries.size() == 1); + REQUIRE(installer2.AppsAndFeaturesEntries.at(0).DisplayName == "DisplayName"); + REQUIRE(installer2.AppsAndFeaturesEntries.at(0).DisplayVersion == "DisplayVersion"); + REQUIRE(installer2.AppsAndFeaturesEntries.at(0).Publisher == "Publisher"); + REQUIRE(installer2.AppsAndFeaturesEntries.at(0).ProductCode == "ProductCode"); + REQUIRE(installer2.AppsAndFeaturesEntries.at(0).UpgradeCode == "UpgradeCode"); + REQUIRE(installer2.AppsAndFeaturesEntries.at(0).InstallerType == InstallerTypeEnum::Exe); + REQUIRE(installer2.Markets.AllowedMarkets.size() == 1); + REQUIRE(installer2.Markets.AllowedMarkets.at(0) == "US"); + REQUIRE(installer2.ExpectedReturnCodes.size() == 1); + REQUIRE(installer2.ExpectedReturnCodes.at(10).ReturnResponseEnum == ExpectedReturnCodeEnum::PackageInUse); + } + + if (manifestVer >= ManifestVer{ s_ManifestVersionV1_2 }) + { + ManifestInstaller installer3 = manifest.Installers.at(2); + REQUIRE(installer3.BaseInstallerType == InstallerTypeEnum::Portable); + REQUIRE(installer3.Arch == Architecture::X86); + REQUIRE(installer3.Url == "https://www.microsoft.com/msixsdk/msixsdkx86.exe"); + REQUIRE(installer3.Sha256 == SHA256::ConvertToBytes("69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8C82")); + REQUIRE(installer3.Commands == MultiValue{ "standalone" }); + REQUIRE(installer3.ExpectedReturnCodes.size() == 1); + REQUIRE(installer3.ExpectedReturnCodes.at(11).ReturnResponseEnum == ExpectedReturnCodeEnum::Custom); + REQUIRE(installer3.ExpectedReturnCodes.at(11).ReturnResponseUrl == "https://defaultReturnResponseUrl.com"); + REQUIRE_FALSE(installer3.DisplayInstallWarnings); + REQUIRE(installer3.UnsupportedArguments.size() == 1); + REQUIRE(installer3.UnsupportedArguments.at(0) == UnsupportedArgumentEnum::Log); + } + + if (manifestVer >= ManifestVer{ s_ManifestVersionV1_4 }) + { + ManifestInstaller installer4 = manifest.Installers.at(3); + REQUIRE(installer4.BaseInstallerType == InstallerTypeEnum::Zip); + REQUIRE(installer4.Arch == Architecture::X64); + REQUIRE(installer4.Url == "https://www.microsoft.com/msixsdk/msixsdkx64.exe"); + REQUIRE(installer4.Sha256 == SHA256::ConvertToBytes("69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8C82")); + REQUIRE(installer4.ProductCode == "{Foo}"); + REQUIRE(installer4.NestedInstallerType == InstallerTypeEnum::Portable); + REQUIRE(installer4.NestedInstallerFiles.size() == 2); + REQUIRE(installer4.NestedInstallerFiles.at(0).RelativeFilePath == "relativeFilePath1"); + REQUIRE(installer4.NestedInstallerFiles.at(0).PortableCommandAlias == "portableAlias1"); + REQUIRE(installer4.NestedInstallerFiles.at(1).RelativeFilePath == "relativeFilePath2"); + REQUIRE(installer4.NestedInstallerFiles.at(1).PortableCommandAlias == "portableAlias2"); + REQUIRE(installer4.InstallationMetadata.DefaultInstallLocation == "%ProgramFiles%\\TestApp2"); + REQUIRE(installer4.InstallationMetadata.Files.size() == 1); + REQUIRE(installer4.InstallationMetadata.Files.at(0).RelativeFilePath == "main2.exe"); + REQUIRE(installer4.InstallationMetadata.Files.at(0).FileType == InstalledFileTypeEnum::Other); + REQUIRE(installer4.InstallationMetadata.Files.at(0).FileSha256 == SHA256::ConvertToBytes("69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8C82")); + REQUIRE(installer4.InstallationMetadata.Files.at(0).InvocationParameter == "/arg2"); + REQUIRE(installer4.InstallationMetadata.Files.at(0).DisplayName == "DisplayName2"); + } } // Localization @@ -886,6 +894,192 @@ TEST_CASE("ValidateV1_6GoodManifestAndVerifyContents", "[ManifestValidation]") VerifyV1ManifestContent(mergedManifest, false, ManifestVer{ s_ManifestVersionV1_6 }); } +TEST_CASE("WriteV1SingletonManifestAndVerifyContents", "[ManifestCreation]") +{ + TempDirectory singletonDirectory{ "SingletonManifest" }; + CopyTestDataFilesToFolder({ "ManifestV1_1-Singleton.yaml" }, singletonDirectory); + Manifest singletonManifest = YamlParser::CreateFromPath(singletonDirectory); + + TempDirectory exportedSingletonDirectory{ "exportedSingleton" }; + std::filesystem::path generatedSingletonManifestPath = exportedSingletonDirectory.GetPath() / "testSingletonManifest.yaml"; + YamlWriter::OutputYamlFile(singletonManifest, singletonManifest.Installers[0], generatedSingletonManifestPath); + + REQUIRE(std::filesystem::exists(generatedSingletonManifestPath)); + Manifest generatedSingletonManifest = YamlParser::CreateFromPath(exportedSingletonDirectory); + VerifyV1ManifestContent(generatedSingletonManifest, true, ManifestVer{ s_ManifestVersionV1 }, true); + + TempDirectory multiFileDirectory{ "MultiFileManifest" }; + CopyTestDataFilesToFolder({ + "ManifestV1-MultiFile-Version.yaml", + "ManifestV1-MultiFile-Installer.yaml", + "ManifestV1-MultiFile-DefaultLocale.yaml", + "ManifestV1-MultiFile-Locale.yaml" }, multiFileDirectory); + + Manifest multiFileManifest = YamlParser::CreateFromPath(multiFileDirectory); + TempDirectory exportedMultiFileDirectory{ "exportedMultiFile" }; + std::filesystem::path generatedMultiFileManifestPath = exportedMultiFileDirectory.GetPath() / "testMultiFileManifest.yaml"; + YamlWriter::OutputYamlFile(multiFileManifest, multiFileManifest.Installers[0], generatedMultiFileManifestPath); + + REQUIRE(std::filesystem::exists(generatedMultiFileManifestPath)); + Manifest generatedMultiFileManifest = YamlParser::CreateFromPath(exportedMultiFileDirectory); + VerifyV1ManifestContent(generatedMultiFileManifest, false, ManifestVer{ s_ManifestVersionV1 }, true); +} + +TEST_CASE("WriteV1_1SingletonManifestAndVerifyContents", "[ManifestCreation]") +{ + TempDirectory singletonDirectory{ "SingletonManifest" }; + CopyTestDataFilesToFolder({ "ManifestV1_1-Singleton.yaml" }, singletonDirectory); + Manifest singletonManifest = YamlParser::CreateFromPath(singletonDirectory); + + TempDirectory exportedSingletonDirectory{ "exportedSingleton" }; + std::filesystem::path generatedSingletonManifestPath = exportedSingletonDirectory.GetPath() / "testSingletonManifest.yaml"; + YamlWriter::OutputYamlFile(singletonManifest, singletonManifest.Installers[0], generatedSingletonManifestPath); + + REQUIRE(std::filesystem::exists(generatedSingletonManifestPath)); + Manifest generatedSingletonManifest = YamlParser::CreateFromPath(exportedSingletonDirectory); + VerifyV1ManifestContent(generatedSingletonManifest, true, ManifestVer{ s_ManifestVersionV1_1 }, true); + + TempDirectory multiFileDirectory{ "MultiFileManifest" }; + CopyTestDataFilesToFolder({ + "ManifestV1_1-MultiFile-Version.yaml", + "ManifestV1_1-MultiFile-Installer.yaml", + "ManifestV1_1-MultiFile-DefaultLocale.yaml", + "ManifestV1_1-MultiFile-Locale.yaml" }, multiFileDirectory); + + Manifest multiFileManifest = YamlParser::CreateFromPath(multiFileDirectory); + TempDirectory exportedMultiFileDirectory{ "exportedMultiFile" }; + std::filesystem::path generatedMultiFileManifestPath = exportedMultiFileDirectory.GetPath() / "testMultiFileManifest.yaml"; + YamlWriter::OutputYamlFile(multiFileManifest, multiFileManifest.Installers[0], generatedMultiFileManifestPath); + + REQUIRE(std::filesystem::exists(generatedMultiFileManifestPath)); + Manifest generatedMultiFileManifest = YamlParser::CreateFromPath(exportedMultiFileDirectory); + VerifyV1ManifestContent(generatedMultiFileManifest, false, ManifestVer{ s_ManifestVersionV1_1 }, true); +} + +TEST_CASE("WriteV1_2SingletonManifestAndVerifyContents", "[ManifestCreation]") +{ + TempDirectory singletonDirectory{ "SingletonManifest" }; + CopyTestDataFilesToFolder({ "ManifestV1_2-Singleton.yaml" }, singletonDirectory); + Manifest singletonManifest = YamlParser::CreateFromPath(singletonDirectory); + + TempDirectory exportedSingletonDirectory{ "exportedSingleton" }; + std::filesystem::path generatedSingletonManifestPath = exportedSingletonDirectory.GetPath() / "testSingletonManifest.yaml"; + YamlWriter::OutputYamlFile(singletonManifest, singletonManifest.Installers[0], generatedSingletonManifestPath); + + REQUIRE(std::filesystem::exists(generatedSingletonManifestPath)); + Manifest generatedSingletonManifest = YamlParser::CreateFromPath(exportedSingletonDirectory); + VerifyV1ManifestContent(generatedSingletonManifest, true, ManifestVer{ s_ManifestVersionV1_2 }, true); + + TempDirectory multiFileDirectory{ "MultiFileManifest" }; + CopyTestDataFilesToFolder({ + "ManifestV1_2-MultiFile-Version.yaml", + "ManifestV1_2-MultiFile-Installer.yaml", + "ManifestV1_2-MultiFile-DefaultLocale.yaml", + "ManifestV1_2-MultiFile-Locale.yaml" }, multiFileDirectory); + + Manifest multiFileManifest = YamlParser::CreateFromPath(multiFileDirectory); + TempDirectory exportedMultiFileDirectory{ "exportedMultiFile" }; + std::filesystem::path generatedMultiFileManifestPath = exportedMultiFileDirectory.GetPath() / "testMultiFileManifest.yaml"; + YamlWriter::OutputYamlFile(multiFileManifest, multiFileManifest.Installers[0], generatedMultiFileManifestPath); + + REQUIRE(std::filesystem::exists(generatedMultiFileManifestPath)); + Manifest generatedMultiFileManifest = YamlParser::CreateFromPath(exportedMultiFileDirectory); + VerifyV1ManifestContent(generatedMultiFileManifest, false, ManifestVer{ s_ManifestVersionV1_2 }, true); +} + +TEST_CASE("WriteV1_4SingletonManifestAndVerifyContents", "[ManifestCreation]") +{ + TempDirectory singletonDirectory{ "SingletonManifest" }; + CopyTestDataFilesToFolder({ "ManifestV1_4-Singleton.yaml" }, singletonDirectory); + Manifest singletonManifest = YamlParser::CreateFromPath(singletonDirectory); + + TempDirectory exportedSingletonDirectory{ "exportedSingleton" }; + std::filesystem::path generatedSingletonManifestPath = exportedSingletonDirectory.GetPath() / "testSingletonManifest.yaml"; + YamlWriter::OutputYamlFile(singletonManifest, singletonManifest.Installers[0], generatedSingletonManifestPath); + + REQUIRE(std::filesystem::exists(generatedSingletonManifestPath)); + Manifest generatedSingletonManifest = YamlParser::CreateFromPath(exportedSingletonDirectory); + VerifyV1ManifestContent(generatedSingletonManifest, true, ManifestVer{ s_ManifestVersionV1_4 }, true); + + TempDirectory multiFileDirectory{ "MultiFileManifest" }; + CopyTestDataFilesToFolder({ + "ManifestV1_4-MultiFile-Version.yaml", + "ManifestV1_4-MultiFile-Installer.yaml", + "ManifestV1_4-MultiFile-DefaultLocale.yaml", + "ManifestV1_4-MultiFile-Locale.yaml" }, multiFileDirectory); + + Manifest multiFileManifest = YamlParser::CreateFromPath(multiFileDirectory); + TempDirectory exportedMultiFileDirectory{ "exportedMultiFile" }; + std::filesystem::path generatedMultiFileManifestPath = exportedMultiFileDirectory.GetPath() / "testMultiFileManifest.yaml"; + YamlWriter::OutputYamlFile(multiFileManifest, multiFileManifest.Installers[0], generatedMultiFileManifestPath); + + REQUIRE(std::filesystem::exists(generatedMultiFileManifestPath)); + Manifest generatedMultiFileManifest = YamlParser::CreateFromPath(exportedMultiFileDirectory); + VerifyV1ManifestContent(generatedMultiFileManifest, false, ManifestVer{ s_ManifestVersionV1_4 }, true); +} + +TEST_CASE("WriteV1_5SingletonManifestAndVerifyContents", "[ManifestCreation]") +{ + TempDirectory singletonDirectory{ "SingletonManifest" }; + CopyTestDataFilesToFolder({ "ManifestV1_5-Singleton.yaml" }, singletonDirectory); + Manifest singletonManifest = YamlParser::CreateFromPath(singletonDirectory); + + TempDirectory exportedSingletonDirectory{ "exportedSingleton" }; + std::filesystem::path generatedSingletonManifestPath = exportedSingletonDirectory.GetPath() / "testSingletonManifest.yaml"; + YamlWriter::OutputYamlFile(singletonManifest, singletonManifest.Installers[0], generatedSingletonManifestPath); + + REQUIRE(std::filesystem::exists(generatedSingletonManifestPath)); + Manifest generatedSingletonManifest = YamlParser::CreateFromPath(exportedSingletonDirectory); + VerifyV1ManifestContent(generatedSingletonManifest, true, ManifestVer{ s_ManifestVersionV1_5 }, true); + + TempDirectory multiFileDirectory{ "MultiFileManifest" }; + CopyTestDataFilesToFolder({ + "ManifestV1_5-MultiFile-Version.yaml", + "ManifestV1_5-MultiFile-Installer.yaml", + "ManifestV1_5-MultiFile-DefaultLocale.yaml", + "ManifestV1_5-MultiFile-Locale.yaml" }, multiFileDirectory); + + Manifest multiFileManifest = YamlParser::CreateFromPath(multiFileDirectory); + TempDirectory exportedMultiFileDirectory{ "exportedMultiFile" }; + std::filesystem::path generatedMultiFileManifestPath = exportedMultiFileDirectory.GetPath() / "testMultiFileManifest.yaml"; + YamlWriter::OutputYamlFile(multiFileManifest, multiFileManifest.Installers[0], generatedMultiFileManifestPath); + + REQUIRE(std::filesystem::exists(generatedMultiFileManifestPath)); + Manifest generatedMultiFileManifest = YamlParser::CreateFromPath(exportedMultiFileDirectory); + VerifyV1ManifestContent(generatedMultiFileManifest, false, ManifestVer{ s_ManifestVersionV1_5 }, true); +} + +TEST_CASE("WriteV1_6SingletonManifestAndVerifyContents", "[ManifestCreation]") +{ + TempDirectory singletonDirectory{ "SingletonManifest" }; + CopyTestDataFilesToFolder({ "ManifestV1_6-Singleton.yaml" }, singletonDirectory); + Manifest singletonManifest = YamlParser::CreateFromPath(singletonDirectory); + + TempDirectory exportedSingletonDirectory{ "exportedSingleton" }; + std::filesystem::path generatedSingletonManifestPath = exportedSingletonDirectory.GetPath() / "testSingletonManifest.yaml"; + YamlWriter::OutputYamlFile(singletonManifest, singletonManifest.Installers[0], generatedSingletonManifestPath); + + REQUIRE(std::filesystem::exists(generatedSingletonManifestPath)); + Manifest generatedSingletonManifest = YamlParser::CreateFromPath(exportedSingletonDirectory); + VerifyV1ManifestContent(generatedSingletonManifest, true, ManifestVer{ s_ManifestVersionV1_6 }, true); + + TempDirectory multiFileDirectory{ "MultiFileManifest" }; + CopyTestDataFilesToFolder({ + "ManifestV1_6-MultiFile-Version.yaml", + "ManifestV1_6-MultiFile-Installer.yaml", + "ManifestV1_6-MultiFile-DefaultLocale.yaml", + "ManifestV1_6-MultiFile-Locale.yaml" }, multiFileDirectory); + + Manifest multiFileManifest = YamlParser::CreateFromPath(multiFileDirectory); + TempDirectory exportedMultiFileDirectory{ "exportedMultiFile" }; + std::filesystem::path generatedMultiFileManifestPath = exportedMultiFileDirectory.GetPath() / "testMultiFileManifest.yaml"; + YamlWriter::OutputYamlFile(multiFileManifest, multiFileManifest.Installers[0], generatedMultiFileManifestPath); + + REQUIRE(std::filesystem::exists(generatedMultiFileManifestPath)); + Manifest generatedMultiFileManifest = YamlParser::CreateFromPath(exportedMultiFileDirectory); + VerifyV1ManifestContent(generatedMultiFileManifest, false, ManifestVer{ s_ManifestVersionV1_6 }, true); +} + YamlManifestInfo CreateYamlManifestInfo(std::string testDataFile) { YamlManifestInfo result; diff --git a/src/AppInstallerCommonCore/AppInstallerCommonCore.vcxproj b/src/AppInstallerCommonCore/AppInstallerCommonCore.vcxproj @@ -423,6 +423,7 @@ <ClInclude Include="Public\winget\ManifestValidation.h" /> <ClInclude Include="Public\winget\ManifestYamlParser.h" /> <ClInclude Include="Public\winget\ManifestYamlPopulator.h" /> + <ClInclude Include="Public\winget\ManifestYamlWriter.h" /> <ClInclude Include="Public\winget\MsiExecArguments.h" /> <ClInclude Include="Public\winget\MsixManifestValidation.h" /> <ClInclude Include="Public\winget\MSStore.h" /> @@ -483,6 +484,7 @@ <ClCompile Include="Manifest\ManifestYamlPopulator.cpp" /> <ClCompile Include="Manifest\MsixManifestValidation.cpp" /> <ClCompile Include="Manifest\YamlParser.cpp" /> + <ClCompile Include="Manifest\YamlWriter.cpp" /> <ClCompile Include="MsiExecArguments.cpp" /> <ClCompile Include="MsixInfo.cpp"> <ExcludedFromBuild Condition="'$(Configuration)'=='Fuzzing'">true</ExcludedFromBuild> diff --git a/src/AppInstallerCommonCore/AppInstallerCommonCore.vcxproj.filters b/src/AppInstallerCommonCore/AppInstallerCommonCore.vcxproj.filters @@ -96,6 +96,9 @@ <ClInclude Include="Public\winget\ManifestYamlParser.h"> <Filter>Public\winget</Filter> </ClInclude> + <ClInclude Include="Public\winget\ManifestYamlWriter.h"> + <Filter>Public\winget</Filter> + </ClInclude> <ClInclude Include="Public\winget\Registry.h"> <Filter>Public\winget</Filter> </ClInclude> @@ -254,6 +257,9 @@ <ClCompile Include="Manifest\YamlParser.cpp"> <Filter>Manifest</Filter> </ClCompile> + <ClCompile Include="Manifest\YamlWriter.cpp"> + <Filter>Manifest</Filter> + </ClCompile> <ClCompile Include="Registry.cpp"> <Filter>Source Files</Filter> </ClCompile> diff --git a/src/AppInstallerCommonCore/Manifest/ManifestCommon.cpp b/src/AppInstallerCommonCore/Manifest/ManifestCommon.cpp @@ -552,6 +552,100 @@ namespace AppInstaller::Manifest return "unknown"sv; } + std::string_view InstallerSwitchTypeToString(InstallerSwitchType installerSwitchType) + { + switch (installerSwitchType) + { + case InstallerSwitchType::Custom: + return "Custom"sv; + case InstallerSwitchType::Silent: + return "Silent"sv; + case InstallerSwitchType::SilentWithProgress: + return "SilentWithProgress"sv; + case InstallerSwitchType::Interactive: + return "Interactive"sv; + case InstallerSwitchType::Language: + return "Language"sv; + case InstallerSwitchType::Log: + return "Log"sv; + case InstallerSwitchType::InstallLocation: + return "InstallLocation"sv; + case InstallerSwitchType::Update: + return "Upgrade"sv; + } + + return "Unknown"sv; + } + + std::string_view ElevationRequirementToString(ElevationRequirementEnum elevationRequirement) + { + switch (elevationRequirement) + { + case ElevationRequirementEnum::ElevationRequired: + return "elevationRequired"sv; + case ElevationRequirementEnum::ElevationProhibited: + return "elevationProhibited"sv; + case ElevationRequirementEnum::ElevatesSelf: + return "elevatesSelf"sv; + } + + return "unknown"sv; + } + + std::string_view UnsupportedArgumentToString(UnsupportedArgumentEnum unsupportedArgument) + { + switch (unsupportedArgument) + { + case UnsupportedArgumentEnum::Log: + return "log"sv; + case UnsupportedArgumentEnum::Location: + return "location"sv; + } + + return "unknown"sv; + } + + std::string_view InstallModeToString(InstallModeEnum installMode) + { + switch (installMode) + { + case InstallModeEnum::Interactive: + return "interactive"sv; + case InstallModeEnum::Silent: + return "silent"sv; + case InstallModeEnum::SilentWithProgress: + return "silentWithProgress"sv; + } + + return "unknown"sv; + } + + std::string_view PlatformToString(PlatformEnum platform) + { + switch (platform) + { + case PlatformEnum::Desktop: + return "Windows.Desktop"sv; + case PlatformEnum::Universal: + return "Windows.Universal"sv; + } + + return "Unknown"sv; + } + + std::string_view UpdateBehaviorToString(UpdateBehaviorEnum updateBehavior) + { + switch (updateBehavior) + { + case UpdateBehaviorEnum::Install: + return "install"sv; + case UpdateBehaviorEnum::UninstallPrevious: + return "uninstallPrevious"sv; + } + + return "unknown"sv; + } + std::string_view ScopeToString(ScopeEnum scope) { switch (scope) @@ -577,7 +671,7 @@ namespace AppInstaller::Manifest return "other"sv; } - return "unknown"; + return "unknown"sv; } std::string_view IconFileTypeToString(IconFileTypeEnum iconFileType) @@ -592,7 +686,7 @@ namespace AppInstaller::Manifest return "png"sv; } - return "unknown"; + return "unknown"sv; } std::string_view IconThemeToString(IconThemeEnum iconTheme) @@ -609,7 +703,7 @@ namespace AppInstaller::Manifest return "highContrast"sv; } - return "unknown"; + return "unknown"sv; } std::string_view IconResolutionToString(IconResolutionEnum iconResolution) @@ -648,7 +742,75 @@ namespace AppInstaller::Manifest return "256x256"sv; } - return "unknown"; + return "unknown"sv; + } + + std::string_view ExpectedReturnCodeToString(ExpectedReturnCodeEnum expectedReturnCode) + { + switch (expectedReturnCode) + { + case ExpectedReturnCodeEnum::AlreadyInstalled: + return "alreadyInstalled"sv; + case ExpectedReturnCodeEnum::PackageInUse: + return "packageInUse"sv; + case ExpectedReturnCodeEnum::PackageInUseByApplication: + return "packageInUseByApplication"sv; + case ExpectedReturnCodeEnum::InstallInProgress: + return "installInProgress"sv; + case ExpectedReturnCodeEnum::FileInUse: + return "fileInUse"sv; + case ExpectedReturnCodeEnum::MissingDependency: + return "missingDependency"sv; + case ExpectedReturnCodeEnum::DiskFull: + return "diskFull"sv; + case ExpectedReturnCodeEnum::InsufficientMemory: + return "insufficientMemory"sv; + case ExpectedReturnCodeEnum::InvalidParameter: + return "invalidParameter"sv; + case ExpectedReturnCodeEnum::NoNetwork: + return "noNetwork"sv; + case ExpectedReturnCodeEnum::ContactSupport: + return "contactSupport"sv; + case ExpectedReturnCodeEnum::RebootRequiredToFinish: + return "rebootRequiredToFinish"sv; + case ExpectedReturnCodeEnum::RebootRequiredForInstall: + return "rebootRequiredForInstall"sv; + case ExpectedReturnCodeEnum::RebootInitiated: + return "rebootInitiated"sv; + case ExpectedReturnCodeEnum::CancelledByUser: + return "cancelledByUser"sv; + case ExpectedReturnCodeEnum::Downgrade: + return "downgrade"sv; + case ExpectedReturnCodeEnum::BlockedByPolicy: + return "blockedByPolicy"sv; + case ExpectedReturnCodeEnum::SystemNotSupported: + return "systemNotSupported"sv; + case ExpectedReturnCodeEnum::Custom: + return "custom"sv; + } + + return "unknown"sv; + } + + std::string_view ManifestTypeToString(ManifestTypeEnum manifestType) + { + switch (manifestType) + { + case ManifestTypeEnum::DefaultLocale: + return "defaultLocale"sv; + case ManifestTypeEnum::Installer: + return "installer"sv; + case ManifestTypeEnum::Locale: + return "locale"sv; + case ManifestTypeEnum::Merged: + return "merged"sv; + case ManifestTypeEnum::Singleton: + return "singleton"sv; + case ManifestTypeEnum::Version: + return "version"sv; + } + + return "unknown"sv; } bool DoesInstallerTypeUsePackageFamilyName(InstallerTypeEnum installerType) @@ -921,13 +1083,12 @@ namespace AppInstaller::Manifest { if (dependency.Type == type && Utility::ICUCaseInsensitiveEquals(dependency.Id(), id)) { - if (dependency.MinVersion) { - if (dependency.MinVersion.value() == minVersion) - { - return true; - } + if (!minVersion.empty()) + { + return dependency.MinVersion.value() == minVersion; } - else { + else + { return true; } } diff --git a/src/AppInstallerCommonCore/Manifest/YamlWriter.cpp b/src/AppInstallerCommonCore/Manifest/YamlWriter.cpp @@ -0,0 +1,667 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "AppInstallerStrings.h" +#include "AppInstallerSHA256.h" +#include "winget/Yaml.h" +#include "winget/ManifestYamlWriter.h" + +namespace AppInstaller::Manifest::YamlWriter +{ + using namespace Utility::literals; + + namespace + { + constexpr std::string_view PackageIdentifier = "PackageIdentifier"sv; + constexpr std::string_view PackageFamilyName = "PackageFamilyName"sv; + constexpr std::string_view ProductCode = "ProductCode"sv; + constexpr std::string_view Versions = "Versions"sv; + constexpr std::string_view PackageVersion = "PackageVersion"sv; + constexpr std::string_view Channel = "Channel"sv; + constexpr std::string_view ManifestVersion = "ManifestVersion"sv; + constexpr std::string_view ManifestType = "ManifestType"sv; + + // Installer + constexpr std::string_view Installers = "Installers"sv; + constexpr std::string_view InstallerIdentifier = "InstallerIdentifier"sv; + constexpr std::string_view InstallerSha256 = "InstallerSha256"sv; + constexpr std::string_view InstallerUrl = "InstallerUrl"sv; + constexpr std::string_view Architecture = "Architecture"sv; + constexpr std::string_view InstallerLocale = "InstallerLocale"sv; + constexpr std::string_view Platform = "Platform"sv; + constexpr std::string_view InstallerType = "InstallerType"sv; + constexpr std::string_view Scope = "Scope"sv; + constexpr std::string_view SignatureSha256 = "SignatureSha256"sv; + constexpr std::string_view InstallModes = "InstallModes"sv; + constexpr std::string_view MSStoreProductIdentifier = "MSStoreProductIdentifier"sv; + constexpr std::string_view ReleaseDate = "ReleaseDate"sv; + constexpr std::string_view InstallerAbortsTerminal = "InstallerAbortsTerminal"sv; + constexpr std::string_view InstallLocationRequired = "InstallLocationRequired"sv; + constexpr std::string_view RequireExplicitUpgrade = "RequireExplicitUpgrade"sv; + constexpr std::string_view UnsupportedOSArchitectures = "UnsupportedOSArchitectures"sv; + constexpr std::string_view AppsAndFeaturesEntries = "AppsAndFeaturesEntries"sv; + constexpr std::string_view DisplayVersion = "DisplayVersion"sv; + constexpr std::string_view UpgradeCode = "UpgradeCode"sv; + constexpr std::string_view Markets = "Markets"sv; + constexpr std::string_view AllowedMarkets = "AllowedMarkets"sv; + constexpr std::string_view ExcludedMarkets = "ExcludedMarkets"sv; + constexpr std::string_view ElevationRequirement = "ElevationRequirement"sv; + constexpr std::string_view ExpectedReturnCodes = "ExpectedReturnCodes"sv; + constexpr std::string_view InstallerReturnCode = "InstallerReturnCode"sv; + constexpr std::string_view ReturnResponse = "ReturnResponse"sv; + constexpr std::string_view ReturnResponseUrl = "ReturnResponseUrl"sv; + constexpr std::string_view NestedInstallerType = "NestedInstallerType"sv; + constexpr std::string_view DisplayInstallWarnings = "DisplayInstallWarnings"sv; + constexpr std::string_view UnsupportedArguments = "UnsupportedArguments"sv; + constexpr std::string_view NestedInstallerFiles = "NestedInstallerFiles"sv; + constexpr std::string_view NestedInstallerFileRelativeFilePath = "RelativeFilePath"sv; + constexpr std::string_view PortableCommandAlias = "PortableCommandAlias"sv; + constexpr std::string_view InstallationMetadata = "InstallationMetadata"sv; + constexpr std::string_view DefaultInstallLocation = "DefaultInstallLocation"sv; + constexpr std::string_view InstallationMetadataFiles = "Files"sv; + constexpr std::string_view InstallationMetadataRelativeFilePath = "RelativeFilePath"sv; + constexpr std::string_view FileSha256 = "FileSha256"sv; + constexpr std::string_view FileType = "FileType"sv; + constexpr std::string_view InvocationParameter = "InvocationParameter"sv; + constexpr std::string_view DisplayName = "DisplayName"sv; + constexpr std::string_view MinimumOSVersion = "MinimumOSVersion"sv; + + // Installer switches + constexpr std::string_view InstallerSwitches = "InstallerSwitches"sv; + constexpr std::string_view Silent = "Silent"sv; + constexpr std::string_view SilentWithProgress = "SilentWithProgress"sv; + constexpr std::string_view Interactive = "Interactive"sv; + constexpr std::string_view InstallLocation = "InstallLocation"sv; + constexpr std::string_view Log = "Log"sv; + constexpr std::string_view Upgrade = "Upgrade"sv; + constexpr std::string_view Custom = "Custom"sv; + + constexpr std::string_view InstallerSuccessCodes = "InstallerSuccessCodes"sv; + constexpr std::string_view UpgradeBehavior = "UpgradeBehavior"sv; + constexpr std::string_view Commands = "Commands"sv; + constexpr std::string_view Protocols = "Protocols"sv; + constexpr std::string_view FileExtensions = "FileExtensions"sv; + + // Dependencies + constexpr std::string_view Dependencies = "Dependencies"sv; + constexpr std::string_view WindowsFeatures = "WindowsFeatures"sv; + constexpr std::string_view WindowsLibraries = "WindowsLibraries"sv; + constexpr std::string_view PackageDependencies = "PackageDependencies"sv; + constexpr std::string_view MinimumVersion = "MinimumVersion"sv; + constexpr std::string_view ExternalDependencies = "ExternalDependencies"sv; + constexpr std::string_view Capabilities = "Capabilities"sv; + constexpr std::string_view RestrictedCapabilities = "RestrictedCapabilities"sv; + + // Locale + constexpr std::string_view Localization = "Localization"sv; + constexpr std::string_view InstallationNotes = "InstallationNotes"sv; + constexpr std::string_view PurchaseUrl = "PurchaseUrl"sv; + constexpr std::string_view Documentations = "Documentations"sv; + constexpr std::string_view DocumentLabel = "DocumentLabel"sv; + constexpr std::string_view DocumentUrl = "DocumentUrl"sv; + constexpr std::string_view Icons = "Icons"sv; + constexpr std::string_view IconUrl = "IconUrl"sv; + constexpr std::string_view IconFileType = "IconFileType"sv; + constexpr std::string_view IconResolution = "IconResolution"sv; + constexpr std::string_view IconTheme = "IconTheme"sv; + constexpr std::string_view IconSha256 = "IconSha256"sv; + constexpr std::string_view ReleaseNotes = "ReleaseNotes"sv; + constexpr std::string_view ReleaseNotesUrl = "ReleaseNotesUrl"sv; + constexpr std::string_view Agreements = "Agreements"sv; + constexpr std::string_view AgreementLabel = "AgreementLabel"sv; + constexpr std::string_view Agreement = "Agreement"sv; + constexpr std::string_view AgreementUrl = "AgreementUrl"sv; + constexpr std::string_view DefaultLocale = "DefaultLocale"sv; + constexpr std::string_view Locales = "Locales"sv; + constexpr std::string_view PackageLocale = "PackageLocale"sv; + constexpr std::string_view Publisher = "Publisher"sv; + constexpr std::string_view PublisherUrl = "PublisherUrl"sv; + constexpr std::string_view PublisherSupportUrl = "PublisherSupportUrl"sv; + constexpr std::string_view PrivacyUrl = "PrivacyUrl"sv; + constexpr std::string_view Author = "Author"sv; + constexpr std::string_view PackageName = "PackageName"sv; + constexpr std::string_view PackageUrl = "PackageUrl"sv; + constexpr std::string_view License = "License"sv; + constexpr std::string_view LicenseUrl = "LicenseUrl"sv; + constexpr std::string_view Copyright = "Copyright"sv; + constexpr std::string_view CopyrightUrl = "CopyrightUrl"sv; + constexpr std::string_view ShortDescription = "ShortDescription"sv; + constexpr std::string_view Description = "Description"sv; + constexpr std::string_view Tags = "Tags"sv; + constexpr std::string_view Moniker = "Moniker"sv; + +#define WRITE_PROPERTY(emitter, key, value) \ + { \ + emitter << YAML::Key << key << YAML::Value << value; \ + } + +#define WRITE_BOOL_PROPERTY(emitter, key, value) \ + { \ + emitter << YAML::Key << key << YAML::Value << Utility::ConvertBoolToString(value); \ + } + +#define WRITE_PROPERTY_IF_EXISTS(emitter, key, value) \ + { \ + if (!value.empty()) \ + { \ + WRITE_PROPERTY(emitter, key, value) \ + } \ + } + + void ProcessAgreements(YAML::Emitter& out, const std::vector<AppInstaller::Manifest::Agreement>& agreements) + { + if (agreements.empty()) + { + return; + } + + out << YAML::Key << Agreements; + out << YAML::BeginSeq; + for (const auto& agreement : agreements) + { + out << YAML::BeginMap; + WRITE_PROPERTY_IF_EXISTS(out, AgreementLabel, agreement.Label); + WRITE_PROPERTY_IF_EXISTS(out, AgreementUrl, agreement.AgreementUrl); + WRITE_PROPERTY_IF_EXISTS(out, Agreement, agreement.AgreementText); + out << YAML::EndMap; + } + out << YAML::EndSeq; + } + + void ProcessDocumentations(YAML::Emitter& out, const std::vector<AppInstaller::Manifest::Documentation>& documentations) + { + if (documentations.empty()) + { + return; + } + + out << YAML::Key << Documentations; + out << YAML::BeginSeq; + for (const auto& document : documentations) + { + out << YAML::BeginMap; + WRITE_PROPERTY_IF_EXISTS(out, DocumentLabel, document.DocumentLabel); + WRITE_PROPERTY_IF_EXISTS(out, DocumentUrl, document.DocumentUrl); + out << YAML::EndMap; + } + out << YAML::EndSeq; + } + + void ProcessIcons(YAML::Emitter& out, std::vector<AppInstaller::Manifest::Icon> icons) + { + if (icons.empty()) + { + return; + } + + out << YAML::Key << Icons; + out << YAML::BeginSeq; + for (const auto& icon : icons) + { + out << YAML::BeginMap; + WRITE_PROPERTY_IF_EXISTS(out, IconUrl, icon.Url); + WRITE_PROPERTY_IF_EXISTS(out, IconFileType, IconFileTypeToString(icon.FileType)); + WRITE_PROPERTY_IF_EXISTS(out, IconResolution, IconResolutionToString(icon.Resolution)); + WRITE_PROPERTY_IF_EXISTS(out, IconTheme, IconThemeToString(icon.Theme)); + WRITE_PROPERTY_IF_EXISTS(out, IconSha256, Utility::LocIndString{ Utility::SHA256::ConvertToString(icon.Sha256)}); + out << YAML::EndMap; + } + out << YAML::EndSeq; + } + + // Generic method for handling a list of strings (i.e. tags) + void ProcessSequence(YAML::Emitter& out, std::string_view name, std::vector<AppInstaller::Manifest::string_t> items) + { + if (items.empty()) + { + return; + } + + out << YAML::Key << name; + out << YAML::BeginSeq; + for (const auto& item : items) + { + out << item; + } + out << YAML::EndSeq; + } + + void ProcessLocaleFields(YAML::Emitter& out, const ManifestLocalization& manifest) + { + ProcessAgreements(out, manifest.Get<Localization::Agreements>()); + ProcessDocumentations(out, manifest.Get<Localization::Documentations>()); + ProcessIcons(out, manifest.Get<Localization::Icons>()); + ProcessSequence(out, Tags, manifest.Get<Localization::Tags>()); + + WRITE_PROPERTY(out, PackageLocale, manifest.Locale); + WRITE_PROPERTY_IF_EXISTS(out, Author, manifest.Get<Localization::Author>()); + WRITE_PROPERTY_IF_EXISTS(out, Copyright, manifest.Get<Localization::Copyright>()); + WRITE_PROPERTY_IF_EXISTS(out, CopyrightUrl, manifest.Get<Localization::CopyrightUrl>()); + WRITE_PROPERTY_IF_EXISTS(out, Description, manifest.Get<Localization::Description>()); + WRITE_PROPERTY_IF_EXISTS(out, ShortDescription, manifest.Get<Localization::ShortDescription>()); + WRITE_PROPERTY_IF_EXISTS(out, License, manifest.Get<Localization::License>()); + WRITE_PROPERTY_IF_EXISTS(out, LicenseUrl, manifest.Get<Localization::LicenseUrl>()); + WRITE_PROPERTY_IF_EXISTS(out, PackageName, manifest.Get<Localization::PackageName>()); + WRITE_PROPERTY_IF_EXISTS(out, PackageUrl, manifest.Get<Localization::PackageUrl>()); + WRITE_PROPERTY_IF_EXISTS(out, PrivacyUrl, manifest.Get<Localization::PrivacyUrl>()); + WRITE_PROPERTY_IF_EXISTS(out, Publisher, manifest.Get<Localization::Publisher>()); + WRITE_PROPERTY_IF_EXISTS(out, PublisherSupportUrl, manifest.Get<Localization::PublisherSupportUrl>()); + WRITE_PROPERTY_IF_EXISTS(out, PublisherUrl, manifest.Get<Localization::PublisherUrl>()); + WRITE_PROPERTY_IF_EXISTS(out, PurchaseUrl, manifest.Get<Localization::PurchaseUrl>()); + WRITE_PROPERTY_IF_EXISTS(out, ReleaseNotes, manifest.Get<Localization::ReleaseNotes>()); + WRITE_PROPERTY_IF_EXISTS(out, ReleaseNotesUrl, manifest.Get<Localization::ReleaseNotesUrl>()); + WRITE_PROPERTY_IF_EXISTS(out, InstallationNotes, manifest.Get<Localization::InstallationNotes>()); + } + + void ProcessAppsAndFeaturesEntries(YAML::Emitter& out, const std::vector<AppsAndFeaturesEntry>& appsAndFeaturesEntries) + { + if (appsAndFeaturesEntries.empty()) + { + return; + } + + out << YAML::Key << AppsAndFeaturesEntries; + out << YAML::BeginSeq; + for (const auto& appsAndFeatureEntry : appsAndFeaturesEntries) + { + out << YAML::BeginMap; + WRITE_PROPERTY_IF_EXISTS(out, DisplayName, appsAndFeatureEntry.DisplayName); + WRITE_PROPERTY_IF_EXISTS(out, DisplayVersion, appsAndFeatureEntry.DisplayVersion); + + if (appsAndFeatureEntry.InstallerType != InstallerTypeEnum::Unknown) + { + WRITE_PROPERTY(out, InstallerType, InstallerTypeToString(appsAndFeatureEntry.InstallerType)); + } + + WRITE_PROPERTY_IF_EXISTS(out, ProductCode, appsAndFeatureEntry.ProductCode); + WRITE_PROPERTY_IF_EXISTS(out, Publisher, appsAndFeatureEntry.Publisher); + WRITE_PROPERTY_IF_EXISTS(out, UpgradeCode, appsAndFeatureEntry.UpgradeCode); + out << YAML::EndMap; + } + out << YAML::EndSeq; + } + + void ProcessInstallerSwitches(YAML::Emitter& out, const std::map<InstallerSwitchType, string_t>& installerSwitches) + { + if (installerSwitches.empty()) + { + return; + } + + out << YAML::Key << InstallerSwitches; + out << YAML::BeginMap; + for (auto const& [type, value] : installerSwitches) + { + WRITE_PROPERTY_IF_EXISTS(out, InstallerSwitchTypeToString(type), value); + } + out << YAML::EndMap; + } + + void ProcessExpectedReturnCodes(YAML::Emitter& out, const std::map<DWORD, ManifestInstaller::ExpectedReturnCodeInfo>& expectedReturnCodes) + { + if (expectedReturnCodes.empty()) + { + return; + } + + out << YAML::Key << ExpectedReturnCodes; + out << YAML::BeginSeq; + for (const auto& expectedReturnCode : expectedReturnCodes) + { + out << YAML::BeginMap; + WRITE_PROPERTY_IF_EXISTS(out, InstallerReturnCode, std::to_string(expectedReturnCode.first)); + WRITE_PROPERTY_IF_EXISTS(out, ReturnResponse, ExpectedReturnCodeToString(expectedReturnCode.second.ReturnResponseEnum)); + WRITE_PROPERTY_IF_EXISTS(out, ReturnResponseUrl, expectedReturnCode.second.ReturnResponseUrl); + out << YAML::EndMap; + } + out << YAML::EndSeq; + } + + void ProcessUnsupportedArguments(YAML::Emitter& out, const std::vector<UnsupportedArgumentEnum>& unsupportedArguments) + { + if (unsupportedArguments.empty()) + { + return; + } + + out << YAML::Key << UnsupportedArguments; + out << YAML::BeginSeq; + for (auto const& unsupportedArgs : unsupportedArguments) + { + out << UnsupportedArgumentToString(unsupportedArgs); + } + out << YAML::EndSeq; + } + + void ProcessUnsupportedOSArchitecture(YAML::Emitter& out, const std::vector<AppInstaller::Utility::Architecture>& architectures) + { + if (architectures.empty()) + { + return; + } + + out << YAML::Key << UnsupportedOSArchitectures; + out << YAML::BeginSeq; + for (auto const& architecture : architectures) + { + out << Utility::ToLower(ToString(architecture)); + } + out << YAML::EndSeq; + } + + void ProcessInstallModes(YAML::Emitter& out, const std::vector<InstallModeEnum>& installModes) + { + if (installModes.empty()) + { + return; + } + + out << YAML::Key << InstallModes; + out << YAML::BeginSeq; + for (auto const& installMode : installModes) + { + out << InstallModeToString(installMode); + } + out << YAML::EndSeq; + } + + void ProcessPlatforms(YAML::Emitter& out, const std::vector<PlatformEnum>& platforms) + { + if (platforms.empty()) + { + return; + } + out << YAML::Key << Platform; + out << YAML::BeginSeq; + for (auto const& platform : platforms) + { + out << PlatformToString(platform); + } + out << YAML::EndSeq; + } + + void ProcessInstallerSuccessCodes(YAML::Emitter& out, const std::vector<DWORD>& installerSuccessCodes) + { + if (installerSuccessCodes.empty()) + { + return; + } + + out << YAML::Key << InstallerSuccessCodes; + out << YAML::BeginSeq; + for (auto const& installerSuccessCode : installerSuccessCodes) + { + out << std::to_string(installerSuccessCode); + } + out << YAML::EndSeq; + } + + void ProcessMarkets(YAML::Emitter& out, const MarketsInfo& marketsInfo) + { + if (marketsInfo.AllowedMarkets.empty() && marketsInfo.ExcludedMarkets.empty()) + { + return; + } + + out << YAML::Key << Markets; + out << YAML::BeginMap; + ProcessSequence(out, AllowedMarkets, marketsInfo.AllowedMarkets); + ProcessSequence(out, ExcludedMarkets, marketsInfo.ExcludedMarkets); + out << YAML::EndMap; + } + + void ProcessNestedInstallerFiles(YAML::Emitter& out, const std::vector<NestedInstallerFile>& nestedInstallerFiles) + { + if (nestedInstallerFiles.empty()) + { + return; + } + + out << YAML::Key << NestedInstallerFiles; + out << YAML::BeginSeq; + for (const auto& nestedInstallerFile : nestedInstallerFiles) + { + out << YAML::BeginMap; + WRITE_PROPERTY_IF_EXISTS(out, NestedInstallerFileRelativeFilePath, nestedInstallerFile.RelativeFilePath); + WRITE_PROPERTY_IF_EXISTS(out, PortableCommandAlias, nestedInstallerFile.PortableCommandAlias); + out << YAML::EndMap; + } + out << YAML::EndSeq; + } + + void ProcessInstallationMetadataInstalledFiles(YAML::Emitter& out, const std::vector<InstalledFile>& installedFiles) + { + if (installedFiles.empty()) + { + return; + } + + out << YAML::Key << InstallationMetadataFiles; + out << YAML::BeginSeq; + for (const auto& installedFile : installedFiles) + { + out << YAML::BeginMap; + WRITE_PROPERTY_IF_EXISTS(out, InstallationMetadataRelativeFilePath, installedFile.RelativeFilePath); + WRITE_PROPERTY_IF_EXISTS(out, FileSha256, Utility::LocIndString{ Utility::SHA256::ConvertToString(installedFile.FileSha256) }); + WRITE_PROPERTY_IF_EXISTS(out, FileType, InstalledFileTypeToString(installedFile.FileType)); + WRITE_PROPERTY_IF_EXISTS(out, InvocationParameter, installedFile.InvocationParameter); + WRITE_PROPERTY_IF_EXISTS(out, DisplayName, installedFile.DisplayName); + + out << YAML::EndMap; + } + out << YAML::EndSeq; + } + + void ProcessInstallationMetadata(YAML::Emitter& out, const InstallationMetadataInfo& installationMetadata) + { + if (installationMetadata.DefaultInstallLocation.empty() && installationMetadata.Files.empty()) + { + return; + } + + out << YAML::Key << InstallationMetadata; + out << YAML::BeginMap; + WRITE_PROPERTY_IF_EXISTS(out, DefaultInstallLocation, installationMetadata.DefaultInstallLocation); + ProcessInstallationMetadataInstalledFiles(out, installationMetadata.Files); + out << YAML::EndMap; + } + + void ProcessDependencies(YAML::Emitter& out, const DependencyList& dependencies) + { + if (!dependencies.HasAny()) + { + return; + } + + out << YAML::Key << Dependencies; + out << YAML::BeginMap; + + if (dependencies.HasAnyOf(DependencyType::WindowsFeature)) + { + out << YAML::Key << WindowsFeatures; + out << YAML::BeginSeq; + dependencies.ApplyToType(DependencyType::WindowsFeature, [&out](Dependency dependency) + { + out << dependency.Id(); + }); + out << YAML::EndSeq; + } + + if (dependencies.HasAnyOf(DependencyType::WindowsLibrary)) + { + out << YAML::Key << WindowsLibraries; + out << YAML::BeginSeq; + dependencies.ApplyToType(DependencyType::WindowsLibrary, [&out](Dependency dependency) + { + out << dependency.Id(); + }); + out << YAML::EndSeq; + } + + if (dependencies.HasAnyOf(DependencyType::Package)) + { + out << YAML::Key << PackageDependencies; + out << YAML::BeginSeq; + dependencies.ApplyToType(DependencyType::Package, [&out](Dependency dependency) + { + out << YAML::BeginMap; + WRITE_PROPERTY_IF_EXISTS(out, PackageIdentifier, dependency.Id()); + + if (dependency.MinVersion.has_value()) + { + WRITE_PROPERTY_IF_EXISTS(out, MinimumVersion, dependency.MinVersion.value().ToString()); + } + + out << YAML::EndMap; + }); + out << YAML::EndSeq; + } + + if (dependencies.HasAnyOf(DependencyType::External)) + { + out << YAML::Key << ExternalDependencies; + out << YAML::BeginSeq; + dependencies.ApplyToType(DependencyType::External, [&out](Dependency dependency) + { + out << dependency.Id(); + }); + out << YAML::EndSeq; + } + + out << YAML::EndMap; + } + + void ProcessInstallerFields(YAML::Emitter& out, const ManifestInstaller& installer) + { + WRITE_PROPERTY(out, Architecture, Utility::ToLower(ToString(installer.Arch))); + WRITE_PROPERTY(out, InstallerType, InstallerTypeToString(installer.BaseInstallerType)); + + if (installer.NestedInstallerType != InstallerTypeEnum::Unknown) + { + WRITE_PROPERTY(out, NestedInstallerType, InstallerTypeToString(installer.NestedInstallerType)); + } + + if (!installer.Sha256.empty()) + { + WRITE_PROPERTY(out, InstallerSha256, Utility::SHA256::ConvertToString(installer.Sha256)); + } + + if (!installer.SignatureSha256.empty()) + { + WRITE_PROPERTY(out, SignatureSha256, Utility::SHA256::ConvertToString(installer.SignatureSha256)); + } + WRITE_PROPERTY_IF_EXISTS(out, InstallerUrl, installer.Url); + WRITE_PROPERTY_IF_EXISTS(out, Scope, Utility::ToLower(ScopeToString(installer.Scope))); + WRITE_PROPERTY_IF_EXISTS(out, InstallerLocale, installer.Locale); + + if (installer.ElevationRequirement != ElevationRequirementEnum::Unknown) + { + WRITE_PROPERTY(out, ElevationRequirement, ElevationRequirementToString(installer.ElevationRequirement)); + } + + WRITE_PROPERTY_IF_EXISTS(out, PackageFamilyName, installer.PackageFamilyName); + WRITE_PROPERTY_IF_EXISTS(out, ReleaseDate, installer.ReleaseDate); + WRITE_BOOL_PROPERTY(out, InstallerAbortsTerminal, installer.InstallerAbortsTerminal); + WRITE_BOOL_PROPERTY(out, InstallLocationRequired, installer.InstallLocationRequired); + WRITE_BOOL_PROPERTY(out, RequireExplicitUpgrade, installer.RequireExplicitUpgrade); + WRITE_BOOL_PROPERTY(out, DisplayInstallWarnings, installer.DisplayInstallWarnings); + WRITE_PROPERTY_IF_EXISTS(out, MinimumOSVersion, installer.MinOSVersion); + WRITE_PROPERTY_IF_EXISTS(out, ProductCode, installer.ProductCode); + WRITE_PROPERTY_IF_EXISTS(out, UpgradeBehavior, UpdateBehaviorToString(installer.UpdateBehavior)); + + ProcessSequence(out, Capabilities, installer.Capabilities); + ProcessSequence(out, Commands, installer.Commands); + ProcessSequence(out, FileExtensions, installer.FileExtensions); + ProcessSequence(out, Protocols, installer.Protocols); + ProcessSequence(out, RestrictedCapabilities, installer.RestrictedCapabilities); + + ProcessAppsAndFeaturesEntries(out, installer.AppsAndFeaturesEntries); + ProcessDependencies(out, installer.Dependencies); + ProcessExpectedReturnCodes(out, installer.ExpectedReturnCodes); + ProcessInstallerSwitches(out, installer.Switches); + ProcessInstallationMetadata(out, installer.InstallationMetadata); + ProcessInstallerSuccessCodes(out, installer.InstallerSuccessCodes); + ProcessInstallModes(out, installer.InstallModes); + ProcessMarkets(out, installer.Markets); + ProcessNestedInstallerFiles(out, installer.NestedInstallerFiles); + ProcessPlatforms(out, installer.Platform); + ProcessUnsupportedArguments(out, installer.UnsupportedArguments); + ProcessUnsupportedOSArchitecture(out, installer.UnsupportedOSArchitectures); + } + + void ProcessInstaller(YAML::Emitter& out, const ManifestInstaller& installer) + { + out << YAML::Key << Installers; + out << YAML::BeginSeq; + out << YAML::BeginMap; + ProcessInstallerFields(out, installer); + out << YAML::EndMap; + out << YAML::EndSeq; + } + + void ProcessLocalizations(YAML::Emitter& out, const std::vector<ManifestLocalization>& localizations) + { + if (!localizations.empty()) + { + out << YAML::Key << Localization; + out << YAML::BeginSeq; + out << YAML::BeginMap; + + for (const auto& localization : localizations) + { + ProcessLocaleFields(out, localization); + } + + out << YAML::EndMap; + out << YAML::EndSeq; + } + } + + void PopulateManifestYamlEmitter(YAML::Emitter& out, const Manifest& manifest, const ManifestInstaller& installer) + { + // Currently, exporting the yaml only supports outputting a single installer. + // TODO: If no single installer is provided, output all installers. + out << YAML::BeginMap; + WRITE_PROPERTY(out, PackageIdentifier, manifest.Id); + WRITE_PROPERTY(out, PackageVersion, manifest.Version); + WRITE_PROPERTY_IF_EXISTS(out, Channel, manifest.Channel); + WRITE_PROPERTY_IF_EXISTS(out, Moniker, manifest.Moniker); + ProcessLocaleFields(out, manifest.DefaultLocalization); + ProcessLocalizations(out, manifest.Localizations); + ProcessInstaller(out, installer); + WRITE_PROPERTY(out, ManifestVersion, manifest.ManifestVersion.ToString()); + + ManifestTypeEnum manifestType = ManifestTypeEnum::Merged; + WRITE_PROPERTY(out, ManifestType, ManifestTypeToString(manifestType)); + out << YAML::EndMap; + } + } + + std::string YamlWriter::ManifestToYamlString(const Manifest& manifest, const ManifestInstaller& installer) + { + YAML::Emitter out; + PopulateManifestYamlEmitter(out, manifest, installer); + return out.str(); + } + + void YamlWriter::OutputYamlFile(const Manifest& manifest, const ManifestInstaller& installer, const std::filesystem::path& out) + { + const std::filesystem::path& parentDirectory = out.parent_path(); + + if (!std::filesystem::exists(parentDirectory)) + { + std::filesystem::create_directories(parentDirectory); + } + else + { + THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_CANNOT_MAKE), !std::filesystem::is_directory(parentDirectory)); + } + + YAML::Emitter emitter; + PopulateManifestYamlEmitter(emitter, manifest, installer); + + std::ofstream outFileStream(out); + emitter.Emit(outFileStream); + outFileStream.close(); + } +}+ \ No newline at end of file diff --git a/src/AppInstallerCommonCore/Public/winget/ManifestCommon.h b/src/AppInstallerCommonCore/Public/winget/ManifestCommon.h @@ -368,6 +368,18 @@ namespace AppInstaller::Manifest std::string_view InstallerTypeToString(InstallerTypeEnum installerType); + std::string_view InstallerSwitchTypeToString(InstallerSwitchType installerSwitchType); + + std::string_view ElevationRequirementToString(ElevationRequirementEnum elevationRequirement); + + std::string_view InstallModeToString(InstallModeEnum installMode); + + std::string_view UnsupportedArgumentToString(UnsupportedArgumentEnum unsupportedArgument); + + std::string_view UpdateBehaviorToString(UpdateBehaviorEnum updateBehavior); + + std::string_view PlatformToString(PlatformEnum platform); + std::string_view ScopeToString(ScopeEnum scope); std::string_view InstalledFileTypeToString(InstalledFileTypeEnum installedFileType); @@ -378,6 +390,10 @@ namespace AppInstaller::Manifest std::string_view IconResolutionToString(IconResolutionEnum iconResolution); + std::string_view ManifestTypeToString(ManifestTypeEnum manifestType); + + std::string_view ExpectedReturnCodeToString(ExpectedReturnCodeEnum expectedReturnCode); + // Gets a value indicating whether the given installer uses the PackageFamilyName system reference. bool DoesInstallerTypeUsePackageFamilyName(InstallerTypeEnum installerType); diff --git a/src/AppInstallerCommonCore/Public/winget/ManifestYamlWriter.h b/src/AppInstallerCommonCore/Public/winget/ManifestYamlWriter.h @@ -0,0 +1,24 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include <winget/Manifest.h> +#include <winget/Yaml.h> + +namespace AppInstaller::Manifest::YamlWriter +{ + /// <summary> + /// Converts the manifest and a single installer to a yaml string. + /// </summary> + /// <param name="manifest">Manifest object.</param> + /// <param name="installer">Manifest installer object.</param> + /// <returns>Yaml string.</returns> + std::string ManifestToYamlString(const Manifest& manifest, const ManifestInstaller& installer); + + /// <summary> + /// Exports the manifest and single manifest installer to a yaml file. + /// </summary> + /// <param name="manifest">Manifest object.</param> + /// <param name="installer">Manifest installer object.</param> + /// <param name="out">Path of the yaml file.</param> + void OutputYamlFile(const Manifest& manifest, const ManifestInstaller& installer, const std::filesystem::path& out); +}+ \ No newline at end of file diff --git a/src/AppInstallerSharedLib/AppInstallerStrings.cpp b/src/AppInstallerSharedLib/AppInstallerStrings.cpp @@ -855,4 +855,9 @@ namespace AppInstaller::Utility result.push_back(input.substr(startIndex)); return result; } + + std::string_view ConvertBoolToString(bool value) + { + return value ? "true"sv : "false"sv; + } } diff --git a/src/AppInstallerSharedLib/Public/AppInstallerStrings.h b/src/AppInstallerSharedLib/Public/AppInstallerStrings.h @@ -262,4 +262,8 @@ namespace AppInstaller::Utility (FindAndReplace(inputStr, "{" + std::to_string(index++) + "}", (std::ostringstream() << args).str()),...); return inputStr; } + + // Converts the given boolean value to a string. + std::string_view ConvertBoolToString(bool value); + }