Powershell function with Parameters throwing null exception - powershell

This script is throwing a null exception and I am not certain why that is the case...
Function StopServices{
Param
(
$ServiceName,
$Remoteserver
)
write-host($Remoteserver)
write-host($ServiceName)
[System.ServiceProcess.ServiceController]$service = Get-Service -Name $ServiceName -ComputerName $Remoteserver
}
the write-host writes the variable. The Get-Service -ComputerName method throws this exception:
powershell cannot validate argument on parameter 'computername' the argument is null or empty
I am wondering what they are talking about, Neither is empty...
StopServices("DUMMY","VALUES")
Neither of those are empty. Why is it throwing that exception?

Unlike most languages, PowerShell does not use parenthesis to call a function.
This means three things:
("DUMMY","VALUES") is actually being interpreted as an array. In other words, you are only giving StopServices one argument instead of the two that it requires.
This array is being assigned to $ServiceName.
Due to the lack of arguments, $Remoteserver is assigned to null.
To fix the problem, you need to call StopServices like this:
PS > StopServices DUMMY VALUES

Actually, $RemoteServer is null.
StopServices("Dummy", "Values") isn't doing what you think it's doing - PowerShell doesn't take arguments to functions in the same way that other programming languages do. PowerShell is interpreting the syntax you're using as an expression to create an array with two values in it ("DUMMY" and "VALUES), and to store that array in $ServiceName, leaving $RemoteServer as $null.
One of the following examples will give you the behavior you're after:
StopServices "Dummy" "Values"
-or-
StopServices -ServiceName "Dummy" -RemoteServer "Values"

You're calling the function incorrectly. You should be calling it like this:
StopServices "DUMMY" "VALUES"
Or, if you want to be more clear:
StopServices -ServiceName DUMMY -Remoteserver VALUES
The way you are passing the parameters to the function, PowerShell interprets ("DUMMY", "VALUES") as an array, which would get assigned to $ServiceName, leaving $Remoteserver null.

Related

How to provide options to script blocks?

I'm trying to learn about script blocks at https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_script_blocks?view=powershell-6.
I want to send a script block to an event registration with a command-line option to the registration command, like this:
$Block = {
Param($option)
Write-Host "The option was $option"
if ($option==ABC) {
Write-Host "ABC was specified"
}
}
But when I test this by appending
Invoke-Command -ScriptBlock $Block -option ABC
I get the error
Invoke-Command : A parameter cannot be found that matches parameter name 'option'.
When in doubt, read the documentation:
-ArgumentList
Supplies the values of local variables in the command. The variables in the command are replaced by these values before the command is run on the remote computer. Enter the values in a comma-separated list. Values are associated with variables in the order that they are listed. The alias for ArgumentList is "Args".
The values in ArgumentList can be actual values, such as "1024", or they can be references to local variables, such as "$max".
To use local variables in a command, use the following command format:
{param($<name1>[, $<name2>]...) <command-with-local-variables>} -ArgumentList <value> -or- <local-variable>
The "param" keyword lists the local variables that are used in the command. The ArgumentList parameter supplies the values of the variables, in the order that they are listed.
The first element from the -ArgumentList array becomes the value of the first named parameter in the scriptblock, the second array element becomes the value of the second named parameter, and so on.
Basically, your statement should look like this:
Invoke-Command -ScriptBlock $Block -ArgumentList 'ABC'
Also, the syntax of the comparison operation in your scriptblock is wrong. The equality comparison operator in PowerShell is -eq, not ==.
Remember that you can always check the documentation: https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/invoke-command?view=powershell-6
$Block = {Param($option)
Write-Host "The option was $option"
if ($option -eq "ABC") // -eq instead of ==, string in quotations
{Write-Host "ABC was specified"}
}
Invoke-Command -ScriptBlock $Block -ArgumentList "ABC"
If there are more arguments you can list them in order. Using script blocks isn't something you want to do very often. It's better to create a function (https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_functions?view=powershell-6)
You can use the call operator on scriptblocks too, and just provide the arguments after a space or -option:
& $block abc
The option was abc
ABC was specified
And you can later assign the scriptblock to a function. That's what a function is.
$function:myfunc = $block

PowerShell: Error "A positional parameter cannot be found that accepts argument ..."

I have two PowerShell Scripts. The one is an initial script which does some work on JSON files and also has some constants in form of an array.
The second one is a function within a separate file.
In my script initial1.ps1 i have the following code:
$IDs=#("02921114-654b-4c28-a9d7-2ebd9ab0ada3",
"9045c61c-55bc-45b0-0000-ec9858b24867",
"011b0c6d-5678-4aaa-a833-e62111103f0a")
. 'D:\...\drop\Add-Folder.ps1' -AdlsAccountName $Accountname `
-PrincipalIds #IDs `
-Path $folderToCreate.path `
-AadGroupName $aadRoleName
My PowerShell script file named 'Add-Folder.ps1` looks like:
[CmdletBinding()]
Param(
[Parameter(Mandatory=$True,Position=1)]
[string] $AdlsAccountName,
[Parameter(Mandatory=$True,Position=2)]
[string[]] $PrincipalIds,
[Parameter(Mandatory=$True,Position=3)]
[string] $Path,
[Parameter(Mandatory=$True,Position=4)]
[string] $AadGroupName
)
Write-Host "Hello from 'Add-AdlsFolderWithPermissions.ps1'!"
Write-Host $AdlsAccountName
Write-Host $PrincipalIds
Write-Host $Path
Write-Host $AadGroupName
But when i execute it i get the following error:
##[error]A positional parameter cannot be found that accepts argument '9045c61c-55bc-45b0-0000-ec9858b24867'.
Why happens this error or how can i pass the array of IDs to my function?
Use $IDs instead of #IDs. Otherwise the array will be expanded and attempted to be passed as multiple arguments. This is called splatting.
You can use this to collect either positional arguments to a command in an array, or named arguments in a hashtable, and then invoke the command with that splatted variable. It's never necessary or correct to use it when you actually want to pass an array as a single argument.
Using # with variable name causes parameter splatting, i.e. values in an array are being used as consecutive parameters for the script.

