如何从应用程序中启动ClickOnce应用程序的卸载?

时间:2010-04-19 23:54:52

标签: .net winforms deployment clickonce

我是否可以可靠地启动ClickOnce应用程序的卸载,来自应用程序

换句话说,我想在其中一个表单上为用户提供一个大的“立即卸载我”按钮。当用户单击该按钮时,我想启动此应用程序的Windows卸载过程,并可能关闭该应用程序。

原因:我们正在使用ClickOnce应用程序,并且希望能够轻松删除 ,因为它要安装。 我们不想发送它们沿着“添加或删除程序”的路径,冒着迷路或分散注意力的风险。

这可以可靠地完成吗?

4 个答案:

答案 0 :(得分:9)

我建议在这里查看这篇MSDN文章。它解释了如何以编程方式卸载应用程序(如果您愿意,可以从新URL重新安装):

http://msdn.microsoft.com/en-us/library/ff369721.aspx

这是jameshart博客条目的变体,但它包含了一些您想要使用的修补程序。 C#和VB都有代码下载。

事实上,您只需推送更新并让应用程序自行卸载,您甚至不需要用户说“确定”。

答案 1 :(得分:5)

我会把这个留给那些寻找代码的人,并发现其他答案中的下载链接已经死了:

https://code.google.com/p/clickonce-application-reinstaller-api

编辑:添加了Reinstaller.cs中的代码和ReadMe.txt中的说明

/* ClickOnceReinstaller v 1.0.0
 *  - Author: Richard Hartness (rhartness@gmail.com)
 *  - Project Site: http://code.google.com/p/clickonce-application-reinstaller-api/
 * 
 * Notes:
 * This code has heavily borrowed from a solution provided on a post by
 * RobinDotNet (sorry, I couldn't find her actual name) on her blog,
 * which was a further improvement of the code posted on James Harte's
 * blog.  (See references below)
 * 
 * This code contains further improvements on the original code and
 * wraps it in an API which you can include into your own .Net, 
 * ClickOnce projects.
 * 
 * See the ReadMe.txt file for instructions on how to use this API.
 * 
 * References:
 * RobinDoNet's Blog Post:
 * - ClickOnce and Expiring Certificates
 *   http://robindotnet.wordpress.com/2009/03/30/clickonce-and-expiring-certificates/
 *   
 * Jim Harte's Original Blog Post:
 * - ClickOnce and Expiring Code Signing Certificates
 *   http://www.jamesharte.com/blog/?p=11
 */


using Microsoft.Win32;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Deployment.Application;
using System.Diagnostics;
using System.IO;
using System.Text;
using System.Net;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Security.Policy;
using System.Windows.Forms;
using System.Xml;

namespace ClickOnceReinstaller
{
    #region Enums
    /// <summary>
    /// Status result of a CheckForUpdates API call.
    /// </summary>
    public enum InstallStatus { 
        /// <summary>
        /// There were no updates on the server or this is not a ClickOnce application.
        /// </summary>
        NoUpdates, 
        /// <summary>
        /// The installation process was successfully executed.
        /// </summary>
        Success, 
        /// <summary>
        /// In uninstall process failed.
        /// </summary>
        FailedUninstall, 
        /// <summary>
        /// The uninstall process succeeded, however the reinstall process failed.
        /// </summary>
        FailedReinstall };
    #endregion

    public static class Reinstaller
    {
        #region Public Methods

        /// <summary>
        /// Check for reinstallation instructions on the server and intiate reinstallation.  Will look for a "reinstall" response at the root of the ClickOnce application update address.
        /// </summary>
        /// <param name="exitAppOnSuccess">If true, when the function is finished, it will execute Environment.Exit(0).</param>
        /// <returns>Value indicating the uninstall and reinstall operations successfully executed.</returns>
        public static InstallStatus CheckForUpdates(bool exitAppOnSuccess)
        {
            //Double-check that this is a ClickOnce application.  If not, simply return and keep running the application.
            if (!ApplicationDeployment.IsNetworkDeployed) return InstallStatus.NoUpdates;

            string reinstallServerFile = ApplicationDeployment.CurrentDeployment.UpdateLocation.ToString();

            try
            {
                reinstallServerFile = reinstallServerFile.Substring(0, reinstallServerFile.LastIndexOf("/") + 1);
                reinstallServerFile = reinstallServerFile + "reinstall";
#if DEBUG
                Trace.WriteLine(reinstallServerFile);

#endif          
            } 
            catch 
            {
                return InstallStatus.FailedUninstall;
            }
            return CheckForUpdates(exitAppOnSuccess, reinstallServerFile);
        }

