New
#1
Completely uninstall provisioned apps: how-to and detailed explanation
*** SECTION ONE ***
*** Introduction ***
In this post I will explain how to completely remove a Metro-style app (or Modern app or Windows app) from a Windows 10 machine. All tests have been performed on 64-bit Windows Home build 10.0.19041.508 (semi-annual channel). I can't guarantee that Microsoft will not change things with the next feature updates.
I will NOT talk about traditional desktop programs, NOR will I worry about removing a Metro-style app for a single user. These two tasks can be easily performed through the Windows GUI (Graphic User Interface) and they are widely documented on the web - see for instance Brink's tutorial "How to Uninstall Desktop Apps and Windows Apps in Windows 10":
Uninstall Apps in Windows 10
Option 6 and 7 in that tutorial are intended to completely uninstall specific apps from the computer, which is the topic I want to cover, but those commands are wrong and they don't actually do the job.
Before getting started, make sure you understand the different apps included in Windows 10. Please, do follow the link and read the article carefully, especially if you're not familiar with the topic:
https://docs.microsoft.com/en-us/win...-in-windows-10
Also, it is important to point out that different apps may require different uninstall procedures. Many apps can be completely removed simply through the Windows GUI, either from the Start menu or from the Settings app - these are the easy ones.
System apps, on the other hand, cannot be removed at all as they are integral to the operative system. There are tutorials around the web showing how to delete them, but I would not advise trying to do so as forcefully deleting system apps is very likely to break things.
In the following I will focus on provisioned apps, which somehow lie in between the two previously mentioned categories. If you're not sure about the meaning of "provisioned apps", the Microsoft link above will help. These apps CAN be completely removed, but the uninstall procedure is often tricky and it can only be completed through CLI (Command Line Interface). Just remember that there is one such app that shall not be removed because it can't be easily re-installed: that is of course the Microsost Store.
*** SECTION TWO ***
*** Brink's suggestion ***
I will take, just as an example, Xbox Console Companion. Brink suggests to open Powershell with admin rights and issue the command:
These cmdlets actually uninstall Xbox Console Companion for the current user i.e. the admin account used to invoke Powershell. The app will remain installed for all the other users on the machine and the packages at %ProgramFiles%\WindowsApps will stay in place. In my tests, more than 99.98% of the disk space originally used by the app is still occupied - this is because I never opened the app; if you ever used it you'd free more disk space.Code:Get-appxpackage -allusers *Microsoft.XboxApp* | Remove-AppxPackage
In order to understand why the command is wrong, we have to recall two concepts about Powershell.
First: differently from the command prompt (cmd.exe), the typical output of a Powershell cmdlet is not simple text, but rather a fully featured object. If you are familiar with OOP (Object-Oriented Programming) you know what I mean. In case you are not, you can imagine an object as a structured variable that contains several fields. Each field has a name and stores a value. For example, the object MyIdentity could be:
Name: Mark
Age: 83
The Name field stores a string value, while the Age field stores an integer value. Get-AppxPackage returns an AppxPackage object. If you're wondering how it looks like, just type:
and hit return.Code:Get-AppxPackage -AllUsers -Name "*xboxapp*"
Second: the second concept is piping. To make it short, it is possible to pass the output of a cmdlet as the input to a second cmdlet by using a pipe (|). For example:
The output of the last cmdlet in the chain (which will be an object) is converted to text and printed on the screen - unless it gets redirected, right, but I'm trying to keep it simple. Please note that you and only you are responbile for granting that the output object type and the following input object type are compatible with each other. If they are not, the execution will be aborted and you'll typically get an error on the screen.Code:cmdlet1 | cmdlet2 | cmdlet3
Back to our inital problem, now you see that the AppxPackage object(s) returned by Get-AppxPackage is(are) sent to Remove-AppxPackage. In case you really want to check what pops out on the right-hand side of the pipe, you can use the pretty redundant command:
No big surprise, the output printed on the screen is the same as using Get-AppxPackage alone.Code:Get-AppxPackage -AllUsers -Name "*xboxapp*" | Write-Output
This is the crucial point: as you can check yourself using the command here above, the AppxPackage object passed down the pipe does NOT contain any reference to the -AllUsers parameter. Moreover, even if it did, Remove-AppxPackage does NOT accept pipeline input for its -AllUsers parameter. Therefore, Remove-AppxPackage gets executed with the default value for its -User parameter i.e. the user who invokes the cmdlet.
Adding -AllUsers to Get-AppxPackage has no effect at all. Both with and without it, the app will be removed only from the admin account if that account has the app installed. Both with and without it, nothing will happen if the admin account doesn't have the app installed.
*** SECTION THREE ***
*** Let's Remove-AppxPackage for -AllUsers ***
Now that we know why the first approach fails, we can attempt a second iteration with:
where I added two parameters to Remove-AppxPackage:Code:Get-AppxPackage -AllUsers -Name "*xboxapp*" | Remove-AppxPackage -AllUsers -Confirm
-AllUsers: the first cmdlet needs it, in order to retrieve the app even if it is not installed to the admin account. The second cmdlet needs it, as previously demonstrated. Also very important, this parameter tells Remove-AppxPackage to work off the parent package type.
-Confirm: because the object that goes down the pipe is not printed on the screen and I want to know what is going to be removed before it actually gets removed. In addition, unless I'm writing a script intended for background execution, I like to be prompted for confirm before any modification is made to the system.
Surprisingly, the command works for a few seconds and then it fails. This time it exits with error code 0x80073D19. A quick look at the developer help page:
https://go.microsoft.com/fwlink/?LinkId=235160
reveals that this error code corresponds to:
ERROR_DEPLOYMENT_BLOCKED_BY_USER_LOG_OFF --- 0x80073D19 --- An error occurred because a user was logged off.
However, this description will turn out to be very misleading. The failure has nothing to do with a user logged off.
In order to gain a better understanding of what is going on, I run the Event Viewer by typing eventvwr.msc in the run dialog box. In the left pane, I expand:
Event Viewer (Local) -> Applications and Services Logs -> Microsoft -> Windows -> AppXDeployment-Server -> Microsoft-Windows-AppXDeploymentServer/Operational
and I review the latest log entries. I see that everything goes well until an attempt is made to access a registry key, then error 0x80070002 is thrown. Since not even this error code is helpful, I switch back to Powershell and I point it to the problematic registry hive by typing:
which returns only one key with name:Code:Get-ChildItem HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Appx\AppxAllUserStore\Applications\*xboxapp*
Microsoft.XboxApp_48.67.14001.0_neutral_~_8wekyb3d8bbwe
Got it. The problem is a registry key mismatch. Get-AppxPackage returns an AppxPackage object with a PackageFullName field containing the value:
Microsoft.XboxApp_48.67.14001.0_x64__8wekyb3d8bbwe
This is the package Remove-AppxPackage works on. Everything goes well while uninstalling the app from the user profiles, but when the cmdlet reaches the final steps of the process and queries the registry, it doesn't find a matching package name.
*** SECTION FOUR ***
*** RTFM ***
So how come the registry key doesn't match? The key was created by Microsoft and they should know what they're doing. Of course they do. It was our mistake. As usual, when things go wrong it is a good idea to RTFM: Read The "Fine" Manual - or at least the one with "Fine" is the polite version of it.
https://docs.microsoft.com/en-us/pow...-ps#parameters
The Remove-AppxPackage official help page states that, when calling the cmdlet with the -AllUsers switch parameter, it is important to check whether the app you want to remove is distributed as a bundle. If so, it is mandatory to feed the bundle package to the cmdlet. As you can check yourself, the app I'm trying to uninstall is a bundle indeed. Actually, the great part of the provisioned apps are installed as bundles.
https://docs.microsoft.com/en-us/pow...-ps#parameters
Reading carefully the Get-AppxPackage help page brings us to the end of the story. Unless otherwise specified, Get-AppxPackage queries the local app repository for packages of type Main and Framawork only. Bundle and Resource packages are not retrieved by default. If you want the bundle package, you have to use the -PackageTypeFilter parameter and specify the bundle. The final and correct version is therefore:
Hit return and wait until the uninstall process completes. The app will be uninstalled from all user profiles on the machine and the app's main files will be removed. All the app remnants under %UserProfile% and %ProgramFiles% will be deleted. Also, the command will take care of deprovisioning the app from the online Windows image, so there is no need to call DISM cmdlets. If you really want to check, just run:Code:Get-AppxPackage -AllUsers -PackageTypeFilter Bundle -Name "*xboxapp*" | Remove-AppxPackage -AllUsers -Confirm
Nothing will be returned, which means the app has been deprovisioned. As an additional check, I had a look at the Event Viewer: only information-level entries are recorded, no errors or warnings. Also, the app's registration in the Windows registry is removed, and a new key is created to flag the app as deprovisioned. I repeated the process with a few more apps and eveything worked seamlessly so far.Code:Get-AppxProvisionedPackage -Online | Where-Object PackageName -Like "*xboxapp*"
*** SECTION FIVE ***
*** Sum it up ***
That's it. If you want to completely uninstall and remove a provisioned app from a Windows 10 machine, follow these steps. Substitute "*appname*" with the name of your app, keeping the double quotes (") and the wildcards (*).
STEP 1 - Open a Powershell prompt: Win + X -> Windows Powershell. At this point it is not important whether or not the shell is elevated. That's because we're going to access the HKLM registry Root Key with a read-only query. Run:
STEP 2 - If the key returned in step 1 contains either _x64__ or _x86__, the app you're about to remove is NOT installed as a bundle. From within an elevated Powershell window, run:Code:Get-ChildItem HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Appx\AppxAllUserStore\Applications\*appname*
STEP 3 - If the key returned in step 1 contains _neutral_~_, the app you're about to remove IS installed as a bundle. From within an elevated Powershell window, run:Code:Get-AppxPackage -AllUsers -Name "*appname*" | Remove-AppxPackage -AllUsers -Confirm
STEP 4 - If you feel the need to verify that everything went well, open the Event Viewer by typing eventvwr.msc in the run dialog box. In the left pane, expand:Code:Get-AppxPackage -AllUsers -PackageTypeFilter Bundle -Name "*appname*" | Remove-AppxPackage -AllUsers -Confirm
Event Viewer (Local) -> Applications and Services Logs -> Microsoft -> Windows -> AppXDeployment-Server -> Microsoft-Windows-AppXDeploymentServer/Operational
and check the last few entries. There should be no errors or warnings.
STEP 5 - In case you find the procedure fails for some apps, please post back so that your feedback can help other users.
Cheers,
Mark
- - - Updated - - -
A few updates.
1 - I see that Brink modified option 6 and 7 in his tutorial by adding -AllUsers to all Remove-AppxPackage cmdlets. However, I'm still convinced that's only part of the problem. @Brink, I'll continue the discussion here because I think this thread is more specific to the topic I'm focusing on.
2 - I disagree that the -Name word makes any difference. If you don't specify -Name, the cmdlet parser will try to fit the literal string you give to the right parameter and it will implicitly add -Name before executing. In case no parameter match is found, the execution is aborted and an error is thrown. My decision to add -Name is just a matter of programming style: I find it more readable and closer to natural language. Both versions behave the same.
3 - In order to verify what stated in point 2, try your command style:
and see that it only returns the Main package, flagged by _x64__. This is because no value is passed to -PackageTypeFilter and therefore the cmdlet queries the local package repository for packages of type Main and Framework only.Code:Get-AppxPackage -AllUsers "*bingweather*"
4 - As an additional test, now try to issue the following command:
This time all packages are returned. In both point 3 and 4 I do NOT explicitly call the -Name parameter. The difference is only made by -PackageTypeFilter.Code:Get-AppxPackage -AllUsers "*bingweather*" -PackageTypeFilter Bundle, Framework, Main, Resource
Cheers,
Mark