投稿日2026年10月25日
更新日2026年10月25日

はじめに:複数台NASの「起動・バックアップ・電源断」の手間をゼロにする

大事なデータを守るため、メインのNASからバックアップ用のNASへ定期的にデータをミラーリングしている方は多いと思います。しかし、バックアップ用のNASを24時間つけっぱなしにするのは電気代の無駄ですし、HDDの寿命やランサムウェア感染リスクの観点からも「必要な時だけ電源を入れて、終わったら切る(コールドバックアップに近い運用)」のが理想です。

とはいえ、毎回手動でWake-on-LAN(WOL)ツールから起動させ、5分ほど待ってからRobocopyのバッチを叩き、コピーが終わるのを待ってブラウザから管理画面にログインしてシャットダウンする……というのはあまりにも面倒です。

3行まとめ

  • Buffalo製NAS「TeraStation(TS5000シリーズ等)」の起動・同期・シャットダウンを完全自動化するスクリプトです。
  • 設定ファイル(config.ini)を書き換えるだけで、プログラム本体を触らずに誰でも自分の環境に合わせて使えます。
  • DHCP環境でのホスト名フォールバックや、グループ別の並列/直列Robocopy制御、タスクスケジューラ自動実行にも完全対応しています。

本記事では、Windows標準のPowerShellとバッチファイルだけを使い、外部ソフトを一切インストールすることなくTeraStationの電源管理とバックアップを全自動化する仕組みを、コピペでそのまま使える完成版スクリプトとして公開・解説します。

【技術解説】TeraStationをコマンドから起動・同期・終了する内部の仕組み

スクリプトの具体的な導入手順に入る前に、「どうやって外部ツールを使わずにPowerShellだけでTeraStationを起動・同期・シャットダウンしているのか」という裏側の技術仕様を詳しく解説します。
(※「細かい理屈いいから早く動かしたい!」という方は、この章を読み飛ばして「ファイル構成と準備」へ進んでいただいて構いません)

1. 起動の仕組み:.NET FrameworkのUdpClientによるマジックパケット生成

通常、Wake-on-LAN(WOL)を行うにはフリーソフトなどを使いますが、実はPowerShellから.NETの System.Net.Sockets.UdpClient クラスを呼び出すことで、OS標準機能だけでマジックパケットを組み立てて送信できます。

WOLのマジックパケットは、データリンク層(L2)でNICが待ち受ける非常にシンプルなペイロード構造をしています。

  • 先頭6バイト(同期ストリーム): 0xFF を6回連続で並べる(FF:FF:FF:FF:FF:FF)。
  • 後続96バイト(ターゲットMAC): 起動したい機器のMACアドレス(6バイト)を16回連続で繰り返す。

合計102バイトのバイト配列を生成し、それをUDPのブロードキャストアドレス(255.255.255.255)のポート9番(Discardポート)へ向けて EnableBroadcast = $true で投げるだけです。スクリプト内では、文字列で書かれたMACアドレスからコロンやハイフンを除去し、16進数バイト配列へ変換した上で、配列の連結演算によってわずか数行でこの102バイトのパケットを構築・送出しています。

2. 終了の仕組み:Web管理画面の裏で動く「JSON-RPC 2.0 API」の解析と活用

今回の自動化の最大の難関であり、かつ最も面白いポイントが「TeraStationの遠隔シャットダウン」です。

TeraStation(TS5000シリーズ等)は内部でLinuxベースのOSが動いていますが、一般的なユーザー向けにSSHが開放されていないため、ssh root@ip "shutdown -h now" のような芸当ができません。公式の方法は「ブラウザで管理画面を開いて手動でシャットダウンボタンを押す」か「PC連動機能(NAS Navigator2)を使う」かの二択になってしまいます。

しかし、ブラウザの管理画面(Web UI)がどうやって本体に指示を出しているのか通信を解析してみると、裏側では http://<NASのIP>/nasapi という単一のエンドポイントに対して、JSON-RPC 2.0 プロトコルでHTTP POSTリクエストを投げていることがわかりました。

