sentinel/Modules/NWA-Compare.ps1

423 lines
16 KiB
PowerShell
Raw Permalink Normal View History

2025-01-03 02:23:16 +00:00
<#
.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."