mirror of
https://github.com/aphrody-labs/PowerShell.git
synced 2026-10-17 20:56:02 +00:00
Previously we used IScriptPosition for context (e.g. error reporting) during parameter binding, but in some cases we want more information, so we'll use the Ast instead. This change just adds the Ast, it doesn't make explicit use of it.
4822 lines
212 KiB
C#
4822 lines
212 KiB
C#
/********************************************************************++
|
|
Copyright (c) Microsoft Corporation. All rights reserved.
|
|
--********************************************************************/
|
|
|
|
using System.Collections;
|
|
using System.Collections.Generic;
|
|
using System.Collections.ObjectModel;
|
|
using System.Linq;
|
|
using System.Management.Automation.Internal;
|
|
using System.Management.Automation.Host;
|
|
using System.Management.Automation.Language;
|
|
using System.Management.Automation.Runspaces;
|
|
using System.Text;
|
|
using System.Globalization;
|
|
using System.Diagnostics.CodeAnalysis;
|
|
|
|
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 == null)
|
|
{
|
|
throw PSTraceSource.NewArgumentNullException("cmdlet");
|
|
}
|
|
|
|
if (commandMetadata == null)
|
|
{
|
|
throw PSTraceSource.NewArgumentNullException("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 == 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 == 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 == 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 == 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 == 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 == 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 (null != parameter.ArgumentValue)
|
|
{
|
|
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,
|
|
_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,
|
|
"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;
|
|
} // BindParameters
|
|
|
|
/// <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 == 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 == 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();
|
|
}
|
|
}
|
|
}
|
|
} // HandleRemainingArguments
|
|
|
|
|
|
/// <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 == 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 == 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);
|
|
}
|
|
}
|
|
} // dynamicParameterCmdlet != null
|
|
}
|
|
}
|
|
} // HandleCommandLineDynamicParameters
|
|
|
|
/// <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;
|
|
} // ValidateParameterSets
|
|
|
|
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;
|
|
} // AtLeastOneValidParameterSetTakesPipelineInput
|
|
|
|
/// <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 == 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;
|
|
} // HandleUnboundMandatoryParameters
|
|
|
|
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 == 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);
|
|
} // foreach unbound parameter
|
|
|
|
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.IndexOf(parameterName[i]) == -1))
|
|
{
|
|
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.IndexOf(parameterName[i]) == -1))
|
|
{
|
|
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.IndexOf(parameterName[i]) == -1))
|
|
{
|
|
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 == 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 == 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 == 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, Utils.EmptyArray<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 == 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;
|
|
} // InvokeAndBindDelayBindScriptBlock
|
|
|
|
/// <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 == 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 == 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 == 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 == 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 == null)
|
|
{
|
|
throw PSTraceSource.NewArgumentNullException("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 == 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 == 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 (null == dictionary)
|
|
{
|
|
throw PSTraceSource.NewArgumentNullException("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 == null)
|
|
{
|
|
throw PSTraceSource.NewArgumentNullException("key");
|
|
}
|
|
|
|
var strKey = key as string;
|
|
if (strKey == 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 == null)
|
|
{
|
|
throw PSTraceSource.NewArgumentNullException("key");
|
|
}
|
|
|
|
var strKey = key as string;
|
|
if (strKey == null)
|
|
{
|
|
throw PSTraceSource.NewArgumentException("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("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 == null) { throw PSTraceSource.NewArgumentNullException("key"); }
|
|
|
|
var strKey = key as string;
|
|
if (strKey == 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 == null)
|
|
{
|
|
throw PSTraceSource.NewArgumentNullException("key");
|
|
}
|
|
|
|
var strKey = key as string;
|
|
if (strKey == 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
|
|
}
|
|
}
|
|
|
|
|