<# .SYNOPSIS Download and silently install a list of installers from a central HTTP share. .DESCRIPTION - Downloads installers from a base URL to $env:TEMP\software_installs - MSI files installed with msiexec /qn /norestart - EXE files: tries common silent switches until the installer exits with code 0 - Logs to console and to a logfile in the temp folder .USAGE Run in an elevated PowerShell prompt: PS> .\Install-Software.ps1 Edit the $packages array if filenames differ. #> # --------- Configuration --------- $baseUrl = 'https://videos.abcreal.com/Software' $tempDir = Join-Path $env:TEMP 'software_installs' $logFile = Join-Path $tempDir ("install_log_{0}.log" -f (Get-Date -Format 'yyyyMMdd_HHmmss')) # List of packages to install. Update names if your files use different names. $packages = @( @{ Name = 'Acrobat Reader'; File = 'acrobat_installer.exe'; Type = 'exe' }, @{ Name = 'Antivirus'; File = 'antivirus_installer.exe'; Type = 'exe' }, @{ Name = 'Google Chrome'; File = 'chrome_installer.exe'; Type = 'exe' }, @{ Name = 'LogMeIn'; File = 'logmein_installer.msi'; Type = 'msi' }, @{ Name = 'Office (At Hand)'; File = 'officeathand_installer.msi'; Type = 'msi' }, @{ Name = 'Zoom'; File = 'zoom_installer.msi'; Type = 'msi' } ) # Common silent switches to try for .exe installers (order matters; many installers accept one of these) $exeSilentSwitches = @( '/S', # many NSIS/Inno installers '/s', # lowercase variant '/quiet', # some vendors '/silent', # some vendors '/verysilent', # NSIS variant '/qn', # sometimes supported '/install /quiet', # compound '/i /quiet' ) # --------- Helper functions --------- function Log { param($msg) $time = Get-Date -Format 'yyyy-MM-dd HH:mm:ss' $line = ("[{0}] {1}" -f $time, $msg) $line $line | Out-File -FilePath $logFile -Append -Encoding utf8 } function Ensure-Admin { $isAdmin = ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole] "Administrator") if (-not $isAdmin) { Write-Error "This script must be run as Administrator. Exiting." exit 2 } } function Download-File($url, $destination) { try { Log "Downloading $url -> $destination" # Use TLS strong protocols [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 Invoke-WebRequest -Uri $url -OutFile $destination -UseBasicParsing -ErrorAction Stop return $true } catch { Log "ERROR downloading $url : $_" return $false } } function Install-MSI($msiPath) { $args = "/i `"$msiPath`" /qn /norestart" Log "Installing MSI: msiexec $args" $proc = Start-Process -FilePath "msiexec.exe" -ArgumentList $args -Wait -PassThru -NoNewWindow Log "msiexec exit code: $($proc.ExitCode)" return $proc.ExitCode } function Install-EXE-TrySwitches($exePath, [string[]]$switches) { foreach ($s in $switches) { # split switches for Start-Process as array keeping full tokenization $argText = $s Log "Trying EXE install: `"$exePath`" $argText" try { $proc = Start-Process -FilePath $exePath -ArgumentList $argText -Wait -PassThru -NoNewWindow -ErrorAction Stop Log "Return code (attempt '$argText'): $($proc.ExitCode)" if ($proc.ExitCode -eq 0) { Log "Installer succeeded using switches: $argText" return 0 } else { Log "Non-zero exit code ($($proc.ExitCode)) with switches: $argText" } } catch { Log "Failed to launch installer with switches '$argText': $_" } } # If none succeeded, try launching without arguments (some installers handle UI-less args differently) Log "All common silent switches failed. Trying to run installer without args (may show UI)." try { $proc = Start-Process -FilePath $exePath -Wait -PassThru -NoNewWindow -ErrorAction Stop Log "Return code (no args): $($proc.ExitCode)" return $proc.ExitCode } catch { Log "Failed to run installer without args: $_" return 999 } } # --------- Script start --------- Ensure-Admin if (-not (Test-Path $tempDir)) { New-Item -Path $tempDir -ItemType Directory -Force | Out-Null } Log "Starting installation run" Log "Download folder: $tempDir" Log "Log file: $logFile" $overallFailures = @() foreach ($pkg in $packages) { $name = $pkg.Name $file = $pkg.File $type = $pkg.Type.ToLower() $url = ($baseUrl.TrimEnd('/') + '/' + $file) $localPath = Join-Path $tempDir $file Log "Processing package: $name (file: $file, type: $type)" if (-not (Download-File -url $url -destination $localPath)) { Log "ERROR: Download failed for $file" $overallFailures += @{Package=$name; File=$file; Reason='Download failed'} continue } if ($type -eq 'msi') { $exit = Install-MSI -msiPath $localPath if ($exit -ne 0) { Log "ERROR: MSI installation failed for $file with exit code $exit" $overallFailures += @{Package=$name; File=$file; Reason=("MSI exit code $exit")} } else { Log "SUCCESS: MSI installed: $file" } } elseif ($type -eq 'exe') { $exit = Install-EXE-TrySwitches -exePath $localPath -switches $exeSilentSwitches if ($exit -ne 0) { Log "ERROR: EXE installation may have failed for $file (exit code $exit). Check installer UI/logs." $overallFailures += @{Package=$name; File=$file; Reason=("EXE exit code $exit")} } else { Log "SUCCESS: EXE installed: $file" } } else { Log "Unknown package type '$type' for $file - skipping" $overallFailures += @{Package=$name; File=$file; Reason=("Unknown type $type")} } } # Summary if ($overallFailures.Count -eq 0) { Log "All packages processed successfully (based on exit codes)." Write-Output "All packages processed successfully. See log: $logFile" exit 0 } else { Log "Some packages failed. Summary:" foreach ($f in $overallFailures) { Log (" - {0} ({1}): {2}" -f $f.Package, $f.File, $f.Reason) } Write-Output "Some packages failed. See log: $logFile" exit 1 }