Advanced syntax - powershell

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.

Related

Correct writing of Powershell Logical Expression?

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.

What does this PowerShell code do?

I found the following code in a book about PowerShell scripts:
$Text = Read-Host -Prompt 'Input your text'
function FastTrain($text) {
$h = #{}
ForEach ($word in [regex]::split($text.ToLower(), '\W+'))
{
$h[$word] = ''
}
$h
}
FastTrain($Text)
I tried to run it, and got this:
What does it do? I think that it gets a string from the user, and then searches for characters, but I am not entirely sure.
Read a line of text from the user
$Text = Read-Host -Prompt 'Input your text'
Define a function
function FastTrain($text) {
Create a new hashtable. This is a data structure that maps keys to values.
$h = #{}
Lower-case the text argument and split it on successive non-word characters. This results in an array of “words” (however, since “word” characters for regular expressions are a quite arbitrary concept of little use anywhere, this will also include numbers, underscores, and a bunch of other things apart from letters).
ForEach ($word in [regex]::split($text.ToLower(), '\W+'))
{
Use the word as key in the hashtable and set the value to an empty string. This is merely a poor-man's version of a set, so the hashtable will contain all unique words from the input as keys (the values are irrelevant).
$h[$word] = ''
}
Return the hashtable
$h
}
Run above function on the input read earlier. This will also cause the hashtable from earlier to be printed on the screen since any object that is returned from a statement or pipeline will be output by default.
FastTrain($Text)
Note that this usage of PowerShell functions is technically incorrect and can easily lead to mistakes. A PowerShell function is invoked like any other PowerShell command, but not like a .NET method. So arguments are separated by spaces and there are no parentheses. In this case it works because there is only a single argument.
Given how atrocious this example is, I guess you should find a better book. This code looks nothing like how PowerShell code should look (in my opinion at least). The function performed by that code is essentially “Given a string, return all unique words from it”. A more PowerShell-ey version of that function would probably be:
function Get-UniqueWords($Text) {
$Text.ToLower() -split '\W+' | Select -Unique
}
No messing around with a hashtable, just to get a set of sorts. No unnecessary call to a .NET method where a PowerShell operator suffices. And using the pipeline to transform and/or filter a stream of data. Loops like that are often unnecessary since the pipeline is often easier to read and grasp (since you can just read how things are piped into another, instead of having to parse what happens to data structures to find out what happens to your data).
However, considering my gripe about \w/\W from earlier, the following regex would probably yield saner results for humans:
function Get-UniqueWords($Text) {
$Text.ToLower() -split '\P{L}+' | Select -Unique
}
This really only considers letters.
It is reading a sentence from the user:
$Text = Read-Host -Prompt 'Input your text'
It then creates an empty hashtable (a collection of key-value pairs):
$h = #{}
Then, splits the sentence into words:
[regex]::split($text.ToLower(), '\W+'))
And adds each one to the hashtable (with the word as the key, and nothing for the value):
$h[$word] = ''
Finally, it prints the hashtable:
$h
There is a function definition/call mixed in, but the above is what the code does.

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 ':'

What is the meaning of ${} in powershell?

I have a script where function parameters are expressed like this:
param(
${param1},
${param2},
${param3}
)
What does it mean? I have been unable to find documentation on this.
What's the point of writing parameters that way instead of the more usual
param(
$param1,
$param2,
$param3
)
?
#MikeZ's answer is quite correct in explaining the example in the question, but as far as addressing the question title, there is actually more to say! The ${} notation actually has two uses; the second one is a hidden gem of PowerShell:
That is, you can use this bracket notation to do file I/O operations if you provide a drive-qualified path, as defined in the MSDN page Provider Paths.
(The above image comes from the Complete Guide to PowerShell Punctuation, a one-page wallchart freely available for download, attached to my recent article at Simple-Talk.com.)
They are both just parameter declarations. The two snippets are equivalent. Either syntax can be used here, however the braced form allows characters that would not otherwise be legal in variable names. From the PowerShell 3.0 language specification:
There are two ways of writing a variable name: A braced variable name, which begins with $, followed by a curly bracket-delimited set of one or more almost-arbitrary characters; and an ordinary variable name, which also begins with $, followed by a set of one or more characters from a more restrictive set than a braced variable name allows. Every ordinary variable name can be expressed using a corresponding braced variable name.
From about_Variables
To create or display a variable name that includes spaces or special characters, enclose the variable name in braces. This directs Windows PowerShell to interpret the characters in the variable name literally.
For example, the following command creates and then displays a variable named "save-items".
C:\PS> ${save-items} = "a", "b", "c"
C:\PS> ${save-items}
a
b
c
They are equivalent. It's just an alternative way of declaring a variable.
If you have characters that are illegal in a normal variable, you'd use the braces (think of it as "escaping" the variablename).
There is one additional usage.
One may have variable names like var1, var2, var11, var12, var101, etc.
Regardless if this is desirable variable naming, it just may be.
Using brackets one can precisely determine what is to be used:
assignment of $var11 may be ambiguous, using ${var1}1 or ${var11} leaves no room for mistakes.

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