VirtualBox

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

Last change on this file since 95720 was 95720, checked in by vboxsync, 2 years ago

Installer/win/VBoxStub: Always install the legacy windows timestamp. Allows doing test builds without attestation signing. bugref:8691

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 57.1 KB
Line 
1/* $Id: VBoxStub.cpp 95720 2022-07-18 17:58:52Z vboxsync $ */
2/** @file
3 * VBoxStub - VirtualBox's Windows installer stub.
4 */
5
6/*
7 * Copyright (C) 2010-2022 Oracle Corporation
8 *
9 * This file is part of VirtualBox Open Source Edition (OSE), as
10 * available from http://www.virtualbox.org. This file is free software;
11 * you can redistribute it and/or modify it under the terms of the GNU
12 * General Public License (GPL) as published by the Free Software
13 * Foundation, in version 2 as it comes in the "COPYING" file of the
14 * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
15 * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
16 */
17
18
19/*********************************************************************************************************************************
20* Header Files *
21*********************************************************************************************************************************/
22#include <iprt/win/windows.h>
23#include <iprt/win/commctrl.h>
24#include <fcntl.h>
25#include <io.h>
26#include <lmerr.h>
27#include <msiquery.h>
28#include <iprt/win/objbase.h>
29
30#include <iprt/win/shlobj.h>
31#include <stdlib.h>
32#include <stdio.h>
33#include <string.h>
34#include <strsafe.h>
35
36#include <VBox/version.h>
37
38#include <iprt/assert.h>
39#include <iprt/dir.h>
40#include <iprt/err.h>
41#include <iprt/file.h>
42#include <iprt/getopt.h>
43#include <iprt/initterm.h>
44#include <iprt/list.h>
45#include <iprt/mem.h>
46#include <iprt/message.h>
47#include <iprt/param.h>
48#include <iprt/path.h>
49#include <iprt/stream.h>
50#include <iprt/string.h>
51#include <iprt/system.h>
52#include <iprt/thread.h>
53#include <iprt/utf16.h>
54
55#include "VBoxStub.h"
56#include "../StubBld/VBoxStubBld.h"
57#include "resource.h"
58
59#ifdef VBOX_WITH_CODE_SIGNING
60# include "VBoxStubCertUtil.h"
61# include "VBoxStubPublicCert.h"
62#endif
63
64
65/*********************************************************************************************************************************
66* Defined Constants And Macros *
67*********************************************************************************************************************************/
68#define MY_UNICODE_SUB(str) L ##str
69#define MY_UNICODE(str) MY_UNICODE_SUB(str)
70
71/* Use an own console window if run in verbose mode. */
72#define VBOX_STUB_WITH_OWN_CONSOLE
73
74
75/*********************************************************************************************************************************
76* Structures and Typedefs *
77*********************************************************************************************************************************/
78/**
79 * Cleanup record.
80 */
81typedef struct STUBCLEANUPREC
82{
83 /** List entry. */
84 RTLISTNODE ListEntry;
85 /** Stub package index (zero-based) this record belongs to. */
86 unsigned idxPkg;
87 /** True if file, false if directory. */
88 bool fFile;
89 /** Set if we should not delete the file/directory.
90 * This is used for user supplied extraction directories. */
91 bool fDontDelete;
92 union
93 {
94 /** File handle (if \a fFile is \c true). */
95 RTFILE hFile;
96 /** Directory handle (if \a fFile is \c false). */
97 RTDIR hDir;
98 };
99 /** The path to the file or directory to clean up. */
100 char szPath[1];
101} STUBCLEANUPREC;
102/** Pointer to a cleanup record. */
103typedef STUBCLEANUPREC *PSTUBCLEANUPREC;
104
105
106/*********************************************************************************************************************************
107* Prototypes *
108*********************************************************************************************************************************/
109static PSTUBCLEANUPREC AddCleanupRec(const char *pszPath, bool fIsFile);
110
111
112/*********************************************************************************************************************************
113* Global Variables *
114*********************************************************************************************************************************/
115/** Whether it's a silent or interactive GUI driven install. */
116static bool g_fSilent = false;
117/** List of temporary files. */
118static RTLISTANCHOR g_TmpFiles;
119/** Verbosity flag. */
120static int g_iVerbosity = 0;
121
122
123
124/**
125 * Shows an error message box with a printf() style formatted string.
126 *
127 * @returns RTEXITCODE_FAILURE
128 * @param pszFmt Printf-style format string to show in the message box body.
129 *
130 */
131static RTEXITCODE ShowError(const char *pszFmt, ...)
132{
133 char *pszMsg;
134 va_list va;
135
136 va_start(va, pszFmt);
137 if (RTStrAPrintfV(&pszMsg, pszFmt, va))
138 {
139 if (g_fSilent)
140 RTMsgError("%s", pszMsg);
141 else
142 {
143 PRTUTF16 pwszMsg;
144 int rc = RTStrToUtf16(pszMsg, &pwszMsg);
145 if (RT_SUCCESS(rc))
146 {
147 MessageBoxW(GetDesktopWindow(), pwszMsg, MY_UNICODE(VBOX_STUB_TITLE), MB_ICONERROR);
148 RTUtf16Free(pwszMsg);
149 }
150 else
151 MessageBoxA(GetDesktopWindow(), pszMsg, VBOX_STUB_TITLE, MB_ICONERROR);
152 }
153 RTStrFree(pszMsg);
154 }
155 else /* Should never happen! */
156 AssertMsgFailed(("Failed to format error text of format string: %s!\n", pszFmt));
157 va_end(va);
158 return RTEXITCODE_FAILURE;
159}
160
161
162/**
163 * Same as ShowError, only it returns RTEXITCODE_SYNTAX.
164 */
165static RTEXITCODE ShowSyntaxError(const char *pszFmt, ...)
166{
167 va_list va;
168 va_start(va, pszFmt);
169 ShowError("%N", pszFmt, &va);
170 va_end(va);
171 return RTEXITCODE_SYNTAX;
172}
173
174
175/**
176 * Shows a message box with a printf() style formatted string.
177 *
178 * @param uType Type of the message box (see MSDN).
179 * @param pszFmt Printf-style format string to show in the message box body.
180 *
181 */
182static void ShowInfo(const char *pszFmt, ...)
183{
184 char *pszMsg;
185 va_list va;
186 va_start(va, pszFmt);
187 int rc = RTStrAPrintfV(&pszMsg, pszFmt, va);
188 va_end(va);
189 if (rc >= 0)
190 {
191 if (g_fSilent)
192 RTPrintf("%s\n", pszMsg);
193 else
194 {
195 PRTUTF16 pwszMsg;
196 rc = RTStrToUtf16(pszMsg, &pwszMsg);
197 if (RT_SUCCESS(rc))
198 {
199 MessageBoxW(GetDesktopWindow(), pwszMsg, MY_UNICODE(VBOX_STUB_TITLE), MB_ICONINFORMATION);
200 RTUtf16Free(pwszMsg);
201 }
202 else
203 MessageBoxA(GetDesktopWindow(), pszMsg, VBOX_STUB_TITLE, MB_ICONINFORMATION);
204 }
205 }
206 else /* Should never happen! */
207 AssertMsgFailed(("Failed to format error text of format string: %s!\n", pszFmt));
208 RTStrFree(pszMsg);
209}
210
211
212/** Logs error details to stderr. */
213static void LogError(const char *pszFmt, ...)
214{
215 va_list va;
216 va_start(va, pszFmt);
217 RTStrmPrintf(g_pStdErr, "error: %N\n", pszFmt, &va);
218 va_end(va);
219}
220
221
222/** Logs error details to stderr, returning @a rc. */
223static int LogErrorRc(int rc, const char *pszFmt, ...)
224{
225 va_list va;
226 va_start(va, pszFmt);
227 RTStrmPrintf(g_pStdErr, "error: %N\n", pszFmt, &va);
228 va_end(va);
229 return rc;
230}
231
232
233/** Logs error details to stderr, RTEXITCODE_FAILURE. */
234static RTEXITCODE LogErrorExitFailure(const char *pszFmt, ...)
235{
236 va_list va;
237 va_start(va, pszFmt);
238 RTStrmPrintf(g_pStdErr, "error: %N\n", pszFmt, &va);
239 va_end(va);
240 return RTEXITCODE_FAILURE;
241}
242
243
244/**
245 * Finds the specified in the resource section of the executable.
246 *
247 * @returns IPRT status code.
248 *
249 * @param pszDataName Name of resource to read.
250 * @param ppbResource Where to return the pointer to the data.
251 * @param pcbResource Where to return the size of the data (if found).
252 * Optional.
253 */
254static int FindData(const char *pszDataName, uint8_t const **ppbResource, DWORD *pcbResource)
255{
256 AssertReturn(pszDataName, VERR_INVALID_PARAMETER);
257 HINSTANCE hInst = NULL; /* indicates the executable image */
258
259 /* Find our resource. */
260 PRTUTF16 pwszDataName;
261 int rc = RTStrToUtf16(pszDataName, &pwszDataName);
262 AssertRCReturn(rc, rc);
263 HRSRC hRsrc = FindResourceExW(hInst,
264 (LPWSTR)RT_RCDATA,
265 pwszDataName,
266 MAKELANGID(LANG_NEUTRAL, SUBLANG_NEUTRAL));
267 RTUtf16Free(pwszDataName);
268 AssertReturn(hRsrc, VERR_IO_GEN_FAILURE);
269
270 /* Get resource size. */
271 DWORD cb = SizeofResource(hInst, hRsrc);
272 AssertReturn(cb > 0, VERR_NO_DATA);
273 if (pcbResource)
274 *pcbResource = cb;
275
276 /* Get pointer to resource. */
277 HGLOBAL hData = LoadResource(hInst, hRsrc);
278 AssertReturn(hData, VERR_IO_GEN_FAILURE);
279
280 /* Lock resource. */
281 *ppbResource = (uint8_t const *)LockResource(hData);
282 AssertReturn(*ppbResource, VERR_IO_GEN_FAILURE);
283 return VINF_SUCCESS;
284}
285
286
287/**
288 * Finds the header for the given package.
289 *
290 * @returns Pointer to the package header on success. On failure NULL is
291 * returned after ShowError has been invoked.
292 * @param iPackage The package number.
293 */
294static const VBOXSTUBPKG *FindPackageHeader(unsigned iPackage)
295{
296 char szHeaderName[32];
297 RTStrPrintf(szHeaderName, sizeof(szHeaderName), "HDR_%02d", iPackage);
298
299 VBOXSTUBPKG const *pPackage;
300 int rc = FindData(szHeaderName, (uint8_t const **)&pPackage, NULL);
301 if (RT_FAILURE(rc))
302 {
303 ShowError("Internal error: Could not find package header #%u: %Rrc", iPackage, rc);
304 return NULL;
305 }
306
307 /** @todo validate it. */
308 return pPackage;
309}
310
311
312
313/**
314 * Constructs a full temporary file path from the given parameters.
315 *
316 * @returns iprt status code.
317 *
318 * @param pszTempPath The pure path to use for construction.
319 * @param pszTargetFileName The pure file name to use for construction.
320 * @param ppszTempFile Pointer to the constructed string. Must be freed
321 * using RTStrFree().
322 */
323static int GetTempFileAlloc(const char *pszTempPath,
324 const char *pszTargetFileName,
325 char **ppszTempFile)
326{
327 if (RTStrAPrintf(ppszTempFile, "%s\\%s", pszTempPath, pszTargetFileName) >= 0)
328 return VINF_SUCCESS;
329 return VERR_NO_STR_MEMORY;
330}
331
332
333/**
334 * Extracts a built-in resource to disk.
335 *
336 * @returns iprt status code.
337 *
338 * @param pszResourceName The resource name to extract.
339 * @param pszTempFile The full file path + name to extract the resource to.
340 * @param hFile Handle to pszTempFile if RTFileCreateUnique was
341 * used to generate the name, otherwise NIL_RTFILE.
342 * @param idxPackage The package index for annotating the cleanup
343 * record with (HACK ALERT).
344 */
345static int ExtractFile(const char *pszResourceName, const char *pszTempFile, RTFILE hFile, unsigned idxPackage)
346{
347 AssertPtrReturn(pszResourceName, VERR_INVALID_POINTER);
348 AssertPtrReturn(pszTempFile, VERR_INVALID_POINTER);
349
350 /* Create new (and replace any old) file. */
351 if (hFile == NIL_RTFILE)
352 {
353 int rc = RTFileOpen(&hFile, pszTempFile,
354 RTFILE_O_CREATE_REPLACE | RTFILE_O_WRITE | RTFILE_O_DENY_WRITE
355 | (0700 << RTFILE_O_CREATE_MODE_SHIFT));
356 AssertRCReturn(rc, LogErrorRc(rc, "#%u: Failed to create/replace '%s' for writing: %Rrc", idxPackage, pszTempFile, rc));
357 }
358
359 /* Add a cleanup record, so that we can properly clean up (partially run) stuff. */
360 int rc = VERR_NO_MEMORY;
361 PSTUBCLEANUPREC pCleanupRec = AddCleanupRec(pszTempFile, true /*fIsFile*/);
362 AssertReturn(pCleanupRec, VERR_NO_MEMORY);
363
364 pCleanupRec->idxPkg = idxPackage;
365 pCleanupRec->hFile = hFile;
366
367 /* Find the data of the built-in resource. */
368 uint8_t const *pbData = NULL;
369 DWORD cbData = 0;
370 rc = FindData(pszResourceName, &pbData, &cbData);
371 AssertRCReturn(rc, LogErrorRc(rc, "#%u: Failed to locate resource '%s': %Rrc", idxPackage, pszResourceName, rc));
372
373 /* Write the contents to the file. */
374 rc = RTFileWrite(hFile, pbData, cbData, NULL);
375 AssertRCReturn(rc, LogErrorRc(rc, "#%u: RTFileWrite('%s',, %#x,) failed: %Rrc", idxPackage, pszTempFile, cbData, rc));
376
377 /*
378 * We now wish to keep the file open, however since we've got it open in write
379 * mode with deny-write sharing (effectively exclusive write mode) this will
380 * prevent the MSI API from opening it in deny-write mode for reading purposes.
381 *
382 * So we have to do the best we can to transition this to a read-only handle
383 * that denies write (and deletion/renaming). First we open it again in
384 * read-only mode only denying deletion, not writing. Then close the original
385 * handle. Finally open a read-only handle that denies both reading and
386 * deletion/renaming, and verify that the file content is still the same.
387 *
388 * Note! DuplicateHandle to read-only and closing the original does not work,
389 * as the kernel doesn't update the sharing access info for the handles.
390 */
391 RTFSOBJINFO ObjInfo1;
392 rc = RTFileQueryInfo(hFile, &ObjInfo1, RTFSOBJATTRADD_UNIX);
393 AssertRCReturn(rc, LogErrorRc(rc, "#%u: RTFileQueryInfo failed on '%s': %Rrc", idxPackage, pszTempFile, rc));
394
395 RTFILE hFile2 = NIL_RTFILE;
396 rc = RTFileOpen(&hFile2, pszTempFile,
397 RTFILE_O_OPEN | RTFILE_O_READ | RTFILE_O_DENY_NONE | (0700 << RTFILE_O_CREATE_MODE_SHIFT));
398 AssertRCReturn(rc, LogErrorRc(rc, "#%u: First re-opening of '%s' failed: %Rrc", idxPackage, pszTempFile, rc));
399
400 rc = RTFileClose(hFile);
401 AssertRCReturnStmt(rc, RTFileClose(hFile2),
402 LogErrorRc(rc, "#%u: RTFileClose('%s') failed: %Rrc", idxPackage, pszTempFile, rc));
403 pCleanupRec->hFile = hFile2;
404
405 rc = RTFileOpen(&hFile, pszTempFile, RTFILE_O_OPEN | RTFILE_O_READ | RTFILE_O_DENY_WRITE);
406 AssertRCReturn(rc, LogErrorRc(rc, "#%u: Second re-opening of '%s' failed: %Rrc", idxPackage, pszTempFile, rc));
407 pCleanupRec->hFile = hFile;
408
409 rc = RTFileClose(hFile2);
410 AssertRCStmt(rc, LogError("#%u: Failed to close 2nd handle to '%s': %Rrc", idxPackage, pszTempFile, rc));
411
412 /* check the size and inode number. */
413 RTFSOBJINFO ObjInfo2;
414 rc = RTFileQueryInfo(hFile, &ObjInfo2, RTFSOBJATTRADD_UNIX);
415 AssertRCReturn(rc, LogErrorRc(rc, "#%u: RTFileQueryInfo failed on '%s': %Rrc", idxPackage, pszTempFile, rc));
416
417 AssertReturn(ObjInfo2.cbObject == cbData,
418 LogErrorRc(VERR_STATE_CHANGED, "#%u: File size of '%s' changed: %'RU64, expected %'RU32",
419 idxPackage, pszTempFile, ObjInfo2.cbObject, pbData));
420
421 AssertReturn(ObjInfo2.Attr.u.Unix.INodeId == ObjInfo1.Attr.u.Unix.INodeId,
422 LogErrorRc(VERR_STATE_CHANGED, "#%u: File ID of '%s' changed: %#RX64, expected %#RX64",
423 idxPackage, pszTempFile, ObjInfo2.Attr.u.Unix.INodeId, ObjInfo1.Attr.u.Unix.INodeId));
424
425
426 /* Check the content. */
427 uint32_t off = 0;
428 while (off < cbData)
429 {
430 uint8_t abBuf[_64K];
431 size_t cbToRead = RT_MIN(cbData - off, sizeof(abBuf));
432 rc = RTFileRead(hFile, abBuf, cbToRead, NULL);
433 AssertRCReturn(rc, LogErrorRc(rc, "#%u: RTFileRead failed on '%s' at offset %#RX32: %Rrc",
434 idxPackage, pszTempFile, off, rc));
435 AssertReturn(memcmp(abBuf, &pbData[off], cbToRead) == 0,
436 LogErrorRc(VERR_STATE_CHANGED, "#%u: File '%s' has change (mismatch in %#zx byte block at %#RX32)",
437 idxPackage, pszTempFile, cbToRead, off));
438 off += cbToRead;
439 }
440
441 return VINF_SUCCESS;
442}
443
444
445/**
446 * Extracts a built-in resource to disk.
447 *
448 * @returns iprt status code.
449 *
450 * @param pPackage Pointer to a VBOXSTUBPKG struct that contains the resource.
451 * @param pszTempFile The full file path + name to extract the resource to.
452 * @param hFile Handle to pszTempFile if RTFileCreateUnique was
453 * used to generate the name, otherwise NIL_RTFILE.
454 * @param idxPackage The package index for annotating the cleanup
455 * record with (HACK ALERT).
456 */
457static int Extract(VBOXSTUBPKG const *pPackage, const char *pszTempFile, RTFILE hFile, unsigned idxPackage)
458{
459 return ExtractFile(pPackage->szResourceName, pszTempFile, hFile, idxPackage);
460}
461
462
463/**
464 * Detects whether we're running on a 32- or 64-bit platform and returns the result.
465 *
466 * @returns TRUE if we're running on a 64-bit OS, FALSE if not.
467 */
468static BOOL IsWow64(void)
469{
470 BOOL fIsWow64 = TRUE;
471 fnIsWow64Process = (LPFN_ISWOW64PROCESS)GetProcAddress(GetModuleHandle(TEXT("kernel32")), "IsWow64Process");
472 if (NULL != fnIsWow64Process)
473 {
474 if (!fnIsWow64Process(GetCurrentProcess(), &fIsWow64))
475 {
476 /* Error in retrieving process type - assume that we're running on 32bit. */
477 return FALSE;
478 }
479 }
480 return fIsWow64;
481}
482
483
484/**
485 * Decides whether we need a specified package to handle or not.
486 *
487 * @returns @c true if we need to handle the specified package, @c false if not.
488 *
489 * @param pPackage Pointer to a VBOXSTUBPKG struct that contains the resource.
490 */
491static bool PackageIsNeeded(VBOXSTUBPKG const *pPackage)
492{
493 if (pPackage->byArch == VBOXSTUBPKGARCH_ALL)
494 return true;
495 VBOXSTUBPKGARCH enmArch = IsWow64() ? VBOXSTUBPKGARCH_AMD64 : VBOXSTUBPKGARCH_X86;
496 return pPackage->byArch == enmArch;
497}
498
499
500/**
501 * Adds a cleanup record.
502 *
503 * The caller must set the hFile or hDir if so desired.
504 *
505 * @returns Pointer to the cleanup record on success, fully complained NULL on
506 * failure.
507 * @param pszPath The path to the file or directory to clean up.
508 * @param fIsFile @c true if file, @c false if directory.
509 */
510static PSTUBCLEANUPREC AddCleanupRec(const char *pszPath, bool fIsFile)
511{
512 size_t cchPath = strlen(pszPath); Assert(cchPath > 0);
513 PSTUBCLEANUPREC pRec = (PSTUBCLEANUPREC)RTMemAllocZ(RT_UOFFSETOF_DYN(STUBCLEANUPREC, szPath[cchPath + 1]));
514 if (pRec)
515 {
516 pRec->idxPkg = ~0U;
517 pRec->fFile = fIsFile;
518 if (fIsFile)
519 pRec->hFile = NIL_RTFILE;
520 else
521 pRec->hDir = NIL_RTDIR;
522 memcpy(pRec->szPath, pszPath, cchPath + 1);
523
524 RTListPrepend(&g_TmpFiles, &pRec->ListEntry);
525 }
526 else
527 ShowError("Out of memory!");
528 return pRec;
529}
530
531
532/**
533 * Cleans up all the extracted files and optionally removes the package
534 * directory.
535 *
536 * @param pszPkgDir The package directory, NULL if it shouldn't be
537 * removed.
538 */
539static void CleanUp(const char *pszPkgDir)
540{
541 for (int i = 0; i < 5; i++)
542 {
543 bool const fFinalTry = i == 4;
544
545 PSTUBCLEANUPREC pCur, pNext;
546 RTListForEachSafe(&g_TmpFiles, pCur, pNext, STUBCLEANUPREC, ListEntry)
547 {
548 int rc = VINF_SUCCESS;
549 if (pCur->fFile)
550 {
551 if (pCur->hFile != NIL_RTFILE)
552 {
553 if (RTFileIsValid(pCur->hFile))
554 {
555 int rcCloseFile = RTFileClose(pCur->hFile);
556 AssertRCStmt(rcCloseFile, LogError("Cleanup file '%s' for #%u: RTFileClose(%p) failed: %Rrc",
557 pCur->szPath, pCur->idxPkg, pCur->hFile, rcCloseFile));
558 }
559 pCur->hFile = NIL_RTFILE;
560 }
561 if (!pCur->fDontDelete)
562 rc = RTFileDelete(pCur->szPath);
563 }
564 else /* Directory */
565 {
566 if (pCur->hDir != NIL_RTDIR)
567 {
568 if (RTDirIsValid(pCur->hDir))
569 {
570 int rcCloseDir = RTDirClose(pCur->hDir);
571 AssertRCStmt(rcCloseDir, LogError("Cleanup dir '%s' for #%u: RTDirClose(%p) failed: %Rrc",
572 pCur->szPath, pCur->idxPkg, pCur->hDir, rcCloseDir));
573 }
574 pCur->hDir = NIL_RTDIR;
575 }
576
577 /* Note: Not removing the directory recursively, as we should have separate cleanup records for that. */
578 if (!pCur->fDontDelete)
579 {
580 rc = RTDirRemove(pCur->szPath);
581 if (rc == VERR_DIR_NOT_EMPTY && fFinalTry)
582 rc = VINF_SUCCESS;
583 }
584 }
585 if (rc == VERR_FILE_NOT_FOUND || rc == VERR_PATH_NOT_FOUND)
586 rc = VINF_SUCCESS;
587 if (RT_SUCCESS(rc))
588 {
589 RTListNodeRemove(&pCur->ListEntry);
590 RTMemFree(pCur);
591 }
592 else if (fFinalTry)
593 {
594 if (pCur->fFile)
595 ShowError("Failed to delete temporary file '%s': %Rrc", pCur->szPath, rc);
596 else
597 ShowError("Failed to delete temporary directory '%s': %Rrc", pCur->szPath, rc);
598 }
599 }
600
601 if (RTListIsEmpty(&g_TmpFiles) || fFinalTry)
602 {
603 if (!pszPkgDir)
604 return;
605 int rc = RTDirRemove(pszPkgDir);
606 if (RT_SUCCESS(rc) || rc == VERR_FILE_NOT_FOUND || rc == VERR_PATH_NOT_FOUND || fFinalTry)
607 return;
608 }
609
610 /* Delay a little and try again. */
611 RTThreadSleep(i == 0 ? 100 : 3000);
612 }
613}
614
615
616/**
617 * Processes an MSI package.
618 *
619 * @returns Fully complained exit code.
620 * @param pszMsi The path to the MSI to process.
621 * @param pszMsiArgs Any additional installer (MSI) argument
622 * @param pszMsiLogFile Where to let MSI log its output to. NULL if logging is disabled.
623 */
624static RTEXITCODE ProcessMsiPackage(const char *pszMsi, const char *pszMsiArgs, const char *pszMsiLogFile)
625{
626 int rc;
627
628 /*
629 * Set UI level.
630 */
631 INSTALLUILEVEL enmDesiredUiLevel = g_fSilent ? INSTALLUILEVEL_NONE : INSTALLUILEVEL_FULL;
632 INSTALLUILEVEL enmRet = MsiSetInternalUI(enmDesiredUiLevel, NULL);
633 if (enmRet == INSTALLUILEVEL_NOCHANGE /* means error */)
634 return ShowError("Internal error: MsiSetInternalUI failed.");
635
636 /*
637 * Enable logging?
638 */
639 if (pszMsiLogFile)
640 {
641 PRTUTF16 pwszLogFile;
642 rc = RTStrToUtf16(pszMsiLogFile, &pwszLogFile);
643 if (RT_FAILURE(rc))
644 return ShowError("RTStrToUtf16 failed on '%s': %Rrc", pszMsiLogFile, rc);
645
646 UINT uLogLevel = MsiEnableLogW(INSTALLLOGMODE_VERBOSE,
647 pwszLogFile,
648 INSTALLLOGATTRIBUTES_FLUSHEACHLINE);
649 RTUtf16Free(pwszLogFile);
650 if (uLogLevel != ERROR_SUCCESS)
651 return ShowError("MsiEnableLogW failed");
652 }
653
654 /*
655 * Initialize the common controls (extended version). This is necessary to
656 * run the actual .MSI installers with the new fancy visual control
657 * styles (XP+). Also, an integrated manifest is required.
658 */
659 INITCOMMONCONTROLSEX ccEx;
660 ccEx.dwSize = sizeof(INITCOMMONCONTROLSEX);
661 ccEx.dwICC = ICC_LINK_CLASS | ICC_LISTVIEW_CLASSES | ICC_PAGESCROLLER_CLASS |
662 ICC_PROGRESS_CLASS | ICC_STANDARD_CLASSES | ICC_TAB_CLASSES | ICC_TREEVIEW_CLASSES |
663 ICC_UPDOWN_CLASS | ICC_USEREX_CLASSES | ICC_WIN95_CLASSES;
664 InitCommonControlsEx(&ccEx); /* Ignore failure. */
665
666 /*
667 * Convert both strings to UTF-16 and start the installation.
668 */
669 PRTUTF16 pwszMsi;
670 rc = RTStrToUtf16(pszMsi, &pwszMsi);
671 if (RT_FAILURE(rc))
672 return ShowError("RTStrToUtf16 failed on '%s': %Rrc", pszMsi, rc);
673 PRTUTF16 pwszMsiArgs;
674 rc = RTStrToUtf16(pszMsiArgs, &pwszMsiArgs);
675 if (RT_FAILURE(rc))
676 {
677 RTUtf16Free(pwszMsi);
678 return ShowError("RTStrToUtf16 failed on '%s': %Rrc", pszMsiArgs, rc);
679 }
680
681 UINT uStatus = MsiInstallProductW(pwszMsi, pwszMsiArgs);
682 RTUtf16Free(pwszMsi);
683 RTUtf16Free(pwszMsiArgs);
684
685 if (uStatus == ERROR_SUCCESS)
686 return RTEXITCODE_SUCCESS;
687 if (uStatus == ERROR_SUCCESS_REBOOT_REQUIRED)
688 {
689 if (g_fSilent)
690 RTMsgInfo("Reboot required (by %s)\n", pszMsi);
691 return (RTEXITCODE)uStatus;
692 }
693
694 /*
695 * Installation failed. Figure out what to say.
696 */
697 switch (uStatus)
698 {
699 case ERROR_INSTALL_USEREXIT:
700 /* Don't say anything? */
701 break;
702
703 case ERROR_INSTALL_PACKAGE_VERSION:
704 ShowError("This installation package cannot be installed by the Windows Installer service.\n"
705 "You must install a Windows service pack that contains a newer version of the Windows Installer service.");
706 break;
707
708 case ERROR_INSTALL_PLATFORM_UNSUPPORTED:
709 ShowError("This installation package is not supported on this platform.");
710 break;
711
712 default:
713 {
714 /*
715 * Try get windows to format the message.
716 */
717 DWORD dwFormatFlags = FORMAT_MESSAGE_ALLOCATE_BUFFER
718 | FORMAT_MESSAGE_IGNORE_INSERTS
719 | FORMAT_MESSAGE_FROM_SYSTEM;
720 HMODULE hModule = NULL;
721 if (uStatus >= NERR_BASE && uStatus <= MAX_NERR)
722 {
723 hModule = LoadLibraryExW(L"netmsg.dll",
724 NULL,
725 LOAD_LIBRARY_AS_DATAFILE);
726 if (hModule != NULL)
727 dwFormatFlags |= FORMAT_MESSAGE_FROM_HMODULE;
728 }
729
730 PWSTR pwszMsg;
731 if (FormatMessageW(dwFormatFlags,
732 hModule, /* If NULL, load system stuff. */
733 uStatus,
734 MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
735 (PWSTR)&pwszMsg,
736 0,
737 NULL) > 0)
738 {
739 ShowError("Installation failed! Error: %ls", pwszMsg);
740 LocalFree(pwszMsg);
741 }
742 else /* If text lookup failed, show at least the error number. */
743 ShowError("Installation failed! Error: %u", uStatus);
744
745 if (hModule)
746 FreeLibrary(hModule);
747 break;
748 }
749 }
750
751 return RTEXITCODE_FAILURE;
752}
753
754
755/**
756 * Processes a package.
757 *
758 * @returns Fully complained exit code.
759 * @param iPackage The package number.
760 * @param pszMsiArgs Any additional installer (MSI) argument
761 * @param pszMsiLogFile Where to let MSI log its output to. NULL if logging is disabled.
762 */
763static RTEXITCODE ProcessPackage(unsigned iPackage, const char *pszMsiArgs, const char *pszMsiLogFile)
764{
765 /*
766 * Get the package header and check if it's needed.
767 */
768 VBOXSTUBPKG const * const pPackage = FindPackageHeader(iPackage);
769 if (pPackage == NULL)
770 return RTEXITCODE_FAILURE;
771
772 if (!PackageIsNeeded(pPackage))
773 return RTEXITCODE_SUCCESS;
774
775 /*
776 * Get the cleanup record for the package so we can get the extracted
777 * filename (pPackage is read-only and thus cannot assist here).
778 */
779 PSTUBCLEANUPREC pRec = NULL;
780 PSTUBCLEANUPREC pCur;
781 RTListForEach(&g_TmpFiles, pCur, STUBCLEANUPREC, ListEntry)
782 {
783 if (pCur->idxPkg == iPackage)
784 {
785 pRec = pCur;
786 break;
787 }
788 }
789 AssertReturn(pRec != NULL, LogErrorExitFailure("Package #%u not found in cleanup records", iPackage));
790
791 /*
792 * Deal with the file based on it's extension.
793 */
794 RTPathChangeToDosSlashes(pRec->szPath, true /* Force conversion. */); /* paranoia */
795
796 RTEXITCODE rcExit;
797 const char *pszSuff = RTPathSuffix(pRec->szPath);
798 if (RTStrICmpAscii(pszSuff, ".msi") == 0)
799 rcExit = ProcessMsiPackage(pRec->szPath, pszMsiArgs, pszMsiLogFile);
800 else if (RTStrICmpAscii(pszSuff, ".cab") == 0)
801 rcExit = RTEXITCODE_SUCCESS; /* Ignore .cab files, they're generally referenced by other files. */
802 else
803 rcExit = ShowError("Internal error: Do not know how to handle file '%s' (%s).", pPackage->szFileName, pRec->szPath);
804 return rcExit;
805}
806
807#ifdef VBOX_WITH_CODE_SIGNING
808
809# ifdef VBOX_WITH_VBOX_LEGACY_TS_CA
810/**
811 * Install the timestamp CA currently needed to support legacy Windows versions.
812 *
813 * See @bugref{8691} for details.
814 *
815 * @returns Fully complained exit code.
816 */
817static RTEXITCODE InstallTimestampCA(bool fForce)
818{
819 /*
820 * Windows 10 desktop should be fine with attestation signed drivers, however
821 * the driver guard (DG) may alter that. Not sure yet how to detect, but
822 * OTOH 1809 and later won't accept the SHA-1 stuff regardless, so out of
823 * options there.
824 *
825 * The Windows 2016 server and later is not fine with attestation signed
826 * drivers, so we need to do the legacy trick there.
827 */
828 if ( !fForce
829 && RTSystemGetNtVersion() >= RTSYSTEM_MAKE_NT_VERSION(10, 0, 0)
830 && RTSystemGetNtProductType() == VER_NT_WORKSTATION)
831 return RTEXITCODE_SUCCESS;
832
833 if (!addCertToStore(CERT_SYSTEM_STORE_LOCAL_MACHINE, "Root", g_abVBoxLegacyWinCA, sizeof(g_abVBoxLegacyWinCA)))
834 return ShowError("Failed add the legacy Windows timestamp CA to the root certificate store.");
835 return RTEXITCODE_SUCCESS;
836}
837# endif /* VBOX_WITH_VBOX_LEGACY_TS_CA*/
838
839/**
840 * Install the public certificate into TrustedPublishers so the installer won't
841 * prompt the user during silent installs.
842 *
843 * @returns Fully complained exit code.
844 */
845static RTEXITCODE InstallCertificates(void)
846{
847 for (uint32_t i = 0; i < RT_ELEMENTS(g_aVBoxStubTrustedCerts); i++)
848 {
849 if (!addCertToStore(CERT_SYSTEM_STORE_LOCAL_MACHINE,
850 "TrustedPublisher",
851 g_aVBoxStubTrustedCerts[i].pab,
852 g_aVBoxStubTrustedCerts[i].cb))
853 return ShowError("Failed to add our certificate(s) to trusted publisher store.");
854 }
855 return RTEXITCODE_SUCCESS;
856}
857
858#endif /* VBOX_WITH_CODE_SIGNING */
859
860/**
861 * Copies the "<exepath>.custom" directory to the extraction path if it exists.
862 *
863 * This is used by the MSI packages from the resource section.
864 *
865 * @returns Fully complained exit code.
866 * @param pszDstDir The destination directory.
867 */
868static RTEXITCODE CopyCustomDir(const char *pszDstDir)
869{
870 char szSrcDir[RTPATH_MAX];
871 int rc = RTPathExecDir(szSrcDir, sizeof(szSrcDir));
872 if (RT_SUCCESS(rc))
873 rc = RTPathAppend(szSrcDir, sizeof(szSrcDir), ".custom");
874 if (RT_FAILURE(rc))
875 return ShowError("Failed to construct '.custom' dir path: %Rrc", rc);
876
877 if (RTDirExists(szSrcDir))
878 {
879 /*
880 * Use SHFileOperation w/ FO_COPY to do the job. This API requires an
881 * extra zero at the end of both source and destination paths.
882 */
883 size_t cwc;
884 RTUTF16 wszSrcDir[RTPATH_MAX + 1];
885 PRTUTF16 pwszSrcDir = wszSrcDir;
886 rc = RTStrToUtf16Ex(szSrcDir, RTSTR_MAX, &pwszSrcDir, RTPATH_MAX, &cwc);
887 if (RT_FAILURE(rc))
888 return ShowError("RTStrToUtf16Ex failed on '%s': %Rrc", szSrcDir, rc);
889 wszSrcDir[cwc] = '\0';
890
891 RTUTF16 wszDstDir[RTPATH_MAX + 1];
892 PRTUTF16 pwszDstDir = wszSrcDir;
893 rc = RTStrToUtf16Ex(pszDstDir, RTSTR_MAX, &pwszDstDir, RTPATH_MAX, &cwc);
894 if (RT_FAILURE(rc))
895 return ShowError("RTStrToUtf16Ex failed on '%s': %Rrc", pszDstDir, rc);
896 wszDstDir[cwc] = '\0';
897
898 SHFILEOPSTRUCTW FileOp;
899 RT_ZERO(FileOp); /* paranoia */
900 FileOp.hwnd = NULL;
901 FileOp.wFunc = FO_COPY;
902 FileOp.pFrom = wszSrcDir;
903 FileOp.pTo = wszDstDir;
904 FileOp.fFlags = FOF_SILENT
905 | FOF_NOCONFIRMATION
906 | FOF_NOCONFIRMMKDIR
907 | FOF_NOERRORUI;
908 FileOp.fAnyOperationsAborted = FALSE;
909 FileOp.hNameMappings = NULL;
910 FileOp.lpszProgressTitle = NULL;
911
912 rc = SHFileOperationW(&FileOp);
913 if (rc != 0) /* Not a Win32 status code! */
914 return ShowError("Copying the '.custom' dir failed: %#x", rc);
915
916 /*
917 * Add a cleanup record for recursively deleting the destination
918 * .custom directory. We should actually add this prior to calling
919 * SHFileOperationW since it may partially succeed...
920 */
921 char *pszDstSubDir = RTPathJoinA(pszDstDir, ".custom");
922 if (!pszDstSubDir)
923 return ShowError("Out of memory!");
924
925 PSTUBCLEANUPREC pCleanupRec = AddCleanupRec(pszDstSubDir, false /*fIsFile*/);
926 AssertReturn(pCleanupRec, RTEXITCODE_FAILURE);
927
928 /*
929 * Open the directory to make it difficult to replace or delete (see @bugref{10201}).
930 */
931 /** @todo this is still race prone, given that SHFileOperationW is the one
932 * creating it and we're really a bit late opening it here. Anyway,
933 * it's harmless as this code isn't used at present. */
934 RTDIR hDstSubDir;
935 rc = RTDirOpen(&hDstSubDir, pszDstSubDir);
936 if (RT_FAILURE(rc))
937 return ShowError("Unable to open the destination .custom directory: %Rrc", rc);
938 pCleanupRec->hDir = hDstSubDir;
939
940 RTStrFree(pszDstSubDir);
941 }
942
943 return RTEXITCODE_SUCCESS;
944}
945
946
947/**
948 * Extracts the files for all needed packages to @a pszDstDir.
949 *
950 * @returns
951 * @param cPackages Number of packages to consinder.
952 * @param pszDstDir Where to extract the files.
953 * @param fExtractOnly Set if only extracting and not doing any installing.
954 * @param ppExtractDirRec Where we keep the cleanup record for @a pszDstDir.
955 * This may have been created by the caller already.
956 */
957static RTEXITCODE ExtractFiles(unsigned cPackages, const char *pszDstDir, bool fExtractOnly, PSTUBCLEANUPREC *ppExtractDirRec)
958{
959 int rc;
960
961 /*
962 * Make sure the directory exists (normally WinMain created it for us).
963 */
964 PSTUBCLEANUPREC pCleanupRec = *ppExtractDirRec;
965 if (!RTDirExists(pszDstDir))
966 {
967 AssertReturn(!pCleanupRec, ShowError("RTDirExists failed on '%s' which we just created!", pszDstDir));
968
969 rc = RTDirCreate(pszDstDir, 0700, 0);
970 if (RT_FAILURE(rc))
971 return ShowError("Failed to create extraction path '%s': %Rrc", pszDstDir, rc);
972
973 *ppExtractDirRec = pCleanupRec = AddCleanupRec(pszDstDir, false /*fFile*/);
974 AssertReturn(pCleanupRec, LogErrorExitFailure("Failed to add cleanup record for dir '%s'", pszDstDir));
975 }
976 /*
977 * If we need to create the cleanup record, the caller did not create the
978 * directory so we should not delete it when done.
979 */
980 else if (!pCleanupRec)
981 {
982 *ppExtractDirRec = pCleanupRec = AddCleanupRec(pszDstDir, false /*fFile*/);
983 AssertReturn(pCleanupRec, LogErrorExitFailure("Failed to add cleanup record for existing dir '%s'", pszDstDir));
984 pCleanupRec->fDontDelete = true;
985 }
986
987 /*
988 * Open up the directory to make it difficult to delete / replace.
989 */
990 rc = RTDirOpen(&pCleanupRec->hDir, pszDstDir);
991 if (RT_FAILURE(rc))
992 return ShowError("Failed to open extraction path '%s': %Rrc", pszDstDir, rc);
993
994 /*
995 * Change current directory to the extraction directory for the same reason
996 * as we open it above.
997 */
998 RTPathSetCurrent(pszDstDir);
999
1000 /*
1001 * Extract files.
1002 */
1003 for (unsigned k = 0; k < cPackages; k++)
1004 {
1005 VBOXSTUBPKG const * const pPackage = FindPackageHeader(k);
1006 if (!pPackage)
1007 return RTEXITCODE_FAILURE; /* Done complaining already. */
1008
1009 if (fExtractOnly || PackageIsNeeded(pPackage))
1010 {
1011 /* If we only extract or if it's a common file, use the original file name,
1012 otherwise generate a random name with the same file extension (@bugref{10201}). */
1013 RTFILE hFile = NIL_RTFILE;
1014 char szDstFile[RTPATH_MAX];
1015 if (fExtractOnly || pPackage->byArch == VBOXSTUBPKGARCH_ALL)
1016 rc = RTPathJoin(szDstFile, sizeof(szDstFile), pszDstDir, pPackage->szFileName);
1017 else
1018 {
1019 rc = RTPathJoin(szDstFile, sizeof(szDstFile), pszDstDir, "XXXXXXXXXXXXXXXXXXXXXXXX");
1020 if (RT_SUCCESS(rc))
1021 {
1022 const char *pszSuffix = RTPathSuffix(pPackage->szFileName);
1023 if (pszSuffix)
1024 rc = RTStrCat(szDstFile, sizeof(szDstFile), pszSuffix);
1025 if (RT_SUCCESS(rc))
1026 {
1027 rc = RTFileCreateUnique(&hFile, szDstFile,
1028 RTFILE_O_CREATE | RTFILE_O_WRITE | RTFILE_O_DENY_WRITE
1029 | (0700 << RTFILE_O_CREATE_MODE_SHIFT));
1030 if (RT_FAILURE(rc))
1031 return ShowError("Failed to create unique filename for '%s' in '%s': %Rrc",
1032 pPackage->szFileName, pszDstDir, rc);
1033 }
1034 }
1035 }
1036 if (RT_FAILURE(rc))
1037 return ShowError("Internal error: Build extraction file name failed: %Rrc", rc);
1038
1039 rc = Extract(pPackage, szDstFile, hFile, k);
1040 if (RT_FAILURE(rc))
1041 return ShowError("Error extracting package #%u (%s): %Rrc", k, pPackage->szFileName, rc);
1042 }
1043 }
1044
1045 return RTEXITCODE_SUCCESS;
1046}
1047
1048
1049int WINAPI WinMain(HINSTANCE hInstance,
1050 HINSTANCE hPrevInstance,
1051 char *lpCmdLine,
1052 int nCmdShow)
1053{
1054 RT_NOREF(hInstance, hPrevInstance, lpCmdLine, nCmdShow);
1055 char **argv = __argv;
1056 int argc = __argc;
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 = true;
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 { "--no-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 fForceTimestampCaInstall = fInstallTimestampCA = false;
1167 break;
1168 case 'T':
1169 fForceTimestampCaInstall = false;
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 = CreateMutex(NULL, FALSE, "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 freopen("CONOUT$", "w", stdout);
1382 setvbuf(stdout, NULL, _IONBF, 0);
1383
1384 freopen("CONOUT$", "w", stderr);
1385 }
1386#endif /* VBOX_STUB_WITH_OWN_CONSOLE */
1387
1388 /* Convenience: Enable logging if a log file (via --log-file) is specified. */
1389 if ( !fEnableLogging
1390 && szMSILogFile[0] != '\0')
1391 fEnableLogging = true;
1392
1393 if ( fEnableLogging
1394 && szMSILogFile[0] == '\0') /* No log file explicitly specified? Use the extract path by default. */
1395 {
1396 vrc = RTStrCopy(szMSILogFile, sizeof(szMSILogFile), szExtractPath);
1397 if (RT_SUCCESS(vrc))
1398 vrc = RTPathAppend(szMSILogFile, sizeof(szMSILogFile), "VBoxInstallLog.txt");
1399 if (RT_FAILURE(vrc))
1400 return ShowError("Error creating MSI log file name, rc=%Rrc", vrc);
1401 }
1402
1403 if (g_iVerbosity)
1404 {
1405 RTPrintf("Extraction path : %s\n", szExtractPath);
1406 RTPrintf("Silent installation : %RTbool\n", g_fSilent);
1407#ifdef VBOX_WITH_CODE_SIGNING
1408 RTPrintf("Certificate installation : %RTbool\n", fEnableSilentCert);
1409#endif
1410 RTPrintf("Additional MSI parameters: %s\n", szMSIArgs[0] ? szMSIArgs : "<None>");
1411 RTPrintf("Logging to file : %s\n", szMSILogFile[0] ? szMSILogFile : "<None>");
1412 }
1413
1414 /*
1415 * 32-bit is not officially supported any more.
1416 */
1417 if ( !fExtractOnly
1418 && !g_fSilent
1419 && !IsWow64())
1420 rcExit = ShowError("32-bit Windows hosts are not supported by this VirtualBox release.");
1421 else
1422 {
1423 /*
1424 * Read our manifest.
1425 */
1426 VBOXSTUBPKGHEADER const *pHeader = NULL;
1427 vrc = FindData("MANIFEST", (uint8_t const **)&pHeader, NULL);
1428 if (RT_SUCCESS(vrc))
1429 {
1430 /** @todo If we could, we should validate the header. Only the magic isn't
1431 * commonly defined, nor the version number... */
1432
1433 /*
1434 * Up to this point, we haven't done anything that requires any cleanup.
1435 * From here on, we do everything in functions so we can counter clean up.
1436 */
1437 rcExit = ExtractFiles(pHeader->byCntPkgs, szExtractPath, fExtractOnly, &pExtractDirRec);
1438 if (rcExit == RTEXITCODE_SUCCESS)
1439 {
1440 if (fExtractOnly)
1441 ShowInfo("Files were extracted to: %s", szExtractPath);
1442 else
1443 {
1444 rcExit = CopyCustomDir(szExtractPath);
1445#ifdef VBOX_WITH_CODE_SIGNING
1446# ifdef VBOX_WITH_VBOX_LEGACY_TS_CA
1447 if (rcExit == RTEXITCODE_SUCCESS && fInstallTimestampCA)
1448 rcExit = InstallTimestampCA(fForceTimestampCaInstall);
1449# endif
1450 if (rcExit == RTEXITCODE_SUCCESS && fEnableSilentCert && g_fSilent)
1451 rcExit = InstallCertificates();
1452#endif
1453 unsigned iPackage = 0;
1454 while ( iPackage < pHeader->byCntPkgs
1455 && (rcExit == RTEXITCODE_SUCCESS || rcExit == (RTEXITCODE)ERROR_SUCCESS_REBOOT_REQUIRED))
1456 {
1457 RTEXITCODE rcExit2 = ProcessPackage(iPackage, szMSIArgs, szMSILogFile[0] ? szMSILogFile : NULL);
1458 if (rcExit2 != RTEXITCODE_SUCCESS)
1459 rcExit = rcExit2;
1460 iPackage++;
1461 }
1462 }
1463 }
1464
1465 /*
1466 * Do cleanups unless we're only extracting (ignoring failures for now).
1467 */
1468 if (!fExtractOnly)
1469 {
1470 RTPathSetCurrent("..");
1471 CleanUp(!fEnableLogging && pExtractDirRec && !pExtractDirRec->fDontDelete ? szExtractPath : NULL);
1472 }
1473
1474 /* Free any left behind cleanup records (not strictly needed). */
1475 PSTUBCLEANUPREC pCur, pNext;
1476 RTListForEachSafe(&g_TmpFiles, pCur, pNext, STUBCLEANUPREC, ListEntry)
1477 {
1478 RTListNodeRemove(&pCur->ListEntry);
1479 RTMemFree(pCur);
1480 }
1481 }
1482 else
1483 rcExit = ShowError("Internal package error: Manifest not found (%Rrc)", vrc);
1484 }
1485
1486#if defined(_WIN32_WINNT) && _WIN32_WINNT >= 0x0501
1487# ifdef VBOX_STUB_WITH_OWN_CONSOLE
1488 if (g_iVerbosity)
1489 FreeConsole();
1490# endif /* VBOX_STUB_WITH_OWN_CONSOLE */
1491#endif
1492
1493 /*
1494 * Release instance mutex just to be on the safe side.
1495 */
1496 if (hMutexAppRunning != NULL)
1497 CloseHandle(hMutexAppRunning);
1498
1499 return rcExit != (RTEXITCODE)ERROR_SUCCESS_REBOOT_REQUIRED || !fIgnoreReboot ? rcExit : RTEXITCODE_SUCCESS;
1500}
Note: See TracBrowser for help on using the repository browser.

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