MSIXアンインストールでTPM鍵が残る問題を完全解説|安全な削除方法・C#/PowerShell実装・運用自動化まで【Windows 10/11】

MSIX で配布したデスクトップ アプリをアンインストールしても TPM(Trusted Platform Module)に格納した鍵やシークレットが残る――この落とし穴は、運用に入ってから初めて気づかれることが少なくありません。結論は明快で、MSIX のアンインストールはハードウェア層のクリーンアップを行いません。本記事ではその背景、危険ポイント、実運用で使える削除戦略、C# / C++ / PowerShell の実装例、そして Intune やスクリプトを使った自動化まで、現場に持ち帰れる形で徹底解説します。

目次

問題の背景と結論(先に要点)

結論:MSIX は「アプリ ファイルとレジストリの追加・削除」を担当する仕組みであり、TPM などのハードウェア・セキュリティ モジュールの中身には触れません。したがって、アプリをアンインストールしても TPM 内に永続化した鍵/シークレットは自動では消えません。必要ならばアプリ側の明示的な削除機構、または外部スクリプトや運用フローで消す設計が必須です。

ポイント内容
MSIX の仕様MSIX はコンテナ化されたファイル/レジストリの管理が守備範囲。TPM・スマートカード・HSM といったハードウェア層は対象外。アンインストールだけでは TPM データは消えない。
推奨対処1) アプリ UI(設定 → セキュリティ → 「TPM データを削除」)を実装し、CNG/TPM API で明示削除。
2) 付属の PowerShell スクリプトを配布し、先にスクリプト → その後アンインストールの手順を周知。
3) 組織配布では Intune/構成管理で「TPM クリーン → Remove-AppxPackage」を連続実行。
自動化の工夫アプリ終了時のクリーンアップ、ユーザー設定に基づく自動削除、ヘッドレス モードでの削除コマンド実装など。
注意事項TPM への書き込み/削除は状況により管理者権限が必要になる。Clear-Tpm はマシン全体の TPM を初期化するため安易に使用しない(BitLocker 等が影響)。

TPM と CNG(Cryptography Next Generation)基礎整理

Windows では、CNG(CNG API / NCrypt* ファミリ)を介して TPM を含む各種 Key Storage Provider(KSP)を扱います。アプリが TPM に鍵を「永続化(persist)」した場合、その鍵は OS のアンインストーラーからは独立しており、MSIX の削除処理では影響を受けません。

対象代表例削除の可否備考
アプリ ファイル/仮想化レジストリ実行ファイル、設定ファイル、MSIX 仮想レジストリMSIX アンインストールで削除MSIX の守備範囲
TPM 永続鍵Microsoft Platform Crypto Provider に保管された鍵アプリ側で削除が必要NCrypt API / CNG を使用
TPM を使って封緘されたデータTPM バインドされたシークレット(復号は当該 TPM のみ)アプリ側で削除が必要封緘データ(バイト列)自体の廃棄が必要
OS 全体の TPM 初期化tpm.msc の「TPM をクリア」、Clear-Tpm可能だが非推奨BitLocker 等のシステム機能に重大影響

MSIX の制約と設計指針

  • アンインストール時スクリプトの同梱不可:MSIX 自体には MSI のような「アンインストール前後フック」はありません。
  • 外部連携で補う:企業環境では Intune 等の配布基盤でプレ/ポスト処理を組み合わせ、TPM クリーン → パッケージ削除を連続実行するのが現実解です。
  • アプリ機能で削除を可能に:UI/CLI/ヘッドレスのいずれかで「TPM データの削除」を実装。ユーザー/管理者がアンインストール前に実行できる導線を設けます。

安全な削除戦略(設計パターン集)

1) アプリ UI からの明示削除(推奨)

設定画面に「TPM データを削除」ボタンを設け、以下の順序で削除します。

  1. アプリが生成した鍵名(後述の命名規約)を取り出す。
  2. 該当鍵を CNG API でオープンし、NCryptDeleteKey(C++)または CngKey.Delete()(C#)で削除。
  3. TPM に封緘保存していたシークレット バイト列(ファイル/DB)をゼロ化してから削除。
  4. 削除結果を検証(存在確認 or 再オープンに失敗することを確認)。

2) アプリ終了時のクリーンアップ

ユーザーが UI 操作を忘れても、アプリ終了フックで自動削除できます。「次回起動後に自動削除」オプションを用意し、運用都合に合わせて切替可能にするのが実務的です。

3) 自動化(組織配布向け)

  • Intune / 管理スクリプト:PowerShell で鍵削除 → Remove-AppxPackage を順次実行。
  • ヘッドレス CLI:myapp.exe --cleanup-tpm のようなサブコマンドを実装。配布基盤からサイレント実行。

