0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Windowsが使えるならPowerShellで小手先の楽をしよう!

0
Posted at

大量展開したWindowsの設定

Windowsで大量展開とかすると、それに対して個別に設定をしなければならない。
みたいなことは、まあ大半は情シの人だとは思うけどそれを生業でやってる人なんかも大変な思いをしなければならなかったりなんかします。

  1. Windowsを起動して
  2. 設定(コントロールパネル)を開いて
  3. ネットワークのプロパティを開いて
  4. IPv4のプロパティを開いて
  5. やっと値を設定できる

みたいな感じです。
これを10台くらいまでならまだいいんですが、20台、50台、100台となってくると面倒くさくなります。
それに量が増えることで設定ミスなんかを起こす可能性もありますし。

設定ミスが怖いなら自動化しよう

つまりそういうことです。
IPを入れて、サブネットを入れて、ゲートウェイを入れて、プライマリDNSを入れて、セカンダリDNSを入れて・・・
その途中での入力ミスは怖いし、なにより一個づつ値を入力していくのは面倒くさくなります(本日二回目)。

そこで登場するのがこのスクリプト。
Geminiさんにところどころ聞きつつ、PowerShellとコマンドプロンプトの合わせ技で、設定ファイル内からホスト名をキーとしてIPアドレスとかの情報を取得して設定してしまう便利なスクリプトです。

設定ファイルとは?

基本的には設定ファイルから情報を受け取ります。
情シとかだったらエクセルで、1行1台で
・ホスト名
・インターフェイス名
・IPアドレス
・サブネット
・ゲートウェイ
・プライマリDNS
・セカンダリDNS
なんてデータがあって、それのデータ区切りにカンマ( , ←これ!)を使った、世間的に言えば CVS 形式のファイルを用意できると思います。
これを上に書かれた順番のに整えれば設定ファイルの完成です。

具体的にはどう使う?

前述の設定ファイルが完成したら ipconfig.cmd というファイルを作りましょう。そして

_ipconfig.cmd
@echo off
setlocal enabledelayedexpansion
cd /d %~dp0

:input
cls
echo ======================================================
echo  IP設定ツール (自動昇格・DD対応)
echo ======================================================
echo.
echo 【設定ファイルの記述ルール】
echo  CSV形式: ホスト名,ネットワーク名,IPアドレス,マスク,GW,DNS1,DNS2
echo.
echo  [例1] PC01,Ethernet,192.168.1.10,255.255.255.0,192.168.1.1,8.8.8.8
echo  [例2] PC02,Wi-Fi,10.0.0.5,24,,, (GW/DNSを空にすると設定クリア)
echo.
echo ------------------------------------------------------
echo  設定ファイルをこのウィンドウにドラッグアンドドロップして、
echo  Enterを押してください。
echo ------------------------------------------------------
echo.

set "TARGET_FILE="
set /p "TARGET_FILE=filepath: "

if not defined TARGET_FILE (
    echo [ERROR] パスが入力されていませんわ!
    pause
    goto :input
)

rem ダブルクォーテーションを安全に除去
set "TARGET_FILE=%TARGET_FILE:"=%"

if not exist "!TARGET_FILE!" (
    echo [ERROR] ファイルが見つかりませんわ! : !TARGET_FILE!
    pause
    goto :input
)

echo.
echo 管理者権限でPowerShellを起動しますわ。
echo 画面に出る「はい」を選択してくださいませ...

rem ExecutionPolicy Bypass で、事前のポリシー設定なしでも確実に実行させますの
powershell -NoProfile -ExecutionPolicy Bypass -Command "Start-Process powershell -ArgumentList '-NoProfile -ExecutionPolicy Bypass -File \""%~dp0ipconfig.ps1\"" \""!TARGET_FILE!\""' -Verb RunAs"

exit

こんな感じのファイルになるようにします。

ちなみにこのバッチファイルとPowerShellスクリプトの文字コードはSJISですよ?

実行されるとファイルの入力待ちになるので、前述の設定ファイルをていやっ! とドラックアンドドロップした上で【 Enter 】をキーインしましょう。
するとUACを経て次のようなPowerShellのプロンプトが開きます。そのプロンプトで処理される内容が次のようなもの。

まあ、難しいことはないですね。一行ずつ読んでいけば簡単!!

ipconfig.ps1
param([string]$ListFile)

# 出力を日本語(Shift-JIS)に固定
[Console]::OutputEncoding = [System.Text.Encoding]::GetEncoding(932)

