Correct writing of Powershell Logical Expression? - powershell

Hi I'm a newbie in Powershell scripting..
I just wrote a simple function to detect if a file existed called isMarked() which takes the argument of file path string and return true/false.
function isMarked{
param($mark_name)
return Test-Path "$mark_name"
}
I tried and this function works correctly.
However, a weird result occurred when I tried to use the logical operator to combine the results.
if(isMarked(".test") -and !(isMarked(".another_test")))
where when I tried individually,
isMarked(".test") returns True
isMarked(".another_test") returns True
But when I combined the logical expression
(isMarked(".test") -and !(isMarked(".another_test"))
which should be False, it gives me a "True".....
I'm not sure what goes wrong. Any suggestions?
Thanks

Reason for this is that the 2 sides being compared must be values. If you have expressions that need to be evaluated first before the comparison you need to surround them with parentheses. You have done this with your 2nd expression but have omitted to do so with your first.
Try instead
( isMarked(".test") ) -and !( isMarked(".another_test"))
or as other have stated in comments
(isMarked ".test") -and !(isMarked ".another_test")
Either way will work. Placing the arguments inside parenthesis is not the problem here as stated in some of the comments.

Related

PowerShell string format different behaviour within function

While using powershell I struggle to build up a filename from two variables. When I originally creaded the powershell script, it was working fine. Now I have tried to move some repeatable steps into a function, but the string behaviour is different.
MWE:
$topa = "ABC"
$topb = "XYZ"
function Test-Fun{
param(
$a,
$b
)
echo "$($a)H$($b).csv"
}
echo "$($topa)H$($topb).csv"
Test-Fun($topa, $topb)
The output on my system is
ABCHXYZ.csv
ABC XYZH.csv
Originally, I wanted to use an underscore instead of H and thought that is causing issues, but its not. What did I miss or rather what is the difference between string expansion within a function and outside of it?
You are calling Test-Func wrong. The comma after $topa will create an array, so you basically pass []"ABC", "XYZ" as an array to $a. In that case $b is empty!
You can easily fix this by removing the comma (also the parentheses are not necessary):
Test-Fun $topa $topb

Null Conditional in Powershell?

C# and other languages have null-conditionals usually ?.
A?.B?.Do($C);
Will not error out when A or B are null.
How do I achieve something similar in powershell, what's a nicer way to do:
if ($A) {
if ($B) {
$A.B.Do($C);
}
}
Powershell 7 Preview 5 has operators that deal with nulls. https://devblogs.microsoft.com/powershell/powershell-7-preview-5/
$a = $null
$a ?? 'is null' # return $a or string if null
is null
$a ??= 'no longer null' # assign if null
$a ?? 'is null'
no longer null
EDIT: Powershell 7 Preview 6 piles on more new operators: https://devblogs.microsoft.com/powershell/powershell-7-preview-6/. Since variable names can have a '?' in the name, you have to surround the variable name with curly braces:
${A}?.${B}?.Do($C)
PowerShell doesn't have the null-conditional operator, but it silently ignores property references on null-value expressions, so you can just "skip" to the method call at the end of the chain:
if($null -ne $A.B){
$A.B.Do($C)
}
Works at any depth:
if($null -ne ($target = $A.B.C.D.E)){
$target.Do($C)
}
As Mathias R. Jessen's answer points out, PowerShell by default has null-conditional access behavior (null-soaking) with respect to property access[1]; e.g., $noSuchVar.Prop quietly returns $null
js2010's answer shows the related null-coalescing operator (??) / null-conditional-assignment operators (??=), which are available in PowerShell [Core] v 7.1+
However, up to PowerShell 7.0:
There is no way to null-conditionally ignore method calls: $noSuchVar.Foo() always fails.
Similarly, there's no way to null-conditionally ignore (array) indexing: $noSuchVar[0] always fails.
If you opt into more rigorous behavior with Set-StrictMode, even the property-access null-soaking is no longer an option: with Set-StrictMode -Version 1 or higher, $noSuchVar.Prop results in an error.
In PowerShell [Core] 7.1+, null-conditional (null-soaking) operators are available:
The new operators:
have the same form as in C# in principle: ?. and ?[...]
but - as of v7.1 - require enclosing the variable name in {...}
That is, you currently cannot use just $noSuchVar?.Foo(), $A?.B, or $A?[1], you have to use
${noSuchVar}?.Foo(), ${A}?.B, or ${A}?[1]
The reason for this cumbersome syntax is that there are backward-compatibility concerns, because ? is a legitimate character in variable names, so hypothetical existing code such as $var? = #{ one = 1}; $var?.one could break without using the {...} to disambiguate the variable name; in practice, such use is vanishingly rare.
If you think that not encumbering the new syntax is more important than potentially breaking scripts with variable names ending in ?, make your voice heard at this GitHub issue.
[1] PowerShell's default behavior even offers existence-conditional property access; e.g., $someObject.NoSuchProp quietly returns $null.

Braces, comma, parameter method call

I'm finding it very hard to google the answer to what the difference is between these two way of executing method calls in powershell:
$member = "1.2.3.4:567" # IPaddress + port for demonstration
$vals1 = $member.Split(":") # typical .NET way of executing String.Split
$vals2 = $member.Split( (,":") ) # something else which ive seen in examples which I dont understand
In the above, both $vals1 and $vals2 appear to have the same result (an array with 2 elements). I would typically use the first way, but in examples (of using both Split and other method calls) I see the second used.
My question is what is the second one doing which is different, and is there any advantages to using it which I may not understand?
Edit: Please don't focus on the Split method - I'm not asking about overloads of Split!
The comma operator used as a unary is what you are seeing. It is a shorthand way to create an array. PowerShell will unroll array in pipelines which is usually desired and standard behavior. Where I see this commonly used is to mitigate that feature of PowerShell
What you would then do in some cases though you do not want PowerShell to unroll the complete array is through the comma unary operator in front of that array. Consider the difference in outputs
Using regular array notation
$array = 1,2,3
$array.Count
$array | ForEach-Object{$_.GetType().FullName}
3
System.Int32
System.Int32
System.Int32
Using the unary operator to create a jagged array
$commaArray = ,#(1,2,3)
$commaArray.Count
$commaArray | ForEach-Object{$_.GetType().FullName}
1
System.Object[]
In the second example the array gets passed as a whole. PowerShell still unrolled it from a single array with one element that was itself an array to that single array 1,2,3.
There are other cases for its use as well. I would more commonly see regular arrays declared statically with 1,2,3 or sometimes the #() is needed depending. Result is the same for both of those.
,";" is a trick/shorthand to create an array (try (,";").GetType()). Why would you need this? Well, let's try calling Split with a list of values directly:
"abc".Split('a','b')
Cannot convert argument "count", with value: "b", for "Split" to type
"System.Int32": "Cannot convert value "b" to type "System.Int32".
Error: "Input string was not in a correct format.""
Doesn't work because the parameters are passed separately, rather than as a char[]. So could we use the comma trick to fix this?
"abc".Split((,'a','b'))
Cannot convert argument "separator", with value: "System.Object[]",
for "Split" to type "System.Char[]": "Cannot convert the
"System.Object[]" value of type "System.Object[]" to type
"System.Char"."
No, because we still have a type mismatch. That's because this approach is too clever for its own good. A much more readable way to create an array is the #() operator:
"abc".Split(#('a', 'b'))
And this calls the desired overload of Split.
The Split method has multiple overloads. The second example will create an array of string which will be convertet to an char[] because its only one character in the double quotes. However if you want to split by two characters, the second example won't work thus I wouldn't use it.
However, the PowerShell way to split is using -split:
$vals1 = $member -split ':'

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

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

Advanced syntax

I came across the following line of syntax which is rather advanced (to me):
(Get-ADReplicationSubnet -Filter *) -notmatch [String]::Join('|',$c.Subnet)
The above does exactly what I want, retrieve the list of subnets that are not matched against the $c.Subnet variable. I tried recreating the same effect with the line below. This does not work.
Get-ADReplicationSubnet -Filter * | Where {$_.Name -notmatch $c.Subnet}
My question is; Can someone explain in simple English how the first line works? (Not sure on the [String]::Join('|',$c.Subnet) part. It is difficult to search for something you don't know the name of. Besides that, why does my version not work?
I should clarify that $c.Subnet is an array of values.
[String]::Join('|',$c.Subnet) is call to the .NET framework -- the Join() method of the String class. You can take a look at the documentation here.
The documentation leads to the following explanation of the return value: A string that consists of the elements in value delimited by the separator string. If value is an empty array, the method returns String.Empty.
This means your return string will be something like value1|value2|value3 (where each value is value from $c.Subnet), which -notmatch interprets as a regex, with | meaning or. What the first line does is return values that do not match value1 or value2 or value3.
Why (I think) your line doesn't work is because you're using -notmatch with an array rather than a string. This article has some usage info about the two.
[String]::Join('|',$c.Subnet)
The [String] part means the .NET class.
The ::Join part means make a call to a static method of the String class, i.e. you don't have to "new up" an object before you use it.
The Join method takes and array and turns it into a string delineated by the pipe (in this case)
Someone else will have an explanation for the rest.