From a0d5ccff8decf9ef4710e9b7312e72d78c228229 Mon Sep 17 00:00:00 2001 From: JohnMcPMS Date: Sat, 20 Jun 2026 14:31:22 -0700 Subject: [PATCH] Block admin uninstall of user package --- .../package-manager/winget/returnCodes.md | 2 +- src/AppInstallerCLICore/Resources.h | 1 + .../ShellExecuteInstallerHandler.cpp | 2 +- .../Workflows/UninstallFlow.cpp | 38 ++++++++++++++++--- src/AppInstallerCLIE2ETests/Constants.cs | 2 +- .../Interop/RepairInterop.cs | 2 +- src/AppInstallerCLIE2ETests/RepairCommand.cs | 2 +- .../Shared/Strings/en-us/winget.resw | 7 +++- src/AppInstallerSharedLib/Errors.cpp | 2 +- .../Public/AppInstallerErrors.h | 2 +- .../Converters.h | 4 +- .../Common/ErrorCode.cs | 4 +- .../WinGetRepairPackageException.cs | 2 +- 13 files changed, 51 insertions(+), 19 deletions(-) diff --git a/doc/windows/package-manager/winget/returnCodes.md b/doc/windows/package-manager/winget/returnCodes.md index 41f647c2f2..4b50cb1c68 100644 --- a/doc/windows/package-manager/winget/returnCodes.md +++ b/doc/windows/package-manager/winget/returnCodes.md @@ -139,7 +139,7 @@ ms.localizationpriority: low | 0x8A15007A | -1978335110 | APPINSTALLER_CLI_ERROR_REPAIR_NOT_APPLICABLE | Repair operation is not applicable. | | 0x8A15007B | -1978335109 | APPINSTALLER_CLI_ERROR_EXEC_REPAIR_FAILED | Repair operation failed. | | 0x8A15007C | -1978335108 | APPINSTALLER_CLI_ERROR_REPAIR_NOT_SUPPORTED | The installer technology in use doesn't support repair. | -| 0x8A15007D | -1978335107 | APPINSTALLER_CLI_ERROR_ADMIN_CONTEXT_REPAIR_PROHIBITED | Repair operations involving administrator privileges are not permitted on packages installed within the user scope. | +| 0x8A15007D | -1978335107 | APPINSTALLER_CLI_ERROR_ADMIN_CONTEXT_ACTION_PROHIBITED | The requested operation is not permitted from an administrator context on packages installed within the user scope. | | 0x8A15007E | -1978335106 | APPINSTALLER_CLI_ERROR_SQLITE_CONNECTION_TERMINATED | The SQLite connection was terminated to prevent corruption. | | 0x8A15007F | -1978335105 | APPINSTALLER_CLI_ERROR_DISPLAYCATALOG_API_FAILED | Failed to get Microsoft Store package catalog. | | 0x8A150080 | -1978335104 | APPINSTALLER_CLI_ERROR_NO_APPLICABLE_DISPLAYCATALOG_PACKAGE | No applicable Microsoft Store package found from Microsoft Store package catalog. | diff --git a/src/AppInstallerCLICore/Resources.h b/src/AppInstallerCLICore/Resources.h index a305f82b93..1ab1e73f3a 100644 --- a/src/AppInstallerCLICore/Resources.h +++ b/src/AppInstallerCLICore/Resources.h @@ -500,6 +500,7 @@ namespace AppInstaller::CLI::Resource WINGET_DEFINE_RESOURCE_STRINGID(NestedInstallerNotSpecified); WINGET_DEFINE_RESOURCE_STRINGID(NestedInstallerNotSupported); WINGET_DEFINE_RESOURCE_STRINGID(NoAdminRepairForUserScopePackage); + WINGET_DEFINE_RESOURCE_STRINGID(NoAdminUninstallForUserScopePackage); WINGET_DEFINE_RESOURCE_STRINGID(NoApplicableInstallers); WINGET_DEFINE_RESOURCE_STRINGID(NoExperimentalFeaturesMessage); WINGET_DEFINE_RESOURCE_STRINGID(NoInstalledFontFound); diff --git a/src/AppInstallerCLICore/Workflows/ShellExecuteInstallerHandler.cpp b/src/AppInstallerCLICore/Workflows/ShellExecuteInstallerHandler.cpp index 66871e39fa..856e6f77c2 100644 --- a/src/AppInstallerCLICore/Workflows/ShellExecuteInstallerHandler.cpp +++ b/src/AppInstallerCLICore/Workflows/ShellExecuteInstallerHandler.cpp @@ -386,7 +386,7 @@ namespace AppInstaller::CLI::Workflow if (scopeEnum == ScopeEnum::User) { context.Reporter.Error() << Resource::String::NoAdminRepairForUserScopePackage << std::endl; - AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_ADMIN_CONTEXT_REPAIR_PROHIBITED); + AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_ADMIN_CONTEXT_ACTION_PROHIBITED); } } diff --git a/src/AppInstallerCLICore/Workflows/UninstallFlow.cpp b/src/AppInstallerCLICore/Workflows/UninstallFlow.cpp index bd6e105a62..334aa77679 100644 --- a/src/AppInstallerCLICore/Workflows/UninstallFlow.cpp +++ b/src/AppInstallerCLICore/Workflows/UninstallFlow.cpp @@ -67,6 +67,21 @@ namespace AppInstaller::CLI::Workflow std::vector Items; }; + + bool AdminExecutionShouldBlockUserScopePackages(InstallerTypeEnum installerType) + { + switch (installerType) + { + case InstallerTypeEnum::Exe: + case InstallerTypeEnum::Burn: + case InstallerTypeEnum::Inno: + case InstallerTypeEnum::Nullsoft: + case InstallerTypeEnum::Portable: + return true; + default: + return false; + } + } } void UninstallSinglePackage(Execution::Context& context) @@ -214,16 +229,27 @@ namespace AppInstaller::CLI::Workflow return; } - const std::string installedTypeString = installedPackageVersion->GetMetadata()[PackageVersionMetadata::InstalledType]; - switch (ConvertToInstallerTypeEnum(installedTypeString)) + auto packageMetadata = installedPackageVersion->GetMetadata(); + auto installedType = ConvertToInstallerTypeEnum(packageMetadata[PackageVersionMetadata::InstalledType]); + + // When running as admin, block attempt to uninstall user scope package to prevent EOP paths. + if (AdminExecutionShouldBlockUserScopePackages(installedType) && Runtime::IsRunningAsAdmin()) + { + auto scopeEnum = ConvertToScopeEnum(packageMetadata[PackageVersionMetadata::InstalledScope]); + if (scopeEnum == ScopeEnum::User) + { + context.Reporter.Error() << Resource::String::NoAdminUninstallForUserScopePackage << std::endl; + AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_ADMIN_CONTEXT_ACTION_PROHIBITED); + } + } + + switch (installedType) { case InstallerTypeEnum::Exe: case InstallerTypeEnum::Burn: case InstallerTypeEnum::Inno: case InstallerTypeEnum::Nullsoft: { - IPackageVersion::Metadata packageMetadata = installedPackageVersion->GetMetadata(); - // Default to silent unless it is not present or interactivity is requested auto uninstallCommandItr = packageMetadata.find(PackageVersionMetadata::SilentUninstallCommand); if (uninstallCommandItr == packageMetadata.end() || context.Args.Contains(Execution::Args::Type::Interactive)) @@ -281,8 +307,8 @@ namespace AppInstaller::CLI::Workflow AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_NO_UNINSTALL_INFO_FOUND); } - const std::string installedScope = context.Get()->GetMetadata()[Repository::PackageVersionMetadata::InstalledScope]; - const std::string installedArch = context.Get()->GetMetadata()[Repository::PackageVersionMetadata::InstalledArchitecture]; + const std::string installedScope = packageMetadata[Repository::PackageVersionMetadata::InstalledScope]; + const std::string installedArch = packageMetadata[Repository::PackageVersionMetadata::InstalledArchitecture]; PortableInstaller portableInstaller = PortableInstaller( Manifest::ConvertToScopeEnum(installedScope), diff --git a/src/AppInstallerCLIE2ETests/Constants.cs b/src/AppInstallerCLIE2ETests/Constants.cs index ddc4911a39..db921b444d 100644 --- a/src/AppInstallerCLIE2ETests/Constants.cs +++ b/src/AppInstallerCLIE2ETests/Constants.cs @@ -279,7 +279,7 @@ public class ErrorCode public const int ERROR_NO_REPAIR_INFO_FOUND = unchecked((int)0x8A150079); public const int ERROR_REPAIR_NOT_SUPPORTED = unchecked((int)0x8A15007C); - public const int ERROR_ADMIN_CONTEXT_REPAIR_PROHIBITED = unchecked((int)0x8A15007D); + public const int ERROR_ADMIN_CONTEXT_ACTION_PROHIBITED = unchecked((int)0x8A15007D); public const int ERROR_INSTALLER_ZERO_BYTE_FILE = unchecked((int)0x8A150086); public const int ERROR_FONT_INSTALL_FAILED = unchecked((int)0x8A150087); diff --git a/src/AppInstallerCLIE2ETests/Interop/RepairInterop.cs b/src/AppInstallerCLIE2ETests/Interop/RepairInterop.cs index 7fc265dc18..f04d1228bf 100644 --- a/src/AppInstallerCLIE2ETests/Interop/RepairInterop.cs +++ b/src/AppInstallerCLIE2ETests/Interop/RepairInterop.cs @@ -167,7 +167,7 @@ public async Task RepairBurnInstallerInAdminContextWithUserScopeInstall() var repairOptions = this.TestFactory.CreateRepairOptions(); repairOptions.PackageRepairMode = PackageRepairMode.Silent; - await this.RepairPackageAndValidateStatus(searchResult.CatalogPackage, repairOptions, RepairResultStatus.RepairError, Constants.ErrorCode.ERROR_ADMIN_CONTEXT_REPAIR_PROHIBITED); + await this.RepairPackageAndValidateStatus(searchResult.CatalogPackage, repairOptions, RepairResultStatus.RepairError, Constants.ErrorCode.ERROR_ADMIN_CONTEXT_ACTION_PROHIBITED); // Cleanup TestCommon.CleanupTestExeAndDirectory(this.installDir); diff --git a/src/AppInstallerCLIE2ETests/RepairCommand.cs b/src/AppInstallerCLIE2ETests/RepairCommand.cs index 11af2bd588..e26e1ad88b 100644 --- a/src/AppInstallerCLIE2ETests/RepairCommand.cs +++ b/src/AppInstallerCLIE2ETests/RepairCommand.cs @@ -134,7 +134,7 @@ public void RepairBurnInstallerInAdminContextWithUserScopeInstall() Assert.True(result.StdOut.Contains("Successfully installed")); result = TestCommon.RunAICLICommand("repair", "AppInstallerTest.TestUserScopeInstallRepairInAdminContext"); - Assert.AreEqual(Constants.ErrorCode.ERROR_ADMIN_CONTEXT_REPAIR_PROHIBITED, result.ExitCode); + Assert.AreEqual(Constants.ErrorCode.ERROR_ADMIN_CONTEXT_ACTION_PROHIBITED, result.ExitCode); Assert.True(result.StdOut.Contains("The package installed for user scope cannot be repaired when running with administrator privileges.")); TestCommon.CleanupTestExeAndDirectory(installDir); } diff --git a/src/AppInstallerCLIPackage/Shared/Strings/en-us/winget.resw b/src/AppInstallerCLIPackage/Shared/Strings/en-us/winget.resw index 8754372ac8..5d18bcd8a9 100644 --- a/src/AppInstallerCLIPackage/Shared/Strings/en-us/winget.resw +++ b/src/AppInstallerCLIPackage/Shared/Strings/en-us/winget.resw @@ -2890,8 +2890,8 @@ Please specify one of them using the --source option to proceed. The package installed for user scope cannot be repaired when running with administrator privileges. - - Repair operations involving administrator privileges are not permitted on packages installed within the user scope. + + The requested operation is not permitted from an administrator context on packages installed within the user scope. Repair failed with exit code: {0} @@ -3649,4 +3649,7 @@ An unlocalized JSON fragment will follow on another line. The DSC processor hash provided does not match hash of the target file. + + The package installed for user scope cannot be uninstalled when running with administrator privileges. + \ No newline at end of file diff --git a/src/AppInstallerSharedLib/Errors.cpp b/src/AppInstallerSharedLib/Errors.cpp index 0e9b811e87..bd7cd45026 100644 --- a/src/AppInstallerSharedLib/Errors.cpp +++ b/src/AppInstallerSharedLib/Errors.cpp @@ -212,7 +212,7 @@ namespace AppInstaller WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_REPAIR_NOT_APPLICABLE, "Repair operation is not applicable."), WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_EXEC_REPAIR_FAILED, "Repair operation failed."), WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_REPAIR_NOT_SUPPORTED, "The installer technology in use doesn't support repair."), - WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_ADMIN_CONTEXT_REPAIR_PROHIBITED, "Repair operations involving administrator privileges are not permitted on packages installed within the user scope."), + WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_ADMIN_CONTEXT_ACTION_PROHIBITED, "The requested operation is not permitted from an administrator context on packages installed within the user scope."), WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_SQLITE_CONNECTION_TERMINATED, "The SQLite connection was terminated to prevent corruption."), WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_DISPLAYCATALOG_API_FAILED, "Failed to get Microsoft Store package catalog."), WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_NO_APPLICABLE_DISPLAYCATALOG_PACKAGE, "No applicable Microsoft Store package found from Microsoft Store package catalog."), diff --git a/src/AppInstallerSharedLib/Public/AppInstallerErrors.h b/src/AppInstallerSharedLib/Public/AppInstallerErrors.h index 6510e363ad..11744bad04 100644 --- a/src/AppInstallerSharedLib/Public/AppInstallerErrors.h +++ b/src/AppInstallerSharedLib/Public/AppInstallerErrors.h @@ -145,7 +145,7 @@ #define APPINSTALLER_CLI_ERROR_REPAIR_NOT_APPLICABLE ((HRESULT)0x8A15007A) #define APPINSTALLER_CLI_ERROR_EXEC_REPAIR_FAILED ((HRESULT)0x8A15007B) #define APPINSTALLER_CLI_ERROR_REPAIR_NOT_SUPPORTED ((HRESULT)0x8A15007C) -#define APPINSTALLER_CLI_ERROR_ADMIN_CONTEXT_REPAIR_PROHIBITED ((HRESULT)0x8A15007D) +#define APPINSTALLER_CLI_ERROR_ADMIN_CONTEXT_ACTION_PROHIBITED ((HRESULT)0x8A15007D) #define APPINSTALLER_CLI_ERROR_SQLITE_CONNECTION_TERMINATED ((HRESULT)0x8A15007E) #define APPINSTALLER_CLI_ERROR_DISPLAYCATALOG_API_FAILED ((HRESULT)0x8A15007F) #define APPINSTALLER_CLI_ERROR_NO_APPLICABLE_DISPLAYCATALOG_PACKAGE ((HRESULT)0x8A150080) diff --git a/src/Microsoft.Management.Deployment/Converters.h b/src/Microsoft.Management.Deployment/Converters.h index 643658705c..0b5d914250 100644 --- a/src/Microsoft.Management.Deployment/Converters.h +++ b/src/Microsoft.Management.Deployment/Converters.h @@ -95,9 +95,11 @@ namespace winrt::Microsoft::Management::Deployment::implementation case APPINSTALLER_CLI_ERROR_REPAIR_NOT_APPLICABLE: case APPINSTALLER_CLI_ERROR_EXEC_REPAIR_FAILED: case APPINSTALLER_CLI_ERROR_REPAIR_NOT_SUPPORTED: - case APPINSTALLER_CLI_ERROR_ADMIN_CONTEXT_REPAIR_PROHIBITED: WINGET_GET_OPERATION_RESULT_STATUS(InternalError, InternalError, InternalError, RepairError); break; + case APPINSTALLER_CLI_ERROR_ADMIN_CONTEXT_ACTION_PROHIBITED: + WINGET_GET_OPERATION_RESULT_STATUS(InternalError, UninstallError, InternalError, RepairError); + break; case APPINSTALLER_CLI_ERROR_PACKAGE_AGREEMENTS_NOT_ACCEPTED: WINGET_GET_OPERATION_RESULT_STATUS(PackageAgreementsNotAccepted, InternalError, PackageAgreementsNotAccepted, PackageAgreementsNotAccepted); break; diff --git a/src/PowerShell/Microsoft.WinGet.Client.Engine/Common/ErrorCode.cs b/src/PowerShell/Microsoft.WinGet.Client.Engine/Common/ErrorCode.cs index f5a927e49e..e3b2a65742 100644 --- a/src/PowerShell/Microsoft.WinGet.Client.Engine/Common/ErrorCode.cs +++ b/src/PowerShell/Microsoft.WinGet.Client.Engine/Common/ErrorCode.cs @@ -52,8 +52,8 @@ internal static class ErrorCode public const int RepairNotSupported = unchecked((int)0x8A15007C); /// - /// Error code for APPINSTALLER_CLI_ERROR_ADMIN_CONTEXT_REPAIR_PROHIBITED. + /// Error code for APPINSTALLER_CLI_ERROR_ADMIN_CONTEXT_ACTION_PROHIBITED. /// - public const int AdminContextRepairProhibited = unchecked((int)0x8A15007D); + public const int AdminContextActionProhibited = unchecked((int)0x8A15007D); } } diff --git a/src/PowerShell/Microsoft.WinGet.Client.Engine/Exceptions/WinGetRepairPackageException.cs b/src/PowerShell/Microsoft.WinGet.Client.Engine/Exceptions/WinGetRepairPackageException.cs index 76071d645c..7315849858 100644 --- a/src/PowerShell/Microsoft.WinGet.Client.Engine/Exceptions/WinGetRepairPackageException.cs +++ b/src/PowerShell/Microsoft.WinGet.Client.Engine/Exceptions/WinGetRepairPackageException.cs @@ -69,7 +69,7 @@ private static string GetMessage(int hresult, uint repairerExitCode = 0) return Resources.RepairOperationNotSupported; case ErrorCode.RepairNotApplicable: return Resources.RepairDifferentInstallTechnology; - case ErrorCode.AdminContextRepairProhibited: + case ErrorCode.AdminContextActionProhibited: return Resources.NoAdminRepairForUserScopePackage; default: return string.Format(Resources.UnknownRepairFailure, hresult);