RemotePrinter.cpp 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518
  1. #include "pch.h"
  2. #include <Windows.h>
  3. #include <winspool.h>
  4. #include <setupapi.h>
  5. #include <memory>
  6. #include <string>
  7. #include <functional>
  8. #include <vector>
  9. #include <iostream>
  10. #include "Common.h"
  11. #pragma comment(lib, "setupapi.lib")
  12. #pragma comment(lib, "winspool.lib")
  13. namespace RemotePrinter
  14. {
  15. #define HRESULT_ERR_ELEMENT_NOT_FOUND 0x80070490
  16. LPCWCH RD_DRIVER_INF_PATH = L"drivers\\RustDeskPrinterDriver\\RustDeskPrinterDriver.inf";
  17. LPCWCH RD_PRINTER_PORT = L"RustDesk Printer";
  18. LPCWCH RD_PRINTER_NAME = L"RustDesk Printer";
  19. LPCWCH RD_PRINTER_DRIVER_NAME = L"RustDesk v4 Printer Driver";
  20. LPCWCH XCV_MONITOR_LOCAL_PORT = L",XcvMonitor Local Port";
  21. using FuncEnum = std::function<BOOL(DWORD level, LPBYTE pDriverInfo, DWORD cbBuf, LPDWORD pcbNeeded, LPDWORD pcReturned)>;
  22. template <typename T, typename R>
  23. using FuncOnData = std::function<std::shared_ptr<R>(const T &)>;
  24. template <typename R>
  25. using FuncOnNoData = std::function<std::shared_ptr<R>()>;
  26. template <class T, class R>
  27. std::shared_ptr<R> commonEnum(std::wstring funcName, FuncEnum func, DWORD level, FuncOnData<T, R> onData, FuncOnNoData<R> onNoData)
  28. {
  29. DWORD needed = 0;
  30. DWORD returned = 0;
  31. func(level, NULL, 0, &needed, &returned);
  32. if (needed == 0)
  33. {
  34. return onNoData();
  35. }
  36. std::vector<BYTE> buffer(needed);
  37. if (!func(level, buffer.data(), needed, &needed, &returned))
  38. {
  39. return nullptr;
  40. }
  41. T *pPortInfo = reinterpret_cast<T *>(buffer.data());
  42. for (DWORD i = 0; i < returned; i++)
  43. {
  44. auto r = onData(pPortInfo[i]);
  45. if (r)
  46. {
  47. return r;
  48. }
  49. }
  50. return onNoData();
  51. }
  52. BOOL isNameEqual(LPCWSTR lhs, LPCWSTR rhs)
  53. {
  54. // https://learn.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-lstrcmpiw
  55. // For some locales, the lstrcmpi function may be insufficient.
  56. // If this occurs, use `CompareStringEx` to ensure proper comparison.
  57. // For example, in Japan call with the NORM_IGNORECASE, NORM_IGNOREKANATYPE, and NORM_IGNOREWIDTH values to achieve the most appropriate non-exact string comparison.
  58. // Note that specifying these values slows performance, so use them only when necessary.
  59. //
  60. // No need to consider `CompareStringEx` for now.
  61. return lstrcmpiW(lhs, rhs) == 0 ? TRUE : FALSE;
  62. }
  63. BOOL enumPrinterPort(
  64. DWORD level,
  65. LPBYTE pPortInfo,
  66. DWORD cbBuf,
  67. LPDWORD pcbNeeded,
  68. LPDWORD pcReturned)
  69. {
  70. // https://learn.microsoft.com/en-us/windows/win32/printdocs/enumports
  71. // This is a blocking or synchronous function and might not return immediately.
  72. // How quickly this function returns depends on run-time factors
  73. // such as network status, print server configuration, and printer driver implementation factors that are difficult to predict when writing an application.
  74. // Calling this function from a thread that manages interaction with the user interface could make the application appear to be unresponsive.
  75. return EnumPortsW(NULL, level, pPortInfo, cbBuf, pcbNeeded, pcReturned);
  76. }
  77. BOOL isPortExists(LPCWSTR port)
  78. {
  79. auto onData = [port](const PORT_INFO_2 &info)
  80. {
  81. if (isNameEqual(info.pPortName, port) == TRUE) {
  82. return std::shared_ptr<BOOL>(new BOOL(TRUE));
  83. }
  84. else {
  85. return std::shared_ptr<BOOL>(nullptr);
  86. } };
  87. auto onNoData = []()
  88. { return nullptr; };
  89. auto res = commonEnum<PORT_INFO_2, BOOL>(L"EnumPortsW", enumPrinterPort, 2, onData, onNoData);
  90. if (res == nullptr)
  91. {
  92. return false;
  93. }
  94. else
  95. {
  96. return *res;
  97. }
  98. }
  99. BOOL executeOnLocalPort(LPCWSTR port, LPCWSTR command)
  100. {
  101. PRINTER_DEFAULTSW dft = {0};
  102. dft.DesiredAccess = SERVER_WRITE;
  103. HANDLE hMonitor = NULL;
  104. if (OpenPrinterW(const_cast<LPWSTR>(XCV_MONITOR_LOCAL_PORT), &hMonitor, &dft) == FALSE)
  105. {
  106. return FALSE;
  107. }
  108. DWORD outputNeeded = 0;
  109. DWORD status = 0;
  110. if (XcvDataW(hMonitor, command, (LPBYTE)port, (lstrlenW(port) + 1) * 2, NULL, 0, &outputNeeded, &status) == FALSE)
  111. {
  112. ClosePrinter(hMonitor);
  113. return FALSE;
  114. }
  115. ClosePrinter(hMonitor);
  116. return TRUE;
  117. }
  118. BOOL addLocalPort(LPCWSTR port)
  119. {
  120. return executeOnLocalPort(port, L"AddPort");
  121. }
  122. BOOL deleteLocalPort(LPCWSTR port)
  123. {
  124. return executeOnLocalPort(port, L"DeletePort");
  125. }
  126. BOOL checkAddLocalPort(LPCWSTR port)
  127. {
  128. if (!isPortExists(port))
  129. {
  130. return addLocalPort(port);
  131. }
  132. return TRUE;
  133. }
  134. std::wstring getPrinterInstalledOnPort(LPCWSTR port);
  135. BOOL checkDeleteLocalPort(LPCWSTR port)
  136. {
  137. if (isPortExists(port))
  138. {
  139. if (getPrinterInstalledOnPort(port) != L"")
  140. {
  141. WcaLog(LOGMSG_STANDARD, "The printer is installed on the port. Please remove the printer first.\n");
  142. return FALSE;
  143. }
  144. return deleteLocalPort(port);
  145. }
  146. return TRUE;
  147. }
  148. BOOL enumPrinterDriver(
  149. DWORD level,
  150. LPBYTE pDriverInfo,
  151. DWORD cbBuf,
  152. LPDWORD pcbNeeded,
  153. LPDWORD pcReturned)
  154. {
  155. // https://learn.microsoft.com/en-us/windows/win32/printdocs/enumprinterdrivers
  156. // This is a blocking or synchronous function and might not return immediately.
  157. // How quickly this function returns depends on run-time factors
  158. // such as network status, print server configuration, and printer driver implementation factors that are difficult to predict when writing an application.
  159. // Calling this function from a thread that manages interaction with the user interface could make the application appear to be unresponsive.
  160. return EnumPrinterDriversW(
  161. NULL,
  162. NULL,
  163. level,
  164. pDriverInfo,
  165. cbBuf,
  166. pcbNeeded,
  167. pcReturned);
  168. }
  169. DWORDLONG getInstalledDriverVersion(LPCWSTR name)
  170. {
  171. auto onData = [name](const DRIVER_INFO_6W &info)
  172. {
  173. if (isNameEqual(name, info.pName) == TRUE)
  174. {
  175. return std::shared_ptr<DWORDLONG>(new DWORDLONG(info.dwlDriverVersion));
  176. }
  177. else
  178. {
  179. return std::shared_ptr<DWORDLONG>(nullptr);
  180. } };
  181. auto onNoData = []()
  182. { return nullptr; };
  183. auto res = commonEnum<DRIVER_INFO_6W, DWORDLONG>(L"EnumPrinterDriversW", enumPrinterDriver, 6, onData, onNoData);
  184. if (res == nullptr)
  185. {
  186. return 0;
  187. }
  188. else
  189. {
  190. return *res;
  191. }
  192. }
  193. std::wstring findInf(LPCWSTR name)
  194. {
  195. auto onData = [name](const DRIVER_INFO_8W &info)
  196. {
  197. if (isNameEqual(name, info.pName) == TRUE)
  198. {
  199. return std::shared_ptr<std::wstring>(new std::wstring(info.pszInfPath));
  200. }
  201. else
  202. {
  203. return std::shared_ptr<std::wstring>(nullptr);
  204. } };
  205. auto onNoData = []()
  206. { return nullptr; };
  207. auto res = commonEnum<DRIVER_INFO_8W, std::wstring>(L"EnumPrinterDriversW", enumPrinterDriver, 8, onData, onNoData);
  208. if (res == nullptr)
  209. {
  210. return L"";
  211. }
  212. else
  213. {
  214. return *res;
  215. }
  216. }
  217. BOOL deletePrinterDriver(LPCWSTR name)
  218. {
  219. // If the printer is used after the spooler service is started. E.g., printing a document through RustDesk Printer.
  220. // `DeletePrinterDriverExW()` may fail with `ERROR_PRINTER_DRIVER_IN_USE`(3001, 0xBB9).
  221. // We can only ignore this error for now.
  222. // Though restarting the spooler service is a solution, it's not a good idea to restart the service.
  223. //
  224. // Deleting the printer driver after deleting the printer is a common practice.
  225. // No idea why `DeletePrinterDriverExW()` fails with `ERROR_UNKNOWN_PRINTER_DRIVER` after using the printer once.
  226. // https://github.com/ChromiumWebApps/chromium/blob/c7361d39be8abd1574e6ce8957c8dbddd4c6ccf7/cloud_print/virtual_driver/win/install/setup.cc#L422
  227. // AnyDesk printer driver and the simplest printer driver also have the same issue.
  228. BOOL res = DeletePrinterDriverExW(NULL, NULL, const_cast<LPWSTR>(name), DPD_DELETE_ALL_FILES, 0);
  229. if (res == FALSE)
  230. {
  231. DWORD error = GetLastError();
  232. if (error == ERROR_UNKNOWN_PRINTER_DRIVER)
  233. {
  234. return TRUE;
  235. }
  236. else
  237. {
  238. WcaLog(LOGMSG_STANDARD, "Failed to delete printer driver. Error (%d)\n", error);
  239. }
  240. }
  241. return res;
  242. }
  243. BOOL deletePrinterDriverPackage(const std::wstring &inf)
  244. {
  245. // https://learn.microsoft.com/en-us/windows/win32/printdocs/deleteprinterdriverpackage
  246. // This function is a blocking or synchronous function and might not return immediately.
  247. // 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.
  248. // Calling this function from a thread that manages interaction with the user interface could make the application appear to be unresponsive.
  249. int tries = 3;
  250. HRESULT result = S_FALSE;
  251. while ((result = DeletePrinterDriverPackage(NULL, inf.c_str(), NULL)) != S_OK)
  252. {
  253. if (result == HRESULT_ERR_ELEMENT_NOT_FOUND)
  254. {
  255. return TRUE;
  256. }
  257. WcaLog(LOGMSG_STANDARD, "Failed to delete printer driver package. HRESULT (%d)\n", result);
  258. tries--;
  259. if (tries <= 0)
  260. {
  261. return FALSE;
  262. }
  263. Sleep(2000);
  264. }
  265. return S_OK;
  266. }
  267. BOOL uninstallDriver(LPCWSTR name)
  268. {
  269. auto infFile = findInf(name);
  270. if (!deletePrinterDriver(name))
  271. {
  272. return FALSE;
  273. }
  274. if (infFile != L"" && !deletePrinterDriverPackage(infFile))
  275. {
  276. return FALSE;
  277. }
  278. return TRUE;
  279. }
  280. BOOL installDriver(LPCWSTR name, LPCWSTR inf)
  281. {
  282. DWORD size = MAX_PATH * 10;
  283. wchar_t package_path[MAX_PATH * 10] = {0};
  284. HRESULT result = UploadPrinterDriverPackage(
  285. NULL, inf, NULL,
  286. UPDP_SILENT_UPLOAD | UPDP_UPLOAD_ALWAYS, NULL, package_path, &size);
  287. if (result != S_OK)
  288. {
  289. WcaLog(LOGMSG_STANDARD, "Uploading the printer driver package to the driver cache silently, failed. Will retry with user UI. HRESULT (%d)\n", result);
  290. result = UploadPrinterDriverPackage(
  291. NULL, inf, NULL, UPDP_UPLOAD_ALWAYS,
  292. GetForegroundWindow(), package_path, &size);
  293. if (result != S_OK)
  294. {
  295. WcaLog(LOGMSG_STANDARD, "Uploading the printer driver package to the driver cache failed with user UI. Aborting...\n");
  296. return FALSE;
  297. }
  298. }
  299. result = InstallPrinterDriverFromPackage(
  300. NULL, package_path, name, NULL, IPDFP_COPY_ALL_FILES);
  301. if (result != S_OK)
  302. {
  303. WcaLog(LOGMSG_STANDARD, "Installing the printer driver failed. HRESULT (%d)\n", result);
  304. }
  305. return result == S_OK;
  306. }
  307. BOOL enumLocalPrinter(
  308. DWORD level,
  309. LPBYTE pPrinterInfo,
  310. DWORD cbBuf,
  311. LPDWORD pcbNeeded,
  312. LPDWORD pcReturned)
  313. {
  314. // https://learn.microsoft.com/en-us/windows/win32/printdocs/enumprinters
  315. // This is a blocking or synchronous function and might not return immediately.
  316. // How quickly this function returns depends on run-time factors
  317. // such as network status, print server configuration, and printer driver implementation factors that are difficult to predict when writing an application.
  318. // Calling this function from a thread that manages interaction with the user interface could make the application appear to be unresponsive.
  319. return EnumPrintersW(PRINTER_ENUM_LOCAL, NULL, level, pPrinterInfo, cbBuf, pcbNeeded, pcReturned);
  320. }
  321. BOOL isPrinterAdded(LPCWSTR name)
  322. {
  323. auto onData = [name](const PRINTER_INFO_1W &info)
  324. {
  325. if (isNameEqual(name, info.pName) == TRUE)
  326. {
  327. return std::shared_ptr<BOOL>(new BOOL(TRUE));
  328. }
  329. else
  330. {
  331. return std::shared_ptr<BOOL>(nullptr);
  332. } };
  333. auto onNoData = []()
  334. { return nullptr; };
  335. auto res = commonEnum<PRINTER_INFO_1W, BOOL>(L"EnumPrintersW", enumLocalPrinter, 1, onData, onNoData);
  336. if (res == nullptr)
  337. {
  338. return FALSE;
  339. }
  340. else
  341. {
  342. return *res;
  343. }
  344. }
  345. std::wstring getPrinterInstalledOnPort(LPCWSTR port)
  346. {
  347. auto onData = [port](const PRINTER_INFO_2W &info)
  348. {
  349. if (isNameEqual(port, info.pPortName) == TRUE)
  350. {
  351. return std::shared_ptr<std::wstring>(new std::wstring(info.pPrinterName));
  352. }
  353. else
  354. {
  355. return std::shared_ptr<std::wstring>(nullptr);
  356. } };
  357. auto onNoData = []()
  358. { return nullptr; };
  359. auto res = commonEnum<PRINTER_INFO_2W, std::wstring>(L"EnumPrintersW", enumLocalPrinter, 2, onData, onNoData);
  360. if (res == nullptr)
  361. {
  362. return L"";
  363. }
  364. else
  365. {
  366. return *res;
  367. }
  368. }
  369. BOOL addPrinter(LPCWSTR name, LPCWSTR driver, LPCWSTR port)
  370. {
  371. PRINTER_INFO_2W printerInfo = {0};
  372. printerInfo.pPrinterName = const_cast<LPWSTR>(name);
  373. printerInfo.pPortName = const_cast<LPWSTR>(port);
  374. printerInfo.pDriverName = const_cast<LPWSTR>(driver);
  375. printerInfo.pPrintProcessor = const_cast<LPWSTR>(L"WinPrint");
  376. printerInfo.pDatatype = const_cast<LPWSTR>(L"RAW");
  377. printerInfo.Attributes = PRINTER_ATTRIBUTE_LOCAL;
  378. HANDLE hPrinter = AddPrinterW(NULL, 2, (LPBYTE)&printerInfo);
  379. return hPrinter == NULL ? FALSE : TRUE;
  380. }
  381. VOID deletePrinter(LPCWSTR name)
  382. {
  383. PRINTER_DEFAULTSW dft = {0};
  384. dft.DesiredAccess = PRINTER_ALL_ACCESS;
  385. HANDLE hPrinter = NULL;
  386. if (OpenPrinterW(const_cast<LPWSTR>(name), &hPrinter, &dft) == FALSE)
  387. {
  388. DWORD error = GetLastError();
  389. if (error == ERROR_INVALID_PRINTER_NAME)
  390. {
  391. return;
  392. }
  393. WcaLog(LOGMSG_STANDARD, "Failed to open printer. error (%d)\n", error);
  394. return;
  395. }
  396. if (SetPrinterW(hPrinter, 0, NULL, PRINTER_CONTROL_PURGE) == FALSE)
  397. {
  398. ClosePrinter(hPrinter);
  399. WcaLog(LOGMSG_STANDARD, "Failed to purge printer queue. error (%d)\n", GetLastError());
  400. return;
  401. }
  402. if (DeletePrinter(hPrinter) == FALSE)
  403. {
  404. ClosePrinter(hPrinter);
  405. WcaLog(LOGMSG_STANDARD, "Failed to delete printer. error (%d)\n", GetLastError());
  406. return;
  407. }
  408. ClosePrinter(hPrinter);
  409. }
  410. bool FileExists(const std::wstring &filePath)
  411. {
  412. DWORD fileAttributes = GetFileAttributes(filePath.c_str());
  413. return (fileAttributes != INVALID_FILE_ATTRIBUTES && !(fileAttributes & FILE_ATTRIBUTE_DIRECTORY));
  414. }
  415. // Steps:
  416. // 1. Add the local port.
  417. // 2. Check if the driver is installed.
  418. // Uninstall the existing driver if it is installed.
  419. // We should not check the driver version because the driver is deployed with the application.
  420. // It's better to uninstall the existing driver and install the driver from the application.
  421. // 3. Add the printer.
  422. VOID installUpdatePrinter(const std::wstring &installFolder)
  423. {
  424. const std::wstring infFile = installFolder + L"\\" + RemotePrinter::RD_DRIVER_INF_PATH;
  425. if (!FileExists(infFile))
  426. {
  427. WcaLog(LOGMSG_STANDARD, "Printer driver INF file not found, aborting...\n");
  428. return;
  429. }
  430. if (!checkAddLocalPort(RD_PRINTER_PORT))
  431. {
  432. WcaLog(LOGMSG_STANDARD, "Failed to check add local port, error (%d)\n", GetLastError());
  433. return;
  434. }
  435. else
  436. {
  437. WcaLog(LOGMSG_STANDARD, "Local port added successfully\n");
  438. }
  439. if (getInstalledDriverVersion(RD_PRINTER_DRIVER_NAME) > 0)
  440. {
  441. deletePrinter(RD_PRINTER_NAME);
  442. if (FALSE == uninstallDriver(RD_PRINTER_DRIVER_NAME))
  443. {
  444. WcaLog(LOGMSG_STANDARD, "Failed to uninstall previous printer driver, error (%d)\n", GetLastError());
  445. }
  446. }
  447. if (FALSE == installDriver(RD_PRINTER_DRIVER_NAME, infFile.c_str()))
  448. {
  449. WcaLog(LOGMSG_STANDARD, "Driver installation failed, still try to add the printer\n");
  450. }
  451. else
  452. {
  453. WcaLog(LOGMSG_STANDARD, "Driver installed successfully\n");
  454. }
  455. if (FALSE == addPrinter(RD_PRINTER_NAME, RD_PRINTER_DRIVER_NAME, RD_PRINTER_PORT))
  456. {
  457. WcaLog(LOGMSG_STANDARD, "Failed to add printer, error (%d)\n", GetLastError());
  458. }
  459. else
  460. {
  461. WcaLog(LOGMSG_STANDARD, "Printer installed successfully\n");
  462. }
  463. }
  464. VOID uninstallPrinter()
  465. {
  466. deletePrinter(RD_PRINTER_NAME);
  467. WcaLog(LOGMSG_STANDARD, "Deleted the printer\n");
  468. uninstallDriver(RD_PRINTER_DRIVER_NAME);
  469. WcaLog(LOGMSG_STANDARD, "Uninstalled the printer driver\n");
  470. checkDeleteLocalPort(RD_PRINTER_PORT);
  471. WcaLog(LOGMSG_STANDARD, "Deleted the local port\n");
  472. }
  473. }