/********************************************************************++ Copyright (c) Microsoft Corporation. All rights reserved. --********************************************************************/ using System.Collections; using System.Collections.ObjectModel; using System.IO; using System.Management.Automation.Language; using System.Xml; using System.Globalization; using Dbg = System.Management.Automation.Diagnostics; namespace System.Management.Automation { /// /// This is the interface between the NativeCommandProcessor and the /// parameter binders required to bind parameters to a minishell. /// /// internal class MinishellParameterBinderController : NativeCommandParameterBinderController { #region ctor /// /// Initializes the parameter binder controller for /// the specified native command and engine context /// /// /// /// The command that the parameters will be bound to. /// /// internal MinishellParameterBinderController( NativeCommand command) : base(command) { InputFormat = NativeCommandIOFormat.Xml; OutputFormat = NativeCommandIOFormat.Text; } #endregion ctor /// /// Override of parent class which should not be used. /// /// /// /// The parameters to bind. /// /// /// /// For any parameters that do not have a name, they are added to the command /// line arguments for the command /// internal override Collection BindParameters(Collection parameters) { Dbg.Assert(false, "this method should be used"); return null; } /// /// Value of input format. This property should be read after binding of parameters. /// internal NativeCommandIOFormat InputFormat { get; private set; } /// /// Value of output format. This property should be read after binding of parameters. /// internal NativeCommandIOFormat OutputFormat { get; private set; } /// /// IF true, child minishell is invoked with no-window /// internal bool NonInteractive { get; private set; } /// /// Binds the specified parameters to the native command /// /// /// /// The parameters to bind. /// /// /// /// true if minishell output is redirected. /// /// /// /// name of the calling host. /// /// /// /// For any parameters that do not have a name, they are added to the command /// line arguments for the command /// /// internal Collection BindParameters(Collection parameters, bool outputRedirected, string hostName) { MinishellParameters seen = 0; string inputFormat = null; string outputFormat = null; for (int i = 0; i < parameters.Count; i++) { var parameter = parameters[i]; if (parameter.ParameterNameSpecified) { var parameterName = parameter.ParameterName; if (CommandParameter.StartsWith(parameterName, StringComparison.OrdinalIgnoreCase)) { HandleSeenParameter(ref seen, MinishellParameters.Command, CommandParameter); // Value must be specified for -Command parameter. if (i + 1 >= parameters.Count) { throw NewParameterBindingException(null, ErrorCategory.InvalidArgument, CommandParameter, typeof(ScriptBlock), null, NativeCP.NoValueForCommandParameter, "NoValueForCommandParameter"); } i += 1; // Value of -Command parameter must be scriptblock var scriptBlockArgument = parameters[i]; var argumentValue = PSObject.Base(scriptBlockArgument.ArgumentValue); if (!scriptBlockArgument.ArgumentSpecified || !(argumentValue is ScriptBlock)) { throw NewParameterBindingException(null, ErrorCategory.InvalidArgument, CommandParameter, typeof(ScriptBlock), argumentValue.GetType(), NativeCP.IncorrectValueForCommandParameter, "IncorrectValueForCommandParameter"); } // Replace the parameters with -EncodedCommand parameters[i - 1] = CommandParameterInternal.CreateParameter(EncodedCommandParameter, "-" + EncodedCommandParameter, parameter.ParameterAst); string encodedScript = StringToBase64Converter.StringToBase64String(argumentValue.ToString()); parameters[i] = CommandParameterInternal.CreateArgument(encodedScript, scriptBlockArgument.ArgumentAst); } else if (InputFormatParameter.StartsWith(parameterName, StringComparison.OrdinalIgnoreCase)) { HandleSeenParameter(ref seen, MinishellParameters.InputFormat, InputFormatParameter); // Value for -Inputformat must be specified if (i + 1 >= parameters.Count) { throw NewParameterBindingException(null, ErrorCategory.InvalidArgument, InputFormatParameter, typeof(string), null, NativeCP.NoValueForInputFormatParameter, "NoValueForInputFormatParameter"); } // Update the argument (partial arguments are allowed) i += 1; var inputFormatArg = parameters[i]; inputFormat = ProcessFormatParameterValue(InputFormatParameter, inputFormatArg.ArgumentValue); parameters[i - 1] = CommandParameterInternal.CreateParameter(InputFormatParameter, "-" + InputFormatParameter, parameter.ParameterAst); parameters[i] = CommandParameterInternal.CreateArgument(inputFormat, inputFormatArg.ArgumentAst); } else if (OutputFormatParameter.StartsWith(parameterName, StringComparison.OrdinalIgnoreCase)) { HandleSeenParameter(ref seen, MinishellParameters.OutputFormat, OutputFormatParameter); // Value for -Inputformat must be specified if (i + 1 >= parameters.Count) { throw NewParameterBindingException(null, ErrorCategory.InvalidArgument, OutputFormatParameter, typeof(string), null, NativeCP.NoValueForOutputFormatParameter, "NoValueForInputFormatParameter"); } // Update the argument (partial arguments are allowed) i += 1; var outputFormatArg = parameters[i]; outputFormat = ProcessFormatParameterValue(OutputFormatParameter, outputFormatArg.ArgumentValue); parameters[i - 1] = CommandParameterInternal.CreateParameter(OutputFormatParameter, "-" + OutputFormatParameter, parameter.ParameterAst); parameters[i] = CommandParameterInternal.CreateArgument(outputFormat, outputFormatArg.ArgumentAst); } else if (ArgsParameter.StartsWith(parameterName, StringComparison.OrdinalIgnoreCase)) { HandleSeenParameter(ref seen, MinishellParameters.Arguments, ArgsParameter); // Value for -Args parameter must be specified if (i + 1 >= parameters.Count) { throw NewParameterBindingException(null, ErrorCategory.InvalidArgument, ArgsParameter, typeof(string), null, NativeCP.NoValuesSpecifiedForArgs, "NoValuesSpecifiedForArgs"); } // Get the encoded value for -args parameter i += 1; var argsArg = parameters[i]; var encodedArgs = ConvertArgsValueToEncodedString(argsArg.ArgumentValue); parameters[i - 1] = CommandParameterInternal.CreateParameter(EncodedArgsParameter, "-" + EncodedArgsParameter, parameter.ParameterAst); parameters[i] = CommandParameterInternal.CreateArgument(encodedArgs, argsArg.ArgumentAst); } } else { // -Command is positional parameter. Bind first scriptblock to it, others are errors. var scriptBlockArgument = parameters[i]; var argumentValue = PSObject.Base(scriptBlockArgument.ArgumentValue); if (argumentValue is ScriptBlock) { HandleSeenParameter(ref seen, MinishellParameters.Command, CommandParameter); // Replace the argument with -EncodedCommand string encodedScript = StringToBase64Converter.StringToBase64String(argumentValue.ToString()); parameters[i] = CommandParameterInternal.CreateParameterWithArgument( parameter.ArgumentAst, EncodedCommandParameter, "-" + EncodedCommandParameter, parameter.ArgumentAst, encodedScript, spaceAfterParameter: true, arrayIsSingleArgumentForNativeCommand: false); } } } // Add InputFormat and OutputFormat parameter if not specified if (inputFormat == null) { // For minishell default input format is xml parameters.Add(CommandParameterInternal.CreateParameter(InputFormatParameter, "-" + InputFormatParameter)); parameters.Add(CommandParameterInternal.CreateArgument(XmlFormatValue)); inputFormat = XmlFormatValue; } if (outputFormat == null) { // If output is redirected, output format should be xml outputFormat = outputRedirected ? XmlFormatValue : TextFormatValue; parameters.Add(CommandParameterInternal.CreateParameter(OutputFormatParameter, "-" + OutputFormatParameter)); parameters.Add(CommandParameterInternal.CreateArgument(outputFormat)); } // Set the output and input format class variable InputFormat = XmlFormatValue.StartsWith(inputFormat, StringComparison.OrdinalIgnoreCase) ? NativeCommandIOFormat.Xml : NativeCommandIOFormat.Text; OutputFormat = XmlFormatValue.StartsWith(outputFormat, StringComparison.OrdinalIgnoreCase) ? NativeCommandIOFormat.Xml : NativeCommandIOFormat.Text; // Note if a minishell is invoked from a non-console host, we need to // pass -nonInteractive flag. Our console host's name is "ConsoleHost". // Correct check would be see if current host has access to console and // pass noninteractive flag if doesn't. if (string.IsNullOrEmpty(hostName) || !hostName.Equals("ConsoleHost", StringComparison.OrdinalIgnoreCase)) { NonInteractive = true; parameters.Insert(0, CommandParameterInternal.CreateParameter(NonInteractiveParameter, "-" + NonInteractiveParameter)); } ((NativeCommandParameterBinder)DefaultParameterBinder).BindParameters(parameters); Diagnostics.Assert(s_emptyReturnCollection.Count == 0, "This list shouldn't be used for anything as it's shared."); return s_emptyReturnCollection; } // BindParameters private static readonly Collection s_emptyReturnCollection = new Collection(); internal const string CommandParameter = "command"; internal const string EncodedCommandParameter = "encodedCommand"; internal const string ArgsParameter = "args"; internal const string EncodedArgsParameter = "encodedarguments"; internal const string InputFormatParameter = "inputFormat"; internal const string OutputFormatParameter = "outputFormat"; internal const string XmlFormatValue = "xml"; internal const string TextFormatValue = "text"; internal const string NonInteractiveParameter = "noninteractive"; [Flags] private enum MinishellParameters { Command = 0x01, Arguments = 0x02, InputFormat = 0x04, OutputFormat = 0x08 }; /// /// Handles error handling if some parameter is specified more than once. /// private void HandleSeenParameter(ref MinishellParameters seen, MinishellParameters parameter, string parameterName) { if ((seen & parameter) == parameter) { throw NewParameterBindingException(null, ErrorCategory.InvalidArgument, "-" + parameterName, null, null, NativeCP.ParameterSpecifiedAlready, "ParameterSpecifiedAlready", parameterName); } else { seen = seen | parameter; } } /// /// This function processes the value for -inputFormat and -outputFormat parameter of minishell. /// /// Name of the parameter for error messages. Value should be -inputFormat or -outputFormat /// value to process /// Processed value private string ProcessFormatParameterValue(string parameterName, object value) { string fpValue; try { fpValue = (string)LanguagePrimitives.ConvertTo(value, typeof(string), CultureInfo.InvariantCulture); } catch (PSInvalidCastException ex) { throw NewParameterBindingException(ex, ErrorCategory.InvalidArgument, parameterName, typeof(string), value.GetType(), NativeCP.StringValueExpectedForFormatParameter, "StringValueExpectedForFormatParameter", parameterName); } if (XmlFormatValue.StartsWith(fpValue, StringComparison.OrdinalIgnoreCase)) { return XmlFormatValue; } if (TextFormatValue.StartsWith(fpValue, StringComparison.OrdinalIgnoreCase)) { return TextFormatValue; } throw NewParameterBindingException(null, ErrorCategory.InvalidArgument, parameterName, typeof(string), value.GetType(), NativeCP.IncorrectValueForFormatParameter, "IncorrectValueForFormatParameter", fpValue, parameterName); } /// /// Converts value of args parameter in to an encoded string /// private static string ConvertArgsValueToEncodedString(object value) { ArrayList list = ConvertArgsValueToArrayList(value); //Serialize the list StringWriter stringWriter = new StringWriter(System.Globalization.CultureInfo.InvariantCulture); //When (if) switching to XmlTextWriter.Create remember the OmitXmlDeclaration difference XmlWriter xmlWriter = XmlWriter.Create(stringWriter); Serializer serializer = new Serializer(xmlWriter); serializer.Serialize(list); serializer.Done(); xmlWriter.Flush(); string result = stringWriter.ToString(); //convert result to encoded string return StringToBase64Converter.StringToBase64String(result); } /// /// Converts the value of -args parameter received from /// parser in to an arraylist /// private static ArrayList ConvertArgsValueToArrayList(object value) { ArrayList results = new ArrayList(); IEnumerator list = LanguagePrimitives.GetEnumerator(value); if (list == null) { results.Add(value); } else { while (list.MoveNext()) { results.Add(list.Current); } } return results; } private ParameterBindingException NewParameterBindingException( Exception innerException, ErrorCategory errorCategory, string parameterName, Type parameterType, Type typeSpecified, string resourceString, string errorId, params object[] args) { return new ParameterBindingException( innerException, errorCategory, this.InvocationInfo, null, parameterName, parameterType, typeSpecified, resourceString, errorId, args); } } }