9

I am creating a Visual Studio 2008 Setup Wizard for my program
http://support.microsoft.com/kb/307353
I see that it is possible to add registry entries, etc.. Is there a possibility to register windows environment variable without writing a custom action?
And if I must write a custom action, what is the least painful way to do so?

Andrey Rubshtein
  • 20,795
  • 11
  • 69
  • 104
  • There is no such thing as Visual Studio 2009. And I can count the number of instances on one hand where an installer actually needs to register an environment variable. Chances are *very* good that you are not one of those cases. – Cody Gray - on strike Dec 19 '11 at 10:53
  • @CodyGray, please explain the possible alternatives. – Andrey Rubshtein Dec 19 '11 at 11:08
  • 1
    I'm guessing he'd recommend the registry or a data file in the user profile directory/program data directory. Or if your project is a .Net project, an app.config value. Although I'd disagree with the thrust of the statement - if you're adding your program to the `PATH`, that might be a good reason to muck around with environment variables. Otherwise I'd agree that there are probably better places to put your data. – Merlyn Morgan-Graham Dec 19 '11 at 11:10
  • 2
    Hrm, yeah. @Merlyn's recommendations are pretty consistent with mine. But I don't think adding your program to the `PATH` is the correct thing to do, either. Again, extremely limited use case. You should not assume that users want you limiting up their `PATH`. I certainly don't. – Cody Gray - on strike Dec 19 '11 at 11:58
  • I'd only do it for a tool designed for enhancing command line scripting, I'd make it optional if I did, and I'd turn it off by default. Definitely limited. – Merlyn Morgan-Graham Dec 20 '11 at 04:54
  • Why environment variables are so evil? What makes the registry better? – Andrey Rubshtein Dec 20 '11 at 08:29

4 Answers4

9

Warning: Please do not use this approach. It is dangerous. Use built-in MSI features for environment variable update.

Example: Writing straight to HKLM\SYSTEM\CurrentControlSet\Control\Session Manager\Environment - Path will wipe out whatever is already there. Very serious.

Solution: MSI features the Environment table to facilitate merging and updating of environment variables in a reliable fashion. Note that this table is also known to be complex enough to trigger unexpected results. Please test well. Uninstall scenarios especially.


Using Visual Studio 2008, you can easily do it by setting the appropriate variable in the Windows registry:

  1. In the Solution Explorer, right-click on your project (not solution), and select View->Registry
  2. Create the registry key(folder):
    1. For a User variable: Right-click on HKEY_CURRENT_USER, select "New Key", and name it "Environment".
    2. For a System variable: Right-Click on HKEY_LOCAL_MACHINE, select "New Key", and name it "SYSTEM". Continue doing this to create the path "HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\Environment" .
  3. Right-click on the Environment key(folder), select New->String, and give it the name you want.
  4. With the string selected, find the Properties window (Alt+Enter will bring it up)
  5. In the Properties window, fill in the Value you want.

