Как инициировать удаление приложения ClickOnce из приложения?

могу ли я надежно инициировать установку приложения ClickOnce, из приложения?

другими словами, Я хочу дать пользователю большую кнопку" Удалить меня сейчас " на одной из форм. Когда пользователь нажимает кнопку, Я хочу запустить процесс удаления windows для этого приложения и, возможно, закрыть приложение.

причина: мы тупиковый приложение ClickOnce и хотите, чтобы сделать его так же легко удалить как было установить. Мы не хотим отправлять их по пути "добавления или удаления программ" и рисковать их потеряться или отвлечься.

можно ли это сделать надежно?

4 ответов


Я бы рекомендовал проверить эту статью MSDN здесь. В нем объясняется, как программно удалить приложение (и переустановить с нового URL-адреса, если вы хотите):

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

Это вариант записи в блоге jameshart, но он включает в себя несколько исправлений, которые вы захотите использовать. Существуют загрузки кода как в C#, так и в VB.

в самом деле, вы можете просто нажать Обновить и приложение Удалите себя, вам даже не нужно, чтобы пользователь сказал "ОК".


Я просто оставлю это здесь для всех, кто ищет код, и обнаруживает, что ссылки для загрузки в других ответах мертвы:

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

Edit: добавлен код из 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:

A. ссылка на этот API в текущих приложениях.

следовать эти инструкции по подготовке вашей заявки к будущей заявке переустановка с другой точки установки. Эти шаги добавляют необходимую библиотеку ссылки, чтобы приложение можно было автоматически переустановить из нового места.
Эти шаги могут последовать в любой момент, даже если новая установка еще не необходимый.

  1. откройте проект ClickOnceReinstaller и создайте проект в режиме выпуска.

  2. открыть ваше приложение ClickOnce и ссылка на ClickOnceReinstaller.dll-файл для запуска проекта.

    кроме того, можно добавить проект ClickOnceReinstaller в приложение и рефренс проекта.

  3. затем откройте файл кода, содержащий точку входа для вашего приложения. (Как правило, в C#, это программа.cs)

    из файла точки входа приложения выполните вызов Reinstaller.Проверка наличия обновлений() функция. Существует несколько методов подписи для CheckForUpdates (). См. описания Intellisense для определение подписи для вызова приложения. Первоначально это не имеет значения, потому что необходимый файл поиска не должен быть опубликован в ваш сервер установки.

    (опционально) Reinstaller.Метод CheckForUpdates возвращает объект InstallStatus который является перечисленным значением состояния процесса установки. Захватите это значение и ручка это соответственно. Определения для каждого потенциального возвращаемого значения можно найти через IntelliSense для каждого значения.

    ответ NoUpdates означает, что в настоящее время нет новых обновлений, требующих переустановка приложения.

  4. Test скомпилируйте приложение и повторно опубликуйте новую версию приложения в установочный сервер.

B. обновление приложения с новой установки местоположение

эти шаги необходимы, как только приложение должно перейти на новый веб-адрес или необходимо внести изменения в приложение, требующее переустановки приложение.

Если ваш веб-сервер должен переместиться в новое место, настоятельно рекомендуется выполните следующие действия и реализуйте новую точку установки Перед выполнением текущей ClickOnce точка установки в автономном режиме.

  1. в текстовом редакторе, создайте новый файл.
  2. в первой строке файла добавьте полное расположение для новой установки местоположение. (т. е. сохраните новый файл вhttp://www.example.com/ClickOnceInstall_NewLocation/)
  3. сохраните файл как "переустановить" в корневой каталог текущих приложений ClickOnce место установки. (Я. e http://www.example.com/ClickOnceInstall/reinstall где http://www.example.com/ClickOnceInstall/ корень путь установки.)
  4. запустите приложение с тестовой машины. Приложение должно автоматическое удаление текущей версии приложения и переустановка это из расположения, указанного в файле reinstall.

С. Особые Примечания

  1. вам не нужно сохранять файл переустановки в корневой каталог оригинала папка установки приложения, однако, вам нужно будет опубликовать версию ваше приложение к исходной точке установки, ссылающейся на веб-адрес это будет содержать файл переустановки, который укажет новую точку установки.

    Это требует немного предварительного планирования, так что ссылка может быть сделана из применение к пути, который вы знаете, вы будете иметь контроль.

  2. файл переустановки можно сохранить в корневом каталоге начальной установки но должен быть оставлен пустым, если приложение еще не нужно переустановить.
    - пустой файл переустановки игнорируется.

  3. технически API ищет веб-резонанс от вызова "переустановить". Механизм потенциально может быть реализован на сервере, который возвращает text reponse с местоположением новой установки.

  4. файл переустановки анализируется, просматривая первую строку файла для расположение новой установки. Все остальные тексты игнорируются. Этот намеренно, чтобы последующие обновления этот API может потенциально реализовать новые свойства в ответе reinstall.

  5. API в его текущем состоянии будет поддерживать только приложения ClickOnce, которые были установлены под вариант английской культуры. Причина этого ограничение заключается в том, что процесс автоматизирован путем поиска удаления диалог и передача команды Click кнопке с текстовым значением "ЛАДНО."


посмотрите на эту тему: http://social.msdn.microsoft.com/Forums/en-US/winformssetup/thread/4b681725-faaa-48c3-bbb0-02ebf3926e25

Он дает ссылку на следующий блог, где код удаляет приложение, а затем повторно устанавливает приложение, вы, вероятно, захотите просто удалить. взглянуть на него.

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


для сумасшедших или отчаянных, отражение на помощь! Замените " X " на ваши приложения .имя файла приложения (не путь) и токен открытого ключа.

протестировано только в 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 });

надеюсь, это кому-то поможет.