VirtualBox

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

Last change on this file since 53628 was 51173, checked in by vboxsync, 11 years ago

Installer/win/VBoxStub: No need to call FreeConsole() for non-verbose runs.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 37.8 KB
Line 
1/* $Id: VBoxStub.cpp 51173 2014-04-30 12:34:10Z vboxsync $ */
2/** @file
3 * VBoxStub - VirtualBox's Windows installer stub.
4 */
5
6/*
7 * Copyright (C) 2010-2014 Oracle Corporation
8 *
9 * This file is part of VirtualBox Open Source Edition (OSE), as
10 * available from http://www.virtualbox.org. This file is free software;
11 * you can redistribute it and/or modify it under the terms of the GNU
12 * General Public License (GPL) as published by the Free Software
13 * Foundation, in version 2 as it comes in the "COPYING" file of the
14 * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
15 * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
16 */
17
18/*******************************************************************************
19* Header Files *
20*******************************************************************************/
21#if defined(_WIN32_WINNT) && _WIN32_WINNT < 0x0501
22# undef _WIN32_WINNT
23# define _WIN32_WINNT 0x0501 /* AttachConsole() / FreeConsole(). */
24#endif
25
26#include <Windows.h>
27#include <commctrl.h>
28#include <fcntl.h>
29#include <io.h>
30#include <lmerr.h>
31#include <msiquery.h>
32#include <objbase.h>
33
34#include <shlobj.h>
35#include <stdlib.h>
36#include <stdio.h>
37#include <string.h>
38#include <strsafe.h>
39
40#include <VBox/version.h>
41
42#include <iprt/assert.h>
43#include <iprt/dir.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/thread.h>
55
56#include "VBoxStub.h"
57#include "../StubBld/VBoxStubBld.h"
58#include "resource.h"
59
60#ifdef VBOX_WITH_CODE_SIGNING
61# include "VBoxStubCertUtil.h"
62# include "VBoxStubPublicCert.h"
63#endif
64
65#ifndef TARGET_NT4
66/* Use an own console window if run in verbose mode. */
67# define VBOX_STUB_WITH_OWN_CONSOLE
68#endif
69
70/*******************************************************************************
71* Defined Constants And Macros *
72*******************************************************************************/
73#define MY_UNICODE_SUB(str) L ##str
74#define MY_UNICODE(str) MY_UNICODE_SUB(str)
75
76
77/*******************************************************************************
78* Structures and Typedefs *
79*******************************************************************************/
80/**
81 * Cleanup record.
82 */
83typedef struct STUBCLEANUPREC
84{
85 /** List entry. */
86 RTLISTNODE ListEntry;
87 /** True if file, false if directory. */
88 bool fFile;
89 /** The path to the file or directory to clean up. */
90 char szPath[1];
91} STUBCLEANUPREC;
92/** Pointer to a cleanup record. */
93typedef STUBCLEANUPREC *PSTUBCLEANUPREC;
94
95
96/*******************************************************************************
97* Global Variables *
98*******************************************************************************/
99/** Whether it's a silent or interactive GUI driven install. */
100static bool g_fSilent = false;
101/** List of temporary files. */
102static RTLISTANCHOR g_TmpFiles;
103/** Verbosity flag. */
104static int g_iVerbosity = 0;
105
106
107
108/**
109 * Shows an error message box with a printf() style formatted string.
110 *
111 * @returns RTEXITCODE_FAILURE
112 * @param pszFmt Printf-style format string to show in the message box body.
113 *
114 */
115static RTEXITCODE ShowError(const char *pszFmt, ...)
116{
117 char *pszMsg;
118 va_list va;
119
120 va_start(va, pszFmt);
121 if (RTStrAPrintfV(&pszMsg, pszFmt, va))
122 {
123 if (g_fSilent)
124 RTMsgError("%s", pszMsg);
125 else
126 {
127 PRTUTF16 pwszMsg;
128 int rc = RTStrToUtf16(pszMsg, &pwszMsg);
129 if (RT_SUCCESS(rc))
130 {
131 MessageBoxW(GetDesktopWindow(), pwszMsg, MY_UNICODE(VBOX_STUB_TITLE), MB_ICONERROR);
132 RTUtf16Free(pwszMsg);
133 }
134 else
135 MessageBoxA(GetDesktopWindow(), pszMsg, VBOX_STUB_TITLE, MB_ICONERROR);
136 }
137 RTStrFree(pszMsg);
138 }
139 else /* Should never happen! */
140 AssertMsgFailed(("Failed to format error text of format string: %s!\n", pszFmt));
141 va_end(va);
142 return RTEXITCODE_FAILURE;
143}
144
145
146/**
147 * Shows a message box with a printf() style formatted string.
148 *
149 * @param uType Type of the message box (see MSDN).
150 * @param pszFmt Printf-style format string to show in the message box body.
151 *
152 */
153static void ShowInfo(const char *pszFmt, ...)
154{
155 char *pszMsg;
156 va_list va;
157 va_start(va, pszFmt);
158 int rc = RTStrAPrintfV(&pszMsg, pszFmt, va);
159 va_end(va);
160 if (rc >= 0)
161 {
162 if (g_fSilent)
163 RTPrintf("%s\n", pszMsg);
164 else
165 {
166 PRTUTF16 pwszMsg;
167 int rc = RTStrToUtf16(pszMsg, &pwszMsg);
168 if (RT_SUCCESS(rc))
169 {
170 MessageBoxW(GetDesktopWindow(), pwszMsg, MY_UNICODE(VBOX_STUB_TITLE), MB_ICONINFORMATION);
171 RTUtf16Free(pwszMsg);
172 }
173 else
174 MessageBoxA(GetDesktopWindow(), pszMsg, VBOX_STUB_TITLE, MB_ICONINFORMATION);
175 }
176 }
177 else /* Should never happen! */
178 AssertMsgFailed(("Failed to format error text of format string: %s!\n", pszFmt));
179 RTStrFree(pszMsg);
180}
181
182
183/**
184 * Finds the specified in the resource section of the executable.
185 *
186 * @returns IPRT status code.
187 *
188 * @param pszDataName Name of resource to read.
189 * @param ppvResource Where to return the pointer to the data.
190 * @param pdwSize Where to return the size of the data (if found).
191 * Optional.
192 */
193static int FindData(const char *pszDataName, PVOID *ppvResource, DWORD *pdwSize)
194{
195 AssertReturn(pszDataName, VERR_INVALID_PARAMETER);
196 HINSTANCE hInst = NULL; /* indicates the executable image */
197
198 /* Find our resource. */
199 PRTUTF16 pwszDataName;
200 int rc = RTStrToUtf16(pszDataName, &pwszDataName);
201 AssertRCReturn(rc, rc);
202 HRSRC hRsrc = FindResourceExW(hInst,
203 (LPWSTR)RT_RCDATA,
204 pwszDataName,
205 MAKELANGID(LANG_NEUTRAL, SUBLANG_NEUTRAL));
206 RTUtf16Free(pwszDataName);
207 AssertReturn(hRsrc, VERR_IO_GEN_FAILURE);
208
209 /* Get resource size. */
210 DWORD cb = SizeofResource(hInst, hRsrc);
211 AssertReturn(cb > 0, VERR_NO_DATA);
212 if (pdwSize)
213 *pdwSize = cb;
214
215 /* Get pointer to resource. */
216 HGLOBAL hData = LoadResource(hInst, hRsrc);
217 AssertReturn(hData, VERR_IO_GEN_FAILURE);
218
219 /* Lock resource. */
220 *ppvResource = LockResource(hData);
221 AssertReturn(*ppvResource, VERR_IO_GEN_FAILURE);
222 return VINF_SUCCESS;
223}
224
225
226/**
227 * Finds the header for the given package.
228 *
229 * @returns Pointer to the package header on success. On failure NULL is
230 * returned after ShowError has been invoked.
231 * @param iPackage The package number.
232 */
233static PVBOXSTUBPKG FindPackageHeader(unsigned iPackage)
234{
235 char szHeaderName[32];
236 RTStrPrintf(szHeaderName, sizeof(szHeaderName), "HDR_%02d", iPackage);
237
238 PVBOXSTUBPKG pPackage;
239 int rc = FindData(szHeaderName, (PVOID *)&pPackage, NULL);
240 if (RT_FAILURE(rc))
241 {
242 ShowError("Internal error: Could not find package header #%u: %Rrc", iPackage, rc);
243 return NULL;
244 }
245
246 /** @todo validate it. */
247 return pPackage;
248}
249
250
251
252/**
253 * Constructs a full temporary file path from the given parameters.
254 *
255 * @returns iprt status code.
256 *
257 * @param pszTempPath The pure path to use for construction.
258 * @param pszTargetFileName The pure file name to use for construction.
259 * @param ppszTempFile Pointer to the constructed string. Must be freed
260 * using RTStrFree().
261 */
262static int GetTempFileAlloc(const char *pszTempPath,
263 const char *pszTargetFileName,
264 char **ppszTempFile)
265{
266 if (RTStrAPrintf(ppszTempFile, "%s\\%s", pszTempPath, pszTargetFileName) >= 0)
267 return VINF_SUCCESS;
268 return VERR_NO_STR_MEMORY;
269}
270
271
272/**
273 * Extracts a built-in resource to disk.
274 *
275 * @returns iprt status code.
276 *
277 * @param pszResourceName The resource name to extract.
278 * @param pszTempFile The full file path + name to extract the resource to.
279 *
280 */
281static int ExtractFile(const char *pszResourceName,
282 const char *pszTempFile)
283{
284 int rc;
285 RTFILE fh;
286 BOOL bCreatedFile = FALSE;
287
288 do
289 {
290 AssertMsgBreak(pszResourceName, ("Resource pointer invalid!\n"));
291 AssertMsgBreak(pszTempFile, ("Temp file pointer invalid!"));
292
293 /* Read the data of the built-in resource. */
294 PVOID pvData = NULL;
295 DWORD dwDataSize = 0;
296 rc = FindData(pszResourceName, &pvData, &dwDataSize);
297 AssertMsgRCBreak(rc, ("Could not read resource data!\n"));
298
299 /* Create new (and replace an old) file. */
300 rc = RTFileOpen(&fh, pszTempFile,
301 RTFILE_O_CREATE_REPLACE
302 | RTFILE_O_WRITE
303 | RTFILE_O_DENY_NOT_DELETE
304 | RTFILE_O_DENY_WRITE);
305 AssertMsgRCBreak(rc, ("Could not open file for writing!\n"));
306 bCreatedFile = TRUE;
307
308 /* Write contents to new file. */
309 size_t cbWritten = 0;
310 rc = RTFileWrite(fh, pvData, dwDataSize, &cbWritten);
311 AssertMsgRCBreak(rc, ("Could not open file for writing!\n"));
312 AssertMsgBreak(dwDataSize == cbWritten, ("File was not extracted completely! Disk full?\n"));
313
314 } while (0);
315
316 if (RTFileIsValid(fh))
317 RTFileClose(fh);
318
319 if (RT_FAILURE(rc))
320 {
321 if (bCreatedFile)
322 RTFileDelete(pszTempFile);
323 }
324 return rc;
325}
326
327
328/**
329 * Extracts a built-in resource to disk.
330 *
331 * @returns iprt status code.
332 *
333 * @param pPackage Pointer to a VBOXSTUBPKG struct that contains the resource.
334 * @param pszTempFile The full file path + name to extract the resource to.
335 *
336 */
337static int Extract(const PVBOXSTUBPKG pPackage,
338 const char *pszTempFile)
339{
340 return ExtractFile(pPackage->szResourceName,
341 pszTempFile);
342}
343
344
345/**
346 * Detects whether we're running on a 32- or 64-bit platform and returns the result.
347 *
348 * @returns TRUE if we're running on a 64-bit OS, FALSE if not.
349 *
350 */
351static BOOL IsWow64(void)
352{
353 BOOL bIsWow64 = TRUE;
354 fnIsWow64Process = (LPFN_ISWOW64PROCESS)GetProcAddress(GetModuleHandle(TEXT("kernel32")), "IsWow64Process");
355 if (NULL != fnIsWow64Process)
356 {
357 if (!fnIsWow64Process(GetCurrentProcess(), &bIsWow64))
358 {
359 /* Error in retrieving process type - assume that we're running on 32bit. */
360 return FALSE;
361 }
362 }
363 return bIsWow64;
364}
365
366
367/**
368 * Decides whether we need a specified package to handle or not.
369 *
370 * @returns @c true if we need to handle the specified package, @c false if not.
371 *
372 * @param pPackage Pointer to a VBOXSTUBPKG struct that contains the resource.
373 *
374 */
375static bool PackageIsNeeded(PVBOXSTUBPKG pPackage)
376{
377 if (pPackage->byArch == VBOXSTUBPKGARCH_ALL)
378 return true;
379 VBOXSTUBPKGARCH enmArch = IsWow64() ? VBOXSTUBPKGARCH_AMD64 : VBOXSTUBPKGARCH_X86;
380 return pPackage->byArch == enmArch;
381}
382
383
384/**
385 * Adds a cleanup record.
386 *
387 * @returns Fully complained boolean success indicator.
388 * @param pszPath The path to the file or directory to clean up.
389 * @param fFile @c true if file, @c false if directory.
390 */
391static bool AddCleanupRec(const char *pszPath, bool fFile)
392{
393 size_t cchPath = strlen(pszPath); Assert(cchPath > 0);
394 PSTUBCLEANUPREC pRec = (PSTUBCLEANUPREC)RTMemAlloc(RT_OFFSETOF(STUBCLEANUPREC, szPath[cchPath + 1]));
395 if (!pRec)
396 {
397 ShowError("Out of memory!");
398 return false;
399 }
400 pRec->fFile = fFile;
401 memcpy(pRec->szPath, pszPath, cchPath + 1);
402
403 RTListPrepend(&g_TmpFiles, &pRec->ListEntry);
404 return true;
405}
406
407
408/**
409 * Cleans up all the extracted files and optionally removes the package
410 * directory.
411 *
412 * @param cPackages The number of packages.
413 * @param pszPkgDir The package directory, NULL if it shouldn't be
414 * removed.
415 */
416static void CleanUp(unsigned cPackages, const char *pszPkgDir)
417{
418 for (int i = 0; i < 5; i++)
419 {
420 int rc;
421 bool fFinalTry = i == 4;
422
423 PSTUBCLEANUPREC pCur, pNext;
424 RTListForEachSafe(&g_TmpFiles, pCur, pNext, STUBCLEANUPREC, ListEntry)
425 {
426 if (pCur->fFile)
427 rc = RTFileDelete(pCur->szPath);
428 else
429 {
430 rc = RTDirRemoveRecursive(pCur->szPath, RTDIRRMREC_F_CONTENT_AND_DIR);
431 if (rc == VERR_DIR_NOT_EMPTY && fFinalTry)
432 rc = VINF_SUCCESS;
433 }
434 if (rc == VERR_FILE_NOT_FOUND || rc == VERR_PATH_NOT_FOUND)
435 rc = VINF_SUCCESS;
436 if (RT_SUCCESS(rc))
437 {
438 RTListNodeRemove(&pCur->ListEntry);
439 RTMemFree(pCur);
440 }
441 else if (fFinalTry)
442 {
443 if (pCur->fFile)
444 ShowError("Failed to delete temporary file '%s': %Rrc", pCur->szPath, rc);
445 else
446 ShowError("Failed to delete temporary directory '%s': %Rrc", pCur->szPath, rc);
447 }
448 }
449
450 if (RTListIsEmpty(&g_TmpFiles) || fFinalTry)
451 {
452 if (!pszPkgDir)
453 return;
454 rc = RTDirRemove(pszPkgDir);
455 if (RT_SUCCESS(rc) || rc == VERR_FILE_NOT_FOUND || rc == VERR_PATH_NOT_FOUND || fFinalTry)
456 return;
457 }
458
459 /* Delay a little and try again. */
460 RTThreadSleep(i == 0 ? 100 : 3000);
461 }
462}
463
464
465/**
466 * Processes an MSI package.
467 *
468 * @returns Fully complained exit code.
469 * @param pszMsi The path to the MSI to process.
470 * @param pszMsiArgs Any additional installer (MSI) argument
471 * @param fLogging Whether to enable installer logging.
472 */
473static RTEXITCODE ProcessMsiPackage(const char *pszMsi, const char *pszMsiArgs, bool fLogging)
474{
475 int rc;
476
477 /*
478 * Set UI level.
479 */
480 INSTALLUILEVEL enmDesiredUiLevel = g_fSilent ? INSTALLUILEVEL_NONE : INSTALLUILEVEL_FULL;
481 INSTALLUILEVEL enmRet = MsiSetInternalUI(enmDesiredUiLevel, NULL);
482 if (enmRet == INSTALLUILEVEL_NOCHANGE /* means error */)
483 return ShowError("Internal error: MsiSetInternalUI failed.");
484
485 /*
486 * Enable logging?
487 */
488 if (fLogging)
489 {
490 char szLogFile[RTPATH_MAX];
491 rc = RTStrCopy(szLogFile, sizeof(szLogFile), pszMsi);
492 if (RT_SUCCESS(rc))
493 {
494 RTPathStripFilename(szLogFile);
495 rc = RTPathAppend(szLogFile, sizeof(szLogFile), "VBoxInstallLog.txt");
496 }
497 if (RT_FAILURE(rc))
498 return ShowError("Internal error: Filename path too long.");
499
500 PRTUTF16 pwszLogFile;
501 rc = RTStrToUtf16(szLogFile, &pwszLogFile);
502 if (RT_FAILURE(rc))
503 return ShowError("RTStrToUtf16 failed on '%s': %Rrc", szLogFile, rc);
504
505 UINT uLogLevel = MsiEnableLogW(INSTALLLOGMODE_VERBOSE,
506 pwszLogFile,
507 INSTALLLOGATTRIBUTES_FLUSHEACHLINE);
508 RTUtf16Free(pwszLogFile);
509 if (uLogLevel != ERROR_SUCCESS)
510 return ShowError("MsiEnableLogW failed");
511 }
512
513 /*
514 * Initialize the common controls (extended version). This is necessary to
515 * run the actual .MSI installers with the new fancy visual control
516 * styles (XP+). Also, an integrated manifest is required.
517 */
518 INITCOMMONCONTROLSEX ccEx;
519 ccEx.dwSize = sizeof(INITCOMMONCONTROLSEX);
520 ccEx.dwICC = ICC_LINK_CLASS | ICC_LISTVIEW_CLASSES | ICC_PAGESCROLLER_CLASS |
521 ICC_PROGRESS_CLASS | ICC_STANDARD_CLASSES | ICC_TAB_CLASSES | ICC_TREEVIEW_CLASSES |
522 ICC_UPDOWN_CLASS | ICC_USEREX_CLASSES | ICC_WIN95_CLASSES;
523 InitCommonControlsEx(&ccEx); /* Ignore failure. */
524
525 /*
526 * Convert both strings to UTF-16 and start the installation.
527 */
528 PRTUTF16 pwszMsi;
529 rc = RTStrToUtf16(pszMsi, &pwszMsi);
530 if (RT_FAILURE(rc))
531 return ShowError("RTStrToUtf16 failed on '%s': %Rrc", pszMsi, rc);
532 PRTUTF16 pwszMsiArgs;
533 rc = RTStrToUtf16(pszMsiArgs, &pwszMsiArgs);
534 if (RT_FAILURE(rc))
535 {
536 RTUtf16Free(pwszMsi);
537 return ShowError("RTStrToUtf16 failed on '%s': %Rrc", pszMsi, rc);
538 }
539
540 UINT uStatus = MsiInstallProductW(pwszMsi, pwszMsiArgs);
541 RTUtf16Free(pwszMsi);
542 RTUtf16Free(pwszMsiArgs);
543
544 if (uStatus == ERROR_SUCCESS)
545 return RTEXITCODE_SUCCESS;
546 if (uStatus == ERROR_SUCCESS_REBOOT_REQUIRED)
547 return RTEXITCODE_SUCCESS; /* we currently don't indicate this */
548
549 /*
550 * Installation failed. Figure out what to say.
551 */
552 switch (uStatus)
553 {
554 case ERROR_INSTALL_USEREXIT:
555 /* Don't say anything? */
556 break;
557
558 case ERROR_INSTALL_PACKAGE_VERSION:
559 ShowError("This installation package cannot be installed by the Windows Installer service.\n"
560 "You must install a Windows service pack that contains a newer version of the Windows Installer service.");
561 break;
562
563 case ERROR_INSTALL_PLATFORM_UNSUPPORTED:
564 ShowError("This installation package is not supported on this platform.");
565 break;
566
567 default:
568 {
569 /*
570 * Try get windows to format the message.
571 */
572 DWORD dwFormatFlags = FORMAT_MESSAGE_ALLOCATE_BUFFER
573 | FORMAT_MESSAGE_IGNORE_INSERTS
574 | FORMAT_MESSAGE_FROM_SYSTEM;
575 HMODULE hModule = NULL;
576 if (uStatus >= NERR_BASE && uStatus <= MAX_NERR)
577 {
578 hModule = LoadLibraryExW(L"netmsg.dll",
579 NULL,
580 LOAD_LIBRARY_AS_DATAFILE);
581 if (hModule != NULL)
582 dwFormatFlags |= FORMAT_MESSAGE_FROM_HMODULE;
583 }
584
585 PWSTR pwszMsg;
586 if (FormatMessageW(dwFormatFlags,
587 hModule, /* If NULL, load system stuff. */
588 uStatus,
589 MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
590 (PWSTR)&pwszMsg,
591 0,
592 NULL) > 0)
593 {
594 ShowError("Installation failed! Error: %ls", pwszMsg);
595 LocalFree(pwszMsg);
596 }
597 else /* If text lookup failed, show at least the error number. */
598 ShowError("Installation failed! Error: %u", uStatus);
599
600 if (hModule)
601 FreeLibrary(hModule);
602 break;
603 }
604 }
605
606 return RTEXITCODE_FAILURE;
607}
608
609
610/**
611 * Processes a package.
612 *
613 * @returns Fully complained exit code.
614 * @param iPackage The package number.
615 * @param pszPkgDir The package directory (aka extraction dir).
616 * @param pszMsiArgs Any additional installer (MSI) argument
617 * @param fLogging Whether to enable installer logging.
618 */
619static RTEXITCODE ProcessPackage(unsigned iPackage, const char *pszPkgDir, const char *pszMsiArgs, bool fLogging)
620{
621 /*
622 * Get the package header and check if it's needed.
623 */
624 PVBOXSTUBPKG pPackage = FindPackageHeader(iPackage);
625 if (pPackage == NULL)
626 return RTEXITCODE_FAILURE;
627
628 if (!PackageIsNeeded(pPackage))
629 return RTEXITCODE_SUCCESS;
630
631 /*
632 * Deal with the file based on it's extension.
633 */
634 char szPkgFile[RTPATH_MAX];
635 int rc = RTPathJoin(szPkgFile, sizeof(szPkgFile), pszPkgDir, pPackage->szFileName);
636 if (RT_FAILURE(rc))
637 return ShowError("Internal error: RTPathJoin failed: %Rrc", rc);
638 RTPathChangeToDosSlashes(szPkgFile, true /* Force conversion. */); /* paranoia */
639
640 RTEXITCODE rcExit;
641 const char *pszSuff = RTPathSuffix(szPkgFile);
642 if (RTStrICmp(pszSuff, ".msi") == 0)
643 rcExit = ProcessMsiPackage(szPkgFile, pszMsiArgs, fLogging);
644 else if (RTStrICmp(pszSuff, ".cab") == 0)
645 rcExit = RTEXITCODE_SUCCESS; /* Ignore .cab files, they're generally referenced by other files. */
646 else
647 rcExit = ShowError("Internal error: Do not know how to handle file '%s'.", pPackage->szFileName);
648
649 return rcExit;
650}
651
652
653#ifdef VBOX_WITH_CODE_SIGNING
654/**
655 * Install the public certificate into TrustedPublishers so the installer won't
656 * prompt the user during silent installs.
657 *
658 * @returns Fully complained exit code.
659 */
660static RTEXITCODE InstallCertificate(void)
661{
662 if (addCertToStore(CERT_SYSTEM_STORE_LOCAL_MACHINE,
663 "TrustedPublisher",
664 g_ab_VBoxStubPublicCert,
665 sizeof(g_ab_VBoxStubPublicCert)))
666 return RTEXITCODE_SUCCESS;
667 return ShowError("Failed to construct install certificate.");
668}
669#endif /* VBOX_WITH_CODE_SIGNING */
670
671
672/**
673 * Copies the "<exepath>.custom" directory to the extraction path if it exists.
674 *
675 * This is used by the MSI packages from the resource section.
676 *
677 * @returns Fully complained exit code.
678 * @param pszDstDir The destination directory.
679 */
680static RTEXITCODE CopyCustomDir(const char *pszDstDir)
681{
682 char szSrcDir[RTPATH_MAX];
683 int rc = RTPathExecDir(szSrcDir, sizeof(szSrcDir));
684 if (RT_SUCCESS(rc))
685 rc = RTPathAppend(szSrcDir, sizeof(szSrcDir), ".custom");
686 if (RT_FAILURE(rc))
687 return ShowError("Failed to construct '.custom' dir path: %Rrc", rc);
688
689 if (RTDirExists(szSrcDir))
690 {
691 /*
692 * Use SHFileOperation w/ FO_COPY to do the job. This API requires an
693 * extra zero at the end of both source and destination paths.
694 */
695 size_t cwc;
696 RTUTF16 wszSrcDir[RTPATH_MAX + 1];
697 PRTUTF16 pwszSrcDir = wszSrcDir;
698 rc = RTStrToUtf16Ex(szSrcDir, RTSTR_MAX, &pwszSrcDir, RTPATH_MAX, &cwc);
699 if (RT_FAILURE(rc))
700 return ShowError("RTStrToUtf16Ex failed on '%s': %Rrc", szSrcDir, rc);
701 wszSrcDir[cwc] = '\0';
702
703 RTUTF16 wszDstDir[RTPATH_MAX + 1];
704 PRTUTF16 pwszDstDir = wszSrcDir;
705 rc = RTStrToUtf16Ex(pszDstDir, RTSTR_MAX, &pwszDstDir, RTPATH_MAX, &cwc);
706 if (RT_FAILURE(rc))
707 return ShowError("RTStrToUtf16Ex failed on '%s': %Rrc", pszDstDir, rc);
708 wszDstDir[cwc] = '\0';
709
710 SHFILEOPSTRUCTW FileOp;
711 RT_ZERO(FileOp); /* paranoia */
712 FileOp.hwnd = NULL;
713 FileOp.wFunc = FO_COPY;
714 FileOp.pFrom = wszSrcDir;
715 FileOp.pTo = wszDstDir;
716 FileOp.fFlags = FOF_SILENT
717 | FOF_NOCONFIRMATION
718 | FOF_NOCONFIRMMKDIR
719 | FOF_NOERRORUI;
720 FileOp.fAnyOperationsAborted = FALSE;
721 FileOp.hNameMappings = NULL;
722 FileOp.lpszProgressTitle = NULL;
723
724 rc = SHFileOperationW(&FileOp);
725 if (rc != 0) /* Not a Win32 status code! */
726 return ShowError("Copying the '.custom' dir failed: %#x", rc);
727
728 /*
729 * Add a cleanup record for recursively deleting the destination
730 * .custom directory. We should actually add this prior to calling
731 * SHFileOperationW since it may partially succeed...
732 */
733 char *pszDstSubDir = RTPathJoinA(pszDstDir, ".custom");
734 if (!pszDstSubDir)
735 return ShowError("Out of memory!");
736 bool fRc = AddCleanupRec(pszDstSubDir, false /*fFile*/);
737 RTStrFree(pszDstSubDir);
738 if (!fRc)
739 return RTEXITCODE_FAILURE;
740 }
741
742 return RTEXITCODE_SUCCESS;
743}
744
745
746static RTEXITCODE ExtractFiles(unsigned cPackages, const char *pszDstDir, bool fExtractOnly, bool *pfCreatedExtractDir)
747{
748 int rc;
749
750 /*
751 * Make sure the directory exists.
752 */
753 *pfCreatedExtractDir = false;
754 if (!RTDirExists(pszDstDir))
755 {
756 rc = RTDirCreate(pszDstDir, 0700, 0);
757 if (RT_FAILURE(rc))
758 return ShowError("Failed to create extraction path '%s': %Rrc", pszDstDir, rc);
759 *pfCreatedExtractDir = true;
760 }
761
762 /*
763 * Extract files.
764 */
765 for (unsigned k = 0; k < cPackages; k++)
766 {
767 PVBOXSTUBPKG pPackage = FindPackageHeader(k);
768 if (!pPackage)
769 return RTEXITCODE_FAILURE; /* Done complaining already. */
770
771 if (fExtractOnly || PackageIsNeeded(pPackage))
772 {
773 char szDstFile[RTPATH_MAX];
774 rc = RTPathJoin(szDstFile, sizeof(szDstFile), pszDstDir, pPackage->szFileName);
775 if (RT_FAILURE(rc))
776 return ShowError("Internal error: RTPathJoin failed: %Rrc", rc);
777
778 rc = Extract(pPackage, szDstFile);
779 if (RT_FAILURE(rc))
780 return ShowError("Error extracting package #%u: %Rrc", k, rc);
781
782 if (!fExtractOnly && !AddCleanupRec(szDstFile, true /*fFile*/))
783 {
784 RTFileDelete(szDstFile);
785 return RTEXITCODE_FAILURE;
786 }
787 }
788 }
789
790 return RTEXITCODE_SUCCESS;
791}
792
793
794int WINAPI WinMain(HINSTANCE hInstance,
795 HINSTANCE hPrevInstance,
796 char *lpCmdLine,
797 int nCmdShow)
798{
799 char **argv = __argv;
800 int argc = __argc;
801
802 /* Check if we're already running and jump out if so. */
803 /* Do not use a global namespace ("Global\\") for mutex name here, will blow up NT4 compatibility! */
804 HANDLE hMutexAppRunning = CreateMutex(NULL, FALSE, "VBoxStubInstaller");
805 if ( hMutexAppRunning != NULL
806 && GetLastError() == ERROR_ALREADY_EXISTS)
807 {
808 /* Close the mutex for this application instance. */
809 CloseHandle(hMutexAppRunning);
810 hMutexAppRunning = NULL;
811 return RTEXITCODE_FAILURE;
812 }
813
814 /* Init IPRT. */
815 int vrc = RTR3InitExe(argc, &argv, 0);
816 if (RT_FAILURE(vrc))
817 {
818 /* Close the mutex for this application instance. */
819 CloseHandle(hMutexAppRunning);
820 hMutexAppRunning = NULL;
821 return RTMsgInitFailure(vrc);
822 }
823
824 /*
825 * Parse arguments.
826 */
827
828 /* Parameter variables. */
829 bool fExtractOnly = false;
830 bool fEnableLogging = false;
831#ifdef VBOX_WITH_CODE_SIGNING
832 bool fEnableSilentCert = true;
833#endif
834 char szExtractPath[RTPATH_MAX] = {0};
835 char szMSIArgs[_4K] = {0};
836
837 /* Parameter definitions. */
838 static const RTGETOPTDEF s_aOptions[] =
839 {
840 /** @todo Replace short parameters with enums since they're not
841 * used (and not documented to the public). */
842 { "--extract", 'x', RTGETOPT_REQ_NOTHING },
843 { "-extract", 'x', RTGETOPT_REQ_NOTHING },
844 { "/extract", 'x', RTGETOPT_REQ_NOTHING },
845 { "--silent", 's', RTGETOPT_REQ_NOTHING },
846 { "-silent", 's', RTGETOPT_REQ_NOTHING },
847 { "/silent", 's', RTGETOPT_REQ_NOTHING },
848#ifdef VBOX_WITH_CODE_SIGNING
849 { "--no-silent-cert", 'c', RTGETOPT_REQ_NOTHING },
850 { "-no-silent-cert", 'c', RTGETOPT_REQ_NOTHING },
851 { "/no-silent-cert", 'c', RTGETOPT_REQ_NOTHING },
852#endif
853 { "--logging", 'l', RTGETOPT_REQ_NOTHING },
854 { "-logging", 'l', RTGETOPT_REQ_NOTHING },
855 { "/logging", 'l', RTGETOPT_REQ_NOTHING },
856 { "--path", 'p', RTGETOPT_REQ_STRING },
857 { "-path", 'p', RTGETOPT_REQ_STRING },
858 { "/path", 'p', RTGETOPT_REQ_STRING },
859 { "--msiparams", 'm', RTGETOPT_REQ_STRING },
860 { "-msiparams", 'm', RTGETOPT_REQ_STRING },
861 { "--reinstall", 'f', RTGETOPT_REQ_NOTHING },
862 { "-reinstall", 'f', RTGETOPT_REQ_NOTHING },
863 { "/reinstall", 'f', RTGETOPT_REQ_NOTHING },
864 { "--verbose", 'v', RTGETOPT_REQ_NOTHING },
865 { "-verbose", 'v', RTGETOPT_REQ_NOTHING },
866 { "/verbose", 'v', RTGETOPT_REQ_NOTHING },
867 { "--version", 'V', RTGETOPT_REQ_NOTHING },
868 { "-version", 'V', RTGETOPT_REQ_NOTHING },
869 { "/version", 'V', RTGETOPT_REQ_NOTHING },
870 { "-v", 'V', RTGETOPT_REQ_NOTHING },
871 { "--help", 'h', RTGETOPT_REQ_NOTHING },
872 { "-help", 'h', RTGETOPT_REQ_NOTHING },
873 { "/help", 'h', RTGETOPT_REQ_NOTHING },
874 { "/?", 'h', RTGETOPT_REQ_NOTHING },
875 };
876
877 RTEXITCODE rcExit = RTEXITCODE_SUCCESS;
878
879 /* Parse the parameters. */
880 int ch;
881 bool fParsingDone = false;
882 RTGETOPTUNION ValueUnion;
883 RTGETOPTSTATE GetState;
884 RTGetOptInit(&GetState, argc, argv, s_aOptions, RT_ELEMENTS(s_aOptions), 1, 0);
885 while ( (ch = RTGetOpt(&GetState, &ValueUnion))
886 && rcExit == RTEXITCODE_SUCCESS
887 && !fParsingDone)
888 {
889 switch (ch)
890 {
891 case 'f': /* Force re-installation. */
892 if (szMSIArgs[0])
893 vrc = RTStrCat(szMSIArgs, sizeof(szMSIArgs), " ");
894 if (RT_SUCCESS(vrc))
895 vrc = RTStrCat(szMSIArgs, sizeof(szMSIArgs),
896 "REINSTALLMODE=vomus REINSTALL=ALL");
897 if (RT_FAILURE(vrc))
898 rcExit = ShowError("MSI parameters are too long.");
899 break;
900
901 case 'x':
902 fExtractOnly = true;
903 break;
904
905 case 's':
906 g_fSilent = true;
907 break;
908
909#ifdef VBOX_WITH_CODE_SIGNING
910 case 'c':
911 fEnableSilentCert = false;
912 break;
913#endif
914 case 'l':
915 fEnableLogging = true;
916 break;
917
918 case 'p':
919 vrc = RTStrCopy(szExtractPath, sizeof(szExtractPath), ValueUnion.psz);
920 if (RT_FAILURE(vrc))
921 rcExit = ShowError("Extraction path is too long.");
922 break;
923
924 case 'm':
925 if (szMSIArgs[0])
926 vrc = RTStrCat(szMSIArgs, sizeof(szMSIArgs), " ");
927 if (RT_SUCCESS(vrc))
928 vrc = RTStrCat(szMSIArgs, sizeof(szMSIArgs), ValueUnion.psz);
929 if (RT_FAILURE(vrc))
930 rcExit = ShowError("MSI parameters are too long.");
931 break;
932
933 case 'V':
934 ShowInfo("Version: %d.%d.%d.%d",
935 VBOX_VERSION_MAJOR, VBOX_VERSION_MINOR, VBOX_VERSION_BUILD,
936 VBOX_SVN_REV);
937 fParsingDone = true;
938 break;
939
940 case 'v':
941 g_iVerbosity++;
942 break;
943
944 case 'h':
945 ShowInfo("-- %s v%d.%d.%d.%d --\n"
946 "\n"
947 "Command Line Parameters:\n\n"
948 "--extract - Extract file contents to temporary directory\n"
949 "--help - Print this help and exit\n"
950 "--logging - Enables installer logging\n"
951 "--msiparams <parameters> - Specifies extra parameters for the MSI installers\n"
952 "--no-silent-cert - Do not install VirtualBox Certificate automatically when --silent option is specified\n"
953 "--path - Sets the path of the extraction directory\n"
954 "--reinstall - Forces VirtualBox to get re-installed\n"
955 "--silent - Enables silent mode installation\n"
956 "--version - Print version number and exit\n\n"
957 "Examples:\n"
958 "%s --msiparams INSTALLDIR=C:\\VBox\n"
959 "%s --extract -path C:\\VBox",
960 VBOX_STUB_TITLE, VBOX_VERSION_MAJOR, VBOX_VERSION_MINOR, VBOX_VERSION_BUILD, VBOX_SVN_REV,
961 argv[0], argv[0]);
962 fParsingDone = true;
963 break;
964
965 case VINF_GETOPT_NOT_OPTION:
966 /* Are (optional) MSI parameters specified and this is the last
967 * parameter? Append everything to the MSI parameter list then. */
968 if (szMSIArgs[0])
969 {
970 vrc = RTStrCat(szMSIArgs, sizeof(szMSIArgs), " ");
971 if (RT_SUCCESS(vrc))
972 vrc = RTStrCat(szMSIArgs, sizeof(szMSIArgs), ValueUnion.psz);
973 if (RT_FAILURE(vrc))
974 rcExit = ShowError("MSI parameters are too long.");
975 continue;
976 }
977 /* Fall through is intentional. */
978
979 default:
980 if (g_fSilent)
981 rcExit = RTGetOptPrintError(ch, &ValueUnion);
982 if (ch == VERR_GETOPT_UNKNOWN_OPTION)
983 rcExit = ShowError("Unknown option \"%s\"\n"
984 "Please refer to the command line help by specifying \"/?\"\n"
985 "to get more information.", ValueUnion.psz);
986 else
987 rcExit = ShowError("Parameter parsing error: %Rrc\n"
988 "Please refer to the command line help by specifying \"/?\"\n"
989 "to get more information.", ch);
990 break;
991 }
992 }
993
994 if (rcExit != RTEXITCODE_SUCCESS)
995 vrc = VERR_PARSE_ERROR;
996
997#if defined(_WIN32_WINNT) && _WIN32_WINNT >= 0x0501
998# ifdef VBOX_STUB_WITH_OWN_CONSOLE /* Use an own console window if run in debug mode. */
999 if ( RT_SUCCESS(vrc)
1000 && g_iVerbosity)
1001 {
1002 if (!AllocConsole())
1003 {
1004 DWORD dwErr = GetLastError();
1005 ShowError("Unable to allocate console, error = %ld\n",
1006 dwErr);
1007
1008 /* Close the mutex for this application instance. */
1009 CloseHandle(hMutexAppRunning);
1010 hMutexAppRunning = NULL;
1011 return RTEXITCODE_FAILURE;
1012 }
1013
1014 freopen("CONOUT$", "w", stdout);
1015 setvbuf(stdout, NULL, _IONBF, 0);
1016
1017 freopen("CONOUT$", "w", stderr);
1018 }
1019# endif /* VBOX_STUB_WITH_OWN_CONSOLE */
1020#endif
1021
1022 if ( RT_SUCCESS(vrc)
1023 && g_iVerbosity)
1024 {
1025 RTPrintf("Silent installation : %RTbool\n", g_fSilent);
1026 RTPrintf("Logging enabled : %RTbool\n", fEnableLogging);
1027#ifdef VBOX_WITH_CODE_SIGNING
1028 RTPrintf("Certificate installation : %RTbool\n", fEnableSilentCert);
1029#endif
1030 RTPrintf("Additional MSI parameters: %s\n",
1031 szMSIArgs[0] ? szMSIArgs : "<None>");
1032 }
1033
1034 if (RT_SUCCESS(vrc))
1035 {
1036 /*
1037 * Determine the extration path if not given by the user, and gather some
1038 * other bits we'll be needing later.
1039 */
1040 if (szExtractPath[0] == '\0')
1041 {
1042 vrc = RTPathTemp(szExtractPath, sizeof(szExtractPath));
1043 if (RT_SUCCESS(vrc))
1044 vrc = RTPathAppend(szExtractPath, sizeof(szExtractPath), "VirtualBox");
1045 if (RT_FAILURE(vrc))
1046 ShowError("Failed to determine extraction path (%Rrc)", vrc);
1047
1048 }
1049 else
1050 {
1051 /** @todo should check if there is a .custom subdirectory there or not. */
1052 }
1053 RTPathChangeToDosSlashes(szExtractPath,
1054 true /* Force conversion. */); /* MSI requirement. */
1055 }
1056
1057 /* Read our manifest. */
1058 PVBOXSTUBPKGHEADER pHeader;
1059 if (RT_SUCCESS(vrc))
1060 {
1061 vrc = FindData("MANIFEST", (PVOID *)&pHeader, NULL);
1062 if (RT_FAILURE(vrc))
1063 rcExit = ShowError("Internal package error: Manifest not found (%Rrc)", vrc);
1064 }
1065 if (RT_SUCCESS(vrc))
1066 {
1067 /** @todo If we could, we should validate the header. Only the magic isn't
1068 * commonly defined, nor the version number... */
1069
1070 RTListInit(&g_TmpFiles);
1071
1072 /*
1073 * Up to this point, we haven't done anything that requires any cleanup.
1074 * From here on, we do everything in function so we can counter clean up.
1075 */
1076 bool fCreatedExtractDir;
1077 rcExit = ExtractFiles(pHeader->byCntPkgs, szExtractPath,
1078 fExtractOnly, &fCreatedExtractDir);
1079 if (rcExit == RTEXITCODE_SUCCESS)
1080 {
1081 if (fExtractOnly)
1082 ShowInfo("Files were extracted to: %s", szExtractPath);
1083 else
1084 {
1085 rcExit = CopyCustomDir(szExtractPath);
1086#ifdef VBOX_WITH_CODE_SIGNING
1087 if (rcExit == RTEXITCODE_SUCCESS && fEnableSilentCert && g_fSilent)
1088 rcExit = InstallCertificate();
1089#endif
1090 unsigned iPackage = 0;
1091 while ( iPackage < pHeader->byCntPkgs
1092 && rcExit == RTEXITCODE_SUCCESS)
1093 {
1094 rcExit = ProcessPackage(iPackage, szExtractPath,
1095 szMSIArgs, fEnableLogging);
1096 iPackage++;
1097 }
1098
1099 /* Don't fail if cleanup fail. At least for now. */
1100 CleanUp(pHeader->byCntPkgs,
1101 !fEnableLogging
1102 && fCreatedExtractDir ? szExtractPath : NULL);
1103 }
1104 }
1105
1106 /* Free any left behind cleanup records (not strictly needed). */
1107 PSTUBCLEANUPREC pCur, pNext;
1108 RTListForEachSafe(&g_TmpFiles, pCur, pNext, STUBCLEANUPREC, ListEntry)
1109 {
1110 RTListNodeRemove(&pCur->ListEntry);
1111 RTMemFree(pCur);
1112 }
1113 }
1114
1115#if defined(_WIN32_WINNT) && _WIN32_WINNT >= 0x0501
1116# ifdef VBOX_STUB_WITH_OWN_CONSOLE
1117 if (g_iVerbosity)
1118 FreeConsole();
1119# endif /* VBOX_STUB_WITH_OWN_CONSOLE */
1120#endif
1121
1122 /*
1123 * Release instance mutex.
1124 */
1125 if (hMutexAppRunning != NULL)
1126 {
1127 CloseHandle(hMutexAppRunning);
1128 hMutexAppRunning = NULL;
1129 }
1130
1131 return rcExit;
1132}
1133
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