MegaBites-AI/Windows-powershell
0308
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 