Softex CelwareTech Blog
PowerShell / bat運用2026-09-03

Node製ローカルアプリをWindowsでオフライン配布する起動設計

Node製ローカルWebアプリを、管理者権限やインターネットなしのWindows PCへ配布するために、bat/ps1ランチャー、PowerShellの戻り値対策、Node同梱、ZIP検証をまとめます。

PowerShellbatNode.jsWindowsオフライン配布ローカルサーバーZIP

Node.js製のローカルWebアプリを、現場スタッフや顧客PCで動かしてもらう場合、npm installやnpm startを打ってもらう前提は現実的ではありません。

さらに、配布先PCがインターネットにつながっていない、管理者権限がない、Node.jsが入っていない、という条件もあります。

この記事では、Node製ローカルアプリをWindowsで配布するときの、bat/ps1ランチャー、PowerShellの戻り値の罠、Node本体同梱、ZIP配布の検証方法をまとめます。

配布物はダブルクリック起動にする

非エンジニア向けの配布では、次のように番号付きのbatを置くと伝わりやすくなります。

1_セットアップ.bat      初回のみ。ネット環境でnpm installする場合
2_起動.bat              サーバーを起動してブラウザを開く
3_初期状態に戻す.bat    データを作り直す

batは最小限にし、日本語メッセージや複雑な処理はps1側へ寄せます。

@echo off
cd /d "%~dp0"
"%SystemRoot%\System32\WindowsPowerShell\v1.0\powershell.exe" -NoProfile -ExecutionPolicy Bypass -File "%~dp0scripts\start.ps1"

batはASCII、CRLF改行にします。ps1は日本語メッセージを出すならUTF-8 BOM付きにします。詳しくはPowerShellスクリプトとbatランチャーの文字コード・改行コード対策でも扱っています。

サーバーは専用ウィンドウで起動する

ランチャーと同じウィンドウでサーバーを動かすと、ブラウザを開く処理がブロックされます。サーバーは別ウィンドウに出し、タイトルに止め方を書きます。

$inner = @"
`$Host.UI.RawUI.WindowTitle = 'ローカルサーバー(このウィンドウを閉じると停止します)'
Set-Location '$ProjectRoot'
node src/index.js
Write-Host ''
Write-Host 'サーバーが停止しました。このウィンドウは閉じてかまいません。'
"@

Start-Process -FilePath 'powershell.exe' `
  -ArgumentList '-NoProfile', '-ExecutionPolicy', 'Bypass', '-NoExit', '-Command', $inner

-NoExitを付けると、サーバーが即終了してもウィンドウが残り、エラーを読めます。配布先では「一瞬で閉じたので原因不明」が一番困ります。

ポート応答を待ってからブラウザを開く

サーバー起動直後にブラウザを開くと、まだ待ち受け開始前で接続エラーになることがあります。TcpClientでポートが開くまで待ってから開きます。

function Test-PortOpen {
  param([int]$Port)

  $client = New-Object System.Net.Sockets.TcpClient
  try {
    $async = $client.BeginConnect('127.0.0.1', $Port, $null, $null)
    $waited = $async.AsyncWaitHandle.WaitOne(300)
    if ($waited -and $client.Connected) {
      $client.EndConnect($async)
      return $true
    }
    return $false
  } catch {
    return $false
  } finally {
    $client.Close()
  }
}

for ($i = 0; $i -lt 60; $i++) {
  Start-Sleep -Milliseconds 500
  if (Test-PortOpen -Port $Port) { $ready = $true; break }
}

同じ関数で二重起動も検出できます。すでにポートが開いているなら、新しくサーバーを起動せずブラウザだけ開きます。

PowerShell関数の戻り値に出力を混ぜない

PowerShellの関数は、returnした値だけでなく、関数内で出力されたものをすべてパイプラインへ流します。

function Invoke-Npm {
  param([string[]]$Arguments)
  & $npm.Source @Arguments
  return $LASTEXITCODE
}

$code = Invoke-Npm @('install')

この$codeは0ではなく、npmの出力行と終了コードを含む配列になることがあります。その結果、成功しているのに失敗判定になることがあります。

外部コマンドの表示はOut-Hostへ流し、戻り値には終了コードだけを残します。

function Invoke-Npm {
  param([string[]]$Arguments)

  $previous = $ErrorActionPreference
  $ErrorActionPreference = 'Continue'
  try {
    & $npm.Source @Arguments | Out-Host
    return $LASTEXITCODE
  } finally {
    $ErrorActionPreference = $previous
  }
}

