diff --git a/src/libraries/Common/src/Interop/Windows/Kernel32/Interop.GetProcessName.cs b/src/libraries/Common/src/Interop/Windows/Kernel32/Interop.GetProcessName.cs new file mode 100644 index 00000000000000..293a9c0ee49743 --- /dev/null +++ b/src/libraries/Common/src/Interop/Windows/Kernel32/Interop.GetProcessName.cs @@ -0,0 +1,74 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Buffers; +using System.Runtime.InteropServices; +using Microsoft.Win32.SafeHandles; + +internal static partial class Interop +{ + internal static partial class Kernel32 + { + [DllImport(Libraries.Kernel32, CharSet = CharSet.Unicode, EntryPoint = "QueryFullProcessImageNameW", ExactSpelling = true, SetLastError = true)] + private static unsafe extern bool QueryFullProcessImageName( + SafeHandle hProcess, + uint dwFlags, + char* lpBuffer, + ref uint lpdwSize); + + internal static unsafe string? GetProcessName(uint processId) + { + using (SafeProcessHandle handle = OpenProcess(Advapi32.ProcessOptions.PROCESS_QUERY_LIMITED_INFORMATION, false, (int)processId)) + { + if (handle.IsInvalid) // OpenProcess can fail + { + return null; + } + + const int StartLength = +#if DEBUG + 1; // in debug, validate ArrayPool growth +#else + Interop.Kernel32.MAX_PATH; +#endif + + Span buffer = stackalloc char[StartLength + 1]; + char[]? rentedArray = null; + + try + { + while (true) + { + uint length = (uint)buffer.Length; + fixed (char* pinnedBuffer = &MemoryMarshal.GetReference(buffer)) + { + if (QueryFullProcessImageName(handle, 0, pinnedBuffer, ref length)) + { + return buffer.Slice(0, (int)length).ToString(); + } + else if (Marshal.GetLastWin32Error() != Errors.ERROR_INSUFFICIENT_BUFFER) + { + return null; + } + } + + char[]? toReturn = rentedArray; + buffer = rentedArray = ArrayPool.Shared.Rent(buffer.Length * 2); + if (toReturn is not null) + { + ArrayPool.Shared.Return(toReturn); + } + } + } + finally + { + if (rentedArray is not null) + { + ArrayPool.Shared.Return(rentedArray); + } + } + } + } + } +} diff --git a/src/libraries/System.Diagnostics.Process/src/System.Diagnostics.Process.csproj b/src/libraries/System.Diagnostics.Process/src/System.Diagnostics.Process.csproj index 41548252d1f5e5..6e17ce297865bf 100644 --- a/src/libraries/System.Diagnostics.Process/src/System.Diagnostics.Process.csproj +++ b/src/libraries/System.Diagnostics.Process/src/System.Diagnostics.Process.csproj @@ -159,6 +159,8 @@ Link="Common\Interop\Windows\Advapi32\Interop.AdjustTokenPrivileges.cs" /> + Gets the friendly name of the process. + public string ProcessName + { + get + { + EnsureState(State.HaveProcessInfo); + return _processInfo!.ProcessName; + } + } } } diff --git a/src/libraries/System.Diagnostics.Process/src/System/Diagnostics/Process.UnknownUnix.cs b/src/libraries/System.Diagnostics.Process/src/System/Diagnostics/Process.UnknownUnix.cs index 897fde77e9a75d..c6b9c1c80d7edd 100644 --- a/src/libraries/System.Diagnostics.Process/src/System/Diagnostics/Process.UnknownUnix.cs +++ b/src/libraries/System.Diagnostics.Process/src/System/Diagnostics/Process.UnknownUnix.cs @@ -83,5 +83,15 @@ private string GetPathToOpenFile() } private int ParentProcessId => throw new PlatformNotSupportedException(); + + /// Gets the friendly name of the process. + public string ProcessName + { + get + { + EnsureState(State.HaveProcessInfo); + return _processInfo!.ProcessName; + } + } } } diff --git a/src/libraries/System.Diagnostics.Process/src/System/Diagnostics/Process.Windows.cs b/src/libraries/System.Diagnostics.Process/src/System/Diagnostics/Process.Windows.cs index ad7209a3f198d2..aea6f31d5e3163 100644 --- a/src/libraries/System.Diagnostics.Process/src/System/Diagnostics/Process.Windows.cs +++ b/src/libraries/System.Diagnostics.Process/src/System/Diagnostics/Process.Windows.cs @@ -17,6 +17,8 @@ public partial class Process : IDisposable { private static readonly object s_createProcessLock = new object(); + private string? _processName; + /// /// Creates an array of components that are associated with process resources on a /// remote computer. These process resources share the specified process name. @@ -125,6 +127,7 @@ private void RefreshCore() _haveMainWindow = false; _mainWindowTitle = null; _haveResponding = false; + _processName = null; } /// Additional logic invoked when the Process is closed. @@ -887,5 +890,35 @@ private static string GetEnvironmentVariablesBlock(IDictionary s } private static string GetErrorMessage(int error) => Interop.Kernel32.GetMessage(error); + + /// Gets the friendly name of the process. + public string ProcessName + { + get + { + if (_processName == null) + { + EnsureState(State.HaveNonExitedId); + // If we already have the name via a populated ProcessInfo + // then use that one. + if (_processInfo?.ProcessName != null) + { + _processName = _processInfo!.ProcessName; + } + else + { + // If we don't have a populated ProcessInfo, then get and cache the process name. + _processName = ProcessManager.GetProcessName(_processId, _machineName); + + if (_processName == null) + { + throw new InvalidOperationException(SR.NoProcessInfo); + } + } + } + + return _processName; + } + } } } diff --git a/src/libraries/System.Diagnostics.Process/src/System/Diagnostics/Process.cs b/src/libraries/System.Diagnostics.Process/src/System/Diagnostics/Process.cs index dab24936576a4b..f69f954b736be7 100644 --- a/src/libraries/System.Diagnostics.Process/src/System/Diagnostics/Process.cs +++ b/src/libraries/System.Diagnostics.Process/src/System/Diagnostics/Process.cs @@ -505,21 +505,6 @@ public int PrivateMemorySize } } - /// - /// - /// Gets - /// the friendly name of the process. - /// - /// - public string ProcessName - { - get - { - EnsureState(State.HaveProcessInfo); - return _processInfo!.ProcessName; - } - } - /// /// /// Gets diff --git a/src/libraries/System.Diagnostics.Process/src/System/Diagnostics/ProcessManager.Windows.cs b/src/libraries/System.Diagnostics.Process/src/System/Diagnostics/ProcessManager.Windows.cs index 103443d7f23251..ad791263cbf078 100644 --- a/src/libraries/System.Diagnostics.Process/src/System/Diagnostics/ProcessManager.Windows.cs +++ b/src/libraries/System.Diagnostics.Process/src/System/Diagnostics/ProcessManager.Windows.cs @@ -85,6 +85,39 @@ public static ProcessInfo[] GetProcessInfos(string machineName) return null; } + /// Gets the process name for the specified process ID on the specified machine. + /// The process ID. + /// The machine name. + /// The process name for the process if it could be found; otherwise, null. + public static string? GetProcessName(int processId, string machineName) + { + if (IsRemoteMachine(machineName)) + { + // remote case: we take the hit of looping through all results + ProcessInfo[] processInfos = NtProcessManager.GetProcessInfos(machineName, isRemoteMachine: true); + foreach (ProcessInfo processInfo in processInfos) + { + if (processInfo.ProcessId == processId) + { + return processInfo.ProcessName; + } + } + } + else + { + // local case: do not use performance counter and also attempt to get the matching (by pid) process only + + string? processName = Interop.Kernel32.GetProcessName((uint)processId); + if (processName is not null) + { + return NtProcessInfoHelper.GetProcessShortName(processName); + } + } + + return null; + } + + /// Gets the IDs of all processes on the specified machine. /// The machine to examine. /// An array of process IDs from the specified machine.