Should I use colons for all arguments of a function/script call? - powershell

I recently started using PowerShell, and noticed that I could pass argument values using a space between the argument name and value, or using a colon, like this:
MyFunction -Parameter value
or
MyFunction -Parameter:value
I started using the colon because it differentiates the line a bit more (for readability reasons), but from what I've seen, most people do not use it.
I've read a bit also about the differences between these approaches when working with switch typed arguments, that normally do not need values. In that situation, one needs to use the colon, or else the command will not work. This is another reason why I'm leaning towards using the colon for every parameter, for consistency reasons.
Is there something else I should keep in mind?

Generally speaking, when I need to execute a function with a switch parameter set to false, I simply omit the switch. That's the design intent of a switch parameter. The only time I think I would ever use the colon in a parameter is when I need to programmatically determine the value of a switch.
For example, let's say I need to get a regular directory listing on even days, and a recursive directory listing on odd days:
Get-ChildItem -Path $Path -Recurse:$((Get-Date).Day % 2 -eq 1) | ForEach-Object {...}
Beyond that, I personally wouldn't bother with the colon unless it significantly added to the readability of a given statement. It's not a commonly used syntax, so people who read your code later are more likely to be confused by it.

In general I would leave the colon off. Only use it in the situation of setting switch a parameter (typically when you want to pass a variable to it, like -Verbose:$someVariable.
Basically, I think you should be consistent with the more accepted style, which is what I've described.
If you really want to set all parameters in an internally consistent way which allows for variables for switch parameters, and is an accepted (though less known) way of passing parameters, consider splatting like so:
$params = #{
'Param1' = $value1
'Param2' = 5
'WhatIf' = $true
}
Some-Cmdlet #params

Related

In Powershell how to generate an error on invalid flags and switches that are not listed in the param statement?

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?
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.

What am I allowed to name a function in Powershell?

PS > function ]{1}
PS > ]
1
PS >
PS
Why does this work?
What else can I name a function? All I've found so far that works is * and ].
You can name it almost anything. You can even include newlines and emoji* in the name.
function Weird`nFunctionの名前😀 { Write-Host hey }
$c = gcm Weird*
$c.Name
& $c
Escaping helps with lots of things like that:
function `{ { Write-Host cool }
`{
function `0 { Write-Host null }
gci function:\?
I'll add that this is true for variables too, and there's a syntax that removes the need to do most escaping in the variable name: ${varname} (as opposed to $varname).
With that, you could easily do:
${My variable has a first name,
it's
V
A
something
R,
whatever I dunno
🤷} = Get-Process
You'll note that if you then start typing like $MyTAB it will tab complete in a usable way.
To (somewhat) answer why this should work, consider that the variable names themselves are just stored in .Net strings. With that in mind, why should there be a limit on the name?
There will be limits on how some of these names can be used in certain contexts, because the parser will not understand what to do with it if the names don't have certain characters escaped, but literal parsing of PowerShell scripts are not the only way to use functions or variables or other language constructs, as I've shown some examples of.
Being less limiting also means being able to support other languages and cultures by having wide support for character sets.
To this end, here's one more thing that might surprise you: there are many different characters to represent the same or similar things that we take for granted in code, like quotation marks for example.
Some (human) languages or cultures just don't use the same quote characters we do in English, don't even have them on the keyboard. How annoying would it be to type code if you have to keep switching your keyboard layout or use ALT codes to quote strings?
So what I'm getting at here is that PowerShell actually does support many quote characters, for instance, what do you think this might do:
'Hello’
Pretty obvious it's not the "right" set of quotes on the right side. But surprisingly, this works just fine, even though they aren't the same character.
This does have important implications if you're ever generating code from user input and want to avoid sneaky injection attacks.
Imaging you did something like this:
Invoke-Expression "echo '$($userMsg -replace "'","''")'"
Looks like you took care of business, but now imagine if $userMsg contained this:
Hi’; gci c: -recurse|ri -force -whatif;'
For what it's worth, the CodeGeneration class is aware of this stuff ;)
Invoke-Expression "echo '$([System.Management.Automation.Language.CodeGeneration]::EscapeSingleQuotedStringContent($userMsg))'"
* PowerShell Console doesn't have good support for Unicode, even though the language does. Use ISE to better see the characters.

What is the purpose of the ${} syntax/operator? [duplicate]

This question already has answers here:
What is the meaning of ${} in powershell?
(4 answers)
Closed 5 years ago.
Edit: My additional thoughts on the functionality of the operator-
It appears to implement the IContentCmdletProvider interface which applies to the env:, function:, and drive: PSDrives. (which is probably how it also interacts with an implicit variable: psdrive). This interface utilizes the Get-Content and Set-Content cmdlets.
I can't find documentation from Microsoft on this feature (it's also really hard to search for symbols in search engines). I've seen some very weird behaviors that can be accomplished with ${}. For example:
${C:\Temp\Filename.txt} = 'This writes to the file!'
$ExampleString = ${C:\Temp\Filename.txt} #this gets file content!?
In some automatic modules, I also see function parameter variables declared with this syntax ${Message} and I'm not sure if I should change it to conform with a coding standard or if the syntax actually does something significant (in this example, Jeffrey Snover's MetaProgramming module). I've used the syntax myself calling variables in string expansion, but that's the only use-case I know of.
So, what gives with ${}?
So ${} has two different purposes. One of them is variable declaration, as it enables you to enclose any special characters in your variable name:
${special var with spaces}
But also allows you to use it in a larger string like:
$Var="middle"
Write-Host "Before${Var}After"
Another use cases, as you are suggesting is that it is able to read paths.
$Var = ${C:\Temp\Filename.txt}
is the same as
$Var = Get-Content -Path 'C:\Temp\Filename.txt'
But also to write to a path:
${C:\Temp\Filename.txt} = 'This writes to the file!'
is the same as
'This writes to the file!' | Set-Content -Path 'C:\Temp\Filename.txt'

