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

管理员权限下用CreateDesktop创建虚拟桌面,无法启动explorer.exe

在管理员权限进程中,将Explorer.exe启动到自定义虚拟桌面的解决方案

问题描述

通过CreateDesktop API创建虚拟桌面后,尝试在该桌面启动explorer.exe,但程序始终在默认桌面运行。当前进程必须以管理员权限运行才能完整访问虚拟桌面,但管理员权限似乎是导致启动失败的根源。曾尝试模拟普通用户启动,同样未能成功。目标是让explorer.exe在指定虚拟桌面加载完整组件(桌面图标、壁纸、任务栏等)。

提供的原始代码

using Microsoft.Win32;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace VirtualDesktop
{
    class Process_Handler
    {
        string DesktopName;
        public Process_Handler(string DesktopName)
        {
            this.DesktopName = DesktopName;
        }
        [DllImport("kernel32.dll")]
        private static extern bool CreateProcess(
         string lpApplicationName,
         string lpCommandLine,
         IntPtr lpProcessAttributes,
         IntPtr lpThreadAttributes,
         bool bInheritHandles,
         int dwCreationFlags,
         IntPtr lpEnvironment,
         string lpCurrentDirectory,
         ref STARTUPINFO lpStartupInfo,
         ref PROCESS_INFORMATION lpProcessInformation);

        [StructLayout(LayoutKind.Sequential)]
        struct STARTUPINFO
        {
            public Int32 cb;
            public string lpReserved;
            public string lpDesktop;
            public string lpTitle;
            public Int32 dwX;
            public Int32 dwY;
            public Int32 dwXSize;
            public Int32 dwYSize;
            public Int32 dwXCountChars;
            public Int32 dwYCountChars;
            public Int32 dwFillAttribute;
            public Int32 dwFlags;
            public Int16 wShowWindow;
            public Int16 cbReserved2;
            public IntPtr lpReserved2;
            public IntPtr hStdInput;
            public IntPtr hStdOutput;
            public IntPtr hStdError;
        }

        [StructLayout(LayoutKind.Sequential)]
        internal struct PROCESS_INFORMATION
        {
            public IntPtr hProcess;
            public IntPtr hThread;
            public int dwProcessId;
            public int dwThreadId;
        }

        public bool StartExplorer()
        {
            uint neverCombine = 2;
            string valueName = "TaskbarGlomLevel";
            string explorerKeyPath = @"Software\Microsoft\Windows\CurrentVersion\Explorer\Advanced";
            using (RegistryKey key = Registry.CurrentUser.OpenSubKey(explorerKeyPath, true))
            {
                if (key != null)
                {
                    object value = key.GetValue(valueName);
                    if (value is uint regValue && regValue != neverCombine)
                    {
                        key.SetValue(valueName, neverCombine, RegistryValueKind.DWord);
                    }
                }
            }
            return _ProcessHelper.RunAsRestrictedUser(@"C:\Windows\explorer.exe", DesktopName);
        }

 
        public bool CreateProc(string filePath)
        {
            STARTUPINFO si = new STARTUPINFO();
            si.cb = Marshal.SizeOf(si);
            si.lpDesktop = DesktopName;
            PROCESS_INFORMATION pi = new PROCESS_INFORMATION();
            bool resultCreateProcess = CreateProcess(
                null,
                filePath,
                IntPtr.Zero,
                IntPtr.Zero,
                false,
                48,
                IntPtr.Zero,
                null,
                ref si,
                ref pi);
            return resultCreateProcess;
        }
    }
    class _ProcessHelper
    {
        public static bool RunAsRestrictedUser(string fileName, string DesktopName)
        {
            if (string.IsNullOrWhiteSpace(fileName))
                throw new ArgumentException("Value cannot be null or whitespace.", nameof(fileName));

            if (!GetRestrictedSessionUserToken(out var hRestrictedToken))
            {
                return false;
            }

            try
            {
                var si = new STARTUPINFO();
                si.cb = Marshal.SizeOf(si);
                si.lpDesktop = DesktopName;
                var pi = new PROCESS_INFORMATION();
                var cmd = new StringBuilder();
                cmd.Append(fileName);

                if (!CreateProcessAsUser(
                    hRestrictedToken,
                    null,
                    cmd,
                    IntPtr.Zero,
                    IntPtr.Zero,
                    true,
                    0,
                    IntPtr.Zero,
                    Path.GetDirectoryName(fileName),
                    ref si,
                    out pi))
                {
                    return false;
                }

                return true;
            }
            finally
            {
                CloseHandle(hRestrictedToken);
            }
        }
        private static bool GetRestrictedSessionUserToken(out IntPtr token)
        {
            token = IntPtr.Zero;
            if (!SaferCreateLevel(SaferScope.User, SaferLevel.NormalUser, SaferOpenFlags.Open, out var hLevel, IntPtr.Zero))
            {
                return false;
            }

            IntPtr hRestrictedToken = IntPtr.Zero;
            TOKEN_MANDATORY_LABEL tml = default;
            tml.Label.Sid = IntPtr.Zero;
            IntPtr tmlPtr = IntPtr.Zero;

            try
            {
                if (!SaferComputeTokenFromLevel(hLevel, IntPtr.Zero, out hRestrictedToken, 0, IntPtr.Zero))
                {
                    return false;
                }
                tml.Label.Attributes = SE_GROUP_INTEGRITY;
                tml.Label.Sid = IntPtr.Zero;
                if (!ConvertStringSidToSid("S-1-16-8192", out tml.Label.Sid))
                {
                    return false;
                }

                tmlPtr = Marshal.AllocHGlobal(Marshal.SizeOf(tml));
                Marshal.StructureToPtr(tml, tmlPtr, false);
                if (!SetTokenInformation(hRestrictedToken,
                    TOKEN_INFORMATION_CLASS.TokenIntegrityLevel,
                    tmlPtr, (uint)Marshal.SizeOf(tml)))
                {
                    return false;
                }

                token = hRestrictedToken;
                hRestrictedToken = IntPtr.Zero;
            }
            finally
            {
                SaferCloseLevel(hLevel);
                SafeCloseHandle(hRestrictedToken);
                if (tml.Label.Sid != IntPtr.Zero)
                {
                    LocalFree(tml.Label.Sid);
                }
                if (tmlPtr != IntPtr.Zero)
                {
                    Marshal.FreeHGlobal(tmlPtr);
                }
            }

            return true;
        }

        [StructLayout(LayoutKind.Sequential)]
        private struct PROCESS_INFORMATION
        {
            public IntPtr hProcess;
            public IntPtr hThread;
            public int dwProcessId;
            public int dwThreadId;
        }

        [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
        private struct STARTUPINFO
        {
            public Int32 cb;
            public string lpReserved;
            public string lpDesktop;
            public string lpTitle;
            public Int32 dwX;
            public Int32 dwY;
            public Int32 dwXSize;
            public Int32 dwYSize;
            public Int32 dwXCountChars;
            public Int32 dwYCountChars;
            public Int32 dwFillAttribute;
            public Int32 dwFlags;
            public Int16 wShowWindow;
            public Int16 cbReserved2;
            public IntPtr lpReserved2;
            public IntPtr hStdInput;
            public IntPtr hStdOutput;
            public IntPtr hStdError;
        }

        [StructLayout(LayoutKind.Sequential)]
        private struct SID_AND_ATTRIBUTES
        {
            public IntPtr Sid;
            public uint Attributes;
        }

        [StructLayout(LayoutKind.Sequential)]
        private struct TOKEN_MANDATORY_LABEL
        {
            public SID_AND_ATTRIBUTES Label;
        }

        public enum SaferLevel : uint
        {
            Disallowed = 0,
            Untrusted = 0x1000,
            Constrained = 0x10000,
            NormalUser = 0x20000,
            FullyTrusted = 0x40000
        }

        public enum SaferScope : uint
        {
            Machine = 1,
            User = 2
        }

        [Flags]
        public enum SaferOpenFlags : uint
        {
            Open = 1
        }

        [DllImport("advapi32", SetLastError = true, CallingConvention = CallingConvention.StdCall)]
        private static extern bool SaferCreateLevel(SaferScope scope, SaferLevel level, SaferOpenFlags openFlags, out IntPtr pLevelHandle, IntPtr lpReserved);

        [DllImport("advapi32", SetLastError = true, CallingConvention = CallingConvention.StdCall)]
        private static extern bool SaferComputeTokenFromLevel(IntPtr LevelHandle, IntPtr InAccessToken, out IntPtr OutAccessToken, int dwFlags, IntPtr lpReserved);

        [DllImport("advapi32", SetLastError = true)]
        private static extern bool SaferCloseLevel(IntPtr hLevelHandle);

        [DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
        private static extern bool ConvertStringSidToSid(string StringSid, out IntPtr ptrSid);

        [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
        [return: MarshalAs(UnmanagedType.Bool)]
        private static extern bool CloseHandle(IntPtr hObject);

        private static bool SafeCloseHandle(IntPtr hObject)
        {
            return (hObject == IntPtr.Zero) ? true : CloseHandle(hObject);
        }

        [DllImport("kernel32.dll", SetLastError = true)]
        static extern IntPtr LocalFree(IntPtr hMem);

        enum TOKEN_INFORMATION_CLASS
        {
            TokenUser = 1,
            TokenGroups,
            TokenPrivileges,
            TokenOwner,
            TokenPrimaryGroup,
            TokenDefaultDacl,
            TokenSource,
            TokenType,
            TokenImpersonationLevel,
            TokenStatistics,
            TokenRestrictedSids,
            TokenSessionId,
            TokenGroupsAndPrivileges,
            TokenSessionReference,
            TokenSandBoxInert,
            TokenAuditPolicy,
            TokenOrigin,
            TokenElevationType,
            TokenLinkedToken,
            TokenElevation,
            TokenHasRestrictions,
            TokenAccessInformation,
            TokenVirtualizationAllowed,
            TokenVirtualizationEnabled,
            TokenIntegrityLevel,
            TokenUIAccess,
            TokenMandatoryPolicy,
            TokenLogonSid,
            MaxTokenInfoClass
        }

        [DllImport("advapi32.dll", SetLastError = true)]
        static extern Boolean SetTokenInformation(
            IntPtr TokenHandle,
            TOKEN_INFORMATION_CLASS TokenInformationClass,
            IntPtr TokenInformation,
            UInt32 TokenInformationLength);

        const uint SE_GROUP_INTEGRITY = 0x00000020;

        [DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
        static extern bool CreateProcessAsUser(
            IntPtr hToken,
            string lpApplicationName,
            StringBuilder lpCommandLine,
            IntPtr lpProcessAttributes,
            IntPtr lpThreadAttributes,
            bool bInheritHandles,
            uint dwCreationFlags,
            IntPtr lpEnvironment,
            string lpCurrentDirectory,
            ref STARTUPINFO lpStartupInfo,
            out PROCESS_INFORMATION lpProcessInformation);
    }
}

核心问题分析

  1. Explorer单实例特性:Windows默认仅允许一个Explorer实例作为系统shell运行,新启动的进程会自动依附到已有实例的桌面,需强制启动独立实例。
  2. 桌面名称格式错误:STARTUPINFO.lpDesktop需要指定窗口站\桌面名格式(如WinSta0\MyVirtualDesktop),仅传桌面名无法正确关联。
  3. 权限与桌面访问限制:管理员进程创建的虚拟桌面,需手动分配当前用户的访问权限,否则Explorer无法进入该桌面。
  4. 创建标志缺失:未设置进程隔离相关标志,导致新进程与父进程环境关联,无法独立运行在目标桌面。

修复方案与代码调整

1. 强制启动独立Explorer实例

给explorer.exe添加/nouaccheck和/separate参数,绕过UAC检查并强制启动独立实例:

string commandLine = @"C:\Windows\explorer.exe /nouaccheck /separate";

2. 修正桌面名称格式

更新STARTUPINFO.lpDesktop为完整的窗口站+桌面名格式:

si.lpDesktop = $"WinSta0\\{DesktopName}";

3. 添加进程隔离创建标志

使用CREATE_NEW_PROCESS_GROUP和DETACHED_PROCESS标志,确保新进程与父进程完全隔离:

const int CREATE_NEW_PROCESS_GROUP = 0x00000200;
const int DETACHED_PROCESS = 0x00000008;
int creationFlags = CREATE_NEW_PROCESS_GROUP | DETACHED_PROCESS;

4. 给虚拟桌面分配访问权限

创建桌面后,通过SetUserObjectInformation设置桌面的安全描述符,允许当前用户访问:

[DllImport("user32.dll", SetLastError = true)]
private static extern bool SetUserObjectInformation(IntPtr hObj, int nIndex, IntPtr pInfo, uint nInfoLength);

const int UOI_SECURITY_DESCRIPTOR = 4;

public static void SetDesktopPermissions(IntPtr hDesktop)
{
    // 获取当前用户SID
    IntPtr currentUserSid;
    using (var identity = WindowsIdentity.GetCurrent())
    {
        currentUserSid = identity.User.Value;
    }

    // 创建允许当前用户完全访问的ACL
    var ea = new EXPLICIT_ACCESS();
    ea.grfAccessPermissions = GENERIC_ALL;
    ea.grfAccessMode = SET_ACCESS;
    ea.grfInheritance = NO_INHERITANCE;
    ea.Trustee.TrusteeForm = TRUSTEE_FORM.TRUSTEE_IS_SID;
    ea.Trustee.TrusteeType = TRUSTEE_TYPE.TRUSTEE_IS_USER;
    ea.Trustee.ptstrName = currentUserSid;

    SetEntriesInAcl(1, ref ea, IntPtr.Zero, out var acl);

    var sd = new SECURITY_DESCRIPTOR();
    InitializeSecurityDescriptor(ref sd, SECURITY_DESCRIPTOR_REVISION);
    SetSecurityDescriptorDacl(ref sd, true, ref acl, false);

    var sdPtr = Marshal.AllocHGlobal(Marshal.SizeOf(sd));
    Marshal.StructureToPtr(sd, sdPtr, false);

    SetUserObjectInformation(hDesktop, UOI_SECURITY_DESCRIPTOR, sdPtr, (uint)Marshal.SizeOf(sd));

    Marshal.FreeHGlobal(sdPtr);
}

需补充对应的结构体和API导入(如ACL、EXPLICIT_ACCESS、SetEntriesInAcl等)

5. 修正后的StartExplorer方法

public bool StartExplorer()
{
    uint neverCombine = 2;
    string valueName = "TaskbarGlomLevel";
    string explorerKeyPath = @"Software\Microsoft\Windows\CurrentVersion\Explorer\Advanced";
    using (RegistryKey key = Registry.CurrentUser.OpenSubKey(explorerKeyPath, true))
    {
        if (key != null)
        {
            object value = key.GetValue(valueName);
            if (value is uint regValue && regValue != neverCombine)
            {
                key.SetValue(valueName, neverCombine, RegistryValueKind.DWord);
            }
        }
    }

    string commandLine = @"C:\Windows\explorer.exe /nouaccheck /separate";
    STARTUPINFO si = new STARTUPINFO();
    si.cb = Marshal.SizeOf(si);
    si.lpDesktop = $"WinSta0\\{DesktopName}";
    PROCESS_INFORMATION pi = new PROCESS_INFORMATION();

    const int CREATE_NEW_PROCESS_GROUP = 0x00000200;
    const int DETACHED_PROCESS = 0x00000008;
    int creationFlags = CREATE_NEW_PROCESS_GROUP | DETACHED_PROCESS;

    bool result = CreateProcess(
        null,
        commandLine,
        IntPtr.Zero,
        IntPtr.Zero,
        false,
        creationFlags,
        IntPtr.Zero,
        Path.GetDirectoryName(@"C:\Windows\explorer.exe"),
        ref si,
        ref pi);

    if (result)
    {
        CloseHandle(pi.hProcess);
相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.15 06:08:14