2008-12-15 32 views
6

Đây là câu hỏi tôi định tự trả lời, nhưng vui lòng thêm các cách khác để thực hiện việc này.Làm cách nào để viết DLL hành động tùy chỉnh để sử dụng trong MSI?

Tôi đã đóng gói ứng dụng để sử dụng trên nhiều cấu hình khác nhau và tôi đã xác định rằng cách đáng tin cậy nhất để thực hiện logic tùy chỉnh trong MSI của mình là viết DLL hành động tùy chỉnh của riêng mình. từ bảng THUỘC TÍNH, giết một quá trình, xác định xem một ứng dụng cần được nâng cấp (và sau đó ghi lại câu trả lời trong bảng THUỘC TÍNH) và ghi vào nhật ký MSI chuẩn.

+0

Một tốt, nhưng lớn hơn, giải pháp cho hành động tùy chỉnh viết bằng Delphi: http://community.flexerasoftware.com/showthread.php?124840-A-primer-on-custom-actions-written-in- Delphi – Mick

Trả lời

11

Giải pháp của tôi ở Delphi và yêu cầu bản dịch API JEDI nguồn mở mà bạn có thể download here. Một vấn đề mà tôi đã tìm thấy là các ví dụ về việc sử dụng các tiêu đề JwaMSI rất ít và rất xa. Hy vọng rằng ai đó sẽ tìm thấy điều này như là một ví dụ hữu ích.

Đây là đơn vị chính, với một đơn vị hỗ trợ thứ hai theo sau nó (bạn có thể bao gồm trong cùng một dự án DLL). Đơn giản chỉ cần tạo một DLL mới (thư viện) trong Delphi, và sao chép/dán mã này. Đơn vị này xuất 2 chức năng được gọi từ MSI. Đó là:

  1. CheckIfUpgradeable
  2. KillRunningApp

Cả hai chức năng đọc một giá trị HỮU từ bảng tài sản, và thiết lập một giá trị khi hoàn tất. Ý tưởng là sau đó hành động tùy chỉnh thứ 2 có thể đọc thuộc tính này và đưa ra lỗi hoặc sử dụng nó làm điều kiện cài đặt.

Mã này là một ví dụ khác, và trong ví dụ dưới đây, kiểm tra xem phiên bản 'notepad.exe' có cần được nâng cấp không (nghĩa là phiên bản được lưu trữ trong giá trị bảng thuộc tính "NOTEPAD_VERSON" là lớn hơn phiên bản notepad.exe trên hệ thống). Nếu không, thì nó đặt thuộc tính của "UPGRADEABLE_VERSION" thành "NO" (thuộc tính này được đặt thành "CÓ" theo mặc định).

Mã này cũng tìm trong bảng THUỘC TÍNH cho "PROGRAM_TO_KILL" và sẽ giết chương trình đó nếu chương trình đang chạy. Nó cần bao gồm phần mở rộng của chương trình để giết, ví dụ: "Notepad.exe"

library MsiHelper; 

uses 
    Windows, 
    SysUtils, 
    Classes, 
    StrUtils, 
    jwaMSI, 
    jwaMSIDefs, 
    jwaMSIQuery, 
    JclSysInfo, 
    PsApi, 
    MSILogging in 'MSILogging.pas'; 

{$R *.res} 


function CompareVersionNumbers(AVersion1, AVersion2: string): Integer; 
var 
    N1, N2: Integer; 
//Returns 1 if AVersion1 < AVersion2 
//Returns -1 if AVersion1 > AVersion2 
//Returns 0 if values are equal 
    function GetNextNumber(var Version: string): Integer; 
    var 
    P: Integer; 
    S: string; 
    begin 
    P := Pos('.', Version); 
    if P > 0 then 
    begin 
     S := Copy(Version, 1, P - 1); 
     Version := Copy(Version, P + 1, Length(Version) - P); 
    end 
    else 
    begin 
     S := Version; 
     Version := ''; 
    end; 
    if S = '' then 
     Result := -1 
    else 
    try 
     Result := StrToInt(S); 
    except 
     Result := -1; 
    end; 
    end; 

begin 
    Result := 0; 
    repeat 
    N1 := GetNextNumber(AVersion1); 
    N2 := GetNextNumber(AVersion2); 
    if N2 > N1 then 
    begin 
     Result := 1; 
     Exit; 
    end 
    else 
    if N2 < N1 then 
    begin 
     Result := -1; 
     Exit; 
    end 
    until (AVersion1 = '') and (AVersion2 = ''); 
end; 

