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

如何获取目标计算机所有活跃会话并模拟执行壁纸修改代码?

问题描述

我有如下代码用于修改壁纸:

public static void Registrar(string caminho, Estilo estilo)
{
    try
    {
        RegistryKey registryKey = Registry.CurrentUser.OpenSubKey(@"Control Panel\Desktop", true);

        switch (estilo)
        {
            case Estilo.Fill:
                registryKey.SetValue(@"WallpaperStyle", 10.ToString());
                registryKey.SetValue(@"TileWallpaper", 0.ToString());
                break;
            case Estilo.Fit:
                registryKey.SetValue(@"WallpaperStyle", 6.ToString());
                registryKey.SetValue(@"TileWallpaper", 0.ToString());
                break;
            case Estilo.Span:
                registryKey.SetValue(@"WallpaperStyle", 22.ToString());
                registryKey.SetValue(@"TileWallpaper", 0.ToString());
                break;
            case Estilo.Stretch:
                registryKey.SetValue(@"WallpaperStyle", 2.ToString());
                registryKey.SetValue(@"TileWallpaper", 0.ToString());
                break;
            case Estilo.Tile:
                registryKey.SetValue(@"WallpaperStyle", 0.ToString());
                registryKey.SetValue(@"TileWallpaper", 1.ToString());
                break;
            case Estilo.Center:
                registryKey.SetValue(@"WallpaperStyle", 0.ToString());
                registryKey.SetValue(@"TileWallpaper", 0.ToString());
                break;
            default:
                registryKey.SetValue(@"WallpaperStyle", 10.ToString());
                registryKey.SetValue(@"TileWallpaper", 0.ToString());
                break;
        }

        SystemParametersInfo(SPI_SETDESKWALLPAPER, 0, caminho, SPIF_UPDATEINIFILE | SPIF_SENDWININICHANGE);
    }
    catch
    {
        throw;
    }
}

背景:需要向组织内多台机器发送并执行程序,通过LogMeIn登录上传执行,但当前代码仅修改LogMeIn登录用户的壁纸。

问题:需要获取目标计算机的所有活跃会话,模拟这些会话执行上述代码,实现为所有会话修改壁纸。

补充说明:尝试过以下方案,但出现“无法模拟”错误:

while (!stoppingToken.IsCancellationRequested)
{
    IntPtr usuarioSessaoToken = IntPtr.Zero;

    Dictionary<string, IntPtr> usuariosLogados = new Dictionary<string, IntPtr>();

    foreach (Process process in Process.GetProcesses())
    {
        try
        {
            OpenProcessToken(process.Handle, TOKEN_QUERY, out usuarioSessaoToken);

            if (usuarioSessaoToken != null && usuarioSessaoToken != IntPtr.Zero)
            {
                using (WindowsIdentity windowsIdentity = new WindowsIdentity(usuarioSessaoToken))
                {
                    if (!usuariosLogados.ContainsKey(windowsIdentity.Name) &&
                        !windowsIdentity.Name.Contains("NT ") &&
                        !windowsIdentity.Name.Contains("Font ") &&
                        !windowsIdentity.Name.Contains("Window "))
                    {
                        usuariosLogados.Add(windowsIdentity.Name, usuarioSessaoToken);
                    }
                }
            }
        }
        catch
        {
        }
    }

    foreach (var usuarioLogado in usuariosLogados)
    {
        Console.WriteLine("{0}: {1}", usuarioLogado.Value, usuarioLogado.Key);

        using (WindowsIdentity windowsIdentity = new WindowsIdentity(usuarioLogado.Value))
        {
            WindowsIdentity.RunImpersonated(windowsIdentity.AccessToken, () =>
            {
                _OnSite.InicializarProcesso(modeloConfiguracao);
            });
        }

        if (usuarioLogado.Value != IntPtr.Zero)
        {
            CloseHandle(usuarioLogado.Value);
        }
    }

    if (usuarioSessaoToken != IntPtr.Zero)
    {
        CloseHandle(usuarioSessaoToken);
    }

    ArquivoLog.EscreverInformacao("PROCESSO DE GERENCIAMENTO INTERNO FINALIZADO.");
    ArquivoLog.EscreverInformacao("ENTRARÁ EM ESPERA DE TRÊS (3) HORAS.");

    await Task.Delay(TimeSpan.FromHours(3), stoppingToken);
}

求可行解决方案?


可行解决方案

核心思路

要修改所有活跃用户的壁纸,关键在于:

  • 正确获取每个活跃用户的SID和会话ID
  • 直接修改对应用户的注册表项(绕过模拟权限问题)
  • 向对应会话发送壁纸更新通知

具体实现步骤

1. 定义必要的Win32 API调用

using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Security.Principal;
using Microsoft.Win32;

