I'm trying to find a line that matches the below pattern in a file. I can see the pattern is output as I expect but it doesn't match.
String in file
"5/29/2019 12:01:03 PM - Sys - Logged Successfully"
Variables
$pattern = "Logged Successfully"
$datePattern = "5/29/2019"
Code - Working - Matches ok
$reponse = select-string -Path $path\$file -Pattern $pattern -allmatches -simplematch
Code - Not Working
$reponse = select-string -Path $path\$file -Pattern "$($datePattern).*$($pattern)" -allmatches -simplematch
Maybe im missing something very simple, any help greatly appreciated.
Remove the -simplematch switch from the code sample that is not working and then it will work. You are disabling a regular expression match while using that switch. See this previous SO answer from Mathias R. Jessen where he explains in more detail.
I have been trying to extract certain values from multiple lines inside a .txt file with PowerShell.
Host
Class
INCLUDE vmware:/?filter=Displayname Equal "server01" OR Displayname Equal "server02" OR Displayname Equal "server03 test"
This is what I want :
server01
server02
server03 test
I have code so far :
$Regex = [Regex]::new("(?<=Equal)(.*)(?=OR")
$Match = $Regex.Match($String)
You may use
[regex]::matches($String, '(?<=Equal\s*")[^"]+')
See the regex demo.
See more ways to extract multiple matches here. However, you main problem is the regex pattern. The (?<=Equal\s*")[^"]+ pattern matches:
(?<=Equal\s*") - a location preceded with Equal and 0+ whitespaces and then a "
[^"]+ - consumes 1+ chars other than double quotation mark.
Demo:
$String = "Host`nClass`nINCLUDE vmware:/?filter=Displayname Equal ""server01"" OR Displayname Equal ""server02"" OR Displayname Equal ""server03 test"""
[regex]::matches($String, '(?<=Equal\s*")[^"]+') | Foreach {$_.Value}
Output:
server01
server02
server03 test
Here is a full snippet reading the file in, getting all matches and saving to file:
$newfile = 'file.txt'
$file = 'newtext.txt'
$regex = '(?<=Equal\s*")[^"]+'
Get-Content $file |
Select-String $regex -AllMatches |
Select-Object -Expand Matches |
ForEach-Object { $_.Value } |
Set-Content $newfile
Another option (PSv3+), combining [regex]::Matches() with the -replace operator for a concise solution:
$str = #'
Host
Class
INCLUDE vmware:/?filter=Displayname Equal "server01" OR Displayname Equal "server02" OR Displayname Equal "server03 test"
'#
[regex]::Matches($str, '".*?"').Value -replace '"'
Regex ".*?" matches all "..."-enclosed tokens; .Value extracts them, and -replace '"' strips the " chars.
It may be not be obvious, but this happens to be the fastest solution among the answers here, based on my tests - see bottom.
As an aside: The above would be even more PowerShell-idiomatic if the -match operator - which only looks for a (one) match - had a variant named, say, -matchall, so that one could write:
# WISHFUL THINKING (as of PowerShell Core 6.2)
$str -matchall '".*?"' -replace '"'
See this feature suggestion on GitHub.
Optional reading: performance comparison
Pragmatically speaking, all solutions here are helpful and may be fast enough, but there may be situations where performance must be optimized.
Generally, using Select-String (and the pipeline in general) comes with a performance penalty - while offering elegance and memory-efficient streaming processing.
Also, repeated invocation of script blocks (e.g., { $_.Value }) tends to be slow - especially in a pipeline with ForEach-Object or Where-Object, but also - to a lesser degree - with the .ForEach() and .Where() collection methods (PSv4+).
In the realm of regexes, you pay a performance penalty for variable-length look-behind expressions (e.g. (?<=EQUAL\s*")) and the use of capture groups (e.g., (.*?)).
Here is a performance comparison using the Time-Command function, averaging 1000 runs:
Time-Command -Count 1e3 { [regex]::Matches($str, '".*?"').Value -replace '"' },
{ [regex]::matches($String, '(?<=Equal\s*")[^"]+') | Foreach {$_.Value} },
{ [regex]::Matches($str, '\"(.*?)\"').Groups.Where({$_.name -eq '1'}).Value },
{ $str | Select-String -Pattern '(?<=Equal\s*")[^"]+' -AllMatches | ForEach-Object{$_.Matches.Value} } |
Format-Table Factor, Command
Sample timings from my MacBook Pro; the exact times aren't important (you can remove the Format-Table call to see them), but the relative performance is reflected in the Factor column, from fastest to slowest.
Factor Command
------ -------
1.00 [regex]::Matches($str, '".*?"').Value -replace '"' # this answer
2.85 [regex]::Matches($str, '\"(.*?)\"').Groups.Where({$_.name -eq '1'}).Value # AdminOfThings'
6.07 [regex]::matches($String, '(?<=Equal\s*")[^"]+') | Foreach {$_.Value} # Wiktor's
8.35 $str | Select-String -Pattern '(?<=Equal\s*")[^"]+' -AllMatches | ForEach-Object{$_.Matches.Value} # LotPings'
You can modify your regex to use a capture group, which is indicated by the parentheses. The backslashes just escape the quotes. This allows you to just capture what you are looking for and then filter it further. The capture group here is automatically named 1 since I didn't provide a name. Capture group 0 is the entire match including quotes. I switched to the Matches method because that encompasses all matches for the string whereas Match only captures the first match.
$regex = [regex]'\"(.*?)\"'
$regex.matches($string).groups.where{$_.name -eq 1}.value
If you want to export the results, you can do the following:
$regex = [regex]'\"(.*?)\"'
$regex.matches($string).groups.where{$_.name -eq 1}.value | sc "c:\temp\export.txt"
An alterative reading the file directly with Select-String using Wiktor's good RegEx:
Select-String -Path .\file.txt -Pattern '(?<=Equal\s*")[^"]+' -AllMatches|
ForEach-Object{$_.Matches.Value} | Set-Content NewFile.txt
Sample output:
> Get-Content .\NewFile.txt
server01
server02
server03 test
So I got this regex expression to work in Regex101 and it captures exactly what I want to capture. https://regex101.com/r/aJ1bZ4/3
But when I try the same thing in powershell all I get is the first set of matches. I've tried using the (?s:), the (?m:) but none of these modifiers seem to do the job. Here is my powershell script.
$reportTitleList = type ReportExecution.log | Out-String |
where {$_ -match "(?<date>\d{4}\/\d{2}\/\d{2}).*ID=(?<reportID>.*):.*Started.*Title=(?<reportName>.*)\[.*\n.*Begin ....... (?<reportHash>.*)"} |
foreach {
new-object PSObject -prop #{
Date=$matches['date']
ReportID=$matches['reportID']
ReportName=$matches['reportName']
ReportHash=$matches['reportHash']
}
}
$reportTitleList > reportTitleList.txt
What am I doing wrong? Why am I not getting all the matches as the regex101 example?
-match only find the first match. To use a global search you need to use [regex]::Matches() or Select-String with the -AllMatches switch. Ex:
#In PoweShell 3.0+ you can replace `Get-Content | Out-String` with `Get-Content -Raw`
$reportlist = Get-Content -Path ReportExecution.log | Out-String |
Select-String -Pattern $pattern -AllMatches |
Select-Object -ExpandProperty Matches |
Select-Object #{n="Date";e={$_.Groups["date"]}},
#{n="ReportID";e={$_.Groups["reportID"]}},
#{n="ReportName";e={$_.Groups["reportName"]}},
#{n="ReportHash";e={$_.Groups["reportHash"]}}
#Show output
$reportlist
Output:
Date ReportID ReportName ReportHash
---- -------- ---------- ----------
2015/03/23 578 Calendar Day Activity/Calendar Day Activity 38C19F4E790446709B8C7A32FF97BC...
2015/03/23 861 Program Format Report/Program Format Report 3C9CB2150AF14B15A1B361729C007B...
2015/03/23 1077 Multi-Station Program Availability/Multi-Station Program Availability 52526430EE4E401BA4376B38A2D88B...
2015/03/23 1299 Program Audit Trail/Program Audit Trail FDD1B7D9F34E46549A377A17B9A7A1...
2015/03/23 1541 Program Availability/Program Availability 843B44F4475C4950A7784C8961B642...
2015/03/23 1756 Program Description Export/Program Description Export E5800A76C68E4D5281B8D680DB2E93...
-match returns as soon as it finds a match (they should have a -matches operator right?). If you want multiple matches, use:
$mymatches = [regex]::matches($input,$pattern)
output will be different than -match, however, and you'll have to massage it a bit, something like: (see here for another example of conversion)
$mymatches | ForEach-Object { if ( $_.Success) { echo $_.value}}
I’m creating a script that reads a text file and compares the results to an array. It works fine, but I have some records that say they match but they don’t.
For example - TG1032 and TG match according to the select-string script.
Here is my select-string:
$Sel = select-string -pattern $strArrVal -path $txt
Is there a way to alter this to make select-string only match records that are 6 characters long?
I would still like to point out where your pattern is wrong but the solution will most likely be the same regardless. If you are looking to match lines that are exactly 6 characters then you could just use the pattern ^.{6}$.
$strArrVal = "^.{6}$"
Select-String -Pattern $strArrVal -Path $txt
If that is really all you are looking for then regex is not really required. You could do this with Get-Content with similar results
Get-Content $txt | Where-Object{$_.length -eq 6}
I store the output of a defragmentation analysis in a variable, then I try to match a pattern to retrieve a number.
In this following online regex tester, it works fine but in powershell, String -match $pattern returns false.
My code:
$result = Defrag C: /A /V | Out-String
echo $result
$pattern = "fragmenté[^.0-9]*([0-9]+)%"
$result -match $pattern
What am I doing wrong?
I actually had no issue with your code. I just needed to change the match to support my English output. It is possible that Wolfgang Kluge is onto something about the whitespace. However if your output actually matches what you have in the regex tester than i'm not sure what this issue you are having.
For fun I propose this update to your code. This uses ConvertFrom-StringData. I explain the code more in this answer.
$defrag = Defrag C: /A /V | out-string
$hash = (($defrag -split "`r`n" | Where-Object{$_ -match "="}) -join "`r`n" | ConvertFrom-StringData)
$result = New-Object -TypeName PSCustomObject -Property $hash
$result."Quantité totale d'espace fragmenté"
This is of course assuming that your PowerShell is perfectly OK with the accents in the words. On my ISE 3.0 that above code works.
Again... your code was working just fine for me in your question. I also don't think the Out-String is required. I still get positive output. With Out-String I get extra output that includes the entire matched line. Else I just get a boolean. In both (using the following code) I still get a result.
$result = Defrag C: /A /V #| Out-String
$pattern = "fragmented space[^.0-9]*([0-9]+)%"
$result -match $pattern
$Matches[1]
-match works as an array operator which changes how $result is treated. With Out-String $result is a System.String and without it you get System.Object
False Theory
The only way I can get the match to be False is if I am not running PowerShell as an administrator. That is important because if not you will get a message
The disk defragmenter cannot start because you have insufficient priveleges to perform this operation. (0x89000024)