1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
|
// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
#include "precomp.h"
// Constants
LPCWSTR vcsEulaQuery = L"SELECT `Text` FROM `Control` WHERE `Control`='LicenseText'";
enum eEulaQuery { eqText = 1};
const int IDM_POPULATE = 100;
const int IDM_PRINT = 101;
const int CONTROL_X_COORDINATE = 0;
const int CONTROL_Y_COORDINATE = 0;
const int CONTROL_WIDTH = 500;
const int CONTROL_HEIGHT = 500;
const int ONE_INCH = 1440; // 1440 TWIPS = 1 inch.
const int TEXT_RECORD_POS = 1;
const int STRING_CAPACITY = 512;
const int NO_OF_COPIES = 1;
const LPWSTR WINDOW_CLASS = L"PrintEulaRichText";
//Forward declarations of functions, check the function definitions for the comments
static LRESULT CALLBACK WndProc(__in HWND hWnd, __in UINT message, __in WPARAM wParam, __in LPARAM lParam);
static HRESULT ReadEulaText(__in MSIHANDLE hInstall, __out LPSTR* ppszEulaText);
static DWORD CALLBACK ReadStreamCallback(__in DWORD Cookie, __out LPBYTE pbBuff, __in LONG cb, __out LONG FAR *pcb);
static HRESULT CreateRichTextWindow(__out HWND* phWndMain, __out BOOL* pfRegisteredClass);
static HRESULT PrintRichText(__in HWND hWndMain);
static void Print(__in_opt HWND hWnd);
static void LoadEulaText(__in_opt HWND hWnd);
static void ShowErrorMessage(__in HRESULT hr);
//Global variables
PRINTDLGEXW* vpPrintDlg = NULL; //Parameters for print (needed on both sides of WndProc callbacks)
LPSTR vpszEulaText = NULL;
LPCWSTR vwzRichEditClass = NULL;
HRESULT vhr = S_OK; //Global hr, used by the functions called from WndProc to set errorcode
/********************************************************************
PrintEula - Custom Action entry point
********************************************************************/
extern "C" UINT __stdcall PrintEula(MSIHANDLE hInstall)
{
//AssertSz(FALSE, "Debug PrintEula");
HRESULT hr = S_OK;
HWND hWndMain = NULL;
HMODULE hRichEdit = NULL;
BOOL fRegisteredClass = FALSE;
hr = WcaInitialize(hInstall, "PrintEula");
ExitOnFailure(hr, "failed to initialize");
// Initialize then display print dialog.
vpPrintDlg = (PRINTDLGEXW*)GlobalAlloc(GPTR, sizeof(PRINTDLGEXW)); // MSDN says to allocate on heap.
ExitOnNullWithLastError(vpPrintDlg, hr, "Failed to allocate memory for print dialog struct.");
vpPrintDlg->lStructSize = sizeof(PRINTDLGEX);
vpPrintDlg->hwndOwner = ::FindWindowW(L"MsiDialogCloseClass", NULL);
vpPrintDlg->Flags = PD_RETURNDC | PD_COLLATE | PD_NOCURRENTPAGE | PD_ALLPAGES | PD_NOPAGENUMS | PD_NOSELECTION;
vpPrintDlg->nCopies = NO_OF_COPIES;
vpPrintDlg->nStartPage = START_PAGE_GENERAL;
hr = ::PrintDlgExW(vpPrintDlg);
ExitOnFailure(hr, "Failed to show print dialog");
// If user said they want to print.
if (PD_RESULT_PRINT == vpPrintDlg->dwResultAction)
{
// Get the stream for Eula
hr = ReadEulaText(hInstall, &vpszEulaText);
ExitOnFailure(hr, "failed to read Eula text from MSI database");
// Have to load Rich Edit since we'll be creating a Rich Edit control in the window
hr = LoadSystemLibrary(L"Msftedit.dll", &hRichEdit);
if (SUCCEEDED(hr))
{
vwzRichEditClass = MSFTEDIT_CLASS;
}
else
{
hr = LoadSystemLibrary(L"Riched20.dll", &hRichEdit);
ExitOnFailure(hr, "failed to load rich edit 2.0 library");
vwzRichEditClass = RICHEDIT_CLASSW;
}
hr = CreateRichTextWindow(&hWndMain, &fRegisteredClass);
ExitOnFailure(hr, "failed to create rich text window for printing");
hr = PrintRichText(hWndMain);
if (FAILED(hr)) // Since we've already shown the print dialog, we better show them a dialog explaining why it didn't print
{
ShowErrorMessage(hr);
}
}
LExit:
ReleaseNullStr(vpszEulaText);
if (vpPrintDlg)
{
if (vpPrintDlg->hDevMode)
{
::GlobalFree(vpPrintDlg->hDevMode);
}
if (vpPrintDlg->hDevNames)
{
::GlobalFree(vpPrintDlg->hDevNames);
}
if (vpPrintDlg->hDC)
{
::DeleteDC(vpPrintDlg->hDC);
}
::GlobalFree(vpPrintDlg);
vpPrintDlg = NULL;
}
if (fRegisteredClass)
{
::UnregisterClassW(WINDOW_CLASS, NULL);
}
vwzRichEditClass = NULL;
if (NULL != hRichEdit)
{
::FreeLibrary(hRichEdit);
}
// Always return success since we dont want to stop the
// installation even if the Eula printing fails.
return WcaFinalize(ERROR_SUCCESS);
}
/********************************************************************
CreateRichTextWindow - Creates Window and Child RichText control.
********************************************************************/
HRESULT CreateRichTextWindow(
__out HWND* phWndMain,
__out BOOL* pfRegisteredClass
)
{
HRESULT hr = S_OK;
HWND hWndMain = NULL;
WNDCLASSEXW wcex;
//
// Register the window class
//
wcex.cbSize = sizeof(WNDCLASSEXW);
wcex.style = CS_HREDRAW | CS_VREDRAW;
wcex.lpfnWndProc = (WNDPROC)WndProc;
wcex.cbClsExtra = 0;
wcex.cbWndExtra = 0;
wcex.hInstance = NULL;
wcex.hIcon = NULL;
wcex.hCursor = LoadCursor(NULL, IDC_ARROW);
wcex.hbrBackground = (HBRUSH)(COLOR_BACKGROUND+1);
wcex.lpszMenuName = NULL;
wcex.lpszClassName = WINDOW_CLASS;
wcex.hIconSm = NULL;
if (0 == ::RegisterClassExW(&wcex))
{
DWORD dwResult = ::GetLastError();
// If we get "Class already exists" error ignore it. We might
// encounter this when the user tries to print more than once
// in the same setup instance and we are unable to clean up fully.
if (dwResult != ERROR_CLASS_ALREADY_EXISTS)
{
ExitOnFailure(hr = HRESULT_FROM_WIN32(dwResult), "failed to register window class");
}
}
*pfRegisteredClass = TRUE;
// Perform application initialization:
hWndMain = ::CreateWindowW(WINDOW_CLASS, NULL, WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, NULL, NULL, NULL, NULL);
ExitOnNullWithLastError(hWndMain, hr, "failed to create window for printing");
::ShowWindow(hWndMain, SW_HIDE);
if (!::UpdateWindow(hWndMain))
{
ExitWithLastError(hr, "failed to update window");
}
*phWndMain = hWndMain;
LExit:
return hr;
}
/********************************************************************
PrintRichText - Sends messages to load the Eula text, print it, and
close the window.
NOTE: Returns errors that have occured while attempting to print,
which were saved in vhr by the print callbacks.
********************************************************************/
HRESULT PrintRichText(
__in HWND hWndMain
)
{
MSG msg;
// Populate the RichEdit control
::SendMessageW(hWndMain, WM_COMMAND, IDM_POPULATE, 0);
// Print Eula
::SendMessageW(hWndMain, WM_COMMAND, IDM_PRINT, 0);
// Done! Lets close the Window
::SendMessage(hWndMain, WM_CLOSE, 0, 0);
// Main message loop:
while (::GetMessageW(&msg, NULL, 0, 0))
{
// if (!::TranslateAcceleratorW(msg.hwnd, NULL, &msg))
// {
// ::TranslateMessage(&msg);
// ::DispatchMessageW(&msg);
// }
}
// return any errors encountered in the print callbacks
return vhr;
}
/********************************************************************
WndProc - Windows callback procedure
********************************************************************/
LRESULT CALLBACK WndProc(
__in HWND hWnd,
__in UINT message,
__in WPARAM wParam,
__in LPARAM lParam
)
{
static HWND hWndRichEdit = NULL;
int wmId, wmEvent;
PAINTSTRUCT ps;
HDC hdc;
switch (message)
{
case WM_CREATE:
hWndRichEdit = ::CreateWindowExW(WS_EX_CLIENTEDGE, vwzRichEditClass, L"", ES_MULTILINE | WS_CHILD | WS_VISIBLE | WS_VSCROLL, CONTROL_X_COORDINATE, CONTROL_Y_COORDINATE, CONTROL_WIDTH, CONTROL_HEIGHT, hWnd, NULL, NULL, NULL);
break;
case WM_COMMAND:
wmId = LOWORD(wParam);
wmEvent = HIWORD(wParam);
switch (wmId)
{
case IDM_POPULATE:
LoadEulaText(hWndRichEdit);
break;
case IDM_PRINT:
Print(hWndRichEdit);
break;
default:
return ::DefWindowProcW(hWnd, message, wParam, lParam);
break;
}
break;
case WM_PAINT:
hdc = ::BeginPaint(hWnd, &ps);
::EndPaint(hWnd, &ps);
break;
case WM_DESTROY:
::PostQuitMessage(0);
break;
default:
return ::DefWindowProcW(hWnd, message, wParam, lParam);
}
return 0;
}
/********************************************************************
ReadStreamCallback - Callback function to read data to the RichText control
NOTE: Richtext control uses this function to read data from the buffer
********************************************************************/
DWORD CALLBACK ReadStreamCallback(
__in DWORD /*Cookie*/,
__out LPBYTE pbBuff,
__in LONG cb,
__out LONG FAR *pcb
)
{
static LPCSTR pszTextBuf = NULL;
DWORD er = ERROR_SUCCESS;
// If it's null set it to the beginning of the EULA buffer
if (pszTextBuf == NULL)
{
pszTextBuf = vpszEulaText;
}
LONG lTextLength = (LONG)lstrlen(pszTextBuf);
if (cb < 0)
{
*pcb = 0;
er = 1;
}
else if (lTextLength < cb ) // If the size to be written is less than then length of the buffer, write the rest
{
*pcb = lTextLength;
memcpy(pbBuff, pszTextBuf, *pcb);
pszTextBuf = NULL;
}
else // Only write the amount being asked for and move the pointer along
{
*pcb = cb;
memcpy(pbBuff, pszTextBuf, *pcb);
pszTextBuf = pszTextBuf + cb;
}
return er;
}
/********************************************************************
LoadEulaText - Reads data for Richedit control
********************************************************************/
void LoadEulaText(
__in HWND hWnd
)
{
HRESULT hr = S_OK;
ExitOnNull(hWnd, hr, ERROR_INVALID_HANDLE, "Invalid Handle passed to LoadEulaText");
// Docs say this doesn't return any value
::SendMessageW(hWnd, EM_LIMITTEXT, static_cast<WPARAM>(lstrlen(vpszEulaText)), 0);
EDITSTREAM es;
::ZeroMemory(&es, sizeof(es));
es.pfnCallback = (EDITSTREAMCALLBACK)ReadStreamCallback;
es.dwCookie = (DWORD)0;
::SendMessageW(hWnd, EM_STREAMIN, SF_RTF, (LPARAM)&es);
if (0 != es.dwError)
{
ExitOnLastError(hr, "failed to load the EULA into the control");
}
LExit:
vhr = hr;
}
/********************************************************************
ReadEulaText - Reads Eula text from the MSI
********************************************************************/
HRESULT ReadEulaText(
__in MSIHANDLE /*hInstall*/,
__out LPSTR* ppszEulaText
)
{
HRESULT hr = S_OK;
PMSIHANDLE hDB;
PMSIHANDLE hView;
PMSIHANDLE hRec;
LPWSTR pwzEula = NULL;
hr = WcaOpenExecuteView(vcsEulaQuery, &hView);
ExitOnFailure(hr, "failed to open and execute view for PrintEula query");
hr = WcaFetchSingleRecord(hView, &hRec);
ExitOnFailure(hr, "failed to fetch the row containing the LicenseText");
hr = WcaGetRecordString(hRec, 1, &pwzEula);
ExitOnFailure(hr, "failed to get LicenseText in PrintEula");
hr = StrAnsiAllocString(ppszEulaText, pwzEula, 0, CP_ACP);
ExitOnFailure(hr, "failed to convert LicenseText to ANSI code page");
LExit:
return hr;
}
/********************************************************************
Print - Function that sends the data from richedit control to the printer
NOTE: Any errors encountered are saved to the vhr variable
********************************************************************/
void Print(
__in_opt HWND hRtfWnd
)
{
HRESULT hr = S_OK;
FORMATRANGE fRange;
RECT rcPage;
RECT rcPrintablePage;
GETTEXTLENGTHEX gTxex;
HDC hPrinterDC = vpPrintDlg->hDC;
int nHorizRes = ::GetDeviceCaps(hPrinterDC, HORZRES);
int nVertRes = ::GetDeviceCaps(hPrinterDC, VERTRES);
int nLogPixelsX = ::GetDeviceCaps(hPrinterDC, LOGPIXELSX);
//int nLogPixelsY = ::GetDeviceCaps(hPrinterDC, LOGPIXELSY);
LONG_PTR lTextLength = 0; // Length of document.
LONG_PTR lTextPrinted = 0; // Amount of document printed.
DOCINFOW dInfo;
LPDEVNAMES pDevnames;
LPWSTR sczProductName = NULL;
BOOL fStartedDoc = FALSE;
BOOL fPrintedSomething = FALSE;
// Ensure the printer DC is in MM_TEXT mode.
if (0 == ::SetMapMode(hPrinterDC, MM_TEXT))
{
ExitWithLastError(hr, "failed to set map mode");
}
// Rendering to the same DC we are measuring.
::ZeroMemory(&fRange, sizeof(fRange));
fRange.hdc = fRange.hdcTarget = hPrinterDC;
// Set up the page.
rcPage.left = rcPage.top = 0;
rcPage.right = MulDiv(nHorizRes, ONE_INCH, nLogPixelsX);
rcPage.bottom = MulDiv(nVertRes, ONE_INCH, nLogPixelsX);
// Set up 1" margins all around.
rcPrintablePage.left = rcPage.left + ONE_INCH;
rcPrintablePage.top = rcPage.top + ONE_INCH;
rcPrintablePage.right = rcPage.right - ONE_INCH;
rcPrintablePage.bottom = rcPage.bottom - ONE_INCH;
// Set up the print job (standard printing stuff here).
::ZeroMemory(&dInfo, sizeof(dInfo));
dInfo.cbSize = sizeof(DOCINFO);
hr = WcaGetProperty(L"ProductName", &sczProductName);
if (FAILED(hr))
{
// If we fail to get the product name, don't fail, just leave it blank;
dInfo.lpszDocName = L"";
hr = S_OK;
}
else
{
dInfo.lpszDocName = sczProductName;
}
pDevnames = (LPDEVNAMES)::GlobalLock(vpPrintDlg->hDevNames);
ExitOnNullWithLastError(pDevnames, hr, "failed to get global lock");
dInfo.lpszOutput = (LPWSTR)pDevnames + pDevnames->wOutputOffset;
if (0 == ::GlobalUnlock(pDevnames))
{
ExitWithLastError(hr, "failed to release global lock");
}
// Start the document.
if (0 >= ::StartDocW(hPrinterDC, &dInfo))
{
ExitWithLastError(hr, "failed to start print document");
}
fStartedDoc = TRUE;
::ZeroMemory(&gTxex, sizeof(gTxex));
gTxex.flags = GTL_NUMCHARS | GTL_PRECISE;
lTextLength = ::SendMessageW(hRtfWnd, EM_GETTEXTLENGTHEX, (LONG_PTR)&gTxex, 0);
while (lTextPrinted < lTextLength)
{
// Start the page.
if (0 >= ::StartPage(hPrinterDC))
{
ExitWithLastError(hr, "failed to start print page");
}
// Always reset to the full printable page and start where the
// last text left off (or zero at the beginning).
fRange.rc = rcPrintablePage;
fRange.rcPage = rcPage;
fRange.chrg.cpMin = (LONG)lTextPrinted;
fRange.chrg.cpMax = -1;
// Print as much text as can fit on a page. The return value is
// the index of the first character on the next page. Using TRUE
// for the wParam parameter causes the text to be printed.
lTextPrinted = ::SendMessageW(hRtfWnd, EM_FORMATRANGE, TRUE, (LPARAM)&fRange);
fPrintedSomething = TRUE;
// If text wasn't printed (i.e. we didn't move past the point we started) then
// something must have gone wrong.
if (lTextPrinted <= fRange.chrg.cpMin)
{
hr = E_FAIL;
ExitOnFailure(hr, "failed to print some text");
}
// Print last page.
if (0 >= ::EndPage(hPrinterDC))
{
ExitWithLastError(hr, "failed to end print page");
}
}
LExit:
// Tell the control to release cached information, if we actually tried to
// print something.
if (fPrintedSomething)
{
::SendMessageW(hRtfWnd, EM_FORMATRANGE, 0, (LPARAM)NULL);
}
if (fStartedDoc)
{
::EndDoc(hPrinterDC);
}
ReleaseStr(sczProductName);
vhr = hr;
}
/********************************************************************
ShowErrorMessage - Display MessageBox showing the message for hr.
********************************************************************/
void ShowErrorMessage(
__in HRESULT hr
)
{
WCHAR wzMsg[STRING_CAPACITY];
#pragma prefast(push)
#pragma prefast(disable:25028)
if (0 != ::FormatMessageW(FORMAT_MESSAGE_FROM_SYSTEM, 0, hr, 0, wzMsg, countof(wzMsg), 0))
#pragma prefast(pop)
{
HWND hWnd = ::GetForegroundWindow();
::MessageBoxW(hWnd, wzMsg, L"PrintEULA", MB_OK | MB_ICONWARNING);
}
}
|