winget-cli

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README | LICENSE

commit 740808ddd8a46d2da91d45eae2136bef4528377d
parent 41e741a9d9473fddf5d528aaafd90e326adf4c48
Author: JohnMcPMS <johnmcp@microsoft.com>
Date:   Thu,  5 May 2022 11:04:58 -0700

Act on elevation requirements in majority cases (#2126)

When `ElevationRequirement` is set to `elevationProhibited` is set and winget is run elevated, block the installation with an error.

When `elevatesSelf` or `elevationRequired` is set and winget is not run elevated, inform the user that an elevation prompt is expected.  Also in the case of `elevationRequired`, use the `runas` verb on ShellExecute to run the installer elevated.  This can be used on installers that will fail if they are not run elevated.
Diffstat:
M.github/actions/spelling/allow.txt | 1+
Msrc/AppInstallerCLICore/Resources.h | 2++
Msrc/AppInstallerCLICore/Workflows/InstallFlow.cpp | 8++++++++
Msrc/AppInstallerCLICore/Workflows/MsiInstallFlow.cpp | 7+++++++
Msrc/AppInstallerCLICore/Workflows/ShellExecuteInstallerHandler.cpp | 29+++++++++++++++++++++++++++--
Msrc/AppInstallerCLIPackage/Shared/Strings/en-us/winget.resw | 6++++++
Msrc/AppInstallerCommonCore/Errors.cpp | 2++
Msrc/AppInstallerCommonCore/Public/AppInstallerErrors.h | 1+
8 files changed, 54 insertions(+), 2 deletions(-)

diff --git a/.github/actions/spelling/allow.txt b/.github/actions/spelling/allow.txt @@ -443,6 +443,7 @@ Rpc rpc rubengustorage ruleset +runas runsettings runtimes safecast diff --git a/src/AppInstallerCLICore/Resources.h b/src/AppInstallerCLICore/Resources.h @@ -116,6 +116,7 @@ namespace AppInstaller::CLI::Resource WINGET_DEFINE_RESOURCE_STRINGID(InstallCommandShortDescription); WINGET_DEFINE_RESOURCE_STRINGID(InstalledPackageNotAvailable); WINGET_DEFINE_RESOURCE_STRINGID(InstalledPackageVersionNotAvailable); + WINGET_DEFINE_RESOURCE_STRINGID(InstallerElevationExpected); WINGET_DEFINE_RESOURCE_STRINGID(InstallerBlockedByPolicy); WINGET_DEFINE_RESOURCE_STRINGID(InstallerFailedSecurityCheck); WINGET_DEFINE_RESOURCE_STRINGID(InstallerFailedVirusScan); @@ -126,6 +127,7 @@ namespace AppInstaller::CLI::Resource WINGET_DEFINE_RESOURCE_STRINGID(InstallerHashMismatchOverrideRequired); WINGET_DEFINE_RESOURCE_STRINGID(InstallerHashVerified); WINGET_DEFINE_RESOURCE_STRINGID(InstallerLogAvailable); + WINGET_DEFINE_RESOURCE_STRINGID(InstallerProhibitsElevation); WINGET_DEFINE_RESOURCE_STRINGID(InstallFlowInstallSuccess); WINGET_DEFINE_RESOURCE_STRINGID(InstallFlowRegistrationDeferred); WINGET_DEFINE_RESOURCE_STRINGID(InstallFlowReturnCodeAlreadyInstalled); diff --git a/src/AppInstallerCLICore/Workflows/InstallFlow.cpp b/src/AppInstallerCLICore/Workflows/InstallFlow.cpp @@ -119,6 +119,14 @@ namespace AppInstaller::CLI::Workflow } context << EnsureSupportForInstall; + + // This installer cannot be run elevated, but we are running elevated. + // Implementation of de-elevation is complex; simply block for now. + if (installer->ElevationRequirement == ElevationRequirementEnum::ElevationProhibited && Runtime::IsRunningAsAdmin()) + { + context.Reporter.Error() << Resource::String::InstallerProhibitsElevation << std::endl; + AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_INSTALLER_PROHIBITS_ELEVATION); + } } void ShowInstallationDisclaimer(Execution::Context& context) diff --git a/src/AppInstallerCLICore/Workflows/MsiInstallFlow.cpp b/src/AppInstallerCLICore/Workflows/MsiInstallFlow.cpp @@ -32,10 +32,17 @@ namespace AppInstaller::CLI::Workflow { context.Reporter.Info() << Resource::String::InstallFlowStartingPackageInstall << std::endl; + const auto& installer = context.Get<Execution::Data::Installer>(); const std::filesystem::path& installerPath = context.Get<Execution::Data::InstallerPath>(); Msi::MsiParsedArguments parsedArgs = Msi::ParseMSIArguments(context.Get<Execution::Data::InstallerArgs>()); + // Inform of elevation requirements + if (!Runtime::IsRunningAsAdmin() && installer->ElevationRequirement == Manifest::ElevationRequirementEnum::ElevatesSelf) + { + context.Reporter.Warn() << Resource::String::InstallerElevationExpected << std::endl; + } + auto installResult = context.Reporter.ExecuteWithProgress( std::bind(InvokeMsiInstallProduct, installerPath, diff --git a/src/AppInstallerCLICore/Workflows/ShellExecuteInstallerHandler.cpp b/src/AppInstallerCLICore/Workflows/ShellExecuteInstallerHandler.cpp @@ -14,7 +14,7 @@ namespace AppInstaller::CLI::Workflow namespace { // ShellExecutes the given path. - std::optional<DWORD> InvokeShellExecute(const std::filesystem::path& filePath, const std::string& args, IProgressCallback& progress) + std::optional<DWORD> InvokeShellExecuteEx(const std::filesystem::path& filePath, const std::string& args, bool useRunAs, IProgressCallback& progress) { AICLI_LOG(CLI, Info, << "Starting: '" << filePath.u8string() << "' with arguments '" << args << '\''); @@ -28,6 +28,13 @@ namespace AppInstaller::CLI::Workflow // Verified setting to SW_SHOW does not hurt silent mode since no UI will be shown. execInfo.nShow = SW_SHOW; + // This installer must be run elevated, but we are not currently. + // Have ShellExecute elevate the installer since it won't do so itself. + if (useRunAs) + { + execInfo.lpVerb = L"runas"; + } + THROW_LAST_ERROR_IF(!ShellExecuteExW(&execInfo) || !execInfo.hProcess); wil::unique_process_handle process{ execInfo.hProcess }; @@ -58,6 +65,11 @@ namespace AppInstaller::CLI::Workflow } } + std::optional<DWORD> InvokeShellExecute(const std::filesystem::path& filePath, const std::string& args, IProgressCallback& progress) + { + return InvokeShellExecuteEx(filePath, args, false, progress); + } + // Gets the escaped installer args. std::string GetInstallerArgsTemplate(Execution::Context& context) { @@ -187,12 +199,25 @@ namespace AppInstaller::CLI::Workflow { context.Reporter.Info() << Resource::String::InstallFlowStartingPackageInstall << std::endl; + const auto& installer = context.Get<Execution::Data::Installer>(); const std::string& installerArgs = context.Get<Execution::Data::InstallerArgs>(); + // Inform of elevation requirements + bool isElevated = Runtime::IsRunningAsAdmin(); + + // The installer will run elevated, either by direct request or through the installer itself doing so. + if ((installer->ElevationRequirement == ElevationRequirementEnum::ElevationRequired || + installer->ElevationRequirement == ElevationRequirementEnum::ElevatesSelf) + && !isElevated) + { + context.Reporter.Warn() << Resource::String::InstallerElevationExpected << std::endl; + } + auto installResult = context.Reporter.ExecuteWithProgress( - std::bind(InvokeShellExecute, + std::bind(InvokeShellExecuteEx, context.Get<Execution::Data::InstallerPath>(), installerArgs, + installer->ElevationRequirement == ElevationRequirementEnum::ElevationRequired && !isElevated, std::placeholders::_1)); if (!installResult) diff --git a/src/AppInstallerCLIPackage/Shared/Strings/en-us/winget.resw b/src/AppInstallerCLIPackage/Shared/Strings/en-us/winget.resw @@ -1287,6 +1287,12 @@ Please specify one of them using the `--source` option to proceed.</value> <data name="WaitArgumentDescription" xml:space="preserve"> <value>Prompts the user to press any key before exiting</value> </data> + <data name="InstallerElevationExpected" xml:space="preserve"> + <value>The installer will request to run as administrator, expect a prompt.</value> + </data> + <data name="InstallerProhibitsElevation" xml:space="preserve"> + <value>The installer cannot be run from an administrator context.</value> + </data> <data name="ModifiedPathRequiresShellRestart" xml:space="preserve"> <value>Path environment variable modified; restart your shell to use the new value.</value> </data> diff --git a/src/AppInstallerCommonCore/Errors.cpp b/src/AppInstallerCommonCore/Errors.cpp @@ -212,6 +212,8 @@ namespace AppInstaller return "A higher version of this application is already installed."; case APPINSTALLER_CLI_ERROR_INSTALL_BLOCKED_BY_POLICY: return "Organization policies are preventing installation. Contact your admin."; + case APPINSTALLER_CLI_ERROR_INSTALLER_PROHIBITS_ELEVATION: + return "The installer cannot be run from an administrator context."; default: return "Unknown Error Code"; } diff --git a/src/AppInstallerCommonCore/Public/AppInstallerErrors.h b/src/AppInstallerCommonCore/Public/AppInstallerErrors.h @@ -98,6 +98,7 @@ #define APPINSTALLER_CLI_ERROR_PORTABLE_REPARSE_POINT_NOT_SUPPORTED ((HRESULT)0x8A150053) #define APPINSTALLER_CLI_ERROR_PORTABLE_PACKAGE_ALREADY_EXISTS ((HRESULT)0x8A150054) #define APPINSTALLER_CLI_ERROR_PORTABLE_SYMLINK_PATH_IS_DIRECTORY ((HRESULT)0x8A150055) +#define APPINSTALLER_CLI_ERROR_INSTALLER_PROHIBITS_ELEVATION ((HRESULT)0x8A150056) // Install errors. #define APPINSTALLER_CLI_ERROR_INSTALL_PACKAGE_IN_USE ((HRESULT)0x8A150101)