Can someone help me to build a custom action in MSI which will copy itself after successful installation to some X location.
I have already seen it can be done using .exe but I want to do it only with CA.DLL (C#) as this exe will be an overhead.
Here's an example VB script that will find an installed product by name and copy the cached copy of the MSI. This will work on Windows 7 and later, as the full MSI is cached and any embedded cab files remain in the MSI. You just get the MSI without the payload on older systems.
Dim installer, products, product, productCode
Set installer = Wscript.CreateObject("WindowsInstaller.Installer")
For Each productCode In installer.Products
If InStr(1, LCase(installer.ProductInfo(productCode, "ProductName")), LCase("My Product Name")) Then Exit For
Next
If IsEmpty(productCode) Then Wscript.Quit 2
Set products = installer.ProductsEx(productCode, "", 7)
filesys.copyFile products(0).InstallProperty("LocalPackage"), "c:\path\to\newcopy.msi"
Related
I'm new with Wix, let me give some info about what I'm trying to accomplish. I have an installer for software "B" but this software needs to have software "A" to actually work. So I want to add on the installer for software "B" to check if software "A" is installed, if it isn't then to show a message and then exit the installer.
So below is the code of what I have tried but the message always shows even though the file is there. So I'm basically looking for a file from software "A", if its present then the install should continue normally, if it isn't then the warning message should be displayed and exit installer.
<Property Id="SOFTWARE_A_INSTALLED">
<DirectorySearch
Id="LocationFile"
Path="C:\Windows\Microsoft.NET\assembly\SOFTWAREA">
<FileSearch Name="A.dll"></FileSearch>
</DirectorySearch>
</Property>
<Condition Message="[ProductName] requires SOFTWARE A installed.">
<![CDATA[Installed OR SOFTWARE_A_INSTALLED]]>
</Condition>
Thanks
There are usually much better ways of doing this. If that product is an MSI file then use an Upgrade element to detect the product's UpgradeCode, or use the Component ID of that assembly to do a component search. Or maybe the product creates a registry key that you can search for.
In general I don't recommend your approach because you said that your product B requires A for it to work. You didn't say that your INSTALL requires A for the install to succeed, so you are creating an install order dependency when there is actually only a product dependency. So what does your product do if A is uninstalled? Crash? Give any kind of warning? A better solution might be for your app to do the check rather then create a required install order.
If that assembly from A is really a dependency of your app and it is just one of a few files you depend on, then maybe it should be a redistributable, available as something such as a merge module. People don't (for example) check if Crystal Reports files are on the system - they just include the merge module that includes the files and installs them in a way that multiple users of a system can all share the same files. The same is true of many other shared files.
This vbscript will enumerate installed component ids to check that you have the right values for a component search:
Option Explicit
Public installer, fullmsg, comp, a, prod, fso, pname, ploc, pid, psorce
Set fso = CreateObject("Scripting.FileSystemObject")
Set a = fso.CreateTextFile("comps.txt", True)
' Connect to Windows Installer object
Set installer = CreateObject("WindowsInstaller.Installer")
a.writeline ("MSI Components")
on error resume next
For Each comp In installer.components
a.writeline (comp & " is used by the product:")
for each prod in Installer.ComponentClients (comp)
pid = installer.componentpath (prod, comp)
pname = installer.productinfo (prod, "InstalledProductName")
a.Writeline (" " & pname & " " & prod & "and is installed at " & pid)
Next
Next
I have a DLL named FSSetup.dll that I want to call from InstallScript code. And I want to call the InstallScript code from an MSI custom action. But there seems to be a problem in that I don't know where to find FSSetup.dll. The InstallScript function is starting and the whole install aborts when it gets to the line where it calls the DLL function. The MSI log reports:
MSI (c) (1C:3C) [09:39:52:261]: Invoking remote custom action. DLL: C:\Users\bmarty\AppData\Local\Temp\MSIBEAE.tmp, Entrypoint: f1
Action ended 9:39:55: BrowseFSKeyFile. Return value 3.
Info 2896. Executing action BrowseFSKeyFile failed.
Action ended 9:39:55: InstallWelcome. Return value 3.
The InstallScript code looks like this:
export prototype BrowseFSKeyFile(HWND);
prototype FSSetup.FSBrowseFile( BYVAL STRING, BYVAL STRING, BYVAL STRING, BYREF STRING );
function BrowseFSKeyFile(hMSI)
NUMBER nKeyFile;
STRING svKeyFile, szKPath, szKFile, szTemp, szTemp1;
begin
MessageBox("Hi", INFORMATION);
svKeyFile = "";
FSBrowseFile( "*.key", "key", "Please select a KEY File...", svKeyFile );
MessageBox("Bye", INFORMATION);
...
end;
I see the "Hi" message, but the install aborts before "Bye". I suspect it's because FSSetup.dll doesn't exist in my %temp% directory where MSIBEAE.tmp resides, which presumably represents the extracted name of the ISSetup.dll that was generated by the InstallScript compile.
Edit:
The MSI debugger is showing a different value for SUPPORTDIR than the InstallScript is seeing. FSSetup.dll exists in the SUPPORTDIR provided by MSI, but not in the one seen from InstallScript.
You are missing a call to UseDll (and UnUseDll). In order to call UseDll, you will need to know the location of the DLL in question. If you have added it to the support files of the MSI project, you will need to retrieve the Windows Installer Property SUPPORTDIR to find this location - that's where Christopher Painter's answer comes into play - and then call e.g. UseDll(szSupportDir ^ "FSSetup.dll") before calling FSBrowseFile.
The SUPPORTDIR behavior was changed about 9 years ago when fundamental design flaws of InstallScript were fixed in InstallShield 12.0. See:
InstallShield 12 Beta2
and
InstallScript, meet CustomActionData
and
Upgrading Projects to InstallShield 12 (search for SUPPORTDIR)
Basically you need to do call MsiGetProperty() to get SUPPORTDIR. Also you need to pass this to your deferred custom actions as CustomActionData.
I can create MSI via WIX -> we installed it on IIS. What is happening - we had some version of application installed already on let's say 1.8, then we installed version let's say 99.0 just for testing purposes, then we uninstalled this 99 version. Then i tryed to install other version and obtained: A newer version of the product is already installed.
Then i tryed following changing upgrade code of product - and make high version again, then uninstall and install lower version - and it worked fine.
So i feel i missing something - additional information is that in programs and features list i cannot find that higher application after uninstall - then my question is how installer evaluate that there is newer version? where exactly are informations about what is installed(and are used for comparison) stored and how to effectively and easily access them? so i can look straight on it?
ProductCode identifies a particular product. It changes every time you ship a new replacement product.
UpgradeCode defines a series of products by using the same UpgradeCode in a updated products whose versions are expected to continually increase. By default, new product versions replace older product versions with a major upgrade. Because upgradecode defines a product series, Windows will look for products with the same UpgradeCode because identical UpgradeCodes means mutually exclusiv products, using them to replace an older product with a new one. In WiX, major upgrade is done with the majorupgrade element which it appears you may be using because you get that "a newer version is installed" message. There is an AllowDowngrade option there if you want to "upgrade" to a lower version.
Product versions (like file versions) are not just useful information - they are used by the system with the understanding that new replaces old and generally it is a bad thing to go back to lower versions, that's why the default behavior disallows downgrades.
This script might help. It uses the Windows Installer scripting API to enumerate all the installed products, showing version, user sid, ProductCode etc:
Option Explicit
Public installer, fullmsg, comp, prod, a, fso, pname, ploc, pid,contxt, sid, psorce, pcache, pvers
Set fso = CreateObject("Scripting.FileSystemObject")
Set a = fso.CreateTextFile("prodex.txt", True)
' Connect to Windows Installer object
Set installer = CreateObject("WindowsInstaller.Installer")
a.writeline ("Products")
'on error resume next
For Each prod In installer.ProductsEx("", "", 7)
pid = prod.ProductCode
contxt = prod.Context
sid=prod.usersid
pname = prod.InstallProperty("ProductName")
psorce = prod.InstallProperty("InstallSource")
ploc =prod.InstallProperty("InstallLocation")
pcache = prod.InstallProperty("LocalPackage")
pvers=prod.InstallProperty("VersionString")
a.writeline (pid & " " & pname & " " & pvers & " installed at <" & ploc & "> from " & psorce & " Context " & contxt & " Local " & pcache)
Next
I am using WiX to create a moderately complex installer. I have a Level 0 Feature that I would like to install conditionally, but also always during an Admin install, to make patching through admin images easier. I've tried several things:
Setting a property before the AdminUISequence's CostFinalize action (which is when msdn claims the Condition table is evaluated). The condition in that case looks like:
<Condition Level="1">( VersionNT >= 601 AND NOT FASTINSTALL = "1" ) OR INSTALLCONDFEATURE = "1"</Condition>
where FASTINSTALL and INSTALLCONDFEATURE are both Secure and Admin.
Using the Windows Installer ACTION or EXECUTEACTION properties. In this case the condition is something like:
<Condition Level="1">( VersionNT >= 601 AND NOT FASTINSTALL = "1" ) OR ACTION = "ADMIN"</Condition>
Neither of these seem to work. Am I just missing something here, or is there a different, better way to do this?
A feature with Feature/#Level="0" will never be installed in an admin install, per the MSI SDK:
During an administrative installation,
the installer creates a source image
for all features in the product except
those feature with 0 in the Level
column of the Feature table.
So you need to reverse your logic; set the feature level to something greater than zero and use Conditions to set it to zero for non-admin installs.
Is there a way to read the properties inside an MSI file?
For example, given a MSI file named Testpackage.msi, I need to find
productName
PackageCode
version
This I am going to use it with WMI uninstall
string objPath = string.Format("Win32_Product.IdentifyingNumber='{0}', Name='{1}', Version='{2}'", "{AC9C1263-2BA8-4863-BE18-01232375CE42}", "testproduct", "10.0.0.0");
Using Orca is a great option, if this can be achieved programmatically. Then I can use this to generate automatic release notes. And in un-installing program too.
You can use the COM-based API for working with MSI, and do something like
Function GetVersion(ByVal msiName)
Const msiOpenDatabaseModeReadOnly = 0
Dim msi, db, view
Set msi = CreateObject("WindowsInstaller.Installer")
Set db = msi.OpenDataBase(msiName, msiOpenDatabaseModeReadOnly)
Set view = db.OpenView("SELECT `Value` FROM `Property` WHERE `Property` = 'ProductVersion'")
Call view.Execute()
GetVersion = view.Fetch().StringData(1)
End Function
WiX toolset: WiX quick-start tips (collection of links to resources). WiX installs DTF.
I just want to mention that things have gotten even easier now. There is a full .NET wrapper for the Windows Installer object model, so you can avoid any COM interop clunkiness.
DTF - Getting Started: Main file: Microsoft.Deployment.WindowsInstaller.dll
Download and install the WiX toolkit
Find the files below in the WixInstallPath\SDK directory
The wrapper is called "Deployment Tools Foundation" (DTF) and here is the basic description: "Deployment Tools Foundation is a rich set of .NET class libraries and related resources that together bring the Windows deployment platform technologies into the .NET world. It is designed to greatly simplify deployment-related development tasks while still exposing the complete functionality of the underlying technology".
Here is a stripped-down, hands-on sample:
using (var db = new Database(FullPath, DatabaseOpenMode.ReadOnly))
{
PackageCode = db.SummaryInfo.RevisionNumber;
AppVendor = db.SummaryInfo.Author;
AppName = db.SummaryInfo.Title;
ProductName = db.SummaryInfo.Subject;
ProductCode = (string)db.ExecuteScalar("SELECT `Value` FROM "+
"`Property` WHERE `Property` = 'ProductCode'");
AppVersion = (string)db.ExecuteScalar("SELECT `Value` FROM "+
"`Property` WHERE `Property` = 'ProductVersion'");
UpgradeCode = (string)db.ExecuteScalar("SELECT `Value` FROM "+
" `Property` WHERE `Property` = 'UpgradeCode'");
}
Primary DTF files (the latter two are the most used ones):
Microsoft.Deployment.Compression.dll - Framework for archive packing and unpacking.
Microsoft.Deployment.Compression.Cab.dll - Implements cabinet archive packing and unpacking.
Microsoft.Deployment.Resources.dll - Classes for reading and writing resource data in executable files.
Microsoft.Deployment.WindowsInstaller.dll - Complete .NET based class library for the Windows Installer APIs.
Microsoft.Deployment.WindowsInstaller.Package.dll - Extended classes for working with Windows Installer installation and patch packages.
Just create a C# project, reference these files, and code your own deployment application with whatever control you desire and need. I am not set up with the tools for DTF at the moment, but see this sample for a general idea of how a C# program would work.
DTF is included with WIX. Download WiX from here.
The DTF dlls are in the SDK folder in the main WiX installation folder (the default location is: %ProgramFiles(x86)%\WiX Toolset v3.10\SDK). The version number will probably be different by the time you see this. Just look for the WiX folder under %ProgramFiles(x86)%.
Look for the DTF help files in the "doc" folder. DTF.chm and DTFAPI.chm. Absolutely excellent documentation for the object model and its usage.
See this serverfault.com post for some more DTF details
Some starter suggestions for working with WiX
You can use Microsoft's Orca.exe. Orca will allow you to open the MSI and edit/view all the tables in it. You will have to download the entire Windows SDK in order to get it, but thankfully that is free.
One alternative (which might be faster due to the download size of the SDK) is to use dark.exe from the WiX project. Dark is a MSI decompiler, which will export everything into an XML file and collection of resources. The XML it outputs will have the information you are looking for.
Here's a similar example in VBScript which I use as part of my build process in creating bootstrapper executables...
Option Explicit
Const MY_MSI = "product.msi"
Dim installer, database, view, result, sumInfo, sPackageCode
Set installer = CreateObject("WindowsInstaller.Installer")
Set database = installer.OpenDatabase (MY_MSI, 0)
Set sumInfo = installer.SummaryInformation(MY_MSI, 0)
sPackageCode = sumInfo.Property(9) ' PID_REVNUMBER = 9, contains the package code.
WScript.Echo "ProductVersion=" & getproperty("ProductVersion")
WScript.Echo "ProductCode=" & getproperty("ProductCode")
WScript.Echo "PackageCode=" & sPackageCode
WScript.Echo "ProductName=" & getproperty("ProductName")
Function getproperty(property)
Set view = database.OpenView ("SELECT Value FROM Property WHERE Property='" & property & "'")
view.Execute
Set result = view.Fetch
getproperty = result.StringData(1)
End Function
I found a lightweight non-programmatic solution in lessmsi. It apparently uses wix and just explodes the whole .msi into a specified folder. (It also has a UI but it didn't render great for me on Win7).