# BurikaRMM agent installer # Run on the target Windows host (admin PowerShell): # # iex (irm https://dashboard.burika.co.za/agent/install.ps1) # # Installs a Scheduled Task that POSTs system info to your Burika # dashboard every minute. #requires -Version 5.0 $ErrorActionPreference = 'Stop' # Force TLS 1.2. Windows Server 2012 / 2012 R2 (and older Win10 builds) # default to TLS 1.0/1.1, which Let's Encrypt-fronted hosts no longer # accept — Invoke-RestMethod fails with "Could not create SSL/TLS # secure channel". Direct assignment (rather than -bor with the # existing value) avoids a .NET 4.5 quirk on Server 2012 R2 where the # OR'd enum value still negotiates TLS 1.0 first and fails. try { [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 } catch { [Net.ServicePointManager]::SecurityProtocol = 3072 # raw Tls12 bit } $ServerUrl = 'https://dashboard.burika.co.za' $AgentToken = 'bnax5y8SYzDGXF5Igx2gLphR5Ev8c-wfVV2hxw0Ou7U' $InstallDir = "$env:ProgramData\BurikaRMM" $ScriptPath = Join-Path $InstallDir 'BurikaRMM-Agent.ps1' $IdPath = Join-Path $InstallDir 'install_id.txt' $TaskName = 'BurikaRMM Agent' if (-not (Test-Path $InstallDir)) { New-Item -ItemType Directory -Path $InstallDir | Out-Null } # Persist a stable per-install GUID so the dashboard upsert keys match # across reboots and reinstalls. if (-not (Test-Path $IdPath)) { [guid]::NewGuid().ToString() | Set-Content -Path $IdPath -Encoding ascii } $InstallId = (Get-Content -Path $IdPath -Raw).Trim() # ---- Embed the worker script ------------------------------------------ $worker = @' $ErrorActionPreference = 'Stop' # Force TLS 1.2. Old Windows defaults to 1.0/1.1; LE+nginx requires 1.2+. # Direct assignment, not -bor — see installer header for the .NET 4.5 # quirk this works around. try { [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 } catch { [Net.ServicePointManager]::SecurityProtocol = 3072 } $ServerUrl = "__SERVER__" $AgentToken = "__TOKEN__" $IdPath = "__IDPATH__" $AgentVersion = "__VERSION__" $InstallId = (Get-Content -Path $IdPath -Raw).Trim() function Get-AvProduct { try { $a = Get-CimInstance -Namespace root/SecurityCenter2 -ClassName AntiVirusProduct -ErrorAction Stop | Select-Object -First 1 if (-not $a) { return @{ name=$null; status='not_detected' } } # productState is a bitfield; common cases: # high byte 0x10 = on, 0x00 = off # bit 0x10 of low byte = signatures up to date $state = [int]$a.productState $enabled = (($state -shr 12) -band 0x10) -ne 0 $upToDate = ($state -band 0x10) -eq 0 $status = if (-not $enabled) { 'install_error' } elseif (-not $upToDate) { 'out_of_date' } else { 'ok' } return @{ name = $a.displayName; status = $status } } catch { return @{ name = $null; status = 'unknown' } } } function Get-RebootRequired { $paths = @( 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Component Based Servicing\RebootPending', 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate\Auto Update\RebootRequired' ) foreach ($p in $paths) { if (Test-Path $p) { return $true } } try { $r = Get-ItemProperty -Path 'HKLM:\SYSTEM\CurrentControlSet\Control\Session Manager' ` -Name PendingFileRenameOperations -ErrorAction SilentlyContinue if ($r) { return $true } } catch { } return $false } function Get-PatchStatus { # Quick proxy: if reboot pending → pending; else fully_patched. # Full WSUS-style state would need Microsoft.Update.Session COM which # is slow + sometimes not allowed. This is a v1 heuristic. if (Get-RebootRequired) { return 'pending' } return 'fully_patched' } function Get-LastUser { try { $u = (Get-CimInstance Win32_ComputerSystem).UserName if ($u) { return $u } } catch { } try { $sess = (quser 2>$null) | Where-Object { $_ -match '\S+' } | Select-Object -Skip 1 if ($sess) { $first = ($sess | Select-Object -First 1).Trim() return ($first -split '\s+')[0] } } catch { } return $null } function Get-DeviceType { try { $os = Get-CimInstance Win32_OperatingSystem if ($os.ProductType -in 2,3) { return 'server' } # 2 = DC, 3 = server return 'workstation' } catch { return 'unknown' } } $os = Get-CimInstance Win32_OperatingSystem $cs = Get-CimInstance Win32_ComputerSystem $cpu = Get-CimInstance Win32_Processor | Select-Object -First 1 $av = Get-AvProduct $ip = (Get-NetIPAddress -AddressFamily IPv4 -PrefixOrigin Dhcp,Manual ` -ErrorAction SilentlyContinue | Where-Object { $_.IPAddress -notlike '169.254.*' } | Select-Object -First 1).IPAddress $body = @{ install_id = $InstallId hostname = $env:COMPUTERNAME type = (Get-DeviceType) last_user = (Get-LastUser) os = $os.Caption os_version = $os.Version description = $cs.Description av_product = $av.name av_status = $av.status reboot_required = [int](Get-RebootRequired) patch_status = (Get-PatchStatus) memory_total_mb = [int]([math]::Round($cs.TotalPhysicalMemory / 1MB)) memory_usable_mb = [int]([math]::Round($os.FreePhysicalMemory / 1KB)) cpu = $cpu.Name cpu_cores = [int]$cpu.NumberOfLogicalProcessors architecture = $os.OSArchitecture local_ip = $ip agent_version = $AgentVersion } | ConvertTo-Json -Depth 4 -Compress $headers = @{ 'X-Agent-Token' = $AgentToken; 'Content-Type' = 'application/json' } $response = $null try { $response = Invoke-RestMethod -Uri "$ServerUrl/api/agent/checkin" -Method Post ` -Headers $headers -Body $body -TimeoutSec 30 } catch { Write-Warning "BurikaRMM check-in failed: $_" } # ---- Self-update ----------------------------------------------------- # The server's check-in response tells us the latest agent version. If # our baked-in version differs, re-run the installer — that script is # idempotent: it rewrites this worker, refreshes the scheduled task, and # kicks off a fresh check-in. We run it via Invoke-Expression on the # downloaded script content rather than `iex (irm ...)` so the failure # mode (network blip, 404, partial body) is logged here rather than # silently leaving the agent un-updated. if ($response -and $response.latest_version ` -and ($response.latest_version -ne $AgentVersion) ` -and $response.update_url) { try { Write-Output ("BurikaRMM: updating from {0} -> {1}" -f $AgentVersion, $response.latest_version) $installerPs1 = Invoke-RestMethod -Uri $response.update_url -TimeoutSec 60 Invoke-Expression $installerPs1 } catch { Write-Warning "BurikaRMM self-update failed: $_" } } '@ $worker = $worker.Replace('__SERVER__', $ServerUrl) $worker = $worker.Replace('__TOKEN__', $AgentToken) $worker = $worker.Replace('__IDPATH__', $IdPath) $worker = $worker.Replace('__VERSION__', '0.7.1') Set-Content -Path $ScriptPath -Value $worker -Encoding utf8 # ---- Run once now to register on the dashboard ------------------------ Write-Host "Running first check-in..." try { & powershell.exe -NoProfile -ExecutionPolicy Bypass -File $ScriptPath } catch { Write-Warning "First run failed: $_" } # ---- Install / refresh the Scheduled Task ----------------------------- # Use schtasks.exe rather than the New-ScheduledTask* cmdlets. The # cmdlets behave subtly differently across Server 2012 R2 / 2016 / 2019 # / 2022 — different valid parameter combos for triggers, principals # and settings, and Register-ScheduledTask returns 0x80070057 on 2012 R2 # for combinations that work fine elsewhere. schtasks.exe has shipped # on every Windows since XP and accepts the same flags everywhere. Write-Host "Installing Scheduled Task '$TaskName'..." $cmd = "powershell.exe -NoProfile -WindowStyle Hidden -ExecutionPolicy Bypass -File `"$ScriptPath`"" & schtasks.exe /Create /TN $TaskName /SC MINUTE /MO 1 ` /TR $cmd /RU SYSTEM /RL HIGHEST /F | Out-Null if ($LASTEXITCODE -ne 0) { throw "schtasks /Create exited with code $LASTEXITCODE" } Write-Host "" Write-Host "BurikaRMM agent installed (version 0.7.1)." Write-Host " Dashboard: $ServerUrl/technical/devices" Write-Host " Install ID: $InstallId" Write-Host " Check-in: every minute (Scheduled Task '$TaskName')" Write-Host " Auto-update: on. The agent self-updates whenever the dashboard" Write-Host " advertises a newer version on its next check-in."