using System.Text;
|
|
using System.Threading;
|
|
using System.Runtime.InteropServices;
|
|
using System.ComponentModel;
|
|
using System.ComponentModel.Design;
|
|
using System.Runtime.CompilerServices;
|
|
using System.Diagnostics;
|
|
using System;
|
|
using System.Collections;
|
|
using System.Collections.Generic;
|
|
using System.IO;
|
|
using Microsoft.Win32.SafeHandles;
|
|
using System.Collections.Specialized;
|
|
using System.Globalization;
|
|
using System.Security;
|
|
using System.Security.Permissions;
|
|
using System.Runtime.Versioning;
|
|
using System.Runtime.ConstrainedExecution;
|
|
using Microsoft.Win32;
|
|
|
|
namespace TrustedUninstaller.Shared
|
|
{
|
|
[DefaultEvent("Exited"), DefaultProperty("StartInfo"), HostProtection(SharedState = true, Synchronization = true, ExternalProcessMgmt = true, SelfAffectingProcessMgmt = true)]
|
|
public static class AugmentedProcess
|
|
{
|
|
public class Process : Component
|
|
{
|
|
public enum CreateType {
|
|
UserToken,
|
|
RawToken
|
|
}
|
|
//
|
|
// FIELDS
|
|
//
|
|
bool haveProcessId;
|
|
int processId;
|
|
bool haveProcessHandle;
|
|
SafeProcessHandle m_processHandle;
|
|
bool isRemoteMachine;
|
|
string machineName;
|
|
ProcessInfo processInfo;
|
|
Int32 m_processAccess;
|
|
ProcessThreadCollection threads;
|
|
ProcessModuleCollection modules;
|
|
bool haveMainWindow;
|
|
IntPtr mainWindowHandle; // no need to use SafeHandle for window
|
|
string mainWindowTitle;
|
|
bool haveWorkingSetLimits;
|
|
bool haveProcessorAffinity;
|
|
IntPtr processorAffinity;
|
|
bool havePriorityClass;
|
|
ProcessPriorityClass priorityClass;
|
|
ProcessStartInfo startInfo;
|
|
bool watchForExit;
|
|
bool watchingForExit;
|
|
EventHandler onExited;
|
|
bool exited;
|
|
int exitCode;
|
|
bool signaled;
|
|
DateTime exitTime;
|
|
bool haveExitTime;
|
|
bool responding;
|
|
bool haveResponding;
|
|
bool priorityBoostEnabled;
|
|
bool havePriorityBoostEnabled;
|
|
bool raisedOnExited;
|
|
bool expandEnvironmentVariables;
|
|
RegisteredWaitHandle registeredWaitHandle;
|
|
WaitHandle waitHandle;
|
|
ISynchronizeInvoke synchronizingObject;
|
|
StreamReader standardOutput;
|
|
StreamWriter standardInput;
|
|
StreamReader standardError;
|
|
OperatingSystem operatingSystem;
|
|
bool disposed;
|
|
static object s_CreateProcessLock = new object();
|
|
|
|
// This enum defines the operation mode for redirected process stream.
|
|
// We don't support switching between synchronous mode and asynchronous mode.
|
|
private enum StreamReadMode
|
|
{
|
|
undefined,
|
|
syncMode,
|
|
asyncMode
|
|
}
|
|
|
|
StreamReadMode outputStreamReadMode;
|
|
StreamReadMode errorStreamReadMode;
|
|
public event DataReceivedEventHandler OutputDataReceived;
|
|
public event DataReceivedEventHandler ErrorDataReceived;
|
|
// Abstract the stream details
|
|
internal AsyncStreamReader output;
|
|
internal AsyncStreamReader error;
|
|
internal bool pendingOutputRead;
|
|
internal bool pendingErrorRead;
|
|
internal static TraceSwitch processTracing = null;
|
|
public Process()
|
|
{
|
|
this.machineName = ".";
|
|
this.outputStreamReadMode = StreamReadMode.undefined;
|
|
this.errorStreamReadMode = StreamReadMode.undefined;
|
|
this.m_processAccess = NativeMethods.PROCESS_ALL_ACCESS;
|
|
}
|
|
[ResourceExposure(ResourceScope.Machine)]
|
|
Process(string machineName, bool isRemoteMachine, int processId, ProcessInfo processInfo) : base()
|
|
{
|
|
this.processInfo = processInfo;
|
|
this.machineName = machineName;
|
|
this.isRemoteMachine = isRemoteMachine;
|
|
this.processId = processId;
|
|
this.haveProcessId = true;
|
|
this.outputStreamReadMode = StreamReadMode.undefined;
|
|
this.errorStreamReadMode = StreamReadMode.undefined;
|
|
this.m_processAccess = NativeMethods.PROCESS_ALL_ACCESS;
|
|
}
|
|
|
|
//
|
|
// PROPERTIES
|
|
//
|
|
bool Associated
|
|
{
|
|
get
|
|
{
|
|
return haveProcessId || haveProcessHandle;
|
|
}
|
|
}
|
|
|
|
public string ProcessName
|
|
{
|
|
get
|
|
{
|
|
this.EnsureState(Process.State.HaveProcessInfo);
|
|
return this.processInfo.processName;
|
|
}
|
|
}
|
|
|
|
public int ExitCode
|
|
{
|
|
get
|
|
{
|
|
EnsureState(State.Exited);
|
|
return exitCode;
|
|
}
|
|
}
|
|
public bool HasExited
|
|
{
|
|
get
|
|
{
|
|
if (!exited)
|
|
{
|
|
EnsureState(State.Associated);
|
|
SafeProcessHandle handle = null;
|
|
try
|
|
{
|
|
handle = GetProcessHandle(NativeMethods.PROCESS_QUERY_INFORMATION | NativeMethods.SYNCHRONIZE, false);
|
|
if (handle.IsInvalid)
|
|
{
|
|
exited = true;
|
|
}
|
|
else
|
|
{
|
|
int exitCode;
|
|
|
|
// Although this is the wrong way to check whether the process has exited,
|
|
// it was historically the way we checked for it, and a lot of code then took a dependency on
|
|
// the fact that this would always be set before the pipes were closed, so they would read
|
|
// the exit code out after calling ReadToEnd() or standard output or standard error. In order
|
|
// to allow 259 to function as a valid exit code and to break as few people as possible that
|
|
// took the ReadToEnd dependency, we check for an exit code before doing the more correct
|
|
// check to see if we have been signalled.
|
|
if (NativeMethods.GetExitCodeProcess(handle, out exitCode) && exitCode != NativeMethods.STILL_ACTIVE)
|
|
{
|
|
this.exited = true;
|
|
this.exitCode = exitCode;
|
|
}
|
|
else
|
|
{
|
|
// The best check for exit is that the kernel process object handle is invalid,
|
|
// or that it is valid and signaled. Checking if the exit code != STILL_ACTIVE
|
|
// does not guarantee the process is closed,
|
|
// since some process could return an actual STILL_ACTIVE exit code (259).
|
|
if (!signaled) // if we just came from WaitForExit, don't repeat
|
|
{
|
|
ProcessWaitHandle wh = null;
|
|
try
|
|
{
|
|
wh = new ProcessWaitHandle(handle);
|
|
this.signaled = wh.WaitOne(0, false);
|
|
}
|
|
finally
|
|
{
|
|
if (wh != null) wh.Close();
|
|
}
|
|
}
|
|
if (signaled)
|
|
{
|
|
if (!NativeMethods.GetExitCodeProcess(handle, out exitCode)) throw new Win32Exception();
|
|
this.exited = true;
|
|
this.exitCode = exitCode;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
finally
|
|
{
|
|
ReleaseProcessHandle(handle);
|
|
}
|
|
if (exited)
|
|
{
|
|
RaiseOnExited();
|
|
}
|
|
}
|
|
return exited;
|
|
}
|
|
}
|
|
public IntPtr Handle
|
|
{
|
|
[ResourceExposure(ResourceScope.Machine)]
|
|
[ResourceConsumption(ResourceScope.Machine)]
|
|
get
|
|
{
|
|
EnsureState(State.Associated);
|
|
return OpenProcessHandle(this.m_processAccess).DangerousGetHandle();
|
|
}
|
|
}
|
|
[Browsable(false), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
|
|
public SafeProcessHandle SafeHandle
|
|
{
|
|
get
|
|
{
|
|
EnsureState(State.Associated);
|
|
return OpenProcessHandle(this.m_processAccess);
|
|
}
|
|
}
|
|
public int Id
|
|
{
|
|
get
|
|
{
|
|
EnsureState(State.HaveId);
|
|
return processId;
|
|
}
|
|
}
|
|
public string MachineName
|
|
{
|
|
get
|
|
{
|
|
EnsureState(State.Associated);
|
|
return machineName;
|
|
}
|
|
}
|
|
[System.Runtime.InteropServices.ComVisible(false)]
|
|
public long NonpagedSystemMemorySize64
|
|
{
|
|
get
|
|
{
|
|
EnsureState(State.HaveNtProcessInfo);
|
|
return processInfo.poolNonpagedBytes;
|
|
}
|
|
}
|
|
[System.Runtime.InteropServices.ComVisible(false)]
|
|
public long PagedMemorySize64
|
|
{
|
|
get
|
|
{
|
|
EnsureState(State.HaveNtProcessInfo);
|
|
return processInfo.pageFileBytes;
|
|
}
|
|
}
|
|
[System.Runtime.InteropServices.ComVisible(false)]
|
|
public long PagedSystemMemorySize64
|
|
{
|
|
get
|
|
{
|
|
EnsureState(State.HaveNtProcessInfo);
|
|
return processInfo.poolPagedBytes;
|
|
}
|
|
}
|
|
[System.Runtime.InteropServices.ComVisible(false)]
|
|
public long PeakPagedMemorySize64
|
|
{
|
|
get
|
|
{
|
|
EnsureState(State.HaveNtProcessInfo);
|
|
return processInfo.pageFileBytesPeak;
|
|
}
|
|
}
|
|
[System.Runtime.InteropServices.ComVisible(false)]
|
|
public long PeakWorkingSet64
|
|
{
|
|
get
|
|
{
|
|
EnsureState(State.HaveNtProcessInfo);
|
|
return processInfo.workingSetPeak;
|
|
}
|
|
}
|
|
[System.Runtime.InteropServices.ComVisible(false)]
|
|
public long PeakVirtualMemorySize64
|
|
{
|
|
get
|
|
{
|
|
EnsureState(State.HaveNtProcessInfo);
|
|
return processInfo.virtualBytesPeak;
|
|
}
|
|
}
|
|
private OperatingSystem OperatingSystem
|
|
{
|
|
get
|
|
{
|
|
if (operatingSystem == null)
|
|
{
|
|
operatingSystem = Environment.OSVersion;
|
|
}
|
|
return operatingSystem;
|
|
}
|
|
}
|
|
[System.Runtime.InteropServices.ComVisible(false)]
|
|
public long PrivateMemorySize64
|
|
{
|
|
get
|
|
{
|
|
EnsureState(State.HaveNtProcessInfo);
|
|
return processInfo.privateBytes;
|
|
}
|
|
}
|
|
public int SessionId
|
|
{
|
|
get
|
|
{
|
|
EnsureState(State.HaveNtProcessInfo);
|
|
return processInfo.sessionId;
|
|
}
|
|
}
|
|
public ProcessStartInfo StartInfo
|
|
{
|
|
get
|
|
{
|
|
return startInfo;
|
|
}
|
|
[ResourceExposure(ResourceScope.Machine)]
|
|
set
|
|
{
|
|
if (value == null)
|
|
{
|
|
throw new ArgumentNullException("value");
|
|
}
|
|
startInfo = value;
|
|
}
|
|
}
|
|
|
|
public bool ExpandEnvironmentVariables
|
|
{
|
|
get
|
|
{
|
|
return expandEnvironmentVariables;
|
|
}
|
|
set
|
|
{
|
|
expandEnvironmentVariables = value;
|
|
}
|
|
}
|
|
|
|
public ISynchronizeInvoke SynchronizingObject
|
|
{
|
|
get
|
|
{
|
|
if (this.synchronizingObject == null && DesignMode)
|
|
{
|
|
IDesignerHost host = (IDesignerHost)GetService(typeof(IDesignerHost));
|
|
if (host != null)
|
|
{
|
|
object baseComponent = host.RootComponent;
|
|
if (baseComponent != null && baseComponent is ISynchronizeInvoke) this.synchronizingObject = (ISynchronizeInvoke)baseComponent;
|
|
}
|
|
}
|
|
return this.synchronizingObject;
|
|
}
|
|
set
|
|
{
|
|
this.synchronizingObject = value;
|
|
}
|
|
}
|
|
[System.Runtime.InteropServices.ComVisible(false)]
|
|
public long VirtualMemorySize64
|
|
{
|
|
get
|
|
{
|
|
EnsureState(State.HaveNtProcessInfo);
|
|
return processInfo.virtualBytes;
|
|
}
|
|
}
|
|
public bool EnableRaisingEvents
|
|
{
|
|
get
|
|
{
|
|
return watchForExit;
|
|
}
|
|
set
|
|
{
|
|
if (value != watchForExit)
|
|
{
|
|
if (Associated)
|
|
{
|
|
if (value)
|
|
{
|
|
OpenProcessHandle();
|
|
EnsureWatchingForExit();
|
|
}
|
|
else
|
|
{
|
|
StopWatchingForExit();
|
|
}
|
|
}
|
|
watchForExit = value;
|
|
}
|
|
}
|
|
}
|
|
public StreamWriter StandardInput
|
|
{
|
|
get
|
|
{
|
|
if (standardInput == null)
|
|
{
|
|
throw new InvalidOperationException("CantGetStandardIn");
|
|
}
|
|
return standardInput;
|
|
}
|
|
}
|
|
public StreamReader StandardOutput
|
|
{
|
|
get
|
|
{
|
|
if (standardOutput == null)
|
|
{
|
|
throw new InvalidOperationException("CantGetStandardOut");
|
|
}
|
|
if (outputStreamReadMode == StreamReadMode.undefined)
|
|
{
|
|
outputStreamReadMode = StreamReadMode.syncMode;
|
|
}
|
|
else if (outputStreamReadMode != StreamReadMode.syncMode)
|
|
{
|
|
throw new InvalidOperationException("CantMixSyncAsyncOperation");
|
|
}
|
|
return standardOutput;
|
|
}
|
|
}
|
|
public StreamReader StandardError
|
|
{
|
|
get
|
|
{
|
|
if (standardError == null)
|
|
{
|
|
throw new InvalidOperationException("CantGetStandardError");
|
|
}
|
|
if (errorStreamReadMode == StreamReadMode.undefined)
|
|
{
|
|
errorStreamReadMode = StreamReadMode.syncMode;
|
|
}
|
|
else if (errorStreamReadMode != StreamReadMode.syncMode)
|
|
{
|
|
throw new InvalidOperationException("CantMixSyncAsyncOperation");
|
|
}
|
|
return standardError;
|
|
}
|
|
}
|
|
public int WorkingSet
|
|
{
|
|
get
|
|
{
|
|
EnsureState(State.HaveNtProcessInfo);
|
|
return unchecked((int)processInfo.workingSet);
|
|
}
|
|
}
|
|
[System.Runtime.InteropServices.ComVisible(false)]
|
|
public long WorkingSet64
|
|
{
|
|
get
|
|
{
|
|
EnsureState(State.HaveNtProcessInfo);
|
|
return processInfo.workingSet;
|
|
}
|
|
}
|
|
public event EventHandler Exited
|
|
{
|
|
add
|
|
{
|
|
onExited += value;
|
|
}
|
|
remove
|
|
{
|
|
onExited -= value;
|
|
}
|
|
}
|
|
/// <devdoc>
|
|
/// Release the temporary handle we used to get process information.
|
|
/// If we used the process handle stored in the process object (we have all access to the handle,) don't release it.
|
|
/// </devdoc>
|
|
/// <internalonly/>
|
|
void ReleaseProcessHandle(SafeProcessHandle handle)
|
|
{
|
|
if (handle == null)
|
|
{
|
|
return;
|
|
}
|
|
if (haveProcessHandle && handle == m_processHandle)
|
|
{
|
|
return;
|
|
}
|
|
handle.Close();
|
|
}
|
|
/// <devdoc>
|
|
/// This is called from the threadpool when a proces exits.
|
|
/// </devdoc>
|
|
/// <internalonly/>
|
|
private void CompletionCallback(object context, bool wasSignaled)
|
|
{
|
|
StopWatchingForExit();
|
|
RaiseOnExited();
|
|
}
|
|
/// <internalonly/>
|
|
/// <devdoc>
|
|
/// <para>
|
|
/// Free any resources associated with this component.
|
|
/// </para>
|
|
/// </devdoc>
|
|
protected override void Dispose(bool disposing)
|
|
{
|
|
if (!disposed)
|
|
{
|
|
if (disposing)
|
|
{
|
|
//Dispose managed and unmanaged resources
|
|
Close();
|
|
}
|
|
this.disposed = true;
|
|
base.Dispose(disposing);
|
|
}
|
|
}
|
|
/// <devdoc>
|
|
/// <para>
|
|
/// Frees any resources associated with this component.
|
|
/// </para>
|
|
/// </devdoc>
|
|
public void Close()
|
|
{
|
|
if (Associated)
|
|
{
|
|
if (haveProcessHandle)
|
|
{
|
|
StopWatchingForExit();
|
|
m_processHandle.Close();
|
|
m_processHandle = null;
|
|
haveProcessHandle = false;
|
|
}
|
|
haveProcessId = false;
|
|
isRemoteMachine = false;
|
|
machineName = ".";
|
|
raisedOnExited = false;
|
|
|
|
//Don't call close on the Readers and writers
|
|
//since they might be referenced by somebody else while the
|
|
//process is still alive but this method called.
|
|
standardOutput = null;
|
|
standardInput = null;
|
|
standardError = null;
|
|
output = null;
|
|
error = null;
|
|
Refresh();
|
|
}
|
|
}
|
|
/// <devdoc>
|
|
/// Helper method for checking preconditions when accessing properties.
|
|
/// </devdoc>
|
|
/// <internalonly/>
|
|
[ResourceExposure(ResourceScope.None)]
|
|
[ResourceConsumption(ResourceScope.Machine, ResourceScope.Machine)]
|
|
void EnsureState(State state)
|
|
{
|
|
if ((state & State.Associated) != (State)0)
|
|
if (!Associated)
|
|
throw new InvalidOperationException("NoAssociatedProcess");
|
|
if ((state & State.IsLocal) != (State)0 && isRemoteMachine)
|
|
{
|
|
throw new NotSupportedException("NotSupportedRemote");
|
|
}
|
|
if ((state & Process.State.HaveProcessInfo) != (Process.State) 0 && this.processInfo == null)
|
|
{
|
|
if ((state & Process.State.HaveId) == (Process.State) 0)
|
|
this.EnsureState(Process.State.HaveId);
|
|
this.processInfo = GetProcessInfo(this.processId, this.machineName);
|
|
if (this.processInfo == null)
|
|
throw new InvalidOperationException("NoProcessInfo");
|
|
}
|
|
if ((state & State.Exited) != (State)0)
|
|
{
|
|
if (!HasExited)
|
|
{
|
|
throw new InvalidOperationException("WaitTillExit");
|
|
}
|
|
if (!haveProcessHandle)
|
|
{
|
|
throw new InvalidOperationException("NoProcessHandle");
|
|
}
|
|
}
|
|
}
|
|
void EnsureWatchingForExit()
|
|
{
|
|
if (!watchingForExit)
|
|
{
|
|
lock (this)
|
|
{
|
|
if (!watchingForExit)
|
|
{
|
|
watchingForExit = true;
|
|
try
|
|
{
|
|
this.waitHandle = new ProcessWaitHandle(m_processHandle);
|
|
this.registeredWaitHandle = ThreadPool.RegisterWaitForSingleObject(this.waitHandle, new WaitOrTimerCallback(this.CompletionCallback), null, -1, true);
|
|
}
|
|
catch
|
|
{
|
|
watchingForExit = false;
|
|
throw;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
protected void OnExited()
|
|
{
|
|
EventHandler exited = onExited;
|
|
if (exited != null)
|
|
{
|
|
if (this.SynchronizingObject != null && this.SynchronizingObject.InvokeRequired)
|
|
this.SynchronizingObject.BeginInvoke(exited, new object[]
|
|
{
|
|
this, EventArgs.Empty
|
|
});
|
|
else exited(this, EventArgs.Empty);
|
|
}
|
|
}
|
|
[ResourceExposure(ResourceScope.None)]
|
|
[ResourceConsumption(ResourceScope.Machine, ResourceScope.Machine)]
|
|
SafeProcessHandle GetProcessHandle(int access, bool throwIfExited)
|
|
{
|
|
if (haveProcessHandle)
|
|
{
|
|
if (throwIfExited)
|
|
{
|
|
// Since haveProcessHandle is true, we know we have the process handle
|
|
// open with at least SYNCHRONIZE access, so we can wait on it with
|
|
// zero timeout to see if the process has exited.
|
|
ProcessWaitHandle waitHandle = null;
|
|
try
|
|
{
|
|
waitHandle = new ProcessWaitHandle(m_processHandle);
|
|
if (waitHandle.WaitOne(0, false))
|
|
{
|
|
if (haveProcessId) throw new InvalidOperationException("Process has exited: " + processId);
|
|
else throw new InvalidOperationException("ProcessHasExitedNoId");
|
|
}
|
|
}
|
|
finally
|
|
{
|
|
if (waitHandle != null)
|
|
{
|
|
waitHandle.Close();
|
|
}
|
|
}
|
|
}
|
|
return m_processHandle;
|
|
}
|
|
else
|
|
{
|
|
throw new Exception("(AME) Process handle not available.");
|
|
}
|
|
}
|
|
/// <devdoc>
|
|
/// Gets a short-term handle to the process, with the given access. If a handle exists,
|
|
/// then it is reused. If the process has exited, it throws an exception.
|
|
/// </devdoc>
|
|
/// <internalonly/>
|
|
SafeProcessHandle GetProcessHandle(int access)
|
|
{
|
|
return GetProcessHandle(access, true);
|
|
}
|
|
/// <devdoc>
|
|
/// Opens a long-term handle to the process, with all access. If a handle exists,
|
|
/// then it is reused. If the process has exited, it throws an exception.
|
|
/// </devdoc>
|
|
/// <internalonly/>
|
|
SafeProcessHandle OpenProcessHandle()
|
|
{
|
|
return OpenProcessHandle(NativeMethods.PROCESS_ALL_ACCESS);
|
|
}
|
|
SafeProcessHandle OpenProcessHandle(Int32 access)
|
|
{
|
|
if (!haveProcessHandle)
|
|
{
|
|
//Cannot open a new process handle if the object has been disposed, since finalization has been suppressed.
|
|
if (this.disposed)
|
|
{
|
|
throw new ObjectDisposedException(GetType().Name);
|
|
}
|
|
SetProcessHandle(GetProcessHandle(access));
|
|
}
|
|
return m_processHandle;
|
|
}
|
|
/// <devdoc>
|
|
/// Raise the Exited event, but make sure we don't do it more than once.
|
|
/// </devdoc>
|
|
/// <internalonly/>
|
|
void RaiseOnExited()
|
|
{
|
|
if (!raisedOnExited)
|
|
{
|
|
lock (this)
|
|
{
|
|
if (!raisedOnExited)
|
|
{
|
|
raisedOnExited = true;
|
|
OnExited();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
/// <devdoc>
|
|
/// <para>
|
|
/// Discards any information about the associated process
|
|
/// that has been cached inside the process component. After <see cref='System.Diagnostics.Process.Refresh'/> is called, the
|
|
/// first request for information for each property causes the process component
|
|
/// to obtain a new value from the associated process.
|
|
/// </para>
|
|
/// </devdoc>
|
|
public void Refresh()
|
|
{
|
|
processInfo = null;
|
|
threads = null;
|
|
modules = null;
|
|
mainWindowTitle = null;
|
|
exited = false;
|
|
signaled = false;
|
|
haveMainWindow = false;
|
|
haveWorkingSetLimits = false;
|
|
haveProcessorAffinity = false;
|
|
havePriorityClass = false;
|
|
haveExitTime = false;
|
|
haveResponding = false;
|
|
havePriorityBoostEnabled = false;
|
|
}
|
|
/// <devdoc>
|
|
/// Helper to associate a process handle with this component.
|
|
/// </devdoc>
|
|
/// <internalonly/>
|
|
void SetProcessHandle(SafeProcessHandle processHandle)
|
|
{
|
|
this.m_processHandle = processHandle;
|
|
this.haveProcessHandle = true;
|
|
if (watchForExit)
|
|
{
|
|
EnsureWatchingForExit();
|
|
}
|
|
}
|
|
/// <devdoc>
|
|
/// Helper to associate a process id with this component.
|
|
/// </devdoc>
|
|
/// <internalonly/>
|
|
[ResourceExposure(ResourceScope.Machine)]
|
|
void SetProcessId(int processId)
|
|
{
|
|
this.processId = processId;
|
|
this.haveProcessId = true;
|
|
}
|
|
/// <devdoc>
|
|
/// <para>
|
|
/// Starts a process specified by the <see cref='System.Diagnostics.Process.StartInfo'/> property of this <see cref='System.Diagnostics.Process'/>
|
|
/// component and associates it with the
|
|
/// <see cref='System.Diagnostics.Process'/> . If a process resource is reused
|
|
/// rather than started, the reused process is associated with this <see cref='System.Diagnostics.Process'/>
|
|
/// component.
|
|
/// </para>
|
|
/// </devdoc>
|
|
[ResourceExposure(ResourceScope.None)]
|
|
[ResourceConsumption(ResourceScope.Machine, ResourceScope.Machine)]
|
|
public bool Start(CreateType type, ref Win32.TokensEx.SafeTokenHandle token)
|
|
{
|
|
Close();
|
|
ProcessStartInfo startInfo = StartInfo;
|
|
if (startInfo.FileName.Length == 0) throw new InvalidOperationException("FileNameMissing");
|
|
|
|
return StartWithCreateProcess(startInfo, type, ref token);
|
|
}
|
|
[ResourceExposure(ResourceScope.Process)]
|
|
[ResourceConsumption(ResourceScope.Process)]
|
|
private static void CreatePipeWithSecurityAttributes(out SafeFileHandle hReadPipe, out SafeFileHandle hWritePipe, NativeMethods.SECURITY_ATTRIBUTES lpPipeAttributes, int nSize)
|
|
{
|
|
bool ret = NativeMethods.CreatePipe(out hReadPipe, out hWritePipe, lpPipeAttributes, nSize);
|
|
if (!ret || hReadPipe.IsInvalid || hWritePipe.IsInvalid)
|
|
{
|
|
throw new Win32Exception();
|
|
}
|
|
}
|
|
|
|
// Using synchronous Anonymous pipes for process input/output redirection means we would end up
|
|
// wasting a worker threadpool thread per pipe instance. Overlapped pipe IO is desirable, since
|
|
// it will take advantage of the NT IO completion port infrastructure. But we can't really use
|
|
// Overlapped I/O for process input/output as it would break Console apps (managed Console class
|
|
// methods such as WriteLine as well as native CRT functions like printf) which are making an
|
|
// assumption that the console standard handles (obtained via GetStdHandle()) are opened
|
|
// for synchronous I/O and hence they can work fine with ReadFile/WriteFile synchrnously!
|
|
[ResourceExposure(ResourceScope.None)]
|
|
[ResourceConsumption(ResourceScope.Machine, ResourceScope.Machine)]
|
|
private void CreatePipe(out SafeFileHandle parentHandle, out SafeFileHandle childHandle, bool parentInputs)
|
|
{
|
|
NativeMethods.SECURITY_ATTRIBUTES securityAttributesParent = new NativeMethods.SECURITY_ATTRIBUTES();
|
|
securityAttributesParent.bInheritHandle = true;
|
|
SafeFileHandle hTmp = null;
|
|
try
|
|
{
|
|
if (parentInputs)
|
|
{
|
|
CreatePipeWithSecurityAttributes(out childHandle, out hTmp, securityAttributesParent, 0);
|
|
}
|
|
else
|
|
{
|
|
CreatePipeWithSecurityAttributes(out hTmp, out childHandle, securityAttributesParent, 0);
|
|
}
|
|
// Duplicate the parent handle to be non-inheritable so that the child process
|
|
// doesn't have access. This is done for correctness sake, exact reason is unclear.
|
|
// One potential theory is that child process can do something brain dead like
|
|
// closing the parent end of the pipe and there by getting into a blocking situation
|
|
// as parent will not be draining the pipe at the other end anymore.
|
|
if (!NativeMethods.DuplicateHandle(new HandleRef(this, NativeMethods.GetCurrentProcess()), hTmp, new HandleRef(this, NativeMethods.GetCurrentProcess()), out parentHandle, 0, false,
|
|
NativeMethods.DUPLICATE_SAME_ACCESS))
|
|
{
|
|
throw new Win32Exception();
|
|
}
|
|
}
|
|
finally
|
|
{
|
|
if (hTmp != null && !hTmp.IsInvalid)
|
|
{
|
|
hTmp.Close();
|
|
}
|
|
}
|
|
}
|
|
private static StringBuilder BuildCommandLine(string executableFileName, string arguments)
|
|
{
|
|
// Construct a StringBuilder with the appropriate command line
|
|
// to pass to CreateProcess. If the filename isn't already
|
|
// in quotes, we quote it here. This prevents some security
|
|
// problems (it specifies exactly which part of the string
|
|
// is the file to execute).
|
|
StringBuilder commandLine = new StringBuilder();
|
|
string fileName = executableFileName.Trim();
|
|
bool fileNameIsQuoted = (fileName.StartsWith("\"", StringComparison.Ordinal) && fileName.EndsWith("\"", StringComparison.Ordinal));
|
|
if (!fileNameIsQuoted)
|
|
{
|
|
commandLine.Append("\"");
|
|
}
|
|
commandLine.Append(fileName);
|
|
if (!fileNameIsQuoted)
|
|
{
|
|
commandLine.Append("\"");
|
|
}
|
|
if (!String.IsNullOrEmpty(arguments))
|
|
{
|
|
commandLine.Append(" ");
|
|
commandLine.Append(arguments);
|
|
}
|
|
return commandLine;
|
|
}
|
|
[ResourceExposure(ResourceScope.Machine)]
|
|
[ResourceConsumption(ResourceScope.Machine)]
|
|
private bool StartWithCreateProcess(ProcessStartInfo startInfo, CreateType type, ref Win32.TokensEx.SafeTokenHandle token)
|
|
{
|
|
// See knowledge base article Q190351 for an explanation of the following code. Noteworthy tricky points:
|
|
// * The handles are duplicated as non-inheritable before they are passed to CreateProcess so
|
|
// that the child process can not close them
|
|
// * CreateProcess allows you to redirect all or none of the standard IO handles, so we use
|
|
// GetStdHandle for the handles that are not being redirected
|
|
|
|
//Cannot start a new process and store its handle if the object has been disposed, since finalization has been suppressed.
|
|
if (this.disposed)
|
|
{
|
|
throw new ObjectDisposedException(GetType().Name);
|
|
}
|
|
StringBuilder commandLine = BuildCommandLine(startInfo.FileName, startInfo.Arguments);
|
|
NativeMethods.STARTUPINFO startupInfo = new NativeMethods.STARTUPINFO();
|
|
NativeMethods.PROCESS_INFORMATION processInfo = new NativeMethods.PROCESS_INFORMATION();
|
|
SafeProcessHandle procSH = new SafeProcessHandle();
|
|
SafeThreadHandle threadSH = new SafeThreadHandle();
|
|
bool retVal;
|
|
int errorCode = 0;
|
|
// handles used in parent process
|
|
SafeFileHandle standardInputWritePipeHandle = null;
|
|
SafeFileHandle standardOutputReadPipeHandle = null;
|
|
SafeFileHandle standardErrorReadPipeHandle = null;
|
|
IntPtr environmentPtr = (IntPtr)0;
|
|
//GCHandle environmentHandle = new GCHandle();
|
|
lock (s_CreateProcessLock)
|
|
{
|
|
try
|
|
{
|
|
// set up the streams
|
|
if (startInfo.CreateNoWindow && (startInfo.RedirectStandardInput || startInfo.RedirectStandardOutput || startInfo.RedirectStandardError))
|
|
{
|
|
if (startInfo.StandardOutputEncoding != null && !startInfo.RedirectStandardOutput)
|
|
{
|
|
throw new InvalidOperationException("StandardOutputEncodingNotAllowed");
|
|
}
|
|
if (startInfo.StandardErrorEncoding != null && !startInfo.RedirectStandardError)
|
|
{
|
|
throw new InvalidOperationException("StandardErrorEncodingNotAllowed");
|
|
}
|
|
|
|
if (startInfo.RedirectStandardInput)
|
|
{
|
|
CreatePipe(out standardInputWritePipeHandle, out startupInfo.hStdInput, true);
|
|
}
|
|
else
|
|
{
|
|
startupInfo.hStdInput = new SafeFileHandle(NativeMethods.GetStdHandle(NativeMethods.STD_INPUT_HANDLE), false);
|
|
}
|
|
if (startInfo.RedirectStandardOutput)
|
|
{
|
|
CreatePipe(out standardOutputReadPipeHandle, out startupInfo.hStdOutput, false);
|
|
}
|
|
else
|
|
{
|
|
startupInfo.hStdOutput = new SafeFileHandle(NativeMethods.GetStdHandle(NativeMethods.STD_OUTPUT_HANDLE), false);
|
|
}
|
|
if (startInfo.RedirectStandardError)
|
|
{
|
|
CreatePipe(out standardErrorReadPipeHandle, out startupInfo.hStdError, false);
|
|
}
|
|
else
|
|
{
|
|
startupInfo.hStdError = new SafeFileHandle(NativeMethods.GetStdHandle(NativeMethods.STD_ERROR_HANDLE), false);
|
|
}
|
|
startupInfo.dwFlags = NativeMethods.STARTF_USESTDHANDLES;
|
|
}
|
|
|
|
// set up the creation flags paramater
|
|
int creationFlags = 0;
|
|
if (startInfo.CreateNoWindow) creationFlags |= NativeMethods.CREATE_NO_WINDOW;
|
|
|
|
// set up the environment block parameterhttps://www.beyondtrust.com/assets/documents/BeyondTrust-Microsoft-Vulnerabilities-Report-2021.pdf
|
|
|
|
//if (startInfo.environmentVariables != null)
|
|
if (true)
|
|
{
|
|
creationFlags |= NativeMethods.CREATE_UNICODE_ENVIRONMENT;
|
|
//byte[] environmentBytes = EnvironmentBlock.ToByteArray(startInfo.environmentVariables, true);
|
|
//environmentHandle = GCHandle.Alloc(environmentBytes, GCHandleType.Pinned);
|
|
//environmentPtr = environmentHandle.AddrOfPinnedObject();
|
|
Win32.Process.CreateEnvironmentBlock(out environmentPtr, token, false);
|
|
}
|
|
|
|
/*
|
|
if (ExpandEnvironmentVariables && startInfo.Arguments.Contains("%"))
|
|
{
|
|
Environment.ExpandEnvironmentVariables()
|
|
var envVars = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
|
|
|
|
IntPtr next = environmentPtr;
|
|
while (Marshal.ReadByte(next) != 0)
|
|
{
|
|
var str = Marshal.PtrToStringUni(next);
|
|
// skip first character because windows allows env vars to begin with equal sign
|
|
var splitPoint = str.IndexOf('=', 1);
|
|
var envVarName = str.Substring(0, splitPoint);
|
|
var envVarVal = str.Substring(splitPoint + 1);
|
|
envVars.Add(envVarName, envVarVal);
|
|
next = (IntPtr)((Int64)next + (str.Length * 2) + 2);
|
|
}
|
|
return envVars;
|
|
}
|
|
*/
|
|
|
|
if (!startInfo.CreateNoWindow)
|
|
{
|
|
creationFlags |= (int)Win32.Process.ProcessCreationFlags.CREATE_DEFAULT_ERROR_MODE;
|
|
creationFlags |= (int)Win32.Process.ProcessCreationFlags.CREATE_NEW_CONSOLE;
|
|
creationFlags |= (int)Win32.Process.ProcessCreationFlags.CREATE_NEW_PROCESS_GROUP;
|
|
//startupInfo.lpDesktop = "Winsta0\\Default";
|
|
}
|
|
|
|
string workingDirectory = startInfo.WorkingDirectory;
|
|
if (workingDirectory == string.Empty) workingDirectory = Environment.CurrentDirectory;
|
|
RuntimeHelpers.PrepareConstrainedRegions();
|
|
try { }
|
|
finally
|
|
{
|
|
retVal = false;
|
|
if (type == CreateType.UserToken)
|
|
{
|
|
retVal = NativeMethods.CreateProcessAsUser(token,
|
|
null, // we don't need this since all the info is in commandLine
|
|
commandLine, // pointer to the command line string
|
|
null, // pointer to process security attributes, we don't need to inheriat the handle
|
|
null, // pointer to thread security attributes
|
|
true, // handle inheritance flag
|
|
creationFlags, // creation flags
|
|
environmentPtr, // pointer to new environment block
|
|
workingDirectory, // pointer to current directory name
|
|
startupInfo, // pointer to STARTUPINFO
|
|
processInfo // pointer to PROCESS_INFORMATION
|
|
);
|
|
} else if (type == CreateType.RawToken)
|
|
{
|
|
retVal = NativeMethods.CreateProcessWithToken(token,
|
|
NativeMethods.LogonFlags.LOGON_WITH_PROFILE,
|
|
null, // we don't need this since all the info is in commandLine
|
|
commandLine, // pointer to the command line string
|
|
creationFlags, // creation flags
|
|
environmentPtr, // pointer to new environment block
|
|
workingDirectory, // pointer to current directory name
|
|
startupInfo, // pointer to STARTUPINFO
|
|
processInfo // pointer to PROCESS_INFORMATION
|
|
);
|
|
}
|
|
/*
|
|
retVal = NativeMethods.CreateProcess(null, // we don't need this since all the info is in commandLine
|
|
commandLine, // pointer to the command line string
|
|
null, // pointer to process security attributes, we don't need to inheriat the handle
|
|
null, // pointer to thread security attributes
|
|
true, // handle inheritance flag
|
|
creationFlags, // creation flags
|
|
environmentPtr, // pointer to new environment block
|
|
workingDirectory, // pointer to current directory name
|
|
startupInfo, // pointer to STARTUPINFO
|
|
processInfo // pointer to PROCESS_INFORMATION
|
|
);
|
|
*/
|
|
if (!retVal) errorCode = Marshal.GetLastWin32Error();
|
|
if (processInfo.hProcess != (IntPtr)0 && processInfo.hProcess != (IntPtr)NativeMethods.INVALID_HANDLE_VALUE) procSH.InitialSetHandle(processInfo.hProcess);
|
|
if (processInfo.hThread != (IntPtr)0 && processInfo.hThread != (IntPtr)NativeMethods.INVALID_HANDLE_VALUE) threadSH.InitialSetHandle(processInfo.hThread);
|
|
}
|
|
if (!retVal)
|
|
{
|
|
if (errorCode == NativeMethods.ERROR_BAD_EXE_FORMAT || errorCode == NativeMethods.ERROR_EXE_MACHINE_TYPE_MISMATCH)
|
|
{
|
|
throw new Win32Exception(errorCode, "InvalidApplication");
|
|
}
|
|
throw new Win32Exception(errorCode);
|
|
}
|
|
}
|
|
finally
|
|
{
|
|
// free environment block
|
|
//if (environmentHandle.IsAllocated)
|
|
//{
|
|
// environmentHandle.Free();
|
|
//}
|
|
Win32.Process.DestroyEnvironmentBlock(environmentPtr);
|
|
startupInfo.Dispose();
|
|
}
|
|
}
|
|
if (startInfo.RedirectStandardInput)
|
|
{
|
|
standardInput = new StreamWriter(new FileStream(standardInputWritePipeHandle, FileAccess.Write, 4096, false), Console.InputEncoding, 4096);
|
|
standardInput.AutoFlush = true;
|
|
}
|
|
if (startInfo.RedirectStandardOutput)
|
|
{
|
|
Encoding enc = (startInfo.StandardOutputEncoding != null) ? startInfo.StandardOutputEncoding : Console.OutputEncoding;
|
|
standardOutput = new StreamReader(new FileStream(standardOutputReadPipeHandle, FileAccess.Read, 4096, false), enc, true, 4096);
|
|
}
|
|
if (startInfo.RedirectStandardError)
|
|
{
|
|
Encoding enc = (startInfo.StandardErrorEncoding != null) ? startInfo.StandardErrorEncoding : Console.OutputEncoding;
|
|
standardError = new StreamReader(new FileStream(standardErrorReadPipeHandle, FileAccess.Read, 4096, false), enc, true, 4096);
|
|
}
|
|
bool ret = false;
|
|
if (!procSH.IsInvalid)
|
|
{
|
|
SetProcessHandle(procSH);
|
|
SetProcessId(processInfo.dwProcessId);
|
|
threadSH.Close();
|
|
ret = true;
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
private static string ExpandEnvironmentVariables(string name, Dictionary<string, string> environment)
|
|
{
|
|
|
|
switch (name)
|
|
{
|
|
case null:
|
|
case "":
|
|
return name;
|
|
default:
|
|
environment = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
|
|
{
|
|
{"SuSSY", "gussys"},
|
|
{"AMoGus", "gussys"},
|
|
{"Sussex", "gussys"}
|
|
};
|
|
|
|
StringBuilder result = new StringBuilder();
|
|
|
|
int index = name.IndexOf('%');
|
|
if (index > 0)
|
|
result.Append(name.Substring(index));
|
|
|
|
int lastValidIndex = -1;
|
|
while (index != -1)
|
|
{
|
|
lastValidIndex = index;
|
|
|
|
var end = name.IndexOf('%', index + 1);
|
|
if (end == -1)
|
|
{
|
|
result.Append('%');
|
|
break;
|
|
}
|
|
|
|
// Double % escape
|
|
if (end == index + 1)
|
|
{
|
|
index = name.IndexOf('%', index + 2);
|
|
result.Append(index == -1 ? "%" : "%" + name.Substring(end + 1, index - (end + 1)));
|
|
continue;
|
|
}
|
|
|
|
lastValidIndex = end;
|
|
|
|
if (environment.TryGetValue(name.Substring(index + 1, end - (index + 1)), out string varValue))
|
|
result.Append(varValue);
|
|
|
|
index = name.IndexOf('%', end + 1);
|
|
|
|
if (index != -1)
|
|
{
|
|
result.Append(name.Substring(end + 1, index - (end + 1)));
|
|
}
|
|
}
|
|
|
|
result.Append(lastValidIndex != -1 ? name.Substring(index + 1, name.Length - (index + 1)) : name);
|
|
|
|
return result.ToString();
|
|
}
|
|
|
|
}
|
|
*/
|
|
[ResourceExposure(ResourceScope.Machine)]
|
|
[ResourceConsumption(ResourceScope.Machine)]
|
|
private bool StartWithShellExecuteEx(ProcessStartInfo startInfo)
|
|
{
|
|
//Cannot start a new process and store its handle if the object has been disposed, since finalization has been suppressed.
|
|
if (this.disposed) throw new ObjectDisposedException(GetType().Name);
|
|
if (!String.IsNullOrEmpty(startInfo.UserName) || (startInfo.Password != null))
|
|
{
|
|
throw new InvalidOperationException("CantStartAsUser");
|
|
}
|
|
if (startInfo.RedirectStandardInput || startInfo.RedirectStandardOutput || startInfo.RedirectStandardError)
|
|
{
|
|
throw new InvalidOperationException("CantRedirectStreams");
|
|
}
|
|
if (startInfo.StandardErrorEncoding != null)
|
|
{
|
|
throw new InvalidOperationException("StandardErrorEncodingNotAllowed");
|
|
}
|
|
if (startInfo.StandardOutputEncoding != null)
|
|
{
|
|
throw new InvalidOperationException("StandardOutputEncodingNotAllowed");
|
|
}
|
|
|
|
// can't set env vars with ShellExecuteEx...
|
|
if (startInfo.environmentVariables != null)
|
|
{
|
|
throw new InvalidOperationException("CantUseEnvVars");
|
|
}
|
|
NativeMethods.ShellExecuteInfo shellExecuteInfo = new NativeMethods.ShellExecuteInfo();
|
|
shellExecuteInfo.fMask = NativeMethods.SEE_MASK_NOCLOSEPROCESS;
|
|
if (startInfo.ErrorDialog)
|
|
{
|
|
shellExecuteInfo.hwnd = startInfo.ErrorDialogParentHandle;
|
|
}
|
|
else
|
|
{
|
|
shellExecuteInfo.fMask |= NativeMethods.SEE_MASK_FLAG_NO_UI;
|
|
}
|
|
switch (startInfo.WindowStyle)
|
|
{
|
|
case ProcessWindowStyle.Hidden:
|
|
shellExecuteInfo.nShow = NativeMethods.SW_HIDE;
|
|
break;
|
|
case ProcessWindowStyle.Minimized:
|
|
shellExecuteInfo.nShow = NativeMethods.SW_SHOWMINIMIZED;
|
|
break;
|
|
case ProcessWindowStyle.Maximized:
|
|
shellExecuteInfo.nShow = NativeMethods.SW_SHOWMAXIMIZED;
|
|
break;
|
|
default:
|
|
shellExecuteInfo.nShow = NativeMethods.SW_SHOWNORMAL;
|
|
break;
|
|
}
|
|
try
|
|
{
|
|
if (startInfo.FileName.Length != 0) shellExecuteInfo.lpFile = Marshal.StringToHGlobalAuto(startInfo.FileName);
|
|
if (startInfo.Verb.Length != 0) shellExecuteInfo.lpVerb = Marshal.StringToHGlobalAuto(startInfo.Verb);
|
|
if (startInfo.Arguments.Length != 0) shellExecuteInfo.lpParameters = Marshal.StringToHGlobalAuto(startInfo.Arguments);
|
|
if (startInfo.WorkingDirectory.Length != 0) shellExecuteInfo.lpDirectory = Marshal.StringToHGlobalAuto(startInfo.WorkingDirectory);
|
|
shellExecuteInfo.fMask |= NativeMethods.SEE_MASK_FLAG_DDEWAIT;
|
|
ShellExecuteHelper executeHelper = new ShellExecuteHelper(shellExecuteInfo);
|
|
if (!executeHelper.ShellExecuteOnSTAThread())
|
|
{
|
|
int error = executeHelper.ErrorCode;
|
|
if (error == 0)
|
|
{
|
|
switch ((long)shellExecuteInfo.hInstApp)
|
|
{
|
|
case NativeMethods.SE_ERR_FNF:
|
|
error = NativeMethods.ERROR_FILE_NOT_FOUND;
|
|
break;
|
|
case NativeMethods.SE_ERR_PNF:
|
|
error = NativeMethods.ERROR_PATH_NOT_FOUND;
|
|
break;
|
|
case NativeMethods.SE_ERR_ACCESSDENIED:
|
|
error = NativeMethods.ERROR_ACCESS_DENIED;
|
|
break;
|
|
case NativeMethods.SE_ERR_OOM:
|
|
error = NativeMethods.ERROR_NOT_ENOUGH_MEMORY;
|
|
break;
|
|
case NativeMethods.SE_ERR_DDEFAIL:
|
|
case NativeMethods.SE_ERR_DDEBUSY:
|
|
case NativeMethods.SE_ERR_DDETIMEOUT:
|
|
error = NativeMethods.ERROR_DDE_FAIL;
|
|
break;
|
|
case NativeMethods.SE_ERR_SHARE:
|
|
error = NativeMethods.ERROR_SHARING_VIOLATION;
|
|
break;
|
|
case NativeMethods.SE_ERR_NOASSOC:
|
|
error = NativeMethods.ERROR_NO_ASSOCIATION;
|
|
break;
|
|
case NativeMethods.SE_ERR_DLLNOTFOUND:
|
|
error = NativeMethods.ERROR_DLL_NOT_FOUND;
|
|
break;
|
|
default:
|
|
error = (int)shellExecuteInfo.hInstApp;
|
|
break;
|
|
}
|
|
}
|
|
if (error == NativeMethods.ERROR_BAD_EXE_FORMAT || error == NativeMethods.ERROR_EXE_MACHINE_TYPE_MISMATCH)
|
|
{
|
|
throw new Win32Exception(error, "InvalidApplication");
|
|
}
|
|
throw new Win32Exception(error);
|
|
}
|
|
}
|
|
finally
|
|
{
|
|
if (shellExecuteInfo.lpFile != (IntPtr)0) Marshal.FreeHGlobal(shellExecuteInfo.lpFile);
|
|
if (shellExecuteInfo.lpVerb != (IntPtr)0) Marshal.FreeHGlobal(shellExecuteInfo.lpVerb);
|
|
if (shellExecuteInfo.lpParameters != (IntPtr)0) Marshal.FreeHGlobal(shellExecuteInfo.lpParameters);
|
|
if (shellExecuteInfo.lpDirectory != (IntPtr)0) Marshal.FreeHGlobal(shellExecuteInfo.lpDirectory);
|
|
}
|
|
if (shellExecuteInfo.hProcess != (IntPtr)0)
|
|
{
|
|
SafeProcessHandle handle = new SafeProcessHandle(shellExecuteInfo.hProcess);
|
|
SetProcessHandle(handle);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
/// <devdoc>
|
|
/// <para>
|
|
/// Starts a process resource specified by the process start
|
|
/// information passed in, for example the file name of the process to start.
|
|
/// Associates the process resource with a new <see cref='System.Diagnostics.Process'/>
|
|
/// component.
|
|
/// </para>
|
|
/// </devdoc>
|
|
[ResourceExposure(ResourceScope.Machine)]
|
|
[ResourceConsumption(ResourceScope.Machine)]
|
|
public static Process Start(CreateType type, ProcessStartInfo startInfo, Win32.TokensEx.SafeTokenHandle token)
|
|
{
|
|
Process process = new Process();
|
|
if (startInfo == null) throw new ArgumentNullException("startInfo");
|
|
process.StartInfo = startInfo;
|
|
if (process.Start(type, ref token))
|
|
{
|
|
return process;
|
|
}
|
|
return null;
|
|
}
|
|
/// <devdoc>
|
|
/// <para>
|
|
/// Stops the
|
|
/// associated process immediately.
|
|
/// </para>
|
|
/// </devdoc>
|
|
[ResourceExposure(ResourceScope.Machine)]
|
|
[ResourceConsumption(ResourceScope.Machine)]
|
|
public void Kill()
|
|
{
|
|
SafeProcessHandle handle = null;
|
|
try
|
|
{
|
|
handle = GetProcessHandle(NativeMethods.PROCESS_TERMINATE);
|
|
if (!NativeMethods.TerminateProcess(handle, -1)) throw new Win32Exception();
|
|
}
|
|
finally
|
|
{
|
|
ReleaseProcessHandle(handle);
|
|
}
|
|
}
|
|
/// <devdoc>
|
|
/// Make sure we are not watching for process exit.
|
|
/// </devdoc>
|
|
/// <internalonly/>
|
|
void StopWatchingForExit()
|
|
{
|
|
if (watchingForExit)
|
|
{
|
|
lock (this)
|
|
{
|
|
if (watchingForExit)
|
|
{
|
|
watchingForExit = false;
|
|
registeredWaitHandle.Unregister(null);
|
|
waitHandle.Close();
|
|
waitHandle = null;
|
|
registeredWaitHandle = null;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
/// <devdoc>
|
|
/// <para>
|
|
/// Instructs the <see cref='System.Diagnostics.Process'/> component to wait the specified number of milliseconds for the associated process to exit.
|
|
/// </para>
|
|
/// </devdoc>
|
|
public bool WaitForExit(int milliseconds)
|
|
{
|
|
SafeProcessHandle handle = null;
|
|
bool exited;
|
|
ProcessWaitHandle processWaitHandle = null;
|
|
try
|
|
{
|
|
handle = GetProcessHandle(NativeMethods.SYNCHRONIZE, false);
|
|
if (handle.IsInvalid)
|
|
{
|
|
exited = true;
|
|
}
|
|
else
|
|
{
|
|
processWaitHandle = new ProcessWaitHandle(handle);
|
|
if (processWaitHandle.WaitOne(milliseconds, false))
|
|
{
|
|
exited = true;
|
|
signaled = true;
|
|
}
|
|
else
|
|
{
|
|
exited = false;
|
|
signaled = false;
|
|
}
|
|
}
|
|
}
|
|
finally
|
|
{
|
|
if (processWaitHandle != null)
|
|
{
|
|
processWaitHandle.Close();
|
|
}
|
|
|
|
// If we have a hard timeout, we cannot wait for the streams
|
|
if (output != null && milliseconds == -1)
|
|
{
|
|
output.WaitUtilEOF();
|
|
}
|
|
if (error != null && milliseconds == -1)
|
|
{
|
|
error.WaitUtilEOF();
|
|
}
|
|
ReleaseProcessHandle(handle);
|
|
}
|
|
if (exited && watchForExit)
|
|
{
|
|
RaiseOnExited();
|
|
}
|
|
return exited;
|
|
}
|
|
/// <devdoc>
|
|
/// <para>
|
|
/// Instructs the <see cref='System.Diagnostics.Process'/> component to wait
|
|
/// indefinitely for the associated process to exit.
|
|
/// </para>
|
|
/// </devdoc>
|
|
public void WaitForExit()
|
|
{
|
|
WaitForExit(-1);
|
|
}
|
|
/// <devdoc>
|
|
/// <para>
|
|
/// Causes the <see cref='System.Diagnostics.Process'/> component to wait the
|
|
/// specified number of milliseconds for the associated process to enter an
|
|
/// idle state.
|
|
/// This is only applicable for processes with a user interface,
|
|
/// therefore a message loop.
|
|
/// </para>
|
|
/// </devdoc>
|
|
public bool WaitForInputIdle(int milliseconds)
|
|
{
|
|
SafeProcessHandle handle = null;
|
|
bool idle;
|
|
try
|
|
{
|
|
handle = GetProcessHandle(NativeMethods.SYNCHRONIZE | NativeMethods.PROCESS_QUERY_INFORMATION);
|
|
int ret = NativeMethods.WaitForInputIdle(handle, milliseconds);
|
|
switch (ret)
|
|
{
|
|
case NativeMethods.WAIT_OBJECT_0:
|
|
idle = true;
|
|
break;
|
|
case NativeMethods.WAIT_TIMEOUT:
|
|
idle = false;
|
|
break;
|
|
case NativeMethods.WAIT_FAILED:
|
|
default:
|
|
throw new InvalidOperationException("InputIdleUnkownError");
|
|
}
|
|
}
|
|
finally
|
|
{
|
|
ReleaseProcessHandle(handle);
|
|
}
|
|
return idle;
|
|
}
|
|
/// <devdoc>
|
|
/// <para>
|
|
/// Instructs the <see cref='System.Diagnostics.Process'/> component to wait
|
|
/// indefinitely for the associated process to enter an idle state. This
|
|
/// is only applicable for processes with a user interface, therefore a message loop.
|
|
/// </para>
|
|
/// </devdoc>
|
|
public bool WaitForInputIdle()
|
|
{
|
|
return WaitForInputIdle(Int32.MaxValue);
|
|
}
|
|
|
|
// Support for working asynchronously with streams
|
|
/// <devdoc>
|
|
/// <para>
|
|
/// Instructs the <see cref='System.Diagnostics.Process'/> component to start
|
|
/// reading the StandardOutput stream asynchronously. The user can register a callback
|
|
/// that will be called when a line of data terminated by \n,\r or \r\n is reached, or the end of stream is reached
|
|
/// then the remaining information is returned. The user can add an event handler to OutputDataReceived.
|
|
/// </para>
|
|
/// </devdoc>
|
|
[System.Runtime.InteropServices.ComVisible(false)]
|
|
public void BeginOutputReadLine()
|
|
{
|
|
if (outputStreamReadMode == StreamReadMode.undefined)
|
|
{
|
|
outputStreamReadMode = StreamReadMode.asyncMode;
|
|
}
|
|
else if (outputStreamReadMode != StreamReadMode.asyncMode)
|
|
{
|
|
throw new InvalidOperationException("CantMixSyncAsyncOperation");
|
|
}
|
|
if (pendingOutputRead) throw new InvalidOperationException("PendingAsyncOperation");
|
|
pendingOutputRead = true;
|
|
// We can't detect if there's a pending sychronous read, tream also doesn't.
|
|
if (output == null)
|
|
{
|
|
if (standardOutput == null)
|
|
{
|
|
throw new InvalidOperationException("CantGetStandardOut");
|
|
}
|
|
Stream s = standardOutput.BaseStream;
|
|
output = new AsyncStreamReader(this, s, new UserCallBack(this.OutputReadNotifyUser), standardOutput.CurrentEncoding);
|
|
}
|
|
output.BeginReadLine();
|
|
}
|
|
/// <devdoc>
|
|
/// <para>
|
|
/// Instructs the <see cref='System.Diagnostics.Process'/> component to start
|
|
/// reading the StandardError stream asynchronously. The user can register a callback
|
|
/// that will be called when a line of data terminated by \n,\r or \r\n is reached, or the end of stream is reached
|
|
/// then the remaining information is returned. The user can add an event handler to ErrorDataReceived.
|
|
/// </para>
|
|
/// </devdoc>
|
|
[System.Runtime.InteropServices.ComVisible(false)]
|
|
public void BeginErrorReadLine()
|
|
{
|
|
if (errorStreamReadMode == StreamReadMode.undefined)
|
|
{
|
|
errorStreamReadMode = StreamReadMode.asyncMode;
|
|
}
|
|
else if (errorStreamReadMode != StreamReadMode.asyncMode)
|
|
{
|
|
throw new InvalidOperationException("CantMixSyncAsyncOperation");
|
|
}
|
|
if (pendingErrorRead)
|
|
{
|
|
throw new InvalidOperationException("PendingAsyncOperation");
|
|
}
|
|
pendingErrorRead = true;
|
|
// We can't detect if there's a pending sychronous read, stream also doesn't.
|
|
if (error == null)
|
|
{
|
|
if (standardError == null)
|
|
{
|
|
throw new InvalidOperationException("CantGetStandardError");
|
|
}
|
|
Stream s = standardError.BaseStream;
|
|
error = new AsyncStreamReader(this, s, new UserCallBack(this.ErrorReadNotifyUser), standardError.CurrentEncoding);
|
|
}
|
|
error.BeginReadLine();
|
|
}
|
|
/// <devdoc>
|
|
/// <para>
|
|
/// Instructs the <see cref='System.Diagnostics.Process'/> component to cancel the asynchronous operation
|
|
/// specified by BeginOutputReadLine().
|
|
/// </para>
|
|
/// </devdoc>
|
|
[System.Runtime.InteropServices.ComVisible(false)]
|
|
public void CancelOutputRead()
|
|
{
|
|
if (output != null)
|
|
{
|
|
output.CancelOperation();
|
|
}
|
|
else
|
|
{
|
|
throw new InvalidOperationException("NoAsyncOperation");
|
|
}
|
|
pendingOutputRead = false;
|
|
}
|
|
/// <devdoc>
|
|
/// <para>
|
|
/// Instructs the <see cref='System.Diagnostics.Process'/> component to cancel the asynchronous operation
|
|
/// specified by BeginErrorReadLine().
|
|
/// </para>
|
|
/// </devdoc>
|
|
[System.Runtime.InteropServices.ComVisible(false)]
|
|
public void CancelErrorRead()
|
|
{
|
|
if (error != null)
|
|
{
|
|
error.CancelOperation();
|
|
}
|
|
else
|
|
{
|
|
throw new InvalidOperationException("No async operation.");
|
|
}
|
|
pendingErrorRead = false;
|
|
}
|
|
internal void OutputReadNotifyUser(String data)
|
|
{
|
|
// To avoid ---- between remove handler and raising the event
|
|
DataReceivedEventHandler outputDataReceived = OutputDataReceived;
|
|
if (outputDataReceived != null)
|
|
{
|
|
DataReceivedEventArgs e = new DataReceivedEventArgs(data);
|
|
if (SynchronizingObject != null && SynchronizingObject.InvokeRequired)
|
|
{
|
|
SynchronizingObject.Invoke(outputDataReceived, new object[]
|
|
{
|
|
this, e
|
|
});
|
|
}
|
|
else
|
|
{
|
|
outputDataReceived(this, e); // Call back to user informing data is available.
|
|
}
|
|
}
|
|
}
|
|
internal void ErrorReadNotifyUser(String data)
|
|
{
|
|
// To avoid ---- between remove handler and raising the event
|
|
DataReceivedEventHandler errorDataReceived = ErrorDataReceived;
|
|
if (errorDataReceived != null)
|
|
{
|
|
DataReceivedEventArgs e = new DataReceivedEventArgs(data);
|
|
if (SynchronizingObject != null && SynchronizingObject.InvokeRequired)
|
|
{
|
|
SynchronizingObject.Invoke(errorDataReceived, new object[]
|
|
{
|
|
this, e
|
|
});
|
|
}
|
|
else
|
|
{
|
|
errorDataReceived(this, e); // Call back to user informing data is available.
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// A desired internal state.
|
|
/// </summary>
|
|
/// <internalonly/>
|
|
enum State
|
|
{
|
|
HaveId = 0x1,
|
|
IsLocal = 0x2,
|
|
IsNt = 0x4,
|
|
HaveProcessInfo = 0x8,
|
|
Exited = 0x10,
|
|
Associated = 0x20,
|
|
IsWin2k = 0x40,
|
|
HaveNtProcessInfo = HaveProcessInfo | IsNt
|
|
}
|
|
}
|
|
|
|
/// <devdoc>
|
|
/// This data structure contains information about a process that is collected
|
|
/// in bulk by querying the operating system. The reason to make this a separate
|
|
/// structure from the process component is so that we can throw it away all at once
|
|
/// when Refresh is called on the component.
|
|
/// </devdoc>
|
|
/// <internalonly/>
|
|
internal class ProcessInfo
|
|
{
|
|
public ArrayList threadInfoList = new ArrayList();
|
|
public int basePriority;
|
|
public string processName;
|
|
public int processId;
|
|
public int handleCount;
|
|
public long poolPagedBytes;
|
|
public long poolNonpagedBytes;
|
|
public long virtualBytes;
|
|
public long virtualBytesPeak;
|
|
public long workingSetPeak;
|
|
public long workingSet;
|
|
public long pageFileBytesPeak;
|
|
public long pageFileBytes;
|
|
public long privateBytes;
|
|
public int mainModuleId; // used only for win9x - id is only for use with CreateToolHelp32
|
|
public int sessionId;
|
|
}
|
|
|
|
/// <devdoc>
|
|
/// This data structure contains information about a thread in a process that
|
|
/// is collected in bulk by querying the operating system. The reason to
|
|
/// make this a separate structure from the ProcessThread component is so that we
|
|
/// can throw it away all at once when Refresh is called on the component.
|
|
/// </devdoc>
|
|
/// <internalonly/>
|
|
internal class ThreadInfo
|
|
{
|
|
public int threadId;
|
|
public int processId;
|
|
public int basePriority;
|
|
public int currentPriority;
|
|
public IntPtr startAddress;
|
|
public System.Diagnostics.ThreadState threadState;
|
|
public ThreadWaitReason threadWaitReason;
|
|
}
|
|
|
|
/// <devdoc>
|
|
/// This data structure contains information about a module in a process that
|
|
/// is collected in bulk by querying the operating system. The reason to
|
|
/// make this a separate structure from the ProcessModule component is so that we
|
|
/// can throw it away all at once when Refresh is called on the component.
|
|
/// </devdoc>
|
|
/// <internalonly/>
|
|
internal class ModuleInfo
|
|
{
|
|
public string baseName;
|
|
public string fileName;
|
|
public IntPtr baseOfDll;
|
|
public IntPtr entryPoint;
|
|
public int sizeOfImage;
|
|
public int Id; // used only on win9x - for matching up with ProcessInfo.mainModuleId
|
|
}
|
|
|
|
internal static class EnvironmentBlock
|
|
{
|
|
public static byte[] ToByteArray(StringDictionary sd, bool unicode)
|
|
{
|
|
// get the keys
|
|
string[] keys = new string[sd.Count];
|
|
byte[] envBlock = null;
|
|
sd.Keys.CopyTo(keys, 0);
|
|
|
|
// get the values
|
|
string[] values = new string[sd.Count];
|
|
sd.Values.CopyTo(values, 0);
|
|
|
|
// sort both by the keys
|
|
// Windows 2000 requires the environment block to be sorted by the key
|
|
// It will first converting the case the strings and do ordinal comparison.
|
|
Array.Sort(keys, values, OrdinalCaseInsensitiveComparer.Default);
|
|
|
|
// create a list of null terminated "key=val" strings
|
|
StringBuilder stringBuff = new StringBuilder();
|
|
for (int i = 0; i < sd.Count; ++i)
|
|
{
|
|
stringBuff.Append(keys[i]);
|
|
stringBuff.Append('=');
|
|
stringBuff.Append(values[i]);
|
|
stringBuff.Append('\0');
|
|
}
|
|
// an extra null at the end indicates end of list.
|
|
stringBuff.Append('\0');
|
|
if (unicode)
|
|
{
|
|
envBlock = Encoding.Unicode.GetBytes(stringBuff.ToString());
|
|
}
|
|
else
|
|
{
|
|
envBlock = Encoding.Default.GetBytes(stringBuff.ToString());
|
|
if (envBlock.Length > UInt16.MaxValue) throw new InvalidOperationException("Environment block is too long.");
|
|
}
|
|
return envBlock;
|
|
}
|
|
}
|
|
|
|
internal class OrdinalCaseInsensitiveComparer : IComparer
|
|
{
|
|
internal static readonly OrdinalCaseInsensitiveComparer Default = new OrdinalCaseInsensitiveComparer();
|
|
public int Compare(Object a, Object b)
|
|
{
|
|
String sa = a as String;
|
|
String sb = b as String;
|
|
if (sa != null && sb != null)
|
|
{
|
|
return String.Compare(sa, sb, StringComparison.OrdinalIgnoreCase);
|
|
}
|
|
return Comparer.Default.Compare(a, b);
|
|
}
|
|
}
|
|
|
|
internal class ShellExecuteHelper
|
|
{
|
|
private NativeMethods.ShellExecuteInfo _executeInfo;
|
|
private int _errorCode;
|
|
private bool _succeeded;
|
|
public ShellExecuteHelper(NativeMethods.ShellExecuteInfo executeInfo)
|
|
{
|
|
_executeInfo = executeInfo;
|
|
}
|
|
[ResourceExposure(ResourceScope.None)]
|
|
[ResourceConsumption(ResourceScope.Machine, ResourceScope.Machine)]
|
|
public void ShellExecuteFunction()
|
|
{
|
|
if (!(_succeeded = NativeMethods.ShellExecuteEx(_executeInfo)))
|
|
{
|
|
_errorCode = Marshal.GetLastWin32Error();
|
|
}
|
|
}
|
|
public bool ShellExecuteOnSTAThread()
|
|
{
|
|
//
|
|
// SHELL API ShellExecute() requires STA in order to work correctly.
|
|
// If current thread is not a STA thread, we need to call ShellExecute on a new thread.
|
|
//
|
|
if (Thread.CurrentThread.GetApartmentState() != ApartmentState.STA)
|
|
{
|
|
ThreadStart threadStart = new ThreadStart(this.ShellExecuteFunction);
|
|
Thread executionThread = new Thread(threadStart);
|
|
executionThread.SetApartmentState(ApartmentState.STA);
|
|
executionThread.Start();
|
|
executionThread.Join();
|
|
}
|
|
else
|
|
{
|
|
ShellExecuteFunction();
|
|
}
|
|
return _succeeded;
|
|
}
|
|
public int ErrorCode
|
|
{
|
|
get
|
|
{
|
|
return _errorCode;
|
|
}
|
|
}
|
|
}
|
|
|
|
private static long[] CachedBuffer;
|
|
|
|
private static int GetNewBufferSize(int existingBufferSize, int requiredSize)
|
|
{
|
|
if (requiredSize == 0)
|
|
{
|
|
int num = existingBufferSize * 2;
|
|
return num >= existingBufferSize ? num : throw new OutOfMemoryException();
|
|
}
|
|
int num1 = requiredSize + 10240;
|
|
return num1 >= requiredSize ? num1 : throw new OutOfMemoryException();
|
|
}
|
|
|
|
internal static ProcessInfo GetProcessInfo(int processId, string machineName)
|
|
{
|
|
ProcessInfo[] processInfos = GetProcessInfos((Predicate<int>)(pid => pid == processId));
|
|
if (processInfos.Length == 1) return processInfos[0];
|
|
return (ProcessInfo)null;
|
|
}
|
|
internal static ProcessInfo[] GetProcessInfos(Predicate<int> processIdFilter = null)
|
|
{
|
|
int returnedSize = 0;
|
|
GCHandle gcHandle = new GCHandle();
|
|
int num = 131072;
|
|
long[] numArray = Interlocked.Exchange<long[]>(ref CachedBuffer, (long[])null);
|
|
try
|
|
{
|
|
int error;
|
|
do
|
|
{
|
|
if (numArray == null) numArray = new long[(num + 7) / 8];
|
|
else num = numArray.Length * 8;
|
|
gcHandle = GCHandle.Alloc((object)numArray, GCHandleType.Pinned);
|
|
error = NativeMethods.NtQuerySystemInformation(5, gcHandle.AddrOfPinnedObject(), num, out returnedSize);
|
|
if (error == -1073741820)
|
|
{
|
|
if (gcHandle.IsAllocated) gcHandle.Free();
|
|
numArray = (long[])null;
|
|
num = GetNewBufferSize(num, returnedSize);
|
|
}
|
|
} while (error == -1073741820);
|
|
if (error < 0) throw new InvalidOperationException("CouldntGetProcessInfos", (Exception)new Win32Exception(error));
|
|
return GetProcessInfos(gcHandle.AddrOfPinnedObject(), processIdFilter);
|
|
}
|
|
finally
|
|
{
|
|
Interlocked.Exchange<long[]>(ref CachedBuffer, numArray);
|
|
if (gcHandle.IsAllocated) gcHandle.Free();
|
|
}
|
|
}
|
|
private static ProcessInfo[] GetProcessInfos(IntPtr dataPtr, Predicate<int> processIdFilter)
|
|
{
|
|
Hashtable hashtable = new Hashtable(60);
|
|
long num = 0;
|
|
while (true)
|
|
{
|
|
IntPtr ptr1 = (IntPtr)((long)dataPtr + num);
|
|
NativeMethods.SystemProcessInformation structure1 = new NativeMethods.SystemProcessInformation();
|
|
Marshal.PtrToStructure(ptr1, (object)structure1);
|
|
int int32 = structure1.UniqueProcessId.ToInt32();
|
|
if (processIdFilter == null || processIdFilter(int32))
|
|
{
|
|
ProcessInfo processInfo = new ProcessInfo();
|
|
processInfo.processId = int32;
|
|
processInfo.handleCount = (int)structure1.HandleCount;
|
|
processInfo.sessionId = (int)structure1.SessionId;
|
|
processInfo.poolPagedBytes = (long)(ulong)structure1.QuotaPagedPoolUsage;
|
|
processInfo.poolNonpagedBytes = (long)(ulong)structure1.QuotaNonPagedPoolUsage;
|
|
processInfo.virtualBytes = (long)(ulong)structure1.VirtualSize;
|
|
processInfo.virtualBytesPeak = (long)(ulong)structure1.PeakVirtualSize;
|
|
processInfo.workingSetPeak = (long)(ulong)structure1.PeakWorkingSetSize;
|
|
processInfo.workingSet = (long)(ulong)structure1.WorkingSetSize;
|
|
processInfo.pageFileBytesPeak = (long)(ulong)structure1.PeakPagefileUsage;
|
|
processInfo.pageFileBytes = (long)(ulong)structure1.PagefileUsage;
|
|
processInfo.privateBytes = (long)(ulong)structure1.PrivatePageCount;
|
|
processInfo.basePriority = structure1.BasePriority;
|
|
if (structure1.NamePtr == IntPtr.Zero)
|
|
{
|
|
processInfo.processName = processInfo.processId != 4 ?
|
|
(processInfo.processId != 0 ? processInfo.processId.ToString((IFormatProvider)CultureInfo.InvariantCulture) : "Idle") : "System";
|
|
}
|
|
else
|
|
{
|
|
string str = GetProcessShortName(Marshal.PtrToStringUni(structure1.NamePtr, (int)structure1.NameLength / 2));
|
|
processInfo.processName = str;
|
|
}
|
|
hashtable[(object)processInfo.processId] = (object)processInfo;
|
|
IntPtr ptr2 = (IntPtr)((long)ptr1 + (long)Marshal.SizeOf((object)structure1));
|
|
for (int index = 0; (long)index < (long)structure1.NumberOfThreads; ++index)
|
|
{
|
|
NativeMethods.SystemThreadInformation structure2 = new NativeMethods.SystemThreadInformation();
|
|
Marshal.PtrToStructure(ptr2, (object)structure2);
|
|
processInfo.threadInfoList.Add((object)new ThreadInfo()
|
|
{
|
|
processId = (int)structure2.UniqueProcess,
|
|
threadId = (int)structure2.UniqueThread,
|
|
basePriority = structure2.BasePriority,
|
|
currentPriority = structure2.Priority,
|
|
startAddress = structure2.StartAddress,
|
|
threadState = (System.Diagnostics.ThreadState)structure2.ThreadState,
|
|
threadWaitReason = GetThreadWaitReason((int)structure2.WaitReason)
|
|
});
|
|
ptr2 = (IntPtr)((long)ptr2 + (long)Marshal.SizeOf((object)structure2));
|
|
}
|
|
}
|
|
if (structure1.NextEntryOffset != 0U) num += (long)structure1.NextEntryOffset;
|
|
else break;
|
|
}
|
|
ProcessInfo[] processInfos = new ProcessInfo[hashtable.Values.Count];
|
|
hashtable.Values.CopyTo((Array)processInfos, 0);
|
|
return processInfos;
|
|
}
|
|
|
|
internal static ThreadWaitReason GetThreadWaitReason(int value)
|
|
{
|
|
switch (value)
|
|
{
|
|
case 0:
|
|
case 7:
|
|
return ThreadWaitReason.Executive;
|
|
case 1:
|
|
case 8:
|
|
return ThreadWaitReason.FreePage;
|
|
case 2:
|
|
case 9:
|
|
return ThreadWaitReason.PageIn;
|
|
case 3:
|
|
case 10:
|
|
return ThreadWaitReason.SystemAllocation;
|
|
case 4:
|
|
case 11:
|
|
return ThreadWaitReason.ExecutionDelay;
|
|
case 5:
|
|
case 12:
|
|
return ThreadWaitReason.Suspended;
|
|
case 6:
|
|
case 13:
|
|
return ThreadWaitReason.UserRequest;
|
|
case 14:
|
|
return ThreadWaitReason.EventPairHigh;
|
|
case 15:
|
|
return ThreadWaitReason.EventPairLow;
|
|
case 16:
|
|
return ThreadWaitReason.LpcReceive;
|
|
case 17:
|
|
return ThreadWaitReason.LpcReply;
|
|
case 18:
|
|
return ThreadWaitReason.VirtualMemory;
|
|
case 19:
|
|
return ThreadWaitReason.PageOut;
|
|
default:
|
|
return ThreadWaitReason.Unknown;
|
|
}
|
|
}
|
|
|
|
internal static string GetProcessShortName(string name)
|
|
{
|
|
if (string.IsNullOrEmpty(name))
|
|
return string.Empty;
|
|
int num1 = -1;
|
|
int startIndex1 = -1;
|
|
for (int index = 0; index < name.Length; ++index)
|
|
{
|
|
if (name[index] == '\\')
|
|
num1 = index;
|
|
else if (name[index] == '.')
|
|
startIndex1 = index;
|
|
}
|
|
int num2 = startIndex1 != -1 ? (!string.Equals(".exe", name.Substring(startIndex1), StringComparison.OrdinalIgnoreCase) ? name.Length - 1 : startIndex1 - 1) : name.Length - 1;
|
|
int startIndex2 = num1 != -1 ? num1 + 1 : 0;
|
|
return name.Substring(startIndex2, num2 - startIndex2 + 1);
|
|
}
|
|
|
|
[HostProtection(MayLeakOnAbort = true)]
|
|
internal static class NativeMethods
|
|
{
|
|
public static readonly IntPtr INVALID_HANDLE_VALUE = new IntPtr(-1);
|
|
public const int STARTF_USESTDHANDLES = 0x00000100;
|
|
public const int STD_INPUT_HANDLE = -10;
|
|
public const int STD_OUTPUT_HANDLE = -11;
|
|
public const int STD_ERROR_HANDLE = -12;
|
|
public const int STILL_ACTIVE = 0x00000103;
|
|
public const int SW_HIDE = 0;
|
|
public const int WAIT_OBJECT_0 = 0x00000000;
|
|
public const int WAIT_FAILED = unchecked((int)0xFFFFFFFF);
|
|
public const int WAIT_TIMEOUT = 0x00000102;
|
|
public const int WAIT_ABANDONED = 0x00000080;
|
|
public const int ERROR_BAD_EXE_FORMAT = 193;
|
|
public const int ERROR_EXE_MACHINE_TYPE_MISMATCH = 216;
|
|
|
|
[DllImport("ntdll.dll", CharSet = CharSet.Auto)]
|
|
public static extern int NtQuerySystemInformation(
|
|
int query,
|
|
IntPtr dataPtr,
|
|
int size,
|
|
out int returnedSize);
|
|
|
|
[StructLayout(LayoutKind.Sequential)]
|
|
internal class SystemProcessInformation
|
|
{
|
|
internal uint NextEntryOffset;
|
|
internal uint NumberOfThreads;
|
|
private long SpareLi1;
|
|
private long SpareLi2;
|
|
private long SpareLi3;
|
|
private long CreateTime;
|
|
private long UserTime;
|
|
private long KernelTime;
|
|
internal ushort NameLength;
|
|
internal ushort MaximumNameLength;
|
|
internal IntPtr NamePtr;
|
|
internal int BasePriority;
|
|
internal IntPtr UniqueProcessId;
|
|
internal IntPtr InheritedFromUniqueProcessId;
|
|
internal uint HandleCount;
|
|
internal uint SessionId;
|
|
internal UIntPtr PageDirectoryBase;
|
|
internal UIntPtr PeakVirtualSize;
|
|
internal UIntPtr VirtualSize;
|
|
internal uint PageFaultCount;
|
|
internal UIntPtr PeakWorkingSetSize;
|
|
internal UIntPtr WorkingSetSize;
|
|
internal UIntPtr QuotaPeakPagedPoolUsage;
|
|
internal UIntPtr QuotaPagedPoolUsage;
|
|
internal UIntPtr QuotaPeakNonPagedPoolUsage;
|
|
internal UIntPtr QuotaNonPagedPoolUsage;
|
|
internal UIntPtr PagefileUsage;
|
|
internal UIntPtr PeakPagefileUsage;
|
|
internal UIntPtr PrivatePageCount;
|
|
private long ReadOperationCount;
|
|
private long WriteOperationCount;
|
|
private long OtherOperationCount;
|
|
private long ReadTransferCount;
|
|
private long WriteTransferCount;
|
|
private long OtherTransferCount;
|
|
}
|
|
|
|
[StructLayout(LayoutKind.Sequential)]
|
|
internal class SystemThreadInformation
|
|
{
|
|
private long KernelTime;
|
|
private long UserTime;
|
|
private long CreateTime;
|
|
private uint WaitTime;
|
|
internal IntPtr StartAddress;
|
|
internal IntPtr UniqueProcess;
|
|
internal IntPtr UniqueThread;
|
|
internal int Priority;
|
|
internal int BasePriority;
|
|
internal uint ContextSwitches;
|
|
internal uint ThreadState;
|
|
internal uint WaitReason;
|
|
}
|
|
|
|
[StructLayout(LayoutKind.Sequential)]
|
|
internal class STARTUPINFO
|
|
{
|
|
public int cb;
|
|
public IntPtr lpReserved = IntPtr.Zero;
|
|
public string lpDesktop = null;
|
|
public IntPtr lpTitle = IntPtr.Zero;
|
|
public int dwX = 0;
|
|
public int dwY = 0;
|
|
public int dwXSize = 0;
|
|
public int dwYSize = 0;
|
|
public int dwXCountChars = 0;
|
|
public int dwYCountChars = 0;
|
|
public int dwFillAttribute = 0;
|
|
public int dwFlags;
|
|
public short wShowWindow = 0;
|
|
public short cbReserved2 = 0;
|
|
public IntPtr lpReserved2 = IntPtr.Zero;
|
|
public SafeFileHandle hStdInput = new SafeFileHandle(IntPtr.Zero, false);
|
|
public SafeFileHandle hStdOutput = new SafeFileHandle(IntPtr.Zero, false);
|
|
public SafeFileHandle hStdError = new SafeFileHandle(IntPtr.Zero, false);
|
|
public STARTUPINFO()
|
|
{
|
|
cb = Marshal.SizeOf(this);
|
|
}
|
|
public void Dispose()
|
|
{
|
|
// close the handles created for child process
|
|
if (hStdInput != null && !hStdInput.IsInvalid)
|
|
{
|
|
hStdInput.Close();
|
|
hStdInput = null;
|
|
}
|
|
if (hStdOutput != null && !hStdOutput.IsInvalid)
|
|
{
|
|
hStdOutput.Close();
|
|
hStdOutput = null;
|
|
}
|
|
if (hStdError != null && !hStdError.IsInvalid)
|
|
{
|
|
hStdError.Close();
|
|
hStdError = null;
|
|
}
|
|
}
|
|
}
|
|
|
|
[StructLayout(LayoutKind.Sequential)]
|
|
internal class SECURITY_ATTRIBUTES
|
|
{
|
|
public int nLength = 12;
|
|
public SafeLocalMemHandle lpSecurityDescriptor = new SafeLocalMemHandle(IntPtr.Zero, false);
|
|
public bool bInheritHandle;
|
|
}
|
|
|
|
[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
|
|
[ResourceExposure(ResourceScope.None)]
|
|
public static extern bool GetExitCodeProcess(SafeProcessHandle processHandle, out int exitCode);
|
|
[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
|
|
[ResourceExposure(ResourceScope.None)]
|
|
public static extern bool GetProcessTimes(SafeProcessHandle handle, out long creation, out long exit, out long kernel, out long user);
|
|
[DllImport("kernel32.dll", CharSet = CharSet.Ansi, SetLastError = true)]
|
|
[ResourceExposure(ResourceScope.Process)]
|
|
public static extern IntPtr GetStdHandle(int whichHandle);
|
|
[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
|
|
[ResourceExposure(ResourceScope.Process)]
|
|
public static extern bool CreatePipe(out SafeFileHandle hReadPipe, out SafeFileHandle hWritePipe, SECURITY_ATTRIBUTES lpPipeAttributes, int nSize);
|
|
[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true, BestFitMapping = false)]
|
|
[ResourceExposure(ResourceScope.Process)]
|
|
public static extern bool CreateProcess([MarshalAs(UnmanagedType.LPTStr)] string lpApplicationName, // LPCTSTR
|
|
StringBuilder lpCommandLine, // LPTSTR - note: CreateProcess might insert a null somewhere in this string
|
|
SECURITY_ATTRIBUTES lpProcessAttributes, // LPSECURITY_ATTRIBUTES
|
|
SECURITY_ATTRIBUTES lpThreadAttributes, // LPSECURITY_ATTRIBUTES
|
|
bool bInheritHandles, // BOOL
|
|
int dwCreationFlags, // DWORD
|
|
IntPtr lpEnvironment, // LPVOID
|
|
[MarshalAs(UnmanagedType.LPTStr)] string lpCurrentDirectory, // LPCTSTR
|
|
STARTUPINFO lpStartupInfo, // LPSTARTUPINFO
|
|
PROCESS_INFORMATION lpProcessInformation // LPPROCESS_INFORMATION
|
|
);
|
|
[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
|
|
[ResourceExposure(ResourceScope.Machine)]
|
|
public static extern bool TerminateProcess(SafeProcessHandle processHandle, int exitCode);
|
|
[DllImport("kernel32.dll", CharSet = CharSet.Ansi, SetLastError = true)]
|
|
[ResourceExposure(ResourceScope.Process)]
|
|
public static extern IntPtr GetCurrentProcess();
|
|
[DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true, BestFitMapping = false)]
|
|
[SuppressUnmanagedCodeSecurityAttribute]
|
|
[ResourceExposure(ResourceScope.Machine)]
|
|
public static extern bool CreateProcessAsUser(Win32.TokensEx.SafeTokenHandle hToken, string lpApplicationName, StringBuilder lpCommandLine, SECURITY_ATTRIBUTES lpProcessAttributes, SECURITY_ATTRIBUTES lpThreadAttributes,
|
|
bool bInheritHandles, int dwCreationFlags, IntPtr lpEnvironment, string lpCurrentDirectory, STARTUPINFO lpStartupInfo, PROCESS_INFORMATION lpProcessInformation);
|
|
|
|
[DllImport("advapi32", SetLastError = true, CharSet = CharSet.Auto)]
|
|
internal static extern bool CreateProcessWithToken(
|
|
Win32.TokensEx.SafeTokenHandle hToken,
|
|
LogonFlags dwLogonFlags,
|
|
string lpApplicationName,
|
|
StringBuilder lpCommandLine,
|
|
int dwCreationFlags,
|
|
IntPtr lpEnvironment,
|
|
string lpCurrentDirectory,
|
|
STARTUPINFO lpStartupInfo,
|
|
PROCESS_INFORMATION lpProcessInformation);
|
|
|
|
|
|
[DllImport("advapi32.dll", CharSet = CharSet.Unicode, ExactSpelling = true, SetLastError = true, BestFitMapping = false)]
|
|
[ResourceExposure(ResourceScope.Machine)]
|
|
internal static extern bool CreateProcessWithLogonW(string userName, string domain, IntPtr password, LogonFlags logonFlags, [MarshalAs(UnmanagedType.LPTStr)] string appName,
|
|
StringBuilder cmdLine, int creationFlags, IntPtr environmentBlock, [MarshalAs(UnmanagedType.LPTStr)] string lpCurrentDirectory, // LPCTSTR
|
|
STARTUPINFO lpStartupInfo, PROCESS_INFORMATION lpProcessInformation);
|
|
|
|
//TODO: TOKEN
|
|
|
|
[StructLayout(LayoutKind.Sequential)]
|
|
internal class PROCESS_INFORMATION
|
|
{
|
|
public IntPtr hProcess = IntPtr.Zero;
|
|
public IntPtr hThread = IntPtr.Zero;
|
|
public int dwProcessId = 0;
|
|
public int dwThreadId = 0;
|
|
}
|
|
|
|
[Flags]
|
|
internal enum LogonFlags
|
|
{
|
|
LOGON_WITH_PROFILE = 0x00000001,
|
|
LOGON_NETCREDENTIALS_ONLY = 0x00000002
|
|
}
|
|
|
|
public const int QS_KEY = 0x0001,
|
|
QS_MOUSEMOVE = 0x0002,
|
|
QS_MOUSEBUTTON = 0x0004,
|
|
QS_POSTMESSAGE = 0x0008,
|
|
QS_TIMER = 0x0010,
|
|
QS_PAINT = 0x0020,
|
|
QS_SENDMESSAGE = 0x0040,
|
|
QS_HOTKEY = 0x0080,
|
|
QS_ALLPOSTMESSAGE = 0x0100,
|
|
QS_MOUSE = QS_MOUSEMOVE | QS_MOUSEBUTTON,
|
|
QS_INPUT = QS_MOUSE | QS_KEY,
|
|
QS_ALLEVENTS = QS_INPUT | QS_POSTMESSAGE | QS_TIMER | QS_PAINT | QS_HOTKEY,
|
|
QS_ALLINPUT = QS_INPUT | QS_POSTMESSAGE | QS_TIMER | QS_PAINT | QS_HOTKEY | QS_SENDMESSAGE;
|
|
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
|
|
[ResourceExposure(ResourceScope.None)]
|
|
public static extern int WaitForInputIdle(SafeProcessHandle handle, int milliseconds);
|
|
[DllImport("shell32.dll", CharSet = CharSet.Auto, SetLastError = true)]
|
|
[ResourceExposure(ResourceScope.Machine)]
|
|
public static extern bool ShellExecuteEx(ShellExecuteInfo info);
|
|
[DllImport("kernel32.dll", CharSet = CharSet.Ansi, SetLastError = true, BestFitMapping = false)]
|
|
[ResourceExposure(ResourceScope.Machine)]
|
|
public static extern bool DuplicateHandle(HandleRef hSourceProcessHandle, SafeHandle hSourceHandle, HandleRef hTargetProcess, out SafeFileHandle targetHandle, int dwDesiredAccess,
|
|
bool bInheritHandle, int dwOptions);
|
|
[DllImport("kernel32.dll", CharSet = System.Runtime.InteropServices.CharSet.Ansi, SetLastError = true, BestFitMapping = false)]
|
|
[ResourceExposure(ResourceScope.Machine)]
|
|
public static extern bool DuplicateHandle(HandleRef hSourceProcessHandle, SafeHandle hSourceHandle, HandleRef hTargetProcess, out SafeWaitHandle targetHandle, int dwDesiredAccess,
|
|
bool bInheritHandle, int dwOptions);
|
|
[DllImport("user32.dll", CharSet = CharSet.Auto, BestFitMapping = true)]
|
|
[ResourceExposure(ResourceScope.None)]
|
|
public static extern int GetWindowText(HandleRef hWnd, StringBuilder lpString, int nMaxCount);
|
|
[DllImport("user32.dll", CharSet = CharSet.Auto)]
|
|
[ResourceExposure(ResourceScope.None)]
|
|
public static extern int GetWindowTextLength(HandleRef hWnd);
|
|
[DllImport("user32.dll", CharSet = CharSet.Auto)]
|
|
[ResourceExposure(ResourceScope.None)]
|
|
public static extern IntPtr SendMessageTimeout(HandleRef hWnd, int msg, IntPtr wParam, IntPtr lParam, int flags, int timeout, out IntPtr pdwResult);
|
|
[DllImport("user32.dll", CharSet = CharSet.Auto)]
|
|
[ResourceExposure(ResourceScope.None)]
|
|
public static extern int GetWindowLong(HandleRef hWnd, int nIndex);
|
|
[DllImport("user32.dll", CharSet = CharSet.Auto)]
|
|
[ResourceExposure(ResourceScope.None)]
|
|
public static extern int PostMessage(HandleRef hwnd, int msg, IntPtr wparam, IntPtr lparam);
|
|
|
|
[StructLayout(LayoutKind.Sequential)]
|
|
internal class ShellExecuteInfo
|
|
{
|
|
public int cbSize;
|
|
public int fMask;
|
|
public IntPtr hwnd = (IntPtr)0;
|
|
public IntPtr lpVerb = (IntPtr)0;
|
|
public IntPtr lpFile = (IntPtr)0;
|
|
public IntPtr lpParameters = (IntPtr)0;
|
|
public IntPtr lpDirectory = (IntPtr)0;
|
|
public int nShow;
|
|
public IntPtr hInstApp = (IntPtr)0;
|
|
public IntPtr lpIDList = (IntPtr)0;
|
|
public IntPtr lpClass = (IntPtr)0;
|
|
public IntPtr hkeyClass = (IntPtr)0;
|
|
public int dwHotKey = 0;
|
|
public IntPtr hIcon = (IntPtr)0;
|
|
public IntPtr hProcess = (IntPtr)0;
|
|
[ResourceExposure(ResourceScope.Machine)]
|
|
public ShellExecuteInfo()
|
|
{
|
|
cbSize = Marshal.SizeOf(this);
|
|
}
|
|
}
|
|
|
|
[StructLayout(LayoutKind.Sequential)]
|
|
internal struct LUID
|
|
{
|
|
public int LowPart;
|
|
public int HighPart;
|
|
}
|
|
|
|
public const int SEE_MASK_NOCLOSEPROCESS = 0x00000040;
|
|
public const int SEE_MASK_CONNECTNETDRV = 0x00000080;
|
|
public const int SEE_MASK_FLAG_DDEWAIT = 0x00000100;
|
|
public const int SEE_MASK_DOENVSUBST = 0x00000200;
|
|
public const int SEE_MASK_FLAG_NO_UI = 0x00000400;
|
|
public const int PROCESS_TERMINATE = 0x0001;
|
|
public const int PROCESS_QUERY_INFORMATION = 0x0400;
|
|
public const int PROCESS_QUERY_LIMITED_INFORMATION = 0x1000;
|
|
public const int STANDARD_RIGHTS_REQUIRED = 0x000F0000;
|
|
public const int SYNCHRONIZE = 0x00100000;
|
|
public const int PROCESS_ALL_ACCESS = STANDARD_RIGHTS_REQUIRED | SYNCHRONIZE | 0xFFF;
|
|
public const int READ_CONTROL = 0x00020000;
|
|
public const int STANDARD_RIGHTS_READ = READ_CONTROL;
|
|
public const int KEY_QUERY_VALUE = 0x0001;
|
|
public const int KEY_ENUMERATE_SUB_KEYS = 0x0008;
|
|
public const int KEY_NOTIFY = 0x0010;
|
|
public const int ERROR_BROKEN_PIPE = 109;
|
|
public const int ERROR_NO_DATA = 232;
|
|
public const int ERROR_HANDLE_EOF = 38;
|
|
public const int ERROR_IO_INCOMPLETE = 996;
|
|
public const int ERROR_IO_PENDING = 997;
|
|
public const int ERROR_FILE_EXISTS = 0x50;
|
|
public const int ERROR_FILENAME_EXCED_RANGE = 0xCE; // filename too long.
|
|
public const int ERROR_MORE_DATA = 234;
|
|
public const int ERROR_CANCELLED = 1223;
|
|
public const int ERROR_FILE_NOT_FOUND = 2;
|
|
public const int ERROR_PATH_NOT_FOUND = 3;
|
|
public const int ERROR_ACCESS_DENIED = 5;
|
|
public const int ERROR_INVALID_HANDLE = 6;
|
|
public const int ERROR_NOT_ENOUGH_MEMORY = 8;
|
|
public const int ERROR_BAD_COMMAND = 22;
|
|
public const int ERROR_SHARING_VIOLATION = 32;
|
|
public const int ERROR_OPERATION_ABORTED = 995;
|
|
public const int ERROR_NO_ASSOCIATION = 1155;
|
|
public const int ERROR_DLL_NOT_FOUND = 1157;
|
|
public const int ERROR_DDE_FAIL = 1156;
|
|
public const int ERROR_INVALID_PARAMETER = 87;
|
|
public const int ERROR_PARTIAL_COPY = 299;
|
|
public const int ERROR_SUCCESS = 0;
|
|
public const int ERROR_ALREADY_EXISTS = 183;
|
|
public const int ERROR_COUNTER_TIMEOUT = 1121;
|
|
public const int DUPLICATE_CLOSE_SOURCE = 1;
|
|
public const int DUPLICATE_SAME_ACCESS = 2;
|
|
public const int SE_ERR_FNF = 2;
|
|
public const int SE_ERR_PNF = 3;
|
|
public const int SE_ERR_ACCESSDENIED = 5;
|
|
public const int SE_ERR_OOM = 8;
|
|
public const int SE_ERR_DLLNOTFOUND = 32;
|
|
public const int SE_ERR_SHARE = 26;
|
|
public const int SE_ERR_ASSOCINCOMPLETE = 27;
|
|
public const int SE_ERR_DDETIMEOUT = 28;
|
|
public const int SE_ERR_DDEFAIL = 29;
|
|
public const int SE_ERR_DDEBUSY = 30;
|
|
public const int SE_ERR_NOASSOC = 31;
|
|
public const int CREATE_NO_WINDOW = 0x08000000;
|
|
public const int CREATE_SUSPENDED = 0x00000004;
|
|
public const int CREATE_UNICODE_ENVIRONMENT = 0x00000400;
|
|
public const int SMTO_ABORTIFHUNG = 0x0002;
|
|
public const int GWL_STYLE = -16;
|
|
public const int GCL_WNDPROC = -24;
|
|
public const int GWL_WNDPROC = -4;
|
|
public const int WS_DISABLED = 0x08000000;
|
|
public const int WM_NULL = 0x0000;
|
|
public const int WM_CLOSE = 0x0010;
|
|
public const int SW_SHOWNORMAL = 1;
|
|
public const int SW_NORMAL = 1;
|
|
public const int SW_SHOWMINIMIZED = 2;
|
|
public const int SW_SHOWMAXIMIZED = 3;
|
|
public const int SW_MAXIMIZE = 3;
|
|
public const int SW_SHOWNOACTIVATE = 4;
|
|
public const int SW_SHOW = 5;
|
|
public const int SW_MINIMIZE = 6;
|
|
public const int SW_SHOWMINNOACTIVE = 7;
|
|
public const int SW_SHOWNA = 8;
|
|
public const int SW_RESTORE = 9;
|
|
public const int SW_SHOWDEFAULT = 10;
|
|
public const int SW_MAX = 10;
|
|
public const int GW_OWNER = 4;
|
|
public const int WHITENESS = 0x00FF0062;
|
|
}
|
|
|
|
internal delegate void UserCallBack(String data);
|
|
|
|
internal class AsyncStreamReader : IDisposable
|
|
{
|
|
internal const int DefaultBufferSize = 1024; // Byte buffer size
|
|
private const int MinBufferSize = 128;
|
|
private Stream stream;
|
|
private Encoding encoding;
|
|
private Decoder decoder;
|
|
private byte[] byteBuffer;
|
|
private char[] charBuffer;
|
|
// Record the number of valid bytes in the byteBuffer, for a few checks.
|
|
|
|
// This is the maximum number of chars we can get from one call to
|
|
// ReadBuffer. Used so ReadBuffer can tell when to copy data into
|
|
// a user's char[] directly, instead of our internal char[].
|
|
private int _maxCharsPerBuffer;
|
|
|
|
// Store a backpointer to the process class, to check for user callbacks
|
|
private Process process;
|
|
|
|
// Delegate to call user function.
|
|
private UserCallBack userCallBack;
|
|
|
|
// Internal Cancel operation
|
|
private bool cancelOperation;
|
|
private ManualResetEvent eofEvent;
|
|
private Queue messageQueue;
|
|
private StringBuilder sb;
|
|
private bool bLastCarriageReturn;
|
|
|
|
// Cache the last position scanned in sb when searching for lines.
|
|
private int currentLinePos;
|
|
internal AsyncStreamReader(Process process, Stream stream, UserCallBack callback, Encoding encoding) : this(process, stream, callback, encoding, DefaultBufferSize) { }
|
|
|
|
// Creates a new AsyncStreamReader for the given stream. The
|
|
// character encoding is set by encoding and the buffer size,
|
|
// in number of 16-bit characters, is set by bufferSize.
|
|
//
|
|
internal AsyncStreamReader(Process process, Stream stream, UserCallBack callback, Encoding encoding, int bufferSize)
|
|
{
|
|
Init(process, stream, callback, encoding, bufferSize);
|
|
messageQueue = new Queue();
|
|
}
|
|
private void Init(Process process, Stream stream, UserCallBack callback, Encoding encoding, int bufferSize)
|
|
{
|
|
this.process = process;
|
|
this.stream = stream;
|
|
this.encoding = encoding;
|
|
this.userCallBack = callback;
|
|
decoder = encoding.GetDecoder();
|
|
if (bufferSize < MinBufferSize) bufferSize = MinBufferSize;
|
|
byteBuffer = new byte[bufferSize];
|
|
_maxCharsPerBuffer = encoding.GetMaxCharCount(bufferSize);
|
|
charBuffer = new char[_maxCharsPerBuffer];
|
|
cancelOperation = false;
|
|
eofEvent = new ManualResetEvent(false);
|
|
sb = null;
|
|
this.bLastCarriageReturn = false;
|
|
}
|
|
public virtual void Close()
|
|
{
|
|
Dispose(true);
|
|
}
|
|
void IDisposable.Dispose()
|
|
{
|
|
Dispose(true);
|
|
GC.SuppressFinalize(this);
|
|
}
|
|
protected virtual void Dispose(bool disposing)
|
|
{
|
|
if (disposing)
|
|
{
|
|
if (stream != null) stream.Close();
|
|
}
|
|
if (stream != null)
|
|
{
|
|
stream = null;
|
|
encoding = null;
|
|
decoder = null;
|
|
byteBuffer = null;
|
|
charBuffer = null;
|
|
}
|
|
if (eofEvent != null)
|
|
{
|
|
eofEvent.Close();
|
|
eofEvent = null;
|
|
}
|
|
}
|
|
public virtual Encoding CurrentEncoding
|
|
{
|
|
get
|
|
{
|
|
return encoding;
|
|
}
|
|
}
|
|
public virtual Stream BaseStream
|
|
{
|
|
get
|
|
{
|
|
return stream;
|
|
}
|
|
}
|
|
|
|
// User calls BeginRead to start the asynchronous read
|
|
internal void BeginReadLine()
|
|
{
|
|
if (cancelOperation)
|
|
{
|
|
cancelOperation = false;
|
|
}
|
|
if (sb == null)
|
|
{
|
|
sb = new StringBuilder(DefaultBufferSize);
|
|
stream.BeginRead(byteBuffer, 0, byteBuffer.Length, new AsyncCallback(ReadBuffer), null);
|
|
}
|
|
else
|
|
{
|
|
FlushMessageQueue();
|
|
}
|
|
}
|
|
internal void CancelOperation()
|
|
{
|
|
cancelOperation = true;
|
|
}
|
|
|
|
// This is the async callback function. Only one thread could/should call this.
|
|
private void ReadBuffer(IAsyncResult ar)
|
|
{
|
|
int byteLen;
|
|
try
|
|
{
|
|
byteLen = stream.EndRead(ar);
|
|
}
|
|
catch (IOException)
|
|
{
|
|
// We should ideally consume errors from operations getting cancelled
|
|
// so that we don't crash the unsuspecting parent with an unhandled exc.
|
|
// This seems to come in 2 forms of exceptions (depending on platform and scenario),
|
|
// namely OperationCanceledException and IOException (for errorcode that we don't
|
|
// map explicitly).
|
|
byteLen = 0; // Treat this as EOF
|
|
}
|
|
catch (OperationCanceledException)
|
|
{
|
|
// We should consume any OperationCanceledException from child read here
|
|
// so that we don't crash the parent with an unhandled exc
|
|
byteLen = 0; // Treat this as EOF
|
|
}
|
|
if (byteLen == 0)
|
|
{
|
|
// We're at EOF, we won't call this function again from here on.
|
|
lock (messageQueue)
|
|
{
|
|
if (sb.Length != 0)
|
|
{
|
|
messageQueue.Enqueue(sb.ToString());
|
|
sb.Length = 0;
|
|
}
|
|
messageQueue.Enqueue(null);
|
|
}
|
|
try
|
|
{
|
|
// UserCallback could throw, we should still set the eofEvent
|
|
FlushMessageQueue();
|
|
}
|
|
finally
|
|
{
|
|
eofEvent.Set();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
int charLen = decoder.GetChars(byteBuffer, 0, byteLen, charBuffer, 0);
|
|
sb.Append(charBuffer, 0, charLen);
|
|
GetLinesFromStringBuilder();
|
|
stream.BeginRead(byteBuffer, 0, byteBuffer.Length, new AsyncCallback(ReadBuffer), null);
|
|
}
|
|
}
|
|
|
|
// Read lines stored in StringBuilder and the buffer we just read into.
|
|
// A line is defined as a sequence of characters followed by
|
|
// a carriage return ('\r'), a line feed ('\n'), or a carriage return
|
|
// immediately followed by a line feed. The resulting string does not
|
|
// contain the terminating carriage return and/or line feed. The returned
|
|
// value is null if the end of the input stream has been reached.
|
|
//
|
|
private void GetLinesFromStringBuilder()
|
|
{
|
|
int currentIndex = currentLinePos;
|
|
int lineStart = 0;
|
|
int len = sb.Length;
|
|
|
|
// skip a beginning '\n' character of new block if last block ended
|
|
// with '\r'
|
|
if (bLastCarriageReturn && (len > 0) && sb[0] == '\n')
|
|
{
|
|
currentIndex = 1;
|
|
lineStart = 1;
|
|
bLastCarriageReturn = false;
|
|
}
|
|
while (currentIndex < len)
|
|
{
|
|
char ch = sb[currentIndex];
|
|
// Note the following common line feed chars:
|
|
// \n - UNIX \r\n - DOS \r - Mac
|
|
if (ch == '\r' || ch == '\n')
|
|
{
|
|
string s = sb.ToString(lineStart, currentIndex - lineStart);
|
|
lineStart = currentIndex + 1;
|
|
// skip the "\n" character following "\r" character
|
|
if ((ch == '\r') && (lineStart < len) && (sb[lineStart] == '\n'))
|
|
{
|
|
lineStart++;
|
|
currentIndex++;
|
|
}
|
|
lock (messageQueue)
|
|
{
|
|
messageQueue.Enqueue(s);
|
|
}
|
|
}
|
|
currentIndex++;
|
|
}
|
|
// Protect length as IndexOutOfRangeException was being thrown when less than a
|
|
// character's worth of bytes was read at the beginning of a line.
|
|
if (len > 0 && sb[len - 1] == '\r')
|
|
{
|
|
bLastCarriageReturn = true;
|
|
}
|
|
// Keep the rest characaters which can't form a new line in string builder.
|
|
if (lineStart < len)
|
|
{
|
|
if (lineStart == 0)
|
|
{
|
|
// we found no breaklines, in this case we cache the position
|
|
// so next time we don't have to restart from the beginning
|
|
currentLinePos = currentIndex;
|
|
}
|
|
else
|
|
{
|
|
sb.Remove(0, lineStart);
|
|
currentLinePos = 0;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
sb.Length = 0;
|
|
currentLinePos = 0;
|
|
}
|
|
FlushMessageQueue();
|
|
}
|
|
private void FlushMessageQueue()
|
|
{
|
|
while (true)
|
|
{
|
|
// When we call BeginReadLine, we also need to flush the queue
|
|
// So there could be a ---- between the ReadBuffer and BeginReadLine
|
|
// We need to take lock before DeQueue.
|
|
if (messageQueue.Count > 0)
|
|
{
|
|
lock (messageQueue)
|
|
{
|
|
if (messageQueue.Count > 0)
|
|
{
|
|
string s = (string)messageQueue.Dequeue();
|
|
// skip if the read is the read is cancelled
|
|
// this might happen inside UserCallBack
|
|
// However, continue to drain the queue
|
|
if (!cancelOperation)
|
|
{
|
|
userCallBack(s);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Wait until we hit EOF. This is called from Process.WaitForExit
|
|
// We will lose some information if we don't do this.
|
|
internal void WaitUtilEOF()
|
|
{
|
|
if (eofEvent != null)
|
|
{
|
|
eofEvent.WaitOne();
|
|
eofEvent.Close();
|
|
eofEvent = null;
|
|
}
|
|
}
|
|
}
|
|
|
|
public delegate void DataReceivedEventHandler(Object sender, DataReceivedEventArgs e);
|
|
|
|
public class DataReceivedEventArgs : EventArgs
|
|
{
|
|
internal string _data;
|
|
internal DataReceivedEventArgs(string data) => this._data = data;
|
|
public string Data => this._data;
|
|
}
|
|
|
|
internal class ProcessWaitHandle : WaitHandle
|
|
{
|
|
[ResourceExposure(ResourceScope.None)]
|
|
[ResourceConsumption(ResourceScope.Machine, ResourceScope.Machine)]
|
|
internal ProcessWaitHandle(SafeProcessHandle processHandle) : base()
|
|
{
|
|
SafeWaitHandle waitHandle = null;
|
|
bool succeeded = NativeMethods.DuplicateHandle(new HandleRef(this, NativeMethods.GetCurrentProcess()), processHandle, new HandleRef(this, NativeMethods.GetCurrentProcess()), out waitHandle,
|
|
0, false, NativeMethods.DUPLICATE_SAME_ACCESS);
|
|
if (!succeeded)
|
|
{
|
|
Marshal.ThrowExceptionForHR(Marshal.GetHRForLastWin32Error());
|
|
}
|
|
this.SafeWaitHandle = waitHandle;
|
|
}
|
|
}
|
|
|
|
[SuppressUnmanagedCodeSecurityAttribute]
|
|
internal sealed class SafeThreadHandle : SafeHandleZeroOrMinusOneIsInvalid
|
|
{
|
|
internal SafeThreadHandle() : base(true) { }
|
|
internal void InitialSetHandle(IntPtr h)
|
|
{
|
|
Debug.Assert(base.IsInvalid, "Safe handle should only be set once");
|
|
base.SetHandle(h);
|
|
}
|
|
override protected bool ReleaseHandle()
|
|
{
|
|
return CloseHandle(handle);
|
|
}
|
|
[DllImport("kernel32.dll", ExactSpelling = true, CharSet = System.Runtime.InteropServices.CharSet.Auto, SetLastError = true)]
|
|
public static extern bool CloseHandle(IntPtr handle);
|
|
}
|
|
|
|
[HostProtectionAttribute(MayLeakOnAbort = true)]
|
|
[SuppressUnmanagedCodeSecurityAttribute]
|
|
internal sealed class SafeLocalMemHandle : SafeHandleZeroOrMinusOneIsInvalid
|
|
{
|
|
[SecurityPermission(SecurityAction.LinkDemand, UnmanagedCode = true)]
|
|
internal SafeLocalMemHandle(IntPtr existingHandle, bool ownsHandle) : base(ownsHandle)
|
|
{
|
|
SetHandle(existingHandle);
|
|
}
|
|
[DllImport("kernel32.dll")]
|
|
[ResourceExposure(ResourceScope.None)]
|
|
[ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)]
|
|
private static extern IntPtr LocalFree(IntPtr hMem);
|
|
override protected bool ReleaseHandle()
|
|
{
|
|
return LocalFree(handle) == IntPtr.Zero;
|
|
}
|
|
}
|
|
|
|
[SuppressUnmanagedCodeSecurityAttribute]
|
|
public sealed class SafeProcessHandle : SafeHandleZeroOrMinusOneIsInvalid
|
|
{
|
|
internal static SafeProcessHandle InvalidHandle = new SafeProcessHandle(IntPtr.Zero);
|
|
|
|
// Note that OpenProcess returns 0 on failure
|
|
internal SafeProcessHandle() : base(true) { }
|
|
internal SafeProcessHandle(IntPtr handle) : base(true)
|
|
{
|
|
SetHandle(handle);
|
|
}
|
|
[SecurityPermission(SecurityAction.LinkDemand, UnmanagedCode = true)]
|
|
public SafeProcessHandle(IntPtr existingHandle, bool ownsHandle) : base(ownsHandle)
|
|
{
|
|
SetHandle(existingHandle);
|
|
}
|
|
[DllImport("kernel32.dll", CharSet = System.Runtime.InteropServices.CharSet.Auto, SetLastError = true)]
|
|
[ResourceExposure(ResourceScope.Machine)]
|
|
internal static extern SafeProcessHandle OpenProcess(int access, bool inherit, int processId);
|
|
internal void InitialSetHandle(IntPtr h)
|
|
{
|
|
Debug.Assert(base.IsInvalid, "Safe handle should only be set once");
|
|
base.handle = h;
|
|
}
|
|
override protected bool ReleaseHandle()
|
|
{
|
|
return CloseHandle(handle);
|
|
}
|
|
[DllImport("kernel32.dll", ExactSpelling = true, CharSet = System.Runtime.InteropServices.CharSet.Auto, SetLastError = true)]
|
|
public static extern bool CloseHandle(IntPtr handle);
|
|
}
|
|
|
|
[TypeConverter(typeof(ExpandableObjectConverter)),
|
|
// Disabling partial trust scenarios
|
|
PermissionSet(SecurityAction.LinkDemand, Name = "FullTrust"), HostProtection(SharedState = true, SelfAffectingProcessMgmt = true)]
|
|
public sealed class ProcessStartInfo
|
|
{
|
|
string fileName;
|
|
string arguments;
|
|
string directory;
|
|
string verb;
|
|
ProcessWindowStyle windowStyle;
|
|
bool errorDialog;
|
|
IntPtr errorDialogParentHandle;
|
|
bool useShellExecute = false;
|
|
string userName;
|
|
string domain;
|
|
SecureString password;
|
|
string passwordInClearText;
|
|
bool loadUserProfile;
|
|
bool redirectStandardInput = false;
|
|
bool redirectStandardOutput = false;
|
|
bool redirectStandardError = false;
|
|
Encoding standardOutputEncoding;
|
|
Encoding standardErrorEncoding;
|
|
bool createNoWindow = false;
|
|
WeakReference weakParentProcess;
|
|
internal StringDictionary environmentVariables;
|
|
/// <devdoc>
|
|
/// Default constructor. At least the <see cref='System.Diagnostics.ProcessStartInfo.FileName'/>
|
|
/// property must be set before starting the process.
|
|
/// </devdoc>
|
|
public ProcessStartInfo() { }
|
|
internal ProcessStartInfo(Process parent)
|
|
{
|
|
this.weakParentProcess = new WeakReference(parent);
|
|
}
|
|
/// <devdoc>
|
|
/// Specifies the name of the application or document that is to be started.
|
|
/// </devdoc>
|
|
[ResourceExposure(ResourceScope.Machine)]
|
|
public ProcessStartInfo(string fileName)
|
|
{
|
|
this.fileName = fileName;
|
|
}
|
|
/// <devdoc>
|
|
/// Specifies the name of the application that is to be started, as well as a set
|
|
/// of command line arguments to pass to the application.
|
|
/// </devdoc>
|
|
[ResourceExposure(ResourceScope.Machine)]
|
|
public ProcessStartInfo(string fileName, string arguments)
|
|
{
|
|
this.fileName = fileName;
|
|
this.arguments = arguments;
|
|
}
|
|
/// <devdoc>
|
|
/// <para>
|
|
/// Specifies the verb to use when opening the filename. For example, the "print"
|
|
/// verb will print a document specified using <see cref='System.Diagnostics.ProcessStartInfo.FileName'/>.
|
|
/// Each file extension has it's own set of verbs, which can be obtained using the
|
|
/// <see cref='System.Diagnostics.ProcessStartInfo.Verbs'/> property.
|
|
/// The default verb can be specified using "".
|
|
/// </para>
|
|
/// <note type="rnotes">
|
|
/// Discuss 'opening' vs. 'starting.' I think the part about the
|
|
/// default verb was a dev comment.
|
|
/// Find out what
|
|
/// that means.
|
|
/// </note>
|
|
/// </devdoc>
|
|
public string Verb
|
|
{
|
|
get
|
|
{
|
|
if (verb == null) return string.Empty;
|
|
return verb;
|
|
}
|
|
set
|
|
{
|
|
verb = value;
|
|
}
|
|
}
|
|
public string Arguments
|
|
{
|
|
get
|
|
{
|
|
if (arguments == null) return string.Empty;
|
|
return arguments;
|
|
}
|
|
set
|
|
{
|
|
arguments = value;
|
|
}
|
|
}
|
|
public bool CreateNoWindow
|
|
{
|
|
get
|
|
{
|
|
return createNoWindow;
|
|
}
|
|
set
|
|
{
|
|
createNoWindow = value;
|
|
}
|
|
}
|
|
public StringDictionary EnvironmentVariables
|
|
{
|
|
[ResourceExposure(ResourceScope.Machine)]
|
|
[ResourceConsumption(ResourceScope.Machine)]
|
|
get
|
|
{
|
|
// Note:
|
|
// Creating a detached ProcessStartInfo will pre-populate the environment
|
|
// with current environmental variables.
|
|
|
|
// When used with an existing Process.ProcessStartInfo the following behavior
|
|
// * Desktop - Populates with current Environment (rather than that of the process)
|
|
if (environmentVariables == null)
|
|
{
|
|
environmentVariables = new StringDictionaryWithComparer();
|
|
|
|
// if not in design mode, initialize the child environment block with all the parent variables
|
|
if (!(this.weakParentProcess != null && this.weakParentProcess.IsAlive && ((Component)this.weakParentProcess.Target).Site != null &&
|
|
((Component)this.weakParentProcess.Target).Site.DesignMode))
|
|
{
|
|
foreach (DictionaryEntry entry in System.Environment.GetEnvironmentVariables()) environmentVariables.Add((string)entry.Key, (string)entry.Value);
|
|
}
|
|
}
|
|
return environmentVariables;
|
|
}
|
|
}
|
|
private IDictionary<string, string> environment;
|
|
public IDictionary<string, string> Environment
|
|
{
|
|
get
|
|
{
|
|
if (environment == null)
|
|
{
|
|
environment = this.EnvironmentVariables.AsGenericDictionary();
|
|
}
|
|
return environment;
|
|
}
|
|
}
|
|
public bool RedirectStandardInput
|
|
{
|
|
get
|
|
{
|
|
return redirectStandardInput;
|
|
}
|
|
set
|
|
{
|
|
redirectStandardInput = value;
|
|
}
|
|
}
|
|
public bool RedirectStandardOutput
|
|
{
|
|
get
|
|
{
|
|
return redirectStandardOutput;
|
|
}
|
|
set
|
|
{
|
|
redirectStandardOutput = value;
|
|
}
|
|
}
|
|
public bool RedirectStandardError
|
|
{
|
|
get
|
|
{
|
|
return redirectStandardError;
|
|
}
|
|
set
|
|
{
|
|
redirectStandardError = value;
|
|
}
|
|
}
|
|
public Encoding StandardErrorEncoding
|
|
{
|
|
get
|
|
{
|
|
return standardErrorEncoding;
|
|
}
|
|
set
|
|
{
|
|
standardErrorEncoding = value;
|
|
}
|
|
}
|
|
public Encoding StandardOutputEncoding
|
|
{
|
|
get
|
|
{
|
|
return standardOutputEncoding;
|
|
}
|
|
set
|
|
{
|
|
standardOutputEncoding = value;
|
|
}
|
|
}
|
|
public bool UseShellExecute
|
|
{
|
|
get
|
|
{
|
|
return useShellExecute;
|
|
}
|
|
set
|
|
{
|
|
useShellExecute = value;
|
|
}
|
|
}
|
|
/// <devdoc>
|
|
/// Returns the set of verbs associated with the file specified by the
|
|
/// <see cref='System.Diagnostics.ProcessStartInfo.FileName'/> property.
|
|
/// </devdoc>
|
|
[Browsable(false), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
|
|
public string[] Verbs
|
|
{
|
|
[ResourceExposure(ResourceScope.None)]
|
|
[ResourceConsumption(ResourceScope.Machine, ResourceScope.Machine)]
|
|
get
|
|
{
|
|
ArrayList verbs = new ArrayList();
|
|
RegistryKey key = null;
|
|
string extension = Path.GetExtension(FileName);
|
|
try
|
|
{
|
|
if (extension != null && extension.Length > 0)
|
|
{
|
|
key = Registry.ClassesRoot.OpenSubKey(extension);
|
|
if (key != null)
|
|
{
|
|
string value = (string)key.GetValue(String.Empty);
|
|
key.Close();
|
|
key = Registry.ClassesRoot.OpenSubKey(value + "\\shell");
|
|
if (key != null)
|
|
{
|
|
string[] names = key.GetSubKeyNames();
|
|
for (int i = 0; i < names.Length; i++)
|
|
if (string.Compare(names[i], "new", StringComparison.OrdinalIgnoreCase) != 0)
|
|
verbs.Add(names[i]);
|
|
key.Close();
|
|
key = null;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
finally
|
|
{
|
|
if (key != null) key.Close();
|
|
}
|
|
string[] temp = new string[verbs.Count];
|
|
verbs.CopyTo(temp, 0);
|
|
return temp;
|
|
}
|
|
}
|
|
public string UserName
|
|
{
|
|
get
|
|
{
|
|
if (userName == null)
|
|
{
|
|
return string.Empty;
|
|
}
|
|
else
|
|
{
|
|
return userName;
|
|
}
|
|
}
|
|
set
|
|
{
|
|
userName = value;
|
|
}
|
|
}
|
|
public SecureString Password
|
|
{
|
|
get
|
|
{
|
|
return password;
|
|
}
|
|
set
|
|
{
|
|
password = value;
|
|
}
|
|
}
|
|
public string PasswordInClearText
|
|
{
|
|
get
|
|
{
|
|
return passwordInClearText;
|
|
}
|
|
set
|
|
{
|
|
passwordInClearText = value;
|
|
}
|
|
}
|
|
public string Domain
|
|
{
|
|
get
|
|
{
|
|
if (domain == null)
|
|
{
|
|
return string.Empty;
|
|
}
|
|
else
|
|
{
|
|
return domain;
|
|
}
|
|
}
|
|
set
|
|
{
|
|
domain = value;
|
|
}
|
|
}
|
|
public bool LoadUserProfile
|
|
{
|
|
get
|
|
{
|
|
return loadUserProfile;
|
|
}
|
|
set
|
|
{
|
|
loadUserProfile = value;
|
|
}
|
|
}
|
|
public string FileName
|
|
{
|
|
[ResourceExposure(ResourceScope.Machine)]
|
|
get
|
|
{
|
|
if (fileName == null) return string.Empty;
|
|
return fileName;
|
|
}
|
|
[ResourceExposure(ResourceScope.Machine)]
|
|
set
|
|
{
|
|
fileName = value;
|
|
}
|
|
}
|
|
public string WorkingDirectory
|
|
{
|
|
[ResourceExposure(ResourceScope.Machine)]
|
|
get
|
|
{
|
|
if (directory == null) return string.Empty;
|
|
return directory;
|
|
}
|
|
[ResourceExposure(ResourceScope.Machine)]
|
|
set
|
|
{
|
|
directory = value;
|
|
}
|
|
}
|
|
public bool ErrorDialog
|
|
{
|
|
get
|
|
{
|
|
return errorDialog;
|
|
}
|
|
set
|
|
{
|
|
errorDialog = value;
|
|
}
|
|
}
|
|
[Browsable(false), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
|
|
public IntPtr ErrorDialogParentHandle
|
|
{
|
|
get
|
|
{
|
|
return errorDialogParentHandle;
|
|
}
|
|
set
|
|
{
|
|
errorDialogParentHandle = value;
|
|
}
|
|
}
|
|
public ProcessWindowStyle WindowStyle
|
|
{
|
|
get
|
|
{
|
|
return windowStyle;
|
|
}
|
|
set
|
|
{
|
|
if (!Enum.IsDefined(typeof(ProcessWindowStyle), value)) throw new InvalidEnumArgumentException("value", (int)value, typeof(ProcessWindowStyle));
|
|
windowStyle = value;
|
|
}
|
|
}
|
|
}
|
|
|
|
[Serializable]
|
|
internal class StringDictionaryWithComparer : StringDictionary
|
|
{
|
|
public StringDictionaryWithComparer() : this((IEqualityComparer)StringComparer.OrdinalIgnoreCase) { }
|
|
public StringDictionaryWithComparer(IEqualityComparer comparer) => this.ReplaceHashtable(new Hashtable(comparer));
|
|
public override string this[string key]
|
|
{
|
|
get => key != null ? (string)this.contents[(object)key] : throw new ArgumentNullException(nameof(key));
|
|
set
|
|
{
|
|
if (key == null) throw new ArgumentNullException(nameof(key));
|
|
this.contents[(object)key] = (object)value;
|
|
}
|
|
}
|
|
public override void Add(string key, string value)
|
|
{
|
|
if (key == null) throw new ArgumentNullException(nameof(key));
|
|
this.contents.Add((object)key, (object)value);
|
|
}
|
|
public override bool ContainsKey(string key) => key != null ? this.contents.ContainsKey((object)key) : throw new ArgumentNullException(nameof(key));
|
|
public override void Remove(string key)
|
|
{
|
|
if (key == null) throw new ArgumentNullException(nameof(key));
|
|
this.contents.Remove((object)key);
|
|
}
|
|
}
|
|
|
|
[Serializable]
|
|
public class StringDictionary : IEnumerable
|
|
{
|
|
internal Hashtable contents = new Hashtable();
|
|
/// <summary>Gets the number of key/value pairs in the <see cref="T:System.Collections.Specialized.StringDictionary" />.</summary>
|
|
/// <returns>The number of key/value pairs in the <see cref="T:System.Collections.Specialized.StringDictionary" />.
|
|
/// Retrieving the value of this property is an O(1) operation.</returns>
|
|
public virtual int Count => this.contents.Count;
|
|
/// <summary>Gets a value indicating whether access to the <see cref="T:System.Collections.Specialized.StringDictionary" /> is synchronized (thread safe).</summary>
|
|
/// <returns>
|
|
/// <see langword="true" /> if access to the <see cref="T:System.Collections.Specialized.StringDictionary" /> is synchronized (thread safe); otherwise, <see langword="false" />.</returns>
|
|
public virtual bool IsSynchronized => this.contents.IsSynchronized;
|
|
/// <summary>Gets or sets the value associated with the specified key.</summary>
|
|
/// <param name="key">The key whose value to get or set.</param>
|
|
/// <returns>The value associated with the specified key. If the specified key is not found, Get returns <see langword="null" />, and Set creates a new entry with the specified key.</returns>
|
|
/// <exception cref="T:System.ArgumentNullException">
|
|
/// <paramref name="key" /> is <see langword="null" />.</exception>
|
|
public virtual string this[string key]
|
|
{
|
|
get
|
|
{
|
|
if (key == null) throw new ArgumentNullException(nameof(key));
|
|
return (string)this.contents[(object)key.ToLower(CultureInfo.InvariantCulture)];
|
|
}
|
|
set
|
|
{
|
|
if (key == null) throw new ArgumentNullException(nameof(key));
|
|
this.contents[(object)key.ToLower(CultureInfo.InvariantCulture)] = (object)value;
|
|
}
|
|
}
|
|
/// <summary>Gets a collection of keys in the <see cref="T:System.Collections.Specialized.StringDictionary" />.</summary>
|
|
/// <returns>An <see cref="T:System.Collections.ICollection" /> that provides the keys in the <see cref="T:System.Collections.Specialized.StringDictionary" />.</returns>
|
|
public virtual ICollection Keys => this.contents.Keys;
|
|
/// <summary>Gets an object that can be used to synchronize access to the <see cref="T:System.Collections.Specialized.StringDictionary" />.</summary>
|
|
/// <returns>An <see cref="T:System.Object" /> that can be used to synchronize access to the <see cref="T:System.Collections.Specialized.StringDictionary" />.</returns>
|
|
public virtual object SyncRoot => this.contents.SyncRoot;
|
|
/// <summary>Gets a collection of values in the <see cref="T:System.Collections.Specialized.StringDictionary" />.</summary>
|
|
/// <returns>An <see cref="T:System.Collections.ICollection" /> that provides the values in the <see cref="T:System.Collections.Specialized.StringDictionary" />.</returns>
|
|
public virtual ICollection Values => this.contents.Values;
|
|
/// <summary>Adds an entry with the specified key and value into the <see cref="T:System.Collections.Specialized.StringDictionary" />.</summary>
|
|
/// <param name="key">The key of the entry to add.</param>
|
|
/// <param name="value">The value of the entry to add. The value can be <see langword="null" />.</param>
|
|
/// <exception cref="T:System.ArgumentNullException">
|
|
/// <paramref name="key" /> is <see langword="null" />.</exception>
|
|
/// <exception cref="T:System.ArgumentException">An entry with the same key already exists in the <see cref="T:System.Collections.Specialized.StringDictionary" />.</exception>
|
|
/// <exception cref="T:System.NotSupportedException">The <see cref="T:System.Collections.Specialized.StringDictionary" /> is read-only.</exception>
|
|
public virtual void Add(string key, string value)
|
|
{
|
|
if (key == null) throw new ArgumentNullException(nameof(key));
|
|
this.contents.Add((object)key.ToLower(CultureInfo.InvariantCulture), (object)value);
|
|
}
|
|
/// <summary>Removes all entries from the <see cref="T:System.Collections.Specialized.StringDictionary" />.</summary>
|
|
/// <exception cref="T:System.NotSupportedException">The <see cref="T:System.Collections.Specialized.StringDictionary" /> is read-only.</exception>
|
|
public virtual void Clear() => this.contents.Clear();
|
|
/// <summary>Determines if the <see cref="T:System.Collections.Specialized.StringDictionary" /> contains a specific key.</summary>
|
|
/// <param name="key">The key to locate in the <see cref="T:System.Collections.Specialized.StringDictionary" />.</param>
|
|
/// <returns>
|
|
/// <see langword="true" /> if the <see cref="T:System.Collections.Specialized.StringDictionary" /> contains an entry with the specified key; otherwise, <see langword="false" />.</returns>
|
|
/// <exception cref="T:System.ArgumentNullException">The key is <see langword="null" />.</exception>
|
|
public virtual bool ContainsKey(string key)
|
|
{
|
|
if (key == null) throw new ArgumentNullException(nameof(key));
|
|
return this.contents.ContainsKey((object)key.ToLower(CultureInfo.InvariantCulture));
|
|
}
|
|
/// <summary>Determines if the <see cref="T:System.Collections.Specialized.StringDictionary" /> contains a specific value.</summary>
|
|
/// <param name="value">The value to locate in the <see cref="T:System.Collections.Specialized.StringDictionary" />. The value can be <see langword="null" />.</param>
|
|
/// <returns>
|
|
/// <see langword="true" /> if the <see cref="T:System.Collections.Specialized.StringDictionary" /> contains an element with the specified value; otherwise, <see langword="false" />.</returns>
|
|
public virtual bool ContainsValue(string value) => this.contents.ContainsValue((object)value);
|
|
/// <summary>Copies the string dictionary values to a one-dimensional <see cref="T:System.Array" /> instance at the specified index.</summary>
|
|
/// <param name="array">The one-dimensional <see cref="T:System.Array" /> that is the destination of the values copied from the <see cref="T:System.Collections.Specialized.StringDictionary" />.</param>
|
|
/// <param name="index">The index in the array where copying begins.</param>
|
|
/// <exception cref="T:System.ArgumentException">
|
|
/// <paramref name="array" /> is multidimensional.
|
|
/// -or-
|
|
/// The number of elements in the <see cref="T:System.Collections.Specialized.StringDictionary" /> is greater than the available space from <paramref name="index" /> to the end of <paramref name="array" />.</exception>
|
|
/// <exception cref="T:System.ArgumentNullException">
|
|
/// <paramref name="array" /> is <see langword="null" />.</exception>
|
|
/// <exception cref="T:System.ArgumentOutOfRangeException">
|
|
/// <paramref name="index" /> is less than the lower bound of <paramref name="array" />.</exception>
|
|
public virtual void CopyTo(Array array, int index) => this.contents.CopyTo(array, index);
|
|
/// <summary>Returns an enumerator that iterates through the string dictionary.</summary>
|
|
/// <returns>An <see cref="T:System.Collections.IEnumerator" /> that iterates through the string dictionary.</returns>
|
|
public virtual IEnumerator GetEnumerator() => (IEnumerator)this.contents.GetEnumerator();
|
|
/// <summary>Removes the entry with the specified key from the string dictionary.</summary>
|
|
/// <param name="key">The key of the entry to remove.</param>
|
|
/// <exception cref="T:System.ArgumentNullException">The key is <see langword="null" />.</exception>
|
|
/// <exception cref="T:System.NotSupportedException">The <see cref="T:System.Collections.Specialized.StringDictionary" /> is read-only.</exception>
|
|
public virtual void Remove(string key)
|
|
{
|
|
if (key == null) throw new ArgumentNullException(nameof(key));
|
|
this.contents.Remove((object)key.ToLower(CultureInfo.InvariantCulture));
|
|
}
|
|
internal void ReplaceHashtable(Hashtable useThisHashtableInstead) => this.contents = useThisHashtableInstead;
|
|
internal IDictionary<string, string> AsGenericDictionary() => (IDictionary<string, string>)new StringDictionary.GenericAdapter(this);
|
|
|
|
private class GenericAdapter : IDictionary<string, string>, ICollection<KeyValuePair<string, string>>, IEnumerable<KeyValuePair<string, string>>, IEnumerable
|
|
{
|
|
private StringDictionary m_stringDictionary;
|
|
private StringDictionary.GenericAdapter.ICollectionToGenericCollectionAdapter _values;
|
|
private StringDictionary.GenericAdapter.ICollectionToGenericCollectionAdapter _keys;
|
|
internal GenericAdapter(StringDictionary stringDictionary) => this.m_stringDictionary = stringDictionary;
|
|
public void Add(string key, string value) => this[key] = value;
|
|
public bool ContainsKey(string key) => this.m_stringDictionary.ContainsKey(key);
|
|
public void Clear() => this.m_stringDictionary.Clear();
|
|
public int Count => this.m_stringDictionary.Count;
|
|
public string this[string key]
|
|
{
|
|
get
|
|
{
|
|
if (key == null) throw new ArgumentNullException(nameof(key));
|
|
return this.m_stringDictionary.ContainsKey(key) ? this.m_stringDictionary[key] : throw new KeyNotFoundException();
|
|
}
|
|
set
|
|
{
|
|
if (key == null) throw new ArgumentNullException(nameof(key));
|
|
this.m_stringDictionary[key] = value;
|
|
}
|
|
}
|
|
public ICollection<string> Keys
|
|
{
|
|
get
|
|
{
|
|
if (this._keys == null)
|
|
this._keys = new StringDictionary.GenericAdapter.ICollectionToGenericCollectionAdapter(this.m_stringDictionary, StringDictionary.GenericAdapter.KeyOrValue.Key);
|
|
return (ICollection<string>)this._keys;
|
|
}
|
|
}
|
|
public ICollection<string> Values
|
|
{
|
|
get
|
|
{
|
|
if (this._values == null)
|
|
this._values = new StringDictionary.GenericAdapter.ICollectionToGenericCollectionAdapter(this.m_stringDictionary, StringDictionary.GenericAdapter.KeyOrValue.Value);
|
|
return (ICollection<string>)this._values;
|
|
}
|
|
}
|
|
public bool Remove(string key)
|
|
{
|
|
if (!this.m_stringDictionary.ContainsKey(key)) return false;
|
|
this.m_stringDictionary.Remove(key);
|
|
return true;
|
|
}
|
|
public bool TryGetValue(string key, out string value)
|
|
{
|
|
if (!this.m_stringDictionary.ContainsKey(key))
|
|
{
|
|
value = (string)null;
|
|
return false;
|
|
}
|
|
value = this.m_stringDictionary[key];
|
|
return true;
|
|
}
|
|
void ICollection<KeyValuePair<string, string>>.Add(KeyValuePair<string, string> item) => this.m_stringDictionary.Add(item.Key, item.Value);
|
|
bool ICollection<KeyValuePair<string, string>>.Contains(KeyValuePair<string, string> item)
|
|
{
|
|
string str;
|
|
return this.TryGetValue(item.Key, out str) && str.Equals(item.Value);
|
|
}
|
|
void ICollection<KeyValuePair<string, string>>.CopyTo(KeyValuePair<string, string>[] array, int arrayIndex)
|
|
{
|
|
if (array == null) throw new ArgumentNullException(nameof(array), "ArgumentNull_Array");
|
|
if (arrayIndex < 0) throw new ArgumentOutOfRangeException(nameof(arrayIndex), "ArgumentOutOfRange_NeedNonNegNum");
|
|
if (array.Length - arrayIndex < this.Count) throw new ArgumentException("Arg_ArrayPlusOffTooSmall");
|
|
int num = arrayIndex;
|
|
foreach (DictionaryEntry dictionaryEntry in this.m_stringDictionary) array[num++] = new KeyValuePair<string, string>((string)dictionaryEntry.Key, (string)dictionaryEntry.Value);
|
|
}
|
|
bool ICollection<KeyValuePair<string, string>>.IsReadOnly => false;
|
|
bool ICollection<KeyValuePair<string, string>>.Remove(KeyValuePair<string, string> item)
|
|
{
|
|
if (!((ICollection<KeyValuePair<string, string>>)this).Contains(item)) return false;
|
|
this.m_stringDictionary.Remove(item.Key);
|
|
return true;
|
|
}
|
|
IEnumerator IEnumerable.GetEnumerator() => (IEnumerator)this.GetEnumerator();
|
|
public IEnumerator<KeyValuePair<string, string>> GetEnumerator()
|
|
{
|
|
foreach (DictionaryEntry dictionaryEntry in this.m_stringDictionary) yield return new KeyValuePair<string, string>((string)dictionaryEntry.Key, (string)dictionaryEntry.Value);
|
|
}
|
|
|
|
internal enum KeyOrValue
|
|
{
|
|
Key,
|
|
Value,
|
|
}
|
|
|
|
private class ICollectionToGenericCollectionAdapter : ICollection<string>, IEnumerable<string>, IEnumerable
|
|
{
|
|
private StringDictionary _internal;
|
|
private StringDictionary.GenericAdapter.KeyOrValue _keyOrValue;
|
|
public ICollectionToGenericCollectionAdapter(StringDictionary source, StringDictionary.GenericAdapter.KeyOrValue keyOrValue)
|
|
{
|
|
this._internal = source != null ? source : throw new ArgumentNullException(nameof(source));
|
|
this._keyOrValue = keyOrValue;
|
|
}
|
|
public void Add(string item) => this.ThrowNotSupportedException();
|
|
public void Clear() => this.ThrowNotSupportedException();
|
|
public void ThrowNotSupportedException()
|
|
{
|
|
if (this._keyOrValue == StringDictionary.GenericAdapter.KeyOrValue.Key) throw new NotSupportedException("NotSupported_KeyCollectionSet");
|
|
throw new NotSupportedException("NotSupported_ValueCollectionSet");
|
|
}
|
|
public bool Contains(string item) => this._keyOrValue == StringDictionary.GenericAdapter.KeyOrValue.Key ? this._internal.ContainsKey(item) : this._internal.ContainsValue(item);
|
|
public void CopyTo(string[] array, int arrayIndex) => this.GetUnderlyingCollection().CopyTo((Array)array, arrayIndex);
|
|
public int Count => this._internal.Count;
|
|
public bool IsReadOnly => true;
|
|
public bool Remove(string item)
|
|
{
|
|
this.ThrowNotSupportedException();
|
|
return false;
|
|
}
|
|
private ICollection GetUnderlyingCollection() => this._keyOrValue == StringDictionary.GenericAdapter.KeyOrValue.Key ? this._internal.Keys : this._internal.Values;
|
|
public IEnumerator<string> GetEnumerator()
|
|
{
|
|
foreach (string underlying in (IEnumerable)this.GetUnderlyingCollection()) yield return underlying;
|
|
}
|
|
IEnumerator IEnumerable.GetEnumerator() => this.GetUnderlyingCollection().GetEnumerator();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|