Windows ADK を入れても、C++ から dism /apply-image と同等の処理を「API 呼び出しだけ」で再現しようとすると、DISM API(dismapi.h)にそれらしい関数が見当たらず詰まりがちです。本記事では WIMGAPI(Windows Imaging API)を使った実装の基本フローと、WIMCreateFile が ERROR_BAD_FORMAT(11) で落ちるときの切り分けを、現場目線で整理します。
結論:dism /apply-image は DISM API ではなく WIMGAPI(WIMApplyImage)の担当
まず押さえるべきポイントは、/apply-image は DISM API の範囲外という点です。DISM API は「オフライン Windows イメージのサービス(追加/削除/設定変更、マウントして編集)」寄りの API が中心で、dism.exe が提供する全コマンドを 1:1 で API 化したものではありません。
そのため、展開(適用)そのものは Windows Imaging API(WIMGAPI) 側の WIMApplyImage を使うのが正攻法です。さらに dism /apply-image ... /compact 相当も、WIMGAPI の「適用フラグ」として扱えます(ヘッダーに定義があるが、ドキュメント上で見つけにくいのが落とし穴です)。
| やりたいこと(DISM コマンド) | 対応する API(WIMGAPI) | メモ |
|---|---|---|
/imagefile:E:\sources\install.esd を開く | WIMCreateFile | .wim / .esd を「WIM コンテナ」として開く(世代差に注意) |
/index:6 を選ぶ | WIMLoadImage | イメージハンドルを取得して以降の操作対象にする |
/applydir:F:\ に展開 | WIMApplyImage | 実体ファイルを展開(ACL/属性/タイムスタンプなども復元) |
/compact | WIM_FLAG_APPLY_COMPACT(適用フラグ) | 適用時に Compact OS 相当の圧縮を有効化 |
DISM API(dismapi.h)で迷子になりやすいポイント
「DismMountImage があるなら DismApplyImage もあるはず」と考えがちですが、DismMountImage は「WIM をマウントして編集する」ためのものです。展開先のボリュームに OS を配置するという /apply-image の用途とは方向性が違います。
結果として、次のような状態になりやすいです。
- DISM API をひたすら探しても
DismApplyImage的な関数が見つからない DismMountImageを使って何とかしようとして、目的と手段がズレる/compactの付け方が分からず「後からcompact.exeを回す」方向に逃げる
ここで一旦割り切り、「適用は WIMGAPI」で設計するとスッキリします。
実装の全体像:WIM を開く → インデックスをロード → 適用
基本フローはシンプルで、概ね次の 4 ステップです。
WIMCreateFile:.wim/.esdを開くWIMLoadImage:indexを指定して対象イメージをロードWIMApplyImage:展開先(applydir)に適用(必要ならWIM_FLAG_APPLY_COMPACT)WIMCloseHandle:取得したハンドルをクローズ
事前準備:ヘッダー/ライブラリと「権限」を揃える
WIMGAPI を使うには、プロジェクトに wimgapi.h をインクルードし、リンクに wimgapi.lib を追加します(Visual Studio ならリンカー入力に追加)。実行時には wimgapi.dll がロードされます。
もう 1 点、見落としがちなのが権限です。OS 展開ではファイル属性や ACL、所有者情報などを復元するため、プロセスに次の権限が必要になるケースがあります。
SeBackupPrivilege(バックアップ権限)SeRestorePrivilege(復元権限)SeSecurityPrivilege(SACL 操作などで必要になる場合)
DISM は内部で必要な権限を整えていますが、自作ツールでは権限不足で apply 中に失敗することがあります。運用前提なら、起動時に AdjustTokenPrivileges で有効化する実装を入れておくと安定します。
最小構成のサンプル(WIMApplyImage で適用する)
以下は「流れが分かる」ことを重視した最小構成例です。プロダクションではログ、権限、例外対策、展開先の事前チェックを厚めにしてください。
#include <windows.h>
#include <wimgapi.h>
#include <string>
static std::wstring FormatWin32Error(DWORD code)
{
wchar_t* buf = nullptr;
const DWORD flags = FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS;
FormatMessageW(flags, nullptr, code, 0, (LPWSTR)&buf, 0, nullptr);
std::wstring msg = buf ? buf : L"";
if (buf) LocalFree(buf);
return msg;
}
bool ApplyWimImage(
const std::wstring& imageFile,
DWORD index,
const std::wstring& applyDir,
bool compact)
{
DWORD creationResult = 0;
// 1) WIM/ESD を開く
HANDLE hWim = WIMCreateFile(
imageFile.c_str(),
WIM_GENERIC_READ,
OPEN_EXISTING,
0, // flags (必要なら WIM_FLAG_VERIFY など)
0, // compression (読み取り用途なら 0 でよいことが多い)
&creationResult);
if (!hWim)
{
DWORD err = GetLastError();
// err==11(ERROR_BAD_FORMAT) のときは本文の切り分けへ
OutputDebugStringW((L"WIMCreateFile failed: " + std::to_wstring(err) + L" " + FormatWin32Error(err) + L"\n").c_str());
return false;
}
// 2) index をロード
HANDLE hImage = WIMLoadImage(hWim, index);
if (!hImage)
{
DWORD err = GetLastError();
OutputDebugStringW((L"WIMLoadImage failed: " + std::to_wstring(err) + L" " + FormatWin32Error(err) + L"\n").c_str());
WIMCloseHandle(hWim);
return false;
}
// 3) apply(/compact 相当は適用フラグに付与)
DWORD applyFlags = 0;
if (compact)
applyFlags |= WIM_FLAG_APPLY_COMPACT;
if (!WIMApplyImage(hImage, applyDir.c_str(), applyFlags))
{
DWORD err = GetLastError();
OutputDebugStringW((L"WIMApplyImage failed: " + std::to_wstring(err) + L" " + FormatWin32Error(err) + L"\n").c_str());
WIMCloseHandle(hImage);
WIMCloseHandle(hWim);
return false;
}
// 4) クローズ
WIMCloseHandle(hImage);
WIMCloseHandle(hWim);
return true;
}
この形で dism /apply-image /imagefile:... /index:... /applydir:... の核となる「適用」自体は再現できます。次に、現場で役立つ実装要素(進捗、テンポラリ、失敗しやすいポイント)を足していきます。
より “DISM っぽく” するための実装ポイント
進捗表示:コールバック登録で WIMApplyImage の状態を追う
展開は数分以上かかることがあり、ユーザー向け/運用向けツールでは進捗表示が重要です。WIMGAPI にはメッセージコールバックがあり、適用中の進捗やエラー通知を受け取れます。
設計のポイントは次の通りです。
- 標準出力に % を出すだけでも運用性が上がる(ログ収集が楽)
- キャンセル機構を入れたい場合、コールバック側でフラグを見て中断要求を返す
- 実際のメッセージ種別は環境/操作で変動するため、最初は「全部ログに吐く」実装が強い
テンポラリの置き場:WIMSetTemporaryPath を速いディスクに
適用処理はテンポラリ領域を使うことがあります。遅いドライブ(USB、ネットワーク、空きの少ない C: など)にテンポラリが置かれると、体感で分かるレベルで遅くなります。運用環境が固定されているなら、SSD 上の作業ディレクトリなどを明示するのがおすすめです。
| 設定 | 狙い | 現場での効果 |
|---|---|---|
WIMSetTemporaryPath を使う | テンポラリを高速/十分な空きがある場所へ誘導 | 展開時間の短縮、途中失敗(空き不足)の回避 |
| テンポラリの容量監視 | 事前に失敗条件を潰す | 夜間バッチや大量展開で事故りにくい |
/compact 相当:WIM_FLAG_APPLY_COMPACT を “適用フラグ” として付与する
dism /apply-image ... /compact を同等にしたい場合、ポイントは適用時のフラグであることです。実装上は WIMApplyImage の第 3 引数(適用フラグ)に WIM_FLAG_APPLY_COMPACT を OR します。
「後から compact.exe で圧縮すればいいのでは?」という疑問もありますが、適用時 compact と事後 compact は運用上の性格が少し違います。
| 方式 | メリット | デメリット/注意点 | 向いている場面 |
|---|---|---|---|
適用時に WIM_FLAG_APPLY_COMPACT | 最初から “狙った形” で展開できる/工程が一つ減る | WIMGAPI の世代差に影響される可能性 | 大量展開、工場出荷イメージ、再現性重視 |
適用後に compact.exe を実行 | API 側が対応していなくても逃げ道になる | 時間が追加でかかる/対象選定・除外が必要になることがある | 既存手順への後付け、互換性優先 |
install.esd を扱うときの実務的注意点
インストールメディアの install.esd は、見た目は “ESD” ですが中身は WIM 系コンテナです。ただし、圧縮方式や機能のサポートが WIMGAPI の世代に依存しやすく、ここが後述の ERROR_BAD_FORMAT(11) に直結することがあります。
実務では次の考え方が安定します。
- 同じファイルで
dism.exewimgapi.dll が古い/別物の可能性がある .esdを直接扱うのが不安なら、まずdism /export-imageで.wimに変換してから API 適用する(切り分けが速い)- 運用ツールとしては「どの DLL をロードしたか」をログに残すと事故解析が一気に楽になる
WIMCreateFile が NULL、ERROR_BAD_FORMAT (11) になるときの切り分け
ここからがハマりどころです。WIMCreateFile(L"E:\\sources\\install.esd") が NULL を返し、GetLastError() が ERROR_BAD_FORMAT (11)。しかし同じファイルで dism /apply-image は成功する――このケースは、「ファイルが壊れている」以外の理由が普通にあります。
BAD_FORMAT は “何が” BAD なのか
このエラーは名前が強すぎて誤解されがちですが、現場で遭遇しやすい原因は大きく分けて次の 2 系統です。
| 系統 | ありがちな原因 | 見え方 | 最短の確認方法 |
|---|---|---|---|
| ファイル解釈の失敗 | ESD 圧縮方式が古い WIMGAPI で未対応、メディア差分、部分破損 | WIMCreateFile 直後に失敗/DISM は成功することがある | 別の wimgapi.dll(ADK 付属など)で試す、WIM へ export して試す |
| 実行環境(DLL/ビット数/ロード)の問題 | x86/x64 の噛み合わせ、ロードしている DLL が想定外、依存 DLL 欠落 | 同じソースでもマシンや配置で再現したり消えたりする | 「ロードされた wimgapi.dll の実体パス」をログ出しして確認 |
質問の状況(DISM は成功するが自作 API は失敗)だと、特に「WIMGAPI の世代差」か「ロードしている DLL の想定違い」が疑わしいです。
まずやる:自分のプロセスが “どの wimgapi.dll” をロードしているか確認
ADK を入れていても、DLL の探索順序の都合で System32 側が優先されたり、アプリの配置場所に置いた DLL が拾われたりします。ここを疑わずに調査すると遠回りになりがちです。
ログに次の情報を出してください。
- 自プロセスが 32bit / 64bit のどちらで動いているか
- ロードされた
wimgapi.dllのフルパス - (可能なら)ファイルバージョン
例として、DLL の実体パスだけでも取れると切り分けが進みます。
HMODULE h = GetModuleHandleW(L"wimgapi.dll");
if (h) {
wchar_t path[MAX_PATH]{};
GetModuleFileNameW(h, path, MAX_PATH);
// path をログへ
}
ここで「思っていた場所と違う DLL」が出てきたら、原因にかなり近づきます。
よくある罠:x86 でビルドした EXE が “別ビット数の DLL” を拾っている
質問文でも触れられている通り、ERROR_BAD_FORMAT はビット数の噛み合わせで出やすい典型例です。具体的には次のパターンが多いです。
- x86 でビルドした EXE の近くに、ADK の amd64 用
wimgapi.dllを置いてしまう - x64 アプリなのに、PATH の順序で x86 側の DLL を拾う
- 依存 DLL(同梱が必要な関連 DLL)が欠けていて、ロードが連鎖的に失敗する
対策はシンプルで、EXE と同じビット数の wimgapi.dll を使うことです。運用ツールなら、最初から x64 に寄せておくと事故が減ります。
“DISM は成功する” のに “自作は失敗する” ときは DLL の世代差が濃厚
DISM は OS 付属のものだけで動いているとは限らず、環境によっては ADK 側のコンポーネントや別の実装を使っているように見えることがあります。結果として、同じ install.esd でも次が起きます。
dism.exe:ESD を問題なく開いて適用できる- 自作アプリ:ロードしている
wimgapi.dllが古く、ESD の仕様差でWIMCreateFileが落ちる
この疑いが強いときの実務的な近道は、ADK 付属の wimgapi.dll(同ビット数)で試すことです。質問でも「24H2 では再現しない」「24H2 以外なら ADK 側を試す」という話が出ていますが、まさにこの方向性です。
ADK 側の DLL を使うときの注意:DLL 探索順序とセキュリティ
「PATH の先頭に ADK のフォルダを入れる」「EXE の隣に DLL を置く」でも動く場合がありますが、運用/セキュリティを考えると、意図しない DLL を拾うリスクが残ります。アプリ側で制御するなら、次の考え方がおすすめです。
- DLL をフルパス指定でロードする(
LoadLibraryEx) - ロードディレクトリを限定する(
SetDefaultDllDirectories/AddDllDirectoryを検討) - ロード結果(実体パス)を必ずログに残す
配布用途の場合は、ADK 付属 DLL の再配布可否や運用上の制約も確認してください(社内ツールなら許容されることが多い一方、一般配布だと条件が変わります)。
切り分けの王道:いったん ESD を WIM に変換して挙動を見る
「DLL の世代差」なのか「ファイル自体」なのかを早く分けたいなら、いったん install.esd を install.wim にエクスポートして、同じコードで開けるか確認します。WIM で開けるなら、ESD 対応(圧縮方式など)が怪しいという判断ができます。
dism /export-image /sourceimagefile:E:\sources\install.esd /sourceindex:6 /destinationimagefile:E:\install.wim /compress:max
この方法は “API だけで完結したい” という理想からは外れますが、現場では原因特定の速度が圧倒的に上がるため、最初の切り分けとしては非常に強いです。
未ドキュメントのフラグ(例:0x20000000)を渡す案について
質問にある「未ドキュメントのフラグ(値 0x20000000)を flags に渡して試す」というアイデアは、たしかに環境依存の回避策として提示されることがあります。ただし、これは次のリスクを伴います。
- 仕様が公開されていないため、将来の Windows/ADK 更新で挙動が変わる可能性がある
- 期待と違う動作(検証回避、互換モード、内部パス変更など)を引き起こす可能性がある
- 運用ツールとして再現性が落ち、障害解析が難しくなる
どうしても試すなら、フラグの有無をスイッチで切り替えられるようにし、ログに「有効化した事実」を必ず残すのが最低ラインです。恒久対策としては、まず「ロードしている DLL の世代」と「ESD 対応の有無」を正攻法で合わせることをおすすめします。
ERROR_BAD_FORMAT(11) で詰まったときのチェックリスト
最後に、現場で効いた順にチェック項目をまとめます。運用手順書にそのまま貼れる粒度にしています。
| チェック | 確認方法 | 狙い |
|---|---|---|
| プロセスのビット数 | x86/x64 のどちらでビルドしているか、実行中プロセスを確認 | DLL 噛み合わせ不具合の早期排除 |
ロードされた wimgapi.dll の実体パス | GetModuleFileName でログ出力 | 「想定外の DLL」を拾っていないか確認 |
同ビット数の ADK 付属 wimgapi.dll で再試行 | DLL ロード順序を制御して切り替える | WIMGAPI 世代差(ESD 対応差)の切り分け |
| ESD → WIM へ export して再試行 | dism /export-image | 「ESD 固有」問題かを分離 |
| 展開先ボリュームの形式 | NTFS か、十分な空きがあるか | ACL/属性復元で詰まる環境要因の排除 |
| 権限(SeRestore/SeBackup) | 管理者権限+トークン権限の有効化 | apply 中の不可解な失敗を減らす |
まとめ:API 実装の最短ルートは「WIMApplyImage」、失敗時は「どの DLL を読んでいるか」から
dism /apply-image 相当を C++ から API で実装したいなら、DISM API を探し続けるよりも、WIMGAPI の WIMCreateFile → WIMLoadImage → WIMApplyImage に寄せるのが最短です。/compact も WIM_FLAG_APPLY_COMPACT を適用フラグに付ける形で寄せられます。
そして、WIMCreateFile が ERROR_BAD_FORMAT(11) で落ちる派生問題は、ファイル破損だけでなく、WIMGAPI(wimgapi.dll)の世代差やビット数/DLL ロードの噛み合わせで起きることが多いです。まずは「どの wimgapi.dll をロードしているか」をログに出し、必要なら ADK 付属 DLL(同ビット数)で再現性を揃えるのが、最も手堅い解決アプローチになります。

コメント