summaryrefslogtreecommitdiffstats
path: root/src/VBox/Installer/win/Stub/VBoxStub.cpp
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-06 03:01:46 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-06 03:01:46 +0000
commitf8fe689a81f906d1b91bb3220acde2a4ecb14c5b (patch)
tree26484e9d7e2c67806c2d1760196ff01aaa858e8c /src/VBox/Installer/win/Stub/VBoxStub.cpp
parentInitial commit. (diff)
downloadvirtualbox-f8fe689a81f906d1b91bb3220acde2a4ecb14c5b.tar.xz
virtualbox-f8fe689a81f906d1b91bb3220acde2a4ecb14c5b.zip
Adding upstream version 6.0.4-dfsg.upstream/6.0.4-dfsgupstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/VBox/Installer/win/Stub/VBoxStub.cpp')
-rw-r--r--src/VBox/Installer/win/Stub/VBoxStub.cpp1197
1 files changed, 1197 insertions, 0 deletions
diff --git a/src/VBox/Installer/win/Stub/VBoxStub.cpp b/src/VBox/Installer/win/Stub/VBoxStub.cpp
new file mode 100644
index 00000000..32647468
--- /dev/null
+++ b/src/VBox/Installer/win/Stub/VBoxStub.cpp
@@ -0,0 +1,1197 @@
+/* $Id: VBoxStub.cpp $ */
+/** @file
+ * VBoxStub - VirtualBox's Windows installer stub.
+ */
+
+/*
+ * Copyright (C) 2010-2019 Oracle Corporation
+ *
+ * This file is part of VirtualBox Open Source Edition (OSE), as
+ * available from http://www.virtualbox.org. This file is free software;
+ * you can redistribute it and/or modify it under the terms of the GNU
+ * General Public License (GPL) as published by the Free Software
+ * Foundation, in version 2 as it comes in the "COPYING" file of the
+ * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
+ * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#if defined(_WIN32_WINNT) && _WIN32_WINNT < 0x0501
+# undef _WIN32_WINNT
+# define _WIN32_WINNT 0x0501 /* AttachConsole() / FreeConsole(). */
+#endif
+
+#include <iprt/win/windows.h>
+#include <commctrl.h>
+#include <fcntl.h>
+#include <io.h>
+#include <lmerr.h>
+#include <msiquery.h>
+#include <iprt/win/objbase.h>
+
+#include <iprt/win/shlobj.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <strsafe.h>
+
+#include <VBox/version.h>
+
+#include <iprt/assert.h>
+#include <iprt/dir.h>
+#include <iprt/err.h>
+#include <iprt/file.h>
+#include <iprt/getopt.h>
+#include <iprt/initterm.h>
+#include <iprt/list.h>
+#include <iprt/mem.h>
+#include <iprt/message.h>
+#include <iprt/param.h>
+#include <iprt/path.h>
+#include <iprt/stream.h>
+#include <iprt/string.h>
+#include <iprt/thread.h>
+#include <iprt/utf16.h>
+
+#include "VBoxStub.h"
+#include "../StubBld/VBoxStubBld.h"
+#include "resource.h"
+
+#ifdef VBOX_WITH_CODE_SIGNING
+# include "VBoxStubCertUtil.h"
+# include "VBoxStubPublicCert.h"
+#endif
+
+#ifndef TARGET_NT4
+/* Use an own console window if run in verbose mode. */
+# define VBOX_STUB_WITH_OWN_CONSOLE
+#endif
+
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+#define MY_UNICODE_SUB(str) L ##str
+#define MY_UNICODE(str) MY_UNICODE_SUB(str)
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+/**
+ * Cleanup record.
+ */
+typedef struct STUBCLEANUPREC
+{
+ /** List entry. */
+ RTLISTNODE ListEntry;
+ /** True if file, false if directory. */
+ bool fFile;
+ /** The path to the file or directory to clean up. */
+ char szPath[1];
+} STUBCLEANUPREC;
+/** Pointer to a cleanup record. */
+typedef STUBCLEANUPREC *PSTUBCLEANUPREC;
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+/** Whether it's a silent or interactive GUI driven install. */
+static bool g_fSilent = false;
+/** List of temporary files. */
+static RTLISTANCHOR g_TmpFiles;
+/** Verbosity flag. */
+static int g_iVerbosity = 0;
+
+
+
+/**
+ * Shows an error message box with a printf() style formatted string.
+ *
+ * @returns RTEXITCODE_FAILURE
+ * @param pszFmt Printf-style format string to show in the message box body.
+ *
+ */
+static RTEXITCODE ShowError(const char *pszFmt, ...)
+{
+ char *pszMsg;
+ va_list va;
+
+ va_start(va, pszFmt);
+ if (RTStrAPrintfV(&pszMsg, pszFmt, va))
+ {
+ if (g_fSilent)
+ RTMsgError("%s", pszMsg);
+ else
+ {
+ PRTUTF16 pwszMsg;
+ int rc = RTStrToUtf16(pszMsg, &pwszMsg);
+ if (RT_SUCCESS(rc))
+ {
+ MessageBoxW(GetDesktopWindow(), pwszMsg, MY_UNICODE(VBOX_STUB_TITLE), MB_ICONERROR);
+ RTUtf16Free(pwszMsg);
+ }
+ else
+ MessageBoxA(GetDesktopWindow(), pszMsg, VBOX_STUB_TITLE, MB_ICONERROR);
+ }
+ RTStrFree(pszMsg);
+ }
+ else /* Should never happen! */
+ AssertMsgFailed(("Failed to format error text of format string: %s!\n", pszFmt));
+ va_end(va);
+ return RTEXITCODE_FAILURE;
+}
+
+
+/**
+ * Shows a message box with a printf() style formatted string.
+ *
+ * @param uType Type of the message box (see MSDN).
+ * @param pszFmt Printf-style format string to show in the message box body.
+ *
+ */
+static void ShowInfo(const char *pszFmt, ...)
+{
+ char *pszMsg;
+ va_list va;
+ va_start(va, pszFmt);
+ int rc = RTStrAPrintfV(&pszMsg, pszFmt, va);
+ va_end(va);
+ if (rc >= 0)
+ {
+ if (g_fSilent)
+ RTPrintf("%s\n", pszMsg);
+ else
+ {
+ PRTUTF16 pwszMsg;
+ int rc = RTStrToUtf16(pszMsg, &pwszMsg);
+ if (RT_SUCCESS(rc))
+ {
+ MessageBoxW(GetDesktopWindow(), pwszMsg, MY_UNICODE(VBOX_STUB_TITLE), MB_ICONINFORMATION);
+ RTUtf16Free(pwszMsg);
+ }
+ else
+ MessageBoxA(GetDesktopWindow(), pszMsg, VBOX_STUB_TITLE, MB_ICONINFORMATION);
+ }
+ }
+ else /* Should never happen! */
+ AssertMsgFailed(("Failed to format error text of format string: %s!\n", pszFmt));
+ RTStrFree(pszMsg);
+}
+
+
+/**
+ * Finds the specified in the resource section of the executable.
+ *
+ * @returns IPRT status code.
+ *
+ * @param pszDataName Name of resource to read.
+ * @param ppvResource Where to return the pointer to the data.
+ * @param pdwSize Where to return the size of the data (if found).
+ * Optional.
+ */
+static int FindData(const char *pszDataName, PVOID *ppvResource, DWORD *pdwSize)
+{
+ AssertReturn(pszDataName, VERR_INVALID_PARAMETER);
+ HINSTANCE hInst = NULL; /* indicates the executable image */
+
+ /* Find our resource. */
+ PRTUTF16 pwszDataName;
+ int rc = RTStrToUtf16(pszDataName, &pwszDataName);
+ AssertRCReturn(rc, rc);
+ HRSRC hRsrc = FindResourceExW(hInst,
+ (LPWSTR)RT_RCDATA,
+ pwszDataName,
+ MAKELANGID(LANG_NEUTRAL, SUBLANG_NEUTRAL));
+ RTUtf16Free(pwszDataName);
+ AssertReturn(hRsrc, VERR_IO_GEN_FAILURE);
+
+ /* Get resource size. */
+ DWORD cb = SizeofResource(hInst, hRsrc);
+ AssertReturn(cb > 0, VERR_NO_DATA);
+ if (pdwSize)
+ *pdwSize = cb;
+
+ /* Get pointer to resource. */
+ HGLOBAL hData = LoadResource(hInst, hRsrc);
+ AssertReturn(hData, VERR_IO_GEN_FAILURE);
+
+ /* Lock resource. */
+ *ppvResource = LockResource(hData);
+ AssertReturn(*ppvResource, VERR_IO_GEN_FAILURE);
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Finds the header for the given package.
+ *
+ * @returns Pointer to the package header on success. On failure NULL is
+ * returned after ShowError has been invoked.
+ * @param iPackage The package number.
+ */
+static PVBOXSTUBPKG FindPackageHeader(unsigned iPackage)
+{
+ char szHeaderName[32];
+ RTStrPrintf(szHeaderName, sizeof(szHeaderName), "HDR_%02d", iPackage);
+
+ PVBOXSTUBPKG pPackage;
+ int rc = FindData(szHeaderName, (PVOID *)&pPackage, NULL);
+ if (RT_FAILURE(rc))
+ {
+ ShowError("Internal error: Could not find package header #%u: %Rrc", iPackage, rc);
+ return NULL;
+ }
+
+ /** @todo validate it. */
+ return pPackage;
+}
+
+
+
+/**
+ * Constructs a full temporary file path from the given parameters.
+ *
+ * @returns iprt status code.
+ *
+ * @param pszTempPath The pure path to use for construction.
+ * @param pszTargetFileName The pure file name to use for construction.
+ * @param ppszTempFile Pointer to the constructed string. Must be freed
+ * using RTStrFree().
+ */
+static int GetTempFileAlloc(const char *pszTempPath,
+ const char *pszTargetFileName,
+ char **ppszTempFile)
+{
+ if (RTStrAPrintf(ppszTempFile, "%s\\%s", pszTempPath, pszTargetFileName) >= 0)
+ return VINF_SUCCESS;
+ return VERR_NO_STR_MEMORY;
+}
+
+
+/**
+ * Extracts a built-in resource to disk.
+ *
+ * @returns iprt status code.
+ *
+ * @param pszResourceName The resource name to extract.
+ * @param pszTempFile The full file path + name to extract the resource to.
+ *
+ */
+static int ExtractFile(const char *pszResourceName,
+ const char *pszTempFile)
+{
+#if 0 /* Another example of how unnecessarily complicated things get with
+ do-break-while-false and you end up with buggy code using uninitialized
+ variables. */
+ int rc;
+ RTFILE fh;
+ BOOL bCreatedFile = FALSE;
+
+ do
+ {
+ AssertMsgBreak(pszResourceName, ("Resource pointer invalid!\n")); /* rc is not initialized here, we'll return garbage. */
+ AssertMsgBreak(pszTempFile, ("Temp file pointer invalid!")); /* Ditto. */
+
+ /* Read the data of the built-in resource. */
+ PVOID pvData = NULL;
+ DWORD dwDataSize = 0;
+ rc = FindData(pszResourceName, &pvData, &dwDataSize);
+ AssertMsgRCBreak(rc, ("Could not read resource data!\n"));
+
+ /* Create new (and replace an old) file. */
+ rc = RTFileOpen(&fh, pszTempFile,
+ RTFILE_O_CREATE_REPLACE
+ | RTFILE_O_WRITE
+ | RTFILE_O_DENY_NOT_DELETE
+ | RTFILE_O_DENY_WRITE);
+ AssertMsgRCBreak(rc, ("Could not open file for writing!\n"));
+ bCreatedFile = TRUE;
+
+ /* Write contents to new file. */
+ size_t cbWritten = 0;
+ rc = RTFileWrite(fh, pvData, dwDataSize, &cbWritten);
+ AssertMsgRCBreak(rc, ("Could not open file for writing!\n"));
+ AssertMsgBreak(dwDataSize == cbWritten, ("File was not extracted completely! Disk full?\n"));
+
+ } while (0);
+
+ if (RTFileIsValid(fh)) /* fh is unused uninitalized (MSC agrees) */
+ RTFileClose(fh);
+
+ if (RT_FAILURE(rc))
+ {
+ if (bCreatedFile)
+ RTFileDelete(pszTempFile);
+ }
+
+#else /* This is exactly the same as above, except no bug and better assertion
+ message. Note only the return-success statment is indented, indicating
+ that the whole do-break-while-false approach was totally unnecessary. */
+
+ AssertPtrReturn(pszResourceName, VERR_INVALID_POINTER);
+ AssertPtrReturn(pszTempFile, VERR_INVALID_POINTER);
+
+ /* Read the data of the built-in resource. */
+ PVOID pvData = NULL;
+ DWORD dwDataSize = 0;
+ int rc = FindData(pszResourceName, &pvData, &dwDataSize);
+ AssertMsgRCReturn(rc, ("Could not read resource data: %Rrc\n", rc), rc);
+
+ /* Create new (and replace an old) file. */
+ RTFILE hFile;
+ rc = RTFileOpen(&hFile, pszTempFile,
+ RTFILE_O_CREATE_REPLACE
+ | RTFILE_O_WRITE
+ | RTFILE_O_DENY_NOT_DELETE
+ | RTFILE_O_DENY_WRITE);
+ AssertMsgRCReturn(rc, ("Could not open '%s' for writing: %Rrc\n", pszTempFile, rc), rc);
+
+ /* Write contents to new file. */
+ size_t cbWritten = 0;
+ rc = RTFileWrite(hFile, pvData, dwDataSize, &cbWritten);
+ AssertMsgStmt(cbWritten == dwDataSize || RT_FAILURE_NP(rc), ("%#zx vs %#x\n", cbWritten, dwDataSize), rc = VERR_WRITE_ERROR);
+
+ int rc2 = RTFileClose(hFile);
+ AssertRC(rc2);
+
+ if (RT_SUCCESS(rc))
+ return VINF_SUCCESS;
+
+ RTFileDelete(pszTempFile);
+
+#endif
+ return rc;
+}
+
+
+/**
+ * Extracts a built-in resource to disk.
+ *
+ * @returns iprt status code.
+ *
+ * @param pPackage Pointer to a VBOXSTUBPKG struct that contains the resource.
+ * @param pszTempFile The full file path + name to extract the resource to.
+ */
+static int Extract(const PVBOXSTUBPKG pPackage,
+ const char *pszTempFile)
+{
+ return ExtractFile(pPackage->szResourceName, pszTempFile);
+}
+
+
+/**
+ * Detects whether we're running on a 32- or 64-bit platform and returns the result.
+ *
+ * @returns TRUE if we're running on a 64-bit OS, FALSE if not.
+ */
+static BOOL IsWow64(void)
+{
+ BOOL fIsWow64 = TRUE;
+ fnIsWow64Process = (LPFN_ISWOW64PROCESS)GetProcAddress(GetModuleHandle(TEXT("kernel32")), "IsWow64Process");
+ if (NULL != fnIsWow64Process)
+ {
+ if (!fnIsWow64Process(GetCurrentProcess(), &fIsWow64))
+ {
+ /* Error in retrieving process type - assume that we're running on 32bit. */
+ return FALSE;
+ }
+ }
+ return fIsWow64;
+}
+
+
+/**
+ * Decides whether we need a specified package to handle or not.
+ *
+ * @returns @c true if we need to handle the specified package, @c false if not.
+ *
+ * @param pPackage Pointer to a VBOXSTUBPKG struct that contains the resource.
+ */
+static bool PackageIsNeeded(PVBOXSTUBPKG pPackage)
+{
+ if (pPackage->byArch == VBOXSTUBPKGARCH_ALL)
+ return true;
+ VBOXSTUBPKGARCH enmArch = IsWow64() ? VBOXSTUBPKGARCH_AMD64 : VBOXSTUBPKGARCH_X86;
+ return pPackage->byArch == enmArch;
+}
+
+
+/**
+ * Adds a cleanup record.
+ *
+ * @returns Fully complained boolean success indicator.
+ * @param pszPath The path to the file or directory to clean up.
+ * @param fFile @c true if file, @c false if directory.
+ */
+static bool AddCleanupRec(const char *pszPath, bool fFile)
+{
+ size_t cchPath = strlen(pszPath); Assert(cchPath > 0);
+ PSTUBCLEANUPREC pRec = (PSTUBCLEANUPREC)RTMemAlloc(RT_UOFFSETOF_DYN(STUBCLEANUPREC, szPath[cchPath + 1]));
+ if (!pRec)
+ {
+ ShowError("Out of memory!");
+ return false;
+ }
+ pRec->fFile = fFile;
+ memcpy(pRec->szPath, pszPath, cchPath + 1);
+
+ RTListPrepend(&g_TmpFiles, &pRec->ListEntry);
+ return true;
+}
+
+
+/**
+ * Cleans up all the extracted files and optionally removes the package
+ * directory.
+ *
+ * @param pszPkgDir The package directory, NULL if it shouldn't be
+ * removed.
+ */
+static void CleanUp(const char *pszPkgDir)
+{
+ for (int i = 0; i < 5; i++)
+ {
+ int rc;
+ bool fFinalTry = i == 4;
+
+ PSTUBCLEANUPREC pCur, pNext;
+ RTListForEachSafe(&g_TmpFiles, pCur, pNext, STUBCLEANUPREC, ListEntry)
+ {
+ if (pCur->fFile)
+ rc = RTFileDelete(pCur->szPath);
+ else
+ {
+ rc = RTDirRemoveRecursive(pCur->szPath, RTDIRRMREC_F_CONTENT_AND_DIR);
+ if (rc == VERR_DIR_NOT_EMPTY && fFinalTry)
+ rc = VINF_SUCCESS;
+ }
+ if (rc == VERR_FILE_NOT_FOUND || rc == VERR_PATH_NOT_FOUND)
+ rc = VINF_SUCCESS;
+ if (RT_SUCCESS(rc))
+ {
+ RTListNodeRemove(&pCur->ListEntry);
+ RTMemFree(pCur);
+ }
+ else if (fFinalTry)
+ {
+ if (pCur->fFile)
+ ShowError("Failed to delete temporary file '%s': %Rrc", pCur->szPath, rc);
+ else
+ ShowError("Failed to delete temporary directory '%s': %Rrc", pCur->szPath, rc);
+ }
+ }
+
+ if (RTListIsEmpty(&g_TmpFiles) || fFinalTry)
+ {
+ if (!pszPkgDir)
+ return;
+ rc = RTDirRemove(pszPkgDir);
+ if (RT_SUCCESS(rc) || rc == VERR_FILE_NOT_FOUND || rc == VERR_PATH_NOT_FOUND || fFinalTry)
+ return;
+ }
+
+ /* Delay a little and try again. */
+ RTThreadSleep(i == 0 ? 100 : 3000);
+ }
+}
+
+
+/**
+ * Processes an MSI package.
+ *
+ * @returns Fully complained exit code.
+ * @param pszMsi The path to the MSI to process.
+ * @param pszMsiArgs Any additional installer (MSI) argument
+ * @param fLogging Whether to enable installer logging.
+ */
+static RTEXITCODE ProcessMsiPackage(const char *pszMsi, const char *pszMsiArgs, bool fLogging)
+{
+ int rc;
+
+ /*
+ * Set UI level.
+ */
+ INSTALLUILEVEL enmDesiredUiLevel = g_fSilent ? INSTALLUILEVEL_NONE : INSTALLUILEVEL_FULL;
+ INSTALLUILEVEL enmRet = MsiSetInternalUI(enmDesiredUiLevel, NULL);
+ if (enmRet == INSTALLUILEVEL_NOCHANGE /* means error */)
+ return ShowError("Internal error: MsiSetInternalUI failed.");
+
+ /*
+ * Enable logging?
+ */
+ if (fLogging)
+ {
+ char szLogFile[RTPATH_MAX];
+ rc = RTStrCopy(szLogFile, sizeof(szLogFile), pszMsi);
+ if (RT_SUCCESS(rc))
+ {
+ RTPathStripFilename(szLogFile);
+ rc = RTPathAppend(szLogFile, sizeof(szLogFile), "VBoxInstallLog.txt");
+ }
+ if (RT_FAILURE(rc))
+ return ShowError("Internal error: Filename path too long.");
+
+ PRTUTF16 pwszLogFile;
+ rc = RTStrToUtf16(szLogFile, &pwszLogFile);
+ if (RT_FAILURE(rc))
+ return ShowError("RTStrToUtf16 failed on '%s': %Rrc", szLogFile, rc);
+
+ UINT uLogLevel = MsiEnableLogW(INSTALLLOGMODE_VERBOSE,
+ pwszLogFile,
+ INSTALLLOGATTRIBUTES_FLUSHEACHLINE);
+ RTUtf16Free(pwszLogFile);
+ if (uLogLevel != ERROR_SUCCESS)
+ return ShowError("MsiEnableLogW failed");
+ }
+
+ /*
+ * Initialize the common controls (extended version). This is necessary to
+ * run the actual .MSI installers with the new fancy visual control
+ * styles (XP+). Also, an integrated manifest is required.
+ */
+ INITCOMMONCONTROLSEX ccEx;
+ ccEx.dwSize = sizeof(INITCOMMONCONTROLSEX);
+ ccEx.dwICC = ICC_LINK_CLASS | ICC_LISTVIEW_CLASSES | ICC_PAGESCROLLER_CLASS |
+ ICC_PROGRESS_CLASS | ICC_STANDARD_CLASSES | ICC_TAB_CLASSES | ICC_TREEVIEW_CLASSES |
+ ICC_UPDOWN_CLASS | ICC_USEREX_CLASSES | ICC_WIN95_CLASSES;
+ InitCommonControlsEx(&ccEx); /* Ignore failure. */
+
+ /*
+ * Convert both strings to UTF-16 and start the installation.
+ */
+ PRTUTF16 pwszMsi;
+ rc = RTStrToUtf16(pszMsi, &pwszMsi);
+ if (RT_FAILURE(rc))
+ return ShowError("RTStrToUtf16 failed on '%s': %Rrc", pszMsi, rc);
+ PRTUTF16 pwszMsiArgs;
+ rc = RTStrToUtf16(pszMsiArgs, &pwszMsiArgs);
+ if (RT_FAILURE(rc))
+ {
+ RTUtf16Free(pwszMsi);
+ return ShowError("RTStrToUtf16 failed on '%s': %Rrc", pszMsi, rc);
+ }
+
+ UINT uStatus = MsiInstallProductW(pwszMsi, pwszMsiArgs);
+ RTUtf16Free(pwszMsi);
+ RTUtf16Free(pwszMsiArgs);
+
+ if (uStatus == ERROR_SUCCESS)
+ return RTEXITCODE_SUCCESS;
+ if (uStatus == ERROR_SUCCESS_REBOOT_REQUIRED)
+ return RTEXITCODE_SUCCESS; /* we currently don't indicate this */
+
+ /*
+ * Installation failed. Figure out what to say.
+ */
+ switch (uStatus)
+ {
+ case ERROR_INSTALL_USEREXIT:
+ /* Don't say anything? */
+ break;
+
+ case ERROR_INSTALL_PACKAGE_VERSION:
+ ShowError("This installation package cannot be installed by the Windows Installer service.\n"
+ "You must install a Windows service pack that contains a newer version of the Windows Installer service.");
+ break;
+
+ case ERROR_INSTALL_PLATFORM_UNSUPPORTED:
+ ShowError("This installation package is not supported on this platform.");
+ break;
+
+ default:
+ {
+ /*
+ * Try get windows to format the message.
+ */
+ DWORD dwFormatFlags = FORMAT_MESSAGE_ALLOCATE_BUFFER
+ | FORMAT_MESSAGE_IGNORE_INSERTS
+ | FORMAT_MESSAGE_FROM_SYSTEM;
+ HMODULE hModule = NULL;
+ if (uStatus >= NERR_BASE && uStatus <= MAX_NERR)
+ {
+ hModule = LoadLibraryExW(L"netmsg.dll",
+ NULL,
+ LOAD_LIBRARY_AS_DATAFILE);
+ if (hModule != NULL)
+ dwFormatFlags |= FORMAT_MESSAGE_FROM_HMODULE;
+ }
+
+ PWSTR pwszMsg;
+ if (FormatMessageW(dwFormatFlags,
+ hModule, /* If NULL, load system stuff. */
+ uStatus,
+ MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
+ (PWSTR)&pwszMsg,
+ 0,
+ NULL) > 0)
+ {
+ ShowError("Installation failed! Error: %ls", pwszMsg);
+ LocalFree(pwszMsg);
+ }
+ else /* If text lookup failed, show at least the error number. */
+ ShowError("Installation failed! Error: %u", uStatus);
+
+ if (hModule)
+ FreeLibrary(hModule);
+ break;
+ }
+ }
+
+ return RTEXITCODE_FAILURE;
+}
+
+
+/**
+ * Processes a package.
+ *
+ * @returns Fully complained exit code.
+ * @param iPackage The package number.
+ * @param pszPkgDir The package directory (aka extraction dir).
+ * @param pszMsiArgs Any additional installer (MSI) argument
+ * @param fLogging Whether to enable installer logging.
+ */
+static RTEXITCODE ProcessPackage(unsigned iPackage, const char *pszPkgDir, const char *pszMsiArgs, bool fLogging)
+{
+ /*
+ * Get the package header and check if it's needed.
+ */
+ PVBOXSTUBPKG pPackage = FindPackageHeader(iPackage);
+ if (pPackage == NULL)
+ return RTEXITCODE_FAILURE;
+
+ if (!PackageIsNeeded(pPackage))
+ return RTEXITCODE_SUCCESS;
+
+ /*
+ * Deal with the file based on it's extension.
+ */
+ char szPkgFile[RTPATH_MAX];
+ int rc = RTPathJoin(szPkgFile, sizeof(szPkgFile), pszPkgDir, pPackage->szFileName);
+ if (RT_FAILURE(rc))
+ return ShowError("Internal error: RTPathJoin failed: %Rrc", rc);
+ RTPathChangeToDosSlashes(szPkgFile, true /* Force conversion. */); /* paranoia */
+
+ RTEXITCODE rcExit;
+ const char *pszSuff = RTPathSuffix(szPkgFile);
+ if (RTStrICmp(pszSuff, ".msi") == 0)
+ rcExit = ProcessMsiPackage(szPkgFile, pszMsiArgs, fLogging);
+ else if (RTStrICmp(pszSuff, ".cab") == 0)
+ rcExit = RTEXITCODE_SUCCESS; /* Ignore .cab files, they're generally referenced by other files. */
+ else
+ rcExit = ShowError("Internal error: Do not know how to handle file '%s'.", pPackage->szFileName);
+
+ return rcExit;
+}
+
+
+#ifdef VBOX_WITH_CODE_SIGNING
+/**
+ * Install the public certificate into TrustedPublishers so the installer won't
+ * prompt the user during silent installs.
+ *
+ * @returns Fully complained exit code.
+ */
+static RTEXITCODE InstallCertificates(void)
+{
+ for (uint32_t i = 0; i < RT_ELEMENTS(g_aVBoxStubTrustedCerts); i++)
+ {
+ if (!addCertToStore(CERT_SYSTEM_STORE_LOCAL_MACHINE,
+ "TrustedPublisher",
+ g_aVBoxStubTrustedCerts[i].pab,
+ g_aVBoxStubTrustedCerts[i].cb))
+ return ShowError("Failed to construct install certificate.");
+ }
+ return RTEXITCODE_SUCCESS;
+}
+#endif /* VBOX_WITH_CODE_SIGNING */
+
+
+/**
+ * Copies the "<exepath>.custom" directory to the extraction path if it exists.
+ *
+ * This is used by the MSI packages from the resource section.
+ *
+ * @returns Fully complained exit code.
+ * @param pszDstDir The destination directory.
+ */
+static RTEXITCODE CopyCustomDir(const char *pszDstDir)
+{
+ char szSrcDir[RTPATH_MAX];
+ int rc = RTPathExecDir(szSrcDir, sizeof(szSrcDir));
+ if (RT_SUCCESS(rc))
+ rc = RTPathAppend(szSrcDir, sizeof(szSrcDir), ".custom");
+ if (RT_FAILURE(rc))
+ return ShowError("Failed to construct '.custom' dir path: %Rrc", rc);
+
+ if (RTDirExists(szSrcDir))
+ {
+ /*
+ * Use SHFileOperation w/ FO_COPY to do the job. This API requires an
+ * extra zero at the end of both source and destination paths.
+ */
+ size_t cwc;
+ RTUTF16 wszSrcDir[RTPATH_MAX + 1];
+ PRTUTF16 pwszSrcDir = wszSrcDir;
+ rc = RTStrToUtf16Ex(szSrcDir, RTSTR_MAX, &pwszSrcDir, RTPATH_MAX, &cwc);
+ if (RT_FAILURE(rc))
+ return ShowError("RTStrToUtf16Ex failed on '%s': %Rrc", szSrcDir, rc);
+ wszSrcDir[cwc] = '\0';
+
+ RTUTF16 wszDstDir[RTPATH_MAX + 1];
+ PRTUTF16 pwszDstDir = wszSrcDir;
+ rc = RTStrToUtf16Ex(pszDstDir, RTSTR_MAX, &pwszDstDir, RTPATH_MAX, &cwc);
+ if (RT_FAILURE(rc))
+ return ShowError("RTStrToUtf16Ex failed on '%s': %Rrc", pszDstDir, rc);
+ wszDstDir[cwc] = '\0';
+
+ SHFILEOPSTRUCTW FileOp;
+ RT_ZERO(FileOp); /* paranoia */
+ FileOp.hwnd = NULL;
+ FileOp.wFunc = FO_COPY;
+ FileOp.pFrom = wszSrcDir;
+ FileOp.pTo = wszDstDir;
+ FileOp.fFlags = FOF_SILENT
+ | FOF_NOCONFIRMATION
+ | FOF_NOCONFIRMMKDIR
+ | FOF_NOERRORUI;
+ FileOp.fAnyOperationsAborted = FALSE;
+ FileOp.hNameMappings = NULL;
+ FileOp.lpszProgressTitle = NULL;
+
+ rc = SHFileOperationW(&FileOp);
+ if (rc != 0) /* Not a Win32 status code! */
+ return ShowError("Copying the '.custom' dir failed: %#x", rc);
+
+ /*
+ * Add a cleanup record for recursively deleting the destination
+ * .custom directory. We should actually add this prior to calling
+ * SHFileOperationW since it may partially succeed...
+ */
+ char *pszDstSubDir = RTPathJoinA(pszDstDir, ".custom");
+ if (!pszDstSubDir)
+ return ShowError("Out of memory!");
+ bool fRc = AddCleanupRec(pszDstSubDir, false /*fFile*/);
+ RTStrFree(pszDstSubDir);
+ if (!fRc)
+ return RTEXITCODE_FAILURE;
+ }
+
+ return RTEXITCODE_SUCCESS;
+}
+
+
+static RTEXITCODE ExtractFiles(unsigned cPackages, const char *pszDstDir, bool fExtractOnly, bool *pfCreatedExtractDir)
+{
+ int rc;
+
+ /*
+ * Make sure the directory exists.
+ */
+ *pfCreatedExtractDir = false;
+ if (!RTDirExists(pszDstDir))
+ {
+ rc = RTDirCreate(pszDstDir, 0700, 0);
+ if (RT_FAILURE(rc))
+ return ShowError("Failed to create extraction path '%s': %Rrc", pszDstDir, rc);
+ *pfCreatedExtractDir = true;
+ }
+
+ /*
+ * Extract files.
+ */
+ for (unsigned k = 0; k < cPackages; k++)
+ {
+ PVBOXSTUBPKG pPackage = FindPackageHeader(k);
+ if (!pPackage)
+ return RTEXITCODE_FAILURE; /* Done complaining already. */
+
+ if (fExtractOnly || PackageIsNeeded(pPackage))
+ {
+ char szDstFile[RTPATH_MAX];
+ rc = RTPathJoin(szDstFile, sizeof(szDstFile), pszDstDir, pPackage->szFileName);
+ if (RT_FAILURE(rc))
+ return ShowError("Internal error: RTPathJoin failed: %Rrc", rc);
+
+ rc = Extract(pPackage, szDstFile);
+ if (RT_FAILURE(rc))
+ return ShowError("Error extracting package #%u: %Rrc", k, rc);
+
+ if (!fExtractOnly && !AddCleanupRec(szDstFile, true /*fFile*/))
+ {
+ RTFileDelete(szDstFile);
+ return RTEXITCODE_FAILURE;
+ }
+ }
+ }
+
+ return RTEXITCODE_SUCCESS;
+}
+
+
+int WINAPI WinMain(HINSTANCE hInstance,
+ HINSTANCE hPrevInstance,
+ char *lpCmdLine,
+ int nCmdShow)
+{
+ RT_NOREF(hInstance, hPrevInstance, lpCmdLine, nCmdShow);
+ char **argv = __argv;
+ int argc = __argc;
+
+ /*
+ * Init IPRT. This is _always_ the very first thing we do.
+ */
+ int vrc = RTR3InitExe(argc, &argv, RTR3INIT_FLAGS_STANDALONE_APP);
+ if (RT_FAILURE(vrc))
+ return RTMsgInitFailure(vrc);
+
+ /*
+ * Check if we're already running and jump out if so.
+ *
+ * Note! Do not use a global namespace ("Global\\") for mutex name here,
+ * will blow up NT4 compatibility!
+ */
+ HANDLE hMutexAppRunning = CreateMutex(NULL, FALSE, "VBoxStubInstaller");
+ if ( hMutexAppRunning != NULL
+ && GetLastError() == ERROR_ALREADY_EXISTS)
+ {
+ /* Close the mutex for this application instance. */
+ CloseHandle(hMutexAppRunning);
+ hMutexAppRunning = NULL;
+ return RTEXITCODE_FAILURE;
+ }
+
+ /*
+ * Parse arguments.
+ */
+
+ /* Parameter variables. */
+ bool fExtractOnly = false;
+ bool fEnableLogging = false;
+#ifdef VBOX_WITH_CODE_SIGNING
+ bool fEnableSilentCert = true;
+#endif
+ char szExtractPath[RTPATH_MAX] = {0};
+ char szMSIArgs[_4K] = {0};
+
+ /* Parameter definitions. */
+ static const RTGETOPTDEF s_aOptions[] =
+ {
+ /** @todo Replace short parameters with enums since they're not
+ * used (and not documented to the public). */
+ { "--extract", 'x', RTGETOPT_REQ_NOTHING },
+ { "-extract", 'x', RTGETOPT_REQ_NOTHING },
+ { "/extract", 'x', RTGETOPT_REQ_NOTHING },
+ { "--silent", 's', RTGETOPT_REQ_NOTHING },
+ { "-silent", 's', RTGETOPT_REQ_NOTHING },
+ { "/silent", 's', RTGETOPT_REQ_NOTHING },
+#ifdef VBOX_WITH_CODE_SIGNING
+ { "--no-silent-cert", 'c', RTGETOPT_REQ_NOTHING },
+ { "-no-silent-cert", 'c', RTGETOPT_REQ_NOTHING },
+ { "/no-silent-cert", 'c', RTGETOPT_REQ_NOTHING },
+#endif
+ { "--logging", 'l', RTGETOPT_REQ_NOTHING },
+ { "-logging", 'l', RTGETOPT_REQ_NOTHING },
+ { "/logging", 'l', RTGETOPT_REQ_NOTHING },
+ { "--path", 'p', RTGETOPT_REQ_STRING },
+ { "-path", 'p', RTGETOPT_REQ_STRING },
+ { "/path", 'p', RTGETOPT_REQ_STRING },
+ { "--msiparams", 'm', RTGETOPT_REQ_STRING },
+ { "-msiparams", 'm', RTGETOPT_REQ_STRING },
+ { "--reinstall", 'f', RTGETOPT_REQ_NOTHING },
+ { "-reinstall", 'f', RTGETOPT_REQ_NOTHING },
+ { "/reinstall", 'f', RTGETOPT_REQ_NOTHING },
+ { "--verbose", 'v', RTGETOPT_REQ_NOTHING },
+ { "-verbose", 'v', RTGETOPT_REQ_NOTHING },
+ { "/verbose", 'v', RTGETOPT_REQ_NOTHING },
+ { "--version", 'V', RTGETOPT_REQ_NOTHING },
+ { "-version", 'V', RTGETOPT_REQ_NOTHING },
+ { "/version", 'V', RTGETOPT_REQ_NOTHING },
+ { "-v", 'V', RTGETOPT_REQ_NOTHING },
+ { "--help", 'h', RTGETOPT_REQ_NOTHING },
+ { "-help", 'h', RTGETOPT_REQ_NOTHING },
+ { "/help", 'h', RTGETOPT_REQ_NOTHING },
+ { "/?", 'h', RTGETOPT_REQ_NOTHING },
+ };
+
+ RTEXITCODE rcExit = RTEXITCODE_SUCCESS;
+
+ /* Parse the parameters. */
+ int ch;
+ bool fExitEarly = false;
+ RTGETOPTUNION ValueUnion;
+ RTGETOPTSTATE GetState;
+ RTGetOptInit(&GetState, argc, argv, s_aOptions, RT_ELEMENTS(s_aOptions), 1, 0);
+ while ( (ch = RTGetOpt(&GetState, &ValueUnion))
+ && rcExit == RTEXITCODE_SUCCESS
+ && !fExitEarly)
+ {
+ switch (ch)
+ {
+ case 'f': /* Force re-installation. */
+ if (szMSIArgs[0])
+ vrc = RTStrCat(szMSIArgs, sizeof(szMSIArgs), " ");
+ if (RT_SUCCESS(vrc))
+ vrc = RTStrCat(szMSIArgs, sizeof(szMSIArgs),
+ "REINSTALLMODE=vomus REINSTALL=ALL");
+ if (RT_FAILURE(vrc))
+ rcExit = ShowError("MSI parameters are too long.");
+ break;
+
+ case 'x':
+ fExtractOnly = true;
+ break;
+
+ case 's':
+ g_fSilent = true;
+ break;
+
+#ifdef VBOX_WITH_CODE_SIGNING
+ case 'c':
+ fEnableSilentCert = false;
+ break;
+#endif
+ case 'l':
+ fEnableLogging = true;
+ break;
+
+ case 'p':
+ vrc = RTStrCopy(szExtractPath, sizeof(szExtractPath), ValueUnion.psz);
+ if (RT_FAILURE(vrc))
+ rcExit = ShowError("Extraction path is too long.");
+ break;
+
+ case 'm':
+ if (szMSIArgs[0])
+ vrc = RTStrCat(szMSIArgs, sizeof(szMSIArgs), " ");
+ if (RT_SUCCESS(vrc))
+ vrc = RTStrCat(szMSIArgs, sizeof(szMSIArgs), ValueUnion.psz);
+ if (RT_FAILURE(vrc))
+ rcExit = ShowError("MSI parameters are too long.");
+ break;
+
+ case 'V':
+ ShowInfo("Version: %d.%d.%d.%d",
+ VBOX_VERSION_MAJOR, VBOX_VERSION_MINOR, VBOX_VERSION_BUILD,
+ VBOX_SVN_REV);
+ fExitEarly = true;
+ break;
+
+ case 'v':
+ g_iVerbosity++;
+ break;
+
+ case 'h':
+ ShowInfo("-- %s v%d.%d.%d.%d --\n"
+ "\n"
+ "Command Line Parameters:\n\n"
+ "--extract - Extract file contents to temporary directory\n"
+ "--help - Print this help and exit\n"
+ "--logging - Enables installer logging\n"
+ "--msiparams <parameters> - Specifies extra parameters for the MSI installers\n"
+ "--no-silent-cert - Do not install VirtualBox Certificate automatically when --silent option is specified\n"
+ "--path - Sets the path of the extraction directory\n"
+ "--reinstall - Forces VirtualBox to get re-installed\n"
+ "--silent - Enables silent mode installation\n"
+ "--version - Print version number and exit\n\n"
+ "Examples:\n"
+ "%s --msiparams INSTALLDIR=C:\\VBox\n"
+ "%s --extract -path C:\\VBox",
+ VBOX_STUB_TITLE, VBOX_VERSION_MAJOR, VBOX_VERSION_MINOR, VBOX_VERSION_BUILD, VBOX_SVN_REV,
+ argv[0], argv[0]);
+ fExitEarly = true;
+ break;
+
+ case VINF_GETOPT_NOT_OPTION:
+ /* Are (optional) MSI parameters specified and this is the last
+ * parameter? Append everything to the MSI parameter list then. */
+ if (szMSIArgs[0])
+ {
+ vrc = RTStrCat(szMSIArgs, sizeof(szMSIArgs), " ");
+ if (RT_SUCCESS(vrc))
+ vrc = RTStrCat(szMSIArgs, sizeof(szMSIArgs), ValueUnion.psz);
+ if (RT_FAILURE(vrc))
+ rcExit = ShowError("MSI parameters are too long.");
+ continue;
+ }
+ /* Fall through is intentional. */
+
+ default:
+ if (g_fSilent)
+ rcExit = RTGetOptPrintError(ch, &ValueUnion);
+ if (ch == VERR_GETOPT_UNKNOWN_OPTION)
+ rcExit = ShowError("Unknown option \"%s\"\n"
+ "Please refer to the command line help by specifying \"/?\"\n"
+ "to get more information.", ValueUnion.psz);
+ else
+ rcExit = ShowError("Parameter parsing error: %Rrc\n"
+ "Please refer to the command line help by specifying \"/?\"\n"
+ "to get more information.", ch);
+ break;
+ }
+ }
+
+ /* Check if we can bail out early. */
+ if (fExitEarly)
+ return rcExit;
+
+ if (rcExit != RTEXITCODE_SUCCESS)
+ vrc = VERR_PARSE_ERROR;
+
+/** @todo
+ *
+ * Split the remainder up in functions and simplify the code flow!!
+ *
+ * */
+
+#if defined(_WIN32_WINNT) && _WIN32_WINNT >= 0x0501
+# ifdef VBOX_STUB_WITH_OWN_CONSOLE /* Use an own console window if run in debug mode. */
+ if ( RT_SUCCESS(vrc)
+ && g_iVerbosity)
+ {
+ if (!AllocConsole())
+ {
+ DWORD dwErr = GetLastError();
+ ShowError("Unable to allocate console, error = %ld\n",
+ dwErr);
+
+ /* Close the mutex for this application instance. */
+ CloseHandle(hMutexAppRunning);
+ hMutexAppRunning = NULL;
+ return RTEXITCODE_FAILURE;
+ }
+
+ freopen("CONOUT$", "w", stdout);
+ setvbuf(stdout, NULL, _IONBF, 0);
+
+ freopen("CONOUT$", "w", stderr);
+ }
+# endif /* VBOX_STUB_WITH_OWN_CONSOLE */
+#endif
+
+ if ( RT_SUCCESS(vrc)
+ && g_iVerbosity)
+ {
+ RTPrintf("Silent installation : %RTbool\n", g_fSilent);
+ RTPrintf("Logging enabled : %RTbool\n", fEnableLogging);
+#ifdef VBOX_WITH_CODE_SIGNING
+ RTPrintf("Certificate installation : %RTbool\n", fEnableSilentCert);
+#endif
+ RTPrintf("Additional MSI parameters: %s\n",
+ szMSIArgs[0] ? szMSIArgs : "<None>");
+ }
+
+ /*
+ * 32-bit is not officially supported any more.
+ */
+ if ( !fExtractOnly
+ && !g_fSilent
+ && !IsWow64())
+ {
+ rcExit = ShowError("32-bit Windows hosts are not supported by this VirtualBox release.");
+ vrc = VERR_NOT_SUPPORTED;
+ }
+
+ if (RT_SUCCESS(vrc))
+ {
+ /*
+ * Determine the extration path if not given by the user, and gather some
+ * other bits we'll be needing later.
+ */
+ if (szExtractPath[0] == '\0')
+ {
+ vrc = RTPathTemp(szExtractPath, sizeof(szExtractPath));
+ if (RT_SUCCESS(vrc))
+ vrc = RTPathAppend(szExtractPath, sizeof(szExtractPath), "VirtualBox");
+ if (RT_FAILURE(vrc))
+ ShowError("Failed to determine extraction path (%Rrc)", vrc);
+
+ }
+ else
+ {
+ /** @todo should check if there is a .custom subdirectory there or not. */
+ }
+ RTPathChangeToDosSlashes(szExtractPath,
+ true /* Force conversion. */); /* MSI requirement. */
+ }
+
+ /* Read our manifest. */
+ if (RT_SUCCESS(vrc))
+ {
+ PVBOXSTUBPKGHEADER pHeader;
+ vrc = FindData("MANIFEST", (PVOID *)&pHeader, NULL);
+ if (RT_SUCCESS(vrc))
+ {
+ /** @todo If we could, we should validate the header. Only the magic isn't
+ * commonly defined, nor the version number... */
+
+ RTListInit(&g_TmpFiles);
+
+ /*
+ * Up to this point, we haven't done anything that requires any cleanup.
+ * From here on, we do everything in function so we can counter clean up.
+ */
+ bool fCreatedExtractDir;
+ rcExit = ExtractFiles(pHeader->byCntPkgs, szExtractPath,
+ fExtractOnly, &fCreatedExtractDir);
+ if (rcExit == RTEXITCODE_SUCCESS)
+ {
+ if (fExtractOnly)
+ ShowInfo("Files were extracted to: %s", szExtractPath);
+ else
+ {
+ rcExit = CopyCustomDir(szExtractPath);
+#ifdef VBOX_WITH_CODE_SIGNING
+ if (rcExit == RTEXITCODE_SUCCESS && fEnableSilentCert && g_fSilent)
+ rcExit = InstallCertificates();
+#endif
+ unsigned iPackage = 0;
+ while ( iPackage < pHeader->byCntPkgs
+ && rcExit == RTEXITCODE_SUCCESS)
+ {
+ rcExit = ProcessPackage(iPackage, szExtractPath,
+ szMSIArgs, fEnableLogging);
+ iPackage++;
+ }
+
+ /* Don't fail if cleanup fail. At least for now. */
+ CleanUp( !fEnableLogging
+ && fCreatedExtractDir ? szExtractPath : NULL);
+ }
+ }
+
+ /* Free any left behind cleanup records (not strictly needed). */
+ PSTUBCLEANUPREC pCur, pNext;
+ RTListForEachSafe(&g_TmpFiles, pCur, pNext, STUBCLEANUPREC, ListEntry)
+ {
+ RTListNodeRemove(&pCur->ListEntry);
+ RTMemFree(pCur);
+ }
+ }
+ else
+ rcExit = ShowError("Internal package error: Manifest not found (%Rrc)", vrc);
+ }
+
+#if defined(_WIN32_WINNT) && _WIN32_WINNT >= 0x0501
+# ifdef VBOX_STUB_WITH_OWN_CONSOLE
+ if (g_iVerbosity)
+ FreeConsole();
+# endif /* VBOX_STUB_WITH_OWN_CONSOLE */
+#endif
+
+ /*
+ * Release instance mutex.
+ */
+ if (hMutexAppRunning != NULL)
+ {
+ CloseHandle(hMutexAppRunning);
+ hMutexAppRunning = NULL;
+ }
+
+ return rcExit;
+}
+