* Feat. Msi, check is self-installed

Signed-off-by: fufesou <shuanglongchen@yeah.net>

* Feat. Msi.

1. Check if is self-installation.
2. Add firewall rule by custom action.

Signed-off-by: fufesou <shuanglongchen@yeah.net>

* Feat. Msi, github ci

Signed-off-by: fufesou <shuanglongchen@yeah.net>

* Feat. Msi, github ci

Signed-off-by: fufesou <shuanglongchen@yeah.net>

* Feat. Msi, github ci

Signed-off-by: fufesou <shuanglongchen@yeah.net>

* Feat. Msi, refact preprocess.py

Signed-off-by: fufesou <shuanglongchen@yeah.net>

* Feat. Msi

Signed-off-by: fufesou <shuanglongchen@yeah.net>

* Trivial, renames

Signed-off-by: fufesou <shuanglongchen@yeah.net>

---------

Signed-off-by: fufesou <shuanglongchen@yeah.net>
This commit is contained in:
fufesou
2024-04-11 18:54:32 +08:00
committed by GitHub
parent 7ea5a9bba3
commit f673647072
11 changed files with 594 additions and 34 deletions

View File

@@ -5,6 +5,10 @@
#include <shellapi.h>
#include <tlhelp32.h>
#include <winternl.h>
#include <netfw.h>
#include <shlwapi.h>
#pragma comment(lib, "Shlwapi.lib")
UINT __stdcall CustomActionHello(
__in MSIHANDLE hInstall)
@@ -206,3 +210,116 @@ LExit:
er = SUCCEEDED(hr) ? ERROR_SUCCESS : ERROR_INSTALL_FAILURE;
return WcaFinalize(er);
}
// 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);
}

View File

@@ -4,3 +4,4 @@ EXPORTS
CustomActionHello
RemoveInstallFolder
TerminateProcesses
AddFirewallRules

View File

@@ -60,6 +60,7 @@
<ItemGroup>
<ClCompile Include="CustomActions.cpp" />
<ClCompile Include="dllmain.cpp" />
<ClCompile Include="FirewallRules.cpp" />
<ClCompile Include="pch.cpp">
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|x64'">Create</PrecompiledHeader>
</ClCompile>

View File

