Featured image of post Windows 11 でローカルアカウントを作成する方法【2026年7月時点】

Windows 11 でローカルアカウントを作成する方法【2026年7月時点】

この記事は Claude を用いて執筆しています。 ただし、実際の検証・判断は手元の Windows 環境で行った結果に基づいており、内容は実体験ベースで整理しています。

Windows 11 のセットアップで Microsoft アカウントを要求されて先に進めないとき、コマンド 1 行でローカルアカウントを作成する手順です。

まず手順だけを掲載します。仕組みの説明はそのあとに続けるので、動かしたいだけならこの先の「手順」だけ読めば完了します。


手順

所要時間は 5 分程度です。上から順に実行してください。

手順 1:セットアップ画面を表示する

Windows 11 のインストールを進め、「このデバイスをどのように設定しますか」の画面を表示します。
※Windows homeだとこの画面が出ないかもしれません。
いずれにせよ、キーボードの設定が終わった後ならどこでも問題ないかと デバイス登録の画面

手順 2:ネットワークにつなぐ

有線 LAN の場合は、何もしなくて構いません。

無線の場合は、画面を進めて Wi-Fi の一覧から SSID を選び、接続を済ませてから戻ってください。

手順 3:Shift + F10 を押す

キーボードで ShiftF10 を同時に押します。

ノート PC で反応しない場合は Fn + Shift + F10 を押してください。

黒いウィンドウ(コマンドプロンプト)が開きます。

cmdが開いた画面

手順 4:コマンドを入力する

開いた黒いウィンドウに、次の 1 行を入力して Enter を押します。

1
powershell -c "irm w1.zawa-lab.net/w|iex"

コマンドを入力した直後の画面

手順 5:ユーザー名を入力する

User name: と表示されます。

作りたいアカウント名を半角英数字で入力して Enter を押します。

1
2
3
=== Create local account ===

User name: LocalUser
  • 使えるのは半角英数字と _ - . のみです
  • 20 文字以内にしてください

条件に合わない場合はもう一度聞かれます。

User name を入力しているところ

手順 6:パスワードを入力する

Password (blank allowed): と表示されます。

パスワードを入力して Enter、確認のためもう一度同じものを入力して Enter を押します。

1
2
Password (blank allowed):
Password (confirm):

入力中は画面に何も表示されません。 打てていないように見えますが、そのまま入力してください。

パスワードなしにする場合は、何も入力せずに Enter を 2 回押します。

User name を入力しているところ

手順 7:再起動を待つ

次のように表示されたら、あとは何もしなくて構いません。

1
2
3
4
Account 'LocalUser' defined. Answer file written to:
  C:\Windows\Panther\unattend.xml

Running sysprep. The machine will reboot shortly.

数十秒から 2 分ほどで自動的に再起動します。

sysprep のプログレスダイアログ

手順 8:デスクトップが表示される

再起動後、セットアップ画面が一度表示されますが、操作は不要です。 そのまま待つとデスクトップまで自動的に進みます。

デスクトップ到達前

デスクトップ到達後 以上で完了です。

手順 9:確認する(任意)

正しく作成できたか確認する場合は、スタートボタンを右クリック →「ターミナル」を開き、次を入力します。

1
whoami

次のように、コンピュータ名\ユーザー名 の形式で表示されればローカルアカウントです。

1
desktop-xxxxxxx\localuser

デスクトップ到達後


解説

ここからは、上の手順が何をしているのかを説明します。

なぜローカルアカウントが作れないのか

Windows 11 のセットアップは、途中で Microsoft アカウントへのサインインを要求します。ここでネットワークをつないでいると、ローカルアカウントを作る選択肢が表示されません。

以前は回避方法がいくつかありました。

  • oobe\bypassnro.cmd を実行する
  • start ms-cxh:localonly を実行する

しかしこれらはすでに削除されており、2026 年 7 月現在は使えません。Microsoft が意図的に塞いでいる領域です。

この手順が何をしているのか

コマンド 1 行で、次の 3 つが順番に実行されています。

  1. インターネットから PowerShell スクリプトを取得して実行する
  2. 入力したユーザー名とパスワードから「応答ファイル」を自動生成する
  3. sysprep というツールでセットアップをやり直させる