What is the meaning of ${} in powershell?

I have a script where function parameters are expressed like this:
param(
${param1},
${param2},
${param3}
)
What does it mean? I have been unable to find documentation on this.
What's the point of writing parameters that way instead of the more usual
param(
$param1,
$param2,
$param3
)
?
#MikeZ's answer is quite correct in explaining the example in the question, but as far as addressing the question title, there is actually more to say! The ${} notation actually has two uses; the second one is a hidden gem of PowerShell:
That is, you can use this bracket notation to do file I/O operations if you provide a drive-qualified path, as defined in the MSDN page Provider Paths.
(The above image comes from the Complete Guide to PowerShell Punctuation, a one-page wallchart freely available for download, attached to my recent article at Simple-Talk.com.)
They are both just parameter declarations. The two snippets are equivalent. Either syntax can be used here, however the braced form allows characters that would not otherwise be legal in variable names. From the PowerShell 3.0 language specification:
There are two ways of writing a variable name: A braced variable name, which begins with $, followed by a curly bracket-delimited set of one or more almost-arbitrary characters; and an ordinary variable name, which also begins with $, followed by a set of one or more characters from a more restrictive set than a braced variable name allows. Every ordinary variable name can be expressed using a corresponding braced variable name.
From about_Variables
To create or display a variable name that includes spaces or special characters, enclose the variable name in braces. This directs Windows PowerShell to interpret the characters in the variable name literally.
For example, the following command creates and then displays a variable named "save-items".
C:\PS> ${save-items} = "a", "b", "c"
C:\PS> ${save-items}
a
b
c
They are equivalent. It's just an alternative way of declaring a variable.
If you have characters that are illegal in a normal variable, you'd use the braces (think of it as "escaping" the variablename).
There is one additional usage.
One may have variable names like var1, var2, var11, var12, var101, etc.
Regardless if this is desirable variable naming, it just may be.
Using brackets one can precisely determine what is to be used:
assignment of $var11 may be ambiguous, using ${var1}1 or ${var11} leaves no room for mistakes.

Build up a string to be passed to call operator

I need to build a string that is actually a command-line, and then execute the contents of that command-line. I'd hoped the call operator (&) would help, but it appears not. Here is a simple contrived example. The following works as expected, it pings a website:
$command = "ping"
$website = "www.bbc.co.uk"
& $command $website
however if I change it to this:
$command = "ping"
$website = "www.bbc.co.uk"
$cmd = "$command $website"
& $cmd
I get an error:
The term 'ping www.bbc.co.uk' is not recognized as the name of a
cmdlet, function, script file, or operable program.
Is there a way to dynamically build up a command-line as a string, and then execute it?
Yes, but you need to use Invoke-Expression (which is just like eval), instead of the call operator. Note that you also need to ensure that all your quoting is correct in that case. E.g.
$cmd = "'$command' '$website'"
would work in your trivial example, unless $command or $website contained single quotes. The problem here is essentially that everything you put into the string is subject to the usual parsing rules of PowerShell.
Generally, if you can, stay as far away from Invoke-Expression as you can. There are a few problems that need it, but invoking external programs ... not so much.
A much better alternative, especially if you have an arbitrary number of arguments, is to just collect the arguments in an array and use the splat operator (note the # in the code example below):
$command = 'ping'
$arguments = '-t','www.bbc.co.uk'
&$command #arguments
This ensures that arguments are properly quoted when necessary and generally avoids a lot of headaches you're going to get with Invoke-Expression.
(Side note: Whenever you have a problem in PowerShell and think »Oh, I'm just going to use a string«, it's often time to rethink that. This includes handling file names, or command lines. PowerShell has objects, reducing it to the capabilities of earlier shells just yields the same pain you have elsewhere too, e.g. multiple levels of escaping, sometimes with different syntaxes, etc. And most of the time there are better ways of solving the problem.)