mirror of
https://github.com/rustdesk/rustdesk.git
synced 2026-02-17 14:07:28 +08:00
feat: remote printer (#11231)
Signed-off-by: fufesou <linlong1266@gmail.com>
This commit is contained in:
@@ -23,7 +23,8 @@ remote = open('src/ui/remote.html').read() \
|
||||
.replace('include "grid.tis";', open('src/ui/grid.tis').read()) \
|
||||
.replace('include "header.tis";', open('src/ui/header.tis').read()) \
|
||||
.replace('include "file_transfer.tis";', open('src/ui/file_transfer.tis').read()) \
|
||||
.replace('include "port_forward.tis";', open('src/ui/port_forward.tis').read())
|
||||
.replace('include "port_forward.tis";', open('src/ui/port_forward.tis').read()) \
|
||||
.replace('include "printer.tis";', open('src/ui/printer.tis').read())
|
||||
|
||||
chatbox = open('src/ui/chatbox.html').read()
|
||||
install = open('src/ui/install.html').read().replace('include "install.tis";', open('src/ui/install.tis').read())
|
||||
|
||||
@@ -15,3 +15,9 @@ 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();
|
||||
}
|
||||
|
||||
@@ -878,3 +878,55 @@ void TryStopDeleteServiceByShell(LPWSTR svcName)
|
||||
WcaLog(LOGMSG_STANDARD, "Failed to delete service: \"%ls\" with shell, current status: %d.", svcName, svcStatus.dwCurrentState);
|
||||
}
|
||||
}
|
||||
|
||||
UINT __stdcall InstallPrinter(
|
||||
__in MSIHANDLE hInstall)
|
||||
{
|
||||
HRESULT hr = S_OK;
|
||||
DWORD er = ERROR_SUCCESS;
|
||||
|
||||
int nResult = 0;
|
||||
LPWSTR installFolder = NULL;
|
||||
LPWSTR pwz = NULL;
|
||||
LPWSTR pwzData = NULL;
|
||||
|
||||
hr = WcaInitialize(hInstall, "InstallPrinter");
|
||||
ExitOnFailure(hr, "Failed to initialize");
|
||||
|
||||
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);
|
||||
|
||||
WcaLog(LOGMSG_STANDARD, "Try to install RD printer in : %ls", installFolder);
|
||||
RemotePrinter::installUpdatePrinter(installFolder);
|
||||
WcaLog(LOGMSG_STANDARD, "Install RD printer done");
|
||||
|
||||
LExit:
|
||||
if (pwzData) {
|
||||
ReleaseStr(pwzData);
|
||||
}
|
||||
|
||||
er = SUCCEEDED(hr) ? ERROR_SUCCESS : ERROR_INSTALL_FAILURE;
|
||||
return WcaFinalize(er);
|
||||
}
|
||||
|
||||
UINT __stdcall UninstallPrinter(
|
||||
__in MSIHANDLE hInstall)
|
||||
{
|
||||
HRESULT hr = S_OK;
|
||||
DWORD er = ERROR_SUCCESS;
|
||||
|
||||
hr = WcaInitialize(hInstall, "UninstallPrinter");
|
||||
ExitOnFailure(hr, "Failed to initialize");
|
||||
|
||||
WcaLog(LOGMSG_STANDARD, "Try to uninstall RD printer");
|
||||
RemotePrinter::uninstallPrinter();
|
||||
WcaLog(LOGMSG_STANDARD, "Uninstall RD printer done");
|
||||
|
||||
LExit:
|
||||
er = SUCCEEDED(hr) ? ERROR_SUCCESS : ERROR_INSTALL_FAILURE;
|
||||
return WcaFinalize(er);
|
||||
}
|
||||
|
||||
@@ -12,3 +12,5 @@ EXPORTS
|
||||
SetPropertyFromConfig
|
||||
AddRegSoftwareSASGeneration
|
||||
RemoveAmyuniIdd
|
||||
InstallPrinter
|
||||
UninstallPrinter
|
||||
|
||||
@@ -67,6 +67,7 @@
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|x64'">Create</PrecompiledHeader>
|
||||
</ClCompile>
|
||||
<ClCompile Include="ReadConfig.cpp" />
|
||||
<ClCompile Include="RemotePrinter.cpp" />
|
||||
<ClCompile Include="ServiceUtils.cpp" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
|
||||
517
res/msi/CustomActions/RemotePrinter.cpp
Normal file
517
res/msi/CustomActions/RemotePrinter.cpp
Normal file
@@ -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");
|
||||
}
|
||||
}
|
||||
@@ -30,6 +30,7 @@
|
||||
<CustomAction Id="SetPropertyServiceStop.SetParam.PropertyName" Return="check" Property="PropertyName" Value="STOP_SERVICE" />
|
||||
<CustomAction Id="TryDeleteStartupShortcut.SetParam" Return="check" Property="ShortcutName" Value="$(var.Product) Tray" />
|
||||
<CustomAction Id="RemoveAmyuniIdd.SetParam" Return="check" Property="RemoveAmyuniIdd" Value="[INSTALLFOLDER_INNER]" />
|
||||
<CustomAction Id="InstallPrinter.SetParam" Return="check" Property="InstallPrinter" Value="[INSTALLFOLDER_INNER]" />
|
||||
<InstallExecuteSequence>
|
||||
|
||||
<Custom Action="SetPropertyIsServiceRunning" After="InstallInitialize" Condition="Installed" />
|
||||
@@ -57,6 +58,18 @@
|
||||
<Custom Action="LaunchApp" After="InstallFinalize" Condition="(NOT UILevel=2) AND (NOT (Installed AND REMOVE AND NOT UPGRADINGPRODUCTCODE)) "/>
|
||||
<Custom Action="LaunchAppTray" After="InstallFinalize" Condition="(NOT UILevel=2) AND (NOT (Installed AND REMOVE AND NOT UPGRADINGPRODUCTCODE)) AND (NOT STOP_SERVICE="'Y'") AND (NOT CC_CONNECTION_TYPE="outgoing")"/>
|
||||
|
||||
<!-- https://learn.microsoft.com/en-us/windows/win32/msi/operating-system-property-values -->
|
||||
<!-- We have to use `VersionNT` to instead of `IsWindows10OrGreater()` in the custom action.
|
||||
Because `IsWindows10OrGreater()` requires the manifest file to be embedded in the executable/dll file.
|
||||
Even I have embedded the manifest file, it still does not work correctly in my case.
|
||||
https://learn.microsoft.com/en-us/windows/win32/sysinfo/version-helper-apis -->
|
||||
<!-- VersionNT >= 603 means can't differentiate between Windows 8.1 and Windows 10.
|
||||
Some msi packages reset the `VersionNT` value to 1000 on Windows 10.
|
||||
https://www.advancedinstaller.com/user-guide/qa-OS-dependent-install.html -->
|
||||
<!-- Remote printer also works on Win8.1 in my test. -->
|
||||
<Custom Action="InstallPrinter" Before="InstallFinalize" Condition="VersionNT >= 603 AND PRINTER = 1 OR PRINTER = "Y" OR PRINTER = "y"" />
|
||||
<Custom Action="InstallPrinter.SetParam" Before="InstallPrinter" Condition="VersionNT >= 603" />
|
||||
|
||||
<!--Workaround of "fire:FirewallException". If Outbound="Yes" or Outbound="true", the following error occurs.-->
|
||||
<!--ExecFirewallExceptions: Error 0x80070057: failed to add app to the authorized apps list-->
|
||||
<Custom Action="AddFirewallRules" Before="InstallFinalize" Condition="NOT (Installed AND REMOVE AND NOT UPGRADINGPRODUCTCODE)"/>
|
||||
@@ -72,6 +85,8 @@
|
||||
<Custom Action="RemoveFirewallRules" Before="RemoveFiles"/>
|
||||
<Custom Action="RemoveFirewallRules.SetParam" Before="RemoveFirewallRules"/>
|
||||
|
||||
<Custom Action="UninstallPrinter" Before="RemoveInstallFolder" Condition="VersionNT >= 603" />
|
||||
|
||||
<Custom Action="TerminateProcesses" Before="RemoveInstallFolder"/>
|
||||
<Custom Action="TerminateProcesses.SetParam" Before="TerminateProcesses"/>
|
||||
<Custom Action="TerminateBrokers" Before="RemoveInstallFolder"/>
|
||||
|
||||
@@ -17,5 +17,7 @@
|
||||
<CustomAction Id="SetPropertyServiceStop" DllEntry="SetPropertyFromConfig" Impersonate="yes" Execute="immediate" Return="ignore" BinaryRef="Custom_Actions_Dll"/>
|
||||
<CustomAction Id="AddRegSoftwareSASGeneration" DllEntry="AddRegSoftwareSASGeneration" Impersonate="no" Execute="deferred" Return="ignore" BinaryRef="Custom_Actions_Dll"/>
|
||||
<CustomAction Id="RemoveAmyuniIdd" DllEntry="RemoveAmyuniIdd" Impersonate="no" Execute="deferred" Return="ignore" BinaryRef="Custom_Actions_Dll"/>
|
||||
<CustomAction Id="InstallPrinter" DllEntry="InstallPrinter" Impersonate="no" Execute="deferred" Return="ignore" BinaryRef="Custom_Actions_Dll"/>
|
||||
<CustomAction Id="UninstallPrinter" DllEntry="UninstallPrinter" Impersonate="no" Execute="deferred" Return="ignore" BinaryRef="Custom_Actions_Dll"/>
|
||||
</Fragment>
|
||||
</Wix>
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
<Property Id="STARTMENUSHORTCUTS" Value="1" Secure="yes"></Property>
|
||||
<Property Id="DESKTOPSHORTCUTS" Value="1" Secure="yes"></Property>
|
||||
<Property Id="STARTUPSHORTCUTS" Value="1" Secure="yes"></Property>
|
||||
<Property Id="PRINTER" Value="1" Secure="yes"></Property>
|
||||
|
||||
<!-- These properties get set from either the command line, bundle or registry value,
|
||||
if set they update the properties above with their value. -->
|
||||
@@ -23,6 +24,9 @@
|
||||
<Property Id="CREATEDESKTOPSHORTCUTS" Secure="yes">
|
||||
<RegistrySearch Id="CreateDesktopShortcutsSearch" Root="HKCR" Key="$(var.RegKeyRoot)" Name="DESKTOPSHORTCUTS" Type="raw" />
|
||||
</Property>
|
||||
<Property Id="INSTALLPRINTER" Secure="yes">
|
||||
<RegistrySearch Id="InstallPrinterSearch" Root="HKCR" Key="$(var.RegKeyRoot)" Name="PRINTER" Type="raw" />
|
||||
</Property>
|
||||
|
||||
<!-- Component that persists the property values to the registry so they are available during an upgrade/modify -->
|
||||
<DirectoryRef Id="INSTALLFOLDER_INNER">
|
||||
@@ -46,6 +50,16 @@
|
||||
<RegistryValue Type="string" Name="DESKTOPSHORTCUTS" Value="0" />
|
||||
</RegistryKey>
|
||||
</Component>
|
||||
<Component Id="Product.Registry.PersistedPrinterProperties1" Guid="AF617116-2502-EB3D-5B52-B47AA89EB4B0" Condition="PRINTER = 1 OR PRINTER = "Y" OR PRINTER = "y"">
|
||||
<RegistryKey Root="HKCR" Key="$(var.RegKeyRoot)">
|
||||
<RegistryValue Type="string" Name="PRINTER" Value="1" />
|
||||
</RegistryKey>
|
||||
</Component>
|
||||
<Component Id="Product.Registry.PersistedPrinterProperties0" Guid="51F944D3-AAEB-F167-03A1-081A38E9468A" Condition="NOT (PRINTER = 1 OR PRINTER = "Y" OR PRINTER = "y")">
|
||||
<RegistryKey Root="HKCR" Key="$(var.RegKeyRoot)">
|
||||
<RegistryValue Type="string" Name="PRINTER" Value="0" />
|
||||
</RegistryKey>
|
||||
</Component>
|
||||
</DirectoryRef>
|
||||
|
||||
<!-- If a property value has been passed via the command line (which includes when set from the bundle), the registry search will
|
||||
@@ -53,14 +67,17 @@
|
||||
is performed so they can be restored after the registry search is complete -->
|
||||
<SetProperty Id="SavedStartMenuShortcutsCmdLineValue" Value="[CREATESTARTMENUSHORTCUTS]" Before="AppSearch" Sequence="first" Condition="CREATESTARTMENUSHORTCUTS" />
|
||||
<SetProperty Id="SavedDesktopShortcutsCmdLineValue" Value="[CREATEDESKTOPSHORTCUTS]" Before="AppSearch" Sequence="first" Condition="CREATEDESKTOPSHORTCUTS" />
|
||||
<SetProperty Id="SavedPrinterCmdLineValue" Value="[INSTALLPRINTER]" Before="AppSearch" Sequence="first" Condition="INSTALLPRINTER" />
|
||||
|
||||
<!-- If a command line value was stored, restore it after the registry search has been performed -->
|
||||
<SetProperty Action="RestoreSavedStartMenuShortcutsValue" Id="CREATESTARTMENUSHORTCUTS" Value="[SavedStartMenuShortcutsCmdLineValue]" After="AppSearch" Sequence="first" Condition="SavedStartMenuShortcutsCmdLineValue" />
|
||||
<SetProperty Action="RestoreSavedDesktopShortcutsValue" Id="CREATEDESKTOPSHORTCUTS" Value="[SavedDesktopShortcutsCmdLineValue]" After="AppSearch" Sequence="first" Condition="SavedDesktopShortcutsCmdLineValue" />
|
||||
<SetProperty Action="RestoreSavedPrinterValue" Id="INSTALLPRINTER" Value="[SavedPrinterCmdLineValue]" After="AppSearch" Sequence="first" Condition="SavedPrinterCmdLineValue" />
|
||||
|
||||
<!-- If a command line value or registry value was set, update the main properties with the value -->
|
||||
<SetProperty Id="STARTMENUSHORTCUTS" Value="" After="RestoreSavedStartMenuShortcutsValue" Sequence="first" Condition="CREATESTARTMENUSHORTCUTS AND NOT (CREATESTARTMENUSHORTCUTS = 1 OR CREATESTARTMENUSHORTCUTS = "Y" OR CREATESTARTMENUSHORTCUTS = "y")" />
|
||||
<SetProperty Id="DESKTOPSHORTCUTS" Value="" After="RestoreSavedDesktopShortcutsValue" Sequence="first" Condition="CREATEDESKTOPSHORTCUTS AND NOT (CREATEDESKTOPSHORTCUTS = 1 OR CREATEDESKTOPSHORTCUTS = "Y" OR CREATEDESKTOPSHORTCUTS = "y")" />
|
||||
<SetProperty Id="PRINTER" Value="" After="RestoreSavedPrinterValue" Sequence="first" Condition="INSTALLPRINTER AND NOT (INSTALLPRINTER = 1 OR INSTALLPRINTER = "Y" OR INSTALLPRINTER = "y")" />
|
||||
|
||||
</Fragment>
|
||||
</Wix>
|
||||
|
||||
@@ -51,5 +51,6 @@ This file contains the declaration of all the localizable strings.
|
||||
|
||||
<String Id="MyInstallDirDlgDesktopShortcuts" Value="Create desktop icon" />
|
||||
<String Id="MyInstallDirDlgStartMenuShortcuts" Value="Create start menu shortcuts" />
|
||||
<String Id="MyInstallDirDlgPrinter" Value="Install RustDesk Printer" />
|
||||
|
||||
</WixLocalization>
|
||||
|
||||
@@ -51,6 +51,8 @@
|
||||
<ComponentRef Id="Product.Registry.PersistedStartMenuShortcutProperties0" />
|
||||
<ComponentRef Id="Product.Registry.PersistedDesktopShortcutProperties1" />
|
||||
<ComponentRef Id="Product.Registry.PersistedDesktopShortcutProperties0" />
|
||||
<ComponentRef Id="Product.Registry.PersistedPrinterProperties1" />
|
||||
<ComponentRef Id="Product.Registry.PersistedPrinterProperties0" />
|
||||
</Feature>
|
||||
|
||||
<!--https://wixtoolset.org/docs/tools/wixext/wixui/#customizing-a-dialog-set-->
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
|
||||
<Control Id="ChkBoxStartMenuShortcuts" Type="CheckBox" X="20" Y="140" Width="290" Height="17" Property="STARTMENUSHORTCUTS" CheckBoxValue="1" Text="!(loc.MyInstallDirDlgStartMenuShortcuts)" />
|
||||
<Control Id="ChkBoxDesktopShortcuts" Type="CheckBox" X="20" Y="160" Width="290" Height="17" Property="DESKTOPSHORTCUTS" CheckBoxValue="1" Text="!(loc.MyInstallDirDlgDesktopShortcuts)" />
|
||||
<Control Id="ChkBoxInstallPrinter" Type="CheckBox" X="20" Y="180" Width="290" Height="17" Property="PRINTER" CheckBoxValue="1" Text="!(loc.MyInstallDirDlgPrinter)" />
|
||||
</Dialog>
|
||||
</UI>
|
||||
</Fragment>
|
||||
|
||||
@@ -8,7 +8,9 @@ import argparse
|
||||
import datetime
|
||||
import subprocess
|
||||
import re
|
||||
import platform
|
||||
from pathlib import Path
|
||||
from itertools import chain
|
||||
import shutil
|
||||
|
||||
g_indent_unit = "\t"
|
||||
@@ -187,6 +189,17 @@ def replace_app_name_in_langs(app_name):
|
||||
with open(file_path, "w", encoding="utf-8") as f:
|
||||
f.writelines(lines)
|
||||
|
||||
def replace_app_name_in_custom_actions(app_name):
|
||||
custion_actions_dir = Path(sys.argv[0]).parent.joinpath("CustomActions")
|
||||
for file_path in chain(custion_actions_dir.glob("*.cpp"), custion_actions_dir.glob("*.h")):
|
||||
with open(file_path, "r", encoding="utf-8") as f:
|
||||
lines = f.readlines()
|
||||
for i, line in enumerate(lines):
|
||||
line = re.sub(r"\bRustDesk\b", app_name, line)
|
||||
line = line.replace(f"{app_name} v4 Printer Driver", "RustDesk v4 Printer Driver")
|
||||
lines[i] = line
|
||||
with open(file_path, "w", encoding="utf-8") as f:
|
||||
f.writelines(lines)
|
||||
|
||||
def gen_upgrade_info():
|
||||
def func(lines, index_start):
|
||||
@@ -542,3 +555,4 @@ if __name__ == "__main__":
|
||||
sys.exit(-1)
|
||||
|
||||
replace_app_name_in_langs(args.app_name)
|
||||
replace_app_name_in_custom_actions(args.app_name)
|
||||
|
||||
Reference in New Issue
Block a user