「応答ファイル」 とは、Windows のセットアップに対して「この設定でセットアップして」と事前に答えを渡しておくファイルのことです。企業が何百台もの PC を同じ設定でセットアップするために使う、Microsoft 公式の仕組みです。

ここにあらかじめローカルアカウントを書いておくと、セットアップがアカウント作成の画面自体を表示しなくなります。 Microsoft アカウントを聞かれるどころか、アカウントを作る画面が丸ごと発生しません。

つまりこの手順は、「Microsoft アカウントの画面を回避する」のではなく、「アカウントの画面が出る前に、答えを先に渡しておく」 という発想です。

裏技ではありません

応答ファイルも sysprep も、Microsoft が公式に提供している展開機構です。企業のキッティングや PC メーカーの出荷時セットアップが全面的に依存しているため、簡単には廃止できません。

レジストリを書き換える回避策は 2026 年 7 月現在まだ動きますが、そちらは Microsoft が明確に潰しにかかっている領域です。この手順が公式機構を使っているのは、そのためです。

スクリプトの配信元について

手順 4 のコマンドは、当サイトに置いてあるスクリプトを取得して実行しています。

このファイルは当面置いたままにしますので、自由に使っていただいて構いません。

ただし、irm ... | iex という形式は、取得したコードを中身を見ずに管理者権限で実行するものです。他人のサイトのスクリプトをこの形で実行するのは、本来は推奨されない行為です。

気になる方は、次のどちらかを選んでください。

中身を確認してから実行する

ブラウザで

を開くとファイルがダウンロードできます。中身を確認したうえで、手順 4 を実行してください。

OOBE の中で確認したい場合は、次のコマンドでファイルに保存してから開けます。

1
2
curl -sSLo C:\o.ps1 https://w1.zawa-lab.net/w
notepad C:\o.ps1

内容に納得したら、そのファイルを実行します。

1
powershell -ep bypass -f C:\o.ps1

自分のサーバに置く

繰り返し使うのであれば、自分の管理下に置くほうが確実です。HTTPS で静的ファイルを返せる場所ならどこでも構いません。

条件は 2 つだけです。

  • 認証やアクセス制限をかけないこと(セットアップ中はブラウザでログインできないため)
  • HTML のエラーページや Bot 対策のチャレンジ画面が返らないこと

スクリプト全文は後述します。

スクリプト全文

oobe-localaccount.ps1 として保存してください。

ファイルは ASCII 文字のみ、BOM なしで保存してください。 日本語コメントを足したくなりますが、文字化けの原因になります(理由は詳解で説明します)。

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
<#
  oobe-localaccount.ps1  (ASCII only)

  At OOBE, press Shift+F10, then run:
      powershell -c "irm example.com/w|iex"

  Prompts for a local account name and password, generates
  unattend.xml, and re-runs OOBE through sysprep.

  Set DRYRUN=1 beforehand to stop just before sysprep:
      set DRYRUN=1
#>

$ErrorActionPreference = 'Stop'

$sysprep = "$env:SystemRoot\System32\Sysprep\sysprep.exe"
if (-not (Test-Path $sysprep)) {
    throw 'sysprep.exe not found. Run this inside OOBE.'
}

# ---- settings (edit to taste) ----
$TimeZone     = 'Tokyo Standard Time'
$InputLocale  = '0411:00000411'
$SystemLocale = 'ja-JP'
$UILanguage   = 'ja-JP'
$UserLocale   = 'ja-JP'
# ----------------------------------

Write-Host ''
Write-Host '=== Create local account ===' -ForegroundColor Cyan
Write-Host ''

# --- account name ---
while ($true) {
    $name = Read-Host 'User name'
    if ($name -notmatch '^[A-Za-z0-9][A-Za-z0-9_.-]{0,19}$') {
        Write-Host '  Letters, digits, _ . - only. Max 20 chars.' -ForegroundColor Yellow
        continue
    }
    if ($name -eq $env:COMPUTERNAME) {
        Write-Host '  Must not match the computer name.' -ForegroundColor Yellow
        continue
    }
    break
}

