Team Ai
Datasetpublic

MegaBites-AI/Windows-powershell

sourceHugging Facemitupdated 6mo agoView on Hugging Face
0likes308downloads
ScriptBlockToPowerShell.cs954 linesDownload Raw Back to runtime
1// Copyright (c) Microsoft Corporation.2// Licensed under the MIT License.3 4using System.Collections.Generic;5using System.Collections.ObjectModel;6using System.Globalization;7using System.Linq;8using System.Management.Automation.Language;9using System.Management.Automation.Runspaces;10 11namespace System.Management.Automation12{13    // A visitor to walk an AST and validate that it can be converted to PowerShell.14    internal class ScriptBlockToPowerShellChecker : AstVisitor15    {16        private readonly HashSet<string> _validVariables = new HashSet<string>(StringComparer.OrdinalIgnoreCase);17 18        internal ScriptBlockAst ScriptBeingConverted { get; set; }19 20        internal bool UsesParameter { get; private set; }21 22        internal bool HasUsingExpr { get; private set; }23 24        public override AstVisitAction VisitParameter(ParameterAst parameterAst)25        {26            if (parameterAst.Name.VariablePath.IsAnyLocal())27            {28                _validVariables.Add(parameterAst.Name.VariablePath.UnqualifiedPath);29            }30 31            return AstVisitAction.Continue;32        }33 34        public override AstVisitAction VisitPipeline(PipelineAst pipelineAst)35        {36            if (pipelineAst.PipelineElements[0] is CommandExpressionAst)37            {38                // If the first element is a CommandExpression, this pipeline should be the value39                // of a parameter. We want to avoid a scriptblock that contains only a pure expression.40                // The check "pipelineAst.Parent.Parent == ScriptBeingConverted" guarantees we throw41                // error on that kind of scriptblock.42 43                // Disallow pure expressions at the "top" level, but allow them otherwise.44                // We want to catch:45                //     1 | echo46                // But we don't want to error out on:47                //     echo $(1)48                // See the comment in VisitCommand on why it's safe to check Parent.Parent, we49                // know that we have at least:50                //     * a NamedBlockAst (the end block)51                //     * a ScriptBlockAst (the ast we're comparing to)52                if (pipelineAst.GetPureExpression() == null || pipelineAst.Parent.Parent == ScriptBeingConverted)53                {54                    ThrowError(55                        new ScriptBlockToPowerShellNotSupportedException(56                            "CantConvertPipelineStartsWithExpression", null,57                            AutomationExceptions.CantConvertPipelineStartsWithExpression),58                        pipelineAst);59                }60            }61 62            return AstVisitAction.Continue;63        }64 65        public override AstVisitAction VisitCommand(CommandAst commandAst)66        {67            if (commandAst.InvocationOperator == TokenKind.Dot)68            {69                ThrowError(70                    new ScriptBlockToPowerShellNotSupportedException(71                        "CantConvertWithDotSourcing", null, AutomationExceptions.CantConvertWithDotSourcing),72                    commandAst);73            }74 75            // Up front checking ensures that we have a simple script block,76            // so we can safely assume that the parents are:77            //     * a PipelineAst78            //     * a NamedBlockAst (the end block)79            //     * a ScriptBlockAst (the ast we're comparing to)80            // If that isn't the case, the conversion isn't allowed.  It81            // is also safe to assume that we have at least 3 parents, a script block can't be simpler.82            if (commandAst.Parent.Parent.Parent != ScriptBeingConverted)83            {84                ThrowError(85                    new ScriptBlockToPowerShellNotSupportedException(86                        "CantConvertWithCommandInvocations", null, AutomationExceptions.CantConvertWithCommandInvocations),87                    commandAst);88            }89 90            if (commandAst.CommandElements[0] is ScriptBlockExpressionAst)91            {92                ThrowError(93                    new ScriptBlockToPowerShellNotSupportedException(94                        "CantConvertWithScriptBlockInvocation", null,95                        AutomationExceptions.CantConvertWithScriptBlockInvocation),96                    commandAst);97            }98 99            return AstVisitAction.Continue;100        }101 102        public override AstVisitAction VisitMergingRedirection(MergingRedirectionAst redirectionAst)103        {104            if (redirectionAst.ToStream != RedirectionStream.Output)105            {106                ThrowError(107                    new ScriptBlockToPowerShellNotSupportedException(108                        "CanConvertOneOutputErrorRedir", null, AutomationExceptions.CanConvertOneOutputErrorRedir),109                    redirectionAst);110            }111 112            return AstVisitAction.Continue;113        }114 115        public override AstVisitAction VisitFileRedirection(FileRedirectionAst redirectionAst)116        {117            ThrowError(118                new ScriptBlockToPowerShellNotSupportedException(119                    "CanConvertOneOutputErrorRedir", null, AutomationExceptions.CanConvertOneOutputErrorRedir),120                redirectionAst);121 122            return AstVisitAction.Continue;123        }124 125        public override AstVisitAction VisitVariableExpression(VariableExpressionAst variableExpressionAst)126        {127            bool usesParameterReference = this.UsesParameter;128            bool ok = variableExpressionAst.IsSafeVariableReference(_validVariables, ref usesParameterReference);129            if (usesParameterReference != this.UsesParameter)130            {131                this.UsesParameter = usesParameterReference;132            }133 134            if (!ok)135            {136                ThrowError(new ScriptBlockToPowerShellNotSupportedException(137                               "CantConvertWithUndeclaredVariables",138                               null,139                               AutomationExceptions.CantConvertWithUndeclaredVariables,140                               variableExpressionAst.VariablePath),141                           variableExpressionAst);142            }143 144            return AstVisitAction.Continue;145        }146 147        public override AstVisitAction VisitScriptBlockExpression(ScriptBlockExpressionAst scriptBlockExpressionAst)148        {149            ThrowError(new ScriptBlockToPowerShellNotSupportedException(150                           "CantConvertWithScriptBlocks",151                           null,152                           AutomationExceptions.CantConvertWithScriptBlocks),153                       scriptBlockExpressionAst);154 155            return AstVisitAction.SkipChildren;156        }157 158        public override AstVisitAction VisitUsingExpression(UsingExpressionAst usingExpressionAst)159        {160            // A using expression is always allowed, it simply gets re-written to be a parameter161            HasUsingExpr = true;162 163            // Skip the children - the expression is evaluated before sending to the remote machine,164            // so it doesn't matter what we might find in the children.165            return AstVisitAction.SkipChildren;166        }167 168        internal static void ThrowError(ScriptBlockToPowerShellNotSupportedException ex, Ast ast)169        {170            InterpreterError.UpdateExceptionErrorRecordPosition(ex, ast.Extent);171            throw ex;172        }173    }174 175    internal sealed class UsingExpressionAstSearcher : AstSearcher176    {177        internal static IEnumerable<Ast> FindAllUsingExpressions(Ast ast)178        {179            Diagnostics.Assert(ast != null, "caller to verify arguments");180 181            var searcher = new UsingExpressionAstSearcher(182                callback: astParam => astParam is UsingExpressionAst,183                stopOnFirst: false,184                searchNestedScriptBlocks: true);185            ast.InternalVisit(searcher);186            return searcher.Results;187        }188 189        private UsingExpressionAstSearcher(Func<Ast, bool> callback, bool stopOnFirst, bool searchNestedScriptBlocks)190            : base(callback, stopOnFirst, searchNestedScriptBlocks)191        {192        }193 194        public override AstVisitAction VisitFunctionDefinition(FunctionDefinitionAst ast)195        {196            // Skip the workflow. We are not interested in the UsingExpressions in a workflow197            if (ast.IsWorkflow)198            {199                return AstVisitAction.SkipChildren;200            }201 202            return CheckScriptBlock(ast);203        }204    }205 206    /// <summary>207    /// Converts a ScriptBlock to a PowerShell object by traversing the208    /// given Ast.209    /// </summary>210    internal sealed class ScriptBlockToPowerShellConverter211    {212        private readonly PowerShell _powershell;213        private ExecutionContext _context;214        private Dictionary<string, object> _usingValueMap;215        private bool? _createLocalScope;216 217        private ScriptBlockToPowerShellConverter()218        {219            _powershell = PowerShell.Create();220        }221 222        internal static PowerShell Convert(ScriptBlockAst body,223                                           ReadOnlyCollection<ParameterAst> functionParameters,224                                           bool isTrustedInput,225                                           ExecutionContext context,226                                           Dictionary<string, object> variables,227                                           bool filterNonUsingVariables,228                                           bool? createLocalScope,229                                           object[] args)230        {231            ExecutionContext.CheckStackDepth();232 233            args ??= Array.Empty<object>();234 235            // Perform validations on the ScriptBlock.  GetSimplePipeline can allow for more than one236            // pipeline if the first parameter is true, but Invoke-Command doesn't yet support multiple237            // pipelines in a PowerShell (it just grabs the last command directly.)  The rest of this238            // code properly supports multiple pipelines, so it should just work to change the false to true239            // if/when Invoke-Command can support multiple pipelines.240            string errorId;241            string errorMsg;242            body.GetSimplePipeline(true, out errorId, out errorMsg);243            if (errorId != null)244            {245                throw new ScriptBlockToPowerShellNotSupportedException(errorId, null, errorMsg);246            }247 248            var checker = new ScriptBlockToPowerShellChecker { ScriptBeingConverted = body };249            if (functionParameters != null)250            {251                foreach (var parameter in functionParameters)252                {253                    parameter.InternalVisit(checker);254                }255            }256 257            body.InternalVisit(checker);258 259            // When the context is null (or they haven't supplied any variables), throw, but only if we really need the260            // context (basically, if we have some variable reference to resolve).261            if (context == null && (checker.HasUsingExpr || checker.UsesParameter) && (variables == null))262            {263                throw new PSInvalidOperationException(AutomationExceptions.CantConvertScriptBlockWithNoContext);264            }265 266            try267            {268                var converter = new ScriptBlockToPowerShellConverter { _context = context, _createLocalScope = createLocalScope };269 270                if (checker.HasUsingExpr)271                {272                    converter._usingValueMap = GetUsingValues(body, isTrustedInput, context, variables, filterNonUsingVariables).Item1;273                }274 275                if (checker.UsesParameter)276                {277                    // If any parameters are used, we create a new scope and bind the parameters.278 279                    var newScope = context.EngineSessionState.NewScope(false);280                    context.EngineSessionState.CurrentScope = newScope;281                    context.EngineSessionState.CurrentScope.ScopeOrigin = CommandOrigin.Internal;282 283                    var locals =284                        MutableTuple.MakeTuple(Compiler.DottedLocalsTupleType, Compiler.DottedLocalsNameIndexMap);285 286                    // Get the parameter metadata for the script block.287                    // If 'functionParameters' is not null, then the ScriptBlockAst is actually the body of a FunctionDefinitionAst, and it doesn't have a ParamBlock.288                    // If 'functionParameters' is null, then the ScriptBlockAst may have parameters defined in its ParamBlock.289                    bool usesCmdletBinding = false;290                    var parameters = functionParameters != null291                                         ? Compiler.GetParameterMetaData(functionParameters, true, ref usesCmdletBinding)292                                         : ((IParameterMetadataProvider)body).GetParameterMetadata(true, ref usesCmdletBinding);293                    object[] remainingArgs = ScriptBlock.BindArgumentsForScriptblockInvoke(294                        (RuntimeDefinedParameter[])parameters.Data, args, context, false, null, locals);295                    locals.SetAutomaticVariable(AutomaticVariable.Args, remainingArgs, context);296                    newScope.LocalsTuple = locals;297                }298 299                foreach (var pipeline in body.EndBlock.Statements.OfType<PipelineAst>())300                {301                    converter._powershell.AddStatement();302                    converter.ConvertPipeline(pipeline, isTrustedInput);303                }304 305                return converter._powershell;306            }307            finally308            {309                if (checker.UsesParameter)310                {311                    context.EngineSessionState.RemoveScope(context.EngineSessionState.CurrentScope);312                }313            }314        }315 316        /// <summary>317        /// Get using values as dictionary for the Foreach-Object parallel cmdlet.318        /// Ignore any using expressions that are associated with inner nested Foreach-Object parallel calls,319        /// since they are only effective in the nested call scope and not the current outer scope.320        /// </summary>321        /// <param name = "scriptBlock">Scriptblock to search.</param>322        /// <param name = "isTrustedInput">True when input is trusted.</param>323        /// <param name = "context">Execution context.</param>324        /// <returns>Dictionary of using variable map.</returns>325        internal static Dictionary<string, object> GetUsingValuesForEachParallel(326            ScriptBlock scriptBlock,327            bool isTrustedInput,328            ExecutionContext context)329        {330            // Using variables for Foreach-Object -Parallel use are restricted to be within the331            // Foreach-Object -Parallel call scope. This will filter the using variable map to variables332            // only within the current (outer) Foreach-Object -Parallel call scope.333            var usingAsts = UsingExpressionAstSearcher.FindAllUsingExpressions(scriptBlock.Ast).ToList();334            UsingExpressionAst usingAst = null;335            var usingValueMap = new Dictionary<string, object>(usingAsts.Count);336            Version oldStrictVersion = null;337            try338            {339                if (context != null)340                {341                    oldStrictVersion = context.EngineSessionState.CurrentScope.StrictModeVersion;342                    context.EngineSessionState.CurrentScope.StrictModeVersion = PSVersionInfo.PSVersion;343                }344 345                for (int i = 0; i < usingAsts.Count; ++i)346                {347                    usingAst = (UsingExpressionAst)usingAsts[i];348                    if (IsInForeachParallelCallingScope(scriptBlock.Ast, usingAst))349                    {350                        var value = Compiler.GetExpressionValue(usingAst.SubExpression, isTrustedInput, context);351                        string usingAstKey = PsUtils.GetUsingExpressionKey(usingAst);352                        usingValueMap.TryAdd(usingAstKey, value);353                    }354                }355            }356            catch (RuntimeException rte)357            {358                if (rte.ErrorRecord.FullyQualifiedErrorId.Equals("VariableIsUndefined", StringComparison.Ordinal))359                {360                    throw InterpreterError.NewInterpreterException(361                        targetObject: null,362                        exceptionType: typeof(RuntimeException),363                        errorPosition: usingAst.Extent,364                        resourceIdAndErrorId: "UsingVariableIsUndefined",365                        resourceString: AutomationExceptions.UsingVariableIsUndefined,366                        args: rte.ErrorRecord.TargetObject);367                }368            }369            finally370            {371                if (context != null)372                {373                    context.EngineSessionState.CurrentScope.StrictModeVersion = oldStrictVersion;374                }375            }376 377            return usingValueMap;378        }379 380        // List of Foreach-Object command names and aliases.381        // TODO: Look into using SessionState.Internal.GetAliasTable() to find all user created aliases.382        //       But update Alias command logic to maintain reverse table that lists all aliases mapping383        //       to a single command definition, for performance.384        private static readonly string[] forEachNames = new string[]385        {386            "ForEach-Object",387            "foreach",388            "%"389        };390 391        private static bool FindForEachInCommand(CommandAst commandAst)392        {393            // Command name is always the first element in the CommandAst.394            //  e.g., 'foreach -parallel {}'395            var commandNameElement = (commandAst.CommandElements.Count > 0) ? commandAst.CommandElements[0] : null;396            if (commandNameElement is StringConstantExpressionAst commandName)397            {398                bool found = false;399                foreach (var foreachName in forEachNames)400                {401                    if (commandName.Value.Equals(foreachName, StringComparison.OrdinalIgnoreCase))402                    {403                        found = true;404                        break;405                    }406                }407 408                if (found)409                {410                    // Verify this is foreach-object with parallel parameter set.411                    var bindingResult = StaticParameterBinder.BindCommand(commandAst);412                    if (bindingResult.BoundParameters.ContainsKey("Parallel"))413                    {414                        return true;415                    }416                }417            }418 419            return false;420        }421 422        /// <summary>423        /// Walks the using Ast to verify it is used within a foreach-object -parallel command424        /// and parameter set scope, and not from within a nested foreach-object -parallel call.425        /// </summary>426        /// <param name="scriptblockAst">Scriptblock Ast containing this using Ast</param>427        /// <param name="usingAst">Using Ast to check.</param>428        /// <returns>True if using expression is in current call scope.</returns>429        private static bool IsInForeachParallelCallingScope(430            Ast scriptblockAst,431            UsingExpressionAst usingAst)432        {433            Diagnostics.Assert(usingAst != null, "usingAst argument cannot be null.");434 435            /*436                Example:437                $Test1 = "Hello"438                1 | ForEach-Object -Parallel {439                   $using:Test1440                   $Test2 = "Goodbye"441                   1 | ForEach-Object -Parallel {442                       $using:Test1    # Invalid using scope443                       $using:Test2    # Valid using scope444                   }445                }446            */447 448            // Search up the parent Ast chain for 'Foreach-Object -Parallel' commands.449            Ast currentParent = usingAst.Parent;450            while (currentParent != scriptblockAst)451            {452                // Look for Foreach-Object outer commands453                if (currentParent is CommandAst commandAst &&454                    FindForEachInCommand(commandAst))455                {456                    // Using Ast is outside the invoking foreach scope.457                    return false;458                }459 460                currentParent = currentParent.Parent;461            }462 463            return true;464        }465 466        /// <summary>467        /// Get using values in the dictionary form.468        /// </summary>469        internal static Dictionary<string, object> GetUsingValuesAsDictionary(ScriptBlock scriptBlock, bool isTrustedInput, ExecutionContext context, Dictionary<string, object> variables)470        {471            return GetUsingValues(scriptBlock.Ast, isTrustedInput, context, variables, false).Item1;472        }473 474        /// <summary>475        /// Get using values in the array form.476        /// </summary>477        internal static object[] GetUsingValuesAsArray(ScriptBlock scriptBlock, bool isTrustedInput, ExecutionContext context, Dictionary<string, object> variables)478        {479            return GetUsingValues(scriptBlock.Ast, isTrustedInput, context, variables, false).Item2;480        }481 482        /// <summary>483        /// Collect values for UsingExpressions, in the form of a dictionary and an array.484        ///  - The dictionary form is used when the remote server is PSv5 and later version for handling UsingExpression in Invoke-Command/Start-Job485        ///  - The array form is used when the remote server is PSv3 and PSv4 for handling UsingExpression in Invoke-Command.486        /// </summary>487        /// <remarks>488        /// We still keep the array-form using values because we want to avoid any breaking changes when running Invoke-Command489        /// targeting PSv3 or PSv4 remote end -- if UsingExpressions are all in the same scope, then we still pass an array of using490        /// values to the remote end; otherwise, we will handle UsingExpression as if the remote end is PSv2.491        /// </remarks>492        /// <returns>493        /// A tuple of the dictionary-form and the array-form using values.494        /// If the array-form using value is null, then there are UsingExpressions used in different scopes.495        /// </returns>496        private static Tuple<Dictionary<string, object>, object[]> GetUsingValues(497            Ast body,498            bool isTrustedInput,499            ExecutionContext context,500            Dictionary<string, object> variables,501            bool filterNonUsingVariables)502        {503            Diagnostics.Assert(context != null || variables != null, "can't retrieve variables with no context and no variables");504 505            var usingAsts = UsingExpressionAstSearcher.FindAllUsingExpressions(body).ToList();506            var usingValueArray = new object[usingAsts.Count];507            var usingValueMap = new Dictionary<string, object>(usingAsts.Count);508            HashSet<string> usingVarNames = (variables != null && filterNonUsingVariables) ? new HashSet<string>() : null;509 510            // Used to check if the PSv3/PSv4 way of handling UsingExpression can continue to be used.511            bool hasUsingExprInDifferentScope = false;512            ScriptBlockAst sbClosestToPreUsingExpr = null;513 514            UsingExpressionAst usingAst = null;515            Version oldStrictVersion = null;516            try517            {518                if (context != null)519                {520                    oldStrictVersion = context.EngineSessionState.CurrentScope.StrictModeVersion;521                    context.EngineSessionState.CurrentScope.StrictModeVersion = PSVersionInfo.PSVersion;522                }523 524                for (int i = 0; i < usingAsts.Count; ++i)525                {526                    usingAst = (UsingExpressionAst)usingAsts[i];527                    object value = null;528 529                    // This happens only when GetUsingValues gets called outside the ScriptBlockToPowerShellConverter class530                    if (!hasUsingExprInDifferentScope && HasUsingExpressionsInDifferentScopes(usingAst, body, ref sbClosestToPreUsingExpr))531                    {532                        // If there are UsingExpressions in different scopes, the array-form using values will not be useful533                        // even if the remote end is PSv3 or PSv4, because the way we handle using expression in PSv3 and PSv4534                        // doesn't support UsingExpression in different scopes. In this case, we will set the array-form using535                        // value to be null before return.536                        //537                        // Note that this check only affect array-form using value. In PSv5, we change the way to handle UsingExpression538                        // on both client and server sides. The dictionary-form using values is used and UsingExpression in different539                        // scope is supported.540                        hasUsingExprInDifferentScope = true;541                    }542 543                    if (variables != null)544                    {545                        if (usingAst.SubExpression is not VariableExpressionAst variableAst)546                        {547                            throw InterpreterError.NewInterpreterException(null, typeof(RuntimeException),548                                usingAst.Extent, "CantGetUsingExpressionValueWithSpecifiedVariableDictionary", AutomationExceptions.CantGetUsingExpressionValueWithSpecifiedVariableDictionary, usingAst.Extent.Text);549                        }550 551                        string varName = variableAst.VariablePath.UserPath;552                        if (varName != null && variables.TryGetValue(varName, out value) && usingVarNames != null)553                        {554                            usingVarNames.Add(varName);555                        }556                    }557                    else558                    {559                        value = Compiler.GetExpressionValue(usingAst.SubExpression, isTrustedInput, context);560                    }561 562                    // Collect UsingExpression value as an array563                    usingValueArray[i] = value;564 565                    // Collect UsingExpression value as a dictionary566                    string usingAstKey = PsUtils.GetUsingExpressionKey(usingAst);567                    usingValueMap.TryAdd(usingAstKey, value);568                }569            }570            catch (RuntimeException rte)571            {572                if (rte.ErrorRecord.FullyQualifiedErrorId.Equals("VariableIsUndefined", StringComparison.Ordinal))573                {574                    throw InterpreterError.NewInterpreterException(null, typeof(RuntimeException),575                        usingAst.Extent, "UsingVariableIsUndefined", AutomationExceptions.UsingVariableIsUndefined, rte.ErrorRecord.TargetObject);576                }577                else if (rte.ErrorRecord.FullyQualifiedErrorId.Equals("CantGetUsingExpressionValueWithSpecifiedVariableDictionary", StringComparison.Ordinal))578                {579                    throw;580                }581            }582            finally583            {584                if (context != null)585                {586                    context.EngineSessionState.CurrentScope.StrictModeVersion = oldStrictVersion;587                }588            }589 590            if (usingVarNames != null)591            {592                string[] keys = variables.Keys.ToArray();593                foreach (string key in keys)594                {595                    if (!usingVarNames.Contains(key))596                    {597                        variables.Remove(key);598                    }599                }600            }601 602            if (hasUsingExprInDifferentScope)603            {604                usingValueArray = null;605            }606 607            return Tuple.Create(usingValueMap, usingValueArray);608        }609 610        /// <summary>611        /// Check if the given UsingExpression is in a different scope from the previous UsingExpression that we analyzed.612        /// </summary>613        /// <remarks>614        /// Note that the value of <paramref name="usingExpr"/> is retrieved by calling 'UsingExpressionAstSearcher.FindAllUsingExpressions'.615        /// So <paramref name="usingExpr"/> is guaranteed not inside a workflow.616        /// </remarks>617        /// <param name="usingExpr">The UsingExpression to analyze.</param>618        /// <param name="topLevelParent">The top level Ast, should be either ScriptBlockAst or FunctionDefinitionAst.</param>619        /// <param name="sbClosestToPreviousUsingExpr">The ScriptBlockAst that represents the scope of the previously analyzed UsingExpressions.</param>620        private static bool HasUsingExpressionsInDifferentScopes(UsingExpressionAst usingExpr, Ast topLevelParent, ref ScriptBlockAst sbClosestToPreviousUsingExpr)621        {622            Diagnostics.Assert(topLevelParent is ScriptBlockAst || topLevelParent is FunctionDefinitionAst,623                "the top level parent should be either a ScriptBlockAst or FunctionDefinitionAst");624 625            // Scan up the parents of a UsingExpression to check if it's in a nested function/filter/ScriptBlock626            Ast current = usingExpr;627            do628            {629                current = current.Parent;630 631                var sbAst = current as ScriptBlockAst;632                if (sbAst != null)633                {634                    // We find the closest parent ScriptBlockAst of the current UsingExpression, which represents the scope635                    // that the current UsingExpression is in.636 637                    if (sbClosestToPreviousUsingExpr == null)638                    {639                        // The current UsingExpression is the first one to be analyzed.640                        sbClosestToPreviousUsingExpr = sbAst;641                        return false;642                    }643 644                    if (sbAst == sbClosestToPreviousUsingExpr)645                    {646                        // The current UsingExpression is in the same scope as the previous UsingExpression we analyzed.647                        return false;648                    }649 650                    // The current UsingExpression is in a different scope from the previous UsingExpression we analyzed.651                    return true;652                }653 654                var funcAst = current as FunctionDefinitionAst;655                if (funcAst != null)656                {657                    // The parent chain of the current UsingExpression reaches a FunctionDefinitionAst, then the UsingExpression658                    // must be in 'Parameters' property of this FunctionDefinitionAst.659                    // In this case, the 'Body' of this FunctionDefinitionAst represents the scope that the UsingExpression is in.660 661                    if (sbClosestToPreviousUsingExpr == null)662                    {663                        // The current UsingExpression is the first one to be analyzed.664                        sbClosestToPreviousUsingExpr = funcAst.Body;665                        return false;666                    }667 668                    if (funcAst.Body == sbClosestToPreviousUsingExpr)669                    {670                        // The current UsingExpression is in the same scope as the previous UsingExpression we analyzed.671                        return false;672                    }673 674                    // The current UsingExpression is in a different scope from the previous UsingExpression we analyzed.675                    return true;676                }677            } while (current != topLevelParent);678 679            Diagnostics.Assert(false, "Unreachable Code. Top level parent is eitehr ScriptBlockAst or FunctionDefinitionAst, so it should return within the loop for sure.");680            // I don't think it's reachable, but if it happens, just assume there are UsingExpressions in different scopes.681            return true;682        }683 684        private void ConvertPipeline(PipelineAst pipelineAst, bool isTrustedInput)685        {686            foreach (var command in pipelineAst.PipelineElements)687            {688                ConvertCommand((CommandAst)command, isTrustedInput);689            }690        }691 692        private void ConvertCommand(CommandAst commandAst, bool isTrustedInput)693        {694            // First need command name.695            var commandName = GetCommandName(commandAst.CommandElements[0], isTrustedInput);696 697            var command = new Command(commandName, isScript: false, useLocalScope: _createLocalScope);698 699            // Handle redirections, if any (there can really be just 0 or 1).700            if (commandAst.Redirections.Count > 0)701            {702                Diagnostics.Assert(commandAst.Redirections.Count == 1, "only 1 kind of redirection is supported");703                Diagnostics.Assert(commandAst.Redirections[0] is MergingRedirectionAst, "unexpected redirection type");704 705                PipelineResultTypes fromType;706                switch (commandAst.Redirections[0].FromStream)707                {708                    case RedirectionStream.Error:709                        fromType = PipelineResultTypes.Error;710                        break;711 712                    case RedirectionStream.Warning:713                        fromType = PipelineResultTypes.Warning;714                        break;715 716                    case RedirectionStream.Verbose:717                        fromType = PipelineResultTypes.Verbose;718                        break;719 720                    case RedirectionStream.Debug:721                        fromType = PipelineResultTypes.Debug;722                        break;723 724                    case RedirectionStream.Information:725                        fromType = PipelineResultTypes.Information;726                        break;727 728                    case RedirectionStream.All:729                        fromType = PipelineResultTypes.All;730                        break;731 732                    default:733                        // Default to Error->Output to be compatible with V2.734                        fromType = PipelineResultTypes.Error;735                        break;736                }737 738                command.MergeMyResults(fromType, toResult: PipelineResultTypes.Output);739            }740 741            _powershell.AddCommand(command);742 743            // Now the parameters and arguments.744            foreach (var ast in commandAst.CommandElements.Skip(1))745            {746                var exprAst = ast as ExpressionAst;747                if (exprAst != null)748                {749                    VariableExpressionAst variableAst = null;750 751                    var usingExprAst = ast as UsingExpressionAst;752                    if (usingExprAst != null)753                    {754                        string usingAstKey = PsUtils.GetUsingExpressionKey(usingExprAst);755                        object usingValue = _usingValueMap[usingAstKey];756                        variableAst = usingExprAst.SubExpression as VariableExpressionAst;757                        if (variableAst != null && variableAst.Splatted)758                        {759                            // Support the splatting of a dictionary760                            var parameters = usingValue as System.Collections.IDictionary;761                            if (parameters != null)762                            {763                                _powershell.AddParameters(parameters);764                            }765                            else766                            {767                                // Support the splatting of an array768                                var arguments = usingValue as System.Collections.IEnumerable;769                                if (arguments != null)770                                {771                                    foreach (object argument in arguments)772                                    {773                                        _powershell.AddArgument(argument);774                                    }775                                }776                                else777                                {778                                    // Splat the object directly.779                                    _powershell.AddArgument(usingValue);780                                }781                            }782                        }783                        else784                        {785                            _powershell.AddArgument(usingValue);786                        }787 788                        continue;789                    }790 791                    variableAst = ast as VariableExpressionAst;792                    if (variableAst != null && variableAst.Splatted)793                    {794                        GetSplattedVariable(variableAst);795                    }796                    else797                    {798                        var constantExprAst = ast as ConstantExpressionAst;799                        object argument;800                        if (constantExprAst != null801                            && (LanguagePrimitives.IsNumeric(LanguagePrimitives.GetTypeCode(constantExprAst.StaticType))802                            || constantExprAst.StaticType == typeof(System.Numerics.BigInteger)))803                        {804                            var commandArgumentText = constantExprAst.Extent.Text;805                            argument = constantExprAst.Value;806                            if (!commandArgumentText.Equals(constantExprAst.Value.ToString(), StringComparison.Ordinal))807                            {808                                // The wrapped number will actually return a PSObject which could end holding a reference to809                                // a typetable, making the object runspace specific.  We should find a better way to avoid810                                // any possibility of sharing problems, though this is unlikely to cause problems.811                                argument = ParserOps.WrappedNumber(argument, commandArgumentText);812                            }813                        }814                        else815                        {816                            if (!isTrustedInput)817                            {818                                try819                                {820                                    argument = GetSafeValueVisitor.GetSafeValue(exprAst, _context, GetSafeValueVisitor.SafeValueContext.GetPowerShell);821                                }822                                catch (System.Exception)823                                {824                                    throw new ScriptBlockToPowerShellNotSupportedException(825                                        "CantConvertWithDynamicExpression",826                                        null,827                                        AutomationExceptions.CantConvertWithDynamicExpression,828                                        exprAst.Extent.Text);829                                }830                            }831                            else832                            {833                                argument = GetExpressionValue(exprAst, isTrustedInput);834                            }835                        }836 837                        _powershell.AddArgument(argument);838                    }839                }840                else841                {842                    AddParameter((CommandParameterAst)ast, isTrustedInput);843                }844            }845        }846 847        private string GetCommandName(CommandElementAst commandNameAst, bool isTrustedInput)848        {849            var exprAst = commandNameAst as ExpressionAst;850            string commandName;851            if (exprAst != null)852            {853                var value = GetExpressionValue(exprAst, isTrustedInput);854                if (value == null)855                {856                    ScriptBlockToPowerShellChecker.ThrowError(857                        new ScriptBlockToPowerShellNotSupportedException(858                            "CantConvertWithScriptBlockInvocation", null, AutomationExceptions.CantConvertWithScriptBlockInvocation),859                        exprAst);860                }861 862                if (value is CommandInfo)863                {864                    commandName = ((CommandInfo)value).Name;865                }866                else867                {868                    commandName = value as string;869                }870            }871            else872            {873                // If this assertion fires, the command name is determined incorrectly.874                Diagnostics.Assert(commandNameAst is CommandParameterAst, "Unexpected element not handled correctly.");875                commandName = commandNameAst.Extent.Text;876            }877 878            if (string.IsNullOrWhiteSpace(commandName))879            {880                // TODO: could use a better error here881                throw new ScriptBlockToPowerShellNotSupportedException(882                    "CantConvertWithScriptBlockInvocation",883                    null,884                    AutomationExceptions.CantConvertWithScriptBlockInvocation);885            }886 887            return commandName;888        }889 890        private void GetSplattedVariable(VariableExpressionAst variableAst)891        {892            if (_context == null)893            {894                throw new PSInvalidOperationException(AutomationExceptions.CantConvertScriptBlockWithNoContext);895            }896 897            // Process the contents of a splatted variable into the arguments for this898            // command. If the variable contains a hashtable, distribute the key/value pairs899            // If it's an enumerable, then distribute the values as $args and finally900            // if it's a scalar, then the effect is equivalent to $var901            object splattedValue = _context.GetVariableValue(variableAst.VariablePath);902            foreach (var splattedParameter in PipelineOps.Splat(splattedValue, variableAst))903            {904                CommandParameter publicParameter = CommandParameter.FromCommandParameterInternal(splattedParameter);905                _powershell.AddParameter(publicParameter);906            }907        }908 909        private object GetExpressionValue(ExpressionAst exprAst, bool isTrustedInput)910        {911            // be sure that there's a context at hand912            if (_context == null)913            {914                var rs = RunspaceFactory.CreateRunspace(InitialSessionState.Create());915                rs.Open();916                _context = rs.ExecutionContext;917            }918 919            if (!isTrustedInput) // if it's not trusted, call the safe value visitor920            {921                return GetSafeValueVisitor.GetSafeValue(exprAst, _context, GetSafeValueVisitor.SafeValueContext.GetPowerShell);922            }923 924            return Compiler.GetExpressionValue(exprAst, isTrustedInput, _context, _usingValueMap);925        }926 927        private void AddParameter(CommandParameterAst commandParameterAst, bool isTrustedInput)928        {929            string nameSuffix;930            object argument;931            if (commandParameterAst.Argument != null)932            {933                var arg = commandParameterAst.Argument;934                var errorPos = commandParameterAst.ErrorPosition;935                bool spaceAfterParameter = (errorPos.EndLineNumber != arg.Extent.StartLineNumber ||936                                            errorPos.EndColumnNumber != arg.Extent.StartColumnNumber);937                nameSuffix = spaceAfterParameter ? ": " : ":";938 939                argument = GetExpressionValue(commandParameterAst.Argument, isTrustedInput);940            }941            else942            {943                nameSuffix = string.Empty;944                argument = null;945            }946 947            // first character in parameter name must be a dash948            _powershell.AddParameter(949                string.Create(CultureInfo.InvariantCulture, $"-{commandParameterAst.ParameterName}{nameSuffix}"),950                argument);951        }952    }953}954