VirtualBox

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

Last change on this file since 107349 was 106984, checked in by vboxsync, 2 months ago

Installer/win/Stub*: ARM64 changes. Kicked out unnecessary VBoxStub.h header file. jiraref:VBP-1442

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