In my installer I need to do the following: obtain location of the external application from registry and create folders for my application executive and various files in this “parent” directory. I know how to get this directory from registry, but unfortunately the string I get looks like this: C:/Programm Files/Manufacturer/ExtApplication/extapp.exe.
For sure, it does not suit me. So, as I understand, I need to remove the “extapp.exe” part from it. I tried to do it via CustomAction using VBScript or JScript, but I am not experienced enough nether in both these scripts, nor wix itself. I read about Type51, Type 35, etc. CustomAction types, but I cannot get how I should 1) remove the part of the string; 2) use it in Directory element; 3) do all this before the installation of the files.
Will be grateful for any advice (without using Scripts as well).
First, you shouldn't use script custom actions. They are very fragile:
Link
Here is how I would roll:
Use RegLocator/AppSearch to pull the registry value into a property at the beginning of the isntall. Then call out to a C++ custom action that parses off the file name. This custom action should be scheduled before CostInitialize.
You won't use a 51 or a 35 here, you'll use a Type 1 with a call to MsiSetProperty() which is equivilant to Type 51. The difference between 51 and 35 is you use 51 before costing and 35 after costing.
The name of the property you set should match the name of the Id for the Directory table row you are using. Any children of that row will be relative path'd to what you set it to.
Related
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.
I have a WIX installer script and I want to be able to let the user of the resultant MSI file to be able to set the default language, product key and user information during that command line call.
How do I use the command line parameter in my XML file so that its written to the registry?
You can pass property values as a command line parameters to msiexec. If I remember correctly, those should be public properties (names all uppercased). For instance:
msiexec /i myproduct.msi LANG=en PRODUCTKEY=mycompany USER=admin
In WiX code, you can reference those as usual properties, just like if you define them in the code. Note that you should handle the situation when a property value is not passed or passed data is not what you expect. You can either terminate the installation and ask user to provide appropriate data, or live with default values.
In order to write those property values to registry, use the usual approach with RegistryKey/RegistryValue elements.
(if not applicable to SO, please refer to another appropriate place, thanks).
When using the registry to associate file extensions and application, I put in the full filename of my application, but that does not work well, only if I use the 8.3 filename.
for example ( taken from the registry) this works:
[HKEY_CLASSES_ROOT\Toto.Document\shell\myVerb\command]
#="C:\\my\\path\\bin\\Debug\\bin\\myexe_~1.EXE /dde"
[HKEY_CLASSES_ROOT\Toto.Document\shell\myVerb\ddeexec]
#="[myVerb(\"%1\")]"
but this does not work :
[HKEY_CLASSES_ROOT\Toto.Document\shell\myVerb\command]
#="C:\\my\\path\\bin\\Debug\\bin\\myexecutable.EXE /dde"
[HKEY_CLASSES_ROOT\Toto.Document\shell\myVerb\ddeexec]
#="[myVerb(\"%1\")]"
The action is called by right-clicking on the file in Explorer, I get the error :
"Windows cannot find 'c:\users\me\desktop\tata.toto'. Make sure you typed the name correctly, and then try again".
I'm creating the keys programatically with CRegKey and using GetModuleFileName to get the application path.
2 questions :
- I'm probably missing something in my registry entry ? (i've tried quoting the paths, but does not work)
- Can I get the "short" filename ? (searching a little bit seems that GetShortPath should work, but not always!)
Thanks.
Max.
(edit 22/03/2011)
I tried using quotes but it did not work (with /dde)
I decided to use normal parameters instead of /dde and it seems to work nicely with the normal path (not shortened like stated above).
I'm still not certain why when creating a simple MFC SDI project it will write out registry values with the old short name instead of the long name.
Thanks again.
Max.
Try creating the key with another couple of double-quotes (note between .EXE and /dde:
#="C:\\my\\path\\bin\\Debug\\bin\\myexecutable.EXE" "/dde"
I want to present the user with a list of known file extensions for him to pick. I know that these are stored in the Registry under HKEY_CLASSES_ROOT usually like this:
.txt -> (default)="txtfile"
where txtfile then contains the information about associated programs etc.
Unfortunately that place in the registry also stores lots of other keys, like the file types (e.g. txtfile) and entries like
CAPICOM.Certificates (whatever that is)
How do I determine which of the entries are file extensions? Or is there a different way to get these extensions like an API function?
(I don't think it matters, but I am using Delphi for the program.)
There is no guarantee that every keys preceded by a dot in HKEY_CLASSES_ROOT is intended for file association, but every file association requires creation of a key preceded by a dot. See MSDN on File Types topic.
AFAIK, the method I describe here conforms with how the Windows Set File Associations feature works to get a list of all known file types. It was based on my former observation when I delved into this subject.
To achieve that, you'll need to do intricate steps as follows:
Enumerating every keys preceded by a dot . , you can use RegQueryInfoKey() and RegEnumKeyEx() for this purpose.
In every keys preceded by a dot, look at the default value data:
a. If the default value is not empty, this is enough indication that the "preceding dot key" is intended for file association in all Windows NT version, then try to open the key name as mentioned by the value data, just says TheKeyNameMentioned.
a1) If there is subkeys shell\open\command under TheKeyNameMentioned, then test the existence of the path pointed by the default value of this key; if the path exists, there is a default application associated with the extension; if the path doesn't exists, the default application is unknown. To get the file extension description, look at the default value of TheKeyNameMentioned. To get the program description, first, test whether the following key contain a value-name equal to the EXE file path, that is HKCR\Local Settings\Software\Microsoft\Windows\Shell\MuiCache. If it is there, then look at the value data to get the file description; if it is not there, use GetFileVersionInfo() directly to get the file description.
a2) If there is no subkeys shell\open\command under TheKeyNameMentioned, then the default application is unknown. To get the file extension description, look at the default value of TheKeyNameMentioned.
b. On Windows Vista and later, when the point [a] fails, you need additional check. If the default value is empty, test whether the key has a subkey named OpenWithProgIDs.
If OpenWithProgIDs subkey exists, use RegEnumValue() to find the first encountered value name that meets the criteria, that is, the name of the value name must point to an existing key (just says TheKeyNameMentioned.) with the same name as the value name. If TheKeyNameMentioned exists, this is enough indication that the "preceding dot key" is intended for file association. Read point a1 and a2 for the next steps.
If OpenWithProgIDs subkey doesn't exist, the default application is unknown. To get the file extension description, look at the default value of TheKeyNameMentioned.
Hope that helps. :-)
For a command-line alternative, the assoc command-line program included in Windows shows registered file extensions.
c:\> assoc
.3g2=VLC.3g2
.3gp=VLC.3gp
.3gp2=VLC.3gp2
.3gpp=VLC.3gpp
...
I'm not sure which verb this looks for. Open perhaps? I'm also not sure which extensions will appear in this list. Perhaps the extensions of files that can open from the command line.
To then find out which executable is mapped to each file type, the ftype command will tell:
c:\> ftype VLC.3g2
VLC.3g2="c:\vlc.exe" --started-from-file "%1"
IMHO - all those registry subkeys starting with the dot (.) - are for file extensions.
For instance in your case .txt stands for the "txt" extension, whereas txtfile doesn't start with the dot.
I don't know the details, but it seems you could use the IQueryAssociations interface.
I have to build a GUI application on Windows Mobile, and would like it to be able user to choose the language she wants, or application to choose the language automatically. I consider using multiple dlls containing just required resources.
1) What is the preferred (default?) way to get the application choose the proper resource language automatically, without user intervention? Any samples?
2) What are my options to allow user / application control what language should it display?
3) If possible, how do I create a dll that would contain multiple language resources and then dynamically choose the language?
For #1, you can use the GetSystemDefaultLangID function to get the language identifier for the machine.
For #2, you could list languages you support and when the user selects one, write the selection into a text file or registry (is there a registry on Windows Mobile?). On startup, use the function in #1 only if there is no selection in the file or registry.
For #3, the way we do it is to have one resource DLL per language, each of which contains the same resource IDs. Once you figure out the language, load the DLL for that language and the rest just works.
Re 1: The previous GetSystemDefuaultLangID suggestion is a good one.
Re 2: You can ask as a first step in your installation. Or you can package different installers for each language.
Re 3:
In theory the DLL method mentioned above sounds great, however in practice it didn't work very well at all for me personally.
A better method is to surround all of the strings in your program with either: Localize or NoLocalize.
MessageBox(Localize("Hello"), Localize("Title"), MB_OK);
RegOpenKey(NoLocalize("\\SOFTWARE\\RegKey"), ...);
Localize is just a function that converts your english text to a the selected language. NoLocalize does nothing.
You want to surround your strings with these values though because you can build a couple of useful scripts in your scripting language of choice.
1) A script that searches for all the Localize(" prefixes and outputs a .ini file with english=otherlangauge name value pairs. If the output .ini file already contains a mapping you don't add it again. You never re-create the ini file completely, your script just adds the missing ones each time you run your script.
2) A script that searches all the strings and makes sure they are surrounded by either Localize(" or NoLocalize(". If not it tells you which strings you still need to localize.
The reason #2 is important is because you need to make sure all of your strings are actually consciously marked as needing localization or not. Otherwise it is absolutely impossible to make sure you have proper localization.
The reason for #1 instead of loading from a DLL is because it takes no work to maintain this solution and you can add new strings that need to be translated on the fly.
You ship the ini files that are output with your program. You also give these ini files to your translators so they can convert the english=otherlanguage pairs. When they send it back to you, you simply replace your checked in .ini file with the one given by your translator. Running your script as mentioned in #1 will re-add any missing translations if any were done while the translator was translating.