main.c 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645
  1. /*
  2. * msidb - command line tool for assembling MSI packages
  3. *
  4. * Copyright 2015 Erich E. Hoover
  5. *
  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.
  10. *
  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.
  15. *
  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
  19. */
  20. #define WIN32_LEAN_AND_MEAN
  21. #include <stdlib.h>
  22. #include <stdio.h>
  23. #include <windows.h>
  24. #include <msi.h>
  25. #include <msiquery.h>
  26. #include <shlwapi.h>
  27. #include "wine/debug.h"
  28. #include "wine/list.h"
  29. WINE_DEFAULT_DEBUG_CHANNEL(msidb);
  30. struct msidb_listentry
  31. {
  32. struct list entry;
  33. WCHAR *name;
  34. };
  35. struct msidb_state
  36. {
  37. WCHAR *database_file;
  38. WCHAR *table_folder;
  39. MSIHANDLE database_handle;
  40. BOOL add_streams;
  41. BOOL extract_streams;
  42. BOOL kill_streams;
  43. BOOL create_database;
  44. BOOL import_tables;
  45. BOOL export_tables;
  46. BOOL short_filenames;
  47. struct list add_stream_list;
  48. struct list extract_stream_list;
  49. struct list kill_stream_list;
  50. struct list table_list;
  51. };
  52. static void list_free( struct list *list )
  53. {
  54. struct msidb_listentry *data, *next;
  55. LIST_FOR_EACH_ENTRY_SAFE( data, next, list, struct msidb_listentry, entry )
  56. {
  57. list_remove( &data->entry );
  58. HeapFree( GetProcessHeap(), 0, data );
  59. }
  60. }
  61. static void list_append( struct list *list, WCHAR *name )
  62. {
  63. struct msidb_listentry *data;
  64. data = HeapAlloc( GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(struct msidb_listentry) );
  65. if (!data)
  66. {
  67. ERR( "Out of memory for list.\n" );
  68. exit( 1 );
  69. }
  70. data->name = name;
  71. list_add_tail( list, &data->entry );
  72. }
  73. static void show_usage( void )
  74. {
  75. WINE_MESSAGE(
  76. "Usage: msidb [OPTION]...[OPTION]... [TABLE]...[TABLE]\n"
  77. "Options:\n"
  78. " -? Show this usage message and exit.\n"
  79. " -a file.cab Add stream/cabinet file to _Streams table.\n"
  80. " -c Create database file (instead of opening existing file).\n"
  81. " -d package.msi Path to the database file.\n"
  82. " -e Export tables from database.\n"
  83. " -f folder Folder in which to open/save the tables.\n"
  84. " -i Import tables into database.\n"
  85. " -k file.cab Kill (remove) stream/cabinet file from _Streams table.\n"
  86. " -s Export with short filenames (eight character max).\n"
  87. " -x file.cab Extract stream/cabinet file from _Streams table.\n"
  88. );
  89. }
  90. static int valid_state( struct msidb_state *state )
  91. {
  92. if (state->database_file == NULL)
  93. {
  94. FIXME( "GUI operation is not currently supported.\n" );
  95. return 0;
  96. }
  97. if (state->table_folder == NULL && !state->add_streams && !state->kill_streams
  98. && !state->extract_streams)
  99. {
  100. ERR( "No table folder specified (-f option).\n" );
  101. show_usage();
  102. return 0;
  103. }
  104. if (!state->create_database && !state->import_tables && !state->export_tables
  105. && !state->add_streams&& !state->kill_streams && !state->extract_streams)
  106. {
  107. ERR( "No mode flag specified (-a, -c, -e, -i, -k, -x).\n" );
  108. show_usage();
  109. return 0;
  110. }
  111. if (list_empty( &state->table_list ) && !state->add_streams && !state->kill_streams
  112. && !state->extract_streams)
  113. {
  114. ERR( "No tables specified.\n" );
  115. return 0;
  116. }
  117. return 1;
  118. }
  119. static int process_argument( struct msidb_state *state, int i, int argc, WCHAR *argv[] )
  120. {
  121. /* msidb accepts either "-" or "/" style flags */
  122. if (lstrlenW(argv[i]) != 2 || (argv[i][0] != '-' && argv[i][0] != '/'))
  123. return 0;
  124. switch( argv[i][1] )
  125. {
  126. case '?':
  127. show_usage();
  128. exit( 0 );
  129. case 'a':
  130. if (i + 1 >= argc) return 0;
  131. state->add_streams = TRUE;
  132. list_append( &state->add_stream_list, argv[i + 1] );
  133. return 2;
  134. case 'c':
  135. state->create_database = TRUE;
  136. return 1;
  137. case 'd':
  138. if (i + 1 >= argc) return 0;
  139. state->database_file = argv[i + 1];
  140. return 2;
  141. case 'e':
  142. state->export_tables = TRUE;
  143. return 1;
  144. case 'f':
  145. if (i + 1 >= argc) return 0;
  146. state->table_folder = argv[i + 1];
  147. return 2;
  148. case 'i':
  149. state->import_tables = TRUE;
  150. return 1;
  151. case 'k':
  152. if (i + 1 >= argc) return 0;
  153. state->kill_streams = TRUE;
  154. list_append( &state->kill_stream_list, argv[i + 1] );
  155. return 2;
  156. case 's':
  157. state->short_filenames = TRUE;
  158. return 1;
  159. case 'x':
  160. if (i + 1 >= argc) return 0;
  161. state->extract_streams = TRUE;
  162. list_append( &state->extract_stream_list, argv[i + 1] );
  163. return 2;
  164. default:
  165. break;
  166. }
  167. show_usage();
  168. exit( 1 );
  169. }
  170. static int open_database( struct msidb_state *state )
  171. {
  172. LPCWSTR db_mode = state->create_database ? MSIDBOPEN_CREATE : MSIDBOPEN_TRANSACT;
  173. UINT ret;
  174. ret = MsiOpenDatabaseW( state->database_file, db_mode, &state->database_handle );
  175. if (ret != ERROR_SUCCESS)
  176. {
  177. ERR( "Failed to open database '%s', error %d\n", wine_dbgstr_w(state->database_file), ret );
  178. return 0;
  179. }
  180. return 1;
  181. }
  182. static void close_database( struct msidb_state *state, BOOL commit_changes )
  183. {
  184. UINT ret = ERROR_SUCCESS;
  185. if (state->database_handle == 0)
  186. return;
  187. if (commit_changes)
  188. ret = MsiDatabaseCommit( state->database_handle );
  189. if (ret != ERROR_SUCCESS)
  190. {
  191. ERR( "Failed to commit changes to database.\n" );
  192. return;
  193. }
  194. ret = MsiCloseHandle( state->database_handle );
  195. if (ret != ERROR_SUCCESS)
  196. {
  197. WARN( "Failed to close database handle.\n" );
  198. return;
  199. }
  200. }
  201. static const WCHAR *basenameW( const WCHAR *filename )
  202. {
  203. const WCHAR *dir_end;
  204. dir_end = wcsrchr( filename, '/' );
  205. if (dir_end) return dir_end + 1;
  206. dir_end = wcsrchr( filename, '\\' );
  207. if (dir_end) return dir_end + 1;
  208. return filename;
  209. }
  210. static int add_stream( struct msidb_state *state, const WCHAR *stream_filename )
  211. {
  212. MSIHANDLE view = 0, record = 0;
  213. UINT ret;
  214. ret = MsiDatabaseOpenViewW( state->database_handle, L"INSERT INTO _Streams (Name, Data) VALUES (?, ?)", &view );
  215. if (ret != ERROR_SUCCESS)
  216. {
  217. ERR( "Failed to open _Streams table.\n" );
  218. goto cleanup;
  219. }
  220. record = MsiCreateRecord( 2 );
  221. if (record == 0)
  222. {
  223. ERR( "Failed to create MSI record.\n" );
  224. ret = ERROR_OUTOFMEMORY;
  225. goto cleanup;
  226. }
  227. ret = MsiRecordSetStringW( record, 1, basenameW( stream_filename ) );
  228. if (ret != ERROR_SUCCESS)
  229. {
  230. ERR( "Failed to add stream filename to MSI record.\n" );
  231. goto cleanup;
  232. }
  233. ret = MsiRecordSetStreamW( record, 2, stream_filename );
  234. if (ret != ERROR_SUCCESS)
  235. {
  236. ERR( "Failed to add stream to MSI record.\n" );
  237. goto cleanup;
  238. }
  239. ret = MsiViewExecute( view, record );
  240. if (ret != ERROR_SUCCESS)
  241. {
  242. ERR( "Failed to add stream to _Streams table.\n" );
  243. goto cleanup;
  244. }
  245. cleanup:
  246. if (record)
  247. MsiCloseHandle( record );
  248. if (view)
  249. MsiViewClose( view );
  250. return (ret == ERROR_SUCCESS);
  251. }
  252. static int add_streams( struct msidb_state *state )
  253. {
  254. struct msidb_listentry *data;
  255. LIST_FOR_EACH_ENTRY( data, &state->add_stream_list, struct msidb_listentry, entry )
  256. {
  257. if (!add_stream( state, data->name ))
  258. return 0; /* failed, do not commit changes */
  259. }
  260. return 1;
  261. }
  262. static int kill_stream( struct msidb_state *state, const WCHAR *stream_filename )
  263. {
  264. MSIHANDLE view = 0, record = 0;
  265. UINT ret;
  266. ret = MsiDatabaseOpenViewW( state->database_handle, L"DELETE FROM _Streams WHERE Name = ?", &view );
  267. if (ret != ERROR_SUCCESS)
  268. {
  269. ERR( "Failed to open _Streams table.\n" );
  270. goto cleanup;
  271. }
  272. record = MsiCreateRecord( 1 );
  273. if (record == 0)
  274. {
  275. ERR( "Failed to create MSI record.\n" );
  276. ret = ERROR_OUTOFMEMORY;
  277. goto cleanup;
  278. }
  279. ret = MsiRecordSetStringW( record, 1, stream_filename );
  280. if (ret != ERROR_SUCCESS)
  281. {
  282. ERR( "Failed to add stream filename to MSI record.\n" );
  283. goto cleanup;
  284. }
  285. ret = MsiViewExecute( view, record );
  286. if (ret != ERROR_SUCCESS)
  287. {
  288. ERR( "Failed to delete stream from _Streams table.\n" );
  289. goto cleanup;
  290. }
  291. cleanup:
  292. if (record)
  293. MsiCloseHandle( record );
  294. if (view)
  295. MsiViewClose( view );
  296. return (ret == ERROR_SUCCESS);
  297. }
  298. static int kill_streams( struct msidb_state *state )
  299. {
  300. struct msidb_listentry *data;
  301. LIST_FOR_EACH_ENTRY( data, &state->kill_stream_list, struct msidb_listentry, entry )
  302. {
  303. if (!kill_stream( state, data->name ))
  304. return 0; /* failed, do not commit changes */
  305. }
  306. return 1;
  307. }
  308. static int extract_stream( struct msidb_state *state, const WCHAR *stream_filename )
  309. {
  310. HANDLE file = INVALID_HANDLE_VALUE;
  311. MSIHANDLE view = 0, record = 0;
  312. DWORD read_size, write_size;
  313. char buffer[1024];
  314. UINT ret;
  315. file = CreateFileW( stream_filename, GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE,
  316. NULL, CREATE_NEW, FILE_ATTRIBUTE_NORMAL, NULL );
  317. if (file == INVALID_HANDLE_VALUE)
  318. {
  319. ret = ERROR_FILE_NOT_FOUND;
  320. ERR( "Failed to open destination file %s.\n", wine_dbgstr_w(stream_filename) );
  321. goto cleanup;
  322. }
  323. ret = MsiDatabaseOpenViewW( state->database_handle, L"SELECT Data FROM _Streams WHERE Name = ?", &view );
  324. if (ret != ERROR_SUCCESS)
  325. {
  326. ERR( "Failed to open _Streams table.\n" );
  327. goto cleanup;
  328. }
  329. record = MsiCreateRecord( 1 );
  330. if (record == 0)
  331. {
  332. ERR( "Failed to create MSI record.\n" );
  333. ret = ERROR_OUTOFMEMORY;
  334. goto cleanup;
  335. }
  336. ret = MsiRecordSetStringW( record, 1, stream_filename );
  337. if (ret != ERROR_SUCCESS)
  338. {
  339. ERR( "Failed to add stream filename to MSI record.\n" );
  340. goto cleanup;
  341. }
  342. ret = MsiViewExecute( view, record );
  343. if (ret != ERROR_SUCCESS)
  344. {
  345. ERR( "Failed to query stream from _Streams table.\n" );
  346. goto cleanup;
  347. }
  348. MsiCloseHandle( record );
  349. record = 0;
  350. ret = MsiViewFetch( view, &record );
  351. if (ret != ERROR_SUCCESS)
  352. {
  353. ERR( "Failed to query row from _Streams table.\n" );
  354. goto cleanup;
  355. }
  356. read_size = sizeof(buffer);
  357. while (read_size == sizeof(buffer))
  358. {
  359. ret = MsiRecordReadStream( record, 1, buffer, &read_size );
  360. if (ret != ERROR_SUCCESS)
  361. {
  362. ERR( "Failed to read stream from _Streams table.\n" );
  363. goto cleanup;
  364. }
  365. if (!WriteFile( file, buffer, read_size, &write_size, NULL ) || read_size != write_size)
  366. {
  367. ret = ERROR_WRITE_FAULT;
  368. ERR( "Failed to write stream to destination file.\n" );
  369. goto cleanup;
  370. }
  371. }
  372. cleanup:
  373. if (record)
  374. MsiCloseHandle( record );
  375. if (view)
  376. MsiViewClose( view );
  377. if (file != INVALID_HANDLE_VALUE)
  378. CloseHandle( file );
  379. return (ret == ERROR_SUCCESS);
  380. }
  381. static int extract_streams( struct msidb_state *state )
  382. {
  383. struct msidb_listentry *data;
  384. LIST_FOR_EACH_ENTRY( data, &state->extract_stream_list, struct msidb_listentry, entry )
  385. {
  386. if (!extract_stream( state, data->name ))
  387. return 0; /* failed, do not commit changes */
  388. }
  389. return 1;
  390. }
  391. static int import_table( struct msidb_state *state, const WCHAR *table_path )
  392. {
  393. UINT ret;
  394. ret = MsiDatabaseImportW( state->database_handle, state->table_folder, table_path );
  395. if (ret != ERROR_SUCCESS)
  396. {
  397. ERR( "Failed to import table '%s', error %d.\n", wine_dbgstr_w(table_path), ret );
  398. return 0;
  399. }
  400. return 1;
  401. }
  402. static int import_tables( struct msidb_state *state )
  403. {
  404. struct msidb_listentry *data;
  405. LIST_FOR_EACH_ENTRY( data, &state->table_list, struct msidb_listentry, entry )
  406. {
  407. WCHAR *table_name = data->name;
  408. WCHAR table_path[MAX_PATH];
  409. WCHAR *ext;
  410. /* permit specifying tables with wildcards ('Feature*') */
  411. if (wcsstr( table_name, L"*" ) != NULL)
  412. {
  413. WIN32_FIND_DATAW f;
  414. HANDLE handle;
  415. WCHAR *path;
  416. DWORD len;
  417. len = lstrlenW( state->table_folder ) + 1 + lstrlenW( table_name ) + 1; /* %s/%s\0 */
  418. path = HeapAlloc( GetProcessHeap(), 0, len * sizeof(WCHAR) );
  419. if (path == NULL)
  420. return 0;
  421. lstrcpyW( path, state->table_folder );
  422. PathAddBackslashW( path );
  423. lstrcatW( path, table_name );
  424. handle = FindFirstFileW( path, &f );
  425. HeapFree( GetProcessHeap(), 0, path );
  426. if (handle == INVALID_HANDLE_VALUE)
  427. return 0;
  428. do
  429. {
  430. if (f.cFileName[0] == '.' && !f.cFileName[1]) continue;
  431. if (f.cFileName[0] == '.' && f.cFileName[1] == '.' && !f.cFileName[2]) continue;
  432. if (f.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) continue;
  433. if ((ext = PathFindExtensionW( f.cFileName )) == NULL) continue;
  434. if (lstrcmpW( ext, L".idt" ) != 0) continue;
  435. if (!import_table( state, f.cFileName ))
  436. {
  437. FindClose( handle );
  438. return 0; /* failed, do not commit changes */
  439. }
  440. } while (FindNextFileW( handle, &f ));
  441. FindClose( handle );
  442. continue;
  443. }
  444. /* permit specifying tables by filename (*.idt) */
  445. if ((ext = PathFindExtensionW( table_name )) == NULL || lstrcmpW( ext, L".idt" ) != 0)
  446. {
  447. /* truncate to 8 characters */
  448. swprintf( table_path, ARRAY_SIZE(table_path), L"%.8s.idt", table_name );
  449. table_name = table_path;
  450. }
  451. if (!import_table( state, table_name ))
  452. return 0; /* failed, do not commit changes */
  453. }
  454. return 1;
  455. }
  456. static int export_table( struct msidb_state *state, const WCHAR *table_name )
  457. {
  458. const WCHAR *format = (state->short_filenames ? L"%.8s.idt" : L"%s.idt");
  459. WCHAR table_path[MAX_PATH];
  460. UINT ret;
  461. swprintf( table_path, ARRAY_SIZE(table_path), format, table_name );
  462. ret = MsiDatabaseExportW( state->database_handle, table_name, state->table_folder, table_path );
  463. if (ret != ERROR_SUCCESS)
  464. {
  465. ERR( "Failed to export table '%s', error %d.\n", wine_dbgstr_w(table_name), ret );
  466. return 0;
  467. }
  468. return 1;
  469. }
  470. static int export_all_tables( struct msidb_state *state )
  471. {
  472. MSIHANDLE view = 0;
  473. UINT ret;
  474. ret = MsiDatabaseOpenViewW( state->database_handle, L"SELECT Name FROM _Tables", &view );
  475. if (ret != ERROR_SUCCESS)
  476. {
  477. ERR( "Failed to open _Tables table.\n" );
  478. goto cleanup;
  479. }
  480. ret = MsiViewExecute( view, 0 );
  481. if (ret != ERROR_SUCCESS)
  482. {
  483. ERR( "Failed to query list from _Tables table.\n" );
  484. goto cleanup;
  485. }
  486. while( 1 )
  487. {
  488. MSIHANDLE record = 0;
  489. WCHAR table[256];
  490. DWORD size;
  491. ret = MsiViewFetch( view, &record );
  492. if (ret == ERROR_NO_MORE_ITEMS)
  493. break;
  494. if (ret != ERROR_SUCCESS)
  495. {
  496. ERR( "Failed to query row from _Tables table.\n" );
  497. goto cleanup;
  498. }
  499. size = ARRAY_SIZE(table);
  500. ret = MsiRecordGetStringW( record, 1, table, &size );
  501. if (ret != ERROR_SUCCESS)
  502. {
  503. ERR( "Failed to retrieve name string.\n" );
  504. goto cleanup;
  505. }
  506. if (!export_table( state, table ))
  507. {
  508. ret = ERROR_FUNCTION_FAILED;
  509. goto cleanup;
  510. }
  511. ret = MsiCloseHandle( record );
  512. if (ret != ERROR_SUCCESS)
  513. {
  514. ERR( "Failed to close record handle.\n" );
  515. goto cleanup;
  516. }
  517. }
  518. ret = ERROR_SUCCESS;
  519. /* the _SummaryInformation table is not listed in _Tables */
  520. if (!export_table( state, L"_SummaryInformation" ))
  521. {
  522. ret = ERROR_FUNCTION_FAILED;
  523. goto cleanup;
  524. }
  525. cleanup:
  526. if (view && MsiViewClose( view ) != ERROR_SUCCESS)
  527. {
  528. ERR( "Failed to close _Streams table.\n" );
  529. return 0;
  530. }
  531. return (ret == ERROR_SUCCESS);
  532. }
  533. static int export_tables( struct msidb_state *state )
  534. {
  535. struct msidb_listentry *data;
  536. LIST_FOR_EACH_ENTRY( data, &state->table_list, struct msidb_listentry, entry )
  537. {
  538. if (lstrcmpW( data->name, L"*" ) == 0)
  539. {
  540. if (!export_all_tables( state ))
  541. return 0; /* failed, do not commit changes */
  542. continue;
  543. }
  544. if (!export_table( state, data->name ))
  545. return 0; /* failed, do not commit changes */
  546. }
  547. return 1;
  548. }
  549. int __cdecl wmain( int argc, WCHAR *argv[] )
  550. {
  551. struct msidb_state state;
  552. int i, n = 1;
  553. int ret = 1;
  554. memset( &state, 0x0, sizeof(state) );
  555. list_init( &state.add_stream_list );
  556. list_init( &state.extract_stream_list );
  557. list_init( &state.kill_stream_list );
  558. list_init( &state.table_list );
  559. /* process and validate all the command line flags */
  560. for (i = 1; n && i < argc; i += n)
  561. n = process_argument( &state, i, argc, argv );
  562. /* process all remaining arguments as table names */
  563. for (; i < argc; i++)
  564. list_append( &state.table_list, argv[i] );
  565. if (!valid_state( &state ))
  566. goto cleanup;
  567. /* perform the requested operations */
  568. if (!open_database( &state ))
  569. {
  570. ERR( "Failed to open database '%s'.\n", wine_dbgstr_w(state.database_file) );
  571. goto cleanup;
  572. }
  573. if (state.add_streams && !add_streams( &state ))
  574. goto cleanup; /* failed, do not commit changes */
  575. if (state.export_tables && !export_tables( &state ))
  576. goto cleanup; /* failed, do not commit changes */
  577. if (state.extract_streams && !extract_streams( &state ))
  578. goto cleanup; /* failed, do not commit changes */
  579. if (state.import_tables && !import_tables( &state ))
  580. goto cleanup; /* failed, do not commit changes */
  581. if (state.kill_streams && !kill_streams( &state ))
  582. goto cleanup; /* failed, do not commit changes */
  583. ret = 0;
  584. cleanup:
  585. close_database( &state, ret == 0 );
  586. list_free( &state.add_stream_list );
  587. list_free( &state.extract_stream_list );
  588. list_free( &state.kill_stream_list );
  589. list_free( &state.table_list );
  590. return ret;
  591. }