Skip to main content

Kiosk Cleanup with Intune Remediations

The Problem
#

Windows ships with a lot of things nobody asked for. On a user laptop, some of that is annoying. On a kiosk, it’s a problem.

Xbox services, Teams, OneDrive, Groove Music, Copilot, Solitaire – none of these belong on a shared kiosk, a digital sign, a reception terminal, or a lab machine. You can strip them during imaging, and you should. But you can’t stop there, because drift happens.

Windows Update re-provisions AppX packages. Someone logs in interactively and OneDrive auto-starts. A feature update pushes new inbox apps. Whatever state you enforced at provisioning time, you cannot assume it’s still true six months later.

That’s the fundamental problem with one-shot cleanup scripts: they run once. What you need is continuous enforcement – something that checks the device state on a schedule and corrects it when reality diverges from intent.

How It Works
#

Two PowerShell scripts deployed as an Intune remediation pair. Detection exits 1 if anything is out of spec; remediation removes whatever triggered the flag. Intune runs the pair on schedule, so drift gets caught automatically – not during the next reimaging cycle.

The scripts cover four categories:

  • AppX packages (provisioned and per-user)
  • Services that should be stopped and disabled
  • Win32 products (registry-based uninstall)
  • OneDrive (full process kill, uninstall, and folder removal)

Prerequisites
#

  • Licensing that includes Intune Remediations – this means Windows 10/11 Enterprise E3 or E5, Microsoft 365 E3/E5/F3, or the Intune Suite. Intune Plan 1 standalone is not enough on its own.
  • Devices enrolled and Intune-managed
  • A device group targeting your kiosks
  • Nothing else – the scripts run as SYSTEM using built-in PowerShell cmdlets, no external modules required

Implementation
#

What Gets Removed
#

Both scripts share the same target lists. Here’s the full scope:

AppX packages (both provisioned and per-user installs):

  • Xbox suite: Microsoft.XboxGameCallableUI, Microsoft.XboxSpeechToTextOverlay, Microsoft.Xbox.TCUI, Microsoft.XboxIdentityProvider, Microsoft.XboxGamingOverlay
  • MSTeams
  • Microsoft.ZuneMusic (Groove Music)
  • Microsoft.Copilot
  • Microsoft.MicrosoftSolitaireCollection
  • Microsoft.BingNews
  • Microsoft.OutlookForWindows
  • Microsoft.PowerAutomateDesktop
  • Microsoft.WindowsFeedbackHub
  • Microsoft.Edge.GameAssist
  • MicrosoftCorporationII.QuickAssist
  • Microsoft.MicrosoftStickyNotes
  • Microsoft.WindowsSoundRecorder
  • Clipchamp.Clipchamp
  • Microsoft.Todos
  • Microsoft.Paint
  • Microsoft.Windows.DevHome
  • Microsoft.Windows.CallingShellApp
  • Microsoft.WindowsMaps
  • Microsoft.BingWeather
  • Microsoft.ZuneVideo (Movies & TV)
  • Microsoft.MicrosoftOfficeHub
  • Microsoft.BingSearch

Services (stopped and disabled):

  • XblAuthManager, XblGameSave, XboxGipSvc, XboxNetApiSvc (Xbox)
  • RetailDemo
  • MapsBroker
  • WMPNetworkSvc
  • SharedAccess
  • PhoneSvc
  • Workfolderssvc

Win32 products: Teams Meeting Add-in (registry-based uninstall)

OneDrive: process kill, uninstaller execution, and removal of all per-user OneDrive folders

Detection Script
#

Detection is just counting problems. The script walks the four categories, tallies per-category counts, and writes a single summary line. Exit 1 if anything’s dirty, exit 0 if not. Intune sees exit 1 and runs the remediation script. The remediation script exits 0 on success, which tells Intune the fix worked. What you’ll see in the portal is something like: Found 46 unwanted items: 22 provisioned, 24 installed, 8 services.

Regex escaping is non-negotiable here. Package names contain dots, which are metacharacters in regex. Without escaping, Microsoft.Teams would match MicrosoftXTeams or anything else with those characters in roughly that order:

# Regex escaping protects against dots, plus signs, and other metacharacters
$AppPattern = ($UnwantedApps | ForEach-Object { [regex]::Escape($_) }) -join '|'

