MegaBites-AI/Windows-powershell
0308
1// Copyright (c) Microsoft Corporation.2// Licensed under the MIT License.3 4using System.Collections;5using System.Collections.ObjectModel;6using System.Management.Automation.Internal;7using System.Management.Automation.Security;8using System.Runtime.InteropServices;9 10namespace System.Management.Automation11{12 /// <summary>13 /// The base class for all command processor classes. It provides14 /// abstract methods to execute a command.15 /// </summary>16 internal abstract class CommandProcessorBase : IDisposable17 {18 #region ctor19 20 /// <summary>21 /// Default constructor.22 /// </summary>23 internal CommandProcessorBase()24 {25 }26 27 /// <summary>28 /// Initializes the base command processor class with the command metadata.29 /// </summary>30 /// <param name="commandInfo">31 /// The metadata about the command to run.32 /// </param>33 internal CommandProcessorBase(CommandInfo commandInfo)34 {35 if (commandInfo == null)36 {37 throw PSTraceSource.NewArgumentNullException(nameof(commandInfo));38 }39 40 if (commandInfo is IScriptCommandInfo scriptCommand)41 {42 ExperimentalAttribute expAttribute = scriptCommand.ScriptBlock.ExperimentalAttribute;43 if (expAttribute != null && expAttribute.ToHide)44 {45 string errorTemplate = expAttribute.ExperimentAction == ExperimentAction.Hide46 ? DiscoveryExceptions.ScriptDisabledWhenFeatureOn47 : DiscoveryExceptions.ScriptDisabledWhenFeatureOff;48 49 string errorMsg = StringUtil.Format(errorTemplate, expAttribute.ExperimentName);50 ErrorRecord errorRecord = new ErrorRecord(51 new InvalidOperationException(errorMsg),52 "ScriptCommandDisabled",53 ErrorCategory.InvalidOperation,54 commandInfo);55 throw new CmdletInvocationException(errorRecord);56 }57 58 HasCleanBlock = scriptCommand.ScriptBlock.HasCleanBlock;59 }60 61 CommandInfo = commandInfo;62 }63 64 #endregion ctor65 66 #region properties67 68 private InternalCommand _command;69 70 // Marker of whether BeginProcessing() has already run,71 // also used by CommandProcessor.72 internal bool RanBeginAlready;73 74 // Marker of whether this command has already been added to75 // a PipelineProcessor. It is an error to add the same command76 // more than once.77 internal bool AddedToPipelineAlready78 {79 get { return _addedToPipelineAlready; }80 81 set { _addedToPipelineAlready = value; }82 }83 84 internal bool _addedToPipelineAlready;85 86 /// <summary>87 /// Gets the CommandInfo for the command this command processor represents.88 /// </summary>89 /// <value></value>90 internal CommandInfo CommandInfo { get; set; }91 92 /// <summary>93 /// Gets whether the command has a 'Clean' block defined.94 /// </summary>95 internal bool HasCleanBlock { get; }96 97 /// <summary>98 /// This indicates whether this command processor is created from99 /// a script file.100 /// </summary>101 /// <remarks>102 /// Script command processor created from a script file is special103 /// in following two perspectives,104 ///105 /// 1. New scope created needs to be a 'script' scope in the106 /// sense that it needs to handle $script: variables.107 /// For normal functions or scriptblocks, script scope108 /// variables are not supported.109 ///110 /// 2. ExitException will be handled by setting lastExitCode.111 /// For normal functions or scriptblocks, exit command will112 /// kill current powershell session.113 /// </remarks>114 public bool FromScriptFile { get { return _fromScriptFile; } }115 116 protected bool _fromScriptFile = false;117 118 /// <summary>119 /// If this flag is true, the commands in this Pipeline will redirect120 /// the global error output pipe to the command's error output pipe.121 /// (See the comment in Pipeline.RedirectShellErrorOutputPipe for an122 /// explanation of why this flag is needed).123 /// </summary>124 internal bool RedirectShellErrorOutputPipe { get; set; } = false;125 126 /// <summary>127 /// Gets or sets the command object.128 /// </summary>129 internal InternalCommand Command130 {131 get132 {133 return _command;134 }135 136 set137 {138 // The command runtime needs to be set up...139 if (value != null)140 {141 value.commandRuntime = this.commandRuntime;142 if (_command != null)143 value.CommandInfo = _command.CommandInfo;144 145 // Set the execution context for the command it's currently146 // null and our context has already been set up.147 if (value.Context == null && _context != null)148 value.Context = _context;149 }150 151 _command = value;152 }153 }154 155 /// <summary>156 /// Get the ObsoleteAttribute of the current command.157 /// </summary>158 internal virtual ObsoleteAttribute ObsoleteAttribute159 {160 get { return null; }161 }162 163 // Full Qualified ID for the obsolete command warning164 private const string FQIDCommandObsolete = "CommandObsolete";165 166 /// <summary>167 /// The command runtime used for this instance of a command processor.168 /// </summary>169 protected MshCommandRuntime commandRuntime;170 171 internal MshCommandRuntime CommandRuntime172 {173 get { return commandRuntime; }174 175 set { commandRuntime = value; }176 }177 178 /// <summary>179 /// For commands that use the scope stack, if this flag is180 /// true, don't create a new scope when running this command.181 /// </summary>182 /// <value></value>183 internal bool UseLocalScope184 {185 get { return _useLocalScope; }186 187 set { _useLocalScope = value; }188 }189 190 protected bool _useLocalScope;191 192 /// <summary>193 /// Ensures that the provided script block is compatible with the current language mode - to194 /// be used when a script block is being dotted.195 /// </summary>196 /// <param name="scriptBlock">The script block being dotted.</param>197 /// <param name="context">The current execution context.</param>198 /// <param name="invocationInfo">The invocation info about the command.</param>199 protected static void ValidateCompatibleLanguageMode(200 ScriptBlock scriptBlock,201 ExecutionContext context,202 InvocationInfo invocationInfo)203 {204 // If we are in a constrained language mode (Core or Restricted), block it.205 // We are currently restricting in one direction:206 // - Can't dot something from a more permissive mode, since that would probably expose207 // functions that were never designed to handle untrusted data.208 // This function won't be called for NoLanguage mode so the only direction checked is trusted209 // (FullLanguage mode) script running in a constrained/restricted session.210 var languageMode = context.LanguageMode;211 if (scriptBlock.LanguageMode.HasValue &&212 scriptBlock.LanguageMode != languageMode &&213 (languageMode == PSLanguageMode.RestrictedLanguage ||214 languageMode == PSLanguageMode.ConstrainedLanguage))215 {216 // Finally check if script block is really just PowerShell commands plus parameters.217 // If so then it is safe to dot source across language mode boundaries.218 bool isSafeToDotSource = false;219 try220 {221 scriptBlock.GetPowerShell();222 isSafeToDotSource = true;223 }224 catch (Exception)225 {226 }227 228 if (!isSafeToDotSource)229 {230 if (SystemPolicy.GetSystemLockdownPolicy() != SystemEnforcementMode.Audit)231 {232 ErrorRecord errorRecord = new ErrorRecord(233 new NotSupportedException(DiscoveryExceptions.DotSourceNotSupported),234 "DotSourceNotSupported",235 ErrorCategory.InvalidOperation,236 targetObject: null);237 errorRecord.SetInvocationInfo(invocationInfo);238 throw new CmdletInvocationException(errorRecord);239 }240 241 string scriptBlockId = scriptBlock.GetFileName() ?? string.Empty;242 SystemPolicy.LogWDACAuditMessage(243 context: context,244 title: CommandBaseStrings.WDACLogTitle,245 message: StringUtil.Format(CommandBaseStrings.WDACLogMessage, scriptBlockId, scriptBlock.LanguageMode, languageMode),246 fqid: "ScriptBlockDotSourceNotAllowed",247 dropIntoDebugger: true);248 }249 }250 }251 252 /// <summary>253 /// The execution context used by the system.254 /// </summary>255 protected ExecutionContext _context;256 257 internal ExecutionContext Context258 {259 get { return _context; }260 261 set { _context = value; }262 }263 264 /// <summary>265 /// Etw activity for this pipeline.266 /// </summary>267 internal Guid PipelineActivityId { get; set; } = Guid.Empty;268 269 #endregion properties270 271 #region methods272 273 #region handling of -? parameter274 275 /// <summary>276 /// Checks if user has requested help (for example passing "-?" parameter for a cmdlet)277 /// and if yes, then returns the help target to display.278 /// </summary>279 /// <param name="helpTarget">Help target to request.</param>280 /// <param name="helpCategory">Help category to request.</param>281 /// <returns><see langword="true"/> if user requested help; <see langword="false"/> otherwise.</returns>282 internal virtual bool IsHelpRequested(out string helpTarget, out HelpCategory helpCategory)283 {284 // by default we don't handle "-?" parameter at all285 // (we want to do the checks only for cmdlets - this method is overridden in CommandProcessor)286 helpTarget = null;287 helpCategory = HelpCategory.None;288 return false;289 }290 291 /// <summary>292 /// Creates a command processor for "get-help [helpTarget]".293 /// </summary>294 /// <param name="context">Context for the command processor.</param>295 /// <param name="helpTarget">Help target.</param>296 /// <param name="helpCategory">Help category.</param>297 /// <returns>Command processor for "get-help [helpTarget]".</returns>298 internal static CommandProcessorBase CreateGetHelpCommandProcessor(299 ExecutionContext context,300 string helpTarget,301 HelpCategory helpCategory)302 {303 if (context == null)304 {305 throw PSTraceSource.NewArgumentNullException(nameof(context));306 }307 308 if (string.IsNullOrEmpty(helpTarget))309 {310 throw PSTraceSource.NewArgumentNullException(nameof(helpTarget));311 }312 313 CommandProcessorBase helpCommandProcessor = context.CreateCommand("get-help", false);314 var cpi = CommandParameterInternal.CreateParameterWithArgument(315 /*parameterAst*/null, "Name", "-Name:",316 /*argumentAst*/null, helpTarget,317 false);318 helpCommandProcessor.AddParameter(cpi);319 cpi = CommandParameterInternal.CreateParameterWithArgument(320 /*parameterAst*/null, "Category", "-Category:",321 /*argumentAst*/null, helpCategory.ToString(),322 false);323 helpCommandProcessor.AddParameter(cpi);324 return helpCommandProcessor;325 }326 327 #endregion328 329 /// <summary>330 /// Tells whether pipeline input is expected or not.331 /// </summary>332 /// <returns>A bool indicating whether pipeline input is expected.</returns>333 internal bool IsPipelineInputExpected()334 {335 return commandRuntime.IsPipelineInputExpected;336 }337 338 /// <summary>339 /// If you want this command to execute in other than the default session340 /// state, use this API to get and set that session state instance...341 /// </summary>342 internal SessionStateInternal CommandSessionState { get; set; }343 344 /// <summary>345 /// Gets or sets the session state scope for this command processor object.346 /// </summary>347 protected internal SessionStateScope CommandScope { get; protected set; }348 349 protected virtual void OnSetCurrentScope()350 {351 }352 353 protected virtual void OnRestorePreviousScope()354 {355 }356 357 /// <summary>358 /// This method sets the current session state scope to the execution scope for the pipeline359 /// that was stored in the pipeline manager when it was first invoked.360 /// </summary>361 internal void SetCurrentScopeToExecutionScope()362 {363 // Make sure we have a session state instance for this command.364 // If one hasn't been explicitly set, then use the session state365 // available on the engine execution context...366 CommandSessionState ??= Context.EngineSessionState;367 368 // Store off the current scope369 _previousScope = CommandSessionState.CurrentScope;370 _previousCommandSessionState = Context.EngineSessionState;371 Context.EngineSessionState = CommandSessionState;372 373 // Set the current scope to the pipeline execution scope374 CommandSessionState.CurrentScope = CommandScope;375 376 OnSetCurrentScope();377 }378 379 /// <summary>380 /// Restores the current session state scope to the scope which was active when SetCurrentScopeToExecutionScope381 /// was called.382 /// </summary>383 internal void RestorePreviousScope()384 {385 OnRestorePreviousScope();386 387 Context.EngineSessionState = _previousCommandSessionState;388 389 // Restore the scope but use the same session state instance we390 // got it from because the command may have changed the execution context391 // session state...392 CommandSessionState.CurrentScope = _previousScope;393 }394 395 private SessionStateScope _previousScope;396 private SessionStateInternal _previousCommandSessionState;397 398 /// <summary>399 /// A collection of arguments that have been added by the parser or400 /// host interfaces. These will be sent to the parameter binder controller401 /// for processing.402 /// </summary>403 internal Collection<CommandParameterInternal> arguments = new Collection<CommandParameterInternal>();404 405 /// <summary>406 /// Adds an unbound parameter.407 /// </summary>408 /// <param name="parameter">409 /// The parameter to add to the unbound arguments list410 /// </param>411 internal void AddParameter(CommandParameterInternal parameter)412 {413 Diagnostics.Assert(parameter != null, "Caller to verify parameter argument");414 arguments.Add(parameter);415 }416 417 /// <summary>418 /// Prepares the command for execution.419 /// This should be called once before ProcessRecord().420 /// </summary>421 internal abstract void Prepare(IDictionary psDefaultParameterValues);422 423 /// <summary>424 /// Write warning message for an obsolete command.425 /// </summary>426 /// <param name="obsoleteAttr"></param>427 private void HandleObsoleteCommand(ObsoleteAttribute obsoleteAttr)428 {429 string commandName =430 string.IsNullOrEmpty(CommandInfo.Name)431 ? "script block"432 : string.Format(System.Globalization.CultureInfo.InvariantCulture,433 CommandBaseStrings.ObsoleteCommand, CommandInfo.Name);434 435 string warningMsg = string.Format(436 System.Globalization.CultureInfo.InvariantCulture,437 CommandBaseStrings.UseOfDeprecatedCommandWarning,438 commandName, obsoleteAttr.Message);439 440 // We ignore the IsError setting because we don't want to break people when obsoleting a command441 using (this.CommandRuntime.AllowThisCommandToWrite(false))442 {443 this.CommandRuntime.WriteWarning(new WarningRecord(FQIDCommandObsolete, warningMsg));444 }445 }446 447 /// <summary>448 /// Sets the execution scope for the pipeline and then calls the Prepare449 /// abstract method which gets overridden by derived classes.450 /// </summary>451 internal void DoPrepare(IDictionary psDefaultParameterValues)452 {453 CommandProcessorBase oldCurrentCommandProcessor = _context.CurrentCommandProcessor;454 try455 {456 Context.CurrentCommandProcessor = this;457 SetCurrentScopeToExecutionScope();458 Prepare(psDefaultParameterValues);459 460 // Check obsolete attribute after Prepare so that -WarningAction will be respected for cmdlets461 if (ObsoleteAttribute != null)462 {463 // Obsolete command is rare. Put the IF here to avoid method call overhead464 HandleObsoleteCommand(ObsoleteAttribute);465 }466 }467 catch (InvalidComObjectException e)468 {469 // This type of exception could be thrown from parameter binding.470 string msg = StringUtil.Format(ParserStrings.InvalidComObjectException, e.Message);471 var newEx = new RuntimeException(msg, e);472 473 newEx.SetErrorId("InvalidComObjectException");474 throw newEx;475 }476 finally477 {478 Context.CurrentCommandProcessor = oldCurrentCommandProcessor;479 RestorePreviousScope();480 }481 }482 483 /// <summary>484 /// Called once before ProcessRecord(). Internally it calls485 /// BeginProcessing() of the InternalCommand.486 /// </summary>487 /// <exception cref="PipelineStoppedException">488 /// a terminating error occurred, or the pipeline was otherwise stopped489 /// </exception>490 internal virtual void DoBegin()491 {492 // Note that DoPrepare() and DoBegin() should NOT be combined.493 // Reason: Encoding of commandline parameters happen as part494 // of DoPrepare(). If they are combined, the first command's495 // DoBegin() will be called before the next command's496 // DoPrepare(). Since BeginProcessing() can write objects497 // to the downstream commandlet, it will end up calling498 // DoExecute() (from Pipe.Add()) before DoPrepare.499 if (!RanBeginAlready)500 {501 RanBeginAlready = true;502 Pipe oldErrorOutputPipe = _context.ShellFunctionErrorOutputPipe;503 CommandProcessorBase oldCurrentCommandProcessor = _context.CurrentCommandProcessor;504 try505 {506 //507 // On V1 the output pipe was redirected to the command's output pipe only when it508 // was already redirected. This is the original comment explaining this behaviour:509 //510 // NTRAID#Windows Out of Band Releases-926183-2005-12-15511 // MonadTestHarness has a bad dependency on an artifact of the current implementation512 // The following code only redirects the output pipe if it's already redirected513 // to preserve the artifact. The test suites need to be fixed and then this514 // the check can be removed and the assignment always done.515 //516 // However, this makes the hosting APIs behave differently than commands executed517 // from the command-line host (for example, see bugs Win7:415915 and Win7:108670).518 // The RedirectShellErrorOutputPipe flag is used by the V2 hosting API to force the519 // redirection.520 //521 if (RedirectShellErrorOutputPipe || _context.ShellFunctionErrorOutputPipe is not null)522 {523 _context.ShellFunctionErrorOutputPipe = commandRuntime.ErrorOutputPipe;524 }525 526 _context.CurrentCommandProcessor = this;527 SetCurrentScopeToExecutionScope();528 529 using (commandRuntime.AllowThisCommandToWrite(true))530 using (ParameterBinderBase.bindingTracer.TraceScope("CALLING BeginProcessing"))531 {532 if (Context._debuggingMode > 0 && Command is not PSScriptCmdlet)533 {534 Context.Debugger.CheckCommand(Command.MyInvocation);535 }536 537 Command.DoBeginProcessing();538 }539 }540 catch (Exception e)541 {542 // This cmdlet threw an exception, so543 // wrap it and bubble it up.544 throw ManageInvocationException(e);545 }546 finally547 {548 _context.ShellFunctionErrorOutputPipe = oldErrorOutputPipe;549 _context.CurrentCommandProcessor = oldCurrentCommandProcessor;550 RestorePreviousScope();551 }552 }553 }554 555 /// <summary>556 /// This calls the command. It assumes that DoPrepare() has already been called.557 /// </summary>558 internal abstract void ProcessRecord();559 560 /// <summary>561 /// This method sets the execution scope to the562 /// appropriate scope for the pipeline and then calls563 /// the ProcessRecord abstract method that derived command processors564 /// override.565 /// </summary>566 internal void DoExecute()567 {568 ExecutionContext.CheckStackDepth();569 570 CommandProcessorBase oldCurrentCommandProcessor = _context.CurrentCommandProcessor;571 try572 {573 Context.CurrentCommandProcessor = this;574 SetCurrentScopeToExecutionScope();575 ProcessRecord();576 }577 finally578 {579 Context.CurrentCommandProcessor = oldCurrentCommandProcessor;580 RestorePreviousScope();581 }582 }583 584 /// <summary>585 /// Called once after ProcessRecord().586 /// Internally it calls EndProcessing() of the InternalCommand.587 /// </summary>588 /// <exception cref="PipelineStoppedException">589 /// A terminating error occurred, or the pipeline was otherwise stopped.590 /// </exception>591 internal virtual void Complete()592 {593 // Call ProcessRecord once from complete. Don't call DoExecute...594 ProcessRecord();595 596 try597 {598 using (commandRuntime.AllowThisCommandToWrite(true))599 using (ParameterBinderBase.bindingTracer.TraceScope("CALLING EndProcessing"))600 {601 this.Command.DoEndProcessing();602 }603 }604 catch (Exception e)605 {606 // This cmdlet threw an exception, wrap it as needed and bubble it up.607 throw ManageInvocationException(e);608 }609 }610 611 /// <summary>612 /// Calls the virtual Complete method after setting the appropriate session state scope.613 /// </summary>614 internal void DoComplete()615 {616 Pipe oldErrorOutputPipe = _context.ShellFunctionErrorOutputPipe;617 CommandProcessorBase oldCurrentCommandProcessor = _context.CurrentCommandProcessor;618 try619 {620 //621 // On V1 the output pipe was redirected to the command's output pipe only when it622 // was already redirected. This is the original comment explaining this behaviour:623 //624 // NTRAID#Windows Out of Band Releases-926183-2005-12-15625 // MonadTestHarness has a bad dependency on an artifact of the current implementation626 // The following code only redirects the output pipe if it's already redirected627 // to preserve the artifact. The test suites need to be fixed and then this628 // the check can be removed and the assignment always done.629 //630 // However, this makes the hosting APIs behave differently than commands executed631 // from the command-line host (for example, see bugs Win7:415915 and Win7:108670).632 // The RedirectShellErrorOutputPipe flag is used by the V2 hosting API to force the633 // redirection.634 //635 if (RedirectShellErrorOutputPipe || _context.ShellFunctionErrorOutputPipe is not null)636 {637 _context.ShellFunctionErrorOutputPipe = commandRuntime.ErrorOutputPipe;638 }639 640 _context.CurrentCommandProcessor = this;641 SetCurrentScopeToExecutionScope();642 Complete();643 }644 finally645 {646 _context.ShellFunctionErrorOutputPipe = oldErrorOutputPipe;647 _context.CurrentCommandProcessor = oldCurrentCommandProcessor;648 649 RestorePreviousScope();650 }651 }652 653 protected virtual void CleanResource()654 {655 try656 {657 using (commandRuntime.AllowThisCommandToWrite(permittedToWriteToPipeline: true))658 using (ParameterBinderBase.bindingTracer.TraceScope("CALLING CleanResource"))659 {660 Command.DoCleanResource();661 }662 }663 catch (HaltCommandException)664 {665 throw;666 }667 catch (FlowControlException)668 {669 throw;670 }671 catch (Exception e)672 {673 // This cmdlet threw an exception, so wrap it and bubble it up.674 throw ManageInvocationException(e);675 }676 }677 678 internal void DoCleanup()679 {680 // The property 'PropagateExceptionsToEnclosingStatementBlock' controls whether a general exception681 // (an exception thrown from a .NET method invocation, or an expression like '1/0') will be turned682 // into a terminating error, which will be propagated up and thus stop the rest of the running script.683 // It is usually used by TryStatement and TrapStatement, which makes the general exception catch-able.684 //685 // For the 'Clean' block, we don't want to bubble up the general exception when the command is enclosed686 // in a TryStatement or has TrapStatement accompanying, because no exception can escape from 'Clean' and687 // thus it's pointless to bubble up the general exception in this case.688 //689 // Therefore we set this property to 'false' here to mask off the previous setting that could be from a690 // TryStatement or TrapStatement. Example:691 // PS:1> function b { end {} clean { 1/0; Write-Host 'clean' } }692 // PS:2> b693 // RuntimeException: Attempted to divide by zero.694 // clean695 // ## Note that, outer 'try/trap' doesn't affect the general exception happens in 'Clean' block.696 // ## so its behavior is consistent regardless of whether the command is enclosed by 'try/catch' or not.697 // PS:3> try { b } catch { 'outer catch' }698 // RuntimeException: Attempted to divide by zero.699 // clean700 //701 // Be noted that, this doesn't affect the TryStatement/TrapStatement within the 'Clean' block. Example:702 // ## 'try/trap' within 'Clean' block makes the general exception catch-able.703 // PS:3> function a { end {} clean { try { 1/0; Write-Host 'clean' } catch { Write-Host "caught: $_" } } }704 // PS:4> a705 // caught: Attempted to divide by zero.706 bool oldExceptionPropagationState = _context.PropagateExceptionsToEnclosingStatementBlock;707 _context.PropagateExceptionsToEnclosingStatementBlock = false;708 709 Pipe oldErrorOutputPipe = _context.ShellFunctionErrorOutputPipe;710 CommandProcessorBase oldCurrentCommandProcessor = _context.CurrentCommandProcessor;711 712 try713 {714 if (RedirectShellErrorOutputPipe || _context.ShellFunctionErrorOutputPipe is not null)715 {716 _context.ShellFunctionErrorOutputPipe = commandRuntime.ErrorOutputPipe;717 }718 719 _context.CurrentCommandProcessor = this;720 SetCurrentScopeToExecutionScope();721 CleanResource();722 }723 finally724 {725 _context.PropagateExceptionsToEnclosingStatementBlock = oldExceptionPropagationState;726 _context.ShellFunctionErrorOutputPipe = oldErrorOutputPipe;727 _context.CurrentCommandProcessor = oldCurrentCommandProcessor;728 729 RestorePreviousScope();730 }731 }732 733 internal void ReportCleanupError(Exception exception)734 {735 var error = exception is IContainsErrorRecord icer736 ? icer.ErrorRecord737 : new ErrorRecord(exception, "Clean.ReportException", ErrorCategory.NotSpecified, targetObject: null);738 739 PSObject errorWrap = PSObject.AsPSObject(error);740 errorWrap.WriteStream = WriteStreamType.Error;741 742 var errorPipe = commandRuntime.ErrorMergeTo == MshCommandRuntime.MergeDataStream.Output743 ? commandRuntime.OutputPipe744 : commandRuntime.ErrorOutputPipe;745 746 errorPipe.Add(errorWrap);747 _context.QuestionMarkVariableValue = false;748 }749 750 /// <summary>751 /// For diagnostic purposes.752 /// </summary>753 public override string ToString()754 {755 if (CommandInfo != null)756 return CommandInfo.ToString();757 return "<NullCommandInfo>"; // does not require localization758 }759 760 /// <summary>761 /// True if Read() has not be called, false otherwise.762 /// </summary>763 private bool _firstCallToRead = true;764 765 /// <summary>766 /// Entry point used by the engine to reads the input pipeline object767 /// and binds the parameters.768 ///769 /// This default implementation reads the next pipeline object and sets770 /// it as the CurrentPipelineObject in the InternalCommand.771 /// Does not throw.772 /// </summary>773 /// <returns>774 /// True if read succeeds.775 /// </returns>776 internal virtual bool Read()777 {778 // Prepare the default value parameter list if this is the first call to Read779 if (_firstCallToRead)780 {781 _firstCallToRead = false;782 }783 784 // Retrieve the object from the input pipeline785 object inputObject = this.commandRuntime.InputPipe.Retrieve();786 787 if (inputObject == AutomationNull.Value)788 {789 return false;790 }791 792 // If we are reading input for the first command in the pipeline increment PipelineIterationInfo[0], which is the number of items read from the input793 if (this.Command.MyInvocation.PipelinePosition == 1)794 {795 this.Command.MyInvocation.PipelineIterationInfo[0]++;796 }797 798 Command.CurrentPipelineObject = LanguagePrimitives.AsPSObjectOrNull(inputObject);799 800 return true;801 }802 803 /// <summary>804 /// Wraps the exception which occurred during cmdlet invocation,805 /// stores that as the exception to be returned from806 /// PipelineProcessor.SynchronousExecute, and writes it to807 /// the error variable.808 /// </summary>809 /// <param name="e">810 /// The exception to wrap in a CmdletInvocationException or811 /// CmdletProviderInvocationException.812 /// </param>813 /// <returns>814 /// Always returns PipelineStoppedException. The caller should815 /// throw this exception.816 /// </returns>817 /// <remarks>818 /// Almost all exceptions which occur during pipeline invocation819 /// are wrapped in CmdletInvocationException before they are stored820 /// in the pipeline. However, there are several exceptions:821 ///822 /// AccessViolationException, StackOverflowException:823 /// These are considered to be such severe errors that we824 /// FailFast the process immediately.825 ///826 /// ProviderInvocationException: In this case, we assume that the827 /// cmdlet is get-item or the like, a thin wrapper around the828 /// provider API. We discard the original ProviderInvocationException829 /// and re-wrap its InnerException (the real error) in830 /// CmdletProviderInvocationException. This makes it easier to reach831 /// the real error.832 ///833 /// CmdletInvocationException, ActionPreferenceStopException:834 /// This indicates that the cmdlet itself ran a command which failed.835 /// We could go ahead and wrap the original exception in multiple836 /// layers of CmdletInvocationException, but this makes it difficult837 /// for the caller to access the root problem, plus the serialization838 /// layer might not communicate properties beyond some fixed depth.839 /// Instead, we choose to not re-wrap the exception.840 ///841 /// PipelineStoppedException: This could mean one of two things.842 /// It usually means that this pipeline has already stopped,843 /// in which case the pipeline already stores the original error.844 /// It could also mean that the cmdlet ran a command which was845 /// stopped by CTRL-C etc, in which case we choose not to846 /// re-wrap the exception as with CmdletInvocationException.847 /// </remarks>848 internal PipelineStoppedException ManageInvocationException(Exception e)849 {850 try851 {852 if (Command != null)853 {854 do // false loop855 {856 if (e is ProviderInvocationException pie)857 {858 // If a ProviderInvocationException occurred, discard the ProviderInvocationException859 // and re-wrap it in CmdletProviderInvocationException.860 e = new CmdletProviderInvocationException(pie, Command.MyInvocation);861 break;862 }863 864 // HaltCommandException will cause the command to stop, but not be reported as an error.865 // FlowControlException should not be wrapped.866 if (e is PipelineStoppedException867 || e is CmdletInvocationException868 || e is ActionPreferenceStopException869 || e is HaltCommandException870 || e is FlowControlException871 || e is ScriptCallDepthException)872 {873 // do nothing; do not rewrap these exceptions874 break;875 }876 877 RuntimeException rte = e as RuntimeException;878 if (rte != null && rte.WasThrownFromThrowStatement)879 {880 // do not rewrap a script based throw881 break;882 }883 884 // wrap all other exceptions885 e = new CmdletInvocationException(e, Command.MyInvocation);886 } while (false);887 888 // commandRuntime.ManageException will always throw PipelineStoppedException889 // Otherwise, just return this exception...890 891 // If this exception happened in a transacted cmdlet,892 // rollback the transaction893 if (commandRuntime.UseTransaction)894 {895 // The "transaction timed out" exception is896 // exceedingly obtuse. We clarify things here.897 bool isTimeoutException = false;898 Exception tempException = e;899 while (tempException != null)900 {901 if (tempException is System.TimeoutException)902 {903 isTimeoutException = true;904 break;905 }906 907 tempException = tempException.InnerException;908 }909 910 if (isTimeoutException)911 {912 ErrorRecord errorRecord = new ErrorRecord(913 new InvalidOperationException(914 TransactionStrings.TransactionTimedOut),915 "TRANSACTION_TIMEOUT",916 ErrorCategory.InvalidOperation,917 e);918 errorRecord.SetInvocationInfo(Command.MyInvocation);919 920 e = new CmdletInvocationException(errorRecord);921 }922 923 // Rollback the transaction in the case of errors.924 if (925 _context.TransactionManager.HasTransaction926 &&927 _context.TransactionManager.RollbackPreference != RollbackSeverity.Never928 )929 {930 Context.TransactionManager.Rollback(true);931 }932 }933 934 return (PipelineStoppedException)this.commandRuntime.ManageException(e);935 }936 937 // Upstream cmdlets see only that execution stopped938 // This should only happen if Command is null939 return new PipelineStoppedException();940 }941 catch (Exception)942 {943 // this method should not throw exceptions; warn about any violations on checked builds and re-throw944 Diagnostics.Assert(false, "This method should not throw exceptions!");945 throw;946 }947 }948 949 /// <summary>950 /// Stores the exception to be returned from951 /// PipelineProcessor.SynchronousExecute, and writes it to952 /// the error variable.953 /// </summary>954 /// <param name="e">955 /// The exception which occurred during script execution956 /// </param>957 /// <exception cref="PipelineStoppedException">958 /// ManageScriptException throws PipelineStoppedException if-and-only-if959 /// the exception is a RuntimeException, otherwise it returns.960 /// This allows the caller to rethrow unexpected exceptions.961 /// </exception>962 internal void ManageScriptException(RuntimeException e)963 {964 if (Command != null && commandRuntime.PipelineProcessor != null)965 {966 commandRuntime.PipelineProcessor.RecordFailure(e, Command);967 968 // An explicit throw is written to $error as an ErrorRecord, so we969 // skip adding what is more or less a duplicate.970 if (e is not PipelineStoppedException && !e.WasThrownFromThrowStatement)971 commandRuntime.AppendErrorToVariables(e);972 }973 // Upstream cmdlets see only that execution stopped974 throw new PipelineStoppedException();975 }976 977 /// <summary>978 /// Sometimes we shouldn't be rethrow the exception we previously caught,979 /// such as when the exception is handled by a trap.980 /// </summary>981 internal void ForgetScriptException()982 {983 if (Command != null && commandRuntime.PipelineProcessor != null)984 {985 commandRuntime.PipelineProcessor.ForgetFailure();986 }987 }988 989 #endregion methods990 991 #region IDispose992 // 2004/03/05-JonN BrucePay has suggested that the IDispose993 // implementations in PipelineProcessor and CommandProcessor can be994 // removed.995 private bool _disposed;996 997 /// <summary>998 /// IDisposable implementation999 /// When the command is complete, the CommandProcessorBase should be disposed.1000 /// This enables cmdlets to reliably release file handles etc.1001 /// without waiting for garbage collection.1002 /// </summary>1003 /// <remarks>We use the standard IDispose pattern</remarks>1004 public void Dispose()1005 {1006 Dispose(true);1007 GC.SuppressFinalize(this);1008 }1009 1010 private void Dispose(bool disposing)1011 {1012 if (_disposed)1013 {1014 return;1015 }1016 1017 if (disposing)1018 {1019 if (UseLocalScope)1020 {1021 // Clean up the PS drives that are associated with this local scope.1022 // This operation may be needed at multiple stages depending on whether the 'clean' block is declared:1023 // 1. when there is a 'clean' block, it needs to be done only after 'clean' block runs, because the scope1024 // needs to be preserved until the 'clean' block finish execution.1025 // 2. when there is no 'clean' block, it needs to be done when1026 // (1) there is any exception thrown from 'DoPrepare()', 'DoBegin()', 'DoExecute()', or 'DoComplete';1027 // (2) OR, the command runs to the end successfully;1028 // Doing this cleanup at those multiple stages is cumbersome. Since we will always dispose the command in1029 // the end, doing this cleanup here will cover all the above cases.1030 CommandSessionState.RemoveScope(CommandScope);1031 }1032 1033 if (Command is IDisposable id)1034 {1035 id.Dispose();1036 }1037 }1038 1039 _disposed = true;1040 }1041 1042 #endregion IDispose1043 }1044}1045 