<# .SYNOPSIS Checks a web repository for a new version based on a version file. If newer, downloads all files listed in a manifest file from the repository, replacing local files and creating necessary subdirectories. .DESCRIPTION This script compares a local version.txt file with one on a web server. If the web server version is newer, it downloads a manifest.txt file from the server. It then iterates through the manifest, downloading each listed file/path to the local script directory ($PSScriptRoot), creating subdirectories as needed. .NOTES Author: [Your Name/Company] Date: [Current Date] Requires: PowerShell 3.0 or later (for Invoke-WebRequest). Internet connectivity to the web repository. A 'version.txt' file in the web repository containing the latest version string. A 'manifest.txt' file in the web repository listing all files/relative paths to download (one per line). .EXAMPLE .\Update.ps1 Runs the update check from the default web repository. #> # --- Configuration --- # Web repository path (Ensure spaces are properly encoded if manually typing, but $webRepoPath variable itself should contain the raw path) $webRepoPath = "https://scripts.poteez.com/ExchangeScript/Scripts/" # Local path to store files (usually the directory where this script resides) $localPath = $PSScriptRoot # Version file name $versionFileName = "version.txt" # Manifest file name (lists all files/paths to download) $manifestFileName = "manifest.txt" # --- Function Definition --- # Function to update from the web repository function UpdateFromWebRepo { param( [Parameter(Mandatory=$true)] [string]$RepoPath ) # --- Step 1: Fetch Central Version --- $centralVersionUri = "$RepoPath/$versionFileName" try { Write-Host "Attempting to fetch version file from: $centralVersionUri" $webRequestParams = @{ Uri = $centralVersionUri UseBasicParsing = $true # Often helps with compatibility/simplicity ErrorAction = 'Stop' # Ensure catch block triggers reliably } # Ensure we get plain text content and trim whitespace $centralVersion = (Invoke-WebRequest @webRequestParams).Content.Trim() Write-Host "Central version fetched: $centralVersion" } catch { $errorMessage = $_.Exception.Message Write-Host ("ERROR: Could not fetch central version file '{0}'. Message: {1}" -f $versionFileName, $errorMessage) Write-Host "Please check the repository path and network connectivity." return $false # Cannot proceed without central version } # --- Step 2: Get Local Version --- $localVersionFilePath = Join-Path -Path $localPath -ChildPath $versionFileName $localVersion = Get-Content -Path $localVersionFilePath -ErrorAction SilentlyContinue # If file doesn't exist, $localVersion will be $null, which is fine for comparison Write-Host "Local version (if present): $localVersion" # --- Step 3: Compare Versions and Update if Needed --- if ($centralVersion -ne $localVersion) { Write-Host "Newer version ($centralVersion) found. Local version is '$localVersion'." Write-Host "Starting update from $RepoPath..." # --- Step 4: Fetch the Manifest File --- $filesToDownload = @() # Initialize empty array $manifestUri = "$RepoPath/$manifestFileName" try { Write-Host "Attempting to fetch manifest file from: $manifestUri" $manifestRequestParams = @{ Uri = $manifestUri UseBasicParsing = $true ErrorAction = 'Stop' } # Split content into lines, remove empty lines/whitespace-only lines $manifestContent = (Invoke-WebRequest @manifestRequestParams).Content $filesToDownload = $manifestContent -split [System.Environment]::NewLine | Where-Object { $_.Trim() -ne '' } Write-Host "Manifest fetched successfully. Found $($filesToDownload.Count) files/paths listed." if ($filesToDownload.Count -eq 0) { Write-Host "WARNING: Manifest file '$manifestFileName' is empty or could not be parsed correctly." Write-Host "No files will be downloaded based on the manifest." # Optional: Decide if you want to force download version.txt anyway or fail completely # Let's attempt to download version.txt as a minimum requirement if manifest is empty if (-not ($filesToDownload -contains $versionFileName)) { $filesToDownload = @($versionFileName) Write-Host "Attempting to download '$versionFileName' as a minimum." } } } catch { $errorMessage = $_.Exception.Message Write-Host ("ERROR: Could not fetch manifest file '{0}'. Message: {1}" -f $manifestFileName, $errorMessage) Write-Host "Update cannot proceed without the manifest file list." return $false # Cannot proceed without the list of files } # --- Step 5: Download Files from Manifest List --- $updateSuccessful = $true # Track overall success Write-Host "----------------------------------------" Write-Host "Downloading files..." Write-Host "----------------------------------------" foreach ($fileRelativePath in $filesToDownload) { # Trim any potential whitespace from the manifest line $cleanRelativePath = $fileRelativePath.Trim() if ([string]::IsNullOrWhiteSpace($cleanRelativePath)) { continue } # Skip if somehow an empty line got through # Construct remote URI (handle spaces by basic encoding) # Note: Assumes forward slashes '/' in manifest for subdirs. $encodedFileRelativePath = $cleanRelativePath -replace ' ', '%20' $downloadUri = "$RepoPath/$encodedFileRelativePath" # Construct full local path # Use Join-Path which handles path separators correctly for the OS $localFilePath = Join-Path -Path $localPath -ChildPath $cleanRelativePath # Ensure local directory exists before downloading $localDirectory = Split-Path -Path $localFilePath -Parent if ($localDirectory -and (-not (Test-Path -Path $localDirectory -PathType Container))) { Write-Host " -> Creating directory: '$localDirectory'" try { New-Item -ItemType Directory -Path $localDirectory -Force -ErrorAction Stop | Out-Null Write-Host " -> Directory created successfully." } catch { $errorMessage = $_.Exception.Message Write-Host (" -> ERROR: Failed to create directory '{0}'. Message: {1}" -f $localDirectory, $errorMessage) $updateSuccessful = $false Write-Host (" -> Skipping download of '{0}' due to directory creation failure." -f $cleanRelativePath) continue # Skip this file if directory creation fails } } # Download the file Write-Host " -> Downloading: '$cleanRelativePath'" # Write-Host " From: '$downloadUri'" # Uncomment for verbose debugging # Write-Host " To: '$localFilePath'" # Uncomment for verbose debugging try { $downloadParams = @{ Uri = $downloadUri OutFile = $localFilePath UseBasicParsing = $true ErrorAction = 'Stop' } Invoke-WebRequest @downloadParams Write-Host " -> Downloaded '$cleanRelativePath' successfully." } catch { $errorMessage = $_.Exception.Message # Check for common 404 error if ($_.Exception.Response -ne $null -and $_.Exception.Response.StatusCode -eq 404) { Write-Host (" -> ERROR: Failed to download '{0}'. File not found (404) at '{1}'." -f $cleanRelativePath, $downloadUri) } else { Write-Host (" -> ERROR: Failed to download '{0}'. Message: {1}" -f $cleanRelativePath, $errorMessage) } $updateSuccessful = $false # Mark update as failed if any file fails # Consider whether to stop entirely or continue with other files (current: continue) } Write-Host "----------" # Separator between files } # End foreach file # --- Step 6: Final Status and Return --- Write-Host "----------------------------------------" if ($updateSuccessful) { Write-Host "Update to version $centralVersion completed successfully!" # CRITICAL: Update the local version file ONLY if all downloads were successful try { Set-Content -Path $localVersionFilePath -Value $centralVersion -Encoding UTF8 -Force -ErrorAction Stop Write-Host "Local version file updated to $centralVersion." } catch { $errorMessage = $_.Exception.Message Write-Host ("WARNING: Update downloads seemed successful, but failed to update local version file '{0}'. Message: {1}" -f $localVersionFilePath, $errorMessage) # Decide if this should still return true or false. Returning true since files likely updated. } return $true } else { Write-Host "Update completed with one or more errors. Please review the log above." Write-Host "Local files may be in an inconsistent state." # Do NOT update the local version file if there were errors. return $false # Indicate partial or full failure } } else { Write-Host "Scripts are already up-to-date (Version: $localVersion)." return $false # Indicate no update was needed } } # End Function UpdateFromWebRepo # --- Main Script Execution --- Write-Host "========================================" Write-Host " Starting Update Process" Write-Host " Repository: $webRepoPath" Write-Host " Local Path: $localPath" Write-Host "========================================" Write-Host "" # Newline for readability $updatePerformed = $false # Default status try { $updatePerformed = UpdateFromWebRepo -RepoPath $webRepoPath } catch { # Catch unexpected errors in the main call Write-Host "An unexpected error occurred during the update process:" Write-Host $_.Exception.Message # Consider logging $_ | Format-List * -Force for more details $updatePerformed = $false } Write-Host "" # Newline for readability Write-Host "========================================" if ($updatePerformed) { Write-Host "Update process finished. Files were updated." } else { # This message covers "already up to date", "web repo unavailable", "manifest error", or "download errors" Write-Host "Update process finished. No updates performed or errors occurred during download." } Write-Host "========================================" Write-Host "" # Optional: Pause if running directly in a console window that might close automatically # Read-Host -Prompt "Press Enter to continue..." # --- End of Update Script --- # The rest of your original batch script (`@echo off`...) would typically follow # AFTER this PowerShell script finishes execution if called from batch. # If this PS1 *is* the main entry point, you would launch your batch file from here if needed. # Example: & cmd /c ".\On-Prem Exchange Script.bat"