How does Windows find the installed location when uninstalling software - windows

When I run an installer that allows a custom install location/path, the files will be correctly placed at the location that I select.
When I run the same MSI and select remove (or uninstall from add/remove programs), how does it know the install location so the correct files are removed?
I thought it would be stored at 'Computer\HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall{GUID}', but when I look at that location for my installed software, the 'InstallLocation' key is empty.
However, no matter how I uninstall it, it knows which folder to go remove, no matter where I put it. Is that information stored elsewhere in the registry, or in the MSI file itself?

This is a very complicated question as MSI can be configured to drop its uninstall files anywhere you tell it. Usually by default though it will create an uninstall .msi file with a specific name in C:\Windows\Installer.
But don't depend on the uninstall .msi being placed in this directory and don't rely on there being an uninstall path in the Uninstall registry key. This key is as much about convenience for the end-user as anything else.
The uninstall information is usually contained within the MSI file, but it need not be and during installation it can create keys to aid upgrading and uninstallation. The information that an installation will leave in the registry is entirely down to how you configure the .msi database.
Adding a few more things... many installers like Nullsoft, InstallAware and InstallShield like to do their own stuff and put their uninstall information in other places. So InstallShield likes to create an InstallShield Installation Information folder and Nullsoft likes to create .dat files and an uninstall.exe. But beyond all this, these installers are still invoking MSI and creating installation tables and database. So where the uninstall information is actually located it not an exact science!

UPDATE:
Find Component's Installation Location: Is there way to detect install location without uninstall registry nor C:\Windows\Installer?
Implementation Details: How MSI stores these things are implementation details that should not be meddled with, attempted modified or used directly for any purpose - just so that is clear. You should go through the MSI API which is implemented as Win32 functions with complementary COM wrappers for access via scripting languages.
Registry: The MSI database is stored mostly in the registry, but there also components on disk - some of which you refer to - for example %SystemDrive%\Windows\Installer (a super-hidden folder that should not be modified in any way). The MSI database is stored in numerous locations throughout the registry:
HKCR\Installer
HKCU\Software\Microsoft\Installer
HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Installer
HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall
HKLM\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall
HKLM\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Installer
Etc...
Some of these are real, some are aliases, some are merges. It is all a bit fuzzy. Again: implementation details - a well-known euphemism for all of us to: "give up right now, will you"? :-). Just apply the MSI API to acquire the information you need.
MSI API: A lot of stuff to read above to get to the point, go via the MSI API to get your information on directory resolution. What we have to do is a bit exotic, we have to spin up a session object for the installed product and run two standard actions (built-in MSI actions from Microsoft) in order to resolve the directory table and installation directories of the MSI in question (about "costing"). Below is a practical sample:
For the record:
How can I find the product GUID of an installed MSI setup?
How can I compare the content of two (or more) MSI files? (on Orca and other, free MSI tools)
Uninstalling an MSI file from the command line without using msiexec (a myriad of ways to uninstall MSI packages)
Set installer = CreateObject("WindowsInstaller.Installer")
' Other test product codes: {2F73A7B2-E50E-39A6-9ABC-EF89E4C62E36}
productcode = Trim(InputBox("Please paste or type in the product code you want to look up details for:", _
"Find Product Details (test GUID provided):", "{766AD270-A684-43D6-AF9A-74165C9B5796}"))
If search = vbCancel Or Trim(productcode) = "" Then
WScript.Quit(0)
End If
Set session = installer.OpenProduct(productcode)
' Crucially, resolve the directory table and properties by running "MSI Costing"
session.DoAction("CostInitialize")
session.DoAction("CostFinalize")
' Can be any directory property from the Directory table in the MSI:
MsgBox session.Property("INSTALLFOLDER")
' Open the MSI in Orca to find the directory folder property names
Throwing in a link to an old answer on how to list tables inside an MSI file.
Resolve All: Got a bit carried away and made one more update to resolve ALL directories for any installed package. Here is a script (not tested much):
' https://stackoverflow.com/questions/17543132/how-can-i-resolve-msi-paths-in-vbscript
' On Error resume Next
Set installer = CreateObject("WindowsInstaller.Installer")
' Other test product codes: {2F73A7B2-E50E-39A6-9ABC-EF89E4C62E36}
const READONLY = 0
Dim DirList
productcode = Trim(InputBox("Please paste or type in the product code you want to look up details for:", _
"Find Product Details (test GUID provided):", "{766AD270-A684-43D6-AF9A-74165C9B5796}"))
If search = vbCancel Or Trim(productcode) = "" Then
WScript.Quit(0)
End If
Set session = installer.OpenProduct(productcode)
session.DoAction("CostInitialize")
session.DoAction("CostFinalize")
set view = session.Database.OpenView("SELECT * FROM Directory")
view.Execute
set record = view.Fetch
Do until record is Nothing
ResolvedDir = session.Property(record.StringData(1))
DirList = DirList + record.StringData(1) + " => " + ResolvedDir + vbCrLf
set record = view.Fetch
Loop
' Dismiss dialog with ESC key if it falls off screen
WScript.Echo DirList ' Use WScript.Echo due to MsgBox restrictions (number of characters)
Links:
How can I resolve MSI paths in VBScript?

