<# .SYNOPSIS Silent ScreenConnect installation script with pre-installation cleanup .DESCRIPTION Downloads and installs ScreenConnect MSI silently with comprehensive error handling Removes existing ScreenConnect instances before installation .NOTES Designed to run as SYSTEM agent #> # Set error handling $ErrorActionPreference = 'Stop' $ProgressPreference = 'SilentlyContinue' # Configuration $msiUrl = "https://sparkcomputers.screenconnect.com/Bin/ScreenConnect.ClientSetup.msi?e=Access&y=Guest&c=Distribution&c=&c=&c=&c=&c=&c=&c=" $tempPath = "$env:TEMP\ScreenConnect" $msiPath = "$tempPath\ScreenConnect.ClientSetup.msi" $logPath = "$tempPath\ScreenConnect_Install.log" $maxRetries = 3 $retryDelay = 5 # Create temp directory try { if (-not (Test-Path $tempPath)) { New-Item -Path $tempPath -ItemType Directory -Force | Out-Null } } catch { $tempPath = $env:TEMP $msiPath = "$tempPath\ScreenConnect.ClientSetup.msi" $logPath = "$tempPath\ScreenConnect_Install.log" } # Logging function function Write-Log { param([string]$Message, [string]$Level = "INFO") $timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss" $logMessage = "[$timestamp] [$Level] $Message" Add-Content -Path $logPath -Value $logMessage -Force # Also write to console for immediate feedback $color = switch ($Level) { "ERROR" { "Red" } "WARN" { "Yellow" } "INFO" { "Cyan" } default { "White" } } Write-Host $Message -ForegroundColor $color } # Display cool ASCII art banner $banner = @" ███████╗██████╗ █████╗ ██████╗ ██╗ ██╗ ██╔════╝██╔══██╗██╔══██╗██╔══██╗██║ ██╔╝ ███████╗██████╔╝███████║██████╔╝█████╔╝ ╚════██║██╔═══╝ ██╔══██║██╔══██╗██╔═██╗ ███████║██║ ██║ ██║██║ ██║██║ ██╗ ╚══════╝╚═╝ ╚═╝ ╚═╝╚═╝ ╚═╝╚═╝ ╚═╝ ⚡ COMPUTERS - ScreenConnect Installer "@ Write-Host $banner -ForegroundColor Cyan Write-Host "" Write-Host "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" -ForegroundColor DarkCyan Write-Host " Starting automated installation..." -ForegroundColor White Write-Host " Log file: $logPath" -ForegroundColor DarkGray Write-Host "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" -ForegroundColor DarkCyan Write-Host "" Write-Log "Starting ScreenConnect installation script" # Function to show progress step function Show-Progress { param([string]$Message, [int]$Step, [int]$TotalSteps) $percentage = [math]::Round(($Step / $TotalSteps) * 100) $bar = "█" * [math]::Floor($percentage / 5) $empty = "░" * (20 - [math]::Floor($percentage / 5)) Write-Host "" Write-Host " [$bar$empty] $percentage% - $Message" -ForegroundColor Yellow } # Function to stop ScreenConnect services function Stop-ScreenConnectServices { Write-Log "Stopping ScreenConnect services..." $services = Get-Service | Where-Object { $_.Name -like "*ScreenConnect*" -or $_.DisplayName -like "*ScreenConnect*" } foreach ($service in $services) { try { Write-Log "Stopping service: $($service.Name)" Stop-Service -Name $service.Name -Force -ErrorAction SilentlyContinue $service.WaitForStatus('Stopped', '00:00:30') Write-Log "Service stopped: $($service.Name)" } catch { Write-Log "Warning: Could not stop service $($service.Name): $_" "WARN" } } } # Function to kill ScreenConnect processes function Stop-ScreenConnectProcesses { Write-Log "Terminating ScreenConnect processes..." $processNames = @("ScreenConnect.ClientService", "ScreenConnect.WindowsClient", "ScreenConnect*") foreach ($processName in $processNames) { try { $processes = Get-Process -Name $processName -ErrorAction SilentlyContinue foreach ($process in $processes) { Write-Log "Terminating process: $($process.Name) (PID: $($process.Id))" Stop-Process -Id $process.Id -Force -ErrorAction SilentlyContinue } } catch { } } Start-Sleep -Seconds 2 } # Function to uninstall existing ScreenConnect instances function Uninstall-ScreenConnect { Write-Log "Checking for existing ScreenConnect installations..." $uninstallPaths = @( "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\*", "HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\*" ) $screenConnectProducts = @() foreach ($path in $uninstallPaths) { try { $products = Get-ItemProperty $path -ErrorAction SilentlyContinue | Where-Object { $_.DisplayName -like "*ScreenConnect*" -or $_.DisplayName -like "*ConnectWise Control*" } $screenConnectProducts += $products } catch { } } if ($screenConnectProducts.Count -eq 0) { Write-Log "No existing ScreenConnect installations found" return } foreach ($product in $screenConnectProducts) { Write-Log "Found installation: $($product.DisplayName) - Version: $($product.DisplayVersion)" $uninstallString = $product.UninstallString if ($uninstallString) { try { Write-Log "Uninstalling: $($product.DisplayName)" if ($uninstallString -match "msiexec") { $productCode = $product.PSChildName $uninstallLog = Join-Path $env:TEMP "ScreenConnect_Uninstall.log" $uninstallArgs = @( "/x" $productCode "/qn" "/norestart" "/L*v" $uninstallLog ) Write-Log "Running: msiexec.exe with args: $($uninstallArgs -join ' ')" $process = Start-Process -FilePath "msiexec.exe" -ArgumentList $uninstallArgs -Wait -PassThru -NoNewWindow Write-Log "Uninstall exit code: $($process.ExitCode)" } else { $uninstallString = $uninstallString -replace '"', '' if ($uninstallString -match "^(.+\.exe)(.*)$") { $exePath = $matches[1] $process = Start-Process -FilePath $exePath -ArgumentList "/S /quiet /norestart" -Wait -PassThru -NoNewWindow -ErrorAction SilentlyContinue Write-Log "Uninstall exit code: $($process.ExitCode)" } } Start-Sleep -Seconds 3 } catch { Write-Log "Error uninstalling $($product.DisplayName): $_" "ERROR" } } } } # Function to remove ScreenConnect directories function Remove-ScreenConnectDirectories { Write-Log "Removing ScreenConnect directories..." $directories = @( "$env:ProgramFiles\ScreenConnect Client*", "$env:ProgramFiles" + "(x86)" + "\ScreenConnect Client*" "$env:ProgramData\ScreenConnect Client*" ) foreach ($dirPattern in $directories) { try { $dirs = Get-Item -Path $dirPattern -ErrorAction SilentlyContinue foreach ($dir in $dirs) { Write-Log "Removing directory: $($dir.FullName)" Remove-Item -Path $dir.FullName -Recurse -Force -ErrorAction SilentlyContinue } } catch { Write-Log "Could not remove directory $dirPattern (may not exist)" "WARN" } } } # Function to download file with retry logic function Download-FileWithRetry { param( [string]$Url, [string]$OutputPath, [int]$MaxRetries = $maxRetries ) for ($i = 1; $i -le $MaxRetries; $i++) { try { Write-Log "Download attempt $i of $MaxRetries" [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 $webClient = New-Object System.Net.WebClient $webClient.DownloadFile($Url, $OutputPath) $webClient.Dispose() if (Test-Path $OutputPath) { $fileSize = (Get-Item $OutputPath).Length Write-Log "Download successful. File size: $fileSize bytes" return $true } } catch { Write-Log "Download attempt $i failed: $_" "WARN" if ($i -lt $MaxRetries) { Write-Log "Retrying in $retryDelay seconds..." Start-Sleep -Seconds $retryDelay } } } return $false } # Function to install MSI without logging (fallback) function Install-MSIWithoutLog { param([string]$MsiPath) Write-Log "Attempting installation without MSI logging" $arguments = @( "/i" $MsiPath "/qn" "/norestart" ) try { $process = Start-Process -FilePath "msiexec.exe" -ArgumentList $arguments -Wait -PassThru -NoNewWindow $exitCode = $process.ExitCode Write-Log "MSI installation (no log) completed with exit code: $exitCode" if ($exitCode -eq 0 -or $exitCode -eq 1641 -or $exitCode -eq 3010) { return $true } return $false } catch { Write-Log "Exception during installation: $_" "ERROR" return $false } } # Function to install MSI function Install-MSI { param([string]$MsiPath) Write-Log "Starting MSI installation: $MsiPath" # Use a simpler path for MSI log to avoid quoting issues $msiLog = Join-Path $env:TEMP "ScreenConnect_MSI_Install.log" # Build arguments as an array - Start-Process handles quoting automatically $arguments = @( "/i" $MsiPath "/qn" "/norestart" "/L*v" $msiLog ) Write-Log "Running: msiexec.exe with arguments: $($arguments -join ' ')" try { $process = Start-Process -FilePath "msiexec.exe" -ArgumentList $arguments -Wait -PassThru -NoNewWindow $exitCode = $process.ExitCode Write-Log "MSI installation completed with exit code: $exitCode" switch ($exitCode) { 0 { Write-Log "Installation completed successfully"; return $true } 1641 { Write-Log "Installation completed successfully (restart initiated)"; return $true } 3010 { Write-Log "Installation completed successfully (restart required)"; return $true } 1603 { Write-Log "Fatal error during installation" "ERROR"; return $false } 1618 { Write-Log "Another installation is in progress" "ERROR"; return $false } 1619 { Write-Log "Installation package could not be opened" "ERROR"; return $false } 1622 { Write-Log "Error opening installation log file - trying without logging" "WARN"; return Install-MSIWithoutLog -MsiPath $MsiPath } default { Write-Log "Installation returned exit code: $exitCode" "WARN"; return $false } } } catch { Write-Log "Exception during installation: $_" "ERROR" return $false } } # Main execution try { Write-Log "===== Starting ScreenConnect Installation Process =====" Write-Log "Running as: $env:USERNAME" Show-Progress "Preparing system..." 1 7 Stop-ScreenConnectServices Stop-ScreenConnectProcesses Show-Progress "Removing old installations..." 2 7 Uninstall-ScreenConnect Show-Progress "Cleaning directories..." 3 7 Remove-ScreenConnectDirectories Stop-ScreenConnectProcesses Start-Sleep -Seconds 2 Show-Progress "Downloading ScreenConnect installer..." 4 7 Write-Log "Downloading ScreenConnect MSI from: $msiUrl" if (-not (Download-FileWithRetry -Url $msiUrl -OutputPath $msiPath)) { throw "Failed to download ScreenConnect MSI after $maxRetries attempts" } if (-not (Test-Path $msiPath)) { throw "Downloaded file not found at: $msiPath" } $fileInfo = Get-Item $msiPath if ($fileInfo.Length -lt 1000) { throw "Downloaded file appears to be invalid (size: $($fileInfo.Length) bytes)" } Show-Progress "Installing ScreenConnect..." 5 7 $installSuccess = Install-MSI -MsiPath $msiPath if (-not $installSuccess) { throw "MSI installation failed" } Show-Progress "Verifying installation..." 6 7 Start-Sleep -Seconds 5 $verifyService = Get-Service | Where-Object { $_.DisplayName -like "*ScreenConnect*" } if ($verifyService) { Write-Log "Installation verified - ScreenConnect service found: $($verifyService.Name)" } else { Write-Log "Warning: ScreenConnect service not detected after installation" "WARN" } Show-Progress "Installation complete!" 7 7 Write-Log "===== ScreenConnect Installation Completed Successfully =====" # Success banner $successBanner = @" ╔══════════════════════════════════════════════════╗ ║ ║ ║ ⚡ INSTALLATION SUCCESSFUL! ⚡ ║ ║ ║ ║ ScreenConnect is now installed and running ║ ║ ║ ╚══════════════════════════════════════════════════╝ "@ Write-Host $successBanner -ForegroundColor Green } catch { Write-Log "FATAL ERROR: $_" "ERROR" Write-Log "Stack Trace: $($_.ScriptStackTrace)" "ERROR" # Error banner $errorBanner = @" ╔══════════════════════════════════════════════════╗ ║ ║ ║ ⚠️ INSTALLATION FAILED ⚠️ ║ ║ ║ ║ Check the log file for details: ║ ║ $logPath ║ ║ ╚══════════════════════════════════════════════════╝ "@ Write-Host $errorBanner -ForegroundColor Red exit 1 } finally { try { if (Test-Path $msiPath) { Write-Log "Cleaning up downloaded MSI" Remove-Item -Path $msiPath -Force -ErrorAction SilentlyContinue } } catch { Write-Log "Could not remove temporary MSI file" "WARN" } } exit 0