-
Notifications
You must be signed in to change notification settings - Fork 8.1k
Start-Process: add parameter -ExitTimeout #5471
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -4,6 +4,7 @@ | |
|
|
||
| using System; | ||
| using System.Text; | ||
| using System.Text.RegularExpressions; | ||
| using System.Collections; | ||
| using System.Collections.Generic; | ||
| using System.Collections.Specialized; | ||
|
|
@@ -1779,6 +1780,14 @@ public ProcessWindowStyle WindowStyle | |
| [Parameter] | ||
| public SwitchParameter Wait { get; set; } | ||
|
|
||
| /// <summary> | ||
| /// If specified, wait for this number of milliseconds for the process to exit. | ||
| /// </summary> | ||
| [Parameter] | ||
| [ValidateNotNullOrEmpty] | ||
| [ValidateRange(ValidateRangeKind.Positive)] | ||
| public int ExitTimeout { get; set; } = Timeout.Infinite; | ||
|
|
||
| /// <summary> | ||
| /// Default Environment | ||
| /// </summary> | ||
|
|
@@ -2013,30 +2022,40 @@ protected override void BeginProcessing() | |
| } | ||
| } | ||
|
|
||
| if (Wait.IsPresent) | ||
| if (Wait.IsPresent || ExitTimeout != Timeout.Infinite) | ||
| { | ||
| if (process != null) | ||
| { | ||
| if (!process.HasExited) | ||
| { | ||
| #if UNIX | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We should remove two code paths and use
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm sorry, I don't quite understand what you are asking here. Are you saying to remove the From what I can see, the Windows specific code keeps track of any child processes that are created, which doesn't happen for UNIX. I am unsure of the reasoning behind this, as the earliest commit on GitHub for these lines does not contain any helpful information. |
||
| process.WaitForExit(); | ||
| if (!process.WaitForExit(ExitTimeout)) | ||
| { | ||
| StopProcessOnTimeout(process); | ||
| } | ||
| #else | ||
| _waithandle = new ManualResetEvent(false); | ||
|
|
||
| // Create and start the job object | ||
| ProcessCollection jobObject = new ProcessCollection(); | ||
| if (jobObject.AssignProcessToJobObject(process)) | ||
| { | ||
| // Wait for the job object to finish | ||
| jobObject.WaitOne(_waithandle); | ||
| // Wait for the job object to finish, or kill it if a timeout occurs | ||
| jobObject.WaitOne(_waithandle, ExitTimeout); | ||
| if (!process.HasExited) | ||
| { | ||
| StopProcessOnTimeout(process); | ||
| } | ||
| } | ||
| else if (!process.HasExited) | ||
| { | ||
| // WinBlue: 27537 Start-Process -Wait doesn't work in a remote session on Windows 7 or lower. | ||
| process.Exited += new EventHandler(myProcess_Exited); | ||
| process.EnableRaisingEvents = true; | ||
| process.WaitForExit(); | ||
| if (!process.WaitForExit(ExitTimeout)) | ||
| { | ||
| StopProcessOnTimeout(process); | ||
| } | ||
| } | ||
| #endif | ||
| } | ||
|
|
@@ -2123,6 +2142,127 @@ private void LoadEnvironmentVariable(ProcessStartInfo startinfo, IDictionary Env | |
| } | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Attempt to stop the process when the timeout has expired. | ||
| /// <see cref="StopProcessCommand" /> is used to stop the process | ||
| /// </summary> | ||
| /// <param name="process"> | ||
| /// The process that should be stopped | ||
| /// </param> | ||
| private void StopProcessOnTimeout(Process process) | ||
| { | ||
| string message = StringUtil.Format(ProcessResources.StartProcessExitTimeoutExceeded, process.ProcessName); | ||
| ErrorRecord er = new ErrorRecord(new TimeoutException(message), "StartProcessExitTimeoutExceeded", ErrorCategory.OperationTimeout, process); | ||
|
|
||
| StopProcessCommand stop = new StopProcessCommand(); | ||
| stop.Id = GetProcessTreeIds(process); | ||
| foreach (Process p in stop.Invoke<Process>()) { } | ||
|
|
||
| ThrowTerminatingError(er); | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Gets IDs of descendant processes, started by a process | ||
| /// On Windows, this reads output from WMI commands | ||
| /// On UNIX, this reads output from `ps axo pid,ppid` | ||
| /// </summary> | ||
| /// <param name="parentProcess"> | ||
| /// The parent process to use to resolve the process tree IDs | ||
| /// </param> | ||
| /// <returns> | ||
| /// IDs of the parent process and all its descendants | ||
| /// </returns> | ||
| private int[] GetProcessTreeIds(Process parentProcess) | ||
| { | ||
| List<int> stopProcessIds = new List<int> {parentProcess.Id}; | ||
| string processRelationships = ""; | ||
| bool processesCollected = true; | ||
| #if UNIX | ||
| try | ||
| { | ||
| Process ps = new Process(); | ||
| ps.StartInfo.FileName = "ps"; | ||
| ps.StartInfo.Arguments = "axo pid,ppid"; | ||
| ps.StartInfo.UseShellExecute = false; | ||
| ps.StartInfo.RedirectStandardOutput = true; | ||
| ps.Start(); | ||
|
|
||
| processRelationships = ps.StandardOutput.ReadToEnd(); | ||
|
|
||
| if (!ps.WaitForExit(4000) || ps.ExitCode != 0) | ||
| { | ||
| processesCollected = false; | ||
| } | ||
| ps.Close(); | ||
| } | ||
| catch (Win32Exception) | ||
| { | ||
| processesCollected = false; | ||
| } | ||
| #else | ||
| string searchQuery = "Select ProcessID, ParentProcessID From Win32_Process"; | ||
| try | ||
| { | ||
| using (CimSession cimSession = CimSession.Create(null)) | ||
| { | ||
| IEnumerable<CimInstance> processCollection = | ||
| cimSession.QueryInstances("root/cimv2", "WQL", searchQuery); | ||
| StringBuilder sb = new StringBuilder(); | ||
| foreach (CimInstance processInstance in processCollection) | ||
| { | ||
| sb.Append(processInstance.CimInstanceProperties["ProcessID"].Value.ToString()); | ||
| sb.Append(' '); | ||
| sb.Append(processInstance.CimInstanceProperties["ParentProcessID"].Value.ToString()); | ||
| sb.Append(Environment.NewLine); | ||
| } | ||
| processRelationships = sb.ToString(); | ||
| } | ||
| } | ||
| catch (CimException) | ||
| { | ||
| processesCollected = false; | ||
| } | ||
| #endif | ||
|
|
||
| if (!processesCollected) | ||
| { | ||
| WriteWarning( | ||
| StringUtil.Format(ProcessResources.CouldNotResolveProcessTree, parentProcess.ProcessName) | ||
| + " " | ||
| + ProcessResources.DescendantProcessesPossiblyRunning | ||
| ); | ||
| return stopProcessIds.ToArray(); | ||
| } | ||
|
|
||
| // processList - key: process ID, value: parent process ID | ||
| Dictionary<int, int> processList = new Dictionary<int, int>(); | ||
| int pid = 0; | ||
| int ppid = 0; | ||
| string relationshipPattern = @"\s?([0-9]+)\s+([0-9]+)"; | ||
| foreach (Match psLine in Regex.Matches(processRelationships, relationshipPattern)) | ||
| { | ||
| pid = int.Parse(psLine.Groups[1].Value); | ||
| ppid = int.Parse(psLine.Groups[2].Value); | ||
| processList.Add(pid, ppid); | ||
| } | ||
|
|
||
| int position = 0; | ||
| do | ||
| { | ||
| foreach (KeyValuePair<int, int> process in processList) | ||
| { | ||
| if (process.Value == stopProcessIds[position]) | ||
| { | ||
| stopProcessIds.Add(process.Key); | ||
| } | ||
| } | ||
|
|
||
| position++; | ||
| } while (position <= (stopProcessIds.Count - 1)); | ||
|
|
||
| return stopProcessIds.ToArray(); | ||
| } | ||
|
|
||
| private Process Start(ProcessStartInfo startInfo) | ||
| { | ||
| #if UNIX | ||
|
|
@@ -2605,12 +2745,15 @@ internal void CheckJobStatus(Object stateInfo) | |
| /// <param name="waitHandleToUse"> | ||
| /// WaitHandle to use for waiting on the job object. | ||
| /// </param> | ||
| internal void WaitOne(ManualResetEvent waitHandleToUse) | ||
| /// <param name="timeout"> | ||
| /// Wait for this number of milliseconds before a time-out occurs. | ||
| /// </param> | ||
| internal void WaitOne(ManualResetEvent waitHandleToUse, Int32 timeout = Timeout.Infinite) | ||
| { | ||
| TimerCallback jobObjectStatusCb = this.CheckJobStatus; | ||
| using (Timer stateTimer = new Timer(jobObjectStatusCb, waitHandleToUse, 0, 1000)) | ||
| { | ||
| waitHandleToUse.WaitOne(); | ||
| waitHandleToUse.WaitOne(timeout); | ||
| } | ||
| } | ||
| } | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
needs to be updated per committee review