# --- 文字コード判定関数 ---
function Get-FileEncoding {
    param([string]$Path)
    if ([string]::IsNullOrWhiteSpace($Path)) { return "default" }
    $bytes = [System.IO.File]::ReadAllBytes($Path)
    # BOM付きUTF-8チェック
    if ($bytes.Length -ge 3 -and $bytes[0] -eq 0xEF -and $bytes[1] -eq 0xBB -and $bytes[2] -eq 0xBF) { return "utf8" }
    try {
        $utf8NoBom = New-Object System.Text.UTF8Encoding($false, $true)
        $null = $utf8NoBom.GetString($bytes)
        return "utf8"
    } catch {
        return "default" # 失敗時はShift-JIS(System既定)
    }
}

# --- IP形式チェック関数 ---
function Test-IsIPAddress {
    param([string]$IP)
    if ([string]::IsNullOrWhiteSpace($IP)) { return $false }
    try { $null = [ipaddress]$IP; return $true } catch { return $false }
}

# --- サブネットマスクをプレフィックス長に変換する関数 ---
function Convert-MaskToPrefix {
    param([string]$Mask)
    if ([string]::IsNullOrWhiteSpace($Mask)) { return 24 }
    if ($Mask -match '^\d+$') { return [int]$Mask }
    try {
        $ip = [ipaddress]$Mask
        $bytes = $ip.GetAddressBytes()
        [string]$binStr = ""
        foreach($b in $bytes) { $binStr += [System.Convert]::ToString($b, 2).PadLeft(8, '0') }
        return ($binStr.Replace("0","").Length)
    } catch { return 24 }
}

# ファイル存在確認
if (-not (Test-Path $ListFile)) {
    Write-Host "【エラー】設定ファイルが見つかりませんわ! : $ListFile" -ForegroundColor Red
    pause; exit
}

$enc = Get-FileEncoding $ListFile
Write-Host "`n設定ファイルを読み込みました ($enc)" -ForegroundColor Cyan

# ホスト名入力
$targetHost = Read-Host "設定を行いたい「ホスト名」を入力してください"
if ([string]::IsNullOrWhiteSpace($targetHost)) { exit }

# 該当行の検索
$configLine = Get-Content $ListFile -Encoding $enc | Where-Object { $_.StartsWith("$targetHost,") }

if (-not $configLine) {
    Write-Host "【エラー】ホスト名 '$targetHost' の設定が見つかりませんでしたわ。" -ForegroundColor Yellow
    pause; exit
}

# データの分割とパース
$data = $configLine.Split(",").Trim()
$netName = if ($data.Count -gt 1) { $data[1] } else { "" }
$ipAddr  = if ($data.Count -gt 2) { $data[2] } else { "" }
$maskVal = if ($data.Count -gt 3) { $data[3] } else { "" }
$gateway = if ($data.Count -gt 4 -and ![string]::IsNullOrWhiteSpace($data[4])) { $data[4] } else { "" }
$dns1    = if ($data.Count -gt 5 -and ![string]::IsNullOrWhiteSpace($data[5])) { $data[5] } else { "" }
$dns2    = if ($data.Count -gt 6 -and ![string]::IsNullOrWhiteSpace($data[6])) { $data[6] } else { "" }

# プレフィックス長への変換
$prefix = Convert-MaskToPrefix $maskVal

# --- 厳格なバリデーション ---
$errors = @()
if ([string]::IsNullOrWhiteSpace($netName)) { $errors += "ネットワーク名が指定されていませんわ。" }
if (-not (Test-IsIPAddress $ipAddr)) { $errors += "IPアドレス '$ipAddr' が不正な形式ですわ。" }
if ($gateway -ne "" -and -not (Test-IsIPAddress $gateway)) { $errors += "ゲートウェイ '$gateway' が不正な形式ですわ。" }
if ($dns1 -ne "" -and -not (Test-IsIPAddress $dns1)) { $errors += "DNS1 '$dns1' が不正な形式ですわ。" }
if ($dns2 -ne "" -and -not (Test-IsIPAddress $dns2)) { $errors += "DNS2 '$dns2' が不正な形式ですわ。" }

if ($errors.Count -gt 0) {
    Write-Host "`n【設定エラー】リストの内容を修正してくださいませ!" -ForegroundColor Red
    $errors | ForEach-Object { Write-Host " - $_" -ForegroundColor Red }
    pause; exit
}

Write-Host "`n--- 設定を開始します: $targetHost ---" -ForegroundColor Cyan