If you want the Value to reference the installation directory, you can do it like this using property variables: [TARGETDIR]SomeFile.ext (see http://msdn.microsoft.com/en-us/library/aa370905%28v=vs.85%29.aspx for more property variables)

Community
  • 1
  • 1
michaelmoo
  • 365
  • 4
  • 11
  • With apologies to the OP - who has provided a thorough answer in good faith - **I am afraid this is a very dangerous approach**. [The answer below is the correct approach](https://stackoverflow.com/a/8560425/129130). Alternatively you can find a longer write-up here: [Path update versus AppPaths](https://stackoverflow.com/a/52432169/129130). – Stein Åsmul Sep 21 '18 at 00:29
  • Is it dangerous in general, or just for specific cases like modifying existing variables (PATH in particular)? If you are making a custom environment variable I'm not clear what the danger is. – michaelmoo Jun 06 '19 at 16:28
  • 1
    These keys are *"implementation details"* in Windows. Only official Windows API functions for environment variable management should write here. What happens is that instead of merging in a new value, you overwrite whatever is there using the above method - wiping out everything that was there. Very serious - especially for the system PATH variable, but it applies to all environment variables (it is just the impact that differs). Suggest you keep the answer, somebody else will suggest the same if you delete it and then they need to read why they should use MSI built-in methods instead. – Stein Åsmul Jun 06 '19 at 19:02
  • I updated the header section, hope you don't mind. Just need to prevent people getting in trouble with wiped-out PATH variables - very bad to deal with - especially in development teams. BTW, there are some tricks to recover the PATH variable: **`1)`** use system restore, **`2)`** try last known good?, **`3)`** get cached PATH from EXE files that haven't been restarted yet using Process Explorer => Environment tab, **`4)`** copy PATH from a similar SOE machine with the same package estate, **`5)`** recover via ControlSet001 (or higher) in the registry. That is all I recall. – Stein Åsmul Jun 06 '19 at 19:54
3

The top answer explains how to do it without a custom action but those looking for a custom action can use the following code as a template:

 [RunInstaller(true)]
public partial class GRInstallCustomAction : System.Configuration.Install.Installer
{
    string environmentKey = @"SYSTEM\CurrentControlSet\Control\Session Manager\Environment";
    string pathUrl = "C:\\Program Files (86)\\TargetFolder";
    public GRInstallCustomAction()
    {
        InitializeComponent();
    }

    [System.Security.Permissions.SecurityPermission(System.Security.Permissions.SecurityAction.Demand)]
    public override void Install(IDictionary stateSaver)
    {
        base.Install(stateSaver);
    }

    [System.Security.Permissions.SecurityPermission(System.Security.Permissions.SecurityAction.Demand)]
    public override void Commit(IDictionary savedState)
    {
        base.Commit(savedState);

        string environmentVar = Environment.GetEnvironmentVariable("PATH");


        //get non-expanded PATH environment variable            
        string oldPath = (string)Registry.LocalMachine.CreateSubKey(environmentKey).GetValue("Path", "", RegistryValueOptions.DoNotExpandEnvironmentNames);


        var index = oldPath.IndexOf(pathUrl);
        if (index < 0)
        {
            //set the path as an an expandable string
            Registry.LocalMachine.CreateSubKey(environmentKey).SetValue("Path", oldPath + ";" + pathUrl, RegistryValueKind.ExpandString);
        }

    }

    [System.Security.Permissions.SecurityPermission(System.Security.Permissions.SecurityAction.Demand)]
    public override void Rollback(IDictionary savedState)
    {
        base.Rollback(savedState);


    }

    [System.Security.Permissions.SecurityPermission(System.Security.Permissions.SecurityAction.Demand)]
    public override void Uninstall(IDictionary savedState)
    {
        base.Uninstall(savedState);

        //get non-expanded PATH environment variable            
        string oldPath = (string)Registry.LocalMachine.CreateSubKey(environmentKey).GetValue("Path", "", RegistryValueOptions.DoNotExpandEnvironmentNames);

        string removeString = pathUrl + ";";
        var index = oldPath.IndexOf(removeString);
        if (index < 0)
        {
            removeString = pathUrl;
            index = oldPath.IndexOf(removeString);
        }

        if (index > -1)
        {
            oldPath = oldPath.Remove(index, pathUrl.Length);
            //set the path as an an expandable string
            Registry.LocalMachine.CreateSubKey(environmentKey).SetValue("Path", oldPath, RegistryValueKind.ExpandString);
        }
    }
}

This walk-through shows you how to create and apply the custom action: https://msdn.microsoft.com/en-us/library/d9k65z2d(v=vs.100).aspx

ickydime
  • 1,051
  • 10
  • 22
3

Windows Installer does support environment variables through Environment table, but Visual Studio setup projects do not allow you to use it.

A solution is to use a different setup authoring tool which supports environment variables: http://en.wikipedia.org/wiki/List_of_installation_software

Another solution is to manually add it in Environment table by editing the MSI with Orca.

There's also the custom action approach you mentioned.

Cosmin
  • 21,216
  • 5
  • 45
  • 60
0

Ickydime (see answers above) custom action sample shows how to add an environment variable by expanding the PATH env variable. I'm sharing below how to create a new environment variable using Environment.SetEnvironmentVariable

    string environmentKey = "MY_VAR";
    string pathUrl = "%ProgramFiles%\\...";
    public Installer1()
    {
        InitializeComponent();
    }

    [System.Security.Permissions.SecurityPermission(System.Security.Permissions.SecurityAction.Demand)]
    public override void Install(IDictionary stateSaver)
    {
        base.Install(stateSaver);
    }

    [System.Security.Permissions.SecurityPermission(System.Security.Permissions.SecurityAction.Demand)]
    public override void Commit(IDictionary savedState)
    {
        base.Commit(savedState);
        Environment.SetEnvironmentVariable(environmentKey, pathUrl, EnvironmentVariableTarget.Machine);
    }

    [System.Security.Permissions.SecurityPermission(System.Security.Permissions.SecurityAction.Demand)]
    public override void Rollback(IDictionary savedState)
    {
        base.Rollback(savedState);
    }

    [System.Security.Permissions.SecurityPermission(System.Security.Permissions.SecurityAction.Demand)]
    public override void Uninstall(IDictionary savedState)
    {
        base.Uninstall(savedState);
        Environment.SetEnvironmentVariable(environmentKey, null, EnvironmentVariableTarget.Machine);
    }