        /// <summary>
        /// Check for reinstallation instructions on the server and intiate reinstall.
        /// </summary>
        /// <param name="exitAppOnSuccess">If true, when the function is finished, it will execute Environment.Exit(0).</param>
        /// <param name="reinstallServerFile">Specify server address for reinstallation instructions.</param>
        /// <returns>InstallStatus state of reinstallation process.</returns>
        public static InstallStatus CheckForUpdates(bool exitAppOnSuccess, string reinstallServerFile)
        {
            string newAddr = "";

            if (!ApplicationDeployment.IsNetworkDeployed) return InstallStatus.NoUpdates;

            //Check to see if there is a new installation.
            try
            {
                HttpWebRequest rqHead = (HttpWebRequest)HttpWebRequest.Create(reinstallServerFile);
                rqHead.Method = "HEAD";
                rqHead.Credentials = CredentialCache.DefaultCredentials;
                HttpWebResponse rsHead = (HttpWebResponse)rqHead.GetResponse();

#if DEBUG
                Trace.WriteLine(rsHead.Headers.ToString());
#endif
                if (rsHead.StatusCode != HttpStatusCode.OK) return InstallStatus.NoUpdates;

                //Download the file and extract the new installation location
                HttpWebRequest rq = (HttpWebRequest)HttpWebRequest.Create(reinstallServerFile);
                WebResponse rs = rq.GetResponse();
                Stream stream = rs.GetResponseStream();
                StreamReader sr = new StreamReader(stream);

                //Instead of reading to the end of the file, split on new lines.
                //Currently there should be only one line but future options may be added.  
                //Taking the first line should maintain a bit of backwards compatibility.
                newAddr = sr.ReadToEnd()
                    .Split(new string[] {Environment.NewLine}, StringSplitOptions.RemoveEmptyEntries)[0];

                //No address, return as if there are no updates.
                if (newAddr == "") return InstallStatus.NoUpdates;
            }
            catch
            {
                //If we receive an error at this point in checking, we can assume that there are no updates.
                return InstallStatus.NoUpdates;
            }


            //Begin Uninstallation Process
            MessageBox.Show("There is a new version available for this application.  Please click OK to start the reinstallation process.");

            try
            {
                string publicKeyToken = GetPublicKeyToken();
#if DEBUG
                Trace.WriteLine(publicKeyToken);
#endif

                // Find Uninstall string in registry    
                string DisplayName = null;
                string uninstallString = GetUninstallString(publicKeyToken, out DisplayName);
                if (uninstallString == null || uninstallString == "") 
                    throw new Exception("No uninstallation string was found.");
                string runDLL32 = uninstallString.Substring(0, uninstallString.IndexOf(" "));
                string args = uninstallString.Substring(uninstallString.IndexOf(" ") + 1);

#if DEBUG
                Trace.WriteLine("Run DLL App: " + runDLL32);
                Trace.WriteLine("Run DLL Args: " + args);
#endif
                Process uninstallProcess = Process.Start(runDLL32, args);
                PushUninstallOKButton(DisplayName);
            }
            catch
            {
                return InstallStatus.FailedUninstall;
            }

            //Start the re-installation process
#if DEBUG
            Trace.WriteLine(reinstallServerFile);
#endif

            try
            {
#if DEBUG
                Trace.WriteLine(newAddr);
#endif
                //Start with IE-- other browser will certainly fail.
                Process.Start("iexplore.exe", newAddr);             
            }
            catch
            {
                return InstallStatus.FailedReinstall;
            }

            if (exitAppOnSuccess) Environment.Exit(0);
            return InstallStatus.Success;
        }
        #endregion

