Permalink
Branch: master
Find file Copy path
Fetching contributors…
Cannot retrieve contributors at this time
2339 lines (2006 sloc) 96.2 KB
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
using System.Collections;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Management.Automation.Configuration;
using System.Management.Automation.Internal;
using System.Management.Automation.Language;
using System.Management.Automation.Runspaces;
using System.Management.Automation.Tracing;
using System.Security.Cryptography.X509Certificates;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Serialization;
using System.Text;
using System.Threading.Tasks;
#if LEGACYTELEMETRY
using Microsoft.PowerShell.Telemetry.Internal;
#endif
namespace System.Management.Automation
{
internal enum CompileInterpretChoice
{
NeverCompile,
AlwaysCompile,
CompileOnDemand
}
internal enum ScriptBlockClauseToInvoke
{
Begin,
Process,
End,
ProcessBlockOnly,
}
internal class CompiledScriptBlockData
{
internal CompiledScriptBlockData(IParameterMetadataProvider ast, bool isFilter)
{
_ast = ast;
this.IsFilter = isFilter;
this.Id = Guid.NewGuid();
}
internal CompiledScriptBlockData(string scriptText, bool isProductCode)
{
_isProductCode = isProductCode;
_scriptText = scriptText;
this.Id = Guid.NewGuid();
}
internal bool Compile(bool optimized)
{
if (_attributes == null)
{
InitializeMetadata();
}
// We need the name to index map to check if any allscope variables are assigned. If they
// are, we can't run the optimized version, so we'll compile once more unoptimized and run that.
if (optimized && NameToIndexMap == null)
{
CompileOptimized();
}
optimized = optimized && !VariableAnalysis.AnyVariablesCouldBeAllScope(NameToIndexMap);
if (!optimized && !_compiledUnoptimized)
{
CompileUnoptimized();
}
else if (optimized && !_compiledOptimized)
{
CompileOptimized();
}
return optimized;
}
private void InitializeMetadata()
{
lock (this)
{
if (_attributes != null)
{
// Another thread must have initialized the metadata.
return;
}
Attribute[] attributes;
CmdletBindingAttribute cmdletBindingAttribute = null;
if (!Ast.HasAnyScriptBlockAttributes())
{
attributes = Utils.EmptyArray<Attribute>();
}
else
{
attributes = Ast.GetScriptBlockAttributes().ToArray();
foreach (var attribute in attributes)
{
if (attribute is CmdletBindingAttribute)
{
cmdletBindingAttribute = cmdletBindingAttribute ?? (CmdletBindingAttribute)attribute;
}
else if (attribute is DebuggerHiddenAttribute)
{
DebuggerHidden = true;
}
else if (attribute is DebuggerStepThroughAttribute || attribute is DebuggerNonUserCodeAttribute)
{
DebuggerStepThrough = true;
}
}
_usesCmdletBinding = cmdletBindingAttribute != null;
}
bool automaticPosition = cmdletBindingAttribute == null || cmdletBindingAttribute.PositionalBinding;
var runtimeDefinedParameterDictionary = Ast.GetParameterMetadata(automaticPosition, ref _usesCmdletBinding);
// Initialize these fields last - if there were any exceptions, we don't want the partial results cached.
_attributes = attributes;
_runtimeDefinedParameterDictionary = runtimeDefinedParameterDictionary;
}
}
private void CompileUnoptimized()
{
lock (this)
{
if (_compiledUnoptimized)
{
// Another thread must have compiled while we were waiting on the lock.
return;
}
ReallyCompile(false);
_compiledUnoptimized = true;
}
}
private void CompileOptimized()
{
lock (this)
{
if (_compiledOptimized)
{
// Another thread must have compiled while we were waiting on the lock.
return;
}
ReallyCompile(true);
_compiledOptimized = true;
}
}
private void ReallyCompile(bool optimize)
{
#if LEGACYTELEMETRY
var sw = new Stopwatch();
sw.Start();
#endif
bool etwEnabled = ParserEventSource.Log.IsEnabled();
if (etwEnabled)
{
var extent = _ast.Body.Extent;
var text = extent.Text;
ParserEventSource.Log.CompileStart(ParserEventSource.GetFileOrScript(extent.File, text), text.Length, optimize);
}
PerformSecurityChecks();
Compiler compiler = new Compiler();
compiler.Compile(this, optimize);
#if LEGACYTELEMETRY
if (!IsProductCode)
{
TelemetryAPI.ReportScriptTelemetry((Ast)_ast, !optimize, sw.ElapsedMilliseconds);
}
#endif
if (etwEnabled) ParserEventSource.Log.CompileStop();
}
private void PerformSecurityChecks()
{
var scriptBlockAst = Ast as ScriptBlockAst;
if (scriptBlockAst == null)
{
// Checks are only needed at the top level.
return;
}
var scriptExtent = scriptBlockAst.Extent;
var scriptFile = scriptExtent.File;
if (scriptFile != null &&
scriptFile.EndsWith(StringLiterals.PowerShellDataFileExtension, StringComparison.OrdinalIgnoreCase)
&& IsScriptBlockInFactASafeHashtable())
{
// Skip the scan for .psd1 files if their content is in fact a safe HashtableAst.
return;
}
// Call the AMSI API to determine if the script block has malicious content
var amsiResult = AmsiUtils.ScanContent(scriptExtent.Text, scriptFile);
if (amsiResult == AmsiUtils.AmsiNativeMethods.AMSI_RESULT.AMSI_RESULT_DETECTED)
{
var parseError = new ParseError(scriptExtent, "ScriptContainedMaliciousContent", ParserStrings.ScriptContainedMaliciousContent);
throw new ParseException(new[] { parseError });
}
else if ((amsiResult >= AmsiUtils.AmsiNativeMethods.AMSI_RESULT.AMSI_RESULT_BLOCKED_BY_ADMIN_BEGIN) &&
(amsiResult <= AmsiUtils.AmsiNativeMethods.AMSI_RESULT.AMSI_RESULT_BLOCKED_BY_ADMIN_END))
{
// Certain policies set by an administrator blocked this content on this machine
var parseError = new ParseError(scriptExtent, "ScriptHasAdminBlockedContent",
StringUtil.Format(ParserStrings.ScriptHasAdminBlockedContent, amsiResult));
throw new ParseException(new[] { parseError });
}
if (ScriptBlock.CheckSuspiciousContent(scriptBlockAst) != null)
{
HasSuspiciousContent = true;
}
// A local function to check if the ScriptBlockAst is in fact a safe HashtableAst.
bool IsScriptBlockInFactASafeHashtable()
{
// NOTE: The code below depends on the current member structure of 'ScriptBlockAst'
// to determine if the ScriptBlockAst is in fact just a HashtableAst. If AST types
// are enhanced, such as new members added to 'ScriptBlockAst', the code here needs
// to be reviewed and changed accordingly.
if (scriptBlockAst.BeginBlock != null || scriptBlockAst.ProcessBlock != null ||
scriptBlockAst.ParamBlock != null || scriptBlockAst.DynamicParamBlock != null ||
scriptBlockAst.ScriptRequirements != null || scriptBlockAst.UsingStatements.Count > 0 ||
scriptBlockAst.Attributes.Count > 0)
{
return false;
}
NamedBlockAst endBlock = scriptBlockAst.EndBlock;
if (!endBlock.Unnamed || endBlock.Traps != null || endBlock.Statements.Count != 1)
{
return false;
}
PipelineAst pipelineAst = endBlock.Statements[0] as PipelineAst;
if (pipelineAst == null)
{
return false;
}
HashtableAst hashtableAst = pipelineAst.GetPureExpression() as HashtableAst;
if (hashtableAst == null)
{
return false;
}
// After the above steps, we know the ScriptBlockAst is in fact just a HashtableAst,
// now we need to check if the HashtableAst is safe.
return IsSafeValueVisitor.Default.IsAstSafe(hashtableAst);
}
}
// We delay parsing scripts loaded on startup, so we save the text.
private string _scriptText;
internal IParameterMetadataProvider Ast { get { return _ast ?? DelayParseScriptText(); } }
private IParameterMetadataProvider _ast;
private IParameterMetadataProvider DelayParseScriptText()
{
lock (this)
{
if (_ast != null)
return _ast;
ParseError[] errors;
_ast = (new Parser()).Parse(null, _scriptText, null, out errors, ParseMode.Default);
if (errors.Length != 0)
{
throw new ParseException(errors);
}
_scriptText = null;
return _ast;
}
}
internal Type LocalsMutableTupleType { get; set; }
internal Type UnoptimizedLocalsMutableTupleType { get; set; }
internal Func<MutableTuple> LocalsMutableTupleCreator { get; set; }
internal Func<MutableTuple> UnoptimizedLocalsMutableTupleCreator { get; set; }
internal Dictionary<string, int> NameToIndexMap { get; set; }
internal Action<FunctionContext> DynamicParamBlock { get; set; }
internal Action<FunctionContext> UnoptimizedDynamicParamBlock { get; set; }
internal Action<FunctionContext> BeginBlock { get; set; }
internal Action<FunctionContext> UnoptimizedBeginBlock { get; set; }
internal Action<FunctionContext> ProcessBlock { get; set; }
internal Action<FunctionContext> UnoptimizedProcessBlock { get; set; }
internal Action<FunctionContext> EndBlock { get; set; }
internal Action<FunctionContext> UnoptimizedEndBlock { get; set; }
internal IScriptExtent[] SequencePoints { get; set; }
private RuntimeDefinedParameterDictionary _runtimeDefinedParameterDictionary;
private Attribute[] _attributes;
private bool _usesCmdletBinding;
private bool _compiledOptimized;
private bool _compiledUnoptimized;
private bool _hasSuspiciousContent;
private bool? _isProductCode;
internal bool DebuggerHidden { get; set; }
internal bool DebuggerStepThrough { get; set; }
internal Guid Id { get; private set; }
internal bool HasLogged { get; set; }
internal bool IsFilter { get; private set; }
internal bool IsProductCode
{
get
{
if (_isProductCode == null)
{
_isProductCode = SecuritySupport.IsProductBinary(((Ast)_ast).Extent.File);
}
return _isProductCode.Value;
}
}
internal bool GetIsConfiguration()
{
// Use _ast instead of Ast
// If we access Ast, we may parse a "delay parsed" script block unnecessarily
// if _ast is null - it can't be a configuration as there is no way to create a configuration that way
var scriptBlockAst = _ast as ScriptBlockAst;
return scriptBlockAst != null && scriptBlockAst.IsConfiguration;
}
internal bool HasSuspiciousContent
{
get
{
Diagnostics.Assert(_compiledOptimized || _compiledUnoptimized, "HasSuspiciousContent is not set correctly before being compiled");
return _hasSuspiciousContent;
}
set { _hasSuspiciousContent = value; }
}
private MergedCommandParameterMetadata _parameterMetadata;
internal List<Attribute> GetAttributes()
{
if (_attributes == null)
{
InitializeMetadata();
}
Diagnostics.Assert(_attributes != null, "after initialization, attributes is never null, must be an empty list if no attributes.");
return _attributes.ToList();
}
internal bool UsesCmdletBinding
{
get
{
if (_attributes != null)
{
return _usesCmdletBinding;
}
return Ast.UsesCmdletBinding();
}
}
internal RuntimeDefinedParameterDictionary RuntimeDefinedParameters
{
get
{
if (_runtimeDefinedParameterDictionary == null)
{
InitializeMetadata();
}
return _runtimeDefinedParameterDictionary;
}
}
internal CmdletBindingAttribute CmdletBindingAttribute
{
get
{
if (_runtimeDefinedParameterDictionary == null)
{
InitializeMetadata();
}
return _usesCmdletBinding ? (CmdletBindingAttribute)_attributes.FirstOrDefault(attr => attr is CmdletBindingAttribute) : null;
}
}
internal ObsoleteAttribute ObsoleteAttribute
{
get
{
if (_runtimeDefinedParameterDictionary == null)
{
InitializeMetadata();
}
return (ObsoleteAttribute)_attributes.FirstOrDefault(attr => attr is ObsoleteAttribute);
}
}
internal ExperimentalAttribute ExperimentalAttribute
{
get
{
if (_expAttribute == ExperimentalAttribute.None)
{
lock (this)
{
if (_expAttribute == ExperimentalAttribute.None)
{
_expAttribute = Ast.GetExperimentalAttributes().FirstOrDefault();
}
}
}
return _expAttribute;
}
}
private ExperimentalAttribute _expAttribute = ExperimentalAttribute.None;
public MergedCommandParameterMetadata GetParameterMetadata(ScriptBlock scriptBlock)
{
if (_parameterMetadata == null)
{
lock (this)
{
if (_parameterMetadata == null)
{
CommandMetadata metadata = new CommandMetadata(scriptBlock, string.Empty, LocalPipeline.GetExecutionContextFromTLS());
_parameterMetadata = metadata.StaticCommandParameterMetadata;
}
}
}
return _parameterMetadata;
}
public override string ToString()
{
if (_scriptText != null)
{
return _scriptText;
}
var sbAst = _ast as ScriptBlockAst;
if (sbAst != null)
{
return sbAst.ToStringForSerialization();
}
var generatedMemberFunctionAst = _ast as CompilerGeneratedMemberFunctionAst;
if (generatedMemberFunctionAst != null)
{
return generatedMemberFunctionAst.Extent.Text;
}
var funcDefn = (FunctionDefinitionAst)_ast;
if (funcDefn.Parameters == null)
{
return funcDefn.Body.ToStringForSerialization();
}
var sb = new StringBuilder();
sb.Append(funcDefn.GetParamTextFromParameterList());
sb.Append(funcDefn.Body.ToStringForSerialization());
return sb.ToString();
}
}
[Serializable]
public partial class ScriptBlock : ISerializable
{
private readonly CompiledScriptBlockData _scriptBlockData;
internal ScriptBlock(IParameterMetadataProvider ast, bool isFilter) :
this(new CompiledScriptBlockData(ast, isFilter))
{
}
private ScriptBlock(CompiledScriptBlockData scriptBlockData)
{
_scriptBlockData = scriptBlockData;
// LanguageMode is a nullable PSLanguageMode enumeration because script blocks
// need to inherit the language mode from the context in which they are executing.
// We can't assume FullLanguage by default when there is no context, as there are
// script blocks (such as the script blocks used in Workflow activities) that are
// created by the host without a "current language mode" to inherit. They ultimately
// get their language mode set when they are finally invoked in a constrained
// language runspace.
// Script blocks that should always be run under FullLanguage mode (i.e.: set in
// InitialSessionState, etc.) should explicitly set the LanguageMode to FullLanguage
// when they are created.
ExecutionContext context = LocalPipeline.GetExecutionContextFromTLS();
if (context != null)
{
this.LanguageMode = context.LanguageMode;
}
}
/// <summary>
/// Protected constructor to support ISerializable.
/// </summary>
protected ScriptBlock(SerializationInfo info, StreamingContext context)
{
}
private static readonly ConcurrentDictionary<Tuple<string, string>, ScriptBlock> s_cachedScripts =
new ConcurrentDictionary<Tuple<string, string>, ScriptBlock>();
internal static ScriptBlock TryGetCachedScriptBlock(string fileName, string fileContents)
{
if (InternalTestHooks.IgnoreScriptBlockCache)
{
return null;
}
ScriptBlock scriptBlock;
var key = Tuple.Create(fileName, fileContents);
if (s_cachedScripts.TryGetValue(key, out scriptBlock))
{
Diagnostics.Assert(scriptBlock.SessionStateInternal == null,
"A cached scriptblock should not have it's session state bound, that causes a memory leak.");
return scriptBlock.Clone();
}
return null;
}
private static bool IsDynamicKeyword(Ast ast)
{
var cmdAst = ast as CommandAst;
return (cmdAst != null && cmdAst.DefiningKeyword != null);
}
private static bool IsUsingTypes(Ast ast)
{
var cmdAst = ast as UsingStatementAst;
return (cmdAst != null && cmdAst.IsUsingModuleOrAssembly());
}
internal static void CacheScriptBlock(ScriptBlock scriptBlock, string fileName, string fileContents)
{
if (InternalTestHooks.IgnoreScriptBlockCache)
{
return;
}
//
// Don't cache scriptblocks that have
// a) dynamic keywords
// b) 'using module' or 'using assembly'
// The definition of the dynamic keyword could change, consequently changing how the source text should be parsed.
// Exported types definitions from 'using module' could change, we need to do all parse-time checks again.
// TODO(sevoroby): we can optimize it to ignore 'using' if there are no actual type usage in locally defined types.
//
// using is always a top-level statements in scriptBlock, we don't need to search in child blocks.
if (scriptBlock.Ast.Find(ast => IsUsingTypes(ast), false) != null ||
scriptBlock.Ast.Find(ast => IsDynamicKeyword(ast), true) != null)
{
return;
}
if (s_cachedScripts.Count > 1024)
{
s_cachedScripts.Clear();
}
var key = Tuple.Create(fileName, fileContents);
s_cachedScripts.TryAdd(key, scriptBlock);
}
/// <summary>
/// Clears the cached scriptblocks.
/// </summary>
internal static void ClearScriptBlockCache()
{
s_cachedScripts.Clear();
}
internal static ScriptBlock EmptyScriptBlock = ScriptBlock.CreateDelayParsedScriptBlock(string.Empty, isProductCode: true);
internal static ScriptBlock Create(Parser parser, string fileName, string fileContents)
{
var scriptBlock = TryGetCachedScriptBlock(fileName, fileContents);
if (scriptBlock != null)
{
return scriptBlock;
}
ParseError[] errors;
var ast = parser.Parse(fileName, fileContents, null, out errors, ParseMode.Default);
if (errors.Length != 0)
{
throw new ParseException(errors);
}
var result = new ScriptBlock(ast, isFilter: false);
CacheScriptBlock(result, fileName, fileContents);
// The value returned will potentially be bound to a session state. We don't want
// the cached script block to end up being bound to any session state, so clone
// the return value to ensure the cached value has no session state.
return result.Clone();
}
internal ScriptBlock Clone()
{
return new ScriptBlock(_scriptBlockData);
}
/// <summary>
/// Returns the text of the script block. The return value might not match the original text exactly.
/// </summary>
public override string ToString()
{
return _scriptBlockData.ToString();
}
/// <summary>
/// Returns the text of the script block with the handling of $using expressions.
/// </summary>
internal string ToStringWithDollarUsingHandling(
Tuple<List<VariableExpressionAst>, string> usingVariablesTuple)
{
FunctionDefinitionAst funcDefn = null;
var sbAst = Ast as ScriptBlockAst;
if (sbAst == null)
{
funcDefn = (FunctionDefinitionAst)Ast;
sbAst = funcDefn.Body;
}
string sbText = sbAst.ToStringForSerialization(usingVariablesTuple, sbAst.Extent.StartOffset, sbAst.Extent.EndOffset);
if (sbAst.ParamBlock != null)
{
return sbText;
}
string paramText;
string additionalNewParams = usingVariablesTuple.Item2;
if (funcDefn == null || funcDefn.Parameters == null)
{
paramText = "param(" + additionalNewParams + ")" + Environment.NewLine;
}
else
{
paramText = funcDefn.GetParamTextFromParameterList(usingVariablesTuple);
}
sbText = paramText + sbText;
return sbText;
}
/// <summary>
/// Support for <see cref="ISerializable"/>.
/// </summary>
public virtual void GetObjectData(SerializationInfo info, StreamingContext context)
{
if (info == null)
{
throw PSTraceSource.NewArgumentNullException("info");
}
string serializedContent = this.ToString();
info.AddValue("ScriptText", serializedContent);
info.SetType(typeof(ScriptBlockSerializationHelper));
}
internal PowerShell GetPowerShellImpl(ExecutionContext context, Dictionary<string, object> variables, bool isTrustedInput,
bool filterNonUsingVariables, bool? createLocalScope, params object[] args)
{
return AstInternal.GetPowerShell(context, variables, isTrustedInput, filterNonUsingVariables, createLocalScope, args);
}
internal SteppablePipeline GetSteppablePipelineImpl(CommandOrigin commandOrigin, object[] args)
{
var pipelineAst = GetSimplePipeline(resourceString => { throw PSTraceSource.NewInvalidOperationException(resourceString); });
Diagnostics.Assert(pipelineAst != null, "This should be checked by GetSimplePipeline");
if (!(pipelineAst.PipelineElements[0] is CommandAst))
{
throw PSTraceSource.NewInvalidOperationException(
AutomationExceptions.CantConvertEmptyPipeline);
}
return PipelineOps.GetSteppablePipeline(pipelineAst, commandOrigin, this, args);
}
private PipelineAst GetSimplePipeline(Func<string, PipelineAst> errorHandler)
{
errorHandler = errorHandler ?? (_ => null);
if (HasBeginBlock || HasProcessBlock)
{
return errorHandler(AutomationExceptions.CanConvertOneClauseOnly);
}
var ast = AstInternal;
var statements = ast.Body.EndBlock.Statements;
if (!statements.Any())
{
return errorHandler(AutomationExceptions.CantConvertEmptyPipeline);
}
if (statements.Count > 1)
{
return errorHandler(AutomationExceptions.CanOnlyConvertOnePipeline);
}
if (ast.Body.EndBlock.Traps != null && ast.Body.EndBlock.Traps.Any())
{
return errorHandler(AutomationExceptions.CantConvertScriptBlockWithTrap);
}
var pipeAst = statements[0] as PipelineAst;
if (pipeAst == null)
{
return errorHandler(AutomationExceptions.CanOnlyConvertOnePipeline);
}
// The old code checked for empty pipeline.
// That can't happen in the new parser (validated in the constructors),
// so the resource CantConvertEmptyPipeline is probably unused.
return pipeAst;
}
internal List<Attribute> GetAttributes()
{
return _scriptBlockData.GetAttributes();
}
internal string GetFileName()
{
return AstInternal.Body.Extent.File;
}
internal bool IsMetaConfiguration()
{
// GetAttributes() is asserted never return null
return GetAttributes().OfType<DscLocalConfigurationManagerAttribute>().Any();
}
internal PSToken GetStartPosition()
{
return new PSToken(Ast.Extent);
}
internal MergedCommandParameterMetadata ParameterMetadata
{
get { return _scriptBlockData.GetParameterMetadata(this); }
}
internal bool UsesCmdletBinding
{
get { return _scriptBlockData.UsesCmdletBinding; }
}
internal bool HasDynamicParameters
{
get { return AstInternal.Body.DynamicParamBlock != null; }
}
/// <summary>
/// DebuggerHidden.
/// </summary>
public bool DebuggerHidden
{
get { return _scriptBlockData.DebuggerHidden; }
set { _scriptBlockData.DebuggerHidden = value; }
}
/// <summary>
/// The unique ID of this script block.
/// </summary>
public Guid Id
{
get { return _scriptBlockData.Id; }
}
internal bool DebuggerStepThrough
{
get { return _scriptBlockData.DebuggerStepThrough; }
set { _scriptBlockData.DebuggerStepThrough = value; }
}
internal RuntimeDefinedParameterDictionary RuntimeDefinedParameters
{
get { return _scriptBlockData.RuntimeDefinedParameters; }
}
internal bool HasLogged
{
get { return _scriptBlockData.HasLogged; }
set { _scriptBlockData.HasLogged = value; }
}
internal Assembly AssemblyDefiningPSTypes { set; get; }
internal HelpInfo GetHelpInfo(ExecutionContext context, CommandInfo commandInfo, bool dontSearchOnRemoteComputer,
Dictionary<Ast, Token[]> scriptBlockTokenCache, out string helpFile, out string helpUriFromDotLink)
{
helpUriFromDotLink = null;
var commentTokens = HelpCommentsParser.GetHelpCommentTokens(AstInternal, scriptBlockTokenCache);
if (commentTokens != null)
{
return HelpCommentsParser.CreateFromComments(context, commandInfo, commentTokens.Item1,
commentTokens.Item2, dontSearchOnRemoteComputer, out helpFile, out helpUriFromDotLink);
}
helpFile = null;
return null;
}
/// <summary>
/// Check the script block to see if it uses any language constructs not allowed in restricted language mode.
/// </summary>
/// <param name="allowedCommands">The commands that are allowed.</param>
/// <param name="allowedVariables">
/// The variables allowed in this scriptblock. If this is null, then the default variable set
/// will be allowed. If it is an empty list, no variables will be allowed. If it is "*" then
/// any variable will be allowed.
/// </param>
/// <param name="allowEnvironmentVariables">The environment variables that are allowed.</param>
public void CheckRestrictedLanguage(IEnumerable<string> allowedCommands, IEnumerable<string> allowedVariables, bool allowEnvironmentVariables)
{
Parser parser = new Parser();
var ast = AstInternal;
if (HasBeginBlock || HasProcessBlock || ast.Body.ParamBlock != null)
{
Ast errorAst = ast.Body.BeginBlock ?? (Ast)ast.Body.ProcessBlock ?? ast.Body.ParamBlock;
parser.ReportError(errorAst.Extent,
nameof(ParserStrings.InvalidScriptBlockInDataSection),
ParserStrings.InvalidScriptBlockInDataSection);
}
if (HasEndBlock)
{
RestrictedLanguageChecker rlc = new RestrictedLanguageChecker(parser, allowedCommands, allowedVariables, allowEnvironmentVariables);
var endBlock = ast.Body.EndBlock;
StatementBlockAst.InternalVisit(rlc, endBlock.Traps, endBlock.Statements, AstVisitAction.Continue);
}
if (parser.ErrorList.Any())
{
throw new ParseException(parser.ErrorList.ToArray());
}
}
internal string GetWithInputHandlingForInvokeCommand()
{
return AstInternal.GetWithInputHandlingForInvokeCommand();
}
internal string GetWithInputHandlingForInvokeCommandWithUsingExpression(
Tuple<List<VariableExpressionAst>, string> usingVariablesTuple)
{
Tuple<string, string> result =
AstInternal.GetWithInputHandlingForInvokeCommandWithUsingExpression(usingVariablesTuple);
// result.Item1 is ParamText; result.Item2 is ScriptBlockText
if (result.Item1 == null)
return result.Item2;
return result.Item1 + result.Item2;
}
internal bool IsUsingDollarInput()
{
return AstSearcher.IsUsingDollarInput(this.Ast);
}
internal void InvokeWithPipeImpl(bool createLocalScope,
Dictionary<string, ScriptBlock> functionsToDefine,
List<PSVariable> variablesToDefine,
ErrorHandlingBehavior errorHandlingBehavior,
object dollarUnder,
object input,
object scriptThis,
Pipe outputPipe,
InvocationInfo invocationInfo,
params object[] args)
{
InvokeWithPipeImpl(ScriptBlockClauseToInvoke.ProcessBlockOnly, createLocalScope,
functionsToDefine,
variablesToDefine,
errorHandlingBehavior,
dollarUnder,
input,
scriptThis,
outputPipe,
invocationInfo,
args);
}
internal void InvokeWithPipeImpl(ScriptBlockClauseToInvoke clauseToInvoke,
bool createLocalScope,
Dictionary<string, ScriptBlock> functionsToDefine,
List<PSVariable> variablesToDefine,
ErrorHandlingBehavior errorHandlingBehavior,
object dollarUnder,
object input,
object scriptThis,
Pipe outputPipe,
InvocationInfo invocationInfo,
params object[] args)
{
if (clauseToInvoke == ScriptBlockClauseToInvoke.Begin && !HasBeginBlock)
{
return;
}
else if (clauseToInvoke == ScriptBlockClauseToInvoke.Process && !HasProcessBlock)
{
return;
}
else if (clauseToInvoke == ScriptBlockClauseToInvoke.End && !HasEndBlock)
{
return;
}
ExecutionContext context = GetContextFromTLS();
Diagnostics.Assert(SessionStateInternal == null || SessionStateInternal.ExecutionContext == context,
"The scriptblock is being invoked in a runspace different than the one where it was created");
if (context.CurrentPipelineStopping)
{
throw new PipelineStoppedException();
}
// Validate at the arguments are consistent. The only public API that gets you here never sets createLocalScope to false...
Diagnostics.Assert(createLocalScope == true || functionsToDefine == null, "When calling ScriptBlock.InvokeWithContext(), if 'functionsToDefine' != null then 'createLocalScope' must be true");
Diagnostics.Assert(createLocalScope == true || variablesToDefine == null, "When calling ScriptBlock.InvokeWithContext(), if 'variablesToDefine' != null then 'createLocalScope' must be true");
if (args == null)
{
args = Utils.EmptyArray<object>();
}
bool runOptimized = context._debuggingMode > 0 ? false : createLocalScope;
var codeToInvoke = GetCodeToInvoke(ref runOptimized, clauseToInvoke);
if (codeToInvoke == null)
return;
if (outputPipe == null)
{
// If we don't have a pipe to write to, we need to discard all results.
outputPipe = new Pipe { NullPipe = true };
}
var locals = MakeLocalsTuple(runOptimized);
if (dollarUnder != AutomationNull.Value)
{
locals.SetAutomaticVariable(AutomaticVariable.Underbar, dollarUnder, context);
}
if (input != AutomationNull.Value)
{
locals.SetAutomaticVariable(AutomaticVariable.Input, input, context);
}
if (scriptThis != AutomationNull.Value)
{
locals.SetAutomaticVariable(AutomaticVariable.This, scriptThis, context);
}
SetPSScriptRootAndPSCommandPath(locals, context);
var oldShellFunctionErrorOutputPipe = context.ShellFunctionErrorOutputPipe;
var oldExternalErrorOutput = context.ExternalErrorOutput;
var oldScopeOrigin = context.EngineSessionState.CurrentScope.ScopeOrigin;
var oldSessionState = context.EngineSessionState;
// If the script block has a different language mode than the current,
// change the language mode.
PSLanguageMode? oldLanguageMode = null;
PSLanguageMode? newLanguageMode = null;
if ((this.LanguageMode.HasValue) &&
(this.LanguageMode != context.LanguageMode))
{
oldLanguageMode = context.LanguageMode;
newLanguageMode = this.LanguageMode;
}
Dictionary<string, PSVariable> backupWhenDotting = null;
try
{
var myInvocationInfo = invocationInfo;
if (myInvocationInfo == null)
{
var callerFrame = context.Debugger.GetCallStack().LastOrDefault();
var extent = (callerFrame != null)
? callerFrame.FunctionContext.CurrentPosition
: Ast.Extent;
myInvocationInfo = new InvocationInfo(null, extent, context);
}
locals.SetAutomaticVariable(AutomaticVariable.MyInvocation, myInvocationInfo, context);
if (SessionStateInternal != null)
context.EngineSessionState = SessionStateInternal;
// If we don't want errors written, hide the error pipe.
switch (errorHandlingBehavior)
{
case ErrorHandlingBehavior.WriteToCurrentErrorPipe:
// no need to do anything
break;
case ErrorHandlingBehavior.WriteToExternalErrorPipe:
context.ShellFunctionErrorOutputPipe = null;
break;
case ErrorHandlingBehavior.SwallowErrors:
context.ShellFunctionErrorOutputPipe = null;
context.ExternalErrorOutput = new DiscardingPipelineWriter();
break;
}
if (createLocalScope)
{
var newScope = context.EngineSessionState.NewScope(false);
context.EngineSessionState.CurrentScope = newScope;
newScope.LocalsTuple = locals;
// Inject passed in functions into the scope
if (functionsToDefine != null)
{
foreach (var def in functionsToDefine)
{
if (string.IsNullOrWhiteSpace(def.Key))
{
PSInvalidOperationException e = PSTraceSource.NewInvalidOperationException(
ParserStrings.EmptyFunctionNameInFunctionDefinitionDictionary);
e.SetErrorId("EmptyFunctionNameInFunctionDefinitionDictionary");
throw e;
}
if (def.Value == null)
{
PSInvalidOperationException e = PSTraceSource.NewInvalidOperationException(
ParserStrings.NullFunctionBodyInFunctionDefinitionDictionary, def.Key);
e.SetErrorId("NullFunctionBodyInFunctionDefinitionDictionary");
throw e;
}
newScope.FunctionTable.Add(def.Key, new FunctionInfo(def.Key, def.Value, context));
}
}
// Inject passed in variables into the scope
if (variablesToDefine != null)
{
int index = 0;
foreach (var psvar in variablesToDefine)
{
// Check for null entries.
if (psvar == null)
{
PSInvalidOperationException e = PSTraceSource.NewInvalidOperationException(
ParserStrings.NullEntryInVariablesDefinitionList, index);
e.SetErrorId("NullEntryInVariablesDefinitionList");
throw e;
}
string name = psvar.Name;
Diagnostics.Assert(!(string.Equals(name, "this") || string.Equals(name, "_") || string.Equals(name, "input")),
"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.");
index++;
newScope.Variables.Add(name, psvar);
}
}
}
else
{
if (context.EngineSessionState.CurrentScope.LocalsTuple == null)
{
// If the locals tuple is, that means either:
// * we're invoking a script block for a module
// * something unexpected
context.EngineSessionState.CurrentScope.LocalsTuple = locals;
}
else
{
context.EngineSessionState.CurrentScope.DottedScopes.Push(locals);
backupWhenDotting = new Dictionary<string, PSVariable>();
}
}
// Set the language mode
if (newLanguageMode.HasValue)
{
context.LanguageMode = newLanguageMode.Value;
}
args = BindArgumentsForScriptblockInvoke(
(RuntimeDefinedParameter[])RuntimeDefinedParameters.Data,
args, context, !createLocalScope, backupWhenDotting, locals);
locals.SetAutomaticVariable(AutomaticVariable.Args, args, context);
context.EngineSessionState.CurrentScope.ScopeOrigin = CommandOrigin.Internal;
var functionContext = new FunctionContext
{
_executionContext = context,
_outputPipe = outputPipe,
_localsTuple = locals,
_scriptBlock = this,
_file = this.File,
_debuggerHidden = this.DebuggerHidden,
_debuggerStepThrough = this.DebuggerStepThrough,
_sequencePoints = SequencePoints,
};
ScriptBlock.LogScriptBlockStart(this, context.CurrentRunspace.InstanceId);
try
{
codeToInvoke(functionContext);
}
finally
{
ScriptBlock.LogScriptBlockEnd(this, context.CurrentRunspace.InstanceId);
}
}
catch (TargetInvocationException tie)
{
// DynamicInvoke always wraps, so unwrap here.
throw tie.InnerException;
}
finally
{
// Restore the language mode
if (oldLanguageMode.HasValue)
{
context.LanguageMode = oldLanguageMode.Value;
}
// Now restore the output pipe...
context.ShellFunctionErrorOutputPipe = oldShellFunctionErrorOutputPipe;
context.ExternalErrorOutput = oldExternalErrorOutput;
// Restore the interactive command state...
context.EngineSessionState.CurrentScope.ScopeOrigin = oldScopeOrigin;
if (createLocalScope)
{
context.EngineSessionState.RemoveScope(context.EngineSessionState.CurrentScope);
}
else if (backupWhenDotting != null)
{
context.EngineSessionState.CurrentScope.DottedScopes.Pop();
Diagnostics.Assert(backupWhenDotting != null, "when dotting, this dictionary isn't null");
foreach (var pair in backupWhenDotting)
{
if (pair.Value != null)
{
context.EngineSessionState.SetVariable(pair.Value, false, CommandOrigin.Internal);
}
else
{
context.EngineSessionState.RemoveVariable(pair.Key);
}
}
}
// Restore session state...
context.EngineSessionState = oldSessionState;
}
}
internal MutableTuple MakeLocalsTuple(bool createLocalScope)
{
MutableTuple locals;
if (createLocalScope)
{
locals = MutableTuple.MakeTuple(_scriptBlockData.LocalsMutableTupleType, _scriptBlockData.NameToIndexMap, _scriptBlockData.LocalsMutableTupleCreator);
}
else
{
locals = MutableTuple.MakeTuple(_scriptBlockData.UnoptimizedLocalsMutableTupleType,
UsesCmdletBinding
? Compiler.DottedScriptCmdletLocalsNameIndexMap
: Compiler.DottedLocalsNameIndexMap,
_scriptBlockData.UnoptimizedLocalsMutableTupleCreator);
}
return locals;
}
internal static object[] BindArgumentsForScriptblockInvoke(
RuntimeDefinedParameter[] parameters,
object[] args,
ExecutionContext context,
bool dotting,
Dictionary<string, PSVariable> backupWhenDotting,
MutableTuple locals)
{
var boundParameters = new CommandLineParameters();
if (parameters.Length == 0)
{
return args;
}
for (int i = 0; i < parameters.Length; i++)
{
var parameter = parameters[i];
object valueToBind;
bool wasDefaulted = false;
if (i >= args.Length)
{
valueToBind = parameter.Value;
if (valueToBind is Compiler.DefaultValueExpressionWrapper)
{
// We pass in a null SessionStateInternal because the current scope is already set correctly.
valueToBind = ((Compiler.DefaultValueExpressionWrapper)valueToBind).GetValue(context, null);
}
wasDefaulted = true;
}
else
{
valueToBind = args[i];
}
bool valueSet = false;
if (dotting && backupWhenDotting != null)
{
backupWhenDotting[parameter.Name] = context.EngineSessionState.GetVariableAtScope(parameter.Name, "local");
}
else
{
valueSet = locals.TrySetParameter(parameter.Name, valueToBind);
}
if (!valueSet)
{
var variable = new PSVariable(parameter.Name, valueToBind, ScopedItemOptions.None, parameter.Attributes);
context.EngineSessionState.SetVariable(variable, false, CommandOrigin.Internal);
}
if (!wasDefaulted)
{
boundParameters.Add(parameter.Name, valueToBind);
boundParameters.MarkAsBoundPositionally(parameter.Name);
}
}
locals.SetAutomaticVariable(AutomaticVariable.PSBoundParameters, boundParameters.GetValueToBindToPSBoundParameters(), context);
var leftOverArgs = args.Length - parameters.Length;
if (leftOverArgs <= 0)
{
return Utils.EmptyArray<object>();
}
object[] result = new object[leftOverArgs];
Array.Copy(args, parameters.Length, result, 0, result.Length);
return result;
}
internal static void SetAutomaticVariable(AutomaticVariable variable, object value, MutableTuple locals)
{
locals.SetValue((int)variable, value);
}
private Action<FunctionContext> GetCodeToInvoke(ref bool optimized, ScriptBlockClauseToInvoke clauseToInvoke)
{
if (clauseToInvoke == ScriptBlockClauseToInvoke.ProcessBlockOnly && (HasBeginBlock || (HasEndBlock && HasProcessBlock)))
{
throw PSTraceSource.NewInvalidOperationException(AutomationExceptions.ScriptBlockInvokeOnOneClauseOnly);
}
optimized = _scriptBlockData.Compile(optimized);
if (optimized)
{
switch (clauseToInvoke)
{
case ScriptBlockClauseToInvoke.Begin:
return _scriptBlockData.BeginBlock;
case ScriptBlockClauseToInvoke.Process:
return _scriptBlockData.ProcessBlock;
case ScriptBlockClauseToInvoke.End:
return _scriptBlockData.EndBlock;
default:
return HasProcessBlock ? _scriptBlockData.ProcessBlock : _scriptBlockData.EndBlock;
}
}
switch (clauseToInvoke)
{
case ScriptBlockClauseToInvoke.Begin:
return _scriptBlockData.UnoptimizedBeginBlock;
case ScriptBlockClauseToInvoke.Process:
return _scriptBlockData.UnoptimizedProcessBlock;
case ScriptBlockClauseToInvoke.End:
return _scriptBlockData.UnoptimizedEndBlock;
default:
return HasProcessBlock ? _scriptBlockData.UnoptimizedProcessBlock : _scriptBlockData.UnoptimizedEndBlock;
}
}
internal CmdletBindingAttribute CmdletBindingAttribute
{
get { return _scriptBlockData.CmdletBindingAttribute; }
}
internal ObsoleteAttribute ObsoleteAttribute
{
get { return _scriptBlockData.ObsoleteAttribute; }
}
internal ExperimentalAttribute ExperimentalAttribute
{
get { return _scriptBlockData.ExperimentalAttribute; }
}
internal bool Compile(bool optimized)
{
return _scriptBlockData.Compile(optimized);
}
internal static void LogScriptBlockCreation(ScriptBlock scriptBlock, bool force)
{
ScriptBlockLogging logSetting = GetScriptBlockLoggingSetting();
if (force || logSetting?.EnableScriptBlockLogging == true)
{
if (!scriptBlock.HasLogged || InternalTestHooks.ForceScriptBlockLogging)
{
// If script block logging is explicitly disabled, or it's from a trusted
// file or internal, skip logging.
if (logSetting?.EnableScriptBlockLogging == false ||
scriptBlock.ScriptBlockData.IsProductCode)
{
return;
}
string scriptBlockText = scriptBlock.Ast.Extent.Text;
bool written = false;
// Maximum size of ETW events is 64kb. Split a message if it is larger than 20k (Unicode) characters.
if (scriptBlockText.Length < 20000)
{
written = WriteScriptBlockToLog(scriptBlock, 0, 1, scriptBlock.Ast.Extent.Text);
}
else
{
// But split the segments into random sizes (10k + between 0 and 10kb extra)
// so that attackers can't creatively force their scripts to span well-known
// segments (making simple rules less reliable).
int segmentSize = 10000 + (new Random()).Next(10000);
int segments = (int)Math.Floor((double)(scriptBlockText.Length / segmentSize)) + 1;
int currentLocation = 0;
int currentSegmentSize = 0;
for (int segment = 0; segment < segments; segment++)
{
currentLocation = segment * segmentSize;
currentSegmentSize = Math.Min(segmentSize, scriptBlockText.Length - currentLocation);
string textToLog = scriptBlockText.Substring(currentLocation, currentSegmentSize);
written = WriteScriptBlockToLog(scriptBlock, segment, segments, textToLog);
}
}
if (written)
{
scriptBlock.HasLogged = true;
}
}
}
}
private static bool WriteScriptBlockToLog(ScriptBlock scriptBlock, int segment, int segments, string textToLog)
{
// See if we need to encrypt the event log message. This info is all cached by Utils.GetPolicySetting(),
// so we're not hitting the configuration file for every script block we compile.
ProtectedEventLogging logSetting = Utils.GetPolicySetting<ProtectedEventLogging>(Utils.SystemWideOnlyConfig);
bool wasEncoded = false;
if (logSetting != null)
{
lock (s_syncObject)
{
// Populates the encryptionRecipients list from the Group Policy, if possible. If not possible,
// does all appropriate logging and encryptionRecipients is 'null'. 'CouldLog' being false
// implies the engine wasn't ready for logging yet.
bool couldLog = GetAndValidateEncryptionRecipients(scriptBlock, logSetting);
if (!couldLog)
{
return false;
}
// If we have recipients to encrypt to, then do so. Otherwise, we'll just log the plain text
// version.
if (s_encryptionRecipients != null)
{
// Encrypt the raw Text from the scriptblock. The user may have to deal with any control characters in the data.
ExecutionContext executionContext = LocalPipeline.GetExecutionContextFromTLS();
ErrorRecord error = null;
byte[] contentBytes = System.Text.Encoding.UTF8.GetBytes(textToLog);
string encodedContent = CmsUtils.Encrypt(contentBytes, s_encryptionRecipients, executionContext.SessionState, out error);
// Can't cache the reporting of encryption errors, as they are likely content-based.
if (error != null)
{
// If we got an error encrypting the content, log an error and continue
// logging the (unencrypted) message anyways. Logging trumps protected logging -
// being able to detect that an attacker has compromised a box outweighs the danger of the
// attacker seeing potentially sensitive data. Because if they aren't detected, then
// they can just wait on the compromised box and see the sensitive data eventually anyways.
string errorMessage = StringUtil.Format(SecuritySupportStrings.CouldNotEncryptContent, textToLog, error.ToString());
PSEtwLog.LogOperationalError(PSEventId.ScriptBlock_Compile_Detail, PSOpcode.Create, PSTask.ExecuteCommand, PSKeyword.UseAlwaysOperational,
0, 0, errorMessage, scriptBlock.Id.ToString(), scriptBlock.File ?? string.Empty);
}
else
{
textToLog = encodedContent;
wasEncoded = true;
}
}
}
}
if (!wasEncoded)
{
// The logging mechanism(s) cannot handle null and rendering may not be able to handle
// null as we have the string defined as a null terminated string in the manifest.
// So, replace null characters with the Unicode `SYMBOL FOR NULL`
// We don't just remove the characters to preserve the fact that a null character was there.
textToLog = textToLog.Replace('\u0000', '\u2400');
}
if (scriptBlock._scriptBlockData.HasSuspiciousContent)
{
PSEtwLog.LogOperationalWarning(PSEventId.ScriptBlock_Compile_Detail, PSOpcode.Create, PSTask.ExecuteCommand, PSKeyword.UseAlwaysOperational,
segment + 1, segments, textToLog, scriptBlock.Id.ToString(), scriptBlock.File ?? string.Empty);
}
else
{
PSEtwLog.LogOperationalVerbose(PSEventId.ScriptBlock_Compile_Detail, PSOpcode.Create, PSTask.ExecuteCommand, PSKeyword.UseAlwaysOperational,
segment + 1, segments, textToLog, scriptBlock.Id.ToString(), scriptBlock.File ?? string.Empty);
}
return true;
}
private static bool GetAndValidateEncryptionRecipients(ScriptBlock scriptBlock, ProtectedEventLogging logSetting)
{
// See if protected event logging is enabled
if (logSetting.EnableProtectedEventLogging == true)
{
// Get the encryption certificate
if (logSetting.EncryptionCertificate != null)
{
ErrorRecord error = null;
ExecutionContext executionContext = LocalPipeline.GetExecutionContextFromTLS();
SessionState sessionState = null;
// Use the session state from the current pipeline, if it exists.
if (executionContext != null)
{
sessionState = executionContext.SessionState;
}
// If the engine hasn't started up yet, then we're just compiling script
// blocks. We'll have to log them when they are used and we have an engine
// to work with.
if (sessionState == null)
{
return false;
}
string fullCertificateContent = string.Join(Environment.NewLine, logSetting.EncryptionCertificate);
// If the certificate has changed, drop all of our cached information
ResetCertificateCacheIfNeeded(fullCertificateContent);
// If we have valid recipients, no need for further analysis.
if (s_encryptionRecipients != null)
{
return true;
}
// If we've already verified all of the properties of the cert we care about (even if it
// didn't result in a valid cert), return now.
if (s_hasProcessedCertificate)
{
return true;
}
// Resolve the certificate to a recipient
CmsMessageRecipient recipient = new CmsMessageRecipient(fullCertificateContent);
recipient.Resolve(sessionState, ResolutionPurpose.Encryption, out error);
s_hasProcessedCertificate = true;
// If there's an error that we haven't already reported, report it in the event log.
// We only do this once, as the error will always be the same for a given certificate.
if (error != null)
{
// If we got an error resolving the encryption certificate, log a warning and continue
// logging the (unencrypted) message anyways. Logging trumps protected logging -
// being able to detect that an attacker has compromised a box outweighs the danger of the
// attacker seeing potentially sensitive data. Because if they aren't detected, then
// they can just wait on the compromised box and see the sensitive data eventually anyways.
string errorMessage = StringUtil.Format(SecuritySupportStrings.CouldNotUseCertificate, error.ToString());
PSEtwLog.LogOperationalError(PSEventId.ScriptBlock_Compile_Detail, PSOpcode.Create, PSTask.ExecuteCommand, PSKeyword.UseAlwaysOperational,
0, 0, errorMessage, scriptBlock.Id.ToString(), scriptBlock.File ?? string.Empty);
return true;
}
// Now, save the certificate. We'll be comfortable using this one from now on.
s_encryptionRecipients = new CmsMessageRecipient[] { recipient };
// Check if the certificate has a private key, and report a warning if so.
// We only do this once, as the error will always be the same for a given certificate.
foreach (X509Certificate2 validationCertificate in recipient.Certificates)
{
if (validationCertificate.HasPrivateKey)
{
// Only log the first line of what we pulled from the configuration. If this is a path, this will have enough information.
// If this is the actual certificate, only include the first line of the certificate content so that we're not permanently keeping the private
// key in the log.
string certificateForLog = fullCertificateContent;
if (logSetting.EncryptionCertificate.Length > 1)
{
certificateForLog = logSetting.EncryptionCertificate[1];
}
string errorMessage = StringUtil.Format(SecuritySupportStrings.CertificateContainsPrivateKey, certificateForLog);
PSEtwLog.LogOperationalError(PSEventId.ScriptBlock_Compile_Detail, PSOpcode.Create, PSTask.ExecuteCommand, PSKeyword.UseAlwaysOperational,
0, 0, errorMessage, scriptBlock.Id.ToString(), scriptBlock.File ?? string.Empty);
}
}
}
}
return true;
}
private static object s_syncObject = new Object();
private static string s_lastSeenCertificate = string.Empty;
private static bool s_hasProcessedCertificate = false;
private static CmsMessageRecipient[] s_encryptionRecipients = null;
// Reset any static caches if the certificate has changed
private static void ResetCertificateCacheIfNeeded(string certificate)
{
if (!string.Equals(s_lastSeenCertificate, certificate, StringComparison.Ordinal))
{
s_hasProcessedCertificate = false;
s_lastSeenCertificate = certificate;
s_encryptionRecipients = null;
}
}
private static ScriptBlockLogging GetScriptBlockLoggingSetting()
{
return Utils.GetPolicySetting<ScriptBlockLogging>(Utils.SystemWideThenCurrentUserConfig);
}
// Quick check for script blocks that may have suspicious content. If this
// is true, we log them to the event log despite event log settings.
internal static string CheckSuspiciousContent(Ast scriptBlockAst)
{
var foundSignature = SuspiciousContentChecker.Match(scriptBlockAst.Extent.Text);
if (foundSignature != null)
{
return foundSignature;
}
if (scriptBlockAst.HasSuspiciousContent)
{
Ast foundAst = scriptBlockAst.Find(ast =>
{
// Try to find the lowest AST that was not considered suspicious, but its parent
// was.
return (!ast.HasSuspiciousContent) && (ast.Parent.HasSuspiciousContent);
}, true);
if (foundAst != null)
{
return foundAst.Parent.Extent.Text;
}
else
{
return scriptBlockAst.Extent.Text;
}
}
return null;
}
class SuspiciousContentChecker
{
// Based on a (bad) random number generator, but good enough
// for our simple needs.
private const uint LCG = 31;
/// <summary>
/// Check if a hash code matches a small set of pre-computed hashes
/// for suspicious strings in a PowerShell script.
///
/// If you need to add a new string, use the commented out
/// method HashNewPattern (commented out because it's dead
/// code - needed only to generate this switch statement below.)
/// </summary>
/// <returns>The string matching the hash, or null.</returns>
static string LookupHash(uint h)
{
switch (h)
{
// Calling Add-Type
case 3012981990: return "Add-Type";
case 3359423881: return "DllImport";
// Doing dynamic assembly building / method indirection
case 2713126922: return "DefineDynamicAssembly";
case 2407049616: return "DefineDynamicModule";
case 3276870517: return "DefineType";
case 419507039: return "DefineConstructor";
case 1370182198: return "CreateType";
case 1973546644: return "DefineLiteral";
case 3276413244: return "DefineEnum";
case 2785322015: return "DefineField";
case 837002512: return "ILGenerator";
case 3117011: return "Emit";
case 883134515: return "UnverifiableCodeAttribute";
case 2920989166: return "DefinePInvokeMethod";
case 1996222179: return "GetTypes";
case 3935635674: return "GetAssemblies";
case 955534258: return "Methods";
case 3368914227: return "Properties";
// Suspicious methods / properties on "Type"
case 398423780: return "GetConstructor";
case 3761202703: return "GetConstructors";
case 1998297230: return "GetDefaultMembers";
case 1982269700: return "GetEvent";
case 1320818671: return "GetEvents";
case 1982805860: return "GetField";
case 1337439631: return "GetFields";
case 2784018083: return "GetInterface";
case 2864332761: return "GetInterfaceMap";
case 405214768: return "GetInterfaces";
case 1534378352: return "GetMember";
case 321088771: return "GetMembers";
case 1534592951: return "GetMethod";
case 327741340: return "GetMethods";
case 1116240007: return "GetNestedType";
case 243701964: return "GetNestedTypes";
case 1077700873: return "GetProperties";
case 1020114731: return "GetProperty";
case 257791250: return "InvokeMember";
case 3217683173: return "MakeArrayType";
case 821968872: return "MakeByRefType";
case 3538448099: return "MakeGenericType";
case 3207725129: return "MakePointerType";
case 1617553224: return "DeclaringMethod";
case 3152745313: return "DeclaringType";
case 4144122198: return "ReflectedType";
case 3455789538: return "TypeHandle";
case 624373608: return "TypeInitializer";
case 637454598: return "UnderlyingSystemType";
// Doing things with System.Runtime.InteropServices
case 1855303451: return "InteropServices";
case 839491486: return "Marshal";
case 1928879414: return "AllocHGlobal";
case 3180922282: return "PtrToStructure";
case 1718292736: return "StructureToPtr";
case 3390778911: return "FreeHGlobal";
case 3111215263: return "IntPtr";
// General Obfuscation
case 1606191041: return "MemoryStream";
case 2147536747: return "DeflateStream";
case 1820815050: return "FromBase64String";
case 3656724093: return "EncodedCommand";
case 2920836328: return "Bypass";
case 3473847323: return "ToBase64String";
case 4192166699: return "ExpandString";
case 2462813217: return "GetPowerShell";
// Suspicious Win32 API calls
case 2123968741: return "OpenProcess";
case 3630248714: return "VirtualAlloc";
case 3303847927: return "VirtualFree";
case 512407217: return "WriteProcessMemory";
case 2357873553: return "CreateUserThread";
case 756544032: return "CloseHandle";
case 3400025495: return "GetDelegateForFunctionPointer";
case 314128220: return "kernel32";
case 2469462534: return "CreateThread";
case 3217199031: return "memcpy";
case 2283745557: return "LoadLibrary";
case 3317813738: return "GetModuleHandle";
case 2491894472: return "GetProcAddress";
case 1757922660: return "VirtualProtect";
case 2693938383: return "FreeLibrary";
case 2873914970: return "ReadProcessMemory";
case 2717270220: return "CreateRemoteThread";
case 2867203884: return "AdjustTokenPrivileges";
case 2889068903: return "WriteByte";
case 3667925519: return "WriteInt32";
case 2742077861: return "OpenThreadToken";
case 2826980154: return "PtrToString";
case 3735047487: return "ZeroFreeGlobalAllocUnicode";
case 788615220: return "OpenProcessToken";
case 1264589033: return "GetTokenInformation";
case 2165372045: return "SetThreadToken";
case 197357349: return "ImpersonateLoggedOnUser";
case 1259149099: return "RevertToSelf";
case 2446460563: return "GetLogonSessionData";
case 2534763616: return "CreateProcessWithToken";
case 3512478977: return "DuplicateTokenEx";
case 3126049082: return "OpenWindowStation";
case 3990594194: return "OpenDesktop";
case 3195806696: return "MiniDumpWriteDump";
case 3990234693: return "AddSecurityPackage";
case 611728017: return "EnumerateSecurityPackages";
case 4283779521: return "GetProcessHandle";
case 845600244: return "DangerousGetHandle";
// Crypto - ransomware, etc.
case 2691669189: return "CryptoServiceProvider";
case 1413809388: return "Cryptography";
case 4113841312: return "RijndaelManaged";
case 1650652922: return "SHA1Managed";
case 1759701889: return "CryptoStream";
case 2439640460: return "CreateEncryptor";
case 1446703796: return "CreateDecryptor";
case 1638240579: return "TransformFinalBlock";
case 1464730593: return "DeviceIoControl";
case 3966822309: return "SetInformationProcess";
case 851965993: return "PasswordDeriveBytes";
// Keylogging
case 793353336: return "GetAsyncKeyState";
case 293877108: return "GetKeyboardState";
case 2448894537: return "GetForegroundWindow";
// Using internal types
case 4059335458: return "BindingFlags";
case 1085624182: return "NonPublic";
// Changing logging settings
case 904148605: return "ScriptBlockLogging";
case 4150524432: return "LogPipelineExecutionDetails";
case 3704712755: return "ProtectedEventLogging";
default: return null;
}
}
/// <summary>
/// Check the list of running hashes for any matches, but
/// only up to the limit of <paramref name="upTo"/>.
///
/// If a hash matches, we ignore the possibility of a
/// collision. If the hash is acceptable, collisions will
/// be infrequent and we'll just log an occasionaly script
/// that isn't really suspicious.
/// </summary>
/// <returns>The string matching the hash, or null.</returns>
private static string CheckForMatches(uint[] runningHash, int upTo)
{
var upToMax = runningHash.Length;
if (upTo == 0) upTo = upToMax;
else if (upTo > upToMax) upTo = upToMax;
for (var i = 0; i < upTo; i++)
{
var result = LookupHash(runningHash[i]);
if (result != null) return result;
}
return null;
}
/// <summary>
/// Scan a string for suspicious content.
///
/// This is based on the Rubin-Karp algorithm, but heavily
/// modified to support searching for multiple patterns at
/// the same time.
///
/// The key difference from Rubin-Karp is that we don't undo
/// the hash of the first character as we shift along in the
/// input.
///
/// Instead, we can rely on knowing we need the hashes for
/// shorter strings anyway, so we reuse their values in
/// computing the hash for the longer patterns. This lets us
/// use a much simpler hash as well - we can avoid the use of
/// mod.
/// </summary>
/// <returns>The string matching the hash, or null.</returns>
public static string Match(string text)
{
// The longest pattern is 29 characters.
// The values in the array are the computed hashes of length
// index-1 (so runningHash[0] holds the hash for length 1).
var runningHash = new uint[29];
int longestPossiblePattern = 0;
for (int i = 0; i < text.Length; i++)
{
uint h = text[i];
if (h >= 'A' && h <= 'Z')
{
h = h | 0x20; // ToLower
}
else if (!((h >= 'a' && h <= 'z') || h == '-'))
{
// If the character isn't in any of our patterns,
// don't bother hashing and reset the running length.
longestPossiblePattern = 0;
continue;
}
for (int j = Math.Min(i, runningHash.Length) - 1; j > 0; j--)
{
// Say our input is: `Emit` (our shortest pattern, len 4).
// Towards the end just before matching, we will:
//
// iter n: compute hash on `Emi` (len 3)
// iter n+1: compute hash on `Emit` (len 4) using hash from previous iteration (j-1)
// iter n+1: compute hash on `mit` (len 3)
// This overwrites the previous iteration, hence we go from longest to shortest.
//
// LCG comes from a trivial (bad) random number generator,
// but it's sufficient for us - the hashes for our patterns
// are unique, and processing of 2200 files found no false matches.
runningHash[j] = LCG * runningHash[j - 1] + h;
}
runningHash[0] = h;
if (++longestPossiblePattern >= 4)
{
var result = CheckForMatches(runningHash, longestPossiblePattern);
if (result != null) return result;
}
}
return CheckForMatches(runningHash, 0);
}
#if false
// This code can be used when adding a new pattern.
internal static uint HashNewPattern(string pattern)
{
char ToLower(char c)
{
if (c >= 'A' && c <= 'Z')
{
c = (char) (c | 0x20);
}
return c;
}
if (pattern.Length > 29) {
throw new Exception("Update runningHash in match for new longest string.\n" +
"Also a longer maximum length could greatly affect the performance of this algorithm, so only increase with care.");
}
uint h = 0;
foreach (var c in pattern)
{
h = LCG * h + ToLower(c);
}
return h;
}
#endif
}
internal static void LogScriptBlockStart(ScriptBlock scriptBlock, Guid runspaceId)
{
// When invoking, log the creation of the script block if it has suspicious
// content
bool forceLogCreation = false;
if (scriptBlock._scriptBlockData.HasSuspiciousContent)
{
forceLogCreation = true;
}
// We delay logging the creation util the 'Start' so that we can be sure we've
// properly analyzed the script block's security.
LogScriptBlockCreation(scriptBlock, forceLogCreation);
if (GetScriptBlockLoggingSetting()?.EnableScriptBlockInvocationLogging == true)
{
PSEtwLog.LogOperationalVerbose(PSEventId.ScriptBlock_Invoke_Start_Detail, PSOpcode.Create, PSTask.CommandStart, PSKeyword.UseAlwaysOperational,
scriptBlock.Id.ToString(), runspaceId.ToString());
}
}
internal static void LogScriptBlockEnd(ScriptBlock scriptBlock, Guid runspaceId)
{
if (GetScriptBlockLoggingSetting()?.EnableScriptBlockInvocationLogging == true)
{
PSEtwLog.LogOperationalVerbose(PSEventId.ScriptBlock_Invoke_Complete_Detail, PSOpcode.Create, PSTask.CommandStop, PSKeyword.UseAlwaysOperational,
scriptBlock.Id.ToString(), runspaceId.ToString());
}
}
internal CompiledScriptBlockData ScriptBlockData { get { return _scriptBlockData; } }
/// <summary>
/// Returns the AST corresponding to the script block.
/// </summary>
public Ast Ast { get { return (Ast)_scriptBlockData.Ast; } }
internal IParameterMetadataProvider AstInternal { get { return _scriptBlockData.Ast; } }
internal IScriptExtent[] SequencePoints { get { return _scriptBlockData.SequencePoints; } }
internal Action<FunctionContext> DynamicParamBlock { get { return _scriptBlockData.DynamicParamBlock; } }
internal Action<FunctionContext> UnoptimizedDynamicParamBlock { get { return _scriptBlockData.UnoptimizedDynamicParamBlock; } }
internal Action<FunctionContext> BeginBlock { get { return _scriptBlockData.BeginBlock; } }
internal Action<FunctionContext> UnoptimizedBeginBlock { get { return _scriptBlockData.UnoptimizedBeginBlock; } }
internal Action<FunctionContext> ProcessBlock { get { return _scriptBlockData.ProcessBlock; } }
internal Action<FunctionContext> UnoptimizedProcessBlock { get { return _scriptBlockData.UnoptimizedProcessBlock; } }
internal Action<FunctionContext> EndBlock { get { return _scriptBlockData.EndBlock; } }
internal Action<FunctionContext> UnoptimizedEndBlock { get { return _scriptBlockData.UnoptimizedEndBlock; } }
internal bool HasBeginBlock { get { return AstInternal.Body.BeginBlock != null; } }
internal bool HasProcessBlock { get { return AstInternal.Body.ProcessBlock != null; } }
internal bool HasEndBlock { get { return AstInternal.Body.EndBlock != null; } }
}
[Serializable]
internal class ScriptBlockSerializationHelper : ISerializable, IObjectReference
{
private readonly string _scriptText;
private ScriptBlockSerializationHelper(SerializationInfo info, StreamingContext context)
{
if (info == null) throw new ArgumentNullException("info");
_scriptText = info.GetValue("ScriptText", typeof(string)) as string;
if (_scriptText == null)
{
throw PSTraceSource.NewArgumentNullException("info");
}
}
/// <summary>
/// Returns a script block that corresponds to the version deserialized.
/// </summary>
/// <param name="context">The streaming context for this instance.</param>
/// <returns>A script block that corresponds to the version deserialized.</returns>
public object GetRealObject(StreamingContext context)
{
return ScriptBlock.Create(_scriptText);
}
/// <summary>
/// Implements the ISerializable contract for serializing a scriptblock.
/// </summary>
/// <param name="info">Serialization information for this instance.</param>
/// <param name="context">The streaming context for this instance.</param>
public virtual void GetObjectData(SerializationInfo info, StreamingContext context)
{
throw new NotSupportedException();
}
}
internal sealed class PSScriptCmdlet : PSCmdlet, IDynamicParameters, IDisposable
{
private readonly ArrayList _input = new ArrayList();
private readonly ScriptBlock _scriptBlock;
private readonly bool _fromScriptFile;
private readonly bool _useLocalScope;
private readonly bool _runOptimized;
private bool _rethrowExitException;
private MshCommandRuntime _commandRuntime;
private readonly MutableTuple _localsTuple;
private bool _exitWasCalled;
private readonly FunctionContext _functionContext;
public PSScriptCmdlet(ScriptBlock scriptBlock, bool useNewScope, bool fromScriptFile, ExecutionContext context)
{
_scriptBlock = scriptBlock;
_useLocalScope = useNewScope;
_fromScriptFile = fromScriptFile;
_runOptimized = _scriptBlock.Compile(optimized: context._debuggingMode > 0 ? false : useNewScope);
_localsTuple = _scriptBlock.MakeLocalsTuple(_runOptimized);
_localsTuple.SetAutomaticVariable(AutomaticVariable.PSCmdlet, this, context);
_scriptBlock.SetPSScriptRootAndPSCommandPath(_localsTuple, context);
_functionContext = new FunctionContext
{
_localsTuple = _localsTuple,
_scriptBlock = _scriptBlock,
_file = _scriptBlock.File,
_sequencePoints = _scriptBlock.SequencePoints,
_debuggerHidden = _scriptBlock.DebuggerHidden,
_debuggerStepThrough = _scriptBlock.DebuggerStepThrough,
_executionContext = context,
};
_rethrowExitException = context.ScriptCommandProcessorShouldRethrowExit;
context.ScriptCommandProcessorShouldRethrowExit = false;
}
protected override void BeginProcessing()
{
// commandRuntime and the execution context are set here and in GetDynamicParameters. GetDynamicParameters isn't
// called unless the scriptblock has a dynamicparam block, so we must set these values in both places. It'd
// be cleaner to set in the constructor, but neither the commandRuntime nor the context are set when being constructed.
_commandRuntime = (MshCommandRuntime)commandRuntime;
// We don't set the output pipe until after GetDynamicParameters because dynamic parameters aren't written to the
// command processors pipe, but once we enter begin, we will write to the default pipe.
_functionContext._outputPipe = _commandRuntime.OutputPipe;
SetPreferenceVariables();
if (_scriptBlock.HasBeginBlock)
{
RunClause(_runOptimized ? _scriptBlock.BeginBlock : _scriptBlock.UnoptimizedBeginBlock, AutomationNull.Value, _input);
}
}
internal override void DoProcessRecord()
{
if (_exitWasCalled)
{
return;
}
object dollarUnder;
if (CurrentPipelineObject == AutomationNull.Value)
{
dollarUnder = null;
}
else
{
dollarUnder = CurrentPipelineObject;
_input.Add(dollarUnder);
}
if (_scriptBlock.HasProcessBlock)
{
RunClause(_runOptimized ? _scriptBlock.ProcessBlock : _scriptBlock.UnoptimizedProcessBlock, dollarUnder, _input);
_input.Clear();
}
}
internal override void DoEndProcessing()
{
if (_exitWasCalled)
{
return;
}
if (_scriptBlock.HasEndBlock)
{
RunClause(_runOptimized ? _scriptBlock.EndBlock : _scriptBlock.UnoptimizedEndBlock, AutomationNull.Value, _input.ToArray());
}
}
private void EnterScope()
{
_commandRuntime.SetVariableListsInPipe();
}
private void ExitScope()
{
_commandRuntime.RemoveVariableListsInPipe();
}
private void RunClause(Action<FunctionContext> clause, object dollarUnderbar, object inputToProcess)
{
Pipe oldErrorOutputPipe = this.Context.ShellFunctionErrorOutputPipe;
// If the script block has a different language mode than the current,
// change the language mode.
PSLanguageMode? oldLanguageMode = null;
PSLanguageMode? newLanguageMode = null;
if ((_scriptBlock.LanguageMode.HasValue) &&
(_scriptBlock.LanguageMode != Context.LanguageMode))
{
oldLanguageMode = Context.LanguageMode;
newLanguageMode = _scriptBlock.LanguageMode;
}
try
{
try
{
EnterScope();
if (_commandRuntime.ErrorMergeTo == MshCommandRuntime.MergeDataStream.Output)
{
Context.RedirectErrorPipe(_commandRuntime.OutputPipe);
}
else if (_commandRuntime.ErrorOutputPipe.IsRedirected)
{
Context.RedirectErrorPipe(_commandRuntime.ErrorOutputPipe);
}
if (dollarUnderbar != AutomationNull.Value)
{
_localsTuple.SetAutomaticVariable(AutomaticVariable.Underbar, dollarUnderbar, Context);
}
if (inputToProcess != AutomationNull.Value)
{
_localsTuple.SetAutomaticVariable(AutomaticVariable.Input, inputToProcess, Context);
}
// Set the language mode
if (newLanguageMode.HasValue)
{
Context.LanguageMode = newLanguageMode.Value;
}
clause(_functionContext);
}
catch (TargetInvocationException tie)
{
// DynamicInvoke wraps exceptions, unwrap them here.
throw tie.InnerException;
}
finally
{
this.Context.RestoreErrorPipe(oldErrorOutputPipe);
// Set the language mode
if (oldLanguageMode.HasValue)
{
Context.LanguageMode = oldLanguageMode.Value;
}
ExitScope();
}
}
catch (ExitException ee)
{
if (!_fromScriptFile || _rethrowExitException)
{
throw;
}
_exitWasCalled = true;
int exitCode = (int)ee.Argument;
this.Context.SetVariable(SpecialVariables.LastExitCodeVarPath, exitCode);
if (exitCode != 0)
_commandRuntime.PipelineProcessor.ExecutionFailed = true;
}
}
public object GetDynamicParameters()
{
_commandRuntime = (MshCommandRuntime)commandRuntime;
if (_scriptBlock.HasDynamicParameters)
{
var resultList = new List<object>();
Diagnostics.Assert(_functionContext._outputPipe == null, "Output pipe should not be set yet.");
_functionContext._outputPipe = new Pipe(resultList);
RunClause(_runOptimized ? _scriptBlock.DynamicParamBlock : _scriptBlock.UnoptimizedDynamicParamBlock,
AutomationNull.Value, AutomationNull.Value);
if (resultList.Count > 1)
{
throw PSTraceSource.NewInvalidOperationException(AutomationExceptions.DynamicParametersWrongType,
PSObject.ToStringParser(this.Context, resultList));
}
return resultList.Count == 0 ? null : PSObject.Base(resultList[0]);
}
return null;
}
/// <summary>
/// If the script cmdlet will run in a new local scope, this method is used to set the locals to the newly created scope.
/// </summary>
internal void SetLocalsTupleForNewScope(SessionStateScope scope)
{
Diagnostics.Assert(scope.LocalsTuple == null, "a newly created scope shouldn't have it's tuple set.");
scope.LocalsTuple = _localsTuple;
}
/// <summary>
/// If the script cmdlet is dotted, this method is used to push the locals to the 'DottedScopes' of the current scope.
/// </summary>
internal void PushDottedScope(SessionStateScope scope)
{
scope.DottedScopes.Push(_localsTuple);
}
/// <summary>
/// If the script cmdlet is dotted, this method is used to pop the locals from the 'DottedScopes' of the current scope.
/// </summary>
internal void PopDottedScope(SessionStateScope scope)
{
scope.DottedScopes.Pop();
}
internal void PrepareForBinding(CommandLineParameters commandLineParameters)
{
_localsTuple.SetAutomaticVariable(AutomaticVariable.PSBoundParameters,
commandLineParameters.GetValueToBindToPSBoundParameters(), this.Context);
_localsTuple.SetAutomaticVariable(AutomaticVariable.MyInvocation, MyInvocation, this.Context);
}
private void SetPreferenceVariables()
{
if (_commandRuntime.IsDebugFlagSet)
{
_localsTuple.SetPreferenceVariable(PreferenceVariable.Debug,
_commandRuntime.Debug ? ActionPreference.Continue : ActionPreference.SilentlyContinue);
}
if (_commandRuntime.IsVerboseFlagSet)
{
_localsTuple.SetPreferenceVariable(PreferenceVariable.Verbose,
_commandRuntime.Verbose ? ActionPreference.Continue : ActionPreference.SilentlyContinue);
}
if (_commandRuntime.IsErrorActionSet)
{
_localsTuple.SetPreferenceVariable(PreferenceVariable.Error, _commandRuntime.ErrorAction);
}
if (_commandRuntime.IsWarningActionSet)
{
_localsTuple.SetPreferenceVariable(PreferenceVariable.Warning, _commandRuntime.WarningPreference);
}
if (_commandRuntime.IsInformationActionSet)
{
_localsTuple.SetPreferenceVariable(PreferenceVariable.Information, _commandRuntime.InformationPreference);
}
if (_commandRuntime.IsWhatIfFlagSet)
{
_localsTuple.SetPreferenceVariable(PreferenceVariable.WhatIf, _commandRuntime.WhatIf);
}
if (_commandRuntime.IsConfirmFlagSet)
{
_localsTuple.SetPreferenceVariable(PreferenceVariable.Confirm,
_commandRuntime.Confirm ? ConfirmImpact.Low : ConfirmImpact.None);
}
}
#region StopProcessing functionality for script cmdlets
internal event EventHandler StoppingEvent;
protected override void StopProcessing()
{
this.StoppingEvent.SafeInvoke(this, EventArgs.Empty);
base.StopProcessing();
}
#endregion
#region IDispose
private bool _disposed;
internal event EventHandler DisposingEvent;
/// <summary>
/// IDisposable implementation
/// When the command is complete, release the associated scope
/// and other members.
/// </summary>
public void Dispose()
{
if (_disposed)
return;
this.DisposingEvent.SafeInvoke(this, EventArgs.Empty);
commandRuntime = null;
currentObjectInPipeline = null;
_input.Clear();
// _scriptBlock = null;
// _localsTuple = null;
// _functionContext = null;
base.InternalDispose(true);
_disposed = true;
}
#endregion IDispose
}
}