Kraang Prime
Kraang Prime

Reputation: 10479

How to set Environment variables permanently in C#

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.

Upvotes: 14

Views: 32951

Answers (5)

komote7665
komote7665

Reputation: 53

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 -------------------------
    }
}

Upvotes: 0

Bimo
Bimo

Reputation: 6587

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);
}

Upvotes: 0

Mrinal Kamboj
Mrinal Kamboj

Reputation: 11482

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

Upvotes: 2

ChrisM
ChrisM

Reputation: 1168

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

Upvotes: 2

Yuval Itzchakov
Yuval Itzchakov

Reputation: 149518

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);
}

Upvotes: 24

Related Questions