Install-FromManifest.ps1: - Get-CurrentMachineNumber reads the eDNC/DNC registry FIRST (reassignment- authoritative), falling back to C:\Enrollment\machine-number.txt. The txt is written once at imaging and is NOT updated on reassignment, so txt-first gated reassigned bays on a stale number. - Per-entry try/catch in the app loop: a single entry that throws no longer aborts the whole scope (skipping every later entry + the status write). It is logged, counted failed, and the loop continues. This was silently killing the collections scope at the MTConnect Makino entry, which also stopped the ShopDB asset reporter (a later entry) from ever running. Deploy-ShopfloorStartLayout.ps1 (new): local-DSC port of the Intune desktop-weblinks + Start-menu pins (copies .url/.lnk to Public Desktop + All-Users Start Menu, writes the ConfigureStartPins JSON policy, resets start2.bin + restarts the shell). Verified on Win11: pins render after logon. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
128 lines
6.0 KiB
PowerShell
128 lines
6.0 KiB
PowerShell
# Deploy-ShopfloorStartLayout.ps1
|
|
#
|
|
# Local-DSC port of the Intune SFLD desktop/Start-menu deployment. Creates the
|
|
# Public Desktop weblinks (.url) + app/folder shortcuts (.lnk) AND pins them to
|
|
# the Windows 11 Start menu - using the exact same mechanism Simple-Install.ps1
|
|
# uses: shortcuts in the All-Users Start Menu, a ConfigureStartPins JSON policy
|
|
# in the registry, and a StartMenuExperienceHost reset so it applies on next
|
|
# logon. Nothing here needs Intune/MDM - it is all file + registry-policy.
|
|
#
|
|
# Designed to run from the GE-Enforce manifest engine as a Type=PS1 entry
|
|
# (DetectionMethod=Always, or Hash on the pins.json). Idempotent.
|
|
#
|
|
# Usage:
|
|
# powershell -ExecutionPolicy Bypass -File Deploy-ShopfloorStartLayout.ps1
|
|
# -AssetsDir <globalassets dir on the share>
|
|
#
|
|
# AssetsDir holds the prebuilt .url/.lnk (the "globalassets" folder). The pin
|
|
# list + order below mirrors device-config.yaml StartMenuPins; entries with a
|
|
# Target are created on the fly (app/folder pins), the rest are copied from
|
|
# AssetsDir.
|
|
|
|
param(
|
|
[string]$AssetsDir = (Join-Path $PSScriptRoot 'globalassets'),
|
|
[string]$DesktopDir = 'C:\Users\Public\Desktop',
|
|
[switch]$NoShellRestart
|
|
)
|
|
|
|
$ErrorActionPreference = 'Continue'
|
|
$logDir = 'C:\Logs\Shopfloor'
|
|
New-Item -ItemType Directory -Path $logDir -Force -EA SilentlyContinue | Out-Null
|
|
$log = Join-Path $logDir ('start-layout-{0}.log' -f (Get-Date -Format 'yyyyMMdd'))
|
|
function Log($m){ "$([DateTime]::Now.ToString('s')) $m" | Tee-Object -FilePath $log -Append | Out-Null }
|
|
|
|
# Ordered pin set - mirrors device-config.yaml StartMenuPins. Name = the file
|
|
# in the All-Users Start Menu (and AssetsDir for prebuilt ones). Target set =>
|
|
# create the shortcut; Target empty => copy the prebuilt file from AssetsDir.
|
|
$Pins = @(
|
|
@{ Name = 'Shopfloor Dashboard.url' }
|
|
@{ Name = 'PN & SN Label Printing.url' }
|
|
@{ Name = 'WJ Shop Floor Homepage.url' }
|
|
@{ Name = 'WJ Web Reports.url' }
|
|
@{ Name = 'Blueprint PDF Viewer.url' }
|
|
@{ Name = 'Central CSF Web Reports.url' }
|
|
@{ Name = 'Plant Apps.url' }
|
|
@{ Name = 'Safety Good Catch Form.url' }
|
|
@{ Name = 'WJ IT Help Desk.url' }
|
|
@{ Name = 'OneIDM.url' }
|
|
@{ Name = 'M365 Webmail.url' }
|
|
@{ Name = 'HR Central.url' }
|
|
@{ Name = 'Defect_Tracker.lnk' }
|
|
@{ Name = 'Calculator.lnk' }
|
|
@{ Name = 'Notepad.lnk' }
|
|
@{ Name = 'eDNC.lnk'; Target = 'C:\Program Files\eDNC\eDNC.exe' }
|
|
@{ Name = 'NTLARS.lnk'; Target = 'C:\Program Files (x86)\NTLARS\NTLARS.exe' }
|
|
@{ Name = 'Shopfloor Tools.lnk'; Target = 'C:\Users\Public\Desktop\Shopfloor Tools' }
|
|
)
|
|
|
|
$startMenuDir = Join-Path $env:ALLUSERSPROFILE 'Microsoft\Windows\Start Menu\Programs'
|
|
|
|
function New-UrlShortcut([string]$Path,[string]$Url){
|
|
@('[InternetShortcut]', "URL=$Url") | Set-Content -LiteralPath $Path -Encoding ASCII
|
|
}
|
|
function New-LnkShortcut([string]$Path,[string]$Target,[string]$Args,[string]$Icon){
|
|
$sh = New-Object -ComObject WScript.Shell
|
|
$sc = $sh.CreateShortcut($Path)
|
|
$sc.TargetPath = $Target
|
|
if ($Args) { $sc.Arguments = $Args }
|
|
# working dir: parent of target for files, the folder itself for folder pins
|
|
$sc.WorkingDirectory = if (Test-Path -LiteralPath $Target -PathType Container) { $Target } else { Split-Path -Parent $Target }
|
|
if ($Icon) { $sc.IconLocation = $Icon }
|
|
$sc.Save()
|
|
}
|
|
|
|
Log "=== Deploy shopfloor start layout (assets: $AssetsDir) ==="
|
|
New-Item -ItemType Directory -Path $startMenuDir -Force -EA SilentlyContinue | Out-Null
|
|
New-Item -ItemType Directory -Path $DesktopDir -Force -EA SilentlyContinue | Out-Null
|
|
|
|
$pinnedList = @()
|
|
foreach ($pin in $Pins) {
|
|
$leaf = $pin.Name
|
|
$dst = Join-Path $startMenuDir $leaf
|
|
try {
|
|
if ($pin.Target) {
|
|
# create app/folder shortcut from Target
|
|
New-LnkShortcut -Path $dst -Target $pin.Target -Args $pin.Arguments -Icon $pin.IconLocation
|
|
Log "created (target) $leaf -> $($pin.Target)"
|
|
} else {
|
|
# copy prebuilt asset (.url/.lnk) from globalassets
|
|
$src = Join-Path $AssetsDir $leaf
|
|
if (-not (Test-Path -LiteralPath $src)) { Log "MISSING asset, skipping pin: $src"; continue }
|
|
Copy-Item -LiteralPath $src -Destination $dst -Force
|
|
# also drop on the Public Desktop
|
|
Copy-Item -LiteralPath $src -Destination (Join-Path $DesktopDir $leaf) -Force
|
|
Log "copied $leaf (start menu + desktop)"
|
|
}
|
|
$pinnedList += @{ desktopAppLink = "%ALLUSERSPROFILE%\Microsoft\Windows\Start Menu\Programs\$leaf" }
|
|
} catch {
|
|
Log "ERROR pin ${leaf}: $($_.Exception.Message)"
|
|
}
|
|
}
|
|
|
|
# ConfigureStartPins JSON -> HKLM policy (same shape Simple-Install.ps1 writes)
|
|
$jsonDir = 'C:\ProgramData\SFLD\StartMenu'
|
|
New-Item -ItemType Directory -Path $jsonDir -Force -EA SilentlyContinue | Out-Null
|
|
$jsonPath = Join-Path $jsonDir 'pins.json'
|
|
([ordered]@{ applyOnce = $false; pinnedList = $pinnedList } | ConvertTo-Json -Depth 6) |
|
|
Set-Content -LiteralPath $jsonPath -Encoding UTF8
|
|
$reg = 'HKLM:\SOFTWARE\Policies\Microsoft\Windows\Explorer'
|
|
if (-not (Test-Path $reg)) { New-Item -Path $reg -Force | Out-Null }
|
|
New-ItemProperty -Path $reg -Name 'ConfigureStartPins' -PropertyType String `
|
|
-Value (Get-Content -LiteralPath $jsonPath -Raw -Encoding UTF8) -Force | Out-Null
|
|
Log "ConfigureStartPins policy written ($($pinnedList.Count) pins) -> $reg"
|
|
|
|
# Apply now: clear each real user's cached start layout + restart the shell.
|
|
if (-not $NoShellRestart) {
|
|
Get-ChildItem 'C:\Users' -Directory -EA SilentlyContinue |
|
|
Where-Object { $_.Name -notin @('Public','Default','Default User','All Users') } |
|
|
ForEach-Object {
|
|
$sb = Join-Path $_.FullName 'AppData\Local\Packages\Microsoft.Windows.StartMenuExperienceHost_cw5n1h2txyewy\LocalState\start2.bin'
|
|
if (Test-Path -LiteralPath $sb) { Remove-Item -LiteralPath $sb -Force -EA SilentlyContinue; Log "cleared start2.bin: $($_.Name)" }
|
|
}
|
|
Get-Process -Name 'StartMenuExperienceHost' -EA SilentlyContinue | Stop-Process -Force -EA SilentlyContinue
|
|
Log 'StartMenuExperienceHost restarted (pins apply on next shell load)'
|
|
}
|
|
|
|
Log '=== done ==='
|
|
exit 0
|