gpt4 book ai didi

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

转载 作者:行者123 更新时间:2023-12-03 14:58:12 27 4
gpt4 key购买 nike

这是我打算自己回答的问题,但请随意添加其他方法来实现此目的。

我正在打包一个应用程序以用于各种配置,并且我确定在 MSI 中执行自定义逻辑的最可靠方法是编写我自己的自定义操作 DLL,该 DLL 能够从PROPERTY 表,终止进程,确定应用程序是否需要升级(然后将答案记录在 PROPERTY 表中),并写入标准 MSI 日志。

最佳答案

我的解决方案是在 Delphi 中,并且需要开源 JEDI API 翻译,您可以 download here 。我发现的一个问题是使用 JwaMSI header 的示例很少。希望有人会发现这是一个有用的例子。

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

  1. 检查是否可升级
  2. KillRunningApp

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

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

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

关于delphi - 如何编写自定义操作 DLL 以在 MSI 中使用?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/367365/

27 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com