Remove all environment-specific data and make the dashboard adoptable anywhere, then add a demo that runs the real PowerShell services. - Strip cached device data, internal knowledge-base articles and screenshots, server/site names and the old project notes; replace the knowledge base with generic sample articles and diagrams - Move every environment value into Config/usmt-dash.psd1 - Route all infrastructure access in the worker and Get-Software.ps1 through a provider (Providers/SCCM.ps1), with a config-driven asset inventory view instead of a hard-coded custom class - Add Providers/Demo.ps1: a deterministic simulated fleet whose migrations progress in real time, and Demo/New-DemoData.ps1 to seed it - Server: -Demo makes the API read-only; add get-config and get-logs; serve static files only from web/, Documentation/ and an allowlist of Data/ files with a path-traversal guard; gzip large responses - Mirror both services' console output to Data/Logs via a Write-Host proxy, and add a terminal panel to the dashboard that streams it - Demo sandbox (web/demo/demo.js): visitor changes to tags, articles and images are kept in the visitor's browser and echoed in the terminals; nothing a visitor does reaches the server - Vendor marked, highlight.js and html2pdf.js instead of loading CDNs - Dockerfile and compose file for hosting the demo (Coolify-ready)
123 lines
6.3 KiB
PowerShell
123 lines
6.3 KiB
PowerShell
# Seeds Data\ with a simulated fleet for demo mode: tracking tags (Tracked-Devices.json),
|
|
# the app blacklist, and a pre-warmed software cache so the first worker cycle is quick.
|
|
# Run before starting the services. -Force wipes any existing state first.
|
|
param(
|
|
[switch]$Force,
|
|
[string]$ConfigPath
|
|
)
|
|
|
|
$root = Split-Path $PSScriptRoot -Parent
|
|
. (Join-Path $root "Lib/Common.ps1")
|
|
$config = Import-DashConfig -Path $ConfigPath -Demo
|
|
$v = $false
|
|
. (Join-Path $root "Providers/Demo.ps1")
|
|
. (Join-Path $root "Lib/Common.ps1") # re-import so nothing in the provider shadows the helpers
|
|
|
|
$dataDir = Join-Path $root "Data"
|
|
if ($Force -and (Test-Path $dataDir)) {
|
|
Get-ChildItem $dataDir -Recurse -File | Remove-Item -Force
|
|
}
|
|
if (-not (Test-Path $dataDir)) { New-Item -ItemType Directory -Path $dataDir | Out-Null }
|
|
|
|
$trackedPath = Join-Path $dataDir "Tracked-Devices.json"
|
|
if ((Test-Path $trackedPath) -and -not $Force) {
|
|
Write-Host "Demo data already present (use -Force to reseed)." -ForegroundColor DarkGray
|
|
return
|
|
}
|
|
|
|
$count = [int]$config.Demo.DeviceCount
|
|
Write-Host "Seeding demo fleet ($count tracked devices)..." -ForegroundColor Cyan
|
|
|
|
# --- 1. Generate source devices ---
|
|
function New-FleetName([int]$i, [string]$prefix) { return "$prefix-$(New-DemoSerial "fleet$i")" }
|
|
|
|
$skipPublic = [System.Collections.Generic.List[string]]::new()
|
|
$skipLaptops = [System.Collections.Generic.List[string]]::new()
|
|
$pool = [System.Collections.Generic.List[string]]::new()
|
|
$i = 0
|
|
while ($skipPublic.Count -lt 14) { $skipPublic.Add((New-FleetName ($i++) "PUBDT")) }
|
|
while ($skipLaptops.Count -lt 16) { $skipLaptops.Add((New-FleetName ($i++) (Get-DemoPick "lt$i" 'p' @("PRVLT", "PRVLT", "PRVLT", "PUBLT")))) }
|
|
while ($pool.Count -lt ($count - $skipPublic.Count - $skipLaptops.Count - 3)) {
|
|
$r = Get-DemoRand "mix$i" 'prefix'
|
|
$prefix = if ($r -lt 0.55) { "PRVDT" } elseif ($r -lt 0.80) { "PUBDT" } elseif ($r -lt 0.95) { "PRVLT" } else { "PUBLT" }
|
|
$pool.Add((New-FleetName ($i++) $prefix))
|
|
}
|
|
|
|
# Manual "Source, Destination" pairs - hardware exceptions with no SCCM association
|
|
$manual = @()
|
|
for ($m = 0; $m -lt 3; $m++) {
|
|
$src = New-FleetName ($i++) "PRVDT"
|
|
$dst = New-FleetName ($i++) "PRVDT"
|
|
$manual += "$src, $dst"
|
|
}
|
|
|
|
# --- 2. Date-batch tags (recent and upcoming working days) ---
|
|
function Get-Workday([datetime]$from, [int]$offset) {
|
|
$d = $from.Date; $step = [math]::Sign($offset); $left = [math]::Abs($offset)
|
|
while ($left -gt 0) { $d = $d.AddDays($step); if ($d.DayOfWeek -ne 'Saturday' -and $d.DayOfWeek -ne 'Sunday') { $left-- } }
|
|
return $d
|
|
}
|
|
$today = Get-Date
|
|
$dateTags = @(-4, -3, -2, -1, 0, 1) | ForEach-Object { (Get-Workday $today $_).ToString("M/d") }
|
|
|
|
$tags = [ordered]@{}
|
|
foreach ($t in $dateTags) { $tags[$t] = [System.Collections.Generic.List[string]]::new() }
|
|
for ($n = 0; $n -lt $pool.Count; $n++) { $tags[$dateTags[$n % $dateTags.Count]].Add($pool[$n]) }
|
|
$tags["Manual Pairs"] = [System.Collections.Generic.List[string]]::new([string[]]$manual)
|
|
|
|
# Department tags overlap the date batches, like a tech grouping their own work
|
|
$byDept = @{}
|
|
foreach ($d in $pool) {
|
|
$dep = (Get-DemoProfile $d).Department.Name
|
|
if (-not $byDept.ContainsKey($dep)) { $byDept[$dep] = [System.Collections.Generic.List[string]]::new() }
|
|
$byDept[$dep].Add($d)
|
|
}
|
|
foreach ($pair in @(@("Accounting", "Accounting"), @("Sales Floor", "Sales"), @("Exec Suite", "Executive"))) {
|
|
if ($byDept.ContainsKey($pair[1])) { $tags[$pair[0]] = [System.Collections.Generic.List[string]]::new([string[]]@($byDept[$pair[1]] | Select-Object -First 12)) }
|
|
}
|
|
$tags["Broken WMI"] = [System.Collections.Generic.List[string]]::new([string[]]@($pool | Where-Object { (Get-DemoRand $_ 'wmi') -lt 0.03 } | Select-Object -First 5))
|
|
$tags["Public PCs"] = $skipPublic
|
|
$tags["Laptops"] = $skipLaptops
|
|
|
|
$skipTags = @("Public PCs", "Laptops")
|
|
$tracked = [ordered]@{}
|
|
foreach ($t in $tags.Keys) {
|
|
$tracked[$t] = [ordered]@{ devices = @($tags[$t]); skipUSMT = ($skipTags -contains $t) }
|
|
}
|
|
($tracked | ConvertTo-Json -Depth 5) | Set-Content -Path $trackedPath -Encoding UTF8
|
|
|
|
# --- 3. Blacklist / suite rules ---
|
|
Copy-Item (Join-Path $PSScriptRoot "Blacklisted-Apps.txt") (Join-Path $dataDir "Blacklisted-Apps.txt") -Force
|
|
$blacklist = @(Get-Content (Join-Path $dataDir "Blacklisted-Apps.txt") | Where-Object { $_.Trim() -ne "" } | ForEach-Object { $_.Trim() })
|
|
|
|
# --- 4. Pre-warmed software cache (the worker would otherwise build it one device at a time) ---
|
|
$allDevices = [System.Collections.Generic.List[string]]::new()
|
|
foreach ($d in ($pool + $skipPublic + $skipLaptops)) {
|
|
$allDevices.Add($d)
|
|
$p = Get-DemoProfile $d
|
|
if (-not $p.NoAssoc -and -not ($skipPublic -contains $d) -and -not ($skipLaptops -contains $d)) { $allDevices.Add((Get-DemoDestName $d)) }
|
|
}
|
|
foreach ($pair in $manual) { $allDevices.AddRange([string[]]@($pair -split '\s*,\s*')) }
|
|
|
|
$stamp = (Get-Date).ToString("o")
|
|
$cache = [ordered]@{}
|
|
foreach ($d in $allDevices) {
|
|
$rows = foreach ($row in (Get-InstalledSoftware -ComputerName $d)) {
|
|
$isSpecialty = $true
|
|
foreach ($bad in $blacklist) { if ($row.Name -like $bad) { $isSpecialty = $false; break } }
|
|
[PSCustomObject]@{ Name = $row.Name; Version = $row.Version; Publisher = $row.Publisher; Source = $row.Source; InstallDate = $row.InstallDate; IsSpecialty = $isSpecialty }
|
|
}
|
|
$cache[$d] = [PSCustomObject]@{ Timestamp = $stamp; Data = @($rows) }
|
|
}
|
|
[System.IO.File]::WriteAllText((Join-Path $dataDir "Software-Cache.json"), ($cache | ConvertTo-Json -Depth 6 -Compress), [System.Text.Encoding]::UTF8)
|
|
|
|
# --- 5. Empty state for the rest ---
|
|
Set-Content -Path (Join-Path $dataDir "Completed.json") -Value "[]" -Encoding UTF8
|
|
Set-Content -Path (Join-Path $dataDir "Data.json") -Value "[]" -Encoding UTF8
|
|
Set-Content -Path (Join-Path $dataDir "Advanced-Cache.json") -Value "{}" -Encoding UTF8
|
|
Set-Content -Path (Join-Path $dataDir "Worker-State.json") -Value '{"Status":"Preparing...","Current":0,"Total":0,"LastUpdated":"Never","NextCycle":"Calculating...","LastDuration":"N/A"}' -Encoding UTF8
|
|
Set-Content -Path (Join-Path $dataDir "Whitelisted-IPs.txt") -Value "" -Encoding UTF8
|
|
|
|
$unique = @($tracked.Values | ForEach-Object { $_.devices } | ForEach-Object { $_ }) | Select-Object -Unique
|
|
Write-Host "Seeded $($tracked.Count) tags, $(@($unique).Count) roster entries, $($cache.Count) software inventories." -ForegroundColor Green
|