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