diff --git a/Windows-Scripts/checksum.ps1 b/Windows-Scripts/checksum.ps1 index 2a5bdca..dac3767 100644 --- a/Windows-Scripts/checksum.ps1 +++ b/Windows-Scripts/checksum.ps1 @@ -5,15 +5,15 @@ param( [Parameter(Position = 0, ParameterSetName = 'Match', ValueFromPipeline = $true)] [string[]]$FilePath, - # Custom output file (-f / -OutFile) + # Custom output file (-f / -OutFile / -of) [Parameter(Mandatory = $false, ParameterSetName = 'Generate')] - [Alias('f')] + [Alias('f', 'of', 'OF')] [string]$OutFile, - # Auto output file switch (-o) + # Auto output file switch (-s / -Save) [Parameter(Mandatory = $false, ParameterSetName = 'Generate')] - [Alias('o')] - [switch]$AutoOut, + [Alias('s', 'S')] + [switch]$Save, # File containing checksums to verify (-c / -Check) [Parameter(Mandatory = $false, ParameterSetName = 'Verify')] @@ -36,6 +36,11 @@ param( [Alias('r')] [switch]$Recursive, + # Princess Pi art flag (-pp / -PrincessPi) + [Parameter(Mandatory = $false)] + [Alias('pp')] + [switch]$PrincessPi, + # Help flag (-h / -Help) [Parameter(Mandatory = $false)] [Alias('h')] @@ -49,9 +54,10 @@ NAME checksum - Compute and verify message digests / file checksums SYNOPSIS - .\checksum.ps1 [-FilePath] [-o] [-f ] [-Algorithm ] [-r] + .\checksum.ps1 [-FilePath] [-s] [-f ] [-Algorithm ] [-r] .\checksum.ps1 [-FilePath] -Match [-Algorithm ] [-r] .\checksum.ps1 -Check [-Algorithm ] [-r] + .\checksum.ps1 -PrincessPi .\checksum.ps1 -Help DESCRIPTION @@ -63,15 +69,15 @@ PARAMETERS -FilePath Specifies the target file(s) or wildcard pattern (*). - -o, -O + -s, -S, -Save Auto-saves checksum results. (Takes no arguments). - If input filename contains a wildcard (*), saves to 'checksums.'. - If input filename does NOT contain a wildcard, saves to '.'. When combined with -r and *, recursively auto-generates checksums per directory. - -f, -F, -OutFile + -f, -F, -OutFile, -of, -OF Saves checksum results to a specific custom filename. - Overrides the auto-naming behavior of -o if both are passed. + Overrides the auto-naming behavior of -s if both are passed. -c, -C, -Check Reads hashes from a specified checksum file and verifies target files on disk. @@ -88,15 +94,18 @@ PARAMETERS -r, -R, -Recursive Processes target files or verification files recursively across directory structures. + -pp, -PrincessPi + Displays Princess Pi ASCII art in magenta. + -h, -H, -Help Displays this help documentation. EXAMPLES 1. Compute SHA256 for a single file and auto-save as 'myfile.iso.sha256': - .\checksum.ps1 myfile.iso -o + .\checksum.ps1 myfile.iso -s 2. Compute SHA512 recursively and auto-save as 'checksums.sha512' in each folder: - .\checksum.ps1 * -r -o -a SHA512 + .\checksum.ps1 * -r -s -a SHA512 3. Compute SHA256 checksums and save to a specific custom file: .\checksum.ps1 * -f my_hashes.txt @@ -109,6 +118,70 @@ EXAMPLES '@ }; + function Write-ErrorWithHelp { + param([string]$ErrorMessage); + Write-Error $ErrorMessage; + Write-Host ""; + Show-Help; + }; + + function Show-PrincessPiArt { + $art = @' + .. ..... + ....------.. + ..:--------.. ....... + ..----------.. ....---.. + ...---------=------::....:----:.. + ..-----=+**************=-----:... + .----+****************=-----... + .--=****************=-----=**+:::--:.... + .-#*********##**++=---====++**+-----:. + ..:==********#=.+@%-==++===++===++*=----... + .:=::******%%+-::=++====+==++====+**=--... + .+..+*****--@===-:-%--====-=+====+*#=:.. + .-.-****#=.#+===--=+===----======+*+.. + .::=:+****#@#+=====-**-----*+====++**:. + .. .=****=--======-..%-----%+==++***+-.. + ..=++***+-.----======..*-----#+==+***#%#+.. + .. .:-..---=====--------=-+==+**-@*. ............::::::.... + ..=:..--===+-:-------*%+==*+#%**=. .....:-------------------------:. + ..............-:..--===+-..:-------+==+==.:... ...:----------------------------::. + ......:----------::=:...--==+--:.:-------+==+=:.... ....--------------------------:..... + ..:::----------------=-::.:--===---:.:------=--:.... ...:---------------------------... + ..:------------------=--:..:--=+=--------::.:+-..... ...------------------------------.. + ...:-----------------:....----==-------:...=%*.......:::--------------------------------:.. + ...--------------:+......---------------:+%%#.----------------------------:.......-----:.. + .:----===-------:-.. .:--------------*%%%%=--------------========------:.......-------:... + ..-:.:------------....-.=---------=#%%%%##%------------=-----------------------------------.. + .:--:....:--------:##*=:.-------=#%%%%%%#%+-----------=-------------------------------------: + ..:-----------------::*%%#-------=###%%%%##=--------------------------------------:............ + ..:-------------------:.-#%%##+==+%%%%%%#%+------------------------...:::--------------::::::.. + ...:----------------------..-#%#%%%%%%%%*+#%=-------------------------=-:....:----------------:... + --------------------------:.-#%%%%%%%%+#@%%%--------------------------=++=-::---------------:... + ---------------------------...+%%%%%]%%%%#---------------------------------------------:.... + ............------.....----:....-#%%%%%%%*=------------------------------::+=---------....... + ..:-... ....-----.. ....-=-------------------------------------.=*+=....... + ..---:....:-----:.......:--------------------------:-------------==+=-...... + .:-------------..........:---------------------------..:-------------==.......... + ..-------------:... ...--------:...----------------....:-----------==-........... + ..:----==------:..-.... ..--------:...:---------------.....:----------=+-..... + ..------==+=++==-..+--:. .:--------.....-----------=*%=.. ..----------=+=...... + ....------------........:... ..:--------.....-------+#%%#@%.. ...----------=+=...... + ..---------===:............... .:----=+*#=. ..--+#**+%@@@#%+. ..:-:..=----=*=:..... + :............:=::+=:--:=.=-.::=... ..*@*-+%@@@:.....-@@#%@*#@@@#%:.. ..-:..---:-#**+-......... + .. ...::::....::-.:.::-.. ..*%:..*@@@*.. ..-%#%@@+%@@@@=.. ...::.=#=..-%*++=:... ... + ...... .......... ..**...*#=*%:. .--*#@%+#%@@@@+... ..*%@%+...+%+=*+=-... + ..*#:.-#+..+-. .--*#**%%*%@@%=.. ..-%@@*:..+@%=:+**+=:.. + ..*@#*%%=..==. .-*%@#%@##@@@#-. ...#@@%*-=%@@#:..-=++=- + .:#@*-:++..==. .=#%@%*@@*%*=+*:....:.+@*::+%@@@%=......-* + .-%+...=%##%-. .+@*@%*%@##*-:*+..:-==*@=..:#@+:=*:....... + .:*#:..:*@@@%:. .--%@*%@#*%*#=..-%-....:+#=-.:#%-.:+-....... + .+@#=.-*@@@@*.. ....-%@@@@@@=..+%*.. .--%--=+#%-.:*-. .... + .-=++++++++=... .:***+++==::::... .-%#=-*#%#**#-. +'@ + Write-Host $art -ForegroundColor Magenta; + }; + function Get-AlgorithmFromHashLength { param([string]$HashString); @@ -119,7 +192,7 @@ EXAMPLES 96 { return 'SHA384'; } 128 { return 'SHA512'; } default { - Write-Error "checksum: Could not auto-detect algorithm for hash length $($HashString.Length). Please specify -Algorithm."; + Write-ErrorWithHelp "checksum: Could not auto-detect algorithm for hash length $($HashString.Length). Please specify -Algorithm."; return $null; } }; @@ -142,6 +215,11 @@ process { return; }; + if ($PrincessPi.IsPresent) { + Show-PrincessPiArt; + return; + }; + if (-not [string]::IsNullOrWhiteSpace($Algorithm)) { $Algorithm = $Algorithm.ToUpper(); }; @@ -149,7 +227,7 @@ process { # --- ITEM GATHERING (Generate & Match Modes) --- if ($PSCmdlet.ParameterSetName -in @('Generate', 'Match')) { if (-not $FilePath -or $FilePath.Count -eq 0) { - Write-Error "checksum: Missing file path parameter. Use -Help for usage."; + Write-ErrorWithHelp "checksum: Missing file path parameter. Use -Help for usage."; return; }; @@ -173,7 +251,7 @@ process { }; if (-not $resolvedItems) { - Write-Error "checksum: $($pathEntry): No such file or directory"; + Write-ErrorWithHelp "checksum: $($pathEntry): No such file or directory"; continue; }; @@ -204,17 +282,26 @@ process { $expectedHash = $Match.Trim().ToLower(); - foreach ($item in ($allItems)) { - $actualHash = (Get-FileHash -Path ($item.FullName) -Algorithm ($Algorithm)).Hash.ToLower(); + $allItems | ForEach-Object -Parallel { + $item = $_ + $algo = $using:Algorithm + $exp = $using:expectedHash + + $actualHash = (Get-FileHash -Path ($item.FullName) -Algorithm ($algo)).Hash.ToLower(); $targetFile = Resolve-Path -Path ($item.FullName) -Relative -ErrorAction 'SilentlyContinue'; if (-not $targetFile) { $targetFile = $item.Name; }; if ($targetFile.StartsWith(".\") -or $targetFile.StartsWith("./")) { $targetFile = $targetFile.Substring(2); }; - if ($actualHash -eq $expectedHash) { - Write-Host "${targetFile}: " -NoNewline; + [PSCustomObject]@{ + TargetFile = $targetFile + IsMatch = ($actualHash -eq $exp) + } + } -ThrottleLimit 16 | ForEach-Object { + if ($_.IsMatch) { + Write-Host "$($_.TargetFile): " -NoNewline; Write-Host "OK" -ForegroundColor Green; } else { - Write-Host "${targetFile}: " -NoNewline; + Write-Host "$($_.TargetFile): " -NoNewline; Write-Host "FAILED" -ForegroundColor Red; }; }; @@ -232,7 +319,7 @@ process { }; if (-not $checkFiles -or $checkFiles.Count -eq 0) { - Write-Error "checksum: $($Check): No such file or directory"; + Write-ErrorWithHelp "checksum: $($Check): No such file or directory"; return; }; @@ -309,7 +396,7 @@ process { }; $wantCustomOut = $PSBoundParameters.ContainsKey('OutFile'); - $wantAutoOut = $AutoOut.IsPresent; + $wantAutoOut = $Save.IsPresent; if ($wantCustomOut -or $wantAutoOut) { # Recursive wildcard auto-saves per directory @@ -326,15 +413,16 @@ process { $currentOutFile = Join-Path -Path ($dirPath) -ChildPath "checksums.$($Algorithm.ToLower())"; }; - $linesToWrite = @(); - foreach ($item in ($filesInDir)) { - $hash = (Get-FileHash -Path ($item.FullName) -Algorithm ($Algorithm)).Hash.ToLower(); - $relativePath = $item.Name; - - $outputLine = "${hash} ${relativePath}"; - $linesToWrite += $outputLine; + $linesToWrite = $filesInDir | ForEach-Object -Parallel { + $item = $_ + $algo = $using:Algorithm + $hash = (Get-FileHash -Path ($item.FullName) -Algorithm ($algo)).Hash.ToLower(); + "${hash} $($item.Name)" + } -ThrottleLimit 16; + + foreach ($outputLine in $linesToWrite) { Write-Output $outputLine; - }; + } Set-Content -Path ($currentOutFile) -Value $linesToWrite -Encoding utf8; }; @@ -348,27 +436,31 @@ process { $singleOutFile = "checksums.$($Algorithm.ToLower())"; }; - $linesToWrite = @(); - foreach ($item in ($allItems)) { - $hash = (Get-FileHash -Path ($item.FullName) -Algorithm ($Algorithm)).Hash.ToLower(); + $linesToWrite = $allItems | ForEach-Object -Parallel { + $item = $_ + $algo = $using:Algorithm + $hash = (Get-FileHash -Path ($item.FullName) -Algorithm ($algo)).Hash.ToLower(); $relativePath = Resolve-Path -Path ($item.FullName) -Relative -ErrorAction 'SilentlyContinue'; if (-not $relativePath) { $relativePath = $item.Name; }; if ($relativePath.StartsWith(".\") -or $relativePath.StartsWith("./")) { $relativePath = $relativePath.Substring(2); }; + "${hash} ${relativePath}" + } -ThrottleLimit 16; - $outputLine = "${hash} ${relativePath}"; - $linesToWrite += $outputLine; + foreach ($outputLine in $linesToWrite) { Write-Output $outputLine; - }; + } Set-Content -Path ($singleOutFile) -Value $linesToWrite -Encoding utf8; } # Auto file for explicit individual files (No wildcard) else { - foreach ($item in ($allItems)) { - $hash = (Get-FileHash -Path ($item.FullName) -Algorithm ($Algorithm)).Hash.ToLower(); + $results = $allItems | ForEach-Object -Parallel { + $item = $_ + $algo = $using:Algorithm + $hash = (Get-FileHash -Path ($item.FullName) -Algorithm ($algo)).Hash.ToLower(); $relativePath = Resolve-Path -Path ($item.FullName) -Relative -ErrorAction 'SilentlyContinue'; if (-not $relativePath) { $relativePath = $item.Name; }; @@ -376,20 +468,25 @@ process { $relativePath = $relativePath.Substring(2); }; - $outputLine = "${hash} ${relativePath}"; - Write-Output $outputLine; - - # Save directly next to the original file: . - $itemOutFile = "$($item.FullName).$($Algorithm.ToLower())"; - Set-Content -Path ($itemOutFile) -Value $outputLine -Encoding utf8; - }; + [PSCustomObject]@{ + OutputLine = "${hash} ${relativePath}" + ItemOutFile = "$($item.FullName).$($algo.ToLower())" + } + } -ThrottleLimit 16; + + foreach ($res in $results) { + Write-Output $res.OutputLine; + Set-Content -Path ($res.ItemOutFile) -Value $res.OutputLine -Encoding utf8; + } }; }; } # Standard console output (No save flags passed) else { - foreach ($item in ($allItems)) { - $hash = (Get-FileHash -Path ($item.FullName) -Algorithm ($Algorithm)).Hash.ToLower(); + $linesToWrite = $allItems | ForEach-Object -Parallel { + $item = $_ + $algo = $using:Algorithm + $hash = (Get-FileHash -Path ($item.FullName) -Algorithm ($algo)).Hash.ToLower(); $relativePath = Resolve-Path -Path ($item.FullName) -Relative -ErrorAction 'SilentlyContinue'; if (-not $relativePath) { $relativePath = $item.Name; }; @@ -397,9 +494,12 @@ process { $relativePath = $relativePath.Substring(2); }; - $outputLine = "${hash} ${relativePath}"; + "${hash} ${relativePath}" + } -ThrottleLimit 16; + + foreach ($outputLine in $linesToWrite) { Write-Output $outputLine; - }; + } }; } }