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

以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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.18 09:53:16