        #region Helper Methods
        //Private Methods
        private static string GetPublicKeyToken()
        {
            ApplicationSecurityInfo asi = new ApplicationSecurityInfo(AppDomain.CurrentDomain.ActivationContext);

            byte[] pk = asi.ApplicationId.PublicKeyToken;
            StringBuilder pkt = new StringBuilder();
            for (int i = 0; i < pk.GetLength(0); i++)
                pkt.Append(String.Format("{0:x2}", pk[i]));

            return pkt.ToString();
        }
        private static string GetUninstallString(string PublicKeyToken, out string DisplayName)
        {
            string uninstallString = null;
            string searchString = "PublicKeyToken=" + PublicKeyToken;
#if DEBUG
            Trace.WriteLine(searchString);
#endif
            RegistryKey uninstallKey = Registry.CurrentUser.OpenSubKey("Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall");
            string[] appKeyNames = uninstallKey.GetSubKeyNames();
            DisplayName = null;
            foreach (string appKeyName in appKeyNames)
            {
                RegistryKey appKey = uninstallKey.OpenSubKey(appKeyName);
                string temp = (string)appKey.GetValue("UninstallString");
                DisplayName = (string)appKey.GetValue("DisplayName");
                appKey.Close();
                if (temp.Contains(searchString))
                {
                    uninstallString = temp;
                    DisplayName = (string)appKey.GetValue("DisplayName");
                    break;
                }
            }
            uninstallKey.Close();
            return uninstallString;
        }
        #endregion

        #region Win32 Interop Code
        //Structs
        [StructLayout(LayoutKind.Sequential)]
        private struct FLASHWINFO
        {
            public uint cbSize;
            public IntPtr hwnd;
            public uint dwFlags;
            public uint uCount;
            public uint dwTimeout;
        }

        //Interop Declarations
        [DllImport("user32.Dll")]
        private static extern int EnumWindows(EnumWindowsCallbackDelegate callback, IntPtr lParam);
        [DllImport("User32.Dll")]
        private static extern void GetWindowText(int h, StringBuilder s, int nMaxCount);
        [DllImport("User32.Dll")]
        private static extern void GetClassName(int h, StringBuilder s, int nMaxCount);
        [DllImport("User32.Dll")]
        private static extern bool EnumChildWindows(IntPtr hwndParent, EnumWindowsCallbackDelegate lpEnumFunc, IntPtr lParam);
        [DllImport("User32.Dll")]
        private static extern IntPtr SendMessage(IntPtr hWnd, uint Msg, IntPtr wParam, IntPtr lParam);
        [DllImport("user32.dll")]
        private static extern short FlashWindowEx(ref FLASHWINFO pwfi);
        [DllImport("user32.dll", SetLastError = true)]
        private static extern IntPtr FindWindow(string lpClassName, string lpWindowName);

        //Constants
        private const int BM_CLICK = 0x00F5;
        private const uint FLASHW_ALL = 3;
        private const uint FLASHW_CAPTION = 1;
        private const uint FLASHW_STOP = 0;
        private const uint FLASHW_TIMER = 4;
        private const uint FLASHW_TIMERNOFG = 12;
        private const uint FLASHW_TRAY = 2;
        private const int FIND_DLG_SLEEP = 200; //Milliseconds to sleep between checks for installation dialogs.
        private const int FIND_DLG_LOOP_CNT = 50; //Total loops to look for an install dialog. Defaulting 200ms sleap time, 50 = 10 seconds.

        //Delegates
        private delegate bool EnumWindowsCallbackDelegate(IntPtr hwnd, IntPtr lParam);

