How to set Environment variables permanently in C#
Asked Answered
G

5

14

I am using the following code to get and set environment variables.

public static string Get( string name, bool ExpandVariables=true ) {
    if ( ExpandVariables ) {
        return System.Environment.GetEnvironmentVariable( name );
    } else {
        return (string)Microsoft.Win32.Registry.LocalMachine.OpenSubKey( @"SYSTEM\CurrentControlSet\Control\Session Manager\Environment\" ).GetValue( name, "", Microsoft.Win32.RegistryValueOptions.DoNotExpandEnvironmentNames );
    }
}

public static void Set( string name, string value ) {
    System.Environment.SetEnvironmentVariable( name, value );
}

The problem I face, is even when the program is running as administrator, the environment variable lasts only as long as the program is running. I have confirmed this by running a Get on the variable I set in a previous instance.

Example usage of above

Set("OPENSSL_CONF", @"c:\openssl\openssl.cfg");

And to retrieve

MessageBox.Show( Get("OPENSSL_CONF") );

While the program is running, after using Set, the value is returned using Get without any issue. The problem is the environment variable isn't permanent (being set on the system).

It also never shows up under advanced properties.

Thanks in advance.

Globetrotter answered 9/6, 2015 at 7:59 Comment(2)
To make them persist, you need to set the system variable, currently you are just setting process specific variable, which are bound to exist only during program durationCottonmouth
If you are just fetching the variable from registry to set the environment variable, then why not use it directly, why would you want to set it as Environment variable at all. It would make sense only when you are dynamically setting stuff (registry stuff is almost static) and want it to persist beyond. Also what after setting registry value change ?Cottonmouth
A
23

While the program is running, after using Set, the value is returned using Get without any issue. The problem is the environment variable isn't permanent (being set on the system).

Thats because the overload of SetEnvironmentVariable that you're using stores in the process variables. From the docs:

Calling this method is equivalent to calling the SetEnvironmentVariable(String, String, EnvironmentVariableTarget) overload with a value of EnvironmentVariableTarget.Process for the target argument.

You need to use the overload specifying EnvironmentVariableTarget.Machine instead:

public static void Set(string name, string value) 
{
    Environment.SetEnvironmentVariable(name, value, EnvironmentVariableTarget.Machine);
}
Allayne answered 9/6, 2015 at 8:1 Comment(1)
Itchakov, do you know if there are any specific permissions required for using EnvironmentVariableTarget.Machine?Lathrope
P
2

According to MSDN the method you are using is just modifying the variable for the runtime of the process.

Try the overload described here: https://msdn.microsoft.com/library/96xafkes%28v=vs.110%29.aspx

Prut answered 9/6, 2015 at 8:3 Comment(0)
C
2

This kind of question has already been asked multiple times, check the following links for more information:

Set Env Variable - 1

Set Env Variable - 2

Set Env Variable - Tutorial

Cottonmouth answered 9/6, 2015 at 8:11 Comment(2)
Also remember, Env variables are always inherited on the process beginning, so cannot be Get when process is already on , this needs restartCottonmouth
not sure why none of those results appeared when i search here, google, and the 'auto' search while writing the article. epic 3 x fail for search engines ;)Globetrotter
C
0

Here's an example that permanently updates the User PATH variable by programmatically editing the registry:

// Admin Permission not-required:
//      HKCU\Environment\Path
// Admin Permission required:
//      HKLM\SYSTEM\CurrentControlSet\Control
//         \Session Manager\Environment\Path

public static void UserPathAppend(string path, int verbose=1) {
    string oldpath = UserPathGet();

    List<string> newpathlist = oldpath.Split(';').ToList();
    newpathlist.Add(path);

    string newpath = String.Join(";", newpathlist.ToArray());

    UserPathSet(newpath);

    UpdateEnvPath();

    if (verbose!=0) {
        System.Windows.MessageBox.Show(
            "PATH APPEND:\n\n"
            + path + "\n\n"
            + "OLD HKCU PATH:\n\n"
            +  oldpath + "\n\n"
            + "NEW HKCU PATH:\n\n"
            +  newpath + "\n\n"
            + "REGISTRY KEY MODIFIED:\n\n"
            + "HKCU\\Environment\\Path\n\n"
            + "NOTE:\n\n"
            + "'Command Path' is a concat of 'HKLM Path' & 'HKCU Path'.\n",
            "Updated Current User Path Environment Variable"
        );
    }
}