具体的には、以下の2ステップのステートフルな通信を行うことで、外部スクリプトから正規のシャットダウン処理を発火させることができます。

【ステップ①:認証とセッションID(sid)の取得】
まず、/nasapi に対して Auth.login メソッドをPOSTします。

// Request: POST http://192.168.x.x/nasapi
{
  "jsonrpc": "2.0",
  "method": "Auth.login",
  "params": {
    "username": "admin",
    "password": "password"
  },
  "id": "9999"
}

認証に成功すると、TeraStation側から以下のようなJSONレスポンスが返ってきます。

// Response
{
  "jsonrpc": "2.0",
  "result": {
    "sid": "1a2b3c4d5e6f..."
  },
  "id": "9999"
}

この result.sid に格納されている文字列が、以降の管理操作を許可する一時的なセッショントークン(合鍵)になります。

【ステップ②:取得したsidを用いたシャットダウン命令の発行】
続いて、取り出した sid をパラメータにセットし、同じく /nasapi に対して今度は System.shutdown メソッドをPOSTします。

// Request: POST http://192.168.x.x/nasapi
{
  "jsonrpc": "2.0",
  "method": "System.shutdown",
  "params": {
    "sid": "1a2b3c4d5e6f..."
  },
  "id": "9999"
}

これが受理されると、{"jsonrpc": "2.0", "id": "9999", "result": null} というレスポンスが返り[cite: 2]、直後にTeraStation本体が「ピッ」とビープ音を鳴らして正規のシャットダウンシーケンス(キャッシュのフラッシュ、RAIDアレイの安全なアンマウント、電源断)へと移行します。
PowerShellでは Invoke-RestMethod コマンドレットを使うことで、JSONのシリアライズ・デシリアライズを含めて非常にスマートにこの一連のRPC通信を実装できます。

3. 同期(Robocopy)の仕組み:NAS間コピーに最適化したフラグ設計とプロセス制御

ファイル同期にはWindows標準にして最強のディレクトリ同期ツールである robocopy を使用していますが、LinuxベースのNAS(Samba/SMB)間で大容量データをやり取りするための専用チューニングを施しています。

デフォルト設定(/mir /copy:dt /r:1 /w:1 /mt:6 /fft)の各オプションには明確な意図があります。

  • /mir(ミラーリング):
    /e(空を含む全サブディレクトリ)と /purge(コピー元にないファイル・フォルダをコピー先から削除)を合わせたオプションです。世代管理ではなく、メイン機とバックアップ機のディレクトリツリーを完全一致させます。
  • /copy:dt(データとタイムスタンプのみコピー):
    標準の /copy:dat だとNTFS属性(Attributes)までコピーしようとしますが、LinuxベースのTeraStation(ext4やXFS上のSamba)に対して属性変更を行うと不要なメタデータ通信や権限エラーの原因になるため、DataとTimestampsのみに絞っています。
  • /fft(FATファイル時間 / 2秒精度の許容)【★超重要】:
    Windows(NTFS)のタイムスタンプ精度は100ナノ秒単位ですが、Linux系NASのSamba実装やファイルシステムによってはタイムスタンプの丸め誤差が生じます。/fft を付けないと、「前回コピーしたはずのファイルが、数ミリ秒のズレで『更新された』と誤判定されて毎回全ファイル上書きコピーされる」という地獄を見ます。2秒以内の差を同一とみなす /fft はNAS運用では必須のフラグです。
  • /mt:6(6スレッド並列転送):
    小さなファイルが大量にある場合、シングルスレッドではSMBのプロトコルオーバーヘッドで速度が出ません。一方で標準の8スレッドや16スレッド以上に増やしすぎると、今度はNAS側のCPUやHDDのランダムアクセス(シーク)が飽和して逆に遅くなります。検証の結果、TeraStationのRAID構成において最もバランスが良い「6スレッド」をデフォルトとしています。
  • /r:1 /w:1(リトライ1回・待機1秒):
    Robocopyの初期値は「失敗時に100万回リトライ、待機30秒」という恐ろしい設定になっており、ロックされたファイルが1つあるだけでバッチが永遠に終わらず、いつまでもNASがシャットダウンされません。これを防ぐため、リトライは1回(1秒待ち)で即座に見切る設定にしています。

