2

Question: Looking to either change the error message displayed to say they need the '-passed, -warning, or -failed' to be included with the cmdlet parameters or to not care if the three are missing and to write the message (assuming its there).

Explanation: Created a function that takes in three arguments: a message, 2nd message, and state of message (pass, fail, warn). I get a default error message of "Parameter cannot be resolved using the specified named parameters." This happens regardless of if you're passing a message through:

PS C:\> Write-Message;
Write-Message : Parameter set cannot be resolved using the specified named parameters.
...
//or
PS C:\> Write-Message -Message "Hello World";

But if you were to enter in the 'status' parameter it would work without any issues:

PS C:\> Write-Message -Message "Hello World" -Passed;
Hello World 

('Hello World' is supposed to be the color Green here, but Stack Overflow doesn't have that feature yet)

Function in question:

Function Write-Message {
    param(
        [string]$Message,
        [string]$MessageTwo,
        [Parameter(Mandatory=$false, ValueFromPipelineByPropertyName = $true, ParameterSetName ="Passed")][switch]$Passed,
        [Parameter(Mandatory=$false, ValueFromPipelineByPropertyName = $true, ParameterSetName ="Warning")][switch]$Warning,
        [Parameter(Mandatory=$false, ValueFromPipelineByPropertyName = $true, ParameterSetName ="Failed")][switch]$Failed
    );
    $NewLineNeeded = [string]::IsNullOrEmpty($MessageTwo);

    if ($Passed){
        $color = 'green';
    }
    if($Warning){
        $color = 'yellow';
    }
    if($Failed){
        $color = 'red';
    }

    if($Passed -or $Warning -or $Failed){
        if(!$NewLineNeeded){
            Write-Host $MessageOne -NoNewline;
            Write-Host $MessageTwo -ForegroundColor $color;
        } else {
            Write-Host $Message -ForegroundColor $color;
        }  
    }
}
mklement0
  • 382,024
  • 64
  • 607
  • 775
Treasure Dev
  • 560
  • 4
  • 8
  • 2
    As an aside: `;` is only ever _required_ in order to terminate a statement in PowerShell if you want to place _multiple statements_ on the _same line_. – mklement0 Nov 12 '19 at 16:28

2 Answers2

3

Add a "default" parameter set to resolve this. You don't actually have to use the parameter set in your code, so even using something like "Default" would work without additional changes to your code. I've run into this myself when writing more complex cmdlets where I use parameter sets for mutually exclusive parameters, but some parameters are parameter-set agnostic.

Function Write-Message {
  [CmdletBinding(DefaultParameterSetName="Default")]
  Param(
...

It seems that Powershell gets confused about which parameter set is in use when you use only parameters without a parameter set, and you also have defined parameter sets in your cmdlet.

You only need to do this all of the following conditions are true:

  • Have explicitly defined two or more parameter sets
  • Have not already bound one of your existing parameter sets as the default
  • Have (and invoke your routine with) arguments that aren't bound to either set

As explained in mklement0's answer, along with some under-the-hood information of what happens, this is possibly a bug of sorts and this solution is the workaround.

codewario
  • 19,553
  • 20
  • 90
  • 159
3

To complement Bender the Greatest' effective solution and helpful explanation with more background information:

Parameters that are not tagged as belonging to at least one parameter set, via their [Parameter()] attributes:

  • are implicitly considered part of all defined parameter sets.

  • are assigned the automatic __AllParameterSets parameter set.

You can verify this as follows with your Write-Message function:

(Get-Command Write-Message).Parameters.GetEnumerator() | 
  Select-Object @{ n='ParameterName'; e = { $_.Key } },
    @{ n='ParameterSets'; e = { $_.Value.ParameterSets.GetEnumerator().ForEach('Key') } }

The above yields (common parameters omitted):

ParameterName       ParameterSets
-------------       -------------
Message             __AllParameterSets
MessageTwo          __AllParameterSets
Passed              Passed
Warning             Warning
Failed              Failed
# ...

Important: Once you tag a parameter as belonging to a parameter set, it belongs only to this parameter set, and if it also needs to belong to others, you need multiple, separate [Parameter(ParameterSetName = '...')] attributes - one for each parameter set of interest.

When a given function or script is invoked with a given set of parameter values, PowerShell tries to unambiguously infer what parameter set to apply and reports the selected parameter set in $PSCmdlet.ParameterSetName.

If a single parameter set cannot be unambiguously inferred, a statement-terminating error occurs; that is, invocation of the function fundamentally fails (but execution of the script continues by default). This can happen for one of two reasons:

  • Mutually exclusive parameters were passed: parameters belonging to different parameter sets were specified (parameters who do not have at least one associated parameters set in common).

  • An ambiguous combination of parameters was passed: it is not clear what parameter set to select, because multiple ones could apply.

The error message is the same in both cases:

Parameter set cannot be resolved using the specified named parameters. One or more parameters issued cannot be used together or an insufficient number of parameters were provided.

As an aside: An additional error may occur if you specified only a parameter name's prefix (which PowerShell generally supports), and that prefix is ambiguous, i.e. it matches multiple parameters; e.g., -Out is ambiguous, because it matches both -OutVariable and -OutBuffer:

Parameter cannot be processed because the parameter name 'Out' is ambiguous. Possible matches include: -OutVariable -OutBuffer.

If you specify no parameters or only specify parameters that aren't explicitly marked as belonging to a parameter set and you haven't defined a default parameter set, PowerShell selects the __AllParameterSets set and reflects that in $PSCmdlet.ParameterSetName.
Surprisingly, however, that only works if there's only one explicitly defined parameter set.

With two or more explicit parameter sets - as in your case - PowerShell considers the this case ambiguous and reports an error.

This surprising behavior is discussed in this GitHub issue.

The workaround, as shown in Bender's answer, is to define a default parameter set via the [CmdletBinding()] attribute placed above the param(...) block; e.g.: [CmdletBinding(DefaultParameterSetName='Default')]

This parameter set is implicitly created and its name can be freely chosen; no actual parameters haven to be tagged as belonging to it.

Now, when you specify no parameters or only those without explicit parameter-set tagging, the invocation succeeds, and $PSCmdlet.ParameterSetName reflects the name of the default parameter set.

mklement0
  • 382,024
  • 64
  • 607
  • 775
  • 2
    Bummer, thought I was missing out on something clever. Yeah that `'...', ...` I felt implied an array of parameter set names, but I guess that would have been a function call so it wasn't an element separator, it was an argument separator. – codewario Nov 21 '19 at 14:49
  • 1
    Yeah, you can't fit a PowerShell array in there without also enclosing it in `(...)` - and if you do, PowerShell simply _stringifies_ the array to a single string in this case, because the `.ParameterSetName` attribute property is a string _scalar_ (`[string]`) - see https://learn.microsoft.com/en-us/dotnet/api/system.management.automation.parameterattribute – mklement0 Nov 21 '19 at 14:53