MegaBites-AI/Windows-powershell
0372
1// Copyright (c) Microsoft Corporation.2// Licensed under the MIT License.3 4using System.Collections;5using System.Collections.ObjectModel;6using System.Globalization;7using System.IO;8using System.Management.Automation.Language;9using System.Xml;10 11using Dbg = System.Management.Automation.Diagnostics;12 13namespace System.Management.Automation14{15 /// <summary>16 /// This is the interface between the NativeCommandProcessor and the17 /// parameter binders required to bind parameters to a minishell.18 /// </summary>19 internal class MinishellParameterBinderController : NativeCommandParameterBinderController20 {21 #region ctor22 23 /// <summary>24 /// Initializes the parameter binder controller for25 /// the specified native command and engine context.26 /// </summary>27 /// <param name="command">28 /// The command that the parameters will be bound to.29 /// </param>30 internal MinishellParameterBinderController(31 NativeCommand command)32 : base(command)33 {34 InputFormat = NativeCommandIOFormat.Xml;35 OutputFormat = NativeCommandIOFormat.Text;36 }37 38 #endregion ctor39 40 /// <summary>41 /// Value of input format. This property should be read after binding of parameters.42 /// </summary>43 internal NativeCommandIOFormat InputFormat { get; private set; }44 45 /// <summary>46 /// Value of output format. This property should be read after binding of parameters.47 /// </summary>48 internal NativeCommandIOFormat OutputFormat { get; private set; }49 50 /// <summary>51 /// IF true, child minishell is invoked with no-window.52 /// </summary>53 internal bool NonInteractive { get; private set; }54 55 /// <summary>56 /// Binds the specified parameters to the native command.57 /// </summary>58 /// <param name="parameters">59 /// The parameters to bind.60 /// </param>61 /// <param name="outputRedirected">62 /// true if minishell output is redirected.63 /// </param>64 /// <param name="hostName">65 /// name of the calling host.66 /// </param>67 /// <remarks>68 /// For any parameters that do not have a name, they are added to the command69 /// line arguments for the command70 /// </remarks>71 internal Collection<CommandParameterInternal> BindParameters(Collection<CommandParameterInternal> parameters, bool outputRedirected, string hostName)72 {73 MinishellParameters seen = 0;74 string inputFormat = null;75 string outputFormat = null;76 for (int i = 0; i < parameters.Count; i++)77 {78 var parameter = parameters[i];79 80 if (parameter.ParameterNameSpecified)81 {82 var parameterName = parameter.ParameterName;83 84 if (CommandParameter.StartsWith(parameterName, StringComparison.OrdinalIgnoreCase))85 {86 HandleSeenParameter(ref seen, MinishellParameters.Command, CommandParameter);87 88 // Value must be specified for -Command parameter.89 if (i + 1 >= parameters.Count)90 {91 throw NewParameterBindingException(null, ErrorCategory.InvalidArgument, CommandParameter,92 typeof(ScriptBlock), null,93 NativeCP.NoValueForCommandParameter,94 "NoValueForCommandParameter");95 }96 97 i += 1;98 99 // Value of -Command parameter must be scriptblock100 var scriptBlockArgument = parameters[i];101 var argumentValue = PSObject.Base(scriptBlockArgument.ArgumentValue);102 if (!scriptBlockArgument.ArgumentSpecified || argumentValue is not ScriptBlock)103 {104 throw NewParameterBindingException(null, ErrorCategory.InvalidArgument, CommandParameter,105 typeof(ScriptBlock), argumentValue.GetType(),106 NativeCP.IncorrectValueForCommandParameter,107 "IncorrectValueForCommandParameter");108 }109 110 // Replace the parameters with -EncodedCommand <base64 encoded scriptblock>111 parameters[i - 1] = CommandParameterInternal.CreateParameter(EncodedCommandParameter, "-" + EncodedCommandParameter, parameter.ParameterAst);112 string encodedScript = StringToBase64Converter.StringToBase64String(argumentValue.ToString());113 parameters[i] = CommandParameterInternal.CreateArgument(encodedScript, scriptBlockArgument.ArgumentAst);114 }115 else if (InputFormatParameter.StartsWith(parameterName, StringComparison.OrdinalIgnoreCase))116 {117 HandleSeenParameter(ref seen, MinishellParameters.InputFormat, InputFormatParameter);118 119 // Value for -Inputformat must be specified120 if (i + 1 >= parameters.Count)121 {122 throw NewParameterBindingException(null, ErrorCategory.InvalidArgument, InputFormatParameter,123 typeof(string), null,124 NativeCP.NoValueForInputFormatParameter,125 "NoValueForInputFormatParameter");126 }127 128 // Update the argument (partial arguments are allowed)129 i += 1;130 var inputFormatArg = parameters[i];131 inputFormat = ProcessFormatParameterValue(InputFormatParameter, inputFormatArg.ArgumentValue);132 parameters[i - 1] = CommandParameterInternal.CreateParameter(InputFormatParameter, "-" + InputFormatParameter, parameter.ParameterAst);133 parameters[i] = CommandParameterInternal.CreateArgument(inputFormat, inputFormatArg.ArgumentAst);134 }135 else if (OutputFormatParameter.StartsWith(parameterName, StringComparison.OrdinalIgnoreCase))136 {137 HandleSeenParameter(ref seen, MinishellParameters.OutputFormat, OutputFormatParameter);138 139 // Value for -Inputformat must be specified140 if (i + 1 >= parameters.Count)141 {142 throw NewParameterBindingException(null, ErrorCategory.InvalidArgument, OutputFormatParameter,143 typeof(string), null,144 NativeCP.NoValueForOutputFormatParameter,145 "NoValueForInputFormatParameter");146 }147 148 // Update the argument (partial arguments are allowed)149 i += 1;150 var outputFormatArg = parameters[i];151 outputFormat = ProcessFormatParameterValue(OutputFormatParameter, outputFormatArg.ArgumentValue);152 parameters[i - 1] = CommandParameterInternal.CreateParameter(OutputFormatParameter, "-" + OutputFormatParameter, parameter.ParameterAst);153 parameters[i] = CommandParameterInternal.CreateArgument(outputFormat, outputFormatArg.ArgumentAst);154 }155 else if (ArgsParameter.StartsWith(parameterName, StringComparison.OrdinalIgnoreCase))156 {157 HandleSeenParameter(ref seen, MinishellParameters.Arguments, ArgsParameter);158 159 // Value for -Args parameter must be specified160 if (i + 1 >= parameters.Count)161 {162 throw NewParameterBindingException(null, ErrorCategory.InvalidArgument, ArgsParameter,163 typeof(string), null, NativeCP.NoValuesSpecifiedForArgs,164 "NoValuesSpecifiedForArgs");165 }166 167 // Get the encoded value for -args parameter168 i += 1;169 var argsArg = parameters[i];170 var encodedArgs = ConvertArgsValueToEncodedString(argsArg.ArgumentValue);171 parameters[i - 1] = CommandParameterInternal.CreateParameter(EncodedArgsParameter, "-" + EncodedArgsParameter, parameter.ParameterAst);172 // NOTE: do not pass the ArgumentAst; it will fail validation in BindParameters if there173 // are multiple arguments (array) but encodedArgs is an encoded string.174 parameters[i] = CommandParameterInternal.CreateArgument(encodedArgs);175 }176 }177 else178 {179 // -Command is positional parameter. Bind first scriptblock to it, others are errors.180 var scriptBlockArgument = parameters[i];181 var argumentValue = PSObject.Base(scriptBlockArgument.ArgumentValue);182 if (argumentValue is ScriptBlock)183 {184 HandleSeenParameter(ref seen, MinishellParameters.Command, CommandParameter);185 186 // Replace the argument with -EncodedCommand <base64 encoded scriptblock>187 string encodedScript = StringToBase64Converter.StringToBase64String(argumentValue.ToString());188 parameters[i] = CommandParameterInternal.CreateParameterWithArgument(189 parameter.ArgumentAst, EncodedCommandParameter, "-" + EncodedCommandParameter,190 parameter.ArgumentAst, encodedScript,191 spaceAfterParameter: true);192 }193 }194 }195 196 // Add InputFormat and OutputFormat parameter if not specified197 if (inputFormat == null)198 {199 // For minishell default input format is xml200 parameters.Add(CommandParameterInternal.CreateParameter(InputFormatParameter, "-" + InputFormatParameter));201 parameters.Add(CommandParameterInternal.CreateArgument(XmlFormatValue));202 inputFormat = XmlFormatValue;203 }204 205 if (outputFormat == null)206 {207 // If output is redirected, output format should be xml208 outputFormat = outputRedirected ? XmlFormatValue : TextFormatValue;209 parameters.Add(CommandParameterInternal.CreateParameter(OutputFormatParameter, "-" + OutputFormatParameter));210 parameters.Add(CommandParameterInternal.CreateArgument(outputFormat));211 }212 213 // Set the output and input format class variable214 InputFormat = XmlFormatValue.StartsWith(inputFormat, StringComparison.OrdinalIgnoreCase)215 ? NativeCommandIOFormat.Xml216 : NativeCommandIOFormat.Text;217 OutputFormat = XmlFormatValue.StartsWith(outputFormat, StringComparison.OrdinalIgnoreCase)218 ? NativeCommandIOFormat.Xml219 : NativeCommandIOFormat.Text;220 221 // Note if a minishell is invoked from a non-console host, we need to222 // pass -nonInteractive flag. Our console host's name is "ConsoleHost".223 // Correct check would be see if current host has access to console and224 // pass noninteractive flag if doesn't.225 if (string.IsNullOrEmpty(hostName) || !hostName.Equals("ConsoleHost", StringComparison.OrdinalIgnoreCase))226 {227 NonInteractive = true;228 parameters.Insert(0, CommandParameterInternal.CreateParameter(NonInteractiveParameter, "-" + NonInteractiveParameter));229 }230 231 ((NativeCommandParameterBinder)DefaultParameterBinder).BindParameters(parameters);232 233 Diagnostics.Assert(s_emptyReturnCollection.Count == 0, "This list shouldn't be used for anything as it's shared.");234 235 return s_emptyReturnCollection;236 }237 238 private static readonly Collection<CommandParameterInternal> s_emptyReturnCollection = new Collection<CommandParameterInternal>();239 240 internal const string CommandParameter = "command";241 internal const string EncodedCommandParameter = "encodedCommand";242 internal const string ArgsParameter = "args";243 internal const string EncodedArgsParameter = "encodedarguments";244 internal const string InputFormatParameter = "inputFormat";245 internal const string OutputFormatParameter = "outputFormat";246 internal const string XmlFormatValue = "xml";247 internal const string TextFormatValue = "text";248 internal const string NonInteractiveParameter = "noninteractive";249 250 [Flags]251 private enum MinishellParameters252 {253 Command = 0x01,254 Arguments = 0x02,255 InputFormat = 0x04,256 OutputFormat = 0x08257 }258 259 /// <summary>260 /// Handles error handling if some parameter is specified more than once.261 /// </summary>262 private void HandleSeenParameter(ref MinishellParameters seen, MinishellParameters parameter, string parameterName)263 {264 if ((seen & parameter) == parameter)265 {266 throw NewParameterBindingException(null, ErrorCategory.InvalidArgument,267 "-" + parameterName, null, null,268 NativeCP.ParameterSpecifiedAlready,269 "ParameterSpecifiedAlready",270 parameterName);271 }272 else273 {274 seen |= parameter;275 }276 }277 278 /// <summary>279 /// This function processes the value for -inputFormat and -outputFormat parameter of minishell.280 /// </summary>281 /// <param name="parameterName">Name of the parameter for error messages. Value should be -inputFormat or -outputFormat.</param>282 /// <param name="value">Value to process.</param>283 /// <returns>Processed value.</returns>284 private string285 ProcessFormatParameterValue(string parameterName, object value)286 {287 string fpValue;288 try289 {290 fpValue = (string)LanguagePrimitives.ConvertTo(value, typeof(string), CultureInfo.InvariantCulture);291 }292 catch (PSInvalidCastException ex)293 {294 throw NewParameterBindingException(ex, ErrorCategory.InvalidArgument, parameterName,295 typeof(string), value.GetType(),296 NativeCP.StringValueExpectedForFormatParameter,297 "StringValueExpectedForFormatParameter", parameterName);298 }299 300 if (XmlFormatValue.StartsWith(fpValue, StringComparison.OrdinalIgnoreCase))301 {302 return XmlFormatValue;303 }304 305 if (TextFormatValue.StartsWith(fpValue, StringComparison.OrdinalIgnoreCase))306 {307 return TextFormatValue;308 }309 310 throw NewParameterBindingException(null, ErrorCategory.InvalidArgument, parameterName,311 typeof(string), value.GetType(),312 NativeCP.IncorrectValueForFormatParameter,313 "IncorrectValueForFormatParameter",314 fpValue, parameterName);315 }316 317 /// <summary>318 /// Converts value of args parameter in to an encoded string.319 /// </summary>320 private static string ConvertArgsValueToEncodedString(object value)321 {322 ArrayList list = ConvertArgsValueToArrayList(value);323 324 // Serialize the list325 StringWriter stringWriter = new StringWriter(System.Globalization.CultureInfo.InvariantCulture);326 // When (if) switching to XmlTextWriter.Create remember the OmitXmlDeclaration difference327 XmlWriter xmlWriter = XmlWriter.Create(stringWriter);328 Serializer serializer = new Serializer(xmlWriter);329 serializer.Serialize(list);330 serializer.Done();331 xmlWriter.Flush();332 string result = stringWriter.ToString();333 334 // convert result to encoded string335 return StringToBase64Converter.StringToBase64String(result);336 }337 338 /// <summary>339 /// Converts the value of -args parameter received from340 /// parser in to an arraylist.341 /// </summary>342 private static ArrayList ConvertArgsValueToArrayList(object value)343 {344 ArrayList results = new ArrayList();345 IEnumerator list = LanguagePrimitives.GetEnumerator(value);346 if (list == null)347 {348 results.Add(value);349 }350 else351 {352 while (list.MoveNext())353 {354 results.Add(list.Current);355 }356 }357 358 return results;359 }360 361 private ParameterBindingException NewParameterBindingException(362 Exception innerException,363 ErrorCategory errorCategory,364 string parameterName,365 Type parameterType,366 Type typeSpecified,367 string resourceString,368 string errorId,369 params object[] args)370 {371 return new ParameterBindingException(372 innerException,373 errorCategory,374 this.InvocationInfo,375 null,376 parameterName,377 parameterType,378 typeSpecified,379 resourceString,380 errorId,381 args);382 }383 }384}385 