VB.NET调用CreateProcessWithTokenW时LPWSTR命令行参数传递失败求助
VB.NET中CreateProcessWithTokenW命令行参数传递问题的解决方案
兄弟,我看你在VB.NET里调用CreateProcessWithTokenW时遇到了命令行参数传不过去的问题——这个坑我之前也踩过,主要是Windows API的Unicode字符串处理和VB.NET的类型交互有几个容易忽略的细节,我给你捋清楚:
核心问题分析
CreateProcessWithTokenW是Unicode版本的API,对命令行参数的格式和传递方式有严格要求:
- 当你指定
lpApplicationName时,lpCommandLine应该只包含参数部分(不能包含程序名);如果lpApplicationName设为Nothing,lpCommandLine需要是完整的命令行(程序名+参数),否则目标进程用Microsoft.VisualBasic.Command()获取不到正确参数。 - VB.NET中传递可修改的Unicode字符串给API时,
StringBuilder的声明需要显式标记输入输出属性,否则API无法正确读取/修改字符串内容。 - 你的
STARTUPINFO初始化存在空引用风险,必须先实例化对象再设置cb字段。
具体修复步骤
1. 修正CreateProcessWithTokenW的API声明
给lpCommandLine添加<[In], Out>属性,确保VB.NET正确处理字符串的双向传递:
<DllImport("advapi32.dll", SetLastError:=True, CharSet:=CharSet.Unicode)> Public Shared Function CreateProcessWithTokenW( hToken As IntPtr, dwLogonFlags As Integer, lpApplicationName As String, <[In], Out> lpCommandLine As StringBuilder, dwCreationFlags As Integer, lpEnvironment As IntPtr, lpCurrentDirectory As IntPtr, ByRef lpStartupInfo As STARTUPINFO, ByRef lpProcessInformation As PROCESS_INFORMATION ) As Boolean End Function
2. 调整命令行参数的传递逻辑
- 如果
ProgramName不为空:CommandLine只传入参数部分(比如"-arg1 value1") - 如果
ProgramName为空:CommandLine传入完整命令行(比如"C:\MyApp.exe -arg1 value1")
3. 修复STARTUPINFO初始化
不要直接设为Nothing,先实例化对象:
Dim si As New STARTUPINFO() si.cb = Marshal.SizeOf(si)
4. 优化字符串指针处理
使用Marshal.StringToHGlobalUni而非Auto,确保传递Unicode格式的工作目录路径:
Dim ptrWorkingDirectory As IntPtr = IntPtr.Zero If Not String.IsNullOrEmpty(WorkingDirectory) Then ptrWorkingDirectory = Marshal.StringToHGlobalUni(WorkingDirectory) End If
5. 添加完整的错误处理
调用API后检查返回值,用Marshal.GetLastWin32Error()获取Windows错误码,方便调试:
Dim success As Boolean = CreateProcessWithTokenW(...) If Not success Then Throw New System.ComponentModel.Win32Exception(Marshal.GetLastWin32Error()) End If
修改后的完整代码
Private Sub LowerPriviledgeStart(ByVal ProgramName As String, ByVal CommandLine As String, ByVal WorkingDirectory As String) Try Dim currentProcess As Process = Process.GetCurrentProcess() 'Enable SeIncreaseQuotaPrivilege in this process. (Requires admin rights) Dim hProcessToken As IntPtr = Nothing If Not OpenProcessToken(currentProcess.Handle, TOKEN_ADJUST_PRIVILEGES, hProcessToken) Then Throw New System.ComponentModel.Win32Exception(Marshal.GetLastWin32Error()) End If Dim tkp As TOKEN_PRIVILEGES tkp.PrivilegeCount = 1 If Not LookupPrivilegeValue(Nothing, SE_INCREASE_QUOTA_NAME, tkp.TheLuid) Then Throw New System.ComponentModel.Win32Exception(Marshal.GetLastWin32Error()) End If tkp.Attributes = SE_PRIVILEGE_ENABLED If Not AdjustTokenPrivileges(hProcessToken, False, tkp, Marshal.SizeOf(tkp), Nothing, Nothing) Then Throw New System.ComponentModel.Win32Exception(Marshal.GetLastWin32Error()) End If 'Get shell window handle Dim hShellWnd As IntPtr = GetShellWindow() If hShellWnd = IntPtr.Zero Then Throw New InvalidOperationException("Could not locate shell window.") End If 'Get shell process ID Dim dwShellPID As Integer GetWindowThreadProcessId(hShellWnd, dwShellPID) 'Open shell process Dim hShellProcess As IntPtr = OpenProcess(PROCESS_QUERY_INFORMATION, False, dwShellPID) If hShellProcess = IntPtr.Zero Then Throw New System.ComponentModel.Win32Exception(Marshal.GetLastWin32Error()) End If 'Get shell process token Dim hShellProcessToken As IntPtr = Nothing If Not OpenProcessToken(hShellProcess, TOKEN_DUPLICATE, hShellProcessToken) Then Throw New System.ComponentModel.Win32Exception(Marshal.GetLastWin32Error()) End If 'Duplicate token to get primary token Dim hPrimaryToken As IntPtr = Nothing Dim dwTokenRights As Integer = TOKEN_QUERY Or TOKEN_ASSIGN_PRIMARY Or TOKEN_DUPLICATE Or TOKEN_ADJUST_DEFAULT Or TOKEN_ADJUST_SESSIONID If Not DuplicateTokenEx(hShellProcessToken, dwTokenRights, Nothing, SecurityImpersonation, TokenPrimary, hPrimaryToken) Then Throw New System.ComponentModel.Win32Exception(Marshal.GetLastWin32Error()) End If 'Initialize STARTUPINFO correctly Dim si As New STARTUPINFO() si.cb = Marshal.SizeOf(si) Dim pi As New PROCESS_INFORMATION() 'Prepare working directory pointer Dim ptrWorkingDirectory As IntPtr = IntPtr.Zero If Not String.IsNullOrEmpty(WorkingDirectory) Then ptrWorkingDirectory = Marshal.StringToHGlobalUni(WorkingDirectory) End If 'Prepare command line Dim sbCommandLine As StringBuilder = Nothing If Not String.IsNullOrEmpty(CommandLine) Then sbCommandLine = New StringBuilder(CommandLine) End If 'Execute CreateProcessWithTokenW Dim success As Boolean = CreateProcessWithTokenW(hPrimaryToken, 0, ProgramName, sbCommandLine, 0, IntPtr.Zero, ptrWorkingDirectory, si, pi) If Not success Then Throw New System.ComponentModel.Win32Exception(Marshal.GetLastWin32Error()) End If 'Clean up all handles to avoid leaks If pi.hProcess <> IntPtr.Zero Then CloseHandle(pi.hProcess) If pi.hThread <> IntPtr.Zero Then CloseHandle(pi.hThread) If hPrimaryToken <> IntPtr.Zero Then CloseHandle(hPrimaryToken) If hShellProcessToken <> IntPtr.Zero Then CloseHandle(hShellProcessToken) If hShellProcess <> IntPtr.Zero Then CloseHandle(hShellProcess) If hProcessToken <> IntPtr.Zero Then CloseHandle(hProcessToken) If ptrWorkingDirectory <> IntPtr.Zero Then Marshal.FreeHGlobal(ptrWorkingDirectory) Catch ex As Exception MessageBox.Show($"Operation failed: {ex.Message}") End Try End Sub 'Required API declarations and constants Public Const TOKEN_ADJUST_PRIVILEGES As Integer = &H20 Public Const SE_INCREASE_QUOTA_NAME As String = "SeIncreaseQuotaPrivilege" Public Const SE_PRIVILEGE_ENABLED As Integer = &H2 Public Const PROCESS_QUERY_INFORMATION As Integer = &H400 Public Const TOKEN_DUPLICATE As Integer = &H2 Public Const TOKEN_QUERY As Integer = &H8 Public Const TOKEN_ASSIGN_PRIMARY As Integer = &H1 Public Const TOKEN_ADJUST_DEFAULT As Integer = &H80 Public Const TOKEN_ADJUST_SESSIONID As Integer = &H100 Public Enum SECURITY_IMPERSONATION_LEVEL SecurityAnonymous SecurityIdentification SecurityImpersonation SecurityDelegation End Enum Public Enum TOKEN_TYPE TokenPrimary = 1 TokenImpersonation End Enum <StructLayout(LayoutKind.Sequential)> Public Structure TOKEN_PRIVILEGES Public PrivilegeCount As Integer Public TheLuid As LUID Public Attributes As Integer End Structure <StructLayout(LayoutKind.Sequential)> Public Structure LUID Public LowPart As Integer Public HighPart As Integer End Structure <StructLayout(LayoutKind.Sequential, CharSet:=CharSet.Unicode)> Public Structure STARTUPINFO Public cb As Integer Public lpReserved As String Public lpDesktop As String Public lpTitle As String Public dwX As Integer Public dwY As Integer Public dwXSize As Integer Public dwYSize As Integer Public dwXCountChars As Integer Public dwYCountChars As Integer Public dwFillAttribute As Integer Public dwFlags As Integer Public wShowWindow As Short Public cbReserved2 As Short Public lpReserved2 As IntPtr Public hStdInput As IntPtr Public hStdOutput As IntPtr Public hStdError As IntPtr End Structure <StructLayout(LayoutKind.Sequential)> Public Structure PROCESS_INFORMATION Public hProcess As IntPtr Public hThread As IntPtr Public dwProcessId As Integer Public dwThreadId As Integer End Structure <DllImport("advapi32.dll", SetLastError:=True, CharSet:=CharSet.Unicode)> Public Shared Function OpenProcessToken(hProcess As IntPtr, dwDesiredAccess As Integer, ByRef phToken As IntPtr) As Boolean End Function <DllImport("advapi32.dll", SetLastError:=True, CharSet:=CharSet.Unicode)> Public Shared Function LookupPrivilegeValue(lpSystemName As String, lpName As String, ByRef lpLuid As LUID) As Boolean End Function <DllImport("advapi32.dll", SetLastError:=True)> Public Shared Function AdjustTokenPrivileges(hToken As IntPtr, DisableAllPrivileges As Boolean, ByRef NewState As TOKEN_PRIVILEGES, BufferLength As Integer, ByRef PreviousState As TOKEN_PRIVILEGES, ByRef ReturnLength As Integer) As Boolean End Function <DllImport("user32.dll")> Public Shared Function GetShellWindow() As IntPtr End Function <DllImport("user32.dll", SetLastError:=True)> Public Shared Function GetWindowThreadProcessId(hWnd As IntPtr, ByRef lpdwProcessId As Integer) As Integer End Function <DllImport("kernel32.dll", SetLastError:=True)> Public Shared Function OpenProcess(dwDesiredAccess As Integer, bInheritHandle As Boolean, dwProcessId As Integer) As IntPtr End Function <DllImport("advapi32.dll", SetLastError:=True)> Public Shared Function DuplicateTokenEx(hExistingToken As IntPtr, dwDesiredAccess As Integer, lpTokenAttributes As IntPtr, ImpersonationLevel As SECURITY_IMPERSONATION_LEVEL, TokenType As TOKEN_TYPE, ByRef phNewToken As IntPtr) As Boolean End Function <DllImport("kernel32.dll", SetLastError:=True)> Public Shared Function CloseHandle(hObject As IntPtr) As Boolean End Function
测试建议
- 如果你要启动的程序是自己写的,先测试用
Command()能否获取参数:比如写一个简单的VB.NET程序,在Main里输出Command()的内容。 - 如果还是有问题,调用
Marshal.GetLastWin32Error()查看错误码,比如ERROR_INVALID_PARAMETER(87)通常表示参数格式不对。
内容的提问来源于stack exchange,提问作者Rob
相关产品推荐
相关产品推荐

