VirtualBox

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

Last change on this file was 98103, checked in by vboxsync, 16 months ago

Copyright year updates by scm.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 57.7 KB
Line 
1/* $Id: VBoxStub.cpp 98103 2023-01-17 14:15:46Z vboxsync $ */
2/** @file
3 * VBoxStub - VirtualBox's Windows installer stub.
4 */
5
6/*
7 * Copyright (C) 2010-2023 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#include "VBoxStub.h"
64#include "../StubBld/VBoxStubBld.h"
65#include "resource.h"
66
67#ifdef VBOX_WITH_CODE_SIGNING
68# include "VBoxStubCertUtil.h"
69# include "VBoxStubPublicCert.h"
70#endif
71
72
73/*********************************************************************************************************************************
74* Defined Constants And Macros *
75*********************************************************************************************************************************/
76#define MY_UNICODE_SUB(str) L ##str
77#define MY_UNICODE(str) MY_UNICODE_SUB(str)
78
79/* Use an own console window if run in verbose mode. */
80#define VBOX_STUB_WITH_OWN_CONSOLE
81
82
83/*********************************************************************************************************************************
84* Structures and Typedefs *
85*********************************************************************************************************************************/
86/**
87 * Cleanup record.
88 */
89typedef struct STUBCLEANUPREC
90{
91 /** List entry. */
92 RTLISTNODE ListEntry;
93 /** Stub package index (zero-based) this record belongs to. */
94 unsigned idxPkg;
95 /** True if file, false if directory. */
96 bool fFile;
97 /** Set if we should not delete the file/directory.
98 * This is used for user supplied extraction directories. */
99 bool fDontDelete;
100 union
101 {
102 /** File handle (if \a fFile is \c true). */
103 RTFILE hFile;
104 /** Directory handle (if \a fFile is \c false). */
105 RTDIR hDir;
106 };
107 /** The path to the file or directory to clean up. */
108 char szPath[1];
109} STUBCLEANUPREC;
110/** Pointer to a cleanup record. */
111typedef STUBCLEANUPREC *PSTUBCLEANUPREC;
112
113
114/*********************************************************************************************************************************
115* Prototypes *
116*********************************************************************************************************************************/
117static PSTUBCLEANUPREC AddCleanupRec(const char *pszPath, bool fIsFile);
118
119
120/*********************************************************************************************************************************
121* Global Variables *
122*********************************************************************************************************************************/
123/** Whether it's a silent or interactive GUI driven install. */
124static bool g_fSilent = false;
125/** List of temporary files. */
126static RTLISTANCHOR g_TmpFiles;
127/** Verbosity flag. */
128static int g_iVerbosity = 0;
129
130
131
132/**
133 * Shows an error message box with a printf() style formatted string.
134 *
135 * @returns RTEXITCODE_FAILURE
136 * @param pszFmt Printf-style format string to show in the message box body.
137 *
138 */
139static RTEXITCODE ShowError(const char *pszFmt, ...)
140{
141 char *pszMsg;
142 va_list va;
143
144 va_start(va, pszFmt);
145 if (RTStrAPrintfV(&pszMsg, pszFmt, va))
146 {
147 if (g_fSilent)
148 RTMsgError("%s", pszMsg);
149 else
150 {
151 PRTUTF16 pwszMsg;
152 int rc = RTStrToUtf16(pszMsg, &pwszMsg);
153 if (RT_SUCCESS(rc))
154 {
155 MessageBoxW(GetDesktopWindow(), pwszMsg, MY_UNICODE(VBOX_STUB_TITLE), MB_ICONERROR);
156 RTUtf16Free(pwszMsg);
157 }
158 else
159 MessageBoxA(GetDesktopWindow(), pszMsg, VBOX_STUB_TITLE, MB_ICONERROR);
160 }
161 RTStrFree(pszMsg);
162 }
163 else /* Should never happen! */
164 AssertMsgFailed(("Failed to format error text of format string: %s!\n", pszFmt));
165 va_end(va);
166 return RTEXITCODE_FAILURE;
167}
168
169
170/**
171 * Same as ShowError, only it returns RTEXITCODE_SYNTAX.
172 */
173static RTEXITCODE ShowSyntaxError(const char *pszFmt, ...)
174{
175 va_list va;
176 va_start(va, pszFmt);
177 ShowError("%N", pszFmt, &va);
178 va_end(va);
179 return RTEXITCODE_SYNTAX;
180}
181
182
183/**
184 * Shows a message box with a printf() style formatted string.
185 *
186 * @param uType Type of the message box (see MSDN).
187 * @param pszFmt Printf-style format string to show in the message box body.
188 *
189 */
190static void ShowInfo(const char *pszFmt, ...)
191{
192 char *pszMsg;
193 va_list va;
194 va_start(va, pszFmt);
195 int rc = RTStrAPrintfV(&pszMsg, pszFmt, va);
196 va_end(va);
197 if (rc >= 0)
198 {
199 if (g_fSilent)
200 RTPrintf("%s\n", pszMsg);
201 else
202 {
203 PRTUTF16 pwszMsg;
204 rc = RTStrToUtf16(pszMsg, &pwszMsg);
205 if (RT_SUCCESS(rc))
206 {
207 MessageBoxW(GetDesktopWindow(), pwszMsg, MY_UNICODE(VBOX_STUB_TITLE), MB_ICONINFORMATION);
208 RTUtf16Free(pwszMsg);
209 }
210 else
211 MessageBoxA(GetDesktopWindow(), pszMsg, VBOX_STUB_TITLE, MB_ICONINFORMATION);
212 }
213 }
214 else /* Should never happen! */
215 AssertMsgFailed(("Failed to format error text of format string: %s!\n", pszFmt));
216 RTStrFree(pszMsg);
217}
218
219
220/** Logs error details to stderr. */
221static void LogError(const char *pszFmt, ...)
222{
223 va_list va;
224 va_start(va, pszFmt);
225 RTStrmPrintf(g_pStdErr, "error: %N\n", pszFmt, &va);
226 va_end(va);
227}
228
229
230/** Logs error details to stderr, returning @a rc. */
231static int LogErrorRc(int rc, 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 return rc;
238}
239
240
241/** Logs error details to stderr, RTEXITCODE_FAILURE. */
242static RTEXITCODE LogErrorExitFailure(const char *pszFmt, ...)
243{
244 va_list va;
245 va_start(va, pszFmt);
246 RTStrmPrintf(g_pStdErr, "error: %N\n", pszFmt, &va);
247 va_end(va);
248 return RTEXITCODE_FAILURE;
249}
250
251
252/**
253 * Finds the specified in the resource section of the executable.
254 *
255 * @returns IPRT status code.
256 *
257 * @param pszDataName Name of resource to read.
258 * @param ppbResource Where to return the pointer to the data.
259 * @param pcbResource Where to return the size of the data (if found).
260 * Optional.
261 */
262static int FindData(const char *pszDataName, uint8_t const **ppbResource, DWORD *pcbResource)
263{
264 AssertReturn(pszDataName, VERR_INVALID_PARAMETER);
265 HINSTANCE hInst = NULL; /* indicates the executable image */
266
267 /* Find our resource. */
268 PRTUTF16 pwszDataName;
269 int rc = RTStrToUtf16(pszDataName, &pwszDataName);
270 AssertRCReturn(rc, rc);
271 HRSRC hRsrc = FindResourceExW(hInst,
272 (LPWSTR)RT_RCDATA,
273 pwszDataName,
274 MAKELANGID(LANG_NEUTRAL, SUBLANG_NEUTRAL));
275 RTUtf16Free(pwszDataName);
276 AssertReturn(hRsrc, VERR_IO_GEN_FAILURE);
277
278 /* Get resource size. */
279 DWORD cb = SizeofResource(hInst, hRsrc);
280 AssertReturn(cb > 0, VERR_NO_DATA);
281 if (pcbResource)
282 *pcbResource = cb;
283
284 /* Get pointer to resource. */
285 HGLOBAL hData = LoadResource(hInst, hRsrc);
286 AssertReturn(hData, VERR_IO_GEN_FAILURE);
287
288 /* Lock resource. */
289 *ppbResource = (uint8_t const *)LockResource(hData);
290 AssertReturn(*ppbResource, VERR_IO_GEN_FAILURE);
291 return VINF_SUCCESS;
292}
293
294
295/**
296 * Finds the header for the given package.
297 *
298 * @returns Pointer to the package header on success. On failure NULL is
299 * returned after ShowError has been invoked.
300 * @param iPackage The package number.
301 */
302static const VBOXSTUBPKG *FindPackageHeader(unsigned iPackage)
303{
304 char szHeaderName[32];
305 RTStrPrintf(szHeaderName, sizeof(szHeaderName), "HDR_%02d", iPackage);
306
307 VBOXSTUBPKG const *pPackage;
308 int rc = FindData(szHeaderName, (uint8_t const **)&pPackage, NULL);
309 if (RT_FAILURE(rc))
310 {
311 ShowError("Internal error: Could not find package header #%u: %Rrc", iPackage, rc);
312 return NULL;
313 }
314
315 /** @todo validate it. */
316 return pPackage;
317}
318
319
320
321/**
322 * Constructs a full temporary file path from the given parameters.
323 *
324 * @returns iprt status code.
325 *
326 * @param pszTempPath The pure path to use for construction.
327 * @param pszTargetFileName The pure file name to use for construction.
328 * @param ppszTempFile Pointer to the constructed string. Must be freed
329 * using RTStrFree().
330 */
331static int GetTempFileAlloc(const char *pszTempPath,
332 const char *pszTargetFileName,
333 char **ppszTempFile)
334{
335 if (RTStrAPrintf(ppszTempFile, "%s\\%s", pszTempPath, pszTargetFileName) >= 0)
336 return VINF_SUCCESS;
337 return VERR_NO_STR_MEMORY;
338}
339
340
341/**
342 * Extracts a built-in resource to disk.
343 *
344 * @returns iprt status code.
345 *
346 * @param pszResourceName The resource name to extract.
347 * @param pszTempFile The full file path + name to extract the resource to.
348 * @param hFile Handle to pszTempFile if RTFileCreateUnique was
349 * used to generate the name, otherwise NIL_RTFILE.
350 * @param idxPackage The package index for annotating the cleanup
351 * record with (HACK ALERT).
352 */
353static int ExtractFile(const char *pszResourceName, const char *pszTempFile, RTFILE hFile, unsigned idxPackage)
354{
355 AssertPtrReturn(pszResourceName, VERR_INVALID_POINTER);
356 AssertPtrReturn(pszTempFile, VERR_INVALID_POINTER);
357
358 /* Create new (and replace any old) file. */
359 if (hFile == NIL_RTFILE)
360 {
361 int rc = RTFileOpen(&hFile, pszTempFile,
362 RTFILE_O_CREATE_REPLACE | RTFILE_O_WRITE | RTFILE_O_DENY_WRITE
363 | (0700 << RTFILE_O_CREATE_MODE_SHIFT));
364 AssertRCReturn(rc, LogErrorRc(rc, "#%u: Failed to create/replace '%s' for writing: %Rrc", idxPackage, pszTempFile, rc));
365 }
366
367 /* Add a cleanup record, so that we can properly clean up (partially run) stuff. */
368 int rc = VERR_NO_MEMORY;
369 PSTUBCLEANUPREC pCleanupRec = AddCleanupRec(pszTempFile, true /*fIsFile*/);
370 AssertReturn(pCleanupRec, VERR_NO_MEMORY);
371
372 pCleanupRec->idxPkg = idxPackage;
373 pCleanupRec->hFile = hFile;
374
375 /* Find the data of the built-in resource. */
376 uint8_t const *pbData = NULL;
377 DWORD cbData = 0;
378 rc = FindData(pszResourceName, &pbData, &cbData);
379 AssertRCReturn(rc, LogErrorRc(rc, "#%u: Failed to locate resource '%s': %Rrc", idxPackage, pszResourceName, rc));
380
381 /* Write the contents to the file. */
382 rc = RTFileWrite(hFile, pbData, cbData, NULL);
383 AssertRCReturn(rc, LogErrorRc(rc, "#%u: RTFileWrite('%s',, %#x,) failed: %Rrc", idxPackage, pszTempFile, cbData, rc));
384
385 /*
386 * We now wish to keep the file open, however since we've got it open in write
387 * mode with deny-write sharing (effectively exclusive write mode) this will
388 * prevent the MSI API from opening it in deny-write mode for reading purposes.
389 *
390 * So we have to do the best we can to transition this to a read-only handle
391 * that denies write (and deletion/renaming). First we open it again in
392 * read-only mode only denying deletion, not writing. Then close the original
393 * handle. Finally open a read-only handle that denies both reading and
394 * deletion/renaming, and verify that the file content is still the same.
395 *
396 * Note! DuplicateHandle to read-only and closing the original does not work,
397 * as the kernel doesn't update the sharing access info for the handles.
398 */
399 RTFSOBJINFO ObjInfo1;
400 rc = RTFileQueryInfo(hFile, &ObjInfo1, RTFSOBJATTRADD_UNIX);
401 AssertRCReturn(rc, LogErrorRc(rc, "#%u: RTFileQueryInfo failed on '%s': %Rrc", idxPackage, pszTempFile, rc));
402
403 RTFILE hFile2 = NIL_RTFILE;
404 rc = RTFileOpen(&hFile2, pszTempFile,
405 RTFILE_O_OPEN | RTFILE_O_READ | RTFILE_O_DENY_NONE | (0700 << RTFILE_O_CREATE_MODE_SHIFT));
406 AssertRCReturn(rc, LogErrorRc(rc, "#%u: First re-opening of '%s' failed: %Rrc", idxPackage, pszTempFile, rc));
407
408 rc = RTFileClose(hFile);
409 AssertRCReturnStmt(rc, RTFileClose(hFile2),
410 LogErrorRc(rc, "#%u: RTFileClose('%s') failed: %Rrc", idxPackage, pszTempFile, rc));
411 pCleanupRec->hFile = hFile2;
412
413 rc = RTFileOpen(&hFile, pszTempFile, RTFILE_O_OPEN | RTFILE_O_READ | RTFILE_O_DENY_WRITE);
414 AssertRCReturn(rc, LogErrorRc(rc, "#%u: Second re-opening of '%s' failed: %Rrc", idxPackage, pszTempFile, rc));
415 pCleanupRec->hFile = hFile;
416
417 rc = RTFileClose(hFile2);
418 AssertRCStmt(rc, LogError("#%u: Failed to close 2nd handle to '%s': %Rrc", idxPackage, pszTempFile, rc));
419
420 /* check the size and inode number. */
421 RTFSOBJINFO ObjInfo2;
422 rc = RTFileQueryInfo(hFile, &ObjInfo2, RTFSOBJATTRADD_UNIX);
423 AssertRCReturn(rc, LogErrorRc(rc, "#%u: RTFileQueryInfo failed on '%s': %Rrc", idxPackage, pszTempFile, rc));
424
425 AssertReturn(ObjInfo2.cbObject == cbData,
426 LogErrorRc(VERR_STATE_CHANGED, "#%u: File size of '%s' changed: %'RU64, expected %'RU32",
427 idxPackage, pszTempFile, ObjInfo2.cbObject, pbData));
428
429 AssertReturn(ObjInfo2.Attr.u.Unix.INodeId == ObjInfo1.Attr.u.Unix.INodeId,
430 LogErrorRc(VERR_STATE_CHANGED, "#%u: File ID of '%s' changed: %#RX64, expected %#RX64",
431 idxPackage, pszTempFile, ObjInfo2.Attr.u.Unix.INodeId, ObjInfo1.Attr.u.Unix.INodeId));
432
433
434 /* Check the content. */
435 uint32_t off = 0;
436 while (off < cbData)
437 {
438 uint8_t abBuf[_64K];
439 size_t cbToRead = RT_MIN(cbData - off, sizeof(abBuf));
440 rc = RTFileRead(hFile, abBuf, cbToRead, NULL);
441 AssertRCReturn(rc, LogErrorRc(rc, "#%u: RTFileRead failed on '%s' at offset %#RX32: %Rrc",
442 idxPackage, pszTempFile, off, rc));
443 AssertReturn(memcmp(abBuf, &pbData[off], cbToRead) == 0,
444 LogErrorRc(VERR_STATE_CHANGED, "#%u: File '%s' has change (mismatch in %#zx byte block at %#RX32)",
445 idxPackage, pszTempFile, cbToRead, off));
446 off += cbToRead;
447 }
448
449 return VINF_SUCCESS;
450}
451
452
453/**
454 * Extracts a built-in resource to disk.
455 *
456 * @returns iprt status code.
457 *
458 * @param pPackage Pointer to a VBOXSTUBPKG struct that contains the resource.
459 * @param pszTempFile The full file path + name to extract the resource to.
460 * @param hFile Handle to pszTempFile if RTFileCreateUnique was
461 * used to generate the name, otherwise NIL_RTFILE.
462 * @param idxPackage The package index for annotating the cleanup
463 * record with (HACK ALERT).
464 */
465static int Extract(VBOXSTUBPKG const *pPackage, const char *pszTempFile, RTFILE hFile, unsigned idxPackage)
466{
467 return ExtractFile(pPackage->szResourceName, pszTempFile, hFile, idxPackage);
468}
469
470
471/**
472 * Detects whether we're running on a 32- or 64-bit platform and returns the result.
473 *
474 * @returns TRUE if we're running on a 64-bit OS, FALSE if not.
475 */
476static BOOL IsWow64(void)
477{
478 BOOL fIsWow64 = TRUE;
479 fnIsWow64Process = (LPFN_ISWOW64PROCESS)GetProcAddress(GetModuleHandle(TEXT("kernel32")), "IsWow64Process");
480 if (NULL != fnIsWow64Process)
481 {
482 if (!fnIsWow64Process(GetCurrentProcess(), &fIsWow64))
483 {
484 /* Error in retrieving process type - assume that we're running on 32bit. */
485 return FALSE;
486 }
487 }
488 return fIsWow64;
489}
490
491
492/**
493 * Decides whether we need a specified package to handle or not.
494 *
495 * @returns @c true if we need to handle the specified package, @c false if not.
496 *
497 * @param pPackage Pointer to a VBOXSTUBPKG struct that contains the resource.
498 */
499static bool PackageIsNeeded(VBOXSTUBPKG const *pPackage)
500{
501 if (pPackage->enmArch == VBOXSTUBPKGARCH_ALL)
502 return true;
503 VBOXSTUBPKGARCH enmArch = IsWow64() ? VBOXSTUBPKGARCH_AMD64 : VBOXSTUBPKGARCH_X86;
504 return pPackage->enmArch == enmArch;
505}
506
507
508/**
509 * Adds a cleanup record.
510 *
511 * The caller must set the hFile or hDir if so desired.
512 *
513 * @returns Pointer to the cleanup record on success, fully complained NULL on
514 * failure.
515 * @param pszPath The path to the file or directory to clean up.
516 * @param fIsFile @c true if file, @c false if directory.
517 */
518static PSTUBCLEANUPREC AddCleanupRec(const char *pszPath, bool fIsFile)
519{
520 size_t cchPath = strlen(pszPath); Assert(cchPath > 0);
521 PSTUBCLEANUPREC pRec = (PSTUBCLEANUPREC)RTMemAllocZ(RT_UOFFSETOF_DYN(STUBCLEANUPREC, szPath[cchPath + 1]));
522 if (pRec)
523 {
524 pRec->idxPkg = ~0U;
525 pRec->fFile = fIsFile;
526 if (fIsFile)
527 pRec->hFile = NIL_RTFILE;
528 else
529 pRec->hDir = NIL_RTDIR;
530 memcpy(pRec->szPath, pszPath, cchPath + 1);
531
532 RTListPrepend(&g_TmpFiles, &pRec->ListEntry);
533 }
534 else
535 ShowError("Out of memory!");
536 return pRec;
537}
538
539
540/**
541 * Cleans up all the extracted files and optionally removes the package
542 * directory.
543 *
544 * @param pszPkgDir The package directory, NULL if it shouldn't be
545 * removed.
546 */
547static void CleanUp(const char *pszPkgDir)
548{
549 for (int i = 0; i < 5; i++)
550 {
551 bool const fFinalTry = i == 4;
552
553 PSTUBCLEANUPREC pCur, pNext;
554 RTListForEachSafe(&g_TmpFiles, pCur, pNext, STUBCLEANUPREC, ListEntry)
555 {
556 int rc = VINF_SUCCESS;
557 if (pCur->fFile)
558 {
559 if (pCur->hFile != NIL_RTFILE)
560 {
561 if (RTFileIsValid(pCur->hFile))
562 {
563 int rcCloseFile = RTFileClose(pCur->hFile);
564 AssertRCStmt(rcCloseFile, LogError("Cleanup file '%s' for #%u: RTFileClose(%p) failed: %Rrc",
565 pCur->szPath, pCur->idxPkg, pCur->hFile, rcCloseFile));
566 }
567 pCur->hFile = NIL_RTFILE;
568 }
569 if (!pCur->fDontDelete)
570 rc = RTFileDelete(pCur->szPath);
571 }
572 else /* Directory */
573 {
574 if (pCur->hDir != NIL_RTDIR)
575 {
576 if (RTDirIsValid(pCur->hDir))
577 {
578 int rcCloseDir = RTDirClose(pCur->hDir);
579 AssertRCStmt(rcCloseDir, LogError("Cleanup dir '%s' for #%u: RTDirClose(%p) failed: %Rrc",
580 pCur->szPath, pCur->idxPkg, pCur->hDir, rcCloseDir));
581 }
582 pCur->hDir = NIL_RTDIR;
583 }
584
585 /* Note: Not removing the directory recursively, as we should have separate cleanup records for that. */
586 if (!pCur->fDontDelete)
587 {
588 rc = RTDirRemove(pCur->szPath);
589 if (rc == VERR_DIR_NOT_EMPTY && fFinalTry)
590 rc = VINF_SUCCESS;
591 }
592 }
593 if (rc == VERR_FILE_NOT_FOUND || rc == VERR_PATH_NOT_FOUND)
594 rc = VINF_SUCCESS;
595 if (RT_SUCCESS(rc))
596 {
597 RTListNodeRemove(&pCur->ListEntry);
598 RTMemFree(pCur);
599 }
600 else if (fFinalTry)
601 {
602 if (pCur->fFile)
603 ShowError("Failed to delete temporary file '%s': %Rrc", pCur->szPath, rc);
604 else
605 ShowError("Failed to delete temporary directory '%s': %Rrc", pCur->szPath, rc);
606 }
607 }
608
609 if (RTListIsEmpty(&g_TmpFiles) || fFinalTry)
610 {
611 if (!pszPkgDir)
612 return;
613 int rc = RTDirRemove(pszPkgDir);
614 if (RT_SUCCESS(rc) || rc == VERR_FILE_NOT_FOUND || rc == VERR_PATH_NOT_FOUND || fFinalTry)
615 return;
616 }
617
618 /* Delay a little and try again. */
619 RTThreadSleep(i == 0 ? 100 : 3000);
620 }
621}
622
623
624/**
625 * Processes an MSI package.
626 *
627 * @returns Fully complained exit code.
628 * @param pszMsi The path to the MSI to process.
629 * @param pszMsiArgs Any additional installer (MSI) argument
630 * @param pszMsiLogFile Where to let MSI log its output to. NULL if logging is disabled.
631 */
632static RTEXITCODE ProcessMsiPackage(const char *pszMsi, const char *pszMsiArgs, const char *pszMsiLogFile)
633{
634 int rc;
635
636 /*
637 * Set UI level.
638 */
639 INSTALLUILEVEL enmDesiredUiLevel = g_fSilent ? INSTALLUILEVEL_NONE : INSTALLUILEVEL_FULL;
640 INSTALLUILEVEL enmRet = MsiSetInternalUI(enmDesiredUiLevel, NULL);
641 if (enmRet == INSTALLUILEVEL_NOCHANGE /* means error */)
642 return ShowError("Internal error: MsiSetInternalUI failed.");
643
644 /*
645 * Enable logging?
646 */
647 if (pszMsiLogFile)
648 {
649 PRTUTF16 pwszLogFile;
650 rc = RTStrToUtf16(pszMsiLogFile, &pwszLogFile);
651 if (RT_FAILURE(rc))
652 return ShowError("RTStrToUtf16 failed on '%s': %Rrc", pszMsiLogFile, rc);
653
654 UINT uLogLevel = MsiEnableLogW(INSTALLLOGMODE_VERBOSE,
655 pwszLogFile,
656 INSTALLLOGATTRIBUTES_FLUSHEACHLINE);
657 RTUtf16Free(pwszLogFile);
658 if (uLogLevel != ERROR_SUCCESS)
659 return ShowError("MsiEnableLogW failed");
660 }
661
662 /*
663 * Initialize the common controls (extended version). This is necessary to
664 * run the actual .MSI installers with the new fancy visual control
665 * styles (XP+). Also, an integrated manifest is required.
666 */
667 INITCOMMONCONTROLSEX ccEx;
668 ccEx.dwSize = sizeof(INITCOMMONCONTROLSEX);
669 ccEx.dwICC = ICC_LINK_CLASS | ICC_LISTVIEW_CLASSES | ICC_PAGESCROLLER_CLASS |
670 ICC_PROGRESS_CLASS | ICC_STANDARD_CLASSES | ICC_TAB_CLASSES | ICC_TREEVIEW_CLASSES |
671 ICC_UPDOWN_CLASS | ICC_USEREX_CLASSES | ICC_WIN95_CLASSES;
672 InitCommonControlsEx(&ccEx); /* Ignore failure. */
673
674 /*
675 * Convert both strings to UTF-16 and start the installation.
676 */
677 PRTUTF16 pwszMsi;
678 rc = RTStrToUtf16(pszMsi, &pwszMsi);
679 if (RT_FAILURE(rc))
680 return ShowError("RTStrToUtf16 failed on '%s': %Rrc", pszMsi, rc);
681 PRTUTF16 pwszMsiArgs;
682 rc = RTStrToUtf16(pszMsiArgs, &pwszMsiArgs);
683 if (RT_FAILURE(rc))
684 {
685 RTUtf16Free(pwszMsi);
686 return ShowError("RTStrToUtf16 failed on '%s': %Rrc", pszMsiArgs, rc);
687 }
688
689 UINT uStatus = MsiInstallProductW(pwszMsi, pwszMsiArgs);
690 RTUtf16Free(pwszMsi);
691 RTUtf16Free(pwszMsiArgs);
692
693 if (uStatus == ERROR_SUCCESS)
694 return RTEXITCODE_SUCCESS;
695 if (uStatus == ERROR_SUCCESS_REBOOT_REQUIRED)
696 {
697 if (g_fSilent)
698 RTMsgInfo("Reboot required (by %s)\n", pszMsi);
699 return (RTEXITCODE)uStatus;
700 }
701
702 /*
703 * Installation failed. Figure out what to say.
704 */
705 switch (uStatus)
706 {
707 case ERROR_INSTALL_USEREXIT:
708 /* Don't say anything? */
709 break;
710
711 case ERROR_INSTALL_PACKAGE_VERSION:
712 ShowError("This installation package cannot be installed by the Windows Installer service.\n"
713 "You must install a Windows service pack that contains a newer version of the Windows Installer service.");
714 break;
715
716 case ERROR_INSTALL_PLATFORM_UNSUPPORTED:
717 ShowError("This installation package is not supported on this platform.");
718 break;
719
720 default:
721 {
722 /*
723 * Try get windows to format the message.
724 */
725 DWORD dwFormatFlags = FORMAT_MESSAGE_ALLOCATE_BUFFER
726 | FORMAT_MESSAGE_IGNORE_INSERTS
727 | FORMAT_MESSAGE_FROM_SYSTEM;
728 HMODULE hModule = NULL;
729 if (uStatus >= NERR_BASE && uStatus <= MAX_NERR)
730 {
731 hModule = LoadLibraryExW(L"netmsg.dll",
732 NULL,
733 LOAD_LIBRARY_AS_DATAFILE);
734 if (hModule != NULL)
735 dwFormatFlags |= FORMAT_MESSAGE_FROM_HMODULE;
736 }
737
738 PWSTR pwszMsg;
739 if (FormatMessageW(dwFormatFlags,
740 hModule, /* If NULL, load system stuff. */
741 uStatus,
742 MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
743 (PWSTR)&pwszMsg,
744 0,
745 NULL) > 0)
746 {
747 ShowError("Installation failed! Error: %ls", pwszMsg);
748 LocalFree(pwszMsg);
749 }
750 else /* If text lookup failed, show at least the error number. */
751 ShowError("Installation failed! Error: %u", uStatus);
752
753 if (hModule)
754 FreeLibrary(hModule);
755 break;
756 }
757 }
758
759 return RTEXITCODE_FAILURE;
760}
761
762
763/**
764 * Processes a package.
765 *
766 * @returns Fully complained exit code.
767 * @param iPackage The package number.
768 * @param pszMsiArgs Any additional installer (MSI) argument
769 * @param pszMsiLogFile Where to let MSI log its output to. NULL if logging is disabled.
770 */
771static RTEXITCODE ProcessPackage(unsigned iPackage, const char *pszMsiArgs, const char *pszMsiLogFile)
772{
773 /*
774 * Get the package header and check if it's needed.
775 */
776 VBOXSTUBPKG const * const pPackage = FindPackageHeader(iPackage);
777 if (pPackage == NULL)
778 return RTEXITCODE_FAILURE;
779
780 if (!PackageIsNeeded(pPackage))
781 return RTEXITCODE_SUCCESS;
782
783 /*
784 * Get the cleanup record for the package so we can get the extracted
785 * filename (pPackage is read-only and thus cannot assist here).
786 */
787 PSTUBCLEANUPREC pRec = NULL;
788 PSTUBCLEANUPREC pCur;
789 RTListForEach(&g_TmpFiles, pCur, STUBCLEANUPREC, ListEntry)
790 {
791 if (pCur->idxPkg == iPackage)
792 {
793 pRec = pCur;
794 break;
795 }
796 }
797 AssertReturn(pRec != NULL, LogErrorExitFailure("Package #%u not found in cleanup records", iPackage));
798
799 /*
800 * Deal with the file based on it's extension.
801 */
802 RTPathChangeToDosSlashes(pRec->szPath, true /* Force conversion. */); /* paranoia */
803
804 RTEXITCODE rcExit;
805 const char *pszSuff = RTPathSuffix(pRec->szPath);
806 if (RTStrICmpAscii(pszSuff, ".msi") == 0)
807 rcExit = ProcessMsiPackage(pRec->szPath, pszMsiArgs, pszMsiLogFile);
808 else if (RTStrICmpAscii(pszSuff, ".cab") == 0)
809 rcExit = RTEXITCODE_SUCCESS; /* Ignore .cab files, they're generally referenced by other files. */
810 else
811 rcExit = ShowError("Internal error: Do not know how to handle file '%s' (%s).", pPackage->szFilename, pRec->szPath);
812 return rcExit;
813}
814
815#ifdef VBOX_WITH_CODE_SIGNING
816
817# ifdef VBOX_WITH_VBOX_LEGACY_TS_CA
818/**
819 * Install the timestamp CA currently needed to support legacy Windows versions.
820 *
821 * See @bugref{8691} for details.
822 *
823 * @returns Fully complained exit code.
824 */
825static RTEXITCODE InstallTimestampCA(bool fForce)
826{
827 /*
828 * Windows 10 desktop should be fine with attestation signed drivers, however
829 * the driver guard (DG) may alter that. Not sure yet how to detect, but
830 * OTOH 1809 and later won't accept the SHA-1 stuff regardless, so out of
831 * options there.
832 *
833 * The Windows 2016 server and later is not fine with attestation signed
834 * drivers, so we need to do the legacy trick there.
835 */
836 if ( !fForce
837 && RTSystemGetNtVersion() >= RTSYSTEM_MAKE_NT_VERSION(10, 0, 0)
838 && RTSystemGetNtProductType() == VER_NT_WORKSTATION)
839 return RTEXITCODE_SUCCESS;
840
841 if (!addCertToStore(CERT_SYSTEM_STORE_LOCAL_MACHINE, "Root", g_abVBoxLegacyWinCA, sizeof(g_abVBoxLegacyWinCA)))
842 return ShowError("Failed add the legacy Windows timestamp CA to the root certificate store.");
843 return RTEXITCODE_SUCCESS;
844}
845# endif /* VBOX_WITH_VBOX_LEGACY_TS_CA*/
846
847/**
848 * Install the public certificate into TrustedPublishers so the installer won't
849 * prompt the user during silent installs.
850 *
851 * @returns Fully complained exit code.
852 */
853static RTEXITCODE InstallCertificates(void)
854{
855 for (uint32_t i = 0; i < RT_ELEMENTS(g_aVBoxStubTrustedCerts); i++)
856 {
857 if (!addCertToStore(CERT_SYSTEM_STORE_LOCAL_MACHINE,
858 "TrustedPublisher",
859 g_aVBoxStubTrustedCerts[i].pab,
860 g_aVBoxStubTrustedCerts[i].cb))
861 return ShowError("Failed to add our certificate(s) to trusted publisher store.");
862 }
863 return RTEXITCODE_SUCCESS;
864}
865
866#endif /* VBOX_WITH_CODE_SIGNING */
867
868/**
869 * Copies the "<exepath>.custom" directory to the extraction path if it exists.
870 *
871 * This is used by the MSI packages from the resource section.
872 *
873 * @returns Fully complained exit code.
874 * @param pszDstDir The destination directory.
875 */
876static RTEXITCODE CopyCustomDir(const char *pszDstDir)
877{
878 char szSrcDir[RTPATH_MAX];
879 int rc = RTPathExecDir(szSrcDir, sizeof(szSrcDir));
880 if (RT_SUCCESS(rc))
881 rc = RTPathAppend(szSrcDir, sizeof(szSrcDir), ".custom");
882 if (RT_FAILURE(rc))
883 return ShowError("Failed to construct '.custom' dir path: %Rrc", rc);
884
885 if (RTDirExists(szSrcDir))
886 {
887 /*
888 * Use SHFileOperation w/ FO_COPY to do the job. This API requires an
889 * extra zero at the end of both source and destination paths.
890 */
891 size_t cwc;
892 RTUTF16 wszSrcDir[RTPATH_MAX + 1];
893 PRTUTF16 pwszSrcDir = wszSrcDir;
894 rc = RTStrToUtf16Ex(szSrcDir, RTSTR_MAX, &pwszSrcDir, RTPATH_MAX, &cwc);
895 if (RT_FAILURE(rc))
896 return ShowError("RTStrToUtf16Ex failed on '%s': %Rrc", szSrcDir, rc);
897 wszSrcDir[cwc] = '\0';
898
899 RTUTF16 wszDstDir[RTPATH_MAX + 1];
900 PRTUTF16 pwszDstDir = wszSrcDir;
901 rc = RTStrToUtf16Ex(pszDstDir, RTSTR_MAX, &pwszDstDir, RTPATH_MAX, &cwc);
902 if (RT_FAILURE(rc))
903 return ShowError("RTStrToUtf16Ex failed on '%s': %Rrc", pszDstDir, rc);
904 wszDstDir[cwc] = '\0';
905
906 SHFILEOPSTRUCTW FileOp;
907 RT_ZERO(FileOp); /* paranoia */
908 FileOp.hwnd = NULL;
909 FileOp.wFunc = FO_COPY;
910 FileOp.pFrom = wszSrcDir;
911 FileOp.pTo = wszDstDir;
912 FileOp.fFlags = FOF_SILENT
913 | FOF_NOCONFIRMATION
914 | FOF_NOCONFIRMMKDIR
915 | FOF_NOERRORUI;
916 FileOp.fAnyOperationsAborted = FALSE;
917 FileOp.hNameMappings = NULL;
918 FileOp.lpszProgressTitle = NULL;
919
920 rc = SHFileOperationW(&FileOp);
921 if (rc != 0) /* Not a Win32 status code! */
922 return ShowError("Copying the '.custom' dir failed: %#x", rc);
923
924 /*
925 * Add a cleanup record for recursively deleting the destination
926 * .custom directory. We should actually add this prior to calling
927 * SHFileOperationW since it may partially succeed...
928 */
929 char *pszDstSubDir = RTPathJoinA(pszDstDir, ".custom");
930 if (!pszDstSubDir)
931 return ShowError("Out of memory!");
932
933 PSTUBCLEANUPREC pCleanupRec = AddCleanupRec(pszDstSubDir, false /*fIsFile*/);
934 AssertReturn(pCleanupRec, RTEXITCODE_FAILURE);
935
936 /*
937 * Open the directory to make it difficult to replace or delete (see @bugref{10201}).
938 */
939 /** @todo this is still race prone, given that SHFileOperationW is the one
940 * creating it and we're really a bit late opening it here. Anyway,
941 * it's harmless as this code isn't used at present. */
942 RTDIR hDstSubDir;
943 rc = RTDirOpen(&hDstSubDir, pszDstSubDir);
944 if (RT_FAILURE(rc))
945 return ShowError("Unable to open the destination .custom directory: %Rrc", rc);
946 pCleanupRec->hDir = hDstSubDir;
947
948 RTStrFree(pszDstSubDir);
949 }
950
951 return RTEXITCODE_SUCCESS;
952}
953
954
955/**
956 * Extracts the files for all needed packages to @a pszDstDir.
957 *
958 * @returns
959 * @param cPackages Number of packages to consinder.
960 * @param pszDstDir Where to extract the files.
961 * @param fExtractOnly Set if only extracting and not doing any installing.
962 * @param ppExtractDirRec Where we keep the cleanup record for @a pszDstDir.
963 * This may have been created by the caller already.
964 */
965static RTEXITCODE ExtractFiles(unsigned cPackages, const char *pszDstDir, bool fExtractOnly, PSTUBCLEANUPREC *ppExtractDirRec)
966{
967 int rc;
968
969 /*
970 * Make sure the directory exists (normally WinMain created it for us).
971 */
972 PSTUBCLEANUPREC pCleanupRec = *ppExtractDirRec;
973 if (!RTDirExists(pszDstDir))
974 {
975 AssertReturn(!pCleanupRec, ShowError("RTDirExists failed on '%s' which we just created!", pszDstDir));
976
977 rc = RTDirCreate(pszDstDir, 0700, 0);
978 if (RT_FAILURE(rc))
979 return ShowError("Failed to create extraction path '%s': %Rrc", pszDstDir, rc);
980
981 *ppExtractDirRec = pCleanupRec = AddCleanupRec(pszDstDir, false /*fFile*/);
982 AssertReturn(pCleanupRec, LogErrorExitFailure("Failed to add cleanup record for dir '%s'", pszDstDir));
983 }
984 /*
985 * If we need to create the cleanup record, the caller did not create the
986 * directory so we should not delete it when done.
987 */
988 else if (!pCleanupRec)
989 {
990 *ppExtractDirRec = pCleanupRec = AddCleanupRec(pszDstDir, false /*fFile*/);
991 AssertReturn(pCleanupRec, LogErrorExitFailure("Failed to add cleanup record for existing dir '%s'", pszDstDir));
992 pCleanupRec->fDontDelete = true;
993 }
994
995 /*
996 * Open up the directory to make it difficult to delete / replace.
997 */
998 rc = RTDirOpen(&pCleanupRec->hDir, pszDstDir);
999 if (RT_FAILURE(rc))
1000 return ShowError("Failed to open extraction path '%s': %Rrc", pszDstDir, rc);
1001
1002 /*
1003 * Change current directory to the extraction directory for the same reason
1004 * as we open it above.
1005 */
1006 RTPathSetCurrent(pszDstDir);
1007
1008 /*
1009 * Extract files.
1010 */
1011 for (unsigned k = 0; k < cPackages; k++)
1012 {
1013 VBOXSTUBPKG const * const pPackage = FindPackageHeader(k);
1014 if (!pPackage)
1015 return RTEXITCODE_FAILURE; /* Done complaining already. */
1016
1017 if (fExtractOnly || PackageIsNeeded(pPackage))
1018 {
1019 /* If we only extract or if it's a common file, use the original file name,
1020 otherwise generate a random name with the same file extension (@bugref{10201}). */
1021 RTFILE hFile = NIL_RTFILE;
1022 char szDstFile[RTPATH_MAX];
1023 if (fExtractOnly || pPackage->enmArch == VBOXSTUBPKGARCH_ALL)
1024 rc = RTPathJoin(szDstFile, sizeof(szDstFile), pszDstDir, pPackage->szFilename);
1025 else
1026 {
1027 rc = RTPathJoin(szDstFile, sizeof(szDstFile), pszDstDir, "XXXXXXXXXXXXXXXXXXXXXXXX");
1028 if (RT_SUCCESS(rc))
1029 {
1030 const char *pszSuffix = RTPathSuffix(pPackage->szFilename);
1031 if (pszSuffix)
1032 rc = RTStrCat(szDstFile, sizeof(szDstFile), pszSuffix);
1033 if (RT_SUCCESS(rc))
1034 {
1035 rc = RTFileCreateUnique(&hFile, szDstFile,
1036 RTFILE_O_CREATE | RTFILE_O_WRITE | RTFILE_O_DENY_WRITE
1037 | (0700 << RTFILE_O_CREATE_MODE_SHIFT));
1038 if (RT_FAILURE(rc))
1039 return ShowError("Failed to create unique filename for '%s' in '%s': %Rrc",
1040 pPackage->szFilename, pszDstDir, rc);
1041 }
1042 }
1043 }
1044 if (RT_FAILURE(rc))
1045 return ShowError("Internal error: Build extraction file name failed: %Rrc", rc);
1046
1047 rc = Extract(pPackage, szDstFile, hFile, k);
1048 if (RT_FAILURE(rc))
1049 return ShowError("Error extracting package #%u (%s): %Rrc", k, pPackage->szFilename, rc);
1050 }
1051 }
1052
1053 return RTEXITCODE_SUCCESS;
1054}
1055
1056int main(int argc, char **argv)
1057{
1058 /*
1059 * Init IPRT. This is _always_ the very first thing we do.
1060 */
1061 int vrc = RTR3InitExe(argc, &argv, RTR3INIT_FLAGS_STANDALONE_APP);
1062 if (RT_FAILURE(vrc))
1063 return RTMsgInitFailure(vrc);
1064
1065 /*
1066 * Parse arguments.
1067 */
1068
1069 /* Parameter variables. */
1070 bool fExtractOnly = false;
1071 bool fEnableLogging = false;
1072#ifdef VBOX_WITH_CODE_SIGNING
1073 bool fEnableSilentCert = true;
1074 bool fInstallTimestampCA = true;
1075 bool fForceTimestampCaInstall = false;
1076#endif
1077 bool fIgnoreReboot = false;
1078 char szExtractPath[RTPATH_MAX] = {0};
1079 char szMSIArgs[_4K] = {0};
1080 char szMSILogFile[RTPATH_MAX] = {0};
1081
1082 /* Argument enumeration IDs. */
1083 enum KVBOXSTUBOPT
1084 {
1085 KVBOXSTUBOPT_MSI_LOG_FILE = 1000
1086 };
1087
1088 /* Parameter definitions. */
1089 static const RTGETOPTDEF s_aOptions[] =
1090 {
1091 /** @todo Replace short parameters with enums since they're not
1092 * used (and not documented to the public). */
1093 { "--extract", 'x', RTGETOPT_REQ_NOTHING },
1094 { "-extract", 'x', RTGETOPT_REQ_NOTHING },
1095 { "/extract", 'x', RTGETOPT_REQ_NOTHING },
1096 { "--silent", 's', RTGETOPT_REQ_NOTHING },
1097 { "-silent", 's', RTGETOPT_REQ_NOTHING },
1098 { "/silent", 's', RTGETOPT_REQ_NOTHING },
1099#ifdef VBOX_WITH_CODE_SIGNING
1100 { "--no-silent-cert", 'c', RTGETOPT_REQ_NOTHING },
1101 { "-no-silent-cert", 'c', RTGETOPT_REQ_NOTHING },
1102 { "/no-silent-cert", 'c', RTGETOPT_REQ_NOTHING },
1103 { "--no-install-timestamp-ca", 't', RTGETOPT_REQ_NOTHING },
1104 { "--force-install-timestamp-ca", 'T', RTGETOPT_REQ_NOTHING },
1105#endif
1106 { "--logging", 'l', RTGETOPT_REQ_NOTHING },
1107 { "-logging", 'l', RTGETOPT_REQ_NOTHING },
1108 { "--msi-log-file", KVBOXSTUBOPT_MSI_LOG_FILE, RTGETOPT_REQ_STRING },
1109 { "-msilogfile", KVBOXSTUBOPT_MSI_LOG_FILE, RTGETOPT_REQ_STRING },
1110 { "/logging", 'l', RTGETOPT_REQ_NOTHING },
1111 { "--path", 'p', RTGETOPT_REQ_STRING },
1112 { "-path", 'p', RTGETOPT_REQ_STRING },
1113 { "/path", 'p', RTGETOPT_REQ_STRING },
1114 { "--msiparams", 'm', RTGETOPT_REQ_STRING },
1115 { "-msiparams", 'm', RTGETOPT_REQ_STRING },
1116 { "--msi-prop", 'P', RTGETOPT_REQ_STRING },
1117 { "--reinstall", 'f', RTGETOPT_REQ_NOTHING },
1118 { "-reinstall", 'f', RTGETOPT_REQ_NOTHING },
1119 { "/reinstall", 'f', RTGETOPT_REQ_NOTHING },
1120 { "--ignore-reboot", 'r', RTGETOPT_REQ_NOTHING },
1121 { "--verbose", 'v', RTGETOPT_REQ_NOTHING },
1122 { "-verbose", 'v', RTGETOPT_REQ_NOTHING },
1123 { "/verbose", 'v', RTGETOPT_REQ_NOTHING },
1124 { "--version", 'V', RTGETOPT_REQ_NOTHING },
1125 { "-version", 'V', RTGETOPT_REQ_NOTHING },
1126 { "/version", 'V', RTGETOPT_REQ_NOTHING },
1127 { "--help", 'h', RTGETOPT_REQ_NOTHING },
1128 { "-help", 'h', RTGETOPT_REQ_NOTHING },
1129 { "/help", 'h', RTGETOPT_REQ_NOTHING },
1130 { "/?", 'h', RTGETOPT_REQ_NOTHING },
1131 };
1132
1133 RTGETOPTSTATE GetState;
1134 vrc = RTGetOptInit(&GetState, argc, argv, s_aOptions, RT_ELEMENTS(s_aOptions), 1, 0);
1135 AssertRCReturn(vrc, ShowError("RTGetOptInit failed: %Rrc", vrc));
1136
1137 /* Loop over the arguments. */
1138 int ch;
1139 RTGETOPTUNION ValueUnion;
1140 while ((ch = RTGetOpt(&GetState, &ValueUnion)) != 0)
1141 {
1142 switch (ch)
1143 {
1144 case 'f': /* Force re-installation. */
1145 if (szMSIArgs[0])
1146 vrc = RTStrCat(szMSIArgs, sizeof(szMSIArgs), " ");
1147 if (RT_SUCCESS(vrc))
1148 vrc = RTStrCat(szMSIArgs, sizeof(szMSIArgs), "REINSTALLMODE=vomus REINSTALL=ALL");
1149 if (RT_FAILURE(vrc))
1150 return ShowSyntaxError("Out of space for MSI parameters and properties");
1151 break;
1152
1153 case 'x':
1154 fExtractOnly = true;
1155 break;
1156
1157 case 's':
1158 g_fSilent = true;
1159 break;
1160
1161#ifdef VBOX_WITH_CODE_SIGNING
1162 case 'c':
1163 fEnableSilentCert = false;
1164 break;
1165 case 't':
1166 fInstallTimestampCA = false;
1167 break;
1168 case 'T':
1169 fForceTimestampCaInstall = fInstallTimestampCA = true;
1170 break;
1171#endif
1172 case 'l':
1173 fEnableLogging = true;
1174 break;
1175
1176 case KVBOXSTUBOPT_MSI_LOG_FILE:
1177 if (*ValueUnion.psz == '\0')
1178 szMSILogFile[0] = '\0';
1179 else
1180 {
1181 vrc = RTPathAbs(ValueUnion.psz, szMSILogFile, sizeof(szMSILogFile));
1182 if (RT_FAILURE(vrc))
1183 return ShowSyntaxError("MSI log file path is too long (%Rrc)", vrc);
1184 }
1185 break;
1186
1187 case 'p':
1188 if (*ValueUnion.psz == '\0')
1189 szExtractPath[0] = '\0';
1190 else
1191 {
1192 vrc = RTPathAbs(ValueUnion.psz, szExtractPath, sizeof(szExtractPath));
1193 if (RT_FAILURE(vrc))
1194 return ShowSyntaxError("Extraction path is too long (%Rrc)", vrc);
1195 }
1196 break;
1197
1198 case 'm':
1199 if (szMSIArgs[0])
1200 vrc = RTStrCat(szMSIArgs, sizeof(szMSIArgs), " ");
1201 if (RT_SUCCESS(vrc))
1202 vrc = RTStrCat(szMSIArgs, sizeof(szMSIArgs), ValueUnion.psz);
1203 if (RT_FAILURE(vrc))
1204 return ShowSyntaxError("Out of space for MSI parameters and properties");
1205 break;
1206
1207 case 'P':
1208 {
1209 const char *pszProp = ValueUnion.psz;
1210 if (strpbrk(pszProp, " \t\n\r") == NULL)
1211 {
1212 vrc = RTGetOptFetchValue(&GetState, &ValueUnion, RTGETOPT_REQ_STRING);
1213 if (RT_SUCCESS(vrc))
1214 {
1215 size_t cchMsiArgs = strlen(szMSIArgs);
1216 if (RTStrPrintf2(&szMSIArgs[cchMsiArgs], sizeof(szMSIArgs) - cchMsiArgs,
1217 strpbrk(ValueUnion.psz, " \t\n\r") == NULL ? "%s%s=%s" : "%s%s=\"%s\"",
1218 cchMsiArgs ? " " : "", pszProp, ValueUnion.psz) <= 1)
1219 return ShowSyntaxError("Out of space for MSI parameters and properties");
1220 }
1221 else if (vrc == VERR_GETOPT_REQUIRED_ARGUMENT_MISSING)
1222 return ShowSyntaxError("--msi-prop takes two arguments, the 2nd is missing");
1223 else
1224 return ShowSyntaxError("Failed to get 2nd --msi-prop argument: %Rrc", vrc);
1225 }
1226 else
1227 return ShowSyntaxError("The first argument to --msi-prop must not contain spaces: %s", pszProp);
1228 break;
1229 }
1230
1231 case 'r':
1232 fIgnoreReboot = true;
1233 break;
1234
1235 case 'V':
1236 ShowInfo("Version: %u.%u.%ur%u", VBOX_VERSION_MAJOR, VBOX_VERSION_MINOR, VBOX_VERSION_BUILD, VBOX_SVN_REV);
1237 return RTEXITCODE_SUCCESS;
1238
1239 case 'v':
1240 g_iVerbosity++;
1241 break;
1242
1243 case 'h':
1244 ShowInfo("-- %s v%u.%u.%ur%u --\n"
1245 "\n"
1246 "Command Line Parameters:\n\n"
1247 "--extract\n"
1248 " Extract file contents to temporary directory\n"
1249 "--logging\n"
1250 " Enables MSI installer logging (to extract path)\n"
1251 "--msi-log-file <path/to/file>\n"
1252 " Sets MSI logging to <file>\n"
1253 "--msiparams <parameters>\n"
1254 " Specifies extra parameters for the MSI installers\n"
1255 " double quoted arguments must be doubled and put\n"
1256 " in quotes: --msiparams \"PROP=\"\"a b c\"\"\"\n"
1257 "--msi-prop <prop> <value>\n"
1258 " Adds <prop>=<value> to the MSI parameters,\n"
1259 " quoting the property value if necessary\n"
1260#ifdef VBOX_WITH_CODE_SIGNING
1261 "--no-silent-cert\n"
1262 " Do not install VirtualBox Certificate automatically\n"
1263 " when --silent option is specified\n"
1264#endif
1265#ifdef VBOX_WITH_VBOX_LEGACY_TS_CA
1266 "--force-install-timestamp-ca\n"
1267 " Install the timestamp CA needed for supporting\n"
1268 " legacy Windows versions regardless of the version or\n"
1269 " type of Windows VirtualBox is being installed on.\n"
1270 " Default: All except Windows 10 & 11 desktop\n"
1271 "--no-install-timestamp-ca\n"
1272 " Do not install the above mentioned timestamp CA.\n"
1273#endif
1274 "--path\n"
1275 " Sets the path of the extraction directory\n"
1276 "--reinstall\n"
1277 " Forces VirtualBox to get re-installed\n"
1278 "--ignore-reboot\n"
1279 " Do not set exit code to 3010 if a reboot is required\n"
1280 "--silent\n"
1281 " Enables silent mode installation\n"
1282 "--version\n"
1283 " Displays version number and exit\n"
1284 "-?, -h, --help\n"
1285 " Displays this help text and exit\n"
1286 "\n"
1287 "Examples:\n"
1288 " %s --msiparams \"INSTALLDIR=\"\"C:\\Program Files\\VirtualBox\"\"\"\n"
1289 " %s --extract -path C:\\VBox",
1290 VBOX_STUB_TITLE, VBOX_VERSION_MAJOR, VBOX_VERSION_MINOR, VBOX_VERSION_BUILD, VBOX_SVN_REV,
1291 argv[0], argv[0]);
1292 return RTEXITCODE_SUCCESS;
1293
1294 case VINF_GETOPT_NOT_OPTION:
1295 /* Are (optional) MSI parameters specified and this is the last
1296 * parameter? Append everything to the MSI parameter list then. */
1297 /** @todo r=bird: this makes zero sense */
1298 if (szMSIArgs[0])
1299 {
1300 vrc = RTStrCat(szMSIArgs, sizeof(szMSIArgs), " ");
1301 if (RT_SUCCESS(vrc))
1302 vrc = RTStrCat(szMSIArgs, sizeof(szMSIArgs), ValueUnion.psz);
1303 if (RT_FAILURE(vrc))
1304 return ShowSyntaxError("Out of space for MSI parameters and properties");
1305 continue;
1306 }
1307 /* Fall through is intentional. */
1308
1309 default:
1310 if (g_fSilent)
1311 return RTGetOptPrintError(ch, &ValueUnion);
1312 if (ch == VERR_GETOPT_UNKNOWN_OPTION)
1313 return ShowSyntaxError("Unknown option \"%s\"\n"
1314 "Please refer to the command line help by specifying \"-?\"\n"
1315 "to get more information.", ValueUnion.psz);
1316 return ShowSyntaxError("Parameter parsing error: %Rrc\n"
1317 "Please refer to the command line help by specifying \"-?\"\n"
1318 "to get more information.", ch);
1319 }
1320 }
1321
1322 /*
1323 * Check if we're already running and jump out if so (this is mainly to
1324 * protect the TEMP directory usage, right?).
1325 */
1326 SetLastError(0);
1327 HANDLE hMutexAppRunning = CreateMutexW(NULL, FALSE, L"VBoxStubInstaller");
1328 if ( hMutexAppRunning != NULL
1329 && GetLastError() == ERROR_ALREADY_EXISTS)
1330 {
1331 CloseHandle(hMutexAppRunning); /* close it so we don't keep it open while showing the error message. */
1332 return ShowError("Another installer is already running");
1333 }
1334
1335/** @todo
1336 *
1337 * Split the remainder up in functions and simplify the code flow!!
1338 *
1339 * */
1340 RTEXITCODE rcExit = RTEXITCODE_SUCCESS;
1341 RTListInit(&g_TmpFiles);
1342
1343 /*
1344 * Create a random extraction directory in the temporary directory if none
1345 * was given by the user (see @bugref{10201}).
1346 */
1347 PSTUBCLEANUPREC pExtractDirRec = NULL; /* This also indicates that */
1348 if (szExtractPath[0] == '\0')
1349 {
1350 vrc = RTPathTemp(szExtractPath, sizeof(szExtractPath));
1351 if (RT_FAILURE(vrc))
1352 {
1353 CloseHandle(hMutexAppRunning); /* close it so we don't keep it open while showing the error message. */
1354 return ShowError("Failed to find temporary directory: %Rrc", vrc);
1355 }
1356 if (!fExtractOnly) /* Only use a random sub-dir if we extract + run (and not just extract). */
1357 {
1358 vrc = RTPathAppend(szExtractPath, sizeof(szExtractPath), "XXXXXXXXXXXXXXXXXXXXXXXX");
1359 if (RT_SUCCESS(vrc))
1360 /** @todo Need something that return a handle as well as a path. */
1361 vrc = RTDirCreateTemp(szExtractPath, 0700);
1362 if (RT_FAILURE(vrc))
1363 {
1364 CloseHandle(hMutexAppRunning); /* close it so we don't keep it open while showing the error message. */
1365 return ShowError("Failed to create extraction path: %Rrc", vrc);
1366 }
1367 pExtractDirRec = AddCleanupRec(szExtractPath, false /*fIsFile*/);
1368 }
1369 }
1370 RTPathChangeToDosSlashes(szExtractPath, true /* Force conversion. */); /* MSI requirement. */
1371
1372 /*
1373 * Create a console for output if we're in verbose mode.
1374 */
1375#ifdef VBOX_STUB_WITH_OWN_CONSOLE
1376 if (g_iVerbosity)
1377 {
1378 if (!AllocConsole())
1379 return ShowError("Unable to allocate console: LastError=%u\n", GetLastError());
1380
1381# ifdef IPRT_NO_CRT
1382 PRTSTREAM pNewStdOutErr = NULL;
1383 vrc = RTStrmOpen("CONOUT$", "a", &pNewStdOutErr);
1384 if (RT_SUCCESS(vrc))
1385 {
1386 RTStrmSetBufferingMode(pNewStdOutErr, RTSTRMBUFMODE_UNBUFFERED);
1387 g_pStdErr = pNewStdOutErr;
1388 g_pStdOut = pNewStdOutErr;
1389 }
1390# else
1391 freopen("CONOUT$", "w", stdout);
1392 setvbuf(stdout, NULL, _IONBF, 0);
1393 freopen("CONOUT$", "w", stderr);
1394# endif
1395 }
1396#endif /* VBOX_STUB_WITH_OWN_CONSOLE */
1397
1398 /* Convenience: Enable logging if a log file (via --log-file) is specified. */
1399 if ( !fEnableLogging
1400 && szMSILogFile[0] != '\0')
1401 fEnableLogging = true;
1402
1403 if ( fEnableLogging
1404 && szMSILogFile[0] == '\0') /* No log file explicitly specified? Use the extract path by default. */
1405 {
1406 vrc = RTStrCopy(szMSILogFile, sizeof(szMSILogFile), szExtractPath);
1407 if (RT_SUCCESS(vrc))
1408 vrc = RTPathAppend(szMSILogFile, sizeof(szMSILogFile), "VBoxInstallLog.txt");
1409 if (RT_FAILURE(vrc))
1410 return ShowError("Error creating MSI log file name, rc=%Rrc", vrc);
1411 }
1412
1413 if (g_iVerbosity)
1414 {
1415 RTPrintf("Extraction path : %s\n", szExtractPath);
1416 RTPrintf("Silent installation : %RTbool\n", g_fSilent);
1417#ifdef VBOX_WITH_CODE_SIGNING
1418 RTPrintf("Certificate installation : %RTbool\n", fEnableSilentCert);
1419#endif
1420 RTPrintf("Additional MSI parameters: %s\n", szMSIArgs[0] ? szMSIArgs : "<None>");
1421 RTPrintf("Logging to file : %s\n", szMSILogFile[0] ? szMSILogFile : "<None>");
1422 }
1423
1424 /*
1425 * 32-bit is not officially supported any more.
1426 */
1427 if ( !fExtractOnly
1428 && !g_fSilent
1429 && !IsWow64())
1430 rcExit = ShowError("32-bit Windows hosts are not supported by this VirtualBox release.");
1431 else
1432 {
1433 /*
1434 * Read our manifest.
1435 */
1436 VBOXSTUBPKGHEADER const *pHeader = NULL;
1437 vrc = FindData("MANIFEST", (uint8_t const **)&pHeader, NULL);
1438 if (RT_SUCCESS(vrc))
1439 {
1440 /** @todo If we could, we should validate the header. Only the magic isn't
1441 * commonly defined, nor the version number... */
1442
1443 /*
1444 * Up to this point, we haven't done anything that requires any cleanup.
1445 * From here on, we do everything in functions so we can counter clean up.
1446 */
1447 rcExit = ExtractFiles(pHeader->cPackages, szExtractPath, fExtractOnly, &pExtractDirRec);
1448 if (rcExit == RTEXITCODE_SUCCESS)
1449 {
1450 if (fExtractOnly)
1451 ShowInfo("Files were extracted to: %s", szExtractPath);
1452 else
1453 {
1454 rcExit = CopyCustomDir(szExtractPath);
1455#ifdef VBOX_WITH_CODE_SIGNING
1456# ifdef VBOX_WITH_VBOX_LEGACY_TS_CA
1457 if (rcExit == RTEXITCODE_SUCCESS && fInstallTimestampCA)
1458 rcExit = InstallTimestampCA(fForceTimestampCaInstall);
1459# endif
1460 if (rcExit == RTEXITCODE_SUCCESS && fEnableSilentCert && g_fSilent)
1461 rcExit = InstallCertificates();
1462#endif
1463 unsigned iPackage = 0;
1464 while ( iPackage < pHeader->cPackages
1465 && (rcExit == RTEXITCODE_SUCCESS || rcExit == (RTEXITCODE)ERROR_SUCCESS_REBOOT_REQUIRED))
1466 {
1467 RTEXITCODE rcExit2 = ProcessPackage(iPackage, szMSIArgs, szMSILogFile[0] ? szMSILogFile : NULL);
1468 if (rcExit2 != RTEXITCODE_SUCCESS)
1469 rcExit = rcExit2;
1470 iPackage++;
1471 }
1472 }
1473 }
1474
1475 /*
1476 * Do cleanups unless we're only extracting (ignoring failures for now).
1477 */
1478 if (!fExtractOnly)
1479 {
1480 RTPathSetCurrent("..");
1481 CleanUp(!fEnableLogging && pExtractDirRec && !pExtractDirRec->fDontDelete ? szExtractPath : NULL);
1482 }
1483
1484 /* Free any left behind cleanup records (not strictly needed). */
1485 PSTUBCLEANUPREC pCur, pNext;
1486 RTListForEachSafe(&g_TmpFiles, pCur, pNext, STUBCLEANUPREC, ListEntry)
1487 {
1488 RTListNodeRemove(&pCur->ListEntry);
1489 RTMemFree(pCur);
1490 }
1491 }
1492 else
1493 rcExit = ShowError("Internal package error: Manifest not found (%Rrc)", vrc);
1494 }
1495
1496#if defined(_WIN32_WINNT) && _WIN32_WINNT >= 0x0501
1497# ifdef VBOX_STUB_WITH_OWN_CONSOLE
1498 if (g_iVerbosity)
1499 FreeConsole();
1500# endif /* VBOX_STUB_WITH_OWN_CONSOLE */
1501#endif
1502
1503 /*
1504 * Release instance mutex just to be on the safe side.
1505 */
1506 if (hMutexAppRunning != NULL)
1507 CloseHandle(hMutexAppRunning);
1508
1509 return rcExit != (RTEXITCODE)ERROR_SUCCESS_REBOOT_REQUIRED || !fIgnoreReboot ? rcExit : RTEXITCODE_SUCCESS;
1510}
1511
1512#ifndef IPRT_NO_CRT
1513int WINAPI WinMain(HINSTANCE hInstance,
1514 HINSTANCE hPrevInstance,
1515 char *lpCmdLine,
1516 int nCmdShow)
1517{
1518 RT_NOREF(hInstance, hPrevInstance, lpCmdLine, nCmdShow);
1519 return main(__argc, __argv);
1520}
1521#endif
1522
Note: See TracBrowser for help on using the repository browser.

© 2023 Oracle
ContactPrivacy policyTerms of Use