I'd like to insert cab file in my msi package at installation time. Is this possible? There is immediate custom action:
UINT __stdcall CreateDataCab(MSIHANDLE hInstall) {
MSIHANDLE hRec = MsiCreateRecord(1);
UINT status = MsiRecordSetStream(hRec, 1, T("C:\\work\\Data2.cab"));
MSIHANDLE hDb = MsiGetActiveDatabase(hInstall);
MSIHANDLE hView = 0;
status = MsiDatabaseOpenView(hDb, _T("INSERT INTO `_Streams` (`Name`, `Data`) VALUES ('Data2.cab', ?)"), &hView);
status = MsiViewExecute(hView, hRec);
status = MsiViewClose(hView);
status = MsiCloseHandle(hView);
status = MsiCloseHandle(hRec);
status = MsiCloseHandle(hDb);
}
It is executed at the very beginning of INSTALL action. Value of status
variable is always ERROR_SUCCESS
. So everything seems to be fine. However in msi log I can see error triggered:
Action start 14:29:27: INSTALL.
MSI (s) (4C:14) [14:29:27:049]: Running ExecuteSequence
MSI (s) (4C:14) [14:29:27:049]: Doing action: SetSilentInstall
Action start 14:29:27: SetSilentInstall.
MSI (s) (4C:14) [14:29:27:051]: PROPERTY CHANGE: Adding SILENT property. Its value is '/s'.
Action ended 14:29:27: SetSilentInstall. Return value 1.
MSI (s) (4C:14) [14:29:27:052]: Doing action: CreateDataCab
Action start 14:29:27: CreateDataCab.
MSI (s) (4C:1C) [14:29:27:072]: Invoking remote custom action. DLL: C:\windows\Installer\MSI3EC2.tmp, Entrypoint: CreateDataCab
MSI (s) (4C:B4) [14:29:27:074]: Generating random cookie.
MSI (s) (4C:B4) [14:29:27:079]: Created Custom Action Server with PID 32604 (0x7F5C).
MSI (s) (4C:14) [14:29:27:147]: Running as a service.
MSI (s) (4C:14) [14:29:27:151]: Hello, I'm your 64bit Impersonated custom action server.
MSI (s) (4C!50) [14:32:22:069]: Note: 1: 2263 2: Data2.cab 3: -2147287035
Note: 1: 2263 2: Data2.cab 3: -2147287035
log record indicates MSI error 2263 Could not open stream [2].
occured in custom action. Hex value of -2147287035
is FFFFFFFF80030005
which is likely to be STG_E_ACCESSDENIED HRESULT - Access Denied
.
My first guess for this error was that MSI service can't access source file when trying to fetch it into the record. I relaxed permissions of C:\work\Data2.cab
but no luck.
Then I decided to use path for not existing file in MsiRecordSetStream()
call.
With this change MsiRecordSetStream()
call returned 161
error. As there is no branching in CreateDataCab()
it get executed up to the end and all MSI functions after MsiRecordSetStream()
succeeded.
msi log:
Action start 16:11:02: CreateDataCab.
MSI (s) (4C:D8) [16:11:02:596]: Invoking remote custom action. DLL: C:\windows\Installer\MSI4154.tmp, Entrypoint: JreCreateDataCab
MSI (s) (4C:54) [16:11:02:597]: Generating random cookie.
MSI (s) (4C:54) [16:11:02:605]: Created Custom Action Server with PID 34668 (0x876C).
MSI (s) (4C:6C) [16:11:02:682]: Running as a service.
MSI (s) (4C:6C) [16:11:02:684]: Hello, I'm your 64bit Impersonated custom action server.
MSI (s) (4C!74) [16:11:18:565]: Note: 1: 1101 2: C:\work\foo.cab 3: 2
MSI (s) (4C!74) [16:15:08:291]: Note: 1: 2263 2: Data2.cab 3: -2147287035
Note: 1: 1101 2: C:\work\Data2.cab 3: 2
indicates MSI error 1101 Could not open file stream: [2]
occured. This is extra error in the msi log, Note: 1: 2263 2: Data2.cab 3: -2147287035
still there.
Stepping in CreateDataCab()
with debugger resulted in the following observations:
Note: 1: 1101 2: C:\work\foo.cab 3: 2
error is added to the log right after failedMsiRecordSetStream()
call when it returns error161
.Note: 1: 2263 2: Data2.cab 3: -2147287035
error is added to the log right after successfulMsiCloseHandle(hView)
call.- Dumping contents of
_Streams
table at the beginning ofCreateDataCab()
and at the end indicates that the new record is added to the table successfully.
This is what I have at the moment. Seems like the following happens:
MsiRecordSetStream()
for the new record works fine. System can fetch file into the record if correct path is specified;- SQL request to insert new record to
_Streams
table succeeds; Data
field of newly added record contains no data.
I'm confused that MSI functions executed in custom action all pass but there is error in msi log file. Also changes to _Streams
table are partially successful: new record added, but the record contains no data. Seems like it is potentially possible to update _Streams
table at run-time, but something goes wrong with storing data in the table.
I have no issues with updating _Streams
in msi files at build time. All works fine. But doing this at run-time from custom action is broken for me. Am I missing something in my CA function? Is it ever legal to alter _Streams
at run-time?
UPD: I made some more investigation of the problem. I reworked my msi to call CreateDataCab()
twice from two CA. Also modified CreateDataCab()
itself. Pseudo code is following:
CreateDataCab() {
dumpStream();
openDb;
_Streams["Data1.cab"].Data = "c:/work/Data1.cab";
commitDb;
closeDb;
dumpStream();
}
dumpStream() {
openDb;
if ("Data1.cab" in _Streams) {
save _Streams["Data1.cab"].Data to "c:/work/saved.cab";
}
closeDb;
}
When CreateDataCab()
is called for the first CA the first dumpStream()
outputs nothing, but the second on writes something to c:/work/saved.cab
. This file appears binary equal to c:/work/Data1.cab
which was used as a source for record stream. This indicates that stream data can be updated in _Streams
table just fine. The data persists and remains in the db not to see that it was inserted and retrieved in two different MsiGetActiveDatabase()/MsiDatabaseClose()
scopes. Very nice, however Note: 1: 2263 2: Data2.cab 3: -2147287035
is still in the log.
When CreateDataCab()
is called for the second CA things are not so nice. The first dumpStream()
empties c:/work/saved.cab
file. This means that Data1.cab
record is still in _Streams
table, but with empty stream data.
As it was noted that _Streams
table is special I experimented with Binary
table. Attempt to modify existing record resulted in Note: 1: 2259 2: 3: 4:
error (Database: [2] Table(s) Update failed), i.e. "UPDATE Binary SET Data = ? WHERE Name='Data1.cab'"
SQL query doesn't work. So I reworked CA code to use MsiViewModify(hView, MSIMODIFY_INSERT_TEMPORARY, hRec)
and removed Data1.cab
record from Binary
table before running installation.
The first CreateDataCab()
CA worked fine: insert OK, no errors in msi log, the second dumpStream()
OK. However when CreateDataCab()
was called for the second CA, the first dumpStream()
dumped zero bytes and subsequent MsiViewModify(hView, MSIMODIFY_INSERT_TEMPORARY, hRec)
failed.
Results for _Streams and Binary table are identical:
- Inserted records persist across CA calls.
- Stream data in added records doesn't persist across CA calls and set zero length in the second CA.
Inserting new temporary records in Binary table results in no error messages in msi log.
The good news is that it is possible to alter records with stream data from CA. Not so bad news is that in Binary
table it is impossible to change data in existing records.
Bad news is that adding/changing stream data corrupts db.
I'll be very happy if somebody can challenge my conclusions.