PowerShell Trim bug with String containing "< char >$< repeated char >"? - powershell

If I use the Trim() method on a string containing -char-$-repeated char-, e.g. "BL$LA" or "LA$AB", Trim() strips the repeated char after the $ as well.
For example:
$a = 'BL$LA'
$b = $a.Trim("BL$")
returns A not LA, but
$a = 'BM$LA'
$b = $a.Trim("BM$")
returns LA.
Any reason why? Or am I missing something?

The Trim() method removes all characters in the given argument (the string is automatically cast to a character array) from beginning and end of the string object. Your second example only seems to be doing what you want, because the remainder of the string does not have any of the characters-to-be-trimmed in it.
Demonstration:
PS C:\> $a = 'BL$LA'
PS C:\> $a.Trim("BL$")
A
PS C:\> $a = 'LxB$LA'
PS C:\> $a.Trim("BL$")
xB$LA
To remove a given substring from beginning and end of a string you need something like this instead:
$a -replace '^BL\$|BL\$$'
Regular expression breakdown:
^ matches the beginning of a string.
$ matches the end of a string.
BL\$ matches the literal character sequence "BL$".
...|... is an alternation (match any of these (sub)expressions).
If you just want to remove text up to and including the first $ from the beginning of a string you could also do something like this:
$a -replace '^.*?\$'
Regular expression breakdown:
^ matches the beginning of a string.
\$ matches a literal $ character.
.*? matches all characters up to the next (sub)expression (shortest/non-greedy match).

Related

Powershell - Remove text and capitalise some letters

Been scratching my head on this one...
I'd like to remove .com and capitalize S and T from: "sometext.com"
So output would be Some Text
Thank you in advance
For most of this you can use the replace() member of the String object.
The syntax is:
$string = $string.replace('what you want replaced', 'what you will replace it with')
Replace can be used to erase things by using blank quotes '' for the second argument. That's how you can get rid of .com
$string = $string.replace('.com','')
It can also be used to insert things. You can insert a space between some and text like this:
$string = $string.replace('et', 'e t')
Note that using replace does NOT change the original variable. The command below will print "that" to your screen, but the value of $string will still be "this"
$string = 'this'
$string.replace('this', 'that')
You have to set the variable to the new value with =
$string = "this"
$string = $string.replace("this", "that")
This command will change the value of $string to that.
The tricky part here comes in changing the first t to capital T without changing the last t. With strings, replace() replaces every instance of the text.
$string = "text"
$string = $string.replace('t', 'T')
This will set $string to TexT. To get around this, you can use Regex. Regex is a complex topic. Here just know that Regex objects look like strings, but their replace method works a little differently. You can add a number as a third argument to specify how many items to replace
$string = "aaaaaa"
[Regex]$reggie = 'a'
$string = $reggie.replace($string,'a',3)
This code sets $string to AAAaaa.
So here's the final code to change sometext.com to Some Text.
$string = 'sometext.com'
#Use replace() to remove text.
$string = $string.Replace('.com','')
#Use replace() to change text
$string = $string.Replace('s','S')
#Use replace() to insert text.
$string = $string.Replace('et', 'e t')
#Use a Regex object to replace the first instance of a string.
[regex]$pattern = 't'
$string = $pattern.Replace($string, 'T', 1)
What you're trying to achieve isn't well-defined, but here's a concise PowerShell Core solution:
PsCore> 'sometext.com' -replace '\.com$' -replace '^s|t(?!$)', { $_.Value.ToUpper() }
SomeText
-replace '\.com$' removes a literal trailing .com from your input string.
-replace '^s|t(?!$), { ... } matches an s char. at the start (^), and a t that is not (!) at the end ($); (?!...) is a so-called negative look-ahead assertion that looks ahead in the input string without including what it finds in the overall match.
Script block { $_.Value.ToUpper() } is called for each match, and converts the match to uppercase.
-replace (a.k.a -ireplace) is case-INsensitive by default; use -creplace for case-SENSITIVE replacements.
For more information about PowerShell's -replace operator see this answer.
Passing a script block ({ ... }) to dynamically determine the replacement string isn't supported in Windows PowerShell, so a Windows PowerShell solution requires direct use of the .NET [regex] class:
WinPs> [regex]::Replace('sometext.com' -replace '\.com$', '^s|t(?!$)', { param($m) $m.Value.ToUpper() })
SomeText

