RemovePackageAsync with RemoveForAllUsers flag not clear user app data, and app is removed after provision
Describe the bug
My App is Packaged MSIX. I use PackageManager API to install/uninstall the app in custom action of MSI (wix project)
I also test PackageManager API in console app, and got same result. For simulate local system account of MSI, I use psexec -i -s cmd.exe.
First Install app and provision for all user. Then use RemovePackageAsync with RemoveForAllUsers flag to remove app not clear user app data. And then reinstall the package again, old app user data still exist. And then do provision again for all user, the app will disappear (be removed).
Steps to reproduce the bug
I have have two users UserA and UserB
UserA and UserB are logged on, and current switch to UserA.
For each step check installed user (by Local System Account)
auto users = packageManager.FindUsers(packageFullName);
print UserSecurityId() for each users
Step 1. Install package (by UserA Account)
const auto deploymentOperation{ packageManager.AddPackageAsync(packageUri, std::move(dependencyPackageUris), DeploymentOptions::None) };
deploymentOperation.get();
Installed users: S-1-5-21-XXXXXXXXXX-XXXXXXXXXX-XXXXXXXXXX-1001 // User A
Step 2. Provision package (by Local System Account)
const auto provisionOperation{ packageManager.ProvisionPackageForAllUsersAsync(packageFamilyName.c_str()) };
provisionOperation.get();
Check app is installed in UserA and UserB and app can be launch. And then close app for each user. Finally switch to UserA.
Installed users: S-1-5-21-XXXXXXXXXX-XXXXXXXXXX-XXXXXXXXXX-1001 // User A S-1-5-21-XXXXXXXXXX-XXXXXXXXXX-XXXXXXXXXX-1002 // User B
Step 3. DeprovisionPackage (optional) (by Local System Account)
const auto deprovisionOperation{ packageManager.DeprovisionPackageForAllUsersAsync(packageFamilyName.c_str()) };
deprovisionOperation.get();
Installed users: S-1-5-21-XXXXXXXXXX-XXXXXXXXXX-XXXXXXXXXX-1001 // User A S-1-5-21-XXXXXXXXXX-XXXXXXXXXX-XXXXXXXXXX-1002 // User B
Step 4. Remove package for all users (by Local System Account)
const auto removeOperation{ packageManager.RemovePackageAsync(packageFullName, RemovalOptions::RemoveForAllUsers) };
removeOperation.get();
The user app data folder still exist for each user:
%UserProfile%\AppData\Local\Packages\%PackageFamilyName%
Installed users: No
Step 5. do "Step 1" Install package (by UserA Account) After app installed, the app in UserA, app can access old app data.
Installed users: S-1-5-21-XXXXXXXXXX-XXXXXXXXXX-XXXXXXXXXX-1001 // User A
Step 6. do "Step 2." Provision package (by Local System Account) The app will be removed.
Installed users: S-1-5-18
Expected behavior
RemovePackageAsync with RemoveForAllUsers can remove app for all users and clear user data for all users.
Screenshots
No response
NuGet package version
1.1.3
Packaging type
Packaged (MSIX)
Windows version
Windows 11 version 21H2 (22000)
IDE
Visual Studio 2019
Additional context
No response
Is this issue still relevant?
The repro steps are unclear on some details so it's hard to say definitively. It's possible or you're misunderstanding some of the subtler aspects here, or maybe you stumbled over a bug in Windows. I'd need more data to say. So let me flesh out the behavior and the fuzzier parts in your story you maybe you have more info to fill in the gaps or otherwise explains what you're seeing.
I assume your package is a Main package, or a Bundle containing Main package(s). Framework, Resource and Optional packages have additional wrinkles in this sort of story but they sound N/A in your case. The order of operations:
- User=A, AddPackageAsync
- User=LocalSystem, ProvisionPackageForAllUsersAsync
- "Check app is installed in UserA and UserB..." sounds like you logged in as UserA and UserB
- DeprovisionPackageForAllUsersAsync
- User=LocalSystem, RemovePackageAsync(...options.RemoveForAllUsers)
- "The user app data folder still exist for each user"
- Installed users: No [per packageManager.FindUsers(packageFullName)]
- User=A, AddPackageAsync, "app can access old app data"
- User=LocalSystem, ProvisionPackageForAllUsersAsync
- "The app will be removed"
- "Installed users: S-1-5-18" [LocalSystem]
#1 will Stage the package (create pkgdir, extract msix content into pkgdir, and add to system inventory of packages on the machine) and register it for User A
==> pkg = Registered for A
==> provisioned list = empty
#2 ProvisionPackageForAllUsersAsync adds the package to Windows' list of provisioned packages. Provision...() also registers the package for the caller if not LocalSystem*, but since the caller is LocalSystem it's not registered for the user. When users login Windows checks what they have against what they should and updates accordingly, e.g. if pkg X is provisioned and user A doesn't have X registered Windows registers package X for user A. Provisioning a package is how Windows knows all users (current and future) should have access to the package.
==> pkg = Registered for A
==> provisioned list = A
#3 Login as user A, Windows sees user A already has the package so nothing changes. Login as user B, Windows sees user B doesn't have the package registered and registers it for user B.
==> pkg = Registered for A+B
==> provisioned list = A
#4 DeprovisionPackageForAllUsersAsync removes the package from the package to Windows' list of provisioned packages That's all. Nothing changes for existing users that have the package registered.
==> pkg = Registered for A+B
==> provisioned list = empty
#5 RemovePackage will deregister the package for the current user (if registered) and, if there's no outstanding references to the package, destage (delete) the package. The RemoveForAllUsers option causes Remove to also mark the package for removal for other users who have it registered. Deregistering a package involves resources and code requiring the user is logged in. There's additional factors in play - is the package provisioned? is the package currently in use by a user? and more - so RemoveForAllUsers doesn't necessarily mean "Uninstall For Every User Now! Now! Now!". Depending on these factors we may not be able to remove the package right now; if so we note it's supposed to go away and act on it at our next opportunity (worst case, the next time a user logs in).
==> pkg = Registered for B or no one (depends)
==> provisioned list = empty
#6 The ApplicationData folder is automagically removed when the last package in a family is deregistered for that user. If RemoveForAllUsers didn't deregister the package for every user then their ApplicationData folder will still be present until the package is actually removed. But even if deregistering a package, removal is a Best-Effort operation e.g. if a file in ApplicationData is open and locked preventing deletion Windows will continue deleting what it can as part of the deregistration and the Remove operation will succeed. The ApplicationData folder will still be present, and Windows will try again later to remove the remaining dir(s)/file(s), up to and even potentially after reboot. A running process with open handles to a file in ApplicationData is one way this can occur. Depending on what users are logged in and what running processes are doing you very well may still see the ApplicationData folder at this point. Hard to say without more data.
#7 means the package isn't registered for any user.
==> pkg = Registered for no one
==> provisioned list = empty
P.S. You can also get this info via Powershell run as admin / elevated: powershell.exe -c Get-AppxPackage pkgname -AllUsers and look for the PackageUserInformation field ([SID]: Installed == registered, [SID]: Staged == staged)
#8 the package is registered for user A and sees the old data as if the old data wasn't deleted on deregister. This also strongly implies the previous Remove didn't delete the ApplicationData folder.
==> pkg = Registered for A
==> provisioned list = empty
BTW you mentioned the folder was present but not if there was any content in it and if that was present or removed. It would be informative if after AddPackageAsync you'd created files in e.g. ApplicationData.LocalFolder (or .LocalCacheFolder) and after Remove if the file(s) were still present in .Local[Cache]Folder. Depending on machine state at the time Remove may have deleted the files but blocked and unable to delete the directory tree if it was blocked e.g. call AddPackage, open a cmd prompt, ~CD /D %UserProfile%\AppData\Local\Packages%PackageFamilyName%` then call RemovePackage. CMD.EXE's current directory is in use and can't be removed so it'll still appear after the Remove completes - but all its content not in use or otherwise locked/blocked should be gone.
#9 same as #2 -- add the package to the provisioned list but since the caller's LocalSystem the package isn't registered for the caller.*
==> pkg = Registered for A
==> provisioned list = A
#10 "The app will be removed". I don't know what this means. Are you saying you called Remove same as #5?
#11 "Installed users: S-1-5-18" [LocalSystem] is cryptic. I don't know if you saw this from FindUsers() differently than in any previous case, or if you saw it earlier but didn't mention, this was misreported due to a bug in Windows or you saw this via some other mechanism e.g. Get-AppxPackage -AllUsers reports PackageUserInformation of [S-1-5-18]: Staged indicating the package is staged (it's not actually registered for LocalSystem*).
* Currently MSIX packages can be registered for any user except LocalSystem. A known limitation; addressing this is in our backlog.
This issue has been automatically marked as stale because it has been marked as requiring author feedback but has not had any activity for 7 days. It will be closed if no further activity occurs within 7 days of this comment.