Как инициировать удаление приложения ClickOnce из приложения? - PullRequest
11 голосов
/ 20 апреля 2010

Можно ли надежно инициировать деинсталляцию приложения ClickOnce, из приложения ?

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

Причина: мы зашли в тупик с приложением ClickOnce и хотим, чтобы его было так просто удалить , как это было при установке. Мы не хотим отправлять их в путь к «Установка и удаление программ» и риск их потерять или отвлечься.

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

Ответы [ 4 ]

9 голосов
/ 20 апреля 2010

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

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

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

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

5 голосов
/ 03 июня 2015

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

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:

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

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

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

  2. Откройте приложение ClickOnce и ссылку на Файл ClickOnceReinstaller.dll для запуска проекта.

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

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

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

    (ДОПОЛНИТЕЛЬНО) Метод Reinstaller.CheckForUpdates возвращает объект InstallStatus который является перечисляемым значением состояния процесса установки. Захватить это цените и обрабатывайте это соответственно. Определения для каждого потенциального возвращаемого значения можно найти через Intellisense для каждого значения.

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

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

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

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

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

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

C. Особые примечания

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

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

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

  3. Технически, API ищет веб-резонанс от вызова «переустановить». На сервере потенциально может быть реализован механизм, который возвращает текстовый ответ с указанием места новой установки.

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

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

1 голос
/ 29 апреля 2016

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

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

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

1 голос
/ 20 апреля 2010

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

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

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

...