さらにスクリプトの実装面では、設定ファイル(config.ini)から読み取ったジョブをグループ単位で束ね、裏で一時的なバッチファイル(chcp 65001 によるUTF-8コードページ指定付き)を %TEMP% に動的生成して Start-Process -PassThru で別ウィンドウ起動しています。
これにより、各プロセスのハンドル(PID)をPowerShell側で正確に握ることができ、すべての cmd.exe プロセスが終了(WaitForExit())した瞬間を検知して、一分の隙もなくシャットダウンフェーズへバトンを渡すことができます。

このツールの仕組みと4つのこだわりポイント

上記の技術仕様を踏まえ、今回作成した「TeraStation Manager」では実運用でトラブルが起きないよう以下の4つの設計を取り入れています。

  1. ブラウザ不要!JSON-RPC APIによる安全なシャットダウン
    先述の通り、内部API(/nasapi)を直接叩くことで、管理画面を開くことなくスクリプトから安全にアンマウント&電源オフを実行します。
  2. DHCP環境でも安心の「IP + ホスト名」二段構え通信
    基本的には指定したIPアドレスへシャットダウン信号を送りますが、もしDHCP環境でIPアドレスが変わってしまっていた場合でも、自動的に「ホスト名(NAS名)」で名前解決を行ってリトライするフェイルセーフ設計になっています。
  3. 負荷をコントロールできる「並列 × 直列」ハイブリッド同期
    別のNAS同士のペア(例:NAS-A→NAS-B と NAS-C→NAS-D)は別ウィンドウで同時に(並列)コピーして時間を短縮しつつ、1つの集約用NASへ複数台から書き込むようなケースではHDDのヘッド移動(シーク負荷)で速度が落ちないよう1つのウィンドウ内で順番に(直列)実行できるようグループ制御を実装しました。
  4. 文字化け・構文エラーと無縁のファイル分離構造
    Windows標準のPowerShell(5.1)は日本語を含むスクリプトの文字コード判定が非常にシビアです。そこでプログラム本体(.ps1)は100%純粋なASCII(英語)のみで記述し、日本語のフォルダパスやNAS設定はすべて外部の config.ini からUTF-8で読み込む構造にしました。

ファイル構成と準備

適当なフォルダ(例:C:\TeraStation など)を作成し、その中に以下のファイルを作成して配置します。

  • TeraStation-Manager.ps1 :プログラム本体(書き換え不要)
  • config.ini :あなたの環境に合わせて編集する設定ファイル
  • 手動実行用バッチ(3種) :画面を見ながら手動実行したい時に使います(終了時にEnterキー待ち)
  • タスクスケジューラ用バッチ(3種) :夜間バッチ等で自動実行したい時に使います(終了時に自動で閉じる)

ステップ1:プログラム本体(TeraStation-Manager.ps1)の作成

まずはコアとなるPowerShellスクリプトを作成します。メモ帳などを開き、以下のコードを丸ごとコピー&ペーストして TeraStation-Manager.ps1 という名前で保存してください。
(※中身はすべて英数字のみなので、文字コードは何で保存しても絶対にエラーになりません)