try {
    # 既存設定のクリア (IP/GW)
    Remove-NetIPAddress -InterfaceAlias $netName -Confirm:$false -ErrorAction SilentlyContinue
    Remove-NetRoute -InterfaceAlias $netName -Confirm:$false -ErrorAction SilentlyContinue

    # IPとゲートウェイの設定
    $ipParams = @{
        InterfaceAlias = $netName
        IPAddress      = $ipAddr
        PrefixLength   = $prefix
        Confirm        = $false
    }
    if ($gateway -ne "") { $ipParams["DefaultGateway"] = $gateway }
    New-NetIPAddress @ipParams

    # DNSの設定
    $dnsList = @()
    if ($dns1 -ne "") { $dnsList += $dns1 }
    if ($dns2 -ne "") { $dnsList += $dns2 }

    if ($dnsList.Count -gt 0) {
        Set-DnsClientServerAddress -InterfaceAlias $netName -ServerAddresses $dnsList
    } else {
        # 空欄時はDNSをリセット
        Set-DnsClientServerAddress -InterfaceAlias $netName -ResetServerAddresses
        Write-Host "DNS設定をクリア(リセット)しましたわ。" -ForegroundColor Gray
    }

    # --- ログ生成 ---
    $header = @"
========================================
設定日時: $(Get-Date)
対象ホスト: $targetHost
----------------------------------------
設定ファイルからの指定値:
ネットワーク名: $netName
設定IP: $ipAddr / $prefix
ゲートウェイ: $(if($gateway){$gateway}else{"(未指定・クリア)"})
DNS: $(if($dnsList){$dnsList -join ', '}else{"(未指定・クリア)"})
========================================
実行結果詳細:
"@

    # 実際の反映結果を取得
    $actualIP = Get-NetIPAddress -InterfaceAlias $netName -AddressFamily IPv4 | Out-String
    $fullLog = $header + "`n" + $actualIP

    # 画面とファイルへ出力
    Write-Host $fullLog -ForegroundColor Green
    $fullLog | Out-File -FilePath "$PSScriptRoot\$($targetHost)_result.txt" -Encoding Default
    Write-Host "ログを保存しました: $($targetHost)_result.txt"
}
catch {
    Write-Host "`n【重大なエラー】が発生いたしました: $_" -ForegroundColor Red
}

echo "`nすべての処理が完了いたしましたわ。"
pause

はい! 流れとしては・・・

ipconfig.ps1
param([string]$ListFile)
[Console]::OutputEncoding = [System.Text.Encoding]::GetEncoding(932)

まずこの部分で準備とか環境設定とかやります

ipconfig.ps1
function Get-FileEncoding {
    param([string]$Path)
    if ([string]::IsNullOrWhiteSpace($Path)) { return "default" }
    $bytes = [System.IO.File]::ReadAllBytes($Path)
    # BOM付きUTF-8チェック
    if ($bytes.Length -ge 3 -and $bytes[0] -eq 0xEF -and $bytes[1] -eq 0xBB -and $bytes[2] -eq 0xBF) { return "utf8" }
    try {
        $utf8NoBom = New-Object System.Text.UTF8Encoding($false, $true)
        $null = $utf8NoBom.GetString($bytes)
        return "utf8"
    } catch {
        return "default" # 失敗時はShift-JIS(System既定)
    }
}

次にこの部分で文字コードの判定をしてます。
その後、

ipconfig.ps1
# --- IP形式チェック関数 ---
function Test-IsIPAddress {
    param([string]$IP)
    if ([string]::IsNullOrWhiteSpace($IP)) { return $false }
    try { $null = [ipaddress]$IP; return $true } catch { return $false }
}

# --- サブネットマスクをプレフィックス長に変換する関数 ---
function Convert-MaskToPrefix {
    param([string]$Mask)
    if ([string]::IsNullOrWhiteSpace($Mask)) { return 24 }
    if ($Mask -match '^\d+$') { return [int]$Mask }
    try {
        $ip = [ipaddress]$Mask
        $bytes = $ip.GetAddressBytes()
        [string]$binStr = ""
        foreach($b in $bytes) { $binStr += [System.Convert]::ToString($b, 2).PadLeft(8, '0') }
        return ($binStr.Replace("0","").Length)
    } catch { return 24 }
}

で、次にこの部分でIPアドレス類の変換とかをいろいろやってます。
ここまでは主に下準備とか関数に関する部分ですね。で、ここから先は実際の処理になっていきます。

