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};
59 static UINT create_component_directory( MSIPACKAGE* package, MSICOMPONENT *comp )
61 UINT rc = ERROR_SUCCESS;
65 install_path = resolve_folder(package, comp->Directory, FALSE, FALSE, &folder);
67 return ERROR_FUNCTION_FAILED;
70 if (folder->State == 0)
72 create_full_pathW(install_path);
75 HeapFree(GetProcessHeap(), 0, install_path);
81 * This is a helper function for handling embedded cabinet media
83 static UINT writeout_cabinet_stream(MSIPACKAGE *package, LPCWSTR stream_name,
93 rc = read_raw_stream_data(package->db,stream_name,&data,&size);
94 if (rc != ERROR_SUCCESS)
98 if (MSI_GetPropertyW(package, cszTempFolder, tmp, &write))
99 GetTempPathW(MAX_PATH,tmp);
101 GetTempFileNameW(tmp,stream_name,0,source);
103 track_tempfile(package,strrchrW(source,'\\'), source);
104 the_file = CreateFileW(source, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS,
105 FILE_ATTRIBUTE_NORMAL, NULL);
107 if (the_file == INVALID_HANDLE_VALUE)
109 ERR("Unable to create file %s\n",debugstr_w(source));
110 rc = ERROR_FUNCTION_FAILED;
114 WriteFile(the_file,data,size,&write,NULL);
115 CloseHandle(the_file);
116 TRACE("wrote %li bytes to %s\n",write,debugstr_w(source));
118 HeapFree(GetProcessHeap(),0,data);
123 /* Support functions for FDI functions */
130 static void * cabinet_alloc(ULONG cb)
132 return HeapAlloc(GetProcessHeap(), 0, cb);
135 static void cabinet_free(void *pv)
137 HeapFree(GetProcessHeap(), 0, pv);
140 static INT_PTR cabinet_open(char *pszFile, int oflag, int pmode)
143 DWORD dwShareMode = 0;
144 DWORD dwCreateDisposition = OPEN_EXISTING;
145 switch (oflag & _O_ACCMODE)
148 dwAccess = GENERIC_READ;
149 dwShareMode = FILE_SHARE_READ | FILE_SHARE_DELETE;
152 dwAccess = GENERIC_WRITE;
153 dwShareMode = FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE;
156 dwAccess = GENERIC_READ | GENERIC_WRITE;
157 dwShareMode = FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE;
160 if ((oflag & (_O_CREAT | _O_EXCL)) == (_O_CREAT | _O_EXCL))
161 dwCreateDisposition = CREATE_NEW;
162 else if (oflag & _O_CREAT)
163 dwCreateDisposition = CREATE_ALWAYS;
164 return (INT_PTR)CreateFileA(pszFile, dwAccess, dwShareMode, NULL,
165 dwCreateDisposition, 0, NULL);
168 static UINT cabinet_read(INT_PTR hf, void *pv, UINT cb)
171 if (ReadFile((HANDLE)hf, pv, cb, &dwRead, NULL))
176 static UINT cabinet_write(INT_PTR hf, void *pv, UINT cb)
179 if (WriteFile((HANDLE)hf, pv, cb, &dwWritten, NULL))
184 static int cabinet_close(INT_PTR hf)
186 return CloseHandle((HANDLE)hf) ? 0 : -1;
189 static long cabinet_seek(INT_PTR hf, long dist, int seektype)
191 /* flags are compatible and so are passed straight through */
192 return SetFilePointer((HANDLE)hf, dist, NULL, seektype);
195 static INT_PTR cabinet_notify(FDINOTIFICATIONTYPE fdint, PFDINOTIFICATION pfdin)
201 CabData *data = (CabData*) pfdin->pv;
202 ULONG len = strlen(data->cab_path) + strlen(pfdin->psz1);
208 static const WCHAR tmpprefix[] = {'C','A','B','T','M','P','_',0};
215 given_file = strdupAtoW(pfdin->psz1);
216 f = get_loaded_file(data->package, given_file);
220 ERR("Unknown File in Cabinent (%s)\n",debugstr_w(given_file));
221 HeapFree(GetProcessHeap(),0,given_file);
225 if (!((f->State == 1 || f->State == 2)))
227 TRACE("Skipping extraction of %s\n",debugstr_w(given_file));
228 HeapFree(GetProcessHeap(),0,given_file);
232 file = cabinet_alloc((len+1)*sizeof(char));
233 strcpy(file, data->cab_path);
234 strcat(file, pfdin->psz1);
236 TRACE("file: %s\n", debugstr_a(file));
238 /* track this file so it can be deleted if not installed */
239 trackpath=strdupAtoW(file);
240 tracknametmp=strdupAtoW(strrchr(file,'\\')+1);
241 trackname = HeapAlloc(GetProcessHeap(),0,(strlenW(tracknametmp) +
242 strlenW(tmpprefix)+1) * sizeof(WCHAR));
244 strcpyW(trackname,tmpprefix);
245 strcatW(trackname,tracknametmp);
247 track_tempfile(data->package, trackname, trackpath);
249 HeapFree(GetProcessHeap(),0,trackpath);
250 HeapFree(GetProcessHeap(),0,trackname);
251 HeapFree(GetProcessHeap(),0,tracknametmp);
254 uirow=MSI_CreateRecord(9);
255 MSI_RecordSetStringW( uirow, 1, f->FileName );
256 uipath = strdupW( f->TargetPath );
257 *(strrchrW(uipath,'\\')+1)=0;
258 MSI_RecordSetStringW(uirow,9,uipath);
259 MSI_RecordSetInteger( uirow, 6, f->FileSize );
260 ui_actiondata(data->package,szInstallFiles,uirow);
261 msiobj_release( &uirow->hdr );
262 HeapFree(GetProcessHeap(),0,uipath);
264 ui_progress( data->package, 2, f->FileSize, 0, 0);
266 return cabinet_open(file, _O_WRONLY | _O_CREAT, 0);
268 case fdintCLOSE_FILE_INFO:
272 if (!DosDateTimeToFileTime(pfdin->date, pfdin->time, &ft))
274 if (!LocalFileTimeToFileTime(&ft, &ftLocal))
276 if (!SetFileTime((HANDLE)pfdin->hf, &ftLocal, 0, &ftLocal))
279 cabinet_close(pfdin->hf);
287 /***********************************************************************
288 * extract_cabinet_file
290 * Extract files from a cab file.
292 static BOOL extract_cabinet_file(MSIPACKAGE* package, LPCWSTR source,
302 TRACE("Extracting %s to %s\n",debugstr_w(source), debugstr_w(path));
304 hfdi = FDICreate(cabinet_alloc,
315 ERR("FDICreate failed\n");
319 if (!(cabinet = strdupWtoA( source )))
324 if (!(cab_path = strdupWtoA( path )))
327 HeapFree(GetProcessHeap(), 0, cabinet);
331 data.package = package;
332 data.cab_path = cab_path;
334 ret = FDICopy(hfdi, cabinet, "", 0, cabinet_notify, NULL, &data);
337 ERR("FDICopy failed\n");
341 HeapFree(GetProcessHeap(), 0, cabinet);
342 HeapFree(GetProcessHeap(), 0, cab_path);
347 static VOID set_file_source(MSIPACKAGE* package, MSIFILE* file, MSICOMPONENT*
350 if (file->Attributes & msidbFileAttributesNoncompressed)
353 p = resolve_folder(package, comp->Directory, TRUE, FALSE, NULL);
354 file->SourcePath = build_directory_name(2, p, file->ShortName);
355 HeapFree(GetProcessHeap(),0,p);
358 file->SourcePath = build_directory_name(2, path, file->File);
361 static BOOL check_volume(LPCWSTR path, LPCWSTR want_volume, LPWSTR volume,
365 WCHAR name[MAX_PATH];
368 if (!(path[0] && path[1] == ':'))
375 TRACE("Checking volume %s .. (%s)\n",debugstr_w(drive), debugstr_w(want_volume));
376 type = GetDriveTypeW(drive);
377 TRACE("drive is of type %x\n",type);
379 if (type == DRIVE_UNKNOWN || type == DRIVE_NO_ROOT_DIR ||
380 type == DRIVE_FIXED || type == DRIVE_RAMDISK)
383 GetVolumeInformationW(drive, name, MAX_PATH, NULL, NULL, NULL, NULL, 0);
384 TRACE("Drive contains %s\n", debugstr_w(name));
385 volume = strdupW(name);
388 return (strcmpiW(want_volume,name)==0);
391 static BOOL check_for_sourcefile(LPCWSTR source)
393 DWORD attrib = GetFileAttributesW(source);
394 return (!(attrib == INVALID_FILE_ATTRIBUTES));
397 static UINT ready_volume(MSIPACKAGE* package, LPCWSTR path, LPWSTR last_volume,
398 MSIRECORD *row,UINT *type )
400 LPWSTR volume = NULL;
401 LPCWSTR want_volume = MSI_RecordGetString(row, 5);
402 BOOL ok = check_volume(path, want_volume, volume, type);
404 TRACE("Readying Volume for %s (%s, %s)\n",debugstr_w(path), debugstr_w(want_volume), debugstr_w(last_volume));
406 if (check_for_sourcefile(path) && !ok)
408 FIXME("Found the Sourcefile but not on the correct volume.(%s,%s,%s)\n",
409 debugstr_w(path),debugstr_w(want_volume), debugstr_w(volume));
410 return ERROR_SUCCESS;
419 prompt = MSI_RecordGetString(row,3);
420 msg = generate_error_string(package, 1302, 1, prompt);
421 rc = MessageBoxW(NULL,msg,NULL,MB_OKCANCEL);
422 HeapFree(GetProcessHeap(),0,volume);
423 HeapFree(GetProcessHeap(),0,msg);
425 ok = check_for_sourcefile(path);
427 return ERROR_INSTALL_USEREXIT;
430 HeapFree(GetProcessHeap(),0,last_volume);
431 last_volume = strdupW(volume);
432 return ERROR_SUCCESS;
435 static UINT ready_media_for_file(MSIPACKAGE *package, MSIFILE *file,
438 UINT rc = ERROR_SUCCESS;
440 static WCHAR source[MAX_PATH];
441 static const WCHAR ExecSeqQuery[] =
442 {'S','E','L','E','C','T',' ','*',' ', 'F','R','O','M',' ',
443 '`','M','e','d','i','a','`',' ','W','H','E','R','E',' ',
444 '`','L','a','s','t','S','e','q','u','e','n','c','e','`',' ','>','=',
445 ' ','%', 'i',' ','O','R','D','E','R',' ','B','Y',' ',
446 '`','L','a','s','t','S','e','q','u','e','n','c','e','`',0};
450 static UINT last_sequence = 0;
451 static LPWSTR last_volume = NULL;
452 static LPWSTR last_path = NULL;
455 static DWORD count = 0;
460 HeapFree(GetProcessHeap(),0,last_path);
461 HeapFree(GetProcessHeap(),0,last_volume);
466 memset(source,0,sizeof(source));
467 return ERROR_SUCCESS;
470 if (file->Sequence <= last_sequence)
472 set_file_source(package,file,comp,last_path);
473 TRACE("Media already ready (%u, %u)\n",file->Sequence,last_sequence);
474 return ERROR_SUCCESS;
478 row = MSI_QueryGetRecord(package->db, ExecSeqQuery, file->Sequence);
481 TRACE("Unable to query row\n");
482 return ERROR_FUNCTION_FAILED;
485 seq = MSI_RecordGetInteger(row,2);
488 volume = MSI_RecordGetString(row, 5);
489 prompt = MSI_RecordGetString(row, 3);
491 HeapFree(GetProcessHeap(),0,last_path);
494 if (file->Attributes & msidbFileAttributesNoncompressed)
496 last_path = resolve_folder(package, comp->Directory, TRUE, FALSE, NULL);
497 set_file_source(package,file,comp,last_path);
498 rc = ready_volume(package, file->SourcePath, last_volume, row,&type);
500 MsiSourceListAddMediaDiskW(package->ProductCode, NULL,
501 MSIINSTALLCONTEXT_USERMANAGED, MSICODE_PRODUCT, count, volume,
504 if (type == DRIVE_REMOVABLE || type == DRIVE_CDROM ||
505 type == DRIVE_RAMDISK)
506 MsiSourceListSetInfoW(package->ProductCode, NULL,
507 MSIINSTALLCONTEXT_USERMANAGED,
508 MSICODE_PRODUCT|MSISOURCETYPE_MEDIA,
509 INSTALLPROPERTY_LASTUSEDSOURCEW, last_path);
511 MsiSourceListSetInfoW(package->ProductCode, NULL,
512 MSIINSTALLCONTEXT_USERMANAGED,
513 MSICODE_PRODUCT|MSISOURCETYPE_NETWORK,
514 INSTALLPROPERTY_LASTUSEDSOURCEW, last_path);
515 msiobj_release(&row->hdr);
519 cab = MSI_RecordGetString(row,4);
522 TRACE("Source is CAB %s\n",debugstr_w(cab));
523 /* the stream does not contain the # character */
528 writeout_cabinet_stream(package,&cab[1],source);
529 last_path = strdupW(source);
530 *(strrchrW(last_path,'\\')+1)=0;
532 path = load_dynamic_property(package,cszSourceDir,NULL);
534 MsiSourceListAddMediaDiskW(package->ProductCode, NULL,
535 MSIINSTALLCONTEXT_USERMANAGED, MSICODE_PRODUCT, count,
538 MsiSourceListSetInfoW(package->ProductCode, NULL,
539 MSIINSTALLCONTEXT_USERMANAGED,
540 MSICODE_PRODUCT|MSISOURCETYPE_NETWORK,
541 INSTALLPROPERTY_LASTUSEDSOURCEW, path);
543 HeapFree(GetProcessHeap(),0,path);
548 last_path = HeapAlloc(GetProcessHeap(),0,MAX_PATH*sizeof(WCHAR));
549 if (MSI_GetPropertyW(package, cszSourceDir, source, &sz))
551 ERR("No Source dir defined \n");
552 rc = ERROR_FUNCTION_FAILED;
556 strcpyW(last_path,source);
559 rc = ready_volume(package, source, last_volume, row, &type);
560 if (type == DRIVE_REMOVABLE || type == DRIVE_CDROM ||
561 type == DRIVE_RAMDISK)
562 MsiSourceListSetInfoW(package->ProductCode, NULL,
563 MSIINSTALLCONTEXT_USERMANAGED,
564 MSICODE_PRODUCT|MSISOURCETYPE_MEDIA,
565 INSTALLPROPERTY_LASTUSEDSOURCEW, last_path);
567 MsiSourceListSetInfoW(package->ProductCode, NULL,
568 MSIINSTALLCONTEXT_USERMANAGED,
569 MSICODE_PRODUCT|MSISOURCETYPE_NETWORK,
570 INSTALLPROPERTY_LASTUSEDSOURCEW, last_path);
572 /* extract the cab file into a folder in the temp folder */
574 if (MSI_GetPropertyW(package, cszTempFolder,last_path, &sz)
576 GetTempPathW(MAX_PATH,last_path);
579 rc = !extract_cabinet_file(package, source, last_path);
584 last_path = HeapAlloc(GetProcessHeap(),0,MAX_PATH*sizeof(WCHAR));
585 MSI_GetPropertyW(package,cszSourceDir,source,&sz);
586 strcpyW(last_path,source);
587 rc = ready_volume(package, last_path, last_volume, row, &type);
589 if (type == DRIVE_REMOVABLE || type == DRIVE_CDROM ||
590 type == DRIVE_RAMDISK)
591 MsiSourceListSetInfoW(package->ProductCode, NULL,
592 MSIINSTALLCONTEXT_USERMANAGED,
593 MSICODE_PRODUCT|MSISOURCETYPE_MEDIA,
594 INSTALLPROPERTY_LASTUSEDSOURCEW, last_path);
596 MsiSourceListSetInfoW(package->ProductCode, NULL,
597 MSIINSTALLCONTEXT_USERMANAGED,
598 MSICODE_PRODUCT|MSISOURCETYPE_NETWORK,
599 INSTALLPROPERTY_LASTUSEDSOURCEW, last_path);
601 set_file_source(package, file, comp, last_path);
603 MsiSourceListAddMediaDiskW(package->ProductCode, NULL,
604 MSIINSTALLCONTEXT_USERMANAGED, MSICODE_PRODUCT, count, volume,
607 msiobj_release(&row->hdr);
612 static UINT get_file_target(MSIPACKAGE *package, LPCWSTR file_key,
618 return ERROR_INVALID_HANDLE;
620 LIST_FOR_EACH_ENTRY( file, &package->files, MSIFILE, entry )
622 if (lstrcmpW( file_key, file->File )==0)
624 if (file->State >= 2)
626 *file_source = strdupW( file->TargetPath );
627 return ERROR_SUCCESS;
630 return ERROR_FILE_NOT_FOUND;
634 return ERROR_FUNCTION_FAILED;
638 * In order to make this work more effeciencly I am going to do this in 2
640 * Pass 1) Correct all the TargetPaths and determin what files are to be
642 * Pass 2) Extract Cabinents and copy files.
644 UINT ACTION_InstallFiles(MSIPACKAGE *package)
646 UINT rc = ERROR_SUCCESS;
651 return ERROR_INVALID_HANDLE;
653 /* increment progress bar each time action data is sent */
654 ui_progress(package,1,1,0,0);
656 /* handle the keys for the SouceList */
657 ptr = strrchrW(package->PackagePath,'\\');
661 MsiSourceListSetInfoW(package->ProductCode, NULL,
662 MSIINSTALLCONTEXT_USERMANAGED,
664 INSTALLPROPERTY_PACKAGENAMEW, ptr);
666 FIXME("Write DiskPrompt\n");
669 LIST_FOR_EACH_ENTRY( file, &package->files, MSIFILE, entry )
671 MSICOMPONENT* comp = NULL;
673 if (!ACTION_VerifyComponentForAction(package, file->Component,
676 ui_progress(package,2,file->FileSize,0,0);
677 TRACE("File %s is not scheduled for install\n",
678 debugstr_w(file->File));
684 if ((file->State == 1) || (file->State == 2))
688 TRACE("Pass 1: %s\n",debugstr_w(file->File));
690 create_component_directory( package, file->Component );
692 /* recalculate file paths because things may have changed */
694 comp = file->Component;
697 ERR("No Component for file\n");
701 p = resolve_folder(package, comp->Directory, FALSE, FALSE, NULL);
702 HeapFree(GetProcessHeap(),0,file->TargetPath);
704 file->TargetPath = build_directory_name(2, p, file->FileName);
705 HeapFree(GetProcessHeap(),0,p);
710 LIST_FOR_EACH_ENTRY( file, &package->files, MSIFILE, entry )
712 if ((file->State == 1) || (file->State == 2))
714 TRACE("Pass 2: %s\n",debugstr_w(file->File));
716 rc = ready_media_for_file( package, file, file->Component );
717 if (rc != ERROR_SUCCESS)
719 ERR("Unable to ready media\n");
720 rc = ERROR_FUNCTION_FAILED;
724 TRACE("file paths %s to %s\n",debugstr_w(file->SourcePath),
725 debugstr_w(file->TargetPath));
727 if (file->Attributes & msidbFileAttributesNoncompressed)
728 rc = CopyFileW(file->SourcePath,file->TargetPath,FALSE);
730 rc = MoveFileW(file->SourcePath, file->TargetPath);
735 ERR("Unable to move/copy file (%s -> %s) (error %d)\n",
736 debugstr_w(file->SourcePath), debugstr_w(file->TargetPath),
738 if (rc == ERROR_ALREADY_EXISTS && file->State == 2)
740 if (!CopyFileW(file->SourcePath,file->TargetPath,FALSE))
741 ERR("Unable to copy file (%s -> %s) (error %ld)\n",
742 debugstr_w(file->SourcePath),
743 debugstr_w(file->TargetPath), GetLastError());
744 if (!(file->Attributes & msidbFileAttributesNoncompressed))
745 DeleteFileW(file->SourcePath);
748 else if (rc == ERROR_FILE_NOT_FOUND)
750 ERR("Source File Not Found! Continuing\n");
753 else if (file->Attributes & msidbFileAttributesVital)
755 ERR("Ignoring Error and continuing (nonvital file)...\n");
768 ready_media_for_file(NULL, NULL, NULL);
772 static UINT ITERATE_DuplicateFiles(MSIRECORD *row, LPVOID param)
774 MSIPACKAGE *package = (MSIPACKAGE*)param;
775 WCHAR *file_source = NULL;
776 WCHAR dest_name[0x100];
777 LPWSTR dest_path, dest;
778 LPCWSTR file_key, component;
783 component = MSI_RecordGetString(row,2);
784 comp = get_loaded_component(package,component);
786 if (!ACTION_VerifyComponentForAction(package, comp, INSTALLSTATE_LOCAL))
788 TRACE("Skipping copy due to disabled component %s\n",
789 debugstr_w(component));
791 /* the action taken was the same as the current install state */
792 comp->Action = comp->Installed;
794 return ERROR_SUCCESS;
797 comp->Action = INSTALLSTATE_LOCAL;
799 file_key = MSI_RecordGetString(row,3);
802 ERR("Unable to get file key\n");
803 return ERROR_FUNCTION_FAILED;
806 rc = get_file_target(package,file_key,&file_source);
808 if (rc != ERROR_SUCCESS)
810 ERR("Original file unknown %s\n",debugstr_w(file_key));
811 HeapFree(GetProcessHeap(),0,file_source);
812 return ERROR_SUCCESS;
815 if (MSI_RecordIsNull(row,4))
816 strcpyW(dest_name,strrchrW(file_source,'\\')+1);
820 MSI_RecordGetStringW(row,4,dest_name,&sz);
821 reduce_to_longfilename(dest_name);
824 if (MSI_RecordIsNull(row,5))
827 dest_path = strdupW(file_source);
828 p = strrchrW(dest_path,'\\');
835 destkey = MSI_RecordGetString(row,5);
836 dest_path = resolve_folder(package, destkey, FALSE,FALSE,NULL);
840 dest_path = load_dynamic_property(package, destkey, NULL);
843 FIXME("Unable to get destination folder, try AppSearch properties\n");
844 HeapFree(GetProcessHeap(),0,file_source);
845 return ERROR_SUCCESS;
850 dest = build_directory_name(2, dest_path, dest_name);
852 TRACE("Duplicating file %s to %s\n",debugstr_w(file_source),
855 if (strcmpW(file_source,dest))
856 rc = !CopyFileW(file_source,dest,TRUE);
860 if (rc != ERROR_SUCCESS)
861 ERR("Failed to copy file %s -> %s, last error %ld\n", debugstr_w(file_source), debugstr_w(dest_path), GetLastError());
863 FIXME("We should track these duplicate files as well\n");
865 HeapFree(GetProcessHeap(),0,dest_path);
866 HeapFree(GetProcessHeap(),0,dest);
867 HeapFree(GetProcessHeap(),0,file_source);
869 return ERROR_SUCCESS;
872 UINT ACTION_DuplicateFiles(MSIPACKAGE *package)
876 static const WCHAR ExecSeqQuery[] =
877 {'S','E','L','E','C','T',' ','*',' ','F','R','O','M',' ',
878 '`','D','u','p','l','i','c','a','t','e','F','i','l','e','`',0};
881 return ERROR_INVALID_HANDLE;
883 rc = MSI_DatabaseOpenViewW(package->db, ExecSeqQuery, &view);
884 if (rc != ERROR_SUCCESS)
885 return ERROR_SUCCESS;
887 rc = MSI_IterateRecords(view, NULL, ITERATE_DuplicateFiles, package);
888 msiobj_release(&view->hdr);