function GetFmtFileVersion(const FileName: String = ''; const Fmt: String = '%d.%d.%d.%d'): String; 
var 
    sFileName: String; 
    iBufferSize: DWORD; 
    iDummy: DWORD; 
    pBuffer: Pointer; 
    pFileInfo: Pointer; 
    iVer: array[1..4] of Word; 
begin 
    // set default value 
    Result := ''; 
    // get filename of exe/dll if no filename is specified 
    sFileName := FileName; 
    if (sFileName = '') then 
    begin 
    // prepare buffer for path and terminating #0 
    SetLength(sFileName, MAX_PATH + 1); 
    SetLength(sFileName, 
     GetModuleFileName(hInstance, PChar(sFileName), MAX_PATH + 1)); 
    end; 
    // get size of version info (0 if no version info exists) 
    iBufferSize := GetFileVersionInfoSize(PChar(sFileName), iDummy); 
    if (iBufferSize > 0) then 
    begin 
    GetMem(pBuffer, iBufferSize); 
    try 
    // get fixed file info (language independent) 
    GetFileVersionInfo(PChar(sFileName), 0, iBufferSize, pBuffer); 
    VerQueryValue(pBuffer, '\', pFileInfo, iDummy); 
    // read version blocks 
    iVer[1] := HiWord(PVSFixedFileInfo(pFileInfo)^.dwFileVersionMS); 
    iVer[2] := LoWord(PVSFixedFileInfo(pFileInfo)^.dwFileVersionMS); 
    iVer[3] := HiWord(PVSFixedFileInfo(pFileInfo)^.dwFileVersionLS); 
    iVer[4] := LoWord(PVSFixedFileInfo(pFileInfo)^.dwFileVersionLS); 
    finally 
     FreeMem(pBuffer); 
    end; 
    // format result string 
    Result := Format(Fmt, [iVer[1], iVer[2], iVer[3], iVer[4]]); 
    end; 
end; 


function KillRunningApp(hInstall: MSIHandle): Integer; stdcall; 
var 
    aProcesses: array[0..1023] of DWORD; 
    cbNeeded: DWORD; 
    cProcesses: DWORD; 
    i: integer; 
    szProcessName: array[0..MAX_PATH - 1] of char; 
    hProcess: THandle; 
    hMod: HModule; 
    sProcessName : PChar; 
    iProcessNameLength : Cardinal; 
begin 
    iProcessNameLength := MAX_PATH; 
    sProcessName := StrAlloc(MAX_PATH); 

    try 
    //reads the value from "PROGRAM_TO_KILL" that is stored in the PROPERTY table 
    MsiGetProperty(hInstall, 'PROGRAM_TO_KILL', sProcessName, iProcessNameLength); 

    if not EnumProcesses(@aProcesses, sizeof(aProcesses), cbNeeded) then 
    begin 
     Exit; 
    end; 
    cProcesses := cbNeeded div sizeof(DWORD); 

    for i := 0 to cProcesses - 1 do 
    begin 
     hProcess := OpenProcess(PROCESS_QUERY_INFORMATION or PROCESS_VM_READ or PROCESS_TERMINATE, False, aProcesses[i]); 
     try 
     if hProcess <> 0 then 
     begin 
     if EnumProcessModules(hProcess, @hMod, sizeof(hMod), cbNeeded) then 
     begin 
      GetModuleBaseName(hProcess, hMod, szProcessName, sizeof(szProcessName)); 
      if UpperCase(szProcessName) = UpperCase(sProcessName) then 
      begin 
      TerminateProcess(hProcess, 0); 
      end; 
     end; 
     end; 
     finally 
     CloseHandle(hProcess); 
     end;      
    end; 
    finally 
    StrDispose(sProcessName); 
    end; 

    Result:= ERROR_SUCCESS; //return success regardless of actual outcome 
end; 


function CheckIfUpgradeable(hInstall: MSIHandle): Integer; stdcall; 
var 
    Current_Notepad_version : PChar; 
    Current_Notepad_version_Length : Cardinal; 
    sWinDir, sProgramFiles : string; 
    bUpgradeableVersion : boolean; 
    iNotepad_compare : integer; 
    sNotepad_version : string; 
    sNotepad_Location : string; 
    iResult : Cardinal; 
begin 
    bUpgradeableVersion := False; 
    sWinDir := ExcludeTrailingBackslash(JclSysInfo.GetWindowsFolder); 
    sProgramFiles := ExcludeTrailingBackslash(JclSysInfo.GetProgramFilesFolder); 

    Current_Notepad_version_Length := MAX_PATH; 
    Current_Notepad_version := StrAlloc(MAX_PATH); 

    sNotepad_Location := sWinDir+'\system32\Notepad.exe'; 

    iResult := ERROR_SUCCESS; 

    try 
    //reads the value from "NOTEPAD_VERSION" that is stored in the PROPERTY table 
    MsiGetProperty(hInstall, 'NOTEPAD_VERSION', Current_Notepad_version, Current_Notepad_version_Length); 

    if Not (FileExists(sNotepad_Location)) then 
    begin 
     bUpgradeableVersion := True; 
     LogString(hInstall,'Notepad.exe was not found at: "'+sNotepad_Location+'"'); 
     LogString(hInstall,'This version will be upgraded.'); 
     iResult := ERROR_SUCCESS; 
     Exit; 
    end; 

    sNotepad_version := GetFmtFileVersion(sNotepad_Location); 
    LogString(hInstall,'Found Notepad version="'+sNotepad_version+'"'); 
    iNotepad_compare := CompareVersionNumbers(sNotepad_version,StrPas(Current_Notepad_version)); 

    if (iNotepad_compare < 0) then 
    begin 
     bUpgradeableVersion := False; 
    end 
    else 
    begin 
     bUpgradeableVersion := True; 
    end; 


    if bUpgradeableVersion then 
    begin 
     LogString(hInstall,'This version will be upgraded.'); 
     iResult := ERROR_SUCCESS; 
    end 
    else 
    begin 
     MsiSetProperty(hInstall,'UPGRADEABLE_VERSION','NO'); //this indicates failure -- this value is read by another custom action executed after this action 
     LogString(hInstall,'ERROR: A newer version of this software is already installed. Setup cannot continue!'); 
     iResult := ERROR_SUCCESS; 
    end; 
    finally 
    StrDispose(Current_Notepad_version); 
    end; 

    Result:= iResult; //this function always returns success, however it could return any of the values listed below 
// 
//Custom Action Return Values 
//================================ 
// 
//Return value      Description 
// 
//ERROR_FUNCTION_NOT_CALLED   Action not executed. 
//ERROR_SUCCESS      Completed actions successfully. 
//ERROR_INSTALL_USEREXIT    User terminated prematurely. 
//ERROR_INSTALL_FAILURE    Unrecoverable error occurred. 
//ERROR_NO_MORE_ITEMS     Skip remaining actions, not an error. 
// 
end; 

exports CheckIfUpgradeable; 
exports KillRunningApp; 

begin 
end. 

Và đây là đơn vị hỗ trợ "MSILogging.pas". Đơn vị này có thể được sử dụng như trong các dự án MSI DLL khác.

unit MSILogging; 

interface 

uses 
    Windows, 
    SysUtils, 
    JwaMsi, 
    JwaMsiQuery, 
    JwaMSIDefs; 

procedure LogString(hInstall: MSIHandle; sMsgString : string); 
function MsiMessageBox(hInstall: MSIHandle; sMsgString : string; dwDlgFlags : integer): integer; 

implementation 

procedure LogString(hInstall: MSIHandle; sMsgString : string); 
var 
    hNewMsiHandle : MSIHandle; 
begin 
    try 
    hNewMsiHandle := MsiCreateRecord(2); 

    sMsgString := '-- MSI_LOGGING -- ' + sMsgString; 
    MsiRecordSetString(hNewMsiHandle, 0, PChar(sMsgString)); 
    MsiProcessMessage(hInstall, INSTALLMESSAGE(INSTALLMESSAGE_INFO), hNewMsiHandle); 
    finally 
    MsiCloseHandle(hNewMsiHandle); 
    end; 
end; 


function MsiMessageBox(hInstall: MSIHandle; sMsgString : string; dwDlgFlags : integer): integer; 
var 
    hNewMsiHandle : MSIHandle; 
begin 
    try 
    hNewMsiHandle := MsiCreateRecord(2); 
    MsiRecordSetString(hNewMsiHandle, 0, PChar(sMsgString)); 
    finally 
    MsiCloseHandle(hNewMsiHandle); 
    end; 

    //Result := (MsiProcessMessage(hInstall, INSTALLMESSAGE(dwDlgFlags), hNewMsiHandle)); 
    Result := (MsiProcessMessage(hInstall, INSTALLMESSAGE(INSTALLMESSAGE_USER + dwDlgFlags), hNewMsiHandle)); 
end; 

end. 
+1

Lưu ý rằng đối với bất kỳ chức năng nào bạn muốn được gọi từ MSI, bạn phải bao gồm "stdcall;" sau khi nó là nguyên mẫu. Ngoài ra, bạn phải bao gồm một dòng ở đâu đó có nội dung "export function_name". – Mick

Các vấn đề liên quan