# 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 # # 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