MSI Log Debug Log Sink - c#

I have an InstallShield MSI project. When I pass an MSIHANDLE from an InstallScript custom action to a managed assembly initialized via DotNetCoCreateObject(), the value received within my managed code is -2.
Does anyone know if it is possible to access an MSIHANDLE from an InstallScript custom action that calls into managed code via DotNetCoCreateObject()? I'd like to log my custom action results to the same log file as the rest of the installation. I am using InstallShield 2010, Windows Install 4.5 and .Net 3.5.

It is only possible via a managed custom action and requires use of InstallShield's InstallShield.Interop.Msi.dll to get at the actual handle.
To write to the MSI log file from a managed custom action, this works:
using (Msi.Install msi = Msi.CustomActionHandle(_msiHandle))
{
using (Msi.Record record = new Msi.Record(100))
{
record.SetString(0, "LOG: [1]");
record.SetString(1, entry.Message);
msi.ProcessMessage(Msi.InstallMessage.Info, record);
}
}
NOTE: As of IS2010, InstallShield.Interop.Msi.dll is not digitally signed, so the assembly with your managed custom action must also be unsigned.

No, it's not possible. You'll have to manage your log output yourself.

Another pt of clarification is that IS has two project types, InstallScript & MSI. You can only access the MSI handle within MSI projects.

Related

Can I use Orca to add a Custom Action to an MSI file, where the action is a C# method?

I have created an MSI file, but I want a specific C# method in the file to be used in a Custom Action that takes place prior to the ExecuteAction action in the InstallExecuteSequence table. Is there any way for me to update the Binary table in Orca so that it references a particular method in the MSI file?
For more information, my MSI file uses three separate C# class library projects. The method I want to use in the Custom Action is called InitialAction and is in a CS file called Initialise.cs in the Initialise project.
No you can't. Windows Installer doesn't natively support managed code custom actions. The Dll type referred to here:
https://msdn.microsoft.com/en-us/library/windows/desktop/aa372048(v=vs.85).aspx
in a Win32 Dll with a standard required entrypoint signature. Managed code custom actions are typically implemented using a shim C++ Dll (Visual Studio) that calls into managed code, or C++ that calls out to an external process (WiX DTF). If you are using Visual Studio or WiX both offer support for managed code custom actions.
Having said that, what does your code do that it is required to be a direct call? Running an external executable is supported, whether managed code or not. The executable can call into the class libraries.
It's often useful to describe your ultimate goal. In effect you have decided that the solution to a problem is to edit the MSI, but there's no description of that actual problem. It appears that you want to modify an existing MSI to call code, but you don't want to rebuild it with the available tools.

stamping the username on c# assembly - who build this dll

So, here is what I am trying to do.
I am trying to stamp name of the user who compiled the particular project. As link assembly version is there any way by using that I can store the original user name on dll.
I tried creating custom attribute and using it in AssemblyInfo.cs, but it gives me the username of person who is running the dll and not who build it.
Any help will be appreciated.
For modifying the Assembly info I use a pre-build event that executes a batch file for the modification.
You could write a custom MSBuild action that updates an assembly attribute in a known file prior to compilation. This has the downside of having to install the custom build step on all dev machines and any build machines.
So here is I found I am trying trick.
I have created new custom attribute AssemblyCompiler. And by changing and using powershell script shown here . Seems it works.
Another C# solution
Create new custom attribute
write c# program to update/add username
call that c# code on prebuild event as follows
call ....\Resources\OverwriteAssemblyInfo.exe [ I placed my the executable file in resources]

How to uninstall MSI using its Product Code in c#

