Fix duplicated startup version string
[reactos.git] / reactos / dll / win32 / 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 }