多数の Windows Server のシステムバックアップを取得する

# 概要

多数の Windows サーバのバックアップを取得する手間を減らす方法を紹介します。Windows Server には Windows Server バックアップという、バックアップを取得する機能（ツール）が標準で搭載されています。しかし、コマンドラインで操作できることがあまり知られていないため、多数のサーバでバックアップを取得することになった作業担当者[^me]にとっては手間のかかるツールと認識されています。そこでこの記事では、作業負荷を軽減する目的で、コマンドラインだけを使って複数リモートサーバの Windows Server バックアップを取得する方法をまとめました。

[^me]: 主に手順書に束縛された私

> 細かいことが気にならない場合は最後にまとめがあるのでそこまでスキップしてください。

# キーワード

- Windows Server バックアップ
- Enable-PSRemoting
- Start-Transcript
- CredSSP
- AllowFreshCredentialsWhenNTLMOnly

# 注意事項

特に断りが無い場合、コマンドは PowerShell で実行します。また、PowerShell は**管理者権限**で起動します。

# Windows Server バックアップ

[Windows Server バックアップ](https://technet.microsoft.com/ja-jp/library/cc732091(v=ws.11).aspx)は、Windows Server 2008 以上に標準で搭載されているバックアップ機能です。サードパーティのバックアップユーティリティと比較すると機能的に劣ると言われていますが、高度な機能が必要無い場合はよく利用されます。[^wsb]

[^wsb]: お金が無いときに利用します。

Windows Server バックアップは OS インストールの時点では無効化されています。この記事の趣旨では既に有効になっている前提ですが、以下のコマンドを実行することで有効にできます。機能の有効化によるサーバの再起動はありませんでした。

```ps1:PowerShell
Install-WindowsFeature -Name Windows-Server-Backup
```

続いて Windows Server バックアップをコマンドラインから実行します。バックアップを実行する際の要件は以下の通りとしました。

- ベアメタル回復ができること
- ベアメタル回復に含まれないドライブ(Dドライブなど)もバックアップ対象とする場合を考慮すること
- バックアップデータはリモートの共有フォルダに格納すること[^sharedfolder]

[^sharedfolder]: 本来はバックアップ専用ドライブに保存されることを想定されているようですが、お金が無いので適当なサーバの空きスペースに投げ込みます。

まずはバックアップを取得したいサーバでの操作を確認します。以下のような順にコマンドを実行すると、リモートホスト RemoteHost の共有フォルダ sharefolder にバックアップが取得できます。

```ps1:PowerShell
# 空のバックアップポリシーを作成する
$policy = New-WBPolicy

# バックアップターゲット（バックアップデータを格納する共有フォルダ）の指定をポリシーに追加する
$target = New-WBTarget -NetworkPath "\\remotehost\sharefolder\"
Add-WBBackupTarget -Policy $policy -Target $target

# ベアメタル回復ができるようにバックアップする指定をポリシーに追加する
Add-WBBareMetalRecovery -Policy $policy

# システム状態をバックアップする指定をポリシーに追加する
Add-WBSystemState -Policy $policy

# Dドライブをバックアップする指定をポリシーに追加する(Cドライブはベアメタル回復に必要なドライブなので既に追加されている)
New-WBFileSpec -FileSpec "D:" | Add-WBFileSpec -Policy $policy

# ポリシーの内容を確認する
$policy

# バックアップを開始する
Start-WBBackup -Policy $policy -Force
```

# 作業ログの記録

作業を完全にコマンドラインで完結させることで、作業ログを記録する手間が削減できます[^excel]。 PowerShell には作業ログを記録する機能があり、以下のコマンドで作業ログを残すことができます。

[^excel]: Excel にスクリーンショットをペタペタする必要がなくなり、精神的な負荷が軽減されます。

```ps1:PowerShell
# ログファイルの名前
$filename = $Env:ComputerName + (Get-Date -f "_yyyyMMdd") + ".txt"

# ログの記録開始
Start-Transcript "C:\Temp\$filename"

# ログの記録停止
Stop-Transcript
```

ところで、作業ログにはコマンドを実行する毎に時間が残っていると、見返す際に便利です。しかし `Start-Transcript` では作業時間を残すオプションが無いため少し工夫をする必要があります。

PowerShell のプロンプトに出力される文字列は `PS DriveLtter:Path> ` ですが、これは PowerShell にデフォルトで定義されている `prompt` 関数によって出力されています。以下のコマンドで確認します。

```ps1:PowerShell
ls Function:prompt | select -expand definition

# 以下のように出力される

"PS $($executionContext.SessionState.Path.CurrentLocation)$('>' * ($nestedPromptLevel + 1)) ";
# .Link
# https://go.microsoft.com/fwlink/?LinkID=225750
# .ExternalHelp System.Management.Automation.dll-help.xml
```

この `prompt` の定義を以下のように書き換えることにより、プロンプトごとに時間を出力することができます。

```ps1:PowerShell
function prompt { "$(Get-Date -f 'hh:mm:ss') [$($Env:UserName)@$($Env:ComputerName)]> " } 

# 以下のように出力される
10:18:32 [asterisk@PHOENIX1ST]> _
```

この定義は PowerShell を再起動すると元に戻ります。恒久的に変更したい場合は `$profile` に `prompt` を定義しておきます。

# PSRemoting

PowerShell には、リモートからコマンドを受け取る機構が備わっています。この技術を利用すると SSH 接続に近い操作感でリモート作業が可能となり、GUI を利用する場合と比較して作業負荷の軽減が見込めます。[^excel]

Windows Server 2012 からはデフォルトで有効になっていますが、クライアント側でも有効化する必要があるため有効化するコマンドを以下に示します。**サーバ**と**クライアント**の双方で PSRemoting を有効化してください。

```ps1:PowerShell
Enable-PSRemoting
```

> 一部は後述しますが、PSRemoting 接続には幾つかの前提条件があります。`Enable-PSRemoting` したのに上手く接続できない場合は、ウェブに詳しく解説されている記事がたくさんあるのでそちらを参照してみてください。

PSRemoting を有効にしたサーバにコマンドを送信するには、以下のようにコマンドを実行します。

```ps1:PowerShell
# 認証情報を作成する(サーバにログインするためのログインIDとパスワードを入力)
$cred = Get-Credential

# 認証情報を使ってリモートホストへのセッションを開く
$session = New-PSSession -ComputerName $remotehost -Credential $cred

# セッションにコマンドを送る（リモートホストのホスト名が出力される）
Invoke-Command $session { $Env:ComputerName }
```

セッションを閉じるには `Invoke-Command` で `exit` を送信するか、`Get-PSSession | Remove-PSSession` を実行します。

## 信頼するホストと権限の移譲

今回の作業のシナリオでは、**作業端末（terminal）**から**複数のサーバ(WSB * n)**へ **PSRemoting で接続(A)**し、Windows Server バックアップを実行し、**共有フォルダ接続（B）**によりバックアップデータを**ストレージサーバ(Strage)**に格納します(Fig.1)。

```:Fig.1
 __________          __________          __________
|          |   A    |          |   B    |          |
| terminal | -----> | WSB * n  | -----> |  Strage  |
|__________|        |__________|        |__________|

```

### 信頼するホスト

PSRemoting(A) のためには、端末(terminal)とサーバ（WSB）が同じドメインに属しているか、端末(terminal)の「信頼するホスト」にサーバ(WSB)が登録されている必要があります。

端末(terminal)の「信頼するホスト」の登録状況を確認するには、以下のコマンドを実行します。

```ps1:PowerShell
Get-Item wsman:\localhost\client\trustedhosts

# 以下のように表示される

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

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

Value の値がワイルドカード `*` になっているので、全てのホストを信頼するよう設定されていることが判ります。もしドメインにも参加しておらず、「信頼するホスト」に何も登録されていない場合は、以下のコマンドで信頼するホストを登録します。[^clear]

[^clear]: 削除するには `Clear-Item` を使います。

```ps1:PowerShell
Set-Item wsman:\localhost\client\trustedhosts -value *
```

### 権限の移譲

続いて、共有フォルダ接続（B）について見ていきます。

今回の場合、PSRemoting 越しにストレージサーバ（Strage）に接続しており、「権限の移譲」が発生しています。つまり端末（Terminal）の代理として、サーバ（WSB）がストレージサーバ（Strage）と認証しようとしています。このような動き(Second Hop, Next Hop)はセキュリティ的に都合が悪いので、Windows(PSRemoting) のデフォルトでは制限されています。

権限の移譲を行うには SSP 認証プロトコルで PSRemoting する必要がありますが、サーバ側・クライアント側の双方では SSP 認証プロトコルは無効化されています。[^a] では、どうするかというと、一旦は普通に PSRemoting で接続して SSP 認証を有効化して切断、改めて SSP 認証プロトコルを使用して PSRemoting 接続を行います。

[^a]: 今回ハマったところです

具体的には以下の通りコマンドを実行します。

```ps1:PowerShell
# 端末（Terminal）の SSP 認証の状態を確認する
Get-WSManCredSSP

# 以下のように出力された場合は、SSP 認証プロトコルが無効
# 
# このコンピューターは、新しい資格情報の委任を許可するようには構成されていません。
# このコンピューターは、リモート クライアント コンピューターから資格情報を受け取るように構成されていません。

# 端末（Terminal）の SSP 認証を有効化する
Enable-WSManCredSSP –Role client –DelegateComputer * -Force

# Get-WSManCredSSP の出力が以下のようになればクライアントとしての SSP 認証プロトコルが有効
#
# このコンピューターは、次のターゲットに対する新しい資格情報の委任を許可するよう構成されています: wsman/*
# このコンピューターは、リモート クライアント コンピューターから資格情報を受け取るように構成されていません。

# サーバ（WSB）に PSRemoting する
$session = New-PSSession -ComputerName $remotehost -Credential (Get-Credential)

# サーバ（WSB）の SSP 認証の状態を確認する
Invoke-Command $session { Get-WSManCredSSP }

# サーバ（WSB）で SSP 認証プロトコルを有効化する
Invoke-Command $session { Enable-WSManCredSSP –Role Server -Force }

# サーバ（WSB）で SSP 認証プロトコルを有効化する
Invoke-Command $session { Get-WSManCredSSP }

# Get-WSManCredSSP の出力が以下のようになればサーバとしての SSP 認証プロトコルが有効
# 
# このコンピューターは、新しい資格情報の委任を許可するようには構成されていません。
# このコンピューターは、リモート クライアント コンピューターから資格情報を受け取るように構成されています。

# サーバ（WSB）から抜ける
Invoke-Command $session { exit }

# 改めてサーバ（WSB）に SSP 認証プロトコルで PSRemoting する
$session = New-PSSession -ComputerName $remotehost -Credential (Get-Credential) -Authentication CredSSP

# ストレージサーバ（strage）の共有フォルダが参照できるようになる
Invoke-Command $session { ls "\\strage\sharedfolder" }
```

`New-PSSession -Authentication CredSSP` する際にエラーが発生する場合は、エラーの内容に従って GPO を弄る必要があります。グループポリシーエディタでは [コンピュータの構成]-[管理用テンプレート]-[システム]-[資格情報の委任]-[NTLMのみのサーバ認証で新しい資格情報の委任を許可する] に「WSMAN/*」を登録のうえ有効にします。

ただし、グループポリシーエディタでは GUI 操作が手間なので、以下のようにレジストリを直接操作します。

```ps1:PowerShell
# レジストリキー
$KEY = "HKLM:\SOFTWARE\Policies\Microsoft\Windows\CredentialsDelegation\AllowFreshCredentialsWhenNTLMOnly"

# 現在の状態を確認
Get-Item $KEY

# レジストリキーが無ければ作成
New-Item $KEY

# レジストリキーに値をセット
New-ItemProperty $KEY -Name 1 -Value WSMAN/* -Type String

# PSRemoting が終わったら削除する
Remove-Item $KEY
```

# まとめ

上記、長々と書いてきたことをまとめると以下のようになります。環境によって要否判断が必要なので、スクリプトブロックや関数を使わずに１ステップ毎にコマンドになるようにしています。

```ps1:PowerShell
################################################################################
# PSRemotingの事前設定
################################################################################

# PSRemotingの設定（Value 列が * であること）
ls wsman:\localhost\client\trustedhosts
Enable-WSManCredSSP –Role client –DelegateComputer * -Force
ls wsman:\localhost\client\trustedhosts

# NTLM 方式のみの場合もセカンドホップを許可する(WSMAN/*があること)
$KEY = "HKLM:\SOFTWARE\Policies\Microsoft\Windows\CredentialsDelegation\AllowFreshCredentialsWhenNTLMOnly"
Get-ItemProperty $KEY
New-Item $KEY
New-ItemProperty $KEY -Name 1 -Value WSMAN/* -Type String

################################################################################
# サーバ側への接続
################################################################################

# プロンプトの加工
function prompt { "$(Get-Date -f 'hh:mm:ss') [$($Env:UserName)@$($Env:ComputerName)]> " } 

# 接続先
$wsb = "hoge.test.com"

# ログの記録開始
Start-Transcript "C:\Users\asterisk\desktop\${wsb}.txt"

# 手動で認証情報を作成
$cred = Get-Credential

# セッションを開く
$session = New-PSSession -ComputerName $wsb -Credential $cred

# wsman の設定を確認する（Value 列が True であること）
Invoke-Command $session { Get-WSManCredSSP }
Invoke-Command $session { Enable-WSManCredSSP –Role server -Force | out-null }
Invoke-Command $session { Get-WSManCredSSP }
Invoke-Command $session { exit }

################################################################################
# サーバ側への接続（SSP認証）
################################################################################

# SSP でセッションを開き直す
$session = New-PSSession -ComputerName $wsb -Credential $cred -Authentication CredSSP

# 空のバックアップポリシーを作る
Invoke-Command $session { $policy = New-WBPolicy }

# バックアップターゲット（バックアップデータの取得先）の指定をポリシーに追加する
Invoke-Command $session { $Target = New-WBBackupTarget -NetworkPath "\\strage.test.com\D$\backup" }
Invoke-Command $session { Add-WBBackupTarget -Policy $policy -Target $Target -Force }

# ベアメタル回復の指定をポリシーに追加する(C:)
Invoke-Command $session { Add-WBBareMetalRecovery -Policy $policy }

# システム状態をバックアップ対象に含める指定をポリシーに追加する
Invoke-Command $session { Add-WBSystemState -Policy $policy }

# D ドライブをバックアップ対象に含める指定をポリシーに追加する
Invoke-Command $session { New-WBFileSpec -FileSpec "D:" | Add-WBFileSpec -Policy $policy }

# ポリシーの内容を確認する
Invoke-Command $session { $policy }

# バックアップを開始する
Invoke-Command $session { Start-WBBackup -Policy $policy -Force }

# セカンドホップの許可を無効化する
Invoke-Command $session { Disable-WSManCredSSP –Role server -Force | out-null }

# セッションを閉じる
Invoke-Command $session { exit }

# ログの記録を停止する
Stop-Transcript

################################################################################
# 設定の戻し
################################################################################

# NTLMのサーバへ権限の移譲を許可する設定を削除する
Remove-Item $KEY

# サーバへの信頼を削除する
Enable-WSManCredSSP –Role client –DelegateComputer * -Force
```

# その他

PSRemoting で共有フォルダ接続しようとしたらハマったことを記録しておこうと思っただけなのに、想定よりも遥かにダラダラ書くことになってしまったので反省して寝ます。

# 参考

- [Windows PowerShell: 次ホップを実行する](https://technet.microsoft.com/ja-jp/library/jj853299.aspx)
- [Enable-PSRemoting](https://docs.microsoft.com/en-us/powershell/module/microsoft.powershell.core/enable-psremoting?view=powershell-5.1)
- もっとあるはずなのにおかしいな