JenkinsでPowerShellを使う際の注意事項

# JenkinsでPowerShellを使う際の注意事項

Windows PowerShell、慣れると便利なんですけど、バージョンアップのたびに前提が変わったりするので、複数の環境が混在していたりするとハマります。
ネットで検索しても、バージョンによって異なる情報が出てきて混乱するし。
あと、ドメイン環境と非ドメイン環境、Jenkinsとかサービスから実行する場合などでも動作が変わるので注意が必要です。
（基本的には実行ユーザの問題と、マシン間の信頼関係の問題なんですが、未だに理解しきれません。間違ってるところあればコメント願います。。。）

ここでは、JenkinsからPowerShellで複数のマシンをコントロールする際にハマりやすいところを中心にメモしておきます。
なお、以降でJenkins側と言ってるのはあくまでPowerShellを実行するマシンのことです。Jenkins Slave使って更に他のマシンからPowerShellを実行する場合は、そこがJenkins側にあたることになります。

なお、以降PoweShellの設定を行うコマンドは、基本的に全て管理者権限のある状態で実行する必要があります。

|ソフト等|バージョン|備考|
|:------|:--------|:---|
|Windows Server 2012R2||
|PowerShell|v4.0|2012R2標準|
|Jenkins|v1.647||
|PowerShell plugin|v1.3||

リモート側は以下の通り。

|ソフト等|バージョン|備考|
|:------|:--------|:---|
|Windows 10|||
|PowerShell|v5.0|Windows10標準|

---
## Jenkins側の設定

### PowerShell plugin

