wine/dlls/msi/action.c
2004-06-28 20:34:35 +00:00

1563 lines
40 KiB
C

/*
* Implementation of the Microsoft Installer (msi.dll)
*
* Copyright 2004 Aric Stewart 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
/*
* pages i need
*
http://msdn.microsoft.com/library/default.asp?url=/library/en-us/msi/setup/installexecutesequence_table.asp
http://msdn.microsoft.com/library/default.asp?url=/library/en-us/msi/setup/standard_actions_reference.asp
*/
#include <stdarg.h>
#include <stdio.h>
#include "windef.h"
#include "winbase.h"
#include "winerror.h"
#include "winreg.h"
#include "wine/debug.h"
#include "msi.h"
#include "msiquery.h"
#include "objbase.h"
#include "objidl.h"
#include "msipriv.h"
#include "winnls.h"
#include "winuser.h"
#include "shlobj.h"
#include "wine/unicode.h"
#define CUSTOM_ACTION_TYPE_MASK 0x3F
/*
* These are hacks to get around the inability to write
* to the database and the inability to select on string values
* once those values are done then it will be possible to do
* all this inside the database.
*/
#define MAX_PROP 1024
typedef struct {
WCHAR *prop_name;
WCHAR *prop_value;
} internal_property;
static internal_property PropTableHack[MAX_PROP];
static INT PropCount = 0;
WINE_DEFAULT_DEBUG_CHANNEL(msi);
/*
* Prototypes
*/
UINT ACTION_PerformAction(MSIHANDLE hPackage, const WCHAR *action);
static UINT ACTION_CreateFolders(MSIHANDLE hPackage);
static UINT ACTION_CostFinalize(MSIHANDLE hPackage);
static UINT ACTION_InstallFiles(MSIHANDLE hPackage);
static UINT ACTION_DuplicateFiles(MSIHANDLE hPackage);
static UINT ACTION_WriteRegistryValues(MSIHANDLE hPackage);
static UINT ACTION_CustomAction(MSIHANDLE hPackage,const WCHAR *action);
static UINT set_property(MSIHANDLE hPackage, const WCHAR* prop,
const WCHAR* value);
UINT get_property(MSIHANDLE hPackage, const WCHAR* prop, WCHAR* value);
static VOID blitz_propertytable();
static VOID set_installer_properties(MSIHANDLE hPackage,
const WCHAR* szPackagePath,
const WCHAR* szCommandLine);
static DWORD deformat_string(MSIHANDLE hPackage, WCHAR* ptr,WCHAR** data);
/*
* consts and values used
*/
static const WCHAR cszSourceDir[] = {'S','o','u','r','c','e','D','i','r',0};
static const WCHAR cszRootDrive[] = {'R','O','O','T','D','R','I','V','E',0};
static const WCHAR cszTargetDir[] = {'T','A','R','G','E','T','D','I','R',0};
static const WCHAR cszlsb[]={'[',0};
static const WCHAR cszrsb[]={']',0};
static const WCHAR cszbs[]={'\\',0};
/********************************************************
* helper functions to get around current HACKS and such
********************************************************/
static VOID blitz_propertytable()
{
if (PropCount > 0)
{
int i;
TRACE("Clearing %i properties\n",PropCount);
for (i = 0; i < PropCount; i++)
{
HeapFree(GetProcessHeap(), 0, PropTableHack[i].prop_name);
HeapFree(GetProcessHeap(), 0, PropTableHack[i].prop_value);
}
PropCount = 0;
}
}
UINT get_property(MSIHANDLE hPackage, const WCHAR* prop, WCHAR* value)
{
UINT rc = 1;
DWORD sz=0x100;
int index = 0;
WCHAR* pName = PropTableHack[0].prop_name;
TRACE("Looking for property %s\n",debugstr_w(prop));
/* prop table hacks take presidence */
while (pName && strcmpW(pName,prop) && index < PropCount)
{
index ++;
pName = PropTableHack[index].prop_name;
}
if (pName)
{
strcpyW(value,PropTableHack[index].prop_value);
TRACE(" found value %s\n",debugstr_w(value));
return 0;
}
rc = MsiGetPropertyW(hPackage,prop,value,&sz);
if (rc == ERROR_SUCCESS)
TRACE(" found value %s\n",debugstr_w(value));
else
TRACE(" value not found\n");
return rc;
}
static UINT set_property(MSIHANDLE hPackage, const WCHAR* prop,
const WCHAR* value)
{
/* prop table hacks take precedence */
UINT rc;
int index = 0;
WCHAR* pName = PropTableHack[0].prop_name;
TRACE("Setting property %s to %s\n",debugstr_w(prop),debugstr_w(value));
while (pName && strcmpW(pName,prop) && index < PropCount)
{
index ++;
pName = PropTableHack[index].prop_name;
}
if (pName)
{
strcpyW(PropTableHack[index].prop_value,value);
return 0;
}
else
{
if (index > MAX_PROP)
{
ERR("EXCEEDING MAX PROP!!!!\n");
return ERROR_FUNCTION_FAILED;
}
PropTableHack[index].prop_name = HeapAlloc(GetProcessHeap(),0,1024);
PropTableHack[index].prop_value= HeapAlloc(GetProcessHeap(),0,1024);
strcpyW(PropTableHack[index].prop_name,prop);
strcpyW(PropTableHack[index].prop_value,value);
PropCount++;
return 0;
}
/* currently unreachable */
rc = MsiSetPropertyW(hPackage,prop,value);
return rc;
}
/*
* There are a whole slew of these we need to set
*
*
http://msdn.microsoft.com/library/default.asp?url=/library/en-us/msi/setup/properties.asp
*/
static VOID set_installer_properties(MSIHANDLE hPackage,
const WCHAR* szPackagePath,
const WCHAR* szCommandLine)
{
WCHAR pth[MAX_PATH];
WCHAR *p;
WCHAR check[MAX_PATH];
static const WCHAR OriginalDatabase[] =
{'O','r','i','g','i','n','a','l','D','a','t','a','b','a','s','e',0};
static const WCHAR c_col[] =
{'C',':','\\',0};
static const WCHAR CFF[] =
{'C','o','m','m','o','n','F','i','l','e','s','F','o','l','d','e','r',0};
static const WCHAR PFF[] =
{'P','r','o','g','r','a','m','F','i','l','e','s','F','o','l','d','e','r',0};
static const WCHAR CADF[] =
{'C','o','m','m','o','n','A','p','p','D','a','t','a','F','o','l','d','e','r',0};
static const WCHAR ATF[] =
{'A','d','m','i','n','T','o','o','l','s','F','o','l','d','e','r',0};
static const WCHAR ADF[] =
{'A','p','p','D','a','t','a','F','o','l','d','e','r',0};
static const WCHAR SF[] =
{'S','y','s','t','e','m','F','o','l','d','e','r',0};
static const WCHAR LADF[] =
{'L','o','c','a','l','A','p','p','D','a','t','a','F','o','l','d','e','r',0};
static const WCHAR MPF[] =
{'M','y','P','i','c','t','u','r','e','s','F','o','l','d','e','r',0};
static const WCHAR PF[] =
{'P','e','r','s','o','n','a','l','F','o','l','d','e','r',0};
static const WCHAR WF[] =
{'W','i','n','d','o','w','s','F','o','l','d','e','r',0};
/* Not yet set ... but needed by iTunes
*
static const WCHAR DF[] =
{'D','e','s','k','t','o','p','F','o','l','d','e','r',0};
static const WCHAR FF[] =
{'F','a','v','o','r','i','t','e','s','F','o','l','d','e','r',0};
static const WCHAR FoF[] =
{'F','o','n','t','s','F','o','l','d','e','r',0};
PrimaryVolumePath
ProgramFiles64Folder
ProgramMenuFolder
SendToFolder
StartMenuFolder
StartupFolder
System16Folder
System64Folder
TempFolder
TemplateFolder
*/
/* asked for by iTunes ... but are they installer set?
*
* GlobalAssemblyCache
*/
set_property(hPackage, OriginalDatabase, szPackagePath);
set_property(hPackage, cszRootDrive, c_col);
strcpyW(pth,szPackagePath);
p = strrchrW(pth,'\\');
if (p)
{
p++;
*p=0;
}
if (get_property(hPackage,cszSourceDir,check) != ERROR_SUCCESS )
set_property(hPackage, cszSourceDir, pth);
SHGetFolderPathW(NULL,CSIDL_PROGRAM_FILES_COMMON,NULL,0,pth);
strcatW(pth,cszbs);
set_property(hPackage, CFF, pth);
SHGetFolderPathW(NULL,CSIDL_PROGRAM_FILES,NULL,0,pth);
strcatW(pth,cszbs);
set_property(hPackage, PFF, pth);
SHGetFolderPathW(NULL,CSIDL_COMMON_APPDATA,NULL,0,pth);
strcatW(pth,cszbs);
set_property(hPackage, CADF, pth);
SHGetFolderPathW(NULL,CSIDL_ADMINTOOLS,NULL,0,pth);
strcatW(pth,cszbs);
set_property(hPackage, ATF, pth);
SHGetFolderPathW(NULL,CSIDL_APPDATA,NULL,0,pth);
strcatW(pth,cszbs);
set_property(hPackage, ADF, pth);
SHGetFolderPathW(NULL,CSIDL_SYSTEM,NULL,0,pth);
strcatW(pth,cszbs);
set_property(hPackage, SF, pth);
SHGetFolderPathW(NULL,CSIDL_LOCAL_APPDATA,NULL,0,pth);
strcatW(pth,cszbs);
set_property(hPackage, LADF, pth);
SHGetFolderPathW(NULL,CSIDL_MYPICTURES,NULL,0,pth);
strcatW(pth,cszbs);
set_property(hPackage, MPF, pth);
SHGetFolderPathW(NULL,CSIDL_PERSONAL,NULL,0,pth);
strcatW(pth,cszbs);
set_property(hPackage, PF, pth);
SHGetFolderPathW(NULL,CSIDL_WINDOWS,NULL,0,pth);
strcatW(pth,cszbs);
set_property(hPackage, WF, pth);
}
/****************************************************
* TOP level entry points
*****************************************************/
UINT ACTION_DoTopLevelINSTALL(MSIHANDLE hPackage, LPCWSTR szPackagePath,
LPCWSTR szCommandLine)
{
MSIHANDLE view;
UINT rc;
static const CHAR *ExecSeqQuery =
"select * from InstallExecuteSequence where Sequence > 0 order by Sequence";
FIXME("****We do not do any of the UI level stuff yet***\n");
/* reset our properties */
/* HACK ! */
memset (&PropTableHack, sizeof(PropTableHack),0);
set_installer_properties(hPackage, szPackagePath, szCommandLine);
rc = MsiDatabaseOpenViewA(hPackage, ExecSeqQuery, &view);
if (rc == ERROR_SUCCESS)
{
rc = MsiViewExecute(view, 0);
if (rc != ERROR_SUCCESS)
{
MsiViewClose(view);
MsiCloseHandle(view);
goto end;
}
TRACE("Running the actions \n");
while (1)
{
WCHAR buffer[0x100];
DWORD sz = 0x100;
MSIHANDLE row = 0;
rc = MsiViewFetch(view,&row);
if (rc != ERROR_SUCCESS)
{
rc = ERROR_SUCCESS;
break;
}
/* check conditions */
if (!MsiRecordIsNull(row,2))
{
sz=0x100;
rc = MsiRecordGetStringW(row,2,buffer,&sz);
if (rc != ERROR_SUCCESS)
{
MsiCloseHandle(row);
break;
}
/* this is a hack to skip errors in the condition code */
if (MsiEvaluateConditionW(hPackage, buffer) ==
MSICONDITION_FALSE)
{
MsiCloseHandle(row);
continue;
}
}
sz=0x100;
rc = MsiRecordGetStringW(row,1,buffer,&sz);
if (rc != ERROR_SUCCESS)
{
ERR("Error is %x\n",rc);
MsiCloseHandle(row);
break;
}
rc = ACTION_PerformAction(hPackage,buffer);
if (rc != ERROR_SUCCESS)
{
ERR("Execution halted due to error (%i)\n",rc);
MsiCloseHandle(row);
break;
}
MsiCloseHandle(row);
}
MsiViewClose(view);
MsiCloseHandle(view);
}
end:
blitz_propertytable();
return rc;
}
/********************************************************
* ACTION helper functions and functions that perform the actions
*******************************************************/
/*
* Alot of actions are really important even if they don't do anything
* explicit.. Lots of properties are set at the beginning of the installation
* CostFinalize does a bunch of work to translated the directorys and such
*
* But until I get write access to the database that is hard. so I am going to
* hack it to see if I can get something to run.
*/
UINT ACTION_PerformAction(MSIHANDLE hPackage, const WCHAR *action)
{
const static WCHAR szCreateFolders[] =
{'C','r','e','a','t','e','F','o','l','d','e','r','s',0};
const static WCHAR szCostFinalize[] =
{'C','o','s','t','F','i','n','a','l','i','z','e',0};
const static WCHAR szInstallFiles[] =
{'I','n','s','t','a','l','l','F','i','l','e','s',0};
const static WCHAR szDuplicateFiles[] =
{'D','u','p','l','i','c','a','t','e','F','i','l','e','s',0};
const static WCHAR szWriteRegistryValues[] =
{'W','r','i','t','e','R','e','g','i','s','t','r','y','V','a','l','u','e','s',0};
TRACE("Performing action (%s)\n",debugstr_w(action));
if (strcmpW(action,szCreateFolders)==0)
return ACTION_CreateFolders(hPackage);
if (strcmpW(action,szCostFinalize)==0)
return ACTION_CostFinalize(hPackage);
if (strcmpW(action,szInstallFiles)==0)
return ACTION_InstallFiles(hPackage);
if (strcmpW(action,szDuplicateFiles)==0)
return ACTION_DuplicateFiles(hPackage);
if (strcmpW(action,szWriteRegistryValues)==0)
return ACTION_WriteRegistryValues(hPackage);
/*
.
.
.
*/
if (ACTION_CustomAction(hPackage,action) != ERROR_SUCCESS)
ERR("UNHANDLED MSI ACTION %s\n",debugstr_w(action));
return ERROR_SUCCESS;
}
static UINT ACTION_CustomAction(MSIHANDLE hPackage,const WCHAR *action)
{
UINT rc;
MSIHANDLE view;
MSIHANDLE row = 0;
WCHAR ExecSeqQuery[1024] =
{'s','e','l','e','c','t',' ','*',' ','f','r','o','m',' ','C','u','s','t','o'
,'m','A','c','t','i','o','n',' ','w','h','e','r','e',' ','`','A','c','t','i'
,'o','n','`',' ','=',' ','`',0};
static const WCHAR end[]={'`',0};
UINT type;
DWORD sz;
WCHAR source[0x100];
WCHAR target[0x200];
WCHAR *deformated=NULL;
strcatW(ExecSeqQuery,action);
strcatW(ExecSeqQuery,end);
rc = MsiDatabaseOpenViewW(hPackage, ExecSeqQuery, &view);
if (rc != ERROR_SUCCESS)
return rc;
rc = MsiViewExecute(view, 0);
if (rc != ERROR_SUCCESS)
{
MsiViewClose(view);
MsiCloseHandle(view);
return rc;
}
rc = MsiViewFetch(view,&row);
if (rc != ERROR_SUCCESS)
{
MsiViewClose(view);
MsiCloseHandle(view);
return rc;
}
TRACE("Handling custom action %s\n",debugstr_w(action));
type = MsiRecordGetInteger(row,2);
switch (type & CUSTOM_ACTION_TYPE_MASK)
{
case 35: /* Directory set with formatted text. */
case 51: /* Property set with formatted text. */
sz=0x100;
MsiRecordGetStringW(row,3,source,&sz);
sz=0x200;
MsiRecordGetStringW(row,4,target,&sz);
deformat_string(hPackage,target,&deformated);
set_property(hPackage,source,deformated);
HeapFree(GetProcessHeap(),0,deformated);
break;
default:
ERR("UNHANDLED ACTION TYPE %i\n",type & CUSTOM_ACTION_TYPE_MASK);
}
MsiCloseHandle(row);
MsiViewClose(view);
MsiCloseHandle(view);
return rc;
}
/***********************************************************************
* create_full_pathW
*
* Recursively create all directories in the path.
*
* shamelessly stolen from setupapi/queue.c
*/
static BOOL create_full_pathW(const WCHAR *path)
{
BOOL ret = TRUE;
int len;
WCHAR *new_path;
new_path = HeapAlloc(GetProcessHeap(), 0, (strlenW(path) + 1) *
sizeof(WCHAR));
strcpyW(new_path, path);
while((len = strlenW(new_path)) && new_path[len - 1] == '\\')
new_path[len - 1] = 0;
while(!CreateDirectoryW(new_path, NULL))
{
WCHAR *slash;
DWORD last_error = GetLastError();
if(last_error == ERROR_ALREADY_EXISTS)
break;
if(last_error != ERROR_PATH_NOT_FOUND)
{
ret = FALSE;
break;
}
if(!(slash = strrchrW(new_path, '\\')))
{
ret = FALSE;
break;
}
len = slash - new_path;
new_path[len] = 0;
if(!create_full_pathW(new_path))
{
ret = FALSE;
break;
}
new_path[len] = '\\';
}
HeapFree(GetProcessHeap(), 0, new_path);
return ret;
}
/*
* Also we cannot enable/disable components either, so for now I am just going
* to do all the directorys for all the components.
*/
static UINT ACTION_CreateFolders(MSIHANDLE hPackage)
{
static const CHAR *ExecSeqQuery = "select * from CreateFolder";
UINT rc;
MSIHANDLE view;
rc = MsiDatabaseOpenViewA(hPackage, ExecSeqQuery, &view);
if (rc != ERROR_SUCCESS)
return rc;
rc = MsiViewExecute(view, 0);
if (rc != ERROR_SUCCESS)
{
MsiViewClose(view);
MsiCloseHandle(view);
return rc;
}
while (1)
{
WCHAR dir[0x100];
WCHAR full_path[MAX_PATH];
DWORD sz;
MSIHANDLE row = 0;
rc = MsiViewFetch(view,&row);
if (rc != ERROR_SUCCESS)
{
rc = ERROR_SUCCESS;
break;
}
sz=0x100;
rc = MsiRecordGetStringW(row,1,dir,&sz);
if (rc!= ERROR_SUCCESS)
{
ERR("Unable to get folder id \n");
MsiCloseHandle(row);
continue;
}
rc = get_property(hPackage, dir,full_path);
if (rc != ERROR_SUCCESS)
{
ERR("Unable to resolve folder id %s\n",debugstr_w(dir));
MsiCloseHandle(row);
continue;
}
TRACE("Folder is %s\n",debugstr_w(full_path));
create_full_pathW(full_path);
MsiCloseHandle(row);
}
MsiViewClose(view);
MsiCloseHandle(view);
return rc;
}
/*
* Workhorse function for creating the directories
* during Costing
*/
static UINT resolve_directory(MSIHANDLE hPackage, const WCHAR* dir,
WCHAR* path, BOOL source)
{
static const WCHAR cszsrc[]={'_','S','o','u','r','c','e',0};
static const WCHAR cszsrcroot[]=
{'[','S','o','u','r','c','e','D','i','r',']',0};
WCHAR Query[1024] =
{'s','e','l','e','c','t',' ','*',' ','f','r','o','m',' ','D','i','r','e','c',
't','o','r','y',' ','w','h','e','r','e',' ','`','D','i','r','e','c','t',
'o','r','y','`',' ','=',' ','`',0};
static const WCHAR end[]={'`',0};
UINT rc;
MSIHANDLE view;
WCHAR targetbuffer[0x100];
WCHAR *srcdir = NULL;
WCHAR *targetdir = NULL;
WCHAR buffer[0x100];
WCHAR parent[0x100];
WCHAR parent_path[MAX_PATH];
DWORD sz=0x100;
MSIHANDLE row = 0;
WCHAR full_path[MAX_PATH];
WCHAR name_source[0x100];
if (get_property(hPackage,dir,path)==ERROR_SUCCESS)
return ERROR_SUCCESS;
TRACE("Working to resolve %s\n",debugstr_w(dir));
/* special case... root drive */
if (strcmpW(dir,cszTargetDir)==0)
{
if (!source)
{
if(!get_property(hPackage,cszRootDrive,buffer))
{
set_property(hPackage,cszTargetDir,buffer);
strcpyW(path,buffer);
}
else
{
ERR("No RootDrive property defined disaster!\n");
MsiCloseHandle(row);
MsiViewClose(view);
MsiCloseHandle(view);
return ERROR_FUNCTION_FAILED;
}
}
else
strcpyW(path,cszsrcroot);
return ERROR_SUCCESS;
}
strcatW(Query,dir);
strcatW(Query,end);
rc = MsiDatabaseOpenViewW(hPackage, Query, &view);
if (rc != ERROR_SUCCESS)
return rc;
rc = MsiViewExecute(view, 0);
if (rc != ERROR_SUCCESS)
{
MsiViewClose(view);
MsiCloseHandle(view);
return rc;
}
rc = MsiViewFetch(view,&row);
if (rc != ERROR_SUCCESS)
{
MsiViewClose(view);
MsiCloseHandle(view);
return rc;
}
sz=0x100;
MsiRecordGetStringW(row,3,targetbuffer,&sz);
targetdir=targetbuffer;
/* split src and target dir */
if (strchrW(targetdir,':'))
{
srcdir=strchrW(targetdir,':');
*srcdir=0;
srcdir ++;
}
else
srcdir=NULL;
/* for now only pick long filename versions */
if (strchrW(targetdir,'|'))
{
targetdir = strchrW(targetdir,'|');
*targetdir = 0;
targetdir ++;
}
if (srcdir && strchrW(srcdir,'|'))
{
srcdir= strchrW(srcdir,'|');
*srcdir= 0;
srcdir ++;
}
/* now check for root dirs */
if (targetdir[0] == '.' && targetdir[1] == 0)
targetdir = NULL;
if (srcdir && srcdir[0] == '.' && srcdir[1] == 0)
srcdir = NULL;
if (MsiRecordIsNull(row,2))
parent[0]=0;
else
{
sz=0x100;
MsiRecordGetStringW(row,2,parent,&sz);
}
if (parent[0])
{
resolve_directory(hPackage,parent,parent_path,FALSE);
strcpyW(full_path,parent_path);
if (targetdir)
{
strcatW(full_path,targetdir);
strcatW(full_path,cszbs);
}
set_property(hPackage,dir,full_path);
if (!source)
strcpyW(path,full_path);
resolve_directory(hPackage,parent,parent_path,TRUE);
strcpyW(full_path,parent_path);
if (srcdir)
{
strcatW(full_path,srcdir);
strcatW(full_path,cszbs);
}
else if (targetdir)
{
strcatW(full_path,targetdir);
strcatW(full_path,cszbs);
}
strcpyW(name_source,dir);
strcatW(name_source,cszsrc);
set_property(hPackage,name_source,full_path);
if (source)
strcpyW(path,full_path);
}
MsiCloseHandle(row);
MsiViewClose(view);
MsiCloseHandle(view);
return rc;
}
/*
* Alot is done in this function aside from just the costing.
* The costing needs to be implemented at some point but for now i am going
* to focus on the directory building
*
* Note about directory names: I know that directory names get processed into
* properties. I am still very unclear where the name_source
* part is used but I am preserving it just as a precaution
*/
static UINT ACTION_CostFinalize(MSIHANDLE hPackage)
{
static const CHAR *ExecSeqQuery = "select * from Directory";
UINT rc;
MSIHANDLE view;
TRACE("Building Directory properties\n");
rc = MsiDatabaseOpenViewA(hPackage, ExecSeqQuery, &view);
if (rc != ERROR_SUCCESS)
return rc;
rc = MsiViewExecute(view, 0);
if (rc != ERROR_SUCCESS)
{
MsiViewClose(view);
MsiCloseHandle(view);
return rc;
}
while (1)
{
WCHAR name[0x100];
WCHAR path[MAX_PATH];
MSIHANDLE row = 0;
DWORD sz;
rc = MsiViewFetch(view,&row);
if (rc != ERROR_SUCCESS)
{
rc = ERROR_SUCCESS;
break;
}
sz=0x100;
MsiRecordGetStringW(row,1,name,&sz);
/* This helper function now does ALL the work */
TRACE("Dir %s ...\n",debugstr_w(name));
resolve_directory(hPackage,name,path,FALSE);
TRACE("resolves to %s\n",debugstr_w(path));
MsiCloseHandle(row);
}
MsiViewClose(view);
MsiCloseHandle(view);
return ERROR_SUCCESS;
}
/*
* This is a helper function for handling embedded cabinet media
*/
static UINT writeout_cabinet_stream(MSIHANDLE hPackage, WCHAR* stream_name,
WCHAR* source)
{
UINT rc;
USHORT* data;
UINT size;
DWORD write;
HANDLE the_file;
rc = read_raw_stream_data(hPackage,stream_name,&data,&size);
if (rc != ERROR_SUCCESS)
return rc;
if (get_property(hPackage, cszSourceDir, source))
{
ERR("No Source dir defined \n");
rc = ERROR_FUNCTION_FAILED;
goto end;
}
strcatW(source,stream_name);
the_file = CreateFileW(source, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS,
FILE_ATTRIBUTE_NORMAL, NULL);
if (the_file == INVALID_HANDLE_VALUE)
{
rc = ERROR_FUNCTION_FAILED;
goto end;
}
WriteFile(the_file,data,size,&write,NULL);
CloseHandle(the_file);
TRACE("wrote %li bytes to %s\n",write,debugstr_w(source));
end:
HeapFree(GetProcessHeap(),0,data);
return rc;
}
inline static char *strdupWtoA( const WCHAR *str )
{
char *ret = NULL;
if (str)
{
DWORD len = WideCharToMultiByte( CP_ACP, 0, str, -1, NULL, 0, NULL, NULL
);
if ((ret = HeapAlloc( GetProcessHeap(), 0, len )))
WideCharToMultiByte( CP_ACP, 0, str, -1, ret, len, NULL, NULL );
}
return ret;
}
/***********************************************************************
* extract_cabinet_file
*
* Extract a file from a .cab file.
*/
static BOOL extract_cabinet_file( const WCHAR *cabinet, const WCHAR *root)
{
static const WCHAR extW[] = {'.','c','a','b',0};
/* from cabinet.h */
typedef struct {
long result1; /* 0x000 */
long unknown1[3]; /* 0x004 */
void* filelist; /* 0x010 */
long filecount; /* 0x014 */
long unknown2; /* 0x018 */
char directory[0x104]; /* 0x01c */
char lastfile[0x20c]; /* 0x120 */
} EXTRACTdest;
extern HRESULT Extract(EXTRACTdest*, LPCSTR);
char *cab_path, *src_path;
int len = strlenW( cabinet );
EXTRACTdest exd;
/* make sure the cabinet file has a .cab extension */
if (len <= 4 || strcmpiW( cabinet + len - 4, extW )) return FALSE;
if (!(cab_path = strdupWtoA( cabinet ))) return FALSE;
if (!(src_path = strdupWtoA( root ))) return FALSE;
memset(&exd,0,sizeof(exd));
strcpy(exd.directory,src_path);
Extract(&exd,cab_path);
HeapFree( GetProcessHeap(), 0, cab_path );
HeapFree( GetProcessHeap(), 0, src_path );
return TRUE;
}
static UINT ready_media_for_file(MSIHANDLE hPackage, UINT sequence,
WCHAR* path)
{
UINT rc;
MSIHANDLE view;
MSIHANDLE row = 0;
WCHAR source[MAX_PATH];
static const CHAR *ExecSeqQuery =
"select * from Media where LastSequence > %i order by LastSequence";
CHAR Query[1024];
WCHAR cab[0x100];
DWORD sz=0x100;
INT seq;
static INT last_sequence = 0;
if (sequence <= last_sequence)
{
TRACE("Media already ready (%i, %i)\n",sequence,last_sequence);
return ERROR_SUCCESS;
}
sprintf(Query,ExecSeqQuery,sequence);
rc = MsiDatabaseOpenViewA(hPackage, Query, &view);
if (rc != ERROR_SUCCESS)
return rc;
rc = MsiViewExecute(view, 0);
if (rc != ERROR_SUCCESS)
{
MsiViewClose(view);
MsiCloseHandle(view);
return rc;
}
rc = MsiViewFetch(view,&row);
if (rc != ERROR_SUCCESS)
{
MsiViewClose(view);
MsiCloseHandle(view);
return rc;
}
seq = MsiRecordGetInteger(row,2);
last_sequence = seq;
if (!MsiRecordIsNull(row,4))
{
sz=0x100;
MsiRecordGetStringW(row,4,cab,&sz);
/* the stream does not contain the # character */
if (cab[0]=='#')
{
writeout_cabinet_stream(hPackage,&cab[1],source);
strcpyW(path,source);
*(strrchrW(path,'\\')+1)=0;
}
else
{
if (get_property(hPackage, cszSourceDir, source))
{
ERR("No Source dir defined \n");
rc = ERROR_FUNCTION_FAILED;
}
else
{
strcpyW(path,source);
strcatW(source,cab);
}
}
rc = !extract_cabinet_file(source,path);
}
MsiCloseHandle(row);
MsiViewClose(view);
MsiCloseHandle(view);
return rc;
}
static void reduce_to_longfilename(WCHAR* filename)
{
if (strchrW(filename,'|'))
{
WCHAR newname[MAX_PATH];
strcpyW(newname,strchrW(filename,'|')+1);
strcpyW(filename,newname);
}
}
static UINT get_directory_for_component(MSIHANDLE hPackage,
const WCHAR* component, WCHAR* install_path)
{
UINT rc;
MSIHANDLE view;
MSIHANDLE row = 0;
WCHAR ExecSeqQuery[1023] =
{'s','e','l','e','c','t',' ','*',' ','f','r','o','m',' ','C','o','m'
,'p','o','n','e','n','t',' ','w','h','e','r','e',' ','C','o','m'
,'p','o','n','e','n','t',' ','=',' ','`',0};
static const WCHAR end[]={'`',0};
WCHAR dir[0x100];
DWORD sz=0x100;
strcatW(ExecSeqQuery,component);
strcatW(ExecSeqQuery,end);
rc = MsiDatabaseOpenViewW(hPackage, ExecSeqQuery, &view);
if (rc != ERROR_SUCCESS)
return rc;
rc = MsiViewExecute(view, 0);
if (rc != ERROR_SUCCESS)
{
MsiViewClose(view);
MsiCloseHandle(view);
return rc;
}
rc = MsiViewFetch(view,&row);
if (rc != ERROR_SUCCESS)
{
MsiViewClose(view);
MsiCloseHandle(view);
MsiCloseHandle(row);
return rc;
}
sz=0x100;
MsiRecordGetStringW(row,3,dir,&sz);
rc = get_property(hPackage, dir, install_path);
MsiCloseHandle(row);
MsiViewClose(view);
MsiCloseHandle(view);
return rc;
}
static UINT ACTION_InstallFiles(MSIHANDLE hPackage)
{
UINT rc;
MSIHANDLE view;
MSIHANDLE row = 0;
static const CHAR *ExecSeqQuery =
"select * from File order by Sequence";
/* REALLY what we want to do is go through all the enabled
* features and check all the components of that feature and
* make sure that component is not already install and blah
* blah blah... I will do it that way some day.. really
* but for sheer gratification I am going to just brute force
* install all the files
*/
rc = MsiDatabaseOpenViewA(hPackage, ExecSeqQuery, &view);
if (rc != ERROR_SUCCESS)
return rc;
rc = MsiViewExecute(view, 0);
if (rc != ERROR_SUCCESS)
{
MsiViewClose(view);
MsiCloseHandle(view);
return rc;
}
while (1)
{
INT seq = 0;
WCHAR component[0x100];
WCHAR install_path[MAX_PATH];
WCHAR path_to_source[MAX_PATH];
WCHAR src_path[MAX_PATH];
WCHAR filename[0x100];
WCHAR sourcename[0x100];
DWORD sz=0x100;
rc = MsiViewFetch(view,&row);
if (rc != ERROR_SUCCESS)
{
rc = ERROR_SUCCESS;
break;
}
seq = MsiRecordGetInteger(row,8);
rc = ready_media_for_file(hPackage,seq,path_to_source);
if (rc != ERROR_SUCCESS)
{
ERR("Unable to ready media\n");
MsiCloseHandle(row);
break;
}
sz=0x100;
rc = MsiRecordGetStringW(row,2,component,&sz);
if (rc != ERROR_SUCCESS)
{
ERR("Unable to read component\n");
MsiCloseHandle(row);
break;
}
rc = get_directory_for_component(hPackage,component,install_path);
if (rc != ERROR_SUCCESS)
{
ERR("Unable to get directory\n");
MsiCloseHandle(row);
break;
}
sz=0x100;
rc = MsiRecordGetStringW(row,1,sourcename,&sz);
if (rc != ERROR_SUCCESS)
{
ERR("Unable to get sourcename\n");
MsiCloseHandle(row);
break;
}
strcpyW(src_path,path_to_source);
strcatW(src_path,sourcename);
sz=0x100;
rc = MsiRecordGetStringW(row,3,filename,&sz);
if (rc != ERROR_SUCCESS)
{
ERR("Unable to get filename\n");
MsiCloseHandle(row);
break;
}
reduce_to_longfilename(filename);
strcatW(install_path,filename);
TRACE("Installing file %s to %s\n",debugstr_w(src_path),
debugstr_w(install_path));
rc = !MoveFileW(src_path,install_path);
if (rc)
{
ERR("Unable to move file\n");
}
/* for future use lets keep track of this file and where it went */
set_property(hPackage,sourcename,install_path);
MsiCloseHandle(row);
}
MsiViewClose(view);
MsiCloseHandle(view);
return rc;
}
static UINT ACTION_DuplicateFiles(MSIHANDLE hPackage)
{
UINT rc;
MSIHANDLE view;
MSIHANDLE row = 0;
static const CHAR *ExecSeqQuery = "select * from DuplicateFile";
/*
* Yes we should only do this for componenets that are installed
* but again I need to do that went I track components.
*/
rc = MsiDatabaseOpenViewA(hPackage, ExecSeqQuery, &view);
if (rc != ERROR_SUCCESS)
return rc;
rc = MsiViewExecute(view, 0);
if (rc != ERROR_SUCCESS)
{
MsiViewClose(view);
MsiCloseHandle(view);
return rc;
}
while (1)
{
WCHAR file_key[0x100];
WCHAR file_source[MAX_PATH];
WCHAR dest_name[0x100];
WCHAR dest_path[MAX_PATH];
DWORD sz=0x100;
rc = MsiViewFetch(view,&row);
if (rc != ERROR_SUCCESS)
{
rc = ERROR_SUCCESS;
break;
}
sz=0x100;
rc = MsiRecordGetStringW(row,3,file_key,&sz);
if (rc != ERROR_SUCCESS)
{
ERR("Unable to get file key\n");
MsiCloseHandle(row);
break;
}
rc = get_property(hPackage,file_key,file_source);
if (rc != ERROR_SUCCESS)
{
ERR("Original file unknown %s\n",debugstr_w(file_key));
MsiCloseHandle(row);
break;
}
if (MsiRecordIsNull(row,4))
strcpyW(dest_name,strrchrW(file_source,'\\')+1);
else
{
sz=0x100;
MsiRecordGetStringW(row,4,dest_name,&sz);
reduce_to_longfilename(dest_name);
}
if (MsiRecordIsNull(row,5))
{
strcpyW(dest_path,file_source);
*strrchrW(dest_path,'\\')=0;
}
else
{
WCHAR destkey[0x100];
sz=0x100;
MsiRecordGetStringW(row,5,destkey,&sz);
rc = get_property(hPackage, destkey, dest_path);
if (rc != ERROR_SUCCESS)
{
ERR("Unable to get destination folder\n");
MsiCloseHandle(row);
break;
}
}
strcatW(dest_path,dest_name);
TRACE("Duplicating file %s to %s\n",debugstr_w(file_source),
debugstr_w(dest_path));
if (strcmpW(file_source,dest_path))
rc = !CopyFileW(file_source,dest_path,TRUE);
else
rc = ERROR_SUCCESS;
if (rc != ERROR_SUCCESS)
ERR("Failed to copy file\n");
MsiCloseHandle(row);
}
MsiViewClose(view);
MsiCloseHandle(view);
return rc;
}
static LPSTR parse_value(MSIHANDLE hPackage, WCHAR *value, DWORD *type,
DWORD *size)
{
LPSTR data = NULL;
if (value[0]=='#' && value[1]!='#')
{
ERR("UNHANDLED VALUE TYPE\n");
}
else
{
WCHAR *ptr;
if (value[0]=='#')
ptr = &value[1];
else
ptr=value;
*type=REG_SZ;
*size = deformat_string(hPackage, ptr,(LPWSTR*)&data);
}
return data;
}
static UINT ACTION_WriteRegistryValues(MSIHANDLE hPackage)
{
UINT rc;
MSIHANDLE view;
MSIHANDLE row = 0;
static const CHAR *ExecSeqQuery = "select * from Registry";
/* Again here we want to key off of the components being installed...
* oh well
*/
rc = MsiDatabaseOpenViewA(hPackage, ExecSeqQuery, &view);
if (rc != ERROR_SUCCESS)
return rc;
rc = MsiViewExecute(view, 0);
if (rc != ERROR_SUCCESS)
{
MsiViewClose(view);
MsiCloseHandle(view);
return rc;
}
while (1)
{
WCHAR key[0x100];
WCHAR name[0x100];
WCHAR value[0x100];
LPSTR value_data = NULL;
HKEY root_key, hkey;
DWORD type,size;
INT root;
DWORD sz=0x100;
rc = MsiViewFetch(view,&row);
if (rc != ERROR_SUCCESS)
{
rc = ERROR_SUCCESS;
break;
}
/* null values have special meanings during uninstalls and such */
if(MsiRecordIsNull(row,5))
{
MsiCloseHandle(row);
continue;
}
root = MsiRecordGetInteger(row,2);
sz = 0x100;
MsiRecordGetStringW(row,3,key,&sz);
sz = 0x100;
if (MsiRecordIsNull(row,4))
name[0]=0;
else
MsiRecordGetStringW(row,4,name,&sz);
sz=0x100;
MsiRecordGetStringW(row,5,value,&sz);
/* get the root key */
switch (root)
{
case 0: root_key = HKEY_CLASSES_ROOT; break;
case 1: root_key = HKEY_CURRENT_USER; break;
case 2: root_key = HKEY_LOCAL_MACHINE; break;
case 3: root_key = HKEY_USERS; break;
default:
ERR("Unknown root %i\n",root);
root_key=NULL;
break;
}
if (!root_key)
{
MsiCloseHandle(row);
continue;
}
if (RegCreateKeyW( root_key, key, &hkey))
{
ERR("Could not create key %s\n",debugstr_w(key));
MsiCloseHandle(row);
continue;
}
value_data = parse_value(hPackage, value, &type, &size);
if (value_data)
{
TRACE("Setting value %s\n",debugstr_w(name));
RegSetValueExW(hkey, name, 0, type, value_data, size);
HeapFree(GetProcessHeap(),0,value_data);
}
MsiCloseHandle(row);
}
MsiViewClose(view);
MsiCloseHandle(view);
return rc;
}
/*
* This helper function should probably go alot of places
*/
static DWORD deformat_string(MSIHANDLE hPackage, WCHAR* ptr,WCHAR** data)
{
WCHAR* mark=NULL;
DWORD size=0;
DWORD chunk=0;
WCHAR key[0x100];
WCHAR value[0x100];
/* scan for special characters */
if (!strchrW(ptr,'[') || (strchrW(ptr,'[') && !strchrW(ptr,']')))
{
/* not formatted */
size = (strlenW(ptr)+1) * sizeof(WCHAR);
*data = HeapAlloc(GetProcessHeap(),0,size);
strcpyW(*data,ptr);
return size;
}
/* formatted string located */
mark = strchrW(ptr,'[');
if (mark != ptr)
{
INT cnt = (mark - ptr);
TRACE("%i (%i) characters before marker\n",cnt,(mark-ptr));
size = cnt * sizeof(WCHAR);
size += sizeof(WCHAR);
*data = HeapAlloc(GetProcessHeap(),0,size);
strncpyW(*data,ptr,cnt);
(*data)[cnt]=0;
}
else
{
size = sizeof(WCHAR);
*data = HeapAlloc(GetProcessHeap(),0,size);
(*data)[0]=0;
}
mark++;
strcpyW(key,mark);
*strchrW(key,']')=0;
mark = strchrW(mark,']');
mark++;
TRACE("Current %s .. %s\n",debugstr_w(*data),debugstr_w(mark));
if (get_property(hPackage, key, value) == ERROR_SUCCESS)
{
LPWSTR newdata;
chunk = (strlenW(value)+1) * sizeof(WCHAR);
size+=chunk;
newdata = HeapReAlloc(GetProcessHeap(),0,*data,size);
*data = newdata;
strcatW(*data,value);
}
TRACE("Current %s .. %s\n",debugstr_w(*data),debugstr_w(mark));
if (*mark!=0)
{
LPWSTR newdata;
chunk = (strlenW(mark)+1) * sizeof(WCHAR);
size+=chunk;
newdata = HeapReAlloc(GetProcessHeap(),0,*data,size);
*data = newdata;
strcatW(*data,mark);
}
(*data)[strlenW(*data)]=0;
TRACE("Current %s .. %s\n",debugstr_w(*data),debugstr_w(mark));
/* recursivly do this to clean up */
mark = HeapAlloc(GetProcessHeap(),0,size);
strcpyW(mark,*data);
TRACE("String at this point %s\n",debugstr_w(mark));
size = deformat_string(hPackage,mark,data);
HeapFree(GetProcessHeap(),0,mark);
return size;
}
#if 0
static UINT ACTION_Template(MSIHANDLE hPackage)
{
UINT rc;
MSIHANDLE view;
MSIHANDLE row = 0;
static const CHAR *ExecSeqQuery;
rc = MsiDatabaseOpenViewA(hPackage, ExecSeqQuery, &view);
if (rc != ERROR_SUCCESS)
return rc;
rc = MsiViewExecute(view, 0);
if (rc != ERROR_SUCCESS)
{
MsiViewClose(view);
MsiCloseHandle(view);
return rc;
}
while (1)
{
rc = MsiViewFetch(view,&row);
if (rc != ERROR_SUCCESS)
{
rc = ERROR_SUCCESS;
break;
}
MsiCloseHandle(row);
}
MsiViewClose(view);
MsiCloseHandle(view);
return rc;
}
#endif