v20140527.01/InstallerLauncher/InstallerLauncher/InstallerLauncher.cpp
author Gregory Szorc <gps@mozilla.com>
Mon, 14 Jul 2014 17:58:04 -0700
changeset 29 d29a3d24405cb8064383bdd4b0b8bfacf15fba5f
parent 23 fc3c622aff83b8d4153ed57704b7a996cce1e0ed
permissions -rw-r--r--
Bug 928173 - Bump version of update hotfix

/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#include "stdafx.h"
#include "InstallerLauncher.h"
#include <string>
#include <sstream>
#include <fstream>
#include <algorithm>
#include <vector>
#include <iostream>
#include <locale>
#include <codecvt>
#include <array>
#include <memory>
#include <Shlwapi.h>

// Typedefs

typedef std::basic_string<TCHAR> tstring;
typedef std::basic_ostringstream<TCHAR> tostringstream;

// Enumeration of the launchers process exit codes.

enum ExitCode {
  Exit_Success = 0,
  Exit_InitializationFailed = 1,
  Exit_InvalidArguments = 2,
  Exit_CantWriteToInstallDir = 3,
  Exit_OpenLauncherLogFailed = 4,
  Exit_ElevationFailed = 5,
  Exit_ElevationCancelled = 6,
  Exit_UnarchivingFailed = 7,
  Exit_AccessingLogFailed = 8,
  Exit_InstallationFailed = 9,
};

// Forward declarations.

ExitCode RunInstaller(const tstring& installerPath,
                      const tstring& iniPath,
                      const tstring& targetPath,
                      std::wofstream& logFile);

// Utilities.

namespace util {

class AutoHandle  {
  HANDLE mHandle;
  AutoHandle(const AutoHandle&);
public:
  AutoHandle(HANDLE h = NULL) : mHandle(h) {}
  ~AutoHandle() { if (mHandle) ::CloseHandle(mHandle); }
  HANDLE get() const { return mHandle; }
  HANDLE* address() { return &mHandle; }
  AutoHandle& operator=(HANDLE h)  {
    mHandle = h;
    return *this;
  }
};

bool AppendPath(const tstring& first, const tstring& second, tstring& out)
{
  std::vector<TCHAR> buffer(MAX_PATH);
  const size_t length = std::min(first.length(), size_t(MAX_PATH)-1);
  first.copy(&buffer[0], length);
  buffer[length] = '\0';

  BOOL result = ::PathAppend(&buffer[0], second.c_str());
  if (!result) {
    return false;
  }

  out = &buffer[0];
  return true;
}

void Trim(tstring& str)
{
  tstring totrim = L" \n\r\t\f";

  size_t first = str.find_first_not_of(totrim);
  size_t last = str.find_last_not_of(totrim);

  if (first == tstring::npos) {
    str.clear();
    return;
  }

  if (last == tstring::npos)
    last = str.length() - 1;

  str = str.substr(first, last - first + 1);
}

bool CanUserElevate()
{
  AutoHandle token;
  if (!::OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, token.address())) {
     return false;
  }

  TOKEN_ELEVATION_TYPE elevationType;
  DWORD len;
  bool canElevate = ::GetTokenInformation(token.get(), TokenElevationType,
                                        &elevationType,
                                        sizeof(elevationType), &len) &&
                    (elevationType == TokenElevationTypeLimited);
  return canElevate;
}

bool IsVistaOrHigher()
{
  OSVERSIONINFO versionInfo;
  versionInfo.dwOSVersionInfoSize = sizeof(versionInfo);
  BOOL res = ::GetVersionEx(&versionInfo);
  if (!res) {
    return false;
  }

  return versionInfo.dwMajorVersion >= 6;
}

bool TempFileName(const tstring& dir, const tstring& prefix, tstring& str)
{
  std::vector<TCHAR> buffer(MAX_PATH);
  UINT result = ::GetTempFileName(dir.c_str(), prefix.c_str(), 0, &buffer[0]);
  if (result == 0) {
    return false;
  }

  str = &buffer[0];
  return true;
}

bool CanOpenFileForWriting(const tstring& path)
{
  {
    AutoHandle file = ::CreateFile(path.c_str(), FILE_GENERIC_WRITE, 0, NULL,
                                   OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
    if (file.get() == INVALID_HANDLE_VALUE) {
      const DWORD err = ::GetLastError();
      tostringstream oss;

      if (err == ERROR_FILE_NOT_FOUND) {
        oss << L"CanOpenFileForWriting() - File not found: " << path << "\n";
      }
      else {
        oss << L"CanOpenFileForWriting() - Error: 0x" << std::hex << err << ", Path: " << path << "\n";
      }

      OutputDebugString(oss.str().c_str());
      return false;
    }
  }

  if (!::DeleteFile(path.c_str()))  {
    tostringstream oss;
    oss << "Failed to delete with error 0x" << std::hex << ::GetLastError() << ", path: " << path << "\n";
    OutputDebugString(oss.str().c_str());
  }

  return true;
}

bool CanWriteToInstallDir(const tstring& dir)
{
  tstring path;
  if (!TempFileName(dir, L"write-check", path))  {
    OutputDebugString(L"TempFileName() failed\n");
    return false;
  }

  return CanOpenFileForWriting(path);
}

bool GetFileLastWriteTime(const tstring& path, FILETIME& lastWriteTime)
{
  AutoHandle file = ::CreateFile(path.c_str(), GENERIC_READ, FILE_SHARE_READ, NULL,
                                 OPEN_EXISTING, 0, NULL);
  if (file.get() == INVALID_HANDLE_VALUE)  {
    tostringstream oss;
    oss << L"Failed to open file " << path << L", error is 0x" << ::GetLastError() << L"\n";
    OutputDebugString(oss.str().c_str());
    return false;
  }

  FILETIME written;
  if (!::GetFileTime(file.get(), NULL, NULL, &written))  {
    tostringstream oss;
    oss << L"GetFileTime() failed for file " << path << L", error is 0x" << ::GetLastError() << L"\n";
    OutputDebugString(oss.str().c_str());
    return false;
  }

  lastWriteTime = written;
  return true;
}

} // namespace util

