Reputation: 43
So I received the task of finding a specific log file in a cyber security lab. I am meant to find the log file for a password tool which contains the information for a man called Alan Jones (username alan.jones).
I have been given the clue of using the findstr command, however, this is only useful for searching through one directory.
Is there a way to use findstr to get powershell to search through the whole drive for a specific string?
Upvotes: 1
Views: 14978
Reputation: 437062
If speed matters, stick with findstr.exe
, and use the /s
switch to perform a recursive search, as vonPryz suggests:
findstr /s /m /c:alan.jones c:\*.log
/s
makes findstr
search recursively; that is, the input directory as well as as all its subdirectories and their subdirectories, ... are searched.
/c:...
looks for the search string literally (default is to treat it as a regular expression, in which case .
is a metacharacter (a character with special meanings) that matches any input character);
/m
prints only the paths of the files in which the search string is found.
Note:
findstr.exe
invariably includes hidden directories and files in the search.
findstr.exe
quietly ignores inaccessible directories, i.e., directories that cannot be accessed due to lack of permissions.
Caveat: The string will only be found in files that use ASCII character encoding or one of its supersets (ANSI, OEM, UTF-8); to match all of the latter, the search string itself must comprise ASCII-range characters only; e.g, it mustn't contain accented letters such as é
.
If you need to also recognize additional character encodings, notably UTF-16LE ("Unicode"), use Select-String
in combination with Get-ChildItem -Recurse
, but note that it will be noticeably slower.
On the plus side, Select-String
:
.Line
property to extract the line only; .Path
contains the full path of the input file.By default, the search argument passed to -Pattern
is interpreted as a regular expression, as with findstr
.
Add the -SimpleMatch
switch to perform a literal string search instead, which not only avoids the need to \
-escape special characters such as .
in the search string, but also speeds up the operation.
As js2010, suggests, another way to speed up the command is to use the -Filter
parameter (e.g., -Filter *.log
) rather than including the wildcard pattern in the -Path
parameter argument (-Path *.log
).
If you need to include hidden directories and files, add the -Force
switch; by default Get-ChildItem
and Get-Item
ignore them.
Given that you're searching the entire drive, you're likely to encounter directories you won't be able to access due to lack of permissions, especially if you don't run with elevation and also include -Force
(but even with elevation (running as admin) access-denied errors can occur).
Use -ErrorAction SilentlyContinue
to silence errors; if you need to inspect errors later, also add something like -ev errs
(-ev
is short for -ErrorVariable
), which collects the errors that occur in variable $errs
.
To put it all together:
# Running with elevation (as admin) lessens the risk of access-denied errors.
# Add -Force to include hidden dirs. and files.
Get-ChildItem -Recurse C:\ -Filter *.log -ErrorAction SilentlyContinue -ev errs |
Select-String -Pattern alan.jones -SimpleMatch
Finally, note that Select-String
can itself target files, but only those in a single directory; also, hidden files are invariably ignored.
E.g., to only search the current directory's *.log
files:
Select-String -Pattern alan.jones -SimpleMatch -Path *.log
Upvotes: 3
Reputation: 27418
I was trying to see if there was some nice parallel way to do it. Here's an interesting yet problematic script from Windows Powershell in Action. It makes a job for each path you give it. Unfortunately, relative paths don't work in jobs.
# Search-FilesInParallel.ps1
# relative paths in jobs are relative to ~\documents
# select-string -list only shows first match in file
# select-string in jobs outputs invisibly
# wait-job -any finishes if any job finishes
param (
[parameter(mandatory=$true, position=0)]
$Pattern,
[parameter(mandatory=$true, position=1)]
[string[]]
$Path,
[parameter()]
$Filter = "*.txt",
[parameter()]
[switch]
$Any
)
$jobid = [Guid]::NewGuid().ToString()
$jobs = foreach ($element in $path)
{
write-host "start-job -name Srch{$jobid}" # added this
Start-Job -name "Srch{$jobid}" -scriptblock { # moved dollar sign to "$jobid"
param($pattern, $path, $filter, $any)
Get-ChildItem -Path $path -Recurse -Filter $filter | # added "|"
Select-String -list:$any $pattern | select * # added "| select *"
} -ArgumentList $pattern,$element,$filter,$any
}
Wait-Job -any:$any $jobs | Receive-Job # -any: an unsuccessful job could finish 1st
Remove-Job -force $jobs
Compile an example list of folders:
$list = ls -r \ -filter *.log -ea 0 | select -expand directoryname -ea 0 | sort -u
Run it with a pattern I know can be found:
.\Search-FilesInParallel.ps1 -path $list -filter *.log -pattern '09/01/2017 23:35:30'
Output without the exceptions:
IgnoreCase : True
LineNumber : 2
Line : INFO: 09/01/2017 23:35:30
Filename : DPINST.LOG
Path : C:\Windows\DPINST.LOG
Pattern : 09/01/2017 23:35:30
Context :
Matches : {09/01/2017 23:35:30}
RunspaceId : 4a9634be-7217-4225-8bcc-bfd326cf722e
IgnoreCase : True
LineNumber : 37
Line : INFO: 09/01/2017 23:35:30
Filename : DPINST.LOG
Path : C:\Windows\DPINST.LOG
Pattern : 09/01/2017 23:35:30
Context :
Matches : {09/01/2017 23:35:30}
RunspaceId : 4a9634be-7217-4225-8bcc-bfd326cf722e
IgnoreCase : True
LineNumber : 39
Line : INFO: 09/01/2017 23:35:30
Filename : DPINST.LOG
Path : C:\Windows\DPINST.LOG
Pattern : 09/01/2017 23:35:30
Context :
Matches : {09/01/2017 23:35:30}
RunspaceId : 4a9634be-7217-4225-8bcc-bfd326cf722e
Upvotes: 0
Reputation: 1768
If you're going to use PowerShell I'd recommend you stick with the native cmdlets over using findstr
, Select-String
can do what you want:
Get-ChildItem C:\*.log -Recurse | Select-String -Pattern "alan\.jones"
For more information on Select-String run
Get-Help Select-String -Online
Upvotes: 3