2 * msidb - command line tool for assembling MSI packages
4 * Copyright 2015 Erich E. Hoover
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
21 #define WIN32_LEAN_AND_MEAN
30 #include "wine/debug.h"
31 #include "wine/list.h"
33 WINE_DEFAULT_DEBUG_CHANNEL(msidb
);
35 struct msidb_listentry
45 MSIHANDLE database_handle
;
53 struct list add_stream_list
;
54 struct list extract_stream_list
;
55 struct list kill_stream_list
;
56 struct list table_list
;
59 static void list_free( struct list
*list
)
61 struct msidb_listentry
*data
, *next
;
63 LIST_FOR_EACH_ENTRY_SAFE( data
, next
, list
, struct msidb_listentry
, entry
)
65 list_remove( &data
->entry
);
66 HeapFree( GetProcessHeap(), 0, data
);
70 static void list_append( struct list
*list
, WCHAR
*name
)
72 struct msidb_listentry
*data
;
74 data
= HeapAlloc( GetProcessHeap(), HEAP_ZERO_MEMORY
, sizeof(struct msidb_listentry
) );
77 ERR( "Out of memory for list.\n" );
81 list_add_tail( list
, &data
->entry
);
84 static void show_usage( void )
87 "Usage: msidb [OPTION]...[OPTION]... [TABLE]...[TABLE]\n"
89 " -? Show this usage message and exit.\n"
90 " -a file.cab Add stream/cabinet file to _Streams table.\n"
91 " -c Create database file (instead of opening existing file).\n"
92 " -d package.msi Path to the database file.\n"
93 " -e Export tables from database.\n"
94 " -f folder Folder in which to open/save the tables.\n"
95 " -i Import tables into database.\n"
96 " -k file.cab Kill (remove) stream/cabinet file from _Streams table.\n"
97 " -s Export with short filenames (eight character max).\n"
98 " -x file.cab Extract stream/cabinet file from _Streams table.\n"
102 static int valid_state( struct msidb_state
*state
)
104 if (state
->database_file
== NULL
)
106 FIXME( "GUI operation is not currently supported.\n" );
109 if (state
->table_folder
== NULL
&& !state
->add_streams
&& !state
->kill_streams
110 && !state
->extract_streams
)
112 ERR( "No table folder specified (-f option).\n" );
116 if (!state
->create_database
&& !state
->import_tables
&& !state
->export_tables
117 && !state
->add_streams
&& !state
->kill_streams
&& !state
->extract_streams
)
119 ERR( "No mode flag specified (-a, -c, -e, -i, -k, -x).\n" );
123 if (list_empty( &state
->table_list
) && !state
->add_streams
&& !state
->kill_streams
124 && !state
->extract_streams
)
126 ERR( "No tables specified.\n" );
132 static int process_argument( struct msidb_state
*state
, int i
, int argc
, WCHAR
*argv
[] )
134 /* msidb accepts either "-" or "/" style flags */
135 if (lstrlenW(argv
[i
]) != 2 || (argv
[i
][0] != '-' && argv
[i
][0] != '/'))
143 if (i
+ 1 >= argc
) return 0;
144 state
->add_streams
= TRUE
;
145 list_append( &state
->add_stream_list
, argv
[i
+ 1] );
148 state
->create_database
= TRUE
;
151 if (i
+ 1 >= argc
) return 0;
152 state
->database_file
= argv
[i
+ 1];
155 state
->export_tables
= TRUE
;
158 if (i
+ 1 >= argc
) return 0;
159 state
->table_folder
= argv
[i
+ 1];
162 state
->import_tables
= TRUE
;
165 if (i
+ 1 >= argc
) return 0;
166 state
->kill_streams
= TRUE
;
167 list_append( &state
->kill_stream_list
, argv
[i
+ 1] );
170 state
->short_filenames
= TRUE
;
173 if (i
+ 1 >= argc
) return 0;
174 state
->extract_streams
= TRUE
;
175 list_append( &state
->extract_stream_list
, argv
[i
+ 1] );
184 static int open_database( struct msidb_state
*state
)
186 LPCWSTR db_mode
= state
->create_database
? MSIDBOPEN_CREATE
: MSIDBOPEN_TRANSACT
;
189 ret
= MsiOpenDatabaseW( state
->database_file
, db_mode
, &state
->database_handle
);
190 if (ret
!= ERROR_SUCCESS
)
192 ERR( "Failed to open database '%s', error %d\n", wine_dbgstr_w(state
->database_file
), ret
);
198 static void close_database( struct msidb_state
*state
, BOOL commit_changes
)
200 UINT ret
= ERROR_SUCCESS
;
202 if (state
->database_handle
== 0)
205 ret
= MsiDatabaseCommit( state
->database_handle
);
206 if (ret
!= ERROR_SUCCESS
)
208 ERR( "Failed to commit changes to database.\n" );
211 ret
= MsiCloseHandle( state
->database_handle
);
212 if (ret
!= ERROR_SUCCESS
)
214 WARN( "Failed to close database handle.\n" );
219 static const WCHAR
*basenameW( const WCHAR
*filename
)
221 const WCHAR
*dir_end
;
223 dir_end
= wcsrchr( filename
, '/' );
224 if (dir_end
) return dir_end
+ 1;
225 dir_end
= wcsrchr( filename
, '\\' );
226 if (dir_end
) return dir_end
+ 1;
230 static int add_stream( struct msidb_state
*state
, const WCHAR
*stream_filename
)
232 MSIHANDLE view
= 0, record
= 0;
235 ret
= MsiDatabaseOpenViewW( state
->database_handle
, L
"INSERT INTO _Streams (Name, Data) VALUES (?, ?)", &view
);
236 if (ret
!= ERROR_SUCCESS
)
238 ERR( "Failed to open _Streams table.\n" );
241 record
= MsiCreateRecord( 2 );
244 ERR( "Failed to create MSI record.\n" );
245 ret
= ERROR_OUTOFMEMORY
;
248 ret
= MsiRecordSetStringW( record
, 1, basenameW( stream_filename
) );
249 if (ret
!= ERROR_SUCCESS
)
251 ERR( "Failed to add stream filename to MSI record.\n" );
254 ret
= MsiRecordSetStreamW( record
, 2, stream_filename
);
255 if (ret
!= ERROR_SUCCESS
)
257 ERR( "Failed to add stream to MSI record.\n" );
260 ret
= MsiViewExecute( view
, record
);
261 if (ret
!= ERROR_SUCCESS
)
263 ERR( "Failed to add stream to _Streams table.\n" );
269 MsiCloseHandle( record
);
271 MsiViewClose( view
);
273 return (ret
== ERROR_SUCCESS
);
276 static int add_streams( struct msidb_state
*state
)
278 struct msidb_listentry
*data
;
280 LIST_FOR_EACH_ENTRY( data
, &state
->add_stream_list
, struct msidb_listentry
, entry
)
282 if (!add_stream( state
, data
->name
))
283 return 0; /* failed, do not commit changes */
288 static int kill_stream( struct msidb_state
*state
, const WCHAR
*stream_filename
)
290 MSIHANDLE view
= 0, record
= 0;
293 ret
= MsiDatabaseOpenViewW( state
->database_handle
, L
"DELETE FROM _Streams WHERE Name = ?", &view
);
294 if (ret
!= ERROR_SUCCESS
)
296 ERR( "Failed to open _Streams table.\n" );
299 record
= MsiCreateRecord( 1 );
302 ERR( "Failed to create MSI record.\n" );
303 ret
= ERROR_OUTOFMEMORY
;
306 ret
= MsiRecordSetStringW( record
, 1, stream_filename
);
307 if (ret
!= ERROR_SUCCESS
)
309 ERR( "Failed to add stream filename to MSI record.\n" );
312 ret
= MsiViewExecute( view
, record
);
313 if (ret
!= ERROR_SUCCESS
)
315 ERR( "Failed to delete stream from _Streams table.\n" );
321 MsiCloseHandle( record
);
323 MsiViewClose( view
);
325 return (ret
== ERROR_SUCCESS
);
328 static int kill_streams( struct msidb_state
*state
)
330 struct msidb_listentry
*data
;
332 LIST_FOR_EACH_ENTRY( data
, &state
->kill_stream_list
, struct msidb_listentry
, entry
)
334 if (!kill_stream( state
, data
->name
))
335 return 0; /* failed, do not commit changes */
340 static int extract_stream( struct msidb_state
*state
, const WCHAR
*stream_filename
)
342 HANDLE file
= INVALID_HANDLE_VALUE
;
343 MSIHANDLE view
= 0, record
= 0;
344 DWORD read_size
, write_size
;
348 file
= CreateFileW( stream_filename
, GENERIC_WRITE
, FILE_SHARE_READ
| FILE_SHARE_WRITE
,
349 NULL
, CREATE_NEW
, FILE_ATTRIBUTE_NORMAL
, NULL
);
350 if (file
== INVALID_HANDLE_VALUE
)
352 ret
= ERROR_FILE_NOT_FOUND
;
353 ERR( "Failed to open destination file %s.\n", wine_dbgstr_w(stream_filename
) );
356 ret
= MsiDatabaseOpenViewW( state
->database_handle
, L
"SELECT Data FROM _Streams WHERE Name = ?", &view
);
357 if (ret
!= ERROR_SUCCESS
)
359 ERR( "Failed to open _Streams table.\n" );
362 record
= MsiCreateRecord( 1 );
365 ERR( "Failed to create MSI record.\n" );
366 ret
= ERROR_OUTOFMEMORY
;
369 ret
= MsiRecordSetStringW( record
, 1, stream_filename
);
370 if (ret
!= ERROR_SUCCESS
)
372 ERR( "Failed to add stream filename to MSI record.\n" );
375 ret
= MsiViewExecute( view
, record
);
376 if (ret
!= ERROR_SUCCESS
)
378 ERR( "Failed to query stream from _Streams table.\n" );
381 MsiCloseHandle( record
);
383 ret
= MsiViewFetch( view
, &record
);
384 if (ret
!= ERROR_SUCCESS
)
386 ERR( "Failed to query row from _Streams table.\n" );
389 read_size
= sizeof(buffer
);
390 while (read_size
== sizeof(buffer
))
392 ret
= MsiRecordReadStream( record
, 1, buffer
, &read_size
);
393 if (ret
!= ERROR_SUCCESS
)
395 ERR( "Failed to read stream from _Streams table.\n" );
398 if (!WriteFile( file
, buffer
, read_size
, &write_size
, NULL
) || read_size
!= write_size
)
400 ret
= ERROR_WRITE_FAULT
;
401 ERR( "Failed to write stream to destination file.\n" );
408 MsiCloseHandle( record
);
410 MsiViewClose( view
);
411 if (file
!= INVALID_HANDLE_VALUE
)
413 return (ret
== ERROR_SUCCESS
);
416 static int extract_streams( struct msidb_state
*state
)
418 struct msidb_listentry
*data
;
420 LIST_FOR_EACH_ENTRY( data
, &state
->extract_stream_list
, struct msidb_listentry
, entry
)
422 if (!extract_stream( state
, data
->name
))
423 return 0; /* failed, do not commit changes */
428 static int import_table( struct msidb_state
*state
, const WCHAR
*table_path
)
432 ret
= MsiDatabaseImportW( state
->database_handle
, state
->table_folder
, table_path
);
433 if (ret
!= ERROR_SUCCESS
)
435 ERR( "Failed to import table '%s', error %d.\n", wine_dbgstr_w(table_path
), ret
);
441 static int import_tables( struct msidb_state
*state
)
443 struct msidb_listentry
*data
;
445 LIST_FOR_EACH_ENTRY( data
, &state
->table_list
, struct msidb_listentry
, entry
)
447 WCHAR
*table_name
= data
->name
;
448 WCHAR table_path
[MAX_PATH
];
451 /* permit specifying tables with wildcards ('Feature*') */
452 if (wcsstr( table_name
, L
"*" ) != NULL
)
459 len
= lstrlenW( state
->table_folder
) + 1 + lstrlenW( table_name
) + 1; /* %s/%s\0 */
460 path
= HeapAlloc( GetProcessHeap(), 0, len
* sizeof(WCHAR
) );
463 lstrcpyW( path
, state
->table_folder
);
464 PathAddBackslashW( path
);
465 lstrcatW( path
, table_name
);
466 handle
= FindFirstFileW( path
, &f
);
467 HeapFree( GetProcessHeap(), 0, path
);
468 if (handle
== INVALID_HANDLE_VALUE
)
472 if (f
.cFileName
[0] == '.' && !f
.cFileName
[1]) continue;
473 if (f
.cFileName
[0] == '.' && f
.cFileName
[1] == '.' && !f
.cFileName
[2]) continue;
474 if (f
.dwFileAttributes
& FILE_ATTRIBUTE_DIRECTORY
) continue;
475 if ((ext
= PathFindExtensionW( f
.cFileName
)) == NULL
) continue;
476 if (lstrcmpW( ext
, L
".idt" ) != 0) continue;
477 if (!import_table( state
, f
.cFileName
))
480 return 0; /* failed, do not commit changes */
482 } while (FindNextFileW( handle
, &f
));
486 /* permit specifying tables by filename (*.idt) */
487 if ((ext
= PathFindExtensionW( table_name
)) == NULL
|| lstrcmpW( ext
, L
".idt" ) != 0)
489 /* truncate to 8 characters */
490 swprintf( table_path
, ARRAY_SIZE(table_path
), L
"%.8s.idt", table_name
);
491 table_name
= table_path
;
493 if (!import_table( state
, table_name
))
494 return 0; /* failed, do not commit changes */
499 static int export_table( struct msidb_state
*state
, const WCHAR
*table_name
)
501 const WCHAR
*format
= (state
->short_filenames
? L
"%.8s.idt" : L
"%s.idt");
502 WCHAR table_path
[MAX_PATH
];
505 swprintf( table_path
, ARRAY_SIZE(table_path
), format
, table_name
);
506 ret
= MsiDatabaseExportW( state
->database_handle
, table_name
, state
->table_folder
, table_path
);
507 if (ret
!= ERROR_SUCCESS
)
509 ERR( "Failed to export table '%s', error %d.\n", wine_dbgstr_w(table_name
), ret
);
515 static int export_all_tables( struct msidb_state
*state
)
520 ret
= MsiDatabaseOpenViewW( state
->database_handle
, L
"SELECT Name FROM _Tables", &view
);
521 if (ret
!= ERROR_SUCCESS
)
523 ERR( "Failed to open _Tables table.\n" );
526 ret
= MsiViewExecute( view
, 0 );
527 if (ret
!= ERROR_SUCCESS
)
529 ERR( "Failed to query list from _Tables table.\n" );
534 MSIHANDLE record
= 0;
538 ret
= MsiViewFetch( view
, &record
);
539 if (ret
== ERROR_NO_MORE_ITEMS
)
541 if (ret
!= ERROR_SUCCESS
)
543 ERR( "Failed to query row from _Tables table.\n" );
546 size
= ARRAY_SIZE(table
);
547 ret
= MsiRecordGetStringW( record
, 1, table
, &size
);
548 if (ret
!= ERROR_SUCCESS
)
550 ERR( "Failed to retrieve name string.\n" );
553 if (!export_table( state
, table
))
555 ret
= ERROR_FUNCTION_FAILED
;
558 ret
= MsiCloseHandle( record
);
559 if (ret
!= ERROR_SUCCESS
)
561 ERR( "Failed to close record handle.\n" );
566 /* the _SummaryInformation table is not listed in _Tables */
567 if (!export_table( state
, L
"_SummaryInformation" ))
569 ret
= ERROR_FUNCTION_FAILED
;
574 if (view
&& MsiViewClose( view
) != ERROR_SUCCESS
)
576 ERR( "Failed to close _Streams table.\n" );
579 return (ret
== ERROR_SUCCESS
);
582 static int export_tables( struct msidb_state
*state
)
584 struct msidb_listentry
*data
;
586 LIST_FOR_EACH_ENTRY( data
, &state
->table_list
, struct msidb_listentry
, entry
)
588 if (lstrcmpW( data
->name
, L
"*" ) == 0)
590 if (!export_all_tables( state
))
591 return 0; /* failed, do not commit changes */
594 if (!export_table( state
, data
->name
))
595 return 0; /* failed, do not commit changes */
600 int __cdecl
wmain( int argc
, WCHAR
*argv
[] )
602 struct msidb_state state
;
606 memset( &state
, 0x0, sizeof(state
) );
607 list_init( &state
.add_stream_list
);
608 list_init( &state
.extract_stream_list
);
609 list_init( &state
.kill_stream_list
);
610 list_init( &state
.table_list
);
611 /* process and validate all the command line flags */
612 for (i
= 1; n
&& i
< argc
; i
+= n
)
613 n
= process_argument( &state
, i
, argc
, argv
);
614 /* process all remaining arguments as table names */
615 for (; i
< argc
; i
++)
616 list_append( &state
.table_list
, argv
[i
] );
617 if (!valid_state( &state
))
620 /* perform the requested operations */
621 if (!open_database( &state
))
623 ERR( "Failed to open database '%s'.\n", wine_dbgstr_w(state
.database_file
) );
626 if (state
.add_streams
&& !add_streams( &state
))
627 goto cleanup
; /* failed, do not commit changes */
628 if (state
.export_tables
&& !export_tables( &state
))
629 goto cleanup
; /* failed, do not commit changes */
630 if (state
.extract_streams
&& !extract_streams( &state
))
631 goto cleanup
; /* failed, do not commit changes */
632 if (state
.import_tables
&& !import_tables( &state
))
633 goto cleanup
; /* failed, do not commit changes */
634 if (state
.kill_streams
&& !kill_streams( &state
))
635 goto cleanup
; /* failed, do not commit changes */
639 close_database( &state
, ret
== 0 );
640 list_free( &state
.add_stream_list
);
641 list_free( &state
.extract_stream_list
);
642 list_free( &state
.kill_stream_list
);
643 list_free( &state
.table_list
);