Related

Change the value of a MSI property loaded from a DLL using a MSI transform

I need to modify the installation behavior of a MSI setup for "IBM i Access for Windows". The setup was created using InstallShield. During the installation the setup triggers two other MSI installations through "chaining". The parameters passed to MSIEXEC.EXE to execute those two installations get loaded by a custom action from a DLL that is included with the installation. The parameters end up in a MSI property.
I want to change the value in that property to manipulate the command line before the chained installation gets launched. Is this possible? If so, how? I have no problem to create an external DLL that reads and modifies the property, but I am at a loss on how to integrate this with the existing installation -- which tables do I have to modify and how, where should I put the DLL, ...
EDIT 1: To clarify this: I want to modify the parameters passed to the chained MSI installations. They are independent from the parameters I pass to the main installation and are loaded from a DLL that is part of the installation.
EDIT 2: I have uploaded the plain MSI + the relevant log file. I start the main installation with "/qn" to suppress all messages. That works without problems, the chained installations get executed without visible prompts. The problem arises when uninstalling the software (again with "/qn"). The remote custom action gets loaded from a DLL (line 6417):
MSI (s) (10:28) [09:00:45:643]: Invoking remote custom action. DLL: C:\Windows\Installer\MSIA4BD.tmp, Entrypoint: ISChainPackages
The command line loaded from the DLL specifies to call MSIEXEC.EXE with the parameter "/qb" instead of "/qn" (line 6958):
MSI (s) (10!60) [09:00:46:033]: PROPERTY CHANGE: Adding IS_CHAINER_POST_COMMANDLINE property. Its value is '/l"c:\temp\IBM_iAccess_7.1_Uninstall.log" /qb /x{CCA40632-843E-48C6-B14F-E1070015D87C} ...
And because the MSI installer has a lock on a file a messagebox pops up triggered by the uninstallation of the chained MSI (line 44046):
MSI (s) (10:C0) [09:01:05:553]: RESTART MANAGER: Did detect that the custom action server with process ID 2352 holds file[s] in use, so a reboot will be necessary.
MSI (s) (10:C0) [09:01:05:553]: Note: 1: 1610
MSI (s) (10:C0) [09:01:11:224]: RESTART MANAGER: The user chose to go on with the installation, although a reboot will be required.
The setup must update files or services that cannot be updated while the system is running. If you choose to continue, a reboot will be required to complete the setup.
The installation files for version 7.1 of this software are no longer available on the IBM website. Only the current version is, and I have not looked into whether the problem still exists with the latest version or not as I have been asked to package v7.1 by the business department.
One approach to this problem would be to create a new Custom Action that executes VBScript code stored in the Binary Table and place the new Custom Action right after ISChainPackagePrepare. The VBScript code will read the value of IS_CHAINER_POST_COMMANDLINE and replace it as specified in the Replace() function
The VBScript Code could look like this:
Option Explicit
Function ReplacePropVal()
dim propvalue
dim newvalue
propvalue = Session.Property("IS_CHAINER_POST_COMMANDLINE")
newvalue = Replace(propvalue,"/qb","/qn")
Session.Property("IS_CHAINER_POST_COMMANDLINE") = newvalue
End Function
You want to give your new Custom Action a Type of 6 to indicate that your Custom Action data is stored as a VBScript in the Binary table. Your Custom Action Source is a reference to the Name in the Binary Table. The Target value of your Custom Action will need to be the name of the VBScript Function which is ReplacePropVal in this case
Afterwards, you place your new Custom Action in the InstallExecutionSequence table using the same name for it as in the CustomAction table. Also make sure to give it a higher Sequence value as ISChainPackagePrepare. I would recommend placing it right after by incrementing the Sequence value of ISChainPackagePrepare by 1.
After you've changed the tables and generated a new transform, just run the package with the new transform being applied by specifying its path in the TRANSFORMS public property and your property value should be changed.
I think the ideal approach here would be to transform the chained package definition. The UI level (documentation) is stored in the first two bits of the Options column of the ISChainPackage table, so all your transform should have to do is alter that value. In particular, you can change those bits from ecoUIBasic (0) to ecoUINone (1), which should be as simple as adding 1 to the current value. Also available are ecoUIReduced (2) and ecoUIFull (3).
If ISChainPackage.Options is altered correctly, the desired IS_CHAINER_POST_COMMANDLINE will be generated for you, and you won't have to add a secondary custom action to alter the /qb to /qn afterward. (Kudos to sevi for suggesting that functional workaround.)
If this is an Advanced or Suite UI Setup.exe, please check that
link for how to pass a property.
Package Database Entries (Software Re-Packaging tips for iAccess and other software):
https://www.itninja.com/company/browse/i - look at the IBM entries
IBM i Access for Windows
IBM iAccess for Windows 7.1
How to perform silent upgrade for IBM I Access for Windows 7.1?
Making a silent package for IBM i Access for Windows 7.1 with latest patch
Approaches: What does this DLL custom action do? Does it create a license key? Often these things have been found and solved many times before. To check for this, I usually use these approaches to find solutions:
File Extraction: try extracting files from the setup and look for help files that describe proper deployment. "Large Scale Deployment.chm", "Installation Command Line Parameters.chm", etc... or ready-made transforms or command line file samples (Install.cmd).
Deployment Sites: Check https://www.itninja.com/company/browse/i (Software Re-Packaging tips - look at the IBM entries. Several entries that look relevant, here is one).
Forums: inspect their support forums or online support - if available.
Phone: get on the phone with the vendor. Sometimes very helpful, often a waste of time. Ask for deployment relevant information sent from support. Do this if you have a support agreement?
See section on file extraction below.
Setup.exe Switches: I have a similar or related answer here, where I also mention setup.exe command line switches: Silent run installer (.exe) with parameters on Windows.
Logging: If the custom action does not create something dynamic (unique license key, machine locking identity, etc...), then you can try to find what was generated by logging the setup and looking for the command line used in the log file. Mock-up sample:
MSI (s) (AC:00) [00:00:00:00]: Command Line: TARGETDIR=C:\ SHORTCUTDIR=C:\Documents and Settings\All Users\Start Menu\Programs\Test ACTION=INSTALL
File Extraction: Is this an Installshield Suite project? did you extract the embedded files and MSI files first?: Programmatically extract contents of InstallShield setup.exe.
What is in a Setup.exe?: Installshield setup.exe files can be lots of different things (explanation of different setup.exe flavors): Regarding silent installation using Setup.exe generated using Installshield 2013 (.issuite) project file.
Links:
Extract MSI from EXE
What is the purpose of administrative installation initiated using msiexec /a?

