Maumee River
Maumee River

Reputation: 263

Advanced PowerShell hashing functions

I have a need to perform change audits on some servers, and as such need to look at a particular bunch of file types, create hashes for the files matching those types, and then at a later time, compare the first hash list with a second hash list I created.

So far, I have created this string:

Get-ChildItem -Path C:\Windows -Force -Recurse -Include "*.exe","*.dll","*.sqr","*.sqc","*.sql","*.dms","*.asps" -ErrorAction SilentlyContinue | Get-Hash | Out-File 'results.txt'

This does a great job of finding all files that I need hashed, and creating hashes for those files. I have two problems with what I have so far...

First problem, when I pipe my results into the "Get-Hash" cmdlet, I lose most of the relevant information about the files that I am hashing, such as the last modified date, file length, and other timestamps.

I tried first piping my results into a Select-Object command, and then into the Get-Hash cmdlet, but it seems that the only information from the Get-Hash output is the file's path and hashstring.

Example:

Path       : C:\Users\MM COS\Documents\results\changes.txt
HashString : 00C89D6C14E29A77DD52644F91E240DF

Second problem...So I decided to push on and work with what I had for the moment, and ran the following command to compare two hash files I created from my first step.

Compare-Object $(Get-Content .\results.txt) $(Get-Content '.\results2.txt')

The problem with this command, is it only displays the hashes that do not match; it does not display the file names associated with the hashes, which is pretty useless to me. I need to know what files are being changed.

Example:

Compare-Object (Get-Content .\hash1.txt) (Get-Content .\hash2.txt)

InputObject                                                 SideIndicator
-----------                                                 -------------
HashString : 1D90ADDE1194C8F1E60AF0BB0D725162               =>
HashString : D591529F73ADCB4ADAC8DD8B7AE58554               <=

Upvotes: 2

Views: 1940

Answers (4)

Andy Arismendi
Andy Arismendi

Reputation: 52639

It looks like you are using the Get-Hash cmdlet from PSCX... You can make your own script cmdlet that outputs all the original object information with the hash (example below) -

For the comparison you can create text files with the file path and hash on the same line -

dir C:\ | Get-FileMd5 | % {$_.FullName + '=' + $_.HashMD5} | Set-Content results.txt

Here's the function -

function Get-FileMd5 {
    begin {
        Add-Type -AssemblyName System.Security
    }
    process {
        $input | % {
            try {
                $path = ($_ | Resolve-Path -ErrorAction SilentlyContinue).Path
                if ($path) {
                    $file = Get-Item -Path $path
                    if ($file -is [System.IO.FileInfo]) {
                        $stream = $file.Open([System.IO.FileMode]::Open)
                        $crypt_prov = New-Object -TypeName System.Security.Cryptography.MD5CryptoServiceProvider
                        $md5_hash = [System.BitConverter]::ToString($crypt_prov.ComputeHash($stream)).Replace('-', '')
                        $stream.Close()
                        $file | Add-Member -MemberType NoteProperty -Name HashMD5 -Value $md5_hash -PassThru
                    }
                }
            } catch {
                if ($stream) {$stream.Close()}
                Write-Error -ErrorRecord $_
            }
        }
    }
}

dir C:\ | Get-FileMd5 | Select *

Upvotes: 0

JPBlanc
JPBlanc

Reputation: 72660

Here is another solution. I would add the hash to the fileobject in a loop, and then I would export in an XML object file. For me it's then more simple to compare objects using Import-Clixml (be careful to use a select to export only the properties you need because Export-Clixml consumes time)

$ext = "*.exe","*.dll","*.sqr","*.sqc","*.sql","*.dms","*.asps"
Get-ChildItem -Path C:\temp\Arielle -Force -Recurse -Include $ext -ErrorAction SilentlyContinue | % {$a=Get-FileHash $_; Add-Member -InputObject $_ -MemberType Noteproperty -name "Hash" -Value $a.Hash; $_} | Export-Clixml 'c:\temp\results.xml'

Upvotes: 0

Keith Hill
Keith Hill

Reputation: 201832

If you are on PowerShell V4, you can use the -PipelineVariable parameter to stash the current FileInfo object for use further down the pipeline when $_ has been redefined to a different object:

$ext = "*.exe","*.dll","*.sqr","*.sqc","*.sql","*.dms","*.asps"
Get-ChildItem C:\Windows -Force -Recurse -Include $ext -pv fileInfo -ErrorAction SilentlyContinue | 
    Get-Hash | Foreach {$_.HashString + " " + $fileInfo.Name} | 
    Out-File results.txt

If you're not on V4 you can use a foreach-object cmdlet like so:

Get-ChildItem C:\Windows -Force -Recurse -Include $ext -ErrorAction SilentlyContinue | 
    Foreach {$fileInfo = $_; $hash = $_ | Get-Hash; $hash.HashString + " " + $fileInfo.Name} |
    Out-File results.txt

Upvotes: 1

Tim Ferrill
Tim Ferrill

Reputation: 1674

You're probably going to want to use ForEach-Object to do multiple things with each file as you cycle through them. The automatic variable $_ will let you target each file within the code block.

Get-ChildItem -Path C:\Windows -Force -Recurse -Include "*.exe","*.dll","*.sqr","*.sqc","*.sql","*.dms","*.asps" -ErrorAction SilentlyContinue | ForEach-Object {
    Get-Hash $_.FullName | Out-File 'results.txt'
    }

I'd think you would want to compare the old hash to the current one, in which case you could perform step 2 while you're going through step 1. If not you could pull the two result sets into variables and compare individual lines with matching filenames.

Upvotes: 1

Related Questions