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
RevLine 
[31659]1/* $Id: VBoxStub.cpp 98103 2023-01-17 14:15:46Z vboxsync $ */
2/** @file
3 * VBoxStub - VirtualBox's Windows installer stub.
4 */
5
6/*
[98103]7 * Copyright (C) 2010-2023 Oracle and/or its affiliates.
[31659]8 *
[96407]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
[31659]26 */
27
[57358]28
29/*********************************************************************************************************************************
30* Header Files *
31*********************************************************************************************************************************/
[62679]32#include <iprt/win/windows.h>
[85121]33#include <iprt/win/commctrl.h>
[34195]34#include <lmerr.h>
[31659]35#include <msiquery.h>
[62780]36#include <iprt/win/objbase.h>
[63311]37#include <iprt/win/shlobj.h>
[31659]38
39#include <VBox/version.h>
40
41#include <iprt/assert.h>
42#include <iprt/dir.h>
[76370]43#include <iprt/err.h>
[31659]44#include <iprt/file.h>
[45332]45#include <iprt/getopt.h>
[31659]46#include <iprt/initterm.h>
[45332]47#include <iprt/list.h>
[31659]48#include <iprt/mem.h>
[43850]49#include <iprt/message.h>
[45332]50#include <iprt/param.h>
[31659]51#include <iprt/path.h>
[43850]52#include <iprt/stream.h>
[31659]53#include <iprt/string.h>
[95696]54#include <iprt/system.h>
[31659]55#include <iprt/thread.h>
[76420]56#include <iprt/utf16.h>
[31659]57
[96571]58#ifndef IPRT_NO_CRT
59# include <stdio.h>
60# include <stdlib.h>
61#endif
62
[31659]63#include "VBoxStub.h"
64#include "../StubBld/VBoxStubBld.h"
65#include "resource.h"
66
[45320]67#ifdef VBOX_WITH_CODE_SIGNING
68# include "VBoxStubCertUtil.h"
69# include "VBoxStubPublicCert.h"
[43894]70#endif
[43878]71
[31659]72
[57358]73/*********************************************************************************************************************************
74* Defined Constants And Macros *
75*********************************************************************************************************************************/
[45332]76#define MY_UNICODE_SUB(str) L ##str
77#define MY_UNICODE(str) MY_UNICODE_SUB(str)
[31659]78
[93391]79/* Use an own console window if run in verbose mode. */
80#define VBOX_STUB_WITH_OWN_CONSOLE
[45332]81
[93391]82
[57358]83/*********************************************************************************************************************************
84* Structures and Typedefs *
85*********************************************************************************************************************************/
[45332]86/**
87 * Cleanup record.
88 */
89typedef struct STUBCLEANUPREC
90{
91 /** List entry. */
92 RTLISTNODE ListEntry;
[94284]93 /** Stub package index (zero-based) this record belongs to. */
94 unsigned idxPkg;
[45332]95 /** True if file, false if directory. */
96 bool fFile;
[94313]97 /** Set if we should not delete the file/directory.
98 * This is used for user supplied extraction directories. */
99 bool fDontDelete;
[94284]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 };
[45332]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
[94285]113
[94284]114/*********************************************************************************************************************************
115* Prototypes *
116*********************************************************************************************************************************/
[94313]117static PSTUBCLEANUPREC AddCleanupRec(const char *pszPath, bool fIsFile);
[45332]118
[94284]119
[57358]120/*********************************************************************************************************************************
121* Global Variables *
122*********************************************************************************************************************************/
[45332]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;
[51170]127/** Verbosity flag. */
128static int g_iVerbosity = 0;
[43850]129
130
[45332]131
[31659]132/**
[43850]133 * Shows an error message box with a printf() style formatted string.
[31659]134 *
[45320]135 * @returns RTEXITCODE_FAILURE
[31659]136 * @param pszFmt Printf-style format string to show in the message box body.
137 *
138 */
[45320]139static RTEXITCODE ShowError(const char *pszFmt, ...)
[31659]140{
141 char *pszMsg;
142 va_list va;
143
144 va_start(va, pszFmt);
[43850]145 if (RTStrAPrintfV(&pszMsg, pszFmt, va))
146 {
147 if (g_fSilent)
148 RTMsgError("%s", pszMsg);
149 else
[45332]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 }
[43850]161 RTStrFree(pszMsg);
162 }
163 else /* Should never happen! */
164 AssertMsgFailed(("Failed to format error text of format string: %s!\n", pszFmt));
[31659]165 va_end(va);
[45320]166 return RTEXITCODE_FAILURE;
[31659]167}
168
169
170/**
[93390]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/**
[43850]184 * Shows a message box with a printf() style formatted string.
[31659]185 *
[43850]186 * @param uType Type of the message box (see MSDN).
[31659]187 * @param pszFmt Printf-style format string to show in the message box body.
188 *
189 */
[43850]190static void ShowInfo(const char *pszFmt, ...)
[31659]191{
192 char *pszMsg;
193 va_list va;
194 va_start(va, pszFmt);
[43850]195 int rc = RTStrAPrintfV(&pszMsg, pszFmt, va);
196 va_end(va);
197 if (rc >= 0)
[34195]198 {
[43850]199 if (g_fSilent)
200 RTPrintf("%s\n", pszMsg);
201 else
[45332]202 {
203 PRTUTF16 pwszMsg;
[83808]204 rc = RTStrToUtf16(pszMsg, &pwszMsg);
[45332]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 }
[34195]213 }
214 else /* Should never happen! */
215 AssertMsgFailed(("Failed to format error text of format string: %s!\n", pszFmt));
[43850]216 RTStrFree(pszMsg);
[31659]217}
218
219
[94313]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
[31659]252/**
[45320]253 * Finds the specified in the resource section of the executable.
[31659]254 *
[45320]255 * @returns IPRT status code.
[31659]256 *
[94313]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.
[31659]261 */
[94313]262static int FindData(const char *pszDataName, uint8_t const **ppbResource, DWORD *pcbResource)
[31659]263{
[45318]264 AssertReturn(pszDataName, VERR_INVALID_PARAMETER);
[45332]265 HINSTANCE hInst = NULL; /* indicates the executable image */
[31659]266
[45318]267 /* Find our resource. */
[45332]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);
[45318]276 AssertReturn(hRsrc, VERR_IO_GEN_FAILURE);
[31659]277
[45318]278 /* Get resource size. */
[45320]279 DWORD cb = SizeofResource(hInst, hRsrc);
280 AssertReturn(cb > 0, VERR_NO_DATA);
[94313]281 if (pcbResource)
282 *pcbResource = cb;
[31659]283
[45318]284 /* Get pointer to resource. */
285 HGLOBAL hData = LoadResource(hInst, hRsrc);
286 AssertReturn(hData, VERR_IO_GEN_FAILURE);
[31659]287
[45318]288 /* Lock resource. */
[94313]289 *ppbResource = (uint8_t const *)LockResource(hData);
290 AssertReturn(*ppbResource, VERR_IO_GEN_FAILURE);
[45318]291 return VINF_SUCCESS;
[31659]292}
293
294
295/**
[45320]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 */
[94313]302static const VBOXSTUBPKG *FindPackageHeader(unsigned iPackage)
[45320]303{
304 char szHeaderName[32];
305 RTStrPrintf(szHeaderName, sizeof(szHeaderName), "HDR_%02d", iPackage);
306
[94313]307 VBOXSTUBPKG const *pPackage;
308 int rc = FindData(szHeaderName, (uint8_t const **)&pPackage, NULL);
[45320]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/**
[31659]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{
[33462]335 if (RTStrAPrintf(ppszTempFile, "%s\\%s", pszTempPath, pszTargetFileName) >= 0)
336 return VINF_SUCCESS;
337 return VERR_NO_STR_MEMORY;
[31659]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.
[94313]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).
[31659]352 */
[94313]353static int ExtractFile(const char *pszResourceName, const char *pszTempFile, RTFILE hFile, unsigned idxPackage)
[31659]354{
[63131]355 AssertPtrReturn(pszResourceName, VERR_INVALID_POINTER);
356 AssertPtrReturn(pszTempFile, VERR_INVALID_POINTER);
357
[94313]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 }
[63131]366
[94313]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);
[94284]371
[94313]372 pCleanupRec->idxPkg = idxPackage;
373 pCleanupRec->hFile = hFile;
[63131]374
[94313]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));
[94284]380
[94313]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));
[63131]384
[94313]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));
[63131]402
[94313]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
[94284]408 rc = RTFileClose(hFile);
[94313]409 AssertRCReturnStmt(rc, RTFileClose(hFile2),
410 LogErrorRc(rc, "#%u: RTFileClose('%s') failed: %Rrc", idxPackage, pszTempFile, rc));
411 pCleanupRec->hFile = hFile2;
[94284]412
[94313]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;
[94284]416
[94313]417 rc = RTFileClose(hFile2);
418 AssertRCStmt(rc, LogError("#%u: Failed to close 2nd handle to '%s': %Rrc", idxPackage, pszTempFile, rc));
[94284]419
[94313]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));
[63131]424
[94313]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;
[31659]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.
[94313]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).
[31659]464 */
[94313]465static int Extract(VBOXSTUBPKG const *pPackage, const char *pszTempFile, RTFILE hFile, unsigned idxPackage)
[31659]466{
[94313]467 return ExtractFile(pPackage->szResourceName, pszTempFile, hFile, idxPackage);
[31659]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{
[75812]478 BOOL fIsWow64 = TRUE;
[31659]479 fnIsWow64Process = (LPFN_ISWOW64PROCESS)GetProcAddress(GetModuleHandle(TEXT("kernel32")), "IsWow64Process");
480 if (NULL != fnIsWow64Process)
481 {
[75812]482 if (!fnIsWow64Process(GetCurrentProcess(), &fIsWow64))
[31659]483 {
484 /* Error in retrieving process type - assume that we're running on 32bit. */
485 return FALSE;
486 }
487 }
[75812]488 return fIsWow64;
[31659]489}
490
491
492/**
493 * Decides whether we need a specified package to handle or not.
494 *
[45332]495 * @returns @c true if we need to handle the specified package, @c false if not.
[31659]496 *
497 * @param pPackage Pointer to a VBOXSTUBPKG struct that contains the resource.
498 */
[94313]499static bool PackageIsNeeded(VBOXSTUBPKG const *pPackage)
[31659]500{
[96571]501 if (pPackage->enmArch == VBOXSTUBPKGARCH_ALL)
[45332]502 return true;
503 VBOXSTUBPKGARCH enmArch = IsWow64() ? VBOXSTUBPKGARCH_AMD64 : VBOXSTUBPKGARCH_X86;
[96571]504 return pPackage->enmArch == enmArch;
[45332]505}
506
507
508/**
509 * Adds a cleanup record.
510 *
[94313]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.
[45332]515 * @param pszPath The path to the file or directory to clean up.
[94313]516 * @param fIsFile @c true if file, @c false if directory.
[45332]517 */
[94313]518static PSTUBCLEANUPREC AddCleanupRec(const char *pszPath, bool fIsFile)
[45332]519{
520 size_t cchPath = strlen(pszPath); Assert(cchPath > 0);
[94284]521 PSTUBCLEANUPREC pRec = (PSTUBCLEANUPREC)RTMemAllocZ(RT_UOFFSETOF_DYN(STUBCLEANUPREC, szPath[cchPath + 1]));
[94313]522 if (pRec)
[31659]523 {
[94313]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);
[31659]533 }
[94284]534 else
[94313]535 ShowError("Out of memory!");
536 return pRec;
[45332]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 */
[63130]547static void CleanUp(const char *pszPkgDir)
[45332]548{
549 for (int i = 0; i < 5; i++)
[31659]550 {
[94313]551 bool const fFinalTry = i == 4;
[45332]552
553 PSTUBCLEANUPREC pCur, pNext;
554 RTListForEachSafe(&g_TmpFiles, pCur, pNext, STUBCLEANUPREC, ListEntry)
555 {
[94313]556 int rc = VINF_SUCCESS;
[45332]557 if (pCur->fFile)
558 {
[94313]559 if (pCur->hFile != NIL_RTFILE)
[94284]560 {
[94313]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;
[94284]568 }
[94313]569 if (!pCur->fDontDelete)
[94284]570 rc = RTFileDelete(pCur->szPath);
[45332]571 }
[94284]572 else /* Directory */
573 {
[94313]574 if (pCur->hDir != NIL_RTDIR)
[94284]575 {
[94313]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;
[94284]583 }
584
[94313]585 /* Note: Not removing the directory recursively, as we should have separate cleanup records for that. */
586 if (!pCur->fDontDelete)
[94284]587 {
588 rc = RTDirRemove(pCur->szPath);
589 if (rc == VERR_DIR_NOT_EMPTY && fFinalTry)
590 rc = VINF_SUCCESS;
591 }
592 }
[45332]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;
[94313]613 int rc = RTDirRemove(pszPkgDir);
[45332]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);
[31659]620 }
621}
622
623
624/**
[45320]625 * Processes an MSI package.
[31659]626 *
[45320]627 * @returns Fully complained exit code.
628 * @param pszMsi The path to the MSI to process.
629 * @param pszMsiArgs Any additional installer (MSI) argument
[95026]630 * @param pszMsiLogFile Where to let MSI log its output to. NULL if logging is disabled.
[45320]631 */
[95026]632static RTEXITCODE ProcessMsiPackage(const char *pszMsi, const char *pszMsiArgs, const char *pszMsiLogFile)
[45320]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 */
[95026]647 if (pszMsiLogFile)
[45320]648 {
649 PRTUTF16 pwszLogFile;
[95026]650 rc = RTStrToUtf16(pszMsiLogFile, &pwszLogFile);
[45320]651 if (RT_FAILURE(rc))
[95026]652 return ShowError("RTStrToUtf16 failed on '%s': %Rrc", pszMsiLogFile, rc);
[45320]653
654 UINT uLogLevel = MsiEnableLogW(INSTALLLOGMODE_VERBOSE,
655 pwszLogFile,
[45332]656 INSTALLLOGATTRIBUTES_FLUSHEACHLINE);
[45320]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);
[95030]686 return ShowError("RTStrToUtf16 failed on '%s': %Rrc", pszMsiArgs, rc);
[45320]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)
[80231]696 {
697 if (g_fSilent)
698 RTMsgInfo("Reboot required (by %s)\n", pszMsi);
699 return (RTEXITCODE)uStatus;
700 }
[45320]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 {
[45332]731 hModule = LoadLibraryExW(L"netmsg.dll",
732 NULL,
733 LOAD_LIBRARY_AS_DATAFILE);
[45320]734 if (hModule != NULL)
735 dwFormatFlags |= FORMAT_MESSAGE_FROM_HMODULE;
736 }
737
[45332]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)
[45320]746 {
[45332]747 ShowError("Installation failed! Error: %ls", pwszMsg);
748 LocalFree(pwszMsg);
[45320]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.
[31659]765 *
[45320]766 * @returns Fully complained exit code.
767 * @param iPackage The package number.
768 * @param pszMsiArgs Any additional installer (MSI) argument
[95026]769 * @param pszMsiLogFile Where to let MSI log its output to. NULL if logging is disabled.
[45320]770 */
[95026]771static RTEXITCODE ProcessPackage(unsigned iPackage, const char *pszMsiArgs, const char *pszMsiLogFile)
[45320]772{
773 /*
774 * Get the package header and check if it's needed.
775 */
[94313]776 VBOXSTUBPKG const * const pPackage = FindPackageHeader(iPackage);
[45320]777 if (pPackage == NULL)
778 return RTEXITCODE_FAILURE;
779
780 if (!PackageIsNeeded(pPackage))
781 return RTEXITCODE_SUCCESS;
782
783 /*
[94313]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).
[45320]786 */
[94284]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 }
[94313]797 AssertReturn(pRec != NULL, LogErrorExitFailure("Package #%u not found in cleanup records", iPackage));
[94284]798
[94313]799 /*
800 * Deal with the file based on it's extension.
801 */
802 RTPathChangeToDosSlashes(pRec->szPath, true /* Force conversion. */); /* paranoia */
[94284]803
[45320]804 RTEXITCODE rcExit;
[94313]805 const char *pszSuff = RTPathSuffix(pRec->szPath);
806 if (RTStrICmpAscii(pszSuff, ".msi") == 0)
[95026]807 rcExit = ProcessMsiPackage(pRec->szPath, pszMsiArgs, pszMsiLogFile);
[94313]808 else if (RTStrICmpAscii(pszSuff, ".cab") == 0)
[45332]809 rcExit = RTEXITCODE_SUCCESS; /* Ignore .cab files, they're generally referenced by other files. */
[45320]810 else
[96571]811 rcExit = ShowError("Internal error: Do not know how to handle file '%s' (%s).", pPackage->szFilename, pRec->szPath);
[45320]812 return rcExit;
813}
814
[95699]815#ifdef VBOX_WITH_CODE_SIGNING
[45320]816
[95699]817# ifdef VBOX_WITH_VBOX_LEGACY_TS_CA
[45320]818/**
[95699]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/**
[45320]848 * Install the public certificate into TrustedPublishers so the installer won't
849 * prompt the user during silent installs.
[31659]850 *
[45320]851 * @returns Fully complained exit code.
[31659]852 */
[64927]853static RTEXITCODE InstallCertificates(void)
[31659]854{
[64927]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))
[95699]861 return ShowError("Failed to add our certificate(s) to trusted publisher store.");
[64927]862 }
863 return RTEXITCODE_SUCCESS;
[45320]864}
[95699]865
[45320]866#endif /* VBOX_WITH_CODE_SIGNING */
[31659]867
[45320]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];
[45332]879 int rc = RTPathExecDir(szSrcDir, sizeof(szSrcDir));
[45320]880 if (RT_SUCCESS(rc))
[45332]881 rc = RTPathAppend(szSrcDir, sizeof(szSrcDir), ".custom");
[45320]882 if (RT_FAILURE(rc))
883 return ShowError("Failed to construct '.custom' dir path: %Rrc", rc);
884
885 if (RTDirExists(szSrcDir))
[31659]886 {
[45320]887 /*
888 * Use SHFileOperation w/ FO_COPY to do the job. This API requires an
[45332]889 * extra zero at the end of both source and destination paths.
[45320]890 */
891 size_t cwc;
[45332]892 RTUTF16 wszSrcDir[RTPATH_MAX + 1];
[45320]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
[45332]899 RTUTF16 wszDstDir[RTPATH_MAX + 1];
[45320]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);
[45332]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!");
[94284]932
[94313]933 PSTUBCLEANUPREC pCleanupRec = AddCleanupRec(pszDstSubDir, false /*fIsFile*/);
934 AssertReturn(pCleanupRec, RTEXITCODE_FAILURE);
935
[94284]936 /*
[94313]937 * Open the directory to make it difficult to replace or delete (see @bugref{10201}).
[94284]938 */
[94313]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. */
[94284]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);
[94313]946 pCleanupRec->hDir = hDstSubDir;
[94284]947
[45332]948 RTStrFree(pszDstSubDir);
[31659]949 }
[45320]950
951 return RTEXITCODE_SUCCESS;
[31659]952}
953
954
[94313]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)
[45320]966{
967 int rc;
968
969 /*
[94313]970 * Make sure the directory exists (normally WinMain created it for us).
[45320]971 */
[94313]972 PSTUBCLEANUPREC pCleanupRec = *ppExtractDirRec;
[45320]973 if (!RTDirExists(pszDstDir))
974 {
[94313]975 AssertReturn(!pCleanupRec, ShowError("RTDirExists failed on '%s' which we just created!", pszDstDir));
976
[45320]977 rc = RTDirCreate(pszDstDir, 0700, 0);
978 if (RT_FAILURE(rc))
979 return ShowError("Failed to create extraction path '%s': %Rrc", pszDstDir, rc);
[94284]980
[94313]981 *ppExtractDirRec = pCleanupRec = AddCleanupRec(pszDstDir, false /*fFile*/);
982 AssertReturn(pCleanupRec, LogErrorExitFailure("Failed to add cleanup record for dir '%s'", pszDstDir));
[45320]983 }
[94313]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 }
[45320]994
[94313]995 /*
996 * Open up the directory to make it difficult to delete / replace.
997 */
998 rc = RTDirOpen(&pCleanupRec->hDir, pszDstDir);
[94284]999 if (RT_FAILURE(rc))
1000 return ShowError("Failed to open extraction path '%s': %Rrc", pszDstDir, rc);
1001
[94313]1002 /*
1003 * Change current directory to the extraction directory for the same reason
1004 * as we open it above.
1005 */
1006 RTPathSetCurrent(pszDstDir);
[94284]1007
[45320]1008 /*
1009 * Extract files.
1010 */
1011 for (unsigned k = 0; k < cPackages; k++)
1012 {
[94313]1013 VBOXSTUBPKG const * const pPackage = FindPackageHeader(k);
[45320]1014 if (!pPackage)
1015 return RTEXITCODE_FAILURE; /* Done complaining already. */
1016
1017 if (fExtractOnly || PackageIsNeeded(pPackage))
1018 {
[94313]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];
[96571]1023 if (fExtractOnly || pPackage->enmArch == VBOXSTUBPKGARCH_ALL)
1024 rc = RTPathJoin(szDstFile, sizeof(szDstFile), pszDstDir, pPackage->szFilename);
[94313]1025 else
[94284]1026 {
[94313]1027 rc = RTPathJoin(szDstFile, sizeof(szDstFile), pszDstDir, "XXXXXXXXXXXXXXXXXXXXXXXX");
[94284]1028 if (RT_SUCCESS(rc))
1029 {
[96571]1030 const char *pszSuffix = RTPathSuffix(pPackage->szFilename);
[94313]1031 if (pszSuffix)
1032 rc = RTStrCat(szDstFile, sizeof(szDstFile), pszSuffix);
[94284]1033 if (RT_SUCCESS(rc))
[94313]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",
[96571]1040 pPackage->szFilename, pszDstDir, rc);
[94313]1041 }
[94284]1042 }
1043 }
[45332]1044 if (RT_FAILURE(rc))
[94284]1045 return ShowError("Internal error: Build extraction file name failed: %Rrc", rc);
[45320]1046
[94313]1047 rc = Extract(pPackage, szDstFile, hFile, k);
[45320]1048 if (RT_FAILURE(rc))
[96571]1049 return ShowError("Error extracting package #%u (%s): %Rrc", k, pPackage->szFilename, rc);
[45320]1050 }
1051 }
1052
1053 return RTEXITCODE_SUCCESS;
1054}
1055
[96571]1056int main(int argc, char **argv)
[31659]1057{
[59404]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 /*
[43850]1066 * Parse arguments.
1067 */
[31659]1068
[43850]1069 /* Parameter variables. */
1070 bool fExtractOnly = false;
1071 bool fEnableLogging = false;
[45320]1072#ifdef VBOX_WITH_CODE_SIGNING
[43878]1073 bool fEnableSilentCert = true;
[95699]1074 bool fInstallTimestampCA = true;
[95729]1075 bool fForceTimestampCaInstall = false;
[43894]1076#endif
[80231]1077 bool fIgnoreReboot = false;
[33462]1078 char szExtractPath[RTPATH_MAX] = {0};
[51170]1079 char szMSIArgs[_4K] = {0};
[96571]1080 char szMSILogFile[RTPATH_MAX] = {0};
[31659]1081
[95026]1082 /* Argument enumeration IDs. */
1083 enum KVBOXSTUBOPT
1084 {
1085 KVBOXSTUBOPT_MSI_LOG_FILE = 1000
1086 };
1087
[43850]1088 /* Parameter definitions. */
1089 static const RTGETOPTDEF s_aOptions[] =
[31659]1090 {
[51126]1091 /** @todo Replace short parameters with enums since they're not
1092 * used (and not documented to the public). */
[95026]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 },
[45320]1099#ifdef VBOX_WITH_CODE_SIGNING
[95026]1100 { "--no-silent-cert", 'c', RTGETOPT_REQ_NOTHING },
1101 { "-no-silent-cert", 'c', RTGETOPT_REQ_NOTHING },
1102 { "/no-silent-cert", 'c', RTGETOPT_REQ_NOTHING },
[95699]1103 { "--no-install-timestamp-ca", 't', RTGETOPT_REQ_NOTHING },
[95729]1104 { "--force-install-timestamp-ca", 'T', RTGETOPT_REQ_NOTHING },
[43894]1105#endif
[95026]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 },
[43850]1131 };
[31659]1132
[93391]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));
[51170]1136
[93391]1137 /* Loop over the arguments. */
[43850]1138 int ch;
1139 RTGETOPTUNION ValueUnion;
[93391]1140 while ((ch = RTGetOpt(&GetState, &ValueUnion)) != 0)
[43850]1141 {
1142 switch (ch)
[31659]1143 {
[51126]1144 case 'f': /* Force re-installation. */
1145 if (szMSIArgs[0])
1146 vrc = RTStrCat(szMSIArgs, sizeof(szMSIArgs), " ");
1147 if (RT_SUCCESS(vrc))
[93391]1148 vrc = RTStrCat(szMSIArgs, sizeof(szMSIArgs), "REINSTALLMODE=vomus REINSTALL=ALL");
[51126]1149 if (RT_FAILURE(vrc))
[93391]1150 return ShowSyntaxError("Out of space for MSI parameters and properties");
[51126]1151 break;
1152
[43850]1153 case 'x':
1154 fExtractOnly = true;
1155 break;
[31659]1156
[43850]1157 case 's':
1158 g_fSilent = true;
1159 break;
[31659]1160
[45320]1161#ifdef VBOX_WITH_CODE_SIGNING
[43878]1162 case 'c':
1163 fEnableSilentCert = false;
1164 break;
[95699]1165 case 't':
[95729]1166 fInstallTimestampCA = false;
[95699]1167 break;
1168 case 'T':
[95729]1169 fForceTimestampCaInstall = fInstallTimestampCA = true;
[95699]1170 break;
[43894]1171#endif
[43850]1172 case 'l':
1173 fEnableLogging = true;
1174 break;
[31659]1175
[95026]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
[43850]1187 case 'p':
[94313]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 }
[43850]1196 break;
[31659]1197
[43850]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))
[93391]1204 return ShowSyntaxError("Out of space for MSI parameters and properties");
[43850]1205 break;
[31659]1206
[93390]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)
[93391]1219 return ShowSyntaxError("Out of space for MSI parameters and properties");
[93390]1220 }
1221 else if (vrc == VERR_GETOPT_REQUIRED_ARGUMENT_MISSING)
[93391]1222 return ShowSyntaxError("--msi-prop takes two arguments, the 2nd is missing");
[93390]1223 else
[93391]1224 return ShowSyntaxError("Failed to get 2nd --msi-prop argument: %Rrc", vrc);
[93390]1225 }
1226 else
[93391]1227 return ShowSyntaxError("The first argument to --msi-prop must not contain spaces: %s", pszProp);
[93390]1228 break;
1229 }
1230
[80231]1231 case 'r':
1232 fIgnoreReboot = true;
1233 break;
1234
[43850]1235 case 'V':
[93390]1236 ShowInfo("Version: %u.%u.%ur%u", VBOX_VERSION_MAJOR, VBOX_VERSION_MINOR, VBOX_VERSION_BUILD, VBOX_SVN_REV);
[93391]1237 return RTEXITCODE_SUCCESS;
[31659]1238
[51170]1239 case 'v':
1240 g_iVerbosity++;
1241 break;
1242
[43850]1243 case 'h':
[93390]1244 ShowInfo("-- %s v%u.%u.%ur%u --\n"
[45332]1245 "\n"
[31659]1246 "Command Line Parameters:\n\n"
[93390]1247 "--extract\n"
1248 " Extract file contents to temporary directory\n"
1249 "--logging\n"
[95026]1250 " Enables MSI installer logging (to extract path)\n"
1251 "--msi-log-file <path/to/file>\n"
1252 " Sets MSI logging to <file>\n"
[93390]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"
[95699]1260#ifdef VBOX_WITH_CODE_SIGNING
[93390]1261 "--no-silent-cert\n"
1262 " Do not install VirtualBox Certificate automatically\n"
1263 " when --silent option is specified\n"
[95699]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
[93390]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"
[80231]1286 "\n"
[31659]1287 "Examples:\n"
[93390]1288 " %s --msiparams \"INSTALLDIR=\"\"C:\\Program Files\\VirtualBox\"\"\"\n"
1289 " %s --extract -path C:\\VBox",
[31659]1290 VBOX_STUB_TITLE, VBOX_VERSION_MAJOR, VBOX_VERSION_MINOR, VBOX_VERSION_BUILD, VBOX_SVN_REV,
[34206]1291 argv[0], argv[0]);
[93391]1292 return RTEXITCODE_SUCCESS;
[43850]1293
[51170]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. */
[93390]1297 /** @todo r=bird: this makes zero sense */
[51170]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))
[93391]1304 return ShowSyntaxError("Out of space for MSI parameters and properties");
[51170]1305 continue;
1306 }
1307 /* Fall through is intentional. */
1308
[43850]1309 default:
1310 if (g_fSilent)
[93391]1311 return RTGetOptPrintError(ch, &ValueUnion);
[51170]1312 if (ch == VERR_GETOPT_UNKNOWN_OPTION)
[93391]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);
[31659]1319 }
1320 }
1321
[94284]1322 /*
[93391]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);
[96571]1327 HANDLE hMutexAppRunning = CreateMutexW(NULL, FALSE, L"VBoxStubInstaller");
[93391]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 }
[51170]1334
[63131]1335/** @todo
1336 *
1337 * Split the remainder up in functions and simplify the code flow!!
1338 *
1339 * */
[93391]1340 RTEXITCODE rcExit = RTEXITCODE_SUCCESS;
[94313]1341 RTListInit(&g_TmpFiles);
[63131]1342
[93391]1343 /*
[94313]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 /*
[93391]1373 * Create a console for output if we're in verbose mode.
1374 */
1375#ifdef VBOX_STUB_WITH_OWN_CONSOLE
1376 if (g_iVerbosity)
[31659]1377 {
[51172]1378 if (!AllocConsole())
[93391]1379 return ShowError("Unable to allocate console: LastError=%u\n", GetLastError());
[51172]1380
[96571]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
[51172]1391 freopen("CONOUT$", "w", stdout);
1392 setvbuf(stdout, NULL, _IONBF, 0);
1393 freopen("CONOUT$", "w", stderr);
[96571]1394# endif
[51172]1395 }
[93391]1396#endif /* VBOX_STUB_WITH_OWN_CONSOLE */
[51172]1397
[95026]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
[93391]1413 if (g_iVerbosity)
[51172]1414 {
[94284]1415 RTPrintf("Extraction path : %s\n", szExtractPath);
[51170]1416 RTPrintf("Silent installation : %RTbool\n", g_fSilent);
[51171]1417#ifdef VBOX_WITH_CODE_SIGNING
[51170]1418 RTPrintf("Certificate installation : %RTbool\n", fEnableSilentCert);
[51171]1419#endif
[95026]1420 RTPrintf("Additional MSI parameters: %s\n", szMSIArgs[0] ? szMSIArgs : "<None>");
1421 RTPrintf("Logging to file : %s\n", szMSILogFile[0] ? szMSILogFile : "<None>");
[51170]1422 }
[34199]1423
[75812]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.");
[93391]1431 else
[45332]1432 {
[51170]1433 /*
[93391]1434 * Read our manifest.
[51170]1435 */
[94313]1436 VBOXSTUBPKGHEADER const *pHeader = NULL;
1437 vrc = FindData("MANIFEST", (uint8_t const **)&pHeader, NULL);
[63131]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... */
[31659]1442
[63131]1443 /*
1444 * Up to this point, we haven't done anything that requires any cleanup.
[93391]1445 * From here on, we do everything in functions so we can counter clean up.
[63131]1446 */
[96571]1447 rcExit = ExtractFiles(pHeader->cPackages, szExtractPath, fExtractOnly, &pExtractDirRec);
[63131]1448 if (rcExit == RTEXITCODE_SUCCESS)
[51170]1449 {
[63131]1450 if (fExtractOnly)
1451 ShowInfo("Files were extracted to: %s", szExtractPath);
1452 else
[51170]1453 {
[63131]1454 rcExit = CopyCustomDir(szExtractPath);
[64927]1455#ifdef VBOX_WITH_CODE_SIGNING
[95699]1456# ifdef VBOX_WITH_VBOX_LEGACY_TS_CA
1457 if (rcExit == RTEXITCODE_SUCCESS && fInstallTimestampCA)
1458 rcExit = InstallTimestampCA(fForceTimestampCaInstall);
1459# endif
[63131]1460 if (rcExit == RTEXITCODE_SUCCESS && fEnableSilentCert && g_fSilent)
[64927]1461 rcExit = InstallCertificates();
1462#endif
[63131]1463 unsigned iPackage = 0;
[96571]1464 while ( iPackage < pHeader->cPackages
[80231]1465 && (rcExit == RTEXITCODE_SUCCESS || rcExit == (RTEXITCODE)ERROR_SUCCESS_REBOOT_REQUIRED))
[63131]1466 {
[95026]1467 RTEXITCODE rcExit2 = ProcessPackage(iPackage, szMSIArgs, szMSILogFile[0] ? szMSILogFile : NULL);
[80231]1468 if (rcExit2 != RTEXITCODE_SUCCESS)
1469 rcExit = rcExit2;
[63131]1470 iPackage++;
1471 }
[51170]1472 }
[63131]1473 }
[51170]1474
[93391]1475 /*
1476 * Do cleanups unless we're only extracting (ignoring failures for now).
1477 */
1478 if (!fExtractOnly)
[94313]1479 {
1480 RTPathSetCurrent("..");
1481 CleanUp(!fEnableLogging && pExtractDirRec && !pExtractDirRec->fDontDelete ? szExtractPath : NULL);
1482 }
[93391]1483
[63131]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);
[31659]1490 }
[51170]1491 }
[63131]1492 else
1493 rcExit = ShowError("Internal package error: Manifest not found (%Rrc)", vrc);
[45320]1494 }
[31659]1495
[51170]1496#if defined(_WIN32_WINNT) && _WIN32_WINNT >= 0x0501
[51172]1497# ifdef VBOX_STUB_WITH_OWN_CONSOLE
[51173]1498 if (g_iVerbosity)
1499 FreeConsole();
[51170]1500# endif /* VBOX_STUB_WITH_OWN_CONSOLE */
1501#endif
[31659]1502
[45332]1503 /*
[93391]1504 * Release instance mutex just to be on the safe side.
[45332]1505 */
[31659]1506 if (hMutexAppRunning != NULL)
1507 CloseHandle(hMutexAppRunning);
1508
[80231]1509 return rcExit != (RTEXITCODE)ERROR_SUCCESS_REBOOT_REQUIRED || !fIgnoreReboot ? rcExit : RTEXITCODE_SUCCESS;
[31659]1510}
[96571]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