何は無くとも、[PowerShell plugin](https://wiki.jenkins-ci.org/display/JENKINS/PowerShell+Plugin)を入れます。
ネットにつながっていれば、プラグインマネージャーからチェックしてインストール。
つながってなければ、つながっているマシンで[(archives)](http://updates.jenkins-ci.org/download/plugins/powershell/) とかから辿って、powershell.hpiファイルを入手してください。
プラグインマネージャの高度な設定から、プラグインのアップロードでインストールします。

### Jenkinsサービスのユーザ変更

Windowsのサービスから「Jenkins」のプロパティを表示すると、ログオンタブにJenkinsサービスを実行するユーザが設定されていますが、デフォルトではマシンのローカルシステムアカウントなどになっていると思います。
これでもPowerShellを実行することはできますが、他のマシンでのリモート実行を行うことが出来ません。
リモート実行を行う場合は、必要に応じて「Administrator」などのアカウントを設定します。（要Jenkinsサービスの再起動）
なお、ドメイン環境ならぜひAdministrator権限を持つドメインユーザの設定をお勧めします。リモート接続が簡単になります。

---
## Jenkins側（リモート操作する側）と、操作される側に共通の設定

### PowerShell実行ポリシー

PowerShellのスクリプトファイル(.ps1ファイル)の実行は、PowerShellのバージョンによってデフォルトの実行ポリシーが違うので、そのままでは動かない場合があります。（まだ動かない環境の方が多いでしょう。）
PowerShellを実行し、次のコマンドで確認できます。（意味は[ここ](https://technet.microsoft.com/ja-jp/library/ee176847.aspx)とか参照）

> Get-ExecutionPolicy

Windows Server 2012（PowerShell v4.0）以降などは、初めから「RemoteSigned」なので変更不要ですが、「Restricted」などの場合は次のようなコマンドを入力し適切な設定が必要です。

> Set-ExecutionPolicy RemoteSigned

---
## Jenkins側（リモート操作する側）の設定

### WinRmサービスの構成

WinRm（リモート管理サービス）は、細かい違いが多くて自分の中でも整理できていませんが、とりあえず次のコマンドで普通は設定できます。（プロンプトは全部「y」で大丈夫）

> WinRm QuickConfig

### 信頼できるマシンの設定

非ドメイン環境では、事前にリモート接続する相手のマシンを登録しておく必要があります。
（ドメイン環境ならやる必要なし。初めての非ドメイン環境は、これでハマった）

> Set-Item WSMan:\localhost\Client\TrustedHosts <相手マシン名>

（相手マシンが多いとか、安全な環境なら、<相手マシン名>は「*」でもＯＫ。）

---
## 操作される側の設定

### リモート接続の構成

次のコマンドで、ポートとかのリモート接続の受け側の設定が行われます。
（これにはWinRmサービスの構成も含まれています。）

> Enable-PSRemoting

### リモート接続するユーザの設定

Jenkinsを実行しているユーザと同じ、またはAdministrator権限を持つユーザが有効になっている必要があります。
ドメイン環境なら何もせずとも問題ない場合が多いのですが、非ドメイン環境の場合、特にAdministratorがデフォルトで無効になっているクライアントOS（Windows10とか）の場合、注意が必要です。
一番無難なのは、JenkinsをAdministratorで動かし、操作される側もAdministratorを有効にした状態です。

![admin.png](https://qiita-image-store.s3.amazonaws.com/0/111541/aadd7bc9-2822-614c-9e68-1f88289e6b2b.png)

---
## Jenkinsからリモートでコマンドを実行してみる

Jenkinsでビルドを作り、ビルド手順の追加で「Windows PowerShell」を選択し、以下のソースを貼り付けます。

###　PowerShellソース

リモートでコマンドを実行する先のマシン名は、初めに配列で記述してあります。
なお、リモート側で実行するコマンドの中身は、[PowerShellからMongoDBにアクセスする。](http://qiita.com/Helicobacter_pylori/items/a76b9d37f3bce259c990)と同じものなのでそちらを参考にしてください。

```ps1:
##### 指定マシンの情報をリモートジョブで取得 #####

# 対象のマシン名
$REMOTE = @( 'V1', 'V2', 'V3' );

# 開始日時を表示
$date = Get-Date;
"##### $date #####";

# 対象のマシンで同時に(AsJob)ジョブ実行
ForEach($computername in $REMOTE) {
  Invoke-Command -ComputerName $computername -AsJob -ScriptBlock {
    @{
      "Processor_per" = [int](Get-Counter -Counter "\Processor(_Total)\% Processor Time").CounterSamples.CookedValue;
      "Network_byte" = [int]((Get-Counter -Counter "\Network Interface(*)\Bytes Total/sec").CounterSamples.CookedValue | Measure-Object -Maximum).Maximum;
      "Disk_byte" = [int](Get-Counter -Counter "\LogicalDisk(_Total)\Disk Bytes/sec").CounterSamples.CookedValue;
    }
  }
};

# ジョブが無くなるまで繰り返し
While((Get-Job) -ne $null) {
  # 1秒待ち、日時とジョブの状況を表示
  Start-Sleep -Second 1;
  $date = Get-Date;
  "##### $date #####";
  Get-Job;

  # 完了(Completed)したジョブを全て受け取り、削除
  ForEach($job in (Get-job -State "Completed")) {
    # 完了したジョブの見出しを表示
    $Id = $job.Id;
    $Location = $job.Location;
    "##### Id = $Id / Location = $Location #####";

    # 結果をjsonで表示し、ジョブを削除
    Receive-job -Id $Id | ConvertTo-json;
    Remove-job -Id $Id;
  };
};
```

ここでは、Invoke-Commandに-AsJobを指定して、最初の配列に定義した、V1,V2,V3の３台のマシンに同時にジョブを実行させています。
なお、ForEach(\$a in \$b) {\$a}は、\$b.ForEach({\$_})と書きたいのですが、(Get-job -State "Completed").ForEach()したところで該当のジョブが１つしかないとエラーとなってしまったので、この書き方にしています。
（たぶんPowerShellが、Jenkins側のV4.0と、リモート側のv5.0で違う影響かと思いますが、詳細不明。。。）

### 実行結果

このビルドを実行したところ、次のような結果となりました。

```text
ユーザーanonymousが実行
ビルドします。 ワークスペース: C:\Jenkins\workspace\VM_Loggong
[VM_Loggong] $ powershell.exe -NonInteractive -ExecutionPolicy ByPass "& 'C:\Users\ADMINI~1\AppData\Local\Temp\hudson2286994124897927906.ps1'"
##### 02/07/2016 11:25:13 #####

Id     Name            PSJobTypeName   State         HasMoreData     Location             Command                  
--     ----            -------------   -----         -----------     --------             -------                  
1      Job1            RemoteJob       Running       True            V1                   ...                      
3      Job3            RemoteJob       Running       True            V2                   ...                      
5      Job5            RemoteJob       Running       True            V3                   ...                      
##### 02/07/2016 11:25:14 #####
1      Job1            RemoteJob       Running       True            V1                   ...                      
3      Job3            RemoteJob       Running       True            V2                   ...                      
5      Job5            RemoteJob       Running       True            V3                   ...                      
##### 02/07/2016 11:25:15 #####
1      Job1            RemoteJob       Running       True            V1                   ...                      
3      Job3            RemoteJob       Running       True            V2                   ...                      
5      Job5            RemoteJob       Running       True            V3                   ...                      
##### 02/07/2016 11:25:16 #####
1      Job1            RemoteJob       Running       True            V1                   ...                      
3      Job3            RemoteJob       Running       True            V2                   ...                      
5      Job5            RemoteJob       Running       True            V3                   ...                      
##### 02/07/2016 11:25:17 #####
1      Job1            RemoteJob       Running       True            V1                   ...                      
3      Job3            RemoteJob       Running       True            V2                   ...                      
5      Job5            RemoteJob       Running       True            V3                   ...                      
##### 02/07/2016 11:25:18 #####
1      Job1            RemoteJob       Running       True            V1                   ...                      
3      Job3            RemoteJob       Completed     True            V2                   ...                      
5      Job5            RemoteJob       Completed     True            V3                   ...                      
##### Id = 3 / Location = V2 #####
{
    "Disk_byte":  40930,
    "Network_byte":  41749,
    "Processor_per":  45,
    "PSComputerName":  "V2",
    "RunspaceId":  "ccf5b5e1-e871-4751-8b00-5484f71e960f",
    "PSShowComputerName":  true
}
##### Id = 5 / Location = V3 #####
{
    "Disk_byte":  0,
    "Network_byte":  60,
    "Processor_per":  0,
    "PSComputerName":  "V3",
    "RunspaceId":  "ebd12ad4-f4d2-48e6-aa21-f6b81cd28aaf",
    "PSShowComputerName":  true
}
##### 02/07/2016 11:25:19 #####
1      Job1            RemoteJob       Completed     True            V1                   ...                      
##### Id = 1 / Location = V1 #####
{
    "Disk_byte":  0,
    "Network_byte":  520745,
    "Processor_per":  25,
    "PSComputerName":  "V1",
    "RunspaceId":  "96a08f24-1f44-4cf8-b1e0-ceb1ecceb90b",
    "PSShowComputerName":  true
}


Finished: SUCCESS
```

V1,V2,V3のジョブが同時に投入され、V2とV3は5秒後に、V1が6秒後に終わりました。
-AsJobで同時に実行しないとすれば、6+5+5で16秒かかる処理が、6秒で終わりました。
リモートでコマンドを実行するマシンが増えるにつれ、-AsJobの効果が大きくなります。