From 1c531bc2ab5722b2188cf0dcf3aeb2053900e73f Mon Sep 17 00:00:00 2001 From: Johan Sandoval Date: Tue, 6 Oct 2026 15:20:43 +0000 Subject: [PATCH] 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) --- .dockerignore | 5 + .gitignore | 3 + Config/usmt-dash.psd1 | 121 + Data/.gitkeep | 0 Demo/Blacklisted-Apps.txt | 38 + Demo/New-DemoData.ps1 | 122 + Dockerfile | 34 + Documentation/Adobe Acrobat Pro.md | 26 + Documentation/Adobe Creative Cloud.md | 21 + Documentation/Archive/Legacy VPN Client.md | 7 + Documentation/Create Bootable USB.md | 22 + Documentation/Device Storage Remediation.md | 31 + Documentation/Diskpart Steps.md | 26 + Documentation/Manually Start USMT Backup.md | 16 + Documentation/Microsoft Office.md | 25 + Documentation/Microsoft Power BI.md | 12 + Documentation/Microsoft Project.md | 12 + Documentation/Microsoft Teams.md | 21 + Documentation/Microsoft Visio.md | 14 + Documentation/Notepad++.md | 9 + Documentation/QuickBooks.md | 23 + Documentation/Salesforce.md | 23 + Documentation/Slack.md | 11 + Documentation/Tableau.md | 13 + Documentation/Using USMT Dash.md | 36 + Documentation/WMI Namespace Fix.md | 31 + Documentation/Zoom.md | 12 + Documentation/images/Migration Lifecycle.png | Bin 0 -> 26621 bytes .../images/USMT Dash Architecture.png | Bin 0 -> 32612 bytes Get-Software.ps1 | 140 + Lib/Common.ps1 | 129 + Providers/Demo.ps1 | 499 ++ Providers/SCCM.ps1 | 448 ++ README.md | 201 + USMT-Server.ps1 | 517 ++ USMT-Worker.ps1 | 590 +++ docker-compose.yml | 13 + docker/start.sh | 23 + docs/screenshots/device-details.png | Bin 0 -> 70730 bytes docs/screenshots/documentation.png | Bin 0 -> 64391 bytes docs/screenshots/missing-software.png | Bin 0 -> 86251 bytes docs/screenshots/terminals.png | Bin 0 -> 70958 bytes docs/screenshots/usmt-status.png | Bin 0 -> 97311 bytes web/demo/demo.css | 108 + web/demo/demo.js | 971 ++++ web/favicon-bg.png | Bin 0 -> 263385 bytes web/favicon.png | Bin 0 -> 161419 bytes web/index.html | 4460 +++++++++++++++++ web/vendor/highlight/LICENSE | 29 + web/vendor/highlight/highlight.min.js | 1213 +++++ .../highlight/languages/powershell.min.js | 39 + .../highlight/styles/atom-one-dark.min.css | 1 + web/vendor/html2pdf/LICENSE | 21 + web/vendor/html2pdf/html2pdf.bundle.min.js | 3 + web/vendor/marked/LICENSE.md | 44 + web/vendor/marked/marked.min.js | 69 + 56 files changed, 10232 insertions(+) create mode 100644 .dockerignore create mode 100644 .gitignore create mode 100644 Config/usmt-dash.psd1 create mode 100644 Data/.gitkeep create mode 100644 Demo/Blacklisted-Apps.txt create mode 100644 Demo/New-DemoData.ps1 create mode 100644 Dockerfile create mode 100644 Documentation/Adobe Acrobat Pro.md create mode 100644 Documentation/Adobe Creative Cloud.md create mode 100644 Documentation/Archive/Legacy VPN Client.md create mode 100644 Documentation/Create Bootable USB.md create mode 100644 Documentation/Device Storage Remediation.md create mode 100644 Documentation/Diskpart Steps.md create mode 100644 Documentation/Manually Start USMT Backup.md create mode 100644 Documentation/Microsoft Office.md create mode 100644 Documentation/Microsoft Power BI.md create mode 100644 Documentation/Microsoft Project.md create mode 100644 Documentation/Microsoft Teams.md create mode 100644 Documentation/Microsoft Visio.md create mode 100644 Documentation/Notepad++.md create mode 100644 Documentation/QuickBooks.md create mode 100644 Documentation/Salesforce.md create mode 100644 Documentation/Slack.md create mode 100644 Documentation/Tableau.md create mode 100644 Documentation/Using USMT Dash.md create mode 100644 Documentation/WMI Namespace Fix.md create mode 100644 Documentation/Zoom.md create mode 100644 Documentation/images/Migration Lifecycle.png create mode 100644 Documentation/images/USMT Dash Architecture.png create mode 100755 Get-Software.ps1 create mode 100644 Lib/Common.ps1 create mode 100644 Providers/Demo.ps1 create mode 100644 Providers/SCCM.ps1 create mode 100644 README.md create mode 100755 USMT-Server.ps1 create mode 100755 USMT-Worker.ps1 create mode 100644 docker-compose.yml create mode 100755 docker/start.sh create mode 100644 docs/screenshots/device-details.png create mode 100644 docs/screenshots/documentation.png create mode 100644 docs/screenshots/missing-software.png create mode 100644 docs/screenshots/terminals.png create mode 100644 docs/screenshots/usmt-status.png create mode 100644 web/demo/demo.css create mode 100644 web/demo/demo.js create mode 100755 web/favicon-bg.png create mode 100755 web/favicon.png create mode 100755 web/index.html create mode 100644 web/vendor/highlight/LICENSE create mode 100644 web/vendor/highlight/highlight.min.js create mode 100644 web/vendor/highlight/languages/powershell.min.js create mode 100644 web/vendor/highlight/styles/atom-one-dark.min.css create mode 100644 web/vendor/html2pdf/LICENSE create mode 100644 web/vendor/html2pdf/html2pdf.bundle.min.js create mode 100644 web/vendor/marked/LICENSE.md create mode 100644 web/vendor/marked/marked.min.js diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..8b0c3c2 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,5 @@ +.git +Data/* +!Data/.gitkeep +*.md.bak +docker-compose.yml diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..fa4bf9f --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +# Runtime state written by the worker and server +Data/* +!Data/.gitkeep diff --git a/Config/usmt-dash.psd1 b/Config/usmt-dash.psd1 new file mode 100644 index 0000000..a86a4d5 --- /dev/null +++ b/Config/usmt-dash.psd1 @@ -0,0 +1,121 @@ +@{ + # ===================================================================================== + # USMT Dash configuration + # + # Every environment-specific value lives in this file. To adopt the dashboard in your + # own environment, copy this file, fill in the SCCM section, and run the two services: + # + # .\USMT-Worker.ps1 # data collector (keep running) + # .\USMT-Server.ps1 # web server + JSON API (keep running) + # + # Pass -Demo to either script (or set Mode = 'Demo', or env USMT_MODE=Demo) to run + # against the built-in simulated fleet instead of real infrastructure. + # Pass -ConfigPath to point at a different config file. + # ===================================================================================== + + # 'Live' = query real infrastructure through the provider below. + # 'Demo' = simulated fleet, read-only API, visitor changes stay in the browser. + Mode = 'Live' + + # Data source adapter used in Live mode. Loaded from .\Providers\.ps1 + Provider = 'SCCM' + + Server = @{ + Port = 8080 + # HttpListener prefix. '*' binds every interface (may need a URL ACL on Windows: + # netsh http add urlacl url=http://*:8080/ user=DOMAIN\svc-account) + Prefix = 'http://*:{0}/' + } + + Worker = @{ + # Seconds between cycles during business hours + CycleSeconds = 180 + + # Outside business hours the worker sleeps until the next morning, with a + # midnight "pitstop" that runs the daily software/advanced-info refresh. + BusinessHours = @{ + Enabled = $true + Start = '07:30' + End = '17:30' + Weekdays = @(1, 2, 3, 4, 5) # 0 = Sunday ... 6 = Saturday + } + + # Flush the local DNS client cache at the start of each cycle (Windows only) + FlushDnsEachCycle = $true + + # Devices whose admin share hangs; disk and log checks are skipped for these + DiskSkipList = @() + + # How long a cached software inventory is considered fresh + SoftwareCacheHours = 24 + + # A task-sequence log modified within this window counts as "active" + ActiveLogHours = 24 + } + + # ------------------------------------------------------------------------------------- + # SCCM / ConfigMgr provider (.\Providers\SCCM.ps1) + # ------------------------------------------------------------------------------------- + SCCM = @{ + SiteCode = 'ABC' + ProviderMachineName = 'sccm01.corp.example.com' # SMS Provider for the CMSite PSDrive + SqlServer = 'sccm01.corp.example.com' # Site database server + Database = 'CM_ABC' + + # Where the worker looks for USMT progress logs on each device (relative to the + # admin share below). First folder containing a matching log wins. + AdminShare = 'C$' + LogFolders = @('Windows\CCM\Logs\SMSTSLog', 'Windows\CCM\Logs') + ScanStateLog = 'scanstateprogress' + LoadStateLog = 'loadstateprogress' + + # Many organizations extend hardware inventory with a custom class that holds + # asset/location details (room, department, mount type, ...). Map that class here. + # Set Enabled = $false if you don't have one - those fields will show "N/A". + AssetInventory = @{ + Enabled = $true + View = 'v_GS_ASSET_INFO0' # primary inventory view + FallbackView = '' # optional older/secondary view, queried only if the primary has no row + # Dashboard field = column in the view + Columns = @{ + Updated = 'Date_Updated0' + Location = 'Location0' + Vanity = 'VanityLocation0' + Department = 'Department0' + Building = 'Building0' + Mount = 'Mount_Type0' + PrimaryUser = 'Primary_User0' + PhoneExt = 'Phone_Extension0' + InfoBy = 'Info_Entered_By0' + Notes = 'Notes0' + } + # Boolean (1/0) columns that describe what kind of space the device is in. + # Any column set to 1 adds its label to the "Room Type" field. + RoomTypes = @( + @{ Label = 'Conference Room'; Column = 'ConferenceRoom0' } + @{ Label = 'Hoteling Desk'; Column = 'HotelingDesk0' } + @{ Label = 'Training Room'; Column = 'TrainingRoom0' } + @{ Label = 'Reception'; Column = 'Reception0' } + @{ Label = 'Shared Space'; Column = 'SharedSpace0' } + ) + } + } + + # ------------------------------------------------------------------------------------- + # Demo provider (.\Providers\Demo.ps1) - simulated fleet used by the public demo + # ------------------------------------------------------------------------------------- + Demo = @{ + DeviceCount = 200 # tracked source devices seeded at startup + CycleSeconds = 45 # cooldown between cycles (business hours are ignored) + DelayMsPerDevice = 35 # artificial per-device latency so the progress bar moves + LifecycleHours = 24 # how long one simulated migration takes, start to finish + Verbose = $true # run the worker with -v style tracing + } + + # Console output from both services is mirrored to Data\Logs\*.log as JSON lines so the + # dashboard's terminal panel can stream it. + Logging = @{ + Enabled = $true + MaxLines = 3000 + } +} diff --git a/Data/.gitkeep b/Data/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/Demo/Blacklisted-Apps.txt b/Demo/Blacklisted-Apps.txt new file mode 100644 index 0000000..414ba39 --- /dev/null +++ b/Demo/Blacklisted-Apps.txt @@ -0,0 +1,38 @@ +Microsoft 365 Apps for enterprise * +Microsoft Edge +Microsoft Edge WebView2 Runtime +Microsoft OneDrive +Microsoft Teams* +Microsoft Visual C++ * +Microsoft Intune Management Extension +Microsoft Policy Platform +Microsoft .NET * +Microsoft Windows Desktop Runtime * +Configuration Manager Client +Google Chrome +Adobe Acrobat Reader* +7-Zip * +Endpoint Protection Agent +Intel(R) Management Engine Components +Realtek Audio Driver +Windows Driver Package * +HP Support Assistant +Dell Command | Update +Lenovo Vantage Service +Cisco Secure Client* +Adobe Acrobat Pro = Adobe Acrobat (64-bit), Adobe Acrobat DC* +QuickBooks = QuickBooks Desktop*, QuickBooks Database Server Manager*, Intuit Data Protect* +Salesforce = Salesforce for Outlook*, Salesforce Data Loader* +Adobe Creative Cloud = Adobe Creative Cloud, Adobe Photoshop*, Adobe Illustrator*, Adobe InDesign* +Microsoft SQL Server Management Studio = SQL Server Management Studio*, SSMS Post Install Tasks* +Microsoft Power BI = Microsoft Power BI Desktop*, Power Pivot for Excel* +Microsoft Visio = Microsoft Visio* +Microsoft Project = Microsoft Project* +Python = Python 3.*, Python Launcher* +Wireshark = Wireshark*, Npcap* +Zoom = Zoom Workplace*, Zoom* +Slack = Slack* +Tableau = Tableau 20* +Notepad++ = Notepad++* +Rule: PUB* = Kiosk Launcher*, Shared PC Cleanup Utility* +Combo: Finance Reporting Bundle = QuickBooks Desktop* && Microsoft Power BI Desktop* diff --git a/Demo/New-DemoData.ps1 b/Demo/New-DemoData.ps1 new file mode 100644 index 0000000..d6c44f8 --- /dev/null +++ b/Demo/New-DemoData.ps1 @@ -0,0 +1,122 @@ +# 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 diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..6187388 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,34 @@ +# USMT Dash - demo container +# +# Runs the real USMT-Server.ps1 and USMT-Worker.ps1 under PowerShell 7 with -Demo, so the +# worker sweeps a simulated fleet instead of SCCM/AD/SMB. The web API is read-only in demo +# mode; visitor changes are simulated in each visitor's browser. +# +# docker build -t usmt-dash . +# docker run -p 8080:8080 usmt-dash +FROM mcr.microsoft.com/powershell:lts-ubuntu-22.04 + +ENV USMT_MODE=Demo \ + POWERSHELL_TELEMETRY_OPTOUT=1 \ + POWERSHELL_UPDATECHECK=Off \ + DOTNET_CLI_TELEMETRY_OPTOUT=1 \ + TZ=UTC + +WORKDIR /app +COPY . /app + +RUN apt-get update \ + && DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends tzdata \ + && rm -rf /var/lib/apt/lists/* \ + && useradd --system --home /app --shell /usr/sbin/nologin usmt \ + && mkdir -p /app/Data \ + && chown -R usmt:usmt /app/Data \ + && chmod +x /app/docker/start.sh + +USER usmt +EXPOSE 8080 + +HEALTHCHECK --interval=30s --timeout=10s --start-period=60s --retries=3 \ + CMD pwsh -NoProfile -Command "try { if ((Invoke-WebRequest -UseBasicParsing http://127.0.0.1:8080/get-config -TimeoutSec 5).StatusCode -eq 200) { exit 0 } } catch {}; exit 1" + +ENTRYPOINT ["/app/docker/start.sh"] diff --git a/Documentation/Adobe Acrobat Pro.md b/Documentation/Adobe Acrobat Pro.md new file mode 100644 index 0000000..a353d2c --- /dev/null +++ b/Documentation/Adobe Acrobat Pro.md @@ -0,0 +1,26 @@ +[[Blue:Licensed]] [[Gray:Named User]] + +# Adobe Acrobat Pro + +Acrobat Pro is licensed per user through the Adobe Admin Console. The installer is the same unified package as Acrobat Reader; signing in with a licensed account unlocks Pro features. + +## Install + +1. Confirm the user is assigned an Acrobat Pro license in the Adobe Admin Console (ask the license owner if not). +2. On the new device open **Software Center** and install **Adobe Acrobat (64-bit)**. + - Manual installer if Software Center is unavailable: [[Copy:\\fileserver\software\AcrobatPro\Setup.exe]] +3. Launch Acrobat and have the user sign in with their company account. +4. Verify **Help > About Adobe Acrobat** shows *Acrobat Pro*. + +## Common Issues + +**Still shows "Acrobat Reader" after sign-in** +1. Sign out (**Help > Sign Out**), close Acrobat, sign back in. +2. If it persists, the license assignment hasn't synced yet. Wait 15 minutes and repeat. + +**Default PDF handler reverted to Edge** +- Settings > Apps > Default apps > search `.pdf` > choose **Adobe Acrobat**. + +## Migration Notes +- USMT carries over Acrobat preferences, but **digital signature certificates** stored outside the user profile must be re-imported. +- Custom stamps live in `%AppData%\Adobe\Acrobat\DC\Stamps`. Confirm they came across. diff --git a/Documentation/Adobe Creative Cloud.md b/Documentation/Adobe Creative Cloud.md new file mode 100644 index 0000000..83cc30d --- /dev/null +++ b/Documentation/Adobe Creative Cloud.md @@ -0,0 +1,21 @@ +[[Blue:Licensed]] [[Yellow:Large Install]] + +# Adobe Creative Cloud + +Creative Cloud apps (Photoshop, Illustrator, InDesign, ...) are installed through the **Creative Cloud Desktop** app after the user signs in. + +## Install + +1. Install **Adobe Creative Cloud** from Software Center. +2. Have the user sign in with their company account. +3. From the **Apps** tab install only what was on the old device. The **All Devices** page in USMT Dash lists them under *Missing Software*. + +> Expect 5-20 GB per app. Check free space on the destination before starting. + +## Fonts and Presets +- Synced fonts return automatically after sign-in. +- Local presets, brushes and swatches are in `%AppData%\Adobe\`. USMT migrates these, but verify on first launch. + +## Troubleshooting +- **"You don't have access to manage apps"**: the user's license isn't assigned. Contact the license owner. +- **Install stuck at 0%**: sign out of Creative Cloud, delete `%LocalAppData%\Adobe\OOBE`, reboot and retry. diff --git a/Documentation/Archive/Legacy VPN Client.md b/Documentation/Archive/Legacy VPN Client.md new file mode 100644 index 0000000..25b8095 --- /dev/null +++ b/Documentation/Archive/Legacy VPN Client.md @@ -0,0 +1,7 @@ +[[Gray:Archived]] + +# Legacy VPN Client + +*Archived: replaced by Cisco Secure Client on all laptops.* + +The old VPN client must be **uninstalled** before installing Cisco Secure Client, or the two network filter drivers will conflict. diff --git a/Documentation/Create Bootable USB.md b/Documentation/Create Bootable USB.md new file mode 100644 index 0000000..21635b6 --- /dev/null +++ b/Documentation/Create Bootable USB.md @@ -0,0 +1,22 @@ +[[Gray:Reference]] + +# Create a Bootable Imaging USB + +Use this when PXE isn't available (remote site, isolated network, or a device that won't PXE boot). + +## You'll Need +- A USB drive of **32 GB or more** (it will be erased) +- Access to the ConfigMgr console + +## Steps +1. In the ConfigMgr console go to **Software Library > Operating Systems > Task Sequences**. +2. Right-click the deployment task sequence > **Create Task Sequence Media**. +3. Choose **Bootable media** > **Dynamic media**. +4. Select **Removable USB drive** and pick the drive. +5. Leave the media unprotected or set a password per team policy. +6. Select the current production **boot image** and distribution point. +7. Finish the wizard. Writing the media takes about 5-10 minutes. + +## Booting From It +- Press the vendor boot-menu key (**F9** HP, **F12** Dell/Lenovo) and choose the USB device (UEFI). +- If Secure Boot blocks the USB, confirm it was built from a signed boot image. diff --git a/Documentation/Device Storage Remediation.md b/Documentation/Device Storage Remediation.md new file mode 100644 index 0000000..8c440f0 --- /dev/null +++ b/Documentation/Device Storage Remediation.md @@ -0,0 +1,31 @@ +[[Yellow:Remediation]] + +# Device Storage Remediation + +USMT needs free space on the source device for its working files. Devices under about **15 GB free** often fail the backup. USMT Dash shows free space in the **USMT Status** table. + +## 1. Remove stale profiles +1. Press **Win**, type *About your PC*, open **Advanced system settings**. +2. Under **User Profiles** click **Settings**. +3. Delete profiles named *Account Unknown* and profiles of users who no longer use the device. **Never** delete the current user's profile. + +## 2. Clear temp folders +```powershell +Remove-Item "$env:WinDir\Temp\*" -Recurse -Force -ErrorAction SilentlyContinue +Remove-Item "C:\Temp\*" -Recurse -Force -ErrorAction SilentlyContinue +``` + +## 3. Clear the ConfigMgr cache +Control Panel > **Configuration Manager** > *Cache* tab > **Configure Settings** > **Delete Files** > *Delete persisted content*. + +## 4. Rebuild the search index (if Windows.edb is huge) +```powershell +Stop-Service WSearch -Force +Remove-Item "C:\ProgramData\Microsoft\Search\Data\Applications\Windows\Windows.edb" -Force +Start-Service WSearch +``` + +## 5. Disk Cleanup +Run `cleanmgr /sageset:1` once to tick every category, then `cleanmgr /sagerun:1`. + +After cleanup, use **Force Update** in USMT Dash (click the status button in the top bar) to refresh the free-space reading. diff --git a/Documentation/Diskpart Steps.md b/Documentation/Diskpart Steps.md new file mode 100644 index 0000000..d1f24f1 --- /dev/null +++ b/Documentation/Diskpart Steps.md @@ -0,0 +1,26 @@ +[[Red:Destructive]] + +# Diskpart: Wipe and Re-partition a Disk (UEFI/GPT) + +> **This erases the disk.** Only use it on a device whose backup is complete, or on new hardware. + +1. Boot into WinPE or the task sequence wizard and press **F8** for a command prompt. +2. Type `diskpart`. +3. Enter each line and press Enter after each: + +``` +list disk +select disk 0 +clean +convert gpt +create partition efi size=300 +format quick fs=fat32 label="System" +assign letter=S +create partition msr size=128 +create partition primary +format quick fs=ntfs label="Windows" +assign letter=C +exit +``` + +4. Close the prompt and restart the task sequence. diff --git a/Documentation/Manually Start USMT Backup.md b/Documentation/Manually Start USMT Backup.md new file mode 100644 index 0000000..5fd40a0 --- /dev/null +++ b/Documentation/Manually Start USMT Backup.md @@ -0,0 +1,16 @@ +[[Yellow:Remediation]] + +# Manually Start a USMT Backup + +Use this when a device in USMT Dash sits at **Not Started** while its replacement is ready. + +1. Find the device (the **Tracked Devices** or **USMT Status** page shows its last ping state). +2. Restart it. The USMT backup won't start while another user is signed in. Then sign in. +3. Open **Software Center**. + - Blank page or "failed to load"? Run [[Copy:C:\Windows\CCM\ccmrepair.exe]], wait for it to finish, then reopen Software Center. + - Still broken? Tag the device in USMT Dash (e.g. `Broken WMI`) and move on. See [WMI Namespace Fix](?doc=WMI%20Namespace%20Fix). +4. Run **USMT Backup** from the *Operating Systems* tab. + - Fails immediately? Run `gpupdate /force`, then Software Center > Options > **Sync Policy**. Wait a few minutes and retry. +5. When the button turns grey and shows *Installing...* the backup is running. You can sign out and move to the next device. + +In USMT Dash the row changes to **Backup In Progress** within one worker cycle, and the scanstate line shows progress. diff --git a/Documentation/Microsoft Office.md b/Documentation/Microsoft Office.md new file mode 100644 index 0000000..8366ec3 --- /dev/null +++ b/Documentation/Microsoft Office.md @@ -0,0 +1,25 @@ +[[Green:Baseline]] + +# Microsoft Office (Microsoft 365 Apps) + +Microsoft 365 Apps for enterprise is part of the standard image and is on every device. It's in the app blacklist, so it never shows up as missing software. + +## After a Migration +1. Open **Outlook** and confirm the mailbox loads. A new profile is created on first launch. +2. Open **Word** > **File > Account** and confirm *Product Activated* with the user's account. +3. Check **OneDrive** is signed in and syncing (blue cloud in the system tray). + +## Common Fixes + +**Activation errors / "Unlicensed Product"** +1. Close all Office apps. +2. Run [[Copy:Microsoft Support and Recovery Assistant|https://aka.ms/SaRA-officeUninstallFromPC]] or sign out of every account under **File > Account** and sign back in. + +**Outlook signatures missing** +- Signatures are stored in `%AppData%\Microsoft\Signatures`. USMT migrates the folder; if they're missing, copy it from the migration store. + +**Quick repair** +- Settings > Apps > Installed apps > Microsoft 365 Apps > Modify > **Quick Repair**. + +## Visio and Project +Visio and Project are licensed separately. See [Microsoft Visio](?doc=Microsoft%20Visio) and [Microsoft Project](?doc=Microsoft%20Project). diff --git a/Documentation/Microsoft Power BI.md b/Documentation/Microsoft Power BI.md new file mode 100644 index 0000000..a5fefd3 --- /dev/null +++ b/Documentation/Microsoft Power BI.md @@ -0,0 +1,12 @@ +[[Gray:Optional]] + +# Microsoft Power BI + +## Install +- Software Center > **Microsoft Power BI Desktop (x64)**. +- Sign in with the user's company account to publish to the Power BI service. + +## Notes +- `.pbix` reports are usually in OneDrive or SharePoint, so nothing to migrate locally. +- **Power Pivot for Excel** is grouped with Power BI in the software view. It's enabled from Excel > File > Options > Add-ins > COM Add-ins. +- Gateways (on-premises data gateway) are installed on servers, **never** on user workstations. diff --git a/Documentation/Microsoft Project.md b/Documentation/Microsoft Project.md new file mode 100644 index 0000000..3a43fb7 --- /dev/null +++ b/Documentation/Microsoft Project.md @@ -0,0 +1,12 @@ +[[Blue:Licensed]] + +# Microsoft Project + +## Install +1. Confirm the user has a Project license (Project Plan 3 or 5). +2. Software Center > **Microsoft Project - en-us**. +3. Launch and verify activation under **File > Account**. + +## Notes +- Like Visio, Project must match the installed Microsoft 365 Apps channel. +- Global template customizations live in `%AppData%\Microsoft\MS Project\` (Global.MPT). Confirm it came across if the user relies on custom views. diff --git a/Documentation/Microsoft Teams.md b/Documentation/Microsoft Teams.md new file mode 100644 index 0000000..3163968 --- /dev/null +++ b/Documentation/Microsoft Teams.md @@ -0,0 +1,21 @@ +[[Green:Baseline]] + +# Microsoft Teams + +The new Teams client is provisioned per user. It installs automatically the first time a user signs in to a device built from the standard image. + +## Checks After Migration +1. Teams launches and signs in without prompting for a password (SSO). +2. **Settings > Devices**: the correct headset/camera is selected. +3. The **Teams Meeting Add-in** appears in Outlook's ribbon. + +## Meeting Add-in Missing in Outlook +1. Close Outlook and Teams. +2. Start Teams, wait for it to fully load, then start Outlook. +3. If still missing: Outlook > File > Options > Add-ins > Manage **COM Add-ins** > Go > enable *Microsoft Teams Meeting Add-in*. + +## Clear the Teams Cache +```powershell +Get-Process ms-teams -ErrorAction SilentlyContinue | Stop-Process -Force +Remove-Item "$env:LocalAppData\Packages\MSTeams_8wekyb3d8bbwe\LocalCache\Microsoft\MSTeams" -Recurse -Force +``` diff --git a/Documentation/Microsoft Visio.md b/Documentation/Microsoft Visio.md new file mode 100644 index 0000000..03ef84b --- /dev/null +++ b/Documentation/Microsoft Visio.md @@ -0,0 +1,14 @@ +[[Blue:Licensed]] + +# Microsoft Visio + +Visio Plan 2 is licensed per user in Microsoft 365. + +## Install +1. Confirm the user has a Visio license assigned (Microsoft 365 admin center). +2. Software Center > **Microsoft Visio - en-us**. +3. Open Visio > **File > Account** and confirm the product is activated. + +## Notes +- Visio installs alongside Microsoft 365 Apps and must use the same channel (Current/Monthly Enterprise) or the install will fail. +- Custom stencils: `Documents\My Shapes`. USMT migrates these with the Documents folder. diff --git a/Documentation/Notepad++.md b/Documentation/Notepad++.md new file mode 100644 index 0000000..b8a773d --- /dev/null +++ b/Documentation/Notepad++.md @@ -0,0 +1,9 @@ +[[Gray:Optional]] + +# Notepad++ + +Free text and code editor, mostly requested by IT and Engineering. + +- Software Center > **Notepad++ (64-bit x64)** +- Plugins live in `C:\Program Files\Notepad++\plugins` and are **not** migrated by USMT. Reinstall them from **Plugins > Plugins Admin**. +- Settings and sessions are in `%AppData%\Notepad++`, which USMT migrates. diff --git a/Documentation/QuickBooks.md b/Documentation/QuickBooks.md new file mode 100644 index 0000000..bab0413 --- /dev/null +++ b/Documentation/QuickBooks.md @@ -0,0 +1,23 @@ +[[Blue:Licensed]] [[Red:Finance Approval]] + +# QuickBooks + +QuickBooks Desktop is installed for Accounting and Finance users only. Each install needs a **license and product number** from the Finance team. Never reuse one from another device without approval. + +The software scan shows three entries that USMT Dash groups as one *QuickBooks* suite: +- QuickBooks Desktop Pro +- QuickBooks Database Server Manager +- Intuit Data Protect + +## Install +1. Get the license and product number from the Finance team's request ticket. +2. Run the installer: [[Copy:\\fileserver\software\QuickBooks\Setup_QuickBooks.exe]] +3. Choose **Custom and Network Options** > *I'll be using QuickBooks on this computer*. +4. Launch, register, and apply all updates (**Help > Update QuickBooks Desktop**). + +## Company Files +- Company files (`.QBW`) live on the finance share, **not** on the local device: [[Copy:\\fileserver\finance\QuickBooks]] +- If the user had a local company file, move it to the share. Don't open it from a USB drive. + +## Multi-user Mode +The file server runs **Database Server Manager**. Workstations don't need it unless the user hosts files locally. diff --git a/Documentation/Salesforce.md b/Documentation/Salesforce.md new file mode 100644 index 0000000..7e00461 --- /dev/null +++ b/Documentation/Salesforce.md @@ -0,0 +1,23 @@ +[[Blue:Licensed]] + +# Salesforce + +Salesforce itself is web based. Desktop components are only needed by some Sales and Support users. + +| Component | Who needs it | +|---|---| +| Salesforce for Outlook / Outlook integration | Sales reps who log email to Salesforce | +| Salesforce Data Loader | Sales Ops / admins doing bulk imports | + +## Install +1. Install the components that were on the old device (check *Missing Software* on the **All Devices** page). +2. Installer share: [[Copy:\\fileserver\software\Salesforce\]] +3. **Data Loader** needs a Java runtime. The installer bundles one; accept the defaults. + +## Outlook Integration +The modern integration is an Outlook add-in deployed from the Microsoft 365 admin center. After migration: +1. Open Outlook and look for the **Salesforce** button on the ribbon. +2. If it's missing, wait for add-in sync (up to 24 hours) or ask the M365 admins to confirm the assignment. + +## Single Sign-On +Users sign in with **Use Custom Domain** > `company` > SSO. They shouldn't need a separate password. diff --git a/Documentation/Slack.md b/Documentation/Slack.md new file mode 100644 index 0000000..75072f6 --- /dev/null +++ b/Documentation/Slack.md @@ -0,0 +1,11 @@ +[[Gray:Optional]] + +# Slack + +## Install +1. Software Center > **Slack (Machine - MSI)**. The machine-wide installer keeps Slack updated for all users on shared devices. +2. Sign in to the company workspace with SSO. + +## Notes +- Slack data is cloud based. Nothing needs to be migrated. +- If the user had the **per-user** Slack installed (from slack.com), uninstall it first so two copies don't fight over the protocol handler. diff --git a/Documentation/Tableau.md b/Documentation/Tableau.md new file mode 100644 index 0000000..952f3a5 --- /dev/null +++ b/Documentation/Tableau.md @@ -0,0 +1,13 @@ +[[Blue:Licensed]] + +# Tableau Desktop + +## Install +1. Installer: [[Copy:\\fileserver\software\Tableau\]] +2. Activate with the user's license key (from the Analytics team) or sign in to Tableau Cloud if using login-based licensing. + +## Before Wiping the Old Device +Licenses activated with a product key must be **deactivated** first (Help > Manage Product Keys > Deactivate), or the key stays consumed. + +## Drivers +Database drivers (SQL Server, Oracle, ...) aren't included. Reinstall the ones the user had. Check *Missing Software* on the **All Devices** page. diff --git a/Documentation/Using USMT Dash.md b/Documentation/Using USMT Dash.md new file mode 100644 index 0000000..426babc --- /dev/null +++ b/Documentation/Using USMT Dash.md @@ -0,0 +1,36 @@ +[[Blue:Start Here]] + +# Using USMT Dash + +USMT Dash tracks a Windows device-refresh project: which old (**source**) device is being replaced by which new (**destination**) device, and how far each migration has got. + +![How USMT Dash works](images/USMT%20Dash%20Architecture.png) + +## The Pages +| Page | What it's for | +|---|---| +| **Tracked Devices** | Register devices under a *tag* (your name, a date batch, a department...). The worker only watches tagged devices. | +| **My Workbench** | Personal stacks: paste the devices on your cart and see just those. Saved in your browser. | +| **USMT Status** | Every tracked migration, grouped by status, with ping, free space and USMT log lines. | +| **All Devices** | Compares source and destination software and lists *specialty* apps missing from the new device. | +| **Custom View** | Build your own table: add, remove, resize, drag and filter any column. | +| **Documentation** | This knowledge base. Articles named after an app are linked from that app in the software view. | + +## Migration Status +![Migration lifecycle](images/Migration%20Lifecycle.png) + +| Status | Meaning | +|---|---| +| Not Started | Association exists, no backup yet | +| Backup In Progress | Scanstate log on the source is actively being written | +| Backup Completed | Backup finished, waiting for the new device | +| Restore In Progress | Loadstate log on the destination is active | +| Completed | Restore finished successfully | +| Not Associated | No computer association in ConfigMgr | +| Skipped | The device's tag has **Skip USMT** ticked (inventory only) | + +## Tips +- Pair devices manually with `SOURCE, DESTINATION` on one line when there's no ConfigMgr association. +- Click any device name for full details: location, users, hardware, AD groups. +- **Copy Location Info** builds a ready-to-paste table for facilities or asset tickets. +- In articles, `[[Copy:text]]` creates a click-to-copy link, and `[[Yellow:Label]]` adds a badge. diff --git a/Documentation/WMI Namespace Fix.md b/Documentation/WMI Namespace Fix.md new file mode 100644 index 0000000..9c0eb6d --- /dev/null +++ b/Documentation/WMI Namespace Fix.md @@ -0,0 +1,31 @@ +[[Yellow:Remediation]] [[Red:Admin Required]] + +# WMI Namespace Fix + +Symptoms: Software Center is blank, the ConfigMgr client shows *Inactive*, or the backup task sequence fails with WMI errors (`0x80041003`, `0x8004100e`). + +## 1. Verify the repository +Open an **elevated** PowerShell: +```powershell +winmgmt /verifyrepository +``` +- `WMI repository is consistent`: skip to step 3. +- `inconsistent`: continue. + +## 2. Salvage the repository +```powershell +winmgmt /salvagerepository +``` +Run `/verifyrepository` again. If it's still inconsistent, as a **last resort**: +```powershell +winmgmt /resetrepository +``` + +## 3. Repair the ConfigMgr client +```powershell +& "$env:WinDir\CCM\ccmrepair.exe" +``` +Wait 10-15 minutes, then confirm **Configuration Manager** in Control Panel lists its actions. + +## 4. Re-check in USMT Dash +The client status in the device details should return to *Installed / Active* after the next hardware inventory cycle. diff --git a/Documentation/Zoom.md b/Documentation/Zoom.md new file mode 100644 index 0000000..dd395c5 --- /dev/null +++ b/Documentation/Zoom.md @@ -0,0 +1,12 @@ +[[Gray:Optional]] + +# Zoom + +## Install +- Software Center > **Zoom Workplace (64-bit)**. +- Users sign in with **SSO** using the company domain `company`. + +## Common Issues +- **Camera or microphone not detected**: Windows Settings > Privacy & security > Camera / Microphone > allow desktop apps. +- **Outlook plugin**: the Zoom add-in for Outlook is deployed centrally. No local install needed. +- **Old version prompts to update**: Software Center installs are updated automatically. Have the user dismiss the in-app prompt. diff --git a/Documentation/images/Migration Lifecycle.png b/Documentation/images/Migration Lifecycle.png new file mode 100644 index 0000000000000000000000000000000000000000..7418ca2c75013c8bd9f26b13a74655a346516cd4 GIT binary patch literal 26621 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 Q{yg9dSyh=*sW%`03vAu`5dZ)H literal 0 HcmV?d00001 diff --git a/Documentation/images/USMT Dash Architecture.png b/Documentation/images/USMT Dash Architecture.png new file mode 100644 index 0000000000000000000000000000000000000000..40406f184311c63915dbfff8ec6d063077f41ad6 GIT binary patch literal 32612 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 literal 0 HcmV?d00001 diff --git a/Get-Software.ps1 b/Get-Software.ps1 new file mode 100755 index 0000000..4990526 --- /dev/null +++ b/Get-Software.ps1 @@ -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) diff --git a/Lib/Common.ps1 b/Lib/Common.ps1 new file mode 100644 index 0000000..a248b0d --- /dev/null +++ b/Lib/Common.ps1 @@ -0,0 +1,129 @@ +# Shared helpers for USMT-Server.ps1, USMT-Worker.ps1 and Get-Software.ps1. +# Dot-source this file; it must stay compatible with Windows PowerShell 5.1 and PowerShell 7. + +function Import-DashConfig { + param([string]$Path, [switch]$Demo) + + if (-not $Path) { $Path = Join-Path (Split-Path $PSScriptRoot -Parent) "Config\usmt-dash.psd1" } + $Path = $Path -replace '[\\/]', [System.IO.Path]::DirectorySeparatorChar + if (-not (Test-Path $Path)) { throw "Config file not found: $Path" } + + $cfg = Import-PowerShellDataFile -Path $Path + if ($Demo -or $env:USMT_MODE -eq 'Demo') { $cfg.Mode = 'Demo' } + $cfg.IsDemo = ($cfg.Mode -eq 'Demo') + return $cfg +} + +function Join-DashPath { + # Join-Path that tolerates the backslash-separated relative paths used in config on Linux + param([string]$Base, [string]$Child) + return Join-Path $Base ($Child -replace '[\\/]', [System.IO.Path]::DirectorySeparatorChar) +} + +# ----------------------------------------------------------------------------------------- +# Console log mirror +# +# Write-Host is proxied so every line printed to the console is also appended to a JSON +# lines file: {"i":,"t":"