VirtualBox

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

Last change on this file since 93941 was 93391, checked in by vboxsync, 3 years ago

Installer/win: Simplified VBoxStub's main function a little.

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

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