VBoxStub.cpp revision aa929735951456d7d4f09261e80d9e461724077b
/* $Id$ */
/** @file
* VBoxStub - VirtualBox's Windows installer stub.
*/
/*
* Copyright (C) 2010-2014 Oracle Corporation
*
* This file is part of VirtualBox Open Source Edition (OSE), as
* available from http://www.virtualbox.org. This file is free software;
* 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 *
*******************************************************************************/
#include <Windows.h>
#include <commctrl.h>
#include <lmerr.h>
#include <msiquery.h>
#include <objbase.h>
#include <shlobj.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <strsafe.h>
#include <iprt/initterm.h>
#include "VBoxStub.h"
#include "../StubBld/VBoxStubBld.h"
#include "resource.h"
#ifdef VBOX_WITH_CODE_SIGNING
# include "VBoxStubCertUtil.h"
# include "VBoxStubPublicCert.h"
#endif
/*******************************************************************************
* Defined Constants And Macros *
*******************************************************************************/
/*******************************************************************************
* Structures and Typedefs *
*******************************************************************************/
/**
* Cleanup record.
*/
typedef struct STUBCLEANUPREC
{
/** List entry. */
/** True if file, false if directory. */
bool fFile;
/** The path to the file or directory to clean up. */
char szPath[1];
/** 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;
/**
* 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.
*
*/
{
char *pszMsg;
{
if (g_fSilent)
else
{
if (RT_SUCCESS(rc))
{
}
else
}
}
else /* Should never happen! */
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.
*
*/
{
char *pszMsg;
if (rc >= 0)
{
if (g_fSilent)
else
{
if (RT_SUCCESS(rc))
{
}
else
}
}
else /* Should never happen! */
}
/**
* 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.
*/
{
/* Find our resource. */
/* Get resource size. */
if (pdwSize)
/* Get pointer to resource. */
/* Lock resource. */
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.
*/
{
char szHeaderName[32];
if (RT_FAILURE(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)
{
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)
{
int rc;
do
{
/* Read the data of the built-in resource. */
DWORD dwDataSize = 0;
/* Create new (and replace an old) file. */
bCreatedFile = TRUE;
/* Write contents to new file. */
} while (0);
if (RTFileIsValid(fh))
if (RT_FAILURE(rc))
{
if (bCreatedFile)
}
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.
*
*/
const char *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.
*
*/
{
fnIsWow64Process = (LPFN_ISWOW64PROCESS)GetProcAddress(GetModuleHandle(TEXT("kernel32")), "IsWow64Process");
if (NULL != fnIsWow64Process)
{
{
/* Error in retrieving process type - assume that we're running on 32bit. */
return FALSE;
}
}
return bIsWow64;
}
/**
* 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.
*
*/
{
return true;
}
/**
* 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.
*/
{
PSTUBCLEANUPREC pRec = (PSTUBCLEANUPREC)RTMemAlloc(RT_OFFSETOF(STUBCLEANUPREC, szPath[cchPath + 1]));
if (!pRec)
{
ShowError("Out of memory!");
return false;
}
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.
*/
{
for (int i = 0; i < 5; i++)
{
int rc;
bool fFinalTry = i == 4;
{
else
{
rc = VINF_SUCCESS;
}
rc = VINF_SUCCESS;
if (RT_SUCCESS(rc))
{
}
else if (fFinalTry)
{
else
}
}
{
if (!pszPkgDir)
return;
return;
}
/* Delay a little and try again. */
}
}
/**
* 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.
*/
{
int rc;
/*
* Set UI level.
*/
return ShowError("Internal error: MsiSetInternalUI failed.");
/*
* Enable logging?
*/
if (fLogging)
{
char szLogFile[RTPATH_MAX];
if (RT_SUCCESS(rc))
{
}
if (RT_FAILURE(rc))
return ShowError("Internal error: Filename path too long.");
if (RT_FAILURE(rc))
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.
*/
/*
* Convert both strings to UTF-16 and start the installation.
*/
if (RT_FAILURE(rc))
if (RT_FAILURE(rc))
{
}
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;
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;
ShowError("This installation package is not supported on this platform.");
break;
default:
{
/*
* Try get windows to format the message.
*/
{
NULL,
}
hModule, /* If NULL, load system stuff. */
0,
NULL) > 0)
{
}
else /* If text lookup failed, show at least the error number. */
if (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.
*/
return RTEXITCODE_FAILURE;
if (!PackageIsNeeded(pPackage))
return RTEXITCODE_SUCCESS;
/*
* Deal with the file based on it's extension.
*/
char szPkgFile[RTPATH_MAX];
if (RT_FAILURE(rc))
else
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)
{
"TrustedPublisher",
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.
*
* This is used by the MSI packages from the resource section.
*
* @returns Fully complained exit code.
* @param pszDstDir The destination directory.
*/
{
char szSrcDir[RTPATH_MAX];
if (RT_SUCCESS(rc))
if (RT_FAILURE(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.
*/
if (RT_FAILURE(rc))
if (RT_FAILURE(rc))
if (rc != 0) /* Not a Win32 status code! */
/*
* 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...
*/
if (!pszDstSubDir)
return ShowError("Out of memory!");
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))
{
if (RT_FAILURE(rc))
*pfCreatedExtractDir = true;
}
/*
* Extract files.
*/
for (unsigned k = 0; k < cPackages; k++)
{
if (!pPackage)
return RTEXITCODE_FAILURE; /* Done complaining already. */
{
char szDstFile[RTPATH_MAX];
if (RT_FAILURE(rc))
if (RT_FAILURE(rc))
{
return RTEXITCODE_FAILURE;
}
}
}
return RTEXITCODE_SUCCESS;
}
char *lpCmdLine,
int nCmdShow)
{
/* 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! */
if ( hMutexAppRunning != NULL
&& GetLastError() == ERROR_ALREADY_EXISTS)
{
/* Close the mutex for this application instance. */
return RTEXITCODE_FAILURE;
}
/* Init IPRT. */
if (RT_FAILURE(vrc))
return RTMsgInitFailure(vrc);
/*
* 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[4096] = {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). */
#ifdef VBOX_WITH_CODE_SIGNING
#endif
};
/* Parse the parameters. */
int ch;
{
switch (ch)
{
case 'f': /* Force re-installation. */
if (szMSIArgs[0])
if (RT_SUCCESS(vrc))
"REINSTALLMODE=vomus REINSTALL=ALL");
if (RT_FAILURE(vrc))
return 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':
if (RT_FAILURE(vrc))
return ShowError("Extraction path is too long.");
break;
case 'm':
if (szMSIArgs[0])
if (RT_SUCCESS(vrc))
if (RT_FAILURE(vrc))
return ShowError("MSI parameters are too long.");
break;
case 'V':
ShowInfo("Version: %d.%d.%d.%d",
return VINF_SUCCESS;
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",
return VINF_SUCCESS;
default:
if (g_fSilent)
ShowError("Unknown option \"%s\"!\n"
"Please refer to the command line help by specifying \"/?\"\n"
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;
}
}
/*
* Determine the extration path if not given by the user, and gather some
* other bits we'll be needing later.
*/
if (szExtractPath[0] == '\0')
{
if (RT_SUCCESS(vrc))
if (RT_FAILURE(vrc))
}
else
{
/** @todo should check if there is a .custom subdirectory there or not. */
}
/* Read our manifest. */
if (RT_FAILURE(vrc))
/** @todo If we could, we should validate the header. Only the magic isn't
* commonly defined, nor the version number... */
/*
* 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)
else
{
#ifdef VBOX_WITH_CODE_SIGNING
rcExit = InstallCertificate();
#endif
unsigned iPackage = 0;
{
iPackage++;
}
/* Don't fail if cleanup fail. At least for now. */
}
}
/* Free any left behind cleanup records (not strictly needed). */
{
}
/*
* Release instance mutex.
*/
if (hMutexAppRunning != NULL)
{
}
return rcExit;
}