VirtualBox

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

最後變更 在這個檔案從94380是 94313,由 vboxsync 提交於 3 年 前

installer/win: More fixes for VBoxStub. bugref:10201

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

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