Initial commit

This commit is contained in:
RobertKohut 2025-01-02 18:23:16 -08:00
commit e46472393a
7 changed files with 1012 additions and 0 deletions

1
.gitignore vendored Normal file
View File

@ -0,0 +1 @@
config.json

View File

@ -0,0 +1,7 @@
@{
ModuleVersion = '1.0.0'
GUID = 'affc6c50-b639-478a-a2f2-b7356f1ace16'
Author = 'Alex Kohut'
Description = 'Handles logging operations.'
FunctionsToExport = @('Initialize-Logging', 'Write-Log')
}

View File

@ -0,0 +1,39 @@
# Modules/Logging/Logging.psm1
# Function to initialize the log file path
function Initialize-Logging {
param (
[Parameter(Mandatory = $true)]
[string]$Path
)
# Store the log file path in a module-scoped variable
$Script:LogFile = $Path
}
# Function to log messages with timestamps
function Write-Log {
param (
[string]$Message,
[ValidateSet("INFO", "WARNING", "ERROR")]
[string]$Type = "INFO"
)
if (-not $Script:LogFile) {
Throw "Log file path is not initialized. Please run Initialize-Logging first."
}
$logTimestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
$logMessage = "$logTimestamp [$Type] - $Message"
Add-Content -Path $Script:LogFile -Value $logMessage
switch ($Type) {
"INFO" { Write-Verbose $logMessage }
"WARNING" { Write-Warning $logMessage }
"ERROR" { Write-Error $logMessage }
}
}
# Export the functions
Export-ModuleMember -Function Initialize-Logging, Write-Log

423
Modules/NWA-Compare.ps1 Normal file
View File

@ -0,0 +1,423 @@
<#
.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."

279
Modules/NWABackup.ps1 Normal file
View File

