private object GetExpressionValue(ExpressionAst exprAst, bool isTrustedInput) { // be sure that there's a context at hand if (_context == null) { var rs = RunspaceFactory.CreateRunspace(InitialSessionState.Create()); rs.Open(); _context = rs.ExecutionContext; } if (!isTrustedInput) // if it's not trusted, call the safe value visitor { return(GetSafeValueVisitor.GetSafeValue(exprAst, _context, GetSafeValueVisitor.SafeValueContext.GetPowerShell)); } return(Compiler.GetExpressionValue(exprAst, isTrustedInput, _context, _usingValueMap)); }
private void ConvertCommand(CommandAst commandAst, bool isTrustedInput) { // First need command name. var commandName = GetCommandName(commandAst.CommandElements[0], isTrustedInput); var command = new Command(commandName, isScript: false, useLocalScope: _createLocalScope); // Handle redirections, if any (there can really be just 0 or 1). if (commandAst.Redirections.Count > 0) { Diagnostics.Assert(commandAst.Redirections.Count == 1, "only 1 kind of redirection is supported"); Diagnostics.Assert(commandAst.Redirections[0] is MergingRedirectionAst, "unexpected redirection type"); PipelineResultTypes toType = PipelineResultTypes.Output; PipelineResultTypes fromType; switch (commandAst.Redirections[0].FromStream) { case RedirectionStream.Error: fromType = PipelineResultTypes.Error; break; case RedirectionStream.Warning: fromType = PipelineResultTypes.Warning; break; case RedirectionStream.Verbose: fromType = PipelineResultTypes.Verbose; break; case RedirectionStream.Debug: fromType = PipelineResultTypes.Debug; break; case RedirectionStream.Information: fromType = PipelineResultTypes.Information; break; case RedirectionStream.All: fromType = PipelineResultTypes.All; break; default: // Default to Error->Output to be compatible with V2. fromType = PipelineResultTypes.Error; break; } command.MergeMyResults(fromType, toType); } _powershell.AddCommand(command); // Now the parameters and arguments. foreach (var ast in commandAst.CommandElements.Skip(1)) { var exprAst = ast as ExpressionAst; if (exprAst != null) { VariableExpressionAst variableAst = null; var usingExprAst = ast as UsingExpressionAst; if (usingExprAst != null) { string usingAstKey = PsUtils.GetUsingExpressionKey(usingExprAst); object usingValue = _usingValueMap[usingAstKey]; variableAst = usingExprAst.SubExpression as VariableExpressionAst; if (variableAst != null && variableAst.Splatted) { // Support the splatting of a dictionary var parameters = usingValue as System.Collections.IDictionary; if (parameters != null) { _powershell.AddParameters(parameters); } else { // Support the splatting of an array var arguments = usingValue as System.Collections.IEnumerable; if (arguments != null) { foreach (object argument in arguments) { _powershell.AddArgument(argument); } } else { // Splat the object directly. _powershell.AddArgument(usingValue); } } } else { _powershell.AddArgument(usingValue); } continue; } variableAst = ast as VariableExpressionAst; if (variableAst != null && variableAst.Splatted) { GetSplattedVariable(variableAst); } else { var constantExprAst = ast as ConstantExpressionAst; object argument; if (constantExprAst != null && LanguagePrimitives.IsNumeric(LanguagePrimitives.GetTypeCode(constantExprAst.StaticType))) { var commandArgumentText = constantExprAst.Extent.Text; argument = constantExprAst.Value; if (!commandArgumentText.Equals(constantExprAst.Value.ToString(), StringComparison.Ordinal)) { // The wrapped number will actually return a PSObject which could end holding a reference to // a typetable, making the object runspace specific. We should find a better way to avoid // any possibility of sharing problems, though this is unlikely to cause problems. argument = ParserOps.WrappedNumber(argument, commandArgumentText); } } else { if (!isTrustedInput) { try { argument = GetSafeValueVisitor.GetSafeValue(exprAst, _context, GetSafeValueVisitor.SafeValueContext.GetPowerShell); } catch (System.Exception) { throw new ScriptBlockToPowerShellNotSupportedException( "CantConvertWithDynamicExpression", null, AutomationExceptions.CantConvertWithDynamicExpression, exprAst.Extent.Text); } } else { argument = GetExpressionValue(exprAst, isTrustedInput); } } _powershell.AddArgument(argument); } } else { AddParameter((CommandParameterAst)ast, isTrustedInput); } } }
// This method does parameter binding for a very limited set of scenarios, specifically // for New-Alias, Set-Alias, Import-Module, and Export-ModuleMember. It might not even // correctly handle these cmdlets if new parameters are added. // // It also only populates the bound parameters for a limited set of parameters needed // for module analysis. private Hashtable DoPsuedoParameterBinding(CommandAst commandAst, string commandName) { var result = new Hashtable(StringComparer.OrdinalIgnoreCase); var parameterBindingInfo = s_parameterBindingInfoTable[commandName].parameterInfo; int positionsBound = 0; for (int i = 1; i < commandAst.CommandElements.Count; i++) { var element = commandAst.CommandElements[i]; var specifiedParameter = element as CommandParameterAst; if (specifiedParameter != null) { bool boundParameter = false; var specifiedParamName = specifiedParameter.ParameterName; foreach (var parameterInfo in parameterBindingInfo) { if (parameterInfo.name.StartsWith(specifiedParamName, StringComparison.OrdinalIgnoreCase)) { if (parameterInfo.position != -1) { positionsBound |= 1 << parameterInfo.position; } var argumentAst = specifiedParameter.Argument; if (argumentAst is null) { argumentAst = commandAst.CommandElements[i] as ExpressionAst; if (argumentAst != null) { i += 1; } } if (argumentAst != null) { boundParameter = true; result[parameterInfo.name] = GetSafeValueVisitor.GetSafeValue(argumentAst, null, GetSafeValueVisitor.SafeValueContext.ModuleAnalysis); } break; } } if (boundParameter || specifiedParameter.Argument != null) { continue; } if (!"PassThru".StartsWith(specifiedParamName, StringComparison.OrdinalIgnoreCase) && !"Force".StartsWith(specifiedParamName, StringComparison.OrdinalIgnoreCase) && !"Confirm".StartsWith(specifiedParamName, StringComparison.OrdinalIgnoreCase) && !"Global".StartsWith(specifiedParamName, StringComparison.OrdinalIgnoreCase) && !"AsCustomObject".StartsWith(specifiedParamName, StringComparison.OrdinalIgnoreCase) && !"Verbose".StartsWith(specifiedParamName, StringComparison.OrdinalIgnoreCase) && !"Debug".StartsWith(specifiedParamName, StringComparison.OrdinalIgnoreCase) && !"DisableNameChecking".StartsWith(specifiedParamName, StringComparison.OrdinalIgnoreCase) && !"NoClobber".StartsWith(specifiedParamName, StringComparison.OrdinalIgnoreCase)) { // Named parameter, skip the argument (except for specific switch parameters i += 1; } } else { // Positional argument, find which position we want to bind int pos = 0; for (; pos < 10; pos++) { if ((positionsBound & (1 << pos)) == 0) { break; } } positionsBound |= 1 << pos; // Now see if we care (we probably do, but if the user did something odd, like specify too many, then we don't really) foreach (var parameterInfo in parameterBindingInfo) { if (parameterInfo.position == pos) { result[parameterInfo.name] = GetSafeValueVisitor.GetSafeValue( commandAst.CommandElements[i], null, GetSafeValueVisitor.SafeValueContext.ModuleAnalysis); } } } } return(result); }
// Visit one the other variations: // - Dotting scripts // - Setting aliases // - Importing modules // - Exporting module members public override AstVisitAction VisitCommand(CommandAst commandAst) { string commandName = commandAst.GetCommandName() ?? GetSafeValueVisitor.GetSafeValue(commandAst.CommandElements[0], null, GetSafeValueVisitor.SafeValueContext.ModuleAnalysis) as string; if (commandName is null) { return(AstVisitAction.SkipChildren); } // They are trying to dot a script if (commandAst.InvocationOperator == TokenKind.Dot) { // . Foo-Bar4.ps1 // . $psScriptRoot\Foo-Bar.ps1 -Bing Baz // . ""$psScriptRoot\Support Files\Foo-Bar2.ps1"" -Bing Baz // . '$psScriptRoot\Support Files\Foo-Bar3.ps1' -Bing Baz DiscoveredModules.Add( new RequiredModuleInfo { Name = commandName, CommandsToPostFilter = new List <string>() }); ModuleIntrinsics.Tracer.WriteLine("Module dots {0}", commandName); } // They are setting an alias. if (string.Equals(commandName, "New-Alias", StringComparison.OrdinalIgnoreCase) || string.Equals(commandName, "Microsoft.PowerShell.Utility\\New-Alias", StringComparison.OrdinalIgnoreCase) || string.Equals(commandName, "Set-Alias", StringComparison.OrdinalIgnoreCase) || string.Equals(commandName, "Microsoft.PowerShell.Utility\\Set-Alias", StringComparison.OrdinalIgnoreCase) || string.Equals(commandName, "nal", StringComparison.OrdinalIgnoreCase) || string.Equals(commandName, "sal", StringComparison.OrdinalIgnoreCase)) { // Set-Alias Foo-Bar5 Foo-Bar // Set-Alias -Name Foo-Bar6 -Value Foo-Bar // sal Foo-Bar7 Foo-Bar // sal -Value Foo-Bar -Name Foo-Bar8 var boundParameters = DoPsuedoParameterBinding(commandAst, commandName); var name = boundParameters["Name"] as string; if (!string.IsNullOrEmpty(name)) { var value = boundParameters["Value"] as string; if (!string.IsNullOrEmpty(value)) { // These aren't stored in DiscoveredExports, as they are only // exported after the user calls Export-ModuleMember. DiscoveredAliases[name] = value; ModuleIntrinsics.Tracer.WriteLine("Module defines alias: {0} = {1}", name, value); } } return(AstVisitAction.SkipChildren); } // They are importing a module if (string.Equals(commandName, "Import-Module", StringComparison.OrdinalIgnoreCase) || string.Equals(commandName, "ipmo", StringComparison.OrdinalIgnoreCase)) { // Import-Module Module1 // Import-Module Module2 -Function Foo-Module2*, Foo-Module2Second* -Cmdlet Foo-Module2Cmdlet,Foo-Module2Cmdlet* // Import-Module Module3 -Function Foo-Module3Command1, Foo-Module3Command2 // Import-Module Module4, // Module5 // Import-Module -Name Module6, // Module7 -Global var boundParameters = DoPsuedoParameterBinding(commandAst, commandName); List <string> commandsToPostFilter = new List <string>(); Action <string> onEachCommand = importedCommandName => { commandsToPostFilter.Add(importedCommandName); }; // Process any exports from the module that we determine from // the -Function, -Cmdlet, or -Alias parameters ProcessCmdletArguments(boundParameters["Function"], onEachCommand); ProcessCmdletArguments(boundParameters["Cmdlet"], onEachCommand); ProcessCmdletArguments(boundParameters["Alias"], onEachCommand); // Now, go through all of the discovered modules on Import-Module // and register them for deeper investigation. Action <string> onEachModule = moduleName => { ModuleIntrinsics.Tracer.WriteLine("Discovered module import: {0}", moduleName); DiscoveredModules.Add( new RequiredModuleInfo { Name = moduleName, CommandsToPostFilter = commandsToPostFilter }); }; ProcessCmdletArguments(boundParameters["Name"], onEachModule); return(AstVisitAction.SkipChildren); } // They are exporting a module member if (string.Equals(commandName, "Export-ModuleMember", StringComparison.OrdinalIgnoreCase) || string.Equals(commandName, "Microsoft.PowerShell.Core\\Export-ModuleMember", StringComparison.OrdinalIgnoreCase) || string.Equals(commandName, "$script:ExportModuleMember", StringComparison.OrdinalIgnoreCase)) { // Export-ModuleMember * // Export-ModuleMember Exported-UnNamedModuleMember // Export-ModuleMember -Function Exported-FunctionModuleMember1, Exported-FunctionModuleMember2 -Cmdlet Exported-CmdletModuleMember ` // -Alias Exported-AliasModuleMember // & $script:ExportModuleMember -Function (...) var boundParameters = DoPsuedoParameterBinding(commandAst, commandName); Action <string> onEachFunction = exportedCommandName => { DiscoveredCommandFilters.Add(exportedCommandName); ModuleIntrinsics.Tracer.WriteLine("Discovered explicit export: {0}", exportedCommandName); // If the export doesn't contain wildcards, then add it to the // discovered commands as well. It is likely that they created // the command dynamically if ((!WildcardPattern.ContainsWildcardCharacters(exportedCommandName)) && (!DiscoveredExports.Contains(exportedCommandName))) { DiscoveredExports.Add(exportedCommandName); } }; ProcessCmdletArguments(boundParameters["Function"], onEachFunction); ProcessCmdletArguments(boundParameters["Cmdlet"], onEachFunction); Action <string> onEachAlias = exportedAlias => { DiscoveredCommandFilters.Add(exportedAlias); // If the export doesn't contain wildcards, then add it to the // discovered commands as well. It is likely that they created // the command dynamically if (!WildcardPattern.ContainsWildcardCharacters(exportedAlias)) { DiscoveredAliases[exportedAlias] = null; } }; ProcessCmdletArguments(boundParameters["Alias"], onEachAlias); return(AstVisitAction.SkipChildren); } // They are exporting a module member using our advanced 'public' function // that we've presented in many demos if ((string.Equals(commandName, "public", StringComparison.OrdinalIgnoreCase)) && (commandAst.CommandElements.Count > 2)) { // public function Publicly-ExportedFunction // public alias Publicly-ExportedAlias string publicCommandName = commandAst.CommandElements[2].ToString().Trim(); DiscoveredExports.Add(publicCommandName); DiscoveredCommandFilters.Add(publicCommandName); } return(AstVisitAction.SkipChildren); }