VirtualBox

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

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

VBoxStub: Some renaming, added error message for missing path on extraction, used RTPATH_MAX defines.

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

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