PowerShell:设置一个计划任务在用户未登录时运行

nkhmeac6  于 2023-05-07  发布在  Shell
关注(0)|答案(8)|浏览(285)

我一直在使用Powershell Scheduled Task Cmdlets在我们的服务器上创建一个计划任务。
如何选择“无论用户是否使用此API登录都运行”?
我已经创建了actiontriggerprincipalsettings对象,并将它们传递给Register-ScheduledTask,如下所示:

$action = New-ScheduledTaskAction -Execute foo.exe -Argument "bar baz"
$trigger = New-ScheduledTaskTrigger -Once -At $startTime -RepetitionInterval (New-TimeSpan -Minutes 1) -RepetitionDuration ([Timespan]::MaxValue)
$principal = New-ScheduledTaskPrincipal -UserId "$($env:USERDOMAIN)\$($env:USERNAME)" -LogonType ServiceAccount
$settings = New-ScheduledTaskSettingsSet -MultipleInstances Parallel

Register-ScheduledTask -TaskName $taskName -TaskPath "\my\path" -Action $action -Trigger $trigger -Settings $settings -Principal $principal

当我创建这样一个计划任务时,它默认为“仅在用户登录时运行”。
This question显示了如何使用COM对象执行此操作,this one显示了如何使用schtasks.exe执行此操作,但如何使用*-ScheduledTask* cmdlet执行此操作?

apeeds0o

apeeds0o1#

我不喜欢或赞成目前评级最高的答案,因为那么你必须知道你的凭据到一个脚本来做到这一点,不能这样做,从一些像Packer或一些其他系统/配置自动化。有一个更好的/正确的方法来做到这一点,Aeyoun提到,但没有详细说明,这是正确地设置主体作为系统用户运行。

$action = New-ScheduledTaskAction -Execute foo.exe -Argument "bar baz"
$trigger = New-ScheduledTaskTrigger -Once -At (Get-Date) -RepetitionInterval (New-TimeSpan -Minutes 1) -RepetitionDuration ([Timespan]::MaxValue)
$principal = New-ScheduledTaskPrincipal -UserID "NT AUTHORITY\SYSTEM" -LogonType ServiceAccount -RunLevel Highest
$settings = New-ScheduledTaskSettingsSet -MultipleInstances Parallel

Register-ScheduledTask -TaskName "tasknamehere" -TaskPath "\my\path" -Action $action -Trigger $trigger -Settings $settings -Principal $principal
6za6bjd0

6za6bjd02#

您需要删除$principal并使用用户和密码注册任务:

Register-ScheduledTask -TaskName $taskname `
                       -TaskPath "\my\path" `
                       -Action $action `
                       -Trigger $trigger `
                       -User "$env:USERDOMAIN\$env:USERNAME" `
                       -Password 'P@ssw0rd' `
                       -Settings $settings
9nvpjoqh

9nvpjoqh3#

Task Scheduler GUI中的“Run whether user is logged in or not”选项相当于New-ScheduledTaskPrincipal -LogonType S4U

i86rm4rw

i86rm4rw4#

通过PowerShell创建定时任务入门

我也试图使用PowerShell在Windows Server 2019上创建一个计划任务。所有答案都不管用。似乎所有的答案都有正确答案的片段,但没有一个有完整的答案。虽然有些答案可能对某些人有用,但我敢肯定,根据他们现有的系统,安全设置和其他因素,这真的是运气。在通过PowerShell创建一个非常简单的计划任务来收集一些应用程序遥测数据的过程中,我学到了很多。
因此,我将完全修改我最初的答案,并带您一步一步地了解(在最热烈的情况下)创建Scheduled Task所需的内容,特别是在服务器上。如果它像cron一样简单就好了。

为运行无人值守任务的用户分配 * 批量登录 *

