PowerShell Select-String from file with Regex - regex

I am trying to get a string of text from a .sln (Visual Studio solution file) to show what project files are contained within the solution.
An example line of text is
Project("{xxxx-xxxxx-xxxx-xxxx-xx}") = "partofname.Genesis.Printing", "Production\partofname.Genesis.Printing\partofname.Genesis.Printing.csproj", "{xxx-xxx-xxx-xxx-xxxx}"
EndProject
The part of the string that I am interested in is the last part between the \ and the ".
\partofname.Genesis.Printing.csproj"
The regular expression I am using is:
$r = [regex] "^[\\]{1}([A-Za-z.]*)[\""]{1}$"
I am reading the file content with:
$sln = gci .\Product\solutionName.sln
I don't know what to put in my string-select statement.
I am very new to PowerShell and would appreciate any and all help...
I did have a very very long-hand way of doing this earlier, but I have lost the work... Essentially it was doing this for each line in a file:
Select-String $sln -pattern 'proj"' | ? {$_.split()}
But a regular expression would be a lot easier (I hope).

The following gets everything between " and proj":
Select-String -Path $PathToSolutionFile ', "([^\\]*?\\)*([^\.]*\..*proj)"' -AllMatches | Foreach-Object {$_.Matches} |
Foreach-Object {$_.Groups[2].Value}
The first group gets the folder that the proj file is in. The second group gets just was you requested (the project file name). AllMatches returns every match, not just the first. After that it's just a matter of looping through each collection of matches on the match objects and getting the value of the second group in the match.

