Search for partial registry key in InstallShield - installation

As part of our installation procedure we have to install Adobe Acrobat XI. According to Adobe:
The basic formula for constructing and decoding the GUID is as follows:
Acrobat: Example: AC76BA86-1033-F400-7760-100000000002
[product family]-[language code]-[additional languages]-[product type]-
[license type][major version][major minor version]
Since I know the key is located in HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall and I know the key starts with {AC76BA86- I would like to simply search for the key(s) that begin with that sequence so I can inspect it to determine if we need to install Acrobat.
Is there a way to do a partial registry key search in InstallShield's install script?

Adobe is bastardizing their product codes and I really DON'T suggest playing along with that game.
However, if you read section 2.11.3 you'll see that all readers share the common UpgradeCode of A6EADE66-0000-0000-484E-7E8A45000000.
What this allows you to do is use the Upgrade Table / FindRelatedProducts to search for ProductCodes based on this UpgradeCode. Use the "Detect Only" setting and the ProductCode found will be assigned to the Property of your choosing.
No custom actions, no bastardized ProductCode GUIDS. Simple, easy... "It Just Works".

Looks like the best way to do this was to use RegDBQueryKey(<registry path>, REGDB_KEYS, <returnList>) to get a list of subkeys and then inspect each key in the list to see if it started with the partial value I was looking for.

Related

Outlook Advanced Search on property GlobalAppointmentID

Alright so I'm needing some help here. I working with Outlook 2007 PIA (Outlook add-in) and using the advanced search. I'm trying to write a DASL filter for the AppointmentItem property GlobalAppointmentID but I cannot seem to find the correct namespace to use in the filter.
I've tried urn:schemas:calendar:uid and I do not get any results when searching on the first appointment in the default calendar list. The MSDN documentation states that this is a mapi property but I am unfamiliar with mapi so at the moment I do not know how to even find it in the msdn that way (I tried and ended up way over my head).
I know there is a work around to go to the default folder and iterate through the collection to find the object that I need but I consider that too inefficient (I'm using it at the moment but want to improve it).
Any help would be appreciated!
OOM will not let you search for GlobalAppointmentId (or any other PT_BINARY property) in Items.Find/FindNext/Restrict. The only workaround is to either loop through all item in the Calendar folder (extremely inefficient) or search using Extended MAPI (C++ or Delphi only) or Redemption (I am its author - any language, its version of RDOFolder.Items.Find allows to search on GlobalAppointmentId or any other binary property)
Not every property can be used in a filter string for Items.Restrict, Table.Restrict or Application.AdvancedSearch methods. For both Jet and DASL queries, you cannot restrict on a binary property such as EntryID or GlobalAppointmentID. Also you cannot restrict or search for computed properties.
Anyway, you may find the Chapter 11: Searching Outlook Data helpful.

How can I resolve MSI paths in VBScript?

I am looking for resolving the paths of files in an MSI with or without installing (whichever is faster) from outside an MSI, using VBScript.
I found a similar query using C# instead and Christpher had provided a solution, below: How can I resolve MSI paths in C#?
I am going through the very same pain now but is there anyway to achieve this using WindowsInstaller object in VBScript, rather than go with endless queries through SQL Tables of MSI back and forth to achieve the same. Though any direction would be welcoming as I have tried tested whatever I can with very limited success.
yes there is a solution without installing the msi and using vbscript.
there is a very good example in the Windows Installer SDK called "WiFilVer.vbs"
using that example i've thrown together an quick example script that does exactly what you need.
set installer = CreateObject("WindowsInstaller.Installer")
const READONLY = 0
set db = installer.OpenDataBase("<FULL PATH TO YOUR MSI>", READONLY)
set session = installer.OpenPackage(db, READONLY)
session.DoAction("CostInitialize")
session.DoAction("CostFinalize")
set view = db.OpenView("SELECT File, Directory_, FileName, Component_, Component FROM File,Component WHERE Component=Component_ ORDER BY Directory_")
view.Execute
set record = view.Fetch
do until record is nothing
file = record.StringData(1)
directoryName = record.StringData(2)
fileName = record.StringData(3)
if instr(fileName, "|") then fileName = split(fileName, "|")(1)
wsh.echo(session.TargetPath(directoryName) & fileName)
set record = view.Fetch
loop
just add the path to your MSI file.
tell me if you need a more detailed answer. i will have some more time to answer this in detail this evening.
EDIT the promised background (and why i need to call ConstFinalize)
naveen actually MSDN was the only resource that can give an definitive answer on this, but you need to know where and how to look since windows installer ist IMHO a pretty complex topic.
I really recommend a mix of the msdn installer function reference, the database reference, and the examples from the windows installer SDK (sorry couldn't find a download link, i think its somewhere hidden in the like 3GB windows SDK)
first you need general knowledge of MSIs:
an MSI is actually a relational database.
Everything is stored in tables that relate to each other.
(actually not everything, but i will try to keep it simple ;))
This database is interpreted by the Windows Installer,
this creates a 'Session'
also some parts are dynamically resolved, depending on the system you install the msi on,
like 'special' folders similar to environment variables.
E.g. msi has a "ProgramFilesFolder", where windows generally has %ProgramFiles%.
All dynamic stuff only exists in the Installer session, not the database itself.
In your case there are 3 tables you need to look at, take care of the relations and resolve them.
the 'File' table contains all Files, the 'Component' table tells you which file goes into which directory and the 'Directory' table contains all information about the filesystem structure.
Using a SQL Query i could link the Component and File table to find the directory name (or primary key in database jargon).
But the directory table has relations in itself, its structured like a tree.
take a look at this example directory table (taken from the instEd MSI)
The columns are Directory, Directory_Parent and DefaultDir
InstEdAllUseAppDat InstEdAppData InstEd
INSTALLDIR InstEdPF InstEd
CUBDIR INSTALLDIR hkyb3vcm|Validation
InstEdAppData CommonAppDataFolder instedit.com
CommonAppDataFolder TARGETDIR .
TARGETDIR SourceDir
InstEdPF ProgramFilesFolder instedit.com
ProgramFilesFolder TARGETDIR .
ProgramMenuFolder TARGETDIR .
SendToFolder TARGETDIR .
WindowsFolder_x86_VC.1DEE2A86_2F57_3629_8107_A71DBB4DBED2 TARGETDIR Win
SystemFolder_x86_VC.1DEE2A86_2F57_3629_8107_A71DBB4DBED2 WindowsFolder_x86_VC.1DEE2A86_2F57_3629_8107_A71DBB4DBED2 System
The directory_parent links it to a directory. the DefaultDir contains the actual name.
You could now resolve the tree by yourself and replace all special folders(which in a vbscript would be very tedious)...
...or let the windows installer handle that (just like when installing a msi).
now i have to introduce a new thing: Actions (and Sequences):
when running (installing, removing, repairing) an msi a defined list of actions is performed.
some actions just collect information, some change the actual database.
there are list of actions (called sequences) for various things a msi can do,
like one sequence for installing (called InstallExecuteSequence), one for collecting information from the user (the UI of the MSI: InstallUISequence) or one for adminpoint installations(AdminExecuteSequence).
in our case we don't want to run a whole sequence (which could alter the system or just take to long),
luckily the windows installer lets us run single actions without running a whole sequence.
reading the reference of the directory table on MSDN (the remarks section) you can see which action you need:
Directory resolution is performed during the CostFinalize action
so putting all this together the script is easier to read
* open the msi file
* 'parse' it (providing the session)
* query component and file table
* run the CostFinalize action to resolve directory table (without running the whole MSI)
* get the resolved path with the targetPath function
btw i found the targetPath function by browsing the Installer Reference on MSDN
also i just noticed that CostInitialize is not required. its only required if you want to get the sourcePath of a file.
I hope this makes everything clearer, its very hard to explain since it took me like half a year to understand it myself ;)
And regarding PhilmEs answer:
Yes there are more influences to the resolution of the directory table, like custom actions.
keeping that in mind also the administrative installation might result in different directorys (eg. because different sequence might hold different custom actions).
Components have conditions so maybe a file is not installed at all.
Im pretty sure InstEd doesnt take custom actions into account either.
So yes, there is no 100% solution. Maybe a mix of everything is necessary.
The script given by weberik (deriven from MS SDK VB code) is very good, because it makes it easy to analyse the directory table without an own algorithm (which is a mid-size effort to do it in a loop or with a recursion algorithm).
But it gives not a 100% perfect view for all files, see below.
The method of the script is semi-dynamic (can be extended by other actions), but in effect it gives only the static directory structure, similar to a default administrative install or advanced MSI viewers.
Normally this is enough and what we want.
But be aware, that this is not the 100% solution (Knowing before exact the path of each file afterwards). That does mean, this will not give you always the correct paths in some cases:
You use command line parameters which substitute predefined directory table entries.
The MSI uses custom actions which change paths.
Especially it is not guaranteed, that every file is installed. There may be optional conditions and features and this may depend on the install environment.
In effect, a 100% solution is very very hard to achieve, without installing really. It comes near to reprogram nearly the whole windows installer engine. So simplifications are normally sufficient and accepted.
But you can extend the method to cover custom actions, e.g. with adding a line "session.DoAction(..)" for each additional action needed. Or to include command line parameters.
Sometimes it can be tricky. The easier the structure of the MSI is, the more likely it is that you succeed without more efforts.
Alternative to write an own program:
The question is, what you really want to find out, and if it is really necessary to program it:
If you don't want to write an automatical every-day MSI analyzer maybe the following is sufficient for you:
First tip: install the MSI with "msiexec /a mysetup.msi TARGETDIR="c:\mytestpath" . (similar restrictions as script above by weberik)
If the MSI has not used custom actions to change pathes including forgetting to add to the admin sequence ("forgetting" should be taken as the normal case for 99% or existing setups :-), you get the filestructure like if you install "really" with some special namings for the Windows predefined folders which you will find out easily.
If the administrative install lacks some folders, it is often a better idea of fixing the custom action (adding to the admin sequence) and using this scenario as your primary test case.
The advantage is, that only you limit the dynamics used by admin install. BTW, you can use the same command line params or path settings custom actions as in real install.
Second tip: Google for the InstEd tool , go to the file or component table and you will see the resulting MSI paths in the same static way as with the mentioned VB-script after calling CostInitialize/CostFinalize. For human view such an editor view maybe better.
For automatic testing and improvements or accuracy, you need an own program of course.
For those of you that mentioned snippet given is a good starting point. :-)
The rest of you should live easier with one of the two given methods without programming.