I know We can uninstall a windows application using its MSI by passing command arguments as follows,
Process p = new Process();
p.StartInfo.FileName = "msiexec.exe";
p.StartInfo.Arguments = "/x \"C:\\MyApplication.msi\"/qn";
p.Start();
But what i want to know is how can we uninstall the application without using MSI ? In the above scenario I should have the MSI in the specific location to uninstall this application, If i could unstaill using product code then I dont need to have the MSI in the target machine.
According to MSDN, You can uninstall it using the product code:
msiexec.exe /x {your-product-code-guid}
When you use the product code, it uses the cached MSI from C:\WINDOWS\Installer.
Along the lines of PhilmE's answer, Windows Installer XML (WiX) ships the Microsoft.Deployment.WindowsInstaller interop library as part of Deployment Tools Foundation (DTF). This skips the COM interop and encapsulates the Win32 API instead.
using Microsoft.Deployment.WindowsInstaller;
public static void Uninstall( string productCode)
{
Installer.ConfigureProduct(productCode, 0, InstallState.Absent, #"REBOOT=""R"" /l*v uninstall.log");
}
Probably for your case, knowing the "/x" Parameter was sufficient. Two remarks on that:
More secure is adding a "REBOOT=R" part to your commandline. And you can add a logfile path:
msiexec /x "..." /qn REBOOT=R /L*v "c:\mylogdir\mymsi.log"
Second, don't try to change anything to "the caching". You don't need even to understand it. If the cached package would be broken, a regular uninstallation is no longer possible, which could bring the computer in a "support needed" state.
Because your question was originally talking about C# .. You don't have to use msiexec for it:
a) Use the original C/C++ API with the function MsiInstallProduct() or MsiConfigureProduct().
MSDN ref:
http://msdn.microsoft.com/en-us/library/windows/desktop/aa370315(v=vs.85).aspx
You have to use interop to use that in C#.
or b)
Use the Windows Installer Object.
For example, this related case was already answered here in stackoverflow:
Programmatically installing MSI packages
But using this function needs the physical package, also for uninstallation.
With a slight indirection, here is the better code for uninstallation:
First, add a reference to COM object "Microsoft Windows Installer Object Library" to your project.
using WindowsInstaller;
public static class MyMsiLib
{
public static void Uninstall(string productCode)
{
Type type = Type.GetTypeFromProgID("WindowsInstaller.Installer");
Installer installer = (Installer)Activator.CreateInstance(type);
installer.UILevel=msiUILevelNone;
installer.ConfigureProduct(productCode, 0, msiInstallStateAbsent);
}
}
The UILevel property before is set here hardcoded to determine the UI level silent as you seem to want. Same for other properties. See MSDN documentation e.g. mentioned in the link above.
Of course "real programmers" work with the original API instead of the "Installer Object" :-)
But for small purposes it is sufficient. And easier.
This command works on the command line:
msiexec /x {3A40307D-6DF2-4412-842F-B1D848043367} /quiet
I haven't tried it in C#, but replacing your arguments with the key shown above should work. You can find the GUID in the registry key for the app you are trying to uninstall.

Programmatically installing MSI packages

