2011-02-01 14:01:42 +01:00
|
|
|
/*
|
|
|
|
* winemsibuilder - tool to build MSI packages
|
|
|
|
*
|
|
|
|
* Copyright 2010 Hans Leidekker for CodeWeavers
|
|
|
|
*
|
|
|
|
* 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
|
|
|
|
#define COBJMACROS
|
|
|
|
|
|
|
|
#include <stdio.h>
|
|
|
|
#include <windows.h>
|
|
|
|
#include <msi.h>
|
|
|
|
#include <msiquery.h>
|
|
|
|
#include <objbase.h>
|
|
|
|
|
|
|
|
#include "wine/debug.h"
|
|
|
|
|
|
|
|
WINE_DEFAULT_DEBUG_CHANNEL(winemsibuilder);
|
|
|
|
|
|
|
|
static UINT open_database( const WCHAR *msifile, MSIHANDLE *handle )
|
|
|
|
{
|
|
|
|
UINT r;
|
|
|
|
MSIHANDLE hdb;
|
|
|
|
|
|
|
|
if (GetFileAttributesW( msifile ) == INVALID_FILE_ATTRIBUTES)
|
|
|
|
{
|
|
|
|
r = MsiOpenDatabaseW( msifile, MSIDBOPEN_CREATE, &hdb );
|
|
|
|
if (r != ERROR_SUCCESS)
|
|
|
|
{
|
|
|
|
WINE_ERR( "failed to create package database %s (%u)\n", wine_dbgstr_w(msifile), r );
|
|
|
|
return r;
|
|
|
|
}
|
|
|
|
r = MsiDatabaseCommit( hdb );
|
|
|
|
if (r != ERROR_SUCCESS)
|
|
|
|
{
|
|
|
|
WINE_ERR( "failed to commit database (%u)\n", r );
|
|
|
|
MsiCloseHandle( hdb );
|
|
|
|
return r;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
r = MsiOpenDatabaseW( msifile, MSIDBOPEN_TRANSACT, &hdb );
|
|
|
|
if (r != ERROR_SUCCESS)
|
|
|
|
{
|
|
|
|
WINE_ERR( "failed to open package database %s (%u)\n", wine_dbgstr_w(msifile), r );
|
|
|
|
return r;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
*handle = hdb;
|
|
|
|
return ERROR_SUCCESS;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int import_tables( const WCHAR *msifile, WCHAR **tables )
|
|
|
|
{
|
|
|
|
UINT r;
|
|
|
|
MSIHANDLE hdb;
|
|
|
|
WCHAR *dir;
|
|
|
|
DWORD len;
|
|
|
|
|
|
|
|
r = open_database( msifile, &hdb );
|
|
|
|
if (r != ERROR_SUCCESS) return 1;
|
|
|
|
|
|
|
|
len = GetCurrentDirectoryW( 0, NULL );
|
|
|
|
if (!(dir = HeapAlloc( GetProcessHeap(), 0, (len + 1) * sizeof(WCHAR) )))
|
|
|
|
{
|
|
|
|
MsiCloseHandle( hdb );
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
GetCurrentDirectoryW( len + 1, dir );
|
|
|
|
|
|
|
|
while (*tables)
|
|
|
|
{
|
|
|
|
r = MsiDatabaseImportW( hdb, dir, *tables );
|
|
|
|
if (r != ERROR_SUCCESS)
|
|
|
|
{
|
|
|
|
WINE_ERR( "failed to import table %s (%u)\n", wine_dbgstr_w(*tables), r );
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
tables++;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (r == ERROR_SUCCESS)
|
|
|
|
{
|
|
|
|
r = MsiDatabaseCommit( hdb );
|
|
|
|
if (r != ERROR_SUCCESS)
|
|
|
|
WINE_ERR( "failed to commit changes (%u)\n", r );
|
|
|
|
}
|
|
|
|
|
|
|
|
HeapFree( GetProcessHeap(), 0, dir );
|
|
|
|
MsiCloseHandle( hdb );
|
|
|
|
return (r != ERROR_SUCCESS);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* taken from dlls/msi/table.c */
|
|
|
|
static int utf2mime( int x )
|
|
|
|
{
|
|
|
|
if (x >= '0' && x <= '9')
|
|
|
|
return x - '0';
|
|
|
|
if (x >= 'A' && x <= 'Z')
|
|
|
|
return x - 'A' + 10;
|
|
|
|
if (x >= 'a' && x <= 'z')
|
|
|
|
return x - 'a' + 10 + 26;
|
|
|
|
if (x == '.')
|
|
|
|
return 10 + 26 + 26;
|
|
|
|
if (x == '_')
|
|
|
|
return 10 + 26 + 26 + 1;
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
#define MAX_STREAM_NAME 0x1f
|
|
|
|
|
|
|
|
static WCHAR *encode_stream( const WCHAR *in )
|
|
|
|
{
|
|
|
|
DWORD c, next, count;
|
|
|
|
WCHAR *out, *p;
|
|
|
|
|
2019-05-02 09:31:02 +02:00
|
|
|
count = lstrlenW( in );
|
2011-02-01 14:01:42 +01:00
|
|
|
if (count > MAX_STREAM_NAME)
|
|
|
|
return NULL;
|
|
|
|
|
|
|
|
count += 2;
|
|
|
|
if (!(out = HeapAlloc( GetProcessHeap(), 0, count * sizeof(WCHAR) ))) return NULL;
|
|
|
|
p = out;
|
|
|
|
while (count--)
|
|
|
|
{
|
|
|
|
c = *in++;
|
|
|
|
if (!c)
|
|
|
|
{
|
|
|
|
*p = c;
|
|
|
|
return out;
|
|
|
|
}
|
|
|
|
if (c < 0x80 && utf2mime( c ) >= 0)
|
|
|
|
{
|
|
|
|
c = utf2mime( c ) + 0x4800;
|
|
|
|
next = *in;
|
|
|
|
if (next && next < 0x80)
|
|
|
|
{
|
|
|
|
next = utf2mime( next );
|
|
|
|
if (next != -1)
|
|
|
|
{
|
|
|
|
next += 0x3ffffc0;
|
|
|
|
c += next << 6;
|
|
|
|
in++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
*p++ = c;
|
|
|
|
}
|
|
|
|
HeapFree( GetProcessHeap(), 0, out );
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int add_stream( const WCHAR *msifile, const WCHAR *stream, const WCHAR *file )
|
|
|
|
{
|
|
|
|
UINT r;
|
|
|
|
HRESULT hr;
|
|
|
|
MSIHANDLE hdb;
|
|
|
|
IStorage *stg;
|
|
|
|
IStream *stm = NULL;
|
|
|
|
HANDLE handle;
|
|
|
|
char buffer[4096];
|
|
|
|
ULARGE_INTEGER size;
|
|
|
|
DWORD low, high, read;
|
|
|
|
WCHAR *encname;
|
|
|
|
int ret = 1;
|
|
|
|
|
|
|
|
/* make sure we have the right type of file */
|
|
|
|
r = open_database( msifile, &hdb );
|
|
|
|
if (r != ERROR_SUCCESS) return 1;
|
|
|
|
MsiCloseHandle( hdb );
|
|
|
|
|
|
|
|
hr = StgOpenStorage( msifile, NULL, STGM_TRANSACTED|STGM_READWRITE|STGM_SHARE_EXCLUSIVE, NULL, 0, &stg );
|
|
|
|
if (hr != S_OK)
|
|
|
|
{
|
|
|
|
WINE_WARN( "failed to open storage %s (0x%08x)\n", wine_dbgstr_w(msifile), hr );
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
encname = encode_stream( stream );
|
|
|
|
if (!encname)
|
|
|
|
{
|
|
|
|
WINE_WARN( "failed to encode stream name %s\n", wine_dbgstr_w(stream) );
|
|
|
|
goto done;
|
|
|
|
}
|
|
|
|
hr = IStorage_CreateStream( stg, encname, STGM_CREATE|STGM_WRITE|STGM_SHARE_EXCLUSIVE, 0, 0, &stm );
|
|
|
|
if (hr != S_OK)
|
|
|
|
{
|
|
|
|
WINE_WARN( "failed to create stream %s (0x%08x)\n", wine_dbgstr_w(encname), hr );
|
|
|
|
goto done;
|
|
|
|
}
|
|
|
|
handle = CreateFileW( file, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL );
|
|
|
|
if (handle == INVALID_HANDLE_VALUE)
|
|
|
|
{
|
|
|
|
WINE_WARN( "failed to open file %s (%u)\n", wine_dbgstr_w(file), GetLastError() );
|
|
|
|
goto done;
|
|
|
|
}
|
|
|
|
low = GetFileSize( handle, &high );
|
|
|
|
if (low == INVALID_FILE_SIZE || high)
|
|
|
|
{
|
|
|
|
WINE_WARN( "file %s too big\n", wine_dbgstr_w(file) );
|
|
|
|
CloseHandle( handle );
|
|
|
|
goto done;
|
|
|
|
}
|
|
|
|
size.QuadPart = low;
|
|
|
|
hr = IStream_SetSize( stm, size );
|
|
|
|
if (hr != S_OK) goto done;
|
|
|
|
|
|
|
|
while (ReadFile( handle, buffer, sizeof(buffer), &read, NULL ) && read)
|
|
|
|
{
|
|
|
|
hr = IStream_Write( stm, buffer, read, NULL );
|
|
|
|
if (hr != S_OK) break;
|
|
|
|
size.QuadPart -= read;
|
|
|
|
}
|
|
|
|
CloseHandle( handle );
|
|
|
|
if (size.QuadPart)
|
|
|
|
{
|
|
|
|
WINE_WARN( "failed to write stream contents\n" );
|
|
|
|
goto done;
|
|
|
|
}
|
|
|
|
IStorage_Commit( stg, 0 );
|
|
|
|
ret = 0;
|
|
|
|
|
|
|
|
done:
|
|
|
|
HeapFree( GetProcessHeap(), 0, encname );
|
|
|
|
if (stm) IStream_Release( stm );
|
|
|
|
IStorage_Release( stg );
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void show_usage( void )
|
|
|
|
{
|
|
|
|
WINE_MESSAGE(
|
|
|
|
"Usage: winemsibuilder [OPTION] [MSIFILE] ...\n"
|
|
|
|
"Options:\n"
|
|
|
|
" -i package.msi table1.idt [table2.idt ...] Import one or more tables into the database.\n"
|
|
|
|
" -a package.msi stream file Add 'stream' to storage with contents of 'file'.\n"
|
|
|
|
"\nExisting tables or streams will be overwritten. If package.msi does not exist a new file\n"
|
|
|
|
"will be created with an empty database.\n"
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
int wmain( int argc, WCHAR *argv[] )
|
|
|
|
{
|
|
|
|
if (argc < 3 || argv[1][0] != '-')
|
|
|
|
{
|
|
|
|
show_usage();
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
switch (argv[1][1])
|
|
|
|
{
|
|
|
|
case 'i':
|
|
|
|
if (argc < 4) break;
|
|
|
|
return import_tables( argv[2], argv + 3 );
|
|
|
|
case 'a':
|
|
|
|
if (argc < 5) break;
|
|
|
|
return add_stream( argv[2], argv[3], argv[4] );
|
|
|
|
default:
|
|
|
|
WINE_WARN( "unknown option\n" );
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
show_usage();
|
|
|
|
return 1;
|
|
|
|
}
|