1.前言
不少朋友曾经问:我自己做了一个程序,打包安装后,发现必须先安装其他支持的库才能使用,或者我的应用程序有好几个安装包,客户不希望一个个点击安装,而是希望傻瓜式的点一个setup.exe就自动全部安装完。
其实这样的安装程序只要留心一下都很容易发现很多公司的安装程序都有这个功能,安装Visual Studio.NET的时候安装完了会提示是否要安装MSDN;安装瑞星杀毒软件的时候安装完了会提示是否安装瑞星防火墙继而提示是否安装卡卡助手;……。
2.编写setup.exe来完成安装引导
现在假设我们设计了一个系统,这个系统是用.net framework1.1编写的,用到了SQL SERVER2000数据库,也就是说这个系统安装之前必须安装.net framework1.1,如果是在Windows 2000一下的机器安装还必须保证数据访问组件Microsoft Data Access Components (MDAC)的版本在2.6以上,因为.net framework1.1使用的是MDAC2.6,而安装.net framework1.1的时候并不会自动安装这个组件。
综上所述,要使我们的系统可以正确的在Windows XP、Windows2000(Server)上正确运行,就必须首先安装MDAC2.6以上版本和.net framework1.1。我们正要编写这样一个setup.exe来来完成这个安装过程。
2.1实现方法
我们检查一下可以发现,MDAC版本信息和.net framework版本信息是可以在注册表里找到的。
MDAC的版本信息在HKEY_CLASSES_ROOT的MDACVer.Version/CurVer中;安装了.net framework的电脑就会有SOFTWARE/Microsoft/.NETFramework/policy/v1.1的项。
假设现在我们手上有MDAC2.7的安装包mdac2.7.exe、.net framework1.1的安装包dotnetfx.exe和自己的系统安装包MyAppSetup.msi,我们要实现setup.exe完成这三个程序的自动安装。
setup.exe运行后,即检查MDAC版本,如果发现它的版本低于2.6则提示安装MDAC2.7,并等待MDAC安装完成后,再检测是否已经安装.net framework1.1,如果已经安装则直接运行MyAppSetup.msi安装自己的系统,否则提示必须安装.net framework1.1,等.net framework1.1的安装进程结束后还必须检测是否成功安装,因为安装过程中用户可能取消安装,如果成功安装就把MyAppSetup.msi安装路径写入注册表的Runonce,等系统重启后才继续安装,这是因为.net framework1.1安装完后需要重启系统。
2.2编码
由于这个程序的安装有可能是在.net framework1.1安装之前安装的,所以我们不能使用.net framework来编写,如果还使用.net framework来编写,它本身又要.net framework下运行,这就造成“反锁”的效果了:房子锁了,我要进去需要钥匙,可是钥匙在房里。
这里我们使用VC++6.0来编写。
首先我们新建一个MFC应用程序,在向导中选择基于窗体的应用程序,我们不用控制台应用程序,是因为我不想在运行setup.exe的时候弹出一个dos控制台。
我们给这个项目起名叫Setup,在VC++6.0自动生成的代码中Source Files里有Setup.cpp、SetupDlg.cpp和StdAfx.cpp;在Header Files里有Resource.h、Setup.h、SetupDlg.h和StdAfx.h。
我们打开Setup.h,在CSetupApp类里增加两个成员函数:
void InstallDotNetApplication();
BOOL InstallMDAC();
继而打开Setup.cpp,由于我们不在需要运行这个工程的窗体,我们把BOOL CSetupApp::InitInstance()函数的下面代码注释掉:
/*CSetupDlg dlg;
m_pMainWnd = &dlg;
int nResponse = dlg.DoModal();
if (nResponse == IDOK)
{
// TODO: Place code here to handle when the dialog is
// dismissed with OK
}
else if (nResponse == IDCANCEL)
{
// TODO: Place code here to handle when the dialog is
// dismissed with Cancel
}*/
这时候我们可以删除SetupDlg.cpp和SetupDlg.h两个文件,并把应用它们的代码也删除。
我们实现安装MDAC的函数InstallMDAC的代码:
//安装MDAC
BOOL CSetupApp::InstallMDAC()
{
HKEY key;
//检测注册表中MDAC的版本
LONG res = RegOpenKeyEx(HKEY_CLASSES_ROOT,"MDACVer.Version//CurVer",
0,KEY_READ,&key);
//如果注册表有MDAC安装信息
if(res == ERROR_SUCCESS)
{
BYTE value[100];
DWORD dw = 100;
DWORD dwType = REG_SZ;
memset(value,0,100);
RegQueryValueEx(key,NULL,NULL,&dwType,value,&dw);
//如果MDAC版本大于等于6.0则不需安装返回TRUE
if(strcmp("MDACVer.Version.2.6",(const char*)value) <= 0)
{
return TRUE;
}
}
RegCloseKey(key);
//如果注册表没有MDAC信息或者版本小于6.0,则提示安装
if(MessageBox(NULL,"此操作系统的MDAC版本太低,需要安装MDAC2.7/r/n现在就安装吗?",
"系统提示",MB_YESNO) == IDNO)
{
//如果用户选择不安装则不能往下进行
return FALSE;
}
//构造进程信息
STARTUPINFO startinfo;
PROCESS_INFORMATION proinfo;
ZeroMemory( &startinfo, sizeof(startinfo) );
startinfo.cb = sizeof(startinfo);
ZeroMemory( &proinfo, sizeof(proinfo) );
//启动MDAC安装进程
BOOL b = CreateProcess(NULL,"mdac2.7.exe",NULL,NULL,FALSE,
0,NULL,NULL,&startinfo,&proinfo);
if(!b)
{
AfxMessageBox("启动mdac2.7 安装包失败,请确定文件是否存在");
return FALSE;
}
//等待安装完成
WaitForSingleObject(proinfo.hProcess,INFINITE);
return TRUE;
}
然后我们继续实现检测安装.net framework1.1和安装自己系统程序的函数InstallDotNetApplication()的代码:
//安装.NET应用程序
void CSetupApp::InstallDotNetApplication()
{
HKEY key;
//检测注册表是否有.net framework1.1
LONG res = RegOpenKeyEx(HKEY_LOCAL_MACHINE,"SOFTWARE//Microsoft//.NETFramework//policy//v1.1",
0,KEY_READ,&key);
RegCloseKey(key);
//如果没有安装.net framework1.1
if(res != ERROR_SUCCESS)
{
//构造启动.net framework1.1的进程信息
STARTUPINFO startinfo;
PROCESS_INFORMATION proinfo;
ZeroMemory( &startinfo, sizeof(startinfo) );
startinfo.cb = sizeof(startinfo);
ZeroMemory( &proinfo, sizeof(proinfo) );
BOOL b = CreateProcess(NULL,"dotnetfx.exe",NULL,NULL,FALSE,
0,NULL,NULL,&startinfo,&proinfo);
if(!b)
{
AfxMessageBox("启动.net Framework1.1 安装包失败,请确定文件是否存在");
return;
}
//等待.net framework1.1安装完成
WaitForSingleObject(proinfo.hProcess,INFINITE);
CloseHandle(proinfo.hProcess);
CloseHandle(proinfo.hThread);
//再次检测是否安装.net framework1.1防止用户安装.net framework1.1的过程中退出
if(RegOpenKeyEx(HKEY_LOCAL_MACHINE,"SOFTWARE//Microsoft//.NETFramework//policy//v1.1",
0,KEY_READ,&key) == ERROR_SUCCESS)
{
HKEY hk;
DWORD nBufferLength = 1000;
char lpBuffer[1000];
//自己的应用程序的路径
DWORD n = GetCurrentDirectory(nBufferLength,(LPTSTR )lpBuffer);
memcpy(lpBuffer + n,"//MyAppSetup.msi",strlen("//MyAppSetup.msi"));
//由于.net framework1.1安装后要重启,自己的应用程序并不是马上安装而是重启后才安装,
//这里写在注册表RunOnce项里,重启后自动执行
res = RegOpenKeyEx(HKEY_LOCAL_MACHINE,"SOFTWARE//Microsoft//Windows//CurrentVersion//RunOnce",
0,KEY_ALL_ACCESS,&hk);
if(res!=ERROR_SUCCESS)
{
AfxMessageBox("打开注册表失败");
return;
}
res=RegSetValueEx(hk,"MyAppSetup",0,REG_SZ, (unsigned char*)lpBuffer,strlen(lpBuffer));
if(res!=ERROR_SUCCESS)
{
AfxMessageBox("写入注册表失败!");
return;
}
RegCloseKey(hk);
Sleep(1000);
//提示重启,重启后可自动安装
if(AfxMessageBox("必须重启计算机才能正确安装系统,现在就重启吗?",MB_OKCANCEL|MB_ICONQUESTION) == IDOK)
{
HANDLE hToken;
TOKEN_PRIVILEGES tkp;
// Get a token for this process.
if (OpenProcessToken(GetCurrentProcess(),
TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hToken))
{
// Get the LUID for the shutdown privilege.
LookupPrivilegeValue(NULL, SE_SHUTDOWN_NAME,
&tkp.Privileges[0].Luid);
tkp.PrivilegeCount = 1; // one privilege to set
tkp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
// Get the shutdown privilege for this process.
AdjustTokenPrivileges(hToken, FALSE, &tkp, 0,
(PTOKEN_PRIVILEGES)NULL, 0);
}
//强制重启系统
if(!ExitWindowsEx(EWX_REBOOT|EWX_FORCE,0))
{
CString s;
s.Format(_T("Windows重启失败,请手动重启Windows后继续安装 %d"),GetLastError());
AfxMessageBox(s);
}
}
}
}
else//如果安装了.net framework1.1则不用安装并且安装自己的应用程序
{
HINSTANCE hin = ShellExecute(NULL,"open","MyAppSetup.msi","","",SW_SHOW);
if((int)hin <= 32)
{
AfxMessageBox("启动安装包MyAppSetup.msi失败,请确定文件是否存在");
return;
}
}
}
我们在CSetupApp::InitInstance()函数的return FALSE;之前添加调用:
//安装MDAC
if(!InstallMDAC())
{
return FALSE;
}
//安装.NET应用程序
InstallDotNetApplication();
最后把图标换成Setup的图标,编译成Release版本的可执行文件,把Setup.exe和mdac2.7.exe、.net framework1.1以及MyAppSetup.msi放在一个文件夹里打包成rar发给客户叫他点击Setup就可以了。