Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Launch COM server for elevated use via packaged path #4567

Merged
merged 2 commits into from
Jun 21, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// -----------------------------------------------------------------------------
// -----------------------------------------------------------------------------
// <copyright file="ErrorCode.cs" company="Microsoft Corporation">
// Copyright (c) Microsoft Corporation. Licensed under the MIT License.
// </copyright>
Expand All @@ -25,5 +25,10 @@ internal static class ErrorCode
/// 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);
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// -----------------------------------------------------------------------------
// -----------------------------------------------------------------------------
// <copyright file="ManagementDeploymentFactory.cs" company="Microsoft Corporation">
// Copyright (c) Microsoft Corporation. Licensed under the MIT License.
// </copyright>
Expand Down Expand Up @@ -179,7 +179,7 @@ private static T Create<T>(Type? type, in Guid iid)

if (hr < 0)
{
if (hr == ErrorCode.FileNotFound)
if (hr == ErrorCode.FileNotFound || hr == ErrorCode.PackageNotRegisteredForUser)
{
throw new WinGetIntegrityException(IntegrityCategory.AppInstallerNotInstalled);
}
Expand Down
134 changes: 111 additions & 23 deletions src/WinGetServer/WinGetServerManualActivation_Client.cpp
Original file line number Diff line number Diff line change
@@ -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>
Expand All @@ -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_
Expand All @@ -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>;

Expand All @@ -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)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this for "in case a new package is added between our calls"?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is defensive, yes.

{
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)
{
Expand Down Expand Up @@ -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;
}
Expand Down
Loading