Generalize USMT Dash and add a containerized demo mode
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)
This commit is contained in:
commit
1c531bc2ab
56 files changed
+10232
No files matched your search
Executable
+140
@@ -0,0 +1,140 @@
|
||||
param(
|
||||
[string]$ComputerName,
|
||||
[string]$ConfigPath,
|
||||
[switch]$Demo
|
||||
)
|
||||
|
||||
if ([string]::IsNullOrWhiteSpace($ComputerName)) { return "[]" }
|
||||
|
||||
$scriptDir = $PSScriptRoot
|
||||
$dataDir = Join-Path $scriptDir "Data"
|
||||
|
||||
if (-not (Test-Path $dataDir)) { New-Item -ItemType Directory -Path $dataDir | Out-Null }
|
||||
$cacheFile = Join-Path $dataDir "Software-Cache.json"
|
||||
$blacklistFile = Join-Path $dataDir "Blacklisted-Apps.txt"
|
||||
|
||||
if (-not (Test-Path $cacheFile)) { Set-Content -Path $cacheFile -Value "{}" -Encoding UTF8 }
|
||||
if (-not (Test-Path $blacklistFile)) { Set-Content -Path $blacklistFile -Value "" -Encoding UTF8 }
|
||||
|
||||
. (Join-Path $scriptDir "Lib/Common.ps1")
|
||||
$config = Import-DashConfig -Path $ConfigPath -Demo:$Demo
|
||||
$cacheHours = $config.Worker.SoftwareCacheHours
|
||||
|
||||
# --- 1. CHECK THE CACHE (24-Hour Timer Only) ---
|
||||
$needsUpdate = $true
|
||||
if (Test-Path $cacheFile) {
|
||||
try {
|
||||
$rawCache = Read-SharedText $cacheFile
|
||||
|
||||
if (-not [string]::IsNullOrWhiteSpace($rawCache)) {
|
||||
$cacheData = $rawCache | ConvertFrom-Json
|
||||
|
||||
$propExists = $false
|
||||
foreach ($p in $cacheData.psobject.properties) {
|
||||
if ($p.Name -eq $ComputerName) { $propExists = $true; break }
|
||||
}
|
||||
|
||||
if ($propExists) {
|
||||
$deviceCache = $cacheData.$ComputerName
|
||||
if ($null -ne $deviceCache.Timestamp -and $null -ne $deviceCache.Data) {
|
||||
$cacheTime = [datetime]$deviceCache.Timestamp
|
||||
|
||||
# Logic Check: Is cache still fresh?
|
||||
if ($cacheTime -gt (Get-Date).AddHours(-1 * $cacheHours)) {
|
||||
$needsUpdate = $false
|
||||
$outData = @($deviceCache.Data)
|
||||
return (ConvertTo-Json -InputObject $outData -Depth 5 -Compress)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
# If cache is corrupt, proceed to the provider
|
||||
}
|
||||
}
|
||||
|
||||
if (-not $needsUpdate) { return }
|
||||
|
||||
# --- 2. LOAD BLACKLIST ---
|
||||
$blacklist = @()
|
||||
if (Test-Path $blacklistFile) {
|
||||
$blacklist = Get-Content $blacklistFile | Where-Object { $_.Trim() -ne "" } | ForEach-Object { $_.Trim() }
|
||||
}
|
||||
|
||||
# --- 3. PROVIDER QUERY (SCCM SQL in Live mode, simulated fleet in Demo mode) ---
|
||||
$providerName = if ($config.IsDemo) { "Demo" } else { $config.Provider }
|
||||
$v = $false
|
||||
. (Join-Path $scriptDir "Providers/$providerName.ps1")
|
||||
|
||||
$results = [System.Collections.Generic.List[PSCustomObject]]::new()
|
||||
|
||||
try {
|
||||
foreach ($row in (Get-InstalledSoftware -ComputerName $ComputerName)) {
|
||||
$appName = "$($row.Name)".Trim()
|
||||
|
||||
if ([string]::IsNullOrWhiteSpace($appName) -or $appName -eq "--") { continue }
|
||||
|
||||
$isSpecialty = $true
|
||||
foreach ($badApp in $blacklist) {
|
||||
if ($appName -like $badApp) {
|
||||
$isSpecialty = $false
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
$results.Add([PSCustomObject]@{
|
||||
Name = $appName
|
||||
Version = $row.Version
|
||||
Publisher = $row.Publisher
|
||||
Source = $row.Source
|
||||
InstallDate = $row.InstallDate
|
||||
IsSpecialty = $isSpecialty
|
||||
})
|
||||
}
|
||||
} catch {}
|
||||
|
||||
# --- 4. ATOMIC CACHE SAVE ---
|
||||
try {
|
||||
$cacheEntry = [PSCustomObject]@{
|
||||
Timestamp = (Get-Date).ToString("o")
|
||||
Data = @($results)
|
||||
}
|
||||
|
||||
for ($i = 0; $i -lt 10; $i++) {
|
||||
try {
|
||||
$fs = [System.IO.File]::Open($cacheFile, [System.IO.FileMode]::OpenOrCreate, [System.IO.FileAccess]::ReadWrite, [System.IO.FileShare]::None)
|
||||
$sr = New-Object System.IO.StreamReader($fs)
|
||||
$rawCache = $sr.ReadToEnd()
|
||||
|
||||
$latestCache = [PSCustomObject]@{}
|
||||
if (-not [string]::IsNullOrWhiteSpace($rawCache)) {
|
||||
try { $latestCache = $rawCache | ConvertFrom-Json } catch {}
|
||||
}
|
||||
|
||||
$propExists = $false
|
||||
foreach ($p in $latestCache.psobject.properties) {
|
||||
if ($p.Name -eq $ComputerName) { $propExists = $true; break }
|
||||
}
|
||||
|
||||
if ($propExists) {
|
||||
$latestCache.$ComputerName = $cacheEntry
|
||||
} else {
|
||||
$latestCache | Add-Member -MemberType NoteProperty -Name $ComputerName -Value $cacheEntry -Force
|
||||
}
|
||||
|
||||
$fs.SetLength(0)
|
||||
$sw = New-Object System.IO.StreamWriter($fs, [System.Text.Encoding]::UTF8)
|
||||
$sw.Write(($latestCache | ConvertTo-Json -Depth 6 -Compress))
|
||||
$sw.Flush()
|
||||
$sw.Close()
|
||||
$sr.Close()
|
||||
$fs.Close()
|
||||
break
|
||||
} catch {
|
||||
Start-Sleep -Milliseconds 300
|
||||
}
|
||||
}
|
||||
} catch {}
|
||||
|
||||
$outData = @($results)
|
||||
return (ConvertTo-Json -InputObject $outData -Depth 5 -Compress)
|
||||
Reference in new issue
Block a user