I would like to install a given .msi package programmatically from my C# .NET application, preferably with the installation parameters that my application specifies (like the installation path, decline crapware, etc.).
I did some searches, but I haven't really found anything useful. The most promising hit was this topic, but I cannot find any documentation of Microsoft.Deployment.WindowsInstaller or of WindowsInstaller.Installer for that matter.
I find the Deployment Tools Foundation project mentioned above to be a solid way to do this from .NET. Having referenced Microsoft.Deployment.WindowsInstaller.dll, use code like this to install a package:
Installer.SetInternalUI(InstallUIOptions.Silent);
Installer.InstallProduct(msiFilename, "ACTION=INSTALL ALLUSERS=2 MSIINSTALLPERUSER=");
The documentation for the .NET wrapper is in a .chm file in the Windows Installer XML installation directory in Program Files. Some parts of that DLL loosely wrap the native Windows APIs so the documentation here can be useful as well, which is how I worked out the string in the above snippet to suit my situation.
There's a COM object that offers an API for the installer:
First add a reference to COM object "Microsoft Windows Installer Object Library" to your project. Then you can start with the following code:
using System;
using WindowsInstaller;
namespace TestApp
{
public class InstallerTest
{
public static void Install()
{
Type type = Type.GetTypeFromProgID("WindowsInstaller.Installer");
Installer installer = (Installer)Activator.CreateInstance(type);
installer.InstallProduct("YourPackage.msi");
}
}
}
And there's a documentation about the Installer Object.
The "Deployment Tools Foundation" project which is a part of the WIX3.5 install contains a .NET wrapper for most (if not all) of the Windows Installer API. Get it by downloading and installing the WiX install: http://wixtoolset.org/ (currently WiX 3.11, updated Aug.2017).
Locate the Microsoft.Deployment.WindowsInstaller.dll file in the %ProgramFiles%\Windows Installer XML v3.??\SDK\ folder. Set a reference in your C# project and try to run the different APIs and see if you get the desired functionality.
I highly recommend using Deployment Tools Foundation over any COM Interop from .NET code.
The basic Win32 API (that can be pinvoked if necessary) is MsiInstallProduct. This is where practically all other mentioned APIs and calls will end up.
https://msdn.microsoft.com/en-us/library/aa370315(v=vs.85).aspx
Just pass the full path to the MSI file and your command line (including quiet options etc) and check the result to see if it installed correctly.
Note that there is a simple p/invoke declaration for managed code:
[DllImport("msi.dll", CharSet = CharSet.Auto, SetLastError=true)]
static extern UInt32 MsiInstallProduct(string packagePath, string commandLine);
The very simplest solution is to use msiexec to invoke the installer on the .msi.
You can customise the installation using command line settings including setting .msi properties, silent installation etc.
There are two approaches to solving your problem.
The first one as mentioned by #Glytzhkof is to use the Microsoft.Deployment.WindowsInstaller .NET wrapper API. This is some seriously powerful stuff but requires some time to get familiar with. You can get the latest version here (UPDATE: Stein Åsmul 28.12.2018: DTF is now part of the WiX toolkit).
The other approach is to use Transforms (.MST files). Transform files can be generated using Microsoft Orca or InstallShiled. The MSTs contains all the customizations that you need and can be applied on the MSI using this command line:
msiexec /i somemsi.msi TRANSFORMS=somemst.mst /qb
Additionally you can pass parameters directly in the command line:
msiexec /i <somemsi.msi> /qb AGREETOLICENSE=YES INSTALLDIR=C:\Temp
etc...
However, you will need to edit the MSI in an ORCA/InstallShield to determine which parameters are actually used.
The parameters used in the above example are not universal.
The actual installation can be complicated because of the presence of custom actions etc. In fact there is a whole industry that is built around msi customizations. Its called Applications Repackaging.

Detect silent install in .NET Custom Action

How do you detect that the install is running in silent mode?
I have a custom application that I've added a .msi setup project. I run the .msi file with the /qb switch, and in my custom installer c# code I would like to be able to detect this.
Edit: nobugs says to test the UILevel property. How do I access the UILevel property from a class derived from the System.Configuration.Install.Installer class?
Taking the hint from nobugz, I did the following:
On the Custom Actions view of the .msi setup project, I added the following to my CustomActionData (to pass the UILevel through to my custom installer):
/UILevel="[UILevel]"
Within my C# code for the code derived from base class Installer, I added code to get the value:
string uiLevelString = Context.Parameters["UILevel"];
It was then simple to parse the string for an int value. If the value is <= 3, it is a silent install.
First I would point out that InstallUtil is a very bad pattern. They run out of process, tatoo the process with a CLR version and when they fail they raise a 1001 error modal dialog even during a silent install.
Instead you should use WiX's DTF pattern.
MsiGetMode ( Session.Mode ) is limited during deferred execution so you will have to serialize and deserialize the UILevel.
http://www.msifaq.com/a/1044.htm

Categories

Resources