Match strings stored in variables using PowerShell - powershell

I am attempting to create a backup script that will move files that are older that 30 days, but I want to be able to exclude folders from the list
$a = "C:\\Temp\\Exclude\\test"
$b = "C:\\Temp\\Exclude"
if I run the following:
$a -match $b
Following PowerShell Basics: Conditional Operators -Match -Like -Contains & -In -NotIn:
$Guy ="Guy Thomas 1949"
$Guy -match "Th"
This returns true.

I'd say use wilcards and the like operator, it can save you a lot of head aches:
$a -like "$b*"
The match operator is using regex pattern and the path is having regex special characters in it (the escape characeter). If you still want to use -match - make sure to escape the string:
$a -match [regex]::escape($b)
This will work but keep in mind that it can match in the middle of the string, you can add the '^' anchor to tell the regex engine to match from the begining of the string:
$a -match ("^"+[regex]::escape($b))

Related

Split string in Powershell

I have sth written in a ".ini" file that i want to read from PS. The file gives the value "notepad.exe" and i want to give the value "notepad" into a variable. So i do the following:
$CLREXE = Get-Content -Path "T:\keeran\Test-kill\test.ini" | Select-String -Pattern 'CLREXE'
#split the value from "CLREXE ="
$CLREXE = $CLREXE -split "="
#everything fine untill here
$CLREXE = $CLREXE[1]
#i am trying to omit ".exe" here. But it doesn't work
$d = $CLREXE -split "." | Select-String -NotMatch 'exe'
How can i do this ?
#Mathias R. Jessen is already answered your question.
But instead of splitting on filename you could use the GetFileNameWithoutExtension method from .NET Path class.
$CLREXE = "notepad.exe"
$fileNameWithoutExtension = [System.IO.Path]::GetFileNameWithoutExtension($CLREXE)
Write-Host $fileNameWithoutExtension # this will print just 'notepad'
-split is a regex operator, and . is a special metacharacter in regex - so you need to escape it:
$CLREXE -split '\.'
A better way would be to use the -replace operator to remove the last . and everything after it:
$CLREXE -replace '\.[^\.]+$'
The regex pattern matches one literal dot (\.), then 1 or more non-dots ([^\.]+) followed by the end of the string $.
If you're not comfortable with regular expressions, you can also use .NET's native string methods for this:
$CLREXE.Remove($CLREXE.LastIndexOf('.'))
Here, we use String.LastIndexOf to locate the index (the position in the string) of the last occurrence of ., then removing anything from there on out

powershell filter filenames with regex

I am building a list of files that I'm putting into my $list variable.
Then I want to filter the list based on the $filter variable. The current solution works, but it doesn't work with a regex.
$filter = #("test.txt","Fake","AnotherFile\d{1..6}")
######### HTML TESTS #############
[string]$list = #"
FakeFile.txt
test120119.txt
AnotherFile120119.txt
LastFile.txt
"#
[array]$files = $list -split '\r?\n'
$files = $files | Where-Object {$_} | Where {$_ -notin $filter} # filter out empty items from the array...
$files
My idea is to put regex patterns in the $filter variable so I can catch filenames that have datestamps in them such as test120119.txt in the $list variable above.
How can I change my code to allow for regex? I tried some variations of select-string without splitting my $list, but was not fruitful. I also tried changing my -notin to -notmatch but this doesn't work at all of course.
If you want to use regex, I think it would be easier to just fully commit to regex with your $filter array.
$filter = "^test\d{0,6}\.txt","^Fake","^AnotherFile\d{0,6}\.txt" -join '|'
$list = #"
FakeFile.txt
test120119.txt
AnotherFile120119.txt
LastFile.txt
"#
$files = $list -split '\r?\n'
$files | Where {$_ -notmatch $filter}
The thing to keep in mind is remembering to escape special regex characters if you want them treated literally. You can use the [regex]::Escape() method to do this for you but not if you already purposely injected regex characters.
Once you have your regex filter list, you can join each item with a regex or using the | character.
Not all operators recognize regex language. -match and -notmatch are among the few that do. -match and -notmatch are not case-sensitive. If you want to match against case, you should use the -c variants of the operators, namely -cmatch and -cnotmatch.
The regex items can be tweaked to your liking. More requirements would need to be given in order to come up with an exact solution. Here are some examples to consider:
\d{0,6} matches 0 to 6 consecutive digits. 122619 will match successfully, but so will 1226. If you want only 0 or 6 digits to match, you can use (\d{6})?.
^ should be used if you want to start each match at the beginning of the input string. So if you want the regex or to apply from the beginning of the string, you need to include ^ in each item or group items succeeding the initial ^ with () accordingly. ^item1|^item2 will return the same capture group 0 match as ^(item1|item2).
\ escape the literal . characters.
Not using anchor characters like ^ and $ create a lot of flexibility and potentially unwanted results. 'FakeFile' -match 'Fake' returns true but so does 'MyFakeFile' -match 'Fake'. However, 'MyFakeFile' -match 'Fake$' returns false and 'MyFake' -match 'Fake$' returns true.

Use "-replace" to compare and replace more variations of a string

I have to check if a string matches with one of these four strings and then replace the string. How can I do it?
I tried the following but it doesn't work
$NewOne = $One -replace("111" -or "112" -or "113" -or "114","000")
Instead of those numbers it should write 000.
Don't use parentheses with operators.
As -replace is RegEx based either use alternation or a character class
$NewOne = $One -replace '111|112|113|114','000'
or
$NewOne = $One -replace '11[1-4]','000'

Contains operator not working in Powershell

