2024-04-03 14:27:35 +08:00
// CustomAction.cpp : Defines the entry point for the custom action.
# include "pch.h"
2024-04-11 12:06:10 +08:00
# include <stdlib.h>
2024-04-06 16:30:33 +08:00
# include <strutil.h>
2024-04-03 14:27:35 +08:00
# include <shellapi.h>
2024-04-11 11:51:35 +08:00
# include <tlhelp32.h>
# include <winternl.h>
2024-04-11 18:54:32 +08:00
# include <netfw.h>
# include <shlwapi.h>
# pragma comment(lib, "Shlwapi.lib")
2024-04-03 14:27:35 +08:00
UINT __stdcall CustomActionHello (
2024-04-11 11:51:35 +08:00
__in MSIHANDLE hInstall )
2024-04-03 14:27:35 +08:00
{
HRESULT hr = S_OK ;
DWORD er = ERROR_SUCCESS ;
hr = WcaInitialize ( hInstall , " CustomActionHello " ) ;
ExitOnFailure ( hr , " Failed to initialize " ) ;
WcaLog ( LOGMSG_STANDARD , " Initialized. " ) ;
// TODO: Add your custom action code here.
WcaLog ( LOGMSG_STANDARD , " ================= Example CustomAction Hello " ) ;
LExit :
er = SUCCEEDED ( hr ) ? ERROR_SUCCESS : ERROR_INSTALL_FAILURE ;
return WcaFinalize ( er ) ;
}
UINT __stdcall RemoveInstallFolder (
2024-04-11 11:51:35 +08:00
__in MSIHANDLE hInstall )
2024-04-03 14:27:35 +08:00
{
HRESULT hr = S_OK ;
DWORD er = ERROR_SUCCESS ;
int nResult = 0 ;
2024-04-06 16:30:33 +08:00
LPWSTR installFolder = NULL ;
LPWSTR pwz = NULL ;
LPWSTR pwzData = NULL ;
2024-04-03 14:27:35 +08:00
hr = WcaInitialize ( hInstall , " RemoveInstallFolder " ) ;
ExitOnFailure ( hr , " Failed to initialize " ) ;
2024-04-06 16:30:33 +08:00
hr = WcaGetProperty ( L " CustomActionData " , & pwzData ) ;
ExitOnFailure ( hr , " failed to get CustomActionData " ) ;
pwz = pwzData ;
hr = WcaReadStringFromCaData ( & pwz , & installFolder ) ;
ExitOnFailure ( hr , " failed to read database key from custom action data: %ls " , pwz ) ;
2024-04-03 14:27:35 +08:00
SHFILEOPSTRUCTW fileOp ;
2024-04-11 11:51:35 +08:00
ZeroMemory ( & fileOp , sizeof ( SHFILEOPSTRUCT ) ) ;
2024-04-03 14:27:35 +08:00
fileOp . wFunc = FO_DELETE ;
2024-04-06 16:30:33 +08:00
fileOp . pFrom = installFolder ;
2024-04-03 14:27:35 +08:00
fileOp . fFlags = FOF_NOCONFIRMATION | FOF_SILENT ;
2024-04-06 16:30:33 +08:00
nResult = SHFileOperation ( & fileOp ) ;
2024-04-03 14:27:35 +08:00
if ( nResult = = 0 )
{
2024-04-06 16:30:33 +08:00
WcaLog ( LOGMSG_STANDARD , " The directory \" %ls \" has been deleted. " , installFolder ) ;
2024-04-03 14:27:35 +08:00
}
else
{
2024-04-06 16:30:33 +08:00
WcaLog ( LOGMSG_STANDARD , " The directory \" %ls \" has not been deleted, error code: 0X%02X. Please refer to https://learn.microsoft.com/en-us/windows/win32/api/shellapi/nf-shellapi-shfileoperationa for the error codes. " , installFolder , nResult ) ;
2024-04-03 14:27:35 +08:00
}
LExit :
2024-04-06 16:30:33 +08:00
ReleaseStr ( installFolder ) ;
2024-04-03 14:27:35 +08:00
er = SUCCEEDED ( hr ) ? ERROR_SUCCESS : ERROR_INSTALL_FAILURE ;
return WcaFinalize ( er ) ;
}
2024-04-11 11:51:35 +08:00
// https://learn.microsoft.com/en-us/windows/win32/api/winternl/nf-winternl-ntqueryinformationprocess
// **NtQueryInformationProcess** may be altered or unavailable in future versions of Windows.
// Applications should use the alternate functions listed in this topic.
// But I do not find the alternate functions.
// https://github.com/heim-rs/heim/issues/105#issuecomment-683647573
typedef NTSTATUS ( NTAPI * pfnNtQueryInformationProcess ) ( HANDLE , PROCESSINFOCLASS , PVOID , ULONG , PULONG ) ;
bool TerminateProcessIfNotContainsParam ( pfnNtQueryInformationProcess NtQueryInformationProcess , HANDLE process , LPCWSTR excludeParam )
{
bool processClosed = false ;
PROCESS_BASIC_INFORMATION processInfo ;
NTSTATUS status = NtQueryInformationProcess ( process , ProcessBasicInformation , & processInfo , sizeof ( processInfo ) , NULL ) ;
if ( status = = 0 & & processInfo . PebBaseAddress ! = NULL )
{
PEB peb ;
SIZE_T dwBytesRead ;
if ( ReadProcessMemory ( process , processInfo . PebBaseAddress , & peb , sizeof ( peb ) , & dwBytesRead ) )
{
RTL_USER_PROCESS_PARAMETERS pebUpp ;
if ( ReadProcessMemory ( process ,
peb . ProcessParameters ,
& pebUpp ,
sizeof ( RTL_USER_PROCESS_PARAMETERS ) ,
& dwBytesRead ) )
{
if ( pebUpp . CommandLine . Length > 0 )
{
WCHAR * commandLine = ( WCHAR * ) malloc ( pebUpp . CommandLine . Length ) ;
if ( commandLine ! = NULL )
{
if ( ReadProcessMemory ( process , pebUpp . CommandLine . Buffer ,
commandLine , pebUpp . CommandLine . Length , & dwBytesRead ) )
{
if ( wcsstr ( commandLine , excludeParam ) = = NULL )
{
WcaLog ( LOGMSG_STANDARD , " Terminate process : %ls " , commandLine ) ;
TerminateProcess ( process , 0 ) ;
processClosed = true ;
}
}
free ( commandLine ) ;
}
}
}
}
}
return processClosed ;
}
// Terminate processes that do not have parameter [excludeParam]
// Note. This function relies on "NtQueryInformationProcess",
// which may not be found.
// Then all processes of [processName] will be terminated.
bool TerminateProcessesByNameW ( LPCWSTR processName , LPCWSTR excludeParam )
{
HMODULE hntdll = GetModuleHandleW ( L " ntdll.dll " ) ;
if ( hntdll = = NULL )
{
WcaLog ( LOGMSG_STANDARD , " Failed to load ntdll. " ) ;
}
pfnNtQueryInformationProcess NtQueryInformationProcess = NULL ;
if ( hntdll ! = NULL )
{
NtQueryInformationProcess = ( pfnNtQueryInformationProcess ) GetProcAddress (
hntdll , " NtQueryInformationProcess " ) ;
}
if ( NtQueryInformationProcess = = NULL )
{
WcaLog ( LOGMSG_STANDARD , " Failed to get address of NtQueryInformationProcess. " ) ;
}
bool processClosed = false ;
// Create a snapshot of the current system processes
HANDLE snapshot = CreateToolhelp32Snapshot ( TH32CS_SNAPPROCESS , 0 ) ;
if ( snapshot ! = INVALID_HANDLE_VALUE )
{
PROCESSENTRY32W processEntry ;
processEntry . dwSize = sizeof ( PROCESSENTRY32W ) ;
if ( Process32FirstW ( snapshot , & processEntry ) )
{
do
{
if ( lstrcmpW ( processName , processEntry . szExeFile ) = = 0 )
{
HANDLE process = OpenProcess ( PROCESS_TERMINATE | PROCESS_QUERY_INFORMATION | PROCESS_VM_READ , FALSE , processEntry . th32ProcessID ) ;
if ( process ! = NULL )
{
if ( NtQueryInformationProcess = = NULL )
{
WcaLog ( LOGMSG_STANDARD , " Terminate process : %ls, while NtQueryInformationProcess is NULL " , processName ) ;
TerminateProcess ( process , 0 ) ;
processClosed = true ;
}
else
{
processClosed = TerminateProcessIfNotContainsParam (
NtQueryInformationProcess ,
process ,
excludeParam ) ;
}
CloseHandle ( process ) ;
}
}
} while ( Process32Next ( snapshot , & processEntry ) ) ;
}
CloseHandle ( snapshot ) ;
}
if ( hntdll ! = NULL )
{
CloseHandle ( hntdll ) ;
}
return processClosed ;
}
UINT __stdcall TerminateProcesses (
__in MSIHANDLE hInstall )
{
HRESULT hr = S_OK ;
DWORD er = ERROR_SUCCESS ;
int nResult = 0 ;
wchar_t szProcess [ 256 ] = { 0 } ;
DWORD cchProcess = sizeof ( szProcess ) / sizeof ( szProcess [ 0 ] ) ;
hr = WcaInitialize ( hInstall , " TerminateProcesses " ) ;
ExitOnFailure ( hr , " Failed to initialize " ) ;
MsiGetPropertyW ( hInstall , L " TerminateProcesses " , szProcess , & cchProcess ) ;
WcaLog ( LOGMSG_STANDARD , " Try terminate processes : %ls " , szProcess ) ;
TerminateProcessesByNameW ( szProcess , L " --install " ) ;
LExit :
er = SUCCEEDED ( hr ) ? ERROR_SUCCESS : ERROR_INSTALL_FAILURE ;
return WcaFinalize ( er ) ;
}
2024-04-11 18:54:32 +08:00
// No use for now, it can be refer as an example of ShellExecuteW.
void AddFirewallRuleCmdline ( LPWSTR exeName , LPWSTR exeFile , LPCWSTR dir )
{
HRESULT hr = S_OK ;
HINSTANCE hi = 0 ;
WCHAR cmdline [ 1024 ] = { 0 , } ;
WCHAR rulename [ 500 ] = { 0 , } ;
StringCchPrintfW ( rulename , sizeof ( rulename ) / sizeof ( rulename [ 0 ] ) , L " %ls Service " , exeName ) ;
if ( hr < 0 ) {
WcaLog ( LOGMSG_STANDARD , " Failed to make rulename: %ls " , exeName ) ;
return ;
}
StringCchPrintfW ( cmdline , sizeof ( cmdline ) / sizeof ( cmdline [ 0 ] ) , L " advfirewall firewall add rule name= \" %ls \" dir=%ls action=allow program= \" %ls \" enable=yes " , rulename , dir , exeFile ) ;
if ( hr < 0 ) {
WcaLog ( LOGMSG_STANDARD , " Failed to make cmdline: %ls " , exeName ) ;
return ;
}
hi = ShellExecuteW ( NULL , L " open " , L " netsh " , cmdline , NULL , SW_HIDE ) ;
// https://learn.microsoft.com/en-us/windows/win32/api/shellapi/nf-shellapi-shellexecutew
if ( ( int ) hi < = 32 ) {
WcaLog ( LOGMSG_STANDARD , " Failed to change firewall rule : %d, last error: %d " , ( int ) hi , GetLastError ( ) ) ;
}
else {
WcaLog ( LOGMSG_STANDARD , " Firewall rule \" %ls \" (%ls) is added " , rulename , dir ) ;
}
}
// No use for now, it can be refer as an example of ShellExecuteW.
void RemoveFirewallRuleCmdline ( LPWSTR exeName )
{
HRESULT hr = S_OK ;
HINSTANCE hi = 0 ;
WCHAR cmdline [ 1024 ] = { 0 , } ;
WCHAR rulename [ 500 ] = { 0 , } ;
StringCchPrintfW ( rulename , sizeof ( rulename ) / sizeof ( rulename [ 0 ] ) , L " %ls Service " , exeName ) ;
if ( hr < 0 ) {
WcaLog ( LOGMSG_STANDARD , " Failed to make rulename: %ls " , exeName ) ;
return ;
}
StringCchPrintfW ( cmdline , sizeof ( cmdline ) / sizeof ( cmdline [ 0 ] ) , L " advfirewall firewall delete rule name= \" %ls \" " , rulename ) ;
if ( hr < 0 ) {
WcaLog ( LOGMSG_STANDARD , " Failed to make cmdline: %ls " , exeName ) ;
return ;
}
hi = ShellExecuteW ( NULL , L " open " , L " netsh " , cmdline , NULL , SW_HIDE ) ;
// https://learn.microsoft.com/en-us/windows/win32/api/shellapi/nf-shellapi-shellexecutew
if ( ( int ) hi < = 32 ) {
WcaLog ( LOGMSG_STANDARD , " Failed to change firewall rule \" %ls \" : %d, last error: %d " , rulename , ( int ) hi , GetLastError ( ) ) ;
}
else {
WcaLog ( LOGMSG_STANDARD , " Firewall rule \" %ls \" is removed " , rulename ) ;
}
}
bool AddFirewallRule ( bool add , LPWSTR exeName , LPWSTR exeFile ) ;
UINT __stdcall AddFirewallRules (
__in MSIHANDLE hInstall )
{
HRESULT hr = S_OK ;
DWORD er = ERROR_SUCCESS ;
int nResult = 0 ;
LPWSTR exeFile = NULL ;
LPWSTR exeName = NULL ;
WCHAR exeNameNoExt [ 500 ] = { 0 , } ;
LPWSTR pwz = NULL ;
LPWSTR pwzData = NULL ;
size_t szNameLen = 0 ;
hr = WcaInitialize ( hInstall , " AddFirewallExceptions " ) ;
ExitOnFailure ( hr , " Failed to initialize " ) ;
hr = WcaGetProperty ( L " CustomActionData " , & pwzData ) ;
ExitOnFailure ( hr , " failed to get CustomActionData " ) ;
pwz = pwzData ;
hr = WcaReadStringFromCaData ( & pwz , & exeFile ) ;
ExitOnFailure ( hr , " failed to read database key from custom action data: %ls " , pwz ) ;
WcaLog ( LOGMSG_STANDARD , " Try add firewall exceptions for file : %ls " , exeFile ) ;
exeName = PathFindFileNameW ( exeFile + 1 ) ;
hr = StringCchPrintfW ( exeNameNoExt , 500 , exeName ) ;
ExitOnFailure ( hr , " Failed to copy exe name: %ls " , exeName ) ;
szNameLen = wcslen ( exeNameNoExt ) ;
if ( szNameLen > = 4 & & wcscmp ( exeNameNoExt + szNameLen - 4 , L " .exe " ) = = 0 ) {
exeNameNoExt [ szNameLen - 4 ] = L ' \0 ' ;
}
//if (exeFile[0] == L'1') {
// AddFirewallRuleCmdline(exeNameNoExt, exeFile, L"in");
// AddFirewallRuleCmdline(exeNameNoExt, exeFile, L"out");
//}
//else {
// RemoveFirewallRuleCmdline(exeNameNoExt);
//}
AddFirewallRule ( exeFile [ 0 ] = = L ' 1 ' , exeNameNoExt , exeFile + 1 ) ;
LExit :
if ( pwzData ) {
ReleaseStr ( pwzData ) ;
}
er = SUCCEEDED ( hr ) ? ERROR_SUCCESS : ERROR_INSTALL_FAILURE ;
return WcaFinalize ( er ) ;
}