Windows Remote Management

PowerShell で Windows Remote Management を使ってみたのでメモ書きを。

表題にある Windows Remote Management(WinRM) というのは、Windows を遠隔で操作する仕組みのことです。

遠隔で操作するなら以前からリモートデスクトップがあるんですが、WinRM はコマンドで操作ができます。Linux で言うところの ssh な感じになります。これができると、多数のサーバで同じ操作を繰り返すような作業をバッチ処理として自動化することができます。

# WinRM を使う準備

WinRM は Windows Server 2012 以降ならデフォルトで有効（デフォルトではポート 5985 で待ち受けています）になっているのでサーバ側での設定は不要です。ただ、ファイアウォールを有効にしてやる必要はあるかもしれないので、確認方法を。この辺は無理に PowerShell でやる必要は無いです。普通に「強化されたWindowsファイアウォール」で見たほうが良いです。

> WINRM-HTTP-In-TCP-Public というルールもありましたが localsubnet のみ許可されているようなので見なかったことにします。コマンドで見ると気づけない。

```console:AWSのWindowsServer2012の場合
PS > # ファイアウォールのルールの確認
PS > Get-NetFirewallRule -Name WINRM-HTTP-In-TCP

Name                  : WINRM-HTTP-In-TCP
DisplayName           : Windows Remote Management (HTTP-In)
Description           : Inbound rule for Windows Remote Management via WS-Management. [TCP 5985]
DisplayGroup          : Windows Remote Management
Group                 : @FirewallAPI.dll,-30267
Enabled               : True
Profile               : Domain, Private
Platform              : {}
Direction             : Inbound
Action                : Allow
EdgeTraversalPolicy   : Block
LooseSourceMapping    : False
LocalOnlyMapping      : False
Owner                 :
PrimaryStatus         : OK
Status                : The rule was parsed successfully from the store. (65536)
EnforcementStatus     : NotApplicable
PolicyStoreSource     : PersistentStore
PolicyStoreSourceType : Local

PS > # 必要に応じて Profile を Any に変更
PS > Get-NetFirewallRule -Name WINRM-HTTP-In-TCP | Set-NetFirewallRule -Profile Any -PassThru

Name                  : WINRM-HTTP-In-TCP
DisplayName           : Windows Remote Management (HTTP-In)
Description           : Inbound rule for Windows Remote Management via WS-Management. [TCP 5985]
DisplayGroup          : Windows Remote Management
Group                 : @FirewallAPI.dll,-30267
Enabled               : True
Profile               : Any
Platform              : {}
Direction             : Inbound
Action                : Allow
EdgeTraversalPolicy   : Block
LooseSourceMapping    : False
LocalOnlyMapping      : False
Owner                 :
PrimaryStatus         : OK
Status                : The rule was parsed successfully from the store. (65536)
EnforcementStatus     : NotApplicable
PolicyStoreSource     : PersistentStore
PolicyStoreSourceType : Local

```

一方、非ドメイン環境の場合、クライアント側では以下の設定が必要です。もし WinRM が無効になっていたら失敗しますので、管理者権限で PowerShell を起動し `Enable-PSRemoting` で有効にしておきます。

```console
PS > # 信頼済みホストの一覧を取得する
PS > Get-Item WSMan:\localhost\client\trustedhosts

   WSManConfig: Microsoft.WSMan.Management\WSMan::localhost\Client

Type            Name                           SourceOfValue   Value
----            ----                           -------------   -----
System.String   TrustedHosts

PS > # 全てのホストを信頼済みにする（少々乱暴）。
PS > # もし個別に設定したければカンマで区切ったホスト名（文字列）を指定します。
PS > Set-Item WSMan:\localhost\client\trustedhosts -Value '*' -PassThru

   WSManConfig: Microsoft.WSMan.Management\WSMan::localhost\Client

Type            Name                           SourceOfValue   Value
----            ----                           -------------   -----
System.String   TrustedHosts                                   *

```

> 補足です。WSMan とは Web Service for Management という標準化された仕様です。名前の通り HTTP をシステム管理に使おうというものです。WinRM は WSMan の Microsoft 実装とのこと。


# WinRM で接続する

とりあえず以下のような感じで接続できます。

```console
PS > $cred = Get-Credential -UserName Administrator -Message "Test1のパスワード"
PS > $host = "Test1.hogehoge.com"
PS > Enter-PSSession -ComputerName $host -Credential $cred
[Test1.hogehoge.com]: PS C:\Users\Administrator\Documents> $Env:ComputerName
Test1
```

ただ、普通にログインしてしまっては意味が無いので、以下のようにバックグラウンドジョブとしてコマンドを実行してやります。

```console
PS > $session = New-PSSession -ComputerName $host -Credential $cred
PS > Invoke-Command -Session $session -ScriptBlock { $Env:ComputerName } -AsJob

Id     Name            PSJobTypeName   State         HasMoreData     Location             Command
--     ----            -------------   -----         -----------     --------             -------
5      Job5            RemoteJob       Running       True            Test1.hogehoge.com  $Env:Computername

PS > Get-Job -State Complete | Receive-Job
Test1
```

# 複数ホストで並列処理するとき

認証情報とホスト名を CSV とかに列挙しておくなら以下のような感じに。

```ps1
PS > Import-CSV .\host_username_passwd.csv | % {
    # Get-Credential では必ずプロンプトが表示されてしまうので New-Object で認証情報を生成しています。
    $SecurePassword = $_.Password | ConvertTo-SecureString -AsPlainText -Force
    $cred = New-Object System.Management.Automation.PSCredential -ArgumentList $_.UserName, $SecurePassword

    # 後は好きなだけジョブを実行
    $session = New-PSSession -ComputerName $_.Host -Credential $cred
    Invoke-Command -Session $session -ScriptBlock { ... } -AsJob
} | wait-job # State Completed した順番に表示される

PS > # Get-Job | % { $_ | Receive-Job -Keep | Out-File -FilePath $_.location } などで結果を受とる
PS > # Get-PSSession | Remove-PSSession でセッションをまとめて閉じる
```

これ認証に失敗したらどうなるんですかね？