423 lines
16 KiB
PowerShell
423 lines
16 KiB
PowerShell
<#
|
|
.SYNOPSIS
|
|
Compares the last two versions of a specified file for a given company based on the manifest.json.
|
|
|
|
.DESCRIPTION
|
|
This script takes a company name and a file name as input, reads the manifest.json to find the last two backup versions of the file,
|
|
extracts them, locates the specific files within the backups, and performs a comparison. The results are logged for audit control.
|
|
|
|
.PARAMETER CompanyName
|
|
The name of the company whose file you want to compare.
|
|
|
|
.PARAMETER FileName
|
|
The specific file within the company's directory to compare.
|
|
|
|
.EXAMPLE
|
|
.\Compare-NWABackup.ps1 -CompanyName "CompanyA" -FileName "DataFile.DAT"
|
|
|
|
.NOTES
|
|
Ensure that the manifest.json is correctly formatted and accessible.
|
|
#>
|
|
|
|
param (
|
|
[Parameter(Mandatory = $true)]
|
|
[string]$CompanyName,
|
|
|
|
[Parameter(Mandatory = $true)]
|
|
[string]$FileName
|
|
)
|
|
|
|
# ----------- Step 1: Define Variables -----------
|
|
# Assuming manifest.json is located in the backup root directory
|
|
$backupRoot = "C:\Users\$env:USERNAME\Desktop\NWABackup" # Adjust as necessary
|
|
$manifestFilePath = Join-Path -Path $backupRoot -ChildPath "manifest.json"
|
|
|
|
# Define log file path
|
|
$logDirectory = Join-Path -Path $backupRoot -ChildPath "Logs"
|
|
$timestamp = Get-Date -Format "yyyyMMdd_HHmmss"
|
|
$logFile = Join-Path -Path $logDirectory -ChildPath "$timestamp.log"
|
|
|
|
# Define temporary extraction directories
|
|
$tempExtractionRoot = Join-Path -Path $backupRoot -ChildPath "TempComparisons"
|
|
$tempDirLatest = Join-Path -Path $tempExtractionRoot -ChildPath "$FileName\Latest_$timestamp"
|
|
$tempDirPrevious = Join-Path -Path $tempExtractionRoot -ChildPath "$FileName\Previous_$timestamp"
|
|
|
|
# ----------- Step 0: Import Logging Module -----------
|
|
$modulesPath = Join-Path -Path (Split-Path -Parent $MyInvocation.MyCommand.Path) -ChildPath ""
|
|
Import-Module (Join-Path -Path $modulesPath -ChildPath "Logging\Logging.psm1") -Force
|
|
|
|
# Create log directory if it doesn't exist
|
|
if (!(Test-Path -Path $logDirectory)) {
|
|
try {
|
|
New-Item -ItemType Directory -Path $logDirectory -Force | Out-Null
|
|
# Initialize Logging after creating the log directory
|
|
Initialize-Logging -Path $logFile
|
|
Write-Log -Message "Log directory created at ${logDirectory}" -Type "INFO"
|
|
}
|
|
catch {
|
|
Write-Error "Failed to create log directory at ${logDirectory}: $_"
|
|
exit 1
|
|
}
|
|
}
|
|
else {
|
|
# Initialize Logging if log directory already exists
|
|
Initialize-Logging -Path $logFile
|
|
}
|
|
|
|
# ----------- Step 2: Initialize Logging -----------
|
|
# Create log directory if it doesn't exist
|
|
if (!(Test-Path -Path $logDirectory)) {
|
|
try {
|
|
New-Item -ItemType Directory -Path $logDirectory -Force | Out-Null
|
|
# Initialize Logging after creating the log directory
|
|
Initialize-Logging -Path $logFile
|
|
Write-Log -Message "Log directory created at ${logDirectory}" -Type "INFO"
|
|
}
|
|
catch {
|
|
Write-Error "Failed to create log directory at ${logDirectory}: $_"
|
|
exit 1
|
|
}
|
|
}
|
|
else {
|
|
# Initialize Logging if log directory already exists
|
|
Initialize-Logging -Path $logFile
|
|
}
|
|
|
|
# Log the start of the comparison process
|
|
Write-Log -Message "=== NWABackup Comparison Process Started at $timestamp ===" -Type "INFO"
|
|
|
|
# ----------- Step 3: Load the Manifest and Find the File -----------
|
|
if (!(Test-Path -Path $manifestFilePath)) {
|
|
Write-Log -Message "Manifest file not found at $manifestFilePath." -Type "ERROR"
|
|
exit 1
|
|
}
|
|
|
|
try {
|
|
$manifestContent = Get-Content -Path $manifestFilePath -Raw | ConvertFrom-Json
|
|
Write-Log -Message "Manifest file loaded successfully." -Type "INFO"
|
|
}
|
|
catch {
|
|
Write-Log -Message "Failed to load manifest file: $_" -Type "ERROR"
|
|
exit 1
|
|
}
|
|
|
|
# Check if the company exists in the manifest
|
|
if (-not ($manifestContent.PSObject.Properties.Name -contains $CompanyName)) {
|
|
Write-Log -Message "Company '$CompanyName' not found in the manifest." -Type "ERROR"
|
|
exit 1
|
|
}
|
|
|
|
# Navigate to the specific part for the given company
|
|
$companyData = $manifestContent.$CompanyName
|
|
$targetPart = $null
|
|
$targetZipPaths = @()
|
|
|
|
foreach ($partName in $companyData.PSObject.Properties.Name) {
|
|
# Check if the part contains a 'DAT' entry
|
|
if ($companyData.$partName.PSObject.Properties.Name -contains 'DAT') {
|
|
# Retrieve the list of zip files for 'DAT'
|
|
$datFiles = $companyData.$partName.DAT
|
|
|
|
# Check if the part name matches the provided file name (excluding extension)
|
|
$fileBaseName = [System.IO.Path]::GetFileNameWithoutExtension($FileName)
|
|
if ($fileBaseName -eq $partName) {
|
|
$targetPart = $partName
|
|
$targetZipPaths = $datFiles
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
if (-not $targetPart) {
|
|
Write-Log -Message "File '$FileName' not found under any part for company '$CompanyName'." -Type "ERROR"
|
|
exit 1
|
|
}
|
|
|
|
Write-Log -Message "Found '$FileName' under part '$targetPart' for company '$CompanyName'." -Type "INFO"
|
|
|
|
# Use the matching zip paths for comparison
|
|
$backupZips = $targetZipPaths
|
|
|
|
# ----------- Step 4: Check Number of Versions -----------
|
|
if ($backupZips.Count -lt 2) {
|
|
Write-Log -Message "Only $($backupZips.Count) version(s) found for $CompanyName\$FileName. At least two versions are required for comparison." -Type "WARNING"
|
|
exit 0
|
|
}
|
|
|
|
# ----------- Step 5: Get Locations of the Two Prior Backups -----------
|
|
# Function to extract DateTime from zip file name
|
|
function Get-ZipTimestamp {
|
|
param (
|
|
[string]$ZipPath
|
|
)
|
|
|
|
# Extract the zip file name without extension
|
|
$zipName = [System.IO.Path]::GetFileNameWithoutExtension($ZipPath)
|
|
|
|
# Assuming zip file name contains the timestamp in 'yyyyMMdd_HHmmss' format
|
|
$timestampString = $zipName -replace '^.*?(\d{8}_\d{6}).*$', '$1'
|
|
|
|
try {
|
|
return [datetime]::ParseExact($timestampString, 'yyyyMMdd_HHmmss', $null)
|
|
}
|
|
catch {
|
|
Write-Log -Message "Failed to parse timestamp from zip file name '$zipName'. Ensure the zip file name contains a timestamp in 'yyyyMMdd_HHmmss' format." -Type "ERROR"
|
|
return $null
|
|
}
|
|
}
|
|
|
|
# Create a list of objects containing zip paths and their timestamps
|
|
$backupList = @()
|
|
|
|
foreach ($zip in $backupZips) {
|
|
if (!(Test-Path -Path $zip)) {
|
|
Write-Log -Message "Backup zip file not found: $zip" -Type "ERROR"
|
|
continue
|
|
}
|
|
|
|
$timestamp = Get-ZipTimestamp -ZipPath $zip
|
|
if ($timestamp -ne $null) {
|
|
$backupList += [PSCustomObject]@{
|
|
ZipPath = $zip
|
|
Timestamp = $timestamp
|
|
}
|
|
}
|
|
}
|
|
|
|
# Check if we have at least two valid backups
|
|
if ($backupList.Count -lt 2) {
|
|
Write-Log -Message "Not enough valid backup versions found for $CompanyName\$FileName." -Type "ERROR"
|
|
exit 1
|
|
}
|
|
|
|
# Sort the backups by timestamp in descending order (latest first)
|
|
$sortedBackups = $backupList | Sort-Object -Property Timestamp -Descending
|
|
|
|
# Select the top two backups
|
|
$latestBackup = $sortedBackups[0]
|
|
$previousBackup = $sortedBackups[1]
|
|
|
|
Write-Log -Message "Latest backup: $($latestBackup.ZipPath) (Timestamp: $($latestBackup.Timestamp))" -Type "INFO"
|
|
Write-Log -Message "Previous backup: $($previousBackup.ZipPath) (Timestamp: $($previousBackup.Timestamp))" -Type "INFO"
|
|
|
|
# ----------- Step 6: Unzip the Two Folders for Inspection -----------
|
|
# Ensure temporary extraction directories exist
|
|
try {
|
|
New-Item -ItemType Directory -Path $tempDirLatest -Force | Out-Null
|
|
New-Item -ItemType Directory -Path $tempDirPrevious -Force | Out-Null
|
|
Write-Log -Message "Created temporary directories for extraction." -Type "INFO"
|
|
}
|
|
catch {
|
|
Write-Log -Message "Failed to create temporary extraction directories: $_" -Type "ERROR"
|
|
exit 1
|
|
}
|
|
|
|
# Function to extract a specific file from a zip archive
|
|
function Extract-SpecificFile {
|
|
param (
|
|
[string]$ZipPath,
|
|
[string]$CompanyName,
|
|
[string]$FileName,
|
|
[string]$DestinationDir
|
|
)
|
|
|
|
try {
|
|
# Expand-Archive extracts all files; to optimize, you could extract only the specific file if needed
|
|
Expand-Archive -Path $ZipPath -DestinationPath $DestinationDir -Force
|
|
Write-Log -Message "Extracted '$FileName' from '$ZipPath' to '$DestinationDir'." -Type "INFO"
|
|
}
|
|
catch {
|
|
Write-Log -Message "Failed to extract '$FileName' from '$ZipPath': $_" -Type "ERROR"
|
|
throw $_
|
|
}
|
|
}
|
|
|
|
# Extract the specified file from the latest backup
|
|
try {
|
|
Extract-SpecificFile -ZipPath $latestBackup.ZipPath -CompanyName $CompanyName -FileName $FileName -DestinationDir $tempDirLatest
|
|
}
|
|
catch {
|
|
Write-Log -Message "Extraction failed for the latest backup. Aborting comparison." -Type "ERROR"
|
|
# Cleanup before exiting
|
|
Remove-Item -Path $tempDirLatest -Recurse -Force -ErrorAction SilentlyContinue
|
|
Remove-Item -Path $tempDirPrevious -Recurse -Force -ErrorAction SilentlyContinue
|
|
exit 1
|
|
}
|
|
|
|
# Extract the specified file from the previous backup
|
|
try {
|
|
Extract-SpecificFile -ZipPath $previousBackup.ZipPath -CompanyName $CompanyName -FileName $FileName -DestinationDir $tempDirPrevious
|
|
}
|
|
catch {
|
|
Write-Log -Message "Extraction failed for the previous backup. Aborting comparison." -Type "ERROR"
|
|
# Cleanup before exiting
|
|
Remove-Item -Path $tempDirLatest -Recurse -Force -ErrorAction SilentlyContinue
|
|
Remove-Item -Path $tempDirPrevious -Recurse -Force -ErrorAction SilentlyContinue
|
|
exit 1
|
|
}
|
|
|
|
# ----------- Step 7: Locate the Files in Each Folder -----------
|
|
# Construct the expected file paths after extraction
|
|
$extractedFileLatest = Join-Path -Path (Join-Path -Path $tempDirLatest -ChildPath $CompanyName) -ChildPath $FileName
|
|
$extractedFilePrevious = Join-Path -Path (Join-Path -Path $tempDirPrevious -ChildPath $CompanyName) -ChildPath $FileName
|
|
|
|
# Verify that the extracted files exist
|
|
if (!(Test-Path -Path $extractedFileLatest)) {
|
|
Write-Log -Message "Latest extracted file not found at '$extractedFileLatest'." -Type "ERROR"
|
|
# Cleanup before exiting
|
|
Remove-Item -Path $tempDirLatest -Recurse -Force -ErrorAction SilentlyContinue
|
|
Remove-Item -Path $tempDirPrevious -Recurse -Force -ErrorAction SilentlyContinue
|
|
exit 1
|
|
}
|
|
|
|
if (!(Test-Path -Path $extractedFilePrevious)) {
|
|
Write-Log -Message "Previous extracted file not found at '$extractedFilePrevious'." -Type "ERROR"
|
|
# Cleanup before exiting
|
|
Remove-Item -Path $tempDirLatest -Recurse -Force -ErrorAction SilentlyContinue
|
|
Remove-Item -Path $tempDirPrevious -Recurse -Force -ErrorAction SilentlyContinue
|
|
exit 1
|
|
}
|
|
|
|
# ----------- Step 8: Run a Diff Against the Two Files -----------
|
|
# Determine if the files are text or binary based on extension
|
|
$extension = [System.IO.Path]::GetExtension($FileName).ToLower()
|
|
|
|
# Function to compare text files
|
|
function Compare-TextFiles {
|
|
param (
|
|
[string]$ReferenceFile,
|
|
[string]$DifferenceFile
|
|
)
|
|
|
|
try {
|
|
# Read all lines from both files
|
|
$refLines = Get-Content -Path $ReferenceFile
|
|
$diffLines = Get-Content -Path $DifferenceFile
|
|
|
|
# Determine the maximum number of lines between both files
|
|
$maxLines = [Math]::Max($refLines.Count, $diffLines.Count)
|
|
|
|
# Initialize an array to store detailed differences
|
|
$differences = @()
|
|
|
|
# Loop through each line number
|
|
for ($i = 0; $i -lt $maxLines; $i++) {
|
|
$lineNumber = $i + 1
|
|
$refLine = if ($i -lt $refLines.Count) { $refLines[$i] } else { $null }
|
|
$diffLine = if ($i -lt $diffLines.Count) { $diffLines[$i] } else { $null }
|
|
|
|
if ($refLine -ne $diffLine) {
|
|
if ($refLine -ne $null -and $diffLine -ne $null) {
|
|
# Line exists in both files but is different (Modified)
|
|
$differences += "Line ${lineNumber}: Modified"
|
|
$differences += " Reference: $refLine"
|
|
$differences += " Difference: $diffLine"
|
|
}
|
|
elseif ($refLine -ne $null) {
|
|
# Line exists only in the reference file (Removed)
|
|
$differences += "Line ${lineNumber}: Removed"
|
|
$differences += " Reference: $refLine"
|
|
}
|
|
elseif ($diffLine -ne $null) {
|
|
# Line exists only in the difference file (Added)
|
|
$differences += "Line ${lineNumber}: Added"
|
|
$differences += " Difference: $diffLine"
|
|
}
|
|
}
|
|
}
|
|
|
|
if ($differences.Count -gt 0) {
|
|
Write-Log -Message "Differences found between '$ReferenceFile' and '$DifferenceFile':" -Type "INFO"
|
|
|
|
# Log each difference
|
|
foreach ($diff in $differences) {
|
|
Write-Log -Message $diff -Type "INFO"
|
|
}
|
|
|
|
# Define the differences output file path
|
|
# Ensure that $logFile and $timestamp are accessible within this function
|
|
if ($global:LogFile -and $global:timestamp) {
|
|
$logDir = Split-Path -Path $global:LogFile -Parent
|
|
$differenceFileName = "$(Split-Path -Path $DifferenceFile -Leaf)-Differences-$($global:timestamp).txt"
|
|
|
|
# Sanitize the difference file name to remove any invalid characters
|
|
$invalidChars = [System.IO.Path]::GetInvalidFileNameChars()
|
|
foreach ($char in $invalidChars) {
|
|
$differenceFileName = $differenceFileName -replace [regex]::Escape($char), '_'
|
|
}
|
|
|
|
$diffFilePath = Join-Path -Path $logDir -ChildPath $differenceFileName
|
|
}
|
|
else {
|
|
# Fallback if $global:LogFile or $global:timestamp is not defined
|
|
$logDir = Split-Path -Path $DifferenceFile -Parent
|
|
$differenceFileName = "Differences-$(Get-Date -Format 'yyyyMMdd_HHmmss').txt"
|
|
$diffFilePath = Join-Path -Path $logDir -ChildPath $differenceFileName
|
|
}
|
|
|
|
# Export differences to the separate file
|
|
$differences | Out-File -FilePath $diffFilePath -Encoding UTF8
|
|
|
|
Write-Output $differences
|
|
|
|
Write-Log -Message "Differences exported to '$diffFilePath'." -Type "INFO"
|
|
}
|
|
else {
|
|
Write-Output "No differences found between '$ReferenceFile' and '$DifferenceFile'."
|
|
Write-Log -Message "No differences found between '$ReferenceFile' and '$DifferenceFile'." -Type "INFO"
|
|
}
|
|
}
|
|
catch {
|
|
Write-Log -Message "Failed to compare text files: $_" -Type "ERROR"
|
|
}
|
|
}
|
|
|
|
# Function to compare binary files using hash comparison
|
|
function Compare-BinaryFiles {
|
|
param (
|
|
[string]$ReferenceFile,
|
|
[string]$DifferenceFile
|
|
)
|
|
|
|
try {
|
|
$hashReference = Get-FileHash -Path $ReferenceFile -Algorithm SHA256
|
|
$hashDifference = Get-FileHash -Path $DifferenceFile -Algorithm SHA256
|
|
|
|
if ($hashReference.Hash -ne $hashDifference.Hash) {
|
|
Write-Log -Message "Hash mismatch detected between '$ReferenceFile' and '$DifferenceFile'." -Type "INFO"
|
|
Write-Log -Message "Reference Hash: $($hashReference.Hash)" -Type "INFO"
|
|
Write-Log -Message "Difference Hash: $($hashDifference.Hash)" -Type "INFO"
|
|
}
|
|
else {
|
|
Write-Log -Message "No hash differences found between '$ReferenceFile' and '$DifferenceFile'." -Type "INFO"
|
|
}
|
|
}
|
|
catch {
|
|
Write-Log -Message "Failed to compare binary files: $_" -Type "ERROR"
|
|
}
|
|
}
|
|
|
|
# Execute the appropriate comparison based on file type
|
|
if ($extension -eq '.dat' -or $extension -eq '.nwh') {
|
|
# Assuming .DAT and .NWH are text files; adjust if they are binary
|
|
Compare-TextFiles -ReferenceFile $extractedFilePrevious -DifferenceFile $extractedFileLatest
|
|
}
|
|
else {
|
|
# For other file types, perform binary comparison
|
|
Compare-BinaryFiles -ReferenceFile $extractedFilePrevious -DifferenceFile $extractedFileLatest
|
|
}
|
|
|
|
# ----------- Step 9: Cleanup Temporary Directories -----------
|
|
try {
|
|
Remove-Item -Path $tempDirLatest -Recurse -Force
|
|
Remove-Item -Path $tempDirPrevious -Recurse -Force
|
|
Remove-Item -Path $tempExtractionRoot -Recurse -Force
|
|
Write-Log -Message "Cleaned up temporary extraction directories." -Type "INFO"
|
|
}
|
|
catch {
|
|
Write-Log -Message "Failed to remove temporary directories: $_" -Type "WARNING"
|
|
}
|
|
|
|
# ----------- Step 10: Completion Message -----------
|
|
Write-Log -Message "Comparison process completed for $CompanyName\$FileName." -Type "INFO"
|
|
Write-Host "Comparison completed. Check the log file at '$logFile' for details." |