        //Methods
        private static IntPtr SearchForTopLevelWindow(string WindowTitle)
        {
            ArrayList windowHandles = new ArrayList();
            /* Create a GCHandle for the ArrayList */
            GCHandle gch = GCHandle.Alloc(windowHandles);
            try
            {
                EnumWindows(new EnumWindowsCallbackDelegate(EnumProc), (IntPtr)gch);
                /* the windowHandles array list contains all of the
                    window handles that were passed to EnumProc.  */
            }
            finally
            {
                /* Free the handle */
                gch.Free();
            }

            /* Iterate through the list and get the handle thats the best match */
            foreach (IntPtr handle in windowHandles)
            {
                StringBuilder sb = new StringBuilder(1024);
                GetWindowText((int)handle, sb, sb.Capacity);
                if (sb.Length > 0)
                {
                    if (sb.ToString().StartsWith(WindowTitle))
                    {
                        return handle;
                    }
                }
            }

            return IntPtr.Zero;
        }
        private static IntPtr SearchForChildWindow(IntPtr ParentHandle, string Caption)
        {
            ArrayList windowHandles = new ArrayList();
            /* Create a GCHandle for the ArrayList */
            GCHandle gch = GCHandle.Alloc(windowHandles);
            try
            {
                EnumChildWindows(ParentHandle, new EnumWindowsCallbackDelegate(EnumProc), (IntPtr)gch);
                /* the windowHandles array list contains all of the
                    window handles that were passed to EnumProc.  */
            }
            finally
            {
                /* Free the handle */
                gch.Free();
            }

            /* Iterate through the list and get the handle thats the best match */
            foreach (IntPtr handle in windowHandles)
            {
                StringBuilder sb = new StringBuilder(1024);
                GetWindowText((int)handle, sb, sb.Capacity);
                if (sb.Length > 0)
                {
                    if (sb.ToString().StartsWith(Caption))
                    {
                        return handle;
                    }
                }
            }

            return IntPtr.Zero;

        }
        private static bool EnumProc(IntPtr hWnd, IntPtr lParam)
        {
            /* get a reference to the ArrayList */
            GCHandle gch = (GCHandle)lParam;
            ArrayList list = (ArrayList)(gch.Target);
            /* and add this window handle */
            list.Add(hWnd);
            return true;
        }
        private static void DoButtonClick(IntPtr ButtonHandle)
        {
            SendMessage(ButtonHandle, BM_CLICK, IntPtr.Zero, IntPtr.Zero);
        }
        private static IntPtr FindDialog(string dialogName)
        {
            IntPtr hWnd = IntPtr.Zero;

            int cnt = 0;
            while (hWnd == IntPtr.Zero && cnt++ != FIND_DLG_LOOP_CNT)
            {
                hWnd = SearchForTopLevelWindow(dialogName);
                System.Threading.Thread.Sleep(FIND_DLG_SLEEP);
            }

            if (hWnd == IntPtr.Zero) 
                throw new Exception(string.Format("Installation Dialog \"{0}\" not found.", dialogName));
            return hWnd;
        }
        private static IntPtr FindDialogButton(IntPtr hWnd, string buttonText)
        {
            IntPtr button = IntPtr.Zero;
            int cnt = 0;
            while (button == IntPtr.Zero && cnt++ != FIND_DLG_LOOP_CNT)
            {
                button = SearchForChildWindow(hWnd, buttonText);
                System.Threading.Thread.Sleep(FIND_DLG_SLEEP);
            }
            return button;
        }
        private static bool FlashWindowAPI(IntPtr handleToWindow)
        {
            FLASHWINFO flashwinfo1 = new FLASHWINFO();
            flashwinfo1.cbSize = (uint)Marshal.SizeOf(flashwinfo1);
            flashwinfo1.hwnd = handleToWindow;
            flashwinfo1.dwFlags = 15;
            flashwinfo1.uCount = uint.MaxValue;
            flashwinfo1.dwTimeout = 0;
            return (FlashWindowEx(ref flashwinfo1) == 0);
        }

        //These are the only functions that should be called above.
        private static void PushUninstallOKButton(string DisplayName)
        {
            IntPtr diag = FindDialog(DisplayName + " Maintenance");
            IntPtr button = FindDialogButton(diag, "&OK");
            DoButtonClick(button);
        }
        #endregion
    }
}

ReadMe.txt的说明:

甲。在当前应用程序中引用此API。

