xref: /reactos/dll/win32/msi/assembly.c (revision d6eebaa4)
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 "msipriv.h"
30 
31 WINE_DEFAULT_DEBUG_CHANNEL(msi);
32 
33 static void load_fusion_dlls( MSIPACKAGE *package )
34 {
35     HRESULT (WINAPI *pLoadLibraryShim)( const WCHAR *, const WCHAR *, void *, HMODULE * );
36     WCHAR path[MAX_PATH];
37     DWORD len = GetSystemDirectoryW( path, MAX_PATH );
38 
39     lstrcpyW( path + len, L"\\mscoree.dll" );
40     if (!package->hmscoree && !(package->hmscoree = LoadLibraryW( path ))) return;
41     if (!(pLoadLibraryShim = (void *)GetProcAddress( package->hmscoree, "LoadLibraryShim" )))
42     {
43         FreeLibrary( package->hmscoree );
44         package->hmscoree = NULL;
45         return;
46     }
47 
48     if (!package->hfusion10) pLoadLibraryShim( L"fusion.dll", L"v1.0.3705", NULL, &package->hfusion10 );
49     if (!package->hfusion11) pLoadLibraryShim( L"fusion.dll", L"v1.1.4322", NULL, &package->hfusion11 );
50     if (!package->hfusion20) pLoadLibraryShim( L"fusion.dll", L"v2.0.50727", NULL, &package->hfusion20 );
51     if (!package->hfusion40) pLoadLibraryShim( L"fusion.dll", L"v4.0.30319", NULL, &package->hfusion40 );
52 }
53 
54 static BOOL init_assembly_caches( MSIPACKAGE *package )
55 {
56     HRESULT (WINAPI *pCreateAssemblyCache)( IAssemblyCache **, DWORD );
57 
58     if (!package->cache_sxs && CreateAssemblyCache( &package->cache_sxs, 0 ) != S_OK) return FALSE;
59 
60     load_fusion_dlls( package );
61     package->pGetFileVersion = (void *)GetProcAddress( package->hmscoree, "GetFileVersion" ); /* missing from v1.0.3705 */
62 
63     if (package->hfusion10 && !package->cache_net[CLR_VERSION_V10])
64     {
65         pCreateAssemblyCache = (void *)GetProcAddress( package->hfusion10, "CreateAssemblyCache" );
66         pCreateAssemblyCache( &package->cache_net[CLR_VERSION_V10], 0 );
67     }
68     if (package->hfusion11 && !package->cache_net[CLR_VERSION_V11])
69     {
70         pCreateAssemblyCache = (void *)GetProcAddress( package->hfusion11, "CreateAssemblyCache" );
71         pCreateAssemblyCache( &package->cache_net[CLR_VERSION_V11], 0 );
72     }
73     if (package->hfusion20 && !package->cache_net[CLR_VERSION_V20])
74     {
75         pCreateAssemblyCache = (void *)GetProcAddress( package->hfusion20, "CreateAssemblyCache" );
76         pCreateAssemblyCache( &package->cache_net[CLR_VERSION_V20], 0 );
77     }
78     if (package->hfusion40 && !package->cache_net[CLR_VERSION_V40])
79     {
80         pCreateAssemblyCache = (void *)GetProcAddress( package->hfusion40, "CreateAssemblyCache" );
81         pCreateAssemblyCache( &package->cache_net[CLR_VERSION_V40], 0 );
82         package->pCreateAssemblyNameObject = (void *)GetProcAddress( package->hfusion40, "CreateAssemblyNameObject" );
83         package->pCreateAssemblyEnum = (void *)GetProcAddress( package->hfusion40, "CreateAssemblyEnum" );
84     }
85 
86     return TRUE;
87 }
88 
89 void msi_destroy_assembly_caches( MSIPACKAGE *package )
90 {
91     UINT i;
92 
93     if (package->cache_sxs)
94     {
95         IAssemblyCache_Release( package->cache_sxs );
96         package->cache_sxs = NULL;
97     }
98     for (i = 0; i < CLR_VERSION_MAX; i++)
99     {
100         if (package->cache_net[i])
101         {
102             IAssemblyCache_Release( package->cache_net[i] );
103             package->cache_net[i] = NULL;
104         }
105     }
106     package->pGetFileVersion = NULL;
107     package->pCreateAssemblyNameObject = NULL;
108     package->pCreateAssemblyEnum = NULL;
109     FreeLibrary( package->hfusion10 );
110     FreeLibrary( package->hfusion11 );
111     FreeLibrary( package->hfusion20 );
112     FreeLibrary( package->hfusion40 );
113     FreeLibrary( package->hmscoree );
114     package->hfusion10 = NULL;
115     package->hfusion11 = NULL;
116     package->hfusion20 = NULL;
117     package->hfusion40 = NULL;
118     package->hmscoree = NULL;
119 }
120 
121 static MSIRECORD *get_assembly_record( MSIPACKAGE *package, const WCHAR *comp )
122 {
123     MSIQUERY *view;
124     MSIRECORD *rec;
125     UINT r;
126 
127     r = MSI_OpenQuery( package->db, &view, L"SELECT * FROM `MsiAssembly` WHERE `Component_` = '%s'", comp );
128     if (r != ERROR_SUCCESS)
129         return NULL;
130 
131     r = MSI_ViewExecute( view, NULL );
132     if (r != ERROR_SUCCESS)
133     {
134         msiobj_release( &view->hdr );
135         return NULL;
136     }
137     r = MSI_ViewFetch( view, &rec );
138     if (r != ERROR_SUCCESS)
139     {
140         msiobj_release( &view->hdr );
141         return NULL;
142     }
143     if (!MSI_RecordGetString( rec, 4 ))
144         TRACE("component is a global assembly\n");
145 
146     msiobj_release( &view->hdr );
147     return rec;
148 }
149 
150 struct assembly_name
151 {
152     DWORD   count;
153     UINT    index;
154     WCHAR **attrs;
155 };
156 
157 static UINT get_assembly_name_attribute( MSIRECORD *rec, LPVOID param )
158 {
159     struct assembly_name *name = param;
160     const WCHAR *attr = MSI_RecordGetString( rec, 2 );
161     const WCHAR *value = MSI_RecordGetString( rec, 3 );
162     int len = lstrlenW( L"%s=\"%s\"" ) + lstrlenW( attr ) + lstrlenW( value );
163 
164     if (!(name->attrs[name->index] = malloc( len * sizeof(WCHAR) )))
165         return ERROR_OUTOFMEMORY;
166 
167     if (!wcsicmp( attr, L"name" )) lstrcpyW( name->attrs[name->index++], value );
168     else swprintf( name->attrs[name->index++], len, L"%s=\"%s\"", attr, value );
169     return ERROR_SUCCESS;
170 }
171 
172 static WCHAR *get_assembly_display_name( MSIDATABASE *db, const WCHAR *comp, MSIASSEMBLY *assembly )
173 {
174     struct assembly_name name;
175     WCHAR *display_name = NULL;
176     MSIQUERY *view;
177     UINT i, r;
178     int len;
179 
180     r = MSI_OpenQuery( db, &view, L"SELECT * FROM `MsiAssemblyName` WHERE `Component_` = '%s'", comp );
181     if (r != ERROR_SUCCESS)
182         return NULL;
183 
184     name.count = 0;
185     name.index = 0;
186     name.attrs = NULL;
187     MSI_IterateRecords( view, &name.count, NULL, NULL );
188     if (!name.count) goto done;
189 
190     name.attrs = malloc( name.count * sizeof(WCHAR *) );
191     if (!name.attrs) goto done;
192 
193     MSI_IterateRecords( view, NULL, get_assembly_name_attribute, &name );
194 
195     len = 0;
196     for (i = 0; i < name.count; i++) len += lstrlenW( name.attrs[i] ) + 1;
197 
198     display_name = malloc( (len + 1) * sizeof(WCHAR) );
199     if (display_name)
200     {
201         display_name[0] = 0;
202         for (i = 0; i < name.count; i++)
203         {
204             lstrcatW( display_name, name.attrs[i] );
205             if (i < name.count - 1) lstrcatW( display_name, L"," );
206         }
207     }
208 
209 done:
210     msiobj_release( &view->hdr );
211     if (name.attrs)
212     {
213         for (i = 0; i < name.count; i++) free( name.attrs[i] );
214         free( name.attrs );
215     }
216     return display_name;
217 }
218 
219 WCHAR *msi_get_assembly_path( MSIPACKAGE *package, const WCHAR *displayname )
220 {
221     HRESULT hr;
222     ASSEMBLY_INFO info;
223     IAssemblyCache *cache;
224 
225     if (!init_assembly_caches( package ) || !(cache = package->cache_net[CLR_VERSION_V40])) return NULL;
226 
227     memset( &info, 0, sizeof(info) );
228     info.cbAssemblyInfo = sizeof(info);
229     hr = IAssemblyCache_QueryAssemblyInfo( cache, 0, displayname, &info );
230     if (hr != E_NOT_SUFFICIENT_BUFFER) return NULL;
231 
232     if (!(info.pszCurrentAssemblyPathBuf = malloc( info.cchBuf * sizeof(WCHAR) ))) return NULL;
233 
234     hr = IAssemblyCache_QueryAssemblyInfo( cache, 0, displayname, &info );
235     if (FAILED( hr ))
236     {
237         free( info.pszCurrentAssemblyPathBuf );
238         return NULL;
239     }
240     TRACE("returning %s\n", debugstr_w(info.pszCurrentAssemblyPathBuf));
241     return info.pszCurrentAssemblyPathBuf;
242 }
243 
244 IAssemblyEnum *msi_create_assembly_enum( MSIPACKAGE *package, const WCHAR *displayname )
245 {
246     HRESULT hr;
247     IAssemblyName *name;
248     IAssemblyEnum *ret;
249     WCHAR *str;
250     DWORD len = 0;
251 
252     if (!init_assembly_caches( package ) || !package->pCreateAssemblyNameObject || !package->pCreateAssemblyEnum)
253         return NULL;
254 
255     hr = package->pCreateAssemblyNameObject( &name, displayname, CANOF_PARSE_DISPLAY_NAME, NULL );
256     if (FAILED( hr )) return NULL;
257 
258     hr = IAssemblyName_GetName( name, &len, NULL );
259     if (hr != E_NOT_SUFFICIENT_BUFFER || !(str = malloc( len * sizeof(WCHAR) )))
260     {
261         IAssemblyName_Release( name );
262         return NULL;
263     }
264 
265     hr = IAssemblyName_GetName( name, &len, str );
266     IAssemblyName_Release( name );
267     if (FAILED( hr ))
268     {
269         free( str );
270         return NULL;
271     }
272 
273     hr = package->pCreateAssemblyNameObject( &name, str, 0, NULL );
274     free( str );
275     if (FAILED( hr )) return NULL;
276 
277     hr = package->pCreateAssemblyEnum( &ret, NULL, name, ASM_CACHE_GAC, NULL );
278     IAssemblyName_Release( name );
279     if (FAILED( hr )) return NULL;
280 
281     return ret;
282 }
283 
284 static const WCHAR *clr_version[] =
285 {
286     L"v1.0.3705",
287     L"v1.2.4322",
288     L"v2.0.50727",
289     L"v4.0.30319"
290 };
291 
292 MSIASSEMBLY *msi_load_assembly( MSIPACKAGE *package, MSICOMPONENT *comp )
293 {
294     MSIRECORD *rec;
295     MSIASSEMBLY *a;
296 
297     if (!(rec = get_assembly_record( package, comp->Component ))) return NULL;
298     if (!(a = calloc( 1, sizeof(MSIASSEMBLY) )))
299     {
300         msiobj_release( &rec->hdr );
301         return NULL;
302     }
303     a->feature = wcsdup( MSI_RecordGetString( rec, 2 ) );
304     TRACE("feature %s\n", debugstr_w(a->feature));
305 
306     a->manifest = wcsdup( MSI_RecordGetString( rec, 3 ) );
307     TRACE("manifest %s\n", debugstr_w(a->manifest));
308 
309     a->application = wcsdup( MSI_RecordGetString( rec, 4 ) );
310     TRACE("application %s\n", debugstr_w(a->application));
311 
312     a->attributes = MSI_RecordGetInteger( rec, 5 );
313     TRACE( "attributes %lu\n", a->attributes );
314 
315     if (!(a->display_name = get_assembly_display_name( package->db, comp->Component, a )))
316     {
317         WARN("can't get display name\n");
318         msiobj_release( &rec->hdr );
319         free( a->feature );
320         free( a->manifest );
321         free( a->application );
322         free( a );
323         return NULL;
324     }
325     TRACE("display name %s\n", debugstr_w(a->display_name));
326 
327     msiobj_release( &rec->hdr );
328     return a;
329 }
330 
331 static enum clr_version get_clr_version( MSIPACKAGE *package, const WCHAR *filename )
332 {
333     DWORD len;
334     HRESULT hr;
335     enum clr_version version = CLR_VERSION_V11;
336     WCHAR *strW;
337 
338     if (!package->pGetFileVersion) return CLR_VERSION_V10;
339 
340     hr = package->pGetFileVersion( filename, NULL, 0, &len );
341     if (hr != E_NOT_SUFFICIENT_BUFFER) return CLR_VERSION_V11;
342     if ((strW = malloc( len * sizeof(WCHAR) )))
343     {
344         hr = package->pGetFileVersion( filename, strW, len, &len );
345         if (hr == S_OK)
346         {
347             UINT i;
348             for (i = 0; i < CLR_VERSION_MAX; i++)
349                 if (!wcscmp( strW, clr_version[i] )) version = i;
350         }
351         free( strW );
352     }
353     return version;
354 }
355 
356 UINT msi_install_assembly( MSIPACKAGE *package, MSICOMPONENT *comp )
357 {
358     HRESULT hr;
359     const WCHAR *manifest;
360     IAssemblyCache *cache;
361     MSIASSEMBLY *assembly = comp->assembly;
362     MSIFEATURE *feature = NULL;
363 
364     if (!init_assembly_caches( package )) return ERROR_FUNCTION_FAILED;
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( package, manifest )];
388         if (!cache) return ERROR_SUCCESS;
389     }
390     TRACE("installing assembly %s\n", debugstr_w(manifest));
391 
392     hr = IAssemblyCache_InstallAssembly( cache, 0, manifest, NULL );
393     if (hr != S_OK)
394     {
395         ERR( "failed to install assembly %s (%#lx)\n", debugstr_w(manifest), hr );
396         return ERROR_FUNCTION_FAILED;
397     }
398     if (feature) feature->Action = INSTALLSTATE_LOCAL;
399     return ERROR_SUCCESS;
400 }
401 
402 UINT msi_uninstall_assembly( MSIPACKAGE *package, MSICOMPONENT *comp )
403 {
404     HRESULT hr;
405     IAssemblyCache *cache;
406     MSIASSEMBLY *assembly = comp->assembly;
407     MSIFEATURE *feature = NULL;
408 
409     if (!init_assembly_caches( package )) return ERROR_FUNCTION_FAILED;
410 
411     if (comp->assembly->feature)
412         feature = msi_get_loaded_feature( package, comp->assembly->feature );
413 
414     if (assembly->application)
415     {
416         if (feature) feature->Action = INSTALLSTATE_ABSENT;
417         return ERROR_SUCCESS;
418     }
419     TRACE("removing %s\n", debugstr_w(assembly->display_name));
420 
421     if (assembly->attributes == msidbAssemblyAttributesWin32)
422     {
423         cache = package->cache_sxs;
424         hr = IAssemblyCache_UninstallAssembly( cache, 0, assembly->display_name, NULL, NULL );
425         if (FAILED( hr )) WARN( "failed to uninstall assembly %#lx\n", hr );
426     }
427     else
428     {
429         unsigned int i;
430         for (i = 0; i < CLR_VERSION_MAX; i++)
431         {
432             if (!assembly->clr_version[i]) continue;
433             cache = package->cache_net[i];
434             if (cache)
435             {
436                 hr = IAssemblyCache_UninstallAssembly( cache, 0, assembly->display_name, NULL, NULL );
437                 if (FAILED( hr )) WARN( "failed to uninstall assembly %#lx\n", hr );
438             }
439         }
440     }
441     if (feature) feature->Action = INSTALLSTATE_ABSENT;
442     return ERROR_SUCCESS;
443 }
444 
445 static WCHAR *build_local_assembly_path( const WCHAR *filename )
446 {
447     UINT i;
448     WCHAR *ret;
449 
450     if (!(ret = malloc( (wcslen( filename ) + 1) * sizeof(WCHAR) )))
451         return NULL;
452 
453     for (i = 0; filename[i]; i++)
454     {
455         if (filename[i] == '\\' || filename[i] == '/') ret[i] = '|';
456         else ret[i] = filename[i];
457     }
458     ret[i] = 0;
459     return ret;
460 }
461 
462 static LONG open_assemblies_key( UINT context, BOOL win32, HKEY *hkey )
463 {
464     HKEY root;
465     const WCHAR *path;
466 
467     if (context == MSIINSTALLCONTEXT_MACHINE)
468     {
469         root = HKEY_CLASSES_ROOT;
470         if (win32) path = L"Installer\\Win32Assemblies\\";
471         else path = L"Installer\\Assemblies\\";
472     }
473     else
474     {
475         root = HKEY_CURRENT_USER;
476         if (win32) path = L"Software\\Microsoft\\Installer\\Win32Assemblies\\";
477         else path = L"Software\\Microsoft\\Installer\\Assemblies\\";
478     }
479     return RegCreateKeyW( root, path, hkey );
480 }
481 
482 static LONG open_local_assembly_key( UINT context, BOOL win32, const WCHAR *filename, HKEY *hkey )
483 {
484     LONG res;
485     HKEY root;
486     WCHAR *path;
487 
488     if (!(path = build_local_assembly_path( filename )))
489         return ERROR_OUTOFMEMORY;
490 
491     if ((res = open_assemblies_key( context, win32, &root )))
492     {
493         free( path );
494         return res;
495     }
496     res = RegCreateKeyW( root, path, hkey );
497     RegCloseKey( root );
498     free( path );
499     return res;
500 }
501 
502 static LONG delete_local_assembly_key( UINT context, BOOL win32, const WCHAR *filename )
503 {
504     LONG res;
505     HKEY root;
506     WCHAR *path;
507 
508     if (!(path = build_local_assembly_path( filename )))
509         return ERROR_OUTOFMEMORY;
510 
511     if ((res = open_assemblies_key( context, win32, &root )))
512     {
513         free( path );
514         return res;
515     }
516     res = RegDeleteKeyW( root, path );
517     RegCloseKey( root );
518     free( path );
519     return res;
520 }
521 
522 static LONG open_global_assembly_key( UINT context, BOOL win32, HKEY *hkey )
523 {
524     HKEY root;
525     const WCHAR *path;
526 
527     if (context == MSIINSTALLCONTEXT_MACHINE)
528     {
529         root = HKEY_CLASSES_ROOT;
530         if (win32) path = L"Installer\\Win32Assemblies\\Global";
531         else path = L"Installer\\Assemblies\\Global";
532     }
533     else
534     {
535         root = HKEY_CURRENT_USER;
536         if (win32) path = L"Software\\Microsoft\\Installer\\Win32Assemblies\\Global";
537         else path = L"Software\\Microsoft\\Installer\\Assemblies\\Global";
538     }
539     return RegCreateKeyW( root, path, hkey );
540 }
541 
542 UINT ACTION_MsiPublishAssemblies( MSIPACKAGE *package )
543 {
544     MSICOMPONENT *comp;
545 
546     if (package->script == SCRIPT_NONE)
547         return msi_schedule_action(package, SCRIPT_INSTALL, L"MsiPublishAssemblies");
548 
549     LIST_FOR_EACH_ENTRY(comp, &package->components, MSICOMPONENT, entry)
550     {
551         LONG res;
552         HKEY hkey;
553         GUID guid;
554         DWORD size;
555         WCHAR buffer[43];
556         MSIRECORD *uirow;
557         MSIASSEMBLY *assembly = comp->assembly;
558         BOOL win32;
559 
560         if (!assembly || !comp->ComponentId) continue;
561 
562         comp->Action = msi_get_component_action( package, comp );
563         if (comp->Action != INSTALLSTATE_LOCAL)
564         {
565             TRACE("component not scheduled for installation %s\n", debugstr_w(comp->Component));
566             continue;
567         }
568         TRACE("publishing %s\n", debugstr_w(comp->Component));
569 
570         CLSIDFromString( package->ProductCode, &guid );
571         encode_base85_guid( &guid, buffer );
572         buffer[20] = '>';
573         CLSIDFromString( comp->ComponentId, &guid );
574         encode_base85_guid( &guid, buffer + 21 );
575         buffer[42] = 0;
576 
577         win32 = assembly->attributes & msidbAssemblyAttributesWin32;
578         if (assembly->application)
579         {
580             MSIFILE *file = msi_get_loaded_file( package, assembly->application );
581             if (!file)
582             {
583                 WARN("no matching file %s for local assembly\n", debugstr_w(assembly->application));
584                 continue;
585             }
586             if ((res = open_local_assembly_key( package->Context, win32, file->TargetPath, &hkey )))
587             {
588                 WARN( "failed to open local assembly key %ld\n", res );
589                 return ERROR_FUNCTION_FAILED;
590             }
591         }
592         else
593         {
594             if ((res = open_global_assembly_key( package->Context, win32, &hkey )))
595             {
596                 WARN( "failed to open global assembly key %ld\n", res );
597                 return ERROR_FUNCTION_FAILED;
598             }
599         }
600         size = sizeof(buffer);
601         if ((res = RegSetValueExW( hkey, assembly->display_name, 0, REG_MULTI_SZ, (const BYTE *)buffer, size )))
602         {
603             WARN( "failed to set assembly value %ld\n", res );
604         }
605         RegCloseKey( hkey );
606 
607         uirow = MSI_CreateRecord( 2 );
608         MSI_RecordSetStringW( uirow, 2, assembly->display_name );
609         MSI_ProcessMessage(package, INSTALLMESSAGE_ACTIONDATA, uirow);
610         msiobj_release( &uirow->hdr );
611     }
612     return ERROR_SUCCESS;
613 }
614 
615 UINT ACTION_MsiUnpublishAssemblies( MSIPACKAGE *package )
616 {
617     MSICOMPONENT *comp;
618 
619     if (package->script == SCRIPT_NONE)
620         return msi_schedule_action(package, SCRIPT_INSTALL, L"MsiUnpublishAssemblies");
621 
622     LIST_FOR_EACH_ENTRY(comp, &package->components, MSICOMPONENT, entry)
623     {
624         LONG res;
625         MSIRECORD *uirow;
626         MSIASSEMBLY *assembly = comp->assembly;
627         BOOL win32;
628 
629         if (!assembly || !comp->ComponentId) continue;
630 
631         comp->Action = msi_get_component_action( package, comp );
632         if (comp->Action != INSTALLSTATE_ABSENT)
633         {
634             TRACE("component not scheduled for removal %s\n", debugstr_w(comp->Component));
635             continue;
636         }
637         TRACE("unpublishing %s\n", debugstr_w(comp->Component));
638 
639         win32 = assembly->attributes & msidbAssemblyAttributesWin32;
640         if (assembly->application)
641         {
642             MSIFILE *file = msi_get_loaded_file( package, assembly->application );
643             if (!file)
644             {
645                 WARN("no matching file %s for local assembly\n", debugstr_w(assembly->application));
646                 continue;
647             }
648             if ((res = delete_local_assembly_key( package->Context, win32, file->TargetPath )))
649                 WARN( "failed to delete local assembly key %ld\n", res );
650         }
651         else
652         {
653             HKEY hkey;
654             if ((res = open_global_assembly_key( package->Context, win32, &hkey )))
655                 WARN( "failed to delete global assembly key %ld\n", res );
656             else
657             {
658                 if ((res = RegDeleteValueW( hkey, assembly->display_name )))
659                     WARN( "failed to delete global assembly value %ld\n", res );
660                 RegCloseKey( hkey );
661             }
662         }
663 
664         uirow = MSI_CreateRecord( 2 );
665         MSI_RecordSetStringW( uirow, 2, assembly->display_name );
666         MSI_ProcessMessage(package, INSTALLMESSAGE_ACTIONDATA, uirow);
667         msiobj_release( &uirow->hdr );
668     }
669     return ERROR_SUCCESS;
670 }
671