When I previously did some updates to the Java Installers for Apache Maven, I noticed that, when I was testing the two versions, it often didn’t update the Environment variables.
At first, I put it down to the fact that the variables for one version where there and maybe they get in the way of the versions for the next. I checked to see if there was a way to force it to override the values and was initially puzzled by the information – until I realised that this is actually a common problem.
It seems that a WiX installer will happily update the environment variables – but that nothing else knows it’s been done until you tell them. You can tell them in any of several ways, including:
- Log Out and back in again or worse, reboot! Tedious, yet depressingly popular.
- Use the Environment Variables system dialog to “nudge” (i.e. edit and save any of them – without necessarily changing the value) a change. Annoying and a little fiddly for many.
- Use the Windows API call
SendMessageTimeout
to broadcast aWM_SETTINGCHANGE
to the system during the installation.
Having verified the problem using the 2nd approach (much less trouble than the first) I set out to add the third method to the WiX Installers.
A WiX Customer Action
So, I needed to add a Custom Action to the WiX projects and that necessitated writing in a Programming Language instead of XML. I’m using Visual Studio 2012 with the Votive (WiX development) plugin and that offers several project templates for writing Custom Actions.
Remember, the code is all in GitHub (https://github.com/Kajabity/Setup.Projects) and you can download the latest version of the installers on the Java Installers page.
Whilst I used to be quite handy with C++, I’ve been a Java and C# softie for a few years and more recently that has been confined to my hobby time. So initially I tried to write a C# custom action.
Right click (context menu) on the Solution in the Solution Explorer in Visual Studio and select Add->New Project… The example below shows the C++ Custom Action Project selected – but I did try the C# version first.
The C# approach proved problematic – first having to hook in a Windows API call that wasn’t covered in the .NET libraries (necessitating lots of messy definitions), and then finding that for some reason it didn’t appear to work anyway.
The final blow came when I read one article pointing out that using C# in WiX requires that you link in the .NET framework and thus create much larger MSI files (not sure of the validity or details, but it had the desired effect).
I’ve included the code in case anyone has any bright ideas about where I’ve gone wrong…
using System; using System.Collections.Generic; using System.Linq; using System.Text; using Microsoft.Deployment.WindowsInstaller; using System.Runtime.InteropServices; namespace SetupCustomActions { public class CustomActions { [Flags] public enum SendMessageTimeoutFlags : uint { SMTO_NORMAL = 0x0, SMTO_BLOCK = 0x1, SMTO_ABORTIFHUNG = 0x2, SMTO_NOTIMEOUTIFNOTHUNG = 0x8 } [DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Auto)] public static extern IntPtr SendMessageTimeout( IntPtr hWnd, uint Msg, UIntPtr wParam, IntPtr lParam, SendMessageTimeoutFlags fuFlags, uint uTimeout, out UIntPtr lpdwResult); //private const int HWND_BROADCAST = 0xffff; public const uint WM_WININICHANGE = 0x001A; public const uint WM_SETTINGCHANGE = WM_WININICHANGE; public const int MSG_TIMEOUT = 15000; [CustomAction] public static ActionResult RefreshEnvironmentVariables(Session session) { session.Log("Begin RefreshEnvironmentVariables"); IntPtr HWND_BROADCAST = new IntPtr(0xffff); UIntPtr RESULT; string ENVIRONMENT = "Environment"; SendMessageTimeout(HWND_BROADCAST, WM_SETTINGCHANGE, UIntPtr.Zero, (IntPtr)Marshal.StringToHGlobalAnsi(ENVIRONMENT), SendMessageTimeoutFlags.SMTO_ABORTIFHUNG, MSG_TIMEOUT, out RESULT); //SendMessageTimeout(HWND_BROADCAST, WM_SETTINGCHANGE, 0, (LPARAM)"Environment", SMTO_ABORTIFHUNG, 5000, NULL); session.Log("Completed RefreshEnvironmentVariables: result=" + RESULT.ToString() ); return ActionResult.Success; } } }
Faced with a long list of problems and disappointments, I bit the bullet and created myself a new C++ Custom Action project. The template handled most of the work – although I had a few issues because the version of Votive was for an older (2010) version of Visual Studio and I had to identify and fix a few path issues to the various headers and libs.
All that remained was to rename the sample CustomAction
method to RefreshEnvironmentVariables
and add the highlighted code, below:
UINT __stdcall RefreshEnvironmentVariables(MSIHANDLE hInstall) { HRESULT hr = S_OK; UINT er = ERROR_SUCCESS; hr = WcaInitialize(hInstall, "RefreshEnvironmentVariables"); ExitOnFailure(hr, "Failed to initialize"); WcaLog(LOGMSG_STANDARD, "Initialized."); // Send out the Settings Changed message - Once using ANSII... SendMessageTimeout(HWND_BROADCAST, WM_SETTINGCHANGE, 0, (LPARAM)"Environment", SMTO_ABORTIFHUNG, 5000, NULL); // ...and once using UniCode (because Windows 8 likes it that way). SendMessageTimeout(HWND_BROADCAST, WM_SETTINGCHANGE, 0, (LPARAM)L"Environment", SMTO_ABORTIFHUNG, 5000, NULL); LExit: er = SUCCEEDED(hr) ? ERROR_SUCCESS : ERROR_INSTALL_FAILURE; return WcaFinalize(er); }
Having written and compiled (successfully, of course) the Custom Action it then needs to be added as a project dependency to both WiX projects.
Finally, it needs to be added to the WiX code using the following lines:
<!-- Include the Custom Actions library - currently just to send notification of Environment changes. --> <Binary Id="SetupCustomActionsCPP.dll" SourceFile="..\SetupCustomActionsCPP\bin\$(var.Configuration)\SetupCustomActionsCPP.dll" /> <!-- Define the custom action to Refresh Environment Variables. --> <CustomAction Id="RefreshEnvironmentVariables" Return="check" Execute="immediate" BinaryKey="SetupCustomActionsCPP.dll" DllEntry="RefreshEnvironmentVariables" /> <!-- Now schedule the custom action to happen after InstallFinalize. --> <InstallExecuteSequence> <Custom Action="RefreshEnvironmentVariables" After="InstallFinalize"/> </InstallExecuteSequence>
Conclusion
On final change was to replace the “%M2%” value being added to the PATH
variable with the actual directory path ("[INSTALLDIR]bin"
) just as used to define M2
. This was because, when I checked the variables created after the above changes, it had failed to expand the variable – meaning that Maven was not on the path.
These changes appear to have worked – though while testing it I had the remaining problem of it being ignored occasionally. In other words, it worked some times, but not others.
Does Windows get bored?
Great job! I’m having a similar issue, environment var not set unless rebooted, or ok-ing the environment dialog.
Your custom action doesn’t seem to work for me. When I launch a command prompt, no environment var. However, when I launch the command prompt as admin, the environment var is set. I’ve tried all things I can think of, but I’m still stuck.
Do you happen to have any suggestions?
Fixed it after all. Problem seemed to occur on Win8 only. Resolved by sending WM_SETTINGCHANGE twice like this:
SendMessageTimeout(HWND_BROADCAST, WM_SETTINGCHANGE, 0, (LPARAM) “Environment”, SMTO_ABORTIFHUNG, 1000, NULL )
SendMessageTimeout(HWND_BROADCAST, WM_SETTINGCHANGE, 0, (LPARAM) L”Environment”, SMTO_ABORTIFHUNG, 1000, NULL )
The string “Environment” is send both in ANSI and Unicode. Unicode seems to be required for Win8. Thanks to http://stackoverflow.com/questions/4968373/why-doesnt-sendmessagetimeout-update-the-environment-variables
Many, many thanks for your custom action. Helped me a lot!
Koen – I’m pleased you found it useful – and thanks for solving the update problem.
I’ve updated my code and checked – it does work on Windows 8. I’ve updated the code examples above as well.
One last problem – I need to make it do the custom action on upgrade… I’ve changed “create” to “set” so it always sets the values but it leaves the old path in place when you upgrade.