Skip to content

Commit

Permalink
Installer: Rename the ShellExtension*.dll file names before installin…
Browse files Browse the repository at this point in the history
…g to avoid installation failures when Explorer is using ShellExtension*.dll
  • Loading branch information
sdottaka committed Feb 14, 2023
1 parent 849035e commit cae41d5
Show file tree
Hide file tree
Showing 42 changed files with 417 additions and 365 deletions.
149 changes: 76 additions & 73 deletions Installer/InnoSetup/WinMergeARM64.is6.iss
Original file line number Diff line number Diff line change
Expand Up @@ -423,11 +423,20 @@ Source: ..\..\Build\{#ARCH}\Release\LogoImages\*.png; DestDir: {app}\LogoImages;
Source: ..\..\Plugins\WinMerge32BitPluginProxy\Release\WinMerge32BitPluginProxy.exe; DestDir: {app}; Flags: promptifolder; Components: Core

; Shell extension
Source: ..\..\Build\ShellExtension\{#ShellExtension32bit}; DestDir: {app}; Flags: uninsrestartdelete restartreplace promptifolder; MinVersion: 0, 4; Components: ShellExtension32bit; Check: not AreSourceAndDestinationOfShellExtensionSame(ExpandConstant('{app}\{#ShellExtension32bit}'))
Source: ..\..\Build\ShellExtension\{#ShellExtension32bit}; DestDir: {app}; Flags: uninsrestartdelete restartreplace promptifolder; \
MinVersion: 0, 4; Components: ShellExtension32bit; \
Check: not AreSourceAndDestinationOfShellExtensionSame(ExpandConstant('{app}\{#ShellExtension32bit}')) and \
RenameShellExtensionDLLIfExists(ExpandConstant('{app}\{#ShellExtension32bit}'))
; 64-bit version of ShellExtension
Source: ..\..\Build\ShellExtension\{#ShellExtension64bit}; DestDir: {app}; Flags: uninsrestartdelete restartreplace promptifolder 64bit; MinVersion: 0,5.01.2600; Check: IsWin64 and not AreSourceAndDestinationOfShellExtensionSame(ExpandConstant('{app}\{#ShellExtension64bit}')) and KillProcesses_ExplorerSeparateProcess
Source: ..\..\Build\ShellExtension\{#ARCH}\WinMergeContextMenu.dll; DestDir: {app}; Flags: uninsrestartdelete restartreplace promptifolder 64bit; MinVersion: 0,5.01.2600; Check: IsWin64 and not AreSourceAndDestinationOfWinMergeContextMenuSame(ExpandConstant('{app}\WinMergeContextMenu.dll')) and UnregisterWinMergeContextMenuPackage and KillProcesses_DllHostWinMergeContextMenu
Source: ..\..\Build\ShellExtension\WinMergeContextMenuPackage.msix; DestDir: {app}; Flags: uninsrestartdelete restartreplace promptifolder 64bit; MinVersion: 0,5.01.2600; Check: IsWin64 and not AreSourceAndDestinationOfWinMergeContextMenuSame(ExpandConstant('{app}\WinMergeContextMenuPackage.msix'))
Source: ..\..\Build\ShellExtension\{#ShellExtension64bit}; DestDir: {app}; Flags: uninsrestartdelete restartreplace promptifolder 64bit; \
MinVersion: 0,5.01.2600; \
Check: IsWin64 and not AreSourceAndDestinationOfShellExtensionSame(ExpandConstant('{app}\{#ShellExtension64bit}')) and \
RenameShellExtensionDLLIfExists(ExpandConstant('{app}\{#ShellExtension64bit}'))
Source: ..\..\Build\ShellExtension\{#ARCH}\WinMergeContextMenu.dll; DestDir: {app}; Flags: uninsrestartdelete restartreplace promptifolder 64bit; \
Check: IsWin64 and not AreSourceAndDestinationOfWinMergeContextMenuSame(ExpandConstant('{app}\WinMergeContextMenu.dll')) and \
UnregisterWinMergeContextMenuPackage and RenameShellExtensionDLLIfExists(ExpandConstant('{app}\WinMergeContextMenu.dll'))
Source: ..\..\Build\ShellExtension\WinMergeContextMenuPackage.msix; DestDir: {app}; Flags: uninsrestartdelete restartreplace promptifolder 64bit; \
Check: IsWin64 and not AreSourceAndDestinationOfWinMergeContextMenuSame(ExpandConstant('{app}\WinMergeContextMenuPackage.msix'))

; ArchiveSupport
;Please do not reorder the 7z Dlls by version they compress better ordered by platform and then by version
Expand Down Expand Up @@ -789,6 +798,7 @@ Name: {app}; Type: dirifempty
[Code]
Var
g_CheckListBox: TNewCheckListBox;
g_IsExplorerRestartRequired: boolean;
{Determines whether or not the user chose to create a start menu}
Function GroupCreated(): boolean;
Expand Down Expand Up @@ -890,13 +900,74 @@ Begin;
Result := true;
End;
function GetCurrentProcessId: DWORD;
external '[email protected] stdcall';
function RenameShellExtensionDLLIfExists(Filename: String) : Boolean;
var
dest: String;
begin
if FileExists(Filename) then begin
dest := Filename + '.' + IntToStr(GetCurrentProcessId()) + '.old';
RenameFile(Filename, dest);
end;
Result := true;
end;
function DeleteRenamedShellExtensionDLL(Filename: String; fRestartReplace: Boolean; fRetry: Boolean) : Boolean;
var
dest: String;
i: Integer;
begin
dest := Filename + '.' + IntToStr(GetCurrentProcessId()) + '.old';
if FileExists(dest) then begin
for i := 0 to 40 do begin
Result := DeleteFile(dest);
if not fRetry or Result = true then break;
Sleep(100);
end;
if Result = false then begin
if fRestartReplace then begin
RestartReplace(dest, '');
end;
g_IsExplorerRestartRequired := true;
end;
end;
Result := true;
end;
procedure RestartExplorer;
var
ResultCode: Integer;
begin
Exec(ExpandConstant('{sys}\taskkill.exe'), '/f /im explorer.exe', '', SW_HIDE, ewWaitUntilTerminated, ResultCode);
Exec(ExpandConstant('{win}\explorer.exe'), '', '', SW_SHOW, ewNoWait, ResultCode);
end;
procedure DeleteRenamedFiles();
begin
DeleteRenamedShellExtensionDLL(ExpandConstant('{app}\{#ShellExtension32bit}'), false, false);
DeleteRenamedShellExtensionDLL(ExpandConstant('{app}\{#ShellExtension64bit}'), false, false);
DeleteRenamedShellExtensionDLL(ExpandConstant('{app}\WinMergeContextMenu.dll'), false, false);
if g_IsExplorerRestartRequired then begin
if SuppressibleMsgBox(ExpandConstant('{cm:ExplorerNeedsRestart}'), mbConfirmation, MB_YESNO or MB_DEFBUTTON2, IDNO) = IDYES then begin
RestartExplorer();
DeleteRenamedShellExtensionDLL(ExpandConstant('{app}\{#ShellExtension32bit}'), true, true);
DeleteRenamedShellExtensionDLL(ExpandConstant('{app}\{#ShellExtension64bit}'), true, true);
DeleteRenamedShellExtensionDLL(ExpandConstant('{app}\WinMergeContextMenu.dll'), true, true);
end;
end;
end;
{This event procedure is queed each time the user changes pages within the installer}
Procedure CurPageChanged(CurPage: integer);
Begin
{if the installer reaches the file copy page then...}
If CurPage = wpInstalling Then
{Delete the previous start menu group if the location has changed since the last install}
DeletePreviousStartMenu;
If CurPage = wpFinished Then
DeleteRenamedFiles;
End;
// Checks if context menu is already enabled for shell extension
Expand Down Expand Up @@ -1025,75 +1096,6 @@ begin
Result := false;
end;
function GetCurrentProcessId:DWORD;
external '[email protected] stdcall';
function GetCurrentSessionId() : Integer;
var
locator: Variant;
services: Variant;
process: Variant;
processes: Variant;
i: Integer;
ResultCode: Integer;
begin
Result := -1
locator := CreateOleObject('WbemScripting.SWbemLocator');
services := locator.ConnectServer('', 'root\CIMV2');
processes := services.ExecQuery('SELECT ProcessId, SessionId FROM Win32_Process WHERE ProcessId = ' + IntToStr(GetCurrentProcessId()));
if not VarIsNull(processes) then begin
for i := 0 to processes.Count - 1 do begin
process := processes.ItemIndex(i);
if not VarIsNull(process) then begin
Result := process.SessionId
end;
end;
end;
end;
procedure KillProcesses(filter: String);
var
locator: Variant;
services: Variant;
process: Variant;
processes: Variant;
i: Integer;
ResultCode: Integer;
begin
locator := CreateOleObject('WbemScripting.SWbemLocator');
services := locator.ConnectServer('', 'root\CIMV2');
processes := services.ExecQuery('SELECT ProcessId FROM Win32_Process WHERE ' + filter);
if not VarIsNull(processes) then begin
for i := 0 to processes.Count - 1 do begin
process := processes.ItemIndex(i);
if not VarIsNull(process) then begin
if GetUserNameString() = process.ExecMethod_('GetOwner').User then begin
process.Terminate();
end;
end;
end;
end;
end;
{ To avoid ShellExtension installation failures, terminate Explorer running as a separate process if it is not in the middle of a file operation. }
function KillProcesses_ExplorerSeparateProcess() : Boolean;
begin
(*
if FindWindowByClassName('OperationStatusWindow') = 0 then begin
KillProcesses('Name = "explorer.exe" AND CommandLine LIKE "%/factory,{%" AND SessionId = ' + IntToStr(GetCurrentSessionId()));
end;
*)
Result := true;
end;
function KillProcesses_DllHostWinMergeContextMenu() : Boolean;
begin
(*
KillProcesses('Name = "dllhost.exe" AND CommandLine LIKE "%/Processid:{90340779-F37E-468E-9728-A2593498ED32}%"');
*)
Result := true;
end;
procedure RegisterPreviousData(PreviousDataKey: Integer);
begin
SetPreviousData(PreviousDataKey, 'UseAs3WayMergeTool', BooleanToString(g_CheckListBox.Checked[0]));
Expand Down Expand Up @@ -1122,6 +1124,7 @@ begin
g_CheckListBox.AddRadioButton(ExpandConstant('{cm:MergeAtCenterPane}'), '', 1, StringToBoolean(GetPreviousData('MergeAtCenterPane', 'false')), True, nil);
g_CheckListBox.AddRadioButton(ExpandConstant('{cm:MergeAtLeftPane}'), '', 1, StringToBoolean(GetPreviousData('MergeAtLeftPane', 'false')), True, nil);
g_CheckListBox.AddCheckBox(ExpandConstant('{cm:AutoMergeAtStartup}'), '', 1, StringToBoolean(GetPreviousData('AutoMergeAtStartup', 'true')), True, False, True, nil);
g_IsExplorerRestartRequired := false;
end;
procedure DeinitializeSetup();
Expand Down
149 changes: 76 additions & 73 deletions Installer/InnoSetup/WinMergeX64.is6.iss
Original file line number Diff line number Diff line change
Expand Up @@ -422,11 +422,20 @@ Source: ..\..\Build\{#ARCH}\Release\LogoImages\*.png; DestDir: {app}\LogoImages;
Source: ..\..\Plugins\WinMerge32BitPluginProxy\Release\WinMerge32BitPluginProxy.exe; DestDir: {app}; Flags: promptifolder; Components: Core

; Shell extension
Source: ..\..\Build\ShellExtension\{#ShellExtension32bit}; DestDir: {app}; Flags: uninsrestartdelete restartreplace promptifolder; MinVersion: 0, 4; Components: ShellExtension32bit; Check: not AreSourceAndDestinationOfShellExtensionSame(ExpandConstant('{app}\{#ShellExtension32bit}'))
Source: ..\..\Build\ShellExtension\{#ShellExtension32bit}; DestDir: {app}; Flags: uninsrestartdelete restartreplace promptifolder; \
MinVersion: 0, 4; Components: ShellExtension32bit; \
Check: not AreSourceAndDestinationOfShellExtensionSame(ExpandConstant('{app}\{#ShellExtension32bit}')) and \
RenameShellExtensionDLLIfExists(ExpandConstant('{app}\{#ShellExtension32bit}'))
; 64-bit version of ShellExtension
Source: ..\..\Build\ShellExtension\{#ShellExtension64bit}; DestDir: {app}; Flags: uninsrestartdelete restartreplace promptifolder 64bit; MinVersion: 0,5.01.2600; Check: IsWin64 and not AreSourceAndDestinationOfShellExtensionSame(ExpandConstant('{app}\{#ShellExtension64bit}')) and KillProcesses_ExplorerSeparateProcess
Source: ..\..\Build\ShellExtension\{#ARCH}\WinMergeContextMenu.dll; DestDir: {app}; Flags: uninsrestartdelete restartreplace promptifolder 64bit; MinVersion: 0,5.01.2600; Check: IsWin64 and not AreSourceAndDestinationOfWinMergeContextMenuSame(ExpandConstant('{app}\WinMergeContextMenu.dll')) and UnregisterWinMergeContextMenuPackage and KillProcesses_DllHostWinMergeContextMenu
Source: ..\..\Build\ShellExtension\WinMergeContextMenuPackage.msix; DestDir: {app}; Flags: uninsrestartdelete restartreplace promptifolder 64bit; MinVersion: 0,5.01.2600; Check: IsWin64 and not AreSourceAndDestinationOfWinMergeContextMenuSame(ExpandConstant('{app}\WinMergeContextMenuPackage.msix'))
Source: ..\..\Build\ShellExtension\{#ShellExtension64bit}; DestDir: {app}; Flags: uninsrestartdelete restartreplace promptifolder 64bit; \
MinVersion: 0,5.01.2600; \
Check: IsWin64 and not AreSourceAndDestinationOfShellExtensionSame(ExpandConstant('{app}\{#ShellExtension64bit}')) and \
RenameShellExtensionDLLIfExists(ExpandConstant('{app}\{#ShellExtension64bit}'))
Source: ..\..\Build\ShellExtension\{#ARCH}\WinMergeContextMenu.dll; DestDir: {app}; Flags: uninsrestartdelete restartreplace promptifolder 64bit; \
Check: IsWin64 and not AreSourceAndDestinationOfWinMergeContextMenuSame(ExpandConstant('{app}\WinMergeContextMenu.dll')) and \
UnregisterWinMergeContextMenuPackage and RenameShellExtensionDLLIfExists(ExpandConstant('{app}\WinMergeContextMenu.dll'))
Source: ..\..\Build\ShellExtension\WinMergeContextMenuPackage.msix; DestDir: {app}; Flags: uninsrestartdelete restartreplace promptifolder 64bit; \
Check: IsWin64 and not AreSourceAndDestinationOfWinMergeContextMenuSame(ExpandConstant('{app}\WinMergeContextMenuPackage.msix'))

; ArchiveSupport
;Please do not reorder the 7z Dlls by version they compress better ordered by platform and then by version
Expand Down Expand Up @@ -788,6 +797,7 @@ Name: {app}; Type: dirifempty
[Code]
Var
g_CheckListBox: TNewCheckListBox;
g_IsExplorerRestartRequired: boolean;
{Determines whether or not the user chose to create a start menu}
Function GroupCreated(): boolean;
Expand Down Expand Up @@ -889,13 +899,74 @@ Begin;
Result := true;
End;
function GetCurrentProcessId: DWORD;
external '[email protected] stdcall';
function RenameShellExtensionDLLIfExists(Filename: String) : Boolean;
var
dest: String;
begin
if FileExists(Filename) then begin
dest := Filename + '.' + IntToStr(GetCurrentProcessId()) + '.old';
RenameFile(Filename, dest);
end;
Result := true;
end;
function DeleteRenamedShellExtensionDLL(Filename: String; fRestartReplace: Boolean; fRetry: Boolean) : Boolean;
var
dest: String;
i: Integer;
begin
dest := Filename + '.' + IntToStr(GetCurrentProcessId()) + '.old';
if FileExists(dest) then begin
for i := 0 to 40 do begin
Result := DeleteFile(dest);
if not fRetry or Result = true then break;
Sleep(100);
end;
if Result = false then begin
if fRestartReplace then begin
RestartReplace(dest, '');
end;
g_IsExplorerRestartRequired := true;
end;
end;
Result := true;
end;
procedure RestartExplorer;
var
ResultCode: Integer;
begin
Exec(ExpandConstant('{sys}\taskkill.exe'), '/f /im explorer.exe', '', SW_HIDE, ewWaitUntilTerminated, ResultCode);
Exec(ExpandConstant('{win}\explorer.exe'), '', '', SW_SHOW, ewNoWait, ResultCode);
end;
procedure DeleteRenamedFiles();
begin
DeleteRenamedShellExtensionDLL(ExpandConstant('{app}\{#ShellExtension32bit}'), false, false);
DeleteRenamedShellExtensionDLL(ExpandConstant('{app}\{#ShellExtension64bit}'), false, false);
DeleteRenamedShellExtensionDLL(ExpandConstant('{app}\WinMergeContextMenu.dll'), false, false);
if g_IsExplorerRestartRequired then begin
if SuppressibleMsgBox(ExpandConstant('{cm:ExplorerNeedsRestart}'), mbConfirmation, MB_YESNO or MB_DEFBUTTON2, IDNO) = IDYES then begin
RestartExplorer();
DeleteRenamedShellExtensionDLL(ExpandConstant('{app}\{#ShellExtension32bit}'), true, true);
DeleteRenamedShellExtensionDLL(ExpandConstant('{app}\{#ShellExtension64bit}'), true, true);
DeleteRenamedShellExtensionDLL(ExpandConstant('{app}\WinMergeContextMenu.dll'), true, true);
end;
end;
end;
{This event procedure is queed each time the user changes pages within the installer}
Procedure CurPageChanged(CurPage: integer);
Begin
{if the installer reaches the file copy page then...}
If CurPage = wpInstalling Then
{Delete the previous start menu group if the location has changed since the last install}
DeletePreviousStartMenu;
If CurPage = wpFinished Then
DeleteRenamedFiles;
End;
// Checks if context menu is already enabled for shell extension
Expand Down Expand Up @@ -1024,75 +1095,6 @@ begin
Result := false;
end;
function GetCurrentProcessId:DWORD;
external '[email protected] stdcall';
function GetCurrentSessionId() : Integer;
var
locator: Variant;
services: Variant;
process: Variant;
processes: Variant;
i: Integer;
ResultCode: Integer;
begin
Result := -1
locator := CreateOleObject('WbemScripting.SWbemLocator');
services := locator.ConnectServer('', 'root\CIMV2');
processes := services.ExecQuery('SELECT ProcessId, SessionId FROM Win32_Process WHERE ProcessId = ' + IntToStr(GetCurrentProcessId()));
if not VarIsNull(processes) then begin
for i := 0 to processes.Count - 1 do begin
process := processes.ItemIndex(i);
if not VarIsNull(process) then begin
Result := process.SessionId
end;
end;
end;
end;
procedure KillProcesses(filter: String);
var
locator: Variant;
services: Variant;
process: Variant;
processes: Variant;
i: Integer;
ResultCode: Integer;
begin
locator := CreateOleObject('WbemScripting.SWbemLocator');
services := locator.ConnectServer('', 'root\CIMV2');
processes := services.ExecQuery('SELECT ProcessId FROM Win32_Process WHERE ' + filter);
if not VarIsNull(processes) then begin
for i := 0 to processes.Count - 1 do begin
process := processes.ItemIndex(i);
if not VarIsNull(process) then begin
if GetUserNameString() = process.ExecMethod_('GetOwner').User then begin
process.Terminate();
end;
end;
end;
end;
end;
{ To avoid ShellExtension installation failures, terminate Explorer running as a separate process if it is not in the middle of a file operation. }
function KillProcesses_ExplorerSeparateProcess() : Boolean;
begin
(*
if FindWindowByClassName('OperationStatusWindow') = 0 then begin
KillProcesses('Name = "explorer.exe" AND CommandLine LIKE "%/factory,{%" AND SessionId = ' + IntToStr(GetCurrentSessionId()));
end;
*)
Result := true;
end;
function KillProcesses_DllHostWinMergeContextMenu() : Boolean;
begin
(*
KillProcesses('Name = "dllhost.exe" AND CommandLine LIKE "%/Processid:{90340779-F37E-468E-9728-A2593498ED32}%"');
*)
Result := true;
end;
procedure RegisterPreviousData(PreviousDataKey: Integer);
begin
SetPreviousData(PreviousDataKey, 'UseAs3WayMergeTool', BooleanToString(g_CheckListBox.Checked[0]));
Expand Down Expand Up @@ -1121,6 +1123,7 @@ begin
g_CheckListBox.AddRadioButton(ExpandConstant('{cm:MergeAtCenterPane}'), '', 1, StringToBoolean(GetPreviousData('MergeAtCenterPane', 'false')), True, nil);
g_CheckListBox.AddRadioButton(ExpandConstant('{cm:MergeAtLeftPane}'), '', 1, StringToBoolean(GetPreviousData('MergeAtLeftPane', 'false')), True, nil);
g_CheckListBox.AddCheckBox(ExpandConstant('{cm:AutoMergeAtStartup}'), '', 1, StringToBoolean(GetPreviousData('AutoMergeAtStartup', 'true')), True, False, True, nil);
g_IsExplorerRestartRequired := false;
end;
procedure DeinitializeSetup();
Expand Down
Loading

0 comments on commit cae41d5

Please sign in to comment.