diff --git a/src/AppInstallerCLI.sln b/src/AppInstallerCLI.sln index 1d07c93cfa..7a3f7f83e5 100644 --- a/src/AppInstallerCLI.sln +++ b/src/AppInstallerCLI.sln @@ -27,7 +27,6 @@ EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Project", "Project", "{8D53D749-D51C-46F8-A162-9371AAA6C2E7}" ProjectSection(SolutionItems) = preProject ..\azure-pipelines.loc.yml = ..\azure-pipelines.loc.yml - ..\azure-pipelines.nuget.in-proc-com.yml = ..\azure-pipelines.nuget.in-proc-com.yml ..\azure-pipelines.nuget.yml = ..\azure-pipelines.nuget.yml ..\azure-pipelines.yml = ..\azure-pipelines.yml ..\cgmanifest.json = ..\cgmanifest.json diff --git a/src/AppInstallerCLICore/Argument.h b/src/AppInstallerCLICore/Argument.h index 748cc4a8b8..480a8b0d9d 100644 --- a/src/AppInstallerCLICore/Argument.h +++ b/src/AppInstallerCLICore/Argument.h @@ -7,6 +7,7 @@ #include #include #include +#include #include #include @@ -21,6 +22,9 @@ namespace AppInstaller::CLI { + using namespace AppInstaller::Utility::literals; + constexpr Utility::LocIndView s_ArgumentName_Scope = "scope"_liv; + // The type of argument. enum class ArgumentType { diff --git a/src/AppInstallerCLICore/Command.cpp b/src/AppInstallerCLICore/Command.cpp index 272c3bda15..ddb93f2778 100644 --- a/src/AppInstallerCLICore/Command.cpp +++ b/src/AppInstallerCLICore/Command.cpp @@ -699,6 +699,15 @@ namespace AppInstaller::CLI } } + if (execArgs.Contains(Execution::Args::Type::InstallScope)) + { + if (Manifest::ConvertToScopeEnum(execArgs.GetArg(Execution::Args::Type::InstallScope)) == Manifest::ScopeEnum::Unknown) + { + auto validOptions = Utility::Join(", "_liv, std::vector{ "user"_lis, "machine"_lis}); + throw CommandException(Resource::String::InvalidArgumentValueError(s_ArgumentName_Scope, validOptions)); + } + } + ValidateArgumentsInternal(execArgs); } diff --git a/src/AppInstallerCLICore/Commands/InstallCommand.cpp b/src/AppInstallerCLICore/Commands/InstallCommand.cpp index 51b38cdfec..c7bfcf0a6f 100644 --- a/src/AppInstallerCLICore/Commands/InstallCommand.cpp +++ b/src/AppInstallerCLICore/Commands/InstallCommand.cpp @@ -15,11 +15,6 @@ using namespace AppInstaller::Utility::literals; namespace AppInstaller::CLI { - namespace - { - constexpr Utility::LocIndView s_ArgumentName_Scope = "scope"_liv; - } - std::vector InstallCommand::GetArguments() const { return { @@ -113,14 +108,6 @@ namespace AppInstaller::CLI throw CommandException(Resource::String::BothManifestAndSearchQueryProvided); } - if (execArgs.Contains(Args::Type::InstallScope)) - { - if (ConvertToScopeEnum(execArgs.GetArg(Args::Type::InstallScope)) == Manifest::ScopeEnum::Unknown) - { - auto validOptions = Utility::Join(", "_liv, std::vector{ "user"_lis, "machine"_lis}); - throw CommandException(Resource::String::InvalidArgumentValueError(s_ArgumentName_Scope, validOptions)); - } - } } void InstallCommand::ExecuteInternal(Context& context) const diff --git a/src/AppInstallerCLICore/Commands/ListCommand.cpp b/src/AppInstallerCLICore/Commands/ListCommand.cpp index d8e9397f26..3828aa44e9 100644 --- a/src/AppInstallerCLICore/Commands/ListCommand.cpp +++ b/src/AppInstallerCLICore/Commands/ListCommand.cpp @@ -22,6 +22,7 @@ namespace AppInstaller::CLI Argument::ForType(Execution::Args::Type::Command), Argument::ForType(Execution::Args::Type::Count), Argument::ForType(Execution::Args::Type::Exact), + Argument{ s_ArgumentName_Scope, Argument::NoAlias, Execution::Args::Type::InstallScope, Resource::String::InstalledScopeArgumentDescription, ArgumentType::Standard, Argument::Visibility::Help }, Argument::ForType(Execution::Args::Type::CustomHeader), Argument::ForType(Execution::Args::Type::AcceptSourceAgreements), }; @@ -74,7 +75,7 @@ namespace AppInstaller::CLI context << Workflow::OpenSource() << - Workflow::OpenCompositeSource(Repository::PredefinedSource::Installed) << + Workflow::OpenCompositeSource(Workflow::DetermineInstalledSource(context)) << Workflow::SearchSourceForMany << Workflow::HandleSearchResultFailures << Workflow::EnsureMatchesFromSearchResult(true) << diff --git a/src/AppInstallerCLICore/Commands/ShowCommand.cpp b/src/AppInstallerCLICore/Commands/ShowCommand.cpp index 7f8c4a042d..c2ae41d3ed 100644 --- a/src/AppInstallerCLICore/Commands/ShowCommand.cpp +++ b/src/AppInstallerCLICore/Commands/ShowCommand.cpp @@ -24,6 +24,7 @@ namespace AppInstaller::CLI Argument::ForType(Execution::Args::Type::Channel), Argument::ForType(Execution::Args::Type::Source), Argument::ForType(Execution::Args::Type::Exact), + Argument{ s_ArgumentName_Scope, Argument::NoAlias, Args::Type::InstallScope, Resource::String::InstallScopeDescription, ArgumentType::Standard, Argument::Visibility::Help }, Argument::ForType(Execution::Args::Type::InstallArchitecture), Argument::ForType(Execution::Args::Type::Locale), Argument::ForType(Execution::Args::Type::ListVersions), diff --git a/src/AppInstallerCLICore/Commands/UninstallCommand.cpp b/src/AppInstallerCLICore/Commands/UninstallCommand.cpp index 098fe8fca8..dc27ed19f9 100644 --- a/src/AppInstallerCLICore/Commands/UninstallCommand.cpp +++ b/src/AppInstallerCLICore/Commands/UninstallCommand.cpp @@ -26,6 +26,7 @@ namespace AppInstaller::CLI Argument::ForType(Args::Type::Channel), Argument::ForType(Args::Type::Source), Argument::ForType(Args::Type::Exact), + Argument{ s_ArgumentName_Scope, Argument::NoAlias, Execution::Args::Type::InstallScope, Resource::String::InstalledScopeArgumentDescription, ArgumentType::Standard, Argument::Visibility::Help }, Argument::ForType(Args::Type::Interactive), Argument::ForType(Args::Type::Silent), Argument::ForType(Args::Type::Force), @@ -118,7 +119,7 @@ namespace AppInstaller::CLI context << Workflow::ReportExecutionStage(ExecutionStage::Discovery) << Workflow::OpenSource() << - Workflow::OpenCompositeSource(Repository::PredefinedSource::Installed); + Workflow::OpenCompositeSource(Workflow::DetermineInstalledSource(context)); // find the uninstaller if (context.Args.Contains(Execution::Args::Type::Manifest)) diff --git a/src/AppInstallerCLICore/Commands/UpgradeCommand.cpp b/src/AppInstallerCLICore/Commands/UpgradeCommand.cpp index 2e6e6e2c07..312c3ab248 100644 --- a/src/AppInstallerCLICore/Commands/UpgradeCommand.cpp +++ b/src/AppInstallerCLICore/Commands/UpgradeCommand.cpp @@ -100,6 +100,7 @@ namespace AppInstaller::CLI Argument::ForType(Args::Type::Log), // -o Argument::ForType(Args::Type::Override), Argument::ForType(Args::Type::InstallLocation), // -l + Argument{ s_ArgumentName_Scope, Argument::NoAlias, Execution::Args::Type::InstallScope, Resource::String::InstalledScopeArgumentDescription, ArgumentType::Standard, Argument::Visibility::Help }, Argument::ForType(Args::Type::InstallArchitecture), // -a Argument::ForType(Args::Type::Locale), Argument::ForType(Args::Type::HashOverride), @@ -212,7 +213,7 @@ namespace AppInstaller::CLI context << Workflow::ReportExecutionStage(ExecutionStage::Discovery) << Workflow::OpenSource() << - Workflow::OpenCompositeSource(Repository::PredefinedSource::Installed); + Workflow::OpenCompositeSource(Workflow::DetermineInstalledSource(context)); if (ShouldListUpgrade(context.Args)) { diff --git a/src/AppInstallerCLICore/Resources.h b/src/AppInstallerCLICore/Resources.h index 98e2ab0dfc..66204f4735 100644 --- a/src/AppInstallerCLICore/Resources.h +++ b/src/AppInstallerCLICore/Resources.h @@ -134,6 +134,7 @@ namespace AppInstaller::CLI::Resource WINGET_DEFINE_RESOURCE_STRINGID(InstallCommandShortDescription); WINGET_DEFINE_RESOURCE_STRINGID(InstalledPackageNotAvailable); WINGET_DEFINE_RESOURCE_STRINGID(InstalledPackageVersionNotAvailable); + WINGET_DEFINE_RESOURCE_STRINGID(InstalledScopeArgumentDescription); WINGET_DEFINE_RESOURCE_STRINGID(InstallerAbortsTerminal); WINGET_DEFINE_RESOURCE_STRINGID(InstallerElevationExpected); WINGET_DEFINE_RESOURCE_STRINGID(InstallerBlockedByPolicy); diff --git a/src/AppInstallerCLICore/Workflows/InstallFlow.cpp b/src/AppInstallerCLICore/Workflows/InstallFlow.cpp index b73a92e489..c1c4f73882 100644 --- a/src/AppInstallerCLICore/Workflows/InstallFlow.cpp +++ b/src/AppInstallerCLICore/Workflows/InstallFlow.cpp @@ -14,6 +14,7 @@ #include "WorkflowBase.h" #include "DependenciesFlow.h" #include "PromptFlow.h" +#include #include #include #include @@ -296,6 +297,21 @@ namespace AppInstaller::CLI::Workflow } } + void EnsureRunningAsAdminForMachineScopeInstall(Execution::Context& context) + { + // Admin is required for machine scope install for installer types like portable, msix and msstore. + auto installerType = context.Get().value().EffectiveInstallerType(); + + if (Manifest::DoesInstallerTypeRequireAdminForMachineScopeInstall(installerType)) + { + Manifest::ScopeEnum scope = ConvertToScopeEnum(context.Args.GetArg(Execution::Args::Type::InstallScope)); + if (scope == Manifest::ScopeEnum::Machine) + { + context << Workflow::EnsureRunningAsAdmin; + } + } + } + void ExecuteInstaller(Execution::Context& context) { context << Workflow::ExecuteInstallerForType(context.Get().value().BaseInstallerType); @@ -354,8 +370,15 @@ namespace AppInstaller::CLI::Workflow try { registrationDeferred = context.Reporter.ExecuteWithProgress([&](IProgressCallback& callback) - { - return Deployment::AddPackageWithDeferredFallback(uri, WI_IsFlagSet(context.GetFlags(), Execution::ContextFlag::InstallerTrusted), callback); + { + if (Manifest::ConvertToScopeEnum(context.Args.GetArg(Execution::Args::Type::InstallScope)) == Manifest::ScopeEnum::Machine) + { + return Deployment::AddPackageMachineScope(uri, callback); + } + else + { + return Deployment::AddPackageWithDeferredFallback(uri, WI_IsFlagSet(context.GetFlags(), Execution::ContextFlag::InstallerTrusted), callback); + } }); } catch (const wil::ResultException& re) @@ -472,6 +495,7 @@ namespace AppInstaller::CLI::Workflow void EnsureSupportForInstall(Execution::Context& context) { context << + Workflow::EnsureRunningAsAdminForMachineScopeInstall << Workflow::EnsureSupportForPortableInstall << Workflow::EnsureValidNestedInstallerMetadataForArchiveInstall; } diff --git a/src/AppInstallerCLICore/Workflows/InstallFlow.h b/src/AppInstallerCLICore/Workflows/InstallFlow.h index 48da383502..a613a934b2 100644 --- a/src/AppInstallerCLICore/Workflows/InstallFlow.h +++ b/src/AppInstallerCLICore/Workflows/InstallFlow.h @@ -35,6 +35,12 @@ namespace AppInstaller::CLI::Workflow // Outputs: None void CheckForUnsupportedArgs(Execution::Context& context); + // Admin is required for machine scope install for installer types like portable, msix and msstore. + // Required Args: None + // Inputs: Installer + // Outputs: None + void EnsureRunningAsAdminForMachineScopeInstall(Execution::Context& context); + // Composite flow that chooses what to do based on the installer type. // Required Args: None // Inputs: Installer, InstallerPath diff --git a/src/AppInstallerCLICore/Workflows/MSStoreInstallerHandler.cpp b/src/AppInstallerCLICore/Workflows/MSStoreInstallerHandler.cpp index 0f8f2100bd..78f8156410 100644 --- a/src/AppInstallerCLICore/Workflows/MSStoreInstallerHandler.cpp +++ b/src/AppInstallerCLICore/Workflows/MSStoreInstallerHandler.cpp @@ -1,6 +1,7 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. #include "pch.h" +#include #include "MSStoreInstallerHandler.h" namespace AppInstaller::CLI::Workflow @@ -140,6 +141,11 @@ namespace AppInstaller::CLI::Workflow installOptions.CompletedInstallToastNotificationMode(AppInstallationToastNotificationMode::NoToast); } + if (Manifest::ConvertToScopeEnum(context.Args.GetArg(Execution::Args::Type::InstallScope)) == Manifest::ScopeEnum::Machine) + { + installOptions.InstallForAllUsers(true); + } + IVectorView installItems = installManager.StartProductInstallAsync( productId, // ProductId winrt::hstring(), // FlightId diff --git a/src/AppInstallerCLICore/Workflows/PortableFlow.cpp b/src/AppInstallerCLICore/Workflows/PortableFlow.cpp index 9967b61006..53ede4e71a 100644 --- a/src/AppInstallerCLICore/Workflows/PortableFlow.cpp +++ b/src/AppInstallerCLICore/Workflows/PortableFlow.cpp @@ -68,16 +68,6 @@ namespace AppInstaller::CLI::Workflow AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_PORTABLE_REPARSE_POINT_NOT_SUPPORTED); } } - - 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 VerifyPackageAndSourceMatch(Execution::Context& context) @@ -334,23 +324,8 @@ namespace AppInstaller::CLI::Workflow if (installerType == InstallerTypeEnum::Portable) { context << - EnsureRunningAsAdminForMachineScopeInstall << EnsureValidArgsForPortableInstall << EnsureVolumeSupportsReparsePoints; } } - - void EnsureSupportForPortableUninstall(Execution::Context& context) - { - auto installedPackageVersion = context.Get(); - const std::string installedTypeString = installedPackageVersion->GetMetadata()[PackageVersionMetadata::InstalledType]; - if (ConvertToInstallerTypeEnum(installedTypeString) == InstallerTypeEnum::Portable) - { - const std::string installedScope = installedPackageVersion->GetMetadata()[Repository::PackageVersionMetadata::InstalledScope]; - if (ConvertToScopeEnum(installedScope) == Manifest::ScopeEnum::Machine) - { - context << EnsureRunningAsAdmin; - } - } - } } \ No newline at end of file diff --git a/src/AppInstallerCLICore/Workflows/PortableFlow.h b/src/AppInstallerCLICore/Workflows/PortableFlow.h index 4b1c1032ca..cb7ae19d06 100644 --- a/src/AppInstallerCLICore/Workflows/PortableFlow.h +++ b/src/AppInstallerCLICore/Workflows/PortableFlow.h @@ -23,12 +23,6 @@ namespace AppInstaller::CLI::Workflow // Outputs: None void EnsureSupportForPortableInstall(Execution::Context& context); - // Verifies that the portable uninstall operation is supported. - // Required Args: None - // Inputs: Scope - // Outputs: None - void EnsureSupportForPortableUninstall(Execution::Context& context); - // Initializes the portable installer. // Required Args: None // Inputs: Scope, Architecture, Manifest, Installer diff --git a/src/AppInstallerCLICore/Workflows/UninstallFlow.cpp b/src/AppInstallerCLICore/Workflows/UninstallFlow.cpp index 832e354e7d..f049a20094 100644 --- a/src/AppInstallerCLICore/Workflows/UninstallFlow.cpp +++ b/src/AppInstallerCLICore/Workflows/UninstallFlow.cpp @@ -58,7 +58,7 @@ namespace AppInstaller::CLI::Workflow { context << Workflow::GetInstalledPackageVersion << - Workflow::EnsureSupportForPortableUninstall << + Workflow::EnsureSupportForUninstall << Workflow::GetUninstallInfo << Workflow::GetDependenciesInfoForUninstall << Workflow::ReportDependencies(Resource::String::UninstallCommandReportDependencies) << @@ -203,7 +203,14 @@ namespace AppInstaller::CLI::Workflow AICLI_LOG(CLI, Info, << "Removing MSIX package: " << packageFullName.value()); try { - context.Reporter.ExecuteWithProgress(std::bind(Deployment::RemovePackage, packageFullName.value(), std::placeholders::_1)); + if (Manifest::ConvertToScopeEnum(context.Args.GetArg(Execution::Args::Type::InstallScope)) == Manifest::ScopeEnum::Machine) + { + context.Reporter.ExecuteWithProgress(std::bind(Deployment::RemovePackageMachineScope, packageFamilyName, packageFullName.value(), std::placeholders::_1)); + } + else + { + context.Reporter.ExecuteWithProgress(std::bind(Deployment::RemovePackage, packageFullName.value(), winrt::Windows::Management::Deployment::RemovalOptions::None, std::placeholders::_1)); + } } catch (const wil::ResultException& re) { @@ -278,4 +285,26 @@ namespace AppInstaller::CLI::Workflow context.Reporter.Info() << Resource::String::UninstallFlowUninstallSuccess << std::endl; } } + + void EnsureSupportForUninstall(Execution::Context& context) + { + auto installedPackageVersion = context.Get(); + const std::string installedTypeString = installedPackageVersion->GetMetadata()[PackageVersionMetadata::InstalledType]; + auto installedType = ConvertToInstallerTypeEnum(installedTypeString); + if (installedType == InstallerTypeEnum::Portable) + { + const std::string installedScope = installedPackageVersion->GetMetadata()[Repository::PackageVersionMetadata::InstalledScope]; + if (Manifest::ConvertToScopeEnum(installedScope) == Manifest::ScopeEnum::Machine) + { + context << EnsureRunningAsAdmin; + } + } + else if (installedType == InstallerTypeEnum::Msix) + { + if (Manifest::ConvertToScopeEnum(context.Args.GetArg(Execution::Args::Type::InstallScope)) == Manifest::ScopeEnum::Machine) + { + context << EnsureRunningAsAdmin; + } + } + } } diff --git a/src/AppInstallerCLICore/Workflows/UninstallFlow.h b/src/AppInstallerCLICore/Workflows/UninstallFlow.h index 60f197bf70..03a17e8d0e 100644 --- a/src/AppInstallerCLICore/Workflows/UninstallFlow.h +++ b/src/AppInstallerCLICore/Workflows/UninstallFlow.h @@ -58,4 +58,10 @@ namespace AppInstaller::CLI::Workflow // Whether the Uninstaller result is an HRESULT. This guides how we show it. bool m_isHResult; }; + + // Verifies that the uninstall operation is supported. + // Required Args: None + // Inputs: InstalledPackageVersion + // Outputs: None + void EnsureSupportForUninstall(Execution::Context& context); } diff --git a/src/AppInstallerCLICore/Workflows/WorkflowBase.cpp b/src/AppInstallerCLICore/Workflows/WorkflowBase.cpp index cb3db051ce..92f93c108d 100644 --- a/src/AppInstallerCLICore/Workflows/WorkflowBase.cpp +++ b/src/AppInstallerCLICore/Workflows/WorkflowBase.cpp @@ -231,6 +231,22 @@ namespace AppInstaller::CLI::Workflow m_func(context); } + Repository::PredefinedSource DetermineInstalledSource(const Execution::Context& context) + { + Repository::PredefinedSource installedSource = Repository::PredefinedSource::Installed; + Manifest::ScopeEnum scope = Manifest::ConvertToScopeEnum(context.Args.GetArg(Execution::Args::Type::InstallScope)); + if (scope == Manifest::ScopeEnum::Machine) + { + installedSource = Repository::PredefinedSource::InstalledMachine; + } + else if (scope == Manifest::ScopeEnum::User) + { + installedSource = Repository::PredefinedSource::InstalledUser; + } + + return installedSource; + } + HRESULT HandleException(Execution::Context& context, std::exception_ptr exception) { try diff --git a/src/AppInstallerCLICore/Workflows/WorkflowBase.h b/src/AppInstallerCLICore/Workflows/WorkflowBase.h index 7316022ebf..0c7f9f2d5b 100644 --- a/src/AppInstallerCLICore/Workflows/WorkflowBase.h +++ b/src/AppInstallerCLICore/Workflows/WorkflowBase.h @@ -57,6 +57,9 @@ namespace AppInstaller::CLI::Workflow std::string m_name; }; + // Helper to determine installed source to use based on context input. + Repository::PredefinedSource DetermineInstalledSource(const Execution::Context& context); + // Helper to report exceptions and return the HRESULT. HRESULT HandleException(Execution::Context& context, std::exception_ptr exception); diff --git a/src/AppInstallerCLIE2ETests/InstallCommand.cs b/src/AppInstallerCLIE2ETests/InstallCommand.cs index 9eb4099f49..1ab46f717a 100644 --- a/src/AppInstallerCLIE2ETests/InstallCommand.cs +++ b/src/AppInstallerCLIE2ETests/InstallCommand.cs @@ -153,19 +153,48 @@ public void InstallMSIX() Assert.True(TestCommon.VerifyTestMsixInstalledAndCleanup()); } + /// + /// Test install msix machine scope. + /// + [Test] + public void InstallMSIXMachineScope() + { + // TODO: Provision and Deprovision api not supported in build server. + Assert.Ignore(); + + var result = TestCommon.RunAICLICommand("install", $"TestMsixInstaller --scope machine"); + Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); + Assert.True(result.StdOut.Contains("Successfully installed")); + Assert.True(TestCommon.VerifyTestMsixInstalledAndCleanup(true)); + } + /// /// Test install msix with signature hash. /// [Test] public void InstallMSIXWithSignature() { - var installDir = TestCommon.GetRandomTestDir(); - var result = TestCommon.RunAICLICommand("install", $"TestMsixWithSignatureHash --silent -l {installDir}"); + var result = TestCommon.RunAICLICommand("install", $"TestMsixWithSignatureHash"); Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); Assert.True(result.StdOut.Contains("Successfully installed")); Assert.True(TestCommon.VerifyTestMsixInstalledAndCleanup()); } + /// + /// Test install msix with signature hash machine scope. + /// + [Test] + public void InstallMSIXWithSignatureMachineScope() + { + // TODO: Provision and Deprovision api not supported in build server. + Assert.Ignore(); + + var result = TestCommon.RunAICLICommand("install", $"TestMsixWithSignatureHash --scope machine"); + Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); + Assert.True(result.StdOut.Contains("Successfully installed")); + Assert.True(TestCommon.VerifyTestMsixInstalledAndCleanup(true)); + } + /// /// Test msix hash mismatch. /// diff --git a/src/AppInstallerCLIE2ETests/Interop/InstallInterop.cs b/src/AppInstallerCLIE2ETests/Interop/InstallInterop.cs index 8b2fce99e4..cbb814e519 100644 --- a/src/AppInstallerCLIE2ETests/Interop/InstallInterop.cs +++ b/src/AppInstallerCLIE2ETests/Interop/InstallInterop.cs @@ -4,438 +4,486 @@ // // ----------------------------------------------------------------------------- -namespace AppInstallerCLIE2ETests.Interop -{ - using System; - using System.IO; - using System.Threading.Tasks; - using Microsoft.Management.Deployment; - using Microsoft.Management.Deployment.Projection; - using NUnit.Framework; - +namespace AppInstallerCLIE2ETests.Interop +{ + using System; + using System.IO; + using System.Threading.Tasks; + using Microsoft.Management.Deployment; + using Microsoft.Management.Deployment.Projection; + using NUnit.Framework; + /// /// Install interop. - /// - [TestFixtureSource(typeof(InstanceInitializersSource), nameof(InstanceInitializersSource.InProcess), Category = nameof(InstanceInitializersSource.InProcess))] - [TestFixtureSource(typeof(InstanceInitializersSource), nameof(InstanceInitializersSource.OutOfProcess), Category = nameof(InstanceInitializersSource.OutOfProcess))] - public class InstallInterop : BaseInterop - { - private string installDir; - private PackageManager packageManager; + /// + [TestFixtureSource(typeof(InstanceInitializersSource), nameof(InstanceInitializersSource.InProcess), Category = nameof(InstanceInitializersSource.InProcess))] + [TestFixtureSource(typeof(InstanceInitializersSource), nameof(InstanceInitializersSource.OutOfProcess), Category = nameof(InstanceInitializersSource.OutOfProcess))] + public class InstallInterop : BaseInterop + { + private string installDir; + private PackageManager packageManager; private PackageCatalogReference testSource; /// /// Initializes a new instance of the class. /// /// Initializer. - public InstallInterop(IInstanceInitializer initializer) - : base(initializer) - { - } - + public InstallInterop(IInstanceInitializer initializer) + : base(initializer) + { + } + /// /// Set up. - /// - [SetUp] - public void SetUp() - { - this.packageManager = this.TestFactory.CreatePackageManager(); - this.testSource = this.packageManager.GetPackageCatalogByName(Constants.TestSourceName); - this.installDir = TestCommon.GetRandomTestDir(); + /// + [SetUp] + public void SetUp() + { + this.packageManager = this.TestFactory.CreatePackageManager(); + this.testSource = this.packageManager.GetPackageCatalogByName(Constants.TestSourceName); + this.installDir = TestCommon.GetRandomTestDir(); } /// /// Install exe. /// - /// A representing the asynchronous unit test. - [Test] - public async Task InstallExe() - { - // Find package - var searchResult = this.FindOnePackage(this.testSource, PackageMatchField.Id, PackageFieldMatchOption.Equals, "AppInstallerTest.TestExeInstaller"); - - // Configure installation - var installOptions = this.TestFactory.CreateInstallOptions(); - installOptions.PackageInstallMode = PackageInstallMode.Silent; - installOptions.PreferredInstallLocation = this.installDir; - - // Install - var installResult = await this.packageManager.InstallPackageAsync(searchResult.CatalogPackage, installOptions); - - // Assert - Assert.AreEqual(InstallResultStatus.Ok, installResult.Status); - } - + /// A representing the asynchronous unit test. + [Test] + public async Task InstallExe() + { + // Find package + var searchResult = this.FindOnePackage(this.testSource, PackageMatchField.Id, PackageFieldMatchOption.Equals, "AppInstallerTest.TestExeInstaller"); + + // Configure installation + var installOptions = this.TestFactory.CreateInstallOptions(); + installOptions.PackageInstallMode = PackageInstallMode.Silent; + installOptions.PreferredInstallLocation = this.installDir; + + // Install + var installResult = await this.packageManager.InstallPackageAsync(searchResult.CatalogPackage, installOptions); + + // Assert + Assert.AreEqual(InstallResultStatus.Ok, installResult.Status); + } + /// /// Test install with inapplicable os version. /// - /// A representing the asynchronous unit test. - [Test] - public async Task InstallExeWithInsufficientMinOsVersion() - { - // Find package - var searchResult = this.FindOnePackage(this.testSource, PackageMatchField.Name, PackageFieldMatchOption.Equals, "InapplicableOsVersion"); - - // Configure installation - var installOptions = this.TestFactory.CreateInstallOptions(); - installOptions.PackageInstallMode = PackageInstallMode.Silent; - installOptions.PreferredInstallLocation = this.installDir; - - // Install - var installResult = await this.packageManager.InstallPackageAsync(searchResult.CatalogPackage, installOptions); - - // Assert - Assert.AreEqual(InstallResultStatus.NoApplicableInstallers, installResult.Status); - Assert.False(TestCommon.VerifyTestExeInstalledAndCleanup(this.installDir)); - } - + /// A representing the asynchronous unit test. + [Test] + public async Task InstallExeWithInsufficientMinOsVersion() + { + // Find package + var searchResult = this.FindOnePackage(this.testSource, PackageMatchField.Name, PackageFieldMatchOption.Equals, "InapplicableOsVersion"); + + // Configure installation + var installOptions = this.TestFactory.CreateInstallOptions(); + installOptions.PackageInstallMode = PackageInstallMode.Silent; + installOptions.PreferredInstallLocation = this.installDir; + + // Install + var installResult = await this.packageManager.InstallPackageAsync(searchResult.CatalogPackage, installOptions); + + // Assert + Assert.AreEqual(InstallResultStatus.NoApplicableInstallers, installResult.Status); + Assert.False(TestCommon.VerifyTestExeInstalledAndCleanup(this.installDir)); + } + /// /// Test install with hash mismatch. /// - /// A representing the asynchronous unit test. - [Test] - public async Task InstallExeWithHashMismatch() - { - // Find package - var searchResult = this.FindOnePackage(this.testSource, PackageMatchField.Name, PackageFieldMatchOption.Equals, "TestExeSha256Mismatch"); - - // Configure installation - var installOptions = this.TestFactory.CreateInstallOptions(); - installOptions.PackageInstallMode = PackageInstallMode.Silent; - installOptions.PreferredInstallLocation = this.installDir; - - // Install - var installResult = await this.packageManager.InstallPackageAsync(searchResult.CatalogPackage, installOptions); - - // Assert - Assert.AreEqual(InstallResultStatus.DownloadError, installResult.Status); - Assert.False(TestCommon.VerifyTestExeInstalledAndCleanup(this.installDir)); - } - + /// A representing the asynchronous unit test. + [Test] + public async Task InstallExeWithHashMismatch() + { + // Find package + var searchResult = this.FindOnePackage(this.testSource, PackageMatchField.Name, PackageFieldMatchOption.Equals, "TestExeSha256Mismatch"); + + // Configure installation + var installOptions = this.TestFactory.CreateInstallOptions(); + installOptions.PackageInstallMode = PackageInstallMode.Silent; + installOptions.PreferredInstallLocation = this.installDir; + + // Install + var installResult = await this.packageManager.InstallPackageAsync(searchResult.CatalogPackage, installOptions); + + // Assert + Assert.AreEqual(InstallResultStatus.DownloadError, installResult.Status); + Assert.False(TestCommon.VerifyTestExeInstalledAndCleanup(this.installDir)); + } + /// /// Test installing inno installer. /// - /// A representing the asynchronous unit test. - [Test] - public async Task InstallWithInno() - { - // Find package - var searchResult = this.FindOnePackage(this.testSource, PackageMatchField.Name, PackageFieldMatchOption.Equals, "TestInnoInstaller"); - - // Configure installation - var installOptions = this.TestFactory.CreateInstallOptions(); - installOptions.PackageInstallMode = PackageInstallMode.Silent; - installOptions.PreferredInstallLocation = this.installDir; - - // Install - var installResult = await this.packageManager.InstallPackageAsync(searchResult.CatalogPackage, installOptions); - - // Assert - Assert.AreEqual(InstallResultStatus.Ok, installResult.Status); - Assert.True(TestCommon.VerifyTestExeInstalledAndCleanup(this.installDir)); - } - + /// A representing the asynchronous unit test. + [Test] + public async Task InstallWithInno() + { + // Find package + var searchResult = this.FindOnePackage(this.testSource, PackageMatchField.Name, PackageFieldMatchOption.Equals, "TestInnoInstaller"); + + // Configure installation + var installOptions = this.TestFactory.CreateInstallOptions(); + installOptions.PackageInstallMode = PackageInstallMode.Silent; + installOptions.PreferredInstallLocation = this.installDir; + + // Install + var installResult = await this.packageManager.InstallPackageAsync(searchResult.CatalogPackage, installOptions); + + // Assert + Assert.AreEqual(InstallResultStatus.Ok, installResult.Status); + Assert.True(TestCommon.VerifyTestExeInstalledAndCleanup(this.installDir)); + } + /// /// Test installing burn installer. /// - /// A representing the asynchronous unit test. - [Test] - public async Task InstallBurn() - { - // Find package - var searchResult = this.FindOnePackage(this.testSource, PackageMatchField.Name, PackageFieldMatchOption.Equals, "TestBurnInstaller"); - - // Configure installation - var installOptions = this.TestFactory.CreateInstallOptions(); - installOptions.PackageInstallMode = PackageInstallMode.Silent; - installOptions.PreferredInstallLocation = this.installDir; - - // Install - var installResult = await this.packageManager.InstallPackageAsync(searchResult.CatalogPackage, installOptions); - - // Assert - Assert.AreEqual(InstallResultStatus.Ok, installResult.Status); - Assert.True(TestCommon.VerifyTestExeInstalledAndCleanup(this.installDir)); - } - + /// A representing the asynchronous unit test. + [Test] + public async Task InstallBurn() + { + // Find package + var searchResult = this.FindOnePackage(this.testSource, PackageMatchField.Name, PackageFieldMatchOption.Equals, "TestBurnInstaller"); + + // Configure installation + var installOptions = this.TestFactory.CreateInstallOptions(); + installOptions.PackageInstallMode = PackageInstallMode.Silent; + installOptions.PreferredInstallLocation = this.installDir; + + // Install + var installResult = await this.packageManager.InstallPackageAsync(searchResult.CatalogPackage, installOptions); + + // Assert + Assert.AreEqual(InstallResultStatus.Ok, installResult.Status); + Assert.True(TestCommon.VerifyTestExeInstalledAndCleanup(this.installDir)); + } + /// /// Test installing nullsoft installer. /// - /// A representing the asynchronous unit test. - [Test] - public async Task InstallNullSoft() - { - // Find package - var searchResult = this.FindOnePackage(this.testSource, PackageMatchField.Name, PackageFieldMatchOption.Equals, "TestNullsoftInstaller"); - - // Configure installation - var installOptions = this.TestFactory.CreateInstallOptions(); - installOptions.PackageInstallMode = PackageInstallMode.Silent; - installOptions.PreferredInstallLocation = this.installDir; - - // Install - var installResult = await this.packageManager.InstallPackageAsync(searchResult.CatalogPackage, installOptions); - - // Assert - Assert.AreEqual(InstallResultStatus.Ok, installResult.Status); - Assert.True(TestCommon.VerifyTestExeInstalledAndCleanup(this.installDir)); - } - + /// A representing the asynchronous unit test. + [Test] + public async Task InstallNullSoft() + { + // Find package + var searchResult = this.FindOnePackage(this.testSource, PackageMatchField.Name, PackageFieldMatchOption.Equals, "TestNullsoftInstaller"); + + // Configure installation + var installOptions = this.TestFactory.CreateInstallOptions(); + installOptions.PackageInstallMode = PackageInstallMode.Silent; + installOptions.PreferredInstallLocation = this.installDir; + + // Install + var installResult = await this.packageManager.InstallPackageAsync(searchResult.CatalogPackage, installOptions); + + // Assert + Assert.AreEqual(InstallResultStatus.Ok, installResult.Status); + Assert.True(TestCommon.VerifyTestExeInstalledAndCleanup(this.installDir)); + } + /// /// Test installing msi. /// - /// A representing the asynchronous unit test. - [Test] - public async Task InstallMSI() - { - if (string.IsNullOrEmpty(TestCommon.MsiInstallerPath)) - { - Assert.Ignore("MSI installer not available"); - } - - // Find package - var searchResult = this.FindOnePackage(this.testSource, PackageMatchField.Name, PackageFieldMatchOption.Equals, "TestMsiInstaller"); - - // Configure installation - var installOptions = this.TestFactory.CreateInstallOptions(); - installOptions.PackageInstallMode = PackageInstallMode.Silent; - installOptions.PreferredInstallLocation = this.installDir; - - // Install - var installResult = await this.packageManager.InstallPackageAsync(searchResult.CatalogPackage, installOptions); - - // Assert - Assert.AreEqual(InstallResultStatus.Ok, installResult.Status); - Assert.True(TestCommon.VerifyTestMsiInstalledAndCleanup(this.installDir)); - } - + /// A representing the asynchronous unit test. + [Test] + public async Task InstallMSI() + { + if (string.IsNullOrEmpty(TestCommon.MsiInstallerPath)) + { + Assert.Ignore("MSI installer not available"); + } + + // Find package + var searchResult = this.FindOnePackage(this.testSource, PackageMatchField.Name, PackageFieldMatchOption.Equals, "TestMsiInstaller"); + + // Configure installation + var installOptions = this.TestFactory.CreateInstallOptions(); + installOptions.PackageInstallMode = PackageInstallMode.Silent; + installOptions.PreferredInstallLocation = this.installDir; + + // Install + var installResult = await this.packageManager.InstallPackageAsync(searchResult.CatalogPackage, installOptions); + + // Assert + Assert.AreEqual(InstallResultStatus.Ok, installResult.Status); + Assert.True(TestCommon.VerifyTestMsiInstalledAndCleanup(this.installDir)); + } + /// /// Test installing an msix. /// - /// A representing the asynchronous unit test. - [Test] - public async Task InstallMSIX() - { - // Find package - var searchResult = this.FindOnePackage(this.testSource, PackageMatchField.Name, PackageFieldMatchOption.Equals, "TestMsixInstaller"); - - // Configure installation - var installOptions = this.TestFactory.CreateInstallOptions(); - - // Install - var installResult = await this.packageManager.InstallPackageAsync(searchResult.CatalogPackage, installOptions); - - // Assert - Assert.AreEqual(InstallResultStatus.Ok, installResult.Status); - Assert.True(TestCommon.VerifyTestMsixInstalledAndCleanup()); - } - + /// A representing the asynchronous unit test. + [Test] + public async Task InstallMSIX() + { + // Find package + var searchResult = this.FindOnePackage(this.testSource, PackageMatchField.Name, PackageFieldMatchOption.Equals, "TestMsixInstaller"); + + // Configure installation + var installOptions = this.TestFactory.CreateInstallOptions(); + + // Install + var installResult = await this.packageManager.InstallPackageAsync(searchResult.CatalogPackage, installOptions); + + // Assert + Assert.AreEqual(InstallResultStatus.Ok, installResult.Status); + Assert.True(TestCommon.VerifyTestMsixInstalledAndCleanup()); + } + + /// + /// Test installing msix with machine scope. + /// + /// A representing the asynchronous unit test. + [Test] + public async Task InstallMSIXMachineScope() + { + // TODO: Provision and Deprovision api not supported in build server. + Assert.Ignore(); + + // Find package + var searchResult = this.FindOnePackage(this.testSource, PackageMatchField.Name, PackageFieldMatchOption.Equals, "TestMsixInstaller"); + + // Configure installation + var installOptions = this.TestFactory.CreateInstallOptions(); + installOptions.PackageInstallScope = PackageInstallScope.System; + + // Install + var installResult = await this.packageManager.InstallPackageAsync(searchResult.CatalogPackage, installOptions); + + // Assert + Assert.AreEqual(InstallResultStatus.Ok, installResult.Status); + Assert.True(TestCommon.VerifyTestMsixInstalledAndCleanup(true)); + } + /// /// Test installing msix with signature. /// - /// A representing the asynchronous unit test. - [Test] - public async Task InstallMSIXWithSignature() - { - // Task to investigate installation error - // TODO: https://task.ms/40489822 - Assert.Ignore(); - - // Find package - var searchResult = this.FindOnePackage(this.testSource, PackageMatchField.Name, PackageFieldMatchOption.Equals, "TestMsixWithSignatureHash"); - - // Configure installation - var installOptions = this.TestFactory.CreateInstallOptions(); - installOptions.PackageInstallMode = PackageInstallMode.Silent; - installOptions.PreferredInstallLocation = this.installDir; - - // Install - var installResult = await this.packageManager.InstallPackageAsync(searchResult.CatalogPackage, installOptions); - - // Assert - Assert.AreEqual(InstallResultStatus.Ok, installResult.Status); - Assert.True(TestCommon.VerifyTestMsixInstalledAndCleanup()); - } - + /// A representing the asynchronous unit test. + [Test] + public async Task InstallMSIXWithSignature() + { + // Task to investigate installation error + // TODO: https://task.ms/40489822 + Assert.Ignore(); + + // Find package + var searchResult = this.FindOnePackage(this.testSource, PackageMatchField.Name, PackageFieldMatchOption.Equals, "TestMsixWithSignatureHash"); + + // Configure installation + var installOptions = this.TestFactory.CreateInstallOptions(); + + // Install + var installResult = await this.packageManager.InstallPackageAsync(searchResult.CatalogPackage, installOptions); + + // Assert + Assert.AreEqual(InstallResultStatus.Ok, installResult.Status); + Assert.True(TestCommon.VerifyTestMsixInstalledAndCleanup()); + } + + /// + /// Test installing msix with signature machine scope. + /// + /// A representing the asynchronous unit test. + [Test] + public async Task InstallMSIXWithSignatureMachineScope() + { + // TODO: Provision and Deprovision api not supported in build server. + Assert.Ignore(); + + // Find package + var searchResult = this.FindOnePackage(this.testSource, PackageMatchField.Name, PackageFieldMatchOption.Equals, "TestMsixWithSignatureHash"); + + // Configure installation + var installOptions = this.TestFactory.CreateInstallOptions(); + installOptions.PackageInstallScope = PackageInstallScope.System; + + // Install + var installResult = await this.packageManager.InstallPackageAsync(searchResult.CatalogPackage, installOptions); + + // Assert + Assert.AreEqual(InstallResultStatus.Ok, installResult.Status); + Assert.True(TestCommon.VerifyTestMsixInstalledAndCleanup(true)); + } + /// /// Test installing msix with signature hash mismatch. /// - /// A representing the asynchronous unit test. - [Test] - public async Task InstallMSIXWithSignatureHashMismatch() - { - // Find package - var searchResult = this.FindOnePackage(this.testSource, PackageMatchField.Name, PackageFieldMatchOption.Equals, "TestMsixSignatureHashMismatch"); - - // Configure installation - var installOptions = this.TestFactory.CreateInstallOptions(); - - // Install - var installResult = await this.packageManager.InstallPackageAsync(searchResult.CatalogPackage, installOptions); - - // Assert - Assert.AreEqual(InstallResultStatus.DownloadError, installResult.Status); - Assert.False(TestCommon.VerifyTestMsixInstalledAndCleanup()); - } - + /// A representing the asynchronous unit test. + [Test] + public async Task InstallMSIXWithSignatureHashMismatch() + { + // Find package + var searchResult = this.FindOnePackage(this.testSource, PackageMatchField.Name, PackageFieldMatchOption.Equals, "TestMsixSignatureHashMismatch"); + + // Configure installation + var installOptions = this.TestFactory.CreateInstallOptions(); + + // Install + var installResult = await this.packageManager.InstallPackageAsync(searchResult.CatalogPackage, installOptions); + + // Assert + Assert.AreEqual(InstallResultStatus.DownloadError, installResult.Status); + Assert.False(TestCommon.VerifyTestMsixInstalledAndCleanup()); + } + /// /// Test installing exe. - /// - [Test] - public void InstallExeWithAlternateSourceFailure() - { - // Add mock source - TestCommon.RunAICLICommand("source add", "failSearch \"{ \"\"SearchHR\"\": \"\"0x80070002\"\" }\" Microsoft.Test.Configurable --header \"{}\""); - - // Get mock source - var failSearchSource = this.packageManager.GetPackageCatalogByName("failSearch"); - - // Find package - var searchResult = this.FindAllPackages(failSearchSource, PackageMatchField.Id, PackageFieldMatchOption.Equals, "AppInstallerTest.TestExeInstaller"); - - // Assert - Assert.NotNull(failSearchSource); - Assert.AreEqual(0, searchResult.Count); - - // Remove mock source - TestCommon.RunAICLICommand("source remove", "failSearch"); - } - + /// + [Test] + public void InstallExeWithAlternateSourceFailure() + { + // Add mock source + TestCommon.RunAICLICommand("source add", "failSearch \"{ \"\"SearchHR\"\": \"\"0x80070002\"\" }\" Microsoft.Test.Configurable --header \"{}\""); + + // Get mock source + var failSearchSource = this.packageManager.GetPackageCatalogByName("failSearch"); + + // Find package + var searchResult = this.FindAllPackages(failSearchSource, PackageMatchField.Id, PackageFieldMatchOption.Equals, "AppInstallerTest.TestExeInstaller"); + + // Assert + Assert.NotNull(failSearchSource); + Assert.AreEqual(0, searchResult.Count); + + // Remove mock source + TestCommon.RunAICLICommand("source remove", "failSearch"); + } + /// /// Test installing portable exe. /// - /// A representing the asynchronous unit test. - [Test] - public async Task InstallPortableExe() - { - string installDir = Path.Combine(Environment.GetEnvironmentVariable(Constants.LocalAppData), "Microsoft", "WinGet", "Packages"); - string productCode = Constants.PortableExePackageDirName; - string commandAlias = $"{Constants.ExeInstaller}.exe"; - string fileName = $"{Constants.ExeInstaller}.exe"; - - // Find package - var searchResult = this.FindOnePackage(this.testSource, PackageMatchField.Id, PackageFieldMatchOption.Equals, Constants.PortableExePackageId); - - // Configure installation - var installOptions = this.TestFactory.CreateInstallOptions(); - - // Install - var installResult = await this.packageManager.InstallPackageAsync(searchResult.CatalogPackage, installOptions); - - // Assert - Assert.AreEqual(InstallResultStatus.Ok, installResult.Status); - TestCommon.VerifyPortablePackage(Path.Combine(installDir, Constants.PortableExePackageDirName), commandAlias, fileName, productCode, true); - } - + /// A representing the asynchronous unit test. + [Test] + public async Task InstallPortableExe() + { + string installDir = Path.Combine(Environment.GetEnvironmentVariable(Constants.LocalAppData), "Microsoft", "WinGet", "Packages"); + string productCode = Constants.PortableExePackageDirName; + string commandAlias = $"{Constants.ExeInstaller}.exe"; + string fileName = $"{Constants.ExeInstaller}.exe"; + + // Find package + var searchResult = this.FindOnePackage(this.testSource, PackageMatchField.Id, PackageFieldMatchOption.Equals, Constants.PortableExePackageId); + + // Configure installation + var installOptions = this.TestFactory.CreateInstallOptions(); + + // Install + var installResult = await this.packageManager.InstallPackageAsync(searchResult.CatalogPackage, installOptions); + + // Assert + Assert.AreEqual(InstallResultStatus.Ok, installResult.Status); + TestCommon.VerifyPortablePackage(Path.Combine(installDir, Constants.PortableExePackageDirName), commandAlias, fileName, productCode, true); + } + /// /// Test installing portable exe with command. /// - /// A representing the asynchronous unit test. - [Test] - public async Task InstallPortableExeWithCommand() - { - string productCode = Constants.PortableExeWithCommandPackageDirName; - string fileName = Constants.AppInstallerTestExeInstallerExe; - string commandAlias = Constants.TestCommandExe; - - // Find package - var searchResult = this.FindOnePackage(this.testSource, PackageMatchField.Id, PackageFieldMatchOption.Equals, Constants.PortableExeWithCommandPackageId); - - // Configure installation - var installOptions = this.TestFactory.CreateInstallOptions(); - installOptions.PreferredInstallLocation = this.installDir; - - // Install - var installResult = await this.packageManager.InstallPackageAsync(searchResult.CatalogPackage, installOptions); - - // Assert - Assert.AreEqual(InstallResultStatus.Ok, installResult.Status); - TestCommon.VerifyPortablePackage(this.installDir, commandAlias, fileName, productCode, true); - } - + /// A representing the asynchronous unit test. + [Test] + public async Task InstallPortableExeWithCommand() + { + string productCode = Constants.PortableExeWithCommandPackageDirName; + string fileName = Constants.AppInstallerTestExeInstallerExe; + string commandAlias = Constants.TestCommandExe; + + // Find package + var searchResult = this.FindOnePackage(this.testSource, PackageMatchField.Id, PackageFieldMatchOption.Equals, Constants.PortableExeWithCommandPackageId); + + // Configure installation + var installOptions = this.TestFactory.CreateInstallOptions(); + installOptions.PreferredInstallLocation = this.installDir; + + // Install + var installResult = await this.packageManager.InstallPackageAsync(searchResult.CatalogPackage, installOptions); + + // Assert + Assert.AreEqual(InstallResultStatus.Ok, installResult.Status); + TestCommon.VerifyPortablePackage(this.installDir, commandAlias, fileName, productCode, true); + } + /// /// Test installing portable package to existing directory. /// - /// A representing the asynchronous unit test. - [Test] - public async Task InstallPortableToExistingDirectory() - { - var existingDir = Path.Combine(this.installDir, "testDirectory"); - Directory.CreateDirectory(existingDir); - - string productCode = Constants.PortableExePackageDirName; - string commandAlias = Constants.AppInstallerTestExeInstallerExe; - string fileName = Constants.AppInstallerTestExeInstallerExe; - - // Find package - var searchResult = this.FindOnePackage(this.testSource, PackageMatchField.Id, PackageFieldMatchOption.Equals, Constants.PortableExePackageId); - - // Configure installation - var installOptions = this.TestFactory.CreateInstallOptions(); - installOptions.PreferredInstallLocation = existingDir; - - // Install - var installResult = await this.packageManager.InstallPackageAsync(searchResult.CatalogPackage, installOptions); - - // Assert - Assert.AreEqual(InstallResultStatus.Ok, installResult.Status); - TestCommon.VerifyPortablePackage(existingDir, commandAlias, fileName, productCode, true); - } - + /// A representing the asynchronous unit test. + [Test] + public async Task InstallPortableToExistingDirectory() + { + var existingDir = Path.Combine(this.installDir, "testDirectory"); + Directory.CreateDirectory(existingDir); + + string productCode = Constants.PortableExePackageDirName; + string commandAlias = Constants.AppInstallerTestExeInstallerExe; + string fileName = Constants.AppInstallerTestExeInstallerExe; + + // Find package + var searchResult = this.FindOnePackage(this.testSource, PackageMatchField.Id, PackageFieldMatchOption.Equals, Constants.PortableExePackageId); + + // Configure installation + var installOptions = this.TestFactory.CreateInstallOptions(); + installOptions.PreferredInstallLocation = existingDir; + + // Install + var installResult = await this.packageManager.InstallPackageAsync(searchResult.CatalogPackage, installOptions); + + // Assert + Assert.AreEqual(InstallResultStatus.Ok, installResult.Status); + TestCommon.VerifyPortablePackage(existingDir, commandAlias, fileName, productCode, true); + } + /// /// Test installing portable package where it fails on clean up. /// - /// A representing the asynchronous unit test. - [Test] - public async Task InstallPortableFailsWithCleanup() - { - if (this.TestFactory.Context == ClsidContext.InProc) - { - // Task to investigate validation error when running in-process - // TODO: https://task.ms/40489822 - Assert.Ignore(); - } - - string winGetDir = Path.Combine(Environment.GetEnvironmentVariable(Constants.LocalAppData), "Microsoft", "WinGet"); - string installDir = Path.Combine(winGetDir, "Packages"); - string symlinkDirectory = Path.Combine(winGetDir, "Links"); - string packageDirName = Constants.PortableExePackageDirName; - string productCode = Constants.PortableExePackageDirName; - string commandAlias = Constants.AppInstallerTestExeInstallerExe; - string fileName = Constants.AppInstallerTestExeInstallerExe; - string conflictDirectory = Path.Combine(symlinkDirectory, commandAlias); - - // Create a directory with the same name as the symlink in order to cause install to fail. - Directory.CreateDirectory(conflictDirectory); - - // Find package - var searchResult = this.FindOnePackage(this.testSource, PackageMatchField.Id, PackageFieldMatchOption.Equals, Constants.PortableExePackageId); - - // Configure installation - var installOptions = this.TestFactory.CreateInstallOptions(); - - // Install - var installResult = await this.packageManager.InstallPackageAsync(searchResult.CatalogPackage, installOptions); - - // Assert - Assert.AreEqual(InstallResultStatus.InstallError, installResult.Status); - TestCommon.VerifyPortablePackage(Path.Combine(installDir, packageDirName), commandAlias, fileName, productCode, false); - Directory.Delete(conflictDirectory, true); + /// A representing the asynchronous unit test. + [Test] + public async Task InstallPortableFailsWithCleanup() + { + if (this.TestFactory.Context == ClsidContext.InProc) + { + // Task to investigate validation error when running in-process + // TODO: https://task.ms/40489822 + Assert.Ignore(); + } + + string winGetDir = Path.Combine(Environment.GetEnvironmentVariable(Constants.LocalAppData), "Microsoft", "WinGet"); + string installDir = Path.Combine(winGetDir, "Packages"); + string symlinkDirectory = Path.Combine(winGetDir, "Links"); + string packageDirName = Constants.PortableExePackageDirName; + string productCode = Constants.PortableExePackageDirName; + string commandAlias = Constants.AppInstallerTestExeInstallerExe; + string fileName = Constants.AppInstallerTestExeInstallerExe; + string conflictDirectory = Path.Combine(symlinkDirectory, commandAlias); + + // Create a directory with the same name as the symlink in order to cause install to fail. + Directory.CreateDirectory(conflictDirectory); + + // Find package + var searchResult = this.FindOnePackage(this.testSource, PackageMatchField.Id, PackageFieldMatchOption.Equals, Constants.PortableExePackageId); + + // Configure installation + var installOptions = this.TestFactory.CreateInstallOptions(); + + // Install + var installResult = await this.packageManager.InstallPackageAsync(searchResult.CatalogPackage, installOptions); + + // Assert + Assert.AreEqual(InstallResultStatus.InstallError, installResult.Status); + TestCommon.VerifyPortablePackage(Path.Combine(installDir, packageDirName), commandAlias, fileName, productCode, false); + Directory.Delete(conflictDirectory, true); } /// /// Test installing a package with user scope. /// /// A representing the asynchronous unit test. - [Test] - public async Task InstallRequireUserScope() - { - // Find package - var searchResult = this.FindOnePackage(this.testSource, PackageMatchField.Id, PackageFieldMatchOption.Equals, "AppInstallerTest.TestExeInstaller"); - - // Configure installation - var installOptions = this.TestFactory.CreateInstallOptions(); - installOptions.PackageInstallMode = PackageInstallMode.Silent; + [Test] + public async Task InstallRequireUserScope() + { + // Find package + var searchResult = this.FindOnePackage(this.testSource, PackageMatchField.Id, PackageFieldMatchOption.Equals, "AppInstallerTest.TestExeInstaller"); + + // Configure installation + var installOptions = this.TestFactory.CreateInstallOptions(); + installOptions.PackageInstallMode = PackageInstallMode.Silent; installOptions.PreferredInstallLocation = this.installDir; installOptions.PackageInstallScope = PackageInstallScope.User; @@ -443,22 +491,22 @@ public async Task InstallRequireUserScope() var installResult = await this.packageManager.InstallPackageAsync(searchResult.CatalogPackage, installOptions); // Assert - Assert.AreEqual(InstallResultStatus.NoApplicableInstallers, installResult.Status); + Assert.AreEqual(InstallResultStatus.NoApplicableInstallers, installResult.Status); } /// /// Test installing package with user scope or unknown. /// /// A representing the asynchronous unit test. - [Test] - public async Task InstallRequireUserScopeAndUnknown() - { - // Find package - var searchResult = this.FindOnePackage(this.testSource, PackageMatchField.Id, PackageFieldMatchOption.Equals, "AppInstallerTest.TestExeInstaller"); - - // Configure installation - var installOptions = this.TestFactory.CreateInstallOptions(); - installOptions.PackageInstallMode = PackageInstallMode.Silent; + [Test] + public async Task InstallRequireUserScopeAndUnknown() + { + // Find package + var searchResult = this.FindOnePackage(this.testSource, PackageMatchField.Id, PackageFieldMatchOption.Equals, "AppInstallerTest.TestExeInstaller"); + + // Configure installation + var installOptions = this.TestFactory.CreateInstallOptions(); + installOptions.PackageInstallMode = PackageInstallMode.Silent; installOptions.PreferredInstallLocation = this.installDir; installOptions.PackageInstallScope = PackageInstallScope.UserOrUnknown; @@ -466,7 +514,7 @@ public async Task InstallRequireUserScopeAndUnknown() var installResult = await this.packageManager.InstallPackageAsync(searchResult.CatalogPackage, installOptions); // Assert - Assert.AreEqual(InstallResultStatus.Ok, installResult.Status); - } - } + Assert.AreEqual(InstallResultStatus.Ok, installResult.Status); + } + } } \ No newline at end of file diff --git a/src/AppInstallerCLIE2ETests/Interop/UninstallInterop.cs b/src/AppInstallerCLIE2ETests/Interop/UninstallInterop.cs index 35bd66a422..9d5648373b 100644 --- a/src/AppInstallerCLIE2ETests/Interop/UninstallInterop.cs +++ b/src/AppInstallerCLIE2ETests/Interop/UninstallInterop.cs @@ -140,6 +140,40 @@ public async Task UninstallTestMsix() Assert.True(TestCommon.VerifyTestMsixUninstalled()); } + /// + /// Test uninstall msix with machine scope. + /// + /// A representing the asynchronous unit test. + [Test] + public async Task UninstallTestMsixMachineScope() + { + // TODO: Provision and Deprovision api not supported in build server. + Assert.Ignore(); + + // Find package + var searchResult = this.FindOnePackage(this.compositeSource, PackageMatchField.Id, PackageFieldMatchOption.Equals, Constants.MsixInstallerPackageId); + + // Configure installation + var installOptions = this.TestFactory.CreateInstallOptions(); + installOptions.PackageInstallScope = PackageInstallScope.System; + + // Install + var installResult = await this.packageManager.InstallPackageAsync(searchResult.CatalogPackage, installOptions); + Assert.AreEqual(InstallResultStatus.Ok, installResult.Status); + + // Find package again, but this time it should detect the installed version + searchResult = this.FindOnePackage(this.compositeSource, PackageMatchField.Id, PackageFieldMatchOption.Equals, Constants.MsixInstallerPackageId); + Assert.NotNull(searchResult.CatalogPackage.InstalledVersion); + + // Uninstall + var uninstallOptions = this.TestFactory.CreateUninstallOptions(); + uninstallOptions.PackageUninstallScope = PackageUninstallScope.System; + + var uninstallResult = await this.packageManager.UninstallPackageAsync(searchResult.CatalogPackage, uninstallOptions); + Assert.AreEqual(UninstallResultStatus.Ok, uninstallResult.Status); + Assert.True(TestCommon.VerifyTestMsixUninstalled(true)); + } + /// /// Test uninstall portable package. /// diff --git a/src/AppInstallerCLIE2ETests/ListCommand.cs b/src/AppInstallerCLIE2ETests/ListCommand.cs index 9b85eafc30..023a1cd580 100644 --- a/src/AppInstallerCLIE2ETests/ListCommand.cs +++ b/src/AppInstallerCLIE2ETests/ListCommand.cs @@ -97,6 +97,103 @@ public void ListWithUpgradeCode() Assert.True(result.StdOut.Contains("AppInstallerTest.TestMsiInstallerUpgradeCode")); } + /// + /// Test list with exe installed with machine scope. + /// + [Test] + public void ListWithScopeExeInstalledAsMachine() + { + System.Guid guid = System.Guid.NewGuid(); + string productCode = guid.ToString(); + var installDir = TestCommon.GetRandomTestDir(); + var result = TestCommon.RunAICLICommand("install", $"AppInstallerTest.TestExeInstaller --override \"/InstallDir {installDir} /ProductID {productCode} /UseHKLM\""); + Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); + + // List with user scope will not find the package + result = TestCommon.RunAICLICommand("list", $"{productCode} --scope user"); + Assert.AreEqual(Constants.ErrorCode.ERROR_NO_APPLICATIONS_FOUND, result.ExitCode); + Assert.False(result.StdOut.Contains(productCode)); + + // List with machine scope will find the package + result = TestCommon.RunAICLICommand("list", $"{productCode} --scope machine"); + Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); + Assert.True(result.StdOut.Contains(productCode)); + + TestCommon.RunCommand(Path.Combine(installDir, Constants.TestExeUninstallerFileName)); + } + + /// + /// Test list with exe installed with user scope. + /// + [Test] + public void ListWithScopeExeInstalledAsUser() + { + System.Guid guid = System.Guid.NewGuid(); + string productCode = guid.ToString(); + var installDir = TestCommon.GetRandomTestDir(); + var result = TestCommon.RunAICLICommand("install", $"AppInstallerTest.TestExeInstaller --override \"/InstallDir {installDir} /ProductID {productCode}\""); + Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); + + // List with user scope will find the package + result = TestCommon.RunAICLICommand("list", $"{productCode} --scope user"); + Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); + Assert.True(result.StdOut.Contains(productCode)); + + // List with machine scope will not find the package + result = TestCommon.RunAICLICommand("list", $"{productCode} --scope machine"); + Assert.AreEqual(Constants.ErrorCode.ERROR_NO_APPLICATIONS_FOUND, result.ExitCode); + Assert.False(result.StdOut.Contains(productCode)); + + TestCommon.RunCommand(Path.Combine(installDir, Constants.TestExeUninstallerFileName)); + } + + /// + /// Test list with msix installed with machine scope. + /// + [Test] + public void ListWithScopeMsixInstalledAsMachine() + { + // TODO: Provision and Deprovision api not supported in build server. + Assert.Ignore(); + + var result = TestCommon.RunAICLICommand("install", $"{Constants.MsixInstallerPackageId} --scope machine"); + Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); + + // List with user scope will also find the package because msix is provisioned for all users + result = TestCommon.RunAICLICommand("list", $"{Constants.MsixInstallerPackageId} --scope user"); + Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); + Assert.True(result.StdOut.Contains(Constants.MsixInstallerPackageId)); + + // List with machine scope will find the package + result = TestCommon.RunAICLICommand("list", $"{Constants.MsixInstallerPackageId} --scope machine"); + Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); + Assert.True(result.StdOut.Contains(Constants.MsixInstallerPackageId)); + + TestCommon.RemoveMsix(Constants.MsixInstallerName, true); + } + + /// + /// Test list with msix installed with user scope. + /// + [Test] + public void ListWithScopeMsixInstalledAsUser() + { + var result = TestCommon.RunAICLICommand("install", $"{Constants.MsixInstallerPackageId} --scope user"); + Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); + + // List with user scope will find the package + result = TestCommon.RunAICLICommand("list", $"{Constants.MsixInstallerPackageId} --scope user"); + Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); + Assert.True(result.StdOut.Contains(Constants.MsixInstallerPackageId)); + + // List with machine scope will not find the package + result = TestCommon.RunAICLICommand("list", $"{Constants.MsixInstallerPackageId} --scope machine"); + Assert.AreEqual(Constants.ErrorCode.ERROR_NO_APPLICATIONS_FOUND, result.ExitCode); + Assert.False(result.StdOut.Contains(Constants.MsixInstallerPackageId)); + + TestCommon.RemoveMsix(Constants.MsixInstallerName); + } + private void ArpVersionMappingTest(string packageIdentifier, string displayNameOverride, string displayVersionOverride, string expectedListVersion, string notExpectedListVersion = "") { System.Guid guid = System.Guid.NewGuid(); diff --git a/src/AppInstallerCLIE2ETests/TestCommon.cs b/src/AppInstallerCLIE2ETests/TestCommon.cs index 51a8bdf23f..508818e22d 100644 --- a/src/AppInstallerCLIE2ETests/TestCommon.cs +++ b/src/AppInstallerCLIE2ETests/TestCommon.cs @@ -432,10 +432,19 @@ public static bool InstallMsixRegister(string packagePath) /// Remove msix package. /// /// Package to remove. + /// Whether the package is provisioned. /// True if removed correctly. - public static bool RemoveMsix(string name) + public static bool RemoveMsix(string name, bool isProvisioned = false) { - return RunCommand("powershell", $"Get-AppxPackage \"{name}\" | Remove-AppxPackage"); + if (isProvisioned) + { + return RunCommand("powershell", $"Get-AppxProvisionedPackage -Online | Where-Object {{$_.PackageName -like \"*{name}*\"}} | Remove-AppxProvisionedPackage -Online -AllUsers") && + RunCommand("powershell", $"Get-AppxPackage \"{name}\" | Remove-AppxPackage -AllUsers"); + } + else + { + return RunCommand("powershell", $"Get-AppxPackage \"{name}\" | Remove-AppxPackage"); + } } /// @@ -610,8 +619,9 @@ public static bool VerifyTestMsiInstalledAndCleanup(string installDir) /// /// Verify msix installed correctly. /// + /// Whether the package is provisioned. /// True if success. - public static bool VerifyTestMsixInstalledAndCleanup() + public static bool VerifyTestMsixInstalledAndCleanup(bool isProvisioned = false) { var result = RunCommandWithResult("powershell", $"Get-AppxPackage {Constants.MsixInstallerName}"); @@ -620,7 +630,16 @@ public static bool VerifyTestMsixInstalledAndCleanup() return false; } - return RemoveMsix(Constants.MsixInstallerName); + if (isProvisioned) + { + result = RunCommandWithResult("powershell", $"Get-AppxProvisionedPackage -Online | Where-Object {{$_.PackageName -like \"*{Constants.MsixInstallerName}*\"}}"); + if (!result.StdOut.Contains(Constants.MsixInstallerName)) + { + return false; + } + } + + return RemoveMsix(Constants.MsixInstallerName, isProvisioned); } /// @@ -646,11 +665,21 @@ public static bool VerifyTestMsiUninstalled(string installDir) /// /// Verify msix uninstalled. /// + /// Whether the package is provisioned. /// True if success. - public static bool VerifyTestMsixUninstalled() + public static bool VerifyTestMsixUninstalled(bool isProvisioned = false) { + bool isUninstalled = false; var result = RunCommandWithResult("powershell", $"Get-AppxPackage {Constants.MsixInstallerName}"); - return string.IsNullOrWhiteSpace(result.StdOut); + isUninstalled = string.IsNullOrWhiteSpace(result.StdOut); + + if (isProvisioned) + { + result = RunCommandWithResult("powershell", $"Get-AppxProvisionedPackage -Online | Where-Object {{$_.PackageName -like \"*{Constants.MsixInstallerName}*\"}}"); + isUninstalled = isUninstalled && string.IsNullOrWhiteSpace(result.StdOut); + } + + return isUninstalled; } /// diff --git a/src/AppInstallerCLIE2ETests/UninstallCommand.cs b/src/AppInstallerCLIE2ETests/UninstallCommand.cs index 5f6566aca2..5b94f6f251 100644 --- a/src/AppInstallerCLIE2ETests/UninstallCommand.cs +++ b/src/AppInstallerCLIE2ETests/UninstallCommand.cs @@ -75,6 +75,23 @@ public void UninstallTestMsix() Assert.True(TestCommon.VerifyTestMsixUninstalled()); } + /// + /// Test uninstall msix package with machine scope. + /// + [Test] + public void UninstallTestMsixMachineScope() + { + // TODO: Provision and Deprovision api not supported in build server. + Assert.Ignore(); + + // Uninstall an MSIX + TestCommon.RunAICLICommand("install", $"{Constants.MsixInstallerPackageId} --scope machine"); + var result = TestCommon.RunAICLICommand("uninstall", $"{Constants.MsixInstallerPackageId} --scope machine"); + Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); + Assert.True(result.StdOut.Contains("Successfully uninstalled")); + Assert.True(TestCommon.VerifyTestMsixUninstalled(true)); + } + /// /// Test uninstall portable package. /// diff --git a/src/AppInstallerCLIPackage/Shared/Strings/en-us/winget.resw b/src/AppInstallerCLIPackage/Shared/Strings/en-us/winget.resw index b438ccf100..23bc85bf23 100644 --- a/src/AppInstallerCLIPackage/Shared/Strings/en-us/winget.resw +++ b/src/AppInstallerCLIPackage/Shared/Strings/en-us/winget.resw @@ -1601,4 +1601,8 @@ Please specify one of them using the --source option to proceed. Settings file couldn't load. Using default values. + + Select installed package scope filter (user or machine) + This argument allows the user to select installed packages for just the user or for the entire machine. + diff --git a/src/AppInstallerCommonCore/AppInstallerCommonCore.vcxproj b/src/AppInstallerCommonCore/AppInstallerCommonCore.vcxproj index 7c51c73099..3b6d5a2f66 100644 --- a/src/AppInstallerCommonCore/AppInstallerCommonCore.vcxproj +++ b/src/AppInstallerCommonCore/AppInstallerCommonCore.vcxproj @@ -394,6 +394,7 @@ + diff --git a/src/AppInstallerCommonCore/AppInstallerCommonCore.vcxproj.filters b/src/AppInstallerCommonCore/AppInstallerCommonCore.vcxproj.filters index 574833856a..3f4da4c727 100644 --- a/src/AppInstallerCommonCore/AppInstallerCommonCore.vcxproj.filters +++ b/src/AppInstallerCommonCore/AppInstallerCommonCore.vcxproj.filters @@ -392,6 +392,9 @@ Source Files + + Source Files + diff --git a/src/AppInstallerCommonCore/Deployment.cpp b/src/AppInstallerCommonCore/Deployment.cpp index b11ea916b6..71ac9a75cb 100644 --- a/src/AppInstallerCommonCore/Deployment.cpp +++ b/src/AppInstallerCommonCore/Deployment.cpp @@ -107,7 +107,7 @@ namespace AppInstaller::Deployment } bool AddPackageWithDeferredFallback( - const std::string& uri, + std::string_view uri, bool skipSmartScreen, IProgressCallback& callback) { @@ -115,11 +115,13 @@ namespace AppInstaller::Deployment // In the event of a failure we want to ensure that the package is not left on the system. Msix::MsixInfo packageInfo{ uri }; - std::wstring packageFullName = packageInfo.GetPackageFullNameWide(); + std::wstring packageFullNameWide = packageInfo.GetPackageFullNameWide(); + std::string packageFullName = Utility::ConvertToUTF8(packageFullNameWide); auto removePackage = wil::scope_exit([&]() { try { - RemovePackage(Utility::ConvertToUTF8(packageFullName), callback); + ProgressCallback cb; + RemovePackage(packageFullName, RemovalOptions::None, cb); } CATCH_LOG(); }); @@ -160,23 +162,25 @@ namespace AppInstaller::Deployment } // If we are skipping SmartScreen or the package was in use, stage then register the package. + PartialPercentProgressCallback progress{ callback, 100 }; + progress.SetRange(0, 95); { size_t id = GetDeploymentOperationId(); AICLI_LOG(Core, Info, << "Starting StagePackageAsync operation #" << id << ": " << uri); IAsyncOperationWithProgress stageOperation = packageManager.StagePackageAsync(uriObject, nullptr); - WaitForDeployment(stageOperation, id, callback); + WaitForDeployment(stageOperation, id, progress); } bool registrationDeferred = false; - + progress.SetRange(95, 100); { size_t id = GetDeploymentOperationId(); - AICLI_LOG(Core, Info, << "Starting RegisterPackageByFullNameAsync operation #" << id << ": " << Utility::ConvertToUTF8(packageFullName)); + AICLI_LOG(Core, Info, << "Starting RegisterPackageByFullNameAsync operation #" << id << ": " << packageFullName); IAsyncOperationWithProgress registerOperation = - packageManager.RegisterPackageByFullNameAsync(packageFullName, nullptr, DeploymentOptions::None); - HRESULT hr = WaitForDeployment(registerOperation, id, callback, false); + packageManager.RegisterPackageByFullNameAsync(packageFullNameWide, nullptr, DeploymentOptions::None); + HRESULT hr = WaitForDeployment(registerOperation, id, progress, false); if (hr == HRESULT_FROM_WIN32(ERROR_PACKAGES_IN_USE)) { @@ -194,6 +198,7 @@ namespace AppInstaller::Deployment void RemovePackage( std::string_view packageFullName, + RemovalOptions options, IProgressCallback& callback) { size_t id = GetDeploymentOperationId(); @@ -201,8 +206,110 @@ namespace AppInstaller::Deployment PackageManager packageManager; winrt::hstring fullName = Utility::ConvertToUTF16(packageFullName).c_str(); - auto deployOperation = packageManager.RemovePackageAsync(fullName, RemovalOptions::None); + auto deployOperation = packageManager.RemovePackageAsync(fullName, options); WaitForDeployment(deployOperation, id, callback); } + + bool AddPackageMachineScope( + std::string_view uri, + IProgressCallback& callback) + { + PackageManager packageManager; + + // In the event of a failure we want to ensure that the package is not left on the system. + Msix::MsixInfo packageInfo{ uri }; + std::wstring packageFullNameWide = packageInfo.GetPackageFullNameWide(); + std::string packageFullName = Utility::ConvertToUTF8(packageFullNameWide); + std::string packageFamilyName = Msix::GetPackageFamilyNameFromFullName(packageFullName); + auto removePackage = wil::scope_exit([&]() { + try + { + ProgressCallback cb; + RemovePackage(packageFullName, RemovalOptions::RemoveForAllUsers, cb); + } + CATCH_LOG(); + }); + + Uri uriObject(Utility::ConvertToUTF16(uri)); + PartialPercentProgressCallback progress{ callback, 100 }; + + // First stage package contents + progress.SetRange(0, 90); + { + size_t id = GetDeploymentOperationId(); + AICLI_LOG(Core, Info, << "Starting StagePackageAsync operation #" << id << ": " << uri); + + IAsyncOperationWithProgress stageOperation = packageManager.StagePackageAsync(uriObject, nullptr); + WaitForDeployment(stageOperation, id, progress); + } + + // Provision for all users + progress.SetRange(90, 95); + { + size_t id = GetDeploymentOperationId(); + AICLI_LOG(Core, Info, << "Starting ProvisionPackage operation #" << id << ": " << packageFamilyName); + + winrt::hstring familyName = Utility::ConvertToUTF16(packageFamilyName).c_str(); + auto deployOperation = packageManager.ProvisionPackageForAllUsersAsync(familyName); + + WaitForDeployment(deployOperation, id, progress); + } + + // Try registration as best effort, operation is considered successful as long as provisioning is successful. + progress.SetRange(95, 100); + bool registrationDeferred = false; + if (Runtime::IsRunningAsSystem()) + { + // Packages cannot be registered under local system, just return registration deferred + registrationDeferred = true; + } + else + { + try + { + size_t id = GetDeploymentOperationId(); + AICLI_LOG(Core, Info, << "Starting RegisterPackageByFullNameAsync operation #" << id << ": " << packageFullName); + + IAsyncOperationWithProgress registerOperation = + packageManager.RegisterPackageByFullNameAsync(packageFullNameWide, nullptr, DeploymentOptions::None); + WaitForDeployment(registerOperation, id, progress); + } + catch (...) + { + registrationDeferred = true; + } + } + + progress.OnProgress(100, 100, ProgressType::Percent); + removePackage.release(); + return registrationDeferred; + } + + void RemovePackageMachineScope( + std::string_view packageFamilyName, + std::string_view packageFullName, + IProgressCallback& callback) + { + PartialPercentProgressCallback progress{ callback, 100 }; + + // Deprovision first + progress.SetRange(0, 5); + { + size_t id = GetDeploymentOperationId(); + AICLI_LOG(Core, Info, << "Starting DeprovisionPackage operation #" << id << ": " << packageFamilyName); + + PackageManager packageManager; + winrt::hstring familyName = Utility::ConvertToUTF16(packageFamilyName).c_str(); + auto deployOperation = packageManager.DeprovisionPackageForAllUsersAsync(familyName); + + WaitForDeployment(deployOperation, id, progress); + } + + // Remove for all users + progress.SetRange(5, 100); + { + RemovePackage(packageFullName, RemovalOptions::RemoveForAllUsers, progress); + } + } } diff --git a/src/AppInstallerCommonCore/Manifest/ManifestCommon.cpp b/src/AppInstallerCommonCore/Manifest/ManifestCommon.cpp index c44f020277..0847fcf3ed 100644 --- a/src/AppInstallerCommonCore/Manifest/ManifestCommon.cpp +++ b/src/AppInstallerCommonCore/Manifest/ManifestCommon.cpp @@ -495,9 +495,18 @@ namespace AppInstaller::Manifest bool DoesInstallerTypeIgnoreScopeFromManifest(InstallerTypeEnum installerType) { - return ( - installerType == InstallerTypeEnum::Portable - ); + return + installerType == InstallerTypeEnum::Portable || + installerType == InstallerTypeEnum::Msix || + installerType == InstallerTypeEnum::MSStore; + } + + bool DoesInstallerTypeRequireAdminForMachineScopeInstall(InstallerTypeEnum installerType) + { + return + installerType == InstallerTypeEnum::Portable || + installerType == InstallerTypeEnum::MSStore || + installerType == InstallerTypeEnum::Msix; } bool IsArchiveType(InstallerTypeEnum installerType) diff --git a/src/AppInstallerCommonCore/MsixInfo.cpp b/src/AppInstallerCommonCore/MsixInfo.cpp index 00db47e0bb..45e1218825 100644 --- a/src/AppInstallerCommonCore/MsixInfo.cpp +++ b/src/AppInstallerCommonCore/MsixInfo.cpp @@ -11,6 +11,7 @@ using namespace winrt::Windows::Storage::Streams; using namespace Microsoft::WRL; using namespace AppInstaller::Utility::HttpStream; +using namespace winrt::Windows::Management::Deployment; namespace AppInstaller::Msix { @@ -363,42 +364,24 @@ namespace AppInstaller::Msix std::optional GetPackageFullNameFromFamilyName(std::string_view familyName) { + PackageManager packageManager; + std::wstring pfn = Utility::ConvertToUTF16(familyName); - UINT32 fullNameCount = 0; - UINT32 bufferLength = 0; - UINT32 properties = 0; + auto packages = packageManager.FindPackages(pfn); - LONG findResult = FindPackagesByPackageFamily(pfn.c_str(), PACKAGE_FILTER_HEAD, &fullNameCount, nullptr, &bufferLength, nullptr, &properties); - if (findResult == ERROR_SUCCESS || fullNameCount == 0) - { - // No package found - return {}; - } - else if (findResult != ERROR_INSUFFICIENT_BUFFER) - { - THROW_WIN32(findResult); - } - else if (fullNameCount != 1) + std::optional result; + for (const auto& package : packages) { - // Don't directly error, let caller deal with it - AICLI_LOG(Core, Error, << "Multiple packages found for family name: " << fullNameCount); - return {}; - } - - // fullNameCount == 1 at this point - PWSTR fullNamePtr; - std::wstring buffer(static_cast(bufferLength) + 1, L'\0'); + if (result.has_value()) + { + // More than 1 package found. Don't directly error, let caller deal with it. + return {}; + } - THROW_IF_WIN32_ERROR(FindPackagesByPackageFamily(pfn.c_str(), PACKAGE_FILTER_HEAD, &fullNameCount, &fullNamePtr, &bufferLength, &buffer[0], &properties)); - if (fullNameCount != 1 || bufferLength == 0) - { - // Something changed in between, abandon - AICLI_LOG(Core, Error, << "Packages found for family name: " << fullNameCount); - return {}; + result = Utility::ConvertToUTF8(package.Id().FullName()); } - buffer.resize(bufferLength - 1); - return Utility::ConvertToUTF8(buffer); + return result; } std::string GetPackageFamilyNameFromFullName(std::string_view fullName) diff --git a/src/AppInstallerCommonCore/Progress.cpp b/src/AppInstallerCommonCore/Progress.cpp new file mode 100644 index 0000000000..f5084a495f --- /dev/null +++ b/src/AppInstallerCommonCore/Progress.cpp @@ -0,0 +1,104 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "AppInstallerProgress.h" + +namespace AppInstaller +{ + ProgressCallback::ProgressCallback(IProgressSink* sink) : m_sink(sink) + { + } + + void ProgressCallback::BeginProgress() + { + IProgressSink* sink = GetSink(); + if (sink) + { + sink->BeginProgress(); + } + } + + void ProgressCallback::OnProgress(uint64_t current, uint64_t maximum, ProgressType type) + { + IProgressSink* sink = GetSink(); + if (sink) + { + sink->OnProgress(current, maximum, type); + } + } + + void ProgressCallback::EndProgress(bool hideProgressWhenDone) + { + IProgressSink* sink = GetSink(); + if (sink) + { + sink->EndProgress(hideProgressWhenDone); + } + }; + + bool ProgressCallback::IsCancelled() + { + return m_cancelled.load(); + } + + [[nodiscard]] IProgressCallback::CancelFunctionRemoval ProgressCallback::SetCancellationFunction(std::function&& f) + { + m_cancellationFunction = std::move(f); + if (m_cancellationFunction) + { + return IProgressCallback::CancelFunctionRemoval(this); + } + else + { + return {}; + } + } + + void ProgressCallback::Cancel() + { + m_cancelled = true; + if (m_cancellationFunction) + { + m_cancellationFunction(); + } + } + + IProgressSink* ProgressCallback::GetSink() + { + return m_sink.load(); + } + + PartialPercentProgressCallback::PartialPercentProgressCallback(IProgressCallback& baseCallback, uint64_t globalMax) : + m_baseCallback(baseCallback), m_globalMax(globalMax) + { + } + + void PartialPercentProgressCallback::BeginProgress() + { + THROW_HR(E_NOTIMPL); + } + + void PartialPercentProgressCallback::OnProgress(uint64_t current, uint64_t maximum, ProgressType type) + { + THROW_HR_IF(E_UNEXPECTED, ProgressType::Percent != type); + + m_baseCallback.OnProgress(m_rangeMin + (m_rangeMax - m_rangeMin) * current / maximum, m_globalMax, type); + } + + void PartialPercentProgressCallback::EndProgress(bool) + { + THROW_HR(E_NOTIMPL); + } + + IProgressCallback::CancelFunctionRemoval PartialPercentProgressCallback::SetCancellationFunction(std::function&& f) + { + return m_baseCallback.SetCancellationFunction(std::move(f)); + } + + void PartialPercentProgressCallback::SetRange(uint64_t rangeMin, uint64_t rangeMax) + { + THROW_HR_IF(E_INVALIDARG, rangeMin > rangeMax || rangeMax > m_globalMax); + m_rangeMin = rangeMin; + m_rangeMax = rangeMax; + } +} diff --git a/src/AppInstallerCommonCore/Public/AppInstallerDeployment.h b/src/AppInstallerCommonCore/Public/AppInstallerDeployment.h index b40e572c41..5e0ab1f862 100644 --- a/src/AppInstallerCommonCore/Public/AppInstallerDeployment.h +++ b/src/AppInstallerCommonCore/Public/AppInstallerDeployment.h @@ -21,12 +21,27 @@ namespace AppInstaller::Deployment // a deferred registration. // Returns true if the registration was deferred; false if not. bool AddPackageWithDeferredFallback( - const std::string& uri, + std::string_view uri, bool skipSmartScreen, IProgressCallback& callback); // Calls winrt::Windows::Management::Deployment::PackageManager::RemovePackageAsync void RemovePackage( + std::string_view packageFullName, + winrt::Windows::Management::Deployment::RemovalOptions options, + IProgressCallback& callback); + + // Calls winrt::Windows::Management::Deployment::PackageManager::StagePackageAsync + // winrt::Windows::Management::Deployment::PackageManager::ProvisionPackageForAllUsersAsync + // winrt::Windows::Management::Deployment::PackageManager::RegisterPackageByFullNameAsync if not running as system + bool AddPackageMachineScope( + std::string_view uri, + IProgressCallback& callback); + + // Calls winrt::Windows::Management::Deployment::PackageManager::DeprovisionPackageForAllUsersAsync + // winrt::Windows::Management::Deployment::PackageManager::RemovePackageAsync with RemoveForAllUsers + void RemovePackageMachineScope( + std::string_view packageFamilyName, std::string_view packageFullName, IProgressCallback& callback); } diff --git a/src/AppInstallerCommonCore/Public/AppInstallerProgress.h b/src/AppInstallerCommonCore/Public/AppInstallerProgress.h index b58b5f4b65..a80f716138 100644 --- a/src/AppInstallerCommonCore/Public/AppInstallerProgress.h +++ b/src/AppInstallerCommonCore/Public/AppInstallerProgress.h @@ -59,66 +59,21 @@ namespace AppInstaller struct ProgressCallback : public IProgressCallback { ProgressCallback() = default; - ProgressCallback(IProgressSink* sink) : m_sink(sink) {} + ProgressCallback(IProgressSink* sink); - void BeginProgress() override - { - IProgressSink* sink = GetSink(); - if (sink) - { - sink->BeginProgress(); - } - }; - - void OnProgress(uint64_t current, uint64_t maximum, ProgressType type) override - { - IProgressSink* sink = GetSink(); - if (sink) - { - sink->OnProgress(current, maximum, type); - } - } + void BeginProgress() override; - void EndProgress(bool hideProgressWhenDone) override - { - IProgressSink* sink = GetSink(); - if (sink) - { - sink->EndProgress(hideProgressWhenDone); - } - }; - - bool IsCancelled() override - { - return m_cancelled.load(); - } + void OnProgress(uint64_t current, uint64_t maximum, ProgressType type) override; - [[nodiscard]] IProgressCallback::CancelFunctionRemoval SetCancellationFunction(std::function&& f) override - { - m_cancellationFunction = std::move(f); - if (m_cancellationFunction) - { - return IProgressCallback::CancelFunctionRemoval(this); - } - else - { - return {}; - } - } + void EndProgress(bool hideProgressWhenDone) override; - void Cancel() - { - m_cancelled = true; - if (m_cancellationFunction) - { - m_cancellationFunction(); - } - } + bool IsCancelled() override; - IProgressSink* GetSink() - { - return m_sink.load(); - } + [[nodiscard]] IProgressCallback::CancelFunctionRemoval SetCancellationFunction(std::function&& f) override; + + void Cancel(); + + IProgressSink* GetSink(); private: std::atomic m_sink = nullptr; @@ -126,6 +81,28 @@ namespace AppInstaller std::function m_cancellationFunction; }; + // A progress callback that reports its progress as a partial range of percentage to its base progress callback + struct PartialPercentProgressCallback : public ProgressCallback + { + PartialPercentProgressCallback(IProgressCallback& baseCallback, uint64_t globalMax); + + void BeginProgress() override; + + void OnProgress(uint64_t current, uint64_t maximum, ProgressType type) override; + + void EndProgress(bool hideProgressWhenDone) override; + + [[nodiscard]] IProgressCallback::CancelFunctionRemoval SetCancellationFunction(std::function&& f) override; + + void SetRange(uint64_t rangeMin, uint64_t rangeMax); + + private: + IProgressCallback& m_baseCallback; + uint64_t m_rangeMin = 0; + uint64_t m_rangeMax = 0; + uint64_t m_globalMax = 0; + }; + namespace details { inline void RemoveCancellationFunction(IProgressCallback* callback) diff --git a/src/AppInstallerCommonCore/Public/AppInstallerRuntime.h b/src/AppInstallerCommonCore/Public/AppInstallerRuntime.h index 451a2fc33b..f7df896850 100644 --- a/src/AppInstallerCommonCore/Public/AppInstallerRuntime.h +++ b/src/AppInstallerCommonCore/Public/AppInstallerRuntime.h @@ -125,6 +125,9 @@ namespace AppInstaller::Runtime // Determines whether the process is running with administrator privileges. bool IsRunningAsAdmin(); + // Determines whether the process is running with local system context. + bool IsRunningAsSystem(); + // Determines whether developer mode is enabled. bool IsDevModeEnabled(); diff --git a/src/AppInstallerCommonCore/Public/winget/ManifestCommon.h b/src/AppInstallerCommonCore/Public/winget/ManifestCommon.h index 478daa9619..5e45c238cb 100644 --- a/src/AppInstallerCommonCore/Public/winget/ManifestCommon.h +++ b/src/AppInstallerCommonCore/Public/winget/ManifestCommon.h @@ -333,6 +333,9 @@ namespace AppInstaller::Manifest // Gets a value indicating whether the given installer ignores the Scope value from the manifest. bool DoesInstallerTypeIgnoreScopeFromManifest(InstallerTypeEnum installerType); + // Gets a value indicating whether the given installer requires admin for install. + bool DoesInstallerTypeRequireAdminForMachineScopeInstall(InstallerTypeEnum installerType); + // Gets a value indicating whether the given installer type is an archive. bool IsArchiveType(InstallerTypeEnum installerType); diff --git a/src/AppInstallerCommonCore/Runtime.cpp b/src/AppInstallerCommonCore/Runtime.cpp index 9564d11305..82e048f00c 100644 --- a/src/AppInstallerCommonCore/Runtime.cpp +++ b/src/AppInstallerCommonCore/Runtime.cpp @@ -697,6 +697,11 @@ namespace AppInstaller::Runtime return wil::test_token_membership(nullptr, SECURITY_NT_AUTHORITY, SECURITY_BUILTIN_DOMAIN_RID, DOMAIN_ALIAS_RID_ADMINS); } + bool IsRunningAsSystem() + { + return wil::test_token_membership(nullptr, SECURITY_NT_AUTHORITY, SECURITY_LOCAL_SYSTEM_RID); + } + // Determines whether developer mode is enabled. // Does not account for the group policy value which takes precedence over this registry value. bool IsDevModeEnabled() diff --git a/src/AppInstallerCommonCore/pch.h b/src/AppInstallerCommonCore/pch.h index c78405ba5b..a5f27e4fc5 100644 --- a/src/AppInstallerCommonCore/pch.h +++ b/src/AppInstallerCommonCore/pch.h @@ -78,6 +78,7 @@ #include #include #include +#include #include #include #include diff --git a/src/AppInstallerRepositoryCore/Microsoft/PreIndexedPackageSourceFactory.cpp b/src/AppInstallerRepositoryCore/Microsoft/PreIndexedPackageSourceFactory.cpp index e80b2ede73..b75f8f8aaa 100644 --- a/src/AppInstallerRepositoryCore/Microsoft/PreIndexedPackageSourceFactory.cpp +++ b/src/AppInstallerRepositoryCore/Microsoft/PreIndexedPackageSourceFactory.cpp @@ -302,7 +302,7 @@ namespace AppInstaller::Repository::Microsoft { if (origin != PackageOrigin::PackageOrigin_Store) { - Deployment::RemovePackage(Utility::ConvertToUTF8(pfn), progress); + Deployment::RemovePackage(Utility::ConvertToUTF8(pfn), winrt::Windows::Management::Deployment::RemovalOptions::None, progress); THROW_HR(APPINSTALLER_CLI_ERROR_SOURCE_DATA_INTEGRITY_FAILURE); } } @@ -322,7 +322,7 @@ namespace AppInstaller::Repository::Microsoft else { AICLI_LOG(Repo, Info, << "Removing package: " << *fullName); - Deployment::RemovePackage(*fullName, callback); + Deployment::RemovePackage(*fullName, winrt::Windows::Management::Deployment::RemovalOptions::None, callback); } return true; diff --git a/src/AppInstallerRepositoryCore/Microsoft/PredefinedInstalledSourceFactory.cpp b/src/AppInstallerRepositoryCore/Microsoft/PredefinedInstalledSourceFactory.cpp index fff0b68357..5bce3eb693 100644 --- a/src/AppInstallerRepositoryCore/Microsoft/PredefinedInstalledSourceFactory.cpp +++ b/src/AppInstallerRepositoryCore/Microsoft/PredefinedInstalledSourceFactory.cpp @@ -18,14 +18,23 @@ namespace AppInstaller::Repository::Microsoft namespace { // Populates the index with the entries from MSIX. - void PopulateIndexFromMSIX(SQLiteIndex& index) + void PopulateIndexFromMSIX(SQLiteIndex& index, Manifest::ScopeEnum scope) { using namespace winrt::Windows::ApplicationModel; using namespace winrt::Windows::Management::Deployment; + using namespace winrt::Windows::Foundation::Collections; - // TODO: Consider if Optional packages should also be enumerated + IIterable packages; PackageManager packageManager; - auto packages = packageManager.FindPackagesForUserWithPackageTypes({}, PackageTypes::Main); + if (scope == Manifest::ScopeEnum::Machine) + { + packages = packageManager.FindProvisionedPackages(); + } + else + { + // TODO: Consider if Optional packages should also be enumerated + packages = packageManager.FindPackagesForUserWithPackageTypes({}, PackageTypes::Main); + } // Reuse the same manifest object, as we will be setting the same values every time. Manifest::Manifest manifest; @@ -57,23 +66,26 @@ namespace AppInstaller::Repository::Microsoft bool isPackageNameSet = false; // Attempt to get the DisplayName. Since this will retrieve the localized value, it has a chance to fail. // Rather than completely skip this package in that case, we will simply fall back to using the package name below. - try + if (!Runtime::IsRunningAsSystem()) { - auto displayName = Utility::ConvertToUTF8(package.DisplayName()); - if (!displayName.empty()) + try { - manifest.DefaultLocalization.Add(displayName); - isPackageNameSet = true; + auto displayName = Utility::ConvertToUTF8(package.DisplayName()); + if (!displayName.empty()) + { + manifest.DefaultLocalization.Add(displayName); + isPackageNameSet = true; + } + } + catch (const winrt::hresult_error& hre) + { + AICLI_LOG(Repo, Info, << "winrt::hresult_error[0x" << Logging::SetHRFormat << hre.code() << ": " << + Utility::ConvertToUTF8(hre.message()) << "] exception thrown when getting DisplayName for " << familyName); + } + catch (...) + { + AICLI_LOG(Repo, Info, << "Unknown exception thrown when getting DisplayName for " << familyName); } - } - catch (const winrt::hresult_error& hre) - { - AICLI_LOG(Repo, Info, << "winrt::hresult_error[0x" << Logging::SetHRFormat << hre.code() << ": " << - Utility::ConvertToUTF8(hre.message()) << "] exception thrown when getting DisplayName for " << familyName); - } - catch (...) - { - AICLI_LOG(Repo, Info, << "Unknown exception thrown when getting DisplayName for " << familyName); } if (!isPackageNameSet) @@ -121,16 +133,29 @@ namespace AppInstaller::Repository::Microsoft SQLiteIndex index = SQLiteIndex::CreateNew(SQLITE_MEMORY_DB_CONNECTION_TARGET, Schema::Version::Latest()); // Put installed packages into the index - if (filter == PredefinedInstalledSourceFactory::Filter::None || filter == PredefinedInstalledSourceFactory::Filter::ARP) + if (filter == PredefinedInstalledSourceFactory::Filter::None || filter == PredefinedInstalledSourceFactory::Filter::ARP || + filter == PredefinedInstalledSourceFactory::Filter::User || filter == PredefinedInstalledSourceFactory::Filter::Machine) { ARPHelper arpHelper; - arpHelper.PopulateIndexFromARP(index, Manifest::ScopeEnum::Machine); - arpHelper.PopulateIndexFromARP(index, Manifest::ScopeEnum::User); + if (filter != PredefinedInstalledSourceFactory::Filter::User) + { + arpHelper.PopulateIndexFromARP(index, Manifest::ScopeEnum::Machine); + } + if (filter != PredefinedInstalledSourceFactory::Filter::Machine) + { + arpHelper.PopulateIndexFromARP(index, Manifest::ScopeEnum::User); + } } - if (filter == PredefinedInstalledSourceFactory::Filter::None || filter == PredefinedInstalledSourceFactory::Filter::MSIX) + if (filter == PredefinedInstalledSourceFactory::Filter::None || + filter == PredefinedInstalledSourceFactory::Filter::MSIX || + filter == PredefinedInstalledSourceFactory::Filter::User) { - PopulateIndexFromMSIX(index); + PopulateIndexFromMSIX(index, Manifest::ScopeEnum::User); + } + else if (filter == PredefinedInstalledSourceFactory::Filter::Machine) + { + PopulateIndexFromMSIX(index, Manifest::ScopeEnum::Machine); } return std::make_shared(m_details, std::move(index), Synchronization::CrossProcessReaderWriteLock{}, true); @@ -180,6 +205,10 @@ namespace AppInstaller::Repository::Microsoft return "ARP"sv; case AppInstaller::Repository::Microsoft::PredefinedInstalledSourceFactory::Filter::MSIX: return "MSIX"sv; + case AppInstaller::Repository::Microsoft::PredefinedInstalledSourceFactory::Filter::User: + return "User"sv; + case AppInstaller::Repository::Microsoft::PredefinedInstalledSourceFactory::Filter::Machine: + return "Machine"sv; default: return "Unknown"sv; } @@ -195,6 +224,14 @@ namespace AppInstaller::Repository::Microsoft { return Filter::MSIX; } + else if (filter == FilterToString(Filter::User)) + { + return Filter::User; + } + else if (filter == FilterToString(Filter::Machine)) + { + return Filter::Machine; + } else { return Filter::None; diff --git a/src/AppInstallerRepositoryCore/Microsoft/PredefinedInstalledSourceFactory.h b/src/AppInstallerRepositoryCore/Microsoft/PredefinedInstalledSourceFactory.h index bc8e59fe4d..3a6bcc216f 100644 --- a/src/AppInstallerRepositoryCore/Microsoft/PredefinedInstalledSourceFactory.h +++ b/src/AppInstallerRepositoryCore/Microsoft/PredefinedInstalledSourceFactory.h @@ -24,9 +24,16 @@ namespace AppInstaller::Repository::Microsoft // The filtering level for the source. enum class Filter { + // Contains user ARP, machine ARP and user MSIX None, + // Contains user ARP and machine ARP ARP, + // Contains user MSIX MSIX, + // Contains user ARP and user MSIX + User, + // Contains machine ARP and machine MSIX + Machine, }; // Converts a filter to its string. diff --git a/src/AppInstallerRepositoryCore/Public/winget/RepositorySource.h b/src/AppInstallerRepositoryCore/Public/winget/RepositorySource.h index 282be1a780..435e2879c4 100644 --- a/src/AppInstallerRepositoryCore/Public/winget/RepositorySource.h +++ b/src/AppInstallerRepositoryCore/Public/winget/RepositorySource.h @@ -56,7 +56,12 @@ namespace AppInstaller::Repository // These sources are not under the direct control of the user, such as packages installed on the system. enum class PredefinedSource { + // Default behavior. Contains ARP packages installed as for user and for machine, MSIX packages for current user. Installed, + // Only contains packages installed as for user + InstalledUser, + // Only contains packages installed as for machine + InstalledMachine, ARP, MSIX, Installing, diff --git a/src/AppInstallerRepositoryCore/RepositorySource.cpp b/src/AppInstallerRepositoryCore/RepositorySource.cpp index 28466bca35..d1474e6a65 100644 --- a/src/AppInstallerRepositoryCore/RepositorySource.cpp +++ b/src/AppInstallerRepositoryCore/RepositorySource.cpp @@ -130,6 +130,14 @@ namespace AppInstaller::Repository details.Type = Microsoft::PredefinedInstalledSourceFactory::Type(); details.Arg = Microsoft::PredefinedInstalledSourceFactory::FilterToString(Microsoft::PredefinedInstalledSourceFactory::Filter::None); return details; + case PredefinedSource::InstalledUser: + details.Type = Microsoft::PredefinedInstalledSourceFactory::Type(); + details.Arg = Microsoft::PredefinedInstalledSourceFactory::FilterToString(Microsoft::PredefinedInstalledSourceFactory::Filter::User); + return details; + case PredefinedSource::InstalledMachine: + details.Type = Microsoft::PredefinedInstalledSourceFactory::Type(); + details.Arg = Microsoft::PredefinedInstalledSourceFactory::FilterToString(Microsoft::PredefinedInstalledSourceFactory::Filter::Machine); + return details; case PredefinedSource::ARP: details.Type = Microsoft::PredefinedInstalledSourceFactory::Type(); details.Arg = Microsoft::PredefinedInstalledSourceFactory::FilterToString(Microsoft::PredefinedInstalledSourceFactory::Filter::ARP); diff --git a/src/AppInstallerTestExeInstaller/main.cpp b/src/AppInstallerTestExeInstaller/main.cpp index 593e956f9d..a7c7535135 100644 --- a/src/AppInstallerTestExeInstaller/main.cpp +++ b/src/AppInstallerTestExeInstaller/main.cpp @@ -16,7 +16,7 @@ std::wstring_view DefaultProductID = L"{A499DD5E-8DC5-4AD2-911A-BCD0263295E9}"; std::wstring_view DefaultDisplayName = L"AppInstallerTestExeInstaller"; std::wstring_view DefaultDisplayVersion = L"1.0.0.0"; -path GenerateUninstaller(std::wostream& out, const path& installDirectory, const std::wstring& productID) +path GenerateUninstaller(std::wostream& out, const path& installDirectory, const std::wstring& productID, bool useHKLM) { path uninstallerPath = installDirectory; uninstallerPath /= "UninstallTestExe.bat"; @@ -26,7 +26,7 @@ path GenerateUninstaller(std::wostream& out, const path& installDirectory, const path uninstallerOutputTextFilePath = installDirectory; uninstallerOutputTextFilePath /= "TestExeUninstalled.txt"; - std::wstring registryKey{ L"HKEY_CURRENT_USER\\" }; + std::wstring registryKey{ useHKLM ? L"HKEY_LOCAL_MACHINE\\" : L"HKEY_CURRENT_USER\\" }; registryKey += RegistrySubkey; if (!productID.empty()) { @@ -53,7 +53,8 @@ void WriteToUninstallRegistry( const path& uninstallerPath, const std::wstring& displayName, const std::wstring& displayVersion, - const std::wstring& installLocation) + const std::wstring& installLocation, + bool useHKLM) { HKEY hkey; LONG lReg; @@ -77,7 +78,7 @@ void WriteToUninstallRegistry( } lReg = RegCreateKeyEx( - HKEY_CURRENT_USER, + useHKLM ? HKEY_LOCAL_MACHINE : HKEY_CURRENT_USER, registryKey.c_str(), 0, NULL, @@ -144,6 +145,7 @@ int wmain(int argc, const wchar_t** argv) std::wstring productCode; std::wstring displayName; std::wstring displayVersion; + bool useHKLM = false; int exitCode = 0; // Output to cout by default, but swap to a file if requested @@ -214,6 +216,12 @@ int wmain(int argc, const wchar_t** argv) outContent << argv[i] << ' '; } } + + // Writes to HKLM + else if (_wcsicmp(argv[i], L"/UseHKLM") == 0) + { + useHKLM = true; + } } if (displayName.empty()) @@ -234,9 +242,9 @@ int wmain(int argc, const wchar_t** argv) file.close(); - path uninstallerPath = GenerateUninstaller(*out, installDirectory, productCode); + path uninstallerPath = GenerateUninstaller(*out, installDirectory, productCode, useHKLM); - WriteToUninstallRegistry(*out, productCode, uninstallerPath, displayName, displayVersion, installDirectory.wstring()); + WriteToUninstallRegistry(*out, productCode, uninstallerPath, displayName, displayVersion, installDirectory.wstring(), useHKLM); return exitCode; } diff --git a/src/Microsoft.Management.Deployment/Converters.cpp b/src/Microsoft.Management.Deployment/Converters.cpp index fe4aee0b89..2fe7071e63 100644 --- a/src/Microsoft.Management.Deployment/Converters.cpp +++ b/src/Microsoft.Management.Deployment/Converters.cpp @@ -307,4 +307,19 @@ namespace winrt::Microsoft::Management::Deployment::implementation return Microsoft::Management::Deployment::PackageInstallerScope::Unknown; } + + ::AppInstaller::Manifest::ScopeEnum GetManifestUninstallScope(winrt::Microsoft::Management::Deployment::PackageUninstallScope scope) + { + switch (scope) + { + case winrt::Microsoft::Management::Deployment::PackageInstallScope::Any: + return ::AppInstaller::Manifest::ScopeEnum::Unknown; + case winrt::Microsoft::Management::Deployment::PackageInstallScope::User: + return ::AppInstaller::Manifest::ScopeEnum::User; + case winrt::Microsoft::Management::Deployment::PackageInstallScope::System: + return ::AppInstaller::Manifest::ScopeEnum::Machine; + } + + return ::AppInstaller::Manifest::ScopeEnum::Unknown; + } } diff --git a/src/Microsoft.Management.Deployment/Converters.h b/src/Microsoft.Management.Deployment/Converters.h index 800cb35b98..f3ee6bd684 100644 --- a/src/Microsoft.Management.Deployment/Converters.h +++ b/src/Microsoft.Management.Deployment/Converters.h @@ -20,6 +20,7 @@ namespace winrt::Microsoft::Management::Deployment::implementation std::pair<::AppInstaller::Manifest::ScopeEnum, bool> GetManifestScope(winrt::Microsoft::Management::Deployment::PackageInstallScope scope); winrt::Microsoft::Management::Deployment::PackageInstallerType GetDeploymentInstallerType(::AppInstaller::Manifest::InstallerTypeEnum installerType); winrt::Microsoft::Management::Deployment::PackageInstallerScope GetDeploymentInstallerScope(::AppInstaller::Manifest::ScopeEnum installerScope); + ::AppInstaller::Manifest::ScopeEnum GetManifestUninstallScope(winrt::Microsoft::Management::Deployment::PackageUninstallScope scope); #define WINGET_GET_OPERATION_RESULT_STATUS(_installResultStatus_, _uninstallResultStatus_) \ if constexpr (std::is_same_v) \ diff --git a/src/Microsoft.Management.Deployment/CreateCompositePackageCatalogOptions.cpp b/src/Microsoft.Management.Deployment/CreateCompositePackageCatalogOptions.cpp index c4609596c5..84fe9dc562 100644 --- a/src/Microsoft.Management.Deployment/CreateCompositePackageCatalogOptions.cpp +++ b/src/Microsoft.Management.Deployment/CreateCompositePackageCatalogOptions.cpp @@ -25,6 +25,14 @@ namespace winrt::Microsoft::Management::Deployment::implementation { m_compositeSearchBehavior = value; } + winrt::Microsoft::Management::Deployment::PackageInstallScope CreateCompositePackageCatalogOptions::InstalledScope() + { + return m_installedScope; + } + void CreateCompositePackageCatalogOptions::InstalledScope(winrt::Microsoft::Management::Deployment::PackageInstallScope const& value) + { + m_installedScope = value; + } CoCreatableMicrosoftManagementDeploymentClass(CreateCompositePackageCatalogOptions); } diff --git a/src/Microsoft.Management.Deployment/CreateCompositePackageCatalogOptions.h b/src/Microsoft.Management.Deployment/CreateCompositePackageCatalogOptions.h index dc880ffc16..538b203361 100644 --- a/src/Microsoft.Management.Deployment/CreateCompositePackageCatalogOptions.h +++ b/src/Microsoft.Management.Deployment/CreateCompositePackageCatalogOptions.h @@ -14,11 +14,14 @@ namespace winrt::Microsoft::Management::Deployment::implementation winrt::Windows::Foundation::Collections::IVector Catalogs(); winrt::Microsoft::Management::Deployment::CompositeSearchBehavior CompositeSearchBehavior(); void CompositeSearchBehavior(winrt::Microsoft::Management::Deployment::CompositeSearchBehavior const& value); + winrt::Microsoft::Management::Deployment::PackageInstallScope InstalledScope(); + void InstalledScope(winrt::Microsoft::Management::Deployment::PackageInstallScope const& value); #if !defined(INCLUDE_ONLY_INTERFACE_METHODS) private: winrt::Windows::Foundation::Collections::IVector m_catalogs{ winrt::single_threaded_vector() }; winrt::Microsoft::Management::Deployment::CompositeSearchBehavior m_compositeSearchBehavior = winrt::Microsoft::Management::Deployment::CompositeSearchBehavior::RemotePackagesFromAllCatalogs; + winrt::Microsoft::Management::Deployment::PackageInstallScope m_installedScope = winrt::Microsoft::Management::Deployment::PackageInstallScope::Any; #endif }; } diff --git a/src/Microsoft.Management.Deployment/PackageCatalogReference.cpp b/src/Microsoft.Management.Deployment/PackageCatalogReference.cpp index e8bbf0f67c..5833c1b765 100644 --- a/src/Microsoft.Management.Deployment/PackageCatalogReference.cpp +++ b/src/Microsoft.Management.Deployment/PackageCatalogReference.cpp @@ -78,7 +78,21 @@ namespace winrt::Microsoft::Management::Deployment::implementation // Check if search behavior indicates that the caller does not want to do local correlation. if (m_compositePackageCatalogOptions.CompositeSearchBehavior() != Microsoft::Management::Deployment::CompositeSearchBehavior::RemotePackagesFromRemoteCatalogs) { - ::AppInstaller::Repository::Source installedSource = ::AppInstaller::Repository::Source{ ::AppInstaller::Repository::PredefinedSource::Installed }; + ::AppInstaller::Repository::Source installedSource; + auto manifestInstalledScope = GetManifestScope(m_compositePackageCatalogOptions.InstalledScope()).first; + if (manifestInstalledScope == ::AppInstaller::Manifest::ScopeEnum::User) + { + installedSource = ::AppInstaller::Repository::Source{ ::AppInstaller::Repository::PredefinedSource::InstalledUser }; + } + else if (manifestInstalledScope == ::AppInstaller::Manifest::ScopeEnum::Machine) + { + installedSource = ::AppInstaller::Repository::Source{ ::AppInstaller::Repository::PredefinedSource::InstalledMachine }; + } + else + { + installedSource = ::AppInstaller::Repository::Source{ ::AppInstaller::Repository::PredefinedSource::Installed }; + } + installedSource.Open(progress); source = ::AppInstaller::Repository::Source{ installedSource, source, searchBehavior }; } diff --git a/src/Microsoft.Management.Deployment/PackageManager.cpp b/src/Microsoft.Management.Deployment/PackageManager.cpp index 16d22c2f31..0d976cce67 100644 --- a/src/Microsoft.Management.Deployment/PackageManager.cpp +++ b/src/Microsoft.Management.Deployment/PackageManager.cpp @@ -423,6 +423,12 @@ namespace winrt::Microsoft::Management::Deployment::implementation { context->Args.AddArg(Execution::Args::Type::Silent); } + + auto uninstallScope = GetManifestUninstallScope(options.PackageUninstallScope()); + if (uninstallScope != ::AppInstaller::Manifest::ScopeEnum::Unknown) + { + context->Args.AddArg(Execution::Args::Type::InstallScope, ScopeToString(uninstallScope)); + } } } diff --git a/src/Microsoft.Management.Deployment/PackageManager.idl b/src/Microsoft.Management.Deployment/PackageManager.idl index 6bc8abb1ed..1217ddb674 100644 --- a/src/Microsoft.Management.Deployment/PackageManager.idl +++ b/src/Microsoft.Management.Deployment/PackageManager.idl @@ -673,6 +673,12 @@ namespace Microsoft.Management.Deployment IVector Catalogs { get; }; /// Sets the default search behavior if the catalog is a composite catalog. CompositeSearchBehavior CompositeSearchBehavior; + + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 5)] + { + /// Create installed package catalog with required installed scope. + PackageInstallScope InstalledScope; + } } /// Required install scope for the package. If the package does not have an installer that @@ -773,6 +779,17 @@ namespace Microsoft.Management.Deployment Interactive, }; + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 5)] + enum PackageUninstallScope + { + /// Use default uninstall behavior. + Any, + /// Uninstall for current user. Currently only applicable to msix. + User, + /// Uninstall for all users. Currently only applicable to msix. + System, + }; + /// Options when uninstalling a package. /// Intended to allow full compatibility with the "winget uninstall" command line interface. [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 4)] @@ -797,6 +814,8 @@ namespace Microsoft.Management.Deployment { /// Force the operation to continue upon non security related failures. Boolean Force; + // The scope the uninstall will perform. Currently only applicable to msix. + PackageUninstallScope PackageUninstallScope; } } diff --git a/src/Microsoft.Management.Deployment/UninstallOptions.cpp b/src/Microsoft.Management.Deployment/UninstallOptions.cpp index 6f41ea50f8..b1b7f5621e 100644 --- a/src/Microsoft.Management.Deployment/UninstallOptions.cpp +++ b/src/Microsoft.Management.Deployment/UninstallOptions.cpp @@ -57,6 +57,14 @@ namespace winrt::Microsoft::Management::Deployment::implementation { m_force = value; } + winrt::Microsoft::Management::Deployment::PackageUninstallScope UninstallOptions::PackageUninstallScope() + { + return m_packageUninstallScope; + } + void UninstallOptions::PackageUninstallScope(winrt::Microsoft::Management::Deployment::PackageUninstallScope const& value) + { + m_packageUninstallScope = value; + } CoCreatableMicrosoftManagementDeploymentClass(UninstallOptions); } diff --git a/src/Microsoft.Management.Deployment/UninstallOptions.h b/src/Microsoft.Management.Deployment/UninstallOptions.h index d0ef68e651..e19babd7e5 100644 --- a/src/Microsoft.Management.Deployment/UninstallOptions.h +++ b/src/Microsoft.Management.Deployment/UninstallOptions.h @@ -21,6 +21,8 @@ namespace winrt::Microsoft::Management::Deployment::implementation void CorrelationData(hstring const& value); bool Force(); void Force(bool value); + winrt::Microsoft::Management::Deployment::PackageUninstallScope PackageUninstallScope(); + void PackageUninstallScope(winrt::Microsoft::Management::Deployment::PackageUninstallScope const& value); #if !defined(INCLUDE_ONLY_INTERFACE_METHODS) private: @@ -29,6 +31,7 @@ namespace winrt::Microsoft::Management::Deployment::implementation std::wstring m_logOutputPath = L""; std::wstring m_correlationData = L""; bool m_force = false; + winrt::Microsoft::Management::Deployment::PackageUninstallScope m_packageUninstallScope = winrt::Microsoft::Management::Deployment::PackageUninstallScope::Any; #endif }; }