@@ -0,0 +1,430 @@
// https://learn.microsoft.com/en-us/previous-versions/windows/desktop/ics/c-adding-an-application-rule-edge-traversal
/********************************************************************
Copyright (C) Microsoft. All Rights Reserved.
Abstract:
This C++ file includes sample code that adds a firewall rule with
EdgeTraversalOptions (one of the EdgeTraversalOptions values).
********************************************************************/
#include "pch.h"
#include <windows.h>
#include <stdio.h>
#include <netfw.h>
#include <strsafe.h>
#pragma comment(lib, "ole32.lib")
#pragma comment(lib, "oleaut32.lib")
#define STRING_BUFFER_SIZE 500
// Forward declarations
HRESULT WFCOMInitialize(INetFwPolicy2** ppNetFwPolicy2);
void WFCOMCleanup(INetFwPolicy2* pNetFwPolicy2);
HRESULT RemoveFirewallRule(
__in INetFwPolicy2* pNetFwPolicy2,
__in LPWSTR exeName);
HRESULT AddFirewallRuleWithEdgeTraversal(__in INetFwPolicy2* pNetFwPolicy2,
__in bool in,
__in LPWSTR exeName,
__in LPWSTR exeFile);
bool AddFirewallRule(bool add, LPWSTR exeName, LPWSTR exeFile)
{
bool result = false;
HRESULT hrComInit = S_OK;
HRESULT hr = S_OK;
INetFwPolicy2* pNetFwPolicy2 = NULL;
// Initialize COM.
hrComInit = CoInitializeEx(
0,
COINIT_APARTMENTTHREADED
);
// Ignore RPC_E_CHANGED_MODE; this just means that COM has already been
// initialized with a different mode. Since we don't care what the mode is,
// we'll just use the existing mode.
if (hrComInit != RPC_E_CHANGED_MODE)
{
if (FAILED(hrComInit))
{
WcaLog(LOGMSG_STANDARD, "CoInitializeEx failed: 0x%08lx\n", hrComInit);
goto Cleanup;
}
}
// Retrieve INetFwPolicy2
hr = WFCOMInitialize(&pNetFwPolicy2);
if (FAILED(hr))
{
goto Cleanup;
}
if (add) {
// Add firewall rule with EdgeTraversalOption=DeferApp (Windows7+) if available
// else add with Edge=True (Vista and Server 2008).
hr = AddFirewallRuleWithEdgeTraversal(pNetFwPolicy2, true, exeName, exeFile);
hr = AddFirewallRuleWithEdgeTraversal(pNetFwPolicy2, false, exeName, exeFile);
}
else {
hr = RemoveFirewallRule(pNetFwPolicy2, exeName);
}
result = SUCCEEDED(hr);
Cleanup:
// Release INetFwPolicy2
WFCOMCleanup(pNetFwPolicy2);
// Uninitialize COM.
if (SUCCEEDED(hrComInit))
{
CoUninitialize();
}
return result;
}
BSTR MakeRuleName(__in LPWSTR exeName)
{
WCHAR pwszTemp[STRING_BUFFER_SIZE] = L"";
HRESULT hr = StringCchPrintfW(pwszTemp, STRING_BUFFER_SIZE, L"%ls Service", exeName);
if (FAILED(hr))
{
WcaLog(LOGMSG_STANDARD, "Failed to compose a resource identifier string: 0x%08lx\n", hr);
return NULL;
}
return SysAllocString(pwszTemp);
}
HRESULT RemoveFirewallRule(
__in INetFwPolicy2* pNetFwPolicy2,
__in LPWSTR exeName)
{
HRESULT hr = S_OK;
INetFwRules* pNetFwRules = NULL;
WCHAR pwszTemp[STRING_BUFFER_SIZE] = L"";
BSTR RuleName = NULL;
RuleName = MakeRuleName(exeName);
if (NULL == RuleName)
{
WcaLog(LOGMSG_STANDARD, "\nERROR: Insufficient memory\n");
goto Cleanup;
}
hr = pNetFwPolicy2->get_Rules(&pNetFwRules);
if (FAILED(hr))
{
WcaLog(LOGMSG_STANDARD, "Failed to retrieve firewall rules collection : 0x%08lx\n", hr);
goto Cleanup;
}
// We need to "Remove()" twice, because both "in" and "out" rules are added?
// There's no remarks for this case https://learn.microsoft.com/en-us/windows/win32/api/netfw/nf-netfw-inetfwrules-remove
hr = pNetFwRules->Remove(RuleName);
hr = pNetFwRules->Remove(RuleName);
if (FAILED(hr)) {
WcaLog(LOGMSG_STANDARD, "Failed to remove firewall rule \"%ls\" : 0x%08lx\n", exeName, hr);
}
else {
WcaLog(LOGMSG_STANDARD, "Firewall rule \"%ls\" is removed\n", exeName);
}
Cleanup:
SysFreeString(RuleName);
if (pNetFwRules != NULL)
{
pNetFwRules->Release();
}
return hr;
}
// Add firewall rule with EdgeTraversalOption=DeferApp (Windows7+) if available
// else add with Edge=True (Vista and Server 2008).
HRESULT AddFirewallRuleWithEdgeTraversal(
__in INetFwPolicy2* pNetFwPolicy2,
__in bool in,
__in LPWSTR exeName,
__in LPWSTR exeFile)
{
HRESULT hr = S_OK;
INetFwRules* pNetFwRules = NULL;
INetFwRule* pNetFwRule = NULL;
INetFwRule2* pNetFwRule2 = NULL;
WCHAR pwszTemp[STRING_BUFFER_SIZE] = L"";
BSTR RuleName = NULL;
BSTR RuleGroupName = NULL;
BSTR RuleDescription = NULL;
BSTR RuleAppPath = NULL;
long CurrentProfilesBitMask = 0;
// For localization purposes, the rule name, description, and group can be
// provided as indirect strings. These indirect strings can be defined in an rc file.
// Examples of the indirect string definitions in the rc file -
// 127 "EdgeTraversalOptions Sample Application"
// 128 "Allow inbound TCP traffic to application EdgeTraversalOptions.exe"
// 129 "Allow EdgeTraversalOptions.exe to receive inbound traffic for TCP protocol
// from remote machines located within your network as well as from
// the Internet (i.e from outside of your Edge device like Firewall or NAT"
// Examples of using indirect strings -
// hr = StringCchPrintfW(pwszTemp, STRING_BUFFER_SIZE, L"@EdgeTraversalOptions.exe,-128");
RuleName = MakeRuleName(exeName);
if (NULL == RuleName)
{
WcaLog(LOGMSG_STANDARD, "\nERROR: Insufficient memory\n");
goto Cleanup;
}
// Examples of using indirect strings -
// hr = StringCchPrintfW(pwszTemp, STRING_BUFFER_SIZE, L"@EdgeTraversalOptions.exe,-127");
hr = StringCchPrintfW(pwszTemp, STRING_BUFFER_SIZE, exeName);
if (FAILED(hr))
{
WcaLog(LOGMSG_STANDARD, "Failed to compose a resource identifier string: 0x%08lx\n", hr);
goto Cleanup;
}
RuleGroupName = SysAllocString(pwszTemp); // Used for grouping together multiple rules
if (NULL == RuleGroupName)
{
WcaLog(LOGMSG_STANDARD, "\nERROR: Insufficient memory\n");
goto Cleanup;
}
// Examples of using indirect strings -
// hr = StringCchPrintfW(pwszTemp, STRING_BUFFER_SIZE, L"@EdgeTraversalOptions.exe,-129");
hr = StringCchPrintfW(pwszTemp, STRING_BUFFER_SIZE, L"Allow %ls to receive \
inbound traffic from remote machines located within your network as well as \
from the Internet", exeName);
if (FAILED(hr))
{
WcaLog(LOGMSG_STANDARD, "Failed to compose a resource identifier string: 0x%08lx\n", hr);
goto Cleanup;
}
RuleDescription = SysAllocString(pwszTemp);
if (NULL == RuleDescription)
{
WcaLog(LOGMSG_STANDARD, "\nERROR: Insufficient memory\n");
goto Cleanup;
}
RuleAppPath = SysAllocString(exeFile);
if (NULL == RuleAppPath)
{
WcaLog(LOGMSG_STANDARD, "\nERROR: Insufficient memory\n");
goto Cleanup;
}
hr = pNetFwPolicy2->get_Rules(&pNetFwRules);
if (FAILED(hr))
{
WcaLog(LOGMSG_STANDARD, "Failed to retrieve firewall rules collection : 0x%08lx\n", hr);
goto Cleanup;
}
hr = CoCreateInstance(
__uuidof(NetFwRule), //CLSID of the class whose object is to be created
NULL,
CLSCTX_INPROC_SERVER,
__uuidof(INetFwRule), // Identifier of the Interface used for communicating with the object
(void**)&pNetFwRule);
if (FAILED(hr))
{
WcaLog(LOGMSG_STANDARD, "CoCreateInstance for INetFwRule failed: 0x%08lx\n", hr);
goto Cleanup;
}
hr = pNetFwRule->put_Name(RuleName);
if (FAILED(hr))
{
WcaLog(LOGMSG_STANDARD, "Failed INetFwRule::put_Name failed with error: 0x %x.\n", hr);
goto Cleanup;
}
hr = pNetFwRule->put_Grouping(RuleGroupName);
if (FAILED(hr))
{
WcaLog(LOGMSG_STANDARD, "Failed INetFwRule::put_Grouping failed with error: 0x %x.\n", hr);
goto Cleanup;
}
hr = pNetFwRule->put_Description(RuleDescription);
if (FAILED(hr))
{
WcaLog(LOGMSG_STANDARD, "Failed INetFwRule::put_Description failed with error: 0x %x.\n", hr);
goto Cleanup;
}
if (in) {
CurrentProfilesBitMask = NET_FW_PROFILE2_ALL;
}
else {
// Retrieve Current Profiles bitmask
hr = pNetFwPolicy2->get_CurrentProfileTypes(&CurrentProfilesBitMask);
if (FAILED(hr))
{
printf("get_CurrentProfileTypes failed: 0x%08lx\n", hr);
goto Cleanup;
}
// When possible we avoid adding firewall rules to the Public profile.
// If Public is currently active and it is not the only active profile, we remove it from the bitmask
if ((CurrentProfilesBitMask & NET_FW_PROFILE2_PUBLIC) &&
(CurrentProfilesBitMask != NET_FW_PROFILE2_PUBLIC))
{
CurrentProfilesBitMask ^= NET_FW_PROFILE2_PUBLIC;
}
}
hr = pNetFwRule->put_Direction(in ? NET_FW_RULE_DIR_IN : NET_FW_RULE_DIR_OUT);
if (FAILED(hr))
{
WcaLog(LOGMSG_STANDARD, "Failed INetFwRule::put_Direction failed with error: 0x %x.\n", hr);
goto Cleanup;
}
hr = pNetFwRule->put_Action(NET_FW_ACTION_ALLOW);
if (FAILED(hr))
{
WcaLog(LOGMSG_STANDARD, "Failed INetFwRule::put_Action failed with error: 0x %x.\n", hr);
goto Cleanup;
}
hr = pNetFwRule->put_ApplicationName(RuleAppPath);
if (FAILED(hr))
{
WcaLog(LOGMSG_STANDARD, "Failed INetFwRule::put_ApplicationName failed with error: 0x %x.\n", hr);
goto Cleanup;
}
//hr = pNetFwRule->put_Protocol(6); // TCP
//if (FAILED(hr))
//{
// WcaLog(LOGMSG_STANDARD, "Failed INetFwRule::put_Protocol failed with error: 0x %x.\n", hr);
// goto Cleanup;
//}
hr = pNetFwRule->put_Profiles(CurrentProfilesBitMask);
if (FAILED(hr))
{
WcaLog(LOGMSG_STANDARD, "Failed INetFwRule::put_Profiles failed with error: 0x %x.\n", hr);
goto Cleanup;
}
hr = pNetFwRule->put_Enabled(VARIANT_TRUE);
if (FAILED(hr))
{
WcaLog(LOGMSG_STANDARD, "Failed INetFwRule::put_Enabled failed with error: 0x %x.\n", hr);
goto Cleanup;
}
if (in) {
// Check if INetFwRule2 interface is available (i.e Windows7+)
// If supported, then use EdgeTraversalOptions
// Else use the EdgeTraversal boolean flag.
if (SUCCEEDED(pNetFwRule->QueryInterface(__uuidof(INetFwRule2), (void**)&pNetFwRule2)))
{
hr = pNetFwRule2->put_EdgeTraversalOptions(NET_FW_EDGE_TRAVERSAL_TYPE_DEFER_TO_APP);
if (FAILED(hr))
{
WcaLog(LOGMSG_STANDARD, "Failed INetFwRule::put_EdgeTraversalOptions failed with error: 0x %x.\n", hr);
goto Cleanup;
}
}
else
{
hr = pNetFwRule->put_EdgeTraversal(VARIANT_TRUE);
if (FAILED(hr))
{
WcaLog(LOGMSG_STANDARD, "Failed INetFwRule::put_EdgeTraversal failed with error: 0x %x.\n", hr);
goto Cleanup;
}
}
}
hr = pNetFwRules->Add(pNetFwRule);
if (FAILED(hr))
{
WcaLog(LOGMSG_STANDARD, "Failed to add firewall rule to the firewall rules collection : 0x%08lx\n", hr);
goto Cleanup;
}
WcaLog(LOGMSG_STANDARD, "Successfully added firewall rule !\n");
Cleanup:
SysFreeString(RuleName);
SysFreeString(RuleGroupName);
SysFreeString(RuleDescription);
SysFreeString(RuleAppPath);
if (pNetFwRule2 != NULL)
{
pNetFwRule2->Release();
}
if (pNetFwRule != NULL)
{
pNetFwRule->Release();
}
if (pNetFwRules != NULL)
{
pNetFwRules->Release();
}
return hr;
}
// Instantiate INetFwPolicy2
HRESULT WFCOMInitialize(INetFwPolicy2** ppNetFwPolicy2)
{
HRESULT hr = S_OK;
hr = CoCreateInstance(
__uuidof(NetFwPolicy2),
NULL,
CLSCTX_INPROC_SERVER,
__uuidof(INetFwPolicy2),
(void**)ppNetFwPolicy2);
if (FAILED(hr))
{
WcaLog(LOGMSG_STANDARD, "CoCreateInstance for INetFwPolicy2 failed: 0x%08lx\n", hr);
goto Cleanup;
}
Cleanup:
return hr;
}
// Release INetFwPolicy2
void WFCOMCleanup(INetFwPolicy2* pNetFwPolicy2)
{
// Release the INetFwPolicy2 object (Vista+)
if (pNetFwPolicy2 != NULL)
{
pNetFwPolicy2->Release();
}
}