我将这里留给任何来寻找代码并发现其他答案中的下载链接已失效的人:
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。
按照这些说明为将来的申请准备申请
从不同的安装点重新安装。这些步骤添加了必要的库
引用,以便您的应用程序可以自动从新位置重新安装。
即使尚未进行新安装,也可以随时执行这些步骤
必要的。
打开 ClickOnceReinstaller 项目并在 Release 模式下构建项目。
-
打开您的 ClickOnce 应用程序并引用
ClickOnceReinstaller.dll 文件到您的启动项目。
或者,您可以将 ClickOnceReinstaller 项目添加到您的应用程序
并引用该项目。
-
接下来,打开包含应用程序入口点的代码文件。
(通常,在 C# 中,这是 Program.cs)
从应用程序入口点文件中,调用
Reinstaller.CheckForUpdates() 函数。有几种方法
CheckForUpdates() 的签名。请参阅 Intellisense 描述
确定为您的应用程序调用哪个签名。最初,这
应该无关紧要,因为不应将必要的查找文件发布到
您的安装服务器。
(可选)Reinstaller.CheckForUpdates 方法返回一个 InstallStatus 对象
这是安装过程状态的枚举值。捕捉这个
重视并相应地处理它。每个潜在返回值的定义
可以通过 Intellisense 找到每个值。
NoUpdates 响应意味着当前没有新的更新需要
重新安装您的应用程序。
测试编译您的应用程序并将应用程序的新版本重新发布到
安装服务器。
B.从新的安装位置更新您的应用程序
一旦应用程序需要移动到新的网址或
需要对需要重新安装的应用程序进行更改
应用。
如果您的网络服务器需要移动到新位置,强烈建议您
遵循这些步骤并实施新的安装点,然后再采用当前的安装点
ClickOnce 安装点离线。
- 在文本编辑器中,创建一个新文件。
- 在文件的第一行,添加新安装的完全限定位置
地点。 (即将新文件保存到http://www.example.com/ClickOnceInstall_NewLocation/)
- 将文件另存为“重新安装”到当前应用程序 ClickOnce 的根目录
安装位置。 (即http://www.example.com/ClickOnceInstall/reinstall
http://www.example.com/ClickOnceInstall/ 是安装路径的根目录。)
- 从您的测试机器启动您的应用程序。该应用程序应
自动卸载您当前版本的应用程序并重新安装
它来自重新安装文件中指定的位置。
C.特别说明
-
您不必将重新安装文件保存到原始根目录
应用程序安装文件夹,但是,您需要发布一个版本的
您的应用程序到引用 Web 地址的原始安装点
这将包含一个重新安装文件,该文件将指定新的安装点。
这需要一些预先计划,以便可以从
应用到您知道可以控制的路径。
可以将重新安装文件保存到初始安装位置的根目录
但如果还不需要重新安装应用程序,则必须留空。
空的重新安装文件将被忽略。
从技术上讲,API 从“重新安装”调用中寻找网络响应。
一种机制可能会在服务器上实现,它返回一个
文本回复新安装的位置。
通过查看文件的第一行来解析重新安装文件
新安装的位置。所有其他文本都被忽略。这
是有意的,以便此 API 的后续更新可能会实现
重新安装响应中的更新属性。
当前状态下的 API 将仅支持 ClickOnce 应用程序
已安装在英语文化变体下。这样做的原因
约束是因为该过程是通过查找卸载来自动化的
对话框并将单击命令传递给具有文本值的按钮
“好的”。