General Info
Introduction
jsMSI.dll is a COM DLL with two objects. Ops and OpsV.
The two objects are identical except for one thing: The methods of
Ops are strongly typed, designed
for use with compiled software.
OpsV methods (V is for Variant) use only variants for parameters and return
values.
OpsV is for use in scripting.
The documentation of methods and properties provided in this file is for the
Ops object,
with data types indicated. For the
OpsV object just ignore the datatype details. All parameters and
function returns will be variants with the
OpsV object. A returned array of strings or longs will
be an array of variants. Etc. Everything else -- parameter definitions, error codes, etc. -- is the same for both
Ops and
OpsV
jsMSI.dll is a "wrapper" around the Windows Installer API. It is similar to the VBScript code available at JSWare
for working with MSI files, but it uses the Windows Installer API functions rather than the Dispatch scripting interface. jsMSI provides a wide
range of functions for working with MSI database files, without the awkward functions and tedious SQL normally required. For instance, you
can use the
GetValue and
SetValue functions to read and write column
values in MSI tables without being concerned with whether the value is string or numeric, and without having to construct error-prone
SQL queries. jsMSI also incorporate CAB extraction and creation functions, since those are needed in dealing with MSI files.
jsMSI does not really achieve anything that
cannot be achieved with VBScript. It is being provided in hopes that it may be useful to some people who may
be put off by the idea of using script, or who may want to incorporate MSI functions into compiled software.
The Windows Installer API is COM-oriented. It comes in two forms: a set of standard programming functions
and a Dispatch (late-bound) object model for scripting. (There is no vTable or early-bound version of the object
model.) Both API systems are awkward to use. The structure of MSI files is a horrendous
mess of unnecessary complexity. The SQL required to deal with that structure is convoluted and tedious.
jsMSI.DLL is an attempt to "tame" the poor design of Windows Installer, wrapping the more efficient
programming API and presenting, hopefully, a more clear and usable API for use with either scripting or compiled software.
Email: Feel free to write with comments or requests for help if necessary. If you send email please send it to jsware@jsware.net. "Webmail" from yahoo, gmail, facebook, hotmail, etc. is not accepted. It is
auto-deleted on the server. Please use a real email address when writing. For full explanation of that requirement
see
http://www.jsware.net/jsware/contact.php5.
jsMSI is mainly designed for unpacking and editing existing MSI installer files. It does not incorporate all of
the functionality that one might want for writing MSI installers from scratch. Part of the reason for that is because we do not regard
Windows Installer as a usable system for software installation, so jsMSI is not designed to be a tool for building MSI installer files.
But a bigger reason for the design of jsMSI is the inherent limitations presented by the design of MSI databases. For instance,
if jsMSI provided a method to create new tables that would require a complex and awkward function to accomodate all the
possible variations in the format of that table. The resulting function would have no advantage over
the native Windows Installer API method using an SQL query string. So jsMSI does not have a "CreateTable" method. Instead it
provides
ImportTable as a way that new tables can be created easily without resorting to
convoluted SQL queries.
Using the object
First, jsMSI.dll is a COM DLL. It must be registered on the system. Put it in a safe place, like the system folder, then drop it onto RegSvr.vbs.
Scripting usage:
Set DB = CreateObject("jsMSI.OpsV")
Usage in VB:
Dim DB as jsMSI.Ops
Set DB = New jsMSI.Ops
jsMSI.dll can be used by any COM-compatible programming language. In most cases you will want
to use the Ops object. OpsV is specifically designed for VBScript.
Update Information for jsMSI v. 2 (9/28/2010)
jsMSI.dll v. 1 required an external DLL named jcabio.dll. jsMSI.dll v. 2
no longer requires that extra file. Also, the returned error codes from
BuildCAB
have been changed. Those are the only major change in v. 2. Differences
between v. 1 and v. 2 are mainly internal.
Things to know about MSI databases
An MSI file is actually an SQL database that uses a subset of SQL functions. In most cases
the names of tables and columns are case-sensitive. In other words, if you want
to set a value in the Color column of a table named Fruit, you must use those exact strings. If you instead
try to set a value in the "fruit" table the function will fail. That is just part of how the database works. It is
not related to whether or not you use jsMSI.dll to access the MSI file.
Also, when importing tables from text files, be careful with the file format. It must be
constructed just-so, with tab characters between items in rows. And there should be
1 carriage return at the end of the last record. If you have two carriage returns the import will fail!
Back to Index
About error codes and error information
jsMSI has 3 properties associated with error information:
ErrorNumber,
ErrorDesc and
ErrorNumberInfo.
Many of the jsMSI functions are marked "sets error values". That means that
when the function is called,
ErrorNumber and
ErrorDesc are
cleared. When the function returns those values will be relevant. 0 indicates success.
Other error codes/descriptions may be jsMSI errors caused by mistakes with function calls.
For example, if you try to extract a CAB to an invalid folder path an error will result. jsMSI errors
are -1 (failed) or 1-5. Error codes can also come from Windows Installer. Those errors
are generally numbered 1600+. Also, Windows Installer sometimes returns standard Windows error
codes rather than MSI error codes. Those numbers will typically be greater than 5 and less than
1600. For example the Windows error 87 indicates an invalid parameter in a function call.
(For anyone who is curious, Windows errors are documented in winerror.h, part of the
Win32 API documentation.)
ErrorNumber - Returns the error number returned by last function, if that function sets error values.
ErrorDesc - Returns a descriptive string about the error number returned by last function, if that
function sets error values.
ErrorDesc returns descriptions of errors raised in jsMSI functions.
ErrorNumberInfo(ErrNo) - In many cases this will return further information beyond
ErrDesc.
ErrDesc attempts to provide a specific problem report when a function fails. It helps with debugging
by telling you what part of the function failed. Often
ErrDesc can tell you which of several calls to
the Windows Installer API failed. By looking up those functions in msi.chm you may be able to figure out
the cause of the error. But
ErrDesc is specific to jsMSI,
ErrorNumberInfo returns a standard
error description string for specific error numbers. (If the number is not listed it returns "".)
Example 1: You call a jsMSI function that sets error values. In the call you provide an invalid file path. jsMSI will
catch that and set both
ErrorNumber and
ErrorDesc. Example 2: You call a jsMSI function
which fails due to problems with a call to Windows Installer functions. In that case
ErrorNumber will
return the 1600+ error number from Windows Installer. If possible, jsMSI will also provide information in the
ErrorDesc property. But since this is an MSI error there may be no info. In that case
ErrorNumberInfo
can be useful because it will return the official error description. For instance, a common error code when using
SQL database queries is error 1615. The official description for that error, which
ErrorNumberInfo
will return, is "SQL query syntax invalid or unsupported."
So
ErrorNumberInfo can be thought of as a generic error number reference table.
Back to Index
Notes about unpacking MSI installers
The typical MSI installer is an MSI database file with a CAB file embedded in it. With very large
installs the CAB may be stored externally. And in some rare cases there may be more than one CAB.
jsMSI's
UnpackMSI function performs a number of operations to unpack an MSI installer.
It reads a large part of the database to get file names, folder structure, Registry settings, etc. It then extracts the
CAB, if necessary, extracts the files stored in the CAB, and copies those with their correct names into a folder tree
representing the default program install. In the course of reading the MSI database and unpacking files,
UnpackMSI
writes a log file that describes those actions, lists "Features" and "Components" in the MSI, and lists Registry settings
made during install.
Occasionally the job is not so simple. Some people like to create an EXE that contains
the MSI installer. In those cases, typically, one has to run the EXE while the TEMP folder is open,
fish out a copy of the MSI that gets copied there, and then cancel the install. (When an install is
cancelled any setup files are typically deleted. Thus the step of copying the MSI file.)
You may also run into problems with "overproduced" installers made using
Wise, InstallShield, etc. Those installers are often MSI files inside EXEs, using custom
compression methods, and adding custom-action installer executables embedded in the MSI database.
Again, you should be able to fish the MSI out of the TEMP folder by starting the install. Then
cancel once you have a copy of the MSI.
With later versions of Windows Installer it may be even more difficult to unpack
the program. With Windows Installer 4.5, for example, it is not unusual to find an installer that
is an EXE but contains an MSI, as detailed above. But in some of these more recent vintage MSIs
there is rather bizarre behavior: The EXE wrapper may do the work of extracting the actual program files, creating a temporary program folder
tree in the TEMP folder! In other words, the installer EXE is using the Windows Installer API to
carry out at least part of the job that Windows Installer would normally perform.
In those
cases jsMSI cannot unpack the files because there is no CAB. (And the EXE does its own unpacking, anyway.)
But jsMSI can still document the file paths, Registry settings, etc. in its log file. So you have to go to the TEMP folder to get
the MSI for documentation purposes, then you have to fish around further in the TEMP folder to
retrieve the unpacked files.
SQL Server 2008 Express is an example of a v. 4.5 installer that only partially uses
Windows Installer. The installer file is an EXE. It is actually a self-executing CAB file! Inside are an
MSI file and a large number of installation files. When the installer is run it dumps the files into a
folder tree, renaming them in the process from gibberish code names to their final destination names.
In other words, the installer creates a folder/file hierarchy in TEMP that represents the installed program.
Apparently those files are then copied to a matching folder tree in Program Files.
With an installer that exhibits this odd behavior you will need to copy the folder tree(s) from the TEMP
folder if you want to inspect the files. Then also process the accompanying MSI file with jsMSI and
use the log file written by jsMSI to make sense of the files.
So the long and the short of it is that you can document the details of an
install with jsMSI if there is an MSI file involved. But the means of getting at that MSI file
can vary. And one should never expect Microsoft to follow their own prescribed standards. More
often than not, they don't.
If you find yourself repeatedly running into these "semi-Windows-Installer" installs
you may want to use jsMSI to write a custom script/EXE to deal with them. For instance, the function
GetFileNameFromID
provides a way to translate file names in an MSI CAB to their real names. The function
EXEtoCAB
provides a way to convert a self-executing CAB file to a normal CAB file. Etc.
Back to Index
UnpackMSI
Function UnpackMSI(sMSIFile As String, sDestFolder As String, ClearLogBeforeUnpack As Boolean, IncludeRegSettings As Boolean) As Boolean
Unpack an MSI file. This function stands alone. All other functions relate to managing MSI file
databases. One can use jsMSI functions to open an MSI, do various operations, then closes the MSI. But
UnpackMSI is an all-in-one
function that opens an MSI installer file, unpacks the contents to a folder tree that mimics the install locations
for the various files, details Registry settings that are created with installation, and writes a log file to provide a detailed
report of the operation. This function does not set error values. Any error reports are written to the unpacking log,
which is named "Program Description.log".
sMSIFile - Full path of MSI file to unpack.
sDestFolder - Full path of folder to unpack into.
ClearLogBeforeUnpack - Clear log entries before starting unpack?
IncludeRegSettings - Do you want all Registry settings listed as part of the log file?
Notes: Function returns True/False. If any part of the unpacking was unsuccessful that is
detailed in the log file. (Function could return True for partial success.)
IncludeRegSettings is included as
an option because some installations add hundreds or even thousands of Registry entries. If that data
is not relevant it can be left out of the log for the sake of brevity.
UnpackMSI does not set
error number or description because the log details all error data. The error functions and logging functions
are all irrelevant with this function.
Back to Index
General MSI Functions
Function OpenMSI(sPath As String, IfReadOnly As Boolean) As Boolean
Opens an MSI file.
Opening is required before any methods that access the database can be used. (With the exception of UnpackMSI.) Sets error values.
sPath - Full path of MSI file.
IfReadOnly - Whether to open the file read-only or read/write. When
IfReadOnly is set to True
the various functions to set values, delete records, etc. cannot be used.
Notes: An MSI cannot be opened by two processes at once. If you do not properly close an MSI you
may not be able to re-open it until reboot, or at least until your script/software stops running. In some
cases an MSI file may be set to read-only mode in the SummaryInformation Security value. There is actually an option to do that.
That setting seems to be a recommendation for MSI editors rather than an enforced rule. But for the sake of completeness there are two functions
SetReadOnlyStatus and
GetReadOnlyStatus included to
ascertain/change the read-only status of an MSI database.
Back to Index
Sub CloseMSI()
Whenever an MSI is opened this method should be called after all operations are finished. It causes jsMSI to
release stored data and it causes Windows Installer to close the MSI database.
jsMSI will call
CloseMSI when it itself is unloaded, but it's best to just always call
CloseMSI anyway. That
is especially true in cases where you may open an MSI more than once during the course of a script or program. If the MSI
has not been properly closed it cannot be reopened because Windows Installer will view it as already being open.
Back to Index
Function CreateMSI(sPath As String) As Boolean
Creates a basic MSI database file. This is a "bare" file with no tables. The function creates the MSI and
then closes it.
OpenMSI must then be called if you want to work with the new file. Sets error values.
sPath - Full path of file to create.
Notes: This function has limited usefulness. jsMSI is not designed for designing MSI databases. The SQL queries
to do that get very involved, with many different data type options for columns. If you want to create an MSI from scratch using
jsMSI, the best approach would be to use
CreateMSI, then write table text files and import them. Export tables
from any MSI installer to see how that is done. A typical MSI installer database has over 80 tables.
The layout of an MSI table in text-file form is actually very simple. The table name, column names, and column data types are
listed in tab-delimited lines at the top of the file.
Any records are then listed in lines below that. So a complex table, with many columns, can be easily designed and created in
Notepad, without needing to deal with the awkwardness of SQL. That file can then be imported using
ImportTable
to create a new database table.
Back to Index
Function GetSummaryInfo(LNumRet As Long) As String()
Returns an array(4) of common SummaryInformation values, if they exist in the MSI.
LNumRet returns the number of values retrieved. Those are not necessarily in order. For example,
the array may hold values in array(1) and array(4). In that case
LNumRet would return 2.
Example:
A4 = DB.GetSummaryInfo(NumberVals)
The values that may be returned are as follows:
Array index-Value: |
0-Title |
1-Subject |
2-Author |
3-Comments |
4-Program Name |
Back to Index
Function GetSummaryInfoName(LIndex As Long) As String
Returns a string that describes SummaryInformation data. See
GetSummaryInfo (above) for the list.
Example:
GetSummaryInfoName(2) will return "Author". This function is just a simple convenience for use in formatting returned
SummaryInformation data.
Back to Index
Function SetSummaryInfoString(sValue As String, sData As String) As Long
Sets the value for any of the 5 properties listed above. Returns 0 on success, -1 if the property name is invalid, or
an MSI error code if the call fails.
sValue is not case-sensitive.
Ex.:
LRet = DB.SetSummaryInfoString("Author", "Ace and Acme Software")
Back to Index
Function GetReadOnlyStatus() As Long
Returns a value that indicates the read-only status of an MSI database.
Possible return values: -1 - failed to find read-only status. 0 - no restrictions set. 2 - restrictions "recommended" but not set. 4 - database is read-only.
Does not set error values. Note that the read-only setting seems to be only a recommendation for MSI editors, not directly enforced by Windows Installer.
Back to Index
Function SetReadOnlyStatus(ROVal As Long) As Long
Sets the read-only status of an MSI database.
ROVal should be 0 (RW), 2 (read-only recommended), or 4 (read-only enforced).
Return: Does not set error values. Return is the MSI error code returned from the MSI function MsiSummaryInfoSetProperty.
For more information try the
ErrorNumberInfo property, which may return an error description for both
jsMSI and Windows/MSI error codes. Note that the read-only setting seems to be only a recommendation for MSI editors, not directly enforced by Windows Installer.
Table
Back to Index
Property Get TableNames() As String()
Returns a string array of table names for the tables in an MSI.
Back to Index
Property Get TableCount() As Long
Returns the number of tables in an MSI.
Back to Index
Function TableExists(sTableName As String) As Boolean
Does this table exist in the MSI?
Ex.:
If DB.TableExists("Classes") = True Then....
Back to Index
Function ImportTable(sFolPath As String, sFileName As String) As Boolean
Imports a table into an MSI from text file. jsMSI does not have functions to create tables because
the possible variations of SQL are so numerous. But tables can easily be created from text files. To see how it works,
use
ExportAllTables (below) to export all of the tables in an MSI to text files. You can copy the syntax from those sample tables.
Ex.:
Boo = DB.ImportTable("C:\tables", "sometable.txt")
Return: Returns boolean. If return is False then error values are set.
Notes on writing tables: If you look at a few exported tables you can see the pattern.
Windows Installer is finicky about table layout. It must be done just-so. The top line contains column names, separated by Tabs (Chr(9)).
The next line specifies the data type of the column. For example "i4" indicates a 4-byte integer. "s72" indicates a string of max. 72 characters.
v0 indicates a binary stream (embedded files). (A numeric value of zero indicates no size limit. An alphabetic value uppercased indicates
the value can be blank. For instance, S72 is a string value of max. 72 characters that is "nullable". s24 is a string value, of max. 24 characters, that is not nullable.)
After the column names and types comes a line containig the table name and key column name.
The key column values cannot be changed or deleted unless the record is deleted. Typically the key column is the first column, but that is
not required.
So there is a line with column names, a 2nd line with column types, and a 3rd line naming the table and key column. All
listed items in these lines are separated by tabs. Directly after those 3 lines the records begin. Records are formatted like the
column names line, with the value of each column separated by a tab. Each record is on a separate line.
The file must end with
a single carriage return (vbCrLf) after the last record. If there is no return, or if there are two, the table will probably fail to import.
By writing a table as a text file and importing it the tedious work of SQL queries can be avoided.
Back to Index
Function ExportTable(sTableName As String, sDestFolder As String, sFileName As String) As Boolean
Creates a text file on disk that represents an MSI database table. Such tables can be edited and
then re-imported.
Ex.:
Boo = DB.ExportTable("Files", "C:\tables", "FilesTable.txt")
sTableName - Name of database table.
sDestFolder - Location for exporting.
sFileName - Name of exported table file.
Return: Returns boolean. If return is False then error values are set.
Back to Index
Function ExportAllTables(sDestFolder As String) As Boolean
Exports all tables in database to text files.
sDestFolder is the location to write
the files. Each table in the database is represented by a text file named for the table. For example, the
"Directory" table will be exported as Directory.txt.
Return: Returns boolean. If return is False then error values are set.
Back to Index
Function RemoveTable(sTableName As String) As Boolean
Removes specified table from database.
Return: Returns boolean. If return is False then error values are set.
Back to Index
Columns
Function ColumnExists(sTableName As String, sColumnName As String) As Boolean
Does the column exist? Returns boolean.
Back to Index
Function FirstColumnName(sTableName As String) As String
Returns the name of the first column in a table. The first column is usually --
but does not have to be -- the key column, meaning that the value cannot be changed without
removing the record and then adding it back.
Back to Index
Function GetColumnInfo(sTable As String, AColNames As Variant, AColTypes As Variant) As Long
Returns 2 arrays that describe a table's columns.
sTable - Name of table.
AColNames - Array of column names, starting from left.
AColTypes - an array of numeric values that indicate the column's data type.
Possible values are 1-short integer. 2-long integer. 3-binary. 4-string. 5-localizable string. (0-error)
The function return is the number of columns, which would be 1 more than UBound of the arrrays. If the function
failes, due to something like an invalid table name, the function return is 0.
Back to Index
Function GetColumnType(sTableName As String, sColumnName As String) As Long
Returns data type of column. Possible values are 1-short integer. 2-long integer. 3-binary. 4-string. 5-localizable string. (0-error)
Back to Index
Function GetColumnTypeValue(sTableName As String, sColumnName As String) As Long
Returns the actual numeric flag value for the column type. This is the hidden
Type column value of the
_Columns table.
For most purposes this function is irrelevant. The other column functions and properties provide the same information in a more usable form.
Back to Index
Function ColumnIsString(sTableName As String, sColumnName As String) As Boolean
Does column hold string data?
Back to Index
Function ColumnIsNumeric(sTableName As String, sColumnName As String) As Boolean
Does column hold numeric values?
Back to Index
Function ColumnIsNullable(sTableName As String, sColumnName As String) As Boolean
Can the column data be blank?
Back to Index
Function ColumnIsPrimary(sTableName As String, sColumnName As String) As Boolean
Is this the primary key column? (The MSI docs refer to "primary" columns and "key" columns. They also
refer to "primary key" columns. The terms seem to be interchangeable. This is the identifying column in a record. It cannot be changed
except by deleting the record and adding a new one. It is possible to have more than one primary column in a table, but it seems
to only occur in the "artificial" tables such as
_Tables and
_Columns. These tables are described as "read only system tables".
They seem to actually be tables in name only, really existing as just some sort of inherent lists in the .msi file.)
Back to Index
Function GetColumnDataMaxLength(sTableName As String, sColumnName As String) As Long
Returns the maximum string length for string values. Irrelevant for other values.
Back to Index
Function GetValue(sTableName As String, sColumnToGet As String, sColumntoCompare As String, CompareValIfString As String, CompareValIfNumeric As Long, NumberValuesReturned As Long, ReturnIsNumeric As Boolean) As Variant
Returns a value from a specific column, based on a value match in another column. The value is returned as an array. In most cases you will probably
be using this function to return a single value: "what is the value of Color in the record where Name is Tangerine?". But by the nature of the database, some queries
could return more than one match. Returning an array accomodates that. The value sought can be string or numeric. (This function will not work with binary values. Use
ExtractFileFromMSI for that.)
sTableName - Name of table.
sColumnToGet - Name of column for which value is sought.
sColumnToCompare - Name of column to match.
CompareValIfString, CompareValIfNumeric - Value to match. If value is string then CompareValIfNumeric must be 0. If value is numeric then CompareValIfString must be "".
NumberValuesReturned - Returns the number of matches found.
ReturnIsNumeric - Returns whether the value sought is numeric.
Notes: Think of this function like so: "Get all values in
sColumnToGet where
sColumnToCompare is [
CompareValIfString / CompareValIfNumeric].
Ex.:
A1 = DB.GetValue("Fruit", "Color", "SeedNumber", "", 20, NumRet, BooNumeric) '-- Get values in Color column where SeedNumber is 20
A1 = DB.GetValue("Fruit", "SeedNumber", "Color", "orange", 0, NumRet, BooNumeric) '-- Get values in SeedNumber where Color is "orange"
Return: Check
NumberValuesReturned first. If that value is 0 no either matches were found or there was an error, in which case error values are set.
If
NumberValuesReturned is greater than 0 then function return is an array of values.
Back to Index
Function SetValue(sTableName As String, sColumnToSet As String, ValToSetIfString As String, ValToSetIfNumeric As Long, sColumntoCompare As String, ValCompareIfString As String, ValCompareIfLong As Long) As Boolean
Sets value(s) based on a match to specified column.
sColumnToSet - Name of column in which to set value(s).
ValToSetIfString, ValToSetIfNumeric - Value to set. If string then
ValToSetIfNumeric must be 0. If numeric then
ValToSetIfString must be "".
sColumntoCompare - Name of column to match.
ValCompareIfString, ValCompareIfLong - Value to match for setting value.
Notes: Think of this function like so: "Set the value in
sColumnToSet to
ValToSet in any records where the value in
sColumntoCompare is
ValCompare"
Ex.:
Boo = DB.SetValue("Fruit", "Color", "Orange", 0, "Fruit", "Tangerine", 0) '-- In the Fruit table, set Color column value to "Orange" where Fruit column contains "Tangerine".
Boo = DB.SetValue("Fruit", "SeedNumber", "", 12, "Fruit", "Apple", 0) '-- In the Fruit table, set SeedNumber column value to 12 where Fruit column contains "Apple".
Return: Returns boolean. If return is False then error values are set.
Back to Index
Function GetAllStringValues(sTableName As String, sColumnName As String, ARet() As String) As Long
Returns an array of all values in column
sColumnName. Column must hold string values.
Return: Returns number of values found. (Which will be UBound(ARet) + 1). If return is 0 there are no values or there
could be an error. If there was an error then error values are set.
ErrorNumber will return non-zero.
Back to Index
Function GetAllNumericValues(sTableName As String, sColumnName As String, ARet() As Long) As Long
Returns an array of all values in column
sColumnName. Column must hold numeric values.
Return: Returns number of values found. (Which will be UBound(ARet) + 1). If return is 0 there are no values or there
could be an error. If there was an error then error values are set.
ErrorNumber will return non-zero.
Back to Index
Records
Function AddRecord(sTableName As String, sColumnValues As String) As Boolean
Adds a record to a table.
Ex.:
Boo = DB.AddRecord("Fruit", "Tangerine, orange, 20")
The column values are sent as a comma-delimited list. No special syntax is needed for
strings vs numeric values. Just put them all in a string and jsMSI will sort it out. However, the values
must accurately match up with existing columns. For instance, if you send the string "Tangerine, orange, 20"
and the 2nd column is numeric then the function will fail.
If blank entries are desired, send "-" for those values. For example "Tangerine, -, 20"
would leave the second column blank when adding the new record.
Return: Returns boolean. If return is False then error values are set.
Notes: 1) When leaving a numeric value blank, Windows Installer will generally write a negative number in its place.
2) If "-" is sent to leave a column blank, and that column is not nullable, the method will fail, probably with error 1627.
Some columns are not allowed to be left blank. (In an exported table text file nullable columns are marked by capitaization.
That is, a column of datatype S72 takes a string value of up to 72 characters and may be left blank. A datatype of s72 may not be left blank.)
Back to Index
Function RemoveRecord(sTableName As String, sColumnName As String, ValToMatchStr As String, ValToMatchLong As Long) As Boolean
Removes a record from a table.
sTableName - Name of table.
sColumnName - Column by which to identify record.
ValToMatchStr, ValToMatchLong - Value to match. If string then long value must be 0. If long then string value must be "".
Notes: Read this function as "Delete any record where the value in sColumnName column matches [ValToMatchStr / ValToMatchLong].
Ex.:
Boo = DB.RemoveRecord("Fruit", "Color", "orange", 0) '-- Delete all records where Color is "orange"
Boo = DB.RemoveRecord("Fruit", "SeedNumber", "", 20) '-- Delete all records where SeedNumber is 20
Return: Returns boolean. If return is False then error values are set.
Back to Index
Binary I/O
Function ExtractFileFromMSI(sTableName As String, sFileID As String, sFilePath As String) As Boolean
This is the reverse of
InsertFile. It extracts a file stored in the
Binary
table, normally. But the
sTableName parameter allows you to specify a different, custom table where binary files are stored.
sFileID is the value from the
Name column of the
Binary table. (If another table is referenced it must still have the
same structure as the Binary table, with two columns named
Name and
Data that hold string and binary data, respectively.)
sFilePath is the full path for the extracted file to be written to.
Return: Returns boolean. If return is False then error values are set.
Back to Index
Function ExtractCAB(sFileID As String, sFilePath As String) As Boolean
Extracts a CAB file from an MSI database.
sFileID must be a value from the
Cabinet
column of the
Media table.
sFilePath is destination path for extracted file.
sFilePath is an in/out value. It must be sent as a variable. When the function is called it contains the
requested path of the extracted CAB. When the function returns, sFilePath contains the actual path.
The reason for that is that CAB files may not always be stored with valid file names. Example:
sPath = "C:\CABFolder\" & sCABID
Boo = DB.ExtractCAB(sCABID, sPath)
If
sCABID, taken from the
Media table, were "_abcdefghi" then
sPath would
be "C:\CABFolder\_abcdefghi". That would not be a valid CAB file path, so the function would write the file to "C:\CABFolder\_abcdefghi.cab"
and when the function returns,
sPath will hold the string "C:\CABFolder\_abcdefghi.cab".
Return: Returns boolean. If return is False then error values are set.
Back to Index
Function ExtractAllFilesFromCAB(sCABPath As String, sDestFolder As String) As Boolean
Extracts all files in a CAB to
sDestFolder.
Return: Returns boolean. If return is False then error values are set.
ErrorNumber will return an error code related
to a basic file error or a cabinet.dll error.
ErrorDesc will return the explanation of that error number. (
ErrorNumberInfo
is not relevant with CAB errors.)
Back to Index
Function ExtractFileFromCAB(sCABPath As String, sFileName As String, sDestFolder As String)) As Boolean
Extracts one file from a CAB.
sFileName must match the name of a file contained within the CAB.
sDestFolder is the location to write the extracted file to.
Return: Returns boolean. If return is False then error values are set.
ErrorNumber will return an error code related
to a basic file error or a cabinet.dll error.
ErrorDesc will return the explanation of that error number. (
ErrorNumberInfo
is not relevant with CAB errors.)
Back to Index
Function BuildCAB(sCABFilePath As String, OverWrite As Boolean, AFileList As Variant) As Long
Creates a new CAB file.
sCABFilePath is the full path of the new CAB file to be created.
OverWrite - Overwrite an existing CAB file of the same name? If this parameter is false and the file exists,
BuildCAB will fail.
AFileList - An array of file paths. These are the files to be included in the CAB.
Dim sPath as String
Dim AList() '-- May be variant or string array.
sPath = "C:\CABFolder\NewCAB.cab"
ReDim AList(2)
AList(0) = "C:\Files\File1.txt"
AList(1) = "C:\Files\OtherFile.dll"
AList2) = "C:\Files\ThirdFile.exe"
Ret = DB.BuildCAB(sPath, True, AList)
Return: The return value is the error number. Returns 0 on success.
ErrorNumber will return an error code related
to a basic file error or a cabinet.dll error.
ErrorDesc will return the explanation of that error number. (
ErrorNumberInfo
is not relevant with CAB errors.)
Note that the errors returned work slightly differently from the jsMSI
v. 1 function. They are simplified and ErrorDesc can now be used to get a description of the error.
Back to Index
Function ReplaceCAB(sCABName As String, sCABPath As String) As Boolean
Replaces a CAB embedded in an MSI file with another CAB.
sCABName is the string from the
Cabinet column of the
Media table.
sCABPath is the full path of the new CAB file.
Return: Returns boolean. If return is False then error values are set.
Back to Index
Function InsertFile(sFileID As String, sFilePath As String) As Boolean
Stores a file in the
Binary table of an MSI database.
sFileID is the value for the
Name column of the
Binary table.
sFilePath is the full path of the file to be stored.
Return: Returns boolean. If return is False then error values are set.
Back to Index
Function GetFileNameFromID(sFileID As String) As String
Returns a file name given a file ID. Files in an MSI are stored with unique IDs. The ID name can be
anything as long as all files have unique names. The file ID is usually not the same as the real file name. This function
provides a way to get the match when unpacking CAB files.
Back to Index
Function EXEtoCAB(sEXEPath As String, sCABPath As String) As Boolean
Converts a self-executing CAB file to a normal CAB file, which can then be
accessed for extraction.
Return: Returns boolean. If return is False then error values are set.
Back to Index
Error Codes
The 3 error properties apply to a large number of the jsMSI methods. They especially apply to
methods where jsMSI has to call Windows Installer methods. For example,
TableCount
does not set error values because it simply returns a stored number. On the other hand,
GetValue
must make a number of internal function calls to Windows Installer functions. Detailed error information could be useful in that case.
The following methods clear error info. when called and return valid error info. on return:
UnpackMSI (Up to opening of database. after that errors go to log file.)
OpenMSI
CreateMSI
ImportTable
ExportTable
GetValue
SetValue
AddRecord
RemoveRecord
RemoveTable
ExportAllTables
GetAllStringValues
GetAllNumericValues
GetSummaryInfo
InsertFile
ExtractFileFromMSI
ExtractFileFromCAB
ExtractCAB
ExtractAllFilesFromCAB
ReplaceCAB
Property ErrorDesc() As String
May return detailed information about the function where an error originated. A particular jsMSI
function might call to 5 Windows Installer functions, for instance.
ErrorDesc can
often provide details about exacly which internal call failed, providing help in debugging.
Back to Index
Property ErrorNumber() As Long
Returns error number when a function fails
Back to Index
Property ErrorNumberInfo(LErrNo As Long) As String
Returns information for specific error numbers.
ErrorDesc will often return error
information about a specific error that occurs.
ErrorNumberInfo returns a generic definition
for a particular error number. Note that ErrorNumberInfo mainly relates to MSI errors. Errors with CAB extraction or building
are separate CAB errors retrieved with
ErrorDesc.
Back to Index
Logging
Sub WriteLogEntry(sEntry As String)
Adds a string to the currently saved log content.
Back to Index
Property LogText() As String
Returns any text currently saved in Log.
Back to Index
Sub ClearLog()
Clears any text currently saved in Log.
Back to Index
Sub WriteLogToFile(sPath As String)
Writes current log to disk.
sPath is full path of new log file. This function does not clear the log.
Contact
If you have trouble with jsMSI, or find an MSI/MSM file that it cannot unpack, please feel
free to write. Occasionally a faulty MSI file shows up that does not unpack properly. If we have a sample of
such an MSI to work from jsMSI can probably be updated to unpack such faulty installers.
Email: jsware@jsware.net
Note: We respect your privacy and expect the same from others. Purveyors of
free webmail do not respect the privacy of email correspondence. Email from free
corporate webmail accounts (hotmail, live.com, yahoo, facebook, gmail) is auto-deleted
and will not be received. When writing please use a real email account.
License
You use all script code and components from JSWare at your own risk.
The components (compiled DLL and EXE files) may be used for personal or
commercial purposes. No payment or attribution is required for either use.
The components may be redistributed if they are required as support files
for scripts or software that you have written.
Also, the script code may be used freely, in part or as whole scripts,
for any purpose, personal or commercial, without payment or attribution.
I ask only that you not redistribute these scripts and components, except
as required for your direct use. Instead, please direct others to obtain copies
of JSWare scripts and components directly from www.jsware.net.
Also, none of the code here may be redistributed under another license. If a
work using code from JSWare is distributed with restrictions of any kind
the code from JSWare must be kept exempt from those restrictions.
This includes, but is not limited to, code sold for profit, code with usage restrictions
and code distributed as so-called "Open Source" with redistribution restrictions.
Joe Priestley