summaryrefslogtreecommitdiffstats
path: root/chrome/installer
diff options
context:
space:
mode:
authorkuchhal@chromium.org <kuchhal@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2008-12-16 00:42:48 +0000
committerkuchhal@chromium.org <kuchhal@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2008-12-16 00:42:48 +0000
commit66a4ff2f884413f935ae72a8e505c1f756381fc4 (patch)
tree7a470d6d204420f1e67221100e93da8539d256b3 /chrome/installer
parent8d2335f413f55bd140a3dad2286620a44df84ba9 (diff)
downloadchromium_src-66a4ff2f884413f935ae72a8e505c1f756381fc4.zip
chromium_src-66a4ff2f884413f935ae72a8e505c1f756381fc4.tar.gz
chromium_src-66a4ff2f884413f935ae72a8e505c1f756381fc4.tar.bz2
* Launch Chrome in non-elevated mode.
Currently Google Update LaunchCmdLine launches command with the callers token. To launch non-elevated we try to get the token of explorer.exe and impersonate it (same as what Google Update does in another place). Also fix a bug in Compatibility check function. We should exit if we fail to load advapi32.dll and not if we succeed. I am still doing testing with various builds of Google Update. Review URL: http://codereview.chromium.org/12970 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@7023 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'chrome/installer')
-rwxr-xr-xchrome/installer/gcapi/gcapi.cc257
-rwxr-xr-xchrome/installer/gcapi/gcapi.h8
-rw-r--r--chrome/installer/gcapi/gcapi.vsprops8
-rwxr-xr-xchrome/installer/gcapi/gcapi_dll.vcproj4
-rwxr-xr-xchrome/installer/gcapi/gcapi_lib.vcproj4
-rwxr-xr-xchrome/installer/gcapi/gcapi_test.cc1
6 files changed, 196 insertions, 86 deletions
diff --git a/chrome/installer/gcapi/gcapi.cc b/chrome/installer/gcapi/gcapi.cc
index 41e5d11..6f6c600 100755
--- a/chrome/installer/gcapi/gcapi.cc
+++ b/chrome/installer/gcapi/gcapi.cc
@@ -4,13 +4,20 @@
#include "chrome/installer/gcapi/gcapi.h"
+#include <atlbase.h>
+#include <atlcom.h>
#include <windows.h>
+#include <sddl.h>
#include <stdlib.h>
#include <strsafe.h>
+#include <tlhelp32.h>
+
+#include "google_update_idl.h"
namespace {
-const wchar_t kChromeRegKey[] = L"Software\\Google\\Update\\Clients\\{8A69D345-D564-463c-AFF1-A69D9E530F96}";
+const wchar_t kChromeRegClientsKey[] = L"Software\\Google\\Update\\Clients\\{8A69D345-D564-463c-AFF1-A69D9E530F96}";
+const wchar_t kChromeRegClientStateKey[] = L"Software\\Google\\Update\\ClientState\\{8A69D345-D564-463c-AFF1-A69D9E530F96}";
const wchar_t kChromeRegLaunchCmd[] = L"InstallerSuccessLaunchCmdLine";
const wchar_t kChromeRegLastLaunchCmd[] = L"LastInstallerSuccessLaunchCmdLine";
const wchar_t kChromeRegVersion[] = L"pv";
@@ -20,7 +27,7 @@ const wchar_t kNoChromeOfferUntil[] = L"SOFTWARE\\Google\\No Chrome Offer Until"
// equal or less than today's date represented in YYYYMMDD form.
void CleanUpRegistryValues() {
HKEY key = NULL;
- if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, kNoChromeOfferUntil,
+ if (::RegOpenKeyEx(HKEY_LOCAL_MACHINE, kNoChromeOfferUntil,
0, KEY_ALL_ACCESS, &key) != ERROR_SUCCESS)
return;
@@ -34,12 +41,12 @@ void CleanUpRegistryValues() {
// First, remove any value whose type is not DWORD
index = 0;
value_len = 0;
- while (RegEnumValue(key, index, value_name, &value_name_len,
+ while (::RegEnumValue(key, index, value_name, &value_name_len,
NULL, &value_type, NULL, &value_len) == ERROR_SUCCESS) {
if (value_type == REG_DWORD)
index++;
else
- RegDeleteValue(key, value_name);
+ ::RegDeleteValue(key, value_name);
value_name_len = _countof(value_name);
value_type = REG_DWORD;
@@ -48,18 +55,18 @@ void CleanUpRegistryValues() {
// Get today's date, and format it as YYYYMMDD numeric value
SYSTEMTIME now;
- GetLocalTime(&now);
+ ::GetLocalTime(&now);
DWORD expiration_date = now.wYear * 10000 + now.wMonth * 100 + now.wDay;
// Remove any DWORD value smaller than the number represent the
// expiration date (YYYYMMDD)
index = 0;
- while (RegEnumValue(key, index, value_name, &value_name_len,
+ while (::RegEnumValue(key, index, value_name, &value_name_len,
NULL, &value_type, (LPBYTE) &value_data, &value_len) == ERROR_SUCCESS) {
if (value_type == REG_DWORD && value_data > expiration_date)
index++; // move on to next value
else
- RegDeleteValue(key, value_name); // delete this value
+ ::RegDeleteValue(key, value_name); // delete this value
value_name_len = _countof(value_name);
value_type = REG_DWORD;
@@ -67,7 +74,7 @@ void CleanUpRegistryValues() {
value_len = sizeof(DWORD);
}
- RegCloseKey(key);
+ ::RegCloseKey(key);
}
// Return the company name specified in the file version info resource.
@@ -76,21 +83,21 @@ bool GetCompanyName(const wchar_t* filename, wchar_t* buffer, DWORD out_len) {
DWORD handle = 0;
DWORD buffer_size = 0;
- buffer_size = GetFileVersionInfoSize(filename, &handle);
+ buffer_size = ::GetFileVersionInfoSize(filename, &handle);
// Cannot stats the file or our buffer size is too small (very unlikely)
if (buffer_size == 0 || buffer_size > _countof(file_version_info))
return false;
buffer_size = _countof(file_version_info);
memset(file_version_info, 0, buffer_size);
- if (!GetFileVersionInfo(filename, handle, buffer_size, file_version_info))
+ if (!::GetFileVersionInfo(filename, handle, buffer_size, file_version_info))
return false;
DWORD data_len = 0;
LPVOID data = NULL;
// Retrieve the language and codepage code if exists.
buffer_size = 0;
- if (!VerQueryValue(file_version_info, TEXT("\\VarFileInfo\\Translation"),
+ if (!::VerQueryValue(file_version_info, TEXT("\\VarFileInfo\\Translation"),
reinterpret_cast<LPVOID *>(&data), reinterpret_cast<UINT *>(&data_len)))
return false;
if (data_len != 4)
@@ -107,7 +114,7 @@ bool GetCompanyName(const wchar_t* filename, wchar_t* buffer, DWORD out_len) {
(lang & 0xff0000)>>16);
data_len = 0;
- if (!VerQueryValue(file_version_info, info_name,
+ if (!::VerQueryValue(file_version_info, info_name,
reinterpret_cast<LPVOID *>(&data), reinterpret_cast<UINT *>(&data_len)))
return false;
if (data_len <= 0 || data_len >= out_len)
@@ -127,7 +134,7 @@ bool CanReOfferChrome() {
// If we cannot retrieve the version info of the executable or company
// name, we allow the Chrome to be offered because there is no past
// history to be found.
- if (GetModuleFileName(NULL, filename, MAX_PATH) == 0)
+ if (::GetModuleFileName(NULL, filename, MAX_PATH) == 0)
return true;
if (!GetCompanyName(filename, company, sizeof(company)))
return true;
@@ -135,11 +142,11 @@ bool CanReOfferChrome() {
bool can_re_offer = true;
DWORD disposition = 0;
HKEY key = NULL;
- if (RegCreateKeyEx(HKEY_LOCAL_MACHINE, kNoChromeOfferUntil,
+ if (::RegCreateKeyEx(HKEY_LOCAL_MACHINE, kNoChromeOfferUntil,
0, NULL, REG_OPTION_NON_VOLATILE, KEY_READ | KEY_WRITE,
NULL, &key, &disposition) == ERROR_SUCCESS) {
// Cannot re-offer, if the timer already exists and is not expired yet
- if (RegQueryValueEx(key, company, 0, 0, 0, 0) == ERROR_SUCCESS) {
+ if (::RegQueryValueEx(key, company, 0, 0, 0, 0) == ERROR_SUCCESS) {
// The expired timers were already removed in CleanUpRegistryValues.
// So if the key is not found, we can offer the Chrome.
can_re_offer = false;
@@ -147,17 +154,17 @@ bool CanReOfferChrome() {
// Set expiration date for offer as six months from today,
// represented as a YYYYMMDD numeric value
SYSTEMTIME timer;
- GetLocalTime(&timer);
+ ::GetLocalTime(&timer);
timer.wMonth = timer.wMonth + 6;
if (timer.wMonth > 12) {
timer.wMonth = timer.wMonth - 12;
timer.wYear = timer.wYear + 1;
}
DWORD value = timer.wYear * 10000 + timer.wMonth * 100 + timer.wDay;
- RegSetValueEx(key, company, 0, REG_DWORD, (LPBYTE)&value, sizeof(DWORD));
+ ::RegSetValueEx(key, company, 0, REG_DWORD, (LPBYTE)&value, sizeof(DWORD));
}
- RegCloseKey(key);
+ ::RegCloseKey(key);
}
return can_re_offer;
@@ -183,7 +190,7 @@ bool ReadValueFromRegistry(HKEY root_key, const wchar_t *sub_key,
bool IsChromeInstalled(HKEY root_key) {
wchar_t version[64];
size_t size = _countof(version);
- if (ReadValueFromRegistry(root_key, kChromeRegKey, kChromeRegVersion,
+ if (ReadValueFromRegistry(root_key, kChromeRegClientsKey, kChromeRegVersion,
version, &size))
return true;
return false;
@@ -210,45 +217,23 @@ bool IsWinXPSp1OrLater(bool* is_vista_or_later) {
return false; // Windows 2000, WinXP no Service Pack
}
+// Note this function should not be called on old Windows versions where these
+// Windows API are not available. We always invoke this function after checking
+// that current OS is Vista or later.
bool VerifyAdminGroup() {
- typedef BOOL (WINAPI *ALLOCATEANDINITIALIZESIDPROC)(
- PSID_IDENTIFIER_AUTHORITY pIdentifierAuthority, BYTE nSubAuthorityCount,
- DWORD nSubAuthority0, DWORD nSubAuthority1, DWORD nSubAuthority2,
- DWORD nSubAuthority3, DWORD nSubAuthority4, DWORD nSubAuthority5,
- DWORD nSubAuthority6, DWORD nSubAuthority7, PSID *pSid);
- typedef BOOL (WINAPI *CHECKTOKENMEMBERSHIPPROC)(HANDLE TokenHandle,
- PSID SidToCheck,
- PBOOL IsMember);
- typedef PVOID (WINAPI *FREESIDPROC)(PSID pSid);
- // Load our admin-checking functions dynamically.
- HMODULE advapi_library = LoadLibrary(L"advapi32.dll");
- if (advapi_library != NULL)
- return false;
-
- ALLOCATEANDINITIALIZESIDPROC allocSid =
- reinterpret_cast<ALLOCATEANDINITIALIZESIDPROC>(GetProcAddress(
- advapi_library, "AllocateAndInitializeSid"));
- CHECKTOKENMEMBERSHIPPROC checkToken =
- reinterpret_cast<CHECKTOKENMEMBERSHIPPROC>(GetProcAddress(
- advapi_library, "CheckTokenMembership"));
- FREESIDPROC freeSid =
- reinterpret_cast<FREESIDPROC>(GetProcAddress(
- advapi_library, "FreeSid"));
- bool result = false;
- if (allocSid != NULL && checkToken != NULL && freeSid != NULL) {
- SID_IDENTIFIER_AUTHORITY NtAuthority = SECURITY_NT_AUTHORITY;
- PSID Group;
- BOOL check = allocSid(&NtAuthority, 2, SECURITY_BUILTIN_DOMAIN_RID,
- DOMAIN_ALIAS_RID_ADMINS, 0, 0, 0, 0, 0, 0, &Group);
- if (check) {
- if (!checkToken(NULL, Group, &check))
- check = FALSE;
- freeSid(Group);
- }
- result = !!check;
+ SID_IDENTIFIER_AUTHORITY NtAuthority = SECURITY_NT_AUTHORITY;
+ PSID Group;
+ BOOL check = ::AllocateAndInitializeSid(&NtAuthority, 2,
+ SECURITY_BUILTIN_DOMAIN_RID,
+ DOMAIN_ALIAS_RID_ADMINS, 0, 0, 0,
+ 0, 0, 0,
+ &Group);
+ if (check) {
+ if (!::CheckTokenMembership(NULL, Group, &check))
+ check = FALSE;
}
- FreeLibrary(advapi_library);
- return result;
+ ::FreeSid(Group);
+ return (check == TRUE);
}
bool VerifyHKLMAccess(const wchar_t* sub_key) {
@@ -258,10 +243,10 @@ bool VerifyHKLMAccess(const wchar_t* sub_key) {
DWORD disposition = 0;
HKEY key = NULL;
- if (RegCreateKeyEx(root, sub_key, 0, NULL,
- REG_OPTION_NON_VOLATILE, KEY_READ | KEY_WRITE, NULL,
- &key, &disposition) == ERROR_SUCCESS) {
- if (RegSetValueEx(key, str, 0, REG_SZ, (LPBYTE)str,
+ if (::RegCreateKeyEx(root, sub_key, 0, NULL,
+ REG_OPTION_NON_VOLATILE, KEY_READ | KEY_WRITE, NULL,
+ &key, &disposition) == ERROR_SUCCESS) {
+ if (::RegSetValueEx(key, str, 0, REG_SZ, (LPBYTE)str,
(DWORD)lstrlen(str)) == ERROR_SUCCESS) {
result = true;
RegDeleteValue(key, str);
@@ -276,6 +261,59 @@ bool VerifyHKLMAccess(const wchar_t* sub_key) {
return result;
}
+
+bool IsRunningElevated() {
+ // This method should be called only for Vista or later.
+ bool is_vista_or_later = false;
+ IsWinXPSp1OrLater(&is_vista_or_later);
+ if (!is_vista_or_later || !VerifyAdminGroup())
+ return false;
+
+ HANDLE process_token;
+ if (!::OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &process_token))
+ return false;
+
+ TOKEN_ELEVATION_TYPE elevation_type = TokenElevationTypeDefault;
+ DWORD size_returned = 0;
+ if (!::GetTokenInformation(process_token, TokenElevationType,
+ &elevation_type, sizeof(elevation_type),
+ &size_returned)) {
+ ::CloseHandle(process_token);
+ return false;
+ }
+
+ ::CloseHandle(process_token);
+ return (elevation_type == TokenElevationTypeFull);
+}
+
+bool GetUserIdForProcess(size_t pid, wchar_t** user_sid) {
+ HANDLE process_handle = ::OpenProcess(PROCESS_QUERY_INFORMATION, TRUE, pid);
+ if (process_handle == NULL)
+ return false;
+
+ HANDLE process_token;
+ bool result = false;
+ if (::OpenProcessToken(process_handle, TOKEN_QUERY, &process_token)) {
+ DWORD size = 0;
+ ::GetTokenInformation(process_token, TokenUser, NULL, 0, &size);
+ if (::GetLastError() == ERROR_INSUFFICIENT_BUFFER ||
+ ::GetLastError() == ERROR_SUCCESS) {
+ DWORD actual_size = 0;
+ BYTE* token_user = new BYTE[size];
+ if ((::GetTokenInformation(process_token, TokenUser, token_user, size,
+ &actual_size)) &&
+ (actual_size <= size)) {
+ PSID sid = reinterpret_cast<TOKEN_USER*>(token_user)->User.Sid;
+ if (::ConvertSidToStringSid(sid, user_sid))
+ result = true;
+ }
+ delete[] token_user;
+ }
+ ::CloseHandle(process_token);
+ }
+ ::CloseHandle(process_handle);
+ return result;
+}
} // namespace
#pragma comment(linker, "/EXPORT:GoogleChromeCompatibilityCheck=_GoogleChromeCompatibilityCheck@4,PRIVATE")
@@ -293,9 +331,11 @@ DLLEXPORT BOOL __stdcall GoogleChromeCompatibilityCheck(DWORD *reasons) {
if (IsChromeInstalled(HKEY_CURRENT_USER))
local_reasons |= GCCC_ERROR_USERLEVELALREADYPRESENT;
- if (!VerifyHKLMAccess(kChromeRegKey)) {
+ if (!VerifyHKLMAccess(kChromeRegClientsKey)) {
local_reasons |= GCCC_ERROR_ACCESSDENIED;
} else if (is_vista_or_later && !VerifyAdminGroup()) {
+ // For Vista or later check for elevation since even for admin user we could
+ // be running in non-elevated mode. We require integrity level High.
local_reasons |= GCCC_ERROR_INTEGRITYLEVEL;
}
@@ -312,33 +352,94 @@ DLLEXPORT BOOL __stdcall GoogleChromeCompatibilityCheck(DWORD *reasons) {
return (*reasons == 0);
}
-#pragma comment(linker, "/EXPORT:LaunchGoogleChrome=_LaunchGoogleChrome@4,PRIVATE")
-DLLEXPORT BOOL __stdcall LaunchGoogleChrome(HANDLE *proc_handle) {
+#pragma comment(linker, "/EXPORT:LaunchGoogleChrome=_LaunchGoogleChrome@0,PRIVATE")
+DLLEXPORT BOOL __stdcall LaunchGoogleChrome() {
wchar_t launch_cmd[MAX_PATH];
size_t size = _countof(launch_cmd);
- if (!ReadValueFromRegistry(HKEY_LOCAL_MACHINE, kChromeRegKey,
+ if (!ReadValueFromRegistry(HKEY_LOCAL_MACHINE, kChromeRegClientStateKey,
kChromeRegLastLaunchCmd, launch_cmd, &size)) {
size = _countof(launch_cmd);
- if (!ReadValueFromRegistry(HKEY_LOCAL_MACHINE, kChromeRegKey,
+ if (!ReadValueFromRegistry(HKEY_LOCAL_MACHINE, kChromeRegClientStateKey,
kChromeRegLaunchCmd, launch_cmd, &size)) {
return false;
}
}
- STARTUPINFOW si = {sizeof(si)};
- PROCESS_INFORMATION pi = {0};
- if (!CreateProcess(NULL, const_cast<wchar_t*>(launch_cmd), NULL, NULL,
- FALSE, 0, NULL, NULL, &si, &pi))
+ HRESULT hr = ::CoInitializeEx(NULL, COINIT_APARTMENTTHREADED);
+ if (hr != S_OK) {
+ if (hr == S_FALSE)
+ ::CoUninitialize();
+ return false;
+ }
+
+ if (::CoInitializeSecurity(NULL, -1, NULL, NULL,
+ RPC_C_AUTHN_LEVEL_PKT_PRIVACY,
+ RPC_C_IMP_LEVEL_IDENTIFY, NULL,
+ EOAC_DYNAMIC_CLOAKING, NULL) != S_OK) {
+ ::CoUninitialize();
return false;
+ }
- // Handles must be closed or they will leak
- CloseHandle(pi.hThread);
+ bool impersonation_success = false;
+ if (IsRunningElevated()) {
+ wchar_t* curr_proc_sid;
+ if (!GetUserIdForProcess(GetCurrentProcessId(), &curr_proc_sid)) {
+ ::CoUninitialize();
+ return false;
+ }
- // If the caller wants the process handle, we won't close it.
- if (proc_handle) {
- *proc_handle = pi.hProcess;
- } else {
- CloseHandle(pi.hProcess);
+ DWORD pid = 0;
+ ::GetWindowThreadProcessId(::GetShellWindow(), &pid);
+ if (pid <= 0) {
+ ::LocalFree(curr_proc_sid);
+ ::CoUninitialize();
+ return false;
+ }
+
+ wchar_t* exp_proc_sid;
+ if (GetUserIdForProcess(pid, &exp_proc_sid)) {
+ if (_wcsicmp(curr_proc_sid, exp_proc_sid) == 0) {
+ HANDLE process_handle = ::OpenProcess(
+ PROCESS_DUP_HANDLE | PROCESS_QUERY_INFORMATION, TRUE, pid);
+ if (process_handle != NULL) {
+ HANDLE process_token;
+ HANDLE user_token;
+ if (::OpenProcessToken(process_handle, TOKEN_DUPLICATE | TOKEN_QUERY,
+ &process_token) &&
+ ::DuplicateTokenEx(process_token,
+ TOKEN_IMPERSONATE | TOKEN_QUERY |
+ TOKEN_ASSIGN_PRIMARY | TOKEN_DUPLICATE,
+ NULL, SecurityImpersonation,
+ TokenPrimary, &user_token) &&
+ (::ImpersonateLoggedOnUser(user_token) != 0)) {
+ impersonation_success = true;
+ }
+ ::CloseHandle(user_token);
+ ::CloseHandle(process_token);
+ ::CloseHandle(process_handle);
+ }
+ }
+ ::LocalFree(exp_proc_sid);
+ }
+
+ ::LocalFree(curr_proc_sid);
+ if (!impersonation_success) {
+ ::CoUninitialize();
+ return false;
+ }
}
- return true;
-} \ No newline at end of file
+
+ bool ret = false;
+ CComPtr<IProcessLauncher> ipl;
+ if (!FAILED(ipl.CoCreateInstance(__uuidof(ProcessLauncherClass), NULL,
+ CLSCTX_LOCAL_SERVER))) {
+ if (!FAILED(ipl->LaunchCmdLine(launch_cmd)))
+ ret = true;
+ ipl.Release();
+ }
+
+ if (impersonation_success)
+ ::RevertToSelf();
+ ::CoUninitialize();
+ return ret;
+}
diff --git a/chrome/installer/gcapi/gcapi.h b/chrome/installer/gcapi/gcapi.h
index da56b23..7848e40 100755
--- a/chrome/installer/gcapi/gcapi.h
+++ b/chrome/installer/gcapi/gcapi.h
@@ -23,10 +23,10 @@ extern "C" {
// for the reason, you can pass NULL for reasons.
DLLEXPORT BOOL __stdcall GoogleChromeCompatibilityCheck(DWORD *reasons);
-// This function launches Google Chrome after a successful install. If
-// proc_handle is not NULL, the process handle of the newly created process
-// will be returned.
-DLLEXPORT BOOL __stdcall LaunchGoogleChrome(HANDLE* proc_handle);
+// This function launches Google Chrome after a successful install. Make
+// sure COM library is NOT initalized before you call this function (so if
+// you called CoInitialize, call CoUninitialize before calling this function).
+DLLEXPORT BOOL __stdcall LaunchGoogleChrome();
// Funtion pointer type declarations to use with GetProcAddress.
typedef BOOL (__stdcall * GCCC_CompatibilityCheck)(DWORD *);
diff --git a/chrome/installer/gcapi/gcapi.vsprops b/chrome/installer/gcapi/gcapi.vsprops
new file mode 100644
index 0000000..d59c7a8
--- /dev/null
+++ b/chrome/installer/gcapi/gcapi.vsprops
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="Windows-1252"?>
+<VisualStudioPropertySheet
+ ProjectType="Visual C++"
+ Version="8.00"
+ Name="gcapi"
+ InheritedPropertySheets="$(SolutionDir)..\build\common.vsprops;$(SolutionDir)..\google_update\using_google_update.vsprops"
+ >
+</VisualStudioPropertySheet>
diff --git a/chrome/installer/gcapi/gcapi_dll.vcproj b/chrome/installer/gcapi/gcapi_dll.vcproj
index ac09788..2a15fa6 100755
--- a/chrome/installer/gcapi/gcapi_dll.vcproj
+++ b/chrome/installer/gcapi/gcapi_dll.vcproj
@@ -17,7 +17,7 @@
<Configuration
Name="Debug|Win32"
ConfigurationType="2"
- InheritedPropertySheets="$(SolutionDir)..\build\common.vsprops;$(SolutionDir)..\build\debug.vsprops"
+ InheritedPropertySheets=".\gcapi.vsprops;$(SolutionDir)..\build\debug.vsprops"
>
<Tool
Name="VCCLCompilerTool"
@@ -29,7 +29,7 @@
<Configuration
Name="Release|Win32"
ConfigurationType="2"
- InheritedPropertySheets="$(SolutionDir)..\build\common.vsprops;$(SolutionDir)..\build\release.vsprops"
+ InheritedPropertySheets=".\gcapi.vsprops;$(SolutionDir)..\build\release.vsprops"
>
<Tool
Name="VCCLCompilerTool"
diff --git a/chrome/installer/gcapi/gcapi_lib.vcproj b/chrome/installer/gcapi/gcapi_lib.vcproj
index 2524db7..75da6c5 100755
--- a/chrome/installer/gcapi/gcapi_lib.vcproj
+++ b/chrome/installer/gcapi/gcapi_lib.vcproj
@@ -17,7 +17,7 @@
<Configuration
Name="Debug|Win32"
ConfigurationType="4"
- InheritedPropertySheets="$(SolutionDir)..\build\common.vsprops;$(SolutionDir)..\build\debug.vsprops"
+ InheritedPropertySheets=".\gcapi.vsprops;$(SolutionDir)..\build\debug.vsprops"
>
<Tool
Name="VCCLCompilerTool"
@@ -29,7 +29,7 @@
<Configuration
Name="Release|Win32"
ConfigurationType="4"
- InheritedPropertySheets="$(SolutionDir)..\build\common.vsprops;$(SolutionDir)..\build\release.vsprops"
+ InheritedPropertySheets=".\gcapi.vsprops;$(SolutionDir)..\build\release.vsprops"
>
<Tool
Name="VCCLCompilerTool"
diff --git a/chrome/installer/gcapi/gcapi_test.cc b/chrome/installer/gcapi/gcapi_test.cc
index 78939497..8d3608c 100755
--- a/chrome/installer/gcapi/gcapi_test.cc
+++ b/chrome/installer/gcapi/gcapi_test.cc
@@ -38,4 +38,5 @@ void call_dynamically() {
int main(int argc, char* argv[]) {
call_dynamically();
call_statically();
+ printf("LaunchChrome returned %d.\n", LaunchGoogleChrome());
}