mmdevapi/tests: Add tests for volume control interfaces.
[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     for (i = 0; i < name.count; i++) msi_free( name.attrs[i] );
251     msi_free( name.attrs );
252     return display_name;
253 }
254
255 static BOOL is_assembly_installed( IAssemblyCache *cache, const WCHAR *display_name )
256 {
257     HRESULT hr;
258     ASSEMBLY_INFO info;
259
260     memset( &info, 0, sizeof(info) );
261     info.cbAssemblyInfo = sizeof(info);
262     hr = IAssemblyCache_QueryAssemblyInfo( cache, QUERYASMINFO_FLAG_GETSIZE, display_name, &info );
263     if (FAILED( hr ))
264     {
265         TRACE("QueryAssemblyInfo returned 0x%08x\n", hr);
266         return FALSE;
267     }
268     return (info.dwAssemblyFlags == ASSEMBLYINFO_FLAG_INSTALLED);
269 }
270
271 static const WCHAR clr_version_v10[] = {'v','1','.','0','.','3','7','0','5',0};
272 static const WCHAR clr_version_v11[] = {'v','1','.','1','.','4','3','2','2',0};
273 static const WCHAR clr_version_v20[] = {'v','2','.','0','.','5','0','7','2','7',0};
274 static const WCHAR clr_version_unknown[] = {'u','n','k','n','o','w','n',0};
275
276 static const WCHAR *clr_version[] =
277 {
278     clr_version_v10,
279     clr_version_v11,
280     clr_version_v20
281 };
282
283 static const WCHAR *get_clr_version_str( enum clr_version version )
284 {
285     if (version >= sizeof(clr_version)/sizeof(clr_version[0])) return clr_version_unknown;
286     return clr_version[version];
287 }
288
289 /* assembly caches must be initialized */
290 MSIASSEMBLY *msi_load_assembly( MSIPACKAGE *package, MSICOMPONENT *comp )
291 {
292     MSIRECORD *rec;
293     MSIASSEMBLY *a;
294
295     if (!(rec = get_assembly_record( package, comp->Component ))) return NULL;
296     if (!(a = msi_alloc_zero( sizeof(MSIASSEMBLY) )))
297     {
298         msiobj_release( &rec->hdr );
299         return NULL;
300     }
301     a->feature = strdupW( MSI_RecordGetString( rec, 2 ) );
302     TRACE("feature %s\n", debugstr_w(a->feature));
303
304     a->manifest = strdupW( MSI_RecordGetString( rec, 3 ) );
305     TRACE("manifest %s\n", debugstr_w(a->manifest));
306
307     a->application = strdupW( MSI_RecordGetString( rec, 4 ) );
308     TRACE("application %s\n", debugstr_w(a->application));
309
310     a->attributes = MSI_RecordGetInteger( rec, 5 );
311     TRACE("attributes %u\n", a->attributes);
312
313     if (!(a->display_name = get_assembly_display_name( package->db, comp->Component, a )))
314     {
315         WARN("can't get display name\n");
316         msiobj_release( &rec->hdr );
317         msi_free( a->feature );
318         msi_free( a->manifest );
319         msi_free( a->application );
320         msi_free( a );
321         return NULL;
322     }
323     TRACE("display name %s\n", debugstr_w(a->display_name));
324
325     if (a->application)
326     {
327         /* We can't check the manifest here because the target path may still change.
328            So we assume that the assembly is not installed and lean on the InstallFiles
329            action to determine which files need to be installed.
330          */
331         a->installed = FALSE;
332     }
333     else
334     {
335         if (a->attributes == msidbAssemblyAttributesWin32)
336             a->installed = is_assembly_installed( package->cache_sxs, a->display_name );
337         else
338         {
339             UINT i;
340             for (i = 0; i < CLR_VERSION_MAX; i++)
341             {
342                 a->clr_version[i] = is_assembly_installed( package->cache_net[i], a->display_name );
343                 if (a->clr_version[i])
344                 {
345                     TRACE("runtime version %s\n", debugstr_w(get_clr_version_str( i )));
346                     a->installed = TRUE;
347                 }
348             }
349         }
350     }
351     TRACE("assembly is %s\n", a->installed ? "installed" : "not installed");
352     msiobj_release( &rec->hdr );
353     return a;
354 }
355
356 static enum clr_version get_clr_version( const WCHAR *filename )
357 {
358     DWORD len;
359     HRESULT hr;
360     enum clr_version version = CLR_VERSION_V11;
361     WCHAR *strW;
362
363     if (!pGetFileVersion) return CLR_VERSION_V10;
364
365     hr = pGetFileVersion( filename, NULL, 0, &len );
366     if (hr != HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER)) return CLR_VERSION_V11;
367     if ((strW = msi_alloc( len * sizeof(WCHAR) )))
368     {
369         hr = pGetFileVersion( filename, strW, len, &len );
370         if (hr == S_OK)
371         {
372             UINT i;
373             for (i = 0; i < CLR_VERSION_MAX; i++)
374                 if (!strcmpW( strW, clr_version[i] )) version = i;
375         }
376         msi_free( strW );
377     }
378     return version;
379 }
380
381 UINT msi_install_assembly( MSIPACKAGE *package, MSICOMPONENT *comp )
382 {
383     HRESULT hr;
384     const WCHAR *manifest;
385     IAssemblyCache *cache;
386     MSIASSEMBLY *assembly = comp->assembly;
387     MSIFEATURE *feature = NULL;
388
389     if (comp->assembly->feature)
390         feature = msi_get_loaded_feature( package, comp->assembly->feature );
391
392     if (assembly->application)
393     {
394         if (feature) feature->Action = INSTALLSTATE_LOCAL;
395         return ERROR_SUCCESS;
396     }
397     if (assembly->attributes == msidbAssemblyAttributesWin32)
398     {
399         if (!assembly->manifest)
400         {
401             WARN("no manifest\n");
402             return ERROR_FUNCTION_FAILED;
403         }
404         manifest = msi_get_loaded_file( package, assembly->manifest )->TargetPath;
405         cache = package->cache_sxs;
406     }
407     else
408     {
409         manifest = msi_get_loaded_file( package, comp->KeyPath )->TargetPath;
410         cache = package->cache_net[get_clr_version( manifest )];
411     }
412     TRACE("installing assembly %s\n", debugstr_w(manifest));
413
414     hr = IAssemblyCache_InstallAssembly( cache, 0, manifest, NULL );
415     if (hr != S_OK)
416     {
417         ERR("Failed to install assembly %s (0x%08x)\n", debugstr_w(manifest), hr);
418         return ERROR_FUNCTION_FAILED;
419     }
420     if (feature) feature->Action = INSTALLSTATE_LOCAL;
421     assembly->installed = TRUE;
422     return ERROR_SUCCESS;
423 }
424
425 static WCHAR *build_local_assembly_path( const WCHAR *filename )
426 {
427     UINT i;
428     WCHAR *ret;
429
430     if (!(ret = msi_alloc( (strlenW( filename ) + 1) * sizeof(WCHAR) )))
431         return NULL;
432
433     for (i = 0; filename[i]; i++)
434     {
435         if (filename[i] == '\\' || filename[i] == '/') ret[i] = '|';
436         else ret[i] = filename[i];
437     }
438     ret[i] = 0;
439     return ret;
440 }
441
442 static LONG open_assemblies_key( UINT context, BOOL win32, HKEY *hkey )
443 {
444     static const WCHAR path_win32[] =
445         {'S','o','f','t','w','a','r','e','\\','M','i','c','r','o','s','o','f','t','\\',
446           '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};
447     static const WCHAR path_dotnet[] =
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','\\','A','s','s','e','m','b','l','i','e','s','\\',0};
450     static const WCHAR classes_path_win32[] =
451         {'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};
452     static const WCHAR classes_path_dotnet[] =
453         {'I','n','s','t','a','l','l','e','r','\\','A','s','s','e','m','b','l','i','e','s','\\',0};
454     HKEY root;
455     const WCHAR *path;
456
457     if (context == MSIINSTALLCONTEXT_MACHINE)
458     {
459         root = HKEY_CLASSES_ROOT;
460         if (win32) path = classes_path_win32;
461         else path = classes_path_dotnet;
462     }
463     else
464     {
465         root = HKEY_CURRENT_USER;
466         if (win32) path = path_win32;
467         else path = path_dotnet;
468     }
469     return RegCreateKeyW( root, path, hkey );
470 }
471
472 static LONG open_local_assembly_key( UINT context, BOOL win32, const WCHAR *filename, HKEY *hkey )
473 {
474     LONG res;
475     HKEY root;
476     WCHAR *path;
477
478     if (!(path = build_local_assembly_path( filename )))
479         return ERROR_OUTOFMEMORY;
480
481     if ((res = open_assemblies_key( context, win32, &root )))
482     {
483         msi_free( path );
484         return res;
485     }
486     res = RegCreateKeyW( root, path, hkey );
487     RegCloseKey( root );
488     msi_free( path );
489     return res;
490 }
491
492 static LONG delete_local_assembly_key( UINT context, BOOL win32, const WCHAR *filename )
493 {
494     LONG res;
495     HKEY root;
496     WCHAR *path;
497
498     if (!(path = build_local_assembly_path( filename )))
499         return ERROR_OUTOFMEMORY;
500
501     if ((res = open_assemblies_key( context, win32, &root )))
502     {
503         msi_free( path );
504         return res;
505     }
506     res = RegDeleteKeyW( root, path );
507     RegCloseKey( root );
508     msi_free( path );
509     return res;
510 }
511
512 static LONG open_global_assembly_key( UINT context, BOOL win32, HKEY *hkey )
513 {
514     static const WCHAR path_win32[] =
515         {'S','o','f','t','w','a','r','e','\\','M','i','c','r','o','s','o','f','t','\\',
516          'I','n','s','t','a','l','l','e','r','\\','W','i','n','3','2','A','s','s','e','m','b','l','i','e','s','\\',
517          'G','l','o','b','a','l',0};
518     static const WCHAR path_dotnet[] =
519         {'S','o','f','t','w','a','r','e','\\','M','i','c','r','o','s','o','f','t','\\',
520          'I','n','s','t','a','l','l','e','r','\\','A','s','s','e','m','b','l','i','e','s','\\',
521          'G','l','o','b','a','l',0};
522     static const WCHAR classes_path_win32[] =
523         {'I','n','s','t','a','l','l','e','r','\\','W','i','n','3','2','A','s','s','e','m','b','l','i','e','s','\\',
524          'G','l','o','b','a','l',0};
525     static const WCHAR classes_path_dotnet[] =
526         {'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};
527     HKEY root;
528     const WCHAR *path;
529
530     if (context == MSIINSTALLCONTEXT_MACHINE)
531     {
532         root = HKEY_CLASSES_ROOT;
533         if (win32) path = classes_path_win32;
534         else path = classes_path_dotnet;
535     }
536     else
537     {
538         root = HKEY_CURRENT_USER;
539         if (win32) path = path_win32;
540         else path = path_dotnet;
541     }
542     return RegCreateKeyW( root, path, hkey );
543 }
544
545 UINT ACTION_MsiPublishAssemblies( MSIPACKAGE *package )
546 {
547     MSICOMPONENT *comp;
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         if (!comp->Enabled)
563         {
564             TRACE("component is disabled: %s\n", debugstr_w(comp->Component));
565             continue;
566         }
567
568         if (comp->ActionRequest != INSTALLSTATE_LOCAL)
569         {
570             TRACE("Component not scheduled for installation: %s\n", debugstr_w(comp->Component));
571             comp->Action = comp->Installed;
572             continue;
573         }
574         comp->Action = INSTALLSTATE_LOCAL;
575
576         TRACE("publishing %s\n", debugstr_w(comp->Component));
577
578         CLSIDFromString( package->ProductCode, &guid );
579         encode_base85_guid( &guid, buffer );
580         buffer[20] = '>';
581         CLSIDFromString( comp->ComponentId, &guid );
582         encode_base85_guid( &guid, buffer + 21 );
583         buffer[42] = 0;
584
585         win32 = assembly->attributes & msidbAssemblyAttributesWin32;
586         if (assembly->application)
587         {
588             MSIFILE *file = msi_get_loaded_file( package, assembly->application );
589             if ((res = open_local_assembly_key( package->Context, win32, file->TargetPath, &hkey )))
590             {
591                 WARN("failed to open local assembly key %d\n", res);
592                 return ERROR_FUNCTION_FAILED;
593             }
594         }
595         else
596         {
597             if ((res = open_global_assembly_key( package->Context, win32, &hkey )))
598             {
599                 WARN("failed to open global assembly key %d\n", res);
600                 return ERROR_FUNCTION_FAILED;
601             }
602         }
603         size = sizeof(buffer);
604         if ((res = RegSetValueExW( hkey, assembly->display_name, 0, REG_MULTI_SZ, (const BYTE *)buffer, size )))
605         {
606             WARN("failed to set assembly value %d\n", res);
607         }
608         RegCloseKey( hkey );
609
610         uirow = MSI_CreateRecord( 2 );
611         MSI_RecordSetStringW( uirow, 2, assembly->display_name );
612         msi_ui_actiondata( package, szMsiPublishAssemblies, uirow );
613         msiobj_release( &uirow->hdr );
614     }
615     return ERROR_SUCCESS;
616 }
617
618 UINT ACTION_MsiUnpublishAssemblies( MSIPACKAGE *package )
619 {
620     MSICOMPONENT *comp;
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         if (!comp->Enabled)
632         {
633             TRACE("component is disabled: %s\n", debugstr_w(comp->Component));
634             continue;
635         }
636
637         if (comp->ActionRequest != INSTALLSTATE_ABSENT)
638         {
639             TRACE("Component not scheduled for removal: %s\n", debugstr_w(comp->Component));
640             comp->Action = comp->Installed;
641             continue;
642         }
643         comp->Action = INSTALLSTATE_ABSENT;
644
645         TRACE("unpublishing %s\n", debugstr_w(comp->Component));
646
647         win32 = assembly->attributes & msidbAssemblyAttributesWin32;
648         if (assembly->application)
649         {
650             MSIFILE *file = msi_get_loaded_file( package, assembly->application );
651             if ((res = delete_local_assembly_key( package->Context, win32, file->TargetPath )))
652                 WARN("failed to delete local assembly key %d\n", res);
653         }
654         else
655         {
656             HKEY hkey;
657             if ((res = open_global_assembly_key( package->Context, win32, &hkey )))
658                 WARN("failed to delete global assembly key %d\n", res);
659             else
660             {
661                 if ((res = RegDeleteValueW( hkey, assembly->display_name )))
662                     WARN("failed to delete global assembly value %d\n", res);
663                 RegCloseKey( hkey );
664             }
665         }
666
667         uirow = MSI_CreateRecord( 2 );
668         MSI_RecordSetStringW( uirow, 2, assembly->display_name );
669         msi_ui_actiondata( package, szMsiPublishAssemblies, uirow );
670         msiobj_release( &uirow->hdr );
671     }
672     return ERROR_SUCCESS;
673 }