2

Trying to get get param(...) to be have some basic error checking... one thing that puzzles me is how to detect invalid switch and flags that are not in the param list?

    function abc {
        param(
            [switch]$one,
            [switch]$two
        )
    }

When I use it:

PS> abc -One -Two
# ok... i like this

PS> abc -One -Two -NotAValidSwitch
# No Error here for -NotAValidSwitch?  How to make it have an error for invalid switches?
mklement0
  • 382,024
  • 64
  • 607
  • 775
Bimo
  • 5,987
  • 2
  • 39
  • 61
  • 3
    Add `[cmdletbinding()]` to your function ..... – Santiago Squarzon Mar 10 '22 at 17:29
  • 1
    What Santi said is the what you're after. That essentially turns it into an *Advanced Function*; so does *metadata* such as parameter attributes - `[parameter(Mandatory=$true)]`. I like your name btw. I used to love Adventure Time:) – Abraham Zinala Mar 10 '22 at 17:39
  • 1
    https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_functions_advanced – zett42 Mar 10 '22 at 17:44
  • As for the substance of your answers (which should really be just _one_ answer; FWIW, I didn't down-vote, but I don't think they deserve up-votes either): Yes, there are legitimate reasons to avoid defining functions as advanced (cmdlet-like) ones, but avoiding the warning about irregularly formed names is not one of them - see my updated answer. Finally, re accepting: if you truly believe your own answer(s) best address your question, consolidate them into one and accept your own. – mklement0 Mar 15 '22 at 15:56

1 Answers1

5

As Santiago Squarzon, Abraham Zinala, and zett42 point out in comments, all you need to do is make your function (or script) an advanced (cmdlet-like) one:

  • explicitly, by decorating the param(...) block with a [CmdletBinding()] attribute.

  • and/or implicitly, by decorating at least one parameter variable with a [Parameter()] attribute.

   function abc {
        [CmdletBinding()] # Make function an advanced one.
        param(
            [switch]$one,
            [switch]$two
        )
   }

An advanced function automatically ensures that only arguments that bind to explicitly declared parameters may be passed.
If unexpected arguments are passed, the invocation fails with a statement-terminating error.

Switching to an advanced script / function has side effects, but mostly beneficial ones:

  • You gain automatic support for common parameters, such as -OutVariable or -Verbose.

  • You lose the ability to receive unbound arguments, via the automatic $args variable variable (which is desired here); however, you can declare a catch-all parameter for any remaining positional arguments via [Parameter(ValueFromRemainingArguments)]

  • To accept pipeline input in an advanced function or script, a parameter must explicitly be declared as pipeline-binding, via [Parameter(ValueFromPipeline)] (objects as a whole) or [Parameter(ValueFromPipelineByPropertyName)] (value of the property of input objects that matches the parameter name) attributes.

For a juxtaposition of simple (non-advanced) and advanced functions, as well as binary cmdlets, see this answer.


If you do not want to make your function an advanced one:

Check if the automatic $args variable - reflecting any unbound arguments (a simpler alternative to $MyInvocation.UnboundArguments) - is empty (an empty array) and, if not, throw an error:

   function abc {
        param(
            [switch]$one,
            [switch]$two
        )
        if ($args.Count) { throw "Unexpected arguments passed: $args" }
   }

Potential reasons for keeping a function a simple (non-advanced) one:

  • To "cut down on ceremony" in the parameter declarations, e.g. for pipeline-input processing via the automatic $input variable alone.

  • Generally, for simple helper functions, such as for module- or script-internal use that don't need support for common parameters.

  • When a function acts as a wrapper for an external program to which arguments are to be passed through and whose parameters (options) conflict with the names and aliases of PowerShell's common parameters, such as -verbose or -ov (-Out-Variable).

What isn't a good reason:

  • When your function is exported from a module and has an irregular name (not adhering to PowerShell's <Verb>-<Noun> naming convention based on approved verbs) and you want to avoid the warning that is emitted when you import that module.

  • First and foremost, this isn't an issue of simple vs. advanced functions, but relates solely to exporting a function from a module; that is, even an irregularly named simple function will trigger the warning. And the warning exists for a good reason: Functions exported from modules are typically "public", i.e. (also) for use by other users, who justifiable expect command names to follow PowerShell's naming conventions, which greatly facilitates command discovery. Similarly, users will expect cmdlet-like behavior from functions exported by a module, so it's best to only export advanced functions.

  • If you still want to use an irregular name while avoiding a warning, you have two options:

    • Disregard the naming conventions altogether (not advisable) and choose a name that contains no - character, e.g. doStuff - PowerShell will then not warn. A better option is to choose a regular name and define the irregular names as an alias for it (see below), but note that even aliases have a (less strictly adhered-to) naming convention, based on an official one or two-letter prefix defined for each approved verb, such as g for Get- and sa for Start- (see the approved-verbs doc link above).

    • If you do want to use the <Verb>-<Noun> convention but use an unapproved verb (token before -), define the function with a regular name (using an approved verb) and also define and export an alias for it that uses the irregular name (aliases aren't subject to the warning). E.g., if you want a command named Ensure-Foo, name the function Set-Foo, for instance, and define
      Set-Alias Ensure-Foo Set-Foo. Do note that both commands need to be exported and are therefore visible to the importer.

  • Finally, note that the warning can also be suppressed on import, namely via Import-Module -DisableNameChecking. The downside of this approach - aside from placing the burden of silencing the warning on the importer - is that custom classes exported by a module can't be imported this way, because importing such classes requires a using module statement, which has no silencing option (as of PowerShell 7.2.1; see GitHub issue #2449 for background information.

mklement0
  • 382,024
  • 64
  • 607
  • 775