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
29 #include "wine/debug.h"
30 #include "wine/unicode.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 (strlenW(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
= strrchrW( filename
, '/' );
224 if (dir_end
) return dir_end
+ 1;
225 dir_end
= strrchrW( filename
, '\\' );
226 if (dir_end
) return dir_end
+ 1;
230 static int add_stream( struct msidb_state
*state
, const WCHAR
*stream_filename
)
232 static const WCHAR insert_command
[] =
233 {'I','N','S','E','R','T',' ','I','N','T','O',' ','_','S','t','r','e','a','m','s',' ',
234 '(','N','a','m','e',',',' ','D','a','t','a',')',' ','V','A','L','U','E','S',' ','(','?',',',' ','?',')',0};
235 MSIHANDLE view
= 0, record
= 0;
238 ret
= MsiDatabaseOpenViewW( state
->database_handle
, insert_command
, &view
);
239 if (ret
!= ERROR_SUCCESS
)
241 ERR( "Failed to open _Streams table.\n" );
244 record
= MsiCreateRecord( 2 );
247 ERR( "Failed to create MSI record.\n" );
248 ret
= ERROR_OUTOFMEMORY
;
251 ret
= MsiRecordSetStringW( record
, 1, basenameW( stream_filename
) );
252 if (ret
!= ERROR_SUCCESS
)
254 ERR( "Failed to add stream filename to MSI record.\n" );
257 ret
= MsiRecordSetStreamW( record
, 2, stream_filename
);
258 if (ret
!= ERROR_SUCCESS
)
260 ERR( "Failed to add stream to MSI record.\n" );
263 ret
= MsiViewExecute( view
, record
);
264 if (ret
!= ERROR_SUCCESS
)
266 ERR( "Failed to add stream to _Streams table.\n" );
272 MsiCloseHandle( record
);
274 MsiViewClose( view
);
276 return (ret
== ERROR_SUCCESS
);
279 static int add_streams( struct msidb_state
*state
)
281 struct msidb_listentry
*data
;
283 LIST_FOR_EACH_ENTRY( data
, &state
->add_stream_list
, struct msidb_listentry
, entry
)
285 if (!add_stream( state
, data
->name
))
286 return 0; /* failed, do not commit changes */
291 static int kill_stream( struct msidb_state
*state
, const WCHAR
*stream_filename
)
293 static const WCHAR delete_command
[] =
294 {'D','E','L','E','T','E',' ','F','R','O','M',' ','_','S','t','r','e','a','m','s',' ',
295 'W','H','E','R','E',' ','N','a','m','e',' ','=',' ','?',0};
296 MSIHANDLE view
= 0, record
= 0;
299 ret
= MsiDatabaseOpenViewW( state
->database_handle
, delete_command
, &view
);
300 if (ret
!= ERROR_SUCCESS
)
302 ERR( "Failed to open _Streams table.\n" );
305 record
= MsiCreateRecord( 1 );
308 ERR( "Failed to create MSI record.\n" );
309 ret
= ERROR_OUTOFMEMORY
;
312 ret
= MsiRecordSetStringW( record
, 1, stream_filename
);
313 if (ret
!= ERROR_SUCCESS
)
315 ERR( "Failed to add stream filename to MSI record.\n" );
318 ret
= MsiViewExecute( view
, record
);
319 if (ret
!= ERROR_SUCCESS
)
321 ERR( "Failed to delete stream from _Streams table.\n" );
327 MsiCloseHandle( record
);
329 MsiViewClose( view
);
331 return (ret
== ERROR_SUCCESS
);
334 static int kill_streams( struct msidb_state
*state
)
336 struct msidb_listentry
*data
;
338 LIST_FOR_EACH_ENTRY( data
, &state
->kill_stream_list
, struct msidb_listentry
, entry
)
340 if (!kill_stream( state
, data
->name
))
341 return 0; /* failed, do not commit changes */
346 static int extract_stream( struct msidb_state
*state
, const WCHAR
*stream_filename
)
348 static const WCHAR select_command
[] =
349 {'S','E','L','E','C','T',' ','D','a','t','a',' ','F','R','O','M',' ','_','S','t','r','e','a','m','s',' ',
350 'W','H','E','R','E',' ','N','a','m','e',' ','=',' ','?',0};
351 HANDLE file
= INVALID_HANDLE_VALUE
;
352 MSIHANDLE view
= 0, record
= 0;
353 DWORD read_size
, write_size
;
357 file
= CreateFileW( stream_filename
, GENERIC_WRITE
, FILE_SHARE_READ
| FILE_SHARE_WRITE
,
358 NULL
, CREATE_NEW
, FILE_ATTRIBUTE_NORMAL
, NULL
);
359 if (file
== INVALID_HANDLE_VALUE
)
361 ret
= ERROR_FILE_NOT_FOUND
;
362 ERR( "Failed to open destination file %s.\n", wine_dbgstr_w(stream_filename
) );
365 ret
= MsiDatabaseOpenViewW( state
->database_handle
, select_command
, &view
);
366 if (ret
!= ERROR_SUCCESS
)
368 ERR( "Failed to open _Streams table.\n" );
371 record
= MsiCreateRecord( 1 );
374 ERR( "Failed to create MSI record.\n" );
375 ret
= ERROR_OUTOFMEMORY
;
378 ret
= MsiRecordSetStringW( record
, 1, stream_filename
);
379 if (ret
!= ERROR_SUCCESS
)
381 ERR( "Failed to add stream filename to MSI record.\n" );
384 ret
= MsiViewExecute( view
, record
);
385 if (ret
!= ERROR_SUCCESS
)
387 ERR( "Failed to query stream from _Streams table.\n" );
390 MsiCloseHandle( record
);
392 ret
= MsiViewFetch( view
, &record
);
393 if (ret
!= ERROR_SUCCESS
)
395 ERR( "Failed to query row from _Streams table.\n" );
398 read_size
= sizeof(buffer
);
399 while (read_size
== sizeof(buffer
))
401 ret
= MsiRecordReadStream( record
, 1, buffer
, &read_size
);
402 if (ret
!= ERROR_SUCCESS
)
404 ERR( "Failed to read stream from _Streams table.\n" );
407 if (!WriteFile( file
, buffer
, read_size
, &write_size
, NULL
) || read_size
!= write_size
)
409 ret
= ERROR_WRITE_FAULT
;
410 ERR( "Failed to write stream to destination file.\n" );
417 MsiCloseHandle( record
);
419 MsiViewClose( view
);
420 if (file
!= INVALID_HANDLE_VALUE
)
422 return (ret
== ERROR_SUCCESS
);
425 static int extract_streams( struct msidb_state
*state
)
427 struct msidb_listentry
*data
;
429 LIST_FOR_EACH_ENTRY( data
, &state
->extract_stream_list
, struct msidb_listentry
, entry
)
431 if (!extract_stream( state
, data
->name
))
432 return 0; /* failed, do not commit changes */
437 static int import_table( struct msidb_state
*state
, const WCHAR
*table_path
)
441 ret
= MsiDatabaseImportW( state
->database_handle
, state
->table_folder
, table_path
);
442 if (ret
!= ERROR_SUCCESS
)
444 ERR( "Failed to import table '%s', error %d.\n", wine_dbgstr_w(table_path
), ret
);
450 static int import_tables( struct msidb_state
*state
)
452 const WCHAR idt_ext
[] = { '.','i','d','t',0 };
453 struct msidb_listentry
*data
;
455 LIST_FOR_EACH_ENTRY( data
, &state
->table_list
, struct msidb_listentry
, entry
)
457 WCHAR
*table_name
= data
->name
;
458 WCHAR table_path
[MAX_PATH
];
461 /* permit specifying tables by filename (*.idt) */
462 if ((ext
= PathFindExtensionW( table_name
)) == NULL
|| lstrcmpW( ext
, idt_ext
) != 0)
464 const WCHAR format
[] = { '%','.','8','s','.','i','d','t',0 }; /* truncate to 8 characters */
465 snprintfW( table_path
, ARRAY_SIZE(table_path
), format
, table_name
);
466 table_name
= table_path
;
468 if (!import_table( state
, table_name
))
469 return 0; /* failed, do not commit changes */
474 static int export_table( struct msidb_state
*state
, const WCHAR
*table_name
)
476 const WCHAR format_dos
[] = { '%','.','8','s','.','i','d','t',0 }; /* truncate to 8 characters */
477 const WCHAR format_full
[] = { '%','s','.','i','d','t',0 };
478 const WCHAR
*format
= (state
->short_filenames
? format_dos
: format_full
);
479 WCHAR table_path
[MAX_PATH
];
482 snprintfW( table_path
, ARRAY_SIZE(table_path
), format
, table_name
);
483 ret
= MsiDatabaseExportW( state
->database_handle
, table_name
, state
->table_folder
, table_path
);
484 if (ret
!= ERROR_SUCCESS
)
486 ERR( "Failed to export table '%s', error %d.\n", wine_dbgstr_w(table_name
), ret
);
492 static int export_all_tables( struct msidb_state
*state
)
494 static const WCHAR summary_information
[] =
495 {'_','S','u','m','m','a','r','y','I','n','f','o','r','m','a','t','i','o','n',0};
496 static const WCHAR query_command
[] =
497 {'S','E','L','E','C','T',' ','N','a','m','e',' ','F','R','O','M',' ','_','T','a','b','l','e','s',0};
501 ret
= MsiDatabaseOpenViewW( state
->database_handle
, query_command
, &view
);
502 if (ret
!= ERROR_SUCCESS
)
504 ERR( "Failed to open _Tables table.\n" );
507 ret
= MsiViewExecute( view
, 0 );
508 if (ret
!= ERROR_SUCCESS
)
510 ERR( "Failed to query list from _Tables table.\n" );
515 MSIHANDLE record
= 0;
519 ret
= MsiViewFetch( view
, &record
);
520 if (ret
== ERROR_NO_MORE_ITEMS
)
522 if (ret
!= ERROR_SUCCESS
)
524 ERR( "Failed to query row from _Tables table.\n" );
527 size
= ARRAY_SIZE(table
);
528 ret
= MsiRecordGetStringW( record
, 1, table
, &size
);
529 if (ret
!= ERROR_SUCCESS
)
531 ERR( "Failed to retrieve name string.\n" );
534 if (!export_table( state
, table
))
536 ret
= ERROR_FUNCTION_FAILED
;
539 ret
= MsiCloseHandle( record
);
540 if (ret
!= ERROR_SUCCESS
)
542 ERR( "Failed to close record handle.\n" );
547 /* the _SummaryInformation table is not listed in _Tables */
548 if (!export_table( state
, summary_information
))
550 ret
= ERROR_FUNCTION_FAILED
;
555 if (view
&& MsiViewClose( view
) != ERROR_SUCCESS
)
557 ERR( "Failed to close _Streams table.\n" );
560 return (ret
== ERROR_SUCCESS
);
563 static int export_tables( struct msidb_state
*state
)
565 const WCHAR wildcard
[] = { '*',0 };
566 struct msidb_listentry
*data
;
568 LIST_FOR_EACH_ENTRY( data
, &state
->table_list
, struct msidb_listentry
, entry
)
570 if (strcmpW( data
->name
, wildcard
) == 0)
572 if (!export_all_tables( state
))
573 return 0; /* failed, do not commit changes */
576 if (!export_table( state
, data
->name
))
577 return 0; /* failed, do not commit changes */
582 int wmain( int argc
, WCHAR
*argv
[] )
584 struct msidb_state state
;
588 memset( &state
, 0x0, sizeof(state
) );
589 list_init( &state
.add_stream_list
);
590 list_init( &state
.extract_stream_list
);
591 list_init( &state
.kill_stream_list
);
592 list_init( &state
.table_list
);
593 /* process and validate all the command line flags */
594 for (i
= 1; n
&& i
< argc
; i
+= n
)
595 n
= process_argument( &state
, i
, argc
, argv
);
596 /* process all remaining arguments as table names */
597 for (; i
< argc
; i
++)
598 list_append( &state
.table_list
, argv
[i
] );
599 if (!valid_state( &state
))
602 /* perform the requested operations */
603 if (!open_database( &state
))
605 ERR( "Failed to open database '%s'.\n", wine_dbgstr_w(state
.database_file
) );
608 if (state
.add_streams
&& !add_streams( &state
))
609 goto cleanup
; /* failed, do not commit changes */
610 if (state
.export_tables
&& !export_tables( &state
))
611 goto cleanup
; /* failed, do not commit changes */
612 if (state
.extract_streams
&& !extract_streams( &state
))
613 goto cleanup
; /* failed, do not commit changes */
614 if (state
.import_tables
&& !import_tables( &state
))
615 goto cleanup
; /* failed, do not commit changes */
616 if (state
.kill_streams
&& !kill_streams( &state
))
617 goto cleanup
; /* failed, do not commit changes */
621 close_database( &state
, ret
== 0 );
622 list_free( &state
.add_stream_list
);
623 list_free( &state
.extract_stream_list
);
624 list_free( &state
.kill_stream_list
);
625 list_free( &state
.table_list
);