2 * Implementation of the Microsoft Installer (msi.dll)
4 * Copyright 2002-2004 Mike McCormack 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
29 #include "wine/debug.h"
30 #include "wine/unicode.h"
43 WINE_DEFAULT_DEBUG_CHANNEL(msidb);
45 #define MSIFIELD_NULL 0
46 #define MSIFIELD_INT 1
47 #define MSIFIELD_WSTR 3
48 #define MSIFIELD_STREAM 4
49 #define MSIFIELD_INTPTR 5
51 static void MSI_FreeField( MSIFIELD *field )
60 msi_free( field->u.szwVal);
63 IStream_Release( field->u.stream );
66 ERR("Invalid field type %d\n", field->type);
70 void MSI_CloseRecord( MSIOBJECTHDR *arg )
72 MSIRECORD *rec = (MSIRECORD *) arg;
75 for( i=0; i<=rec->count; i++ )
76 MSI_FreeField( &rec->fields[i] );
79 MSIRECORD *MSI_CreateRecord( UINT cParams )
84 TRACE("%d\n", cParams);
89 len = sizeof (MSIRECORD) + sizeof (MSIFIELD)*cParams;
90 rec = alloc_msiobject( MSIHANDLETYPE_RECORD, len, MSI_CloseRecord );
96 MSIHANDLE WINAPI MsiCreateRecord( UINT cParams )
101 TRACE("%d\n", cParams);
103 rec = MSI_CreateRecord( cParams );
106 ret = alloc_msihandle( &rec->hdr );
107 msiobj_release( &rec->hdr );
112 UINT MSI_RecordGetFieldCount( const MSIRECORD *rec )
117 UINT WINAPI MsiRecordGetFieldCount( MSIHANDLE handle )
122 TRACE("%d\n", handle );
124 rec = msihandle2msiinfo( handle, MSIHANDLETYPE_RECORD );
128 msiobj_lock( &rec->hdr );
129 ret = MSI_RecordGetFieldCount( rec );
130 msiobj_unlock( &rec->hdr );
131 msiobj_release( &rec->hdr );
136 static BOOL string2intW( LPCWSTR str, int *out )
141 if( *p == '-' ) /* skip the minus sign */
145 if( (*p < '0') || (*p > '9') )
152 if( str[0] == '-' ) /* check if it's negative */
159 WCHAR *msi_strdupW( const WCHAR *value, int len )
163 if (!value) return NULL;
164 if (!(ret = msi_alloc( (len + 1) * sizeof(WCHAR) ))) return NULL;
165 memcpy( ret, value, len * sizeof(WCHAR) );
170 UINT MSI_RecordCopyField( MSIRECORD *in_rec, UINT in_n,
171 MSIRECORD *out_rec, UINT out_n )
173 UINT r = ERROR_SUCCESS;
175 msiobj_lock( &in_rec->hdr );
177 if ( in_n > in_rec->count || out_n > out_rec->count )
178 r = ERROR_FUNCTION_FAILED;
179 else if ( in_rec != out_rec || in_n != out_n )
184 in = &in_rec->fields[in_n];
185 out = &out_rec->fields[out_n];
192 out->u.iVal = in->u.iVal;
194 case MSIFIELD_INTPTR:
195 out->u.pVal = in->u.pVal;
198 if ((str = msi_strdupW( in->u.szwVal, in->len )))
203 else r = ERROR_OUTOFMEMORY;
205 case MSIFIELD_STREAM:
206 IStream_AddRef( in->u.stream );
207 out->u.stream = in->u.stream;
210 ERR("invalid field type %d\n", in->type);
212 if (r == ERROR_SUCCESS)
213 out->type = in->type;
216 msiobj_unlock( &in_rec->hdr );
220 INT_PTR MSI_RecordGetIntPtr( MSIRECORD *rec, UINT iField )
224 TRACE( "%p %d\n", rec, iField );
226 if( iField > rec->count )
229 switch( rec->fields[iField].type )
232 return rec->fields[iField].u.iVal;
233 case MSIFIELD_INTPTR:
234 return rec->fields[iField].u.pVal;
236 if( string2intW( rec->fields[iField].u.szwVal, &ret ) )
246 int MSI_RecordGetInteger( MSIRECORD *rec, UINT iField)
250 TRACE("%p %d\n", rec, iField );
252 if( iField > rec->count )
253 return MSI_NULL_INTEGER;
255 switch( rec->fields[iField].type )
258 return rec->fields[iField].u.iVal;
259 case MSIFIELD_INTPTR:
260 return rec->fields[iField].u.pVal;
262 if( string2intW( rec->fields[iField].u.szwVal, &ret ) )
264 return MSI_NULL_INTEGER;
269 return MSI_NULL_INTEGER;
272 int WINAPI MsiRecordGetInteger( MSIHANDLE handle, UINT iField)
277 TRACE("%d %d\n", handle, iField );
279 rec = msihandle2msiinfo( handle, MSIHANDLETYPE_RECORD );
281 return MSI_NULL_INTEGER;
283 msiobj_lock( &rec->hdr );
284 ret = MSI_RecordGetInteger( rec, iField );
285 msiobj_unlock( &rec->hdr );
286 msiobj_release( &rec->hdr );
291 UINT WINAPI MsiRecordClearData( MSIHANDLE handle )
296 TRACE("%d\n", handle );
298 rec = msihandle2msiinfo( handle, MSIHANDLETYPE_RECORD );
300 return ERROR_INVALID_HANDLE;
302 msiobj_lock( &rec->hdr );
303 for( i=0; i<=rec->count; i++)
305 MSI_FreeField( &rec->fields[i] );
306 rec->fields[i].type = MSIFIELD_NULL;
307 rec->fields[i].u.iVal = 0;
309 msiobj_unlock( &rec->hdr );
310 msiobj_release( &rec->hdr );
312 return ERROR_SUCCESS;
315 UINT MSI_RecordSetIntPtr( MSIRECORD *rec, UINT iField, INT_PTR pVal )
317 TRACE("%p %u %ld\n", rec, iField, pVal);
319 if( iField > rec->count )
320 return ERROR_INVALID_PARAMETER;
322 MSI_FreeField( &rec->fields[iField] );
323 rec->fields[iField].type = MSIFIELD_INTPTR;
324 rec->fields[iField].u.pVal = pVal;
326 return ERROR_SUCCESS;
329 UINT MSI_RecordSetInteger( MSIRECORD *rec, UINT iField, int iVal )
331 TRACE("%p %u %d\n", rec, iField, iVal);
333 if( iField > rec->count )
334 return ERROR_INVALID_PARAMETER;
336 MSI_FreeField( &rec->fields[iField] );
337 rec->fields[iField].type = MSIFIELD_INT;
338 rec->fields[iField].u.iVal = iVal;
340 return ERROR_SUCCESS;
343 UINT WINAPI MsiRecordSetInteger( MSIHANDLE handle, UINT iField, int iVal )
348 TRACE("%d %u %d\n", handle, iField, iVal);
350 rec = msihandle2msiinfo( handle, MSIHANDLETYPE_RECORD );
352 return ERROR_INVALID_HANDLE;
354 msiobj_lock( &rec->hdr );
355 ret = MSI_RecordSetInteger( rec, iField, iVal );
356 msiobj_unlock( &rec->hdr );
357 msiobj_release( &rec->hdr );
361 BOOL MSI_RecordIsNull( MSIRECORD *rec, UINT iField )
365 TRACE("%p %d\n", rec, iField );
367 r = ( iField > rec->count ) ||
368 ( rec->fields[iField].type == MSIFIELD_NULL );
373 BOOL WINAPI MsiRecordIsNull( MSIHANDLE handle, UINT iField )
378 TRACE("%d %d\n", handle, iField );
380 rec = msihandle2msiinfo( handle, MSIHANDLETYPE_RECORD );
383 msiobj_lock( &rec->hdr );
384 ret = MSI_RecordIsNull( rec, iField );
385 msiobj_unlock( &rec->hdr );
386 msiobj_release( &rec->hdr );
391 UINT MSI_RecordGetStringA(MSIRECORD *rec, UINT iField,
392 LPSTR szValue, LPDWORD pcchValue)
394 UINT len = 0, ret = ERROR_SUCCESS;
397 TRACE("%p %d %p %p\n", rec, iField, szValue, pcchValue);
399 if( iField > rec->count )
401 if ( szValue && *pcchValue > 0 )
405 return ERROR_SUCCESS;
408 switch( rec->fields[iField].type )
411 wsprintfA(buffer, "%d", rec->fields[iField].u.iVal);
412 len = lstrlenA( buffer );
414 lstrcpynA(szValue, buffer, *pcchValue);
417 len = WideCharToMultiByte( CP_ACP, 0, rec->fields[iField].u.szwVal,
418 rec->fields[iField].len + 1, NULL, 0 , NULL, NULL );
420 WideCharToMultiByte( CP_ACP, 0, rec->fields[iField].u.szwVal,
421 rec->fields[iField].len + 1, szValue, *pcchValue, NULL, NULL );
422 if( szValue && *pcchValue && len>*pcchValue )
423 szValue[*pcchValue-1] = 0;
428 if( szValue && *pcchValue > 0 )
432 ret = ERROR_INVALID_PARAMETER;
436 if( szValue && *pcchValue <= len )
437 ret = ERROR_MORE_DATA;
443 UINT WINAPI MsiRecordGetStringA(MSIHANDLE handle, UINT iField,
444 LPSTR szValue, LPDWORD pcchValue)
449 TRACE("%d %d %p %p\n", handle, iField, szValue, pcchValue);
451 rec = msihandle2msiinfo( handle, MSIHANDLETYPE_RECORD );
453 return ERROR_INVALID_HANDLE;
454 msiobj_lock( &rec->hdr );
455 ret = MSI_RecordGetStringA( rec, iField, szValue, pcchValue);
456 msiobj_unlock( &rec->hdr );
457 msiobj_release( &rec->hdr );
461 const WCHAR *msi_record_get_string( const MSIRECORD *rec, UINT field, int *len )
463 if (field > rec->count)
466 if (rec->fields[field].type != MSIFIELD_WSTR)
469 if (len) *len = rec->fields[field].len;
471 return rec->fields[field].u.szwVal;
474 const WCHAR *MSI_RecordGetString( const MSIRECORD *rec, UINT iField )
476 return msi_record_get_string( rec, iField, NULL );
479 UINT MSI_RecordGetStringW(MSIRECORD *rec, UINT iField,
480 LPWSTR szValue, LPDWORD pcchValue)
482 static const WCHAR szFormat[] = {'%','d',0};
483 UINT len = 0, ret = ERROR_SUCCESS;
486 TRACE("%p %d %p %p\n", rec, iField, szValue, pcchValue);
488 if( iField > rec->count )
490 if ( szValue && *pcchValue > 0 )
494 return ERROR_SUCCESS;
497 switch( rec->fields[iField].type )
500 wsprintfW(buffer, szFormat, rec->fields[iField].u.iVal);
501 len = lstrlenW( buffer );
503 lstrcpynW(szValue, buffer, *pcchValue);
506 len = rec->fields[iField].len;
508 memcpy( szValue, rec->fields[iField].u.szwVal, min(len + 1, *pcchValue) * sizeof(WCHAR) );
511 if( szValue && *pcchValue > 0 )
518 if( szValue && *pcchValue <= len )
519 ret = ERROR_MORE_DATA;
525 UINT WINAPI MsiRecordGetStringW(MSIHANDLE handle, UINT iField,
526 LPWSTR szValue, LPDWORD pcchValue)
531 TRACE("%d %d %p %p\n", handle, iField, szValue, pcchValue);
533 rec = msihandle2msiinfo( handle, MSIHANDLETYPE_RECORD );
535 return ERROR_INVALID_HANDLE;
537 msiobj_lock( &rec->hdr );
538 ret = MSI_RecordGetStringW( rec, iField, szValue, pcchValue );
539 msiobj_unlock( &rec->hdr );
540 msiobj_release( &rec->hdr );
544 static UINT msi_get_stream_size( IStream *stm )
549 r = IStream_Stat( stm, &stat, STATFLAG_NONAME );
552 return stat.cbSize.QuadPart;
555 static UINT MSI_RecordDataSize(MSIRECORD *rec, UINT iField)
557 TRACE("%p %d\n", rec, iField);
559 if( iField > rec->count )
562 switch( rec->fields[iField].type )
567 return rec->fields[iField].len;
570 case MSIFIELD_STREAM:
571 return msi_get_stream_size( rec->fields[iField].u.stream );
576 UINT WINAPI MsiRecordDataSize(MSIHANDLE handle, UINT iField)
581 TRACE("%d %d\n", handle, iField);
583 rec = msihandle2msiinfo( handle, MSIHANDLETYPE_RECORD );
586 msiobj_lock( &rec->hdr );
587 ret = MSI_RecordDataSize( rec, iField);
588 msiobj_unlock( &rec->hdr );
589 msiobj_release( &rec->hdr );
593 UINT WINAPI MsiRecordSetStringA( MSIHANDLE handle, UINT iField, LPCSTR szValue )
595 WCHAR *valueW = NULL;
599 TRACE("%d %d %s\n", handle, iField, debugstr_a(szValue));
601 if (szValue && !(valueW = strdupAtoW( szValue ))) return ERROR_OUTOFMEMORY;
603 rec = msihandle2msiinfo( handle, MSIHANDLETYPE_RECORD );
607 return ERROR_INVALID_HANDLE;
609 msiobj_lock( &rec->hdr );
610 ret = MSI_RecordSetStringW( rec, iField, valueW );
611 msiobj_unlock( &rec->hdr );
612 msiobj_release( &rec->hdr );
617 UINT msi_record_set_string( MSIRECORD *rec, UINT field, const WCHAR *value, int len )
619 if (field > rec->count)
620 return ERROR_INVALID_FIELD;
622 MSI_FreeField( &rec->fields[field] );
624 if (value && len < 0) len = strlenW( value );
628 rec->fields[field].type = MSIFIELD_WSTR;
629 rec->fields[field].u.szwVal = msi_strdupW( value, len );
630 rec->fields[field].len = len;
634 rec->fields[field].type = MSIFIELD_NULL;
635 rec->fields[field].u.szwVal = NULL;
636 rec->fields[field].len = 0;
641 UINT MSI_RecordSetStringW( MSIRECORD *rec, UINT iField, LPCWSTR szValue )
643 TRACE("%p %d %s\n", rec, iField, debugstr_w(szValue));
645 return msi_record_set_string( rec, iField, szValue, -1 );
648 UINT WINAPI MsiRecordSetStringW( MSIHANDLE handle, UINT iField, LPCWSTR szValue )
653 TRACE("%d %d %s\n", handle, iField, debugstr_w(szValue));
655 rec = msihandle2msiinfo( handle, MSIHANDLETYPE_RECORD );
657 return ERROR_INVALID_HANDLE;
659 msiobj_lock( &rec->hdr );
660 ret = MSI_RecordSetStringW( rec, iField, szValue );
661 msiobj_unlock( &rec->hdr );
662 msiobj_release( &rec->hdr );
666 /* read the data in a file into an IStream */
667 static UINT RECORD_StreamFromFile(LPCWSTR szFile, IStream **pstm)
669 DWORD sz, szHighWord = 0, read;
673 ULARGE_INTEGER ulSize;
675 TRACE("reading %s\n", debugstr_w(szFile));
677 /* read the file into memory */
678 handle = CreateFileW(szFile, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL);
679 if( handle == INVALID_HANDLE_VALUE )
680 return GetLastError();
681 sz = GetFileSize(handle, &szHighWord);
682 if( sz != INVALID_FILE_SIZE && szHighWord == 0 )
684 hGlob = GlobalAlloc(GMEM_FIXED, sz);
687 BOOL r = ReadFile(handle, hGlob, sz, &read, NULL);
697 return ERROR_FUNCTION_FAILED;
699 /* make a stream out of it, and set the correct file size */
700 hr = CreateStreamOnHGlobal(hGlob, TRUE, pstm);
704 return ERROR_FUNCTION_FAILED;
707 /* set the correct size - CreateStreamOnHGlobal screws it up */
708 ulSize.QuadPart = sz;
709 IStream_SetSize(*pstm, ulSize);
711 TRACE("read %s, %d bytes into IStream %p\n", debugstr_w(szFile), sz, *pstm);
713 return ERROR_SUCCESS;
716 UINT MSI_RecordSetStream(MSIRECORD *rec, UINT iField, IStream *stream)
718 if ( (iField == 0) || (iField > rec->count) )
719 return ERROR_INVALID_PARAMETER;
721 MSI_FreeField( &rec->fields[iField] );
722 rec->fields[iField].type = MSIFIELD_STREAM;
723 rec->fields[iField].u.stream = stream;
725 return ERROR_SUCCESS;
728 UINT MSI_RecordSetStreamFromFileW(MSIRECORD *rec, UINT iField, LPCWSTR szFilename)
733 if( (iField == 0) || (iField > rec->count) )
734 return ERROR_INVALID_PARAMETER;
736 /* no filename means we should seek back to the start of the stream */
742 if( rec->fields[iField].type != MSIFIELD_STREAM )
743 return ERROR_INVALID_FIELD;
745 stm = rec->fields[iField].u.stream;
747 return ERROR_INVALID_FIELD;
750 r = IStream_Seek( stm, ofs, STREAM_SEEK_SET, &cur );
752 return ERROR_FUNCTION_FAILED;
756 /* read the file into a stream and save the stream in the record */
757 r = RECORD_StreamFromFile(szFilename, &stm);
758 if( r != ERROR_SUCCESS )
761 /* if all's good, store it in the record */
762 MSI_RecordSetStream(rec, iField, stm);
765 return ERROR_SUCCESS;
768 UINT WINAPI MsiRecordSetStreamA(MSIHANDLE hRecord, UINT iField, LPCSTR szFilename)
773 TRACE("%d %d %s\n", hRecord, iField, debugstr_a(szFilename));
777 wstr = strdupAtoW( szFilename );
779 return ERROR_OUTOFMEMORY;
781 ret = MsiRecordSetStreamW(hRecord, iField, wstr);
787 UINT WINAPI MsiRecordSetStreamW(MSIHANDLE handle, UINT iField, LPCWSTR szFilename)
792 TRACE("%d %d %s\n", handle, iField, debugstr_w(szFilename));
794 rec = msihandle2msiinfo( handle, MSIHANDLETYPE_RECORD );
796 return ERROR_INVALID_HANDLE;
798 msiobj_lock( &rec->hdr );
799 ret = MSI_RecordSetStreamFromFileW( rec, iField, szFilename );
800 msiobj_unlock( &rec->hdr );
801 msiobj_release( &rec->hdr );
805 UINT MSI_RecordReadStream(MSIRECORD *rec, UINT iField, char *buf, LPDWORD sz)
811 TRACE("%p %d %p %p\n", rec, iField, buf, sz);
814 return ERROR_INVALID_PARAMETER;
816 if( iField > rec->count)
817 return ERROR_INVALID_PARAMETER;
819 if ( rec->fields[iField].type == MSIFIELD_NULL )
822 return ERROR_INVALID_DATA;
825 if( rec->fields[iField].type != MSIFIELD_STREAM )
826 return ERROR_INVALID_DATATYPE;
828 stm = rec->fields[iField].u.stream;
830 return ERROR_INVALID_PARAMETER;
832 /* if there's no buffer pointer, calculate the length to the end */
836 ULARGE_INTEGER end, cur;
838 ofs.QuadPart = cur.QuadPart = 0;
840 IStream_Seek( stm, ofs, STREAM_SEEK_SET, &cur );
841 IStream_Seek( stm, ofs, STREAM_SEEK_END, &end );
842 ofs.QuadPart = cur.QuadPart;
843 IStream_Seek( stm, ofs, STREAM_SEEK_SET, &cur );
844 *sz = end.QuadPart - cur.QuadPart;
846 return ERROR_SUCCESS;
851 r = IStream_Read( stm, buf, *sz, &count );
855 return ERROR_FUNCTION_FAILED;
860 return ERROR_SUCCESS;
863 UINT WINAPI MsiRecordReadStream(MSIHANDLE handle, UINT iField, char *buf, LPDWORD sz)
868 TRACE("%d %d %p %p\n", handle, iField, buf, sz);
870 rec = msihandle2msiinfo( handle, MSIHANDLETYPE_RECORD );
872 return ERROR_INVALID_HANDLE;
873 msiobj_lock( &rec->hdr );
874 ret = MSI_RecordReadStream( rec, iField, buf, sz );
875 msiobj_unlock( &rec->hdr );
876 msiobj_release( &rec->hdr );
880 UINT MSI_RecordSetIStream( MSIRECORD *rec, UINT iField, IStream *stm )
882 TRACE("%p %d %p\n", rec, iField, stm);
884 if( iField > rec->count )
885 return ERROR_INVALID_FIELD;
887 MSI_FreeField( &rec->fields[iField] );
889 rec->fields[iField].type = MSIFIELD_STREAM;
890 rec->fields[iField].u.stream = stm;
891 IStream_AddRef( stm );
893 return ERROR_SUCCESS;
896 UINT MSI_RecordGetIStream( MSIRECORD *rec, UINT iField, IStream **pstm)
898 TRACE("%p %d %p\n", rec, iField, pstm);
900 if( iField > rec->count )
901 return ERROR_INVALID_FIELD;
903 if( rec->fields[iField].type != MSIFIELD_STREAM )
904 return ERROR_INVALID_FIELD;
906 *pstm = rec->fields[iField].u.stream;
907 IStream_AddRef( *pstm );
909 return ERROR_SUCCESS;
912 static UINT msi_dump_stream_to_file( IStream *stm, LPCWSTR name )
920 stgm = STGM_READWRITE | STGM_SHARE_EXCLUSIVE | STGM_FAILIFTHERE;
921 r = SHCreateStreamOnFileW( name, stgm, &out );
923 return ERROR_FUNCTION_FAILED;
926 r = IStream_Seek( stm, pos, STREAM_SEEK_END, &size );
931 r = IStream_Seek( stm, pos, STREAM_SEEK_SET, NULL );
935 r = IStream_CopyTo( stm, out, size, NULL, NULL );
938 IStream_Release( out );
940 return ERROR_FUNCTION_FAILED;
941 return ERROR_SUCCESS;
944 UINT MSI_RecordStreamToFile( MSIRECORD *rec, UINT iField, LPCWSTR name )
949 TRACE("%p %u %s\n", rec, iField, debugstr_w(name));
951 msiobj_lock( &rec->hdr );
953 r = MSI_RecordGetIStream( rec, iField, &stm );
954 if( r == ERROR_SUCCESS )
956 r = msi_dump_stream_to_file( stm, name );
957 IStream_Release( stm );
960 msiobj_unlock( &rec->hdr );
965 MSIRECORD *MSI_CloneRecord(MSIRECORD *rec)
970 count = MSI_RecordGetFieldCount(rec);
971 clone = MSI_CreateRecord(count);
975 for (i = 0; i <= count; i++)
977 if (rec->fields[i].type == MSIFIELD_STREAM)
979 if (FAILED(IStream_Clone(rec->fields[i].u.stream,
980 &clone->fields[i].u.stream)))
982 msiobj_release(&clone->hdr);
985 clone->fields[i].type = MSIFIELD_STREAM;
989 r = MSI_RecordCopyField(rec, i, clone, i);
990 if (r != ERROR_SUCCESS)
992 msiobj_release(&clone->hdr);
1001 BOOL MSI_RecordsAreFieldsEqual(MSIRECORD *a, MSIRECORD *b, UINT field)
1003 if (a->fields[field].type != b->fields[field].type)
1006 switch (a->fields[field].type)
1012 if (a->fields[field].u.iVal != b->fields[field].u.iVal)
1017 if (a->fields[field].len != b->fields[field].len) return FALSE;
1018 if (memcmp( a->fields[field].u.szwVal, b->fields[field].u.szwVal,
1019 a->fields[field].len * sizeof(WCHAR) )) return FALSE;
1022 case MSIFIELD_STREAM:
1030 BOOL MSI_RecordsAreEqual(MSIRECORD *a, MSIRECORD *b)
1034 if (a->count != b->count)
1037 for (i = 0; i <= a->count; i++)
1039 if (!MSI_RecordsAreFieldsEqual( a, b, i ))
1046 WCHAR *msi_dup_record_field( MSIRECORD *rec, INT field )
1052 if (MSI_RecordIsNull( rec, field )) return NULL;
1054 r = MSI_RecordGetStringW( rec, field, NULL, &sz );
1055 if (r != ERROR_SUCCESS)
1059 str = msi_alloc( sz * sizeof(WCHAR) );
1060 if (!str) return NULL;
1062 r = MSI_RecordGetStringW( rec, field, str, &sz );
1063 if (r != ERROR_SUCCESS)
1065 ERR("failed to get string!\n");