当大多数人想要创建一个计划任务时,特别是对于服务器/应用程序维护,或者只是定期运行一些东西,第一站是Windows任务计划程序。提供了一个很好的GUI(好吧,它可以更好/更现代化,但嘿,至少它是一个GUI),您可以在其中指定所有必要的细节。问题是,你不能自动化GUI。正如我所发现的,GUI在幕后做的事情,微软并没有直截了当地告诉你它是如何做的(或者它是如何不做的)。这可能会导致很多问题,包括任务无法运行,甚至运行任务的用户帐户被锁定。
在Microsoft关于组策略标题下的 * 作为批处理作业登录 *(SeBatchLogonRight)的文档中,Microsoft指出,“当用户计划任务时,任务计划程序自动赠款此权限。”
使用任务计划程序GUI创建计划任务时,是的,如果计划任务配置为 * 无论用户是否登录都运行 *,并且用户没有 * 作为批处理作业登录 * 权限,则任务计划程序将向用户分配该权限(除非更改了默认值-请参阅上面引用的链接)。
但是,当使用PowerShell ScheduledTask模块的cmdlet创建计划任务时,此自动用户权限分配不会发生。因此,您必须手动执行此操作。GUI方法是使用本地安全策略MMC(Microsoft管理控制台)。当然,在自动化场景中,GUI是不存在的,因此您的朋友将是secedit.exe。(我自己也是围绕secedit.exe编写PowerShell Package 器的。
因此,让我们假设我们有一个计划任务,该任务将为在您的服务器上运行的应用程序收集遥测数据,然后将该遥测数据发送到您的遥测数据收集服务,例如New Relic或Data Dog。任务将在用户帐户CONTOSO\AppTelemetry下运行。考虑到我们将在蓝/绿色部署期间通过PowerShell以自动方式创建计划任务,我们需要为该用户分配 Logon as a batch job 用户权限。

使用secedit分配用户权限

这里的步骤很简单:
1.将现有服务器的安全策略导出到安全策略模板(可选,仅包含您关心的部分)
1.创建新的安全策略模板
1.将运行计划任务的用户的安全标识符(SID)添加到新安全策略模板中的相应用户权限
1.将包含安全策略更改的安全策略模板导入到新数据库
1.配置系统的安全策略,以包含在步骤4中创建的安全策略数据库中包含的更改。
让我们来看看实现这一点的代码。

将现有服务器的安全策略导出到安全策略模板

您可以从CMD或PowerShell(桌面版或核心版,无所谓)完成此操作。所有示例都将在PowerShell中,除非另有说明。

secedit.exe /export /cfg secpol.inf /areas USER_RIGHTS

上述命令导出系统的安全策略,但仅导出包含有关用户权限分配信息的部分。如果您需要添加其他设置,如注册表项,也可以通过安全策略完成。阅读有关secedit.exe或安全策略的Microsoft文档。

新建一个安全策略模板,将我们的定时任务用户的SID添加到SeBatchLogonRight值中

现在,我们需要创建一个新的安全策略模板,其中包含我们希望的新安全策略与当前的安全策略是什么?我建议新的安全策略模板包含尽可能少的信息--只包含那些需要更改的设置。

有人会认为您需要做的就是指定SeBatchLogonRight必须包括用户的SID,这就是它。但如果你这么想那你就错了。默认情况下,SeBatchLogonRight会分配一些用户(请参阅上面引用的Microsoft文档链接)。如果我们只是在新的策略模板中将用户的SID分配给这个权限,它将有效地 * 替换 * 系统安全策略中的现有值,而不是 * 更新 * 它。因此,由于我们正在进行 additive 更改,因此需要使用上面导出的系统安全策略中的现有值将此用户权限添加到模板中,然后将用户SID添加到列表中。
默认情况下,SeBatchLogonRight分配有以下SID:

SeBatchLogonRight = *S-1-5-32-544,*S-1-5-32-551,*S-1-5-32-559,*S-1-5-32-568

这些是一些标准Windows安全组的“知名”SID,其用户应具有此权限。假设CONTOSO\AppTelemetry的SID是S-1-5-21-0000000000-1111111111-2222222222-3333。但是,等等,你是怎么得到的?

function ConvertTo-SecurityIdentifier {
    Param (
        [Parameter(Position = 0, Mandatory, ValueFromPipeline)]
        [string[]] $UsernameOrGroup
    )

    Process {
        foreach ($Name in $UsernameOrGroup) {
            $Account = New-Object -Type System.Security.Principal.NTAccount `
                                  -Argument $Name
            $Account.Translate([System.Security.Principal.SecurityIdentifier]).Value
        }
    }
}
Set-Alias -Name ConvertTo-SID -Value ConvertTo-SecurityIdentifier

function ConvertFrom-SecurityIdentifier {
    Param (
        [Parameter(Position = 0, Mandatory, ValueFromPipeline)]
        [string[]] $SecurityIdentifier
    )

    Process {
        foreach ($SID in $SecurityIdentifier) {
            $Account = New-Object -Type System.Security.Principal.SecurityIdentifier `
                                  -ArgumentList $SID
            $Account.Translate([System.Security.Principal.NTAccount]).Value
        }
    }
}
Set-Alias -Name ConvertFrom-SID -Value ConvertFrom-SecurityIdentifier

(Why我不是简单地使用Get-ADUser来获取用户帐户的SID吗?两个原因。首先,使用Get-ADUser从SID获取用户帐户名并不那么简单。这是可以做到的,但上面的代码更清晰。其次,也是最重要的一点,并非所有用户的机器上都安装了Get-ADUser。我以前的笔记本电脑安装了Windows远程服务器管理工具(RSAT),所以我有Get-ADUser可用。如果未安装RSAT或未安装Active Directory PowerShell模块,则Get-ADUser不可用。上面的cmdlet不依赖于Windows和.NET框架之外的任何东西,如果您试图获取用户帐户SID并使用PowerShell来执行此操作,则根据定义,您很可能具有两个先决条件。
然后你可以简单地运行:

$SID = 'CONTOSO\AppTelemtry' | ConvertTo-SecurityIdentifier

现在我们已经有了SID,我们可以创建一个安全策略模板(注意,有更好的方法可以做到这一点-我创建了PowerShell cmdlet,让我以编程方式与这些INF文件交互,但我只打算使用这里的文档)。请注意,在导出的安全策略中,所有SID都以*为前缀,并且多个值以逗号分隔:

$NewPolicy = @'
[Unicode]
Unicode=yes
[Version]
signature="$CHICAGO$"
Revision=1
[Privilege Rights]
SeBatchLogonRight = *S-1-5-32-544,*S-1-5-32-551,*S-1-5-32-559,*S-1-5-32-568,*S-1-5-21-0000000000-1111111111-2222222222-3333
'@

$NewPolicy | Set-Content batchlogon.inf

将新的安全策略模板导入到新的安全策略库

标题说明了一切:

secedit.exe /import /db batchlogon.sdb /cfg batchlogon.inf

使用新安全策略库中包含的更改配置系统安全策略

再一次,标题说明了一切:

secedit.exe /configure /db batchlogon.sdb

这就是从CLI为用户分配 * Logonasabatchjob * 的全部内容。

通过PowerShell创建定时任务

既然用户无论是否登录都有权运行计划任务,我们需要创建一个无论用户是否登录都将运行的计划任务。您将看到许多指向许多Q&A答案的链接,这些链接说您需要这个或那个LogonType(特别是S4UServiceAccount)或以最高权限运行它,等等。别听那些(大部分是错误的)。本节将概述创建无论用户是否登录都运行的计划任务所需的最低步骤。

创建定时任务动作

首先是创建任务操作。计划任务由几个部分组成:

  • 行动
  • 触发器
  • 设置
  • 本金
  • 注册

这些名称都是相当不言自明的。我们将创建一个执行程序的预定任务操作。(还有其他类型的动作。参见Task Actions上的文档。)
关于Exec操作,需要了解的重要一点是,Task Scheduler服务将生成cmd.exe的一个示例来运行所提供的程序。这意味着,如果需要为程序指定参数,则需要确保遵循CMD命令的复杂引号规则。(根据您的参数,这些引用规则可能需要 * 大量 * 测试,以确保命令将按预期运行。即使在我的简单例子中,我也做错了,任务似乎运行正确-0退出代码-但什么都没做!)见cmd.exe /?为所有血淋淋的细节。此外,你可以通过网络搜索找到很多信息。
让我们创建任务操作:

$TaskAction = New-ScheduledTaskAction -Execute 'powershell.exe' `
    -Argument ('-NoLogo -ExecutionPolicy Bypass -NoProfile -NonInteractive' + `
     '-File "C:\Telemetry\Send-ApplicationTelemetry.ps1"')

再次,要小心引用规则(即如果你的文件路径中有空格,或者,在这种情况下,你传递了一点powershell脚本到-Command参数,而不是像我在这里所做的那样使用-File)。这里有几点需要指出:

  • 我可以使用pwsh.exe来运行PowerShell Core

  • -NoLogo避免打印启动PowerShell时出现的“横幅”。如果你这样做的话,它可以使重定向到日志文件的输出更好。

  • -ExecutionPolicy Bypass指定此脚本将绕过系统上的任何当前执行策略。默认的执行策略是Restricted,它不允许任何脚本运行,除非指定了完整路径,并且不允许任何来自远程源的脚本。这个开关基本上确保脚本将运行。这可能并不总是必要的,但如果您信任正在安排的脚本,这也不会有什么坏处。

  • -NoProfile用于不加载用户的PowerShell配置文件(这与Linux操作系统上的用户bash配置文件类似)。但是,也可以存在全局配置文件脚本。除非您知道在PowerShell启动时需要运行配置文件脚本,否则添加此开关不会有什么坏处,而且很可能会防止错误。

  • -NonInteractive是一个非常重要的交换机。基本上,这可以防止PowerShell提示用户输入(例如对于需要确认或用户输入的cmdlet)。这也意味着,如果你的脚本需要确认/用户输入,它将不会以非交互方式工作(即:当用户未登录时)。

  • -File告诉PowerShell执行指定的文件。您也可以使用-Command,并传入一些“字符串化”的PowerShell代码。

创建定时任务主体

这是正确创建无论用户是否登录都将运行的计划任务的最重要步骤之一.实际上,这一步是REQUIRED,将定时任务配置为 * 无论用户是否登录都运行 *。

$TaskPrincipal = New-ScheduledTaskPrincipal -Id 'Author' `
                                            -UserId 'CONTOSO\AppTelemetry' `
                                            -LogonType Password `
                                            -RunLevel Limited

让我们更详细地了解一下这些设置。

-Id

这是一个自由格式的文本字段,就我所知。任务计划程序GUI始终使用术语“作者”,但通常,您可以在这里使用任何您想要的内容。需要注意的是,计划任务可以包含许多不同的操作(最多32个)。动作也有一个与之相关的“上下文”。任务调度程序自动将上下文分配给“作者”(对于简单的单动作任务)。文档似乎表明您可以为计划任务提供多个主体,并且主体的ID与操作的上下文相关,将确定操作将在哪个主体下执行。对于我们的单动作任务,我们将使用“Author”。

-UserId-GroupId

任务可以在用户帐户或属于指定组的任何用户下运行。请注意,在使用组时,只有当该组的用户登录时,计划任务才会运行。这是因为使用组ID时,您无法为任务指定密码。
在99%的情况下,您会像我上面所做的那样使用-UserId。它不需要是完全限定的用户ID。请注意,我们不将密码存储在主体中。那是以后的事。

-LogonType

这可能是此cmdlet最容易被误解的参数之一。LogonType的文档非常好。可惜New-ScheduledTaskPrincipal的PowerShell帮助没有链接到它(我对此提出了反馈)。
基本上,您可能永远不需要关心S4U登录类型。它可能在某些场景中是相关的(这就是它存在的原因),但对于大多数你想做的事情,它可能不会。
例如,如果计划任务将在NT AUTHORITY\LOCALSYSTEMNT AUTHORITY\SYSTEM用户帐户下运行,则只能使用ServiceAccount登录类型。(还有第三个,但这个名字我一时想不起来了。)由于我们的任务不是在系统帐户下运行的,所以这不是我们想要的值。
正如您可能能够猜测的那样,Interactive意味着任务将在登录用户的上下文中运行。当任务需要启动GUI应用程序,或者任务操作是消息框以在系统上显示对话框时,这可能很有用。显然,这在我们的情况下没有用。还有InteractiveOrPassword类型,它将这个登录类型与我们想要的Password结合在一起。所以我不想再讨论了。
当然,现在是Password登录类型,这是我们想要的。这意味着密码将与计划任务一起存储,该密码将用于登录用户(作为批处理作业),因此无论用户是否登录,任务都可以运行。是的,this 是在任务计划程序UI中选中 * 无论用户是否登录都运行 * 复选框的值。

-RunLevel

可以将此选项视为指定任务是否需要以提升的权限运行。当您运行命令时,例如安装某些软件时,用户访问控制(UAC)会启动并显示一个对话框,询问您是否要允许该程序对计算机进行更改。您可能有一半的时间点击Yes,甚至没有阅读它。将此参数设置为Highest就像在UAC对话框中单击Yes(或仅使用提升的权限启动进程,即 * 以管理员身份运行 *)。这是一件坏事,除非你知道你需要它。您应该始终从Limited开始,如果您发现计划任务无法在这些权限下运行,则提升。我就是这么做的。我将从最小特权原则和使用Limited运行级别开始。

创建定时任务触发器

您可以有一个或多个触发器来触发计划任务。存在不同的触发器类型。参见the documentation了解这些不同的类型。我只做一个很简单的例子:

$TaskTrigger = New-ScheduledTaskTrigger -Once -At ([DateTime]::Now.AddMinutes(5)) `
                                        -RepetitionInterval ( `
                                            [TimeSpan]::FromMinutes(5) `
                                        )

我只想要一个任务,将运行一次,从现在开始5分钟,然后运行随后每5分钟之后。

任务设置集

如果我需要自定义一些其他的任务设置,我可以使用New-ScheduledTaskSettingsSet。我对默认值没意见,所以我跳过这个。但任务计划程序文档详细说明了这一切,而且实际上,大部分设置都是不言自明的(除了 RunOnlyIfNetworkIsAvailable,但它并不难理解)。

创建定时任务

现在你可能认为这是我们创建任务的地方,我们将在任务计划程序中看到它。错。但是我们将创建一个Scheduled Task对象。您必须执行此步骤,否则您的任务将无法正确注册-最重要的是,它将无法设置为 * 无论用户是否登录都运行 *。

$Task = New-ScheduledTask -Description 'Collects application telemetry' `
                          -Action $TaskAction `
                          -Principal $TaskPrincipal `
                          -Trigger $TaskTrigger

这里发生的一切就是创建一个计划任务示例。但尚未将其添加到任务计划程序中的任务列表中。这就是所谓的 * 注册 *,我们接下来会做这个。如果在执行上述命令后在shell中键入$Task,您会注意到任务名称和路径将为空。您甚至不能在调用New-ScheduledTask时指定它。同样,奇怪的是,这发生在注册过程中。所以让我们来谈谈 * 最重要的部分 *,注册

注册定时任务

这是真的,我可以简单地:

$Task = Register-ScheduledTask -TaskName 'Foo' -TaskPath `\` `
                               -Action $TaskAction -Trigger $TaskTrigger `
                               -User 'SomeUser' -Password '$uper53cr37'

但这将导致任务以交互方式运行(* 仅在用户登录时运行 *)。这不是我们想要的。这就是为什么我们在上面创建了任务主体和包含任务主体的任务示例。
让我们注册我们的任务:

$Task = $Task | Register-ScheduledTask -TaskName 'Collect Telemetry' `
                                       -TaskPath '\Awesome App' `
                                       -User 'CONTOSO\AppTelemetry' `
                                       -Password 'ShhD0ntT3ll4ny0n3!'

所以你可以看到,是的,你必须提供一个密码。有多种方法可以安全地以自动化的方式实现这一点,但遗憾的是,对于演示目的来说并不那么容易。这里重要的部分是提供给Register-ScheduledTask的用户和密码。指定的用户必须与我们创建的主体中指定的用户相同。显然,该用户的密码必须正确。如果您使用其他用户注册计划任务,则任务prinipal更新为使用注册任务时指定的用户帐户运行任务。(这是记录here。)
这就是调度任务的全部内容,无论用户是否使用PowerShell登录。但在我结束之前还有一个主题要讨论:更新计划任务。

更新定时任务

信不信由你,这并不像它应该的那样直截了当。Set-ScheduledTask的语法文档不正确。我忘记为上面的任务操作设置工作目录。(假设我需要在某个地方写入一个文件,但写入该文件的代码不使用绝对路径。)让我们修复该操作并更新任务。
这是正确的做法。使用Set-ScheduledTask有不同的方法,但我发现其他方法可能会锁定用户帐户,或者根本不起作用(例如:给予你一个错误)。这并不是说其他方法是错误的,但对于这种特殊的改变,这是我每次都能做到的:

$Task = Get-ScheduledTask -TaskName 'Collect Telemetry' -TaskPath '\Awesome App'
$Task.Actions[0].WorkingDirectory = 'C:\AwesomeApp'
$Task | Set-ScheduledTask -User 'CONTOSO\AppTelemetry' -Password 'ShhD0ntT3ll4ny0n3!'

同样,使用注册任务时使用的用户和密码也很重要。如果您使用不同的用户名/密码,则任务主体也将更新。这可能不是你想要的。
这几乎就是在PowerShell中使用计划任务的全部内容。

调试帮助使用schtasks

当您在通过PowerShell创建计划任务时遇到问题时,一种帮助自己的方法是通过GUI在本地计算机上创建类似的计划任务。然后,您可以使用schtasks命令查询通过GUI创建的计划任务,并将其与通过PowerShell创建的计划任务进行比较。要使用schtasks,例如:

# To get the XML for the task:
schtasks /query /tn '\My Task Path\MyTask' /xml ONE

# To get a nice formatted list of the task properties:
schtasks /query /tn '\My Task Path\MyTask' /v /fo LIST

您还可以使用Export-ScheduledTask通过PowerShell获取计划任务的XML表示。您可以找到所有PowerShell计划任务cmdlet here的文档。文档是OK的。其中一些是误导性的,大部分是不完整的(即。它假定您除了PowerShell cmdlet本身之外还了解任务计划程序)。关于任务调度器的文档,它的COM接口,XML模式等,可以在here中找到。

总结

我希望这对某人有帮助,因为我花了很长时间才弄清楚这一切。主要是,很多,“为什么这个任务没有做任何事情,即使它说它成功了?”或者“为什么帐户总是被锁起来?”“(错误的登录类型、密码或用户权限分配,或全部3个!)

pvabu6sv

pvabu6sv5#

在GUI中设置好任务后,运行以下命令:

$task = Get-ScheduledTask "test task for notepad"
$task.Principal.LogonType = "Password"
Set-ScheduledTask $task
lp0sw83n

lp0sw83n6#

还控制运行水平检查:

运行级别

指定运行与主体关联的任务所需的权限级别。
例如:“最高”或“有限”

s4n0splo

s4n0splo7#

当我试图在Powershell上创建一个计划任务来将文件复制到Map驱动器时,我遇到了类似的挑战。

我是这样解决的

首先,我必须使用UNC路径来指定Map驱动器的路径:

Get-ChildItem -Path "C:\MyFiles\*" -Include *.jpg -Recurse | Copy-Item -Destination "\\192.168.54.20\CopiedFiles"

接下来,我使用以下命令设置计划作业:

$TaskName = "FileSync"
$Description = "This task will run periodically to sync .fin files from a specified source directory to a specified destination directory"
$ScriptPath = "C:\Users\my_userDesktop\file_sync.ps1"
$UserAccount = "COMP1\my_user"
$Action = New-ScheduledTaskAction -Execute "PowerShell.exe" -Argument "-ExecutionPolicy Bypass -File $ScriptPath"
$Principal = New-ScheduledTaskPrincipal -UserID $UserAccount -LogonType ServiceAccount
$Trigger = New-ScheduledTaskTrigger -Once -At (Get-Date) -RepetitionInterval (New-TimeSpan -Minutes 1) -RepetitionDuration ([System.TimeSpan]::MaxValue)
Register-ScheduledTask -TaskName $TaskName -Action $Action -Description $Description -Trigger $Trigger -Principal $Principal

注意$Principal允许任务在用户登录或未登录的情况下运行,对于同步到Map驱动器的定时作业来说是非常重要的。

就这样

希望这能帮上忙

lb3vh1jj

lb3vh1jj8#

不知道在Windows中发生了什么,但我不喜欢找不到解决方案,有这个问题,并解决了它,使用户运行它系统(更改用户名-〉高级-〉查找-〉选择系统)。
它将自动强制任务计划为“运行时,用户没有登录”,您可以运行PS脚本之后。
已测试在Windows 11 Pro上运行。
[Use完全限定路径]

相关问题