MegaBites-AI/Windows-powershell
0372
1// Copyright (c) Microsoft Corporation.2// Licensed under the MIT License.3 4using System;5using System.Collections;6using System.Collections.Generic;7using System.Diagnostics.CodeAnalysis;8using System.Management.Automation;9using System.Management.Automation.Internal;10using System.Text;11 12namespace Microsoft.PowerShell.Commands.Internal.Format13{14 /// <summary>15 /// Normalized parameter class to be constructed from the command line parameters16 /// using the metadata information provided by an instance of CommandParameterDefinition17 /// it's basically the hash table with the normalized values.18 /// </summary>19 internal class MshParameter20 {21 internal Hashtable hash = null;22 23 internal object GetEntry(string key)24 {25 if (this.hash.ContainsKey(key))26 return this.hash[key];27 28 return AutomationNull.Value;29 }30 }31 32 internal class NameEntryDefinition : HashtableEntryDefinition33 {34 internal const string NameEntryKey = "name";35 36 internal NameEntryDefinition()37 : base(NameEntryKey, new string[] { FormatParameterDefinitionKeys.LabelEntryKey }, new Type[] { typeof(string) }, false)38 {39 }40 }41 42 /// <summary>43 /// Metadata base class for hashtable entry definitions44 /// it contains the key name and the allowable types45 /// it also provides hooks for type expansion.46 /// </summary>47 internal class HashtableEntryDefinition48 {49 internal HashtableEntryDefinition(string name, IEnumerable<string> secondaryNames, Type[] types, bool mandatory)50 : this(name, types, mandatory)51 {52 SecondaryNames = secondaryNames;53 }54 55 internal HashtableEntryDefinition(string name, Type[] types, bool mandatory)56 {57 KeyName = name;58 AllowedTypes = types;59 Mandatory = mandatory;60 }61 62 internal HashtableEntryDefinition(string name, Type[] types)63 : this(name, types, false)64 {65 }66 67 internal virtual Hashtable CreateHashtableFromSingleType(object val)68 {69 // NOTE: must override for the default type(s) entry70 // this entry will have to expand the object into a hash table71 throw PSTraceSource.NewNotSupportedException();72 }73 74 internal bool IsKeyMatch(string key)75 {76 if (CommandParameterDefinition.FindPartialMatch(key, this.KeyName))77 {78 return true;79 }80 81 if (this.SecondaryNames != null)82 {83 foreach (string secondaryKey in this.SecondaryNames)84 {85 if (CommandParameterDefinition.FindPartialMatch(key, secondaryKey))86 {87 return true;88 }89 }90 }91 92 return false;93 }94 95 internal virtual object Verify(object val,96 TerminatingErrorContext invocationContext,97 bool originalParameterWasHashTable)98 {99 return null;100 }101 102 internal virtual object ComputeDefaultValue()103 {104 return AutomationNull.Value;105 }106 107 internal string KeyName { get; }108 109 internal Type[] AllowedTypes { get; }110 111 internal bool Mandatory { get; }112 113 internal IEnumerable<string> SecondaryNames { get; }114 }115 116 /// <summary>117 /// Metadata abstract base class to contain hash entries definitions.118 /// </summary>119 internal abstract class CommandParameterDefinition120 {121 [SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors")]122 internal CommandParameterDefinition()123 {124 SetEntries();125 }126 127 protected abstract void SetEntries();128 129 internal virtual MshParameter CreateInstance() { return new MshParameter(); }130 131 /// <summary>132 /// For a key name, verify it is a legal entry:133 /// 1. it must match (partial match allowed)134 /// 2. it must be unambiguous (if partial match)135 /// If an error condition occurs, an exception will be thrown.136 /// </summary>137 /// <param name="keyName">Key to verify.</param>138 /// <param name="invocationContext">Invocation context for error reporting.</param>139 /// <returns>Matching hash table entry.</returns>140 /// <exception cref="ArgumentException"></exception>141 internal HashtableEntryDefinition MatchEntry(string keyName, TerminatingErrorContext invocationContext)142 {143 if (string.IsNullOrEmpty(keyName))144 PSTraceSource.NewArgumentNullException(nameof(keyName));145 146 HashtableEntryDefinition matchingEntry = null;147 for (int k = 0; k < this.hashEntries.Count; k++)148 {149 if (this.hashEntries[k].IsKeyMatch(keyName))150 {151 // we have a match152 if (matchingEntry == null)153 {154 // this is the first match, we save the entry155 // and we keep going for ambiguity check156 matchingEntry = this.hashEntries[k];157 }158 else159 {160 // we already had a match, we have an ambiguous key161 ProcessAmbiguousKey(invocationContext, keyName, matchingEntry, this.hashEntries[k]);162 }163 }164 }165 166 if (matchingEntry != null)167 {168 // we found an unambiguous match169 return matchingEntry;170 }171 // we did not have a match172 ProcessIllegalKey(invocationContext, keyName);173 return null;174 }175 176 internal static bool FindPartialMatch(string key, string normalizedKey)177 {178 if (key.Length < normalizedKey.Length)179 {180 // shorter, could be an abbreviation181 if (key.AsSpan().Equals(normalizedKey.AsSpan(0, key.Length), StringComparison.OrdinalIgnoreCase))182 {183 // found abbreviation184 return true;185 }186 }187 188 if (string.Equals(key, normalizedKey, StringComparison.OrdinalIgnoreCase))189 {190 // found full match191 return true;192 }193 194 return false;195 }196 197 #region Error Processing198 199 private static void ProcessAmbiguousKey(TerminatingErrorContext invocationContext,200 string keyName,201 HashtableEntryDefinition matchingEntry,202 HashtableEntryDefinition currentEntry)203 {204 string msg = StringUtil.Format(FormatAndOut_MshParameter.AmbiguousKeyError,205 keyName, matchingEntry.KeyName, currentEntry.KeyName);206 207 ParameterProcessor.ThrowParameterBindingException(invocationContext, "DictionaryKeyAmbiguous", msg);208 }209 210 private static void ProcessIllegalKey(TerminatingErrorContext invocationContext,211 string keyName)212 {213 string msg = StringUtil.Format(FormatAndOut_MshParameter.IllegalKeyError, keyName);214 215 ParameterProcessor.ThrowParameterBindingException(invocationContext, "DictionaryKeyIllegal", msg);216 }217 #endregion218 219 internal List<HashtableEntryDefinition> hashEntries = new List<HashtableEntryDefinition>();220 }221 222 /// <summary>223 /// Engine to process a generic object[] from the command line and224 /// generate a list of MshParameter objects , given the metadata provided by225 /// a class derived from CommandParameterDefinition.226 /// </summary>227 internal sealed class ParameterProcessor228 {229 #region tracer230 [TraceSource("ParameterProcessor", "ParameterProcessor")]231 internal static readonly PSTraceSource tracer = PSTraceSource.GetTracer("ParameterProcessor", "ParameterProcessor");232 #endregion tracer233 234 internal static void ThrowParameterBindingException(TerminatingErrorContext invocationContext,235 string errorId,236 string msg)237 {238 ErrorRecord errorRecord = new ErrorRecord(239 new NotSupportedException(),240 errorId,241 ErrorCategory.InvalidArgument,242 null);243 244 errorRecord.ErrorDetails = new ErrorDetails(msg);245 invocationContext.ThrowTerminatingError(errorRecord);246 }247 248 internal ParameterProcessor(CommandParameterDefinition p)249 {250 _paramDef = p;251 }252 253 /// <exception cref="ArgumentException"></exception>254 internal List<MshParameter> ProcessParameters(object[] p, TerminatingErrorContext invocationContext)255 {256 if (p == null || p.Length == 0)257 return null;258 259 List<MshParameter> retVal = new List<MshParameter>();260 MshParameter currParam;261 262 bool originalParameterWasHashTable = false;263 for (int k = 0; k < p.Length; k++)264 {265 // we always copy into a fresh hash table266 currParam = _paramDef.CreateInstance();267 var actualObject = PSObject.Base(p[k]);268 if (actualObject is IDictionary)269 {270 originalParameterWasHashTable = true;271 currParam.hash = VerifyHashTable((IDictionary)actualObject, invocationContext);272 }273 else if ((actualObject != null) && MatchesAllowedTypes(actualObject.GetType(), _paramDef.hashEntries[0].AllowedTypes))274 {275 // a simple type was specified, build a hash with one entry276 currParam.hash = _paramDef.hashEntries[0].CreateHashtableFromSingleType(actualObject);277 }278 else279 {280 // unknown type, error281 // provide error message (the user did not enter a hash table)282 ProcessUnknownParameterType(invocationContext, actualObject, _paramDef.hashEntries[0].AllowedTypes);283 }284 285 // value range validation and post processing on the hash table286 VerifyAndNormalizeParameter(currParam, invocationContext, originalParameterWasHashTable);287 retVal.Add(currParam);288 }289 290 return retVal;291 }292 293 private static bool MatchesAllowedTypes(Type t, Type[] allowedTypes)294 {295 for (int k = 0; k < allowedTypes.Length; k++)296 {297 if (allowedTypes[k].IsAssignableFrom(t))298 return true;299 }300 301 return false;302 }303 304 /// <exception cref="ArgumentException"></exception>305 private Hashtable VerifyHashTable(IDictionary hash, TerminatingErrorContext invocationContext)306 {307 // full blown hash, need to:308 // 1. verify names(keys) and expand names if there are partial matches309 // 2. verify value types310 Hashtable retVal = new Hashtable();311 312 foreach (DictionaryEntry e in hash)313 {314 if (e.Key == null)315 {316 ProcessNullHashTableKey(invocationContext);317 }318 319 string currentStringKey = e.Key as string;320 if (currentStringKey == null)321 {322 ProcessNonStringHashTableKey(invocationContext, e.Key);323 }324 325 // find a match for the key326 HashtableEntryDefinition def = _paramDef.MatchEntry(currentStringKey, invocationContext);327 if (retVal.Contains(def.KeyName))328 {329 // duplicate key error330 ProcessDuplicateHashTableKey(invocationContext, currentStringKey, def.KeyName);331 }332 333 // now the key is verified, need to check the type334 bool matchType = false;335 336 if (def.AllowedTypes == null || def.AllowedTypes.Length == 0)337 {338 // we match on any type, it will be up to the entry to further check339 matchType = true;340 }341 else342 {343 for (int t = 0; t < def.AllowedTypes.Length; t++)344 {345 if (e.Value == null)346 {347 ProcessMissingKeyValue(invocationContext, currentStringKey);348 }349 350 if (def.AllowedTypes[t].IsAssignableFrom(e.Value.GetType()))351 {352 matchType = true;353 break;354 }355 }356 }357 358 if (!matchType)359 {360 // bad type error361 ProcessIllegalHashTableKeyValue(invocationContext, currentStringKey, e.Value.GetType(), def.AllowedTypes);362 }363 364 retVal.Add(def.KeyName, e.Value);365 }366 367 return retVal;368 }369 370 /// <exception cref="ArgumentException"></exception>371 private void VerifyAndNormalizeParameter(MshParameter parameter,372 TerminatingErrorContext invocationContext,373 bool originalParameterWasHashTable)374 {375 for (int k = 0; k < _paramDef.hashEntries.Count; k++)376 {377 if (parameter.hash.ContainsKey(_paramDef.hashEntries[k].KeyName))378 {379 // we have a key, just do some post processing normalization380 // retrieve the value381 object val = parameter.hash[_paramDef.hashEntries[k].KeyName];382 object newVal = _paramDef.hashEntries[k].Verify(val, invocationContext, originalParameterWasHashTable);383 384 if (newVal != null)385 {386 // if a new value is provided, we need to update the hash entry387 parameter.hash[_paramDef.hashEntries[k].KeyName] = newVal;388 }389 }390 else391 {392 // we do not have the key, we might want to have a default value393 object defaultValue = _paramDef.hashEntries[k].ComputeDefaultValue();394 395 if (defaultValue != AutomationNull.Value)396 {397 // we have a default value, add it398 parameter.hash[_paramDef.hashEntries[k].KeyName] = defaultValue;399 }400 else if (_paramDef.hashEntries[k].Mandatory)401 {402 // no default value and mandatory: we cannot proceed403 ProcessMissingMandatoryKey(invocationContext, _paramDef.hashEntries[k].KeyName);404 }405 }406 }407 }408 409 #region Error Processing410 411 private static void ProcessUnknownParameterType(TerminatingErrorContext invocationContext, object actualObject, Type[] allowedTypes)412 {413 string allowedTypesList = CatenateTypeArray(allowedTypes);414 string msg;415 416 if (actualObject != null)417 {418 msg = StringUtil.Format(FormatAndOut_MshParameter.UnknownParameterTypeError,419 actualObject.GetType().FullName, allowedTypesList);420 }421 else422 {423 msg = StringUtil.Format(FormatAndOut_MshParameter.NullParameterTypeError,424 allowedTypesList);425 }426 427 ParameterProcessor.ThrowParameterBindingException(invocationContext, "DictionaryKeyUnknownType", msg);428 }429 430 private static void ProcessDuplicateHashTableKey(TerminatingErrorContext invocationContext, string duplicateKey, string existingKey)431 {432 string msg = StringUtil.Format(FormatAndOut_MshParameter.DuplicateKeyError,433 duplicateKey, existingKey);434 435 ParameterProcessor.ThrowParameterBindingException(invocationContext, "DictionaryKeyDuplicate", msg);436 }437 438 private static void ProcessNullHashTableKey(TerminatingErrorContext invocationContext)439 {440 string msg = StringUtil.Format(FormatAndOut_MshParameter.DictionaryKeyNullError);441 ParameterProcessor.ThrowParameterBindingException(invocationContext, "DictionaryKeyNull", msg);442 }443 444 private static void ProcessNonStringHashTableKey(TerminatingErrorContext invocationContext, object key)445 {446 string msg = StringUtil.Format(FormatAndOut_MshParameter.DictionaryKeyNonStringError, key.GetType().Name);447 ParameterProcessor.ThrowParameterBindingException(invocationContext, "DictionaryKeyNonString", msg);448 }449 450 private static void ProcessIllegalHashTableKeyValue(TerminatingErrorContext invocationContext, string key, Type actualType, Type[] allowedTypes)451 {452 string msg;453 string errorID;454 if (allowedTypes.Length > 1)455 {456 string legalTypes = CatenateTypeArray(allowedTypes);457 458 msg = StringUtil.Format(FormatAndOut_MshParameter.IllegalTypeMultiError,459 key,460 actualType.FullName,461 legalTypes462 );463 464 errorID = "DictionaryKeyIllegalValue1";465 }466 else467 {468 msg = StringUtil.Format(FormatAndOut_MshParameter.IllegalTypeSingleError,469 key,470 actualType.FullName,471 allowedTypes[0]472 );473 474 errorID = "DictionaryKeyIllegalValue2";475 }476 477 ParameterProcessor.ThrowParameterBindingException(invocationContext, errorID, msg);478 }479 480 private static void ProcessMissingKeyValue(TerminatingErrorContext invocationContext, string keyName)481 {482 string msg = StringUtil.Format(FormatAndOut_MshParameter.MissingKeyValueError, keyName);483 ParameterProcessor.ThrowParameterBindingException(invocationContext, "DictionaryKeyMissingValue", msg);484 }485 486 private static void ProcessMissingMandatoryKey(TerminatingErrorContext invocationContext, string keyName)487 {488 string msg = StringUtil.Format(FormatAndOut_MshParameter.MissingKeyMandatoryEntryError, keyName);489 ParameterProcessor.ThrowParameterBindingException(invocationContext, "DictionaryKeyMandatoryEntry", msg);490 }491 492 #endregion493 494 #region Utilities495 496 private static string CatenateTypeArray(Type[] arr)497 {498 string[] strings = new string[arr.Length];499 for (int k = 0; k < arr.Length; k++)500 {501 strings[k] = arr[k].FullName;502 }503 504 return CatenateStringArray(strings);505 }506 507 internal static string CatenateStringArray(string[] arr)508 {509 StringBuilder sb = new StringBuilder();510 sb.Append('{');511 for (int k = 0; k < arr.Length; k++)512 {513 if (k > 0)514 {515 sb.Append(", ");516 }517 518 sb.Append(arr[k]);519 }520 521 sb.Append('}');522 return sb.ToString();523 }524 525 #endregion526 527 private readonly CommandParameterDefinition _paramDef = null;528 }529}530 