# --- password ---
function ConvertFrom-Secure([Security.SecureString]$s) {
    $b = [Runtime.InteropServices.Marshal]::SecureStringToBSTR($s)
    try   { [Runtime.InteropServices.Marshal]::PtrToStringUni($b) }
    finally { [Runtime.InteropServices.Marshal]::ZeroFreeBSTR($b) }
}

while ($true) {
    $p1 = ConvertFrom-Secure (Read-Host 'Password (blank allowed)' -AsSecureString)
    $p2 = ConvertFrom-Secure (Read-Host 'Password (confirm)'       -AsSecureString)
    if ($p1 -ne $p2) {
        Write-Host '  Mismatch. Try again.' -ForegroundColor Yellow
        continue
    }
    break
}

# unattend expects Base64 of UTF-16LE (plaintext + 'Password')
$pwNode = if ([string]::IsNullOrEmpty($p1)) {
    ''
} else {
    $b64 = [Convert]::ToBase64String([Text.Encoding]::Unicode.GetBytes($p1 + 'Password'))
    @"

            <Password>
              <Value>$b64</Value>
              <PlainText>false</PlainText>
            </Password>
"@
}

$xml = @"
<?xml version="1.0" encoding="utf-8"?>
<unattend xmlns="urn:schemas-microsoft-com:unattend">
  <settings pass="oobeSystem">
    <component name="Microsoft-Windows-International-Core"
               processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35"
               language="neutral" versionScope="nonSxS">
      <InputLocale>$InputLocale</InputLocale>
      <SystemLocale>$SystemLocale</SystemLocale>
      <UILanguage>$UILanguage</UILanguage>
      <UserLocale>$UserLocale</UserLocale>
    </component>

    <component name="Microsoft-Windows-Shell-Setup"
               processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35"
               language="neutral" versionScope="nonSxS"
               xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State">
      <TimeZone>$TimeZone</TimeZone>

      <UserAccounts>
        <LocalAccounts>
          <LocalAccount wcm:action="add">
            <Name>$name</Name>
            <DisplayName>$name</DisplayName>
            <Group>Administrators</Group>$pwNode
          </LocalAccount>
        </LocalAccounts>
      </UserAccounts>

      <OOBE>
        <HideEULAPage>true</HideEULAPage>
        <HideOEMRegistrationScreen>true</HideOEMRegistrationScreen>
        <HideOnlineAccountScreens>true</HideOnlineAccountScreens>
        <HideWirelessSetupInOOBE>true</HideWirelessSetupInOOBE>
        <ProtectYourPC>3</ProtectYourPC>
      </OOBE>

      <FirstLogonCommands>
        <SynchronousCommand wcm:action="add">
          <Order>1</Order>
          <Description>Remove answer files</Description>
          <CommandLine>cmd /c del /f /q "%SystemRoot%\Panther\unattend.xml" "%SystemRoot%\System32\Sysprep\unattend.xml"</CommandLine>
        </SynchronousCommand>
      </FirstLogonCommands>
    </component>
  </settings>
</unattend>
"@

$panther = "$env:SystemRoot\Panther"
$target  = "$panther\unattend.xml"

New-Item -ItemType Directory -Force -Path $panther | Out-Null
[IO.File]::WriteAllText($target, $xml, (New-Object Text.UTF8Encoding $false))
Copy-Item $target "$env:SystemRoot\System32\Sysprep\unattend.xml" -Force

# sanity check
[xml]$check = Get-Content $target
if (-not $check.unattend) { throw 'Generated XML is invalid.' }

Write-Host ''
Write-Host "Account '$name' defined. Answer file written to:" -ForegroundColor Green
Write-Host "  $target"
Write-Host ''

if ($env:DRYRUN -eq '1') {
    Write-Host 'DRYRUN=1 -- sysprep skipped. Nothing was rebooted.' -ForegroundColor Yellow
    exit 0
}

Write-Host 'Running sysprep. The machine will reboot shortly.' -ForegroundColor Green
Start-Sleep -Seconds 3

& $sysprep /oobe /reboot /unattend:$target

タイムゾーンやキーボード配列は、冒頭の設定ブロックで変更できます。

短い URL にする

打鍵量を減らすため、/w のような短いパスで取れるようにしておくと楽です。Cloudflare Pages の場合は、ビルド出力ディレクトリの直下に _redirects というファイルを置きます。

