VirtualBox

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

Last change on this file was 108546, checked in by vboxsync, 2 months ago

Windows host installer stub (VBoxStub): GdiplusShutdown() apparently forgets to uninit COM, so we have to do that ourselves via CoUninitialize(). Not doing that will result in a debug assertion in experimental code in rtThreadNativeUninitComAndOle() where we check for dangling COM inits. Sigh. bugref:10870

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 70.9 KB
Line 
1/* $Id: VBoxStub.cpp 108546 2025-03-13 09:28:55Z 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 RTFileCreateUnique was
359 * used to generate the name, 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 RTFileCreateUnique was
471 * used to generate the name, 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 /* If we only extract or if it's a common file, use the original file name,
1083 otherwise generate a random name with the same file extension (@bugref{10201}). */
1084 RTFILE hFile = NIL_RTFILE;
1085 char szDstFile[RTPATH_MAX];
1086 if (fExtractOnly || pPackage->enmArch == VBOXSTUBPKGARCH_ALL)
1087 rc = RTPathJoin(szDstFile, sizeof(szDstFile), pszDstDir, pPackage->szFilename);
1088 else
1089 {
1090 rc = RTPathJoin(szDstFile, sizeof(szDstFile), pszDstDir, "XXXXXXXXXXXXXXXXXXXXXXXX");
1091 if (RT_SUCCESS(rc))
1092 {
1093 const char *pszSuffix = RTPathSuffix(pPackage->szFilename);
1094 if (pszSuffix)
1095 rc = RTStrCat(szDstFile, sizeof(szDstFile), pszSuffix);
1096 if (RT_SUCCESS(rc))
1097 {
1098 rc = RTFileCreateUnique(&hFile, szDstFile,
1099 RTFILE_O_CREATE | RTFILE_O_WRITE | RTFILE_O_DENY_WRITE
1100 | (0700 << RTFILE_O_CREATE_MODE_SHIFT));
1101 if (RT_FAILURE(rc))
1102 return ShowError("Failed to create unique filename for '%s' in '%s': %Rrc",
1103 pPackage->szFilename, pszDstDir, rc);
1104 }
1105 }
1106 }
1107 if (RT_FAILURE(rc))
1108 return ShowError("Internal error: Build extraction file name failed: %Rrc", rc);
1109
1110 rc = Extract(pPackage, szDstFile, hFile, k);
1111 if (RT_FAILURE(rc))
1112 return ShowError("Error extracting package #%u (%s): %Rrc", k, pPackage->szFilename, rc);
1113 }
1114 }
1115
1116 return RTEXITCODE_SUCCESS;
1117}
1118
1119#ifdef VBOX_STUB_WITH_SPLASH
1120/**
1121 * Structure for keeping the splash screen context.
1122 */
1123typedef struct VBOXSPLASHSCREENCTX
1124{
1125 /** The instance the window is running in. */
1126 HINSTANCE hInst;
1127 /** The actual splash screen window. */
1128 HWND hWnd;
1129 /** The worker thread handle. */
1130 RTTHREAD hThread;
1131 /** Allocated image which is drawn as the splash screen.
1132 * Owned by the splash screen thread. */
1133 Gdiplus::Image *pImage;
1134 /** The current angle we draw the loading dot. */
1135 unsigned uDotAngle;
1136 /** Back buffer DC.
1137 * Also acts as a beacon for initializing / destroying the back buffer data. */
1138 HDC hDCBackBuffer;
1139 /** Back buffer bitmap. */
1140 HBITMAP hBmpBackBuffer;
1141} VBOXSPLASHSCREENCTX;
1142/** Pointer to a structure for keeping the splash screen context. */
1143typedef VBOXSPLASHSCREENCTX *PVBOXSPLASHSCREENCTX;
1144
1145/**
1146 * Paints the plash screen.
1147 *
1148 * @param pCtx Splash screen context to use.
1149 * @param hdc HDC to use for drawing.
1150 */
1151static void SplashScreenPaint(PVBOXSPLASHSCREENCTX pCtx, HDC hdc)
1152{
1153 unsigned const uWidth = pCtx->pImage->GetWidth();
1154 unsigned const uHeight = pCtx->pImage->GetHeight();
1155
1156 /* To prevent flickering we use a back buffer. Create it if not done yet. */
1157 if (!pCtx->hDCBackBuffer)
1158 {
1159 pCtx->hDCBackBuffer = CreateCompatibleDC(hdc);
1160 pCtx->hBmpBackBuffer = CreateCompatibleBitmap(hdc, uWidth, uHeight);
1161 SelectObject(pCtx->hDCBackBuffer, pCtx->hBmpBackBuffer);
1162 }
1163
1164 Gdiplus::Graphics graphics(pCtx->hDCBackBuffer);
1165
1166 graphics.Clear(Gdiplus::Color(255, 255, 255, 255)); /* White background. */
1167 graphics.DrawImage(pCtx->pImage, 0, 0, pCtx->pImage->GetWidth(), pCtx->pImage->GetHeight());
1168
1169 unsigned const uRadius = 10;
1170 unsigned const uCenterX = uWidth / 2;
1171 unsigned const uCenterY = uHeight / 2 + 80 /* Offset for drawing the dot below the VBox logo */;
1172
1173 /* Calculate the position of the spinning circle. */
1174 unsigned const x = uCenterX + static_cast<int>(uRadius * cos(pCtx->uDotAngle * 3.14159 /* PI */ / 180));
1175 unsigned const y = uCenterY + static_cast<int>(uRadius * sin(pCtx->uDotAngle * 3.14159 /* PI */ / 180));
1176
1177 Gdiplus::SolidBrush brush(Gdiplus::Color(255, 0, 0, 100)); /* Dark blue dot. */
1178 graphics.FillEllipse(&brush, x - 5, y - 5, 10, 10);
1179
1180 /* Blit back buffer DC to front DC. */
1181 BitBlt(hdc, 0, 0, uWidth, uHeight, pCtx->hDCBackBuffer, 0, 0, SRCCOPY);
1182}
1183
1184/**
1185 * WndProc for the splash screen.
1186 *
1187 * @returns LRESULT
1188 * @param hWnd Window handle.
1189 * @param message Window message.
1190 * @param wParam wParam.
1191 * @param lParam lParam.
1192 */
1193static LRESULT CALLBACK SplashScreenWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
1194{
1195 switch (message)
1196 {
1197 /* Note: WM_NCCREATE is not the first ever message which arrives, but early enough for us. */
1198 case WM_NCCREATE:
1199 {
1200 LPCREATESTRUCT pCS = (LPCREATESTRUCT)lParam;
1201 AssertPtr(pCS);
1202 SetWindowLongPtr(hWnd, GWLP_USERDATA, (LONG_PTR)pCS->lpCreateParams);
1203 break;
1204 }
1205
1206 case WM_PAINT:
1207 {
1208 PVBOXSPLASHSCREENCTX pCtx = (PVBOXSPLASHSCREENCTX)GetWindowLongPtr(hWnd, GWLP_USERDATA);
1209 AssertPtrReturn(pCtx, 0);
1210
1211 PAINTSTRUCT ps;
1212 HDC hdc = BeginPaint(hWnd, &ps);
1213 if (hdc)
1214 {
1215 SplashScreenPaint(pCtx, hdc);
1216 EndPaint(hWnd, &ps);
1217 }
1218 break;
1219 }
1220
1221 case WM_TIMER:
1222 {
1223 PVBOXSPLASHSCREENCTX pCtx = (PVBOXSPLASHSCREENCTX)GetWindowLongPtr(hWnd, GWLP_USERDATA);
1224 AssertPtrReturn(pCtx, 0);
1225
1226 pCtx->uDotAngle = (pCtx->uDotAngle + 10) % 360;
1227 InvalidateRect(hWnd, NULL, FALSE);
1228 break;
1229 }
1230
1231 case WM_DESTROY:
1232 {
1233 KillTimer(hWnd, 1 /* ID */);
1234 PostQuitMessage(0);
1235 break;
1236 }
1237
1238 default:
1239 break;
1240
1241 }
1242
1243 return DefWindowProc(hWnd, message, wParam, lParam);
1244}
1245
1246/**
1247 * The splash screen thread.
1248 *
1249 * @returns VBox status code.
1250 * @param hSelf Thread handle.
1251 * @param pvUser User-supplied pointer. Of type PVBOXSPLASHSCREENCTX.
1252 */
1253static DECLCALLBACK(int) SplashScreenThread(RTTHREAD hSelf, void *pvUser)
1254{
1255 PVBOXSPLASHSCREENCTX pCtx = (PVBOXSPLASHSCREENCTX)pvUser;
1256 AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
1257
1258 ULONG_PTR gdiplusToken;
1259 Gdiplus::GdiplusStartupInput gdiplusStartupInput;
1260 Gdiplus::GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL);
1261
1262 HRSRC const hResource = FindResourceW(pCtx->hInst, MAKEINTRESOURCEW(IDB_SPLASH), L"PNG");
1263 if (hResource)
1264 {
1265 HGLOBAL const hGlobal = LoadResource(pCtx->hInst, hResource);
1266 if (hGlobal)
1267 {
1268 DWORD const dwSize = SizeofResource(pCtx->hInst, hResource);
1269 if (dwSize)
1270 {
1271 LPVOID const pvData = LockResource(hGlobal);
1272 if (pvData)
1273 {
1274 IStream *pStream = SHCreateMemStream((BYTE*)pvData, dwSize);
1275 if (pStream)
1276 {
1277 pCtx->pImage = new Gdiplus::Image(pStream);
1278 pStream->Release();
1279 }
1280
1281 UnlockResource(pvData);
1282 }
1283 }
1284 }
1285 }
1286
1287 if (pCtx->pImage)
1288 {
1289 WNDCLASS wc = {0};
1290 wc.lpfnWndProc = SplashScreenWndProc;
1291 wc.hInstance = pCtx->hInst;
1292 wc.lpszClassName = TEXT("VBoxSplashScreenClass");
1293 wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
1294
1295 RegisterClass(&wc);
1296
1297 pCtx->hWnd = CreateWindow(TEXT("VBoxSplashScreenClass"), VBOX_STUB_TITLE,
1298 WS_POPUP | WS_VISIBLE,
1299 (GetSystemMetrics(SM_CXSCREEN) - pCtx->pImage->GetWidth()) / 2,
1300 (GetSystemMetrics(SM_CYSCREEN) - pCtx->pImage->GetHeight()) / 2,
1301 pCtx->pImage->GetWidth(), pCtx->pImage->GetHeight(),
1302 NULL, NULL, pCtx->hInst, pCtx /* GWLP_USERDATA */);
1303 if (!pCtx->hWnd)
1304 {
1305 DWORD const dwErr = GetLastError();
1306 ShowError("Error creating splash window, rc=%Rrc (%#x)", RTErrConvertFromWin32(dwErr), dwErr);
1307 }
1308 else
1309 {
1310 /* Start timer for splash screen updates. */
1311 SetTimer(pCtx->hWnd, 1 /* ID */, 50 /* ms */, NULL);
1312 }
1313 }
1314 else
1315 ShowError("Error loading splash image");
1316
1317 RTThreadUserSignal(hSelf);
1318
1319 /*
1320 * Enter message loop.
1321 */
1322 if (pCtx->hWnd)
1323 {
1324 MSG msg;
1325 while (GetMessage(&msg, NULL, 0, 0))
1326 {
1327 TranslateMessage(&msg);
1328 DispatchMessage(&msg);
1329 }
1330 }
1331
1332 /*
1333 * Cleanup.
1334 */
1335 if (pCtx->pImage)
1336 {
1337 delete pCtx->pImage;
1338 pCtx->pImage = NULL;
1339 }
1340
1341 DeleteObject(pCtx->hBmpBackBuffer);
1342 DeleteDC(pCtx->hDCBackBuffer);
1343
1344 if (pCtx->hWnd)
1345 {
1346 DestroyWindow(pCtx->hWnd);
1347 pCtx->hWnd = NULL;
1348 }
1349
1350 /* Must come after pImage has been destroyed. */
1351 Gdiplus::GdiplusShutdown(gdiplusToken);
1352
1353 /* GdiplusShutdown() apparently forgets to uninit COM, so we have to do that ourselves via CoUninitialize().
1354 * Not doing that will result in a debug assertion in experimental code in rtThreadNativeUninitComAndOle()
1355 * where we check for dangling COM inits. Sigh. */
1356 CoUninitialize();
1357
1358 return VINF_SUCCESS;
1359}
1360
1361/**
1362 * Creates a splash screen.
1363 *
1364 * @returns VBox status code.
1365 * @param ppCtx Where to return the splash screen context on success.
1366 */
1367static int SplashScreenCreate(PVBOXSPLASHSCREENCTX *ppCtx)
1368{
1369 PVBOXSPLASHSCREENCTX pCtx = (PVBOXSPLASHSCREENCTX)RTMemAlloc(sizeof(VBOXSPLASHSCREENCTX));
1370 AssertPtrReturn(pCtx, VERR_NO_MEMORY);
1371
1372 pCtx->hInst = GetModuleHandle(NULL);
1373 pCtx->hThread = NIL_RTTHREAD;
1374 pCtx->hDCBackBuffer = NULL;
1375
1376 int vrc = RTThreadCreate(&pCtx->hThread, SplashScreenThread, pCtx, 0, RTTHREADTYPE_DEFAULT,
1377 RTTHREADFLAGS_WAITABLE, "vbxStbSplsh");
1378 if (RT_SUCCESS(vrc))
1379 {
1380 vrc = RTThreadUserWait(pCtx->hThread, RT_MS_5SEC);
1381 if (RT_SUCCESS(vrc))
1382 *ppCtx = pCtx;
1383 }
1384
1385 return vrc;
1386}
1387
1388/**
1389 * Destroys a splash screen.
1390 *
1391 * @param pCtx Splash screen context to destroy.
1392 * The pointer will be invalid on return.
1393 */
1394static void SplashScreenDestroy(PVBOXSPLASHSCREENCTX pCtx)
1395{
1396 if (!pCtx)
1397 return;
1398
1399 if (pCtx->hWnd)
1400 {
1401 PostMessage(pCtx->hWnd, WM_QUIT, 0, 0);
1402 pCtx->hWnd = NULL;
1403 }
1404
1405 int vrc = VINF_SUCCESS;
1406
1407 if (pCtx->hThread != NIL_RTTHREAD)
1408 {
1409 int rcThread;
1410 vrc = RTThreadWait(pCtx->hThread, RT_MS_5SEC /* Timeout in ms */, &rcThread);
1411 if (RT_SUCCESS(vrc))
1412 vrc = rcThread;
1413
1414 if (RT_FAILURE(vrc))
1415 ShowError("Destruction of splash screen failed with %Rrc\n", vrc);
1416
1417 pCtx->hThread = NIL_RTTHREAD;
1418 }
1419
1420 RTMemFree(pCtx);
1421 return;
1422}
1423#endif /* VBOX_STUB_WITH_SPLASH */
1424
1425int main(int argc, char **argv)
1426{
1427 /*
1428 * Init IPRT. This is _always_ the very first thing we do.
1429 */
1430 int vrc = RTR3InitExe(argc, &argv, RTR3INIT_FLAGS_STANDALONE_APP);
1431 if (RT_FAILURE(vrc))
1432 return RTMsgInitFailure(vrc);
1433
1434 /*
1435 * Parse arguments.
1436 */
1437
1438 /* Parameter variables. */
1439 bool fExtractOnly = false;
1440 bool fEnableLogging = false;
1441#ifdef VBOX_WITH_CODE_SIGNING
1442 bool fEnableSilentCert = true;
1443 bool fInstallTimestampCA = true;
1444 bool fForceTimestampCaInstall = false;
1445#endif
1446 bool fIgnoreReboot = false;
1447 char szExtractPath[RTPATH_MAX] = {0};
1448 char szMSIArgs[_4K] = {0};
1449 char szMSILogFile[RTPATH_MAX] = {0};
1450
1451 /* Argument enumeration IDs. */
1452 enum KVBOXSTUBOPT
1453 {
1454 KVBOXSTUBOPT_MSI_LOG_FILE = 1000,
1455#if defined(VBOX_STUB_WITH_SPLASH) && defined(DEBUG)
1456 KVBOXSTUBOPT_SPLASH_TEST
1457#endif
1458 };
1459
1460 /* Parameter definitions. */
1461 static const RTGETOPTDEF s_aOptions[] =
1462 {
1463 /** @todo Replace short parameters with enums since they're not
1464 * used (and not documented to the public). */
1465 { "--extract", 'x', RTGETOPT_REQ_NOTHING },
1466 { "-extract", 'x', RTGETOPT_REQ_NOTHING },
1467 { "/extract", 'x', RTGETOPT_REQ_NOTHING },
1468 { "--silent", 's', RTGETOPT_REQ_NOTHING },
1469 { "-silent", 's', RTGETOPT_REQ_NOTHING },
1470 { "/silent", 's', RTGETOPT_REQ_NOTHING },
1471#ifdef VBOX_WITH_CODE_SIGNING
1472 { "--no-silent-cert", 'c', RTGETOPT_REQ_NOTHING },
1473 { "-no-silent-cert", 'c', RTGETOPT_REQ_NOTHING },
1474 { "/no-silent-cert", 'c', RTGETOPT_REQ_NOTHING },
1475 { "--no-install-timestamp-ca", 't', RTGETOPT_REQ_NOTHING },
1476 { "--force-install-timestamp-ca", 'T', RTGETOPT_REQ_NOTHING },
1477#endif
1478 { "--logging", 'l', RTGETOPT_REQ_NOTHING },
1479 { "-logging", 'l', RTGETOPT_REQ_NOTHING },
1480 { "--msi-log-file", KVBOXSTUBOPT_MSI_LOG_FILE, RTGETOPT_REQ_STRING },
1481 { "-msilogfile", KVBOXSTUBOPT_MSI_LOG_FILE, RTGETOPT_REQ_STRING },
1482 { "/logging", 'l', RTGETOPT_REQ_NOTHING },
1483 { "--path", 'p', RTGETOPT_REQ_STRING },
1484 { "-path", 'p', RTGETOPT_REQ_STRING },
1485 { "/path", 'p', RTGETOPT_REQ_STRING },
1486 { "--msiparams", 'm', RTGETOPT_REQ_STRING },
1487 { "-msiparams", 'm', RTGETOPT_REQ_STRING },
1488 { "--msi-prop", 'P', RTGETOPT_REQ_STRING },
1489 { "--reinstall", 'f', RTGETOPT_REQ_NOTHING },
1490 { "-reinstall", 'f', RTGETOPT_REQ_NOTHING },
1491 { "/reinstall", 'f', RTGETOPT_REQ_NOTHING },
1492 { "--ignore-reboot", 'r', RTGETOPT_REQ_NOTHING },
1493 { "--verbose", 'v', RTGETOPT_REQ_NOTHING },
1494 { "-verbose", 'v', RTGETOPT_REQ_NOTHING },
1495 { "/verbose", 'v', RTGETOPT_REQ_NOTHING },
1496 { "--version", 'V', RTGETOPT_REQ_NOTHING },
1497 { "-version", 'V', RTGETOPT_REQ_NOTHING },
1498 { "/version", 'V', RTGETOPT_REQ_NOTHING },
1499 { "--help", 'h', RTGETOPT_REQ_NOTHING },
1500 { "-help", 'h', RTGETOPT_REQ_NOTHING },
1501 { "/help", 'h', RTGETOPT_REQ_NOTHING },
1502 { "/?", 'h', RTGETOPT_REQ_NOTHING },
1503#if defined(VBOX_STUB_WITH_SPLASH) && defined(DEBUG)
1504 { "--splash-test", KVBOXSTUBOPT_SPLASH_TEST, RTGETOPT_REQ_UINT32 },
1505#endif
1506 };
1507
1508 RTGETOPTSTATE GetState;
1509 vrc = RTGetOptInit(&GetState, argc, argv, s_aOptions, RT_ELEMENTS(s_aOptions), 1, 0);
1510 AssertRCReturn(vrc, ShowError("RTGetOptInit failed: %Rrc", vrc));
1511
1512 /* Loop over the arguments. */
1513 int ch;
1514 RTGETOPTUNION ValueUnion;
1515 while ((ch = RTGetOpt(&GetState, &ValueUnion)) != 0)
1516 {
1517 switch (ch)
1518 {
1519 case 'f': /* Force re-installation. */
1520 if (szMSIArgs[0])
1521 vrc = RTStrCat(szMSIArgs, sizeof(szMSIArgs), " ");
1522 if (RT_SUCCESS(vrc))
1523 vrc = RTStrCat(szMSIArgs, sizeof(szMSIArgs), "REINSTALLMODE=vomus REINSTALL=ALL");
1524 if (RT_FAILURE(vrc))
1525 return ShowSyntaxError("Out of space for MSI parameters and properties");
1526 break;
1527
1528 case 'x':
1529 fExtractOnly = true;
1530 break;
1531
1532 case 's':
1533 g_fSilent = true;
1534 break;
1535
1536#ifdef VBOX_WITH_CODE_SIGNING
1537 case 'c':
1538 fEnableSilentCert = false;
1539 break;
1540 case 't':
1541 fInstallTimestampCA = false;
1542 break;
1543 case 'T':
1544 fForceTimestampCaInstall = fInstallTimestampCA = true;
1545 break;
1546#endif
1547 case 'l':
1548 fEnableLogging = true;
1549 break;
1550
1551 case KVBOXSTUBOPT_MSI_LOG_FILE:
1552 if (*ValueUnion.psz == '\0')
1553 szMSILogFile[0] = '\0';
1554 else
1555 {
1556 vrc = RTPathAbs(ValueUnion.psz, szMSILogFile, sizeof(szMSILogFile));
1557 if (RT_FAILURE(vrc))
1558 return ShowSyntaxError("MSI log file path is too long (%Rrc)", vrc);
1559 }
1560 break;
1561
1562 case 'p':
1563 if (*ValueUnion.psz == '\0')
1564 szExtractPath[0] = '\0';
1565 else
1566 {
1567 vrc = RTPathAbs(ValueUnion.psz, szExtractPath, sizeof(szExtractPath));
1568 if (RT_FAILURE(vrc))
1569 return ShowSyntaxError("Extraction path is too long (%Rrc)", vrc);
1570 }
1571 break;
1572
1573 case 'm':
1574 if (szMSIArgs[0])
1575 vrc = RTStrCat(szMSIArgs, sizeof(szMSIArgs), " ");
1576 if (RT_SUCCESS(vrc))
1577 vrc = RTStrCat(szMSIArgs, sizeof(szMSIArgs), ValueUnion.psz);
1578 if (RT_FAILURE(vrc))
1579 return ShowSyntaxError("Out of space for MSI parameters and properties");
1580 break;
1581
1582 case 'P':
1583 {
1584 const char *pszProp = ValueUnion.psz;
1585 if (strpbrk(pszProp, " \t\n\r") == NULL)
1586 {
1587 vrc = RTGetOptFetchValue(&GetState, &ValueUnion, RTGETOPT_REQ_STRING);
1588 if (RT_SUCCESS(vrc))
1589 {
1590 size_t cchMsiArgs = strlen(szMSIArgs);
1591 if (RTStrPrintf2(&szMSIArgs[cchMsiArgs], sizeof(szMSIArgs) - cchMsiArgs,
1592 strpbrk(ValueUnion.psz, " \t\n\r") == NULL ? "%s%s=%s" : "%s%s=\"%s\"",
1593 cchMsiArgs ? " " : "", pszProp, ValueUnion.psz) <= 1)
1594 return ShowSyntaxError("Out of space for MSI parameters and properties");
1595 }
1596 else if (vrc == VERR_GETOPT_REQUIRED_ARGUMENT_MISSING)
1597 return ShowSyntaxError("--msi-prop takes two arguments, the 2nd is missing");
1598 else
1599 return ShowSyntaxError("Failed to get 2nd --msi-prop argument: %Rrc", vrc);
1600 }
1601 else
1602 return ShowSyntaxError("The first argument to --msi-prop must not contain spaces: %s", pszProp);
1603 break;
1604 }
1605
1606 case 'r':
1607 fIgnoreReboot = true;
1608 break;
1609
1610 case 'V':
1611 ShowInfo("Version: %u.%u.%ur%u", VBOX_VERSION_MAJOR, VBOX_VERSION_MINOR, VBOX_VERSION_BUILD, VBOX_SVN_REV);
1612 return RTEXITCODE_SUCCESS;
1613
1614 case 'v':
1615 g_iVerbosity++;
1616 break;
1617
1618 case 'h':
1619 ShowInfo("-- %s v%u.%u.%ur%u --\n"
1620 "\n"
1621 "Command Line Parameters:\n\n"
1622 "--extract\n"
1623 " Extract file contents to temporary directory\n"
1624 "--logging\n"
1625 " Enables MSI installer logging (to extract path)\n"
1626 "--msi-log-file <path/to/file>\n"
1627 " Sets MSI logging to <file>\n"
1628 "--msiparams <parameters>\n"
1629 " Specifies extra parameters for the MSI installers\n"
1630 " double quoted arguments must be doubled and put\n"
1631 " in quotes: --msiparams \"PROP=\"\"a b c\"\"\"\n"
1632 "--msi-prop <prop> <value>\n"
1633 " Adds <prop>=<value> to the MSI parameters,\n"
1634 " quoting the property value if necessary\n"
1635#ifdef VBOX_WITH_CODE_SIGNING
1636 "--no-silent-cert\n"
1637 " Do not install VirtualBox Certificate automatically\n"
1638 " when --silent option is specified\n"
1639#endif
1640#ifdef VBOX_WITH_VBOX_LEGACY_TS_CA
1641 "--force-install-timestamp-ca\n"
1642 " Install the timestamp CA needed for supporting\n"
1643 " legacy Windows versions regardless of the version or\n"
1644 " type of Windows VirtualBox is being installed on.\n"
1645 " Default: All except Windows 10 & 11 desktop\n"
1646 "--no-install-timestamp-ca\n"
1647 " Do not install the above mentioned timestamp CA.\n"
1648#endif
1649 "--path\n"
1650 " Sets the path of the extraction directory\n"
1651 "--reinstall\n"
1652 " Forces VirtualBox to get re-installed\n"
1653 "--ignore-reboot\n"
1654 " Do not set exit code to 3010 if a reboot is required\n"
1655 "--silent\n"
1656 " Enables silent mode installation\n"
1657 "--version\n"
1658 " Displays version number and exit\n"
1659 "-?, -h, --help\n"
1660 " Displays this help text and exit\n"
1661#if defined(VBOX_STUB_WITH_SPLASH) && defined(DEBUG)
1662 "--splash-test <s>\n"
1663 " Displays the splash screen for <s> seconds and exit\n"
1664#endif
1665 "\n"
1666 "Examples:\n"
1667 " %s --msiparams \"INSTALLDIR=\"\"C:\\Program Files\\VirtualBox\"\"\"\n"
1668 " %s --extract -path C:\\VBox",
1669 VBOX_STUB_TITLE, VBOX_VERSION_MAJOR, VBOX_VERSION_MINOR, VBOX_VERSION_BUILD, VBOX_SVN_REV,
1670 argv[0], argv[0]);
1671 return RTEXITCODE_SUCCESS;
1672
1673#if defined(VBOX_STUB_WITH_SPLASH) && defined(DEBUG)
1674 case KVBOXSTUBOPT_SPLASH_TEST:
1675 {
1676 PVBOXSPLASHSCREENCTX pSplashCtx = NULL;
1677 int rc = SplashScreenCreate(&pSplashCtx);
1678 if (RT_SUCCESS(rc))
1679 {
1680 /* Simulate some work. */
1681 RTThreadSleep(ValueUnion.u32 * RT_MS_1SEC);
1682
1683 SplashScreenDestroy(pSplashCtx);
1684 }
1685
1686 if (RT_FAILURE(rc) && !g_fSilent) /* Don't block if running on testboxes. */
1687 ShowError("Splash screen test failed with %Rrc\n", rc);
1688
1689 return RT_SUCCESS(rc) ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE;
1690 }
1691#endif
1692 case VINF_GETOPT_NOT_OPTION:
1693 /* Are (optional) MSI parameters specified and this is the last
1694 * parameter? Append everything to the MSI parameter list then. */
1695 /** @todo r=bird: this makes zero sense */
1696 if (szMSIArgs[0])
1697 {
1698 vrc = RTStrCat(szMSIArgs, sizeof(szMSIArgs), " ");
1699 if (RT_SUCCESS(vrc))
1700 vrc = RTStrCat(szMSIArgs, sizeof(szMSIArgs), ValueUnion.psz);
1701 if (RT_FAILURE(vrc))
1702 return ShowSyntaxError("Out of space for MSI parameters and properties");
1703 continue;
1704 }
1705 /* Fall through is intentional. */
1706
1707 default:
1708 if (g_fSilent)
1709 return RTGetOptPrintError(ch, &ValueUnion);
1710 if (ch == VERR_GETOPT_UNKNOWN_OPTION)
1711 return ShowSyntaxError("Unknown option \"%s\"\n"
1712 "Please refer to the command line help by specifying \"-?\"\n"
1713 "to get more information.", ValueUnion.psz);
1714 return ShowSyntaxError("Parameter parsing error: %Rrc\n"
1715 "Please refer to the command line help by specifying \"-?\"\n"
1716 "to get more information.", ch);
1717 }
1718 }
1719
1720 /*
1721 * Check if we're already running and jump out if so (this is mainly to
1722 * protect the TEMP directory usage, right?).
1723 */
1724 SetLastError(0);
1725 HANDLE hMutexAppRunning = CreateMutexW(NULL, FALSE, L"VBoxStubInstaller");
1726 if ( hMutexAppRunning != NULL
1727 && GetLastError() == ERROR_ALREADY_EXISTS)
1728 {
1729 CloseHandle(hMutexAppRunning); /* close it so we don't keep it open while showing the error message. */
1730 return ShowError("Another installer is already running");
1731 }
1732
1733#ifdef VBOX_STUB_WITH_SPLASH
1734 /* Only show the splash screen if we're *not* in silent mode. */
1735 PVBOXSPLASHSCREENCTX pSplashCtx = NULL;
1736 if (!g_fSilent)
1737 /* ignore rc, not fatal */ SplashScreenCreate(&pSplashCtx);
1738#endif
1739
1740/** @todo
1741 *
1742 * Split the remainder up in functions and simplify the code flow!!
1743 *
1744 * */
1745 RTEXITCODE rcExit = RTEXITCODE_SUCCESS;
1746 RTListInit(&g_TmpFiles);
1747
1748 /*
1749 * Create a random extraction directory in the temporary directory if none
1750 * was given by the user (see @bugref{10201}).
1751 */
1752 PSTUBCLEANUPREC pExtractDirRec = NULL; /* This also indicates that */
1753 if (szExtractPath[0] == '\0')
1754 {
1755 vrc = RTPathTemp(szExtractPath, sizeof(szExtractPath));
1756 if (RT_FAILURE(vrc))
1757 {
1758 CloseHandle(hMutexAppRunning); /* close it so we don't keep it open while showing the error message. */
1759 return ShowError("Failed to find temporary directory: %Rrc", vrc);
1760 }
1761 if (!fExtractOnly) /* Only use a random sub-dir if we extract + run (and not just extract). */
1762 {
1763 vrc = RTPathAppend(szExtractPath, sizeof(szExtractPath), "XXXXXXXXXXXXXXXXXXXXXXXX");
1764 if (RT_SUCCESS(vrc))
1765 /** @todo Need something that return a handle as well as a path. */
1766 vrc = RTDirCreateTemp(szExtractPath, 0700);
1767 if (RT_FAILURE(vrc))
1768 {
1769 CloseHandle(hMutexAppRunning); /* close it so we don't keep it open while showing the error message. */
1770 return ShowError("Failed to create extraction path: %Rrc", vrc);
1771 }
1772 pExtractDirRec = AddCleanupRec(szExtractPath, false /*fIsFile*/);
1773 }
1774 }
1775 RTPathChangeToDosSlashes(szExtractPath, true /* Force conversion. */); /* MSI requirement. */
1776
1777 /*
1778 * Create a console for output if we're in verbose mode.
1779 */
1780#ifdef VBOX_STUB_WITH_OWN_CONSOLE
1781 if (g_iVerbosity)
1782 {
1783 if (!AllocConsole())
1784 return ShowError("Unable to allocate console: LastError=%u\n", GetLastError());
1785
1786# ifdef IPRT_NO_CRT
1787 PRTSTREAM pNewStdOutErr = NULL;
1788 vrc = RTStrmOpen("CONOUT$", "a", &pNewStdOutErr);
1789 if (RT_SUCCESS(vrc))
1790 {
1791 RTStrmSetBufferingMode(pNewStdOutErr, RTSTRMBUFMODE_UNBUFFERED);
1792 g_pStdErr = pNewStdOutErr;
1793 g_pStdOut = pNewStdOutErr;
1794 }
1795# else
1796 freopen("CONOUT$", "w", stdout);
1797 setvbuf(stdout, NULL, _IONBF, 0);
1798 freopen("CONOUT$", "w", stderr);
1799# endif
1800 }
1801#endif /* VBOX_STUB_WITH_OWN_CONSOLE */
1802
1803 /* Convenience: Enable logging if a log file (via --log-file) is specified. */
1804 if ( !fEnableLogging
1805 && szMSILogFile[0] != '\0')
1806 fEnableLogging = true;
1807
1808 if ( fEnableLogging
1809 && szMSILogFile[0] == '\0') /* No log file explicitly specified? Use the extract path by default. */
1810 {
1811 vrc = RTStrCopy(szMSILogFile, sizeof(szMSILogFile), szExtractPath);
1812 if (RT_SUCCESS(vrc))
1813 vrc = RTPathAppend(szMSILogFile, sizeof(szMSILogFile), "VBoxInstallLog.txt");
1814 if (RT_FAILURE(vrc))
1815 return ShowError("Error creating MSI log file name, rc=%Rrc", vrc);
1816 }
1817
1818 VBOXSTUBPKGARCH const enmPkgArch = GetNativePackageArch();
1819
1820 if (g_iVerbosity)
1821 {
1822 RTPrintf("Host architecture : %s\n", GetPackageArchStr(enmPkgArch));
1823 RTPrintf("Extraction path : %s\n", szExtractPath);
1824 RTPrintf("Silent installation : %RTbool\n", g_fSilent);
1825#ifdef VBOX_WITH_CODE_SIGNING
1826 RTPrintf("Certificate installation : %RTbool\n", fEnableSilentCert);
1827#endif
1828 RTPrintf("Additional MSI parameters: %s\n", szMSIArgs[0] ? szMSIArgs : "<None>");
1829 RTPrintf("Logging to file : %s\n", szMSILogFile[0] ? szMSILogFile : "<None>");
1830 }
1831
1832 /*
1833 * 32-bit is not officially supported any more.
1834 */
1835 if ( !fExtractOnly
1836 && !g_fSilent
1837 && enmPkgArch == VBOXSTUBPKGARCH_X86)
1838 rcExit = ShowError("32-bit Windows hosts are not supported by this VirtualBox release.");
1839 else
1840 {
1841 /*
1842 * Read our manifest.
1843 */
1844 VBOXSTUBPKGHEADER const *pHeader = NULL;
1845 vrc = FindData("MANIFEST", (uint8_t const **)&pHeader, NULL);
1846 if (RT_SUCCESS(vrc))
1847 {
1848 /** @todo If we could, we should validate the header. Only the magic isn't
1849 * commonly defined, nor the version number... */
1850
1851 /*
1852 * Up to this point, we haven't done anything that requires any cleanup.
1853 * From here on, we do everything in functions so we can counter clean up.
1854 */
1855 rcExit = ExtractFiles(pHeader->cPackages, szExtractPath, fExtractOnly, &pExtractDirRec);
1856 if (rcExit == RTEXITCODE_SUCCESS)
1857 {
1858#ifdef VBOX_STUB_WITH_SPLASH
1859 /* Hide the splash right after extraction. */
1860 SplashScreenDestroy(pSplashCtx);
1861 pSplashCtx = NULL;
1862#endif
1863 if (fExtractOnly)
1864 ShowInfo("Files were extracted to: %s", szExtractPath);
1865 else
1866 {
1867 rcExit = CopyCustomDir(szExtractPath);
1868#ifdef VBOX_WITH_CODE_SIGNING
1869# ifdef VBOX_WITH_VBOX_LEGACY_TS_CA
1870 if (rcExit == RTEXITCODE_SUCCESS && fInstallTimestampCA)
1871 rcExit = InstallTimestampCA(fForceTimestampCaInstall);
1872# endif
1873 if (rcExit == RTEXITCODE_SUCCESS && fEnableSilentCert && g_fSilent)
1874 rcExit = InstallCertificates();
1875#endif
1876 unsigned iPackage = 0;
1877 while ( iPackage < pHeader->cPackages
1878 && (rcExit == RTEXITCODE_SUCCESS || rcExit == (RTEXITCODE)ERROR_SUCCESS_REBOOT_REQUIRED))
1879 {
1880 RTEXITCODE rcExit2 = ProcessPackage(iPackage, szMSIArgs, szMSILogFile[0] ? szMSILogFile : NULL);
1881 if (rcExit2 != RTEXITCODE_SUCCESS)
1882 rcExit = rcExit2;
1883 iPackage++;
1884 }
1885 }
1886 }
1887
1888 /*
1889 * Do cleanups unless we're only extracting (ignoring failures for now).
1890 */
1891 if (!fExtractOnly)
1892 {
1893 RTPathSetCurrent("..");
1894 CleanUp(!fEnableLogging && pExtractDirRec && !pExtractDirRec->fDontDelete ? szExtractPath : NULL);
1895 }
1896
1897 /* Free any left behind cleanup records (not strictly needed). */
1898 PSTUBCLEANUPREC pCur, pNext;
1899 RTListForEachSafe(&g_TmpFiles, pCur, pNext, STUBCLEANUPREC, ListEntry)
1900 {
1901 RTListNodeRemove(&pCur->ListEntry);
1902 RTMemFree(pCur);
1903 }
1904 }
1905 else
1906 rcExit = ShowError("Internal package error: Manifest not found (%Rrc)", vrc);
1907 }
1908
1909#if defined(_WIN32_WINNT) && _WIN32_WINNT >= 0x0501
1910# ifdef VBOX_STUB_WITH_OWN_CONSOLE
1911 if (g_iVerbosity)
1912 FreeConsole();
1913# endif /* VBOX_STUB_WITH_OWN_CONSOLE */
1914#endif
1915
1916#ifdef VBOX_STUB_WITH_SPLASH
1917 /* Make sure the splash screen got destroyed (if not already above). */
1918 SplashScreenDestroy(pSplashCtx);
1919 pSplashCtx = NULL;
1920#endif
1921
1922 /*
1923 * Release instance mutex just to be on the safe side.
1924 */
1925 if (hMutexAppRunning != NULL)
1926 CloseHandle(hMutexAppRunning);
1927
1928 return rcExit != (RTEXITCODE)ERROR_SUCCESS_REBOOT_REQUIRED || !fIgnoreReboot ? rcExit : RTEXITCODE_SUCCESS;
1929}
1930
1931#ifndef IPRT_NO_CRT
1932int WINAPI WinMain(HINSTANCE hInstance,
1933 HINSTANCE hPrevInstance,
1934 char *lpCmdLine,
1935 int nCmdShow)
1936{
1937 RT_NOREF(hInstance, hPrevInstance, lpCmdLine, nCmdShow);
1938 return main(__argc, __argv);
1939}
1940#endif
1941
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