You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何通过C#实现Windows用户远程登录?gRPC服务开机启动场景问询

实现远程登录用户A的可行方案

1. 命令行/PowerShell 实现方式

方法1:利用tscon切换已有会话

如果用户A已存在断开的会话(计划任务以用户A启动服务时通常会创建会话),可通过以下步骤切换到控制台登录:

  • 先查询用户A的会话ID:
query user
  • 找到用户A对应的会话ID(比如2),执行切换命令:
tscon 2 /dest:console

该命令会将用户A的后台会话切换到当前控制台界面,完成登录操作。

方法2:修改注册表实现自动登录

若需要触发一次性自动登录,可修改Windows登录注册表项:

# 开启自动登录
Set-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon" -Name "AutoAdminLogon" -Value "1"
# 设置默认登录用户名
Set-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon" -Name "DefaultUserName" -Value "用户A"
# 设置默认登录密码
Set-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon" -Name "DefaultPassword" -Value "用户A的密码"

执行后重启机器或刷新登录界面即可自动登录。注意:操作完成后务必将AutoAdminLogon改回0,避免后续自动登录带来的安全风险。

2. C# 实现方式

方式1:调用Windows API切换会话

通过Wtsapi32.dll的API枚举会话并连接到目标用户的会话:

using System;
using System.Runtime.InteropServices;

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

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

    [DllImport("wtsapi32.dll", SetLastError = true)]
    private static extern bool WTSConnectSession(uint sessionId, uint targetSessionId, string password, bool bWait);

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

    private const int WTS_CURRENT_SERVER_HANDLE = 0;
    private const int WTS_SESSION_INFO_LEVEL = 1;

    public static bool SwitchToUserSession(string userName)
    {
        IntPtr pSessionInfo = IntPtr.Zero;
        int sessionCount = 0;

        try
        {
            if (!WTSEnumerateSessions((IntPtr)WTS_CURRENT_SERVER_HANDLE, 0, WTS_SESSION_INFO_LEVEL, ref pSessionInfo, ref sessionCount))
                return false;

            int size = Marshal.SizeOf(typeof(WTS_SESSION_INFO));
            for (int i = 0; i < sessionCount; i++)
            {
                IntPtr currentSession = IntPtr.Add(pSessionInfo, i * size);
                WTS_SESSION_INFO sessionInfo = (WTS_SESSION_INFO)Marshal.PtrToStructure(currentSession, typeof(WTS_SESSION_INFO));
                
                string sessionUserName = GetSessionUserName(sessionInfo.SessionId);
                if (sessionUserName.Equals(userName, StringComparison.OrdinalIgnoreCase))
                {
                    return WTSConnectSession(sessionInfo.SessionId, 0, null, true);
                }
            }
            return false;
        }
        finally
        {
            if (pSessionInfo != IntPtr.Zero)
                WTSFreeMemory(pSessionInfo);
        }
    }

    private static string GetSessionUserName(uint sessionId)
    {
        IntPtr buffer = IntPtr.Zero;
        uint bytesReturned;
        if (WTSQuerySessionInformation((IntPtr)WTS_CURRENT_SERVER_HANDLE, sessionId, WTS_INFO_CLASS.WTSUserName, out buffer, out bytesReturned) && buffer != IntPtr.Zero)
        {
            try
            {
                return Marshal.PtrToStringAnsi(buffer);
            }
            finally
            {
                WTSFreeMemory(buffer);
            }
        }
        return string.Empty;
    }
}

[StructLayout(LayoutKind.Sequential)]
private struct WTS_SESSION_INFO
{
    public uint SessionId;
    public string pWinStationName;
    public WTS_CONNECTSTATE_CLASS State;
}

public enum WTS_CONNECTSTATE_CLASS
{
    WTSActive,
    WTSConnected,
    WTSConnectQuery,
    WTSShadow,
    WTSDisconnected,
    WTSIdle,
    WTSListen,
    WTSReset,
    WTSDown,
    WTSInit
}

public enum WTS_INFO_CLASS
{
    WTSUserName = 5
}

方式2:调用命令行执行tscon

通过C#启动进程执行tscon命令:

using System.Diagnostics;
using System.Linq;

public static void TriggerUserLogin()
{
    // 查询用户会话
    var queryProcess = new Process
    {
        StartInfo = new ProcessStartInfo
        {
            FileName = "query",
            Arguments = "user",
            RedirectStandardOutput = true,
            UseShellExecute = false,
            CreateNoWindow = true
        }
    };
    queryProcess.Start();
    string output = queryProcess.StandardOutput.ReadToEnd();
    queryProcess.WaitForExit();

    // 解析用户A的会话ID
    var sessionLine = output.Split('\n').FirstOrDefault(line => line.Contains("用户A"));
    if (sessionLine == null) return;
    
    string[] parts = sessionLine.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
    if (!int.TryParse(parts[1], out int sessionId)) return;

    // 执行切换命令
    var tsconProcess = new Process
    {
        StartInfo = new ProcessStartInfo
        {
            FileName = "tscon",
            Arguments = $"{sessionId} /dest:console",
            UseShellExecute = false,
            CreateNoWindow = true
        }
    };
    tsconProcess.Start();
    tsconProcess.WaitForExit();
}

3. 远程触发的实现思路

要通过移动应用远程触发登录:

  • 在现有gRPC服务中新增TriggerUserLogin方法,内部调用上述命令行/PowerShell或C#逻辑。
  • 移动应用通过gRPC客户端调用该方法,触发登录操作。
  • 注意:执行这些操作需要管理员权限,确保用户A账户具备足够权限,或代码中提升权限执行。

安全注意事项

  • 注册表自动登录会明文存储密码,操作后必须恢复AutoAdminLogon为0,避免泄露风险。
  • tscon命令需要执行账户拥有会话切换权限(通常为管理员或会话所有者)。
  • 确保gRPC通信启用TLS加密,防止未授权调用。

内容的提问来源于stack exchange,提问作者Álvaro García

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.07 04:42:56