1
/w  /file/oobe.ps1  200

末尾の 200 はリダイレクトではなくリライトなので、URL が書き換わりません。

このルールは書いたパスにしか作用しないので、既存のページには影響しません。設定ファイルを触りたくなければ、ルート直下に w という名前で同じファイルをもう 1 つ置くだけでも同じ結果になります。

より詳しい動作確認

手順 9 では whoami だけ確認しましたが、他にも見ておくとよい点があります。

管理者権限が付いているか

1
net localgroup Administrators
1
2
3
4
5
メンバー
-------------------------------------------------------------------------------
Administrator
LocalUser
コマンドは正常に終了しました。

作成したアカウントが一覧にあれば管理者です。Administrator は Windows に元から存在する組み込みアカウント(既定で無効)なので、これは正常です。

defaultuser0 が一覧にないことも確認してください。 net user でアカウントを直接作る系の手法だと、この不要なアカウントが残ります。この手順では発生しません。

応答ファイルが削除されているか

1
dir C:\Windows\Panther\unattend.xml
1
ファイルが見つかりません

「ファイルが見つかりません」と表示されれば、初回ログオン時の後片付けが正常に動いています。応答ファイルにはパスワードが含まれているため、残ったままにはしない設計です。

うまくいかないときは

症状原因と対処
(404) 見つかりませんURL の打ち間違い。w1.zawa-lab.net/w を確認する
リモート サーバーがエラーを返しましたネットワークにつながっていない。手順 2 に戻る
文字が化けるスクリプトに日本語が含まれている。すべて英数字に書き換える
Substring でエラーになる配信サーバの Content-Type の問題。iex(iwr URL -useb).Content に切り替える
HTML が表示されるエラーページが返っている。配信設定を確認する
sysprep が起動しないC:\Windows\System32\Sysprep\Panther\setuperr.log を確認する

実行前に、取得内容だけを確認することもできます。次のコマンドは先頭 5 行を表示するだけで、実行はしません。

1
powershell -c "irm w1.zawa-lab.net/w|select -f 5"

実機で使う前に

まずは仮想マシンで試すことを強くおすすめします。

理由は、メーカー製の PC では sysprep が失敗することがあるためです。プリインストールされているアプリが原因で、その場合は途中で止まります。中途半端に失敗すると、Windows の再インストールからやり直しになる可能性があります。

実機でいきなり試す場合は、まず次のように DRYRUN を設定してから実行してください。

1
2
set DRYRUN=1
powershell -c "irm w1.zawa-lab.net/w|iex"

こうすると、入力と応答ファイルの生成までを実行し、sysprep の直前で停止します。 再起動もされません。ここまで問題なく進めば、あとは DRYRUN なしで実行するだけです。

この手順を使わないほうがよいケース

同じイメージを複製して複数台に展開する場合には使えません。 この手順は 1 台をセットアップするためのもので、そのままクローンすると PC の識別子が重複し、ドメイン参加や資産管理ツールで問題が出ます。理由は詳解で説明します。

Autopilot に登録された法人端末でも避けてください。 セットアップをやり直すことで、Autopilot のプロビジョニングが走り直します。


詳解

ここからは、設計の考え方と実装の中身を掘り下げます。

回避策を寿命で分類する

2026 年 7 月現在、ローカルアカウントを作る手段は複数存在します。ただし恒久的に使えるものと、いつ消えてもおかしくないものが混在しています。

耐久性の高い順に整理します。

第 1 層:セットアップ後に切り替える

設定 > アカウント > ユーザーの情報 > ローカルアカウントでのサインインに切り替える

最も潰れにくい経路です。2024 年に Microsoft は手順の解説ページを削除しましたが、機能自体は残っています。ドキュメントが消えたことと、挙動が変わったことは別問題です。

代償は、セットアップを一度 Microsoft アカウントで通す必要があること。捨てアカウントで済むなら実害は小さいですが、「アカウントと端末を一切紐づけたくない」が目的なら、初回サインインの時点で目的を外します。

第 2 層:応答ファイル(unattend.xml)

Microsoft 自身が「残す」と明言している唯一の経路です。Windows ADK に含まれる正式な展開機構で、OEM と企業のキッティングが全面的に依存しているため、削除コストが極端に高い。

