none
How to uninstall a ClickOnce application silently? RRS feed

  • Question

  • I see that the unintall string is like

    rundll32.exe dfshim.dll,ShArpMaintain appname.application, Culture=neutral, PublicKeyToken=d67ac05a79f37e8a, processorArchitecture=msil

    But that brings up UI. I have a need to silently uninstall a ClickOnce application. Is it possible to do this?

    Friday, November 11, 2005 11:35 AM

Answers

  • Hi Neo, this release of ClickOnce does not provide programmatic support for uninstalling "installed" apps.

    There is a way to purge the online application cache: use "Mage.exe -cc" or ClearOnlineAppCache API exposed via dfshim.dll.

    For installed apps though you have to go through Add/Remove Programs UI.

    Regards,
    Sameer
    Friday, November 11, 2005 7:29 PM

All replies

  • Hi Neo, this release of ClickOnce does not provide programmatic support for uninstalling "installed" apps.

    There is a way to purge the online application cache: use "Mage.exe -cc" or ClearOnlineAppCache API exposed via dfshim.dll.

    For installed apps though you have to go through Add/Remove Programs UI.

    Regards,
    Sameer
    Friday, November 11, 2005 7:29 PM
  • Sameer,

     

    Has anything changed on this issue?  I also would like to silently uninstall a clickonce application.  Wouldn't it be fairly easy to modify dfshim.dll to expose a "ForceUninstall"  method that basically does what ShArpMaintain would have done but without the UI?

     

    Thanks,

    Spencer

     

    p.s. The reason I want to do this is that we have some custom actions that we need to do upon uninstalling the ClickOnce application  (removing some files).  There is no way to do that with ClickOnce itself, but we can create a standard Setup.exe bootstrapper that installs the ClickOnce application and which has a custom uninstall action.  However this creates two entries in the Add /Remove programs.  I can hide one of the entries, but I still need to silently do the uninstall of the clickonce app.
    Monday, July 2, 2007 6:08 PM
  • Spencer,

    Did you receive an answer to this? I need to do the exact same thing.

    Thanks.

    Thursday, July 5, 2007 3:35 PM
  • No,  I did not receive an official answer & after looking into the dfshim.dll with a hex editor, I don't think there is any entry point that is applicable.  It would be a nice enhancement if MS exposed this from dfshim.dll.
    Tuesday, July 10, 2007 5:09 PM
  • I just wanna point that this thread is old and maybe Microsoft has updated the ClickOnce thingy so maybe that .dll dont work anymore?
    Besides, on my web  browser when i published the folder i got an un-install that i just has to load up into my website just like the other things in the folder.
    //Zeelia
    Wednesday, July 18, 2007 1:20 PM
  • hello Neo, i'm also looking for a way to uninstall clickonce application. i tried your unsinstall string, but i cant use it properly. what is the format of "appname.application" in your uninstall string? can i also use the same publickeytoken as yours? and lastly, do you found out a way to do silent uninstall? thank you very much in advance. i'm sorry for the many questions. have a nice day!

    Wednesday, August 22, 2007 1:12 AM
  •  

    hello Neo. please disregard my first two questions. i made it work. you are right that it is really nice if clickonce can do silent uninstall. thank you very much again.
    Wednesday, August 22, 2007 1:20 AM
  • Can you share how you managed to provide a silent uninstall?

     

    Thanks,

    Paul

     

    Friday, November 2, 2007 8:06 PM
  • So, what is the answer: it is possible to uninstall ClickOnce application or not? And if it is possible, let me know how to do it.
    Thanks.
    Thursday, March 6, 2008 8:45 AM
  • I never did get an official answer from Microsoft on this, however after much investigation, we came down to two possible options.

     

    Option 1)

     

    a.  Get the uninstall string from the registry.  It will be in some location like:

     

    HKCU\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\183fc12ecada4

     

    and look like:

     

    rundll32.exe dfshim.dll,ShArpMaintain appname.application, Culture=neutral, PublicKeyToken=d67ac05a79f37e8a, processorArchitecture=msil

     

    b.  Create a program that launches this process.  This will pop up the Add/Remove dialog.

    c.  Use the Win32 method "SendKeys" to send a SHIFT-TAB and ENTER to the dialog.  Here's some code that might help you (unsupported of course :-)...)

     

    using Microsoft.Win32;

     

    /// <summary>

    /// The sole purpose of this class is so we an send SHIFT-TAB and ENTER to the ClickOnce removal dialog.

    /// </summary>

    /// <remarks>If the click once removal dialog is not yet open, this class will do nothing.</remarks>

    public partial class AutomateKeystrokes : Form

    {

    [DllImport("user32.dll")]

    private static extern IntPtr FindWindow(string lpClassName, string lpWindowName);

    [DllImport("user32.dll")]

    private static extern bool SetForegroundWindow(IntPtr hWnd);

    public AutomateKeystrokes()

    {

    this.Load += new System.EventHandler(this.AutomateKeystrokes_OnLoad);

    }

    private void AutomateKeystrokes_OnLoad(object sender, EventArgs e)

    {

    RespondToClickOnceRemovalDialog();

    Application.Exit();

    }

    /// <summary>

    /// Use automated keystrokes to answer "OK" to the ClickOnce removal dialog.

    /// </summary>

    /// <remarks>Timing is very critical here. Even Debug.Writeline lines can cause this to fail.</remarks>

    private void RespondToClickOnceRemovalDialog()

    {

    IntPtr myWindowHandle = IntPtr.Zero;

    for (int i = 0; i < 60 && myWindowHandle == IntPtr.Zero; i++)

    {

    Thread.Sleep(500);

    myWindowHandle = FindWindow(null, _MAGIC_LENS_APPLICATION + " Maintenance");

    }

    if (myWindowHandle != IntPtr.Zero)

    {

    SetForegroundWindow(myWindowHandle);

    SendKeys.Send("+{TAB}"); // SHIFT-TAB

    SendKeys.Send("{ENTER}");

    SendKeys.Flush();

    }

    }

    }

     

    Not silent, but at least automated.

     

     

    Option 2)

     

    a.  Run the uninstall process and record all the actions the installer makes (deleting reg keys, deleting files, etc).  You can do this with ProcMon.

     

    b.  Write a program that manually runs these same actions.

     

    Works, but is very kludgy and fragile.

     

    We went with Option 1 which met our needs. 

     

    I know this is pretty lame, but maybe someone can put in an enhancement request for Microsoft to support a "ForceUninstall" entry point option to dfshim.dll which should be pretty trivial to implement since all the real code already exists for ShArpMaintain.

     

     

    Thursday, March 6, 2008 7:33 PM
  • Hi.

    I found a little bit different solution (I use VS 2008 and plugin for Outlook 2007)

    Here in registry: HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Uninstall

    there are some keys. One of that keys is a key correspoiding to our solution.

    It contains UninstallString like:

    "C:\Program Files\Common Files\Microsoft Shared\VSTO\9.0\VSTOInstaller.exe" /Uninstall file:///<full path to deployed vsto file>.

    Let this string contained in variable uninstallString:

     

    Process uninstProc = new Process();

         //get name of uninstallation file to run
    uninstProc.StartInfo.FileName = uninstallString.Substring(1, uninstallString.IndexOf('"', 2)-1);

         //get arguments to run uninstall and add "/Silent" key to silent uninstall
    uninstProc.StartInfo.Arguments = String.Format("/Silent {0}", uninstallString.Substring(uninstallString.IndexOf('"', 2)+1));

    uninstProc.Start();
    uninstProc.WaitForExit();

     

    That's all.

    Thursday, March 13, 2008 5:06 AM
  • I found a way around this, using an old utility called scriptit. We use SCCM, so we can run this script for "Every user that logs onto the computer", but you can also do this without SCCM or SMS by configuring it to use ActiveSetup, and running once for each user that logs on.

    This process is not silent, as it must run with the users credentials and there is no quiet switch. However the user does not have an option to interfere with the task. Basically it pops up the uninstall command and selects OK too quickly for the user to interfere. Hope this helps, it is a lame workaround, but until Microsoft releases a cleanup utility there's not much else that can be done.

    In the working directory:
    Scriptit.exe
    script.INI
    ClickOnceUninstall.vbs

    ClickOnceUninstall.vbs contents:

    On Error Resume Next
    Set sho = Wscript.CreateObject("Wscript.Shell")
    Set objEnv = WshShell.Environment("Process")
    objEnv("SEE_MASK_NOZONECHECKS") = 1

    '****** Run Scriptit with INI file ********
    sho.run "scriptit.exe Script.ini",,True

    objEnv.Remove("SEE_MASK_NOZONECHECKS")

    Script.INI contents:

    [SCRIPT]
    run="[REGISTRY KEY FOUND AT: HKCU\Software\Microsoft\Windows\CurrentVersion\Uninstall\<guid> in the uninstall key. Should start with rundll32.exe dfshim.dll etc...]"
    [NAME OF WINDOW THAT APPEARS DURING UNINSTALL]=OK




    Thursday, January 22, 2009 7:46 PM
  • Actually just found a faster way using only VB script. 

    On Error Resume Next

    Set objShell = WScript.CreateObject("WScript.Shell")

    objShell.Run "taskkill /f /im [your app process name]*"

    objShell.Run "[your app uninstall key]"
    Do Until Success = True
        Success = objShell.AppActivate("[your window title]")
        Wscript.Sleep 200
    Loop
    objShell.SendKeys "OK"

    Thursday, January 22, 2009 8:07 PM
  • Actually just found a faster way using only VB script. 

    On Error Resume Next

    Set objShell = WScript.CreateObject("WScript.Shell")

    objShell.Run "taskkill /f /im [your app process name]*"

    objShell.Run "[your app uninstall key]"
    Do Until Success = True
        Success = objShell.AppActivate("[your window title]")
        Wscript.Sleep 200
    Loop
    objShell.SendKeys "OK"

    Great solution. Works for me!

     

    Regards, 

    Bart


    Monday, October 10, 2011 11:55 AM
  • Its works Awesome can i know how to select  "Remove the application from this computer" Option by default.

    Thanks in advance


    lavanyar
    Monday, January 23, 2012 10:00 PM
  • Its works Awesome can i know how to select  "Remove the application from this computer" Option by default.

    Thanks in advance


    lavanyar

    I think you need to use SendKeys to switch to the "Remove the application from this computer" option, the proper key should be SHIFT + TAB

    On Error Resume Next
    Set objShell = WScript.CreateObject("WScript.Shell")
    objShell.Run "taskkill /f /im [your app process name]*"
    objShell.Run "[your app uninstall key]"
    Do Until Success = True
        Success = objShell.AppActivate("[your window title]")
        Wscript.Sleep 200
    Loop
    objShell.SendKeys "+{TAB}"
    objShell.SendKeys "OK"
    
    Hope this helps.

    Saturday, April 27, 2013 9:29 AM
  • I myself needed that and took a few things here and there and made the below.

    It kills the app, finds the registry information and uninstall by creating a temporary batch file. Like that it can wait that the whole process to be over before uninstalling.

    Just in case its useful to anyone... run isAppRunning()

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using Microsoft.Win32;
    using System.Threading;
    using System.IO;
    using System.Diagnostics;
    using System.Windows.Forms;
    using System.Security.Permissions;
    
    namespace MyNameSpace
    {
        public class uninstallclickonce
        {
                   
                [System.Runtime.InteropServices.DllImport("user32.dll")]
    
                private static extern IntPtr FindWindow(string lpClassName, string lpWindowName);
    
                [System.Runtime.InteropServices.DllImport("user32.dll")]
    
                private static extern bool SetForegroundWindow(IntPtr hWnd);
    
                private Process process;
                private ProcessStartInfo startInfo;
    
                public void isAppRunning()
                {
                    //
                    //Run the below command in CMD to find the name of the process in the text file.
                    //WMIC /OUTPUT:C:\ProcessList.txt PROCESS get Caption,Commandline,Processid
                    //
                    string processNameToKill = "Auto-Crop"; //Change the name of the process to kill
    
                    Process [] runningProcesses = Process.GetProcesses();
    
                    foreach (Process myProcess in runningProcesses)
                    {
                        //Check if given process name is running
                        if (myProcess.ProcessName == processNameToKill)
                        {
                            killAppRunning(myProcess);
                        }
                    }
                }
    
                private void killAppRunning(Process myProcess)
                {
                    //Ask the user if he wants to kill the process now or cancel the installation altogether
                    DialogResult killMsgBox = MessageBox.Show("Crop-Me for OCA must not be running in order to get the new version\nIf you are ready to close the app, click OK.\nClick Cancel to abort the installation.", "Crop-Me Still Running", MessageBoxButtons.OKCancel, MessageBoxIcon.Question);
                    
                    switch(killMsgBox)
                    {
                        case DialogResult.OK:
                            //Kill the process
                            myProcess.Kill();
                            findRegistryClickOnce();
                            break;
                        case DialogResult.Cancel:
                            //Cancel whole instalation
                            break;
                    }
    
                }
    
                private void findRegistryClickOnce()
                {
                    string uninstallRegString = null; //Will be ClickOnce Uninstall String
                    string valueToFind = "Crop Me for OCA"; //Name of the application we want to uninstall (found in registry)
                    string keyNameToFind = "DisplayName"; //Name of the Value in registry
                    string uninstallValueName = "UninstallString"; //Name of the uninstall string
                    string regProgsLocation = "Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall"; //Registry location where we find all installed ClickOnce apps
    
                    using (RegistryKey baseLocRegKey = Registry.CurrentUser.OpenSubKey(regProgsLocation))
                    {
                        //Console.WriteLine("There are {0} subkeys in here", baseLocRegKey.SubKeyCount.ToString());
    
                        foreach (string subkeyfirstlevel in baseLocRegKey.GetSubKeyNames())
                        {
                           // Console.WriteLine("{0,-8}: {1}", subkeyfirstlevel, baseLocRegKey.GetValueNames()); //can use to see what you find in registry
                            
                            try
                            {
                                string subtest = baseLocRegKey.ToString() + "\\" + subkeyfirstlevel.ToString();
                                
                                using (RegistryKey cropMeLocRegKey = Registry.CurrentUser.OpenSubKey(regProgsLocation + "\\" + subkeyfirstlevel)) 
                                {
                                    //Console.WriteLine("Subkey DisplayName: " + cropMeLocRegKey.GetValueNames()); //can use to see what you find in registry
                                    //For each 
                                    foreach (string subkeysecondlevel in cropMeLocRegKey.GetValueNames())
                                    {
                                        //If the Value Name equals the name app to uninstall
                                        if (cropMeLocRegKey.GetValue(keyNameToFind).ToString() == valueToFind)
                                        {
                                            uninstallRegString = cropMeLocRegKey.GetValue(uninstallValueName).ToString();
                                            //Exit Foreach
                                            break;
                                        }
                                    }
                                }
                            }
                            catch (System.Security.SecurityException)
                            {
                                MessageBox.Show("security exception?");
                            }
                        }
                    }
                    if (uninstallRegString != null)
                    {
                        batFileCreateStartProcess(uninstallRegString);
                    }
                }
    
                private void batFileCreateStartProcess(string uninstallRegstring) //Creates batch file to run the uninstall from
                {
                    string tempPathfile = Path.GetTempPath() + "cropmeuninstall.bat"; //Batch file name, which will be created in Window's temps foler
                                    
                    if (!File.Exists(@tempPathfile))
                    {
                        using (FileStream createfile = File.Create(@tempPathfile))
                        {
                            createfile.Close();
                        }
                    }
    
                    using (StreamWriter writefile = new StreamWriter(@tempPathfile))
                    {
                        writefile.WriteLine(@"Start " + uninstallRegstring); //Writes our uninstall value found earlier in batch file
                    }
    
    
                    process = new Process();
                    startInfo = new ProcessStartInfo();
    
                    startInfo.FileName = tempPathfile;
                    process.StartInfo = startInfo;
                    process.Start();
                    process.WaitForExit();
                   
                    File.Delete(tempPathfile); //Deletes the file
                    
                    removeClickOnceAuto();
                     
                    }
    
                private void removeClickOnceAuto() //Automation of clicks in the uninstall to remove the need of any user interactions
                {
                    IntPtr myWindowHandle = IntPtr.Zero;
    
                    for (int i = 0; i < 60 && myWindowHandle == IntPtr.Zero; i++)
                    {
    
                        Thread.Sleep(1500);
    
                        myWindowHandle = FindWindow(null, "Crop Me for OCA Maintenance");
    
                    }
    
                    if (myWindowHandle != IntPtr.Zero)
                    {
    
                        SetForegroundWindow(myWindowHandle);
    
                        SendKeys.Send("+{TAB}"); // SHIFT-TAB
    
                        SendKeys.Send("{ENTER}");
    
                        SendKeys.Flush();
                    }
                }
    
        }
    }


    • Edited by K Rol Thursday, September 19, 2013 12:49 AM
    Thursday, September 19, 2013 12:45 AM
  • I found this simple PowerShell script which uninstall the ClickOnce app and handles the UI:

    $InstalledApplicationNotMSI = Get-ChildItem HKCU:\Software\Microsoft\Windows\CurrentVersion\Uninstall | foreach-object {Get-ItemProperty $_.PsPath}
    $UninstallString = $InstalledApplicationNotMSI | ? { $_.displayname -match "[your app process name]" } | select UninstallString 
    $wshell = new-object -com wscript.shell
    $selectedUninstallString = $UninstallString.UninstallString
    $wshell.run("cmd /c $selectedUninstallString")
    Start-Sleep 5
    $wshell.sendkeys("`"OK`"~")
    

    Thursday, June 26, 2014 8:08 PM
  • Here is an improvement.  Instead of setting a delay and hoping it is enough.  Use a while loop.

    $uninstall64 = gci "HKCU:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall" | foreach { gp $_.PSPath } | ? { $_ -match "YourProgram" } | select UninstallString
    
    if ($uninstall64) {
    $uninstall64 = $uninstall64.UninstallString
    $uninstall64 = $uninstall64.Trim()
    
    $wshell = New-Object -ComObject wscript.shell;
    $wshell.run($uninstall64)
    $wshell.AppActivate('YourProgram Maintenance')
    while (-not $wshell.AppActivate('YourProgram Maintenance')) 
    {
    Sleep 1
    $wshell.AppActivate('YourProgram Maintenance')
    }
    
    $wshell.SendKeys('+{TAB}')
    $wshell.Sendkeys('+{TAB}')
    $wshell.sendkeys('{DOWN}')
    $wshell.sendkeys('{TAB}')
    $wshell.sendkeys('~')
    }


    Greg Chao
    Tuesday, February 21, 2017 8:44 PM