4) Clear-Tpm は最終手段

TPM 全体を初期化する Clear-Tpm は、BitLocker などシステム全体に影響するためアプリのアンインストール目的では使用しないでください。どうしても必要な場合は、影響範囲の周知・バックアップ・再プロビジョニング手順を伴う正式な運用手順とし、一般ユーザーには提供しない構成が安全です。

鍵の命名規約と追跡手法(削除成功率を上げるコツ)

削除の精度は「どの鍵を自分が作ったか、確実に識別できるか」に依存します。実装初期から以下を徹底します。

  • 接頭辞+GUID:{ProductShortName}-{PFN}-{UserSid}-{GUID} など、アプリに固有の接頭辞を含む鍵名を使用。
  • 鍵名の台帳化:作成時に鍵名を %LOCALAPPDATA%\Packages\<PackageFamilyName>\LocalState\keys.json 等に永続化。
  • 範囲を絞った列挙:列挙 API(C++ の NCryptEnumKeys)を使う場合も、接頭辞一致だけを対象にすることで、他システムの鍵を誤って削除しない。

C# 実装例(CNG / Microsoft Platform Crypto Provider)

以下は、台帳(keys.json)に記録した鍵名を削除する最小例です。Platform Crypto Provider を明示して開き、Delete() を呼び出します。

// 対象: .NET 6+ / Windows
// 参照: System.Security.Cryptography
using System;
using System.Collections.Generic;
using System.IO;
using System.Security.Cryptography;
using System.Text.Json;

static class TpmCleanup
{
private static readonly CngProvider Pcp = new CngProvider("Microsoft Platform Crypto Provider");
private const string LedgerPath = "keys.json"; // LocalState などに配置


public static void DeleteAllFromLedger()
{
    if (!File.Exists(LedgerPath))
    {
        Console.WriteLine("ledger not found.");
        return;
    }

    var names = JsonSerializer.Deserialize<List<string>>(File.ReadAllText(LedgerPath)) ?? new();
    foreach (var name in names)
    {
        try
        {
            using var key = CngKey.Open(name, Pcp);
            key.Delete();
            Console.WriteLine($"deleted: {name}");
        }
        catch (CryptographicException ex)
        {
            Console.WriteLine($"skip {name}: {ex.Message}");
        }
    }
}

public static void DeleteByPrefix(string prefix)
{
    // 台帳がない場合のフォールバック: 接頭辞に一致する既知の候補を生成する等、設計に応じて実装
    // ここでは簡略化し、台帳経由のみとする
    DeleteAllFromLedger();
}


} 

ポイント:

  • 台帳に鍵名を必ず記録(アプリ初回起動時/鍵作成時)。
  • キーは Microsoft Platform Crypto Provider でオープンすること(new CngProvider("Microsoft Platform Crypto Provider"))。
  • 例外は握りつぶさずログ化。NTE_BAD_KEYSET 相当等の典型エラーを後述のトラブルシューティングで扱います。

C++ 実装例(NCrypt API で列挙と削除)

台帳がない/補助として列挙したい場合の例です。NCryptEnumKeys で鍵名を走査し、接頭辞一致のみを NCryptDeleteKey で削除します。

// 対象: Windows, ncrypt.h / ncrypt.lib
#include <windows.h>
#include <ncrypt.h>
#include <string>
#include <iostream>

#pragma comment(lib, "ncrypt.lib")

int wmain(int argc, wchar_t** argv)
{
const wchar_t* providerName = L"Microsoft Platform Crypto Provider";
const std::wstring prefix = L"MyApp-"; // 自アプリの接頭辞


NCRYPT_PROV_HANDLE prov = 0;
SECURITY_STATUS s = NCryptOpenStorageProvider(&prov, providerName, 0);
if (s != ERROR_SUCCESS) { std::wcerr << L"OpenStorageProvider failed: " << s << std::endl; return 1; }

PVOID enumState = nullptr;
NCryptKeyName* pKeyName = nullptr;

while ((s = NCryptEnumKeys(prov, nullptr, &pKeyName, &enumState, 0)) == ERROR_SUCCESS)
{
    std::wstring name = pKeyName->pszName;
    if (name.rfind(prefix, 0) == 0) // 接頭辞一致
    {
        NCRYPT_KEY_HANDLE key = 0;
        if (NCryptOpenKey(prov, &key, name.c_str(), 0, 0) == ERROR_SUCCESS)
        {
            SECURITY_STATUS ds = NCryptDeleteKey(key, 0);
            if (ds == ERROR_SUCCESS)
                std::wcout << L"deleted: " << name << std::endl;
            else
                std::wcerr << L"delete failed (" << ds << L"): " << name << std::endl;

            NCryptFreeObject(key);
        }
    }
    NCryptFreeBuffer(pKeyName);
    pKeyName = nullptr;
}

if (s != NTE_NO_MORE_ITEMS) // 列挙完了コード
    std::wcerr << L"EnumKeys ended with: " << s << std::endl;

if (enumState) NCryptFreeBuffer(enumState);
if (prov) NCryptFreeObject(prov);

return 0;


} 

