Wednesday, January 6, 2010

Uninstalling ClickOnce

ClickOnce deployment model has many benefits, but it also has some deficiencies. One of these deficiencies is inability to uninstall ClickOnce applications for a different user.
Let’s take a look at the following scenario.
You are an departmental computer admin in a large enterprise where regular users do not have the ability to open “Add/Remove Programs”, but they can however install ClickOnce applications. When ClickOnce application is installed a folder is created in %USERPROFILE%\Local Settings\Apps for that application. There is also a registry entry added under HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Uninstall. This entry will have an UninstallString for a specific application. Something like this:
rundll32.exe dfshim.dll,ShArpMaintain MyClickOnceApplicationName.application, Culture=neutral, PublicKeyToken=9999999999999999, processorArchitecture=msil

Let’s say there are some obsolete ClickOnce applications which need to be removed from a regular user profile.

If you try logging in under regular user account and SHIFT+RightClick on “Add/Remove Programs” you would be able to invoke “Run as…”, in this case you would attempt to use your admin credentials. But the list of installed applications will not have the one you are looking for, because “Add/Remove Programs” will list apps from CURRENT_MACHINE and admin user profile. Now there is a problem. You could of course give necessary privileges to that regular user and let him do it. But what if you can’t give such privileges to a user, because of a security concern you have or a company policy, or you lack required permissions? Then there is a better way.

You may create an uninstall.cmd script and place it into original application installation folder, like a shared drive or your IIS. Below is script content. Replace MyClickOnceApplication with the name of your application. Use uninstall command line from the registry (see above on how to get that command).

   1: echo off

   2:  
   3: cls
   4:  
   5: Echo MyClickOnceApplication...
   6:  
   7: cd c:\
   8:  
   9: taskkill /F /IM "MyClickOnceApplication.exe"
  10:  
  11: cls
  12:  
  13: Echo Uninstalling MyClickOnceApplication...
  14:  
  15: cd %USERPROFILE%\Start Menu\Programs\Startup 
  16:  
  17: if exist "MyClickOnceApplication.appref-ms" del "MyClickOnceApplication.appref-ms"
  18:  
  19: cd c:\windows
  20: rundll32.exe dfshim.dll,ShArpMaintain MyClickOnceApplicationName.application, Culture=neutral, PublicKeyToken=0000000000000000, processorArchitecture=msil
This is using VB script and SendKey to auto press OK button during uninstall.
   1: On Error Resume Next 
   2: Set objShell = WScript.CreateObject("WScript.Shell")
   3: objShell.Run "taskkill /f /im MyClickOnceApplication*"
   4: Dim returnCode
   5: returnCode = objShell.Run("cmd /K CD " + Chr(34) + "%USERPROFILE%\Start Menu\Programs\Startup" + Chr(34)+" & del " + Chr(34)+"MyClickOnceApplication.appref-ms" + Chr(34),0, false)
   6: objShell.Run "rundll32.exe dfshim.dll,ShArpMaintain MyClickOnceApplication.application, Culture=neutral, PublicKeyToken=0000000000000000, processorArchitecture=msil"
   7: Do Until Success = True
   8:     Success = objShell.AppActivate("MyClickOnceApplication")
   9:     Wscript.Sleep 200
  10: Loop
  11: objShell.SendKeys "OK"

Then you may either send a link to the user, or issue required update which in turn calls one of the above scripts. If you are using script without SendKey command then user will see the following window and you would need to instruct user to click OK button.

image 

Conclusion

Now you know how to remove ClickOnce apps for a different user. Although many of these commands are exposed via APIs you would still need to run ProcMon to monitor registry changes. Simply removing HKEY_USERS\<Retrieved User SID>\Software\Microsoft\Windows\CurrentVersion\Uninstall  and related entries in “USERPROFILE\Local Settings\Apps\..” folder corrupts ClickOnce cache. Removing the whole Apps store fixes it, but user looses all ClickOnce apps in that case and related settings.
This problem was somewhat ISolvable problem :).