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 )
83 TRACE("%d\n", cParams);
88 rec = alloc_msiobject( MSIHANDLETYPE_RECORD, FIELD_OFFSET(MSIRECORD, fields[cParams + 1]),
95 MSIHANDLE WINAPI MsiCreateRecord( UINT cParams )
100 TRACE("%d\n", cParams);
102 rec = MSI_CreateRecord( cParams );
105 ret = alloc_msihandle( &rec->hdr );
106 msiobj_release( &rec->hdr );
111 UINT MSI_RecordGetFieldCount( const MSIRECORD *rec )
116 UINT WINAPI MsiRecordGetFieldCount( MSIHANDLE handle )
121 TRACE("%d\n", handle );
123 rec = msihandle2msiinfo( handle, MSIHANDLETYPE_RECORD );
127 msiobj_lock( &rec->hdr );
128 ret = MSI_RecordGetFieldCount( rec );
129 msiobj_unlock( &rec->hdr );
130 msiobj_release( &rec->hdr );
135 static BOOL string2intW( LPCWSTR str, int *out )
140 if( *p == '-' ) /* skip the minus sign */
144 if( (*p < '0') || (*p > '9') )
151 if( str[0] == '-' ) /* check if it's negative */
158 WCHAR *msi_strdupW( const WCHAR *value, int len )
162 if (!value) return NULL;
163 if (!(ret = msi_alloc( (len + 1) * sizeof(WCHAR) ))) return NULL;
164 memcpy( ret, value, len * sizeof(WCHAR) );
169 UINT MSI_RecordCopyField( MSIRECORD *in_rec, UINT in_n,
170 MSIRECORD *out_rec, UINT out_n )
172 UINT r = ERROR_SUCCESS;
174 msiobj_lock( &in_rec->hdr );
176 if ( in_n > in_rec->count || out_n > out_rec->count )
177 r = ERROR_FUNCTION_FAILED;
178 else if ( in_rec != out_rec || in_n != out_n )
183 in = &in_rec->fields[in_n];
184 out = &out_rec->fields[out_n];
191 out->u.iVal = in->u.iVal;
193 case MSIFIELD_INTPTR:
194 out->u.pVal = in->u.pVal;
197 if ((str = msi_strdupW( in->u.szwVal, in->len )))
202 else r = ERROR_OUTOFMEMORY;
204 case MSIFIELD_STREAM:
205 IStream_AddRef( in->u.stream );
206 out->u.stream = in->u.stream;
209 ERR("invalid field type %d\n", in->type);
211 if (r == ERROR_SUCCESS)
212 out->type = in->type;
215 msiobj_unlock( &in_rec->hdr );
219 INT_PTR MSI_RecordGetIntPtr( MSIRECORD *rec, UINT iField )
223 TRACE( "%p %d\n", rec, iField );
225 if( iField > rec->count )
228 switch( rec->fields[iField].type )
231 return rec->fields[iField].u.iVal;
232 case MSIFIELD_INTPTR:
233 return rec->fields[iField].u.pVal;
235 if( string2intW( rec->fields[iField].u.szwVal, &ret ) )
245 int MSI_RecordGetInteger( MSIRECORD *rec, UINT iField)
249 TRACE("%p %d\n", rec, iField );
251 if( iField > rec->count )
252 return MSI_NULL_INTEGER;
254 switch( rec->fields[iField].type )
257 return rec->fields[iField].u.iVal;
258 case MSIFIELD_INTPTR:
259 return rec->fields[iField].u.pVal;
261 if( string2intW( rec->fields[iField].u.szwVal, &ret ) )
263 return MSI_NULL_INTEGER;
268 return MSI_NULL_INTEGER;
271 int WINAPI MsiRecordGetInteger( MSIHANDLE handle, UINT iField)
276 TRACE("%d %d\n", handle, iField );
278 rec = msihandle2msiinfo( handle, MSIHANDLETYPE_RECORD );
280 return MSI_NULL_INTEGER;
282 msiobj_lock( &rec->hdr );
283 ret = MSI_RecordGetInteger( rec, iField );
284 msiobj_unlock( &rec->hdr );
285 msiobj_release( &rec->hdr );
290 UINT WINAPI MsiRecordClearData( MSIHANDLE handle )
295 TRACE("%d\n", handle );
297 rec = msihandle2msiinfo( handle, MSIHANDLETYPE_RECORD );
299 return ERROR_INVALID_HANDLE;
301 msiobj_lock( &rec->hdr );
302 for( i=0; i<=rec->count; i++)
304 MSI_FreeField( &rec->fields[i] );
305 rec->fields[i].type = MSIFIELD_NULL;
306 rec->fields[i].u.iVal = 0;
308 msiobj_unlock( &rec->hdr );
309 msiobj_release( &rec->hdr );
311 return ERROR_SUCCESS;
314 UINT MSI_RecordSetIntPtr( MSIRECORD *rec, UINT iField, INT_PTR pVal )
316 TRACE("%p %u %ld\n", rec, iField, pVal);
318 if( iField > rec->count )
319 return ERROR_INVALID_PARAMETER;
321 MSI_FreeField( &rec->fields[iField] );
322 rec->fields[iField].type = MSIFIELD_INTPTR;
323 rec->fields[iField].u.pVal = pVal;
325 return ERROR_SUCCESS;
328 UINT MSI_RecordSetInteger( MSIRECORD *rec, UINT iField, int iVal )
330 TRACE("%p %u %d\n", rec, iField, iVal);
332 if( iField > rec->count )
333 return ERROR_INVALID_PARAMETER;
335 MSI_FreeField( &rec->fields[iField] );
336 rec->fields[iField].type = MSIFIELD_INT;
337 rec->fields[iField].u.iVal = iVal;
339 return ERROR_SUCCESS;
342 UINT WINAPI MsiRecordSetInteger( MSIHANDLE handle, UINT iField, int iVal )
347 TRACE("%d %u %d\n", handle, iField, iVal);
349 rec = msihandle2msiinfo( handle, MSIHANDLETYPE_RECORD );
351 return ERROR_INVALID_HANDLE;
353 msiobj_lock( &rec->hdr );
354 ret = MSI_RecordSetInteger( rec, iField, iVal );
355 msiobj_unlock( &rec->hdr );
356 msiobj_release( &rec->hdr );
360 BOOL MSI_RecordIsNull( MSIRECORD *rec, UINT iField )
364 TRACE("%p %d\n", rec, iField );
366 r = ( iField > rec->count ) ||
367 ( rec->fields[iField].type == MSIFIELD_NULL );
372 BOOL WINAPI MsiRecordIsNull( MSIHANDLE handle, UINT iField )
377 TRACE("%d %d\n", handle, iField );
379 rec = msihandle2msiinfo( handle, MSIHANDLETYPE_RECORD );
382 msiobj_lock( &rec->hdr );
383 ret = MSI_RecordIsNull( rec, iField );
384 msiobj_unlock( &rec->hdr );
385 msiobj_release( &rec->hdr );
390 UINT MSI_RecordGetStringA(MSIRECORD *rec, UINT iField,
391 LPSTR szValue, LPDWORD pcchValue)
393 UINT len = 0, ret = ERROR_SUCCESS;
396 TRACE("%p %d %p %p\n", rec, iField, szValue, pcchValue);
398 if( iField > rec->count )
400 if ( szValue && *pcchValue > 0 )
404 return ERROR_SUCCESS;
407 switch( rec->fields[iField].type )
410 wsprintfA(buffer, "%d", rec->fields[iField].u.iVal);
411 len = lstrlenA( buffer );
413 lstrcpynA(szValue, buffer, *pcchValue);
416 len = WideCharToMultiByte( CP_ACP, 0, rec->fields[iField].u.szwVal,
417 rec->fields[iField].len + 1, NULL, 0 , NULL, NULL );
419 WideCharToMultiByte( CP_ACP, 0, rec->fields[iField].u.szwVal,
420 rec->fields[iField].len + 1, szValue, *pcchValue, NULL, NULL );
421 if( szValue && *pcchValue && len>*pcchValue )
422 szValue[*pcchValue-1] = 0;
427 if( szValue && *pcchValue > 0 )
431 ret = ERROR_INVALID_PARAMETER;
435 if( szValue && *pcchValue <= len )
436 ret = ERROR_MORE_DATA;
442 UINT WINAPI MsiRecordGetStringA(MSIHANDLE handle, UINT iField,
443 LPSTR szValue, LPDWORD pcchValue)
448 TRACE("%d %d %p %p\n", handle, iField, szValue, pcchValue);
450 rec = msihandle2msiinfo( handle, MSIHANDLETYPE_RECORD );
452 return ERROR_INVALID_HANDLE;
453 msiobj_lock( &rec->hdr );
454 ret = MSI_RecordGetStringA( rec, iField, szValue, pcchValue);
455 msiobj_unlock( &rec->hdr );
456 msiobj_release( &rec->hdr );
460 const WCHAR *msi_record_get_string( const MSIRECORD *rec, UINT field, int *len )
462 if (field > rec->count)
465 if (rec->fields[field].type != MSIFIELD_WSTR)
468 if (len) *len = rec->fields[field].len;
470 return rec->fields[field].u.szwVal;
473 const WCHAR *MSI_RecordGetString( const MSIRECORD *rec, UINT iField )
475 return msi_record_get_string( rec, iField, NULL );
478 UINT MSI_RecordGetStringW(MSIRECORD *rec, UINT iField,
479 LPWSTR szValue, LPDWORD pcchValue)
481 static const WCHAR szFormat[] = {'%','d',0};
482 UINT len = 0, ret = ERROR_SUCCESS;
485 TRACE("%p %d %p %p\n", rec, iField, szValue, pcchValue);
487 if( iField > rec->count )
489 if ( szValue && *pcchValue > 0 )
493 return ERROR_SUCCESS;
496 switch( rec->fields[iField].type )
499 wsprintfW(buffer, szFormat, rec->fields[iField].u.iVal);
500 len = lstrlenW( buffer );
502 lstrcpynW(szValue, buffer, *pcchValue);
505 len = rec->fields[iField].len;
507 memcpy( szValue, rec->fields[iField].u.szwVal, min(len + 1, *pcchValue) * sizeof(WCHAR) );
510 if( szValue && *pcchValue > 0 )
517 if( szValue && *pcchValue <= len )
518 ret = ERROR_MORE_DATA;
524 UINT WINAPI MsiRecordGetStringW(MSIHANDLE handle, UINT iField,
525 LPWSTR szValue, LPDWORD pcchValue)
530 TRACE("%d %d %p %p\n", handle, iField, szValue, pcchValue);
532 rec = msihandle2msiinfo( handle, MSIHANDLETYPE_RECORD );
534 return ERROR_INVALID_HANDLE;
536 msiobj_lock( &rec->hdr );
537 ret = MSI_RecordGetStringW( rec, iField, szValue, pcchValue );
538 msiobj_unlock( &rec->hdr );
539 msiobj_release( &rec->hdr );
543 static UINT msi_get_stream_size( IStream *stm )
548 r = IStream_Stat( stm, &stat, STATFLAG_NONAME );
551 return stat.cbSize.QuadPart;
554 static UINT MSI_RecordDataSize(MSIRECORD *rec, UINT iField)
556 TRACE("%p %d\n", rec, iField);
558 if( iField > rec->count )
561 switch( rec->fields[iField].type )
566 return rec->fields[iField].len;
569 case MSIFIELD_STREAM:
570 return msi_get_stream_size( rec->fields[iField].u.stream );
575 UINT WINAPI MsiRecordDataSize(MSIHANDLE handle, UINT iField)
580 TRACE("%d %d\n", handle, iField);
582 rec = msihandle2msiinfo( handle, MSIHANDLETYPE_RECORD );
585 msiobj_lock( &rec->hdr );
586 ret = MSI_RecordDataSize( rec, iField);
587 msiobj_unlock( &rec->hdr );
588 msiobj_release( &rec->hdr );
592 UINT WINAPI MsiRecordSetStringA( MSIHANDLE handle, UINT iField, LPCSTR szValue )
594 WCHAR *valueW = NULL;
598 TRACE("%d %d %s\n", handle, iField, debugstr_a(szValue));
600 if (szValue && !(valueW = strdupAtoW( szValue ))) return ERROR_OUTOFMEMORY;
602 rec = msihandle2msiinfo( handle, MSIHANDLETYPE_RECORD );
606 return ERROR_INVALID_HANDLE;
608 msiobj_lock( &rec->hdr );
609 ret = MSI_RecordSetStringW( rec, iField, valueW );
610 msiobj_unlock( &rec->hdr );
611 msiobj_release( &rec->hdr );
616 UINT msi_record_set_string( MSIRECORD *rec, UINT field, const WCHAR *value, int len )
618 if (field > rec->count)
619 return ERROR_INVALID_FIELD;
621 MSI_FreeField( &rec->fields[field] );
623 if (value && len < 0) len = strlenW( value );
627 rec->fields[field].type = MSIFIELD_WSTR;
628 rec->fields[field].u.szwVal = msi_strdupW( value, len );
629 rec->fields[field].len = len;
633 rec->fields[field].type = MSIFIELD_NULL;
634 rec->fields[field].u.szwVal = NULL;
635 rec->fields[field].len = 0;
640 UINT MSI_RecordSetStringW( MSIRECORD *rec, UINT iField, LPCWSTR szValue )
642 TRACE("%p %d %s\n", rec, iField, debugstr_w(szValue));
644 return msi_record_set_string( rec, iField, szValue, -1 );
647 UINT WINAPI MsiRecordSetStringW( MSIHANDLE handle, UINT iField, LPCWSTR szValue )
652 TRACE("%d %d %s\n", handle, iField, debugstr_w(szValue));
654 rec = msihandle2msiinfo( handle, MSIHANDLETYPE_RECORD );
656 return ERROR_INVALID_HANDLE;
658 msiobj_lock( &rec->hdr );
659 ret = MSI_RecordSetStringW( rec, iField, szValue );
660 msiobj_unlock( &rec->hdr );
661 msiobj_release( &rec->hdr );
665 /* read the data in a file into an IStream */
666 static UINT RECORD_StreamFromFile(LPCWSTR szFile, IStream **pstm)
668 DWORD sz, szHighWord = 0, read;
672 ULARGE_INTEGER ulSize;
674 TRACE("reading %s\n", debugstr_w(szFile));
676 /* read the file into memory */
677 handle = CreateFileW(szFile, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL);
678 if( handle == INVALID_HANDLE_VALUE )
679 return GetLastError();
680 sz = GetFileSize(handle, &szHighWord);
681 if( sz != INVALID_FILE_SIZE && szHighWord == 0 )
683 hGlob = GlobalAlloc(GMEM_FIXED, sz);
686 BOOL r = ReadFile(handle, hGlob, sz, &read, NULL);
696 return ERROR_FUNCTION_FAILED;
698 /* make a stream out of it, and set the correct file size */
699 hr = CreateStreamOnHGlobal(hGlob, TRUE, pstm);
703 return ERROR_FUNCTION_FAILED;
706 /* set the correct size - CreateStreamOnHGlobal screws it up */
707 ulSize.QuadPart = sz;
708 IStream_SetSize(*pstm, ulSize);
710 TRACE("read %s, %d bytes into IStream %p\n", debugstr_w(szFile), sz, *pstm);
712 return ERROR_SUCCESS;
715 UINT MSI_RecordSetStream(MSIRECORD *rec, UINT iField, IStream *stream)
717 if ( (iField == 0) || (iField > rec->count) )
718 return ERROR_INVALID_PARAMETER;
720 MSI_FreeField( &rec->fields[iField] );
721 rec->fields[iField].type = MSIFIELD_STREAM;
722 rec->fields[iField].u.stream = stream;
724 return ERROR_SUCCESS;
727 UINT MSI_RecordSetStreamFromFileW(MSIRECORD *rec, UINT iField, LPCWSTR szFilename)
732 if( (iField == 0) || (iField > rec->count) )
733 return ERROR_INVALID_PARAMETER;
735 /* no filename means we should seek back to the start of the stream */
741 if( rec->fields[iField].type != MSIFIELD_STREAM )
742 return ERROR_INVALID_FIELD;
744 stm = rec->fields[iField].u.stream;
746 return ERROR_INVALID_FIELD;
749 r = IStream_Seek( stm, ofs, STREAM_SEEK_SET, &cur );
751 return ERROR_FUNCTION_FAILED;
755 /* read the file into a stream and save the stream in the record */
756 r = RECORD_StreamFromFile(szFilename, &stm);
757 if( r != ERROR_SUCCESS )
760 /* if all's good, store it in the record */
761 MSI_RecordSetStream(rec, iField, stm);
764 return ERROR_SUCCESS;
767 UINT WINAPI MsiRecordSetStreamA(MSIHANDLE hRecord, UINT iField, LPCSTR szFilename)
772 TRACE("%d %d %s\n", hRecord, iField, debugstr_a(szFilename));
776 wstr = strdupAtoW( szFilename );
778 return ERROR_OUTOFMEMORY;
780 ret = MsiRecordSetStreamW(hRecord, iField, wstr);
786 UINT WINAPI MsiRecordSetStreamW(MSIHANDLE handle, UINT iField, LPCWSTR szFilename)
791 TRACE("%d %d %s\n", handle, iField, debugstr_w(szFilename));
793 rec = msihandle2msiinfo( handle, MSIHANDLETYPE_RECORD );
795 return ERROR_INVALID_HANDLE;
797 msiobj_lock( &rec->hdr );
798 ret = MSI_RecordSetStreamFromFileW( rec, iField, szFilename );
799 msiobj_unlock( &rec->hdr );
800 msiobj_release( &rec->hdr );
804 UINT MSI_RecordReadStream(MSIRECORD *rec, UINT iField, char *buf, LPDWORD sz)
810 TRACE("%p %d %p %p\n", rec, iField, buf, sz);
813 return ERROR_INVALID_PARAMETER;
815 if( iField > rec->count)
816 return ERROR_INVALID_PARAMETER;
818 if ( rec->fields[iField].type == MSIFIELD_NULL )
821 return ERROR_INVALID_DATA;
824 if( rec->fields[iField].type != MSIFIELD_STREAM )
825 return ERROR_INVALID_DATATYPE;
827 stm = rec->fields[iField].u.stream;
829 return ERROR_INVALID_PARAMETER;
831 /* if there's no buffer pointer, calculate the length to the end */
835 ULARGE_INTEGER end, cur;
837 ofs.QuadPart = cur.QuadPart = 0;
839 IStream_Seek( stm, ofs, STREAM_SEEK_SET, &cur );
840 IStream_Seek( stm, ofs, STREAM_SEEK_END, &end );
841 ofs.QuadPart = cur.QuadPart;
842 IStream_Seek( stm, ofs, STREAM_SEEK_SET, &cur );
843 *sz = end.QuadPart - cur.QuadPart;
845 return ERROR_SUCCESS;
850 r = IStream_Read( stm, buf, *sz, &count );
854 return ERROR_FUNCTION_FAILED;
859 return ERROR_SUCCESS;
862 UINT WINAPI MsiRecordReadStream(MSIHANDLE handle, UINT iField, char *buf, LPDWORD sz)
867 TRACE("%d %d %p %p\n", handle, iField, buf, sz);
869 rec = msihandle2msiinfo( handle, MSIHANDLETYPE_RECORD );
871 return ERROR_INVALID_HANDLE;
872 msiobj_lock( &rec->hdr );
873 ret = MSI_RecordReadStream( rec, iField, buf, sz );
874 msiobj_unlock( &rec->hdr );
875 msiobj_release( &rec->hdr );
879 UINT MSI_RecordSetIStream( MSIRECORD *rec, UINT iField, IStream *stm )
881 TRACE("%p %d %p\n", rec, iField, stm);
883 if( iField > rec->count )
884 return ERROR_INVALID_FIELD;
886 MSI_FreeField( &rec->fields[iField] );
888 rec->fields[iField].type = MSIFIELD_STREAM;
889 rec->fields[iField].u.stream = stm;
890 IStream_AddRef( stm );
892 return ERROR_SUCCESS;
895 UINT MSI_RecordGetIStream( MSIRECORD *rec, UINT iField, IStream **pstm)
897 TRACE("%p %d %p\n", rec, iField, pstm);
899 if( iField > rec->count )
900 return ERROR_INVALID_FIELD;
902 if( rec->fields[iField].type != MSIFIELD_STREAM )
903 return ERROR_INVALID_FIELD;
905 *pstm = rec->fields[iField].u.stream;
906 IStream_AddRef( *pstm );
908 return ERROR_SUCCESS;
911 static UINT msi_dump_stream_to_file( IStream *stm, LPCWSTR name )
919 stgm = STGM_READWRITE | STGM_SHARE_EXCLUSIVE | STGM_FAILIFTHERE;
920 r = SHCreateStreamOnFileW( name, stgm, &out );
922 return ERROR_FUNCTION_FAILED;
925 r = IStream_Seek( stm, pos, STREAM_SEEK_END, &size );
930 r = IStream_Seek( stm, pos, STREAM_SEEK_SET, NULL );
934 r = IStream_CopyTo( stm, out, size, NULL, NULL );
937 IStream_Release( out );
939 return ERROR_FUNCTION_FAILED;
940 return ERROR_SUCCESS;
943 UINT MSI_RecordStreamToFile( MSIRECORD *rec, UINT iField, LPCWSTR name )
948 TRACE("%p %u %s\n", rec, iField, debugstr_w(name));
950 msiobj_lock( &rec->hdr );
952 r = MSI_RecordGetIStream( rec, iField, &stm );
953 if( r == ERROR_SUCCESS )
955 r = msi_dump_stream_to_file( stm, name );
956 IStream_Release( stm );
959 msiobj_unlock( &rec->hdr );
964 MSIRECORD *MSI_CloneRecord(MSIRECORD *rec)
969 count = MSI_RecordGetFieldCount(rec);
970 clone = MSI_CreateRecord(count);
974 for (i = 0; i <= count; i++)
976 if (rec->fields[i].type == MSIFIELD_STREAM)
978 if (FAILED(IStream_Clone(rec->fields[i].u.stream,
979 &clone->fields[i].u.stream)))
981 msiobj_release(&clone->hdr);
984 clone->fields[i].type = MSIFIELD_STREAM;
988 r = MSI_RecordCopyField(rec, i, clone, i);
989 if (r != ERROR_SUCCESS)
991 msiobj_release(&clone->hdr);
1000 BOOL MSI_RecordsAreFieldsEqual(MSIRECORD *a, MSIRECORD *b, UINT field)
1002 if (a->fields[field].type != b->fields[field].type)
1005 switch (a->fields[field].type)
1011 if (a->fields[field].u.iVal != b->fields[field].u.iVal)
1016 if (a->fields[field].len != b->fields[field].len) return FALSE;
1017 if (memcmp( a->fields[field].u.szwVal, b->fields[field].u.szwVal,
1018 a->fields[field].len * sizeof(WCHAR) )) return FALSE;
1021 case MSIFIELD_STREAM:
1029 BOOL MSI_RecordsAreEqual(MSIRECORD *a, MSIRECORD *b)
1033 if (a->count != b->count)
1036 for (i = 0; i <= a->count; i++)
1038 if (!MSI_RecordsAreFieldsEqual( a, b, i ))
1045 WCHAR *msi_dup_record_field( MSIRECORD *rec, INT field )
1051 if (MSI_RecordIsNull( rec, field )) return NULL;
1053 r = MSI_RecordGetStringW( rec, field, NULL, &sz );
1054 if (r != ERROR_SUCCESS)
1058 str = msi_alloc( sz * sizeof(WCHAR) );
1059 if (!str) return NULL;
1061 r = MSI_RecordGetStringW( rec, field, str, &sz );
1062 if (r != ERROR_SUCCESS)
1064 ERR("failed to get string!\n");