What does this PowerShell code do? - powershell

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.

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

Powershell confusion about Variable

I am confused regarding variables.
I have code, where I have the following line:
$search = $Name.SelectedItem.Split('-')[$($Name.SelectedItem.Split('-').Count-1)]+'*'
This line does nothing else, as split up a selected item (I am working with a Dropdownbox) and transfers it to $search.
The funny thing is, it does exactly that, what I want it to do.
When I type $search, the result can be for example:
Rue de Rivoli*
When I continue in the code and use $search through several arrays, for some reason, it does not function, because it does not find anything in a foreach loop. I have no error message and even the Rue de Rivoli* existing in one of the arrays, it does not find anything.
When I replace the above code and give directly the name to the variable $search, as seen below
$search = 'Rue de Rivoli*'
my search in the array works.
What am I missing here? I am doing something wrong, but I do not know what it is, can someone help me please to understand?
Thank you very much,
Mike
As requested, here more of the code. It is a lot to deal with, that is why I shorten it.
Clear-Host
$search = $CreateNewUserFormDropDownBoxLocation.SelectedItem.Split('-')[$($CreateNewUserFormDropDownBoxLocation.SelectedItem.Split('-').Count-1)]+'*'
#$search = 'Rue de Rivoli*'
$AllLocations = (get-variable -Include USPennsylvaniaAve, USSixthStreet, USRodeoDrive, USOneMicrosoftWay,`
USNorthTantauAvenue, USMarketStreet, USMainStreet, USEmilyDrive,`
USCalle8, USBroadway, US18thStreetNW, UKOxfordStreet, UKDowningStreet,`
UKBondStreet, FRRuedeRivoli, FRChampsElysees, CHBahnhofstrasse,`
CA17thAvenue) | ? {$_.value -is [array]}
Foreach ($Array in $AllLocations)
{
if ($array.value -like $search)
{break}
}
$result = "`$$($array.name)"
$result
This is about to become a function and does nothing else, as from the selecteditem, it takes it apart and add's the * behind it, so I can search
for a name with a wildcard.
I have several arrays and therefore I included only the necessary ones. Next step is to loop through the arrays and as soon as it found the item, it stops and gives the result to result.
This is my test code and it runs and does what I want, besides the line after Clear-Host. The code is correctly resolved and added to $search but does not work.
Below that code of line, I have my cheat line, where I add directly the correct result to the variable and it works fine.
As commented, this should solve the problem.
(I'm adding this as answer too, so the OP can accept it. Otherwise this question will remain seemingly unsolved)
When hardcoding the search string $search = 'Rue de Rivoli*' works, but using a Split() to get the search string does not, then usually the string you obtain using the split is surrounded by whitespace characters. If you leave these in, the string will appear to look just fine, but when using as comparison it won't work.
If for instance the complete $CreateNewUserFormDropDownBoxLocation.SelectedItem string is:
"François Exemple - Rue de Rivoli"
Then, using $CreateNewUserFormDropDownBoxLocation.SelectedItem.Split("-")[-1] will return:
" Rue de Rivoli"
Note the space in front.
By simply performing a Trim() you will get rid of that space.
The line therefore should be:
$search = ($CreateNewUserFormDropDownBoxLocation.SelectedItem.Split('-')[-1]).Trim() + '*'

String variable position being overwritten in write-host

If I run the below code, $SRN can be written as output or added to another variable, but trying to include either another variable or regular text causes it to be overwritten from the beginning of the line. I'm assuming it's something to do with how I'm assigning $autocode and $SRN initially but can't tell what it's trying to do.
# Load the property set to allow us to get to the email body.
$item.load($psPropertySet) # Load the data.
$bod = ($item.Body.Text -creplace '(?m)^\s*\r?\n','') -split "\n" # Get the body text, remove blank lines, split on line breaks to create an array (otherwise it is a single string).
$autocode = $bod[4].split('-')[2] # Get line 4 (should be Title), split on dash, look for 3rd element, this should contain our automation code.
$SRN = $bod[1] -replace 'ID: ','' # Get line 2 (should be ID), find and replace the preceding text.
# Skip processing if autocode does not match our list of handled ones.
if ($autocode -cin $autocodes)
{
write-host "$SRN $autocode"
write-host "$autocode $SRN"
write-host "$SRN test"
$var = "$SRN $autocode"
$var
}
The code results in this, you can see if $SRN isn't at the start of the line it is fine. Unsure where the extra spaces come from either:
KRNE8385
KRNE SR1788385
test8385
KRNE8385
I would expect to see this:
SR1788385 KRNE
KRNE SR1788385
SR1788385 test
SR1788385 KRNE
LotPings pointed me down the right path, both variables still had either "0D" or "\r" in them. My regex replace was only getting rid of them on blank lines, and I split the array on "\n" only. Changing line 3 in the original code to the below appears to have resolved the issue. First time seeing Format-Hex, but it appears to be excellent for troubleshooting such issues.
$bod = ($item.Body.Text -creplace '(?m)^\s*\r?\n','') -split "\r\n"

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.

Powershell script problem (Get-content vs assigning to variable)

I'm attempting to write a Twitter Powershell script that will use community created interfaces PoshTwitter with the Twitter API to attempt and find a list of followers who are potential spammers.
I have a feeling that my problem lies not with the particular cmdlet I'm calling (Get-TwitterFollowers), but rather with the difference between assigning a variable:
If I try this:
$rawFol = get-twitterfollowers -page $page -raw 1
$rawFol is different than if I do this:
get-twitterfollowers -page $page -raw 1 > .\page$page.txt
$rawFol = gc .\page$page.txt
The Get-TwitterFollowers cmdlet returns an XML file converted to string.
What things can I try to determine the differences between these two assignments? They look like they'd result with same content.
The difference you're seeing is how powershell handles new lines in strings. When calling the get-twitterfollowers CmdLet, it is either returning a single string or an array of strings. My guess by your description is that it returns a string. So the $rawFol variable will have a single string value. Any new lines are simply embedded into the string value.
The second command you write the return to a file. Now all of the newlines in the string are represented as lines in the file. Later when you call gc on that file, each line will be returned as a separate string. So the $rawFol variable will now have an array of strings.