MegaBites-AI/Windows-powershell
0372
1// Copyright (c) Microsoft Corporation.2// Licensed under the MIT License.3 4using System.Collections;5using System.Collections.Concurrent;6using System.Collections.Generic;7using System.Diagnostics;8using System.Linq;9using System.Management.Automation.Configuration;10using System.Management.Automation.Internal;11using System.Management.Automation.Language;12using System.Management.Automation.Runspaces;13using System.Management.Automation.Security;14using System.Management.Automation.Tracing;15using System.Reflection;16using System.Runtime.Serialization;17using System.Security.Cryptography.X509Certificates;18using System.Text;19#if LEGACYTELEMETRY20using Microsoft.PowerShell.Telemetry.Internal;21#endif22 23namespace System.Management.Automation24{25 internal enum CompileInterpretChoice26 {27 NeverCompile,28 AlwaysCompile,29 CompileOnDemand30 }31 32 internal enum ScriptBlockClauseToInvoke33 {34 Begin,35 Process,36 End,37 Clean,38 ProcessBlockOnly,39 }40 41 internal class CompiledScriptBlockData42 {43 internal CompiledScriptBlockData(IParameterMetadataProvider ast, bool isFilter)44 {45 _ast = ast;46 this.IsFilter = isFilter;47 this.Id = Guid.NewGuid();48 }49 50 internal CompiledScriptBlockData(string scriptText, bool isProductCode)51 {52 _isProductCode = isProductCode;53 _scriptText = scriptText;54 this.Id = Guid.NewGuid();55 }56 57 internal bool Compile(bool optimized)58 {59 if (_attributes == null)60 {61 InitializeMetadata();62 }63 64 // We need the name to index map to check if any allscope variables are assigned. If they65 // are, we can't run the optimized version, so we'll compile once more unoptimized and run that.66 if (optimized && NameToIndexMap == null)67 {68 CompileOptimized();69 }70 71 optimized = optimized && !VariableAnalysis.AnyVariablesCouldBeAllScope(NameToIndexMap);72 73 if (!optimized && !_compiledUnoptimized)74 {75 CompileUnoptimized();76 }77 else if (optimized && !_compiledOptimized)78 {79 CompileOptimized();80 }81 82 return optimized;83 }84 85 private void InitializeMetadata()86 {87 lock (this)88 {89 if (_attributes != null)90 {91 // Another thread must have initialized the metadata.92 return;93 }94 95 Attribute[] attributes;96 CmdletBindingAttribute cmdletBindingAttribute = null;97 if (!Ast.HasAnyScriptBlockAttributes())98 {99 attributes = Array.Empty<Attribute>();100 }101 else102 {103 attributes = Ast.GetScriptBlockAttributes().ToArray();104 foreach (var attribute in attributes)105 {106 if (attribute is CmdletBindingAttribute c)107 {108 cmdletBindingAttribute ??= c;109 }110 else if (attribute is DebuggerHiddenAttribute)111 {112 DebuggerHidden = true;113 }114 else if (attribute is DebuggerStepThroughAttribute || attribute is DebuggerNonUserCodeAttribute)115 {116 DebuggerStepThrough = true;117 }118 }119 120 _usesCmdletBinding = cmdletBindingAttribute != null;121 }122 123 bool automaticPosition = cmdletBindingAttribute == null || cmdletBindingAttribute.PositionalBinding;124 var runtimeDefinedParameterDictionary =125 Ast.GetParameterMetadata(automaticPosition, ref _usesCmdletBinding);126 127 // Initialize these fields last - if there were any exceptions, we don't want the partial results cached.128 _attributes = attributes;129 _runtimeDefinedParameterDictionary = runtimeDefinedParameterDictionary;130 }131 }132 133 private void CompileUnoptimized()134 {135 lock (this)136 {137 if (_compiledUnoptimized)138 {139 // Another thread must have compiled while we were waiting on the lock.140 return;141 }142 143 ReallyCompile(false);144 _compiledUnoptimized = true;145 }146 }147 148 private void CompileOptimized()149 {150 lock (this)151 {152 if (_compiledOptimized)153 {154 // Another thread must have compiled while we were waiting on the lock.155 return;156 }157 158 ReallyCompile(true);159 _compiledOptimized = true;160 }161 }162 163 private void ReallyCompile(bool optimize)164 {165#if LEGACYTELEMETRY166 var sw = new Stopwatch();167 sw.Start();168#endif169 bool etwEnabled = ParserEventSource.Log.IsEnabled();170 if (etwEnabled)171 {172 var extent = _ast.Body.Extent;173 var text = extent.Text;174 ParserEventSource.Log.CompileStart(175 FileName: ParserEventSource.GetFileOrScript(extent.File, text),176 text.Length,177 optimize);178 }179 180 PerformSecurityChecks();181 182 Compiler compiler = new Compiler();183 compiler.Compile(this, optimize);184 185#if LEGACYTELEMETRY186 if (!IsProductCode)187 {188 TelemetryAPI.ReportScriptTelemetry((Ast)_ast, !optimize, sw.ElapsedMilliseconds);189 }190#endif191 if (etwEnabled)192 {193 ParserEventSource.Log.CompileStop();194 }195 }196 197 private void PerformSecurityChecks()198 {199 if (Ast is not ScriptBlockAst scriptBlockAst)200 {201 // Checks are only needed at the top level.202 return;203 }204 205 var scriptExtent = scriptBlockAst.Extent;206 var scriptFile = scriptExtent.File;207 208 if (scriptFile != null209 && scriptFile.EndsWith(StringLiterals.PowerShellDataFileExtension, StringComparison.OrdinalIgnoreCase)210 && IsScriptBlockInFactASafeHashtable())211 {212 // Skip the scan for .psd1 files if their content is in fact a safe HashtableAst.213 return;214 }215 216 // Call the AMSI API to determine if the script block has malicious content217 var amsiResult = AmsiUtils.ScanContent(scriptExtent.Text, scriptFile);218 219 if (amsiResult == AmsiUtils.AmsiNativeMethods.AMSI_RESULT.AMSI_RESULT_DETECTED)220 {221 var parseError = new ParseError(222 scriptExtent,223 "ScriptContainedMaliciousContent",224 ParserStrings.ScriptContainedMaliciousContent);225 throw new ParseException(new[] { parseError });226 }227 else if (amsiResult >= AmsiUtils.AmsiNativeMethods.AMSI_RESULT.AMSI_RESULT_BLOCKED_BY_ADMIN_BEGIN228 && amsiResult <= AmsiUtils.AmsiNativeMethods.AMSI_RESULT.AMSI_RESULT_BLOCKED_BY_ADMIN_END)229 {230 // Certain policies set by an administrator blocked this content on this machine231 var parseError = new ParseError(232 scriptExtent,233 "ScriptHasAdminBlockedContent",234 StringUtil.Format(ParserStrings.ScriptHasAdminBlockedContent, amsiResult));235 throw new ParseException(new[] { parseError });236 }237 238 if (ScriptBlock.CheckSuspiciousContent(scriptBlockAst) != null)239 {240 HasSuspiciousContent = true;241 }242 243 // A local function to check if the ScriptBlockAst is in fact a safe HashtableAst.244 bool IsScriptBlockInFactASafeHashtable()245 {246 // NOTE: The code below depends on the current member structure of 'ScriptBlockAst'247 // to determine if the ScriptBlockAst is in fact just a HashtableAst. If AST types248 // are enhanced, such as new members added to 'ScriptBlockAst', the code here needs249 // to be reviewed and changed accordingly.250 251 if (scriptBlockAst.BeginBlock != null252 || scriptBlockAst.ProcessBlock != null253 || scriptBlockAst.CleanBlock != null254 || scriptBlockAst.ParamBlock != null255 || scriptBlockAst.DynamicParamBlock != null256 || scriptBlockAst.ScriptRequirements != null257 || scriptBlockAst.UsingStatements.Count > 0258 || scriptBlockAst.Attributes.Count > 0)259 {260 return false;261 }262 263 NamedBlockAst endBlock = scriptBlockAst.EndBlock;264 if (!endBlock.Unnamed || endBlock.Traps != null || endBlock.Statements.Count != 1)265 {266 return false;267 }268 269 if (endBlock.Statements[0] is not PipelineAst pipelineAst)270 {271 return false;272 }273 274 if (pipelineAst.GetPureExpression() is not HashtableAst hashtableAst)275 {276 return false;277 }278 279 // After the above steps, we know the ScriptBlockAst is in fact just a HashtableAst,280 // now we need to check if the HashtableAst is safe.281 return IsSafeValueVisitor.Default.IsAstSafe(hashtableAst);282 }283 }284 285 // We delay parsing scripts loaded on startup, so we save the text.286 private string _scriptText;287 288 internal IParameterMetadataProvider Ast { get => _ast ?? DelayParseScriptText(); }289 290 private IParameterMetadataProvider _ast;291 292 private IParameterMetadataProvider DelayParseScriptText()293 {294 lock (this)295 {296 if (_ast != null)297 {298 return _ast;299 }300 301 ParseError[] errors;302 _ast = (new Parser()).Parse(null, _scriptText, null, out errors, ParseMode.Default);303 if (errors.Length != 0)304 {305 throw new ParseException(errors);306 }307 308 _scriptText = null;309 return _ast;310 }311 }312 313 internal Type LocalsMutableTupleType { get; set; }314 315 internal Type UnoptimizedLocalsMutableTupleType { get; set; }316 317 internal Func<MutableTuple> LocalsMutableTupleCreator { get; set; }318 319 internal Func<MutableTuple> UnoptimizedLocalsMutableTupleCreator { get; set; }320 321 internal Dictionary<string, int> NameToIndexMap { get; set; }322 323 #region Named Blocks324 325 internal Action<FunctionContext> DynamicParamBlock { get; set; }326 327 internal Action<FunctionContext> UnoptimizedDynamicParamBlock { get; set; }328 329 internal Action<FunctionContext> BeginBlock { get; set; }330 331 internal Action<FunctionContext> UnoptimizedBeginBlock { get; set; }332 333 internal Action<FunctionContext> ProcessBlock { get; set; }334 335 internal Action<FunctionContext> UnoptimizedProcessBlock { get; set; }336 337 internal Action<FunctionContext> EndBlock { get; set; }338 339 internal Action<FunctionContext> UnoptimizedEndBlock { get; set; }340 341 internal Action<FunctionContext> CleanBlock { get; set; }342 343 internal Action<FunctionContext> UnoptimizedCleanBlock { get; set; }344 345 #endregion Named Blocks346 347 internal IScriptExtent[] SequencePoints { get; set; }348 349 private RuntimeDefinedParameterDictionary _runtimeDefinedParameterDictionary;350 private Attribute[] _attributes;351 private bool _usesCmdletBinding;352 private bool _compiledOptimized;353 private bool _compiledUnoptimized;354 private bool _hasSuspiciousContent;355 private bool? _isProductCode;356 357 internal bool DebuggerHidden { get; set; }358 359 internal bool DebuggerStepThrough { get; set; }360 361 internal Guid Id { get; private set; }362 363 internal bool HasLogged { get; set; }364 365 internal bool SkipLogging { get; set; }366 367 internal bool IsFilter { get; }368 369 internal bool IsProductCode370 {371 get372 {373 _isProductCode ??= SecuritySupport.IsProductBinary(((Ast)_ast).Extent.File);374 375 return _isProductCode.Value;376 }377 }378 379 internal bool GetIsConfiguration()380 {381 // Use _ast instead of Ast382 // If we access Ast, we may parse a "delay parsed" script block unnecessarily383 // if _ast is null - it can't be a configuration as there is no way to create a configuration that way384 var scriptBlockAst = _ast as ScriptBlockAst;385 return scriptBlockAst != null && scriptBlockAst.IsConfiguration;386 }387 388 internal bool HasSuspiciousContent389 {390 get391 {392 Diagnostics.Assert(393 _compiledOptimized || _compiledUnoptimized,394 "HasSuspiciousContent is not set correctly before being compiled");395 return _hasSuspiciousContent;396 }397 398 set => _hasSuspiciousContent = value;399 }400 401 private MergedCommandParameterMetadata _parameterMetadata;402 403 internal List<Attribute> GetAttributes()404 {405 if (_attributes == null)406 {407 InitializeMetadata();408 }409 410 Diagnostics.Assert(411 _attributes != null,412 "after initialization, attributes is never null, must be an empty list if no attributes.");413 return _attributes.ToList();414 }415 416 internal bool UsesCmdletBinding417 {418 get419 {420 if (_attributes != null)421 {422 return _usesCmdletBinding;423 }424 425 return Ast.UsesCmdletBinding();426 }427 }428 429 internal RuntimeDefinedParameterDictionary RuntimeDefinedParameters430 {431 get432 {433 if (_runtimeDefinedParameterDictionary == null)434 {435 InitializeMetadata();436 }437 438 return _runtimeDefinedParameterDictionary;439 }440 }441 442 internal CmdletBindingAttribute CmdletBindingAttribute443 {444 get445 {446 if (_runtimeDefinedParameterDictionary == null)447 {448 InitializeMetadata();449 }450 451 return _usesCmdletBinding452 ? (CmdletBindingAttribute)Array.Find(_attributes, static attr => attr is CmdletBindingAttribute)453 : null;454 }455 }456 457 internal ObsoleteAttribute ObsoleteAttribute458 {459 get460 {461 if (_runtimeDefinedParameterDictionary == null)462 {463 InitializeMetadata();464 }465 466 return (ObsoleteAttribute)Array.Find(_attributes, static attr => attr is ObsoleteAttribute);467 }468 }469 470 internal ExperimentalAttribute ExperimentalAttribute471 {472 get473 {474 if (_expAttribute == ExperimentalAttribute.None)475 {476 lock (this)477 {478 if (_expAttribute == ExperimentalAttribute.None)479 {480 _expAttribute = Ast.GetExperimentalAttributes().FirstOrDefault();481 }482 }483 }484 485 return _expAttribute;486 }487 }488 489 private ExperimentalAttribute _expAttribute = ExperimentalAttribute.None;490 491 public MergedCommandParameterMetadata GetParameterMetadata(ScriptBlock scriptBlock)492 {493 if (_parameterMetadata == null)494 {495 lock (this)496 {497 if (_parameterMetadata == null)498 {499 CommandMetadata metadata = new CommandMetadata(500 scriptBlock,501 string.Empty,502 LocalPipeline.GetExecutionContextFromTLS());503 _parameterMetadata = metadata.StaticCommandParameterMetadata;504 }505 }506 }507 508 return _parameterMetadata;509 }510 511 public override string ToString()512 {513 if (_scriptText != null)514 {515 return _scriptText;516 }517 518 if (_ast is ScriptBlockAst sbAst)519 {520 return sbAst.ToStringForSerialization();521 }522 523 var generatedMemberFunctionAst = _ast as CompilerGeneratedMemberFunctionAst;524 if (generatedMemberFunctionAst != null)525 {526 return generatedMemberFunctionAst.Extent.Text;527 }528 529 var funcDefn = (FunctionDefinitionAst)_ast;530 if (funcDefn.Parameters == null)531 {532 return funcDefn.Body.ToStringForSerialization();533 }534 535 var sb = new StringBuilder();536 sb.Append(funcDefn.GetParamTextFromParameterList());537 sb.Append(funcDefn.Body.ToStringForSerialization());538 return sb.ToString();539 }540 }541 542 public partial class ScriptBlock543 {544 private readonly CompiledScriptBlockData _scriptBlockData;545 546 internal ScriptBlock(IParameterMetadataProvider ast, bool isFilter)547 : this(new CompiledScriptBlockData(ast, isFilter))548 {549 }550 551 private ScriptBlock(CompiledScriptBlockData scriptBlockData)552 {553 _scriptBlockData = scriptBlockData;554 555 // LanguageMode is a nullable PSLanguageMode enumeration because script blocks556 // need to inherit the language mode from the context in which they are executing.557 // We can't assume FullLanguage by default when there is no context, as there are558 // script blocks (such as the script blocks used in Workflow activities) that are559 // created by the host without a "current language mode" to inherit. They ultimately560 // get their language mode set when they are finally invoked in a constrained561 // language runspace.562 // Script blocks that should always be run under FullLanguage mode (i.e.: set in563 // InitialSessionState, etc.) should explicitly set the LanguageMode to FullLanguage564 // when they are created.565 ExecutionContext context = LocalPipeline.GetExecutionContextFromTLS();566 if (context != null)567 {568 this.LanguageMode = context.LanguageMode;569 }570 }571 572 /// <summary>573 /// Protected constructor to support ISerializable.574 /// </summary>575 [Obsolete("Legacy serialization support is deprecated since .NET 8", DiagnosticId = "SYSLIB0051")]576 protected ScriptBlock(SerializationInfo info, StreamingContext context)577 {578 }579 580 private static readonly ConcurrentDictionary<Tuple<string, string>, ScriptBlock> s_cachedScripts =581 new ConcurrentDictionary<Tuple<string, string>, ScriptBlock>();582 583 internal static ScriptBlock TryGetCachedScriptBlock(string fileName, string fileContents)584 {585 if (InternalTestHooks.IgnoreScriptBlockCache)586 {587 return null;588 }589 590 ScriptBlock scriptBlock;591 var key = Tuple.Create(fileName, fileContents);592 if (s_cachedScripts.TryGetValue(key, out scriptBlock))593 {594 Diagnostics.Assert(595 scriptBlock.SessionStateInternal == null,596 "A cached scriptblock should not have it's session state bound, that causes a memory leak.");597 return scriptBlock.Clone();598 }599 600 return null;601 }602 603 private static bool IsDynamicKeyword(Ast ast)604 => ast is CommandAst cmdAst && cmdAst.DefiningKeyword != null;605 606 private static bool IsUsingTypes(Ast ast)607 => ast is UsingStatementAst cmdAst && cmdAst.IsUsingModuleOrAssembly();608 609 internal static void CacheScriptBlock(ScriptBlock scriptBlock, string fileName, string fileContents)610 {611 if (InternalTestHooks.IgnoreScriptBlockCache)612 {613 return;614 }615 616 // Don't cache scriptblocks that have617 // a) dynamic keywords618 // b) 'using module' or 'using assembly'619 // The definition of the dynamic keyword could change, consequently changing how the source text should be parsed.620 // Exported types definitions from 'using module' could change, we need to do all parse-time checks again.621 // TODO(sevoroby): we can optimize it to ignore 'using' if there are no actual type usage in locally defined types.622 623 // using is always a top-level statements in scriptBlock, we don't need to search in child blocks.624 if (scriptBlock.Ast.Find(static ast => IsUsingTypes(ast), false) != null625 || scriptBlock.Ast.Find(static ast => IsDynamicKeyword(ast), true) != null)626 {627 return;628 }629 630 if (s_cachedScripts.Count > 1024)631 {632 s_cachedScripts.Clear();633 }634 635 var key = Tuple.Create(fileName, fileContents);636 s_cachedScripts.TryAdd(key, scriptBlock);637 }638 639 /// <summary>640 /// Clears the cached scriptblocks.641 /// </summary>642 internal static void ClearScriptBlockCache()643 {644 s_cachedScripts.Clear();645 }646 647 internal static readonly ScriptBlock EmptyScriptBlock =648 ScriptBlock.CreateDelayParsedScriptBlock(string.Empty, isProductCode: true);649 650 internal static ScriptBlock Create(Parser parser, string fileName, string fileContents)651 {652 var scriptBlock = TryGetCachedScriptBlock(fileName, fileContents);653 if (scriptBlock != null)654 {655 return scriptBlock;656 }657 658 var ast = parser.Parse(fileName, fileContents, null, out ParseError[] errors, ParseMode.Default);659 if (errors.Length != 0)660 {661 throw new ParseException(errors);662 }663 664 var result = new ScriptBlock(ast, isFilter: false);665 CacheScriptBlock(result, fileName, fileContents);666 667 // The value returned will potentially be bound to a session state. We don't want668 // the cached script block to end up being bound to any session state, so clone669 // the return value to ensure the cached value has no session state.670 return result.Clone();671 }672 673 internal ScriptBlock Clone() => new ScriptBlock(_scriptBlockData);674 675 /// <summary>676 /// Returns the text of the script block. The return value might not match the original text exactly.677 /// </summary>678 public override string ToString() => _scriptBlockData.ToString();679 680 /// <summary>681 /// Returns the text of the script block with the handling of $using expressions.682 /// </summary>683 internal string ToStringWithDollarUsingHandling(684 Tuple<List<VariableExpressionAst>, string> usingVariablesTuple)685 {686 FunctionDefinitionAst funcDefn = null;687 var sbAst = Ast as ScriptBlockAst;688 if (sbAst == null)689 {690 funcDefn = (FunctionDefinitionAst)Ast;691 sbAst = funcDefn.Body;692 }693 694 string sbText = sbAst.ToStringForSerialization(usingVariablesTuple, sbAst.Extent.StartOffset, sbAst.Extent.EndOffset);695 if (sbAst.ParamBlock != null)696 {697 return sbText;698 }699 700 string paramText;701 string additionalNewParams = usingVariablesTuple.Item2;702 if (funcDefn == null || funcDefn.Parameters == null)703 {704 paramText = "param(" + additionalNewParams + ")" + Environment.NewLine;705 }706 else707 {708 paramText = funcDefn.GetParamTextFromParameterList(usingVariablesTuple);709 }710 711 sbText = paramText + sbText;712 return sbText;713 }714 715 internal PowerShell GetPowerShellImpl(716 ExecutionContext context,717 Dictionary<string, object> variables,718 bool isTrustedInput,719 bool filterNonUsingVariables,720 bool? createLocalScope,721 params object[] args)722 {723 return AstInternal.GetPowerShell(724 context,725 variables,726 isTrustedInput,727 filterNonUsingVariables,728 createLocalScope,729 args);730 }731 732 internal SteppablePipeline GetSteppablePipelineImpl(CommandOrigin commandOrigin, object[] args)733 {734 var pipelineAst = GetSimplePipeline(735 resourceString => throw PSTraceSource.NewInvalidOperationException(resourceString));736 Diagnostics.Assert(pipelineAst != null, "This should be checked by GetSimplePipeline");737 738 if (pipelineAst.PipelineElements[0] is not CommandAst)739 {740 throw PSTraceSource.NewInvalidOperationException(AutomationExceptions.CantConvertEmptyPipeline);741 }742 743 return PipelineOps.GetSteppablePipeline(pipelineAst, commandOrigin, this, args);744 }745 746 private PipelineAst GetSimplePipeline(Func<string, PipelineAst> errorHandler)747 {748 errorHandler ??= (static _ => null);749 750 if (HasBeginBlock || HasProcessBlock || HasCleanBlock)751 {752 return errorHandler(AutomationExceptions.CanConvertOneClauseOnly);753 }754 755 var ast = AstInternal;756 var statements = ast.Body.EndBlock.Statements;757 if (statements.Count == 0)758 {759 return errorHandler(AutomationExceptions.CantConvertEmptyPipeline);760 }761 762 if (statements.Count > 1)763 {764 return errorHandler(AutomationExceptions.CanOnlyConvertOnePipeline);765 }766 767 if (ast.Body.EndBlock.Traps != null && ast.Body.EndBlock.Traps.Count > 0)768 {769 return errorHandler(AutomationExceptions.CantConvertScriptBlockWithTrap);770 }771 772 if (statements[0] is not PipelineAst pipeAst)773 {774 return errorHandler(AutomationExceptions.CanOnlyConvertOnePipeline);775 }776 777 // The old code checked for empty pipeline.778 // That can't happen in the new parser (validated in the constructors),779 // so the resource CantConvertEmptyPipeline is probably unused.780 781 return pipeAst;782 }783 784 internal List<Attribute> GetAttributes() => _scriptBlockData.GetAttributes();785 786 internal string GetFileName() => AstInternal.Body.Extent.File;787 788 // GetAttributes() is asserted never return null789 internal bool IsMetaConfiguration() => GetAttributes().OfType<DscLocalConfigurationManagerAttribute>().Any();790 791 internal PSToken GetStartPosition() => new PSToken(Ast.Extent);792 793 internal MergedCommandParameterMetadata ParameterMetadata794 {795 get => _scriptBlockData.GetParameterMetadata(this);796 }797 798 internal bool UsesCmdletBinding { get => _scriptBlockData.UsesCmdletBinding; }799 800 internal bool HasDynamicParameters { get => AstInternal.Body.DynamicParamBlock != null; }801 802 /// <summary>803 /// DebuggerHidden.804 /// </summary>805 public bool DebuggerHidden806 {807 get => _scriptBlockData.DebuggerHidden;808 set => _scriptBlockData.DebuggerHidden = value;809 }810 811 /// <summary>812 /// The unique ID of this script block.813 /// </summary>814 public Guid Id { get => _scriptBlockData.Id; }815 816 internal bool DebuggerStepThrough817 {818 get { return _scriptBlockData.DebuggerStepThrough; }819 820 set { _scriptBlockData.DebuggerStepThrough = value; }821 }822 823 internal RuntimeDefinedParameterDictionary RuntimeDefinedParameters824 {825 get => _scriptBlockData.RuntimeDefinedParameters;826 }827 828 internal bool HasLogged829 {830 get => _scriptBlockData.HasLogged;831 set => _scriptBlockData.HasLogged = value;832 }833 834 internal bool SkipLogging835 {836 get { return _scriptBlockData.SkipLogging; }837 838 set { _scriptBlockData.SkipLogging = value; }839 }840 841 internal Assembly AssemblyDefiningPSTypes { get; set; }842 843 internal HelpInfo GetHelpInfo(844 ExecutionContext context,845 CommandInfo commandInfo,846 bool dontSearchOnRemoteComputer,847 Dictionary<Ast, Token[]> scriptBlockTokenCache,848 out string helpFile,849 out string helpUriFromDotLink)850 {851 helpUriFromDotLink = null;852 853 var commentTokens = HelpCommentsParser.GetHelpCommentTokens(AstInternal, scriptBlockTokenCache);854 if (commentTokens != null)855 {856 return HelpCommentsParser.CreateFromComments(857 context,858 commandInfo,859 commentTokens.Item1,860 commentTokens.Item2,861 dontSearchOnRemoteComputer,862 out helpFile,863 out helpUriFromDotLink);864 }865 866 helpFile = null;867 return null;868 }869 870 /// <summary>871 /// Check the script block to see if it uses any language constructs not allowed in restricted language mode.872 /// </summary>873 /// <param name="allowedCommands">The commands that are allowed.</param>874 /// <param name="allowedVariables">875 /// The variables allowed in this scriptblock. If this is null, then the default variable set876 /// will be allowed. If it is an empty list, no variables will be allowed. If it is "*" then877 /// any variable will be allowed.878 /// </param>879 /// <param name="allowEnvironmentVariables">The environment variables that are allowed.</param>880 public void CheckRestrictedLanguage(881 IEnumerable<string> allowedCommands,882 IEnumerable<string> allowedVariables,883 bool allowEnvironmentVariables)884 {885 Parser parser = new Parser();886 887 var ast = AstInternal;888 if (HasBeginBlock889 || HasProcessBlock890 || HasCleanBlock891 || ast.Body.ParamBlock is not null)892 {893 Ast errorAst = ast.Body.BeginBlock ?? (Ast)ast.Body.ProcessBlock ?? ast.Body.ParamBlock;894 parser.ReportError(895 errorAst.Extent,896 nameof(ParserStrings.InvalidScriptBlockInDataSection),897 ParserStrings.InvalidScriptBlockInDataSection);898 }899 900 if (HasEndBlock)901 {902 var rlc = new RestrictedLanguageChecker(903 parser,904 allowedCommands,905 allowedVariables,906 allowEnvironmentVariables);907 908 StatementBlockAst.InternalVisit(909 rlc,910 ast.Body.EndBlock.Traps,911 ast.Body.EndBlock.Statements,912 AstVisitAction.Continue);913 }914 915 if (parser.ErrorList.Count > 0)916 {917 throw new ParseException(parser.ErrorList.ToArray());918 }919 }920 921 internal string GetWithInputHandlingForInvokeCommand() => AstInternal.GetWithInputHandlingForInvokeCommand();922 923 internal string GetWithInputHandlingForInvokeCommandWithUsingExpression(924 Tuple<List<VariableExpressionAst>, string> usingVariablesTuple)925 {926 Tuple<string, string> result =927 AstInternal.GetWithInputHandlingForInvokeCommandWithUsingExpression(usingVariablesTuple);928 929 // result.Item1 is ParamText; result.Item2 is ScriptBlockText930 return result.Item1 == null ? result.Item2 : result.Item1 + result.Item2;931 }932 933 internal bool IsUsingDollarInput() => AstSearcher.IsUsingDollarInput(this.Ast);934 935 internal void InvokeWithPipeImpl(936 bool createLocalScope,937 Dictionary<string, ScriptBlock> functionsToDefine,938 List<PSVariable> variablesToDefine,939 ErrorHandlingBehavior errorHandlingBehavior,940 object dollarUnder,941 object input,942 object scriptThis,943 Pipe outputPipe,944 InvocationInfo invocationInfo,945 params object[] args)946 {947 InvokeWithPipeImpl(948 ScriptBlockClauseToInvoke.ProcessBlockOnly,949 createLocalScope,950 functionsToDefine,951 variablesToDefine,952 errorHandlingBehavior,953 dollarUnder,954 input,955 scriptThis,956 outputPipe,957 invocationInfo,958 args);959 }960 961 internal void InvokeWithPipeImpl(962 ScriptBlockClauseToInvoke clauseToInvoke,963 bool createLocalScope,964 Dictionary<string, ScriptBlock> functionsToDefine,965 List<PSVariable> variablesToDefine,966 ErrorHandlingBehavior errorHandlingBehavior,967 object dollarUnder,968 object input,969 object scriptThis,970 Pipe outputPipe,971 InvocationInfo invocationInfo,972 params object[] args)973 {974 if (clauseToInvoke == ScriptBlockClauseToInvoke.Clean)975 {976 throw new PSNotSupportedException(ParserStrings.InvokingCleanBlockNotSupported);977 }978 979 if ((clauseToInvoke == ScriptBlockClauseToInvoke.Begin && !HasBeginBlock)980 || (clauseToInvoke == ScriptBlockClauseToInvoke.Process && !HasProcessBlock)981 || (clauseToInvoke == ScriptBlockClauseToInvoke.End && !HasEndBlock))982 {983 return;984 }985 986 ExecutionContext context = GetContextFromTLS();987 Diagnostics.Assert(988 SessionStateInternal == null || SessionStateInternal.ExecutionContext == context,989 "The scriptblock is being invoked in a runspace different than the one where it was created");990 991 if (context.CurrentPipelineStopping)992 {993 throw new PipelineStoppedException();994 }995 996 // Validate that the arguments are consistent. The only public API that gets you here never sets createLocalScope to false...997 Diagnostics.Assert(998 createLocalScope || functionsToDefine == null,999 "When calling ScriptBlock.InvokeWithContext(), if 'functionsToDefine' != null then 'createLocalScope' must be true");1000 Diagnostics.Assert(1001 createLocalScope || variablesToDefine == null,1002 "When calling ScriptBlock.InvokeWithContext(), if 'variablesToDefine' != null then 'createLocalScope' must be true");1003 1004 args ??= Array.Empty<object>();1005 1006 bool runOptimized = context._debuggingMode <= 0 && createLocalScope;1007 var codeToInvoke = GetCodeToInvoke(ref runOptimized, clauseToInvoke);1008 if (codeToInvoke == null)1009 {1010 return;1011 }1012 1013 // If we don't have a pipe to write to, we need to discard all results.1014 outputPipe ??= new Pipe { NullPipe = true };1015 1016 var locals = MakeLocalsTuple(runOptimized);1017 1018 if (dollarUnder != AutomationNull.Value)1019 {1020 locals.SetAutomaticVariable(AutomaticVariable.Underbar, dollarUnder, context);1021 }1022 1023 if (input != AutomationNull.Value)1024 {1025 locals.SetAutomaticVariable(AutomaticVariable.Input, input, context);1026 }1027 1028 if (scriptThis != AutomationNull.Value)1029 {1030 locals.SetAutomaticVariable(AutomaticVariable.This, scriptThis, context);1031 }1032 1033 SetPSScriptRootAndPSCommandPath(locals, context);1034 1035 var oldShellFunctionErrorOutputPipe = context.ShellFunctionErrorOutputPipe;1036 var oldExternalErrorOutput = context.ExternalErrorOutput;1037 var oldScopeOrigin = context.EngineSessionState.CurrentScope.ScopeOrigin;1038 var oldSessionState = context.EngineSessionState;1039 1040 // If the script block has a different language mode than the current context,1041 // change the language mode.1042 PSLanguageMode? oldLanguageMode = null;1043 PSLanguageMode? newLanguageMode = null;1044 if (this.LanguageMode.HasValue && this.LanguageMode != context.LanguageMode)1045 {1046 // Don't allow context: ConstrainedLanguage -> FullLanguage transition if1047 // this is dot sourcing into the current scope, unless it is within a trusted module scope.1048 if (this.LanguageMode != PSLanguageMode.FullLanguage1049 || createLocalScope1050 || context.EngineSessionState.Module?.LanguageMode == PSLanguageMode.FullLanguage)1051 {1052 oldLanguageMode = context.LanguageMode;1053 newLanguageMode = this.LanguageMode;1054 }1055 else if (SystemPolicy.GetSystemLockdownPolicy() == SystemEnforcementMode.Audit)1056 {1057 string scriptBlockId = this.GetFileName() ?? string.Empty;1058 SystemPolicy.LogWDACAuditMessage(1059 context: context,1060 title: AutomationExceptions.WDACCompiledScriptBlockLogTitle,1061 message: StringUtil.Format(AutomationExceptions.WDACCompiledScriptBlockLogMessage, scriptBlockId, this.LanguageMode, context.LanguageMode),1062 fqid: "ScriptBlockDotSourceNotAllowed",1063 dropIntoDebugger: true);1064 1065 // Since we are in audit mode, go ahead and allow the language transition.1066 oldLanguageMode = context.LanguageMode;1067 newLanguageMode = this.LanguageMode;1068 }1069 }1070 1071 Dictionary<string, PSVariable> backupWhenDotting = null;1072 try1073 {1074 var myInvocationInfo = invocationInfo;1075 if (myInvocationInfo == null)1076 {1077 var callerFrame = context.Debugger.GetCallStack().LastOrDefault();1078 var extent = (callerFrame != null)1079 ? callerFrame.FunctionContext.CurrentPosition1080 : Ast.Extent;1081 myInvocationInfo = new InvocationInfo(null, extent, context);1082 }1083 1084 locals.SetAutomaticVariable(AutomaticVariable.MyInvocation, myInvocationInfo, context);1085 1086 if (SessionStateInternal != null)1087 {1088 context.EngineSessionState = SessionStateInternal;1089 }1090 1091 // If we don't want errors written, hide the error pipe.1092 switch (errorHandlingBehavior)1093 {1094 case ErrorHandlingBehavior.WriteToCurrentErrorPipe:1095 // no need to do anything1096 break;1097 case ErrorHandlingBehavior.WriteToExternalErrorPipe:1098 context.ShellFunctionErrorOutputPipe = null;1099 break;1100 case ErrorHandlingBehavior.SwallowErrors:1101 context.ShellFunctionErrorOutputPipe = null;1102 context.ExternalErrorOutput = new DiscardingPipelineWriter();1103 break;1104 }1105 1106 if (createLocalScope)1107 {1108 var newScope = context.EngineSessionState.NewScope(false);1109 context.EngineSessionState.CurrentScope = newScope;1110 newScope.LocalsTuple = locals;1111 // Inject passed in functions into the scope1112 if (functionsToDefine != null)1113 {1114 foreach (var def in functionsToDefine)1115 {1116 if (string.IsNullOrWhiteSpace(def.Key))1117 {1118 PSInvalidOperationException e = PSTraceSource.NewInvalidOperationException(1119 ParserStrings.EmptyFunctionNameInFunctionDefinitionDictionary);1120 1121 e.SetErrorId("EmptyFunctionNameInFunctionDefinitionDictionary");1122 throw e;1123 }1124 1125 if (def.Value == null)1126 {1127 PSInvalidOperationException e = PSTraceSource.NewInvalidOperationException(1128 ParserStrings.NullFunctionBodyInFunctionDefinitionDictionary, def.Key);1129 1130 e.SetErrorId("NullFunctionBodyInFunctionDefinitionDictionary");1131 throw e;1132 }1133 1134 newScope.FunctionTable.Add(def.Key, new FunctionInfo(def.Key, def.Value, context));1135 }1136 }1137 // Inject passed in variables into the scope1138 if (variablesToDefine != null)1139 {1140 int index = 0;1141 foreach (var psvar in variablesToDefine)1142 {1143 // Check for null entries.1144 if (psvar == null)1145 {1146 PSInvalidOperationException e = PSTraceSource.NewInvalidOperationException(1147 ParserStrings.NullEntryInVariablesDefinitionList, index);1148 1149 e.SetErrorId("NullEntryInVariablesDefinitionList");1150 throw e;1151 }1152 1153 string name = psvar.Name;1154 Diagnostics.Assert(1155 !(string.Equals(name, "this") || string.Equals(name, "_") || string.Equals(name, "input")),1156 "The list of variables to set in the scriptblock's scope cannot contain 'this', '_' or 'input'. These variables should be removed before passing the collection to this routine.");1157 index++;1158 newScope.Variables.Add(name, psvar);1159 }1160 }1161 }1162 else1163 {1164 if (context.EngineSessionState.CurrentScope.LocalsTuple == null)1165 {1166 // If the locals tuple is null, that means either:1167 // * we're invoking a script block for a module1168 // * something unexpected1169 context.EngineSessionState.CurrentScope.LocalsTuple = locals;1170 }1171 else1172 {1173 context.EngineSessionState.CurrentScope.DottedScopes.Push(locals);1174 backupWhenDotting = new Dictionary<string, PSVariable>();1175 }1176 }1177 1178 // Set the language mode1179 if (newLanguageMode.HasValue)1180 {1181 context.LanguageMode = newLanguageMode.Value;1182 }1183 1184 args = BindArgumentsForScriptblockInvoke(1185 (RuntimeDefinedParameter[])RuntimeDefinedParameters.Data,1186 args,1187 context,1188 !createLocalScope,1189 backupWhenDotting,1190 locals);1191 locals.SetAutomaticVariable(AutomaticVariable.Args, args, context);1192 1193 context.EngineSessionState.CurrentScope.ScopeOrigin = CommandOrigin.Internal;1194 1195 var functionContext = new FunctionContext1196 {1197 _executionContext = context,1198 _outputPipe = outputPipe,1199 _localsTuple = locals,1200 _scriptBlock = this,