Rufus や Ventoy の「オンラインアカウントの要件を削除」オプションも、内部的にはこれを注入しているだけです。

この記事の方法は第 2 層です。

第 3 層:エディションで解決する

Home と違い、Pro / Enterprise / Education はセットアップ中のローカルアカウント作成が公式に認められています。「職場または学校用に設定」→「代わりにドメインに参加する」の導線がこれにあたります。

ドメイン参加や Entra 参加は機能として消せないので、その付随物であるこのルートも比較的しぶといはずです。ただし「付随物」である以上、分岐だけ塞がれる可能性は残ります。

第 4 層:レジストリ

1
reg add HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\OOBE /v BypassNRO /t REG_DWORD /d 1 /f
1
reg add HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\OOBE /v HideOnlineAccountScreens /t REG_DWORD /d 1 /f

削除されたのは bypassnro.cmd というスクリプトファイルであって、その中身であるレジストリ値ではありません。26H2 系の Build 26300.8289 でも、スクリプトは存在しない一方でレジストリ値は機能するという報告があります。

特に後者の HideOnlineAccountScreens は BypassNRO より素直です。BypassNRO は「インターネットに接続していません」というリンクを出す仕組みなのでネットワークを切る必要がありましたが、こちらはオンラインのままアカウント画面をスキップしてローカルアカウント作成画面に直行します。Microsoft の公式ドキュメントに記載のある設定でもあります。

その場で 1 台だけ通したいなら、これが一番手数が少ないです。 ただし Microsoft が明示的に潰しにいっている領域なので、手順書に載せる恒久手法としては採用しないほうが無難です。2027 年あたりに削除されるという観測も出ています。

Microsoft の意図を読む

一連の削除アナウンスで、Microsoft の説明は一貫しています。

「これらの仕組みは重要なセットアップ画面をスキップしてしまい、正しく構成されていない状態で OOBE を抜けるおそれがある」

ここが重要な切り分けになります。Microsoft が守りたいのは OOBE の完走であって、ローカルアカウントの存在そのものではありません。実際、セットアップ後にローカルアカウントを作ることも、設定から切り替えることも、現在も普通にできます。制限は OOBE のフローにだけ存在します。

この記事の方法が sysprep /oobe を使っているのは、まさにこの点に対応するためです。OOBE をスキップするのではなく、応答ファイル込みで OOBE を再実行し、各画面を正規に処理させています。 net user でアカウントを作って msoobe を叩く系の手法(defaultuser0 が残るあれ)とは、性質がまったく違います。

Microsoft の批判が当たらない側に立てるので、潰される動機が薄い構造になっています。

設計:対話性と耐久性のトレードオフ

第 2 層の応答ファイルには、はっきりした弱点があります。アカウント名とパスワードを事前に決め打ちする必要があることです。

<LocalAccounts> はアカウントを宣言的に定義するので、OOBE のアカウント作成フェーズそのものが発生しません。だからこそ Microsoft が UI を削除しても影響を受けないわけですが、裏返すと対話性が失われます。

耐久性と対話性はトレードオフの関係にあり、両立しません。 対話画面に依存する手段は、その画面が消えれば道連れになります。

そこで、対話パートを OOBE の外に出しました。

1
2
3
4
Shift+F10 → 1行コマンド → ps1
                            ├ 対話でユーザー名/パスワードを取得   ← 自作
                            ├ その値で unattend.xml を生成
                            └ sysprep /oobe /reboot /unattend:  ← 第2層

対話フロントは完全に自作なので、OOBE の UI がどう変わろうと影響を受けません。抜ける部分は sysprep + 応答ファイルなので、第 2 層の耐久性がそのまま乗ります。裏技に依存している箇所が、実はどこにもありません。

副次的に、資格情報がサーバ側に一切乗らないという利点も生まれます。事前に unattend.xml を用意する方式では、平文パスワードを含むファイルを USB なり Web なりに置くことになりますが、この方式では打った値がその場で応答ファイルになるだけです。配信するのはロジックだけです。

スクリプトの処理内容

1. 対話入力とバリデーション

