MegaBites-AI/Windows-powershell
0372
1// Copyright (c) Microsoft Corporation.2// Licensed under the MIT License.3 4using System;5using System.Collections.Generic;6using System.Diagnostics.CodeAnalysis;7using System.Management.Automation;8using System.Management.Automation.Internal;9using System.Management.Automation.Remoting.Internal;10using System.Management.Automation.Runspaces;11 12namespace Microsoft.PowerShell.Commands13{14 /// <summary>15 /// This cmdlet takes a Job object and checks to see if it is debuggable. If it16 /// is debuggable then it breaks into the job debugger in step mode. If it is not17 /// debuggable then it is treated as a parent job and each child job is checked if18 /// it is debuggable and if it is will break into its job debugger in step mode.19 /// For multiple debuggable child jobs, each job execution will be halted and the20 /// debugger will step to each job execution point sequentially.21 ///22 /// When a job is debugged its output data is written to host and the executing job23 /// script will break into the host debugger, in step mode, at the next stoppable24 /// execution point.25 /// </summary>26 [SuppressMessage("Microsoft.PowerShell", "PS1012:CallShouldProcessOnlyIfDeclaringSupport")]27 [Cmdlet(VerbsDiagnostic.Debug, "Job", SupportsShouldProcess = true, DefaultParameterSetName = DebugJobCommand.JobParameterSet,28 HelpUri = "https://go.microsoft.com/fwlink/?LinkId=330208")]29 public sealed class DebugJobCommand : PSCmdlet30 {31 #region Strings32 33 private const string JobParameterSet = "JobParameterSet";34 private const string JobNameParameterSet = "JobNameParameterSet";35 private const string JobIdParameterSet = "JobIdParameterSet";36 private const string JobInstanceIdParameterSet = "JobInstanceIdParameterSet";37 38 #endregion39 40 #region Private members41 42 private Job _job;43 private Debugger _debugger;44 private PSDataCollection<PSStreamObject> _debugCollection;45 46 #endregion47 48 #region Parameters49 50 /// <summary>51 /// The Job object to be debugged.52 /// </summary>53 [Parameter(Position = 0,54 Mandatory = true,55 ValueFromPipelineByPropertyName = true,56 ValueFromPipeline = true,57 ParameterSetName = DebugJobCommand.JobParameterSet)]58 public Job Job59 {60 get;61 set;62 }63 64 /// <summary>65 /// The Job object name to be debugged.66 /// </summary>67 [Parameter(Position = 0,68 Mandatory = true,69 ParameterSetName = DebugJobCommand.JobNameParameterSet)]70 public string Name71 {72 get;73 set;74 }75 76 /// <summary>77 /// The Job object Id to be debugged.78 /// </summary>79 [Parameter(Position = 0,80 Mandatory = true,81 ParameterSetName = DebugJobCommand.JobIdParameterSet)]82 public int Id83 {84 get;85 set;86 }87 88 /// <summary>89 /// The Job object InstanceId to be debugged.90 /// </summary>91 [Parameter(Position = 0,92 Mandatory = true,93 ParameterSetName = DebugJobCommand.JobInstanceIdParameterSet)]94 public Guid InstanceId95 {96 get;97 set;98 }99 100 /// <summary>101 /// Gets or sets a flag that tells PowerShell to automatically perform a BreakAll when the debugger is attached to the remote target.102 /// </summary>103 [Parameter]104 public SwitchParameter BreakAll { get; set; }105 106 #endregion107 108 #region Overrides109 110 /// <summary>111 /// End processing. Do work.112 /// </summary>113 protected override void EndProcessing()114 {115 switch (ParameterSetName)116 {117 case DebugJobCommand.JobParameterSet:118 _job = Job;119 break;120 121 case DebugJobCommand.JobNameParameterSet:122 _job = GetJobByName(Name);123 break;124 125 case DebugJobCommand.JobIdParameterSet:126 _job = GetJobById(Id);127 break;128 129 case DebugJobCommand.JobInstanceIdParameterSet:130 _job = GetJobByInstanceId(InstanceId);131 break;132 }133 134 if (!ShouldProcess(_job.Name, VerbsDiagnostic.Debug))135 {136 return;137 }138 139 Runspace runspace = LocalRunspace.DefaultRunspace;140 if (runspace == null || runspace.Debugger == null)141 {142 ThrowTerminatingError(143 new ErrorRecord(144 new PSInvalidOperationException(RemotingErrorIdStrings.CannotDebugJobNoHostDebugger),145 "DebugJobNoHostDebugger",146 ErrorCategory.InvalidOperation,147 this)148 );149 }150 151 if ((runspace.Debugger.DebugMode == DebugModes.Default) || (runspace.Debugger.DebugMode == DebugModes.None))152 {153 ThrowTerminatingError(154 new ErrorRecord(155 new PSInvalidOperationException(RemotingErrorIdStrings.CannotDebugJobInvalidDebuggerMode),156 "DebugJobWrongDebugMode",157 ErrorCategory.InvalidOperation,158 this)159 );160 }161 162 if (this.Host == null || this.Host.UI == null)163 {164 ThrowTerminatingError(165 new ErrorRecord(166 new PSInvalidOperationException(RemotingErrorIdStrings.CannotDebugJobNoHostUI),167 "DebugJobNoHostAvailable",168 ErrorCategory.InvalidOperation,169 this)170 );171 }172 173 if (!CheckForDebuggableJob())174 {175 ThrowTerminatingError(176 new ErrorRecord(177 new PSInvalidOperationException(DebuggerStrings.NoDebuggableJobsFound),178 "DebugJobNoDebuggableJobsFound",179 ErrorCategory.InvalidOperation,180 this)181 );182 }183 184 // Set up host script debugger to debug the job.185 _debugger = runspace.Debugger;186 _debugger.DebugJob(_job, breakAll: BreakAll);187 188 // Blocking call. Send job output to host UI while debugging and wait for Job completion.189 WaitAndReceiveJobOutput();190 }191 192 /// <summary>193 /// Stop processing.194 /// </summary>195 protected override void StopProcessing()196 {197 // Cancel job debugging.198 Debugger debugger = _debugger;199 if ((debugger != null) && (_job != null))200 {201 debugger.StopDebugJob(_job);202 }203 204 // Unblock the data collection.205 PSDataCollection<PSStreamObject> debugCollection = _debugCollection;206 debugCollection?.Complete();207 }208 209 #endregion210 211 #region Private methods212 213 /// <summary>214 /// Check for debuggable job. Job must implement IJobDebugger and also215 /// must be running or in Debug stopped state.216 /// </summary>217 /// <returns></returns>218 private bool CheckForDebuggableJob()219 {220 // Check passed in job object.221 bool debuggableJobFound = GetJobDebuggable(_job);222 223 if (!debuggableJobFound)224 {225 // Assume passed in job is a container job and check child jobs.226 foreach (var cJob in _job.ChildJobs)227 {228 debuggableJobFound = GetJobDebuggable(cJob);229 if (debuggableJobFound)230 {231 break;232 }233 }234 }235 236 return debuggableJobFound;237 }238 239 private static bool GetJobDebuggable(Job job)240 {241 if (job is IJobDebugger)242 {243 return ((job.JobStateInfo.State == JobState.Running) ||244 (job.JobStateInfo.State == JobState.AtBreakpoint));245 }246 247 return false;248 }249 250 private void WaitAndReceiveJobOutput()251 {252 _debugCollection = new PSDataCollection<PSStreamObject>();253 _debugCollection.BlockingEnumerator = true;254 255 try256 {257 AddEventHandlers();258 259 // This call blocks (blocking enumerator) until the job completes260 // or this command is cancelled.261 foreach (var streamItem in _debugCollection)262 {263 streamItem?.WriteStreamObject(this);264 }265 }266 catch (Exception)267 {268 // Terminate job on exception.269 if (!_job.IsFinishedState(_job.JobStateInfo.State))270 {271 _job.StopJob();272 }273 274 throw;275 }276 finally277 {278 RemoveEventHandlers();279 _debugCollection = null;280 }281 }282 283 private void HandleJobStateChangedEvent(object sender, JobStateEventArgs stateChangedArgs)284 {285 Job job = sender as Job;286 if (job.IsFinishedState(stateChangedArgs.JobStateInfo.State))287 {288 _debugCollection.Complete();289 }290 }291 292 private void HandleResultsDataAdding(object sender, DataAddingEventArgs dataAddingArgs)293 {294 if (_debugCollection.IsOpen)295 {296 PSStreamObject streamObject = dataAddingArgs.ItemAdded as PSStreamObject;297 if (streamObject != null)298 {299 try300 {301 _debugCollection.Add(streamObject);302 }303 catch (PSInvalidOperationException) { }304 }305 }306 }307 308 private void HandleDebuggerNestedDebuggingCancelledEvent(object sender, EventArgs e)309 {310 StopProcessing();311 }312 313 private void AddEventHandlers()314 {315 _job.StateChanged += HandleJobStateChangedEvent;316 _debugger.NestedDebuggingCancelledEvent += HandleDebuggerNestedDebuggingCancelledEvent;317 318 if (_job.ChildJobs.Count == 0)319 {320 // No child jobs, monitor this job's results collection.321 _job.Results.DataAdding += HandleResultsDataAdding;322 }323 else324 {325 // Monitor each child job's results collections.326 foreach (var childJob in _job.ChildJobs)327 {328 childJob.Results.DataAdding += HandleResultsDataAdding;329 }330 }331 }332 333 private void RemoveEventHandlers()334 {335 _job.StateChanged -= HandleJobStateChangedEvent;336 _debugger.NestedDebuggingCancelledEvent -= HandleDebuggerNestedDebuggingCancelledEvent;337 338 if (_job.ChildJobs.Count == 0)339 {340 // Remove single job DataAdding event handler.341 _job.Results.DataAdding -= HandleResultsDataAdding;342 }343 else344 {345 // Remove each child job's DataAdding event handler.346 foreach (var childJob in _job.ChildJobs)347 {348 childJob.Results.DataAdding -= HandleResultsDataAdding;349 }350 }351 }352 353 private Job GetJobByName(string name)354 {355 // Search jobs in job repository.356 List<Job> jobs1 = new List<Job>();357 WildcardPattern pattern =358 WildcardPattern.Get(name, WildcardOptions.IgnoreCase | WildcardOptions.Compiled);359 360 foreach (Job job in JobRepository.Jobs)361 {362 if (pattern.IsMatch(job.Name))363 {364 jobs1.Add(job);365 }366 }367 368 // Search jobs in job manager.369 List<Job2> jobs2 = JobManager.GetJobsByName(name, this, false, false, false, null);370 371 int jobCount = jobs1.Count + jobs2.Count;372 if (jobCount == 1)373 {374 return (jobs1.Count > 0) ? jobs1[0] : jobs2[0];375 }376 377 if (jobCount > 1)378 {379 ThrowTerminatingError(380 new ErrorRecord(381 new PSInvalidOperationException(StringUtil.Format(RemotingErrorIdStrings.FoundMultipleJobsWithName, name)),382 "DebugJobFoundMultipleJobsWithName",383 ErrorCategory.InvalidOperation,384 this)385 );386 387 return null;388 }389 390 ThrowTerminatingError(391 new ErrorRecord(392 new PSInvalidOperationException(StringUtil.Format(RemotingErrorIdStrings.CannotFindJobWithName, name)),393 "DebugJobCannotFindJobWithName",394 ErrorCategory.InvalidOperation,395 this)396 );397 398 return null;399 }400 401 private Job GetJobById(int id)402 {403 // Search jobs in job repository.404 List<Job> jobs1 = new List<Job>();405 foreach (Job job in JobRepository.Jobs)406 {407 if (job.Id == id)408 {409 jobs1.Add(job);410 }411 }412 413 // Search jobs in job manager.414 Job job2 = JobManager.GetJobById(id, this, false, false, false);415 416 if ((jobs1.Count == 0) && (job2 == null))417 {418 ThrowTerminatingError(419 new ErrorRecord(420 new PSInvalidOperationException(StringUtil.Format(RemotingErrorIdStrings.CannotFindJobWithId, id)),421 "DebugJobCannotFindJobWithId",422 ErrorCategory.InvalidOperation,423 this)424 );425 426 return null;427 }428 429 if ((jobs1.Count > 1) ||430 (jobs1.Count == 1) && (job2 != null))431 {432 ThrowTerminatingError(433 new ErrorRecord(434 new PSInvalidOperationException(StringUtil.Format(RemotingErrorIdStrings.FoundMultipleJobsWithId, id)),435 "DebugJobFoundMultipleJobsWithId",436 ErrorCategory.InvalidOperation,437 this)438 );439 440 return null;441 }442 443 return (jobs1.Count > 0) ? jobs1[0] : job2;444 }445 446 private Job GetJobByInstanceId(Guid instanceId)447 {448 // Search jobs in job repository.449 foreach (Job job in JobRepository.Jobs)450 {451 if (job.InstanceId == instanceId)452 {453 return job;454 }455 }456 457 // Search jobs in job manager.458 Job2 job2 = JobManager.GetJobByInstanceId(instanceId, this, false, false, false);459 if (job2 != null)460 {461 return job2;462 }463 464 ThrowTerminatingError(465 new ErrorRecord(466 new PSInvalidOperationException(StringUtil.Format(RemotingErrorIdStrings.CannotFindJobWithInstanceId, instanceId)),467 "DebugJobCannotFindJobWithInstanceId",468 ErrorCategory.InvalidOperation,469 this)470 );471 472 return null;473 }474 475 #endregion476 }477}478 