I have never got the -contains operator to work in Powershell I don't know why.
Here's an example of where it isn't working. I use -like in its place but I'd love it if you could tell me why this isn't working.
PS HKLM:\Software\Microsoft\Windows NT\CurrentVersion> (gp . P*).ProductName
Windows 10 Enterprise
PS HKLM:\Software\Microsoft\Windows NT\CurrentVersion> (gp . P*).ProductName -contains "Windows"
False
PS HKLM:\Software\Microsoft\Windows NT\CurrentVersion> (gp . P*).ProductName | gm | select TypeName | Get-Unique
TypeName
--------
System.String
The -contains operator is not a string operator, but a collection containment operator:
'a','b','c' -contains 'b' # correct use of -contains against collection
From the about_Comparison_Operators help topic:
Type Operator Description
Containment -contains Returns true when reference value contained in a collection
-notcontains Returns true when reference value not contained in a collection
-in Returns true when test value contained in a collection
-notin Returns true when test value not contained in a collection
Usually you would use the -like string operator in PowerShell, which supports Windows-style wildcard matching (* for any number of any characters, ? for exactly one of any character, [abcdef] for one of a character set):
'abc' -like '*b*' # $true
'abc' -like 'a*' # $true
Another alternative is the -match operator:
'abc' -match 'b' # $true
'abc' -match '^a' # $true
For verbatim substring matching, you would want to escape any input pattern, since -match is a regex operator:
'abc.e' -match [regex]::Escape('c.e')
An alternative is to use the String.Contains() method:
'abc'.Contains('b') # $true
With the caveat that, unlike powershell string operators, it's case-sensitive.
String.IndexOf() is yet another alternative, this one allows you to override the default case-sensitivity:
'ABC'.IndexOf('b', [System.StringComparison]::InvariantCultureIgnoreCase) -ge 0
IndexOf() returns -1 if the substring is not found, so any non-negative return value can be interpreted as having found the substring.
The '-contains' operator is best used for comparison to lists or arrays, e.g.
$list = #("server1","server2","server3")
if ($list -contains "server2"){"True"}
else {"False"}
output:
True
I'd suggest using '-match' instead for string comparisons:
$str = "windows"
if ($str -match "win") {"`$str contains 'win'"}
if ($str -match "^win") {"`$str starts with 'win'"}
if ($str -match "win$") {"`$str ends with 'win'"} else {"`$str does not end with 'win'"}
if ($str -match "ows$") {"`$str ends with 'ows'"}
output:
$str contains 'win'
$str starts with 'win'
$str does not end with 'win'
$str ends with 'ows'

powershell multiple block expressions

I am replacing multiple strings in a file. The following works, but is it the best way to do it? I'm not sure if doing multiple block expressions is a good way.
(Get-Content $tmpFile1) |
ForEach-Object {$_ -replace 'replaceMe1.*', 'replacedString1'} |
% {$_ -replace 'replaceMe2.*', 'replacedString2'} |
% {$_ -replace 'replaceMe3.*', 'replacedString3'} |
Out-File $tmpFile2
You don't really need to foreach through each replace operations. Those operators can be chained in a single command:
#(Get-Content $tmpFile1) -replace 'replaceMe1.*', 'replacedString1' -replace 'replaceMe2.*', 'replacedString2' -replace 'replaceMe3.*', 'replacedString3' |
Out-File $tmpFile2
I'm going to assume that your patterns and replacements don't really just have a digit on the end that is different, so you might want to execute different code based on which regex actually matched.
If so you can consider using a single regular expression but using a function instead of a replacement string. The only catch is you have to use the regex Replace method instead of the operator.
PS C:\temp> set-content -value #"
replaceMe1 something
replaceMe2 something else
replaceMe3 and another
"# -path t.txt
PS C:\temp> Get-Content t.txt |
ForEach-Object { ([regex]'replaceMe([1-3])(.*)').Replace($_,
{ Param($m)
$head = switch($m.Groups[1]) { 1 {"First"}; 2 {"Second"}; 3 {"Third"} }
$tail = $m.Groups[2]
"Head: $head, Tail: $tail"
})}
Head: First, Tail: something
Head: Second, Tail: something else
Head: Third, Tail: and another
This may be overly complex for what you need today, but it is worth remembering you have the option to use a function.
The -replace operator uses regular expressions, so you can merge your three script blocks into one like this:
Get-Content $tmpFile1 `
| ForEach-Object { $_ -replace 'replaceMe([1-3]).*', 'replacedString$1' } `
| Out-File $tmpFile2
That will search for the literal text 'replaceMe' followed by a '1', '2', or '3' and replace it with 'replacedString' followed by whichever digit was found (the '$1').
Also, note that -replace works like -match, not -like; that is, it works with regular expressions, not wildcards. When you use 'replaceMe1.*' it doesn't mean "the text 'replaceMe1.' followed by zero or more characters" but rather "the text 'replaceMe1' followed by zero or more occurrences ('*') of any character ('.')". The following demonstrates text that will be replaced even though it wouldn't match with wildcards:
PS> 'replaceMe1_some_extra_text_with_no_period' -replace 'replaceMe1.*', 'replacedString1'
replacedString1
The wildcard pattern 'replaceMe1.*' would be written in regular expressions as 'replaceMe1\..*', which you'll see produces the expected result (no replacement performed):
PS> 'replaceMe1_some_extra_text_with_no_period' -replace 'replaceMe1\..*', 'replacedString1'
replaceMe1_some_extra_text_with_no_period
You can read more about regular expressions in the .NET Framework here.