public static class Win32Api
{
    public const int SPI_SETDESKWALLPAPER = 0x0014;
    public const int SPIF_UPDATEINIFILE = 0x01;
    public const int SPIF_SENDWININICHANGE = 0x02;

    [DllImport("user32.dll", CharSet = CharSet.Auto)]
    public static extern bool SystemParametersInfo(int uAction, int uParam, string lpvParam, int fuWinIni);

    [DllImport("wtsapi32.dll")]
    public static extern bool WTSEnumerateSessions(IntPtr hServer, int Reserved, int Version, ref IntPtr ppSessionInfo, ref int pCount);

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

    [DllImport("wtsapi32.dll", CharSet = CharSet.Auto)]
    public static extern bool WTSQuerySessionInformation(IntPtr hServer, int sessionId, WTS_INFO_CLASS wtsInfoClass, ref IntPtr ppBuffer, ref int pBytesReturned);

    [DllImport("advapi32.dll", SetLastError = true)]
    public static extern bool OpenProcessToken(IntPtr ProcessHandle, uint DesiredAccess, out IntPtr TokenHandle);

    [DllImport("advapi32.dll", SetLastError = true)]
    public static extern bool DuplicateTokenEx(IntPtr hExistingToken, uint dwDesiredAccess, IntPtr lpTokenAttributes, SECURITY_IMPERSONATION_LEVEL ImpersonationLevel, TOKEN_TYPE TokenType, out IntPtr phNewToken);

    [DllImport("kernel32.dll", SetLastError = true)]
    public static extern bool CloseHandle(IntPtr hObject);

    public enum WTS_INFO_CLASS
    {
        WTSUserName = 5,
        WTSDomainName = 7
    }

    public enum SECURITY_IMPERSONATION_LEVEL
    {
        SecurityImpersonation
    }

    public enum TOKEN_TYPE
    {
        TokenPrimary = 1
    }

    public struct WTS_SESSION_INFO
    {
        public int SessionId;
        public IntPtr pWinStationName;
        public WTS_CONNECTSTATE_CLASS State;
    }

    public enum WTS_CONNECTSTATE_CLASS
    {
        WTSActive
    }
}

2. 获取所有活跃用户会话ID

public static List<int> GetActiveSessionIds()
{
    List<int> activeSessions = new List<int>();
    IntPtr serverHandle = IntPtr.Zero;
    IntPtr sessionInfoPtr = IntPtr.Zero;
    int sessionCount = 0;

    try
    {
        if (Win32Api.WTSEnumerateSessions(serverHandle, 0, 1, ref sessionInfoPtr, ref sessionCount))
        {
            int sessionSize = Marshal.SizeOf(typeof(Win32Api.WTS_SESSION_INFO));
            IntPtr currentSessionPtr = sessionInfoPtr;

            for (int i = 0; i < sessionCount; i++)
            {
                Win32Api.WTS_SESSION_INFO sessionInfo = (Win32Api.WTS_SESSION_INFO)Marshal.PtrToStructure(currentSessionPtr, typeof(Win32Api.WTS_SESSION_INFO));
                if (sessionInfo.State == Win32Api.WTS_CONNECTSTATE_CLASS.WTSActive)
                {
                    activeSessions.Add(sessionInfo.SessionId);
                }
                currentSessionPtr = IntPtr.Add(currentSessionPtr, sessionSize);
            }
        }
    }
    finally
    {
        if (sessionInfoPtr != IntPtr.Zero)
        {
            Win32Api.WTSFreeMemory(sessionInfoPtr);
        }
    }

    return activeSessions;
}

3. 修改指定会话用户的壁纸

