How to distinguish between empty argument and zero-value argument in Powershell? - powershell

I want to be able to pass a single int through to a powershell script, and be able to tell when no variable is passed. My understanding was that the following should identify whether an argument is null or not:
if (!$args[0]) { Write-Host "Null" }
else { Write-Host "Not null" }
This works fine until I try to pass 0 as an int. If I use 0 as an argument, Powershell treats it as null. Whats the correct way to be able to distinguish between the argument being empty or having a zero value?

You can just test $args variable or $args.count to see how many vars are passed to the script.
Another thing $args[0] -eq $null is different from $args[0] -eq 0 and from !$args[0].

If users like me come from Google and want to know how to treat empty command line parameters, here is a possible solution:
if (!$args) { Write-Host "Null" }
This checks the $args array. If you want to check the first element of the array (i.e. the first cmdline parameter), use the solution from the OP:
if (!$args[0]) { Write-Host "Null" }

If the variable is declared in param() as an integer then its value will be '0' even if no value is specified for the argument. To prevent that you have to declare it as nullable:
param([AllowNull()][System.Nullable[int]]$Variable)
This will allow you to validate with If ($Variable -eq $null) {}

Related

using if statement to check if variable is null returns incorrectly

I have a powershell script i have created and there is step where it tries to import a module if it is not already imported.
Try {
Import-Module -Name 'ModuleName' -ErrorAction Stop -ErrorVariable ModFail
}
Catch {
Write-Error "Module failed to be loaded."
}
later in the script i am trying to check if module failed to be imported but checking the error variable. For other variables not being set through the errorvariable parameter i just use the below to check if its null.
If ($null -eq $var) {
Do stuff
}
But doing so with the variable i have set using errorvariable is not working.
if ($null -ne $Modfail) {
Do stuff
}
when testing if the variable is actually null the above evaluates to true. This is the opposite of what i want. When i run the variable it is indeed null and running
$modfail | Gm
fails because it is empty. Why is this happening? If i set the variable outside of the errorvariable parameter or do not set it all it returns correct. Even if it had whitespace it should return as string correct when piping to Get-Member?
The -ErrorVariable common parameter, like all -*Variable common parameters, reports the collected output from the relevant stream as a System.Collections.ArrayList instance.
(Unless there's a syntactic problem with the invocation), a System.Collections.ArrayList is always created and assigned to the specified variable, even if no objects are output to the targeted stream; that is, in the case of -ErrorAction, if no errors occur, an empty System.Collections.ArrayList is created - which is distinct from $null.
Therefore, if ($null -ne $Modfail) ... is not the right test, as it will always return $true (any object is by definition not $null, irrespective of its type or content).
Instead, use if ($Modfail.Count -gt 0) ... or, more simply, rely on the fact that an empty collection is implicitly coerced to $false[1]: if ($Modfail) ...
As for:
$modfail | Get-Member fails because it is empty. Why is this happening?
Whenever you send a collection through the pipeline, it is enumerated.
Enumerating an empty array enumerates nothing, in which case Get-Member (rightfully) complains about missing input.
(If the array isn't empty, you should see information about System.Management.Automation.ErrorRecord, the type of the elements stored in $ModFail).
If you want to inspect the collection itself with Get-Member, use Get-Member -InputObject $Modfail - you'll see that it is of type System.Collections.ArrayList (as a non-generic collection, its elements have no predetermined type).
[1] Note that single-element collections may be coerced to $false as well, depending on the value of that single element; however, with the collections created by -ErrorVariable, which contain System.Management.Automation.ErrorRecord instances when nonempty, that is not a concern; for background information, see the bottom section of this answer.
You can check the truthiness of the error variable instead:
if( !$ModFail ){
# Do stuff if the module loaded correctly
}
You don't want to check if( $null -eq $ModFail ) because the -ErrorVariable is always set to a collection, albeit an empty one if no error occurs. The collection itself is not null, so this check will always return $true.
Since it is a collection, you can also check the Count of the -ErrorVariable to determine if any errors exist in the collection. Using $ModFail in your case:
if( $ModFail.Count -eq 0 ){
# Do stuff if the module loaded correctly
}

Beginner Problems with Equals Operator

everyone trying to learn Powershell off and on and I'm stuck on this problem. I cannot seem to find an equals operator that this code will accept at the = true portion .
Ive tried -eq, =, ==, and === .
Trying to get the Msg box to pop up if this Test-path command returns a true condition.
$wshell = New-Object -ComObject Wscript.Shell
If( Test-Path 'C:\wmw\~$test.xlsx' **= True)**
{
$wshell.Popup("Hey $Env:ComputerName This file is in use!",0,"test")}
else
{$wshell.Popup("Hey $Env:ComputerName This file is not in use!",0,"test")}
First of all, the literal for true is $true in PowerShell. And the operator for equality comparison is -eq. Then there is the issue that parameters to cmdlets start with - and you'd need to wrap the command in parentheses. Otherwise -eq would be interpreted as a (non-existent) parameter to Test-Path. So putting that all together:
If( (Test-Path 'C:\wmw\~$test.xlsx') -eq $True) { ... }
or, since if just needs a value that can be coerced to a boolean you don't even need the explicit comparison in most cases:
if (Test-Path 'C:\wmw\~$test.xlsx') { ... }
One hint for future exploration of the shell: Read the error messages. Most of the time they are helpful.
Omitting the parentheses and using -eq tells you about the fact that it's interpreted as a parameter:
Test-Path : A parameter cannot be found that matches parameter name 'eq'.
Same with = which is interpreted as a parameter value here:
Test-Path : A positional parameter cannot be found that accepts argument '='.
Using parentheses correctly and using -eq breaks the parser, admittedly:
You must provide a value expression following the '-eq' operator.
Unexpected token 'True' in expression or statement.
Missing closing ')' after expression in 'if' statement.
Unexpected token ')' in expression or statement.
Using parentheses and = is helpful again:
The assignment expression is not valid. The input to an assignment operator must be an object that is able to accept assignments, such as a variable or a property.

Choose from multiple return values

When I have a function, which uses several Write-Output commands and returns single number, how can I get number value in function caller code?
As far as I got, line
[int] $var = Get-MyNumber(...)
gets me the error
Cannot convert the "System.Object[]" value of type "System.Object[]" to type "System.Int32"".
Probably because PowerShell returns an array of objects (containing Write-Output messages) back to caller code, where the assignment to the [int]-typed variable fails. Got that.
Now, how can I tell PowerShell that I'm interested in only a single value from those returned from the function, which is typed as [int].
BTW, I DON'T want to choose output by indexing the return array as I could mess up the indexes in the return array simply by adding another Write-Output line. (Which will happen sooner or later due to code maintenance).
Code
function f1() {
Write-Output "Lala"
return 5
}
[int] $x = f1
Write-Output $x
Results in the same error.
I see from your edit that you are using Write-Output to display a status message.
You should use Write-Host for that, or if you were using an advanced function, I would recommend using Write-Verbose and calling the function with -Verbose when you want to see the messages (see about_CommonParameters).
Updated code:
function f1() {
Write-Host "Lala"
return 5
}
[int] $x = f1
Write-Host $x
Advanced Function Example
function f1 {
[CmdletBinding()]
param()
Write-Verbose "Lala"
return 5
}
$x = f1
# The "Lala" message will not be seen.
$x = f1 -Verbose
# The "Lala" message will be seen.
Why Write-Output seems to work outside of a function:
Write-Output passes the input object to the caller. In the case of code that is executed directly in the host, and not in a function or cmdlet, the caller is the host, and the host decides what to do with it. In the case of powershell.exe (or ISE), it displays it.
Write-Host on the other hand, always writes to the host; it doesn't pass anything back to the caller.
Also note that Write-Output is, basically, optional. The following lines are equivalent:
Write-Output $x
$x

Passing a string with a function and always getting $null variable

I am using the following in the start of a function that is meant to query a computer for various wmi objects. If just get_volumes is ran alone it should use 127.0.0.1. It seems that when I run get_volumes without any string this it passes right on to the else segment. What is the proper or a better way to accomplish this?
PS> function get_volumes([string]$a){
if ($a -eq $null){
write-host 'Using localhost'
$a = '127.0.0.1'
}else{ write-host 'Using' $a}
}
PS>get_volumes
Using
Thanks
$a is an empty string and not $null. Try this instead:
function Get-Volumes([string]$IPAddress = '127.0.0.1'){
Write-Host "Using $IPAddress"
}
Note that you can set a default value for when the user doesn't supply an argument for a parameter.

Conditional logic no longer working as expected?

Its been a long day and I think I'm going mad. I wanted to test for a file and generate an email if none existed. Here it is pared down to its most minimal:
> IF('False' -eq (Test-Path D:\Scripts\SFTP\Import\*)){ECHO "SEND EMAIL"}
> SEND EMAIL
__________________________________________________________________________
> IF((Test-Path D:\Scripts\SFTP\Import\*) -eq 'False'){ECHO "SEND EMAIL"}
>
Why doesn't the second command work?
I've tried running the Test-Path outside of the 'IF' statement into a variable and then testing against that, again it doesn't work.
If I simply run the 'Test-Path' command I get a boolean 'False' as expected. I've used conditional logic in this way before and its worked.
What am I doing wrong?
The reason is this. In the first one you have a string as the first operand of the comparison. This forces PS to coerce the second operand to a string if possible. In this case that means calling the .ToString() method of the boolean which would return the 'False' string (if the boolean is actually false of course). In the second case though, you are presenting a boolean as the first operand, so the string is being coerced to a boolean. Obviously it is not working. To avoid these issues, use the builtin $false (or $true) variable. Personally I would suggest just negating the Test-Path. Here are a couple of ways that should work:
if( -NOT (Test-Path D:\Scripts\SFTP\Import\*)){
if( (Test-Path D:\Scripts\SFTP\Import\*) -eq $false){
For the coercing rules of powershell
'False' -eq (Test-Path D:\Scripts\SFTP\Import\*)
the second value of comparision is evaluated as [string]
here
(Test-Path D:\Scripts\SFTP\Import\*) -eq 'False'
the second value of comparison can't be evaluated as [Bool] then it fails.
For bool comparin is optima use the automatic variable $false and $true