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.Collections.ObjectModel;8using System.Globalization;9using System.IO;10using System.Linq;11using System.Text;12using System.Text.RegularExpressions;13using System.Xml;14using System.Xml.Schema;15 16using Microsoft.PowerShell.Cmdletization.Xml;17using System.Management.Automation;18using System.Management.Automation.Language;19using Microsoft.PowerShell.Commands;20using Dbg = System.Management.Automation.Diagnostics;21 22namespace Microsoft.PowerShell.Cmdletization23{24 internal sealed class ScriptWriter25 {26 #region Static code reused for reading cmdletization xml27 28 private static readonly XmlReaderSettings s_xmlReaderSettings;29 30 static ScriptWriter()31 {32 //33 // XmlReaderSettings34 //35 ScriptWriter.s_xmlReaderSettings = new XmlReaderSettings();36 // general settings37 ScriptWriter.s_xmlReaderSettings.CheckCharacters = true;38 ScriptWriter.s_xmlReaderSettings.CloseInput = false;39 ScriptWriter.s_xmlReaderSettings.ConformanceLevel = ConformanceLevel.Document;40 ScriptWriter.s_xmlReaderSettings.IgnoreComments = true;41 ScriptWriter.s_xmlReaderSettings.IgnoreProcessingInstructions = true;42 ScriptWriter.s_xmlReaderSettings.IgnoreWhitespace = false;43 ScriptWriter.s_xmlReaderSettings.MaxCharactersFromEntities = 16384; // generous guess for the upper bound44 ScriptWriter.s_xmlReaderSettings.MaxCharactersInDocument = 128 * 1024 * 1024; // generous guess for the upper bound45 46#if CORECLR // The XML Schema file 'cmdlets-over-objects.xsd' is missing in Github, and it's likely the resource string47 // 'CmdletizationCoreResources.Xml_cmdletsOverObjectsXsd' needs to be reworked to work in .NET Core.48 ScriptWriter.s_xmlReaderSettings.DtdProcessing = DtdProcessing.Ignore;49#else50 ScriptWriter.s_xmlReaderSettings.DtdProcessing = DtdProcessing.Parse; // Allowing DTD parsing with limits of MaxCharactersFromEntities/MaxCharactersInDocument51 ScriptWriter.s_xmlReaderSettings.XmlResolver = null; // do not fetch external documents52 // xsd schema related settings53 ScriptWriter.s_xmlReaderSettings.ValidationFlags = XmlSchemaValidationFlags.ProcessIdentityConstraints |54 XmlSchemaValidationFlags.ReportValidationWarnings;55 ScriptWriter.s_xmlReaderSettings.ValidationType = ValidationType.Schema;56 string cmdletizationXsd = CmdletizationCoreResources.Xml_cmdletsOverObjectsXsd;57 XmlReader cmdletizationSchemaReader = XmlReader.Create(new StringReader(cmdletizationXsd), ScriptWriter.s_xmlReaderSettings);58 ScriptWriter.s_xmlReaderSettings.Schemas = new XmlSchemaSet();59 ScriptWriter.s_xmlReaderSettings.Schemas.Add(null, cmdletizationSchemaReader);60 ScriptWriter.s_xmlReaderSettings.Schemas.XmlResolver = null; // do not fetch external documents61#endif62 }63 64 #endregion Static code reused for reading cmdletization xml65 66 #region Constructors / setup code67 68 [Flags]69 internal enum GenerationOptions70 {71 TypesPs1Xml = 1,72 FormatPs1Xml = 2,73 HelpXml = 4,74 }75 76 private readonly PowerShellMetadata _cmdletizationMetadata;77 private readonly string _moduleName;78 private readonly Type _objectModelWrapper;79 private readonly Type _objectInstanceType;80 private readonly InvocationInfo _invocationInfo;81 private readonly GenerationOptions _generationOptions;82 83 internal ScriptWriter(84 TextReader cmdletizationXmlReader,85 string moduleName,86 string defaultObjectModelWrapper,87 InvocationInfo invocationInfo,88 GenerationOptions generationOptions)89 {90 Dbg.Assert(cmdletizationXmlReader != null, "Caller should verify that cmdletizationXmlReader != null");91 Dbg.Assert(!string.IsNullOrEmpty(moduleName), "Caller should verify that moduleName != null");92 Dbg.Assert(invocationInfo != null, "Caller should verify that invocationInfo != null");93 Dbg.Assert(!string.IsNullOrEmpty(defaultObjectModelWrapper), "Caller should verify that defaultObjectModelWrapper != null");94 95 XmlReader xmlReader = XmlReader.Create(cmdletizationXmlReader, ScriptWriter.s_xmlReaderSettings);96 try97 {98 var xmlSerializer = new PowerShellMetadataSerializer();99 _cmdletizationMetadata = (PowerShellMetadata)xmlSerializer.Deserialize(xmlReader);100 }101 catch (InvalidOperationException e)102 {103 if (e.InnerException is XmlSchemaException schemaException)104 {105 throw new XmlException(schemaException.Message, schemaException, schemaException.LineNumber, schemaException.LinePosition);106 }107 108 if (e.InnerException is XmlException xmlException)109 {110 throw xmlException;111 }112 113 if (e.InnerException != null)114 {115 string message = string.Format(116 CultureInfo.CurrentCulture,117 CmdletizationCoreResources.ScriptWriter_ConcatenationOfDeserializationExceptions,118 e.Message,119 e.InnerException.Message);120 121 throw new InvalidOperationException(message, e.InnerException);122 }123 124 throw;125 }126 127 string objectModelWrapperName = _cmdletizationMetadata.Class.CmdletAdapter ?? defaultObjectModelWrapper;128 _objectModelWrapper = (Type)LanguagePrimitives.ConvertTo(objectModelWrapperName, typeof(Type), CultureInfo.InvariantCulture);129 if (_objectModelWrapper.IsGenericType)130 {131 string message = string.Format(132 CultureInfo.CurrentCulture,133 CmdletizationCoreResources.ScriptWriter_ObjectModelWrapperIsStillGeneric,134 objectModelWrapperName);135 throw new XmlException(message);136 }137 138 Type baseType = _objectModelWrapper;139 while ((!baseType.IsGenericType) || baseType.GetGenericTypeDefinition() != typeof(CmdletAdapter<>))140 {141 baseType = baseType.BaseType;142 if (baseType == typeof(object))143 {144 string message = string.Format(145 CultureInfo.CurrentCulture,146 CmdletizationCoreResources.ScriptWriter_ObjectModelWrapperNotDerivedFromObjectModelWrapper,147 objectModelWrapperName,148 typeof(CmdletAdapter<>).FullName);149 throw new XmlException(message);150 }151 }152 153 _objectInstanceType = baseType.GetGenericArguments()[0];154 155 _moduleName = moduleName;156 _invocationInfo = invocationInfo;157 _generationOptions = generationOptions;158 }159 160 #endregion Constructors / setup code161 162 #region psm1163 164 private const string HeaderTemplate = @"165#requires -version 3.0166 167try {{ Microsoft.PowerShell.Core\Set-StrictMode -Off }} catch {{ }}168 169$script:MyModule = $MyInvocation.MyCommand.ScriptBlock.Module170 171$script:ClassName = '{0}'172$script:ClassVersion = '{1}'173$script:ModuleVersion = '{2}'174$script:ObjectModelWrapper = [{3}]175 176$script:PrivateData = [System.Collections.Generic.Dictionary[string,string]]::new()177 178Microsoft.PowerShell.Core\Export-ModuleMember -Function @()179 ";180 181 private void WriteModulePreamble(TextWriter output)182 {183 output.WriteLine(184 ScriptWriter.HeaderTemplate,185 CodeGeneration.EscapeSingleQuotedStringContent(_cmdletizationMetadata.Class.ClassName),186 CodeGeneration.EscapeSingleQuotedStringContent(_cmdletizationMetadata.Class.ClassVersion ?? string.Empty),187 CodeGeneration.EscapeSingleQuotedStringContent(new Version(_cmdletizationMetadata.Class.Version).ToString()),188 CodeGeneration.EscapeSingleQuotedStringContent(_objectModelWrapper.FullName));189 190 if (_cmdletizationMetadata.Class.CmdletAdapterPrivateData != null)191 {192 foreach (ClassMetadataData data in _cmdletizationMetadata.Class.CmdletAdapterPrivateData)193 {194 output.WriteLine(195 "$script:PrivateData.Add('{0}', '{1}')",196 CodeGeneration.EscapeSingleQuotedStringContent(data.Name),197 CodeGeneration.EscapeSingleQuotedStringContent(data.Value));198 }199 }200 }201 202 private void WriteBindCommonParametersFunction(TextWriter output)203 {204 output.WriteLine(@"205function __cmdletization_BindCommonParameters206{207 param(208 $__cmdletization_objectModelWrapper,209 $myPSBoundParameters210 )211 ");212 213 foreach (ParameterMetadata commonParameter in this.GetCommonParameters().Values)214 {215 output.WriteLine(@"216 if ($myPSBoundParameters.ContainsKey('{0}')) {{217 $__cmdletization_objectModelWrapper.PSObject.Properties['{0}'].Value = $myPSBoundParameters['{0}']218 }}219 ",220 CodeGeneration.EscapeSingleQuotedStringContent(commonParameter.Name));221 }222 223 output.WriteLine(@"224}225 ");226 }227 228 private string GetCmdletName(CommonCmdletMetadata cmdletMetadata)229 {230 string noun = cmdletMetadata.Noun ?? _cmdletizationMetadata.Class.DefaultNoun;231 string verb = cmdletMetadata.Verb;232 return verb + "-" + noun;233 }234 235 private static string GetCmdletAttributes(CommonCmdletMetadata cmdletMetadata)236 {237 // Generate the script for the Alias and Obsolete Attribute if any is declared in CDXML238 StringBuilder attributes = new(150);239 if (cmdletMetadata.Aliases != null)240 {241 attributes.Append("[Alias('" + string.Join("','", cmdletMetadata.Aliases.Select(static alias => CodeGeneration.EscapeSingleQuotedStringContent(alias))) + "')]");242 }243 244 if (cmdletMetadata.Obsolete != null)245 {246 string obsoleteMsg = (cmdletMetadata.Obsolete.Message != null)247 ? ("'" + CodeGeneration.EscapeSingleQuotedStringContent(cmdletMetadata.Obsolete.Message) + "'")248 : string.Empty;249 string newline = (attributes.Length > 0) ? Environment.NewLine : string.Empty;250 attributes.Append(CultureInfo.InvariantCulture, $"{newline}[Obsolete({obsoleteMsg})]");251 }252 253 return attributes.ToString();254 }255 256 private Dictionary<string, ParameterMetadata> GetCommonParameters()257 {258 Dictionary<string, ParameterMetadata> commonParameters = new(StringComparer.OrdinalIgnoreCase);259 260 InternalParameterMetadata internalParameterMetadata = new(_objectModelWrapper, false);261 foreach (CompiledCommandParameter compiledCommandParameter in internalParameterMetadata.BindableParameters.Values)262 {263 ParameterMetadata parameterMetadata = new(compiledCommandParameter);264 foreach (ParameterSetMetadata psetMetadata in parameterMetadata.ParameterSets.Values)265 {266 if (psetMetadata.ValueFromPipeline)267 {268 string message = string.Format(269 CultureInfo.InvariantCulture,270 CmdletizationCoreResources.ScriptWriter_ObjectModelWrapperUsesIgnoredParameterMetadata,271 _objectModelWrapper.FullName,272 parameterMetadata.Name,273 "ValueFromPipeline");274 throw new XmlException(message);275 }276 277 if (psetMetadata.ValueFromPipelineByPropertyName)278 {279 string message = string.Format(280 CultureInfo.InvariantCulture,281 CmdletizationCoreResources.ScriptWriter_ObjectModelWrapperUsesIgnoredParameterMetadata,282 _objectModelWrapper.FullName,283 parameterMetadata.Name,284 "ValueFromPipelineByPropertyName");285 throw new XmlException(message);286 }287 288 if (psetMetadata.ValueFromRemainingArguments)289 {290 string message = string.Format(291 CultureInfo.InvariantCulture,292 CmdletizationCoreResources.ScriptWriter_ObjectModelWrapperUsesIgnoredParameterMetadata,293 _objectModelWrapper.FullName,294 parameterMetadata.Name,295 "ValueFromRemainingArguments");296 throw new XmlException(message);297 }298 299 psetMetadata.ValueFromPipeline = false;300 psetMetadata.ValueFromPipelineByPropertyName = false;301 psetMetadata.ValueFromRemainingArguments = false;302 }303 304 commonParameters.Add(parameterMetadata.Name, parameterMetadata);305 }306 307 List<string> commonParameterSets = GetCommonParameterSets(commonParameters);308 if (commonParameterSets.Count > 1)309 {310 string message = string.Format(311 CultureInfo.InvariantCulture,312 CmdletizationCoreResources.ScriptWriter_ObjectModelWrapperDefinesMultipleParameterSets,313 _objectModelWrapper.FullName);314 throw new XmlException(message);315 }316 317 foreach (ParameterMetadata parameter in commonParameters.Values)318 {319 if ((parameter.ParameterSets.Count == 1) && (parameter.ParameterSets.ContainsKey(ParameterAttribute.AllParameterSets)))320 {321 ParameterSetMetadata oldParameterSetMetadata = parameter.ParameterSets[ParameterAttribute.AllParameterSets];322 323 parameter.ParameterSets.Clear();324 foreach (string parameterSetName in commonParameterSets)325 {326 parameter.ParameterSets.Add(parameterSetName, oldParameterSetMetadata);327 }328 }329 }330 331 return commonParameters;332 }333 334 private static List<string> GetCommonParameterSets(Dictionary<string, ParameterMetadata> commonParameters)335 {336 Dictionary<string, object> parameterSetNames = new(StringComparer.OrdinalIgnoreCase);337 foreach (ParameterMetadata parameter in commonParameters.Values)338 {339 foreach (string parameterSetName in parameter.ParameterSets.Keys)340 {341 if (!parameterSetName.Equals(ParameterAttribute.AllParameterSets))342 {343 parameterSetNames[parameterSetName] = null;344 }345 }346 }347 348 if (parameterSetNames.Count == 0)349 {350 parameterSetNames.Add(ParameterAttribute.AllParameterSets, null);351 }352 353 List<string> result = new(parameterSetNames.Keys);354 result.Sort(StringComparer.Ordinal); // to have a deterministic order of parameter sets (also means that Ordinal instead of OrdinalIgnoreCase is ok)355 return result;356 }357 358 private string GetMethodParameterSet(StaticMethodMetadata staticMethod)359 {360 Dbg.Assert(staticMethod != null, "Caller should verify that staticMethod != null");361 return staticMethod.CmdletParameterSet ?? GetMethodParameterSet((CommonMethodMetadata)staticMethod);362 }363 364 private List<string> GetMethodParameterSets(StaticCmdletMetadata staticCmdlet)365 {366 Dictionary<string, object> parameterSetNames = new(StringComparer.OrdinalIgnoreCase);367 368 foreach (StaticMethodMetadata method in staticCmdlet.Method)369 {370 string parameterSetName = GetMethodParameterSet(method);371 if (parameterSetNames.ContainsKey(parameterSetName))372 {373 string message = string.Format(374 CultureInfo.InvariantCulture,375 CmdletizationCoreResources.ScriptWriter_DuplicateParameterSetInStaticCmdlet,376 this.GetCmdletName(staticCmdlet.CmdletMetadata),377 parameterSetName);378 throw new XmlException(message);379 }380 381 parameterSetNames.Add(parameterSetName, null);382 }383 384 return new List<string>(parameterSetNames.Keys);385 }386 387 private readonly Dictionary<CommonMethodMetadata, int> _staticMethodMetadataToUniqueId = new();388 389 private string GetMethodParameterSet(CommonMethodMetadata methodMetadata)390 {391 Dbg.Assert(methodMetadata != null, "Caller should verify that instanceMethod != null");392 393 int uniqueId;394 if (!_staticMethodMetadataToUniqueId.TryGetValue(methodMetadata, out uniqueId))395 {396 uniqueId = _staticMethodMetadataToUniqueId.Count;397 _staticMethodMetadataToUniqueId.Add(methodMetadata, uniqueId);398 }399 400 return methodMetadata.MethodName + uniqueId;401 }402 403 private List<string> GetMethodParameterSets(InstanceCmdletMetadata instanceCmdlet)404 {405 Dictionary<string, object> parameterSetNames = new(StringComparer.OrdinalIgnoreCase);406 407 InstanceMethodMetadata method = instanceCmdlet.Method;408 string parameterSetName = GetMethodParameterSet(method);409 parameterSetNames.Add(parameterSetName, null);410 411 return new List<string>(parameterSetNames.Keys);412 }413 414 private GetCmdletParameters GetGetCmdletParameters(InstanceCmdletMetadata instanceCmdlet)415 {416 if (instanceCmdlet == null)417 {418 if ((_cmdletizationMetadata.Class.InstanceCmdlets.GetCmdlet != null) &&419 (_cmdletizationMetadata.Class.InstanceCmdlets.GetCmdlet.GetCmdletParameters != null))420 {421 return _cmdletizationMetadata.Class.InstanceCmdlets.GetCmdlet.GetCmdletParameters;422 }423 }424 else425 {426 if (instanceCmdlet.GetCmdletParameters != null)427 {428 return instanceCmdlet.GetCmdletParameters;429 }430 }431 432 return _cmdletizationMetadata.Class.InstanceCmdlets.GetCmdletParameters;433 }434 435 private List<string> GetQueryParameterSets(InstanceCmdletMetadata instanceCmdlet)436 {437 Dictionary<string, object> parameterSetNames = new(StringComparer.OrdinalIgnoreCase);438 439 var parameters = new List<CmdletParameterMetadataForGetCmdletParameter>();440 bool anyQueryParameters = false;441 442 GetCmdletParameters getCmdletParameters = GetGetCmdletParameters(instanceCmdlet);443 if (getCmdletParameters.QueryableProperties != null)444 {445 foreach (PropertyMetadata property in getCmdletParameters.QueryableProperties)446 {447 if (property.Items != null)448 {449 foreach (PropertyQuery query in property.Items)450 {451 anyQueryParameters = true;452 if (query.CmdletParameterMetadata != null)453 {454 parameters.Add(query.CmdletParameterMetadata);455 }456 }457 }458 }459 }460 461 if (getCmdletParameters.QueryableAssociations != null)462 {463 foreach (Association association in getCmdletParameters.QueryableAssociations)464 {465 if (association.AssociatedInstance != null)466 {467 anyQueryParameters = true;468 if (association.AssociatedInstance.CmdletParameterMetadata != null)469 {470 parameters.Add(association.AssociatedInstance.CmdletParameterMetadata);471 }472 }473 }474 }475 476 if (getCmdletParameters.QueryOptions != null)477 {478 foreach (QueryOption option in getCmdletParameters.QueryOptions)479 {480 anyQueryParameters = true;481 if (option.CmdletParameterMetadata != null)482 {483 parameters.Add(option.CmdletParameterMetadata);484 }485 }486 }487 488 foreach (CmdletParameterMetadataForGetCmdletParameter parameter in parameters)489 {490 if (parameter.CmdletParameterSets != null)491 {492 foreach (string parameterSetName in parameter.CmdletParameterSets)493 {494 parameterSetNames[parameterSetName] = null;495 }496 }497 }498 499 if (anyQueryParameters && (parameterSetNames.Count == 0))500 {501 parameterSetNames.Add(ScriptWriter.SingleQueryParameterSetName, null);502 getCmdletParameters.DefaultCmdletParameterSet = ScriptWriter.SingleQueryParameterSetName;503 }504 505 if (instanceCmdlet != null)506 {507 parameterSetNames.Add(ScriptWriter.InputObjectQueryParameterSetName, null);508 }509 510 return new List<string>(parameterSetNames.Keys);511 }512 513 private Type GetDotNetType(TypeMetadata typeMetadata)514 {515 Dbg.Assert(typeMetadata != null, "Caller should verify typeMetadata != null");516 517 string psTypeText;518 EnumMetadataEnum matchingEnum = null;519 520 if (_cmdletizationMetadata.Enums is not null)521 {522 string psType = typeMetadata.PSType;523 foreach (EnumMetadataEnum e in _cmdletizationMetadata.Enums)524 {525 int index = psType.IndexOf(e.EnumName, StringComparison.Ordinal);526 if (index == -1)527 {528 // Fast return if 'PSType' doesn't contain the enum name at all.529 continue;530 }531 532 bool matchFound = false;533 if (index == 0)534 {535 // Handle 2 common cases here (cover over 99% of how enum name is used in 'PSType'):536 // - 'PSType' is exactly the enum name.537 // - 'PSType' is the array format of the enum.538 ReadOnlySpan<char> remains = psType.AsSpan(e.EnumName.Length);539 matchFound = remains.Length is 0 || remains.Equals("[]", StringComparison.Ordinal);540 }541 542 if (!matchFound)543 {544 // Now we have to fall back to the expensive regular expression matching, because 'PSType'545 // could be a composite type like 'Nullable<enum_name>' or 'Dictionary<enum_name, object>',546 // but we don't want the case where the enum name is part of another type's name.547 matchFound = Regex.IsMatch(psType, $@"\b{Regex.Escape(e.EnumName)}\b");548 }549 550 if (matchFound)551 {552 if (matchingEnum is null)553 {554 matchingEnum = e;555 continue;556 }557 558 // If more than one matching enum names were found, we treat it as no match found.559 matchingEnum = null;560 break;561 }562 }563 }564 565 if (matchingEnum != null)566 {567 psTypeText = typeMetadata.PSType.Replace(matchingEnum.EnumName, EnumWriter.GetEnumFullName(matchingEnum));568 }569 else570 {571 psTypeText = typeMetadata.PSType;572 }573 574 Type dotNetType = (Type)LanguagePrimitives.ConvertTo(psTypeText, typeof(Type), CultureInfo.InvariantCulture);575 return dotNetType;576 }577 578 private ParameterMetadata GetParameter(579 string parameterSetName,580 string objectModelParameterName,581 TypeMetadata parameterTypeMetadata,582 CmdletParameterMetadata parameterCmdletization,583 bool isValueFromPipeline,584 bool isValueFromPipelineByPropertyName)585 {586 string parameterName;587 if ((parameterCmdletization != null) && (!string.IsNullOrEmpty(parameterCmdletization.PSName)))588 {589 parameterName = parameterCmdletization.PSName;590 }591 else592 {593 parameterName = objectModelParameterName;594 }595 596 ParameterMetadata parameterMetadata = new(parameterName);597 parameterMetadata.ParameterType = GetDotNetType(parameterTypeMetadata);598 if (typeof(PSCredential).Equals(parameterMetadata.ParameterType))599 {600 parameterMetadata.Attributes.Add(new CredentialAttribute());601 }602 603 if (parameterTypeMetadata.ETSType != null)604 {605 parameterMetadata.Attributes.Add(new PSTypeNameAttribute(parameterTypeMetadata.ETSType));606 }607 608 if (parameterCmdletization != null)609 {610 if (parameterCmdletization.Aliases != null)611 {612 foreach (string alias in parameterCmdletization.Aliases)613 {614 if (!string.IsNullOrEmpty(alias))615 {616 parameterMetadata.Aliases.Add(alias);617 }618 }619 }620 621 if (parameterCmdletization.AllowEmptyCollection != null)622 {623 parameterMetadata.Attributes.Add(new AllowEmptyCollectionAttribute());624 }625 626 if (parameterCmdletization.AllowEmptyString != null)627 {628 parameterMetadata.Attributes.Add(new AllowEmptyStringAttribute());629 }630 631 if (parameterCmdletization.AllowNull != null)632 {633 parameterMetadata.Attributes.Add(new AllowNullAttribute());634 }635 636 if (parameterCmdletization.ValidateCount != null)637 {638 int min = (int)LanguagePrimitives.ConvertTo(parameterCmdletization.ValidateCount.Min, typeof(int), CultureInfo.InvariantCulture);639 int max = (int)LanguagePrimitives.ConvertTo(parameterCmdletization.ValidateCount.Max, typeof(int), CultureInfo.InvariantCulture);640 parameterMetadata.Attributes.Add(new ValidateCountAttribute(min, max));641 }642 643 if (parameterCmdletization.ValidateLength != null)644 {645 int min = (int)LanguagePrimitives.ConvertTo(parameterCmdletization.ValidateLength.Min, typeof(int), CultureInfo.InvariantCulture);646 int max = (int)LanguagePrimitives.ConvertTo(parameterCmdletization.ValidateLength.Max, typeof(int), CultureInfo.InvariantCulture);647 parameterMetadata.Attributes.Add(new ValidateLengthAttribute(min, max));648 }649 650 if (parameterCmdletization.Obsolete != null)651 {652 string obsoleteMessage = parameterCmdletization.Obsolete.Message;653 parameterMetadata.Attributes.Add(obsoleteMessage != null ? new ObsoleteAttribute(obsoleteMessage) : new ObsoleteAttribute());654 }655 656 if (parameterCmdletization.ValidateNotNull != null)657 {658 parameterMetadata.Attributes.Add(new ValidateNotNullAttribute());659 }660 661 if (parameterCmdletization.ValidateNotNullOrEmpty != null)662 {663 parameterMetadata.Attributes.Add(new ValidateNotNullOrEmptyAttribute());664 }665 666 if (parameterCmdletization.ValidateRange != null)667 {668 Type parameterType = parameterMetadata.ParameterType;669 Type elementType;670 if (parameterType == null)671 {672 elementType = typeof(string);673 }674 else675 {676 elementType = parameterType.HasElementType ? parameterType.GetElementType() : parameterType;677 }678 679 object min = LanguagePrimitives.ConvertTo(parameterCmdletization.ValidateRange.Min, elementType, CultureInfo.InvariantCulture);680 object max = LanguagePrimitives.ConvertTo(parameterCmdletization.ValidateRange.Max, elementType, CultureInfo.InvariantCulture);681 parameterMetadata.Attributes.Add(new ValidateRangeAttribute(min, max));682 }683 684 if (parameterCmdletization.ValidateSet != null)685 {686 List<string> allowedValues = new();687 foreach (string allowedValue in parameterCmdletization.ValidateSet)688 {689 allowedValues.Add(allowedValue);690 }691 692 parameterMetadata.Attributes.Add(new ValidateSetAttribute(allowedValues.ToArray()));693 }694 }695 696 int position = int.MinValue;697 ParameterSetMetadata.ParameterFlags parameterFlags = 0;698 if (parameterCmdletization != null)699 {700 if (!string.IsNullOrEmpty(parameterCmdletization.Position))701 {702 position = (int)LanguagePrimitives.ConvertTo(parameterCmdletization.Position, typeof(int), CultureInfo.InvariantCulture);703 }704 705 if (parameterCmdletization.IsMandatorySpecified && parameterCmdletization.IsMandatory)706 {707 parameterFlags |= ParameterSetMetadata.ParameterFlags.Mandatory;708 }709 }710 711 if (isValueFromPipeline)712 {713 parameterFlags |= ParameterSetMetadata.ParameterFlags.ValueFromPipeline;714 }715 716 if (isValueFromPipelineByPropertyName)717 {718 parameterFlags |= ParameterSetMetadata.ParameterFlags.ValueFromPipelineByPropertyName;719 }720 721 parameterMetadata.ParameterSets.Add(parameterSetName, new ParameterSetMetadata(position, parameterFlags, null));722 723 return parameterMetadata;724 }725 726 private ParameterMetadata GetParameter(727 string parameterSetName,728 string objectModelParameterName,729 TypeMetadata parameterType,730 CmdletParameterMetadataForInstanceMethodParameter parameterCmdletization)731 {732 return GetParameter(733 parameterSetName,734 objectModelParameterName,735 parameterType,736 parameterCmdletization,737 false, /* isValueFromPipeline */738 parameterCmdletization != null && parameterCmdletization.ValueFromPipelineByPropertyNameSpecified && parameterCmdletization.ValueFromPipelineByPropertyName);739 }740 741 private ParameterMetadata GetParameter(742 IEnumerable<string> queryParameterSets,743 string objectModelParameterName,744 TypeMetadata parameterType,745 CmdletParameterMetadataForGetCmdletParameter parameterCmdletization)746 {747 ParameterMetadata result = GetParameter(748 ParameterAttribute.AllParameterSets,749 objectModelParameterName,750 parameterType,751 parameterCmdletization,752 parameterCmdletization != null && parameterCmdletization.ValueFromPipelineSpecified && parameterCmdletization.ValueFromPipeline,753 parameterCmdletization != null && parameterCmdletization.ValueFromPipelineByPropertyNameSpecified && parameterCmdletization.ValueFromPipelineByPropertyName);754 755 ParameterSetMetadata parameterSetMetadata = result.ParameterSets[ParameterAttribute.AllParameterSets];756 result.ParameterSets.Clear();757 if (parameterCmdletization != null && parameterCmdletization.CmdletParameterSets != null && parameterCmdletization.CmdletParameterSets.Length > 0)758 {759 queryParameterSets = parameterCmdletization.CmdletParameterSets;760 }761 762 foreach (string parameterSetName in queryParameterSets)763 {764 if (parameterSetName.Equals(ScriptWriter.InputObjectQueryParameterSetName, StringComparison.OrdinalIgnoreCase))765 {766 continue;767 }768 769 result.ParameterSets.Add(parameterSetName, parameterSetMetadata);770 }771 772 return result;773 }774 775 private ParameterMetadata GetParameter(776 string parameterSetName,777 string objectModelParameterName,778 TypeMetadata parameterType,779 CmdletParameterMetadataForStaticMethodParameter parameterCmdletization)780 {781 return GetParameter(782 parameterSetName,783 objectModelParameterName,784 parameterType,785 parameterCmdletization,786 parameterCmdletization != null && parameterCmdletization.ValueFromPipelineSpecified && parameterCmdletization.ValueFromPipeline,787 parameterCmdletization != null && parameterCmdletization.ValueFromPipelineByPropertyNameSpecified && parameterCmdletization.ValueFromPipelineByPropertyName);788 }789 790 private void SetParameters(CommandMetadata commandMetadata, params Dictionary<string, ParameterMetadata>[] allParameters)791 {792 commandMetadata.Parameters.Clear();793 foreach (Dictionary<string, ParameterMetadata> parameters in allParameters)794 {795 foreach (KeyValuePair<string, ParameterMetadata> parameter in parameters)796 {797 if (commandMetadata.Parameters.ContainsKey(parameter.Key))798 {799 if (this.GetCommonParameters().ContainsKey(parameter.Key))800 {801 string message = string.Format(802 CultureInfo.InvariantCulture, // parameter name803 CmdletizationCoreResources.ScriptWriter_ParameterNameConflictsWithCommonParameters,804 parameter.Key,805 commandMetadata.Name,806 _objectModelWrapper.FullName);807 throw new XmlException(message);808 }809 else810 {811 string message = string.Format(812 CultureInfo.InvariantCulture, // parameter name813 CmdletizationCoreResources.ScriptWriter_ParameterNameConflictsWithQueryParameters,814 parameter.Key,815 commandMetadata.Name,816 "<GetCmdletParameters>");817 throw new XmlException(message);818 }819 }820 821 commandMetadata.Parameters.Add(parameter.Key, parameter.Value);822 }823 }824 }825 826 private CommandMetadata GetCommandMetadata(CommonCmdletMetadata cmdletMetadata)827 {828 string defaultParameterSetName = null;829 if (cmdletMetadata is StaticCmdletMetadataCmdletMetadata staticCmdletMetadata)830 {831 if (!string.IsNullOrEmpty(staticCmdletMetadata.DefaultCmdletParameterSet))832 {833 defaultParameterSetName = staticCmdletMetadata.DefaultCmdletParameterSet;834 }835 }836 837 var confirmImpact = System.Management.Automation.ConfirmImpact.None;838 if (cmdletMetadata.ConfirmImpactSpecified)839 {840 confirmImpact = (System.Management.Automation.ConfirmImpact)(int)cmdletMetadata.ConfirmImpact;841 }842 843 Dictionary<string, ParameterMetadata> parameters = new(StringComparer.OrdinalIgnoreCase);844 845 CommandMetadata commandMetadata = new(846 name: this.GetCmdletName(cmdletMetadata),847 commandType: CommandTypes.Cmdlet,848 isProxyForCmdlet: true,849 defaultParameterSetName: defaultParameterSetName, // this can only be figured out for static cmdlets - instance cmdlets have to set that separately850 supportsShouldProcess: confirmImpact != System.Management.Automation.ConfirmImpact.None,851 confirmImpact: confirmImpact,852 supportsPaging: false,853 supportsTransactions: false,854 positionalBinding: false,855 parameters: parameters);856 857 if (!string.IsNullOrEmpty(cmdletMetadata.HelpUri))858 {859 commandMetadata.HelpUri = cmdletMetadata.HelpUri;860 }861 862 return commandMetadata;863 }864 865 private static string EscapeModuleNameForHelpComment(string name)866 {867 Dbg.Assert(name != null, "Caller should verify name != null");868 869 StringBuilder result = new(name.Length);870 foreach (char c in name)871 {872 if (!"\"'`$#".Contains(c)873 && !char.IsControl(c)874 && !char.IsWhiteSpace(c))875 {876 result.Append(c);877 }878 }879 880 return result.ToString();881 }882 883 private static List<List<string>> GetCombinations(params IEnumerable<string>[] x)884 {885 Dbg.Assert(x != null, "Caller to verify that x != null");886 Dbg.Assert(x.Length > 0, "Caller to verify that x.Length > 0");887 888 if (x.Length == 1)889 {890 List<List<string>> result = new();891 foreach (string s in x[0])892 {893 List<string> subresult = new();894 subresult.Add(s);895 result.Add(subresult);896 }897 898 return result;899 }900 else901 {902 IEnumerable<string>[] smallX = new IEnumerable<string>[x.Length - 1];903 Array.Copy(x, 0, smallX, 0, smallX.Length);904 List<List<string>> smallResult = GetCombinations(smallX);905 906 List<List<string>> result = new();907 foreach (List<string> smallSubresult in smallResult)908 {909 foreach (string s in x[x.Length - 1])910 {911 List<string> newsubresult = new(smallSubresult);912 newsubresult.Add(s);913 result.Add(newsubresult);914 }915 }916 917 return result;918 }919 }920 921 private static void EnsureOrderOfPositionalParameters(922 Dictionary<string, ParameterMetadata> beforeParameters,923 Dictionary<string, ParameterMetadata> afterParameters)924 {925 int maxBeforePosition = int.MinValue;926 foreach (ParameterMetadata beforeParameter in beforeParameters.Values)927 {928 foreach (ParameterSetMetadata beforeParameterSet in beforeParameter.ParameterSets.Values)929 {930 maxBeforePosition = Math.Max(beforeParameterSet.Position, maxBeforePosition);931 }932 }933 934 int minAfterPosition = int.MaxValue;935 foreach (ParameterMetadata afterParameter in afterParameters.Values)936 {937 foreach (ParameterSetMetadata afterParameterSet in afterParameter.ParameterSets.Values)938 {939 if (afterParameterSet.Position != int.MinValue)940 {941 minAfterPosition = Math.Min(afterParameterSet.Position, minAfterPosition);942 }943 }944 }945 946 if ((maxBeforePosition >= 0) && (minAfterPosition <= maxBeforePosition))947 {948 int delta = (1001 - minAfterPosition % 1000);949 foreach (ParameterMetadata afterParameter in afterParameters.Values)950 {951 foreach (ParameterSetMetadata afterParameterSet in afterParameter.ParameterSets.Values)952 {953 if (afterParameterSet.Position != int.MinValue)954 {955 checked { afterParameterSet.Position += delta; }956 }957 }958 }959 }960 }961 962 private const string StaticCommonParameterSetTemplate = "{1}"; // "{0}::{1}";963 private const string StaticMethodParameterSetTemplate = "{0}"; // "{1}::{0}";964 965 private const string InstanceCommonParameterSetTemplate = "{1}"; // "{0}::{1}::{2}";966 private const string InstanceQueryParameterSetTemplate = "{0}"; // "{1}::{0}::{2}";967 private const string InstanceMethodParameterSetTemplate = "{2}"; // "{1}::{2}::{0}";968 969 private const string InputObjectQueryParameterSetName = "InputObject (cdxml)";970 private const string SingleQueryParameterSetName = "Query (cdxml)";971 972 private static void MultiplyParameterSets(973 Dictionary<string, ParameterMetadata> parameters,974 string parameterSetNameTemplate, // {0} is the original parameter set, other ones are taken from the otherParameterSets array975 params IEnumerable<string>[] otherParameterSets)976 {977 List<List<string>> combinations = GetCombinations(otherParameterSets);978 979 foreach (ParameterMetadata parameter in parameters.Values)980 {981 List<KeyValuePair<string, ParameterSetMetadata>> oldParameterSets = new(parameter.ParameterSets);982 983 parameter.ParameterSets.Clear();984 foreach (KeyValuePair<string, ParameterSetMetadata> oldParameterSet in oldParameterSets)985 {986 foreach (List<string> combination in combinations)987 {988 string[] formattingArray = new string[otherParameterSets.Length + 1];989 formattingArray[0] = oldParameterSet.Key;990 combination.CopyTo(formattingArray, 1);991 string newParameterSetName = string.Format(CultureInfo.InvariantCulture, parameterSetNameTemplate, formattingArray);992 993 parameter.ParameterSets.Add(newParameterSetName, oldParameterSet.Value);994 }995 }996 }997 }998 999 private static IEnumerable<string> MultiplyParameterSets(1000 string mainParameterSet,1001 string parameterSetNameTemplate, // {0} is the original parameter set, other ones are taken from the otherParameterSets array1002 params IEnumerable<string>[] otherParameterSets)1003 {1004 List<string> result = new();1005 1006 List<List<string>> combinations = GetCombinations(otherParameterSets);1007 foreach (List<string> combination in combinations)1008 {1009 string[] formattingArray = new string[otherParameterSets.Length + 1];1010 formattingArray[0] = mainParameterSet;1011 combination.CopyTo(formattingArray, 1);1012 string newParameterSetName = string.Format(CultureInfo.InvariantCulture, parameterSetNameTemplate, formattingArray);1013 result.Add(newParameterSetName);1014 }1015 1016 return result;1017 }1018 1019 private static MethodParameterBindings GetMethodParameterKind(InstanceMethodParameterMetadata methodParameter)1020 {1021 Dbg.Assert(methodParameter != null, "Caller should verify methodParameter != null");1022 1023 MethodParameterBindings bindings = 0;1024 if (methodParameter.CmdletParameterMetadata != null)1025 {1026 bindings |= MethodParameterBindings.In;1027 }1028 1029 if (methodParameter.CmdletOutputMetadata != null)1030 {1031 if (methodParameter.CmdletOutputMetadata.ErrorCode == null)1032 {1033 bindings |= MethodParameterBindings.Out;1034 }1035 else1036 {1037 bindings |= MethodParameterBindings.Error;1038 }1039 }1040 1041 return bindings;1042 }1043 1044 private static MethodParameterBindings GetMethodParameterKind(StaticMethodParameterMetadata methodParameter)1045 {1046 Dbg.Assert(methodParameter != null, "Caller should verify methodParameter != null");1047 1048 MethodParameterBindings bindings = 0;1049 if (methodParameter.CmdletParameterMetadata != null)1050 {1051 bindings |= MethodParameterBindings.In;1052 }1053 1054 if (methodParameter.CmdletOutputMetadata != null)1055 {1056 if (methodParameter.CmdletOutputMetadata.ErrorCode == null)1057 {1058 bindings |= MethodParameterBindings.Out;1059 }1060 else1061 {1062 bindings |= MethodParameterBindings.Error;1063 }1064 }1065 1066 return bindings;1067 }1068 1069 private static MethodParameterBindings GetMethodParameterKind(CommonMethodMetadataReturnValue returnValue)1070 {1071 Dbg.Assert(returnValue != null, "Caller should verify returnValue != null");1072 1073 MethodParameterBindings bindings = 0;1074 if (returnValue.CmdletOutputMetadata != null)1075 {1076 if (returnValue.CmdletOutputMetadata.ErrorCode == null)1077 {1078 bindings |= MethodParameterBindings.Out;1079 }1080 else1081 {1082 bindings |= MethodParameterBindings.Error;1083 }1084 }1085 1086 return bindings;1087 }1088 1089 private static void GenerateSingleMethodParameterProcessing(1090 TextWriter output,1091 string prefix,1092 string cmdletParameterName,1093 Type cmdletParameterType,1094 string etsParameterTypeName,1095 string cmdletParameterDefaultValue,1096 string methodParameterName,1097 MethodParameterBindings methodParameterBindings)1098 {1099 Dbg.Assert(output != null, "Called should verify output != null");1100 Dbg.Assert(prefix != null, "Called should verify output != null");1101 // cmdletParameterName can be null for 'out' parameters1102 string cmdletParameterTypeName = (cmdletParameterType ?? typeof(object)).FullName;1103 Dbg.Assert(methodParameterName != null, "Caller should verify methodParameterName != null");1104 1105 if (cmdletParameterDefaultValue != null)1106 {1107 output.WriteLine(1108 "{0}[object]$__cmdletization_defaultValue = [System.Management.Automation.LanguagePrimitives]::ConvertTo('{1}', '{2}')",1109 prefix,1110 CodeGeneration.EscapeSingleQuotedStringContent(cmdletParameterDefaultValue),1111 CodeGeneration.EscapeSingleQuotedStringContent(cmdletParameterTypeName));1112 output.WriteLine(1113 "{0}[object]$__cmdletization_defaultValueIsPresent = $true",1114 prefix);1115 }1116 else1117 {1118 output.WriteLine(1119 "{0}[object]$__cmdletization_defaultValue = $null",1120 prefix);1121 output.WriteLine(1122 "{0}[object]$__cmdletization_defaultValueIsPresent = $false",1123 prefix);1124 }1125 1126 if ((methodParameterBindings & MethodParameterBindings.In) == MethodParameterBindings.In)1127 {1128 Dbg.Assert(cmdletParameterName != null, "Called should verify cmdletParameterName!=null for 'in' parameters");1129 1130 output.WriteLine(1131 "{0}if ($PSBoundParameters.ContainsKey('{1}')) {{",1132 prefix,1133 CodeGeneration.EscapeSingleQuotedStringContent(cmdletParameterName));1134 output.WriteLine(1135 "{0} [object]$__cmdletization_value = ${{{1}}}",1136 prefix,1137 CodeGeneration.EscapeVariableName(cmdletParameterName));1138 output.WriteLine(1139 "{0} $__cmdletization_methodParameter = [Microsoft.PowerShell.Cmdletization.MethodParameter]@{{Name = '{1}'; ParameterType = '{2}'; Bindings = '{3}'; Value = $__cmdletization_value; IsValuePresent = $true}}",1140 prefix,1141 CodeGeneration.EscapeSingleQuotedStringContent(methodParameterName),1142 CodeGeneration.EscapeSingleQuotedStringContent(cmdletParameterTypeName),1143 CodeGeneration.EscapeSingleQuotedStringContent(methodParameterBindings.ToString()));1144 output.WriteLine("{0}}} else {{", prefix);1145 }1146 1147 output.WriteLine(1148 "{0} $__cmdletization_methodParameter = [Microsoft.PowerShell.Cmdletization.MethodParameter]@{{Name = '{1}'; ParameterType = '{2}'; Bindings = '{3}'; Value = $__cmdletization_defaultValue; IsValuePresent = $__cmdletization_defaultValueIsPresent}}",1149 prefix,1150 CodeGeneration.EscapeSingleQuotedStringContent(methodParameterName),1151 CodeGeneration.EscapeSingleQuotedStringContent(cmdletParameterTypeName),1152 CodeGeneration.EscapeSingleQuotedStringContent(methodParameterBindings.ToString()));1153 1154 if ((methodParameterBindings & MethodParameterBindings.In) == MethodParameterBindings.In)1155 {1156 output.WriteLine("{0}}}", prefix);1157 }1158 1159 if (!string.IsNullOrEmpty(etsParameterTypeName))1160 {1161 output.WriteLine(1162 "{0}$__cmdletization_methodParameter.ParameterTypeName = '{1}'",1163 prefix,1164 CodeGeneration.EscapeSingleQuotedStringContent(etsParameterTypeName));1165 }1166 1167 output.WriteLine("{0}$__cmdletization_methodParameters.Add($__cmdletization_methodParameter)", prefix);1168 output.WriteLine();1169 }1170 1171 private void GenerateMethodParametersProcessing(1172 StaticCmdletMetadata staticCmdlet,1173 IEnumerable<string> commonParameterSets,1174 out string scriptCode,1175 out Dictionary<string, ParameterMetadata> methodParameters,1176 out string outputTypeAttributeDeclaration)1177 {1178 methodParameters = new Dictionary<string, ParameterMetadata>(StringComparer.OrdinalIgnoreCase);1179 StringBuilder outputTypeAttributeDeclarationBuilder = new();1180 StringWriter output = new(CultureInfo.InvariantCulture);1181 1182 output.WriteLine(" $__cmdletization_methodParameters = [System.Collections.Generic.List[Microsoft.PowerShell.Cmdletization.MethodParameter]]::new()");1183 output.WriteLine();1184 1185 bool multipleMethods = staticCmdlet.Method.Length > 1;1186 if (multipleMethods)1187 {1188 output.WriteLine(" switch -exact ($PSCmdlet.ParameterSetName) { ");1189 }1190 1191 foreach (StaticMethodMetadata method in staticCmdlet.Method)1192 {1193 if (multipleMethods)1194 {1195 output.Write(" { @(");1196 bool firstParameterSet = true;1197 foreach (1198 string parameterSetName in1199 MultiplyParameterSets(1200 GetMethodParameterSet(method), StaticMethodParameterSetTemplate, commonParameterSets))