public static void SetWallpaperForSession(int sessionId, string wallpaperPath, Estilo estilo)
{
    IntPtr serverHandle = IntPtr.Zero;
    IntPtr userNamePtr = IntPtr.Zero;
    IntPtr domainNamePtr = IntPtr.Zero;
    int bytesReturned = 0;

    try
    {
        // 获取会话用户名和域名
        Win32Api.WTSQuerySessionInformation(serverHandle, sessionId, Win32Api.WTS_INFO_CLASS.WTSUserName, ref userNamePtr, ref bytesReturned);
        string userName = Marshal.PtrToStringAuto(userNamePtr);
        Win32Api.WTSQuerySessionInformation(serverHandle, sessionId, Win32Api.WTS_INFO_CLASS.WTSDomainName, ref domainNamePtr, ref bytesReturned);
        string domainName = Marshal.PtrToStringAuto(domainNamePtr);
        string fullUserName = $"{domainName}\\{userName}";

        // 获取用户SID
        NTAccount ntAccount = new NTAccount(fullUserName);
        SecurityIdentifier sid = (SecurityIdentifier)ntAccount.Translate(typeof(SecurityIdentifier));
        string sidString = sid.ToString();

        // 修改用户注册表项
        using (RegistryKey userKey = Registry.Users.OpenSubKey($@"{sidString}\Control Panel\Desktop", true))
        {
            if (userKey == null) return;

            switch (estilo)
            {
                case Estilo.Fill:
                    userKey.SetValue("WallpaperStyle", "10", RegistryValueKind.String);
                    userKey.SetValue("TileWallpaper", "0", RegistryValueKind.String);
                    break;
                case Estilo.Fit:
                    userKey.SetValue("WallpaperStyle", "6", RegistryValueKind.String);
                    userKey.SetValue("TileWallpaper", "0", RegistryValueKind.String);
                    break;
                case Estilo.Span:
                    userKey.SetValue("WallpaperStyle", "22", RegistryValueKind.String);
                    userKey.SetValue("TileWallpaper", "0", RegistryValueKind.String);
                    break;
                case Estilo.Stretch:
                    userKey.SetValue("WallpaperStyle", "2", RegistryValueKind.String);
                    userKey.SetValue("TileWallpaper", "0", RegistryValueKind.String);
                    break;
                case Estilo.Tile:
                    userKey.SetValue("WallpaperStyle", "0", RegistryValueKind.String);
                    userKey.SetValue("TileWallpaper", "1", RegistryValueKind.String);
                    break;
                case Estilo.Center:
                    userKey.SetValue("WallpaperStyle", "0", RegistryValueKind.String);
                    userKey.SetValue("TileWallpaper", "0", RegistryValueKind.String);
                    break;
                default:
                    userKey.SetValue("WallpaperStyle", "10", RegistryValueKind.String);
                    userKey.SetValue("TileWallpaper", "0", RegistryValueKind.String);
                    break;
            }
            userKey.SetValue("Wallpaper", wallpaperPath, RegistryValueKind.String);
        }

        // 获取会话进程令牌并发送更新通知
        IntPtr primaryToken = GetSessionPrimaryToken(sessionId);
        if (primaryToken != IntPtr.Zero)
        {
            WindowsIdentity.RunImpersonated(primaryToken, () =>
            {
                Win32Api.SystemParametersInfo(Win32Api.SPI_SETDESKWALLPAPER, 0, wallpaperPath, Win32Api.SPIF_UPDATEINIFILE | Win32Api.SPIF_SENDWININICHANGE);
            });
            Win32Api.CloseHandle(primaryToken);
        }
    }
    finally
    {
        if (userNamePtr != IntPtr.Zero) Win32Api.WTSFreeMemory(userNamePtr);
        if (domainNamePtr != IntPtr.Zero) Win32Api.WTSFreeMemory(domainNamePtr);
    }
}

// 获取会话主令牌
private static IntPtr GetSessionPrimaryToken(int sessionId)
{
    foreach (Process process in Process.GetProcesses())
    {
        if (process.SessionId == sessionId && process.ProcessName.Equals("explorer", StringComparison.OrdinalIgnoreCase))
        {
            IntPtr processToken = IntPtr.Zero;
            if (Win32Api.OpenProcessToken(process.Handle, 0x0002 | 0x0008, out processToken))
            {
                IntPtr primaryToken = IntPtr.Zero;
                if (Win32Api.DuplicateTokenEx(processToken, 0x0010 | 0x0020 | 0x0008, IntPtr.Zero, Win32Api.SECURITY_IMPERSONATION_LEVEL.SecurityImpersonation, Win32Api.TOKEN_TYPE.TokenPrimary, out primaryToken))
                {
                    Win32Api.CloseHandle(processToken);
                    return primaryToken;
                }
                Win32Api.CloseHandle(processToken);
            }
            break;
        }
    }
    return IntPtr.Zero;
}

4. 批量修改所有活跃会话壁纸

public static void SetWallpaperForAllActiveSessions(string wallpaperPath, Estilo estilo)
{
    List<int> activeSessions = GetActiveSessionIds();
    foreach (int sessionId in activeSessions)
    {
        try
        {
            SetWallpaperForSession(sessionId, wallpaperPath, estilo);
        }
        catch (Exception ex)
        {
            ArquivoLog.EscreverErro($"无法修改会话 {sessionId} 的壁纸: {ex.Message}");
        }
    }
}

关键注意事项

  • 权限要求:程序必须以管理员权限运行,否则无法访问其他用户的注册表和进程令牌。
  • 路径要求:壁纸路径必须是目标机器的本地绝对路径,不能使用网络共享路径(除非映射为本地驱动器)。
  • 资源释放:所有Win32 API获取的指针和句柄必须及时释放,避免内存泄漏。

内容的提问来源于stack exchange,提问作者Rodrigo Duarte

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.20 10:22:05