For services, the script matches on either DisplayName or Name, then checks whether the service is actually clean. A service needs to be both Stopped AND Disabled to pass. The startup type check uses the registry directly (HKLM:\SYSTEM\CurrentControlSet\Services\<name>Start value, where 4 = Disabled) because Get-Service’s StartupType property is unreliable on Windows PowerShell 5.1 – it returns empty on some builds, which would cause every service to be flagged regardless of its actual state:

# Registry-based startup type check -- Get-Service StartupType is unreliable on PS 5.1
$regStart = (Get-ItemProperty "HKLM:\SYSTEM\CurrentControlSet\Services\$($_.Name)" -ErrorAction SilentlyContinue).Start
# Registry Start values: 2=Auto, 3=Manual, 4=Disabled
if ($_.Status -ne 'Stopped' -or $regStart -ne 4) {
    $serviceCount++
}

Win32 product detection goes through the registry rather than Get-WmiObject. This is deliberate – Get-WmiObject -Class Win32_Product triggers an MSI consistency check on every installed product, which can cause silent reconfiguration and is notoriously slow. Registry lookup is fast and has no side effects:

# Registry-based detection -- no Get-WmiObject, no MSI reconfiguration side effects
$registryPaths = @(
    'HKLM:\Software\Microsoft\Windows\CurrentVersion\Uninstall\*'
    'HKLM:\Software\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\*'
)
$installedProducts = Get-ItemProperty $registryPaths -ErrorAction SilentlyContinue |
    Where-Object { $_.DisplayName }

$installedProducts |
Where-Object { $_.DisplayName -match $Win32ProductPattern } |
ForEach-Object { $win32Count++ }

Both the 64-bit and WOW6432Node paths are checked to catch 32-bit and 64-bit installations. OneDrive detection scans for per-user OneDrive folders under C:\Users, skipping system profiles (Public, Default, Default User, All Users).

Remediation Script
#

The remediation script runs when detection exits 1. It works through the same four categories in sequence.

AppX packages: First it deprovisions the package (prevents it from being installed for new users), then removes it from all existing user profiles. Packages under C:\Windows\SystemApps are excluded – those are system-critical and not something you want to touch. Deprovisioning without per-user removal leaves existing users with the app still installed. Both steps matter.

Services: For each matched service, the script stops it if it’s currently running, then unconditionally sets the startup type to Disabled. The stop is conditional – if a service is already stopped but set to Manual, it skips the stop and just disables it. Services with dependency chains may restart themselves even after being stopped. The scheduled remediation will catch and re-disable them on the next check-in.

Win32 products: The uninstall logic reads the UninstallString from the registry and branches on whether it references msiexec. MSI-based products get a clean silent uninstall via product code extraction. EXE-based installers get wrapped in a cmd.exe /c call with a silent flag:

# Registry-based uninstall -- handles both MSI and EXE installers
$installedProducts | Where-Object { $_.DisplayName -match $Win32ProductPattern } | ForEach-Object {
    $uninstallCmd = $_.UninstallString
    if ($uninstallCmd) {
        Write-Output "Uninstalling Win32 product: $($_.DisplayName)"
        if ($uninstallCmd -match 'msiexec') {
            $productCode = [regex]::Match($uninstallCmd, '\{[0-9A-Fa-f-]+\}').Value
            if ($productCode) {
                Start-Process 'msiexec.exe' -ArgumentList "/x $productCode /qn /norestart" -Wait -NoNewWindow
            }
        } else {
            Start-Process cmd.exe -ArgumentList "/c `"$uninstallCmd`" /S" -Wait -NoNewWindow
        }
    }
}

OneDrive: OneDrive needs to be killed before the uninstaller runs. If the process is running when you call the uninstaller, it either fails silently or leaves behind partial state. Kill first, uninstall second, then sweep for leftover folders:

# Kill OneDrive before uninstalling -- avoids race condition with running process
Get-Process -Name 'OneDrive' -ErrorAction SilentlyContinue | Stop-Process -Force

The script then searches four candidate paths to locate OneDriveSetup.exe (SysWOW64, System32, Program Files, Program Files (x86)) and runs the first one it finds with /uninstall, waiting for the uninstaller to finish before proceeding. After that, it sweeps all non-system profiles under C:\Users and removes any leftover OneDrive folders. This is where locked profiles become an issue – covered in the next section.

Deploying in Intune
#

Create the remediation in the Intune admin center: Devices > Manage devices > Scripts and remediations > Remediations tab > Create. Upload your detection script and remediation script, name it something sensible, and configure these settings:

  • Run this script using the logged-on credentials: No (run as SYSTEM)
  • Enforce script signature check: your call – sign the scripts if your environment requires it
  • Run script in 64-bit PowerShell: Yes
  • Schedule: Depends on the device role. For standard kiosks that power on during business hours, daily is enough – drift doesn’t happen that fast. For always-on digital signage, every 8-12 hours catches anything that slips in overnight. Running it hourly is technically valid but creates noise in the portal and adds script execution overhead that doesn’t buy you much unless you have a specific re-provisioning problem. Start daily, tighten only if you see repeat detections between runs.

Assign to your kiosk device group. Scope tags if your tenant uses them.

The remediation will run on the configured schedule indefinitely. Every check-in either confirms the device is clean (exit 0) or triggers a fix (exit 1 -> remediation runs).

What to Look For
#

Remediation status in the portal is where you look first. Under Devices > Manage devices > Scripts and remediations > Remediations, select your remediation and check the device status tab. You want to see “Without issues” across the board. Any device showing “With issues” or “Error” needs attention.

Detection output is captured by Intune and visible per device. If a device keeps triggering remediation on every cycle, the output will tell you which specific item keeps coming back. That’s usually an AppX package being re-provisioned by Windows Update or a service that restarts via a dependency chain.

Common failure modes:

  • Locked user profiles prevent folder deletion for OneDrive. If a user is logged in when remediation runs, the per-user OneDrive folder removal will fail. The next run after they log off will clean it up.
  • Service dependency chains cause services to restart even after being stopped and disabled. If XblAuthManager keeps coming back, check whether something else is pulling it up. The remediation will keep winning on each scheduled run – it just won’t be a one-and-done.
  • AppX re-provisioning via Windows Update is the most common cause of repeat detections. Windows feature updates can restore provisioned packages. The scheduled remediation catches this automatically – that’s the whole point.
  • Assigned Access conflict – if your kiosks use Assigned Access (single-app or multi-app kiosk mode), double-check that none of the removal targets overlap with the app configured as your kiosk shell. Removing a provisioned app that’s also the Assigned Access shell app breaks the kiosk entirely: the device boots into a broken shell with no user-visible error message and no way to recover without manual intervention or a reimaging cycle. Cross-reference your removal list with your Assigned Access configuration before deploying.

Security Considerations
#

The scripts run as SYSTEM. Full machine access, no user restrictions. That’s what’s needed to touch provisioned packages and system services – just don’t deploy this without reading your own target lists first.

  • Audit your app and service lists before deploying. Remove only what you’ve verified isn’t needed. Getting this wrong on a production kiosk is disruptive.
  • Test on a pilot group first. Deploy to a subset of devices, verify the output, confirm nothing breaks, then expand scope.
  • OneDrive removal is permanent for unsynced local data. On a kiosk this should be a non-issue – but if your kiosk accounts are somehow being used for personal storage, that’s a bigger problem you should fix before running this.
  • Output stays under Intune’s 2048-char limit. Both scripts write a single summary line via Write-Host – detection reports what was found, remediation reports what was fixed. No per-item verbosity that gets truncated in the portal.

When to Use This / When Not To
#

Use this for kiosks, shared devices, digital signage, reception terminals, lab machines, and anything else where users shouldn’t have consumer apps. If the device has a defined, narrow purpose and a known user base with no need for Xbox or OneDrive, this fits.

Don’t use this on user-assigned devices. If someone actually needs Teams, OneDrive, or Paint to do their job, this will break their day. This is a kiosk hardening tool – the target audience is devices with a narrow purpose, not general-purpose debloating. Running this on a developer’s laptop because you wanted to clean up Solitaire is exactly the kind of mistake that generates a help desk ticket and a tense conversation.

The distinction sounds obvious. Make sure it’s enforced at the assignment group level, not left to documentation.

GitHub
#