【PowerShell】特定のプロセス起動を監視したいですか?~Register-WMIEvent コマンドレット


imageぜんぜん話は違うのですが、日本の SQL Server サポートチームの Blog が気合入っています(笑)。今後ともご贔屓にしてくださいませ。評判が悪かったら元に戻すと言っていたので、4月には元に戻っている可能性があります。

さて、かなり前のことになりますが、以下の資料を SlideShare に投稿しました。このスライドでは、VBScript を使用して、WMI のイベントをリアルタイムに収集するスクリプトについて解説しています。

image

このスライドで解説している手法を使用すると、システム内に発生したさまざまなイベントを待ち合わせて、その次のアクションを自動的に実行することができます。

例えば、次のような処理が簡単に自作できます。

  • 監査ログを監視して、ユーザーが作成されことを検出したらホームディレクトリを自動的に作成する
  • 特定のサービスを監視して、停止したらメールする
  • 特定の誰かがログオンしたらメッセージを表示する
  • 特定のプログラムが起動したら強制的にKILLする

などなど。

※上記スライドには MOF を使用してスクリプトをサービス化する手法についても書かれています

【PowerShell 1.0 の場合】

これと同じことが PowerShell でできないだろうか....と思いつつすっかり忘れていましたが、実は初期の PowerShell の頃から同じことは実現可能でした。

(参考)Windows PowerShell 2.0 - WMI Event Monitoring

上記の参考リンクでは、以下の様なスクリプトを紹介しています(ちょっとだけ変えています)。このスクリプトでは、WMI の __InstanceCreationEvent というイベント監視用クラスを使用し、Win32_Process を監視しています。クラス名に「Creation」という言葉が入っている通り、「何らかのプロセスが起動したらコンソールにプロセス名を表示する」という動作をします。

