2016-03-30 14:20:52 -07:00
/********************************************************************++
Copyright (c) Microsoft Corporation. All rights reserved.
--********************************************************************/
2016-07-29 13:02:49 -07:00
2016-03-30 14:20:52 -07:00
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>
2017-01-16 13:31:14 -08:00
///
2016-03-30 14:20:52 -07:00
internal class CmdletParameterBinderController : ParameterBinderController
{
#region tracer
[TraceSource("ParameterBinderController", "Controls the interaction between the command processor and the parameter binder(s).")]
2016-07-29 13:02:49 -07:00
private static readonly PSTraceSource s_tracer = PSTraceSource . GetTracer ( "ParameterBinderController" , "Controls the interaction between the command processor and the parameter binder(s)." );
2016-03-30 14:20:52 -07:00
#endregion tracer
#region ctor
/// <summary>
/// Initializes the cmdlet parameter binder controller for
/// the specified cmdlet and engine context
/// </summary>
2017-01-16 13:31:14 -08:00
///
2016-03-30 14:20:52 -07:00
/// <param name="cmdlet">
/// The cmdlet that the parameters will be bound to.
/// </param>
2017-01-16 13:31:14 -08:00
///
2016-03-30 14:20:52 -07:00
/// <param name="commandMetadata">
/// The metadata about the cmdlet.
/// </param>
2017-01-16 13:31:14 -08:00
///
2016-03-30 14:20:52 -07:00
/// <param name="parameterBinder">
/// The default parameter binder to use.
/// </param>
2017-01-16 13:31:14 -08:00
///
2016-03-30 14:20:52 -07:00
internal CmdletParameterBinderController (
Cmdlet cmdlet ,
CommandMetadata commandMetadata ,
ParameterBinderBase parameterBinder )
: base (
2016-07-29 13:02:49 -07:00
cmdlet . MyInvocation ,
cmdlet . Context ,
2016-03-30 14:20:52 -07:00
parameterBinder )
{
if ( cmdlet == null )
{
throw PSTraceSource . NewArgumentNullException ( "cmdlet" );
}
if ( commandMetadata == null )
{
throw PSTraceSource . NewArgumentNullException ( "commandMetadata" );
}
this . Command = cmdlet ;
2016-07-29 13:02:49 -07:00
_commandRuntime = ( MshCommandRuntime ) cmdlet . CommandRuntime ;
_commandMetadata = commandMetadata ;
2016-03-30 14:20:52 -07:00
// 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>
2017-01-16 13:31:14 -08:00
///
2016-03-30 14:20:52 -07:00
/// <param name="arguments">
/// Parameters to the command.
/// </param>
2017-01-16 13:31:14 -08:00
///
2016-03-30 14:20:52 -07:00
/// <exception cref="ParameterBindingException">
/// If any parameters fail to bind,
/// or
/// If any mandatory parameters are missing.
/// </exception>
2017-01-16 13:31:14 -08:00
///
2016-03-30 14:20:52 -07:00
/// <exception cref="MetadataException">
/// If there is an error generating the metadata for dynamic parameters.
/// </exception>
2017-01-16 13:31:14 -08:00
///
2016-03-30 14:20:52 -07:00
internal void BindCommandLineParameters ( Collection < CommandParameterInternal > arguments )
{
2016-07-29 13:02:49 -07:00
s_tracer . WriteLine ( "Argument count: {0}" , arguments . Count );
2016-03-30 14:20:52 -07:00
BindCommandLineParametersNoValidation ( arguments );
// Is pipeline input expected?
2016-07-29 13:02:49 -07:00
bool isPipelineInputExpected = !( _commandRuntime . IsClosed && _commandRuntime . InputPipe . Empty );
2016-03-30 14:20:52 -07:00
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
2016-07-29 13:02:49 -07:00
if ( validParameterSetCount == 1 && ! DefaultParameterBindingInUse )
2016-03-30 14:20:52 -07:00
{
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}]" ,
2016-07-29 13:02:49 -07:00
_commandMetadata . Name ))
2016-03-30 14:20:52 -07:00
{
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 )
{
2016-07-29 13:02:49 -07:00
if (! DefaultParameterBindingInUse )
2016-03-30 14:20:52 -07:00
{
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 )
{
2017-09-06 10:25:33 -07:00
psCompiledScriptCmdlet . PrepareForBinding ( this . CommandLineParameters );
2016-03-30 14:20:52 -07:00
}
// Add the passed in arguments to the unboundArguments collection
foreach ( CommandParameterInternal argument in arguments )
{
UnboundArguments . Add ( argument );
}
2016-07-29 13:02:49 -07:00
CommandMetadata cmdletMetadata = _commandMetadata ;
2016-03-30 14:20:52 -07:00
// Clear the warningSet at the beginning.
2016-07-29 13:02:49 -07:00
_warningSet . Clear ();
2016-03-30 14:20:52 -07:00
// Parse $PSDefaultParameterValues to get all valid <parameter, value> pairs
2016-07-29 13:02:49 -07:00
_allDefaultParameterValuePairs = this . GetDefaultParameterValuePairs ( true );
2016-03-30 14:20:52 -07:00
// 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}]" ,
2016-07-29 13:02:49 -07:00
_commandMetadata . Name ))
2016-03-30 14:20:52 -07:00
{
// Bind the actual arguments
UnboundArguments = BindParameters ( _currentParameterSetFlag , this . UnboundArguments );
}
ParameterBindingException reportedBindingException ;
ParameterBindingException currentBindingException ;
using ( ParameterBinderBase . bindingTracer . TraceScope (
"BIND POSITIONAL cmd line args [{0}]" ,
2016-07-29 13:02:49 -07:00
_commandMetadata . Name ))
2016-03-30 14:20:52 -07:00
{
// 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>
2017-01-16 13:31:14 -08:00
///
2016-03-30 14:20:52 -07:00
/// <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 )
{
2016-08-28 05:44:55 +00:00
// If a parameter doesn't take pipeline input at all, we can skip it
2016-03-30 14:20:52 -07:00
if (! parameter . Parameter . IsPipelineParameterInSomeParameterSet )
{
continue ;
}
2016-07-29 13:02:49 -07:00
var matchingParameterSetMetadata =
2016-03-30 14:20:52 -07:00
parameter . Parameter . GetMatchingParameterSetData ( _currentParameterSetFlag );
2016-07-29 13:02:49 -07:00
2016-03-30 14:20:52 -07:00
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>
2017-01-16 13:31:14 -08:00
/// Apply the binding for the default parameter defined by the user
2016-03-30 14:20:52 -07:00
/// </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 )
{
2016-07-29 13:02:49 -07:00
if (! _useDefaultParameterBinding )
2016-03-30 14:20:52 -07:00
{
return ;
}
2016-07-29 13:02:49 -07:00
if ( isDynamic )
2016-03-30 14:20:52 -07:00
{
2017-01-16 13:31:14 -08:00
// Get user defined default parameter value pairs again, so that the
2016-03-30 14:20:52 -07:00
// 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}]" ,
2016-07-29 13:02:49 -07:00
bindingStage , _commandMetadata . Name ))
2016-03-30 14:20:52 -07:00
{
isSuccess = BindDefaultParameters ( _currentParameterSetFlag , qualifiedParameterValuePairs );
if ( isSuccess && ! DefaultParameterBindingInUse )
{
DefaultParameterBindingInUse = true ;
}
}
2016-07-29 13:02:49 -07:00
s_tracer . WriteLine ( "BIND DEFAULT after [{0}] result [{1}]" , bindingStage , isSuccess );
2016-03-30 14:20:52 -07:00
}
return ;
}
/// <summary>
/// Bind the default parameter value pairs
/// </summary>
/// <param name="validParameterSetFlag">validParameterSetFlag</param>
/// <param name="defaultParameterValues">default value pairs</param>
/// <returns>
2016-08-28 05:44:55 +00:00
/// true if there is at least one default parameter bound successfully
2016-03-30 14:20:52 -07:00
/// 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 ;
2016-07-29 13:02:49 -07:00
if ( scriptBlockArg != null )
2016-03-30 14:20:52 -07:00
{
// 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 );
2016-07-29 13:02:49 -07:00
if ( results == null || results . Count == 0 )
2016-03-30 14:20:52 -07:00
{
continue ;
}
2016-07-29 13:02:49 -07:00
else if ( results . Count == 1 )
2016-03-30 14:20:52 -07:00
{
argumentValue = results [ 0 ];
}
else
{
argumentValue = results ;
}
}
CommandParameterInternal bindableArgument =
CommandParameterInternal . CreateParameterWithArgument (
PositionUtilities . EmptyExtent , parameterName , "-" + parameterName + ":" ,
PositionUtilities . EmptyExtent , argumentValue , false );
bool bindResult =
BindParameter (
validParameterSetFlag ,
bindableArgument ,
parameter ,
ParameterBindingFlags . ShouldCoerceType | ParameterBindingFlags . DelayBindScriptBlock );
2016-07-29 13:02:49 -07:00
if ( bindResult && ! ret )
2016-03-30 14:20:52 -07:00
{
ret = true ;
}
2016-07-29 13:02:49 -07:00
if ( bindResult )
2016-03-30 14:20:52 -07:00
{
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
2016-07-29 13:02:49 -07:00
if (! _warningSet . Contains ( _commandMetadata . Name + Separator + parameterName ))
2016-03-30 14:20:52 -07:00
{
string message = String . Format ( CultureInfo . InvariantCulture ,
ParameterBinderStrings . FailToBindDefaultParameter ,
LanguagePrimitives . IsNull ( argumentValue ) ? "null" : argumentValue . ToString (),
parameterName , ex . Message );
2016-07-29 13:02:49 -07:00
_commandRuntime . WriteWarning ( message );
_warningSet . Add ( _commandMetadata . Name + Separator + parameterName );
2016-03-30 14:20:52 -07:00
}
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 );
2016-07-29 13:02:49 -07:00
HashSet < string > boundPositionalParameterNames =
2016-03-30 14:20:52 -07:00
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>
2017-01-16 13:31:14 -08:00
/// Get all qualified default parameter value pairs based on the
2016-03-30 14:20:52 -07:00
/// 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 )
{
2016-07-29 13:02:49 -07:00
if ( availableParameterValuePairs == null )
2016-03-30 14:20:52 -07:00
{
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 ;
}
2016-07-29 13:02:49 -07:00
if ( BoundArguments . ContainsKey ( param . Parameter . Name ))
2016-03-30 14:20:52 -07:00
{
continue ;
}
2016-08-28 05:44:55 +00:00
// check if this param's set conflicts with other possible params.
2016-07-29 13:02:49 -07:00
if ( param . Parameter . ParameterSetFlags != 0 )
2016-03-30 14:20:52 -07:00
{
possibleParameterFlag &= param . Parameter . ParameterSetFlags ;
if ( possibleParameterFlag == 0 )
{
return null ;
}
}
result . Add ( param , pair . Value );
}
2016-07-29 13:02:49 -07:00
if ( result . Count > 0 )
2016-03-30 14:20:52 -07:00
{
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>
2017-01-16 13:31:14 -08:00
/// Get all available default parameter value pairs
2016-03-30 14:20:52 -07:00
/// </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
2016-07-29 13:02:49 -07:00
_aliasList = GetAliasOfCurrentCmdlet ();
2016-03-30 14:20:52 -07:00
}
// Set flag to true by default
_useDefaultParameterBinding = true ;
2016-07-29 13:02:49 -07:00
string currentCmdletName = _commandMetadata . Name ;
2016-03-30 14:20:52 -07:00
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
2017-01-16 13:31:14 -08:00
// of the current cmdlet, or is an alias name of the current cmdlet.
2016-03-30 14:20:52 -07:00
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 ))
{
2016-07-29 13:02:49 -07:00
_commandRuntime . WriteWarning (
2016-03-30 14:20:52 -07:00
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 ))
{
2016-07-29 13:02:49 -07:00
_commandRuntime . WriteWarning (
2016-03-30 14:20:52 -07:00
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 ;
2016-07-29 13:02:49 -07:00
_commandRuntime . WriteWarning (
2016-03-30 14:20:52 -07:00
String . Format ( CultureInfo . InvariantCulture , formatString , keysInError ));
}
foreach ( MergedCompiledCommandParameter param in parametersToRemove )
{
availablePairs . Remove ( param );
}
if ( availablePairs . Count > 0 )
{
return availablePairs ;
}
2016-07-29 13:02:49 -07:00
2016-03-30 14:20:52 -07:00
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 (
2016-07-29 13:02:49 -07:00
string cmdletName , string paramName , object paramValue ,
2016-03-30 14:20:52 -07:00
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 ))
{
2016-07-29 13:02:49 -07:00
_commandRuntime . WriteWarning (
2016-03-30 14:20:52 -07:00
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">
2016-08-28 05:44:55 +00:00
/// Previous binding exceptions that possibly causes the failure
2016-03-30 14:20:52 -07:00
/// </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 );
2016-07-29 13:02:49 -07:00
if (! DefaultParameterBindingInUse )
2016-03-30 14:20:52 -07:00
{
throw bindingException ;
}
else
{
ThrowElaboratedBindingException ( bindingException );
}
}
}
2016-07-29 13:02:49 -07:00
2016-03-30 14:20:52 -07:00
bindingException =
new ParameterBindingException (
ErrorCategory . InvalidArgument ,
this . Command . MyInvocation ,
null ,
argument ,
null ,
specifiedType ,
ParameterBinderStrings . PositionalParameterNotFound ,
"PositionalParameterNotFound" );
}
}
2016-07-29 13:02:49 -07:00
if (! DefaultParameterBindingInUse )
2016-03-30 14:20:52 -07:00
{
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>
2017-01-16 13:31:14 -08:00
///
2016-03-30 14:20:52 -07:00
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>
2017-01-16 13:31:14 -08:00
///
2016-03-30 14:20:52 -07:00
/// <param name="argumentToBind">
/// The argument containing the value to restore.
/// </param>
2017-01-16 13:31:14 -08:00
///
2016-03-30 14:20:52 -07:00
/// <param name="parameter">
/// The metadata for the parameter to restore.
/// </param>
2017-01-16 13:31:14 -08:00
///
2016-03-30 14:20:52 -07:00
/// <returns>
/// True if the parameter was restored correctly, or false otherwise.
/// </returns>
2017-01-16 13:31:14 -08:00
///
2016-07-29 13:02:49 -07:00
private bool RestoreParameter ( CommandParameterInternal argumentToBind , MergedCompiledCommandParameter parameter )
2016-03-30 14:20:52 -07:00
{
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 (
2016-07-29 13:02:49 -07:00
_commandMetadata . SupportsShouldProcess ,
2016-03-30 14:20:52 -07:00
"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 (
2016-07-29 13:02:49 -07:00
_commandMetadata . SupportsPaging ,
2016-03-30 14:20:52 -07:00
"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 (
2016-07-29 13:02:49 -07:00
_commandMetadata . SupportsTransactions ,
2016-03-30 14:20:52 -07:00
"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 (
2016-07-29 13:02:49 -07:00
_commandMetadata . ImplementsDynamicParameters ,
2016-03-30 14:20:52 -07:00
"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>
2017-01-16 13:31:14 -08:00
///
2016-03-30 14:20:52 -07:00
/// <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>
2017-01-16 13:31:14 -08:00
///
2016-03-30 14:20:52 -07:00
/// <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 )
{
2016-07-29 13:02:49 -07:00
if (! DefaultParameterBindingInUse )
2016-03-30 14:20:52 -07:00
{
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>
2017-01-16 13:31:14 -08:00
///
2016-03-30 14:20:52 -07:00
/// <param name="parameter">
/// The metadata of the parameter to check the type of.
/// </param>
2017-01-16 13:31:14 -08:00
///
2016-03-30 14:20:52 -07:00
/// <returns>
2017-01-16 13:31:14 -08:00
/// true if the parameter type is Object, ScriptBlock, derived from ScriptBlock, a
2016-03-30 14:20:52 -07:00
/// collection of ScriptBlocks, a collection of Objects, or a collection of types derived from
/// ScriptBlock.
/// False otherwise.
/// </returns>
2017-01-16 13:31:14 -08:00
///
2016-03-30 14:20:52 -07:00
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 );
2016-07-29 13:02:49 -07:00
s_tracer . WriteLine ( "IsParameterScriptBlockBindable: result = {0}" , result );
2016-03-30 14:20:52 -07:00
return result ;
}
2016-07-29 13:02:49 -07:00
2016-03-30 14:20:52 -07:00
/// <summary>
/// Binds the specified parameters to the cmdlet
/// </summary>
2017-01-16 13:31:14 -08:00
///
2016-03-30 14:20:52 -07:00
/// <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>
2017-01-16 13:31:14 -08:00
///
2016-03-30 14:20:52 -07:00
/// <param name="parameterSets">
/// The parameter set used to bind the arguments.
/// </param>
2017-01-16 13:31:14 -08:00
///
2016-03-30 14:20:52 -07:00
/// <param name="argument">
/// The argument to be bound.
/// </param>
2017-01-16 13:31:14 -08:00
///
2016-03-30 14:20:52 -07:00
/// <param name="parameter">
/// The metadata for the parameter to bind the argument to.
/// </param>
2017-01-16 13:31:14 -08:00
///
2016-03-30 14:20:52 -07:00
/// <param name="flags">
/// Flags for type coercion, validation, and script block binding.
2017-01-16 13:31:14 -08:00
///
2016-03-30 14:20:52 -07:00
/// 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>
2017-01-16 13:31:14 -08:00
///
2016-03-30 14:20:52 -07:00
/// <returns>
2017-01-16 13:31:14 -08:00
/// True if the parameter was successfully bound. False if <paramref name="flags"/>
2016-03-30 14:20:52 -07:00
/// has the flag <see cref="ParameterBindingFlags.ShouldCoerceType"/> set and the type does not match the parameter type.
/// </returns>
2017-01-16 13:31:14 -08:00
///
2016-03-30 14:20:52 -07:00
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
2017-01-16 13:31:14 -08:00
// not ScriptBlock and not Object, then we need to delay
// binding until a pipeline object is provided to invoke
2016-03-30 14:20:52 -07:00
// 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.
2016-07-29 13:02:49 -07:00
if ( _commandRuntime . IsClosed && _commandRuntime . InputPipe . Empty )
2016-03-30 14:20:52 -07:00
{
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
2016-08-02 22:25:13 -07:00
DelayedScriptBlockArgument delayedArg = argumentValue as DelayedScriptBlockArgument ??
new DelayedScriptBlockArgument { _argument = argument , _parameterBinder = this };
2016-03-30 14:20:52 -07:00
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 ;
}
2016-07-29 13:02:49 -07:00
2016-03-30 14:20:52 -07:00
/// <summary>
/// Binds the specified argument to the specified parameter using the appropriate
/// parameter binder.
/// </summary>
2017-01-16 13:31:14 -08:00
///
2016-03-30 14:20:52 -07:00
/// <param name="argument">
/// The argument to be bound.
/// </param>
2017-01-16 13:31:14 -08:00
///
2016-03-30 14:20:52 -07:00
/// <param name="parameter">
/// The metadata for the parameter to bind the argument to.
/// </param>
2017-01-16 13:31:14 -08:00
///
2016-03-30 14:20:52 -07:00
/// <param name="flags">
/// Flags for type coercion and validation.
/// </param>
2017-01-16 13:31:14 -08:00
///
2016-03-30 14:20:52 -07:00
/// <returns>
2017-01-16 13:31:14 -08:00
/// True if the parameter was successfully bound. False if <paramref name="flags"/>
2016-03-30 14:20:52 -07:00
/// has the flag <see cref="ParameterBindingFlags.ShouldCoerceType"/> set and the type does not match the parameter type.
/// </returns>
2017-01-16 13:31:14 -08:00
///
2016-03-30 14:20:52 -07:00
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 (
2016-07-29 13:02:49 -07:00
_commandMetadata . SupportsShouldProcess ,
2016-03-30 14:20:52 -07:00
"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 (
2016-07-29 13:02:49 -07:00
_commandMetadata . SupportsPaging ,
2016-03-30 14:20:52 -07:00
"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 (
2016-07-29 13:02:49 -07:00
_commandMetadata . SupportsTransactions ,
2016-03-30 14:20:52 -07:00
"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 (
2016-07-29 13:02:49 -07:00
_commandMetadata . ImplementsDynamicParameters ,
2016-03-30 14:20:52 -07:00
"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>
2017-01-16 13:31:14 -08:00
///
2016-03-30 14:20:52 -07:00
/// <exception cref="ParameterBindingException">
/// If there was an error binding the arguments to the parameters.
/// </exception>
2017-01-16 13:31:14 -08:00
///
2016-03-30 14:20:52 -07:00
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
2016-07-29 13:02:49 -07:00
if (! DefaultParameterBindingInUse )
2016-03-30 14:20:52 -07:00
{
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.
IScriptExtent argumentExtent = UnboundArguments . Count == 1
? UnboundArguments [ 0 ]. ArgumentExtent
: PositionUtilities . EmptyExtent ;
var cpi = CommandParameterInternal . CreateParameterWithArgument (
PositionUtilities . EmptyExtent , varargsParameter . Parameter . Name , "-" + varargsParameter . Parameter . Name + ":" ,
argumentExtent , valueFromRemainingArguments , false );
try
{
BindParameter ( cpi , varargsParameter , ParameterBindingFlags . ShouldCoerceType );
}
catch ( ParameterBindingException pbex )
{
// 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 make one additional attempt at converting, but only if there is a single argument of type object[].
if ( valueFromRemainingArguments . Count == 1 && valueFromRemainingArguments [ 0 ] is object [])
{
cpi . SetArgumentValue ( UnboundArguments [ 0 ]. ArgumentExtent , valueFromRemainingArguments [ 0 ]);
BindParameter ( cpi , varargsParameter , ParameterBindingFlags . ShouldCoerceType );
}
else
{
2016-07-29 13:02:49 -07:00
if (! DefaultParameterBindingInUse )
2016-03-30 14:20:52 -07:00
{
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>
2017-01-16 13:31:14 -08:00
///
2016-03-30 14:20:52 -07:00
/// <param name="outgoingBindingException">
/// Returns the underlying parameter binding exception if any was generated.
/// </param>
2017-01-16 13:31:14 -08:00
///
2016-03-30 14:20:52 -07:00
/// <exception cref="MetadataException">
/// If there was an error compiling the parameter metadata.
/// </exception>
2017-01-16 13:31:14 -08:00
///
2016-03-30 14:20:52 -07:00
/// <exception cref="ParameterBindingException">
/// If there was an error binding the arguments to the parameters.
/// </exception>
2017-01-16 13:31:14 -08:00
///
2016-03-30 14:20:52 -07:00
private void HandleCommandLineDynamicParameters ( out ParameterBindingException outgoingBindingException )
{
outgoingBindingException = null ;
2016-07-29 13:02:49 -07:00
if ( _commandMetadata . ImplementsDynamicParameters )
2016-03-30 14:20:52 -07:00
{
using ( ParameterBinderBase . bindingTracer . TraceScope (
"BIND cmd line args to DYNAMIC parameters." ))
{
2016-07-29 13:02:49 -07:00
s_tracer . WriteLine ( "The Cmdlet supports the dynamic parameter interface" );
2016-03-30 14:20:52 -07:00
IDynamicParameters dynamicParameterCmdlet = this . Command as IDynamicParameters ;
if ( dynamicParameterCmdlet != null )
{
if ( _dynamicParameterBinder == null )
{
2016-07-29 13:02:49 -07:00
s_tracer . WriteLine ( "Getting the bindable object from the Cmdlet" );
2016-03-30 14:20:52 -07:00
// 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 ());
2016-07-29 13:02:49 -07:00
s_tracer . WriteLine ( "Creating a new parameter binder for the dynamic parameter object" );
2016-03-30 14:20:52 -07:00
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 )
{
2016-07-29 13:02:49 -07:00
s_tracer . WriteLine ( "No dynamic parameter object was returned from the Cmdlet" );
2016-03-30 14:20:52 -07:00
return ;
}
if ( UnboundArguments . Count > 0 )
{
using ( ParameterBinderBase . bindingTracer . TraceScope (
"BIND NAMED args to DYNAMIC parameters" ))
{
2017-01-16 13:31:14 -08:00
// Try to bind the unbound arguments as static parameters to the
2016-03-30 14:20:52 -07:00
// dynamic parameter object.
ReparseUnboundArguments ();
UnboundArguments = BindParameters ( _currentParameterSetFlag , UnboundArguments );
}
using ( ParameterBinderBase . bindingTracer . TraceScope (
"BIND POSITIONAL args to DYNAMIC parameters" ))
{
UnboundArguments =
BindPositionalParameters (
UnboundArguments ,
_currentParameterSetFlag ,
2016-07-29 13:02:49 -07:00
_commandMetadata . DefaultParameterSetFlag ,
2016-03-30 14:20:52 -07:00
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
2017-01-16 13:31:14 -08:00
/// set is set as the current parameter set and processing can continue. If there are
2016-03-30 14:20:52 -07:00
/// more than one valid parameter sets and the unbound mandatory parameters are not
2017-01-16 13:31:14 -08:00
/// consistent across parameter sets or there is no default parameter set then a
2016-03-30 14:20:52 -07:00
/// ParameterBindingException is thrown with an errorId of AmbiguousParameterSet.
/// </summary>
2017-01-16 13:31:14 -08:00
///
2016-03-30 14:20:52 -07:00
/// <param name="validParameterSetCount">
/// The number of valid parameter sets.
/// </param>
2017-01-16 13:31:14 -08:00
///
2016-03-30 14:20:52 -07:00
/// <param name="isPipelineInputExpected">
/// True if the pipeline is open to receive input.
/// </param>
2017-01-16 13:31:14 -08:00
///
2016-03-30 14:20:52 -07:00
/// <exception cref="ParameterBindingException">
2017-01-16 13:31:14 -08:00
/// If there are multiple valid parameter sets and the missing mandatory parameters are
2016-03-30 14:20:52 -07:00
/// not consistent across parameter sets, or there is no default parameter set.
/// </exception>
2017-01-16 13:31:14 -08:00
///
2016-03-30 14:20:52 -07:00
[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 ;
}
}
}
2017-01-16 13:31:14 -08:00
// We are not expecting pipeline input
2016-03-30 14:20:52 -07:00
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
2016-08-28 05:44:55 +00:00
// parameter set and have one characteristic: NONE of them contain unbound mandatory parameters
2016-03-30 14:20:52 -07:00
//
// 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
2016-09-01 14:54:41 +00:00
_parameterSetToBePrioritizedInPipelineBinding = defaultParameterSet ;
2016-03-30 14:20:52 -07:00
}
}
// 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 )
{
2017-01-16 13:31:14 -08:00
// Since we have only one valid parameter set, add all
2016-03-30 14:20:52 -07:00
foreach ( ParameterSetPromptingData promptingSetData in promptingData . Values )
{
if (( promptingSetData . ParameterSet & commandMandatorySets ) != 0 ||
promptingSetData . IsAllSet )
{
foreach ( MergedCompiledCommandParameter mandatoryParameter in promptingSetData . NonpipelineableMandatoryParameters . Keys )
{
result . Add ( mandatoryParameter );
}
}
}
}
2016-09-01 14:54:41 +00:00
else if ( _parameterSetToBePrioritizedInPipelineBinding == 0 )
2016-03-30 14:20:52 -07:00
{
// 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.
2017-01-16 13:31:14 -08:00
//
2016-03-30 14:20:52 -07:00
// 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
// )
2017-01-16 13:31:14 -08:00
//
2016-03-30 14:20:52 -07:00
// 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>
2017-01-16 13:31:14 -08:00
///
2016-03-30 14:20:52 -07:00
/// <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 )
{
2016-09-01 14:54:41 +00:00
_parameterSetToBePrioritizedInPipelineBinding = chosenMandatorySet ;
2016-03-30 14:20:52 -07:00
}
}
}
/// <summary>
/// Update _currentParameterSetFlag to ignore the specified mandatory sets
/// </summary>
2017-01-16 13:31:14 -08:00
///
2016-03-30 14:20:52 -07:00
/// <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>
2017-01-16 13:31:14 -08:00
///
2016-03-30 14:20:52 -07:00
/// <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>
2017-01-16 13:31:14 -08:00
///
2016-03-30 14:20:52 -07:00
/// <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>
2017-01-16 13:31:14 -08:00
///
2016-03-30 14:20:52 -07:00
/// <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>
2017-01-16 13:31:14 -08:00
///
2016-03-30 14:20:52 -07:00
/// <returns>
/// The number of valid parameter sets.
/// </returns>
2017-01-16 13:31:14 -08:00
///
2016-03-30 14:20:52 -07:00
/// <exception cref="ParameterBindingException">
/// If the more than one or zero parameter sets were resolved from the named
/// parameters.
/// </exception>
2017-01-16 13:31:14 -08:00
///
2016-03-30 14:20:52 -07:00
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
2016-07-29 13:02:49 -07:00
else if ( prePipelineInput &&
2016-03-30 14:20:52 -07:00
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.
2016-07-29 13:02:49 -07:00
validParameterSetCount =
( this . BindableParameters . ParameterSetCount > 0 ) ?
2016-03-30 14:20:52 -07:00
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.
}
2016-07-29 13:02:49 -07:00
else if ( _commandMetadata . DefaultParameterSetFlag != 0 )
2016-03-30 14:20:52 -07:00
{
if ( setDefault )
{
2016-07-29 13:02:49 -07:00
_currentParameterSetFlag = _commandMetadata . DefaultParameterSetFlag ;
2016-03-30 14:20:52 -07:00
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 ;
2016-07-29 13:02:49 -07:00
2016-03-30 14:20:52 -07:00
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 )
{
2016-07-29 13:02:49 -07:00
ParameterBindingException bindingException =
2016-03-30 14:20:52 -07:00
new ParameterBindingException (
ErrorCategory . InvalidArgument ,
this . Command . MyInvocation ,
null ,
null ,
null ,
null ,
ParameterBinderStrings . AmbiguousParameterSet ,
"AmbiguousParameterSet" );
// Trace the parameter sets still active
uint currentParameterSet = 1 ;
2016-07-29 13:02:49 -07:00
2016-03-30 14:20:52 -07:00
while ( parameterSetFlags != 0 )
{
uint currentParameterSetActive = parameterSetFlags & 0x1 ;
if ( currentParameterSetActive == 1 )
{
string parameterSetName = bindableParameters . GetParameterSetName ( currentParameterSet );
2016-07-29 13:02:49 -07:00
if (! String . IsNullOrEmpty ( parameterSetName ))
2016-03-30 14:20:52 -07:00
{
ParameterBinderBase . bindingTracer . WriteLine ( "Remaining valid parameter set: {0}" , parameterSetName );
}
}
parameterSetFlags >>= 1 ;
currentParameterSet <<= 1 ;
}
2016-07-29 13:02:49 -07:00
if (! DefaultParameterBindingInUse )
2016-03-30 14:20:52 -07:00
{
throw bindingException ;
}
else
{
ThrowElaboratedBindingException ( bindingException );
}
}
/// <summary>
/// Determines if there are any unbound parameters that take pipeline input
/// for the specified parameter sets.
/// </summary>
2017-01-16 13:31:14 -08:00
///
2016-03-30 14:20:52 -07:00
/// <param name="validParameterSetFlags">
/// The parameter sets that should be checked for each unbound parameter to see
/// if it accepts pipeline input.
/// </param>
2017-01-16 13:31:14 -08:00
///
2016-03-30 14:20:52 -07:00
/// <returns>
/// True if there is at least one parameter that takes pipeline input for the
/// specified parameter sets, or false otherwise.
/// </returns>
2017-01-16 13:31:14 -08:00
///
2016-03-30 14:20:52 -07:00
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>
2016-08-28 05:44:55 +00:00
/// Checks for unbound mandatory parameters. If any are found, an exception is thrown.
2016-03-30 14:20:52 -07:00
/// </summary>
2017-01-16 13:31:14 -08:00
///
2016-03-30 14:20:52 -07:00
/// <param name="missingMandatoryParameters">
/// Returns the missing mandatory parameters, if any.
/// </param>
2017-01-16 13:31:14 -08:00
///
2016-03-30 14:20:52 -07:00
/// <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 );
}
2016-07-29 13:02:49 -07:00
2016-03-30 14:20:52 -07:00
/// <summary>
2016-08-28 05:44:55 +00:00
/// Checks for unbound mandatory parameters. If any are found and promptForMandatory is true,
2016-03-30 14:20:52 -07:00
/// the user will be prompted for the missing mandatory parameters.
/// </summary>
2017-01-16 13:31:14 -08:00
///
2016-03-30 14:20:52 -07:00
/// <param name="validParameterSetCount">
/// The number of valid parameter sets.
/// </param>
2017-01-16 13:31:14 -08:00
///
2016-03-30 14:20:52 -07:00
/// <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>
2017-01-16 13:31:14 -08:00
///
2016-03-30 14:20:52 -07:00
/// <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>
2017-01-16 13:31:14 -08:00
///
2016-03-30 14:20:52 -07:00
/// <param name="missingMandatoryParameters">
/// Returns the missing mandatory parameters, if any.
/// </param>
2017-01-16 13:31:14 -08:00
///
2016-03-30 14:20:52 -07:00
/// <returns>
/// True if there are no unbound mandatory parameters. False if there are unbound mandatory parameters
/// and promptForMandatory if false.
/// </returns>
2017-01-16 13:31:14 -08:00
///
2016-03-30 14:20:52 -07:00
/// <exception cref="ParameterBindingException">
/// If prompting didn't result in a value for the parameter (only when <paramref name="promptForMandatory"/> is true.)
/// </exception>
2017-01-16 13:31:14 -08:00
///
2016-03-30 14:20:52 -07:00
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
2016-07-29 13:02:49 -07:00
if (( Context . EngineHostInterface == null ) || (! promptForMandatory ))
2016-03-30 14:20:52 -07:00
{
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 (
PositionUtilities . EmptyExtent , entry . Key , "-" + entry . Key + ":" ,
PositionUtilities . EmptyExtent , 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 )
{
2016-07-29 13:02:49 -07:00
ParameterBinderBase . bindingTracer . WriteLine (
2016-03-30 14:20:52 -07:00
"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 ;
}
2016-07-29 13:02:49 -07:00
if (( parameters == null ) || ( parameters . Count == 0 ))
2016-03-30 14:20:52 -07:00
{
2016-07-29 13:02:49 -07:00
ParameterBinderBase . bindingTracer . WriteLine (
2016-03-30 14:20:52 -07:00
"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>
2017-01-16 13:31:14 -08:00
/// Creates a label with a Hotkey from <paramref name="parameterName"/>. The Hotkey is
2016-03-30 14:20:52 -07:00
/// <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 ();
}
2016-07-29 13:02:49 -07:00
2016-03-30 14:20:52 -07:00
/// <summary>
/// Gets the parameter set name for the current parameter set.
/// </summary>
2017-01-16 13:31:14 -08:00
///
2016-03-30 14:20:52 -07:00
internal string CurrentParameterSetName
{
get
{
string currentParameterSetName = BindableParameters . GetParameterSetName ( _currentParameterSetFlag );
2016-07-29 13:02:49 -07:00
s_tracer . WriteLine ( "CurrentParameterSetName = {0}" , currentParameterSetName );
2016-03-30 14:20:52 -07:00
return currentParameterSetName ;
}
2016-07-29 13:02:49 -07:00
}
2016-03-30 14:20:52 -07:00
/// <summary>
/// Binds the specified object or its properties to parameters
/// that accept pipeline input.
/// </summary>
2017-01-16 13:31:14 -08:00
///
2016-03-30 14:20:52 -07:00
/// <param name="inputToOperateOn">
/// The pipeline object to bind.
/// </param>
2017-01-16 13:31:14 -08:00
///
2016-03-30 14:20:52 -07:00
/// <returns>
/// True if the pipeline input was bound successfully or there was nothing
/// to bind, or false if there was an error.
/// </returns>
2017-01-16 13:31:14 -08:00
///
2016-03-30 14:20:52 -07:00
internal bool BindPipelineParameters ( PSObject inputToOperateOn )
{
bool result ;
try
{
using ( ParameterBinderBase . bindingTracer . TraceScope (
"BIND PIPELINE object to parameters: [{0}]" ,
2016-07-29 13:02:49 -07:00
_commandMetadata . Name ))
2016-03-30 14:20:52 -07:00
{
// 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
2016-08-28 05:44:55 +00:00
// if it failed some post binding verification step.
2016-03-30 14:20:52 -07:00
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
2016-08-28 05:44:55 +00:00
// if it failed some post binding verification step.
2016-03-30 14:20:52 -07:00
this . RestoreDefaultParameterValues ( ParametersBoundThroughPipelineInput );
throw ;
}
if (! result )
{
// Reset the default values
// This prevents the last pipeline object from being bound during EndProcessing
2016-08-28 05:44:55 +00:00
// if it failed some post binding verification step.
2016-03-30 14:20:52 -07:00
this . RestoreDefaultParameterValues ( ParametersBoundThroughPipelineInput );
}
return result ;
}
/// <summary>
/// Binds the pipeline parameters using the specified input and parameter set
/// </summary>
2017-01-16 13:31:14 -08:00
///
2016-03-30 14:20:52 -07:00
/// <param name="inputToOperateOn">
/// The pipeline input to be bound to the parameters.
/// </param>
2017-01-16 13:31:14 -08:00
///
2016-03-30 14:20:52 -07:00
/// <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>
2017-01-16 13:31:14 -08:00
///
2016-03-30 14:20:52 -07:00
/// <remarks>
/// The algorithm for binding the pipeline object is as follows. If any
/// step is successful true gets returned immediately.
2017-01-16 13:31:14 -08:00
///
2016-03-30 14:20:52 -07:00
/// - 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
2017-01-16 13:31:14 -08:00
///
2016-03-30 14:20:52 -07:00
/// 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.
2017-01-16 13:31:14 -08:00
///
2016-03-30 14:20:52 -07:00
/// - 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
2017-01-16 13:31:14 -08:00
///
2016-03-30 14:20:52 -07:00
/// </remarks>
2017-01-16 13:31:14 -08:00
///
2016-03-30 14:20:52 -07:00
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 ;
2016-09-01 14:54:41 +00:00
bool needToPrioritizeOneSpecificParameterSet = _parameterSetToBePrioritizedInPipelineBinding != 0 ;
2016-03-30 14:20:52 -07:00
int steps = needToPrioritizeOneSpecificParameterSet ? 2 : 1 ;
if ( needToPrioritizeOneSpecificParameterSet )
{
2016-09-01 14:54:41 +00:00
// _parameterSetToBePrioritizedInPipelineBinding is set, so we are certain that the specified parameter set must be valid,
2016-03-30 14:20:52 -07:00
// and it's not the only valid parameter set.
2016-09-01 14:54:41 +00:00
Diagnostics . Assert (( _currentParameterSetFlag & _parameterSetToBePrioritizedInPipelineBinding ) != 0 , "_parameterSetToBePrioritizedInPipelineBinding should be valid if it's set" );
validParameterSets = _parameterSetToBePrioritizedInPipelineBinding ;
2016-03-30 14:20:52 -07:00
}
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 ;
}
2016-07-29 13:02:49 -07:00
2016-03-30 14:20:52 -07:00
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
2016-09-01 14:54:41 +00:00
if ( _currentParameterSetFlag == _parameterSetToBePrioritizedInPipelineBinding ) break ;
validParameterSets = _currentParameterSetFlag & (~ _parameterSetToBePrioritizedInPipelineBinding );
2016-03-30 14:20:52 -07:00
}
}
// Now make sure we only have one valid parameter set
// Note, this will throw if we have more than one.
ValidateParameterSets ( false , true );
2016-07-29 13:02:49 -07:00
if (! DefaultParameterBindingInUse )
2016-03-30 14:20:52 -07:00
{
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 );
}
2016-07-29 13:02:49 -07:00
s_tracer . WriteLine ( "aParameterWasBound = {0}" , aParameterWasBound );
2016-03-30 14:20:52 -07:00
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.
//
2017-01-16 13:31:14 -08:00
// We loop the unbound parameters in reversed order, so that we can move
2016-03-30 14:20:52 -07:00
// items from the unboundParameters collection to the boundParameters
2017-01-16 13:31:14 -08:00
// collection as we process, without the need to make a copy of the
2016-03-30 14:20:52 -07:00
// unboundParameters collection.
//
2017-01-16 13:31:14 -08:00
// We used to make a copy of UnboundParameters and loop from the head of the
2016-03-30 14:20:52 -07:00
// list. Now we are processing the unbound parameters from the end of the list.
2017-01-16 13:31:14 -08:00
// This change should NOT be a breaking change. The 'validParameterSets' in
2016-03-30 14:20:52 -07:00
// 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 ;
}
2016-07-29 13:02:49 -07:00
if ( parameterBindingException != null )
2016-03-30 14:20:52 -07:00
{
2016-07-29 13:02:49 -07:00
if (! DefaultParameterBindingInUse )
2016-03-30 14:20:52 -07:00
{
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 ;
}
2016-07-29 13:02:49 -07:00
if ( parameterBindingException != null )
2016-03-30 14:20:52 -07:00
{
2016-07-29 13:02:49 -07:00
if (! DefaultParameterBindingInUse )
2016-03-30 14:20:52 -07:00
{
throw parameterBindingException ;
}
else
{
ThrowElaboratedBindingException ( parameterBindingException );
}
}
}
return bindResult ;
}
/// <summary>
/// Used for defining the state of the binding state machine.
/// </summary>
2017-01-16 13:31:14 -08:00
///
2016-03-30 14:20:52 -07:00
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>
2017-01-16 13:31:14 -08:00
///
2016-03-30 14:20:52 -07:00
/// <param name="inputToOperateOn">
/// The input to the script block.
/// </param>
2017-01-16 13:31:14 -08:00
///
2016-03-30 14:20:52 -07:00
/// <param name="thereWasSomethingToBind">
/// Returns True if there was a ScriptBlock to invoke and bind, or false if there
/// are no ScriptBlocks to invoke.
/// </param>
2017-01-16 13:31:14 -08:00
///
2016-03-30 14:20:52 -07:00
/// <returns>
/// True if the binding succeeds, or false otherwise.
/// </returns>
2017-01-16 13:31:14 -08:00
///
2016-03-30 14:20:52 -07:00
/// <exception cref="ArgumentNullException">
/// if <paramref name="inputToOperateOn"/> is null.
/// </exception>
2017-01-16 13:31:14 -08:00
///
2016-03-30 14:20:52 -07:00
/// <exception cref="ParameterBindingException">
/// If execution of the script block throws an exception or if it doesn't produce
/// any output.
/// </exception>
2017-01-16 13:31:14 -08:00
///
2016-03-30 14:20:52 -07:00
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 (
2016-08-02 23:09:38 -07:00
null ,
2016-03-30 14:20:52 -07:00
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 . ParameterExtent , argument . ParameterName , "-" + argument . ParameterName + ":" ,
argument . ArgumentExtent , 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>
2017-01-16 13:31:14 -08:00
///
2016-03-30 14:20:52 -07:00
/// <param name="parameterSetFlags">
/// The valid parameter set flags.
/// </param>
2017-01-16 13:31:14 -08:00
///
2016-03-30 14:20:52 -07:00
/// <returns>
/// The number of valid parameter sets in the parameterSetFlags.
/// </returns>
2017-01-16 13:31:14 -08:00
///
2016-03-30 14:20:52 -07:00
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>
2017-01-16 13:31:14 -08:00
///
2016-03-30 14:20:52 -07:00
/// <param name="name">
/// The name of the parameter to get the default value of.
/// </param>
2017-01-16 13:31:14 -08:00
///
2016-03-30 14:20:52 -07:00
/// <returns>
/// The value of the parameter specified by name.
/// </returns>
2017-01-16 13:31:14 -08:00
///
2016-03-30 14:20:52 -07:00
/// <exception cref="ParameterBindingParameterDefaultValueException">
/// If the parameter binder encounters an error getting the default value.
/// </exception>
2017-01-16 13:31:14 -08:00
///
2016-03-30 14:20:52 -07:00
internal object GetDefaultParameterValue ( string name )
{
MergedCompiledCommandParameter matchingParameter =
BindableParameters . GetMatchingParameter (
2016-07-29 13:02:49 -07:00
name ,
2016-03-30 14:20:52 -07:00
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
2016-07-29 13:02:49 -07:00
2016-03-30 14:20:52 -07:00
/// <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
2017-01-16 13:31:14 -08:00
// parameter) in warningSet when the corresponding warnings are written out, so they won't get
2016-03-30 14:20:52 -07:00
// 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
2016-09-01 14:54:41 +00:00
private uint _parameterSetToBePrioritizedInPipelineBinding = 0 ;
2016-03-30 14:20:52 -07:00
/// <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
2016-07-29 13:02:49 -07:00
ShouldProcessParameters shouldProcessParameters = new ShouldProcessParameters ( _commandRuntime );
2016-03-30 14:20:52 -07:00
// 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
2016-07-29 13:02:49 -07:00
PagingParameters pagingParameters = new PagingParameters ( _commandRuntime );
2016-03-30 14:20:52 -07:00
// 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
2016-07-29 13:02:49 -07:00
TransactionParameters transactionParameters = new TransactionParameters ( _commandRuntime );
2016-03-30 14:20:52 -07:00
// 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>
2017-01-16 13:31:14 -08:00
///
2016-03-30 14:20:52 -07:00
internal ReflectionParameterBinder CommonParametersBinder
{
get
{
if ( _commonParametersBinder == null )
{
// Construct a new instance of the user feedback parameters object
2016-07-29 13:02:49 -07:00
CommonParameters commonParameters = new CommonParameters ( _commandRuntime );
2016-03-30 14:20:52 -07:00
// 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
2016-08-28 05:44:55 +00:00
// and when to use the evaluated argument.
2016-03-30 14:20:52 -07:00
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>
2017-01-16 13:31:14 -08:00
///
2016-03-30 14:20:52 -07:00
private readonly Dictionary < MergedCompiledCommandParameter , DelayedScriptBlockArgument > _delayBindScriptBlocks =
new Dictionary < MergedCompiledCommandParameter , DelayedScriptBlockArgument >();
/// <summary>
2017-01-16 13:31:14 -08:00
/// A collection of the default values of the parameters.
2016-03-30 14:20:52 -07:00
/// </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>
2017-01-16 13:31:14 -08:00
///
2016-03-30 14:20:52 -07:00
/// <param name="parameterValue">
/// The value to bind to the parameter
/// </param>
2017-01-16 13:31:14 -08:00
///
2016-03-30 14:20:52 -07:00
/// <param name="parameter">
/// The parameter to bind the value to.
/// </param>
2017-01-16 13:31:14 -08:00
///
2016-03-30 14:20:52 -07:00
/// <param name="flags">
/// Parameter binding flags for type coercion and validation.
/// </param>
2017-01-16 13:31:14 -08:00
///
2016-03-30 14:20:52 -07:00
/// <returns>
2017-01-16 13:31:14 -08:00
/// True if the parameter was successfully bound. False if <paramref name="flags"/>
2016-03-30 14:20:52 -07:00
/// specifies no coercion and the type does not match the parameter type.
/// </returns>
2017-01-16 13:31:14 -08:00
///
2016-03-30 14:20:52 -07:00
/// <exception cref="ParameterBindingParameterDefaultValueException">
/// If the parameter binder encounters an error getting the default value.
/// </exception>
2017-01-16 13:31:14 -08:00
///
2016-07-29 13:02:49 -07:00
private bool BindPipelineParameter (
2016-03-30 14:20:52 -07:00
object parameterValue ,
MergedCompiledCommandParameter parameter ,
ParameterBindingFlags flags )
{
bool result = false ;
if ( parameterValue != AutomationNull . Value )
{
2016-07-29 13:02:49 -07:00
s_tracer . WriteLine ( "Adding PipelineParameter name={0}; value={1}" ,
2016-03-30 14:20:52 -07:00
parameter . Parameter . Name , parameterValue ?? "null" );
// Backup the default value
BackupDefaultParameter ( parameter );
// Now bind the new value
CommandParameterInternal param = CommandParameterInternal . CreateParameterWithArgument (
PositionUtilities . EmptyExtent , parameter . Parameter . Name , "-" + parameter . Parameter . Name + ":" ,
PositionUtilities . EmptyExtent , 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 (
PositionUtilities . EmptyExtent , name , "-" + name + ":" ,
PositionUtilities . EmptyExtent , value ,
false ));
}
/// <summary>
/// Backs up the specified parameter value by calling the GetDefaultParameterValue
/// abstract method.
2017-01-16 13:31:14 -08:00
///
2016-03-30 14:20:52 -07:00
/// This method is called when binding a parameter value that came from a pipeline
/// object.
/// </summary>
2017-01-16 13:31:14 -08:00
///
2016-03-30 14:20:52 -07:00
/// <exception cref="ParameterBindingParameterDefaultValueException">
/// If the parameter binder encounters an error getting the default value.
/// </exception>
2017-01-16 13:31:14 -08:00
///
2016-07-29 13:02:49 -07:00
private void BackupDefaultParameter ( MergedCompiledCommandParameter parameter )
2016-03-30 14:20:52 -07:00
{
if (! _defaultParameterValues . ContainsKey ( parameter . Parameter . Name ))
{
object defaultParameterValue = GetDefaultParameterValue ( parameter . Parameter . Name );
_defaultParameterValues . Add (
parameter . Parameter . Name ,
CommandParameterInternal . CreateParameterWithArgument (
PositionUtilities . EmptyExtent , parameter . Parameter . Name , "-" + parameter . Parameter . Name + ":" ,
PositionUtilities . EmptyExtent , defaultParameterValue ,
false ));
}
}
/// <summary>
/// Replaces the values of the parameters with their initial value for the
/// parameters specified.
/// </summary>
2017-01-16 13:31:14 -08:00
///
2016-03-30 14:20:52 -07:00
/// <param name="parameters">
/// The parameters that should have their default values restored.
/// </param>
2017-01-16 13:31:14 -08:00
///
2016-03-30 14:20:52 -07:00
/// <exception cref="ArgumentNullException">
/// If <paramref name="parameters"/> is null.
/// </exception>
2017-01-16 13:31:14 -08:00
///
2016-07-29 13:02:49 -07:00
private void RestoreDefaultParameterValues ( IEnumerable < MergedCompiledCommandParameter > parameters )
2016-03-30 14:20:52 -07:00
{
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 ();
2016-07-29 13:02:49 -07:00
ParameterBindingException bindingException =
2016-03-30 14:20:52 -07:00
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>
2017-01-16 13:31:14 -08:00
///
2016-03-30 14:20:52 -07:00
/// <remarks>
/// Check for the keys' formats and make it versionable
/// </remarks>
2017-01-16 13:31:14 -08:00
///
2016-03-30 14:20:52 -07:00
/// <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 ; }
2016-07-29 13:02:49 -07:00
2016-03-30 14:20:52 -07:00
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;
2017-01-16 13:31:14 -08:00
///
2016-03-30 14:20:52 -07:00
/// For parameterName:
2016-08-28 05:44:55 +00:00
/// 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);
2016-03-30 14:20:52 -07:00
/// When the name is not enclosed by quotes, the index returned should be the length of the key.
2017-01-16 13:31:14 -08:00
///
2016-03-30 14:20:52 -07:00
/// </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
}
}