commit a91559d09e1fd146ee28443b147a81a14c9d8ffa parent 888b4ed8f4f7d25cb05a47210e083fe29348163b Author: Ryan Fu <69221034+ryfu-msft@users.noreply.github.com> Date: Thu, 28 Jul 2022 08:23:28 -0700 Remove scope filter from being applied to portables (#2383) Diffstat:
20 files changed, 292 insertions(+), 63 deletions(-)
diff --git a/doc/Settings.md b/doc/Settings.md @@ -59,24 +59,24 @@ The `disableInstallNotes` behavior affects whether installation notes are shown ``` ### Portable Package User Root -The `PortablePackageUserRoot` setting affects the default root directory where packages are installed to under `User` scope. This setting only applies to packages with the `portable` installer type. Defaults to `%LOCALAPPDATA%/Microsoft/WinGet/Packages/` if value is not set or is invalid. +The `portablePackageUserRoot` setting affects the default root directory where packages are installed to under `User` scope. This setting only applies to packages with the `portable` installer type. Defaults to `%LOCALAPPDATA%/Microsoft/WinGet/Packages/` if value is not set or is invalid. > Note: This setting value must be an absolute path. ```json "installBehavior": { - "PortablePackageUserRoot": "C:/Users/FooBar/Packages" + "portablePackageUserRoot": "C:/Users/FooBar/Packages" }, ``` ### Portable Package Machine Root -The `PortablePackageMachineRoot` setting affects the default root directory where packages are installed to under `Machine` scope. This setting only applies to packages with the `portable` installer type. Defaults to `%PROGRAMFILES%/WinGet/Packages/` if value is not set or is invalid. +The `portablePackageMachineRoot` setting affects the default root directory where packages are installed to under `Machine` scope. This setting only applies to packages with the `portable` installer type. Defaults to `%PROGRAMFILES%/WinGet/Packages/` if value is not set or is invalid. > Note: This setting value must be an absolute path. ```json "installBehavior": { - "PortablePackageMachineRoot": "C:/Program Files/Packages/Portable" + "portablePackageMachineRoot": "C:/Program Files/Packages/Portable" }, ``` diff --git a/schemas/JSON/settings/settings.schema.0.2.json b/schemas/JSON/settings/settings.schema.0.2.json @@ -107,12 +107,12 @@ "type": "boolean", "default": false }, - "PortablePackageUserRoot": { + "portablePackageUserRoot": { "description": "The default root directory where packages are installed to under User scope. Applies to the portable installer type.", "type": "string", "default": "%LOCALAPPDATA%/Microsoft/WinGet/Packages/" }, - "PortablePackageMachineRoot": { + "portablePackageMachineRoot": { "description": "The default root directory where packages are installed to under Machine scope. Applies to the portable installer type.", "type": "string", "default": "%PROGRAMFILES%/WinGet/Packages/" diff --git a/src/AppInstallerCLICore/Workflows/ManifestComparator.cpp b/src/AppInstallerCLICore/Workflows/ManifestComparator.cpp @@ -287,7 +287,7 @@ namespace AppInstaller::CLI::Workflow InapplicabilityFlags IsApplicable(const Manifest::ManifestInstaller& installer) override { // We have to assume the unknown scope will match our required scope, or the entire catalog would stop working for upgrade. - if (installer.Scope == Manifest::ScopeEnum::Unknown || installer.Scope == m_requirement) + if (installer.Scope == Manifest::ScopeEnum::Unknown || installer.Scope == m_requirement || DoesInstallerIgnoreScopeFromManifest(installer)) { return InapplicabilityFlags::None; } @@ -342,7 +342,7 @@ namespace AppInstaller::CLI::Workflow InapplicabilityFlags IsApplicable(const Manifest::ManifestInstaller& installer) override { - if (m_requirement == Manifest::ScopeEnum::Unknown || installer.Scope == m_requirement) + if (m_requirement == Manifest::ScopeEnum::Unknown || installer.Scope == m_requirement || DoesInstallerIgnoreScopeFromManifest(installer)) { return InapplicabilityFlags::None; } diff --git a/src/AppInstallerCLICore/Workflows/PortableFlow.cpp b/src/AppInstallerCLICore/Workflows/PortableFlow.cpp @@ -556,12 +556,38 @@ namespace AppInstaller::CLI::Workflow } } } + + void EnsureRunningAsAdminForMachineScopeInstall(Execution::Context& context) + { + // Admin is required for machine scope install or else creating a symlink in the %PROGRAMFILES% link location will fail. + Manifest::ScopeEnum scope = ConvertToScopeEnum(context.Args.GetArg(Execution::Args::Type::InstallScope)); + if (scope == Manifest::ScopeEnum::Machine) + { + context << Workflow::EnsureRunningAsAdmin; + } + } } void PortableInstallImpl(Execution::Context& context) { + Manifest::ScopeEnum scope = Manifest::ScopeEnum::Unknown; + bool isUpdate = WI_IsFlagSet(context.GetFlags(), Execution::ContextFlag::InstallerExecutionUseUpdate); + if (isUpdate) + { + IPackageVersion::Metadata installationMetadata = context.Get<Execution::Data::InstalledPackageVersion>()->GetMetadata(); + auto installerScopeItr = installationMetadata.find(Repository::PackageVersionMetadata::InstalledScope); + if (installerScopeItr != installationMetadata.end()) + { + scope = Manifest::ConvertToScopeEnum(installerScopeItr->second); + } + } + else + { + scope = ConvertToScopeEnum(context.Args.GetArg(Execution::Args::Type::InstallScope)); + } + PortableARPEntry uninstallEntry = PortableARPEntry( - ConvertToScopeEnum(context.Args.GetArg(Execution::Args::Type::InstallScope)), + scope, context.Get<Execution::Data::Installer>()->Arch, GetPortableProductCode(context)); @@ -589,7 +615,6 @@ namespace AppInstaller::CLI::Workflow // Perform cleanup only if the install fails and is not an update. const auto& installReturnCode = context.Get<Execution::Data::OperationReturnCode>(); - bool isUpdate = WI_IsFlagSet(context.GetFlags(), Execution::ContextFlag::InstallerExecutionUseUpdate); if (installReturnCode != 0 && installReturnCode != APPINSTALLER_CLI_ERROR_PORTABLE_PACKAGE_ALREADY_EXISTS && !isUpdate) { @@ -634,6 +659,7 @@ namespace AppInstaller::CLI::Workflow { context << EnsureSymlinkCreationPrivilege << + EnsureRunningAsAdminForMachineScopeInstall << EnsureValidArgsForPortableInstall << EnsureVolumeSupportsReparsePoints; } diff --git a/src/AppInstallerCLIE2ETests/BaseCommand.cs b/src/AppInstallerCLIE2ETests/BaseCommand.cs @@ -39,6 +39,16 @@ namespace AppInstallerCLIE2ETests File.WriteAllText(Path.Combine(localAppDataPath, TestCommon.SettingsJsonFilePath), settingsJson.ToString()); } + public void ConfigureInstallBehavior(string settingName, string value) + { + string localAppDataPath = Environment.GetEnvironmentVariable(Constants.LocalAppData); + JObject settingsJson = JObject.Parse(File.ReadAllText(Path.Combine(localAppDataPath, TestCommon.SettingsJsonFilePath))); + JObject installBehavior = (JObject)settingsJson["installBehavior"]; + installBehavior[settingName] = value; + + File.WriteAllText(Path.Combine(localAppDataPath, TestCommon.SettingsJsonFilePath), settingsJson.ToString()); + } + public void InitializeAllFeatures(bool status) { ConfigureFeature("experimentalArg", status); diff --git a/src/AppInstallerCLIE2ETests/Constants.cs b/src/AppInstallerCLIE2ETests/Constants.cs @@ -83,6 +83,13 @@ namespace AppInstallerCLIE2ETests // Registry keys public const string WinGetPackageIdentifier = "WinGetPackageIdentifier"; public const string WinGetSourceIdentifier = "WinGetSourceIdentifier"; + public const string UninstallSubKey = @"Software\Microsoft\Windows\CurrentVersion\Uninstall"; + public const string PathSubKey_User = @"Environment"; + public const string PathSubKey_Machine = @"SYSTEM\CurrentControlSet\Control\Session Manager\Environment"; + + // User settings + public const string PortablePackageUserRoot = "portablePackageUserRoot"; + public const string PortablePackageMachineRoot = "portablePackageMachineRoot"; public class ErrorCode { diff --git a/src/AppInstallerCLIE2ETests/InstallCommand.cs b/src/AppInstallerCLIE2ETests/InstallCommand.cs @@ -259,7 +259,7 @@ namespace AppInstallerCLIE2ETests commandAlias = fileName = "AppInstallerTestExeInstaller.exe"; // Create a directory with the same name as the symlink in order to cause install to fail. - string symlinkDirectory = TestCommon.GetPortableSymlinkDirectory(); + string symlinkDirectory = TestCommon.GetPortableSymlinkDirectory(TestCommon.Scope.User); string conflictDirectory = Path.Combine(symlinkDirectory, commandAlias); Directory.CreateDirectory(conflictDirectory); @@ -285,7 +285,7 @@ namespace AppInstallerCLIE2ETests var result = TestCommon.RunAICLICommand("install", "AppInstallerTest.TestPortableExe"); Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); - string symlinkDirectory = TestCommon.GetPortableSymlinkDirectory(); + string symlinkDirectory = TestCommon.GetPortableSymlinkDirectory(TestCommon.Scope.User); string symlinkPath = Path.Combine(symlinkDirectory, commandAlias); // Clean first install should not display file overwrite message. @@ -302,6 +302,42 @@ namespace AppInstallerCLIE2ETests } [Test] + public void InstallPortable_UserScope() + { + string installDir = TestCommon.GetRandomTestDir(); + ConfigureInstallBehavior(Constants.PortablePackageUserRoot, installDir); + + string packageId, commandAlias, fileName, packageDirName, productCode; + packageId = "AppInstallerTest.TestPortableExe"; + packageDirName = productCode = packageId + "_" + Constants.TestSourceIdentifier; + commandAlias = fileName = "AppInstallerTestExeInstaller.exe"; + + var result = TestCommon.RunAICLICommand("install", "AppInstallerTest.TestPortableExe --scope user"); + ConfigureInstallBehavior(Constants.PortablePackageUserRoot, string.Empty); + Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); + Assert.True(result.StdOut.Contains("Successfully installed")); + TestCommon.VerifyPortablePackage(Path.Combine(installDir, packageDirName), commandAlias, fileName, productCode, true); + } + + [Test] + public void InstallPortable_MachineScope() + { + string installDir = TestCommon.GetRandomTestDir(); + ConfigureInstallBehavior(Constants.PortablePackageMachineRoot, installDir); + + string packageId, commandAlias, fileName, packageDirName, productCode; + packageId = "AppInstallerTest.TestPortableExe"; + packageDirName = productCode = packageId + "_" + Constants.TestSourceIdentifier; + commandAlias = fileName = "AppInstallerTestExeInstaller.exe"; + + var result = TestCommon.RunAICLICommand("install", "AppInstallerTest.TestPortableExe --scope machine"); + ConfigureInstallBehavior(Constants.PortablePackageMachineRoot, string.Empty); + Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); + Assert.True(result.StdOut.Contains("Successfully installed")); + TestCommon.VerifyPortablePackage(Path.Combine(installDir, packageDirName), commandAlias, fileName, productCode, true, TestCommon.Scope.Machine); + } + + [Test] public void InstallZipWithExe() { var installDir = TestCommon.GetRandomTestDir(); diff --git a/src/AppInstallerCLIE2ETests/SetUpFixture.cs b/src/AppInstallerCLIE2ETests/SetUpFixture.cs @@ -216,6 +216,11 @@ namespace AppInstallerCLIE2ETests { enableSelfInitiatedMinidump = true }, + installBehavior = new + { + portablePackageUserRoot = "", + portablePackageMachineRoot = "", + } }; // Run winget one time to initialize settings directory diff --git a/src/AppInstallerCLIE2ETests/TestCommon.cs b/src/AppInstallerCLIE2ETests/TestCommon.cs @@ -8,7 +8,6 @@ namespace AppInstallerCLIE2ETests using System; using System.Diagnostics; using System.IO; - using System.Linq; using System.Threading; public class TestCommon @@ -46,6 +45,12 @@ namespace AppInstallerCLIE2ETests } } + public enum Scope + { + User, + Machine + } + public struct RunCommandResult { public int ExitCode; @@ -280,9 +285,16 @@ namespace AppInstallerCLIE2ETests return RunCommand("powershell", $"Get-AppxPackage \"{name}\" | Remove-AppxPackage"); } - public static string GetPortableSymlinkDirectory() + public static string GetPortableSymlinkDirectory(Scope scope) { - return Path.Combine(Environment.GetEnvironmentVariable("LocalAppData"), "Microsoft", "WinGet", "Links"); + if (scope == Scope.User) + { + return Path.Combine(Environment.GetEnvironmentVariable("LocalAppData"), "Microsoft", "WinGet", "Links"); + } + else + { + return Path.Combine(Environment.GetEnvironmentVariable("ProgramFiles"), "WinGet", "Links"); + } } public static string GetPortablePackagesDirectory() @@ -295,25 +307,28 @@ namespace AppInstallerCLIE2ETests string commandAlias, string filename, string productCode, - bool shouldExist) + bool shouldExist, + Scope scope = Scope.User) { string exePath = Path.Combine(installDir, filename); bool exeExists = File.Exists(exePath); - string symlinkDirectory = GetPortableSymlinkDirectory(); + string symlinkDirectory = GetPortableSymlinkDirectory(scope); string symlinkPath = Path.Combine(symlinkDirectory, commandAlias); bool symlinkExists = File.Exists(symlinkPath); bool portableEntryExists; - string subKey = @$"Software\Microsoft\Windows\CurrentVersion\Uninstall"; - using (RegistryKey uninstallRegistryKey = Registry.CurrentUser.OpenSubKey(subKey, true)) + RegistryKey baseKey = (scope == Scope.User) ? Registry.CurrentUser : Registry.LocalMachine; + string uninstallSubKey = Constants.UninstallSubKey; + using (RegistryKey uninstallRegistryKey = baseKey.OpenSubKey(uninstallSubKey, true)) { RegistryKey portableEntry = uninstallRegistryKey.OpenSubKey(productCode, true); portableEntryExists = portableEntry != null; } bool isAddedToPath; - using (RegistryKey environmentRegistryKey = Registry.CurrentUser.OpenSubKey(@"Environment", true)) + string pathSubKey = (scope == Scope.User) ? Constants.PathSubKey_User : Constants.PathSubKey_Machine; + using (RegistryKey environmentRegistryKey = baseKey.OpenSubKey(pathSubKey, true)) { string pathName = "Path"; var currentPathValue = (string)environmentRegistryKey.GetValue(pathName); @@ -328,7 +343,7 @@ namespace AppInstallerCLIE2ETests Assert.AreEqual(shouldExist, exeExists, $"Expected portable exe path: {exePath}"); Assert.AreEqual(shouldExist, symlinkExists, $"Expected portable symlink path: {symlinkPath}"); - Assert.AreEqual(shouldExist, portableEntryExists, $"Expected {productCode} subkey in path: {subKey}"); + Assert.AreEqual(shouldExist, portableEntryExists, $"Expected {productCode} subkey in path: {uninstallSubKey}"); Assert.AreEqual(shouldExist, isAddedToPath, $"Expected path variable: {symlinkDirectory}"); } diff --git a/src/AppInstallerCLIE2ETests/UpgradeCommand.cs b/src/AppInstallerCLIE2ETests/UpgradeCommand.cs @@ -11,7 +11,7 @@ namespace AppInstallerCLIE2ETests [Test] public void UpgradePortable() { - string installDir = Path.Combine(System.Environment.GetEnvironmentVariable("LocalAppData"), "Microsoft", "WinGet", "Packages"); + string installDir = TestCommon.GetPortablePackagesDirectory(); string packageId, commandAlias, fileName, packageDirName, productCode; packageId = "AppInstallerTest.TestPortableExe"; packageDirName = productCode = packageId + "_" + Constants.TestSourceIdentifier; @@ -53,7 +53,7 @@ namespace AppInstallerCLIE2ETests [Test] public void UpgradePortableForcedOverride() { - string installDir = Path.Combine(System.Environment.GetEnvironmentVariable("LocalAppData"), "Microsoft", "WinGet", "Packages"); + string installDir = TestCommon.GetPortablePackagesDirectory(); string packageId, commandAlias, fileName, packageDirName, productCode; packageId = "AppInstallerTest.TestPortableExe"; packageDirName = productCode = packageId + "_" + Constants.TestSourceIdentifier; @@ -76,7 +76,7 @@ namespace AppInstallerCLIE2ETests [Test] public void UpgradePortableUninstallPrevious() { - string installDir = Path.Combine(System.Environment.GetEnvironmentVariable("LocalAppData"), "Microsoft", "WinGet", "Packages"); + string installDir = TestCommon.GetPortablePackagesDirectory(); string packageId, commandAlias, fileName, packageDirName, productCode; packageId = "AppInstallerTest.TestPortableExe"; packageDirName = productCode = packageId + "_" + Constants.TestSourceIdentifier; @@ -91,5 +91,27 @@ namespace AppInstallerCLIE2ETests Assert.True(result2.StdOut.Contains("Successfully installed")); TestCommon.VerifyPortablePackage(Path.Combine(installDir, packageDirName), commandAlias, fileName, productCode, true); } + + [Test] + public void UpgradePortableMachineScope() + { + string installDir = TestCommon.GetRandomTestDir(); + ConfigureInstallBehavior(Constants.PortablePackageMachineRoot, installDir); + + string packageId, commandAlias, fileName, packageDirName, productCode; + packageId = "AppInstallerTest.TestPortableExe"; + packageDirName = productCode = packageId + "_" + Constants.TestSourceIdentifier; + commandAlias = fileName = "AppInstallerTestExeInstaller.exe"; + + var result = TestCommon.RunAICLICommand("install", $"{packageId} -v 1.0.0.0 --scope machine"); + Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); + Assert.True(result.StdOut.Contains("Successfully installed")); + + var result2 = TestCommon.RunAICLICommand("upgrade", $"{packageId} -v 2.0.0.0"); + ConfigureInstallBehavior(Constants.PortablePackageMachineRoot, string.Empty); + Assert.AreEqual(Constants.ErrorCode.S_OK, result2.ExitCode); + Assert.True(result2.StdOut.Contains("Successfully installed")); + TestCommon.VerifyPortablePackage(Path.Combine(installDir, packageDirName), commandAlias, fileName, productCode, true, TestCommon.Scope.Machine); + } } } diff --git a/src/AppInstallerCLITests/UserSettings.cpp b/src/AppInstallerCLITests/UserSettings.cpp @@ -414,15 +414,50 @@ TEST_CASE("SettingsExperimentalCmd", "[settings]") } } -TEST_CASE("SettingsPortableAppRoot", "[settings]") +TEST_CASE("SettingsPortablePackageUserRoot", "[settings]") { SECTION("Relative path") { - std::string_view json = R"({ "installBehavior": { "portableAppUserRoot": %LOCALAPPDATA%/Portable/Root } })"; + DeleteUserSettingsFiles(); + std::string_view json = R"({ "installBehavior": { "portablePackageUserRoot": %LOCALAPPDATA%/Portable/Root } })"; SetSetting(Stream::PrimaryUserSettings, json); UserSettingsTest userSettingTest; - REQUIRE(userSettingTest.Get<Setting::PortableAppUserRoot>().empty()); + REQUIRE(userSettingTest.Get<Setting::PortablePackageUserRoot>().empty()); REQUIRE(userSettingTest.GetWarnings().size() == 1); } + SECTION("Valid path") + { + DeleteUserSettingsFiles(); + std::string_view json = R"({ "installBehavior": { "portablePackageUserRoot": "C:/Foo/Bar" } })"; + SetSetting(Stream::PrimaryUserSettings, json); + UserSettingsTest userSettingTest; + + REQUIRE(userSettingTest.Get<Setting::PortablePackageUserRoot>() == "C:/Foo/Bar"); + REQUIRE(userSettingTest.GetWarnings().size() == 0); + } +} + +TEST_CASE("SettingsPortablePackageMachineRoot", "[settings]") +{ + SECTION("Relative path") + { + DeleteUserSettingsFiles(); + std::string_view json = R"({ "installBehavior": { "portablePackageMachineRoot": %LOCALAPPDATA%/Portable/Root } })"; + SetSetting(Stream::PrimaryUserSettings, json); + UserSettingsTest userSettingTest; + + REQUIRE(userSettingTest.Get<Setting::PortablePackageMachineRoot>().empty()); + REQUIRE(userSettingTest.GetWarnings().size() == 1); + } + SECTION("Valid path") + { + DeleteUserSettingsFiles(); + std::string_view json = R"({ "installBehavior": { "portablePackageMachineRoot": "C:/Foo/Bar" } })"; + SetSetting(Stream::PrimaryUserSettings, json); + UserSettingsTest userSettingTest; + + REQUIRE(userSettingTest.Get<Setting::PortablePackageMachineRoot>() == "C:/Foo/Bar"); + REQUIRE(userSettingTest.GetWarnings().size() == 0); + } } diff --git a/src/AppInstallerCLITests/WorkFlow.cpp b/src/AppInstallerCLITests/WorkFlow.cpp @@ -615,13 +615,6 @@ void OverrideForPortableUninstall(TestContext& context) } }); } -void OverrideForEnsureSupportForPortable(TestContext& context) -{ - context.Override({ EnsureSupportForPortableInstall, [](TestContext&) - { - } }); -} - void OverrideForArchiveInstall(TestContext& context) { context.Override({ ExtractFilesFromArchive, [](TestContext&) @@ -1217,6 +1210,50 @@ TEST_CASE("PortableInstallFlow", "[InstallFlow][workflow]") REQUIRE(std::filesystem::exists(portableInstallResultPath.GetPath())); } +TEST_CASE("PortableInstallFlow_UserScope", "[InstallFlow][workflow]") +{ + TestCommon::TempDirectory tempDirectory("TestPortableInstallRoot", false); + TestCommon::TempFile portableInstallResultPath("TestPortableInstalled.txt"); + + std::ostringstream installOutput; + TestContext context{ installOutput, std::cin }; + auto previousThreadGlobals = context.SetForCurrentThread(); + OverrideForPortableInstallFlow(context); + context.Args.AddArg(Execution::Args::Type::Manifest, TestDataFile("InstallFlowTest_Portable.yaml").GetPath().u8string()); + context.Args.AddArg(Execution::Args::Type::InstallLocation, tempDirectory); + context.Args.AddArg(Execution::Args::Type::InstallScope, "user"sv); + + InstallCommand install({}); + install.Execute(context); + INFO(installOutput.str()); + + REQUIRE(std::filesystem::exists(portableInstallResultPath.GetPath())); +} + +TEST_CASE("PortableInstallFlow_MachineScope", "[InstallFlow][workflow]") +{ + if (!AppInstaller::Runtime::IsRunningAsAdmin()) + { + WARN("Test requires admin privilege. Skipped."); + return; + } + + TestCommon::TempDirectory tempDirectory("TestPortableInstallRoot", false); + TestCommon::TempFile portableInstallResultPath("TestPortableInstalled.txt"); + + std::ostringstream installOutput; + TestContext context{ installOutput, std::cin }; + auto previousThreadGlobals = context.SetForCurrentThread(); + OverrideForPortableInstallFlow(context); + context.Args.AddArg(Execution::Args::Type::Manifest, TestDataFile("InstallFlowTest_Portable.yaml").GetPath().u8string()); + context.Args.AddArg(Execution::Args::Type::InstallLocation, tempDirectory); + context.Args.AddArg(Execution::Args::Type::InstallScope, "machine"sv); + + InstallCommand install({}); + install.Execute(context); + INFO(installOutput.str()); + REQUIRE(std::filesystem::exists(portableInstallResultPath.GetPath())); +} TEST_CASE("PortableInstallFlow_DevModeDisabled", "[InstallFlow][workflow]") { @@ -1863,7 +1900,6 @@ TEST_CASE("UpdateFlow_UpdatePortableWithManifest", "[UpdateFlow][workflow]") TestContext context{ updateOutput, std::cin }; auto previousThreadGlobals = context.SetForCurrentThread(); OverrideForCompositeInstalledSource(context); - OverrideForEnsureSupportForPortable(context); OverrideForPortableInstallFlow(context); context.Args.AddArg(Execution::Args::Type::Manifest, TestDataFile("UpdateFlowTest_Portable.yaml").GetPath().u8string()); diff --git a/src/AppInstallerCommonCore/Manifest/Manifest.cpp b/src/AppInstallerCommonCore/Manifest/Manifest.cpp @@ -94,7 +94,7 @@ namespace AppInstaller::Manifest for (auto const& installer : Installers) { - if (DoesInstallerTypeSupportArpVersionRange(installer.InstallerType)) + if (DoesInstallerSupportArpVersionRange(installer)) { for (auto const& entry : installer.AppsAndFeaturesEntries) { diff --git a/src/AppInstallerCommonCore/Manifest/ManifestCommon.cpp b/src/AppInstallerCommonCore/Manifest/ManifestCommon.cpp @@ -35,6 +35,17 @@ namespace AppInstaller::Manifest return CompatibilitySet::None; } } + + InstallerTypeEnum GetInstallerTypeFromInstaller(ManifestInstaller installer) + { + InstallerTypeEnum installerType = installer.InstallerType; + if (IsArchiveType(installerType)) + { + installerType = installer.NestedInstallerType; + } + + return installerType; + } } ManifestVer::ManifestVer(std::string_view version) @@ -438,13 +449,15 @@ namespace AppInstaller::Manifest return "Unknown"sv; } - bool DoesInstallerTypeUsePackageFamilyName(InstallerTypeEnum installerType) + bool DoesInstallerUsePackageFamilyName(ManifestInstaller installer) { + InstallerTypeEnum installerType = GetInstallerTypeFromInstaller(installer); return (installerType == InstallerTypeEnum::Msix || installerType == InstallerTypeEnum::MSStore); } - bool DoesInstallerTypeUseProductCode(InstallerTypeEnum installerType) + bool DoesInstallerUseProductCode(ManifestInstaller installer) { + InstallerTypeEnum installerType = GetInstallerTypeFromInstaller(installer); return ( installerType == InstallerTypeEnum::Exe || installerType == InstallerTypeEnum::Inno || @@ -456,8 +469,9 @@ namespace AppInstaller::Manifest ); } - bool DoesInstallerTypeWriteAppsAndFeaturesEntry(InstallerTypeEnum installerType) + bool DoesInstallerWriteAppsAndFeaturesEntry(ManifestInstaller installer) { + InstallerTypeEnum installerType = GetInstallerTypeFromInstaller(installer); return ( installerType == InstallerTypeEnum::Exe || installerType == InstallerTypeEnum::Inno || @@ -469,6 +483,12 @@ namespace AppInstaller::Manifest ); } + bool DoesInstallerSupportArpVersionRange(ManifestInstaller installer) + { + InstallerTypeEnum installerType = GetInstallerTypeFromInstaller(installer); + return DoesInstallerTypeSupportArpVersionRange(installerType); + } + bool DoesInstallerTypeSupportArpVersionRange(InstallerTypeEnum installerType) { return ( @@ -481,6 +501,14 @@ namespace AppInstaller::Manifest ); } + bool DoesInstallerIgnoreScopeFromManifest(ManifestInstaller installer) + { + InstallerTypeEnum installerType = GetInstallerTypeFromInstaller(installer); + return ( + installerType == InstallerTypeEnum::Portable + ); + } + bool IsArchiveType(InstallerTypeEnum installerType) { return (installerType == InstallerTypeEnum::Zip); diff --git a/src/AppInstallerCommonCore/Manifest/ManifestValidation.cpp b/src/AppInstallerCommonCore/Manifest/ManifestValidation.cpp @@ -105,17 +105,17 @@ namespace AppInstaller::Manifest // Validate system reference strings if they are set at the installer level // Allow PackageFamilyName to be declared with non msix installers to support nested installer scenarios after manifest version 1.1 - if (manifest.ManifestVersion <= ManifestVer{ s_ManifestVersionV1_1 } && !installer.PackageFamilyName.empty() && !DoesInstallerTypeUsePackageFamilyName(installer.InstallerType)) + if (manifest.ManifestVersion <= ManifestVer{ s_ManifestVersionV1_1 } && !installer.PackageFamilyName.empty() && !DoesInstallerUsePackageFamilyName(installer)) { resultErrors.emplace_back(ManifestError::InstallerTypeDoesNotSupportPackageFamilyName, "InstallerType", InstallerTypeToString(installer.InstallerType)); } - if (!installer.ProductCode.empty() && !DoesInstallerTypeUseProductCode(installer.InstallerType)) + if (!installer.ProductCode.empty() && !DoesInstallerUseProductCode(installer)) { resultErrors.emplace_back(ManifestError::InstallerTypeDoesNotSupportProductCode, "InstallerType", InstallerTypeToString(installer.InstallerType)); } - if (!installer.AppsAndFeaturesEntries.empty() && !DoesInstallerTypeWriteAppsAndFeaturesEntry(installer.InstallerType)) + if (!installer.AppsAndFeaturesEntries.empty() && !DoesInstallerWriteAppsAndFeaturesEntry(installer)) { resultErrors.emplace_back(ManifestError::InstallerTypeDoesNotWriteAppsAndFeaturesEntry, "InstallerType", InstallerTypeToString(installer.InstallerType)); } diff --git a/src/AppInstallerCommonCore/Manifest/ManifestYamlPopulator.cpp b/src/AppInstallerCommonCore/Manifest/ManifestYamlPopulator.cpp @@ -922,17 +922,17 @@ namespace AppInstaller::Manifest std::move(errors.begin(), errors.end(), std::inserter(resultErrors, resultErrors.end())); // Copy in system reference strings from the root if not set in the installer and appropriate - if (installer.PackageFamilyName.empty() && DoesInstallerTypeUsePackageFamilyName(installer.InstallerType)) + if (installer.PackageFamilyName.empty() && DoesInstallerUsePackageFamilyName(installer)) { installer.PackageFamilyName = manifest.DefaultInstallerInfo.PackageFamilyName; } - if (installer.ProductCode.empty() && DoesInstallerTypeUseProductCode(installer.InstallerType)) + if (installer.ProductCode.empty() && DoesInstallerUseProductCode(installer)) { installer.ProductCode = manifest.DefaultInstallerInfo.ProductCode; } - if (installer.AppsAndFeaturesEntries.empty() && DoesInstallerTypeWriteAppsAndFeaturesEntry(installer.InstallerType)) + if (installer.AppsAndFeaturesEntries.empty() && DoesInstallerWriteAppsAndFeaturesEntry(installer)) { installer.AppsAndFeaturesEntries = manifest.DefaultInstallerInfo.AppsAndFeaturesEntries; } diff --git a/src/AppInstallerCommonCore/Public/winget/ManifestCommon.h b/src/AppInstallerCommonCore/Public/winget/ManifestCommon.h @@ -10,6 +10,9 @@ namespace AppInstaller::Manifest { + // Forward declaration + struct ManifestInstaller; + using string_t = Utility::NormalizedString; using namespace std::string_view_literals; @@ -299,17 +302,23 @@ namespace AppInstaller::Manifest std::string_view ScopeToString(ScopeEnum scope); - // Gets a value indicating whether the given installer type uses the PackageFamilyName system reference. - bool DoesInstallerTypeUsePackageFamilyName(InstallerTypeEnum installerType); + // Gets a value indicating whether the given installer uses the PackageFamilyName system reference. + bool DoesInstallerUsePackageFamilyName(ManifestInstaller installer); + + // Gets a value indicating whether the given installer uses the ProductCode system reference. + bool DoesInstallerUseProductCode(ManifestInstaller installer); - // Gets a value indicating whether the given installer type uses the ProductCode system reference. - bool DoesInstallerTypeUseProductCode(InstallerTypeEnum installerType); + // Gets a value indicating whether the given installer writes ARP entry. + bool DoesInstallerWriteAppsAndFeaturesEntry(ManifestInstaller installer); - // Gets a value indicating whether the given installer type writes ARP entry. - bool DoesInstallerTypeWriteAppsAndFeaturesEntry(InstallerTypeEnum installerType); + // Gets a value indicating whether the given installer supports ARP version range. + bool DoesInstallerSupportArpVersionRange(ManifestInstaller installer); // Gets a value indicating whether the given installer type supports ARP version range. - bool DoesInstallerTypeSupportArpVersionRange(InstallerTypeEnum installerType); + bool DoesInstallerTypeSupportArpVersionRange(InstallerTypeEnum installer); + + // Gets a value indicating whether the given installer ignores the Scope value from the manifest. + bool DoesInstallerIgnoreScopeFromManifest(ManifestInstaller installer); // Gets a value indicating whether the given installer type is an archive. bool IsArchiveType(InstallerTypeEnum installerType); diff --git a/src/AppInstallerCommonCore/Public/winget/UserSettings.h b/src/AppInstallerCommonCore/Public/winget/UserSettings.h @@ -88,8 +88,8 @@ namespace AppInstaller::Settings LoggingLevelPreference, InstallIgnoreWarnings, DisableInstallNotes, - PortableAppUserRoot, - PortableAppMachineRoot, + PortablePackageUserRoot, + PortablePackageMachineRoot, UninstallPurgePortablePackage, Max }; @@ -141,8 +141,8 @@ namespace AppInstaller::Settings SETTINGMAPPING_SPECIALIZATION(Setting::InstallLocaleRequirement, std::vector<std::string>, std::vector<std::string>, {}, ".installBehavior.requirements.locale"sv); SETTINGMAPPING_SPECIALIZATION(Setting::InstallIgnoreWarnings, bool, bool, false, ".installBehavior.ignoreWarnings"sv); SETTINGMAPPING_SPECIALIZATION(Setting::DisableInstallNotes, bool, bool, false, ".installBehavior.disableInstallNotes"sv); - SETTINGMAPPING_SPECIALIZATION(Setting::PortableAppUserRoot, std::string, std::filesystem::path, {}, ".installBehavior.portableAppUserRoot"sv); - SETTINGMAPPING_SPECIALIZATION(Setting::PortableAppMachineRoot, std::string, std::filesystem::path, {}, ".installBehavior.portableAppMachineRoot"sv); + SETTINGMAPPING_SPECIALIZATION(Setting::PortablePackageUserRoot, std::string, std::filesystem::path, {}, ".installBehavior.portablePackageUserRoot"sv); + SETTINGMAPPING_SPECIALIZATION(Setting::PortablePackageMachineRoot, std::string, std::filesystem::path, {}, ".installBehavior.portablePackageMachineRoot"sv); SETTINGMAPPING_SPECIALIZATION(Setting::UninstallPurgePortablePackage, bool, bool, false, ".uninstallBehavior.purgePortablePackage"sv); SETTINGMAPPING_SPECIALIZATION(Setting::EFDirectMSI, bool, bool, false, ".experimentalFeatures.directMSI"sv); SETTINGMAPPING_SPECIALIZATION(Setting::EnableSelfInitiatedMinidump, bool, bool, false, ".debugging.enableSelfInitiatedMinidump"sv); diff --git a/src/AppInstallerCommonCore/Runtime.cpp b/src/AppInstallerCommonCore/Runtime.cpp @@ -444,7 +444,7 @@ namespace AppInstaller::Runtime result.Create = false; break; case PathName::PortablePackageUserRoot: - result.Path = Settings::User().Get<Setting::PortableAppUserRoot>(); + result.Path = Settings::User().Get<Setting::PortablePackageUserRoot>(); if (result.Path.empty()) { result.Path = GetKnownFolderPath(FOLDERID_LocalAppData); @@ -454,7 +454,7 @@ namespace AppInstaller::Runtime } break; case PathName::PortablePackageMachineRootX64: - result.Path = Settings::User().Get<Setting::PortableAppMachineRoot>(); + result.Path = Settings::User().Get<Setting::PortablePackageMachineRoot>(); if (result.Path.empty()) { result.Path = GetKnownFolderPath(FOLDERID_ProgramFilesX64); @@ -463,7 +463,7 @@ namespace AppInstaller::Runtime } break; case PathName::PortablePackageMachineRootX86: - result.Path = Settings::User().Get<Setting::PortableAppMachineRoot>(); + result.Path = Settings::User().Get<Setting::PortablePackageMachineRoot>(); if (result.Path.empty()) { result.Path = GetKnownFolderPath(FOLDERID_ProgramFilesX86); @@ -478,7 +478,7 @@ namespace AppInstaller::Runtime result.Path /= s_LinksDirectory; break; case PathName::PortableLinksMachineLocation: - result.Path = GetKnownFolderPath(FOLDERID_ProgramFilesX64); + result.Path = GetKnownFolderPath(FOLDERID_ProgramFiles); result.Path /= s_PortablePackageRoot; result.Path /= s_LinksDirectory; break; diff --git a/src/AppInstallerCommonCore/UserSettings.cpp b/src/AppInstallerCommonCore/UserSettings.cpp @@ -243,7 +243,7 @@ namespace AppInstaller::Settings WINGET_VALIDATE_PASS_THROUGH(DisableInstallNotes) WINGET_VALIDATE_PASS_THROUGH(UninstallPurgePortablePackage) - WINGET_VALIDATE_SIGNATURE(PortableAppUserRoot) + WINGET_VALIDATE_SIGNATURE(PortablePackageUserRoot) { std::filesystem::path root = ConvertToUTF16(value); if (!root.is_absolute()) @@ -254,9 +254,9 @@ namespace AppInstaller::Settings return root; } - WINGET_VALIDATE_SIGNATURE(PortableAppMachineRoot) + WINGET_VALIDATE_SIGNATURE(PortablePackageMachineRoot) { - return SettingMapping<Setting::PortableAppUserRoot>::Validate(value); + return SettingMapping<Setting::PortablePackageUserRoot>::Validate(value); } WINGET_VALIDATE_SIGNATURE(InstallArchitecturePreference)