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