1 /*
2  * PROJECT:     ReactOS Spooler API
3  * LICENSE:     GPL-2.0+ (https://spdx.org/licenses/GPL-2.0+)
4  * PURPOSE:     Functions related to Printers and printing
5  * COPYRIGHT:   Copyright 2015-2017 Colin Finck (colin@reactos.org)
6  */
7 
8 #include "precomp.h"
9 
10 // Local Constants
11 
12 /** And the award for the most confusingly named setting goes to "Device", for storing the default printer of the current user.
13     Ok, I admit that this has historical reasons. It's still not straightforward in any way though! */
14 static const WCHAR wszWindowsKey[] = L"Software\\Microsoft\\Windows NT\\CurrentVersion\\Windows";
15 static const WCHAR wszDeviceValue[] = L"Device";
16 
17 static void
18 _MarshallUpPrinterInfo(PBYTE* ppPrinterInfo, DWORD Level)
19 {
20     // Replace relative offset addresses in the output by absolute pointers and advance to the next structure.
21     if (Level == 0)
22     {
23         PPRINTER_INFO_STRESS pPrinterInfo0 = (PPRINTER_INFO_STRESS)(*ppPrinterInfo);
24 
25         pPrinterInfo0->pPrinterName = (PWSTR)((ULONG_PTR)pPrinterInfo0->pPrinterName + (ULONG_PTR)pPrinterInfo0);
26 
27         if (pPrinterInfo0->pServerName)
28             pPrinterInfo0->pServerName = (PWSTR)((ULONG_PTR)pPrinterInfo0->pServerName + (ULONG_PTR)pPrinterInfo0);
29 
30         *ppPrinterInfo += sizeof(PRINTER_INFO_STRESS);
31     }
32     else if (Level == 1)
33     {
34         PPRINTER_INFO_1W pPrinterInfo1 = (PPRINTER_INFO_1W)(*ppPrinterInfo);
35 
36         pPrinterInfo1->pName = (PWSTR)((ULONG_PTR)pPrinterInfo1->pName + (ULONG_PTR)pPrinterInfo1);
37         pPrinterInfo1->pDescription = (PWSTR)((ULONG_PTR)pPrinterInfo1->pDescription + (ULONG_PTR)pPrinterInfo1);
38         pPrinterInfo1->pComment = (PWSTR)((ULONG_PTR)pPrinterInfo1->pComment + (ULONG_PTR)pPrinterInfo1);
39 
40         *ppPrinterInfo += sizeof(PRINTER_INFO_1W);
41     }
42     else if (Level == 2)
43     {
44         PPRINTER_INFO_2W pPrinterInfo2 = (PPRINTER_INFO_2W)(*ppPrinterInfo);
45 
46         pPrinterInfo2->pPrinterName = (PWSTR)((ULONG_PTR)pPrinterInfo2->pPrinterName + (ULONG_PTR)pPrinterInfo2);
47         pPrinterInfo2->pShareName = (PWSTR)((ULONG_PTR)pPrinterInfo2->pShareName + (ULONG_PTR)pPrinterInfo2);
48         pPrinterInfo2->pPortName = (PWSTR)((ULONG_PTR)pPrinterInfo2->pPortName + (ULONG_PTR)pPrinterInfo2);
49         pPrinterInfo2->pDriverName = (PWSTR)((ULONG_PTR)pPrinterInfo2->pDriverName + (ULONG_PTR)pPrinterInfo2);
50         pPrinterInfo2->pComment = (PWSTR)((ULONG_PTR)pPrinterInfo2->pComment + (ULONG_PTR)pPrinterInfo2);
51         pPrinterInfo2->pLocation = (PWSTR)((ULONG_PTR)pPrinterInfo2->pLocation + (ULONG_PTR)pPrinterInfo2);
52         pPrinterInfo2->pDevMode = (PDEVMODEW)((ULONG_PTR)pPrinterInfo2->pDevMode + (ULONG_PTR)pPrinterInfo2);
53         pPrinterInfo2->pSepFile = (PWSTR)((ULONG_PTR)pPrinterInfo2->pSepFile + (ULONG_PTR)pPrinterInfo2);
54         pPrinterInfo2->pPrintProcessor = (PWSTR)((ULONG_PTR)pPrinterInfo2->pPrintProcessor + (ULONG_PTR)pPrinterInfo2);
55         pPrinterInfo2->pDatatype = (PWSTR)((ULONG_PTR)pPrinterInfo2->pDatatype + (ULONG_PTR)pPrinterInfo2);
56         pPrinterInfo2->pParameters = (PWSTR)((ULONG_PTR)pPrinterInfo2->pParameters + (ULONG_PTR)pPrinterInfo2);
57 
58         if (pPrinterInfo2->pServerName)
59             pPrinterInfo2->pServerName = (PWSTR)((ULONG_PTR)pPrinterInfo2->pServerName + (ULONG_PTR)pPrinterInfo2);
60 
61         if (pPrinterInfo2->pSecurityDescriptor)
62             pPrinterInfo2->pSecurityDescriptor = (PSECURITY_DESCRIPTOR)((ULONG_PTR)pPrinterInfo2->pSecurityDescriptor + (ULONG_PTR)pPrinterInfo2);
63 
64         *ppPrinterInfo += sizeof(PRINTER_INFO_2W);
65     }
66     else if (Level == 3)
67     {
68         PPRINTER_INFO_3 pPrinterInfo3 = (PPRINTER_INFO_3)(*ppPrinterInfo);
69 
70         pPrinterInfo3->pSecurityDescriptor = (PSECURITY_DESCRIPTOR)((ULONG_PTR)pPrinterInfo3->pSecurityDescriptor + (ULONG_PTR)pPrinterInfo3);
71 
72         *ppPrinterInfo += sizeof(PRINTER_INFO_3);
73     }
74     else if (Level == 4)
75     {
76         PPRINTER_INFO_4W pPrinterInfo4 = (PPRINTER_INFO_4W)(*ppPrinterInfo);
77 
78         pPrinterInfo4->pPrinterName = (PWSTR)((ULONG_PTR)pPrinterInfo4->pPrinterName + (ULONG_PTR)pPrinterInfo4);
79 
80         if (pPrinterInfo4->pServerName)
81             pPrinterInfo4->pServerName = (PWSTR)((ULONG_PTR)pPrinterInfo4->pServerName + (ULONG_PTR)pPrinterInfo4);
82 
83         *ppPrinterInfo += sizeof(PRINTER_INFO_4W);
84     }
85     else if (Level == 5)
86     {
87         PPRINTER_INFO_5W pPrinterInfo5 = (PPRINTER_INFO_5W)(*ppPrinterInfo);
88 
89         pPrinterInfo5->pPrinterName = (PWSTR)((ULONG_PTR)pPrinterInfo5->pPrinterName + (ULONG_PTR)pPrinterInfo5);
90         pPrinterInfo5->pPortName = (PWSTR)((ULONG_PTR)pPrinterInfo5->pPortName + (ULONG_PTR)pPrinterInfo5);
91 
92         *ppPrinterInfo += sizeof(PRINTER_INFO_5W);
93     }
94     else if (Level == 6)
95     {
96         *ppPrinterInfo += sizeof(PRINTER_INFO_6);
97     }
98     else if (Level == 7)
99     {
100         PPRINTER_INFO_7W pPrinterInfo7 = (PPRINTER_INFO_7W)(*ppPrinterInfo);
101 
102         if (pPrinterInfo7->pszObjectGUID)
103             pPrinterInfo7->pszObjectGUID = (PWSTR)((ULONG_PTR)pPrinterInfo7->pszObjectGUID + (ULONG_PTR)pPrinterInfo7);
104 
105         *ppPrinterInfo += sizeof(PRINTER_INFO_7W);
106     }
107     else if (Level == 8)
108     {
109         PPRINTER_INFO_8W pPrinterInfo8 = (PPRINTER_INFO_8W)(*ppPrinterInfo);
110 
111         pPrinterInfo8->pDevMode = (PDEVMODEW)((ULONG_PTR)pPrinterInfo8->pDevMode + (ULONG_PTR)pPrinterInfo8);
112 
113         *ppPrinterInfo += sizeof(PRINTER_INFO_8W);
114     }
115     else if (Level == 9)
116     {
117         PPRINTER_INFO_9W pPrinterInfo9 = (PPRINTER_INFO_9W)(*ppPrinterInfo);
118 
119         pPrinterInfo9->pDevMode = (PDEVMODEW)((ULONG_PTR)pPrinterInfo9->pDevMode + (ULONG_PTR)pPrinterInfo9);
120 
121         *ppPrinterInfo += sizeof(PRINTER_INFO_9W);
122     }
123 }
124 
125 static DWORD
126 _StartDocPrinterSpooled(PSPOOLER_HANDLE pHandle, PDOC_INFO_1W pDocInfo1, PADDJOB_INFO_1W pAddJobInfo1)
127 {
128     DWORD cbNeeded;
129     DWORD dwErrorCode;
130     PJOB_INFO_1W pJobInfo1 = NULL;
131 
132     // Create the spool file.
133     pHandle->hSPLFile = CreateFileW(pAddJobInfo1->Path, GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, CREATE_ALWAYS, 0, NULL);
134     if (pHandle->hSPLFile == INVALID_HANDLE_VALUE)
135     {
136         dwErrorCode = GetLastError();
137         ERR("CreateFileW failed for \"%S\" with error %lu!\n", pAddJobInfo1->Path, dwErrorCode);
138         goto Cleanup;
139     }
140 
141     // Get the size of the job information.
142     GetJobW((HANDLE)pHandle, pAddJobInfo1->JobId, 1, NULL, 0, &cbNeeded);
143     if (GetLastError() != ERROR_INSUFFICIENT_BUFFER)
144     {
145         dwErrorCode = GetLastError();
146         ERR("GetJobW failed with error %lu!\n", dwErrorCode);
147         goto Cleanup;
148     }
149 
150     // Allocate enough memory for the returned job information.
151     pJobInfo1 = HeapAlloc(hProcessHeap, 0, cbNeeded);
152     if (!pJobInfo1)
153     {
154         dwErrorCode = ERROR_NOT_ENOUGH_MEMORY;
155         ERR("HeapAlloc failed!\n");
156         goto Cleanup;
157     }
158 
159     // Get the job information.
160     if (!GetJobW((HANDLE)pHandle, pAddJobInfo1->JobId, 1, (PBYTE)pJobInfo1, cbNeeded, &cbNeeded))
161     {
162         dwErrorCode = GetLastError();
163         ERR("GetJobW failed with error %lu!\n", dwErrorCode);
164         goto Cleanup;
165     }
166 
167     // Add our document information.
168     if (pDocInfo1->pDatatype)
169         pJobInfo1->pDatatype = pDocInfo1->pDatatype;
170 
171     pJobInfo1->pDocument = pDocInfo1->pDocName;
172 
173     // Set the new job information.
174     if (!SetJobW((HANDLE)pHandle, pAddJobInfo1->JobId, 1, (PBYTE)pJobInfo1, 0))
175     {
176         dwErrorCode = GetLastError();
177         ERR("SetJobW failed with error %lu!\n", dwErrorCode);
178         goto Cleanup;
179     }
180 
181     // We were successful!
182     pHandle->dwJobID = pAddJobInfo1->JobId;
183     dwErrorCode = ERROR_SUCCESS;
184 
185 Cleanup:
186     if (pJobInfo1)
187         HeapFree(hProcessHeap, 0, pJobInfo1);
188 
189     return dwErrorCode;
190 }
191 
192 static DWORD
193 _StartDocPrinterWithRPC(PSPOOLER_HANDLE pHandle, PDOC_INFO_1W pDocInfo1)
194 {
195     DWORD dwErrorCode;
196     WINSPOOL_DOC_INFO_CONTAINER DocInfoContainer;
197 
198     DocInfoContainer.Level = 1;
199     DocInfoContainer.DocInfo.pDocInfo1 = (WINSPOOL_DOC_INFO_1*)pDocInfo1;
200 
201     RpcTryExcept
202     {
203         dwErrorCode = _RpcStartDocPrinter(pHandle->hPrinter, &DocInfoContainer, &pHandle->dwJobID);
204     }
205     RpcExcept(EXCEPTION_EXECUTE_HANDLER)
206     {
207         dwErrorCode = RpcExceptionCode();
208         ERR("_RpcStartDocPrinter failed with exception code %lu!\n", dwErrorCode);
209     }
210     RpcEndExcept;
211 
212     return dwErrorCode;
213 }
214 
215 HANDLE WINAPI
216 AddPrinterW(PWSTR pName, DWORD Level, PBYTE pPrinter)
217 {
218     UNIMPLEMENTED;
219     return NULL;
220 }
221 
222 BOOL WINAPI
223 ClosePrinter(HANDLE hPrinter)
224 {
225     DWORD dwErrorCode;
226     PSPOOLER_HANDLE pHandle = (PSPOOLER_HANDLE)hPrinter;
227 
228     // Sanity checks.
229     if (!pHandle)
230     {
231         dwErrorCode = ERROR_INVALID_HANDLE;
232         goto Cleanup;
233     }
234 
235     // Do the RPC call.
236     RpcTryExcept
237     {
238         dwErrorCode = _RpcClosePrinter(&pHandle->hPrinter);
239     }
240     RpcExcept(EXCEPTION_EXECUTE_HANDLER)
241     {
242         dwErrorCode = RpcExceptionCode();
243         ERR("_RpcClosePrinter failed with exception code %lu!\n", dwErrorCode);
244     }
245     RpcEndExcept;
246 
247     // Close any open file handle.
248     if (pHandle->hSPLFile != INVALID_HANDLE_VALUE)
249         CloseHandle(pHandle->hSPLFile);
250 
251     // Free the memory for the handle.
252     HeapFree(hProcessHeap, 0, pHandle);
253 
254 Cleanup:
255     SetLastError(dwErrorCode);
256     return (dwErrorCode == ERROR_SUCCESS);
257 }
258 
259 DWORD WINAPI
260 DeviceCapabilitiesA(LPCSTR pDevice, LPCSTR pPort, WORD fwCapability, LPSTR pOutput, const DEVMODEA* pDevMode)
261 {
262     return 0;
263 }
264 
265 DWORD WINAPI
266 DeviceCapabilitiesW(LPCWSTR pDevice, LPCWSTR pPort, WORD fwCapability, LPWSTR pOutput, const DEVMODEW* pDevMode)
267 {
268     return 0;
269 }
270 
271 LONG WINAPI
272 DocumentPropertiesA(HWND hWnd, HANDLE hPrinter, LPSTR pDeviceName, PDEVMODEA pDevModeOutput, PDEVMODEA pDevModeInput, DWORD fMode)
273 {
274     return 0;
275 }
276 
277 LONG WINAPI
278 DocumentPropertiesW(HWND hWnd, HANDLE hPrinter, LPWSTR pDeviceName, PDEVMODEW pDevModeOutput, PDEVMODEW pDevModeInput, DWORD fMode)
279 {
280     return 0;
281 }
282 
283 BOOL WINAPI
284 EndDocPrinter(HANDLE hPrinter)
285 {
286     DWORD dwErrorCode;
287     PSPOOLER_HANDLE pHandle = (PSPOOLER_HANDLE)hPrinter;
288 
289     // Sanity checks.
290     if (!pHandle)
291     {
292         dwErrorCode = ERROR_INVALID_HANDLE;
293         goto Cleanup;
294     }
295 
296     if (pHandle->hSPLFile != INVALID_HANDLE_VALUE)
297     {
298         // For spooled jobs, the document is finished by calling _RpcScheduleJob.
299         RpcTryExcept
300         {
301             dwErrorCode = _RpcScheduleJob(pHandle->hPrinter, pHandle->dwJobID);
302         }
303         RpcExcept(EXCEPTION_EXECUTE_HANDLER)
304         {
305             dwErrorCode = RpcExceptionCode();
306             ERR("_RpcScheduleJob failed with exception code %lu!\n", dwErrorCode);
307         }
308         RpcEndExcept;
309 
310         // Close the spool file handle.
311         CloseHandle(pHandle->hSPLFile);
312     }
313     else
314     {
315         // In all other cases, just call _RpcEndDocPrinter.
316         RpcTryExcept
317         {
318             dwErrorCode = _RpcEndDocPrinter(pHandle->hPrinter);
319         }
320         RpcExcept(EXCEPTION_EXECUTE_HANDLER)
321         {
322             dwErrorCode = RpcExceptionCode();
323             ERR("_RpcEndDocPrinter failed with exception code %lu!\n", dwErrorCode);
324         }
325         RpcEndExcept;
326     }
327 
328     // A new document can now be started again.
329     pHandle->bStartedDoc = FALSE;
330 
331 Cleanup:
332     SetLastError(dwErrorCode);
333     return (dwErrorCode == ERROR_SUCCESS);
334 }
335 
336 BOOL WINAPI
337 EndPagePrinter(HANDLE hPrinter)
338 {
339     DWORD dwErrorCode;
340     PSPOOLER_HANDLE pHandle = (PSPOOLER_HANDLE)hPrinter;
341 
342     // Sanity checks.
343     if (!pHandle)
344     {
345         dwErrorCode = ERROR_INVALID_HANDLE;
346         goto Cleanup;
347     }
348 
349     if (pHandle->hSPLFile != INVALID_HANDLE_VALUE)
350     {
351         // For spooled jobs, we don't need to do anything.
352         dwErrorCode = ERROR_SUCCESS;
353     }
354     else
355     {
356         // In all other cases, just call _RpcEndPagePrinter.
357         RpcTryExcept
358         {
359             dwErrorCode = _RpcEndPagePrinter(pHandle->hPrinter);
360         }
361         RpcExcept(EXCEPTION_EXECUTE_HANDLER)
362         {
363             dwErrorCode = RpcExceptionCode();
364             ERR("_RpcEndPagePrinter failed with exception code %lu!\n", dwErrorCode);
365         }
366         RpcEndExcept;
367     }
368 
369 Cleanup:
370     SetLastError(dwErrorCode);
371     return (dwErrorCode == ERROR_SUCCESS);
372 }
373 
374 BOOL WINAPI
375 EnumPrintersA(DWORD Flags, PSTR Name, DWORD Level, PBYTE pPrinterEnum, DWORD cbBuf, PDWORD pcbNeeded, PDWORD pcReturned)
376 {
377     return FALSE;
378 }
379 
380 BOOL WINAPI
381 EnumPrintersW(DWORD Flags, PWSTR Name, DWORD Level, PBYTE pPrinterEnum, DWORD cbBuf, PDWORD pcbNeeded, PDWORD pcReturned)
382 {
383     DWORD dwErrorCode;
384 
385     // Dismiss invalid levels already at this point.
386     if (Level == 3 || Level > 5)
387     {
388         dwErrorCode = ERROR_INVALID_LEVEL;
389         goto Cleanup;
390     }
391 
392     if (cbBuf && pPrinterEnum)
393         ZeroMemory(pPrinterEnum, cbBuf);
394 
395     // Do the RPC call
396     RpcTryExcept
397     {
398         dwErrorCode = _RpcEnumPrinters(Flags, Name, Level, pPrinterEnum, cbBuf, pcbNeeded, pcReturned);
399     }
400     RpcExcept(EXCEPTION_EXECUTE_HANDLER)
401     {
402         dwErrorCode = RpcExceptionCode();
403         ERR("_RpcEnumPrinters failed with exception code %lu!\n", dwErrorCode);
404     }
405     RpcEndExcept;
406 
407     if (dwErrorCode == ERROR_SUCCESS)
408     {
409         DWORD i;
410         PBYTE p = pPrinterEnum;
411 
412         for (i = 0; i < *pcReturned; i++)
413             _MarshallUpPrinterInfo(&p, Level);
414     }
415 
416 Cleanup:
417     SetLastError(dwErrorCode);
418     return (dwErrorCode == ERROR_SUCCESS);
419 }
420 
421 BOOL WINAPI
422 GetDefaultPrinterA(LPSTR pszBuffer, LPDWORD pcchBuffer)
423 {
424     DWORD dwErrorCode;
425     PWSTR pwszBuffer = NULL;
426 
427     // Sanity check.
428     if (!pcchBuffer)
429     {
430         dwErrorCode = ERROR_INVALID_PARAMETER;
431         goto Cleanup;
432     }
433 
434     // Check if an ANSI buffer was given and if so, allocate a Unicode buffer of the same size.
435     if (pszBuffer && *pcchBuffer)
436     {
437         pwszBuffer = HeapAlloc(hProcessHeap, 0, *pcchBuffer * sizeof(WCHAR));
438         if (!pwszBuffer)
439         {
440             dwErrorCode = ERROR_NOT_ENOUGH_MEMORY;
441             ERR("HeapAlloc failed!\n");
442             goto Cleanup;
443         }
444     }
445 
446     if (!GetDefaultPrinterW(pwszBuffer, pcchBuffer))
447     {
448         dwErrorCode = GetLastError();
449         goto Cleanup;
450     }
451 
452     dwErrorCode = ERROR_SUCCESS;
453 
454 Cleanup:
455     if (pwszBuffer)
456         HeapFree(hProcessHeap, 0, pwszBuffer);
457 
458     SetLastError(dwErrorCode);
459     return (dwErrorCode == ERROR_SUCCESS);
460 }
461 
462 BOOL WINAPI
463 GetDefaultPrinterW(LPWSTR pszBuffer, LPDWORD pcchBuffer)
464 {
465     DWORD cbNeeded;
466     DWORD cchInputBuffer;
467     DWORD dwErrorCode;
468     HKEY hWindowsKey = NULL;
469     PWSTR pwszDevice = NULL;
470     PWSTR pwszComma;
471 
472     // Sanity check.
473     if (!pcchBuffer)
474     {
475         dwErrorCode = ERROR_INVALID_PARAMETER;
476         goto Cleanup;
477     }
478 
479     cchInputBuffer = *pcchBuffer;
480 
481     // Open the registry key where the default printer for the current user is stored.
482     dwErrorCode = (DWORD)RegOpenKeyExW(HKEY_CURRENT_USER, wszWindowsKey, 0, KEY_READ, &hWindowsKey);
483     if (dwErrorCode != ERROR_SUCCESS)
484     {
485         ERR("RegOpenKeyExW failed with status %lu!\n", dwErrorCode);
486         goto Cleanup;
487     }
488 
489     // Determine the size of the required buffer.
490     dwErrorCode = (DWORD)RegQueryValueExW(hWindowsKey, wszDeviceValue, NULL, NULL, NULL, &cbNeeded);
491     if (dwErrorCode != ERROR_SUCCESS)
492     {
493         ERR("RegQueryValueExW failed with status %lu!\n", dwErrorCode);
494         goto Cleanup;
495     }
496 
497     // Allocate it.
498     pwszDevice = HeapAlloc(hProcessHeap, 0, cbNeeded);
499     if (!pwszDevice)
500     {
501         dwErrorCode = ERROR_NOT_ENOUGH_MEMORY;
502         ERR("HeapAlloc failed!\n");
503         goto Cleanup;
504     }
505 
506     // Now get the actual value.
507     dwErrorCode = RegQueryValueExW(hWindowsKey, wszDeviceValue, NULL, NULL, (PBYTE)pwszDevice, &cbNeeded);
508     if (dwErrorCode != ERROR_SUCCESS)
509     {
510         ERR("RegQueryValueExW failed with status %lu!\n", dwErrorCode);
511         goto Cleanup;
512     }
513 
514     // We get a string "<Printer Name>,winspool,<Port>:".
515     // Extract the printer name from it.
516     pwszComma = wcschr(pwszDevice, L',');
517     if (!pwszComma)
518     {
519         ERR("Found no or invalid default printer: %S!\n", pwszDevice);
520         dwErrorCode = ERROR_INVALID_NAME;
521         goto Cleanup;
522     }
523 
524     // Store the length of the Printer Name (including the terminating NUL character!) in *pcchBuffer.
525     *pcchBuffer = pwszComma - pwszDevice + 1;
526 
527     // Check if the supplied buffer is large enough.
528     if (cchInputBuffer < *pcchBuffer)
529     {
530         dwErrorCode = ERROR_INSUFFICIENT_BUFFER;
531         goto Cleanup;
532     }
533 
534     // Copy the default printer.
535     *pwszComma = 0;
536     CopyMemory(pszBuffer, pwszDevice, *pcchBuffer * sizeof(WCHAR));
537 
538     dwErrorCode = ERROR_SUCCESS;
539 
540 Cleanup:
541     if (hWindowsKey)
542         RegCloseKey(hWindowsKey);
543 
544     if (pwszDevice)
545         HeapFree(hProcessHeap, 0, pwszDevice);
546 
547     SetLastError(dwErrorCode);
548     return (dwErrorCode == ERROR_SUCCESS);
549 }
550 
551 BOOL WINAPI
552 GetPrinterA(HANDLE hPrinter, DWORD Level, LPBYTE pPrinter, DWORD cbBuf, LPDWORD pcbNeeded)
553 {
554     return FALSE;
555 }
556 
557 BOOL WINAPI
558 GetPrinterDriverA(HANDLE hPrinter, LPSTR pEnvironment, DWORD Level, LPBYTE pDriverInfo, DWORD cbBuf, LPDWORD pcbNeeded)
559 {
560     return FALSE;
561 }
562 
563 BOOL WINAPI
564 GetPrinterDriverW(HANDLE hPrinter, LPWSTR pEnvironment, DWORD Level, LPBYTE pDriverInfo, DWORD cbBuf, LPDWORD pcbNeeded)
565 {
566     return FALSE;
567 }
568 
569 BOOL WINAPI
570 GetPrinterW(HANDLE hPrinter, DWORD Level, LPBYTE pPrinter, DWORD cbBuf, LPDWORD pcbNeeded)
571 {
572     DWORD dwErrorCode;
573 
574     // Dismiss invalid levels already at this point.
575     if (Level > 9)
576     {
577         dwErrorCode = ERROR_INVALID_LEVEL;
578         goto Cleanup;
579     }
580 
581     if (cbBuf && pPrinter)
582         ZeroMemory(pPrinter, cbBuf);
583 
584     // Do the RPC call
585     RpcTryExcept
586     {
587         dwErrorCode = _RpcGetPrinter(hPrinter, Level, pPrinter, cbBuf, pcbNeeded);
588     }
589     RpcExcept(EXCEPTION_EXECUTE_HANDLER)
590     {
591         dwErrorCode = RpcExceptionCode();
592         ERR("_RpcGetPrinter failed with exception code %lu!\n", dwErrorCode);
593     }
594     RpcEndExcept;
595 
596     if (dwErrorCode == ERROR_SUCCESS)
597     {
598         PBYTE p = pPrinter;
599         _MarshallUpPrinterInfo(&p, Level);
600     }
601 
602 Cleanup:
603     SetLastError(dwErrorCode);
604     return (dwErrorCode == ERROR_SUCCESS);
605 }
606 
607 BOOL WINAPI
608 OpenPrinterA(LPSTR pPrinterName, LPHANDLE phPrinter, LPPRINTER_DEFAULTSA pDefault)
609 {
610     BOOL bReturnValue = FALSE;
611     DWORD cch;
612     PWSTR pwszPrinterName = NULL;
613     PRINTER_DEFAULTSW wDefault = { 0 };
614 
615     if (pPrinterName)
616     {
617         // Convert pPrinterName to a Unicode string pwszPrinterName
618         cch = strlen(pPrinterName);
619 
620         pwszPrinterName = HeapAlloc(hProcessHeap, 0, (cch + 1) * sizeof(WCHAR));
621         if (!pwszPrinterName)
622         {
623             SetLastError(ERROR_NOT_ENOUGH_MEMORY);
624             ERR("HeapAlloc failed!\n");
625             goto Cleanup;
626         }
627 
628         MultiByteToWideChar(CP_ACP, 0, pPrinterName, -1, pwszPrinterName, cch + 1);
629     }
630 
631     if (pDefault)
632     {
633         wDefault.DesiredAccess = pDefault->DesiredAccess;
634 
635         if (pDefault->pDatatype)
636         {
637             // Convert pDefault->pDatatype to a Unicode string wDefault.pDatatype
638             cch = strlen(pDefault->pDatatype);
639 
640             wDefault.pDatatype = HeapAlloc(hProcessHeap, 0, (cch + 1) * sizeof(WCHAR));
641             if (!wDefault.pDatatype)
642             {
643                 SetLastError(ERROR_NOT_ENOUGH_MEMORY);
644                 ERR("HeapAlloc failed!\n");
645                 goto Cleanup;
646             }
647 
648             MultiByteToWideChar(CP_ACP, 0, pDefault->pDatatype, -1, wDefault.pDatatype, cch + 1);
649         }
650 
651         if (pDefault->pDevMode)
652             wDefault.pDevMode = GdiConvertToDevmodeW(pDefault->pDevMode);
653     }
654 
655     bReturnValue = OpenPrinterW(pwszPrinterName, phPrinter, &wDefault);
656 
657 Cleanup:
658     if (wDefault.pDatatype)
659         HeapFree(hProcessHeap, 0, wDefault.pDatatype);
660 
661     if (wDefault.pDevMode)
662         HeapFree(hProcessHeap, 0, wDefault.pDevMode);
663 
664     if (pwszPrinterName)
665         HeapFree(hProcessHeap, 0, pwszPrinterName);
666 
667     return bReturnValue;
668 }
669 
670 BOOL WINAPI
671 OpenPrinterW(LPWSTR pPrinterName, LPHANDLE phPrinter, LPPRINTER_DEFAULTSW pDefault)
672 {
673     DWORD dwErrorCode;
674     HANDLE hPrinter;
675     PSPOOLER_HANDLE pHandle;
676     PWSTR pDatatype = NULL;
677     WINSPOOL_DEVMODE_CONTAINER DevModeContainer = { 0 };
678     ACCESS_MASK AccessRequired = 0;
679 
680     // Sanity check
681     if (!phPrinter)
682     {
683         dwErrorCode = ERROR_INVALID_PARAMETER;
684         goto Cleanup;
685     }
686 
687     // Prepare the additional parameters in the format required by _RpcOpenPrinter
688     if (pDefault)
689     {
690         pDatatype = pDefault->pDatatype;
691         DevModeContainer.cbBuf = sizeof(DEVMODEW);
692         DevModeContainer.pDevMode = (BYTE*)pDefault->pDevMode;
693         AccessRequired = pDefault->DesiredAccess;
694     }
695 
696     // Do the RPC call
697     RpcTryExcept
698     {
699         dwErrorCode = _RpcOpenPrinter(pPrinterName, &hPrinter, pDatatype, &DevModeContainer, AccessRequired);
700     }
701     RpcExcept(EXCEPTION_EXECUTE_HANDLER)
702     {
703         dwErrorCode = RpcExceptionCode();
704         ERR("_RpcOpenPrinter failed with exception code %lu!\n", dwErrorCode);
705     }
706     RpcEndExcept;
707 
708     if (dwErrorCode == ERROR_SUCCESS)
709     {
710         // Create a new SPOOLER_HANDLE structure.
711         pHandle = HeapAlloc(hProcessHeap, HEAP_ZERO_MEMORY, sizeof(SPOOLER_HANDLE));
712         if (!pHandle)
713         {
714             dwErrorCode = ERROR_NOT_ENOUGH_MEMORY;
715             ERR("HeapAlloc failed!\n");
716             goto Cleanup;
717         }
718 
719         pHandle->hPrinter = hPrinter;
720         pHandle->hSPLFile = INVALID_HANDLE_VALUE;
721 
722         // Return it as phPrinter.
723         *phPrinter = (HANDLE)pHandle;
724     }
725 
726 Cleanup:
727     SetLastError(dwErrorCode);
728     return (dwErrorCode == ERROR_SUCCESS);
729 }
730 
731 BOOL WINAPI
732 ReadPrinter(HANDLE hPrinter, PVOID pBuf, DWORD cbBuf, PDWORD pNoBytesRead)
733 {
734     DWORD dwErrorCode;
735     PSPOOLER_HANDLE pHandle = (PSPOOLER_HANDLE)hPrinter;
736 
737     // Sanity checks.
738     if (!pHandle)
739     {
740         dwErrorCode = ERROR_INVALID_HANDLE;
741         goto Cleanup;
742     }
743 
744     // Do the RPC call
745     RpcTryExcept
746     {
747         dwErrorCode = _RpcReadPrinter(pHandle->hPrinter, pBuf, cbBuf, pNoBytesRead);
748     }
749     RpcExcept(EXCEPTION_EXECUTE_HANDLER)
750     {
751         dwErrorCode = RpcExceptionCode();
752         ERR("_RpcReadPrinter failed with exception code %lu!\n", dwErrorCode);
753     }
754     RpcEndExcept;
755 
756 Cleanup:
757     SetLastError(dwErrorCode);
758     return (dwErrorCode == ERROR_SUCCESS);
759 }
760 
761 BOOL WINAPI
762 ResetPrinterW(HANDLE hPrinter, PPRINTER_DEFAULTSW pDefault)
763 {
764     UNIMPLEMENTED;
765     return FALSE;
766 }
767 
768 BOOL WINAPI
769 SetDefaultPrinterA(LPCSTR pszPrinter)
770 {
771     BOOL bReturnValue = FALSE;
772     DWORD cch;
773     PWSTR pwszPrinter = NULL;
774 
775     if (pszPrinter)
776     {
777         // Convert pszPrinter to a Unicode string pwszPrinter
778         cch = strlen(pszPrinter);
779 
780         pwszPrinter = HeapAlloc(hProcessHeap, 0, (cch + 1) * sizeof(WCHAR));
781         if (!pwszPrinter)
782         {
783             SetLastError(ERROR_NOT_ENOUGH_MEMORY);
784             ERR("HeapAlloc failed!\n");
785             goto Cleanup;
786         }
787 
788         MultiByteToWideChar(CP_ACP, 0, pszPrinter, -1, pwszPrinter, cch + 1);
789     }
790 
791     bReturnValue = SetDefaultPrinterW(pwszPrinter);
792 
793 Cleanup:
794     if (pwszPrinter)
795         HeapFree(hProcessHeap, 0, pwszPrinter);
796 
797     return bReturnValue;
798 }
799 
800 BOOL WINAPI
801 SetDefaultPrinterW(LPCWSTR pszPrinter)
802 {
803     const WCHAR wszDevicesKey[] = L"Software\\Microsoft\\Windows NT\\CurrentVersion\\Devices";
804 
805     DWORD cbDeviceValueData;
806     DWORD cbPrinterValueData = 0;
807     DWORD cchPrinter;
808     DWORD dwErrorCode;
809     HKEY hDevicesKey = NULL;
810     HKEY hWindowsKey = NULL;
811     PWSTR pwszDeviceValueData = NULL;
812     WCHAR wszPrinter[MAX_PRINTER_NAME + 1];
813 
814     // Open the Devices registry key.
815     dwErrorCode = (DWORD)RegOpenKeyExW(HKEY_CURRENT_USER, wszDevicesKey, 0, KEY_READ, &hDevicesKey);
816     if (dwErrorCode != ERROR_SUCCESS)
817     {
818         ERR("RegOpenKeyExW failed with status %lu!\n", dwErrorCode);
819         goto Cleanup;
820     }
821 
822     // Did the caller give us a printer to set as default?
823     if (pszPrinter && *pszPrinter)
824     {
825         // Check if the given printer exists and query the value data size.
826         dwErrorCode = (DWORD)RegQueryValueExW(hDevicesKey, pszPrinter, NULL, NULL, NULL, &cbPrinterValueData);
827         if (dwErrorCode == ERROR_FILE_NOT_FOUND)
828         {
829             dwErrorCode = ERROR_INVALID_PRINTER_NAME;
830             goto Cleanup;
831         }
832         else if (dwErrorCode != ERROR_SUCCESS)
833         {
834             ERR("RegQueryValueExW failed with status %lu!\n", dwErrorCode);
835             goto Cleanup;
836         }
837 
838         cchPrinter = wcslen(pszPrinter);
839     }
840     else
841     {
842         // If there is already a default printer, we're done!
843         cchPrinter = _countof(wszPrinter);
844         if (GetDefaultPrinterW(wszPrinter, &cchPrinter))
845         {
846             dwErrorCode = ERROR_SUCCESS;
847             goto Cleanup;
848         }
849 
850         // Otherwise, get us the first printer from the "Devices" key to later set it as default and query the value data size.
851         cchPrinter = _countof(wszPrinter);
852         dwErrorCode = (DWORD)RegEnumValueW(hDevicesKey, 0, wszPrinter, &cchPrinter, NULL, NULL, NULL, &cbPrinterValueData);
853         if (dwErrorCode != ERROR_MORE_DATA)
854             goto Cleanup;
855 
856         pszPrinter = wszPrinter;
857     }
858 
859     // We now need to query the value data, which has the format "winspool,<Port>:"
860     // and make "<Printer Name>,winspool,<Port>:" out of it.
861     // Allocate a buffer large enough for the final data.
862     cbDeviceValueData = (cchPrinter + 1) * sizeof(WCHAR) + cbPrinterValueData;
863     pwszDeviceValueData = HeapAlloc(hProcessHeap, 0, cbDeviceValueData);
864     if (!pwszDeviceValueData)
865     {
866         dwErrorCode = ERROR_NOT_ENOUGH_MEMORY;
867         ERR("HeapAlloc failed!\n");
868         goto Cleanup;
869     }
870 
871     // Copy the Printer Name and a comma into it.
872     CopyMemory(pwszDeviceValueData, pszPrinter, cchPrinter * sizeof(WCHAR));
873     pwszDeviceValueData[cchPrinter] = L',';
874 
875     // Append the value data, which has the format "winspool,<Port>:"
876     dwErrorCode = (DWORD)RegQueryValueExW(hDevicesKey, pszPrinter, NULL, NULL, (PBYTE)&pwszDeviceValueData[cchPrinter + 1], &cbPrinterValueData);
877     if (dwErrorCode != ERROR_SUCCESS)
878         goto Cleanup;
879 
880     // Open the Windows registry key.
881     dwErrorCode = (DWORD)RegOpenKeyExW(HKEY_CURRENT_USER, wszWindowsKey, 0, KEY_SET_VALUE, &hWindowsKey);
882     if (dwErrorCode != ERROR_SUCCESS)
883     {
884         ERR("RegOpenKeyExW failed with status %lu!\n", dwErrorCode);
885         goto Cleanup;
886     }
887 
888     // Store our new default printer.
889     dwErrorCode = (DWORD)RegSetValueExW(hWindowsKey, wszDeviceValue, 0, REG_SZ, (PBYTE)pwszDeviceValueData, cbDeviceValueData);
890     if (dwErrorCode != ERROR_SUCCESS)
891     {
892         ERR("RegSetValueExW failed with status %lu!\n", dwErrorCode);
893         goto Cleanup;
894     }
895 
896 Cleanup:
897     if (hDevicesKey)
898         RegCloseKey(hDevicesKey);
899 
900     if (hWindowsKey)
901         RegCloseKey(hWindowsKey);
902 
903     if (pwszDeviceValueData)
904         HeapFree(hProcessHeap, 0, pwszDeviceValueData);
905 
906     SetLastError(dwErrorCode);
907     return (dwErrorCode == ERROR_SUCCESS);
908 }
909 
910 BOOL WINAPI
911 SetPrinterW(HANDLE hPrinter, DWORD Level, PBYTE pPrinter, DWORD Command)
912 {
913     UNIMPLEMENTED;
914     return FALSE;
915 }
916 
917 DWORD WINAPI
918 StartDocPrinterA(HANDLE hPrinter, DWORD Level, PBYTE pDocInfo)
919 {
920     DOC_INFO_1W wDocInfo1 = { 0 };
921     DWORD cch;
922     DWORD dwErrorCode;
923     DWORD dwReturnValue = 0;
924     PDOC_INFO_1A pDocInfo1 = (PDOC_INFO_1A)pDocInfo;
925 
926     // Only check the minimum required for accessing pDocInfo.
927     // Additional sanity checks are done in StartDocPrinterW.
928     if (!pDocInfo1)
929     {
930         dwErrorCode = ERROR_INVALID_PARAMETER;
931         goto Cleanup;
932     }
933 
934     if (Level != 1)
935     {
936         dwErrorCode = ERROR_INVALID_LEVEL;
937         goto Cleanup;
938     }
939 
940     if (pDocInfo1->pDatatype)
941     {
942         // Convert pDocInfo1->pDatatype to a Unicode string wDocInfo1.pDatatype
943         cch = strlen(pDocInfo1->pDatatype);
944 
945         wDocInfo1.pDatatype = HeapAlloc(hProcessHeap, 0, (cch + 1) * sizeof(WCHAR));
946         if (!wDocInfo1.pDatatype)
947         {
948             dwErrorCode = ERROR_NOT_ENOUGH_MEMORY;
949             ERR("HeapAlloc failed!\n");
950             goto Cleanup;
951         }
952 
953         MultiByteToWideChar(CP_ACP, 0, pDocInfo1->pDatatype, -1, wDocInfo1.pDatatype, cch + 1);
954     }
955 
956     if (pDocInfo1->pDocName)
957     {
958         // Convert pDocInfo1->pDocName to a Unicode string wDocInfo1.pDocName
959         cch = strlen(pDocInfo1->pDocName);
960 
961         wDocInfo1.pDocName = HeapAlloc(hProcessHeap, 0, (cch + 1) * sizeof(WCHAR));
962         if (!wDocInfo1.pDocName)
963         {
964             dwErrorCode = ERROR_NOT_ENOUGH_MEMORY;
965             ERR("HeapAlloc failed!\n");
966             goto Cleanup;
967         }
968 
969         MultiByteToWideChar(CP_ACP, 0, pDocInfo1->pDocName, -1, wDocInfo1.pDocName, cch + 1);
970     }
971 
972     if (pDocInfo1->pOutputFile)
973     {
974         // Convert pDocInfo1->pOutputFile to a Unicode string wDocInfo1.pOutputFile
975         cch = strlen(pDocInfo1->pOutputFile);
976 
977         wDocInfo1.pOutputFile = HeapAlloc(hProcessHeap, 0, (cch + 1) * sizeof(WCHAR));
978         if (!wDocInfo1.pOutputFile)
979         {
980             dwErrorCode = ERROR_NOT_ENOUGH_MEMORY;
981             ERR("HeapAlloc failed!\n");
982             goto Cleanup;
983         }
984 
985         MultiByteToWideChar(CP_ACP, 0, pDocInfo1->pOutputFile, -1, wDocInfo1.pOutputFile, cch + 1);
986     }
987 
988     dwReturnValue = StartDocPrinterW(hPrinter, Level, (PBYTE)&wDocInfo1);
989     dwErrorCode = GetLastError();
990 
991 Cleanup:
992     if (wDocInfo1.pDatatype)
993         HeapFree(hProcessHeap, 0, wDocInfo1.pDatatype);
994 
995     if (wDocInfo1.pDocName)
996         HeapFree(hProcessHeap, 0, wDocInfo1.pDocName);
997 
998     if (wDocInfo1.pOutputFile)
999         HeapFree(hProcessHeap, 0, wDocInfo1.pOutputFile);
1000 
1001     SetLastError(dwErrorCode);
1002     return dwReturnValue;
1003 }
1004 
1005 DWORD WINAPI
1006 StartDocPrinterW(HANDLE hPrinter, DWORD Level, PBYTE pDocInfo)
1007 {
1008     DWORD cbAddJobInfo1;
1009     DWORD cbNeeded;
1010     DWORD dwErrorCode;
1011     DWORD dwReturnValue = 0;
1012     PADDJOB_INFO_1W pAddJobInfo1 = NULL;
1013     PDOC_INFO_1W pDocInfo1 = (PDOC_INFO_1W)pDocInfo;
1014     PSPOOLER_HANDLE pHandle = (PSPOOLER_HANDLE)hPrinter;
1015 
1016     // Sanity checks.
1017     if (!pHandle)
1018     {
1019         dwErrorCode = ERROR_INVALID_HANDLE;
1020         goto Cleanup;
1021     }
1022 
1023     if (!pDocInfo1)
1024     {
1025         dwErrorCode = ERROR_INVALID_PARAMETER;
1026         goto Cleanup;
1027     }
1028 
1029     if (Level != 1)
1030     {
1031         dwErrorCode = ERROR_INVALID_LEVEL;
1032         goto Cleanup;
1033     }
1034 
1035     if (pHandle->bStartedDoc)
1036     {
1037         dwErrorCode = ERROR_INVALID_PRINTER_STATE;
1038         goto Cleanup;
1039     }
1040 
1041     // Check if we want to redirect output into a file.
1042     if (pDocInfo1->pOutputFile)
1043     {
1044         // Do a StartDocPrinter RPC call in this case.
1045         dwErrorCode = _StartDocPrinterWithRPC(pHandle, pDocInfo1);
1046     }
1047     else
1048     {
1049         // Allocate memory for the ADDJOB_INFO_1W structure and a path.
1050         cbAddJobInfo1 = sizeof(ADDJOB_INFO_1W) + MAX_PATH * sizeof(WCHAR);
1051         pAddJobInfo1 = HeapAlloc(hProcessHeap, 0, cbAddJobInfo1);
1052         if (!pAddJobInfo1)
1053         {
1054             dwErrorCode = ERROR_NOT_ENOUGH_MEMORY;
1055             ERR("HeapAlloc failed!\n");
1056             goto Cleanup;
1057         }
1058 
1059         // Try to add a new job.
1060         // This only succeeds if the printer is set to do spooled printing.
1061         if (AddJobW((HANDLE)pHandle, 1, (PBYTE)pAddJobInfo1, cbAddJobInfo1, &cbNeeded))
1062         {
1063             // Do spooled printing.
1064             dwErrorCode = _StartDocPrinterSpooled(pHandle, pDocInfo1, pAddJobInfo1);
1065         }
1066         else if (GetLastError() == ERROR_INVALID_ACCESS)
1067         {
1068             // ERROR_INVALID_ACCESS is returned when the printer is set to do direct printing.
1069             // In this case, we do a StartDocPrinter RPC call.
1070             dwErrorCode = _StartDocPrinterWithRPC(pHandle, pDocInfo1);
1071         }
1072         else
1073         {
1074             dwErrorCode = GetLastError();
1075             ERR("AddJobW failed with error %lu!\n", dwErrorCode);
1076             goto Cleanup;
1077         }
1078     }
1079 
1080     if (dwErrorCode == ERROR_SUCCESS)
1081     {
1082         pHandle->bStartedDoc = TRUE;
1083         dwReturnValue = pHandle->dwJobID;
1084     }
1085 
1086 Cleanup:
1087     if (pAddJobInfo1)
1088         HeapFree(hProcessHeap, 0, pAddJobInfo1);
1089 
1090     SetLastError(dwErrorCode);
1091     return dwReturnValue;
1092 }
1093 
1094 BOOL WINAPI
1095 StartPagePrinter(HANDLE hPrinter)
1096 {
1097     DWORD dwErrorCode;
1098     PSPOOLER_HANDLE pHandle = (PSPOOLER_HANDLE)hPrinter;
1099 
1100     // Sanity checks.
1101     if (!pHandle)
1102     {
1103         dwErrorCode = ERROR_INVALID_HANDLE;
1104         goto Cleanup;
1105     }
1106 
1107     // Do the RPC call
1108     RpcTryExcept
1109     {
1110         dwErrorCode = _RpcStartPagePrinter(pHandle->hPrinter);
1111     }
1112     RpcExcept(EXCEPTION_EXECUTE_HANDLER)
1113     {
1114         dwErrorCode = RpcExceptionCode();
1115         ERR("_RpcStartPagePrinter failed with exception code %lu!\n", dwErrorCode);
1116     }
1117     RpcEndExcept;
1118 
1119 Cleanup:
1120     SetLastError(dwErrorCode);
1121     return (dwErrorCode == ERROR_SUCCESS);
1122 }
1123 
1124 BOOL WINAPI
1125 WritePrinter(HANDLE hPrinter, PVOID pBuf, DWORD cbBuf, PDWORD pcWritten)
1126 {
1127     DWORD dwErrorCode;
1128     PSPOOLER_HANDLE pHandle = (PSPOOLER_HANDLE)hPrinter;
1129 
1130     // Sanity checks.
1131     if (!pHandle)
1132     {
1133         dwErrorCode = ERROR_INVALID_HANDLE;
1134         goto Cleanup;
1135     }
1136 
1137     if (!pHandle->bStartedDoc)
1138     {
1139         dwErrorCode = ERROR_SPL_NO_STARTDOC;
1140         goto Cleanup;
1141     }
1142 
1143     if (pHandle->hSPLFile != INVALID_HANDLE_VALUE)
1144     {
1145         // Write to the spool file. This doesn't need an RPC request.
1146         if (!WriteFile(pHandle->hSPLFile, pBuf, cbBuf, pcWritten, NULL))
1147         {
1148             dwErrorCode = GetLastError();
1149             ERR("WriteFile failed with error %lu!\n", dwErrorCode);
1150             goto Cleanup;
1151         }
1152 
1153         dwErrorCode = ERROR_SUCCESS;
1154     }
1155     else
1156     {
1157         // TODO: This case (for direct printing or remote printing) has bad performance if multiple small-sized WritePrinter calls are performed.
1158         // We may increase performance by writing into a buffer and only doing a single RPC call when the buffer is full.
1159 
1160         // Do the RPC call
1161         RpcTryExcept
1162         {
1163             dwErrorCode = _RpcWritePrinter(pHandle->hPrinter, pBuf, cbBuf, pcWritten);
1164         }
1165         RpcExcept(EXCEPTION_EXECUTE_HANDLER)
1166         {
1167             dwErrorCode = RpcExceptionCode();
1168             ERR("_RpcWritePrinter failed with exception code %lu!\n", dwErrorCode);
1169         }
1170         RpcEndExcept;
1171     }
1172 
1173 Cleanup:
1174     SetLastError(dwErrorCode);
1175     return (dwErrorCode == ERROR_SUCCESS);
1176 }
1177 
1178 BOOL WINAPI
1179 XcvDataW(HANDLE hXcv, PCWSTR pszDataName, PBYTE pInputData, DWORD cbInputData, PBYTE pOutputData, DWORD cbOutputData, PDWORD pcbOutputNeeded, PDWORD pdwStatus)
1180 {
1181     return FALSE;
1182 }
1183