# encoding: utf-8 # api: powershell # title: chkhash.ps1 # description: ChkHash.ps1 – ChkHash.ps1 can create a .XML database of files and their SHA-512 hashes and check files against the database, in order to detect corrupt or hacked files. # version: 0.1 # type: function # author: James Gentile # license: CC0 # function: Get-SHA512 # x-poshcode-id: 2876 # x-archived: 2014-12-22T02:45:39 # x-published: 2011-07-28T17:31:00 # # Run with -h option for help and usage. Options include -u to add new and modified files to a database, -e to exclude directories, -c to create a database, -x to specify a .xml file to create/use. Subdirs are automatically processed. # # calculate SHA512 of file. function Get-SHA512([System.IO.FileInfo] $file = $(throw 'Usage: Get-MD5 [System.IO.FileInfo]')) { $stream = $null; $cryptoServiceProvider = [System.Security.Cryptography.SHA512CryptoServiceProvider]; $hashAlgorithm = new-object $cryptoServiceProvider $stream = $file.OpenRead(); $hashByteArray = $hashAlgorithm.ComputeHash($stream); $stream.Close(); ## We have to be sure that we close the file stream if any exceptions are thrown. trap { if ($stream -ne $null) { $stream.Close(); } break; } foreach ($byte in $hashByteArray) { if ($byte -lt 16) {$result += “0{0:X}” -f $byte } else { $result += “{0:X}” -f $byte }} return [string]$result; } function noequal ( $first, $second) { if (!($second) -or $second -eq "") {return $true} $first=join-path $first "\" foreach($s in $second) { if ($first.tolower().startswith($s.tolower())) {return $false} } return $true } # functions to print overwriting multi-line messages. Test script will accept a file/filespec/dir and iterate through all files in all subdirs printing a test message + file name to demostrate. # e.g. PS>.\writefilename.ps1 c:\ # call WriteFileName [string] # after done writing series of overwriting messages, call WriteFileNameEnd function WriteFileName ( [string]$writestr ) # this function prints multiline messages on top of each other, good for iterating through filenames without filling { # the console with a huge wall of text. Call this function to print each of the filename messages, then call WriteFileNameEnd when done # before printing anything else, so that you are not printing into a long file name with extra characters from it visible. if ($Host.Name -match 'ise') { write-host $writestr; return } if ($global:wfnlastlen -eq $null) {$global:wfnlastlen=0} $ctop=[console]::cursortop $cleft=[console]::cursorleft $oldwritestrlen=$writestr.length $rem=$null $writelines = [math]::divrem($writestr.length+$cleft, [console]::bufferwidth, [ref]$rem) #if ($rem -ne 0) {$writelines+=1} $cwe = ($writelines-(([console]::bufferheight-1)-$ctop)) # calculate where text has scroll back to. if ($cwe -gt 0) {$ctop-=($cwe)} write-host "$writestr" -nonewline $global:wfnoldctop=[console]::cursortop $global:wfnoldcleft=[console]::cursorleft if ($global:wfnlastlen -gt $writestr.length) { write-host (" " * ($global:wfnlastlen-$writestr.length)) -nonewline # this only overwrites previously written text if needed, so no need to compute buffer movement on this } $global:wfnlastlen = $oldwritestrlen if ($ctop -lt 0) {$ctop=$cleft=0} [console]::cursortop=$ctop [console]::cursorleft=$cleft } function WriteFileNameEnd ( $switch=$true) # call this function when you are done overwriting messages on top of each other { # and before printing something else. Default switch=$true, which prints a newline, $false restores cursor position same line. if ($Host.Name -match 'ise') { return } if ($global:wfnoldctop -ne $null -and $global:wfnoldcleft -ne $null) { [console]::cursortop=$global:wfnoldctop [console]::cursorleft=$wfnoldcleft if ($global:wfnoldcleft -ne 0 -and $switch) { write-host "" } } $global:wfnoldctop=$null $global:wfnlastlen=$null $global:wfnoldcleft=$null } # chkhash.ps1 [file(s)/dir #1] [file(s)/dir #2] ... [file(s)/dir #3] [-u] [-h [path of .xml database]] # -u updates the XML file database and exits # otherwise, all files are checked against the XML file database. # -h specifies location of xml hash database $hashespath=".\hashes.xml" del variable:\args3 -ea 0 del variable:\args2 -ea 0 del variable:\xfiles -ea 0 del variable:\files -ea 0 del variable:\exclude -ea 0 $args3=@() $args2=@($args) $nu = 0 $errs = 0 $fc = 0 $fm = 0 $upd = $false $create = $false $n = $null "ChkHash.ps1 - ChkHash.ps1 can create a .XML database of files and their SHA-512 hashes and check files against the database, " "in order to detect corrupt or hacked files." "" ".\chkhash.ps1 -h for usage." "" for($i=0;$i -lt $args2.count; $i++) { if ($args2[$i] -like "-h*") # -help specified? { "Usage: .\chkhash.ps1 [-h] [-u] [-c] [-x ] [file(s)/dir #1] [file(s)/dir #2] ... [file(s)/dir #n] [-e ]" "Options: -h - Help display." " -c - Create hash database. If .xml hash database does not exist, -c will be assumed." " -u - Update changed files and add new files to existing database." " -x - specifies .xml database file path to use. Default is .\hashes.xml" " -e - exclude dirs. Put this after the files/dirs you want to check with SHA512 and needs to be fullpath (e.g. c:\users\bob not ..\bob)." "" "Examples: PS>.\chkhash.ps1 c:\ d:\ -c -x c:\users\bob\hashes\hashes.xml" " [hash all files on c:\ and d:\ and subdirs, create and store hashes in c:\users\bob\hashes\hashes.xml]" " PS>.\chkhash.ps1 c:\users\alice\pictures\sunset.jpg -u -x c:\users\alice\hashes\pictureshashes.xml]" " [hash c:\users\alice\pictures\sunset.jpg and add or update the hash to c:\users\alice\hashes\picturehashes.xml" " PS>.\chkhash.ps1 c:\users\eve\documents d:\media\movies -x c:\users\eve\hashes\private.xml" " [hash all files in c:\users\eve\documents and d:\media\movies, check against hashes stored in c:\users\eve\hashes\private.xml" " or create it and store hashes there if not present]" " PS>.\chkhash.ps1 c:\users\eve -x c:\users\eve\hashes\private.xml -e c:\users\eve\hashes" " [hash all files in c:\users\eve and subdirs, check hashes against c:\users\eve\hashes\private.xml or store if not present, exclude " " c:\users\eve\hashes directory and subdirs]" " PS>.\chkhash.p1s c:\users\ted\documents\f* d:\data -x d:\hashes.xml -e d:\data\test d:\data\favorites -u" " [hash all files starting with 'f' in c:\users\ted\documents, and all files in d:\data, add or update hashes to" " existing d:\hashes.xml, exclude d:\data\test and d:\data\favorites and subdirs]" " PS>.\chkhash -x c:\users\alice\hashes\hashes.xml" " [Load hashes.xml and check hashes of all files contained within.]" "" "Note: files in subdirectories of any specified directory are automatically processed." " if you specify only an -x option, or no option and .\hash.xml exists, only files in the database will be checked." exit } if ($args2[$i] -like "-u*") {$upd=$true;continue} # Update and Add new files to database? if ($args2[$i] -like "-c*") {$create=$true;continue} # Create database specified? if ($args2[$i] -like "-x*") { $i++ # Get hashes xml database path if ($i -ge $args2.count) { write-host "-X specified but no file path of .xml database specified. Exiting." exit } $hashespath=$args2[$i] continue } if ($args2[$i] -like "-e*") # Exclude files, dirs { while (($i+1) -lt $args2.count) { $i++ if ($args2[$i] -like "-*") {break} $exclude+=@(join-path $args2[$i] "\") # collect array of excluded directories. } continue } $args3+=@($args2[$i]) # Add files/dirs } if ($args3.count -ne 0) { # Get list of files and SHA512 hash them. "Enumerating files from specified locations..." $files=@(dir $args3 -recurse -ea 0 | ?{$_.mode -notmatch "d"} | ?{noequal $_.directoryname $exclude}) # Get list of files, minus directories and minus files in excluded paths if ($create -eq $true -or !(test-path $hashespath)) # Create database? { # Create SHA512 hashes of files and write to new database if ($files.count -eq 0) {"No files found. Exiting."; exit} $files = $files | %{WriteFileName "SHA-512 Hashing: `"$($_.fullname)`" ...";add-member -inputobject $_ -name SHA512 -membertype noteproperty -value $(get-SHA512 $_.fullname) -passthru} WriteFileNameEnd $files |export-clixml $hashespath "Created $hashespath" "$($files.count) file hash(es) saved. Exiting." exit } write-host "Loading file hashes from $hashespath..." -nonewline $xfiles=@(import-clixml $hashespath|?{noequal $_.directoryname $exclude}) # Load database if (($xfiles.count -eq 0) -or ($files.count -eq 0)) {"No files in Database or no files specified. Exiting.";Exit} } else { if (!(test-path $hashespath)) {"No database found or specified, exiting."; exit} write-host "Loading file hashes from $hashespath..." -nonewline $xfiles=@(import-clixml $hashespath|?{noequal $_.directoryname $exclude}) # Load database and check it if ($xfiles.count -eq 0) {"No files specified and no files in Database. Exiting.";Exit} $files=$xfiles | %{get-item -ea 0 -literalpath $_.fullname} } "Loaded $($xfiles.count) file hash(es)." $hash=@{} for($x=0;$x -lt $xfiles.count; $x++) # Build dictionary (hash) of filenames and indexes into file array { if ($hash.contains($xfiles[$x].fullname)) {continue} $hash.Add($xfiles[$x].fullname,$x) } foreach($f in $files) { if ((get-item -ea 0 -literalpath $f.fullname) -eq $null) {continue} # skip if file no longer exists. $n=($hash.($f.fullname)) if ($n -eq $null) { $nu++ # increment needs/needed updating count if ($upd -eq $false) {WriteFileName "Needs to be added: `"$($f.fullname)`"";WriteFileNameEnd;continue} # if not updating, then continue WriteFileName "SHA-512 Hashing `"$($f.fullname)`" ..." # Create SHA512 hash of file $f=$f |%{add-member -inputobject $_ -name SHA512 -membertype noteproperty -value $(get-SHA512 $_.fullname) -passthru -force} $xfiles+=@($f) # then add file + hash to list continue } WriteFileName "SHA-512 Hashing and checking: `"$($f.fullname)`" ..." $fc++ # File checked increment. if (($upd -eq $false) -and ($f.length -ne $xfiles[$n].length)) { $errs++ WriteFileName "Size mixmatch: `"$($f.fullname)`""; WriteFileNameEnd continue } $f=$f |%{add-member -inputobject $_ -name SHA512 -membertype noteproperty -value $(get-SHA512 $_.fullname) -passthru -force} # Check SHA512 and size for mixmatch. if ($xfiles[$n].length -eq $f.length) { if ($xfiles[$n].SHA512 -eq $f.SHA512) { $fm++;continue # if matched, increment file matches and continue loop } } $errs++ # increment mixmatches if ($upd -eq $true) { $xfiles[$n]=$f; WriteFileName "Updated `"$($f.fullname)`""; WriteFileNameEnd;continue} WriteFileName "SHA-512 and/or size mixmatch found: `"$($f.fullname)`""; WriteFileNameEnd } WriteFileNameEnd # restore cursor position after last write string if ($upd -eq $true) # if database updated { $xfiles|export-clixml $hashespath # write xml database "Updated Database: $hashespath" "$nu file hash(es) added to Database." "$errs file hash(es) updated in Database." exit } "$errs SHA-512 or size mixmatch(es) found." "$fm file(s) SHA512 and size matched." "$fc file(s) checked total." if ($nu -ne 0) {"$nu file(s) need to be added [run with -u option to Add file hashes to database]."}