winget-cli

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

commit 75803c312eb442e03184ce0dacf197ee8f5bb432
parent 38aac878ab34d0176b34faf6946d7a0b55d4ce97
Author: JohnMcPMS <johnmcp@microsoft.com>
Date:   Thu, 20 Jun 2024 17:13:05 -0700

Launch COM server for elevated use via packaged path (#4567)

## Change
Leverage the feature that allows launching a packaged process via its
packaged executable location as long as there is a matching alias. This
better ensures that we are launching the correct process.
Diffstat:
Msrc/PowerShell/Microsoft.WinGet.Client.Engine/Common/ErrorCode.cs | 7++++++-
Msrc/PowerShell/Microsoft.WinGet.Client.Engine/Helpers/ManagementDeploymentFactory.cs | 4++--
Msrc/WinGetServer/WinGetServerManualActivation_Client.cpp | 134+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--------------
3 files changed, 119 insertions(+), 26 deletions(-)

diff --git a/src/PowerShell/Microsoft.WinGet.Client.Engine/Common/ErrorCode.cs b/src/PowerShell/Microsoft.WinGet.Client.Engine/Common/ErrorCode.cs @@ -1,4 +1,4 @@ -// ----------------------------------------------------------------------------- +// ----------------------------------------------------------------------------- // <copyright file="ErrorCode.cs" company="Microsoft Corporation"> // Copyright (c) Microsoft Corporation. Licensed under the MIT License. // </copyright> @@ -25,5 +25,10 @@ namespace Microsoft.WinGet.Client.Engine.Common /// Error code for RPC_S_CALL_FAILED. /// </summary> public const int RpcCallFailed = unchecked((int)0x800706BE); + + /// <summary> + /// Error code for ERROR_PACKAGE_NOT_REGISTERED_FOR_USER. + /// </summary> + public const int PackageNotRegisteredForUser = unchecked((int)0x80073D35); } } diff --git a/src/PowerShell/Microsoft.WinGet.Client.Engine/Helpers/ManagementDeploymentFactory.cs b/src/PowerShell/Microsoft.WinGet.Client.Engine/Helpers/ManagementDeploymentFactory.cs @@ -1,4 +1,4 @@ -// ----------------------------------------------------------------------------- +// ----------------------------------------------------------------------------- // <copyright file="ManagementDeploymentFactory.cs" company="Microsoft Corporation"> // Copyright (c) Microsoft Corporation. Licensed under the MIT License. // </copyright> @@ -179,7 +179,7 @@ namespace Microsoft.WinGet.Client.Engine.Helpers if (hr < 0) { - if (hr == ErrorCode.FileNotFound) + if (hr == ErrorCode.FileNotFound || hr == ErrorCode.PackageNotRegisteredForUser) { throw new WinGetIntegrityException(IntegrityCategory.AppInstallerNotInstalled); } diff --git a/src/WinGetServer/WinGetServerManualActivation_Client.cpp b/src/WinGetServer/WinGetServerManualActivation_Client.cpp @@ -1,6 +1,7 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. #include "WinGetServer.h" +#include "appmodel.h" #include "Utils.h" #include <wil/com.h> @@ -15,9 +16,12 @@ #include <shlobj_core.h> #ifdef USE_PROD_WINGET_SERVER -const std::wstring_view s_ServerExePath = L"Microsoft\\WindowsApps\\Microsoft.DesktopAppInstaller_8wekyb3d8bbwe\\WindowsPackageManagerServer.exe"; +const std::wstring_view s_ServerPackageFamilyName = L"Microsoft.DesktopAppInstaller_8wekyb3d8bbwe"; +const std::wstring_view s_ServerFileName = L"WindowsPackageManagerServer.exe"; #else -const std::wstring_view s_ServerExePath = L"Microsoft\\WindowsApps\\WinGetDevCLI_8wekyb3d8bbwe\\WindowsPackageManagerServerDev.exe"; +const std::wstring_view s_LocalAppDataRelativeServerExePath = L"Microsoft\\WindowsApps\\WinGetDevCLI_8wekyb3d8bbwe\\WindowsPackageManagerServerDev.exe"; +const std::wstring_view s_ServerPackageFamilyName = L"WinGetDevCLI_8wekyb3d8bbwe"; +const std::wstring_view s_ServerFileName = L"WinGetServer\\WindowsPackageManagerServer.exe"; #endif _Must_inspect_result_ @@ -35,13 +39,6 @@ void __RPC_USER MIDL_user_free(_Pre_maybenull_ _Post_invalid_ void* ptr) } } -std::filesystem::path GetKnownFolderPath(const KNOWNFOLDERID& id) -{ - wil::unique_cotaskmem_string knownFolder = nullptr; - THROW_IF_FAILED(SHGetKnownFolderPath(id, KF_FLAG_NO_ALIAS | KF_FLAG_DONT_VERIFY | KF_FLAG_NO_PACKAGE_REDIRECTION, NULL, &knownFolder)); - return knownFolder.get(); -} - struct FreeWithRpcStringFree { void operator()(RPC_CSTR* in) { RpcStringFreeA(in); } }; using UniqueRpcString = std::unique_ptr<RPC_CSTR, FreeWithRpcStringFree>; @@ -64,23 +61,112 @@ void InitializeRpcBinding() THROW_HR_IF(HRESULT_FROM_WIN32(status), status != RPC_S_OK); } -HRESULT LaunchWinGetServerWithManualActivation() +struct ServerProcessLauncher { - const std::filesystem::path& localAppDataPath = GetKnownFolderPath(FOLDERID_LocalAppData); - const std::filesystem::path& serverExePath = localAppDataPath / s_ServerExePath; - std::wstring commandLineInput = std::wstring{ serverExePath } + L" --manualActivation"; + ServerProcessLauncher() + { + try + { + m_serverExePath = GetPackageLocation(s_ServerPackageFamilyName, s_ServerFileName) / s_ServerFileName; + +#ifndef USE_PROD_WINGET_SERVER + // The feature that allows directly launching a packaged process as long as it has a matching alias + // requires a failure to trigger, and the dev package is not ACL'd to force this to happen. Attempting + // to use the other code path results in an unpackaged server, causing other issues. + // We run the product code above to ensure that it is functioning properly, but then replace it with + // the path of the alias. + m_serverExePath = GetKnownFolderPath(FOLDERID_LocalAppData) / s_LocalAppDataRelativeServerExePath; +#endif + } + catch (wil::ResultException& re) + { + m_hr = re.GetErrorCode(); + } + } + + HRESULT LaunchWinGetServerWithManualActivation() + { + RETURN_IF_FAILED(m_hr); - STARTUPINFO info = { sizeof(info) }; - wil::unique_process_information process; + std::wstring commandLineInput = std::wstring{ m_serverExePath } + L" --manualActivation"; - RETURN_LAST_ERROR_IF(!CreateProcessW(NULL, &commandLineInput[0], NULL, NULL, FALSE, 0, NULL, NULL, &info, &process)); + STARTUPINFO info = { sizeof(info) }; + wil::unique_process_information process; - // Wait for manual reset event from server before proceeding with COM activation. - wil::unique_event manualResetEvent = CreateOrOpenServerStartEvent(); - manualResetEvent.wait(10000); + RETURN_LAST_ERROR_IF(!CreateProcessW(NULL, &commandLineInput[0], NULL, NULL, FALSE, 0, NULL, NULL, &info, &process)); - return S_OK; -} + // Wait for manual reset event from server before proceeding with COM activation. + wil::unique_event manualResetEvent = CreateOrOpenServerStartEvent(); + manualResetEvent.wait(10000); + + return S_OK; + } + +private: + std::filesystem::path GetPackageLocation(std::wstring_view packageFamilyName, std::wstring_view fileName) + { + std::wstring pfn{ packageFamilyName }; + UINT32 count = 0; + std::unique_ptr<PWSTR[]> fullNames; + UINT32 bufferLength = 0; + std::unique_ptr<WCHAR[]> buffer; + std::unique_ptr<UINT32[]> properties; + + LONG result = FindPackagesByPackageFamily(pfn.c_str(), PACKAGE_FILTER_HEAD, &count, nullptr, &bufferLength, nullptr, nullptr); + THROW_WIN32_IF(result, result != ERROR_INSUFFICIENT_BUFFER); + + for (size_t i = 0; i < 10 && result == ERROR_INSUFFICIENT_BUFFER; ++i) + { + fullNames = std::make_unique<PWSTR[]>(count); + buffer = std::make_unique<WCHAR[]>(bufferLength); + properties = std::make_unique<UINT32[]>(count); + + result = FindPackagesByPackageFamily(pfn.c_str(), PACKAGE_FILTER_HEAD, &count, fullNames.get(), &bufferLength, buffer.get(), properties.get()); + } + + THROW_IF_WIN32_ERROR(result); + + for (UINT32 i = 0; i < count; ++i) + { + // Includes null terminator + UINT32 pathLength = 0; + result = GetPackagePathByFullName(fullNames[i], &pathLength, nullptr); + if (result != ERROR_INSUFFICIENT_BUFFER) + { + continue; + } + + std::wstring packagePath; + packagePath.resize(static_cast<size_t>(pathLength)); + + if (FAILED_WIN32(GetPackagePathByFullName(fullNames[i], &pathLength, &packagePath[0]))) + { + continue; + } + packagePath.resize(static_cast<size_t>(pathLength - 1), L'\0'); + + std::filesystem::path resultPath = std::move(packagePath); + std::filesystem::path exePath = resultPath / fileName; + + if (GetFileAttributesW(exePath.c_str()) != INVALID_FILE_ATTRIBUTES) + { + return resultPath; + } + } + + THROW_WIN32(ERROR_PACKAGE_NOT_REGISTERED_FOR_USER); + } + + std::filesystem::path GetKnownFolderPath(const KNOWNFOLDERID& id) + { + wil::unique_cotaskmem_string knownFolder = nullptr; + THROW_IF_FAILED(SHGetKnownFolderPath(id, KF_FLAG_NO_ALIAS | KF_FLAG_DONT_VERIFY | KF_FLAG_NO_PACKAGE_REDIRECTION, NULL, &knownFolder)); + return knownFolder.get(); + } + + std::filesystem::path m_serverExePath; + HRESULT m_hr = S_OK; +}; HRESULT CallCreateInstance(REFCLSID rclsid, REFIID riid, UINT32 flags, UINT32* bufferByteCount, BYTE** buffer) { @@ -132,10 +218,12 @@ extern "C" HRESULT WinGetServerManualActivation_CreateInstance(REFCLSID rclsid, HRESULT result = CreateComInstance(rclsid, riid, flags, out); if (FAILED(result)) { + ServerProcessLauncher launcher; + for (int i = 0; i < 3; i++) { - result = LaunchWinGetServerWithManualActivation(); - if (result == HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND)) + result = launcher.LaunchWinGetServerWithManualActivation(); + if (result == HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND) || result == HRESULT_FROM_WIN32(ERROR_PACKAGE_NOT_REGISTERED_FOR_USER)) { break; }