# -------------------------------------------- # 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"