Last active
August 29, 2015 14:23
-
-
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.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#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