summaryrefslogtreecommitdiff
path: root/src/VBox/Installer/win/Stub/VBoxStub.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/VBox/Installer/win/Stub/VBoxStub.cpp')
-rw-r--r--src/VBox/Installer/win/Stub/VBoxStub.cpp1124
1 files changed, 720 insertions, 404 deletions
diff --git a/src/VBox/Installer/win/Stub/VBoxStub.cpp b/src/VBox/Installer/win/Stub/VBoxStub.cpp
index 22b9bccb..188bf5e6 100644
--- a/src/VBox/Installer/win/Stub/VBoxStub.cpp
+++ b/src/VBox/Installer/win/Stub/VBoxStub.cpp
@@ -4,7 +4,7 @@
*/
/*
- * Copyright (C) 2010 Oracle Corporation
+ * Copyright (C) 2010-2013 Oracle Corporation
*
* This file is part of VirtualBox Open Source Edition (OSE), as
* available from http://www.virtualbox.org. This file is free software;
@@ -18,11 +18,12 @@
/*******************************************************************************
* Header Files *
*******************************************************************************/
-#include <windows.h>
+#include <Windows.h>
#include <commctrl.h>
#include <lmerr.h>
#include <msiquery.h>
#include <objbase.h>
+
#include <shlobj.h>
#include <stdlib.h>
#include <stdio.h>
@@ -34,10 +35,14 @@
#include <iprt/assert.h>
#include <iprt/dir.h>
#include <iprt/file.h>
+#include <iprt/getopt.h>
#include <iprt/initterm.h>
+#include <iprt/list.h>
#include <iprt/mem.h>
-#include <iprt/path.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>
@@ -45,109 +50,192 @@
#include "../StubBld/VBoxStubBld.h"
#include "resource.h"
-#ifndef _UNICODE
-#define _UNICODE
+#ifdef VBOX_WITH_CODE_SIGNING
+# include "VBoxStubCertUtil.h"
+# include "VBoxStubPublicCert.h"
#endif
+/*******************************************************************************
+* Defined Constants And Macros *
+*******************************************************************************/
+#define MY_UNICODE_SUB(str) L ##str
+#define MY_UNICODE(str) MY_UNICODE_SUB(str)
+
+
+/*******************************************************************************
+* Structures and Typedefs *
+*******************************************************************************/
/**
- * Shows a message box with a printf() style formatted string.
- *
- * @returns Message box result (IDOK, IDCANCEL, ...).
- *
- * @param uType Type of the message box (see MSDN).
- * @param pszFmt Printf-style format string to show in the message box body.
- *
+ * Cleanup record.
*/
-static int ShowInfo(const char *pszFmt, ...)
+typedef struct STUBCLEANUPREC
{
- char *pszMsg;
- va_list va;
+ /** 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;
- va_start(va, pszFmt);
- RTStrAPrintfV(&pszMsg, pszFmt, va);
- va_end(va);
- int rc;
- if (pszMsg)
- rc = MessageBox(GetDesktopWindow(), pszMsg, VBOX_STUB_TITLE, MB_ICONINFORMATION);
- else
- rc = MessageBox(GetDesktopWindow(), pszFmt, VBOX_STUB_TITLE, MB_ICONINFORMATION);
- RTStrFree(pszMsg);
- return rc;
-}
+/*******************************************************************************
+* 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;
+
/**
* Shows an error message box with a printf() style formatted string.
*
- * @returns Message box result (IDOK, IDCANCEL, ...).
- *
+ * @returns RTEXITCODE_FAILURE
* @param pszFmt Printf-style format string to show in the message box body.
*
*/
-static int ShowError(const char *pszFmt, ...)
+static RTEXITCODE ShowError(const char *pszFmt, ...)
{
char *pszMsg;
va_list va;
- int rc;
va_start(va, pszFmt);
if (RTStrAPrintfV(&pszMsg, pszFmt, va))
{
- rc = MessageBox(GetDesktopWindow(), pszMsg, VBOX_STUB_TITLE, MB_ICONERROR);
+ 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 rc;
+ return RTEXITCODE_FAILURE;
}
/**
- * Reads data from a built-in resource.
- *
- * @returns iprt status code.
+ * Shows a message box with a printf() style formatted string.
*
- * @param hInst Instance to read the data from.
- * @param pszDataName Name of resource to read.
- * @param ppvResource Pointer to buffer which holds the read resource data.
- * @param pdwSize Pointer which holds the read data size.
+ * @param uType Type of the message box (see MSDN).
+ * @param pszFmt Printf-style format string to show in the message box body.
*
*/
-static int ReadData(HINSTANCE hInst,
- const char *pszDataName,
- PVOID *ppvResource,
- DWORD *pdwSize)
+static void ShowInfo(const char *pszFmt, ...)
{
- do
+ char *pszMsg;
+ va_list va;
+ va_start(va, pszFmt);
+ int rc = RTStrAPrintfV(&pszMsg, pszFmt, va);
+ va_end(va);
+ if (rc >= 0)
{
- AssertMsgBreak(pszDataName, ("Resource name is empty!\n"));
+ 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);
+}
- /* Find our resource. */
- HRSRC hRsrc = FindResourceEx(hInst, RT_RCDATA, pszDataName, MAKELANGID(LANG_NEUTRAL, SUBLANG_NEUTRAL));
- AssertMsgBreak(hRsrc, ("Could not find resource!\n"));
- /* Get resource size. */
- *pdwSize = SizeofResource(hInst, hRsrc);
- AssertMsgBreak(*pdwSize > 0, ("Size of resource is invalid!\n"));
+/**
+ * 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;
+}
- /* Get pointer to resource. */
- HGLOBAL hData = LoadResource(hInst, hRsrc);
- AssertMsgBreak(hData, ("Could not load resource!\n"));
- /* Lock resource. */
- *ppvResource = LockResource(hData);
- AssertMsgBreak(*ppvResource, ("Could not lock resource!\n"));
- 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);
- } while (0);
+ 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;
+ }
- return VERR_IO_GEN_FAILURE;
+ /** @todo validate it. */
+ return pPackage;
}
+
/**
* Constructs a full temporary file path from the given parameters.
*
@@ -192,7 +280,7 @@ static int ExtractFile(const char *pszResourceName,
/* Read the data of the built-in resource. */
PVOID pvData = NULL;
DWORD dwDataSize = 0;
- rc = ReadData(NULL, pszResourceName, &pvData, &dwDataSize);
+ rc = FindData(pszResourceName, &pvData, &dwDataSize);
AssertMsgRCBreak(rc, ("Could not read resource data!\n"));
/* Create new (and replace an old) file. */
@@ -266,61 +354,427 @@ static BOOL IsWow64(void)
/**
* Decides whether we need a specified package to handle or not.
*
- * @returns TRUE if we need to handle the specified package, FALSE if 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)
+static bool PackageIsNeeded(PVBOXSTUBPKG pPackage)
{
- BOOL bIsWow64 = IsWow64();
- if ((bIsWow64 && pPackage->byArch == VBOXSTUBPKGARCH_AMD64)) /* 64bit Windows. */
+ 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_OFFSETOF(STUBCLEANUPREC, szPath[cchPath + 1]));
+ if (!pRec)
{
- return TRUE;
+ ShowError("Out of memory!");
+ return false;
}
- else if ((!bIsWow64 && pPackage->byArch == VBOXSTUBPKGARCH_X86)) /* 32bit. */
+ 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 cPackages The number of packages.
+ * @param pszPkgDir The package directory, NULL if it shouldn't be
+ * removed.
+ */
+static void CleanUp(unsigned cPackages, const char *pszPkgDir)
+{
+ for (int i = 0; i < 5; i++)
{
- return TRUE;
+ 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");
}
- else if (pPackage->byArch == VBOXSTUBPKGARCH_ALL)
+
+ /*
+ * 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))
{
- return TRUE;
+ RTUtf16Free(pwszMsi);
+ return ShowError("RTStrToUtf16 failed on '%s': %Rrc", pszMsi, rc);
}
- return FALSE;
+
+ 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;
}
/**
- * Recursively copies a directory to another location.
+ * Processes a package.
*
- * @returns iprt status code.
+ * @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 *pszExt = RTPathExt(szPkgFile);
+ if (RTStrICmp(pszExt, ".msi") == 0)
+ rcExit = ProcessMsiPackage(szPkgFile, pszMsiArgs, fLogging);
+ else if (RTStrICmp(pszExt, ".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 InstallCertificate(void)
+{
+ if (addCertToStore(CERT_SYSTEM_STORE_LOCAL_MACHINE,
+ "TrustedPublisher",
+ g_ab_VBoxStubPublicCert,
+ sizeof(g_ab_VBoxStubPublicCert)))
+ return RTEXITCODE_SUCCESS;
+ return ShowError("Failed to construct install certificate.");
+}
+#endif /* VBOX_WITH_CODE_SIGNING */
+
+
+/**
+ * Copies the "<exepath>.custom" directory to the extraction path if it exists.
*
- * @param pszDestDir Location to copy the source directory to.
- * @param pszSourceDir The source directory to copy.
+ * This is used by the MSI packages from the resource section.
*
+ * @returns Fully complained exit code.
+ * @param pszDstDir The destination directory.
*/
-int CopyDir(const char *pszDestDir, const char *pszSourceDir)
+static RTEXITCODE CopyCustomDir(const char *pszDstDir)
{
- char szDest[RTPATH_MAX + 1];
- char szSource[RTPATH_MAX + 1];
+ 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;
+}
- AssertStmt(pszDestDir, "Destination directory invalid!");
- AssertStmt(pszSourceDir, "Source directory invalid!");
- SHFILEOPSTRUCT s = {0};
- if ( RTStrPrintf(szDest, _MAX_PATH, "%s%c", pszDestDir, '\0') > 0
- && RTStrPrintf(szSource, _MAX_PATH, "%s%c", pszSourceDir, '\0') > 0)
+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++)
{
- s.hwnd = NULL;
- s.wFunc = FO_COPY;
- s.pTo = szDest;
- s.pFrom = szSource;
- s.fFlags = FOF_SILENT |
- FOF_NOCONFIRMATION |
- FOF_NOCONFIRMMKDIR |
- FOF_NOERRORUI;
+ 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 RTErrConvertFromWin32(SHFileOperation(&s));
+
+ return RTEXITCODE_SUCCESS;
}
@@ -334,361 +788,223 @@ int WINAPI WinMain(HINSTANCE hInstance,
/* Check if we're already running and jump out if so. */
/* 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))
+ 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 1;
+ return RTEXITCODE_FAILURE;
}
/* Init IPRT. */
int vrc = RTR3InitExe(argc, &argv, 0);
if (RT_FAILURE(vrc))
- return vrc;
+ return RTMsgInitFailure(vrc);
- BOOL fExtractOnly = FALSE;
- BOOL fSilent = FALSE;
- BOOL fEnableLogging = FALSE;
- BOOL fExit = FALSE;
+ /*
+ * Parse arguments.
+ */
- /* Temp variables for 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[RTPATH_MAX] = {0};
+ char szMSIArgs[4096] = {0};
- /* Process arguments. */
- for (int i = 0; i < argc; i++)
+ /* Parameter definitions. */
+ static const RTGETOPTDEF s_aOptions[] =
{
- if ( (0 == RTStrICmp(argv[i], "-x"))
- || (0 == RTStrICmp(argv[i], "-extract"))
- || (0 == RTStrICmp(argv[i], "/extract")))
- {
- fExtractOnly = TRUE;
- }
-
- else if ( (0 == RTStrICmp(argv[i], "-s"))
- || (0 == RTStrICmp(argv[i], "-silent"))
- || (0 == RTStrICmp(argv[i], "/silent")))
- {
- fSilent = TRUE;
- }
-
- else if ( (0 == RTStrICmp(argv[i], "-l"))
- || (0 == RTStrICmp(argv[i], "-logging"))
- || (0 == RTStrICmp(argv[i], "/logging")))
- {
- fEnableLogging = TRUE;
- }
-
- else if (( (0 == RTStrICmp(argv[i], "-p"))
- || (0 == RTStrICmp(argv[i], "-path"))
- || (0 == RTStrICmp(argv[i], "/path")))
- )
- {
- if (argc > i)
- {
- vrc = ::StringCbCat(szExtractPath, sizeof(szExtractPath), argv[i+1]);
- i++; /* Avoid the specified path from being parsed. */
- }
- else
- {
- ShowError("No path for extraction specified!");
- fExit = TRUE;
- }
- }
-
- else if (( (0 == RTStrICmp(argv[i], "-msiparams"))
- || (0 == RTStrICmp(argv[i], "/msiparams")))
- && (argc > i))
+ { "--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 },
+ { "--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 },
+ };
+
+ /* Parse the parameters. */
+ int ch;
+ RTGETOPTUNION ValueUnion;
+ RTGETOPTSTATE GetState;
+ RTGetOptInit(&GetState, argc, argv, s_aOptions, RT_ELEMENTS(s_aOptions), 1, 0);
+ while ((ch = RTGetOpt(&GetState, &ValueUnion)))
+ {
+ switch (ch)
{
- for (int a = i + 1; a < argc; a++)
- {
- if (a > i+1) /* Insert a space. */
- vrc = ::StringCbCat(szMSIArgs, sizeof(szMSIArgs), " ");
+ case 'x':
+ fExtractOnly = true;
+ break;
+
+ case 's':
+ g_fSilent = true;
+ break;
+
+#ifdef VBOX_WITH_CODE_SIGNING
+ case 'c':
+ fEnableSilentCert = false;
+ break;
+#endif
- vrc = ::StringCbCat(szMSIArgs, sizeof(szMSIArgs), argv[a]);
- }
- }
+ case 'l':
+ fEnableLogging = true;
+ break;
- else if ( (0 == RTStrICmp(argv[i], "-v"))
- || (0 == RTStrICmp(argv[i], "-version"))
- || (0 == RTStrICmp(argv[i], "/version")))
- {
- ShowInfo("Version: %d.%d.%d.%d",
- VBOX_VERSION_MAJOR, VBOX_VERSION_MINOR, VBOX_VERSION_BUILD, VBOX_SVN_REV);
- fExit = TRUE;
- }
+ case 'p':
+ vrc = RTStrCopy(szExtractPath, sizeof(szExtractPath), ValueUnion.psz);
+ if (RT_FAILURE(vrc))
+ return ShowError("Extraction path is too long.");
+ break;
- else if ( (0 == RTStrICmp(argv[i], "-help"))
- || (0 == RTStrICmp(argv[i], "/help"))
- || (0 == RTStrICmp(argv[i], "/?")))
- {
- ShowInfo("-- %s v%d.%d.%d.%d --\n"
+ 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))
+ return 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);
+ return VINF_SUCCESS;
+
+ case 'h':
+ ShowInfo("-- %s v%d.%d.%d.%d --\n"
+ "\n"
"Command Line Parameters:\n\n"
- "-extract | -x - Extract file contents to temporary directory\n"
- "-silent | -s - Enables silent mode installation\n"
- "-path | -p - Sets the path of the extraction directory\n"
- "-help | /? - Print this help and exit\n"
- "-msiparams <parameters> - Specifies extra parameters for the MSI installers\n"
- "-logging | -l - Enables installer logging\n"
- "-version | -v - Print version number and exit\n\n"
+ "--extract - Extract file contents to temporary directory\n"
+ "--silent - Enables silent mode installation\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"
+ "--msiparams <parameters> - Specifies extra parameters for the MSI installers\n"
+ "--logging - Enables installer logging\n"
+ "--help - Print this help and exit\n"
+ "--version - Print version number and exit\n\n"
"Examples:\n"
- "%s -msiparams INSTALLDIR=C:\\VBox\n"
- "%s -extract -path C:\\VBox\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]);
- fExit = TRUE;
- }
- else
- {
- if (i > 0)
- {
- ShowError("Unknown option \"%s\"!\n"
- "Please refer to the command line help by specifying \"/?\"\n"
- "to get more information.", argv[i]);
- fExit = TRUE;
- }
- }
- }
+ return VINF_SUCCESS;
- if (fExit)
- return 0;
+ default:
+ if (g_fSilent)
+ return RTGetOptPrintError(ch, &ValueUnion);
+ if (ch == VINF_GETOPT_NOT_OPTION || ch == VERR_GETOPT_UNKNOWN_OPTION)
+ ShowError("Unknown option \"%s\"!\n"
+ "Please refer to the command line help by specifying \"/?\"\n"
+ "to get more information.", ValueUnion.psz);
+ else
+ ShowError("Parameter parsing error: %Rrc\n"
+ "Please refer to the command line help by specifying \"/?\"\n"
+ "to get more information.", ch);
+ return RTEXITCODE_SYNTAX;
- HRESULT hr = S_OK;
+ }
+ }
- do /* break loop */
+ /*
+ * Determine the extration path if not given by the user, and gather some
+ * other bits we'll be needing later.
+ */
+ if (szExtractPath[0] == '\0')
{
- /* Get/create our temp path (only if not already set). */
- if (szExtractPath[0] == '\0')
- {
- vrc = RTPathTemp(szExtractPath, sizeof(szExtractPath));
- AssertMsgRCBreak(vrc, ("Could not retrieve temp directory!\n"));
-
+ vrc = RTPathTemp(szExtractPath, sizeof(szExtractPath));
+ if (RT_SUCCESS(vrc))
vrc = RTPathAppend(szExtractPath, sizeof(szExtractPath), "VirtualBox");
- AssertMsgRCBreak(vrc, ("Could not construct temp directory!\n"));
-
- /* Convert slahes; this is necessary for MSI routines later! */
- RTPathChangeToDosSlashes(szExtractPath, true /* Force conversion. */);
- }
- if (!RTDirExists(szExtractPath))
- {
- vrc = RTDirCreate(szExtractPath, 0700, 0);
- AssertMsgRCBreak(vrc, ("Could not create temp directory!\n"));
- }
+ if (RT_FAILURE(vrc))
+ return ShowError("Failed to determin extraction path (%Rrc)", vrc);
- /* Get our executable path */
- char szPathExe[_MAX_PATH];
- vrc = RTPathExecDir(szPathExe, sizeof(szPathExe));
- /** @todo error checking */
-
- /* Read our manifest. */
- PVBOXSTUBPKGHEADER pHeader = NULL;
- DWORD cbHeader = 0;
- vrc = ReadData(NULL, "MANIFEST", (LPVOID*)&pHeader, &cbHeader);
- AssertMsgRCBreak(vrc, ("Manifest not found!\n"));
-
- /* Extract files. */
- for (BYTE k = 0; k < pHeader->byCntPkgs; k++)
- {
- PVBOXSTUBPKG pPackage = NULL;
- DWORD cbPackage = 0;
- char szHeaderName[RTPATH_MAX + 1] = {0};
-
- hr = ::StringCchPrintf(szHeaderName, RTPATH_MAX, "HDR_%02d", k);
- vrc = ReadData(NULL, szHeaderName, (LPVOID*)&pPackage, &cbPackage);
- AssertMsgRCBreak(vrc, ("Header not found!\n")); /** @todo include header name, how? */
-
- if (PackageIsNeeded(pPackage) || fExtractOnly)
- {
- char *pszTempFile = NULL;
- vrc = GetTempFileAlloc(szExtractPath, pPackage->szFileName, &pszTempFile);
- AssertMsgRCBreak(vrc, ("Could not create name for temporary extracted file!\n"));
- vrc = Extract(pPackage, pszTempFile);
- AssertMsgRCBreak(vrc, ("Could not extract file!\n"));
- RTStrFree(pszTempFile);
- }
- }
+ }
+ else
+ {
+ /** @todo should check if there is a .custom subdirectory there or not. */
+ }
+ RTPathChangeToDosSlashes(szExtractPath, true /* Force conversion. */); /* MSI requirement. */
- if (FALSE == fExtractOnly && !RT_FAILURE(vrc))
+ /* Read our manifest. */
+ PVBOXSTUBPKGHEADER pHeader;
+ vrc = FindData("MANIFEST", (PVOID *)&pHeader, NULL);
+ if (RT_FAILURE(vrc))
+ return ShowError("Internal package error: Manifest not found (%Rrc)", 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;
+ RTEXITCODE rcExit = ExtractFiles(pHeader->byCntPkgs, szExtractPath, fExtractOnly, &fCreatedExtractDir);
+ if (rcExit == RTEXITCODE_SUCCESS)
+ {
+ if (fExtractOnly)
+ ShowInfo("Files were extracted to: %s", szExtractPath);
+ else
{
- /*
- * Copy ".custom" directory into temp directory so that the extracted .MSI
- * file(s) can use it.
- */
- char *pszPathCustomDir = RTPathJoinA(szPathExe, ".custom");
- pszPathCustomDir = RTPathChangeToDosSlashes(pszPathCustomDir, true /* Force conversion. */);
- if (pszPathCustomDir && RTDirExists(pszPathCustomDir))
+ rcExit = CopyCustomDir(szExtractPath);
+#ifdef VBOX_WITH_CODE_SIGNING
+ if (rcExit == RTEXITCODE_SUCCESS && fEnableSilentCert && g_fSilent)
+ rcExit = InstallCertificate();
+#endif
+ unsigned iPackage = 0;
+ while (iPackage < pHeader->byCntPkgs && rcExit == RTEXITCODE_SUCCESS)
{
- vrc = CopyDir(szExtractPath, pszPathCustomDir);
- if (RT_FAILURE(vrc)) /* Don't fail if it's missing! */
- vrc = VINF_SUCCESS;
-
- RTStrFree(pszPathCustomDir);
+ rcExit = ProcessPackage(iPackage, szExtractPath, szMSIArgs, fEnableLogging);
+ iPackage++;
}
- /* Do actions on files. */
- for (BYTE k = 0; k < pHeader->byCntPkgs; k++)
- {
- PVBOXSTUBPKG pPackage = NULL;
- DWORD cbPackage = 0;
- char szHeaderName[RTPATH_MAX] = {0};
-
- hr = StringCchPrintf(szHeaderName, RTPATH_MAX, "HDR_%02d", k);
- vrc = ReadData(NULL, szHeaderName, (LPVOID*)&pPackage, &cbPackage);
- AssertMsgRCBreak(vrc, ("Package not found!\n"));
-
- if (PackageIsNeeded(pPackage))
- {
- char *pszTempFile = NULL;
-
- vrc = GetTempFileAlloc(szExtractPath, pPackage->szFileName, &pszTempFile);
- AssertMsgRCBreak(vrc, ("Could not create name for temporary action file!\n"));
-
- /* Handle MSI files. */
- if (RTStrICmp(RTPathExt(pszTempFile), ".msi") == 0)
- {
- /* Set UI level. */
- INSTALLUILEVEL UILevel = MsiSetInternalUI( fSilent
- ? INSTALLUILEVEL_NONE
- : INSTALLUILEVEL_FULL,
- NULL);
- AssertMsgBreak(UILevel != INSTALLUILEVEL_NOCHANGE, ("Could not set installer UI level!\n"));
-
- /* Enable logging? */
- if (fEnableLogging)
- {
- char *pszLog = RTPathJoinA(szExtractPath, "VBoxInstallLog.txt");
- /* Convert slahes; this is necessary for MSI routines! */
- pszLog = RTPathChangeToDosSlashes(pszLog, true /* Force conversion. */);
- AssertMsgBreak(pszLog, ("Could not construct path for log file!\n"));
- UINT uLogLevel = MsiEnableLog(INSTALLLOGMODE_VERBOSE,
- pszLog, INSTALLLOGATTRIBUTES_FLUSHEACHLINE);
- RTStrFree(pszLog);
- AssertMsgBreak(uLogLevel == ERROR_SUCCESS, ("Could not set installer logging level!\n"));
- }
-
- /* 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. */
-
- UINT uStatus = ::MsiInstallProductA(pszTempFile, szMSIArgs);
- if ( (uStatus != ERROR_SUCCESS)
- && (uStatus != ERROR_SUCCESS_REBOOT_REQUIRED)
- && (uStatus != ERROR_INSTALL_USEREXIT))
- {
- if (!fSilent)
- {
- switch (uStatus)
- {
- 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:
- {
- 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 = LoadLibraryEx(TEXT("netmsg.dll"),
- NULL,
- LOAD_LIBRARY_AS_DATAFILE);
- if (hModule != NULL)
- dwFormatFlags |= FORMAT_MESSAGE_FROM_HMODULE;
- }
-
- DWORD dwBufferLength;
- LPSTR szMessageBuffer;
- if (dwBufferLength = FormatMessageA(dwFormatFlags,
- hModule, /* If NULL, load system stuff. */
- uStatus,
- MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
- (LPSTR)&szMessageBuffer,
- 0,
- NULL))
- {
- ShowError("Installation failed! Error: %s", szMessageBuffer);
- LocalFree(szMessageBuffer);
- }
- else /* If text lookup failed, show at least the error number. */
- ShowError("Installation failed! Error: %u", uStatus);
- if (hModule)
- FreeLibrary(hModule);
- break;
- }
- }
- }
-
- vrc = VERR_NO_CHANGE; /* No change done to the system. */
- }
- }
- RTStrFree(pszTempFile);
- } /* Package needed? */
- } /* For all packages */
- }
-
- /* Clean up (only on success - prevent deleting the log). */
- if ( !fExtractOnly
- && RT_SUCCESS(vrc))
- {
- for (int i=0; i<5; i++)
- {
- vrc = RTDirRemoveRecursive(szExtractPath, 0 /*fFlags*/);
- if (RT_SUCCESS(vrc))
- break;
- RTThreadSleep(3000 /* Wait 3 seconds.*/);
- }
+ /* Don't fail if cleanup fail. At least for now. */
+ CleanUp(pHeader->byCntPkgs, !fEnableLogging && fCreatedExtractDir ? szExtractPath : NULL);
}
+ }
- } while (0);
-
- if (RT_SUCCESS(vrc))
+ /* Free any left behind cleanup records (not strictly needed). */
+ PSTUBCLEANUPREC pCur, pNext;
+ RTListForEachSafe(&g_TmpFiles, pCur, pNext, STUBCLEANUPREC, ListEntry)
{
- if ( fExtractOnly
- && !fSilent)
- {
- ShowInfo("Files were extracted to: %s", szExtractPath);
- }
-
- /** @todo Add more post installation stuff here if required. */
+ RTListNodeRemove(&pCur->ListEntry);
+ RTMemFree(pCur);
}
- /* Release instance mutex. */
+ /*
+ * Release instance mutex.
+ */
if (hMutexAppRunning != NULL)
{
CloseHandle(hMutexAppRunning);
hMutexAppRunning = NULL;
}
- /* Set final exit (return) code (error level). */
- if (RT_FAILURE(vrc))
- {
- switch(vrc)
- {
- case VERR_NO_CHANGE:
- default:
- vrc = 1;
- }
- }
- else /* Always set to (VINF_SUCCESS), even if we got something else (like a VWRN etc). */
- vrc = VINF_SUCCESS;
- return vrc;
+ return rcExit;
}