Reputation: 406
I recently tried to write a PowerShell script (.ps1) that would be called from a Windows batch file (.bat) (or potentially cmd shell, or AutoHotKey script) - which would pass parameters into the .ps1 script for it to use (to display a toast notification). Thanks to the instructions on ss64.com, I have used $args
to do this kind of thing in the past, however for some reason I could access the parameters this way (despite passing parameters, $args[0] = ''
(empty string) and $args.Count = 0
) so eventually had to remove all the $args
code, and replace it with Param()
script instead.
I'm still not quite sure why, but thought this is something I should get to the bottom of before I try to write my next script...
Code Example 1: Args (un-named parameters)
ToastNotificationArgs.ps1
-------------------------
Write-Debug "The script has been passed $($args.Count) parameters"
If (!$args[0]) { # Handle first parameter missing }
If (!$args[1]) { # Handle second parameter missing }
Import-Module -Name BurntToast
New-BurntToastNotification -Text "$args[0], $args[1]"
^ I thought the above code was correct, but like I say, I kept struggling to access the parameters for some reason and could not figure out why. (If anyone can spot what I was doing wrong, please shout!)
Is $args[] a valid approach? I assume so given it's use of ss64.com, but maybe there are some pitfalls / limitations I need to be aware of?
Code Example 2: Param (named parameters)
ToastNotificationParams.ps1
---------------------------
Param(
[Parameter(Mandatory=$false, Position=0, ValueFromPipeline=$true)] [string]$Title,
[Parameter(Mandatory=$false, Position=1, ValueFromPipeline=$true)] [string]$Message
)
Import-Module -Name BurntToast
New-BurntToastNotification -Text "$Title, $Message"
^ This was the only way to get my script working in the end. However when I passed the parameters in, my calling cmd script sent the parameters by position i.e. (pwsh.exe -File "ToastNotificationParams.ps1" "This is the title" "Message goes here"
) rather than by named pairs. (Not sure if this is best practice, but is how my script was initially intended to be used to left it for now).
While Param() got my script working this time (and I also realise the inherent dangers of position-based parameters), there are times when a position-based approach might be necessary (e.g. the number of parameter is unknown)...
Code Example 3: Hybrid
ToastNotificationMix.ps1
------------------------
Param(
[Parameter(Mandatory=$false, Position=0, ValueFromPipeline=$true)] [string]$Title
)
Import-Module -Name BurntToast
For ( $i = 1; $i -lt $args.count; $i++ ) {
New-BurntToastNotification -Text "$Title, $args[i]"
}
Is something like this valid?.. If not (or there is a better solution), any help would be greatly appreciated!
Thanks in advance!
Upvotes: 2
Views: 1079
Reputation: 440471
The automatic $args
variable is only available in simple (non-advanced) functions / scripts. A script automatically becomes an advanced one by using the [CmdletBinding()]
attribute and/or at least one per-parameter [Parameter()]
attribute.
Using $args
allows a function/script to accept an open-ended number of positional arguments, usually instead of, but also in addition to using explicitly declared parameters.
But it doesn't allow passing named arguments (arguments prefixed by a predeclared target parameter name, e.g., -Title
)
For robustness, using an advanced (cmdlet-like) function or script is preferable; such functions / scripts:
[Parameter(ValueFromRemainingArguments)]
.Explicitly defined parameters are positional by default, in the order in which they are declared inside the param(...)
block.
[CmdletBinding(PositionalBinding=$false)]
,Position
property of the individual [Parameter()]
attributes.When you call a PowerShell script via the PowerShell's CLI's -File
parameter, the invocation syntax is fundamentally the same as when calling script from inside PowerShell; that is, you can pass named arguments and/or - if supported - positional arguments.
,
-separated elements) is not supported.-Command
/ -c
CLI parameter instead-File
vs. `-Command.To put it all together:
ToastNotificationMix.ps1
:
[CmdletBinding(PositionalBinding=$false)]
Param(
[Parameter(Position=0)]
[string]$Title
,
[Parameter(Mandatory, ValueFromRemainingArguments)]
[string[]] $Rest
)
Import-Module -Name BurntToast
foreach ($restArg in $Rest) {
New-BurntToastNotification -Text "$Title, $restArg"
}
You can then call your script from cmd.exe
as follows, for instance (I'm using pwsh.exe
, the PowerShell (Core) CLI; for Windows PowerShell, use powershell.exe
):
Positional binding only:
:: "foo" binds to $Title, "bar" to $Rest
pwsh -File ./ToastNotificationMix.ps1 foo bar
Mix of named and positional binding:
:: "bar" and "baz" both bind to $Rest
pwsh -File ./ToastNotificationMix.ps1 -Title foo bar baz
Upvotes: 2