"Cannot bind parameter because Param2 is specified more than once"

I am trying to call a PS script via batch file, like so
Powershell.exe -file "C:\Scripts\Blah\Blah\Blah.ps1" -webUID "usernameValue" -webPWD "passwordValue" -Param "param value" -Param2 "param 2 value"
The issue seems to be the batch file is confusing Param and Param2. It thinks I am setting Param2 twice however Param and Param2 are separate parameters altogether. Has anyone experienced this? Is there perhaps a way to explicitly state the param names? Thanks
Param block
# Parameters
Param
(
[string]$WebUID,
[string]$WebPWD,
[string]$Param,
[string]$Param2
)
In an effort to support concise command-line use, PowerShell's "elastic syntax" allows specifying unambiguous prefix substrings of parameter names so that you only need to type as much of a parameter name as is necessary to identify it without ambiguity;
e.g., typing -p to refer to -Path is enough, if no other parameters start with p.
However, an exact match is always recognized, so that specifying -Param in your case should unambiguously match the -Param parameter, even though its full name happens to be a prefix substring of different parameter -Param2.
If the problem were an issue of ambiguity (it isn't), you'd see a different error message. For instance, were you to use the ambiguous -Para, you'd see:
Parameter cannot be processed because the parameter name 'para' is ambiguous. Possible matches include: -Param -Param2.
Instead, the wording of your error message suggests that the exact same parameter name - -Param2 - was indeed specified more than once - even though your sample code doesn't show that.
I've tested the behavior in PSv2 and PSv5.1 / 6.0 alpha 10 - it's conceivable, however, that other versions act differently due to a bug. Do let us know.
Consider an alternative approach:
If you invoked your script from within PowerShell, you could use a single, array-valued parameter - e.g. [string[]] $Params - and then simply pass as many parameters as needed, comma-separated, without needing to specify a distinct parameter name for each value.
Sadly, when invoking a script from outside of PowerShell, this approach won't work, because passing arrays isn't supported from the outside.
There is a workaround, however:
Declare the array-valued parameter decorated with [parameter(ValueFromRemainingArguments=$true)]
Invoke the script with the parameters as a space-separated list at the end of the command.
Applied to your scenario:
If your script defined its parameters as follows:
Param
(
[string]$WebUID,
[string]$WebPWD,
[parameter(ValueFromRemainingArguments=$true)]
[string[]] $Params
)
You could then invoke your script as follows:
Powershell.exe -file "C:\Scripts\Blah\Blah\Blah.ps1" `
-webUID "usernameValue" `
-webPWD "passwordValue" `
"param value" "param 2 value"
and $Params would receive an array of values: $Params[0] would receive param value, and $Params[1] would receive param 2 value.
Note that when calling from outside of PowerShell:
you must not use parameter name -Params in the invocation - just specify the values at the end.
you must not use , to separate the values - use spaces.
I'm no guru, but this looks like it's related to "Partial Parameters" and "Parameter Completion". See this article for more information.
Simply changing Param to Param1 should fix the issue.

How do I enforce command line argument checking in PowerShell

Here's my script (test.ps1):
[CmdLetBinding()]
Param
(
[parameter(Mandatory=$true)][string]$environment,
[switch][bool]$continue=$true
)
Write-Host $environment
Write-Host $continue
Question:
If I invoke this script by giving an argument which is a substring of the parameter I specified in the script like this: PS> .\test.ps1 -envi:blah, PowerShell doesn't seem to check the argument name. I want PowerShell to enforce parameter spelling, i.e., it should only accept -environment which matches the parameter name in the script. For anything else, it should raise an exception. Is that doable? How do I do that?
Thanks.
It's not pretty, but it will keep you from using anything except -environment as a parameter name.
Param
(
[parameter(Mandatory=$true)][string]$environment,
[parameter()]
[ValidateScript({throw "Invalid parameter. 'environment' required."})]
[string]$environmen,
[switch][bool]$continue=$true
)
Write-Host $environment
Write-Host $continue
}
Edit: As Matt noted in his comment the automatic disambiguation will force you to specify enough of the parameter name to find a unique substring match. What I'm doing here is basically giving it a parameter that satisfies all but the last character to prevent using any substring up to the last character (because it's ambiguous), and throwing an error to prevent you from using that.
And, FWIW, that could well be the ugliest parameter validation I've ever done but I don't have any better ideas right now.

Powershell: Colon in commandlet parameters

What's the deal with Powershell commandlet switch parameters that require a colon?
Consider Exchange 2010 management shell cmdlet Move-ActiveMailboxDatabase. The Confirm switch is a System.Management.Automation.SwitchParameter and must be used like so,
Move-ActiveMailboxDatabase -Confirm:$false
Without the colon the command fails to recognize the don't confirm switch like so,
Move-ActiveMailboxDatabase -Confirm $false
Why is that? What's the difference the colon makes there? Why Exchange2010 seems to be about the only thing I've noticed this behavior?
I've browsed through Powershell in Action and Powershell 2.0, but didn't find anything about this syntax. Scope resolution and .Net object access uses are documented on those books though.
My Google-fu found an article which claims that it explicitly forwards switch parameter values, but fails to explain what that is about.
When you do:
Move-ActiveMailboxDatabase -Confirm $false
you are not saying Confirm parameter accepts the $false. You are saying -Confirm and also passing an (separate) argument to the cmdlet with value $false.
Since Confirm is a switch, just the presence of -Confirm means it is true. Absence of -Confirm means it is false.
Let me give you a script example:
param([switch]$test)
write-host Test is $test
If you just run the script without any arguments / paramters i.e .\script.ps1 you get output:
Test is False
If you run it as .\script.ps1 -test, the output is
Test is True
If you run it as .\script.ps1 -test $false, the output is
Test is True
If you run it as .\script.ps1 -test:$false the output is
Test is False
It is in scenarios where the value for a switch variable itself has to be determined from another variable that the : is used.
For example, consider the script:
param ([boolean]$in)
function func([switch] $test){
write-host Test is $test
}
func -test:$in
Here if you run it as .\script.ps1 -in $false, you get
Test is false
If you weren't able to use the :, you would have had to write it as:
if($in){ func -test}
else { func }
The colon can be used with every parameter value but is more special in the case of switch parameters. Switch parameters don't take values, they are either present ($true) or absent ($false).
Imagine you have a function like this:
function test-switch ([string]$name,[switch]$force) { ... }
And you call it like so:
test-switch -force $false
Since switch parameters are either present or not, $false would actually bind to the Name parameter. So, how do you bind a value to a switch parameter? With the colon:
test-switch -force:$false
Now the parameter binder knows which parameter the value goes to.