Your Script works great. To make into a one liner add -Path on the Select String:
Select-String -path $pathtoSolutionFile ', "([^\\]*?\\)?([^\.]*\..*proj)"' -
AllMatches | Foreach-Object {$_.Matches} | Foreach-Object {$_.Groups[2].Value}
To build from this you can use Groups[0]
(((Select-String -path $pathtoSoultionFile ', "([^\\]*?\\)?([^\.]*\..*proj)"' -AllMatches | Foreach-Object {$_.Matches} |
Foreach-Object {$_.Groups[0].Value})-replace ', "','.\').trim('"'))

For me this pattern was the best:
[^"]+\.csproj

Related

How can i replace all lines in a file with a pattern using Powershell?

I have a file with lines that i wish to remove like the following:
key="Id" value=123"
key="FirstName" value=Name1"
key="LastName" value=Name2"
<!--key="FirstName" value=Name3"
key="LastName" value=Name4"-->
key="Address" value=Address1"
<!--key="Address" value=Address2"
key="FirstName" value=Name1"
key="LastName" value=Name2"-->
key="ReferenceNo" value=765
have tried the following: `
$values = #('key="FirstName"','key="Lastname"', 'add key="Address"');
$regexValues = [string]::Join('|',$values)
$lineprod = Get-Content "D:\test\testfile.txt" | Select-String $regexValues|Select-Object -
ExpandProperty Line
if ($null -ne $lineprod)
{
foreach ($value in $lineprod)
{
$prod = $value.Trim()
$contentProd | ForEach-Object {$_ -replace $prod,""} |Set-Content "D:\test\testfile.txt"
}
}
The issue is that only some of the lines get replaced and or removed and some remain.
The output should be
key="Id" value=123"
key="ReferenceNo" value=765
But i seem to get
key="Id" value=123"
key="ReferenceNo" value=765
<!--key="Address" value=Address2"
key="FirstName" value=Name1"
key="LastName" value=Name2"-->
Any ideas as to why this is happening or changes to the code above ?
Based on your comment, the token 'add key="Address"' should be changed for just 'key="Address"' then the concatenating logic to build your regex looks good. You need to use the -NotMatch switch so it matches anything but those values. Also, Select-String can read files, so, Get-Content can be removed.
Note, the use of (...) in this case is important because you're reading and writing to the same file in the same pipeline. Wrapping the statement in parentheses ensure that all output from Select-String is consumed before passing it through the pipeline. Otherwise, you would end up with an empty file.
$values = 'key="FirstName"', 'key="Lastname"', 'key="Address"'
$regexValues = [string]::Join('|', $values)
(Select-String D:\test\testfile.txt -Pattern $regexValues -NotMatch) |
ForEach-Object Line | Set-Content D:\test\testfile.txt
Outputs:
key="Id" value=123"
key="ReferenceNo" value=765

Using "notin" with matching groups

Using powershell, I am trying to determine which perl scripts in a directory are not called from any other script. In my Select-String I am grouping the matches because there is some other logic I use to filter out results where the line is commented, and a bunch of other scenarios I want to exclude(for simplicity I excluded that from the code posted below). My main problem is in the "-notin" part.
I can get this to work if I remove the grouping from Select-string and only match the filename itself. So this works.
$searchlocation = "C:\Temp\"
$allresults = Select-String -Path "$searchlocation*.pl" -Pattern '\w+\.pl'
$allperlfiles = Get-Childitem -Path "$searchlocation*.pl"
$allperlfiles | foreach-object -process{
$_ | where {$_.name -notin $allresults.matches.value} | Select -expandproperty name | Write-Host
}
However I cannot get the following to work. The only difference between this and above is the value for the "-Pattern" and the value after "-notin". I'm not sure how to use "notin" along with matching groups.
$searchlocation = "C:\Temp\"
$allresults = Select-String -Path "$searchlocation*.pl" -Pattern '(.*?)(\w+\.pl)'
$allperlfiles = Get-Childitem -Path "$searchlocation*.pl"
$allperlfiles | foreach-object -process{
$_ | where {$_.name -notin $allresults.matches.groups[2].value} | Select -expandproperty name | Write-Host}
At a high level the code should search all perl scripts in a directory for any lines that execute any other perl script. With that I now have $allresults which basically gives me a list of all perl scripts called from other files. To get the inverse of that(files that are NOT called from any other file) I get a list of all perl scripts in the directory, cycle through those and list out the ones that DONT show up in $allresults.
When you select a grouping you need to do so using a Select statement, or iteratively in a loop, otherwise you are only going to select the value from the Nth match.
IE if your $Allresults object contains
File.pl, File 2.pl, File 3.pl
Then $allresults.Matches.Groups[2].value Only Returns File2.pl
Instead, you need to select those values!
$allresults | select #{N="Match";E={ $($_.Matches.Groups[2].value) } }
Which will return:
Match
-----
File1.pl
File2.pl
File3.pl
In your specific example, each match has three sub-items, the results will be completely sequential, so what you would term "match 1, group 1" is groups[0] while "match 2, group 1" is groups[3]
This means the matches you care about (those with grouping 2) are in the array values contained in the set {2,5,8,11,...,etc.} or can be described as (N*3-1) Where N is the number of the match. So For Match 1 = (1*3)-1 = [2]; while For Match 13 = (13*3)-1 = [38]
You can iterate through them using a loop to check:
for($i=0; $i -le ($allresults.Matches.groups.count-1); $i++){
"Group[$i] = ""$($allresults.Matches.Groups[$i].value)"""
}
I noticed that you took the time to avoid loops in collecting your data, but then accidentally seem to have fallen prey to using one in matching your data.
Not-In and other compares when used by the select and where clauses don't need a loop structure and are faster if not looped, so you can forego the Foreach-object loop and have a better process just by using a simple Where (?).
$SearchLocation = "C:\Temp\"
$FileGlob = "*.pl"
$allresults = Select-String -Path "$SearchLocation$FileGlob" -Pattern '(.*?)([\w\.]+\.bat)'
$allperlfiles = Get-Childitem -Path "$SearchLocation$FileGlob"
$allperlfiles | ? {
$_.name -notin $(
$allresults | select #{N="Match";E={ $($_.Matches.Groups[2].value) } }
)
} | Select -expandproperty name | Write-Host
Now, that should be faster and simpler code to maintain, but, as you may have noticed, it still has some redundancies now that you are not looping.
As you are piping it all into a Select which can do the work of the where, and what's more you only are looking to match the NAME property here so you can either for-go the last select by only piping the name of the file in the first place, or you can forgo the where and select exactly what you want.
I think the former is far simpler, and the latter is useful if you are going to actually do something with those other values inside the loop that we don't know yet.
Finally, Write-host is likely redundant as any object output will echo to the console.
Here is that version which incorporates the removal of the unnecessary loops and removes redundancies related to the output of the info you wanted, all together.
$SearchLocation = "C:\Temp\"
$FileGlob = "*.pl"
$allresults = Select-String -Path "$SearchLocation$FileGlob" -Pattern ('(.*?)([\w\.]+\'+$FileGlob+')')
$allperlfiles = Get-Childitem -Path "$SearchLocation$FileGlob"
$allperlfiles.name | ? {
$_ -notin $(
$allresults | select #{
N="Match";E={
$($_.Matches.Groups[2].value)
}
}
)
}

Unable to match pattern when string exists in file

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.

Search for RegEx string in files and return ONLY file name, path and string

i am kinda stuck with this search for Regex string. The scenario is as follows:
I have a bunch of files of certain extension (*.tlt) with random content
All the files are across the subfolders of BETA folder on drive F:
Each one of the files has at least one Revision 1.234 somewhere in the content. (sometimes multiple times - only the first appearance is important)
This is what I have so far:
$files = gci f:\beta\ -Include "*.tlt" -Recurse
$results = $files |
Select-String -Pattern 'Revision:.+.{1}[.]\d{1,3}'|
ForEach-Object { $_.Matches } |
select Value |
Format-Table -GroupBy Filename
What I need is a PowerShell script that searches through the files and returns the list of files with the full path and ONLY the Revision 1.234 but not the whole line.
A single-pipeline solution is possible with the help of calculated properties:
Get-ChildItem f:\beta -Filter *.tlt -Recurse |
Select-String -List -Pattern 'Revision:.+?\.\d{3}' |
Select-Object #{ n='FullName'; e='Path' }, #{ n='Revision'; e={ $_.Matches.Value } }
Sample output:
FullName Revision
-------- --------
/Users/jdoe/foo.tlt Revision: 1.234
/Users/jdoe/sub/bar.tlt Revision: 10.235
As mentioned in TheIncorrigible1's answer, using -Filter performs much better than using -Include, because -Filter filters at the source (lets the filesystem provider do the filtering) rather than collecting all file-info objects first and then letting PowerShell do the filtering.
Select-String -List limits matching in each input file to the first match.
Each match output by Select-String is a [Microsoft.PowerShell.Commands.MatchInfo] instance, which contains rich metadata about each match, such as .Path with the full input filename, and .Matches with information about what the regex (-Pattern) matched - this metadata is used to populate the output custom objects created by Select-Object via the aforementioned calculated properties.
You were close, but you inevitably need to loop through your files. Note -Filter is significantly faster than -Include since it doesn't collect every object before filtering.
$fileList = Get-ChildItem -Path F:\beta -Filter *.tlt -Recurse
$results = foreach ($file in $fileList)
{
$find = $file | Select-String -Pattern '(Revision:.+?\.\d{1,3})'
if ($find)
{
#{
Path = $file.FullName
Rev = $find.Matches.Groups[0].Value
}
}
}

PowerShellRegex match and replace string in kix file

I'm importing a KIX file:
$KIXOLD = get-content E:\File.kix
The file contains content such as this:
$ScriptVer = "12.0" ; Current Script Version Number
I need to get the script version, in this case 12.0, however that number can vary based upon which file I'm importing.
I've tried Select-String and regex like this:
$OLDVER = $KIXOLD | Select-String -Pattern "\$ScriptVer = `"\d\d\.\d`""
But that still grabs the entire line including ; Current Script Version Number and not just the $scriptver = "12.0"
I'd imagine this has to be simple and I'm just going about it all wrong, but nothing I've tried has worked for me.
The end goal would be to just get 12.0 as an int, increment it and replace it, but I can't get that far until I can isolate the $scriptver = "12.0" from the rest of the multi-thousand line KIX file
try this
get-content "E:\File.kix" | where {$_ -like '$ScriptVer*'} | %{$_.split( '=;"')[2]}
Other mehod :
$template=#"
{Row*:ScriptVer = "{Version:12.0}" ; Xxx}
"#
(get-content "E:\File.kix" | ConvertFrom-String -TemplateContent $template).Row.Version
Does this help?
$OLDVER = [regex]::Match($KIXOLD,"ScriptVer = ([1-9]\d.\d)").groups[1].value
Select-String outputs Regex MatchInfo objects. To just get the value, you need to match against it, with a group of the text you want, then expand the match, then expand the group, then expand the value, e.g.
$Ver = Select-String -Path E:\File.kix -Pattern '^\$ScriptVer = "(.*?)"' |
Select-Object -ExpandProperty Matches |
ForEach-Object { $_.Groups[1].Value }