Powershell confusion about Variable - powershell

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() + '*'

Related

What am I allowed to name a function in Powershell?

PS > function ]{1}
PS > ]
1
PS >
PS
Why does this work?
What else can I name a function? All I've found so far that works is * and ].
You can name it almost anything. You can even include newlines and emoji* in the name.
function Weird`nFunctionの名前😀 { Write-Host hey }
$c = gcm Weird*
$c.Name
& $c
Escaping helps with lots of things like that:
function `{ { Write-Host cool }
`{
function `0 { Write-Host null }
gci function:\?
I'll add that this is true for variables too, and there's a syntax that removes the need to do most escaping in the variable name: ${varname} (as opposed to $varname).
With that, you could easily do:
${My variable has a first name,
it's
V
A
something
R,
whatever I dunno
🤷} = Get-Process
You'll note that if you then start typing like $MyTAB it will tab complete in a usable way.
To (somewhat) answer why this should work, consider that the variable names themselves are just stored in .Net strings. With that in mind, why should there be a limit on the name?
There will be limits on how some of these names can be used in certain contexts, because the parser will not understand what to do with it if the names don't have certain characters escaped, but literal parsing of PowerShell scripts are not the only way to use functions or variables or other language constructs, as I've shown some examples of.
Being less limiting also means being able to support other languages and cultures by having wide support for character sets.
To this end, here's one more thing that might surprise you: there are many different characters to represent the same or similar things that we take for granted in code, like quotation marks for example.
Some (human) languages or cultures just don't use the same quote characters we do in English, don't even have them on the keyboard. How annoying would it be to type code if you have to keep switching your keyboard layout or use ALT codes to quote strings?
So what I'm getting at here is that PowerShell actually does support many quote characters, for instance, what do you think this might do:
'Hello’
Pretty obvious it's not the "right" set of quotes on the right side. But surprisingly, this works just fine, even though they aren't the same character.
This does have important implications if you're ever generating code from user input and want to avoid sneaky injection attacks.
Imaging you did something like this:
Invoke-Expression "echo '$($userMsg -replace "'","''")'"
Looks like you took care of business, but now imagine if $userMsg contained this:
Hi’; gci c: -recurse|ri -force -whatif;'
For what it's worth, the CodeGeneration class is aware of this stuff ;)
Invoke-Expression "echo '$([System.Management.Automation.Language.CodeGeneration]::EscapeSingleQuotedStringContent($userMsg))'"
* PowerShell Console doesn't have good support for Unicode, even though the language does. Use ISE to better see the characters.

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"

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.

Powershell indent here-string

Does anybody know if it is possible to intend a here-string. Actually I've to write:
$someString = #"
First line
second line
"#
This becomes very ugly if you define a here-string on a deeper indentation level, because the ending "# must be the first character on the line. Additionally somebody might "fix" the missing indentation and break the script ...
Is it possible to define a here-string like:
$someString = #"
First line
second line
"#
Thx
The closing "# must be at the start of the line.
I agree with you that this can make the script file harder to read, but it is the rule, and there is no way around it as far as I know.
You can find a uservoice entry here, and you should vote for it if you feel this is important to you. It does not look like a priority at the moment, with only 3 votes, but the more votes, the higher the priority for the powershell team to look into it.
Late answer (technically "workaround"), I know, but this is currently one of the first search results for "PowerShell here-string indentation".
This becomes very ugly if you define a here-string on a deeper indentation level
I agree. For those of us concerned with the aesthetics of the resulting code, I found the following workaround on this TechNet question.
It's definitely not a here-string (especially since you must still escape embedded quotes), but at least for many multi-line use-cases, it will serve the same purpose, and allow you to keep indenting at the same level as the rest of the code-block:
$somestring = (
"First line",
"Second line"
) -join "`r`n"
To expand upon #NotTheDr01ds's answer, you can make it even more aesthetically pleasing by excluding commas after each item. So long as each item in the array is on its own line:
$HTML = #(
"<h1>OIDC Services are <font style='color:green'>Online</font></h1>"
"<br/><p>Your identity: <ul><li>Username: $($Context.User.Identity.Name)</li></ul></p>"
"<br/><p>Troubleshooting: <ul><li><a href='/restart-service'>Restart Service</a></li></ul></p>"
) -Join "`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.