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 :).

14 comments:

  1. Hello Ivan,
    Nice article, really helped me after my clickonce deployment broke and I needed a VBS file to run on the machines to remove it!

    However, if there have been updates in the past, the ApplicationName Maintenance window has a restore option - (which it defaults to) so when i sendkeys "OK" it performs the restore action instead of the remove :(

    I naively tried a SendKeys call with "Remove the application from this computer" with no joy.

    Can you think of a way that I can change the selected option to "Remove the application from this computer"?

    Thanks for any help in advance!

    Kind Regards,
    Matt

    ReplyDelete
  2. Matt,

    Restore option only available if you issue optional updates. In my environment I only issue mandatory updates.

    In your case you will need to send more keys. Like tab, down arrow, tab, tab and OK. You may refer to this MSDN page for more information on how to send other commands http://msdn.microsoft.com/en-us/library/8c6yea83%28VS.85%29.aspx
    Keep in mind, that the more commands you send using SendKey method the less reliable your script becomes, because user can still interact with OS environment while your script is running.

    I actually changed the script a little. I fire up the script from batch file, this way customers do not see black window and there is no Script Engine in the memory after script finishes executing.

    ReplyDelete
  3. I see - thanks for the help Ivan!

    Cheers,
    Matt

    ReplyDelete
  4. Hi,

    I am trying a uninstall an application called function breeze
    and its uninstall string is located in "C:\Program Files\Ngs\uninstall.exe"
    during the uninstall process i have to hit enter (On Next screen)
    how can i use above example..
    thanks for help...

    ReplyDelete
  5. Well, it looks like you are not using Click Once. But in your case you may still try to use Vb Scripts to automate button clicks.
    Please refere to the above example and this line:
    Success = objShell.AppActivate("MyClickOnceApplication")

    replace MyClickOnceApplication with the name of the dialog box that shows up on your screen. Once window handler is acquired you may use

    objShell.SendKeys

    To send commands which are needed in your case.

    Please refer to the article below for more command examples:

    http://msdn.microsoft.com/en-us/library/8c6yea83%28VS.85%29.aspx

    ReplyDelete
  6. Hi Ivan this is very helpful information you have given in this blog , my scenario is a diffenet one , i have once xla addin file,a .NET Dll (Win form),a COM dll for UDF Functions and a click once deployment. I am writing into registry that has the value of .xla physical path in HKCURRENTUSER\Software\Microsoft\Excel\Version\Options\ and through a command prompt i am registering my COM dll. Now i want to clean up everything from user's system on uninstall.Can the above description given in your blog is suitable for me.

    Thanks
    Dwipayan Das

    ReplyDelete
  7. Hi Dwipayan,

    I am glad it was helpful. In your case you will need to run a batch file to remove registry entry and unregister COM component. The user running it may need elevated privileges.

    Also consider moving:
    - registry entry into app.config
    - UDF functions into another .NET dll

    At the end you will be able to have entire code base in Visual Studio .Net project, and hence hassle free deployment.

    ReplyDelete
  8. Hi Ivan,

    Thank you for the solution that you have provided, seems like it would work in my case.
    My requirement is a bit different I have a click once solution which is being used at different locations
    The application build that was done till now was done on a 32 bit machine, I have upgraded my machine to 64 bit, so the build that I would make
    now would not update the old version and would give an error.

    I was looking for a solution where I could create a batch file to uninstall the application and then
    delete the reference files from the apps folder as the users at the location are novice to computer and
    would be tough job to guide all of them to uninstall and then delete the files which are hidden.
    Thats when I cam across your solution and found it suitable for me to implement.

    I did use this code of yours and created a batch file to just uninstall the application, but then it din't work for me
    it gave me a error

    Below is the error that I received. Any help on this would be great. Thanks in advance

    IDENTITIES
    Deployment Identity : staging.windows.application, Culture=neutral, PublicKeyToken=0000000000000000, processorArchitecture=msil

    ERROR SUMMARY
    Below is a summary of the errors, details of these errors are listed later in the log.
    * Error occurred during store lookup, component store may have been corrupted. Following failure messages were detected:
    + Application is not installed.
    * Error occurred during uninstall of the application. Following failure messages were detected:
    + Application is not installed.
    * Exception occurred during uninstall of application staging.windows.application, Culture=neutral, PublicKeyToken=0000000000000000, processorArchitecture=msil. Following failure messages were detected:
    + ARP entry 3843ec9fb69e8292 does not exist.
    + Cannot delete a subkey tree because the subkey does not exist.

    COMPONENT STORE TRANSACTION FAILURE SUMMARY
    No transaction error was detected.

    WARNINGS
    There were no warnings during this operation.

    OPERATION PROGRESS STATUS
    * [7/25/2012 2:29:27 PM] : Looking up information from component store.
    * [7/25/2012 2:29:27 PM] : Uninstall of application staging.windows.application, Culture=neutral, PublicKeyToken=0000000000000000, processorArchitecture=msil did not succeed.

    ERROR DETAILS
    Following errors were detected during this operation.
    * [7/25/2012 2:29:27 PM] System.Deployment.Application.DeploymentException (SubscriptionState)
    - Application is not installed.
    - Source: System.Deployment
    - Stack trace:
    at System.Deployment.Application.SubscriptionStore.CheckInstalled(SubscriptionState subState)
    at System.Deployment.Application.SubscriptionStore.CheckInstalledAndShellVisible(SubscriptionState subState)
    at System.Deployment.Application.DeploymentServiceCom.MaintainSubscriptionInternal(String textualSubId)
    * [7/25/2012 2:29:27 PM] System.Deployment.Application.DeploymentException (SubscriptionState)

    Thanks
    Binu B

    ReplyDelete
  9. Hi Ivan,

    Thanks a ton.. your blog helped me... the issue that I had mentioned earlier was my mistake was using the uninstall command from your blog rather than taking it from the registry.

    You rock buddy... you saved a lot of my time...

    ReplyDelete
  10. Hi Ivan,

    Thanks for the helpful information. I tried the approach but was only able to delete the clickonce app, however, it left behind the underlying application. I guess the clickonce app was only use to install the underlying application. Any thoughts on delete that as well?

    cheers,
    Rufus

    ReplyDelete
  11. Hi Ivan,

    How would you under the same script and re-install the clickonce application ? for example if you moved the deployment server else where. Thanks!

    RT

    ReplyDelete
  12. To re-install, simply execute in the script the manifest file which points to a new location.

    ReplyDelete
  13. Thanks for the logical, easy to read steps.

    ReplyDelete