For a deployment component that I am currently building for an application I have a requirement to check if the .Net Platform Update 1 is installed as per the instructions here.
The key in question is HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\.NETFramework\v4.0.30319\SKUs\.NETFramework,Version=v4.0.1, which has no default value.
After taking a look around on the internet I could not find a code snippet that was flexible and reusable. Most where simple hard coded examples on how to work around the problem. So I decided to build my own Custom Action and add it to an assembly of custom actions that I share and reuse between deployment components.
The Custom Action
The code is pretty straight forward. A key name, as specified by the session property REGKEY, is opened from the registry hive as specified by REGHIVE session property. The session property REGKEYEXISTS is used to store the result of the check./// <returns>Returns ActionResult.Success if the key can be found</returns> /// <exception cref="ArgumentNullException">Thrown if the <paramref name="session"/> parameter is null.</exception> [CustomAction] public static ActionResult CheckRegistryKeyExists(Session session) { if (null == session) { throw new ArgumentNullException("session"); } try { #if DEBUG Debugger.Launch(); #endif session.Log("CheckRegistryKeyExists: Begin"); String key = session["REGKEY"]; String hive = session["REGHIVE"]; RegistryKey registryKey = null; session.Log("CheckRegistryKeyExists: Looking up key {0} in Hive {1}", key, hive); switch (hive) { case "HKLM": registryKey = Registry.LocalMachine.OpenSubKey(key); break; case "HKCC": registryKey = Registry.CurrentConfig.OpenSubKey(key); break; case "HKCR": registryKey = Registry.ClassesRoot.OpenSubKey(key); break; case "HKU": registryKey = Registry.Users.OpenSubKey(key); break; case "HKCU": registryKey = Registry.CurrentUser.OpenSubKey(key); break; default: session.Log("Unknown hive {0}", hive); break; } session["REGKEYEXISTS"] = registryKey == null ? "0" : "1"; if(registryKey != null) { registryKey.Close(); } session.Log("CheckRegistryKeyExists: End"); } catch (Exception ex) { session.Log("CheckRegistryKeyExists: exception: {0}", ex.Message); throw; } return ActionResult.Success; } }
Using the Custom Action
To use the custom action in an installer first embed the custom action as a Binary Element.
< SourceFile="$(var.CustomActions.TargetDir)\Installer.CustomActions.CA.dll" CustomActions?>Then define the Custom Action and sequence it for execution, in this case it is before LaunchConditions. <CustomAction Id="CheckRegistryKeyExists" BinaryKey="CustomActions" DllEntry="CheckRegistryKeyExists" Execute="immediate" Return="check" /> <InstallUISequence> <Custom Action="CheckRegistryKeyExists" Before="LaunchConditions">NOT Installed</Custom> </InstallUISequence>Then specify the required properties and setup the condition and message.
<Property Id="REGHIVE">HKLM</Property> <Property Id="REGKEYEXISTS">0</Property> <Condition Message='!(loc.Update401Missing)'>Installed OR REGKEYEXISTS="1"</Condition>
When the custom action is run with logging msiexec /i Workflow.Designer.Installer.msi /lv out.txt the following output is displayed to aid with debugging.
Action start 14:38:41: CheckRegistryKeyExists.
MSI (c) (A0:FC) [14:38:41:385]: Invoking remote custom action. DLL: C:\MSIA543.tmp, Entrypoint: CheckRegistryKeyExists
MSI (c) (A0:24) [14:38:41:387]: Cloaking enabled.
MSI (c) (A0:24) [14:38:41:387]: Attempting to enable all disabled privileges before calling Install on Server
MSI (c) (A0:24) [14:38:41:387]: Connected to service for CA interface.
SFXCA: Extracting custom action to temporary directory: C:\MSIA543.tmp-\
SFXCA: Binding to CLR version v4.0.30319
Calling custom action Emex.Installer.CustomActions!Emex.Installer.CustomActions.CustomActions.CheckRegistryKeyExists
CheckRegistryKeyExists: Begin
CheckRegistryKeyExists: Looking up key SOFTWARE\Microsoft\.NETFramework\v4.0.30319\SKUs\.NETFramework,Version=v4.0.1 in Hive HKLM
MSI (c) (A0!F4) [14:39:09:498]: PROPERTY CHANGE: Modifying REGKEYEXISTS property. Its current value is '0'. Its new value: '1'.
CheckRegistryKeyExists: End
Debugging
The statement Debugger.Launch() statement launches and attaches the debugger to the installer when the Custom Action is executed. In the above code it is surrounded with the #if DEBUG to prevent the debugger being launched in release mode.When the debug version of the installer is launched the following dialog is displayed. You can attach the VS project that contains the source for the Custom Action and simply step through the code as you normally would.