Files
usmt-dash/Demo/New-DemoData.ps1
Johan Sandoval 41ff9e2b49 Demo: migrations follow the schedule, worked by a simulated team
Each device now migrates on the day of its date tag instead of on a
looping 24-hour clock: earlier days are finished, today's devices go
through backup and restore during working hours, later days haven't
started. The few that don't finish have a cause the dashboard shows
(broken client, broken WMI, low disk, a USMT error, unreachable PC).

A simulated team keeps two weeks of working days tagged. During working
hours it adds devices, reschedules or drops one, tags stuck PCs with
their problem, then fixes them and moves them to a new day; each morning
finished days are closed out and a new day is scheduled. Actions print
in the worker terminal as [Team] lines. Settings live in Demo.Team.

The visitor sandbox now keeps per-tag edits laid over the live roster,
so the team's changes keep showing around a visitor's own edits.

Overview calendar: today is a filled badge on the date number, so it no
longer looks like the selected day (outline plus tint).
2026-10-07 20:41:07 +00:00

131 lines
6.7 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. Schedule: the last few working days (mostly finished) and the next two weeks ---
$team = $config.Demo.Team
$today = (Get-Date).Date
$days = @(@(1..[int]$team.DaysKept | ForEach-Object { Get-DemoWorkday $today (-$_) }) | Sort-Object) + @(0..([int]$team.DaysAhead - 1) | ForEach-Object { Get-DemoWorkday $today $_ })
$tags = [ordered]@{}
foreach ($d in $days) { $tags[(ConvertTo-DemoDateTag $d)] = [System.Collections.Generic.List[string]]::new() }
$dateTags = @($tags.Keys)
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)) }
}
# Problems that have already shown up on earlier days are tagged, as the team would have
Set-DemoSchedule $tags @() -Pin
foreach ($d in $pool) {
$kind = Get-DemoStuck $d
if (-not $kind) { continue }
$t = $script:DemoIssueTags[$kind]
if (-not $tags.Contains($t)) { $tags[$t] = [System.Collections.Generic.List[string]]::new() }
$tags[$t].Add($d)
}
$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
# The simulated team picks up from here (see Invoke-DemoTeam in Providers/Demo.ps1)
$teamState = [ordered]@{ Day = $today.ToString("yyyy-MM-dd"); NextAction = (Get-Date).AddMinutes(2).ToString("o"); Serial = 0; Fixed = @(); Moves = @{} }
[System.IO.File]::WriteAllText((Join-Path $dataDir "Demo-Team.json"), ($teamState | ConvertTo-Json), [System.Text.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