msi: Destroy assembly caches right after use.
[wine] / dlls / msi / assembly.c
1 /*
2  * Implementation of the Microsoft Installer (msi.dll)
3  *
4  * Copyright 2010 Hans Leidekker for CodeWeavers
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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
19  */
20
21 #include <stdarg.h>
22
23 #define COBJMACROS
24
25 #include "windef.h"
26 #include "winbase.h"
27 #include "winreg.h"
28 #include "wine/debug.h"
29 #include "wine/unicode.h"
30 #include "msipriv.h"
31
32 WINE_DEFAULT_DEBUG_CHANNEL(msi);
33
34 static HRESULT (WINAPI *pCreateAssemblyCacheNet11)( IAssemblyCache **, DWORD );
35 static HRESULT (WINAPI *pCreateAssemblyCacheNet20)( IAssemblyCache **, DWORD );
36 static HRESULT (WINAPI *pCreateAssemblyCacheSxs)( IAssemblyCache **, DWORD );
37 static HRESULT (WINAPI *pLoadLibraryShim)( LPCWSTR, LPCWSTR, LPVOID, HMODULE * );
38 static HRESULT (WINAPI *pGetFileVersion)( LPCWSTR, LPWSTR, DWORD, DWORD * );
39
40 static HMODULE hfusion11, hfusion20, hmscoree, hsxs;
41
42 static BOOL init_function_pointers( void )
43 {
44     static const WCHAR szFusion[] = {'f','u','s','i','o','n','.','d','l','l',0};
45     static const WCHAR szVersion11[] = {'v','1','.','1','.','4','3','2','2',0};
46     static const WCHAR szVersion20[] = {'v','2','.','0','.','5','0','7','2','7',0};
47
48     if (pCreateAssemblyCacheNet11 || pCreateAssemblyCacheNet20) return TRUE;
49
50     if (!(hmscoree = LoadLibraryA( "mscoree.dll" ))) return FALSE;
51     if (!(pGetFileVersion = (void *)GetProcAddress( hmscoree, "GetFileVersion" ))) goto error;
52     if (!(pLoadLibraryShim = (void *)GetProcAddress( hmscoree, "LoadLibraryShim" ))) goto error;
53
54     if (!pLoadLibraryShim( szFusion, szVersion11, NULL, &hfusion11 ))
55         pCreateAssemblyCacheNet11 = (void *)GetProcAddress( hfusion11, "CreateAssemblyCache" );
56
57     if (!pLoadLibraryShim( szFusion, szVersion20, NULL, &hfusion20 ))
58         pCreateAssemblyCacheNet20 = (void *)GetProcAddress( hfusion20, "CreateAssemblyCache" );
59
60     if (!pCreateAssemblyCacheNet11 && !pCreateAssemblyCacheNet20) goto error;
61
62     if (!(hsxs = LoadLibraryA( "sxs.dll" ))) goto error;
63     if (!(pCreateAssemblyCacheSxs = (void *)GetProcAddress( hsxs, "CreateAssemblyCache" ))) goto error;
64     return TRUE;
65
66 error:
67     pCreateAssemblyCacheNet11 = NULL;
68     pCreateAssemblyCacheNet20 = NULL;
69     FreeLibrary( hfusion11 );
70     FreeLibrary( hfusion20 );
71     FreeLibrary( hmscoree );
72     return FALSE;
73 }
74
75 BOOL msi_init_assembly_caches( MSIPACKAGE *package )
76 {
77     if (!init_function_pointers()) return FALSE;
78     if (package->cache_net[CLR_VERSION_V11] || package->cache_net[CLR_VERSION_V20]) return TRUE;
79     if (pCreateAssemblyCacheSxs( &package->cache_sxs, 0 ) != S_OK) return FALSE;
80
81     if (pCreateAssemblyCacheNet11) pCreateAssemblyCacheNet11( &package->cache_net[CLR_VERSION_V11], 0 );
82     if (pCreateAssemblyCacheNet20) pCreateAssemblyCacheNet20( &package->cache_net[CLR_VERSION_V20], 0 );
83
84     if (package->cache_net[CLR_VERSION_V11] || package->cache_net[CLR_VERSION_V20])
85     {
86         return TRUE;
87     }
88     if (package->cache_net[CLR_VERSION_V11])
89     {
90         IAssemblyCache_Release( package->cache_net[CLR_VERSION_V11] );
91         package->cache_net[CLR_VERSION_V11] = NULL;
92     }
93     if (package->cache_net[CLR_VERSION_V20])
94     {
95         IAssemblyCache_Release( package->cache_net[CLR_VERSION_V20] );
96         package->cache_net[CLR_VERSION_V20] = NULL;
97     }
98     IAssemblyCache_Release( package->cache_sxs );
99     package->cache_sxs = NULL;
100     return FALSE;
101 }
102
103 void msi_destroy_assembly_caches( MSIPACKAGE *package )
104 {
105     UINT i;
106
107     for (i = 0; i < CLR_VERSION_MAX; i++)
108     {
109         if (package->cache_net[i])
110         {
111             IAssemblyCache_Release( package->cache_net[i] );
112             package->cache_net[i] = NULL;
113         }
114     }
115     if (package->cache_sxs)
116     {
117         IAssemblyCache_Release( package->cache_sxs );
118         package->cache_sxs = NULL;
119     }
120     pCreateAssemblyCacheNet11 = NULL;
121     pCreateAssemblyCacheNet20 = NULL;
122     FreeLibrary( hfusion11 );
123     FreeLibrary( hfusion20 );
124     FreeLibrary( hmscoree );
125     FreeLibrary( hsxs );
126 }
127
128 static MSIRECORD *get_assembly_record( MSIPACKAGE *package, const WCHAR *comp )
129 {
130     static const WCHAR query[] = {
131         'S','E','L','E','C','T',' ','*',' ','F','R','O','M',' ',
132          '`','M','s','i','A','s','s','e','m','b','l','y','`',' ',
133          'W','H','E','R','E',' ','`','C','o','m','p','o','n','e','n','t','_','`',
134          ' ','=',' ','\'','%','s','\'',0};
135     MSIQUERY *view;
136     MSIRECORD *rec;
137     UINT r;
138
139     r = MSI_OpenQuery( package->db, &view, query, comp );
140     if (r != ERROR_SUCCESS)
141         return NULL;
142
143     r = MSI_ViewExecute( view, NULL );
144     if (r != ERROR_SUCCESS)
145     {
146         msiobj_release( &view->hdr );
147         return NULL;
148     }
149     r = MSI_ViewFetch( view, &rec );
150     if (r != ERROR_SUCCESS)
151     {
152         msiobj_release( &view->hdr );
153         return NULL;
154     }
155     if (!MSI_RecordGetString( rec, 4 ))
156         TRACE("component is a global assembly\n");
157
158     msiobj_release( &view->hdr );
159     return rec;
160 }
161
162 struct assembly_name
163 {
164     UINT    count;
165     UINT    index;
166     WCHAR **attrs;
167 };
168
169 static UINT get_assembly_name_attribute( MSIRECORD *rec, LPVOID param )
170 {
171     static const WCHAR fmtW[] = {'%','s','=','"','%','s','"',0};
172     static const WCHAR nameW[] = {'n','a','m','e',0};
173     struct assembly_name *name = param;
174     const WCHAR *attr = MSI_RecordGetString( rec, 2 );
175     const WCHAR *value = MSI_RecordGetString( rec, 3 );
176     int len = strlenW( fmtW ) + strlenW( attr ) + strlenW( value );
177
178     if (!(name->attrs[name->index] = msi_alloc( len * sizeof(WCHAR) )))
179         return ERROR_OUTOFMEMORY;
180
181     if (!strcmpiW( attr, nameW )) strcpyW( name->attrs[name->index++], value );
182     else sprintfW( name->attrs[name->index++], fmtW, attr, value );
183     return ERROR_SUCCESS;
184 }
185
186 static WCHAR *get_assembly_display_name( MSIDATABASE *db, const WCHAR *comp, MSIASSEMBLY *assembly )
187 {
188     static const WCHAR commaW[] = {',',0};
189     static const WCHAR queryW[] = {
190         'S','E','L','E','C','T',' ','*',' ','F','R','O','M',' ',
191         '`','M','s','i','A','s','s','e','m','b','l','y','N','a','m','e','`',' ',
192         'W','H','E','R','E',' ','`','C','o','m','p','o','n','e','n','t','_','`',
193         ' ','=',' ','\'','%','s','\'',0};
194     struct assembly_name name;
195     WCHAR *display_name = NULL;
196     MSIQUERY *view;
197     UINT i, r;
198     int len;
199
200     r = MSI_OpenQuery( db, &view, queryW, comp );
201     if (r != ERROR_SUCCESS)
202         return NULL;
203
204     name.count = 0;
205     name.index = 0;
206     name.attrs = NULL;
207     MSI_IterateRecords( view, &name.count, NULL, NULL );
208     if (!name.count) goto done;
209
210     name.attrs = msi_alloc( name.count * sizeof(WCHAR *) );
211     if (!name.attrs) goto done;
212
213     MSI_IterateRecords( view, NULL, get_assembly_name_attribute, &name );
214
215     len = 0;
216     for (i = 0; i < name.count; i++) len += strlenW( name.attrs[i] ) + 1;
217
218     display_name = msi_alloc( (len + 1) * sizeof(WCHAR) );
219     if (display_name)
220     {
221         display_name[0] = 0;
222         for (i = 0; i < name.count; i++)
223         {
224             strcatW( display_name, name.attrs[i] );
225             if (i < name.count - 1) strcatW( display_name, commaW );
226         }
227     }
228
229 done:
230     msiobj_release( &view->hdr );
231     for (i = 0; i < name.count; i++) msi_free( name.attrs[i] );
232     msi_free( name.attrs );
233     return display_name;
234 }
235
236 static BOOL is_assembly_installed( IAssemblyCache *cache, const WCHAR *display_name )
237 {
238     HRESULT hr;
239     ASSEMBLY_INFO info;
240
241     memset( &info, 0, sizeof(info) );
242     info.cbAssemblyInfo = sizeof(info);
243     hr = IAssemblyCache_QueryAssemblyInfo( cache, QUERYASMINFO_FLAG_GETSIZE, display_name, &info );
244     if (FAILED( hr ))
245     {
246         TRACE("QueryAssemblyInfo returned 0x%08x\n", hr);
247         return FALSE;
248     }
249     return (info.dwAssemblyFlags == ASSEMBLYINFO_FLAG_INSTALLED);
250 }
251
252 static const WCHAR clr_version_v11[] = {'v','1','.','1','.','4','3','2','2',0};
253 static const WCHAR clr_version_v20[] = {'v','2','.','0','.','5','0','7','2','7',0};
254 static const WCHAR clr_version_unknown[] = {'u','n','k','n','o','w','n',0};
255
256 static const WCHAR *clr_version[] =
257 {
258     clr_version_v11,
259     clr_version_v20
260 };
261
262 static const WCHAR *get_clr_version_str( enum clr_version version )
263 {
264     if (version >= sizeof(clr_version)/sizeof(clr_version[0])) return clr_version_unknown;
265     return clr_version[version];
266 }
267
268 /* assembly caches must be initialized */
269 MSIASSEMBLY *msi_load_assembly( MSIPACKAGE *package, MSICOMPONENT *comp )
270 {
271     MSIRECORD *rec;
272     MSIASSEMBLY *a;
273
274     if (!(rec = get_assembly_record( package, comp->Component ))) return NULL;
275     if (!(a = msi_alloc_zero( sizeof(MSIASSEMBLY) )))
276     {
277         msiobj_release( &rec->hdr );
278         return NULL;
279     }
280     a->feature = strdupW( MSI_RecordGetString( rec, 2 ) );
281     TRACE("feature %s\n", debugstr_w(a->feature));
282
283     a->manifest = strdupW( MSI_RecordGetString( rec, 3 ) );
284     TRACE("manifest %s\n", debugstr_w(a->manifest));
285
286     a->application = strdupW( MSI_RecordGetString( rec, 4 ) );
287     TRACE("application %s\n", debugstr_w(a->application));
288
289     a->attributes = MSI_RecordGetInteger( rec, 5 );
290     TRACE("attributes %u\n", a->attributes);
291
292     if (!(a->display_name = get_assembly_display_name( package->db, comp->Component, a )))
293     {
294         WARN("can't get display name\n");
295         msiobj_release( &rec->hdr );
296         msi_free( a->feature );
297         msi_free( a->manifest );
298         msi_free( a->application );
299         msi_free( a );
300         return NULL;
301     }
302     TRACE("display name %s\n", debugstr_w(a->display_name));
303
304     if (a->application)
305     {
306         /* We can't check the manifest here because the target path may still change.
307            So we assume that the assembly is not installed and lean on the InstallFiles
308            action to determine which files need to be installed.
309          */
310         a->installed = FALSE;
311     }
312     else
313     {
314         if (a->attributes == msidbAssemblyAttributesWin32)
315             a->installed = is_assembly_installed( package->cache_sxs, a->display_name );
316         else
317         {
318             UINT i;
319             for (i = 0; i < CLR_VERSION_MAX; i++)
320             {
321                 a->clr_version[i] = is_assembly_installed( package->cache_net[i], a->display_name );
322                 if (a->clr_version[i])
323                 {
324                     TRACE("runtime version %s\n", debugstr_w(get_clr_version_str( i )));
325                     a->installed = TRUE;
326                 }
327             }
328         }
329     }
330     TRACE("assembly is %s\n", a->installed ? "installed" : "not installed");
331     msiobj_release( &rec->hdr );
332     return a;
333 }
334
335 static enum clr_version get_clr_version( const WCHAR *filename )
336 {
337     DWORD len;
338     HRESULT hr;
339     enum clr_version version = CLR_VERSION_V11;
340     WCHAR *strW;
341
342     hr = pGetFileVersion( filename, NULL, 0, &len );
343     if (hr != HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER)) return CLR_VERSION_V11;
344     if ((strW = msi_alloc( len * sizeof(WCHAR) )))
345     {
346         hr = pGetFileVersion( filename, strW, len, &len );
347         if (hr == S_OK)
348         {
349             UINT i;
350             for (i = 0; i < CLR_VERSION_MAX; i++)
351                 if (!strcmpW( strW, clr_version[i] )) version = i;
352         }
353         msi_free( strW );
354     }
355     return version;
356 }
357
358 UINT msi_install_assembly( MSIPACKAGE *package, MSICOMPONENT *comp )
359 {
360     HRESULT hr;
361     const WCHAR *manifest;
362     IAssemblyCache *cache;
363     MSIASSEMBLY *assembly = comp->assembly;
364     MSIFEATURE *feature = NULL;
365
366     if (comp->assembly->feature)
367         feature = msi_get_loaded_feature( package, comp->assembly->feature );
368
369     if (assembly->application)
370     {
371         if (feature) feature->Action = INSTALLSTATE_LOCAL;
372         return ERROR_SUCCESS;
373     }
374     if (assembly->attributes == msidbAssemblyAttributesWin32)
375     {
376         if (!assembly->manifest)
377         {
378             WARN("no manifest\n");
379             return ERROR_FUNCTION_FAILED;
380         }
381         manifest = msi_get_loaded_file( package, assembly->manifest )->TargetPath;
382         cache = package->cache_sxs;
383     }
384     else
385     {
386         manifest = msi_get_loaded_file( package, comp->KeyPath )->TargetPath;
387         cache = package->cache_net[get_clr_version( manifest )];
388     }
389     TRACE("installing assembly %s\n", debugstr_w(manifest));
390
391     hr = IAssemblyCache_InstallAssembly( cache, 0, manifest, NULL );
392     if (hr != S_OK)
393     {
394         ERR("Failed to install assembly %s (0x%08x)\n", debugstr_w(manifest), hr);
395         return ERROR_FUNCTION_FAILED;
396     }
397     if (feature) feature->Action = INSTALLSTATE_LOCAL;
398     assembly->installed = TRUE;
399     return ERROR_SUCCESS;
400 }
401
402 static WCHAR *build_local_assembly_path( const WCHAR *filename )
403 {
404     UINT i;
405     WCHAR *ret;
406
407     if (!(ret = msi_alloc( (strlenW( filename ) + 1) * sizeof(WCHAR) )))
408         return NULL;
409
410     for (i = 0; filename[i]; i++)
411     {
412         if (filename[i] == '\\' || filename[i] == '/') ret[i] = '|';
413         else ret[i] = filename[i];
414     }
415     ret[i] = 0;
416     return ret;
417 }
418
419 static LONG open_assemblies_key( UINT context, BOOL win32, HKEY *hkey )
420 {
421     static const WCHAR path_win32[] =
422         {'S','o','f','t','w','a','r','e','\\','M','i','c','r','o','s','o','f','t','\\',
423           'I','n','s','t','a','l','l','e','r','\\','W','i','n','3','2','A','s','s','e','m','b','l','i','e','s','\\',0};
424     static const WCHAR path_dotnet[] =
425         {'S','o','f','t','w','a','r','e','\\','M','i','c','r','o','s','o','f','t','\\',
426          'I','n','s','t','a','l','l','e','r','\\','A','s','s','e','m','b','l','i','e','s','\\',0};
427     static const WCHAR classes_path_win32[] =
428         {'I','n','s','t','a','l','l','e','r','\\','W','i','n','3','2','A','s','s','e','m','b','l','i','e','s','\\',0};
429     static const WCHAR classes_path_dotnet[] =
430         {'I','n','s','t','a','l','l','e','r','\\','A','s','s','e','m','b','l','i','e','s','\\',0};
431     HKEY root;
432     const WCHAR *path;
433
434     if (context == MSIINSTALLCONTEXT_MACHINE)
435     {
436         root = HKEY_CLASSES_ROOT;
437         if (win32) path = classes_path_win32;
438         else path = classes_path_dotnet;
439     }
440     else
441     {
442         root = HKEY_CURRENT_USER;
443         if (win32) path = path_win32;
444         else path = path_dotnet;
445     }
446     return RegCreateKeyW( root, path, hkey );
447 }
448
449 static LONG open_local_assembly_key( UINT context, BOOL win32, const WCHAR *filename, HKEY *hkey )
450 {
451     LONG res;
452     HKEY root;
453     WCHAR *path;
454
455     if (!(path = build_local_assembly_path( filename )))
456         return ERROR_OUTOFMEMORY;
457
458     if ((res = open_assemblies_key( context, win32, &root )))
459     {
460         msi_free( path );
461         return res;
462     }
463     res = RegCreateKeyW( root, path, hkey );
464     RegCloseKey( root );
465     msi_free( path );
466     return res;
467 }
468
469 static LONG delete_local_assembly_key( UINT context, BOOL win32, const WCHAR *filename )
470 {
471     LONG res;
472     HKEY root;
473     WCHAR *path;
474
475     if (!(path = build_local_assembly_path( filename )))
476         return ERROR_OUTOFMEMORY;
477
478     if ((res = open_assemblies_key( context, win32, &root )))
479     {
480         msi_free( path );
481         return res;
482     }
483     res = RegDeleteKeyW( root, path );
484     RegCloseKey( root );
485     msi_free( path );
486     return res;
487 }
488
489 static LONG open_global_assembly_key( UINT context, BOOL win32, HKEY *hkey )
490 {
491     static const WCHAR path_win32[] =
492         {'S','o','f','t','w','a','r','e','\\','M','i','c','r','o','s','o','f','t','\\',
493          'I','n','s','t','a','l','l','e','r','\\','W','i','n','3','2','A','s','s','e','m','b','l','i','e','s','\\',
494          'G','l','o','b','a','l',0};
495     static const WCHAR path_dotnet[] =
496         {'S','o','f','t','w','a','r','e','\\','M','i','c','r','o','s','o','f','t','\\',
497          'I','n','s','t','a','l','l','e','r','\\','A','s','s','e','m','b','l','i','e','s','\\',
498          'G','l','o','b','a','l',0};
499     static const WCHAR classes_path_win32[] =
500         {'I','n','s','t','a','l','l','e','r','\\','W','i','n','3','2','A','s','s','e','m','b','l','i','e','s','\\',
501          'G','l','o','b','a','l',0};
502     static const WCHAR classes_path_dotnet[] =
503         {'I','n','s','t','a','l','l','e','r','\\','A','s','s','e','m','b','l','i','e','s','\\','G','l','o','b','a','l',0};
504     HKEY root;
505     const WCHAR *path;
506
507     if (context == MSIINSTALLCONTEXT_MACHINE)
508     {
509         root = HKEY_CLASSES_ROOT;
510         if (win32) path = classes_path_win32;
511         else path = classes_path_dotnet;
512     }
513     else
514     {
515         root = HKEY_CURRENT_USER;
516         if (win32) path = path_win32;
517         else path = path_dotnet;
518     }
519     return RegCreateKeyW( root, path, hkey );
520 }
521
522 UINT ACTION_MsiPublishAssemblies( MSIPACKAGE *package )
523 {
524     MSICOMPONENT *comp;
525
526     LIST_FOR_EACH_ENTRY(comp, &package->components, MSICOMPONENT, entry)
527     {
528         LONG res;
529         HKEY hkey;
530         GUID guid;
531         DWORD size;
532         WCHAR buffer[43];
533         MSIRECORD *uirow;
534         MSIASSEMBLY *assembly = comp->assembly;
535         BOOL win32;
536
537         if (!assembly || !comp->ComponentId) continue;
538
539         if (!comp->Enabled)
540         {
541             TRACE("component is disabled: %s\n", debugstr_w(comp->Component));
542             continue;
543         }
544
545         if (comp->ActionRequest != INSTALLSTATE_LOCAL)
546         {
547             TRACE("Component not scheduled for installation: %s\n", debugstr_w(comp->Component));
548             comp->Action = comp->Installed;
549             continue;
550         }
551         comp->Action = INSTALLSTATE_LOCAL;
552
553         TRACE("publishing %s\n", debugstr_w(comp->Component));
554
555         CLSIDFromString( package->ProductCode, &guid );
556         encode_base85_guid( &guid, buffer );
557         buffer[20] = '>';
558         CLSIDFromString( comp->ComponentId, &guid );
559         encode_base85_guid( &guid, buffer + 21 );
560         buffer[42] = 0;
561
562         win32 = assembly->attributes & msidbAssemblyAttributesWin32;
563         if (assembly->application)
564         {
565             MSIFILE *file = msi_get_loaded_file( package, assembly->application );
566             if ((res = open_local_assembly_key( package->Context, win32, file->TargetPath, &hkey )))
567             {
568                 WARN("failed to open local assembly key %d\n", res);
569                 return ERROR_FUNCTION_FAILED;
570             }
571         }
572         else
573         {
574             if ((res = open_global_assembly_key( package->Context, win32, &hkey )))
575             {
576                 WARN("failed to open global assembly key %d\n", res);
577                 return ERROR_FUNCTION_FAILED;
578             }
579         }
580         size = sizeof(buffer);
581         if ((res = RegSetValueExW( hkey, assembly->display_name, 0, REG_MULTI_SZ, (const BYTE *)buffer, size )))
582         {
583             WARN("failed to set assembly value %d\n", res);
584         }
585         RegCloseKey( hkey );
586
587         uirow = MSI_CreateRecord( 2 );
588         MSI_RecordSetStringW( uirow, 2, assembly->display_name );
589         msi_ui_actiondata( package, szMsiPublishAssemblies, uirow );
590         msiobj_release( &uirow->hdr );
591     }
592     return ERROR_SUCCESS;
593 }
594
595 UINT ACTION_MsiUnpublishAssemblies( MSIPACKAGE *package )
596 {
597     MSICOMPONENT *comp;
598
599     LIST_FOR_EACH_ENTRY(comp, &package->components, MSICOMPONENT, entry)
600     {
601         LONG res;
602         MSIRECORD *uirow;
603         MSIASSEMBLY *assembly = comp->assembly;
604         BOOL win32;
605
606         if (!assembly || !comp->ComponentId) continue;
607
608         if (!comp->Enabled)
609         {
610             TRACE("component is disabled: %s\n", debugstr_w(comp->Component));
611             continue;
612         }
613
614         if (comp->ActionRequest != INSTALLSTATE_ABSENT)
615         {
616             TRACE("Component not scheduled for removal: %s\n", debugstr_w(comp->Component));
617             comp->Action = comp->Installed;
618             continue;
619         }
620         comp->Action = INSTALLSTATE_ABSENT;
621
622         TRACE("unpublishing %s\n", debugstr_w(comp->Component));
623
624         win32 = assembly->attributes & msidbAssemblyAttributesWin32;
625         if (assembly->application)
626         {
627             MSIFILE *file = msi_get_loaded_file( package, assembly->application );
628             if ((res = delete_local_assembly_key( package->Context, win32, file->TargetPath )))
629                 WARN("failed to delete local assembly key %d\n", res);
630         }
631         else
632         {
633             HKEY hkey;
634             if ((res = open_global_assembly_key( package->Context, win32, &hkey )))
635                 WARN("failed to delete global assembly key %d\n", res);
636             else
637             {
638                 if ((res = RegDeleteValueW( hkey, assembly->display_name )))
639                     WARN("failed to delete global assembly value %d\n", res);
640                 RegCloseKey( hkey );
641             }
642         }
643
644         uirow = MSI_CreateRecord( 2 );
645         MSI_RecordSetStringW( uirow, 2, assembly->display_name );
646         msi_ui_actiondata( package, szMsiPublishAssemblies, uirow );
647         msiobj_release( &uirow->hdr );
648     }
649     return ERROR_SUCCESS;
650 }