当前位置:  开发笔记 > 开发工具 > 正文

如何编写自定义动作DLL以在MSI中使用?

如何解决《如何编写自定义动作DLL以在MSI中使用?》经验,为你挑选了1个好方法。

这是我打算自己回答的问题,但请随意添加其他方法来完成此任务.

我正在打包应用程序以用于各种配置,我确定在MSI中执行自定义逻辑的最可靠方法是编写我自己的自定义操作DLL,它可以从PROPERTY表读取/写入,杀死进程,确定是否需要升级应用程序(然后在PROPERTY表中记录答案),并写入标准MSI日志.



1> Mick..:

我的解决方案是在Delphi中,需要您可以在此处下载的开源JEDI API翻译.我发现的一个问题是使用JwaMSI头文件的例子很少.希望有人会发现这是一个有用的例子.

这是主要单元,后面有第二个支持单元(可以包含在同一个DLL项目中).只需在Delphi中创建一个新的DLL(库),然后复制/粘贴此代码即可.该单元导出2个可从MSI调用的函数.他们是:

    CheckIfUpgradeable

    KillRunningApp

这两个函数都从属性表中读取一个PROPERTY值,并在完成时设置一个值.这个想法是,然后第二个自定义操作可以读取此属性并抛出错误,或将其用作安装条件.

此代码更多是一个示例,在下面的示例中,它检查是否需要升级'notepad.exe'的版本(这意味着存储在属性表值"NOTEPAD_VERSON"中的版本大于版本系统上的notepad.exe).如果不是,则将"UPGRADEABLE_VERSION"的属性设置为"NO"(默认情况下,此属性设置为"YES").

此代码还在PROPERTY表中查找"PROGRAM_TO_KILL",并在程序运行时终止该程序.它需要包含要杀死的程序的文件扩展名,例如"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.

这是支持单位"MSILogging.pas".此单元可以在其他MSI DLL项目中原样使用.

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.

推荐阅读
落单鸟人
这个屌丝很懒,什么也没留下!
DevBox开发工具箱 | 专业的在线开发工具网站    京公网安备 11010802040832号  |  京ICP备19059560号-6
Copyright © 1998 - 2020 DevBox.CN. All Rights Reserved devBox.cn 开发工具箱 版权所有