2 * Implementation of the Microsoft Installer (msi.dll)
4 * Copyright 2005 Aric Stewart for CodeWeavers
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.
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.
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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
23 * Actions dealing with files These are
29 * RemoveDuplicateFiles(TODO)
38 #include "wine/debug.h"
42 #include "msvcrt/fcntl.h"
45 #include "wine/unicode.h"
48 WINE_DEFAULT_DEBUG_CHANNEL(msi);
50 extern const WCHAR szInstallFiles[];
51 extern const WCHAR szDuplicateFiles[];
52 extern const WCHAR szMoveFiles[];
53 extern const WCHAR szPatchFiles[];
54 extern const WCHAR szRemoveDuplicateFiles[];
55 extern const WCHAR szRemoveFiles[];
57 static const WCHAR cszTempFolder[]= {'T','e','m','p','F','o','l','d','e','r',0};
58 static const WCHAR INSTALLPROPERTY_LASTUSEDSOURCE[] = {'L','a','s','t','U','s','e','d','S','o','u','r','c','e',0};
59 static const WCHAR INSTALLPROPERTY_PACKAGENAME[] = {'P','a','c','k','a','g','e','N','a','m','e',0};
61 inline static UINT create_component_directory ( MSIPACKAGE* package, INT component)
63 UINT rc = ERROR_SUCCESS;
67 install_path = resolve_folder(package, package->components[component].Directory,
68 FALSE, FALSE, &folder);
70 return ERROR_FUNCTION_FAILED;
73 if (folder->State == 0)
75 create_full_pathW(install_path);
78 HeapFree(GetProcessHeap(), 0, install_path);
84 * This is a helper function for handling embedded cabinet media
86 static UINT writeout_cabinet_stream(MSIPACKAGE *package, LPCWSTR stream_name,
96 rc = read_raw_stream_data(package->db,stream_name,&data,&size);
97 if (rc != ERROR_SUCCESS)
101 if (MSI_GetPropertyW(package, cszTempFolder, tmp, &write))
102 GetTempPathW(MAX_PATH,tmp);
104 GetTempFileNameW(tmp,stream_name,0,source);
106 track_tempfile(package,strrchrW(source,'\\'), source);
107 the_file = CreateFileW(source, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS,
108 FILE_ATTRIBUTE_NORMAL, NULL);
110 if (the_file == INVALID_HANDLE_VALUE)
112 ERR("Unable to create file %s\n",debugstr_w(source));
113 rc = ERROR_FUNCTION_FAILED;
117 WriteFile(the_file,data,size,&write,NULL);
118 CloseHandle(the_file);
119 TRACE("wrote %li bytes to %s\n",write,debugstr_w(source));
121 HeapFree(GetProcessHeap(),0,data);
126 /* Support functions for FDI functions */
133 static void * cabinet_alloc(ULONG cb)
135 return HeapAlloc(GetProcessHeap(), 0, cb);
138 static void cabinet_free(void *pv)
140 HeapFree(GetProcessHeap(), 0, pv);
143 static INT_PTR cabinet_open(char *pszFile, int oflag, int pmode)
146 DWORD dwShareMode = 0;
147 DWORD dwCreateDisposition = OPEN_EXISTING;
148 switch (oflag & _O_ACCMODE)
151 dwAccess = GENERIC_READ;
152 dwShareMode = FILE_SHARE_READ | FILE_SHARE_DELETE;
155 dwAccess = GENERIC_WRITE;
156 dwShareMode = FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE;
159 dwAccess = GENERIC_READ | GENERIC_WRITE;
160 dwShareMode = FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE;
163 if ((oflag & (_O_CREAT | _O_EXCL)) == (_O_CREAT | _O_EXCL))
164 dwCreateDisposition = CREATE_NEW;
165 else if (oflag & _O_CREAT)
166 dwCreateDisposition = CREATE_ALWAYS;
167 return (INT_PTR)CreateFileA(pszFile, dwAccess, dwShareMode, NULL,
168 dwCreateDisposition, 0, NULL);
171 static UINT cabinet_read(INT_PTR hf, void *pv, UINT cb)
174 if (ReadFile((HANDLE)hf, pv, cb, &dwRead, NULL))
179 static UINT cabinet_write(INT_PTR hf, void *pv, UINT cb)
182 if (WriteFile((HANDLE)hf, pv, cb, &dwWritten, NULL))
187 static int cabinet_close(INT_PTR hf)
189 return CloseHandle((HANDLE)hf) ? 0 : -1;
192 static long cabinet_seek(INT_PTR hf, long dist, int seektype)
194 /* flags are compatible and so are passed straight through */
195 return SetFilePointer((HANDLE)hf, dist, NULL, seektype);
198 static INT_PTR cabinet_notify(FDINOTIFICATIONTYPE fdint, PFDINOTIFICATION pfdin)
204 CabData *data = (CabData*) pfdin->pv;
205 ULONG len = strlen(data->cab_path) + strlen(pfdin->psz1);
211 static const WCHAR tmpprefix[] = {'C','A','B','T','M','P','_',0};
218 given_file = strdupAtoW(pfdin->psz1);
219 index = get_loaded_file(data->package, given_file);
223 ERR("Unknown File in Cabinent (%s)\n",debugstr_w(given_file));
224 HeapFree(GetProcessHeap(),0,given_file);
228 if (!((data->package->files[index].State == 1 ||
229 data->package->files[index].State == 2)))
231 TRACE("Skipping extraction of %s\n",debugstr_w(given_file));
232 HeapFree(GetProcessHeap(),0,given_file);
236 file = cabinet_alloc((len+1)*sizeof(char));
237 strcpy(file, data->cab_path);
238 strcat(file, pfdin->psz1);
240 TRACE("file: %s\n", debugstr_a(file));
242 /* track this file so it can be deleted if not installed */
243 trackpath=strdupAtoW(file);
244 tracknametmp=strdupAtoW(strrchr(file,'\\')+1);
245 trackname = HeapAlloc(GetProcessHeap(),0,(strlenW(tracknametmp) +
246 strlenW(tmpprefix)+1) * sizeof(WCHAR));
248 strcpyW(trackname,tmpprefix);
249 strcatW(trackname,tracknametmp);
251 track_tempfile(data->package, trackname, trackpath);
253 HeapFree(GetProcessHeap(),0,trackpath);
254 HeapFree(GetProcessHeap(),0,trackname);
255 HeapFree(GetProcessHeap(),0,tracknametmp);
258 uirow=MSI_CreateRecord(9);
259 MSI_RecordSetStringW(uirow,1,data->package->files[index].File);
260 uipath = strdupW(data->package->files[index].TargetPath);
261 *(strrchrW(uipath,'\\')+1)=0;
262 MSI_RecordSetStringW(uirow,9,uipath);
263 MSI_RecordSetInteger(uirow,6,data->package->files[index].FileSize);
264 ui_actiondata(data->package,szInstallFiles,uirow);
265 msiobj_release( &uirow->hdr );
266 HeapFree(GetProcessHeap(),0,uipath);
268 ui_progress(data->package,2,data->package->files[index].FileSize,0,0);
270 return cabinet_open(file, _O_WRONLY | _O_CREAT, 0);
272 case fdintCLOSE_FILE_INFO:
276 if (!DosDateTimeToFileTime(pfdin->date, pfdin->time, &ft))
278 if (!LocalFileTimeToFileTime(&ft, &ftLocal))
280 if (!SetFileTime((HANDLE)pfdin->hf, &ftLocal, 0, &ftLocal))
283 cabinet_close(pfdin->hf);
291 /***********************************************************************
292 * extract_cabinet_file
294 * Extract files from a cab file.
296 static BOOL extract_cabinet_file(MSIPACKAGE* package, LPCWSTR source,
306 TRACE("Extracting %s to %s\n",debugstr_w(source), debugstr_w(path));
308 hfdi = FDICreate(cabinet_alloc,
319 ERR("FDICreate failed\n");
323 if (!(cabinet = strdupWtoA( source )))
328 if (!(cab_path = strdupWtoA( path )))
331 HeapFree(GetProcessHeap(), 0, cabinet);
335 data.package = package;
336 data.cab_path = cab_path;
338 ret = FDICopy(hfdi, cabinet, "", 0, cabinet_notify, NULL, &data);
341 ERR("FDICopy failed\n");
345 HeapFree(GetProcessHeap(), 0, cabinet);
346 HeapFree(GetProcessHeap(), 0, cab_path);
351 static VOID set_file_source(MSIPACKAGE* package, MSIFILE* file, MSICOMPONENT*
354 if (file->Attributes & msidbFileAttributesNoncompressed)
357 p = resolve_folder(package, comp->Directory, TRUE, FALSE, NULL);
358 file->SourcePath = build_directory_name(2, p, file->ShortName);
359 HeapFree(GetProcessHeap(),0,p);
362 file->SourcePath = build_directory_name(2, path, file->File);
365 static BOOL check_volume(LPCWSTR path, LPCWSTR want_volume, LPWSTR volume,
369 WCHAR name[MAX_PATH];
372 if (!(path[0] && path[1] == ':'))
379 TRACE("Checking volume %s .. (%s)\n",debugstr_w(drive), debugstr_w(want_volume));
380 type = GetDriveTypeW(drive);
381 TRACE("drive is of type %x\n",type);
383 if (type == DRIVE_UNKNOWN || type == DRIVE_NO_ROOT_DIR ||
384 type == DRIVE_FIXED || type == DRIVE_RAMDISK)
387 GetVolumeInformationW(drive, name, MAX_PATH, NULL, NULL, NULL, NULL, 0);
388 TRACE("Drive contains %s\n", debugstr_w(name));
389 volume = strdupW(name);
392 return (strcmpiW(want_volume,name)==0);
395 static BOOL check_for_sourcefile(LPCWSTR source)
397 DWORD attrib = GetFileAttributesW(source);
398 return (!(attrib == INVALID_FILE_ATTRIBUTES));
401 static UINT ready_volume(MSIPACKAGE* package, LPCWSTR path, LPWSTR last_volume,
402 MSIRECORD *row,UINT *type )
404 LPWSTR volume = NULL;
405 LPCWSTR want_volume = MSI_RecordGetString(row, 5);
406 BOOL ok = check_volume(path, want_volume, volume, type);
408 TRACE("Readying Volume for %s (%s, %s)\n",debugstr_w(path), debugstr_w(want_volume), debugstr_w(last_volume));
410 if (check_for_sourcefile(path) && !ok)
412 FIXME("Found the Sourcefile but not on the correct volume.(%s,%s,%s)\n",
413 debugstr_w(path),debugstr_w(want_volume), debugstr_w(volume));
414 return ERROR_SUCCESS;
423 prompt = MSI_RecordGetString(row,3);
424 msg = generate_error_string(package, 1302, 1, prompt);
425 rc = MessageBoxW(NULL,msg,NULL,MB_OKCANCEL);
426 HeapFree(GetProcessHeap(),0,volume);
427 HeapFree(GetProcessHeap(),0,msg);
429 ok = check_for_sourcefile(path);
431 return ERROR_INSTALL_USEREXIT;
434 HeapFree(GetProcessHeap(),0,last_volume);
435 last_volume = strdupW(volume);
436 return ERROR_SUCCESS;
439 static UINT ready_media_for_file(MSIPACKAGE *package, int fileindex,
442 UINT rc = ERROR_SUCCESS;
444 static WCHAR source[MAX_PATH];
445 static const WCHAR ExecSeqQuery[] =
446 {'S','E','L','E','C','T',' ','*',' ', 'F','R','O','M',' ',
447 '`','M','e','d','i','a','`',' ','W','H','E','R','E',' ',
448 '`','L','a','s','t','S','e','q','u','e','n','c','e','`',' ','>','=',
449 ' ','%', 'i',' ','O','R','D','E','R',' ','B','Y',' ',
450 '`','L','a','s','t','S','e','q','u','e','n','c','e','`',0};
454 static UINT last_sequence = 0;
455 static LPWSTR last_volume = NULL;
456 static LPWSTR last_path = NULL;
457 MSIFILE* file = NULL;
460 static DWORD count = 0;
465 HeapFree(GetProcessHeap(),0,last_path);
466 HeapFree(GetProcessHeap(),0,last_volume);
467 return ERROR_SUCCESS;
470 file = &package->files[fileindex];
472 if (file->Sequence <= last_sequence)
474 set_file_source(package,file,comp,last_path);
475 TRACE("Media already ready (%u, %u)\n",file->Sequence,last_sequence);
476 return ERROR_SUCCESS;
480 row = MSI_QueryGetRecord(package->db, ExecSeqQuery, file->Sequence);
483 TRACE("Unable to query row\n");
484 return ERROR_FUNCTION_FAILED;
487 seq = MSI_RecordGetInteger(row,2);
490 volume = MSI_RecordGetString(row, 5);
491 prompt = MSI_RecordGetString(row, 3);
493 HeapFree(GetProcessHeap(),0,last_path);
496 if (file->Attributes & msidbFileAttributesNoncompressed)
498 last_path = resolve_folder(package, comp->Directory, TRUE, FALSE, NULL);
499 set_file_source(package,file,comp,last_path);
500 rc = ready_volume(package, file->SourcePath, last_volume, row,&type);
502 MsiSourceListAddMediaDiskW(package->ProductCode, NULL,
503 MSIINSTALLCONTEXT_USERMANAGED, MSICODE_PRODUCT, count, volume,
506 if (type == DRIVE_REMOVABLE || type == DRIVE_CDROM ||
507 type == DRIVE_RAMDISK)
508 MsiSourceListSetInfoW(package->ProductCode, NULL,
509 MSIINSTALLCONTEXT_USERMANAGED,
510 MSICODE_PRODUCT|MSISOURCETYPE_MEDIA,
511 INSTALLPROPERTY_LASTUSEDSOURCE, last_path);
513 MsiSourceListSetInfoW(package->ProductCode, NULL,
514 MSIINSTALLCONTEXT_USERMANAGED,
515 MSICODE_PRODUCT|MSISOURCETYPE_NETWORK,
516 INSTALLPROPERTY_LASTUSEDSOURCE, last_path);
517 msiobj_release(&row->hdr);
521 cab = MSI_RecordGetString(row,4);
524 TRACE("Source is CAB %s\n",debugstr_w(cab));
525 /* the stream does not contain the # character */
530 writeout_cabinet_stream(package,&cab[1],source);
531 last_path = strdupW(source);
532 *(strrchrW(last_path,'\\')+1)=0;
534 path = strdupW(package->PackagePath);
535 *strrchrW(path,'\\')=0;
537 MsiSourceListAddMediaDiskW(package->ProductCode, NULL,
538 MSIINSTALLCONTEXT_USERMANAGED, MSICODE_PRODUCT, count,
541 MsiSourceListSetInfoW(package->ProductCode, NULL,
542 MSIINSTALLCONTEXT_USERMANAGED,
543 MSICODE_PRODUCT|MSISOURCETYPE_NETWORK,
544 INSTALLPROPERTY_LASTUSEDSOURCE, path);
546 HeapFree(GetProcessHeap(),0,path);
551 last_path = HeapAlloc(GetProcessHeap(),0,MAX_PATH*sizeof(WCHAR));
552 if (MSI_GetPropertyW(package, cszSourceDir, source, &sz))
554 ERR("No Source dir defined \n");
555 rc = ERROR_FUNCTION_FAILED;
559 strcpyW(last_path,source);
562 rc = ready_volume(package, source, last_volume, row, &type);
563 if (type == DRIVE_REMOVABLE || type == DRIVE_CDROM ||
564 type == DRIVE_RAMDISK)
565 MsiSourceListSetInfoW(package->ProductCode, NULL,
566 MSIINSTALLCONTEXT_USERMANAGED,
567 MSICODE_PRODUCT|MSISOURCETYPE_MEDIA,
568 INSTALLPROPERTY_LASTUSEDSOURCE, last_path);
570 MsiSourceListSetInfoW(package->ProductCode, NULL,
571 MSIINSTALLCONTEXT_USERMANAGED,
572 MSICODE_PRODUCT|MSISOURCETYPE_NETWORK,
573 INSTALLPROPERTY_LASTUSEDSOURCE, last_path);
575 /* extract the cab file into a folder in the temp folder */
577 if (MSI_GetPropertyW(package, cszTempFolder,last_path, &sz)
579 GetTempPathW(MAX_PATH,last_path);
582 rc = !extract_cabinet_file(package, source, last_path);
583 /* reaquire file ptr */
584 file = &package->files[fileindex];
589 last_path = HeapAlloc(GetProcessHeap(),0,MAX_PATH*sizeof(WCHAR));
590 MSI_GetPropertyW(package,cszSourceDir,source,&sz);
591 strcpyW(last_path,source);
592 rc = ready_volume(package, last_path, last_volume, row, &type);
594 if (type == DRIVE_REMOVABLE || type == DRIVE_CDROM ||
595 type == DRIVE_RAMDISK)
596 MsiSourceListSetInfoW(package->ProductCode, NULL,
597 MSIINSTALLCONTEXT_USERMANAGED,
598 MSICODE_PRODUCT|MSISOURCETYPE_MEDIA,
599 INSTALLPROPERTY_LASTUSEDSOURCE, last_path);
601 MsiSourceListSetInfoW(package->ProductCode, NULL,
602 MSIINSTALLCONTEXT_USERMANAGED,
603 MSICODE_PRODUCT|MSISOURCETYPE_NETWORK,
604 INSTALLPROPERTY_LASTUSEDSOURCE, last_path);
606 set_file_source(package, file, comp, last_path);
608 MsiSourceListAddMediaDiskW(package->ProductCode, NULL,
609 MSIINSTALLCONTEXT_USERMANAGED, MSICODE_PRODUCT, count, volume,
612 msiobj_release(&row->hdr);
617 inline static UINT get_file_target(MSIPACKAGE *package, LPCWSTR file_key,
623 return ERROR_INVALID_HANDLE;
625 for (index = 0; index < package->loaded_files; index ++)
627 if (strcmpW(file_key,package->files[index].File)==0)
629 if (package->files[index].State >= 2)
631 *file_source = strdupW(package->files[index].TargetPath);
632 return ERROR_SUCCESS;
635 return ERROR_FILE_NOT_FOUND;
639 return ERROR_FUNCTION_FAILED;
643 * In order to make this work more effeciencly I am going to do this in 2
645 * Pass 1) Correct all the TargetPaths and determin what files are to be
647 * Pass 2) Extract Cabinents and copy files.
649 UINT ACTION_InstallFiles(MSIPACKAGE *package)
651 UINT rc = ERROR_SUCCESS;
656 return ERROR_INVALID_HANDLE;
658 /* increment progress bar each time action data is sent */
659 ui_progress(package,1,1,0,0);
661 /* handle the keys for the SouceList */
662 ptr = strrchrW(package->PackagePath,'\\');
666 MsiSourceListSetInfoW(package->ProductCode, NULL,
667 MSIINSTALLCONTEXT_USERMANAGED,
669 INSTALLPROPERTY_PACKAGENAME, ptr);
671 FIXME("Write DiskPrompt\n");
674 for (index = 0; index < package->loaded_files; index++)
677 MSICOMPONENT* comp = NULL;
679 file = &package->files[index];
684 if (!ACTION_VerifyComponentForAction(package, file->ComponentIndex,
687 ui_progress(package,2,file->FileSize,0,0);
688 TRACE("File %s is not scheduled for install\n",
689 debugstr_w(file->File));
695 if ((file->State == 1) || (file->State == 2))
699 TRACE("Pass 1: %s\n",debugstr_w(file->File));
701 create_component_directory( package, file->ComponentIndex);
703 /* recalculate file paths because things may have changed */
705 if (file->ComponentIndex >= 0)
706 comp = &package->components[file->ComponentIndex];
709 ERR("No Component for file\n");
713 p = resolve_folder(package, comp->Directory, FALSE, FALSE, NULL);
714 HeapFree(GetProcessHeap(),0,file->TargetPath);
716 file->TargetPath = build_directory_name(2, p, file->FileName);
717 HeapFree(GetProcessHeap(),0,p);
722 for (index = 0; index < package->loaded_files; index++)
725 MSICOMPONENT* comp = NULL;
727 file = &package->files[index];
732 if ((file->State == 1) || (file->State == 2))
734 TRACE("Pass 2: %s\n",debugstr_w(file->File));
736 if (file->ComponentIndex >= 0)
737 comp = &package->components[file->ComponentIndex];
739 rc = ready_media_for_file(package, index, comp);
740 if (rc != ERROR_SUCCESS)
742 ERR("Unable to ready media\n");
743 rc = ERROR_FUNCTION_FAILED;
749 * our file table could change here because a new temp file
750 * may have been created. So reaquire our ptr.
752 file = &package->files[index];
754 TRACE("file paths %s to %s\n",debugstr_w(file->SourcePath),
755 debugstr_w(file->TargetPath));
757 if (file->Attributes & msidbFileAttributesNoncompressed)
758 rc = CopyFileW(file->SourcePath,file->TargetPath,FALSE);
760 rc = MoveFileW(file->SourcePath, file->TargetPath);
765 ERR("Unable to move/copy file (%s -> %s) (error %d)\n",
766 debugstr_w(file->SourcePath), debugstr_w(file->TargetPath),
768 if (rc == ERROR_ALREADY_EXISTS && file->State == 2)
770 if (!CopyFileW(file->SourcePath,file->TargetPath,FALSE))
771 ERR("Unable to copy file (%s -> %s) (error %ld)\n",
772 debugstr_w(file->SourcePath),
773 debugstr_w(file->TargetPath), GetLastError());
774 if (!(file->Attributes & msidbFileAttributesNoncompressed))
775 DeleteFileW(file->SourcePath);
778 else if (rc == ERROR_FILE_NOT_FOUND)
780 ERR("Source File Not Found! Continuing\n");
783 else if (file->Attributes & msidbFileAttributesVital)
785 ERR("Ignoring Error and continuing (nonvital file)...\n");
798 ready_media_for_file(NULL, 0, NULL);
802 static UINT ITERATE_DuplicateFiles(MSIRECORD *row, LPVOID param)
804 MSIPACKAGE *package = (MSIPACKAGE*)param;
805 WCHAR *file_source = NULL;
806 WCHAR dest_name[0x100];
807 LPWSTR dest_path, dest;
808 LPCWSTR file_key, component;
813 component = MSI_RecordGetString(row,2);
814 component_index = get_loaded_component(package,component);
816 if (!ACTION_VerifyComponentForAction(package, component_index,
819 TRACE("Skipping copy due to disabled component %s\n",
820 debugstr_w(component));
822 /* the action taken was the same as the current install state */
823 package->components[component_index].Action =
824 package->components[component_index].Installed;
826 return ERROR_SUCCESS;
829 package->components[component_index].Action = INSTALLSTATE_LOCAL;
831 file_key = MSI_RecordGetString(row,3);
834 ERR("Unable to get file key\n");
835 return ERROR_FUNCTION_FAILED;
838 rc = get_file_target(package,file_key,&file_source);
840 if (rc != ERROR_SUCCESS)
842 ERR("Original file unknown %s\n",debugstr_w(file_key));
843 HeapFree(GetProcessHeap(),0,file_source);
844 return ERROR_SUCCESS;
847 if (MSI_RecordIsNull(row,4))
848 strcpyW(dest_name,strrchrW(file_source,'\\')+1);
852 MSI_RecordGetStringW(row,4,dest_name,&sz);
853 reduce_to_longfilename(dest_name);
856 if (MSI_RecordIsNull(row,5))
859 dest_path = strdupW(file_source);
860 p = strrchrW(dest_path,'\\');
867 destkey = MSI_RecordGetString(row,5);
868 dest_path = resolve_folder(package, destkey, FALSE,FALSE,NULL);
872 dest_path = load_dynamic_property(package, destkey, NULL);
875 FIXME("Unable to get destination folder, try AppSearch properties\n");
876 HeapFree(GetProcessHeap(),0,file_source);
877 return ERROR_SUCCESS;
882 dest = build_directory_name(2, dest_path, dest_name);
884 TRACE("Duplicating file %s to %s\n",debugstr_w(file_source),
887 if (strcmpW(file_source,dest))
888 rc = !CopyFileW(file_source,dest,TRUE);
892 if (rc != ERROR_SUCCESS)
893 ERR("Failed to copy file %s -> %s, last error %ld\n", debugstr_w(file_source), debugstr_w(dest_path), GetLastError());
895 FIXME("We should track these duplicate files as well\n");
897 HeapFree(GetProcessHeap(),0,dest_path);
898 HeapFree(GetProcessHeap(),0,dest);
899 HeapFree(GetProcessHeap(),0,file_source);
901 return ERROR_SUCCESS;
904 UINT ACTION_DuplicateFiles(MSIPACKAGE *package)
908 static const WCHAR ExecSeqQuery[] =
909 {'S','E','L','E','C','T',' ','*',' ','F','R','O','M',' ',
910 '`','D','u','p','l','i','c','a','t','e','F','i','l','e','`',0};
913 return ERROR_INVALID_HANDLE;
915 rc = MSI_DatabaseOpenViewW(package->db, ExecSeqQuery, &view);
916 if (rc != ERROR_SUCCESS)
917 return ERROR_SUCCESS;
919 rc = MSI_IterateRecords(view, NULL, ITERATE_DuplicateFiles, package);
920 msiobj_release(&view->hdr);