Team Ai
Datasetpublic

MegaBites-AI/Windows-powershell

sourceHugging Facemitupdated 6mo agoView on Hugging Face
0likes308downloads
CommandProcessorBase.cs1045 linesDownload Raw Back to engine
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