981d568bd83a43c27182ad635b0b7ec1fd02d952
[reactos.git] / dll / win32 / msi / files.c
1 /*
2 * Implementation of the Microsoft Installer (msi.dll)
3 *
4 * Copyright 2005 Aric Stewart 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
22 /*
23 * Actions dealing with files:
24 *
25 * InstallFiles
26 * DuplicateFiles
27 * MoveFiles
28 * PatchFiles
29 * RemoveDuplicateFiles
30 * RemoveFiles
31 */
32
33 #include "msipriv.h"
34
35 #include <patchapi.h>
36
37 WINE_DEFAULT_DEBUG_CHANNEL(msi);
38
39 static void msi_file_update_ui( MSIPACKAGE *package, MSIFILE *f, const WCHAR *action )
40 {
41 MSIRECORD *uirow;
42
43 uirow = MSI_CreateRecord( 9 );
44 MSI_RecordSetStringW( uirow, 1, f->FileName );
45 MSI_RecordSetStringW( uirow, 9, f->Component->Directory );
46 MSI_RecordSetInteger( uirow, 6, f->FileSize );
47 msi_ui_actiondata( package, action, uirow );
48 msiobj_release( &uirow->hdr );
49 msi_ui_progress( package, 2, f->FileSize, 0, 0 );
50 }
51
52 static BOOL is_registered_patch_media( MSIPACKAGE *package, UINT disk_id )
53 {
54 MSIPATCHINFO *patch;
55
56 LIST_FOR_EACH_ENTRY( patch, &package->patches, MSIPATCHINFO, entry )
57 {
58 if (patch->disk_id == disk_id && patch->registered) return TRUE;
59 }
60 return FALSE;
61 }
62
63 static BOOL is_obsoleted_by_patch( MSIPACKAGE *package, MSIFILE *file )
64 {
65 if (!list_empty( &package->patches ) && file->disk_id < MSI_INITIAL_MEDIA_TRANSFORM_DISKID)
66 {
67 if (!msi_get_property_int( package->db, szInstalled, 0 )) return FALSE;
68 return TRUE;
69 }
70 if (is_registered_patch_media( package, file->disk_id )) return TRUE;
71 return FALSE;
72 }
73
74 static msi_file_state calculate_install_state( MSIPACKAGE *package, MSIFILE *file )
75 {
76 MSICOMPONENT *comp = file->Component;
77 VS_FIXEDFILEINFO *file_version;
78 WCHAR *font_version;
79 msi_file_state state;
80 DWORD size;
81
82 comp->Action = msi_get_component_action( package, comp );
83 if (!comp->Enabled || comp->Action != INSTALLSTATE_LOCAL || (comp->assembly && comp->assembly->installed))
84 {
85 TRACE("skipping %s (not scheduled for install)\n", debugstr_w(file->File));
86 return msifs_skipped;
87 }
88 if (is_obsoleted_by_patch( package, file ))
89 {
90 TRACE("skipping %s (obsoleted by patch)\n", debugstr_w(file->File));
91 return msifs_skipped;
92 }
93 if ((msi_is_global_assembly( comp ) && !comp->assembly->installed) ||
94 GetFileAttributesW( file->TargetPath ) == INVALID_FILE_ATTRIBUTES)
95 {
96 TRACE("installing %s (missing)\n", debugstr_w(file->File));
97 return msifs_missing;
98 }
99 if (file->Version)
100 {
101 if ((file_version = msi_get_disk_file_version( file->TargetPath )))
102 {
103 if (msi_compare_file_versions( file_version, file->Version ) < 0)
104 {
105 TRACE("overwriting %s (new version %s old version %u.%u.%u.%u)\n",
106 debugstr_w(file->File), debugstr_w(file->Version),
107 HIWORD(file_version->dwFileVersionMS), LOWORD(file_version->dwFileVersionMS),
108 HIWORD(file_version->dwFileVersionLS), LOWORD(file_version->dwFileVersionLS));
109 state = msifs_overwrite;
110 }
111 else
112 {
113 TRACE("keeping %s (new version %s old version %u.%u.%u.%u)\n",
114 debugstr_w(file->File), debugstr_w(file->Version),
115 HIWORD(file_version->dwFileVersionMS), LOWORD(file_version->dwFileVersionMS),
116 HIWORD(file_version->dwFileVersionLS), LOWORD(file_version->dwFileVersionLS));
117 state = msifs_present;
118 }
119 msi_free( file_version );
120 return state;
121 }
122 else if ((font_version = msi_font_version_from_file( file->TargetPath )))
123 {
124 if (msi_compare_font_versions( font_version, file->Version ) < 0)
125 {
126 TRACE("overwriting %s (new version %s old version %s)\n",
127 debugstr_w(file->File), debugstr_w(file->Version), debugstr_w(font_version));
128 state = msifs_overwrite;
129 }
130 else
131 {
132 TRACE("keeping %s (new version %s old version %s)\n",
133 debugstr_w(file->File), debugstr_w(file->Version), debugstr_w(font_version));
134 state = msifs_present;
135 }
136 msi_free( font_version );
137 return state;
138 }
139 }
140 if ((size = msi_get_disk_file_size( file->TargetPath )) != file->FileSize)
141 {
142 TRACE("overwriting %s (old size %u new size %u)\n", debugstr_w(file->File), size, file->FileSize);
143 return msifs_overwrite;
144 }
145 if (file->hash.dwFileHashInfoSize)
146 {
147 if (msi_file_hash_matches( file ))
148 {
149 TRACE("keeping %s (hash match)\n", debugstr_w(file->File));
150 return msifs_hashmatch;
151 }
152 else
153 {
154 TRACE("overwriting %s (hash mismatch)\n", debugstr_w(file->File));
155 return msifs_overwrite;
156 }
157 }
158 /* assume present */
159 TRACE("keeping %s\n", debugstr_w(file->File));
160 return msifs_present;
161 }
162
163 static void schedule_install_files(MSIPACKAGE *package)
164 {
165 MSIFILE *file;
166
167 LIST_FOR_EACH_ENTRY(file, &package->files, MSIFILE, entry)
168 {
169 MSICOMPONENT *comp = file->Component;
170
171 file->state = calculate_install_state( package, file );
172 if (file->state == msifs_overwrite && (comp->Attributes & msidbComponentAttributesNeverOverwrite))
173 {
174 TRACE("not overwriting %s\n", debugstr_w(file->TargetPath));
175 file->state = msifs_skipped;
176 }
177 }
178 }
179
180 static UINT copy_file(MSIFILE *file, LPWSTR source)
181 {
182 BOOL ret;
183
184 ret = CopyFileW(source, file->TargetPath, FALSE);
185 if (!ret)
186 return GetLastError();
187
188 SetFileAttributesW(file->TargetPath, FILE_ATTRIBUTE_NORMAL);
189
190 file->state = msifs_installed;
191 return ERROR_SUCCESS;
192 }
193
194 static UINT copy_install_file(MSIPACKAGE *package, MSIFILE *file, LPWSTR source)
195 {
196 UINT gle;
197
198 TRACE("Copying %s to %s\n", debugstr_w(source), debugstr_w(file->TargetPath));
199
200 gle = copy_file(file, source);
201 if (gle == ERROR_SUCCESS)
202 return gle;
203
204 if (gle == ERROR_ALREADY_EXISTS && file->state == msifs_overwrite)
205 {
206 TRACE("overwriting existing file\n");
207 return ERROR_SUCCESS;
208 }
209 else if (gle == ERROR_ACCESS_DENIED)
210 {
211 SetFileAttributesW(file->TargetPath, FILE_ATTRIBUTE_NORMAL);
212
213 gle = copy_file(file, source);
214 TRACE("Overwriting existing file: %d\n", gle);
215 }
216 if (gle == ERROR_SHARING_VIOLATION || gle == ERROR_USER_MAPPED_FILE)
217 {
218 WCHAR *tmpfileW, *pathW, *p;
219 DWORD len;
220
221 TRACE("file in use, scheduling rename operation\n");
222
223 if (!(pathW = strdupW( file->TargetPath ))) return ERROR_OUTOFMEMORY;
224 if ((p = strrchrW(pathW, '\\'))) *p = 0;
225 len = strlenW( pathW ) + 16;
226 if (!(tmpfileW = msi_alloc(len * sizeof(WCHAR))))
227 {
228 msi_free( pathW );
229 return ERROR_OUTOFMEMORY;
230 }
231 if (!GetTempFileNameW( pathW, szMsi, 0, tmpfileW )) tmpfileW[0] = 0;
232 msi_free( pathW );
233
234 if (CopyFileW(source, tmpfileW, FALSE) &&
235 MoveFileExW(file->TargetPath, NULL, MOVEFILE_DELAY_UNTIL_REBOOT) &&
236 MoveFileExW(tmpfileW, file->TargetPath, MOVEFILE_DELAY_UNTIL_REBOOT))
237 {
238 file->state = msifs_installed;
239 package->need_reboot_at_end = 1;
240 gle = ERROR_SUCCESS;
241 }
242 else
243 {
244 gle = GetLastError();
245 WARN("failed to schedule rename operation: %d)\n", gle);
246 DeleteFileW( tmpfileW );
247 }
248 msi_free(tmpfileW);
249 }
250
251 return gle;
252 }
253
254 static UINT msi_create_directory( MSIPACKAGE *package, const WCHAR *dir )
255 {
256 MSIFOLDER *folder;
257 const WCHAR *install_path;
258
259 install_path = msi_get_target_folder( package, dir );
260 if (!install_path) return ERROR_FUNCTION_FAILED;
261
262 folder = msi_get_loaded_folder( package, dir );
263 if (folder->State == FOLDER_STATE_UNINITIALIZED)
264 {
265 msi_create_full_path( install_path );
266 folder->State = FOLDER_STATE_CREATED;
267 }
268 return ERROR_SUCCESS;
269 }
270
271 static MSIFILE *find_file( MSIPACKAGE *package, UINT disk_id, const WCHAR *filename )
272 {
273 MSIFILE *file;
274
275 LIST_FOR_EACH_ENTRY( file, &package->files, MSIFILE, entry )
276 {
277 if (file->disk_id == disk_id &&
278 file->state != msifs_installed &&
279 !strcmpiW( filename, file->File )) return file;
280 }
281 return NULL;
282 }
283
284 static BOOL installfiles_cb(MSIPACKAGE *package, LPCWSTR filename, DWORD action,
285 LPWSTR *path, DWORD *attrs, PVOID user)
286 {
287 MSIFILE *file = *(MSIFILE **)user;
288
289 if (action == MSICABEXTRACT_BEGINEXTRACT)
290 {
291 if (!(file = find_file( package, file->disk_id, filename )))
292 {
293 TRACE("unknown file in cabinet (%s)\n", debugstr_w(filename));
294 return FALSE;
295 }
296 if (file->state != msifs_missing && file->state != msifs_overwrite)
297 return FALSE;
298
299 if (!msi_is_global_assembly( file->Component ))
300 {
301 msi_create_directory( package, file->Component->Directory );
302 }
303 *path = strdupW( file->TargetPath );
304 *attrs = file->Attributes;
305 *(MSIFILE **)user = file;
306 }
307 else if (action == MSICABEXTRACT_FILEEXTRACTED)
308 {
309 if (!msi_is_global_assembly( file->Component )) file->state = msifs_installed;
310 }
311
312 return TRUE;
313 }
314
315 WCHAR *msi_resolve_file_source( MSIPACKAGE *package, MSIFILE *file )
316 {
317 WCHAR *p, *path;
318
319 TRACE("Working to resolve source of file %s\n", debugstr_w(file->File));
320
321 if (file->IsCompressed) return NULL;
322
323 p = msi_resolve_source_folder( package, file->Component->Directory, NULL );
324 path = msi_build_directory_name( 2, p, file->ShortName );
325
326 if (file->LongName && GetFileAttributesW( path ) == INVALID_FILE_ATTRIBUTES)
327 {
328 msi_free( path );
329 path = msi_build_directory_name( 2, p, file->LongName );
330 }
331 msi_free( p );
332 TRACE("file %s source resolves to %s\n", debugstr_w(file->File), debugstr_w(path));
333 return path;
334 }
335
336 /*
337 * ACTION_InstallFiles()
338 *
339 * For efficiency, this is done in two passes:
340 * 1) Correct all the TargetPaths and determine what files are to be installed.
341 * 2) Extract Cabinets and copy files.
342 */
343 UINT ACTION_InstallFiles(MSIPACKAGE *package)
344 {
345 MSIMEDIAINFO *mi;
346 UINT rc = ERROR_SUCCESS;
347 MSIFILE *file;
348
349 schedule_install_files(package);
350 mi = msi_alloc_zero( sizeof(MSIMEDIAINFO) );
351
352 LIST_FOR_EACH_ENTRY( file, &package->files, MSIFILE, entry )
353 {
354 msi_file_update_ui( package, file, szInstallFiles );
355
356 rc = msi_load_media_info( package, file->Sequence, mi );
357 if (rc != ERROR_SUCCESS)
358 {
359 ERR("Unable to load media info for %s (%u)\n", debugstr_w(file->File), rc);
360 rc = ERROR_FUNCTION_FAILED;
361 goto done;
362 }
363
364 if (file->state != msifs_hashmatch &&
365 file->state != msifs_skipped &&
366 (file->state != msifs_present || !msi_get_property_int( package->db, szInstalled, 0 )) &&
367 (rc = ready_media( package, file->IsCompressed, mi )))
368 {
369 ERR("Failed to ready media for %s\n", debugstr_w(file->File));
370 goto done;
371 }
372
373 if (file->state != msifs_missing && !mi->is_continuous && file->state != msifs_overwrite)
374 continue;
375
376 if (file->Sequence > mi->last_sequence || mi->is_continuous ||
377 (file->IsCompressed && !mi->is_extracted))
378 {
379 MSICABDATA data;
380 MSIFILE *cursor = file;
381
382 data.mi = mi;
383 data.package = package;
384 data.cb = installfiles_cb;
385 data.user = &cursor;
386
387 if (file->IsCompressed && !msi_cabextract(package, mi, &data))
388 {
389 ERR("Failed to extract cabinet: %s\n", debugstr_w(mi->cabinet));
390 rc = ERROR_INSTALL_FAILURE;
391 goto done;
392 }
393 }
394
395 if (!file->IsCompressed)
396 {
397 WCHAR *source = msi_resolve_file_source(package, file);
398
399 TRACE("copying %s to %s\n", debugstr_w(source), debugstr_w(file->TargetPath));
400
401 if (!msi_is_global_assembly( file->Component ))
402 {
403 msi_create_directory(package, file->Component->Directory);
404 }
405 rc = copy_install_file(package, file, source);
406 if (rc != ERROR_SUCCESS)
407 {
408 ERR("Failed to copy %s to %s (%u)\n", debugstr_w(source), debugstr_w(file->TargetPath), rc);
409 rc = ERROR_INSTALL_FAILURE;
410 msi_free(source);
411 goto done;
412 }
413 msi_free(source);
414 }
415 else if (!msi_is_global_assembly( file->Component ) &&
416 file->state != msifs_installed && !(file->Attributes & msidbFileAttributesPatchAdded))
417 {
418 ERR("compressed file wasn't installed (%s)\n", debugstr_w(file->File));
419 rc = ERROR_INSTALL_FAILURE;
420 goto done;
421 }
422 }
423 LIST_FOR_EACH_ENTRY( file, &package->files, MSIFILE, entry )
424 {
425 MSICOMPONENT *comp = file->Component;
426
427 if (!msi_is_global_assembly( comp ) || comp->assembly->installed ||
428 (file->state != msifs_missing && file->state != msifs_overwrite)) continue;
429
430 rc = msi_install_assembly( package, comp );
431 if (rc != ERROR_SUCCESS)
432 {
433 ERR("Failed to install assembly\n");
434 rc = ERROR_INSTALL_FAILURE;
435 break;
436 }
437 file->state = msifs_installed;
438 }
439
440 done:
441 msi_free_media_info(mi);
442 return rc;
443 }
444
445 static MSIFILEPATCH *find_filepatch( MSIPACKAGE *package, UINT disk_id, const WCHAR *key )
446 {
447 MSIFILEPATCH *patch;
448
449 LIST_FOR_EACH_ENTRY( patch, &package->filepatches, MSIFILEPATCH, entry )
450 {
451 if (!patch->extracted && patch->disk_id == disk_id && !strcmpW( key, patch->File->File ))
452 return patch;
453 }
454 return NULL;
455 }
456
457 static BOOL patchfiles_cb(MSIPACKAGE *package, LPCWSTR file, DWORD action,
458 LPWSTR *path, DWORD *attrs, PVOID user)
459 {
460 MSIFILEPATCH *patch = *(MSIFILEPATCH **)user;
461
462 if (action == MSICABEXTRACT_BEGINEXTRACT)
463 {
464 MSICOMPONENT *comp;
465
466 if (is_registered_patch_media( package, patch->disk_id ) ||
467 !(patch = find_filepatch( package, patch->disk_id, file ))) return FALSE;
468
469 comp = patch->File->Component;
470 comp->Action = msi_get_component_action( package, comp );
471 if (!comp->Enabled || comp->Action != INSTALLSTATE_LOCAL)
472 {
473 TRACE("file %s component %s not installed or disabled\n",
474 debugstr_w(patch->File->File), debugstr_w(comp->Component));
475 return FALSE;
476 }
477
478 patch->path = msi_create_temp_file( package->db );
479 *path = strdupW( patch->path );
480 *attrs = patch->File->Attributes;
481 *(MSIFILEPATCH **)user = patch;
482 }
483 else if (action == MSICABEXTRACT_FILEEXTRACTED)
484 {
485 patch->extracted = TRUE;
486 }
487
488 return TRUE;
489 }
490
491 static UINT patch_file( MSIPACKAGE *package, MSIFILEPATCH *patch )
492 {
493 UINT r = ERROR_SUCCESS;
494 WCHAR *tmpfile = msi_create_temp_file( package->db );
495
496 if (!tmpfile) return ERROR_INSTALL_FAILURE;
497 if (ApplyPatchToFileW( patch->path, patch->File->TargetPath, tmpfile, 0 ))
498 {
499 DeleteFileW( patch->File->TargetPath );
500 MoveFileW( tmpfile, patch->File->TargetPath );
501 }
502 else
503 {
504 WARN("failed to patch %s: %08x\n", debugstr_w(patch->File->TargetPath), GetLastError());
505 r = ERROR_INSTALL_FAILURE;
506 }
507 DeleteFileW( patch->path );
508 DeleteFileW( tmpfile );
509 msi_free( tmpfile );
510 return r;
511 }
512
513 static UINT patch_assembly( MSIPACKAGE *package, MSIASSEMBLY *assembly, MSIFILEPATCH *patch )
514 {
515 UINT r = ERROR_FUNCTION_FAILED;
516 IAssemblyName *name;
517 IAssemblyEnum *iter;
518
519 if (!(iter = msi_create_assembly_enum( package, assembly->display_name )))
520 return ERROR_FUNCTION_FAILED;
521
522 while ((IAssemblyEnum_GetNextAssembly( iter, NULL, &name, 0 ) == S_OK))
523 {
524 WCHAR *displayname, *path;
525 UINT len = 0;
526 HRESULT hr;
527
528 hr = IAssemblyName_GetDisplayName( name, NULL, &len, 0 );
529 if (hr != E_NOT_SUFFICIENT_BUFFER || !(displayname = msi_alloc( len * sizeof(WCHAR) )))
530 break;
531
532 hr = IAssemblyName_GetDisplayName( name, displayname, &len, 0 );
533 if (FAILED( hr ))
534 {
535 msi_free( displayname );
536 break;
537 }
538
539 if ((path = msi_get_assembly_path( package, displayname )))
540 {
541 if (!CopyFileW( path, patch->File->TargetPath, FALSE ))
542 {
543 ERR("Failed to copy file %s -> %s (%u)\n", debugstr_w(path),
544 debugstr_w(patch->File->TargetPath), GetLastError() );
545 msi_free( path );
546 msi_free( displayname );
547 IAssemblyName_Release( name );
548 break;
549 }
550 r = patch_file( package, patch );
551 msi_free( path );
552 }
553
554 msi_free( displayname );
555 IAssemblyName_Release( name );
556 if (r == ERROR_SUCCESS) break;
557 }
558
559 IAssemblyEnum_Release( iter );
560 return r;
561 }
562
563 UINT ACTION_PatchFiles( MSIPACKAGE *package )
564 {
565 MSIFILEPATCH *patch;
566 MSIMEDIAINFO *mi;
567 UINT rc = ERROR_SUCCESS;
568
569 TRACE("%p\n", package);
570
571 mi = msi_alloc_zero( sizeof(MSIMEDIAINFO) );
572
573 TRACE("extracting files\n");
574
575 LIST_FOR_EACH_ENTRY( patch, &package->filepatches, MSIFILEPATCH, entry )
576 {
577 MSIFILE *file = patch->File;
578 MSICOMPONENT *comp = file->Component;
579
580 rc = msi_load_media_info( package, patch->Sequence, mi );
581 if (rc != ERROR_SUCCESS)
582 {
583 ERR("Unable to load media info for %s (%u)\n", debugstr_w(file->File), rc);
584 rc = ERROR_FUNCTION_FAILED;
585 goto done;
586 }
587 comp->Action = msi_get_component_action( package, comp );
588 if (!comp->Enabled || comp->Action != INSTALLSTATE_LOCAL) continue;
589
590 if (!patch->extracted)
591 {
592 MSICABDATA data;
593 MSIFILEPATCH *cursor = patch;
594
595 rc = ready_media( package, TRUE, mi );
596 if (rc != ERROR_SUCCESS)
597 {
598 ERR("Failed to ready media for %s\n", debugstr_w(file->File));
599 goto done;
600 }
601 data.mi = mi;
602 data.package = package;
603 data.cb = patchfiles_cb;
604 data.user = &cursor;
605
606 if (!msi_cabextract( package, mi, &data ))
607 {
608 ERR("Failed to extract cabinet: %s\n", debugstr_w(mi->cabinet));
609 rc = ERROR_INSTALL_FAILURE;
610 goto done;
611 }
612 }
613 }
614
615 TRACE("applying patches\n");
616
617 LIST_FOR_EACH_ENTRY( patch, &package->filepatches, MSIFILEPATCH, entry )
618 {
619 MSICOMPONENT *comp = patch->File->Component;
620
621 if (!patch->path) continue;
622
623 if (msi_is_global_assembly( comp ))
624 rc = patch_assembly( package, comp->assembly, patch );
625 else
626 rc = patch_file( package, patch );
627
628 if (rc && !(patch->Attributes & msidbPatchAttributesNonVital))
629 {
630 ERR("Failed to apply patch to file: %s\n", debugstr_w(patch->File->File));
631 break;
632 }
633
634 if (msi_is_global_assembly( comp ))
635 {
636 if ((rc = msi_install_assembly( package, comp )))
637 {
638 ERR("Failed to install patched assembly\n");
639 break;
640 }
641 }
642 }
643
644 done:
645 msi_free_media_info(mi);
646 return rc;
647 }
648
649 #define is_dot_dir(x) ((x[0] == '.') && ((x[1] == 0) || ((x[1] == '.') && (x[2] == 0))))
650
651 typedef struct
652 {
653 struct list entry;
654 LPWSTR sourcename;
655 LPWSTR destname;
656 LPWSTR source;
657 LPWSTR dest;
658 } FILE_LIST;
659
660 static BOOL msi_move_file(LPCWSTR source, LPCWSTR dest, int options)
661 {
662 BOOL ret;
663
664 if (GetFileAttributesW(source) == FILE_ATTRIBUTE_DIRECTORY ||
665 GetFileAttributesW(dest) == FILE_ATTRIBUTE_DIRECTORY)
666 {
667 WARN("Source or dest is directory, not moving\n");
668 return FALSE;
669 }
670
671 if (options == msidbMoveFileOptionsMove)
672 {
673 TRACE("moving %s -> %s\n", debugstr_w(source), debugstr_w(dest));
674 ret = MoveFileExW(source, dest, MOVEFILE_REPLACE_EXISTING);
675 if (!ret)
676 {
677 WARN("MoveFile failed: %d\n", GetLastError());
678 return FALSE;
679 }
680 }
681 else
682 {
683 TRACE("copying %s -> %s\n", debugstr_w(source), debugstr_w(dest));
684 ret = CopyFileW(source, dest, FALSE);
685 if (!ret)
686 {
687 WARN("CopyFile failed: %d\n", GetLastError());
688 return FALSE;
689 }
690 }
691
692 return TRUE;
693 }
694
695 static LPWSTR wildcard_to_file(LPWSTR wildcard, LPWSTR filename)
696 {
697 LPWSTR path, ptr;
698 DWORD dirlen, pathlen;
699
700 ptr = strrchrW(wildcard, '\\');
701 dirlen = ptr - wildcard + 1;
702
703 pathlen = dirlen + lstrlenW(filename) + 1;
704 path = msi_alloc(pathlen * sizeof(WCHAR));
705
706 lstrcpynW(path, wildcard, dirlen + 1);
707 lstrcatW(path, filename);
708
709 return path;
710 }
711
712 static void free_file_entry(FILE_LIST *file)
713 {
714 msi_free(file->source);
715 msi_free(file->dest);
716 msi_free(file);
717 }
718
719 static void free_list(FILE_LIST *list)
720 {
721 while (!list_empty(&list->entry))
722 {
723 FILE_LIST *file = LIST_ENTRY(list_head(&list->entry), FILE_LIST, entry);
724
725 list_remove(&file->entry);
726 free_file_entry(file);
727 }
728 }
729
730 static BOOL add_wildcard(FILE_LIST *files, LPWSTR source, LPWSTR dest)
731 {
732 FILE_LIST *new, *file;
733 LPWSTR ptr, filename;
734 DWORD size;
735
736 new = msi_alloc_zero(sizeof(FILE_LIST));
737 if (!new)
738 return FALSE;
739
740 new->source = strdupW(source);
741 ptr = strrchrW(dest, '\\') + 1;
742 filename = strrchrW(new->source, '\\') + 1;
743
744 new->sourcename = filename;
745
746 if (*ptr)
747 new->destname = ptr;
748 else
749 new->destname = new->sourcename;
750
751 size = (ptr - dest) + lstrlenW(filename) + 1;
752 new->dest = msi_alloc(size * sizeof(WCHAR));
753 if (!new->dest)
754 {
755 free_file_entry(new);
756 return FALSE;
757 }
758
759 lstrcpynW(new->dest, dest, ptr - dest + 1);
760 lstrcatW(new->dest, filename);
761
762 if (list_empty(&files->entry))
763 {
764 list_add_head(&files->entry, &new->entry);
765 return TRUE;
766 }
767
768 LIST_FOR_EACH_ENTRY(file, &files->entry, FILE_LIST, entry)
769 {
770 if (strcmpW( source, file->source ) < 0)
771 {
772 list_add_before(&file->entry, &new->entry);
773 return TRUE;
774 }
775 }
776
777 list_add_after(&file->entry, &new->entry);
778 return TRUE;
779 }
780
781 static BOOL move_files_wildcard(LPWSTR source, LPWSTR dest, int options)
782 {
783 WIN32_FIND_DATAW wfd;
784 HANDLE hfile;
785 LPWSTR path;
786 BOOL res;
787 FILE_LIST files, *file;
788 DWORD size;
789
790 hfile = FindFirstFileW(source, &wfd);
791 if (hfile == INVALID_HANDLE_VALUE) return FALSE;
792
793 list_init(&files.entry);
794
795 for (res = TRUE; res; res = FindNextFileW(hfile, &wfd))
796 {
797 if (is_dot_dir(wfd.cFileName)) continue;
798
799 path = wildcard_to_file(source, wfd.cFileName);
800 if (!path)
801 {
802 res = FALSE;
803 goto done;
804 }
805
806 add_wildcard(&files, path, dest);
807 msi_free(path);
808 }
809
810 /* no files match the wildcard */
811 if (list_empty(&files.entry))
812 goto done;
813
814 /* only the first wildcard match gets renamed to dest */
815 file = LIST_ENTRY(list_head(&files.entry), FILE_LIST, entry);
816 size = (strrchrW(file->dest, '\\') - file->dest) + lstrlenW(file->destname) + 2;
817 file->dest = msi_realloc(file->dest, size * sizeof(WCHAR));
818 if (!file->dest)
819 {
820 res = FALSE;
821 goto done;
822 }
823
824 /* file->dest may be shorter after the reallocation, so add a NULL
825 * terminator. This is needed for the call to strrchrW, as there will no
826 * longer be a NULL terminator within the bounds of the allocation in this case.
827 */
828 file->dest[size - 1] = '\0';
829 lstrcpyW(strrchrW(file->dest, '\\') + 1, file->destname);
830
831 while (!list_empty(&files.entry))
832 {
833 file = LIST_ENTRY(list_head(&files.entry), FILE_LIST, entry);
834
835 msi_move_file(file->source, file->dest, options);
836
837 list_remove(&file->entry);
838 free_file_entry(file);
839 }
840
841 res = TRUE;
842
843 done:
844 free_list(&files);
845 FindClose(hfile);
846 return res;
847 }
848
849 void msi_reduce_to_long_filename( WCHAR *filename )
850 {
851 WCHAR *p = strchrW( filename, '|' );
852 if (p) memmove( filename, p + 1, (strlenW( p + 1 ) + 1) * sizeof(WCHAR) );
853 }
854
855 static UINT ITERATE_MoveFiles( MSIRECORD *rec, LPVOID param )
856 {
857 MSIPACKAGE *package = param;
858 MSIRECORD *uirow;
859 MSICOMPONENT *comp;
860 LPCWSTR sourcename, component;
861 LPWSTR sourcedir, destname = NULL, destdir = NULL, source = NULL, dest = NULL;
862 int options;
863 DWORD size;
864 BOOL wildcards;
865
866 component = MSI_RecordGetString(rec, 2);
867 comp = msi_get_loaded_component(package, component);
868 if (!comp)
869 return ERROR_SUCCESS;
870
871 comp->Action = msi_get_component_action( package, comp );
872 if (comp->Action != INSTALLSTATE_LOCAL)
873 {
874 TRACE("component not scheduled for installation %s\n", debugstr_w(component));
875 return ERROR_SUCCESS;
876 }
877
878 sourcename = MSI_RecordGetString(rec, 3);
879 options = MSI_RecordGetInteger(rec, 7);
880
881 sourcedir = msi_dup_property(package->db, MSI_RecordGetString(rec, 5));
882 if (!sourcedir)
883 goto done;
884
885 destdir = msi_dup_property(package->db, MSI_RecordGetString(rec, 6));
886 if (!destdir)
887 goto done;
888
889 if (!sourcename)
890 {
891 if (GetFileAttributesW(sourcedir) == INVALID_FILE_ATTRIBUTES)
892 goto done;
893
894 source = strdupW(sourcedir);
895 if (!source)
896 goto done;
897 }
898 else
899 {
900 size = lstrlenW(sourcedir) + lstrlenW(sourcename) + 2;
901 source = msi_alloc(size * sizeof(WCHAR));
902 if (!source)
903 goto done;
904
905 lstrcpyW(source, sourcedir);
906 if (source[lstrlenW(source) - 1] != '\\')
907 lstrcatW(source, szBackSlash);
908 lstrcatW(source, sourcename);
909 }
910
911 wildcards = strchrW(source, '*') || strchrW(source, '?');
912
913 if (MSI_RecordIsNull(rec, 4))
914 {
915 if (!wildcards)
916 {
917 WCHAR *p;
918 if (sourcename)
919 destname = strdupW(sourcename);
920 else if ((p = strrchrW(sourcedir, '\\')))
921 destname = strdupW(p + 1);
922 else
923 destname = strdupW(sourcedir);
924 if (!destname)
925 goto done;
926 }
927 }
928 else
929 {
930 destname = strdupW(MSI_RecordGetString(rec, 4));
931 if (destname) msi_reduce_to_long_filename(destname);
932 }
933
934 size = 0;
935 if (destname)
936 size = lstrlenW(destname);
937
938 size += lstrlenW(destdir) + 2;
939 dest = msi_alloc(size * sizeof(WCHAR));
940 if (!dest)
941 goto done;
942
943 lstrcpyW(dest, destdir);
944 if (dest[lstrlenW(dest) - 1] != '\\')
945 lstrcatW(dest, szBackSlash);
946
947 if (destname)
948 lstrcatW(dest, destname);
949
950 if (GetFileAttributesW(destdir) == INVALID_FILE_ATTRIBUTES)
951 {
952 if (!msi_create_full_path(destdir))
953 {
954 WARN("failed to create directory %u\n", GetLastError());
955 goto done;
956 }
957 }
958
959 if (!wildcards)
960 msi_move_file(source, dest, options);
961 else
962 move_files_wildcard(source, dest, options);
963
964 done:
965 uirow = MSI_CreateRecord( 9 );
966 MSI_RecordSetStringW( uirow, 1, MSI_RecordGetString(rec, 1) );
967 MSI_RecordSetInteger( uirow, 6, 1 ); /* FIXME */
968 MSI_RecordSetStringW( uirow, 9, destdir );
969 msi_ui_actiondata( package, szMoveFiles, uirow );
970 msiobj_release( &uirow->hdr );
971
972 msi_free(sourcedir);
973 msi_free(destdir);
974 msi_free(destname);
975 msi_free(source);
976 msi_free(dest);
977
978 return ERROR_SUCCESS;
979 }
980
981 UINT ACTION_MoveFiles( MSIPACKAGE *package )
982 {
983 static const WCHAR query[] = {
984 'S','E','L','E','C','T',' ','*',' ','F','R','O','M',' ',
985 '`','M','o','v','e','F','i','l','e','`',0};
986 MSIQUERY *view;
987 UINT rc;
988
989 rc = MSI_DatabaseOpenViewW(package->db, query, &view);
990 if (rc != ERROR_SUCCESS)
991 return ERROR_SUCCESS;
992
993 rc = MSI_IterateRecords(view, NULL, ITERATE_MoveFiles, package);
994 msiobj_release(&view->hdr);
995 return rc;
996 }
997
998 static WCHAR *get_duplicate_filename( MSIPACKAGE *package, MSIRECORD *row, const WCHAR *file_key, const WCHAR *src )
999 {
1000 DWORD len;
1001 WCHAR *dst_name, *dst_path, *dst;
1002
1003 if (MSI_RecordIsNull( row, 4 ))
1004 {
1005 len = strlenW( src ) + 1;
1006 if (!(dst_name = msi_alloc( len * sizeof(WCHAR)))) return NULL;
1007 strcpyW( dst_name, strrchrW( src, '\\' ) + 1 );
1008 }
1009 else
1010 {
1011 MSI_RecordGetStringW( row, 4, NULL, &len );
1012 if (!(dst_name = msi_alloc( ++len * sizeof(WCHAR) ))) return NULL;
1013 MSI_RecordGetStringW( row, 4, dst_name, &len );
1014 msi_reduce_to_long_filename( dst_name );
1015 }
1016
1017 if (MSI_RecordIsNull( row, 5 ))
1018 {
1019 WCHAR *p;
1020 dst_path = strdupW( src );
1021 p = strrchrW( dst_path, '\\' );
1022 if (p) *p = 0;
1023 }
1024 else
1025 {
1026 const WCHAR *dst_key = MSI_RecordGetString( row, 5 );
1027
1028 dst_path = strdupW( msi_get_target_folder( package, dst_key ) );
1029 if (!dst_path)
1030 {
1031 /* try a property */
1032 dst_path = msi_dup_property( package->db, dst_key );
1033 if (!dst_path)
1034 {
1035 FIXME("Unable to get destination folder, try AppSearch properties\n");
1036 msi_free( dst_name );
1037 return NULL;
1038 }
1039 }
1040 }
1041
1042 dst = msi_build_directory_name( 2, dst_path, dst_name );
1043 msi_create_full_path( dst_path );
1044
1045 msi_free( dst_name );
1046 msi_free( dst_path );
1047 return dst;
1048 }
1049
1050 static UINT ITERATE_DuplicateFiles(MSIRECORD *row, LPVOID param)
1051 {
1052 MSIPACKAGE *package = param;
1053 LPWSTR dest;
1054 LPCWSTR file_key, component;
1055 MSICOMPONENT *comp;
1056 MSIRECORD *uirow;
1057 MSIFILE *file;
1058
1059 component = MSI_RecordGetString(row,2);
1060 comp = msi_get_loaded_component(package, component);
1061 if (!comp)
1062 return ERROR_SUCCESS;
1063
1064 comp->Action = msi_get_component_action( package, comp );
1065 if (comp->Action != INSTALLSTATE_LOCAL)
1066 {
1067 TRACE("component not scheduled for installation %s\n", debugstr_w(component));
1068 return ERROR_SUCCESS;
1069 }
1070
1071 file_key = MSI_RecordGetString(row,3);
1072 if (!file_key)
1073 {
1074 ERR("Unable to get file key\n");
1075 return ERROR_FUNCTION_FAILED;
1076 }
1077
1078 file = msi_get_loaded_file( package, file_key );
1079 if (!file)
1080 {
1081 ERR("Original file unknown %s\n", debugstr_w(file_key));
1082 return ERROR_SUCCESS;
1083 }
1084
1085 dest = get_duplicate_filename( package, row, file_key, file->TargetPath );
1086 if (!dest)
1087 {
1088 WARN("Unable to get duplicate filename\n");
1089 return ERROR_SUCCESS;
1090 }
1091
1092 TRACE("Duplicating file %s to %s\n", debugstr_w(file->TargetPath), debugstr_w(dest));
1093
1094 if (!CopyFileW( file->TargetPath, dest, TRUE ))
1095 {
1096 WARN("Failed to copy file %s -> %s (%u)\n",
1097 debugstr_w(file->TargetPath), debugstr_w(dest), GetLastError());
1098 }
1099
1100 FIXME("We should track these duplicate files as well\n");
1101
1102 uirow = MSI_CreateRecord( 9 );
1103 MSI_RecordSetStringW( uirow, 1, MSI_RecordGetString( row, 1 ) );
1104 MSI_RecordSetInteger( uirow, 6, file->FileSize );
1105 MSI_RecordSetStringW( uirow, 9, MSI_RecordGetString( row, 5 ) );
1106 msi_ui_actiondata( package, szDuplicateFiles, uirow );
1107 msiobj_release( &uirow->hdr );
1108
1109 msi_free(dest);
1110 return ERROR_SUCCESS;
1111 }
1112
1113 UINT ACTION_DuplicateFiles(MSIPACKAGE *package)
1114 {
1115 static const WCHAR query[] = {
1116 'S','E','L','E','C','T',' ','*',' ','F','R','O','M',' ',
1117 '`','D','u','p','l','i','c','a','t','e','F','i','l','e','`',0};
1118 MSIQUERY *view;
1119 UINT rc;
1120
1121 rc = MSI_DatabaseOpenViewW(package->db, query, &view);
1122 if (rc != ERROR_SUCCESS)
1123 return ERROR_SUCCESS;
1124
1125 rc = MSI_IterateRecords(view, NULL, ITERATE_DuplicateFiles, package);
1126 msiobj_release(&view->hdr);
1127 return rc;
1128 }
1129
1130 static UINT ITERATE_RemoveDuplicateFiles( MSIRECORD *row, LPVOID param )
1131 {
1132 MSIPACKAGE *package = param;
1133 LPWSTR dest;
1134 LPCWSTR file_key, component;
1135 MSICOMPONENT *comp;
1136 MSIRECORD *uirow;
1137 MSIFILE *file;
1138
1139 component = MSI_RecordGetString( row, 2 );
1140 comp = msi_get_loaded_component( package, component );
1141 if (!comp)
1142 return ERROR_SUCCESS;
1143
1144 comp->Action = msi_get_component_action( package, comp );
1145 if (comp->Action != INSTALLSTATE_ABSENT)
1146 {
1147 TRACE("component not scheduled for removal %s\n", debugstr_w(component));
1148 return ERROR_SUCCESS;
1149 }
1150
1151 file_key = MSI_RecordGetString( row, 3 );
1152 if (!file_key)
1153 {
1154 ERR("Unable to get file key\n");
1155 return ERROR_FUNCTION_FAILED;
1156 }
1157
1158 file = msi_get_loaded_file( package, file_key );
1159 if (!file)
1160 {
1161 ERR("Original file unknown %s\n", debugstr_w(file_key));
1162 return ERROR_SUCCESS;
1163 }
1164
1165 dest = get_duplicate_filename( package, row, file_key, file->TargetPath );
1166 if (!dest)
1167 {
1168 WARN("Unable to get duplicate filename\n");
1169 return ERROR_SUCCESS;
1170 }
1171
1172 TRACE("Removing duplicate %s of %s\n", debugstr_w(dest), debugstr_w(file->TargetPath));
1173
1174 if (!DeleteFileW( dest ))
1175 {
1176 WARN("Failed to delete duplicate file %s (%u)\n", debugstr_w(dest), GetLastError());
1177 }
1178
1179 uirow = MSI_CreateRecord( 9 );
1180 MSI_RecordSetStringW( uirow, 1, MSI_RecordGetString( row, 1 ) );
1181 MSI_RecordSetStringW( uirow, 9, MSI_RecordGetString( row, 5 ) );
1182 msi_ui_actiondata( package, szRemoveDuplicateFiles, uirow );
1183 msiobj_release( &uirow->hdr );
1184
1185 msi_free(dest);
1186 return ERROR_SUCCESS;
1187 }
1188
1189 UINT ACTION_RemoveDuplicateFiles( MSIPACKAGE *package )
1190 {
1191 static const WCHAR query[] = {
1192 'S','E','L','E','C','T',' ','*',' ','F','R','O','M',' ',
1193 '`','D','u','p','l','i','c','a','t','e','F','i','l','e','`',0};
1194 MSIQUERY *view;
1195 UINT rc;
1196
1197 rc = MSI_DatabaseOpenViewW( package->db, query, &view );
1198 if (rc != ERROR_SUCCESS)
1199 return ERROR_SUCCESS;
1200
1201 rc = MSI_IterateRecords( view, NULL, ITERATE_RemoveDuplicateFiles, package );
1202 msiobj_release( &view->hdr );
1203 return rc;
1204 }
1205
1206 static BOOL verify_comp_for_removal(MSICOMPONENT *comp, UINT install_mode)
1207 {
1208 /* special case */
1209 if (comp->Action != INSTALLSTATE_SOURCE &&
1210 comp->Attributes & msidbComponentAttributesSourceOnly &&
1211 (install_mode == msidbRemoveFileInstallModeOnRemove ||
1212 install_mode == msidbRemoveFileInstallModeOnBoth)) return TRUE;
1213
1214 switch (comp->Action)
1215 {
1216 case INSTALLSTATE_LOCAL:
1217 case INSTALLSTATE_SOURCE:
1218 if (install_mode == msidbRemoveFileInstallModeOnInstall ||
1219 install_mode == msidbRemoveFileInstallModeOnBoth) return TRUE;
1220 break;
1221 case INSTALLSTATE_ABSENT:
1222 if (install_mode == msidbRemoveFileInstallModeOnRemove ||
1223 install_mode == msidbRemoveFileInstallModeOnBoth) return TRUE;
1224 break;
1225 default: break;
1226 }
1227 return FALSE;
1228 }
1229
1230 static UINT ITERATE_RemoveFiles(MSIRECORD *row, LPVOID param)
1231 {
1232 MSIPACKAGE *package = param;
1233 MSICOMPONENT *comp;
1234 MSIRECORD *uirow;
1235 LPCWSTR component, dirprop;
1236 UINT install_mode;
1237 LPWSTR dir = NULL, path = NULL, filename = NULL;
1238 DWORD size;
1239 UINT ret = ERROR_SUCCESS;
1240
1241 component = MSI_RecordGetString(row, 2);
1242 dirprop = MSI_RecordGetString(row, 4);
1243 install_mode = MSI_RecordGetInteger(row, 5);
1244
1245 comp = msi_get_loaded_component(package, component);
1246 if (!comp)
1247 return ERROR_SUCCESS;
1248
1249 comp->Action = msi_get_component_action( package, comp );
1250 if (!verify_comp_for_removal(comp, install_mode))
1251 {
1252 TRACE("Skipping removal due to install mode\n");
1253 return ERROR_SUCCESS;
1254 }
1255 if (comp->assembly && !comp->assembly->application)
1256 {
1257 return ERROR_SUCCESS;
1258 }
1259 if (comp->Attributes & msidbComponentAttributesPermanent)
1260 {
1261 TRACE("permanent component, not removing file\n");
1262 return ERROR_SUCCESS;
1263 }
1264
1265 dir = msi_dup_property(package->db, dirprop);
1266 if (!dir)
1267 {
1268 WARN("directory property has no value\n");
1269 return ERROR_SUCCESS;
1270 }
1271 size = 0;
1272 if ((filename = strdupW( MSI_RecordGetString(row, 3) )))
1273 {
1274 msi_reduce_to_long_filename( filename );
1275 size = lstrlenW( filename );
1276 }
1277 size += lstrlenW(dir) + 2;
1278 path = msi_alloc(size * sizeof(WCHAR));
1279 if (!path)
1280 {
1281 ret = ERROR_OUTOFMEMORY;
1282 goto done;
1283 }
1284
1285 if (filename)
1286 {
1287 lstrcpyW(path, dir);
1288 PathAddBackslashW(path);
1289 lstrcatW(path, filename);
1290
1291 TRACE("Deleting misc file: %s\n", debugstr_w(path));
1292 DeleteFileW(path);
1293 }
1294 else
1295 {
1296 TRACE("Removing misc directory: %s\n", debugstr_w(dir));
1297 RemoveDirectoryW(dir);
1298 }
1299
1300 done:
1301 uirow = MSI_CreateRecord( 9 );
1302 MSI_RecordSetStringW( uirow, 1, MSI_RecordGetString(row, 1) );
1303 MSI_RecordSetStringW( uirow, 9, dir );
1304 msi_ui_actiondata( package, szRemoveFiles, uirow );
1305 msiobj_release( &uirow->hdr );
1306
1307 msi_free(filename);
1308 msi_free(path);
1309 msi_free(dir);
1310 return ret;
1311 }
1312
1313 static void remove_folder( MSIFOLDER *folder )
1314 {
1315 FolderList *fl;
1316
1317 LIST_FOR_EACH_ENTRY( fl, &folder->children, FolderList, entry )
1318 {
1319 remove_folder( fl->folder );
1320 }
1321 if (!folder->persistent && folder->State != FOLDER_STATE_REMOVED)
1322 {
1323 if (RemoveDirectoryW( folder->ResolvedTarget )) folder->State = FOLDER_STATE_REMOVED;
1324 }
1325 }
1326
1327 UINT ACTION_RemoveFiles( MSIPACKAGE *package )
1328 {
1329 static const WCHAR query[] = {
1330 'S','E','L','E','C','T',' ','*',' ','F','R','O','M',' ',
1331 '`','R','e','m','o','v','e','F','i','l','e','`',0};
1332 MSIQUERY *view;
1333 MSICOMPONENT *comp;
1334 MSIFILE *file;
1335 UINT r;
1336
1337 r = MSI_DatabaseOpenViewW(package->db, query, &view);
1338 if (r == ERROR_SUCCESS)
1339 {
1340 r = MSI_IterateRecords(view, NULL, ITERATE_RemoveFiles, package);
1341 msiobj_release(&view->hdr);
1342 if (r != ERROR_SUCCESS)
1343 return r;
1344 }
1345
1346 LIST_FOR_EACH_ENTRY( file, &package->files, MSIFILE, entry )
1347 {
1348 MSIRECORD *uirow;
1349 VS_FIXEDFILEINFO *ver;
1350
1351 comp = file->Component;
1352 msi_file_update_ui( package, file, szRemoveFiles );
1353
1354 comp->Action = msi_get_component_action( package, comp );
1355 if (comp->Action != INSTALLSTATE_ABSENT || comp->Installed == INSTALLSTATE_SOURCE)
1356 continue;
1357
1358 if (comp->assembly && !comp->assembly->application)
1359 continue;
1360
1361 if (comp->Attributes & msidbComponentAttributesPermanent)
1362 {
1363 TRACE("permanent component, not removing file\n");
1364 continue;
1365 }
1366
1367 if (file->Version)
1368 {
1369 ver = msi_get_disk_file_version( file->TargetPath );
1370 if (ver && msi_compare_file_versions( ver, file->Version ) > 0)
1371 {
1372 TRACE("newer version detected, not removing file\n");
1373 msi_free( ver );
1374 continue;
1375 }
1376 msi_free( ver );
1377 }
1378
1379 if (file->state == msifs_installed)
1380 WARN("removing installed file %s\n", debugstr_w(file->TargetPath));
1381
1382 TRACE("removing %s\n", debugstr_w(file->File) );
1383
1384 SetFileAttributesW( file->TargetPath, FILE_ATTRIBUTE_NORMAL );
1385 if (!DeleteFileW( file->TargetPath ))
1386 {
1387 WARN("failed to delete %s (%u)\n", debugstr_w(file->TargetPath), GetLastError());
1388 }
1389 file->state = msifs_missing;
1390
1391 uirow = MSI_CreateRecord( 9 );
1392 MSI_RecordSetStringW( uirow, 1, file->FileName );
1393 MSI_RecordSetStringW( uirow, 9, comp->Directory );
1394 msi_ui_actiondata( package, szRemoveFiles, uirow );
1395 msiobj_release( &uirow->hdr );
1396 }
1397
1398 LIST_FOR_EACH_ENTRY( comp, &package->components, MSICOMPONENT, entry )
1399 {
1400 comp->Action = msi_get_component_action( package, comp );
1401 if (comp->Action != INSTALLSTATE_ABSENT) continue;
1402
1403 if (comp->Attributes & msidbComponentAttributesPermanent)
1404 {
1405 TRACE("permanent component, not removing directory\n");
1406 continue;
1407 }
1408 if (comp->assembly && !comp->assembly->application)
1409 msi_uninstall_assembly( package, comp );
1410 else
1411 {
1412 MSIFOLDER *folder = msi_get_loaded_folder( package, comp->Directory );
1413 remove_folder( folder );
1414 }
1415 }
1416 return ERROR_SUCCESS;
1417 }