Browse Source
Refactor install/update into Agent-InstallCore, add GPO startup scripts, GPU and disk model collection, DB migration 002, and collector UI updates. Co-authored-by: Cursor <cursoragent@cursor.com>master
30 changed files with 1452 additions and 333 deletions
@ -1,120 +1,239 @@
@@ -1,120 +1,239 @@
|
||||
# GeoNetAgent — Progress & Monitoring Fase |
||||
|
||||
|
||||
|
||||
> **Update file ini** setiap milestone selesai. Ini sumber tunggal untuk cek *kita ada di fase mana*. |
||||
|
||||
|
||||
|
||||
**Terakhir diupdate:** 2026-06-17 (web install agent · domain gisportal.id · reverse proxy) |
||||
|
||||
|
||||
|
||||
--- |
||||
|
||||
|
||||
|
||||
## Snapshot (baca cepat) |
||||
|
||||
|
||||
|
||||
| Item | Status | |
||||
|
||||
|------|--------| |
||||
|
||||
| **Fase aktif** | **Fase 1 — Foundation** 🔄 | |
||||
|
||||
| Fase berikutnya | Laravel asset index · auth IT | |
||||
|
||||
| Agent POC | `v0.1.6-phase0` — collect + POST + web install | |
||||
|
||||
| Collector | `https://collector.gisportal.id` UI · `agent.gisportal.id` POST · `:8000` HTTP LAN | |
||||
|
||||
| Monorepo | Partial — `agent/` + `collector/` + `samples/` + `infra/` ✅ · `portal/` ❌ | |
||||
|
||||
| Blocker utama | Laravel portal belum scaffold | |
||||
|
||||
|
||||
|
||||
### Progress ringkas |
||||
|
||||
|
||||
|
||||
| Fase | Status | Progress | |
||||
|
||||
|------|--------|----------| |
||||
|
||||
| 0 — Blueprint | ✅ Selesai | 8 / 8 | |
||||
|
||||
| 1 — Foundation | 🔄 Aktif | 7 / 9 | |
||||
|
||||
| 2 — Integrasi monitoring | ⏳ | 0 / 2 | |
||||
|
||||
| 3 — Infra sync & CMDB | ⏳ | 0 / 3 | |
||||
|
||||
| 4 — Scale & intelligence | ⏳ | 1 / 3 | |
||||
|
||||
|
||||
|
||||
--- |
||||
|
||||
|
||||
|
||||
## Fase 0 — Blueprint ✅ **(SELESAI)** |
||||
|
||||
|
||||
|
||||
**DoD:** Dokumentasi lengkap, blueprint disetujui user, struktur monorepo ter-scaffold. |
||||
|
||||
|
||||
|
||||
- [x] Dokumentasi `.cursor/` lengkap (blueprint, db, API, repo, monitoring, playbook) |
||||
|
||||
- [x] `server.md` + SSH key-only diverifikasi |
||||
|
||||
- [x] Rule `project-context.mdc` (server, Dockge, QNAP Object Storage) |
||||
|
||||
- [x] Scaffold partial — folder `agent/` + `samples/agent-reports/` |
||||
|
||||
- [x] Uji agent collect-only di laptop dev (`DESKTOP-RRJ9G01`) |
||||
|
||||
- [ ] Review & approval user terhadap blueprint |
||||
|
||||
- [ ] Scaffold monorepo penuh (`portal/`) |
||||
|
||||
- [x] Pindahkan sample DXDiag ke `samples/dxdiag/` |
||||
|
||||
|
||||
|
||||
### Sub-task: Agent POC |
||||
|
||||
|
||||
|
||||
- [x] `Collect-GeoNetReport.ps1` + modul `lib/` |
||||
|
||||
- [x] Payload: system, disk, memory, network, health, peripherals, monitor |
||||
|
||||
- [x] `Send-GeoNetReport.ps1` — E2E POST ke collector |
||||
|
||||
- [x] Web install `https://agent.gisportal.id/install/` — pilot `DESKTOP-RRJ9G01` |
||||
|
||||
- [ ] Uji collect di laptop kedua (`BKK-MINIPC`) |
||||
|
||||
|
||||
|
||||
--- |
||||
|
||||
|
||||
|
||||
## Fase 1 — Foundation 🔄 **(AKTIF)** |
||||
|
||||
|
||||
|
||||
**DoD:** Agent di 1 laptop test POST → data di DB → Laravel tampilkan halaman asset list. |
||||
|
||||
|
||||
|
||||
- [x] PostgreSQL `geonetagent_dev` + SQL migration `001_initial_schema` di main-db |
||||
|
||||
- [x] FastAPI `/api/v1/agent/report` + `/health` + read API assets |
||||
|
||||
- [x] PowerShell agent v0.1.6 + kirim ke collector (E2E verified) |
||||
|
||||
- [x] UI uji read-only — `https://collector.gisportal.id/ui/` · `http://10.100.1.24:8000/ui/` |
||||
|
||||
- [x] Nginx reverse proxy — POST agent `agent.gisportal.id` · HTTPS UI `collector.gisportal.id` |
||||
|
||||
- [x] DNS publik + split-DNS LAN `agent.gisportal.id` / `collector.gisportal.id` |
||||
|
||||
- [x] Firewalld port `8000` untuk subnet internal di `.24` |
||||
|
||||
- [ ] Laravel asset index (read-only) |
||||
|
||||
- [ ] Auth IT user lokal (M9) |
||||
|
||||
|
||||
|
||||
--- |
||||
|
||||
|
||||
|
||||
## Fase 2 — Integrasi monitoring ⏳ |
||||
|
||||
|
||||
|
||||
**DoD:** Alert Zabbix di portal; status host dari Zabbix API. |
||||
|
||||
|
||||
|
||||
- [ ] Zabbix webhook → Laravel |
||||
|
||||
- [ ] Zabbix host status cache di dashboard |
||||
|
||||
|
||||
|
||||
--- |
||||
|
||||
|
||||
|
||||
## Fase 3 — Infra sync & CMDB ⏳ |
||||
|
||||
|
||||
|
||||
**DoD:** VM Proxmox otomatis di CMDB; license manual bisa di-assign. |
||||
|
||||
|
||||
|
||||
- [ ] Proxmox sync job (FastAPI) |
||||
|
||||
- [ ] License module (Laravel) |
||||
|
||||
- [ ] User ↔ device assignment UI |
||||
|
||||
|
||||
|
||||
--- |
||||
|
||||
|
||||
|
||||
## Fase 4 — Scale & intelligence ⏳ |
||||
|
||||
|
||||
|
||||
- [ ] GPO deploy agent ke semua laptop (web install siap; GPO opsional) |
||||
|
||||
- [x] Web install `https://agent.gisportal.id/install/` + `Install-GeoNetAgent.ps1` |
||||
|
||||
- [ ] Trend & compliance report |
||||
|
||||
- [ ] Ollama assistant (opsional) |
||||
|
||||
|
||||
|
||||
--- |
||||
|
||||
|
||||
|
||||
## Cara update |
||||
|
||||
|
||||
|
||||
1. Centang `[x]` item yang selesai |
||||
|
||||
2. Update **Terakhir diupdate** dan tabel **Snapshot** |
||||
|
||||
3. Hitung ulang kolom **Progress** (item selesai / total item fase) |
||||
|
||||
4. Jika pindah fase: ubah **Fase aktif** di Snapshot + banner `(AKTIF)` pada section fase |
||||
|
||||
5. Sinkronkan baris status singkat di `blueprint.md`, `me.md`, `monitoring-architecture.md`, `project-context.mdc`, `perintah.md` |
||||
|
||||
|
||||
|
||||
--- |
||||
|
||||
|
||||
|
||||
## Riwayat milestone |
||||
|
||||
|
||||
|
||||
| Tanggal | Milestone | |
||||
|
||||
|---------|-----------| |
||||
|
||||
| 2026-06-17 | Dokumentasi `.cursor/` + SSH server verified | |
||||
|
||||
| 2026-06-17 | Agent POC `Collect-GeoNetReport.ps1` v0.1.2 — collect JSON lokal | |
||||
|
||||
| 2026-06-17 | File `progress.md` dibuat — tracking fase terpusat | |
||||
|
||||
| 2026-06-17 | Agent v0.1.4 — `peripherals[]` USB/HID/BT/audio jack | |
||||
|
||||
| 2026-06-17 | Collector redeploy `.24` + UI peripherals di `/ui/` | |
||||
|
||||
| 2026-06-17 | Nginx reverse proxy — domain `gisportal.id` | |
||||
|
||||
| 2026-06-17 | Web install agent + pilot deploy `DESKTOP-RRJ9G01` | |
||||
|
||||
|
||||
|
||||
@ -0,0 +1,6 @@
@@ -0,0 +1,6 @@
|
||||
@echo off |
||||
REM GeoNetAgent — GPO Computer Startup Script (wrapper) |
||||
REM Deploy folder ini ke SYSVOL / NETLOGON, lalu tautkan .cmd ini di GPO Startup. |
||||
|
||||
powershell.exe -NoProfile -ExecutionPolicy Bypass -WindowStyle Hidden -File "%~dp0GeoNetAgent-GPO-Startup.ps1" |
||||
exit /b 0 |
||||
@ -0,0 +1,185 @@
@@ -0,0 +1,185 @@
|
||||
#Requires -Version 5.1 |
||||
<# |
||||
.SYNOPSIS |
||||
GPO Startup Script — pasang atau update GeoNetAgent (silent, SYSTEM). |
||||
|
||||
.DESCRIPTION |
||||
Dipanggil dari Group Policy (Computer Configuration → Startup Scripts). |
||||
- Belum terpasang → install dari https://agent.gisportal.id/install/ |
||||
- Sudah terpasang → cek manifest vs VERSION, update jika perlu |
||||
- Memastikan Scheduled Task ada (startup + tiap 3 jam) |
||||
- Tidak kirim laporan langsung (-SkipInitialRun); task SYSTEM yang kirim |
||||
|
||||
.PARAMETER BaseUrl |
||||
URL paket install. Default: https://agent.gisportal.id/install |
||||
|
||||
.PARAMETER ScheduleHours |
||||
Interval laporan terjadwal. Default: 3 |
||||
|
||||
.PARAMETER MaxNetworkWaitSeconds |
||||
Tunggu jaringan siap sebelum unduh (boot awal). Default: 120 |
||||
|
||||
.NOTES |
||||
Log: %ProgramData%\GeoNetAgent\logs\gpo-startup.log |
||||
Exit selalu 0 agar GPO tidak mengulang agresif; cek log jika perlu. |
||||
#> |
||||
[CmdletBinding()] |
||||
param( |
||||
[string]$BaseUrl = 'https://agent.gisportal.id/install', |
||||
[int]$ScheduleHours = 3, |
||||
[int]$MaxNetworkWaitSeconds = 120 |
||||
) |
||||
|
||||
$ErrorActionPreference = 'Continue' |
||||
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 |
||||
|
||||
$InstallRoot = Join-Path $env:ProgramData 'GeoNetAgent' |
||||
$LogDir = Join-Path $InstallRoot 'logs' |
||||
$LogFile = Join-Path $LogDir 'gpo-startup.log' |
||||
|
||||
function Write-GeoNetGpoLog { |
||||
param([string]$Message) |
||||
$line = '{0} {1}' -f (Get-Date -Format 'yyyy-MM-dd HH:mm:ss'), $Message |
||||
try { |
||||
if (-not (Test-Path $LogDir)) { |
||||
New-Item -ItemType Directory -Path $LogDir -Force | Out-Null |
||||
} |
||||
Add-Content -Path $LogFile -Value $line -Encoding UTF8 |
||||
} |
||||
catch { } |
||||
} |
||||
|
||||
function Wait-GeoNetNetwork { |
||||
param( |
||||
[string]$Url, |
||||
[int]$MaxSeconds |
||||
) |
||||
$base = $Url.TrimEnd('/') |
||||
$probe = "$base/manifest.json" |
||||
$deadline = (Get-Date).AddSeconds($MaxSeconds) |
||||
while ((Get-Date) -lt $deadline) { |
||||
try { |
||||
$null = Invoke-WebRequest -Uri $probe -Method Head -UseBasicParsing -TimeoutSec 8 |
||||
return $true |
||||
} |
||||
catch { |
||||
Start-Sleep -Seconds 5 |
||||
} |
||||
} |
||||
return $false |
||||
} |
||||
|
||||
function Save-GeoNetRemoteFile { |
||||
param([string]$Url, [string]$Destination) |
||||
$parent = Split-Path -Parent $Destination |
||||
if (-not (Test-Path $parent)) { |
||||
New-Item -ItemType Directory -Path $parent -Force | Out-Null |
||||
} |
||||
try { |
||||
Invoke-WebRequest -Uri $Url -OutFile $Destination -UseBasicParsing -TimeoutSec 90 |
||||
if ((Get-Item $Destination).Length -gt 0) { return } |
||||
} |
||||
catch { |
||||
if (Get-Command curl.exe -ErrorAction SilentlyContinue) { |
||||
& curl.exe -fsSL --tlsv1.2 $Url -o $Destination |
||||
if ($LASTEXITCODE -eq 0 -and (Test-Path $Destination)) { return } |
||||
} |
||||
throw |
||||
} |
||||
} |
||||
|
||||
function Invoke-GeoNetGpoWebInstall { |
||||
param([string]$Url) |
||||
$base = $Url.TrimEnd('/') |
||||
$stage = Join-Path $env:TEMP 'GeoNetAgent-gpo-install' |
||||
$installer = Join-Path $stage 'Install-GeoNetAgent.ps1' |
||||
$coreLib = Join-Path $stage 'lib\Agent-InstallCore.ps1' |
||||
|
||||
Write-GeoNetGpoLog "Unduh installer dari $base" |
||||
Save-GeoNetRemoteFile -Url "$base/Install-GeoNetAgent.ps1" -Destination $installer |
||||
Save-GeoNetRemoteFile -Url "$base/lib/Agent-InstallCore.ps1" -Destination $coreLib |
||||
|
||||
Write-GeoNetGpoLog 'Jalankan Install-GeoNetAgent.ps1 -SkipInitialRun' |
||||
& powershell.exe -NoProfile -ExecutionPolicy Bypass -File $installer ` |
||||
-BaseUrl $base -InstallRoot $InstallRoot -SkipInitialRun -ScheduleHours $ScheduleHours |
||||
if ($LASTEXITCODE -ne 0) { |
||||
throw "Install-GeoNetAgent exit code $LASTEXITCODE" |
||||
} |
||||
} |
||||
|
||||
function Test-GeoNetScheduledTasks { |
||||
$names = @('GeoNetAgent-Report', 'GeoNetAgent-Report-Startup') |
||||
foreach ($name in $names) { |
||||
$null = & schtasks.exe /Query /TN $name /FO LIST 2>$null |
||||
if ($LASTEXITCODE -ne 0) { return $false } |
||||
} |
||||
return $true |
||||
} |
||||
|
||||
function Repair-GeoNetScheduledTasks { |
||||
param([string]$Root, [int]$Hours) |
||||
$core = Join-Path $Root 'lib\Agent-InstallCore.ps1' |
||||
if (-not (Test-Path $core)) { |
||||
throw "Agent-InstallCore.ps1 tidak ada di $Root" |
||||
} |
||||
. $core |
||||
Register-GeoNetAgentTask -InstallRoot $Root -Hours $Hours |
||||
Write-GeoNetGpoLog "Scheduled Task diperbaiki (interval ${Hours} jam)" |
||||
} |
||||
|
||||
Write-GeoNetGpoLog '=== GPO startup begin ===' |
||||
|
||||
try { |
||||
if (-not (Wait-GeoNetNetwork -Url $BaseUrl -MaxSeconds $MaxNetworkWaitSeconds)) { |
||||
throw "Jaringan/install URL tidak reachable dalam ${MaxNetworkWaitSeconds}s" |
||||
} |
||||
|
||||
$userAgent = Join-Path $InstallRoot 'UserAgent.ps1' |
||||
$updateScript = Join-Path $InstallRoot 'lib\Update-GeoNetAgent.ps1' |
||||
|
||||
if (-not (Test-Path $userAgent)) { |
||||
Write-GeoNetGpoLog 'Agent belum terpasang — install baru' |
||||
Invoke-GeoNetGpoWebInstall -Url $BaseUrl |
||||
} |
||||
elseif (-not (Test-Path $updateScript)) { |
||||
Write-GeoNetGpoLog 'Agent lama (tanpa auto-update) — reinstall' |
||||
Invoke-GeoNetGpoWebInstall -Url $BaseUrl |
||||
} |
||||
else { |
||||
Write-GeoNetGpoLog 'Agent ada — cek update' |
||||
. $updateScript |
||||
$result = Update-GeoNetAgentIfNeeded ` |
||||
-InstallRoot $InstallRoot ` |
||||
-BaseUrl $BaseUrl ` |
||||
-ConfigPath (Join-Path $InstallRoot 'config.json') ` |
||||
-ScheduleHours $ScheduleHours ` |
||||
-Quiet |
||||
if ($result.Updated) { |
||||
Write-GeoNetGpoLog "Updated: $($result.From) -> $($result.To)" |
||||
} |
||||
else { |
||||
Write-GeoNetGpoLog "Versi OK: $($result.From)" |
||||
if ($result.Error) { |
||||
Write-GeoNetGpoLog "Update check warning: $($result.Error)" |
||||
} |
||||
} |
||||
} |
||||
|
||||
if (-not (Test-GeoNetScheduledTasks)) { |
||||
Write-GeoNetGpoLog 'Scheduled Task hilang — perbaiki' |
||||
Repair-GeoNetScheduledTasks -Root $InstallRoot -Hours $ScheduleHours |
||||
} |
||||
|
||||
$version = $null |
||||
$versionPath = Join-Path $InstallRoot 'VERSION' |
||||
if (Test-Path $versionPath) { |
||||
$version = (Get-Content $versionPath -Raw).Trim() |
||||
} |
||||
Write-GeoNetGpoLog "Selesai OK. VERSION=$version" |
||||
} |
||||
catch { |
||||
Write-GeoNetGpoLog "ERROR: $($_.Exception.Message)" |
||||
} |
||||
|
||||
Write-GeoNetGpoLog '=== GPO startup end ===' |
||||
exit 0 |
||||
@ -0,0 +1,146 @@
@@ -0,0 +1,146 @@
|
||||
# GeoNetAgent — Rollout massal via GPO |
||||
|
||||
Skrip startup untuk **Computer Configuration** Active Directory. Jalan sebagai **SYSTEM** saat boot — tidak perlu user Admin login. |
||||
|
||||
## Isi folder |
||||
|
||||
| File | Fungsi | |
||||
|------|--------| |
||||
| `GeoNetAgent-GPO-Startup.cmd` | Entry point untuk GPO (tambahkan file ini di policy) | |
||||
| `GeoNetAgent-GPO-Startup.ps1` | Logika install / update / perbaiki task | |
||||
| `README.md` | Panduan ini | |
||||
|
||||
## Prasyarat server |
||||
|
||||
1. Agent v0.1.8+ sudah di-publish: `bash infra/collector/publish_agent_install.sh` |
||||
2. `https://agent.gisportal.id/install/manifest.json` bisa diakses dari jaringan user |
||||
3. Migration DB `002_disk_model.sql` sudah dijalankan (jika pakai fitur model disk) |
||||
|
||||
## Langkah 1 — Salin ke SYSVOL |
||||
|
||||
Salin seluruh folder `agent/gpo/` ke share domain, contoh: |
||||
|
||||
``` |
||||
\\gisportal.id\SYSVOL\gisportal.id\scripts\GeoNetAgent\ |
||||
``` |
||||
|
||||
Atau (klassik): |
||||
|
||||
``` |
||||
\\<DC>\NETLOGON\GeoNetAgent\ |
||||
``` |
||||
|
||||
Pastikan **Computer Account** domain bisa **Read** folder tersebut. |
||||
|
||||
## Langkah 2 — Buat / edit GPO |
||||
|
||||
1. Buka **Group Policy Management** di DC (`AD 2012 R2` OK) |
||||
2. Buat GPO baru, mis. `GeoNetAgent — Deploy Endpoint` |
||||
3. Edit GPO → **Computer Configuration** → **Policies** → **Windows Settings** → **Scripts (Startup/Shutdown)** |
||||
4. Tab **Startup** → **Add** → pilih: |
||||
|
||||
``` |
||||
\\gisportal.id\SYSVOL\gisportal.id\scripts\GeoNetAgent\GeoNetAgent-GPO-Startup.cmd |
||||
``` |
||||
|
||||
5. **OK** → tutup editor GPO |
||||
|
||||
## Langkah 3 — Link ke OU |
||||
|
||||
- Link GPO ke OU yang berisi **laptop/PC Windows** user |
||||
- **Jangan** link ke OU server jika server tidak perlu agent |
||||
- Urutan: taruh GPO di level OU yang tepat; hindari konflik dengan GPO lain yang memblokir script |
||||
|
||||
## Langkah 4 — Uji (1–2 mesin dulu) |
||||
|
||||
Pada laptop uji (sudah join domain): |
||||
|
||||
```cmd |
||||
gpupdate /force |
||||
shutdown /r /t 0 |
||||
``` |
||||
|
||||
Setelah boot, cek: |
||||
|
||||
```powershell |
||||
Get-Content "$env:ProgramData\GeoNetAgent\VERSION" |
||||
Get-Content "$env:ProgramData\GeoNetAgent\logs\gpo-startup.log" -Tail 20 |
||||
schtasks /Query /TN GeoNetAgent-Report /V /FO LIST |
||||
Test-Path "$env:ProgramData\GeoNetAgent\UserAgent.ps1" |
||||
``` |
||||
|
||||
Harapan: |
||||
|
||||
- `VERSION` = versi terbaru di server (mis. `0.1.8-phase0`) |
||||
- Log berisi `Selesai OK` |
||||
- Task `GeoNetAgent-Report` — HOURLY, interval **3** jam, run as **SYSTEM** |
||||
|
||||
## Perilaku skrip |
||||
|
||||
| Kondisi | Aksi | |
||||
|---------|------| |
||||
| Agent belum ada | Install penuh dari web (silent, `-SkipInitialRun`) | |
||||
| Agent lama tanpa modul auto-update | Reinstall | |
||||
| Agent ada | Cek `manifest.json` vs `VERSION`, update jika beda | |
||||
| Scheduled Task hilang | Daftar ulang (startup + 3 jam) | |
||||
| Jaringan belum siap saat boot | Tunggu hingga 120 detik | |
||||
| Error | Catat di log, **exit 0** (boot tidak terganggu) | |
||||
|
||||
Laporan inventaris dikirim oleh Scheduled Task (`UserAgent.ps1`), bukan langsung dari skrip GPO. |
||||
|
||||
## Log |
||||
|
||||
``` |
||||
%ProgramData%\GeoNetAgent\logs\gpo-startup.log |
||||
``` |
||||
|
||||
Contoh: |
||||
|
||||
``` |
||||
2026-06-18 08:15:02 === GPO startup begin === |
||||
2026-06-18 08:15:08 Agent belum terpasang — install baru |
||||
2026-06-18 08:15:45 Selesai OK. VERSION=0.1.8-phase0 |
||||
2026-06-18 08:15:45 === GPO startup end === |
||||
``` |
||||
|
||||
## Firewall / proxy |
||||
|
||||
Laptop harus bisa **HTTPS outbound** ke: |
||||
|
||||
- `https://agent.gisportal.id` (install + POST laporan) |
||||
|
||||
Split-DNS MikroTik sudah mengarahkan domain ini ke `10.100.1.24` di LAN. |
||||
|
||||
## Opsi parameter (advanced) |
||||
|
||||
Edit baris di `.cmd` jika perlu: |
||||
|
||||
```batch |
||||
powershell.exe -NoProfile -ExecutionPolicy Bypass -WindowStyle Hidden -File "%~dp0GeoNetAgent-GPO-Startup.ps1" -ScheduleHours 3 -MaxNetworkWaitSeconds 180 |
||||
``` |
||||
|
||||
## Rollback / uninstall |
||||
|
||||
1. Unlink GPO dari OU |
||||
2. Hapus task: |
||||
|
||||
```cmd |
||||
schtasks /Delete /TN GeoNetAgent-Report /F |
||||
schtasks /Delete /TN GeoNetAgent-Report-Startup /F |
||||
``` |
||||
|
||||
3. Hapus folder: `%ProgramData%\GeoNetAgent` |
||||
|
||||
## Troubleshooting |
||||
|
||||
| Gejala | Cek | |
||||
|--------|-----| |
||||
| Log tidak ada | GPO belum jalan / path script salah / OU salah | |
||||
| `Jaringan tidak reachable` | DNS, firewall, proxy; boot terlalu cepat — naikkan `MaxNetworkWaitSeconds` | |
||||
| VERSION kosong | Install gagal — buka log, coba web install manual Admin | |
||||
| Task tidak ada | Jalankan manual: `Repair` via install Admin sekali | |
||||
| GPO tidak apply | `gpresult /H report.html` pada laptop | |
||||
|
||||
## Alternatif tanpa GPO |
||||
|
||||
Web install manual (2 baris PS Admin) — lihat `agent/install/index.html` atau `agent/README.md`. |
||||
@ -0,0 +1,209 @@
@@ -0,0 +1,209 @@
|
||||
function Get-GeoNetInstallBaseUrl { |
||||
param([string]$Url) |
||||
return $Url.TrimEnd('/') |
||||
} |
||||
|
||||
function Get-GeoNetRemoteJson { |
||||
param([string]$Url) |
||||
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 |
||||
return (Invoke-WebRequest -Uri $Url -UseBasicParsing -TimeoutSec 60).Content | ConvertFrom-Json |
||||
} |
||||
|
||||
function Get-GeoNetRemoteFile { |
||||
param([string]$Url, [string]$Destination) |
||||
$parent = Split-Path -Parent $Destination |
||||
if (-not (Test-Path $parent)) { |
||||
New-Item -ItemType Directory -Path $parent -Force | Out-Null |
||||
} |
||||
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 |
||||
$lastErr = $null |
||||
for ($try = 1; $try -le 3; $try++) { |
||||
try { |
||||
Invoke-WebRequest -Uri $Url -OutFile $Destination -UseBasicParsing -TimeoutSec 90 |
||||
if ((Get-Item $Destination).Length -gt 0) { return } |
||||
} |
||||
catch { |
||||
$lastErr = $_ |
||||
Start-Sleep -Seconds 2 |
||||
} |
||||
} |
||||
if (Get-Command curl.exe -ErrorAction SilentlyContinue) { |
||||
& curl.exe -fsSL --tlsv1.2 $Url -o $Destination |
||||
if ($LASTEXITCODE -eq 0 -and (Test-Path $Destination)) { return } |
||||
} |
||||
throw $lastErr |
||||
} |
||||
|
||||
function Copy-GeoNetAgentPayload { |
||||
param( |
||||
[string]$SourceRoot, |
||||
[string]$DestinationRoot, |
||||
[object]$Manifest |
||||
) |
||||
foreach ($rel in @($Manifest.files)) { |
||||
$src = Join-Path $SourceRoot ($rel -replace '/', [IO.Path]::DirectorySeparatorChar) |
||||
$dst = Join-Path $DestinationRoot ($rel -replace '/', [IO.Path]::DirectorySeparatorChar) |
||||
if (-not (Test-Path $src)) { |
||||
throw "File sumber tidak ada: $src" |
||||
} |
||||
$dir = Split-Path -Parent $dst |
||||
if (-not (Test-Path $dir)) { |
||||
New-Item -ItemType Directory -Path $dir -Force | Out-Null |
||||
} |
||||
Copy-Item -Path $src -Destination $dst -Force |
||||
} |
||||
} |
||||
|
||||
function Install-GeoNetAgentPayload { |
||||
param( |
||||
[string]$Base, |
||||
[string]$DestinationRoot, |
||||
[object]$Manifest, |
||||
[switch]$Quiet |
||||
) |
||||
foreach ($rel in @(Get-GeoNetManifestFileOrder -Files $Manifest.files)) { |
||||
$url = "$Base/$($rel -replace '\\', '/')" |
||||
$dst = Join-Path $DestinationRoot ($rel -replace '/', [IO.Path]::DirectorySeparatorChar) |
||||
if (-not $Quiet) { Write-Host " -> $rel" } |
||||
Get-GeoNetRemoteFile -Url $url -Destination $dst |
||||
} |
||||
} |
||||
|
||||
function Get-GeoNetManifestFileOrder { |
||||
param([object[]]$Files) |
||||
@($Files | Sort-Object { |
||||
if ($_ -eq 'UserAgent.ps1') { 'zzz' } else { $_ } |
||||
}) |
||||
} |
||||
|
||||
function Get-GeoNetLocalAgentVersion { |
||||
param([string]$InstallRoot) |
||||
$versionPath = Join-Path $InstallRoot 'VERSION' |
||||
if (-not (Test-Path $versionPath)) { return $null } |
||||
$text = (Get-Content $versionPath -Raw -ErrorAction SilentlyContinue) |
||||
if ([string]::IsNullOrWhiteSpace($text)) { return $null } |
||||
return $text.Trim() |
||||
} |
||||
|
||||
function Set-GeoNetLocalAgentVersion { |
||||
param( |
||||
[string]$InstallRoot, |
||||
[string]$Version |
||||
) |
||||
$Version.Trim() | Set-Content -Path (Join-Path $InstallRoot 'VERSION') -Encoding ASCII -NoNewline |
||||
} |
||||
|
||||
function Invoke-GeoNetSchtasks { |
||||
param( |
||||
[Parameter(Mandatory)][string[]]$ArgumentList, |
||||
[switch]$IgnoreError |
||||
) |
||||
$prev = $ErrorActionPreference |
||||
$ErrorActionPreference = 'SilentlyContinue' |
||||
$output = & schtasks.exe @ArgumentList 2>&1 |
||||
$code = $LASTEXITCODE |
||||
$ErrorActionPreference = $prev |
||||
if ($code -ne 0 -and -not $IgnoreError) { |
||||
$msg = ($output | Out-String).Trim() |
||||
if ($msg) { throw $msg } |
||||
throw "schtasks gagal (exit $code): schtasks.exe $($ArgumentList -join ' ')" |
||||
} |
||||
return $code |
||||
} |
||||
|
||||
function Register-GeoNetAgentTask { |
||||
param( |
||||
[string]$InstallRoot, |
||||
[int]$Hours = 3 |
||||
) |
||||
$userScript = Join-Path $InstallRoot 'UserAgent.ps1' |
||||
if (-not (Test-Path $userScript)) { |
||||
throw "UserAgent.ps1 tidak ditemukan di $InstallRoot" |
||||
} |
||||
|
||||
$taskArgs = "-NoProfile -ExecutionPolicy Bypass -WindowStyle Hidden -File `"$userScript`"" |
||||
$taskCmd = "powershell.exe $taskArgs" |
||||
$taskNames = @('GeoNetAgent-Report', 'GeoNetAgent-Report-Startup') |
||||
|
||||
foreach ($name in $taskNames) { |
||||
Unregister-ScheduledTask -TaskName $name -Confirm:$false -ErrorAction SilentlyContinue |
||||
Invoke-GeoNetSchtasks -ArgumentList @('/Delete', '/TN', $name, '/F') -IgnoreError | Out-Null |
||||
} |
||||
|
||||
Invoke-GeoNetSchtasks -ArgumentList @( |
||||
'/Create', '/TN', 'GeoNetAgent-Report-Startup', '/TR', $taskCmd, |
||||
'/SC', 'ONSTART', '/RU', 'SYSTEM', '/RL', 'HIGHEST', '/F' |
||||
) | Out-Null |
||||
|
||||
Invoke-GeoNetSchtasks -ArgumentList @( |
||||
'/Create', '/TN', 'GeoNetAgent-Report', '/TR', $taskCmd, |
||||
'/SC', 'HOURLY', '/MO', "$Hours", '/RU', 'SYSTEM', '/RL', 'HIGHEST', '/F' |
||||
) | Out-Null |
||||
} |
||||
|
||||
function Set-GeoNetAgentConfig { |
||||
param( |
||||
[string]$InstallRoot, |
||||
[string]$Base, |
||||
[string]$Token, |
||||
[bool]$UseRemoteConfig |
||||
) |
||||
$configPath = Join-Path $InstallRoot 'config.json' |
||||
|
||||
if ($Token) { |
||||
$cfg = [ordered]@{ |
||||
collector_url = 'https://agent.gisportal.id/api/v1/agent/report' |
||||
agent_token = $Token |
||||
include_software = $true |
||||
max_software = 500 |
||||
timeout_seconds = 120 |
||||
} |
||||
if (Test-Path $configPath) { |
||||
try { |
||||
$existing = Get-Content $configPath -Raw | ConvertFrom-Json |
||||
if ($existing.collector_url) { $cfg.collector_url = $existing.collector_url } |
||||
if ($null -ne $existing.include_software) { $cfg.include_software = [bool]$existing.include_software } |
||||
if ($existing.max_software) { $cfg.max_software = [int]$existing.max_software } |
||||
if ($existing.timeout_seconds) { $cfg.timeout_seconds = [int]$existing.timeout_seconds } |
||||
} |
||||
catch { } |
||||
} |
||||
$cfg | ConvertTo-Json | Set-Content -Path $configPath -Encoding UTF8 |
||||
return |
||||
} |
||||
|
||||
if ((Test-Path $configPath) -and -not $UseRemoteConfig) { |
||||
return |
||||
} |
||||
|
||||
if ($UseRemoteConfig) { |
||||
Get-GeoNetRemoteFile -Url "$Base/config.json" -Destination $configPath |
||||
return |
||||
} |
||||
|
||||
throw 'config.json tidak ada. Sediakan -AgentToken atau config.json di URL install.' |
||||
} |
||||
|
||||
function Test-GeoNetAdmin { |
||||
$id = [Security.Principal.WindowsIdentity]::GetCurrent() |
||||
$p = New-Object Security.Principal.WindowsPrincipal($id) |
||||
return $p.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator) |
||||
} |
||||
|
||||
function Get-GeoNetInstallBaseUrlFromConfig { |
||||
param([string]$ConfigPath) |
||||
|
||||
$default = 'https://agent.gisportal.id/install' |
||||
if (-not $ConfigPath -or -not (Test-Path $ConfigPath)) { return $default } |
||||
|
||||
try { |
||||
$cfg = Get-Content $ConfigPath -Raw | ConvertFrom-Json |
||||
if ($cfg.install_url) { return Get-GeoNetInstallBaseUrl -Url $cfg.install_url } |
||||
if ($cfg.collector_url -match '^(https?://[^/]+)') { |
||||
return Get-GeoNetInstallBaseUrl -Url "$($Matches[1])/install" |
||||
} |
||||
} |
||||
catch { } |
||||
|
||||
return $default |
||||
} |
||||
@ -0,0 +1,39 @@
@@ -0,0 +1,39 @@
|
||||
$formatText = Join-Path $PSScriptRoot 'Format-Text.ps1' |
||||
if (Test-Path $formatText) { . $formatText } |
||||
|
||||
function Convert-GeoNetAdapterRamMb { |
||||
param([AllowNull()]$AdapterRam) |
||||
|
||||
if ($null -eq $AdapterRam -or $AdapterRam -le 0) { return $null } |
||||
|
||||
try { |
||||
$mb = [math]::Round([decimal]$AdapterRam / 1MB, 0) |
||||
if ($mb -le 0 -or $mb -gt 131072) { return $null } |
||||
return [int]$mb |
||||
} catch { |
||||
return $null |
||||
} |
||||
} |
||||
|
||||
function Get-GeoNetGpuInfo { |
||||
$controllers = Invoke-GeoNetSafe { |
||||
Get-CimInstance Win32_VideoController -ErrorAction Stop | |
||||
Where-Object { |
||||
$_.Name -and $_.Name -notmatch 'Microsoft Basic|Remote|Sage Display|Display Manager' |
||||
} |
||||
} -Default @() |
||||
|
||||
$gpus = @($controllers | ForEach-Object { |
||||
$name = Convert-GeoNetOptionalString -Value $_.Name |
||||
if (-not $name) { return } |
||||
|
||||
[pscustomobject]@{ |
||||
name = $name |
||||
dedicated_memory_mb = Convert-GeoNetAdapterRamMb -AdapterRam $_.AdapterRAM |
||||
} |
||||
}) |
||||
|
||||
@($gpus | Sort-Object { |
||||
if ($null -ne $_.dedicated_memory_mb) { $_.dedicated_memory_mb } else { -1 } |
||||
} -Descending) |
||||
} |
||||
@ -0,0 +1,74 @@
@@ -0,0 +1,74 @@
|
||||
$installCore = Join-Path $PSScriptRoot 'Agent-InstallCore.ps1' |
||||
if (Test-Path $installCore) { . $installCore } |
||||
|
||||
function Update-GeoNetAgentIfNeeded { |
||||
<# |
||||
.SYNOPSIS |
||||
Cek manifest.json di server vs VERSION lokal; unduh paket jika beda. |
||||
Gagal update tidak menghentikan pemanggil (mis. kirim laporan tetap jalan). |
||||
#> |
||||
[CmdletBinding()] |
||||
param( |
||||
[Parameter(Mandatory)] |
||||
[string]$InstallRoot, |
||||
[string]$BaseUrl, |
||||
[string]$ConfigPath, |
||||
[int]$ScheduleHours = 3, |
||||
[switch]$Quiet |
||||
) |
||||
|
||||
$result = [ordered]@{ |
||||
Updated = $false |
||||
From = $null |
||||
To = $null |
||||
Error = $null |
||||
} |
||||
|
||||
try { |
||||
if (-not $BaseUrl) { |
||||
$BaseUrl = Get-GeoNetInstallBaseUrlFromConfig -ConfigPath $ConfigPath |
||||
} |
||||
$base = Get-GeoNetInstallBaseUrl -Url $BaseUrl |
||||
|
||||
$manifest = Get-GeoNetRemoteJson -Url "$base/manifest.json" |
||||
$remoteVersion = [string]$manifest.version |
||||
if ([string]::IsNullOrWhiteSpace($remoteVersion)) { |
||||
throw 'manifest.json server tidak memuat version.' |
||||
} |
||||
$remoteVersion = $remoteVersion.Trim() |
||||
|
||||
$localVersion = Get-GeoNetLocalAgentVersion -InstallRoot $InstallRoot |
||||
$result.From = $localVersion |
||||
|
||||
if ($localVersion -eq $remoteVersion) { |
||||
return [pscustomobject]$result |
||||
} |
||||
|
||||
if (-not $Quiet) { |
||||
Write-Host "GeoNetAgent auto-update: $localVersion -> $remoteVersion" -ForegroundColor Cyan |
||||
} |
||||
|
||||
Install-GeoNetAgentPayload -Base $base -DestinationRoot $InstallRoot -Manifest $manifest -Quiet:$Quiet |
||||
Set-GeoNetLocalAgentVersion -InstallRoot $InstallRoot -Version $remoteVersion |
||||
|
||||
try { |
||||
Register-GeoNetAgentTask -InstallRoot $InstallRoot -Hours $ScheduleHours |
||||
} |
||||
catch { |
||||
if (-not $Quiet) { |
||||
Write-Warning "Auto-update: file terbaru, tetapi refresh Scheduled Task gagal: $_" |
||||
} |
||||
} |
||||
|
||||
$result.Updated = $true |
||||
$result.To = $remoteVersion |
||||
} |
||||
catch { |
||||
$result.Error = $_.Exception.Message |
||||
if (-not $Quiet) { |
||||
Write-Warning "GeoNetAgent auto-update dilewati: $($result.Error)" |
||||
} |
||||
} |
||||
|
||||
return [pscustomobject]$result |
||||
} |
||||
@ -0,0 +1,3 @@
@@ -0,0 +1,3 @@
|
||||
-- Migration 002: physical disk model on asset_disks |
||||
ALTER TABLE asset_disks |
||||
ADD COLUMN IF NOT EXISTS model varchar(100); |
||||
@ -0,0 +1,320 @@
@@ -0,0 +1,320 @@
|
||||
{ |
||||
"schema_version": "1.0", |
||||
"agent_version": "0.1.7-phase0", |
||||
"collected_at": "2026-06-18T11:25:23+07:00", |
||||
"hostname": "DESKTOP-RRJ9G01", |
||||
"machine_id": "3709baa5-5265-4e4d-9a81-fb8c8927bb3b", |
||||
"smbios_uuid": "03D502E0-045E-0550-8E06-BA0700080009", |
||||
"serial_number": null, |
||||
"asset_type": "endpoint", |
||||
"logged_in_user": "DESKTOP-RRJ9G01\\admin", |
||||
"system": { |
||||
"manufacturer": "Gigabyte Technology Co., Ltd.", |
||||
"model": "H81M-DS2", |
||||
"bios_version": "F2", |
||||
"os_name": "Microsoft Windows 11 Pro", |
||||
"os_version": "10.0", |
||||
"os_build": "10.0.26200 (26200)", |
||||
"cpu_model": "Intel(R) Core(TM) i5-4460 CPU @ 3.20GHz", |
||||
"ram_gb": 15.9, |
||||
"gpu_model": "NVIDIA GeForce GT 730", |
||||
"last_boot_at": "2026-06-18T10:41:02+07:00" |
||||
}, |
||||
"gpus": [ |
||||
{ |
||||
"name": "NVIDIA GeForce GT 730", |
||||
"dedicated_memory_mb": 2048 |
||||
}, |
||||
{ |
||||
"name": "Intel(R) HD Graphics 4600", |
||||
"dedicated_memory_mb": 1024 |
||||
} |
||||
], |
||||
"disks": [ |
||||
{ |
||||
"drive_letter": "C:", |
||||
"total_gb": 188.7, |
||||
"free_gb": 4.6, |
||||
"pct_used": 97.6, |
||||
"file_system": "NTFS", |
||||
"model": "ADATA SU650" |
||||
}, |
||||
{ |
||||
"drive_letter": "D:", |
||||
"total_gb": 256.5, |
||||
"free_gb": 68.5, |
||||
"pct_used": 73.3, |
||||
"file_system": "NTFS", |
||||
"model": "ADATA SU650" |
||||
} |
||||
], |
||||
"memory_modules": [ |
||||
{ |
||||
"slot": "ChannelA-DIMM0", |
||||
"bank_label": "BANK 0", |
||||
"capacity_gb": 8, |
||||
"speed_mhz": 1600, |
||||
"manufacturer": "0000", |
||||
"part_number": "", |
||||
"form_factor": "tsop", |
||||
"memory_type": 24 |
||||
}, |
||||
{ |
||||
"slot": "ChannelB-DIMM0", |
||||
"bank_label": "BANK 2", |
||||
"capacity_gb": 8, |
||||
"speed_mhz": 1600, |
||||
"manufacturer": "0000", |
||||
"part_number": "", |
||||
"form_factor": "tsop", |
||||
"memory_type": 24 |
||||
} |
||||
], |
||||
"memory_summary": { |
||||
"total_gb": 16, |
||||
"module_count": 2, |
||||
"slot_count": 2, |
||||
"empty_slots": 0, |
||||
"layout": "2x8GB", |
||||
"configuration": "2x8GB tsop", |
||||
"upgradeable": false |
||||
}, |
||||
"network": [ |
||||
{ |
||||
"adapter_name": "Realtek(R) PCI(e) Ethernet Controller", |
||||
"mac_address": "E0:D5:5E:50:8E:BA", |
||||
"ip_address": "192.168.0.21", |
||||
"adapter_kind": "physical", |
||||
"status": "Up", |
||||
"is_primary": true, |
||||
"is_built_in": true |
||||
}, |
||||
{ |
||||
"adapter_name": "Fortinet SSL VPN Virtual Ethernet Adapter", |
||||
"mac_address": "00:09:0F:AA:00:01", |
||||
"ip_address": null, |
||||
"adapter_kind": "virtual", |
||||
"status": "Not Present", |
||||
"is_primary": false, |
||||
"is_built_in": null |
||||
}, |
||||
{ |
||||
"adapter_name": "Fortinet Virtual Ethernet Adapter (NDIS 6.30)", |
||||
"mac_address": "00:09:0F:FE:00:01", |
||||
"ip_address": null, |
||||
"adapter_kind": "virtual", |
||||
"status": "Disconnected", |
||||
"is_primary": false, |
||||
"is_built_in": null |
||||
}, |
||||
{ |
||||
"adapter_name": "Hyper-V Virtual Ethernet Adapter", |
||||
"mac_address": "00:15:5D:F1:DB:09", |
||||
"ip_address": "172.27.176.1", |
||||
"adapter_kind": "virtual", |
||||
"status": "Up", |
||||
"is_primary": false, |
||||
"is_built_in": null |
||||
}, |
||||
{ |
||||
"adapter_name": "OpenVPN Data Channel Offload", |
||||
"mac_address": null, |
||||
"ip_address": null, |
||||
"adapter_kind": "virtual", |
||||
"status": "Disconnected", |
||||
"is_primary": false, |
||||
"is_built_in": null |
||||
}, |
||||
{ |
||||
"adapter_name": "PANGP Virtual Ethernet Adapter Secure #2", |
||||
"mac_address": "02:50:41:00:00:01", |
||||
"ip_address": null, |
||||
"adapter_kind": "virtual", |
||||
"status": "Disabled", |
||||
"is_primary": false, |
||||
"is_built_in": null |
||||
}, |
||||
{ |
||||
"adapter_name": "TAP-Windows Adapter V9 for OpenVPN Connect", |
||||
"mac_address": "00:FF:E0:E1:4D:4D", |
||||
"ip_address": null, |
||||
"adapter_kind": "virtual", |
||||
"status": "Disconnected", |
||||
"is_primary": false, |
||||
"is_built_in": null |
||||
} |
||||
], |
||||
"asset_identity": { |
||||
"primary_physical_mac": "E0:D5:5E:50:8E:BA", |
||||
"primary_physical_adapter": "Realtek(R) PCI(e) Ethernet Controller", |
||||
"primary_mac_built_in": true, |
||||
"primary_mac_source": "primary_route_built_in_physical", |
||||
"merge_key": "mac:E0:D5:5E:50:8E:BA", |
||||
"recommended_match_order": [ |
||||
"serial_number", |
||||
"smbios_uuid", |
||||
"primary_physical_mac", |
||||
"machine_id", |
||||
"hostname" |
||||
] |
||||
}, |
||||
"peripherals": [ |
||||
{ |
||||
"name": "E1942", |
||||
"device_class": "Monitor", |
||||
"manufacturer": "GSM", |
||||
"connection": "display", |
||||
"device_role": "display", |
||||
"vid": null, |
||||
"pid": null, |
||||
"pnp_device_id": "DISPLAY\\GSM4C09\\5\u0026316DE597\u00260\u0026UID4865", |
||||
"device_serial": "211INNG5R639", |
||||
"edid_product": "GSM4C09", |
||||
"resolution": "1366x768", |
||||
"resolution_width": 1366, |
||||
"resolution_height": 768, |
||||
"refresh_hz": 60, |
||||
"status": "OK", |
||||
"is_external": true |
||||
}, |
||||
{ |
||||
"name": "USB Input Device", |
||||
"device_class": "HIDClass", |
||||
"manufacturer": "(Standard system devices)", |
||||
"connection": "usb", |
||||
"device_role": "peripheral", |
||||
"vid": "3151", |
||||
"pid": "3022", |
||||
"pnp_device_id": "USB\\VID_3151\u0026PID_3022\u0026MI_01\\8\u0026104DB858\u00260\u00260001", |
||||
"device_serial": null, |
||||
"edid_product": null, |
||||
"resolution": null, |
||||
"resolution_width": null, |
||||
"resolution_height": null, |
||||
"refresh_hz": null, |
||||
"status": "OK", |
||||
"is_external": true |
||||
}, |
||||
{ |
||||
"name": "USB Input Device", |
||||
"device_class": "HIDClass", |
||||
"manufacturer": "(Standard system devices)", |
||||
"connection": "usb", |
||||
"device_role": "peripheral", |
||||
"vid": "3151", |
||||
"pid": "3022", |
||||
"pnp_device_id": "USB\\VID_3151\u0026PID_3022\u0026MI_00\\8\u0026104DB858\u00260\u00260000", |
||||
"device_serial": null, |
||||
"edid_product": null, |
||||
"resolution": null, |
||||
"resolution_width": null, |
||||
"resolution_height": null, |
||||
"refresh_hz": null, |
||||
"status": "OK", |
||||
"is_external": true |
||||
}, |
||||
{ |
||||
"name": "HID-compliant consumer control device", |
||||
"device_class": "HIDClass", |
||||
"manufacturer": "Microsoft", |
||||
"connection": "usb_hid", |
||||
"device_role": "input", |
||||
"vid": "3151", |
||||
"pid": "3022", |
||||
"pnp_device_id": "HID\\VID_3151\u0026PID_3022\u0026MI_01\u0026COL03\\9\u00262CF1CDFF\u00260\u00260002", |
||||
"device_serial": null, |
||||
"edid_product": null, |
||||
"resolution": null, |
||||
"resolution_width": null, |
||||
"resolution_height": null, |
||||
"refresh_hz": null, |
||||
"status": "OK", |
||||
"is_external": true |
||||
}, |
||||
{ |
||||
"name": "HID-compliant system controller", |
||||
"device_class": "HIDClass", |
||||
"manufacturer": "(Standard system devices)", |
||||
"connection": "usb_hid", |
||||
"device_role": "input", |
||||
"vid": "3151", |
||||
"pid": "3022", |
||||
"pnp_device_id": "HID\\VID_3151\u0026PID_3022\u0026MI_01\u0026COL02\\9\u00262CF1CDFF\u00260\u00260001", |
||||
"device_serial": null, |
||||
"edid_product": null, |
||||
"resolution": null, |
||||
"resolution_width": null, |
||||
"resolution_height": null, |
||||
"refresh_hz": null, |
||||
"status": "OK", |
||||
"is_external": true |
||||
}, |
||||
{ |
||||
"name": "HID-compliant vendor-defined device", |
||||
"device_class": "HIDClass", |
||||
"manufacturer": "(Standard system devices)", |
||||
"connection": "usb_hid", |
||||
"device_role": "input", |
||||
"vid": "3151", |
||||
"pid": "3022", |
||||
"pnp_device_id": "HID\\VID_3151\u0026PID_3022\u0026MI_01\u0026COL04\\9\u00262CF1CDFF\u00260\u00260003", |
||||
"device_serial": null, |
||||
"edid_product": null, |
||||
"resolution": null, |
||||
"resolution_width": null, |
||||
"resolution_height": null, |
||||
"refresh_hz": null, |
||||
"status": "OK", |
||||
"is_external": true |
||||
}, |
||||
{ |
||||
"name": "HID Keyboard Device", |
||||
"device_class": "Keyboard", |
||||
"manufacturer": "(Standard keyboards)", |
||||
"connection": "usb_hid", |
||||
"device_role": "input", |
||||
"vid": "3151", |
||||
"pid": "3022", |
||||
"pnp_device_id": "HID\\VID_3151\u0026PID_3022\u0026MI_00\\9\u002638016031\u00260\u00260000", |
||||
"device_serial": null, |
||||
"edid_product": null, |
||||
"resolution": null, |
||||
"resolution_width": null, |
||||
"resolution_height": null, |
||||
"refresh_hz": null, |
||||
"status": "OK", |
||||
"is_external": true |
||||
}, |
||||
{ |
||||
"name": "HID-compliant mouse", |
||||
"device_class": "Mouse", |
||||
"manufacturer": "Microsoft", |
||||
"connection": "usb_hid", |
||||
"device_role": "input", |
||||
"vid": "3151", |
||||
"pid": "3022", |
||||
"pnp_device_id": "HID\\VID_3151\u0026PID_3022\u0026MI_01\u0026COL01\\9\u00262CF1CDFF\u00260\u00260000", |
||||
"device_serial": null, |
||||
"edid_product": null, |
||||
"resolution": null, |
||||
"resolution_width": null, |
||||
"resolution_height": null, |
||||
"refresh_hz": null, |
||||
"status": "OK", |
||||
"is_external": true |
||||
} |
||||
], |
||||
"health": { |
||||
"bsod_count_30d": 6, |
||||
"pending_reboot": false, |
||||
"last_patch_installed": "2026-02-27" |
||||
}, |
||||
"software": [ |
||||
|
||||
], |
||||
"metadata": { |
||||
"collect_mode": "phase0-local", |
||||
"software_included": false, |
||||
"software_count": 0 |
||||
} |
||||
} |
||||
Loading…
Reference in new issue