Reboot-time deletion + full COM surface cleanup#2
Open
rianbk wants to merge 1 commit into
Open
Conversation
The previous in-session deletion approach can't succeed against
AINppShell.dll / OverlayIcon.dll because they're loaded into virtually
every shell-using GUI process on the machine -- Explorer, Office apps,
browsers, anything that opens a file dialog. Killing Explorer loses
the race against AutoRestartShell (~1s respawn) and can't unload the
DLL from the other consumers anyway. On real devices this left 270
residual files and exit 1603 after every cycle.
Major architectural changes:
* Pivot to PendingFileRenameOperations + reboot as the primary path
for the install directory. Phase A cleans what's cleanable in-session,
queues locked files via PFRO, writes a sentinel, returns 3010
(Intune-compatible success). Phase B fires after the user's organic
reboot -- install dir clears at boot via SMSS before any shell process
exists, next detect cycle confirms.
* Self-relaunch under 64-bit PowerShell. Intune defaults Proactive
Remediation scripts to 32-bit PowerShell, which silently WOW64-
redirects HKLM:\SOFTWARE reads to Wow6432Node -- the AI Now uninstall
key and all three shell-ext CLSIDs are in the 64-bit hive, so the
32-bit script ran blind and cleaned nothing. Both detect.ps1 and
remediate.ps1 now relaunch themselves via SysNative if started 32-bit.
* Full mapping of the COM registration surface (confirmed via reg query
on a live install): 3 CLSIDs + AppID + TypeLib + 2 ProgIDs + 10 shellex
handlers across {*, Directory, Drive, Folder, lnkfile} x
{ContextMenu, DragDrop} + ShellIconOverlayIdentifiers (with the
whitespace-prefixed subkey name Lenovo uses to game the 15-slot limit)
+ Shell Extensions\Approved value. Hardcoded list + dynamic .NET
registry-API walk as backup, anchored to filename + parent path
containing \Lenovo\Lenovo AI.
* MSIX/AppX handling. AI Now 1.3 ships AINowContextWIN11 as a packaged
Win11 context-menu extension; Remove-AppxPackage handles the main
package but leaves orphaned per-user repository stubs at
HKU\<sid>\Software\Classes\Local Settings\...\AppModel\Repository
which Remove-LenovoAINowAppxRepositoryStubs scrubs explicitly across
every resolved user SID, plus the HKLM AppxAllUserStore mirrors.
* Disable services before stopping (sc config start= disabled ->
Stop-Service -> WaitForStatus('Stopped', 30s) -> sc delete) to
prevent auto-restart races during cleanup.
* Wildcard DisplayName match. The original ^Lenovo AI Now\b regex
silently failed against the actual "Lenovo AI Now 1.3" entry on
observed devices, so the Uninstall registry key survived every
remediation cycle. Both scripts now use the same -like wildcard.
* -LiteralPath for registry paths containing literal '*'. The default
-Path treats '*' as a wildcard and expanded across every HKCR subkey,
causing multi-minute hangs in the shellex handler loops.
* .NET Microsoft.Win32.Registry API for the dynamic CLSID hive walk
(~200ms vs ~40s via Get-ChildItem cmdlet on a 7k-key hive).
* Sentinel + suppression. After Phase A writes
HKLM:\SOFTWARE\LenovoAINowRemediation\PhaseAComplete, detect.ps1
short-circuits to exit 0 while the sentinel is fresh (<=7 days) and
PFRO still references Lenovo entries. Prevents the detect-remediate
loop bloat that would otherwise happen between Phase A and reboot.
* Removed dead code (Invoke-Uninstaller, Invoke-MsiUninstall) and the
robocopy /MIR fallback (could deadlock against kernel-locked DLLs).
* Files renamed to *.tobedeleted before PFRO queue so detect's file
count doesn't see them between Phase A and reboot.
Validated end-to-end on two real Lenovo devices: one starting from
full fresh-install state with locked DLLs (exercised PFRO + reboot
path), one in mid-cleanup state from prior partial runs (exercised
direct deletion after natural reboot). Both reached exit 0.
Adds tools/diagnose_clsid_walk.ps1 for measuring registry-walk
performance on a specific device (useful when diagnosing hangs).
Updates README.md to reflect the new architecture, requirements
(64-bit PowerShell setting), exit code semantics, and monitoring
KQL queries for fleet rollout.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #1
Summary
Substantial rewrite to handle the case where Lenovo AI Now's shell-extension DLLs are loaded into other shell-using processes (Explorer, Office, browsers, anything that opens a file dialog) and can't be deleted in-session. The existing approach loses against
AutoRestartShellwhen killing Explorer and can't unload the DLLs from the 17+ other consumers anyway, so most devices end up with 270 residual files and exit 1603 after every remediation cycle.This PR pivots the remediation to
PendingFileRenameOperations+ reboot as the primary path. Validated end-to-end on two real Lenovo devices.Major changes
HKLM:\SOFTWAREreads toWow6432Node. The AI Now uninstall key and all three shell-ext CLSIDs live in the 64-bit hive, so a 32-bit script ran blind and cleaned nothing.reg queryon a live install): 3 CLSIDs + AppID + TypeLib + 2 ProgIDs + 10 shellex handler subkeys +ShellIconOverlayIdentifiersentry +Shell Extensions\Approvedvalue. Hardcoded list + dynamic .NETMicrosoft.Win32.Registrywalk as backup (the cmdlet-based walk took 25+ minutes on real devices and timed out under Intune).AINowContextWIN11as a Win11 context-menu MSIX package —Remove-AppxPackagehandles the main package but leaves orphaned per-user repository stubs atHKU\<sid>\Software\Classes\Local Settings\...\AppModel\Repository, which this PR scrubs across every user SID plus the HKLMAppxAllUserStoremirrors.sc config disabled→Stop-Service→WaitForStatus('Stopped', 30s)→sc delete) to prevent auto-restart races.^Lenovo AI Now\bregex silently failed on the actualLenovo AI Now 1.3entry, so the uninstall registry key survived every cycle.-LiteralPathfor registry paths containing literal*— the existing-Pathtreated*as a wildcard and expanded across every HKCR subkey, causing multi-minute hangs in the shellex handler loops.Invoke-Uninstaller,Invoke-MsiUninstall) and the robocopy/MIRfallback (could deadlock against kernel-locked DLLs).How it was validated
End-to-end on two real Lenovo devices:
Plus MDE Advanced Hunting across a 36-device fleet to confirm the scope of DLL loading (18 different processes load
OverlayIcon.dll, 5 loadAINppShell.dll).Caveats
\Lenovo\Lenovo AI), but truly new DLL filenames would need the$lenovoAIDllFileNameslist extended.Test plan
🤖 Discovery and authoring assisted with Claude Code.