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