Files
pxe-server/playbook/shopfloor-setup/Shopfloor/08-EdgeDefaultBrowser.ps1
cproudlock 900180cd12 Shopfloor: desktop folder org, taskbar pins, Edge defaults
Three new baseline scripts that run during shopfloor imaging to clean up
the end-user Public Desktop. Before this, Azure AD users logged into a
shopfloor PC and saw 20+ loose shortcuts at the desktop root (Office
apps, OpenText sessions, WJ web portals, DNC utilities, Defect Tracker,
plus .url files for every intranet page) with no organization. End users
couldn't find anything.

06-OrganizeDesktop.ps1 - Single source of truth for Public Desktop layout
  Phase 1: sweeps loose shortcuts at the desktop root into three category
    folders - Office\, Shopfloor Tools\, Web Links\ - by filename regex,
    extension, and .lnk target resolution. Allowlists eDNC.lnk and
    NTLARS.lnk to stay at root since end users click them too often.
    Unknown items are left at the root on purpose (never delete).
  Phase 2: materializes specific app shortcuts into Shopfloor Tools\.
    UDC / eDNC / NTLARS are built fresh from their .exe paths; WJ
    Shopfloor and Defect_Tracker are MSI-advertised (empty TargetPath,
    Darwin descriptor) so we copy the existing .lnk from wherever it
    lives via a multi-location lookup. Each entry is conditional on its
    source being present - script runs cleanly on PC types without DnC.
  Phase 3: drops eDNC.lnk and NTLARS.lnk at desktop root from the
    Shopfloor Tools\ copies, so end users have both a folder version
    and a quick-access root version.
  Phase 4: registers an "Organize Public Desktop" scheduled task that
    re-runs phase 1 at every logon. Shortcuts added later by DSC /
    Intune / msiexec get filed automatically without another imaging
    pass. Admin check at the top, -ErrorAction Stop on Register-
    ScheduledTask and directory creation so failures are caught
    instead of printing false success.

07-TaskbarLayout.ps1 - Minimal taskbar pinner
  Checks which .lnk files 06 created in Shopfloor Tools\, then writes
  LayoutModification.xml to the Default User profile with taskbar pins
  in order: Edge, WJ Shopfloor, UDC, eDNC, Defect_Tracker. No shortcut
  creation in this script - all shortcut management lives in 06.
  Missing .lnks are skipped (PC types without DnC just get fewer pins).
  Applies on first logon of new user profiles (Azure AD users after
  enrollment). Existing profiles don't re-read Default User - Windows
  design limitation since 1703, no programmatic fix.

