1 // Licensed to the .NET Foundation under one or more agreements. 2 // The .NET Foundation licenses this file to you under the MIT license. 3 // See the LICENSE file in the project root for more information. 4 5 using Microsoft.Win32.SafeHandles; 6 using System.Collections.Generic; 7 using System.ComponentModel; 8 using System.Globalization; 9 using System.IO; 10 using System.Runtime.InteropServices; 11 using System.Security; 12 using System.Text; 13 using System.Threading; 14 15 namespace System.Diagnostics 16 { 17 public partial class Process : IDisposable 18 { 19 /// <summary> 20 /// Creates an array of <see cref="Process"/> components that are associated with process resources on a 21 /// remote computer. These process resources share the specified process name. 22 /// </summary> GetProcessesByName(string processName, string machineName)23 public static Process[] GetProcessesByName(string processName, string machineName) 24 { 25 if (processName == null) 26 { 27 processName = string.Empty; 28 } 29 30 Process[] procs = GetProcesses(machineName); 31 var list = new List<Process>(); 32 33 for (int i = 0; i < procs.Length; i++) 34 { 35 if (string.Equals(processName, procs[i].ProcessName, StringComparison.OrdinalIgnoreCase)) 36 { 37 list.Add(procs[i]); 38 } 39 else 40 { 41 procs[i].Dispose(); 42 } 43 } 44 45 return list.ToArray(); 46 } 47 48 [CLSCompliant(false)] Start(string fileName, string userName, SecureString password, string domain)49 public static Process Start(string fileName, string userName, SecureString password, string domain) 50 { 51 ProcessStartInfo startInfo = new ProcessStartInfo(fileName); 52 startInfo.UserName = userName; 53 startInfo.Password = password; 54 startInfo.Domain = domain; 55 startInfo.UseShellExecute = false; 56 return Start(startInfo); 57 } 58 59 [CLSCompliant(false)] Start(string fileName, string arguments, string userName, SecureString password, string domain)60 public static Process Start(string fileName, string arguments, string userName, SecureString password, string domain) 61 { 62 ProcessStartInfo startInfo = new ProcessStartInfo(fileName, arguments); 63 startInfo.UserName = userName; 64 startInfo.Password = password; 65 startInfo.Domain = domain; 66 startInfo.UseShellExecute = false; 67 return Start(startInfo); 68 } 69 70 /// <summary> 71 /// Puts a Process component in state to interact with operating system processes that run in a 72 /// special mode by enabling the native property SeDebugPrivilege on the current thread. 73 /// </summary> EnterDebugMode()74 public static void EnterDebugMode() 75 { 76 SetPrivilege(Interop.Advapi32.SeDebugPrivilege, (int)Interop.Advapi32.SEPrivileges.SE_PRIVILEGE_ENABLED); 77 } 78 79 /// <summary> 80 /// Takes a Process component out of the state that lets it interact with operating system processes 81 /// that run in a special mode. 82 /// </summary> LeaveDebugMode()83 public static void LeaveDebugMode() 84 { 85 SetPrivilege(Interop.Advapi32.SeDebugPrivilege, 0); 86 } 87 88 /// <summary>Stops the associated process immediately.</summary> Kill()89 public void Kill() 90 { 91 using (SafeProcessHandle handle = GetProcessHandle(Interop.Advapi32.ProcessOptions.PROCESS_TERMINATE)) 92 { 93 if (!Interop.Kernel32.TerminateProcess(handle, -1)) 94 throw new Win32Exception(); 95 } 96 } 97 98 /// <summary>Discards any information about the associated process.</summary> RefreshCore()99 private void RefreshCore() 100 { 101 _signaled = false; 102 } 103 104 /// <summary>Additional logic invoked when the Process is closed.</summary> CloseCore()105 private void CloseCore() 106 { 107 // Nop 108 } 109 110 /// <devdoc> 111 /// Make sure we are watching for a process exit. 112 /// </devdoc> 113 /// <internalonly/> EnsureWatchingForExit()114 private void EnsureWatchingForExit() 115 { 116 if (!_watchingForExit) 117 { 118 lock (this) 119 { 120 if (!_watchingForExit) 121 { 122 Debug.Assert(_haveProcessHandle, "Process.EnsureWatchingForExit called with no process handle"); 123 Debug.Assert(Associated, "Process.EnsureWatchingForExit called with no associated process"); 124 _watchingForExit = true; 125 try 126 { 127 _waitHandle = new Interop.Kernel32.ProcessWaitHandle(_processHandle); 128 _registeredWaitHandle = ThreadPool.RegisterWaitForSingleObject(_waitHandle, 129 new WaitOrTimerCallback(CompletionCallback), null, -1, true); 130 } 131 catch 132 { 133 _watchingForExit = false; 134 throw; 135 } 136 } 137 } 138 } 139 } 140 141 /// <summary> 142 /// Instructs the Process component to wait the specified number of milliseconds for the associated process to exit. 143 /// </summary> WaitForExitCore(int milliseconds)144 private bool WaitForExitCore(int milliseconds) 145 { 146 SafeProcessHandle handle = null; 147 try 148 { 149 handle = GetProcessHandle(Interop.Advapi32.ProcessOptions.SYNCHRONIZE, false); 150 if (handle.IsInvalid) 151 return true; 152 153 using (Interop.Kernel32.ProcessWaitHandle processWaitHandle = new Interop.Kernel32.ProcessWaitHandle(handle)) 154 { 155 return _signaled = processWaitHandle.WaitOne(milliseconds); 156 } 157 } 158 finally 159 { 160 // If we have a hard timeout, we cannot wait for the streams 161 if (_output != null && milliseconds == Timeout.Infinite) 162 _output.WaitUtilEOF(); 163 164 if (_error != null && milliseconds == Timeout.Infinite) 165 _error.WaitUtilEOF(); 166 167 handle?.Dispose(); 168 } 169 } 170 171 /// <summary>Gets the main module for the associated process.</summary> 172 public ProcessModule MainModule 173 { 174 get 175 { 176 // We only return null if we couldn't find a main module. This could be because 177 // the process hasn't finished loading the main module (most likely). 178 // On NT, the first module is the main module. 179 EnsureState(State.HaveId | State.IsLocal); 180 return NtProcessManager.GetFirstModule(_processId); 181 } 182 } 183 184 /// <summary>Checks whether the process has exited and updates state accordingly.</summary> UpdateHasExited()185 private void UpdateHasExited() 186 { 187 using (SafeProcessHandle handle = GetProcessHandle( 188 Interop.Advapi32.ProcessOptions.PROCESS_QUERY_LIMITED_INFORMATION | Interop.Advapi32.ProcessOptions.SYNCHRONIZE, false)) 189 { 190 if (handle.IsInvalid) 191 { 192 _exited = true; 193 } 194 else 195 { 196 int localExitCode; 197 198 // Although this is the wrong way to check whether the process has exited, 199 // it was historically the way we checked for it, and a lot of code then took a dependency on 200 // the fact that this would always be set before the pipes were closed, so they would read 201 // the exit code out after calling ReadToEnd() or standard output or standard error. In order 202 // to allow 259 to function as a valid exit code and to break as few people as possible that 203 // took the ReadToEnd dependency, we check for an exit code before doing the more correct 204 // check to see if we have been signaled. 205 if (Interop.Kernel32.GetExitCodeProcess(handle, out localExitCode) && localExitCode != Interop.Kernel32.HandleOptions.STILL_ACTIVE) 206 { 207 _exitCode = localExitCode; 208 _exited = true; 209 } 210 else 211 { 212 // The best check for exit is that the kernel process object handle is invalid, 213 // or that it is valid and signaled. Checking if the exit code != STILL_ACTIVE 214 // does not guarantee the process is closed, 215 // since some process could return an actual STILL_ACTIVE exit code (259). 216 if (!_signaled) // if we just came from WaitForExit, don't repeat 217 { 218 using (var wh = new Interop.Kernel32.ProcessWaitHandle(handle)) 219 { 220 _signaled = wh.WaitOne(0); 221 } 222 } 223 if (_signaled) 224 { 225 if (!Interop.Kernel32.GetExitCodeProcess(handle, out localExitCode)) 226 throw new Win32Exception(); 227 228 _exitCode = localExitCode; 229 _exited = true; 230 } 231 } 232 } 233 } 234 } 235 236 /// <summary>Gets the time that the associated process exited.</summary> 237 private DateTime ExitTimeCore 238 { 239 get { return GetProcessTimes().ExitTime; } 240 } 241 242 /// <summary>Gets the amount of time the process has spent running code inside the operating system core.</summary> 243 public TimeSpan PrivilegedProcessorTime 244 { 245 get { return GetProcessTimes().PrivilegedProcessorTime; } 246 } 247 248 /// <summary>Gets the time the associated process was started.</summary> 249 internal DateTime StartTimeCore 250 { 251 get { return GetProcessTimes().StartTime; } 252 } 253 254 /// <summary> 255 /// Gets the amount of time the associated process has spent utilizing the CPU. 256 /// It is the sum of the <see cref='System.Diagnostics.Process.UserProcessorTime'/> and 257 /// <see cref='System.Diagnostics.Process.PrivilegedProcessorTime'/>. 258 /// </summary> 259 public TimeSpan TotalProcessorTime 260 { 261 get { return GetProcessTimes().TotalProcessorTime; } 262 } 263 264 /// <summary> 265 /// Gets the amount of time the associated process has spent running code 266 /// inside the application portion of the process (not the operating system core). 267 /// </summary> 268 public TimeSpan UserProcessorTime 269 { 270 get { return GetProcessTimes().UserProcessorTime; } 271 } 272 273 /// <summary> 274 /// Gets or sets a value indicating whether the associated process priority 275 /// should be temporarily boosted by the operating system when the main window 276 /// has focus. 277 /// </summary> 278 private bool PriorityBoostEnabledCore 279 { 280 get 281 { 282 using (SafeProcessHandle handle = GetProcessHandle(Interop.Advapi32.ProcessOptions.PROCESS_QUERY_INFORMATION)) 283 { 284 bool disabled; 285 if (!Interop.Kernel32.GetProcessPriorityBoost(handle, out disabled)) 286 { 287 throw new Win32Exception(); 288 } 289 return !disabled; 290 } 291 } 292 set 293 { 294 using (SafeProcessHandle handle = GetProcessHandle(Interop.Advapi32.ProcessOptions.PROCESS_SET_INFORMATION)) 295 { 296 if (!Interop.Kernel32.SetProcessPriorityBoost(handle, !value)) 297 throw new Win32Exception(); 298 } 299 } 300 } 301 302 /// <summary> 303 /// Gets or sets the overall priority category for the associated process. 304 /// </summary> 305 private ProcessPriorityClass PriorityClassCore 306 { 307 get 308 { 309 using (SafeProcessHandle handle = GetProcessHandle(Interop.Advapi32.ProcessOptions.PROCESS_QUERY_INFORMATION)) 310 { 311 int value = Interop.Kernel32.GetPriorityClass(handle); 312 if (value == 0) 313 { 314 throw new Win32Exception(); 315 } 316 return (ProcessPriorityClass)value; 317 } 318 } 319 set 320 { 321 using (SafeProcessHandle handle = GetProcessHandle(Interop.Advapi32.ProcessOptions.PROCESS_SET_INFORMATION)) 322 { 323 if (!Interop.Kernel32.SetPriorityClass(handle, (int)value)) 324 throw new Win32Exception(); 325 } 326 } 327 } 328 329 /// <summary> 330 /// Gets or sets which processors the threads in this process can be scheduled to run on. 331 /// </summary> 332 private IntPtr ProcessorAffinityCore 333 { 334 get 335 { 336 using (SafeProcessHandle handle = GetProcessHandle(Interop.Advapi32.ProcessOptions.PROCESS_QUERY_INFORMATION)) 337 { 338 IntPtr processAffinity, systemAffinity; 339 if (!Interop.Kernel32.GetProcessAffinityMask(handle, out processAffinity, out systemAffinity)) 340 throw new Win32Exception(); 341 return processAffinity; 342 } 343 } 344 set 345 { 346 using (SafeProcessHandle handle = GetProcessHandle(Interop.Advapi32.ProcessOptions.PROCESS_SET_INFORMATION)) 347 { 348 if (!Interop.Kernel32.SetProcessAffinityMask(handle, value)) 349 throw new Win32Exception(); 350 } 351 } 352 } 353 354 /// <summary>Gets the ID of the current process.</summary> GetCurrentProcessId()355 private static int GetCurrentProcessId() 356 { 357 return unchecked((int)Interop.Kernel32.GetCurrentProcessId()); 358 } 359 360 /// <summary> 361 /// Gets a short-term handle to the process, with the given access. If a handle exists, 362 /// then it is reused. If the process has exited, it throws an exception. 363 /// </summary> GetProcessHandle()364 private SafeProcessHandle GetProcessHandle() 365 { 366 return GetProcessHandle(Interop.Advapi32.ProcessOptions.PROCESS_ALL_ACCESS); 367 } 368 369 /// <summary>Get the minimum and maximum working set limits.</summary> GetWorkingSetLimits(out IntPtr minWorkingSet, out IntPtr maxWorkingSet)370 private void GetWorkingSetLimits(out IntPtr minWorkingSet, out IntPtr maxWorkingSet) 371 { 372 using (SafeProcessHandle handle = GetProcessHandle(Interop.Advapi32.ProcessOptions.PROCESS_QUERY_INFORMATION)) 373 { 374 int ignoredFlags; 375 if (!Interop.Kernel32.GetProcessWorkingSetSizeEx(handle, out minWorkingSet, out maxWorkingSet, out ignoredFlags)) 376 throw new Win32Exception(); 377 } 378 } 379 380 /// <summary>Sets one or both of the minimum and maximum working set limits.</summary> 381 /// <param name="newMin">The new minimum working set limit, or null not to change it.</param> 382 /// <param name="newMax">The new maximum working set limit, or null not to change it.</param> 383 /// <param name="resultingMin">The resulting minimum working set limit after any changes applied.</param> 384 /// <param name="resultingMax">The resulting maximum working set limit after any changes applied.</param> 385 private void SetWorkingSetLimitsCore(IntPtr? newMin, IntPtr? newMax, out IntPtr resultingMin, out IntPtr resultingMax) 386 { 387 using (SafeProcessHandle handle = GetProcessHandle(Interop.Advapi32.ProcessOptions.PROCESS_QUERY_INFORMATION | Interop.Advapi32.ProcessOptions.PROCESS_SET_QUOTA)) 388 { 389 IntPtr min, max; 390 int ignoredFlags; 391 if (!Interop.Kernel32.GetProcessWorkingSetSizeEx(handle, out min, out max, out ignoredFlags)) 392 { 393 throw new Win32Exception(); 394 } 395 396 if (newMin.HasValue) 397 { 398 min = newMin.Value; 399 } 400 if (newMax.HasValue) 401 { 402 max = newMax.Value; 403 } 404 405 if ((long)min > (long)max) 406 { 407 if (newMin != null) 408 { 409 throw new ArgumentException(SR.BadMinWorkset); 410 } 411 else 412 { 413 throw new ArgumentException(SR.BadMaxWorkset); 414 } 415 } 416 417 // We use SetProcessWorkingSetSizeEx which gives an option to follow 418 // the max and min value even in low-memory and abundant-memory situations. 419 // However, we do not use these flags to emulate the existing behavior 420 if (!Interop.Kernel32.SetProcessWorkingSetSizeEx(handle, min, max, 0)) 421 { 422 throw new Win32Exception(); 423 } 424 425 // The value may be rounded/changed by the OS, so go get it 426 if (!Interop.Kernel32.GetProcessWorkingSetSizeEx(handle, out min, out max, out ignoredFlags)) 427 { 428 throw new Win32Exception(); 429 } 430 431 resultingMin = min; 432 resultingMax = max; 433 } 434 } 435 436 private static readonly IntPtr INVALID_HANDLE_VALUE = new IntPtr(-1); 437 438 /// <summary>Starts the process using the supplied start info.</summary> 439 /// <param name="startInfo">The start info with which to start the process.</param> StartWithCreateProcess(ProcessStartInfo startInfo)440 private bool StartWithCreateProcess(ProcessStartInfo startInfo) 441 { 442 // See knowledge base article Q190351 for an explanation of the following code. Noteworthy tricky points: 443 // * The handles are duplicated as non-inheritable before they are passed to CreateProcess so 444 // that the child process can not close them 445 // * CreateProcess allows you to redirect all or none of the standard IO handles, so we use 446 // GetStdHandle for the handles that are not being redirected 447 448 StringBuilder commandLine = BuildCommandLine(startInfo.FileName, startInfo.Arguments); 449 450 Interop.Kernel32.STARTUPINFO startupInfo = new Interop.Kernel32.STARTUPINFO(); 451 Interop.Kernel32.PROCESS_INFORMATION processInfo = new Interop.Kernel32.PROCESS_INFORMATION(); 452 Interop.Kernel32.SECURITY_ATTRIBUTES unused_SecAttrs = new Interop.Kernel32.SECURITY_ATTRIBUTES(); 453 SafeProcessHandle procSH = new SafeProcessHandle(); 454 SafeThreadHandle threadSH = new SafeThreadHandle(); 455 bool retVal; 456 int errorCode = 0; 457 // handles used in parent process 458 SafeFileHandle standardInputWritePipeHandle = null; 459 SafeFileHandle standardOutputReadPipeHandle = null; 460 SafeFileHandle standardErrorReadPipeHandle = null; 461 GCHandle environmentHandle = new GCHandle(); 462 lock (s_createProcessLock) 463 { 464 try 465 { 466 // set up the streams 467 if (startInfo.RedirectStandardInput || startInfo.RedirectStandardOutput || startInfo.RedirectStandardError) 468 { 469 if (startInfo.RedirectStandardInput) 470 { 471 CreatePipe(out standardInputWritePipeHandle, out startupInfo.hStdInput, true); 472 } 473 else 474 { 475 startupInfo.hStdInput = new SafeFileHandle(Interop.Kernel32.GetStdHandle(Interop.Kernel32.HandleTypes.STD_INPUT_HANDLE), false); 476 } 477 478 if (startInfo.RedirectStandardOutput) 479 { 480 CreatePipe(out standardOutputReadPipeHandle, out startupInfo.hStdOutput, false); 481 } 482 else 483 { 484 startupInfo.hStdOutput = new SafeFileHandle(Interop.Kernel32.GetStdHandle(Interop.Kernel32.HandleTypes.STD_OUTPUT_HANDLE), false); 485 } 486 487 if (startInfo.RedirectStandardError) 488 { 489 CreatePipe(out standardErrorReadPipeHandle, out startupInfo.hStdError, false); 490 } 491 else 492 { 493 startupInfo.hStdError = new SafeFileHandle(Interop.Kernel32.GetStdHandle(Interop.Kernel32.HandleTypes.STD_ERROR_HANDLE), false); 494 } 495 496 startupInfo.dwFlags = Interop.Advapi32.StartupInfoOptions.STARTF_USESTDHANDLES; 497 } 498 499 // set up the creation flags parameter 500 int creationFlags = 0; 501 if (startInfo.CreateNoWindow) creationFlags |= Interop.Advapi32.StartupInfoOptions.CREATE_NO_WINDOW; 502 503 // set up the environment block parameter 504 IntPtr environmentPtr = (IntPtr)0; 505 if (startInfo._environmentVariables != null) 506 { 507 creationFlags |= Interop.Advapi32.StartupInfoOptions.CREATE_UNICODE_ENVIRONMENT; 508 byte[] environmentBytes = EnvironmentVariablesToByteArray(startInfo._environmentVariables); 509 environmentHandle = GCHandle.Alloc(environmentBytes, GCHandleType.Pinned); 510 environmentPtr = environmentHandle.AddrOfPinnedObject(); 511 } 512 string workingDirectory = startInfo.WorkingDirectory; 513 if (workingDirectory == string.Empty) 514 workingDirectory = Directory.GetCurrentDirectory(); 515 516 if (startInfo.UserName.Length != 0) 517 { 518 if (startInfo.Password != null && startInfo.PasswordInClearText != null) 519 { 520 throw new ArgumentException(SR.CantSetDuplicatePassword); 521 } 522 523 Interop.Advapi32.LogonFlags logonFlags = (Interop.Advapi32.LogonFlags)0; 524 if (startInfo.LoadUserProfile) 525 { 526 logonFlags = Interop.Advapi32.LogonFlags.LOGON_WITH_PROFILE; 527 } 528 529 if (startInfo.Password != null) 530 { 531 IntPtr passwordPtr = Marshal.SecureStringToGlobalAllocUnicode(startInfo.Password); 532 try 533 { 534 retVal = Interop.Advapi32.CreateProcessWithLogonW( 535 startInfo.UserName, 536 startInfo.Domain, 537 passwordPtr, 538 logonFlags, 539 null, // we don't need this since all the info is in commandLine 540 commandLine, 541 creationFlags, 542 environmentPtr, 543 workingDirectory, 544 startupInfo, // pointer to STARTUPINFO 545 processInfo // pointer to PROCESS_INFORMATION 546 ); 547 548 if (!retVal) 549 errorCode = Marshal.GetLastWin32Error(); 550 } 551 finally { Marshal.ZeroFreeGlobalAllocUnicode(passwordPtr); } 552 } 553 else 554 { 555 unsafe 556 { 557 fixed (char* passwordPtr = startInfo.PasswordInClearText ?? string.Empty) 558 { 559 retVal = Interop.Advapi32.CreateProcessWithLogonW( 560 startInfo.UserName, 561 startInfo.Domain, 562 (IntPtr)passwordPtr, 563 logonFlags, 564 null, // we don't need this since all the info is in commandLine 565 commandLine, 566 creationFlags, 567 environmentPtr, 568 workingDirectory, 569 startupInfo, // pointer to STARTUPINFO 570 processInfo // pointer to PROCESS_INFORMATION 571 ); 572 573 } 574 } 575 if (!retVal) 576 errorCode = Marshal.GetLastWin32Error(); 577 } 578 if (processInfo.hProcess != IntPtr.Zero && processInfo.hProcess != (IntPtr)INVALID_HANDLE_VALUE) 579 procSH.InitialSetHandle(processInfo.hProcess); 580 if (processInfo.hThread != IntPtr.Zero && processInfo.hThread != (IntPtr)INVALID_HANDLE_VALUE) 581 threadSH.InitialSetHandle(processInfo.hThread); 582 if (!retVal) 583 { 584 if (errorCode == Interop.Errors.ERROR_BAD_EXE_FORMAT || errorCode == Interop.Errors.ERROR_EXE_MACHINE_TYPE_MISMATCH) 585 { 586 throw new Win32Exception(errorCode, SR.InvalidApplication); 587 } 588 throw new Win32Exception(errorCode); 589 } 590 } 591 else 592 { 593 retVal = Interop.Kernel32.CreateProcess( 594 null, // we don't need this since all the info is in commandLine 595 commandLine, // pointer to the command line string 596 ref unused_SecAttrs, // address to process security attributes, we don't need to inherit the handle 597 ref unused_SecAttrs, // address to thread security attributes. 598 true, // handle inheritance flag 599 creationFlags, // creation flags 600 environmentPtr, // pointer to new environment block 601 workingDirectory, // pointer to current directory name 602 startupInfo, // pointer to STARTUPINFO 603 processInfo // pointer to PROCESS_INFORMATION 604 ); 605 if (!retVal) 606 errorCode = Marshal.GetLastWin32Error(); 607 if (processInfo.hProcess != (IntPtr)0 && processInfo.hProcess != (IntPtr)INVALID_HANDLE_VALUE) 608 procSH.InitialSetHandle(processInfo.hProcess); 609 if (processInfo.hThread != (IntPtr)0 && processInfo.hThread != (IntPtr)INVALID_HANDLE_VALUE) 610 threadSH.InitialSetHandle(processInfo.hThread); 611 612 if (!retVal) 613 { 614 if (errorCode == Interop.Errors.ERROR_BAD_EXE_FORMAT || errorCode == Interop.Errors.ERROR_EXE_MACHINE_TYPE_MISMATCH) 615 { 616 throw new Win32Exception(errorCode, SR.InvalidApplication); 617 } 618 throw new Win32Exception(errorCode); 619 } 620 } 621 } 622 finally 623 { 624 // free environment block 625 if (environmentHandle.IsAllocated) 626 { 627 environmentHandle.Free(); 628 } 629 630 startupInfo.Dispose(); 631 } 632 } 633 634 if (startInfo.RedirectStandardInput) 635 { 636 Encoding enc = startInfo.StandardInputEncoding ?? GetEncoding((int)Interop.Kernel32.GetConsoleCP()); 637 _standardInput = new StreamWriter(new FileStream(standardInputWritePipeHandle, FileAccess.Write, 4096, false), enc, 4096); 638 _standardInput.AutoFlush = true; 639 } 640 if (startInfo.RedirectStandardOutput) 641 { 642 Encoding enc = startInfo.StandardOutputEncoding ?? GetEncoding((int)Interop.Kernel32.GetConsoleOutputCP()); 643 _standardOutput = new StreamReader(new FileStream(standardOutputReadPipeHandle, FileAccess.Read, 4096, false), enc, true, 4096); 644 } 645 if (startInfo.RedirectStandardError) 646 { 647 Encoding enc = startInfo.StandardErrorEncoding ?? GetEncoding((int)Interop.Kernel32.GetConsoleOutputCP()); 648 _standardError = new StreamReader(new FileStream(standardErrorReadPipeHandle, FileAccess.Read, 4096, false), enc, true, 4096); 649 } 650 651 bool ret = false; 652 if (!procSH.IsInvalid) 653 { 654 SetProcessHandle(procSH); 655 SetProcessId((int)processInfo.dwProcessId); 656 threadSH.Dispose(); 657 ret = true; 658 } 659 660 return ret; 661 } 662 GetEncoding(int codePage)663 private static Encoding GetEncoding(int codePage) 664 { 665 Encoding enc = EncodingHelper.GetSupportedConsoleEncoding(codePage); 666 return new ConsoleEncoding(enc); // ensure encoding doesn't output a preamble 667 } 668 669 // ----------------------------- 670 // ---- PAL layer ends here ---- 671 // ----------------------------- 672 673 private bool _signaled; 674 BuildCommandLine(string executableFileName, string arguments)675 private static StringBuilder BuildCommandLine(string executableFileName, string arguments) 676 { 677 // Construct a StringBuilder with the appropriate command line 678 // to pass to CreateProcess. If the filename isn't already 679 // in quotes, we quote it here. This prevents some security 680 // problems (it specifies exactly which part of the string 681 // is the file to execute). 682 StringBuilder commandLine = new StringBuilder(); 683 string fileName = executableFileName.Trim(); 684 bool fileNameIsQuoted = (fileName.StartsWith("\"", StringComparison.Ordinal) && fileName.EndsWith("\"", StringComparison.Ordinal)); 685 if (!fileNameIsQuoted) 686 { 687 commandLine.Append("\""); 688 } 689 690 commandLine.Append(fileName); 691 692 if (!fileNameIsQuoted) 693 { 694 commandLine.Append("\""); 695 } 696 697 if (!string.IsNullOrEmpty(arguments)) 698 { 699 commandLine.Append(" "); 700 commandLine.Append(arguments); 701 } 702 703 return commandLine; 704 } 705 706 /// <summary>Gets timing information for the current process.</summary> GetProcessTimes()707 private ProcessThreadTimes GetProcessTimes() 708 { 709 using (SafeProcessHandle handle = GetProcessHandle(Interop.Advapi32.ProcessOptions.PROCESS_QUERY_LIMITED_INFORMATION, false)) 710 { 711 if (handle.IsInvalid) 712 { 713 throw new InvalidOperationException(SR.Format(SR.ProcessHasExited, _processId.ToString(CultureInfo.CurrentCulture))); 714 } 715 716 ProcessThreadTimes processTimes = new ProcessThreadTimes(); 717 if (!Interop.Kernel32.GetProcessTimes(handle, 718 out processTimes._create, out processTimes._exit, 719 out processTimes._kernel, out processTimes._user)) 720 { 721 throw new Win32Exception(); 722 } 723 724 return processTimes; 725 } 726 } 727 SetPrivilege(string privilegeName, int attrib)728 private static void SetPrivilege(string privilegeName, int attrib) 729 { 730 SafeTokenHandle hToken = null; 731 Interop.Advapi32.LUID debugValue = new Interop.Advapi32.LUID(); 732 733 // this is only a "pseudo handle" to the current process - no need to close it later 734 SafeProcessHandle processHandle = Interop.Kernel32.GetCurrentProcess(); 735 736 // get the process token so we can adjust the privilege on it. We DO need to 737 // close the token when we're done with it. 738 if (!Interop.Advapi32.OpenProcessToken(processHandle, Interop.Kernel32.HandleOptions.TOKEN_ADJUST_PRIVILEGES, out hToken)) 739 { 740 throw new Win32Exception(); 741 } 742 743 try 744 { 745 if (!Interop.Advapi32.LookupPrivilegeValue(null, privilegeName, out debugValue)) 746 { 747 throw new Win32Exception(); 748 } 749 750 Interop.Advapi32.TokenPrivileges tkp = new Interop.Advapi32.TokenPrivileges(); 751 tkp.Luid = debugValue; 752 tkp.Attributes = attrib; 753 754 Interop.Advapi32.AdjustTokenPrivileges(hToken, false, tkp, 0, IntPtr.Zero, IntPtr.Zero); 755 756 // AdjustTokenPrivileges can return true even if it failed to 757 // set the privilege, so we need to use GetLastError 758 if (Marshal.GetLastWin32Error() != Interop.Errors.ERROR_SUCCESS) 759 { 760 throw new Win32Exception(); 761 } 762 } 763 finally 764 { 765 #if FEATURE_TRACESWITCH 766 Debug.WriteLineIf(_processTracing.TraceVerbose, "Process - CloseHandle(processToken)"); 767 #endif 768 if (hToken != null) 769 { 770 hToken.Dispose(); 771 } 772 } 773 } 774 775 /// <devdoc> 776 /// Gets a short-term handle to the process, with the given access. 777 /// If a handle is stored in current process object, then use it. 778 /// Note that the handle we stored in current process object will have all access we need. 779 /// </devdoc> 780 /// <internalonly/> GetProcessHandle(int access, bool throwIfExited)781 private SafeProcessHandle GetProcessHandle(int access, bool throwIfExited) 782 { 783 #if FEATURE_TRACESWITCH 784 Debug.WriteLineIf(_processTracing.TraceVerbose, "GetProcessHandle(access = 0x" + access.ToString("X8", CultureInfo.InvariantCulture) + ", throwIfExited = " + throwIfExited + ")"); 785 #if DEBUG 786 if (_processTracing.TraceVerbose) { 787 StackFrame calledFrom = new StackTrace(true).GetFrame(0); 788 Debug.WriteLine(" called from " + calledFrom.GetFileName() + ", line " + calledFrom.GetFileLineNumber()); 789 } 790 #endif 791 #endif 792 if (_haveProcessHandle) 793 { 794 if (throwIfExited) 795 { 796 // Since haveProcessHandle is true, we know we have the process handle 797 // open with at least SYNCHRONIZE access, so we can wait on it with 798 // zero timeout to see if the process has exited. 799 using (Interop.Kernel32.ProcessWaitHandle waitHandle = new Interop.Kernel32.ProcessWaitHandle(_processHandle)) 800 { 801 if (waitHandle.WaitOne(0)) 802 { 803 if (_haveProcessId) 804 throw new InvalidOperationException(SR.Format(SR.ProcessHasExited, _processId.ToString(CultureInfo.CurrentCulture))); 805 else 806 throw new InvalidOperationException(SR.ProcessHasExitedNoId); 807 } 808 } 809 } 810 811 // If we dispose of our contained handle we'll be in a bad state. NetFX dealt with this 812 // by doing a try..finally around every usage of GetProcessHandle and only disposed if 813 // it wasn't our handle. 814 return new SafeProcessHandle(_processHandle.DangerousGetHandle(), ownsHandle: false); 815 } 816 else 817 { 818 EnsureState(State.HaveId | State.IsLocal); 819 SafeProcessHandle handle = SafeProcessHandle.InvalidHandle; 820 handle = ProcessManager.OpenProcess(_processId, access, throwIfExited); 821 if (throwIfExited && (access & Interop.Advapi32.ProcessOptions.PROCESS_QUERY_INFORMATION) != 0) 822 { 823 if (Interop.Kernel32.GetExitCodeProcess(handle, out _exitCode) && _exitCode != Interop.Kernel32.HandleOptions.STILL_ACTIVE) 824 { 825 throw new InvalidOperationException(SR.Format(SR.ProcessHasExited, _processId.ToString(CultureInfo.CurrentCulture))); 826 } 827 } 828 return handle; 829 } 830 } 831 832 /// <devdoc> 833 /// Gets a short-term handle to the process, with the given access. If a handle exists, 834 /// then it is reused. If the process has exited, it throws an exception. 835 /// </devdoc> 836 /// <internalonly/> GetProcessHandle(int access)837 private SafeProcessHandle GetProcessHandle(int access) 838 { 839 return GetProcessHandle(access, true); 840 } 841 CreatePipeWithSecurityAttributes(out SafeFileHandle hReadPipe, out SafeFileHandle hWritePipe, ref Interop.Kernel32.SECURITY_ATTRIBUTES lpPipeAttributes, int nSize)842 private static void CreatePipeWithSecurityAttributes(out SafeFileHandle hReadPipe, out SafeFileHandle hWritePipe, ref Interop.Kernel32.SECURITY_ATTRIBUTES lpPipeAttributes, int nSize) 843 { 844 bool ret = Interop.Kernel32.CreatePipe(out hReadPipe, out hWritePipe, ref lpPipeAttributes, nSize); 845 if (!ret || hReadPipe.IsInvalid || hWritePipe.IsInvalid) 846 { 847 throw new Win32Exception(); 848 } 849 } 850 851 // Using synchronous Anonymous pipes for process input/output redirection means we would end up 852 // wasting a worker threadpool thread per pipe instance. Overlapped pipe IO is desirable, since 853 // it will take advantage of the NT IO completion port infrastructure. But we can't really use 854 // Overlapped I/O for process input/output as it would break Console apps (managed Console class 855 // methods such as WriteLine as well as native CRT functions like printf) which are making an 856 // assumption that the console standard handles (obtained via GetStdHandle()) are opened 857 // for synchronous I/O and hence they can work fine with ReadFile/WriteFile synchronously! CreatePipe(out SafeFileHandle parentHandle, out SafeFileHandle childHandle, bool parentInputs)858 private void CreatePipe(out SafeFileHandle parentHandle, out SafeFileHandle childHandle, bool parentInputs) 859 { 860 Interop.Kernel32.SECURITY_ATTRIBUTES securityAttributesParent = new Interop.Kernel32.SECURITY_ATTRIBUTES(); 861 securityAttributesParent.bInheritHandle = Interop.BOOL.TRUE; 862 863 SafeFileHandle hTmp = null; 864 try 865 { 866 if (parentInputs) 867 { 868 CreatePipeWithSecurityAttributes(out childHandle, out hTmp, ref securityAttributesParent, 0); 869 } 870 else 871 { 872 CreatePipeWithSecurityAttributes(out hTmp, 873 out childHandle, 874 ref securityAttributesParent, 875 0); 876 } 877 // Duplicate the parent handle to be non-inheritable so that the child process 878 // doesn't have access. This is done for correctness sake, exact reason is unclear. 879 // One potential theory is that child process can do something brain dead like 880 // closing the parent end of the pipe and there by getting into a blocking situation 881 // as parent will not be draining the pipe at the other end anymore. 882 SafeProcessHandle currentProcHandle = Interop.Kernel32.GetCurrentProcess(); 883 if (!Interop.Kernel32.DuplicateHandle(currentProcHandle, 884 hTmp, 885 currentProcHandle, 886 out parentHandle, 887 0, 888 false, 889 Interop.Kernel32.HandleOptions.DUPLICATE_SAME_ACCESS)) 890 { 891 throw new Win32Exception(); 892 } 893 } 894 finally 895 { 896 if (hTmp != null && !hTmp.IsInvalid) 897 { 898 hTmp.Dispose(); 899 } 900 } 901 } 902 EnvironmentVariablesToByteArray(IDictionary<string, string> sd)903 private static byte[] EnvironmentVariablesToByteArray(IDictionary<string, string> sd) 904 { 905 // get the keys 906 string[] keys = new string[sd.Count]; 907 byte[] envBlock = null; 908 sd.Keys.CopyTo(keys, 0); 909 910 // sort both by the keys 911 // Windows 2000 requires the environment block to be sorted by the key 912 // It will first converting the case the strings and do ordinal comparison. 913 914 // We do not use Array.Sort(keys, values, IComparer) since it is only supported 915 // in System.Runtime contract from 4.20.0.0 and Test.Net depends on System.Runtime 4.0.10.0 916 // we workaround this by sorting only the keys and then lookup the values form the keys. 917 Array.Sort(keys, StringComparer.OrdinalIgnoreCase); 918 919 // create a list of null terminated "key=val" strings 920 StringBuilder stringBuff = new StringBuilder(); 921 for (int i = 0; i < sd.Count; ++i) 922 { 923 stringBuff.Append(keys[i]); 924 stringBuff.Append('='); 925 stringBuff.Append(sd[keys[i]]); 926 stringBuff.Append('\0'); 927 } 928 // an extra null at the end indicates end of list. 929 stringBuff.Append('\0'); 930 envBlock = Encoding.Unicode.GetBytes(stringBuff.ToString()); 931 return envBlock; 932 } 933 } 934 } 935