以Local System身份运行PowerShell脚本创建计划任务失败的解决方法
问题解决:Local System身份下创建计划任务触发器失败
问题现象
在Win11系统中,通过线程令牌切换到Local System身份后,执行New-ScheduledTaskTrigger时报错:
New-ScheduledTaskTrigger : Cannot connect to CIM server. Access to a CIM resource was not available to the client.
原因分析
通过SetThreadToken实现的线程级身份切换,仅修改了当前线程的令牌,但未初始化完整的进程级安全上下文。CIM/WMI服务(计划任务Cmdlet的底层依赖)要求调用者具备完整的进程身份验证信息,这种线程级切换无法满足该要求,因此被拒绝访问。
解决办法
放弃线程令牌切换的方式,改用完整进程级的Local System身份执行,以下提供两种可行方案:
方案1:使用schtasks.exe直接操作(轻量高效)
原生命令行工具schtasks.exe在Local System身份下可直接与计划任务服务交互,绕过PowerShell的CIM调用限制。示例代码:
# 以Local System身份创建带一次性触发器的任务 schtasks /create /tn "LocalSystem_TestTask" /tr "cmd.exe /c echo Task executed" /sc once /st (Get-Date).ToString("HH:mm:ss") /ru "NT AUTHORITY\SYSTEM"
若仅需生成触发器对象,可先在管理员身份下创建后复用;若必须在Local System环境中生成,此工具是最优选择。
方案2:用CreateProcessAsUser创建独立Local System进程
通过Windows API创建一个拥有完整Local System身份上下文的新PowerShell进程,在该进程中执行计划任务相关操作。示例代码:
#requires -runasadmin $id = [System.Security.Principal.WindowsIdentity] if (-not $id::GetCurrent().IsSystem) { # 导入所需Windows API $processApi = Add-Type @" [DllImport("kernel32.dll")] public static extern IntPtr OpenProcess(UInt32 dwDesiredAccess, bool bInheritHandle, UInt32 dwProcessId); [DllImport("advapi32.dll", SetLastError = true)] public static extern bool OpenProcessToken(IntPtr ProcessHandle, UInt32 DesiredAccess, out IntPtr TokenHandle); [DllImport("advapi32.dll", SetLastError = true)] public static extern bool DuplicateTokenEx(IntPtr hExistingToken, UInt32 dwDesiredAccess, IntPtr lpTokenAttributes, int ImpersonationLevel, int TokenType, out IntPtr phNewToken); [DllImport("advapi32.dll", SetLastError = true)] public static extern bool CreateProcessAsUser(IntPtr hToken, string lpApplicationName, string lpCommandLine, IntPtr lpProcessAttributes, IntPtr lpThreadAttributes, bool bInheritHandles, UInt32 dwCreationFlags, IntPtr lpEnvironment, string lpCurrentDirectory, [In] ref STARTUPINFO lpStartupInfo, out PROCESS_INFORMATION lpProcessInformation); [StructLayout(LayoutKind.Sequential)] public struct STARTUPINFO { public int cb; public string lpReserved; public string lpDesktop; public string lpTitle; public int dwX; public int dwY; public int dwXSize; public int dwYSize; public int dwXCountChars; public int dwYCountChars; public int dwFillAttribute; public int dwFlags; public short wShowWindow; public short cbReserved2; public IntPtr lpReserved2; public IntPtr hStdInput; public IntPtr hStdOutput; public IntPtr hStdError; } [StructLayout(LayoutKind.Sequential)] public struct PROCESS_INFORMATION { public IntPtr hProcess; public IntPtr hThread; public UInt32 dwProcessId; public UInt32 dwThreadId; } "@ -Name ProcessApi -PassThru # 获取Local System令牌(从SamSs服务进程) $samSsService = Get-WmiObject win32_service | Where-Object { $_.name -eq 'SamSs'} $sysProcHandle = $processApi::OpenProcess(0x001F0FFF, $false, $samSsService.processID) $sourceToken = [IntPtr]0 $null = $processApi::OpenProcessToken($sysProcHandle, 0x00020008, [ref]$sourceToken) $primaryToken = [IntPtr]0 # 复制为可用于创建进程的Primary Token $null = $processApi::DuplicateTokenEx($sourceToken, 0x000F0000, [IntPtr]::Zero, 2, 1, [ref]$primaryToken) # 定义要在Local System进程中执行的脚本内容 $targetScript = @' # 创建触发器并注册任务 $trigger = New-ScheduledTaskTrigger -Once -At (Get-Date).AddMinutes(1) $action = New-ScheduledTaskAction -Execute "powershell.exe" -Argument "-Command Write-Host 'Task run as Local System'" Register-ScheduledTask -TaskName "FullContext_LocalSystemTask" -Trigger $trigger -Action $action -User "NT AUTHORITY\SYSTEM" -Force Write-Host "任务创建完成" '@ # 编码脚本以避免命令行解析问题 $encodedScript = [Convert]::ToBase64String([Text.Encoding]::Unicode.GetBytes($targetScript)) $commandLine = "powershell.exe -EncodedCommand $encodedScript" # 创建新进程 $startupInfo = New-Object ProcessApi+STARTUPINFO $startupInfo.cb = [System.Runtime.InteropServices.Marshal]::SizeOf($startupInfo) $processInfo = New-Object ProcessApi+PROCESS_INFORMATION $null = $processApi::CreateProcessAsUser($primaryToken, $null, $commandLine, [IntPtr]::Zero, [IntPtr]::Zero, $false, 0x00000010, [IntPtr]::Zero, $null, [ref]$startupInfo, [ref]$processInfo) # 等待进程执行完成并释放资源 Wait-Process -Id $processInfo.dwProcessId [System.Runtime.InteropServices.Marshal]::CloseHandle($processInfo.hProcess) [System.Runtime.InteropServices.Marshal]::CloseHandle($processInfo.hThread) [System.Runtime.InteropServices.Marshal]::CloseHandle($primaryToken) [System.Runtime.InteropServices.Marshal]::CloseHandle($sourceToken) [System.Runtime.InteropServices.Marshal]::CloseHandle($sysProcHandle) }
关键提示
SetThreadToken仅适用于文件读写、注册表操作等简单场景,无法支持CIM/WMI这类需要完整身份上下文的服务调用。- 进程级身份切换能提供完整的Local System权限上下文,可正常访问所有系统服务。
内容的提问来源于stack exchange,提问作者Carsten
相关产品推荐
相关产品推荐