public static void UserPathPrepend(string path, int verbose=1) {
    string oldpath = UserPathGet();

    List<string> newpathlist = oldpath.Split(';').ToList();
    newpathlist.Insert(0, path);

    string newpath = String.Join(";", newpathlist.ToArray());

    UserPathSet(newpath);

    UpdateEnvPath();

    if (verbose != 0) {
        System.Windows.MessageBox.Show(
            "PATH PREPEND:\n\n"
            + path + "\n\n"
            + "OLD HKCU PATH:\n\n"
            +  oldpath + "\n\n"
            + "NEW HKCU PATH:\n\n"
            +  newpath + "\n\n"
            + "REGISTRY KEY MODIFIED:\n\n"
            + "HKCU\\Environment\\Path\n\n"
            + "NOTE:\n\n"
            + "'Command Path' is a concat of 'HKLM Path' & 'HKCU Path'.\n",
            "Updated Current User Path Environment Variable"
        );
    }
}

public static string UserPathGet()
{
    // Reads Registry Path "HKCU\Environment\Path"
    string subKey = "Environment";

    Microsoft.Win32.RegistryKey sk = Microsoft.Win32.Registry.CurrentUser.OpenSubKey(subKey);

    if (sk == null)
        return null;
    else
        return sk.GetValue("Path").ToString();
}

public static void UserPathSet(string newpath)
{
    // Writes Registry Path "HKCU\Environment\Path"
    string subKey = "Environment";

    Microsoft.Win32.RegistryKey sk1 = Microsoft.Win32.Registry.CurrentUser.CreateSubKey(subKey);
    sk1.SetValue("Path", newpath);
}

//===========================================================
// Private: This part required if you don't want to logout 
//          and login again to see Path Variable update
//===========================================================

[DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Auto)]
private static extern IntPtr SendMessageTimeout(IntPtr hWnd, 
            uint Msg, UIntPtr wParam, string lParam, 
            SendMessageTimeoutFlags fuFlags, 
            uint uTimeout, out UIntPtr lpdwResult);

private enum SendMessageTimeoutFlags : uint
{
    SMTO_NORMAL = 0x0, SMTO_BLOCK = 0x1, 
    SMTO_ABORTIFHUNG = 0x2, SMTO_NOTIMEOUTIFNOTHUNG = 0x8
}

private static void UpdateEnvPath() {
    // SEE: https://support.microsoft.com/en-us/help/104011/how-to-propagate-environment-variables-to-the-system
    // Need to send WM_SETTINGCHANGE Message to 
    //    propagage changes to Path env from registry
    IntPtr HWND_BROADCAST = (IntPtr)0xffff;
    const UInt32 WM_SETTINGCHANGE = 0x001A;
    UIntPtr result;
    IntPtr settingResult
        = SendMessageTimeout(HWND_BROADCAST,
                             WM_SETTINGCHANGE, (UIntPtr)0,
                             "Environment",
                             SendMessageTimeoutFlags.SMTO_ABORTIFHUNG,
                             5000, out result);
}
Chinaman answered 26/8, 2017 at 7:21 Comment(0)
S
0

I came from JS so C# is still very confusing to me. But these 2 ways are working as of today (2024-08-12) in .NET 8.0.303:


USING .env

dotnet new console --framework net8.0 --use-program-main
dotnet add package DotNetEnv

your <project_name>.csproj file should look like this

<Project Sdk="Microsoft.NET.Sdk">
    
      <PropertyGroup>
        <OutputType>Exe</OutputType>
        <TargetFramework>net8.0</TargetFramework>
        <ImplicitUsings>disable</ImplicitUsings> <!-- Enabled by default, I recommend to disable -->
        <Nullable>enable</Nullable>
      </PropertyGroup>
      <ItemGroup> 
        <PackageReference Include="DotNetEnv" Version="3.1.0" />
      </ItemGroup>
    
