Advertisement
jargon

7Scan.ps1

Mar 12th, 2025 (edited)
366
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
PowerShell 25.42 KB | Gaming | 0 0
  1. # "7Scan.ps1" - Copyright Tim Keal alias jargon 2025 03/14
  2. # This script scans for 7zip archives and lists file names matched by similarity threshold.
  3. # This script is for Powershell 7 ("pwsh.exe" not "Powershell.exe")
  4.  
  5. # Locate 7-Zip dynamically if not in default location
  6. $SevenZipPath = (Get-Command 7z.exe -ErrorAction SilentlyContinue).Source
  7. if (-not $SevenZipPath) {
  8.     $SevenZipPath = "C:\Program Files\7-Zip\7z.exe"
  9. }
  10.  
  11. $debugMode = $false
  12.  
  13. # Variable to control early exit
  14. $EarlyExit = 0
  15. $hits = @() # Initialize as an empty array instead of a hashtable
  16.  
  17. $scannedArchives = @()
  18. $scannedFiles = @()
  19.  
  20. function str_repeat {
  21.     param (
  22.         [string]$string,
  23.         [int]$count
  24.     )
  25.     return ($string * $count) -join ''
  26. }
  27.  
  28. function Convert-Type {
  29.     param (
  30.         [string]$typeString,
  31.         [Parameter(Mandatory=$true)]
  32.         [Object]$inputValue
  33.     )
  34.  
  35.     # Extract type name if it's enclosed in brackets (e.g., "[int]" -> "int")
  36.     if ($typeString -match "^\[(.+)\]$") {
  37.         $typeString = $matches[1]
  38.     }
  39.  
  40.     # Map common PowerShell type accelerators
  41.     $typeMap = @{
  42.         "int"       = [int]
  43.         "string"    = [string]
  44.         "bool"      = [bool]
  45.         "double"    = [double]
  46.         "datetime"  = [datetime]
  47.         "decimal"   = [decimal]
  48.         "guid"      = [guid]
  49.         "float"     = [float]
  50.         "char"      = [char]
  51.         "byte"      = [byte]
  52.         "sbyte"     = [sbyte]
  53.         "short"     = [int16]
  54.         "ushort"    = [uint16]
  55.         "uint"      = [uint32]
  56.         "long"      = [int64]
  57.         "ulong"     = [uint64]
  58.     }
  59.  
  60.     # Determine target type
  61.     $targetType = $typeMap[$typeString] ?? [System.Type]::GetType("System.$typeString")
  62.  
  63.     if (-not $targetType) {
  64.         throw "Invalid type string: $typeString"
  65.     }
  66.  
  67.     # Attempt conversion using PowerShell's built-in conversion
  68.     try {
  69.         return [System.Management.Automation.LanguagePrimitives]::ConvertTo($inputValue, $targetType)
  70.     } catch {
  71.         throw "Failed to convert value '$inputValue' to type '$typeString'. Error: $_"
  72.     }
  73. }
  74.  
  75. function barLog {
  76.     param (
  77.         [string]$line
  78.     )
  79.    
  80.     if ($line -cmatch "{{ ([a-z]+) bar }}") {
  81.         switch ($matches[1]) {
  82.             "single" { $line = str_repeat -string "-" -count 36 }
  83.             "double" { $line = str_repeat -string "=" -count 36 }
  84.         }
  85.     }
  86.    
  87.     return $line
  88. }
  89.  
  90. function writeLog {
  91.     param (
  92.         [string]$path = "",
  93.         [string]$line = "{{ double bar }}",
  94.         [string]$color = "Yellow",
  95.         [string]$cast = "",
  96.         [string]$append = 1
  97.     )
  98.    
  99.     if ($path -eq "") { return }
  100.     if ($line -eq "") { return }
  101.    
  102.     $line = barLog -line $line
  103.        
  104.     echoLog -line $line -color $color -cast $cast -path $path
  105.    
  106.     if ($cast -ne "") { $line = "$($cast): $($line)" }
  107.    
  108.     if ($append -eq 1) {
  109.         Add-Content -Path $path -Value "$($line)`r`n"
  110.     } else {
  111.         Set-Content -Path $path -Value "$($line)`r`n"
  112.     }
  113. }
  114.  
  115. function debugLog {
  116.     param (
  117.         [string]$line = "",
  118.         [string]$color = "Cyan",
  119.         [string]$cast = "Debug",
  120.         [string]$path = "",
  121.         [string]$append = 0
  122.     )
  123.        
  124.     if ($global:debugMode -eq $false) { return }
  125.    
  126.     $line = barLog -line $line
  127.  
  128.     $color = "Cyan"
  129.     $cast = "Debug"
  130.    
  131.     echoLog -line $line -color $color -cast $cast -path $path -append $append
  132. }
  133.  
  134. function echoLog {
  135.     param (
  136.         [string]$line = "",
  137.         [string]$color = "Yellow",
  138.         [string]$cast = "",
  139.         [string]$path = "",
  140.         [string]$append = 0
  141.        
  142.     )
  143.    
  144.     if ($line -eq "") { return }
  145.    
  146.     $line = barLog -line $line
  147.  
  148.     if ($color -eq "") { $color = "White" }
  149.    
  150.     if ($cast -ne "") { $line = "$($cast): $($line)" }
  151.    
  152.     Write-Host "$($line)" -ForegroundColor $color
  153.  
  154. }
  155.  
  156. # Function to safely load JSON files
  157. function Get-SafeJsonContent {
  158.     param (
  159.         [string]$FilePath
  160.     )
  161.     if (-not (Test-Path $FilePath -PathType Leaf)) {
  162.         echoLog -line "Missing JSON file - $FilePath" -color "Yellow" -cast "Warning"
  163.         return @()  # Return empty array
  164.     }
  165.     try {
  166.         return (Get-Content -Path $FilePath -Raw | ConvertFrom-Json)
  167.     } catch {
  168.         echoLog -line "Invalid JSON format in $($FilePath)" -color "Red" -cast "Error"
  169.         return @()
  170.     }
  171. }
  172.    
  173. $cleanPatterns = (Get-SafeJsonContent "Setup\Patterns.json")
  174.  
  175. # Validate 7-Zip installation
  176. if (-not (Test-Path $SevenZipPath -PathType Leaf)) {
  177.     echoLog -line "7-Zip not found. Please install it or adjust the path." -color "Red" -cast "Error"
  178.     Read-Host "Press Enter to exit"
  179.     exit 1
  180. }
  181.  
  182. echoLog -line "7-Zip found: $($SevenZipPath)" -color "Green" -cast ""
  183.  
  184. function EarlyExit {
  185.  
  186.     # Check if a key is pressed
  187.     if ([System.Console]::KeyAvailable) {
  188.         $key = [System.Console]::ReadKey($true)  # Read the key without displaying it  
  189.         if ($key.Key -eq "Escape") {
  190.             $global:EarlyExit = 1
  191.         }
  192.     }
  193.            
  194.     return $global:EarlyExit
  195. }
  196.  
  197. function archiveAbout {
  198.     param (
  199.         [object]$archiveNameOnly = ""
  200.     )
  201.        
  202.     if ($archiveNameOnly -match $global:config.timestampPattern) {
  203.         $timestamp = $matches[0]
  204.     } else {
  205.         $timestamp = "NO_TIMESTAMP"
  206.     }
  207.    
  208.     $pattern = "^(.*?)[\-\s]+" + [regex]::Escape($timestamp)
  209.  
  210.     if ($archiveNameOnly -match $pattern) {
  211.         $project = $matches[1]
  212.         $project = $project -replace "-", " "
  213.     } else {
  214.         $project = "UNKNOWN_PROJECT"
  215.     }
  216.    
  217.     $cleanedName = ($archiveNameOnly -replace $pattern, "" -replace "\s{2,}", " " -replace "^\s+|\s+$", "")
  218.    
  219.     $pattern = '.*\((.*?)\).*'
  220.     if ($cleanedName -cmatch $pattern) {
  221.         $cleanedName = $matches[1]
  222.     }
  223.    
  224.     foreach ($p in $cleanPatterns) {
  225.         $cleanedName = ($cleanedName -replace $p, " " -replace "\s{2,}", " " -replace "^\s+|\s+$", "")
  226.     }
  227.    
  228.     foreach ($p in $global:config.titleCasePattern) {
  229.         $cleanedName = ($cleanedName -replace $p, { Convert-UpperCaseSeries $_.Value })
  230.     }
  231.    
  232.     $cleanedName = $cleanedName.Trim()
  233.    
  234.     $archiveNameOnly = $archiveNameOnly -replace "\\", "/"
  235.    
  236.     if ($archiveNameOnly -match "^(.+)\/([^\/]+)$") {
  237.         $archivePathOnly = $matches[1]
  238.         $archiveBaseOnly = $matches[2]
  239.     } else {
  240.         # echoLog -line "Regex did not match. Check `$archiveNameOnly." -color "Red" -cast "Debug"
  241.         $archivePathOnly = ""
  242.         $archiveBaseOnly = ""
  243.     }
  244.    
  245.     # Return as an object
  246.     return [PSCustomObject]@{
  247.         archiveNameOnly = $archiveNameOnly
  248.         archivePathOnly = $archivePathOnly
  249.         archiveBaseOnly = $archiveBaseOnly
  250.         cleanedName = $cleanedName
  251.         project = $project
  252.         timestamp = $timestamp
  253.     }
  254. }
  255.  
  256. function fileNameOnly {
  257.     param (
  258.         [string]$line
  259.     )
  260.    
  261.     $fileNameOnly = ($line -replace '^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}\s+\S+\s+\d+\s+', '')
  262.    
  263.     if ($fileNameOnly -match '^\s*$' -or $line -match 'D\.\.\.\.') { return $False }
  264.    
  265.     if ( $fileNameOnly -match "^(.*(\\|\/)){0,}\.($($global:config.searchBlacklist))((\\|\/).*){0,}$" ) { return $False }
  266.    
  267.     $fileNameOnly = $fileNameOnly -replace "\\", "/"
  268.    
  269.     $FileFullName = $fileNameOnly
  270.        
  271.     if ($fileNameOnly -match '([^\\\/]+)$') {
  272.         $fileNameOnly = $matches[1]
  273.     }
  274.    
  275.     if ($fileNameOnly -match '^.*\.([^\.]+)$') {
  276.         $fileExtOnly = $matches[1]
  277.     }
  278.  
  279.     if ($fileNameOnly -match '^(.*)\.[^\.]+$') {
  280.         $fileNameOnly = $matches[1]
  281.     }
  282.    
  283.     # Return as an object
  284.     return [PSCustomObject]@{
  285.         FileFullName = $FileFullName
  286.         FileNameOnly = $fileNameOnly
  287.         FileExtOnly  = $fileExtOnly
  288.     }
  289. }
  290.  
  291. function Convert-UpperCaseSeries {
  292.     param (
  293.         [string]$inputString
  294.     )
  295.  
  296.     if ($inputString.Length -gt 1) {
  297.         return $inputString.Substring(0,1) + $inputString.Substring(1).ToLower()
  298.     }
  299.    
  300.     return $inputString
  301. }
  302.  
  303. # Function to calculate Levenshtein Distance as a percentile
  304. function Get-LevenshteinSimilarity {
  305.     param (
  306.         [string]$source,
  307.         [string]$target,
  308.         [string]$flags = "ickd",
  309.         [double]$percentileTolerance = 0.0  # Default to 0 (no early exit)
  310.        
  311.         # case (i)nsensitive
  312.         # (c)ontains
  313.         # (k)eywords
  314.         # Levenshtein (d)istance
  315.     )
  316.    
  317.     # case (i)nsensitive
  318.     if($flags.Contains("i")) {
  319.         $source = $source.ToLower()
  320.         $target = $target.ToLower()
  321.     }
  322.    
  323.     $sourceLength = $source.Length
  324.     $targetLength = $target.Length
  325.    
  326.     if ($sourceLength -eq 0 -and $targetLength -eq 0) { return 100.000000 }
  327.     if ($sourceLength -ne 0 -and $targetLength -eq 0) { return 0.000000 }
  328.    
  329.     # (c)ontains
  330.     if($flags.Contains("c")) {
  331.         if ($target.Contains($source)) { return 100.000000 }
  332.     }
  333.  
  334.     # (k)eywords
  335.     if($flags.Contains("k")) {
  336.         # Split the source string into an array of keywords
  337.         $keywords = $source -split '\s+'
  338.    
  339.         if ($keywords.Count -gt 0) {
  340.             # Count the number of keywords that appear in the target string
  341.             $count = ($keywords | Where-Object { $target -match "\b$_\b" }).Count
  342.            
  343.             if ($count -gt 0) {
  344.                 return $count / $keywords.Count * 100.000000
  345.             }
  346.         }
  347.     }
  348.    
  349.     # Levenshtein (d)istance
  350.     if($flags.Contains("d")) {
  351.         $distanceMatrix = @()
  352.         for ($i = 0; $i -le $sourceLength; $i++) {
  353.             $distanceMatrix += ,(@(0) * ($targetLength + 1))  
  354.         }
  355.        
  356.         for ($i = 0; $i -le $sourceLength; $i++) { $distanceMatrix[$i][0] = $i }
  357.         for ($j = 0; $j -le $targetLength; $j++) { $distanceMatrix[0][$j] = $j }
  358.        
  359.         for ($i = 1; $i -le $sourceLength; $i++) {
  360.             for ($j = 1; $j -le $targetLength; $j++) {
  361.                 $cost = if ($source[$i - 1] -eq $target[$j - 1]) { 0 } else { 1 }
  362.                
  363.                 $delete = $distanceMatrix[$i - 1][$j] + 1
  364.                 $insert = $distanceMatrix[$i][$j - 1] + 1
  365.                 $substitute = $distanceMatrix[$i - 1][$j - 1] + $cost
  366.                
  367.                 $distanceMatrix[$i][$j] = [math]::Min([math]::Min($delete, $insert), $substitute)
  368.             }
  369.            
  370.             # Early exit condition:
  371.             $maxLength = [math]::Max($sourceLength, $targetLength)
  372.             $currentDistance = $distanceMatrix[$i][$targetLength]
  373.             $projectedSimilarity = ((1 - ($currentDistance / $maxLength)) * 100)
  374.            
  375.             if ($projectedSimilarity -lt $percentileTolerance) {
  376.                 return 0.000000
  377.             }
  378.         }
  379.        
  380.         $distance = $distanceMatrix[$sourceLength][$targetLength]
  381.         $maxLength = [math]::Max($sourceLength, $targetLength)
  382.         $similarity = ((1 - ($distance / $maxLength)) * 100)
  383.         return [math]::Round($similarity, 8)
  384.     }
  385.    
  386.     return 0.000000
  387. }
  388.  
  389. # Function to scan inside .7z files that meet archive similarity criteria
  390. function Scan-Archives {
  391.     param (
  392.         [string]$archiveExt = "7z"
  393.     )
  394.    
  395.     if ((EarlyExit) -eq 1)  { return }
  396.    
  397.     # $basePath = Resolve-Path -Path $global:config.searchPath
  398.     $basePath = $global:config.searchPath
  399.  
  400.     $basePath = $basePath -replace "\\", "/"
  401.  
  402.     if ($basePath -match "^(.+)[\\\/]([^\\\/]+)$") {
  403.         $archivePathOnly = $matches[1]
  404.         $archiveBaseOnly = $matches[2]
  405.     } else {
  406.         # echoLog -line "Regex did not match. Check `$archiveNameOnly." -color "Red" -cast "Debug"
  407.         $archivePathOnly = ""
  408.         $archiveBaseOnly = ""
  409.     }
  410.    
  411.     echoLog -line "Files: $($global:scannedFiles.Count.ToString("N0")) / Archives: $($global:scannedArchives.Count.ToString("N0")) > $($archiveBaseOnly)" -color "Yellow" -cast "Scanning"
  412.    
  413.     try {
  414.        
  415.         $archives = Get-ChildItem -Path $basePath -Filter "*.$($archiveExt)" -Recurse -File | ForEach-Object {
  416.        
  417.             $archive = $_
  418.             $archiveSize = (Get-Item "$($archive)").Length
  419.  
  420.             $fileCount = (& $SevenZipPath l $archive) | Where-Object { $_ -match '^\d{4}-\d{2}-\d{2}' } | Measure-Object | Select-Object -ExpandProperty Count
  421.  
  422.             if ((EarlyExit) -eq 1) {
  423.                
  424.                 throw
  425.             }
  426.                
  427.             if ($archive.FullName -match ".*(\/|\\)\.($($global:config.searchBlacklist))(\/|\\).*") {
  428.                 echoLog -line "$($archive.FullName)" -color "Red" -cast "Blacklisted"
  429.                 # Use 'return' to effectively 'continue' to the next item
  430.                 return
  431.             }
  432.            
  433.             $archiveFullName = $archive.FullName
  434.             $archiveFullName -match "^(.+)[\\\/]([^\\\/]+)$"
  435.             $archivePathOnly = $matches[1]
  436.             $archiveBaseOnly = $matches[2]
  437.            
  438.             $archiveSize = "$([math]::floor(($archiveSize + 999) / 1000).ToString("N0")) ~KiB"
  439.            
  440.             echoLog -line "Contained Files: $($fileCount.ToString("N0")) / Archive Size: $($archiveSize)) for $($archiveFullName)" -color "Yellow" -cast "Mounting"
  441.  
  442.             echoLog -line "Files: $($global:scannedFiles.Count.ToString("N0")) / Archives: $($global:scannedArchives.Count.ToString("N0")) > $($archiveBaseOnly)" -color "Yellow" -cast "Crawling" 
  443.            
  444.             Scan-Archive -archive $archive.FullName
  445.         }
  446.     }
  447.     catch {
  448.         return
  449.     }
  450. }
  451.  
  452. function Scan-Archive {
  453.     param (
  454.         [string]$archive = ""
  455.     )
  456.    
  457.     $global:scannedArchives += $archive
  458.    
  459.     if ((EarlyExit) -eq 1)  { return }
  460.    
  461.     if ($archive -eq "") { return }
  462.    
  463.     $partsArchive = (archiveAbout -archive $archive)
  464.  
  465.     $archiveSimilarity = (Get-LevenshteinSimilarity -source $global:config.archiveTestString -target $partsArchive.cleanedName -flags $global:config.archiveFlags -percentileTolerance $global:config.archiveThreshold)
  466.        
  467.     $formattedArchiveSimilarity = $archiveSimilarity.ToString("000.00000000")
  468.        
  469.     $dumpName = "$($formattedArchiveSimilarity)% > $($partsArchive.archiveBaseOnly) > $($partsArchive.timestamp) > $($partsArchive.cleanedName)"
  470.        
  471.     if ($archiveSimilarity -lt $global:config.archiveThreshold) {
  472.         echoLog -line "Did not meet or exceed archival tolerance of $($global:config.archiveThreshold)% -- is $($archiveSimilarity)% for $($partsArchive.archiveBaseOnly) > $($partsArchive.timestamp) > $($partsArchive.cleanedName)" -color "Red" -cast "Skipping"
  473.     }
  474.  
  475.     echoLog -line "Files: $($global:scannedFiles.Count.ToString("N0")) / Archives: $($global:scannedArchives.Count.ToString("N0")) > $($partsArchive.archiveBaseOnly)" -color "Yellow" -cast "Scanning"
  476.        
  477.     $output = (& $SevenZipPath l -ba "$($archive)" 2>$null)
  478.  
  479.     foreach ($line in $output) {
  480.        
  481.         if ((EarlyExit) -eq 1)  { break }
  482.        
  483.         $partsFile = ( fileNameOnly -line $line )
  484.        
  485.         if ($partsFile -eq $False) { continue }
  486.                
  487.         if ($partsFile.fileExtOnly -notmatch  "^(?:$($global:config.searchExtensions))$") {
  488.             # echoLog -line "$($partsFile.fileExtOnly) > $($partsFile.fileNameOnly)" -color "Red" -cast "Skipping"
  489.             continue
  490.         }
  491.        
  492.         # echoLog -line "$($partsFile.fileExtOnly) > $($partsFile.fileNameOnly)" -color "Blue" -cast "Checking"
  493.                
  494.         $similarity = (Get-LevenshteinSimilarity -source $global:config.searchTerm -target $partsFile.fileNameOnly -flags $global:config.searchFlags -percentileTolerance $global:config.searchThreshold)
  495.        
  496.         $formattedSimilarity = $similarity.ToString("000.00000000")
  497.        
  498.         $matchString = "$($formattedSimilarity)% > $($partsArchive.archiveBaseOnly) > $($partsFile.fileExtOnly) > $($partsFile.fileNameOnly)"
  499.        
  500.         $global:scannedFiles += $partsFile.FileFullName
  501.                        
  502.         if ($similarity -ge $global:config.searchThreshold) {          
  503.            
  504.             $global:hits += "$($formattedSimilarity)% > $($partsArchive.archiveNameOnly) > $($partsFile.FileFullName)"
  505.             # Ensure we are adding to an array
  506.  
  507.             echoLog -line "Hit: $($global:hits.Count.ToString("N0")) > $($matchString)" -color "Green" -cast "Found"
  508.  
  509.         }else{
  510.            
  511.             # echoLog -line "Did not meet or exceed filepath tolerance of $($global:config.searchThreshold)% -- is $($similarity)% for $($partsFile.archiveBaseOnly) > $($partsFile.fileExtOnly) > $($partsFile.fileNameOnly)" -color "Red" -cast "Failed"
  512.         }
  513.     }
  514. }
  515.  
  516. function Json-Results {
  517.     param (
  518.         [array]$results
  519.     )
  520.  
  521.     $arr = @()  # Initialize as an array
  522.  
  523.     foreach ($result in $results) {
  524.        
  525.         $parts = $result -split " > "
  526.  
  527.         # Ensure we have enough parts to avoid index errors
  528.         if ($parts.Count -ge 3) {
  529.            
  530.             $score = $parts[0] -replace "%", ""  # Remove % symbol
  531.  
  532.             $obj = @{
  533.                 score   = $score
  534.                 archive = $parts[1]
  535.                 file    = $parts[2]
  536.             }
  537.  
  538.             $arr += $obj  # Append object to array
  539.         }
  540.     }
  541.    
  542.     # Convert the array to a JSON string
  543.     $jsonString = $arr | ConvertTo-Json -Depth 1
  544.     return $jsonString
  545. }
  546.  
  547. function Json-Config {
  548.     # Convert the array to a JSON string
  549.     $jsonString = $global:config | ConvertTo-Json -Depth 1
  550.     return $jsonString
  551. }
  552.  
  553. function Save-Results {
  554.     param (
  555.         [array]$hits
  556.     )
  557.    
  558.     $path = "Results/$($global:config.configFile)"
  559.     if (-Not (Test-Path -Path $path)) {
  560.         New-Item -Path $path -ItemType Directory | Out-Null
  561.     }
  562.    
  563.     $ts = Get-Date -Format "$($global:config.timestampLayout)"
  564.    
  565.     $outputPrefix = "Results/$($global:config.configFile)/$($global:config.configFile) $($ts)"
  566.    
  567.     $outputFile = "$($outputPrefix) Summary.txt"
  568.     $jsonOutputFile = "$($outputPrefix) Results.json"
  569.     $jsonConfigFile = "$($outputPrefix) Config.json"
  570.    
  571.     $jsonConfig = (Json-Config)
  572.     Set-Content -Path $jsonConfigFile -Value $jsonConfig
  573.    
  574.     $jsonResults = (Json-Results -results $hits)
  575.     Set-Content -Path $jsonOutputFile -Value $jsonResults
  576.  
  577.     writeLog -path $outputFile -line "7Scan Archive Results" -color "White" -cast "" -append 0
  578.    
  579.     # Ensure the Results directory exists
  580.     if (-not (Test-Path -Path "Results")) {
  581.         New-Item -ItemType Directory -Path "Results" | Out-Null
  582.     }
  583.    
  584.     # Ensure $hits is an array before sorting
  585.     $sortedHits = $hits | Sort-Object {
  586.         $similarity = 0
  587.         if ($_ -match '^(\d+\.\d+)%') {
  588.             $similarity = [double]$matches[1]
  589.         }
  590.         $similarity
  591.     } -Descending
  592.    
  593.     $archivesCount = $global:scannedArchives.Count  # Ensure proper counting
  594.    
  595.     $filesCount = $global:scannedFiles.Count  # Ensure proper counting
  596.    
  597.     $hitCount = $sortedHits.Count  # Ensure proper counting
  598.  
  599.     echoLog -line "$($hitCount.ToString("N0")) results to $($outputFile)" -color "Green" -cast "Saving"
  600.  
  601.     # Write to output file
  602.    
  603.     writeLog -path $outputFile -line "7Scan Archive Results" -color "White" -cast "" -append 0
  604.     writeLog -path $outputFile -line "{{ single bar }}" -color "Green" -cast ""
  605.     writeLog -path $outputFile -line "$($filesCount.ToString("N0")) files in $($archivesCount.ToString("N0")) archives" -color "White" -cast "Scanned"
  606.     writeLog -path $outputFile -line "$($hits.Count.ToString("N0"))" -color "White" -cast "Hits"
  607.     writeLog -path $outputFile -line "{{ single bar }}" -color "Blue" -cast ""
  608.  
  609.     # Dynamically write config settings
  610.     foreach ($key in $global:config.Keys) {
  611.         writeLog -path $outputFile -line "$($key): $($global:config[$key])" -color "White" -cast ""
  612.     }
  613.  
  614.     writeLog -path $outputFile -line "{{ double bar }}" -color "Green" -cast ""
  615.  
  616.     writeLog -path $outputFile -line ($sortedHits -join "`r`n") -color "White" -cast ""
  617.  
  618.     writeLog -path $outputFile -line "{{ double bar }}" -color "Green" -cast ""
  619.  
  620.     writeLog -path $outputFile -line "$($hitCount.ToString("N0")) scanned results saved to $($outputFile)" -color "White" -cast ""
  621. }
  622.  
  623. function Convert-Type {
  624.     param (
  625.         [string]$typeString,
  626.         [string]$inputValue
  627.     )
  628.     switch ($typeString) {
  629.         "int" { return [int]$inputValue }
  630.         "bool" { return [bool]$inputValue }
  631.         "float" { return [float]$inputValue }
  632.         "string" { return [string]$inputValue }
  633.         default { return $inputValue }
  634.     }
  635. }
  636.  
  637. function Ini-Config {
  638.     param (
  639.         [string]$configFile = "Default"
  640.     )
  641.  
  642.     # Ensure the global config exists
  643.     if (-not $global:config) {
  644.         $global:config = @{}
  645.     }
  646.  
  647.     # Assign configuration properties
  648.     $global:config.configFile = $configFile
  649.     $global:config.configPath = "Config/$($global:config.configFile) Config.json"
  650.  
  651.     # Debugging: Print config path before proceeding
  652.     debugLog -line "Config path set to '$($global:config.configPath)'" -color "Cyan" -cast "Debug"
  653.  
  654.     # Check if the config file exists
  655.     if (-Not (Test-Path $global:config.configPath)) {
  656.         debugLog -line "Config file not found: $($global:config.configPath)" -color "Red" -cast "Error"
  657.         return $false
  658.     }
  659.  
  660.     return $true
  661. }
  662.  
  663. function Load-Config {
  664.     param (
  665.         [bool]$PromptUser = $true
  666.     )
  667.  
  668.     # Ensure the global config exists
  669.     if (-not $global:config) {
  670.         debugLog -line "Global config is not initialized!" -color "Red" -cast "Error"
  671.         return $false
  672.     }
  673.  
  674.     # Debugging: Print config before JSON loading
  675.     debugLog -line "Checking configPath at start of Load-Config: '$($global:config.configPath)'" -color "Yellow" -cast "Debug"
  676.  
  677.     # Ensure the config path is set
  678.     if (-not $global:config.configPath) {
  679.         debugLog -line "Config path is not set properly at Load-Config start!" -color "Red" -cast "Error"
  680.         return $false
  681.     }
  682.  
  683.     # Debugging: Print config just before reading JSON
  684.     debugLog -line "Attempting to load config from '$($global:config.configPath)'" -color "Yellow" -cast "Debug"
  685.  
  686.     # Check if the config file exists
  687.     if (-Not (Test-Path $global:config.configPath)) {
  688.         debugLog -line "Config file not found: $($global:config.configPath)" -color "Red" -cast "Error"
  689.         return $false
  690.     }
  691.  
  692.     try {
  693.         # Read JSON file
  694.         $rawJson = Get-Content -Path $global:config.configPath -Raw
  695.  
  696.         # Debugging: Print file contents
  697.         debugLog -line "Raw JSON Data: $($rawJson)" -color "Cyan" -cast "Debug"
  698.  
  699.         if (-not $rawJson) {
  700.             debugLog -line "Config file is empty or unreadable!" -color "Red" -cast "Error"
  701.             return $false
  702.         }
  703.  
  704.         $parsedJson = $rawJson | ConvertFrom-Json
  705.  
  706.         # Debugging: Print parsed JSON
  707.         debugLog -line "Parsed JSON: $(($parsedJson | ConvertTo-Json -Compress))" -color "Cyan" -cast "Debug"
  708.  
  709.         if (-not $parsedJson) {
  710.             debugLog -line "Error: Failed to parse JSON file!" -color "Red" -cast "Error"
  711.             return $false
  712.         }
  713.  
  714.         # Special case for configFile (allows reloading)
  715.         if ($parsedJson.PSObject.Properties.Name -contains "configFile") {
  716.             $entry = $parsedJson.configFile
  717.             if ($entry -is [array] -and $entry.Count -ge 2) {
  718.                 $defaultValue = $entry[1]
  719.  
  720.                 # Debugging: Print before modifying config path
  721.                 debugLog -line "Before modifying configPath: '$($global:config.configPath)'" -color "Cyan" -cast "Debug"
  722.  
  723.                 # User input for config file selection
  724.                 if ($PromptUser) {
  725.                     $userInput = Read-Host "Choose a config file (default: '$($defaultValue)')"
  726.                     $global:config.configFile = if ($userInput) { $userInput } else { $defaultValue }
  727.                 } else {
  728.                     $global:config.configFile = $defaultValue
  729.                 }
  730.  
  731.                 # ✅ Fix: Prevent overwriting configPath with an empty string
  732.                 if ($global:config.configFile -and $global:config.configFile -ne "") {
  733.                     $global:config.configPath = "Config/$($global:config.configFile) Config.json"
  734.                     debugLog -line "Updated configPath: '$($global:config.configPath)'" -color "Cyan" -cast "Debug"
  735.                 } else {
  736.                     debugLog -line "Warning: configFile is empty. Retaining previous configPath." -color "Yellow" -cast "Debug"
  737.                 }
  738.  
  739.                 # Revalidate config file existence
  740.                 if (-not (Test-Path $global:config.configPath)) {
  741.                     debugLog -line "Updated config file not found: $global:config.configPath" -color "Red" -cast "Error"
  742.                     return $false
  743.                 }
  744.             }
  745.         }
  746.  
  747.         # Debugging: Final config path after loading JSON
  748.         debugLog -line "Final config path in Load-Config: '$($global:config.configPath)'" -color "Green" -cast "Debug"
  749.  
  750.         return $true
  751.     }
  752.     catch {
  753.         debugLog -line "Error parsing config file: $_" -color "Red" -cast "Error"
  754.         return $false
  755.     }
  756. }
  757.  
  758. function Process-Config {
  759.     param (
  760.         [bool]$PromptUser = $true
  761.     )
  762.  
  763.     # Ensure the global config exists
  764.     if (-not $global:config) {
  765.         echoLog -line "Global config is not initialized!" -color "Red" -cast "Error"
  766.         return $false
  767.     }
  768.  
  769.     # Ensure the config path is set
  770.     if (-not $global:config.configPath) {
  771.         echoLog -line "Config path is not set properly at Load-Config start!" -color "Red" -cast "Error"
  772.         return $false
  773.     }
  774.  
  775.     debugLog -line "Attempting to load config from '$($global:config.configPath)'" -color "Yellow" -cast "Debug"
  776.  
  777.     # Check if the config file exists
  778.     if (-Not (Test-Path $global:config.configPath)) {
  779.         echoLog -line "Config file not found: $($global:config.configPath)" -color "Red" -cast "Error"
  780.         return $false
  781.     }
  782.  
  783.     try {
  784.         # Read JSON file
  785.         $rawJson = Get-Content -Path $global:config.configPath -Raw
  786.         if (-not $rawJson) {
  787.             echoLog -line "Config file is empty or unreadable!" -color "Red" -cast "Error"
  788.             return $false
  789.         }
  790.  
  791.         # Convert JSON to object
  792.         $parsedJson = $rawJson | ConvertFrom-Json
  793.         if (-not $parsedJson) {
  794.             echoLog -line "Error: Failed to parse JSON file!" -color "Red" -cast "Error"
  795.             return $false
  796.         }
  797.  
  798.         # Process configuration
  799.         foreach ($key in $parsedJson.PSObject.Properties.Name) {
  800.             $valueArray = $parsedJson.$key  # Expecting: ["type", "default_value"]
  801.  
  802.             # Validate the expected structure
  803.             if ($valueArray -isnot [System.Array] -or $valueArray.Count -ne 2) {
  804.                 echoLog -line "Skipping invalid config entry: $key" -color "Red" -cast "Error"
  805.                 continue
  806.             }
  807.  
  808.             $type = $valueArray[0]  # Expected type
  809.             $defaultValue = $valueArray[1]  # Default value
  810.  
  811.             # Skip `configFile` after reading it
  812.             if ($key -eq "configFile") {
  813.                 $global:config[$key] = $defaultValue
  814.                 continue
  815.             }
  816.  
  817.             # Prompt user if necessary
  818.             if ($PromptUser) {
  819.                 $userInput = Read-Host "Enter value for '$key' (default: '$defaultValue')"
  820.                 $finalValue = if ($userInput) { $userInput } else { $defaultValue }
  821.             } else {
  822.                 $finalValue = $defaultValue
  823.             }
  824.  
  825.             # Ensure type safety
  826.             switch ($type) {
  827.                 "double" {
  828.                     $global:config[$key] = [double]$finalValue
  829.                 }
  830.                 "int" {
  831.                     $global:config[$key] = [int]$finalValue
  832.                 }
  833.                 "string" {
  834.                     $global:config[$key] = [string]$finalValue
  835.                 }
  836.                 default {
  837.                     echoLog -line "Unknown type '$type' for '$key', storing as string." -color "Yellow" -cast "Warning"
  838.                     $global:config[$key] = [string]$finalValue
  839.                 }
  840.             }
  841.         }
  842.        
  843.         return $true
  844.     }
  845.     catch {
  846.         echoLog -line "Error parsing config file: $_" -color "Red" -cast "Error"
  847.         return $false
  848.     }
  849. }
  850.  
  851. $config = @{}
  852.  
  853. # Main Execution
  854. if (Ini-Config -configFile "Default") {
  855.    
  856.     debugLog -line "Confirming config path before Load-Config: '$($config.configPath)'" -color "Cyan" -cast "Debug"
  857.    
  858.     # Debugging: Print entire global config
  859.     debugLog -line "Global Config Dump BEFORE Load-Config: $(($config | ConvertTo-Json -Compress))" -color "Cyan" -cast "Debug"
  860.  
  861.     if (Load-Config -PromptUser $true) {
  862.         if (Process-Config -PromptUser $true) {
  863.             Scan-Archives
  864.         }
  865.     }
  866. }
  867. else {
  868.     debugLog -line "Ini-Config failed! Check why config path is not being set." -color "Red" -cast "Error"
  869. }
  870.  
  871. Save-Results -hits $hits
  872. Read-Host "Press Enter to exit"
  873.  
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement