我是否可以可靠地启动ClickOnce应用程序的卸载,来自应用程序?
换句话说,我想在其中一个表单上为用户提供一个大的“立即卸载我”按钮。当用户单击该按钮时,我想启动此应用程序的Windows卸载过程,并可能关闭该应用程序。
原因:我们正在使用ClickOnce应用程序,并且希望能够轻松删除 ,因为它要安装。 我们不想发送它们沿着“添加或删除程序”的路径,冒着迷路或分散注意力的风险。
这可以可靠地完成吗?
答案 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的说明:
按照这些说明为将来的应用程序准备应用程序 从不同的安装点重新安装。这些步骤添加了必要的库 引用,以便您的应用程序可以从新位置自动重新安装 即使尚未安装新安装,也可以随时执行这些步骤 必要的。
打开ClickOnceReinstaller项目并在发布模式下构建项目。
打开ClickOnce应用程序并参考 ClickOnceReinstaller.dll文件到您的启动项目。
或者,您可以将ClickOnceReinstaller项目添加到您的应用程序中 并参考该项目。
接下来,打开包含应用程序入口点的代码文件。 (通常,在C#中,这是Program.cs)
在应用程序入口点文件中,拨打电话 Reinstaller.CheckForUpdates()函数。有几种方法 CheckForUpdates()的签名。请参阅Intellisense说明 确定为您的应用程序调用哪个签名。最初,这个 应该无关紧要,因为不应该发布必要的查找文件 你的安装服务器。
(可选)Reinstaller.CheckForUpdates方法返回InstallStatus对象 这是安装过程状态的枚举值。抓住这个 价值并相应地处理它。每个潜在回报值的定义 可以通过Intellisense找到每个值。
NoUpdates响应意味着目前没有新的更新需要 重新安装您的应用程序。
测试编译您的应用程序并将新版本的应用程序重新发布到 安装服务器。
一旦应用程序需要移动到新的Web地址或,则需要执行这些步骤 需要对需要重新安装的应用程序进行更改 应用
如果您的网络服务器需要移动到新位置,强烈建议您 在获取当前值之前,请按照以下步骤操作并实施新的安装点 ClickOnce安装点离线。
您不必将重新安装文件保存到原始文件的根目录 但是,应用程序安装文件夹需要发布一个版本的 您的应用程序到引用Web地址的原始安装点 将包含将指定新安装点的重新安装文件。
这需要一些预先计划,以便可以从中进行参考 应用于您知道可以控制的路径。
重新安装文件可以保存到初始安装位置的根目录 但如果尚不需要重新安装应用程序,则必须保留为空 空的重新安装文件将被忽略。
从技术上讲,API会通过调用“重新安装”来寻找网络共鸣。 可能会在返回a的服务器上实现一种机制 文本响应新安装的位置。
通过查看文件的第一行来解析重新安装文件 新安装的位置。所有其他文本都被忽略。这个 是故意的,以便可以实现对此API的后续更新 重新安装响应中的较新属性。
其当前状态的API仅支持ClickOnce应用程序 已根据英国文化变体安装。原因就在于此 约束是因为通过查找卸载来自动化该过程 对话框并将Click命令传递给文本值为的按钮 “OK”。
答案 2 :(得分:1)
它提供了以下博客的链接,其中代码卸载了应用程序,然后重新安装了应用程序,您可能只想卸载。看看吧。
答案 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 });
希望这有助于某人。