advpack: Set the ldids of the install section in install_init.
[wine] / dlls / advpack / install.c
1 /*
2  * Advpack install functions
3  *
4  * Copyright 2006 James Hawkins
5  *
6  * This library is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU Lesser General Public
8  * License as published by the Free Software Foundation; either
9  * version 2.1 of the License, or (at your option) any later version.
10  *
11  * This library is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * Lesser General Public License for more details.
15  *
16  * You should have received a copy of the GNU Lesser General Public
17  * License along with this library; if not, write to the Free Software
18  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
19  */
20
21 #include <stdarg.h>
22 #include <stdlib.h>
23
24 #include "windef.h"
25 #include "winbase.h"
26 #include "winuser.h"
27 #include "winreg.h"
28 #include "winver.h"
29 #include "winternl.h"
30 #include "winnls.h"
31 #include "setupapi.h"
32 #include "advpub.h"
33 #include "wine/debug.h"
34 #include "wine/unicode.h"
35 #include "advpack_private.h"
36
37 WINE_DEFAULT_DEBUG_CHANNEL(advpack);
38
39 #define SPAPI_ERROR     0xE0000000L
40 #define SPAPI_PREFIX    0x800F0000L
41 #define SPAPI_MASK      0xFFFFL
42 #define HRESULT_FROM_SPAPI(x)   ((x & SPAPI_MASK) | SPAPI_PREFIX)
43
44 #define ADV_HRESULT(x)  ((x & SPAPI_ERROR) ? HRESULT_FROM_SPAPI(x) : HRESULT_FROM_WIN32(x))
45
46 /* contains information about a specific install instance */
47 typedef struct _ADVInfo
48 {
49     HINF hinf;
50     LPWSTR inf_filename;
51     LPWSTR install_sec;
52     LPWSTR working_dir;
53     DWORD flags;
54     BOOL need_reboot;
55 } ADVInfo;
56
57 typedef HRESULT (*iterate_fields_func)(HINF hinf, PCWSTR field, void *arg);
58
59 /* Advanced INF commands */
60 static const WCHAR RegisterOCXs[] = {'R','e','g','i','s','t','e','r','O','C','X','s',0};
61
62 /* Advanced INF callbacks */
63 static HRESULT register_ocxs_callback(HINF hinf, PCWSTR field, void *arg)
64 {
65     FIXME("Unhandled command: RegisterOCXs\n");
66     return E_FAIL;
67 }
68
69 /* sequentially returns pointers to parameters in a parameter list
70  * returns NULL if the parameter is empty, e.g. one,,three  */
71 LPWSTR get_parameter(LPWSTR *params, WCHAR separator)
72 {
73     LPWSTR token = *params;
74
75     if (!*params)
76         return NULL;
77
78     *params = strchrW(*params, separator);
79     if (*params)
80         *(*params)++ = '\0';
81
82     if (!*token)
83         return NULL;
84
85     return token;
86 }
87
88 static BOOL is_full_path(LPWSTR path)
89 {
90     const int MIN_PATH_LEN = 3;
91
92     if (!path || lstrlenW(path) < MIN_PATH_LEN)
93         return FALSE;
94
95     if (path[1] == ':' || (path[0] == '\\' && path[1] == '\\'))
96         return TRUE;
97
98     return FALSE;
99 }
100
101 /* retrieves the contents of a field, dynamically growing the buffer if necessary */
102 static WCHAR *get_field_string(INFCONTEXT *context, DWORD index, WCHAR *buffer,
103                                WCHAR *static_buffer, DWORD *size)
104 {
105     DWORD required;
106
107     if (SetupGetStringFieldW(context, index, buffer, *size, &required)) return buffer;
108
109     if (GetLastError() == ERROR_INSUFFICIENT_BUFFER)
110     {
111         /* now grow the buffer */
112         if (buffer != static_buffer) HeapFree(GetProcessHeap(), 0, buffer);
113         if (!(buffer = HeapAlloc(GetProcessHeap(), 0, required*sizeof(WCHAR)))) return NULL;
114         *size = required;
115         if (SetupGetStringFieldW(context, index, buffer, *size, &required)) return buffer;
116     }
117
118     if (buffer != static_buffer) HeapFree(GetProcessHeap(), 0, buffer);
119     return NULL;
120 }
121
122 /* iterates over all fields of a certain key of a certain section */
123 static HRESULT iterate_section_fields(HINF hinf, PCWSTR section, PCWSTR key,
124                                       iterate_fields_func callback, void *arg)
125 {
126     WCHAR static_buffer[200];
127     WCHAR *buffer = static_buffer;
128     DWORD size = sizeof(static_buffer) / sizeof(WCHAR);
129     INFCONTEXT context;
130     HRESULT hr = E_FAIL;
131
132     BOOL ok = SetupFindFirstLineW(hinf, section, key, &context);
133     while (ok)
134     {
135         UINT i, count = SetupGetFieldCount(&context);
136
137         for (i = 1; i <= count; i++)
138         {
139             if (!(buffer = get_field_string(&context, i, buffer, static_buffer, &size)))
140                 goto done;
141
142             if ((hr = callback(hinf, buffer, arg)) != S_OK)
143                 goto done;
144         }
145
146         ok = SetupFindNextMatchLineW(&context, key, &context);
147     }
148
149     hr = S_OK;
150
151  done:
152     if (buffer && buffer != static_buffer) HeapFree(GetProcessHeap(), 0, buffer);
153     return hr;
154 }
155
156 /* performs a setupapi-level install of the INF file */
157 static HRESULT spapi_install(ADVInfo *info)
158 {
159     BOOL ret;
160     HRESULT res;
161     PVOID context;
162
163     context = SetupInitDefaultQueueCallbackEx(NULL, INVALID_HANDLE_VALUE, 0, 0, NULL);
164     if (!context)
165         return ADV_HRESULT(GetLastError());
166
167     ret = SetupInstallFromInfSectionW(NULL, info->hinf, info->install_sec,
168                                       SPINST_FILES, NULL, info->working_dir,
169                                       SP_COPY_NEWER, SetupDefaultQueueCallbackW,
170                                       context, NULL, NULL);
171     if (!ret)
172     {
173         res = ADV_HRESULT(GetLastError());
174         SetupTermDefaultQueueCallback(context);
175
176         return res;
177     }
178
179     SetupTermDefaultQueueCallback(context);
180
181     ret = SetupInstallFromInfSectionW(NULL, info->hinf, info->install_sec,
182                                       SPINST_INIFILES | SPINST_REGISTRY,
183                                       HKEY_LOCAL_MACHINE, NULL, 0,
184                                       NULL, NULL, NULL, NULL);
185     if (!ret)
186         return ADV_HRESULT(GetLastError());
187
188     return S_OK;
189 }
190
191 /* processes the Advanced INF commands */
192 static HRESULT adv_install(ADVInfo *info)
193 {
194     HRESULT hr;
195
196     hr = iterate_section_fields(info->hinf, info->install_sec,
197                                 RegisterOCXs, register_ocxs_callback, NULL);
198
199     return hr;
200 }
201
202 /* loads the INF file and performs checks on it */
203 HRESULT install_init(LPCWSTR inf_filename, LPCWSTR install_sec,
204                      LPCWSTR working_dir, DWORD flags, ADVInfo *info)
205 {
206     DWORD len;
207     LPCWSTR ptr;
208
209     static const WCHAR default_install[] = {
210         'D','e','f','a','u','l','t','I','n','s','t','a','l','l',0
211     };
212
213     len = lstrlenW(inf_filename);
214
215     info->inf_filename = HeapAlloc(GetProcessHeap(), 0, (len + 1) * sizeof(WCHAR));
216     if (!info->inf_filename)
217         return E_OUTOFMEMORY;
218
219     lstrcpyW(info->inf_filename, inf_filename);
220
221     /* FIXME: determine the proper platform to install (NTx86, etc) */
222     if (!install_sec || !*install_sec)
223     {
224         len = sizeof(default_install) - 1;
225         ptr = default_install;
226     }
227     else
228     {
229         len = lstrlenW(install_sec);
230         ptr = install_sec;
231     }
232
233     info->install_sec = HeapAlloc(GetProcessHeap(), 0, (len + 1) * sizeof(WCHAR));
234     if (!info->install_sec)
235         return E_OUTOFMEMORY;
236
237     lstrcpyW(info->install_sec, ptr);
238
239     /* FIXME: need to get the real working directory */
240     if (!working_dir || !*working_dir)
241     {
242         ptr = strrchrW(info->inf_filename, '\\');
243         len = ptr - info->inf_filename + 1;
244         ptr = info->inf_filename;
245     }
246     else
247     {
248         len = lstrlenW(working_dir);
249         ptr = working_dir;
250     }
251
252     info->working_dir = HeapAlloc(GetProcessHeap(), 0, (len + 1) * sizeof(WCHAR));
253     if (!info->working_dir)
254         return E_OUTOFMEMORY;
255
256     lstrcpynW(info->working_dir, ptr, len);
257
258     info->hinf = SetupOpenInfFileW(info->inf_filename, NULL, INF_STYLE_WIN4, NULL);
259     if (info->hinf == INVALID_HANDLE_VALUE)
260         return ADV_HRESULT(GetLastError());
261
262     set_ldids(info->hinf, info->install_sec, info->working_dir);
263
264     /* FIXME: check that the INF is advanced */
265
266     info->flags = flags;
267     info->need_reboot = FALSE;
268
269     return S_OK;
270 }
271
272 /* release the install instance information */
273 void install_release(ADVInfo *info)
274 {
275     if (info->hinf && info->hinf != INVALID_HANDLE_VALUE)
276         SetupCloseInfFile(info->hinf);
277
278     HeapFree(GetProcessHeap(), 0, info->inf_filename);
279     HeapFree(GetProcessHeap(), 0, info->install_sec);
280     HeapFree(GetProcessHeap(), 0, info->working_dir);
281 }
282
283 /* this structure very closely resembles parameters of RunSetupCommand() */
284 typedef struct
285 {
286     HWND hwnd;
287     LPCSTR title;
288     LPCSTR inf_name;
289     LPCSTR dir;
290     LPCSTR section_name;
291 } SETUPCOMMAND_PARAMS;
292
293 /***********************************************************************
294  *      DoInfInstall  (ADVPACK.@)
295  *
296  * Install an INF section.
297  *
298  * PARAMS
299  *  setup [I] Structure containing install information.
300  *
301  * RETURNS
302  *   S_OK                                Everything OK
303  *   HRESULT_FROM_WIN32(GetLastError())  Some other error
304  */
305 HRESULT WINAPI DoInfInstall(const SETUPCOMMAND_PARAMS *setup)
306 {
307     BOOL ret;
308     HINF hinf;
309     void *callback_context;
310
311     TRACE("(%p)\n", setup);
312
313     hinf = SetupOpenInfFileA(setup->inf_name, NULL, INF_STYLE_WIN4, NULL);
314     if (hinf == INVALID_HANDLE_VALUE) return HRESULT_FROM_WIN32(GetLastError());
315
316     callback_context = SetupInitDefaultQueueCallback(setup->hwnd);
317
318     ret = SetupInstallFromInfSectionA(NULL, hinf, setup->section_name, SPINST_ALL,
319                                       NULL, NULL, 0, SetupDefaultQueueCallbackA,
320                                       callback_context, NULL, NULL);
321     SetupTermDefaultQueueCallback(callback_context);
322     SetupCloseInfFile(hinf);
323
324     return ret ? S_OK : HRESULT_FROM_WIN32(GetLastError());
325 }
326
327 /***********************************************************************
328  *             ExecuteCabA    (ADVPACK.@)
329  *
330  * See ExecuteCabW.
331  */
332 HRESULT WINAPI ExecuteCabA(HWND hwnd, CABINFOA* pCab, LPVOID pReserved)
333 {
334     UNICODE_STRING cab, inf, section;
335     CABINFOW cabinfo;
336     HRESULT hr;
337
338     TRACE("(%p, %p, %p)\n", hwnd, pCab, pReserved);
339
340     if (!pCab)
341         return E_INVALIDARG;
342
343     if (pCab->pszCab)
344     {
345         RtlCreateUnicodeStringFromAsciiz(&cab, pCab->pszCab);
346         cabinfo.pszCab = cab.Buffer;
347     }
348     else
349         cabinfo.pszCab = NULL;
350
351     RtlCreateUnicodeStringFromAsciiz(&inf, pCab->pszInf);
352     RtlCreateUnicodeStringFromAsciiz(&section, pCab->pszSection);
353     
354     MultiByteToWideChar(CP_ACP, 0, pCab->szSrcPath, -1, cabinfo.szSrcPath,
355                         sizeof(cabinfo.szSrcPath) / sizeof(WCHAR));
356
357     cabinfo.pszInf = inf.Buffer;
358     cabinfo.pszSection = section.Buffer;
359     cabinfo.dwFlags = pCab->dwFlags;
360
361     hr = ExecuteCabW(hwnd, &cabinfo, pReserved);
362
363     if (pCab->pszCab)
364         RtlFreeUnicodeString(&cab);
365
366     RtlFreeUnicodeString(&inf);
367     RtlFreeUnicodeString(&section);
368
369     return hr;
370 }
371
372 /***********************************************************************
373  *             ExecuteCabW    (ADVPACK.@)
374  * 
375  * Installs the INF file extracted from a specified cabinet file.
376  * 
377  * PARAMS
378  *   hwnd      [I] Handle to the window used for the display.
379  *   pCab      [I] Information about the cabinet file.
380  *   pReserved [I] Reserved.  Must be NULL.
381  * 
382  * RETURNS
383  *   Success: S_OK.
384  *   Failure: E_FAIL.
385  *
386  * BUGS
387  *   Unimplemented
388  */
389 HRESULT WINAPI ExecuteCabW(HWND hwnd, CABINFOW* pCab, LPVOID pReserved)
390 {
391     FIXME("(%p, %p, %p): stub\n", hwnd, pCab, pReserved);
392     return E_FAIL;
393 }
394
395 /***********************************************************************
396  *      LaunchINFSectionA   (ADVPACK.@)
397  *
398  * See LaunchINFSectionW.
399  */
400 INT WINAPI LaunchINFSectionA(HWND hWnd, HINSTANCE hInst, LPSTR cmdline, INT show)
401 {
402     UNICODE_STRING cmd;
403     HRESULT hr;
404
405     TRACE("(%p, %p, %s, %i)\n", hWnd, hInst, debugstr_a(cmdline), show);
406
407     RtlCreateUnicodeStringFromAsciiz(&cmd, cmdline);
408
409     hr = LaunchINFSectionW(hWnd, hInst, cmd.Buffer, show);
410
411     RtlFreeUnicodeString(&cmd);
412
413     return hr;
414 }
415
416 /***********************************************************************
417  *      LaunchINFSectionW   (ADVPACK.@)
418  *
419  * Installs an INF section without BACKUP/ROLLBACK capabilities.
420  *
421  * PARAMS
422  *   hWnd    [I] Handle to parent window, NULL for desktop.
423  *   hInst   [I] Instance of the process.
424  *   cmdline [I] Contains parameters in the order INF,section,flags,reboot.
425  *   show    [I] How the window should be shown.
426  *
427  * RETURNS
428  *  Success: S_OK.
429  *  Failure: S_FALSE
430  *
431  * NOTES
432  *  INF - Filename of the INF to launch.
433  *  section - INF section to install.
434  *  flags - see advpub.h.
435  *  reboot - smart reboot behavior
436  *    'A' Always reboot.
437  *    'I' Reboot if needed (default).
438  *    'N' No reboot.
439  * 
440  * BUGS
441  *  Unimplemented.
442  */
443 INT WINAPI LaunchINFSectionW(HWND hWnd, HINSTANCE hInst, LPWSTR cmdline, INT show)
444 {
445     FIXME("(%p, %p, %s, %i): stub\n", hWnd, hInst, debugstr_w(cmdline), show);
446     return 0;
447 }
448
449 /***********************************************************************
450  *      LaunchINFSectionExA (ADVPACK.@)
451  *
452  * See LaunchINFSectionExW.
453  */
454 HRESULT WINAPI LaunchINFSectionExA(HWND hWnd, HINSTANCE hInst, LPSTR cmdline, INT show)
455 {
456     UNICODE_STRING cmd;
457     HRESULT hr;
458
459     TRACE("(%p, %p, %s, %i)\n", hWnd, hInst, debugstr_a(cmdline), show);
460
461     RtlCreateUnicodeStringFromAsciiz(&cmd, cmdline);
462
463     hr = LaunchINFSectionExW(hWnd, hInst, cmd.Buffer, show);
464
465     RtlFreeUnicodeString(&cmd);
466
467     return hr;
468 }
469
470 /***********************************************************************
471  *      LaunchINFSectionExW (ADVPACK.@)
472  *
473  * Installs an INF section with BACKUP/ROLLBACK capabilities.
474  *
475  * PARAMS
476  *   hWnd    [I] Handle to parent window, NULL for desktop.
477  *   hInst   [I] Instance of the process.
478  *   cmdline [I] Contains parameters in the order INF,section,CAB,flags,reboot.
479  *   show    [I] How the window should be shown.
480  *
481  * RETURNS
482  *  Success: S_OK.
483  *  Failure: E_FAIL.
484  *
485  * NOTES
486  *  INF - Filename of the INF to launch.
487  *  section - INF section to install.
488  *  flags - see advpub.h.
489  *  reboot - smart reboot behavior
490  *    'A' Always reboot.
491  *    'I' Reboot if needed (default).
492  *    'N' No reboot.
493  *
494  * BUGS
495  *  Doesn't handle the reboot flag.
496  */
497 HRESULT WINAPI LaunchINFSectionExW(HWND hWnd, HINSTANCE hInst, LPWSTR cmdline, INT show)
498 {
499     LPWSTR cmdline_copy, cmdline_ptr;
500     LPWSTR flags, ptr;
501     CABINFOW cabinfo;
502     HRESULT hr = S_OK;
503
504     TRACE("(%p, %p, %s, %d)\n", hWnd, hInst, debugstr_w(cmdline), show);
505
506     if (!cmdline)
507         return E_INVALIDARG;
508
509     cmdline_copy = HeapAlloc(GetProcessHeap(), 0, (lstrlenW(cmdline) + 1) * sizeof(WCHAR));
510     cmdline_ptr = cmdline_copy;
511     lstrcpyW(cmdline_copy, cmdline);
512
513     cabinfo.pszInf = get_parameter(&cmdline_ptr, ',');
514     cabinfo.pszSection = get_parameter(&cmdline_ptr, ',');
515     cabinfo.pszCab = get_parameter(&cmdline_ptr, ',');
516
517     flags = get_parameter(&cmdline_ptr, ',');
518     if (flags)
519         cabinfo.dwFlags = atolW(flags);
520
521     /* get the source path from the cab filename */
522     if (cabinfo.pszCab && *cabinfo.pszCab)
523     {
524         if (!is_full_path(cabinfo.pszCab))
525             goto done;
526
527         lstrcpyW(cabinfo.szSrcPath, cabinfo.pszCab);
528         ptr = strrchrW(cabinfo.szSrcPath, '\\');
529         *(++ptr) = '\0';
530     }
531
532     hr = ExecuteCabW(hWnd, &cabinfo, NULL);
533
534 done:
535     HeapFree(GetProcessHeap(), 0, cmdline_copy);
536
537     return hr;
538 }
539
540 HRESULT launch_exe(LPCWSTR cmd, LPCWSTR dir, HANDLE *phEXE)
541 {
542     STARTUPINFOW si;
543     PROCESS_INFORMATION pi;
544
545     if (phEXE) *phEXE = NULL;
546
547     ZeroMemory(&pi, sizeof(pi));
548     ZeroMemory(&si, sizeof(si));
549     si.cb = sizeof(si);
550
551     if (!CreateProcessW(NULL, (LPWSTR)cmd, NULL, NULL, FALSE,
552                         CREATE_DEFAULT_ERROR_MODE | CREATE_NEW_PROCESS_GROUP,
553                         NULL, dir, &si, &pi))
554     {
555         return HRESULT_FROM_WIN32(GetLastError());
556     }
557
558     CloseHandle(pi.hThread);
559
560     if (phEXE)
561     {
562         *phEXE = pi.hProcess;
563         return S_ASYNCHRONOUS;
564     }
565
566     /* wait for the child process to finish */
567     WaitForSingleObject(pi.hProcess, INFINITE);
568     CloseHandle(pi.hProcess);
569
570     return S_OK;
571 }
572
573 /***********************************************************************
574  *      RunSetupCommandA  (ADVPACK.@)
575  *
576  * See RunSetupCommandW.
577  */
578 HRESULT WINAPI RunSetupCommandA(HWND hWnd, LPCSTR szCmdName,
579                                 LPCSTR szInfSection, LPCSTR szDir,
580                                 LPCSTR lpszTitle, HANDLE *phEXE,
581                                 DWORD dwFlags, LPVOID pvReserved)
582 {
583     UNICODE_STRING cmdname, infsec;
584     UNICODE_STRING dir, title;
585     HRESULT hr;
586
587     TRACE("(%p, %s, %s, %s, %s, %p, %ld, %p)\n",
588           hWnd, debugstr_a(szCmdName), debugstr_a(szInfSection),
589           debugstr_a(szDir), debugstr_a(lpszTitle),
590           phEXE, dwFlags, pvReserved);
591
592     if (!szCmdName || !szDir)
593         return E_INVALIDARG;
594
595     RtlCreateUnicodeStringFromAsciiz(&cmdname, szCmdName);
596     RtlCreateUnicodeStringFromAsciiz(&infsec, szInfSection);
597     RtlCreateUnicodeStringFromAsciiz(&dir, szDir);
598     RtlCreateUnicodeStringFromAsciiz(&title, lpszTitle);
599
600     hr = RunSetupCommandW(hWnd, cmdname.Buffer, infsec.Buffer, dir.Buffer,
601                           title.Buffer, phEXE, dwFlags, pvReserved);
602
603     RtlFreeUnicodeString(&cmdname);
604     RtlFreeUnicodeString(&infsec);
605     RtlFreeUnicodeString(&dir);
606     RtlFreeUnicodeString(&title);
607
608     return hr;
609 }
610
611 /***********************************************************************
612  *      RunSetupCommandW  (ADVPACK.@)
613  *
614  * Executes an install section in an INF file or a program.
615  *
616  * PARAMS
617  *   hWnd          [I] Handle to parent window, NULL for quiet mode
618  *   szCmdName     [I] Inf or EXE filename to execute
619  *   szInfSection  [I] Inf section to install, NULL for DefaultInstall
620  *   szDir         [I] Path to extracted files
621  *   szTitle       [I] Title of all dialogs
622  *   phEXE         [O] Handle of EXE to wait for
623  *   dwFlags       [I] Flags; see include/advpub.h
624  *   pvReserved    [I] Reserved
625  *
626  * RETURNS
627  *   S_OK                                 Everything OK
628  *   S_ASYNCHRONOUS                       OK, required to wait on phEXE
629  *   ERROR_SUCCESS_REBOOT_REQUIRED        Reboot required
630  *   E_INVALIDARG                         Invalid argument given
631  *   HRESULT_FROM_WIN32(ERROR_OLD_WIN_VERSION)
632  *                                        Not supported on this Windows version
633  *   E_UNEXPECTED                         Unexpected error
634  *   HRESULT_FROM_WIN32(GetLastError())   Some other error
635  *
636  * BUGS
637  *   INF install unimplemented.
638  */
639 HRESULT WINAPI RunSetupCommandW(HWND hWnd, LPCWSTR szCmdName,
640                                 LPCWSTR szInfSection, LPCWSTR szDir,
641                                 LPCWSTR lpszTitle, HANDLE *phEXE,
642                                 DWORD dwFlags, LPVOID pvReserved)
643 {
644     ADVInfo info;
645     HRESULT hr;
646
647     TRACE("(%p, %s, %s, %s, %s, %p, %ld, %p)\n",
648           hWnd, debugstr_w(szCmdName), debugstr_w(szInfSection),
649           debugstr_w(szDir), debugstr_w(lpszTitle),
650           phEXE, dwFlags, pvReserved);
651
652     if (dwFlags)
653         FIXME("Unhandled flags: 0x%08lx\n", dwFlags);
654
655     if (!szCmdName || !szDir)
656         return E_INVALIDARG;
657
658     if (!(dwFlags & RSC_FLAG_INF))
659         return launch_exe(szCmdName, szDir, phEXE);
660
661     ZeroMemory(&info, sizeof(ADVInfo));
662
663     hr = install_init(szCmdName, szInfSection, szDir, dwFlags, &info);
664     if (hr != S_OK)
665         goto done;
666
667     hr = spapi_install(&info);
668     if (hr != S_OK)
669         goto done;
670
671     hr = adv_install(&info);
672
673 done:
674     install_release(&info);
675
676     return hr;
677 }