08-EdgeDefaultBrowser.ps1 - Edge as default browser + startup tabs
  Motivated by the ppkg installing Chrome alongside Edge: new Azure AD
  users hit a "Choose your default app" picker on first URL click
  because nothing is marked default. Two layers:
    1. dism /Online /Import-DefaultAppAssociations:<xml> writes an XML
       with Edge ProgIds for http/https/.htm/.html/.pdf/.svg/.webp into
       the Default User profile template. New profiles inherit.
    2. HKLM:\SOFTWARE\Policies\Microsoft\Windows\System\
       DefaultAssociationsConfiguration registry value (the "Set a
       default associations configuration file" GPO) points at the same
       XML so Windows re-applies on every logon, catching Windows-update
       defaults-reset cases.
  Leaves Chrome installed, just not the default URL handler.

  Also sets Edge startup tabs via machine-wide policies under
  HKLM:\SOFTWARE\Policies\Microsoft\Edge:
    RestoreOnStartup      = 4 (open specific URLs)
    RestoreOnStartupURLs  = Plant Apps, WJ Shop Floor Homepage, Shopfloor
                            Dashboard (tab order per spec)
    HomepageLocation      = first tab (Plant Apps)
    HomepageIsNewTabPage  = 0
    ShowHomeButton        = 1
  URLs are resolved dynamically from the .url files on the Public
  Desktop (or Web Links\ after the sweep), so if WJDT changes a URL
  later the script picks it up without a code change. Fallbacks are
  hardcoded for the two portals we have URLs memorized for; Plant Apps
  has no fallback and will be skipped if the .url file is missing.

Test workflow: admin-check in all three scripts fails fast on non-
elevated runs instead of spamming half-successful Access Denied output
like the first draft did.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-09 14:38:38 -04:00

255 lines
12 KiB
PowerShell

# 08-EdgeDefaultBrowser.ps1 - Set Microsoft Edge as the default browser
# and PDF handler for all new user profiles, and configure startup tabs
# so Edge opens with Plant Apps / Shop Floor Homepage / Shopfloor Dashboard
# when the end user first launches it.
#
# WHY THIS EXISTS:
# The West Jefferson shopfloor ppkg installs Google Chrome alongside Edge.
# On first URL click in a new Azure AD user profile, Windows pops a "Choose
# your default app" picker because multiple browsers are installed and
# nothing has been marked as default. End users hit this on every fresh PC
# and it derails the shopfloor workflow.
#
# HOW IT WORKS:
# Two layers, belt-and-suspenders:
#
# 1. DISM default-app-associations XML
# Writes an XML file mapping http/https/.htm/.html/.pdf/.svg/etc to
# Edge's ProgIds, then runs:
# dism /Online /Import-DefaultAppAssociations:<xml>
# This imports the XML into the Default User profile template. Any
# NEW user profile created after this point inherits the associations
# on first logon. Microsoft-supported, works across Win10/Win11.
#
# 2. Group Policy registry key
# HKLM:\SOFTWARE\Policies\Microsoft\Windows\System\DefaultAssociationsConfiguration
# points at the same XML file. This is the "Set a default associations
# configuration file" GPO. With this set, Windows re-applies the XML
# on every logon, not just once at profile creation - so Windows
# update defaults-reset and similar edge cases don't un-do us.
#
# CAVEATS:
# - Applies to NEW profiles on first logon. An existing profile that's
# already been logged in (e.g. SupportUser) won't pick up the change
# without a manual Settings > Default apps visit. Acceptable because
# SupportUser isn't the end-user account.
# - Does not UNinstall Chrome. Chrome remains available, it's just not
# the default handler for URLs.
# - Microsoft blocks programmatic UserChoice hash tampering since Win10
# 1703. The DISM + policy route is the only supported path, and it
# only fires for new profiles - that's by design.
$ErrorActionPreference = 'Stop'
# ----------------------------------------------------------------------------
# Admin check
# ----------------------------------------------------------------------------
$isAdmin = ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)
if (-not $isAdmin) {
Write-Host ""
Write-Host "ERROR: 08-EdgeDefaultBrowser.ps1 must run as Administrator." -ForegroundColor Red
Write-Host " Re-run from an elevated PowerShell." -ForegroundColor Red
Write-Host ""
exit 1
}
# ----------------------------------------------------------------------------
# Sanity: Edge must be installed for this to mean anything
# ----------------------------------------------------------------------------
$edgeCandidates = @(
'C:\Program Files (x86)\Microsoft\Edge\Application\msedge.exe',
'C:\Program Files\Microsoft\Edge\Application\msedge.exe'
)
$edgePath = $edgeCandidates | Where-Object { Test-Path -LiteralPath $_ } | Select-Object -First 1
if (-not $edgePath) {
Write-Host "Microsoft Edge not found in either Program Files location - skipping default browser config."
exit 0
}
Write-Host "Found Edge at: $edgePath"
# ----------------------------------------------------------------------------
# XML: Edge as default for web browsing + PDFs + common image formats
# ----------------------------------------------------------------------------
$xml = @'
<?xml version="1.0" encoding="UTF-8"?>
<DefaultAssociations>
<Association Identifier=".htm" ProgId="MSEdgeHTM" ApplicationName="Microsoft Edge" />
<Association Identifier=".html" ProgId="MSEdgeHTM" ApplicationName="Microsoft Edge" />
<Association Identifier=".mht" ProgId="MSEdgeMHT" ApplicationName="Microsoft Edge" />
<Association Identifier=".mhtml" ProgId="MSEdgeMHT" ApplicationName="Microsoft Edge" />
<Association Identifier=".pdf" ProgId="MSEdgePDF" ApplicationName="Microsoft Edge" />
<Association Identifier=".svg" ProgId="MSEdgeSVG" ApplicationName="Microsoft Edge" />
<Association Identifier=".webp" ProgId="MSEdgeHTM" ApplicationName="Microsoft Edge" />
<Association Identifier="http" ProgId="MSEdgeHTM" ApplicationName="Microsoft Edge" />
<Association Identifier="https" ProgId="MSEdgeHTM" ApplicationName="Microsoft Edge" />
<Association Identifier="ftp" ProgId="MSEdgeHTM" ApplicationName="Microsoft Edge" />
<Association Identifier="microsoft-edge" ProgId="MSEdgeHTM" ApplicationName="Microsoft Edge" />
</DefaultAssociations>
'@
# Write to a stable location that persists across reimaging (C:\Enrollment is
# the canonical staging dir in this repo, survives logoff/logon, referenced
# by the GP policy below so it must be a path every user-session can read).
$xmlDir = 'C:\Enrollment'
$xmlPath = Join-Path $xmlDir 'edge-default-associations.xml'
if (-not (Test-Path -LiteralPath $xmlDir)) {
New-Item -ItemType Directory -Path $xmlDir -Force | Out-Null
}
Set-Content -LiteralPath $xmlPath -Value $xml -Encoding UTF8 -Force
Write-Host "Wrote associations XML to: $xmlPath"
# ----------------------------------------------------------------------------
# Layer 1: DISM import into the Default User profile template
# ----------------------------------------------------------------------------
Write-Host ""
Write-Host "Layer 1: dism /Online /Import-DefaultAppAssociations..."
& dism.exe /Online /Import-DefaultAppAssociations:"$xmlPath" | Out-Null
$dismExit = $LASTEXITCODE
if ($dismExit -eq 0) {
Write-Host " OK - defaults imported into Default User template."
} else {
Write-Warning " dism returned exit code $dismExit - import may not have applied."
}
# ----------------------------------------------------------------------------
# Layer 2: Group Policy registry key - enforces XML on every logon
# ----------------------------------------------------------------------------
Write-Host ""
Write-Host "Layer 2: Setting DefaultAssociationsConfiguration policy..."
$polKey = 'HKLM:\SOFTWARE\Policies\Microsoft\Windows\System'
if (-not (Test-Path $polKey)) {
New-Item -Path $polKey -Force | Out-Null
}
Set-ItemProperty -Path $polKey -Name 'DefaultAssociationsConfiguration' -Value $xmlPath -Type String -Force
Write-Host " OK - HKLM:\SOFTWARE\Policies\Microsoft\Windows\System\DefaultAssociationsConfiguration = $xmlPath"
Write-Host ""
Write-Host "================================================================"
Write-Host "Edge default browser config applied."
Write-Host "================================================================"
# ----------------------------------------------------------------------------
# Edge startup tabs + homepage
#
# End users should see Plant Apps, the Shop Floor Homepage, and the
# Shopfloor Dashboard open in three tabs the moment Edge launches. Rather
# than hardcoding URLs in this script, we read them out of the .url files
# that already live on the Public Desktop (copied there by ppkg). This
# way, if an admin renames or retargets a .url file later, the script
# picks up the new URL on the next imaging run without a code change.
#
# Fallback: if the .url file is missing, we fall back to a hardcoded URL
# (only for the ones we know for sure). Plant Apps has no fallback because
# I don't have its URL memorized.
#
# Machine policies (all under HKLM:\SOFTWARE\Policies\Microsoft\Edge):
# RestoreOnStartup = 4 (open a specific set of URLs)
# RestoreOnStartupURLs = subkey with "1","2","3"... = URLs in tab order
# HomepageLocation = first startup URL (home button opens same)
# HomepageIsNewTabPage = 0 (home button opens HomepageLocation, not NTP)
# ShowHomeButton = 1 (show the home button in the toolbar)
# ----------------------------------------------------------------------------
Write-Host ""
Write-Host "Configuring Edge startup tabs..."
function Get-UrlFromFile {
param([string]$Path)
try {
$content = Get-Content -LiteralPath $Path -ErrorAction Stop
$urlLine = $content | Where-Object { $_ -match '^URL=' } | Select-Object -First 1
if ($urlLine) {
return ($urlLine -replace '^URL=', '').Trim()
}
} catch {}
return $null
}
function Resolve-StartupUrl {
param(
[string]$BaseName, # .url filename without extension
[string]$Fallback = ''
)
# Look in Public Desktop root first, then Web Links subfolder (06's
# sweeper may have moved it there already).
$candidates = @(
"C:\Users\Public\Desktop\$BaseName.url",
"C:\Users\Public\Desktop\Web Links\$BaseName.url"
)
foreach ($c in $candidates) {
if (Test-Path -LiteralPath $c) {
$url = Get-UrlFromFile $c
if ($url) {
Write-Host " resolved $BaseName -> $url (from $c)"
return $url
}
}
}
if ($Fallback) {
Write-Host " fallback $BaseName -> $Fallback (.url file not found)" -ForegroundColor DarkGray
return $Fallback
}
Write-Warning " $BaseName : no .url file found and no fallback - will be skipped"
return $null
}
# Tab order as requested: Plant Apps, Shop Floor Homepage, Shopfloor Dashboard
$startupTabs = @()
$plantApps = Resolve-StartupUrl -BaseName 'Plant Apps'
if ($plantApps) { $startupTabs += $plantApps }
$shopFloorHome = Resolve-StartupUrl -BaseName 'WJ Shop Floor Homepage' -Fallback 'http://tsgwp00524.logon.ds.ge.com/'
if ($shopFloorHome) { $startupTabs += $shopFloorHome }
$dashboard = Resolve-StartupUrl -BaseName 'Shopfloor Dashboard' -Fallback 'https://tsgwp00525.wjs.geaerospace.net/shopdb/shopfloor-dashboard/'
if ($dashboard) { $startupTabs += $dashboard }
if ($startupTabs.Count -eq 0) {
Write-Warning "No startup tab URLs resolved - skipping Edge startup config."
} else {
$edgePolKey = 'HKLM:\SOFTWARE\Policies\Microsoft\Edge'
if (-not (Test-Path $edgePolKey)) {
New-Item -Path $edgePolKey -Force | Out-Null
}
# RestoreOnStartup = 4 means "open a specific set of URLs" (the list
# lives under the RestoreOnStartupURLs subkey below).
Set-ItemProperty -Path $edgePolKey -Name 'RestoreOnStartup' -Value 4 -Type DWord -Force
# Build RestoreOnStartupURLs subkey: numbered string values "1","2","3"
$urlsKey = Join-Path $edgePolKey 'RestoreOnStartupURLs'
if (Test-Path $urlsKey) {
# Wipe any prior entries so we don't leak stale tabs if the list
# shrinks between runs
Remove-Item -Path $urlsKey -Recurse -Force
}
New-Item -Path $urlsKey -Force | Out-Null
for ($i = 0; $i -lt $startupTabs.Count; $i++) {
$name = [string]($i + 1)
Set-ItemProperty -Path $urlsKey -Name $name -Value $startupTabs[$i] -Type String -Force
}
# Homepage = the first startup tab. Home button opens it, not NTP.
Set-ItemProperty -Path $edgePolKey -Name 'HomepageLocation' -Value $startupTabs[0] -Type String -Force
Set-ItemProperty -Path $edgePolKey -Name 'HomepageIsNewTabPage' -Value 0 -Type DWord -Force
Set-ItemProperty -Path $edgePolKey -Name 'ShowHomeButton' -Value 1 -Type DWord -Force
Write-Host ""
Write-Host "Edge startup tabs set ($($startupTabs.Count) tab(s)):"
for ($i = 0; $i -lt $startupTabs.Count; $i++) {
Write-Host " $($i + 1). $($startupTabs[$i])"
}
Write-Host "Homepage set to: $($startupTabs[0])"
}
Write-Host ""
Write-Host "================================================================"
Write-Host "08-EdgeDefaultBrowser.ps1 complete."
Write-Host "Effective for new user profiles on first logon (Azure AD users)."
Write-Host "Existing profiles will pick up the startup-tab policy on next"
Write-Host "Edge launch - it's a machine-wide GPO."
Write-Host "================================================================"
exit 0