1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
6 #include <setupapi.h> // Must be included after windows.h
9 #include "base/at_exit.h"
10 #include "base/command_line.h"
11 #include "base/file_util.h"
12 #include "base/file_version_info_win.h"
13 #include "base/logging.h"
14 #include "base/path_service.h"
15 #include "base/process.h"
16 #include "base/process_util.h"
17 #include "base/string16.h"
18 #include "base/win/registry.h"
19 #include "base/win/scoped_handle.h"
20 #include "base/win/windows_version.h"
21 #include "cloud_print/virtual_driver/win/virtual_driver_consts.h"
22 #include "cloud_print/virtual_driver/win/virtual_driver_helpers.h"
23 #include "grit/virtual_driver_setup_resources.h"
25 #include <strsafe.h> // Must be after base headers to avoid deprecation
29 const wchar_t kVersionKey
[] = L
"pv";
30 const wchar_t kNameKey
[] = L
"name";
31 const wchar_t kNameValue
[] = L
"GCP Virtual Driver";
32 const wchar_t kPpdName
[] = L
"gcp-driver.ppd";
33 const wchar_t kDriverName
[] = L
"MXDWDRV.DLL";
34 const wchar_t kUiDriverName
[] = L
"PS5UI.DLL";
35 const wchar_t kHelpName
[] = L
"PSCRIPT.HLP";
36 const wchar_t* kDependencyList
[] = {kDriverName
, kUiDriverName
, kHelpName
};
37 const wchar_t kUninstallRegistry
[] =
38 L
"Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\"
39 L
"{74AA24E0-AC50-4B28-BA46-9CF05467C9B7}";
40 const wchar_t kInstallerName
[] = L
"virtual_driver_setup.exe";
41 const wchar_t kGcpUrl
[] = L
"http://www.google.com/cloudprint";
44 base::win::RegKey key
;
45 if (key
.Create(HKEY_LOCAL_MACHINE
, cloud_print::kKeyLocation
,
46 KEY_SET_VALUE
) != ERROR_SUCCESS
) {
47 LOG(ERROR
) << "Unable to open key";
50 // Get the version from the resource file.
51 string16 version_string
;
52 scoped_ptr
<FileVersionInfo
> version_info(
53 FileVersionInfo::CreateFileVersionInfoForCurrentModule());
55 if (version_info
.get()) {
56 FileVersionInfoWin
* version_info_win
=
57 static_cast<FileVersionInfoWin
*>(version_info
.get());
58 version_string
= version_info_win
->product_version();
60 LOG(ERROR
) << "Unable to get version string";
61 // Use a random version string so that Omaha has something to go by.
62 version_string
= L
"0.0.0.99";
65 if (key
.WriteValue(kVersionKey
, version_string
.c_str()) != ERROR_SUCCESS
||
66 key
.WriteValue(kNameKey
, kNameValue
) != ERROR_SUCCESS
) {
67 LOG(ERROR
) << "Unable to set registry keys";
71 void DeleteOmahaKeys() {
72 base::win::RegKey key
;
73 if (key
.Open(HKEY_LOCAL_MACHINE
, cloud_print::kKeyLocation
,
74 DELETE
) != ERROR_SUCCESS
) {
75 LOG(ERROR
) << "Unable to open key to delete";
78 if (key
.DeleteKey(L
"") != ERROR_SUCCESS
) {
79 LOG(ERROR
) << "Unable to delete key";
83 HRESULT
GetNativeSystemPath(FilePath
* path
) {
84 if (cloud_print::IsSystem64Bit()) {
85 if (!PathService::Get(base::DIR_WINDOWS
, path
)) {
86 return ERROR_PATH_NOT_FOUND
;
88 // Sysnative will bypass filesystem redirection and give us
89 // the location of the 64bit system32 from a 32 bit process.
90 *path
= path
->Append(L
"sysnative");
92 if (!PathService::Get(base::DIR_SYSTEM
, path
)) {
93 LOG(ERROR
) << "Unable to get system path.";
94 return ERROR_PATH_NOT_FOUND
;
100 HRESULT
GetPortMonitorTargetPath(FilePath
* path
) {
101 HRESULT result
= GetNativeSystemPath(path
);
102 if (SUCCEEDED(result
))
103 *path
= path
->Append(cloud_print::GetPortMonitorDllName());
107 HRESULT
GetRegsvr32Path(FilePath
* path
) {
108 HRESULT result
= GetNativeSystemPath(path
);
109 if (SUCCEEDED(result
))
110 *path
= path
->Append(FilePath(L
"regsvr32.exe"));
114 HRESULT
RegisterPortMonitor(bool install
, const FilePath
& install_path
) {
115 FilePath target_path
;
116 HRESULT result
= S_OK
;
117 result
= GetPortMonitorTargetPath(&target_path
);
118 if (!SUCCEEDED(result
)) {
119 LOG(ERROR
) << "Unable to get port monitor target path.";
123 source
= cloud_print::GetPortMonitorDllName();
124 FilePath source_path
= install_path
.Append(source
);
126 if (!file_util::CopyFileW(source_path
, target_path
)) {
127 LOG(ERROR
) << "Unable copy port monitor dll from " <<
128 source_path
.value() << " to " << target_path
.value();
129 return ERROR_ACCESS_DENIED
;
131 } else if (!file_util::PathExists(source_path
)) {
132 // Already removed. Just "succeed" silently.
136 FilePath regsvr32_path
;
137 result
= GetRegsvr32Path(®svr32_path
);
138 if (!SUCCEEDED(result
)) {
139 LOG(ERROR
) << "Can't find regsvr32.exe.";
143 CommandLine
command_line(regsvr32_path
);
144 command_line
.AppendArg("/s");
146 command_line
.AppendArg("/u");
150 if (!PathService::Get(base::DIR_SYSTEM
, &final_path
)) {
151 LOG(ERROR
) << "Unable to get system path.";
152 return ERROR_PATH_NOT_FOUND
;
154 final_path
= final_path
.Append(cloud_print::GetPortMonitorDllName());
155 command_line
.AppendArgPath(final_path
);
157 base::LaunchOptions options
;
158 HANDLE process_handle
;
160 if (!base::LaunchProcess(command_line
, options
, &process_handle
)) {
161 LOG(ERROR
) << "Unable to launch regsvr32.exe.";
162 return ERROR_NOT_SUPPORTED
;
164 base::win::ScopedHandle
scoped_process_handle(process_handle
);
166 DWORD exit_code
= S_OK
;
168 if (!GetExitCodeProcess(scoped_process_handle
, &exit_code
)) {
169 HRESULT result
= cloud_print::GetLastHResult();
170 LOG(ERROR
) << "Unable to get regsvr32.exe exit code.";
173 if (exit_code
!= 0) {
174 LOG(ERROR
) << "Regsvr32.exe failed with " << exit_code
;
175 return HRESULT_FROM_WIN32(exit_code
);
178 if (!file_util::Delete(target_path
, false)) {
179 LOG(ERROR
) << "Unable to delete " << target_path
.value();
180 return ERROR_ACCESS_DENIED
;
186 DWORDLONG
GetVersionNumber() {
187 DWORDLONG retval
= 0;
188 scoped_ptr
<FileVersionInfo
> version_info(
189 FileVersionInfo::CreateFileVersionInfoForCurrentModule());
190 if (version_info
.get()) {
191 FileVersionInfoWin
* version_info_win
=
192 static_cast<FileVersionInfoWin
*>(version_info
.get());
193 VS_FIXEDFILEINFO
* fixed_file_info
= version_info_win
->fixed_file_info();
194 retval
= fixed_file_info
->dwFileVersionMS
;
196 retval
|= fixed_file_info
->dwFileVersionLS
;
201 UINT CALLBACK
CabinetCallback(PVOID data
,
205 FilePath
* temp_path
= reinterpret_cast<FilePath
*>(data
);
206 if (notification
== SPFILENOTIFY_FILEINCABINET
) {
207 FILE_IN_CABINET_INFO
* info
=
208 reinterpret_cast<FILE_IN_CABINET_INFO
*>(param1
);
209 for (int i
= 0; i
< arraysize(kDependencyList
); i
++) {
210 FilePath
base_name(info
->NameInCabinet
);
211 base_name
= base_name
.BaseName();
212 if (FilePath::CompareEqualIgnoreCase(base_name
.value().c_str(),
213 kDependencyList
[i
])) {
214 StringCchCopy(info
->FullTargetName
, MAX_PATH
,
215 temp_path
->Append(kDependencyList
[i
]).value().c_str());
225 void ReadyPpdDependencies(const FilePath
& install_path
) {
226 base::win::Version version
= base::win::GetVersion();
227 if (version
>= base::win::VERSION_VISTA
) {
228 // GetCorePrinterDrivers and GetPrinterDriverPackagePath only exist on
229 // Vista and later. Winspool.drv must be delayloaded so these calls don't
230 // create problems on XP.
231 DWORD size
= MAX_PATH
;
232 wchar_t package_path
[MAX_PATH
] = {0};
233 CORE_PRINTER_DRIVER driver
;
234 GetCorePrinterDrivers(NULL
,
236 L
"{D20EA372-DD35-4950-9ED8-A6335AFE79F5}",
239 GetPrinterDriverPackagePath(NULL
,
246 SetupIterateCabinet(package_path
,
249 const_cast<FilePath
*>(&install_path
));
251 // PS driver files are in the sp3 cab.
252 FilePath package_path
;
253 PathService::Get(base::DIR_WINDOWS
, &package_path
);
254 package_path
= package_path
.Append(L
"Driver Cache\\i386\\sp3.cab");
255 SetupIterateCabinet(package_path
.value().c_str(),
258 const_cast<FilePath
*>(&install_path
));
260 // The XPS driver files are just sitting uncompressed in the driver cache.
262 PathService::Get(base::DIR_WINDOWS
, &xps_path
);
263 xps_path
= xps_path
.Append(L
"Driver Cache\\i386");
264 xps_path
= xps_path
.Append(kDriverName
);
265 file_util::CopyFile(xps_path
, install_path
.Append(kDriverName
));
269 HRESULT
InstallPpd(const FilePath
& install_path
) {
270 DRIVER_INFO_6 driver_info
= {0};
271 HRESULT result
= S_OK
;
273 // Set up paths for the files we depend on.
274 FilePath ppd_path
= install_path
.Append(kPpdName
);
275 FilePath xps_path
= install_path
.Append(kDriverName
);
276 FilePath ui_path
= install_path
.Append(kUiDriverName
);
277 FilePath ui_help_path
= install_path
.Append(kHelpName
);
278 ReadyPpdDependencies(install_path
);
279 // None of the print API structures likes constant strings even though they
280 // don't modify the string. const_casting is the cleanest option.
281 driver_info
.pDataFile
= const_cast<LPWSTR
>(ppd_path
.value().c_str());
282 driver_info
.pHelpFile
= const_cast<LPWSTR
>(ui_help_path
.value().c_str());
283 driver_info
.pDriverPath
= const_cast<LPWSTR
>(xps_path
.value().c_str());
284 driver_info
.pConfigFile
= const_cast<LPWSTR
>(ui_path
.value().c_str());
286 // Set up user visible strings.
287 string16 manufacturer
= cloud_print::LoadLocalString(IDS_GOOGLE
);
288 driver_info
.pszMfgName
= const_cast<LPWSTR
>(manufacturer
.c_str());
289 driver_info
.pszProvider
= const_cast<LPWSTR
>(manufacturer
.c_str());
290 driver_info
.pszOEMUrl
= const_cast<LPWSTR
>(kGcpUrl
);
291 driver_info
.dwlDriverVersion
= GetVersionNumber();
292 string16 driver_name
= cloud_print::LoadLocalString(IDS_DRIVER_NAME
);
293 driver_info
.pName
= const_cast<LPWSTR
>(driver_name
.c_str());
295 // Set up supported print system version. Must be 3.
296 driver_info
.cVersion
= 3;
298 if (!AddPrinterDriverEx(NULL
,
300 reinterpret_cast<BYTE
*>(&driver_info
),
301 APD_COPY_NEW_FILES
|APD_COPY_FROM_DIRECTORY
)) {
302 result
= cloud_print::GetLastHResult();
303 LOG(ERROR
) << "Unable to add printer driver";
308 HRESULT
UninstallPpd() {
310 string16 driver_name
= cloud_print::LoadLocalString(IDS_DRIVER_NAME
);
311 while (!DeletePrinterDriverEx(NULL
,
313 const_cast<LPWSTR
>(driver_name
.c_str()),
314 DPD_DELETE_UNUSED_FILES
,
316 if (GetLastError() == ERROR_UNKNOWN_PRINTER_DRIVER
) {
317 LOG(WARNING
) << "Print driver is already uninstalled.";
320 // After deleting the printer it can take a few seconds before
321 // the driver is free for deletion. Retry a few times before giving up.
322 LOG(WARNING
) << "Attempt to delete printer driver failed. Retrying.";
327 HRESULT result
= cloud_print::GetLastHResult();
328 LOG(ERROR
) << "Unable to delete printer driver.";
334 HRESULT
InstallPrinter(void) {
335 PRINTER_INFO_2 printer_info
= {0};
337 // None of the print API structures likes constant strings even though they
338 // don't modify the string. const_casting is the cleanest option.
339 string16 driver_name
= cloud_print::LoadLocalString(IDS_DRIVER_NAME
);
340 printer_info
.pDriverName
= const_cast<LPWSTR
>(driver_name
.c_str());
341 printer_info
.pPrinterName
= const_cast<LPWSTR
>(driver_name
.c_str());
342 printer_info
.pComment
= const_cast<LPWSTR
>(driver_name
.c_str());
343 printer_info
.pLocation
= const_cast<LPWSTR
>(kGcpUrl
);
345 printer_info
.pPortName
= const_cast<LPWSTR
>(cloud_print::kPortName
);
346 printer_info
.Attributes
= PRINTER_ATTRIBUTE_DIRECT
|PRINTER_ATTRIBUTE_LOCAL
;
347 printer_info
.pPrintProcessor
= L
"winprint";
348 HANDLE handle
= AddPrinter(NULL
, 2, reinterpret_cast<BYTE
*>(&printer_info
));
349 if (handle
== NULL
) {
350 HRESULT result
= cloud_print::GetLastHResult();
351 LOG(ERROR
) << "Unable to add printer";
354 ClosePrinter(handle
);
358 HRESULT
UninstallPrinter(void) {
359 HANDLE handle
= NULL
;
360 PRINTER_DEFAULTS printer_defaults
= {0};
361 printer_defaults
.DesiredAccess
= PRINTER_ALL_ACCESS
;
362 string16 driver_name
= cloud_print::LoadLocalString(IDS_DRIVER_NAME
);
363 if (!OpenPrinter(const_cast<LPWSTR
>(driver_name
.c_str()),
365 &printer_defaults
)) {
366 // If we can't open the printer, it was probably already removed.
367 LOG(WARNING
) << "Unable to open printer";
370 if (!DeletePrinter(handle
)) {
371 HRESULT result
= cloud_print::GetLastHResult();
372 LOG(ERROR
) << "Unable to delete printer";
373 ClosePrinter(handle
);
376 ClosePrinter(handle
);
380 void SetupUninstall(const FilePath
& install_path
) {
381 // Now write the Windows Uninstall entries
382 // Minimal error checking here since the install can contiunue
384 base::win::RegKey key
;
385 if (key
.Create(HKEY_LOCAL_MACHINE
, kUninstallRegistry
,
386 KEY_SET_VALUE
) != ERROR_SUCCESS
) {
387 LOG(ERROR
) << "Unable to open key";
390 CommandLine
uninstall_command(install_path
.Append(kInstallerName
));
391 uninstall_command
.AppendArg("--uninstall");
392 key
.WriteValue(L
"UninstallString",
393 uninstall_command
.GetCommandLineString().c_str());
394 key
.WriteValue(L
"InstallLocation", install_path
.value().c_str());
397 // Get the version resource.
398 scoped_ptr
<FileVersionInfo
> version_info(
399 FileVersionInfo::CreateFileVersionInfoForCurrentModule());
401 if (version_info
.get()) {
402 FileVersionInfoWin
* version_info_win
=
403 static_cast<FileVersionInfoWin
*>(version_info
.get());
404 key
.WriteValue(L
"DisplayVersion",
405 version_info_win
->file_version().c_str());
406 key
.WriteValue(L
"Publisher", version_info_win
->company_name().c_str());
408 LOG(ERROR
) << "Unable to get version string";
410 key
.WriteValue(L
"DisplayName",
411 cloud_print::LoadLocalString(IDS_DRIVER_NAME
).c_str());
412 key
.WriteValue(L
"NoModify", 1);
413 key
.WriteValue(L
"NoRepair", 1);
416 void CleanupUninstall() {
417 ::RegDeleteKey(HKEY_LOCAL_MACHINE
, kUninstallRegistry
);
420 bool IsOSSupported() {
421 // We don't support XP service pack 2 or older.
422 base::win::Version version
= base::win::GetVersion();
423 return (version
> base::win::VERSION_XP
) ||
424 ((version
== base::win::VERSION_XP
) &&
425 (base::win::OSInfo::GetInstance()->service_pack().major
>= 3));
428 HRESULT
InstallVirtualDriver(const FilePath
& install_path
) {
429 HRESULT result
= S_OK
;
431 if (!IsOSSupported()) {
432 LOG(ERROR
) << "Requires XP SP3 or later.";
433 return ERROR_OLD_WIN_VERSION
;
436 if (!file_util::CreateDirectory(install_path
)) {
437 LOG(ERROR
) << "Can't create install directory.";
438 return ERROR_ACCESS_DENIED
;
440 SetupUninstall(install_path
);
441 result
= RegisterPortMonitor(true, install_path
);
442 if (!SUCCEEDED(result
)) {
443 LOG(ERROR
) << "Unable to register port monitor.";
446 result
= InstallPpd(install_path
);
447 if (!SUCCEEDED(result
)) {
448 LOG(ERROR
) << "Unable to install Ppd.";
451 result
= InstallPrinter();
452 if (!SUCCEEDED(result
)) {
453 LOG(ERROR
) << "Unable to install printer.";
460 void GetCurrentInstallPath(FilePath
* install_path
) {
461 base::win::RegKey key
;
462 if (key
.Open(HKEY_LOCAL_MACHINE
, kUninstallRegistry
,
463 KEY_QUERY_VALUE
) != ERROR_SUCCESS
) {
465 *install_path
= FilePath();
468 string16 install_path_value
;
469 key
.ReadValue(L
"InstallLocation", &install_path_value
);
470 *install_path
= FilePath(install_path_value
);
473 HRESULT
UninstallVirtualDriver() {
474 FilePath install_path
;
475 GetCurrentInstallPath(&install_path
);
476 if (install_path
.value().empty()) {
479 HRESULT result
= S_OK
;
480 result
= UninstallPrinter();
481 if (!SUCCEEDED(result
)) {
482 LOG(ERROR
) << "Unable to uninstall Ppd.";
485 result
= UninstallPpd();
486 if (!SUCCEEDED(result
)) {
487 LOG(ERROR
) << "Unable to remove Ppd.";
488 // Put the printer back since we're not able to
489 // complete the uninstallation.
490 // TODO(abodenha@chromium.org) Figure out a better way to recover.
491 // See http://code.google.com/p/chromium/issues/detail?id=123039
495 result
= RegisterPortMonitor(false, install_path
);
496 if (!SUCCEEDED(result
)) {
497 LOG(ERROR
) << "Unable to remove port monitor.";
501 file_util::Delete(install_path
, true);
506 HRESULT
DoLaunchUninstall(const FilePath
& installer_source
, bool wait
) {
508 if (file_util::CreateTemporaryFile(&temp_path
)) {
509 file_util::CopyFile(installer_source
, temp_path
);
510 file_util::DeleteAfterReboot(temp_path
);
511 CommandLine
command_line(temp_path
);
512 command_line
.AppendArg("--douninstall");
513 base::LaunchOptions options
;
515 base::ProcessHandle process_handle
;
516 if (!base::LaunchProcess(command_line
, options
, &process_handle
)) {
517 LOG(ERROR
) << "Unable to launch child uninstall.";
518 return ERROR_NOT_SUPPORTED
;
522 base::TerminationStatus status
=
523 base::GetTerminationStatus(process_handle
, &exit_code
);
524 if (status
== base::TERMINATION_STATUS_NORMAL_TERMINATION
) {
527 LOG(ERROR
) << "Improper termination of uninstall. " << status
;
535 HRESULT
LaunchChildForUninstall() {
536 FilePath installer_source
;
537 if (PathService::Get(base::FILE_EXE
, &installer_source
)) {
538 return DoLaunchUninstall(installer_source
, false);
543 HRESULT
UninstallPreviousVersion() {
544 base::win::RegKey key
;
545 if (key
.Open(HKEY_LOCAL_MACHINE
, kUninstallRegistry
,
546 KEY_QUERY_VALUE
) != ERROR_SUCCESS
) {
550 string16 install_path
;
551 key
.ReadValue(L
"InstallLocation", &install_path
);
552 FilePath
installer_source(install_path
);
553 installer_source
= installer_source
.Append(kInstallerName
);
554 if (file_util::PathExists(installer_source
)) {
555 return DoLaunchUninstall(installer_source
, true);
562 int WINAPI
WinMain(__in HINSTANCE hInstance
,
563 __in HINSTANCE hPrevInstance
,
564 __in LPSTR lpCmdLine
,
566 base::AtExitManager at_exit_manager
;
567 CommandLine::Init(0, NULL
);
568 HRESULT retval
= S_OK
;
569 if (CommandLine::ForCurrentProcess()->HasSwitch("douninstall")) {
570 retval
= UninstallVirtualDriver();
571 } else if (CommandLine::ForCurrentProcess()->HasSwitch("uninstall")) {
572 retval
= LaunchChildForUninstall();
574 retval
= UninstallPreviousVersion();
575 if (SUCCEEDED(retval
)) {
576 FilePath install_path
;
577 retval
= PathService::Get(base::DIR_EXE
, &install_path
);
578 if (SUCCEEDED(retval
)) {
579 retval
= InstallVirtualDriver(install_path
);
583 // Installer is silent by default as required by Omaha.
584 if (CommandLine::ForCurrentProcess()->HasSwitch("verbose")) {
585 cloud_print::DisplayWindowsMessage(NULL
, retval
,
586 cloud_print::LoadLocalString(IDS_DRIVER_NAME
));