Catalin
Posts: 7513
Joined: Wed Jun 13, 2018 7:49 am

How to access feature states in a .NET custom action - VBScript alternative since deprecation

Hello,

Recently, Microsoft has announced the VBScript deprecation.

Although most Windows admins are just fine with this, as PowerShell is even a more powerful and capable tool, packagers who used VBScript type custom actions took a hit.

VBScript was a really capable language, especially when it comes to MSI installers due to its' Session object.

The Session object is really powerful, as it has lots of methods and properties that directly work with MSI components (e.g. actions, features, etc.).

Since its' deprecation, many customers have asked me if it's possible to achieve the same in any other language (such as PowerShell and .NET).

While PowerShell can do pretty much all VBScript can and more, in terms of Windows Administration, when it comes to its' Windows Installer support, PowerShell really lacks it.

Recently, we have added support for .NET Custom Actions (both Core and Framework). This will prove really useful as, after many tries to reproduce VBScript behavior in another language, I have finally managed to do so from a .NET Framework Custom Action.

Some more details about our .NET Custom Action can be found here: How to create a .NET Custom Action

Enough storytelling for today, let's get to today's scenario. Few days ago, I had yet another customer asking me how to replicate the VBScript behavior in a different type of Custom Action. While this wasn't possible in the past, I thought I'd give it another go with our current support for .NET Custom Actions.

This specific customer wanted to access the FeatureRequestState property of the Session object.

In VBScript, this is really easy because it has predefined support. For instance, accessing the FeatureRequestState property is as easy as:

Code: Select all

Session.FeatureRequestState("MyFeature")
Now, when it comes to .NET Custom Actions, from what I've tried so far, directly accessing the FeatureRequestState property of the Session object is not possible.

However, a workaround that I've found is making use of the GetFeatureState function, which returns two special objects, a "state" and an "action", where:

1. state represents whether the feature is installed or not on the machine - we can compare this with the FeatureCurrentState property of the Session object

2. action represents whether the feature will be installed or not on the machine - we can compare this with the FeatureRequestState property of the Session object

Now that we know how to achieve our goal, let's get to coding.

1. the first step would be creating the .NET Framework custom action as per the article I've linked at the start

2. under Project Solution, double click on the MsiSession.cs and add the following declaration:

Code: Select all

[DllImport("msi.dll", CharSet = CharSet.Unicode)] 
internal static extern uint MsiGetFeatureState(int hInstall, string szFeature, out int iInstalled, out int iAction);
Note: this should be added where the other declarations are (as there are multiple functions imported from both user32.dll and msi.dll)
Screenshot_193.png
Screenshot_193.png (77.58 KiB) Viewed 23929 times
3. in the same file, we have to declare the function as well:

Code: Select all

 public void GetFeatureState(string szFeature, out int iInstalled, out int iAction)
{
NativeMethods.MsiGetFeatureState(MsiHandle.ToInt32(), szFeature, out iInstalled, out iAction);
}

Note: This sohuld be done towards the end, where the other functions are declared.

Screenshot_194.png
Screenshot_194.png (60.87 KiB) Viewed 23929 times
4. now that all the "prerequisites" are met, here's the final code that calls the GetFeatureState function:

Code: Select all

using System;
using System.Windows.Forms;
using WindowsInstaller;

namespace CustomActionFRQNew
{
	public class CustomActions
	{
		public static int CustomAction1(string aMsiHandle)
		{

            MsiSession session = new MsiSession(aMsiHandle);
            int state = 0;
            int action = 0;
            session.GetFeatureState("featureSample", out state, out action);
            

            MessageBox.Show(state.ToString());
            MessageBox.Show(action.ToString());
            return 0;
		}
	}
}

Implementing this in Advanced Installer is quite easy and it is also showcased in the first article that gives more details about the .NET Custom Action.

Here's how I defined the custom action in a sample project:

1. add the DLL file as a temporary file in the "Files and Folders" page

2. Custom Actions page

3. CallNetMethod custom action

4. define it as it follows:
Screenshot_195.png
Screenshot_195.png (39.79 KiB) Viewed 23929 times

After building the project and running the MSI, the two message boxes will appear, giving us our state (Session.FeatureCurrentState) and action (Session.FeatureRequestState).

In our case, here's the results:

1. state = 2 (meaning that the feature is not installed on the machine - true, considering it's our first time installing the package)
Screenshot_197.png
Screenshot_197.png (24.66 KiB) Viewed 23929 times

2. action = 3 (meaning that the feature is to be installed locally)
Screenshot_198.png
Screenshot_198.png (24.93 KiB) Viewed 23929 times

For more details about the Feature state and action values, please refer to the following article: Edit Control Dialog

Hope this helps! :)

Best regards,
Catalin
Catalin Gheorghe - Advanced Installer Team
Follow us: Twitter - Facebook - YouTube

Return to “Sample Projects”