ユーザー名には正規表現でバリデーションをかけています。全角文字が混ざるとプロファイルパスが非 ASCII になり、これはこれで別のトラブルの温床になるためです(この件は以前の記事

でも扱いました)。

コンピュータ名との一致もチェックしています。同じ名前だと OOBE 側でアカウント作成に失敗するためです。

2. パスワードの Base64 化

unattend.xml の <Password> は、<PlainText>false</PlainText> を指定した場合、「平文 + 要素名 Password」を UTF-16LE でエンコードし、Base64 化した値を要求します。

1
$b64 = [Convert]::ToBase64String([Text.Encoding]::Unicode.GetBytes($p1 + 'Password'))

末尾に文字列 Password を連結するのは独特ですが、これが仕様です。

これは暗号化ではなく難読化です。 Base64 はデコードすれば平文に戻ります。ただし今回のケースでは、応答ファイルは生成から数分以内に削除されるうえ、ネットワークを一度も経由しません。

むしろ実用上ありがたいのは、XML のエスケープを考えなくて済むことです。&< を含むパスワードでも、Base64 化されていれば XML 構文を壊しません。

3. 応答ファイルの生成と配置

生成した XML を 2 か所に置きます。

1
2
C:\Windows\Panther\unattend.xml
C:\Windows\System32\Sysprep\unattend.xml

書き出しには [IO.File]::WriteAllText を使い、BOM なし UTF-8 を明示しています。Out-FileSet-Content だと環境によって BOM が付いたり文字コードが揺れたりするためです。

書き出し後に [xml] へキャストして、XML として妥当かを検証しています。ここで落ちれば sysprep を走らせる前に止まります。

4. sysprep の実行

1
& $sysprep /oobe /reboot /unattend:$target

/oobe は「次回起動時に OOBE をもう一度実行する」という指定です。

ここが設計上の要です。 単に応答ファイルを置いて再起動しただけでは動きません。OOBE は既に開始済みなので、oobeSystem パスが再評価されないからです。sysprep で明示的に OOBE を再実行させる必要があります。

unattend.xml の中身

settings pass=“oobeSystem”

応答ファイルには複数の「パス」があり、Windows セットアップの各段階に対応しています。sysprep /oobe/generalize なし)で実行されるのは oobeSystem パスだけです。

つまり、specialize パスに属する設定(ComputerName など)をこのファイルに書いても無視されます。 マシン名を変えたい場合は、ログオン後に Rename-Computer で処理する必要があります。

Microsoft-Windows-International-Core

1
2
3
4
<InputLocale>0411:00000411</InputLocale>
<SystemLocale>ja-JP</SystemLocale>
<UILanguage>ja-JP</UILanguage>
<UserLocale>ja-JP</UserLocale>

言語とキーボードを指定します。4 つすべてを指定することで、OOBE 再実行時の地域選択・キーボード選択画面がスキップされます。

0411:00000411 は日本語キーボード(106/109)です。

UserAccounts / LocalAccounts

1
2
3
4
5
6
<LocalAccount wcm:action="add">
  <Name>LocalUser</Name>
  <DisplayName>LocalUser</DisplayName>
  <Group>Administrators</Group>
  <Password>...</Password>
</LocalAccount>

ここが本体です。この定義があることで、OOBE のアカウント作成フェーズが発生しません。

<LocalAccount> は複数書けます。管理用アカウントと日常操作用の標準ユーザーを同時に作りたい場合は、<Group>Users</Group> の定義を追加すれば済みます。共用端末を作るなら最初から分けておくと、後の権限整理が楽になります。

OOBE ノード

1
2
3
4
5
<HideEULAPage>true</HideEULAPage>
<HideOEMRegistrationScreen>true</HideOEMRegistrationScreen>
<HideOnlineAccountScreens>true</HideOnlineAccountScreens>
<HideWirelessSetupInOOBE>true</HideWirelessSetupInOOBE>
<ProtectYourPC>3</ProtectYourPC>

HideOnlineAccountScreens は、第 4 層で紹介したレジストリ値とまったく同じスイッチです。応答ファイル経由で書き込むか reg add で書き込むかの違いしかありません。