# ==============================================================================
# TeraStation Manager Core Script (TeraStation-Manager.ps1)
# Mode: Wake | Shutdown | FullSequence
# ==============================================================================
param (
    [Parameter(Mandatory = $true)]
    [ValidateSet("Wake", "Shutdown", "FullSequence")]
    [string]$Mode,
    [string]$ConfigFile = "config.ini",
    [switch]$NoPause
)
$ErrorActionPreference = "Stop"
# ------------------------------------------------------------------------------
# 1. Parse INI File
# ------------------------------------------------------------------------------
$iniPath = Join-Path $PSScriptRoot $ConfigFile
if (-not (Test-Path $iniPath)) {
    Write-Host "[ERROR] Config file not found: $iniPath" -ForegroundColor Red
    if (-not $NoPause) { Read-Host "Press Enter to exit..." }
    exit 1
}
$Settings = @{
    Username        = "admin"
    Password        = "password"
    BootWaitSeconds = 300
    RobocopyOptions = "/mir /copy:dt /r:1 /w:1 /mt:6 /fft"
}
$NasList  = @()
$SyncJobs = @()
$section = ""
foreach ($line in [System.IO.File]::ReadLines($iniPath, [System.Text.Encoding]::UTF8)) {
    $trim = $line.Trim()
    if ([string]::IsNullOrWhiteSpace($trim) -or $trim.StartsWith("#") -or $trim.StartsWith(";")) {
        continue
    }
    if ($trim -match "^\[(.+)\]$") {
        $section = $matches[1].ToUpper()
        continue
    }
    $parts = $trim -split "=", 2
    if ($parts.Count -ne 2) { continue }
    $key = $parts[0].Trim()
    $val = $parts[1].Trim()
    switch ($section) {
        "SETTINGS" {
            $Settings[$key] = $val
        }
        "NAS" {
            $nasParts = $val -split ","
            if ($nasParts.Count -ge 2) {
                $nasUser = if ($nasParts.Count -ge 3 -and -not [string]::IsNullOrWhiteSpace($nasParts[2])) { $nasParts[2].Trim() } else { $null }
                $nasPass = if ($nasParts.Count -ge 4 -and -not [string]::IsNullOrWhiteSpace($nasParts[3])) { $nasParts[3].Trim() } else { $null }
                $NasList += [PSCustomObject]@{
                    Name = $key
                    IP   = $nasParts[0].Trim()
                    MAC  = $nasParts[1].Trim()
                    User = $nasUser
                    Pass = $nasPass
                }
            }
        }
        "SYNCJOBS" {
            $jobParts = $val -split ","
            if ($jobParts.Count -ge 4) {
                $SyncJobs += [PSCustomObject]@{
                    Group   = $key
                    SrcHost = $jobParts[0].Trim()
                    SrcPath = $jobParts[1].Trim()
                    DstHost = $jobParts[2].Trim()
                    DstPath = $jobParts[3].Trim()
                }
            }
        }
    }
}
# ------------------------------------------------------------------------------
# 2. Core Functions
# ------------------------------------------------------------------------------
function Invoke-WakeAll {
    Write-Host "`n>>> Sending Wake-on-LAN Magic Packets..." -ForegroundColor Cyan
    if ($NasList.Count -eq 0) {
        Write-Host "  [WARN] No active NAS entries in [NAS] section." -ForegroundColor Yellow
        return
    }
    foreach ($nas in $NasList) {
        try {
            $cleanMac = $nas.MAC -replace '[:-]', ''
            if ($cleanMac.Length -ne 12) { throw "Invalid MAC format" }
            $bytes = 0..5 | ForEach-Object { [Convert]::ToByte($cleanMac.Substring($_ * 2, 2), 16) }
            $packet = [byte[]](@(0xFF) * 6 + ($bytes * 16))
            $client = [System.Net.Sockets.UdpClient]::new()
            $client.EnableBroadcast = $true
            $client.Connect([System.Net.IPAddress]::Broadcast, 9)
            $client.Send($packet, $packet.Length) | Out-Null
            $client.Dispose()
            Write-Host ("  [OK] WOL Sent : {0,-12} ({1})" -f $nas.Name, $nas.MAC) -ForegroundColor Green
        }
        catch {
            Write-Host ("  [NG] WOL Error: {0,-12} ({1}) - {2}" -f $nas.Name, $nas.MAC, $_.Exception.Message) -ForegroundColor Red
        }
    }
}
function Invoke-WaitBoot {
    $totalSec = [int]$Settings.BootWaitSeconds
    Write-Host "`n>>> Waiting $totalSec seconds for NAS startup..." -ForegroundColor Cyan
    for ($i = $totalSec; $i -gt 0; $i--) {
        $m = [int][math]::Floor($i / 60)
        $s = [int]($i % 60)
        $pct = [int]((($totalSec - $i) / $totalSec) * 100)
        $statusStr = "Remaining: {0}:{1}" -f $m.ToString("00"), $s.ToString("00")
        Write-Progress -Activity "Waiting for TeraStation Boot" -Status $statusStr -PercentComplete $pct
        Start-Sleep -Seconds 1
    }
    Write-Progress -Activity "Waiting for TeraStation Boot" -Completed
    Write-Host "  [OK] Wait timer finished." -ForegroundColor Green
}
function Invoke-RobocopyJobs {
    Write-Host "`n>>> Starting Robocopy Sync Jobs..." -ForegroundColor Cyan
    if ($SyncJobs.Count -eq 0) {
        Write-Host "  [WARN] No active jobs in [SyncJobs] section." -ForegroundColor Yellow
        return
    }
    $grouped = $SyncJobs | Group-Object -Property Group
    $opts = $Settings.RobocopyOptions
    $procs = @()
    $tempBats = @()
    $utf8NoBom = New-Object System.Text.UTF8Encoding($false)
    foreach ($g in $grouped) {
        $groupName = $g.Name
        $batFile = Join-Path $env:TEMP ("ts_sync_" + $groupName + "_" + [guid]::NewGuid().ToString("N") + ".bat")
        $tempBats += $batFile
        $lines = @(
            "@echo off",
            "chcp 65001 >nul",
            "title Sync: $groupName"
        )
        foreach ($job in $g.Group) {
            $sh = $job.SrcHost
            $sp = $job.SrcPath
            $dh = $job.DstHost
            $dp = $job.DstPath
            $lines += "echo =========================================="
            $lines += "echo [$groupName] $sp -^> $dp"
            $lines += "echo =========================================="
            $lines += "ping -n 1 -w 1000 $sh >nul && ping -n 1 -w 1000 $dh >nul && ("
            $lines += "    robocopy `"$sp`" `"$dp`" $opts"
            $lines += ") || ("
            $lines += "    echo [SKIP] $sh or $dh is offline."
            $lines += ")"
            $lines += "echo."
        }
        $lines += "timeout /t 5"
        [System.IO.File]::WriteAllLines($batFile, $lines, $utf8NoBom)
        Write-Host "  -> Launching Window: Group [$groupName] ($($g.Count) job(s))" -ForegroundColor White
        $p = Start-Process -FilePath "cmd.exe" -ArgumentList "/c `"$batFile`"" -PassThru
        $procs += $p
    }
    Write-Host "  Waiting for all sync windows to finish..." -ForegroundColor Yellow
    foreach ($p in $procs) {
        if (-not $p.HasExited) {
            $p.WaitForExit()
        }
    }
    foreach ($tb in $tempBats) {
        Remove-Item $tb -Force -ErrorAction SilentlyContinue
    }
    Write-Host "  [OK] All Robocopy jobs finished." -ForegroundColor Green
}
function Invoke-ShutdownAll {
    Write-Host "`n>>> Sending Shutdown Commands via JSON-RPC..." -ForegroundColor Cyan
    if ($NasList.Count -eq 0) {
        Write-Host "  [WARN] No active NAS entries in [NAS] section." -ForegroundColor Yellow
        return
    }
    $headers = @{ "Content-Type" = "application/json" }
    foreach ($nas in $NasList) {
        $user = if ($nas.User) { $nas.User } else { $Settings.Username }
        $pass = if ($nas.Pass) { $nas.Pass } else { $Settings.Password }
        # Try configured IP first; fallback to NAS Name (hostname) if DHCP changed the IP
        $targets = @($nas.IP)
        if ($nas.Name -ne $nas.IP) { $targets += $nas.Name }
        $success = $false
        $lastErr = ""
        foreach ($t in $targets) {
            $url = "http://$t/nasapi"
            try {
                $loginBody = @{
                    jsonrpc = "2.0"
                    method  = "Auth.login"
                    params  = @{ username = $user; password = $pass }
                    id      = "9999"
                } | ConvertTo-Json
                $loginRes = Invoke-RestMethod -Uri $url -Method Post -Body $loginBody -Headers $headers -TimeoutSec 5
                if (-not $loginRes.result -or -not $loginRes.result.sid) {
                    throw "Login failed (User: $user)"
                }
                $sid = $loginRes.result.sid
                $shutBody = @{
                    jsonrpc = "2.0"
                    method  = "System.shutdown"
                    params  = @{ sid = $sid }
                    id      = "9999"
                } | ConvertTo-Json
                $shutRes = Invoke-RestMethod -Uri $url -Method Post -Body $shutBody -Headers $headers -TimeoutSec 5
                if ($shutRes.error) {
                    throw $shutRes.error.message
                }
                Write-Host ("  [OK] Shutdown Sent : {0,-12} (via {1})" -f $nas.Name, $t) -ForegroundColor Green
                $success = $true
                break
            }
            catch {
                $lastErr = $_.Exception.Message
            }
        }
        if (-not $success) {
            Write-Host ("  [NG] Shutdown Error: {0,-12} ({1}) - {2}" -f $nas.Name, $nas.IP, $lastErr) -ForegroundColor Red
        }
    }
}
# ------------------------------------------------------------------------------
# 3. Main Execution Router
# ------------------------------------------------------------------------------
Write-Host "======================================================" -ForegroundColor Cyan
Write-Host " TeraStation Manager - Mode: $Mode" -ForegroundColor Cyan
Write-Host "======================================================" -ForegroundColor Cyan
switch ($Mode) {
    "Wake" {
        Invoke-WakeAll
    }
    "Shutdown" {
        Invoke-ShutdownAll
    }
    "FullSequence" {
        Invoke-WakeAll
        Invoke-WaitBoot
        Invoke-RobocopyJobs
        Invoke-ShutdownAll
    }
}
Write-Host "`nAll operations completed." -ForegroundColor Cyan
if (-not $NoPause) {
    Write-Host "Press Enter to close..." -ForegroundColor Cyan
    Read-Host
}

ステップ2:設定ファイル(config.ini)の作成

続いて、ご自身のTeraStationの情報を記載する config.ini を同じフォルダ内に作成します。

# ==============================================================================
# TeraStation Manager 設定ファイル (config.ini)
# 先頭に「#」または「;」がある行はコメント(無効)として扱われます。
# ==============================================================================
[Settings]
# TeraStation 共通の管理者ログイン情報(シャットダウン時に使用します)
Username = admin
Password = password
# 一括シーケンス実行時の、WOL送信から同期開始までの待機時間(秒) ※5分 = 300
BootWaitSeconds = 300
# Robocopy 実行時の共通オプション
# /mir     : ミラーリング(コピー元とコピー先を完全同期・不要ファイル削除)
# /copy:dt : データとタイムスタンプをコピー
# /r:1     : 失敗時の再試行回数 (1回)
# /w:1     : 再試行までの待機時間 (1秒)
# /mt:6    : マルチスレッドコピー (6スレッド)
# /fft     : ファイル時間の精度を2秒単位として扱う(NAS間コピーでの誤判定防止)
RobocopyOptions = /mir /copy:dt /r:1 /w:1 /mt:6 /fft
[NAS]
# ------------------------------------------------------------------------------
# 起動(WOL)およびシャットダウンの対象となるTeraStationを登録します。
#
# 基本書式(共通ID/パスワードを使用する場合):
#   ホスト名 = IPアドレス(またはホスト名), MACアドレス
#
# 個別書式(その機器だけ異なるID/パスワードを使用する場合):
#   ホスト名 = IPアドレス(またはホスト名), MACアドレス, 個別ID, 個別パスワード
# ------------------------------------------------------------------------------
NAS-MAIN01   = 192.168.1.101, 00:11:22:33:44:01
NAS-MAIN02   = 192.168.1.102, 00:11:22:33:44:02
NAS-BACKUP01 = 192.168.1.201, 00:11:22:33:44:11
# ↓ 特定のNASだけパスワードが違う場合の記述例
NAS-BACKUP02 = 192.168.1.202, 00:11:22:33:44:12, admin, custom_pass999
# ↓ 一時的に対象から外したい機器は先頭に # をつけます
# NAS-ARCHIVE = 192.168.1.250, 00:11:22:33:44:99
[SyncJobs]
# ------------------------------------------------------------------------------
# Robocopyによるフォルダ同期ペアを登録します。
# 書式: グループ名 = コピー元ホスト(またはIP), コピー元パス, コピー先ホスト(またはIP), コピー先パス
#
# ・同じ「グループ名」を指定した行は、1つの画面内で【順番(直列)】に実行されます。
# ・異なる「グループ名」を指定した行は、別々の画面で【同時(並列)】に実行されます。
# ------------------------------------------------------------------------------
# --- 例1: GroupA(NAS-MAIN01 の複数フォルダを NAS-BACKUP01 へ順番に同期) ---
GroupA = nas-main01, \\nas-main01\共有フォルダ, nas-backup01, \\nas-backup01\共有フォルダ
GroupA = nas-main01, \\nas-main01\営業資料,     nas-backup01, \\nas-backup01\営業資料
# --- 例2: GroupB(GroupAと並行して、別ペアのNAS間で同期を実行) ---
GroupB = nas-main02, \\nas-main02\写真データ,   nas-backup02, \\nas-backup02\写真データ

設定ファイル(config.ini)の書き方ポイント

  • コメントアウトで簡単に対象を切り替え可能:
    「今日はこのNASだけ起動したい」という場合でも、[NAS] セクションで行頭に # をつけたり外したりするだけで柔軟に対象をコントロールできます。
  • 個別のID・パスワードにも完全対応:
    基本的には [Settings] に書いた共通の管理者ID/パスワードを使ってシャットダウンを行いますが、1台だけパスワードが違う機体がある場合は、NAS-BACKUP02 = 192.168.1.202, 00:11:22:33:44:12, admin, custom_pass999 のようにカンマ区切りで3番目・4番目に個別のIDとパスワードを追記すればそちらが優先されます。
  • グループ名による直列・並列コントロール:
    たとえば「2台の異なるNASから、1台の大きなNASへバックアップを集約する」という場合、同時に書き込むと受け手側のHDDがカリカリと鳴りっぱなしになって速度が落ちます。そういう時は両方とも GroupC = ... のように同じグループ名にしておけば、1台目のコピーが完全に終わってから2台目のコピーが始まるため非常にスムーズです。

ステップ3:実行用バッチファイルの作成

PowerShellスクリプトをダブルクリック一発で呼び出せるように、同じフォルダ内にバッチファイル(.bat)を作成します。用途に合わせて作成してください。

① 手動実行用バッチ(処理完了後、Enterキーを押すまで画面が残る)

普段デスクトップなどから手動でダブルクリックして使うためのバッチです。エラーが出ていないか目視で確認できます。

:: ファイル名:1_Start-NAS.bat(起動のみ)
@echo off
cd /d "%~dp0"
powershell.exe -NoProfile -ExecutionPolicy Bypass -File "TeraStation-Manager.ps1" -Mode Wake
:: ファイル名:2_Stop-NAS.bat(シャットダウンのみ)
@echo off
cd /d "%~dp0"
powershell.exe -NoProfile -ExecutionPolicy Bypass -File "TeraStation-Manager.ps1" -Mode Shutdown
:: ファイル名:3_Backup-Sequence.bat(起動 ~ 5分待機 ~ Robocopy同期 ~ シャットダウン)
@echo off
cd /d "%~dp0"
powershell.exe -NoProfile -ExecutionPolicy Bypass -File "TeraStation-Manager.ps1" -Mode FullSequence

② タスクスケジューラ用バッチ(処理完了後、自動でウィンドウが閉じる)

Windowsのタスクスケジューラに登録して、深夜や休日に無人実行させるためのバッチです。末尾に -NoPause オプションがついており、処理が終わると自動的にウィンドウが閉じます。

:: ファイル名:Task_1_Start-NAS.bat
@echo off
cd /d "%~dp0"
powershell.exe -NoProfile -ExecutionPolicy Bypass -File "TeraStation-Manager.ps1" -Mode Wake -NoPause
:: ファイル名:Task_2_Stop-NAS.bat
@echo off
cd /d "%~dp0"
powershell.exe -NoProfile -ExecutionPolicy Bypass -File "TeraStation-Manager.ps1" -Mode Shutdown -NoPause
:: ファイル名:Task_3_Backup-Sequence.bat
@echo off
cd /d "%~dp0"
powershell.exe -NoProfile -ExecutionPolicy Bypass -File "TeraStation-Manager.ps1" -Mode FullSequence -NoPause

実際に動かしてみる(3_Backup-Sequence.bat の流れ)

準備ができたら、3_Backup-Sequence.bat をダブルクリックして実行してみます。以下の4ステップが全自動で進行します。

  1. WOLマジックパケットの一括送信:
    config.ini に記載されたすべてのTeraStationへ起動パケットが飛びます。
  2. 起動待機カウントダウン(デフォルト5分):
    TeraStationが完全にブートしてファイル共有(SMB)が使えるようになるまで、プログレスバー付きで5分間カウントダウン待機します。
  3. グループ別Robocopyウィンドウの展開と終了監視:
    グループごとに別ウィンドウが立ち上がり、まずコピー元とコピー先の双方にPingが通るかチェックした上で、高速マルチスレッド同期(/mt:6)を開始します。PowerShell側は立ち上げたすべての同期ウィンドウが閉じるのをじっと待っています。
  4. JSON-RPCによる安全シャットダウン:
    すべてのRobocopyが完了した瞬間、各TeraStationのAPIへシャットダウン命令を送信します。数秒後にTeraStationから「ピッ」と音が鳴り、安全に電源が落ちて全工程が完了します!

よくある質問・トラブルシューティング

  • Q. WOLを送ってもTeraStationが起動しない
    ・TeraStationの管理画面で「Wake on LAN」が有効になっているか確認してください。
    ・本体背面に「電源連動スイッチ(AUTO / MANUAL)」がある機種は、MANUAL 側になっているか確認してください。
  • Q. Robocopyが「offline」と判定されてスキップされる
    ・HDD本数が多いモデルやRAIDチェック中などで、起動に5分(300秒)以上かかっている可能性があります。config.ini の BootWaitSeconds = 420(7分)などに増やしてみてください。
    ・共有フォルダにパスワード制限がかかっている場合は、あらかじめWindowsのエクスプローラーから一度そのNASを開き、「資格情報を記憶する」にチェックを入れてログイン情報をPCに覚えさせておいてください。
  • Q. /mir オプションの注意点
    初期設定の RobocopyOptions には /mir(ミラーリング)が含まれています。これは「コピー元にないファイルはコピー先からも消去する」という完全同期オプションです。コピー元とコピー先の記述順序を逆に書いてしまうとデータが消えてしまうため、初回実行時はパス指定に間違いがないか必ず確認してください。

まとめ

これで、何台ものTeraStationを抱えていても「バッチを1回ダブルクリックするだけ(あるいはタスクスケジューラに任せて寝ている間)」に、起動からミラーリングバックアップ、そして安全なシャットダウンまでが完結するようになりました。

電気代の節約になるだけでなく、普段はバックアップ機の電源が物理的に落ちているため、万が一メイン環境で誤削除やトラブルが起きてもバックアップ側のデータは確実に守られます。TeraStationを複数運用されている方はぜひ導入してみてください!