Visual Studio Setup Project - Shortcuts cause installation rollback - visual-studio

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.

Related

How does Windows find the installed location when uninstalling software

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?

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

Make Windows Installer ignore a running process

Using Installshield 2010 and Basic MSI project.
I have an exe that was previously installed by my installer. That exe needs to be running during an installer upgrade. Is there a way to guarantee that the installer won't try shutdown the process? Basically, I would like the behavior to be : If file doesn't exist, lay it down, otherwise ignore it.
I have made the exe a key file in a component and set it 'Never Overwrite' to true. Should this give me my desired behavior ?
Never Overwrite will be used by future installers to determine if the file will be overwritten or not by other MSI packages. Basically, this attribute should have been set for the installed EXE.
A good approach is to use a file search to determine if the EXE exists. The search property can then be used to condition the new component.
Windows Installer doesn't automatically close applications, but it does show a FilesInUse dialog which offers this option to the user.

How to find the setup location in an VBScript custom action for InstallShield?

In an InstallShield project I have a VBScript custom action that conditionally needs to execute a certain file packaged with the install.
Normally I get the current directory of a vbs using code such as
sCurPath = CreateObject("Scripting.FileSystemObject").GetAbsolutePathName(".")
Which, if it returned the location of the Setup.exe that initiated the install, should work.
However when running the install, the current path (on XP) is C:\Windows\system32 instead of the location of the Setup.exe file I was expecting.
Assuming the output of my InstallShield build looks like the following
Disk1
->Setup.exe
->ISSetupPrerequisites
-->Req1
-->Req2
-->...
->OtherReqs
-->ConditionallyRunMe.exe
How could I run "\OtherReqs\ConditionallyRunMe.exe" from a VBScript custom action?
Thanks!!
(Note: I realize there are ways to conditionally run exe files from withing InstallShield, but in this case the requirements are not supported by InstallShield - unless there is a way to use a VBScript custom action return value as a condition to run another file?)
After a lot of messing around, I got it (actually found it in the InstallShield manual, and not Google, go figure :))!
This line of VBScript does the trick
disk1Path = Session.Property("SETUPEXEDIR")
The line above points to where ever the setup exe file was, so from there it's trivial to run any exe included with your install media.
Other useful ones I found, which I'll past here for reference are
'points to app data\downloaded install directory
MsgBox Session.Property("SourceDir")
'where the software wants to install to on the users system
MsgBox Session.Property("INSTALLDIR")
Not sure why it's so hard to find a good reference on MSI Standard properties (even just a list). The closest I found was this, but not all of them work (and not specifically for InstallShield at all). If anyone finds a good link with documentation about MIS Standard properties and their description please add a link here, so no one has to waste as much time on this as I did :).

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