C++でdism /apply-image相当をAPI実装する方法|WIMGAPI(WIMApplyImage)とERROR_BAD_FORMAT(11)対策

Windows ADK を入れても、C++ から dism /apply-image と同等の処理を「API 呼び出しだけ」で再現しようとすると、DISM API(dismapi.h)にそれらしい関数が見当たらず詰まりがちです。本記事では WIMGAPI(Windows Imaging API)を使った実装の基本フローと、WIMCreateFileERROR_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/属性/タイムスタンプなども復元)
/compactWIM_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 を開く
  • WIMLoadImageindex を指定して対象イメージをロード
  • 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.esdinstall.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 の WIMCreateFileWIMLoadImageWIMApplyImage に寄せるのが最短です。/compactWIM_FLAG_APPLY_COMPACT を適用フラグに付ける形で寄せられます。

そして、WIMCreateFileERROR_BAD_FORMAT(11) で落ちる派生問題は、ファイル破損だけでなく、WIMGAPI(wimgapi.dll)の世代差ビット数/DLL ロードの噛み合わせで起きることが多いです。まずは「どの wimgapi.dll をロードしているか」をログに出し、必要なら ADK 付属 DLL(同ビット数)で再現性を揃えるのが、最も手堅い解決アプローチになります。

この記事を書いた人

実務の現場で詰まりがちなポイントを地図にするITブログ「IT trip」を運営。Windows/Office(Teams・Excel)からSQL、サーバ運用、ガジェットまで、再現性のある手順と“なぜそうなるか”を丁寧に解説します。読んだらすぐ試せること、そして迷った人の次の一歩が見えることを大切にしています。

コメント

コメントする

目次