File: src\libraries\System.Private.CoreLib\src\System\Environment.Variables.Windows.cs
Web Access
Project: src\src\coreclr\System.Private.CoreLib\System.Private.CoreLib.csproj (System.Private.CoreLib)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System.Collections;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Text;
 
namespace System
{
    public static partial class Environment
    {
        private static string? GetEnvironmentVariableCore(string variable)
        {
            var builder = new ValueStringBuilder(stackalloc char[128]);
 
            uint length;
            while ((length = Interop.Kernel32.GetEnvironmentVariable(variable, ref builder.GetPinnableReference(), (uint)builder.Capacity)) > builder.Capacity)
            {
                builder.EnsureCapacity((int)length);
            }
 
            if (length == 0 && Marshal.GetLastPInvokeError() == Interop.Errors.ERROR_ENVVAR_NOT_FOUND)
            {
                builder.Dispose();
                return null;
            }
 
            builder.Length = (int)length;
            return builder.ToString();
        }
 
        /// <summary>GetEnvironmentVariableCore that avoids using the ArrayPool. Environment variables must be less than 128 characters in length or won't be found.</summary>
        internal static string? GetEnvironmentVariableCore_NoArrayPool(string variable)
        {
            Span<char> span = stackalloc char[128];
            uint length = Interop.Kernel32.GetEnvironmentVariable(variable, ref MemoryMarshal.GetReference(span), (uint)span.Length);
            return length > 0 && length <= span.Length ?
                span.Slice(0, (int)length).ToString() :
                null;
        }
 
        private static void SetEnvironmentVariableCore(string variable, string? value)
        {
            if (!Interop.Kernel32.SetEnvironmentVariable(variable, value))
            {
                int errorCode = Marshal.GetLastPInvokeError();
                switch (errorCode)
                {
                    case Interop.Errors.ERROR_ENVVAR_NOT_FOUND:
                        // Allow user to try to clear a environment variable
                        return;
 
                    case Interop.Errors.ERROR_FILENAME_EXCED_RANGE:
                        // The error message from Win32 is "The filename or extension is too long",
                        // which is not accurate.
                        throw new ArgumentException(SR.Argument_LongEnvVarValue);
 
                    case Interop.Errors.ERROR_NOT_ENOUGH_MEMORY:
                    case Interop.Errors.ERROR_NO_SYSTEM_RESOURCES:
                        throw new OutOfMemoryException(Marshal.GetPInvokeErrorMessage(errorCode));
 
                    default:
                        throw new ArgumentException(Marshal.GetPInvokeErrorMessage(errorCode));
                }
            }
        }
 
        public static unsafe IDictionary GetEnvironmentVariables()
        {
            // Format for GetEnvironmentStrings is:
            //     [=HiddenVar=value\0]* [Variable=value\0]* \0
            // See the description of Environment Blocks in MSDN's CreateProcess
            // page (null-terminated array of null-terminated strings). Note
            // the =HiddenVar's aren't always at the beginning.
 
            // Copy strings out, parsing into pairs and inserting into the table.
            // The first few environment variable entries start with an '='.
            // The current working directory of every drive (except for those drives
            // you haven't cd'ed into in your DOS window) are stored in the
            // environment block (as =C:=pwd) and the program's exit code is
            // as well (=ExitCode=00000000).
 
            char* stringPtr = Interop.Kernel32.GetEnvironmentStringsW();
            if (stringPtr == null)
            {
                throw new OutOfMemoryException();
            }
 
            try
            {
                var results = new Hashtable();
 
                char* currentPtr = stringPtr;
                while (true)
                {
                    ReadOnlySpan<char> variable = MemoryMarshal.CreateReadOnlySpanFromNullTerminated(currentPtr);
                    if (variable.IsEmpty)
                    {
                        break;
                    }
 
                    // Find the = separating the key and value. We skip entries that begin with =.  We also skip entries that don't
                    // have =, which can happen on some older OSes when the environment block gets corrupted.
                    int i = variable.IndexOf('=');
                    if (i > 0)
                    {
                        // Add the key and value.
                        string key = new string(variable.Slice(0, i));
                        string value = new string(variable.Slice(i + 1));
                        try
                        {
                            // Add may throw if the environment block was corrupted leading to duplicate entries.
                            // We allow such throws and eat them (rather than proactively checking for duplication)
                            // to provide a non-fatal notification about the corruption.
                            results.Add(key, value);
                        }
                        catch (ArgumentException) { }
                    }
 
                    // Move to the end of this variable, after its terminator.
                    currentPtr += variable.Length + 1;
                }
 
                return results;
            }
            finally
            {
                Interop.BOOL success = Interop.Kernel32.FreeEnvironmentStringsW(stringPtr);
                Debug.Assert(success != Interop.BOOL.FALSE);
            }
        }
    }
}