# BACKUP_NOT Server Agent - Instalační skript # Verze: 1.6.5 param( [Parameter(Mandatory=$true)] [string]$ConsoleUrl, [Parameter(Mandatory=$true)] [string]$ApiKey, [string]$BackupPath = "D:\Backups", [int]$HeartbeatInterval = 300, [int]$Port = 8443 ) # Samocinne povyseni na spravce - pokud skript nebezi jako administrator, spusti se znovu se zvysenymi opravnenimi $isAdmin = ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator) if (-not $isAdmin) { Write-Host "Skript vyzaduje opravneni spravce - spoustim znovu jako administrator..." -ForegroundColor Yellow $relaunchArgs = "-ExecutionPolicy Bypass -NoExit -File `"$PSCommandPath`" -ConsoleUrl `"$ConsoleUrl`" -ApiKey `"$ApiKey`" -BackupPath `"$BackupPath`" -HeartbeatInterval $HeartbeatInterval -Port $Port" Start-Process powershell.exe -Verb RunAs -ArgumentList $relaunchArgs exit } $ErrorActionPreference = "Stop" $ServiceName = "BACKUP_NOT_ServerAgent" $InstallPath = "C:\Program Files\BACKUP_NOT\ServerAgent" Write-Host "============================================" -ForegroundColor Cyan Write-Host " BACKUP_NOT Server Agent - Instalace" -ForegroundColor Cyan Write-Host "============================================" -ForegroundColor Cyan Write-Host "" # Kontrola .NET Write-Host "[1/6] Kontrola prerekvizit..." -ForegroundColor Yellow $dotnetVersion = (Get-ItemProperty "HKLM:\SOFTWARE\Microsoft\NET Framework Setup\NDP\v4\Full").Release if ($dotnetVersion -lt 461808) { Write-Host "CHYBA: Je vyzadovan .NET Framework 4.7.2 nebo novejsi" -ForegroundColor Red exit 1 } # Vytvoření adresářů Write-Host "[2/6] Vytvareni adresaru..." -ForegroundColor Yellow New-Item -ItemType Directory -Force -Path $InstallPath | Out-Null # Pokud disk z $BackupPath na tomto serveru neexistuje (napr. vychozi 'D:' na stroji # jen s diskem C:), pouzijeme systemovy disk. Opravena cesta se ulozi i do configu. try { $backupQualifier = Split-Path -Qualifier $BackupPath -ErrorAction SilentlyContinue } catch { $backupQualifier = $null } if ($backupQualifier) { $driveLetter = $backupQualifier.TrimEnd(':') if (-not (Test-Path "$backupQualifier\")) { $sysDrive = $env:SystemDrive # napr. "C:" $fallback = Join-Path "$sysDrive\" "Backups" Write-Host " Disk '$backupQualifier' neexistuje - pouzivam '$fallback'" -ForegroundColor Yellow $BackupPath = $fallback } } New-Item -ItemType Directory -Force -Path $BackupPath | Out-Null New-Item -ItemType Directory -Force -Path "$InstallPath\logs" | Out-Null # Prideleni prava zapisu skupine Users do logs (stavova ikona bezi bez elevace a musi do logu zapisovat) try { icacls "$InstallPath\logs" /grant "*S-1-5-32-545:(OI)(CI)M" /T | Out-Null } catch { } # Diagnosticky install log (zapisuje elevovany instalator, vzdy k dispozici) $InstallLog = "$InstallPath\logs\install.log" function Write-Install { param([string]$Message) try { Add-Content -Path $InstallLog -Value ("[" + (Get-Date -Format "yyyy-MM-dd HH:mm:ss") + "] " + $Message) -ErrorAction SilentlyContinue } catch { } } Write-Install "=== Instalace spustena (verze 1.6.5) ===" # Zjisteni interaktivniho uzivatele podle vlastnika explorer.exe (funguje i pres RDP). # Win32_ComputerSystem.UserName vraci pro RDP relace null, proto ho nepouzivame jako prvni. function Get-InteractiveUser { $u = $null try { $exp = Get-CimInstance Win32_Process -Filter "Name='explorer.exe'" -ErrorAction SilentlyContinue | Select-Object -First 1 if ($exp) { $owner = Invoke-CimMethod -InputObject $exp -MethodName GetOwner -ErrorAction SilentlyContinue if ($owner.User) { if ($owner.Domain) { $u = "$($owner.Domain)\$($owner.User)" } else { $u = $owner.User } } } } catch { } if (-not $u) { try { $u = (Get-CimInstance Win32_ComputerSystem).UserName } catch { } } return $u } # Konfigurace Write-Host "[3/6] Ukladani konfigurace..." -ForegroundColor Yellow @{ ConsoleUrl = $ConsoleUrl ApiKey = $ApiKey BackupPath = $BackupPath HeartbeatInterval = $HeartbeatInterval Port = $Port Version = "1.6.5" } | ConvertTo-Json | Out-File "$InstallPath\config.json" -Encoding UTF8 # Firewall Write-Host "[4/6] Konfigurace firewallu..." -ForegroundColor Yellow $ruleName = "BACKUP_NOT Server Agent" $existingRule = Get-NetFirewallRule -DisplayName $ruleName -ErrorAction SilentlyContinue if (-not $existingRule) { New-NetFirewallRule -DisplayName $ruleName -Direction Inbound -Protocol TCP -LocalPort $Port -Action Allow | Out-Null } # Registrace služby Write-Host "[5/6] Registrace sluzby..." -ForegroundColor Yellow $service = Get-Service -Name $ServiceName -ErrorAction SilentlyContinue if ($service) { Stop-Service -Name $ServiceName -Force sc.exe delete $ServiceName | Out-Null Start-Sleep -Seconds 2 } # Ukonceni bezici ulohy a jejiho procesu (aby stary agent neblokoval port 8443) $existingAgentTask = Get-ScheduledTask -TaskName $ServiceName -ErrorAction SilentlyContinue if ($existingAgentTask) { Write-Host " Zastavuji bezici agent..." -ForegroundColor Yellow Stop-ScheduledTask -TaskName $ServiceName -ErrorAction SilentlyContinue Unregister-ScheduledTask -TaskName $ServiceName -Confirm:$false -ErrorAction SilentlyContinue } # Ukoncit stary bezici agent (podle plne cesty k nainstalovanemu skriptu), # nikdy ne tento instalacni proces ($PID). $installedAgentScript = "$InstallPath\server-agent.ps1" Get-CimInstance Win32_Process -Filter "Name = 'powershell.exe'" -ErrorAction SilentlyContinue | Where-Object { $_.ProcessId -ne $PID -and $_.CommandLine -like "*$installedAgentScript*" } | ForEach-Object { Write-Host " Ukoncuji stary proces agenta (PID $($_.ProcessId))..." -ForegroundColor Yellow Stop-Process -Id $_.ProcessId -Force -ErrorAction SilentlyContinue } Start-Sleep -Seconds 2 # Kopírování hlavního skriptu Copy-Item -Path "$PSScriptRoot\server-agent.ps1" -Destination "$InstallPath\server-agent.ps1" -Force # Vytvoření wrapper exe pomocí NSSM nebo PowerShell scheduled task $action = New-ScheduledTaskAction -Execute "powershell.exe" -Argument "-ExecutionPolicy Bypass -WindowStyle Hidden -File `"$InstallPath\server-agent.ps1`"" # Spousti se pri startu systemu + kazdou hodinu (samo-oziveni, pokud agent nebezi) $triggerStartup = New-ScheduledTaskTrigger -AtStartup $triggerHourly = New-ScheduledTaskTrigger -Once -At (Get-Date) -RepetitionInterval (New-TimeSpan -Hours 1) $principal = New-ScheduledTaskPrincipal -UserId "SYSTEM" -LogonType ServiceAccount -RunLevel Highest $settings = New-ScheduledTaskSettingsSet -AllowStartIfOnBatteries -DontStopIfGoingOnBatteries -StartWhenAvailable -RestartCount 3 -RestartInterval (New-TimeSpan -Minutes 1) Register-ScheduledTask -TaskName $ServiceName -Action $action -Trigger $triggerStartup, $triggerHourly -Principal $principal -Settings $settings -Force | Out-Null # Spuštění Start-ScheduledTask -TaskName $ServiceName # Stavova ikona v oznamovaci oblasti (system tray) - bezi v relaci prihlaseneho uzivatele Write-Host "Nastaveni stavove ikony (system tray)..." -ForegroundColor Yellow try { $trayScriptPath = "$InstallPath\tray-monitor.ps1" Invoke-WebRequest -Uri "$ConsoleUrl/api/downloads/server-tray-monitor" -OutFile $trayScriptPath -UseBasicParsing -TimeoutSec 30 # Stazeni ikony aplikace (cerny stit) pro tray i seznam aplikaci try { Invoke-WebRequest -Uri "$ConsoleUrl/agent-icon.ico" -OutFile "$InstallPath\agent-icon.ico" -UseBasicParsing -TimeoutSec 30 } catch { } $trayTaskName = "BACKUP_NOT_ServerTray" $existingTray = Get-ScheduledTask -TaskName $trayTaskName -ErrorAction SilentlyContinue if ($existingTray) { Unregister-ScheduledTask -TaskName $trayTaskName -Confirm:$false } # Ukoncit uz bezici starou ikonu (jinak by po preinstalaci zustala viset druha). # Zrušeni ulohy nezabije bezici proces - musime ho ukoncit rucne. # Filtr je vazany na TUTO instalacni cestu, aby preinstalace serveru nezabila # ikonu stanice (obe pouzivaji stejny nazev souboru tray-monitor.ps1). Get-CimInstance Win32_Process -Filter "Name = 'powershell.exe' OR Name = 'wscript.exe'" -ErrorAction SilentlyContinue | Where-Object { $_.CommandLine -like "*$InstallPath\tray-monitor.ps1*" -or $_.CommandLine -like "*$InstallPath\tray-launcher.vbs*" } | ForEach-Object { Write-Install "Tray: ukoncuji starou ikonu (PID $($_.ProcessId))" Stop-Process -Id $_.ProcessId -Force -ErrorAction SilentlyContinue } Start-Sleep -Seconds 1 # VBScript launcher spusti PowerShell zcela skryte (okno se vubec nevytvori). # Nutne kvuli Windows Terminalu ve Win11, ktery -WindowStyle Hidden ignoruje. $vbsPath = "$InstallPath\tray-launcher.vbs" $vbsContent = @" Set s = CreateObject("WScript.Shell") s.Run "powershell.exe -ExecutionPolicy Bypass -WindowStyle Hidden -NonInteractive -File ""$trayScriptPath""", 0, False "@ Set-Content -Path $vbsPath -Value $vbsContent -Encoding ASCII $trayAction = New-ScheduledTaskAction -Execute "wscript.exe" -Argument "`"$vbsPath`"" # Ikona musi bezet v interaktivni relaci prihlaseneho uzivatele (ne skupina/SYSTEM). $loggedUser = Get-InteractiveUser Write-Install "Tray: zjisteny interaktivni uzivatel = '$loggedUser'" if ($loggedUser) { $trayTrigger = New-ScheduledTaskTrigger -AtLogOn -User $loggedUser $trayPrincipal = New-ScheduledTaskPrincipal -UserId $loggedUser -LogonType Interactive -RunLevel Limited } else { $trayTrigger = New-ScheduledTaskTrigger -AtLogOn $trayPrincipal = New-ScheduledTaskPrincipal -GroupId "S-1-5-32-545" -RunLevel Limited Write-Install "Tray: VAROVANI - interaktivni uzivatel nezjisten, pouzivam skupinu Users" } $traySettings = New-ScheduledTaskSettingsSet -AllowStartIfOnBatteries -DontStopIfGoingOnBatteries try { Register-ScheduledTask -TaskName $trayTaskName -Action $trayAction -Trigger $trayTrigger -Principal $trayPrincipal -Settings $traySettings -Force | Out-Null Write-Install "Tray: uloha zaregistrovana" } catch { Write-Install "Tray: CHYBA registrace ulohy: $($_.Exception.Message)"; throw } # Spustit ikonu hned v relaci prihlaseneho uzivatele Start-ScheduledTask -TaskName $trayTaskName -ErrorAction SilentlyContinue Start-Sleep -Seconds 2 try { $tState = (Get-ScheduledTask -TaskName $trayTaskName).State $tResult = (Get-ScheduledTaskInfo -TaskName $trayTaskName).LastTaskResult Write-Install "Tray: po spusteni State=$tState LastTaskResult=$tResult" } catch { Write-Install "Tray: nelze zjistit stav ulohy: $($_.Exception.Message)" } Write-Host " OK - stavova ikona nainstalovana (uzivatel: $loggedUser)" -ForegroundColor Green Write-Host " Tip: ve Windows 11 muze byt ikona skryta pod sipkou '^' vedle hodin." -ForegroundColor Gray } catch { Write-Install "Tray: CHYBA: $($_.Exception.Message)" Write-Host " Upozorneni: stavovou ikonu se nepodarilo nastavit: $($_.Exception.Message)" -ForegroundColor Yellow } # Registrace jako nainstalovana aplikace (Pridat/odebrat programy) Write-Host "Registrace aplikace do systemu..." -ForegroundColor Yellow try { $uninstallScriptPath = "$InstallPath\uninstall.ps1" Invoke-WebRequest -Uri "$ConsoleUrl/api/downloads/server-agent-uninstall" -OutFile $uninstallScriptPath -UseBasicParsing -TimeoutSec 30 $arpKey = "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\BACKUP_NOT_ServerAgent" New-Item -Path $arpKey -Force | Out-Null Set-ItemProperty -Path $arpKey -Name "DisplayName" -Value "NotBackup Server Agent" Set-ItemProperty -Path $arpKey -Name "DisplayVersion" -Value "1.6.5" Set-ItemProperty -Path $arpKey -Name "Publisher" -Value "NotBackup" Set-ItemProperty -Path $arpKey -Name "InstallLocation" -Value $InstallPath Set-ItemProperty -Path $arpKey -Name "UninstallString" -Value "powershell.exe -ExecutionPolicy Bypass -File `"$uninstallScriptPath`"" $iconFile = "$InstallPath\agent-icon.ico" if (Test-Path $iconFile) { Set-ItemProperty -Path $arpKey -Name "DisplayIcon" -Value $iconFile } else { Set-ItemProperty -Path $arpKey -Name "DisplayIcon" -Value "powershell.exe" } Set-ItemProperty -Path $arpKey -Name "NoModify" -Value 1 -Type DWord Set-ItemProperty -Path $arpKey -Name "NoRepair" -Value 1 -Type DWord Write-Host " OK - aplikace zaregistrovana (viditelna v Pridat/odebrat programy)" -ForegroundColor Green } catch { Write-Host " Upozorneni: registraci aplikace se nepodarilo dokoncit: $($_.Exception.Message)" -ForegroundColor Yellow } # Test pripojeni ke konzoli - posle skutecny heartbeat hned pri instalaci Write-Host "" Write-Host "[6/6] Test pripojeni ke konzoli..." -ForegroundColor Yellow try { $os = Get-CimInstance Win32_OperatingSystem $disk = Get-CimInstance Win32_LogicalDisk -Filter "DeviceID='C:'" # CPU pres WMI perf tridu (nezavisle na jazyku Windows) $cpuPercent = 0 try { $cpuData = Get-CimInstance Win32_PerfFormattedData_PerfOS_Processor -Filter "Name='_Total'" -ErrorAction Stop $cpuPercent = [math]::Round([double]$cpuData.PercentProcessorTime, 2) } catch { try { $cpuPercent = [math]::Round((Get-CimInstance Win32_Processor | Measure-Object -Property LoadPercentage -Average).Average, 2) } catch { $cpuPercent = 0 } } $ramTotal = [math]::Round($os.TotalVisibleMemorySize / 1MB, 2) $ramFree = [math]::Round($os.FreePhysicalMemory / 1MB, 2) $ramUsed = [math]::Round($ramTotal - $ramFree, 2) $ramPercent = if ($ramTotal -gt 0) { [math]::Round(($ramUsed / $ramTotal) * 100, 2) } else { 0 } $uptime = (Get-Date) - $os.LastBootUpTime $testBody = @{ hostname = $env:COMPUTERNAME ipAddress = (Get-NetIPAddress -AddressFamily IPv4 | Where-Object { $_.InterfaceAlias -notlike "*Loopback*" } | Select-Object -First 1).IPAddress osVersion = $os.Caption osBuild = $os.BuildNumber totalDiskSpaceGb = [math]::Round($disk.Size / 1GB, 2) usedDiskSpaceGb = [math]::Round(($disk.Size - $disk.FreeSpace) / 1GB, 2) updatesPending = 0 agentVersion = "1.6.5" cpuPercent = $cpuPercent ramTotalGb = $ramTotal ramUsedGb = $ramUsed ramPercent = $ramPercent uptimeHours = [math]::Floor($uptime.TotalHours) } | ConvertTo-Json -Depth 10 $testResp = Invoke-RestMethod -Uri "$ConsoleUrl/api/agent/heartbeat" -Method POST -Body $testBody -ContentType "application/json" -Headers @{ "x-api-key" = $ApiKey } -TimeoutSec 30 Write-Host " OK - server se prihlasil do konzole (ID: $($testResp.serverId))" -ForegroundColor Green } catch { Write-Host " CHYBA pri pripojeni ke konzoli:" -ForegroundColor Red Write-Host " $($_.Exception.Message)" -ForegroundColor Red if ($_.Exception.Response) { $statusCode = [int]$_.Exception.Response.StatusCode Write-Host " HTTP status: $statusCode (zkontrolujte ApiKey a ConsoleUrl)" -ForegroundColor Red } Write-Host " Agent na pozadi bude pokus opakovat kazdych $HeartbeatInterval s." -ForegroundColor Yellow } Write-Host "" Write-Host "============================================" -ForegroundColor Green Write-Host " Instalace dokoncena uspesne!" -ForegroundColor Green Write-Host "============================================" -ForegroundColor Green Write-Host "" Write-Host "Konfigurace:" Write-Host " Konzole: $ConsoleUrl" Write-Host " Backup: $BackupPath" Write-Host " Port: $Port" Write-Host "" Write-Host "Sluzba $ServiceName byla spustena." Write-Host "Log agenta: $InstallPath\logs\agent_.log"