$ErrorActionPreference = 'Stop'にしているスクリプトでは、npmの警告が標準エラーへ出るだけで異常終了扱いになることもあります。外部コマンド実行区間だけContinueへ戻すと安全です。

オフライン配布ではNode本体を同梱する

node_modulesだけをZIPに入れても、配布先のNode.jsバージョンが違うとネイティブモジュールが動かないことがあります。better-sqlite3、sharp、canvasのようなNode.js モジュールは、NodeのABIバージョンに依存するためです。

配布先でNode.jsをインストールさせないなら、node.exe本体も同梱します。

配布ZIP
  起動.bat
  node
    node.exe
    NODEJS-LICENSE.txt
  node_modules
  src
  public
  data
  はじめにお読みください.txt

起動スクリプトでは、同梱版を最優先で使います。

function Get-NodeExe {
  $bundled = Join-Path $ProjectRoot 'node\node.exe'
  if (Test-Path $bundled) { return $bundled }

  $found = Get-Command node -ErrorAction SilentlyContinue
  if ($null -ne $found) { return $found.Source }

  return $null
}

配布パッケージでは、起動だけでなく初期化処理もnpm runに依存しないようにします。

$node = Get-NodeExe
& $node 'src/db/seed.js' '--force' | Out-Host

ZIP化は.NETのZipFileを使う

node_modulesはファイル数が多いため、PowerShellのCompress-Archiveでは極端に遅くなることがあります。配布ZIP作成では.NETのZipFileを使うと安定します。

Add-Type -AssemblyName System.IO.Compression.FileSystem
[System.IO.Compression.ZipFile]::CreateFromDirectory(
  $stageDir,
  $zipPath,
  [System.IO.Compression.CompressionLevel]::Optimal,
  $true
)

第4引数を$trueにすると、ZIPの中にトップフォルダが作られます。展開時にファイルが散らばらないので、利用者が扱いやすくなります。

配布検証はNodeなし、ネットなし、ZIP展開後で行う

開発フォルダで動いても、配布物が動く証明にはなりません。最低限、次を確認します。

確認目的
PATHからNode.jsを外す同梱Nodeだけで動くか確認する
ZIPを別フォルダへ展開する開発フォルダ依存を検出する
機内モードにするnpmやCDNへの依存を検出する
git archive相当で展開するDownload ZIP時の改行コード問題を確認する

Node.jsをPATHから外す例です。

$env:PATH = ($env:PATH -split ';' | Where-Object { $_ -notmatch 'nodejs' }) -join ';'
Start-Process -FilePath "$dir\起動.bat" -WorkingDirectory $dir -Wait

期待する表示は、同梱版Nodeを使っていること、サーバーが起動したこと、ブラウザが開けることです。

[OK] Node.js v20.15.0(同梱版を使用。インストール不要)
[OK] サーバーが起動しました。

利用者向けREADMEに書くこと

配布物には、技術説明よりも運用上の注意を短く書きます。

  • USBメモリ上で直接動かさず、PCへコピーしてから起動する
  • Windowsの保護画面が出たら、詳細情報から実行する
  • セキュリティソフトが未知のnode.exeとして警告する可能性がある
  • 停止するときはサーバーウィンドウを閉じる
  • フォルダを削除すればアンインストールできる
  • Node.jsのライセンスファイルを同梱している

まとめ

Node製ローカルアプリをWindowsで配布するときは、アプリ本体だけでなく、起動、停止、復旧、オフライン動作まで含めて設計する必要があります。

  • batは最小限にし、複雑な処理はps1へ寄せる
  • サーバーは専用ウィンドウで起動し、エラーを読めるようにする
  • ポート応答を待ってからブラウザを開く
  • PowerShell関数の戻り値に外部コマンド出力を混ぜない
  • オフライン配布ではNode本体とnode_modulesを同梱する
  • npm依存を起動経路から外す
  • ZIP展開後、Nodeなし、ネットなしで検証する

ここまで確認しておくと、「開発PCでは動いたのに、現場PCでは動かない」という事故を大きく減らせます。

関連記事

この技術で業務改善しませんか?

Excel VBA・GAS・Webアプリで業務の自動化ツールを開発しています。 「こんなことできる?」というご相談だけでもお気軽にどうぞ。

無料相談はこちら →