文章题目看起来有点绕,解释一下,假如你基于框架写了一个程序,想装到客户机上,但是客户机上可能并没有安装框架,因此你的程序需要预先将框架安装在目标机上,然后再执行一些安装程序的标准功能,如创建快捷方式、创建程序组、写入卸载信息以便让Windows能够对程序进行卸载管理等,实现这个功能的方法有很多,例如使用InstallShield、Wix Toolset等均可实现此功能。
不过本文并不是介绍使用这些工具的方法,而是要使用框架来编写一个安装程序,实现一般安装程序的复制文件、创建快捷方式、创建程序组、安装字体、安装服务、写入反安装信息等一些常见的功能,有重复发明轮子之嫌,主要目的是个人兴趣,想探究一下安装程序是怎么实现这些有趣的功能的,觉得无聊的朋友可飘过,勿喷。
说明:
框架安装程序:指安装.Net Framework 4.0到客户机上的安装程序,框架安装程序使用微软提供的dotNetFx40_Full_x86_x64.exe,为32位平台和64位平台通用的安装包。
应用安装程序:指基于.Net Framework 4.0编写的安装程序,将应用程序安装到客户机上。
编程环境: Visual Studio 2010 + .Net Framework 4.0。
要想用安装程序将编写好的程序安装到客户机上,首先得解决安装程序运行的问题,安装程序是基于框架编写的,得装上框架才行,恩,要有鸡先得有蛋。好,来理一理思路:
(1)使用一个引导程序安装.Net Framework 4.0,这个引导程序应该是已经编译为二进制代码的可执行文件,要求在 Windows XP 以上的操作系统中直接运行,不需依赖第三方DLL。这个引导程序需要检查目标机上是否安装了框架,如果已经安装了框架,则直接启动应用安装程序进行安装,如果检测到没有安装,则启动微软提供的.Net Framework 4.0框架安装程序进行框架的安装。
(2)框架安装成功后,启动应用安装程序,显示安装协议、提供程序功能选择、选择安装路径、执行安装(创建桌面快捷方式、创建程序组、复制文件到指定目录、安装字体、安装服务、写入反安装信息以便在控制面板中的“添加/删除程序”中能看到安装的程序、生成本地可执行映像以加快程序启动速度等等功能),安装完成后,提供“启动程序”的选项,以便用户能够在安装完成后立即启动程序。
(3)需要编写一个反安装程序,该程序根据已经安装的程序功能和配置文件删除已经安装的程序功能。
好了,应该要写三个程序,一步一步来。
1 引导程序的制作
引导程序的作用是检测客户机上是否已经安装了.Net Framework 4.0 框架,可以通过检查注册表相关键值的方式来实现,微软知识库提到可以检测如下的注册表项来查看是否安装了.Net Framework 4.0:
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\NET Framework Setup\NDP\v4\Full
该键下有名称为“Install”的项目,如果该项目值为1(0x1)则表示该计算机已经安装了框架,若不存在该注册表项目或该键值不为1,则可认为当前计算机尚未安装框架。
为了能够让引导程序能够独立运行,又能比较容易的编写,且可执行读写注册表等操作,我使用MFC框架来编写这个引导程序,并选择“在静态库中使用 MFC”这个选项,这样就能够将引导程序所使用的相关库文件打包到最终的EXE中,虽然看起来有点大(编译后1.78MB),但是放到客户机上可直接运行,并不需要客户机再安装MFC框架来运行这个引导程序。
(1)新建一个MFC应用程序,命名为Setup。
(2)在应用程序向导的应用程序类型设置中,选择“基于对话框”和“在静态库中使用 MFC”,其他选择可以使用默认设置。
创建完毕后,编写一个函数来检测是否安装了框架,这里借用了微软知识库的方法:
// 检测是否已经安装 .Net Framework 4.0。
#define NETFX40_FULL_REVISION 0
#define NETFX45_RC_REVISON MAKELONG(50309, 5)
bool IsNetFx4Present(DWORD dwMinimumRelease)
{
DWORD dwError = ERROR_SUCCESS;
HKEY hKey = NULL;
DWORD dwData = 0;
DWORD dwType = 0;
DWORD dwSize = sizeof(dwData);
dwError = ::RegOpenKeyExW(HKEY_LOCAL_MACHINE, L"SOFTWARE\\Microsoft\\NET Framework Setup\\NDP\\v4\\Full", 0, KEY_READ, &hKey);
if (ERROR_SUCCESS == dwError)
{
dwError = ::RegQueryValueExW(hKey, L"Release", 0, &dwType, (LPBYTE)&dwData, &dwSize);
if ((ERROR_SUCCESS == dwError) && (REG_DWORD != dwType))
{
dwError = ERROR_INVALID_DATA;
}
else if (ERROR_FILE_NOT_FOUND == dwError)
{
dwError = ::RegQueryValueExW(hKey, L"Install", 0, &dwType, (LPBYTE)&dwData, &dwSize);
if ((ERROR_SUCCESS == dwError) && (REG_DWORD == dwType) && (dwData == 1))
{
// 如果安装的是 .Net 4.0,则认为其版本号为 0。
dwData = 0;
}
else
{
dwError = ERROR_INVALID_DATA;
}
}
}
if (hKey != NULL)
{
::RegCloseKey(hKey);
}
return ((ERROR_SUCCESS == dwError) && (dwData >= dwMinimumRelease));
}
如果检测到没有安装框架,则直接启动 .Net Framework 4.0 安装程序。这里还有一个问题需要处理,.Net Framework 4.0 安装程序在执行安装时需要 Windows Imaging Component 的支持,如果客户机上未安装此组件,则框架安装程序将无法继续,经过试验,在新装的 MSDN Windows Server 2003 SP1 上该组件是未安装的,而对于 Windows XP SP3,Windows Vista,Windows 7 等以上的操作系统,都是已经包含了该组件的,所以为了保证框架安装程序能够正常安装,必需保证操作系统已经包含了该组件,因此需要检测操作系统上是否已经安装了WIC(Windows Imaging Component)。经过一番网上查阅,发现有两种方法来间接判断是否已经安装了WIC,一种是通过检测注册表相关键值的方式,即检测下列注册表键值是否存在:
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\WIC
另外一种方法是可以通过检测系统目录是否包含了WindowsCodecs.dll 这个文件来进行判定,只要系统安装了WIC,系统目录下就会包含这个文件,我使用检测文件的这种方法。
// 检查计算机是否安装了 Windows Imaging Component,通过检测系统目录是否存在对应的文件:WindowsCodecs.dll 来进行判定。
bool IsWicInstalled()
{
// 获取系统目录。
int MAX_PATH_LENGTH = 64;
wchar_t systemDirectory[MAX_PATH_LENGTH];
int nLength = ::GetSystemDirectoryW(systemDirectory, MAX_PATH_LENGTH);
// 函数调用失败或者返回的系统路径超过缓冲区长度。
if ((nLength == 0) || (nLength > (MAX_PATH_LENGTH + 1)))
{
return false;
}
// 生成文件名。
CString wicFileName = CString(systemDirectory);
if (wicFileName.GetAt(wicFileName.GetLength() - 1) != '\\')
{
wicFileName += L"\\";
}
wicFileName += L"WindowsCodecs.dll";
// 查找文件是否存在。
WIN32_FIND_DATA fData;
HANDLE hFile;
hFile = FindFirstFile(wicFileName, &fData);
bool isFileExist = (hFile != INVALID_HANDLE_VALUE);
FindClose(hFile);
return isFileExist;
}
如果检测到WIC尚未安装,则首先运行WIC的补丁安装程序,这个补丁安装程序可以使用命令行参数来定义安装行为,安装程序的可用选项如下图所示:
我使用的是 /passive /norestart 这两个参数,通过如下方式来启动安装程序并获取其退出码,当退出码为0时,表示安装程序正常退出,可用继续框架安装程序的安装了。
// 从命令行启动安装并返回退出码。
DWORD CSetupApp::InstallPrerequisiteAndReturnExitCode(CString cmdline)
{
STARTUPINFO si = {0};
si.cb = sizeof(si);
PROCESS_INFORMATION pi = {0};
BOOL bLaunchedSetup = ::CreateProcess(NULL,
cmdline.GetBuffer(),
NULL, NULL, FALSE, 0, NULL, NULL,
&si,
&pi);
DWORD dwExitCode;
if (bLaunchedSetup)
{
// 持续等待相关进程退出。
::WaitForSingleObject(pi.hProcess, INFINITE);
::GetExitCodeProcess(pi.hProcess, &dwExitCode);
::CloseHandle(pi.hProcess);
}
return dwExitCode;
}
2 .Net Framework 4.0 安装程序安装进度的获取
在进行框架安装程序的安装时,有两种方式可以选择&#