<# .Synopsis Snaffler output file parser .Description Split, sort and beautify the Snaffler output. Adds explorer++ integration for easy file and share browsing (runas /netonly support) .Parameter outformat Output options: - all : write txt, csv, html and json - txt : write txt - csv : write csv - json : write json - html : write html - default : write txt, csv, html .Parameter in Input file (full path or file name) Defaults to snafflerout.txt .Parameter sort Field to sort output: - modified: File modified date (default) - keyword: Snaffler keyword - unc: File UNC Path - rule: Snaffler rule name .Parameter split Will create splitted (by severity black, red, yellow, green) export files .Parameter gridview Analyze the file and display in PS gridview .Parameter gridviewload Switch to load an existing PS gridview output (CSV) .Parameter gridin Input file (full path or filename) Defaults to snafflerout.txt_loot_gridview.csv .Parameter pte pte (pass to explorer) exports the shares to Explorer++ as bookmarks (grouped by host) Explorer++ must be configured to be in Portable mode (settings saved in xml file) and only one instance is allowed. .Parameter snaffel Run Snaffler and execute parser with default settings. .Example .\snafflerparser.ps1 (will try to load snafflerout.txt and output in HTML, CSV and TXT format) .Example .\snafflerparser.ps1 -in mysnaffleroutput.tvs (will try to load mysnaffleroutput.tvs in HTML, CSV and TXT format) .Example .\snafflerparser.ps1 outformat csv -split (will store results as CSV and split the files by severity) .Example .\snafflerparser.ps1 -sort unc (will sort by the column unc) .Example .\snafflerparser.ps1 -gridview (Will additionally show the output in PS Gridview and save the gridview for later use) .Example .\snafflerparser.ps1 -gridviewload (Load a existing gridview (defaults to snafflerout.txt_loot_gridview.csv)) .Example .\snafflerparser.ps1 -gridviewload -gridin mygridviewfile.csv (Load specific gridview file) .Example .\snafflerparser.ps1 -pte (Add Shares as Bookmarks to explorer++) .LINK https://github.com/zh54321/snaffler_parser #> Param ( [String] $in = 'snafflerout.txt', [ValidateSet("modified", "keyword", "rule", "unc")] [String] $sort = "modified", [ValidateSet("all", "csv", "txt", "json","html","default")] [String] $outformat = "default", [switch] $gridview, [switch] $gridviewload, [switch] $split, [String] $gridin = 'snafflerout.txt_loot_gridview.csv', [String] $exlorerpp = '.\Explorer++.exe', [switch] $pte, [switch] $snaffel, [switch] $help ) # Resolve input file path if ([System.IO.Path]::IsPathRooted($in)) { # Absolute path provided $inPath = $in } else { # Relative path or filename only → current directory $inPath = Join-Path -Path (Get-Location) -ChildPath $in } # Normalize (removes .\, ..\, etc.) $inPath = [System.IO.Path]::GetFullPath($inPath) # Function section----------------------------------------------------------------------------------- function Format-TimePrettyUtc { param([object]$value) if ($null -eq $value -or [string]::IsNullOrWhiteSpace([string]$value)) { return "" } try { switch ($value.GetType().FullName) { 'System.DateTimeOffset' { return $value.ToUniversalTime().ToString("yyyy-MM-dd HH:mm:ss 'UTC'") } 'System.DateTime' { return ([DateTime]$value).ToUniversalTime().ToString("yyyy-MM-dd HH:mm:ss 'UTC'") } default { # Try to parse string as DateTimeOffset first (handles Z nicely) $dto = [DateTimeOffset]::Parse( [string]$value, [System.Globalization.CultureInfo]::InvariantCulture, [System.Globalization.DateTimeStyles]::AssumeUniversal ) return $dto.ToUniversalTime().ToString("yyyy-MM-dd HH:mm:ss 'UTC'") } } } catch { # If parsing fails, just return original string return [string]$value } } function Format-DurationPretty { param([Nullable[TimeSpan]]$ts) if ($null -eq $ts) { return "" } $parts = @() if ($ts.Days -gt 0) { $parts += "$($ts.Days)d" } if ($ts.Hours -gt 0) { $parts += "$($ts.Hours)h" } if ($ts.Minutes -gt 0) { $parts += "$($ts.Minutes)m" } # Round to whole seconds (0.5s rounds up) $totalSecondsRounded = [int][math]::Round($ts.TotalSeconds, 0, [MidpointRounding]::AwayFromZero) if ($parts.Count -gt 0) { # show remaining seconds within the minute (also whole) $secWithinMinute = $totalSecondsRounded % 60 $parts += ("{0}s" -f $secWithinMinute) } else { # only seconds (whole) $parts += ("{0}s" -f $totalSecondsRounded) } return ($parts -join " ") } function gridview($action){ if ($action -eq "load") { write-host "[*] Loading stored Gridview file: $($gridin)" if (!(Test-Path -LiteralPath $inpath -PathType Leaf)) { write-host "[-] Input file not found $($gridin) use -gridin to specify the file csv" exit } write-host "[*] Starting Gridview (opens in background)" $passthruobjec = Import-Csv -Path "$($gridin)" | Out-GridView -Title "FullView" -PassThru } elseif ($action -eq "start") { write-host "[*] Writing Gridview output file for further use" $fulloutput | select-object severity,rule,keyword,modified,extension,unc,content | Export-Csv -Path "$($outputname)_loot_gridview.csv" -NoTypeInformation write-host "[*] Starting Gridview (opens in background)" $passthruobjec = $fulloutput | select-object severity,rule,keyword,modified,extension,unc,content | Out-GridView -Title "FullView" -PassThru } $countpassthruobjec = $passthruobjec | Measure-Object -Line -Property unc if ($countpassthruobjec.lines -ge 1) { if (!(Test-Path -Path $exlorerpp -PathType Leaf)) { write-host "[-] Explorer++ not found at $exlorerpp use -explorerpp to specify the exe file" exit } else { write-host "[-] Explorer++ found at $exlorerpp" write-host "[*] Found $($countpassthruobjec.lines) object. Trying to open them in Explorer++ " write-host "[i] Start the script in console window runas ... /netonly to access the files as different user" write-host "[i] Disables the 'Allow multiple instance' in Explorer++ to open multiple location in tabs " foreach ($path in $passthruobjec.unc) { $pathtoopen = (Split-Path -Path $path -Parent) # Danger danger Invoke-Expression & $exlorerpp $pathtoopen Start-Sleep -Milliseconds 500 } } } else { write-host "[!] No PassThru object found" } write-host "[*] Exiting" exit } function explorerpp($objects) { $explorerppfolder = Split-Path $exlorerpp $configPath = Join-Path $explorerppfolder "config.xml" # If exlorerpp is ".\Explorer++.exe", Split-Path returns "." if ($explorerppfolder -eq ".") { $configPath = Join-Path $pwd "config.xml" } # ----------------------------- # Default config.xml template # ----------------------------- $defaultConfig = @' yes no yes yes no yes no no 30090 no yes no no no no no no 0 yes 9 no 0 yes no ::{20D04FE0-3AEA-1069-A2D8-08002B30309D} no 500 yes yes 1 yes yes yes yes yes yes yes yes no yes no yes yes yes no no yes no yes yes no 1 yes 1 yes no no 0 no 208 1 '@ # ----------------------------- # Load or create config.xml # ----------------------------- if (-not (Test-Path -LiteralPath $configPath -PathType Leaf)) { Write-Host "[*] Explorer++ config.xml not found. Creating default at: $configPath" $defaultConfig | Out-File -FilePath $configPath -Encoding UTF8 } else { Write-Host "[*] Found Explorer++ config.xml: $configPath" } # Load XML try { $xmlfile = [xml](Get-Content -LiteralPath $configPath) } catch { Write-Host "[-] Failed to read XML at $configPath" Write-Host " $($_.Exception.Message)" exit } # ----------------------------- # Ensure Settings/ShowBookmarksToolbar=yes # ----------------------------- $settingsNode = $xmlfile.SelectSingleNode("/ExplorerPlusPlus/Settings") if (-not $settingsNode) { $settingsNode = $xmlfile.CreateElement("Settings") [void]$xmlfile.ExplorerPlusPlus.AppendChild($settingsNode) } $showBm = $xmlfile.SelectSingleNode("/ExplorerPlusPlus/Settings/Setting[@name='ShowBookmarksToolbar']") if (-not $showBm) { $showBm = $xmlfile.CreateElement("Setting") [void]$showBm.SetAttribute("name", "ShowBookmarksToolbar") $showBm.InnerText = "yes" [void]$settingsNode.AppendChild($showBm) Write-Host "[*] Added Setting ShowBookmarksToolbar=yes" } else { if ($showBm.InnerText -ne "yes") { $showBm.InnerText = "yes" Write-Host "[*] Updated Setting ShowBookmarksToolbar=yes" } } # ----------------------------- # Ensure Bookmarksv2 + BookmarksToolbar node exists # ----------------------------- $bmRoot = $xmlfile.SelectSingleNode("/ExplorerPlusPlus/Bookmarksv2") if (-not $bmRoot) { $bmRoot = $xmlfile.CreateElement("Bookmarksv2") [void]$xmlfile.ExplorerPlusPlus.AppendChild($bmRoot) } $toolbarNode = $xmlfile.SelectSingleNode("/ExplorerPlusPlus/Bookmarksv2/PermanentItem[@name='BookmarksToolbar']") if (-not $toolbarNode) { $toolbarNode = $xmlfile.CreateElement("PermanentItem") [void]$toolbarNode.SetAttribute("name", "BookmarksToolbar") # Minimal timestamps (Explorer++ seems fine with any ints; keeping your style) [void]$toolbarNode.SetAttribute("DateCreatedLow", "3561811627") [void]$toolbarNode.SetAttribute("DateCreatedHigh", "3561811627") [void]$toolbarNode.SetAttribute("DateModifiedLow", "3561811627") [void]$toolbarNode.SetAttribute("DateModifiedHigh", "3561811627") [void]$bmRoot.AppendChild($toolbarNode) Write-Host "[*] Created BookmarksToolbar container" } # ----------------------------- # Delete existing bookmarks ONLY under BookmarksToolbar # ----------------------------- Write-Host "[*] Deleting existing bookmarks in BookmarksToolbar" $existing = $toolbarNode.SelectNodes("./Bookmark") foreach ($node in @($existing)) { [void]$toolbarNode.RemoveChild($node) } # ----------------------------- # Add new bookmarks grouped by host # ----------------------------- $counteruncstats = 0 $counterhosts = 0 # We'll keep folder "name" indexes stable per host, and bookmark "name" indexes per folder $hostFolders = @{} # server => folderNode $hostCounters = @{} # server => nextBookmarkIndex foreach ($element in $objects.unc) { if ([string]::IsNullOrWhiteSpace($element)) { continue } # Isolate Server: \\server\share\... $server = $null if ($element -match '^\\\\([^\\]+)\\') { $server = $Matches[1] } else { # If it's not a UNC, just bucket it under "(local/other)" $server = "(other)" } if (-not $hostFolders.ContainsKey($server)) { # Create folder bookmark (Type=0) under BookmarksToolbar $folder = $xmlfile.CreateElement("Bookmark") [void]$folder.SetAttribute("name", [string]$counterhosts) [void]$folder.SetAttribute("Type", "0") [void]$folder.SetAttribute("GUID", ([guid]::NewGuid().ToString())) [void]$folder.SetAttribute("ItemName", $server) [void]$folder.SetAttribute("DateCreatedLow", "3561811627") [void]$folder.SetAttribute("DateCreatedHigh", "3561811627") [void]$folder.SetAttribute("DateModifiedLow", "3561811627") [void]$folder.SetAttribute("DateModifiedHigh", "3561811627") [void]$toolbarNode.AppendChild($folder) $hostFolders[$server] = $folder $hostCounters[$server] = 0 $counterhosts++ } # Add the actual bookmark (Type=1) inside the server folder $folderNode = $hostFolders[$server] $idx = [int]$hostCounters[$server] $bm = $xmlfile.CreateElement("Bookmark") [void]$bm.SetAttribute("name", [string]$idx) [void]$bm.SetAttribute("Type", "1") [void]$bm.SetAttribute("GUID", ([guid]::NewGuid().ToString())) [void]$bm.SetAttribute("ItemName", $element) [void]$bm.SetAttribute("Location", $element) [void]$bm.SetAttribute("DateCreatedLow", "3561811627") [void]$bm.SetAttribute("DateCreatedHigh", "3561811627") [void]$bm.SetAttribute("DateModifiedLow", "3561811627") [void]$bm.SetAttribute("DateModifiedHigh", "3561811627") [void]$folderNode.AppendChild($bm) $hostCounters[$server] = $idx + 1 $counteruncstats++ } # ----------------------------- # Save # ----------------------------- try { $xmlfile.Save($configPath) Write-Host "[+] Added $counterhosts bookmark-folders with $counteruncstats bookmarks" Write-Host "[+] Saved: $configPath" } catch { Write-Host "[-] Failed to save XML: $($_.Exception.Message)" exit } } # Function to export as CSV function exportcsv($object ,$name){ write-host "[*] Storing: $($outputname)_loot_$($name).csv" $object | select-object severity,rule,keyword,modified,extension,unc,content | Export-Csv -Path "$($outputname)_loot_$($name).csv" -NoTypeInformation } # Function to export as TXT function exporttxt($object ,$name){ write-host "[*] Storing: $($outputname)_loot_$($name).txt" $object | Format-Table severity,rule,keyword,modified,extension,unc,content -AutoSize | Out-String -Width 10000 | Out-File -FilePath "$($outputname)_loot_$($name).txt" } # Function to export as JSON function exportjson($object ,$name){ write-host "[*] Storing: $($outputname)_loot_$($name).json" $object | select-object severity,rule,keyword,modified,extension,unc,content | ConvertTo-Json -depth 50 | Out-File -FilePath "$($outputname)_loot_$($name).json" } # Function to export as HTML function exporthtml($object ,$name){ # ---------------- JS: data-driven table with pagination ---------------- $Header = @' '@ # ---------------- CSS ---------------- $css = @" "@ # ---------------- HTML skeleton (NO huge table) ---------------- $titleAndTable = @"
SnafflerParser Loot Report
Input: $($baseInfo.Snaffler_File) Files: $filesum B: $blackscount R: $redscount Y: $yellowscount G: $greenscount Parsed: $(Format-TimePrettyUtc $baseInfo.Report_GeneratedUtc)
Repo ↗
severity rule keyword modified unc extension actions content
"@ # ---------------- Build JSON blob from the PS objects ---------------- $rowsForJson = $object | ForEach-Object { [pscustomobject]@{ severity = $_.severity rule = $_.rule keyword = $_.keyword modified = $_.modified unc = $_.unc extension = $_.extension content = $_.content check = $false done = $false } } $json = $rowsForJson | ConvertTo-Json -Depth 6 -Compress $json = $json -replace ' "@ # Put inputInfo + skeleton table + json blob into body $body = "$inputInfo $titleAndTable $dataBlob" $htmlOutput = ConvertTo-Html -Head $css,$Header -Body $body $htmlOutput | Out-File -FilePath "$($outputname)_loot_$($name).html" -Encoding UTF8 } # Script section----------------------------------------------------------------------------------- $banner = @" ____ __ __ _ ____ / ___| _ __ __ _ / _|/ _| | ___ _ __ | _ \ __ _ _ __ ___ ___ _ __ \___ \| _ \ / _ | |_| |_| |/ _ \ '__| | |_) / _ | '__/ __|/ _ \ '__| ___) | | | | (_| | _| _| | __/ | | __/ (_| | | \__ \ __/ | |____/|_| |_|\__,_|_| |_| |_|\___|_| |_| \__,_|_| |___/\___|_| "@ Write-Host $banner -ForegroundColor Cyan $parserStart = [DateTimeOffset]::UtcNow # Check if snaffler should be executed if ($help) { get-help $MyInvocation.MyCommand.Definition -full exit } if ($snaffel) { .\Snaffler.exe -o snafflerout.txt -s -y } # Check if gridviewfile should be loaded if ($gridviewload) { gridview load } # Check snaffler input file and load it write-host "[*] Checking input file $inpath" if (!(Test-Path -LiteralPath $inpath -PathType Leaf)) { write-host "[-] Input file not found $inpath" exit } else { write-host "[+] Input file exists" #Check if file size is at least 300 bytes $FileSize = (Get-ChildItem $inpath).Length / 1014 $FileSizeRound = [math]::Round($FileSize,2) if ($FileSizeRound -ge 0.3) { write-host "[+] Input file is $FileSizeRound KB" write-host "[*] Importing data from file" $outputname = (Get-Item $inpath).BaseName # Streaming containers $files = [System.Collections.Generic.List[object]]::new() $sharesSet = [System.Collections.Generic.HashSet[string]]::new([System.StringComparer]::OrdinalIgnoreCase) $baseInfo = [PsCustomObject]@{ Snaffler_File = Split-Path $inpath -Leaf SHA256 = $(Get-FileHash $inpath).Hash Snaffler_EndTime = $null Snaffler_Duration = $null } $firstLine = Get-Content $inpath -TotalCount 1 # Define the regular expression pattern to extract Computername, User and timestamp $pattern = '\[(?.*?)\\(?.*?)@.*?\]\s+(?\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}Z)' if ($firstLine -match $pattern) { $baseInfo | Add-Member -NotePropertyName Snaffler_ComputerName -NotePropertyValue $matches['machine'] $baseInfo | Add-Member -NotePropertyName Snaffler_User -NotePropertyValue $matches['user'] $baseInfo | Add-Member -NotePropertyName Snaffler_StartTime -NotePropertyValue $matches['timestamp'] } } else { write-host "[!] Input file is less than 0.3 KB" exit } } write-host "[*] Streaming parse of input file" # We already read first line for baseInfo, now stream everything. # Use .NET StreamReader for speed and low memory. $sr = [System.IO.StreamReader]::new($inpath) try { while (-not $sr.EndOfStream) { $raw = $sr.ReadLine() if ([string]::IsNullOrWhiteSpace($raw)) { continue } # Split on tab; keep empties (important because the snaffler output file has blank columns) $cols = $raw.Split("`t", [System.StringSplitOptions]::None) # Need at least 3 columns for Type check: [0]=user, [1]=timestamp, [2]=typ if ($cols.Length -lt 3) { continue } $typ = $cols[2] # ---- Job end/duration info (cheap: only runs for [Info] lines) ---- if ($typ -eq "[Info]" -and $cols.Length -ge 4) { $msg = $cols[3] # Example: "Finished at 21/01/2025 07:30:59" if ($msg -like "Finished at*") { if ($msg -match '^Finished at\s+(?
\d{1,2}/\d{1,2}/\d{4}\s+\d{2}:\d{2}:\d{2})') { $baseInfo.Snaffler_EndTime = $Matches.dt } else { # fallback: store full message if format differs $baseInfo.Snaffler_EndTime = $msg } continue } # Example: "Snafflin' took 00:05:00.0467798" if ($msg -like "Snafflin'*took*") { if ($msg -match "took\s+(?\d{2}:\d{2}:\d{2}(?:\.\d+)?)") { $baseInfo.Snaffler_Duration = $Matches.dur } else { $baseInfo.Snaffler_Duration = $msg } continue } } if ($typ -eq "[Share]") { # In current format, UNC is column index 4 (0-based): user, time, typ, color, unc, rights if ($cols.Length -gt 4) { $shareUnc = $cols[4] if (-not [string]::IsNullOrWhiteSpace($shareUnc)) { [void]$sharesSet.Add($shareUnc) } } continue } if ($typ -eq "[File]") { # A [File] line is tab-separated. Columns by index: # cols[0] user@host # cols[1] timestamp # cols[2] [File] # cols[3] severity (Black/Red/Yellow/Green) # cols[4] rule name # cols[5] readable (R or empty) # cols[6] writable (W or empty) # cols[7] modifiable (M or empty) # cols[8] matched keyword/string # cols[9] file size (bytes) # cols[10] last-modified timestamp # cols[11] UNC path # # Snaffler added a new column after the UNC in release 1.0.244: # OLD (13 cols): cols[12] = content (matched text snippet) # NEW (14 cols): cols[12] = original filename (for SCCM content-lib files; usually empty) # cols[13] = content if ($cols.Length -lt 12) { continue } $unc = $cols[11] if ([string]::IsNullOrWhiteSpace($unc)) { continue } # Support both old (13-col) and new (14-col) Snaffler output formats. # The new format added alt_filename at cols[12]; content shifted to cols[13]. $content = if ($cols.Length -gt 13) { $cols[13] } elseif ($cols.Length -gt 12) { $cols[12] } else { '' } # UNC sanitize for GetExtension $uncSafe = $unc -replace '[\x00-\x1F]', '' if ($IsWindows -or $PSVersionTable.PSVersion.Major -le 5) { $uncSafe = $uncSafe -replace '[<>:"|?*]', '' } $ext = '' try { $ext = [System.IO.Path]::GetExtension($uncSafe) } catch { $ext = '' } $files.Add([PsCustomObject]@{ check = $false done = $false severity = if ($cols.Length -gt 3) { $cols[3] } else { '' } rule = if ($cols.Length -gt 4) { $cols[4] } else { '' } keyword = if ($cols.Length -gt 8) { $cols[8] } else { '' } modified = if ($cols.Length -gt 10) { $cols[10] } else { '' } unc = $unc extension = $ext content = $content }) } } } finally { if ($null -ne $sr) { $sr.Dispose() } } write-host "[*] Processing shares" $shares = $sharesSet | ForEach-Object { [PsCustomObject]@{ unc = $_ } } | Sort-Object -Property unc # Check share count and write to file $sharescount = $shares | Measure-Object -Line -Property unc if ($sharescount.lines -ge 1) { write-host "[+] Shares identified: $($sharescount.lines)" write-host "[*] Writing share output file" $shares | Format-Table -AutoSize | Out-File -FilePath "$($outputname)_shares.txt" } else { write-host "[!] Shares identified: 0" write-host "[?] Was Snaffler executed with parameter -y ?" } # Define fixed severity order $severityRank = @{ Black = 0 Red = 1 Yellow = 2 Green = 3 } # Whether to sort descending $sortDescending = ($sort -eq 'modified') # Sort once: # 1) by severity rank so Black/Red/Yellow/Green always stay grouped + ordered # 2) then by chosen $sort column (descending only for modified) $fulloutput = $files | Sort-Object ` @{ Expression = { $severityRank[$_.severity] } ; Ascending = $true } , @{ Expression = { $_.$sort } ; Descending = $sortDescending } # Group once into a hashtable: keys = "Black"/"Red"/... $bySeverity = $fulloutput | Group-Object -Property severity -AsHashTable -AsString # Pull groups out (always define them as arrays, even if empty) $blacks = @($bySeverity['Black']) $reds = @($bySeverity['Red']) $yellows = @($bySeverity['Yellow']) $greens = @($bySeverity['Green']) # Counts $blackscount = $blacks.Count $redscount = $reds.Count $yellowscount = $yellows.Count $greenscount = $greens.Count $filesum = $blackscount + $redscount + $yellowscount + $greenscount if ($filesum -ge 1) { write-host "[+] Files total: $filesum " write-host "[+] Files with severity BLACK: $blackscount" write-host "[+] Files with severity RED: $redscount" write-host "[+] Files with severity YELLOW: $yellowscount" write-host "[+] Files with severity GREEN: $greenscount" $reportGeneratedUtc = [DateTimeOffset]::UtcNow $parserDuration = $reportGeneratedUtc - $parserStart $baseInfo | Add-Member -NotePropertyName Report_GeneratedUtc -NotePropertyValue $reportGeneratedUtc -Force $baseInfo | Add-Member -NotePropertyName Parser_Duration -NotePropertyValue $parserDuration -Force #Write outputs depening on desired format if ($outformat -eq "all"){ write-host "[*] Exporting full CSV + TXT + JSON + HTML" exporttxt $fulloutput full exportcsv $fulloutput full exportjson $fulloutput full exporthtml $fulloutput full if ($split) { write-host "[*] Exporting splitted CSV + TXT" if ($blackscount -ge 1) {exportcsv $blacks blacks} if ($redscount -ge 1) {exportcsv $reds reds} if ($yellowscount -ge 1) {exportcsv $yellows yellows} if ($greenscount -ge 1) {exportcsv $greens greens} if ($blackscount -ge 1) {exporttxt $blacks blacks} if ($redscount -ge 1) {exporttxt $reds reds} if ($yellowscount -ge 1) {exporttxt $yellows yellows} if ($greenscount -ge 1) {exporttxt $greens greens} if ($blackscount -ge 1) {exportjson $blacks blacks} if ($redscount -ge 1) {exportjson $reds reds} if ($yellowscount -ge 1) {exportjson $yellows yellows} if ($greenscount -ge 1) {exportjson $greens greens} if ($blackscount -ge 1) {exporthtml $blacks blacks} if ($redscount -ge 1) {exporthtml $reds reds} if ($yellowscount -ge 1) {exporthtml $yellows yellows} if ($greenscount -ge 1) {exporthtml $greens greens} } } elseif ($outformat -eq "default") { write-host "[*] Exporting full CSV + TXT + HTML" exporttxt $fulloutput full exportcsv $fulloutput full exporthtml $fulloutput full if ($split) { write-host "[*] Exporting splitted CSV + TXT" if ($blackscount -ge 1) {exportcsv $blacks blacks} if ($redscount -ge 1) {exportcsv $reds reds} if ($yellowscount -ge 1) {exportcsv $yellows yellows} if ($greenscount -ge 1) {exportcsv $greens greens} if ($blackscount -ge 1) {exporttxt $blacks blacks} if ($redscount -ge 1) {exporttxt $reds reds} if ($yellowscount -ge 1) {exporttxt $yellows yellows} if ($greenscount -ge 1) {exporttxt $greens greens} if ($blackscount -ge 1) {exporthtml $blacks blacks} if ($redscount -ge 1) {exporthtml $reds reds} if ($yellowscount -ge 1) {exporthtml $yellows yellows} if ($greenscount -ge 1) {exporthtml $greens greens} } } elseif ($outformat -eq "txt") { write-host "[*] Exporting full TXT" exporttxt $fulloutput full if ($split) { write-host "[*] Exporting splitted TXT" if ($blackscount -ge 1) {exporttxt $blacks blacks} if ($redscount -ge 1) {exporttxt $reds reds} if ($yellowscount -ge 1) {exporttxt $yellows yellows} if ($greenscount -ge 1) {exporttxt $greens greens} } } elseif ($outformat -eq "csv") { write-host "[*] Exporting full CSV" exportcsv $fulloutput full if ($split) { write-host "[*] Exporting splitted CSV" if ($blackscount -ge 1) {exportcsv $blacks blacks} if ($redscount -ge 1) {exportcsv $reds reds} if ($yellowscount -ge 1) {exportcsv $yellows yellows} if ($greenscount -ge 1) {exportcsv $greens greens} } } elseif ($outformat -eq "json") { write-host "[*] Exporting full JSON" exportjson $fulloutput full if ($split) { write-host "[*] Exporting splitted JSON" if ($blackscount -ge 1) {exportjson $blacks blacks} if ($redscount -ge 1) {exportjson $reds reds} if ($yellowscount -ge 1) {exportjson $yellows yellows} if ($greenscount -ge 1) {exportjson $greens greens} } } elseif ($outformat -eq "html") { write-host "[*] Exporting full HTML" exporthtml $fulloutput full if ($split) { write-host "[*] Exporting splitted HTML" if ($blackscount -ge 1) {exporthtml $blacks blacks} if ($redscount -ge 1) {exporthtml $reds reds} if ($yellowscount -ge 1) {exporthtml $yellows yellows} if ($greenscount -ge 1) {exporthtml $greens greens} } } } else { # Error handling if no files detected write-host "[!] Something is wrong. Number of files identified: $filesum" write-host "[?] Was Snaffler executed with parameter -y ?" exit } # Start grid view if desired if ($gridview) { gridview start } # Check if shares should be exported as bookmarks to Explorer++ if ($pte) { write-host "[*] Will export $($sharescount.lines) shares to explorer" explorerpp($shares) }