xref: /reactos/dll/win32/msi/files.c (revision c2c66aff)
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 }
1418