Public/Search-PatternInFile.ps1
|
Set-StrictMode -Version Latest #### <h2 style="color: #DCA657;">Search-PatternInFile</h2> #### function Search-PatternInFile { #### Recursively searches files for a regex pattern. #### Writes every matching file and every occurrence inside it to a JSON file. #### #### The function writes `SearchPatternInFile.json` under `OutPath` and returns #### a summary containing the file count, the occurrence count, and the output path. #### #### Each entry in the file names one matching path and carries one record per #### matching line, the same shape `Select-String` reports: line number, the #### whole line, and every value matched on it. #### #### ## Examples #### #### ### Search for ToDo #### ```powershell #### Search-PatternInFile -Directory .\src -Pattern 'TODO' -OutPath .\results -Throttle 8 #### ``` #### #### ### Search for IP addresses #### #### ```powershell #### Search-PatternInFile -Directory C:\Users\DirWith\IP\Addresses\ ` #### -Pattern '(?<IP>(?<![\d.])(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(?:\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}(?![\d.]))' ` #### -OutPath .\Generated\ #### ``` #### #### ### Search for home directory paths #### #### ```powershell #### Search-PatternInFile -Directory .\ ` #### -Pattern '(?<HomePath>(?i)[A-Za-z]:[\\/]{1,2}Users[\\/]{1,2}[^\\/\s"<>|,]+)' ` #### -OutPath ..\Generated\ #### ``` #### #### ### Search for email addresses #### #### ```powershell #### Search-PatternInFile -Directory .\ ` #### -Pattern '(?<Email>(?<![A-Za-z0-9._%+-])[A-Za-z0-9._%+-]+@[A-Za-z0-9-]+(?:\.[A-Za-z0-9-]+)*\.[A-Za-z]{2,}(?![A-Za-z0-9.-]))' ` #### -OutPath ..\Generated\ #### ``` #### #### ### Search for private key material #### #### ```powershell #### Search-PatternInFile -Directory .\ ` #### -Pattern '(?<KeyHeader>-----BEGIN (?:[A-Z0-9 ]+ )?PRIVATE KEY-----)' ` #### -OutPath ..\Generated\ #### ``` #### #### ### Search for AWS access key ids #### #### ```powershell #### Search-PatternInFile -Directory .\ ` #### -Pattern '(?<AwsKey>(?<![A-Z0-9])(?:AKIA|ASIA)[0-9A-Z]{16}(?![A-Z0-9]))' ` #### -OutPath ..\Generated\ #### ``` #### #### ### Search for JSON web tokens #### #### ```powershell #### Search-PatternInFile -Directory .\ ` #### -Pattern '(?<Jwt>eyJ[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,})' ` #### -OutPath ..\Generated\ #### ``` #### #### ### Search for hardcoded secret assignments #### #### ```powershell #### $keyword = 'password|passwd|pwd|secret|apikey|api_key|access_token' #### $value = '(?<Value>(?![$<{%(]|null\b|none\b)[^\s,;"]{6,})' #### Search-PatternInFile -Directory .\ ` #### -Pattern "(?<Secret>(?i)\b(?:$keyword)\b\s*[=:]\s*$value)" ` #### -OutPath ..\Generated\ #### ``` #### #### ### Search for download cradles and obfuscation #### #### ```powershell #### Search-PatternInFile -Directory .\ ` #### -Pattern '(?<Cradle>(?i)\b(?:Invoke-Expression|IEX|DownloadString|DownloadFile|FromBase64String|EncodedCommand)\b)' ` #### -OutPath ..\Generated\ #### ``` #### #### ### Search for social security numbers #### #### ```powershell #### Search-PatternInFile -Directory .\ ` #### -Pattern '(?<Ssn>(?<!\d)\d{3}-\d{2}-\d{4}(?!\d))' ` #### -OutPath ..\Generated\ #### ``` #### #### <b style="color: #D2A8FF;">Parameters</b> #### [CmdletBinding()] param ( #### - `[string]`: __Directory__ #### - *Directory to recursively enumerate files in.* [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [string]$Directory, #### - `[string]`: __Pattern__ #### - *Regex passed to `Select-String -Pattern`.* [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [string]$Pattern, #### - `[string]`: __OutPath__ #### - *Directory where `SearchPatternInFile.json` is written.* [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [string]$OutPath, #### - `[int]`: __Throttle__ #### - *Maximum number of parallel workers. Defaults to 4.* [Parameter(Mandatory = $false)] [int]$Throttle = 4 ) # Resolve the search root before starting parallel workers. $resolvedDirectory = Resolve-Path -Path $Directory # Return one record per matching file from each worker. Files with no match # emit nothing, so the results are already the matched set. $results = @( Get-ChildItem -Path $resolvedDirectory -Recurse -File -ErrorAction SilentlyContinue -Force | ForEach-Object -Parallel { $pattern = $using:Pattern $matchInfo = @(Select-String -Path $_.FullName -Pattern $pattern -Encoding utf8 -AllMatches -ErrorAction SilentlyContinue) if ($matchInfo.Count -gt 0) { # Select-String already emits one object per matching line, so keep # that shape and project it. Member enumeration on .Matches pulls the # matched text without walking it. A full MatchInfo does not belong on # the wire out of the worker or in the JSON. # The leading comma keeps a single match an array, so Values is # never a bare string in the JSON. $values = @{ Name = 'Values'; Expression = { , @($_.Matches.Value) } } [PSCustomObject]@{ Path = $_.FullName MatchCount = @($matchInfo.Matches).Count Matches = @($matchInfo | Select-Object -Property LineNumber, Line, $values) } } } -ThrottleLimit $Throttle ) # Workers finish out of order, so sort by path to keep the file diffable. $matchedFiles = @($results | Sort-Object -Property Path) # Measure-Object emits nothing for an empty set, and strict mode rejects the # property read on the resulting null. $matchCount = 0 if ($matchedFiles.Count -gt 0) { $matchCount = [int]($matchedFiles | Measure-Object -Property MatchCount -Sum).Sum } # Persist matches so large result sets do not depend on terminal output. if (-not (Test-Path -Path $OutPath)) { [void](New-Item -Path $OutPath -ItemType Directory -Force) } $jsonPath = Join-Path $OutPath 'SearchPatternInFile.json' $jsonOut = ConvertTo-Json -InputObject $matchedFiles -Depth 10 $jsonOut | Out-File -FilePath $jsonPath -Encoding utf8 # Use the same summary for pipeline output and the optional notification. $summary = "$($matchedFiles.Count) files, $matchCount matches written to $jsonPath" # Skip the notification when BurntToast is unavailable. Reuse one identifier so # repeated searches replace the previous notification. if (Get-Module -ListAvailable -Name BurntToast) { Show-DesktopNotification ` -Title 'Search-PatternInFile complete' ` -Message $summary ` -UniqueIdentifier 'Search-PatternInFile' ` -LaunchPath $jsonPath } #### #### <b style="color: #369FFF;">Returns</b> #### #### - `[string]` #### - *`<files> files, <matches> matches written to <path>`.* #### - *The matches themselves are in the file, not the return value.* #### #### <b style="color: #369FFF;">Side Effect</b> #### #### > Writes this PSCustomObject to a SearchPatternInFile.json in OutPath #### #### - `[PSCustomObject[]]` #### - *One entry per matching file, sorted by path.* #### - `[string]`: __Path__ #### - *Full path of the matching file.* #### - `[int]`: __MatchCount__ #### - *Occurrences found in that file, counting repeats on one line.* #### - `[PSCustomObject[]]`: __Matches__ #### - *One entry per matching line, in file order.* #### - `[int]`: __LineNumber__ — *1-based line the match sits on.* #### - `[string]`: __Line__ — *The whole line, as `Select-String` reports it.* #### - `[string[]]`: __Values__ — *Every value matched on that line.* Write-Output $summary } #### #### --- #### |