MegaBites-AI/Windows-powershell
0372
1// Copyright (c) Microsoft Corporation.2// Licensed under the MIT License.3 4using System.Collections;5using System.Collections.Generic;6using System.Collections.ObjectModel;7using System.Diagnostics;8using System.Globalization;9using System.Management.Automation.Internal;10using System.Management.Automation.Language;11using System.Reflection;12using System.Text;13 14using Microsoft.PowerShell.Commands;15 16using Dbg = System.Diagnostics.Debug;17using System.Diagnostics.CodeAnalysis;18 19namespace System.Management.Automation20{21 /// <summary>22 /// Defines session capabilities provided by a PowerShell session.23 /// </summary>24 /// <seealso cref="System.Management.Automation.Runspaces.InitialSessionState.CreateRestricted"/>25 /// <seealso cref="System.Management.Automation.CommandMetadata.GetRestrictedCommands"/>26 [Flags]27 public enum SessionCapabilities28 {29 /// <summary>30 /// Session with <see cref="RemoteServer"/> capabilities can be made available on a server31 /// that wants to provide a full user experience to PowerShell clients.32 /// Clients connecting to the server will be able to use implicit remoting33 /// (Import-PSSession, Export-PSSession) as well as interactive remoting (Enter-PSSession, Exit-PSSession).34 /// </summary>35 RemoteServer = 0x1,36 37 /// <summary>38 /// Include language capabilities.39 /// </summary>40 Language = 0x441 }42 43 /// <summary>44 /// This class represents the compiled metadata for a command type.45 /// </summary>46 [DebuggerDisplay("CommandName = {Name}; Type = {CommandType}")]47 public sealed class CommandMetadata48 {49 #region Public Constructor50 51 /// <summary>52 /// Constructs a CommandMetadata object for the given CLS complaint type53 /// <paramref name="commandType"/>.54 /// </summary>55 /// <param name="commandType">56 /// CLS complaint type to inspect for Cmdlet metadata.57 /// </param>58 /// <exception cref="ArgumentNullException">59 /// commandType is null.60 /// </exception>61 /// <exception cref="MetadataException">62 /// If a parameter defines the same parameter-set name multiple times.63 /// If the attributes could not be read from a property or field.64 /// </exception>65 public CommandMetadata(Type commandType)66 {67 Init(null, null, commandType, false);68 }69 70 /// <summary>71 /// Construct a CommandMetadata object for the given commandInfo.72 /// </summary>73 /// <param name="commandInfo">74 /// The commandInfo object to construct CommandMetadata for75 /// </param>76 /// <exception cref="ArgumentNullException">77 /// commandInfo is null.78 /// </exception>79 /// <exception cref="PSNotSupportedException">80 /// If the commandInfo is an alias to an unknown command, or if the commandInfo81 /// is an unsupported command type.82 /// </exception>83 public CommandMetadata(CommandInfo commandInfo)84 : this(commandInfo, false)85 {86 }87 88 /// <summary>89 /// Construct a CommandMetadata object for the given commandInfo.90 /// </summary>91 /// <param name="commandInfo">92 /// The commandInfo object to construct CommandMetadata for93 /// </param>94 /// <param name="shouldGenerateCommonParameters">95 /// Should common parameters be included in the metadata?96 /// </param>97 /// <exception cref="ArgumentNullException">98 /// commandInfo is null.99 /// </exception>100 /// <exception cref="PSNotSupportedException">101 /// If the commandInfo is an alias to an unknown command, or if the commandInfo102 /// is an unsupported command type.103 /// </exception>104 public CommandMetadata(CommandInfo commandInfo, bool shouldGenerateCommonParameters)105 {106 if (commandInfo == null)107 {108 throw PSTraceSource.NewArgumentNullException(nameof(commandInfo));109 }110 while (commandInfo is AliasInfo)111 {112 commandInfo = ((AliasInfo)commandInfo).ResolvedCommand;113 if (commandInfo == null)114 {115 throw PSTraceSource.NewNotSupportedException();116 }117 }118 119 CmdletInfo cmdletInfo;120 ExternalScriptInfo scriptInfo;121 FunctionInfo funcInfo;122 if ((cmdletInfo = commandInfo as CmdletInfo) != null)123 {124 Init(commandInfo.Name, cmdletInfo.FullName, cmdletInfo.ImplementingType, shouldGenerateCommonParameters);125 }126 else if ((scriptInfo = commandInfo as ExternalScriptInfo) != null)127 {128 // Accessing the script block property here reads and parses the script129 Init(scriptInfo.ScriptBlock, scriptInfo.Path, shouldGenerateCommonParameters);130 _wrappedCommandType = CommandTypes.ExternalScript;131 }132 else if ((funcInfo = commandInfo as FunctionInfo) != null)133 {134 Init(funcInfo.ScriptBlock, funcInfo.Name, shouldGenerateCommonParameters);135 _wrappedCommandType = commandInfo.CommandType;136 }137 else138 {139 throw PSTraceSource.NewNotSupportedException();140 }141 }142 143 /// <summary>144 /// Construct a CommandMetadata object for a script file.145 /// </summary>146 /// <param name="path">The path to the script file.</param>147 public CommandMetadata(string path)148 {149 string scriptName = IO.Path.GetFileName(path);150 ExternalScriptInfo scriptInfo = new ExternalScriptInfo(scriptName, path);151 152 Init(scriptInfo.ScriptBlock, path, false);153 _wrappedCommandType = CommandTypes.ExternalScript;154 }155 156 /// <summary>157 /// A copy constructor that creates a deep copy of the <paramref name="other"/> CommandMetadata object.158 /// Instances of Attribute and Type classes are copied by reference.159 /// </summary>160 /// <param name="other">Object to copy.</param>161 public CommandMetadata(CommandMetadata other)162 {163 if (other == null)164 {165 throw PSTraceSource.NewArgumentNullException(nameof(other));166 }167 168 Name = other.Name;169 ConfirmImpact = other.ConfirmImpact;170 _defaultParameterSetFlag = other._defaultParameterSetFlag;171 _defaultParameterSetName = other._defaultParameterSetName;172 _implementsDynamicParameters = other._implementsDynamicParameters;173 SupportsShouldProcess = other.SupportsShouldProcess;174 SupportsPaging = other.SupportsPaging;175 SupportsTransactions = other.SupportsTransactions;176 this.CommandType = other.CommandType;177 _wrappedAnyCmdlet = other._wrappedAnyCmdlet;178 _wrappedCommand = other._wrappedCommand;179 _wrappedCommandType = other._wrappedCommandType;180 181 _parameters = new Dictionary<string, ParameterMetadata>(other.Parameters.Count, StringComparer.OrdinalIgnoreCase);182 183 // deep copy184 foreach (KeyValuePair<string, ParameterMetadata> entry in other.Parameters)185 {186 _parameters.Add(entry.Key, new ParameterMetadata(entry.Value));187 }188 189 // deep copy of the collection, collection items (Attributes) copied by reference190 if (other._otherAttributes == null)191 {192 _otherAttributes = null;193 }194 else195 {196 _otherAttributes = new Collection<Attribute>(new List<Attribute>(other._otherAttributes.Count));197 foreach (Attribute attribute in other._otherAttributes)198 {199 _otherAttributes.Add(attribute);200 }201 }202 203 // not copying those fields/members as they are untouched (and left set to null)204 // by public constructors, so we can't rely on those fields/members to be set205 // when CommandMetadata comes from a user206 _staticCommandParameterMetadata = null;207 }208 209 /// <summary>210 /// Constructor used by implicit remoting.211 /// </summary>212 internal CommandMetadata(213 string name,214 CommandTypes commandType,215 bool isProxyForCmdlet,216 string defaultParameterSetName,217 bool supportsShouldProcess,218 ConfirmImpact confirmImpact,219 bool supportsPaging,220 bool supportsTransactions,221 bool positionalBinding,222 Dictionary<string, ParameterMetadata> parameters)223 {224 Name = _wrappedCommand = name;225 _wrappedCommandType = commandType;226 _wrappedAnyCmdlet = isProxyForCmdlet;227 _defaultParameterSetName = defaultParameterSetName;228 SupportsShouldProcess = supportsShouldProcess;229 SupportsPaging = supportsPaging;230 ConfirmImpact = confirmImpact;231 SupportsTransactions = supportsTransactions;232 PositionalBinding = positionalBinding;233 this.Parameters = parameters;234 }235 236 private void Init(string name, string fullyQualifiedName, Type commandType, bool shouldGenerateCommonParameters)237 {238 Name = name;239 this.CommandType = commandType;240 241 if (commandType != null)242 {243 ConstructCmdletMetadataUsingReflection();244 _shouldGenerateCommonParameters = shouldGenerateCommonParameters;245 }246 247 // Use fully qualified name if available.248 _wrappedCommand = !string.IsNullOrEmpty(fullyQualifiedName) ? fullyQualifiedName : Name;249 _wrappedCommandType = CommandTypes.Cmdlet;250 _wrappedAnyCmdlet = true;251 }252 253 private void Init(ScriptBlock scriptBlock, string name, bool shouldGenerateCommonParameters)254 {255 if (scriptBlock.UsesCmdletBinding)256 {257 _wrappedAnyCmdlet = true;258 }259 else260 {261 // Ignore what was passed in, there are no common parameters if cmdlet binding is not used.262 shouldGenerateCommonParameters = false;263 }264 265 CmdletBindingAttribute cmdletBindingAttribute = scriptBlock.CmdletBindingAttribute;266 if (cmdletBindingAttribute != null)267 {268 ProcessCmdletAttribute(cmdletBindingAttribute);269 }270 else if (scriptBlock.UsesCmdletBinding)271 {272 _defaultParameterSetName = null;273 }274 275 Obsolete = scriptBlock.ObsoleteAttribute;276 _scriptBlock = scriptBlock;277 _wrappedCommand = Name = name;278 _shouldGenerateCommonParameters = shouldGenerateCommonParameters;279 }280 281 #endregion282 283 #region ctor284 285 /// <summary>286 /// Gets the metadata for the specified cmdlet from the cache or creates287 /// a new instance if its not in the cache.288 /// </summary>289 /// <param name="commandName">290 /// The name of the command that this metadata represents.291 /// </param>292 /// <param name="cmdletType">293 /// The cmdlet to get the metadata for.294 /// </param>295 /// <param name="context">296 /// The current engine context.297 /// </param>298 /// <returns>299 /// The CommandMetadata for the specified cmdlet.300 /// </returns>301 /// <exception cref="ArgumentException">302 /// If <paramref name="commandName"/> is null or empty.303 /// </exception>304 /// <exception cref="ArgumentNullException">305 /// If <paramref name="cmdletType"/> is null.306 /// </exception>307 /// <exception cref="ParsingMetadataException">308 /// If more than int.MaxValue parameter-sets are defined for the command.309 /// </exception>310 /// <exception cref="MetadataException">311 /// If a parameter defines the same parameter-set name multiple times.312 /// If the attributes could not be read from a property or field.313 /// </exception>314 internal static CommandMetadata Get(string commandName, Type cmdletType, ExecutionContext context)315 {316 if (string.IsNullOrEmpty(commandName))317 {318 throw PSTraceSource.NewArgumentException(nameof(commandName));319 }320 321 CommandMetadata result = null;322 323 if ((context != null) && (cmdletType != null))324 {325 string cmdletTypeName = cmdletType.AssemblyQualifiedName;326 s_commandMetadataCache.TryGetValue(cmdletTypeName, out result);327 }328 329 if (result == null)330 {331 result = new CommandMetadata(commandName, cmdletType, context);332 333 if ((context != null) && (cmdletType != null))334 {335 string cmdletTypeName = cmdletType.AssemblyQualifiedName;336 s_commandMetadataCache.TryAdd(cmdletTypeName, result);337 }338 }339 340 return result;341 }342 343 /// <summary>344 /// Constructs an instance of CommandMetadata using reflection against a bindable object.345 /// </summary>346 /// <param name="commandName">347 /// The name of the command that this metadata represents.348 /// </param>349 /// <param name="cmdletType">350 /// An instance of an object type that can be used to bind MSH parameters. A type is351 /// considered bindable if it has at least one field and/or property that is decorated352 /// with the ParameterAttribute.353 /// </param>354 /// <param name="context">355 /// The current engine context. If null, the command and type metadata will be generated356 /// and will not be cached.357 /// </param>358 /// <exception cref="ArgumentNullException">359 /// If <paramref name="cmdletType"/> is null.360 /// </exception>361 /// <exception cref="ParsingMetadataException">362 /// If more than int.MaxValue parameter-sets are defined for the command.363 /// </exception>364 /// <exception cref="MetadataException">365 /// If a parameter defines the same parameter-set name multiple times.366 /// If the attributes could not be read from a property or field.367 /// </exception>368 internal CommandMetadata(string commandName, Type cmdletType, ExecutionContext context)369 {370 if (string.IsNullOrEmpty(commandName))371 {372 throw PSTraceSource.NewArgumentException(nameof(commandName));373 }374 375 Name = commandName;376 this.CommandType = cmdletType;377 378 if (cmdletType != null)379 {380 InternalParameterMetadata parameterMetadata = InternalParameterMetadata.Get(cmdletType, context, false);381 ConstructCmdletMetadataUsingReflection();382 _staticCommandParameterMetadata = MergeParameterMetadata(context, parameterMetadata, true);383 _defaultParameterSetFlag = _staticCommandParameterMetadata.GenerateParameterSetMappingFromMetadata(_defaultParameterSetName);384 _staticCommandParameterMetadata.MakeReadOnly();385 }386 }387 388 /// <summary>389 /// Constructor for creating command metadata from a script block.390 /// </summary>391 /// <param name="scriptblock"></param>392 /// <param name="context"></param>393 /// <param name="commandName"></param>394 /// <remarks>395 /// Unlike cmdlet based on a C# type where cmdlet metadata and parameter396 /// metadata is created through reflecting the implementation type, script397 /// cmdlet has different way for constructing metadata.398 ///399 /// 1. Metadata for cmdlet itself comes from cmdlet statement, which400 /// is parsed into CmdletDeclarationNode and then converted into401 /// a CmdletAttribute object.402 /// 2. Metadata for parameter comes from parameter declaration statement,403 /// which is parsed into parameter nodes with parameter annotations.404 /// Information in ParameterNodes is eventually transformed into a405 /// dictionary of RuntimeDefinedParameters.406 ///407 /// By the time this constructor is called, information about CmdletAttribute408 /// and RuntimeDefinedParameters for the script block has been setup with409 /// the scriptblock object.410 /// </remarks>411 internal CommandMetadata(ScriptBlock scriptblock, string commandName, ExecutionContext context)412 {413 if (scriptblock == null)414 {415 throw PSTraceSource.NewArgumentException(nameof(scriptblock));416 }417 418 CmdletBindingAttribute cmdletBindingAttribute = scriptblock.CmdletBindingAttribute;419 420 if (cmdletBindingAttribute != null)421 {422 ProcessCmdletAttribute(cmdletBindingAttribute);423 }424 else425 {426 _defaultParameterSetName = null;427 }428 429 Obsolete = scriptblock.ObsoleteAttribute;430 Name = commandName;431 this.CommandType = typeof(PSScriptCmdlet);432 433 if (scriptblock.HasDynamicParameters)434 {435 _implementsDynamicParameters = true;436 }437 438 InternalParameterMetadata parameterMetadata = InternalParameterMetadata.Get(scriptblock.RuntimeDefinedParameters, false,439 scriptblock.UsesCmdletBinding);440 _staticCommandParameterMetadata = MergeParameterMetadata(context, parameterMetadata, scriptblock.UsesCmdletBinding);441 _defaultParameterSetFlag = _staticCommandParameterMetadata.GenerateParameterSetMappingFromMetadata(_defaultParameterSetName);442 _staticCommandParameterMetadata.MakeReadOnly();443 }444 445 #endregion ctor446 447 #region Public Properties448 449 /// <summary>450 /// Gets the name of the command this metadata represents.451 /// </summary>452 public string Name { get; set; } = string.Empty;453 454 /// <summary>455 /// The Type which this CommandMetadata represents.456 /// </summary>457 public Type CommandType { get; private set; }458 459 // The ScriptBlock which this CommandMetadata represents.460 private ScriptBlock _scriptBlock;461 462 /// <summary>463 /// Gets/Sets the default parameter set name.464 /// </summary>465 public string DefaultParameterSetName466 {467 get468 {469 return _defaultParameterSetName;470 }471 472 set473 {474 if (string.IsNullOrEmpty(value))475 {476 value = ParameterAttribute.AllParameterSets;477 }478 479 _defaultParameterSetName = value;480 }481 }482 483 private string _defaultParameterSetName = ParameterAttribute.AllParameterSets;484 485 /// <summary>486 /// True if the cmdlet declared that it supports ShouldProcess, false otherwise.487 /// </summary>488 /// <value></value>489 public bool SupportsShouldProcess { get; set; }490 491 /// <summary>492 /// True if the cmdlet declared that it supports Paging, false otherwise.493 /// </summary>494 /// <value></value>495 public bool SupportsPaging { get; set; }496 497 /// <summary>498 /// When true, the command will auto-generate appropriate parameter metadata to support positional499 /// parameters if the script hasn't already specified multiple parameter sets or specified positions500 /// explicitly via the <see cref="ParameterAttribute"/>.501 /// </summary>502 public bool PositionalBinding { get; set; } = true;503 504 /// <summary>505 /// True if the cmdlet declared that it supports transactions, false otherwise.506 /// </summary>507 /// <value></value>508 public bool SupportsTransactions { get; set; }509 510 /// <summary>511 /// Related link URI for Get-Help -Online.512 /// </summary>513 [SuppressMessage("Microsoft.Design", "CA1056:UriPropertiesShouldNotBeStrings")]514 public string HelpUri { get; set; } = string.Empty;515 516 /// <summary>517 /// The remoting capabilities of this cmdlet, when exposed in a context518 /// with ambient remoting.519 /// </summary>520 public RemotingCapability RemotingCapability521 {522 get523 {524 RemotingCapability currentRemotingCapability = _remotingCapability;525 if ((currentRemotingCapability == Automation.RemotingCapability.PowerShell) &&526 ((this.Parameters != null) && this.Parameters.ContainsKey("ComputerName")))527 {528 _remotingCapability = Automation.RemotingCapability.SupportedByCommand;529 }530 531 return _remotingCapability;532 }533 534 set535 {536 _remotingCapability = value;537 }538 }539 540 private RemotingCapability _remotingCapability = RemotingCapability.PowerShell;541 542 /// <summary>543 /// Indicates the "destructiveness" of the command operation and544 /// when it should be confirmed. This is only effective when545 /// the command calls ShouldProcess, which should only occur when546 /// SupportsShouldProcess is specified.547 /// </summary>548 /// <value></value>549 public ConfirmImpact ConfirmImpact { get; set; } = ConfirmImpact.Medium;550 551 /// <summary>552 /// Gets the parameter data for this command.553 /// </summary>554 public Dictionary<string, ParameterMetadata> Parameters555 {556 get557 {558 if (_parameters == null)559 {560 // Return parameters for a script block561 if (_scriptBlock != null)562 {563 InternalParameterMetadata parameterMetadata = InternalParameterMetadata.Get(_scriptBlock.RuntimeDefinedParameters, false,564 _scriptBlock.UsesCmdletBinding);565 MergedCommandParameterMetadata mergedCommandParameterMetadata =566 MergeParameterMetadata(null, parameterMetadata, _shouldGenerateCommonParameters);567 _parameters = ParameterMetadata.GetParameterMetadata(mergedCommandParameterMetadata);568 }569 else if (this.CommandType != null)570 {571 // Construct compiled parameter metadata from this572 InternalParameterMetadata parameterMetadata = InternalParameterMetadata.Get(this.CommandType, null, false);573 MergedCommandParameterMetadata mergedCommandParameterMetadata =574 MergeParameterMetadata(null, parameterMetadata, _shouldGenerateCommonParameters);575 576 // Construct parameter metadata from compiled parameter metadata577 // compiled parameter metadata is used for internal purposes. It has lots of information578 // which is used by ParameterBinder.579 _parameters = ParameterMetadata.GetParameterMetadata(mergedCommandParameterMetadata);580 }581 }582 583 return _parameters;584 }585 586 private set587 {588 _parameters = value;589 }590 }591 592 private Dictionary<string, ParameterMetadata> _parameters;593 private bool _shouldGenerateCommonParameters;594 595 /// <summary>596 /// Gets or sets the obsolete attribute on the command.597 /// </summary>598 /// <value></value>599 internal ObsoleteAttribute Obsolete { get; set; }600 601 #endregion602 603 #region internal members604 605 /// <summary>606 /// Gets the merged metadata for the command including cmdlet declared parameters,607 /// common parameters, and (optionally) ShouldProcess and Transactions parameters.608 /// </summary>609 /// <value></value>610 internal MergedCommandParameterMetadata StaticCommandParameterMetadata611 {612 get613 {614 return _staticCommandParameterMetadata;615 }616 }617 618 private readonly MergedCommandParameterMetadata _staticCommandParameterMetadata;619 620 /// <summary>621 /// True if the cmdlet implements dynamic parameters, or false otherwise.622 /// </summary>623 /// <value></value>624 internal bool ImplementsDynamicParameters625 {626 get { return _implementsDynamicParameters; }627 }628 629 private bool _implementsDynamicParameters;630 631 /// <summary>632 /// Gets the bit in the parameter set map for the default parameter set.633 /// </summary>634 internal uint DefaultParameterSetFlag635 {636 get { return _defaultParameterSetFlag; }637 638 set { _defaultParameterSetFlag = value; }639 }640 641 private uint _defaultParameterSetFlag;642 643 /// <summary>644 /// A collection of attributes that were declared at the cmdlet level but were not645 /// recognized by the engine.646 /// </summary>647 private readonly Collection<Attribute> _otherAttributes = new Collection<Attribute>();648 649 // command this CommandMetadata instance is intended to wrap650 private string _wrappedCommand;651 // the type of command this CommandMetadata instance is intended to wrap652 private CommandTypes _wrappedCommandType;653 // The CommandType for a script cmdlet is not CommandTypes.Cmdlet, yet654 // proxy generation needs to know the difference between script and script cmdlet.655 private bool _wrappedAnyCmdlet;656 657 internal bool WrappedAnyCmdlet658 {659 get { return _wrappedAnyCmdlet; }660 }661 662 internal CommandTypes WrappedCommandType663 {664 get665 {666 return _wrappedCommandType;667 }668 }669 670 #endregion internal members671 672 #region helper methods673 674 /// <summary>675 /// Constructs the command metadata by using reflection against the676 /// CLR type.677 /// </summary>678 /// <exception cref="ParsingMetadataException">679 /// If more than int.MaxValue parameter-sets are defined for the command.680 /// </exception>681 private void ConstructCmdletMetadataUsingReflection()682 {683 Diagnostics.Assert(684 CommandType != null,685 "This method should only be called when constructed with the Type");686 687 // Determine if the cmdlet implements dynamic parameters by looking for the interface688 689 Type dynamicParametersType = CommandType.GetInterface(nameof(IDynamicParameters), true);690 691 if (dynamicParametersType != null)692 {693 _implementsDynamicParameters = true;694 }695 696 // Process the attributes on the cmdlet697 698 var customAttributes = CommandType.GetCustomAttributes(false);699 700 foreach (Attribute attribute in customAttributes)701 {702 CmdletAttribute cmdletAttribute = attribute as CmdletAttribute;703 if (cmdletAttribute != null)704 {705 ProcessCmdletAttribute(cmdletAttribute);706 this.Name = cmdletAttribute.VerbName + "-" + cmdletAttribute.NounName;707 }708 else if (attribute is ObsoleteAttribute)709 {710 Obsolete = (ObsoleteAttribute)attribute;711 }712 else713 {714 _otherAttributes.Add(attribute);715 }716 }717 }718 719 /// <summary>720 /// Extracts the cmdlet data from the CmdletAttribute.721 /// </summary>722 /// <param name="attribute">723 /// The CmdletAttribute to process724 /// </param>725 /// <exception cref="ArgumentNullException">726 /// If <paramref name="attribute"/> is null.727 /// </exception>728 /// <exception cref="ParsingMetadataException">729 /// If more than int.MaxValue parameter-sets are defined for the command.730 /// </exception>731 private void ProcessCmdletAttribute(CmdletCommonMetadataAttribute attribute)732 {733 if (attribute == null)734 {735 throw PSTraceSource.NewArgumentNullException(nameof(attribute));736 }737 738 // Process the default parameter set name739 _defaultParameterSetName = attribute.DefaultParameterSetName;740 741 // Check to see if the cmdlet supports ShouldProcess742 SupportsShouldProcess = attribute.SupportsShouldProcess;743 744 // Determine the cmdlet's impact confirmation745 ConfirmImpact = attribute.ConfirmImpact;746 747 // Check to see if the cmdlet supports paging748 SupportsPaging = attribute.SupportsPaging;749 750 // Check to see if the cmdlet supports transactions751 SupportsTransactions = attribute.SupportsTransactions;752 753 // Grab related link754 HelpUri = attribute.HelpUri;755 756 // Remoting support757 _remotingCapability = attribute.RemotingCapability;758 759 // Check to see if the cmdlet uses positional binding760 var cmdletBindingAttribute = attribute as CmdletBindingAttribute;761 if (cmdletBindingAttribute != null)762 {763 PositionalBinding = cmdletBindingAttribute.PositionalBinding;764 }765 }766 767 /// <summary>768 /// Merges parameter metadata from different sources: those that are coming from Type,769 /// CommonParameters, should process etc.770 /// </summary>771 /// <param name="context"></param>772 /// <param name="parameterMetadata"></param>773 /// <param name="shouldGenerateCommonParameters">774 /// true if metadata info about Verbose,Debug etc needs to be generated.775 /// false otherwise.776 /// </param>777 private MergedCommandParameterMetadata MergeParameterMetadata(ExecutionContext context, InternalParameterMetadata parameterMetadata, bool shouldGenerateCommonParameters)778 {779 // Create an instance of the static metadata class780 MergedCommandParameterMetadata staticCommandParameterMetadata =781 new MergedCommandParameterMetadata();782 783 // First add the metadata for the formal cmdlet parameters784 staticCommandParameterMetadata.AddMetadataForBinder(785 parameterMetadata,786 ParameterBinderAssociation.DeclaredFormalParameters);787 788 // Now add the common parameters metadata789 if (shouldGenerateCommonParameters)790 {791 InternalParameterMetadata commonParametersMetadata =792 InternalParameterMetadata.Get(typeof(CommonParameters), context, false);793 794 staticCommandParameterMetadata.AddMetadataForBinder(795 commonParametersMetadata,796 ParameterBinderAssociation.CommonParameters);797 798 // If the command supports ShouldProcess, add the metadata for799 // those parameters800 if (this.SupportsShouldProcess)801 {802 InternalParameterMetadata shouldProcessParametersMetadata =803 InternalParameterMetadata.Get(typeof(ShouldProcessParameters), context, false);804 805 staticCommandParameterMetadata.AddMetadataForBinder(806 shouldProcessParametersMetadata,807 ParameterBinderAssociation.ShouldProcessParameters);808 }809 810 // If the command supports paging, add the metadata for811 // those parameters812 if (this.SupportsPaging)813 {814 InternalParameterMetadata pagingParametersMetadata =815 InternalParameterMetadata.Get(typeof(PagingParameters), context, false);816 817 staticCommandParameterMetadata.AddMetadataForBinder(818 pagingParametersMetadata,819 ParameterBinderAssociation.PagingParameters);820 }821 822 // If the command supports transactions, add the metadata for823 // those parameters824 if (this.SupportsTransactions)825 {826 InternalParameterMetadata transactionParametersMetadata =827 InternalParameterMetadata.Get(typeof(TransactionParameters), context, false);828 829 staticCommandParameterMetadata.AddMetadataForBinder(830 transactionParametersMetadata,831 ParameterBinderAssociation.TransactionParameters);832 }833 }834 835 return staticCommandParameterMetadata;836 }837 838 #endregion helper methods839 840 #region Proxy Command generation841 842 /// <summary>843 /// Gets the ScriptCmdlet in string format.844 /// </summary>845 /// <returns></returns>846 internal string GetProxyCommand(string helpComment, bool generateDynamicParameters)847 {848 if (string.IsNullOrEmpty(helpComment))849 {850 helpComment = string.Create(CultureInfo.InvariantCulture, $@"851.ForwardHelpTargetName {_wrappedCommand}852.ForwardHelpCategory {_wrappedCommandType}853");854 }855 856 string dynamicParamblock = string.Empty;857 if (generateDynamicParameters && this.ImplementsDynamicParameters)858 {859 dynamicParamblock = string.Create(CultureInfo.InvariantCulture, $@"860dynamicparam861{{{GetDynamicParamBlock()}}}862 863");864 }865 866 string result = string.Create(CultureInfo.InvariantCulture, $@"{GetDecl()}867param({GetParamBlock()})868 869{dynamicParamblock}begin870{{{GetBeginBlock()}}}871 872process873{{{GetProcessBlock()}}}874 875end876{{{GetEndBlock()}}}877 878clean879{{{GetCleanBlock()}}}880<#881{CodeGeneration.EscapeBlockCommentContent(helpComment)}882#>883");884 885 return result;886 }887 888 internal string GetDecl()889 {890 string result = string.Empty;891 string separator = string.Empty;892 if (_wrappedAnyCmdlet)893 {894 StringBuilder decl = new StringBuilder("[CmdletBinding(");895 896 if (!string.IsNullOrEmpty(_defaultParameterSetName))897 {898 decl.Append(separator);899 decl.Append("DefaultParameterSetName='");900 decl.Append(CodeGeneration.EscapeSingleQuotedStringContent(_defaultParameterSetName));901 decl.Append('\'');902 separator = ", ";903 }904 905 if (SupportsShouldProcess)906 {907 decl.Append(separator);908 decl.Append("SupportsShouldProcess=$true");909 separator = ", ";910 decl.Append(separator);911 decl.Append("ConfirmImpact='");912 decl.Append(ConfirmImpact);913 decl.Append('\'');914 }915 916 if (SupportsPaging)917 {918 decl.Append(separator);919 decl.Append("SupportsPaging=$true");920 separator = ", ";921 }922 923 if (SupportsTransactions)924 {925 decl.Append(separator);926 decl.Append("SupportsTransactions=$true");927 separator = ", ";928 }929 930 if (!PositionalBinding)931 {932 decl.Append(separator);933 decl.Append("PositionalBinding=$false");934 separator = ", ";935 }936 937 if (!string.IsNullOrEmpty(HelpUri))938 {939 decl.Append(separator);940 decl.Append("HelpUri='");941 decl.Append(CodeGeneration.EscapeSingleQuotedStringContent(HelpUri));942 decl.Append('\'');943 separator = ", ";944 }945 946 if (_remotingCapability != RemotingCapability.PowerShell)947 {948 decl.Append(separator);949 decl.Append("RemotingCapability='");950 decl.Append(_remotingCapability);951 decl.Append('\'');952 separator = ", ";953 }954 955 decl.Append(")]");956 957 result = decl.ToString();958 }959 960 return result;961 }962 963 internal string GetParamBlock()964 {965 if (Parameters.Keys.Count > 0)966 {967 StringBuilder parameters = new StringBuilder();968 string prefix = string.Concat(Environment.NewLine, " ");969 string paramDataPrefix = null;970 971 foreach (var pair in Parameters)972 {973 if (paramDataPrefix != null)974 {975 parameters.Append(paramDataPrefix);976 }977 else978 {979 // syntax for parameter separation : comma followed by new-line.980 paramDataPrefix = string.Concat(",", Environment.NewLine);981 }982 // generate the parameter proxy and append to the list983 string paramData = pair.Value.GetProxyParameterData(prefix, pair.Key, _wrappedAnyCmdlet);984 parameters.Append(paramData);985 }986 987 return parameters.ToString();988 }989 990 return string.Empty;991 }992 993 internal string GetBeginBlock()994 {995 string result;996 997 if (string.IsNullOrEmpty(_wrappedCommand))998 {999 string error = ProxyCommandStrings.CommandMetadataMissingCommandName;1000 throw new InvalidOperationException(error);1001 }1002 1003 string commandOrigin = "$myInvocation.CommandOrigin";1004 1005 // For functions, don't proxy the command origin, otherwise they will1006 // be subject to the runspace restrictions1007 if (_wrappedCommandType == CommandTypes.Function)1008 {1009 commandOrigin = string.Empty;1010 }1011 1012 string wrappedCommand = CodeGeneration.EscapeSingleQuotedStringContent(_wrappedCommand);1013 if (_wrappedAnyCmdlet)1014 {1015 result = string.Create(CultureInfo.InvariantCulture, $@"1016 try {{1017 $outBuffer = $null1018 if ($PSBoundParameters.TryGetValue('OutBuffer', [ref]$outBuffer))1019 {{1020 $PSBoundParameters['OutBuffer'] = 11021 }}1022 1023 $wrappedCmd = $ExecutionContext.InvokeCommand.GetCommand('{wrappedCommand}', [System.Management.Automation.CommandTypes]::{_wrappedCommandType})1024 $scriptCmd = {{& $wrappedCmd @PSBoundParameters }}1025 1026 $steppablePipeline = $scriptCmd.GetSteppablePipeline({commandOrigin})1027 $steppablePipeline.Begin($PSCmdlet)1028 }} catch {{1029 throw1030 }}1031");1032 }1033 else1034 {1035 result = string.Create(CultureInfo.InvariantCulture, $@"1036 try {{1037 $wrappedCmd = $ExecutionContext.InvokeCommand.GetCommand('{wrappedCommand}', [System.Management.Automation.CommandTypes]::{_wrappedCommandType})1038 $PSBoundParameters.Add('$args', $args)1039 $scriptCmd = {{& $wrappedCmd @PSBoundParameters }}1040 1041 $steppablePipeline = $scriptCmd.GetSteppablePipeline({commandOrigin})1042 $steppablePipeline.Begin($myInvocation.ExpectingInput, $ExecutionContext)1043 }} catch {{1044 throw1045 }}1046");1047 }1048 1049 return result;1050 }1051 1052 internal string GetProcessBlock()1053 {1054 // The reason we wrap scripts in 'try { } catch { throw }' (here and elsewhere) is to turn1055 // an exception that could be thrown from .NET method invocation into a terminating error1056 // that can be propagated up.1057 // By default, an exception thrown from .NET method is not terminating, but when enclosed1058 // in try/catch, it will be turned into a terminating error.1059 return @"1060 try {1061 $steppablePipeline.Process($_)1062 } catch {1063 throw1064 }1065";1066 }1067 1068 internal string GetDynamicParamBlock()1069 {1070 string wrappedCommand = CodeGeneration.EscapeSingleQuotedStringContent(_wrappedCommand);1071 return string.Create(CultureInfo.InvariantCulture, $@"1072 try {{1073 $targetCmd = $ExecutionContext.InvokeCommand.GetCommand('{wrappedCommand}', [System.Management.Automation.CommandTypes]::{_wrappedCommandType}, $PSBoundParameters)1074 $dynamicParams = @($targetCmd.Parameters.GetEnumerator() | Microsoft.PowerShell.Core\Where-Object {{ $_.Value.IsDynamic }})1075 if ($dynamicParams.Length -gt 0)1076 {{1077 $paramDictionary = [Management.Automation.RuntimeDefinedParameterDictionary]::new()1078 foreach ($param in $dynamicParams)1079 {{1080 $param = $param.Value1081 1082 if(-not $MyInvocation.MyCommand.Parameters.ContainsKey($param.Name))1083 {{1084 $dynParam = [Management.Automation.RuntimeDefinedParameter]::new($param.Name, $param.ParameterType, $param.Attributes)1085 $paramDictionary.Add($param.Name, $dynParam)1086 }}1087 }}1088 1089 return $paramDictionary1090 }}1091 }} catch {{1092 throw1093 }}1094");1095 }1096 1097 internal string GetEndBlock()1098 {1099 return @"1100 try {1101 $steppablePipeline.End()1102 } catch {1103 throw1104 }1105";1106 }1107 1108 internal string GetCleanBlock()1109 {1110 // Here we don't need to enclose the script in a 'try/catch' like elsewhere, because1111 // 1. the 'Clean' block doesn't propagate up any exception (terminating error);1112 // 2. only one expression in the script, so nothing else needs to be stopped when invoking the method fails.1113 return @"1114 if ($null -ne $steppablePipeline) {1115 $steppablePipeline.Clean()1116 }1117";1118 }1119 1120 #endregion1121 1122 #region Helper methods for restricting commands needed by implicit and interactive remoting1123 1124 internal const string isSafeNameOrIdentifierRegex = @"^[-._:\\\p{Ll}\p{Lu}\p{Lt}\p{Lo}\p{Nd}\p{Lm}]{1,100}$";1125 1126 private static CommandMetadata GetRestrictedCmdlet(string cmdletName, string defaultParameterSet, string helpUri, params ParameterMetadata[] parameters)1127 {1128 Dictionary<string, ParameterMetadata> parametersDictionary = new Dictionary<string, ParameterMetadata>(StringComparer.OrdinalIgnoreCase);1129 foreach (ParameterMetadata parameter in parameters)1130 {1131 parametersDictionary.Add(parameter.Name, parameter);1132 }1133 1134 // isProxyForCmdlet:1135 // 1a. we would want to set it to false to get rid of unused common parameters1136 // (like OutBuffer - see bug Windows 7: #402213)1137 // 1b. otoh common parameters are going to be present anyway on all proxy functions1138 // that the host generates for its cmdlets that need cmdletbinding, so1139 // we should make sure that common parameters are safe, not hide them1140 // 2. otoh without cmdletbinding() unspecified parameters get bound to $null which might1141 // unnecessarily trigger validation attribute failures - see bug Windows 7: #4772181142 1143 CommandMetadata metadata = new CommandMetadata(1144 name: cmdletName,1145 commandType: CommandTypes.Cmdlet,1146 isProxyForCmdlet: true,1147 defaultParameterSetName: defaultParameterSet,1148 supportsShouldProcess: false,1149 confirmImpact: ConfirmImpact.None,1150 supportsPaging: false,1151 supportsTransactions: false,1152 positionalBinding: true,1153 parameters: parametersDictionary);1154 1155 metadata.HelpUri = helpUri;1156 1157 return metadata;1158 }1159 1160 private static CommandMetadata GetRestrictedGetCommand()1161 {1162 // remote Get-Command called by Import/Export-PSSession to get metadata for remote commands that user wants to import1163 1164 // remote Get-Command is also called by interactive remoting before entering the remote session to verify1165 // that Out-Default and Exit-PSSession commands are present in the remote session1166 1167 // value passed directly from Import-PSSession -CommandName to Get-Command -Name1168 // can't really restrict beyond basics1169 ParameterMetadata nameParameter = new ParameterMetadata("Name", typeof(string[]));1170 nameParameter.Attributes.Add(new ValidateLengthAttribute(0, 1000));1171 nameParameter.Attributes.Add(new ValidateCountAttribute(0, 1000));1172 1173 // value passed directly from Import-PSSession -PSSnapIn to Get-Command -Module1174 // can't really restrict beyond basics1175 ParameterMetadata moduleParameter = new ParameterMetadata("Module", typeof(string[]));1176 moduleParameter.Attributes.Add(new ValidateLengthAttribute(0, 1000));1177 moduleParameter.Attributes.Add(new ValidateCountAttribute(0, 100));1178 1179 // value passed directly from Import-PSSession -ArgumentList to Get-Command -ArgumentList1180 // can't really restrict beyond basics1181 ParameterMetadata argumentListParameter = new ParameterMetadata("ArgumentList", typeof(object[]));1182 argumentListParameter.Attributes.Add(new ValidateCountAttribute(0, 100));1183 1184 // value passed directly from Import-PSSession -CommandType to Get-Command -CommandType1185 // can't really restrict beyond basics1186 ParameterMetadata commandTypeParameter = new ParameterMetadata("CommandType", typeof(CommandTypes));1187 1188 // we do allow -ListImported switch1189 ParameterMetadata listImportedParameter = new ParameterMetadata("ListImported", typeof(SwitchParameter));1190 1191 // Need to expose ShowCommandInfo parameter for remote ShowCommand support.1192 ParameterMetadata showCommandInfo = new ParameterMetadata("ShowCommandInfo", typeof(SwitchParameter));1193 1194 return GetRestrictedCmdlet(1195 "Get-Command",1196 null, // defaultParameterSet1197 "https://go.microsoft.com/fwlink/?LinkID=113309", // helpUri1198 nameParameter,1199 moduleParameter,1200 argumentListParameter,