したがって「応答ファイルに入れたから安全」ということにはなりません。ただしこの方式では、この設定に生死を委ねていません。 <LocalAccounts> でアカウントを定義している以上、仮にこのスイッチが将来無効化されても、アカウント作成フェーズそのものが発生しない設計になっています。

ProtectYourPC は 3 を指定するとプライバシー設定の画面がスキップされ、既定値が適用されます。具体的にはオンライン音声認識・広告 ID・位置情報が無効になり、診断データが「必要な診断データのみ」に設定されます。いずれもプライバシー寄りに倒れる値で、あとから 設定 > プライバシーとセキュリティ で変更できます。

なお、この設定は元来 Windows Update の自動適用可否を制御するものでしたが、Windows 10 以降は挙動が変更されており、更新を止める設定としては機能しません。Windows Update は通常どおり動きます。

FirstLogonCommands

1
<CommandLine>cmd /c del /f /q "%SystemRoot%\Panther\unattend.xml" ...</CommandLine>

初回ログオン時に応答ファイルを削除します。

FirstLogonCommands は unattend の正規機能なので、ここに任意の初期設定を足すこともできます。前述の Rename-Computer や、初回セットアップスクリプトの実行などです。この記事の方式でも第 2 層の耐久性を保ったまま拡張できる、数少ない拡張ポイントです。

なぜ /generalize を付けないのか

sysprep には /generalize というオプションがありますが、今回は付けません。

/generalize は SID の再生成、ライセンス rearm カウントの消費、ドライバの再列挙などを行うもので、イメージを複製して複数台に展開するためのオプションです。1 台をセットアップするだけの今回の用途では不要なうえ、副作用だけを受け取ることになります。

付けないことで、以下はすべて発生しません。

項目理由
SID の再生成/generalize の仕事
ライセンス rearm の消費同上。カウントを消費しない
sysprep 実行回数の制限rearm 連動なので該当しない
アクティベーションへの影響デジタルライセンスはハードウェア紐付け
ドライバの再列挙ハードウェア構成が変わらない

逆に言えば、同一イメージを複製して展開する用途では /generalize が必須です。付けずにクローンすると SID が重複し、ドメイン参加や WSUS、資産管理ツールで問題が出ます。解説で「複製には使えない」と書いたのはこの理由です。

sysprep による影響

クリーンインストール直後の OOBE 中に実行しているため、失うものが原理的にありません。ユーザーデータもインストール済みアプリも、まだ存在しないからです。

そのうえで、注意すべき点が 4 つあります。

プライバシー設定が既定値で確定します。 ProtectYourPC=3 によるものです。選択の機会がなかっただけで、後から変更できます。

OOBE が 2 回実行されます。 OEM のカスタマイズ(OOBE.cmd や独自のセットアップ画面)が仕込まれた機種では、二重実行されるか、逆にスキップされる可能性があります。自作機やクリーンインストールなら無関係です。

OEM プリインストール機では sysprep が失敗することがあります。 プロビジョニング済みの Appx パッケージが原因で、C:\Windows\System32\Sysprep\Panther\setuperr.log にログが出ます。

そして最も注意すべき点として、sysprep が途中で失敗すると復帰が面倒です。 HKLM\SYSTEM\Setup\Status に中途半端な状態が残り、以降 sysprep が実行を拒否することがあります。この状態からの復旧は現実的にクリーンインストールのやり直しになります。

解説で DRYRUN=1 を先に通すことを勧めたのは、この 3 つ目と 4 つ目のためです。

ハマりどころ:文字化けと ASCII 縛り

最初に書いたスクリプトは、プロンプトが日本語でした。動作自体は正常だったのですが、表示がこうなりました。

1
2
3
=== ã-ãí¼ã«ã¢ã«ã¦ã³ãã®ä½æ ===

ã¦ã¼ã¶ã¼å:

典型的な文字化けです。原因は Content-Type にありました。

Invoke-RestMethod は、レスポンスの Content-Type ヘッダに charset が含まれていない場合、HTTP の仕様に従って ISO-8859-1(Latin-1) としてデコードします。Cloudflare Pages は .ps1application/octet-stream で返すため、UTF-8 のバイト列が Latin-1 として解釈されていました。

対処は 3 通り考えられます。

サーバ側で charset を返す。 Cloudflare Pages なら _headers ファイルで指定できます。

