Team Ai
Datasetpublic

MegaBites-AI/Windows-powershell

sourceHugging Facemitupdated 6mo agoView on Hugging Face
0likes308downloads
ScriptCommandProcessor.cs663 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.Diagnostics;7using System.Management.Automation.Internal;8using System.Management.Automation.Language;9using System.Management.Automation.Runspaces;10using System.Reflection;11 12using Dbg = System.Management.Automation.Diagnostics;13 14namespace System.Management.Automation15{16    /// <summary>17    /// A common base class for code shared between an interpreted (old) script block and a compiled (new) script block.18    /// </summary>19    internal abstract class ScriptCommandProcessorBase : CommandProcessorBase20    {21        protected ScriptCommandProcessorBase(ScriptBlock scriptBlock, ExecutionContext context, bool useLocalScope, CommandOrigin origin, SessionStateInternal sessionState)22            : base(new ScriptInfo(string.Empty, scriptBlock, context))23        {24            this._dontUseScopeCommandOrigin = false;25            this._fromScriptFile = false;26 27            CommonInitialization(scriptBlock, context, useLocalScope, origin, sessionState);28        }29 30        protected ScriptCommandProcessorBase(IScriptCommandInfo commandInfo, ExecutionContext context, bool useLocalScope, SessionStateInternal sessionState)31            : base((CommandInfo)commandInfo)32        {33            Diagnostics.Assert(commandInfo != null, "commandInfo cannot be null");34            Diagnostics.Assert(commandInfo.ScriptBlock != null, "scriptblock cannot be null");35 36            this._fromScriptFile = (this.CommandInfo is ExternalScriptInfo || this.CommandInfo is ScriptInfo);37            this._dontUseScopeCommandOrigin = true;38 39            CommonInitialization(commandInfo.ScriptBlock, context, useLocalScope, CommandOrigin.Internal, sessionState);40        }41 42        /// <summary>43        /// When executing a scriptblock, the command origin needs to be set for the current scope.44        /// If this true, then the scope origin will be set to the command origin. If it's false,45        /// then the scope origin will be set to Internal. This allows public functions to call46        /// private functions but still see $MyInvocation.CommandOrigin as $true.47        /// </summary>48        protected bool _dontUseScopeCommandOrigin;49 50        /// <summary>51        /// If true, then an exit exception will be rethrown instead of caught and processed...52        /// </summary>53        protected bool _rethrowExitException;54 55        /// <summary>56        /// This indicates whether exit is called during the execution of57        /// script block.58        /// </summary>59        /// <remarks>60        /// Exit command can be executed in any of begin/process/end blocks.61        ///62        /// If exit is called in one block (for example, begin), any subsequent63        /// blocks (for example, process and end) should not be executed.64        /// </remarks>65        protected bool _exitWasCalled;66 67        protected ScriptBlock _scriptBlock;68 69        private ScriptParameterBinderController _scriptParameterBinderController;70 71        internal ScriptParameterBinderController ScriptParameterBinderController72        {73            get74            {75                if (_scriptParameterBinderController == null)76                {77                    // Set up the hashtable that will be used to hold all of the bound parameters...78                    _scriptParameterBinderController =79                        new ScriptParameterBinderController(((IScriptCommandInfo)CommandInfo).ScriptBlock,80                            Command.MyInvocation, Context, Command, CommandScope);81                    _scriptParameterBinderController.CommandLineParameters.UpdateInvocationInfo(this.Command.MyInvocation);82                    this.Command.MyInvocation.UnboundArguments = _scriptParameterBinderController.DollarArgs;83                }84 85                return _scriptParameterBinderController;86            }87        }88 89        /// <summary>90        /// Helper function for setting up command object and commandRuntime object91        /// for script command processor.92        /// </summary>93        protected void CommonInitialization(ScriptBlock scriptBlock, ExecutionContext context, bool useLocalScope, CommandOrigin origin, SessionStateInternal sessionState)94        {95            Diagnostics.Assert(context != null, "execution context cannot be null");96            Diagnostics.Assert(context.Engine != null, "context.engine cannot be null");97 98            this.CommandSessionState = sessionState;99            this._context = context;100            this._rethrowExitException = this.Context.ScriptCommandProcessorShouldRethrowExit;101            this._context.ScriptCommandProcessorShouldRethrowExit = false;102 103            ScriptCommand scriptCommand = new ScriptCommand { CommandInfo = this.CommandInfo };104 105            this.Command = scriptCommand;106            // WinBlue: 219115107            // Set the command origin for the new ScriptCommand object since we're not108            // going through command discovery here where it's usually set.109            this.Command.CommandOriginInternal = origin;110            this.Command.commandRuntime = this.commandRuntime = new MshCommandRuntime(this.Context, this.CommandInfo, scriptCommand);111 112            this.CommandScope = useLocalScope113                                    ? CommandSessionState.NewScope(this.FromScriptFile)114                                    : CommandSessionState.CurrentScope;115 116            this.UseLocalScope = useLocalScope;117 118            _scriptBlock = scriptBlock;119 120            // If the script has been dotted, throw an error if it's from a different language mode.121            // Unless it was a script loaded through -File, in which case the danger of dotting other122            // language modes (getting internal functions in the user's state) isn't a danger123            if ((!this.UseLocalScope) && (!this._rethrowExitException))124            {125                ValidateCompatibleLanguageMode(_scriptBlock, context, Command.MyInvocation);126            }127        }128 129        /// <summary>130        /// Checks if user has requested help (for example passing "-?" parameter for a cmdlet)131        /// and if yes, then returns the help target to display.132        /// </summary>133        /// <param name="helpTarget">Help target to request.</param>134        /// <param name="helpCategory">Help category to request.</param>135        /// <returns><see langword="true"/> if user requested help; <see langword="false"/> otherwise.</returns>136        internal override bool IsHelpRequested(out string helpTarget, out HelpCategory helpCategory)137        {138            if (arguments != null && CommandInfo != null && !string.IsNullOrEmpty(CommandInfo.Name) && _scriptBlock != null)139            {140                foreach (CommandParameterInternal parameter in this.arguments)141                {142                    Dbg.Assert(parameter != null, "CommandProcessor.arguments shouldn't have any null arguments");143                    if (parameter.IsDashQuestion())144                    {145                        Dictionary<Ast, Token[]> scriptBlockTokenCache = new Dictionary<Ast, Token[]>();146                        HelpInfo helpInfo = _scriptBlock.GetHelpInfo(context: Context, commandInfo: CommandInfo,147                            dontSearchOnRemoteComputer: false, scriptBlockTokenCache: scriptBlockTokenCache, helpFile: out _, helpUriFromDotLink: out _);148                        if (helpInfo == null)149                        {150                            break;151                        }152 153                        helpTarget = helpInfo.Name;154                        helpCategory = helpInfo.HelpCategory;155                        return true;156                    }157                }158            }159 160            return base.IsHelpRequested(out helpTarget, out helpCategory);161        }162    }163 164    /// <summary>165    /// This class implements a command processor for script related commands.166    /// </summary>167    /// <remarks>168    /// 1. Usage scenarios169    ///170    /// ScriptCommandProcessor is used for four kinds of commands.171    ///172    /// a. Functions and filters173    ///174    /// For example,175    ///176    ///     function foo($a) {$a}177    ///     foo "my text"178    ///179    /// Second command is an example of a function invocation.180    ///181    /// In this case, a FunctionInfo object is provided while constructing182    /// command processor.183    ///184    /// b. Script File185    ///186    /// For example,187    ///188    ///     . .\my.ps1189    ///190    /// In this case, a ExternalScriptInfo or ScriptInfo object is provided191    /// while constructing command processor.192    ///193    /// c. ScriptBlock194    ///195    /// For example,196    ///197    ///     . {$a = 5}198    ///199    /// In this case, a ScriptBlock object is provided while constructing command200    /// processor.201    ///202    /// d. Script Text203    ///204    /// This is used internally for directly running a text stream of script.205    ///206    /// 2. Design207    ///208    /// a. Script block209    ///210    /// No matter how a script command processor is created, core piece of information211    /// is always a ScriptBlock object, which can come from either a FunctionInfo object,212    /// a ScriptInfo object, or directly parsed from script text.213    ///214    /// b. Script scope215    ///216    /// A script block can be executed either in current scope or in a new scope.217    ///218    /// New scope created should be a scope supporting $script: in case the command219    /// processor is created from a script file.220    ///221    /// c. Begin/Process/End blocks222    ///223    /// Each script block can have one block of script for begin/process/end. These map224    /// to BeginProcessing, ProcessingRecord, and EndProcessing of cmdlet api.225    ///226    /// d. ExitException handling227    ///228    /// If the command processor is created based on a script file, its exit exception229    /// handling is different in the sense that it indicates an exitcode instead of killing230    /// current powershell session.231    /// </remarks>232    internal sealed class DlrScriptCommandProcessor : ScriptCommandProcessorBase233    {234        private readonly ArrayList _input = new ArrayList();235        private readonly object _dollarUnderbar = AutomationNull.Value;236        private new ScriptBlock _scriptBlock;237        private MutableTuple _localsTuple;238        private bool _runOptimizedCode;239        private bool _argsBound;240        private bool _anyClauseExecuted;241        private FunctionContext _functionContext;242 243        internal DlrScriptCommandProcessor(ScriptBlock scriptBlock, ExecutionContext context, bool useNewScope, CommandOrigin origin, SessionStateInternal sessionState, object dollarUnderbar)244            : base(scriptBlock, context, useNewScope, origin, sessionState)245        {246            Init();247            _dollarUnderbar = dollarUnderbar;248        }249 250        internal DlrScriptCommandProcessor(ScriptBlock scriptBlock, ExecutionContext context, bool useNewScope, CommandOrigin origin, SessionStateInternal sessionState)251            : base(scriptBlock, context, useNewScope, origin, sessionState)252        {253            Init();254        }255 256        internal DlrScriptCommandProcessor(FunctionInfo functionInfo, ExecutionContext context, bool useNewScope, SessionStateInternal sessionState)257            : base(functionInfo, context, useNewScope, sessionState)258        {259            Init();260        }261 262        internal DlrScriptCommandProcessor(ScriptInfo scriptInfo, ExecutionContext context, bool useNewScope, SessionStateInternal sessionState)263            : base(scriptInfo, context, useNewScope, sessionState)264        {265            Init();266        }267 268        internal DlrScriptCommandProcessor(ExternalScriptInfo scriptInfo, ExecutionContext context, bool useNewScope, SessionStateInternal sessionState)269            : base(scriptInfo, context, useNewScope, sessionState)270        {271            Init();272        }273 274        private void Init()275        {276            _scriptBlock = base._scriptBlock;277            _obsoleteAttribute = _scriptBlock.ObsoleteAttribute;278            _runOptimizedCode = _scriptBlock.Compile(optimized: _context._debuggingMode <= 0 && UseLocalScope);279            _localsTuple = _scriptBlock.MakeLocalsTuple(_runOptimizedCode);280 281            if (UseLocalScope)282            {283                Diagnostics.Assert(CommandScope.LocalsTuple == null, "a newly created scope shouldn't have it's tuple set.");284                CommandScope.LocalsTuple = _localsTuple;285            }286        }287 288        /// <summary>289        /// Get the ObsoleteAttribute of the current command.290        /// </summary>291        internal override ObsoleteAttribute ObsoleteAttribute292        {293            get { return _obsoleteAttribute; }294        }295 296        private ObsoleteAttribute _obsoleteAttribute;297 298        internal override void Prepare(IDictionary psDefaultParameterValues)299        {300            _localsTuple.SetAutomaticVariable(AutomaticVariable.MyInvocation, this.Command.MyInvocation, _context);301            _scriptBlock.SetPSScriptRootAndPSCommandPath(_localsTuple, _context);302            _functionContext = new FunctionContext303            {304                _executionContext = _context,305                _outputPipe = commandRuntime.OutputPipe,306                _localsTuple = _localsTuple,307                _scriptBlock = _scriptBlock,308                _file = _scriptBlock.File,309                _debuggerHidden = _scriptBlock.DebuggerHidden,310                _debuggerStepThrough = _scriptBlock.DebuggerStepThrough,311                _sequencePoints = _scriptBlock.SequencePoints,312            };313        }314 315        /// <summary>316        /// Execute BeginProcessing part of command. It sets up the overall scope317        /// object for this command and runs the begin clause of the script block if318        /// it isn't empty.319        /// </summary>320        /// <exception cref="PipelineStoppedException">321        /// a terminating error occurred, or the pipeline was otherwise stopped322        /// </exception>323        internal override void DoBegin()324        {325            if (!RanBeginAlready)326            {327                RanBeginAlready = true;328 329                ScriptBlock.LogScriptBlockStart(_scriptBlock, Context.CurrentRunspace.InstanceId);330 331                // Even if there is no begin, we need to set up the execution scope for this script...332                SetCurrentScopeToExecutionScope();333                CommandProcessorBase oldCurrentCommandProcessor = Context.CurrentCommandProcessor;334                try335                {336                    Context.CurrentCommandProcessor = this;337 338                    if (_scriptBlock.HasBeginBlock)339                    {340                        RunClause(_runOptimizedCode ? _scriptBlock.BeginBlock : _scriptBlock.UnoptimizedBeginBlock,341                                  AutomationNull.Value, _input);342                    }343                }344                finally345                {346                    Context.CurrentCommandProcessor = oldCurrentCommandProcessor;347                    RestorePreviousScope();348                }349            }350        }351 352        internal override void ProcessRecord()353        {354            if (_exitWasCalled)355            {356                return;357            }358 359            if (!this.RanBeginAlready)360            {361                RanBeginAlready = true;362 363                if (_scriptBlock.HasBeginBlock)364                {365                    RunClause(_runOptimizedCode ? _scriptBlock.BeginBlock : _scriptBlock.UnoptimizedBeginBlock, AutomationNull.Value, _input);366                }367            }368 369            if (_scriptBlock.HasProcessBlock)370            {371                if (!IsPipelineInputExpected())372                {373                    RunClause(_runOptimizedCode ? _scriptBlock.ProcessBlock : _scriptBlock.UnoptimizedProcessBlock, null, _input);374                }375                else376                {377                    DoProcessRecordWithInput();378                }379            }380            else if (IsPipelineInputExpected())381            {382                // accumulate the input when working in "synchronous" mode383                Debug.Assert(this.Command.MyInvocation.PipelineIterationInfo != null); // this should have been allocated when the pipe was started384                if (this.CommandRuntime.InputPipe.ExternalReader == null)385                {386                    while (Read())387                    {388                        // accumulate all of the objects and execute at the end.389                        _input.Add(Command.CurrentPipelineObject);390                    }391                }392            }393        }394 395        internal override void Complete()396        {397            try398            {399                if (_exitWasCalled)400                {401                    return;402                }403 404                // process any items that may still be in the input pipeline405                if (_scriptBlock.HasProcessBlock && IsPipelineInputExpected())406                {407                    DoProcessRecordWithInput();408                }409 410                if (_scriptBlock.HasEndBlock)411                {412                    var endBlock = _runOptimizedCode ? _scriptBlock.EndBlock : _scriptBlock.UnoptimizedEndBlock;413 414                    if (this.CommandRuntime.InputPipe.ExternalReader == null)415                    {416                        if (IsPipelineInputExpected())417                        {418                            // read any items that may still be in the input pipe419                            while (Read())420                            {421                                _input.Add(Command.CurrentPipelineObject);422                            }423                        }424 425                        // run with accumulated input426                        RunClause(endBlock, AutomationNull.Value, _input);427                    }428                    else429                    {430                        // run with asynchronously updated $input enumerator431                        RunClause(endBlock, AutomationNull.Value, this.CommandRuntime.InputPipe.ExternalReader.GetReadEnumerator());432                    }433                }434            }435            finally436            {437                if (!_scriptBlock.HasCleanBlock)438                {439                    ScriptBlock.LogScriptBlockEnd(_scriptBlock, Context.CurrentRunspace.InstanceId);440                }441            }442        }443 444        protected override void CleanResource()445        {446            if (_scriptBlock.HasCleanBlock && _anyClauseExecuted)447            {448                // The 'Clean' block doesn't write to pipeline.449                Pipe oldOutputPipe = _functionContext._outputPipe;450                _functionContext._outputPipe = new Pipe { NullPipe = true };451 452                try453                {454                    RunClause(455                        clause: _runOptimizedCode ? _scriptBlock.CleanBlock : _scriptBlock.UnoptimizedCleanBlock,456                        dollarUnderbar: AutomationNull.Value,457                        inputToProcess: AutomationNull.Value);458                }459                finally460                {461                    _functionContext._outputPipe = oldOutputPipe;462                    ScriptBlock.LogScriptBlockEnd(_scriptBlock, Context.CurrentRunspace.InstanceId);463                }464            }465        }466 467        private void DoProcessRecordWithInput()468        {469            // block for input and execute "process" block for all input objects470            Debug.Assert(this.Command.MyInvocation.PipelineIterationInfo != null); // this should have been allocated when the pipe was started471            var processBlock = _runOptimizedCode ? _scriptBlock.ProcessBlock : _scriptBlock.UnoptimizedProcessBlock;472            while (Read())473            {474                _input.Add(Command.CurrentPipelineObject);475 476                this.Command.MyInvocation.PipelineIterationInfo[this.Command.MyInvocation.PipelinePosition]++;477 478                RunClause(processBlock, Command.CurrentPipelineObject, _input);479 480                // now clear input for next iteration; also makes it clear for the end clause.481                _input.Clear();482            }483        }484 485        private void RunClause(Action<FunctionContext> clause, object dollarUnderbar, object inputToProcess)486        {487            ExecutionContext.CheckStackDepth();488 489            _anyClauseExecuted = true;490            Pipe oldErrorOutputPipe = this.Context.ShellFunctionErrorOutputPipe;491 492            // If the script block has a different language mode than the current,493            // change the language mode.494            PSLanguageMode? oldLanguageMode = null;495            PSLanguageMode? newLanguageMode = null;496            if ((_scriptBlock.LanguageMode.HasValue) &&497                (_scriptBlock.LanguageMode != Context.LanguageMode))498            {499                oldLanguageMode = Context.LanguageMode;500                newLanguageMode = _scriptBlock.LanguageMode;501            }502 503            try504            {505                var oldScopeOrigin = this.Context.EngineSessionState.CurrentScope.ScopeOrigin;506 507                try508                {509                    this.Context.EngineSessionState.CurrentScope.ScopeOrigin =510                        this._dontUseScopeCommandOrigin ? CommandOrigin.Internal : this.Command.CommandOrigin;511 512                    // Set the language mode. We do this before EnterScope(), so that the language513                    // mode is appropriately applied for evaluation parameter defaults.514                    if (newLanguageMode.HasValue)515                    {516                        Context.LanguageMode = newLanguageMode.Value;517                    }518 519                    bool? oldLangModeTransitionStatus = null;520                    try521                    {522                        // If it's from ConstrainedLanguage to FullLanguage, indicate the transition before parameter binding takes place.523                        if (oldLanguageMode == PSLanguageMode.ConstrainedLanguage && newLanguageMode == PSLanguageMode.FullLanguage)524                        {525                            oldLangModeTransitionStatus = Context.LanguageModeTransitionInParameterBinding;526                            Context.LanguageModeTransitionInParameterBinding = true;527                        }528 529                        EnterScope();530                    }531                    finally532                    {533                        if (oldLangModeTransitionStatus.HasValue)534                        {535                            // Revert the transition state to old value after doing the parameter binding536                            Context.LanguageModeTransitionInParameterBinding = oldLangModeTransitionStatus.Value;537                        }538                    }539 540                    if (commandRuntime.ErrorMergeTo == MshCommandRuntime.MergeDataStream.Output)541                    {542                        Context.RedirectErrorPipe(commandRuntime.OutputPipe);543                    }544                    else if (commandRuntime.ErrorOutputPipe.IsRedirected)545                    {546                        Context.RedirectErrorPipe(commandRuntime.ErrorOutputPipe);547                    }548 549                    if (dollarUnderbar != AutomationNull.Value)550                    {551                        _localsTuple.SetAutomaticVariable(AutomaticVariable.Underbar, dollarUnderbar, _context);552                    }553                    else if (_dollarUnderbar != AutomationNull.Value)554                    {555                        _localsTuple.SetAutomaticVariable(AutomaticVariable.Underbar, _dollarUnderbar, _context);556                    }557 558                    if (inputToProcess != AutomationNull.Value)559                    {560                        if (inputToProcess == null)561                        {562                            inputToProcess = MshCommandRuntime.StaticEmptyArray.GetEnumerator();563                        }564                        else565                        {566                            IList list = inputToProcess as IList;567                            inputToProcess = (list != null)568                                                 ? list.GetEnumerator()569                                                 : LanguagePrimitives.GetEnumerator(inputToProcess);570                        }571 572                        _localsTuple.SetAutomaticVariable(AutomaticVariable.Input, inputToProcess, _context);573                    }574 575                    clause(_functionContext);576                }577                catch (TargetInvocationException tie)578                {579                    // DynamicInvoke wraps exceptions, unwrap them here.580                    throw tie.InnerException;581                }582                finally583                {584                    Context.ShellFunctionErrorOutputPipe = oldErrorOutputPipe;585 586                    if (oldLanguageMode.HasValue)587                    {588                        Context.LanguageMode = oldLanguageMode.Value;589                    }590 591                    Context.EngineSessionState.CurrentScope.ScopeOrigin = oldScopeOrigin;592                }593            }594            catch (ExitException ee)595            {596                if (!this.FromScriptFile || _rethrowExitException)597                {598                    throw;599                }600 601                this._exitWasCalled = true;602 603                int exitCode = (int)ee.Argument;604                this.Command.Context.SetVariable(SpecialVariables.LastExitCodeVarPath, exitCode);605 606                if (exitCode != 0)607                    this.commandRuntime.PipelineProcessor.ExecutionFailed = true;608            }609            catch (FlowControlException)610            {611                throw;612            }613            catch (RuntimeException e)614            {615                // This method always throws.616                ManageScriptException(e);617            }618            catch (Exception e)619            {620                // This cmdlet threw an exception, so wrap it and bubble it up.621                throw ManageInvocationException(e);622            }623        }624 625        private void EnterScope()626        {627            if (!_argsBound)628            {629                _argsBound = true;630 631                // Parameter binder may need to write warning messages for obsolete parameters632                using (commandRuntime.AllowThisCommandToWrite(false))633                {634                    this.ScriptParameterBinderController.BindCommandLineParameters(arguments);635                }636 637                _localsTuple.SetAutomaticVariable(AutomaticVariable.PSBoundParameters,638                                                  this.ScriptParameterBinderController.CommandLineParameters.GetValueToBindToPSBoundParameters(), _context);639            }640        }641 642        protected override void OnSetCurrentScope()643        {644            // When dotting a script, push the locals of automatic variables to645            // the 'DottedScopes' of the current scope.646            if (!UseLocalScope)647            {648                CommandSessionState.CurrentScope.DottedScopes.Push(_localsTuple);649            }650        }651 652        protected override void OnRestorePreviousScope()653        {654            // When dotting a script, pop the locals of automatic variables from655            // the 'DottedScopes' of the current scope.656            if (!UseLocalScope)657            {658                CommandSessionState.CurrentScope.DottedScopes.Pop();659            }660        }661    }662}663