diff --git a/.github/actions/spelling/expect/expect.txt b/.github/actions/spelling/expect/expect.txt index c12609802..e214b370a 100644 --- a/.github/actions/spelling/expect/expect.txt +++ b/.github/actions/spelling/expect/expect.txt @@ -189,13 +189,12 @@ cacafire callee capslock CARETBLINKINGENABLED +carlos CARRIAGERETURN cascadia cassert castsi catid -carlos -zamora cazamor CBash cbegin @@ -1232,6 +1231,7 @@ KLF KLMNO KLMNOPQRST KLMNOPQRSTQQQQQ +KPRIORITY KVM langid LANGUAGELIST @@ -1803,6 +1803,7 @@ POSX POSXSCROLL POSYSCROLL ppci +PPEB ppf ppguid ppidl @@ -2850,6 +2851,7 @@ YSize YSubstantial YVIRTUALSCREEN YWalk +zamora ZCmd ZCtrl zsh diff --git a/src/cascadia/TerminalApp/TerminalPage.cpp b/src/cascadia/TerminalApp/TerminalPage.cpp index bb1a35922..92ccca4da 100644 --- a/src/cascadia/TerminalApp/TerminalPage.cpp +++ b/src/cascadia/TerminalApp/TerminalPage.cpp @@ -218,7 +218,7 @@ namespace winrt::TerminalApp::implementation _RegisterActionCallbacks(); // Hook up inbound connection event handler - TerminalConnection::ConptyConnection::NewConnection({ this, &TerminalPage::_OnNewConnection }); + ConptyConnection::NewConnection({ this, &TerminalPage::_OnNewConnection }); //Event Bindings (Early) _newTabButton.Click([weakThis{ get_weak() }](auto&&, auto&&) { @@ -2700,38 +2700,13 @@ namespace winrt::TerminalApp::implementation return _isAlwaysOnTop; } - HRESULT TerminalPage::_OnNewConnection(winrt::Microsoft::Terminal::TerminalConnection::ITerminalConnection connection) + HRESULT TerminalPage::_OnNewConnection(const ConptyConnection& connection) { // We need to be on the UI thread in order for _OpenNewTab to run successfully. // HasThreadAccess will return true if we're currently on a UI thread and false otherwise. // When we're on a COM thread, we'll need to dispatch the calls to the UI thread // and wait on it hence the locking mechanism. - if (Dispatcher().HasThreadAccess()) - { - try - { - NewTerminalArgs newTerminalArgs{}; - // TODO GH#10952: When we pass the actual commandline (or originating application), the - // settings model can choose the right settings based on command matching, or synthesize - // a profile from the registry/link settings (TODO GH#9458). - // TODO GH#9458: Get and pass the LNK/EXE filenames. - // Passing in a commandline forces GetProfileForArgs to use Base Layer instead of Default Profile; - // in the future, it can make a better decision based on the value we pull out of the process handle. - // TODO GH#5047: When we hang on to the N.T.A., try not to spawn "default... .exe" :) - newTerminalArgs.Commandline(L"default-terminal-invocation-placeholder"); - const auto profile{ _settings.GetProfileForArgs(newTerminalArgs) }; - const auto settings{ TerminalSettings::CreateWithProfile(_settings, profile, *_bindings) }; - - _CreateNewTabWithProfileAndSettings(profile, settings, connection); - - // Request a summon of this window to the foreground - _SummonWindowRequestedHandlers(*this, nullptr); - } - CATCH_RETURN(); - - return S_OK; - } - else + if (!Dispatcher().HasThreadAccess()) { til::latch latch{ 1 }; HRESULT finalVal = S_OK; @@ -2739,13 +2714,27 @@ namespace winrt::TerminalApp::implementation Dispatcher().RunAsync(CoreDispatcherPriority::Normal, [&]() { // Re-running ourselves under the dispatcher will cause us to take the first branch above. finalVal = _OnNewConnection(connection); - latch.count_down(); }); latch.wait(); return finalVal; } + + try + { + NewTerminalArgs newTerminalArgs; + newTerminalArgs.Commandline(connection.Commandline()); + const auto profile{ _settings.GetProfileForArgs(newTerminalArgs) }; + const auto settings{ TerminalSettings::CreateWithProfile(_settings, profile, *_bindings) }; + + _CreateNewTabWithProfileAndSettings(profile, settings, connection); + + // Request a summon of this window to the foreground + _SummonWindowRequestedHandlers(*this, nullptr); + return S_OK; + } + CATCH_RETURN() } // Method Description: diff --git a/src/cascadia/TerminalApp/TerminalPage.h b/src/cascadia/TerminalApp/TerminalPage.h index 6fdc88d15..e3b9f2f97 100644 --- a/src/cascadia/TerminalApp/TerminalPage.h +++ b/src/cascadia/TerminalApp/TerminalPage.h @@ -388,7 +388,7 @@ namespace winrt::TerminalApp::implementation winrt::Microsoft::Terminal::Settings::Model::Command _lastPreviewedCommand{ nullptr }; std::vector> _restorePreviewFuncs{}; - HRESULT _OnNewConnection(winrt::Microsoft::Terminal::TerminalConnection::ITerminalConnection connection); + HRESULT _OnNewConnection(const winrt::Microsoft::Terminal::TerminalConnection::ConptyConnection& connection); void _HandleToggleInboundPty(const IInspectable& sender, const Microsoft::Terminal::Settings::Model::ActionEventArgs& args); void _WindowRenamerActionClick(const IInspectable& sender, const IInspectable& eventArgs); diff --git a/src/cascadia/TerminalConnection/ConptyConnection.cpp b/src/cascadia/TerminalConnection/ConptyConnection.cpp index b6ad2ce7f..a9907f000 100644 --- a/src/cascadia/TerminalConnection/ConptyConnection.cpp +++ b/src/cascadia/TerminalConnection/ConptyConnection.cpp @@ -1,12 +1,12 @@ -// Copyright (c) Microsoft Corporation. +// Copyright (c) Microsoft Corporation. // Licensed under the MIT license. #include "pch.h" #include "ConptyConnection.h" -#include -#include +#include +#include #include "ConptyConnection.g.cpp" #include "CTerminalHandoff.h" @@ -276,24 +276,17 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation const HANDLE hClientProcess) : _initialRows{ 25 }, _initialCols{ 80 }, - _commandline{ L"" }, - _startingDirectory{ L"" }, - _startingTitle{ L"" }, - _environment{ nullptr }, - _guid{}, - _u8State{}, - _u16Str{}, - _buffer{}, _inPipe{ hIn }, _outPipe{ hOut } { THROW_IF_FAILED(ConptyPackPseudoConsole(hServerProcess, hRef, hSig, &_hPC)); - if (_guid == guid{}) - { - _guid = Utils::CreateGuid(); - } - _piClient.hProcess = hClientProcess; + + try + { + _commandline = _commandlineFromProcess(hClientProcess); + } + CATCH_LOG() } // Function Description: @@ -355,6 +348,11 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation return _guid; } + winrt::hstring ConptyConnection::Commandline() const + { + return _commandline; + } + void ConptyConnection::Start() try { @@ -560,6 +558,41 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation } CATCH_LOG() + // Returns the command line of the given process. + // Requires PROCESS_BASIC_INFORMATION | PROCESS_VM_READ privileges. + winrt::hstring ConptyConnection::_commandlineFromProcess(HANDLE process) + { + // I know MSDN documents NtQueryInformationProcess with Reserved1/2/3 + // fields, but... uh... that feels like security by obfuscation. + // .NET kindly published this struct before us. + struct PROCESS_BASIC_INFORMATION + { + NTSTATUS ExitStatus; + PPEB PebBaseAddress; + ULONG_PTR AffinityMask; + KPRIORITY BasePriority; + ULONG_PTR UniqueProcessId; + ULONG_PTR InheritedFromUniqueProcessId; + } info; + THROW_IF_NTSTATUS_FAILED(NtQueryInformationProcess(process, ProcessBasicInformation, &info, sizeof(info), nullptr)); + + // PEB: Process Environment Block + // This is a funny structure allocated by the kernel which contains all sorts of useful + // information, only a tiny fraction of which are documented publicly unfortunately. + // Fortunately however it contains a copy of the command line the process launched with. + PEB peb; + THROW_IF_WIN32_BOOL_FALSE(ReadProcessMemory(process, info.PebBaseAddress, &peb, sizeof(peb), nullptr)); + + RTL_USER_PROCESS_PARAMETERS params; + THROW_IF_WIN32_BOOL_FALSE(ReadProcessMemory(process, peb.ProcessParameters, ¶ms, sizeof(params), nullptr)); + + // Yeah I know... Don't use "impl" stuff... But why do you make something _that_ useful private? :( + // The hstring_builder allows us to create a hstring without intermediate copies. Neat! + winrt::impl::hstring_builder commandline{ params.CommandLine.Length / 2u }; + THROW_IF_WIN32_BOOL_FALSE(ReadProcessMemory(process, params.CommandLine.Buffer, commandline.data(), params.CommandLine.Length, nullptr)); + return commandline.to_hstring(); + } + DWORD ConptyConnection::_OutputThread() { // Keep us alive until the output thread terminates; the destructor @@ -636,8 +669,7 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation HRESULT ConptyConnection::NewHandoff(HANDLE in, HANDLE out, HANDLE signal, HANDLE ref, HANDLE server, HANDLE client) noexcept try { - auto conn = winrt::make(signal, in, out, ref, server, client); - _newConnectionHandlers(conn); + _newConnectionHandlers(winrt::make(signal, in, out, ref, server, client)); return S_OK; } diff --git a/src/cascadia/TerminalConnection/ConptyConnection.h b/src/cascadia/TerminalConnection/ConptyConnection.h index 9a2fc3a6e..91114377d 100644 --- a/src/cascadia/TerminalConnection/ConptyConnection.h +++ b/src/cascadia/TerminalConnection/ConptyConnection.h @@ -38,6 +38,7 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation void ClearBuffer(); winrt::guid Guid() const noexcept; + winrt::hstring Commandline() const; static void StartInboundListener(); static void StopInboundListener(); @@ -56,12 +57,13 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation WINRT_CALLBACK(TerminalOutput, TerminalOutputHandler); private: + static HRESULT NewHandoff(HANDLE in, HANDLE out, HANDLE signal, HANDLE ref, HANDLE server, HANDLE client) noexcept; + static winrt::hstring _commandlineFromProcess(HANDLE process); + HRESULT _LaunchAttachedClient() noexcept; void _indicateExitWithStatus(unsigned int status) noexcept; void _ClientTerminated() noexcept; - static HRESULT NewHandoff(HANDLE in, HANDLE out, HANDLE signal, HANDLE ref, HANDLE server, HANDLE client) noexcept; - uint32_t _initialRows{}; uint32_t _initialCols{}; hstring _commandline{}; diff --git a/src/cascadia/TerminalConnection/ConptyConnection.idl b/src/cascadia/TerminalConnection/ConptyConnection.idl index 2e6cce5c9..4c3df03ce 100644 --- a/src/cascadia/TerminalConnection/ConptyConnection.idl +++ b/src/cascadia/TerminalConnection/ConptyConnection.idl @@ -5,10 +5,13 @@ import "ITerminalConnection.idl"; namespace Microsoft.Terminal.TerminalConnection { + delegate void NewConnectionHandler(ConptyConnection connection); + [default_interface] runtimeclass ConptyConnection : ITerminalConnection { ConptyConnection(); Guid Guid { get; }; + String Commandline { get; }; void ClearBuffer(); static event NewConnectionHandler NewConnection; diff --git a/src/cascadia/TerminalConnection/ITerminalConnection.idl b/src/cascadia/TerminalConnection/ITerminalConnection.idl index 28de4f520..06137e83d 100644 --- a/src/cascadia/TerminalConnection/ITerminalConnection.idl +++ b/src/cascadia/TerminalConnection/ITerminalConnection.idl @@ -29,6 +29,4 @@ namespace Microsoft.Terminal.TerminalConnection event Windows.Foundation.TypedEventHandler StateChanged; ConnectionState State { get; }; }; - - delegate void NewConnectionHandler(ITerminalConnection connection); } diff --git a/src/cascadia/TerminalSettingsModel/CascadiaSettings.cpp b/src/cascadia/TerminalSettingsModel/CascadiaSettings.cpp index c5b214a06..e2e56b555 100644 --- a/src/cascadia/TerminalSettingsModel/CascadiaSettings.cpp +++ b/src/cascadia/TerminalSettingsModel/CascadiaSettings.cpp @@ -10,6 +10,9 @@ #include #include +#include +#include + using namespace winrt::Microsoft::Terminal; using namespace winrt::Microsoft::Terminal::Settings; using namespace winrt::Microsoft::Terminal::Settings::Model::implementation; @@ -529,9 +532,12 @@ Model::Profile CascadiaSettings::GetProfileForArgs(const Model::NewTerminalArgs& { if (newTerminalArgs) { - if (auto profile = GetProfileByName(newTerminalArgs.Profile())) + if (const auto name = newTerminalArgs.Profile(); !name.empty()) { - return profile; + if (auto profile = GetProfileByName(name)) + { + return profile; + } } if (const auto index = newTerminalArgs.ProfileIndex()) @@ -541,6 +547,14 @@ Model::Profile CascadiaSettings::GetProfileForArgs(const Model::NewTerminalArgs& return profile; } } + + if (const auto commandLine = newTerminalArgs.Commandline(); !commandLine.empty()) + { + if (auto profile = _getProfileForCommandLine(commandLine)) + { + return profile; + } + } } if constexpr (Feature_ShowProfileDefaultsInSettings::IsEnabled()) @@ -563,6 +577,179 @@ Model::Profile CascadiaSettings::GetProfileForArgs(const Model::NewTerminalArgs& } } +// The method does some crude command line matching for our console hand-off support. +// If you have hand-off enabled and start PowerShell from the start menu we might be called with +// "C:\Program Files\PowerShell\7\pwsh.exe -WorkingDirectory ~" +// This function then checks all known user profiles for one that's compatible with the commandLine. +// In this case we might have a profile with the command line +// "C:\Program Files\PowerShell\7\pwsh.exe" +// This function will then match this profile return it. +// +// If no matching profile could be found a nullptr will be returned. +Model::Profile CascadiaSettings::_getProfileForCommandLine(const winrt::hstring& commandLine) const +{ + // We're going to cache all the command lines we got, as + // _normalizeCommandLine is a relatively heavy operation. + std::call_once(_commandLinesCacheOnce, [this]() { + _commandLinesCache.reserve(_allProfiles.Size()); + + for (const auto& profile : _allProfiles) + { + if (const auto cmd = profile.Commandline(); !cmd.empty()) + { + _commandLinesCache.emplace_back(_normalizeCommandLine(cmd.c_str()), profile); + } + } + + // We're trying to find the command line with the longest common prefix below. + // Given the commandLine "foo.exe -bar -baz" and these two user profiles: + // * "foo.exe" + // * "foo.exe -bar" + // we want to choose the second one. By sorting the _commandLinesCache in a descending order + // by command line length, we can return from this function the moment we found a matching + // profile as there cannot possibly be any other profile anymore with a longer command line. + std::stable_sort(_commandLinesCache.begin(), _commandLinesCache.end(), [](const auto& lhs, const auto& rhs) { + return lhs.first.size() > rhs.first.size(); + }); + }); + + const auto needle = _normalizeCommandLine(commandLine.c_str()); + + // til::starts_with(string, prefix) will always return false if prefix.size() > string.size(). + // --> Using binary search we can safely skip all items in _commandLinesCache where .first.size() > needle.size(). + const auto end = _commandLinesCache.end(); + auto it = std::lower_bound(_commandLinesCache.begin(), end, needle, [&](const auto& lhs, const auto& rhs) { + return lhs.first.size() > rhs.size(); + }); + + // `it` is now at a position where it->first.size() <= needle.size(). + // Hopefully we'll now find a command line with matching prefix. + for (; it != end; ++it) + { + if (til::starts_with(needle, it->first)) + { + return it->second; + } + } + + return nullptr; +} + +// Given a commandLine like the following: +// * "C:\WINDOWS\System32\cmd.exe" +// * "pwsh -WorkingDirectory ~" +// * "C:\Program Files\PowerShell\7\pwsh.exe" +// * "C:\Program Files\PowerShell\7\pwsh.exe -WorkingDirectory ~" +// +// This function returns: +// * "C:\Windows\System32\cmd.exe" +// * "C:\Program Files\PowerShell\7\pwsh.exe\0-WorkingDirectory\0~" +// * "C:\Program Files\PowerShell\7\pwsh.exe" +// * "C:\Program Files\PowerShell\7\pwsh.exe\0-WorkingDirectory\0~" +// +// The resulting strings are then used for comparisons in _getProfileForCommandLine(). +// For instance a resulting string of +// "C:\Program Files\PowerShell\7\pwsh.exe" +// is considered a compatible profile with +// "C:\Program Files\PowerShell\7\pwsh.exe -WorkingDirectory ~" +// as it shares the same (normalized) prefix. +std::wstring CascadiaSettings::_normalizeCommandLine(LPCWSTR commandLine) +{ + // Turn "%SystemRoot%\System32\cmd.exe" into "C:\WINDOWS\System32\cmd.exe". + // We do this early, as environment variables might occur anywhere in the commandLine. + std::wstring normalized; + THROW_IF_FAILED(wil::ExpandEnvironmentStringsW(commandLine, normalized)); + + // One of the most important things this function does is to strip quotes. + // That way the commandLine "foo.exe -bar" and "\"foo.exe\" \"-bar\"" appear identical. + // We'll abuse CommandLineToArgvW for that as it's close to what CreateProcessW uses. + int argc = 0; + const auto argv = CommandLineToArgvW(normalized.c_str(), &argc); + THROW_LAST_ERROR_IF(!argc); + const auto argvRelease = wil::scope_exit([=]() { LocalFree(argv); }); + + // The given commandLine should start with an executable name or path. + // For instance given the following argv arrays: + // * {"C:\WINDOWS\System32\cmd.exe"} + // * {"pwsh", "-WorkingDirectory", "~"} + // * {"C:\Program", "Files\PowerShell\7\pwsh.exe"} + // ^^^^ + // Notice how there used to be a space in the path, which was split by ExpandEnvironmentStringsW(). + // CreateProcessW() supports such atrocities, so we got to do the same. + // * {"C:\Program Files\PowerShell\7\pwsh.exe", "-WorkingDirectory", "~"} + // + // This loop tries to resolve relative paths, as well as executable names in %PATH% + // into absolute paths and normalizes them. The results for the above would be: + // * "C:\Windows\System32\cmd.exe" + // * "C:\Program Files\PowerShell\7\pwsh.exe" + // * "C:\Program Files\PowerShell\7\pwsh.exe" + // * "C:\Program Files\PowerShell\7\pwsh.exe" + for (;;) + { + // CreateProcessW uses RtlGetExePath to get the lpPath for SearchPathW. + // The difference between the behavior of SearchPathW if lpPath is nullptr and what RtlGetExePath returns + // seems to be mostly whether SafeProcessSearchMode is respected and the support for relative paths. + // Windows Terminal makes the use relative paths rather impractical which is why we simply dropped the call to RtlGetExePath. + const auto status = wil::SearchPathW(nullptr, argv[0], L".exe", normalized); + + if (status == S_OK) + { + std::filesystem::path path{ std::move(normalized) }; + + // ExpandEnvironmentStringsW() might have returned a string that's not in the canonical capitalization. + // For instance %SystemRoot% is set to C:\WINDOWS on my system (ugh), even though the path is actually C:\Windows. + // We need to fix this as case-sensitive path comparisons will fail otherwise (Windows supports case-sensitive file systems). + // If we fail to resolve the path for whatever reason (pretty unlikely given that SearchPathW found it) + // we fall back to leaving the path as is. Better than throwing a random exception and making this unusable. + { + std::error_code ec; + auto canonicalPath = canonical(path, ec); + if (!ec) + { + path = std::move(canonicalPath); + } + } + + // std::filesystem::path has no way to extract the internal path. + // So about that.... I own you, computer. Give me that path. + normalized = std::move(const_cast(path.native())); + break; + } + + // If the file path couldn't be found by SearchPathW this could be the result of us being given a commandLine + // like "C:\foo bar\baz.exe -arg" which is resolved to the argv array {"C:\foo", "bar\baz.exe", "-arg"}. + // Just like CreateProcessW() we thus try to concatenate arguments until we successfully resolve a valid path. + // Of course we can only do that if we have at least 2 remaining arguments in argv. + // All other error types aren't handled at the moment. + if (argc < 2 || status != HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND)) + { + break; + } + + // As described in the comment right above, we concatenate arguments in an attempt to resolve a valid path. + // The code below turns argv from {"C:\foo", "bar\baz.exe", "-arg"} into {"C:\foo bar\baz.exe", "-arg"}. + // The code abuses the fact that CommandLineToArgvW allocates all arguments back-to-back on the heap separated by '\0'. + argv[1][-1] = L' '; + --argc; + } + + // We've (hopefully) finished resolving the path to the executable. + // We're now going to append all remaining arguments to the resulting string. + // If argv is {"C:\Program Files\PowerShell\7\pwsh.exe", "-WorkingDirectory", "~"}, + // then we'll get "C:\Program Files\PowerShell\7\pwsh.exe\0-WorkingDirectory\0~" + if (argc > 1) + { + // normalized contains a canonical form of argv[0] at this point. + // -1 allows us to include the \0 between argv[0] and argv[1] in the call to append(). + const auto beg = argv[1] - 1; + const auto lastArg = argv[argc - 1]; + const auto end = lastArg + wcslen(lastArg); + normalized.append(beg, end); + } + + return normalized; +} + // Method Description: // - Helper to get a profile given a name that could be a guid or an actual name. // Arguments: diff --git a/src/cascadia/TerminalSettingsModel/CascadiaSettings.h b/src/cascadia/TerminalSettingsModel/CascadiaSettings.h index d25e7373c..e6672e3e7 100644 --- a/src/cascadia/TerminalSettingsModel/CascadiaSettings.h +++ b/src/cascadia/TerminalSettingsModel/CascadiaSettings.h @@ -140,8 +140,10 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation private: static const std::filesystem::path& _settingsPath(); + static std::wstring _normalizeCommandLine(LPCWSTR commandLine); winrt::com_ptr _createNewProfile(const std::wstring_view& name) const; + Model::Profile _getProfileForCommandLine(const winrt::hstring& commandLine) const; void _resolveDefaultProfile() const; @@ -165,6 +167,10 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation // defterm Model::DefaultTerminal _currentDefaultTerminal{ nullptr }; + + // GetProfileForArgs cache + mutable std::once_flag _commandLinesCacheOnce; + mutable std::vector> _commandLinesCache; }; } diff --git a/src/cascadia/TerminalSettingsModel/WslDistroGenerator.cpp b/src/cascadia/TerminalSettingsModel/WslDistroGenerator.cpp index b0cfadfbd..c39fc1bfb 100644 --- a/src/cascadia/TerminalSettingsModel/WslDistroGenerator.cpp +++ b/src/cascadia/TerminalSettingsModel/WslDistroGenerator.cpp @@ -265,14 +265,12 @@ static bool getWslNames(const wil::unique_hkey& wslRootKey, std::wstring buffer; auto result = wil::AdaptFixedSizeToAllocatedResult(buffer, [&](PWSTR value, size_t valueLength, size_t* valueLengthNeededWithNull) -> HRESULT { - auto length = static_cast(valueLength); + auto length = gsl::narrow(valueLength * sizeof(wchar_t)); const auto status = RegQueryValueExW(distroKey.get(), RegKeyDistroName, 0, nullptr, reinterpret_cast(value), &length); - // length will receive the number of bytes - convert to a number of - // wchar_t's. AdaptFixedSizeToAllocatedResult will resize buffer to - // valueLengthNeededWithNull - *valueLengthNeededWithNull = (length / sizeof(wchar_t)); - // If you add one for another trailing null, then there'll actually - // be _two_ trailing nulls in the buffer. + // length will receive the number of bytes including trailing null byte. Convert to a number of wchar_t's. + // AdaptFixedSizeToAllocatedResult will then resize buffer to valueLengthNeededWithNull. + // We're rounding up to prevent infinite loops if the data isn't a REG_SZ and length isn't divisible by 2. + *valueLengthNeededWithNull = (length + sizeof(wchar_t) - 1) / sizeof(wchar_t); return status == ERROR_MORE_DATA ? S_OK : HRESULT_FROM_WIN32(status); }); diff --git a/src/host/srvinit.cpp b/src/host/srvinit.cpp index 4d17ddfe1..b91a12873 100644 --- a/src/host/srvinit.cpp +++ b/src/host/srvinit.cpp @@ -453,7 +453,7 @@ try RETURN_IF_WIN32_BOOL_FALSE(CreatePipe(outPipeTheirSide.addressof(), outPipeOurSide.addressof(), nullptr, 0)); - wil::unique_handle clientProcess{ OpenProcess(PROCESS_QUERY_INFORMATION | SYNCHRONIZE, TRUE, static_cast(connectMessage->Descriptor.Process)) }; + wil::unique_handle clientProcess{ OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ | SYNCHRONIZE, TRUE, static_cast(connectMessage->Descriptor.Process)) }; RETURN_LAST_ERROR_IF_NULL(clientProcess.get()); wil::unique_handle refHandle;