From 4f8b807f0ce173f3dcf70a48d9b08b092702219c Mon Sep 17 00:00:00 2001 From: rcourtman Date: Thu, 13 Nov 2025 23:09:22 +0000 Subject: [PATCH] Refine Windows host installer logging (related to #709) --- frontend-modern/public/install-host-agent.ps1 | 187 ++++++++++++------ .../public/uninstall-host-agent.ps1 | 96 ++++----- scripts/install-host-agent.ps1 | 136 ++++++------- scripts/uninstall-host-agent.ps1 | 96 ++++----- 4 files changed, 298 insertions(+), 217 deletions(-) diff --git a/frontend-modern/public/install-host-agent.ps1 b/frontend-modern/public/install-host-agent.ps1 index 634e99c..8e97988 100644 --- a/frontend-modern/public/install-host-agent.ps1 +++ b/frontend-modern/public/install-host-agent.ps1 @@ -17,22 +17,24 @@ param( $ErrorActionPreference = "Stop" -# ANSI color codes for output -$Red = "`e[31m" -$Green = "`e[32m" -$Yellow = "`e[33m" -$Blue = "`e[34m" -$Reset = "`e[0m" +function Write-PulseMessage { + param( + [string]$Label, + [string]$Message, + [ConsoleColor]$Color + ) -function Write-Color { - param([string]$Color, [string]$Message) - Write-Host "${Color}${Message}${Reset}" + if ($Label) { + Write-Host ("[{0}] {1}" -f $Label, $Message) -ForegroundColor $Color + } else { + Write-Host $Message -ForegroundColor $Color + } } -function Write-Success { param([string]$msg) Write-Color $Green "✓ $msg" } -function Write-Error { param([string]$msg) Write-Color $Red "✗ $msg" } -function Write-Info { param([string]$msg) Write-Color $Blue "ℹ $msg" } -function Write-Warning { param([string]$msg) Write-Color $Yellow "⚠ $msg" } +function PulseSuccess { param([string]$msg) Write-PulseMessage -Label 'OK' -Message $msg -Color 'Green' } +function PulseError { param([string]$msg) Write-PulseMessage -Label 'FAIL' -Message $msg -Color 'Red' } +function PulseInfo { param([string]$msg) Write-PulseMessage -Label 'INFO' -Message $msg -Color 'Cyan' } +function PulseWarn { param([string]$msg) Write-PulseMessage -Label 'WARN' -Message $msg -Color 'Yellow' } function Write-InstallerEvent { param( @@ -47,7 +49,7 @@ function Write-InstallerEvent { try { Write-EventLog -LogName Application -Source $SourceName -EventId $EventId -EntryType $EntryType -Message $Message } catch { - Write-Warning "Unable to write installer event log entry: $_" + PulseWarn "Unable to write installer event log entry: $_" } } @@ -92,16 +94,17 @@ function Test-AgentRegistration { } Write-Host "" -Write-Color $Blue "═══════════════════════════════════════════════════════════" -Write-Color $Blue " Pulse Host Agent - Windows Installation" -Write-Color $Blue "═══════════════════════════════════════════════════════════" +$banner = "=" * 59 +Write-Host $banner -ForegroundColor Cyan +Write-Host " Pulse Host Agent - Windows Installation" -ForegroundColor Cyan +Write-Host $banner -ForegroundColor Cyan Write-Host "" # Check if running as Administrator $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" - Write-Info "Right-click PowerShell and select 'Run as Administrator'" + PulseError "This script must be run as Administrator" + PulseInfo "Right-click PowerShell and select 'Run as Administrator'" exit 1 } @@ -112,7 +115,7 @@ if (-not $PulseUrl) { $PulseUrl = $PulseUrl.TrimEnd('/') if (-not $Token) { - Write-Warning "No API token provided - agent will attempt to connect without authentication" + PulseWarn "No API token provided - agent will attempt to connect without authentication" $response = Read-Host "Continue without token? (y/N)" if ($response -ne 'y' -and $response -ne 'Y') { $Token = Read-Host "Enter API token" @@ -123,7 +126,7 @@ if (-not $Interval) { $Interval = "30s" } -Write-Info "Configuration:" +PulseInfo "Configuration:" Write-Host " Pulse URL: $PulseUrl" Write-Host " Token: $(if ($Token) { '***' + $Token.Substring([Math]::Max(0, $Token.Length - 4)) } else { 'none' })" Write-Host " Interval: $Interval" @@ -137,13 +140,19 @@ switch ($osArch) { 'X64' { $arch = 'amd64' } 'X86' { $arch = '386' } default { - Write-Error "Unsupported architecture: $osArch" + PulseError "Unsupported architecture: $osArch" exit 1 } } $downloadUrl = "$PulseUrl/download/pulse-host-agent?platform=windows&arch=$arch" -Write-Info "Downloading agent binary from $downloadUrl..." +PulseInfo "System Information:" +Write-Host " OS Architecture: $osArch" +Write-Host " Download Architecture: $arch" +Write-Host " Download URL: $downloadUrl" +Write-Host "" + +PulseInfo "Downloading agent binary from $downloadUrl..." try { # Create install directory if (-not (Test-Path $InstallPath)) { @@ -154,7 +163,66 @@ try { # Download binary Invoke-WebRequest -Uri $downloadUrl -OutFile $agentPath -UseBasicParsing - Write-Success "Downloaded agent to $agentPath" + PulseSuccess "Downloaded agent to $agentPath" + + # Validate PE header + $fileBytes = [System.IO.File]::ReadAllBytes($agentPath) + $fileSizeMB = [math]::Round($fileBytes.Length / 1MB, 2) + PulseInfo "File size: $fileSizeMB MB ($($fileBytes.Length) bytes)" + + if ($fileBytes.Length -lt 64) { + throw "Downloaded file is too small ($($fileBytes.Length) bytes) - expected Windows PE executable" + } + + # Check for MZ signature (PE header) + if ($fileBytes[0] -ne 0x4D -or $fileBytes[1] -ne 0x5A) { + $firstBytes = ($fileBytes[0..15] | ForEach-Object { $_.ToString("X2") }) -join " " + throw "Downloaded file is not a valid Windows executable (missing MZ signature). First bytes: $firstBytes" + } + + # Get PE header offset (at 0x3C) + $peOffset = [BitConverter]::ToUInt32($fileBytes, 0x3C) + if ($peOffset -ge $fileBytes.Length - 6) { + throw "Invalid PE header offset in downloaded file" + } + + # Check PE signature + if ($fileBytes[$peOffset] -ne 0x50 -or $fileBytes[$peOffset+1] -ne 0x45) { + throw "Downloaded file has invalid PE signature" + } + + # Check machine type (should be 0x8664 for x64, 0xAA64 for ARM64) + $machineType = [BitConverter]::ToUInt16($fileBytes, $peOffset + 4) + $expectedMachine = switch ($arch) { + 'amd64' { 0x8664 } + 'arm64' { 0xAA64 } + '386' { 0x014C } + default { 0x0000 } + } + + if ($machineType -ne $expectedMachine) { + $machineStr = "0x" + $machineType.ToString("X4") + $expectedStr = "0x" + $expectedMachine.ToString("X4") + throw "Downloaded binary is for wrong architecture (got $machineStr, expected $expectedStr for $arch)" + } + + PulseSuccess "Verified PE executable for $osArch architecture" + + # Verify checksum + PulseInfo "Verifying checksum..." + $checksumUrl = "$PulseUrl/download/pulse-host-agent.sha256?platform=windows&arch=$arch" + try { + $expectedChecksum = (Invoke-WebRequest -Uri $checksumUrl -UseBasicParsing).Content.Trim().Split()[0] + $actualChecksum = (Get-FileHash -Path $agentPath -Algorithm SHA256).Hash.ToLower() + + if ($actualChecksum -ne $expectedChecksum.ToLower()) { + throw "Checksum mismatch! Expected: $expectedChecksum, Got: $actualChecksum" + } + PulseSuccess "Checksum verified: $actualChecksum" + } catch { + PulseWarn "Could not verify checksum: $_" + PulseInfo "Continuing anyway (PE header was validated)" + } $agentArgs = @("--url", "`"$PulseUrl`"", "--interval", $Interval) if ($Token) { @@ -163,7 +231,7 @@ try { $serviceBinaryPath = "`"$agentPath`" $($agentArgs -join ' ')" $manualCommand = "& `"$agentPath`" $($agentArgs -join ' ')" } catch { - Write-Error "Failed to download agent: $_" + PulseError "Failed to download agent: $_" exit 1 } @@ -178,23 +246,23 @@ if ($Token) { } $config | ConvertTo-Json | Set-Content $configPath -Write-Success "Created configuration at $configPath" +PulseSuccess "Created configuration at $configPath" # Stop existing service if running $serviceName = "PulseHostAgent" $existingService = Get-Service -Name $serviceName -ErrorAction SilentlyContinue if ($existingService) { - Write-Info "Stopping existing service..." + PulseInfo "Stopping existing service..." Stop-Service -Name $serviceName -Force - Write-Success "Stopped existing service" + PulseSuccess "Stopped existing service" } if (-not $NoService) { - Write-Info "Installing native Windows service with built-in service support..." + PulseInfo "Installing native Windows service with built-in service support..." try { if ($existingService) { - Write-Info "Removing existing service..." + PulseInfo "Removing existing service..." sc.exe delete $serviceName | Out-Null Start-Sleep -Seconds 2 } @@ -206,73 +274,73 @@ if (-not $NoService) { -Description "Monitors system metrics and reports to Pulse monitoring server" ` -StartupType Automatic | Out-Null - Write-Success "Created Windows service '$serviceName'" + PulseSuccess "Created Windows service '$serviceName'" # Register Windows Event Log source try { if (-not ([System.Diagnostics.EventLog]::SourceExists($serviceName))) { New-EventLog -LogName Application -Source $serviceName - Write-Success "Registered Event Log source" + PulseSuccess "Registered Event Log source" } } catch { - Write-Warning "Could not register Event Log source (not critical): $_" + PulseWarn "Could not register Event Log source (not critical): $_" } Write-InstallerEvent -SourceName $serviceName -Message "Pulse Host Agent installer registered service version $(Get-Item $agentPath).VersionInfo.FileVersion" -EventId 1000 # Configure service recovery options (restart on failure) sc.exe failure $serviceName reset= 86400 actions= restart/60000/restart/60000/restart/60000 | Out-Null - Write-Success "Configured automatic restart on failure" + PulseSuccess "Configured automatic restart on failure" # Start the service - Write-Info "Starting service..." + PulseInfo "Starting service..." Start-Service -Name $serviceName Start-Sleep -Seconds 3 $status = (Get-Service -Name $serviceName).Status if ($status -eq 'Running') { - Write-Success "Service started successfully!" + PulseSuccess "Service started successfully!" - Write-Info "Waiting 10 seconds to validate agent reporting..." + PulseInfo "Waiting 10 seconds to validate agent reporting..." Start-Sleep -Seconds 10 $hostname = $env:COMPUTERNAME $lookupHost = Test-AgentRegistration -PulseUrl $PulseUrl -Hostname $hostname -Token $Token if ($lookupHost) { - Write-Success "Agent successfully registered with Pulse (host '$hostname')." + PulseSuccess "Agent successfully registered with Pulse (host '$hostname')." if ($lookupHost.status) { $lastSeen = $lookupHost.lastSeen if ($lastSeen -is [DateTime]) { $lastSeen = $lastSeen.ToString("u") } - Write-Info ("Pulse reports status: {0} (last seen {1})" -f $lookupHost.status, $lastSeen) + PulseInfo ("Pulse reports status: {0} (last seen {1})" -f $lookupHost.status, $lastSeen) } - Write-Info "Check your Pulse dashboard - this host should appear shortly." + PulseInfo "Check your Pulse dashboard - this host should appear shortly." $statusForLog = if ($lookupHost.status) { $lookupHost.status } else { 'unknown' } Write-InstallerEvent -SourceName $serviceName -Message "Installer verified host '$hostname' reporting to Pulse (status: $statusForLog)." -EventId 1010 } elseif ($Token) { - Write-Warning "Agent is running but the lookup endpoint has not confirmed registration yet." - Write-Info "It may take another moment for metrics to appear in the dashboard." + PulseWarn "Agent is running but the lookup endpoint has not confirmed registration yet." + PulseInfo "It may take another moment for metrics to appear in the dashboard." Write-InstallerEvent -SourceName $serviceName -Message "Installer could not yet confirm host '$hostname' registration with Pulse." -EntryType Warning -EventId 1011 } else { - Write-Info "Registration check skipped (no API token available)." + PulseInfo "Registration check skipped (no API token available)." Write-InstallerEvent -SourceName $serviceName -Message "Installer skipped registration lookup (no API token provided)." -EventId 1012 } $recentLogs = Get-RecentAgentEvents -ProviderName $serviceName -Max 5 if ($recentLogs) { - Write-Info "Recent service events:" + PulseInfo "Recent service events:" $recentLogs | Select-Object -First 3 | ForEach-Object { $time = $_.TimeCreated if (-not $time) { $time = $_.TimeGenerated } Write-Host (" [{0}] {1}" -f $time.ToString("u"), $_.Message) } } else { - Write-Warning "No recent Application log entries were found for $serviceName." + PulseWarn "No recent Application log entries were found for $serviceName." } } else { - Write-Warning "Service status: $status" - Write-Info "Checking service logs..." + PulseWarn "Service status: $status" + PulseInfo "Checking service logs..." $recentLogs = Get-RecentAgentEvents -ProviderName $serviceName -Max 5 if ($recentLogs) { $recentLogs | ForEach-Object { @@ -281,32 +349,33 @@ if (-not $NoService) { Write-Host (" [{0}] {1}" -f $time.ToString("u"), $_.Message) } } else { - Write-Warning "No Application log entries were found for $serviceName." + PulseWarn "No Application log entries were found for $serviceName." } } } catch { - Write-Error "Failed to create/start service: $_" - Write-Info "You can start the agent manually with:" + PulseError "Failed to create/start service: $_" + PulseInfo "You can start the agent manually with:" Write-Host " $manualCommand" Write-Host "" - Write-Info "Or check Windows Event Viewer (Application log) for error details." + PulseInfo "Or check Windows Event Viewer (Application log) for error details." exit 1 } } else { - Write-Info "Skipping service installation (--NoService flag)" + PulseInfo "Skipping service installation (--NoService flag)" Write-Host "" - Write-Info "To start the agent manually:" + PulseInfo "To start the agent manually:" Write-Host " $manualCommand" } Write-Host "" -Write-Color $Green "═══════════════════════════════════════════════════════════" -Write-Success "Installation complete!" -Write-Color $Green "═══════════════════════════════════════════════════════════" +$successBanner = "=" * 59 +Write-Host $successBanner -ForegroundColor Green +PulseSuccess "Installation complete!" +Write-Host $successBanner -ForegroundColor Green Write-Host "" -Write-Info "Service Management Commands:" +PulseInfo "Service Management Commands:" Write-Host " Start: Start-Service -Name PulseHostAgent" Write-Host " Stop: Stop-Service -Name PulseHostAgent" Write-Host " Restart: Restart-Service -Name PulseHostAgent" @@ -315,10 +384,10 @@ Write-Host " Remove: sc.exe delete PulseHostAgent" Write-Host " Logs: Get-WinEvent -FilterHashtable @{LogName='Application'; ProviderName='PulseHostAgent'} -MaxEvents 50" Write-Host "" -Write-Info "Files installed:" +PulseInfo "Files installed:" Write-Host " Binary: $agentPath" Write-Host " Config: $configPath" Write-Host "" -Write-Info "The agent is now reporting to: $PulseUrl" +PulseInfo "The agent is now reporting to: $PulseUrl" Write-Host "" diff --git a/frontend-modern/public/uninstall-host-agent.ps1 b/frontend-modern/public/uninstall-host-agent.ps1 index d1caa89..caecf29 100644 --- a/frontend-modern/public/uninstall-host-agent.ps1 +++ b/frontend-modern/public/uninstall-host-agent.ps1 @@ -10,92 +10,95 @@ param( $ErrorActionPreference = "Stop" -# ANSI color codes for output -$Red = "`e[31m" -$Green = "`e[32m" -$Yellow = "`e[33m" -$Blue = "`e[34m" -$Reset = "`e[0m" +function Write-PulseMessage { + param( + [string]$Label, + [string]$Message, + [ConsoleColor]$Color + ) -function Write-Color { - param([string]$Color, [string]$Message) - Write-Host "${Color}${Message}${Reset}" + if ($Label) { + Write-Host ("[{0}] {1}" -f $Label, $Message) -ForegroundColor $Color + } else { + Write-Host $Message -ForegroundColor $Color + } } -function Write-Success { param([string]$msg) Write-Color $Green "✓ $msg" } -function Write-Error { param([string]$msg) Write-Color $Red "✗ $msg" } -function Write-Info { param([string]$msg) Write-Color $Blue "ℹ $msg" } -function Write-Warning { param([string]$msg) Write-Color $Yellow "⚠ $msg" } +function PulseSuccess { param([string]$msg) Write-PulseMessage -Label 'OK' -Message $msg -Color 'Green' } +function PulseError { param([string]$msg) Write-PulseMessage -Label 'FAIL' -Message $msg -Color 'Red' } +function PulseInfo { param([string]$msg) Write-PulseMessage -Label 'INFO' -Message $msg -Color 'Cyan' } +function PulseWarn { param([string]$msg) Write-PulseMessage -Label 'WARN' -Message $msg -Color 'Yellow' } Write-Host "" -Write-Color $Blue "═══════════════════════════════════════════════════════════" -Write-Color $Blue " Pulse Host Agent - Windows Uninstallation" -Write-Color $Blue "═══════════════════════════════════════════════════════════" +$banner = "=" * 59 +Write-Host $banner -ForegroundColor Cyan +Write-Host " Pulse Host Agent - Windows Uninstallation" -ForegroundColor Cyan +Write-Host $banner -ForegroundColor Cyan Write-Host "" # Check if running as Administrator $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" - Write-Info "Right-click PowerShell and select 'Run as Administrator'" + PulseError "This script must be run as Administrator" + PulseInfo "Right-click PowerShell and select 'Run as Administrator'" exit 1 } $serviceName = "PulseHostAgent" # Stop and remove service -Write-Info "Checking for Pulse Host Agent service..." +PulseInfo "Checking for Pulse Host Agent service..." $service = Get-Service -Name $serviceName -ErrorAction SilentlyContinue if ($service) { if ($service.Status -eq 'Running') { - Write-Info "Stopping service..." + PulseInfo "Stopping service..." try { Stop-Service -Name $serviceName -Force - Write-Success "Service stopped" + PulseSuccess "Service stopped" } catch { - Write-Warning "Could not stop service: $_" + PulseWarn "Could not stop service: $_" } } - Write-Info "Removing service..." + PulseInfo "Removing service..." try { sc.exe delete $serviceName | Out-Null - Write-Success "Service removed" + PulseSuccess "Service removed" } catch { - Write-Warning "Could not remove service: $_" + PulseWarn "Could not remove service: $_" } } else { - Write-Info "Service not found (already removed or never installed)" + PulseInfo "Service not found (already removed or never installed)" } # Ensure all processes are terminated -Write-Info "Ensuring all processes are terminated..." +PulseInfo "Ensuring all processes are terminated..." $processes = Get-Process -Name "pulse-host-agent" -ErrorAction SilentlyContinue if ($processes) { $processes | Stop-Process -Force Start-Sleep -Seconds 2 - Write-Success "Processes terminated" + PulseSuccess "Processes terminated" } else { - Write-Info "No running processes found" + PulseInfo "No running processes found" } # Remove Event Log source -Write-Info "Removing Event Log source..." +PulseInfo "Removing Event Log source..." try { if ([System.Diagnostics.EventLog]::SourceExists($serviceName)) { Remove-EventLog -Source $serviceName - Write-Success "Event Log source removed" + PulseSuccess "Event Log source removed" } else { - Write-Info "Event Log source not found" + PulseInfo "Event Log source not found" } } catch { - Write-Warning "Could not remove Event Log source: $_" + PulseWarn "Could not remove Event Log source: $_" } # Remove installation directory with retry logic (Windows file locking) if (Test-Path $InstallPath) { - Write-Info "Removing installation directory..." + PulseInfo "Removing installation directory..." $retries = 3 $success = $false @@ -106,30 +109,31 @@ if (Test-Path $InstallPath) { Start-Sleep -Seconds 2 Remove-Item -Path $InstallPath -Recurse -Force -ErrorAction Stop - Write-Success "Installation directory removed: $InstallPath" + PulseSuccess "Installation directory removed: $InstallPath" $success = $true } catch { $retries-- if ($retries -gt 0) { - Write-Warning "File still locked, retrying... ($retries attempts remaining)" + PulseWarn "File still locked, retrying... ($retries attempts remaining)" } else { - Write-Error "Could not remove installation directory after multiple attempts: $_" - Write-Warning "The service may still have file handles open." - Write-Warning "Please wait a few seconds and manually delete: $InstallPath" - Write-Info "Or reboot and run the uninstall script again." + PulseError "Could not remove installation directory after multiple attempts: $_" + PulseWarn "The service may still have file handles open." + PulseWarn "Please wait a few seconds and manually delete: $InstallPath" + PulseInfo "Or reboot and run the uninstall script again." } } } } else { - Write-Info "Installation directory not found: $InstallPath" + PulseInfo "Installation directory not found: $InstallPath" } Write-Host "" -Write-Color $Green "═══════════════════════════════════════════════════════════" -Write-Success "Uninstallation complete!" -Write-Color $Green "═══════════════════════════════════════════════════════════" +$successBanner = "=" * 59 +Write-Host $successBanner -ForegroundColor Green +PulseSuccess "Uninstallation complete!" +Write-Host $successBanner -ForegroundColor Green Write-Host "" -Write-Info "The Pulse Host Agent has been removed from this system." -Write-Info "This host will no longer appear in your Pulse dashboard." +PulseInfo "The Pulse Host Agent has been removed from this system." +PulseInfo "This host will no longer appear in your Pulse dashboard." Write-Host "" diff --git a/scripts/install-host-agent.ps1 b/scripts/install-host-agent.ps1 index 949b364..8e97988 100644 --- a/scripts/install-host-agent.ps1 +++ b/scripts/install-host-agent.ps1 @@ -17,22 +17,24 @@ param( $ErrorActionPreference = "Stop" -# ANSI color codes for output -$Red = "`e[31m" -$Green = "`e[32m" -$Yellow = "`e[33m" -$Blue = "`e[34m" -$Reset = "`e[0m" +function Write-PulseMessage { + param( + [string]$Label, + [string]$Message, + [ConsoleColor]$Color + ) -function Write-Color { - param([string]$Color, [string]$Message) - Write-Host "${Color}${Message}${Reset}" + if ($Label) { + Write-Host ("[{0}] {1}" -f $Label, $Message) -ForegroundColor $Color + } else { + Write-Host $Message -ForegroundColor $Color + } } -function Write-Success { param([string]$msg) Write-Color $Green "✓ $msg" } -function Write-Error { param([string]$msg) Write-Color $Red "✗ $msg" } -function Write-Info { param([string]$msg) Write-Color $Blue "ℹ $msg" } -function Write-Warning { param([string]$msg) Write-Color $Yellow "⚠ $msg" } +function PulseSuccess { param([string]$msg) Write-PulseMessage -Label 'OK' -Message $msg -Color 'Green' } +function PulseError { param([string]$msg) Write-PulseMessage -Label 'FAIL' -Message $msg -Color 'Red' } +function PulseInfo { param([string]$msg) Write-PulseMessage -Label 'INFO' -Message $msg -Color 'Cyan' } +function PulseWarn { param([string]$msg) Write-PulseMessage -Label 'WARN' -Message $msg -Color 'Yellow' } function Write-InstallerEvent { param( @@ -47,7 +49,7 @@ function Write-InstallerEvent { try { Write-EventLog -LogName Application -Source $SourceName -EventId $EventId -EntryType $EntryType -Message $Message } catch { - Write-Warning "Unable to write installer event log entry: $_" + PulseWarn "Unable to write installer event log entry: $_" } } @@ -92,16 +94,17 @@ function Test-AgentRegistration { } Write-Host "" -Write-Color $Blue "═══════════════════════════════════════════════════════════" -Write-Color $Blue " Pulse Host Agent - Windows Installation" -Write-Color $Blue "═══════════════════════════════════════════════════════════" +$banner = "=" * 59 +Write-Host $banner -ForegroundColor Cyan +Write-Host " Pulse Host Agent - Windows Installation" -ForegroundColor Cyan +Write-Host $banner -ForegroundColor Cyan Write-Host "" # Check if running as Administrator $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" - Write-Info "Right-click PowerShell and select 'Run as Administrator'" + PulseError "This script must be run as Administrator" + PulseInfo "Right-click PowerShell and select 'Run as Administrator'" exit 1 } @@ -112,7 +115,7 @@ if (-not $PulseUrl) { $PulseUrl = $PulseUrl.TrimEnd('/') if (-not $Token) { - Write-Warning "No API token provided - agent will attempt to connect without authentication" + PulseWarn "No API token provided - agent will attempt to connect without authentication" $response = Read-Host "Continue without token? (y/N)" if ($response -ne 'y' -and $response -ne 'Y') { $Token = Read-Host "Enter API token" @@ -123,7 +126,7 @@ if (-not $Interval) { $Interval = "30s" } -Write-Info "Configuration:" +PulseInfo "Configuration:" Write-Host " Pulse URL: $PulseUrl" Write-Host " Token: $(if ($Token) { '***' + $Token.Substring([Math]::Max(0, $Token.Length - 4)) } else { 'none' })" Write-Host " Interval: $Interval" @@ -137,19 +140,19 @@ switch ($osArch) { 'X64' { $arch = 'amd64' } 'X86' { $arch = '386' } default { - Write-Error "Unsupported architecture: $osArch" + PulseError "Unsupported architecture: $osArch" exit 1 } } $downloadUrl = "$PulseUrl/download/pulse-host-agent?platform=windows&arch=$arch" -Write-Info "System Information:" +PulseInfo "System Information:" Write-Host " OS Architecture: $osArch" Write-Host " Download Architecture: $arch" Write-Host " Download URL: $downloadUrl" Write-Host "" -Write-Info "Downloading agent binary from $downloadUrl..." +PulseInfo "Downloading agent binary from $downloadUrl..." try { # Create install directory if (-not (Test-Path $InstallPath)) { @@ -160,12 +163,12 @@ try { # Download binary Invoke-WebRequest -Uri $downloadUrl -OutFile $agentPath -UseBasicParsing - Write-Success "Downloaded agent to $agentPath" + PulseSuccess "Downloaded agent to $agentPath" # Validate PE header $fileBytes = [System.IO.File]::ReadAllBytes($agentPath) $fileSizeMB = [math]::Round($fileBytes.Length / 1MB, 2) - Write-Info "File size: $fileSizeMB MB ($($fileBytes.Length) bytes)" + PulseInfo "File size: $fileSizeMB MB ($($fileBytes.Length) bytes)" if ($fileBytes.Length -lt 64) { throw "Downloaded file is too small ($($fileBytes.Length) bytes) - expected Windows PE executable" @@ -203,10 +206,10 @@ try { throw "Downloaded binary is for wrong architecture (got $machineStr, expected $expectedStr for $arch)" } - Write-Success "Verified PE executable for $osArch architecture" + PulseSuccess "Verified PE executable for $osArch architecture" # Verify checksum - Write-Info "Verifying checksum..." + PulseInfo "Verifying checksum..." $checksumUrl = "$PulseUrl/download/pulse-host-agent.sha256?platform=windows&arch=$arch" try { $expectedChecksum = (Invoke-WebRequest -Uri $checksumUrl -UseBasicParsing).Content.Trim().Split()[0] @@ -215,10 +218,10 @@ try { if ($actualChecksum -ne $expectedChecksum.ToLower()) { throw "Checksum mismatch! Expected: $expectedChecksum, Got: $actualChecksum" } - Write-Success "Checksum verified: $actualChecksum" + PulseSuccess "Checksum verified: $actualChecksum" } catch { - Write-Warning "Could not verify checksum: $_" - Write-Info "Continuing anyway (PE header was validated)" + PulseWarn "Could not verify checksum: $_" + PulseInfo "Continuing anyway (PE header was validated)" } $agentArgs = @("--url", "`"$PulseUrl`"", "--interval", $Interval) @@ -228,7 +231,7 @@ try { $serviceBinaryPath = "`"$agentPath`" $($agentArgs -join ' ')" $manualCommand = "& `"$agentPath`" $($agentArgs -join ' ')" } catch { - Write-Error "Failed to download agent: $_" + PulseError "Failed to download agent: $_" exit 1 } @@ -243,23 +246,23 @@ if ($Token) { } $config | ConvertTo-Json | Set-Content $configPath -Write-Success "Created configuration at $configPath" +PulseSuccess "Created configuration at $configPath" # Stop existing service if running $serviceName = "PulseHostAgent" $existingService = Get-Service -Name $serviceName -ErrorAction SilentlyContinue if ($existingService) { - Write-Info "Stopping existing service..." + PulseInfo "Stopping existing service..." Stop-Service -Name $serviceName -Force - Write-Success "Stopped existing service" + PulseSuccess "Stopped existing service" } if (-not $NoService) { - Write-Info "Installing native Windows service with built-in service support..." + PulseInfo "Installing native Windows service with built-in service support..." try { if ($existingService) { - Write-Info "Removing existing service..." + PulseInfo "Removing existing service..." sc.exe delete $serviceName | Out-Null Start-Sleep -Seconds 2 } @@ -271,73 +274,73 @@ if (-not $NoService) { -Description "Monitors system metrics and reports to Pulse monitoring server" ` -StartupType Automatic | Out-Null - Write-Success "Created Windows service '$serviceName'" + PulseSuccess "Created Windows service '$serviceName'" # Register Windows Event Log source try { if (-not ([System.Diagnostics.EventLog]::SourceExists($serviceName))) { New-EventLog -LogName Application -Source $serviceName - Write-Success "Registered Event Log source" + PulseSuccess "Registered Event Log source" } } catch { - Write-Warning "Could not register Event Log source (not critical): $_" + PulseWarn "Could not register Event Log source (not critical): $_" } Write-InstallerEvent -SourceName $serviceName -Message "Pulse Host Agent installer registered service version $(Get-Item $agentPath).VersionInfo.FileVersion" -EventId 1000 # Configure service recovery options (restart on failure) sc.exe failure $serviceName reset= 86400 actions= restart/60000/restart/60000/restart/60000 | Out-Null - Write-Success "Configured automatic restart on failure" + PulseSuccess "Configured automatic restart on failure" # Start the service - Write-Info "Starting service..." + PulseInfo "Starting service..." Start-Service -Name $serviceName Start-Sleep -Seconds 3 $status = (Get-Service -Name $serviceName).Status if ($status -eq 'Running') { - Write-Success "Service started successfully!" + PulseSuccess "Service started successfully!" - Write-Info "Waiting 10 seconds to validate agent reporting..." + PulseInfo "Waiting 10 seconds to validate agent reporting..." Start-Sleep -Seconds 10 $hostname = $env:COMPUTERNAME $lookupHost = Test-AgentRegistration -PulseUrl $PulseUrl -Hostname $hostname -Token $Token if ($lookupHost) { - Write-Success "Agent successfully registered with Pulse (host '$hostname')." + PulseSuccess "Agent successfully registered with Pulse (host '$hostname')." if ($lookupHost.status) { $lastSeen = $lookupHost.lastSeen if ($lastSeen -is [DateTime]) { $lastSeen = $lastSeen.ToString("u") } - Write-Info ("Pulse reports status: {0} (last seen {1})" -f $lookupHost.status, $lastSeen) + PulseInfo ("Pulse reports status: {0} (last seen {1})" -f $lookupHost.status, $lastSeen) } - Write-Info "Check your Pulse dashboard - this host should appear shortly." + PulseInfo "Check your Pulse dashboard - this host should appear shortly." $statusForLog = if ($lookupHost.status) { $lookupHost.status } else { 'unknown' } Write-InstallerEvent -SourceName $serviceName -Message "Installer verified host '$hostname' reporting to Pulse (status: $statusForLog)." -EventId 1010 } elseif ($Token) { - Write-Warning "Agent is running but the lookup endpoint has not confirmed registration yet." - Write-Info "It may take another moment for metrics to appear in the dashboard." + PulseWarn "Agent is running but the lookup endpoint has not confirmed registration yet." + PulseInfo "It may take another moment for metrics to appear in the dashboard." Write-InstallerEvent -SourceName $serviceName -Message "Installer could not yet confirm host '$hostname' registration with Pulse." -EntryType Warning -EventId 1011 } else { - Write-Info "Registration check skipped (no API token available)." + PulseInfo "Registration check skipped (no API token available)." Write-InstallerEvent -SourceName $serviceName -Message "Installer skipped registration lookup (no API token provided)." -EventId 1012 } $recentLogs = Get-RecentAgentEvents -ProviderName $serviceName -Max 5 if ($recentLogs) { - Write-Info "Recent service events:" + PulseInfo "Recent service events:" $recentLogs | Select-Object -First 3 | ForEach-Object { $time = $_.TimeCreated if (-not $time) { $time = $_.TimeGenerated } Write-Host (" [{0}] {1}" -f $time.ToString("u"), $_.Message) } } else { - Write-Warning "No recent Application log entries were found for $serviceName." + PulseWarn "No recent Application log entries were found for $serviceName." } } else { - Write-Warning "Service status: $status" - Write-Info "Checking service logs..." + PulseWarn "Service status: $status" + PulseInfo "Checking service logs..." $recentLogs = Get-RecentAgentEvents -ProviderName $serviceName -Max 5 if ($recentLogs) { $recentLogs | ForEach-Object { @@ -346,32 +349,33 @@ if (-not $NoService) { Write-Host (" [{0}] {1}" -f $time.ToString("u"), $_.Message) } } else { - Write-Warning "No Application log entries were found for $serviceName." + PulseWarn "No Application log entries were found for $serviceName." } } } catch { - Write-Error "Failed to create/start service: $_" - Write-Info "You can start the agent manually with:" + PulseError "Failed to create/start service: $_" + PulseInfo "You can start the agent manually with:" Write-Host " $manualCommand" Write-Host "" - Write-Info "Or check Windows Event Viewer (Application log) for error details." + PulseInfo "Or check Windows Event Viewer (Application log) for error details." exit 1 } } else { - Write-Info "Skipping service installation (--NoService flag)" + PulseInfo "Skipping service installation (--NoService flag)" Write-Host "" - Write-Info "To start the agent manually:" + PulseInfo "To start the agent manually:" Write-Host " $manualCommand" } Write-Host "" -Write-Color $Green "═══════════════════════════════════════════════════════════" -Write-Success "Installation complete!" -Write-Color $Green "═══════════════════════════════════════════════════════════" +$successBanner = "=" * 59 +Write-Host $successBanner -ForegroundColor Green +PulseSuccess "Installation complete!" +Write-Host $successBanner -ForegroundColor Green Write-Host "" -Write-Info "Service Management Commands:" +PulseInfo "Service Management Commands:" Write-Host " Start: Start-Service -Name PulseHostAgent" Write-Host " Stop: Stop-Service -Name PulseHostAgent" Write-Host " Restart: Restart-Service -Name PulseHostAgent" @@ -380,10 +384,10 @@ Write-Host " Remove: sc.exe delete PulseHostAgent" Write-Host " Logs: Get-WinEvent -FilterHashtable @{LogName='Application'; ProviderName='PulseHostAgent'} -MaxEvents 50" Write-Host "" -Write-Info "Files installed:" +PulseInfo "Files installed:" Write-Host " Binary: $agentPath" Write-Host " Config: $configPath" Write-Host "" -Write-Info "The agent is now reporting to: $PulseUrl" +PulseInfo "The agent is now reporting to: $PulseUrl" Write-Host "" diff --git a/scripts/uninstall-host-agent.ps1 b/scripts/uninstall-host-agent.ps1 index d1caa89..caecf29 100644 --- a/scripts/uninstall-host-agent.ps1 +++ b/scripts/uninstall-host-agent.ps1 @@ -10,92 +10,95 @@ param( $ErrorActionPreference = "Stop" -# ANSI color codes for output -$Red = "`e[31m" -$Green = "`e[32m" -$Yellow = "`e[33m" -$Blue = "`e[34m" -$Reset = "`e[0m" +function Write-PulseMessage { + param( + [string]$Label, + [string]$Message, + [ConsoleColor]$Color + ) -function Write-Color { - param([string]$Color, [string]$Message) - Write-Host "${Color}${Message}${Reset}" + if ($Label) { + Write-Host ("[{0}] {1}" -f $Label, $Message) -ForegroundColor $Color + } else { + Write-Host $Message -ForegroundColor $Color + } } -function Write-Success { param([string]$msg) Write-Color $Green "✓ $msg" } -function Write-Error { param([string]$msg) Write-Color $Red "✗ $msg" } -function Write-Info { param([string]$msg) Write-Color $Blue "ℹ $msg" } -function Write-Warning { param([string]$msg) Write-Color $Yellow "⚠ $msg" } +function PulseSuccess { param([string]$msg) Write-PulseMessage -Label 'OK' -Message $msg -Color 'Green' } +function PulseError { param([string]$msg) Write-PulseMessage -Label 'FAIL' -Message $msg -Color 'Red' } +function PulseInfo { param([string]$msg) Write-PulseMessage -Label 'INFO' -Message $msg -Color 'Cyan' } +function PulseWarn { param([string]$msg) Write-PulseMessage -Label 'WARN' -Message $msg -Color 'Yellow' } Write-Host "" -Write-Color $Blue "═══════════════════════════════════════════════════════════" -Write-Color $Blue " Pulse Host Agent - Windows Uninstallation" -Write-Color $Blue "═══════════════════════════════════════════════════════════" +$banner = "=" * 59 +Write-Host $banner -ForegroundColor Cyan +Write-Host " Pulse Host Agent - Windows Uninstallation" -ForegroundColor Cyan +Write-Host $banner -ForegroundColor Cyan Write-Host "" # Check if running as Administrator $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" - Write-Info "Right-click PowerShell and select 'Run as Administrator'" + PulseError "This script must be run as Administrator" + PulseInfo "Right-click PowerShell and select 'Run as Administrator'" exit 1 } $serviceName = "PulseHostAgent" # Stop and remove service -Write-Info "Checking for Pulse Host Agent service..." +PulseInfo "Checking for Pulse Host Agent service..." $service = Get-Service -Name $serviceName -ErrorAction SilentlyContinue if ($service) { if ($service.Status -eq 'Running') { - Write-Info "Stopping service..." + PulseInfo "Stopping service..." try { Stop-Service -Name $serviceName -Force - Write-Success "Service stopped" + PulseSuccess "Service stopped" } catch { - Write-Warning "Could not stop service: $_" + PulseWarn "Could not stop service: $_" } } - Write-Info "Removing service..." + PulseInfo "Removing service..." try { sc.exe delete $serviceName | Out-Null - Write-Success "Service removed" + PulseSuccess "Service removed" } catch { - Write-Warning "Could not remove service: $_" + PulseWarn "Could not remove service: $_" } } else { - Write-Info "Service not found (already removed or never installed)" + PulseInfo "Service not found (already removed or never installed)" } # Ensure all processes are terminated -Write-Info "Ensuring all processes are terminated..." +PulseInfo "Ensuring all processes are terminated..." $processes = Get-Process -Name "pulse-host-agent" -ErrorAction SilentlyContinue if ($processes) { $processes | Stop-Process -Force Start-Sleep -Seconds 2 - Write-Success "Processes terminated" + PulseSuccess "Processes terminated" } else { - Write-Info "No running processes found" + PulseInfo "No running processes found" } # Remove Event Log source -Write-Info "Removing Event Log source..." +PulseInfo "Removing Event Log source..." try { if ([System.Diagnostics.EventLog]::SourceExists($serviceName)) { Remove-EventLog -Source $serviceName - Write-Success "Event Log source removed" + PulseSuccess "Event Log source removed" } else { - Write-Info "Event Log source not found" + PulseInfo "Event Log source not found" } } catch { - Write-Warning "Could not remove Event Log source: $_" + PulseWarn "Could not remove Event Log source: $_" } # Remove installation directory with retry logic (Windows file locking) if (Test-Path $InstallPath) { - Write-Info "Removing installation directory..." + PulseInfo "Removing installation directory..." $retries = 3 $success = $false @@ -106,30 +109,31 @@ if (Test-Path $InstallPath) { Start-Sleep -Seconds 2 Remove-Item -Path $InstallPath -Recurse -Force -ErrorAction Stop - Write-Success "Installation directory removed: $InstallPath" + PulseSuccess "Installation directory removed: $InstallPath" $success = $true } catch { $retries-- if ($retries -gt 0) { - Write-Warning "File still locked, retrying... ($retries attempts remaining)" + PulseWarn "File still locked, retrying... ($retries attempts remaining)" } else { - Write-Error "Could not remove installation directory after multiple attempts: $_" - Write-Warning "The service may still have file handles open." - Write-Warning "Please wait a few seconds and manually delete: $InstallPath" - Write-Info "Or reboot and run the uninstall script again." + PulseError "Could not remove installation directory after multiple attempts: $_" + PulseWarn "The service may still have file handles open." + PulseWarn "Please wait a few seconds and manually delete: $InstallPath" + PulseInfo "Or reboot and run the uninstall script again." } } } } else { - Write-Info "Installation directory not found: $InstallPath" + PulseInfo "Installation directory not found: $InstallPath" } Write-Host "" -Write-Color $Green "═══════════════════════════════════════════════════════════" -Write-Success "Uninstallation complete!" -Write-Color $Green "═══════════════════════════════════════════════════════════" +$successBanner = "=" * 59 +Write-Host $successBanner -ForegroundColor Green +PulseSuccess "Uninstallation complete!" +Write-Host $successBanner -ForegroundColor Green Write-Host "" -Write-Info "The Pulse Host Agent has been removed from this system." -Write-Info "This host will no longer appear in your Pulse dashboard." +PulseInfo "The Pulse Host Agent has been removed from this system." +PulseInfo "This host will no longer appear in your Pulse dashboard." Write-Host ""