管理员权限下用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); } }
核心问题分析
- Explorer单实例特性:Windows默认仅允许一个Explorer实例作为系统shell运行,新启动的进程会自动依附到已有实例的桌面,需强制启动独立实例。
- 桌面名称格式错误:
STARTUPINFO.lpDesktop需要指定窗口站\桌面名格式(如WinSta0\MyVirtualDesktop),仅传桌面名无法正确关联。 - 权限与桌面访问限制:管理员进程创建的虚拟桌面,需手动分配当前用户的访问权限,否则Explorer无法进入该桌面。
- 创建标志缺失:未设置进程隔离相关标志,导致新进程与父进程环境关联,无法独立运行在目标桌面。
修复方案与代码调整
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);
相关产品推荐
相关产品推荐