Adding an external CAB to an MSI with an internal one

I have a visual studio installer (vs2015) that installs an application. I want it to also install a set of configuration files, the contents of which vary by physical install location, that will be delivered as a cab file in the same directory as the msi. The cab has a known set of files that will be distributed across 2 folders in the install location and is created by a different project than the installer. How do I get the msi to install both its internal contents and the contents of the external cabinet file?
Wise Package Studio
I actually successfully updated MSI files with new files to install using Wise Package Studio back in the day. It added a CAB file to the Cabs table (I don't know if this table is actually a Wise "view" or a real MSI table - it seems missing from Orca if all CABs are embedded in the MSI). There is a corresponding entry in the Media table where the LastSequence value (in the Media table) describes which CAB contains what files. In the File table you will find a field called Sequence which specifies the "order" of the files listed. Essentially your new files will be at the end of the "order" and hence in a new CAB. A bit involved the whole thing.
However, I successfully got Wise Package Studio to both embed a new CAB file, and to use an external CAB file to install the new files during installation, but I don't have the procedure documented and don't recall all the steps. Moreover I don't recommend the procedure - in fact I would never use this approach now. It was just used at the place I worked at the time. In most cases we used a transform to add this content to the main package, rather than hacking the MSI itself.
MSI SDK: Including a Cabinet File in an Installation
The procedure to add a CAB file to your MSI is documented in the MSI SDK here: Including a Cabinet File in an Installation. Quite involved - as I said, but definitely possible. As you will see the lack of a # flag at the start of the CAB name in the Media table indicates an external CAB file.
So I suppose, in short:
Add a new entry to the file table, set the Sequence number to +1 from the highest sequence File entry already there. I would add a new, corresponding entry in the Component table as well.
Add a new row to the Media table, specify the number you set for the file in the LastSequence column. Add the name of your cabinet file to Cabinet. Make sure to not prefix the CAB name with #.
Wise would also add entries in the MsiFileHash table. Not sure if this is required or not. Pretty sure it is not required to add entries here.
As you will see in the linked MSI SDK article, you can embed the whole cab following the last few steps listed in the linked MSDN content.
I wish I had time to test all this, but I don't. Which brings me to the next point:
Default / Instantiate Your Settings?
When I see questions like these I invariably ask myself: what is in these files? Are they "trivial settings" that could actually be defaulted instead of hard coded in config files? This has saved me a lot of work, many times.
The deployment of settings and data files have always been problematic with both MSI and legacy style installers. My philosophy of deployment for such files is to treat what you install as "read-only" and then you copy them to per-user locations or set them in HKCU on application launch, using some sane process of obtaining appropriate values (from settings written to HKLM during installation, retrieved from the user, retrieved from the Internet, etc...). Please see this longer answer on the subject: Create folder and file on Current user profile, from Admin Profile. The best approach, in my opinion, is to retrieve settings on launch from an online database (as you will see if you read that linked content), but that is involved.
It's not clear precisely what you mean, but you cannot alter the MSI to unpack all the files in your separate CAB as if they were in the MSI as the other files are. There is too much internal data on the files inside the MSI file, in the file table, component table, and so on.
So if the CAB file is in the same location as the MSI file then you could create a custom action to copy it to the target system, and unpack if you want. The copy can be told where the MSI location is by using the [SourceDir] property or the [OriginalDatabase] property OriginalDatabase property
You'd parse that location to get the path and then do the copy to the TARGETDIR location.

Visual Studio Setup Project - Shortcuts cause installation rollback

I have a project with lot of plugins and config files for them. Now I am doing a Visual Studio Setup Project for it.
I don't want to add each config file manually to the setup project, so I thought to do this:
create an empty zip file, say config.zip, and add it to the setup project
Add a pre-build action to zip all the config files into config.zip
Add a custom action that runs a vbs script that unzip config.zip to the right folder and deletes it.
The vbs script is the following:
sArchiveName = "Config.zip"
sLocation = "C:\Data\Configurations"
Set oFSO = CreateObject("Scripting.FileSystemObject")
Set oShell = CreateObject("Wscript.Shell")
oShell.Run """" & s7zLocation & "7z.exe"" x " & sLocation & "\" & sArchiveName & " -aoa -o" & sLocation, 1, True
'--- If I uncomment the following 2 lines,
'--- as I click on the shortcuts the installation rollbacks.
'--- If I leave them the shortcuts work fine.
'Set f = oFSO.GetFile(sLocation & "\" & sArchiveName)
'f.Delete True
My problem is that the shortcuts that I add in the programs menu causes the rollback of the installation. The reason is the deletion of config.zip at the end of the installation process. If I leave it everything works fine.
I have googled for a solution but cannot find anything, can someone help me?
This seems to be a self-repair problem.
Self-repair is a core Windows Installer feature which checks that your software is correctly installed whenever you launch it via an "advertised shortcut" (essentially a special type of shortcut that points to a Windows Installer feature and not directly to a file - see more information in the link provided).
Windows Installer tracks your zip file and discovers that it is missing, and hence correctly triggers a repair of your installation.
There is no good fix for this, because your deployment design is not sound. MSI is specifically designed to behave this way, and you can not disable this "self-repair" feature. Once you install the zip file it is tracked.
If you really want to understand self-repair there is a long answer here, but it is generally too detailed to understand without some prior idea of what it is and how it works. Still adding the link for reference: How can I determine what causes repeated Windows Installer self-repair?
The easiest way to deal with this is to install the config files properly via your MSI file, and remove the whole zip file. Will these config files be updated after installation, or will they be treated as read-only?
Visual Studio Setup Projects are very basic. The recommended way to create MSI files these days is by using the WiX toolkit. This is a new framework for creating MSI files (Windows Installer files), and it allows you to author the installation as an XML file that is then compiled into an MSI binary. It is a very elegant toolkit, but there is a learning curve.
Perhaps check this answer for more context on WiX: Wix generate single component id for entire tree. If you download and install the WiX toolkit and the Visual Studio plugin you will get new project types that allow the creation of a WiX XML package file.
Here is an answer on the "origin of WiX": Windows Installer and the creation of WiX. In essence the rationale behind its creation.
Those symptoms almost certainly mean that your custom action is failing, so the install will roll back. You'd need to post your VBScript for us to have a look.
A verbose log should show the place that the script is failing. Do an msiexec /I [path to msi file] /l*vx [path to a log file]
A common problem in VBScript custom actions is to use the WScript object, such as WScript.CreateObject. This will fail because the WScript object is provided when running in the WSH environment, but that's not happening during a call from Windows Installer.
In the script you posted, s7zLocation seems to be uninitialized.
Also, note that the code is being called from msixec.exe, running with the system account, and does not have any of the infrastructure that would see if you were running from an interactive user explorer environment (such as working directory). You need to specify the full path to all executables and files being used.

Running a VBScript from an MSI installation

I am running a VBScript as a Custom Action at the Commit part of an MSI installation. The script calls an .exe that installs drivers for a ZB device. What I want to do is check the file system first to see if the drivers are already there and skip the installation if they are.
So far the script looks like this:
Sub Run(ByVal sFile)
Dim shell
Set shell = CreateObject("WScript.Shell")
shell.Run Chr(34) & sFile & Chr(34), 1, false
Set shell = Nothing
End Sub
Set objFSO = CreateObject("Scripting.FileSystemObject")
IF objFSO.fileExists("c:\windows\system32\drivers\ftser2k.sys") THEN
MsgBox("You already have the drivers installed.")
ELSEIF objFSO.fileExists("c:\windows\system32\ftserui2.dll") THEN
MsgBox("You already have the drivers installed.")
ELSE
Run Session.Property("CustomActionData") & "CDM20600.exe"
END IF
These files do exist on my machine. So if I double click the vbs file I get the MsgBox coming saying that I already have the file. However, when I run the msi installation, no matter what it installs the driver as if the first two conditional statements weren't even there. I did read that you cannot use the WScript object in MSI, so I took out the WScript.Echo lines and replaced them with MsgBox. I was wondering if maybe you can't use the FileSystemObject in msi either.
My ultimate goal is not to have any message come up. I just want the driver install to be skipped if the files are present on the system. The messages are there just for debug purposes right now.
If it helps, the msi package was built in Visual Studio 2010. Also the CustomActionData is the TARGETDIR.
I am new to both VBScript and install packages, so please be gentle :)
I have to be honest, I have many concerns about your proposed solution:
1) VB/JScript CA's Suck. I would read the link and take it to heart.
2) I've seen many machines in my career where the FSO was broken.
3) You've hard coded the path to System32 instead of using SystemFolder or System64Folder.
4) Commit custom actions don't execute when rollback is disabled.
5) You are running double out of process with no error logging of the EXE call.
6) Visual Studio Deployment Projects suck in so many ways that I can't count. Evidence is that Micrsoft has killed them in Visual Studio 11.
If it was me, I'd ask if you have to use this EXE to install the driver package or if there's an INF file to go along with the SYS/DLL files. If so, I'd look at create a WiX merge module that uses the DifxAppExtension. This allows you to encapsulate the behavior of driver installation in a discrete module and then add it to your VDPROJ installer or even better a WiX or InstallShield Limited Edition ( free ) installer.
Here are several blog articles that should help you understand what I mean:
Augmenting InstallShield using Windows Installer XML - Certificates
Augmenting InstallShield using Windows Installer XML - Windows Services
Redemption of Visual Studio Deployment Projects

Freeware tool for creating a single file installer from an msi file + config file (thats easy enough for an End-User to use)

My installer requires there be two files in the same directory in order for it to install.
The installer (.msi file)
An organization specific config file that the installer copies. (This file is customized by the organization and then distributed to it's end users).
Since there are two files, the file has to be distributed as a zip file. Which presents the issue of if a user tries running the .msi without actually extracting the zip... only the msi file gets extracted. I am able to detect the issue in the install process and tell the user they need to unzip the file... but you know how noone actually reads error messages.
So, I'd like to make it more foolproof and so i was wondering if there was a simple tool that i could let my customers (ie the organization) be able to make modifications to the config file and when finished create a .exe file which when clicked would extract to a temp folder and then run the msi. I know there are solutions for this which require commercial software. I'm wondering if a simple freeware tool exists that can do this.
Edit: Accepted Solution Notes:
The one issue i ran into is the iexpress wasn't designed to be used for .msi files. As a result on the step that asks you for the Install Program. It's a combo box which if you had added a .exe file in the previous step could just select the .exe file from. Instead you have to manually type in
msiexec /i yourinstaller.msi
I was very pleased to find such a simple solution that's built in to windows. The only way this could be better is if it allowed for wildcards so that your iexpress project would be able to handle changes in the msi file's name which occur with each version. And defaulting the Install Program to the .msi file. These minor inconveniences are offset by the fact that end user wouldn't need to install any new software to create the package so I have stopped looking for other tools.
You could try using iexpress.
It enables you to package up a set of files which can be extracted, with the option of running an installation command automatically after extraction. It also has options to enable you to prompt users about things, show a EULA, restart the computer, etc..
I believe it comes as part of Windows (part of IE?) - try running iexpress.exe from the run dialog to get the UI.
The Wix project has a bootstrapper and packager for dealing with this kind of thing.
I've used wix a lot but haven't really looked at the bootstrapper/packager much - last time I had a quick look it wasn't really usable but that was a long time ago so it may be better now.
I'm guessing that the config file is something like a properties file, and that you want users to set the values of the properties "foo" and "bar". You don't need a separate tool to update the file.
I would do this:
Put one or more dialogs in the install that ask the user what the values of foo and bar should be, and set a couple public properties accordingly.
Write a custom action that writes the config file out to whatever location you want, including whatever values you want for foo and bar. This would be pretty easy in vbscript.
Put the custom action somewhere in the execute sequence (ideally as a deferred execution action, since you're making changes to the system).
Add an entry to the RemoveFile table, so the config file is removed on uninstall (assuming you don't want it to be left behind.)
Add an entry to the LaunchCondition table, to prevent users from doing a silent install. Or if you want silent install to be allowed, make the names of the public properties that hold the config data known, and make them part of the LaunchCondition. You would block "msiexec /i myapp.msi", but you could choose to allow "msiexec /i myapp.msi FOO=Something BAR=SomethingElse".

Resources