Powershellを使ったバックアップツールを作ってみた(検証1回目)

投稿者:

今回はPowershell & .NetCoreあたりを利用したバックアップツールを作ってみました。
Windowsタスクスケジューラ等でぐりぐり回せば幸せになれるかな。

要件としてはアプリケーションを強制終了(Kill)せずにバックアップを取得するのが目的。
あとは個人的にFunctionと外だし設定スクリプトを書いてみたかった。

【やりたいこと】
Thunderbirdのプロファイルバックアップ
メール編集中とかはスキップしたい。。。

【環境】
Windows 10
Powershell
※バッチ不使用、バックアップはRobocopy

それでは、検証開始。
まずは、プロセスの特定をしていく。

Powershellでプロセス取得するには、「Get-Process」コマンドを利用する。
まずは何も考えずに。
勿論取得したいアプリは起動していることが前提

Get-Process

結果はこんな感じに「thunderbird」プロセスがあることが確認できる。

Handles  NPM(K)    PM(K)      WS(K)     CPU(s)     Id  SI ProcessName
-------  ------    -----      -----     ------     --  -- -----------
    354      21     8964      27060       0.05   8640   1 ApplicationFrameHost
    164      10     5872       8020       0.02   8444   0 audiodg
    328      18     6076      24032       0.56   8052   1 conhost
    588      22     1816       5384               572   0 csrss
 .....
   1215      92   160736     232376       1.89   1548   1 thunderbird
    159      10     1816       8120              2348   0 VSSVC
    690      25    10936      40592       0.23   5608   1 WindowsInternal.ComposableShell.Experiences.TextInput.InputApp
    174      11     1664       7024               648   0 wininit
    295      14     2896      12516               720   1 winlogon
    231      11     2488       9000              8220   3 winlogon
    524      36    15092      13792       0.13   8664   1 WinStore.App
    332      14     5916      14864              7824   0 WmiPrvSE
    191      11     2572       9164              9384   0 WmiPrvSE
    325      13     7580      26312              3428   0 WUDFHost
    729      48    23048      10556       0.42   6864   1 YourPhone

次にプロセス名だけで、出力できるか見てみる。
コマンドに -Nameオプションでプロセス名を入れる。

Get-Process -Name thunderbird

Handles  NPM(K)    PM(K)      WS(K)     CPU(s)     Id  SI ProcessName
-------  ------    -----      -----     ------     --  -- -----------
   1325     103   145592     230808       3.53   1548   1 thunderbird

絞れた。
勿論アプリが停止していれば、表示されなくなる。
Powershellの場合はこんな感じで怒ってくる。

Get-Process -Name thunderbird
Get-Process : 名前 "thunderbird" のプロセスが見つかりません。プロセス名を確認し、コマンドレットを再度呼び出してください。
発生場所 行:1 文字:1
+ Get-Process -Name thunderbird
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : ObjectNotFound: (thunderbird:String) [Get-Process], ProcessCommandException
    + FullyQualifiedErrorId : NoProcessFoundForGivenName,Microsoft.PowerShell.Commands.GetProcessCommand

起動しているかいないのか、判定するのにこれではやりづらいので、
コマンドの実行結果を取得する。

コマンドそのものが成功したかを見たい場合は「$?」を入れれば確認できる。
Trueなら成功、失敗ならFalseとなる。
コマンドによっては最後まで走り切るタイプのものもあるので、それらは「$?」を使った場合、
永遠にTrueが入るので、気を付けるように。
そんなタイプの終了コードを調べたい場合は、「$LASTEXITCODE」を使って終了のコード番号から、
成功か失敗かを判定することとなる。
それはこの後のRobocopy検証で話そうと思う。

では、脱線したが、プロセスの調べ方について。
コマンドの実行は成功失敗を判断できるようになったが、そもそもプロセスって一つだけしか起動しないの?ってなると思う。

例えばWindowsのメモ帳などだと複数のテキストを編集しているときはこうなる。

Get-Process -Name notepad

Handles  NPM(K)    PM(K)      WS(K)     CPU(s)     Id  SI ProcessName
-------  ------    -----      -----     ------     --  -- -----------
    271      17     4140      20908       0.14   5672   1 notepad
    271      17     4160      20896       0.14   7408   1 notepad
    268      17     4148      20532       0.06   8320   1 notepad
    268      17     4136      20544       0.11   9748   1 notepad

ものによってはメインプロセスと子プロセスが分かれて起動している場合もあるので、
それらも併せて停止が必要になる事もあります。

まずは、何もしていない状態のプロセス一覧を取得して、
次にお目当てのアプリを起動した状態で再度取得して比較すれば何が増えたのかは容易にわかる。
もうちょっと詳しく見たいなら。

Get-Process notepad | Format-List *

とすれば詳細の情報が表示されます。
今回は複数のプロセスが起動しているであろう想定で、取得結果を見ていく。

(Get-Process -ErrorAction 0 "thunderbird").Count

上記はGet-Processで取得したオブジェクトの数を表示するようにしている。
またプロセスがない時のエラー表示がウザいので、-ErrorActionを0に指定して出力を抑止している。
目的はプロセスの数が0なのかそれ以外なのかを知りたいだけなので問題ない。
Powershellでは結果はString(文字)ではなくオブジェクト(文字以外の何らかの型)になるので、
結果を「()」でくくって.Countとやってやれば要素の数=プロセス数が取得できる。

(Get-Process -ErrorAction 0 "thunderbird").Count
0

 (Get-Process -ErrorAction 0 "notepad").Count