ipconfig.ps1
# データの分割とパース
$data = $configLine.Split(",").Trim()
$netName = if ($data.Count -gt 1) { $data[1] } else { "" }
$ipAddr  = if ($data.Count -gt 2) { $data[2] } else { "" }
$maskVal = if ($data.Count -gt 3) { $data[3] } else { "" }
$gateway = if ($data.Count -gt 4 -and ![string]::IsNullOrWhiteSpace($data[4])) { $data[4] } else { "" }
$dns1    = if ($data.Count -gt 5 -and ![string]::IsNullOrWhiteSpace($data[5])) { $data[5] } else { "" }
$dns2    = if ($data.Count -gt 6 -and ![string]::IsNullOrWhiteSpace($data[6])) { $data[6] } else { "" }

# プレフィックス長への変換
$prefix = Convert-MaskToPrefix $maskVal

# --- 厳格なバリデーション ---
$errors = @()
if ([string]::IsNullOrWhiteSpace($netName)) { $errors += "ネットワーク名が指定されていませんわ。" }
if (-not (Test-IsIPAddress $ipAddr)) { $errors += "IPアドレス '$ipAddr' が不正な形式ですわ。" }
if ($gateway -ne "" -and -not (Test-IsIPAddress $gateway)) { $errors += "ゲートウェイ '$gateway' が不正な形式ですわ。" }
if ($dns1 -ne "" -and -not (Test-IsIPAddress $dns1)) { $errors += "DNS1 '$dns1' が不正な形式ですわ。" }
if ($dns2 -ne "" -and -not (Test-IsIPAddress $dns2)) { $errors += "DNS2 '$dns2' が不正な形式ですわ。" }

if ($errors.Count -gt 0) {
    Write-Host "`n【設定エラー】リストの内容を修正してくださいませ!" -ForegroundColor Red
    $errors | ForEach-Object { Write-Host " - $_" -ForegroundColor Red }
    pause; exit
}

1行=1台分のデータを取得、それをばらしたりチェックしたりして問題無いかの確認をします。の上で次のステップに移り実際の設定を行っていきます。そんな設定部分が次の通り!
あ、ついでにテキストファイルとして実行結果を書き出し、ログファイル替わりへの保存もしているのが次の部分。

ipconfig.ps1
try {
    # 既存設定のクリア (IP/GW)
    Remove-NetIPAddress -InterfaceAlias $netName -Confirm:$false -ErrorAction SilentlyContinue
    Remove-NetRoute -InterfaceAlias $netName -Confirm:$false -ErrorAction SilentlyContinue

    # IPとゲートウェイの設定
    $ipParams = @{
        InterfaceAlias = $netName
        IPAddress      = $ipAddr
        PrefixLength   = $prefix
        Confirm        = $false
    }
    if ($gateway -ne "") { $ipParams["DefaultGateway"] = $gateway }
    New-NetIPAddress @ipParams

    # DNSの設定
    $dnsList = @()
    if ($dns1 -ne "") { $dnsList += $dns1 }
    if ($dns2 -ne "") { $dnsList += $dns2 }

    if ($dnsList.Count -gt 0) {
        Set-DnsClientServerAddress -InterfaceAlias $netName -ServerAddresses $dnsList
    } else {
        # 空欄時はDNSをリセット
        Set-DnsClientServerAddress -InterfaceAlias $netName -ResetServerAddresses
        Write-Host "DNS設定をクリア(リセット)しましたわ。" -ForegroundColor Gray
    }

    # --- ログ生成 ---
    $header = @"
========================================
設定日時: $(Get-Date)
対象ホスト: $targetHost
----------------------------------------
設定ファイルからの指定値:
ネットワーク名: $netName
設定IP: $ipAddr / $prefix
ゲートウェイ: $(if($gateway){$gateway}else{"(未指定・クリア)"})
DNS: $(if($dnsList){$dnsList -join ', '}else{"(未指定・クリア)"})
========================================
実行結果詳細:
"@

    # 実際の反映結果を取得
    $actualIP = Get-NetIPAddress -InterfaceAlias $netName -AddressFamily IPv4 | Out-String
    $fullLog = $header + "`n" + $actualIP

    # 画面とファイルへ出力
    Write-Host $fullLog -ForegroundColor Green
    $fullLog | Out-File -FilePath "$PSScriptRoot\$($targetHost)_result.txt" -Encoding Default
    Write-Host "ログを保存しました: $($targetHost)_result.txt"
}
catch {
    Write-Host "`n【重大なエラー】が発生いたしました: $_" -ForegroundColor Red
}

これで設定できれば try{ … }の部分を実行し、だめだったら catch{ … }の部分を実行するという流れになります。
こんな感じで、楽できるところは楽しようよ! というお話でした。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?