作為和NSIS並立的、兩個最流行的免費Windows應用程序安裝包制作工具之一,Inno在學習難度上相對要低一些,非常適合對一些簡單的桌面程序打包。但對於較復雜的安裝過程,或者Web應用程序來說,我個人覺得不是Inno的強項。當然,既然Inno內嵌了Pascal語言用以擴展功能,理論上不是不可以應付復雜的安裝過程,但實現起來要復雜一些。
比如對於在安裝過程中連接數據庫並執行SQL腳本這樣的需求,使用InstallShield應該會簡單地多,而Inno卻不支持直接操作數據庫,並且相關的資料說明少之又少,還不如NSIS豐富,以至於我踏破鐵鞋無覓處,最終卻在NSIS的資料中找到了思路。
主要的思路是,在安裝過程中,調用數據庫客戶端連接數據庫並執行SQL腳本,然后將執行結果或錯誤信息輸出到文件中,最后通過分析這個文件來判斷命令執行的結果。但是,既然是調用特定的客戶端,那么對不同數據庫的操作自然就有所區別,具體情況如下所述。
首先在打包腳本的[Files]段將必需的文件包含進來:
[Files]
Source: "D:/Development/MyDemoApp/code/*"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs
;osql.exe在SQL Server2000安裝目錄中
Source: "D:/Development/MyDemoApp/osql.exe"; Flags: dontcopy
Source: "D:/Development/MyDemoApp/mysql.exe"; Flags: dontcopy
Source: "D:/Development/MyDemoApp/script_mssql.sql"; Flags: dontcopy
Source: "D:/Development/MyDemoApp/script_mysql.sql"; Flags: dontcopy
Source: "D:/Development/MyDemoApp/script_oracle.sql"; Flags: dontcopy
在SQL Server中執行腳本的代碼片斷:
function ExecScriptInMSSQL(DBHost, DBLogin, DBPass, DBName: String): Boolean;
var
ConnectExe: String;
ConnectParam: String;
begin
{解壓臨時文件}
ExtractTemporaryFile('osql.exe');
ExtractTemporaryFile('script_mssql.sql');
{構造數據庫連接字符串}
ConnectExe := ExpandConstant('{tmp}') + '/osql.exe';
ConnectParam := ' -S ' + DBHost
+ ' -U ' + DBLogin
+ ' -P ' + DBPass
+ ' -d ' + DBName
+ ' -i script_mssql.sql -o '
+ ExpandConstant('{tmp}') + '/dbstatus.txt';
{建立數據庫連接並執行腳本}
if Exec(ConnectExe, ConnectParam, '', SW_HIDE, ewWaitUntilTerminated, ResultCode) then begin
Result := ResultCode = 0;
LoadStringFromFile(ExpandConstant('{tmp}') + '/dbstatus.txt', StatusString);
if StatusString <> '' then begin
MsgBox(StatusString, mbError, MB_OK);
Result := False;
end else begin
Result := True;
end;
end else begin
MsgBox('Database update failed:'#10#10 + SysErrorMessage(ResultCode), mbError, MB_OK);
Result := False;
end;
end;
在MySQL中執行腳本的代碼片斷:
function ExecScriptInMYSQL(DBHost, DBLogin, DBPass, DBName: String): Boolean;
var
ConnectExe: String;
ConnectParam: String;
begin
{解壓臨時文件}
ExtractTemporaryFile('mysql.exe');
ExtractTemporaryFile('script_mysql.sql');
{構造數據庫連接字符串}
ConnectExe := ExpandConstant('cmd');
ConnectParam := ' /c "' + ExpandConstant('{tmp}') + '/mysql.exe'
+ ' -h' + DBHost
+ ' -u' + DBLogin
+ ' -p' + DBPass
+ ' -D' + DBName
+ ' -e "source ' + ExpandConstant('{tmp}') + '/script_mysql.sql""> ' + ExpandConstant('{tmp}') + '/dbstatus.txt 2>&1';
{建立數據庫連接並執行腳本}
if Exec(ConnectExe, ConnectParam, '', SW_HIDE, ewWaitUntilTerminated, ResultCode) then begin
Result := ResultCode = 0;
LoadStringFromFile(ExpandConstant('{tmp}') + '/dbstatus.txt', StatusString);
if StatusString <> '' then begin
MsgBox(StatusString, mbError, MB_OK);
Result := False;
end else begin
Result := True;
end;
end else begin
MsgBox('Database update failed:'#10#10 + SysErrorMessage(ResultCode), mbError, MB_OK);
Result := False;
end;
end;
由於mysql.exe沒有輸出結果到文件的參數,故需要使用cmd.exe來運行mysql.exe以便將其輸出重定向到文件dbstatus.txt中。此外,在命令的最后加上參數2>&1,將標准錯誤輸出設備也重定向到文件上,否則命令執行的錯誤信息不會輸出到文件中。
在Oracle中執行腳本的代碼片斷:
function ExecScriptInORACLE(ClientPath, DBInstance, DBLogin, DBPass: String): Boolean;
begin
{解壓臨時文件}
ExtractTemporaryFile('script_oracle.sql');
{連接數據庫並執行腳本}
if Exec(ExpandConstant('cmd'), ' /c "' + ClientPath + ' -L -S ' + DBLogin
+ '/' + DBPass
+ '@' + DBInstance
+ ' @' + ExpandConstant('{tmp}') + '/script_oracle.sql> ' + ExpandConstant('{tmp}') + '/dbstatus.txt 2>&1',
'',
SW_HIDE, ewWaitUntilTerminated, ResultCode)
then begin
Result := ResultCode = 0;
LoadStringFromFile(ExpandConstant('{tmp}') + '/dbstatus.txt', StatusString);
if Pos('holytail', StatusString) <> 0 then begin
{若輸出信息中有“holytail”的子串,則表示腳本成功執行}
{若執行有誤,提示用戶打開日志文件}
if Pos('ORA-', StatusString) <> 0 then begin
{提示用戶腳本執行出錯}
if MsgBox('數據庫更新出錯,是否打開日志文件?', mbConfirmation, MB_YESNO) = IDYES then begin
{打開日志}
if not ShellExec('', ExpandConstant('{tmp}') + '/dbstatus.txt', '', '', SW_SHOW, ewNoWait, ErrorCode) then begin
MsgBox('日志文件打開錯誤!', mbError, MB_OK);
end;
end;
Result := False;
{若執行無誤,返回True}
end else begin
Result := True;
end;
end else if StatusString <> '' then begin
MsgBox(StatusString, mbError, MB_OK);
Result := False;
end else begin
Result := True;
end;
end else begin
MsgBox('Database update failed:'#10#10 + SysErrorMessage(ResultCode), mbError, MB_OK);
Result := False;
end;
end;
Oracle的客戶端太大,不能集成到安裝包中,應使用一個TInputFileWizardPage由用戶選擇sqlplus.exe的安裝位置。同時,由於sqlplus.exe也沒有輸出結果到文件的參數,也須使用cmd.exe來運行它並重定向輸出到文件。此外,由於sqlplus.exe執行腳本時無論成功還是失敗,都會輸出信息,故無法像使用sqlcmd.exe和mysql.exe那樣簡單地判斷腳本是否執行成功,需要在腳本的最后通過select語句輸出一個特殊的字符串到文件中,然后通過判斷dbstatus.txt中是否存在該字符串來判斷腳本的執行情況;且由於sqlplus.exe執行完腳本后不會自動退出,還要在腳本最后加上exit語句;故script_oracle.sql的最后必須是如下內容:
SELECT 'holytail' FROM dual;
exit;