Team Ai
Datasetpublic

MegaBites-AI/Windows-powershell

sourceHugging Facemitupdated 6mo agoView on Hugging Face
0likes372downloads
pipeline.cs1669 linesDownload Raw Back to engine
1// Copyright (c) Microsoft Corporation.2// Licensed under the MIT License.3 4using System.Collections;5using System.Collections.Generic;6using System.Management.Automation.Runspaces;7using System.Management.Automation.Tracing;8using System.Reflection;9using System.Runtime.ExceptionServices;10using System.Threading;11using Microsoft.PowerShell.Telemetry;12 13using Dbg = System.Management.Automation.Diagnostics;14 15#pragma warning disable 1634, 1691 // Stops compiler from warning about unknown warnings16 17namespace System.Management.Automation.Internal18{19    /// <summary>20    /// Loads InternalCommand objects and executes them.21    /// </summary>22    /// <remarks>23    /// The PipelineProcessor class is not thread-safe, so methods such as24    /// AddCommand and SynchronousExecute should not be called25    /// simultaneously.  While SynchronousExecute is running, it may access26    /// ExternalInput, ExternalSuccessOutput and ExternalErrorOutput, and27    /// those objects are thread-safe.28    /// </remarks>29    internal class PipelineProcessor : IDisposable30    {31        #region private_members32 33        private readonly CancellationTokenSource _pipelineStopTokenSource = new CancellationTokenSource();34        private List<CommandProcessorBase> _commands = new List<CommandProcessorBase>();35        private List<PipelineProcessor> _redirectionPipes;36        private PipelineReader<object> _externalInputPipe;37        private PipelineWriter _externalSuccessOutput;38        private PipelineWriter _externalErrorOutput;39        private bool _executionStarted = false;40        private bool _stopping = false;41        private SessionStateScope _executionScope;42 43        private ExceptionDispatchInfo _firstTerminatingError = null;44 45        private bool _linkedSuccessOutput = false;46        private bool _linkedErrorOutput = false;47 48        private NativeCommandProcessor _lastNativeCommand;49 50        private bool _haveReportedNativePipeUsage;51 52#if !CORECLR // Impersonation Not Supported On CSS53        // This is the security context when the pipeline was allocated54        internal System.Security.SecurityContext SecurityContext =55            System.Security.SecurityContext.Capture();56#endif57        #endregion private_members58 59        #region IDispose60 61        private bool _disposed = false;62 63        /// <summary>64        /// When the command is complete, PipelineProcessor will be65        /// disposed.66        /// </summary>67        /// <remarks>68        /// This is only public because it implements an interface method.69        /// The class itself is internal.70        /// We use the standard IDispose pattern.71        /// </remarks>72        public void Dispose()73        {74            Dispose(true);75        }76 77        private void Dispose(bool disposing)78        {79            if (_disposed)80                return;81 82            if (disposing)83            {84                DisposeCommands();85                _localPipeline = null;86                _externalSuccessOutput = null;87                _externalErrorOutput = null;88                _executionScope = null;89                _eventLogBuffer = null;90                _pipelineStopTokenSource.Dispose();91#if !CORECLR // Impersonation Not Supported On CSS92                SecurityContext.Dispose();93                SecurityContext = null;94#endif95            }96 97            _disposed = true;98        }99 100        #endregion IDispose101 102        #region Execution Logging103 104        private bool _executionFailed = false;105 106        internal List<CommandProcessorBase> Commands107        {108            get { return _commands; }109        }110 111        internal bool ExecutionFailed112        {113            get114            {115                return _executionFailed;116            }117 118            set119            {120                _executionFailed = value;121            }122        }123 124        /// <summary>125        /// Gets the CancellationToken that is signaled when the pipeline is stopping.126        /// </summary>127        internal CancellationToken PipelineStopToken => _pipelineStopTokenSource.Token;128 129        internal void LogExecutionInfo(InvocationInfo invocationInfo, string text)130        {131            string message = StringUtil.Format(PipelineStrings.PipelineExecutionInformation, GetCommand(invocationInfo), text);132            Log(message, invocationInfo, PipelineExecutionStatus.Started);133        }134 135        internal void LogExecutionComplete(InvocationInfo invocationInfo, string text)136        {137            string message = StringUtil.Format(PipelineStrings.PipelineExecutionInformation, GetCommand(invocationInfo), text);138            Log(message, invocationInfo, PipelineExecutionStatus.Complete);139        }140 141        internal void LogPipelineComplete()142        {143            Log(null, null, PipelineExecutionStatus.PipelineComplete);144        }145 146        internal void LogExecutionParameterBinding(InvocationInfo invocationInfo, string parameterName, string parameterValue)147        {148            string message = StringUtil.Format(PipelineStrings.PipelineExecutionParameterBinding, GetCommand(invocationInfo), parameterName, parameterValue);149            Log(message, invocationInfo, PipelineExecutionStatus.ParameterBinding);150        }151 152        internal void LogExecutionError(InvocationInfo invocationInfo, ErrorRecord errorRecord)153        {154            if (errorRecord == null)155                return;156 157            string message = StringUtil.Format(PipelineStrings.PipelineExecutionNonTerminatingError, GetCommand(invocationInfo), errorRecord.ToString());158            Log(message, invocationInfo, PipelineExecutionStatus.Error);159        }160 161        private bool _terminatingErrorLogged = false;162 163        internal void LogExecutionException(Exception exception)164        {165            _executionFailed = true;166 167            // Only log one terminating error for pipeline execution.168            if (_terminatingErrorLogged)169                return;170 171            _terminatingErrorLogged = true;172 173            if (exception == null)174                return;175 176            string message = StringUtil.Format(PipelineStrings.PipelineExecutionTerminatingError, GetCommand(exception), exception.Message);177            Log(message, null, PipelineExecutionStatus.Error);178        }179 180        private static string GetCommand(InvocationInfo invocationInfo)181        {182            if (invocationInfo == null)183                return string.Empty;184 185            if (invocationInfo.MyCommand != null)186            {187                return invocationInfo.MyCommand.Name;188            }189 190            return string.Empty;191        }192 193        private static string GetCommand(Exception exception)194        {195            IContainsErrorRecord icer = exception as IContainsErrorRecord;196            if (icer != null && icer.ErrorRecord != null)197                return GetCommand(icer.ErrorRecord.InvocationInfo);198 199            return string.Empty;200        }201 202        private void Log(string logElement, InvocationInfo invocation, PipelineExecutionStatus pipelineExecutionStatus)203        {204            System.Management.Automation.Host.PSHostUserInterface hostInterface = null;205            if (this.LocalPipeline != null)206            {207                hostInterface = this.LocalPipeline.Runspace.GetExecutionContext.EngineHostInterface.UI;208            }209 210            // Acknowledge command completion211            if (hostInterface != null)212            {213                if (pipelineExecutionStatus == PipelineExecutionStatus.Complete)214                {215                    hostInterface.TranscribeCommandComplete(invocation);216                    return;217                }218                else if (pipelineExecutionStatus == PipelineExecutionStatus.PipelineComplete)219                {220                    hostInterface.TranscribePipelineComplete();221                    return;222                }223            }224 225            // Log the cmdlet invocation execution details if we didn't have an associated script line with it.226            if ((invocation == null) || string.IsNullOrEmpty(invocation.Line))227            {228                hostInterface?.TranscribeCommand(logElement, invocation);229            }230 231            if (_needToLog && !string.IsNullOrEmpty(logElement))232            {233                _eventLogBuffer ??= new List<string>();234                _eventLogBuffer.Add(logElement);235            }236        }237 238        private void LogToEventLog()239        {240            // We check to see if there is anything in the buffer before we flush it.241            // Flushing the empty buffer causes a measurable performance degradation.242            if (_commands?.Count > 0 && _eventLogBuffer?.Count > 0)243            {244                InternalCommand firstCmd = _commands[0].Command;245                MshLog.LogPipelineExecutionDetailEvent(246                    firstCmd.Context,247                    _eventLogBuffer,248                    firstCmd.MyInvocation);249            }250 251            // Clear the log buffer after writing the event.252            _eventLogBuffer?.Clear();253        }254 255        private bool _needToLog = false;256        private List<string> _eventLogBuffer;257 258        #endregion259 260        #region public_methods261 262        /// <summary>263        /// Add a single InternalCommand to the end of the pipeline.264        /// </summary>265        /// <returns>Results from last pipeline stage.</returns>266        /// <exception cref="InvalidOperationException">267        /// see AddCommand268        /// </exception>269        /// <exception cref="ObjectDisposedException"></exception>270        internal int Add(CommandProcessorBase commandProcessor)271        {272            if (commandProcessor is NativeCommandProcessor nativeCommand)273            {274                if (_lastNativeCommand is not null)275                {276                    // Only report experimental feature usage once per pipeline.277                    if (!_haveReportedNativePipeUsage)278                    {279                        ApplicationInsightsTelemetry.SendExperimentalUseData("PSNativeCommandPreserveBytePipe", "p");280                        _haveReportedNativePipeUsage = true;281                    }282 283                    _lastNativeCommand.DownStreamNativeCommand = nativeCommand;284                    nativeCommand.UpstreamIsNativeCommand = true;285                }286 287                _lastNativeCommand = nativeCommand;288            }289            else290            {291                _lastNativeCommand = null;292            }293 294            commandProcessor.CommandRuntime.PipelineProcessor = this;295            return AddCommand(commandProcessor, _commands.Count, readErrorQueue: false);296        }297 298        internal void AddRedirectionPipe(PipelineProcessor pipelineProcessor)299        {300            if (pipelineProcessor is null)301            {302                throw PSTraceSource.NewArgumentNullException(nameof(pipelineProcessor));303            }304 305            _redirectionPipes ??= new List<PipelineProcessor>();306            _redirectionPipes.Add(pipelineProcessor);307        }308 309        // 2004/02/28-JSnover (from spec review) ReadFromErrorQueue310        //   should be an int or enum to allow for more queues311        // 2005/03/08-JonN: This is an internal API312        /// <summary>313        /// Add a command to the pipeline.314        /// </summary>315        /// <param name="commandProcessor"></param>316        /// <param name="readFromCommand">Reference number of command from which to read, 0 for none.</param>317        /// <param name="readErrorQueue">Read from error queue of command readFromCommand.</param>318        /// <returns>Reference number of this command for use in readFromCommand.</returns>319        /// <exception cref="ObjectDisposedException"></exception>320        /// <exception cref="ArgumentException">321        /// FirstCommandCannotHaveInput: <paramref name="readFromCommand"/> must be zero322        ///   for the first command in the pipe323        /// InvalidCommandNumber: there is no command numbered <paramref name="readFromCommand"/>324        ///   A command can only read from earlier commands; this prevents circular queues325        /// </exception>326        /// <exception cref="InvalidOperationException">327        /// ExecutionAlreadyStarted: pipeline has already started or completed328        /// PipeAlreadyTaken: the downstream pipe of command <paramref name="readFromCommand"/>329        ///   is already taken330        /// </exception>331        private int AddCommand(CommandProcessorBase commandProcessor, int readFromCommand, bool readErrorQueue)332        {333            if (commandProcessor == null)334            {335                throw PSTraceSource.NewArgumentNullException(nameof(commandProcessor));336            }337 338            if (_commands == null)339            {340                // "_commands == null"341                throw PSTraceSource.NewInvalidOperationException();342            }343 344            if (_disposed)345            {346                throw PSTraceSource.NewObjectDisposedException("PipelineProcessor");347            }348 349            if (_executionStarted)350            {351                throw PSTraceSource.NewInvalidOperationException(352                    PipelineStrings.ExecutionAlreadyStarted);353            }354 355            if (commandProcessor.AddedToPipelineAlready)356            {357                throw PSTraceSource.NewInvalidOperationException(358                    PipelineStrings.CommandProcessorAlreadyUsed);359            }360 361            if (_commands.Count == 0)362            {363                if (readFromCommand != 0)364                {365                    // "First command cannot have input"366                    throw PSTraceSource.NewArgumentException(367                        nameof(readFromCommand),368                        PipelineStrings.FirstCommandCannotHaveInput);369                }370 371                commandProcessor.AddedToPipelineAlready = true;372            }373            // 2003/08/11-JonN Subsequent commands must have predecessor374            else if (readFromCommand > _commands.Count || readFromCommand <= 0)375            {376                // "invalid command number"377                throw PSTraceSource.NewArgumentException(378                    nameof(readFromCommand),379                    PipelineStrings.InvalidCommandNumber);380            }381            else382            {383                var prevcommandProcessor = _commands[readFromCommand - 1] as CommandProcessorBase;384                ValidateCommandProcessorNotNull(prevcommandProcessor, errorMessage: null);385 386                Pipe UpstreamPipe = (readErrorQueue)387                    ? prevcommandProcessor.CommandRuntime.ErrorOutputPipe388                    : prevcommandProcessor.CommandRuntime.OutputPipe;389 390                if (UpstreamPipe == null)391                {392                    throw PSTraceSource.NewInvalidOperationException();393                }394 395                if (UpstreamPipe.DownstreamCmdlet != null)396                {397                    throw PSTraceSource.NewInvalidOperationException(398                        PipelineStrings.PipeAlreadyTaken);399                }400 401                commandProcessor.AddedToPipelineAlready = true;402 403                commandProcessor.CommandRuntime.InputPipe = UpstreamPipe;404                UpstreamPipe.DownstreamCmdlet = commandProcessor;405 406                // 2004/09/14-JonN This code could be moved to SynchronousExecute407                //  if this setting needed to bind at a later time408                //  than AddCommand.409                if (commandProcessor.CommandRuntime.MergeUnclaimedPreviousErrorResults)410                {411                    for (int i = 0; i < _commands.Count; i++)412                    {413                        prevcommandProcessor = _commands[i];414                        ValidateCommandProcessorNotNull(prevcommandProcessor, errorMessage: null);415 416                        // check whether the error output is already claimed417                        if (prevcommandProcessor.CommandRuntime.ErrorOutputPipe.DownstreamCmdlet != null)418                            continue;419                        if (prevcommandProcessor.CommandRuntime.ErrorOutputPipe.ExternalWriter != null)420                            continue;421 422                        // Set the upstream cmdlet's error output to go down423                        // the same pipe as the downstream cmdlet's input424                        prevcommandProcessor.CommandRuntime.ErrorOutputPipe = UpstreamPipe;425                    }426                }427            }428 429            _commands.Add(commandProcessor);430 431            // We will log event(s) about the pipeline execution details if any command in the pipeline requests that.432            _needToLog |= commandProcessor.CommandRuntime.LogPipelineExecutionDetail;433 434            // We give the Command a pointer back to the435            // PipelineProcessor so that it can check whether the436            // command has been stopped.437            commandProcessor.CommandRuntime.PipelineProcessor = this;438 439            return _commands.Count;440        }441 442        // 2005/03/08-JonN: This is an internal API443        /// <summary>444        /// Execute the accumulated commands and clear the pipeline.445        /// SynchronousExecute does not return until all commands have446        /// completed.  There is no asynchronous variant; instead, once the447        /// pipeline is set up, the caller can spawn a thread and call448        /// SynchronousExecute from that thread.  This does not mean that449        /// PipelineProcessor is thread-safe; once SynchronousExecute is450        /// running, PipelineProcessor should not be accessed through any451        /// other means. This variant of the routine looks at it's input452        /// object to see if it's enumerable or not.453        /// </summary>454        /// <param name="input">455        /// Input objects for first stage. If this is AutomationNull.Value, the456        /// first cmdlet is the beginning of the pipeline.457        /// </param>458        /// <returns>459        /// Results from last pipeline stage.  This will be empty if460        /// ExternalSuccessOutput is set.461        /// </returns>462        /// <exception cref="InvalidOperationException">463        /// ExecutionAlreadyStarted: pipeline has already started or completed464        /// </exception>465        /// <exception cref="InvalidOperationException">466        /// PipelineExecuteRequiresAtLeastOneCommand467        /// </exception>468        /// <exception cref="CmdletInvocationException">469        /// A cmdlet encountered a terminating error470        /// </exception>471        /// <exception cref="PipelineStoppedException">472        /// The pipeline was stopped asynchronously473        /// </exception>474        /// <exception cref="ActionPreferenceStopException">475        /// The ActionPreference.Stop or ActionPreference.Inquire policy476        /// triggered a terminating error.477        /// </exception>478        /// <exception cref="ParameterBindingException">479        /// If any parameters fail to bind,480        /// or481        /// If any mandatory parameters are missing.482        /// </exception>483        /// <exception cref="MetadataException">484        /// If there is an error generating the metadata for dynamic parameters.485        /// </exception>486        /// <exception cref="ExtendedTypeSystemException">487        /// An error occurred clearing the error variable.488        /// </exception>489        /// <exception cref="HaltCommandException">490        /// HaltCommandException will cause the command491        /// to stop, but should not be reported as an error.492        /// </exception>493        internal Array SynchronousExecuteEnumerate(object input)494        {495            if (Stopping)496            {497                throw new PipelineStoppedException();498            }499 500            bool pipelineSucceeded = false;501            ExceptionDispatchInfo toRethrowInfo = null;502            CommandProcessorBase commandRequestingUpstreamCommandsToStop = null;503 504            try505            {506                try507                {508                    try509                    {510                        // If the caller specified an input object array, we run assuming there is an incoming "stream"511                        // of objects. This will prevent the one default call to ProcessRecord on the first command.512                        Start(incomingStream: input != AutomationNull.Value);513 514                        // Start has already validated firstcommandProcessor515                        CommandProcessorBase firstCommandProcessor = _commands[0];516 517                        // Add any input to the first command.518                        if (ExternalInput is not null)519                        {520                            firstCommandProcessor.CommandRuntime.InputPipe.ExternalReader = ExternalInput;521                        }522 523                        Inject(input, enumerate: true);524                    }525                    catch (PipelineStoppedException)526                    {527                        if (_firstTerminatingError?.SourceException is StopUpstreamCommandsException exception)528                        {529                            _firstTerminatingError = null;530                            commandRequestingUpstreamCommandsToStop = exception.RequestingCommandProcessor;531                        }532                        else533                        {534                            throw;535                        }536                    }537 538                    DoCompleteCore(commandRequestingUpstreamCommandsToStop);539                    pipelineSucceeded = true;540                }541                finally542                {543                    // Clean up resources for script commands, no matter the pipeline succeeded or not.544                    // This method catches and handles all exceptions inside, so it will never throw.545                    Clean();546                }547 548                if (pipelineSucceeded)549                {550                    // Now, we are sure all 'commandProcessors' hosted by the current 'pipelineProcessor' are done execution,551                    // so if there are any redirection 'pipelineProcessors' associated with any of those 'commandProcessors',552                    // they must have successfully executed 'StartStepping' and 'Step', and thus we should call 'DoComplete'553                    // on them for completeness.554                    if (_redirectionPipes is not null)555                    {556                        foreach (PipelineProcessor redirectPipelineProcessor in _redirectionPipes)557                        {558                            // The 'Clean' block for each 'commandProcessor' might still write to a pipe that is associated559                            // with the redirection 'pipelineProcessor' (e.g. a redirected error pipe), which would trigger560                            // the call to 'pipelineProcessor.Step'.561                            // It's possible (though very unlikely) that the call to 'pipelineProcessor.Step' failed with an562                            // exception, and in such case, the 'pipelineProcessor' would have been disposed, and therefore563                            // the call to 'DoComplete' will simply return, because '_commands' was already set to null.564                            redirectPipelineProcessor.DoCompleteCore(null);565                        }566                    }567 568                    // The 'Clean' blocks write nothing to the output pipe, so the results won't be affected by them.569                    return RetrieveResults();570                }571            }572            catch (RuntimeException e)573            {574                toRethrowInfo = GetFirstError(e);575            }576            finally577            {578                DisposeCommands();579            }580 581            // By rethrowing the exception outside of the handler, we allow the CLR on X64/IA64 to free from582            // the stack the exception records related to this exception.583 584            // The only reason we should get here is if an exception should be rethrown.585            Diagnostics.Assert(toRethrowInfo != null, "Alternate protocol path failure");586            toRethrowInfo.Throw();587 588            // UNREACHABLE589            return null;590        }591 592        private ExceptionDispatchInfo GetFirstError(RuntimeException e)593        {594            // The error we want to report is the first terminating error which occurred during pipeline execution,595            // regardless of whether other errors occurred afterward.596            var firstError = _firstTerminatingError ?? ExceptionDispatchInfo.Capture(e);597            LogExecutionException(firstError.SourceException);598            return firstError;599        }600 601        private void ThrowFirstErrorIfExisting(bool logException)602        {603            if (_firstTerminatingError != null)604            {605                if (logException)606                {607                    LogExecutionException(_firstTerminatingError.SourceException);608                }609 610                _firstTerminatingError.Throw();611            }612        }613 614        private void DoCompleteCore(CommandProcessorBase commandRequestingUpstreamCommandsToStop)615        {616            if (_commands is null)617            {618                // This could happen to a redirection pipeline, either for an expression (e.g. 1 > a.txt)619                // or for a command (e.g. command > a.txt).620                // An exception may be thrown from the call to 'StartStepping' or 'Step' on the pipeline,621                // which causes the pipeline commands to be disposed.622                return;623            }624 625            // Call DoComplete() for all the commands, which will internally call Complete()626            MshCommandRuntime lastCommandRuntime = null;627 628            for (int i = 0; i < _commands.Count; i++)629            {630                CommandProcessorBase commandProcessor = _commands[i];631 632                if (commandProcessor is null)633                {634                    // An internal error that should not happen.635                    throw PSTraceSource.NewInvalidOperationException();636                }637 638                if (object.ReferenceEquals(commandRequestingUpstreamCommandsToStop, commandProcessor))639                {640                    // Do not call DoComplete/EndProcessing on the command that initiated stopping.641                    commandRequestingUpstreamCommandsToStop = null;642                    continue;643                }644 645                if (commandRequestingUpstreamCommandsToStop is not null)646                {647                    // Do not call DoComplete/EndProcessing on commands that were stopped upstream.648                    continue;649                }650 651                try652                {653                    commandProcessor.DoComplete();654                }655                catch (PipelineStoppedException)656                {657                    if (_firstTerminatingError?.SourceException is StopUpstreamCommandsException exception)658                    {659                        _firstTerminatingError = null;660                        commandRequestingUpstreamCommandsToStop = exception.RequestingCommandProcessor;661                    }662                    else663                    {664                        throw;665                    }666                }667 668                EtwActivity.SetActivityId(commandProcessor.PipelineActivityId);669 670                // Log a command stopped event671                MshLog.LogCommandLifecycleEvent(672                    commandProcessor.Command.Context,673                    CommandState.Stopped,674                    commandProcessor.Command.MyInvocation);675 676                // Log the execution of a command (not script chunks, as they are not commands in and of themselves).677                if (commandProcessor.CommandInfo.CommandType != CommandTypes.Script)678                {679                    LogExecutionComplete(commandProcessor.Command.MyInvocation, commandProcessor.CommandInfo.Name);680                }681 682                lastCommandRuntime = commandProcessor.CommandRuntime;683            }684 685            // Log the pipeline completion.686            if (lastCommandRuntime is not null)687            {688                // Only log the pipeline completion if this wasn't a nested pipeline, as689                // pipeline state in transcription is associated with the toplevel pipeline690                if (LocalPipeline is null || !LocalPipeline.IsNested)691                {692                    lastCommandRuntime.PipelineProcessor.LogPipelineComplete();693                }694            }695 696            // If a terminating error occurred, report it now.697            // This pipeline could have been stopped asynchronously, by 'Ctrl+c' manually or698            // 'PowerShell.Stop' programatically. We need to check and see if that's the case.699            // An example:700            // - 'Start-Sleep' is running in this pipeline, and 'pipelineProcessor.Stop' gets701            //   called on a different thread, which sets a 'PipelineStoppedException' object702            //   to '_firstTerminatingError' and runs 'StopProcessing' on 'Start-Sleep'.703            // - The 'StopProcessing' will cause 'Start-Sleep' to return from 'ProcessRecord'704            //   call, and thus the pipeline execution will move forward to run 'DoComplete'705            //   for the 'Start-Sleep' command and thus the code flow will reach here.706            // For this given example, we need to check '_firstTerminatingError' and throw out707            // the 'PipelineStoppedException' if the pipeline was indeed being stopped.708            ThrowFirstErrorIfExisting(logException: true);709        }710 711        /// <summary>712        /// Clean up resources for script commands in this pipeline processor.713        /// </summary>714        /// <remarks>715        /// Exception from a 'Clean' block is not allowed to propagate up and terminate the pipeline716        /// so that other 'Clean' blocks can run without being affected. Therefore, this method will717        /// catch and handle all exceptions inside, and it will never throw.718        /// </remarks>719        private void Clean()720        {721            if (!_executionStarted || _commands is null)722            {723                // Simply return if the pipeline execution wasn't even started, or the commands of724                // the pipeline have already been disposed.725                return;726            }727 728            // So far, if '_firstTerminatingError' is not null, then it must be a terminating error729            // thrown from one of 'Begin/Process/End' blocks. There can be terminating error thrown730            // from 'Clean' block as well, which needs to be handled in this method.731            // In order to capture the subsequent first terminating error thrown from 'Clean', we732            // need to forget the previous '_firstTerminatingError' value before calling 'DoClean'733            // on each command processor, so we have to save the old value here and restore later.734            ExceptionDispatchInfo oldFirstTerminatingError = _firstTerminatingError;735 736            // Suspend a stopping pipeline by setting 'IsStopping' to false and restore it afterwards.737            bool oldIsStopping = ExceptionHandlingOps.SuspendStoppingPipelineImpl(LocalPipeline);738 739            try740            {741                foreach (CommandProcessorBase commandProcessor in _commands)742                {743                    if (commandProcessor is null || !commandProcessor.HasCleanBlock)744                    {745                        continue;746                    }747 748                    try749                    {750                        // Forget the terminating error we saw before, so a terminating error thrown751                        // from the subsequent 'Clean' block can be recorded and handled properly.752                        _firstTerminatingError = null;753                        commandProcessor.DoCleanup();754                    }755                    catch (RuntimeException e)756                    {757                        // Retrieve and report the terminating error that was thrown in the 'Clean' block.758                        ExceptionDispatchInfo firstError = GetFirstError(e);759                        commandProcessor.ReportCleanupError(firstError.SourceException);760                    }761                    catch (Exception ex)762                    {763                        // Theoretically, only 'RuntimeException' could be thrown out, but we catch764                        // all and log them here just to be safe.765                        // Skip special flow control exceptions and log others.766                        if (ex is not FlowControlException && ex is not HaltCommandException)767                        {768                            MshLog.LogCommandHealthEvent(commandProcessor.Context, ex, Severity.Warning);769                        }770                    }771                }772            }773            finally774            {775                _firstTerminatingError = oldFirstTerminatingError;776                ExceptionHandlingOps.RestoreStoppingPipelineImpl(LocalPipeline, oldIsStopping);777            }778        }779 780        /// <summary>781        /// Clean up resources for the script commands of a steppable pipeline.782        /// </summary>783        /// <remarks>784        /// The way we handle 'Clean' blocks in 'StartStepping', 'Step', and 'DoComplete' makes sure that:785        ///  1. The 'Clean' blocks get to run if any exception is thrown from the pipeline execution.786        ///  2. The 'Clean' blocks get to run if the pipeline runs to the end successfully.787        /// However, this is not enough for a steppable pipeline, because the function, where the steppable788        /// pipeline gets used, may fail (think about a proxy function). And that may lead to the situation789        /// where "no exception was thrown from the steppable pipeline" but "the steppable pipeline didn't790        /// run to the end". In that case, 'Clean' won't run unless it's triggered explicitly on the steppable791        /// pipeline. This method is how we will expose this functionality to 'SteppablePipeline'.792        /// </remarks>793        internal void DoCleanup()794        {795            Clean();796            DisposeCommands();797        }798 799        /// <summary>800        /// Implements DoComplete as a stand-alone function for completing801        /// the execution of a steppable pipeline.802        /// </summary>803        /// <returns>The results of the execution.</returns>804        internal Array DoComplete()805        {806            if (!_executionStarted)807            {808                throw PSTraceSource.NewInvalidOperationException(809                    PipelineStrings.PipelineNotStarted);810            }811 812            try813            {814                if (Stopping)815                {816                    throw new PipelineStoppedException();817                }818 819                ExceptionDispatchInfo toRethrowInfo;820                try821                {822                    DoCompleteCore(null);823                    return RetrieveResults();824                }825                catch (RuntimeException e)826                {827                    toRethrowInfo = GetFirstError(e);828                }829 830                // By rethrowing the exception outside of the handler, we allow the CLR on X64/IA64 to free from the stack831                // the exception records related to this exception.832 833                // The only reason we should get here is an exception should be rethrown.834                Diagnostics.Assert(toRethrowInfo != null, "Alternate protocol path failure");835                toRethrowInfo.Throw();836 837                // UNREACHABLE838                return null;839            }840            finally841            {842                Clean();843                DisposeCommands();844            }845        }846 847        /// <summary>848        /// This routine starts the stepping process. It is optional to call this but can be useful849        /// if you want the begin clauses of the pipeline to be run even when there may not be any850        /// input to process as is the case for I/O redirection into a file. We still want the file851        /// opened, even if there was nothing to write to it.852        /// </summary>853        /// <param name="expectInput">True if you want to write to this pipeline.</param>854        internal void StartStepping(bool expectInput)855        {856            bool startSucceeded = false;857            try858            {859                Start(expectInput);860                startSucceeded = true;861 862                // Check if this pipeline is being stopped asynchronously.863                ThrowFirstErrorIfExisting(logException: false);864            }865            catch (Exception e)866            {867                Clean();868                DisposeCommands();869 870                if (!startSucceeded && e is PipelineStoppedException)871                {872                    // When a terminating error happens during command execution, PowerShell will first save it873                    // to '_firstTerminatingError', and then throw a 'PipelineStoppedException' to tear down the874                    // pipeline. So when the caught exception here is 'PipelineStoppedException', it may not be875                    // the actual original terminating error.876                    // In this case, we want to report the first terminating error which occurred during pipeline877                    // execution, regardless of whether other errors occurred afterward.878                    ThrowFirstErrorIfExisting(logException: false);879                }880 881                throw;882            }883        }884 885        /// <summary>886        /// Request that the pipeline execution should stop.  Unlike other887        /// methods of PipelineProcessor, this method can be called888        /// asynchronously.889        /// </summary>890        internal void Stop()891        {892            // Only call StopProcessing if the pipeline is being stopped893            // for the first time894 895            if (!RecordFailure(new PipelineStoppedException(), command: null))896            {897                return;898            }899 900            // Retain copy of _commands in case Dispose() is called901            List<CommandProcessorBase> commands = _commands;902            if (commands is null)903            {904                return;905            }906 907            _pipelineStopTokenSource.Cancel();908 909            // Call StopProcessing() for all the commands.910            foreach (CommandProcessorBase commandProcessor in commands)911            {912                if (commandProcessor == null)913                {914                    throw PSTraceSource.NewInvalidOperationException();915                }916 917                try918                {919                    commandProcessor.Command.DoStopProcessing();920                }921                catch (Exception)922                {923                    // We swallow exceptions which occur during StopProcessing.924                    continue;925                }926            }927        }928 929        #endregion public_methods930 931        #region private_methods932 933        /// <summary>934        /// Partially execute the pipeline, and retrieve the output935        /// after the input objects have been entered into the pipe.936        /// </summary>937        /// <param name="input">938        /// Array of input objects for first stage939        /// </param>940        /// <returns>941        /// Results from last pipeline stage.  This will be empty if942        /// ExternalSuccessOutput is set.943        /// </returns>944        /// <exception cref="InvalidOperationException">945        /// PipelineExecuteRequiresAtLeastOneCommand946        /// </exception>947        /// <exception cref="PipelineStoppedException">948        /// The pipeline has already been stopped, or a cmdlet encountered949        /// a terminating error950        /// </exception>951        /// <exception cref="ParameterBindingException">952        /// If any parameters fail to bind,953        /// or954        /// If any mandatory parameters are missing.955        /// </exception>956        /// <exception cref="MetadataException">957        /// If there is an error generating the metadata for dynamic parameters.958        /// </exception>959        /// <exception cref="PipelineStoppedException">960        /// The pipeline has already been stopped,961        /// or a terminating error occurred.962        /// </exception>963        /// <exception cref="ExtendedTypeSystemException">964        /// An error occurred clearing the error variable.965        /// </exception>966        internal Array Step(object input)967        {968            bool injectSucceeded = false;969            try970            {971                Start(true);972                Inject(input, enumerate: false);973                injectSucceeded = true;974 975                // Check if this pipeline is being stopped asynchronously.976                ThrowFirstErrorIfExisting(logException: false);977                return RetrieveResults();978            }979            catch (Exception e)980            {981                Clean();982                DisposeCommands();983 984                if (!injectSucceeded && e is PipelineStoppedException)985                {986                    // When a terminating error happens during command execution, PowerShell will first save it987                    // to '_firstTerminatingError', and then throw a 'PipelineStoppedException' to tear down the988                    // pipeline. So when the caught exception here is 'PipelineStoppedException', it may not be989                    // the actual original terminating error.990                    // In this case, we want to report the first terminating error which occurred during pipeline991                    // execution, regardless of whether other errors occurred afterward.992                    ThrowFirstErrorIfExisting(logException: false);993                }994 995                throw;996            }997        }998 999        /// <summary>1000        /// Prepares the pipeline for execution.1001        /// </summary>1002        /// <param name="incomingStream">1003        /// Input objects are expected, so do not close the first command.1004        /// This will prevent the one default call to ProcessRecord1005        /// on the first command.1006        /// </param>1007        /// <remarks>1008        /// Start must always be called in a context where terminating errors will1009        /// be caught and result in DisposeCommands.1010        /// </remarks>1011        /// <exception cref="InvalidOperationException">1012        /// PipelineExecuteRequiresAtLeastOneCommand1013        /// </exception>1014        /// <exception cref="ParameterBindingException">1015        /// If any parameters fail to bind,1016        /// or1017        /// If any mandatory parameters are missing.1018        /// </exception>1019        /// <exception cref="MetadataException">1020        /// If there is an error generating the metadata for dynamic parameters.1021        /// </exception>1022        /// <exception cref="PipelineStoppedException">1023        /// The pipeline has already been stopped,1024        /// or a terminating error occurred in a downstream cmdlet.1025        /// </exception>1026        /// <exception cref="ExtendedTypeSystemException">1027        /// An error occurred clearing the error variable.1028        /// </exception>1029        private void Start(bool incomingStream)1030        {1031            // Every call to Step or SynchronousExecute will call Start.1032            if (_disposed)1033            {1034                throw PSTraceSource.NewObjectDisposedException("PipelineProcessor");1035            }1036 1037            if (Stopping)1038            {1039                throw new PipelineStoppedException();1040            }1041 1042            if (_executionStarted)1043            {1044                return;1045            }1046 1047            if (_commands == null || _commands.Count == 0)1048            {1049                throw PSTraceSource.NewInvalidOperationException(1050                    PipelineStrings.PipelineExecuteRequiresAtLeastOneCommand);1051            }1052 1053            CommandProcessorBase firstcommandProcessor = _commands[0];1054            ValidateCommandProcessorNotNull(firstcommandProcessor, PipelineStrings.PipelineExecuteRequiresAtLeastOneCommand);1055 1056            // Set the execution scope using the current scope1057            _executionScope ??= firstcommandProcessor.Context.EngineSessionState.CurrentScope;1058 1059            // add ExternalSuccessOutput to the last command1060            CommandProcessorBase LastCommandProcessor = _commands[_commands.Count - 1];1061            ValidateCommandProcessorNotNull(LastCommandProcessor, errorMessage: null);1062 1063            if (ExternalSuccessOutput != null)1064            {1065                LastCommandProcessor.CommandRuntime.OutputPipe.ExternalWriter = ExternalSuccessOutput;1066            }1067 1068            // add ExternalErrorOutput to all commands whose error1069            // output is not yet claimed1070            SetExternalErrorOutput();1071 1072            if (ExternalInput == null && !incomingStream)1073            {1074                // no upstream cmdlet from the first command1075                firstcommandProcessor.CommandRuntime.IsClosed = true;1076            }1077 1078            // We want the value of PSDefaultParameterValues before possibly changing to the commands scopes.1079            // This ensures we use the value from the caller's scope, not the callee's scope.1080            IDictionary psDefaultParameterValues =1081                firstcommandProcessor.Context.GetVariableValue(SpecialVariables.PSDefaultParameterValuesVarPath, false) as IDictionary;1082 1083            _executionStarted = true;1084 1085            // Allocate the pipeline iteration array; note that the pipeline position for1086            // each command starts at 1 so we need to allocate _commands.Count + 1 items.1087            int[] pipelineIterationInfo = new int[_commands.Count + 1];1088 1089            // Prepare all commands from Engine's side, and make sure they are all valid1090            for (int i = 0; i < _commands.Count; i++)1091            {1092                CommandProcessorBase commandProcessor = _commands[i];1093                if (commandProcessor == null)1094                {1095                    // "null command " + i1096                    throw PSTraceSource.NewInvalidOperationException();1097                }1098 1099                // Generate new Activity Id for the thread1100                Guid pipelineActivityId = EtwActivity.CreateActivityId();1101                EtwActivity.SetActivityId(pipelineActivityId);1102                commandProcessor.PipelineActivityId = pipelineActivityId;1103 1104                // Log a command started event1105                MshLog.LogCommandLifecycleEvent(1106                    commandProcessor.Context,1107                    CommandState.Started,1108                    commandProcessor.Command.MyInvocation);1109 1110#if LEGACYTELEMETRY1111                Microsoft.PowerShell.Telemetry.Internal.TelemetryAPI.TraceExecutedCommand(commandProcessor.Command.CommandInfo, commandProcessor.Command.CommandOrigin);1112#endif1113 1114                // Log the execution of a command (not script chunks, as they are not commands in and of themselves)1115                if (commandProcessor.CommandInfo.CommandType != CommandTypes.Script)1116                {1117                    LogExecutionInfo(commandProcessor.Command.MyInvocation, commandProcessor.CommandInfo.Name);1118                }1119 1120                InvocationInfo myInfo = commandProcessor.Command.MyInvocation;1121                myInfo.PipelinePosition = i + 1;1122                myInfo.PipelineLength = _commands.Count;1123                myInfo.PipelineIterationInfo = pipelineIterationInfo;1124                myInfo.ExpectingInput = commandProcessor.IsPipelineInputExpected();1125                commandProcessor.DoPrepare(psDefaultParameterValues);1126            }1127 1128            // Clear ErrorVariable as appropriate1129            SetupParameterVariables();1130 1131            // Prepare all commands from Command's side.1132            // Note that DoPrepare() and DoBegin() should NOT be combined1133            // in a single for loop.1134            // Reason: Encoding of commandline parameters happen1135            // as part of DoPrepare(). If they are combined,1136            // the first command's DoBegin() will be called before1137            // the next command's DoPrepare(). Since BeginProcessing()1138            // can write objects to the downstream commandlet,1139            // it will end up calling DoExecute() (from Pipe.Add())1140            // before DoPrepare.1141            for (int i = 0; i < _commands.Count; i++)1142            {1143                CommandProcessorBase commandProcessor = _commands[i];1144 1145                commandProcessor.DoBegin();1146            }1147        }1148 1149        /// <summary>1150        /// Add ExternalErrorOutput to all commands whose error output is not yet claimed.1151        /// </summary>1152        private void SetExternalErrorOutput()1153        {1154            if (ExternalErrorOutput != null)1155            {1156                for (int i = 0; i < _commands.Count; i++)1157                {1158                    CommandProcessorBase commandProcessor = _commands[i];1159                    Pipe errorPipe = commandProcessor.CommandRuntime.ErrorOutputPipe;1160 1161                    // check whether a cmdlet is consuming the error pipe1162                    if (!errorPipe.IsRedirected)1163                    {1164                        errorPipe.ExternalWriter = ExternalErrorOutput;1165                    }1166                }1167            }1168        }1169 1170        /// <summary>1171        /// Clear ErrorVariable as appropriate.1172        /// </summary>1173        private void SetupParameterVariables()1174        {1175            foreach (CommandProcessorBase commandProcessor in _commands)1176            {1177                ValidateCommandProcessorNotNull(commandProcessor, errorMessage: null);1178 1179                commandProcessor.CommandRuntime.SetupOutVariable();1180                commandProcessor.CommandRuntime.SetupErrorVariable();1181                commandProcessor.CommandRuntime.SetupWarningVariable();1182                commandProcessor.CommandRuntime.SetupPipelineVariable();1183                commandProcessor.CommandRuntime.SetupInformationVariable();1184            }1185        }1186 1187        private static void ValidateCommandProcessorNotNull(CommandProcessorBase commandProcessor, string errorMessage)1188        {1189            if (commandProcessor?.CommandRuntime is null)1190            {1191                throw errorMessage is null1192                    ? PSTraceSource.NewInvalidOperationException()1193                    : PSTraceSource.NewInvalidOperationException(errorMessage, Array.Empty<object>());1194            }1195        }1196 1197        /// <summary>1198        /// Partially execute the pipeline.  The output remains in1199        /// the pipes.1200        /// </summary>

Showing the first 1,200 of 1669 lines. Download the file for the rest.