使用 Windows Installer 更新应用程序客户端:一个简单的解决方法






4.37/5 (9投票s)
如何使用 Windows Installer 和批处理文件更新本地分发应用程序的所有客户端。
引言
如果使用 Windows Installer 部署的应用程序分布在本地网络的许多客户端上,并且更新频繁,那么更新应用程序可能是一项耗时的任务。
我编写了这个简单的函数,它可以自动检测应用程序的新版本,并以透明的方式安装它,无需用户干预。
唯一的要求是将应用程序的安装文件 (.msi) 放置在一个预先确定的网络文件夹中,每个客户端都可以访问和检查它。
工作原理
必须修改应用程序的 Main
函数,包括下面提供的小段代码。 现在,当应用程序在客户端上启动时,代码会检查 .msi 文件是否具有新版本,如果是,则
- 删除当前版本的应用程序
- 从 .msi 文件安装新版本
- 再次启动应用程序
如果安装文件的文件日期至少比可执行文件 (.exe) 的文件日期晚两分钟,则认为安装文件是较新的(粗略的方法,但有效)。
由于应用程序无法在运行时卸载自身,因此它会自行退出并调用批处理文件来完成卸载/安装工作。 这个批处理文件由程序直接生成并放入临时文件夹中。 它看起来像这样
@echo off
ping localhost -n 2
C:\WINDOWS\system32\msiexec.exe /x {67BC5B51-1534-4C68-86C4-C74F4469C2BA} /qr
C:\WINDOWS\system32\msiexec.exe /i "Z:\setup\MyAppSetup.msi" /qr
cd "C:\Program Files\Myapp"
start "" "C:\Program Files\Myapp\MyApp.exe"
第 2 行的 ping 命令用于在卸载之前添加一个小的延迟,以确保调用应用程序已终止。 使用 ping 是因为没有标准的 Windows 命令来引入延迟(例如,sleep 命令)。
在第 3 行,使用 Windows Installer (/x 开关) 卸载旧应用程序。 /qr 开关最小化用户界面。
在第 4 行,安装新版本的应用程序。
在第 5 行和第 6 行,再次启动应用程序,提供相同的工作文件夹和相同的命令行参数(如果有)。 使用 start 命令使批处理文件继续执行,而无需等待应用程序完成。
使用代码
所有工作都在一个名为 CheckApplicationUpdate()
的函数中完成,您必须将其复制并粘贴到应用程序的 Main
程序中。 必须在 Application.Run()
之前调用此函数,例如
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
// add the following line to your main
if(CheckApplicationUpdate()) return; // update is needed, quit
Application.Run(new Form1());
}
需要通过修改三个字符串来定制 CheckApplicationUpdate()
static bool CheckApplicationUpdate()
{
string InstallFile = "Z:\\setup_folder\\MyApp.msi";
string BatName = "c:\\windows\\temp\\update.bat";
string ProductCode = "{67BC5B51-1534-4C68-86C4-C74F4469C2BA}";
InstallFile
是您放置更新安装包的位置;通常,它是一个网络文件夹,以便所有客户端都可以访问它。BatName
是程序生成的临时批处理文件,用于完成所有安装/卸载工作。ProductCode
是应用程序的产品代码,可以直接从应用程序的“设置”属性页获取。
整个函数
static bool CheckApplicationUpdate()
{
// location of setup file
string InstallFile = "Z:\\setup_folder\\MyApplication.msi";
// name of the .bat file that does the uninstall/install
string BatName = "c:\\windows\\temp\\update.bat";
// product code of the application
string ProductCode = "{67BC5B51-1534-4C68-86C4-C74F4469C2BA}";
// if install file is not available skip the whole process
if(!File.Exists(InstallFile))
return false;
// calculates the time difference betwenn install package and executable
DateTime EXE_Stamp = Directory.GetLastWriteTime(Application.ExecutablePath);
DateTime MSI_Stamp = Directory.GetLastWriteTime(InstallFile);
TimeSpan diff = MSI_Stamp - EXE_Stamp;
// if installable is newer than 2 minutes, does the new install
if(diff.Minutes > 2)
{
string msg = "A new version of "+Application.ProductName+
" is available.\r\n\r\nClick OK to install it.";
MessageBox.Show(msg,"Updates available",
MessageBoxButtons.OK,
MessageBoxIcon.Information);
// prepares the batch file
string BatFile = "";
string old_dir = Directory.GetCurrentDirectory();
BatFile += "@echo off\r\n";
BatFile += "ping localhost -n 2\r\n";
BatFile += "C:\\WINDOWS\\system32\\msiexec.exe /x "+
ProductCode+" /qr \r\n";
BatFile += "C:\\WINDOWS\\system32\\msiexec.exe /i \""+
InstallFile+"\" /qr\r\n";
BatFile += "cd \""+old_dir+"\"\r\n";
BatFile += "start \"\" "+
Environment.CommandLine+"\r\n";
StreamWriter sw = new StreamWriter(BatName);
sw.Write(BatFile);
sw.Close();
// executes the batch file
System.Diagnostics.ProcessStartInfo psi =
new System.Diagnostics.ProcessStartInfo();
psi.FileName = BatName;
psi.Arguments = "";
psi.WindowStyle = System.Diagnostics.ProcessWindowStyle.Hidden;
System.Diagnostics.Process p = new System.Diagnostics.Process();
p.StartInfo = psi;
p.Start();
return true;
}
return false;
}
关注点
这种解决方法远非优雅,而且相当粗糙。 但是,它被证明是有效的,因为它在更新客户端计算机时为我节省了很多时间,因为我经常进行更新。 现在,我所做的就是将安装文件放入文件夹中,然后忘记更新。
历史
- 2008 年 9 月 30 日 - 第一个(可能也是唯一一个)版本。