Greater-than PowerShell operator giving inaccurate results - powershell

For some reason, when I debug - $debug is equal to 1, yet when the debugger hits if(numUsers -gt 2), it enters the if block...
function numUsers()
{#(query user /server:$server).Count - 1
}
$debug = numUsers
if(numUsers -gt 2)
{#true
}
else
{#Nothing
}
Am I using the -gt operator properly? Am I missing something really obvious?
Appreciate any insight

The -gt 2 are being seen as arguments to the numUsers call as written.
You can see that by looking at the value of $args in your numUsers function.
You need to tell powershell that you want to call numUsers on its own for this to work.
if ((numUsers) -gt 2) {
"gt 2"
} else {
"lt 2"
}

Related

Elegant way of setting default value for variable in Powershell 6.0?

I have the following, which works but looks clunky:
if($config.contentDir){
$contentDir = $config.contentDir
} else {
$contentDir = "contents"
}
Is there a nicer way of doing this? I have seen this answer here, but it isn't exactly "nicer". Just wondering if 6.0 brought any improvements?
I'm likely to be handling a large amount of config options, so it's going to get fairly messy.
This is a little shorter...
$contentDir = if ( $config.contentDir ) { $config.contentDir } else { "contents" }
You could also define an iif function:
function iif {
param(
[ScriptBlock] $testExpr,
[ScriptBlock] $trueExpr,
[ScriptBlock] $falseExpr
)
if ( & $testExpr ) {
& $trueExpr
}
else {
& $falseExpr
}
}
Then you could shorten to this:
$contentDir = iif { $config.contentDir } { $config.contentDir } { "contents" }
As an aside, it looks like the next version of PowerShell will support the ternary operator (see https://devblogs.microsoft.com/powershell/powershell-7-preview-4/), so in the future, you'll be able to write something like:
$contentDir = $config.contentDir ? $config.contentDir : "contents"
Update:
Null-coalescing operators were introduced in PowerShell (Core) 7.0 (along with a ternary operator), which enables the following v7+ solution:
$contentDir = $config.contentDir ?? 'content'
PowerShell v6- solutions:
What you'e looking for is null-coalescing, which PowerShell doesn't have as of v7.0.0-preview.4.
For now, this will have to do:
$contentDir = if ($null -eq $config.contentDir) { 'content' } else { $config.contentDir }
Note: $null is deliberately placed on the LHS of -eq to unambiguously test for $null, because as the RHS it would act as a filter if the value to test happens to be array-valued.
An adaptation of Lee Daily's array-based answer enables a more concise solution:
$contentDir = ($config.ContentDir, 'content')[$null -eq $config.ContentDir]
Use of the ternary operator (conditional), which will be implemented in v7.0, enables a similarly concise equivalent:
$contentDir = $null -eq $config.contentDir ? 'content' : $config.contentDir
However, all these approaches have the following undesirable aspects:
They require an explicit reference to $null; note that if ($config.ContentDir) - i.e. coercing the value to a Boolean - may work with strings, but is not generally robust, because non-$null values such as 0 can evaluate to $false too.
$config.contentDir, the value to test for $null, must be accessed twice, which can have side effects.
Defining a custom function named, say, ??, can address these problems:
# Custom function that emulates null-coalescing.
function ?? ($PossiblyNull, $ValueIfNull) {
if ($null -eq $PossiblyNull) { $ValueIfNull } else { $PossiblyNull }
}
$contentDir = ?? $config.contentDir 'content'
However, such a custom function has down-sides:
The down-sides of custom functions are:
You need to include or import them into in every piece of code you want to use them in.
If you choose familiar name such as ??, the placement of operands can get confusing, because you must (invariably) place them differently in PowerShell, given the implementation as a function (e.g., a ?? b in C# vs. ?? $a $b in PowerShell) - especially once true null-coalescing gets implemented in PowerShell: see next section.
And, of course, calling a function adds overhead.
If this GitHub feature request is implemented, you'll be able to use true null-coalescing, which is both the most concise solution and avoids the aforementioned undesirable aspects:
# Hopefully soon
$contentDir = $config.contentDir ?? 'content'
A related feature also proposed in the linked GitHub issue is null-conditional assignment, $config.ContentDir ?= 'content'
as Bill_Stewart showed, there is a ternary operator due in ps7. however, you can get something similar by using a two-item array and taking advantage of how PoSh will coerce values -- $False gives 0, $True gives 1.
$Config = [PSCustomObject]#{
ContentDir = 'SomewhereElse'
}
#$Config.ContentDir = ''
$ContentDir = #('contents', $Config.ContentDir)[[bool]$Config.ContentDir]
$ContentDir
output with line 4 commented out = SomewhereElse
output with line 4 enabled = contents
Sort of like '||' in bash. If the first one is false or null, it will do the second one.
[void](($contentDir = $config.contentDir) -or ($contentDir = "contents"))

If Else Statement is not working in Else side using PowerShell

I use if else in my powershell script.
if ($match.Groups.Count) {
while ($match.Success) {
Write-Host ("Match found: {0}" -f $match.Value)
$match = $match.NextMatch()
}
}
else {
Write-Host "Not Found"
}
in the if side, it works, but in the else side, It cannot return "Not Found" . It does not show any error.
PetSerAl, as countless times before, has provided the crucial pointer in a comment:
Perhaps surprisingly, the [System.Text.RegularExpressions.Match] instance returned by the static [regex]::Match() method (or its instance-method counterpart) contains 1 element in its .Groups property even if the matching operation didn't succeed[1], so that, assuming an instance stored in $match, $match.Groups.Count always returns $true.
Instead, use the .Success property to determine if a match was found, as you already do in the while loop:
if ($match.Success) {
while ($match.Success) {
"Match found: {0}" -f $match.Value
$match = $match.NextMatch()
}
} else {
"Not Found"
}
Note that I've removed the Write-Host calls, because Write-Host is generally the wrong tool to use, unless the intent is explicitly to write to the display only, thereby bypassing PowerShell's output streams and thus the ability to send the output to other commands, capture it in a variable or redirect it to a file.
[1] [regex]::Match('a', 'b').Groups.Count returns 1, even though the match clearly didn't succeed.

If statement - Four variables, only run if one true

I have the following which doesn't allow both variables to be enabled (boolean true value):
If (($Variable1) -and ($Variable2)) {
Write-Warning "Both variables have been enabled. Modify script to enable just one."
Pause
Break
}
This works great, however, how would I ensure only one is ever enabled when 4 possible variables exist? I'm thinking a combination of -and & -or?
You can add the boolean values and check their count:
If (([bool]$Variable1 + [bool]$Variable2 + [bool]$Variable3) -ne 1) {
...
}
but of course you have to make sure that these can actually be cast to boolean.
That's what "exclusive or" (xor) is for:
If ($Variable1 -xor $Variable2 -xor $Variable3) {
....
}
About logical operators in Powershell
Cannot think of a way to do this that avoids using a counter. You have to check the value of each variable and keep count of how many are $true.
$trueCount = 0
($variable1, $variable2, $variable3, $variable4) | % { if ($_ ) { $trueCount++} }
if ($trueCount -eq 1) {
write-host "only one variable true"
}
else {
write-host "condition not met"
}

How to check to see if what went through the hashtable matched anything

I have a hashtable and I'm trying to make an if statement right now that will check to see if what went through the hashtable matched anything within it.
$netVerConv = #{
'v2.0' = "lib\net20";
'v3.0' = "lib\net30";
'v3.5' = "lib\net35";
'v4.0' = "lib\net40";
'v4.5' = "lib\net45";
}
$target = $netVerConv.Get_Item($netVerShort)
if () {
}
Above is the area of my code I'm working with, the target variable runs $netVerShort through the $netVerConv hashtable using a Get_Item command. The if statement that I've laid the framework for would check to see if netVerShort matched anything within the hashtable and if it didn't it will stop the program, which I know how to do with a simple exit command.
The other suggestions will work in your specific scenario but in general you should use the ContainsKey() method to see if a key exists in the hashtable. For instance the hashtable value could be $null or $false in which case, testing via the result of Get_Item() or more simply Item[$netVerShort], will return a false negative. So I recommend this approach for testing existence of a key in a hashtable. It is also more obvious what your intent is:
if (!$netVerConv.ContainsKey($netVerShort) {
...
}
How about this:
if( $target -eq $null ) {
echo "Didn't Match"
exit
}
Another option:
if (-not ($target = $netVerConv.Get_Item($netVerShort)))
{
Write-Error "Version $netVerShort not found"
Exit
}
You could also re-factor that as a Switch
$target =
Switch ($netVerShort)
{
'v2.0' {"lib\net20"}
'v3.0' {"lib\net30"}
'v3.5' {"lib\net35"}
'v4.0' {"lib\net40"}
'v4.5' {"lib\net45"}
Default {
Write-Error "Version $netVerShort not found"
Exit
}
}

Test for function empty return value

(edit) an empty function does in fact return a null, something led me astray, leaving the question here as a reminder...
Where $null does not cut it. My scenario is simplest explained as a snippet, I have a function someone wrote
function PositiveInt([string]$value) {
if ([int]($value) -ge 0) {[int]$value}
}
Which returns nothing in the negative case or throws if the input is not a numeric string. How can I test for the return in the negative case? I tried this
if ($null -eq (PositiveInt -1)) {
write-host "not positive :)"
}
But it obviously won't work, because no return value is not equal to $null . How can I test if a function or expression simply does not return anything at all? Don't try fix my contrived function, its the absence of "$empty" (sic) I want to do a test for, but cannot because powershell binding does not mandate that a function or even an expression actually returns anything at all?
# hacky unclear solution proposed
$temp = #(PositiveInt -1)
if ($temp.length -eq 0) {
write-host "not positive :)"
}
I drew some inspiration from this In Powershell what is the idiomatic way of converting a string to an int? posting. but I'm asking a different question I believe.
Aside from the casting to array workaround, is there a cleaner way?
(edit) Have to admit something environmental or in the actual code context was at play, in PS 5.1 a function call returns $null as #AnsgarWiechers pointed out.
You could use the -is or -isnot type operators to check if the result successfully converted to an integer:
if( (PositiveInt '-1') -isnot [int] ) {
write-host 'negative'
}
You could try the following:
if ((PositiveInt -1) -eq $null) {
write-host "not positive :)"
}
do you like this?
if ( [string]::IsNullOrEmpty( (PositiveInt -1) ) ) {
write-host "not positive :)"
}