</Project>

now create a .env file in your project root folder (where Program.cs is located). this is mine for example:

# ---------------------- Telegram ----------------------
TELEGRAM_BOT_TOKEN='0123456789:Aa1Bb2Cc3...'

# ---------------------- PostgreSQL ----------------------
POSTGRESQL_HOST='localhost'
POSTGRESQL_PORT='5432'
POSTGRESQL_DATABASE='postgres'
POSTGRESQL_USERNAME='postgres'
POSTGRESQL_PASSWORD='1234'

Now, in your Program.cs you must use DotNetEnv.Env.Load() (otherwise you will need to use this method inside every namespace that needs environment variables)

using System;

namespace ProjectName;

public static class Program {
    internal static void Main() {
        DotNetEnv.Env.Load(); // only this in Main method of Program.cs is enough, all namespaces now will have access to the environment variables through the method System.Environment.GetEnvironmentVariable

        // ------------------------- encapsulate this within the logic of a certain namespace and class that you want -------------------------
        string myEnvironmentVariable = Environment.GetEnvironmentVariable("TELEGRAM_BOT_TOKEN")!; // return type is string? not string
        if (String.IsNullOrEmpty(myEnvironmentVariable)) throw new Exception($"{nameof(myEnvironmentVariable)} was not properly initialized.");
        // ------------------------- encapsulate this within the logic of a certain namespace and class that you want -------------------------
    }
}

USING appsettings.json

dotnet new console --framework net8.0 --use-program-main
dotnet add package Microsoft.Extensions.Configuration
dotnet add package Microsoft.Extensions.Configuration.Json

your <project_name>.csproj file should look like this

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net8.0</TargetFramework>
    <ImplicitUsings>disable</ImplicitUsings> <!-- Enabled by default, I recommend to disable -->
    <Nullable>enable</Nullable>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="Microsoft.Extensions.Configuration" Version="8.0.0" />
    <PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="8.0.0" />
  </ItemGroup>

  <!--
  You must add this otherwise you will receive an error
  Unhandled exception. System.IO.FileNotFoundException: The configuration file 'appsettings.json' was not found and is not optional.
  The expected physical path was '<project_path>\bin\Debug\net8.0\appsettings.json'.
  -->
  <ItemGroup>
    <None Update="appsettings.json"> <!-- Adding this, will make appsettings.json be copied to <project_path>\bin\Debug\net8.0 -->
      <CopyToOutputDirectory>Always</CopyToOutputDirectory>
    </None>
  </ItemGroup>

</Project>

now create a appsettings.json file in your project root folder (where Program.cs is located). this is mine for example:

{
  "Telegram": {
      "BotToken": "0123456789:Aa1Bb2Cc3..."
  },
  "PostgreSQL": {
      "Host": "localhost",
      "Port": "5432",
      "Database": "postgres",
      "Username": "postgres",
      "Password": "1234"
  }
}

Now, in your Program.cs

using System;
using Microsoft.Extensions.Configuration;

namespace ProjectName;

public static class Program {
    internal static void Main() {
        IConfigurationRoot config = new ConfigurationBuilder()
            .AddJsonFile("appsettings.json", optional: false, reloadOnChange: false)
            .Build(); // only this in Main method of Program.cs is NOT enough, only this namespace will have access to the enviroment variables
        // To resolve this problem, create a Centralized Configuration Class, or something like that

        // ------------------------- encapsulate this within the logic of a certain namespace and class that you want -------------------------
        string myEnvironmentVariable = config["Telegram:BotToken"]!; // return type is string? not string
        if (String.IsNullOrEmpty(myEnvironmentVariable)) throw new Exception($"{nameof(myEnvironmentVariable)} was not properly initialized.");
        // ------------------------- encapsulate this within the logic of a certain namespace and class that you want -------------------------
    }
}
Sketchbook answered 12/8 at 15:3 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.