4

あとはこの数字を変数に当ててあげてifなりで処理を判断させればよい。

$ps_count = (Get-Process -ErrorAction 0 "thunderbird").Count

if ($ps_count -eq 0){
  Write-Host "雷鳥は停止中"
  }else{
  Write-Host "雷鳥は起動中"
  }

結果

雷鳥は停止中

ではここから、アプリの停止処理を実装していくことになる。
単純に強制終了でも問題ないアプリなら「Stop-Process -Name “Thunderbird”」としてやれば落ちるのだが、環境ファイルなどが壊れる可能性もあるので、GUIでいうとバツボタンを押した状態にしてやりたい。

そんな時は「.CloseMainWindow」を使用して閉じると同様の動作になる。
但し、あくまでもMainWindowが取得できる状態にあることが条件となる。

つまりPowershellを実行しているユーザーとは別のユーザーが開いているアプリは閉じれないのです。
どうしても別のユーザープロセスを閉じたい場合はStop-Processを使ってあげることになる。
※勉強不足なのかも、他にいい方法あるならコメントで教えてください。

とりま、こんな感じで書く、起動していれば停止、停止していれば何もしない。

$ps_count = (Get-Process -ErrorAction 0 "thunderbird").Count

if ($ps_count -eq 0){
  Write-Host "雷鳥は停止中"
  }else{
  Write-Host "雷鳥は起動中"
  Write-Host "雷鳥停止します"
  (Get-Process thunderbird).CloseMainWindow() 
  }

結果

雷鳥は起動中
雷鳥停止します

な感じになって、Thunderbirdは正常に終了する。
CloseMainWindowについてはドキュメントがいくつもあるのですが、今一掴めない内容だったので、
いろいろ検証してみる。

検証1 MainWindowsなんだからきっとメインプロセスのウィンドウか今アクティブな物に対して働くんだろう。
結果 指定したプロセスのウィンドウ全体に作用する。

メモ帳4つ開いてやってみると。

ノートパッドを4つ起動してプロセスは4の状態

$ps_count = (Get-Process -ErrorAction 0 "notepad").Count

if ($ps_count -eq 0){
  Write-Host "ノートパッドは停止中"
  }else{
  Write-Host "ノートパッドは起動中"
  Write-Host "ノートパッド停止します"
  (Get-Process notepad).CloseMainWindow() 
  }

結果
ノートパッドは起動中
ノートパッド停止します
True
True
True
True

こんな感じで、Get-Processで合致したプロセス全体に走って閉じた。

検証2 上書きとかが必要なダイアログが出たらどうなるの?
結果 処理は走るがプロセスはユーザーの入力待ちになって落ちない。

まぁこれが、強制終了との違いで今回のキモなわけですね。

はい、これが出て処理自体はTrueで終わりました。
ちなみにこの状態で再実行した場合は停止処理がFalseになる。
それが返ってくるだけでプロセスは落ちません。

実際は保存するとかしたいところですが、今回はその要件はないので、
起動中で確認ダイアログがでちゃったら動作止める。としてます。

スクリプトの動作自体を止めるにはもういっちょif文でプロセス数取ってあげて、
0以外なら停止できない旨吐いて終了でいいからちょちょっと書く
更に分岐させたいのなら最後に追加したif文で表現をしていくことになる。

以下では単純にメッセージを吐いてスクリプト最下行まで到達して終了となるが、
判定を入れていて後続処理がある場合はexitなどを置いて
判定が出た時点でスクリプトを終了することもできる。

$ps_count = (Get-Process -ErrorAction 0 "notepad").Count

if ($ps_count -eq 0){
  Write-Host "ノートパッドは停止中"
  }else{
  Write-Host "ノートパッドは起動中"
  Write-Host "ノートパッド停止します"
  (Get-Process notepad).CloseMainWindow() 
  }

$ps_count2 = (Get-Process -ErrorAction 0 "notepad").Count

if ($ps_count2 -ne 0){
  Write-Host "ノートパッドは停止できませんでした"
  }

結果
ノートパッドは起動中
ノートパッド停止します
False
False
ノートパッドは停止できませんでした

では、次にこの停止判定を使いまわしたいのでFunction化していく。

そんなに難しくない、判定部分だけをFunctionでくくってあとは好きなところに配置して、
使いたいところでFunction名で呼び出せばいいだけ。便利

#判定ファンクション
Function hantei {
$ps_count2 = (Get-Process -ErrorAction 0 "notepad").Count

if ($ps_count2 -ne 0){
  Write-Host "ノートパッドは停止できませんでした"
  }
}
#ここまで

$ps_count = (Get-Process -ErrorAction 0 "notepad").Count

if ($ps_count -eq 0){
  Write-Host "ノートパッドは停止中"
  }else{
  Write-Host "ノートパッドは起動中"
  Write-Host "ノートパッド停止します"
  (Get-Process notepad).CloseMainWindow() 
  hantei
  }

大体ほら、(Get-Process notepad).CloseMainWindow()の直後に判定入れたいじゃないっすか。
でもifの中にifをネストすると可読性最悪になるんで、Functionでhanteiって入れてあげれば、
判定が動いてくれるようになってます。

hentaiではないので読み違えないでね。

こんな感じで処理をFunction化していけば、必要な時に呼び出して使えるので、
コードをだらだら書かなくて済むようになります。

さて今回はこのぐらいにして、次回は変数設定スクリプトを作ってみることにします。

返信を残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です