Initial commit: RustDesk 1.4.7 macOS desktop port
- Filled empty libs/hbb_common/ submodule (cloned from rustdesk/hbb_common) - Patched Flutter 3.44 / Dart 3.12 compatibility: * flutter/lib/generated_bridge.dart: asTypedList with cast<>, DartPort=Int64 * flutter/lib/common.dart: DialogTheme->DialogThemeData, TabBarTheme->TabBarThemeData * flutter/pubspec.yaml: extended_text 14.0.0->15.0.2, google_fonts override 5.0.0 * flutter/macos/Runner/Configs/Release.xcconfig: EXCLUDED_ARCHS=x86_64 - Build verified: cargo check + cargo build --features flutter + cargo build --release --features flutter - Verified flutter build macos --debug and --release both produce working .app - Verified .dmg installer (27MB arm64) created via hdiutil - Build deps: Xcode 26.5, CocoaPods 1.16.2, Flutter 3.44, VCPKG arm64-osx
This commit is contained in:
@@ -0,0 +1,23 @@
|
||||
#pragma once
|
||||
|
||||
#include <Windows.h>
|
||||
#include <string>
|
||||
|
||||
bool AddFirewallRule(bool add, LPWSTR exeName, LPWSTR exeFile);
|
||||
|
||||
bool QueryServiceStatusExW(LPCWSTR serviceName, SERVICE_STATUS_PROCESS* status);
|
||||
bool IsServiceRunningW(LPCWSTR serviceName);
|
||||
bool MyCreateServiceW(LPCWSTR serviceName, LPCWSTR displayName, LPCWSTR binaryPath);
|
||||
bool MyDeleteServiceW(LPCWSTR serviceName);
|
||||
bool MyStartServiceW(LPCWSTR serviceName);
|
||||
bool MyStopServiceW(LPCWSTR serviceName);
|
||||
|
||||
std::wstring ReadConfig(const std::wstring& filename, const std::wstring& key);
|
||||
|
||||
void UninstallDriver(LPCWSTR hardwareId, BOOL &rebootRequired);
|
||||
|
||||
namespace RemotePrinter
|
||||
{
|
||||
VOID installUpdatePrinter(const std::wstring& installFolder);
|
||||
VOID uninstallPrinter();
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,16 @@
|
||||
LIBRARY "CustomActions"
|
||||
|
||||
EXPORTS
|
||||
CustomActionHello
|
||||
RemoveRuntimeGeneratedFiles
|
||||
TerminateProcesses
|
||||
AddFirewallRules
|
||||
SetPropertyIsServiceRunning
|
||||
TryStopDeleteService
|
||||
CreateStartService
|
||||
TryDeleteStartupShortcut
|
||||
SetPropertyFromConfig
|
||||
AddRegSoftwareSASGeneration
|
||||
RemoveAmyuniIdd
|
||||
InstallPrinter
|
||||
UninstallPrinter
|
||||
@@ -0,0 +1,86 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<Import Project="..\packages\WixToolset.WcaUtil.4.0.5\build\WixToolset.WcaUtil.props" Condition="Exists('..\packages\WixToolset.WcaUtil.4.0.5\build\WixToolset.WcaUtil.props')" />
|
||||
<Import Project="..\packages\WixToolset.DUtil.4.0.5\build\WixToolset.DUtil.props" Condition="Exists('..\packages\WixToolset.DUtil.4.0.5\build\WixToolset.DUtil.props')" />
|
||||
<ItemGroup Label="ProjectConfigurations">
|
||||
<ProjectConfiguration Include="Release|x64">
|
||||
<Configuration>Release</Configuration>
|
||||
<Platform>x64</Platform>
|
||||
</ProjectConfiguration>
|
||||
</ItemGroup>
|
||||
<PropertyGroup Label="Globals">
|
||||
<Keyword>Win32Proj</Keyword>
|
||||
<ProjectGuid>{6b3647e0-b4a3-46ae-8757-a22ee51c1dac}</ProjectGuid>
|
||||
<RootNamespace>CustomActions</RootNamespace>
|
||||
<PlatformToolset>v143</PlatformToolset>
|
||||
<WindowsTargetPlatformVersion>10.0</WindowsTargetPlatformVersion>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
|
||||
<ConfigurationType>DynamicLibrary</ConfigurationType>
|
||||
<UseDebugLibraries>false</UseDebugLibraries>
|
||||
<WholeProgramOptimization>true</WholeProgramOptimization>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
|
||||
<ImportGroup Label="ExtensionSettings">
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="Shared">
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<PropertyGroup Label="UserMacros" />
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
||||
<ClCompile>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<FunctionLevelLinking>true</FunctionLevelLinking>
|
||||
<IntrinsicFunctions>true</IntrinsicFunctions>
|
||||
<SDLCheck>true</SDLCheck>
|
||||
<PreprocessorDefinitions>NDEBUG;EXAMPLECADLL_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<ConformanceMode>true</ConformanceMode>
|
||||
<PrecompiledHeader>Use</PrecompiledHeader>
|
||||
<PrecompiledHeaderFile>pch.h</PrecompiledHeaderFile>
|
||||
<RuntimeLibrary>MultiThreaded</RuntimeLibrary>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<AdditionalDependencies>msi.lib;version.lib;%(AdditionalDependencies)</AdditionalDependencies>
|
||||
<SubSystem>Windows</SubSystem>
|
||||
<EnableCOMDATFolding>true</EnableCOMDATFolding>
|
||||
<OptimizeReferences>true</OptimizeReferences>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
<EnableUAC>false</EnableUAC>
|
||||
<ModuleDefinitionFile>CustomActions.def</ModuleDefinitionFile>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="Common.h" />
|
||||
<ClInclude Include="framework.h" />
|
||||
<ClInclude Include="pch.h" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="CustomActions.cpp" />
|
||||
<ClCompile Include="DeviceUtils.cpp" />
|
||||
<ClCompile Include="dllmain.cpp" />
|
||||
<ClCompile Include="FirewallRules.cpp" />
|
||||
<ClCompile Include="pch.cpp">
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|x64'">Create</PrecompiledHeader>
|
||||
</ClCompile>
|
||||
<ClCompile Include="ReadConfig.cpp" />
|
||||
<ClCompile Include="RemotePrinter.cpp" />
|
||||
<ClCompile Include="ServiceUtils.cpp" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="CustomActions.def" />
|
||||
<None Include="packages.config" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
|
||||
<ImportGroup Label="ExtensionTargets" />
|
||||
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
|
||||
<PropertyGroup>
|
||||
<ErrorText>This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
|
||||
</PropertyGroup>
|
||||
<Error Condition="!Exists('..\packages\WixToolset.DUtil.4.0.5\build\WixToolset.DUtil.props')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\WixToolset.DUtil.4.0.5\build\WixToolset.DUtil.props'))" />
|
||||
<Error Condition="!Exists('..\packages\WixToolset.WcaUtil.4.0.5\build\WixToolset.WcaUtil.props')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\WixToolset.WcaUtil.4.0.5\build\WixToolset.WcaUtil.props'))" />
|
||||
</Target>
|
||||
</Project>
|
||||
@@ -0,0 +1,84 @@
|
||||
#include "pch.h"
|
||||
|
||||
#include <Windows.h>
|
||||
#include <setupapi.h>
|
||||
#include <devguid.h>
|
||||
#include <cfgmgr32.h>
|
||||
|
||||
#pragma comment(lib, "SetupAPI.lib")
|
||||
|
||||
|
||||
void UninstallDriver(LPCWSTR hardwareId, BOOL &rebootRequired)
|
||||
{
|
||||
HDEVINFO deviceInfoSet = SetupDiGetClassDevsW(&GUID_DEVCLASS_DISPLAY, NULL, NULL, DIGCF_PRESENT);
|
||||
if (deviceInfoSet == INVALID_HANDLE_VALUE)
|
||||
{
|
||||
WcaLog(LOGMSG_STANDARD, "Failed to get device information set, last error: %d", GetLastError());
|
||||
return;
|
||||
}
|
||||
|
||||
SP_DEVINFO_LIST_DETAIL_DATA devInfoListDetail;
|
||||
devInfoListDetail.cbSize = sizeof(SP_DEVINFO_LIST_DETAIL_DATA);
|
||||
if (!SetupDiGetDeviceInfoListDetailW(deviceInfoSet, &devInfoListDetail))
|
||||
{
|
||||
SetupDiDestroyDeviceInfoList(deviceInfoSet);
|
||||
WcaLog(LOGMSG_STANDARD, "Failed to call SetupDiGetDeviceInfoListDetail, last error: %d", GetLastError());
|
||||
return;
|
||||
}
|
||||
|
||||
SP_DEVINFO_DATA deviceInfoData;
|
||||
deviceInfoData.cbSize = sizeof(SP_DEVINFO_DATA);
|
||||
|
||||
DWORD dataType;
|
||||
WCHAR deviceId[MAX_DEVICE_ID_LEN] = { 0, };
|
||||
|
||||
DWORD deviceIndex = 0;
|
||||
while (SetupDiEnumDeviceInfo(deviceInfoSet, deviceIndex, &deviceInfoData))
|
||||
{
|
||||
if (!SetupDiGetDeviceRegistryPropertyW(deviceInfoSet, &deviceInfoData, SPDRP_HARDWAREID, &dataType, (PBYTE)deviceId, MAX_DEVICE_ID_LEN, NULL))
|
||||
{
|
||||
WcaLog(LOGMSG_STANDARD, "Failed to get hardware id, last error: %d", GetLastError());
|
||||
deviceIndex++;
|
||||
continue;
|
||||
}
|
||||
if (wcscmp(deviceId, hardwareId) != 0)
|
||||
{
|
||||
deviceIndex++;
|
||||
continue;
|
||||
}
|
||||
|
||||
SP_REMOVEDEVICE_PARAMS remove_device_params;
|
||||
remove_device_params.ClassInstallHeader.cbSize = sizeof(SP_CLASSINSTALL_HEADER);
|
||||
remove_device_params.ClassInstallHeader.InstallFunction = DIF_REMOVE;
|
||||
remove_device_params.Scope = DI_REMOVEDEVICE_GLOBAL;
|
||||
remove_device_params.HwProfile = 0;
|
||||
|
||||
if (!SetupDiSetClassInstallParamsW(deviceInfoSet, &deviceInfoData, &remove_device_params.ClassInstallHeader, sizeof(SP_REMOVEDEVICE_PARAMS)))
|
||||
{
|
||||
WcaLog(LOGMSG_STANDARD, "Failed to set class install params, last error: %d", GetLastError());
|
||||
deviceIndex++;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!SetupDiCallClassInstaller(DIF_REMOVE, deviceInfoSet, &deviceInfoData))
|
||||
{
|
||||
WcaLog(LOGMSG_STANDARD, "ailed to uninstall driver, last error: %d", GetLastError());
|
||||
deviceIndex++;
|
||||
continue;
|
||||
}
|
||||
|
||||
SP_DEVINSTALL_PARAMS deviceParams;
|
||||
if (SetupDiGetDeviceInstallParamsW(deviceInfoSet, &deviceInfoData, &deviceParams))
|
||||
{
|
||||
if (deviceParams.Flags & (DI_NEEDRESTART | DI_NEEDREBOOT))
|
||||
{
|
||||
rebootRequired = true;
|
||||
}
|
||||
}
|
||||
|
||||
WcaLog(LOGMSG_STANDARD, "Driver uninstalled successfully");
|
||||
deviceIndex++;
|
||||
}
|
||||
|
||||
SetupDiDestroyDeviceInfoList(deviceInfoSet);
|
||||
}
|
||||
@@ -0,0 +1,413 @@
|
||||
// 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 you want the rule to avoid public, you can refer to
|
||||
// https://learn.microsoft.com/en-us/previous-versions/windows/desktop/ics/c-adding-an-outbound-rule
|
||||
CurrentProfilesBitMask = NET_FW_PROFILE2_ALL;
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
#include "pch.h"
|
||||
|
||||
#include <iostream>
|
||||
#include <fstream>
|
||||
#include <string>
|
||||
#include <cwctype>
|
||||
|
||||
void trim(std::wstring& str) {
|
||||
str.erase(str.begin(), std::find_if(str.begin(), str.end(), [](wchar_t ch) {
|
||||
return !std::iswspace(ch);
|
||||
}));
|
||||
str.erase(std::find_if(str.rbegin(), str.rend(), [](wchar_t ch) {
|
||||
return !std::iswspace(ch);
|
||||
}).base(), str.end());
|
||||
}
|
||||
|
||||
std::wstring ReadConfig(const std::wstring& filename, const std::wstring& key)
|
||||
{
|
||||
std::wstring configValue;
|
||||
std::wstring line;
|
||||
std::wifstream file(filename);
|
||||
while (std::getline(file, line)) {
|
||||
trim(line);
|
||||
if (line.find(key) == 0) {
|
||||
std::size_t position = line.find(L"=", key.size());
|
||||
if (position != std::string::npos) {
|
||||
configValue = line.substr(position + 1);
|
||||
trim(configValue);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
file.close();
|
||||
return configValue;
|
||||
}
|
||||
@@ -0,0 +1,517 @@
|
||||
#include "pch.h"
|
||||
|
||||
#include <Windows.h>
|
||||
#include <winspool.h>
|
||||
#include <setupapi.h>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <functional>
|
||||
#include <vector>
|
||||
#include <iostream>
|
||||
|
||||
#include "Common.h"
|
||||
|
||||
#pragma comment(lib, "setupapi.lib")
|
||||
#pragma comment(lib, "winspool.lib")
|
||||
|
||||
namespace RemotePrinter
|
||||
{
|
||||
#define HRESULT_ERR_ELEMENT_NOT_FOUND 0x80070490
|
||||
|
||||
LPCWCH RD_DRIVER_INF_PATH = L"drivers\\RustDeskPrinterDriver\\RustDeskPrinterDriver.inf";
|
||||
LPCWCH RD_PRINTER_PORT = L"RustDesk Printer";
|
||||
LPCWCH RD_PRINTER_NAME = L"RustDesk Printer";
|
||||
LPCWCH RD_PRINTER_DRIVER_NAME = L"RustDesk v4 Printer Driver";
|
||||
LPCWCH XCV_MONITOR_LOCAL_PORT = L",XcvMonitor Local Port";
|
||||
|
||||
using FuncEnum = std::function<BOOL(DWORD level, LPBYTE pDriverInfo, DWORD cbBuf, LPDWORD pcbNeeded, LPDWORD pcReturned)>;
|
||||
template <typename T, typename R>
|
||||
using FuncOnData = std::function<std::shared_ptr<R>(const T &)>;
|
||||
template <typename R>
|
||||
using FuncOnNoData = std::function<std::shared_ptr<R>()>;
|
||||
|
||||
template <class T, class R>
|
||||
std::shared_ptr<R> commonEnum(std::wstring funcName, FuncEnum func, DWORD level, FuncOnData<T, R> onData, FuncOnNoData<R> onNoData)
|
||||
{
|
||||
DWORD needed = 0;
|
||||
DWORD returned = 0;
|
||||
func(level, NULL, 0, &needed, &returned);
|
||||
if (needed == 0)
|
||||
{
|
||||
return onNoData();
|
||||
}
|
||||
|
||||
std::vector<BYTE> buffer(needed);
|
||||
if (!func(level, buffer.data(), needed, &needed, &returned))
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
T *pPortInfo = reinterpret_cast<T *>(buffer.data());
|
||||
for (DWORD i = 0; i < returned; i++)
|
||||
{
|
||||
auto r = onData(pPortInfo[i]);
|
||||
if (r)
|
||||
{
|
||||
return r;
|
||||
}
|
||||
}
|
||||
return onNoData();
|
||||
}
|
||||
|
||||
BOOL isNameEqual(LPCWSTR lhs, LPCWSTR rhs)
|
||||
{
|
||||
// https://learn.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-lstrcmpiw
|
||||
// For some locales, the lstrcmpi function may be insufficient.
|
||||
// If this occurs, use `CompareStringEx` to ensure proper comparison.
|
||||
// For example, in Japan call with the NORM_IGNORECASE, NORM_IGNOREKANATYPE, and NORM_IGNOREWIDTH values to achieve the most appropriate non-exact string comparison.
|
||||
// Note that specifying these values slows performance, so use them only when necessary.
|
||||
//
|
||||
// No need to consider `CompareStringEx` for now.
|
||||
return lstrcmpiW(lhs, rhs) == 0 ? TRUE : FALSE;
|
||||
}
|
||||
|
||||
BOOL enumPrinterPort(
|
||||
DWORD level,
|
||||
LPBYTE pPortInfo,
|
||||
DWORD cbBuf,
|
||||
LPDWORD pcbNeeded,
|
||||
LPDWORD pcReturned)
|
||||
{
|
||||
// https://learn.microsoft.com/en-us/windows/win32/printdocs/enumports
|
||||
// This is a blocking or synchronous function and might not return immediately.
|
||||
// How quickly this function returns depends on run-time factors
|
||||
// such as network status, print server configuration, and printer driver implementation factors that are difficult to predict when writing an application.
|
||||
// Calling this function from a thread that manages interaction with the user interface could make the application appear to be unresponsive.
|
||||
return EnumPortsW(NULL, level, pPortInfo, cbBuf, pcbNeeded, pcReturned);
|
||||
}
|
||||
|
||||
BOOL isPortExists(LPCWSTR port)
|
||||
{
|
||||
auto onData = [port](const PORT_INFO_2 &info)
|
||||
{
|
||||
if (isNameEqual(info.pPortName, port) == TRUE) {
|
||||
return std::shared_ptr<BOOL>(new BOOL(TRUE));
|
||||
}
|
||||
else {
|
||||
return std::shared_ptr<BOOL>(nullptr);
|
||||
} };
|
||||
auto onNoData = []()
|
||||
{ return nullptr; };
|
||||
auto res = commonEnum<PORT_INFO_2, BOOL>(L"EnumPortsW", enumPrinterPort, 2, onData, onNoData);
|
||||
if (res == nullptr)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
return *res;
|
||||
}
|
||||
}
|
||||
|
||||
BOOL executeOnLocalPort(LPCWSTR port, LPCWSTR command)
|
||||
{
|
||||
PRINTER_DEFAULTSW dft = {0};
|
||||
dft.DesiredAccess = SERVER_WRITE;
|
||||
HANDLE hMonitor = NULL;
|
||||
if (OpenPrinterW(const_cast<LPWSTR>(XCV_MONITOR_LOCAL_PORT), &hMonitor, &dft) == FALSE)
|
||||
{
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
DWORD outputNeeded = 0;
|
||||
DWORD status = 0;
|
||||
if (XcvDataW(hMonitor, command, (LPBYTE)port, (lstrlenW(port) + 1) * 2, NULL, 0, &outputNeeded, &status) == FALSE)
|
||||
{
|
||||
ClosePrinter(hMonitor);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
ClosePrinter(hMonitor);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
BOOL addLocalPort(LPCWSTR port)
|
||||
{
|
||||
return executeOnLocalPort(port, L"AddPort");
|
||||
}
|
||||
|
||||
BOOL deleteLocalPort(LPCWSTR port)
|
||||
{
|
||||
return executeOnLocalPort(port, L"DeletePort");
|
||||
}
|
||||
|
||||
BOOL checkAddLocalPort(LPCWSTR port)
|
||||
{
|
||||
if (!isPortExists(port))
|
||||
{
|
||||
return addLocalPort(port);
|
||||
}
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
std::wstring getPrinterInstalledOnPort(LPCWSTR port);
|
||||
|
||||
BOOL checkDeleteLocalPort(LPCWSTR port)
|
||||
{
|
||||
if (isPortExists(port))
|
||||
{
|
||||
if (getPrinterInstalledOnPort(port) != L"")
|
||||
{
|
||||
WcaLog(LOGMSG_STANDARD, "The printer is installed on the port. Please remove the printer first.\n");
|
||||
return FALSE;
|
||||
}
|
||||
return deleteLocalPort(port);
|
||||
}
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
BOOL enumPrinterDriver(
|
||||
DWORD level,
|
||||
LPBYTE pDriverInfo,
|
||||
DWORD cbBuf,
|
||||
LPDWORD pcbNeeded,
|
||||
LPDWORD pcReturned)
|
||||
{
|
||||
// https://learn.microsoft.com/en-us/windows/win32/printdocs/enumprinterdrivers
|
||||
// This is a blocking or synchronous function and might not return immediately.
|
||||
// How quickly this function returns depends on run-time factors
|
||||
// such as network status, print server configuration, and printer driver implementation factors that are difficult to predict when writing an application.
|
||||
// Calling this function from a thread that manages interaction with the user interface could make the application appear to be unresponsive.
|
||||
return EnumPrinterDriversW(
|
||||
NULL,
|
||||
NULL,
|
||||
level,
|
||||
pDriverInfo,
|
||||
cbBuf,
|
||||
pcbNeeded,
|
||||
pcReturned);
|
||||
}
|
||||
|
||||
DWORDLONG getInstalledDriverVersion(LPCWSTR name)
|
||||
{
|
||||
auto onData = [name](const DRIVER_INFO_6W &info)
|
||||
{
|
||||
if (isNameEqual(name, info.pName) == TRUE)
|
||||
{
|
||||
return std::shared_ptr<DWORDLONG>(new DWORDLONG(info.dwlDriverVersion));
|
||||
}
|
||||
else
|
||||
{
|
||||
return std::shared_ptr<DWORDLONG>(nullptr);
|
||||
} };
|
||||
auto onNoData = []()
|
||||
{ return nullptr; };
|
||||
auto res = commonEnum<DRIVER_INFO_6W, DWORDLONG>(L"EnumPrinterDriversW", enumPrinterDriver, 6, onData, onNoData);
|
||||
if (res == nullptr)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
return *res;
|
||||
}
|
||||
}
|
||||
|
||||
std::wstring findInf(LPCWSTR name)
|
||||
{
|
||||
auto onData = [name](const DRIVER_INFO_8W &info)
|
||||
{
|
||||
if (isNameEqual(name, info.pName) == TRUE)
|
||||
{
|
||||
return std::shared_ptr<std::wstring>(new std::wstring(info.pszInfPath));
|
||||
}
|
||||
else
|
||||
{
|
||||
return std::shared_ptr<std::wstring>(nullptr);
|
||||
} };
|
||||
auto onNoData = []()
|
||||
{ return nullptr; };
|
||||
auto res = commonEnum<DRIVER_INFO_8W, std::wstring>(L"EnumPrinterDriversW", enumPrinterDriver, 8, onData, onNoData);
|
||||
if (res == nullptr)
|
||||
{
|
||||
return L"";
|
||||
}
|
||||
else
|
||||
{
|
||||
return *res;
|
||||
}
|
||||
}
|
||||
|
||||
BOOL deletePrinterDriver(LPCWSTR name)
|
||||
{
|
||||
// If the printer is used after the spooler service is started. E.g., printing a document through RustDesk Printer.
|
||||
// `DeletePrinterDriverExW()` may fail with `ERROR_PRINTER_DRIVER_IN_USE`(3001, 0xBB9).
|
||||
// We can only ignore this error for now.
|
||||
// Though restarting the spooler service is a solution, it's not a good idea to restart the service.
|
||||
//
|
||||
// Deleting the printer driver after deleting the printer is a common practice.
|
||||
// No idea why `DeletePrinterDriverExW()` fails with `ERROR_UNKNOWN_PRINTER_DRIVER` after using the printer once.
|
||||
// https://github.com/ChromiumWebApps/chromium/blob/c7361d39be8abd1574e6ce8957c8dbddd4c6ccf7/cloud_print/virtual_driver/win/install/setup.cc#L422
|
||||
// AnyDesk printer driver and the simplest printer driver also have the same issue.
|
||||
BOOL res = DeletePrinterDriverExW(NULL, NULL, const_cast<LPWSTR>(name), DPD_DELETE_ALL_FILES, 0);
|
||||
if (res == FALSE)
|
||||
{
|
||||
DWORD error = GetLastError();
|
||||
if (error == ERROR_UNKNOWN_PRINTER_DRIVER)
|
||||
{
|
||||
return TRUE;
|
||||
}
|
||||
else
|
||||
{
|
||||
WcaLog(LOGMSG_STANDARD, "Failed to delete printer driver. Error (%d)\n", error);
|
||||
}
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
BOOL deletePrinterDriverPackage(const std::wstring &inf)
|
||||
{
|
||||
// https://learn.microsoft.com/en-us/windows/win32/printdocs/deleteprinterdriverpackage
|
||||
// This function is a blocking or synchronous function and might not return immediately.
|
||||
// How quickly this function returns depends on run-time factors such as network status, print server configuration, and printer driver implementation factors that are difficult to predict when writing an application.
|
||||
// Calling this function from a thread that manages interaction with the user interface could make the application appear to be unresponsive.
|
||||
int tries = 3;
|
||||
HRESULT result = S_FALSE;
|
||||
while ((result = DeletePrinterDriverPackage(NULL, inf.c_str(), NULL)) != S_OK)
|
||||
{
|
||||
if (result == HRESULT_ERR_ELEMENT_NOT_FOUND)
|
||||
{
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
WcaLog(LOGMSG_STANDARD, "Failed to delete printer driver package. HRESULT (%d)\n", result);
|
||||
tries--;
|
||||
if (tries <= 0)
|
||||
{
|
||||
return FALSE;
|
||||
}
|
||||
Sleep(2000);
|
||||
}
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
BOOL uninstallDriver(LPCWSTR name)
|
||||
{
|
||||
auto infFile = findInf(name);
|
||||
if (!deletePrinterDriver(name))
|
||||
{
|
||||
return FALSE;
|
||||
}
|
||||
if (infFile != L"" && !deletePrinterDriverPackage(infFile))
|
||||
{
|
||||
return FALSE;
|
||||
}
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
BOOL installDriver(LPCWSTR name, LPCWSTR inf)
|
||||
{
|
||||
DWORD size = MAX_PATH * 10;
|
||||
wchar_t package_path[MAX_PATH * 10] = {0};
|
||||
HRESULT result = UploadPrinterDriverPackage(
|
||||
NULL, inf, NULL,
|
||||
UPDP_SILENT_UPLOAD | UPDP_UPLOAD_ALWAYS, NULL, package_path, &size);
|
||||
if (result != S_OK)
|
||||
{
|
||||
WcaLog(LOGMSG_STANDARD, "Uploading the printer driver package to the driver cache silently, failed. Will retry with user UI. HRESULT (%d)\n", result);
|
||||
result = UploadPrinterDriverPackage(
|
||||
NULL, inf, NULL, UPDP_UPLOAD_ALWAYS,
|
||||
GetForegroundWindow(), package_path, &size);
|
||||
if (result != S_OK)
|
||||
{
|
||||
WcaLog(LOGMSG_STANDARD, "Uploading the printer driver package to the driver cache failed with user UI. Aborting...\n");
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
result = InstallPrinterDriverFromPackage(
|
||||
NULL, package_path, name, NULL, IPDFP_COPY_ALL_FILES);
|
||||
if (result != S_OK)
|
||||
{
|
||||
WcaLog(LOGMSG_STANDARD, "Installing the printer driver failed. HRESULT (%d)\n", result);
|
||||
}
|
||||
return result == S_OK;
|
||||
}
|
||||
|
||||
BOOL enumLocalPrinter(
|
||||
DWORD level,
|
||||
LPBYTE pPrinterInfo,
|
||||
DWORD cbBuf,
|
||||
LPDWORD pcbNeeded,
|
||||
LPDWORD pcReturned)
|
||||
{
|
||||
// https://learn.microsoft.com/en-us/windows/win32/printdocs/enumprinters
|
||||
// This is a blocking or synchronous function and might not return immediately.
|
||||
// How quickly this function returns depends on run-time factors
|
||||
// such as network status, print server configuration, and printer driver implementation factors that are difficult to predict when writing an application.
|
||||
// Calling this function from a thread that manages interaction with the user interface could make the application appear to be unresponsive.
|
||||
return EnumPrintersW(PRINTER_ENUM_LOCAL, NULL, level, pPrinterInfo, cbBuf, pcbNeeded, pcReturned);
|
||||
}
|
||||
|
||||
BOOL isPrinterAdded(LPCWSTR name)
|
||||
{
|
||||
auto onData = [name](const PRINTER_INFO_1W &info)
|
||||
{
|
||||
if (isNameEqual(name, info.pName) == TRUE)
|
||||
{
|
||||
return std::shared_ptr<BOOL>(new BOOL(TRUE));
|
||||
}
|
||||
else
|
||||
{
|
||||
return std::shared_ptr<BOOL>(nullptr);
|
||||
} };
|
||||
auto onNoData = []()
|
||||
{ return nullptr; };
|
||||
auto res = commonEnum<PRINTER_INFO_1W, BOOL>(L"EnumPrintersW", enumLocalPrinter, 1, onData, onNoData);
|
||||
if (res == nullptr)
|
||||
{
|
||||
return FALSE;
|
||||
}
|
||||
else
|
||||
{
|
||||
return *res;
|
||||
}
|
||||
}
|
||||
|
||||
std::wstring getPrinterInstalledOnPort(LPCWSTR port)
|
||||
{
|
||||
auto onData = [port](const PRINTER_INFO_2W &info)
|
||||
{
|
||||
if (isNameEqual(port, info.pPortName) == TRUE)
|
||||
{
|
||||
return std::shared_ptr<std::wstring>(new std::wstring(info.pPrinterName));
|
||||
}
|
||||
else
|
||||
{
|
||||
return std::shared_ptr<std::wstring>(nullptr);
|
||||
} };
|
||||
auto onNoData = []()
|
||||
{ return nullptr; };
|
||||
auto res = commonEnum<PRINTER_INFO_2W, std::wstring>(L"EnumPrintersW", enumLocalPrinter, 2, onData, onNoData);
|
||||
if (res == nullptr)
|
||||
{
|
||||
return L"";
|
||||
}
|
||||
else
|
||||
{
|
||||
return *res;
|
||||
}
|
||||
}
|
||||
|
||||
BOOL addPrinter(LPCWSTR name, LPCWSTR driver, LPCWSTR port)
|
||||
{
|
||||
PRINTER_INFO_2W printerInfo = {0};
|
||||
printerInfo.pPrinterName = const_cast<LPWSTR>(name);
|
||||
printerInfo.pPortName = const_cast<LPWSTR>(port);
|
||||
printerInfo.pDriverName = const_cast<LPWSTR>(driver);
|
||||
printerInfo.pPrintProcessor = const_cast<LPWSTR>(L"WinPrint");
|
||||
printerInfo.pDatatype = const_cast<LPWSTR>(L"RAW");
|
||||
printerInfo.Attributes = PRINTER_ATTRIBUTE_LOCAL;
|
||||
HANDLE hPrinter = AddPrinterW(NULL, 2, (LPBYTE)&printerInfo);
|
||||
return hPrinter == NULL ? FALSE : TRUE;
|
||||
}
|
||||
|
||||
VOID deletePrinter(LPCWSTR name)
|
||||
{
|
||||
PRINTER_DEFAULTSW dft = {0};
|
||||
dft.DesiredAccess = PRINTER_ALL_ACCESS;
|
||||
HANDLE hPrinter = NULL;
|
||||
if (OpenPrinterW(const_cast<LPWSTR>(name), &hPrinter, &dft) == FALSE)
|
||||
{
|
||||
DWORD error = GetLastError();
|
||||
if (error == ERROR_INVALID_PRINTER_NAME)
|
||||
{
|
||||
return;
|
||||
}
|
||||
WcaLog(LOGMSG_STANDARD, "Failed to open printer. error (%d)\n", error);
|
||||
return;
|
||||
}
|
||||
|
||||
if (SetPrinterW(hPrinter, 0, NULL, PRINTER_CONTROL_PURGE) == FALSE)
|
||||
{
|
||||
ClosePrinter(hPrinter);
|
||||
WcaLog(LOGMSG_STANDARD, "Failed to purge printer queue. error (%d)\n", GetLastError());
|
||||
return;
|
||||
}
|
||||
|
||||
if (DeletePrinter(hPrinter) == FALSE)
|
||||
{
|
||||
ClosePrinter(hPrinter);
|
||||
WcaLog(LOGMSG_STANDARD, "Failed to delete printer. error (%d)\n", GetLastError());
|
||||
return;
|
||||
}
|
||||
|
||||
ClosePrinter(hPrinter);
|
||||
}
|
||||
|
||||
bool FileExists(const std::wstring &filePath)
|
||||
{
|
||||
DWORD fileAttributes = GetFileAttributes(filePath.c_str());
|
||||
return (fileAttributes != INVALID_FILE_ATTRIBUTES && !(fileAttributes & FILE_ATTRIBUTE_DIRECTORY));
|
||||
}
|
||||
|
||||
// Steps:
|
||||
// 1. Add the local port.
|
||||
// 2. Check if the driver is installed.
|
||||
// Uninstall the existing driver if it is installed.
|
||||
// We should not check the driver version because the driver is deployed with the application.
|
||||
// It's better to uninstall the existing driver and install the driver from the application.
|
||||
// 3. Add the printer.
|
||||
VOID installUpdatePrinter(const std::wstring &installFolder)
|
||||
{
|
||||
const std::wstring infFile = installFolder + L"\\" + RemotePrinter::RD_DRIVER_INF_PATH;
|
||||
if (!FileExists(infFile))
|
||||
{
|
||||
WcaLog(LOGMSG_STANDARD, "Printer driver INF file not found, aborting...\n");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!checkAddLocalPort(RD_PRINTER_PORT))
|
||||
{
|
||||
WcaLog(LOGMSG_STANDARD, "Failed to check add local port, error (%d)\n", GetLastError());
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
WcaLog(LOGMSG_STANDARD, "Local port added successfully\n");
|
||||
}
|
||||
|
||||
if (getInstalledDriverVersion(RD_PRINTER_DRIVER_NAME) > 0)
|
||||
{
|
||||
deletePrinter(RD_PRINTER_NAME);
|
||||
if (FALSE == uninstallDriver(RD_PRINTER_DRIVER_NAME))
|
||||
{
|
||||
WcaLog(LOGMSG_STANDARD, "Failed to uninstall previous printer driver, error (%d)\n", GetLastError());
|
||||
}
|
||||
}
|
||||
|
||||
if (FALSE == installDriver(RD_PRINTER_DRIVER_NAME, infFile.c_str()))
|
||||
{
|
||||
WcaLog(LOGMSG_STANDARD, "Driver installation failed, still try to add the printer\n");
|
||||
}
|
||||
else
|
||||
{
|
||||
WcaLog(LOGMSG_STANDARD, "Driver installed successfully\n");
|
||||
}
|
||||
|
||||
if (FALSE == addPrinter(RD_PRINTER_NAME, RD_PRINTER_DRIVER_NAME, RD_PRINTER_PORT))
|
||||
{
|
||||
WcaLog(LOGMSG_STANDARD, "Failed to add printer, error (%d)\n", GetLastError());
|
||||
}
|
||||
else
|
||||
{
|
||||
WcaLog(LOGMSG_STANDARD, "Printer installed successfully\n");
|
||||
}
|
||||
}
|
||||
|
||||
VOID uninstallPrinter()
|
||||
{
|
||||
deletePrinter(RD_PRINTER_NAME);
|
||||
WcaLog(LOGMSG_STANDARD, "Deleted the printer\n");
|
||||
uninstallDriver(RD_PRINTER_DRIVER_NAME);
|
||||
WcaLog(LOGMSG_STANDARD, "Uninstalled the printer driver\n");
|
||||
checkDeleteLocalPort(RD_PRINTER_PORT);
|
||||
WcaLog(LOGMSG_STANDARD, "Deleted the local port\n");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,175 @@
|
||||
// https://learn.microsoft.com/en-us/windows/win32/services/installing-a-service
|
||||
|
||||
#include "pch.h"
|
||||
|
||||
#include <iostream>
|
||||
#include <Windows.h>
|
||||
#include <strsafe.h>
|
||||
|
||||
bool MyCreateServiceW(LPCWSTR serviceName, LPCWSTR displayName, LPCWSTR binaryPath)
|
||||
{
|
||||
SC_HANDLE schSCManager;
|
||||
SC_HANDLE schService;
|
||||
|
||||
// Get a handle to the SCM database.
|
||||
schSCManager = OpenSCManagerW(
|
||||
NULL, // local computer
|
||||
NULL, // ServicesActive database
|
||||
SC_MANAGER_ALL_ACCESS); // full access rights
|
||||
|
||||
if (NULL == schSCManager)
|
||||
{
|
||||
WcaLog(LOGMSG_STANDARD, "OpenSCManager failed (%d)\n", GetLastError());
|
||||
return false;
|
||||
}
|
||||
|
||||
// Create the service
|
||||
schService = CreateServiceW(
|
||||
schSCManager, // SCM database
|
||||
serviceName, // name of service
|
||||
displayName, // service name to display
|
||||
SERVICE_ALL_ACCESS, // desired access
|
||||
SERVICE_WIN32_OWN_PROCESS, // service type
|
||||
SERVICE_AUTO_START, // start type
|
||||
SERVICE_ERROR_NORMAL, // error control type
|
||||
binaryPath, // path to service's binary
|
||||
NULL, // no load ordering group
|
||||
NULL, // no tag identifier
|
||||
NULL, // no dependencies
|
||||
NULL, // LocalSystem account
|
||||
NULL); // no password
|
||||
if (schService == NULL)
|
||||
{
|
||||
WcaLog(LOGMSG_STANDARD, "CreateService failed (%d)\n", GetLastError());
|
||||
CloseServiceHandle(schSCManager);
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
WcaLog(LOGMSG_STANDARD, "Service installed successfully\n");
|
||||
}
|
||||
|
||||
CloseServiceHandle(schService);
|
||||
CloseServiceHandle(schSCManager);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MyDeleteServiceW(LPCWSTR serviceName)
|
||||
{
|
||||
SC_HANDLE hSCManager = OpenSCManagerW(NULL, NULL, SC_MANAGER_CONNECT);
|
||||
if (hSCManager == NULL) {
|
||||
WcaLog(LOGMSG_STANDARD, "Failed to open Service Control Manager, error: 0x%02X", GetLastError());
|
||||
return false;
|
||||
}
|
||||
|
||||
SC_HANDLE hService = OpenServiceW(hSCManager, serviceName, SERVICE_STOP | DELETE);
|
||||
if (hService == NULL) {
|
||||
WcaLog(LOGMSG_STANDARD, "Failed to open service: %ls, error: 0x%02X", serviceName, GetLastError());
|
||||
CloseServiceHandle(hSCManager);
|
||||
return false;
|
||||
}
|
||||
|
||||
SERVICE_STATUS serviceStatus;
|
||||
if (ControlService(hService, SERVICE_CONTROL_STOP, &serviceStatus)) {
|
||||
WcaLog(LOGMSG_STANDARD, "Stopping service: %ls", serviceName);
|
||||
}
|
||||
|
||||
bool success = DeleteService(hService);
|
||||
if (!success) {
|
||||
WcaLog(LOGMSG_STANDARD, "Failed to delete service: %ls, error: 0x%02X", serviceName, GetLastError());
|
||||
}
|
||||
|
||||
CloseServiceHandle(hService);
|
||||
CloseServiceHandle(hSCManager);
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
bool MyStartServiceW(LPCWSTR serviceName)
|
||||
{
|
||||
SC_HANDLE hSCManager = OpenSCManagerW(NULL, NULL, SC_MANAGER_CONNECT);
|
||||
if (hSCManager == NULL) {
|
||||
WcaLog(LOGMSG_STANDARD, "Failed to open Service Control Manager, error: 0x%02X", GetLastError());
|
||||
return false;
|
||||
}
|
||||
|
||||
SC_HANDLE hService = OpenServiceW(hSCManager, serviceName, SERVICE_START);
|
||||
if (hService == NULL) {
|
||||
WcaLog(LOGMSG_STANDARD, "Failed to open service: %ls, error: 0x%02X", serviceName, GetLastError());
|
||||
CloseServiceHandle(hSCManager);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool success = StartServiceW(hService, 0, NULL);
|
||||
if (!success) {
|
||||
WcaLog(LOGMSG_STANDARD, "Failed to start service: %ls, error: 0x%02X", serviceName, GetLastError());
|
||||
}
|
||||
|
||||
CloseServiceHandle(hService);
|
||||
CloseServiceHandle(hSCManager);
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
bool MyStopServiceW(LPCWSTR serviceName)
|
||||
{
|
||||
SC_HANDLE hSCManager = OpenSCManagerW(NULL, NULL, SC_MANAGER_CONNECT);
|
||||
if (hSCManager == NULL) {
|
||||
WcaLog(LOGMSG_STANDARD, "Failed to open Service Control Manager");
|
||||
return false;
|
||||
}
|
||||
|
||||
SC_HANDLE hService = OpenServiceW(hSCManager, serviceName, SERVICE_STOP);
|
||||
if (hService == NULL) {
|
||||
WcaLog(LOGMSG_STANDARD, "Failed to open service: %ls", serviceName);
|
||||
CloseServiceHandle(hSCManager);
|
||||
return false;
|
||||
}
|
||||
|
||||
SERVICE_STATUS serviceStatus;
|
||||
if (!ControlService(hService, SERVICE_CONTROL_STOP, &serviceStatus)) {
|
||||
WcaLog(LOGMSG_STANDARD, "Failed to stop service: %ls", serviceName);
|
||||
CloseServiceHandle(hService);
|
||||
CloseServiceHandle(hSCManager);
|
||||
return false;
|
||||
}
|
||||
|
||||
CloseServiceHandle(hService);
|
||||
CloseServiceHandle(hSCManager);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool QueryServiceStatusExW(LPCWSTR serviceName, SERVICE_STATUS_PROCESS* status)
|
||||
{
|
||||
SC_HANDLE hSCManager = OpenSCManagerW(NULL, NULL, SC_MANAGER_CONNECT);
|
||||
if (hSCManager == NULL) {
|
||||
WcaLog(LOGMSG_STANDARD, "Failed to open Service Control Manager");
|
||||
return false;
|
||||
}
|
||||
|
||||
SC_HANDLE hService = OpenServiceW(hSCManager, serviceName, SERVICE_QUERY_STATUS);
|
||||
if (hService == NULL) {
|
||||
WcaLog(LOGMSG_STANDARD, "Failed to open service: %ls", serviceName);
|
||||
CloseServiceHandle(hSCManager);
|
||||
return false;
|
||||
}
|
||||
|
||||
DWORD bytesNeeded;
|
||||
BOOL success = QueryServiceStatusEx(hService, SC_STATUS_PROCESS_INFO, reinterpret_cast<LPBYTE>(status), sizeof(*status), &bytesNeeded);
|
||||
if (!success) {
|
||||
WcaLog(LOGMSG_STANDARD, "Failed to query service: %ls", serviceName);
|
||||
}
|
||||
|
||||
CloseServiceHandle(hService);
|
||||
CloseServiceHandle(hSCManager);
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
bool IsServiceRunningW(LPCWSTR serviceName)
|
||||
{
|
||||
SERVICE_STATUS_PROCESS serviceStatus;
|
||||
QueryServiceStatusExW(serviceName, &serviceStatus);
|
||||
return (serviceStatus.dwCurrentState == SERVICE_RUNNING);
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
// dllmain.cpp : Defines the entry point for the DLL application.
|
||||
#include "pch.h"
|
||||
|
||||
BOOL APIENTRY DllMain(
|
||||
__in HMODULE hModule,
|
||||
__in DWORD ulReasonForCall,
|
||||
__in LPVOID
|
||||
)
|
||||
{
|
||||
switch (ulReasonForCall)
|
||||
{
|
||||
case DLL_PROCESS_ATTACH:
|
||||
WcaGlobalInitialize(hModule);
|
||||
break;
|
||||
|
||||
case DLL_PROCESS_DETACH:
|
||||
WcaGlobalFinalize();
|
||||
break;
|
||||
|
||||
case DLL_THREAD_ATTACH:
|
||||
case DLL_THREAD_DETACH:
|
||||
break;
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
#pragma once
|
||||
|
||||
#define WIN32_LEAN_AND_MEAN // Exclude rarely-used stuff from Windows headers
|
||||
// Windows Header Files
|
||||
#include <windows.h>
|
||||
#include <strsafe.h>
|
||||
#include <msiquery.h>
|
||||
|
||||
// WiX Header Files:
|
||||
#include <wcautil.h>
|
||||
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<packages>
|
||||
<package id="WixToolset.DUtil" version="4.0.5" targetFramework="native" />
|
||||
<package id="WixToolset.WcaUtil" version="4.0.5" targetFramework="native" />
|
||||
</packages>
|
||||
@@ -0,0 +1,5 @@
|
||||
// pch.cpp: source file corresponding to the pre-compiled header
|
||||
|
||||
#include "pch.h"
|
||||
|
||||
// When you are using pre-compiled headers, this source file is necessary for compilation to succeed.
|
||||
@@ -0,0 +1,13 @@
|
||||
// pch.h: This is a precompiled header file.
|
||||
// Files listed below are compiled only once, improving build performance for future builds.
|
||||
// This also affects IntelliSense performance, including code completion and many code browsing features.
|
||||
// However, files listed here are ALL re-compiled if any one of them is updated between builds.
|
||||
// Do not add files here that you will be updating frequently as this negates the performance advantage.
|
||||
|
||||
#ifndef PCH_H
|
||||
#define PCH_H
|
||||
|
||||
// add headers that you want to pre-compile here
|
||||
#include "framework.h"
|
||||
|
||||
#endif //PCH_H
|
||||
Reference in New Issue
Block a user