VirtualBox

source: vbox/trunk/src/VBox/Installer/win/Stub/VBoxStub.cpp@ 86149

Last change on this file since 86149 was 85121, checked in by vboxsync, 5 years ago

iprt/cdefs.h: Refactored the typedef use of DECLCALLBACK as well as DECLCALLBACKMEMBER to wrap the whole expression, similar to the DECLR?CALLBACKMEMBER macros. This allows adding a throw() at the end when compiling with the VC++ compiler to indicate that the callbacks won't throw anything, so we can stop supressing the C5039 warning about passing functions that can potential throw C++ exceptions to extern C code that can't necessarily cope with such (unwind,++). Introduced a few _EX variations that allows specifying different/no calling convention too, as that's handy when dynamically resolving host APIs. Fixed numerous places missing DECLCALLBACK and such. Left two angry @todos regarding use of CreateThread. bugref:9794

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 41.3 KB
Line 
1/* $Id: VBoxStub.cpp 85121 2020-07-08 19:33:26Z vboxsync $ */
2/** @file
3 * VBoxStub - VirtualBox's Windows installer stub.
4 */
5
6/*
7 * Copyright (C) 2010-2020 Oracle Corporation
8 *
9 * This file is part of VirtualBox Open Source Edition (OSE), as
10 * available from http://www.virtualbox.org. This file is free software;
11 * you can redistribute it and/or modify it under the terms of the GNU
12 * General Public License (GPL) as published by the Free Software
13 * Foundation, in version 2 as it comes in the "COPYING" file of the
14 * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
15 * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
16 */
17
18
19/*********************************************************************************************************************************
20* Header Files *
21*********************************************************************************************************************************/
22#if defined(_WIN32_WINNT) && _WIN32_WINNT < 0x0501
23# undef _WIN32_WINNT
24# define _WIN32_WINNT 0x0501 /* AttachConsole() / FreeConsole(). */
25#endif
26
27#include <iprt/win/windows.h>
28#include <iprt/win/commctrl.h>
29#include <fcntl.h>
30#include <io.h>
31#include <lmerr.h>
32#include <msiquery.h>
33#include <iprt/win/objbase.h>
34
35#include <iprt/win/shlobj.h>
36#include <stdlib.h>
37#include <stdio.h>
38#include <string.h>
39#include <strsafe.h>
40
41#include <VBox/version.h>
42
43#include <iprt/assert.h>
44#include <iprt/dir.h>
45#include <iprt/err.h>
46#include <iprt/file.h>
47#include <iprt/getopt.h>
48#include <iprt/initterm.h>
49#include <iprt/list.h>
50#include <iprt/mem.h>
51#include <iprt/message.h>
52#include <iprt/param.h>
53#include <iprt/path.h>
54#include <iprt/stream.h>
55#include <iprt/string.h>
56#include <iprt/thread.h>
57#include <iprt/utf16.h>
58
59#include "VBoxStub.h"
60#include "../StubBld/VBoxStubBld.h"
61#include "resource.h"
62
63#ifdef VBOX_WITH_CODE_SIGNING
64# include "VBoxStubCertUtil.h"
65# include "VBoxStubPublicCert.h"
66#endif
67
68#ifndef TARGET_NT4
69/* Use an own console window if run in verbose mode. */
70# define VBOX_STUB_WITH_OWN_CONSOLE
71#endif
72
73
74/*********************************************************************************************************************************
75* Defined Constants And Macros *
76*********************************************************************************************************************************/
77#define MY_UNICODE_SUB(str) L ##str
78#define MY_UNICODE(str) MY_UNICODE_SUB(str)
79
80
81/*********************************************************************************************************************************
82* Structures and Typedefs *
83*********************************************************************************************************************************/
84/**
85 * Cleanup record.
86 */
87typedef struct STUBCLEANUPREC
88{
89 /** List entry. */
90 RTLISTNODE ListEntry;
91 /** True if file, false if directory. */
92 bool fFile;
93 /** The path to the file or directory to clean up. */
94 char szPath[1];
95} STUBCLEANUPREC;
96/** Pointer to a cleanup record. */
97typedef STUBCLEANUPREC *PSTUBCLEANUPREC;
98
99
100/*********************************************************************************************************************************
101* Global Variables *
102*********************************************************************************************************************************/
103/** Whether it's a silent or interactive GUI driven install. */
104static bool g_fSilent = false;
105/** List of temporary files. */
106static RTLISTANCHOR g_TmpFiles;
107/** Verbosity flag. */
108static int g_iVerbosity = 0;
109
110
111
112/**
113 * Shows an error message box with a printf() style formatted string.
114 *
115 * @returns RTEXITCODE_FAILURE
116 * @param pszFmt Printf-style format string to show in the message box body.
117 *
118 */
119static RTEXITCODE ShowError(const char *pszFmt, ...)
120{
121 char *pszMsg;
122 va_list va;
123
124 va_start(va, pszFmt);
125 if (RTStrAPrintfV(&pszMsg, pszFmt, va))
126 {
127 if (g_fSilent)
128 RTMsgError("%s", pszMsg);
129 else
130 {
131 PRTUTF16 pwszMsg;
132 int rc = RTStrToUtf16(pszMsg, &pwszMsg);
133 if (RT_SUCCESS(rc))
134 {
135 MessageBoxW(GetDesktopWindow(), pwszMsg, MY_UNICODE(VBOX_STUB_TITLE), MB_ICONERROR);
136 RTUtf16Free(pwszMsg);
137 }
138 else
139 MessageBoxA(GetDesktopWindow(), pszMsg, VBOX_STUB_TITLE, MB_ICONERROR);
140 }
141 RTStrFree(pszMsg);
142 }
143 else /* Should never happen! */
144 AssertMsgFailed(("Failed to format error text of format string: %s!\n", pszFmt));
145 va_end(va);
146 return RTEXITCODE_FAILURE;
147}
148
149
150/**
151 * Shows a message box with a printf() style formatted string.
152 *
153 * @param uType Type of the message box (see MSDN).
154 * @param pszFmt Printf-style format string to show in the message box body.
155 *
156 */
157static void ShowInfo(const char *pszFmt, ...)
158{
159 char *pszMsg;
160 va_list va;
161 va_start(va, pszFmt);
162 int rc = RTStrAPrintfV(&pszMsg, pszFmt, va);
163 va_end(va);
164 if (rc >= 0)
165 {
166 if (g_fSilent)
167 RTPrintf("%s\n", pszMsg);
168 else
169 {
170 PRTUTF16 pwszMsg;
171 rc = RTStrToUtf16(pszMsg, &pwszMsg);
172 if (RT_SUCCESS(rc))
173 {
174 MessageBoxW(GetDesktopWindow(), pwszMsg, MY_UNICODE(VBOX_STUB_TITLE), MB_ICONINFORMATION);
175 RTUtf16Free(pwszMsg);
176 }
177 else
178 MessageBoxA(GetDesktopWindow(), pszMsg, VBOX_STUB_TITLE, MB_ICONINFORMATION);
179 }
180 }
181 else /* Should never happen! */
182 AssertMsgFailed(("Failed to format error text of format string: %s!\n", pszFmt));
183 RTStrFree(pszMsg);
184}
185
186
187/**
188 * Finds the specified in the resource section of the executable.
189 *
190 * @returns IPRT status code.
191 *
192 * @param pszDataName Name of resource to read.
193 * @param ppvResource Where to return the pointer to the data.
194 * @param pdwSize Where to return the size of the data (if found).
195 * Optional.
196 */
197static int FindData(const char *pszDataName, PVOID *ppvResource, DWORD *pdwSize)
198{
199 AssertReturn(pszDataName, VERR_INVALID_PARAMETER);
200 HINSTANCE hInst = NULL; /* indicates the executable image */
201
202 /* Find our resource. */
203 PRTUTF16 pwszDataName;
204 int rc = RTStrToUtf16(pszDataName, &pwszDataName);
205 AssertRCReturn(rc, rc);
206 HRSRC hRsrc = FindResourceExW(hInst,
207 (LPWSTR)RT_RCDATA,
208 pwszDataName,
209 MAKELANGID(LANG_NEUTRAL, SUBLANG_NEUTRAL));
210 RTUtf16Free(pwszDataName);
211 AssertReturn(hRsrc, VERR_IO_GEN_FAILURE);
212
213 /* Get resource size. */
214 DWORD cb = SizeofResource(hInst, hRsrc);
215 AssertReturn(cb > 0, VERR_NO_DATA);
216 if (pdwSize)
217 *pdwSize = cb;
218
219 /* Get pointer to resource. */
220 HGLOBAL hData = LoadResource(hInst, hRsrc);
221 AssertReturn(hData, VERR_IO_GEN_FAILURE);
222
223 /* Lock resource. */
224 *ppvResource = LockResource(hData);
225 AssertReturn(*ppvResource, VERR_IO_GEN_FAILURE);
226 return VINF_SUCCESS;
227}
228
229
230/**
231 * Finds the header for the given package.
232 *
233 * @returns Pointer to the package header on success. On failure NULL is
234 * returned after ShowError has been invoked.
235 * @param iPackage The package number.
236 */
237static PVBOXSTUBPKG FindPackageHeader(unsigned iPackage)
238{
239 char szHeaderName[32];
240 RTStrPrintf(szHeaderName, sizeof(szHeaderName), "HDR_%02d", iPackage);
241
242 PVBOXSTUBPKG pPackage;
243 int rc = FindData(szHeaderName, (PVOID *)&pPackage, NULL);
244 if (RT_FAILURE(rc))
245 {
246 ShowError("Internal error: Could not find package header #%u: %Rrc", iPackage, rc);
247 return NULL;
248 }
249
250 /** @todo validate it. */
251 return pPackage;
252}
253
254
255
256/**
257 * Constructs a full temporary file path from the given parameters.
258 *
259 * @returns iprt status code.
260 *
261 * @param pszTempPath The pure path to use for construction.
262 * @param pszTargetFileName The pure file name to use for construction.
263 * @param ppszTempFile Pointer to the constructed string. Must be freed
264 * using RTStrFree().
265 */
266static int GetTempFileAlloc(const char *pszTempPath,
267 const char *pszTargetFileName,
268 char **ppszTempFile)
269{
270 if (RTStrAPrintf(ppszTempFile, "%s\\%s", pszTempPath, pszTargetFileName) >= 0)
271 return VINF_SUCCESS;
272 return VERR_NO_STR_MEMORY;
273}
274
275
276/**
277 * Extracts a built-in resource to disk.
278 *
279 * @returns iprt status code.
280 *
281 * @param pszResourceName The resource name to extract.
282 * @param pszTempFile The full file path + name to extract the resource to.
283 *
284 */
285static int ExtractFile(const char *pszResourceName,
286 const char *pszTempFile)
287{
288#if 0 /* Another example of how unnecessarily complicated things get with
289 do-break-while-false and you end up with buggy code using uninitialized
290 variables. */
291 int rc;
292 RTFILE fh;
293 BOOL bCreatedFile = FALSE;
294
295 do
296 {
297 AssertMsgBreak(pszResourceName, ("Resource pointer invalid!\n")); /* rc is not initialized here, we'll return garbage. */
298 AssertMsgBreak(pszTempFile, ("Temp file pointer invalid!")); /* Ditto. */
299
300 /* Read the data of the built-in resource. */
301 PVOID pvData = NULL;
302 DWORD dwDataSize = 0;
303 rc = FindData(pszResourceName, &pvData, &dwDataSize);
304 AssertMsgRCBreak(rc, ("Could not read resource data!\n"));
305
306 /* Create new (and replace an old) file. */
307 rc = RTFileOpen(&fh, pszTempFile,
308 RTFILE_O_CREATE_REPLACE
309 | RTFILE_O_WRITE
310 | RTFILE_O_DENY_NOT_DELETE
311 | RTFILE_O_DENY_WRITE);
312 AssertMsgRCBreak(rc, ("Could not open file for writing!\n"));
313 bCreatedFile = TRUE;
314
315 /* Write contents to new file. */
316 size_t cbWritten = 0;
317 rc = RTFileWrite(fh, pvData, dwDataSize, &cbWritten);
318 AssertMsgRCBreak(rc, ("Could not open file for writing!\n"));
319 AssertMsgBreak(dwDataSize == cbWritten, ("File was not extracted completely! Disk full?\n"));
320
321 } while (0);
322
323 if (RTFileIsValid(fh)) /* fh is unused uninitalized (MSC agrees) */
324 RTFileClose(fh);
325
326 if (RT_FAILURE(rc))
327 {
328 if (bCreatedFile)
329 RTFileDelete(pszTempFile);
330 }
331
332#else /* This is exactly the same as above, except no bug and better assertion
333 message. Note only the return-success statment is indented, indicating
334 that the whole do-break-while-false approach was totally unnecessary. */
335
336 AssertPtrReturn(pszResourceName, VERR_INVALID_POINTER);
337 AssertPtrReturn(pszTempFile, VERR_INVALID_POINTER);
338
339 /* Read the data of the built-in resource. */
340 PVOID pvData = NULL;
341 DWORD dwDataSize = 0;
342 int rc = FindData(pszResourceName, &pvData, &dwDataSize);
343 AssertMsgRCReturn(rc, ("Could not read resource data: %Rrc\n", rc), rc);
344
345 /* Create new (and replace an old) file. */
346 RTFILE hFile;
347 rc = RTFileOpen(&hFile, pszTempFile,
348 RTFILE_O_CREATE_REPLACE
349 | RTFILE_O_WRITE
350 | RTFILE_O_DENY_NOT_DELETE
351 | RTFILE_O_DENY_WRITE);
352 AssertMsgRCReturn(rc, ("Could not open '%s' for writing: %Rrc\n", pszTempFile, rc), rc);
353
354 /* Write contents to new file. */
355 size_t cbWritten = 0;
356 rc = RTFileWrite(hFile, pvData, dwDataSize, &cbWritten);
357 AssertMsgStmt(cbWritten == dwDataSize || RT_FAILURE_NP(rc), ("%#zx vs %#x\n", cbWritten, dwDataSize), rc = VERR_WRITE_ERROR);
358
359 int rc2 = RTFileClose(hFile);
360 AssertRC(rc2);
361
362 if (RT_SUCCESS(rc))
363 return VINF_SUCCESS;
364
365 RTFileDelete(pszTempFile);
366
367#endif
368 return rc;
369}
370
371
372/**
373 * Extracts a built-in resource to disk.
374 *
375 * @returns iprt status code.
376 *
377 * @param pPackage Pointer to a VBOXSTUBPKG struct that contains the resource.
378 * @param pszTempFile The full file path + name to extract the resource to.
379 */
380static int Extract(const PVBOXSTUBPKG pPackage,
381 const char *pszTempFile)
382{
383 return ExtractFile(pPackage->szResourceName, pszTempFile);
384}
385
386
387/**
388 * Detects whether we're running on a 32- or 64-bit platform and returns the result.
389 *
390 * @returns TRUE if we're running on a 64-bit OS, FALSE if not.
391 */
392static BOOL IsWow64(void)
393{
394 BOOL fIsWow64 = TRUE;
395 fnIsWow64Process = (LPFN_ISWOW64PROCESS)GetProcAddress(GetModuleHandle(TEXT("kernel32")), "IsWow64Process");
396 if (NULL != fnIsWow64Process)
397 {
398 if (!fnIsWow64Process(GetCurrentProcess(), &fIsWow64))
399 {
400 /* Error in retrieving process type - assume that we're running on 32bit. */
401 return FALSE;
402 }
403 }
404 return fIsWow64;
405}
406
407
408/**
409 * Decides whether we need a specified package to handle or not.
410 *
411 * @returns @c true if we need to handle the specified package, @c false if not.
412 *
413 * @param pPackage Pointer to a VBOXSTUBPKG struct that contains the resource.
414 */
415static bool PackageIsNeeded(PVBOXSTUBPKG pPackage)
416{
417 if (pPackage->byArch == VBOXSTUBPKGARCH_ALL)
418 return true;
419 VBOXSTUBPKGARCH enmArch = IsWow64() ? VBOXSTUBPKGARCH_AMD64 : VBOXSTUBPKGARCH_X86;
420 return pPackage->byArch == enmArch;
421}
422
423
424/**
425 * Adds a cleanup record.
426 *
427 * @returns Fully complained boolean success indicator.
428 * @param pszPath The path to the file or directory to clean up.
429 * @param fFile @c true if file, @c false if directory.
430 */
431static bool AddCleanupRec(const char *pszPath, bool fFile)
432{
433 size_t cchPath = strlen(pszPath); Assert(cchPath > 0);
434 PSTUBCLEANUPREC pRec = (PSTUBCLEANUPREC)RTMemAlloc(RT_UOFFSETOF_DYN(STUBCLEANUPREC, szPath[cchPath + 1]));
435 if (!pRec)
436 {
437 ShowError("Out of memory!");
438 return false;
439 }
440 pRec->fFile = fFile;
441 memcpy(pRec->szPath, pszPath, cchPath + 1);
442
443 RTListPrepend(&g_TmpFiles, &pRec->ListEntry);
444 return true;
445}
446
447
448/**
449 * Cleans up all the extracted files and optionally removes the package
450 * directory.
451 *
452 * @param pszPkgDir The package directory, NULL if it shouldn't be
453 * removed.
454 */
455static void CleanUp(const char *pszPkgDir)
456{
457 for (int i = 0; i < 5; i++)
458 {
459 int rc;
460 bool fFinalTry = i == 4;
461
462 PSTUBCLEANUPREC pCur, pNext;
463 RTListForEachSafe(&g_TmpFiles, pCur, pNext, STUBCLEANUPREC, ListEntry)
464 {
465 if (pCur->fFile)
466 rc = RTFileDelete(pCur->szPath);
467 else
468 {
469 rc = RTDirRemoveRecursive(pCur->szPath, RTDIRRMREC_F_CONTENT_AND_DIR);
470 if (rc == VERR_DIR_NOT_EMPTY && fFinalTry)
471 rc = VINF_SUCCESS;
472 }
473 if (rc == VERR_FILE_NOT_FOUND || rc == VERR_PATH_NOT_FOUND)
474 rc = VINF_SUCCESS;
475 if (RT_SUCCESS(rc))
476 {
477 RTListNodeRemove(&pCur->ListEntry);
478 RTMemFree(pCur);
479 }
480 else if (fFinalTry)
481 {
482 if (pCur->fFile)
483 ShowError("Failed to delete temporary file '%s': %Rrc", pCur->szPath, rc);
484 else
485 ShowError("Failed to delete temporary directory '%s': %Rrc", pCur->szPath, rc);
486 }
487 }
488
489 if (RTListIsEmpty(&g_TmpFiles) || fFinalTry)
490 {
491 if (!pszPkgDir)
492 return;
493 rc = RTDirRemove(pszPkgDir);
494 if (RT_SUCCESS(rc) || rc == VERR_FILE_NOT_FOUND || rc == VERR_PATH_NOT_FOUND || fFinalTry)
495 return;
496 }
497
498 /* Delay a little and try again. */
499 RTThreadSleep(i == 0 ? 100 : 3000);
500 }
501}
502
503
504/**
505 * Processes an MSI package.
506 *
507 * @returns Fully complained exit code.
508 * @param pszMsi The path to the MSI to process.
509 * @param pszMsiArgs Any additional installer (MSI) argument
510 * @param fLogging Whether to enable installer logging.
511 */
512static RTEXITCODE ProcessMsiPackage(const char *pszMsi, const char *pszMsiArgs, bool fLogging)
513{
514 int rc;
515
516 /*
517 * Set UI level.
518 */
519 INSTALLUILEVEL enmDesiredUiLevel = g_fSilent ? INSTALLUILEVEL_NONE : INSTALLUILEVEL_FULL;
520 INSTALLUILEVEL enmRet = MsiSetInternalUI(enmDesiredUiLevel, NULL);
521 if (enmRet == INSTALLUILEVEL_NOCHANGE /* means error */)
522 return ShowError("Internal error: MsiSetInternalUI failed.");
523
524 /*
525 * Enable logging?
526 */
527 if (fLogging)
528 {
529 char szLogFile[RTPATH_MAX];
530 rc = RTStrCopy(szLogFile, sizeof(szLogFile), pszMsi);
531 if (RT_SUCCESS(rc))
532 {
533 RTPathStripFilename(szLogFile);
534 rc = RTPathAppend(szLogFile, sizeof(szLogFile), "VBoxInstallLog.txt");
535 }
536 if (RT_FAILURE(rc))
537 return ShowError("Internal error: Filename path too long.");
538
539 PRTUTF16 pwszLogFile;
540 rc = RTStrToUtf16(szLogFile, &pwszLogFile);
541 if (RT_FAILURE(rc))
542 return ShowError("RTStrToUtf16 failed on '%s': %Rrc", szLogFile, rc);
543
544 UINT uLogLevel = MsiEnableLogW(INSTALLLOGMODE_VERBOSE,
545 pwszLogFile,
546 INSTALLLOGATTRIBUTES_FLUSHEACHLINE);
547 RTUtf16Free(pwszLogFile);
548 if (uLogLevel != ERROR_SUCCESS)
549 return ShowError("MsiEnableLogW failed");
550 }
551
552 /*
553 * Initialize the common controls (extended version). This is necessary to
554 * run the actual .MSI installers with the new fancy visual control
555 * styles (XP+). Also, an integrated manifest is required.
556 */
557 INITCOMMONCONTROLSEX ccEx;
558 ccEx.dwSize = sizeof(INITCOMMONCONTROLSEX);
559 ccEx.dwICC = ICC_LINK_CLASS | ICC_LISTVIEW_CLASSES | ICC_PAGESCROLLER_CLASS |
560 ICC_PROGRESS_CLASS | ICC_STANDARD_CLASSES | ICC_TAB_CLASSES | ICC_TREEVIEW_CLASSES |
561 ICC_UPDOWN_CLASS | ICC_USEREX_CLASSES | ICC_WIN95_CLASSES;
562 InitCommonControlsEx(&ccEx); /* Ignore failure. */
563
564 /*
565 * Convert both strings to UTF-16 and start the installation.
566 */
567 PRTUTF16 pwszMsi;
568 rc = RTStrToUtf16(pszMsi, &pwszMsi);
569 if (RT_FAILURE(rc))
570 return ShowError("RTStrToUtf16 failed on '%s': %Rrc", pszMsi, rc);
571 PRTUTF16 pwszMsiArgs;
572 rc = RTStrToUtf16(pszMsiArgs, &pwszMsiArgs);
573 if (RT_FAILURE(rc))
574 {
575 RTUtf16Free(pwszMsi);
576 return ShowError("RTStrToUtf16 failed on '%s': %Rrc", pszMsi, rc);
577 }
578
579 UINT uStatus = MsiInstallProductW(pwszMsi, pwszMsiArgs);
580 RTUtf16Free(pwszMsi);
581 RTUtf16Free(pwszMsiArgs);
582
583 if (uStatus == ERROR_SUCCESS)
584 return RTEXITCODE_SUCCESS;
585 if (uStatus == ERROR_SUCCESS_REBOOT_REQUIRED)
586 {
587 if (g_fSilent)
588 RTMsgInfo("Reboot required (by %s)\n", pszMsi);
589 return (RTEXITCODE)uStatus;
590 }
591
592 /*
593 * Installation failed. Figure out what to say.
594 */
595 switch (uStatus)
596 {
597 case ERROR_INSTALL_USEREXIT:
598 /* Don't say anything? */
599 break;
600
601 case ERROR_INSTALL_PACKAGE_VERSION:
602 ShowError("This installation package cannot be installed by the Windows Installer service.\n"
603 "You must install a Windows service pack that contains a newer version of the Windows Installer service.");
604 break;
605
606 case ERROR_INSTALL_PLATFORM_UNSUPPORTED:
607 ShowError("This installation package is not supported on this platform.");
608 break;
609
610 default:
611 {
612 /*
613 * Try get windows to format the message.
614 */
615 DWORD dwFormatFlags = FORMAT_MESSAGE_ALLOCATE_BUFFER
616 | FORMAT_MESSAGE_IGNORE_INSERTS
617 | FORMAT_MESSAGE_FROM_SYSTEM;
618 HMODULE hModule = NULL;
619 if (uStatus >= NERR_BASE && uStatus <= MAX_NERR)
620 {
621 hModule = LoadLibraryExW(L"netmsg.dll",
622 NULL,
623 LOAD_LIBRARY_AS_DATAFILE);
624 if (hModule != NULL)
625 dwFormatFlags |= FORMAT_MESSAGE_FROM_HMODULE;
626 }
627
628 PWSTR pwszMsg;
629 if (FormatMessageW(dwFormatFlags,
630 hModule, /* If NULL, load system stuff. */
631 uStatus,
632 MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
633 (PWSTR)&pwszMsg,
634 0,
635 NULL) > 0)
636 {
637 ShowError("Installation failed! Error: %ls", pwszMsg);
638 LocalFree(pwszMsg);
639 }
640 else /* If text lookup failed, show at least the error number. */
641 ShowError("Installation failed! Error: %u", uStatus);
642
643 if (hModule)
644 FreeLibrary(hModule);
645 break;
646 }
647 }
648
649 return RTEXITCODE_FAILURE;
650}
651
652
653/**
654 * Processes a package.
655 *
656 * @returns Fully complained exit code.
657 * @param iPackage The package number.
658 * @param pszPkgDir The package directory (aka extraction dir).
659 * @param pszMsiArgs Any additional installer (MSI) argument
660 * @param fLogging Whether to enable installer logging.
661 */
662static RTEXITCODE ProcessPackage(unsigned iPackage, const char *pszPkgDir, const char *pszMsiArgs, bool fLogging)
663{
664 /*
665 * Get the package header and check if it's needed.
666 */
667 PVBOXSTUBPKG pPackage = FindPackageHeader(iPackage);
668 if (pPackage == NULL)
669 return RTEXITCODE_FAILURE;
670
671 if (!PackageIsNeeded(pPackage))
672 return RTEXITCODE_SUCCESS;
673
674 /*
675 * Deal with the file based on it's extension.
676 */
677 char szPkgFile[RTPATH_MAX];
678 int rc = RTPathJoin(szPkgFile, sizeof(szPkgFile), pszPkgDir, pPackage->szFileName);
679 if (RT_FAILURE(rc))
680 return ShowError("Internal error: RTPathJoin failed: %Rrc", rc);
681 RTPathChangeToDosSlashes(szPkgFile, true /* Force conversion. */); /* paranoia */
682
683 RTEXITCODE rcExit;
684 const char *pszSuff = RTPathSuffix(szPkgFile);
685 if (RTStrICmp(pszSuff, ".msi") == 0)
686 rcExit = ProcessMsiPackage(szPkgFile, pszMsiArgs, fLogging);
687 else if (RTStrICmp(pszSuff, ".cab") == 0)
688 rcExit = RTEXITCODE_SUCCESS; /* Ignore .cab files, they're generally referenced by other files. */
689 else
690 rcExit = ShowError("Internal error: Do not know how to handle file '%s'.", pPackage->szFileName);
691
692 return rcExit;
693}
694
695
696#ifdef VBOX_WITH_CODE_SIGNING
697/**
698 * Install the public certificate into TrustedPublishers so the installer won't
699 * prompt the user during silent installs.
700 *
701 * @returns Fully complained exit code.
702 */
703static RTEXITCODE InstallCertificates(void)
704{
705 for (uint32_t i = 0; i < RT_ELEMENTS(g_aVBoxStubTrustedCerts); i++)
706 {
707 if (!addCertToStore(CERT_SYSTEM_STORE_LOCAL_MACHINE,
708 "TrustedPublisher",
709 g_aVBoxStubTrustedCerts[i].pab,
710 g_aVBoxStubTrustedCerts[i].cb))
711 return ShowError("Failed to construct install certificate.");
712 }
713 return RTEXITCODE_SUCCESS;
714}
715#endif /* VBOX_WITH_CODE_SIGNING */
716
717
718/**
719 * Copies the "<exepath>.custom" directory to the extraction path if it exists.
720 *
721 * This is used by the MSI packages from the resource section.
722 *
723 * @returns Fully complained exit code.
724 * @param pszDstDir The destination directory.
725 */
726static RTEXITCODE CopyCustomDir(const char *pszDstDir)
727{
728 char szSrcDir[RTPATH_MAX];
729 int rc = RTPathExecDir(szSrcDir, sizeof(szSrcDir));
730 if (RT_SUCCESS(rc))
731 rc = RTPathAppend(szSrcDir, sizeof(szSrcDir), ".custom");
732 if (RT_FAILURE(rc))
733 return ShowError("Failed to construct '.custom' dir path: %Rrc", rc);
734
735 if (RTDirExists(szSrcDir))
736 {
737 /*
738 * Use SHFileOperation w/ FO_COPY to do the job. This API requires an
739 * extra zero at the end of both source and destination paths.
740 */
741 size_t cwc;
742 RTUTF16 wszSrcDir[RTPATH_MAX + 1];
743 PRTUTF16 pwszSrcDir = wszSrcDir;
744 rc = RTStrToUtf16Ex(szSrcDir, RTSTR_MAX, &pwszSrcDir, RTPATH_MAX, &cwc);
745 if (RT_FAILURE(rc))
746 return ShowError("RTStrToUtf16Ex failed on '%s': %Rrc", szSrcDir, rc);
747 wszSrcDir[cwc] = '\0';
748
749 RTUTF16 wszDstDir[RTPATH_MAX + 1];
750 PRTUTF16 pwszDstDir = wszSrcDir;
751 rc = RTStrToUtf16Ex(pszDstDir, RTSTR_MAX, &pwszDstDir, RTPATH_MAX, &cwc);
752 if (RT_FAILURE(rc))
753 return ShowError("RTStrToUtf16Ex failed on '%s': %Rrc", pszDstDir, rc);
754 wszDstDir[cwc] = '\0';
755
756 SHFILEOPSTRUCTW FileOp;
757 RT_ZERO(FileOp); /* paranoia */
758 FileOp.hwnd = NULL;
759 FileOp.wFunc = FO_COPY;
760 FileOp.pFrom = wszSrcDir;
761 FileOp.pTo = wszDstDir;
762 FileOp.fFlags = FOF_SILENT
763 | FOF_NOCONFIRMATION
764 | FOF_NOCONFIRMMKDIR
765 | FOF_NOERRORUI;
766 FileOp.fAnyOperationsAborted = FALSE;
767 FileOp.hNameMappings = NULL;
768 FileOp.lpszProgressTitle = NULL;
769
770 rc = SHFileOperationW(&FileOp);
771 if (rc != 0) /* Not a Win32 status code! */
772 return ShowError("Copying the '.custom' dir failed: %#x", rc);
773
774 /*
775 * Add a cleanup record for recursively deleting the destination
776 * .custom directory. We should actually add this prior to calling
777 * SHFileOperationW since it may partially succeed...
778 */
779 char *pszDstSubDir = RTPathJoinA(pszDstDir, ".custom");
780 if (!pszDstSubDir)
781 return ShowError("Out of memory!");
782 bool fRc = AddCleanupRec(pszDstSubDir, false /*fFile*/);
783 RTStrFree(pszDstSubDir);
784 if (!fRc)
785 return RTEXITCODE_FAILURE;
786 }
787
788 return RTEXITCODE_SUCCESS;
789}
790
791
792static RTEXITCODE ExtractFiles(unsigned cPackages, const char *pszDstDir, bool fExtractOnly, bool *pfCreatedExtractDir)
793{
794 int rc;
795
796 /*
797 * Make sure the directory exists.
798 */
799 *pfCreatedExtractDir = false;
800 if (!RTDirExists(pszDstDir))
801 {
802 rc = RTDirCreate(pszDstDir, 0700, 0);
803 if (RT_FAILURE(rc))
804 return ShowError("Failed to create extraction path '%s': %Rrc", pszDstDir, rc);
805 *pfCreatedExtractDir = true;
806 }
807
808 /*
809 * Extract files.
810 */
811 for (unsigned k = 0; k < cPackages; k++)
812 {
813 PVBOXSTUBPKG pPackage = FindPackageHeader(k);
814 if (!pPackage)
815 return RTEXITCODE_FAILURE; /* Done complaining already. */
816
817 if (fExtractOnly || PackageIsNeeded(pPackage))
818 {
819 char szDstFile[RTPATH_MAX];
820 rc = RTPathJoin(szDstFile, sizeof(szDstFile), pszDstDir, pPackage->szFileName);
821 if (RT_FAILURE(rc))
822 return ShowError("Internal error: RTPathJoin failed: %Rrc", rc);
823
824 rc = Extract(pPackage, szDstFile);
825 if (RT_FAILURE(rc))
826 return ShowError("Error extracting package #%u: %Rrc", k, rc);
827
828 if (!fExtractOnly && !AddCleanupRec(szDstFile, true /*fFile*/))
829 {
830 RTFileDelete(szDstFile);
831 return RTEXITCODE_FAILURE;
832 }
833 }
834 }
835
836 return RTEXITCODE_SUCCESS;
837}
838
839
840int WINAPI WinMain(HINSTANCE hInstance,
841 HINSTANCE hPrevInstance,
842 char *lpCmdLine,
843 int nCmdShow)
844{
845 RT_NOREF(hInstance, hPrevInstance, lpCmdLine, nCmdShow);
846 char **argv = __argv;
847 int argc = __argc;
848
849 /*
850 * Init IPRT. This is _always_ the very first thing we do.
851 */
852 int vrc = RTR3InitExe(argc, &argv, RTR3INIT_FLAGS_STANDALONE_APP);
853 if (RT_FAILURE(vrc))
854 return RTMsgInitFailure(vrc);
855
856 /*
857 * Check if we're already running and jump out if so.
858 *
859 * Note! Do not use a global namespace ("Global\\") for mutex name here,
860 * will blow up NT4 compatibility!
861 */
862 HANDLE hMutexAppRunning = CreateMutex(NULL, FALSE, "VBoxStubInstaller");
863 if ( hMutexAppRunning != NULL
864 && GetLastError() == ERROR_ALREADY_EXISTS)
865 {
866 /* Close the mutex for this application instance. */
867 CloseHandle(hMutexAppRunning);
868 hMutexAppRunning = NULL;
869 return RTEXITCODE_FAILURE;
870 }
871
872 /*
873 * Parse arguments.
874 */
875
876 /* Parameter variables. */
877 bool fExtractOnly = false;
878 bool fEnableLogging = false;
879#ifdef VBOX_WITH_CODE_SIGNING
880 bool fEnableSilentCert = true;
881#endif
882 bool fIgnoreReboot = false;
883 char szExtractPath[RTPATH_MAX] = {0};
884 char szMSIArgs[_4K] = {0};
885
886 /* Parameter definitions. */
887 static const RTGETOPTDEF s_aOptions[] =
888 {
889 /** @todo Replace short parameters with enums since they're not
890 * used (and not documented to the public). */
891 { "--extract", 'x', RTGETOPT_REQ_NOTHING },
892 { "-extract", 'x', RTGETOPT_REQ_NOTHING },
893 { "/extract", 'x', RTGETOPT_REQ_NOTHING },
894 { "--silent", 's', RTGETOPT_REQ_NOTHING },
895 { "-silent", 's', RTGETOPT_REQ_NOTHING },
896 { "/silent", 's', RTGETOPT_REQ_NOTHING },
897#ifdef VBOX_WITH_CODE_SIGNING
898 { "--no-silent-cert", 'c', RTGETOPT_REQ_NOTHING },
899 { "-no-silent-cert", 'c', RTGETOPT_REQ_NOTHING },
900 { "/no-silent-cert", 'c', RTGETOPT_REQ_NOTHING },
901#endif
902 { "--logging", 'l', RTGETOPT_REQ_NOTHING },
903 { "-logging", 'l', RTGETOPT_REQ_NOTHING },
904 { "/logging", 'l', RTGETOPT_REQ_NOTHING },
905 { "--path", 'p', RTGETOPT_REQ_STRING },
906 { "-path", 'p', RTGETOPT_REQ_STRING },
907 { "/path", 'p', RTGETOPT_REQ_STRING },
908 { "--msiparams", 'm', RTGETOPT_REQ_STRING },
909 { "-msiparams", 'm', RTGETOPT_REQ_STRING },
910 { "--reinstall", 'f', RTGETOPT_REQ_NOTHING },
911 { "-reinstall", 'f', RTGETOPT_REQ_NOTHING },
912 { "/reinstall", 'f', RTGETOPT_REQ_NOTHING },
913 { "--ignore-reboot", 'r', RTGETOPT_REQ_NOTHING },
914 { "--verbose", 'v', RTGETOPT_REQ_NOTHING },
915 { "-verbose", 'v', RTGETOPT_REQ_NOTHING },
916 { "/verbose", 'v', RTGETOPT_REQ_NOTHING },
917 { "--version", 'V', RTGETOPT_REQ_NOTHING },
918 { "-version", 'V', RTGETOPT_REQ_NOTHING },
919 { "/version", 'V', RTGETOPT_REQ_NOTHING },
920 { "-v", 'V', RTGETOPT_REQ_NOTHING },
921 { "--help", 'h', RTGETOPT_REQ_NOTHING },
922 { "-help", 'h', RTGETOPT_REQ_NOTHING },
923 { "/help", 'h', RTGETOPT_REQ_NOTHING },
924 { "/?", 'h', RTGETOPT_REQ_NOTHING },
925 };
926
927 RTEXITCODE rcExit = RTEXITCODE_SUCCESS;
928
929 /* Parse the parameters. */
930 int ch;
931 bool fExitEarly = false;
932 RTGETOPTUNION ValueUnion;
933 RTGETOPTSTATE GetState;
934 RTGetOptInit(&GetState, argc, argv, s_aOptions, RT_ELEMENTS(s_aOptions), 1, 0);
935 while ( (ch = RTGetOpt(&GetState, &ValueUnion))
936 && rcExit == RTEXITCODE_SUCCESS
937 && !fExitEarly)
938 {
939 switch (ch)
940 {
941 case 'f': /* Force re-installation. */
942 if (szMSIArgs[0])
943 vrc = RTStrCat(szMSIArgs, sizeof(szMSIArgs), " ");
944 if (RT_SUCCESS(vrc))
945 vrc = RTStrCat(szMSIArgs, sizeof(szMSIArgs),
946 "REINSTALLMODE=vomus REINSTALL=ALL");
947 if (RT_FAILURE(vrc))
948 rcExit = ShowError("MSI parameters are too long.");
949 break;
950
951 case 'x':
952 fExtractOnly = true;
953 break;
954
955 case 's':
956 g_fSilent = true;
957 break;
958
959#ifdef VBOX_WITH_CODE_SIGNING
960 case 'c':
961 fEnableSilentCert = false;
962 break;
963#endif
964 case 'l':
965 fEnableLogging = true;
966 break;
967
968 case 'p':
969 vrc = RTStrCopy(szExtractPath, sizeof(szExtractPath), ValueUnion.psz);
970 if (RT_FAILURE(vrc))
971 rcExit = ShowError("Extraction path is too long.");
972 break;
973
974 case 'm':
975 if (szMSIArgs[0])
976 vrc = RTStrCat(szMSIArgs, sizeof(szMSIArgs), " ");
977 if (RT_SUCCESS(vrc))
978 vrc = RTStrCat(szMSIArgs, sizeof(szMSIArgs), ValueUnion.psz);
979 if (RT_FAILURE(vrc))
980 rcExit = ShowError("MSI parameters are too long.");
981 break;
982
983 case 'r':
984 fIgnoreReboot = true;
985 break;
986
987 case 'V':
988 ShowInfo("Version: %d.%d.%d.%d",
989 VBOX_VERSION_MAJOR, VBOX_VERSION_MINOR, VBOX_VERSION_BUILD,
990 VBOX_SVN_REV);
991 fExitEarly = true;
992 break;
993
994 case 'v':
995 g_iVerbosity++;
996 break;
997
998 case 'h':
999 ShowInfo("-- %s v%d.%d.%d.%d --\n"
1000 "\n"
1001 "Command Line Parameters:\n\n"
1002 "--extract - Extract file contents to temporary directory\n"
1003 "--help - Print this help and exit\n"
1004 "--logging - Enables installer logging\n"
1005 "--msiparams <parameters> - Specifies extra parameters for the MSI installers\n"
1006 "--no-silent-cert - Do not install VirtualBox Certificate automatically when --silent option is specified\n"
1007 "--path - Sets the path of the extraction directory\n"
1008 "--reinstall - Forces VirtualBox to get re-installed\n"
1009 "--ignore-reboot - Don't set exit code to 3010 if a reboot is required\n"
1010 "--silent - Enables silent mode installation\n"
1011 "--version - Print version number and exit\n"
1012 "\n"
1013 "Examples:\n"
1014 "%s --msiparams INSTALLDIR=C:\\VBox\n"
1015 "%s --extract -path C:\\VBox",
1016 VBOX_STUB_TITLE, VBOX_VERSION_MAJOR, VBOX_VERSION_MINOR, VBOX_VERSION_BUILD, VBOX_SVN_REV,
1017 argv[0], argv[0]);
1018 fExitEarly = true;
1019 break;
1020
1021 case VINF_GETOPT_NOT_OPTION:
1022 /* Are (optional) MSI parameters specified and this is the last
1023 * parameter? Append everything to the MSI parameter list then. */
1024 if (szMSIArgs[0])
1025 {
1026 vrc = RTStrCat(szMSIArgs, sizeof(szMSIArgs), " ");
1027 if (RT_SUCCESS(vrc))
1028 vrc = RTStrCat(szMSIArgs, sizeof(szMSIArgs), ValueUnion.psz);
1029 if (RT_FAILURE(vrc))
1030 rcExit = ShowError("MSI parameters are too long.");
1031 continue;
1032 }
1033 /* Fall through is intentional. */
1034
1035 default:
1036 if (g_fSilent)
1037 rcExit = RTGetOptPrintError(ch, &ValueUnion);
1038 if (ch == VERR_GETOPT_UNKNOWN_OPTION)
1039 rcExit = ShowError("Unknown option \"%s\"\n"
1040 "Please refer to the command line help by specifying \"/?\"\n"
1041 "to get more information.", ValueUnion.psz);
1042 else
1043 rcExit = ShowError("Parameter parsing error: %Rrc\n"
1044 "Please refer to the command line help by specifying \"/?\"\n"
1045 "to get more information.", ch);
1046 break;
1047 }
1048 }
1049
1050 /* Check if we can bail out early. */
1051 if (fExitEarly)
1052 return rcExit;
1053
1054 if (rcExit != RTEXITCODE_SUCCESS)
1055 vrc = VERR_PARSE_ERROR;
1056
1057/** @todo
1058 *
1059 * Split the remainder up in functions and simplify the code flow!!
1060 *
1061 * */
1062
1063#if defined(_WIN32_WINNT) && _WIN32_WINNT >= 0x0501
1064# ifdef VBOX_STUB_WITH_OWN_CONSOLE /* Use an own console window if run in debug mode. */
1065 if ( RT_SUCCESS(vrc)
1066 && g_iVerbosity)
1067 {
1068 if (!AllocConsole())
1069 {
1070 DWORD dwErr = GetLastError();
1071 ShowError("Unable to allocate console, error = %ld\n",
1072 dwErr);
1073
1074 /* Close the mutex for this application instance. */
1075 CloseHandle(hMutexAppRunning);
1076 hMutexAppRunning = NULL;
1077 return RTEXITCODE_FAILURE;
1078 }
1079
1080 freopen("CONOUT$", "w", stdout);
1081 setvbuf(stdout, NULL, _IONBF, 0);
1082
1083 freopen("CONOUT$", "w", stderr);
1084 }
1085# endif /* VBOX_STUB_WITH_OWN_CONSOLE */
1086#endif
1087
1088 if ( RT_SUCCESS(vrc)
1089 && g_iVerbosity)
1090 {
1091 RTPrintf("Silent installation : %RTbool\n", g_fSilent);
1092 RTPrintf("Logging enabled : %RTbool\n", fEnableLogging);
1093#ifdef VBOX_WITH_CODE_SIGNING
1094 RTPrintf("Certificate installation : %RTbool\n", fEnableSilentCert);
1095#endif
1096 RTPrintf("Additional MSI parameters: %s\n",
1097 szMSIArgs[0] ? szMSIArgs : "<None>");
1098 }
1099
1100 /*
1101 * 32-bit is not officially supported any more.
1102 */
1103 if ( !fExtractOnly
1104 && !g_fSilent
1105 && !IsWow64())
1106 {
1107 rcExit = ShowError("32-bit Windows hosts are not supported by this VirtualBox release.");
1108 vrc = VERR_NOT_SUPPORTED;
1109 }
1110
1111 if (RT_SUCCESS(vrc))
1112 {
1113 /*
1114 * Determine the extration path if not given by the user, and gather some
1115 * other bits we'll be needing later.
1116 */
1117 if (szExtractPath[0] == '\0')
1118 {
1119 vrc = RTPathTemp(szExtractPath, sizeof(szExtractPath));
1120 if (RT_SUCCESS(vrc))
1121 vrc = RTPathAppend(szExtractPath, sizeof(szExtractPath), "VirtualBox");
1122 if (RT_FAILURE(vrc))
1123 ShowError("Failed to determine extraction path (%Rrc)", vrc);
1124
1125 }
1126 else
1127 {
1128 /** @todo should check if there is a .custom subdirectory there or not. */
1129 }
1130 RTPathChangeToDosSlashes(szExtractPath,
1131 true /* Force conversion. */); /* MSI requirement. */
1132 }
1133
1134 /* Read our manifest. */
1135 if (RT_SUCCESS(vrc))
1136 {
1137 PVBOXSTUBPKGHEADER pHeader;
1138 vrc = FindData("MANIFEST", (PVOID *)&pHeader, NULL);
1139 if (RT_SUCCESS(vrc))
1140 {
1141 /** @todo If we could, we should validate the header. Only the magic isn't
1142 * commonly defined, nor the version number... */
1143
1144 RTListInit(&g_TmpFiles);
1145
1146 /*
1147 * Up to this point, we haven't done anything that requires any cleanup.
1148 * From here on, we do everything in function so we can counter clean up.
1149 */
1150 bool fCreatedExtractDir;
1151 rcExit = ExtractFiles(pHeader->byCntPkgs, szExtractPath,
1152 fExtractOnly, &fCreatedExtractDir);
1153 if (rcExit == RTEXITCODE_SUCCESS)
1154 {
1155 if (fExtractOnly)
1156 ShowInfo("Files were extracted to: %s", szExtractPath);
1157 else
1158 {
1159 rcExit = CopyCustomDir(szExtractPath);
1160#ifdef VBOX_WITH_CODE_SIGNING
1161 if (rcExit == RTEXITCODE_SUCCESS && fEnableSilentCert && g_fSilent)
1162 rcExit = InstallCertificates();
1163#endif
1164 unsigned iPackage = 0;
1165 while ( iPackage < pHeader->byCntPkgs
1166 && (rcExit == RTEXITCODE_SUCCESS || rcExit == (RTEXITCODE)ERROR_SUCCESS_REBOOT_REQUIRED))
1167 {
1168 RTEXITCODE rcExit2 = ProcessPackage(iPackage, szExtractPath, szMSIArgs, fEnableLogging);
1169 if (rcExit2 != RTEXITCODE_SUCCESS)
1170 rcExit = rcExit2;
1171 iPackage++;
1172 }
1173
1174 /* Don't fail if cleanup fail. At least for now. */
1175 CleanUp( !fEnableLogging
1176 && fCreatedExtractDir ? szExtractPath : NULL);
1177 }
1178 }
1179
1180 /* Free any left behind cleanup records (not strictly needed). */
1181 PSTUBCLEANUPREC pCur, pNext;
1182 RTListForEachSafe(&g_TmpFiles, pCur, pNext, STUBCLEANUPREC, ListEntry)
1183 {
1184 RTListNodeRemove(&pCur->ListEntry);
1185 RTMemFree(pCur);
1186 }
1187 }
1188 else
1189 rcExit = ShowError("Internal package error: Manifest not found (%Rrc)", vrc);
1190 }
1191
1192#if defined(_WIN32_WINNT) && _WIN32_WINNT >= 0x0501
1193# ifdef VBOX_STUB_WITH_OWN_CONSOLE
1194 if (g_iVerbosity)
1195 FreeConsole();
1196# endif /* VBOX_STUB_WITH_OWN_CONSOLE */
1197#endif
1198
1199 /*
1200 * Release instance mutex.
1201 */
1202 if (hMutexAppRunning != NULL)
1203 {
1204 CloseHandle(hMutexAppRunning);
1205 hMutexAppRunning = NULL;
1206 }
1207
1208 return rcExit != (RTEXITCODE)ERROR_SUCCESS_REBOOT_REQUIRED || !fIgnoreReboot ? rcExit : RTEXITCODE_SUCCESS;
1209}
1210
Note: See TracBrowser for help on using the repository browser.

© 2024 Oracle Support Privacy / Do Not Sell My Info Terms of Use Trademark Policy Automated Access Etiquette