VirtualBox

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

Last change on this file since 109258 was 109258, checked in by vboxsync, 11 days ago

Windows host installer: Don't use randomized file names which already are in a randomized temp directory. Should make it easier for users recovering from failed installs. bugref:10201

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 69.7 KB
Line 
1/* $Id: VBoxStub.cpp 109258 2025-05-14 13:06:16Z vboxsync $ */
2/** @file
3 * VBoxStub - VirtualBox's Windows installer stub.
4 */
5
6/*
7 * Copyright (C) 2010-2024 Oracle and/or its affiliates.
8 *
9 * This file is part of VirtualBox base platform packages, as
10 * available from https://www.virtualbox.org.
11 *
12 * This program is free software; you can redistribute it and/or
13 * modify it under the terms of the GNU General Public License
14 * as published by the Free Software Foundation, in version 3 of the
15 * License.
16 *
17 * This program is distributed in the hope that it will be useful, but
18 * WITHOUT ANY WARRANTY; without even the implied warranty of
19 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
20 * General Public License for more details.
21 *
22 * You should have received a copy of the GNU General Public License
23 * along with this program; if not, see <https://www.gnu.org/licenses>.
24 *
25 * SPDX-License-Identifier: GPL-3.0-only
26 */
27
28
29/*********************************************************************************************************************************
30* Header Files *
31*********************************************************************************************************************************/
32#include <iprt/win/windows.h>
33#include <iprt/win/commctrl.h>
34#include <lmerr.h>
35#include <msiquery.h>
36#include <iprt/win/objbase.h>
37#include <iprt/win/shlobj.h>
38
39#include <VBox/version.h>
40
41#include <iprt/assert.h>
42#include <iprt/dir.h>
43#include <iprt/err.h>
44#include <iprt/file.h>
45#include <iprt/getopt.h>
46#include <iprt/initterm.h>
47#include <iprt/list.h>
48#include <iprt/mem.h>
49#include <iprt/message.h>
50#include <iprt/param.h>
51#include <iprt/path.h>
52#include <iprt/stream.h>
53#include <iprt/string.h>
54#include <iprt/system.h>
55#include <iprt/thread.h>
56#include <iprt/utf16.h>
57
58#ifndef IPRT_NO_CRT
59# include <stdio.h>
60# include <stdlib.h>
61#endif
62
63#ifdef VBOX_STUB_WITH_SPLASH
64# include <math.h>
65# include <gdiplus.h>
66# include <shlwapi.h>
67#endif
68
69#include "../StubBld/VBoxStubBld.h"
70#include "resource.h"
71
72#ifdef VBOX_WITH_CODE_SIGNING
73# include "VBoxStubCertUtil.h"
74# include "VBoxStubPublicCert.h"
75#endif
76
77
78/*********************************************************************************************************************************
79* Defined Constants And Macros *
80*********************************************************************************************************************************/
81#define VBOX_STUB_TITLE "VirtualBox Installer"
82
83#define MY_UNICODE_SUB(str) L ##str
84#define MY_UNICODE(str) MY_UNICODE_SUB(str)
85
86/* Use an own console window if run in verbose mode. */
87#define VBOX_STUB_WITH_OWN_CONSOLE
88
89
90/*********************************************************************************************************************************
91* Structures and Typedefs *
92*********************************************************************************************************************************/
93/**
94 * Cleanup record.
95 */
96typedef struct STUBCLEANUPREC
97{
98 /** List entry. */
99 RTLISTNODE ListEntry;
100 /** Stub package index (zero-based) this record belongs to. */
101 unsigned idxPkg;
102 /** True if file, false if directory. */
103 bool fFile;
104 /** Set if we should not delete the file/directory.
105 * This is used for user supplied extraction directories. */
106 bool fDontDelete;
107 union
108 {
109 /** File handle (if \a fFile is \c true). */
110 RTFILE hFile;
111 /** Directory handle (if \a fFile is \c false). */
112 RTDIR hDir;
113 };
114 /** The path to the file or directory to clean up. */
115 char szPath[1];
116} STUBCLEANUPREC;
117/** Pointer to a cleanup record. */
118typedef STUBCLEANUPREC *PSTUBCLEANUPREC;
119
120typedef BOOL (WINAPI *PFNISWOW64PROCESS)(HANDLE, PBOOL);
121typedef BOOL (WINAPI *PFNISWOW64PROCESS2)(HANDLE, USHORT *, USHORT *);
122
123
124/*********************************************************************************************************************************
125* Prototypes *
126*********************************************************************************************************************************/
127static PSTUBCLEANUPREC AddCleanupRec(const char *pszPath, bool fIsFile);
128
129
130/*********************************************************************************************************************************
131* Global Variables *
132*********************************************************************************************************************************/
133/** Whether it's a silent or interactive GUI driven install. */
134static bool g_fSilent = false;
135/** List of temporary files. */
136static RTLISTANCHOR g_TmpFiles;
137/** Verbosity flag. */
138static int g_iVerbosity = 0;
139
140
141
142/**
143 * Shows an error message box with a printf() style formatted string.
144 *
145 * @returns RTEXITCODE_FAILURE
146 * @param pszFmt Printf-style format string to show in the message box body.
147 *
148 */
149static RTEXITCODE ShowError(const char *pszFmt, ...)
150{
151 char *pszMsg;
152 va_list va;
153
154 va_start(va, pszFmt);
155 if (RTStrAPrintfV(&pszMsg, pszFmt, va))
156 {
157 if (g_fSilent)
158 RTMsgError("%s", pszMsg);
159 else
160 {
161 PRTUTF16 pwszMsg;
162 int rc = RTStrToUtf16(pszMsg, &pwszMsg);
163 if (RT_SUCCESS(rc))
164 {
165 MessageBoxW(GetDesktopWindow(), pwszMsg, MY_UNICODE(VBOX_STUB_TITLE), MB_ICONERROR);
166 RTUtf16Free(pwszMsg);
167 }
168 else
169 MessageBoxA(GetDesktopWindow(), pszMsg, VBOX_STUB_TITLE, MB_ICONERROR);
170 }
171 RTStrFree(pszMsg);
172 }
173 else /* Should never happen! */
174 AssertMsgFailed(("Failed to format error text of format string: %s!\n", pszFmt));
175 va_end(va);
176 return RTEXITCODE_FAILURE;
177}
178
179
180/**
181 * Same as ShowError, only it returns RTEXITCODE_SYNTAX.
182 */
183static RTEXITCODE ShowSyntaxError(const char *pszFmt, ...)
184{
185 va_list va;
186 va_start(va, pszFmt);
187 ShowError("%N", pszFmt, &va);
188 va_end(va);
189 return RTEXITCODE_SYNTAX;
190}
191
192
193/**
194 * Shows a message box with a printf() style formatted string.
195 *
196 * @param uType Type of the message box (see MSDN).
197 * @param pszFmt Printf-style format string to show in the message box body.
198 *
199 */
200static void ShowInfo(const char *pszFmt, ...)
201{
202 char *pszMsg;
203 va_list va;
204 va_start(va, pszFmt);
205 int rc = RTStrAPrintfV(&pszMsg, pszFmt, va);
206 va_end(va);
207 if (rc >= 0)
208 {
209 if (g_fSilent)
210 RTPrintf("%s\n", pszMsg);
211 else
212 {
213 PRTUTF16 pwszMsg;
214 rc = RTStrToUtf16(pszMsg, &pwszMsg);
215 if (RT_SUCCESS(rc))
216 {
217 MessageBoxW(GetDesktopWindow(), pwszMsg, MY_UNICODE(VBOX_STUB_TITLE), MB_ICONINFORMATION);
218 RTUtf16Free(pwszMsg);
219 }
220 else
221 MessageBoxA(GetDesktopWindow(), pszMsg, VBOX_STUB_TITLE, MB_ICONINFORMATION);
222 }
223 }
224 else /* Should never happen! */
225 AssertMsgFailed(("Failed to format error text of format string: %s!\n", pszFmt));
226 RTStrFree(pszMsg);
227}
228
229
230/** Logs error details to stderr. */
231static void LogError(const char *pszFmt, ...)
232{
233 va_list va;
234 va_start(va, pszFmt);
235 RTStrmPrintf(g_pStdErr, "error: %N\n", pszFmt, &va);
236 va_end(va);
237}
238
239
240/** Logs error details to stderr, returning @a rc. */
241static int LogErrorRc(int rc, const char *pszFmt, ...)
242{
243 va_list va;
244 va_start(va, pszFmt);
245 RTStrmPrintf(g_pStdErr, "error: %N\n", pszFmt, &va);
246 va_end(va);
247 return rc;
248}
249
250
251/** Logs error details to stderr, RTEXITCODE_FAILURE. */
252static RTEXITCODE LogErrorExitFailure(const char *pszFmt, ...)
253{
254 va_list va;
255 va_start(va, pszFmt);
256 RTStrmPrintf(g_pStdErr, "error: %N\n", pszFmt, &va);
257 va_end(va);
258 return RTEXITCODE_FAILURE;
259}
260
261
262/**
263 * Finds the specified in the resource section of the executable.
264 *
265 * @returns IPRT status code.
266 *
267 * @param pszDataName Name of resource to read.
268 * @param ppbResource Where to return the pointer to the data.
269 * @param pcbResource Where to return the size of the data (if found).
270 * Optional.
271 */
272static int FindData(const char *pszDataName, uint8_t const **ppbResource, DWORD *pcbResource)
273{
274 AssertReturn(pszDataName, VERR_INVALID_PARAMETER);
275 HINSTANCE hInst = NULL; /* indicates the executable image */
276
277 /* Find our resource. */
278 PRTUTF16 pwszDataName;
279 int rc = RTStrToUtf16(pszDataName, &pwszDataName);
280 AssertRCReturn(rc, rc);
281 HRSRC hRsrc = FindResourceExW(hInst,
282 (LPWSTR)RT_RCDATA,
283 pwszDataName,
284 MAKELANGID(LANG_NEUTRAL, SUBLANG_NEUTRAL));
285 RTUtf16Free(pwszDataName);
286 AssertReturn(hRsrc, VERR_IO_GEN_FAILURE);
287
288 /* Get resource size. */
289 DWORD cb = SizeofResource(hInst, hRsrc);
290 AssertReturn(cb > 0, VERR_NO_DATA);
291 if (pcbResource)
292 *pcbResource = cb;
293
294 /* Get pointer to resource. */
295 HGLOBAL hData = LoadResource(hInst, hRsrc);
296 AssertReturn(hData, VERR_IO_GEN_FAILURE);
297
298 /* Lock resource. */
299 *ppbResource = (uint8_t const *)LockResource(hData);
300 AssertReturn(*ppbResource, VERR_IO_GEN_FAILURE);
301 return VINF_SUCCESS;
302}
303
304
305/**
306 * Finds the header for the given package.
307 *
308 * @returns Pointer to the package header on success. On failure NULL is
309 * returned after ShowError has been invoked.
310 * @param iPackage The package number.
311 */
312static const VBOXSTUBPKG *FindPackageHeader(unsigned iPackage)
313{
314 char szHeaderName[32];
315 RTStrPrintf(szHeaderName, sizeof(szHeaderName), "HDR_%02d", iPackage);
316
317 VBOXSTUBPKG const *pPackage;
318 int rc = FindData(szHeaderName, (uint8_t const **)&pPackage, NULL);
319 if (RT_FAILURE(rc))
320 {
321 ShowError("Internal error: Could not find package header #%u: %Rrc", iPackage, rc);
322 return NULL;
323 }
324
325 /** @todo validate it. */
326 return pPackage;
327}
328
329
330
331/**
332 * Constructs a full temporary file path from the given parameters.
333 *
334 * @returns iprt status code.
335 *
336 * @param pszTempPath The pure path to use for construction.
337 * @param pszTargetFileName The pure file name to use for construction.
338 * @param ppszTempFile Pointer to the constructed string. Must be freed
339 * using RTStrFree().
340 */
341static int GetTempFileAlloc(const char *pszTempPath,
342 const char *pszTargetFileName,
343 char **ppszTempFile)
344{
345 if (RTStrAPrintf(ppszTempFile, "%s\\%s", pszTempPath, pszTargetFileName) >= 0)
346 return VINF_SUCCESS;
347 return VERR_NO_STR_MEMORY;
348}
349
350
351/**
352 * Extracts a built-in resource to disk.
353 *
354 * @returns iprt status code.
355 *
356 * @param pszResourceName The resource name to extract.
357 * @param pszTempFile The full file path + name to extract the resource to.
358 * @param hFile Handle to pszTempFile if a custom file handle was used to
359 * open / create the file, otherwise NIL_RTFILE.
360 * @param idxPackage The package index for annotating the cleanup
361 * record with (HACK ALERT).
362 */
363static int ExtractFile(const char *pszResourceName, const char *pszTempFile, RTFILE hFile, unsigned idxPackage)
364{
365 AssertPtrReturn(pszResourceName, VERR_INVALID_POINTER);
366 AssertPtrReturn(pszTempFile, VERR_INVALID_POINTER);
367
368 /* Create new (and replace any old) file. */
369 if (hFile == NIL_RTFILE)
370 {
371 int rc = RTFileOpen(&hFile, pszTempFile,
372 RTFILE_O_CREATE_REPLACE | RTFILE_O_WRITE | RTFILE_O_DENY_WRITE
373 | (0700 << RTFILE_O_CREATE_MODE_SHIFT));
374 AssertRCReturn(rc, LogErrorRc(rc, "#%u: Failed to create/replace '%s' for writing: %Rrc", idxPackage, pszTempFile, rc));
375 }
376
377 /* Add a cleanup record, so that we can properly clean up (partially run) stuff. */
378 int rc = VERR_NO_MEMORY;
379 PSTUBCLEANUPREC pCleanupRec = AddCleanupRec(pszTempFile, true /*fIsFile*/);
380 AssertReturn(pCleanupRec, VERR_NO_MEMORY);
381
382 pCleanupRec->idxPkg = idxPackage;
383 pCleanupRec->hFile = hFile;
384
385 /* Find the data of the built-in resource. */
386 uint8_t const *pbData = NULL;
387 DWORD cbData = 0;
388 rc = FindData(pszResourceName, &pbData, &cbData);
389 AssertRCReturn(rc, LogErrorRc(rc, "#%u: Failed to locate resource '%s': %Rrc", idxPackage, pszResourceName, rc));
390
391 /* Write the contents to the file. */
392 rc = RTFileWrite(hFile, pbData, cbData, NULL);
393 AssertRCReturn(rc, LogErrorRc(rc, "#%u: RTFileWrite('%s',, %#x,) failed: %Rrc", idxPackage, pszTempFile, cbData, rc));
394
395 /*
396 * We now wish to keep the file open, however since we've got it open in write
397 * mode with deny-write sharing (effectively exclusive write mode) this will
398 * prevent the MSI API from opening it in deny-write mode for reading purposes.
399 *
400 * So we have to do the best we can to transition this to a read-only handle
401 * that denies write (and deletion/renaming). First we open it again in
402 * read-only mode only denying deletion, not writing. Then close the original
403 * handle. Finally open a read-only handle that denies both reading and
404 * deletion/renaming, and verify that the file content is still the same.
405 *
406 * Note! DuplicateHandle to read-only and closing the original does not work,
407 * as the kernel doesn't update the sharing access info for the handles.
408 */
409 RTFSOBJINFO ObjInfo1;
410 rc = RTFileQueryInfo(hFile, &ObjInfo1, RTFSOBJATTRADD_UNIX);
411 AssertRCReturn(rc, LogErrorRc(rc, "#%u: RTFileQueryInfo failed on '%s': %Rrc", idxPackage, pszTempFile, rc));
412
413 RTFILE hFile2 = NIL_RTFILE;
414 rc = RTFileOpen(&hFile2, pszTempFile,
415 RTFILE_O_OPEN | RTFILE_O_READ | RTFILE_O_DENY_NONE | (0700 << RTFILE_O_CREATE_MODE_SHIFT));
416 AssertRCReturn(rc, LogErrorRc(rc, "#%u: First re-opening of '%s' failed: %Rrc", idxPackage, pszTempFile, rc));
417
418 rc = RTFileClose(hFile);
419 AssertRCReturnStmt(rc, RTFileClose(hFile2),
420 LogErrorRc(rc, "#%u: RTFileClose('%s') failed: %Rrc", idxPackage, pszTempFile, rc));
421 pCleanupRec->hFile = hFile2;
422
423 rc = RTFileOpen(&hFile, pszTempFile, RTFILE_O_OPEN | RTFILE_O_READ | RTFILE_O_DENY_WRITE);
424 AssertRCReturn(rc, LogErrorRc(rc, "#%u: Second re-opening of '%s' failed: %Rrc", idxPackage, pszTempFile, rc));
425 pCleanupRec->hFile = hFile;
426
427 rc = RTFileClose(hFile2);
428 AssertRCStmt(rc, LogError("#%u: Failed to close 2nd handle to '%s': %Rrc", idxPackage, pszTempFile, rc));
429
430 /* check the size and inode number. */
431 RTFSOBJINFO ObjInfo2;
432 rc = RTFileQueryInfo(hFile, &ObjInfo2, RTFSOBJATTRADD_UNIX);
433 AssertRCReturn(rc, LogErrorRc(rc, "#%u: RTFileQueryInfo failed on '%s': %Rrc", idxPackage, pszTempFile, rc));
434
435 AssertReturn(ObjInfo2.cbObject == cbData,
436 LogErrorRc(VERR_STATE_CHANGED, "#%u: File size of '%s' changed: %'RU64, expected %'RU32",
437 idxPackage, pszTempFile, ObjInfo2.cbObject, pbData));
438
439 AssertReturn(ObjInfo2.Attr.u.Unix.INodeId == ObjInfo1.Attr.u.Unix.INodeId,
440 LogErrorRc(VERR_STATE_CHANGED, "#%u: File ID of '%s' changed: %#RX64, expected %#RX64",
441 idxPackage, pszTempFile, ObjInfo2.Attr.u.Unix.INodeId, ObjInfo1.Attr.u.Unix.INodeId));
442
443
444 /* Check the content. */
445 size_t off = 0;
446 while (off < cbData)
447 {
448 uint8_t abBuf[_64K];
449 size_t cbToRead = RT_MIN(cbData - off, sizeof(abBuf));
450 rc = RTFileRead(hFile, abBuf, cbToRead, NULL);
451 AssertRCReturn(rc, LogErrorRc(rc, "#%u: RTFileRead failed on '%s' at offset %#zx: %Rrc",
452 idxPackage, pszTempFile, off, rc));
453 AssertReturn(memcmp(abBuf, &pbData[off], cbToRead) == 0,
454 LogErrorRc(VERR_STATE_CHANGED, "#%u: File '%s' has change (mismatch in %#zx byte block at %#zx)",
455 idxPackage, pszTempFile, cbToRead, off));
456 off += cbToRead;
457 }
458
459 return VINF_SUCCESS;
460}
461
462
463/**
464 * Extracts a built-in resource to disk.
465 *
466 * @returns iprt status code.
467 *
468 * @param pPackage Pointer to a VBOXSTUBPKG struct that contains the resource.
469 * @param pszTempFile The full file path + name to extract the resource to.
470 * @param hFile Handle to pszTempFile if a custom file handle was used to
471 * open / create the file, otherwise NIL_RTFILE.
472 * @param idxPackage The package index for annotating the cleanup
473 * record with (HACK ALERT).
474 */
475static int Extract(VBOXSTUBPKG const *pPackage, const char *pszTempFile, RTFILE hFile, unsigned idxPackage)
476{
477 return ExtractFile(pPackage->szResourceName, pszTempFile, hFile, idxPackage);
478}
479
480/**
481 * Returns a string for the given package architecture.
482 *
483 * @returns String for the given package architecture, or "<Unknown>" if invalid/unknown.
484 * @param enmPkgArch Package architecture to return as a string.
485 */
486static const char *GetPackageArchStr(VBOXSTUBPKGARCH enmPkgArch)
487{
488 switch (enmPkgArch)
489 {
490 case VBOXSTUBPKGARCH_X86: return "x86";
491 case VBOXSTUBPKGARCH_AMD64: return "x86_64";
492 case VBOXSTUBPKGARCH_ARM64: return "arm64";
493 default: break;
494 }
495
496 return "<Unknown>";
497}
498
499/**
500 * Detects whether we're running on a 32- or 64-bit platform and returns the result.
501 *
502 * @returns Returns the native package architecture.
503 */
504static VBOXSTUBPKGARCH GetNativePackageArch(void)
505{
506 HMODULE const hModKernel32 = GetModuleHandleW(L"kernel32.dll");
507 PFNISWOW64PROCESS2 pfnIsWow64Process2 = (PFNISWOW64PROCESS2)GetProcAddress(hModKernel32, "IsWow64Process2");
508 if (pfnIsWow64Process2)
509 {
510 USHORT usWowMachine = IMAGE_FILE_MACHINE_UNKNOWN;
511 USHORT usHostMachine = IMAGE_FILE_MACHINE_UNKNOWN;
512 if (pfnIsWow64Process2(GetCurrentProcess(), &usWowMachine, &usHostMachine))
513 {
514 if (usHostMachine == IMAGE_FILE_MACHINE_AMD64)
515 return VBOXSTUBPKGARCH_AMD64;
516 if (usHostMachine == IMAGE_FILE_MACHINE_ARM64)
517 return VBOXSTUBPKGARCH_ARM64;
518 if (usHostMachine == IMAGE_FILE_MACHINE_I386)
519 return VBOXSTUBPKGARCH_X86;
520 LogError("IsWow64Process2 return unknown host machine value: %#x (wow machine %#x)", usHostMachine, usWowMachine);
521 }
522 else
523 LogError("IsWow64Process2 failed: %u", GetLastError());
524 }
525 else
526 {
527 PFNISWOW64PROCESS pfnIsWow64Process = (PFNISWOW64PROCESS)GetProcAddress(hModKernel32, "IsWow64Process");
528 if (pfnIsWow64Process)
529 {
530 BOOL fIsWow64 = TRUE;
531 if (pfnIsWow64Process(GetCurrentProcess(), &fIsWow64))
532 {
533 if (fIsWow64)
534 return VBOXSTUBPKGARCH_AMD64;
535 }
536 else
537 LogError("IsWow64Process failed: %u\n", GetLastError());
538 }
539 else
540 LogError("Neither IsWow64Process nor IsWow64Process2 was found!");
541 }
542
543#ifdef RT_ARCH_X86
544 return VBOXSTUBPKGARCH_X86;
545#elif defined(RT_ARCH_AMD64)
546 return VBOXSTUBPKGARCH_AMD64;
547#elif defined(RT_ARCH_ARM64)
548 return VBOXSTUBPKGARCH_ARM64;
549#else
550# error "port me"
551#endif
552}
553
554
555/**
556 * Decides whether we need a specified package to handle or not.
557 *
558 * @returns @c true if we need to handle the specified package, @c false if not.
559 *
560 * @param pPackage Pointer to a VBOXSTUBPKG struct that contains the resource.
561 */
562static bool PackageIsNeeded(VBOXSTUBPKG const *pPackage)
563{
564 if (pPackage->enmArch == VBOXSTUBPKGARCH_ALL)
565 return true;
566 VBOXSTUBPKGARCH enmArch = GetNativePackageArch();
567 return pPackage->enmArch == enmArch;
568}
569
570
571/**
572 * Adds a cleanup record.
573 *
574 * The caller must set the hFile or hDir if so desired.
575 *
576 * @returns Pointer to the cleanup record on success, fully complained NULL on
577 * failure.
578 * @param pszPath The path to the file or directory to clean up.
579 * @param fIsFile @c true if file, @c false if directory.
580 */
581static PSTUBCLEANUPREC AddCleanupRec(const char *pszPath, bool fIsFile)
582{
583 size_t cchPath = strlen(pszPath); Assert(cchPath > 0);
584 PSTUBCLEANUPREC pRec = (PSTUBCLEANUPREC)RTMemAllocZ(RT_UOFFSETOF_DYN(STUBCLEANUPREC, szPath[cchPath + 1]));
585 if (pRec)
586 {
587 pRec->idxPkg = ~0U;
588 pRec->fFile = fIsFile;
589 if (fIsFile)
590 pRec->hFile = NIL_RTFILE;
591 else
592 pRec->hDir = NIL_RTDIR;
593 memcpy(pRec->szPath, pszPath, cchPath + 1);
594
595 RTListPrepend(&g_TmpFiles, &pRec->ListEntry);
596 }
597 else
598 ShowError("Out of memory!");
599 return pRec;
600}
601
602
603/**
604 * Cleans up all the extracted files and optionally removes the package
605 * directory.
606 *
607 * @param pszPkgDir The package directory, NULL if it shouldn't be
608 * removed.
609 */
610static void CleanUp(const char *pszPkgDir)
611{
612 for (int i = 0; i < 5; i++)
613 {
614 bool const fFinalTry = i == 4;
615
616 PSTUBCLEANUPREC pCur, pNext;
617 RTListForEachSafe(&g_TmpFiles, pCur, pNext, STUBCLEANUPREC, ListEntry)
618 {
619 int rc = VINF_SUCCESS;
620 if (pCur->fFile)
621 {
622 if (pCur->hFile != NIL_RTFILE)
623 {
624 if (RTFileIsValid(pCur->hFile))
625 {
626 int rcCloseFile = RTFileClose(pCur->hFile);
627 AssertRCStmt(rcCloseFile, LogError("Cleanup file '%s' for #%u: RTFileClose(%p) failed: %Rrc",
628 pCur->szPath, pCur->idxPkg, pCur->hFile, rcCloseFile));
629 }
630 pCur->hFile = NIL_RTFILE;
631 }
632 if (!pCur->fDontDelete)
633 rc = RTFileDelete(pCur->szPath);
634 }
635 else /* Directory */
636 {
637 if (pCur->hDir != NIL_RTDIR)
638 {
639 if (RTDirIsValid(pCur->hDir))
640 {
641 int rcCloseDir = RTDirClose(pCur->hDir);
642 AssertRCStmt(rcCloseDir, LogError("Cleanup dir '%s' for #%u: RTDirClose(%p) failed: %Rrc",
643 pCur->szPath, pCur->idxPkg, pCur->hDir, rcCloseDir));
644 }
645 pCur->hDir = NIL_RTDIR;
646 }
647
648 /* Note: Not removing the directory recursively, as we should have separate cleanup records for that. */
649 if (!pCur->fDontDelete)
650 {
651 rc = RTDirRemove(pCur->szPath);
652 if (rc == VERR_DIR_NOT_EMPTY && fFinalTry)
653 rc = VINF_SUCCESS;
654 }
655 }
656 if (rc == VERR_FILE_NOT_FOUND || rc == VERR_PATH_NOT_FOUND)
657 rc = VINF_SUCCESS;
658 if (RT_SUCCESS(rc))
659 {
660 RTListNodeRemove(&pCur->ListEntry);
661 RTMemFree(pCur);
662 }
663 else if (fFinalTry)
664 {
665 if (pCur->fFile)
666 ShowError("Failed to delete temporary file '%s': %Rrc", pCur->szPath, rc);
667 else
668 ShowError("Failed to delete temporary directory '%s': %Rrc", pCur->szPath, rc);
669 }
670 }
671
672 if (RTListIsEmpty(&g_TmpFiles) || fFinalTry)
673 {
674 if (!pszPkgDir)
675 return;
676 int rc = RTDirRemove(pszPkgDir);
677 if (RT_SUCCESS(rc) || rc == VERR_FILE_NOT_FOUND || rc == VERR_PATH_NOT_FOUND || fFinalTry)
678 return;
679 }
680
681 /* Delay a little and try again. */
682 RTThreadSleep(i == 0 ? 100 : 3000);
683 }
684}
685
686
687/**
688 * Processes an MSI package.
689 *
690 * @returns Fully complained exit code.
691 * @param pszMsi The path to the MSI to process.
692 * @param pszMsiArgs Any additional installer (MSI) argument
693 * @param pszMsiLogFile Where to let MSI log its output to. NULL if logging is disabled.
694 */
695static RTEXITCODE ProcessMsiPackage(const char *pszMsi, const char *pszMsiArgs, const char *pszMsiLogFile)
696{
697 int rc;
698
699 /*
700 * Set UI level.
701 */
702 INSTALLUILEVEL enmDesiredUiLevel = g_fSilent ? INSTALLUILEVEL_NONE : INSTALLUILEVEL_FULL;
703 INSTALLUILEVEL enmRet = MsiSetInternalUI(enmDesiredUiLevel, NULL);
704 if (enmRet == INSTALLUILEVEL_NOCHANGE /* means error */)
705 return ShowError("Internal error: MsiSetInternalUI failed.");
706
707 /*
708 * Enable logging?
709 */
710 if (pszMsiLogFile)
711 {
712 PRTUTF16 pwszLogFile;
713 rc = RTStrToUtf16(pszMsiLogFile, &pwszLogFile);
714 if (RT_FAILURE(rc))
715 return ShowError("RTStrToUtf16 failed on '%s': %Rrc", pszMsiLogFile, rc);
716
717 UINT uLogLevel = MsiEnableLogW(INSTALLLOGMODE_VERBOSE,
718 pwszLogFile,
719 INSTALLLOGATTRIBUTES_FLUSHEACHLINE);
720 RTUtf16Free(pwszLogFile);
721 if (uLogLevel != ERROR_SUCCESS)
722 return ShowError("MsiEnableLogW failed");
723 }
724
725 /*
726 * Initialize the common controls (extended version). This is necessary to
727 * run the actual .MSI installers with the new fancy visual control
728 * styles (XP+). Also, an integrated manifest is required.
729 */
730 INITCOMMONCONTROLSEX ccEx;
731 ccEx.dwSize = sizeof(INITCOMMONCONTROLSEX);
732 ccEx.dwICC = ICC_LINK_CLASS | ICC_LISTVIEW_CLASSES | ICC_PAGESCROLLER_CLASS |
733 ICC_PROGRESS_CLASS | ICC_STANDARD_CLASSES | ICC_TAB_CLASSES | ICC_TREEVIEW_CLASSES |
734 ICC_UPDOWN_CLASS | ICC_USEREX_CLASSES | ICC_WIN95_CLASSES;
735 InitCommonControlsEx(&ccEx); /* Ignore failure. */
736
737 /*
738 * Convert both strings to UTF-16 and start the installation.
739 */
740 PRTUTF16 pwszMsi;
741 rc = RTStrToUtf16(pszMsi, &pwszMsi);
742 if (RT_FAILURE(rc))
743 return ShowError("RTStrToUtf16 failed on '%s': %Rrc", pszMsi, rc);
744 PRTUTF16 pwszMsiArgs;
745 rc = RTStrToUtf16(pszMsiArgs, &pwszMsiArgs);
746 if (RT_FAILURE(rc))
747 {
748 RTUtf16Free(pwszMsi);
749 return ShowError("RTStrToUtf16 failed on '%s': %Rrc", pszMsiArgs, rc);
750 }
751
752 UINT uStatus = MsiInstallProductW(pwszMsi, pwszMsiArgs);
753 RTUtf16Free(pwszMsi);
754 RTUtf16Free(pwszMsiArgs);
755
756 if (uStatus == ERROR_SUCCESS)
757 return RTEXITCODE_SUCCESS;
758 if (uStatus == ERROR_SUCCESS_REBOOT_REQUIRED)
759 {
760 if (g_fSilent)
761 RTMsgInfo("Reboot required (by %s)\n", pszMsi);
762 return (RTEXITCODE)uStatus;
763 }
764
765 /*
766 * Installation failed. Figure out what to say.
767 */
768 switch (uStatus)
769 {
770 case ERROR_INSTALL_USEREXIT:
771 /* Don't say anything? */
772 break;
773
774 case ERROR_INSTALL_PACKAGE_VERSION:
775 ShowError("This installation package cannot be installed by the Windows Installer service.\n"
776 "You must install a Windows service pack that contains a newer version of the Windows Installer service.");
777 break;
778
779 case ERROR_INSTALL_PLATFORM_UNSUPPORTED:
780 ShowError("This installation package is not supported on this platform.");
781 break;
782
783 default:
784 {
785 /*
786 * Try get windows to format the message.
787 */
788 DWORD dwFormatFlags = FORMAT_MESSAGE_ALLOCATE_BUFFER
789 | FORMAT_MESSAGE_IGNORE_INSERTS
790 | FORMAT_MESSAGE_FROM_SYSTEM;
791 HMODULE hModule = NULL;
792 if (uStatus >= NERR_BASE && uStatus <= MAX_NERR)
793 {
794 hModule = LoadLibraryExW(L"netmsg.dll",
795 NULL,
796 LOAD_LIBRARY_AS_DATAFILE);
797 if (hModule != NULL)
798 dwFormatFlags |= FORMAT_MESSAGE_FROM_HMODULE;
799 }
800
801 PWSTR pwszMsg;
802 if (FormatMessageW(dwFormatFlags,
803 hModule, /* If NULL, load system stuff. */
804 uStatus,
805 MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
806 (PWSTR)&pwszMsg,
807 0,
808 NULL) > 0)
809 {
810 ShowError("Installation failed! Error: %ls", pwszMsg);
811 LocalFree(pwszMsg);
812 }
813 else /* If text lookup failed, show at least the error number. */
814 ShowError("Installation failed! Error: %u", uStatus);
815
816 if (hModule)
817 FreeLibrary(hModule);
818 break;
819 }
820 }
821
822 return RTEXITCODE_FAILURE;
823}
824
825
826/**
827 * Processes a package.
828 *
829 * @returns Fully complained exit code.
830 * @param iPackage The package number.
831 * @param pszMsiArgs Any additional installer (MSI) argument
832 * @param pszMsiLogFile Where to let MSI log its output to. NULL if logging is disabled.
833 */
834static RTEXITCODE ProcessPackage(unsigned iPackage, const char *pszMsiArgs, const char *pszMsiLogFile)
835{
836 /*
837 * Get the package header and check if it's needed.
838 */
839 VBOXSTUBPKG const * const pPackage = FindPackageHeader(iPackage);
840 if (pPackage == NULL)
841 return RTEXITCODE_FAILURE;
842
843 if (!PackageIsNeeded(pPackage))
844 return RTEXITCODE_SUCCESS;
845
846 /*
847 * Get the cleanup record for the package so we can get the extracted
848 * filename (pPackage is read-only and thus cannot assist here).
849 */
850 PSTUBCLEANUPREC pRec = NULL;
851 PSTUBCLEANUPREC pCur;
852 RTListForEach(&g_TmpFiles, pCur, STUBCLEANUPREC, ListEntry)
853 {
854 if (pCur->idxPkg == iPackage)
855 {
856 pRec = pCur;
857 break;
858 }
859 }
860 AssertReturn(pRec != NULL, LogErrorExitFailure("Package #%u not found in cleanup records", iPackage));
861
862 /*
863 * Deal with the file based on it's extension.
864 */
865 RTPathChangeToDosSlashes(pRec->szPath, true /* Force conversion. */); /* paranoia */
866
867 RTEXITCODE rcExit;
868 const char *pszSuff = RTPathSuffix(pRec->szPath);
869 if (RTStrICmpAscii(pszSuff, ".msi") == 0)
870 rcExit = ProcessMsiPackage(pRec->szPath, pszMsiArgs, pszMsiLogFile);
871 else if (RTStrICmpAscii(pszSuff, ".cab") == 0)
872 rcExit = RTEXITCODE_SUCCESS; /* Ignore .cab files, they're generally referenced by other files. */
873 else
874 rcExit = ShowError("Internal error: Do not know how to handle file '%s' (%s).", pPackage->szFilename, pRec->szPath);
875 return rcExit;
876}
877
878#ifdef VBOX_WITH_CODE_SIGNING
879
880# ifdef VBOX_WITH_VBOX_LEGACY_TS_CA
881/**
882 * Install the timestamp CA currently needed to support legacy Windows versions.
883 *
884 * See @bugref{8691} for details.
885 *
886 * @returns Fully complained exit code.
887 */
888static RTEXITCODE InstallTimestampCA(bool fForce)
889{
890 /*
891 * Windows 10 desktop should be fine with attestation signed drivers, however
892 * the driver guard (DG) may alter that. Not sure yet how to detect, but
893 * OTOH 1809 and later won't accept the SHA-1 stuff regardless, so out of
894 * options there.
895 *
896 * The Windows 2016 server and later is not fine with attestation signed
897 * drivers, so we need to do the legacy trick there.
898 */
899 if ( !fForce
900 && RTSystemGetNtVersion() >= RTSYSTEM_MAKE_NT_VERSION(10, 0, 0)
901 && RTSystemGetNtProductType() == VER_NT_WORKSTATION)
902 return RTEXITCODE_SUCCESS;
903
904 if (!addCertToStore(CERT_SYSTEM_STORE_LOCAL_MACHINE, "Root", g_abVBoxLegacyWinCA, sizeof(g_abVBoxLegacyWinCA)))
905 return ShowError("Failed add the legacy Windows timestamp CA to the root certificate store.");
906 return RTEXITCODE_SUCCESS;
907}
908# endif /* VBOX_WITH_VBOX_LEGACY_TS_CA*/
909
910/**
911 * Install the public certificate into TrustedPublishers so the installer won't
912 * prompt the user during silent installs.
913 *
914 * @returns Fully complained exit code.
915 */
916static RTEXITCODE InstallCertificates(void)
917{
918 for (uint32_t i = 0; i < RT_ELEMENTS(g_aVBoxStubTrustedCerts); i++)
919 {
920 if (!addCertToStore(CERT_SYSTEM_STORE_LOCAL_MACHINE,
921 "TrustedPublisher",
922 g_aVBoxStubTrustedCerts[i].pab,
923 g_aVBoxStubTrustedCerts[i].cb))
924 return ShowError("Failed to add our certificate(s) to trusted publisher store.");
925 }
926 return RTEXITCODE_SUCCESS;
927}
928
929#endif /* VBOX_WITH_CODE_SIGNING */
930
931/**
932 * Copies the "<exepath>.custom" directory to the extraction path if it exists.
933 *
934 * This is used by the MSI packages from the resource section.
935 *
936 * @returns Fully complained exit code.
937 * @param pszDstDir The destination directory.
938 */
939static RTEXITCODE CopyCustomDir(const char *pszDstDir)
940{
941 char szSrcDir[RTPATH_MAX];
942 int rc = RTPathExecDir(szSrcDir, sizeof(szSrcDir));
943 if (RT_SUCCESS(rc))
944 rc = RTPathAppend(szSrcDir, sizeof(szSrcDir), ".custom");
945 if (RT_FAILURE(rc))
946 return ShowError("Failed to construct '.custom' dir path: %Rrc", rc);
947
948 if (RTDirExists(szSrcDir))
949 {
950 /*
951 * Use SHFileOperation w/ FO_COPY to do the job. This API requires an
952 * extra zero at the end of both source and destination paths.
953 */
954 size_t cwc;
955 RTUTF16 wszSrcDir[RTPATH_MAX + 1];
956 PRTUTF16 pwszSrcDir = wszSrcDir;
957 rc = RTStrToUtf16Ex(szSrcDir, RTSTR_MAX, &pwszSrcDir, RTPATH_MAX, &cwc);
958 if (RT_FAILURE(rc))
959 return ShowError("RTStrToUtf16Ex failed on '%s': %Rrc", szSrcDir, rc);
960 wszSrcDir[cwc] = '\0';
961
962 RTUTF16 wszDstDir[RTPATH_MAX + 1];
963 PRTUTF16 pwszDstDir = wszSrcDir;
964 rc = RTStrToUtf16Ex(pszDstDir, RTSTR_MAX, &pwszDstDir, RTPATH_MAX, &cwc);
965 if (RT_FAILURE(rc))
966 return ShowError("RTStrToUtf16Ex failed on '%s': %Rrc", pszDstDir, rc);
967 wszDstDir[cwc] = '\0';
968
969 SHFILEOPSTRUCTW FileOp;
970 RT_ZERO(FileOp); /* paranoia */
971 FileOp.hwnd = NULL;
972 FileOp.wFunc = FO_COPY;
973 FileOp.pFrom = wszSrcDir;
974 FileOp.pTo = wszDstDir;
975 FileOp.fFlags = FOF_SILENT
976 | FOF_NOCONFIRMATION
977 | FOF_NOCONFIRMMKDIR
978 | FOF_NOERRORUI;
979 FileOp.fAnyOperationsAborted = FALSE;
980 FileOp.hNameMappings = NULL;
981 FileOp.lpszProgressTitle = NULL;
982
983 rc = SHFileOperationW(&FileOp);
984 if (rc != 0) /* Not a Win32 status code! */
985 return ShowError("Copying the '.custom' dir failed: %#x", rc);
986
987 /*
988 * Add a cleanup record for recursively deleting the destination
989 * .custom directory. We should actually add this prior to calling
990 * SHFileOperationW since it may partially succeed...
991 */
992 char *pszDstSubDir = RTPathJoinA(pszDstDir, ".custom");
993 if (!pszDstSubDir)
994 return ShowError("Out of memory!");
995
996 PSTUBCLEANUPREC pCleanupRec = AddCleanupRec(pszDstSubDir, false /*fIsFile*/);
997 AssertReturn(pCleanupRec, RTEXITCODE_FAILURE);
998
999 /*
1000 * Open the directory to make it difficult to replace or delete (see @bugref{10201}).
1001 */
1002 /** @todo this is still race prone, given that SHFileOperationW is the one
1003 * creating it and we're really a bit late opening it here. Anyway,
1004 * it's harmless as this code isn't used at present. */
1005 RTDIR hDstSubDir;
1006 rc = RTDirOpen(&hDstSubDir, pszDstSubDir);
1007 if (RT_FAILURE(rc))
1008 return ShowError("Unable to open the destination .custom directory: %Rrc", rc);
1009 pCleanupRec->hDir = hDstSubDir;
1010
1011 RTStrFree(pszDstSubDir);
1012 }
1013
1014 return RTEXITCODE_SUCCESS;
1015}
1016
1017
1018/**
1019 * Extracts the files for all needed packages to @a pszDstDir.
1020 *
1021 * @returns
1022 * @param cPackages Number of packages to consinder.
1023 * @param pszDstDir Where to extract the files.
1024 * @param fExtractOnly Set if only extracting and not doing any installing.
1025 * @param ppExtractDirRec Where we keep the cleanup record for @a pszDstDir.
1026 * This may have been created by the caller already.
1027 */
1028static RTEXITCODE ExtractFiles(unsigned cPackages, const char *pszDstDir, bool fExtractOnly, PSTUBCLEANUPREC *ppExtractDirRec)
1029{
1030 int rc;
1031
1032 /*
1033 * Make sure the directory exists (normally WinMain created it for us).
1034 */
1035 PSTUBCLEANUPREC pCleanupRec = *ppExtractDirRec;
1036 if (!RTDirExists(pszDstDir))
1037 {
1038 AssertReturn(!pCleanupRec, ShowError("RTDirExists failed on '%s' which we just created!", pszDstDir));
1039
1040 rc = RTDirCreate(pszDstDir, 0700, 0);
1041 if (RT_FAILURE(rc))
1042 return ShowError("Failed to create extraction path '%s': %Rrc", pszDstDir, rc);
1043
1044 *ppExtractDirRec = pCleanupRec = AddCleanupRec(pszDstDir, false /*fFile*/);
1045 AssertReturn(pCleanupRec, LogErrorExitFailure("Failed to add cleanup record for dir '%s'", pszDstDir));
1046 }
1047 /*
1048 * If we need to create the cleanup record, the caller did not create the
1049 * directory so we should not delete it when done.
1050 */
1051 else if (!pCleanupRec)
1052 {
1053 *ppExtractDirRec = pCleanupRec = AddCleanupRec(pszDstDir, false /*fFile*/);
1054 AssertReturn(pCleanupRec, LogErrorExitFailure("Failed to add cleanup record for existing dir '%s'", pszDstDir));
1055 pCleanupRec->fDontDelete = true;
1056 }
1057
1058 /*
1059 * Open up the directory to make it difficult to delete / replace.
1060 */
1061 rc = RTDirOpen(&pCleanupRec->hDir, pszDstDir);
1062 if (RT_FAILURE(rc))
1063 return ShowError("Failed to open extraction path '%s': %Rrc", pszDstDir, rc);
1064
1065 /*
1066 * Change current directory to the extraction directory for the same reason
1067 * as we open it above.
1068 */
1069 RTPathSetCurrent(pszDstDir);
1070
1071 /*
1072 * Extract files.
1073 */
1074 for (unsigned k = 0; k < cPackages; k++)
1075 {
1076 VBOXSTUBPKG const * const pPackage = FindPackageHeader(k);
1077 if (!pPackage)
1078 return RTEXITCODE_FAILURE; /* Done complaining already. */
1079
1080 if (fExtractOnly || PackageIsNeeded(pPackage))
1081 {
1082 char szDstFile[RTPATH_MAX];
1083 rc = RTPathJoin(szDstFile, sizeof(szDstFile), pszDstDir, pPackage->szFilename);
1084 if (RT_FAILURE(rc))
1085 return ShowError("Internal error: Build extraction file name failed: %Rrc", rc);
1086
1087 rc = Extract(pPackage, szDstFile, NIL_RTFILE /* Custom file handle, not used (anymore) */, k);
1088 if (RT_FAILURE(rc))
1089 return ShowError("Error extracting package #%u (%s): %Rrc", k, pPackage->szFilename, rc);
1090 }
1091 }
1092
1093 return RTEXITCODE_SUCCESS;
1094}
1095
1096#ifdef VBOX_STUB_WITH_SPLASH
1097/**
1098 * Structure for keeping the splash screen context.
1099 */
1100typedef struct VBOXSPLASHSCREENCTX
1101{
1102 /** The instance the window is running in. */
1103 HINSTANCE hInst;
1104 /** The actual splash screen window. */
1105 HWND hWnd;
1106 /** The worker thread handle. */
1107 RTTHREAD hThread;
1108 /** Allocated image which is drawn as the splash screen.
1109 * Owned by the splash screen thread. */
1110 Gdiplus::Image *pImage;
1111 /** The current angle we draw the loading dot. */
1112 unsigned uDotAngle;
1113 /** Back buffer DC.
1114 * Also acts as a beacon for initializing / destroying the back buffer data. */
1115 HDC hDCBackBuffer;
1116 /** Back buffer bitmap. */
1117 HBITMAP hBmpBackBuffer;
1118} VBOXSPLASHSCREENCTX;
1119/** Pointer to a structure for keeping the splash screen context. */
1120typedef VBOXSPLASHSCREENCTX *PVBOXSPLASHSCREENCTX;
1121
1122/**
1123 * Paints the plash screen.
1124 *
1125 * @param pCtx Splash screen context to use.
1126 * @param hdc HDC to use for drawing.
1127 */
1128static void SplashScreenPaint(PVBOXSPLASHSCREENCTX pCtx, HDC hdc)
1129{
1130 unsigned const uWidth = pCtx->pImage->GetWidth();
1131 unsigned const uHeight = pCtx->pImage->GetHeight();
1132
1133 /* To prevent flickering we use a back buffer. Create it if not done yet. */
1134 if (!pCtx->hDCBackBuffer)
1135 {
1136 pCtx->hDCBackBuffer = CreateCompatibleDC(hdc);
1137 pCtx->hBmpBackBuffer = CreateCompatibleBitmap(hdc, uWidth, uHeight);
1138 SelectObject(pCtx->hDCBackBuffer, pCtx->hBmpBackBuffer);
1139 }
1140
1141 Gdiplus::Graphics graphics(pCtx->hDCBackBuffer);
1142
1143 graphics.Clear(Gdiplus::Color(255, 255, 255, 255)); /* White background. */
1144 graphics.DrawImage(pCtx->pImage, 0, 0, pCtx->pImage->GetWidth(), pCtx->pImage->GetHeight());
1145
1146 unsigned const uRadius = 10;
1147 unsigned const uCenterX = uWidth / 2;
1148 unsigned const uCenterY = uHeight / 2 + 80 /* Offset for drawing the dot below the VBox logo */;
1149
1150 /* Calculate the position of the spinning circle. */
1151 unsigned const x = uCenterX + static_cast<int>(uRadius * cos(pCtx->uDotAngle * 3.14159 /* PI */ / 180));
1152 unsigned const y = uCenterY + static_cast<int>(uRadius * sin(pCtx->uDotAngle * 3.14159 /* PI */ / 180));
1153
1154 Gdiplus::SolidBrush brush(Gdiplus::Color(255, 0, 0, 100)); /* Dark blue dot. */
1155 graphics.FillEllipse(&brush, x - 5, y - 5, 10, 10);
1156
1157 /* Blit back buffer DC to front DC. */
1158 BitBlt(hdc, 0, 0, uWidth, uHeight, pCtx->hDCBackBuffer, 0, 0, SRCCOPY);
1159}
1160
1161/**
1162 * WndProc for the splash screen.
1163 *
1164 * @returns LRESULT
1165 * @param hWnd Window handle.
1166 * @param message Window message.
1167 * @param wParam wParam.
1168 * @param lParam lParam.
1169 */
1170static LRESULT CALLBACK SplashScreenWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
1171{
1172 switch (message)
1173 {
1174 /* Note: WM_NCCREATE is not the first ever message which arrives, but early enough for us. */
1175 case WM_NCCREATE:
1176 {
1177 LPCREATESTRUCT pCS = (LPCREATESTRUCT)lParam;
1178 AssertPtr(pCS);
1179 SetWindowLongPtr(hWnd, GWLP_USERDATA, (LONG_PTR)pCS->lpCreateParams);
1180 break;
1181 }
1182
1183 case WM_PAINT:
1184 {
1185 PVBOXSPLASHSCREENCTX pCtx = (PVBOXSPLASHSCREENCTX)GetWindowLongPtr(hWnd, GWLP_USERDATA);
1186 AssertPtrReturn(pCtx, 0);
1187
1188 PAINTSTRUCT ps;
1189 HDC hdc = BeginPaint(hWnd, &ps);
1190 if (hdc)
1191 {
1192 SplashScreenPaint(pCtx, hdc);
1193 EndPaint(hWnd, &ps);
1194 }
1195 break;
1196 }
1197
1198 case WM_TIMER:
1199 {
1200 PVBOXSPLASHSCREENCTX pCtx = (PVBOXSPLASHSCREENCTX)GetWindowLongPtr(hWnd, GWLP_USERDATA);
1201 AssertPtrReturn(pCtx, 0);
1202
1203 pCtx->uDotAngle = (pCtx->uDotAngle + 10) % 360;
1204 InvalidateRect(hWnd, NULL, FALSE);
1205 break;
1206 }
1207
1208 case WM_DESTROY:
1209 {
1210 KillTimer(hWnd, 1 /* ID */);
1211 PostQuitMessage(0);
1212 break;
1213 }
1214
1215 default:
1216 break;
1217
1218 }
1219
1220 return DefWindowProc(hWnd, message, wParam, lParam);
1221}
1222
1223/**
1224 * The splash screen thread.
1225 *
1226 * @returns VBox status code.
1227 * @param hSelf Thread handle.
1228 * @param pvUser User-supplied pointer. Of type PVBOXSPLASHSCREENCTX.
1229 */
1230static DECLCALLBACK(int) SplashScreenThread(RTTHREAD hSelf, void *pvUser)
1231{
1232 PVBOXSPLASHSCREENCTX pCtx = (PVBOXSPLASHSCREENCTX)pvUser;
1233 AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
1234
1235 ULONG_PTR gdiplusToken;
1236 Gdiplus::GdiplusStartupInput gdiplusStartupInput;
1237 Gdiplus::GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL);
1238
1239 HRSRC const hResource = FindResourceW(pCtx->hInst, MAKEINTRESOURCEW(IDB_SPLASH), L"PNG");
1240 if (hResource)
1241 {
1242 HGLOBAL const hGlobal = LoadResource(pCtx->hInst, hResource);
1243 if (hGlobal)
1244 {
1245 DWORD const dwSize = SizeofResource(pCtx->hInst, hResource);
1246 if (dwSize)
1247 {
1248 LPVOID const pvData = LockResource(hGlobal);
1249 if (pvData)
1250 {
1251 IStream *pStream = SHCreateMemStream((BYTE*)pvData, dwSize);
1252 if (pStream)
1253 {
1254 pCtx->pImage = new Gdiplus::Image(pStream);
1255 pStream->Release();
1256 }
1257
1258 UnlockResource(pvData);
1259 }
1260 }
1261 }
1262 }
1263
1264 if (pCtx->pImage)
1265 {
1266 WNDCLASS wc = {0};
1267 wc.lpfnWndProc = SplashScreenWndProc;
1268 wc.hInstance = pCtx->hInst;
1269 wc.lpszClassName = TEXT("VBoxSplashScreenClass");
1270 wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
1271
1272 RegisterClass(&wc);
1273
1274 pCtx->hWnd = CreateWindow(TEXT("VBoxSplashScreenClass"), VBOX_STUB_TITLE,
1275 WS_POPUP | WS_VISIBLE,
1276 (GetSystemMetrics(SM_CXSCREEN) - pCtx->pImage->GetWidth()) / 2,
1277 (GetSystemMetrics(SM_CYSCREEN) - pCtx->pImage->GetHeight()) / 2,
1278 pCtx->pImage->GetWidth(), pCtx->pImage->GetHeight(),
1279 NULL, NULL, pCtx->hInst, pCtx /* GWLP_USERDATA */);
1280 if (!pCtx->hWnd)
1281 {
1282 DWORD const dwErr = GetLastError();
1283 ShowError("Error creating splash window, rc=%Rrc (%#x)", RTErrConvertFromWin32(dwErr), dwErr);
1284 }
1285 else
1286 {
1287 /* Start timer for splash screen updates. */
1288 SetTimer(pCtx->hWnd, 1 /* ID */, 50 /* ms */, NULL);
1289 }
1290 }
1291 else
1292 ShowError("Error loading splash image");
1293
1294 RTThreadUserSignal(hSelf);
1295
1296 /*
1297 * Enter message loop.
1298 */
1299 if (pCtx->hWnd)
1300 {
1301 MSG msg;
1302 while (GetMessage(&msg, NULL, 0, 0))
1303 {
1304 TranslateMessage(&msg);
1305 DispatchMessage(&msg);
1306 }
1307 }
1308
1309 /*
1310 * Cleanup.
1311 */
1312 if (pCtx->pImage)
1313 {
1314 delete pCtx->pImage;
1315 pCtx->pImage = NULL;
1316 }
1317
1318 DeleteObject(pCtx->hBmpBackBuffer);
1319 DeleteDC(pCtx->hDCBackBuffer);
1320
1321 if (pCtx->hWnd)
1322 {
1323 DestroyWindow(pCtx->hWnd);
1324 pCtx->hWnd = NULL;
1325 }
1326
1327 /* Must come after pImage has been destroyed. */
1328 Gdiplus::GdiplusShutdown(gdiplusToken);
1329
1330 /* GdiplusShutdown() apparently forgets to uninit COM, so we have to do that ourselves via CoUninitialize().
1331 * Not doing that will result in a debug assertion in experimental code in rtThreadNativeUninitComAndOle()
1332 * where we check for dangling COM inits. Sigh. */
1333 CoUninitialize();
1334
1335 return VINF_SUCCESS;
1336}
1337
1338/**
1339 * Creates a splash screen.
1340 *
1341 * @returns VBox status code.
1342 * @param ppCtx Where to return the splash screen context on success.
1343 */
1344static int SplashScreenCreate(PVBOXSPLASHSCREENCTX *ppCtx)
1345{
1346 PVBOXSPLASHSCREENCTX pCtx = (PVBOXSPLASHSCREENCTX)RTMemAlloc(sizeof(VBOXSPLASHSCREENCTX));
1347 AssertPtrReturn(pCtx, VERR_NO_MEMORY);
1348
1349 pCtx->hInst = GetModuleHandle(NULL);
1350 pCtx->hThread = NIL_RTTHREAD;
1351 pCtx->hDCBackBuffer = NULL;
1352
1353 int vrc = RTThreadCreate(&pCtx->hThread, SplashScreenThread, pCtx, 0, RTTHREADTYPE_DEFAULT,
1354 RTTHREADFLAGS_WAITABLE, "vbxStbSplsh");
1355 if (RT_SUCCESS(vrc))
1356 {
1357 vrc = RTThreadUserWait(pCtx->hThread, RT_MS_5SEC);
1358 if (RT_SUCCESS(vrc))
1359 *ppCtx = pCtx;
1360 }
1361
1362 return vrc;
1363}
1364
1365/**
1366 * Destroys a splash screen.
1367 *
1368 * @param pCtx Splash screen context to destroy.
1369 * The pointer will be invalid on return.
1370 */
1371static void SplashScreenDestroy(PVBOXSPLASHSCREENCTX pCtx)
1372{
1373 if (!pCtx)
1374 return;
1375
1376 if (pCtx->hWnd)
1377 {
1378 PostMessage(pCtx->hWnd, WM_QUIT, 0, 0);
1379 pCtx->hWnd = NULL;
1380 }
1381
1382 int vrc = VINF_SUCCESS;
1383
1384 if (pCtx->hThread != NIL_RTTHREAD)
1385 {
1386 int rcThread;
1387 vrc = RTThreadWait(pCtx->hThread, RT_MS_5SEC /* Timeout in ms */, &rcThread);
1388 if (RT_SUCCESS(vrc))
1389 vrc = rcThread;
1390
1391 if (RT_FAILURE(vrc))
1392 ShowError("Destruction of splash screen failed with %Rrc\n", vrc);
1393
1394 pCtx->hThread = NIL_RTTHREAD;
1395 }
1396
1397 RTMemFree(pCtx);
1398 return;
1399}
1400#endif /* VBOX_STUB_WITH_SPLASH */
1401
1402int main(int argc, char **argv)
1403{
1404 /*
1405 * Init IPRT. This is _always_ the very first thing we do.
1406 */
1407 int vrc = RTR3InitExe(argc, &argv, RTR3INIT_FLAGS_STANDALONE_APP);
1408 if (RT_FAILURE(vrc))
1409 return RTMsgInitFailure(vrc);
1410
1411 /*
1412 * Parse arguments.
1413 */
1414
1415 /* Parameter variables. */
1416 bool fExtractOnly = false;
1417 bool fEnableLogging = false;
1418#ifdef VBOX_WITH_CODE_SIGNING
1419 bool fEnableSilentCert = true;
1420 bool fInstallTimestampCA = true;
1421 bool fForceTimestampCaInstall = false;
1422#endif
1423 bool fIgnoreReboot = false;
1424 char szExtractPath[RTPATH_MAX] = {0};
1425 char szMSIArgs[_4K] = {0};
1426 char szMSILogFile[RTPATH_MAX] = {0};
1427
1428 /* Argument enumeration IDs. */
1429 enum KVBOXSTUBOPT
1430 {
1431 KVBOXSTUBOPT_MSI_LOG_FILE = 1000,
1432#if defined(VBOX_STUB_WITH_SPLASH) && defined(DEBUG)
1433 KVBOXSTUBOPT_SPLASH_TEST
1434#endif
1435 };
1436
1437 /* Parameter definitions. */
1438 static const RTGETOPTDEF s_aOptions[] =
1439 {
1440 /** @todo Replace short parameters with enums since they're not
1441 * used (and not documented to the public). */
1442 { "--extract", 'x', RTGETOPT_REQ_NOTHING },
1443 { "-extract", 'x', RTGETOPT_REQ_NOTHING },
1444 { "/extract", 'x', RTGETOPT_REQ_NOTHING },
1445 { "--silent", 's', RTGETOPT_REQ_NOTHING },
1446 { "-silent", 's', RTGETOPT_REQ_NOTHING },
1447 { "/silent", 's', RTGETOPT_REQ_NOTHING },
1448#ifdef VBOX_WITH_CODE_SIGNING
1449 { "--no-silent-cert", 'c', RTGETOPT_REQ_NOTHING },
1450 { "-no-silent-cert", 'c', RTGETOPT_REQ_NOTHING },
1451 { "/no-silent-cert", 'c', RTGETOPT_REQ_NOTHING },
1452 { "--no-install-timestamp-ca", 't', RTGETOPT_REQ_NOTHING },
1453 { "--force-install-timestamp-ca", 'T', RTGETOPT_REQ_NOTHING },
1454#endif
1455 { "--logging", 'l', RTGETOPT_REQ_NOTHING },
1456 { "-logging", 'l', RTGETOPT_REQ_NOTHING },
1457 { "--msi-log-file", KVBOXSTUBOPT_MSI_LOG_FILE, RTGETOPT_REQ_STRING },
1458 { "-msilogfile", KVBOXSTUBOPT_MSI_LOG_FILE, RTGETOPT_REQ_STRING },
1459 { "/logging", 'l', RTGETOPT_REQ_NOTHING },
1460 { "--path", 'p', RTGETOPT_REQ_STRING },
1461 { "-path", 'p', RTGETOPT_REQ_STRING },
1462 { "/path", 'p', RTGETOPT_REQ_STRING },
1463 { "--msiparams", 'm', RTGETOPT_REQ_STRING },
1464 { "-msiparams", 'm', RTGETOPT_REQ_STRING },
1465 { "--msi-prop", 'P', RTGETOPT_REQ_STRING },
1466 { "--reinstall", 'f', RTGETOPT_REQ_NOTHING },
1467 { "-reinstall", 'f', RTGETOPT_REQ_NOTHING },
1468 { "/reinstall", 'f', RTGETOPT_REQ_NOTHING },
1469 { "--ignore-reboot", 'r', RTGETOPT_REQ_NOTHING },
1470 { "--verbose", 'v', RTGETOPT_REQ_NOTHING },
1471 { "-verbose", 'v', RTGETOPT_REQ_NOTHING },
1472 { "/verbose", 'v', RTGETOPT_REQ_NOTHING },
1473 { "--version", 'V', RTGETOPT_REQ_NOTHING },
1474 { "-version", 'V', RTGETOPT_REQ_NOTHING },
1475 { "/version", 'V', RTGETOPT_REQ_NOTHING },
1476 { "--help", 'h', RTGETOPT_REQ_NOTHING },
1477 { "-help", 'h', RTGETOPT_REQ_NOTHING },
1478 { "/help", 'h', RTGETOPT_REQ_NOTHING },
1479 { "/?", 'h', RTGETOPT_REQ_NOTHING },
1480#if defined(VBOX_STUB_WITH_SPLASH) && defined(DEBUG)
1481 { "--splash-test", KVBOXSTUBOPT_SPLASH_TEST, RTGETOPT_REQ_UINT32 },
1482#endif
1483 };
1484
1485 RTGETOPTSTATE GetState;
1486 vrc = RTGetOptInit(&GetState, argc, argv, s_aOptions, RT_ELEMENTS(s_aOptions), 1, 0);
1487 AssertRCReturn(vrc, ShowError("RTGetOptInit failed: %Rrc", vrc));
1488
1489 /* Loop over the arguments. */
1490 int ch;
1491 RTGETOPTUNION ValueUnion;
1492 while ((ch = RTGetOpt(&GetState, &ValueUnion)) != 0)
1493 {
1494 switch (ch)
1495 {
1496 case 'f': /* Force re-installation. */
1497 if (szMSIArgs[0])
1498 vrc = RTStrCat(szMSIArgs, sizeof(szMSIArgs), " ");
1499 if (RT_SUCCESS(vrc))
1500 vrc = RTStrCat(szMSIArgs, sizeof(szMSIArgs), "REINSTALLMODE=vomus REINSTALL=ALL");
1501 if (RT_FAILURE(vrc))
1502 return ShowSyntaxError("Out of space for MSI parameters and properties");
1503 break;
1504
1505 case 'x':
1506 fExtractOnly = true;
1507 break;
1508
1509 case 's':
1510 g_fSilent = true;
1511 break;
1512
1513#ifdef VBOX_WITH_CODE_SIGNING
1514 case 'c':
1515 fEnableSilentCert = false;
1516 break;
1517 case 't':
1518 fInstallTimestampCA = false;
1519 break;
1520 case 'T':
1521 fForceTimestampCaInstall = fInstallTimestampCA = true;
1522 break;
1523#endif
1524 case 'l':
1525 fEnableLogging = true;
1526 break;
1527
1528 case KVBOXSTUBOPT_MSI_LOG_FILE:
1529 if (*ValueUnion.psz == '\0')
1530 szMSILogFile[0] = '\0';
1531 else
1532 {
1533 vrc = RTPathAbs(ValueUnion.psz, szMSILogFile, sizeof(szMSILogFile));
1534 if (RT_FAILURE(vrc))
1535 return ShowSyntaxError("MSI log file path is too long (%Rrc)", vrc);
1536 }
1537 break;
1538
1539 case 'p':
1540 if (*ValueUnion.psz == '\0')
1541 szExtractPath[0] = '\0';
1542 else
1543 {
1544 vrc = RTPathAbs(ValueUnion.psz, szExtractPath, sizeof(szExtractPath));
1545 if (RT_FAILURE(vrc))
1546 return ShowSyntaxError("Extraction path is too long (%Rrc)", vrc);
1547 }
1548 break;
1549
1550 case 'm':
1551 if (szMSIArgs[0])
1552 vrc = RTStrCat(szMSIArgs, sizeof(szMSIArgs), " ");
1553 if (RT_SUCCESS(vrc))
1554 vrc = RTStrCat(szMSIArgs, sizeof(szMSIArgs), ValueUnion.psz);
1555 if (RT_FAILURE(vrc))
1556 return ShowSyntaxError("Out of space for MSI parameters and properties");
1557 break;
1558
1559 case 'P':
1560 {
1561 const char *pszProp = ValueUnion.psz;
1562 if (strpbrk(pszProp, " \t\n\r") == NULL)
1563 {
1564 vrc = RTGetOptFetchValue(&GetState, &ValueUnion, RTGETOPT_REQ_STRING);
1565 if (RT_SUCCESS(vrc))
1566 {
1567 size_t cchMsiArgs = strlen(szMSIArgs);
1568 if (RTStrPrintf2(&szMSIArgs[cchMsiArgs], sizeof(szMSIArgs) - cchMsiArgs,
1569 strpbrk(ValueUnion.psz, " \t\n\r") == NULL ? "%s%s=%s" : "%s%s=\"%s\"",
1570 cchMsiArgs ? " " : "", pszProp, ValueUnion.psz) <= 1)
1571 return ShowSyntaxError("Out of space for MSI parameters and properties");
1572 }
1573 else if (vrc == VERR_GETOPT_REQUIRED_ARGUMENT_MISSING)
1574 return ShowSyntaxError("--msi-prop takes two arguments, the 2nd is missing");
1575 else
1576 return ShowSyntaxError("Failed to get 2nd --msi-prop argument: %Rrc", vrc);
1577 }
1578 else
1579 return ShowSyntaxError("The first argument to --msi-prop must not contain spaces: %s", pszProp);
1580 break;
1581 }
1582
1583 case 'r':
1584 fIgnoreReboot = true;
1585 break;
1586
1587 case 'V':
1588 ShowInfo("Version: %u.%u.%ur%u", VBOX_VERSION_MAJOR, VBOX_VERSION_MINOR, VBOX_VERSION_BUILD, VBOX_SVN_REV);
1589 return RTEXITCODE_SUCCESS;
1590
1591 case 'v':
1592 g_iVerbosity++;
1593 break;
1594
1595 case 'h':
1596 ShowInfo("-- %s v%u.%u.%ur%u --\n"
1597 "\n"
1598 "Command Line Parameters:\n\n"
1599 "--extract\n"
1600 " Extract file contents to temporary directory\n"
1601 "--logging\n"
1602 " Enables MSI installer logging (to extract path)\n"
1603 "--msi-log-file <path/to/file>\n"
1604 " Sets MSI logging to <file>\n"
1605 "--msiparams <parameters>\n"
1606 " Specifies extra parameters for the MSI installers\n"
1607 " double quoted arguments must be doubled and put\n"
1608 " in quotes: --msiparams \"PROP=\"\"a b c\"\"\"\n"
1609 "--msi-prop <prop> <value>\n"
1610 " Adds <prop>=<value> to the MSI parameters,\n"
1611 " quoting the property value if necessary\n"
1612#ifdef VBOX_WITH_CODE_SIGNING
1613 "--no-silent-cert\n"
1614 " Do not install VirtualBox Certificate automatically\n"
1615 " when --silent option is specified\n"
1616#endif
1617#ifdef VBOX_WITH_VBOX_LEGACY_TS_CA
1618 "--force-install-timestamp-ca\n"
1619 " Install the timestamp CA needed for supporting\n"
1620 " legacy Windows versions regardless of the version or\n"
1621 " type of Windows VirtualBox is being installed on.\n"
1622 " Default: All except Windows 10 & 11 desktop\n"
1623 "--no-install-timestamp-ca\n"
1624 " Do not install the above mentioned timestamp CA.\n"
1625#endif
1626 "--path\n"
1627 " Sets the path of the extraction directory\n"
1628 "--reinstall\n"
1629 " Forces VirtualBox to get re-installed\n"
1630 "--ignore-reboot\n"
1631 " Do not set exit code to 3010 if a reboot is required\n"
1632 "--silent\n"
1633 " Enables silent mode installation\n"
1634 "--version\n"
1635 " Displays version number and exit\n"
1636 "-?, -h, --help\n"
1637 " Displays this help text and exit\n"
1638#if defined(VBOX_STUB_WITH_SPLASH) && defined(DEBUG)
1639 "--splash-test <s>\n"
1640 " Displays the splash screen for <s> seconds and exit\n"
1641#endif
1642 "\n"
1643 "Examples:\n"
1644 " %s --msiparams \"INSTALLDIR=\"\"C:\\Program Files\\VirtualBox\"\"\"\n"
1645 " %s --extract -path C:\\VBox",
1646 VBOX_STUB_TITLE, VBOX_VERSION_MAJOR, VBOX_VERSION_MINOR, VBOX_VERSION_BUILD, VBOX_SVN_REV,
1647 argv[0], argv[0]);
1648 return RTEXITCODE_SUCCESS;
1649
1650#if defined(VBOX_STUB_WITH_SPLASH) && defined(DEBUG)
1651 case KVBOXSTUBOPT_SPLASH_TEST:
1652 {
1653 PVBOXSPLASHSCREENCTX pSplashCtx = NULL;
1654 int rc = SplashScreenCreate(&pSplashCtx);
1655 if (RT_SUCCESS(rc))
1656 {
1657 /* Simulate some work. */
1658 RTThreadSleep(ValueUnion.u32 * RT_MS_1SEC);
1659
1660 SplashScreenDestroy(pSplashCtx);
1661 }
1662
1663 if (RT_FAILURE(rc) && !g_fSilent) /* Don't block if running on testboxes. */
1664 ShowError("Splash screen test failed with %Rrc\n", rc);
1665
1666 return RT_SUCCESS(rc) ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE;
1667 }
1668#endif
1669 case VINF_GETOPT_NOT_OPTION:
1670 /* Are (optional) MSI parameters specified and this is the last
1671 * parameter? Append everything to the MSI parameter list then. */
1672 /** @todo r=bird: this makes zero sense */
1673 if (szMSIArgs[0])
1674 {
1675 vrc = RTStrCat(szMSIArgs, sizeof(szMSIArgs), " ");
1676 if (RT_SUCCESS(vrc))
1677 vrc = RTStrCat(szMSIArgs, sizeof(szMSIArgs), ValueUnion.psz);
1678 if (RT_FAILURE(vrc))
1679 return ShowSyntaxError("Out of space for MSI parameters and properties");
1680 continue;
1681 }
1682 /* Fall through is intentional. */
1683
1684 default:
1685 if (g_fSilent)
1686 return RTGetOptPrintError(ch, &ValueUnion);
1687 if (ch == VERR_GETOPT_UNKNOWN_OPTION)
1688 return ShowSyntaxError("Unknown option \"%s\"\n"
1689 "Please refer to the command line help by specifying \"-?\"\n"
1690 "to get more information.", ValueUnion.psz);
1691 return ShowSyntaxError("Parameter parsing error: %Rrc\n"
1692 "Please refer to the command line help by specifying \"-?\"\n"
1693 "to get more information.", ch);
1694 }
1695 }
1696
1697 /*
1698 * Check if we're already running and jump out if so (this is mainly to
1699 * protect the TEMP directory usage, right?).
1700 */
1701 SetLastError(0);
1702 HANDLE hMutexAppRunning = CreateMutexW(NULL, FALSE, L"VBoxStubInstaller");
1703 if ( hMutexAppRunning != NULL
1704 && GetLastError() == ERROR_ALREADY_EXISTS)
1705 {
1706 CloseHandle(hMutexAppRunning); /* close it so we don't keep it open while showing the error message. */
1707 return ShowError("Another installer is already running");
1708 }
1709
1710#ifdef VBOX_STUB_WITH_SPLASH
1711 /* Only show the splash screen if we're *not* in silent mode. */
1712 PVBOXSPLASHSCREENCTX pSplashCtx = NULL;
1713 if (!g_fSilent)
1714 /* ignore rc, not fatal */ SplashScreenCreate(&pSplashCtx);
1715#endif
1716
1717/** @todo
1718 *
1719 * Split the remainder up in functions and simplify the code flow!!
1720 *
1721 * */
1722 RTEXITCODE rcExit = RTEXITCODE_SUCCESS;
1723 RTListInit(&g_TmpFiles);
1724
1725 /*
1726 * Create a random extraction directory in the temporary directory if none
1727 * was given by the user (see @bugref{10201}).
1728 */
1729 PSTUBCLEANUPREC pExtractDirRec = NULL; /* This also indicates that */
1730 if (szExtractPath[0] == '\0')
1731 {
1732 vrc = RTPathTemp(szExtractPath, sizeof(szExtractPath));
1733 if (RT_FAILURE(vrc))
1734 {
1735 CloseHandle(hMutexAppRunning); /* close it so we don't keep it open while showing the error message. */
1736 return ShowError("Failed to find temporary directory: %Rrc", vrc);
1737 }
1738 if (!fExtractOnly) /* Only use a random sub-dir if we extract + run (and not just extract). */
1739 {
1740 vrc = RTPathAppend(szExtractPath, sizeof(szExtractPath), "XXXXXXXXXXXXXXXXXXXXXXXX");
1741 if (RT_SUCCESS(vrc))
1742 /** @todo Need something that return a handle as well as a path. */
1743 vrc = RTDirCreateTemp(szExtractPath, 0700);
1744 if (RT_FAILURE(vrc))
1745 {
1746 CloseHandle(hMutexAppRunning); /* close it so we don't keep it open while showing the error message. */
1747 return ShowError("Failed to create extraction path: %Rrc", vrc);
1748 }
1749 pExtractDirRec = AddCleanupRec(szExtractPath, false /*fIsFile*/);
1750 }
1751 }
1752 RTPathChangeToDosSlashes(szExtractPath, true /* Force conversion. */); /* MSI requirement. */
1753
1754 /*
1755 * Create a console for output if we're in verbose mode.
1756 */
1757#ifdef VBOX_STUB_WITH_OWN_CONSOLE
1758 if (g_iVerbosity)
1759 {
1760 if (!AllocConsole())
1761 return ShowError("Unable to allocate console: LastError=%u\n", GetLastError());
1762
1763# ifdef IPRT_NO_CRT
1764 PRTSTREAM pNewStdOutErr = NULL;
1765 vrc = RTStrmOpen("CONOUT$", "a", &pNewStdOutErr);
1766 if (RT_SUCCESS(vrc))
1767 {
1768 RTStrmSetBufferingMode(pNewStdOutErr, RTSTRMBUFMODE_UNBUFFERED);
1769 g_pStdErr = pNewStdOutErr;
1770 g_pStdOut = pNewStdOutErr;
1771 }
1772# else
1773 freopen("CONOUT$", "w", stdout);
1774 setvbuf(stdout, NULL, _IONBF, 0);
1775 freopen("CONOUT$", "w", stderr);
1776# endif
1777 }
1778#endif /* VBOX_STUB_WITH_OWN_CONSOLE */
1779
1780 /* Convenience: Enable logging if a log file (via --log-file) is specified. */
1781 if ( !fEnableLogging
1782 && szMSILogFile[0] != '\0')
1783 fEnableLogging = true;
1784
1785 if ( fEnableLogging
1786 && szMSILogFile[0] == '\0') /* No log file explicitly specified? Use the extract path by default. */
1787 {
1788 vrc = RTStrCopy(szMSILogFile, sizeof(szMSILogFile), szExtractPath);
1789 if (RT_SUCCESS(vrc))
1790 vrc = RTPathAppend(szMSILogFile, sizeof(szMSILogFile), "VBoxInstallLog.txt");
1791 if (RT_FAILURE(vrc))
1792 return ShowError("Error creating MSI log file name, rc=%Rrc", vrc);
1793 }
1794
1795 VBOXSTUBPKGARCH const enmPkgArch = GetNativePackageArch();
1796
1797 if (g_iVerbosity)
1798 {
1799 RTPrintf("Host architecture : %s\n", GetPackageArchStr(enmPkgArch));
1800 RTPrintf("Extraction path : %s\n", szExtractPath);
1801 RTPrintf("Silent installation : %RTbool\n", g_fSilent);
1802#ifdef VBOX_WITH_CODE_SIGNING
1803 RTPrintf("Certificate installation : %RTbool\n", fEnableSilentCert);
1804#endif
1805 RTPrintf("Additional MSI parameters: %s\n", szMSIArgs[0] ? szMSIArgs : "<None>");
1806 RTPrintf("Logging to file : %s\n", szMSILogFile[0] ? szMSILogFile : "<None>");
1807 }
1808
1809 /*
1810 * 32-bit is not officially supported any more.
1811 */
1812 if ( !fExtractOnly
1813 && !g_fSilent
1814 && enmPkgArch == VBOXSTUBPKGARCH_X86)
1815 rcExit = ShowError("32-bit Windows hosts are not supported by this VirtualBox release.");
1816 else
1817 {
1818 /*
1819 * Read our manifest.
1820 */
1821 VBOXSTUBPKGHEADER const *pHeader = NULL;
1822 vrc = FindData("MANIFEST", (uint8_t const **)&pHeader, NULL);
1823 if (RT_SUCCESS(vrc))
1824 {
1825 /** @todo If we could, we should validate the header. Only the magic isn't
1826 * commonly defined, nor the version number... */
1827
1828 /*
1829 * Up to this point, we haven't done anything that requires any cleanup.
1830 * From here on, we do everything in functions so we can counter clean up.
1831 */
1832 rcExit = ExtractFiles(pHeader->cPackages, szExtractPath, fExtractOnly, &pExtractDirRec);
1833 if (rcExit == RTEXITCODE_SUCCESS)
1834 {
1835#ifdef VBOX_STUB_WITH_SPLASH
1836 /* Hide the splash right after extraction. */
1837 SplashScreenDestroy(pSplashCtx);
1838 pSplashCtx = NULL;
1839#endif
1840 if (fExtractOnly)
1841 ShowInfo("Files were extracted to: %s", szExtractPath);
1842 else
1843 {
1844 rcExit = CopyCustomDir(szExtractPath);
1845#ifdef VBOX_WITH_CODE_SIGNING
1846# ifdef VBOX_WITH_VBOX_LEGACY_TS_CA
1847 if (rcExit == RTEXITCODE_SUCCESS && fInstallTimestampCA)
1848 rcExit = InstallTimestampCA(fForceTimestampCaInstall);
1849# endif
1850 if (rcExit == RTEXITCODE_SUCCESS && fEnableSilentCert && g_fSilent)
1851 rcExit = InstallCertificates();
1852#endif
1853 unsigned iPackage = 0;
1854 while ( iPackage < pHeader->cPackages
1855 && (rcExit == RTEXITCODE_SUCCESS || rcExit == (RTEXITCODE)ERROR_SUCCESS_REBOOT_REQUIRED))
1856 {
1857 RTEXITCODE rcExit2 = ProcessPackage(iPackage, szMSIArgs, szMSILogFile[0] ? szMSILogFile : NULL);
1858 if (rcExit2 != RTEXITCODE_SUCCESS)
1859 rcExit = rcExit2;
1860 iPackage++;
1861 }
1862 }
1863 }
1864
1865 /*
1866 * Do cleanups unless we're only extracting (ignoring failures for now).
1867 */
1868 if (!fExtractOnly)
1869 {
1870 RTPathSetCurrent("..");
1871 CleanUp(!fEnableLogging && pExtractDirRec && !pExtractDirRec->fDontDelete ? szExtractPath : NULL);
1872 }
1873
1874 /* Free any left behind cleanup records (not strictly needed). */
1875 PSTUBCLEANUPREC pCur, pNext;
1876 RTListForEachSafe(&g_TmpFiles, pCur, pNext, STUBCLEANUPREC, ListEntry)
1877 {
1878 RTListNodeRemove(&pCur->ListEntry);
1879 RTMemFree(pCur);
1880 }
1881 }
1882 else
1883 rcExit = ShowError("Internal package error: Manifest not found (%Rrc)", vrc);
1884 }
1885
1886#if defined(_WIN32_WINNT) && _WIN32_WINNT >= 0x0501
1887# ifdef VBOX_STUB_WITH_OWN_CONSOLE
1888 if (g_iVerbosity)
1889 FreeConsole();
1890# endif /* VBOX_STUB_WITH_OWN_CONSOLE */
1891#endif
1892
1893#ifdef VBOX_STUB_WITH_SPLASH
1894 /* Make sure the splash screen got destroyed (if not already above). */
1895 SplashScreenDestroy(pSplashCtx);
1896 pSplashCtx = NULL;
1897#endif
1898
1899 /*
1900 * Release instance mutex just to be on the safe side.
1901 */
1902 if (hMutexAppRunning != NULL)
1903 CloseHandle(hMutexAppRunning);
1904
1905 return rcExit != (RTEXITCODE)ERROR_SUCCESS_REBOOT_REQUIRED || !fIgnoreReboot ? rcExit : RTEXITCODE_SUCCESS;
1906}
1907
1908#ifndef IPRT_NO_CRT
1909int WINAPI WinMain(HINSTANCE hInstance,
1910 HINSTANCE hPrevInstance,
1911 char *lpCmdLine,
1912 int nCmdShow)
1913{
1914 RT_NOREF(hInstance, hPrevInstance, lpCmdLine, nCmdShow);
1915 return main(__argc, __argv);
1916}
1917#endif
1918
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