Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
4f3f950
Initial plan
Copilot Feb 11, 2026
92de299
Add ProcessStartOptions class with path resolution and tests
Copilot Feb 11, 2026
0c9d3e2
Fix build errors and test failures in ProcessStartOptions
Copilot Feb 11, 2026
794a8a2
Improve code comments based on review feedback
Copilot Feb 11, 2026
a1b3426
Fix exception documentation and add caching comment
Copilot Feb 11, 2026
10ff95a
Address PR feedback: use Process.ResolvePath, fix tests, add null checks
Copilot Feb 11, 2026
fc0fd30
Refactor: ProcessStartOptions.ResolvePath as main impl, Process calls it
Copilot Feb 11, 2026
f687eb8
address my own feedback
adamsitnik Feb 11, 2026
5459ec9
Simplify Windows tests: remove ConditionalFact, fix nested try-finally
Copilot Feb 11, 2026
c3517ee
Refactor tests: partial classes, remove Test prefix, use new() syntax…
Copilot Feb 12, 2026
bbea91e
Fix test conditions: remove redundant IsWindows check, use GetFullPat…
Copilot Feb 12, 2026
d9d381f
Fix test failures: use ResolveTarget helper for symlinks, fix lambda …
Copilot Feb 12, 2026
b4d6e64
Fix Unix test failures: use EndsWith instead of exact path comparison
Copilot Feb 12, 2026
98d4201
Address code review: add file check, fix .exe logic, update comments,…
Copilot Feb 12, 2026
5b024d4
Cache Windows directory value for performance
Copilot Feb 12, 2026
add4af6
Remove DictionaryWrapper and fix Unix path test for macOS symlinks
Copilot Feb 12, 2026
b3939ad
Use Environment.SystemDirectory and fix Unix path test for macOS
Copilot Feb 13, 2026
63e7fc0
Inline GetSystemDirectory, fix Unix path test, use EndsWith for util …
Copilot Feb 13, 2026
9ceaad1
Update src/libraries/System.Diagnostics.Process/src/System.Diagnostic…
adamsitnik Feb 13, 2026
82e2cec
fix the ResolvePath_UsesCurrentDirectory test
adamsitnik Feb 13, 2026
0f37ab7
Merge remote-tracking branch 'origin/main' into copilot/add-processst…
adamsitnik Feb 13, 2026
27ad99b
Initial plan
Copilot Feb 13, 2026
90ba788
Add ProcessExitStatus type, Windows interop files, and ProcessUtils
Copilot Feb 13, 2026
23a457a
Add SafeProcessHandle new APIs, ProcessExitStatus, interop definition…
Copilot Feb 13, 2026
3017731
Add test files for SafeProcessHandle APIs
Copilot Feb 13, 2026
fb6e0a4
Merge remote-tracking branch 'origin/main' into copilot/add-new-apis-…
adamsitnik Feb 13, 2026
51bee9d
Address review feedback: consolidate interop files, reuse existing AP…
Copilot Feb 13, 2026
2e5f817
fix the build
adamsitnik Feb 13, 2026
e65c2bf
Fix bugs found during code review: remove CloseHandle on pseudo-handl…
Copilot Feb 16, 2026
5a26663
Add ReaderWriterLockSlim to prevent accidental handle inheritance bet…
Copilot Feb 16, 2026
180fc9b
Address jkotas feedback: simplify handle checks, fix OOM safety, fold…
Copilot Feb 17, 2026
5271b03
Merge remote-tracking branch 'origin/main' into copilot/add-new-apis-…
adamsitnik Feb 18, 2026
febd0a6
cleanup
adamsitnik Feb 18, 2026
7973122
Address round 4 review feedback: Resume protection, NativeMemory.Allo…
Copilot Feb 18, 2026
8f3c12e
Remove leftover blank line in Process.cs and add test for starting pr…
Copilot Feb 18, 2026
adeeb03
Use typed pointers instead of IntPtr for NativeMemory.Alloc results
Copilot Feb 18, 2026
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
Expand Up @@ -44,6 +44,7 @@ internal static partial class StartupInfoOptions
internal const int CREATE_UNICODE_ENVIRONMENT = 0x00000400;
internal const int CREATE_NO_WINDOW = 0x08000000;
internal const int CREATE_NEW_PROCESS_GROUP = 0x00000200;
internal const int CREATE_SUSPENDED = 0x00000004;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,5 +16,9 @@ internal static partial class Kernel32
[LibraryImport(Libraries.Kernel32, SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
internal static unsafe partial bool SetConsoleCtrlHandler(delegate* unmanaged<int, BOOL> handler, [MarshalAs(UnmanagedType.Bool)] bool Add);

[LibraryImport(Libraries.Kernel32, SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
internal static partial bool GenerateConsoleCtrlEvent(int dwCtrlEvent, int dwProcessGroupId);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,5 +20,13 @@ internal enum HandleFlags : uint
[LibraryImport(Libraries.Kernel32, SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
internal static partial bool SetHandleInformation(SafeHandle hObject, HandleFlags dwMask, HandleFlags dwFlags);

[LibraryImport(Libraries.Kernel32, SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
internal static partial bool GetHandleInformation(IntPtr hObject, out int lpdwFlags);

[LibraryImport(Libraries.Kernel32, SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
internal static partial bool SetHandleInformation(IntPtr hObject, int dwMask, int dwFlags);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ internal static partial class HandleOptions
internal const int DUPLICATE_SAME_ACCESS = 2;
internal const int STILL_ACTIVE = 0x00000103;
internal const int TOKEN_ADJUST_PRIVILEGES = 0x20;
internal const int HANDLE_FLAG_INHERIT = 0x00000001;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
// 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.Runtime.InteropServices;

internal static partial class Interop
{
internal static partial class Kernel32
{
[LibraryImport(Libraries.Kernel32, SetLastError = true)]
internal static partial IntPtr CreateJobObjectW(IntPtr lpJobAttributes, IntPtr lpName);

internal const uint JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE = 0x00002000;

internal enum JOBOBJECTINFOCLASS
{
JobObjectBasicLimitInformation = 2,
JobObjectExtendedLimitInformation = 9
}

[StructLayout(LayoutKind.Sequential)]
internal struct IO_COUNTERS
{
internal ulong ReadOperationCount;
internal ulong WriteOperationCount;
internal ulong OtherOperationCount;
internal ulong ReadTransferCount;
internal ulong WriteTransferCount;
internal ulong OtherTransferCount;
}

[StructLayout(LayoutKind.Sequential)]
internal struct JOBOBJECT_BASIC_LIMIT_INFORMATION
{
internal long PerProcessUserTimeLimit;
internal long PerJobUserTimeLimit;
internal uint LimitFlags;
internal UIntPtr MinimumWorkingSetSize;
internal UIntPtr MaximumWorkingSetSize;
internal uint ActiveProcessLimit;
internal UIntPtr Affinity;
internal uint PriorityClass;
internal uint SchedulingClass;
}

[StructLayout(LayoutKind.Sequential)]
internal struct JOBOBJECT_EXTENDED_LIMIT_INFORMATION
{
internal JOBOBJECT_BASIC_LIMIT_INFORMATION BasicLimitInformation;
internal IO_COUNTERS IoInfo;
internal UIntPtr ProcessMemoryLimit;
internal UIntPtr JobMemoryLimit;
internal UIntPtr PeakProcessMemoryUsed;
internal UIntPtr PeakJobMemoryUsed;
}

[LibraryImport(Libraries.Kernel32, SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
internal static partial bool SetInformationJobObject(IntPtr hJob, JOBOBJECTINFOCLASS JobObjectInfoClass, ref JOBOBJECT_EXTENDED_LIMIT_INFORMATION lpJobObjectInfo, uint cbJobObjectInfoLength);

[LibraryImport(Libraries.Kernel32, SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
internal static partial bool TerminateJobObject(IntPtr hJob, uint uExitCode);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
// 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.Runtime.InteropServices;

internal static partial class Interop
{
internal static partial class Kernel32
{
internal const int PROC_THREAD_ATTRIBUTE_HANDLE_LIST = 0x00020002;
internal const int PROC_THREAD_ATTRIBUTE_JOB_LIST = 0x0002000D;
internal const int EXTENDED_STARTUPINFO_PRESENT = 0x00080000;

[StructLayout(LayoutKind.Sequential)]
internal struct STARTUPINFOEX
{
internal STARTUPINFO StartupInfo;
internal LPPROC_THREAD_ATTRIBUTE_LIST lpAttributeList;
}

internal struct LPPROC_THREAD_ATTRIBUTE_LIST
{
internal IntPtr AttributeList;
}

[LibraryImport(Libraries.Kernel32, EntryPoint = "CreateProcessW", SetLastError = true, StringMarshalling = StringMarshalling.Utf16)]
[return: MarshalAs(UnmanagedType.Bool)]
internal static unsafe partial bool CreateProcess(
char* lpApplicationName,
char* lpCommandLine,
ref SECURITY_ATTRIBUTES procSecAttrs,
ref SECURITY_ATTRIBUTES threadSecAttrs,
[MarshalAs(UnmanagedType.Bool)] bool bInheritHandles,
int dwCreationFlags,
char* lpEnvironment,
string? lpCurrentDirectory,
ref STARTUPINFOEX lpStartupInfo,
ref PROCESS_INFORMATION lpProcessInformation
);

[LibraryImport(Libraries.Kernel32, SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
internal static unsafe partial bool InitializeProcThreadAttributeList(
LPPROC_THREAD_ATTRIBUTE_LIST lpAttributeList,
int dwAttributeCount,
int dwFlags,
ref IntPtr lpSize);

[LibraryImport(Libraries.Kernel32, SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
internal static unsafe partial bool UpdateProcThreadAttribute(
LPPROC_THREAD_ATTRIBUTE_LIST lpAttributeList,
int dwFlags,
IntPtr attribute,
void* lpValue,
IntPtr cbSize,
void* lpPreviousValue,
IntPtr lpReturnSize);

[LibraryImport(Libraries.Kernel32, SetLastError = true)]
internal static unsafe partial void DeleteProcThreadAttributeList(LPPROC_THREAD_ATTRIBUTE_LIST lpAttributeList);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
// 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.Runtime.InteropServices;

internal static partial class Interop
{
internal static partial class Kernel32
{
[LibraryImport(Libraries.Kernel32, SetLastError = true)]
internal static partial int ResumeThread(IntPtr hThread);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,21 @@ public sealed partial class SafeProcessHandle : Microsoft.Win32.SafeHandles.Safe
{
public SafeProcessHandle() : base (default(bool)) { }
public SafeProcessHandle(System.IntPtr existingHandle, bool ownsHandle) : base (default(bool)) { }
public int ProcessId { get { throw null; } }
public bool Kill() { throw null; }
public bool KillProcessGroup() { throw null; }
public static Microsoft.Win32.SafeHandles.SafeProcessHandle Open(int processId) { throw null; }
protected override bool ReleaseHandle() { throw null; }
public void Resume() { }
public void Signal(System.Runtime.InteropServices.PosixSignal signal) { }
public void SignalProcessGroup(System.Runtime.InteropServices.PosixSignal signal) { }
public static Microsoft.Win32.SafeHandles.SafeProcessHandle Start(System.Diagnostics.ProcessStartOptions options, Microsoft.Win32.SafeHandles.SafeFileHandle? input, Microsoft.Win32.SafeHandles.SafeFileHandle? output, Microsoft.Win32.SafeHandles.SafeFileHandle? error) { throw null; }
public static Microsoft.Win32.SafeHandles.SafeProcessHandle StartSuspended(System.Diagnostics.ProcessStartOptions options, Microsoft.Win32.SafeHandles.SafeFileHandle? input, Microsoft.Win32.SafeHandles.SafeFileHandle? output, Microsoft.Win32.SafeHandles.SafeFileHandle? error) { throw null; }
public bool TryWaitForExit(System.TimeSpan timeout, [System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] out System.Diagnostics.ProcessExitStatus? exitStatus) { throw null; }
public System.Diagnostics.ProcessExitStatus WaitForExit() { throw null; }
public System.Threading.Tasks.Task<System.Diagnostics.ProcessExitStatus> WaitForExitAsync(System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
public System.Threading.Tasks.Task<System.Diagnostics.ProcessExitStatus> WaitForExitOrKillOnCancellationAsync(System.Threading.CancellationToken cancellationToken) { throw null; }
public System.Diagnostics.ProcessExitStatus WaitForExitOrKillOnTimeout(System.TimeSpan timeout) { throw null; }
}
}
namespace System.Diagnostics
Expand Down
Original file line number Diff line number Diff line change
@@ -1,17 +1,14 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

/*============================================================
**
** Class: SafeProcessHandle
**
** A wrapper for a process handle
**
**
===========================================================*/

using System;
using System.ComponentModel;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;

namespace Microsoft.Win32.SafeHandles
{
Expand All @@ -35,16 +32,34 @@ internal SafeProcessHandle(int processId, SafeWaitHandle handle) :
handle.DangerousAddRef(ref _releaseRef);
}

internal int ProcessId { get; }

protected override bool ReleaseHandle()
{
if (_releaseRef)
{
Debug.Assert(_handle != null);
Debug.Assert(_handle is not null);
_handle.DangerousRelease();
}
return true;
}

private static SafeProcessHandle OpenCore(int processId) => throw new NotImplementedException();

private static SafeProcessHandle StartCore(ProcessStartOptions options, SafeFileHandle inputHandle, SafeFileHandle outputHandle, SafeFileHandle errorHandle, bool createSuspended) => throw new NotImplementedException();

private ProcessExitStatus WaitForExitCore() => throw new NotImplementedException();

private bool TryWaitForExitCore(int milliseconds, [NotNullWhen(true)] out ProcessExitStatus? exitStatus) => throw new NotImplementedException();

private ProcessExitStatus WaitForExitOrKillOnTimeoutCore(int milliseconds) => throw new NotImplementedException();

private Task<ProcessExitStatus> WaitForExitAsyncCore(CancellationToken cancellationToken) => throw new NotImplementedException();

private Task<ProcessExitStatus> WaitForExitOrKillOnCancellationAsyncCore(CancellationToken cancellationToken) => throw new NotImplementedException();

internal bool KillCore(bool throwOnError, bool entireProcessGroup = false) => throw new NotImplementedException();

private void ResumeCore() => throw new NotImplementedException();

private void SendSignalCore(PosixSignal signal, bool entireProcessGroup) => throw new NotImplementedException();
Comment on lines +45 to +63
Copy link

Copilot AI Feb 18, 2026

Choose a reason for hiding this comment

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

Unix implementations currently throw NotImplementedException for the new SafeProcessHandle APIs. For a public, cross-platform type, consider throwing PlatformNotSupportedException (or adding SupportedOSPlatform attributes) until Unix support is implemented, to avoid signaling an incomplete runtime implementation to callers in shipping builds.

Suggested change
private static SafeProcessHandle OpenCore(int processId) => throw new NotImplementedException();
private static SafeProcessHandle StartCore(ProcessStartOptions options, SafeFileHandle inputHandle, SafeFileHandle outputHandle, SafeFileHandle errorHandle, bool createSuspended) => throw new NotImplementedException();
private ProcessExitStatus WaitForExitCore() => throw new NotImplementedException();
private bool TryWaitForExitCore(int milliseconds, [NotNullWhen(true)] out ProcessExitStatus? exitStatus) => throw new NotImplementedException();
private ProcessExitStatus WaitForExitOrKillOnTimeoutCore(int milliseconds) => throw new NotImplementedException();
private Task<ProcessExitStatus> WaitForExitAsyncCore(CancellationToken cancellationToken) => throw new NotImplementedException();
private Task<ProcessExitStatus> WaitForExitOrKillOnCancellationAsyncCore(CancellationToken cancellationToken) => throw new NotImplementedException();
internal bool KillCore(bool throwOnError, bool entireProcessGroup = false) => throw new NotImplementedException();
private void ResumeCore() => throw new NotImplementedException();
private void SendSignalCore(PosixSignal signal, bool entireProcessGroup) => throw new NotImplementedException();
private static SafeProcessHandle OpenCore(int processId) => throw new PlatformNotSupportedException();
private static SafeProcessHandle StartCore(ProcessStartOptions options, SafeFileHandle inputHandle, SafeFileHandle outputHandle, SafeFileHandle errorHandle, bool createSuspended) => throw new PlatformNotSupportedException();
private ProcessExitStatus WaitForExitCore() => throw new PlatformNotSupportedException();
private bool TryWaitForExitCore(int milliseconds, [NotNullWhen(true)] out ProcessExitStatus? exitStatus) => throw new PlatformNotSupportedException();
private ProcessExitStatus WaitForExitOrKillOnTimeoutCore(int milliseconds) => throw new PlatformNotSupportedException();
private Task<ProcessExitStatus> WaitForExitAsyncCore(CancellationToken cancellationToken) => throw new PlatformNotSupportedException();
private Task<ProcessExitStatus> WaitForExitOrKillOnCancellationAsyncCore(CancellationToken cancellationToken) => throw new PlatformNotSupportedException();
internal bool KillCore(bool throwOnError, bool entireProcessGroup = false) => throw new PlatformNotSupportedException();
private void ResumeCore() => throw new PlatformNotSupportedException();
private void SendSignalCore(PosixSignal signal, bool entireProcessGroup) => throw new PlatformNotSupportedException();

Copilot uses AI. Check for mistakes.
}
}
Loading
Loading