4745 lines
211 KiB
C#
4745 lines
211 KiB
C#
// Copyright (c) Microsoft Corporation.
|
|
// Licensed under the MIT License.
|
|
|
|
using System.Collections;
|
|
using System.Collections.Generic;
|
|
using System.Collections.ObjectModel;
|
|
using System.Diagnostics.CodeAnalysis;
|
|
using System.Globalization;
|
|
using System.Linq;
|
|
using System.Management.Automation.Host;
|
|
using System.Management.Automation.Internal;
|
|
using System.Management.Automation.Language;
|
|
using System.Management.Automation.Runspaces;
|
|
using System.Text;
|
|
|
|
namespace System.Management.Automation
|
|
{
|
|
/// <summary>
|
|
/// This is the interface between the CommandProcessor and the various
|
|
/// parameter binders required to bind parameters to a cmdlet.
|
|
/// </summary>
|
|
internal class CmdletParameterBinderController : ParameterBinderController
|
|
{
|
|
#region tracer
|
|
|
|
[TraceSource("ParameterBinderController", "Controls the interaction between the command processor and the parameter binder(s).")]
|
|
private static readonly PSTraceSource s_tracer = PSTraceSource.GetTracer("ParameterBinderController", "Controls the interaction between the command processor and the parameter binder(s).");
|
|
|
|
#endregion tracer
|
|
|
|
#region ctor
|
|
|
|
/// <summary>
|
|
/// Initializes the cmdlet parameter binder controller for
|
|
/// the specified cmdlet and engine context.
|
|
/// </summary>
|
|
/// <param name="cmdlet">
|
|
/// The cmdlet that the parameters will be bound to.
|
|
/// </param>
|
|
/// <param name="commandMetadata">
|
|
/// The metadata about the cmdlet.
|
|
/// </param>
|
|
/// <param name="parameterBinder">
|
|
/// The default parameter binder to use.
|
|
/// </param>
|
|
internal CmdletParameterBinderController(
|
|
Cmdlet cmdlet,
|
|
CommandMetadata commandMetadata,
|
|
ParameterBinderBase parameterBinder)
|
|
: base(
|
|
cmdlet.MyInvocation,
|
|
cmdlet.Context,
|
|
parameterBinder)
|
|
{
|
|
if (cmdlet is null)
|
|
{
|
|
throw PSTraceSource.NewArgumentNullException(nameof(cmdlet));
|
|
}
|
|
|
|
if (commandMetadata is null)
|
|
{
|
|
throw PSTraceSource.NewArgumentNullException(nameof(commandMetadata));
|
|
}
|
|
|
|
this.Command = cmdlet;
|
|
_commandRuntime = (MshCommandRuntime)cmdlet.CommandRuntime;
|
|
_commandMetadata = commandMetadata;
|
|
|
|
// Add the static parameter metadata to the bindable parameters
|
|
// And add them to the unbound parameters list
|
|
|
|
if (commandMetadata.ImplementsDynamicParameters)
|
|
{
|
|
// ReplaceMetadata makes a copy for us, so we can use that collection as is.
|
|
this.UnboundParameters = this.BindableParameters.ReplaceMetadata(commandMetadata.StaticCommandParameterMetadata);
|
|
}
|
|
else
|
|
{
|
|
_bindableParameters = commandMetadata.StaticCommandParameterMetadata;
|
|
|
|
// Must make a copy of the list because we'll modify it.
|
|
this.UnboundParameters = new List<MergedCompiledCommandParameter>(_bindableParameters.BindableParameters.Values);
|
|
}
|
|
}
|
|
|
|
#endregion ctor
|
|
|
|
#region helper_methods
|
|
|
|
/// <summary>
|
|
/// Binds the specified command-line parameters to the target.
|
|
/// </summary>
|
|
/// <param name="arguments">
|
|
/// Parameters to the command.
|
|
/// </param>
|
|
/// <exception cref="ParameterBindingException">
|
|
/// If any parameters fail to bind,
|
|
/// or
|
|
/// If any mandatory parameters are missing.
|
|
/// </exception>
|
|
/// <exception cref="MetadataException">
|
|
/// If there is an error generating the metadata for dynamic parameters.
|
|
/// </exception>
|
|
internal void BindCommandLineParameters(Collection<CommandParameterInternal> arguments)
|
|
{
|
|
s_tracer.WriteLine("Argument count: {0}", arguments.Count);
|
|
|
|
BindCommandLineParametersNoValidation(arguments);
|
|
|
|
// Is pipeline input expected?
|
|
bool isPipelineInputExpected = !(_commandRuntime.IsClosed && _commandRuntime.InputPipe.Empty);
|
|
|
|
int validParameterSetCount;
|
|
|
|
if (!isPipelineInputExpected)
|
|
{
|
|
// Since pipeline input is not expected, ensure that we have a single
|
|
// parameter set and that all the mandatory
|
|
// parameters for the working parameter set are specified, or prompt
|
|
|
|
validParameterSetCount = ValidateParameterSets(false, true);
|
|
}
|
|
else
|
|
{
|
|
// Use ValidateParameterSets to get the number of valid parameter
|
|
// sets.
|
|
|
|
// NTRAID#Windows Out Of Band Releases-2005/11/07-923917-JonN
|
|
validParameterSetCount = ValidateParameterSets(true, false);
|
|
}
|
|
|
|
// If the parameter set is determined and the default parameters are not used
|
|
// we try the default parameter binding again because it may contain some mandatory
|
|
// parameters
|
|
if (validParameterSetCount == 1 && !DefaultParameterBindingInUse)
|
|
{
|
|
ApplyDefaultParameterBinding("Mandatory Checking", false);
|
|
}
|
|
|
|
// If there are multiple valid parameter sets and we are expecting pipeline inputs,
|
|
// we should filter out those parameter sets that cannot take pipeline inputs anymore.
|
|
if (validParameterSetCount > 1 && isPipelineInputExpected)
|
|
{
|
|
uint filteredValidParameterSetFlags = FilterParameterSetsTakingNoPipelineInput();
|
|
if (filteredValidParameterSetFlags != _currentParameterSetFlag)
|
|
{
|
|
_currentParameterSetFlag = filteredValidParameterSetFlags;
|
|
// The valid parameter set flag is narrowed down, we get the new validParameterSetCount
|
|
validParameterSetCount = ValidateParameterSets(true, false);
|
|
}
|
|
}
|
|
|
|
using (ParameterBinderBase.bindingTracer.TraceScope(
|
|
"MANDATORY PARAMETER CHECK on cmdlet [{0}]",
|
|
_commandMetadata.Name))
|
|
{
|
|
try
|
|
{
|
|
// The missingMandatoryParameters out parameter is used for error reporting when binding from the pipeline.
|
|
// We're not binding from the pipeline here, and if a mandatory non-pipeline parameter is missing, it will
|
|
// be prompted for, or an exception will be raised, so we can ignore the missingMandatoryParameters out parameter.
|
|
Collection<MergedCompiledCommandParameter> missingMandatoryParameters;
|
|
|
|
// We shouldn't prompt for mandatory parameters if this command is private.
|
|
bool promptForMandatoryParameters = (Command.CommandInfo.Visibility == SessionStateEntryVisibility.Public);
|
|
HandleUnboundMandatoryParameters(validParameterSetCount, true, promptForMandatoryParameters, isPipelineInputExpected, out missingMandatoryParameters);
|
|
|
|
if (DefaultParameterBinder is ScriptParameterBinder)
|
|
{
|
|
BindUnboundScriptParameters();
|
|
}
|
|
}
|
|
catch (ParameterBindingException pbex)
|
|
{
|
|
if (!DefaultParameterBindingInUse)
|
|
{
|
|
throw;
|
|
}
|
|
|
|
ThrowElaboratedBindingException(pbex);
|
|
}
|
|
}
|
|
|
|
// If there is no more expected input, ensure there is a single
|
|
// parameter set selected
|
|
|
|
if (!isPipelineInputExpected)
|
|
{
|
|
VerifyParameterSetSelected();
|
|
}
|
|
|
|
// Set the prepipeline parameter set flags so that they can be restored
|
|
// between each pipeline object.
|
|
|
|
_prePipelineProcessingParameterSetFlags = _currentParameterSetFlag;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Binds the unbound arguments to parameters but does not
|
|
/// perform mandatory parameter validation or parameter set validation.
|
|
/// </summary>
|
|
internal void BindCommandLineParametersNoValidation(Collection<CommandParameterInternal> arguments)
|
|
{
|
|
var psCompiledScriptCmdlet = this.Command as PSScriptCmdlet;
|
|
if (psCompiledScriptCmdlet != null)
|
|
{
|
|
psCompiledScriptCmdlet.PrepareForBinding(this.CommandLineParameters);
|
|
}
|
|
|
|
// Add the passed in arguments to the unboundArguments collection
|
|
|
|
foreach (CommandParameterInternal argument in arguments)
|
|
{
|
|
UnboundArguments.Add(argument);
|
|
}
|
|
|
|
CommandMetadata cmdletMetadata = _commandMetadata;
|
|
// Clear the warningSet at the beginning.
|
|
_warningSet.Clear();
|
|
// Parse $PSDefaultParameterValues to get all valid <parameter, value> pairs
|
|
_allDefaultParameterValuePairs = this.GetDefaultParameterValuePairs(true);
|
|
// Set to false at the beginning
|
|
DefaultParameterBindingInUse = false;
|
|
// Clear the bound default parameters at the beginning
|
|
BoundDefaultParameters.Clear();
|
|
|
|
// Reparse the arguments based on the merged metadata
|
|
ReparseUnboundArguments();
|
|
|
|
using (ParameterBinderBase.bindingTracer.TraceScope(
|
|
"BIND NAMED cmd line args [{0}]",
|
|
_commandMetadata.Name))
|
|
{
|
|
// Bind the actual arguments
|
|
UnboundArguments = BindParameters(_currentParameterSetFlag, this.UnboundArguments);
|
|
}
|
|
|
|
ParameterBindingException reportedBindingException;
|
|
ParameterBindingException currentBindingException;
|
|
|
|
using (ParameterBinderBase.bindingTracer.TraceScope(
|
|
"BIND POSITIONAL cmd line args [{0}]",
|
|
_commandMetadata.Name))
|
|
{
|
|
// Now that we know the parameter set, bind the positional parameters
|
|
UnboundArguments =
|
|
BindPositionalParameters(
|
|
UnboundArguments,
|
|
_currentParameterSetFlag,
|
|
cmdletMetadata.DefaultParameterSetFlag,
|
|
out currentBindingException);
|
|
|
|
reportedBindingException = currentBindingException;
|
|
}
|
|
|
|
// Try applying the default parameter binding after POSITIONAL BIND so that the default parameter
|
|
// values can influence the parameter set selection earlier than the default parameter set.
|
|
ApplyDefaultParameterBinding("POSITIONAL BIND", false);
|
|
|
|
// We need to make sure there is at least one valid parameter set. Its
|
|
// OK to allow more than one as long as one of them takes pipeline input.
|
|
|
|
// NTRAID#Windows Out Of Band Releases-2006/02/14-928660-JonN
|
|
// Pipeline input fails to bind to pipeline enabled parameter
|
|
// second parameter changed from true to false
|
|
ValidateParameterSets(true, false);
|
|
|
|
// Always get the dynamic parameters as there may be mandatory parameters there
|
|
|
|
// Now try binding the dynamic parameters
|
|
HandleCommandLineDynamicParameters(out currentBindingException);
|
|
|
|
// Try binding the default parameters again. After dynamic binding, new parameter metadata are
|
|
// included, so it's possible a previously unsuccessful binding will succeed.
|
|
ApplyDefaultParameterBinding("DYNAMIC BIND", true);
|
|
|
|
// If this generated an exception (but we didn't have one from the non-dynamic
|
|
// parameters, report on this one.
|
|
if (reportedBindingException is null)
|
|
reportedBindingException = currentBindingException;
|
|
|
|
// If the cmdlet implements a ValueFromRemainingArguments parameter (VarArgs)
|
|
// bind the unbound arguments to that parameter.
|
|
HandleRemainingArguments();
|
|
|
|
VerifyArgumentsProcessed(reportedBindingException);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Process all valid parameter sets, and filter out those that don't take any pipeline input.
|
|
/// </summary>
|
|
/// <returns>
|
|
/// The new valid parameter set flags
|
|
/// </returns>
|
|
private uint FilterParameterSetsTakingNoPipelineInput()
|
|
{
|
|
uint parameterSetsTakingPipeInput = 0;
|
|
bool findPipeParameterInAllSets = false;
|
|
|
|
foreach (KeyValuePair<MergedCompiledCommandParameter, DelayedScriptBlockArgument> entry in _delayBindScriptBlocks)
|
|
{
|
|
parameterSetsTakingPipeInput |= entry.Key.Parameter.ParameterSetFlags;
|
|
}
|
|
|
|
foreach (MergedCompiledCommandParameter parameter in UnboundParameters)
|
|
{
|
|
// If a parameter doesn't take pipeline input at all, we can skip it
|
|
if (!parameter.Parameter.IsPipelineParameterInSomeParameterSet)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
var matchingParameterSetMetadata =
|
|
parameter.Parameter.GetMatchingParameterSetData(_currentParameterSetFlag);
|
|
|
|
foreach (ParameterSetSpecificMetadata parameterSetMetadata in matchingParameterSetMetadata)
|
|
{
|
|
if (parameterSetMetadata.ValueFromPipeline || parameterSetMetadata.ValueFromPipelineByPropertyName)
|
|
{
|
|
if (parameterSetMetadata.ParameterSetFlag == 0 && parameterSetMetadata.IsInAllSets)
|
|
{
|
|
// The parameter takes pipeline input and is in all sets, we don't change the _currentParameterSetFlag
|
|
parameterSetsTakingPipeInput = 0;
|
|
findPipeParameterInAllSets = true;
|
|
break;
|
|
}
|
|
else
|
|
{
|
|
parameterSetsTakingPipeInput |= parameterSetMetadata.ParameterSetFlag;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (findPipeParameterInAllSets)
|
|
break;
|
|
}
|
|
|
|
// If parameterSetsTakingPipeInput is 0, then no parameter set from the _currentParameterSetFlag can take piped objects.
|
|
// Then we just leave what it was, and the pipeline binding deal with the error later
|
|
if (parameterSetsTakingPipeInput != 0)
|
|
return _currentParameterSetFlag & parameterSetsTakingPipeInput;
|
|
else
|
|
return _currentParameterSetFlag;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Apply the binding for the default parameter defined by the user.
|
|
/// </summary>
|
|
/// <param name="bindingStage">
|
|
/// Dictate which binding stage this default binding happens
|
|
/// </param>
|
|
/// <param name="isDynamic">
|
|
/// Special operation needed if the default binding happens at the dynamic binding stage
|
|
/// </param>
|
|
/// <returns></returns>
|
|
private void ApplyDefaultParameterBinding(string bindingStage, bool isDynamic)
|
|
{
|
|
if (!_useDefaultParameterBinding)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (isDynamic)
|
|
{
|
|
// Get user defined default parameter value pairs again, so that the
|
|
// dynamic parameter value pairs could be involved.
|
|
_allDefaultParameterValuePairs = GetDefaultParameterValuePairs(false);
|
|
}
|
|
|
|
Dictionary<MergedCompiledCommandParameter, object> qualifiedParameterValuePairs = GetQualifiedParameterValuePairs(_currentParameterSetFlag, _allDefaultParameterValuePairs);
|
|
if (qualifiedParameterValuePairs != null)
|
|
{
|
|
bool isSuccess = false;
|
|
using (ParameterBinderBase.bindingTracer.TraceScope(
|
|
"BIND DEFAULT <parameter, value> pairs after [{0}] for [{1}]",
|
|
bindingStage, _commandMetadata.Name))
|
|
{
|
|
isSuccess = BindDefaultParameters(_currentParameterSetFlag, qualifiedParameterValuePairs);
|
|
if (isSuccess && !DefaultParameterBindingInUse)
|
|
{
|
|
DefaultParameterBindingInUse = true;
|
|
}
|
|
}
|
|
|
|
s_tracer.WriteLine("BIND DEFAULT after [{0}] result [{1}]", bindingStage, isSuccess);
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Bind the default parameter value pairs.
|
|
/// </summary>
|
|
/// <param name="validParameterSetFlag">ValidParameterSetFlag.</param>
|
|
/// <param name="defaultParameterValues">Default value pairs.</param>
|
|
/// <returns>
|
|
/// true if there is at least one default parameter bound successfully
|
|
/// false if there is no default parameter bound successfully
|
|
/// </returns>
|
|
private bool BindDefaultParameters(uint validParameterSetFlag, Dictionary<MergedCompiledCommandParameter, object> defaultParameterValues)
|
|
{
|
|
bool ret = false;
|
|
foreach (var pair in defaultParameterValues)
|
|
{
|
|
MergedCompiledCommandParameter parameter = pair.Key;
|
|
object argumentValue = pair.Value;
|
|
string parameterName = parameter.Parameter.Name;
|
|
|
|
try
|
|
{
|
|
ScriptBlock scriptBlockArg = argumentValue as ScriptBlock;
|
|
if (scriptBlockArg != null)
|
|
{
|
|
// Get the current binding state, and pass it to the ScriptBlock as the argument
|
|
// The 'arg' includes HashSet properties 'BoundParameters', 'BoundPositionalParameters',
|
|
// 'BoundDefaultParameters', and 'LastBindingStage'. So the user can set value
|
|
// to a parameter depending on the current binding state.
|
|
PSObject arg = WrapBindingState();
|
|
Collection<PSObject> results = scriptBlockArg.Invoke(arg);
|
|
if (results is null || results.Count == 0)
|
|
{
|
|
continue;
|
|
}
|
|
else if (results.Count == 1)
|
|
{
|
|
argumentValue = results[0];
|
|
}
|
|
else
|
|
{
|
|
argumentValue = results;
|
|
}
|
|
}
|
|
|
|
CommandParameterInternal bindableArgument =
|
|
CommandParameterInternal.CreateParameterWithArgument(
|
|
/*parameterAst*/null, parameterName, "-" + parameterName + ":",
|
|
/*argumentAst*/null, argumentValue, false);
|
|
|
|
bool bindResult =
|
|
BindParameter(
|
|
validParameterSetFlag,
|
|
bindableArgument,
|
|
parameter,
|
|
ParameterBindingFlags.ShouldCoerceType | ParameterBindingFlags.DelayBindScriptBlock);
|
|
|
|
if (bindResult && !ret)
|
|
{
|
|
ret = true;
|
|
}
|
|
|
|
if (bindResult)
|
|
{
|
|
BoundDefaultParameters.Add(parameterName);
|
|
}
|
|
}
|
|
catch (ParameterBindingException ex)
|
|
{
|
|
// We don't want the failures in default binding affect the command line binding,
|
|
// so we write out a warning and ignore this binding failure
|
|
if (!_warningSet.Contains(_commandMetadata.Name + Separator + parameterName))
|
|
{
|
|
string message = string.Format(CultureInfo.InvariantCulture,
|
|
ParameterBinderStrings.FailToBindDefaultParameter,
|
|
LanguagePrimitives.IsNull(argumentValue) ? "null" : argumentValue.ToString(),
|
|
parameterName, ex.Message);
|
|
_commandRuntime.WriteWarning(message);
|
|
_warningSet.Add(_commandMetadata.Name + Separator + parameterName);
|
|
}
|
|
|
|
continue;
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Wrap up current binding state to provide more information to the user.
|
|
/// </summary>
|
|
/// <returns></returns>
|
|
private PSObject WrapBindingState()
|
|
{
|
|
HashSet<string> boundParameterNames = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
|
HashSet<string> boundPositionalParameterNames =
|
|
this.DefaultParameterBinder.CommandLineParameters.CopyBoundPositionalParameters();
|
|
HashSet<string> boundDefaultParameterNames = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
|
|
|
foreach (string paramName in BoundParameters.Keys)
|
|
{
|
|
boundParameterNames.Add(paramName);
|
|
}
|
|
|
|
foreach (string paramName in BoundDefaultParameters)
|
|
{
|
|
boundDefaultParameterNames.Add(paramName);
|
|
}
|
|
|
|
PSObject result = new PSObject();
|
|
result.Properties.Add(new PSNoteProperty("BoundParameters", boundParameterNames));
|
|
result.Properties.Add(new PSNoteProperty("BoundPositionalParameters", boundPositionalParameterNames));
|
|
result.Properties.Add(new PSNoteProperty("BoundDefaultParameters", boundDefaultParameterNames));
|
|
|
|
return result;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get all qualified default parameter value pairs based on the
|
|
/// given currentParameterSetFlag.
|
|
/// </summary>
|
|
/// <param name="currentParameterSetFlag"></param>
|
|
/// <param name="availableParameterValuePairs"></param>
|
|
/// <returns>Null if no qualified pair found.</returns>
|
|
private Dictionary<MergedCompiledCommandParameter, object> GetQualifiedParameterValuePairs(
|
|
uint currentParameterSetFlag,
|
|
Dictionary<MergedCompiledCommandParameter, object> availableParameterValuePairs)
|
|
{
|
|
if (availableParameterValuePairs is null)
|
|
{
|
|
return null;
|
|
}
|
|
|
|
Dictionary<MergedCompiledCommandParameter, object> result = new Dictionary<MergedCompiledCommandParameter, object>();
|
|
|
|
uint possibleParameterFlag = uint.MaxValue;
|
|
foreach (var pair in availableParameterValuePairs)
|
|
{
|
|
MergedCompiledCommandParameter param = pair.Key;
|
|
if ((param.Parameter.ParameterSetFlags & currentParameterSetFlag) == 0 && !param.Parameter.IsInAllSets)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (BoundArguments.ContainsKey(param.Parameter.Name))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
// check if this param's set conflicts with other possible params.
|
|
if (param.Parameter.ParameterSetFlags != 0)
|
|
{
|
|
possibleParameterFlag &= param.Parameter.ParameterSetFlags;
|
|
if (possibleParameterFlag == 0)
|
|
{
|
|
return null;
|
|
}
|
|
}
|
|
|
|
result.Add(param, pair.Value);
|
|
}
|
|
|
|
if (result.Count > 0)
|
|
{
|
|
return result;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get the aliases of the the current cmdlet.
|
|
/// </summary>
|
|
/// <returns></returns>
|
|
private List<string> GetAliasOfCurrentCmdlet()
|
|
{
|
|
var results = Context.SessionState.Internal.GetAliasesByCommandName(_commandMetadata.Name).ToList();
|
|
|
|
return results.Count > 0 ? results : null;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Check if the passed-in aliasName matches an alias name in _aliasList.
|
|
/// </summary>
|
|
/// <param name="aliasName"></param>
|
|
/// <returns></returns>
|
|
private bool MatchAnyAlias(string aliasName)
|
|
{
|
|
if (_aliasList is null)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
bool result = false;
|
|
WildcardPattern aliasPattern = WildcardPattern.Get(aliasName, WildcardOptions.IgnoreCase);
|
|
foreach (string alias in _aliasList)
|
|
{
|
|
if (aliasPattern.IsMatch(alias))
|
|
{
|
|
result = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
internal IDictionary DefaultParameterValues { get; set; }
|
|
/// <summary>
|
|
/// Get all available default parameter value pairs.
|
|
/// </summary>
|
|
/// <returns>Return the available parameter value pairs. Otherwise return null.</returns>
|
|
private Dictionary<MergedCompiledCommandParameter, object> GetDefaultParameterValuePairs(bool needToGetAlias)
|
|
{
|
|
if (DefaultParameterValues is null)
|
|
{
|
|
_useDefaultParameterBinding = false;
|
|
return null;
|
|
}
|
|
|
|
var availablePairs = new Dictionary<MergedCompiledCommandParameter, object>();
|
|
|
|
if (needToGetAlias && DefaultParameterValues.Count > 0)
|
|
{
|
|
// Get all aliases of the current cmdlet
|
|
_aliasList = GetAliasOfCurrentCmdlet();
|
|
}
|
|
|
|
// Set flag to true by default
|
|
_useDefaultParameterBinding = true;
|
|
|
|
string currentCmdletName = _commandMetadata.Name;
|
|
|
|
IDictionary<string, MergedCompiledCommandParameter> bindableParameters = BindableParameters.BindableParameters;
|
|
IDictionary<string, MergedCompiledCommandParameter> bindableAlias = BindableParameters.AliasedParameters;
|
|
|
|
// Contains parameters that are set with different values by settings in $PSDefaultParameterValues.
|
|
// We should ignore those settings and write out a warning
|
|
var parametersToRemove = new HashSet<MergedCompiledCommandParameter>();
|
|
var wildcardDefault = new Dictionary<string, object>();
|
|
// Contains keys that are in bad format. For every bad format key, we should write out a warning message
|
|
// the first time we encounter it, and remove it from the $PSDefaultParameterValues
|
|
var keysToRemove = new List<object>();
|
|
|
|
foreach (DictionaryEntry entry in DefaultParameterValues)
|
|
{
|
|
string key = entry.Key as string;
|
|
if (key is null)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
key = key.Trim();
|
|
string cmdletName = null;
|
|
string parameterName = null;
|
|
|
|
// The key is not in valid format
|
|
if (!DefaultParameterDictionary.CheckKeyIsValid(key, ref cmdletName, ref parameterName))
|
|
{
|
|
if (key.Equals("Disabled", StringComparison.OrdinalIgnoreCase) &&
|
|
LanguagePrimitives.IsTrue(entry.Value))
|
|
{
|
|
_useDefaultParameterBinding = false;
|
|
return null;
|
|
}
|
|
// Write out a warning message if the key is not 'Disabled'
|
|
if (!key.Equals("Disabled", StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
keysToRemove.Add(entry.Key);
|
|
}
|
|
|
|
continue;
|
|
}
|
|
|
|
Diagnostics.Assert(cmdletName != null && parameterName != null, "The cmdletName and parameterName should be set in CheckKeyIsValid");
|
|
|
|
if (WildcardPattern.ContainsWildcardCharacters(key))
|
|
{
|
|
wildcardDefault.Add(cmdletName + Separator + parameterName, entry.Value);
|
|
continue;
|
|
}
|
|
|
|
// Continue to process this entry only if the specified cmdletName is the name
|
|
// of the current cmdlet, or is an alias name of the current cmdlet.
|
|
if (!cmdletName.Equals(currentCmdletName, StringComparison.OrdinalIgnoreCase) && !MatchAnyAlias(cmdletName))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
GetDefaultParameterValuePairsHelper(
|
|
cmdletName, parameterName, entry.Value,
|
|
bindableParameters, bindableAlias,
|
|
availablePairs, parametersToRemove);
|
|
}
|
|
|
|
foreach (KeyValuePair<string, object> wildcard in wildcardDefault)
|
|
{
|
|
string key = wildcard.Key;
|
|
|
|
string cmdletName = key.Substring(0, key.IndexOf(Separator, StringComparison.OrdinalIgnoreCase));
|
|
string parameterName = key.Substring(key.IndexOf(Separator, StringComparison.OrdinalIgnoreCase) + Separator.Length);
|
|
|
|
WildcardPattern cmdletPattern = WildcardPattern.Get(cmdletName, WildcardOptions.IgnoreCase);
|
|
// Continue to process this entry only if the cmdletName matches the name of the current
|
|
// cmdlet, or matches an alias name of the current cmdlet
|
|
if (!cmdletPattern.IsMatch(currentCmdletName) && !MatchAnyAlias(cmdletName))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (!WildcardPattern.ContainsWildcardCharacters(parameterName))
|
|
{
|
|
GetDefaultParameterValuePairsHelper(
|
|
cmdletName, parameterName, wildcard.Value,
|
|
bindableParameters, bindableAlias,
|
|
availablePairs, parametersToRemove);
|
|
|
|
continue;
|
|
}
|
|
|
|
WildcardPattern parameterPattern = MemberMatch.GetNamePattern(parameterName);
|
|
var matches = new List<MergedCompiledCommandParameter>();
|
|
|
|
foreach (KeyValuePair<string, MergedCompiledCommandParameter> entry in bindableParameters)
|
|
{
|
|
if (parameterPattern.IsMatch(entry.Key))
|
|
{
|
|
matches.Add(entry.Value);
|
|
}
|
|
}
|
|
|
|
foreach (KeyValuePair<string, MergedCompiledCommandParameter> entry in bindableAlias)
|
|
{
|
|
if (parameterPattern.IsMatch(entry.Key))
|
|
{
|
|
matches.Add(entry.Value);
|
|
}
|
|
}
|
|
|
|
if (matches.Count > 1)
|
|
{
|
|
// The parameterPattern matches more than one parameters, so we write out a warning message and ignore this setting
|
|
if (!_warningSet.Contains(cmdletName + Separator + parameterName))
|
|
{
|
|
_commandRuntime.WriteWarning(
|
|
string.Format(CultureInfo.InvariantCulture, ParameterBinderStrings.MultipleParametersMatched, parameterName));
|
|
_warningSet.Add(cmdletName + Separator + parameterName);
|
|
}
|
|
|
|
continue;
|
|
}
|
|
|
|
if (matches.Count == 1)
|
|
{
|
|
if (!availablePairs.ContainsKey(matches[0]))
|
|
{
|
|
availablePairs.Add(matches[0], wildcard.Value);
|
|
continue;
|
|
}
|
|
|
|
if (!wildcard.Value.Equals(availablePairs[matches[0]]))
|
|
{
|
|
if (!_warningSet.Contains(cmdletName + Separator + parameterName))
|
|
{
|
|
_commandRuntime.WriteWarning(
|
|
string.Format(CultureInfo.InvariantCulture, ParameterBinderStrings.DifferentValuesAssignedToSingleParameter, parameterName));
|
|
_warningSet.Add(cmdletName + Separator + parameterName);
|
|
}
|
|
|
|
parametersToRemove.Add(matches[0]);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (keysToRemove.Count > 0)
|
|
{
|
|
var keysInError = new StringBuilder();
|
|
foreach (object badFormatKey in keysToRemove)
|
|
{
|
|
if (DefaultParameterValues.Contains(badFormatKey))
|
|
DefaultParameterValues.Remove(badFormatKey);
|
|
|
|
keysInError.Append(badFormatKey.ToString() + ", ");
|
|
}
|
|
|
|
keysInError.Remove(keysInError.Length - 2, 2);
|
|
var multipleKeys = keysToRemove.Count > 1;
|
|
string formatString = multipleKeys
|
|
? ParameterBinderStrings.MultipleKeysInBadFormat
|
|
: ParameterBinderStrings.SingleKeyInBadFormat;
|
|
_commandRuntime.WriteWarning(
|
|
string.Format(CultureInfo.InvariantCulture, formatString, keysInError));
|
|
}
|
|
|
|
foreach (MergedCompiledCommandParameter param in parametersToRemove)
|
|
{
|
|
availablePairs.Remove(param);
|
|
}
|
|
|
|
if (availablePairs.Count > 0)
|
|
{
|
|
return availablePairs;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
/// <summary>
|
|
/// A helper method for GetDefaultParameterValuePairs.
|
|
/// </summary>
|
|
/// <param name="cmdletName"></param>
|
|
/// <param name="paramName"></param>
|
|
/// <param name="paramValue"></param>
|
|
/// <param name="bindableParameters"></param>
|
|
/// <param name="bindableAlias"></param>
|
|
/// <param name="result"></param>
|
|
/// <param name="parametersToRemove"></param>
|
|
private void GetDefaultParameterValuePairsHelper(
|
|
string cmdletName, string paramName, object paramValue,
|
|
IDictionary<string, MergedCompiledCommandParameter> bindableParameters,
|
|
IDictionary<string, MergedCompiledCommandParameter> bindableAlias,
|
|
Dictionary<MergedCompiledCommandParameter, object> result,
|
|
HashSet<MergedCompiledCommandParameter> parametersToRemove)
|
|
{
|
|
// No exception should be thrown if we cannot find a match for the 'paramName',
|
|
// because the 'paramName' could be a dynamic parameter name, and this dynamic parameter
|
|
// hasn't been introduced at the current stage.
|
|
bool writeWarning = false;
|
|
MergedCompiledCommandParameter matchParameter;
|
|
object resultObject;
|
|
if (bindableParameters.TryGetValue(paramName, out matchParameter))
|
|
{
|
|
if (!result.TryGetValue(matchParameter, out resultObject))
|
|
{
|
|
result.Add(matchParameter, paramValue);
|
|
return;
|
|
}
|
|
|
|
if (!paramValue.Equals(resultObject))
|
|
{
|
|
writeWarning = true;
|
|
parametersToRemove.Add(matchParameter);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (bindableAlias.TryGetValue(paramName, out matchParameter))
|
|
{
|
|
if (!result.TryGetValue(matchParameter, out resultObject))
|
|
{
|
|
result.Add(matchParameter, paramValue);
|
|
return;
|
|
}
|
|
|
|
if (!paramValue.Equals(resultObject))
|
|
{
|
|
writeWarning = true;
|
|
parametersToRemove.Add(matchParameter);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (writeWarning && !_warningSet.Contains(cmdletName + Separator + paramName))
|
|
{
|
|
_commandRuntime.WriteWarning(
|
|
string.Format(CultureInfo.InvariantCulture, ParameterBinderStrings.DifferentValuesAssignedToSingleParameter, paramName));
|
|
_warningSet.Add(cmdletName + Separator + paramName);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Verify if all arguments from the command line are bound.
|
|
/// </summary>
|
|
/// <param name="originalBindingException">
|
|
/// Previous binding exceptions that possibly causes the failure
|
|
/// </param>
|
|
private void VerifyArgumentsProcessed(ParameterBindingException originalBindingException)
|
|
{
|
|
// Now verify that all the arguments that were passed in were processed.
|
|
|
|
if (UnboundArguments.Count > 0)
|
|
{
|
|
ParameterBindingException bindingException;
|
|
CommandParameterInternal parameter = UnboundArguments[0];
|
|
|
|
// Get the argument type that was specified
|
|
|
|
Type specifiedType = null;
|
|
object argumentValue = parameter.ArgumentValue;
|
|
if (argumentValue != null && argumentValue != UnboundParameter.Value)
|
|
{
|
|
specifiedType = argumentValue.GetType();
|
|
}
|
|
|
|
if (parameter.ParameterNameSpecified)
|
|
{
|
|
bindingException =
|
|
new ParameterBindingException(
|
|
ErrorCategory.InvalidArgument,
|
|
this.Command.MyInvocation,
|
|
GetParameterErrorExtent(parameter),
|
|
parameter.ParameterName,
|
|
null,
|
|
specifiedType,
|
|
ParameterBinderStrings.NamedParameterNotFound,
|
|
"NamedParameterNotFound");
|
|
}
|
|
else
|
|
{
|
|
// If this was a positional parameter, and we have the original exception,
|
|
// report on the original error
|
|
if (originalBindingException != null)
|
|
{
|
|
bindingException = originalBindingException;
|
|
}
|
|
// Otherwise, give a generic error.
|
|
else
|
|
{
|
|
string argument = StringLiterals.DollarNull;
|
|
if (parameter.ArgumentValue != null)
|
|
{
|
|
try
|
|
{
|
|
argument = parameter.ArgumentValue.ToString();
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
bindingException =
|
|
new ParameterBindingArgumentTransformationException(
|
|
e,
|
|
ErrorCategory.InvalidData,
|
|
this.InvocationInfo,
|
|
null,
|
|
null,
|
|
null,
|
|
parameter.ArgumentValue.GetType(),
|
|
ParameterBinderStrings.ParameterArgumentTransformationErrorMessageOnly,
|
|
"ParameterArgumentTransformationErrorMessageOnly",
|
|
e.Message);
|
|
|
|
if (!DefaultParameterBindingInUse)
|
|
{
|
|
throw bindingException;
|
|
}
|
|
else
|
|
{
|
|
ThrowElaboratedBindingException(bindingException);
|
|
}
|
|
}
|
|
}
|
|
|
|
bindingException =
|
|
new ParameterBindingException(
|
|
ErrorCategory.InvalidArgument,
|
|
this.Command.MyInvocation,
|
|
null,
|
|
argument,
|
|
null,
|
|
specifiedType,
|
|
ParameterBinderStrings.PositionalParameterNotFound,
|
|
"PositionalParameterNotFound");
|
|
}
|
|
}
|
|
|
|
if (!DefaultParameterBindingInUse)
|
|
{
|
|
throw bindingException;
|
|
}
|
|
else
|
|
{
|
|
ThrowElaboratedBindingException(bindingException);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Verifies that a single parameter set is selected and throws an exception if
|
|
/// one of there are multiple and one of them is not the default parameter set.
|
|
/// </summary>
|
|
private void VerifyParameterSetSelected()
|
|
{
|
|
// Now verify that a parameter set has been selected if any parameter sets
|
|
// were defined.
|
|
|
|
if (this.BindableParameters.ParameterSetCount > 1)
|
|
{
|
|
if (_currentParameterSetFlag == uint.MaxValue)
|
|
{
|
|
if ((_currentParameterSetFlag &
|
|
_commandMetadata.DefaultParameterSetFlag) != 0 &&
|
|
_commandMetadata.DefaultParameterSetFlag != uint.MaxValue)
|
|
{
|
|
ParameterBinderBase.bindingTracer.WriteLine(
|
|
"{0} valid parameter sets, using the DEFAULT PARAMETER SET: [{0}]",
|
|
this.BindableParameters.ParameterSetCount.ToString(),
|
|
_commandMetadata.DefaultParameterSetName);
|
|
|
|
_currentParameterSetFlag =
|
|
_commandMetadata.DefaultParameterSetFlag;
|
|
}
|
|
else
|
|
{
|
|
ParameterBinderBase.bindingTracer.TraceError(
|
|
"ERROR: {0} valid parameter sets, but NOT DEFAULT PARAMETER SET.",
|
|
this.BindableParameters.ParameterSetCount);
|
|
|
|
// Throw an exception for ambiguous parameter set
|
|
ThrowAmbiguousParameterSetException(_currentParameterSetFlag, BindableParameters);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Restores the specified parameter to the original value.
|
|
/// </summary>
|
|
/// <param name="argumentToBind">
|
|
/// The argument containing the value to restore.
|
|
/// </param>
|
|
/// <param name="parameter">
|
|
/// The metadata for the parameter to restore.
|
|
/// </param>
|
|
/// <returns>
|
|
/// True if the parameter was restored correctly, or false otherwise.
|
|
/// </returns>
|
|
private bool RestoreParameter(CommandParameterInternal argumentToBind, MergedCompiledCommandParameter parameter)
|
|
{
|
|
switch (parameter.BinderAssociation)
|
|
{
|
|
case ParameterBinderAssociation.DeclaredFormalParameters:
|
|
DefaultParameterBinder.BindParameter(argumentToBind.ParameterName, argumentToBind.ArgumentValue, parameter.Parameter);
|
|
break;
|
|
|
|
case ParameterBinderAssociation.CommonParameters:
|
|
CommonParametersBinder.BindParameter(argumentToBind.ParameterName, argumentToBind.ArgumentValue, parameter.Parameter);
|
|
break;
|
|
|
|
case ParameterBinderAssociation.ShouldProcessParameters:
|
|
Diagnostics.Assert(
|
|
_commandMetadata.SupportsShouldProcess,
|
|
"The metadata for the ShouldProcessParameters should only be available if the command supports ShouldProcess");
|
|
|
|
ShouldProcessParametersBinder.BindParameter(argumentToBind.ParameterName, argumentToBind.ArgumentValue, parameter.Parameter);
|
|
break;
|
|
|
|
case ParameterBinderAssociation.PagingParameters:
|
|
Diagnostics.Assert(
|
|
_commandMetadata.SupportsPaging,
|
|
"The metadata for the PagingParameters should only be available if the command supports paging");
|
|
|
|
PagingParametersBinder.BindParameter(argumentToBind.ParameterName, argumentToBind.ArgumentValue, parameter.Parameter);
|
|
break;
|
|
|
|
case ParameterBinderAssociation.TransactionParameters:
|
|
Diagnostics.Assert(
|
|
_commandMetadata.SupportsTransactions,
|
|
"The metadata for the TransactionParameters should only be available if the command supports Transactions");
|
|
|
|
TransactionParametersBinder.BindParameter(argumentToBind.ParameterName, argumentToBind.ArgumentValue, parameter.Parameter);
|
|
break;
|
|
|
|
case ParameterBinderAssociation.DynamicParameters:
|
|
Diagnostics.Assert(
|
|
_commandMetadata.ImplementsDynamicParameters,
|
|
"The metadata for the dynamic parameters should only be available if the command supports IDynamicParameters");
|
|
|
|
if (_dynamicParameterBinder != null)
|
|
{
|
|
_dynamicParameterBinder.BindParameter(argumentToBind.ParameterName, argumentToBind.ArgumentValue, parameter.Parameter);
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Binds the actual arguments to only the formal parameters
|
|
/// for only the parameters in the specified parameter set.
|
|
/// </summary>
|
|
/// <param name="parameterSets">
|
|
/// The parameter set used to bind the arguments.
|
|
/// </param>
|
|
/// <param name="arguments">
|
|
/// The arguments that should be attempted to bind to the parameters of the specified
|
|
/// parameter binder.
|
|
/// </param>
|
|
/// <exception cref="ParameterBindingException">
|
|
/// if multiple parameters are found matching the name.
|
|
/// or
|
|
/// if no match could be found.
|
|
/// or
|
|
/// If argument transformation fails.
|
|
/// or
|
|
/// The argument could not be coerced to the appropriate type for the parameter.
|
|
/// or
|
|
/// The parameter argument transformation, prerequisite, or validation failed.
|
|
/// or
|
|
/// If the binding to the parameter fails.
|
|
/// </exception>
|
|
private Collection<CommandParameterInternal> BindParameters(uint parameterSets, Collection<CommandParameterInternal> arguments)
|
|
{
|
|
Collection<CommandParameterInternal> result = new Collection<CommandParameterInternal>();
|
|
|
|
foreach (CommandParameterInternal argument in arguments)
|
|
{
|
|
if (!argument.ParameterNameSpecified)
|
|
{
|
|
result.Add(argument);
|
|
continue;
|
|
}
|
|
|
|
// We don't want to throw an exception yet because
|
|
// the parameter might be a positional argument or it
|
|
// might match up to a dynamic parameter
|
|
|
|
MergedCompiledCommandParameter parameter =
|
|
BindableParameters.GetMatchingParameter(
|
|
argument.ParameterName,
|
|
false, true,
|
|
new InvocationInfo(this.InvocationInfo.MyCommand, argument.ParameterExtent));
|
|
|
|
// If the parameter is not in the specified parameter set,
|
|
// throw a binding exception
|
|
|
|
if (parameter != null)
|
|
{
|
|
// Now check to make sure it hasn't already been
|
|
// bound by looking in the boundParameters collection
|
|
|
|
if (BoundParameters.ContainsKey(parameter.Parameter.Name))
|
|
{
|
|
ParameterBindingException bindingException =
|
|
new ParameterBindingException(
|
|
ErrorCategory.InvalidArgument,
|
|
this.InvocationInfo,
|
|
GetParameterErrorExtent(argument),
|
|
argument.ParameterName,
|
|
null,
|
|
null,
|
|
ParameterBinderStrings.ParameterAlreadyBound,
|
|
nameof(ParameterBinderStrings.ParameterAlreadyBound));
|
|
|
|
// Multiple values assigned to the same parameter.
|
|
// Not caused by default parameter binding
|
|
throw bindingException;
|
|
}
|
|
|
|
if ((parameter.Parameter.ParameterSetFlags & parameterSets) == 0 &&
|
|
!parameter.Parameter.IsInAllSets)
|
|
{
|
|
string parameterSetName = BindableParameters.GetParameterSetName(parameterSets);
|
|
|
|
ParameterBindingException bindingException =
|
|
new ParameterBindingException(
|
|
ErrorCategory.InvalidArgument,
|
|
this.Command.MyInvocation,
|
|
null,
|
|
argument.ParameterName,
|
|
null,
|
|
null,
|
|
ParameterBinderStrings.ParameterNotInParameterSet,
|
|
"ParameterNotInParameterSet",
|
|
parameterSetName);
|
|
|
|
// Might be caused by default parameter binding
|
|
if (!DefaultParameterBindingInUse)
|
|
{
|
|
throw bindingException;
|
|
}
|
|
else
|
|
{
|
|
ThrowElaboratedBindingException(bindingException);
|
|
}
|
|
}
|
|
|
|
try
|
|
{
|
|
BindParameter(parameterSets, argument, parameter,
|
|
ParameterBindingFlags.ShouldCoerceType | ParameterBindingFlags.DelayBindScriptBlock);
|
|
}
|
|
catch (ParameterBindingException pbex)
|
|
{
|
|
if (!DefaultParameterBindingInUse)
|
|
{
|
|
throw;
|
|
}
|
|
|
|
ThrowElaboratedBindingException(pbex);
|
|
}
|
|
}
|
|
else if (argument.ParameterName.Equals(Parser.VERBATIM_PARAMETERNAME, StringComparison.Ordinal))
|
|
{
|
|
// We sometimes send a magic parameter from a remote machine with the values referenced via
|
|
// a using expression ($using:x). We then access these values via PSBoundParameters, so
|
|
// "bind" them here.
|
|
DefaultParameterBinder.CommandLineParameters.SetImplicitUsingParameters(argument.ArgumentValue);
|
|
}
|
|
else
|
|
{
|
|
result.Add(argument);
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Determines if a ScriptBlock can be bound directly to the type of the specified parameter.
|
|
/// </summary>
|
|
/// <param name="parameter">
|
|
/// The metadata of the parameter to check the type of.
|
|
/// </param>
|
|
/// <returns>
|
|
/// true if the parameter type is Object, ScriptBlock, derived from ScriptBlock, a
|
|
/// collection of ScriptBlocks, a collection of Objects, or a collection of types derived from
|
|
/// ScriptBlock.
|
|
/// False otherwise.
|
|
/// </returns>
|
|
private static bool IsParameterScriptBlockBindable(MergedCompiledCommandParameter parameter)
|
|
{
|
|
bool result = false;
|
|
|
|
Type parameterType = parameter.Parameter.Type;
|
|
|
|
do // false loop
|
|
{
|
|
if (parameterType == typeof(object))
|
|
{
|
|
result = true;
|
|
break;
|
|
}
|
|
|
|
if (parameterType == typeof(ScriptBlock))
|
|
{
|
|
result = true;
|
|
break;
|
|
}
|
|
|
|
if (parameterType.IsSubclassOf(typeof(ScriptBlock)))
|
|
{
|
|
result = true;
|
|
break;
|
|
}
|
|
|
|
ParameterCollectionTypeInformation parameterCollectionTypeInfo = parameter.Parameter.CollectionTypeInformation;
|
|
if (parameterCollectionTypeInfo.ParameterCollectionType != ParameterCollectionType.NotCollection)
|
|
{
|
|
if (parameterCollectionTypeInfo.ElementType == typeof(object))
|
|
{
|
|
result = true;
|
|
break;
|
|
}
|
|
|
|
if (parameterCollectionTypeInfo.ElementType == typeof(ScriptBlock))
|
|
{
|
|
result = true;
|
|
break;
|
|
}
|
|
|
|
if (parameterCollectionTypeInfo.ElementType.IsSubclassOf(typeof(ScriptBlock)))
|
|
{
|
|
result = true;
|
|
break;
|
|
}
|
|
}
|
|
} while (false);
|
|
|
|
s_tracer.WriteLine("IsParameterScriptBlockBindable: result = {0}", result);
|
|
return result;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Binds the specified parameters to the cmdlet.
|
|
/// </summary>
|
|
/// <param name="parameters">
|
|
/// The parameters to bind.
|
|
/// </param>
|
|
internal override Collection<CommandParameterInternal> BindParameters(Collection<CommandParameterInternal> parameters)
|
|
{
|
|
return BindParameters(uint.MaxValue, parameters);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Binds the specified argument to the specified parameter using the appropriate
|
|
/// parameter binder. If the argument is of type ScriptBlock and the parameter takes
|
|
/// pipeline input, then the ScriptBlock is saved off in the delay-bind ScriptBlock
|
|
/// container for further processing of pipeline input and is not bound as the argument
|
|
/// to the parameter.
|
|
/// </summary>
|
|
/// <param name="parameterSets">
|
|
/// The parameter set used to bind the arguments.
|
|
/// </param>
|
|
/// <param name="argument">
|
|
/// The argument to be bound.
|
|
/// </param>
|
|
/// <param name="parameter">
|
|
/// The metadata for the parameter to bind the argument to.
|
|
/// </param>
|
|
/// <param name="flags">
|
|
/// Flags for type coercion, validation, and script block binding.
|
|
///
|
|
/// ParameterBindingFlags.DelayBindScriptBlock:
|
|
/// If set, arguments that are of type ScriptBlock where the parameter is not of type ScriptBlock,
|
|
/// Object, or PSObject will be stored for execution during pipeline input and not bound as
|
|
/// an argument to the parameter.
|
|
/// </param>
|
|
/// <returns>
|
|
/// True if the parameter was successfully bound. False if <paramref name="flags"/>
|
|
/// has the flag <see cref="ParameterBindingFlags.ShouldCoerceType"/> set and the type does not match the parameter type.
|
|
/// </returns>
|
|
internal override bool BindParameter(
|
|
uint parameterSets,
|
|
CommandParameterInternal argument,
|
|
MergedCompiledCommandParameter parameter,
|
|
ParameterBindingFlags flags)
|
|
{
|
|
// Now we need to check to see if the argument value is
|
|
// a ScriptBlock. If it is and the parameter type is
|
|
// not ScriptBlock and not Object, then we need to delay
|
|
// binding until a pipeline object is provided to invoke
|
|
// the ScriptBlock.
|
|
|
|
// Note: we haven't yet determined that only a single parameter
|
|
// set is valid, so we have to take a best guess on pipeline input
|
|
// based on the current valid parameter sets.
|
|
|
|
bool continueWithBinding = true;
|
|
|
|
if ((flags & ParameterBindingFlags.DelayBindScriptBlock) != 0 &&
|
|
parameter.Parameter.DoesParameterSetTakePipelineInput(parameterSets) &&
|
|
argument.ArgumentSpecified)
|
|
{
|
|
object argumentValue = argument.ArgumentValue;
|
|
if ((argumentValue is ScriptBlock || argumentValue is DelayedScriptBlockArgument) &&
|
|
!IsParameterScriptBlockBindable(parameter))
|
|
{
|
|
// Now check to see if the command expects to have pipeline input.
|
|
// If not, we should throw an exception now to inform the
|
|
// user with more information than they would get if it was
|
|
// considered an unbound mandatory parameter.
|
|
|
|
if (_commandRuntime.IsClosed && _commandRuntime.InputPipe.Empty)
|
|
{
|
|
ParameterBindingException bindingException =
|
|
new ParameterBindingException(
|
|
ErrorCategory.MetadataError,
|
|
this.Command.MyInvocation,
|
|
GetErrorExtent(argument),
|
|
parameter.Parameter.Name,
|
|
parameter.Parameter.Type,
|
|
null,
|
|
ParameterBinderStrings.ScriptBlockArgumentNoInput,
|
|
"ScriptBlockArgumentNoInput");
|
|
|
|
throw bindingException;
|
|
}
|
|
|
|
ParameterBinderBase.bindingTracer.WriteLine(
|
|
"Adding ScriptBlock to delay-bind list for parameter '{0}'",
|
|
parameter.Parameter.Name);
|
|
|
|
// We need to delay binding of this argument to the parameter
|
|
|
|
DelayedScriptBlockArgument delayedArg = argumentValue as DelayedScriptBlockArgument ??
|
|
new DelayedScriptBlockArgument { _argument = argument, _parameterBinder = this };
|
|
if (!_delayBindScriptBlocks.ContainsKey(parameter))
|
|
{
|
|
_delayBindScriptBlocks.Add(parameter, delayedArg);
|
|
}
|
|
|
|
// We treat the parameter as bound, but really the
|
|
// script block gets run for each pipeline object and
|
|
// the result is bound.
|
|
|
|
if (parameter.Parameter.ParameterSetFlags != 0)
|
|
{
|
|
_currentParameterSetFlag &= parameter.Parameter.ParameterSetFlags;
|
|
}
|
|
|
|
UnboundParameters.Remove(parameter);
|
|
|
|
BoundParameters[parameter.Parameter.Name] = parameter;
|
|
BoundArguments[parameter.Parameter.Name] = argument;
|
|
|
|
if (DefaultParameterBinder.RecordBoundParameters &&
|
|
!DefaultParameterBinder.CommandLineParameters.ContainsKey(parameter.Parameter.Name))
|
|
{
|
|
DefaultParameterBinder.CommandLineParameters.Add(parameter.Parameter.Name, delayedArg);
|
|
}
|
|
|
|
continueWithBinding = false;
|
|
}
|
|
}
|
|
|
|
bool result = false;
|
|
if (continueWithBinding)
|
|
{
|
|
try
|
|
{
|
|
result = BindParameter(argument, parameter, flags);
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
bool rethrow = true;
|
|
if ((flags & ParameterBindingFlags.ShouldCoerceType) == 0)
|
|
{
|
|
// Attributes are used to do type coercion and result in various exceptions.
|
|
// We assume that if we aren't trying to do type coercion, we should avoid
|
|
// propagating type conversion exceptions.
|
|
while (e != null)
|
|
{
|
|
if (e is PSInvalidCastException)
|
|
{
|
|
rethrow = false;
|
|
break;
|
|
}
|
|
|
|
e = e.InnerException;
|
|
}
|
|
}
|
|
|
|
if (rethrow)
|
|
{
|
|
throw;
|
|
}
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Binds the specified argument to the specified parameter using the appropriate
|
|
/// parameter binder.
|
|
/// </summary>
|
|
/// <param name="argument">
|
|
/// The argument to be bound.
|
|
/// </param>
|
|
/// <param name="parameter">
|
|
/// The metadata for the parameter to bind the argument to.
|
|
/// </param>
|
|
/// <param name="flags">
|
|
/// Flags for type coercion and validation.
|
|
/// </param>
|
|
/// <returns>
|
|
/// True if the parameter was successfully bound. False if <paramref name="flags"/>
|
|
/// has the flag <see cref="ParameterBindingFlags.ShouldCoerceType"/> set and the type does not match the parameter type.
|
|
/// </returns>
|
|
private bool BindParameter(
|
|
CommandParameterInternal argument,
|
|
MergedCompiledCommandParameter parameter,
|
|
ParameterBindingFlags flags)
|
|
{
|
|
bool result = false;
|
|
|
|
switch (parameter.BinderAssociation)
|
|
{
|
|
case ParameterBinderAssociation.DeclaredFormalParameters:
|
|
result =
|
|
DefaultParameterBinder.BindParameter(
|
|
argument,
|
|
parameter.Parameter,
|
|
flags);
|
|
break;
|
|
|
|
case ParameterBinderAssociation.CommonParameters:
|
|
result =
|
|
CommonParametersBinder.BindParameter(
|
|
argument,
|
|
parameter.Parameter,
|
|
flags);
|
|
break;
|
|
|
|
case ParameterBinderAssociation.ShouldProcessParameters:
|
|
Diagnostics.Assert(
|
|
_commandMetadata.SupportsShouldProcess,
|
|
"The metadata for the ShouldProcessParameters should only be available if the command supports ShouldProcess");
|
|
|
|
result =
|
|
ShouldProcessParametersBinder.BindParameter(
|
|
argument,
|
|
parameter.Parameter,
|
|
flags);
|
|
break;
|
|
|
|
case ParameterBinderAssociation.PagingParameters:
|
|
Diagnostics.Assert(
|
|
_commandMetadata.SupportsPaging,
|
|
"The metadata for the PagingParameters should only be available if the command supports paging");
|
|
|
|
result =
|
|
PagingParametersBinder.BindParameter(
|
|
argument,
|
|
parameter.Parameter,
|
|
flags);
|
|
break;
|
|
|
|
case ParameterBinderAssociation.TransactionParameters:
|
|
Diagnostics.Assert(
|
|
_commandMetadata.SupportsTransactions,
|
|
"The metadata for the TransactionsParameters should only be available if the command supports transactions");
|
|
|
|
result =
|
|
TransactionParametersBinder.BindParameter(
|
|
argument,
|
|
parameter.Parameter,
|
|
flags);
|
|
break;
|
|
|
|
case ParameterBinderAssociation.DynamicParameters:
|
|
Diagnostics.Assert(
|
|
_commandMetadata.ImplementsDynamicParameters,
|
|
"The metadata for the dynamic parameters should only be available if the command supports IDynamicParameters");
|
|
|
|
if (_dynamicParameterBinder != null)
|
|
{
|
|
result =
|
|
_dynamicParameterBinder.BindParameter(
|
|
argument,
|
|
parameter.Parameter,
|
|
flags);
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
if (result && ((flags & ParameterBindingFlags.IsDefaultValue) == 0))
|
|
{
|
|
// Update the current valid parameter set flags
|
|
|
|
if (parameter.Parameter.ParameterSetFlags != 0)
|
|
{
|
|
_currentParameterSetFlag &= parameter.Parameter.ParameterSetFlags;
|
|
}
|
|
|
|
UnboundParameters.Remove(parameter);
|
|
|
|
if (!BoundParameters.ContainsKey(parameter.Parameter.Name))
|
|
{
|
|
BoundParameters.Add(parameter.Parameter.Name, parameter);
|
|
}
|
|
|
|
if (!BoundArguments.ContainsKey(parameter.Parameter.Name))
|
|
{
|
|
BoundArguments.Add(parameter.Parameter.Name, argument);
|
|
}
|
|
|
|
if (parameter.Parameter.ObsoleteAttribute != null &&
|
|
(flags & ParameterBindingFlags.IsDefaultValue) == 0 &&
|
|
!BoundObsoleteParameterNames.Contains(parameter.Parameter.Name))
|
|
{
|
|
string obsoleteWarning = string.Format(
|
|
CultureInfo.InvariantCulture,
|
|
ParameterBinderStrings.UseOfDeprecatedParameterWarning,
|
|
parameter.Parameter.Name,
|
|
parameter.Parameter.ObsoleteAttribute.Message);
|
|
var warningRecord = new WarningRecord(ParameterBinderBase.FQIDParameterObsolete, obsoleteWarning);
|
|
|
|
BoundObsoleteParameterNames.Add(parameter.Parameter.Name);
|
|
|
|
if (ObsoleteParameterWarningList is null)
|
|
ObsoleteParameterWarningList = new List<WarningRecord>();
|
|
|
|
ObsoleteParameterWarningList.Add(warningRecord);
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Binds the remaining arguments to an unbound ValueFromRemainingArguments parameter (Varargs)
|
|
/// </summary>
|
|
/// <exception cref="ParameterBindingException">
|
|
/// If there was an error binding the arguments to the parameters.
|
|
/// </exception>
|
|
private void HandleRemainingArguments()
|
|
{
|
|
if (UnboundArguments.Count > 0)
|
|
{
|
|
// Find the parameters that take the remaining args, if there are more
|
|
// than one and the parameter set has not been defined, this is an error
|
|
|
|
MergedCompiledCommandParameter varargsParameter = null;
|
|
|
|
foreach (MergedCompiledCommandParameter parameter in UnboundParameters)
|
|
{
|
|
ParameterSetSpecificMetadata parameterSetData = parameter.Parameter.GetParameterSetData(_currentParameterSetFlag);
|
|
|
|
if (parameterSetData is null)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
// If the parameter takes the remaining arguments, bind them.
|
|
|
|
if (parameterSetData.ValueFromRemainingArguments)
|
|
{
|
|
if (varargsParameter != null)
|
|
{
|
|
ParameterBindingException bindingException =
|
|
new ParameterBindingException(
|
|
ErrorCategory.MetadataError,
|
|
this.Command.MyInvocation,
|
|
null,
|
|
parameter.Parameter.Name,
|
|
parameter.Parameter.Type,
|
|
null,
|
|
ParameterBinderStrings.AmbiguousParameterSet,
|
|
"AmbiguousParameterSet");
|
|
|
|
// Might be caused by the default parameter binding
|
|
if (!DefaultParameterBindingInUse)
|
|
{
|
|
throw bindingException;
|
|
}
|
|
else
|
|
{
|
|
ThrowElaboratedBindingException(bindingException);
|
|
}
|
|
}
|
|
|
|
varargsParameter = parameter;
|
|
}
|
|
}
|
|
|
|
if (varargsParameter != null)
|
|
{
|
|
using (ParameterBinderBase.bindingTracer.TraceScope(
|
|
"BIND REMAININGARGUMENTS cmd line args to param: [{0}]",
|
|
varargsParameter.Parameter.Name))
|
|
{
|
|
// Accumulate the unbound arguments in to an list and then bind it to the parameter
|
|
|
|
List<object> valueFromRemainingArguments = new List<object>();
|
|
|
|
foreach (CommandParameterInternal argument in UnboundArguments)
|
|
{
|
|
if (argument.ParameterNameSpecified)
|
|
{
|
|
Diagnostics.Assert(!string.IsNullOrEmpty(argument.ParameterText), "Don't add a null argument");
|
|
valueFromRemainingArguments.Add(argument.ParameterText);
|
|
}
|
|
|
|
if (argument.ArgumentSpecified)
|
|
{
|
|
object argumentValue = argument.ArgumentValue;
|
|
if (argumentValue != AutomationNull.Value && argumentValue != UnboundParameter.Value)
|
|
{
|
|
valueFromRemainingArguments.Add(argumentValue);
|
|
}
|
|
}
|
|
}
|
|
|
|
// If there are multiple arguments, it's not clear how best to represent the extent as the extent
|
|
// may be disjoint, as in 'echo a -verbose b', we have 'a' and 'b' in UnboundArguments.
|
|
var argumentAst = UnboundArguments.Count == 1 ? UnboundArguments[0].ArgumentAst : null;
|
|
var cpi = CommandParameterInternal.CreateParameterWithArgument(
|
|
/*parameterAst*/null, varargsParameter.Parameter.Name, "-" + varargsParameter.Parameter.Name + ":",
|
|
argumentAst, valueFromRemainingArguments, false);
|
|
|
|
// To make all of the following work similarly (the first is handled elsewhere, but second and third are
|
|
// handled here):
|
|
// Set-ClusterOwnerNode -Owners foo,bar
|
|
// Set-ClusterOwnerNode foo bar
|
|
// Set-ClusterOwnerNode foo,bar
|
|
// we unwrap our List, but only if there is a single argument which is a collection.
|
|
if (valueFromRemainingArguments.Count == 1 && LanguagePrimitives.IsObjectEnumerable(valueFromRemainingArguments[0]))
|
|
{
|
|
cpi.SetArgumentValue(UnboundArguments[0].ArgumentAst, valueFromRemainingArguments[0]);
|
|
}
|
|
|
|
try
|
|
{
|
|
BindParameter(cpi, varargsParameter, ParameterBindingFlags.ShouldCoerceType);
|
|
}
|
|
catch (ParameterBindingException pbex)
|
|
{
|
|
if (!DefaultParameterBindingInUse)
|
|
{
|
|
throw;
|
|
}
|
|
else
|
|
{
|
|
ThrowElaboratedBindingException(pbex);
|
|
}
|
|
}
|
|
|
|
UnboundArguments.Clear();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Determines if the cmdlet supports dynamic parameters. If it does,
|
|
/// the dynamic parameter bindable object is retrieved and the unbound
|
|
/// arguments are bound to it.
|
|
/// </summary>
|
|
/// <param name="outgoingBindingException">
|
|
/// Returns the underlying parameter binding exception if any was generated.
|
|
/// </param>
|
|
/// <exception cref="MetadataException">
|
|
/// If there was an error compiling the parameter metadata.
|
|
/// </exception>
|
|
/// <exception cref="ParameterBindingException">
|
|
/// If there was an error binding the arguments to the parameters.
|
|
/// </exception>
|
|
private void HandleCommandLineDynamicParameters(out ParameterBindingException outgoingBindingException)
|
|
{
|
|
outgoingBindingException = null;
|
|
|
|
if (_commandMetadata.ImplementsDynamicParameters)
|
|
{
|
|
using (ParameterBinderBase.bindingTracer.TraceScope(
|
|
"BIND cmd line args to DYNAMIC parameters."))
|
|
{
|
|
s_tracer.WriteLine("The Cmdlet supports the dynamic parameter interface");
|
|
|
|
IDynamicParameters dynamicParameterCmdlet = this.Command as IDynamicParameters;
|
|
|
|
if (dynamicParameterCmdlet != null)
|
|
{
|
|
if (_dynamicParameterBinder is null)
|
|
{
|
|
s_tracer.WriteLine("Getting the bindable object from the Cmdlet");
|
|
|
|
// Now get the dynamic parameter bindable object.
|
|
object dynamicParamBindableObject;
|
|
|
|
try
|
|
{
|
|
dynamicParamBindableObject = dynamicParameterCmdlet.GetDynamicParameters();
|
|
}
|
|
catch (Exception e) // Catch-all OK, this is a third-party callout
|
|
{
|
|
if (e is ProviderInvocationException) { throw; }
|
|
|
|
ParameterBindingException bindingException =
|
|
new ParameterBindingException(
|
|
e,
|
|
ErrorCategory.InvalidArgument,
|
|
this.Command.MyInvocation,
|
|
null,
|
|
null,
|
|
null,
|
|
null,
|
|
ParameterBinderStrings.GetDynamicParametersException,
|
|
"GetDynamicParametersException",
|
|
e.Message);
|
|
|
|
// This exception is caused because failure happens when retrieving the dynamic parameters,
|
|
// this is not caused by introducing the default parameter binding.
|
|
throw bindingException;
|
|
}
|
|
|
|
if (dynamicParamBindableObject != null)
|
|
{
|
|
ParameterBinderBase.bindingTracer.WriteLine(
|
|
"DYNAMIC parameter object: [{0}]",
|
|
dynamicParamBindableObject.GetType());
|
|
|
|
s_tracer.WriteLine("Creating a new parameter binder for the dynamic parameter object");
|
|
|
|
InternalParameterMetadata dynamicParameterMetadata;
|
|
|
|
RuntimeDefinedParameterDictionary runtimeParamDictionary = dynamicParamBindableObject as RuntimeDefinedParameterDictionary;
|
|
if (runtimeParamDictionary != null)
|
|
{
|
|
// Generate the type metadata for the runtime-defined parameters
|
|
dynamicParameterMetadata =
|
|
InternalParameterMetadata.Get(runtimeParamDictionary, true, true);
|
|
|
|
_dynamicParameterBinder =
|
|
new RuntimeDefinedParameterBinder(
|
|
runtimeParamDictionary,
|
|
this.Command,
|
|
this.CommandLineParameters);
|
|
}
|
|
else
|
|
{
|
|
// Generate the type metadata or retrieve it from the cache
|
|
dynamicParameterMetadata =
|
|
InternalParameterMetadata.Get(dynamicParamBindableObject.GetType(), Context, true);
|
|
|
|
// Create the parameter binder for the dynamic parameter object
|
|
|
|
_dynamicParameterBinder =
|
|
new ReflectionParameterBinder(
|
|
dynamicParamBindableObject,
|
|
this.Command,
|
|
this.CommandLineParameters);
|
|
}
|
|
|
|
// Now merge the metadata with other metadata for the command
|
|
|
|
var dynamicParams =
|
|
BindableParameters.AddMetadataForBinder(
|
|
dynamicParameterMetadata,
|
|
ParameterBinderAssociation.DynamicParameters);
|
|
foreach (var param in dynamicParams)
|
|
{
|
|
UnboundParameters.Add(param);
|
|
}
|
|
|
|
// Now set the parameter set flags for the new type metadata.
|
|
_commandMetadata.DefaultParameterSetFlag =
|
|
this.BindableParameters.GenerateParameterSetMappingFromMetadata(_commandMetadata.DefaultParameterSetName);
|
|
}
|
|
}
|
|
|
|
if (_dynamicParameterBinder is null)
|
|
{
|
|
s_tracer.WriteLine("No dynamic parameter object was returned from the Cmdlet");
|
|
return;
|
|
}
|
|
|
|
if (UnboundArguments.Count > 0)
|
|
{
|
|
using (ParameterBinderBase.bindingTracer.TraceScope(
|
|
"BIND NAMED args to DYNAMIC parameters"))
|
|
{
|
|
// Try to bind the unbound arguments as static parameters to the
|
|
// dynamic parameter object.
|
|
|
|
ReparseUnboundArguments();
|
|
|
|
UnboundArguments = BindParameters(_currentParameterSetFlag, UnboundArguments);
|
|
}
|
|
|
|
using (ParameterBinderBase.bindingTracer.TraceScope(
|
|
"BIND POSITIONAL args to DYNAMIC parameters"))
|
|
{
|
|
UnboundArguments =
|
|
BindPositionalParameters(
|
|
UnboundArguments,
|
|
_currentParameterSetFlag,
|
|
_commandMetadata.DefaultParameterSetFlag,
|
|
out outgoingBindingException);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// This method determines if the unbound mandatory parameters take pipeline input or
|
|
/// if we can use the default parameter set. If all the unbound mandatory parameters
|
|
/// take pipeline input and the default parameter set is valid, then the default parameter
|
|
/// set is set as the current parameter set and processing can continue. If there are
|
|
/// more than one valid parameter sets and the unbound mandatory parameters are not
|
|
/// consistent across parameter sets or there is no default parameter set then a
|
|
/// ParameterBindingException is thrown with an errorId of AmbiguousParameterSet.
|
|
/// </summary>
|
|
/// <param name="validParameterSetCount">
|
|
/// The number of valid parameter sets.
|
|
/// </param>
|
|
/// <param name="isPipelineInputExpected">
|
|
/// True if the pipeline is open to receive input.
|
|
/// </param>
|
|
/// <exception cref="ParameterBindingException">
|
|
/// If there are multiple valid parameter sets and the missing mandatory parameters are
|
|
/// not consistent across parameter sets, or there is no default parameter set.
|
|
/// </exception>
|
|
[SuppressMessage("Microsoft.Maintainability", "CA1505:AvoidUnmaintainableCode", Justification = "Consider Simplifying it.")]
|
|
private Collection<MergedCompiledCommandParameter> GetMissingMandatoryParameters(
|
|
int validParameterSetCount,
|
|
bool isPipelineInputExpected)
|
|
{
|
|
Collection<MergedCompiledCommandParameter> result = new Collection<MergedCompiledCommandParameter>();
|
|
|
|
uint defaultParameterSet = _commandMetadata.DefaultParameterSetFlag;
|
|
uint commandMandatorySets = 0;
|
|
|
|
Dictionary<uint, ParameterSetPromptingData> promptingData = new Dictionary<uint, ParameterSetPromptingData>();
|
|
|
|
bool missingAMandatoryParameter = false;
|
|
bool missingAMandatoryParameterInAllSet = false;
|
|
|
|
// See if any of the unbound parameters are mandatory
|
|
|
|
foreach (MergedCompiledCommandParameter parameter in UnboundParameters)
|
|
{
|
|
// If a parameter is never mandatory, we can skip lots of work here.
|
|
if (!parameter.Parameter.IsMandatoryInSomeParameterSet)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
var matchingParameterSetMetadata = parameter.Parameter.GetMatchingParameterSetData(_currentParameterSetFlag);
|
|
|
|
uint parameterMandatorySets = 0;
|
|
bool thisParameterMissing = false;
|
|
|
|
foreach (ParameterSetSpecificMetadata parameterSetMetadata in matchingParameterSetMetadata)
|
|
{
|
|
uint newMandatoryParameterSetFlag = NewParameterSetPromptingData(promptingData, parameter, parameterSetMetadata, defaultParameterSet, isPipelineInputExpected);
|
|
|
|
if (newMandatoryParameterSetFlag != 0)
|
|
{
|
|
missingAMandatoryParameter = true;
|
|
thisParameterMissing = true;
|
|
|
|
if (newMandatoryParameterSetFlag != uint.MaxValue)
|
|
{
|
|
parameterMandatorySets |= (_currentParameterSetFlag & newMandatoryParameterSetFlag);
|
|
commandMandatorySets |= (_currentParameterSetFlag & parameterMandatorySets);
|
|
}
|
|
else
|
|
{
|
|
missingAMandatoryParameterInAllSet = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
// We are not expecting pipeline input
|
|
if (!isPipelineInputExpected)
|
|
{
|
|
// The parameter is mandatory so we need to prompt for it
|
|
if (thisParameterMissing)
|
|
{
|
|
result.Add(parameter);
|
|
continue;
|
|
}
|
|
// The parameter was not mandatory in any parameter set
|
|
}
|
|
}
|
|
|
|
if (missingAMandatoryParameter && isPipelineInputExpected)
|
|
{
|
|
if (commandMandatorySets == 0)
|
|
{
|
|
commandMandatorySets = _currentParameterSetFlag;
|
|
}
|
|
|
|
if (missingAMandatoryParameterInAllSet)
|
|
{
|
|
uint availableParameterSetFlags = this.BindableParameters.AllParameterSetFlags;
|
|
if (availableParameterSetFlags == 0)
|
|
{
|
|
availableParameterSetFlags = uint.MaxValue;
|
|
}
|
|
|
|
commandMandatorySets = (_currentParameterSetFlag & availableParameterSetFlags);
|
|
}
|
|
|
|
// First we need to see if there are multiple valid parameter sets, and if one is
|
|
// the default parameter set, and it is not missing any mandatory parameters, then
|
|
// use the default parameter set.
|
|
|
|
if (validParameterSetCount > 1 &&
|
|
defaultParameterSet != 0 &&
|
|
(defaultParameterSet & commandMandatorySets) == 0 &&
|
|
(defaultParameterSet & _currentParameterSetFlag) != 0)
|
|
{
|
|
// If no other set takes pipeline input, then latch on to the default set
|
|
|
|
uint setThatTakesPipelineInput = 0;
|
|
foreach (ParameterSetPromptingData promptingSetData in promptingData.Values)
|
|
{
|
|
if ((promptingSetData.ParameterSet & _currentParameterSetFlag) != 0 &&
|
|
(promptingSetData.ParameterSet & defaultParameterSet) == 0 &&
|
|
!promptingSetData.IsAllSet)
|
|
{
|
|
if (promptingSetData.PipelineableMandatoryParameters.Count > 0)
|
|
{
|
|
setThatTakesPipelineInput = promptingSetData.ParameterSet;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (setThatTakesPipelineInput == 0)
|
|
{
|
|
// Old algorithm starts
|
|
// // latch on to the default parameter set
|
|
// commandMandatorySets = defaultParameterSet;
|
|
// _currentParameterSetFlag = defaultParameterSet;
|
|
// Command.SetParameterSetName(CurrentParameterSetName);
|
|
// Old algorithm ends
|
|
|
|
// At this point, we have the following information:
|
|
// 1. There are unbound mandatory parameter(s)
|
|
// 2. No unbound mandatory parameter is in AllSet
|
|
// 3. All unbound mandatory parameters don't take pipeline input
|
|
// 4. Default parameter set is valid
|
|
// 5. Default parameter set doesn't contain unbound mandatory parameters
|
|
//
|
|
// We ignore those parameter sets that contain unbound mandatory parameters, but leave
|
|
// all other parameter sets remain valid. The other parameter sets contains the default
|
|
// parameter set and have one characteristic: NONE of them contain unbound mandatory parameters
|
|
//
|
|
// Comparing to the old algorithm, we keep more possible parameter sets here, but
|
|
// we need to prioritize the default parameter set for pipeline binding, so as NOT to
|
|
// make breaking changes. This is to handle the following scenario:
|
|
// Old Algorithm New Algorithm (without prioritizing default) New Algorithm (with prioritizing default)
|
|
// Remaining Parameter Sets A(default) A(default), B A(default), B
|
|
// Pipeline parameter P1(string) A: P1(string); B: P2(System.DateTime) A: P1(string); B: P2(System.DateTime)
|
|
// Pipeline parameter type P1:By Value P1:By Value; P2:By Value P1:By Value; P2:By Value
|
|
// Pipeline input $a (System.DateTime) $a (System.DateTime) $a (System.DateTime)
|
|
// Pipeline binding result P1 --> $a.ToString() P2 --> $a P1 --> $a.ToString()
|
|
// Pipeline binding type ByValueWithCoercion ByValueWithoutCoercion ByValueWithCoercion
|
|
|
|
commandMandatorySets = _currentParameterSetFlag & (~commandMandatorySets);
|
|
_currentParameterSetFlag = commandMandatorySets;
|
|
|
|
if (_currentParameterSetFlag == defaultParameterSet)
|
|
Command.SetParameterSetName(CurrentParameterSetName);
|
|
else
|
|
_parameterSetToBePrioritizedInPipelineBinding = defaultParameterSet;
|
|
}
|
|
}
|
|
// We need to analyze the prompting data that was gathered to determine what parameter
|
|
// set to use, which parameters need prompting for, and which parameters take pipeline input.
|
|
|
|
int commandMandatorySetsCount = ValidParameterSetCount(commandMandatorySets);
|
|
if (commandMandatorySetsCount == 0)
|
|
{
|
|
ThrowAmbiguousParameterSetException(_currentParameterSetFlag, BindableParameters);
|
|
}
|
|
else if (commandMandatorySetsCount == 1)
|
|
{
|
|
// Since we have only one valid parameter set, add all
|
|
foreach (ParameterSetPromptingData promptingSetData in promptingData.Values)
|
|
{
|
|
if ((promptingSetData.ParameterSet & commandMandatorySets) != 0 ||
|
|
promptingSetData.IsAllSet)
|
|
{
|
|
foreach (MergedCompiledCommandParameter mandatoryParameter in promptingSetData.NonpipelineableMandatoryParameters.Keys)
|
|
{
|
|
result.Add(mandatoryParameter);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else if (_parameterSetToBePrioritizedInPipelineBinding == 0)
|
|
{
|
|
// We have more than one valid parameter set. Need to figure out which one to
|
|
// use.
|
|
// First we need to process the default parameter set if it can fill its parameters
|
|
// from the pipeline.
|
|
|
|
bool latchOnToDefault = false;
|
|
if (defaultParameterSet != 0 && (commandMandatorySets & defaultParameterSet) != 0)
|
|
{
|
|
// Determine if another set could be satisfied by pipeline input - that is, it
|
|
// has mandatory pipeline input parameters but no mandatory command-line only parameters.
|
|
bool anotherSetTakesPipelineInput = false;
|
|
foreach (ParameterSetPromptingData paramPromptingData in promptingData.Values)
|
|
{
|
|
if (!paramPromptingData.IsAllSet &&
|
|
!paramPromptingData.IsDefaultSet &&
|
|
paramPromptingData.PipelineableMandatoryParameters.Count > 0 &&
|
|
paramPromptingData.NonpipelineableMandatoryParameters.Count == 0)
|
|
{
|
|
anotherSetTakesPipelineInput = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Determine if another set takes pipeline input by property name
|
|
bool anotherSetTakesPipelineInputByPropertyName = false;
|
|
foreach (ParameterSetPromptingData paramPromptingData in promptingData.Values)
|
|
{
|
|
if (!paramPromptingData.IsAllSet &&
|
|
!paramPromptingData.IsDefaultSet &&
|
|
paramPromptingData.PipelineableMandatoryByPropertyNameParameters.Count > 0)
|
|
{
|
|
anotherSetTakesPipelineInputByPropertyName = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// See if we should pick the default set if it can bind strongly to the incoming objects
|
|
ParameterSetPromptingData defaultSetPromptingData;
|
|
if (promptingData.TryGetValue(defaultParameterSet, out defaultSetPromptingData))
|
|
{
|
|
bool defaultSetTakesPipelineInput = defaultSetPromptingData.PipelineableMandatoryParameters.Count > 0;
|
|
bool defaultSetTakesPipelineInputByPropertyName = defaultSetPromptingData.PipelineableMandatoryByPropertyNameParameters.Count > 0;
|
|
|
|
if (defaultSetTakesPipelineInputByPropertyName && !anotherSetTakesPipelineInputByPropertyName)
|
|
{
|
|
latchOnToDefault = true;
|
|
}
|
|
else if (defaultSetTakesPipelineInput && !anotherSetTakesPipelineInput)
|
|
{
|
|
latchOnToDefault = true;
|
|
}
|
|
}
|
|
|
|
if (!latchOnToDefault)
|
|
{
|
|
// If only the all set takes pipeline input then latch on to the
|
|
// default set
|
|
|
|
if (!anotherSetTakesPipelineInput)
|
|
{
|
|
latchOnToDefault = true;
|
|
}
|
|
}
|
|
|
|
if (!latchOnToDefault)
|
|
{
|
|
// Need to see if there are nonpipelineable mandatory parameters in the
|
|
// all set.
|
|
|
|
ParameterSetPromptingData allSetPromptingData;
|
|
if (promptingData.TryGetValue(uint.MaxValue, out allSetPromptingData))
|
|
{
|
|
if (allSetPromptingData.NonpipelineableMandatoryParameters.Count > 0)
|
|
{
|
|
latchOnToDefault = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (latchOnToDefault)
|
|
{
|
|
// latch on to the default parameter set
|
|
commandMandatorySets = defaultParameterSet;
|
|
_currentParameterSetFlag = defaultParameterSet;
|
|
Command.SetParameterSetName(CurrentParameterSetName);
|
|
|
|
// Add all missing mandatory parameters that don't take pipeline input
|
|
foreach (ParameterSetPromptingData promptingSetData in promptingData.Values)
|
|
{
|
|
if ((promptingSetData.ParameterSet & commandMandatorySets) != 0 ||
|
|
promptingSetData.IsAllSet)
|
|
{
|
|
foreach (MergedCompiledCommandParameter mandatoryParameter in promptingSetData.NonpipelineableMandatoryParameters.Keys)
|
|
{
|
|
result.Add(mandatoryParameter);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!latchOnToDefault)
|
|
{
|
|
// When we select a mandatory set to latch on, we should try to preserve other parameter sets that contain no mandatory parameters or contain only common mandatory parameters
|
|
// as much as possible, so as to support the binding for the following scenarios:
|
|
//
|
|
// (1) Scenario 1:
|
|
// Valid parameter sets when it comes to the mandatory checking: A, B
|
|
// Mandatory parameters in A, B:
|
|
// Set Nonpipelineable-Mandatory-InSet Pipelineable-Mandatory-InSet Common-Nonpipelineable-Mandatory Common-Pipelineable-Mandatory
|
|
// A N/A N/A N/A AllParam (of type DateTime)
|
|
// B N/A ParamB (of type TimeSpan) N/A AllParam (of type DateTime)
|
|
//
|
|
// Piped-in object: Get-Date
|
|
//
|
|
// (2) Scenario 2:
|
|
// Valid parameter sets when it comes to the mandatory checking: A, B, C, Default
|
|
// Mandatory parameters in A, B, C and Default:
|
|
// Set Nonpipelineable-Mandatory-InSet Pipelineable-Mandatory-InSet Common-Nonpipelineable-Mandatory Common-Pipelineable-Mandatory
|
|
// A N/A N/A N/A AllParam (of type DateTime)
|
|
// B N/A ParamB (of type TimeSpan) N/A AllParam (of type DateTime)
|
|
// C N/A N/A N/A AllParam (of type DateTime)
|
|
// Default N/A N/A N/A AllParam (of type DateTime)
|
|
//
|
|
// Piped-in object: Get-Date
|
|
//
|
|
// Before the fix, the mandatory checking will resolve the parameter set to be B in both scenario 1 and 2, which will fail in the subsequent pipeline binding.
|
|
// After the fix, the parameter set "A" in the scenario 1 and the set "A", "C", "Default" in the scenario 2 will be preserved, and the subsequent pipeline binding will succeed.
|
|
//
|
|
// (3) Scenario 3:
|
|
// Valid parameter sets when it comes to the mandatory checking: A, B, C
|
|
// Mandatory parameters in A, B and C:
|
|
// Set Nonpipelineable-Mandatory-InSet Pipelineable-Mandatory-InSet Pipelineable-Nonmandatory-InSet Common-Nonpipelineable-Mandatory Common-Pipelineable-Mandatory Common-Pipelineable-Nonmandatory
|
|
// A N/A ParamA (of type TimeSpan) N/A N/A N/A N/A
|
|
// B ParamB-1 N/A ParamB-2 (of type string[]) N/A N/A N/A
|
|
// C N/A N/A ParamC (of type DateTime) N/A N/A N/A
|
|
//
|
|
// (4) Scenario 4:
|
|
// Valid parameter sets when it comes to the mandatory checking: A, B, C, Default
|
|
// Mandatory parameters in A, B, C and Default:
|
|
// Set Nonpipelineable-Mandatory-InSet Pipelineable-Mandatory-InSet Pipelineable-Nonmandatory-InSet Common-Nonpipelineable-Mandatory Common-Pipelineable-Mandatory Common-Pipelineable-Nonmandatory
|
|
// A N/A ParamA (of type TimeSpan) N/A N/A N/A AllParam (of type DateTime)
|
|
// B ParamB-1 N/A ParamB-2 (of type string[]) N/A N/A AllParam (of type DateTime)
|
|
// C N/A N/A N/A N/A N/A AllParam (of type DateTime)
|
|
// Default N/A N/A N/A N/A N/A AllParam (of type DateTime)
|
|
//
|
|
// Piped-in object: Get-Date
|
|
//
|
|
// Before the fix, the mandatory checking will resolve the parameter set to be A in both scenario 3 and 4, which will fail in the subsequent pipeline binding.
|
|
// After the fix, the parameter set "C" in the scenario 1 and the set "C" and "Default" in the scenario 2 will be preserved, and the subsequent pipeline binding will succeed.
|
|
//
|
|
// Examples:
|
|
// (1) Scenario 1
|
|
// Function Get-Cmdlet
|
|
// {
|
|
// [CmdletBinding()]
|
|
// param(
|
|
// [Parameter(Mandatory=$true, ValueFromPipeline=$true)]
|
|
// [System.DateTime]
|
|
// $Date,
|
|
// [Parameter(ParameterSetName="computer")]
|
|
// [Parameter(ParameterSetName="session")]
|
|
// $ComputerName,
|
|
// [Parameter(ParameterSetName="session", Mandatory=$true, ValueFromPipeline=$true)]
|
|
// [System.TimeSpan]
|
|
// $TimeSpan
|
|
// )
|
|
//
|
|
// Process
|
|
// {
|
|
// Write-Output $PsCmdlet.ParameterSetName
|
|
// }
|
|
// }
|
|
//
|
|
// PS:\> Get-Date | Get-Cmdlet
|
|
// PS:\> computer
|
|
//
|
|
// (2) Scenario 2
|
|
//
|
|
// Function Get-Cmdlet
|
|
// {
|
|
// [CmdletBinding(DefaultParameterSetName="computer")]
|
|
// param(
|
|
// [Parameter(ParameterSetName="new")]
|
|
// $NewName,
|
|
// [Parameter(Mandatory=$true, ValueFromPipeline=$true)]
|
|
// [System.DateTime]
|
|
// $Date,
|
|
// [Parameter(ParameterSetName="computer")]
|
|
// [Parameter(ParameterSetName="session")]
|
|
// $ComputerName,
|
|
// [Parameter(ParameterSetName="session", Mandatory=$true, ValueFromPipeline=$true)]
|
|
// [System.TimeSpan]
|
|
// $TimeSpan
|
|
// )
|
|
//
|
|
// Process
|
|
// {
|
|
// Write-Output $PsCmdlet.ParameterSetName
|
|
// }
|
|
// }
|
|
//
|
|
// PS:\> Get-Date | Get-Cmdlet
|
|
// PS:\> computer
|
|
//
|
|
// (3) Scenario 3
|
|
//
|
|
// Function Get-Cmdlet
|
|
// {
|
|
// [CmdletBinding()]
|
|
// param(
|
|
// [Parameter(ParameterSetName="network", Mandatory=$true, ValueFromPipeline=$true)]
|
|
// [TimeSpan]
|
|
// $network,
|
|
//
|
|
// [Parameter(ParameterSetName="computer", ValueFromPipelineByPropertyName=$true)]
|
|
// [string[]]
|
|
// $ComputerName,
|
|
//
|
|
// [Parameter(ParameterSetName="computer", Mandatory=$true)]
|
|
// [switch]
|
|
// $DisableComputer,
|
|
//
|
|
// [Parameter(ParameterSetName="session", ValueFromPipeline=$true)]
|
|
// [DateTime]
|
|
// $Date
|
|
// )
|
|
//
|
|
// Process
|
|
// {
|
|
// Write-Output $PsCmdlet.ParameterSetName
|
|
// }
|
|
// }
|
|
//
|
|
// PS:\> Get-Date | Get-Cmdlet
|
|
// PS:\> session
|
|
//
|
|
// (4) Scenario 4
|
|
//
|
|
// Function Get-Cmdlet
|
|
// {
|
|
// [CmdletBinding(DefaultParameterSetName="server")]
|
|
// param(
|
|
// [Parameter(ParameterSetName="network", Mandatory=$true, ValueFromPipeline=$true)]
|
|
// [TimeSpan]
|
|
// $network,
|
|
//
|
|
// [Parameter(ParameterSetName="computer", ValueFromPipelineByPropertyName=$true)]
|
|
// [string[]]
|
|
// $ComputerName,
|
|
//
|
|
// [Parameter(ParameterSetName="computer", Mandatory=$true)]
|
|
// [switch]
|
|
// $DisableComputer,
|
|
//
|
|
// [Parameter(ParameterSetName="session")]
|
|
// [Parameter(ParameterSetName="server")]
|
|
// [string]
|
|
// $Param,
|
|
//
|
|
// [Parameter(ValueFromPipeline=$true)]
|
|
// [DateTime]
|
|
// $Date
|
|
// )
|
|
|
|
// Process
|
|
// {
|
|
// Write-Output $PsCmdlet.ParameterSetName
|
|
// }
|
|
// }
|
|
//
|
|
// PS:\> Get-Date | Get-Cmdlet
|
|
// PS:\> server
|
|
//
|
|
|
|
uint setThatTakesPipelineInputByValue = 0;
|
|
uint setThatTakesPipelineInputByPropertyName = 0;
|
|
|
|
// Find the single set that takes pipeline input by value
|
|
bool foundSetThatTakesPipelineInputByValue = false;
|
|
bool foundMultipleSetsThatTakesPipelineInputByValue = false;
|
|
foreach (ParameterSetPromptingData promptingSetData in promptingData.Values)
|
|
{
|
|
if ((promptingSetData.ParameterSet & commandMandatorySets) != 0 &&
|
|
!promptingSetData.IsAllSet)
|
|
{
|
|
if (promptingSetData.PipelineableMandatoryByValueParameters.Count > 0)
|
|
{
|
|
if (foundSetThatTakesPipelineInputByValue)
|
|
{
|
|
foundMultipleSetsThatTakesPipelineInputByValue = true;
|
|
setThatTakesPipelineInputByValue = 0;
|
|
break;
|
|
}
|
|
|
|
setThatTakesPipelineInputByValue = promptingSetData.ParameterSet;
|
|
foundSetThatTakesPipelineInputByValue = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Find the single set that takes pipeline input by property name
|
|
bool foundSetThatTakesPipelineInputByPropertyName = false;
|
|
bool foundMultipleSetsThatTakesPipelineInputByPropertyName = false;
|
|
foreach (ParameterSetPromptingData promptingSetData in promptingData.Values)
|
|
{
|
|
if ((promptingSetData.ParameterSet & commandMandatorySets) != 0 &&
|
|
!promptingSetData.IsAllSet)
|
|
{
|
|
if (promptingSetData.PipelineableMandatoryByPropertyNameParameters.Count > 0)
|
|
{
|
|
if (foundSetThatTakesPipelineInputByPropertyName)
|
|
{
|
|
foundMultipleSetsThatTakesPipelineInputByPropertyName = true;
|
|
setThatTakesPipelineInputByPropertyName = 0;
|
|
break;
|
|
}
|
|
|
|
setThatTakesPipelineInputByPropertyName = promptingSetData.ParameterSet;
|
|
foundSetThatTakesPipelineInputByPropertyName = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
// If we have one or the other, we can latch onto that set without difficulty
|
|
uint uniqueSetThatTakesPipelineInput = 0;
|
|
if ((foundSetThatTakesPipelineInputByValue & foundSetThatTakesPipelineInputByPropertyName) &&
|
|
(setThatTakesPipelineInputByValue == setThatTakesPipelineInputByPropertyName))
|
|
{
|
|
uniqueSetThatTakesPipelineInput = setThatTakesPipelineInputByValue;
|
|
}
|
|
|
|
if (foundSetThatTakesPipelineInputByValue ^ foundSetThatTakesPipelineInputByPropertyName)
|
|
{
|
|
uniqueSetThatTakesPipelineInput = foundSetThatTakesPipelineInputByValue ?
|
|
setThatTakesPipelineInputByValue : setThatTakesPipelineInputByPropertyName;
|
|
}
|
|
|
|
if (uniqueSetThatTakesPipelineInput != 0)
|
|
{
|
|
// latch on to the set that takes pipeline input
|
|
commandMandatorySets = uniqueSetThatTakesPipelineInput;
|
|
uint otherMandatorySetsToBeIgnored = 0;
|
|
bool chosenMandatorySetContainsNonpipelineableMandatoryParameters = false;
|
|
|
|
// Add all missing mandatory parameters that don't take pipeline input
|
|
foreach (ParameterSetPromptingData promptingSetData in promptingData.Values)
|
|
{
|
|
if ((promptingSetData.ParameterSet & commandMandatorySets) != 0 ||
|
|
promptingSetData.IsAllSet)
|
|
{
|
|
if (!promptingSetData.IsAllSet)
|
|
{
|
|
chosenMandatorySetContainsNonpipelineableMandatoryParameters =
|
|
promptingSetData.NonpipelineableMandatoryParameters.Count > 0;
|
|
}
|
|
|
|
foreach (MergedCompiledCommandParameter mandatoryParameter in promptingSetData.NonpipelineableMandatoryParameters.Keys)
|
|
{
|
|
result.Add(mandatoryParameter);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
otherMandatorySetsToBeIgnored |= promptingSetData.ParameterSet;
|
|
}
|
|
}
|
|
|
|
// Preserve potential parameter sets as much as possible
|
|
PreservePotentialParameterSets(uniqueSetThatTakesPipelineInput,
|
|
otherMandatorySetsToBeIgnored,
|
|
chosenMandatorySetContainsNonpipelineableMandatoryParameters);
|
|
}
|
|
else
|
|
{
|
|
// Now if any valid parameter sets have nonpipelineable mandatory parameters we have
|
|
// an error
|
|
bool foundMissingParameters = false;
|
|
uint setsThatContainNonpipelineableMandatoryParameter = 0;
|
|
foreach (ParameterSetPromptingData promptingSetData in promptingData.Values)
|
|
{
|
|
if ((promptingSetData.ParameterSet & commandMandatorySets) != 0 ||
|
|
promptingSetData.IsAllSet)
|
|
{
|
|
if (promptingSetData.NonpipelineableMandatoryParameters.Count > 0)
|
|
{
|
|
foundMissingParameters = true;
|
|
if (!promptingSetData.IsAllSet)
|
|
{
|
|
setsThatContainNonpipelineableMandatoryParameter |= promptingSetData.ParameterSet;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (foundMissingParameters)
|
|
{
|
|
// As a last-ditch effort, bind to the set that takes pipeline input by value
|
|
if (setThatTakesPipelineInputByValue != 0)
|
|
{
|
|
// latch on to the set that takes pipeline input
|
|
commandMandatorySets = setThatTakesPipelineInputByValue;
|
|
uint otherMandatorySetsToBeIgnored = 0;
|
|
bool chosenMandatorySetContainsNonpipelineableMandatoryParameters = false;
|
|
|
|
// Add all missing mandatory parameters that don't take pipeline input
|
|
foreach (ParameterSetPromptingData promptingSetData in promptingData.Values)
|
|
{
|
|
if ((promptingSetData.ParameterSet & commandMandatorySets) != 0 ||
|
|
promptingSetData.IsAllSet)
|
|
{
|
|
if (!promptingSetData.IsAllSet)
|
|
{
|
|
chosenMandatorySetContainsNonpipelineableMandatoryParameters =
|
|
promptingSetData.NonpipelineableMandatoryParameters.Count > 0;
|
|
}
|
|
|
|
foreach (MergedCompiledCommandParameter mandatoryParameter in promptingSetData.NonpipelineableMandatoryParameters.Keys)
|
|
{
|
|
result.Add(mandatoryParameter);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
otherMandatorySetsToBeIgnored |= promptingSetData.ParameterSet;
|
|
}
|
|
}
|
|
|
|
// Preserve potential parameter sets as much as possible
|
|
PreservePotentialParameterSets(setThatTakesPipelineInputByValue,
|
|
otherMandatorySetsToBeIgnored,
|
|
chosenMandatorySetContainsNonpipelineableMandatoryParameters);
|
|
}
|
|
else
|
|
{
|
|
if ((!foundMultipleSetsThatTakesPipelineInputByValue) &&
|
|
(!foundMultipleSetsThatTakesPipelineInputByPropertyName))
|
|
{
|
|
ThrowAmbiguousParameterSetException(_currentParameterSetFlag, BindableParameters);
|
|
}
|
|
|
|
// Remove the data set that contains non-pipelineable mandatory parameters, since we are not
|
|
// prompting for them and they will not be bound later.
|
|
// If no data set left, throw ambiguous parameter set exception
|
|
if (setsThatContainNonpipelineableMandatoryParameter != 0)
|
|
{
|
|
IgnoreOtherMandatoryParameterSets(setsThatContainNonpipelineableMandatoryParameter);
|
|
if (_currentParameterSetFlag == 0)
|
|
{
|
|
ThrowAmbiguousParameterSetException(_currentParameterSetFlag, BindableParameters);
|
|
}
|
|
|
|
if (ValidParameterSetCount(_currentParameterSetFlag) == 1)
|
|
{
|
|
Command.SetParameterSetName(CurrentParameterSetName);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Preserve potential parameter sets as much as possible.
|
|
/// </summary>
|
|
/// <param name="chosenMandatorySet">The mandatory set we choose to latch on.</param>
|
|
/// <param name="otherMandatorySetsToBeIgnored">Other mandatory parameter sets to be ignored.</param>
|
|
/// <param name="chosenSetContainsNonpipelineableMandatoryParameters">Indicate if the chosen mandatory set contains any non-pipelineable mandatory parameters.</param>
|
|
private void PreservePotentialParameterSets(uint chosenMandatorySet, uint otherMandatorySetsToBeIgnored, bool chosenSetContainsNonpipelineableMandatoryParameters)
|
|
{
|
|
// If the chosen set contains nonpipelineable mandatory parameters, then we set it as the only valid parameter set since we will prompt for those mandatory parameters
|
|
if (chosenSetContainsNonpipelineableMandatoryParameters)
|
|
{
|
|
_currentParameterSetFlag = chosenMandatorySet;
|
|
Command.SetParameterSetName(CurrentParameterSetName);
|
|
}
|
|
else
|
|
{
|
|
// Otherwise, we additionally preserve those valid parameter sets that contain no mandatory parameter, or contain only the common mandatory parameters
|
|
IgnoreOtherMandatoryParameterSets(otherMandatorySetsToBeIgnored);
|
|
Command.SetParameterSetName(CurrentParameterSetName);
|
|
|
|
if (_currentParameterSetFlag != chosenMandatorySet)
|
|
{
|
|
_parameterSetToBePrioritizedInPipelineBinding = chosenMandatorySet;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Update _currentParameterSetFlag to ignore the specified mandatory sets.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// This method is used only when we try to preserve parameter sets during the mandatory parameter checking.
|
|
/// In cases where this method is used, there must be at least one parameter set declared.
|
|
/// </remarks>
|
|
/// <param name="otherMandatorySetsToBeIgnored">The mandatory parameter sets to be ignored.</param>
|
|
private void IgnoreOtherMandatoryParameterSets(uint otherMandatorySetsToBeIgnored)
|
|
{
|
|
if (otherMandatorySetsToBeIgnored == 0)
|
|
return;
|
|
|
|
if (_currentParameterSetFlag == uint.MaxValue)
|
|
{
|
|
// We cannot update the _currentParameterSetFlag to remove some parameter sets directly when it's AllSet as that will get it to an incorrect state.
|
|
uint availableParameterSets = this.BindableParameters.AllParameterSetFlags;
|
|
Diagnostics.Assert(availableParameterSets != 0, "At least one parameter set must be declared");
|
|
_currentParameterSetFlag = availableParameterSets & (~otherMandatorySetsToBeIgnored);
|
|
}
|
|
else
|
|
{
|
|
_currentParameterSetFlag &= (~otherMandatorySetsToBeIgnored);
|
|
}
|
|
}
|
|
|
|
private uint NewParameterSetPromptingData(
|
|
Dictionary<uint, ParameterSetPromptingData> promptingData,
|
|
MergedCompiledCommandParameter parameter,
|
|
ParameterSetSpecificMetadata parameterSetMetadata,
|
|
uint defaultParameterSet,
|
|
bool pipelineInputExpected)
|
|
{
|
|
uint parameterMandatorySets = 0;
|
|
uint parameterSetFlag = parameterSetMetadata.ParameterSetFlag;
|
|
if (parameterSetFlag == 0)
|
|
{
|
|
parameterSetFlag = uint.MaxValue;
|
|
}
|
|
|
|
bool isDefaultSet = (defaultParameterSet != 0) && ((defaultParameterSet & parameterSetFlag) != 0);
|
|
|
|
bool isMandatory = false;
|
|
if (parameterSetMetadata.IsMandatory)
|
|
{
|
|
parameterMandatorySets |= parameterSetFlag;
|
|
isMandatory = true;
|
|
}
|
|
|
|
bool isPipelineable = false;
|
|
if (pipelineInputExpected)
|
|
{
|
|
if (parameterSetMetadata.ValueFromPipeline || parameterSetMetadata.ValueFromPipelineByPropertyName)
|
|
{
|
|
isPipelineable = true;
|
|
}
|
|
}
|
|
|
|
if (isMandatory)
|
|
{
|
|
ParameterSetPromptingData promptingDataForSet;
|
|
if (!promptingData.TryGetValue(parameterSetFlag, out promptingDataForSet))
|
|
{
|
|
promptingDataForSet = new ParameterSetPromptingData(parameterSetFlag, isDefaultSet);
|
|
promptingData.Add(parameterSetFlag, promptingDataForSet);
|
|
}
|
|
|
|
if (isPipelineable)
|
|
{
|
|
promptingDataForSet.PipelineableMandatoryParameters[parameter] = parameterSetMetadata;
|
|
|
|
if (parameterSetMetadata.ValueFromPipeline)
|
|
{
|
|
promptingDataForSet.PipelineableMandatoryByValueParameters[parameter] = parameterSetMetadata;
|
|
}
|
|
|
|
if (parameterSetMetadata.ValueFromPipelineByPropertyName)
|
|
{
|
|
promptingDataForSet.PipelineableMandatoryByPropertyNameParameters[parameter] = parameterSetMetadata;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
promptingDataForSet.NonpipelineableMandatoryParameters[parameter] = parameterSetMetadata;
|
|
}
|
|
}
|
|
|
|
return parameterMandatorySets;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Ensures that only one parameter set is valid or throws an appropriate exception.
|
|
/// </summary>
|
|
/// <param name="prePipelineInput">
|
|
/// If true, it is acceptable to have multiple valid parameter sets as long as one
|
|
/// of those parameter sets take pipeline input.
|
|
/// </param>
|
|
/// <param name="setDefault">
|
|
/// If true, the default parameter set will be selected if there is more than
|
|
/// one valid parameter set and one is the default set.
|
|
/// If false, the count of valid parameter sets will be returned but no error
|
|
/// will occur and the default parameter set will not be used.
|
|
/// </param>
|
|
/// <returns>
|
|
/// The number of valid parameter sets.
|
|
/// </returns>
|
|
/// <exception cref="ParameterBindingException">
|
|
/// If the more than one or zero parameter sets were resolved from the named
|
|
/// parameters.
|
|
/// </exception>
|
|
private int ValidateParameterSets(bool prePipelineInput, bool setDefault)
|
|
{
|
|
// Compute how many parameter sets are still valid
|
|
int validParameterSetCount = ValidParameterSetCount(_currentParameterSetFlag);
|
|
|
|
if (validParameterSetCount == 0 && _currentParameterSetFlag != uint.MaxValue)
|
|
{
|
|
ThrowAmbiguousParameterSetException(_currentParameterSetFlag, BindableParameters);
|
|
}
|
|
else if (validParameterSetCount > 1)
|
|
{
|
|
uint defaultParameterSetFlag = _commandMetadata.DefaultParameterSetFlag;
|
|
bool hasDefaultSetDefined = defaultParameterSetFlag != 0;
|
|
|
|
bool validSetIsAllSet = _currentParameterSetFlag == uint.MaxValue;
|
|
bool validSetIsDefault = _currentParameterSetFlag == defaultParameterSetFlag;
|
|
|
|
// If no default parameter set is defined and the valid set is the "all" set
|
|
// then use the all set.
|
|
|
|
if (validSetIsAllSet && !hasDefaultSetDefined)
|
|
{
|
|
// The current parameter set flags are valid.
|
|
// Note: this is the same as having a single valid parameter set flag.
|
|
validParameterSetCount = 1;
|
|
}
|
|
// If the valid parameter set is the default parameter set, or if the default
|
|
// parameter set has been defined and one of the valid parameter sets is
|
|
// the default parameter set, then use the default parameter set.
|
|
else if (!prePipelineInput &&
|
|
validSetIsDefault ||
|
|
(hasDefaultSetDefined && (_currentParameterSetFlag & defaultParameterSetFlag) != 0))
|
|
{
|
|
// NTRAID#Windows Out Of Band Releases-2006/02/14-928660-JonN
|
|
// Set currentParameterSetName regardless of setDefault
|
|
string currentParameterSetName = BindableParameters.GetParameterSetName(defaultParameterSetFlag);
|
|
Command.SetParameterSetName(currentParameterSetName);
|
|
if (setDefault)
|
|
{
|
|
_currentParameterSetFlag = _commandMetadata.DefaultParameterSetFlag;
|
|
validParameterSetCount = 1;
|
|
}
|
|
}
|
|
// There are multiple valid parameter sets but at least one parameter set takes
|
|
// pipeline input
|
|
else if (prePipelineInput &&
|
|
AtLeastOneUnboundValidParameterSetTakesPipelineInput(_currentParameterSetFlag))
|
|
{
|
|
// We haven't fixated on a valid parameter set yet, but will wait for pipeline input to
|
|
// determine which parameter set to use.
|
|
}
|
|
else
|
|
{
|
|
int resolvedParameterSetCount = ResolveParameterSetAmbiguityBasedOnMandatoryParameters();
|
|
if (resolvedParameterSetCount != 1)
|
|
{
|
|
ThrowAmbiguousParameterSetException(_currentParameterSetFlag, BindableParameters);
|
|
}
|
|
|
|
validParameterSetCount = resolvedParameterSetCount;
|
|
}
|
|
}
|
|
else // validParameterSetCount == 1
|
|
{
|
|
// If the valid parameter set is the "all" set, and a default set was defined,
|
|
// then set the current parameter set to the default set.
|
|
|
|
if (_currentParameterSetFlag == uint.MaxValue)
|
|
{
|
|
// Since this is the "all" set, default the parameter set count to the
|
|
// number of parameter sets that were defined for the cmdlet or 1 if
|
|
// none were defined.
|
|
|
|
validParameterSetCount =
|
|
(this.BindableParameters.ParameterSetCount > 0) ?
|
|
this.BindableParameters.ParameterSetCount : 1;
|
|
|
|
if (prePipelineInput &&
|
|
AtLeastOneUnboundValidParameterSetTakesPipelineInput(_currentParameterSetFlag))
|
|
{
|
|
// Don't fixate on the default parameter set yet. Wait until after
|
|
// we have processed pipeline input.
|
|
}
|
|
else if (_commandMetadata.DefaultParameterSetFlag != 0)
|
|
{
|
|
if (setDefault)
|
|
{
|
|
_currentParameterSetFlag = _commandMetadata.DefaultParameterSetFlag;
|
|
validParameterSetCount = 1;
|
|
}
|
|
}
|
|
// NTRAID#Windows Out Of Band Releases-2005/11/07-923917-JonN
|
|
else if (validParameterSetCount > 1)
|
|
{
|
|
int resolvedParameterSetCount = ResolveParameterSetAmbiguityBasedOnMandatoryParameters();
|
|
if (resolvedParameterSetCount != 1)
|
|
{
|
|
ThrowAmbiguousParameterSetException(_currentParameterSetFlag, BindableParameters);
|
|
}
|
|
|
|
validParameterSetCount = resolvedParameterSetCount;
|
|
}
|
|
}
|
|
|
|
Command.SetParameterSetName(CurrentParameterSetName);
|
|
}
|
|
|
|
return validParameterSetCount;
|
|
}
|
|
|
|
private int ResolveParameterSetAmbiguityBasedOnMandatoryParameters()
|
|
{
|
|
return ResolveParameterSetAmbiguityBasedOnMandatoryParameters(this.BoundParameters, this.UnboundParameters, this.BindableParameters, ref _currentParameterSetFlag, Command);
|
|
}
|
|
|
|
internal static int ResolveParameterSetAmbiguityBasedOnMandatoryParameters(
|
|
Dictionary<string, MergedCompiledCommandParameter> boundParameters,
|
|
ICollection<MergedCompiledCommandParameter> unboundParameters,
|
|
MergedCommandParameterMetadata bindableParameters,
|
|
ref uint _currentParameterSetFlag,
|
|
Cmdlet command
|
|
)
|
|
{
|
|
uint remainingParameterSetsWithNoMandatoryUnboundParameters = _currentParameterSetFlag;
|
|
|
|
IEnumerable<ParameterSetSpecificMetadata> allParameterSetMetadatas = boundParameters.Values
|
|
.Concat(unboundParameters)
|
|
.SelectMany(p => p.Parameter.ParameterSetData.Values);
|
|
uint allParameterSetFlags = 0;
|
|
foreach (ParameterSetSpecificMetadata parameterSetMetadata in allParameterSetMetadatas)
|
|
{
|
|
allParameterSetFlags |= parameterSetMetadata.ParameterSetFlag;
|
|
}
|
|
|
|
remainingParameterSetsWithNoMandatoryUnboundParameters &= allParameterSetFlags;
|
|
|
|
Diagnostics.Assert(
|
|
ValidParameterSetCount(remainingParameterSetsWithNoMandatoryUnboundParameters) > 1,
|
|
"This method should only be called when there is an ambiguity wrt parameter sets");
|
|
|
|
IEnumerable<ParameterSetSpecificMetadata> parameterSetMetadatasForUnboundMandatoryParameters = unboundParameters
|
|
.SelectMany(p => p.Parameter.ParameterSetData.Values)
|
|
.Where(p => p.IsMandatory);
|
|
foreach (ParameterSetSpecificMetadata parameterSetMetadata in parameterSetMetadatasForUnboundMandatoryParameters)
|
|
{
|
|
remainingParameterSetsWithNoMandatoryUnboundParameters &= (~parameterSetMetadata.ParameterSetFlag);
|
|
}
|
|
|
|
int finalParameterSetCount = ValidParameterSetCount(remainingParameterSetsWithNoMandatoryUnboundParameters);
|
|
if (finalParameterSetCount == 1)
|
|
{
|
|
_currentParameterSetFlag = remainingParameterSetsWithNoMandatoryUnboundParameters;
|
|
|
|
if (command != null)
|
|
{
|
|
string currentParameterSetName = bindableParameters.GetParameterSetName(_currentParameterSetFlag);
|
|
command.SetParameterSetName(currentParameterSetName);
|
|
}
|
|
|
|
return finalParameterSetCount;
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
private void ThrowAmbiguousParameterSetException(uint parameterSetFlags, MergedCommandParameterMetadata bindableParameters)
|
|
{
|
|
ParameterBindingException bindingException =
|
|
new ParameterBindingException(
|
|
ErrorCategory.InvalidArgument,
|
|
this.Command.MyInvocation,
|
|
null,
|
|
null,
|
|
null,
|
|
null,
|
|
ParameterBinderStrings.AmbiguousParameterSet,
|
|
"AmbiguousParameterSet");
|
|
|
|
// Trace the parameter sets still active
|
|
uint currentParameterSet = 1;
|
|
|
|
while (parameterSetFlags != 0)
|
|
{
|
|
uint currentParameterSetActive = parameterSetFlags & 0x1;
|
|
|
|
if (currentParameterSetActive == 1)
|
|
{
|
|
string parameterSetName = bindableParameters.GetParameterSetName(currentParameterSet);
|
|
if (!string.IsNullOrEmpty(parameterSetName))
|
|
{
|
|
ParameterBinderBase.bindingTracer.WriteLine("Remaining valid parameter set: {0}", parameterSetName);
|
|
}
|
|
}
|
|
|
|
parameterSetFlags >>= 1;
|
|
currentParameterSet <<= 1;
|
|
}
|
|
|
|
if (!DefaultParameterBindingInUse)
|
|
{
|
|
throw bindingException;
|
|
}
|
|
else
|
|
{
|
|
ThrowElaboratedBindingException(bindingException);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Determines if there are any unbound parameters that take pipeline input
|
|
/// for the specified parameter sets.
|
|
/// </summary>
|
|
/// <param name="validParameterSetFlags">
|
|
/// The parameter sets that should be checked for each unbound parameter to see
|
|
/// if it accepts pipeline input.
|
|
/// </param>
|
|
/// <returns>
|
|
/// True if there is at least one parameter that takes pipeline input for the
|
|
/// specified parameter sets, or false otherwise.
|
|
/// </returns>
|
|
private bool AtLeastOneUnboundValidParameterSetTakesPipelineInput(uint validParameterSetFlags)
|
|
{
|
|
bool result = false;
|
|
|
|
// Loop through all the unbound parameters to see if there are any
|
|
// that take pipeline input for the specified parameter sets.
|
|
|
|
foreach (MergedCompiledCommandParameter parameter in UnboundParameters)
|
|
{
|
|
if (parameter.Parameter.DoesParameterSetTakePipelineInput(validParameterSetFlags))
|
|
{
|
|
result = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Checks for unbound mandatory parameters. If any are found, an exception is thrown.
|
|
/// </summary>
|
|
/// <param name="missingMandatoryParameters">
|
|
/// Returns the missing mandatory parameters, if any.
|
|
/// </param>
|
|
/// <returns>
|
|
/// True if there are no unbound mandatory parameters. False if there are unbound mandatory parameters.
|
|
/// </returns>
|
|
internal bool HandleUnboundMandatoryParameters(out Collection<MergedCompiledCommandParameter> missingMandatoryParameters)
|
|
{
|
|
return HandleUnboundMandatoryParameters(
|
|
ValidParameterSetCount(_currentParameterSetFlag),
|
|
false,
|
|
false,
|
|
false,
|
|
out missingMandatoryParameters);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Checks for unbound mandatory parameters. If any are found and promptForMandatory is true,
|
|
/// the user will be prompted for the missing mandatory parameters.
|
|
/// </summary>
|
|
/// <param name="validParameterSetCount">
|
|
/// The number of valid parameter sets.
|
|
/// </param>
|
|
/// <param name="processMissingMandatory">
|
|
/// If true, unbound mandatory parameters will be processed via user prompting (if allowed by promptForMandatory).
|
|
/// If false, unbound mandatory parameters will cause false to be returned.
|
|
/// </param>
|
|
/// <param name="promptForMandatory">
|
|
/// If true, unbound mandatory parameters will cause the user to be prompted. If false, unbound
|
|
/// mandatory parameters will cause an exception to be thrown.
|
|
/// </param>
|
|
/// <param name="isPipelineInputExpected">
|
|
/// If true, then only parameters that don't take pipeline input will be prompted for.
|
|
/// If false, any mandatory parameter that has not been specified will be prompted for.
|
|
/// </param>
|
|
/// <param name="missingMandatoryParameters">
|
|
/// Returns the missing mandatory parameters, if any.
|
|
/// </param>
|
|
/// <returns>
|
|
/// True if there are no unbound mandatory parameters. False if there are unbound mandatory parameters
|
|
/// and promptForMandatory if false.
|
|
/// </returns>
|
|
/// <exception cref="ParameterBindingException">
|
|
/// If prompting didn't result in a value for the parameter (only when <paramref name="promptForMandatory"/> is true.)
|
|
/// </exception>
|
|
internal bool HandleUnboundMandatoryParameters(
|
|
int validParameterSetCount,
|
|
bool processMissingMandatory,
|
|
bool promptForMandatory,
|
|
bool isPipelineInputExpected,
|
|
out Collection<MergedCompiledCommandParameter> missingMandatoryParameters)
|
|
{
|
|
bool result = true;
|
|
|
|
missingMandatoryParameters = GetMissingMandatoryParameters(validParameterSetCount, isPipelineInputExpected);
|
|
|
|
if (missingMandatoryParameters.Count > 0)
|
|
{
|
|
if (processMissingMandatory)
|
|
{
|
|
// If the host interface wasn't specified or we were instructed not to prmopt, then throw
|
|
// an exception instead
|
|
if ((Context.EngineHostInterface is null) || (!promptForMandatory))
|
|
{
|
|
Diagnostics.Assert(
|
|
Context.EngineHostInterface != null,
|
|
"The EngineHostInterface should never be null");
|
|
|
|
ParameterBinderBase.bindingTracer.WriteLine(
|
|
"ERROR: host does not support prompting for missing mandatory parameters");
|
|
|
|
string missingParameters = BuildMissingParamsString(missingMandatoryParameters);
|
|
|
|
ParameterBindingException bindingException =
|
|
new ParameterBindingException(
|
|
ErrorCategory.InvalidArgument,
|
|
this.Command.MyInvocation,
|
|
null,
|
|
missingParameters,
|
|
null,
|
|
null,
|
|
ParameterBinderStrings.MissingMandatoryParameter,
|
|
"MissingMandatoryParameter");
|
|
|
|
throw bindingException;
|
|
}
|
|
|
|
// Create a collection to store the prompt descriptions of unbound mandatory parameters
|
|
|
|
Collection<FieldDescription> fieldDescriptionList = CreatePromptDataStructures(missingMandatoryParameters);
|
|
|
|
Dictionary<string, PSObject> parameters =
|
|
PromptForMissingMandatoryParameters(
|
|
fieldDescriptionList,
|
|
missingMandatoryParameters);
|
|
|
|
using (ParameterBinderBase.bindingTracer.TraceScope(
|
|
"BIND PROMPTED mandatory parameter args"))
|
|
{
|
|
// Now bind any parameters that were retrieved.
|
|
|
|
foreach (KeyValuePair<string, PSObject> entry in parameters)
|
|
{
|
|
var argument =
|
|
CommandParameterInternal.CreateParameterWithArgument(
|
|
/*parameterAst*/null, entry.Key, "-" + entry.Key + ":",
|
|
/*argumentAst*/null, entry.Value,
|
|
false);
|
|
|
|
// Ignore the result since any failure should cause an exception
|
|
result =
|
|
BindParameter(argument, ParameterBindingFlags.ShouldCoerceType | ParameterBindingFlags.ThrowOnParameterNotFound);
|
|
|
|
Diagnostics.Assert(
|
|
result,
|
|
"Any error in binding the parameter with type coercion should result in an exception");
|
|
}
|
|
|
|
result = true;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
result = false;
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
private Dictionary<string, PSObject> PromptForMissingMandatoryParameters(
|
|
Collection<FieldDescription> fieldDescriptionList,
|
|
Collection<MergedCompiledCommandParameter> missingMandatoryParameters)
|
|
{
|
|
Dictionary<string, PSObject> parameters = null;
|
|
|
|
Exception error = null;
|
|
|
|
// Prompt
|
|
try
|
|
{
|
|
ParameterBinderBase.bindingTracer.WriteLine(
|
|
"PROMPTING for missing mandatory parameters using the host");
|
|
string msg = ParameterBinderStrings.PromptMessage;
|
|
InvocationInfo invoInfo = Command.MyInvocation;
|
|
string caption = StringUtil.Format(ParameterBinderStrings.PromptCaption,
|
|
invoInfo.MyCommand.Name,
|
|
invoInfo.PipelinePosition);
|
|
|
|
parameters = Context.EngineHostInterface.UI.Prompt(caption, msg, fieldDescriptionList);
|
|
}
|
|
catch (NotImplementedException notImplemented)
|
|
{
|
|
error = notImplemented;
|
|
}
|
|
catch (HostException hostException)
|
|
{
|
|
error = hostException;
|
|
}
|
|
catch (PSInvalidOperationException invalidOperation)
|
|
{
|
|
error = invalidOperation;
|
|
}
|
|
|
|
if (error != null)
|
|
{
|
|
ParameterBinderBase.bindingTracer.WriteLine(
|
|
"ERROR: host does not support prompting for missing mandatory parameters");
|
|
|
|
string missingParameters = BuildMissingParamsString(missingMandatoryParameters);
|
|
|
|
ParameterBindingException bindingException =
|
|
new ParameterBindingException(
|
|
ErrorCategory.InvalidArgument,
|
|
this.Command.MyInvocation,
|
|
null,
|
|
missingParameters,
|
|
null,
|
|
null,
|
|
ParameterBinderStrings.MissingMandatoryParameter,
|
|
"MissingMandatoryParameter");
|
|
|
|
throw bindingException;
|
|
}
|
|
|
|
if ((parameters is null) || (parameters.Count == 0))
|
|
{
|
|
ParameterBinderBase.bindingTracer.WriteLine(
|
|
"ERROR: still missing mandatory parameters after PROMPTING");
|
|
|
|
string missingParameters = BuildMissingParamsString(missingMandatoryParameters);
|
|
|
|
ParameterBindingException bindingException =
|
|
new ParameterBindingException(
|
|
ErrorCategory.InvalidArgument,
|
|
this.Command.MyInvocation,
|
|
null,
|
|
missingParameters,
|
|
null,
|
|
null,
|
|
ParameterBinderStrings.MissingMandatoryParameter,
|
|
"MissingMandatoryParameter");
|
|
|
|
throw bindingException;
|
|
}
|
|
|
|
return parameters;
|
|
}
|
|
|
|
internal static string BuildMissingParamsString(Collection<MergedCompiledCommandParameter> missingMandatoryParameters)
|
|
{
|
|
StringBuilder missingParameters = new StringBuilder();
|
|
|
|
foreach (MergedCompiledCommandParameter missingParameter in missingMandatoryParameters)
|
|
{
|
|
missingParameters.AppendFormat(CultureInfo.InvariantCulture, " {0}", missingParameter.Parameter.Name);
|
|
}
|
|
|
|
return missingParameters.ToString();
|
|
}
|
|
|
|
private Collection<FieldDescription> CreatePromptDataStructures(
|
|
Collection<MergedCompiledCommandParameter> missingMandatoryParameters)
|
|
{
|
|
StringBuilder usedHotKeys = new StringBuilder();
|
|
Collection<FieldDescription> fieldDescriptionList = new Collection<FieldDescription>();
|
|
|
|
// See if any of the unbound parameters are mandatory
|
|
|
|
foreach (MergedCompiledCommandParameter parameter in missingMandatoryParameters)
|
|
{
|
|
ParameterSetSpecificMetadata parameterSetMetadata =
|
|
parameter.Parameter.GetParameterSetData(_currentParameterSetFlag);
|
|
|
|
FieldDescription fDesc = new FieldDescription(parameter.Parameter.Name);
|
|
|
|
string helpInfo = null;
|
|
|
|
try
|
|
{
|
|
helpInfo = parameterSetMetadata.GetHelpMessage(Command);
|
|
}
|
|
catch (InvalidOperationException)
|
|
{
|
|
}
|
|
catch (ArgumentException)
|
|
{
|
|
}
|
|
|
|
if (!string.IsNullOrEmpty(helpInfo))
|
|
{
|
|
fDesc.HelpMessage = helpInfo;
|
|
}
|
|
|
|
fDesc.SetParameterType(parameter.Parameter.Type);
|
|
fDesc.Label = BuildLabel(parameter.Parameter.Name, usedHotKeys);
|
|
|
|
foreach (ValidateArgumentsAttribute vaAttr in parameter.Parameter.ValidationAttributes)
|
|
{
|
|
fDesc.Attributes.Add(vaAttr);
|
|
}
|
|
|
|
foreach (ArgumentTransformationAttribute arAttr in parameter.Parameter.ArgumentTransformationAttributes)
|
|
{
|
|
fDesc.Attributes.Add(arAttr);
|
|
}
|
|
|
|
fDesc.IsMandatory = true;
|
|
|
|
fieldDescriptionList.Add(fDesc);
|
|
}
|
|
|
|
return fieldDescriptionList;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates a label with a Hotkey from <paramref name="parameterName"/>. The Hotkey is
|
|
/// <paramref name="parameterName"/>'s first capital character not in <paramref name="usedHotKeys"/>.
|
|
/// If <paramref name="parameterName"/> does not have any capital character, the first lower
|
|
/// case character is used. The Hotkey is preceded by an ampersand in the label.
|
|
/// </summary>
|
|
/// <param name="parameterName">
|
|
/// The parameter name from which the Hotkey is created
|
|
/// </param>
|
|
/// <param name="usedHotKeys">
|
|
/// A list of used HotKeys
|
|
/// </param>
|
|
/// <returns>
|
|
/// A label made from parameterName with a HotKey indicated by an ampersand
|
|
/// </returns>
|
|
private static string BuildLabel(string parameterName, StringBuilder usedHotKeys)
|
|
{
|
|
Diagnostics.Assert(!string.IsNullOrEmpty(parameterName), "parameterName is not set");
|
|
const char hotKeyPrefix = '&';
|
|
bool built = false;
|
|
StringBuilder label = new StringBuilder(parameterName);
|
|
string usedHotKeysStr = usedHotKeys.ToString();
|
|
|
|
for (int i = 0; i < parameterName.Length; i++)
|
|
{
|
|
// try Upper case
|
|
if (char.IsUpper(parameterName[i]) && usedHotKeysStr.Contains(parameterName[i]))
|
|
{
|
|
label.Insert(i, hotKeyPrefix);
|
|
usedHotKeys.Append(parameterName[i]);
|
|
built = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!built)
|
|
{
|
|
// try Lower case
|
|
for (int i = 0; i < parameterName.Length; i++)
|
|
{
|
|
if (char.IsLower(parameterName[i]) && usedHotKeysStr.Contains(parameterName[i]))
|
|
{
|
|
label.Insert(i, hotKeyPrefix);
|
|
usedHotKeys.Append(parameterName[i]);
|
|
built = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!built)
|
|
{
|
|
// try non-letters
|
|
for (int i = 0; i < parameterName.Length; i++)
|
|
{
|
|
if (!char.IsLetter(parameterName[i]) && usedHotKeysStr.Contains(parameterName[i]))
|
|
{
|
|
label.Insert(i, hotKeyPrefix);
|
|
usedHotKeys.Append(parameterName[i]);
|
|
built = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!built)
|
|
{
|
|
// use first char
|
|
label.Insert(0, hotKeyPrefix);
|
|
}
|
|
|
|
return label.ToString();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the parameter set name for the current parameter set.
|
|
/// </summary>
|
|
internal string CurrentParameterSetName
|
|
{
|
|
get
|
|
{
|
|
string currentParameterSetName = BindableParameters.GetParameterSetName(_currentParameterSetFlag);
|
|
s_tracer.WriteLine("CurrentParameterSetName = {0}", currentParameterSetName);
|
|
return currentParameterSetName;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Binds the specified object or its properties to parameters
|
|
/// that accept pipeline input.
|
|
/// </summary>
|
|
/// <param name="inputToOperateOn">
|
|
/// The pipeline object to bind.
|
|
/// </param>
|
|
/// <returns>
|
|
/// True if the pipeline input was bound successfully or there was nothing
|
|
/// to bind, or false if there was an error.
|
|
/// </returns>
|
|
internal bool BindPipelineParameters(PSObject inputToOperateOn)
|
|
{
|
|
bool result;
|
|
|
|
try
|
|
{
|
|
using (ParameterBinderBase.bindingTracer.TraceScope(
|
|
"BIND PIPELINE object to parameters: [{0}]",
|
|
_commandMetadata.Name))
|
|
{
|
|
// First run any of the delay bind ScriptBlocks and bind the
|
|
// result to the appropriate parameter.
|
|
|
|
bool thereWasSomethingToBind;
|
|
bool invokeScriptResult = InvokeAndBindDelayBindScriptBlock(inputToOperateOn, out thereWasSomethingToBind);
|
|
|
|
bool continueBindingAfterScriptBlockProcessing = !thereWasSomethingToBind || invokeScriptResult;
|
|
|
|
bool bindPipelineParametersResult = false;
|
|
|
|
if (continueBindingAfterScriptBlockProcessing)
|
|
{
|
|
// If any of the parameters in the parameter set which are not yet bound
|
|
// accept pipeline input, process the input object and bind to those
|
|
// parameters
|
|
|
|
bindPipelineParametersResult = BindPipelineParametersPrivate(inputToOperateOn);
|
|
}
|
|
|
|
// We are successful at binding the pipeline input if there was a ScriptBlock to
|
|
// run and it ran successfully or if we successfully bound a parameter based on
|
|
// the pipeline input.
|
|
|
|
result = (thereWasSomethingToBind && invokeScriptResult) || bindPipelineParametersResult;
|
|
}
|
|
}
|
|
catch (ParameterBindingException)
|
|
{
|
|
// Reset the default values
|
|
// This prevents the last pipeline object from being bound during EndProcessing
|
|
// if it failed some post binding verification step.
|
|
this.RestoreDefaultParameterValues(ParametersBoundThroughPipelineInput);
|
|
|
|
// Let the parameter binding errors propagate out
|
|
throw;
|
|
}
|
|
|
|
try
|
|
{
|
|
// Now make sure we have latched on to a single parameter set.
|
|
VerifyParameterSetSelected();
|
|
}
|
|
catch (ParameterBindingException)
|
|
{
|
|
// Reset the default values
|
|
// This prevents the last pipeline object from being bound during EndProcessing
|
|
// if it failed some post binding verification step.
|
|
this.RestoreDefaultParameterValues(ParametersBoundThroughPipelineInput);
|
|
|
|
throw;
|
|
}
|
|
|
|
if (!result)
|
|
{
|
|
// Reset the default values
|
|
// This prevents the last pipeline object from being bound during EndProcessing
|
|
// if it failed some post binding verification step.
|
|
this.RestoreDefaultParameterValues(ParametersBoundThroughPipelineInput);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Binds the pipeline parameters using the specified input and parameter set.
|
|
/// </summary>
|
|
/// <param name="inputToOperateOn">
|
|
/// The pipeline input to be bound to the parameters.
|
|
/// </param>
|
|
/// <exception cref="ParameterBindingException">
|
|
/// If argument transformation fails.
|
|
/// or
|
|
/// The argument could not be coerced to the appropriate type for the parameter.
|
|
/// or
|
|
/// The parameter argument transformation, prerequisite, or validation failed.
|
|
/// or
|
|
/// If the binding to the parameter fails.
|
|
/// or
|
|
/// If there is a failure resetting values prior to binding from the pipeline
|
|
/// </exception>
|
|
/// <remarks>
|
|
/// The algorithm for binding the pipeline object is as follows. If any
|
|
/// step is successful true gets returned immediately.
|
|
///
|
|
/// - If parameter supports ValueFromPipeline
|
|
/// - attempt to bind input value without type coercion
|
|
/// - If parameter supports ValueFromPipelineByPropertyName
|
|
/// - attempt to bind the value of the property with the matching name without type coercion
|
|
///
|
|
/// Now see if we have a single valid parameter set and reset the validParameterSets flags as
|
|
/// necessary. If there are still multiple valid parameter sets, then we need to use TypeDistance
|
|
/// to determine which parameters to do type coercion binding on.
|
|
///
|
|
/// - If parameter supports ValueFromPipeline
|
|
/// - attempt to bind input value using type coercion
|
|
/// - If parameter support ValueFromPipelineByPropertyName
|
|
/// - attempt to bind the vlue of the property with the matching name using type coercion
|
|
/// </remarks>
|
|
private bool BindPipelineParametersPrivate(PSObject inputToOperateOn)
|
|
{
|
|
if (ParameterBinderBase.bindingTracer.IsEnabled)
|
|
{
|
|
ConsolidatedString dontuseInternalTypeNames;
|
|
ParameterBinderBase.bindingTracer.WriteLine(
|
|
"PIPELINE object TYPE = [{0}]",
|
|
inputToOperateOn is null || inputToOperateOn == AutomationNull.Value
|
|
? "null"
|
|
: ((dontuseInternalTypeNames = inputToOperateOn.InternalTypeNames).Count > 0 && dontuseInternalTypeNames[0] != null)
|
|
? dontuseInternalTypeNames[0]
|
|
: inputToOperateOn.BaseObject.GetType().FullName);
|
|
|
|
ParameterBinderBase.bindingTracer.WriteLine("RESTORING pipeline parameter's original values");
|
|
}
|
|
|
|
bool result = false;
|
|
|
|
// Reset the default values
|
|
|
|
this.RestoreDefaultParameterValues(ParametersBoundThroughPipelineInput);
|
|
|
|
// Now clear the parameter names from the previous pipeline input
|
|
|
|
ParametersBoundThroughPipelineInput.Clear();
|
|
|
|
// Now restore the parameter set flags
|
|
|
|
_currentParameterSetFlag = _prePipelineProcessingParameterSetFlags;
|
|
uint validParameterSets = _currentParameterSetFlag;
|
|
bool needToPrioritizeOneSpecificParameterSet = _parameterSetToBePrioritizedInPipelineBinding != 0;
|
|
int steps = needToPrioritizeOneSpecificParameterSet ? 2 : 1;
|
|
|
|
if (needToPrioritizeOneSpecificParameterSet)
|
|
{
|
|
// _parameterSetToBePrioritizedInPipelineBinding is set, so we are certain that the specified parameter set must be valid,
|
|
// and it's not the only valid parameter set.
|
|
Diagnostics.Assert((_currentParameterSetFlag & _parameterSetToBePrioritizedInPipelineBinding) != 0, "_parameterSetToBePrioritizedInPipelineBinding should be valid if it's set");
|
|
validParameterSets = _parameterSetToBePrioritizedInPipelineBinding;
|
|
}
|
|
|
|
for (int i = 0; i < steps; i++)
|
|
{
|
|
for (CurrentlyBinding currentlyBinding = CurrentlyBinding.ValueFromPipelineNoCoercion; currentlyBinding <= CurrentlyBinding.ValueFromPipelineByPropertyNameWithCoercion; ++currentlyBinding)
|
|
{
|
|
// The parameterBoundForCurrentlyBindingState will be true as long as there is one parameter gets bound, even if it belongs to AllSet
|
|
bool parameterBoundForCurrentlyBindingState =
|
|
BindUnboundParametersForBindingState(
|
|
inputToOperateOn,
|
|
currentlyBinding,
|
|
validParameterSets);
|
|
|
|
if (parameterBoundForCurrentlyBindingState)
|
|
{
|
|
// Now validate the parameter sets again and update the valid sets.
|
|
// No need to validate the parameter sets and update the valid sets when dealing with the prioritized parameter set,
|
|
// this is because the prioritized parameter set is a single set, and when binding succeeds, _currentParameterSetFlag
|
|
// must be equal to the specific prioritized parameter set.
|
|
if (!needToPrioritizeOneSpecificParameterSet || i == 1)
|
|
{
|
|
ValidateParameterSets(true, true);
|
|
validParameterSets = _currentParameterSetFlag;
|
|
}
|
|
|
|
result = true;
|
|
}
|
|
}
|
|
|
|
// Update the validParameterSets after the binding attempt for the prioritized parameter set
|
|
if (needToPrioritizeOneSpecificParameterSet && i == 0)
|
|
{
|
|
// If the prioritized set can be bound successfully, there is no need to do the second round binding
|
|
if (_currentParameterSetFlag == _parameterSetToBePrioritizedInPipelineBinding) break;
|
|
validParameterSets = _currentParameterSetFlag & (~_parameterSetToBePrioritizedInPipelineBinding);
|
|
}
|
|
}
|
|
|
|
// Now make sure we only have one valid parameter set
|
|
// Note, this will throw if we have more than one.
|
|
|
|
ValidateParameterSets(false, true);
|
|
|
|
if (!DefaultParameterBindingInUse)
|
|
{
|
|
ApplyDefaultParameterBinding("PIPELINE BIND", false);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
private bool BindUnboundParametersForBindingState(
|
|
PSObject inputToOperateOn,
|
|
CurrentlyBinding currentlyBinding,
|
|
uint validParameterSets)
|
|
{
|
|
bool aParameterWasBound = false;
|
|
|
|
// First check to see if the default parameter set has been defined and if it
|
|
// is still valid.
|
|
|
|
uint defaultParameterSetFlag = _commandMetadata.DefaultParameterSetFlag;
|
|
|
|
if (defaultParameterSetFlag != 0 && (validParameterSets & defaultParameterSetFlag) != 0)
|
|
{
|
|
// Since we have a default parameter set and it is still valid, give preference to the
|
|
// parameters in the default set.
|
|
|
|
aParameterWasBound =
|
|
BindUnboundParametersForBindingStateInParameterSet(
|
|
inputToOperateOn,
|
|
currentlyBinding,
|
|
defaultParameterSetFlag);
|
|
|
|
if (!aParameterWasBound)
|
|
{
|
|
validParameterSets &= ~(defaultParameterSetFlag);
|
|
}
|
|
}
|
|
|
|
if (!aParameterWasBound)
|
|
{
|
|
// Since nothing was bound for the default parameter set, try all
|
|
// the other parameter sets that are still valid.
|
|
|
|
aParameterWasBound =
|
|
BindUnboundParametersForBindingStateInParameterSet(
|
|
inputToOperateOn,
|
|
currentlyBinding,
|
|
validParameterSets);
|
|
}
|
|
|
|
s_tracer.WriteLine("aParameterWasBound = {0}", aParameterWasBound);
|
|
return aParameterWasBound;
|
|
}
|
|
|
|
private bool BindUnboundParametersForBindingStateInParameterSet(
|
|
PSObject inputToOperateOn,
|
|
CurrentlyBinding currentlyBinding,
|
|
uint validParameterSets)
|
|
{
|
|
bool aParameterWasBound = false;
|
|
|
|
// For all unbound parameters in the parameter set, see if we can bind
|
|
// from the input object directly from pipeline without type coercion.
|
|
//
|
|
// We loop the unbound parameters in reversed order, so that we can move
|
|
// items from the unboundParameters collection to the boundParameters
|
|
// collection as we process, without the need to make a copy of the
|
|
// unboundParameters collection.
|
|
//
|
|
// We used to make a copy of UnboundParameters and loop from the head of the
|
|
// list. Now we are processing the unbound parameters from the end of the list.
|
|
// This change should NOT be a breaking change. The 'validParameterSets' in
|
|
// this method never changes, so no matter we start from the head or the end of
|
|
// the list, every unbound parameter in the list that takes pipeline input and
|
|
// satisfy the 'validParameterSets' will be bound. If parameters from more than
|
|
// one sets got bound, then "parameter set cannot be resolved" error will be thrown,
|
|
// which is expected.
|
|
|
|
for (int i = UnboundParameters.Count - 1; i >= 0; i--)
|
|
{
|
|
var parameter = UnboundParameters[i];
|
|
|
|
// if the parameter is never a pipeline parameter, don't consider it
|
|
if (!parameter.Parameter.IsPipelineParameterInSomeParameterSet)
|
|
continue;
|
|
|
|
// if the parameter is not in the specified parameter set, don't consider it
|
|
if ((validParameterSets & parameter.Parameter.ParameterSetFlags) == 0 &&
|
|
!parameter.Parameter.IsInAllSets)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
// Get the appropriate parameter set data
|
|
var parameterSetData = parameter.Parameter.GetMatchingParameterSetData(validParameterSets);
|
|
|
|
bool bindResult = false;
|
|
|
|
foreach (ParameterSetSpecificMetadata parameterSetMetadata in parameterSetData)
|
|
{
|
|
// In the first phase we try to bind the value from the pipeline without
|
|
// type coercion
|
|
|
|
if (currentlyBinding == CurrentlyBinding.ValueFromPipelineNoCoercion &&
|
|
parameterSetMetadata.ValueFromPipeline)
|
|
{
|
|
bindResult = BindValueFromPipeline(inputToOperateOn, parameter, ParameterBindingFlags.None);
|
|
}
|
|
// In the next phase we try binding the value from the pipeline by matching
|
|
// the property name
|
|
else if (currentlyBinding == CurrentlyBinding.ValueFromPipelineByPropertyNameNoCoercion &&
|
|
parameterSetMetadata.ValueFromPipelineByPropertyName &&
|
|
inputToOperateOn != null)
|
|
{
|
|
bindResult = BindValueFromPipelineByPropertyName(inputToOperateOn, parameter, ParameterBindingFlags.None);
|
|
}
|
|
// The third step is to attempt to bind the value from the pipeline with
|
|
// type coercion.
|
|
else if (currentlyBinding == CurrentlyBinding.ValueFromPipelineWithCoercion &&
|
|
parameterSetMetadata.ValueFromPipeline)
|
|
{
|
|
bindResult = BindValueFromPipeline(inputToOperateOn, parameter, ParameterBindingFlags.ShouldCoerceType);
|
|
}
|
|
// The final step is to attempt to bind the value from the pipeline by matching
|
|
// the property name
|
|
else if (currentlyBinding == CurrentlyBinding.ValueFromPipelineByPropertyNameWithCoercion &&
|
|
parameterSetMetadata.ValueFromPipelineByPropertyName &&
|
|
inputToOperateOn != null)
|
|
{
|
|
bindResult = BindValueFromPipelineByPropertyName(inputToOperateOn, parameter, ParameterBindingFlags.ShouldCoerceType);
|
|
}
|
|
|
|
if (bindResult)
|
|
{
|
|
aParameterWasBound = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
return aParameterWasBound;
|
|
}
|
|
|
|
private bool BindValueFromPipeline(
|
|
PSObject inputToOperateOn,
|
|
MergedCompiledCommandParameter parameter,
|
|
ParameterBindingFlags flags)
|
|
{
|
|
bool bindResult = false;
|
|
|
|
// Attempt binding the value from the pipeline
|
|
// without type coercion
|
|
|
|
ParameterBinderBase.bindingTracer.WriteLine(
|
|
((flags & ParameterBindingFlags.ShouldCoerceType) != 0) ?
|
|
"Parameter [{0}] PIPELINE INPUT ValueFromPipeline WITH COERCION" :
|
|
"Parameter [{0}] PIPELINE INPUT ValueFromPipeline NO COERCION",
|
|
parameter.Parameter.Name);
|
|
|
|
ParameterBindingException parameterBindingException = null;
|
|
try
|
|
{
|
|
bindResult = BindPipelineParameter(inputToOperateOn, parameter, flags);
|
|
}
|
|
catch (ParameterBindingArgumentTransformationException e)
|
|
{
|
|
PSInvalidCastException invalidCast;
|
|
if (e.InnerException is ArgumentTransformationMetadataException)
|
|
{
|
|
invalidCast = e.InnerException.InnerException as PSInvalidCastException;
|
|
}
|
|
else
|
|
{
|
|
invalidCast = e.InnerException as PSInvalidCastException;
|
|
}
|
|
|
|
if (invalidCast is null)
|
|
{
|
|
parameterBindingException = e;
|
|
}
|
|
// Just ignore and continue;
|
|
bindResult = false;
|
|
}
|
|
catch (ParameterBindingValidationException e)
|
|
{
|
|
parameterBindingException = e;
|
|
}
|
|
catch (ParameterBindingParameterDefaultValueException e)
|
|
{
|
|
parameterBindingException = e;
|
|
}
|
|
catch (ParameterBindingException)
|
|
{
|
|
// Just ignore and continue;
|
|
bindResult = false;
|
|
}
|
|
|
|
if (parameterBindingException != null)
|
|
{
|
|
if (!DefaultParameterBindingInUse)
|
|
{
|
|
throw parameterBindingException;
|
|
}
|
|
else
|
|
{
|
|
ThrowElaboratedBindingException(parameterBindingException);
|
|
}
|
|
}
|
|
|
|
return bindResult;
|
|
}
|
|
|
|
private bool BindValueFromPipelineByPropertyName(
|
|
PSObject inputToOperateOn,
|
|
MergedCompiledCommandParameter parameter,
|
|
ParameterBindingFlags flags)
|
|
{
|
|
bool bindResult = false;
|
|
|
|
ParameterBinderBase.bindingTracer.WriteLine(
|
|
((flags & ParameterBindingFlags.ShouldCoerceType) != 0) ?
|
|
"Parameter [{0}] PIPELINE INPUT ValueFromPipelineByPropertyName WITH COERCION" :
|
|
"Parameter [{0}] PIPELINE INPUT ValueFromPipelineByPropertyName NO COERCION",
|
|
parameter.Parameter.Name);
|
|
|
|
PSMemberInfo member = inputToOperateOn.Properties[parameter.Parameter.Name];
|
|
|
|
if (member is null)
|
|
{
|
|
// Since a member matching the name of the parameter wasn't found,
|
|
// check the aliases.
|
|
|
|
foreach (string alias in parameter.Parameter.Aliases)
|
|
{
|
|
member = inputToOperateOn.Properties[alias];
|
|
|
|
if (member != null)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (member != null)
|
|
{
|
|
ParameterBindingException parameterBindingException = null;
|
|
try
|
|
{
|
|
bindResult =
|
|
BindPipelineParameter(
|
|
member.Value,
|
|
parameter,
|
|
flags);
|
|
}
|
|
catch (ParameterBindingArgumentTransformationException e)
|
|
{
|
|
parameterBindingException = e;
|
|
}
|
|
catch (ParameterBindingValidationException e)
|
|
{
|
|
parameterBindingException = e;
|
|
}
|
|
catch (ParameterBindingParameterDefaultValueException e)
|
|
{
|
|
parameterBindingException = e;
|
|
}
|
|
catch (ParameterBindingException)
|
|
{
|
|
// Just ignore and continue;
|
|
bindResult = false;
|
|
}
|
|
|
|
if (parameterBindingException != null)
|
|
{
|
|
if (!DefaultParameterBindingInUse)
|
|
{
|
|
throw parameterBindingException;
|
|
}
|
|
else
|
|
{
|
|
ThrowElaboratedBindingException(parameterBindingException);
|
|
}
|
|
}
|
|
}
|
|
|
|
return bindResult;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Used for defining the state of the binding state machine.
|
|
/// </summary>
|
|
private enum CurrentlyBinding
|
|
{
|
|
ValueFromPipelineNoCoercion = 0,
|
|
ValueFromPipelineByPropertyNameNoCoercion = 1,
|
|
ValueFromPipelineWithCoercion = 2,
|
|
ValueFromPipelineByPropertyNameWithCoercion = 3
|
|
}
|
|
|
|
/// <summary>
|
|
/// Invokes any delay bind script blocks and binds the resulting value
|
|
/// to the appropriate parameter.
|
|
/// </summary>
|
|
/// <param name="inputToOperateOn">
|
|
/// The input to the script block.
|
|
/// </param>
|
|
/// <param name="thereWasSomethingToBind">
|
|
/// Returns True if there was a ScriptBlock to invoke and bind, or false if there
|
|
/// are no ScriptBlocks to invoke.
|
|
/// </param>
|
|
/// <returns>
|
|
/// True if the binding succeeds, or false otherwise.
|
|
/// </returns>
|
|
/// <exception cref="ArgumentNullException">
|
|
/// if <paramref name="inputToOperateOn"/> is null.
|
|
/// </exception>
|
|
/// <exception cref="ParameterBindingException">
|
|
/// If execution of the script block throws an exception or if it doesn't produce
|
|
/// any output.
|
|
/// </exception>
|
|
private bool InvokeAndBindDelayBindScriptBlock(PSObject inputToOperateOn, out bool thereWasSomethingToBind)
|
|
{
|
|
thereWasSomethingToBind = false;
|
|
bool result = true;
|
|
|
|
// NOTE: we are not doing backup and restore of default parameter
|
|
// values here. It is not needed because each script block will be
|
|
// invoked and each delay bind parameter bound for each pipeline object.
|
|
// This is unlike normal pipeline object processing which may bind
|
|
// different parameters depending on the type of the incoming pipeline
|
|
// object.
|
|
|
|
// Loop through each of the delay bind script blocks and invoke them.
|
|
// Bind the result to the associated parameter
|
|
|
|
foreach (KeyValuePair<MergedCompiledCommandParameter, DelayedScriptBlockArgument> delayedScriptBlock in _delayBindScriptBlocks)
|
|
{
|
|
thereWasSomethingToBind = true;
|
|
|
|
CommandParameterInternal argument = delayedScriptBlock.Value._argument;
|
|
MergedCompiledCommandParameter parameter = delayedScriptBlock.Key;
|
|
|
|
ScriptBlock script = argument.ArgumentValue as ScriptBlock;
|
|
|
|
Diagnostics.Assert(
|
|
script != null,
|
|
"An argument should only be put in the delayBindScriptBlocks collection if it is a ScriptBlock");
|
|
|
|
Collection<PSObject> output = null;
|
|
|
|
Exception error = null;
|
|
using (ParameterBinderBase.bindingTracer.TraceScope(
|
|
"Invoking delay-bind ScriptBlock"))
|
|
{
|
|
if (delayedScriptBlock.Value._parameterBinder == this)
|
|
{
|
|
try
|
|
{
|
|
output = script.DoInvoke(inputToOperateOn, inputToOperateOn, Array.Empty<object>());
|
|
delayedScriptBlock.Value._evaluatedArgument = output;
|
|
}
|
|
catch (RuntimeException runtimeException)
|
|
{
|
|
error = runtimeException;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
output = delayedScriptBlock.Value._evaluatedArgument;
|
|
}
|
|
}
|
|
|
|
if (error != null)
|
|
{
|
|
ParameterBindingException bindingException =
|
|
new ParameterBindingException(
|
|
error,
|
|
ErrorCategory.InvalidArgument,
|
|
this.Command.MyInvocation,
|
|
GetErrorExtent(argument),
|
|
parameter.Parameter.Name,
|
|
null,
|
|
null,
|
|
ParameterBinderStrings.ScriptBlockArgumentInvocationFailed,
|
|
"ScriptBlockArgumentInvocationFailed",
|
|
error.Message);
|
|
|
|
throw bindingException;
|
|
}
|
|
|
|
if (output is null || output.Count == 0)
|
|
{
|
|
ParameterBindingException bindingException =
|
|
new ParameterBindingException(
|
|
null,
|
|
ErrorCategory.InvalidArgument,
|
|
this.Command.MyInvocation,
|
|
GetErrorExtent(argument),
|
|
parameter.Parameter.Name,
|
|
null,
|
|
null,
|
|
ParameterBinderStrings.ScriptBlockArgumentNoOutput,
|
|
"ScriptBlockArgumentNoOutput");
|
|
|
|
throw bindingException;
|
|
}
|
|
|
|
// Check the output. If it is only a single value, just pass the single value,
|
|
// if not, pass in the whole collection.
|
|
|
|
object newValue = output;
|
|
if (output.Count == 1)
|
|
{
|
|
newValue = output[0];
|
|
}
|
|
|
|
// Create a new CommandParameterInternal for the output of the script block.
|
|
var newArgument = CommandParameterInternal.CreateParameterWithArgument(
|
|
argument.ParameterAst, argument.ParameterName, "-" + argument.ParameterName + ":",
|
|
argument.ArgumentAst, newValue,
|
|
false);
|
|
|
|
if (!BindParameter(newArgument, parameter, ParameterBindingFlags.ShouldCoerceType))
|
|
{
|
|
result = false;
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Determines the number of valid parameter sets based on the valid parameter
|
|
/// set flags.
|
|
/// </summary>
|
|
/// <param name="parameterSetFlags">
|
|
/// The valid parameter set flags.
|
|
/// </param>
|
|
/// <returns>
|
|
/// The number of valid parameter sets in the parameterSetFlags.
|
|
/// </returns>
|
|
private static int ValidParameterSetCount(uint parameterSetFlags)
|
|
{
|
|
int result = 0;
|
|
|
|
if (parameterSetFlags == uint.MaxValue)
|
|
{
|
|
result = 1;
|
|
}
|
|
else
|
|
{
|
|
while (parameterSetFlags != 0)
|
|
{
|
|
result += (int)(parameterSetFlags & 0x1);
|
|
parameterSetFlags >>= 1;
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
#endregion helper_methods
|
|
|
|
#region private_members
|
|
|
|
/// <summary>
|
|
/// This method gets a backup of the default value of a parameter.
|
|
/// Derived classes may override this method to get the default parameter
|
|
/// value in a different way.
|
|
/// </summary>
|
|
/// <param name="name">
|
|
/// The name of the parameter to get the default value of.
|
|
/// </param>
|
|
/// <returns>
|
|
/// The value of the parameter specified by name.
|
|
/// </returns>
|
|
/// <exception cref="ParameterBindingParameterDefaultValueException">
|
|
/// If the parameter binder encounters an error getting the default value.
|
|
/// </exception>
|
|
internal object GetDefaultParameterValue(string name)
|
|
{
|
|
MergedCompiledCommandParameter matchingParameter =
|
|
BindableParameters.GetMatchingParameter(
|
|
name,
|
|
false,
|
|
true,
|
|
null);
|
|
|
|
object result = null;
|
|
|
|
try
|
|
{
|
|
switch (matchingParameter.BinderAssociation)
|
|
{
|
|
case ParameterBinderAssociation.DeclaredFormalParameters:
|
|
result = DefaultParameterBinder.GetDefaultParameterValue(name);
|
|
break;
|
|
|
|
case ParameterBinderAssociation.CommonParameters:
|
|
result = CommonParametersBinder.GetDefaultParameterValue(name);
|
|
break;
|
|
|
|
case ParameterBinderAssociation.ShouldProcessParameters:
|
|
result = ShouldProcessParametersBinder.GetDefaultParameterValue(name);
|
|
break;
|
|
|
|
case ParameterBinderAssociation.DynamicParameters:
|
|
if (_dynamicParameterBinder != null)
|
|
{
|
|
result = _dynamicParameterBinder.GetDefaultParameterValue(name);
|
|
}
|
|
|
|
break;
|
|
}
|
|
}
|
|
catch (GetValueException getValueException)
|
|
{
|
|
ParameterBindingParameterDefaultValueException bindingError =
|
|
new ParameterBindingParameterDefaultValueException(
|
|
getValueException,
|
|
ErrorCategory.ReadError,
|
|
this.Command.MyInvocation,
|
|
null,
|
|
name,
|
|
null,
|
|
null,
|
|
"ParameterBinderStrings",
|
|
"GetDefaultValueFailed",
|
|
getValueException.Message);
|
|
|
|
throw bindingError;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets or sets the command that this parameter binder controller
|
|
/// will bind parameters to.
|
|
/// </summary>
|
|
internal Cmdlet Command { get; private set; }
|
|
|
|
#region DefaultParameterBindingStructures
|
|
|
|
/// <summary>
|
|
/// The separator used in GetDefaultParameterValuePairs function.
|
|
/// </summary>
|
|
private const string Separator = ":::";
|
|
|
|
// Hold all aliases of the current cmdlet
|
|
private List<string> _aliasList;
|
|
// Method GetDefaultParameterValuePairs() will be invoked twice, one time before the Named Bind,
|
|
// one time after Dynamic Bind. We don't want the same warning message to be written out twice.
|
|
// Put the key(in case the key format is invalid), or cmdletName+separator+parameterName(in case
|
|
// setting resolves to multiple parameters or multiple different values are assigned to the same
|
|
// parameter) in warningSet when the corresponding warnings are written out, so they won't get
|
|
// written out the second time GetDefaultParameterValuePairs() is called.
|
|
private readonly HashSet<string> _warningSet = new HashSet<string>();
|
|
|
|
// Hold all user defined default parameter values
|
|
private Dictionary<MergedCompiledCommandParameter, object> _allDefaultParameterValuePairs;
|
|
private bool _useDefaultParameterBinding = true;
|
|
|
|
#endregion DefaultParameterBindingStructures
|
|
|
|
private uint _parameterSetToBePrioritizedInPipelineBinding = 0;
|
|
|
|
/// <summary>
|
|
/// The cmdlet metadata.
|
|
/// </summary>
|
|
private readonly CommandMetadata _commandMetadata;
|
|
|
|
/// <summary>
|
|
/// THe command runtime object for this cmdlet.
|
|
/// </summary>
|
|
private readonly MshCommandRuntime _commandRuntime;
|
|
|
|
/// <summary>
|
|
/// Keep the obsolete parameter warnings generated from parameter binding.
|
|
/// </summary>
|
|
internal List<WarningRecord> ObsoleteParameterWarningList { get; private set; }
|
|
|
|
/// <summary>
|
|
/// Keep names of the parameters for which we have generated obsolete warning messages.
|
|
/// </summary>
|
|
private HashSet<string> BoundObsoleteParameterNames
|
|
{
|
|
get
|
|
{
|
|
return _boundObsoleteParameterNames ??
|
|
(_boundObsoleteParameterNames = new HashSet<string>(StringComparer.OrdinalIgnoreCase));
|
|
}
|
|
}
|
|
|
|
private HashSet<string> _boundObsoleteParameterNames;
|
|
|
|
/// <summary>
|
|
/// The parameter binder for the dynamic parameters. Currently this
|
|
/// can be either a ReflectionParameterBinder or a RuntimeDefinedParameterBinder.
|
|
/// </summary>
|
|
private ParameterBinderBase _dynamicParameterBinder;
|
|
|
|
/// <summary>
|
|
/// The parameter binder for the ShouldProcess parameters.
|
|
/// </summary>
|
|
internal ReflectionParameterBinder ShouldProcessParametersBinder
|
|
{
|
|
get
|
|
{
|
|
if (_shouldProcessParameterBinder is null)
|
|
{
|
|
// Construct a new instance of the should process parameters object
|
|
ShouldProcessParameters shouldProcessParameters = new ShouldProcessParameters(_commandRuntime);
|
|
|
|
// Create reflection binder for this object
|
|
|
|
_shouldProcessParameterBinder =
|
|
new ReflectionParameterBinder(
|
|
shouldProcessParameters,
|
|
this.Command,
|
|
this.CommandLineParameters);
|
|
}
|
|
|
|
return _shouldProcessParameterBinder;
|
|
}
|
|
}
|
|
|
|
private ReflectionParameterBinder _shouldProcessParameterBinder;
|
|
|
|
/// <summary>
|
|
/// The parameter binder for the Paging parameters.
|
|
/// </summary>
|
|
internal ReflectionParameterBinder PagingParametersBinder
|
|
{
|
|
get
|
|
{
|
|
if (_pagingParameterBinder is null)
|
|
{
|
|
// Construct a new instance of the should process parameters object
|
|
PagingParameters pagingParameters = new PagingParameters(_commandRuntime);
|
|
|
|
// Create reflection binder for this object
|
|
|
|
_pagingParameterBinder =
|
|
new ReflectionParameterBinder(
|
|
pagingParameters,
|
|
this.Command,
|
|
this.CommandLineParameters);
|
|
}
|
|
|
|
return _pagingParameterBinder;
|
|
}
|
|
}
|
|
|
|
private ReflectionParameterBinder _pagingParameterBinder;
|
|
|
|
/// <summary>
|
|
/// The parameter binder for the Transactions parameters.
|
|
/// </summary>
|
|
internal ReflectionParameterBinder TransactionParametersBinder
|
|
{
|
|
get
|
|
{
|
|
if (_transactionParameterBinder is null)
|
|
{
|
|
// Construct a new instance of the transactions parameters object
|
|
TransactionParameters transactionParameters = new TransactionParameters(_commandRuntime);
|
|
|
|
// Create reflection binder for this object
|
|
|
|
_transactionParameterBinder =
|
|
new ReflectionParameterBinder(
|
|
transactionParameters,
|
|
this.Command,
|
|
this.CommandLineParameters);
|
|
}
|
|
|
|
return _transactionParameterBinder;
|
|
}
|
|
}
|
|
|
|
private ReflectionParameterBinder _transactionParameterBinder;
|
|
|
|
/// <summary>
|
|
/// The parameter binder for the CommonParameters.
|
|
/// </summary>
|
|
internal ReflectionParameterBinder CommonParametersBinder
|
|
{
|
|
get
|
|
{
|
|
if (_commonParametersBinder is null)
|
|
{
|
|
// Construct a new instance of the user feedback parameters object
|
|
CommonParameters commonParameters = new CommonParameters(_commandRuntime);
|
|
|
|
// Create reflection binder for this object
|
|
|
|
_commonParametersBinder =
|
|
new ReflectionParameterBinder(
|
|
commonParameters,
|
|
this.Command,
|
|
this.CommandLineParameters);
|
|
}
|
|
|
|
return _commonParametersBinder;
|
|
}
|
|
}
|
|
|
|
private ReflectionParameterBinder _commonParametersBinder;
|
|
|
|
private class DelayedScriptBlockArgument
|
|
{
|
|
// Remember the parameter binder so we know when to invoke the script block
|
|
// and when to use the evaluated argument.
|
|
internal CmdletParameterBinderController _parameterBinder;
|
|
internal CommandParameterInternal _argument;
|
|
internal Collection<PSObject> _evaluatedArgument;
|
|
|
|
public override string ToString()
|
|
{
|
|
return _argument.ArgumentValue.ToString();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// This dictionary is used to contain the arguments that were passed in as ScriptBlocks
|
|
/// but the parameter isn't a ScriptBlock. So we have to wait to bind the parameter
|
|
/// until there is a pipeline object available to invoke the ScriptBlock with.
|
|
/// </summary>
|
|
private readonly Dictionary<MergedCompiledCommandParameter, DelayedScriptBlockArgument> _delayBindScriptBlocks =
|
|
new Dictionary<MergedCompiledCommandParameter, DelayedScriptBlockArgument>();
|
|
|
|
/// <summary>
|
|
/// A collection of the default values of the parameters.
|
|
/// </summary>
|
|
private readonly Dictionary<string, CommandParameterInternal> _defaultParameterValues =
|
|
new Dictionary<string, CommandParameterInternal>(StringComparer.OrdinalIgnoreCase);
|
|
|
|
#endregion private_members
|
|
|
|
/// <summary>
|
|
/// Binds the specified value to the specified parameter.
|
|
/// </summary>
|
|
/// <param name="parameterValue">
|
|
/// The value to bind to the parameter
|
|
/// </param>
|
|
/// <param name="parameter">
|
|
/// The parameter to bind the value to.
|
|
/// </param>
|
|
/// <param name="flags">
|
|
/// Parameter binding flags for type coercion and validation.
|
|
/// </param>
|
|
/// <returns>
|
|
/// True if the parameter was successfully bound. False if <paramref name="flags"/>
|
|
/// specifies no coercion and the type does not match the parameter type.
|
|
/// </returns>
|
|
/// <exception cref="ParameterBindingParameterDefaultValueException">
|
|
/// If the parameter binder encounters an error getting the default value.
|
|
/// </exception>
|
|
private bool BindPipelineParameter(
|
|
object parameterValue,
|
|
MergedCompiledCommandParameter parameter,
|
|
ParameterBindingFlags flags)
|
|
{
|
|
bool result = false;
|
|
|
|
if (parameterValue != AutomationNull.Value)
|
|
{
|
|
s_tracer.WriteLine("Adding PipelineParameter name={0}; value={1}",
|
|
parameter.Parameter.Name, parameterValue ?? "null");
|
|
|
|
// Backup the default value
|
|
BackupDefaultParameter(parameter);
|
|
|
|
// Now bind the new value
|
|
CommandParameterInternal param = CommandParameterInternal.CreateParameterWithArgument(
|
|
/*parameterAst*/null, parameter.Parameter.Name, "-" + parameter.Parameter.Name + ":",
|
|
/*argumentAst*/null, parameterValue,
|
|
false);
|
|
|
|
flags = flags & ~ParameterBindingFlags.DelayBindScriptBlock;
|
|
result = BindParameter(_currentParameterSetFlag, param, parameter, flags);
|
|
|
|
if (result)
|
|
{
|
|
// Now make sure to remember that the default value needs to be restored
|
|
// if we get another pipeline object
|
|
ParametersBoundThroughPipelineInput.Add(parameter);
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
protected override void SaveDefaultScriptParameterValue(string name, object value)
|
|
{
|
|
_defaultParameterValues.Add(name,
|
|
CommandParameterInternal.CreateParameterWithArgument(
|
|
/*parameterAst*/null, name, "-" + name + ":",
|
|
/*argumentAst*/null, value,
|
|
false));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Backs up the specified parameter value by calling the GetDefaultParameterValue
|
|
/// abstract method.
|
|
///
|
|
/// This method is called when binding a parameter value that came from a pipeline
|
|
/// object.
|
|
/// </summary>
|
|
/// <exception cref="ParameterBindingParameterDefaultValueException">
|
|
/// If the parameter binder encounters an error getting the default value.
|
|
/// </exception>
|
|
private void BackupDefaultParameter(MergedCompiledCommandParameter parameter)
|
|
{
|
|
if (!_defaultParameterValues.ContainsKey(parameter.Parameter.Name))
|
|
{
|
|
object defaultParameterValue = GetDefaultParameterValue(parameter.Parameter.Name);
|
|
_defaultParameterValues.Add(
|
|
parameter.Parameter.Name,
|
|
CommandParameterInternal.CreateParameterWithArgument(
|
|
/*parameterAst*/null, parameter.Parameter.Name, "-" + parameter.Parameter.Name + ":",
|
|
/*argumentAst*/null, defaultParameterValue,
|
|
false));
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Replaces the values of the parameters with their initial value for the
|
|
/// parameters specified.
|
|
/// </summary>
|
|
/// <param name="parameters">
|
|
/// The parameters that should have their default values restored.
|
|
/// </param>
|
|
/// <exception cref="ArgumentNullException">
|
|
/// If <paramref name="parameters"/> is null.
|
|
/// </exception>
|
|
private void RestoreDefaultParameterValues(IEnumerable<MergedCompiledCommandParameter> parameters)
|
|
{
|
|
if (parameters is null)
|
|
{
|
|
throw PSTraceSource.NewArgumentNullException(nameof(parameters));
|
|
}
|
|
|
|
// Get all the matching arguments from the defaultParameterValues collection
|
|
// and bind those that had parameters that were bound via pipeline input
|
|
|
|
foreach (MergedCompiledCommandParameter parameter in parameters)
|
|
{
|
|
if (parameter is null)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
CommandParameterInternal argumentToBind = null;
|
|
|
|
// If the argument was found then bind it to the parameter
|
|
// and manage the bound and unbound parameter list
|
|
|
|
if (_defaultParameterValues.TryGetValue(parameter.Parameter.Name, out argumentToBind))
|
|
{
|
|
// Don't go through the normal binding routine to run data generation,
|
|
// type coercion, validation, or prerequisites since we know the
|
|
// type is already correct, and we don't want data generation to
|
|
// run when resetting the default value.
|
|
|
|
Exception error = null;
|
|
try
|
|
{
|
|
// We shouldn't have to coerce the type here so its
|
|
// faster to pass false
|
|
|
|
bool bindResult = RestoreParameter(argumentToBind, parameter);
|
|
|
|
Diagnostics.Assert(
|
|
bindResult,
|
|
"Restoring the default value should not require type coercion");
|
|
}
|
|
catch (SetValueException setValueException)
|
|
{
|
|
error = setValueException;
|
|
}
|
|
|
|
if (error != null)
|
|
{
|
|
Type specifiedType = (argumentToBind.ArgumentValue is null) ? null : argumentToBind.ArgumentValue.GetType();
|
|
ParameterBindingException bindingException =
|
|
new ParameterBindingException(
|
|
error,
|
|
ErrorCategory.WriteError,
|
|
this.InvocationInfo,
|
|
GetErrorExtent(argumentToBind),
|
|
parameter.Parameter.Name,
|
|
parameter.Parameter.Type,
|
|
specifiedType,
|
|
ParameterBinderStrings.ParameterBindingFailed,
|
|
"ParameterBindingFailed",
|
|
error.Message);
|
|
throw bindingException;
|
|
}
|
|
|
|
// Since the parameter was returned to its original value,
|
|
// ensure that it is not in the boundParameters list but
|
|
// is in the unboundParameters list
|
|
|
|
BoundParameters.Remove(parameter.Parameter.Name);
|
|
|
|
if (!UnboundParameters.Contains(parameter))
|
|
{
|
|
UnboundParameters.Add(parameter);
|
|
}
|
|
|
|
BoundArguments.Remove(parameter.Parameter.Name);
|
|
}
|
|
else
|
|
{
|
|
// Since the parameter was not reset, ensure that the parameter
|
|
// is in the bound parameters list and not in the unbound
|
|
// parameters list
|
|
|
|
if (!BoundParameters.ContainsKey(parameter.Parameter.Name))
|
|
{
|
|
BoundParameters.Add(parameter.Parameter.Name, parameter);
|
|
}
|
|
|
|
// Ensure the parameter is not in the unboundParameters list
|
|
|
|
UnboundParameters.Remove(parameter);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// A versionable hashtable, so the caching of UserInput -> ParameterBindingResult will work.
|
|
/// </summary>
|
|
[SuppressMessage("Microsoft.Usage", "CA2237:MarkISerializableTypesWithSerializable", Justification = "DefaultParameterDictionary will only be used for $PSDefaultParameterValues.")]
|
|
public sealed class DefaultParameterDictionary : Hashtable
|
|
{
|
|
private bool _isChanged;
|
|
|
|
/// <summary>
|
|
/// Check to see if the hashtable has been changed since last check.
|
|
/// </summary>
|
|
/// <returns>True for changed; false for not changed.</returns>
|
|
public bool ChangeSinceLastCheck()
|
|
{
|
|
bool ret = _isChanged;
|
|
_isChanged = false;
|
|
return ret;
|
|
}
|
|
|
|
#region Constructor
|
|
|
|
/// <summary>
|
|
/// Default constructor.
|
|
/// </summary>
|
|
public DefaultParameterDictionary()
|
|
: base(StringComparer.OrdinalIgnoreCase)
|
|
{
|
|
_isChanged = true;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Constructor takes a hash table.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// Check for the keys' formats and make it versionable
|
|
/// </remarks>
|
|
/// <param name="dictionary">A hashtable instance.</param>
|
|
public DefaultParameterDictionary(IDictionary dictionary)
|
|
: this()
|
|
{
|
|
if (dictionary is null)
|
|
{
|
|
throw PSTraceSource.NewArgumentNullException(nameof(dictionary));
|
|
}
|
|
// Contains keys that are in bad format. For every bad format key, we should write out a warning message
|
|
// the first time we encounter it, and remove it from the $PSDefaultParameterValues
|
|
var keysInBadFormat = new List<object>();
|
|
|
|
foreach (DictionaryEntry entry in dictionary)
|
|
{
|
|
var entryKey = entry.Key as string;
|
|
if (entryKey != null)
|
|
{
|
|
string key = entryKey.Trim();
|
|
string cmdletName = null;
|
|
string parameterName = null;
|
|
bool isSpecialKey = false; // The key is 'Disabled'
|
|
|
|
// The key is not with valid format
|
|
if (!CheckKeyIsValid(key, ref cmdletName, ref parameterName))
|
|
{
|
|
isSpecialKey = key.Equals("Disabled", StringComparison.OrdinalIgnoreCase);
|
|
if (!isSpecialKey)
|
|
{
|
|
keysInBadFormat.Add(entryKey);
|
|
continue;
|
|
}
|
|
}
|
|
|
|
Diagnostics.Assert(isSpecialKey || (cmdletName != null && parameterName != null), "The cmdletName and parameterName should be set in CheckKeyIsValid");
|
|
if (keysInBadFormat.Count == 0 && !base.ContainsKey(key))
|
|
{
|
|
base.Add(key, entry.Value);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
keysInBadFormat.Add(entry.Key);
|
|
}
|
|
}
|
|
|
|
var keysInError = new StringBuilder();
|
|
foreach (object badFormatKey in keysInBadFormat)
|
|
{
|
|
keysInError.Append(badFormatKey.ToString() + ", ");
|
|
}
|
|
|
|
if (keysInError.Length > 0)
|
|
{
|
|
keysInError.Remove(keysInError.Length - 2, 2);
|
|
string resourceString = keysInBadFormat.Count > 1
|
|
? ParameterBinderStrings.MultipleKeysInBadFormat
|
|
: ParameterBinderStrings.SingleKeyInBadFormat;
|
|
throw PSTraceSource.NewInvalidOperationException(resourceString, keysInError);
|
|
}
|
|
}
|
|
|
|
#endregion Constructor
|
|
|
|
/// <summary>
|
|
/// Override Contains.
|
|
/// </summary>
|
|
public override bool Contains(object key)
|
|
{
|
|
return this.ContainsKey(key);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Override ContainsKey.
|
|
/// </summary>
|
|
public override bool ContainsKey(object key)
|
|
{
|
|
if (key is null)
|
|
{
|
|
throw PSTraceSource.NewArgumentNullException(nameof(key));
|
|
}
|
|
|
|
var strKey = key as string;
|
|
if (strKey is null) { return false; }
|
|
|
|
string keyAfterTrim = strKey.Trim();
|
|
return base.ContainsKey(keyAfterTrim);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Override the Add to check for key's format and make it versionable.
|
|
/// </summary>
|
|
/// <param name="key">Key.</param>
|
|
/// <param name="value">Value.</param>
|
|
public override void Add(object key, object value)
|
|
{
|
|
AddImpl(key, value, isSelfIndexing: false);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Actual implementation for Add.
|
|
/// </summary>
|
|
private void AddImpl(object key, object value, bool isSelfIndexing)
|
|
{
|
|
if (key is null)
|
|
{
|
|
throw PSTraceSource.NewArgumentNullException(nameof(key));
|
|
}
|
|
|
|
var strKey = key as string;
|
|
if (strKey is null)
|
|
{
|
|
throw PSTraceSource.NewArgumentException(nameof(key), ParameterBinderStrings.StringValueKeyExpected, key, key.GetType().FullName);
|
|
}
|
|
|
|
string keyAfterTrim = strKey.Trim();
|
|
string cmdletName = null;
|
|
string parameterName = null;
|
|
|
|
if (base.ContainsKey(keyAfterTrim))
|
|
{
|
|
if (isSelfIndexing)
|
|
{
|
|
_isChanged = true;
|
|
base[keyAfterTrim] = value;
|
|
return;
|
|
}
|
|
|
|
throw PSTraceSource.NewArgumentException(nameof(key), ParameterBinderStrings.KeyAlreadyAdded, key);
|
|
}
|
|
|
|
if (!CheckKeyIsValid(keyAfterTrim, ref cmdletName, ref parameterName))
|
|
{
|
|
// The key is not in valid format
|
|
if (!keyAfterTrim.Equals("Disabled", StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
throw PSTraceSource.NewInvalidOperationException(ParameterBinderStrings.SingleKeyInBadFormat, key);
|
|
}
|
|
}
|
|
|
|
_isChanged = true;
|
|
base.Add(keyAfterTrim, value);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Override the indexing to check for key's format and make it versionable.
|
|
/// </summary>
|
|
/// <param name="key"></param>
|
|
/// <returns></returns>
|
|
public override object this[object key]
|
|
{
|
|
get
|
|
{
|
|
if (key is null) { throw PSTraceSource.NewArgumentNullException(nameof(key)); }
|
|
|
|
var strKey = key as string;
|
|
if (strKey is null) { return null; }
|
|
|
|
string keyAfterTrim = strKey.Trim();
|
|
return base[keyAfterTrim];
|
|
}
|
|
|
|
set
|
|
{
|
|
AddImpl(key, value, isSelfIndexing: true);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Override the Remove to make it versionable.
|
|
/// </summary>
|
|
/// <param name="key">Key.</param>
|
|
public override void Remove(object key)
|
|
{
|
|
if (key is null)
|
|
{
|
|
throw PSTraceSource.NewArgumentNullException(nameof(key));
|
|
}
|
|
|
|
var strKey = key as string;
|
|
if (strKey is null) { return; }
|
|
|
|
string keyAfterTrim = strKey.Trim();
|
|
if (base.ContainsKey(keyAfterTrim))
|
|
{
|
|
base.Remove(keyAfterTrim);
|
|
_isChanged = true;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Override the Clear to make it versionable.
|
|
/// </summary>
|
|
public override void Clear()
|
|
{
|
|
base.Clear();
|
|
_isChanged = true;
|
|
}
|
|
|
|
#region KeyValidation
|
|
|
|
/// <summary>
|
|
/// Check if the key is in valid format. If it is, get the cmdlet name and parameter name.
|
|
/// </summary>
|
|
/// <param name="key"></param>
|
|
/// <param name="cmdletName"></param>
|
|
/// <param name="parameterName"></param>
|
|
/// <returns>Return true if the key is valid, false if not.</returns>
|
|
internal static bool CheckKeyIsValid(string key, ref string cmdletName, ref string parameterName)
|
|
{
|
|
if (key == string.Empty)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// The index returned should point to the separator or a character that is before the separator
|
|
int index = GetValueToken(0, key, ref cmdletName, true);
|
|
if (index == -1)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// The index returned should point to the first non-whitespace character, and it should be the separator
|
|
index = SkipWhiteSpace(index, key);
|
|
if (index == -1 || key[index] != ':')
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// The index returned should point to the first non-whitespace character after the separator
|
|
index = SkipWhiteSpace(index + 1, key);
|
|
if (index == -1)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// The index returned should point to the last character in key
|
|
index = GetValueToken(index, key, ref parameterName, false);
|
|
if (index == -1 || index != key.Length)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get the cmdlet name and the parameter name.
|
|
/// </summary>
|
|
/// <param name="index">Point to a non-whitespace character.</param>
|
|
/// <param name="key">The key to iterate over.</param>
|
|
/// <param name="name"></param>
|
|
/// <param name="getCmdletName">Specify whether to get the cmdlet name or parameter name.</param>
|
|
/// <returns>
|
|
/// For cmdletName:
|
|
/// When the name is enclosed by quotes, the index returned should be the index of the character right after the second quote;
|
|
/// When the name is not enclosed by quotes, the index returned should be the index of the separator;
|
|
///
|
|
/// For parameterName:
|
|
/// When the name is enclosed by quotes, the index returned should be the index of the second quote plus 1 (the length of the key if the key is in a valid format);
|
|
/// When the name is not enclosed by quotes, the index returned should be the length of the key.
|
|
/// </returns>
|
|
private static int GetValueToken(int index, string key, ref string name, bool getCmdletName)
|
|
{
|
|
char quoteChar = '\0';
|
|
if (key[index].IsSingleQuote() || key[index].IsDoubleQuote())
|
|
{
|
|
quoteChar = key[index];
|
|
index++;
|
|
}
|
|
|
|
StringBuilder builder = new StringBuilder(string.Empty);
|
|
for (; index < key.Length; index++)
|
|
{
|
|
if (quoteChar != '\0')
|
|
{
|
|
if ((quoteChar.IsSingleQuote() && key[index].IsSingleQuote()) ||
|
|
(quoteChar.IsDoubleQuote() && key[index].IsDoubleQuote()))
|
|
{
|
|
name = builder.ToString().Trim();
|
|
// Make the index point to the character right after the quote
|
|
return name.Length == 0 ? -1 : index + 1;
|
|
}
|
|
|
|
builder.Append(key[index]);
|
|
continue;
|
|
}
|
|
|
|
if (getCmdletName)
|
|
{
|
|
if (key[index] != ':')
|
|
{
|
|
builder.Append(key[index]);
|
|
continue;
|
|
}
|
|
|
|
name = builder.ToString().Trim();
|
|
return name.Length == 0 ? -1 : index;
|
|
}
|
|
else
|
|
{
|
|
builder.Append(key[index]);
|
|
}
|
|
}
|
|
|
|
if (!getCmdletName && quoteChar == '\0')
|
|
{
|
|
name = builder.ToString().Trim();
|
|
Diagnostics.Assert(name.Length > 0, "name should not be empty at this point");
|
|
return index;
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Skip whitespace characters.
|
|
/// </summary>
|
|
/// <param name="index">Start index.</param>
|
|
/// <param name="key">The string to iterate over.</param>
|
|
/// <returns>
|
|
/// Return -1 if we reach the end of the key, otherwise return the index of the first
|
|
/// non-whitespace character we encounter.
|
|
/// </returns>
|
|
private static int SkipWhiteSpace(int index, string key)
|
|
{
|
|
for (; index < key.Length; index++)
|
|
{
|
|
if (key[index].IsWhitespace() || key[index] == '\r' || key[index] == '\n')
|
|
continue;
|
|
return index;
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
#endregion KeyValidation
|
|
}
|
|
}
|
|
|