VirtualBox

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

Last change on this file since 62487 was 62487, checked in by vboxsync, 8 years ago

(C) 2016

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