VirtualBox

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

Last change on this file since 33462 was 33462, checked in by vboxsync, 14 years ago

VBoxStub.cpp: assertion macro confusion, RTStrAPrintf -> RTPathAppend.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 21.0 KB
Line 
1/* $Id: VBoxStub.cpp 33462 2010-10-26 12:15:19Z vboxsync $ */
2/** @file
3 * VBoxStub - VirtualBox's Windows installer stub.
4 */
5
6/*
7 * Copyright (C) 2010 Oracle Corporation
8 *
9 * This file is part of VirtualBox Open Source Edition (OSE), as
10 * available from http://www.virtualbox.org. This file is free software;
11 * you can redistribute it and/or modify it under the terms of the GNU
12 * General Public License (GPL) as published by the Free Software
13 * Foundation, in version 2 as it comes in the "COPYING" file of the
14 * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
15 * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
16 */
17
18/*******************************************************************************
19* Header Files *
20*******************************************************************************/
21#include <windows.h>
22#include <msiquery.h>
23#include <objbase.h>
24#include <shlobj.h>
25#include <stdlib.h>
26#include <stdio.h>
27#include <string.h>
28#include <strsafe.h>
29
30#include <VBox/version.h>
31
32#include <iprt/assert.h>
33#include <iprt/dir.h>
34#include <iprt/file.h>
35#include <iprt/initterm.h>
36#include <iprt/mem.h>
37#include <iprt/path.h>
38#include <iprt/string.h>
39#include <iprt/thread.h>
40
41#include "VBoxStub.h"
42#include "../StubBld/VBoxStubBld.h"
43#include "resource.h"
44
45#ifndef _UNICODE
46#define _UNICODE
47#endif
48
49
50/**
51 * Shows a message box with a printf() style formatted string.
52 *
53 * @returns Message box result (IDOK, IDCANCEL, ...).
54 *
55 * @param uType Type of the message box (see MSDN).
56 * @param pszFmt Printf-style format string to show in the message box body.
57 *
58 */
59static int ShowInfo(const char *pszFmt, ...)
60{
61 char *pszMsg;
62 va_list va;
63
64 va_start(va, pszFmt);
65 RTStrAPrintfV(&pszMsg, pszFmt, va);
66 va_end(va);
67
68 int rc;
69 if (pszMsg)
70 rc = MessageBox(GetDesktopWindow(), pszMsg, VBOX_STUB_TITLE, MB_ICONINFORMATION);
71 else
72 rc = MessageBox(GetDesktopWindow(), pszFmt, VBOX_STUB_TITLE, MB_ICONINFORMATION);
73 RTStrFree(pszMsg);
74 return rc;
75}
76
77
78/**
79 * Shows an error message box with a printf() style formatted string.
80 *
81 * @returns Message box result (IDOK, IDCANCEL, ...).
82 *
83 * @param pszFmt Printf-style format string to show in the message box body.
84 *
85 */
86static int ShowError(const char *pszFmt, ...)
87{
88 char *pszMsg;
89 va_list va;
90
91 va_start(va, pszFmt);
92 RTStrAPrintfV(&pszMsg, pszFmt, va);
93 va_end(va);
94
95 int rc;
96 if (pszMsg)
97 rc = MessageBox(GetDesktopWindow(), pszMsg, VBOX_STUB_TITLE, MB_ICONERROR);
98 else
99 rc = MessageBox(GetDesktopWindow(), pszFmt, VBOX_STUB_TITLE, MB_ICONERROR);
100 RTStrFree(pszMsg);
101 return rc;
102}
103
104
105/**
106 * Reads data from a built-in resource.
107 *
108 * @returns iprt status code.
109 *
110 * @param hInst Instance to read the data from.
111 * @param pszDataName Name of resource to read.
112 * @param ppvResource Pointer to buffer which holds the read resource data.
113 * @param pdwSize Pointer which holds the read data size.
114 *
115 */
116static int ReadData(HINSTANCE hInst,
117 const char *pszDataName,
118 PVOID *ppvResource,
119 DWORD *pdwSize)
120{
121 do
122 {
123 AssertMsgBreak(pszDataName, ("Resource name is empty!\n"));
124
125 /* Find our resource. */
126 HRSRC hRsrc = FindResourceEx(hInst, RT_RCDATA, pszDataName, MAKELANGID(LANG_NEUTRAL, SUBLANG_NEUTRAL));
127 AssertMsgBreak(hRsrc, ("Could not find resource!\n"));
128
129 /* Get resource size. */
130 *pdwSize = SizeofResource(hInst, hRsrc);
131 AssertMsgBreak((*pdwSize > 0, "Size of resource is invalid!\n"));
132
133 /* Get pointer to resource. */
134 HGLOBAL hData = LoadResource(hInst, hRsrc);
135 AssertMsgBreak(hData, ("Could not load resource!\n"));
136
137 /* Lock resource. */
138 *ppvResource = LockResource(hData);
139 AssertMsgBreak(*ppvResource, ("Could not lock resource!\n"));
140 return VINF_SUCCESS;
141
142 } while (0);
143
144 return VERR_IO_GEN_FAILURE;
145}
146
147
148/**
149 * Constructs a full temporary file path from the given parameters.
150 *
151 * @returns iprt status code.
152 *
153 * @param pszTempPath The pure path to use for construction.
154 * @param pszTargetFileName The pure file name to use for construction.
155 * @param ppszTempFile Pointer to the constructed string. Must be freed
156 * using RTStrFree().
157 */
158static int GetTempFileAlloc(const char *pszTempPath,
159 const char *pszTargetFileName,
160 char **ppszTempFile)
161{
162 if (RTStrAPrintf(ppszTempFile, "%s\\%s", pszTempPath, pszTargetFileName) >= 0)
163 return VINF_SUCCESS;
164 return VERR_NO_STR_MEMORY;
165}
166
167
168/**
169 * Extracts a built-in resource to disk.
170 *
171 * @returns iprt status code.
172 *
173 * @param pszResourceName The resource name to extract.
174 * @param pszTempFile The full file path + name to extract the resource to.
175 *
176 */
177static int ExtractFile(const char *pszResourceName,
178 const char *pszTempFile)
179{
180 int rc;
181 RTFILE fh;
182 BOOL bCreatedFile = FALSE;
183
184 do
185 {
186 AssertMsgBreak(pszResourceName, ("Resource pointer invalid!\n"));
187 AssertMsgBreak(pszTempFile, ("Temp file pointer invalid!"));
188
189 /* Read the data of the built-in resource. */
190 PVOID pvData = NULL;
191 DWORD dwDataSize = 0;
192 rc = ReadData(NULL, pszResourceName, &pvData, &dwDataSize);
193 AssertMsgRCBreak(rc, ("Could not read resource data!\n"));
194
195 /* Create new (and replace an old) file. */
196 rc = RTFileOpen(&fh, pszTempFile,
197 RTFILE_O_CREATE_REPLACE
198 | RTFILE_O_WRITE
199 | RTFILE_O_DENY_NOT_DELETE
200 | RTFILE_O_DENY_WRITE);
201 AssertMsgRCBreak(rc, ("Could not open file for writing!\n"));
202 bCreatedFile = TRUE;
203
204 /* Write contents to new file. */
205 size_t cbWritten = 0;
206 rc = RTFileWrite(fh, pvData, dwDataSize, &cbWritten);
207 AssertMsgRCBreak(rc, ("Could not open file for writing!\n"));
208 AssertMsgBreak(dwDataSize == cbWritten, ("File was not extracted completely! Disk full?\n"));
209
210 } while (0);
211
212 if (RTFileIsValid(fh))
213 RTFileClose(fh);
214
215 if (RT_FAILURE(rc))
216 {
217 if (bCreatedFile)
218 RTFileDelete(pszTempFile);
219 }
220 return rc;
221}
222
223
224/**
225 * Extracts a built-in resource to disk.
226 *
227 * @returns iprt status code.
228 *
229 * @param pPackage Pointer to a VBOXSTUBPKG struct that contains the resource.
230 * @param pszTempFile The full file path + name to extract the resource to.
231 *
232 */
233static int Extract(const PVBOXSTUBPKG pPackage,
234 const char *pszTempFile)
235{
236 return ExtractFile(pPackage->szResourceName,
237 pszTempFile);
238}
239
240
241/**
242 * Detects whether we're running on a 32- or 64-bit platform and returns the result.
243 *
244 * @returns TRUE if we're running on a 64-bit OS, FALSE if not.
245 *
246 */
247static BOOL IsWow64(void)
248{
249 BOOL bIsWow64 = TRUE;
250 fnIsWow64Process = (LPFN_ISWOW64PROCESS)GetProcAddress(GetModuleHandle(TEXT("kernel32")), "IsWow64Process");
251 if (NULL != fnIsWow64Process)
252 {
253 if (!fnIsWow64Process(GetCurrentProcess(), &bIsWow64))
254 {
255 /* Error in retrieving process type - assume that we're running on 32bit. */
256 return FALSE;
257 }
258 }
259 return bIsWow64;
260}
261
262
263/**
264 * Decides whether we need a specified package to handle or not.
265 *
266 * @returns TRUE if we need to handle the specified package, FALSE if not.
267 *
268 * @param pPackage Pointer to a VBOXSTUBPKG struct that contains the resource.
269 *
270 */
271static BOOL PackageIsNeeded(PVBOXSTUBPKG pPackage)
272{
273 BOOL bIsWow64 = IsWow64();
274 if ((bIsWow64 && pPackage->byArch == VBOXSTUBPKGARCH_AMD64)) /* 64bit Windows. */
275 {
276 return TRUE;
277 }
278 else if ((!bIsWow64 && pPackage->byArch == VBOXSTUBPKGARCH_X86)) /* 32bit. */
279 {
280 return TRUE;
281 }
282 else if (pPackage->byArch == VBOXSTUBPKGARCH_ALL)
283 {
284 return TRUE;
285 }
286 return FALSE;
287}
288
289
290/**
291 * Recursivly copies a directory to another location.
292 *
293 * @returns iprt status code.
294 *
295 * @param pszDestDir Location to copy the source directory to.
296 * @param pszSourceDir The source directory to copy.
297 *
298 */
299int CopyDir(const char *pszDestDir, const char *pszSourceDir)
300{
301 char szDest[_MAX_PATH + 1];
302 char szSource[_MAX_PATH + 1];
303
304 AssertStmt(pszDestDir, "Destination directory invalid!");
305 AssertStmt(pszSourceDir, "Source directory invalid!");
306
307 SHFILEOPSTRUCT s = {0};
308 if ( RTStrPrintf(szDest, _MAX_PATH, "%s%c", pszDestDir, '\0') > 0
309 && RTStrPrintf(szSource, _MAX_PATH, "%s%c", pszSourceDir, '\0') > 0)
310 {
311 s.hwnd = NULL;
312 s.wFunc = FO_COPY;
313 s.pTo = szDest;
314 s.pFrom = szSource;
315 s.fFlags = FOF_SILENT |
316 FOF_NOCONFIRMATION |
317 FOF_NOCONFIRMMKDIR |
318 FOF_NOERRORUI;
319 }
320 return RTErrConvertFromWin32(SHFileOperation(&s));
321}
322
323
324int WINAPI WinMain(HINSTANCE hInstance,
325 HINSTANCE hPrevInstance,
326 char *lpCmdLine,
327 int nCmdShow)
328{
329 char **pArgV = __argv;
330 int iArgC = __argc;
331
332 /* Check if we're already running and jump out if so. */
333 /* Do not use a global namespace ("Global\\") for mutex name here, will blow up NT4 compatibility! */
334 HANDLE hMutexAppRunning = CreateMutex (NULL, FALSE, "VBoxStubInstaller");
335 if ( (hMutexAppRunning != NULL)
336 && (GetLastError() == ERROR_ALREADY_EXISTS))
337 {
338 /* Close the mutex for this application instance. */
339 CloseHandle(hMutexAppRunning);
340 hMutexAppRunning = NULL;
341 return 1;
342 }
343
344 /* Init IPRT. */
345 int vrc = RTR3Init();
346 if (RT_FAILURE(vrc))
347 return vrc;
348
349 BOOL fExtractOnly = FALSE;
350 BOOL fSilent = FALSE;
351 BOOL fEnableLogging = FALSE;
352 BOOL bExit = FALSE;
353
354 /* Temp variables for arguments. */
355 char szExtractPath[RTPATH_MAX] = {0};
356 char szMSIArgs[RTPATH_MAX] = {0};
357
358 /* Process arguments. */
359 for (int i = 0; i < iArgC; i++)
360 {
361 if ( (0 == RTStrICmp(pArgV[i], "-x"))
362 || (0 == RTStrICmp(pArgV[i], "-extract"))
363 || (0 == RTStrICmp(pArgV[i], "/extract")))
364 {
365 fExtractOnly = TRUE;
366 }
367
368 else if ( (0 == RTStrICmp(pArgV[i], "-s"))
369 || (0 == RTStrICmp(pArgV[i], "-silent"))
370 || (0 == RTStrICmp(pArgV[i], "/silent")))
371 {
372 fSilent = TRUE;
373 }
374
375 else if ( (0 == RTStrICmp(pArgV[i], "-l"))
376 || (0 == RTStrICmp(pArgV[i], "-logging"))
377 || (0 == RTStrICmp(pArgV[i], "/logging")))
378 {
379 fEnableLogging = TRUE;
380 }
381
382 else if (( (0 == RTStrICmp(pArgV[i], "-p"))
383 || (0 == RTStrICmp(pArgV[i], "-path"))
384 || (0 == RTStrICmp(pArgV[i], "/path")))
385 && (iArgC > i))
386 {
387 vrc = ::StringCbCat(szExtractPath, sizeof(szExtractPath), pArgV[i+1]);
388 i++; /* Avoid the specify path from being parsed */
389 }
390
391 else if (( (0 == RTStrICmp(pArgV[i], "-msiparams"))
392 || (0 == RTStrICmp(pArgV[i], "/msiparams")))
393 && (iArgC > i))
394 {
395 for (int a=i+1; a<iArgC; a++)
396 {
397 if (a > i+1) /* Insert a space. */
398 vrc = ::StringCbCat(szMSIArgs, sizeof(szMSIArgs), " ");
399
400 vrc = ::StringCbCat(szMSIArgs, sizeof(szMSIArgs), pArgV[a]);
401 }
402 }
403
404 else if ( (0 == RTStrICmp(pArgV[i], "-v"))
405 || (0 == RTStrICmp(pArgV[i], "-version"))
406 || (0 == RTStrICmp(pArgV[i], "/version")))
407 {
408 ShowInfo("Version: %d.%d.%d.%d",
409 VBOX_VERSION_MAJOR, VBOX_VERSION_MINOR, VBOX_VERSION_BUILD, VBOX_SVN_REV);
410 bExit = TRUE;
411 }
412
413 else if ( (0 == RTStrICmp(pArgV[i], "-help"))
414 || (0 == RTStrICmp(pArgV[i], "/help"))
415 || (0 == RTStrICmp(pArgV[i], "/?")))
416 {
417 ShowInfo("-- %s v%d.%d.%d.%d --\n"
418 "Command Line Parameters:\n\n"
419 "-extract | -x - Extract file contents to temporary directory\n"
420 "-silent | -s - Enables silent mode installation\n"
421 "-path | -p - Sets the path of the extraction directory\n"
422 "-help | /? - Print this help and exit\n"
423 "-msiparams <parameters> - Specifies extra parameters for the MSI installers\n"
424 "-logging | -l - Enables installer logging\n"
425 "-version | -v - Print version number and exit\n\n"
426 "Examples:\n"
427 "%s -msiparams INSTALLDIR=C:\\VBox\n"
428 "%s -extract -path C:\\VBox\n",
429 VBOX_STUB_TITLE, VBOX_VERSION_MAJOR, VBOX_VERSION_MINOR, VBOX_VERSION_BUILD, VBOX_SVN_REV,
430 pArgV[0], pArgV[0]);
431 bExit = TRUE;
432 }
433 else
434 {
435 if (i > 0)
436 {
437 ShowInfo("Unknown option \"%s\"!\n"
438 "Please refer to the command line help by specifying \"/?\"\n"
439 "to get more information.", pArgV[i]);
440 bExit = TRUE;
441 }
442 }
443 }
444
445 if (bExit)
446 return 0;
447
448 HRESULT hr = S_OK;
449
450 do /* break loop */
451 {
452 /* Get/create our temp path (only if not already set). */
453 if (szExtractPath[0] == '\0')
454 {
455 vrc = RTPathTemp(szExtractPath, sizeof(szExtractPath));
456 AssertMsgRCBreak(vrc, ("Could not retrieve temp directory!\n"));
457
458 vrc = RTPathAppend(szExtractPath, sizeof(szExtractPath), "VirtualBox");
459 AssertMsgRCBreak(vrc, ("Could not construct temp directory!\n"));
460 }
461 if (!RTDirExists(szExtractPath))
462 {
463 vrc = RTDirCreate(szExtractPath, 0700);
464 AssertMsgRCBreak(vrc, ("Could not create temp directory!\n"));
465 }
466
467 /* Get our executable path */
468 char szPathExe[_MAX_PATH];
469 vrc = RTPathExecDir(szPathExe, sizeof(szPathExe));
470 /** @todo error checking */
471
472 /* Read our manifest. */
473 PVBOXSTUBPKGHEADER pHeader = NULL;
474 DWORD cbHeader = 0;
475 vrc = ReadData(NULL, "MANIFEST", (LPVOID*)&pHeader, &cbHeader);
476 AssertMsgRCBreak(vrc, ("Manifest not found!\n"));
477
478 /* Extract files. */
479 for (BYTE k = 0; k < pHeader->byCntPkgs; k++)
480 {
481 PVBOXSTUBPKG pPackage = NULL;
482 DWORD cbPackage = 0;
483 char szHeaderName[_MAX_PATH] = {0};
484
485 hr = ::StringCchPrintf(szHeaderName, _MAX_PATH, "HDR_%02d", k);
486 vrc = ReadData(NULL, szHeaderName, (LPVOID*)&pPackage, &cbPackage);
487 AssertMsgRCBreak(vrc, ("Header not found!\n")); /** @todo include header name, how? */
488
489 if (PackageIsNeeded(pPackage) || fExtractOnly)
490 {
491 char *pszTempFile = NULL;
492 vrc = GetTempFileAlloc(szExtractPath, pPackage->szFileName, &pszTempFile);
493 AssertMsgRCBreak(vrc, ("Could not create name for temporary extracted file!\n"));
494 vrc = Extract(pPackage, pszTempFile);
495 AssertMsgRCBreak(vrc, ("Could not extract file!\n"));
496 RTStrFree(pszTempFile);
497 }
498 }
499
500 if (FALSE == fExtractOnly && !RT_FAILURE(vrc))
501 {
502 /*
503 * Copy ".custom" directory into temp directory so that the extracted .MSI
504 * file(s) can use it.
505 */
506 char *pszPathCustomDir = RTPathJoinA(szPathExe, ".custom");
507 if (pszPathCustomDir && RTDirExists(pszPathCustomDir))
508 {
509 vrc = CopyDir(szExtractPath, pszPathCustomDir);
510 if (RT_FAILURE(vrc)) /* Don't fail if it's missing! */
511 vrc = VINF_SUCCESS;
512
513 RTStrFree(pszPathCustomDir);
514 }
515
516 /* Do actions on files. */
517 for (BYTE k = 0; k < pHeader->byCntPkgs; k++)
518 {
519 PVBOXSTUBPKG pPackage = NULL;
520 DWORD cbPackage = 0;
521 char szHeaderName[_MAX_PATH] = {0};
522
523 hr = StringCchPrintf(szHeaderName, _MAX_PATH, "HDR_%02d", k);
524 vrc = ReadData(NULL, szHeaderName, (LPVOID*)&pPackage, &cbPackage);
525 AssertMsgRCBreak(vrc, ("Package not found!\n"));
526
527 if (PackageIsNeeded(pPackage))
528 {
529 char *pszTempFile = NULL;
530
531 vrc = GetTempFileAlloc(szExtractPath, pPackage->szFileName, &pszTempFile);
532 AssertMsgRCBreak(vrc, ("Could not create name for temporary action file!\n"));
533
534 /* Handle MSI files. */
535 if (RTStrICmp(RTPathExt(pszTempFile), ".msi") == 0)
536 {
537 /* Set UI level. */
538 INSTALLUILEVEL UILevel = MsiSetInternalUI( fSilent
539 ? INSTALLUILEVEL_NONE
540 : INSTALLUILEVEL_FULL,
541 NULL);
542 AssertMsgBreak(UILevel != INSTALLUILEVEL_NOCHANGE, ("Could not set installer UI level!\n"));
543
544 /* Enable logging? */
545 if (fEnableLogging)
546 {
547 char *pszLog = RTPathJoinA(szExtractPath, "VBoxInstallLog.txt");
548 AssertMsgRCBreak(vrc, ("Could not convert MSI log string to current codepage!\n"));
549 UINT uLogLevel = MsiEnableLog(INSTALLLOGMODE_VERBOSE,
550 pszLog, INSTALLLOGATTRIBUTES_FLUSHEACHLINE);
551 RTStrFree(pszLog);
552 AssertMsgBreak(uLogLevel == ERROR_SUCCESS, ("Could not set installer logging level!\n"));
553 }
554
555 UINT uStatus = ::MsiInstallProductA(pszTempFile, szMSIArgs);
556 if ( (uStatus != ERROR_SUCCESS)
557 && (uStatus != ERROR_SUCCESS_REBOOT_REQUIRED)
558 && (uStatus != ERROR_INSTALL_USEREXIT))
559 {
560 if (!fSilent)
561 {
562 switch (uStatus)
563 {
564 case ERROR_INSTALL_PACKAGE_VERSION:
565
566 ShowError("This installation package cannot be installed by the Windows Installer service.\n"
567 "You must install a Windows service pack that contains a newer version of the Windows Installer service.");
568 break;
569
570 case ERROR_INSTALL_PLATFORM_UNSUPPORTED:
571
572 ShowError("This installation package is not supported on this platform.\n");
573 break;
574
575 default:
576
577 /** @todo Use FormatMessage here! */
578 ShowError("Installation failed! ERROR: %u", uStatus);
579 break;
580 }
581 }
582
583 vrc = VERR_NO_CHANGE; /* No change done to the system. */
584 }
585 }
586 RTStrFree(pszTempFile);
587 } /* Package needed? */
588 } /* For all packages */
589 }
590
591 /* Clean up (only on success - prevent deleting the log). */
592 if ( !fExtractOnly
593 && RT_SUCCESS(vrc))
594 {
595 for (int i=0; i<5; i++)
596 {
597 vrc = RTDirRemoveRecursive(szExtractPath, 0 /*fFlags*/);
598 if (RT_SUCCESS(vrc))
599 break;
600 RTThreadSleep(3000 /* Wait 3 seconds.*/);
601 }
602 }
603
604 } while (0);
605
606 if (RT_SUCCESS(vrc))
607 {
608 if ( fExtractOnly
609 && !fSilent)
610 {
611 ShowInfo("Files were extracted to: %s", szExtractPath);
612 }
613
614 /** @todo Add more post installation stuff here if required. */
615 }
616
617 /* Release instance mutex. */
618 if (hMutexAppRunning != NULL)
619 {
620 CloseHandle(hMutexAppRunning);
621 hMutexAppRunning = NULL;
622 }
623
624 /* Set final exit (return) code (error level). */
625 if (RT_FAILURE(vrc))
626 {
627 switch(vrc)
628 {
629 case VERR_NO_CHANGE:
630 default:
631 vrc = 1;
632 }
633 }
634 else /* Always set to (VINF_SUCCESS), even if we got something else (like a VWRN etc). */
635 vrc = VINF_SUCCESS;
636 return vrc;
637}
638
Note: See TracBrowser for help on using the repository browser.

© 2025 Oracle Support Privacy / Do Not Sell My Info Terms of Use Trademark Policy Automated Access Etiquette