Avatar ·

Как получить имя текущего пользователя Windows в C#?

Всем привет! Я начинающий C# разработчик и пишу небольшое приложение для внутреннего использования в нашей компании. Мне нужно, чтобы программа автоматически определяла, кто из сотрудников сейчас работает за компьютером, и записывала это имя в лог. Я пробовал использовать `Environment.UserName`, но, кажется, он возвращает не совсем то, что нужно. Еще я слышал про `WindowsIdentity.GetCurrent()`, но не понимаю разницы между этими подходами. Вот пример моего кода, который, как я думал, должен работать: ```csharp using System; class Program { static void Main() { string currentUser = Environment.UserName; Console.WriteLine("Текущий пользователь: " + currentUser); } } ``` Проблема в том, что когда я запускаю приложение через Планировщик задач от имени SYSTEM или под другим сервисным аккаунтом, то получаю не имя реального пользователя, который сидит за компьютером, а имя сервиса. Подскажите, как правильно получить именно имя пользователя, который вошел в систему Windows, а не того, под которым запущено приложение? Желательно с примером кода, который работает в .NET Framework 4.8 и .NET 6+. Заранее спасибо! <<>>

Правильный способ получения имени текущего пользователя в Windows

Вы столкнулись с классической проблемой — разница между пользователем, запустившим процесс, и пользователем, вошедшим в систему. Давайте разберем оба подхода.

1. Environment.UserName — быстрый, но ограниченный

Этот метод возвращает имя пользователя, под которым запущен текущий процесс. Он берется из переменной окружения. Плюсы — скорость, минусы — если приложение запущено от SYSTEM или через runas, вы получите не то, что ожидаете.

// Возвращает: "SYSTEM", "IIS APPPOOL\DefaultAppPool" или "Вася"
string name = Environment.UserName;

2. WindowsIdentity.GetCurrent() — для Windows-аутентификации

Возвращает объект WindowsIdentity, который содержит токен безопасности текущего потока. Работает только на Windows, требует ссылки на System.Security.Principal.

using System.Security.Principal;

WindowsIdentity identity = WindowsIdentity.GetCurrent();
string name = identity.Name; // "DOMAIN\Username" или "MACHINE\Username"

Этот способ тоже ориентирован на контекст выполнения, а не на интерактивного пользователя.

3. Решение: получаем пользователя, который вошел в систему (интерактивная сессия)

Чтобы получить пользователя, который реально сидит за компьютером (например, через RDP или локально), нужно использовать Windows API. Вот проверенный способ для .NET:

using System;
using System.Runtime.InteropServices;
using System.Security.Principal;

public class InteractiveUserHelper
{
    [DllImport("wtsapi32.dll", SetLastError = true)]
    private static extern IntPtr WTSOpenServer(string pServerName);

    [DllImport("wtsapi32.dll", SetLastError = true)]
    private static extern void WTSCloseServer(IntPtr hServer);

    [DllImport("wtsapi32.dll", SetLastError = true)]
    private static extern int WTSEnumerateSessions(
        IntPtr hServer,
        int Reserved,
        int Version,
        ref IntPtr ppSessionInfo,
        ref int pCount);

    [DllImport("wtsapi32.dll", SetLastError = true)]
    private static extern bool WTSQuerySessionInformation(
        IntPtr hServer,
        int sessionId,
        int wtsInfoClass,
        out IntPtr ppBuffer,
        out int pBytesReturned);

    [DllImport("wtsapi32.dll", SetLastError = true)]
    private static extern void WTSFreeMemory(IntPtr pMemory);

    private const int WTS_CURRENT_SERVER_HANDLE = 0;
    private const int WTSUserName = 5;
    private const int WTSDomainName = 7;
    private const int WTSConnectState = 9;

    public static string GetInteractiveUserName()
    {
        IntPtr serverHandle = WTSOpenServer(Environment.MachineName);
        try
        {
            IntPtr sessionInfo = IntPtr.Zero;
            int sessionCount = 0;

            if (WTSEnumerateSessions(serverHandle, 0, 1, ref sessionInfo, ref sessionCount) == 0)
                return null;

            try
            {
                // Размер структуры WTS_SESSION_INFO (4 байта SessionId + 4 байта pWinStationName + 4 байта State)
                int structSize = Marshal.SizeOf(typeof(int)) * 3;
                IntPtr current = sessionInfo;

                for (int i = 0; i < sessionCount; i++)
                {
                    int sessionId = Marshal.ReadInt32(current);
                    int state = Marshal.ReadInt32(current + 8); // State — третье поле

                    // Нас интересует активная сессия (WTSActive = 0)
                    if (state == 0)
                    {
                        string userName = QuerySessionInfo(serverHandle, sessionId, WTSUserName);
                        string domain = QuerySessionInfo(serverHandle, sessionId, WTSDomainName);

                        if (!string.IsNullOrEmpty(userName))
                            return $"{domain}\\{userName}";
                    }

                    current += structSize;
                }
            }
            finally
            {
                WTSFreeMemory(sessionInfo);
            }
        }
        finally
        {
            WTSCloseServer(serverHandle);
        }

        return Environment.UserName; // fallback
    }

    private static string QuerySessionInfo(IntPtr serverHandle, int sessionId, int infoClass)
    {
        IntPtr buffer = IntPtr.Zero;
        int bytesReturned = 0;

        if (WTSQuerySessionInformation(serverHandle, sessionId, infoClass, out buffer, out bytesReturned))
        {
            try
            {
                return Marshal.PtrToStringUni(buffer);
            }
            finally
            {
                WTSFreeMemory(buffer);
            }
        }
        return null;
    }
}

// Использование:
string realUser = InteractiveUserHelper.GetInteractiveUserName();
Console.WriteLine($"Реальный пользователь: {realUser}");

Когда что использовать?

  • Environment.UserName — для простых логов, когда приложение запускается пользователем напрямую.
  • WindowsIdentity.GetCurrent() — для проверки прав доступа, работы с Impersonation.
  • WTS API — когда нужно узнать, кто сидит за компьютером, независимо от того, как запущено приложение (сервис, планировщик, etc.).

Обратите внимание: WTS API работает только на Windows и требует прав администратора для некоторых вызовов. Для .NET Core/.NET 5+ код будет тем же самым, так как используется P/Invoke.

<<>>

Альтернатива через WMI (проще, но медленнее)

Если вам не хочется возиться с P/Invoke, можно использовать WMI-запросы. Этот подход работает в .NET Framework и .NET Core (с пакетом System.Management).

using System;
using System.Management;

public static string GetCurrentUserViaWmi()
{
    string query = "SELECT UserName FROM Win32_ComputerSystem";
    using (var searcher = new ManagementObjectSearcher(query))
    {
        foreach (ManagementObject obj in searcher.Get())
        {
            string userName = obj["UserName"]?.ToString();
            if (!string.IsNullOrEmpty(userName))
                return userName;
        }
    }
    return Environment.UserName;
}

// Использование:
string user = GetCurrentUserViaWmi();
Console.WriteLine($"Пользователь (WMI): {user}");

Этот метод возвращает домен\имя пользователя, который вошел в систему. Минусы: требуется ссылка на System.Management.dll, работает медленнее, чем прямой вызов WinAPI, и может быть заблокирован политиками безопасности.

Для большинства случаев я рекомендую первый вариант с WTS API как самый надежный, но WMI-подход — отличная альтернатива для быстрых прототипов.