// Main implementation.

int APIENTRY _tWinMain(_In_ HINSTANCE hInstance,
                     _In_opt_ HINSTANCE hPrevInstance,
                     _In_ LPTSTR    lpCmdLine,
                     _In_ int       nCmdShow)
{
  UNREFERENCED_PARAMETER(hPrevInstance);
  UNREFERENCED_PARAMETER(lpCmdLine);

  int argc;
  std::shared_ptr<LPWSTR> argv(::CommandLineToArgvW(lpCmdLine, &argc), &::LocalFree);
  if (!argv) {
    OutputDebugString(L"Failed to parse arguments.\n");
    return Exit_InvalidArguments;
  }

  if (argc < 4)  {
    OutputDebugString(L"Not enough arguments.\n");
    return Exit_InvalidArguments;
  }

  const tstring installerPath = argv.get()[0];
  const tstring iniPath = argv.get()[1];
  const tstring targetPath = argv.get()[2];
  std::wofstream logFile(argv.get()[3]);

  if (!logFile.good()) {
    OutputDebugString(L"Failed to open launcher log file.\n");;
    return Exit_OpenLauncherLogFailed;
  }

  return RunInstaller(installerPath, iniPath, targetPath, logFile);
}

ExitCode RunElevated(const tstring& file, const tstring& parameters,
                     std::wofstream& logFile, util::AutoHandle& handle)
{
  std::vector<TCHAR> fileCopy(file.begin(), file.end());
  fileCopy.push_back('\0');
  std::vector<TCHAR> parametersCopy(parameters.begin(), parameters.end());
  parametersCopy.push_back('\0');

  SHELLEXECUTEINFO sinfo;
  memset(&sinfo, 0, sizeof(SHELLEXECUTEINFO));
  sinfo.cbSize = sizeof(SHELLEXECUTEINFO);
  sinfo.fMask = SEE_MASK_FLAG_NO_UI |
    SEE_MASK_FLAG_DDEWAIT |
    SEE_MASK_NOCLOSEPROCESS;
  sinfo.hwnd = nullptr;
  sinfo.lpFile = &fileCopy[0];
  sinfo.lpParameters = &parametersCopy[0];
  sinfo.lpVerb = L"runas";
  sinfo.nShow = SW_SHOWNORMAL;

  OutputDebugString(L"Requesting Elevation\n");
  BOOL result = ::ShellExecuteEx(&sinfo);
  DWORD dwErr = ::GetLastError();

  if (!result) {
    OutputDebugString(L"Elevation failed\n");
    if (dwErr == ERROR_CANCELLED) {
      logFile << Exit_ElevationCancelled << "\n";
      logFile << L"Elevation was cancelled.\n";
      return Exit_ElevationCancelled;
    }

    logFile << Exit_ElevationFailed << "\n";
    logFile << "Elevation failed - GetLastError() returned " << dwErr << "\n";
    return Exit_ElevationFailed;
  }

  handle = sinfo.hProcess;
  return Exit_Success;
}

ExitCode RunNonElevated(const tstring& file, const tstring& parameters,
                        std::wofstream& logFile, util::AutoHandle& handle)
{
  tostringstream oss;
  oss << L"\"" << file << "\" " << parameters;
  tstring temp = oss.str();
  std::vector<TCHAR> cmdLine(temp.begin(), temp.end());
  cmdLine.push_back('\0');

  STARTUPINFO startupInfo;
  PROCESS_INFORMATION processInformation;

  ::ZeroMemory(&startupInfo, sizeof(startupInfo));

  BOOL bret = ::CreateProcess(NULL, &cmdLine[0],
                              NULL, NULL, FALSE, 0, NULL, NULL,
                              &startupInfo, &processInformation);
  if (!bret) {
    logFile << Exit_InstallationFailed << "\n";
    logFile << L"GetLastError() returns " << ::GetLastError() << "\n";
    return Exit_InstallationFailed;
  }

  ::CloseHandle(processInformation.hThread);
  handle = processInformation.hProcess;
  return Exit_Success;
}

