1. はじめに — なぜ exe 化が必要なのか
製造業の現場 PC には、たいてい Python がインストールされていません。Windows 10 / 11 がプリインストールされた業務用 PC であり、勝手に python.exe を入れるのは情報システム部門のポリシーで禁止されているケースも多いはずです。
こうした環境に Python アプリを届ける現実解が、PyInstaller での .exe 化です。スクリプトと依存ライブラリを 1 つの実行ファイル(または 1 つのフォルダ)にまとめ、Python 環境がなくても動くようにします。
とはいえ、開発機で動いた exe が現場 PC では動かない——これは PyInstaller を業務で使う人なら誰しも一度は遭遇する罠です。本記事では、「動く exe を作る」と「現場で運用に耐える exe を配る」の間にある具体的な差を埋めます。
2. exe 化の最小手順
まずは最小構成を確認します。これだけで、ほとんどのスクリプトは動く exe になります。
2.1 PyInstaller のインストール
python -m pip install pyinstaller
仮想環境(venv)に入れることを強く推奨します。グローバル Python に入れると、依存ライブラリの取り込みでバージョン衝突を起こすことがあります。
2.2 1 ファイル exe の生成
pyinstaller --onefile --noconsole app.py
完了すると、dist/app.exe が生成されます。--onefile は単一ファイル化、--noconsole は GUI アプリ用に DOS 窓を抑止するオプションです。コンソールアプリの場合は --noconsole を外してください。
2.3 1 フォルダ exe の生成(推奨)
pyinstaller --noconsole app.py
--onefile を外すと、dist/app/ 以下に exe と依存ファイルがフォルダごと展開されます。実は、現場運用では 1 フォルダ形式のほうが扱いやすい場面が多くあります。
- 起動が速い:
--onefileは実行のたびに一時フォルダへ展開するため、起動に数秒かかる - ウイルス対策ソフトと相性が良い: 単一 exe は誤検知されやすい。フォルダ形式だと中身が見えるため誤検知が減る傾向
- 差分更新がしやすい: アプリ本体だけ差し替え、Qt や numpy の DLL は据え置き、といった配布が可能
3. 製造現場特有の落とし穴と対策
動く exe を作るところまでは公式ドキュメント通りで進みます。ここからが、現場固有の問題です。
3.1 CP932(Shift_JIS)絡みの文字化け
Windows の現場 PC は、いまだに既定の文字コードが CP932(Shift_JIS の Microsoft 拡張)であるケースが大半です。Python の標準入出力・ログファイル・ファイル読み書きで文字コードを明示しないと、現場で文字化けや UnicodeDecodeError が頻発します。
対策の基本:
- ログファイルは
open(path, "w", encoding="utf-8")のように明示 - 環境変数
PYTHONUTF8=1を設定して Python 自体を UTF-8 モードで動かす(後述の.batランチャーで設定) - 外部から渡される CSV / 設定ファイルは、UTF-8 / CP932 / UTF-8 with BOM のいずれもありえると想定し、デコード側で柔軟に扱う
このトピックは深いので、別記事に体系的にまとめています。
3.2 ウイルス対策ソフトでの誤検知
PyInstaller でビルドした exe は、Microsoft Defender や法人向けセキュリティソフトに「未知のプログラム」として警告される、最悪の場合自動削除される、という事例がよくあります(PyInstaller 公式の動作仕様も参照)。
対策:
- 1 フォルダ形式で配る: 単一 exe より誤検知率が下がる
- UPX 圧縮を切る:
--noupxオプションを付ける(誤検知の主原因の 1 つ) - 署名する: コード署名証明書を取得して exe に署名(中長期で運用するなら投資価値あり)
- 情シスに事前申請: 法人ポリシーで未署名 exe が拒否される環境では、配布前に IT 部門に許可を取る
3.3 隠れた依存ファイル(hidden imports / data files)
PyInstaller は静的解析で依存を辿りますが、動的 import(importlib 経由、プラグイン構造など)は検出できません。また、テンプレートや CSV など「コードでは import していないがランタイムで読み込むファイル」は自動では含まれません。
対策: 明示的に指定します。
pyinstaller --noconsole \
--hidden-import pymodbus.transaction \
--add-data "config.yaml;." \
--add-data "templates;templates" \
app.py
多数の指定が必要になったら、.spec ファイル(PyInstaller の設定ファイル)に切り出すと管理しやすくなります。pyinstaller コマンドは初回実行時に app.spec を生成するので、それを編集して以降は pyinstaller app.spec でビルドするフローが推奨です。
3.4 ファイルパス(__file__)の罠
PyInstaller でバンドルした実行時、__file__ は意図しない場所(--onefile なら一時展開フォルダ)を指します。設定ファイルや出力先を「exe と同じディレクトリ」に置きたい場合は、sys.executable を基準にする必要があります。
import sys
from pathlib import Path
def app_dir() -> Path:
"""exe 実行時は exe があるフォルダ、開発時はスクリプトのフォルダを返す。"""
if getattr(sys, "frozen", False):
return Path(sys.executable).resolve().parent
return Path(__file__).resolve().parent
CONFIG_PATH = app_dir() / "config.yaml"
LOG_DIR = app_dir() / "logs"
sys.frozen は PyInstaller でビルドされた状態で True になります。これを基準に分岐するのが定番です。
3.5 起動時の謎エラー(DLL 不足、VC++ ランタイム)
「開発機では動くのに、現場 PC で起動した瞬間にエラー、もしくは何も起きない」というケース。原因の多くは、開発機にだけ入っている Visual C++ 再頒布可能パッケージや、特定バージョンの DLL に依存しているライブラリです。
対策: 「現場 PC を模した環境」での動作確認を必ず行います。
- クリーンインストール直後の Windows 仮想マシンを 1 台用意し、配布物を置いて起動するだけのテストを毎回行う
- VC++ 再頒布可能パッケージ(最新版)を配布物の
install/に同梱し、初回セットアップで入れてもらう - 起動失敗時にログを残せるよう、
.batランチャー経由で起動する設計にする(次節)
4. .bat ランチャーで起動を堅牢にする
exe を直接ダブルクリックさせるのではなく、.bat 経由で起動する設計にすると、現場運用が一気に楽になります。
4.1 ランチャー .bat のサンプル
@echo off
chcp 65001 > nul
setlocal
REM UTF-8 モードと作業ディレクトリ
set PYTHONUTF8=1
cd /d "%~dp0"
REM ログフォルダ
if not exist logs mkdir logs
REM 起動(標準出力・標準エラーともログへ)
"%~dp0app\app.exe" 1>> "logs\stdout.log" 2>> "logs\stderr.log"
if errorlevel 1 (
echo [%date% %time%] app exited with errorlevel %errorlevel% >> logs\stderr.log
exit /b %errorlevel%
)
endlocal
ポイント:
chcp 65001でコンソールを UTF-8 に。> nulは出力を抑止%~dp0は.batファイルが置かれたフォルダ。常にここを基準にすることで「ショートカットから起動した」「別フォルダから呼ばれた」場合でも作業ディレクトリが安定- 標準出力・標準エラーを別ファイルに記録。「現場で何も起こらず終了した」現象の原因はだいたいここに残ります
--noconsoleでビルドしている場合、stderr.logに Python のトレースバックが出ないこともあります。その場合はアプリ側でloggingをファイルに向けておく設計が重要
4.2 自動再起動付きランチャー
監視アプリのように 24 時間動かし続けたい場合、異常終了時に自動再起動するランチャーが便利です。
@echo off
chcp 65001 > nul
setlocal
set PYTHONUTF8=1
cd /d "%~dp0"
if not exist logs mkdir logs
:loop
"%~dp0app\app.exe" 1>> "logs\stdout.log" 2>> "logs\stderr.log"
echo [%date% %time%] restarted (errorlevel %errorlevel%) >> logs\stderr.log
timeout /t 5 /nobreak > nul
goto loop
無限再起動はアプリ側のバグで暴走する危険もあるため、本番では「N 回連続で失敗したら停止」のようなガード(カウンタ管理)を加えてください。あるいは Windows のタスクスケジューラから「失敗時に再実行」する方法でも代用できます。
5. 管理者権限の制御
レジストリ書き込みや一部の COM デバイスアクセスなど、管理者権限が必要な処理を含むアプリでは、起動時に UAC(ユーザーアカウント制御)プロンプトを出す必要があります。
方法 1: PyInstaller の --uac-admin オプション
pyinstaller --noconsole --uac-admin app.py
マニフェストに「常に管理者として実行」が埋め込まれ、起動のたびに UAC プロンプトが出るようになります。
方法 2: 自前のマニフェストファイル
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v3">
<security>
<requestedPrivileges>
<requestedExecutionLevel level="requireAdministrator" uiAccess="false"/>
</requestedPrivileges>
</security>
</trustInfo>
</assembly>
このマニフェストを app.manifest として保存し、.spec ファイル経由で組み込む方法もあります。本当に管理者権限が必要かは慎重に判断してください。多くの業務アプリは「ユーザー領域に書き込む」設計に変えれば管理者権限なしで運用できます。常時 UAC プロンプトが出るアプリは、現場の心理的負担が大きいです。
6. アップデート配布の戦略
exe 化したアプリは、リリース後にバグ修正や機能追加が発生します。「現場の 30 台にどうやって新版を届けるか」という現実問題を、運用開始前に決めておくべきです。
6.1 共有フォルダ方式
社内のファイルサーバーに最新バージョンを置き、ランチャー .bat で「ローカル版とサーバー版を比較し、新しければコピー → 起動」というフローにします。
@echo off
setlocal
set REMOTE=\\fileserver\apps\monitor
set LOCAL=%~dp0app
REM タイムスタンプを比較して新しければ更新
robocopy "%REMOTE%" "%LOCAL%" /MIR /XO /R:1 /W:1 /NFL /NDL /NJH /NJS > nul
REM 起動
"%LOCAL%\app.exe"
endlocal
robocopy /XO は「コピー先より新しいファイルだけ上書き」のオプションです。シンプルですが、ファイルサーバーが落ちたら起動できなくなる依存リスクはあります。
6.2 バージョン情報を付ける
アプリ起動時に「バージョン番号」をログとタイトルバーに必ず出すようにしておくと、現場からの問い合わせ時に「どの版で起きた問題か」が即特定できます。
__version__ = "1.4.0"
logger.info("starting app version %s", __version__)
root.title(f"監視アプリ v{__version__}")
現場で「起動しないんだけど」と相談された際に、まず聞くべきは「バージョンは何ですか」です。タイトルバーに出ていればこの確認が一瞬で済みます。
6.3 設定ファイルとアプリ本体を分離する
現場ごとに異なる設定(IP アドレス、しきい値、保存先パス)を config.yaml や config.ini に切り出しておくと、アップデート時に設定が消えるトラブルを避けられます。
- アプリ本体:
app/フォルダ — 上書き更新 - 設定ファイル:
config/フォルダ — 上書きしない - ログ・データ:
logs/,data/— 上書きしない
このように更新対象と保持対象を物理的に分けるのが、運用ミスを防ぐ最も効果的な方法です。
7. 現場配布チェックリスト
本記事の内容を、配布前のチェックリストとしてまとめます。
- クリーンな Windows 環境(仮想マシン推奨)で動作確認した
- 1 フォルダ形式・
--noupxでビルドし、UPX 由来の誤検知リスクを避けた - UTF-8 モード(
PYTHONUTF8=1)を.batで設定した - 標準出力・標準エラー・アプリログがファイルに残る設計にした
sys.frozen分岐で実行ファイル基準のパス取得を実装した- バージョン番号をログとタイトルに出している
- 設定ファイルとアプリ本体を物理的に別フォルダに分けた
- アップデート手順(誰がいつどう配るか)を文書化した
- 異常終了時の復旧フロー(再起動、問い合わせ先、ログ送付方法)を現場に共有した
8. おわりに
PyInstaller 自体の使い方は、公式ドキュメントとブログ記事が大量にあります。しかし、「現場 PC に配って長期運用する」段階で必要になる知識は、断片的にしか出回っていません。本記事は、Windows の現場 PC に Python アプリを長期運用してきた経験を、自宅環境で再現可能な形に一般化したものです。
「動く exe」と「現場で頼られる exe」の差は、設計の細部に宿ります。配布前のチェックリストを 1 度通すだけで、初動トラブルの大半は防げます。