MegaBites-AI/Windows-powershell
0372
1// Copyright (c) Microsoft Corporation.2// Licensed under the MIT License.3 4using System.Collections;5using System.Collections.Concurrent;6using System.Collections.Generic;7using System.Diagnostics.CodeAnalysis;8using System.Globalization;9using System.Management.Automation.Internal;10using System.Management.Automation.Internal.Host;11using System.Management.Automation.Language;12using System.Management.Automation.Runspaces;13using System.Runtime.CompilerServices;14using System.Runtime.Serialization;15using System.Text.RegularExpressions;16 17using Dbg = System.Management.Automation.Diagnostics;18 19#pragma warning disable 1634, 1691 // Stops compiler from warning about unknown warnings20 21namespace System.Management.Automation22{23 #region Flow Control Exceptions24 25 /// <summary>26 /// FlowControlException, base class for flow control exceptions.27 /// </summary>28 public abstract class FlowControlException : SystemException29 {30 internal FlowControlException() { }31 }32 33 /// <summary>34 /// LoopFlowException, base class for loop control exceptions.35 /// </summary>36 public abstract class LoopFlowException : FlowControlException37 {38 internal LoopFlowException(string label)39 {40 this.Label = label ?? string.Empty;41 }42 43 internal LoopFlowException() { }44 45 /// <summary>46 /// Label, indicates nested loop level affected by exception.47 /// No label means most nested loop is affected.48 /// </summary>49 public string Label50 {51 get;52 internal set;53 }54 55 internal bool MatchLabel(string loopLabel)56 {57 return MatchLoopLabel(Label, loopLabel);58 }59 60 internal static bool MatchLoopLabel(string flowLabel, string loopLabel)61 {62 // If the flow statement has no label, it always matches (because it just means, break or continue from63 // the most nested loop.) Otherwise, compare the labels.64 65 return string.IsNullOrEmpty(flowLabel) || flowLabel.Equals(loopLabel, StringComparison.OrdinalIgnoreCase);66 }67 }68 69 /// <summary>70 /// Flow control BreakException.71 /// </summary>72 public sealed class BreakException : LoopFlowException73 {74 [SuppressMessage("Microsoft.Design", "CA1032:ImplementStandardExceptionConstructors", Justification = "This exception should only be thrown from SMA.dll")]75 internal BreakException(string label)76 : base(label)77 {78 }79 80 [SuppressMessage("Microsoft.Design", "CA1032:ImplementStandardExceptionConstructors", Justification = "This exception should only be thrown from SMA.dll")]81 internal BreakException() { }82 83 [SuppressMessage("Microsoft.Design", "CA1032:ImplementStandardExceptionConstructors", Justification = "This exception should only be thrown from SMA.dll")]84 internal BreakException(string label, Exception innerException)85 : base(label)86 {87 }88 }89 90 /// <summary>91 /// Flow control ContinueException.92 /// </summary>93 public sealed class ContinueException : LoopFlowException94 {95 [SuppressMessage("Microsoft.Design", "CA1032:ImplementStandardExceptionConstructors", Justification = "This exception should only be thrown from SMA.dll")]96 internal ContinueException(string label)97 : base(label)98 {99 }100 101 [SuppressMessage("Microsoft.Design", "CA1032:ImplementStandardExceptionConstructors", Justification = "This exception should only be thrown from SMA.dll")]102 internal ContinueException() { }103 104 [SuppressMessage("Microsoft.Design", "CA1032:ImplementStandardExceptionConstructors", Justification = "This exception should only be thrown from SMA.dll")]105 internal ContinueException(string label, Exception innerException)106 : base(label)107 {108 }109 }110 111 internal class ReturnException : FlowControlException112 {113 internal ReturnException(object argument)114 {115 this.Argument = argument;116 }117 118 internal object Argument { get; set; }119 }120 121 /// <summary>122 /// Implements the exit keyword.123 /// </summary>124 public class ExitException : FlowControlException125 {126 [SuppressMessage("Microsoft.Design", "CA1032:ImplementStandardExceptionConstructors", Justification = "This exception should only be thrown from SMA.dll")]127 internal ExitException(object argument)128 {129 this.Argument = argument;130 }131 132 /// <summary>133 /// Argument.134 /// </summary>135 public object Argument { get; internal set; }136 137 [SuppressMessage("Microsoft.Design", "CA1032:ImplementStandardExceptionConstructors", Justification = "This exception should only be thrown from SMA.dll")]138 internal ExitException() { }139 }140 141 /// <summary>142 /// Used by InternalHost.ExitNestedPrompt() to pop out of an interpreter level...143 /// </summary>144 internal class ExitNestedPromptException : FlowControlException145 {146 }147 148 /// <summary>149 /// Used by the debugger to terminate the execution of the current command.150 /// </summary>151 public sealed class TerminateException : FlowControlException152 {153 }154 155 /// <summary>156 /// Used by Select-Object cmdlet to stop all the upstream cmdlets, but continue157 /// executing downstream cmdlets. The semantics of stopping is intended to mimic158 /// a user pressing Ctrl-C [but which only affects upstream cmdlets].159 /// </summary>160 internal class StopUpstreamCommandsException : FlowControlException161 {162 public StopUpstreamCommandsException(InternalCommand requestingCommand)163 {164 this.RequestingCommandProcessor = requestingCommand.Context.CurrentCommandProcessor;165 }166 167 public CommandProcessorBase RequestingCommandProcessor { get; }168 }169 170 #endregion Flow Control Exceptions171 172 /// <summary>173 /// A enum corresponding to the options on the -split operator.174 /// </summary>175 [Flags]176 public enum SplitOptions177 {178 /// <summary>179 /// Use simple string comparison when evaluating the delimiter.180 /// Cannot be used with RegexMatch.181 /// </summary>182 SimpleMatch = 0x01,183 /// <summary>184 /// Use regular expression matching to evaluate the delimiter.185 /// This is the default behavior. Cannot be used with SimpleMatch.186 /// </summary>187 RegexMatch = 0x02,188 /// <summary>189 /// CultureInvariant: Ignores cultural differences in language when evaluating the delimiter.190 /// Valid only with RegexMatch.191 /// </summary>192 CultureInvariant = 0x04,193 /// <summary>194 /// Ignores unescaped whitespace and comments marked with #.195 /// Valid only with RegexMatch.196 /// </summary>197 [SuppressMessage("Microsoft.Naming", "CA1702:CompoundWordsShouldBeCasedCorrectly", MessageId = "Whitespace")]198 IgnorePatternWhitespace = 0x08,199 /// <summary>200 /// Regex multiline mode, which recognizes the start and end of lines,201 /// as well as the start and end of strings.202 /// Valid only with RegexMatch.203 /// Singleline is the default.204 /// </summary>205 Multiline = 0x10,206 /// <summary>207 /// Regex Singleline mode, which recognizes only the start and end of strings.208 /// Valid only with RegexMatch.209 /// Singleline is the default.210 /// </summary>211 Singleline = 0x20,212 /// <summary>213 /// Forces case-insensitive matching, even if -cSplit is specified.214 /// </summary>215 IgnoreCase = 0x40,216 /// <summary>217 /// Ignores non-named match groups, so that only explicit capture groups218 /// are returned in the result list.219 /// </summary>220 ExplicitCapture = 0x80,221 }222 223 #region ParserOps224 225 internal delegate object PowerShellBinaryOperator(ExecutionContext context, IScriptExtent errorPosition, object lval, object rval);226 227 /// <summary>228 /// A static class holding various operations specific to the PowerShell interpreter such as229 /// various math operations, ToString() and a routine to extract the base object from an230 /// PSObject in a canonical fashion.231 /// </summary>232 internal static class ParserOps233 {234 internal const string MethodNotFoundErrorId = "MethodNotFound";235 236 /// <summary>237 /// Construct the various caching structures used by the runtime routines...238 /// </summary>239 static ParserOps()240 {241 // Cache for ints and chars to avoid overhead of boxing every time...242 for (int i = 0; i < (_MaxCache - _MinCache); i++)243 {244 s_integerCache[i] = (object)(i + _MinCache);245 }246 247 for (char ch = (char)0; ch < 255; ch++)248 {249 s_chars[ch] = new string(ch, 1);250 }251 }252 253 private const int _MinCache = -100;254 private const int _MaxCache = 1000;255 256 private static readonly object[] s_integerCache = new object[_MaxCache - _MinCache];257 private static readonly string[] s_chars = new string[255];258 internal static readonly object _TrueObject = (object)true;259 internal static readonly object _FalseObject = (object)false;260 261 internal static string CharToString(char ch)262 {263 if (ch < 255) return s_chars[ch];264 return new string(ch, 1);265 }266 267 internal static object BoolToObject(bool value)268 {269 return value ? _TrueObject : _FalseObject;270 }271 272 /// <summary>273 /// Convert an object into an int, avoiding boxing small integers...274 /// </summary>275 /// <param name="value">The int to convert.</param>276 /// <returns>The reference equivalent.</returns>277 internal static object IntToObject(int value)278 {279 if (value < _MaxCache && value >= _MinCache)280 {281 return s_integerCache[value - _MinCache];282 }283 284 return (object)value;285 }286 287 internal static PSObject WrappedNumber(object data, string text)288 {289 PSObject wrapped = new PSObject(data);290 wrapped.TokenText = text;291 return wrapped;292 }293 294 /// <summary>295 /// A helper routine that turns the argument object into an296 /// integer. It handles PSObject and conversions.297 /// </summary>298 /// <param name="obj"></param>299 /// <param name="errorPosition">The position to use for error reporting.</param>300 /// <returns></returns>301 /// <exception cref="RuntimeException">The result could not be represented as an integer.</exception>302 internal static int FixNum(object obj, IScriptExtent errorPosition)303 {304 obj = PSObject.Base(obj);305 306 if (obj == null)307 return 0;308 if (obj is int)309 return (int)obj;310 int result = ConvertTo<int>(obj, errorPosition);311 return result;312 }313 314 /// <summary>315 /// This is a helper function for converting an object to a particular type.316 ///317 /// It will throw exception with information about token representing the object.318 /// </summary>319 internal static T ConvertTo<T>(object obj, IScriptExtent errorPosition)320 {321 T result;322 323 try324 {325 result = (T)LanguagePrimitives.ConvertTo(obj, typeof(T), CultureInfo.InvariantCulture);326 }327 catch (PSInvalidCastException mice)328 {329 RuntimeException re = new RuntimeException(mice.Message, mice);330 re.ErrorRecord.SetInvocationInfo(new InvocationInfo(null, errorPosition));331 throw re;332 }333 334 return result;335 }336 337 /// <summary>338 /// Private method used to call the op_* operations for the math operators.339 /// </summary>340 /// <param name="lval">Left operand.</param>341 /// <param name="rval">Right operand.</param>342 /// <param name="op">Name of the operation method to perform.</param>343 /// <param name="errorPosition">The position to use for error reporting.</param>344 /// <param name="errorOp">The string to use in error messages representing the op.</param>345 /// <returns>The result of the operation.</returns>346 /// <exception cref="RuntimeException">An error occurred performing the operation, see inner exception.</exception>347 internal static object ImplicitOp(object lval, object rval, string op, IScriptExtent errorPosition, string errorOp)348 {349 // Get the base object. At somepoint, we may allow users to dynamically extend350 // the implicit operators at which point, we'll need to change this to find the351 // extension method...352 lval = PSObject.Base(lval);353 rval = PSObject.Base(rval);354 355 Type lvalType = lval?.GetType();356 Type rvalType = rval?.GetType();357 Type opType;358 if (lvalType == null || (lvalType.IsPrimitive))359 {360 // Prefer the LHS type when looking for the operator, but attempt the right361 // the lhs can't have an operator.362 //363 // This logic is overly simplified and doesn't match other languages which364 // would look for overloads in both types, but this logic covers the most common365 // cases.366 367 opType = (rvalType == null || rvalType.IsPrimitive) ? null : rvalType;368 }369 else370 {371 opType = lvalType;372 }373 374 if (opType == null)375 {376 throw InterpreterError.NewInterpreterException(lval, typeof(RuntimeException), errorPosition,377 "NotADefinedOperationForType", ParserStrings.NotADefinedOperationForType,378 lvalType == null ? "$null" : lvalType.FullName,379 errorOp,380 rvalType == null ? "$null" : rvalType.FullName);381 }382 383 // None of the explicit conversions worked so try and invoke a method instead...384 object[] parms = new object[2];385 parms[0] = lval;386 parms[1] = rval;387 return CallMethod(388 errorPosition,389 opType,390 op, /* methodName */391 null, /* invocationConstraints */392 parms,393 true,394 AutomationNull.Value);395 }396 397 [Flags]398 private enum SplitImplOptions399 {400 None = 0x00,401 TrimContent = 0x01,402 }403 404 private static object[] unfoldTuple(ExecutionContext context, IScriptExtent errorPosition, object tuple)405 {406 List<object> result = new List<object>();407 408 IEnumerator enumerator = LanguagePrimitives.GetEnumerator(tuple);409 if (enumerator != null)410 {411 while (ParserOps.MoveNext(context, errorPosition, enumerator))412 {413 object element = ParserOps.Current(errorPosition, enumerator);414 result.Add(element);415 }416 }417 else418 {419 // Not a tuple at all, just a single item. Treat it420 // as a 1-tuple.421 result.Add(tuple);422 }423 424 return result.ToArray();425 }426 427 // uses "yield" from C# 2.0, which automatically creates428 // an enumerable out of the loop code. See429 // https://msdn.microsoft.com/msdnmag/issues/04/05/C20/ for430 // more details.431 private static IEnumerable<string> enumerateContent(ExecutionContext context, IScriptExtent errorPosition, SplitImplOptions implOptions, object tuple)432 {433 IEnumerator enumerator = LanguagePrimitives.GetEnumerator(tuple) ?? new object[] { tuple }.GetEnumerator();434 435 while (ParserOps.MoveNext(context, errorPosition, enumerator))436 {437 string strValue = PSObject.ToStringParser(context, enumerator.Current);438 if ((implOptions & SplitImplOptions.TrimContent) != 0)439 strValue = strValue.Trim();440 441 yield return strValue;442 }443 }444 445 private static RegexOptions parseRegexOptions(SplitOptions options)446 {447 RegexOptions result = RegexOptions.None;448 if ((options & SplitOptions.CultureInvariant) != 0)449 {450 result |= RegexOptions.CultureInvariant;451 }452 453 if ((options & SplitOptions.IgnorePatternWhitespace) != 0)454 {455 result |= RegexOptions.IgnorePatternWhitespace;456 }457 458 if ((options & SplitOptions.Multiline) != 0)459 {460 result |= RegexOptions.Multiline;461 }462 463 if ((options & SplitOptions.Singleline) != 0)464 {465 result |= RegexOptions.Singleline;466 }467 468 if ((options & SplitOptions.IgnoreCase) != 0)469 {470 result |= RegexOptions.IgnoreCase;471 }472 473 if ((options & SplitOptions.ExplicitCapture) != 0)474 {475 result |= RegexOptions.ExplicitCapture;476 }477 478 return result;479 }480 481 internal static object UnarySplitOperator(ExecutionContext context, IScriptExtent errorPosition, object lval)482 {483 // unary split does a little extra processing to make484 // whitespace processing more convenient. Specifically,485 // it will ignore leading/trailing whitespace.486 return SplitOperatorImpl(context, errorPosition, lval, new object[] { @"\s+" }, SplitImplOptions.TrimContent, false);487 }488 489 internal static object SplitOperator(ExecutionContext context, IScriptExtent errorPosition, object lval, object rval, bool ignoreCase)490 {491 return SplitOperatorImpl(context, errorPosition, lval, rval, SplitImplOptions.None, ignoreCase);492 }493 494 private static IReadOnlyList<string> SplitOperatorImpl(ExecutionContext context, IScriptExtent errorPosition, object lval, object rval, SplitImplOptions implOptions, bool ignoreCase)495 {496 IEnumerable<string> content = enumerateContent(context, errorPosition, implOptions, lval);497 498 ScriptBlock predicate = null;499 string separatorPattern = null;500 int limit = 0;501 SplitOptions options = 0;502 503 object[] args = unfoldTuple(context, errorPosition, rval);504 if (args.Length >= 1)505 {506 predicate = args[0] as ScriptBlock;507 if (predicate == null)508 {509 separatorPattern = PSObject.ToStringParser(context, args[0]);510 }511 }512 else513 {514 // The first argument to split is always required.515 throw InterpreterError.NewInterpreterException(rval, typeof(RuntimeException), errorPosition,516 "BadOperatorArgument", ParserStrings.BadOperatorArgument, "-split", rval);517 }518 519 if (args.Length >= 2)520 limit = FixNum(args[1], errorPosition);521 if (args.Length >= 3 && args[2] != null)522 {523 string args2asString = args[2] as string;524 if (args2asString == null || !string.IsNullOrEmpty(args2asString))525 {526 options = ConvertTo<SplitOptions>(args[2], errorPosition);527 if (predicate != null)528 {529 throw InterpreterError.NewInterpreterException(null, typeof(ParseException),530 errorPosition, "InvalidSplitOptionWithPredicate", ParserStrings.InvalidSplitOptionWithPredicate);531 }532 533 if (ignoreCase && (options & SplitOptions.IgnoreCase) == 0)534 {535 options |= SplitOptions.IgnoreCase;536 }537 }538 }539 else if (ignoreCase)540 {541 options |= SplitOptions.IgnoreCase;542 }543 544 if (predicate == null)545 {546 return SplitWithPattern(context, errorPosition, content, separatorPattern, limit, options);547 }548 else if (limit >= 0)549 {550 return SplitWithPredicate(context, errorPosition, content, predicate, limit);551 }552 else553 {554 return NegativeSplitWithPredicate(context, errorPosition, content, predicate, limit);555 }556 }557 558 private static IReadOnlyList<string> NegativeSplitWithPredicate(ExecutionContext context, IScriptExtent errorPosition, IEnumerable<string> content, ScriptBlock predicate, int limit)559 {560 var results = new List<string>();561 562 if (limit == -1)563 {564 // If the user just wants 1 string565 // then just return the content566 return new List<string>(content);567 }568 569 foreach (string item in content)570 {571 var split = new List<string>();572 573 // Used to traverse through the item574 int cursor = item.Length - 1;575 576 int subStringLength = 0;577 578 for (int charCount = 0; charCount < item.Length; charCount++)579 {580 // Evaluate the predicate using the character at cursor.581 object predicateResult = predicate.DoInvokeReturnAsIs(582 useLocalScope: true,583 errorHandlingBehavior: ScriptBlock.ErrorHandlingBehavior.WriteToExternalErrorPipe,584 dollarUnder: CharToString(item[cursor]),585 input: AutomationNull.Value,586 scriptThis: AutomationNull.Value,587 args: new object[] { item, cursor });588 589 if (!LanguagePrimitives.IsTrue(predicateResult))590 {591 subStringLength++;592 cursor -= 1;593 continue;594 }595 596 split.Add(item.Substring(cursor + 1, subStringLength));597 598 subStringLength = 0;599 600 cursor -= 1;601 602 if (System.Math.Abs(limit) == (split.Count + 1))603 {604 break;605 }606 }607 608 if (cursor == -1)609 {610 // Used when the limit is negative611 // and the cursor was allowed to go612 // all the way to the start of the613 // string.614 split.Add(item.Substring(0, subStringLength));615 }616 else617 {618 // Used to get the rest of the string619 // when using a negative limit and620 // the cursor doesn't reach the end621 // of the string.622 split.Add(item.Substring(0, cursor + 1));623 }624 625 split.Reverse();626 627 results.AddRange(split);628 }629 630 return results.ToArray();631 }632 633 private static IReadOnlyList<string> SplitWithPredicate(ExecutionContext context, IScriptExtent errorPosition, IEnumerable<string> content, ScriptBlock predicate, int limit)634 {635 var results = new List<string>();636 637 if (limit == 1)638 {639 // If the user just wants 1 string640 // then just return the content641 return new List<string>(content);642 }643 644 foreach (string item in content)645 {646 var split = new List<string>();647 648 // Used to traverse through the item649 int cursor = 0;650 651 // This is used to calculate how much to split from item.652 int subStringLength = 0;653 654 for (int charCount = 0; charCount < item.Length; charCount++)655 {656 // Evaluate the predicate using the character at cursor.657 object predicateResult = predicate.DoInvokeReturnAsIs(658 useLocalScope: true,659 errorHandlingBehavior: ScriptBlock.ErrorHandlingBehavior.WriteToExternalErrorPipe,660 dollarUnder: CharToString(item[cursor]),661 input: AutomationNull.Value,662 scriptThis: AutomationNull.Value,663 args: new object[] { item, cursor });664 665 // If the current character is not a delimiter666 // then it must be included into a substring.667 if (!LanguagePrimitives.IsTrue(predicateResult))668 {669 subStringLength++;670 671 cursor += 1;672 673 continue;674 }675 676 // Else, if the character is a delimiter677 // then add a substring to the split list.678 split.Add(item.Substring(cursor - subStringLength, subStringLength));679 680 subStringLength = 0;681 682 cursor += 1;683 684 if (limit == (split.Count + 1))685 {686 break;687 }688 }689 690 if (cursor == item.Length)691 {692 // Used to get the rest of the string693 // when the limit is not negative and694 // the cursor is allowed to make it to695 // the end of the string.696 split.Add(item.Substring(cursor - subStringLength, subStringLength));697 }698 else699 {700 // Used to get the rest of the string701 // when the limit is not negative and702 // the cursor is not at the end of the703 // string.704 split.Add(item.Substring(cursor, item.Length - cursor));705 }706 707 results.AddRange(split);708 }709 710 return results.ToArray();711 }712 713 private static IReadOnlyList<string> SplitWithPattern(ExecutionContext context, IScriptExtent errorPosition, IEnumerable<string> content, string separatorPattern, int limit, SplitOptions options)714 {715 // Default to Regex matching if no match specified.716 if ((options & SplitOptions.SimpleMatch) == 0 &&717 (options & SplitOptions.RegexMatch) == 0)718 {719 options |= SplitOptions.RegexMatch;720 }721 722 if ((options & SplitOptions.SimpleMatch) != 0)723 {724 if ((options & ~(SplitOptions.SimpleMatch | SplitOptions.IgnoreCase)) != 0)725 {726 throw InterpreterError.NewInterpreterException(null, typeof(ParseException),727 errorPosition, "InvalidSplitOptionCombination", ParserStrings.InvalidSplitOptionCombination);728 }729 }730 731 if ((options & SplitOptions.SimpleMatch) != 0)732 {733 separatorPattern = Regex.Escape(separatorPattern);734 }735 736 RegexOptions regexOptions = parseRegexOptions(options);737 738 int calculatedLimit = limit;739 740 // If the limit is negative then set Regex to read from right to left741 if (calculatedLimit < 0)742 {743 regexOptions |= RegexOptions.RightToLeft;744 calculatedLimit *= -1;745 }746 747 Regex regex = NewRegex(separatorPattern, regexOptions);748 749 var results = new List<string>();750 751 foreach (string item in content)752 {753 string[] split = regex.Split(item, calculatedLimit);754 results.AddRange(split);755 }756 757 return results.ToArray();758 }759 760 /// <summary>761 /// Implementation of the PowerShell unary -join operator...762 /// </summary>763 /// <param name="context">The execution context to use.</param>764 /// <param name="errorPosition">The position to use for error reporting.</param>765 /// <param name="lval">Left operand.</param>766 /// <returns>The result of the operator.</returns>767 internal static object UnaryJoinOperator(ExecutionContext context, IScriptExtent errorPosition, object lval)768 {769 return JoinOperator(context, errorPosition, lval, string.Empty);770 }771 772 /// <summary>773 /// Implementation of the PowerShell binary -join operator.774 /// </summary>775 /// <param name="context">The execution context to use.</param>776 /// <param name="errorPosition">The position to use for error reporting.</param>777 /// <param name="lval">Left operand.</param>778 /// <param name="rval">Right operand.</param>779 /// <returns>The result of the operator.</returns>780 internal static object JoinOperator(ExecutionContext context, IScriptExtent errorPosition, object lval, object rval)781 {782 string separator = PSObject.ToStringParser(context, rval);783 784 // PSObject already has join functionality; just expose it785 // as an operator.786 IEnumerable enumerable = LanguagePrimitives.GetEnumerable(lval);787 if (enumerable != null)788 {789 return PSObject.ToStringEnumerable(context, enumerable, separator, null, null);790 }791 else792 {793 return PSObject.ToStringParser(context, lval);794 }795 }796 797 /// <summary>798 /// The implementation of the PowerShell range operator.799 /// </summary>800 /// <param name="lval">The object on which to start.</param>801 /// <param name="rval">The object on which to stop.</param>802 /// <returns>The array of objects.</returns>803 internal static object RangeOperator(object lval, object rval)804 {805 var lbase = PSObject.Base(lval);806 var rbase = PSObject.Base(rval);807 808 // If both arguments is [char] type or [string] type with length==1809 // return objects of [char] type.810 // In special case "0".."9" return objects of [int] type.811 if (AsChar(lbase) is char lc && AsChar(rbase) is char rc)812 {813 return CharOps.Range(lc, rc);814 }815 816 // As a last resort, the range operator tries to return objects of [int] type.817 // 1..10818 // "1".."10"819 // [int]"1"..[int]"10"820 var l = Convert.ToInt32(lbase);821 var r = Convert.ToInt32(rbase);822 823 return IntOps.Range(l, r);824 }825 826 /// <summary>827 /// The implementation of an enumerator for the PowerShell range operator.828 /// </summary>829 /// <param name="lval">The object on which to start.</param>830 /// <param name="rval">The object on which to stop.</param>831 /// <returns>The enumerator.</returns>832 internal static IEnumerator GetRangeEnumerator(object lval, object rval)833 {834 var lbase = PSObject.Base(lval);835 var rbase = PSObject.Base(rval);836 837 // If both arguments is [char] type or [string] type with length==1838 // return objects of [char] type.839 // In special case "0".."9" return objects of [int] type.840 if (AsChar(lbase) is char lc && AsChar(rbase) is char rc)841 {842 return new CharRangeEnumerator(lc, rc);843 }844 845 // As a last resort, the range operator tries to return objects of [int] type.846 // 1..10847 // "1".."10"848 // [int]"1"..[int]"10"849 var l = Convert.ToInt32(lbase);850 var r = Convert.ToInt32(rbase);851 852 return new RangeEnumerator(l, r);853 }854 855 // Help function for Range operator.856 //857 // In common case:858 // for [char] type859 // for [string] type and length == 1860 // return objects of [char] type:861 // [char]'A'..[char]'Z'862 // [char]'A'..'Z'863 // [char]'A'.."Z"864 // 'A'..[char]'Z'865 // "A"..[char]'Z'866 // "A".."Z"867 // [char]"A"..[string]"Z"868 // "A"..[char]"Z"869 // [string]"A".."Z"870 // and so on.871 //872 // In special case:873 // "0".."9"874 // return objects of [int] type.875 private static object AsChar(object obj)876 {877 if (obj is char) return obj;878 if (obj is string str && str.Length == 1 && !char.IsDigit(str, 0)) return str[0];879 return null;880 }881 882 /// <summary>883 /// The implementation of the PowerShell -replace operator....884 /// </summary>885 /// <param name="context">The execution context in which to evaluate the expression.</param>886 /// <param name="errorPosition">The position to use for error reporting.</param>887 /// <param name="lval">The object on which to replace the values.</param>888 /// <param name="rval">The replacement description.</param>889 /// <param name="ignoreCase">True for -ireplace/-replace, false for -creplace.</param>890 /// <returns>The result of the operator.</returns>891 internal static object ReplaceOperator(ExecutionContext context, IScriptExtent errorPosition, object lval, object rval, bool ignoreCase)892 {893 object pattern = string.Empty;894 object substitute = string.Empty;895 896 rval = PSObject.Base(rval);897 IList rList = rval as IList;898 if (rList != null)899 {900 if (rList.Count > 2)901 {902 // only allow 1 or 2 arguments to -replace903 throw InterpreterError.NewInterpreterException(rval, typeof(RuntimeException), errorPosition,904 "BadReplaceArgument", ParserStrings.BadReplaceArgument, errorPosition.Text, rList.Count);905 }906 907 if (rList.Count > 0)908 {909 pattern = rList[0];910 if (rList.Count > 1)911 {912 substitute = PSObject.Base(rList[1]);913 }914 }915 }916 else917 {918 pattern = rval;919 }920 921 RegexOptions rreOptions = RegexOptions.None;922 923 if (ignoreCase)924 {925 rreOptions = RegexOptions.IgnoreCase;926 }927 928 Regex rr = pattern as Regex;929 if (rr == null)930 {931 try932 {933 rr = NewRegex((string)PSObject.ToStringParser(context, pattern), rreOptions);934 }935 catch (ArgumentException ae)936 {937 throw InterpreterError.NewInterpreterExceptionWithInnerException(pattern, typeof(RuntimeException),938 null, "InvalidRegularExpression", ParserStrings.InvalidRegularExpression, ae, pattern);939 }940 }941 942 var replacer = ReplaceOperatorImpl.Create(context, rr, substitute);943 IEnumerator list = LanguagePrimitives.GetEnumerator(lval);944 if (list == null)945 {946 string lvalString = PSObject.ToStringParser(context, lval) ?? string.Empty;947 948 return replacer.Replace(lvalString);949 }950 else951 {952 List<object> resultList = new List<object>();953 while (ParserOps.MoveNext(context, errorPosition, list))954 {955 string lvalString = PSObject.ToStringParser(context, ParserOps.Current(errorPosition, list));956 resultList.Add(replacer.Replace(lvalString));957 }958 959 return resultList.ToArray();960 }961 }962 963 private struct ReplaceOperatorImpl964 {965 public static ReplaceOperatorImpl Create(ExecutionContext context, Regex regex, object substitute)966 {967 return new ReplaceOperatorImpl(context, regex, substitute);968 }969 970 private readonly Regex _regex;971 private readonly string _cachedReplacementString;972 private readonly MatchEvaluator _cachedMatchEvaluator;973 974 private ReplaceOperatorImpl(975 ExecutionContext context,976 Regex regex,977 object substitute)978 {979 _regex = regex;980 _cachedReplacementString = null;981 _cachedMatchEvaluator = null;982 983 switch (substitute)984 {985 case string replacement:986 _cachedReplacementString = replacement;987 break;988 989 case ScriptBlock sb:990 _cachedMatchEvaluator = GetMatchEvaluator(context, sb);991 break;992 993 case object val when LanguagePrimitives.TryConvertTo(val, out _cachedMatchEvaluator):994 break;995 996 default:997 _cachedReplacementString = PSObject.ToStringParser(context, substitute);998 break;999 }1000 }1001 1002 // Local helper function to avoid creating an instance of the generated delegate helper class1003 // every time 'ReplaceOperatorImpl' is invoked.1004 private static MatchEvaluator GetMatchEvaluator(ExecutionContext context, ScriptBlock sb)1005 {1006 return match =>1007 {1008 var result = sb.DoInvokeReturnAsIs(1009 useLocalScope: false, /* Use current scope to be consistent with 'ForEach/Where-Object {}' and 'collection.ForEach{}/Where{}' */1010 errorHandlingBehavior: ScriptBlock.ErrorHandlingBehavior.WriteToCurrentErrorPipe,1011 dollarUnder: match,1012 input: AutomationNull.Value,1013 scriptThis: AutomationNull.Value,1014 args: Array.Empty<object>());1015 1016 return PSObject.ToStringParser(context, result);1017 };1018 }1019 1020 /// <summary>1021 /// ReplaceOperator implementation.1022 /// Abstracts away conversion of the optional substitute parameter to either a string or a MatchEvaluator delegate1023 /// and finally returns the result of the final Regex.Replace operation.1024 /// </summary>1025 public object Replace(string input)1026 {1027 if (_cachedReplacementString is not null)1028 {1029 return _regex.Replace(input, _cachedReplacementString);1030 }1031 1032 Dbg.Assert(_cachedMatchEvaluator is not null, "_cachedMatchEvaluator should be not null when code reach here.");1033 return _regex.Replace(input, _cachedMatchEvaluator);1034 }1035 }1036 1037 /// <summary>1038 /// Implementation of the PowerShell type operators...1039 /// </summary>1040 /// <param name="context">The execution context to use.</param>1041 /// <param name="errorPosition">The position to use for error reporting.</param>1042 /// <param name="left">Left operand.</param>1043 /// <param name="right">Right operand.</param>1044 /// <returns>The result of the operator.</returns>1045 internal static object IsOperator(ExecutionContext context, IScriptExtent errorPosition, object left, object right)1046 {1047 object lval = PSObject.Base(left);1048 object rval = PSObject.Base(right);1049 1050 Type rType = rval as Type;1051 1052 if (rType == null)1053 {1054 rType = ConvertTo<Type>(rval, errorPosition);1055 1056 if (rType == null)1057 {1058 // "the right operand of '-is' must be a type"1059 throw InterpreterError.NewInterpreterException(rval, typeof(RuntimeException),1060 errorPosition, "IsOperatorRequiresType", ParserStrings.IsOperatorRequiresType);1061 }1062 }1063 1064 if (rType == typeof(PSCustomObject) && lval is PSObject)1065 {1066 Diagnostics.Assert(rType.IsInstanceOfType(((PSObject)lval).ImmediateBaseObject), "Unexpect PSObject");1067 return _TrueObject;1068 }1069 1070 if (rType.Equals(typeof(PSObject)) && left is PSObject)1071 {1072 return _TrueObject;1073 }1074 1075 return BoolToObject(rType.IsInstanceOfType(lval));1076 }1077 1078 /// <summary>1079 /// Implementation of the PowerShell type operators...1080 /// </summary>1081 /// <param name="context">The execution context to use.</param>1082 /// <param name="errorPosition">The position to use for error reporting.</param>1083 /// <param name="left">Left operand.</param>1084 /// <param name="right">Right operand.</param>1085 /// <returns>The result of the operator.</returns>1086 internal static object IsNotOperator(ExecutionContext context, IScriptExtent errorPosition, object left, object right)1087 {1088 object lval = PSObject.Base(left);1089 object rval = PSObject.Base(right);1090 1091 Type rType = rval as Type;1092 1093 if (rType == null)1094 {1095 rType = ConvertTo<Type>(rval, errorPosition);1096 1097 if (rType == null)1098 {1099 // "the right operand of '-is' must be a type"1100 throw InterpreterError.NewInterpreterException(rval, typeof(RuntimeException),1101 errorPosition, "IsOperatorRequiresType", ParserStrings.IsOperatorRequiresType);1102 }1103 }1104 1105 if (rType == typeof(PSCustomObject) && lval is PSObject)1106 {1107 Diagnostics.Assert(rType.IsInstanceOfType(((PSObject)lval).ImmediateBaseObject), "Unexpect PSObject");1108 return _FalseObject;1109 }1110 1111 if (rType.Equals(typeof(PSObject)) && left is PSObject)1112 {1113 return _FalseObject;1114 }1115 1116 return BoolToObject(!rType.IsInstanceOfType(lval));1117 }1118 1119 /// <summary>1120 /// Implementation of the PowerShell -like operator.1121 /// </summary>1122 /// <param name="context">The execution context to use.</param>1123 /// <param name="errorPosition">The position to use for error reporting.</param>1124 /// <param name="lval">Left operand.</param>1125 /// <param name="rval">Right operand.</param>1126 /// <param name="operator">The operator.</param>1127 /// <returns>The result of the operator.</returns>1128 internal static object LikeOperator(ExecutionContext context, IScriptExtent errorPosition, object lval, object rval, TokenKind @operator)1129 {1130 var wcp = rval as WildcardPattern;1131 if (wcp == null)1132 {1133 var ignoreCase = @operator == TokenKind.Ilike || @operator == TokenKind.Inotlike;1134 wcp = WildcardPattern.Get(PSObject.ToStringParser(context, rval),1135 ignoreCase ? WildcardOptions.IgnoreCase : WildcardOptions.None);1136 }1137 1138 bool notLike = @operator == TokenKind.Inotlike || @operator == TokenKind.Cnotlike;1139 IEnumerator list = LanguagePrimitives.GetEnumerator(lval);1140 if (list == null)1141 {1142 string lvalString = lval == null ? string.Empty : PSObject.ToStringParser(context, lval);1143 1144 return BoolToObject(wcp.IsMatch(lvalString) ^ notLike);1145 }1146 1147 List<object> resultList = new List<object>();1148 1149 while (ParserOps.MoveNext(context, errorPosition, list))1150 {1151 object val = ParserOps.Current(errorPosition, list);1152 1153 string lvalString = val == null ? string.Empty : PSObject.ToStringParser(context, val);1154 1155 if (wcp.IsMatch(lvalString) ^ notLike)1156 {1157 resultList.Add(lvalString);1158 }1159 }1160 1161 return resultList.ToArray();1162 }1163 1164 /// <summary>1165 /// Implementation of the PowerShell -match operator.1166 /// </summary>1167 /// <param name="context">The execution context to use.</param>1168 /// <param name="errorPosition">The position to use for error reporting.</param>1169 /// <param name="lval">Left operand.</param>1170 /// <param name="rval">Right operand.</param>1171 /// <param name="ignoreCase">Ignore case?</param>1172 /// <param name="notMatch">True for -notmatch, false for -match.</param>1173 /// <returns>The result of the operator.</returns>1174 internal static object MatchOperator(ExecutionContext context, IScriptExtent errorPosition, object lval, object rval, bool notMatch, bool ignoreCase)1175 {1176 RegexOptions reOptions = ignoreCase ? RegexOptions.IgnoreCase : RegexOptions.None;1177 1178 // if passed an explicit regex, just use it1179 // otherwise compile the expression.1180 // In this situation, creation of Regex should not fail. We are not1181 // processing ArgumentException in this case.1182 Regex r = PSObject.Base(rval) as Regex1183 ?? NewRegex(PSObject.ToStringParser(context, rval), reOptions);1184 1185 IEnumerator list = LanguagePrimitives.GetEnumerator(lval);1186 if (list == null)1187 {1188 string lvalString = lval == null ? string.Empty : PSObject.ToStringParser(context, lval);1189 1190 // Find a match in the string.1191 Match m = r.Match(lvalString);1192 1193 if (m.Success)1194 {1195 GroupCollection groups = m.Groups;1196 if (groups.Count > 0)1197 {1198 Hashtable h = new Hashtable(StringComparer.CurrentCultureIgnoreCase);1199 1200 foreach (string groupName in r.GetGroupNames())