123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645 |
- /*
- * msidb - command line tool for assembling MSI packages
- *
- * Copyright 2015 Erich E. Hoover
- *
- * This library is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 2.1 of the License, or (at your option) any later version.
- *
- * This library is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public
- * License along with this library; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
- */
- #define WIN32_LEAN_AND_MEAN
- #include <stdlib.h>
- #include <stdio.h>
- #include <windows.h>
- #include <msi.h>
- #include <msiquery.h>
- #include <shlwapi.h>
- #include "wine/debug.h"
- #include "wine/list.h"
- WINE_DEFAULT_DEBUG_CHANNEL(msidb);
- struct msidb_listentry
- {
- struct list entry;
- WCHAR *name;
- };
- struct msidb_state
- {
- WCHAR *database_file;
- WCHAR *table_folder;
- MSIHANDLE database_handle;
- BOOL add_streams;
- BOOL extract_streams;
- BOOL kill_streams;
- BOOL create_database;
- BOOL import_tables;
- BOOL export_tables;
- BOOL short_filenames;
- struct list add_stream_list;
- struct list extract_stream_list;
- struct list kill_stream_list;
- struct list table_list;
- };
- static void list_free( struct list *list )
- {
- struct msidb_listentry *data, *next;
- LIST_FOR_EACH_ENTRY_SAFE( data, next, list, struct msidb_listentry, entry )
- {
- list_remove( &data->entry );
- HeapFree( GetProcessHeap(), 0, data );
- }
- }
- static void list_append( struct list *list, WCHAR *name )
- {
- struct msidb_listentry *data;
- data = HeapAlloc( GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(struct msidb_listentry) );
- if (!data)
- {
- ERR( "Out of memory for list.\n" );
- exit( 1 );
- }
- data->name = name;
- list_add_tail( list, &data->entry );
- }
- static void show_usage( void )
- {
- WINE_MESSAGE(
- "Usage: msidb [OPTION]...[OPTION]... [TABLE]...[TABLE]\n"
- "Options:\n"
- " -? Show this usage message and exit.\n"
- " -a file.cab Add stream/cabinet file to _Streams table.\n"
- " -c Create database file (instead of opening existing file).\n"
- " -d package.msi Path to the database file.\n"
- " -e Export tables from database.\n"
- " -f folder Folder in which to open/save the tables.\n"
- " -i Import tables into database.\n"
- " -k file.cab Kill (remove) stream/cabinet file from _Streams table.\n"
- " -s Export with short filenames (eight character max).\n"
- " -x file.cab Extract stream/cabinet file from _Streams table.\n"
- );
- }
- static int valid_state( struct msidb_state *state )
- {
- if (state->database_file == NULL)
- {
- FIXME( "GUI operation is not currently supported.\n" );
- return 0;
- }
- if (state->table_folder == NULL && !state->add_streams && !state->kill_streams
- && !state->extract_streams)
- {
- ERR( "No table folder specified (-f option).\n" );
- show_usage();
- return 0;
- }
- if (!state->create_database && !state->import_tables && !state->export_tables
- && !state->add_streams&& !state->kill_streams && !state->extract_streams)
- {
- ERR( "No mode flag specified (-a, -c, -e, -i, -k, -x).\n" );
- show_usage();
- return 0;
- }
- if (list_empty( &state->table_list ) && !state->add_streams && !state->kill_streams
- && !state->extract_streams)
- {
- ERR( "No tables specified.\n" );
- return 0;
- }
- return 1;
- }
- static int process_argument( struct msidb_state *state, int i, int argc, WCHAR *argv[] )
- {
- /* msidb accepts either "-" or "/" style flags */
- if (lstrlenW(argv[i]) != 2 || (argv[i][0] != '-' && argv[i][0] != '/'))
- return 0;
- switch( argv[i][1] )
- {
- case '?':
- show_usage();
- exit( 0 );
- case 'a':
- if (i + 1 >= argc) return 0;
- state->add_streams = TRUE;
- list_append( &state->add_stream_list, argv[i + 1] );
- return 2;
- case 'c':
- state->create_database = TRUE;
- return 1;
- case 'd':
- if (i + 1 >= argc) return 0;
- state->database_file = argv[i + 1];
- return 2;
- case 'e':
- state->export_tables = TRUE;
- return 1;
- case 'f':
- if (i + 1 >= argc) return 0;
- state->table_folder = argv[i + 1];
- return 2;
- case 'i':
- state->import_tables = TRUE;
- return 1;
- case 'k':
- if (i + 1 >= argc) return 0;
- state->kill_streams = TRUE;
- list_append( &state->kill_stream_list, argv[i + 1] );
- return 2;
- case 's':
- state->short_filenames = TRUE;
- return 1;
- case 'x':
- if (i + 1 >= argc) return 0;
- state->extract_streams = TRUE;
- list_append( &state->extract_stream_list, argv[i + 1] );
- return 2;
- default:
- break;
- }
- show_usage();
- exit( 1 );
- }
- static int open_database( struct msidb_state *state )
- {
- LPCWSTR db_mode = state->create_database ? MSIDBOPEN_CREATE : MSIDBOPEN_TRANSACT;
- UINT ret;
- ret = MsiOpenDatabaseW( state->database_file, db_mode, &state->database_handle );
- if (ret != ERROR_SUCCESS)
- {
- ERR( "Failed to open database '%s', error %d\n", wine_dbgstr_w(state->database_file), ret );
- return 0;
- }
- return 1;
- }
- static void close_database( struct msidb_state *state, BOOL commit_changes )
- {
- UINT ret = ERROR_SUCCESS;
- if (state->database_handle == 0)
- return;
- if (commit_changes)
- ret = MsiDatabaseCommit( state->database_handle );
- if (ret != ERROR_SUCCESS)
- {
- ERR( "Failed to commit changes to database.\n" );
- return;
- }
- ret = MsiCloseHandle( state->database_handle );
- if (ret != ERROR_SUCCESS)
- {
- WARN( "Failed to close database handle.\n" );
- return;
- }
- }
- static const WCHAR *basenameW( const WCHAR *filename )
- {
- const WCHAR *dir_end;
- dir_end = wcsrchr( filename, '/' );
- if (dir_end) return dir_end + 1;
- dir_end = wcsrchr( filename, '\\' );
- if (dir_end) return dir_end + 1;
- return filename;
- }
- static int add_stream( struct msidb_state *state, const WCHAR *stream_filename )
- {
- MSIHANDLE view = 0, record = 0;
- UINT ret;
- ret = MsiDatabaseOpenViewW( state->database_handle, L"INSERT INTO _Streams (Name, Data) VALUES (?, ?)", &view );
- if (ret != ERROR_SUCCESS)
- {
- ERR( "Failed to open _Streams table.\n" );
- goto cleanup;
- }
- record = MsiCreateRecord( 2 );
- if (record == 0)
- {
- ERR( "Failed to create MSI record.\n" );
- ret = ERROR_OUTOFMEMORY;
- goto cleanup;
- }
- ret = MsiRecordSetStringW( record, 1, basenameW( stream_filename ) );
- if (ret != ERROR_SUCCESS)
- {
- ERR( "Failed to add stream filename to MSI record.\n" );
- goto cleanup;
- }
- ret = MsiRecordSetStreamW( record, 2, stream_filename );
- if (ret != ERROR_SUCCESS)
- {
- ERR( "Failed to add stream to MSI record.\n" );
- goto cleanup;
- }
- ret = MsiViewExecute( view, record );
- if (ret != ERROR_SUCCESS)
- {
- ERR( "Failed to add stream to _Streams table.\n" );
- goto cleanup;
- }
- cleanup:
- if (record)
- MsiCloseHandle( record );
- if (view)
- MsiViewClose( view );
- return (ret == ERROR_SUCCESS);
- }
- static int add_streams( struct msidb_state *state )
- {
- struct msidb_listentry *data;
- LIST_FOR_EACH_ENTRY( data, &state->add_stream_list, struct msidb_listentry, entry )
- {
- if (!add_stream( state, data->name ))
- return 0; /* failed, do not commit changes */
- }
- return 1;
- }
- static int kill_stream( struct msidb_state *state, const WCHAR *stream_filename )
- {
- MSIHANDLE view = 0, record = 0;
- UINT ret;
- ret = MsiDatabaseOpenViewW( state->database_handle, L"DELETE FROM _Streams WHERE Name = ?", &view );
- if (ret != ERROR_SUCCESS)
- {
- ERR( "Failed to open _Streams table.\n" );
- goto cleanup;
- }
- record = MsiCreateRecord( 1 );
- if (record == 0)
- {
- ERR( "Failed to create MSI record.\n" );
- ret = ERROR_OUTOFMEMORY;
- goto cleanup;
- }
- ret = MsiRecordSetStringW( record, 1, stream_filename );
- if (ret != ERROR_SUCCESS)
- {
- ERR( "Failed to add stream filename to MSI record.\n" );
- goto cleanup;
- }
- ret = MsiViewExecute( view, record );
- if (ret != ERROR_SUCCESS)
- {
- ERR( "Failed to delete stream from _Streams table.\n" );
- goto cleanup;
- }
- cleanup:
- if (record)
- MsiCloseHandle( record );
- if (view)
- MsiViewClose( view );
- return (ret == ERROR_SUCCESS);
- }
- static int kill_streams( struct msidb_state *state )
- {
- struct msidb_listentry *data;
- LIST_FOR_EACH_ENTRY( data, &state->kill_stream_list, struct msidb_listentry, entry )
- {
- if (!kill_stream( state, data->name ))
- return 0; /* failed, do not commit changes */
- }
- return 1;
- }
- static int extract_stream( struct msidb_state *state, const WCHAR *stream_filename )
- {
- HANDLE file = INVALID_HANDLE_VALUE;
- MSIHANDLE view = 0, record = 0;
- DWORD read_size, write_size;
- char buffer[1024];
- UINT ret;
- file = CreateFileW( stream_filename, GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE,
- NULL, CREATE_NEW, FILE_ATTRIBUTE_NORMAL, NULL );
- if (file == INVALID_HANDLE_VALUE)
- {
- ret = ERROR_FILE_NOT_FOUND;
- ERR( "Failed to open destination file %s.\n", wine_dbgstr_w(stream_filename) );
- goto cleanup;
- }
- ret = MsiDatabaseOpenViewW( state->database_handle, L"SELECT Data FROM _Streams WHERE Name = ?", &view );
- if (ret != ERROR_SUCCESS)
- {
- ERR( "Failed to open _Streams table.\n" );
- goto cleanup;
- }
- record = MsiCreateRecord( 1 );
- if (record == 0)
- {
- ERR( "Failed to create MSI record.\n" );
- ret = ERROR_OUTOFMEMORY;
- goto cleanup;
- }
- ret = MsiRecordSetStringW( record, 1, stream_filename );
- if (ret != ERROR_SUCCESS)
- {
- ERR( "Failed to add stream filename to MSI record.\n" );
- goto cleanup;
- }
- ret = MsiViewExecute( view, record );
- if (ret != ERROR_SUCCESS)
- {
- ERR( "Failed to query stream from _Streams table.\n" );
- goto cleanup;
- }
- MsiCloseHandle( record );
- record = 0;
- ret = MsiViewFetch( view, &record );
- if (ret != ERROR_SUCCESS)
- {
- ERR( "Failed to query row from _Streams table.\n" );
- goto cleanup;
- }
- read_size = sizeof(buffer);
- while (read_size == sizeof(buffer))
- {
- ret = MsiRecordReadStream( record, 1, buffer, &read_size );
- if (ret != ERROR_SUCCESS)
- {
- ERR( "Failed to read stream from _Streams table.\n" );
- goto cleanup;
- }
- if (!WriteFile( file, buffer, read_size, &write_size, NULL ) || read_size != write_size)
- {
- ret = ERROR_WRITE_FAULT;
- ERR( "Failed to write stream to destination file.\n" );
- goto cleanup;
- }
- }
- cleanup:
- if (record)
- MsiCloseHandle( record );
- if (view)
- MsiViewClose( view );
- if (file != INVALID_HANDLE_VALUE)
- CloseHandle( file );
- return (ret == ERROR_SUCCESS);
- }
- static int extract_streams( struct msidb_state *state )
- {
- struct msidb_listentry *data;
- LIST_FOR_EACH_ENTRY( data, &state->extract_stream_list, struct msidb_listentry, entry )
- {
- if (!extract_stream( state, data->name ))
- return 0; /* failed, do not commit changes */
- }
- return 1;
- }
- static int import_table( struct msidb_state *state, const WCHAR *table_path )
- {
- UINT ret;
- ret = MsiDatabaseImportW( state->database_handle, state->table_folder, table_path );
- if (ret != ERROR_SUCCESS)
- {
- ERR( "Failed to import table '%s', error %d.\n", wine_dbgstr_w(table_path), ret );
- return 0;
- }
- return 1;
- }
- static int import_tables( struct msidb_state *state )
- {
- struct msidb_listentry *data;
- LIST_FOR_EACH_ENTRY( data, &state->table_list, struct msidb_listentry, entry )
- {
- WCHAR *table_name = data->name;
- WCHAR table_path[MAX_PATH];
- WCHAR *ext;
- /* permit specifying tables with wildcards ('Feature*') */
- if (wcsstr( table_name, L"*" ) != NULL)
- {
- WIN32_FIND_DATAW f;
- HANDLE handle;
- WCHAR *path;
- DWORD len;
- len = lstrlenW( state->table_folder ) + 1 + lstrlenW( table_name ) + 1; /* %s/%s\0 */
- path = HeapAlloc( GetProcessHeap(), 0, len * sizeof(WCHAR) );
- if (path == NULL)
- return 0;
- lstrcpyW( path, state->table_folder );
- PathAddBackslashW( path );
- lstrcatW( path, table_name );
- handle = FindFirstFileW( path, &f );
- HeapFree( GetProcessHeap(), 0, path );
- if (handle == INVALID_HANDLE_VALUE)
- return 0;
- do
- {
- if (f.cFileName[0] == '.' && !f.cFileName[1]) continue;
- if (f.cFileName[0] == '.' && f.cFileName[1] == '.' && !f.cFileName[2]) continue;
- if (f.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) continue;
- if ((ext = PathFindExtensionW( f.cFileName )) == NULL) continue;
- if (lstrcmpW( ext, L".idt" ) != 0) continue;
- if (!import_table( state, f.cFileName ))
- {
- FindClose( handle );
- return 0; /* failed, do not commit changes */
- }
- } while (FindNextFileW( handle, &f ));
- FindClose( handle );
- continue;
- }
- /* permit specifying tables by filename (*.idt) */
- if ((ext = PathFindExtensionW( table_name )) == NULL || lstrcmpW( ext, L".idt" ) != 0)
- {
- /* truncate to 8 characters */
- swprintf( table_path, ARRAY_SIZE(table_path), L"%.8s.idt", table_name );
- table_name = table_path;
- }
- if (!import_table( state, table_name ))
- return 0; /* failed, do not commit changes */
- }
- return 1;
- }
- static int export_table( struct msidb_state *state, const WCHAR *table_name )
- {
- const WCHAR *format = (state->short_filenames ? L"%.8s.idt" : L"%s.idt");
- WCHAR table_path[MAX_PATH];
- UINT ret;
- swprintf( table_path, ARRAY_SIZE(table_path), format, table_name );
- ret = MsiDatabaseExportW( state->database_handle, table_name, state->table_folder, table_path );
- if (ret != ERROR_SUCCESS)
- {
- ERR( "Failed to export table '%s', error %d.\n", wine_dbgstr_w(table_name), ret );
- return 0;
- }
- return 1;
- }
- static int export_all_tables( struct msidb_state *state )
- {
- MSIHANDLE view = 0;
- UINT ret;
- ret = MsiDatabaseOpenViewW( state->database_handle, L"SELECT Name FROM _Tables", &view );
- if (ret != ERROR_SUCCESS)
- {
- ERR( "Failed to open _Tables table.\n" );
- goto cleanup;
- }
- ret = MsiViewExecute( view, 0 );
- if (ret != ERROR_SUCCESS)
- {
- ERR( "Failed to query list from _Tables table.\n" );
- goto cleanup;
- }
- while( 1 )
- {
- MSIHANDLE record = 0;
- WCHAR table[256];
- DWORD size;
- ret = MsiViewFetch( view, &record );
- if (ret == ERROR_NO_MORE_ITEMS)
- break;
- if (ret != ERROR_SUCCESS)
- {
- ERR( "Failed to query row from _Tables table.\n" );
- goto cleanup;
- }
- size = ARRAY_SIZE(table);
- ret = MsiRecordGetStringW( record, 1, table, &size );
- if (ret != ERROR_SUCCESS)
- {
- ERR( "Failed to retrieve name string.\n" );
- goto cleanup;
- }
- if (!export_table( state, table ))
- {
- ret = ERROR_FUNCTION_FAILED;
- goto cleanup;
- }
- ret = MsiCloseHandle( record );
- if (ret != ERROR_SUCCESS)
- {
- ERR( "Failed to close record handle.\n" );
- goto cleanup;
- }
- }
- ret = ERROR_SUCCESS;
- /* the _SummaryInformation table is not listed in _Tables */
- if (!export_table( state, L"_SummaryInformation" ))
- {
- ret = ERROR_FUNCTION_FAILED;
- goto cleanup;
- }
- cleanup:
- if (view && MsiViewClose( view ) != ERROR_SUCCESS)
- {
- ERR( "Failed to close _Streams table.\n" );
- return 0;
- }
- return (ret == ERROR_SUCCESS);
- }
- static int export_tables( struct msidb_state *state )
- {
- struct msidb_listentry *data;
- LIST_FOR_EACH_ENTRY( data, &state->table_list, struct msidb_listentry, entry )
- {
- if (lstrcmpW( data->name, L"*" ) == 0)
- {
- if (!export_all_tables( state ))
- return 0; /* failed, do not commit changes */
- continue;
- }
- if (!export_table( state, data->name ))
- return 0; /* failed, do not commit changes */
- }
- return 1;
- }
- int __cdecl wmain( int argc, WCHAR *argv[] )
- {
- struct msidb_state state;
- int i, n = 1;
- int ret = 1;
- memset( &state, 0x0, sizeof(state) );
- list_init( &state.add_stream_list );
- list_init( &state.extract_stream_list );
- list_init( &state.kill_stream_list );
- list_init( &state.table_list );
- /* process and validate all the command line flags */
- for (i = 1; n && i < argc; i += n)
- n = process_argument( &state, i, argc, argv );
- /* process all remaining arguments as table names */
- for (; i < argc; i++)
- list_append( &state.table_list, argv[i] );
- if (!valid_state( &state ))
- goto cleanup;
- /* perform the requested operations */
- if (!open_database( &state ))
- {
- ERR( "Failed to open database '%s'.\n", wine_dbgstr_w(state.database_file) );
- goto cleanup;
- }
- if (state.add_streams && !add_streams( &state ))
- goto cleanup; /* failed, do not commit changes */
- if (state.export_tables && !export_tables( &state ))
- goto cleanup; /* failed, do not commit changes */
- if (state.extract_streams && !extract_streams( &state ))
- goto cleanup; /* failed, do not commit changes */
- if (state.import_tables && !import_tables( &state ))
- goto cleanup; /* failed, do not commit changes */
- if (state.kill_streams && !kill_streams( &state ))
- goto cleanup; /* failed, do not commit changes */
- ret = 0;
- cleanup:
- close_database( &state, ret == 0 );
- list_free( &state.add_stream_list );
- list_free( &state.extract_stream_list );
- list_free( &state.kill_stream_list );
- list_free( &state.table_list );
- return ret;
- }
|