2 * Implementation of the Microsoft Installer (msi.dll)
4 * Copyright 2004,2005 Aric Stewart for CodeWeavers
5 * Copyright 2011 Hans Leidekker for CodeWeavers
7 * This library is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU Lesser General Public
9 * License as published by the Free Software Foundation; either
10 * version 2.1 of the License, or (at your option) any later version.
12 * This library is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * Lesser General Public License for more details.
17 * You should have received a copy of the GNU Lesser General Public
18 * License along with this library; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
29 #include "wine/debug.h"
30 #include "wine/unicode.h"
33 WINE_DEFAULT_DEBUG_CHANNEL(msi);
35 static UINT check_transform_applicable( MSIPACKAGE *package, IStorage *patch )
37 static const WCHAR szSystemLanguageID[] = {
38 'S','y','s','t','e','m','L','a','n','g','u','a','g','e','I','D',0};
39 LPWSTR prod_code, patch_product, langid = NULL, template = NULL;
40 UINT ret = ERROR_FUNCTION_FAILED;
42 prod_code = msi_dup_property( package->db, szProductCode );
43 patch_product = msi_get_suminfo_product( patch );
45 TRACE("db = %s patch = %s\n", debugstr_w(prod_code), debugstr_w(patch_product));
47 if (strstrW( patch_product, prod_code ))
52 si = MSI_GetSummaryInformationW( patch, 0 );
55 ERR("no summary information!\n");
58 template = msi_suminfo_dup_string( si, PID_TEMPLATE );
61 ERR("no template property!\n");
62 msiobj_release( &si->hdr );
68 msiobj_release( &si->hdr );
71 langid = msi_dup_property( package->db, szSystemLanguageID );
74 msiobj_release( &si->hdr );
77 p = strchrW( template, ';' );
78 if (p && (!strcmpW( p + 1, langid ) || !strcmpW( p + 1, szZero )))
80 TRACE("applicable transform\n");
83 /* FIXME: check platform */
84 msiobj_release( &si->hdr );
88 msi_free( patch_product );
89 msi_free( prod_code );
95 static UINT apply_substorage_transform( MSIPACKAGE *package, MSIDATABASE *patch_db, LPCWSTR name )
97 UINT ret = ERROR_FUNCTION_FAILED;
101 TRACE("%p %s\n", package, debugstr_w(name));
105 ERR("expected a colon in %s\n", debugstr_w(name));
106 return ERROR_FUNCTION_FAILED;
108 r = IStorage_OpenStorage( patch_db->storage, name, NULL, STGM_SHARE_EXCLUSIVE, NULL, 0, &stg );
111 ret = check_transform_applicable( package, stg );
112 if (ret == ERROR_SUCCESS)
113 msi_table_apply_transform( package->db, stg );
115 TRACE("substorage transform %s wasn't applicable\n", debugstr_w(name));
116 IStorage_Release( stg );
120 ERR("failed to open substorage %s\n", debugstr_w(name));
122 return ERROR_SUCCESS;
125 UINT msi_check_patch_applicable( MSIPACKAGE *package, MSISUMMARYINFO *si )
127 LPWSTR guid_list, *guids, product_code;
128 UINT i, ret = ERROR_FUNCTION_FAILED;
130 product_code = msi_dup_property( package->db, szProductCode );
133 /* FIXME: the property ProductCode should be written into the DB somewhere */
134 ERR("no product code to check\n");
135 return ERROR_SUCCESS;
137 guid_list = msi_suminfo_dup_string( si, PID_TEMPLATE );
138 guids = msi_split_string( guid_list, ';' );
139 for (i = 0; guids[i] && ret != ERROR_SUCCESS; i++)
141 if (!strcmpW( guids[i], product_code )) ret = ERROR_SUCCESS;
144 msi_free( guid_list );
145 msi_free( product_code );
149 UINT msi_parse_patch_summary( MSISUMMARYINFO *si, MSIPATCHINFO **patch )
152 UINT r = ERROR_SUCCESS;
155 if (!(pi = msi_alloc_zero( sizeof(MSIPATCHINFO) )))
157 return ERROR_OUTOFMEMORY;
159 if (!(pi->patchcode = msi_suminfo_dup_string( si, PID_REVNUMBER )))
162 return ERROR_OUTOFMEMORY;
167 msi_free( pi->patchcode );
169 return ERROR_PATCH_PACKAGE_INVALID;
171 if (!(p = strchrW( p + 1, '}' )))
173 msi_free( pi->patchcode );
175 return ERROR_PATCH_PACKAGE_INVALID;
179 FIXME("patch obsoletes %s\n", debugstr_w(p + 1));
182 TRACE("patch code %s\n", debugstr_w(pi->patchcode));
184 if (!(pi->transforms = msi_suminfo_dup_string( si, PID_LASTAUTHOR )))
186 msi_free( pi->patchcode );
188 return ERROR_OUTOFMEMORY;
194 static UINT patch_set_media_source_prop( MSIPACKAGE *package )
196 static const WCHAR query[] = {
197 'S','E','L','E','C','T',' ','`','S','o','u','r','c','e','`',' ','F','R','O','M',' ',
198 '`','M','e','d','i','a','`',' ','W','H','E','R','E',' ','`','S','o','u','r','c','e','`',' ',
199 'I','S',' ','N','O','T',' ','N','U','L','L',0};
202 const WCHAR *property;
206 r = MSI_DatabaseOpenViewW( package->db, query, &view );
207 if (r != ERROR_SUCCESS)
210 r = MSI_ViewExecute( view, 0 );
211 if (r != ERROR_SUCCESS)
214 if (MSI_ViewFetch( view, &rec ) == ERROR_SUCCESS)
216 property = MSI_RecordGetString( rec, 1 );
217 patch = msi_dup_property( package->db, szPatch );
218 msi_set_property( package->db, property, patch );
220 msiobj_release( &rec->hdr );
224 msiobj_release( &view->hdr );
235 struct patch_offset_list
238 UINT count, min, max;
239 UINT offset_to_apply;
242 static struct patch_offset_list *patch_offset_list_create( void )
244 struct patch_offset_list *pos = msi_alloc( sizeof(struct patch_offset_list) );
245 list_init( &pos->files );
246 pos->count = pos->max = 0;
251 static void patch_offset_list_free( struct patch_offset_list *pos )
253 struct patch_offset *po, *po2;
255 LIST_FOR_EACH_ENTRY_SAFE( po, po2, &pos->files, struct patch_offset, entry )
257 msi_free( po->name );
263 static void patch_offset_get_patches( MSIDATABASE *db, UINT last_sequence, struct patch_offset_list *pos )
265 static const WCHAR query_patch[] = {
266 'S','E','L','E','C','T',' ','*',' ','F','R','O','M',' ','P','a','t','c','h',' ',
267 'W','H','E','R','E',' ','S','e','q','u','e','n','c','e',' ','<','=',' ','?',' ',
268 'O','R','D','E','R',' ','B','Y',' ','S','e','q','u','e','n','c','e',0};
273 r = MSI_DatabaseOpenViewW( db, query_patch, &view );
274 if (r != ERROR_SUCCESS)
277 rec = MSI_CreateRecord( 1 );
278 MSI_RecordSetInteger( rec, 1, last_sequence );
280 r = MSI_ViewExecute( view, rec );
281 msiobj_release( &rec->hdr );
282 if (r != ERROR_SUCCESS)
285 while (MSI_ViewFetch( view, &rec ) == ERROR_SUCCESS)
287 UINT sequence = MSI_RecordGetInteger( rec, 2 );
289 /* FIXME: we only use the max/min sequence numbers for now */
290 pos->min = min( pos->min, sequence );
291 pos->max = max( pos->max, sequence );
293 msiobj_release( &rec->hdr );
295 msiobj_release( &view->hdr );
298 static void patch_offset_get_files( MSIDATABASE *db, UINT last_sequence, struct patch_offset_list *pos )
300 static const WCHAR query_files[] = {
301 'S','E','L','E','C','T',' ','*',' ','F','R','O','M',' ','F','i','l','e',' ',
302 'W','H','E','R','E',' ','S','e','q','u','e','n','c','e',' ','<','=',' ','?',' ',
303 'O','R','D','E','R',' ','B','Y',' ','S','e','q','u','e','n','c','e',0};
308 r = MSI_DatabaseOpenViewW( db, query_files, &view );
309 if (r != ERROR_SUCCESS)
312 rec = MSI_CreateRecord( 1 );
313 MSI_RecordSetInteger( rec, 1, last_sequence );
315 r = MSI_ViewExecute( view, rec );
316 msiobj_release( &rec->hdr );
317 if (r != ERROR_SUCCESS)
320 while (MSI_ViewFetch( view, &rec ) == ERROR_SUCCESS)
322 UINT attributes = MSI_RecordGetInteger( rec, 7 );
323 if (attributes & msidbFileAttributesPatchAdded)
325 struct patch_offset *po = msi_alloc( sizeof(struct patch_offset) );
327 po->name = msi_dup_record_field( rec, 1 );
328 po->sequence = MSI_RecordGetInteger( rec, 8 );
329 pos->min = min( pos->min, po->sequence );
330 pos->max = max( pos->max, po->sequence );
331 list_add_tail( &pos->files, &po->entry );
334 msiobj_release( &rec->hdr );
336 msiobj_release( &view->hdr );
339 static UINT patch_offset_modify_db( MSIDATABASE *db, struct patch_offset_list *pos )
341 static const WCHAR query_files[] = {
342 'S','E','L','E','C','T',' ','*',' ','F','R','O','M',' ','F','i','l','e',' ',
343 'W','H','E','R','E',' ','S','e','q','u','e','n','c','e',' ','>','=',' ','?',' ',
344 'A','N','D',' ','S','e','q','u','e','n','c','e',' ','<','=',' ','?',' ',
345 'O','R','D','E','R',' ','B','Y',' ','S','e','q','u','e','n','c','e',0};
346 struct patch_offset *po;
351 r = MSI_DatabaseOpenViewW( db, query_files, &view );
352 if (r != ERROR_SUCCESS)
353 return ERROR_SUCCESS;
355 rec = MSI_CreateRecord( 2 );
356 MSI_RecordSetInteger( rec, 1, pos->min );
357 MSI_RecordSetInteger( rec, 2, pos->max );
359 r = MSI_ViewExecute( view, rec );
360 msiobj_release( &rec->hdr );
361 if (r != ERROR_SUCCESS)
364 LIST_FOR_EACH_ENTRY( po, &pos->files, struct patch_offset, entry )
367 while ((r_fetch = MSI_ViewFetch( view, &rec )) == ERROR_SUCCESS)
369 const WCHAR *file = MSI_RecordGetString( rec, 1 );
372 if (!strcmpiW( file, po->name ))
375 seq = MSI_RecordGetInteger( rec, 8 );
376 MSI_RecordSetInteger( rec, 8, seq + pos->offset_to_apply );
377 r = MSI_ViewModify( view, MSIMODIFY_UPDATE, rec );
378 if (r != ERROR_SUCCESS)
379 ERR("Failed to update offset for file %s\n", debugstr_w(file));
380 msiobj_release( &rec->hdr );
383 msiobj_release( &rec->hdr );
385 if (r_fetch != ERROR_SUCCESS) break;
389 msiobj_release( &view->hdr );
390 return ERROR_SUCCESS;
393 static const WCHAR patch_media_query[] = {
394 'S','E','L','E','C','T',' ','*',' ','F','R','O','M',' ','`','M','e','d','i','a','`',' ',
395 'W','H','E','R','E',' ','`','S','o','u','r','c','e','`',' ','I','S',' ','N','O','T',' ','N','U','L','L',' ',
396 'A','N','D',' ','`','C','a','b','i','n','e','t','`',' ','I','S',' ','N','O','T',' ','N','U','L','L',' ',
397 'O','R','D','E','R',' ','B','Y',' ','`','D','i','s','k','I','d','`',0};
410 static UINT add_patch_media( MSIPACKAGE *package, IStorage *patch )
412 static const WCHAR delete_query[] = {
413 'D','E','L','E','T','E',' ','F','R','O','M',' ','`','M','e','d','i','a','`',' ',
414 'W','H','E','R','E',' ','`','D','i','s','k','I','d','`','=','?',0};
415 static const WCHAR insert_query[] = {
416 'I','N','S','E','R','T',' ','I','N','T','O',' ','`','M','e','d','i','a','`',' ',
417 '(','`','D','i','s','k','I','d','`',',','`','L','a','s','t','S','e','q','u','e','n','c','e','`',',',
418 '`','D','i','s','k','P','r','o','m','p','t','`',',','`','C','a','b','i','n','e','t','`',',',
419 '`','V','o','l','u','m','e','L','a','b','e','l','`',',','`','S','o','u','r','c','e','`',')',' ',
420 'V','A','L','U','E','S',' ','(','?',',','?',',','?',',','?',',','?',',','?',')',0};
424 struct list media_list;
425 struct patch_media *media, *next;
427 r = MSI_DatabaseOpenViewW( package->db, patch_media_query, &view );
428 if (r != ERROR_SUCCESS) return r;
430 r = MSI_ViewExecute( view, 0 );
431 if (r != ERROR_SUCCESS)
433 msiobj_release( &view->hdr );
434 TRACE("query failed %u\n", r);
437 list_init( &media_list );
438 while (MSI_ViewFetch( view, &rec ) == ERROR_SUCCESS)
440 disk_id = MSI_RecordGetInteger( rec, 1 );
441 TRACE("disk_id %u\n", disk_id);
442 if (disk_id >= MSI_INITIAL_MEDIA_TRANSFORM_DISKID)
444 msiobj_release( &rec->hdr );
447 if (!(media = msi_alloc( sizeof( *media )))) goto done;
448 media->disk_id = disk_id;
449 media->last_sequence = MSI_RecordGetInteger( rec, 2 );
450 media->prompt = msi_dup_record_field( rec, 3 );
451 media->cabinet = msi_dup_record_field( rec, 4 );
452 media->volume = msi_dup_record_field( rec, 5 );
453 media->source = msi_dup_record_field( rec, 6 );
455 list_add_tail( &media_list, &media->entry );
456 msiobj_release( &rec->hdr );
458 LIST_FOR_EACH_ENTRY( media, &media_list, struct patch_media, entry )
460 MSIQUERY *delete_view, *insert_view;
462 r = MSI_DatabaseOpenViewW( package->db, delete_query, &delete_view );
463 if (r != ERROR_SUCCESS) goto done;
465 rec = MSI_CreateRecord( 1 );
466 MSI_RecordSetInteger( rec, 1, media->disk_id );
468 r = MSI_ViewExecute( delete_view, rec );
469 msiobj_release( &delete_view->hdr );
470 msiobj_release( &rec->hdr );
471 if (r != ERROR_SUCCESS) goto done;
473 r = MSI_DatabaseOpenViewW( package->db, insert_query, &insert_view );
474 if (r != ERROR_SUCCESS) goto done;
476 disk_id = package->db->media_transform_disk_id;
477 TRACE("disk id %u\n", disk_id);
478 TRACE("last sequence %u\n", media->last_sequence);
479 TRACE("prompt %s\n", debugstr_w(media->prompt));
480 TRACE("cabinet %s\n", debugstr_w(media->cabinet));
481 TRACE("volume %s\n", debugstr_w(media->volume));
482 TRACE("source %s\n", debugstr_w(media->source));
484 rec = MSI_CreateRecord( 6 );
485 MSI_RecordSetInteger( rec, 1, disk_id );
486 MSI_RecordSetInteger( rec, 2, media->last_sequence );
487 MSI_RecordSetStringW( rec, 3, media->prompt );
488 MSI_RecordSetStringW( rec, 4, media->cabinet );
489 MSI_RecordSetStringW( rec, 5, media->volume );
490 MSI_RecordSetStringW( rec, 6, media->source );
492 r = MSI_ViewExecute( insert_view, rec );
493 msiobj_release( &insert_view->hdr );
494 msiobj_release( &rec->hdr );
495 if (r != ERROR_SUCCESS) goto done;
497 r = msi_add_cabinet_stream( package, disk_id, patch, media->cabinet );
498 if (r != ERROR_SUCCESS) WARN("failed to add cabinet stream %u\n", r);
499 package->db->media_transform_disk_id++;
503 msiobj_release( &view->hdr );
504 LIST_FOR_EACH_ENTRY_SAFE( media, next, &media_list, struct patch_media, entry )
506 list_remove( &media->entry );
507 msi_free( media->prompt );
508 msi_free( media->cabinet );
509 msi_free( media->volume );
510 msi_free( media->source );
516 static UINT set_patch_offsets( MSIDATABASE *db )
522 r = MSI_DatabaseOpenViewW( db, patch_media_query, &view );
523 if (r != ERROR_SUCCESS)
526 r = MSI_ViewExecute( view, 0 );
527 if (r != ERROR_SUCCESS)
530 while (MSI_ViewFetch( view, &rec ) == ERROR_SUCCESS)
532 UINT last_sequence = MSI_RecordGetInteger( rec, 2 );
533 struct patch_offset_list *pos;
535 /* FIXME: set/check Source field instead? */
536 if (last_sequence >= MSI_INITIAL_MEDIA_TRANSFORM_OFFSET)
538 msiobj_release( &rec->hdr );
541 pos = patch_offset_list_create();
542 patch_offset_get_files( db, last_sequence, pos );
543 patch_offset_get_patches( db, last_sequence, pos );
546 UINT offset = db->media_transform_offset - pos->min;
547 last_sequence = offset + pos->max;
549 /* FIXME: this is for the patch table, which is not yet properly transformed */
550 last_sequence += pos->min;
551 pos->offset_to_apply = offset;
552 patch_offset_modify_db( db, pos );
554 MSI_RecordSetInteger( rec, 2, last_sequence );
555 r = MSI_ViewModify( view, MSIMODIFY_UPDATE, rec );
556 if (r != ERROR_SUCCESS)
557 ERR("Failed to update Media table entry, expect breakage (%u)\n", r);
558 db->media_transform_offset = last_sequence + 1;
560 patch_offset_list_free( pos );
561 msiobj_release( &rec->hdr );
565 msiobj_release( &view->hdr );
569 UINT msi_apply_patch_db( MSIPACKAGE *package, MSIDATABASE *patch_db, MSIPATCHINFO *patch )
571 UINT i, r = ERROR_SUCCESS;
574 /* apply substorage transforms */
575 substorage = msi_split_string( patch->transforms, ';' );
576 for (i = 0; substorage && substorage[i] && r == ERROR_SUCCESS; i++)
578 r = apply_substorage_transform( package, patch_db, substorage[i] );
579 if (r == ERROR_SUCCESS)
581 add_patch_media( package, patch_db->storage );
582 set_patch_offsets( package->db );
585 msi_free( substorage );
586 if (r != ERROR_SUCCESS)
589 patch_set_media_source_prop( package );
591 patch->state = MSIPATCHSTATE_APPLIED;
592 list_add_tail( &package->patches, &patch->entry );
593 return ERROR_SUCCESS;
596 static UINT msi_apply_patch_package( MSIPACKAGE *package, const WCHAR *file )
598 static const WCHAR dotmsp[] = {'.','m','s','p',0};
599 MSIDATABASE *patch_db = NULL;
600 WCHAR localfile[MAX_PATH];
602 MSIPATCHINFO *patch = NULL;
603 UINT r = ERROR_SUCCESS;
605 TRACE("%p %s\n", package, debugstr_w(file));
607 r = MSI_OpenDatabaseW( file, MSIDBOPEN_READONLY + MSIDBOPEN_PATCHFILE, &patch_db );
608 if (r != ERROR_SUCCESS)
610 ERR("failed to open patch collection %s\n", debugstr_w( file ) );
613 if (!(si = MSI_GetSummaryInformationW( patch_db->storage, 0 )))
615 msiobj_release( &patch_db->hdr );
616 return ERROR_FUNCTION_FAILED;
618 r = msi_check_patch_applicable( package, si );
619 if (r != ERROR_SUCCESS)
621 TRACE("patch not applicable\n");
625 r = msi_parse_patch_summary( si, &patch );
626 if ( r != ERROR_SUCCESS )
629 r = msi_get_local_package_name( localfile, dotmsp );
630 if ( r != ERROR_SUCCESS )
633 TRACE("copying to local package %s\n", debugstr_w(localfile));
635 if (!CopyFileW( file, localfile, FALSE ))
637 ERR("Unable to copy package (%s -> %s) (error %u)\n",
638 debugstr_w(file), debugstr_w(localfile), GetLastError());
642 patch->localfile = strdupW( localfile );
644 r = msi_apply_patch_db( package, patch_db, patch );
645 if (r != ERROR_SUCCESS) WARN("patch failed to apply %u\n", r);
648 msiobj_release( &si->hdr );
649 msiobj_release( &patch_db->hdr );
650 if (patch && r != ERROR_SUCCESS)
652 if (patch->localfile) DeleteFileW( patch->localfile );
653 msi_free( patch->patchcode );
654 msi_free( patch->transforms );
655 msi_free( patch->localfile );
661 /* get the PATCH property, and apply all the patches it specifies */
662 UINT msi_apply_patches( MSIPACKAGE *package )
664 LPWSTR patch_list, *patches;
665 UINT i, r = ERROR_SUCCESS;
667 patch_list = msi_dup_property( package->db, szPatch );
669 TRACE("patches to be applied: %s\n", debugstr_w(patch_list));
671 patches = msi_split_string( patch_list, ';' );
672 for (i = 0; patches && patches[i] && r == ERROR_SUCCESS; i++)
673 r = msi_apply_patch_package( package, patches[i] );
676 msi_free( patch_list );
680 UINT msi_apply_transforms( MSIPACKAGE *package )
682 static const WCHAR szTransforms[] = {'T','R','A','N','S','F','O','R','M','S',0};
683 LPWSTR xform_list, *xforms;
684 UINT i, r = ERROR_SUCCESS;
686 xform_list = msi_dup_property( package->db, szTransforms );
687 xforms = msi_split_string( xform_list, ';' );
689 for (i = 0; xforms && xforms[i] && r == ERROR_SUCCESS; i++)
691 if (xforms[i][0] == ':')
692 r = apply_substorage_transform( package, package->db, xforms[i] );
697 if (!PathIsRelativeW( xforms[i] )) transform = xforms[i];
700 WCHAR *p = strrchrW( package->PackagePath, '\\' );
701 DWORD len = p - package->PackagePath + 1;
703 if (!(transform = msi_alloc( (len + strlenW( xforms[i] ) + 1) * sizeof(WCHAR)) ))
706 msi_free( xform_list );
707 return ERROR_OUTOFMEMORY;
709 memcpy( transform, package->PackagePath, len * sizeof(WCHAR) );
710 memcpy( transform + len, xforms[i], (strlenW( xforms[i] ) + 1) * sizeof(WCHAR) );
712 r = MSI_DatabaseApplyTransformW( package->db, transform, 0 );
713 if (transform != xforms[i]) msi_free( transform );
717 msi_free( xform_list );