I am trying to make use of RegenerateUserEnvironment
method so that I can refresh environment variables without restarting the instance. However, it ends up with an access violation error. I am not sure but to me, a process updating its own memory should not cause an error.
Things I tried:
- Passing the actual token of current process. No chance.
- Duplicating current token with all possible permissions. No chance.
- Running as administrator, in case it is about
SeDebugPrivilege
or similar, but no chance.
Any ideas?
Code:
using System;
using System.Runtime.InteropServices;
using System.Security.Principal;
using Microsoft.Win32;
namespace EnvVarControl
{
internal static partial class Program
{
#region Pinvoke
public const uint STANDARD_RIGHTS_READ = 0x00020000;
public const uint STANDARD_RIGHTS_REQUIRED = 0x000F0000;
public const uint TOKEN_ADJUST_DEFAULT = 0x0080;
public const uint TOKEN_ADJUST_GROUPS = 0x0040;
public const uint TOKEN_ADJUST_PRIVILEGES = 0x0020;
public const uint TOKEN_ADJUST_SESSIONID = 0x0100;
public const uint TOKEN_ALL_ACCESS = STANDARD_RIGHTS_REQUIRED | TOKEN_ASSIGN_PRIMARY |
TOKEN_DUPLICATE | TOKEN_IMPERSONATE | TOKEN_QUERY | TOKEN_QUERY_SOURCE |
TOKEN_ADJUST_PRIVILEGES | TOKEN_ADJUST_GROUPS | TOKEN_ADJUST_DEFAULT |
TOKEN_ADJUST_SESSIONID;
public const uint TOKEN_ASSIGN_PRIMARY = 0x0001;
public const uint TOKEN_DUPLICATE = 0x0002;
public const uint TOKEN_IMPERSONATE = 0x0004;
public const uint TOKEN_QUERY = 0x0008;
public const uint TOKEN_QUERY_SOURCE = 0x0010;
public const uint TOKEN_READ = STANDARD_RIGHTS_READ | TOKEN_QUERY;
public enum SECURITY_IMPERSONATION_LEVEL
{
SecurityAnonymous,
SecurityIdentification,
SecurityImpersonation,
SecurityDelegation
}
public enum TOKEN_TYPE
{
TokenPrimary = 1,
TokenImpersonation = 2
}
[StructLayout(LayoutKind.Sequential)]
public struct SECURITY_ATTRIBUTES
{
public int Length;
public IntPtr lpSecurityDescriptor;
public bool bInheritHandle;
}
[DllImport("advapi32.dll", SetLastError = true, EntryPoint = "DuplicateTokenEx")]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool DuplicateTokenEx(IntPtr ExistingTokenHandle, uint dwDesiredAccess, ref SECURITY_ATTRIBUTES lpThreadAttributes, TOKEN_TYPE TokenType, SECURITY_IMPERSONATION_LEVEL ImpersonationLevel, ref IntPtr DuplicateTokenHandle);
[LibraryImport("shell32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
public static partial bool RegenerateUserEnvironment(IntPtr hToken,
[MarshalAs(UnmanagedType.Bool)] bool bSet);
#endregion Pinvoke
public static void Main()
{
// Subscribe to the UserPreferenceChanged event
SystemEvents.UserPreferenceChanged = OnUserPreferenceChanged;
Console.WriteLine("Press any key to exit...");
Console.ReadKey();
}
private static void CheckPathBeforeAndAfterRegenerateUserEnvironment()
{
// Get the value of the PATH environment variable before calling RegenerateUserEnvironment
var pathBefore = Environment.GetEnvironmentVariable("PATH");
Console.WriteLine($"BEFORE: {pathBefore}\n");
RefreshEnvironmentVariables();
// Get the value of the PATH environment variable after calling RegenerateUserEnvironment
var pathAfter = Environment.GetEnvironmentVariable("PATH");
Console.WriteLine($"AFTER: {pathAfter}\n");
// Compare the two values of the PATH environment variable
if (pathBefore != pathAfter)
{
Console.WriteLine("Warning: The value of the PATH environment variable has changed!");
}
else
{
Console.WriteLine("No change.");
}
}
private static void OnUserPreferenceChanged(object sender, UserPreferenceChangedEventArgs e)
{
// Check if the environment variables have changed
if (e.Category == UserPreferenceCategory.General)
{
// Check the value of the PATH environment variable before and after calling RegenerateUserEnvironment
CheckPathBeforeAndAfterRegenerateUserEnvironment();
}
}
private static void RefreshEnvironmentVariables()
{
// Open the process token and duplicate
var token = WindowsIdentity.GetCurrent().AccessToken.DangerousGetHandle();
var duplicatededToken = DuplicateToken(token);
// Call the RegenerateUserEnvironment function to update the environment variables
if (!RegenerateUserEnvironment(duplicatededToken, true))
{
throw new System.ComponentModel.Win32Exception(Marshal.GetLastWin32Error());
}
}
private static nint DuplicateToken(nint token)
{
var sa = new SECURITY_ATTRIBUTES
{
bInheritHandle = false
};
sa.Length = Marshal.SizeOf(sa);
sa.lpSecurityDescriptor = 0;
const TOKEN_TYPE TokenType = TOKEN_TYPE.TokenPrimary;
const SECURITY_IMPERSONATION_LEVEL SecurityImpersonation = SECURITY_IMPERSONATION_LEVEL.SecurityIdentification;
var DupedToken = new IntPtr(0);
if (!DuplicateTokenEx(token, TOKEN_ALL_ACCESS, ref sa, TokenType, SecurityImpersonation, ref DupedToken))
{
throw new System.ComponentModel.Win32Exception(Marshal.GetLastWin32Error());
}
return DupedToken;
}
}
}
Error message:
Exception thrown at ... (shell32.dll) in EnvVarControl.exe: ...: Access violation writing location ...
Edit: Based on the very accurate answer, I paste the correct piece of code below:
using System;
using System.Runtime.InteropServices;
using Microsoft.Win32;
namespace EnvVarControl
{
internal static partial class Program
{
#region Pinvoke
[LibraryImport("shell32.dll", SetLastError = true)]
[UnmanagedCallConv(CallConvs = new Type[] { typeof(System.Runtime.CompilerServices.CallConvStdcall) })]
[return: MarshalAs(UnmanagedType.Bool)]
public static partial bool RegenerateUserEnvironment(out IntPtr pPrevEnv, [MarshalAs(UnmanagedType.Bool)] bool bSetCurrentEnv);
#endregion Pinvoke
public static void Main()
{
// Subscribe to the UserPreferenceChanged event
SystemEvents.UserPreferenceChanged = OnUserPreferenceChanged;
Console.WriteLine("Press any key to exit...");
Console.ReadKey();
}
private static void CheckPathBeforeAndAfterRegenerateUserEnvironment()
{
// Get the value of the PATH environment variable before calling RegenerateUserEnvironment
var pathBefore = Environment.GetEnvironmentVariable("PATH");
Console.WriteLine($"BEFORE: {pathBefore}\n");
RefreshEnvironmentVariables();
// Get the value of the PATH environment variable after calling RegenerateUserEnvironment
var pathAfter = Environment.GetEnvironmentVariable("PATH");
Console.WriteLine($"AFTER: {pathAfter}\n");
// Compare the two values of the PATH environment variable
if (pathBefore != pathAfter)
{
Console.WriteLine("Warning: The value of the PATH environment variable has changed!");
}
else
{
Console.WriteLine("No change.");
}
}
private static void OnUserPreferenceChanged(object sender, UserPreferenceChangedEventArgs e)
{
// Check if the environment variables have changed
if (e.Category == UserPreferenceCategory.General)
{
// Check the value of the PATH environment variable before and after calling RegenerateUserEnvironment
CheckPathBeforeAndAfterRegenerateUserEnvironment();
}
}
private static void RefreshEnvironmentVariables()
{
// Call the RegenerateUserEnvironment function to update the environment variables
// Reference: https://stackoverflow.com/questions/74913727/trying-to-create-a-demo-using-regenerateuserenvironment-throwing-access-violatio/74914038#74914038
if (!RegenerateUserEnvironment(out _, true))
{
throw new System.ComponentModel.Win32Exception(Marshal.GetLastWin32Error());
}
}
}
}
CodePudding user response:
RegenerateUserEnvironment
does not need a token; the first parameter is a 2-level pointer, which will be set to the previous environmet block.
define RegenerateUserEnvironment
as:
[DllImport("shell32.dll", CallingConvention = CallingConvention.StdCall, SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool RegenerateUserEnvironment(out IntPtr pPrevEnv, [MarshalAs(UnmanagedType.Bool)] bool bSetCurrentEnv);
and call it like follows:
RegenerateUserEnvironment(out IntPtr pPrevEnv, true)