$a = 0 
$timespan = New-Object System.TimeSpan(0, 0, 1)
$scope = New-Object System.Management.ManagementScope("\\.\root\cimV2")
$query = New-Object System.Management.WQLEventQuery `
("__InstanceCreationEvent",$timespan, "TargetInstance ISA 'Win32_Process'" )
$watcher = New-Object System.Management.ManagementEventWatcher($scope,$query)
do
{
$b = $watcher.WaitForNextEvent()
$b.TargetInstance.Name
}
while ($a -ne 1)

このスクリプトの記載をご覧いただければわかる通り、VBScript とほとんど同じです。Do ~ While でイベントを待ち合わせるのも同じですね。

もし、「プロセスの停止」を待ち合わせるのであれば、クラス部分を __InstanceDeletionEvent に置き換えます。

非常に便利なのですが、この書き方だとスクリプトを常駐しておかなければならないという面倒くささがあります。

【PowerShell 2.0 の場合】

PowerShell  2.0 では、専用のコマンドレットが提供されました。

Register-WMIEvent コマンドレットです。

このコマンドレットにより、上記のスライドに書かれた「スクリプトのサービス化」なんてことをせずとも、WMIイベントを待ち合わせるジョブがシステムに登録されます。

※ 以前、田辺さんが紹介されていたのを思い出しました(Windows PowerShell V2 の新機能)。

そこで、上記のスクリプトを Register-WMIEvent で置き換えると、以下ようなかんじになります。

少し長いですが、これで1行です。

Register-WMIEvent -query "Select * From __InstanceCreationEvent within 3 
Where TargetInstance ISA 'Win32_Process'"

-sourceIdentifier "NewProcess"
-Action { $Event.SourceEventArgs.NewEvent.TargetInstance.Name |
Out-File -FilePath "c:\tmp\log.txt" -Append}

 

-query の部分は vbscript と変わらないですね。

上記コマンドレットを実行すると、新しいプロセスが起動するびに、- Action {} 部分が実行されます。

ここでは起動したプロセスの Name を log.txt というテキストファイルに Out-File しています。

$Event は、このコマンドレットに組み込みの変数で、WMI のイベントが格納されています。このコマンドレット内でした使用することができません。

$Event.SourceEventArgs.NewEvent.TargetInstance は、イベント(ここではプロセスの起動)が発生したときに返される Win32_Process のインスタンスです。

ためしに、以下のように修正して実行し、log.txt の結果を見てみましょう。

Register-WMIEvent -query "Select * From __InstanceCreationEvent within 3
Where TargetInstance ISA 'Win32_Process'"

-sourceIdentifier "testProcess"
-Action { $Event.SourceEventArgs.NewEvent.TargetInstance | Get-Member |
Out-File -FilePath "c:\tmp\log.txt" -Append}

以下のような結果が格納されているはずです。Win32_Process だってことがわかりますね。


TypeName: System.Management.ManagementBaseObject#root\CIMV2\Win32_Process

Name MemberType Definition
---- ---------- ----------
AttachDebugger Method System.Management.ManagementBaseObject...
GetOwner Method System.Management.ManagementBaseObject...
GetOwnerSid Method System.Management.ManagementBaseObject...
SetPriority Method System.Management.ManagementBaseObject...
Terminate Method System.Management.ManagementBaseObject...
Caption Property System.String Caption {get;set;}
CommandLine Property System.String CommandLine {get;set;}
CreationClassName Property System.String CreationClassName {get;s...
CreationDate Property System.String CreationDate {get;set;}
CSCreationClassName Property System.String CSCreationClassName {get...
CSName Property System.String CSName {get;set;}
Description Property System.String Description {get;set;}
ExecutablePath Property System.String ExecutablePath {get;set;}
ExecutionState Property System.UInt16 ExecutionState {get;set;}
Handle Property System.String Handle {get;set;}
HandleCount Property System.UInt32 HandleCount {get;set;}
InstallDate Property System.String InstallDate {get;set;}
KernelModeTime Property System.UInt64 KernelModeTime {get;set;}
MaximumWorkingSetSize Property System.UInt32 MaximumWorkingSetSize {g...
MinimumWorkingSetSize Property System.UInt32 MinimumWorkingSetSize {g...
Name Property System.String Name {get;set;}
OSCreationClassName Property System.String OSCreationClassName {get...
OSName Property System.String OSName {get;set;}
OtherOperationCount Property System.UInt64 OtherOperationCount {get...
OtherTransferCount Property System.UInt64 OtherTransferCount {get;...
PageFaults Property System.UInt32 PageFaults {get;set;}
PageFileUsage Property System.UInt32 PageFileUsage {get;set;}
ParentProcessId Property System.UInt32 ParentProcessId {get;set;}
PeakPageFileUsage Property System.UInt32 PeakPageFileUsage {get;s...
PeakVirtualSize Property System.UInt64 PeakVirtualSize {get;set;}
PeakWorkingSetSize Property System.UInt32 PeakWorkingSetSize {get;...
Priority Property System.UInt32 Priority {get;set;}
PrivatePageCount Property System.UInt64 PrivatePageCount {get;set;}
ProcessId Property System.UInt32 ProcessId {get;set;}
QuotaNonPagedPoolUsage Property System.UInt32 QuotaNonPagedPoolUsage {...
QuotaPagedPoolUsage Property System.UInt32 QuotaPagedPoolUsage {get...
QuotaPeakNonPagedPoolUsage Property System.UInt32 QuotaPeakNonPagedPoolUsa...
QuotaPeakPagedPoolUsage Property System.UInt32 QuotaPeakPagedPoolUsage ...
ReadOperationCount Property System.UInt64 ReadOperationCount {get;...
ReadTransferCount Property System.UInt64 ReadTransferCount {get;s...
SessionId Property System.UInt32 SessionId {get;set;}
Status Property System.String Status {get;set;}
TerminationDate Property System.String TerminationDate {get;set;}
ThreadCount Property System.UInt32 ThreadCount {get;set;}
UserModeTime Property System.UInt64 UserModeTime {get;set;}
VirtualSize Property System.UInt64 VirtualSize {get;set;}
WindowsVersion Property System.String WindowsVersion {get;set;}
WorkingSetSize Property System.UInt64 WorkingSetSize {get;set;}
WriteOperationCount Property System.UInt64 WriteOperationCount {get...
WriteTransferCount Property System.UInt64 WriteTransferCount {get;...
__CLASS Property System.String __CLASS {get;set;}
__DERIVATION Property System.String[] __DERIVATION {get;set;}
__DYNASTY Property System.String __DYNASTY {get;set;}
__GENUS Property System.Int32 __GENUS {get;set;}
__NAMESPACE Property System.String __NAMESPACE {get;set;}
__PATH Property System.String __PATH {get;set;}
__PROPERTY_COUNT Property System.Int32 __PROPERTY_COUNT {get;set;}
__RELPATH Property System.String __RELPATH {get;set;}
__SERVER Property System.String __SERVER {get;set;}
__SUPERCLASS Property System.String __SUPERCLASS {get;set;}


ここで注意していただきたいのが –SourceIdentifier パラメタです。-SourceIdentifier には、このジョブの識別名を指定します。ここでは NewProcess と指定しています。
同じ名前の識別名は指定できないので注意してください。

登録しておけば、あとは何もせずともずっと監視が続けられます。もちろん、OS を再起動しても消えません。

監視を終了するには、以下のように Unregister-Event コマンドレットを使用します。

Unregister-Event NewProcess

今回は単純な例をご紹介しましたが、もっともっと複雑な処理を実装することもできます。

それについては今後。

p.s.

テストを繰り返していると以下のエラーに見舞われることがあります。そのときは Windows Management Instrumentation サービスを再起動してください。そういう意味では...本番環境ではテストをしないほうがよいです。

Register-WmiEvent : クォータ違反です
発生場所 行:1 文字:18
+ Register-WMIEvent <<<< -query
"Select * From __InstanceCreationEvent within
3 Where TargetInstance ISA 'Win32_Process'"
-sourceIdentifier "NewProcess" -Act
ion { $Event.SourceEventArgs.NewEvent.TargetInstance.Name | Out-File -FilePath
"c:\tmp\log.txt" -Append}
+ CategoryInfo : NotSpecified: (:) [Register-WmiEvent]、Management
Exception
+ FullyQualifiedErrorId : System.Management.ManagementException,Microsoft.
PowerShell.Commands.RegisterWmiEventCommand
 
これ、解消されていなかったんだなぁ....。
Comments (7)

  1. 匿名 より:

    ssuzuki さん、返信が遅くなりましてすみません!

    >そのセッションを終了したら消えてなくなるのではないでしょうか?

    いまWin8で確認したところ。。。うわ、ほんとだ。。。ちょっと対策確認しますね。

    ご指摘感謝です!

  2. ssuzuki より:

    古いブログに対して申し訳ありません。大変、役にたっております。

    ところで Register-WMIEvent ってその PowerSehll セッション (といういい方で正しい?) でのみ有効で、そのセッションを終了したら消えてなくなるのではないでしょうか?OS を再起動しても消えない、という記述があるのですが、これは  Register-WMIEvent プラス、何かをすれば、という意味でしょうか?

  3. ssuzuki より:

    やっぱ MOF ですかね?

    MOF でできることはわかっているのですが、MOF ファイルの書き方がイマイチわかりません。

    MOF ファイルの文法が書かれた文書(URL)を探しているのですが、見つかっていません。

  4. ssuzuki より:

    MOF でやっていたことを PS1 に書き換え、それをさらに C# に書き換えました。

    自己解決しました。

    お騒がせしました。

  5. lice より:

    ローカルマシン内のとあるプロセスの終了を監視して別プロセスを実行する方法を簡単に実現したく
    いろいろ調査しておりこちらのページに辿り着きました。
    丁寧に記載いただいており、あらかた理解できたのですが、ssuzuki様が書かれているように、
    起動中のpowershellセッションでのみ有効となるようです。
    ssuzuki様はご自分で解決されたようですが、恥ずかしながら私の力不足で、マシン起動中に
    常時有効にする方法がわからないのです。
    よろしければご教授いただけると助かります。

  6. ssuzuki より:

    もう1年も経過しますので、記憶を頼りにざっと説明します。

    MOF については、安納さんが書いている
    http://blogs.technet.com/b/junichia/archive/2009/04/07/3223444.aspx
    を参考にしてください。MOF で書くと永続化します。

    したがって、このような MOF を PowerShell で書き換えることになります。

    MOF はフィルタ、スクリプト、そしてバインドの部分の3つから構成されますが、それらはそれぞれ、Set-WmiInstance を使って PowerShell スクリプトに修正します。

    なお、これだけですと、一度作った PowerShell スクリプトを修正し、それを再び実行しても同名のオブジェクトがあると怒られてしまいます。最初に、Get-WmiObject を使って同名のオブジェクトがあるかどうか調べ、あった場合は Remove-WMIObject で削除します。

  7. junichia より:

    す、すみません。。。。返信していないことに今頃気づきました。。。
    別件で記事を執筆しており、その関係で検索したら自分の記事に行きあたったという。。。本当にごめんなさい。
    で、おっしゃるとおり、MOFです。
    で、調べてみたら便利なモジュールが CodePlexで提供されていました。
    ぜひこちらを使ってみてください。。。といいつつ私もこれから検証します。
    https://powerevents.codeplex.com/

Skip to main content