按照这些说明为将来的应用程序准备应用程序 从不同的安装点重新安装。这些步骤添加了必要的库 引用,以便您的应用程序可以从新位置自动重新安装 即使尚未安装新安装,也可以随时执行这些步骤 必要的。

  1. 打开ClickOnceReinstaller项目并在发布模式下构建项目。

  2. 打开ClickOnce应用程序并参考 ClickOnceReinstaller.dll文件到您的启动项目。

    或者,您可以将ClickOnceReinstaller项目添加到您的应用程序中 并参考该项目。

  3. 接下来,打开包含应用程序入口点的代码文件。 (通常,在C#中,这是Program.cs)

    在应用程序入口点文件中,拨打电话 Reinstaller.CheckForUpdates()函数。有几种方法 CheckForUpdates()的签名。请参阅Intellisense说明 确定为您的应用程序调用哪个签名。最初,这个 应该无关紧要,因为不应该发布必要的查找文件 你的安装服务器。

    (可选)Reinstaller.CheckForUpdates方法返回InstallStatus对象 这是安装过程状态的枚举值。抓住这个 价值并相应地处理它。每个潜在回报值的定义 可以通过Intellisense找到每个值。

    NoUpdates响应意味着目前没有新的更新需要 重新安装您的应用程序。

  4. 测试编译您的应用程序并将新版本的应用程序重新发布到 安装服务器。

  5. B中。从新的安装位置更新您的应用程序

    一旦应用程序需要移动到新的Web地址或,则需要执行这些步骤 需要对需要重新安装的应用程序进行更改 应用

    如果您的网络服务器需要移动到新位置,强烈建议您 在获取当前值之前,请按照以下步骤操作并实施新的安装点 ClickOnce安装点离线。

    1. 在文本编辑器中,创建一个新文件。
    2. 在文件的第一行,添加新安装的完全限定位置 地点。 (即将新文件保存到http://www.example.com/ClickOnceInstall_NewLocation/
    3. 将文件另存为“重新安装”到当前应用程序ClickOnce的根目录 安装位置。 (即http://www.example.com/ClickOnceInstall/reinstall所在地 http://www.example.com/ClickOnceInstall/是安装路径的根。)
    4. 从您的测试机器启动您的应用程序。应用程序应该 自动卸载您当前版本的应用程序并重新安装 它来自重新安装文件中指定的位置。
    5. ℃。特别说明

      1. 您不必将重新安装文件保存到原始文件的根目录 但是,应用程序安装文件夹需要发布一个版本的 您的应用程序到引用Web地址的原始安装点 将包含将指定新安装点的重新安装文件。

        这需要一些预先计划,以便可以从中进行参考 应用于您知道可以控制的路径。

      2. 重新安装文件可以保存到初始安装位置的根目录 但如果尚不需要重新安装应用程序,则必须保留为空 空的重新安装文件将被忽略。

      3. 从技术上讲,API会通过调用“重新安装”来寻找网络共鸣。 可能会在返回a的服务器上实现一种机制 文本响应新安装的位置。

      4. 通过查看文件的第一行来解析重新安装文件 新安装的位置。所有其他文本都被忽略。这个 是故意的,以便可以实现对此API的后续更新 重新安装响应中的较新属性。

      5. 其当前状态的API仅支持ClickOnce应用程序 已根据英国文化变体安装。原因就在于此 约束是因为通过查找卸载来自动化该过程 对话框并将Click命令传递给文本值为的按钮 “OK”。

答案 2 :(得分:1)

看一下这个帖子: http://social.msdn.microsoft.com/Forums/en-US/winformssetup/thread/4b681725-faaa-48c3-bbb0-02ebf3926e25

它提供了以下博客的链接,其中代码卸载了应用程序,然后重新安装了应用程序,您可能只想卸载。看看吧。

http://www.jamesharte.com/blog/?p=11

答案 3 :(得分:1)

对于疯狂或绝望,反思救援!用你的应用程序的.application文件名(不是路径)和公钥令牌替换&#34; X&#34;

仅在Windows 10上测试。

        var textualSubId = "XXXXXXXXXXXXXXXXXX.application, Culture=neutral, PublicKeyToken=XXXXXXXXXXXXXXXX, processorArchitecture=amd64";

        var deploymentServiceCom = new System.Deployment.Application.DeploymentServiceCom();
        var _r_m_GetSubscriptionState = typeof(System.Deployment.Application.DeploymentServiceCom).GetMethod("GetSubscriptionState", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);

        var subState = _r_m_GetSubscriptionState.Invoke(deploymentServiceCom, new[] { textualSubId });
        var subscriptionStore = subState.GetType().GetProperty("SubscriptionStore").GetValue(subState);
        subscriptionStore.GetType().GetMethod("UninstallSubscription").Invoke(subscriptionStore, new[] { subState });

希望这有助于某人。