@ -0,0 +1,279 @@
# --------------------------------------------
# Northwest Analytics (NWA) Backup Script
# --------------------------------------------
# This script backs up .DAT and .NWH files from the shared
# directory to a user-specific backup folder, preserving
# the company directory structure and compressing the
# backup into a zip file. It also maintains a global
# manifest in JSON format.
# --------------------------------------------
function ConvertTo-Hashtable {
param (
[Parameter(Mandatory = $true)]
[object]$InputObject
)
if ($InputObject -is [System.Management.Automation.PSCustomObject]) {
$hash = @{}
foreach ($prop in $InputObject.PSObject.Properties) {
$hash[$prop.Name] = ConvertTo-Hashtable -InputObject $prop.Value
}
return $hash
}
elseif ($InputObject -is [System.Array]) {
return $InputObject | ForEach-Object { ConvertTo-Hashtable -InputObject $_ }
}
else {
return $InputObject
}
}
# ----------- 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
# ----------- Step 1: Define Variables -----------
$sharedDirectory = "X:\dev\BOYD\%NWA\Data Collection" # Using environment variable
$username = $env:USERNAME
$backupRoot = "C:\Users\$username\Desktop\NWABackup"
$timestamp = Get-Date -Format "yyyyMMdd_HHmmss"
$daysBack = 4
$thresholdDate = (Get-Date).AddDays(-$daysBack)
$backupDirectory = Join-Path -Path $backupRoot -ChildPath $timestamp
$zipFilePath = Join-Path -Path $backupRoot -ChildPath "$timestamp.zip"
$manifestFilePath = Join-Path -Path $backupRoot -ChildPath "manifest.json"
# Define log file path
$logDirectory = Join-Path -Path $backupRoot -ChildPath "Logs"
$logFile = Join-Path -Path $logDirectory -ChildPath "$timestamp.log"
# ----------- 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 backup process
Write-Log -Message "=== NWABackup Process Started at $timestamp ===" -Type "INFO"
# ----------- Step 3: Create Backup Directory -----------
if (!(Test-Path -Path $backupDirectory)) {
try {
New-Item -ItemType Directory -Path $backupDirectory -Force | Out-Null
Write-Log -Message "Backup directory created at ${backupDirectory}" -Type "INFO"
}
catch {
Write-Log -Message "Failed to create backup directory at ${backupDirectory}. Error: $_" -Type "ERROR"
exit 1
}
}
else {
Write-Log -Message "Backup directory already exists at ${backupDirectory}" -Type "INFO"
}
# ----------- Step 4: Copy .DAT and .NWH Files -----------
# Define the file extensions to include (case-insensitive)
$fileExtensions = @('.DAT', '.NWH')
Write-Log -Message "Starting file retrieval and copy process." -Type "INFO"
# Get all files recursively and filter by extension and modification date in a single pass
try {
$files = Get-ChildItem -Path $sharedDirectory -Recurse -File | Where-Object {
($fileExtensions -contains $_.Extension.ToUpper()) -and ($_.LastWriteTime -ge $thresholdDate)
}
Write-Log -Message "Found $($files.Count) files with specified extensions modified in the last $daysBack days." -Type "INFO"
}
catch {
Write-Log -Message "Error retrieving files: $_" -Type "ERROR"
exit 1
}
# Initialize a hashtable to build the manifest
$manifest = @{}
foreach ($file in $files) {
# Extract company name from the folder structure
$relativePath = $file.FullName.Substring($sharedDirectory.Length).TrimStart("\")
$pathParts = $relativePath.Split("\")
if ($pathParts.Length -lt 2) {
Write-Log -Message "Invalid file path structure: $relativePath" -Type "WARNING"
continue
}
$companyName = $pathParts[0]
$fileName = $pathParts[-1]
# Extract part name and file type from the file name
$partName, $fileType = $fileName -split '\.', 2
if (-not $fileType) {
Write-Log -Message "Invalid file format: $fileName" -Type "WARNING"
continue
}
# Initialize company entry if not exists
if (-not $manifest.ContainsKey($companyName)) {
$manifest[$companyName] = @{}
}
# Initialize part entry if not exists
if (-not $manifest[$companyName].ContainsKey($partName)) {
$manifest[$companyName][$partName] = @{} # This must be a hash table, not an array
}
# Initialize file type entry if not exists
if (-not $manifest[$companyName][$partName].ContainsKey($fileType)) {
$manifest[$companyName][$partName][$fileType] = @()
}
# Add the current backup zip file to the file type's backup array
if (-not $manifest[$companyName][$partName][$fileType].Contains($zipFilePath)) {
$manifest[$companyName][$partName][$fileType] += $zipFilePath
}
# Define the destination path
$destinationPath = Join-Path -Path $backupDirectory -ChildPath $relativePath
# Get the destination directory
$destinationDir = Split-Path -Path $destinationPath -Parent
# Create the destination directory if it doesn't exist
if (!(Test-Path -Path $destinationDir)) {
try {
New-Item -ItemType Directory -Path $destinationDir -Force | Out-Null
Write-Log -Message "Created directory: ${destinationDir}" -Type "INFO"
}
catch {
Write-Log -Message "Failed to create directory ${destinationDir}: $_" -Type "ERROR"
continue
}
}
# Copy the file to the backup directory
try {
Copy-Item -Path $file.FullName -Destination $destinationPath -Force -ErrorAction Stop
Write-Log -Message "Copied: $($file.FullName) to ${destinationPath}" -Type "INFO"
}
catch {
Write-Log -Message "Failed to copy $($file.FullName) to ${destinationPath}: $_" -Type "ERROR"
}
}
# ----------- Step 5: Update and Save the Manifest JSON -----------
if (Test-Path -Path $manifestFilePath) {
try {
$existingManifest = Get-Content -Path $manifestFilePath | ConvertFrom-Json
Write-Log -Message "Loaded existing manifest file." -Type "INFO"
}
catch {
Write-Log -Message "Failed to load existing manifest file. Creating a new one. Error: $_" -Type "ERROR"
$existingManifest = @{}
}
}
else {
$existingManifest = @{}
}
# Convert PSCustomObject to Hashtable
$existingManifest = ConvertTo-Hashtable -InputObject $existingManifest
# Merge the current manifest into the existing manifest
foreach ($company in $manifest.Keys) {
if (-not $existingManifest.ContainsKey($company)) {
$existingManifest[$company] = @{}
Write-Log -Message "Added new company '$company' to manifest." -Type "INFO"
}
foreach ($partName in $manifest[$company].Keys) {
if (-not $existingManifest[$company].ContainsKey($partName)) {
$existingManifest[$company][$partName] = @{}
Write-Log -Message "Added new part '$partName' under company '$company' to manifest." -Type "INFO"
}
foreach ($fileType in $manifest[$company][$partName].Keys) {
if (-not $existingManifest[$company][$partName].ContainsKey($fileType)) {
$existingManifest[$company][$partName][$fileType] = @()
Write-Log -Message "Added new file type '$fileType' under part '$partName' for company '$company' to manifest." -Type "INFO"
}
foreach ($zip in $manifest[$company][$partName][$fileType]) {
# Ensure the value is treated as an array
if ($existingManifest[$company][$partName][$fileType] -isnot [System.Collections.ArrayList]) {
$existingManifest[$company][$partName][$fileType] = @($existingManifest[$company][$partName][$fileType])
}
# Add the zip path to the array if it doesn't already exist
if (-not $existingManifest[$company][$partName][$fileType].Contains($zip)) {
$existingManifest[$company][$partName][$fileType] += $zip
Write-Log -Message "Added zip '$zip' to '$company\\$partName\\$fileType' in manifest." -Type "INFO"
}
}
}
}
}
# Convert the hashtable to JSON with formatting
$jsonContent = $existingManifest | ConvertTo-Json -Depth 10 -Compress:$false
# Save the updated manifest
try {
Set-Content -Path $manifestFilePath -Value $jsonContent -Encoding UTF8
Write-Log -Message "Manifest file updated at $manifestFilePath" -Type "INFO"
}
catch {
Write-Log -Message "Failed to update manifest file at $manifestFilePath. Error: $_" -Type "ERROR"
}
# ----------- Step 6: Compress the Backup Directory -----------
try {
# Compress the *contents* of the backup directory, not the directory itself
Compress-Archive -Path "$backupDirectory\*" -DestinationPath $zipFilePath -Force
Write-Log -Message "Successfully compressed backup to $zipFilePath" -Type "INFO"
}
catch {
Write-Log -Message "Failed to compress backup directory: $_" -Type "ERROR"
# Optionally, decide whether to exit or continue
}
# ----------- Step 7: Cleanup (Optional) -----------
# Optionally, remove the uncompressed backup directory after compression
try {
Remove-Item -Path $backupDirectory -Recurse -Force
Write-Log -Message "Removed uncompressed backup directory: ${backupDirectory}" -Type "INFO"
}
catch {
Write-Log -Message "Failed to remove uncompressed backup directory ${backupDirectory}: $_" -Type "ERROR"
}
# ----------- Step 8: Completion Message -----------
Write-Log -Message "Backup process completed." -Type "INFO"
# Optionally, display a message to the user
Write-Host "Backup completed successfully. Log file located at $logFile"

258
NWA-Sentinel.ps1 Normal file
View File

@ -0,0 +1,258 @@
$configFilePath = Join-Path -Path $PSScriptRoot -ChildPath "config.json"
# Check if the configuration file exists
if (-Not (Test-Path $configFilePath)) {
Write-Error "Configuration file not found at $configFilePath"
exit 1
}
# Load the configuration file
$config = Get-Content $configFilePath | ConvertFrom-Json
# Variables for script paths
$backupScriptPath = $config.BackupScriptPath
$compareScriptPath = $config.CompareScriptPath
$outputPath = $config.OutputPath
# Import necessary assemblies for GUI
Add-Type -AssemblyName System.Windows.Forms
Add-Type -AssemblyName System.Drawing
$processing = $false
# Create Form
$form = New-Object System.Windows.Forms.Form
$form.Text = "NWA Sentinel"
$form.Size = New-Object System.Drawing.Size(800, 700)
$form.StartPosition = "CenterScreen"
# Create Backup Button
$backupButton = New-Object System.Windows.Forms.Button
$backupButton.Text = "Backup"
$backupButton.Size = New-Object System.Drawing.Size(100, 30)
$backupButton.Location = New-Object System.Drawing.Point(10, 10)
$form.Controls.Add($backupButton)
# Create Company List Box
$companyList = New-Object System.Windows.Forms.ListBox
$companyList.Size = New-Object System.Drawing.Size(200, 300)
$companyList.Location = New-Object System.Drawing.Point(10, 50)
$form.Controls.Add($companyList)
# Create File Details List Box
$fileDetailsListBox = New-Object System.Windows.Forms.ListBox
$fileDetailsListBox.Size = New-Object System.Drawing.Size(550, 300)
$fileDetailsListBox.Location = New-Object System.Drawing.Point(220, 50)
$form.Controls.Add($fileDetailsListBox)
# Create Compare Button
$compareButton = New-Object System.Windows.Forms.Button
$compareButton.Text = "Compare"
$compareButton.Size = New-Object System.Drawing.Size(100, 30)
$compareButton.Location = New-Object System.Drawing.Point(10, 360)
$compareButton.Enabled = $false
$form.Controls.Add($compareButton)
# Create QXP Button
$qxpButton = New-Object System.Windows.Forms.Button
$qxpButton.Text = "QXP"
$qxpButton.Size = New-Object System.Drawing.Size(100, 30)
$qxpButton.Location = New-Object System.Drawing.Point(120, 360)
$qxpButton.Enabled = $false
$form.Controls.Add($qxpButton)
# Create Compare Output Text Box
$compareOutputBox = New-Object System.Windows.Forms.TextBox
$compareOutputBox.Multiline = $true
$compareOutputBox.ReadOnly = $true
$compareOutputBox.ScrollBars = "Vertical"
$compareOutputBox.Size = New-Object System.Drawing.Size(760, 200)
$compareOutputBox.Location = New-Object System.Drawing.Point(10, 400)
$form.Controls.Add($compareOutputBox)
# Load Company Names into ListBox from manifest.json
$manifestPath = "C:\Users\$env:USERNAME\Desktop\NWABackup\manifest.json"
$manifestData = $null
if (Test-Path $manifestPath) {
try {
$manifestData = Get-Content -Path $manifestPath | ConvertFrom-Json
$companyNames = $manifestData.PSObject.Properties.Name
$companyList.Items.AddRange($companyNames)
} catch {
[System.Windows.Forms.MessageBox]::Show("Failed to parse manifest.json: $_", "Error", [System.Windows.Forms.MessageBoxButtons]::OK, [System.Windows.Forms.MessageBoxIcon]::Error)
}
} else {
[System.Windows.Forms.MessageBox]::Show("Manifest file not found at $manifestPath", "Error", [System.Windows.Forms.MessageBoxButtons]::OK, [System.Windows.Forms.MessageBoxIcon]::Error)
}
# Event: When a company is selected, populate file details
$companyList.Add_SelectedIndexChanged({
$selectedCompany = $companyList.SelectedItem
$compareOutputBox.Text = ""
$qxpButton.Enabled = $false # Disable the QXP button when the company changes
if ($selectedCompany -and $manifestData) {
$fileDetailsListBox.Items.Clear()
try {
# Retrieve the file groups for the selected company
$fileGroups = $manifestData.$selectedCompany.PSObject.Properties
foreach ($group in $fileGroups) {
$fileData = $group.Value
$datFiles = $fileData.DAT
$qxpFiles = $fileData.QXP
# Extract dates from zip file paths
$dates = $datFiles | ForEach-Object {
if ($_ -match "\\(\d{8})_(\d{6})\.zip$") {
# Combine date and time into a single string
$dateTimeString = "$($matches[1])$($matches[2])"
[datetime]::ParseExact($dateTimeString, "yyyyMMddHHmmss", $null)
} else {
$null
}
}
# Get the latest date
$latestDate = $dates | Where-Object { $_ } | Sort-Object -Descending | Select-Object -First 1
# Display the file group and its latest date
$latestDateText = if ($latestDate) {
$latestDate.ToString("yyyy-MM-dd HH:mm:ss")
} else {
"No valid date found"
}
$groupName = [string]$group.Name
$fileDetailsListBox.Items.Add("$latestDateText :: $groupName")
}
} catch {
[System.Windows.Forms.MessageBox]::Show("Error processing files for company '$selectedCompany': $_", "Error", [System.Windows.Forms.MessageBoxButtons]::OK, [System.Windows.Forms.MessageBoxIcon]::Error)
}
}
})
$fileDetailsListBox.Add_SelectedIndexChanged({
$compareOutputBox.Text = "" # Clear the output box
$compareButton.Enabled = $true # Enable the Compare button
$selectedFileGroup = $fileDetailsListBox.SelectedItem
$selectedCompany = $companyList.SelectedItem
$qxpButton.Enabled = $false # Disable the QXP button by default
if ($selectedFileGroup -and $selectedCompany -and $manifestData) {
try {
# Extract the part name from the selected file group
$partName = $selectedFileGroup -replace ".* :: ", ""
# Check if the selected part has QXP files
$qxpFiles = $manifestData.$selectedCompany.$partName.QXP
if ($qxpFiles -and $qxpFiles.Count -gt 0) {
$qxpButton.Enabled = $true
}
} catch {
[System.Windows.Forms.MessageBox]::Show("Error checking QXP availability for '$selectedFileGroup': $_", "Error", [System.Windows.Forms.MessageBoxButtons]::OK, [System.Windows.Forms.MessageBoxIcon]::Error)
}
}
})
# Backup Button Click Event
$backupButton.Add_Click({
if (Test-Path $backupScriptPath) {
Start-Process -FilePath "powershell.exe" -ArgumentList "-File `"$backupScriptPath`"" -NoNewWindow -Wait
[System.Windows.Forms.MessageBox]::Show("Backup completed.", "Information", [System.Windows.Forms.MessageBoxButtons]::OK, [System.Windows.Forms.MessageBoxIcon]::Information)
} else {
[System.Windows.Forms.MessageBox]::Show("Backup script not found at $backupScriptPath", "Error", [System.Windows.Forms.MessageBoxButtons]::OK, [System.Windows.Forms.MessageBoxIcon]::Error)
}
})
# Compare Button Click Event
$compareButton.Add_Click({
if ($processing) {
[System.Windows.Forms.MessageBox]::Show("Please wait for the current operation to complete.", "Info", [System.Windows.Forms.MessageBoxButtons]::OK, [System.Windows.Forms.MessageBoxIcon]::Information)
return
}
$selectedCompany = $companyList.SelectedItem
$selectedFileGroup = $fileDetailsListBox.SelectedItem
if (-not $selectedCompany) {
[System.Windows.Forms.MessageBox]::Show("Please select a company from the list.", "Error", [System.Windows.Forms.MessageBoxButtons]::OK, [System.Windows.Forms.MessageBoxIcon]::Error)
return
}
if (-not $selectedFileGroup) {
[System.Windows.Forms.MessageBox]::Show("Please select a file group from the list.", "Error", [System.Windows.Forms.MessageBoxButtons]::OK, [System.Windows.Forms.MessageBoxIcon]::Error)
return
}
try {
$processing = $true
$backupButton.Enabled = $false
$compareButton.Enabled = $false
$compareOutputBox.text = ""
# Extract the file name (ignore the date and time)
$fileName = $selectedFileGroup -replace ".* :: ", ""
# Construct the PowerShell command
$command = "$compareScriptPath -CompanyName '$selectedCompany' -FileName '$fileName.DAT'"
# Run the compare script and redirect output
$processInfo = New-Object System.Diagnostics.ProcessStartInfo
$processInfo.FileName = "powershell.exe"
$processInfo.Arguments = "-NoProfile -Command `"$command`""
$processInfo.RedirectStandardOutput = $true
$processInfo.RedirectStandardError = $true
$processInfo.UseShellExecute = $false
$processInfo.CreateNoWindow = $true
$process = New-Object System.Diagnostics.Process
$process.StartInfo = $processInfo
$process.Start() | Out-Null
# Read both standard output and error output
$output = $process.StandardOutput.ReadToEnd()
$errorOutput = $process.StandardError.ReadToEnd()
$process.WaitForExit()
# Display the output or errors in the Compare Output TextBox
if ($output) {
$compareOutputBox.Text = $output -join "`r`n"
} elseif ($errorOutput) {
$compareOutputBox.Text = "Error: $errorOutput"
} else {
$compareOutputBox.Text = "No output received from the compare script."
}
} catch {
[System.Windows.Forms.MessageBox]::Show("Error running compare script: $($_.Exception.Message)", "Error", [System.Windows.Forms.MessageBoxButtons]::OK, [System.Windows.Forms.MessageBoxIcon]::Error)
} finally {
$processing = $false
$backupButton.Enabled = $true
}
})
# Event: QXP Button Click
$qxpButton.Add_Click({
$selectedCompany = $companyList.SelectedItem
if (-not $selectedCompany) {
[System.Windows.Forms.MessageBox]::Show("Please select a company from the list.", "Error", [System.Windows.Forms.MessageBoxButtons]::OK, [System.Windows.Forms.MessageBoxIcon]::Error)
return
}
$selectedFileGroup = $fileDetailsListBox.SelectedItem
if (-not $selectedFileGroup) {
[System.Windows.Forms.MessageBox]::Show("Please select a file group from the list.", "Error", [System.Windows.Forms.MessageBoxButtons]::OK, [System.Windows.Forms.MessageBoxIcon]::Error)
return
}
$fileName = $selectedFileGroup -replace ".* - ", ""
[System.Windows.Forms.MessageBox]::Show("Opening QXP file for ${selectedCompany}: $fileName", "QXP File", [System.Windows.Forms.MessageBoxButtons]::OK, [System.Windows.Forms.MessageBoxIcon]::Information)
# Implement the actual logic for opening the QXP file if needed
})
# Show Form
$form.ShowDialog()

5
config.json.sample Normal file
View File

@ -0,0 +1,5 @@
{
"BackupScriptPath": "X:\\dev\\BOYD\\sentinel\\Modules\\NWABackup.ps1",
"CompareScriptPath": "X:\\dev\\BOYD\\sentinel\\Modules\\NWA-Compare.ps1",
"OutputPath": "C:\\Users\\$env:USERNAME\\Desktop\\NWABackup\\logs\\compare-output.txt"
}