Powershell Store Variables into Variable does not work

I need some help with Powershell, i hope somebody can help.
I want to store multiple Variables into one single variable.
Here is my code:
$Content = #"
$Var1 = "1"
$Var2 = "2"
$Var3 = "3"
"#
echo $Content
And thats my output:
echo $Content
= "1"
= "2"
= "3"
It should look like this:
$Var1 = "1", etc...
But every variable gets removed. I don't know why. Could somebody please explain this? Do i need to store them into something like an object?
Thanks in advance!
The quickest fix for your situation is to switch to a single-quoted here-string.
$Content = #'
$Var1 = "1"
$Var2 = "2"
$Var3 = "3"
'#
In general, double quotes around a string instruct PowerShell to interpolate. Single quotes around a string tells PowerShell to treat the content as a literal string. This also applies to here-strings (#""# and #''#).
# Double Quotes
$string = "my string"
"$string"
my string
#"
$string
"#
my string
# Single Quotes
'$string'
$string
#'
$string
'#
$string
When interpolation happens, variables are expanded and evaluated. A variable is identified starting with the $ character until an illegal character for a variable name is read. Notice in the example below, how the variable interpolation stops at the ..
"$string.length"
my string.length
You can use escape characters or other operators to control interpolation within double quoted strings. The subexpression operator $() allows an expression to be evaluated before it is converted into a string.
"$($string.length)"
9
The backtick character is the PowerShell escape character. It can be used to escape $ when you don't want it to be treated as a variable.
"`$string"
$string
Mixing quotes can create certain gotchas. If you surround your string with single quotes, everything inside will be a literal string regardless of using escape characters or subexpressions.
'"$($string.length)"'
"$($string.length)"
'"`$string"'
"`$string"
Surrounding your string with double quotes with inside single quotes will just treat the inside single quotes literally because the outer quotes determine how the string will be interpreted.
"'$string'"
'my string'
Using multiple single quote pairs or double quote pairs requires special treatment. In this unique situation for double quotes, you can use the backtick escape or a two " to print a single ".
"""$string"""
"my string"
"`"$string`""
"my string"
For multiple single quotes, you must use two ' because a backtick will be treated literally and not tell PowerShell to escape anything.
'''$string'''
'$string'
Please reference About_Quoting_Rules for official documentation.

Validate against an array

I'm trying to setup a validation check against an array. I have the following
$ValidDomain = "*.com","*.co.uk"
$ForwardDomain = Read-Host "What domain do you want to forward to? e.g. contoso.com"
#while (!($ForwardDomain -contains $ValidDomain)) {
while (!($ValidDomain.Contains($ForwardDomain))) {
Write-Warning "$ForwardDomain isn't a valid domain name format. Please try again."
$ForwardDomain = Read-Host "What domain do you want to forward to? e.g. contoso.com"
}
The commented while line is just an alternative way I've been testing this.
If I enter, when prompted by Read-Host, "fjdkjfl.com" this displays the warning message rather than saying it's valid and keeps looping.
I have tried using -match instead of -contains but get the message:
parsing "*com *co.uk" - Quantifier {x,y} following nothing.
Have I got this completely wrong?
Contains() and -contains don't work the way you expect. Use a regular expression instead:
$ValidDomain = '\.(com|co\.uk)$'
$ForwardDomain = Read-Host ...
while ($ForwardDomain -notmatch $ValidDomain) {
...
}
You can construct $ValidDomain from a list of strings like this:
$domains = 'com', 'co.uk'
$ValidDomains = '\.({0})$' -f (($domains | ForEach-Object {[regex]::Escape($_)}) -join '|')
Regular expression breakdown:
.: The dot is a special character that matches any character except newlines. To match a literal dot you need the escape sequence \..
(...): Parentheses define a (capturing) group or subexpression.
|: The pipe defines an alternation (basically an "OR"). Alternations are typically put in grouping constructs to distinguish the alternation from the rest of the expression.
$: The dollar sign is a special character that matches the end of a string.
The {0} in the format string for the -f operator is not part of the regular expression, but a placeholder that defines where (and optionally how) the second argument of the operator is inserted into the format string.

How to convert string to int, if possible in powershell?

I am getting a string from VSO (using TFPT.exe) that can be either the item number or the item number plus a letter
"830" or "830a"
How can I break off the letter if it exists - and convert the number to int
$a = 830
#or
$a = 830
$b = "a"
I tried to test if "830" was a number - but i guess because it pulls it in as a string, i don't know how to ask: could this string be a int?
Assuming only the one set of numbers you can -match that pretty easily with regex. Where \d+ will match a group of consecutive digits.
PS C:\temp> "830a" -match "\d+"
True
PS C:\temp> $matches[0]
830
Knowing that you could incorporate something like this in your code.
$b = If($a -match "\d+"){[int]$matches[0]}
Obviously it would be more appropriate to use better variable names but this is just proof of concept. This as written would cause an issue if the alpha characters were in the middle of the string. As long as the number are grouped together it will work either way.
The other way you could do this would be to replace all of the character that are not digits.
$a = "830adasdf"
$a = $a -replace "\D" -as [int]
\D meaning any non digit character. -as [int] will perform the cast.
In either case [int] will cast the remaining digit string as an integer.
If you could guarantee that it is just the one character on the end that could be there then you could use the string method .TrimEnd() as well. It removes all characters found on the end of a string as determined by a char array. Lets give it an array of all letters. In practice this was having an issue with case so we take the string, converted it to uppercase and then remove any trailing letters.
"830z".ToUpper().TrimEnd([char[]](65..99)) -as [int]
It actually seems to convert the number array to char automatically so this would do just the same
"830z".ToUpper().TrimEnd(65..99) -as [int]
This is the best I have been able to come up with, seams to work: doesn't seam the most efficient way...
$t = $parent.Substring($parent.Length-1)
if($t -in #("a","b","c","d","e","f","g","h","i","j","k","l","m","n","o","p","q","r","s","t","u","v","w","x","y","z"))
{
[int]$parentSRP = $parent.Substring(0,$parent.Length-1)
$parentVer = $parent.Substring($parent.Length-1,1)
}
else{[int]$parentSRP = $parent}

$macro substitution - ExpandString limitations

I am trying to implement macro replacement based on this discussion. Basically it works, but seems the ExpandString have some limitatoins:
main.ps1:
$foo = 'foo'
$text = [IO.File]::ReadAllText('in.config')
$ExecutionContext.InvokeCommand.ExpandString($text) | out-file 'out.config'
in.config (OK):
$foo
in.config (Error: "Encountered end of line while processing a string token."):
"
in.config (Error: "Missing ' at end of string."):
'
The documentation states:
Return Value: The expanded string
with all the variable and expression
substitutions done.
What is 'expression substitution' (may be this is my case)?
Is there some workaround?
The error is occurring because quotes (single and double) are special characters to the PowerShell runtime. They indicate a string and if they are to be used as just that character, they need to be escaped.
A possible workaround would be to escape quotes with a backtick, depending on your desired result.
For example if my text file had
'$foo'
The resulting expansion of that string would be
PS>$text = [io.file]::ReadAllText('test.config')
PS>$ExecutionContext.InvokeCommand.ExpandString($text)
$foo
If you wanted to have that variable expanded, you would need to escape those quotes.
`'$foo`'
PS>$text = [io.file]::ReadAllText('test.config')
PS>$ExecutionContext.InvokeCommand.ExpandString($text)
'foo'
or if you were going to have an unpaired single or double quote, you would need to escape it.
You could do a -replace on the string to escape those characters, but you'll have to make sure that is the desired effect across the board.
PS>$single, $double = "'", '"'
PS>$text = [io.file]::ReadAllText('test.config') -replace "($single|$double)", '`$1'
PS>$ExecutionContext.InvokeCommand.ExpandString($text)
NOTE: After you do the ExpandString call, you won't have the backticks hanging around anymore.