ExitCode RunInstaller(const tstring& installerPath,
                      const tstring& iniPath,
                      const tstring& targetPath,
                      std::wofstream& logFile)
{
  // Get install.log timestamp. We parse that file to determine whether the installer succeeded.
  // If an old version exists and the installer fails to write one, we incorrectly declare success.

  tstring installerLogPath;
  if (!util::AppendPath(targetPath, L"install.log", installerLogPath))  {
    OutputDebugString(L"Failed to build path for install.log file.\n");
    logFile << Exit_AccessingLogFailed << "\n";
    logFile << L"Failed to build path for install.log file.\n";
    return Exit_AccessingLogFailed;
  }

  FILETIME installerLogWrittenPreInstall;
  ::ZeroMemory(&installerLogWrittenPreInstall, sizeof(installerLogWrittenPreInstall));
  util::GetFileLastWriteTime(installerLogPath, installerLogWrittenPreInstall);

  // Try running installer elevated

  const bool vistaOrHigher = util::IsVistaOrHigher();
  const bool canElevate = util::CanUserElevate();

  bool elevated = false;
  if (vistaOrHigher) {
    const bool canWriteToInstallDir = util::CanWriteToInstallDir(targetPath);
    elevated = canElevate || !canWriteToInstallDir;

    tostringstream oss;
    oss << "CanWriteToInstallDir=" << std::boolalpha << canWriteToInstallDir << "\n";
    OutputDebugString(oss.str().c_str());
  }

  {
    tostringstream oss;
    oss << L"elevated=" << std::boolalpha << elevated
        << L", IsVistaOrHigher=" << vistaOrHigher
        << L", CanUserElevate=" << canElevate << " \n";
    OutputDebugString(oss.str().c_str());
  }

  const tstring parameters = L"/INI=" + iniPath;
  util::AutoHandle process;
  ExitCode code;

  if (elevated) {
    code = RunElevated(installerPath, parameters, logFile, process);
  } else {
    code = RunNonElevated(installerPath, parameters, logFile, process);
  }
  if (code != Exit_Success) {
    return code;
  }

  OutputDebugString(L"Waiting for process\n");
  WaitForSingleObject(process.get(), INFINITE);
  OutputDebugString(L"Process completed\n");

  // Check unarchiving process exit code

  DWORD exitCode;
  BOOL result = ::GetExitCodeProcess(process.get(), &exitCode);

  if (result) {
    tostringstream oss;
    oss << "Process exit code: " << exitCode << "\n";
    OutputDebugString(oss.str().c_str());

    if (exitCode != 0) {
      OutputDebugString(L"Failed to unarchive.\n");
      logFile << Exit_UnarchivingFailed << "\n";
      logFile << L"Unarchiving failed.\n";
      return Exit_UnarchivingFailed;
    }
  }

  // Check if we actually got a new or modified install.log file.

  FILETIME installerLogWrittenPostInstall;
  ::ZeroMemory(&installerLogWrittenPostInstall, sizeof(installerLogWrittenPostInstall));
  util::GetFileLastWriteTime(installerLogPath, installerLogWrittenPostInstall);

  if (::CompareFileTime(&installerLogWrittenPreInstall, &installerLogWrittenPostInstall) != -1)  {
    OutputDebugString(L"install.log file was not modified or does not exist.\n");
    logFile << Exit_AccessingLogFailed << "\n";
    logFile << L"install.log file was not modified or does not exist.\n";
    return Exit_AccessingLogFailed;
  }

  // Check installer log file

  std::wifstream installerLogFile(installerLogPath);
  if (!installerLogFile.good()) {
    OutputDebugString(L"Failed to open install.log file.\n");
    logFile << Exit_AccessingLogFailed << "\n";
    logFile << L"Failed to open install.log file.\n";
    return Exit_AccessingLogFailed;
  }

  installerLogFile.imbue(std::locale(installerLogFile.getloc(),
    new std::codecvt_utf16<wchar_t, 0x10ffff, std::consume_header>));

  const std::array<tstring, 2> successLogLines = {
    L"Added Registry Key: HKLM | Software\\Microsoft\\MediaPlayer\\ShimInclusionList\\plugin-container.exe",
    L"Added Registry Key: HKCU | Software\\Microsoft\\MediaPlayer\\ShimInclusionList\\plugin-container.exe",
  };

  tstring line, nonemptyLine;

  while (std::getline(installerLogFile, line))
  {
    tostringstream oss;
    oss << L"line: " << line << L"\n";
    //OutputDebugString(oss.str().c_str());

    util::Trim(line);
    if (line.empty()) {
      continue;
    }

    if (std::find(successLogLines.begin(), successLogLines.end(), line) != successLogLines.end()) {
      OutputDebugString(L"Found success log line\n");
      logFile << Exit_Success << "\n";
      return Exit_Success;
    }

    std::swap(line, nonemptyLine);
  }

  tostringstream oss;
  oss << L"Installation failed. Details:" << nonemptyLine << "\n";
  OutputDebugString(oss.str().c_str());

  logFile << Exit_InstallationFailed << "\n";
  logFile << L"Details: " << nonemptyLine << "\n";
  return Exit_InstallationFailed;
}