ポイント:必ず接頭辞で範囲を限定し、他アプリや OS の鍵に触れないようにします。削除後に再オープンできないことを確認すると堅牢です。

PowerShell 実装例(管理者向けワンライナー/スクリプト)

PowerShell から CNG を直接叩くには P/Invoke が必要ですが、実務では certutil を併用すると簡潔です。以下は Microsoft Platform Crypto Provider にある「接頭辞一致」の鍵だけを削除するスクリプトです(管理者で実行)。

# ファイル名: cleanup-tpm-keys.ps1
param(
  [Parameter(Mandatory=$true)][string]$Prefix = "MyApp-"
)

# 列挙(Key Container 名を抽出)

$lines = certutil -csp "Microsoft Platform Crypto Provider" -key 2>&1
$names = @()
foreach ($l in $lines) {
if ($l -match "Key Container = (.+)$") { $names += $matches[1] }
}

# 削除(接頭辞一致のみ)

foreach ($n in $names) {
if ($n -like "$Prefix*") {
Write-Host "Deleting $n"
certutil -csp "Microsoft Platform Crypto Provider" -delkey "$n" | Out-Null
}
}

Write-Host "done." 

アプリのアンインストール手順書・README には、先にこのスクリプトを実行 → その後アンインストールと明記してください。

アンインストール自動化(Intune / 構成管理の例)

組織配布では、PowerShell を用いて TPM クリーンと MSIX の削除をまとまって実行するのが実務的です。以下は概念的な例です。

# 1) TPM クリーン(接頭辞一致)
powershell.exe -ExecutionPolicy Bypass -File .\cleanup-tpm-keys.ps1 -Prefix "MyApp-"

# 2) MSIX パッケージの削除

$pkg = Get-AppxPackage -Name "Contoso.MyApp" -AllUsers
if ($pkg) {
$pkg | Remove-AppxPackage -AllUsers
} 
  • 権限:-AllUsers は管理者権限が必要です。端末管理のロールに合わせて付与。
  • 検証:パッケージ削除前に TPM クリーンが完了したかログで判定し、失敗したらロールバック/再試行へ。

UI/UX 設計(ユーザーに安心して消してもらう)

  • 設定画面:「セキュリティ」内にTPM データの削除(説明テキスト+確認ダイアログ)。
  • 情報開示:削除対象(鍵名、作成日、用途)を一覧で確認できるようにする。
  • アンインストール直前の案内:アプリからアンインストール UI を開く前に「TPM データを消しますか?」を提示。
  • ヘッドレス対応:IT 管理者向けに --cleanup-tpm オプションや cleanup.exe を提供。

権限とセキュリティ留意点

  • 管理者権限:TPM の永続鍵削除は構成によっては管理者権限を要します。UAC 昇格プロンプトやサービス コンポーネントで対応。
  • 誤削除の防止:接頭辞と台帳で厳密に絞り込み、他アプリ/OS 鍵は絶対に触らない。
  • ログと監査:削除時刻・対象鍵名・呼出し元(UI/CLI/スクリプト)をイベント ログ等へ記録。
  • Clear-Tpm の封印:通常運用から外し、実行権限を強く制限。利用時は BitLocker 等の再プロビジョニング計画が必須。

テスト計画(再現性と安全性を担保)

観点試験内容期待結果
鍵作成/台帳記録初回起動で鍵生成・keys.json に記録鍵名が接頭辞付きで台帳に残る
UI 削除設定画面から削除→確認ダイアログ→実行該当鍵が開けなくなり、封緘データも消える
終了時削除終了フックで自動削除(オプション有効)再起動後に鍵が存在しない
スクリプト実行管理者で cleanup-tpm-keys.ps1 を実行ログに削除結果が記録、OS 鍵は無傷
アンインストール連携TPM クリーン→Remove-AppxPackageアプリが消え、TPM もクリーン
異常系鍵不存在/アクセス拒否/一部失敗リトライ/スキップの設計通りに振る舞う

トラブルシューティング(典型エラーと対処)

