MSI 包管理器






4.60/5 (6投票s)
2004 年 8 月 12 日
9分钟阅读

102587

1006
本文介绍如何仅通过提供一个配置文件来处理一个或多个 MSI 包。它还提供了许多可用于其他项目的有用提示和技巧。
目录
引言
从文章标题来看,我们认为我将介绍伟大的 MSI 技术(Windows Installer)。事实上,这根本不是文章的主题。本文介绍了一种在 MSI 处理之前、之中和之后轻松管理任何 MSI 包的方法。它与 MSI 自定义操作以及产品安装过程中的不同步骤无关,正如我们在 Visual Studio 设置和部署、InstallShield Studio、Wise Installer 等各种市场产品中所见。
在安装之前和之后执行的操作方面,我们可能无法在 MSI 包中完成所有想要的操作。您在本文中介绍的程序中需要立即使用的唯一功能是配置文件,我将在下一节中进行说明。
同时,本文介绍了很多有用的、可直接使用的技巧,关于
- 如何在 UI 编程中创建线程并禁用 UI 以防止其关闭(例如,通过 Alt+F4),
- 如何静默启动带有/不带参数的 Win32 或控制台程序以及 DOS 命令,
- 如何销毁整个目录或删除一个或多个目录中具有特定模式的文件,
- 如何轻松设计一个非确定性进度条(见上图),当无法准确评估操作在时间或计数方面的长度时,
- 如何通过调用一个函数轻松地在任何控件的任何矩形上实现渐变色画笔(见上图)。
在深入细节之前,我必须感谢一些在 CodeProject 上免费贡献源代码的人,我从他们那里获得了许多好的提示和有用的想法来构建文章中的一些函数。
- Patrik Svensson 撰写的文章 A Gradient Static control,我从中获得了渐变填充的想法。
- Robert Edward Caldecott 撰写的文章 WTL Button menu Class,我从中获得了在设计非确定性进度条时使用 Marlett 字体的主意。
通过查看注册表来测试 Windows Installer 是否存在的想法不是我的,我也没有作者的名字。尽管此方法适用于所有 Windows 操作系统(Windows 95 除外),但至少对于 Windows 2000/XP/2003 而言,测试目标系统上是否安装了 Windows Installer 服务更为合适。文件msiexec.exe存在于System32目录中这一事实并不能完全证明 Windows Installer 正在运行。
如何使用
MsiMgr.exe Configuration_File
其中Configuration_File
是包含包版本和描述、日志文件路径以及 MsiMgr 应处理的操作的配置文件。
示例
MsiMgr C:\MyFile.ini
MsiMgr C:\My directory\MyFile.ini
即使配置文件路径包含空格,也请勿加引号,否则无效。我将在下一节中详细解释配置文件应如何组织,以及每行应遵守的规则。
程序组织方式
配置文件采用.INI文件格式。下图显示了不同的节以及每个节中包含行的通用规则。
配置文件的每一行都遵循以下模式,除了SETTINGS
节,在该节中,我们按如下所示指示包的版本、描述和日志文件路径。
FILE = [PARAMTERS], VERB, [CONDITION, CONDITION_DATA], [MESSGE_TO_DISPLAY]
方括号 [] 中的参数是可选的。当然,VERB
和 FILE
是必需的。如果 FILE
或 CONDITION_DATA
是包含空格的路径,请确保将字符串用两个引号""括起来。
这一行表示
仅当 CONDITION
应用于 CONDITION_DATA
为真,或者没有条件时,应用谓词 VERB
到 FILE
+PARAMTERS
,并在行处理期间显示 MESSGE_TO_DISPLAY
。
其中
表 1:典型行元素表
文件 |
是根据谓词值处理的文件 |
PARAMTERS |
在应用谓词期间传递给File 的参数 |
VERB |
是要处理的谓词 |
CONDITION |
是条件 |
CONDITION_DATA |
是应用条件的条件数据 |
MESSGE_TO_DISPLAY |
是谓词执行期间将在 UI 上显示的消息 |
下表显示了参数 VERB 和 CONDITION 可以具有的值。请注意,VERB
应用于 FILE
,而 CONDITION
应用于 CONDITION_DATA
。另请注意,谓词值和条件值都不区分大小写。
表 2:VERB
值表
DelFile |
删除文件 |
DelDir |
删除目录 |
DelKey |
删除注册表项 |
DelKeyValue |
删除注册表项值 |
ExecFile |
执行文件,如有参数则传递 PARAMTERS |
UnIns |
卸载 MSI 包 |
Ins |
安装 MSI 包 |
表 3:CONDITION
值表
IF_EXIST |
如果文件/目录存在 |
IF_NOT_EXIST |
如果文件/目录不存在 |
IF_KEY_EXIST |
如果项存在。在这种情况下,CONDITIONS_DATA 应以反斜杠结尾,以区别于项值。 |
IF_KEY_NOT_EXIST |
如果项不存在。在这种情况下,CONDITIONS_DATA 应以反斜杠结尾。 |
IF_KEY_VALUE_EXIST |
如果项值存在。在这种情况下,CONDITIONS_DATA 不应以反斜杠结尾。 |
IF_KEY_VALUE_NOT_EXIST |
如果项值不存在。 |
这是一个配置文件的示例,以使整个概念更清晰。您可以根据您的需求和想象力来填写配置文件。
[SETTINGS] ;Package version Version=1.0.0.1 ;Package description Description=this is install test ;Logs will be generated in C:\Mylog.log logfile=C:\Mylog.log ;Wait at most 5 minutes for processes execution ExecTimeout=300000 [PREINSTALL] ;This line will delete directory C:\Test ! c:\Test=,deldir,,,Deleting directory C:\test... ;This line will delete directory C:\Temp only if directory C:\Test1 exist! C:\Temp\=,delfile,IF_EXIST,C:\Test1, deleting all *.dll in C:\Temp directory... ;This line will delete directory C:\Temp1 only if file C:\Test2\MyFlagFile.001 exist! C:\Temp1=,delfile,IF_EXIST,C:\Test2\MyFlagFile.001, deleting all *.dll in C:\Temp directory... ;This line will unregister the service whose path C:\Windows\system32\MyService.exe if this one exist C:\Windows\system32\MyService.exe=/Unregister,ExecFile,IF_EXIST, C:\Windows\system32\MyService.exe,Unregistring the service Myservice... ;This line will delete the service binary C:\Windows\system32\MyService.exe if this one exist C:\Windows\system32\MyService.exe=/Unregister,DelFile,IF_EXIST, C:\Windows\system32\MyService.exe,deleting the service Myservice... ;This line will launch NotePad (works only on WinXP/2000/2003), for Win9X replace cmd by Command cmd /C %windir%\notepad.exe=,ExecFile,,,Launching NotePad... [UNINSTALL] ;This section and the next one apply only to MSI packages ;(that's why we don't need to specify msi engine/service msiexec.exe) ;This line will uninstall the product with code indicated on the left ;by hiding the cancel button and generating the msi log in C:\MSIlog.log {125F0499-6759-11D5-A54F-0090278A1BB8}=/QB+! /LC:\MSIlog.log, UnIns,,,Uninsall my product in progress... [INSTALL] ;Install c:\Packages\MyPackage\MyMsi.msi with the MSI paramter "ALLUSERS=1 /QB" without any condition c:\Packages\MyPackage\MyMsi.msi=ALLUSERS=1 /QB,INS,,, Installing MyMsi in prgress. Please wait... [POSTINSTALL] ;The same rules apply to this section as those of PREINSTALL section
注意:当您将 VERB
指定为 ExecFile 时,默认情况下,执行将等待进程终止,最多等待 SETTINGS
节中指定的 ExecTimeout
毫秒。此超时时间可以针对每个包进行调整,通过在最坏的情况下评估最长的操作,即使目标计算机具有较少或更多的 CPU 性能。
使用代码
该项目是一个 Win32 MFC 应用程序。主窗口只包含一个子对话框。UI 周围的其余部分只是装饰,这些装饰是通过一些 GDI 函数实现的,我稍后会解释。
通过对照上表(表 1、2 和 3)以及程序源代码,它们分别对应于
enum VerbFlag { DelFile, //delete file DelDir, //delete directory DelKey, //delete reg key DelKeyValue, //delete reg key value ExecFile, //Execute file UnIns, //UnInstall Msi package Ins, //Install Msi package }; enum ConditionFlag{ IF_EXIST, //If file exist IF_NOT_EXIST, //If file not exist IF_KEY_EXIST, //if key exist IF_KEY_NOT_EXIST, //if key not exist IF_KEY_VALUE_EXIST, //if key value exist IF_KEY_VALUE_NOT_EXIST, //if key value not exist }; struct InstallStruct { CString Data; //file/dir/registry on which the verb is applied CString Parameter; //Parameters execution for Data if anny int Verb; //the verb (see VerbFlag) int Flag; //the flag condition (see ConditionFlag) CString FlagFile; //Flag file/dir/key etc to be tested by the falg condition CString Message; //Message to be displayed };
// Program Main thread DWORD WINAPI MainThread(LPVOID lp) { DWORD ExitCode=0; BOOL ret[4]; for (int i= 0;i<4; i++) ret[i]=TRUE; // We go to next section only if the current // section has returned with success ret[0]=PreInstall(hdlg,ExitCode); if (ret[0]==TRUE) { ret[1]=UnInstallPackage(hdlg,ExitCode); if (ret[1]==TRUE) { ret[2]=InstallPackage(hdlg,ExitCode); if (ret[2]==TRUE) ret[3]=PostInstall(hdlg,ExitCode); } } CString Msg; if ( (ret[0]&ret[1]&ret[2]&ret[3])!=0) { Msg=_T("\nMSI package processing has finished with success."); UpdateMessage(hdlg,Msg); } else { Msg=_T("\nThe MSI package processing has failed!"); UpdateMessage(hdlg,Msg); // Don't call MessageBox with respect to the main window // (the attached main thread), it will not work! // Instead, call MessageBox with respect to the dialog MessageBox(hdlg, Msg, TITLE, MB_OK|MB_ICONEXCLAMATION|MB_SYSTEMMODAL); } WriteToLogFile(LOG_FILE,Msg.GetBuffer(0));Msg.ReleaseBuffer(); Sleep(2000); // Destroy our unique dialog SendMessage(hdlg,WM_DESTROY,0,0); // Tell to the main thread associated with // our frame window that we are done DONE=1; return ExitCode; }
// Purpose: Make a filled rectangle by a gradient color // hWnd: Handle to the control on which the gradient will be made // FirstColor: First gradient color // SecondColor: Second gradient color // TextOut: Text will be drawn // TextColor: Text color // TextFormat: Text format // FontName: font name used in the text output // FontSize: font size used in the text output // Rect: Rectangle on which the gradient rectangle will take place // Direction: Gradient direction (Vertical:GRADIENT_FILL_RECT_V, // Horizontal:GRADIENT_FILL_RECT_H) // CleanUp: Tell that we can free the library msimg32.dll // (this should be TRUE only in the last call to this function) // For exmaples on how to call this function, see below void MakeGradient(HWND hWnd, COLORREF FirstColor, COLORREF SecondColor, CString &TextOut, COLORREF TextColor, UINT TextFormat, CString &FontName, int FontSize, CRect &Rect, DWORD Direction, BOOL CleanUp) { static HINSTANCE h_msimg32; if (h_msimg32==NULL) h_msimg32= LoadLibrary( "msimg32.dll" ); CDC dc; HDC hDC=GetWindowDC(hWnd); dc.Attach(hDC); CFont Font; int nHeight = -1-MulDiv(FontSize, GetDeviceCaps(hDC, LOGPIXELSY), 72); int nWidth = -1-MulDiv(FontSize, GetDeviceCaps(hDC, LOGPIXELSX), 72); Font.CreateFont(nHeight, nWidth, 0, 0, FW_DONTCARE, FALSE, FALSE, 0, DEFAULT_CHARSET, OUT_CHARACTER_PRECIS, CLIP_CHARACTER_PRECIS, ANTIALIASED_QUALITY, DEFAULT_PITCH | FF_DONTCARE, FontName ); // Select the newly created font into // the device context CFont *pOldFont = dc.SelectObject( &Font ); if (h_msimg32) { typedef UINT (CALLBACK* LPFNDLLFUNC1) (HDC,CONST PTRIVERTEX,DWORD, CONST PVOID,DWORD,DWORD); LPFNDLLFUNC1 dllfunc_GradientFill = ((LPFNDLLFUNC1) GetProcAddress( h_msimg32, "GradientFill" )); if (Rect.IsRectNull()) { GetClientRect(hWnd, &Rect); Rect.top-=1; Rect.bottom+=2; Rect.right+=2; Rect.left-=1; } TRIVERTEX rcVertex[2]; // Define vertex array rcVertex[0].x=Rect.left; rcVertex[0].y=Rect.top; // color values from 0x0000 to 0xff00(255) rcVertex[0].Red=GetRValue(FirstColor)<<8; rcVertex[0].Green=GetGValue(FirstColor)<<8; rcVertex[0].Blue=GetBValue(FirstColor)<<8; rcVertex[0].Alpha=0x0000; rcVertex[1].x=Rect.right; rcVertex[1].y=Rect.bottom; rcVertex[1].Red=GetRValue(SecondColor)<<8; rcVertex[1].Green=GetGValue(SecondColor)<<8; rcVertex[1].Blue=GetBValue(SecondColor)<<8; rcVertex[1].Alpha=0; GRADIENT_RECT rect; rect.UpperLeft=0; rect.LowerRight=1; // fill the area dllfunc_GradientFill(dc, rcVertex, 2, &rect, 1, Direction); if ( (h_msimg32!=NULL) && (CleanUp==TRUE) ) { h_msimg32=NULL; FreeLibrary(h_msimg32); } } // Draw text SetTextColor(dc, TextColor); SetBkMode(dc, TRANSPARENT); DrawText(dc, TextOut, -1, &Rect, TextFormat); // Draw the contour Rect.DeflateRect(-1,-1, -1, -1 ); dc.DrawEdge(&Rect,EDGE_BUMP,BF_RECT); // Select the old font back into the device context. dc.SelectObject( pOldFont ); dc.Detach(); }
为了制作进度条,在 InitInstance
函数中设置了一个计时器。然后,在主框架窗口过程中,通过使用字符串表和 Marlett 字体作为响应 WM_TIMER
消息的函数来调用上述函数,如下所示:
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) { .............. static int i; ................. static CString str[22]={ {"gggg11111111111111111111111111111111111111111111"}, {"111gggg11111111111111111111111111111111111111111"}, {"11111gggg111111111111111111111111111111111111111"}, {"1111111gggg1111111111111111111111111111111111111"}, {"111111111gggg11111111111111111111111111111111111"}, {"11111111111gggg111111111111111111111111111111111"}, {"1111111111111gggg1111111111111111111111111111111"}, {"111111111111111gggg11111111111111111111111111111"}, {"11111111111111111gggg111111111111111111111111111"}, {"1111111111111111111gggg1111111111111111111111111"}, {"111111111111111111111gggg11111111111111111111111"}, {"11111111111111111111111gggg111111111111111111111"}, {"1111111111111111111111111gggg1111111111111111111"}, {"111111111111111111111111111gggg11111111111111111"}, {"11111111111111111111111111111gggg111111111111111"}, {"1111111111111111111111111111111gggg1111111111111"}, {"111111111111111111111111111111111gggg11111111111"}, {"11111111111111111111111111111111111gggg111111111"}, {"1111111111111111111111111111111111111gggg1111111"}, {"111111111111111111111111111111111111111gggg11111"}, {"11111111111111111111111111111111111111111gggg111"}, {"1111111111111111111111111111111111111111111gggg1"} }; CRect rect; switch (message) { .................. case WM_TIMER: // If DONE then Kill our timer and exit by destroying our window if (DONE==1) { KillTimer(hWnd, 1); DestroyWindow(hWnd); return 0; } // Progress animation is done here GetClientRect(GetDlgItem(hdlg,IDC_STATIC_PROGRESS),&rect); MakeGradient(GetDlgItem(hdlg,IDC_STATIC_PROGRESS), RGB(128, 128, 128), RGB(255, 255, 255), str[i], RGB(119, 60, 0), DT_CENTER|DT_VCENTER, CString("Marlett"), 10, rect, GRADIENT_FILL_RECT_H,FALSE); i+=1; i=i%18; // Create our unique thread as long as static // variable thread handle h is null // It sould be done only once if (h==NULL) h=CreateThread(NULL,0,MainThread, &hWnd, NULL,&ID); break; ............. default: return DefWindowProc(hWnd, message, wParam, lParam); } return 0; }
程序如何处理每个节中的每一行?
行的处理顺序与它们在配置文件中的顺序相同。一次,每一行都由一个全局变量表示。
InstallStruct Install; //Global Install variable
并按以下步骤处理:
- 获取将存储在
Install.Data
中的FILE
。 - 获取
=
右侧的值(称为strArr
)作为CStringArray
类型。 - 通过调用函数将
strArr
拆分为Parameter
、Verb
、Flag
(CONDITION
)、FlagFile
(CONDITION_DATA
)和Message
。// Split string str and return the slices in the CString array SplitArray // This function works even if there is nothing beteween separators such // as if str="toto,tata,,,mama,," void SplitString(CString &str, char Sep, CStringArray &SplitArray)
- 获取
VERB
、CONDITION
、CONDITION_DATA
和MESSGE_TO_DISPLAY
的值。由于VERB
和CONDITION
可以具有不同的值,因此使用两个函数来获取它们的值。int GetVerb(CStringArray strArr) int GetCondition(CStringArray strArr)
- 根据表中指示调用对应于当前节的函数:
SECTION | 对应的函数 |
PREINSTALL | BOOL PreInstall(HWND hWnd, DWORD &ExitCode) |
UNINSTALL | BOOL UnInstallPackage(HWND hWnd, DWORD &ExitCode) |
INSTALL | BOOL InstallPackage(HWND hWnd, DWORD &ExitCode) |
POSTINSTALL | BOOL PostInstall(HWND hWnd, DWORD &ExitCode) |
我将不解释这些函数如何工作,但正如我在引言中提到的,我将在下一节中提供一些间接使用的中间函数。这些函数可以在任何程序中用于实现常见任务。
操作指南
- 如何创建 UI 编程中的线程并禁用 UI 的正常关闭(例如,通过 Alt+F4)
ATOM MyRegisterClass(HINSTANCE hInstance) { WNDCLASSEX wcex; wcex.cbSize = sizeof(WNDCLASSEX); wcex.style = CS_NOCLOSE; //This prevent the use of Alt+F4 ........... }
并查看上面MainThread中的线程创建。
- 如何静默启动 Windows 或控制台程序和 DOS 命令,并带有/不带参数
////////////////////////////////////////////////////////////// // Purpose: // -Create a process with module name: ModuleName, // command line: CommandLine // -and wait for the end of the process or not: WaitOrNot // -You can fix a TimeOut (milliseconds), // otherwise pass -1 (INFINITE) // -You can specify if the UI associeted // to the process will be visible // by passing Show parameter // (TRUE:Visible, FALSE:Not visible) // -[OUT] ExitCode is the process exit code (very usefull) // Returns: creation status (TRUE or FALSE) ////////////////////////////////////////////////////////////// BOOL CreateProc(char *ModuleName, char *CommandLine, BOOL WaitOrNot, DWORD TimeOut, BOOL Show, DWORD &ExitCode) { STARTUPINFO startup_info; PROCESS_INFORMATION process_info; BOOL status; DWORD dwexitcode = 0; if ( strcmp(CommandLine, "")==0 && strcmp(ModuleName, "")==0 ) return TRUE; ZeroMemory(&startup_info, sizeof(STARTUPINFO)); startup_info.cb = sizeof ( startup_info ); startup_info.dwFlags = STARTF_USESHOWWINDOW; startup_info.wShowWindow = SW_SHOWNORMAL?(Show==TRUE):SW_HIDE; status = CreateProcess((LPTSTR) (strcmp(ModuleName,"")==0)?NULL:ModuleName, (LPTSTR)CommandLine, NULL, NULL, FALSE, 0 , NULL, NULL, &startup_info, &process_info ); if ( status ) { if ( WaitOrNot ) if (TimeOut>0) WaitForSingleObject ( process_info.hProcess, TimeOut ); else WaitForSingleObject ( process_info.hProcess, INFINITE ); } if (GetExitCodeProcess(process_info.hProcess, &dwexitcode) != 0) { ExitCode = dwexitcode; CloseHandle ( process_info.hThread ); CloseHandle ( process_info.hProcess ); } if (status == 0 ) return FALSE; else return TRUE; }
示例:如果要静默执行 DOS 命令或控制台程序,请执行以下操作:
//Get Command prompt (...\cmd.exe for WINNT/XP/2002/2003 //and Command.com for Win9X/ME) char *Cmd=getenv("COMSPEC"); char cmdline[255]; sprintf(cmdline,"%s /C Dir *.*/s>C:\\Out.txt",Cmd); DWORD ExitCode=0; //This line will produce the output file C:\Out.txt //with all files in the current directory and all sub-directorie BOOL ret=CreateProc("", cmdline, TRUE /*Wait*/, 10000 /*10 seconds as wait timeout*/, FALSE /*Hide console window*/, ExitCode); //...Proceed the program sequence //with respect to ret and ExitCode //Open now with NotePad our file C:\Out.txt char Windir[MAX_PATH]; if (GetWindowsDirectory(Windir,MAX_PATH)>0){ char modName[MAX_PATH]; sprintf(modName,"%s\\Notepad.exe",Windir); sprintf(cmdline,"%s C:\\Out.txt",modName); ret=CreateProc("", cmdline, FALSE /*don't wait*/, 0 /*put any int number*/, TRUE /*Show NotePad*/, ExitCode); }
- 如何销毁整个目录或仅删除一个或多个目录中具有特定模式的文件
警告:请注意!不加注意地使用以下函数可能对您的数据造成危险。如果要测试它们,请指定一个包含非相关数据的目录。
通过简单地调用函数即可删除目录,并可选择删除子目录和根目录。
///////////////////////////////////////////////////////////// // Purpose: delete all directory files and optionally // subdirectories by recursive method // Parameters: // TheDir : the directory to empty // DelSubDirs : tell to delete sub-directories or not // DelRootDir : tell to delete root (TheDir) directory or not // Return: TRUE if success ///////////////////////////////////////////////////////////// BOOL ClearDirEx( CString &TheDir, BOOL DelSubDirs, BOOL DelRootDir) { CString Pattern="*.*"; BOOL ret= ClearDir(TheDir.GetBuffer(MAX_PATH), DelSubDirs, DelRootDir, Pattern.GetBuffer(3)); TheDir.ReleaseBuffer(); Pattern.ReleaseBuffer(); return ret; }
通过调用函数可以删除具有特定扩展名的文件,并可选择删除目录及其所有子目录中的文件。
//////////////////////////////////////////////////////////// // Purpose: delete all files with extension Pattern // and optionally include allsubdirectories // Parameters: // TheDir : the basic directory where where files are to be deleted // Pattern : delete only files with this extension pattern // IncludeSubDirs : tell to delete all sub-directory files too // Return: TRUE if success //////////////////////////////////////////////////////////// BOOL DeleteFilesEx( CString &TheDir, CString &Pattern, BOOL IncludeSubDirs) { BOOL ret= ClearDir(TheDir.GetBuffer(MAX_PATH), IncludeSubDirs, FALSE, Pattern.GetBuffer(3)); TheDir.ReleaseBuffer(); Pattern.ReleaseBuffer(); return ret; }
上面两个函数以不同方式调用的公共函数是:
BOOL ClearDir(char *TheDir, BOOL DelSubDirs, BOOL DelRootDir, char *Pattern) { BOOL bFound=TRUE; WIN32_FIND_DATA FileData; HANDLE hSearch; static char *CurDir=TheDir; char TempDir[MAX_PATH]; if (TheDir==NULL) return TRUE; // Of course, if we want to delete root directory then // all sub-directories should be deleted too if (DelRootDir==TRUE) DelSubDirs=TRUE; sprintf(TempDir, TheDir); strcat(TempDir,"\\"); strcat(TempDir, "*.*"); static char *revPat=_strrev(Pattern); hSearch = FindFirstFile((LPCTSTR)TempDir, &FileData); while ( bFound ) { if (hSearch != INVALID_HANDLE_VALUE) { GetFileAttributes( (LPCTSTR)FileData.cFileName ); if ( FileData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY ) { //Directory data if ( strcmp((char*)FileData.cFileName, "." )!=0 && strcmp((char*)FileData.cFileName,"..")!=0 ) { //There is some more files sprintf(TempDir, TheDir); strcat(TempDir, "\\"); strcat(TempDir,(char*)FileData.cFileName); if ( ClearDir(TempDir, DelSubDirs, DelRootDir, revPat)==FALSE) return FALSE; } } else { //file data if ( SetCurrentDirectory(TheDir)!=0 ) { if (SetFileAttributes((LPCTSTR)FileData.cFileName, FILE_ATTRIBUTE_ARCHIVE)!=0 && SetFileAttributes((LPCTSTR)FileData.cFileName, FILE_ATTRIBUTE_NORMAL)!=0 ) { if (strcmp(Pattern,"*.*")==0) { if ( DeleteFile((LPCTSTR)FileData.cFileName)==0) // cannot delete file, return FALSE return FALSE; } else { char *revFile=_strrev(FileData.cFileName); if (_strnicmp(revFile,revPat,strlen(Pattern))==0){ revFile=_strrev(FileData.cFileName); if ( DeleteFile((LPCTSTR)revFile)==0) return FALSE; } } } } } bFound=FindNextFile(hSearch, &FileData); } else return TRUE; } FindClose(hSearch); SetCurrentDirectory("\\"); if ( (DelRootDir==TRUE) && (stricmp(TheDir,CurDir)==0) ) { // We cannot remove local or network drive !!! if ( strlen(TheDir)>2 && IsDriveLetter(TheDir)==FALSE ) { if ( RemoveDirectory(TheDir)==0) return FALSE; } else return TRUE; } else if ( (stricmp(Pattern,"*.*")==0) && (DelSubDirs==TRUE) && (stricmp(TheDir,CurDir)!=0) ) { if ( RemoveDirectory(TheDir)==0) return FALSE; } return TRUE; }
- 如何轻松设计一个非确定性进度条(见上图),当无法准确评估操作在时间或计数方面的长度时
这个问题的答案包括以下步骤:
- 在您的对话框上放置一个静态控件,例如。(实际上,它可以是其他类型的控件,如编辑框或按钮,但静态控件更合适。)这将是我们的进度条。
- 在长任务(长复制、搜索等)开始时,设置一个计时器,指定一个计时器过程(如果您还没有与 UI 相关的全局窗口过程)。
- 在计时器过程或全局过程(在
WM_TIMER
消息处理中)中,调用负责进度条更新的函数,例如调用函数:MakeGradient(...)。在本例中,调用发生在WM_TIMER 消息处理中。用于显示进度条的技巧是该控件的 Marlett 字体。我使用了数字 1(1)表示空方块,字母 g(g)表示填充方块,这些方块以循环时间移动。实际上,您可以在目标机器上存在的其他字体中显示任何您想要的内容;基本思想是相同的。我们的字符串表(上面代码中的str)的通用长度可以根据控件宽度动态固定,以几乎容纳整个字符串。 - 一旦操作完成,就停止计时器。
- 如何通过调用一个函数轻松地在任何控件的任何矩形上实现渐变色画笔(Alpha 混合)(见上图)
如前所述,主框架窗口周围的所有装饰和进度条绘制都是通过调用一个函数实现的,即 MakeGradient(...),该函数带有许多参数来个性化控件的外观。该函数可以应用于任何窗口(静态、按钮、编辑等),只需提供一个有效的窗口句柄来获取设备上下文句柄、一个可以进行渐变的矩形,以及其他信息,如颜色、文本、字体名称、字体大小等。您可以根据自己的想象力在项目中使用此函数。请注意,此函数仅在 Windows 98/2000 及更高版本上运行,因为 Microsoft 库msimg32.dll(Windows GDI 的扩展组件)在 Windows 95 和 NT 4.0 上不可用。我至少确信它在 Win 98/2000/XP 上可以正常工作。有关可以从该库调用的函数的详细信息,您可以查看头文件WINGDI.H,它们在那里进行了原型声明。
结论
本文介绍了如何通过提供配置文件来驱动一个或多个 MSI 包的安装/卸载,以及可以执行的所有操作(安装前、安装中和安装后)。随时提出任何建议、批评或改进。
历史
初始版本 1.0.0.1 - 2004 年 8 月 8 日。