Skip to content

Instantly share code, notes, and snippets.

@bryanvine
Last active August 29, 2015 14:23
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save bryanvine/8d52e0ec8dca9b23baac to your computer and use it in GitHub Desktop.
Save bryanvine/8d52e0ec8dca9b23baac to your computer and use it in GitHub Desktop.
Powershell Script - Compare Source Files - utility function to compare code between dev, prod, qa, bcp, etc.
#Requires -Version 2.0
Function Compare-SourceFiles {
<#
.SYNOPSIS
Compares two directories and calculates any differences in files between them using MD5 hash comparison.
.DESCRIPTION
Only looks at full file paths, empty directories aren't scanned.
.PARAMETER Source1
Full path (or UNC network share) to a directory to compare
.PARAMETER Source2
Full path (or UNC network share) to a directory to compare
.PARAMETER SummaryOnly
Displays only minimal information and a summary table
.PARAMETER ACLs
Compares security Descriptors (SDDLs) and security access control lists (ACLs). Only file ACLs are analyized (directories ignored).
.PARAMETER ReportFile
Outputs all analysis to a text file specified.
.EXAMPLE
Compare-SourceFiles -Source1 '\\CodeRepoServer\release\Prod' -Source2 '\\CodeRepoServer\release\Dev' -ReportFile 'c:\temp\differenceReport.log'
This will generate an output report of the different files between two code releases
.EXAMPLE
Compare-SourceFiles -Source1 '\\webserver1\c$\webapp\wwwroot' -Source2 '\\webserver2\c$\webapp\wwwroot' -ACLs -SummaryOnly
This creates a summary of the differences between two webservers, also checking for ACL differences.
.LINK
http://www.bryanvine.com/2015/06/powershell-script-compare-sourcefiles.html
.LINK
Compare-Object
.LINK
Get-ACL
.NOTES
Author: Bryan Vine
Last updated: 6/28/2015
#>
[CmdletBinding()]
Param(
[Parameter(Mandatory=$true,valuefrompipeline=$false,Position=0)]
[ValidateNotNullOrEmpty()][string]
$Source1,
[Parameter(Mandatory=$true,valuefrompipeline=$false,Position=0)]
[ValidateNotNullOrEmpty()][string]
$Source2,
[switch]$SummaryOnly,
[switch]$ACLs,
[string]$ReportFile
)
#if Powershell v4 or later, use faster file searcher, otherwise use slower where filter
if($host.Version.Major -gt 3){
$filelist1 = dir -Recurse $Source1 -File | select -ExpandProperty fullname | ForEach-Object{$_.replace($Source1,"")}
$filelist2 = dir -Recurse $Source2 -File | select -ExpandProperty fullname | ForEach-Object{$_.replace($Source2,"")}
}
else{
$filelist1 = dir -Recurse $Source1 | Where-Object{$_.mode -notlike 'd*'} | select -ExpandProperty fullname | ForEach-Object{$_.replace($Source1,"")}
$filelist2 = dir -Recurse $Source2 | Where-Object{$_.mode -notlike 'd*'} | select -ExpandProperty fullname | ForEach-Object{$_.replace($Source2,"")}
}
#Counters
$DifferentFiles = 0
if($ACLs){
$DifferentACLs = 0
$DifferentSDDLs = 0
}
if($ReportFile){
$Report = @()
$Report += "Comparing '$Source1' and '$Source2'"
$Report += ''
}
#compare filenames
$FileNameCompare = Compare-Object -ReferenceObject $filelist1 -DifferenceObject $filelist2 -IncludeEqual
$Missing1 = $FileNameCompare | Where-Object{$_.SideIndicator -like "=>"} | Select -ExpandProperty InputObject
if($Missing1){
Write-Warning "Files missing from '$Source1': $($Missing1.count)"
if($ReportFile){
$Report += "Files missing from '$Source1': $($Missing1.count)"
$Report += $Missing1
$Report += ''
$Report += ''
}
if(!$SummaryOnly){
Write-Host ($Missing1 | Out-String)
Write-Host ''
Write-Host ''
}
}
$Missing2 = $FileNameCompare | Where-Object{$_.SideIndicator -like "<="} | Select -ExpandProperty InputObject
if($Missing2){
Write-Warning "Files missing from '$Source2': $($Missing2.count)"
if($ReportFile){
$Report += "Files missing from '$Source2': $($Missing2.count)"
$Report += $Missing2
$Report += ''
$Report += ''
}
if(!$SummaryOnly){
Write-Host ($Missing2 | Out-String)
Write-Host ''
Write-Host ''
}
}
#For shared filenames, iterate through them to compare further
$SharedFiles = $FileNameCompare | Where-Object{$_.SideIndicator -like "=="} | Select -ExpandProperty InputObject
$SharedFiles | ForEach-Object{
#Compare MD5 Hashes
$hashes = "$Source1$_","$Source2$_" | ForEach-Object{
$MD5 = [Security.Cryptography.HashAlgorithm]::Create( "MD5" )
$stream = ([IO.StreamReader]"$_").BaseStream
-join ($MD5.ComputeHash($stream) | ForEach-Object { "{0:x2}" -f $_ })
$stream.Close()
}
#Hashes aren't the same
if ($hashes[0] -ne $hashes[1]){
$DifferentFiles++
Write-Warning "File Hash different: '$_'"
if($ReportFile){
$Report += "File Hash different: '$_'"
}
if($_ -notlike "*.exe" -and $_ -notlike "*.dll"){
#Attempt to do text comparison and display different lines
$dif = diff -ReferenceObject (Get-Content "$Source1$_") -DifferenceObject (Get-Content "$Source2$_")
if(!$SummaryOnly){write-Host ($dif | Out-String)}
if($ReportFile){
$Report += ($dif | Out-String)
}
}
if(!$SummaryOnly){
Write-Host ''
Write-Host ''
}
if($Report){
$Report += ''
$Report += ''
}
}
#Compare ACLs
if($ACLs){
$acl1 = Get-Acl "$Source1$_"
$acl2 = Get-Acl "$Source2$_"
if($acls1.sddl -ne $acls2.sddl){
Write-Warning "ACL Security descriptor different: '$_'"
$DifferentSDDLs++
if(!$SummaryOnly){
Write-Host ''
Write-Host ''
}
if($ReportFile){
$Report += "ACL Security descriptor different: '$_'"
$Report += ''
$Report += ''
}
}
#compute access differences
$Access = Compare-Object -ReferenceObject $acl1 -DifferenceObject $acl2 -Property Access -PassThru -ErrorAction SilentlyContinue
if($Access){
Write-Warning "ACL Access Permissions different: '$_'"
$DifferentACLs++
if(!$SummaryOnly){
Write-Host " File Permissions: '$Source1$_'"
Write-Host ($Access[0].Access | sort IdentityReference | Format-Table -AutoSize| Out-String)
Write-Host " File Permissions: '$Source2$_'"
Write-Host ($Access[1].Access | sort IdentityReference | Format-Table -AutoSize| Out-String)
Write-Host ''
Write-Host ''
}
if($ReportFile){
$Report += "ACL Access Permissions different: '$_'"
$Report += " File Permissions: '$Source1$_'"
$Report += ($Access[0].Access | sort IdentityReference | Format-Table -AutoSize| Out-String)
$Report += " File Permissions: '$Source2$_'"
$Report += ($Access[1].Access | sort IdentityReference | Format-Table -AutoSize| Out-String)
$Report += ''
$Report += ''
}
}
}
}
#Build Summary output
$S1Obj = New-Object PSObject
$S2Obj = New-Object PSObject
$S1Obj | Add-Member NoteProperty Path $Source1
$S1Obj | Add-Member NoteProperty FileCount $filelist1.count
$S1Obj | Add-Member NoteProperty MissingFiles $Missing1.Count
$S2Obj | Add-Member NoteProperty Path $Source2
$S2Obj | Add-Member NoteProperty FileCount $filelist2.count
$S2Obj | Add-Member NoteProperty MissingFiles $Missing2.Count
$S1Obj,$S2Obj | Add-Member NoteProperty SharedFiles $SharedFiles.Count
$S1Obj,$S2Obj | Add-Member NoteProperty DifferentHashes $DifferentFiles
if($ACLs){
if($DifferentSDDLs){
$S1Obj,$S2Obj | Add-Member NoteProperty DifferentSDDL $DifferentSDDLs
}
$S1Obj,$S2Obj | Add-Member NoteProperty DifferentACLs $DifferentACLs
}
$SummaryReport = @()
$SummaryReport += $S1Obj
$SummaryReport += $S2Obj
Write-Host ''
Write-Host '=============================='
Write-Host 'File Source Comparison Summary'
Write-Host '=============================='
Write-Output $SummaryReport | FT -AutoSize
if($ReportFile){
$Report += ''
$Report += '=============================='
$Report += 'File Source Comparison Summary'
$Report += '=============================='
$Report += ($SummaryReport | FT -AutoSize | Out-String)
$Report | Set-Content $ReportFile
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment