From 4495c640c7b61e032b804c4fb6476b80c9e74c0f Mon Sep 17 00:00:00 2001 From: Martin Robinson Date: Mon, 22 Feb 2010 18:14:32 -0800 Subject: [PATCH] First pass at new update installer. [#291] --- installation/net_installer/win32/common.cpp | 346 ++++++++++ installation/net_installer/win32/common.h | 10 + .../net_installer/win32/progress_dialog.cpp | 94 +++ .../net_installer/win32/progress_dialog.h | 30 + .../net_installer/win32/update_installer.cpp | 201 ++++++ installer/win32/SConscript | 38 +- installer/win32/titanium_actions.cpp | 627 ++++-------------- 7 files changed, 848 insertions(+), 498 deletions(-) create mode 100644 installation/net_installer/win32/common.cpp create mode 100644 installation/net_installer/win32/common.h create mode 100755 installation/net_installer/win32/progress_dialog.cpp create mode 100755 installation/net_installer/win32/progress_dialog.h create mode 100644 installation/net_installer/win32/update_installer.cpp diff --git a/installation/net_installer/win32/common.cpp b/installation/net_installer/win32/common.cpp new file mode 100644 index 000000000..c6e61b108 --- /dev/null +++ b/installation/net_installer/win32/common.cpp @@ -0,0 +1,346 @@ +/** + * Appcelerator Titanium - licensed under the Apache Public License 2 + * see LICENSE in the root folder for details on the license. + * Copyright (c) 2009-2010 Appcelerator, Inc. All Rights Reserved. + */ +#include +#include +#include +#include +#include +#include + +using namespace KrollUtils; +using KrollUtils::Application; +using KrollUtils::SharedApplication; +using KrollUtils::KComponentType; +using std::wstring; +using std::string; + +// These functions must be defined in the installer implementation. +extern HWND GetInstallerHWND(); +extern bool Progress(SharedDependency dependency, int percent); + +void ShowError(const wstring& wmsg) +{ + MessageBoxW(GetDesktopWindow(), wmsg.c_str(), L"Installation Failed", + MB_OK | MB_SYSTEMMODAL | MB_ICONEXCLAMATION); +} + +void ShowError(const string& msg) +{ + wstring wmsg(UTF8ToWide(msg)); + ShowError(wmsg); +} + +static wstring GetTempFilePathForDependency(SharedDependency dependency) +{ + string filename; + switch (dependency->type) + { + case MODULE: + filename = "module-"; + break; + case RUNTIME: + filename = "runtime-"; + break; + case MOBILESDK: + filename = "mobilesdk-"; + break; + case APP_UPDATE: + filename = "appupdate-"; + break; + case SDK: + filename = "sdk-"; + break; + } + filename.append(dependency->name); + filename.append("-"); + filename.append(dependency->version); + filename.append(".zip"); + static string tempdir; + if (tempdir.empty()) + { + tempdir.assign(FileUtils::GetTempDirectory()); + FileUtils::CreateDirectory(tempdir); + } + + return UTF8ToWide(FileUtils::Join(tempdir.c_str(), filename.c_str(), 0)); +} + +static HINTERNET netHandle = 0; +static HINTERNET GetNetConnection() +{ + if (netHandle) + return netHandle; + + netHandle = InternetOpenW( + L"Mozilla/5.0 (compatible; Titanium_Downloader/0.1; Win32)", + INTERNET_OPEN_TYPE_PRECONFIG, 0, 0, 0); + + if (!netHandle) + { + string error(Win32Utils::QuickFormatMessage(GetLastError())); + error = string("Could not open Internet connection: ") + error; + ShowError(error); + } + + return netHandle; +} + +void ShutdownNetConnection() +{ + if (!netHandle) + return; + + InternetCloseHandle(netHandle); +} + +static void ShowLastDownloadEror() +{ + DWORD bufferSize = 1024, error; + wchar_t staticErrorBuffer[1024]; + wchar_t* errorBuffer = staticErrorBuffer; + BOOL success = InternetGetLastResponseInfo(&error, errorBuffer, &bufferSize); + + if (!success && GetLastError() == ERROR_INSUFFICIENT_BUFFER) + { + errorBuffer = new wchar_t[bufferSize]; + success = InternetGetLastResponseInfo(&error, errorBuffer, &bufferSize); + } + + std::wstring errorString(L"Download failed: Unknown error"); + if (success) + errorString = std::wstring(L"Download failed:") + errorBuffer; + + ShowError(::WideToSystem(errorString)); + + if (errorBuffer != staticErrorBuffer) + delete [] errorBuffer; +} + +bool DownloadDependency(SharedApplication app, SharedDependency dependency) +{ + HINTERNET hINet = GetNetConnection(); + if (!hINet) + { + ShowError("Could not establish an Internet connection."); + return false; + } + + // If ths URL is a file on the local file system, then this dependency + // is packaged with the application ala the SDK Installer. In that case + // just pretend that we downloaded successfully. InstallDependency will + // take care of unpacking it properly. + wstring url(UTF8ToWide(app->GetURLForDependency(dependency))); + if (FileUtils::IsFile(url)) + { + return true; + } + + wstring outFilename(GetTempFilePathForDependency(dependency)); + WCHAR szDecodedUrl[INTERNET_MAX_URL_LENGTH]; + DWORD cchDecodedUrl = INTERNET_MAX_URL_LENGTH; + WCHAR szDomainName[INTERNET_MAX_URL_LENGTH]; + + // parse the URL + HRESULT hr = CoInternetParseUrl(url.c_str(), PARSE_DECODE, + URL_ENCODING_NONE, szDecodedUrl, INTERNET_MAX_URL_LENGTH, + &cchDecodedUrl, 0); + if (hr != S_OK) + { + string error = Win32Utils::QuickFormatMessage(GetLastError()); + error = string("Could not decode URL: ") + error; + ShowError(error); + return false; + } + + // figure out the domain/hostname + hr = CoInternetParseUrl(szDecodedUrl, PARSE_DOMAIN, + 0, szDomainName, INTERNET_MAX_URL_LENGTH, &cchDecodedUrl, 0); + if (hr != S_OK) + { + string error = Win32Utils::QuickFormatMessage(GetLastError()); + error = string("Could not parse domain: ") + error; + ShowError(error); + return false; + } + + // start the HTTP fetch + HINTERNET hConnection = InternetConnectW(hINet, szDomainName, + 80, L" ", L" ", INTERNET_SERVICE_HTTP, 0, 0 ); + if (!hConnection) + { + string error = Win32Utils::QuickFormatMessage(GetLastError()); + error = string("Could not start connection: ") + error; + ShowError(error); + return false; + } + + wstring wurl(szDecodedUrl); + wstring path = wurl.substr(wurl.find(szDomainName)+wcslen(szDomainName)); + HINTERNET hRequest = HttpOpenRequestW(hConnection, L"GET", path.c_str(), + 0, 0, 0, + INTERNET_FLAG_IGNORE_CERT_CN_INVALID | // Disregard TLS certificate errors. + INTERNET_FLAG_IGNORE_CERT_DATE_INVALID | + INTERNET_FLAG_KEEP_CONNECTION | // Needed for NTLM authentication. + INTERNET_FLAG_NO_CACHE_WRITE | INTERNET_FLAG_RELOAD | // Always get the latest. + INTERNET_FLAG_NO_COOKIES, 0); + + resend: + HttpSendRequest(hRequest, 0, 0, 0, 0); + + DWORD dwErrorCode = hRequest ? ERROR_SUCCESS : GetLastError(); + if (InternetErrorDlg(GetInstallerHWND(), hRequest, dwErrorCode, + FLAGS_ERROR_UI_FILTER_FOR_ERRORS | + FLAGS_ERROR_UI_FLAGS_CHANGE_OPTIONS | + FLAGS_ERROR_UI_FLAGS_GENERATE_DATA, + 0) == ERROR_INTERNET_FORCE_RETRY) + goto resend; + + CHAR buffer[2048]; + DWORD bytesRead; + DWORD contentLength = 0; + DWORD statusCode = 0; + DWORD size = sizeof(contentLength); + BOOL success = HttpQueryInfo(hRequest, + HTTP_QUERY_STATUS_CODE | HTTP_QUERY_FLAG_NUMBER, + (LPDWORD) &statusCode, (LPDWORD) &size, 0); + if (!success || statusCode != 200) + { + string error = Win32Utils::QuickFormatMessage(GetLastError()); + if (success) + { + std::ostringstream str; + str << "Invalid HTTP Status Code (" << statusCode << ")"; + error = str.str(); + } + error = string("Could not query info: ") + error; + ShowError(error); + return false; + } + + success = HttpQueryInfo(hRequest, + HTTP_QUERY_CONTENT_LENGTH | HTTP_QUERY_FLAG_NUMBER, + (LPDWORD)&contentLength, (LPDWORD)&size, 0); + if (!success) + { + string error = Win32Utils::QuickFormatMessage(GetLastError()); + error = string("Could not determine content length: ") + error; + ShowError(error); + return false; + } + + // now stream the resulting HTTP into a file + HANDLE file = CreateFileW(outFilename.c_str(), GENERIC_WRITE, + 0, 0, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0); + if (file == INVALID_HANDLE_VALUE) + { + string error = Win32Utils::QuickFormatMessage(GetLastError()); + error = string("Could not open output file (") + WideToUTF8(outFilename) + + string("): ") + error; + ShowError(error); + return false; + } + + // Keep reading from InternetReadFile as long as it's successful and the number + // of bytes read is greater than zero. + bool showError = true; + DWORD total = 0; + while ((success = InternetReadFile(hRequest, buffer, 2047, &bytesRead)) && bytesRead > 0) + { + // Be sure to Write the entire buffer into to the file. + DWORD bytesWritten = 0; + while (bytesWritten < bytesRead) + { + if (!WriteFile(file, buffer + bytesWritten, + bytesRead - bytesWritten, &bytesWritten, 0)) + { + showError = success = false; + string error = Win32Utils::QuickFormatMessage(GetLastError()); + error = string("Could write data to output file (") + WideToUTF8(outFilename) + + string("): ") + error; + ShowError(error); + break; + } + } + + total += bytesRead; + printf("total: %i contentLength: %i\n", total, contentLength); + if (!Progress(dependency, (double)total/(double)contentLength*100)) + { + showError = success = false; + break; + } + } + + if (!success) + { + if (showError) + ShowLastDownloadEror(); + + CancelIo(file); + CloseHandle(file); + DeleteFileW(outFilename.c_str()); + } + else + { + CloseHandle(file); + } + + InternetCloseHandle(hRequest); + return success; +} + +static bool UnzipProgressCallback(char* message, int current, int total, void* data) +{ + SharedDependency* dep = (SharedDependency*) data; + int percent = total == 0 ? 0 : floor(((double)current/(double)total)*100); + return Progress(*dep, percent); +} + +bool InstallDependency(SharedApplication app, SharedDependency dependency) +{ + string destination(FileUtils::GetSystemRuntimeHomeDirectory()); + if (dependency->type == MODULE) + { + destination = FileUtils::Join(destination.c_str(), + "modules", OS_NAME, dependency->name.c_str(), + dependency->version.c_str(), 0); + } + else if (dependency->type == RUNTIME) + { + destination = FileUtils::Join(destination.c_str(), + "runtime", OS_NAME, dependency->version.c_str(), 0); + } + else if (dependency->type == SDK || dependency->type == MOBILESDK) + { + // The SDKs unzip directly into the component installation path. + } + else if (dependency->type == APP_UPDATE) + { + // Application updates need to be unzipped into the application + // installation directory. + destination = app->path; + } + else + { + return false; + } + + // Recursively create directories and then unzip the temporary + // download into that directory, calling the progress callback + // the entire time. + FileUtils::CreateDirectory(destination, true); + + // First check if this dependency is packaged in the 'dist' folder + // ala the SDK installer. In that case, we didn't actual + string zipFile(app->GetURLForDependency(dependency)); + if (!FileUtils::IsFile(zipFile)) + zipFile = WideToUTF8(GetTempFilePathForDependency(dependency)); + + return FileUtils::Unzip(zipFile, destination, &UnzipProgressCallback, &dependency); +} + + + diff --git a/installation/net_installer/win32/common.h b/installation/net_installer/win32/common.h new file mode 100644 index 000000000..622647407 --- /dev/null +++ b/installation/net_installer/win32/common.h @@ -0,0 +1,10 @@ +/** + * Appcelerator Titanium - licensed under the Apache Public License 2 + * see LICENSE in the root folder for details on the license. + * Copyright (c) 2009-2010 Appcelerator, Inc. All Rights Reserved. + */ +void ShowError(const std::string& msg); +void ShowError(const std::wstring& wmsg); +void ShutdownNetConnection(); +bool DownloadDependency(SharedApplication app, SharedDependency dependency); +bool InstallDependency(SharedApplication app, SharedDependency dependency); diff --git a/installation/net_installer/win32/progress_dialog.cpp b/installation/net_installer/win32/progress_dialog.cpp new file mode 100755 index 000000000..8d5bd8558 --- /dev/null +++ b/installation/net_installer/win32/progress_dialog.cpp @@ -0,0 +1,94 @@ +/** + * Appcelerator Titanium - licensed under the Apache Public License 2 + * see LICENSE in the root folder for details on the license. + * Copyright (c) 2009-2010 Appcelerator, Inc. All Rights Reserved. + */ + +#include +#include "progress_dialog.h" + +using std::string; +using std::wstring; + +ProgressDialog::ProgressDialog() : + dialogWindow(0) +{ + HRESULT hr = CoCreateInstance( + CLSID_ProgressDialog, + 0, + CLSCTX_INPROC_SERVER, + IID_IProgressDialog, (void**) &dialog ); +} + +ProgressDialog::~ProgressDialog() +{ + dialog->Release(); + CoUninitialize(); +} + +void ProgressDialog::SetCancelMessage(const std::wstring& message) +{ + dialog->SetCancelMsg(message.c_str(), 0); +} + +void ProgressDialog::SetTitle(const std::wstring& title) +{ + dialog->SetTitle(title.c_str()); +} + +void ProgressDialog::SetLineText(DWORD line, const std::wstring& message, bool compact) +{ + dialog->SetLine(line, message.c_str(), compact, 0); +} + +void ProgressDialog::Update(DWORD value, DWORD max) +{ + dialog->SetProgress(value, max); +} + +bool ProgressDialog::IsCancelled() +{ + return dialog->HasUserCancelled()==TRUE; +} + +void ProgressDialog::Show() +{ + DWORD flags = PROGDLG_NORMAL | PROGDLG_NOMINIMIZE; + HRESULT hr = dialog->StartProgressDialog(GetDesktopWindow(), 0, flags, 0); + if (!SUCCEEDED(hr)) + return; + + dialog->Timer(PDTIMER_RESET, 0); + IOleWindow* pIWnd = 0; + if (dialog->QueryInterface(IID_IOleWindow, (void**)&pIWnd) != S_OK) + return; + + HWND windowHandle; + if (pIWnd->GetWindow(&windowHandle) != S_OK) + { + pIWnd->Release(); + return; + } + + // Get the center of the screen. + this->dialogWindow = windowHandle; + HDC hScreenDC = CreateCompatibleDC(0); + int screenWidth = GetDeviceCaps(hScreenDC, HORZRES); + int screenHeight = GetDeviceCaps(hScreenDC, VERTRES); + DeleteDC(hScreenDC); + + // Get the dialog size. + RECT dialogRect; + GetWindowRect(dialogWindow, &dialogRect); + + // Calculate center position for the dialog and reposition it. + int centerX = ( screenWidth - (dialogRect.right - dialogRect.left)) / 2; + int centerY = ( screenHeight - (dialogRect.bottom - dialogRect.top)) / 2; + SetWindowPos(this->dialogWindow, 0, centerX, centerY-20, 0, 0, SWP_NOSIZE | SWP_NOZORDER); +} + +void ProgressDialog::Hide() +{ + dialog->StopProgressDialog(); +} + diff --git a/installation/net_installer/win32/progress_dialog.h b/installation/net_installer/win32/progress_dialog.h new file mode 100755 index 000000000..403dbdb32 --- /dev/null +++ b/installation/net_installer/win32/progress_dialog.h @@ -0,0 +1,30 @@ +/** + * Appcelerator Titanium - licensed under the Apache Public License 2 + * see LICENSE in the root folder for details on the license. + * Copyright (c) 2009-2010 Appcelerator, Inc. All Rights Reserved. + */ + +#include +#include +#include +#include + +class ProgressDialog +{ +public: + ProgressDialog(); + ~ProgressDialog(); + + void SetTitle(const std::wstring& title); + void SetCancelMessage(const std::wstring& message); + void SetLineText(DWORD line, const std::wstring& message, bool compact); + void Update(DWORD value, DWORD max); + void Show(); + void Hide(); + bool IsCancelled(); + HWND GetWindowHandle() { return this->dialogWindow; } + +private: + IProgressDialog* dialog; + HWND dialogWindow; +}; diff --git a/installation/net_installer/win32/update_installer.cpp b/installation/net_installer/win32/update_installer.cpp new file mode 100644 index 000000000..00ca6b64d --- /dev/null +++ b/installation/net_installer/win32/update_installer.cpp @@ -0,0 +1,201 @@ +/** + * Appcelerator Titanium - licensed under the Apache Public License 2 + * see LICENSE in the root folder for details on the license. + * Copyright (c) 2009-2010 Appcelerator, Inc. All Rights Reserved. + */ +#include +using namespace KrollUtils; +using KrollUtils::Application; +using KrollUtils::SharedApplication; +using KrollUtils::KComponentType; +using std::wstring; +using std::string; + +#include +#include +#include +#include +#include "progress_dialog.h" +#include "common.h" + +ProgressDialog* dialog = 0; +HWND GetInstallerHWND() +{ + if (!dialog) + return 0; + + return dialog->GetWindowHandle(); +} + +bool Progress(SharedDependency dependency, int percent) +{ + if (dialog->IsCancelled()) + return false; + + std::wstringstream str; + str << L"About " << percent << L"%" << " done"; + dialog->SetLineText(3, str.str(), false); + dialog->Update(percent, 100); + return true; +} + +static void Cleanup() +{ + if (dialog) + { + dialog->Hide(); + delete dialog; + } + + CoUninitialize(); +} + +static void ExitWithError(const std::string& message, int exitCode) +{ + ShowError(message); + Cleanup(); + exit(exitCode); +} + +static wstring GetDependencyDescription(SharedDependency d) +{ + if (d->type == MODULE) + return UTF8ToWide(d->name) + L" module"; + else if (d->type == SDK) + return L" SDK"; + else if (d->type == MOBILESDK) + return L" Mobile SDK"; + else if (d->type == APP_UPDATE) + return L" application update"; + else + return L" runtime"; +} + +static bool HandleAllJobs(SharedApplication app, vector& jobs) +{ + for (size_t i = 0; i < jobs.size(); i++) + { + SharedDependency dep(jobs.at(i)); + wstring description(GetDependencyDescription(dep)); + + wstring text(L"Downloading the "); + text.append(description); + dialog->SetLineText(2, text, false); + if (!DownloadDependency(app, dep)) + return false; + + text.assign(L"Extracting the "); + text.append(description); + dialog->SetLineText(2, text, false); + if (!InstallDependency(app, dep)) + return false; + } +} + +void ParseCommandLineDependencies(wstring toSearch, vector& jobs) +{ + string utf8ToSearch(WideToUTF8(toSearch)); + + vector tokens; + FileUtils::Tokenize(utf8ToSearch, tokens, ","); + for (size_t i = 0; i < tokens.size(); i++) + { + string& token = tokens[i]; + size_t pos = token.find(":"); + if (pos == string::npos || pos == 0 || + pos == token.length() - 1) + continue; + + string name(token.substr(0, pos)); + string version(token.substr(pos + 1, token.length())); + + KComponentType type = MODULE; + if (name == "sdk") + type = SDK; + else if (name == "mobilesdk") + type = MOBILESDK; + else if (name == "runtime") + type = RUNTIME; + + jobs.push_back(Dependency::NewDependencyFromValues( + type, name, version)); + } +} + +int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, + int nCmdShow) +{ + int argc; + LPWSTR* argv = CommandLineToArgvW(GetCommandLineW(), &argc); + + std::wstring applicationPath, updateFile; + vector jobs; + for (int i = 1; i < argc; i++) + { + wstring arg = argv[i]; + if (arg == L"-app") + { + i++; + applicationPath = argv[i]; + } + else if (arg == L"-update") + { + i++; + updateFile = argv[i]; + } + else + { + ParseCommandLineDependencies(argv[i], jobs); + } + } + + if (applicationPath.empty()) + ExitWithError("The installer could not determine the application path.", __LINE__); + + SharedApplication app(0); + string utf8Path(WideToUTF8(applicationPath)); + if (updateFile.empty()) + { + app = Application::NewApplication(utf8Path); + } + else + { + string utf8Update(WideToUTF8(updateFile)); + app = Application::NewApplication(utf8Update, utf8Path); + + // Always delete the update file as soon as possible. That + // way the application can continue booting even if the update + // cannot be downloaded. + FileUtils::DeleteFile(utf8Update); + } + + if (app.isNull()) + ExitWithError("The installer could not read the application manifest.", __LINE__); + + // If this is an application update, add a job for the update to + // this list of necessary jobs. + if (!updateFile.empty()) + { + jobs.push_back(Dependency::NewDependencyFromValues( + APP_UPDATE, "appupdate", app->version)); + } + + if (jobs.empty()) + ExitWithError("The installer was not given any work to do.", __LINE__); + + if (CoInitialize(0) != S_OK) + ExitWithError("The installer could not initialize COM.", __LINE__); + + dialog = new ProgressDialog(); + dialog->SetTitle(KrollUtils::UTF8ToWide(app->name)); + dialog->SetCancelMessage(L"Aborting installation..."); + dialog->SetLineText(1, L"Installing components", false); + dialog->Show(); + + if (!dialog->GetWindowHandle()) + ExitWithError("Could not get progress dialog window handle.", __LINE__); + + bool success = HandleAllJobs(app, jobs); + Cleanup(); + return success ? 0 : 1; +} diff --git a/installer/win32/SConscript b/installer/win32/SConscript index 5aaf88332..a7d0f8bf4 100644 --- a/installer/win32/SConscript +++ b/installer/win32/SConscript @@ -2,22 +2,38 @@ import os, os.path as path Import('build') -env = build.env.Clone(); +env = build.env.Clone() env.Append(CPPDEFINES=['USE_NO_EXPORT=1', 'UNICODE=1', '_UNICODE=1']) -build.add_thirdparty(env, 'poco') -env.Append(LIBS=['msi', 'user32', 'shell32', 'iphlpapi', 'advapi32', 'wininet', 'urlmon']) -# Compile against the MSVSCRT statically. +env.Append(LIBS=[ + 'msi', + 'user32', + 'shell32', + 'iphlpapi', + 'advapi32', + 'wininet', + 'urlmon', + 'ole32', + 'gdi32' +]) + +# Common sources for both the MSI DLL and the update installer. +env.Append(CPPPATH=[build.kroll_utils_dir]) +sources = ["common.cpp"] + build.get_kroll_utils( + path.join(build.dir, 'objs', 'netinstaller')) + +build.mark_build_target(env.Program( + path.join(build.runtime_build_dir, 'sdk', 'installer', 'installer.exe'), + sources + ['progress_dialog.cpp', 'update_installer.cpp'])) + +# Statically-link the MSI DLL. ccf = env['CCFLAGS'][:] if '/MD' in ccf: ccf.remove('/MD') ccf.append('/MT') env['CCFLAGS'] = ccf -env.Append(CPPPATH=[build.kroll_utils_dir]) - -dest = path.join(build.dir, 'sdk', 'installer', 'titanium_installer') -installer_objects = Glob("*.cpp") -installer_objects += build.get_kroll_utils(path.join(build.dir, 'objs', 'installer')) - -build.mark_build_target(env.SharedLibrary(dest, installer_objects)) +env.Append(LIBS=['msi']); # Add the MSI lib. +build.mark_build_target(env.SharedLibrary( + path.join(build.runtime_build_dir, 'sdk', 'installer', 'titanium_installer'), + sources + ['titanium_actions.cpp'])) diff --git a/installer/win32/titanium_actions.cpp b/installer/win32/titanium_actions.cpp index b1656241d..b6830bee2 100644 --- a/installer/win32/titanium_actions.cpp +++ b/installer/win32/titanium_actions.cpp @@ -1,96 +1,83 @@ /** * Appcelerator Titanium - licensed under the Apache Public License 2 * see LICENSE in the root folder for details on the license. - * Copyright (c) 2009 Appcelerator, Inc. All Rights Reserved. + * Copyright (c) 2009-2010 Appcelerator, Inc. All Rights Reserved. */ #include +using namespace KrollUtils; +using KrollUtils::Application; +using KrollUtils::SharedApplication; +using KrollUtils::KComponentType; +using std::wstring; +using std::string; + #include #include -#include #include -#include +#include +#include +#include "common.h" #pragma comment(linker, "/EXPORT:NetInstallSetup=_NetInstallSetup@4") #pragma comment(linker, "/EXPORT:NetInstall=_NetInstall@4") #pragma comment(linker, "/EXPORT:Clean=_Clean@4") -using namespace KrollUtils; -using namespace std; wstring MsiProperty(MSIHANDLE hInstall, const wchar_t* property) { wchar_t buffer[4096]; DWORD bufferLength = 4096; MsiGetProperty(hInstall, property, buffer, &bufferLength); - return wstring(buffer, bufferLength); } vector& Split(const wstring& s, wchar_t delim, vector& elems) { - wstringstream ss(s); + std::wstringstream ss(s); wstring item; - while(getline(ss, item, delim)) + while (getline(ss, item, delim)) { elems.push_back(item); } return elems; } -void ShowError(string msg) +HWND GetInstallerHWND() { - wstring wmsg = KrollUtils::UTF8ToWide(msg); - MessageBoxW( - GetDesktopWindow(), - wmsg.c_str(), - L"Installation Failed", - MB_OK | MB_SYSTEMMODAL | MB_ICONEXCLAMATION); -} + HWND hwnd = FindWindowW(L"MsiDialogCloseClass", NULL); + if (!hwnd) + hwnd = GetActiveWindow(); -void ShowError(wstring wmsg) -{ - MessageBoxW( - GetDesktopWindow(), - wmsg.c_str(), - L"Installation Failed", - MB_OK | MB_SYSTEMMODAL | MB_ICONEXCLAMATION); + return hwnd; } + SharedApplication CreateApplication(MSIHANDLE hInstall) { wstring params = MsiProperty(hInstall, L"CustomActionData"); vector tokens; Split(params, L';', tokens); - if (tokens[0] == L"app_update") + wstring dependencies(tokens[0]); + vector > manifest; + Split(dependencies, L'&', tokens); + for (size_t i = 0; i < tokens.size(); i++) { - wstring updateManifest = tokens[1]; - wstring appPath = tokens[2]; + wstring token = tokens[i]; + wstring key = token.substr(0, token.find(L"=")); + wstring value = token.substr(token.find(L"=")+1); - return Application::NewApplication( - WideToUTF8(updateManifest), WideToUTF8(appPath)); + manifest.push_back(pair( + WideToUTF8(key), WideToUTF8(value))); } - else - { - wstring dependencies(tokens[0]); - vector > manifest; - Split(dependencies, L'&', tokens); - for (size_t i = 0; i < tokens.size(); i++) - { - wstring token = tokens[i]; - wstring key = token.substr(0, token.find(L"=")); - wstring value = token.substr(token.find(L"=")+1); - manifest.push_back(pair( - WideToUTF8(key), WideToUTF8(value))); - } - - return Application::NewApplication(manifest); - } + return Application::NewApplication(manifest); } -vector -FindUnresolvedDependencies(MSIHANDLE hInstall) +vector FindUnresolvedDependencies(MSIHANDLE hInstall) { + // Tell the installer to check the installation state and execute + // the code needed during the rollback, acquisition, or + // execution phases of the installation. vector unresolved; vector components; vector& installedComponents = @@ -100,8 +87,7 @@ FindUnresolvedDependencies(MSIHANDLE hInstall) components.push_back(installedComponents.at(i)); } - wstring dependencies, bundledModules, bundledRuntime; - wstring updateManifest, installDir; + wstring dependencies, bundledModules, bundledRuntime, installDir; vector tokens; // deferred / async mode, get from the hacked "CustomActionData" property if (MsiGetMode(hInstall, MSIRUNMODE_SCHEDULED) == TRUE) @@ -109,17 +95,10 @@ FindUnresolvedDependencies(MSIHANDLE hInstall) wstring params = MsiProperty(hInstall, L"CustomActionData"); Split(params, L';', tokens); - if (tokens[0] == L"app_update") - { - updateManifest.assign(tokens[1]); - installDir.assign(tokens[2]); - } - else - { - dependencies.assign(tokens[0]); - if (tokens.size() > 1) bundledModules.assign(tokens[1]); - if (tokens.size() > 2) bundledRuntime.assign(tokens[2]); - } + dependencies.assign(tokens[0]); + if (tokens.size() > 1) bundledModules.assign(tokens[1]); + if (tokens.size() > 2) bundledRuntime.assign(tokens[2]); + } else // immediate mode, get from actual properties.. god this sucks { @@ -141,20 +120,10 @@ FindUnresolvedDependencies(MSIHANDLE hInstall) RUNTIME, "runtime", "", "", true)); } - if (updateManifest.size() > 0) - { - SharedApplication app = Application::NewApplication( - WideToUTF8(updateManifest), WideToUTF8(installDir)); - - - unresolved = app->ResolveDependencies(); - if (FileUtils::Basename(WideToUTF8(updateManifest)) == ".update") - { - unresolved.push_back(Dependency::NewDependencyFromValues( - APP_UPDATE, "app_update", app->version)); - } - } + // We cannot resolve dependencies in the normal way, since bundled modules + // are still packed into the MSI and won't be found. Instead, go through + // the unbundled modules line-by-line and try to resolve them like that. tokens.clear(); Split(dependencies, L'&', tokens); for (size_t i = 0; i < tokens.size(); i++) @@ -169,8 +138,7 @@ FindUnresolvedDependencies(MSIHANDLE hInstall) SharedDependency dependency = Dependency::NewDependencyFromManifestLine( WideToUTF8(key), WideToUTF8(value)); - SharedComponent c = BootUtils::ResolveDependency(dependency, components); - if (c.isNull()) + if (BootUtils::ResolveDependency(dependency, components).isNull()) { unresolved.push_back(dependency); } @@ -179,448 +147,134 @@ FindUnresolvedDependencies(MSIHANDLE hInstall) return unresolved; } -// a helper function that sends a progress message to the installer -UINT Progress(MSIHANDLE hInstall, SharedDependency dependency, - const wchar_t *intro, int percent) + +// A helper function that sends a progress message to the installer, returns +// false if the user has cancelled the action. +static int currentProgress = 0; +static MSIHANDLE installHandle; +static bool isDownloading = false; +bool Progress(SharedDependency dependency, int percentage) { - static int oldPercent = -1; - if (oldPercent == percent) // prevent updating too often / flickering - return IDOK; + // The download step represents the first 50% of the download+extract + // action, so divide the progress in half here. + if (isDownloading) + percentage = percentage / 2; - oldPercent = percent; - - wstring message(intro); - if (dependency->type == MODULE) - { - message += L"Module \""; - message += UTF8ToWide(dependency->name); - message += L"\" "; - } - else if (dependency->type == SDK) - { - message += L"SDK "; - } - else if (dependency->type == MOBILESDK) - { - message += L"Mobile SDK "; - } - else if (dependency->type == APP_UPDATE) - { - message += L"Application Update "; - } - else - { - message += L"Runtime "; - } - message += UTF8ToWide(dependency->version); - message += L" ("; - wchar_t buffer[8]; - _itow(percent, buffer, 10); - message += buffer; - message += L"%)"; + // If the progress hasn't increased, just bail here. + if (percentage <= currentProgress) + return true; PMSIHANDLE actionRecord = MsiCreateRecord(3); + MsiRecordSetInteger(actionRecord, 1, percentage); // Current percentage. + MsiRecordSetInteger(actionRecord, 2, 0); // Unused + MsiRecordSetInteger(actionRecord, 3, 0); // Unused + UINT result = MsiProcessMessage(installHandle, INSTALLMESSAGE_ACTIONDATA, actionRecord); + if (result == IDCANCEL) + return false; - MsiRecordSetString(actionRecord, 1, L"NetInstall"); - MsiRecordSetString(actionRecord, 2, message.c_str()); - MsiRecordSetString(actionRecord, 3, L"Downloading.."); + MsiRecordSetInteger(actionRecord, 1, 2); // Type of message: Increment the bar + MsiRecordSetInteger(actionRecord, 2, currentProgress - percentage); // Number of ticks the bar has moved. + MsiRecordSetInteger(actionRecord, 3, 0); // Unused + result = MsiProcessMessage(installHandle, INSTALLMESSAGE_PROGRESS, actionRecord); + if (result == IDCANCEL) + return false; - return MsiProcessMessage(hInstall, INSTALLMESSAGE_ACTIONSTART, actionRecord); + currentProgress = percentage; + return true; } -extern "C" UINT __stdcall -NetInstallSetup(MSIHANDLE hInstall) +extern "C" UINT __stdcall NetInstallSetup(MSIHANDLE hInstall) { - // Installer is generating the installation script of the - // custom action. - - // Tell the installer to increase the value of the final total - // length of the progress bar by the total number of ticks in - // the custom action. - vector unresolved = FindUnresolvedDependencies(hInstall); - PMSIHANDLE hProgressRec = MsiCreateRecord(2); - - MsiRecordSetInteger(hProgressRec, 1, 3); - MsiRecordSetInteger(hProgressRec, 2, unresolved.size()); - - UINT iResult = MsiProcessMessage( - hInstall, INSTALLMESSAGE_PROGRESS, hProgressRec); - if ((iResult == IDCANCEL)) - return ERROR_INSTALL_USEREXIT; + // No-op return ERROR_SUCCESS; } -typedef struct { - MSIHANDLE hInstall; - SharedDependency dependency; -} UnzipProgressData; - -bool UnzipProgress(char *message, int current, int total, void *data) +bool ProcessDependency(MSIHANDLE hInstall, SharedApplication app, SharedDependency dependency) { - UnzipProgressData* progressData = (UnzipProgressData*) data; - - int percent = total == 0 ? 0 : floor(((double)current/(double)total)*100); - UINT result = Progress(progressData->hInstall, - progressData->dependency, L"Extracting ", percent); + // Wow, this is convoluted and easy to get wrong. It feels like a + // scene out of Fear and Loathing in Las Vegas. Essentially we've been + // consuming mind-altering substances for the better part of a day + // (coffee, of course) and now we're in bat country. + + // All the information on these magic numbers can be found here: + // http://msdn.microsoft.com/en-us/library/aa370354(VS.85).aspx + // If that page disappears, we're basically all screwed, but you might + // have luck Binging or whatever for: INSTALLMESSAGE_PROGRESS. + + // I pity you, I really do, if you need to change this code. Rest assured + // though, if you add code here without documenting what every single + // magic number does, I will build a time machine, travel into the future/past + // as appropriate and engage in the most heinous psychological torture that + // our busted legal system affords. Yes, that is a threat. + + // Tell the installer to use explicit progress messages and reset the progress bar. + PMSIHANDLE record = MsiCreateRecord(3); + MsiRecordSetInteger(record, 1, 1); // Type of message: Information about progress. + MsiRecordSetInteger(record, 2, 1); // Number of ticks to move per message. + MsiRecordSetInteger(record, 3, 0); // We will send explicit progress message. + UINT result = MsiProcessMessage(hInstall, INSTALLMESSAGE_PROGRESS, record); if (result == IDCANCEL) - return false; - return true; -} - -wstring GetFilePath(SharedDependency dependency) -{ - wstring outFilename; - string filename; - switch (dependency->type) - { - case MODULE: filename = "module-"; break; - case RUNTIME: filename = "runtime-"; break; - case MOBILESDK:filename = "mobilesdk-"; break; - case APP_UPDATE: filename = "appupdate-"; break; - case SDK: filename = "sdk-"; break; - } - filename.append(dependency->name); - filename.append("-"); - filename.append(dependency->version); - filename.append(".zip"); - static string tempdir; - if (tempdir.empty()) - { - tempdir.assign(FileUtils::GetTempDirectory()); - FileUtils::CreateDirectory(tempdir); - } - - return UTF8ToWide(FileUtils::Join(tempdir.c_str(), filename.c_str(), 0)); -} + return ERROR_INSTALL_USEREXIT; + MsiRecordSetInteger(record, 1, 0); // Type of message: Reset progress bar. + MsiRecordSetInteger(record, 2, 100); // Total number of ticks to use. + MsiRecordSetInteger(record, 3, 0); // Left-to-right progress bar. + result = MsiProcessMessage(hInstall, INSTALLMESSAGE_PROGRESS, record); + if (result == IDCANCEL) + return ERROR_INSTALL_USEREXIT; -bool Install(MSIHANDLE hInstall, SharedDependency dependency) -{ - string componentInstallPath = FileUtils::GetSystemRuntimeHomeDirectory(); - string destination, updateFile; + // Set up the custom action description and template. + wstring description(L"Downloading and extracting the"); if (dependency->type == MODULE) - { - destination = FileUtils::Join( - componentInstallPath.c_str(), "modules", OS_NAME, - dependency->name.c_str(), dependency->version.c_str(), 0); - } - else if (dependency->type == RUNTIME) - { - destination = FileUtils::Join( - componentInstallPath.c_str(), "runtime", OS_NAME, - dependency->version.c_str(), 0); - } - else if (dependency->type == SDK || dependency->type == MOBILESDK) - { - destination = componentInstallPath; - } + description.append(UTF8ToWide(dependency->name) + L" module"); + else if (dependency->type == SDK) + description.append(L" SDK"); + else if (dependency->type == MOBILESDK) + description.append(L" Mobile SDK"); else if (dependency->type == APP_UPDATE) - { - wstring params = MsiProperty(hInstall, L"CustomActionData"); - vector tokens; - Split(params, L';', tokens); - - updateFile = WideToUTF8(tokens[1]); - destination = WideToUTF8(tokens[2]); - } + description.append(L" application update"); else - { - return false; - } - - // Recursively create directories - UnzipProgressData *data = new UnzipProgressData(); - data->hInstall = hInstall; - data->dependency = dependency; - FileUtils::CreateDirectory(destination, true); - - string utf8Path = WideToUTF8(GetFilePath(dependency)); - bool success = FileUtils::Unzip( - utf8Path, destination, &UnzipProgress, (void*)data); - - if (success && dependency->type == APP_UPDATE) - { - FileUtils::DeleteFile(updateFile); - } - - //delete data; - return success; -} - -static HWND GetInstallerHWND() -{ - HWND hwnd = FindWindowW(L"MsiDialogCloseClass", NULL); - if (!hwnd) - hwnd = GetActiveWindow(); - - return hwnd; -} - -static void ShowLastDownloadEror() -{ - DWORD bufferSize = 1024, error; - wchar_t staticErrorBuffer[1024]; - wchar_t* errorBuffer = staticErrorBuffer; - BOOL success = InternetGetLastResponseInfo(&error, errorBuffer, &bufferSize); - - if (!success && GetLastError() == ERROR_INSUFFICIENT_BUFFER) - { - errorBuffer = new wchar_t[bufferSize]; - success = InternetGetLastResponseInfo(&error, errorBuffer, &bufferSize); - } - - std::wstring errorString(L"Download failed: Unknown error"); - if (success) - errorString = std::wstring(L"Download failed:") + errorBuffer; - - ShowError(::WideToSystem(errorString)); - - if (errorBuffer != staticErrorBuffer) - delete [] errorBuffer; -} - -bool DownloadDependency(MSIHANDLE hInstall, HINTERNET hINet, SharedDependency dependency) -{ - SharedApplication app = CreateApplication(hInstall); - wstring url(UTF8ToWide(app->GetURLForDependency(dependency))); - wstring outFilename(GetFilePath(dependency)); - - WCHAR szDecodedUrl[INTERNET_MAX_URL_LENGTH]; - DWORD cchDecodedUrl = INTERNET_MAX_URL_LENGTH; - WCHAR szDomainName[INTERNET_MAX_URL_LENGTH]; - - // parse the URL - HRESULT hr = CoInternetParseUrl(url.c_str(), PARSE_DECODE, - URL_ENCODING_NONE, szDecodedUrl, INTERNET_MAX_URL_LENGTH, - &cchDecodedUrl, 0); - if (hr != S_OK) - { - string error = Win32Utils::QuickFormatMessage(GetLastError()); - error = string("Could not decode URL: ") + error; - ShowError(error); - return false; - } - - // figure out the domain/hostname - hr = CoInternetParseUrl(szDecodedUrl, PARSE_DOMAIN, - 0, szDomainName, INTERNET_MAX_URL_LENGTH, &cchDecodedUrl, 0); - if (hr != S_OK) - { - string error = Win32Utils::QuickFormatMessage(GetLastError()); - error = string("Could not parse domain: ") + error; - ShowError(error); - return false; - } - - // start the HTTP fetch - HINTERNET hConnection = InternetConnectW(hINet, szDomainName, - 80, L" ", L" ", INTERNET_SERVICE_HTTP, 0, 0 ); - if (!hConnection) - { - string error = Win32Utils::QuickFormatMessage(GetLastError()); - error = string("Could not start connection: ") + error; - ShowError(error); - return false; - } - - wstring wurl(szDecodedUrl); - wstring path = wurl.substr(wurl.find(szDomainName)+wcslen(szDomainName)); - HINTERNET hRequest = HttpOpenRequestW(hConnection, L"GET", path.c_str(), - 0, 0, 0, - INTERNET_FLAG_IGNORE_CERT_CN_INVALID | // Disregard TLS certificate errors. - INTERNET_FLAG_IGNORE_CERT_DATE_INVALID | - INTERNET_FLAG_KEEP_CONNECTION | // Needed for NTLM authentication. - INTERNET_FLAG_NO_CACHE_WRITE | INTERNET_FLAG_RELOAD | // Always get the latest. - INTERNET_FLAG_NO_COOKIES, 0); - - resend: - HttpSendRequest(hRequest, 0, 0, 0, 0); - - DWORD dwErrorCode = hRequest ? ERROR_SUCCESS : GetLastError(); - if (InternetErrorDlg(GetInstallerHWND(), hRequest, dwErrorCode, - FLAGS_ERROR_UI_FILTER_FOR_ERRORS | - FLAGS_ERROR_UI_FLAGS_CHANGE_OPTIONS | - FLAGS_ERROR_UI_FLAGS_GENERATE_DATA, - 0) == ERROR_INTERNET_FORCE_RETRY) - goto resend; - - CHAR buffer[2048]; - DWORD bytesRead; - DWORD contentLength = 0; - DWORD statusCode = 0; - DWORD size = sizeof(contentLength); - BOOL success = HttpQueryInfo(hRequest, - HTTP_QUERY_STATUS_CODE | HTTP_QUERY_FLAG_NUMBER, - (LPDWORD) &statusCode, (LPDWORD) &size, 0); - if (!success || statusCode != 200) - { - string error = Win32Utils::QuickFormatMessage(GetLastError()); - if (success) - { - ostringstream str; - str << "Invalid HTTP Status Code (" << statusCode << ")"; - error = str.str(); - } - error = string("Could not query info: ") + error; - ShowError(error); - return false; - } - - success = HttpQueryInfo(hRequest, - HTTP_QUERY_CONTENT_LENGTH | HTTP_QUERY_FLAG_NUMBER, - (LPDWORD)&contentLength, (LPDWORD)&size, 0); - if (!success) - { - string error = Win32Utils::QuickFormatMessage(GetLastError()); - error = string("Could not determine content length: ") + error; - ShowError(error); - return false; - } + description.append(L" runtime"); + description += L" (" + UTF8ToWide(dependency->version) + L")"; + wstring plate(L"[1]\% complete..."); + + // Update the install message display. We've basically set up a template + // above that looks like '[1]% complete". When we sent the INSTALLMESSAGE_ACTIONDATA + // message, the '[1]' will turn into the value in the first message field. + // We aren't using the other fields right now, but we may in the future. + MsiRecordSetString(record, 1, L"NetInstall"); // Custom action name + MsiRecordSetString(record, 2, description.c_str()); // Description. + MsiRecordSetString(record, 3, plate.c_str()); // Template for action data messages. + result = MsiProcessMessage(hInstall, INSTALLMESSAGE_ACTIONSTART, record); + if (result == IDCANCEL) + return ERROR_INSTALL_USEREXIT; - // now stream the resulting HTTP into a file - HANDLE file = CreateFileW(outFilename.c_str(), GENERIC_WRITE, - 0, 0, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0); - if (file == INVALID_HANDLE_VALUE) - { - string error = Win32Utils::QuickFormatMessage(GetLastError()); - error = string("Could not open output file (") + WideToUTF8(outFilename) + - string("): ") + error; - ShowError(error); + currentProgress = 0; + installHandle = hInstall; + isDownloading = true; + if (!DownloadDependency(app, dependency)) return false; - } - - // Keep reading from InternetReadFile as long as it's successful and the number - // of bytes read is greater than zero. - bool showError = true; - DWORD total = 0; - while ((success = InternetReadFile(hRequest, buffer, 2047, &bytesRead)) && bytesRead > 0) - { - // Be sure to Write the entire buffer into to the file. - DWORD bytesWritten = 0; - while (bytesWritten < bytesRead) - { - if (!WriteFile(file, buffer + bytesWritten, - bytesRead - bytesWritten, &bytesWritten, 0)) - { - showError = success = false; - string error = Win32Utils::QuickFormatMessage(GetLastError()); - error = string("Could write data to output file (") + WideToUTF8(outFilename) + - string("): ") + error; - ShowError(error); - break; - } - } - - total += bytesRead; - UINT result = Progress(hInstall, dependency, L"Downloading ", - floor(((double)total/(double)contentLength)*100)); - if (result == IDCANCEL) - { - showError = success = false; - break; - } - } - - if (!success) - { - if (showError) - ShowLastDownloadEror(); - - CancelIo(file); - CloseHandle(file); - DeleteFileW(outFilename.c_str()); - } - else - { - CloseHandle(file); - } - - InternetCloseHandle(hRequest); - return success; -} -bool ProcessDependency(MSIHANDLE hInstall, PMSIHANDLE hProgressRec, - HINTERNET hINet, SharedDependency dependency) -{ - UINT result = MsiProcessMessage(hInstall, INSTALLMESSAGE_PROGRESS, hProgressRec); - if ((result == IDCANCEL)) - return false; - if (!DownloadDependency(hInstall, hINet, dependency)) - return false; - if (!Install(hInstall, dependency)) + isDownloading = false; + if (!InstallDependency(app, dependency)) return false; return true; } -// inspired by http://msdn.microsoft.com/en-us/library/aa367525(VS.85).aspx -// also see http://msdn.microsoft.com/en-us/library/aa370354(VS.85).aspx for INSTALLMESSAGE_PROGRESS message values -extern "C" UINT __stdcall -NetInstall(MSIHANDLE hInstall) +extern "C" UINT __stdcall NetInstall(MSIHANDLE hInstall) { - // Tell the installer to check the installation state and execute - // the code needed during the rollback, acquisition, or - // execution phases of the installation. + // If the SDK is listed, we need to ignore other non-SDK components below. + SharedApplication app(CreateApplication(hInstall)); vector unresolved = FindUnresolvedDependencies(hInstall); - - PMSIHANDLE hActionRec = MsiCreateRecord(3); - PMSIHANDLE hProgressRec = MsiCreateRecord(3); - - // Installer is executing the installation script. Set up a - // record specifying appropriate templates and text for - // messages that will inform the user about what the custom - // action is doing. Tell the installer to use this template and - // text in progress messages. - - MsiRecordSetString(hActionRec, 1, TEXT("NetInstall")); - MsiRecordSetString(hActionRec, 2, TEXT("Downloading dependencies...")); - MsiRecordSetString(hActionRec, 3, TEXT("Downloading [4] [5] ([1] of [2]...)")); - UINT result = MsiProcessMessage(hInstall, INSTALLMESSAGE_ACTIONSTART, hActionRec); - if ((result == IDCANCEL)) - return ERROR_INSTALL_USEREXIT; - - // Tell the installer to use explicit progress messages. - MsiRecordSetInteger(hProgressRec, 1, 1); - MsiRecordSetInteger(hProgressRec, 2, 1); - MsiRecordSetInteger(hProgressRec, 3, 0); - result = MsiProcessMessage(hInstall, INSTALLMESSAGE_PROGRESS, hProgressRec); - if ((result == IDCANCEL)) - return ERROR_INSTALL_USEREXIT; - - //Specify that an update of the progress bar's position in - //this case means to move it forward by one increment. - MsiRecordSetInteger(hProgressRec, 1, 2); - MsiRecordSetInteger(hProgressRec, 2, 1); - MsiRecordSetInteger(hProgressRec, 3, 0); - - // The following loop sets up the record needed by the action - // messages and tells the installer to send a message to update - // the progress bar. - - MsiRecordSetInteger(hActionRec, 2, unresolved.size()); - - // Initialize the Interent DLL - HINTERNET hINet = InternetOpenW( - L"Mozilla/5.0 (compatible; Titanium_Downloader/0.1; Win32)", - INTERNET_OPEN_TYPE_PRECONFIG, 0, 0, 0); - if (!hINet) - { - string error(Win32Utils::QuickFormatMessage(GetLastError())); - error = string("Could not open Internet connection: ") + error; - ShowError(error); - return ERROR_INSTALL_FAILURE; - } - - // Install app updates and SDKs first. - // If the (non-mobile) SDK is listed, we need to ignore runtime+modules below bool sdkInstalled = false; for (size_t i = 0; i < unresolved.size(); i++) { SharedDependency dep = unresolved.at(i); - if (dep->type == SDK || dep->type == MOBILESDK || dep->type == APP_UPDATE) + if (dep->type == SDK || dep->type == MOBILESDK) { - if (!ProcessDependency(hInstall, hProgressRec, hINet, dep)) + if (!ProcessDependency(hInstall, app, dep)) { - InternetCloseHandle(hINet); + ShutdownNetConnection(); return ERROR_INSTALL_USEREXIT; } @@ -637,21 +291,20 @@ NetInstall(MSIHANDLE hInstall) if (dep->type != SDK && dep->type != MOBILESDK && dep->type != APP_UPDATE && !sdkInstalled) { - if (!ProcessDependency(hInstall, hProgressRec, hINet, dep)) + if (!ProcessDependency(hInstall, app, dep)) { - InternetCloseHandle(hINet); + ShutdownNetConnection(); return ERROR_INSTALL_USEREXIT; } } } } - InternetCloseHandle(hINet); + ShutdownNetConnection(); return ERROR_SUCCESS; } -extern "C" UINT __stdcall -Clean(MSIHANDLE hInstall) +extern "C" UINT __stdcall Clean(MSIHANDLE hInstall) { wchar_t dir[MAX_PATH]; DWORD dirSize = MAX_PATH;