Skip to content

Commit

Permalink
Allow installation to close shell extension DLLs via the custom actio…
Browse files Browse the repository at this point in the history
…n. Disable reboot prompt in case of the version with this change was previously already installed.

Signed-off-by: alex-z <[email protected]>
  • Loading branch information
allexzander committed Mar 19, 2024
1 parent 7262696 commit c1719bc
Show file tree
Hide file tree
Showing 11 changed files with 330 additions and 4 deletions.
5 changes: 5 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,13 @@ set(BIN_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin")

include(${CMAKE_SOURCE_DIR}/NEXTCLOUD.cmake)

set ( NCCONTEXTMENU_SHELLEXT_WINDOW_CLASS_NAME "${APPLICATION_SHORTNAME}_Shxt_CntMenuHndlr_WndClass" )
set ( NCOVERLAYS_SHELLEXT_WINDOW_CLASS_NAME "${APPLICATION_SHORTNAME}_Shxt_Ovs_WndClass" )

# CfAPI Shell Extensions
set( CFAPI_SHELL_EXTENSIONS_LIB_NAME CfApiShellExtensions )

set ( CFAPI_SHELLEXT_WINDOW_CLASS_NAME "${APPLICATION_SHORTNAME}_${CFAPI_SHELL_EXTENSIONS_LIB_NAME}_WndClass" )

set( CFAPI_SHELLEXT_APPID_REG "{E314A650-DCA4-416E-974E-18EA37C213EA}")
set( CFAPI_SHELLEXT_APPID_DISPLAY_NAME "${APPLICATION_NAME} CfApi Shell Extensions" )
Expand Down
28 changes: 24 additions & 4 deletions admin/win/msi/Nextcloud.wxs
Original file line number Diff line number Diff line change
Expand Up @@ -60,18 +60,33 @@
<FileSearch Id="LegacyUninstallFileName" Name="Uninstall.exe"/>
</RegistrySearch>
</Property>

<Property Id="IS_PREV_VERSION_SHELL_EXT_CLOSE_SUPPORTED">
<RegistrySearch Id="RegIsPrevVersionShellExtCloseSupported" Type="raw" Root="HKLM" Key="Software\$(var.AppVendor)\$(var.AppName)" Name="isShellExtCloseSupported" Win64="$(var.PlatformWin64)" />
</Property>

<!-- Property to disable update checks -->
<Property Id="SKIPAUTOUPDATE" Value="0" />

<!-- Quit / restart application -->
<util:RestartResource ProcessName="$(var.AppExe)" />

<Property Id="WNDCLASSNAMETOCLOSE" Value="$(var.CfApiShellextWndClassName)" />
<CustomAction Id="SetCfApiWindowClassName" Property="WNDCLASSNAMETOCLOSE" Value="$(var.CfApiShellextWndClassName)" />
<CustomAction Id="SetNCContextMenuWindowClassName" Property="WNDCLASSNAMETOCLOSE" Value="$(var.NCContextMenuShellextWndClassName)" />
<CustomAction Id="SetNCOverlaysWindowClassName" Property="WNDCLASSNAMETOCLOSE" Value="$(var.NCOverlaysShellextWndClassName)" />

<!-- Helper DLL Custom Actions -->
<!-- Helper DLL Custom Actions -->
<SetProperty Id="ExecNsisUninstaller" Value="&quot;$(var.AppShortName)&quot; &quot;[NSIS_UNINSTALLEXE]&quot;" Before="ExecNsisUninstaller" Sequence="execute" />
<SetProperty Id="RemoveNavigationPaneEntries" Value="&quot;$(var.AppName)&quot;" Before="RemoveNavigationPaneEntries" Sequence="execute" />

<InstallExecuteSequence>
<Custom Action="SetCfApiWindowClassName" Before="InstallValidate"/>
<Custom Action="CloseCfApiShellExtension" After="SetCfApiWindowClassName" />
<Custom Action="SetNCContextMenuWindowClassName" After="CloseCfApiShellExtension"/>
<Custom Action="CloseNCContextMenuShellExtension" After="SetNCContextMenuWindowClassName" />
<Custom Action="SetNCOverlaysWindowClassName" After="CloseNCContextMenuShellExtension"/>
<Custom Action="CloseNCOverlaysShellExtension" After="SetNCOverlaysWindowClassName" />
<!-- Install: Remove previous NSIS installation, if detected -->
<Custom Action="ExecNsisUninstaller" Before="ProcessComponents">NSIS_UNINSTALLEXE AND NOT Installed</Custom>

Expand All @@ -80,9 +95,8 @@

<!-- Uninstall: Cleanup the Registry -->
<Custom Action="RegistryCleanupCustomAction" After="RemoveFiles">(NOT UPGRADINGPRODUCTCODE) AND (REMOVE="ALL")</Custom>

<!-- Schedule Reboot for the Shell Extensions (in silent installation mode only, or if SCHEDULE_REBOOT argument is set-->
<ScheduleReboot After="InstallFinalize">(SCHEDULE_REBOOT=1) OR NOT (UILevel=2)</ScheduleReboot>
<!-- Schedule Reboot for the Shell Extensions (only if SCHEDULE_REBOOT argument is set or if th version is less than 3.12.2 -->
<ScheduleReboot After="InstallFinalize">(SCHEDULE_REBOOT=1) OR ((NOT (UILevel=2)) AND (NOT IS_PREV_VERSION_SHELL_EXT_CLOSE_SUPPORTED))</ScheduleReboot>
</InstallExecuteSequence>

<!-- "Add or Remove" Programs Entries -->
Expand Down Expand Up @@ -184,6 +198,11 @@
<RegistryValue Type="string" Name="InstallerProductCode" Value="[ProductCode]" />
</RegistryKey>
</Component>
<Component Id="RegistryMiscInfo" Guid="*" Win64="$(var.PlatformWin64)">
<RegistryKey Root="HKLM" Key="Software\$(var.AppVendor)\$(var.AppName)">
<RegistryValue Type="integer" Name="isShellExtCloseSupported" Value="1" />
</RegistryKey>
</Component>

<!-- Platform bitness-dependent settings -->
<Component Id="RegistryDefaultSettings" Guid="*" Win64="$(var.PlatformWin64)">
Expand Down Expand Up @@ -213,6 +232,7 @@
<ComponentGroupRef Id="ClientFiles" />

<ComponentRef Id="RegistryVersionInfo" />
<ComponentRef Id="RegistryMiscInfo" />
<ComponentRef Id="RegistryDefaultSettings" />
<ComponentRef Id="RegistryUriHandler" />

Expand Down
4 changes: 4 additions & 0 deletions admin/win/msi/OEM.wxi.in
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,10 @@

<?define AppCommandOpenUrlScheme = "@APPLICATION_URI_HANDLER_SCHEME@" ?>

<?define CfApiShellextWndClassName = "@CFAPI_SHELLEXT_WINDOW_CLASS_NAME@" ?>
<?define NCOverlaysShellextWndClassName = "@NCOVERLAYS_SHELLEXT_WINDOW_CLASS_NAME@" ?>
<?define NCContextMenuShellextWndClassName = "@NCCONTEXTMENU_SHELLEXT_WINDOW_CLASS_NAME@" ?>

<!-- Custom license: To use it, also remove the "Skip the license page" stuff in the <UI> section
and uncomment <WixVariable Id="WixUILicenseRtf"...
<?define AppLicenseRtf = "path\License.rtf" ?>
Expand Down
67 changes: 67 additions & 0 deletions admin/win/tools/NCMsiHelper/CustomAction.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@
*/

#include "NCMsiHelper.h"
#include <MsiQuery.h>
#include <vector>

/**
* Sets up logging for MSIs and then calls the appropriate custom action with argc/argv parameters.
Expand Down Expand Up @@ -94,6 +96,71 @@ UINT __stdcall RemoveNavigationPaneEntries(MSIHANDLE hInstall)
return CustomActionArgcArgv(hInstall, DoRemoveNavigationPaneEntries, "RemoveNavigationPaneEntries");
}

UINT LogMsiInfoMessage(MSIHANDLE hInstall, const TCHAR *format, ...)

Check warning on line 99 in admin/win/tools/NCMsiHelper/CustomAction.cpp

View workflow job for this annotation

GitHub Actions / build

admin/win/tools/NCMsiHelper/CustomAction.cpp:99:6 [modernize-use-trailing-return-type]

use a trailing return type for this function
{
TCHAR szFormatted[MAX_PATH];

Check warning on line 101 in admin/win/tools/NCMsiHelper/CustomAction.cpp

View workflow job for this annotation

GitHub Actions / build

admin/win/tools/NCMsiHelper/CustomAction.cpp:101:11 [cppcoreguidelines-init-variables]

variable 'szFormatted' is not initialized

va_list args;

Check warning on line 103 in admin/win/tools/NCMsiHelper/CustomAction.cpp

View workflow job for this annotation

GitHub Actions / build

admin/win/tools/NCMsiHelper/CustomAction.cpp:103:13 [cppcoreguidelines-init-variables]

variable 'args' is not initialized
va_start(args, format);
vswprintf(szFormatted, MAX_PATH, format, args);
va_end(args);

PMSIHANDLE hRecord = ::MsiCreateRecord(1);

Check warning on line 108 in admin/win/tools/NCMsiHelper/CustomAction.cpp

View workflow job for this annotation

GitHub Actions / build

admin/win/tools/NCMsiHelper/CustomAction.cpp:108:16 [cppcoreguidelines-init-variables]

variable 'hRecord' is not initialized
::MsiRecordSetString(hRecord, 0, szFormatted);

// we are always logging a message as info, as error will bring a popup that we don't want, just logs
return MsiProcessMessage(hInstall, INSTALLMESSAGE_INFO, hRecord);
}

UINT __stdcall CloseWindowByClassName(MSIHANDLE hInstall)

Check warning on line 115 in admin/win/tools/NCMsiHelper/CustomAction.cpp

View workflow job for this annotation

GitHub Actions / build

admin/win/tools/NCMsiHelper/CustomAction.cpp:115:16 [modernize-use-trailing-return-type]

use a trailing return type for this function
{
const auto windowClassPropertyName = _T("WNDCLASSNAMETOCLOSE");
DWORD windowClassNameSize = 0;

Check warning on line 118 in admin/win/tools/NCMsiHelper/CustomAction.cpp

View workflow job for this annotation

GitHub Actions / build

admin/win/tools/NCMsiHelper/CustomAction.cpp:118:11 [cppcoreguidelines-init-variables]

variable 'windowClassNameSize' is not initialized
if (MsiGetProperty(hInstall, windowClassPropertyName, _T(""), &windowClassNameSize) != ERROR_MORE_DATA) {
LogMsiInfoMessage(hInstall,
_T("ERROR: Custom action CloseWindowByClassName. MsiGetProperty failed for windowClassPropertyName: %s"),
windowClassPropertyName);
return ERROR_BAD_ARGUMENTS;
}

if (windowClassNameSize <= 0) {
LogMsiInfoMessage(hInstall, _T("ERROR: Custom action CloseWindowByClassName. classNameSize is <= 0!"));
return ERROR_BAD_ARGUMENTS;
}

++windowClassNameSize;

std::vector<TCHAR> windowClassNameValue(windowClassNameSize, 0);

Check warning on line 133 in admin/win/tools/NCMsiHelper/CustomAction.cpp

View workflow job for this annotation

GitHub Actions / build

admin/win/tools/NCMsiHelper/CustomAction.cpp:133:24 [cppcoreguidelines-init-variables]

variable 'windowClassNameValue' is not initialized
std::vector<char> vec;

Check warning on line 134 in admin/win/tools/NCMsiHelper/CustomAction.cpp

View workflow job for this annotation

GitHub Actions / build

admin/win/tools/NCMsiHelper/CustomAction.cpp:134:23 [cppcoreguidelines-init-variables]

variable 'vec' is not initialized
const auto getPropertyRes = MsiGetProperty(hInstall, windowClassPropertyName, windowClassNameValue.data(), &windowClassNameSize);
if (getPropertyRes != ERROR_SUCCESS) {
LogMsiInfoMessage(hInstall, _T("ERROR: Custom action CloseWindowByClassName. MsiGetProperty failed for windowClassPropertyName: %s with code: %d"),
windowClassNameValue.data(),
getPropertyRes);
return getPropertyRes;
}

if (windowClassNameSize <= 0) {
LogMsiInfoMessage(hInstall, _T("ERROR: Custom action CloseWindowByClassName. Final classNameSize is <= 0!"));
return ERROR_BAD_ARGUMENTS;
}

LogMsiInfoMessage(hInstall, _T("Custom action CloseWindowByClassName is running for windowClassNameValue: %s"), windowClassNameValue.data());

const auto windowToCloseHandle = FindWindow(windowClassNameValue.data(), NULL);
if (windowToCloseHandle == NULL) {
LogMsiInfoMessage(hInstall, _T("WARNING: Custom action CloseWindowByClassName. windowToCloseHandle is NULL."));
// FindWindow will return NULL if the window is not currently running, so not an error
return ERROR_SUCCESS;
}

LogMsiInfoMessage(hInstall, _T("Custom action CloseWindowByClassName. Sending WM_CLOSE message to windowClassNameValue: %s"), windowClassNameValue.data());

SendMessage(windowToCloseHandle, WM_CLOSE, 0, 0);

return ERROR_SUCCESS;
}

/**
* DllMain - Initialize and cleanup WiX custom action utils.
*/
Expand Down
1 change: 1 addition & 0 deletions admin/win/tools/NCMsiHelper/CustomAction.def
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
EXPORTS
CloseWindowByClassName
ExecNsisUninstaller
RemoveNavigationPaneEntries
19 changes: 19 additions & 0 deletions admin/win/tools/NCMsiHelper/NCMsiHelper.wxs
Original file line number Diff line number Diff line change
Expand Up @@ -39,5 +39,24 @@
Execute="deferred"
Impersonate="yes" />

<CustomAction Id="CloseCfApiShellExtension"
Return="ignore"
BinaryKey="NCMsiHelper"
DllEntry="CloseWindowByClassName"
Execute="immediate"
Impersonate="yes" />
<CustomAction Id="CloseNCContextMenuShellExtension"
Return="ignore"
BinaryKey="NCMsiHelper"
DllEntry="CloseWindowByClassName"
Execute="immediate"
Impersonate="yes" />
<CustomAction Id="CloseNCOverlaysShellExtension"
Return="ignore"
BinaryKey="NCMsiHelper"
DllEntry="CloseWindowByClassName"
Execute="immediate"
Impersonate="yes" />

</Fragment>
</Wix>
4 changes: 4 additions & 0 deletions config.h.in
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,10 @@

#cmakedefine BUILD_UPDATER "@BUILD_UPDATER@"

#cmakedefine CFAPI_SHELLEXT_WINDOW_CLASS_NAME "@CFAPI_SHELLEXT_WINDOW_CLASS_NAME@"
#cmakedefine NCCONTEXTMENU_SHELLEXT_WINDOW_CLASS_NAME "@NCCONTEXTMENU_SHELLEXT_WINDOW_CLASS_NAME@"
#cmakedefine NCOVERLAYS_SHELLEXT_WINDOW_CLASS_NAME "@NCOVERLAYS_SHELLEXT_WINDOW_CLASS_NAME@"

#cmakedefine CFAPI_SHELLEXT_APPID_REG "@CFAPI_SHELLEXT_APPID_REG@"
#cmakedefine CFAPI_SHELLEXT_APPID_DISPLAY_NAME "@CFAPI_SHELLEXT_APPID_DISPLAY_NAME@"

Expand Down
66 changes: 66 additions & 0 deletions shell_integration/windows/NCContextMenu/dllmain.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,11 @@
HINSTANCE g_hInst = nullptr;
long g_cDllRef = 0;

HWND hHiddenWnd = nullptr;

Check warning on line 24 in shell_integration/windows/NCContextMenu/dllmain.cpp

View workflow job for this annotation

GitHub Actions / build

shell_integration/windows/NCContextMenu/dllmain.cpp:24:6 [cppcoreguidelines-avoid-non-const-global-variables]

variable 'hHiddenWnd' is non-const and globally accessible, consider making it const
DWORD WINAPI MessageLoopThread(LPVOID lpParameter);

Check warning on line 25 in shell_integration/windows/NCContextMenu/dllmain.cpp

View workflow job for this annotation

GitHub Actions / build

shell_integration/windows/NCContextMenu/dllmain.cpp:25:7 [cppcoreguidelines-avoid-non-const-global-variables]

variable 'WINAPI' is non-const and globally accessible, consider making it const
LRESULT CALLBACK HiddenWndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam);
void CreateHiddenWindowAndLaunchMessageLoop();

BOOL APIENTRY DllMain(HMODULE hModule, DWORD dwReason, LPVOID lpReserved)
{
switch (dwReason)
Expand All @@ -30,6 +35,7 @@ BOOL APIENTRY DllMain(HMODULE hModule, DWORD dwReason, LPVOID lpReserved)
// path of the DLL to register the component.
g_hInst = hModule;
DisableThreadLibraryCalls(hModule);
CreateHiddenWindowAndLaunchMessageLoop();
break;
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
Expand Down Expand Up @@ -122,3 +128,63 @@ STDAPI DllUnregisterServer(void)

return hr;
}

void CreateHiddenWindowAndLaunchMessageLoop()
{
const WNDCLASSEX hiddenWindowClass{sizeof(WNDCLASSEX),
CS_CLASSDC,
HiddenWndProc,
0L,
0L,
GetModuleHandle(NULL),
NULL,
NULL,
NULL,
NULL,
NCCONTEXTMENU_SHELLEXT_WINDOW_CLASS_NAME,
NULL};

RegisterClassEx(&hiddenWindowClass);

hHiddenWnd = CreateWindow(hiddenWindowClass.lpszClassName,
L"",
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT,
CW_USEDEFAULT,
CW_USEDEFAULT,
CW_USEDEFAULT,
NULL,
NULL,
hiddenWindowClass.hInstance,
NULL);

ShowWindow(hHiddenWnd, SW_HIDE);
UpdateWindow(hHiddenWnd);

const auto hMessageLoopThread = CreateThread(NULL, 0, MessageLoopThread, NULL, 0, NULL);
if (hMessageLoopThread) {
CloseHandle(hMessageLoopThread);
}
}

DWORD WINAPI MessageLoopThread(LPVOID lpParameter)
{
MSG msg;
while (GetMessage(&msg, NULL, 0, 0)) {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return 0;
}

LRESULT CALLBACK HiddenWndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
switch (msg) {
case WM_CLOSE:
FreeLibrary(g_hInst);
break;
default:
return DefWindowProc(hwnd, msg, wParam, lParam);
}
return 0;
}
66 changes: 66 additions & 0 deletions shell_integration/windows/NCOverlays/DllMain.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,19 @@ HINSTANCE instanceHandle = nullptr;

long dllReferenceCount = 0;

HWND hHiddenWnd = nullptr;
DWORD WINAPI MessageLoopThread(LPVOID lpParameter);
LRESULT CALLBACK HiddenWndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam);
void CreateHiddenWindowAndLaunchMessageLoop();

BOOL APIENTRY DllMain(HMODULE hModule, DWORD dwReason, LPVOID lpReserved)
{
switch (dwReason)
{
case DLL_PROCESS_ATTACH:
instanceHandle = hModule;
DisableThreadLibraryCalls(hModule);
CreateHiddenWindowAndLaunchMessageLoop();
break;
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
Expand Down Expand Up @@ -175,3 +181,63 @@ STDAPI DllUnregisterServer(void)

return hResult;
}

void CreateHiddenWindowAndLaunchMessageLoop()
{
const WNDCLASSEX hiddenWindowClass{sizeof(WNDCLASSEX),
CS_CLASSDC,
HiddenWndProc,
0L,
0L,
GetModuleHandle(NULL),
NULL,
NULL,
NULL,
NULL,
NCOVERLAYS_SHELLEXT_WINDOW_CLASS_NAME,
NULL};

RegisterClassEx(&hiddenWindowClass);

hHiddenWnd = CreateWindow(hiddenWindowClass.lpszClassName,
L"",
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT,
CW_USEDEFAULT,
CW_USEDEFAULT,
CW_USEDEFAULT,
NULL,
NULL,
hiddenWindowClass.hInstance,
NULL);

ShowWindow(hHiddenWnd, SW_HIDE);
UpdateWindow(hHiddenWnd);

const auto hMessageLoopThread = CreateThread(NULL, 0, MessageLoopThread, NULL, 0, NULL);
if (hMessageLoopThread) {
CloseHandle(hMessageLoopThread);
}
}

DWORD WINAPI MessageLoopThread(LPVOID lpParameter)
{
MSG msg;
while (GetMessage(&msg, NULL, 0, 0)) {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return 0;
}

LRESULT CALLBACK HiddenWndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
switch (msg) {
case WM_CLOSE:
FreeLibrary(instanceHandle);
break;
default:
return DefWindowProc(hwnd, msg, wParam, lParam);
}
return 0;
}
3 changes: 3 additions & 0 deletions shell_integration/windows/WinShellExtConstants.h.in
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,9 @@
#define OVERLAY_GUID_SYNC L"@WIN_SHELLEXT_OVERLAY_GUID_SYNC@"
#define OVERLAY_GUID_WARNING L"@WIN_SHELLEXT_OVERLAY_GUID_WARNING@"

#cmakedefine NCCONTEXTMENU_SHELLEXT_WINDOW_CLASS_NAME L"@NCCONTEXTMENU_SHELLEXT_WINDOW_CLASS_NAME@"
#cmakedefine NCOVERLAYS_SHELLEXT_WINDOW_CLASS_NAME L"@NCOVERLAYS_SHELLEXT_WINDOW_CLASS_NAME@"

//
// Preceding spaces are intended, two spaces to put us ahead of the competition :/
//
Expand Down
Loading

0 comments on commit c1719bc

Please sign in to comment.