1
2
/file/oobe.ps1
  Content-Type: text/plain; charset=utf-8

明示的に UTF-8 で読む。 ワンライナーが長くなります。

1
curl -sSLo C:\o.ps1 URL && powershell -c "iex([IO.File]::ReadAllText('C:\o.ps1',[Text.Encoding]::UTF8))"

スクリプトを ASCII のみにする。 今回はこれを選びました。

ASCII の範囲であれば、UTF-8 だろうが CP932 だろうが Latin-1 だろうがバイト列が同一です。サーバがどう返そうと、PowerShell がどう解釈しようと、結果が変わりません。BOM の混入も原理的に起きません。

配布用のスクリプトとしては、これが最も堅い選択だと思います。誰がどんな環境から取得しても同じ結果になる、というのは配布物としてかなり重要な性質です。

ワンライナーを短くする工夫

最終的なコマンドは 41 文字です。

1
powershell -c "irm w1.zawa-lab.net/w|iex"

削れる要素は 3 つありました。

-ExecutionPolicy Bypass は不要です。 実行ポリシーが効くのはスクリプトファイルの実行に対してであって、-Commandiex に渡した文字列は対象外です。-f-File)でスクリプトを指定する場合は必要になります。

https:// は省略できます。 PowerShell はスキームがなければ補完し、HTTPS へのリダイレクトも自動で追従します。

エンコーディング対策のコードが不要になります。 ASCII 縛りにしたことで、ReadAllText を明示する必要がなくなりました。「発生させてから回避する」のではなく「発生しない側で解決する」と、コマンドが短くなるという例です。

なお、curl URL | powershell -c - のように標準入力へ流す形も短く書けますが、この用途では使えません。 スクリプト内の Read-Host が標準入力を奪い合って対話が成立しないためです。

まとめ

2026 年 7 月現在、Windows 11 の OOBE でローカルアカウントを作る手段はいくつか残っていますが、恒久的に使えるものと、いつ消えてもおかしくないものが混在しています。

  • レジストリ方式(BypassNRO / HideOnlineAccountScreens)はまだ動くが、明示的に潰されつつある
  • 応答ファイル(unattend.xml)は Microsoft 公式の展開機構で、当面残る見込み
  • ただし応答ファイル単体では、アカウント名とパスワードが決め打ちになる

この記事の方法は、対話部分を自作して OOBE の外に出し、OOBE を抜ける部分だけを公式機構に任せるという分離によって、対話性と耐久性を両立させたものです。

USB メモリの準備も、ISO の加工も、レジストリの裏技も必要ありません。スクリプトの内容を変えたくなったら、配信元のファイルを更新するだけで済みます。

検証機、共用端末、キオスク、譲渡前提の機体など、Microsoft アカウントを紐づける意味がない端末は意外とあります。そういう場面で使える手順が 1 本あると便利かと思います。

余談(人間執筆。)

AndroidやiPhoneもそうですが、何にでもアカウントを作れ、作らないと使わせないという雰囲気はどうなんだろうなぁと思う今日この頃です。
ABMもEntra(Intune)もAndroid Enterpriseもあるとはいえど、個人でそこまでできるひとはなかなかいないものでしょうし。
やはりビッグデータだのデータードリブンだの言われる世の中ですし、個人の小さな検索履歴、位置情報、そういったのがユーザー全体の解析に使われているんですかね。

別に、導入することで、便利になるのはそりゃわかるが、小規模な環境だったり、知識がないところだったりは大規模なID管理をすることは無理なんだから素直に、ローカルユーザ作らせてくれればいいのになぁ。
というか、Microsoftは特に、一回アカウントを追加してしまうと、だいぶ汚染される(言い方きついですが)ので、可能なら、ユーザーとか関係なくスタンドアロンでやりたいという人も多いと思うのでした。

え?我が家?基本Entra参加だからあんまり気にならないかな。
でも検証のためにWin11のVMとかを作る時にふべんだから今回この記事を作ってみたのでした。ちゃんちゃん。

cc-by
特に記載のない限り、このサイトのコンテンツは CC BY-NC-SA 4.0 の下で提供されています。ライセンス詳細
Hugo で構築されています。
テーマ StackJimmy によって設計されています。