كيف يمكنني كتابة الإجراء المخصص DLL للاستخدام في MSI?

StackOverflow https://stackoverflow.com/questions/367365

سؤال

هذا هو السؤال أنوي الإجابة على نفسي ، ولكن من فضلك لا تتردد في إضافة طرق أخرى لتحقيق ذلك.

كان التعبئة والتغليف تطبيق للاستخدام في مجموعة متنوعة من التشكيلات ، قررت أن الطريقة الأكثر موثوقية أداء منطق العرف داخل MSI أن أكتب بلدي مخصص العمل DLL التي من شأنها أن تكون قادرة على القراءة/الكتابة من مكان الإقامة الجدول ، قتل في عملية تحديد ما إذا كان التطبيق يحتاج إلى أن تتم ترقية (ثم سجل الجواب في الممتلكات الجدول) ، وكتابة القياسية MSI السجل.

هل كانت مفيدة؟

المحلول

الحل هو في دلفي ، ويتطلب مفتوحة المصدر جدي ترجمة API التي يمكنك تحميل هنا.مشكلة واحدة أن أكون قد وجدت أن الأمثلة باستخدام JwaMSI رؤوس قليلة ومتباعدة.نأمل شخص ما سوف تجد هذا مفيد سبيل المثال.

هنا هو الوحدة الرئيسية مع 2 وحدة الدعم التالية (التي يمكن أن تدرج في نفس المشروع DLL).ببساطة إنشاء DLL جديد (المكتبة) في دلفي ، ونسخ/لصق هذه التعليمات البرمجية.هذه الوحدة الصادرات 2 وظائف للاستدعاء من MSI.وهم:

  1. CheckIfUpgradeable
  2. KillRunningApp

كل من هذه المهام قراءة قيمة العقار من ملك المائدة تعيين قيمة عندما تكون كاملة.الفكرة هي أن ثم 2 إجراء مخصص يمكن قراءة هذه الخاصية و رمي خطأ ، أو استخدامه بمثابة تثبيت الشرط.

هذا الرمز هو أكثر على سبيل المثال ، في هذا المثال أدناه هو فحص لمعرفة ما إذا كان إصدار 'notepad.exe' يحتاج إلى ترقية (هذا يعني أن النسخة المخزنة في الجدول الخاصية قيمة "NOTEPAD_VERSON" أكبر من إصدار notepad.exe على النظام).إذا لم يكن, ثم يقوم بتعيين خاصية "UPGRADEABLE_VERSION" إلى "لا" (يتم تعيين هذه الخاصية إلى "نعم" بشكل افتراضي).

هذا الرمز كما يبدو في الجدول الخاصية من أجل "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.
مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top