function Test-FileHash { <# .Synopsis This is a simple file hash comparison tool that writes to Windows Events when changes are detected .Description This is a simple file hash comparison tool that writes to Windows Events when changes are detected .PARAMETER FilePath The path to the file to hash and compare .PARAMETER DictionaryFile The path to the directionary file with previously calculated hashes Defaults to the current directory + compare.xml This file is created if it does not exist .NOTES Copyright: (c) 2022 by Chrissy LeMaire, licensed under MIT License: MIT https://opensource.org/licenses/MIT .Example PS> Test-FileHash -FilePath C:\indexes\index.json -DictionaryFile D:\audit\allhashes.xml Tests to see if the filehash of the file at C:\indexes\index.json has changed since the last time it was hashed. .Example PS> Get-ChildItem -Recurse -Path "C:\Program Files\Microsoft SQL Server" | Test-FileHash -DictionaryFile D:\audit\allhashes.xml Tests to see if the filehash of the file at C:\indexes\index.json has changed since the last time it was hashed. #> [CmdletBinding()] param ( [parameter(ValueFromPipeline, Mandatory)] [Alias("FileName", "FullName")] [psobject[]]$FilePath, # this allows directories, gci and strings to be passed in [System.IO.FileInfo]$DictionaryFile = ".\compare.xml" ) begin { # Test for admin -- it's needed to create the event log source $user = [Security.Principal.WindowsIdentity]::GetCurrent() $isadmin = (New-Object Security.Principal.WindowsPrincipal $user).IsInRole([Security.Principal.WindowsBuiltinRole]::Administrator) if (-not $isadmin) { throw "You must run this script as administrator" } if ((Test-Path -Path $DictionaryFile)) { $compare = Import-CliXml -Path $DictionaryFile } else { $compare = $null $baseline = $true } if (-not ([System.Diagnostics.EventLog]::SourceExists("FileHash Checker"))) { $null = New-EventLog -LogName Application -Source "FileHash Checker" } $message = New-Object System.Collections.ArrayList $output = New-Object System.Collections.Hashtable } process { foreach ($file in $FilePath) { if ($file.FullName) { $filename = $file.FullName } else { $filename = $file } $current = Get-FileHash -Path $filename -Algorithm MD5 -ErrorAction SilentlyContinue if (-not $baseline -and $current) { $original = $compare[$filename] if ($original -ne $current.Hash) { $currenthash = $current.Hash $oldhash = $original if (-not $original) { $currentmessage = "$filename is a new file with a hash of $currenthash" } else { $currentmessage = "$filename had a previous hash of $oldhash but now has a hash of $currenthash" } $null = $message.Add($currentmessage) Write-Warning -Message $currentmessage } } if ($current) { $null = $output.Add($filename, $current.Hash) $current } } } end { if ($message) { # Just make one entry with all of the filehash failures $params = @{ LogName = "Application" Source = "FileHash Checker" EntryType = "Warning" Category = 0 EventId = 1000 Message = ($message -join [Environment]::NewLine) } Write-EventLog @params } $null = $output | Export-CliXml -Path $DictionaryFile } }