PowerShellを介したスケジュールされたタスクの作成に関する入門書
私も、PowerShellを使用してWindowsServer2019でスケジュールされたタスクを作成しようとしていました。答えはどれもうまくいきませんでした。すべての答えが本当に正しい解決策の断片を持っているように見えますが、完全な解決策を持っているものはありませんでした。一部の回答は一部の人にとってはうまくいったかもしれませんが、既存のシステム、セキュリティ設定、およびその他の要因に基づいて、実際にはすべて運が良かったと確信しています。いくつかのアプリケーションテレメトリを収集するために、PowerShellを介して非常に単純なスケジュールされたタスクを作成する過程で私が学んだことはたくさんあります。
そのため、元の回答を完全に修正し、特にサーバーでスケジュールされたタスクを作成するために必要なこと(最も熱心な場合)を段階的に説明します。それが。のように単純だったとしたらcron
。
無人タスクを実行しているユーザーのバッチジョブとしてログオンを割り当てる
ほとんどの人が、特にサーバー/アプリケーションのメンテナンスのためにスケジュールされたタスクを作成したい場合、または単に定期的に何かを実行したい場合、最初に停止するのはWindowsタスクスケジューラです。優れたGUIが提供されており(まあ、それはより優れた/近代化されている可能性がありますが、少なくともそれはGUIです)、作業を進めるために必要なすべての詳細を指定できます。問題は、GUIを自動化できないことです。そして、私が知ったように、GUIは、Microsoftがそれをどのように行っているか(または、それがどのように行っていないか)を正確に伝えていないことを舞台裏で行っています。そして、それは多くの問題につながる可能性があります。たとえば、タスクが実行されていない、またはタスクが実行されているユーザーアカウントがロックされているなどです。
グループポリシーの見出しの下にあるバッチジョブとしてのログオン(SeBatchLogonRight
)に関するMicrosoftのドキュメントでは、Microsoftは、「ユーザーがタスクをスケジュールすると、タスクスケジューラがこの権限を自動的に付与する」と述べています。まあ、このステートメントは常に正しいとは限りません。
タスクスケジューラGUIを使用してスケジュールされたタスクを作成する場合、はい。ユーザーがログオンしているかどうかに関係なく実行するようにスケジュールされたタスクが構成されていて、ユーザーがバッチジョブとしてログオンする権利を持っていない場合、タスクスケジューラは割り当てます。ユーザーに対するその権利(デフォルトが変更されていない限り、上記の参照リンクを参照してください)。
ただし、PowerShellのScheduledTaskモジュールのコマンドレットを使用してスケジュールされたタスクを作成する場合、この自動ユーザー権限の割り当ては発生しません。したがって、これは手動で行う必要があります。これを行うGUIの方法は、ローカルセキュリティポリシーMMC(Microsoft管理コンソール)を使用することです。もちろん、自動化のシナリオでは、GUIが出ているので、ここにいる友達はになりますsecedit.exe
。(私自身、PowerShellラッパーを作成しましsecedit.exe
た。)
したがって、サーバーで実行されているアプリケーションのテレメトリを収集するスケジュールされたタスクがあると仮定します。このタスクは、そのテレメトリを、たとえばNewRelicやDataDogなどのテレメトリ収集サービスに送信します。タスクはユーザーアカウントで実行されますCONTOSO\AppTelemetry
。青/緑の展開中にPowerShellを介して自動化された方法でスケジュールされたタスクを作成することを考えると、このユーザーにバッチジョブユーザー権利としてログオンを割り当てる必要があります。
secedit
ユーザー権限の割り当てに使用
ここでの手順は非常に簡単です。
- 既存のサーバーのセキュリティポリシーをセキュリティポリシーテンプレートにエクスポートします(オプションで、関心のあるセクションのみを含む)
- 新しいセキュリティポリシーテンプレートを作成する
- スケジュールされたタスクが実行されるユーザーのセキュリティ識別子(SID)を、新しいセキュリティポリシーテンプレートの対応するユーザー権利に追加します
- セキュリティポリシーの変更を含むセキュリティポリシーテンプレートを新しいデータベースにインポートします
- 手順4で作成したセキュリティポリシーデータベースに含まれる変更を組み込むように、システムのセキュリティポリシーを構成します。
これを実現するコードを見ていきましょう。
既存のサーバーのセキュリティポリシーをセキュリティポリシーテンプレートにエクスポートします
これは、CMDまたはPowerShellから実行できます(デスクトップエディションまたはコア、関係ありません)。特に明記されていない限り、すべての例はPowerShellに含まれます。
secedit.exe /export /cfg secpol.inf /areas USER_RIGHTS
上記のコマンドは、システムのセキュリティポリシーをエクスポートしますが、ユーザー権利の割り当てに関する情報を含むセクションのみをエクスポートします。レジストリキーなどの設定を追加する必要がある場合は、セキュリティポリシーを使用して追加することもできます。secedit.exe
一般に、またはセキュリティポリシーに関するMicrosoftのドキュメントをお読みください。
新しいセキュリティポリシーテンプレートを作成し、スケジュールされたタスクユーザーのSIDをSeBatchLogonRight
値に追加します
次に、新しいセキュリティポリシーになりたいものと、現在のセキュリティポリシーとのデルタを含む、新しいセキュリティポリシーテンプレートを作成する必要があります。新しいセキュリティポリシーテンプレートには、変更が必要な設定のみを含む情報をできるだけ少なくすることをお勧めします。
SeBatchLogonRight
あなたがする必要があるのは、ユーザーのSIDを含める必要があることを指定することだけだと思うでしょう。それだけです。しかし、あなたがそれを考えたなら、あなたは間違っているでしょう。にはSeBatchLogonRight
、デフォルトで一部のユーザーが割り当てられています(上記のMicrosoftドキュメントへの参照リンクを参照してください)。新しいポリシーテンプレートでユーザーのSIDをこの権利に割り当てるだけの場合、システムのセキュリティポリシーの既存の値は更新されず、効果的に置き換えられます。したがって、追加の変更を行っているため、上記でエクスポートしたシステムセキュリティポリシーの既存の値を使用して、このユーザー権利をテンプレートに追加してから、ユーザー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
(ユーザーアカウントのSIDを取得するために単に使用しなかったのはなぜGet-ADUser
ですか?2つの理由。1つは、を使用してSIDからユーザーアカウント名を取得するのはそれほど簡単ではありませんGet-ADUser
。それは可能ですが、上記のコードはより明確です。 、そして最も重要なこととして、すべてのユーザーがGet-ADUser
自分のマシンにインストールされているわけではありません。以前のラップトップにはWindowsリモートサーバー管理ツール(RSAT)がインストールされていたため、Get-ADUser
利用できました。RSATがインストールされていないか、ActiveDirectoryPowerShellモジュールがインストールされてGet-ADUser
いないと利用できません。上記のコマンドレットは、Windowsと.NETフレームワーク以外には依存せずに機能します。ユーザーアカウントSIDを取得しようとして、PowerShellを使用して取得しようとしている場合、定義上、2つの前提条件があります。)
次に、次のコマンドを実行するだけです。
$SID = 'CONTOSO\AppTelemtry' | ConvertTo-SecurityIdentifier
SIDができたので、セキュリティポリシーテンプレートを作成できます(これを行うには、より良い方法があります。これらのINFファイルをプログラムで操作できるPowerShellコマンドレットを作成しましたが、ここではドキュメントを使用します) 。エクスポートしたセキュリティポリシーでは、すべての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から直接ユーザーにログオンをバッチジョブとして割り当てることができます。
PowerShellを使用してスケジュールされたタスクを作成する
ユーザーがログインしているかどうかに関係なく、スケジュールされたタスクを実行する権利を持っているので、ユーザーがログインしているかどうかに関係なく実行されるスケジュールされたタスクを作成する必要があります。これまたはそのLogonType(特に、S4U
またはServiceAccount
)が必要であるか、最高の特権で実行するなどです。そのいずれもリッスンしないでください。それは(ほとんど)間違っています。このセクションでは、ユーザーがログオンしているかどうかに関係なく実行されるスケジュールされたタスクの作成を実現するために必要な最小限の手順の概要を説明します。
スケジュールされたタスクアクションを作成する
まず、タスクアクションを作成します。スケジュールされたタスクは、いくつかの部分で構成されています。
名前はかなり自明です。プログラムを実行するスケジュールされたタスクアクションを作成します。(他のアクションタイプがあります。タスクアクションのドキュメントを参照してください。)
Execアクションについて知っておくべき重要なことは、タスクスケジューラサービスがcmd.exe
提供されたプログラムを実行するためのインスタンスを生成することです。つまり、プログラムに引数を指定する必要がある場合は、CMDコマンドの難しい引用符の規則に従う必要があります。(引数によっては、これらの引用ルールでは、コマンドが期待どおりに実行されることを確認するために多くのテストが必要になる場合があります。私の単純なケースでも、間違っていて、タスクは正しく実行されたように見えました(0
終了コード)が、何もしませんでしたcmd.exe /?
すべての厄介な詳細については、を参照してください。また、あなたはウェブ検索を通して多くの情報を見つけることができます。
タスクアクションを作成しましょう:
$TaskAction = New-ScheduledTaskAction -Execute 'powershell.exe' `
-Argument ('-NoLogo -ExecutionPolicy Bypass -NoProfile -NonInteractive' + `
'-File "C:\Telemetry\Send-ApplicationTelemetry.ps1"')
-Command
繰り返しになりますが、引用符の規則に注意してください(つまり、ファイルパスにスペースが含まれている場合、またはここで行ったように使用するのではなく、パラメーターにPowerShellスクリプトを少し渡す場合-File
)。ここで指摘すべきことがいくつかあります。
pwsh.exe
代わりにPowerShellCoreを実行するために使用できました
-NoLogo
PowerShellの起動時に表示される「バナー」の印刷を回避します。このようなことを行うと、ログファイルへのリダイレクト出力がより適切になります。
-ExecutionPolicy Bypass
このスクリプトがシステム上の現在の実行ポリシーをバイパスすることを指定します。デフォルトの実行ポリシーはです。これはRestricted
、フルパスが指定されていない限り、スクリプトの実行を禁止し、リモートソースからのスクリプトを禁止します。このスイッチは基本的に、スクリプトが実行されることを保証します。必ずしも必要なわけではありませんが、スケジュールしているスクリプトを信頼できる場合は、これも問題にはなりません。
-NoProfile
bash
ユーザーのPowerShellプロファイルを読み込まないために使用されます( Linux OSのユーザーのプロファイルと同様の概念です)。ただし、グローバルプロファイルスクリプトが存在する場合もあります。PowerShellの起動時にプロファイルスクリプトを実行する必要があることがわかっていない限り、このスイッチを追加しても問題はなく、エラーを防ぐことができます。
-NonInteractive
非常に重要なスイッチです。基本的に、これにより、PowerShellがユーザー入力の入力を求めるプロンプトが表示されなくなります(たとえば、確認またはユーザー入力が必要なコマンドレットの場合)。これは、スクリプトが確認/ユーザー入力を必要とする場合、非対話的に(つまり、ユーザーがログオンしていない場合)機能しないことも意味します。
-File
指定されたファイルを実行するようにPowerShellに指示します。-Command
代わりに使用して、「文字列化された」PowerShellコードを渡すこともできます。
スケジュールされたタスクプリンシパルを作成する
これは、ユーザーがログインしているかどうかに関係なく実行されるスケジュールされたタスクを適切に作成するための最も重要な手順の1つです。実際、この手順は、ユーザーがログオンしているかどうかに関係なく実行するようにスケジュールされたタスクを構成するために必要です。
$TaskPrincipal = New-ScheduledTaskPrincipal -Id 'Author' `
-UserId 'CONTOSO\AppTelemetry' `
-LogonType Password `
-RunLevel Limited
これらの設定についてもう少し詳しく見ていきましょう。
-Id
私が知る限り、これは自由形式のテキストフィールドです。タスクスケジューラGUIは常に「作成者」という用語を使用しますが、通常、ここでは好きなものを使用できます。スケジュールされたタスクには、さまざまなアクション(最大32)が含まれている可能性があることに注意してください。また、アクションには「コンテキスト」が関連付けられています。タスクスケジューラは、コンテキストを「作成者」に自動的に割り当てます(単純なシングルアクションタスクの場合)。ドキュメントには、スケジュールされたタスクに複数のプリンシパルを提供できることが示されているようです。プリンシパルのIDは、アクションのコンテキストと相関して、アクションが実行されるプリンシパルを決定します。シングルアクションタスクでは、「作成者」を使用します。
-UserId
また-GroupId
タスクは、ユーザーアカウントまたは指定されたグループに属する任意のユーザーで実行できます。グループを使用する場合、グループのユーザーがログオンしている場合にのみ、スケジュールされたタスクが実行されることに注意してください。これは、グループIDを使用すると、タスクのパスワードを指定できないためです。
99%の場合、上記のように使用したいと思うでしょう-UserId
。完全に修飾されたユーザーIDである必要はありません。パスワードはプリンシパルと一緒に保存されないことに注意してください。それは後で来ます。
-LogonType
これは、おそらくこのコマンドレットで最も誤解されているパラメーターの1つです。LogonTypeに関するドキュメントはかなり良いです。残念ながら、PowerShellヘルプNew-ScheduledTaskPrincipal
はそれにリンクしていません(私はそれについてのフィードバックを入れました)。
S4U
基本的に、ログオンの種類を気にする必要はおそらくないでしょう。いくつかのシナリオでは関連があるかもしれませんが(それが存在する理由です)、あなたがやりたいと思うほとんどすべてのことについて、おそらくそうではないでしょう。
たとえば、スケジュールされたタスクがユーザーアカウントで実行される場合にのみ、ServiceAccount
ログオンタイプを使用します。(3つ目はありますが、現時点では名前がわかりません。)タスクはシステムアカウントで実行されていないため、これは必要な値ではありません。NT AUTHORITY\LOCALSYSTEM
NT AUTHORITY\SYSTEM
ご想像のとおり、これInteractive
は、ログオンしたユーザーのコンテキストでタスクが実行されることを意味します。これは、タスクがGUIアプリケーションを開始する必要がある場合、またはタスクアクションがシステムにダイアログを表示するためのメッセージボックスである場合に役立ちます。明らかに、これは私たちの場合には役に立ちません。InteractiveOrPassword
このログオンタイプと必要なタイプを組み合わせたタイプもありPassword
ます。したがって、これ以上は説明しません。
そして今、もちろん、Password
私たちが望むものであるログオンタイプ。これは、パスワードがスケジュールされたタスクとともに保存され、ユーザーがログオンしているかどうかに関係なくタスクを実行できるように(バッチジョブとして)ユーザーにログオンするために使用されることを意味します。はい、これは、タスクスケジューラUIの[ユーザーがログオンしているかどうかを実行する]チェックボックスをオンにする値です。
-RunLevel
このオプションは、タスクを昇格された特権で実行する必要があるかどうかを指定するものと考えてください。一部のソフトウェアのインストールなどのコマンドを実行すると、ユーザーアクセス制御(UAC)が起動し、プログラムによるコンピューターへの変更を許可するかどうかを尋ねるダイアログが表示されます。あなたはおそらくYes
それを読まなくても半分の時間クリックします。このパラメーターをに設定することは、UACダイアログHighest
をクリックYes
するのと似ています(または、昇格された特権でプロセスを開始する、つまり管理者として実行する)。あなたがそれを必要としていることを知らない限り、これは悪いことです。あなたは常にから始めるべきですLimited
、およびスケジュールされたタスクがこれらの特権で実行されないことがわかった場合は、昇格します。それが私がやったことです。私は最小特権の原則から始めて、Limited
実行レベルを使用しています。
スケジュールされたタスクトリガーの作成
スケジュールされたタスクをトリガーするトリガーを1つ以上持つことができます。さまざまなトリガータイプがあります。これらのさまざまなタイプのドキュメントを参照してください。非常に単純なものを実行します。
$TaskTrigger = New-ScheduledTaskTrigger -Once -At ([DateTime]::Now.AddMinutes(5)) `
-RepetitionInterval ( `
[TimeSpan]::FromMinutes(5) `
)
今から5分後に開始し、その後5分ごとに実行するタスクが必要です。
タスク設定セット
他のタスク設定をカスタマイズする必要がある場合は、を使用してカスタマイズできますNew-ScheduledTaskSettingsSet
。デフォルトで問題ないので、これをスキップします。ただし、タスクスケジューラのドキュメントにはすべてが詳しく説明されており、実際、設定の大部分は非常にわかりやすいものです(RunOnlyIfNetworkIsAvailableを除くが、理解するのはそれほど難しくありません)。
スケジュールされたタスクを作成する
ここでタスクを作成し、タスクスケジューラで確認できると思うかもしれません。間違い。ただし、スケジュールされたタスクオブジェクトを作成します。この手順を実行する必要があります。そうしないと、タスクが正しく登録されません。最も重要なのは、ユーザーがログインしているかどうかに関係なく、実行するように設定されないことです。
$Task = New-ScheduledTask -Description 'Collects application telemetry' `
-Action $TaskAction `
-Principal $TaskPrincipal `
-Trigger $TaskTrigger
ここで行われているのは、スケジュールされたタスクインスタンスを作成していることだけです。ただし、タスクスケジューラのタスクのリストには追加されていません。これは登録と呼ばれ、次に行います。上記のコマンドを実行した後でシェルに入力するだけ$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
。指定されたユーザーは、作成したプリンシパルで指定されたユーザーと同じである必要があります。そして明らかに、そのユーザーのパスワードは正しい必要があります。別のユーザーを使用してスケジュールされたタスクを登録すると、タスクプリンシパルが更新され、タスクの登録時に指定されたユーザーアカウントを使用してタスクが実行されます。(それはここに文書化されています。)
これで、ユーザーがPowerShellを使用してログオンしているかどうかに関係なく、実行するタスクをスケジュールすることができます。ただし、終了する前にもう1つ話し合うトピックがあります。それは、スケジュールされたタスクの更新です。
スケジュールされたタスクの更新
信じられないかもしれませんが、これは思ったほど簡単ではありません。の構文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
を使用してPowerShellを介してスケジュールされたタスクのXML表現を取得することもできますExport-ScheduledTask
。すべてのPowerShellスケジュールタスクコマンドレットのドキュメントは、ここにあります。ドキュメントはOKです。一部は誤解を招く可能性があり、ほとんどは不完全です(つまり、PowerShellコマンドレット自体とは別にタスクスケジューラに関する知識があることを前提としています)。タスクスケジューラ、そのCOMインターフェイス、XMLスキーマなどに関するドキュメントは、ここにあります。
結論
これを理解するのに長い時間がかかったので、これが誰かに役立つことを願っています。主に、「成功したと言っても、なぜこのタスクは何もしないのですか?」または「アカウントがロックアウトされ続けるのはなぜですか?」(ログオンの種類、パスワード、ユーザー権限の割り当て、または3つすべてが間違っています!)