「Google Meetで画面共有した直後から、特定PCだけ自作のWebView2アプリ(.exe)が起動即落ちする」「デバッガは“WebView2 Runtime 初期化失敗”、でもランタイムは“インストール済み”と出る」――この厄介な現象を、最短で復旧させ再発も防ぐための実践ノウハウを、原因の本質・切り分け・修復・恒久対策まで一気通貫で解説します。
なにが起きているのか(症状の整理)
- Google Meetで画面共有を行った“以降”、当該PCだけでWebView2ベースの.exeが起動直後にクラッシュ(ウィンドウが一瞬で消える)。
- デバッガ/ログには「WebView2 Runtime 初期化失敗」、あるいは
environment create failed・shell process failed等が残る。 - 同じバイナリを他PCで実行すると正常動作(=環境依存)。
- WebView2 Runtime は「インストール済み」表示で、再起動や再ログオンでは直りにくい。
結論(最頻原因):ユーザーデータフォルダー(UDF)の破損
WebView2 はユーザー単位のプロファイル(ユーザーデータフォルダー=UDF)を既定で %LOCALAPPDATA%\{アプリ名}\EBWebView 以下に保持します。画面共有時にGPU/レンダラープロセスが不意にクラッシュ・強制終了すると、このプロファイル内部(例えば LevelDB・Origin Trial・Service Worker キャッシュ等)が中途半端な状態で残り、次回起動で同じ壊れたプロファイルを再利用しにいきます。その結果、環境生成(CoreWebView2Environment.CreateAsync)やブラウザプロセス起動が失敗し、即時終了という挙動に繋がります。
破損はユーザー単位で発生するため、他PCや他ユーザーでは再現しません。逆に言えば、UDFをクリーン化すると復旧するケースが圧倒的多数です。
まず試すべき「3ステップ」切り分け
ステップ1:ランタイム単体の起動確認
まずはWebView2 Runtime自体が壊れていないかを最短で切り分けます。
"%ProgramFiles(x86)%\Microsoft\EdgeWebView\Application\*\msedgewebview2.exe" --version
- バージョン文字列が表示されれば、ランタイム本体は概ね正常。
- 異常終了・無反応なら、ランタイム修復へ(後述)。
ステップ2:新規UDFでの起動試験(決定打)
既存UDFを使わず、新規の空ディレクトリをUDFとして指定して起動できるかを確認します。
var freshDir = Path.Combine(
Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
"MyApp_FreshUDF");
Directory.CreateDirectory(freshDir);
// 既存の WebView2 コントロールを webView2 と仮定
var env = await CoreWebView2Environment.CreateAsync(
browserExecutableFolder: null,
userDataFolder: freshDir,
options: null);
await webView2.EnsureCoreWebView2Async(env); </code></pre>
<ul>
<li>これで<strong>正常起動</strong>するなら、<strong>元のUDF破損が確定</strong>です(修復方法へ)。</li>
<li>それでも失敗する場合は、権限やウイルス対策ソフト、ドライバ、ランタイム側問題を疑います。</li>
</ul>
<h3>ステップ3:例外とHRESULTで当たりを付ける</h3>
<p>例外から <code>HResult</code> を採取すると、問題の性質が浮かびます。</p>
<pre><code class="language-csharp">try
{
var env = await CoreWebView2Environment.CreateAsync(null, udfPath);
await webView2.EnsureCoreWebView2Async(env);
}
catch (System.Runtime.InteropServices.COMException ex)
{
// ログへ書き出す
Debug.WriteLine($"WebView2 init failed. HRESULT=0x{ex.HResult:X8} Message={ex.Message}");
}
catch (Exception ex)
{
Debug.WriteLine($"Unexpected: {ex}");
}
</code></pre>
<table>
<thead>
<tr><th>HRESULT</th><th>よくある意味</th><th>疑うべきポイント</th><th>初手</th></tr>
</thead>
<tbody>
<tr><td><code>0x80070002</code></td><td>ファイル/ディレクトリ欠落</td><td>UDF内部の破損・欠落</td><td>UDF削除/リネーム</td></tr>
<tr><td><code>0x80070005</code></td><td>アクセス拒否</td><td>UDFの権限/ロック・AV干渉</td><td>プロセス停止&権限確認</td></tr>
<tr><td><code>0x80004005</code></td><td>不明な失敗</td><td>UDF破損/ドライバ/ポリシー</td><td>UDF刷新→GPU無効で再試験</td></tr>
<tr><td><code>0x80040154</code></td><td>クラス未登録</td><td>ランタイム壊れ/未インストール</td><td>ランタイム修復/再配布</td></tr>
<tr><td><code>0x80070020</code></td><td>共有違反</td><td>プロセス残骸/ロック</td><td>プロセスKILL→再試</td></tr>
</tbody>
</table>
<h2>最短復旧:推奨順の実際的な対処</h2>
<h3>1) 壊れたUDF(EBWebView)を削除またはリネーム</h3>
<ol>
<li>対象アプリを完全終了(タスクトレイ/バックグラウンドも)</li>
<li><code>%LOCALAPPDATA%\{アプリ名}\EBWebView</code> を<strong>リネーム</strong>(安全)または削除</li>
<li>アプリを再起動(クリーンUDFが自動生成)</li>
</ol>
<p>自動化するなら以下が便利です。</p>
<pre><code class="language-powershell">$udf = Join-Path $env:LOCALAPPDATA "MyApp\EBWebView"
Stop-Process -Name msedgewebview2 -Force -ErrorAction SilentlyContinue
if (Test-Path $udf) {
$stamp = Get-Date -Format "yyyyMMdd-HHmmss"
Rename-Item $udf "$udf.broken.$stamp"
}
</code></pre>
<pre><code class="language-batch">@echo off
set "UDF=%LOCALAPPDATA%\MyApp\EBWebView"
taskkill /IM msedgewebview2.exe /F >nul 2>&1
if exist "%UDF%" ren "%UDF%" "EBWebView.broken.%RANDOM%"
</code></pre>
<h3>2) WebView2 Runtime を修復/再インストール</h3>
<ul>
<li>コントロールパネル →「Microsoft Edge WebView2 Runtime」→「変更」→「修復」</li>
<li>管理者権限があるなら、再配布パッケージ(Evergreenオフライン)で再セットアップ</li>
<li>コマンド派なら:
<pre><code class="language-powershell"># 表示名は環境によって異なる場合あり
winget list "WebView2"
# 強制再インストール例
winget install --id Microsoft.EdgeWebView2Runtime -e --force
3) 固定バージョン(Fixed Version)を同梱して安定化
Google Meet 等により端末のWebView2ランタイムが更新されても、アプリは想定バージョンで固定できるよう、アプリ配布物に固定版ランタイムを同梱します。
MyApp\Runtime\WebView2Fixed\<version>\に固定版を配置- 起動時に
browserExecutableFolderへそのパスを渡す
var fixedRuntime = Path.Combine(AppContext.BaseDirectory, "Runtime", "WebView2Fixed", "120.0.0.0");
var env = await CoreWebView2Environment.CreateAsync(
browserExecutableFolder: fixedRuntime,
userDataFolder: udfPath,
options: null);
await webView2.EnsureCoreWebView2Async(env);
環境変数を使うパターンもあります。
Environment.SetEnvironmentVariable("WEBVIEW2_BROWSER_EXECUTABLE_FOLDER", fixedRuntime);
Environment.SetEnvironmentVariable("WEBVIEW2_CHANNEL_SEARCH_KIND", "0"); // Fixedを最優先
4) 一時回避としてGPUを無効化
GPUドライバ起因のクラッシュが疑わしい場合、まずは回避できるかを確認します。
var opts = new CoreWebView2EnvironmentOptions
{
AdditionalBrowserArguments = "--disable-gpu"
};
var env = await CoreWebView2Environment.CreateAsync(null, udfPath, opts);
await webView2.EnsureCoreWebView2Async(env);
この状態で安定するなら、後日GPUドライバの更新やOS更新を行い、フラグを外して描画性能を戻します。
診断を加速する補助ワザ
イベントログ/プロセス確認
- イベント ビューアーのアプリケーションログで、アプリの例外と
msedgewebview2.exeのクラッシュイベント(アプリケーション エラー)を確認。 - プロセス残骸がUDFを握っていないか:
Get-Process msedgewebview2 -ErrorAction SilentlyContinue | Stop-Process -Force
レジストリからランタイム版数を目視
reg query "HKCU\Software\Microsoft\EdgeWebView\BLBeacon" /v version
インストーラ観点の整合性確認に役立ちます。
恒久対策:アプリ側の「自動復旧」設計
ユーザーに手作業でフォルダー削除をお願いしなくても、アプリが自動で立て直せるようにしておくと運用コストが激減します。
パターンA:UDFの自動リトライ&サニタイズ
async Task InitWebView2WithSelfHealingAsync(WebView2 webView2, string udfBase)
{
var udf = Path.Combine(udfBase, "EBWebView");
var opts = new CoreWebView2EnvironmentOptions();
for (var attempt = 1; attempt <= 2; attempt++)
{
try
{
var env = await CoreWebView2Environment.CreateAsync(null, udf, opts);
await webView2.EnsureCoreWebView2Async(env);
return; // 成功
}
catch (System.Runtime.InteropServices.COMException ex) when (
ex.HResult == unchecked((int)0x80070002) || // Not found
ex.HResult == unchecked((int)0x80070005) || // Access denied
ex.HResult == unchecked((int)0x80004005) // Unspecified
)
{
// 初回だけサニタイズ
if (attempt == 1 && Directory.Exists(udf))
{
var broken = udf + ".broken." + DateTime.Now.ToString("yyyyMMdd-HHmmss");
Directory.Move(udf, broken);
continue; // 新規UDFで再トライ
}
throw; // 2回目もダメなら上位に任せる
}
}
}
パターンB:アプリ バージョンごとにプロファイル分離
ランタイム更新やスキーマ不整合の影響を局所化するため、UDFをアプリ版数でサブフォルダー分離します。
var appVer = typeof(App).Assembly.GetName().Version!.ToString();
var udf = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
"MyApp", "EBWebView", "app-" + appVer);
var env = await CoreWebView2Environment.CreateAsync(null, udf);
await webView2.EnsureCoreWebView2Async(env);
パターンC:ProcessFailedを監視してユーザー通知
webView2.CoreWebView2InitializationCompleted += (_, e) =>
{
if (e.IsSuccess)
{
webView2.CoreWebView2.ProcessFailed += (_, args) =>
{
// GPU/レンダラ/ブラウザプロセスのどれが落ちたか
var kind = args.ProcessFailedKind.ToString();
var exitCode = args.ExitCode;
Log($"ProcessFailed Kind={kind} ExitCode={exitCode}");
// 連続発生なら UDF リセットを促すUIを出す、等
};
}
}; </code></pre>
<h2>Google Meet(画面共有)と相性が悪くなる背景</h2>
<p>画面共有時は描画パスやエンコーダーの負荷が上がり、GPUドライバのリセット(TDR)・電源管理の遷移・仮想デスクトップ複製などが重なることがあります。WebView2のGPU/レンダラープロセスが<strong>予期しないシャットダウン</strong>を食らうと、UDF内のファイルが<strong>書き換え途中のまま</strong>残存し、次回起動で環境作成が失敗しやすくなります。つまり、<strong>画面共有は「直接の原因」ではなく、破損を誘発しやすいトリガー</strong>として働く、と捉えると整理しやすいです。</p>
<h2>現場で役立つ「一枚表」</h2>
<table>
<thead>
<tr>
<th>現象</th>
<th>主原因の目安</th>
<th>優先アクション</th>
<th>代替/補助</th>
</tr>
</thead>
<tbody>
<tr>
<td>起動直後に即終了</td>
<td>UDF破損</td>
<td>EBWebViewの削除/リネーム</td>
<td>新規UDF指定で試験</td>
</tr>
<tr>
<td>初期化でHRESULT=0x80070005</td>
<td>権限/ロック/AV</td>
<td>プロセス停止・権限修正</td>
<td>一時的にリアルタイム保護除外</td>
</tr>
<tr>
<td>Meet後のみ不安定</td>
<td>GPUドライバ</td>
<td>--disable-gpuで回避</td>
<td>ドライバ更新・OS更新</td>
</tr>
<tr>
<td>別PCでは正常</td>
<td>ユーザー/端末依存</td>
<td>UDF/ドライバ/AVの切り分け</td>
<td>固定版ランタイム同梱</td>
</tr>
</tbody>
</table>
<h2>運用チェックリスト(そのまま現場で使える)</h2>
<ul>
<li>① ランタイム単体の <code>--version</code> 表示テストはOKか</li>
<li>② 新規UDFで起動できるか(できれば破損確定)</li>
<li>③ HRESULTを採取し、UDF/権限/ドライバのどれかに当たりを付けたか</li>
<li>④ EBWebViewをリネームして正常化したか</li>
<li>⑤ 修復でも不安定なら固定版ランタイムで安定するか</li>
<li>⑥ 画面共有が絡むなら <code>--disable-gpu</code> で安定するか</li>
<li>⑦ 自動復旧(UDFサニタイズ)をアプリに実装したか</li>
</ul>
<h2>よくある勘違い/補足知識</h2>
<ul>
<li><strong>「ランタイムを入れ直せば全部直る」</strong>:UDF破損が原因の場合、ランタイム再インストールだけでは直らず、<strong>EBWebViewの刷新</strong>が必要です。</li>
<li><strong>「UDFはアプリごとに分離される」</strong>:はい。既定では <code>%LOCALAPPDATA%\{アプリ名}\EBWebView</code>。複数アプリで共有にはなりません。</li>
<li><strong>「ブラウザのキャッシュ削除と同じか」</strong>:概念的には近いですが、WebView2のUDFはアプリローカルです。<strong>Edgeのキャッシュ削除とは別物</strong>です。</li>
<li><strong>「UDFを消すとログイン状態は?」</strong>:そのUDFに保存されたクッキー/localStorage/サービスワーカー等は消えます。業務要件に応じて<strong>バックアップ/マイグレーション</strong>を検討してください。</li>
</ul>
<h2>実装Tips:より堅牢な初期化コード(WPF/WinForms)</h2>
<pre><code class="language-csharp">public static async Task InitWebView2RobustAsync(WebView2 view, string appName, bool gpuWorkaround)
{
var baseUdf = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), appName);
var udf = Path.Combine(baseUdf, "EBWebView");
var args = gpuWorkaround ? "--disable-gpu" : string.Empty;
var opts = new CoreWebView2EnvironmentOptions(args);
for (int i = 0; i < 2; i++)
{
try
{
var env = await CoreWebView2Environment.CreateAsync(null, udf, opts);
await view.EnsureCoreWebView2Async(env);
// 監視
view.CoreWebView2.ProcessFailed += (_, e) =>
{
Log($"ProcessFailed: {e.ProcessFailedKind} Exit={e.ExitCode}");
};
return;
}
catch (System.Runtime.InteropServices.COMException ex) when (
ex.HResult == unchecked((int)0x80070002) ||
ex.HResult == unchecked((int)0x80070005) ||
ex.HResult == unchecked((int)0x80004005))
{
if (i == 0 && Directory.Exists(udf))
{
var broken = udf + ".broken." + DateTime.Now.ToString("yyyyMMdd-HHmmss");
Directory.Move(udf, broken);
continue;
}
throw;
}
}
} </code></pre>
<h2>現場導入しやすい「運用スクリプト」例</h2>
<table>
<thead>
<tr><th>目的</th><th>手段</th><th>コマンド/コード</th></tr>
</thead>
<tbody>
<tr>
<td>UDFの即時バックアップ</td>
<td>PowerShell</td>
<td><pre><code>$udf = "$env:LOCALAPPDATA\MyApp\EBWebView"
$dst = "$udf.backup.$(Get-Date -Format yyyyMMddHHmmss)"
Copy-Item $udf $dst -Recurse -Force
</code></pre></td>
</tr>
<tr>
<td>ランタイムの有無確認</td>
<td>コマンド</td>
<td><pre><code>"%ProgramFiles(x86)%\Microsoft\EdgeWebView\Application\*\msedgewebview2.exe" --version
残骸プロセスの掃除 PowerShell
Get-Process msedgewebview2 -ErrorAction SilentlyContinue | Stop-Process -Force
固定ランタイムの利用 C#
Environment.SetEnvironmentVariable("WEBVIEW2_BROWSER_EXECUTABLE_FOLDER", fixedPath);
Environment.SetEnvironmentVariable("WEBVIEW2_CHANNEL_SEARCH_KIND", "0");
トラブルが長引いたときの追加観点
- 権限/ローミングプロファイル:UDF配下のNTFS権限が壊れていないか(継承の崩れ、読み取り専用属性)。
- ウイルス対策ソフトの干渉:リアルタイム監視がLevelDBやロックファイルを掴み、共有違反を誘発していないか。
- RDP/仮想化環境:リモートデスクトップや仮想GPUでの描画パス差異(
--disable-gpuで切り分け)。 - OS/ランタイムの整合:OSが古すぎないか、再配布ランタイムのバージョンとAPIが合っているか。
ケーススタディ:Google Meet直後にのみ落ちる
Meet終了後もGPUプロセスの異常終了が続く場合、一度UDFをクリーン化した上で、しばらく --disable-gpu で運用し、ドライバとOSを更新。その間に固定版ランタイムへ切り替え、安定を優先するのが実務的です。業務端末では「再起動のタイミングが合わず破損が残留」することが多く、UDFの自動自己修復を入れるだけで報告数が目に見えて減ります。
最終まとめ(要点の再掲)
- 最頻原因は UDF(
%LOCALAPPDATA%\{アプリ名}\EBWebView)破損。まずはフォルダー削除/リネーム→再起動。 - 起動できれば破損確定。できなければランタイム修復やGPU無効化、権限/AVを点検。
- 再発防止は自動復旧(UDFサニタイズ/バージョン分離)と、固定版ランタイム同梱で堅牢化。
- 画面共有はトリガーに過ぎない。ドライバ・電源・プロセスの複合条件で破損が生じるので、再現PCだけに影響しがち。
以上の流れをチーム標準にしておけば、同種インシデントは数分で復旧・恒久対策で再発激減を狙えます。
付録:本文の重要断片(コピー用)
UDFを新規にして起動する最小コード
var fresh = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "MyApp_FreshUDF");
Directory.CreateDirectory(fresh);
var env = await CoreWebView2Environment.CreateAsync(null, fresh);
await webView2.EnsureCoreWebView2Async(env);
GPU無効化での暫定回避
var opts = new CoreWebView2EnvironmentOptions { AdditionalBrowserArguments = "--disable-gpu" };
var env = await CoreWebView2Environment.CreateAsync(null, udfPath, opts);
await webView2.EnsureCoreWebView2Async(env);
EBWebViewフォルダーの手動クリーン(PowerShell)
$udf = "$env:LOCALAPPDATA\MyApp\EBWebView"
Stop-Process -Name msedgewebview2 -Force -ErrorAction SilentlyContinue
Remove-Item $udf -Recurse -Force
HRESULT逆引き表(再掲・ミニ)
| 値 | 意味/疑い | 対処 |
|---|---|---|
0x80070002 | 欠落/破損 | UDF刷新 |
0x80070005 | アクセス拒否/ロック | プロセス停止・権限確認 |
0x80004005 | 不明な失敗 | UDF刷新+GPU無効 |
0x80040154 | 未登録 | ランタイム修復 |
この記事をどう使うか(導入・復旧・設計)
- 導入:まずはUDFの削除/リネーム→再起動を実施(ユーザーへの案内テンプレとして活用)。
- 復旧:ランタイム修復・固定版同梱・GPU無効化の3本柱で確度高く復旧。
- 設計:アプリ側に自動サニタイズ/プロファイル分割/ProcessFailed監視を実装して再発を抑止。

コメント