Ennforcing mutually exclusive install on Windows?

We have Product A and Product A'. They're nearly identical and easily confused. For legal reasons, it is necessary to keep these subtly different. For technical reasons, it is not possible for the two to co-exist and run correctly. Therefore, we want to prevent the user from installing Product A if Product A' is already installed, and vice versa.
Is there a best practice for enforcing this on Windows?
My initial thought is to use a different upgrade code for Product A and A' and use this to clue in that the other is installed, but I'm sure there are other approaches and/or best practices.
I believe the Windows Installer package developer is able do this without resorting to custom actions by Using Properties in Conditional Statements.
The LaunchConditions action queries the LaunchCondition table and evaluates each conditional statement recorded there. If any of these conditional statements fail, an error message is displayed to the user and the installation is terminated.
The LaunchConditions action is normally the first in the sequence, but the AppSearch Action may be sequenced before the LaunchConditions action.
The AppSearch action uses file signatures to search for existing versions of products. The AppSearch action can also be used to set a property to the existing value of an registry or .ini file entry.
The first time the Installer finds the file signature at a suggested location, it stops searching for this file or directory, and sets the corresponding property in the AppSearch Table. That property can then be evaluated using Conditional Statement Syntax in the LaunchCondition table.
You could use a custom action to enumerate through the list of installed products.
//using using Microsoft.Deployment.WindowsInstaller;
IEnumerable<ProductInstallation> installedProducts = ProductInstallation.GetProducts(null, null, Microsoft.Deployment.WindowsInstaller.UserContexts.Machine);
foreach (ProductInstallation installedProduct in installedProducts)
{
if (installedProduct.ProductName == "Name of Product A'")
{
// set some property in your installer to indicate the product can't be installed
}
}
I haven't do before, but a solution is to storing a key-value on Windows registry when installing (first time) the product A (or A').
Every time, the installer of A (or A') runs, it checks if that key exists, if true abort the installation, else continue with installation.
Remember if the user uninstall the product, then remove the key in the registry too.
For more info about Windows registry: http://en.wikipedia.org/wiki/Windows_Registry
For information about add, edit, delete keys: http://support.microsoft.com/kb/310516

How to show an image in group template in a LongListSelector

Let's go with the toolkit example of LongListSelector, but say I'd like to display a flag of its country with the city name, in the GroupHeader.
Is this even possible?
If yes, how and what kind of a strategy do you propose? Remember the cities are grouped by name, and the Flag Image path is not part of the key. The Country name and FlagImage could be in a master table or List for lookup.
It appears to that the simplest solution is to add an image (which uses a converter) to the GroupItemTemplate and the GroupHeaderTemplate and have a lookup up in the converter which determines which path to use for the image based on the key. (Option 1)
Alternatively you could use a compound key (of the group name and image path) for the List being displayed but this might require changes to the toolkit code and so is, potentially, more complicated. (Option 2)
I'd be more drawn to the second option as it feels cleaner to me. The first option feels like a bit of a hack, and I only like to use converters when I have to.
The second option requires you to be happy with the potential long term consequences of adding your own customizations to an open source project which could change in the future in ways which could break your changes.

Systeminfo: Product ID

If you type systeminfo at the command prompt, there is a listing for Product ID. Can someone explain what this is exactly, and would it make a good seed for a registration code algorithm?
i believe it's representative (or is) your windows product key. Either way, all you really want is a GUID.
From http://technet.microsoft.com/en-us/library/bb878062.aspx
You can find the product ID of your
installation by right clicking on My
Computer and choose Properties, then
viewing the General tab. You can also
find the product key in the registry
at:
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows
NT\ CurrentVersion\ProductId.
It's a good start. But (IMHO) you can use directly a GUID.
Read more here: http://en.wikipedia.org/wiki/Device_fingerprint (read about Active fingerprinting)

Resources