症状/エラー原因対処
NTE_BAD_KEYSET / 鍵が開けない既に削除済み/鍵名が違う台帳と接頭辞を見直し、存在確認を分岐
NTE_PERM / アクセス拒否権限不足(UAC、サービス権限)管理者昇格、サービス経由で呼び出し
他アプリの鍵を誤ってマッチ接頭辞の設計不備固有性の高い接頭辞+PFN/SID を含める
封緘データが残る鍵だけ削除してデータを未削除鍵削除とシークレット バイト列の廃棄をセットで実装
Clear-Tpm で BitLocker が影響TPM 全体初期化の副作用最終手段に限定。業務手順書と再プロビジョニング必須

実装チェックリスト(配布前の最終確認)

  • 鍵名は接頭辞+PFN/SID/GUID で一意か。
  • 鍵名の台帳(keys.json)が作成・更新・削除の各タイミングで整合しているか。
  • 削除 UI/CLI/ヘッドレスが網羅され、手順書に「TPM クリーン → アンインストール」が明記されているか。
  • 管理者権限が必要なパスで UAC 対応が実装されているか。
  • ログと監査(誰がいつ何を消したか)が残るか。
  • Intune 等の配布基盤で自動クリーンのジョブが構成されているか。
  • 異常系テスト(鍵不存在・アクセス拒否・部分失敗)に対する挙動が定義されているか。

FAQ(よくある質問)

Q. MSIX Packaging Tool に「TPM を削除する」オプションはありますか?

A. ありません。MSIX のアンインストーラーはハードウェア層を扱いません。

Q. ユーザー権限アプリでも TPM 鍵は消せますか?

A. 構成により管理者権限が必要な場合があります。UAC 昇格やバックエンド サービスで実行してください。

Q. アンインストール後に消すことはできますか?

A. 可能ですが、パッケージが消えた後は自アプリの実行が困難になるため、アンインストール前に消すフローを推奨します。やむを得ない場合は、独立した cleanup.exe を別配布し、Intune などから実行します。

Q. 鍵を消すと封緘データは自動で読み出せなくなりますか?

A. はい。ただし封緘データ(ファイル/DB のバイト列)は別途存在するため、そのものもゼロ化・削除しないとデータ残存リスクが残ります。

Q. UWP API で作った鍵もこの方針で消せますか?

A. UWP のみで作成した資材は API 面が異なりますが、パッケージ化したデスクトップ アプリ(Desktop Bridge)から CNG/NCrypt で扱うか、アプリ側の削除機構を実装するのが基本方針です。

サンプル:封緘データ(アプリ側バイト列)の安全な廃棄

TPM による鍵管理だけでなく、アプリが保持していたシークレット バイト列も安全に廃棄します。Windows ではオーバーライトを 1 回以上行い、ファイル ハンドルをフラッシュしたうえで削除するのが基本です。

// C# サンプル:安全なファイル削除(簡易版)
using System;
using System.IO;
using System.Security.Cryptography;

public static class SecureDelete
{
public static void OverwriteAndDelete(string path)
{
if (!File.Exists(path)) return;
var fi = new FileInfo(path);
var len = fi.Length;
using (var fs = new FileStream(path, FileMode.Open, FileAccess.Write, FileShare.None))
{
var rng = RandomNumberGenerator.Create();
var buf = new byte[8192];
long written = 0;
while (written < len)
{
rng.GetBytes(buf);
var toWrite = (int)Math.Min(buf.Length, len - written);
fs.Write(buf, 0, toWrite);
written += toWrite;
}
fs.Flush(true);
}
File.Delete(path);
}
} 

実運用テンプレート(README / 手順書にそのまま載せられる文例)

  1. アンインストール前に実施:アプリを起動 → 設定 → セキュリティ → 「TPM データを削除」を実行(または cleanup-tpm-keys.ps1 を管理者で実行)。
  2. 削除完了のメッセージを確認。必要に応じてログを保存。
  3. アプリのアンインストール:「アプリと機能」または配布基盤からアンインストール。
  4. (組織管理)自動化シナリオでは、管理者が「TPM クリーン → パッケージ削除」の順で一括実行。

まとめ(実務ポイントの再掲)

  • MSIX だけでは TPM データは消えない――仕様通りの挙動です。
  • アプリ内の削除機能 or スクリプトを提供し、アンインストール前に実行させる導線を用意。
  • 接頭辞+台帳で誤削除を防ぐ設計を徹底。CNG/NCrypt で確実に削除。
  • 組織配布では Intune 等でTPM クリーン → Remove-AppxPackageの連続ジョブを構成。
  • Clear-Tpm は最後の切り札。通常運用から外し、影響(BitLocker 等)を理解したうえで限定使用。

この記事を書いた人

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

コメント

コメントする

目次