Scenario: Shared PC Mode
This scenario covers physical shared devices — lab computers, training room workstations, reception desks, and similar endpoints where multiple users share a single machine with session isolation and automatic profile cleanup.
Why Shared PC Mode for CMMC
Shared physical devices collapse two NIST SP 800-171 Rev. 2 control families onto a single endpoint:
- AC.L2-3.1.1 / 3.1.2 / 3.1.7 — Access Control. "Limit system access to authorized users" gets harder when the user changes every 20 minutes. The control isn't about who signs in — Entra handles that — it's about ensuring the previous user's residual state (Recent Items, cached files, half-finished documents on the desktop, hard-cached credentials) is unreachable by the next user.
- MP.L2-3.8.3 — Media Protection. "Sanitize or destroy system media containing CUI before disposal or release for reuse." On a shared device, the local profile is the media reused at every sign-out. Profile-deletion-on-sign-out is the cheapest Microsoft-stack implementation of 3.8.3 an assessor recognizes without a side conversation.
Is Shared PC Mode literally required? No. A documented Windows Autopilot Reset between sessions, or wipe-on-reboot via image management, both pass the control text. Shared PC Mode is the only built-in option that makes profile cleanup automatic, policy-managed via Intune, and continuously enforced by a SYSTEM-context service — for a 12-machine lab, the alternatives cost more in operations than the hardware.
The mechanism — what Shared PC Mode actually writes
Behind the Settings Catalog table below sits the SharedPC CSP at ./Vendor/MSFT/SharedPC. Each Settings Catalog row writes one CSP node:
| Settings Catalog row | CSP node | What it actually does |
|---|---|---|
| Enable Shared PC Mode | EnableSharedPCMode | Switches the device into Shared PC mode |
| Enable Account Manager | EnableAccountManager | Activates shpamsvc — the SYSTEM-context service that performs profile deletion |
| Account Model | AccountModel | Constrains which sign-in flows can create local profiles (AzureAD = Entra-only) |
| Deletion Policy | DeletionPolicy | Immediate / Threshold / Disk-space-based — when the Account Manager actually deletes |
| Restrict Local Storage | Explorer\Allowed* allowlists | Hides drives in File Explorer via registry-level allowlists (covered in Admin Maintenance Procedure) |
There is no per-user Shared PC policy and no group-targeted version. The CSP writes to HKLM and shpamsvc runs as SYSTEM device-wide. Anything you want to except from deletion — admin profiles, kiosk-shell users — must be exempted at the same HKLM layer, not via Intune assignment or filter. This is why the SID-exemption registry write in Exempt Admins from Profile Deletion is the only mechanism Windows exposes for opting profiles out of the cleanup.
One behavior worth flagging up front: tattooing
The SharedPC CSP tattoos — its registry writes persist after the Intune policy that delivered them is unassigned. Un-enrolling a device, or removing the policy assignment, does not return the device to a non-Shared-PC state; shpamsvc keeps deleting profiles on sign-out, and the Explorer drive-hiding allowlists keep hiding drives, until the registry is manually cleaned. This is true on commercial and GCC High; in CMMC environments where lab devices commonly cycle between enclaves during retest, plan removal as a deliberate task, not a side-effect of un-assignment. The Admin Maintenance Procedure section below treats removal as a first-class procedure with the Remove-SharedPCTattoo.ps1 and Restore-SharedPCTattoo.ps1 scripts.
What an assessor asks
"Walk me through what happens to user data on this device when the user signs out." If the answer involves "the OS handles it" or "we re-image quarterly," it's not a 3.8.3 implementation an assessor recognizes. If the answer is "Shared PC Mode is enabled with DeletionPolicy = Delete immediately; shpamsvc runs as SYSTEM and removes the profile within seconds of sign-out; here is the registry tattoo at HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\SharedPC confirming it's enforced device-wide independent of Intune connectivity" — they move on. The rest of this chapter implements that answer.
Lab: Shared PC Mode ("Wipe on Logout")
- Type: Settings Catalog
- Goal: Enforce session isolation and data wiping.
This may result in slow login experiences. Investigate if problematic.
| Category Shared PC | Value | Explanation |
|---|---|---|
| Account Model | Azure AD | Entra-joined devices use Azure AD account model. Use Domain only for Hybrid/Domain-joined shared PCs. |
| Deletion Policy | Delete immediately | Account profiles are deleted immediately after sign-out. |
| Enable Account Manager | true | Activates the service that deletes user profiles. |
| Enable Shared PC Mode | true | Switches the device into Shared PC mode. |
| Maintenance Start Time | 180 | 3am start time of maintenance window. |
| Restrict Local Storage | true | Restricts user from using local storage. |
| Set Power Policies | false | Don't apply SharedPC defaults; configure power via the Power Settings policy below. |
| Sign In On Resume | true | Require signing in on waking from sleep. |
Exempt Admins from Profile Deletion
Shared PC Mode's Account Manager is a device-level service — it deletes profiles for all non-active users after sign-out, regardless of group membership or Intune assignment. You cannot exempt admins via Intune policy assignment or filters.
Windows supports a registry-based exemption mechanism: adding an account's SID to the SharedPC\Exemptions registry key tells the Account Manager to skip that profile during cleanup. Deploy this as an Intune Remediation — a detection + remediation script pair that runs on a schedule. Remediations are the right shape for this work because they re-evaluate periodically and self-heal: notably, the Admin Maintenance Procedure below removes the SharedPC key as part of opening a maintenance window, which clears the Exemptions sub-key with it. The next daily detection notices and the remediation script puts the SIDs back, without operator intervention.
The exemption preserves admin profiles across sign-out, which supports two workflows on a Shared PC. The first is troubleshooting convenience — cached credentials, saved diagnostic tool layouts, familiar shell configuration. The second is the "configure and copy default profile" workflow, where an admin signs in, sets up the desired baseline (browser bookmarks, app associations, desktop shortcuts, etc.), and copies that profile into the default user template so every subsequent user inherits it. Both are valid uses of the exemption today. If you'd prefer to retire the configure-and-copy approach in favor of a model that's per-fleet, audited, and survives Windows updates, the Customizing the Default User Experience section below describes the policy-based alternative.
Step 1 — Decide which accounts to exempt:
Configure the scripts by account name, not SID. The scripts resolve names to SIDs at runtime on each device, which matters because:
- Domain or Entra accounts (e.g.,
CONTOSO\labadmin) have SIDs issued by AD or Entra — identical on every device. Resolving once gets the same answer fleet-wide. - Local accounts (built-in
Administrator, any custom local) have SIDs of the formS-1-5-21-<machine-SID>-<RID>, where the machine portion is generated at OS install and differs per device. The name is consistent across machines; the SID is not.
Both scripts below are uploaded as a paired script package in Intune at Devices > Scripts and remediations > Remediations. Save each as a .ps1 file locally first; Step 4 below covers the upload settings.
Step 2 — Create the detection script:
Save the following as Detect-SharedPCExemptions.ps1. Exit code 0 = every expected account is already exempt (no remediation needed); exit code 1 = at least one is missing (Intune runs the remediation script). Replace CONTOSO and the account names with your own — domain/Entra entries use your NetBIOS domain name, local entries use $env:COMPUTERNAME.
# Detect whether all expected admin accounts are exempted in the SharedPC key.
# Exit 0 = compliant; Exit 1 = remediation needed.
$exemptionPath = "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\SharedPC\Exemptions"
# Configure by NAME. Domain/Entra accounts resolve fleet-wide; local accounts
# resolve to the specific machine's SID at runtime.
$expectedAccounts = @(
@{ Domain = 'CONTOSO'; Name = 'labadmin1' } # domain account
@{ Domain = 'CONTOSO'; Name = 'labadmin2' } # domain account
@{ Domain = $env:COMPUTERNAME; Name = 'Administrator' } # built-in local
@{ Domain = $env:COMPUTERNAME; Name = 'labtech' } # custom local account
)
$expectedSIDs = foreach ($a in $expectedAccounts) {
try {
[System.Security.Principal.NTAccount]::new($a.Domain, $a.Name).
Translate([System.Security.Principal.SecurityIdentifier]).Value
} catch {
Write-Output "WARN: cannot resolve $($a.Domain)\$($a.Name): $($_.Exception.Message)"
}
}
if (-not (Test-Path $exemptionPath)) {
Write-Output "Exemptions key missing -- remediation needed"
exit 1
}
$missing = $expectedSIDs | Where-Object {
$null -eq (Get-Item -LiteralPath $exemptionPath).GetValue($_, $null)
}
if ($missing) {
Write-Output "Missing SIDs: $($missing -join ', ')"
exit 1
}
Write-Output "All expected exemption SIDs present"
exit 0
Step 3 — Create the remediation script:
Save the following as Set-SharedPCExemptions.ps1. The $expectedAccounts list must be identical to the detection script — the two are maintained as a pair.
# Write the expected admin SIDs to the SharedPC Exemptions key.
# Intune runs this only when Detect-SharedPCExemptions.ps1 returns exit 1.
$exemptionPath = "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\SharedPC\Exemptions"
$expectedAccounts = @(
@{ Domain = 'CONTOSO'; Name = 'labadmin1' }
@{ Domain = 'CONTOSO'; Name = 'labadmin2' }
@{ Domain = $env:COMPUTERNAME; Name = 'Administrator' }
@{ Domain = $env:COMPUTERNAME; Name = 'labtech' }
)
if (-not (Test-Path $exemptionPath)) {
New-Item -Path $exemptionPath -Force | Out-Null
}
foreach ($a in $expectedAccounts) {
try {
$sid = [System.Security.Principal.NTAccount]::new($a.Domain, $a.Name).
Translate([System.Security.Principal.SecurityIdentifier]).Value
New-ItemProperty -Path $exemptionPath -Name $sid -Value 1 -PropertyType DWord -Force | Out-Null
Write-Output "Exempted $($a.Domain)\$($a.Name) ($sid)"
} catch {
Write-Output "WARN: cannot resolve $($a.Domain)\$($a.Name): $($_.Exception.Message)"
}
}
If a configured account doesn't resolve on a specific Shared PC (e.g., labtech exists on three of twelve machines), the Translate call throws, the WARN line goes to the Remediation's per-device output in Intune, and the script silently skips that account on that device. Devices where the account is missing remain compliant with no exemption for it — desired behavior, since you can't exempt a SID that doesn't exist.
Step 4 — Deploy as an Intune Remediation:
- Navigate to Intune > Devices > Scripts and remediations > Remediations > Create
- Name:
SharedPC – Admin Exemptions. Description: one line on intent. - Upload
Detect-SharedPCExemptions.ps1as the Detection script file andSet-SharedPCExemptions.ps1as the Remediation script file - Run this script using the logged-on credentials: No (run as SYSTEM)
- Enforce script signature check: No (or sign per your org's signing policy)
- Run script in 64-bit PowerShell host: Yes
- Assign to the Shared PC device group
- Schedule: Daily, at a time outside the SharedPC maintenance window (3 AM in this chapter), so the Remediation isn't running concurrently with the Account Manager's own maintenance pass. Once-per-day detection is sufficient — the typical work is picking up new SIDs as you add accounts to
$expectedAccounts.
The Account Manager service checks HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\SharedPC\Exemptions before deleting a profile. If the signed-out user's SID appears as a value under this key, the profile is preserved. All non-exempt profiles continue to be deleted per the Deletion Policy.
When an admin account changes (new IT staff, departures), update $expectedAccounts in both scripts and re-upload. Devices pick up the change on the next detection cycle. Note that the Admin Maintenance Procedure briefly removes Exemptions along with the rest of the SharedPC key, but Restore-SharedPCTattoo.ps1 re-imports the full SharedPC tree from its .reg backup — so Exemptions returns to its pre-maintenance state automatically, without the Remediation needing to act. The daily Remediation is the safety net if Remove ever runs without a matching Restore.
Admin Maintenance Procedure
Admins occasionally need full local access to a SharedPC-configured machine — driver installs, log collection, third-party software updates, hardware troubleshooting. The SharedPC CSP blocks all of those during normal operation, and its blocking is more durable than most CSP behavior. This section explains the mechanism and gives you a script-based workflow for opening a maintenance window and closing it back up.
The procedure runs entirely on the target machine. There is no Intune-side action — no assignment changes, no filter editing, no waiting for policy sync. The next subsection explains why.
The enforcement mechanism: registry tattooing
Most Intune CSP settings live under HKLM\Software\Policies\... or HKLM\Software\Microsoft\Windows\CurrentVersion\Policies\.... When the policy is un-provisioned in any way, the MDM stack walks those Policies hives and clears the values it owns. The device returns to its pre-policy state.
The SharedPC CSP — and a small but consequential set of related Windows configuration surfaces — writes registry values outside the Policies hives. Those writes survive un-provisioning. This behavior is known as tattooing, and it has been documented since at least 2021 (see Peter van der Woude's original write-up on the BitLocker removable-data-drives setting and the follow-up community thread on Removable Media tattooing). Microsoft changed the default CSP processing model in 2020 to clear values on un-provisioning, but the SharedPC CSP and a handful of others still tattoo because their writes target non-Policies paths.
This is why the maintenance procedure is script-based: un-assigning the policy in Intune does not remove the tattooed registry surface that's actually enforcing the restrictions. You'd wait 5–15 minutes for the sync, confirm un-provisioning, and find Explorer just as locked down as before. Going straight to the registry — what the window-opening script below does — is faster and produces an identical end state.
For SharedPC, the tattooed surface lives at:
HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\
This is the Allowed* family of CLSID-keyed allowlists that govern what the Explorer shell namespace will navigate to and enumerate. The members commonly seen in the wild are:
| Registry name | What it controls |
|---|---|
AllowedNavigation | Which CLSIDs can be navigated to (sidebar, breadcrumbs, address bar drop-downs) |
AllowedEnumeration | Which CLSIDs can be enumerated as children of a namespace (what shows up under This PC) |
AllowedStorageLocations | REG_DWORD bitmask of permitted storage locations |
WCOSNavigationBaselineAllowedlist | Baseline navigation allowlist for WCOS / Windows 11 kiosk-style scenarios |
WCOSEnumerationBaselineAllowedlist | Baseline enumeration allowlist for the same |
Two things to know about this table in practice, both of which the scripts handle defensively so you rarely need to think about them:
- WCOS baselines are conditional. Not every SharedPC profile provisions the two
WCOS*allowlists — they're specific to Windows Core OS / IoT / kiosk SKUs and a few stricter enterprise baselines. Explorer queries for them during namespace enumeration but tolerates their absence.Remove-SharedPCTattoo.ps1skips any key that isn't present;Test-SharedPCMaintenanceState.ps1treats absence as a passing check. AllowedStorageLocationsplacement varies. It's aREG_DWORDvalue, not a key, and it's been observed in the wild both on theExplorerkey directly and as a child value insideAllowedEnumeration(or otherAllowed*/WCOS*subkeys).Remove-SharedPCTattoo.ps1handles the directly-on-Explorer placement explicitly and picks up sub-key placements incidentally via the recursive removal of each parent allowlist.Test-SharedPCMaintenanceState.ps1walks all plausible parent locations.
When SharedPC is fully applied, everything in Explorer's local UI is blocked: This PC won't enumerate drives, the address bar won't accept path input, Control Panel won't open. The keys above each control a different facet of the shell namespace — AllowedNavigation governs address-bar input, AllowedEnumeration governs drive enumeration under This PC — so a partial removal produces partial relief. The window-opening script below removes all members of the family in a single pass; there is no diagnostic reason to do it by parts.
One observable behavior worth knowing for troubleshooting: mapped network drives stay visible under "Network locations" in This PC even when local fixed drives are hidden. The Allowed* family filters the local CLSID-typed enumeration path; mapped drives ride a different code path (the nethood delegate folder) and aren't affected. Get-Volume and GetLogicalDrives() always report the full drive list because those APIs sit below the shell namespace layer entirely, so a clean drive list from PowerShell while Explorer shows nothing is the signature of an Allowed* tattoo, not a disk-level problem.
For canonical Microsoft references on what SharedPC actually sets, see the SharedPC CSP node definitions and the Shared PC technical reference.
Maintenance workflow
This procedure opens a time-bounded maintenance window. The Intune SharedPC assignment stays in place throughout, which means the next MDM sync cycle (every 8 hours by default) will re-deliver the SharedPC settings and re-tattoo the registry. Step 2 below covers the "just re-run the cleanup" remedy for that case. If you're permanently decommissioning a device from the SharedPC fleet — a different scenario that is not the subject of this procedure — unassign the SharedPC profile in Intune first; what follows assumes the assignment stays intact.
-
Open the maintenance window. Run
Remove-SharedPCTattoo.ps1on the target device, in an elevated PowerShell session. The script backs up affected keys to timestamped.regfiles underC:\IT_Tools\SharedPCBackup\, removes theAllowed*family and the SharedPC key, stops the SharedPC Account Manager service so it doesn't recreate state mid-window, and restarts Explorer to flush the shell namespace cache. Then runTest-SharedPCMaintenanceState.ps1— every check should pass before you start work. -
Perform maintenance. If you notice restrictions returning mid-window — drives vanishing from This PC, Control Panel re-blocking — that means an Intune policy sync ran and re-delivered the SharedPC settings, which re-tattooed the registry. The remedy is to re-run
Remove-SharedPCTattoo.ps1. The default Intune sync cycle is 8 hours, so this is a real risk only on long maintenance windows or when a sync is triggered manually (by the user, by an Intune admin, or by a co-management agent). Each re-run produces a fresh timestamped backup; the most recent backup is whatRestore-SharedPCTattoo.ps1will restore from in step 3. -
Close the maintenance window. Run
Restore-SharedPCTattoo.ps1when work is complete. The script imports the most recent backup written by the window-opening script (or a specific path if you pass-BackupPath), restarts the SharedPC Account Manager service, restarts Explorer, and verifies the keys are back in place. The device returns to its fully-restricted state without any Intune-side action — the assignment was never disturbed.
Between steps 1 and 3, every SharedPC-derived restriction is gone — the drive-hiding allowlists, the SharedPC key, and the Account Manager service. AppLocker / WDAC and any other policies not delivered by the SharedPC CSP remain in force, but a walk-up user gets an unrestricted desktop. Schedule maintenance during off-hours or in a physically locked room, RDP in rather than working at the console, and always run Restore-SharedPCTattoo.ps1 (and confirm it succeeds) before disconnecting.
Window-opening script: Remove-SharedPCTattoo.ps1
Run on the target device in an elevated PowerShell session at the start of a maintenance window. The script writes timestamped .reg backups under C:\IT_Tools\SharedPCBackup\, one per affected key — Restore-SharedPCTattoo.ps1 reads them at end-of-window. To roll back manually, double-click the .reg files or run reg.exe import <path>.
Download: Remove-SharedPCTattoo.ps1
<#
.SYNOPSIS
Removes tattooed registry remnants from the SharedPC CSP, opening
a maintenance window in which Explorer, Control Panel, and the
full drive namespace work normally.
.DESCRIPTION
The SharedPC CSP writes registry values outside the standard Policies
hives, so unassigning the policy in Intune does not remove them. This
script removes the Allowed* family of CLSID-keyed allowlists at
HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\, removes the
SharedPC key, stops the Account Manager service, and restarts Explorer
to flush the shell namespace cache.
No Intune-side action is required first. The policy assignment is
not disturbed; this script simply removes the registry state that
is doing the actual enforcement, then Restore-SharedPCTattoo.ps1
puts it back at end-of-window.
.PARAMETER BackupRoot
Directory where backup .reg files will be written. A timestamped
subdirectory is created under this path. Default: C:\IT_Tools\SharedPCBackup.
.PARAMETER NoExplorerRestart
Skip the Explorer restart at the end. Use only if you intend to
restart Explorer manually or sign out and back in.
#>
[CmdletBinding()]
param(
[string]$BackupRoot = "C:\IT_Tools\SharedPCBackup",
[switch]$NoExplorerRestart
)
$ErrorActionPreference = 'Stop'
# Require elevation
$identity = [Security.Principal.WindowsIdentity]::GetCurrent()
$principal = New-Object Security.Principal.WindowsPrincipal($identity)
if (-not $principal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)) {
throw "This script must be run from an elevated PowerShell session."
}
$timestamp = Get-Date -Format "yyyy-MM-dd_HHmmss"
$backupDir = Join-Path $BackupRoot $timestamp
New-Item -ItemType Directory -Path $backupDir -Force | Out-Null
Write-Host "Backups -> $backupDir" -ForegroundColor Cyan
# Keys to remove if present. Order matters only insofar as the SharedPC key
# is removed last so that the Account Manager has no chance to react.
$keysToRemove = @(
'HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\AllowedNavigation',
'HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\AllowedEnumeration',
'HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\WCOSNavigationBaselineAllowedlist',
'HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\WCOSEnumerationBaselineAllowedlist',
'HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\SharedPC'
)
# AllowedStorageLocations is a REG_DWORD value on the Explorer key (not a
# subkey of its own), so it is handled separately to avoid deleting the
# whole Explorer key by accident.
$explorerKey = 'HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer'
# Stop the SharedPC Account Manager first so it does not race the registry removal.
$shpamsvc = Get-Service -Name shpamsvc -ErrorAction SilentlyContinue
if ($shpamsvc -and $shpamsvc.Status -eq 'Running') {
Write-Host "Stopping shpamsvc (SharedPC Account Manager)..." -ForegroundColor Yellow
Stop-Service -Name shpamsvc -Force
}
# Back up + remove each Allowed* / SharedPC key
foreach ($key in $keysToRemove) {
$psPath = $key -replace '^HKLM\\', 'HKLM:\'
if (Test-Path -LiteralPath $psPath) {
$safeName = ($key -replace '[\\:]', '_')
$backupFile = Join-Path $backupDir "$safeName.reg"
Write-Host "Backing up $key" -ForegroundColor Gray
& reg.exe export $key $backupFile /y | Out-Null
Write-Host "Removing $key" -ForegroundColor Yellow
Remove-Item -LiteralPath $psPath -Recurse -Force
} else {
Write-Host "Skipping $key (not present)" -ForegroundColor DarkGray
}
}
# Handle the AllowedStorageLocations VALUE on the Explorer key
$explorerPsPath = $explorerKey -replace '^HKLM\\', 'HKLM:\'
if (Test-Path -LiteralPath $explorerPsPath) {
$existing = (Get-Item -LiteralPath $explorerPsPath).GetValue('AllowedStorageLocations', $null)
if ($null -ne $existing) {
$backupFile = Join-Path $backupDir 'AllowedStorageLocations_value.reg'
Write-Host "Backing up AllowedStorageLocations value" -ForegroundColor Gray
& reg.exe export $explorerKey $backupFile /y | Out-Null
Write-Host "Removing AllowedStorageLocations value" -ForegroundColor Yellow
Remove-ItemProperty -LiteralPath $explorerPsPath -Name 'AllowedStorageLocations' -Force
}
}
# Restart Explorer to flush the shell namespace cache. The Allowed* family is
# read at namespace enumeration time, but Explorer caches the result for the
# lifetime of the process.
if (-not $NoExplorerRestart) {
Write-Host "Restarting Explorer..." -ForegroundColor Yellow
Stop-Process -Name explorer -Force -ErrorAction SilentlyContinue
Start-Sleep -Milliseconds 500
Start-Process explorer.exe
}
Write-Host ""
Write-Host "Maintenance window opened." -ForegroundColor Green
Write-Host "Backup directory: $backupDir" -ForegroundColor Cyan
Write-Host "Run Test-SharedPCMaintenanceState.ps1 to verify."
exit 0
Verification script: Test-SharedPCMaintenanceState.ps1
Run after Remove-SharedPCTattoo.ps1 and before starting work. Exit code 0 if every check passes, 1 otherwise — suitable as a gate in a runbook or scheduled-task wrapper.
Download: Test-SharedPCMaintenanceState.ps1
<#
.SYNOPSIS
Verifies that a Shared PC has been fully released from SharedPC CSP
enforcement and is ready for admin maintenance work.
.DESCRIPTION
Confirms that both the SharedPC CSP key AND the tattooed Allowed*
family of allowlists are absent, and that drive enumeration is
working at the volume layer. Intended to be run after
Remove-SharedPCTattoo.ps1 in the maintenance workflow.
Exit codes: 0 = all checks passed, 1 = one or more checks failed.
#>
[CmdletBinding()]
param()
function New-CheckResult {
param($Check, $Path, $Present, $ExpectedAbsent = $true)
[PSCustomObject]@{
Check = $Check
Path = $Path
Present = $Present
Passed = if ($ExpectedAbsent) { -not $Present } else { $Present }
}
}
$results = @()
# Tattooed keys that must be absent
$keysExpectedAbsent = @{
'SharedPC key' = 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\SharedPC'
'AllowedNavigation' = 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\AllowedNavigation'
'AllowedEnumeration' = 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\AllowedEnumeration'
'WCOSNavigationBaselineAllowedlist' = 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\WCOSNavigationBaselineAllowedlist'
'WCOSEnumerationBaselineAllowedlist' = 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\WCOSEnumerationBaselineAllowedlist'
}
foreach ($entry in $keysExpectedAbsent.GetEnumerator()) {
$present = Test-Path -LiteralPath $entry.Value
$results += New-CheckResult -Check $entry.Key -Path $entry.Value -Present $present
}
# AllowedStorageLocations is a REG_DWORD value (not a key) that SharedPC can
# place either on the Explorer key directly OR as a child value of one of the
# Allowed*/WCOS* allowlist subkeys. Observed in the field inside AllowedEnumeration
# on some devices. Check all plausible parent locations so the test isn't a
# false-negative when the value survives in a subkey Remove should have cleared.
$asCandidateParents = @(
'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer',
'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\AllowedNavigation',
'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\AllowedEnumeration',
'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\WCOSNavigationBaselineAllowedlist',
'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\WCOSEnumerationBaselineAllowedlist'
)
$asFoundAt = $null
foreach ($parent in $asCandidateParents) {
if (Test-Path -LiteralPath $parent) {
if ($null -ne (Get-Item -LiteralPath $parent).GetValue('AllowedStorageLocations', $null)) {
$asFoundAt = $parent
break
}
}
}
$results += New-CheckResult -Check 'AllowedStorageLocations value' `
-Path $(if ($asFoundAt) { "$asFoundAt\AllowedStorageLocations" } else { 'Explorer key or any Allowed*/WCOS* subkey' }) `
-Present ($null -ne $asFoundAt)
# SharedPC Account Manager service should be Stopped
$shpamsvc = Get-Service -Name shpamsvc -ErrorAction SilentlyContinue
$shpamRunning = ($shpamsvc -and $shpamsvc.Status -eq 'Running')
$results += New-CheckResult -Check 'shpamsvc not running' `
-Path 'Service: shpamsvc' -Present $shpamRunning
# Drive enumeration sanity -- at least one drive letter should be visible.
# Force-array with @() so .Count is defined when the filter yields a single CimInstance.
$driveCount = @(Get-Volume | Where-Object { $_.DriveLetter }).Count
$results += New-CheckResult -Check 'Drive letters enumerable' `
-Path 'Get-Volume' -Present ($driveCount -gt 0) -ExpectedAbsent:$false
$results | Format-Table -AutoSize
if ($results | Where-Object { -not $_.Passed }) {
Write-Host "FAIL: one or more checks did not pass -- maintenance state is not clean." -ForegroundColor Red
exit 1
} else {
Write-Host "PASS: all checks passed -- maintenance state is clean." -ForegroundColor Green
exit 0
}
Restore script: Restore-SharedPCTattoo.ps1
Run at the end of the maintenance window to put the device back into its SharedPC state. By default the script restores from the most recent timestamped subdirectory under C:\IT_Tools\SharedPCBackup\ — that's the backup from the most recent Remove-SharedPCTattoo.ps1 run, including any mid-window re-runs. Pass -BackupPath to restore a specific session.
Download: Restore-SharedPCTattoo.ps1
<#
.SYNOPSIS
Restores the SharedPC tattoo state previously captured by
Remove-SharedPCTattoo.ps1, returning the device to its locked-down
SharedPC configuration.
.DESCRIPTION
Imports the timestamped .reg files written by Remove-SharedPCTattoo.ps1,
restarts the SharedPC Account Manager service, and restarts Explorer
so the shell picks up the restored Allowed* family of allowlists.
By default, restores from the most recent backup subdirectory under
BackupRoot. Pass -BackupPath to restore from a specific session.
No Intune-side action is required — the policy assignment was never
disturbed; this script simply puts back the registry state that the
window-opening script removed.
.PARAMETER BackupRoot
Root directory containing timestamped backup subdirectories.
Default: C:\IT_Tools\SharedPCBackup.
.PARAMETER BackupPath
Path to a specific timestamped backup subdirectory. If omitted,
the most recent directory under BackupRoot is used.
.PARAMETER NoExplorerRestart
Skip the Explorer restart at the end. The restored Allowed* family
will not take effect for the current Explorer session until it is
restarted or the user signs out and back in.
#>
[CmdletBinding()]
param(
[string]$BackupRoot = "C:\IT_Tools\SharedPCBackup",
[string]$BackupPath,
[switch]$NoExplorerRestart
)
$ErrorActionPreference = 'Stop'
# Require elevation
$identity = [Security.Principal.WindowsIdentity]::GetCurrent()
$principal = New-Object Security.Principal.WindowsPrincipal($identity)
if (-not $principal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)) {
throw "This script must be run from an elevated PowerShell session."
}
# Determine which backup to restore
if (-not $BackupPath) {
if (-not (Test-Path -LiteralPath $BackupRoot)) {
throw "No backup root found at $BackupRoot. Was Remove-SharedPCTattoo.ps1 ever run on this machine?"
}
$latest = Get-ChildItem -LiteralPath $BackupRoot -Directory |
Sort-Object Name -Descending |
Select-Object -First 1
if (-not $latest) {
throw "No backup subdirectories found under $BackupRoot."
}
$BackupPath = $latest.FullName
}
if (-not (Test-Path -LiteralPath $BackupPath)) {
throw "Backup path does not exist: $BackupPath"
}
Write-Host "Restoring from: $BackupPath" -ForegroundColor Cyan
$regFiles = @(Get-ChildItem -LiteralPath $BackupPath -Filter '*.reg')
if ($regFiles.Count -eq 0) {
throw "No .reg files found in $BackupPath."
}
foreach ($file in $regFiles) {
Write-Host "Importing $($file.Name)" -ForegroundColor Yellow
# reg.exe writes "The operation completed successfully." to stderr even on
# success. Under the script-wide $ErrorActionPreference='Stop', redirecting
# that line with `2>&1` (or `2>$null`) folds it into the PS error stream,
# where PS 5.1 wraps it as a NativeCommandError and terminates the script.
# A try/catch swallows the termination but PS also resets $LASTEXITCODE to
# -1 during the error-unwind -- so a later $LASTEXITCODE check can't tell
# success from failure. Run the native call in a scriptblock with a local
# 'Continue' preference: stderr stays non-terminating AND $LASTEXITCODE
# reliably reflects reg.exe's real exit code.
$regExitCode = & {
$ErrorActionPreference = 'Continue'
& reg.exe import $file.FullName 2>&1 | Out-Null
$LASTEXITCODE
}
if ($regExitCode -ne 0) {
Write-Host " WARNING: reg import returned exit code $regExitCode for $($file.Name)" -ForegroundColor Red
}
}
# Restart the SharedPC Account Manager so it can resume profile cleanup duties.
# Service operations are wrapped so a startup failure (e.g. restoring from a
# partial backup where the full SharedPC key set is absent) does not terminate
# the script; the Explorer restart and sanity check below should still run.
$shpamsvc = Get-Service -Name shpamsvc -ErrorAction SilentlyContinue
$shpamServiceWarning = $null
if ($shpamsvc) {
if ($shpamsvc.StartType -eq 'Disabled') {
Write-Host "Re-enabling shpamsvc (was Disabled)" -ForegroundColor Yellow
try {
Set-Service -Name shpamsvc -StartupType Automatic
} catch {
$shpamServiceWarning = "Set-Service shpamsvc -StartupType Automatic failed: $($_.Exception.Message)"
Write-Host " WARNING: $shpamServiceWarning" -ForegroundColor Red
}
}
if ($shpamsvc.Status -ne 'Running') {
Write-Host "Starting shpamsvc (SharedPC Account Manager)" -ForegroundColor Yellow
try {
Start-Service -Name shpamsvc
} catch {
$shpamServiceWarning = "Start-Service shpamsvc failed: $($_.Exception.Message). This commonly indicates the restored backup did not include the full SharedPC key set."
Write-Host " WARNING: $shpamServiceWarning" -ForegroundColor Red
}
}
}
# Restart Explorer so it re-enumerates the namespace with the restored Allowed* family
if (-not $NoExplorerRestart) {
Write-Host "Restarting Explorer..." -ForegroundColor Yellow
Stop-Process -Name explorer -Force -ErrorAction SilentlyContinue
Start-Sleep -Milliseconds 500
Start-Process explorer.exe
}
# Sanity-check: verify each key defined in each imported .reg file is now
# present in the registry. This replaces a previous hard-coded list of expected
# keys (SharedPC, AllowedNavigation) which falsely flagged legitimate
# partial-backup restores on devices whose tattoo footprint didn't include
# those specific keys.
$missing = @()
foreach ($file in $regFiles) {
# Each .reg file contains one or more "[HKEY_...\path]" header lines.
# Extract them and verify each is present in the live registry.
$keyHeaders = @(Select-String -LiteralPath $file.FullName -Pattern '^\[(HKEY_[^\]]+)\]' |
ForEach-Object { $_.Matches.Groups[1].Value } |
Sort-Object -Unique)
foreach ($header in $keyHeaders) {
if (-not (Test-Path -LiteralPath "Registry::$header")) {
$missing += "$($file.Name) -> $header"
}
}
}
$hadFailure = $false
Write-Host ""
if ($missing.Count -gt 0) {
Write-Host "FAIL: keys defined in the backup did not appear after import:" -ForegroundColor Red
$missing | ForEach-Object { Write-Host " $_" -ForegroundColor Red }
Write-Host "Inspect $BackupPath and re-run, or check for registry ACL issues." -ForegroundColor Red
$hadFailure = $true
}
if ($shpamServiceWarning) {
Write-Host "NOTE: shpamsvc could not be returned to its running state:" -ForegroundColor Yellow
Write-Host " $shpamServiceWarning" -ForegroundColor Yellow
Write-Host "If the restored backup did not include the full SharedPC key set," -ForegroundColor Yellow
Write-Host "this is expected -- shpamsvc will not start without the SharedPC configuration." -ForegroundColor Yellow
$hadFailure = $true
}
if ($hadFailure) {
exit 1
}
Write-Host "Restore complete. All backed-up keys are present at their expected locations." -ForegroundColor Green
Write-Host "Backup source: $BackupPath" -ForegroundColor Cyan
exit 0
Treat all three scripts as production artifacts: store them in the same source-control location as your Intune policy exports, version them alongside policy changes, and require a code review for changes. The window-opening script can destroy administratively-significant state if pointed at the wrong machine, and the rollback path is the .reg backup it produces — which only exists if the script ran cleanly to that point.
To exercise Restore-SharedPCTattoo.ps1 (or test the full Remove → Restore cycle) without a live SharedPC-provisioned device, download sharedpc-sample-tattoo.zip — a sanitized capture of the real SharedPC CSP footprint from an Intune-managed lab device. Three .reg files plus a README. Import the files on a test machine to simulate the tattoo, then run the scripts against the simulated state.
Lab: Power Settings ("Always Ready for Updates")
- Type: Settings Catalog
- Goal: Ensures machines never sleep so they can receive "Simultaneous" updates.
| Category / Setting | Value | Explanation |
|---|---|---|
| Administrative Templates / Power Management / Video and Display Settings / Turn off the display (plugged in) | 1200 sec | Display goes dark after 20 minutes idle. |
| Administrative Templates / Power Management / Sleep Settings / Specify the system sleep timeout (plugged in) | 0 (never) | Keeps a logged-in PC awake — required for the overnight maintenance window to actually run. |
| Administrative Templates / Power Management / Hard Disk Settings / Turn Off the hard disk (plugged in) | 1200 sec | Drive spins down after 20 minutes idle. Does not affect the PC's wake state. |
| Power / Unattended Sleep Timeout Plugged In | 0 (never) | Keeps the PC awake at the lock screen when no user is signed in — needed for overnight updates on idle machines. |
Lab: Device Restrictions (The "Clean" Look)
- Type: Settings Catalog
- Goal: Enforces the "Kiosk-like" visual restrictions without breaking File Explorer.
These restrictions apply device-wide — they affect every user who signs in, including administrators. Default user experience customizations (browser settings, app associations, desktop shortcuts) can be set up either through the traditional admin-driven configure-and-copy default profile workflow — supported by the admin-exemption mechanism above — or through Intune policy. See Customizing the Default User Experience below for the policy-based approach.
Admins who need to work on a restricted lab machine should follow the Admin Maintenance Procedure above — run Remove-SharedPCTattoo.ps1 to open a maintenance window, do the work, then run Restore-SharedPCTattoo.ps1 to close it back up. Typing C:\ directly into the File Explorer address bar does not work around the restriction: the tattooed Allowed* family of allowlists at HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\ blocks path-based navigation alongside the "This PC" namespace, and there is no in-session workaround that doesn't involve removing those allowlists from the registry.
| Category/ Setting | Value | Explanation |
|---|---|---|
| Administrative Templates / Control Panel/ Prohibit access to Control Panel and PC settings | Enabled | Blocks tampering with OS settings. |
| Administrative Templates / Personalization/ Prevent changing desktop background | Enabled | Blocks setting wallpaper. |
| Administrative Templates / Personalization/ Prevent changing lock screen image | Enabled | Maintains org branding. |
| Administrative Templates / Windows Components / File Explorer/ Hide these specified drives in My Computer | Restrict A, B, C and D drives only | Hides local drives to prevent browsing system files, mapped drives visible. Admins can still access drives by typing the path directly in File Explorer. |
| Windows Hello For Business/ Use Windows Hello For Business (Device) | false | Prevent WHFB on shared machines. |
Lab: Windows Update Ring (The "Uniformity" Engine)
- Type: Update ring for Windows 10 and later
- Goal: Ensures all shared machines update simultaneously rather than staggering.
This ring is a dedicated policy for shared devices. It operates independently of the IT/Dev, Pilot, General Ops, and Critical Ops rings described in Mobile & Endpoint Security: Windows Update Rings. Do not assign shared PC devices to any of those rings.
| Setting Name | Recommended Value | Why? |
|---|---|---|
| UPDATE SETTINGS | ||
| Microsoft product updates | Allow | Ensures Office/Edge get patched alongside Windows. |
| Windows drivers | Allow | Critical for Shared PC stability. |
| Quality update deferral period (days) | 0 | Critical: Ensures all machines see the update the moment it releases (Uniformity). |
| Feature update deferral period (days) | 0 | Same reason. We don't want fragmentation. |
| Upgrade Windows 10 devices... | No | Safety: Keep this "No". You should control major OS upgrades (e.g., moving to Windows 12) using a separate Feature Update policy, not this ring. |
| Enable pre-release builds | Not Configured | Critical: Labs must be on "General Availability" channel, not Insider/Beta channel. |
| USER EXPERIENCE SETTINGS | ||
| Automatic update behavior | Auto install and restart at maintenance time | Matches "Shared PC" maintenance window (3am). |
| Active hours start | 7 AM | Covers early usage. |
| Active hours end | 10 PM | Covers evening usage. |
| Option to pause Windows updates | Disable | Shared PC users should never be able to stop a security patch. |
| Option to check for Windows updates | Disable | Shared PC users should not be checking for updates. |
| Change notification update level | Disable all notifications... | Shared PC users should not be deciding whether to schedule reboots. |
| DEADLINE SETTINGS | ||
| Use deadline settings | Allow | Unlocks the settings below. |
| Deadline for feature updates | 2 (days) | Forces the install quickly. |
| Deadline for quality updates | 2 (days) | If a machine was off for the weekend, update it NOW. |
| Grace period | 0 (days) | Once the 2-day deadline hits, reboot immediately (during maintenance). |
| Auto reboot before deadline | Yes | If the machine is idle at the login screen, reboot to finish the patch. |
Lab: AppLocker — Block Unauthorized Apps and Shell Hosts
- Type: Templates > Custom
- Goal: Block unauthorized executables (e.g.,
.exefrom Downloads or USB) and the command-line shells that turn a low-trust Shared PC seat into a scripting console.
Path-based allow rules at %WINDIR%\* and %PROGRAMFILES%\* permit every shell host on Windows by accident — cmd.exe, powershell.exe, powershell_ise.exe, pwsh.exe, wscript.exe, cscript.exe, mshta.exe, regedit.exe all live there. The policy below uses <Exceptions> inside each Users-scoped rule to carve them out while keeping the Administrators rule (path *) unrestricted, so lab admins keep full shell access during maintenance.
A Deny rule scoped to BUILTIN\Users (S-1-5-32-545) also fires for administrators, since every admin is a member of BUILTIN\Users. <Exceptions> inside the Users Allow rule scopes the carve-out exactly to non-admins.
Name: Enforce EXE Restrictions
Description: Blocks everything not in Windows/Program Files; carves out shell hosts for non-admins.
OMA-URI: ./Vendor/MSFT/AppLocker/ApplicationLaunchRestrictions/Native/EXE/Policy (no spaces in the path)
Data type: String
Value: paste the XML below.
<RuleCollection Type="Exe" EnforcementMode="Enabled">
<FilePathRule Id="fd686d83-a829-4351-8ff4-27c7de5755d2" Name="All files for Administrators" Description="Allows members of the Administrators group to run all applications." UserOrGroupSid="S-1-5-32-544" Action="Allow">
<Conditions>
<FilePathCondition Path="*" />
</Conditions>
</FilePathRule>
<FilePathRule Id="9428c9b1-60d4-493f-b839-9d1da1692257" Name="Windows folder, except shell hosts" Description="Allows BUILTIN\Users to run executables in %WINDIR%, except command-line shells and scripting hosts." UserOrGroupSid="S-1-5-32-545" Action="Allow">
<Conditions>
<FilePathCondition Path="%WINDIR%\*" />
</Conditions>
<Exceptions>
<FilePathCondition Path="%WINDIR%\System32\cmd.exe" />
<FilePathCondition Path="%WINDIR%\SysWOW64\cmd.exe" />
<FilePathCondition Path="%WINDIR%\System32\WindowsPowerShell\v1.0\powershell.exe" />
<FilePathCondition Path="%WINDIR%\SysWOW64\WindowsPowerShell\v1.0\powershell.exe" />
<FilePathCondition Path="%WINDIR%\System32\WindowsPowerShell\v1.0\powershell_ise.exe" />
<FilePathCondition Path="%WINDIR%\SysWOW64\WindowsPowerShell\v1.0\powershell_ise.exe" />
<FilePathCondition Path="%WINDIR%\System32\wscript.exe" />
<FilePathCondition Path="%WINDIR%\SysWOW64\wscript.exe" />
<FilePathCondition Path="%WINDIR%\System32\cscript.exe" />
<FilePathCondition Path="%WINDIR%\SysWOW64\cscript.exe" />
<FilePathCondition Path="%WINDIR%\System32\mshta.exe" />
<FilePathCondition Path="%WINDIR%\SysWOW64\mshta.exe" />
<FilePathCondition Path="%WINDIR%\regedit.exe" />
<FilePathCondition Path="%WINDIR%\SysWOW64\regedit.exe" />
</Exceptions>
</FilePathRule>
<FilePathRule Id="921f6095-f287-4605-bf53-277437833072" Name="Program Files, except PowerShell 7" Description="Allows BUILTIN\Users to run executables in %PROGRAMFILES%, except pwsh.exe." UserOrGroupSid="S-1-5-32-545" Action="Allow">
<Conditions>
<FilePathCondition Path="%PROGRAMFILES%\*" />
</Conditions>
<Exceptions>
<FilePathCondition Path="%PROGRAMFILES%\PowerShell\*\pwsh.exe" />
</Exceptions>
</FilePathRule>
<FilePathRule Id="a61c8b2c-a319-4cd0-9690-d2177cad7b51" Name="Program Files (x86)" Description="Allows BUILTIN\Users to run executables in the 32-bit Program Files folder." UserOrGroupSid="S-1-5-32-545" Action="Allow">
<Conditions>
<FilePathCondition Path="%OSDRIVE%\Program Files (x86)\*" />
</Conditions>
</FilePathRule>
</RuleCollection>
When AppLocker is enforcing on a device, PowerShell automatically switches into a restricted mode called Constrained Language Mode. Most of the commands attackers rely on just stop working — you don't configure it, it kicks in because AppLocker is on. See How App Control works with PowerShell for the full description of what's blocked.
Audit trail beyond the block
The AppLocker block itself produces event ID 8004 in the Microsoft-Windows-AppLocker/EXE and DLL log — that is the preventive-control evidence. If you also want a record of any PowerShell script block that actually executed (admin sessions, an alternate host, a future bypass), enable PowerShell Script Block Logging via the CSP node ./Device/Vendor/MSFT/Policy/Config/WindowsPowerShell/TurnOnPowerShellScriptBlockLogging (reference). The policy writes event ID 4104 to the Microsoft-Windows-PowerShell/Operational channel, which any Windows event collector can pick up.
For the Sentinel-specific implementation — Data Collection Rule channel configuration, hunting KQL for the suspicious-script-block pattern, and the Save-dialog conventions for grouping Shared PC queries — see SIEM Strategy.
What an assessor asks
"Show me how you'd know if a standard user opened PowerShell on one of these Shared PCs." The preventive answer is Microsoft-Windows-AppLocker/EXE and DLL event ID 8004, which confirms the launch was blocked at the kernel boundary. If you're also forwarding Windows event logs to a SIEM, the 4104 PowerShell script-block event completes the picture for anything that did execute. The AppLocker XML is in the Intune policy export, versioned alongside the SharedPC configuration as a single deployable unit.
Lab: Office 365 Licensing
Type: Settings Catalog
Goal: Prevents licensing errors on shared hardware.
| Category | Setting Name | Value | Description |
|---|---|---|---|
| Microsoft Office 2016 (Machine) \ Licensing Settings | Use shared computer activation | Enabled | Allows unlimited users to activate Office on the machine without consuming their 5-device limit. |
Lab: Start Menu
Type: Templates > Device Restrictions
Goal: Pins academic apps to the Start Menu/Taskbar.
| Setting Name | Value | Description |
|---|---|---|
| Start menu layout | [Upload XML File] | XML file pinning Word, Excel, Chrome, and required apps. |
Start menu layout: Paste the entire XML code block below:
<LayoutModificationTemplate
xmlns:defaultlayout="http://schemas.microsoft.com/Start/2014/FullDefaultLayout"
xmlns:start="http://schemas.microsoft.com/Start/2014/StartLayout"
xmlns:taskbar="http://schemas.microsoft.com/Start/2014/TaskbarLayout"
Version="1">
<LayoutOptions StartTileGroupCellWidth="6" />
<DefaultLayoutOverride>
<StartLayoutCollection>
<defaultlayout:StartLayoutGroup Name="Lab Tools">
<start:DesktopApplicationTile Size="2x2" Column="0" Row="0" DesktopApplicationLinkPath="%ALLUSERSPROFILE%\Microsoft\Windows\Start Menu\Programs\Google Chrome.lnk" />
<start:DesktopApplicationTile Size="2x2" Column="2" Row="0" DesktopApplicationID="Microsoft.Windows.Explorer" />
<start:DesktopApplicationTile Size="2x2" Column="4" Row="0" DesktopApplicationID="Microsoft.MicrosoftEdge.Stable_8wekyb3d8bbwe!App" />
<start:DesktopApplicationTile Size="2x2" Column="0" Row="2" DesktopApplicationID="Microsoft.Office.WINWORD.EXE.15" />
<start:DesktopApplicationTile Size="2x2" Column="2" Row="2" DesktopApplicationID="Microsoft.Office.EXCEL.EXE.15" />
<start:DesktopApplicationTile Size="2x2" Column="4" Row="2" DesktopApplicationID="Microsoft.Office.POWERPNT.EXE.15" />
</defaultlayout:StartLayoutGroup>
</StartLayoutCollection>
</DefaultLayoutOverride>
<CustomTaskbarLayoutCollection PinListPlacement="Replace">
<defaultlayout:TaskbarLayout>
<taskbar:TaskbarPinList>
<taskbar:DesktopApp DesktopApplicationLinkPath="%ALLUSERSPROFILE%\Microsoft\Windows\Start Menu\Programs\Google Chrome.lnk" />
<taskbar:DesktopApp DesktopApplicationID="Microsoft.Windows.Explorer" />
<taskbar:DesktopApp DesktopApplicationID="Microsoft.Office.WINWORD.EXE.15" />
<taskbar:DesktopApp DesktopApplicationID="Microsoft.Office.EXCEL.EXE.15" />
<taskbar:DesktopApp DesktopApplicationID="Microsoft.Office.POWERPNT.EXE.15" />
</taskbar:TaskbarPinList>
</defaultlayout:TaskbarLayout>
</CustomTaskbarLayoutCollection>
</LayoutModificationTemplate>
Customizing the Default User Experience
There are two ways to seed the default user experience on a Shared PC. The traditional approach is the configure-and-copy default profile workflow: an admin signs in, sets up the desired baseline (browser bookmarks, app defaults, desktop shortcuts, printer mappings), and copies the profile to C:\Users\Default so every new user inherits those settings. The admin-exemption mechanism in Exempt Admins from Profile Deletion is what keeps the admin profile available for this work between sign-outs.
The alternative covered in this section is Intune policy-based delivery: every customization is expressed as a Settings Catalog policy, an Administrative Template, or a platform script, assigned to the device group. A few characteristics worth considering when choosing between the two:
- Per-fleet propagation — a single policy change rolls out to every machine in the device group; no per-machine repeat needed
- Update durability — Windows feature updates that reset or invalidate the default profile registry hive don't disturb policies, since policies re-apply on every check-in
- Compatibility with "delete immediately" — Shared PC Mode's profile-deletion policy doesn't fight policy-delivered customizations, which apply at sign-in rather than living in a copied profile
- Auditable — Intune surfaces what's deployed and where; manually configured profiles produce no equivalent record
The rest of this section walks through the policy-based approach for the most common customization categories.
Browser Configuration (Edge)
Type: Settings Catalog — Administrative Templates > Microsoft Edge
| Setting | Value | What it replaces |
|---|---|---|
| Configure the home page URL | https://portal.college.edu (or your institution's portal) | Manually setting the homepage in Edge |
| Configure the new tab page URL | Same as above, or about:blank | New tab page configuration |
| Configure Managed Bookmarks | JSON array of bookmarks (see below) | Manually adding bookmarks |
| Configure the default search provider | Enabled, with search URL for your preferred provider | Changing default search engine |
| Control which extensions are installed silently | Extension IDs for any required browser extensions | Manually installing extensions |
Managed Bookmarks example (paste into the "Configure Managed Bookmarks" value field):
[
{"toplevel_name": "College Resources"},
{"name": "Internal Portal", "url": "https://portal.college.edu"},
{"name": "Library", "url": "https://library.college.edu"},
{"name": "Canvas LMS", "url": "https://college.instructure.com"},
{"name": "Office 365", "url": "https://www.office.com"}
]
Default Application Associations
Type: Settings Catalog
Setting: Default Associations Configuration File
What it replaces: Manually setting "Open with" defaults in Settings and copying the profile.
How to create the XML:
-
On a reference machine, configure the desired default app associations (e.g.,
.pdf→ Adobe Acrobat,.docx→ Word) -
Export the associations:
DISM /Online /Export-DefaultAppAssociations:"C:\IT_Tools\DefaultAppAssociations.xml" -
In the Settings Catalog policy, upload the exported XML under Default Associations Configuration File
This applies device-wide — every user who signs in gets the same default app associations without any profile copy.
Desktop Shortcuts
Type: Intune Platform Script
What it replaces: Manually creating desktop shortcuts and copying the profile.
Deploy a PowerShell script that creates shortcuts in C:\Users\Public\Desktop. Public Desktop shortcuts appear for every user on the machine automatically.
# Deploy via Intune > Devices > Scripts > Platform scripts
# Run as: System | Run in 64-bit host: Yes
$publicDesktop = "C:\Users\Public\Desktop"
$shell = New-Object -ComObject WScript.Shell
# Example: Canvas LMS shortcut
$shortcut = $shell.CreateShortcut("$publicDesktop\Canvas LMS.lnk")
$shortcut.TargetPath = "https://college.instructure.com"
$shortcut.IconLocation = "C:\Program Files (x86)\Microsoft\Edge\Application\msedge.exe,0"
$shortcut.Save()
# Example: Internal Portal shortcut
$shortcut = $shell.CreateShortcut("$publicDesktop\Internal Portal.lnk")
$shortcut.TargetPath = "https://portal.college.edu"
$shortcut.IconLocation = "C:\Program Files (x86)\Microsoft\Edge\Application\msedge.exe,0"
$shortcut.Save()
Printer Mappings
Type: Settings Catalog (Universal Print) or Intune Platform Script
Option 1 — Universal Print (preferred): If the college uses Universal Print, deploy printer mappings via the Settings Catalog Universal Print policy. This provisions printers automatically for every user on the device.
Option 2 — Platform Script: For traditional print servers, deploy a PowerShell script:
# Deploy via Intune > Devices > Scripts > Platform scripts
# Run as: System | Run in 64-bit host: Yes
# Add network printer
Add-PrinterPort -Name "IP_Lab_Printer" -PrinterHostAddress "10.1.50.25" -ErrorAction SilentlyContinue
Add-Printer -Name "Lab Building A - HP LaserJet" -DriverName "HP Universal Printing PCL6" -PortName "IP_Lab_Printer" -ErrorAction SilentlyContinue
# Set as default
(Get-WmiObject -Query "SELECT * FROM Win32_Printer WHERE Name='Lab Building A - HP LaserJet'").SetDefaultPrinter() | Out-Null
File Explorer Defaults
Type: Settings Catalog — Administrative Templates > File Explorer
| Setting | Value | What it replaces |
|---|---|---|
| Default folder for the Open File Dialog | This PC | Changing File Explorer's default open location |
| Turn off caching of thumbnails | Enabled | Reduces profile size on shared PCs |
| Turn off display of recent search entries | Enabled | Prevents user search history from persisting |
Office First-Run and Privacy Prompts
Type: Settings Catalog — Administrative Templates > Microsoft Office
| Setting | Value | What it replaces |
|---|---|---|
| Disable First Run Movie | Enabled | Suppresses the Office "Welcome" animation on every new profile |
| Disable First Run on application boot | Enabled | Skips the "What's New" pane |
| Disable Opt-in Wizard on first run | Enabled | Suppresses the privacy/telemetry opt-in prompt |
| Send personal information | Disabled | Prevents Office from prompting for optional connected experiences |
Notification Suppression
Type: Settings Catalog — Administrative Templates > Notifications
| Setting | Value | What it replaces |
|---|---|---|
| Turn off toast notifications on the lock screen | Enabled | Prevents notifications from appearing on the lock screen between user sessions |
| Turn off Notifications Network Usage | Enabled | Reduces unnecessary network traffic on shared hardware |
Accessibility preferences (high contrast, display scaling, narrator, magnifier) are intentionally per-user settings. Forcing them device-wide via policy would create problems for users who don't need them. If specific lab machines require accessibility defaults (e.g., an accessibility-focused lab), create a separate Settings Catalog policy with the appropriate display and ease-of-access settings and assign it to that device group only.
📩 Don't Miss the Next Solution
Join the list to see the real-time solutions I'm delivering to my GCC High clients.