Windowsのアプリインストールはどのように実現しているのか？

## はじめに

Windowsでアプリケーションを使用する方法はざっくり二種類あります。

- 実行ファイルを何処かに配置して直接起動する
- インストーラを利用してソフトウェアをインストールする(以下 単に*インストール*)

~~Microsoft Store..? ちょっと何言ってるかわからない~~

このうち後者を利用すると
- アプリケーションファイルの自動展開
- スタートメニューへの表示
- 専用ファイルとの関連付け
- エクスプローラーのファイル表示、使用に関する変化
- システムからアンインストール可能
- アプリケーションのパーミッション管理
- etc..

といった様々な恩恵を受けられます。素晴らしい。

**いやどういう仕組みだよ**

という疑問を頭に浮かばせた数年前の自分に今、答えを与えるべく電子の海ならぬレジストリの沼に片足突っ込んで調べてきた内容をまとめました。

なお特に断りが無ければ環境は *Windows 10* を想定しています。といっても最近のバージョンならどれも大きくは変わらないはずですが。

# インストーラとは何者なのか

インストーラは環境にアプリケーションを追加し利用可能にする(インストール)ソフトウェアです。

Windowsにおいては通常`.exe`や`.msi`などの拡張子を持ちます。`.exe`形式のものについては比較的自由に作成可能であり簡単に説明することができないので、この項では`.msi`形式を主に説明します。

## `.msi`ファイル

この拡張子が付与されたファイルは **Windows インストーラ**と呼ばれます。
MicrosoftがWindowsへのアプリケーションインストーラとして提供している形式です。
2018年からは新たに`.msix`も提供されています。~~Microsoftは改良版ファイル拡張子に「x」つけたがる気がする[^1]~~

https://ja.wikipedia.org/wiki/Microsoft_Windows_Installer

ファイルのフォーマットについては [COM 構造化ストレージ](https://learn.microsoft.com/ja-jp/windows/win32/stg/structured-storage-start-page) とされていますが、厳密に規定があるわけではない?[^2]ようです。(COMについては後で触れます)
COM 構造化ストレージはデータベースファイルです。つまり**Windows インストーラは実は純粋な実行可能形式のファイルではない**です。EXEフォーマットじゃない的な意味で
こういった構造であるためか、中身のデータベースを分割して別ファイル(`.idt`ファイル)として扱うみたいなこともできるそうです。...いつ使うんだろうな

そのためこのファイルは実際には **Microsoft Standard Installer** (`msiexec.exe`)が展開し、インストールを実行します。
`msiexec.exe`はインストール失敗時にはロールバック機能によって実行前の状態に復元したり、コマンドラインオプションによりログ出力をしたり、必要に応じてシステムの再起動を要求するといった機能をアプリケーションによらず共通で実施してくれます。

```powershell:コンソールによる実行でのログ出力の例
installer.msi /l*v msi_log.txt

# msiexec.exeの明示的な呼び出し 上のコマンドはこれと同じことをしている
msiexec /i installer.msi /l*v msi_log.log
```

既にインストールが行われた後はアプリケーションのアンインストールやアップデートに利用できます。(**メンテナンスインストール**と呼びます[^3])

### 実行時の権限 (TrustedInstaller)

インストーラは実行時に *UAC*(ユーザーアカウント制御)によって管理者ユーザーに権限を要求します。
(この時に権限昇格のダイアログが出るのですがこれの表示中 Windows が *Secure Desktop* というモードになる[^4]らしくスクリーンショットが使えませんでした)
この時に与えた権限によって後述する`Program Files`フォルダやレジストリなどへのアクセス権を得ます。

ところでWindowsには一部の非常に重要なシステムファイルなど、管理者ユーザーにもアクセスできないファイルが存在します。(例えば`explorer.exe`は権限*Administrator*でも書き込みが許可されていません)
一方でUACによって許可されたインストーラの権限は**TrustedInstaller**になります。これはWindowsにおける最上位の権限であり、もちろん管理者権限より上です。

この権限を持ってすれば**どれほど重要なファイルでも好き勝手弄れます**。
怖すぎない？

信頼できないファイルの権限昇格は止めた方が良いですね。

### .msiファイルのキャッシュ

この方法でインストールした場合、`%SystemRoot%\Installer`フォルダ内 (具体的には`C:\Windows\Installer`,隠しフォルダ) にインストーラファイルが保存されます。これらはメンテナンスインストール用として用いられるようです。
![C:Windows\Installer](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2705544/9774ae4a-0593-2415-8c91-eff91a573725.jpeg)
一応ここのファイルを直接実行してアプリケーションをアンインストールすることも可能です、推奨はされませんが。[設定]か[コントロールパネル]からアンインストールしましょう。

このフォルダは通常そこそこ大きくなるとされています(実際に自身の環境で確認しましたが2.5GBにも及んでいました)のでクリーナーソフトやらの削除対象になったり人によってはドライブの容量の関係で手動削除したくなるかもしれませんが、前述した事情があるので消すのはよした方が良いでしょう。

# ファイル操作

さてWindowsインストーラ自体の紹介はこの辺にして、ここから実際の振る舞いに触れていきます。まずはファイル操作関係です。「これくらい常識だろ」って方は飛ばしていただいても構いません。

## 関連ファイルの展開

アプリケーションが動作するには何であれとにかく実行ファイルや必要なリソースファイルが存在しなければいけません。
インストーラはこれらのファイルをフォルダに自動的に展開します。

64bit Windowsの場合、インストールされたファイルは`%PROGRAMFILES%`(`C:\Program Files`)や`%PROGRAMFILES(x86)%`(`C:\Program Files (x86)`)といったフォルダ以下に専用のディレクトリを作成して展開されます。 
基本的には`Program Files`が利用されますが、32bit環境向けにビルドされたソフトウェアは`Program Files (x86)`を利用します。  
(ちなみに環境変数`%PROGRAMFILES%`がどちらのフォルダを指すかは環境によって異なる場合があるらしいです。)

![C:\Program Files\paint.net](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2705544/5490806d-0564-dd85-a04d-3991fd3cbc01.jpeg)
こちらはWindowsにインストール済みのペイントソフト [Paint.net](https://www.getpaint.net/index.html) のアプリケーションフォルダです。今回の記事ではちょくちょく登場させます。
実行ファイル本体である`paintdotnet.exe`の他にDLLファイルなども展開されています。パスが`C:\Program Files\paint.net`であることも確認できます。
ちなみにこちらのexeファイルを直接実行してもしっかりアプリケーションは起動します。

通常これらのフォルダはアクセスに相応の権限を要求するので、インストーラの実行時も権限の昇格が必要になります。



## スタートメニューへの追加

インストールしたソフトウェアはしばしばスタートメニューに表示されていることがあるかと思います。
![スタートメニュー](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2705544/84e8155d-96dc-78e5-c539-285d7a31e217.jpeg)
こちらも実体はインストーラが作成したショートカットファイルです。
これは各アイコンを`[右クリック]->[その他]->[ファイルの場所を開く]`によってショートカットファイルの場所をエクスプローラーで参照できます。
![スタートメニューフォルダ](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2705544/22f8e248-6740-53b7-dafe-3f15916106b4.jpeg)

パスは`C:\ProgramData\Microsoft\Windows\Start Menu\Programs` および`C:\Users\{User_Name}\AppData\Roaming\Microsoft\Windows\Start Menu\Programs`でした。各ファイルへのショートカット (拡張子`.lnk`) がここに作成されます。前者はマシン全体対象で、後者は特定のユーザーのみに適応されるものです。最終的にはこの二つのディレクトリ内の内容を統合したものが表示されます。
ちなみに手動でここにファイルを作成してもちゃんとスタートメニューに反映されます。

<details>
<summary>.lnkファイルのフォーマット</summary>

せっかくなので少しだけWindowsのショートカットファイルについて調べてみます。
[*Shell Link Binary File Format*](https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-shllink/16cb4ca1-9339-4d0c-a68d-bf1d6cc0f943) というようです。
見た感じリンク先の場所、作成時刻、ファイルサイズなどの情報に加えキーボード入力の組み合わせも保持するらしい。

(うわなにこれめんどくさそう)

これはブラックボックスのままだった方が幸せなやつだと思うのでここまでにします

</details>

# レジストリ操作

ここからが本番です。レジストリ操作が絡んできます。ただその前に事前知識の説明を少しさせていただきます。
<details>
<summary>ちょっと長いので折り畳み　ご存知なら読み飛ばしても構いません</summary>

### レジストリ

Windowsの動作に必要な構成データを格納するデータベースです。
実体のファイルは`%SystemRoot%\System32\Config`、各ユーザー用に`%SystemRoot%\Profiles\{User_Name}`に保存されているようです[^5]。
|拡張子|内容物|
|:--|:--|
|{なし}|レジストリデータ|
|.sav|バックアップデータ|
|.alt|非常に重要なバックアップデータ|
|.log|変更のトランザクションログ|

ただし通常はレジストリエディタ(`regedit.exe`)を経由して書き換えます。(通常はそもそも手で書き換えるものではないけど)

#### 構造

データは複数の根からなる木構造で表現されます。各根をさして**ハイブ** (`Hive`)、各ノードを**キー**と呼び、あるキーの下に存在するキーを**サブキー**と呼びます。キーには"既定"を含む**値名とそれに対応したデータ**(文字列や数値)がマップのような形で保存されます。
![レジストリの例.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2705544/2b27a1e3-537e-2364-4155-acdff7bccc63.jpeg)

#### ハイブの種類
ハイブは複数存在します。以下ではこれ以降の説明に登場するハイブの役割を説明します。
|名前|役割|
|:-|:-|
|HKEY_CURRENT_USER|現在ログイン中のユーザに適応されるレジストリデータ|
|HEKY_LOCAL_MACHINE|そのマシン全体でグローバルに適応されるレジストリデータ|
|HKEY_CLASSES_ROOT|`HKEY_LOCAL_MACHINE\SOFTWARE\Classes`と`HKEY_CURRENT_USER\SOFTWARE\Classes`を結合した仮想的なハイブ、現在読み取り専用であり過去のバージョンとの互換性のために提供される|

### COM

`Component Object Model`の略称でMicorosoftが提供するアプリケーションをモジュール化して別プログラムから呼び出せるようにする~~遺物~~機能です。現在はその役割では.NET Assemblyの利用が推奨されています。Windowsのクリップボードが文字列もハイパーテキストも画像もExcelのグラフでも何でもかんでも保存できるのもこいつのおかげだったりします(正確にはCOMを利用して実装されている [*OLE*](https://ja.wikipedia.org/wiki/Object_Linking_and_Embedding) の機能)
プログラムはCOM クラスの定義データを基に`.dll`ファイルなどに保存されたCOMオブジェクトを呼び出せます。

### Windows Shell

*Microsoft Windows* の GUIを**Windows Shell**と呼びます。(Powershellとかとは関係ない)
アプリケーションが提供するウィンドウではなく、つまりWindowsが提供する機能の"表面"、デスクトップやスタートメニュー、タスクバー等のことです。  
現在は`explorer.exe`がこれを担当しています。あのエクスプローラーです。(だからタスクマネージャーとかでエクスプローラーを強制停止させるとデスクトップもまとめて落ちたりする)

で、ファイル関連付けなど機能はこのWindows Shellが提供するものです。例えば[プログラムから開く]などですね。そして、エクスプローラーでファイルのサムネイルを表示したりする機能(jpegのサムネが表示されたりするやつ)はアプリケーション開発者が **Windows Shell 拡張(Extension) として提供した機能**になります

</details>

## アプリケーションの識別

では実際のレジストリの様子を見ていきます。
Windowsがアプリケーションとそのコンポーネントの管理に用いる識別子(ID)は何種類かあります。
なおこれ以降で`[Class_Root]`はレジストリ上の`HKEY_CLASSES_ROOT`ハイブ、`HKEY_LOCAL_MACHINE\SOFTWARE\Classes`、`HKEY_CURRENT_USER\SOFTWARE\Classes`キーなどを指すこととします。頻繁に登場する~~からめんどくさい~~ので。

|名前|説明|レジストリ内の記録場所|
|:-|:-|:-|
|**Class ID** (*ClsID*)|Windowsに登録された **COMクラスオブジェクトの一意の識別子**。実体は128bitのGUID。|`[Class_Root]\CLSID\{ClsID}`|
|**Programmatic ID** (*ProgID*)|**ProgramのID**。Microsoftによれば**Class IDの別名**らしい、一意であることは保証されない。Class IDよりもユーザーフレンドリーな名前(例：\<Word.Document.6>)をしており、一部の場合にClass IDの代用として用いる。実際にはClass ID(≒COMオブジェクト)に紐づけられてないProgIDも多い。|`[Class_Root]\{ProgID}`|
|**Application ID** (*AppID*)|**特定のアプリケーションが利用するCOMオブジェクトのグループに割り当てられる識別子**。アプリケーションのセキュリティ設定などの簡略化を目的としている。実体はGUID。|`[Class_Root]\AppID\{AppID}`|
|**Product Code**|**アプリケーション自体の主要識別子**。実体はGUID。`.msi`のインストーラがアプリケーションを識別する用途にもこれを用いる。|*1(長いため別記)|
|**Package Code**|**Windows Installerが持つ識別子**、通常Product Codeと一致させるが更新があった場合にはこちらを変化させる。インストール時、そのPackage Codeが既に登録されていた場合インストールを省略される可能性がある。|(未調査)|

*1 : `HKEY_LOCLA_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall`のサブキーなど？

そして各ProgIDやAppID、ClsIDなどは各々のサブキーとして格納されることで紐づけがなされます。

つまりこんな感じ
```mermaid
graph TD;
    Product(Product Code)-->AppID(Application ID);
    Product---Package(Package Code)
    AppID-->clsID1(Class ID);
    AppID-->clsID2(Class ID);
    AppID-->clsID3(Class ID);
    clsID1-.-ProgID1(Program ID);
    clsID2-.-ProgID2(Program ID);
    clsID3-.-ProgID3(Program ID);
```
```text:レジストリ構造
HKEY_LOCAL_MACHINE (or HEKY_CURRENT_USER)
└─SOFTWARE
   ├─Classes
   │  ├─AppID
   │  │  ├─{Application ID 1}
   │  │  ├─{Application ID 2}
   │  │  └─...
   │  ├─CLSID
   │  │  ├─{Class ID 1}
   │  │  │  ├─Appid (Application IDと関連付け)
   │  │  │  └─ProgID (Program IDと関連付け)
   │  │  ├─{Class ID 2}
   │  │  └─...
   │  ├─{Program ID 1}
   │  │  └─CLSID (Class IDと関連付け)
   │  ├─{Program ID 2}
   │  └─...
   └─Microsoft
      └─Windows
         └─CurrentVersion
            └─Uninstall
               ├─{Product Code 1}
               ├─{Product Code 2}
               └─...
```
これらの識別子を利用してWindowsは各種の情報を紐づけます。

## 各識別子と実体との関連付け

コンポーネントに名前を付けたので次はそれを実際のexeファイルやdllファイルの呼び出しと紐づけなければなりません。

### Program IDと実行可能ファイルの紐づけ

Windows ShellがProgIDへ実行を要求した場合に呼び出される処理は`{ProgID}`キー直下の`shell`キーに定義されます。
```text:レジストリ構造
HKEY_LOCAL_MACHINE (or HEKY_CURRENT_USER)
└─SOFTWARE
   └─Classes
      └─{ProgID}
         ├─CLSID (存在する場合)
         └─shell
            ├─edit
            │  └─command
            ├─open
            │  └─command
            └─print
               └─command
```
それぞれの`command`キーには"既定"の値に実行するコマンドが保持されます。またここに示した`open`や`edit`以外にも任意の値を指定できますが、特に`open`などの特定の値に関してはWindows Shellは自動的にローカライズされた名前で表示してくれます。例えば`open`はエクスプローラーでファイルを右クリックした際のコンテキストメニューにある「開く」に対応します。
![コンテキストメニュー.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2705544/320074ce-31b7-3036-7d40-ff858223f72b.png)

![例 Paint.netの場合.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2705544/2f5b5f15-65b9-a3da-9647-f32534a85146.jpeg)
上の場合はProgIDは`paint.net.1`でコマンドは`"C:\Program Files\paint.net\paintdotnet.exe" "%1"`が対応します。なお`paint.net.1`にはCOMオブジェクトが紐づけられていないので`CLSID`キーは存在しません。

コマンドには何種類か挿入できる変数が存在します。例えば`%1`の部分は実行時呼び出しの対象となるファイルのパスが展開されます。つまり例えばWindowsが`C:\dogeza.jpg`ファイルを`paint.net.1`に編集するように命令するとき
```powershell:shell\edit\command
"C:\Program Files\paint.net\paintdotnet.exe" "C:\dogeza.jpg"
```
というようなコマンドに展開され Paint.net が開きます。

### Class IDとCOMオブジェクトの紐づけ

COMとの紐づけにはそのCOMの定義が存在するDLLファイルが必要です。

レジストリ上での*Class ID*はこのようになっています

```text:レジストリ構造
HKEY_LOCAL_MACHINE (or HEKY_CURRENT_USER)
└─SOFTWARE
   └─Classes
      └─CLSID
         └─{Class ID}
            └─InprocServer32
```
```text:InprocServer32の値
InprocServer
├─"既定" = {DLLファイルへのパス}
└─ThreadingModel(任意)
```
この`InprocServer32`(または`InprocServer`)の"既定"にCOMが定義されているDLLファイルへのパスが保存されています( 誤解を恐れずに言うとCOMが定義されたDLLファイルのことを**インプロセスサーバー**と呼びます)

これでClass IDからも実体のDLLファイルが参照できるようになりました。

## アプリケーション情報の提供

### Applications キー
インストーラは`[Class_Root]\Applications\{Application_Name}.exe`というキーを作成します。これによりWindowsにアプリケーションについてのいくらかの(全てではない)情報を通知できます。エクスプローラーなどでファイルを開くときに出てくるアプリケーション名やアイコンはProgIDに記録した`shell`の内容に含まれるEXEファイル名を基にここを参照するようです。

```text:
HKEY_LOCAL_MACHINE (or HEKY_CURRENT_USER)
└─SOFTWARE
   └─Classes
      └─Applications
         ├─excel.exe
         ├─notepad.exe
         ├─paintdotnet.exe
         │  ├─shell
         │  └─SupportedTypes
         └─...
```

|含まれるサブキー|説明|
|:-|:-|
|shell|前述した`shell`キーと同じ形でコマンドを指定します。アプリケーション呼び出し時に参照されます。|
|DefaultIcon|通常アプリケーションはその.exeファイル内に存在する最初のアイコンリソースをアイコンとして利用しますが( exeやdllは内部にアイコンなどのリソースを持つ )、このキーに.exeまたは.dllとそのリソース番号などを指定することで明示的にアイコンを指定できます|
|SupportedTypes|任意の拡張子をサブキーに列挙します。ここに保存された拡張子については、**Windows Shellの[プログラムから開く]の候補としてサジェストされます**。ちなみにこの代わりに`NoOpenWith`を指定すると[プログラムから開く]には表示されなくなります。|

またこのキーには`FriendlyAppName`という値が存在し、ローカライズされたユーザー向けのアプリケーション名を指定できます。

このキーは主に実行用の情報を保持する場所でありアプリケーションのメタデータ的な情報(ベンダーやバージョン情報など)は別にあります(後述します)

### App Path
Windows アプリケーションの実行ファイル名を実際のEXEファイルにマッピングするために`HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\App Paths`キー以下の`{Application_Name}.exe`サブキーに保存します。

```text:
HKEY_LOCAL_MACHINE (or HEKY_CURRENT_USER)
└─SOFTWARE
   └─Microsoft
      └─Windows
         └─CurrentVersion
            └─App Paths
               ├─excel.exe
               ├─paintdotnet.exe
               └─PowerShell.exe
```

|含まれる値|説明|
|:-|:-|
|"既定"|EXEファイルへの完全パス|
|Path|EXEファイルのあるディレクトリへの完全パス|

## ファイル拡張子との関連付け

### [既定のプログラム]

次はアプリケーションと拡張子との関連付けです。

まずファイル拡張子自体の登録です。拡張子の情報は~~またも~~`[Class_Root]`キー以下の`.{拡張子}`といった形式のキーに保存されます。
```text:例
HKEY_LOCAL_MACHINE (or HEKY_CURRENT_USER)
└─SOFTWARE
   └─Classes
      ├─*
      ├─.jpeg
      ├─.png
      └─.xlsx
```
(`*`内の内容は全ての拡張子に適応されます)
各拡張子キーの中身は以下のようになっています。

|各拡張子の値|説明|
|:-|:-|
|"既定"|このファイルを扱うProgID。ファイルを普通にダブルクリックなどで開こうとするとこのProgIDからプログラムを呼び出します。[**既定のプログラム**]を表現する|
|ContentType|ファイルのMIMEタイプ|
|PerceivedType|ファイルがどのようなものとして扱われるか。後述する`SystemFileAssociations`で利用されます。|

```text:.pngの場合の値
.png
├─"既定" = pngfile
├─ContentType = "image/png"
└─PerceivedType = "image"
```
ちなみに今回このProgIDは`pngfile`となっています。これはpngfileを開くためのProgIDとして実体のプログラムを抽象化するように存在するものであり、このようにファイルを開くためのProgIDを別で定義することで異なった拡張子(`.jpg`と`.jpeg`など)に対しても同一の形で呼び出し可能になるなどの恩恵を受けています。

### SystemFileAssociations

他にファイル関連付けに用いるキーとして`SystemFileAssociations`があります。このキーには[既定のプログラム]とは別にファイルを開く方法を定義できます。関連付けとしての優先度は低いため呼び出されない場合もありますが、[既定のプログラム]の設定に関わらず安定しているサブの処理設定として使用できます[^8]。
```text:SystemFileAssociationsの例
HKEY_LOCAL_MACHINE (or HEKY_CURRENT_USER)
└─SOFTWARE
   └─Classes
      └─SystemFileAssociations
         ├─.jpeg
         ├─.mp3
         ├─image
         └─text
```
基本的には上述した`.{file_extension}`キーと同一の形のキーを配置しますが、ここでは上でも示した`PerceivedType`に対応するキーを配置できます(上の例では`image`と`text`がそれ)。中身は同じですがこの内容は対応する`PerceivedType`が指定されている拡張子全般に適応されます。

## [プログラムから開く]の実現
### SupportedTypes
エクスプローラーのコンテキストメニューに存在する[プログラムから開く(Open With...)]の選択肢は[前述のように](#アプリケーション情報の提供)`Applications\{App_Name}.exe`キーの`SupportedTypes`という値を参照します。
```text
Applications
 └─paintdotnet.exe
    └─SupportedTypes
       ├─.gif
       ├─.jpeg
       └─...
```

### OpenWithProgids
また各拡張子キー側に存在する`OpenWithProgids`サブキーに保存することもできます。

```text
HKEY_LOCAL_MACHINE (or HEKY_CURRENT_USER)
└─SOFTWARE
   └─Classes
      └─.png
         └─OpenWithProgids
```
```text:OpenWithProgidsの値
OpenWithProgids
 ├─paint.net.1
 └─pngfile
```
これの値として対象のProgIDが列挙されます。これによって[プログラムから開く]でサジェストされるプログラムを指定できます。

なおこのような操作の結果として同一のアプリケーションが複数サジェストされる場合、重複は取り除かれるとか。

:::note warn
`OpenWithProgids`は *Windows XP*でのみ利用されます[^6]。現在存在するものは互換性維持を目的とするものです。
:::

## エクスプローラーでのサムネ表示の登録

画像のサムネイル表示のような処理についてはアプリケーションが専用の実装を提供します。このようなWindows Shellに特殊な処理を加えるものを指して**Shell Extension**[^7] (シェル拡張)と呼びます。

例えばサムネイル表示を担当するシェル拡張の実体は[`IThumbnailProvider`](https://learn.microsoft.com/ja-jp/windows/win32/api/thumbcache/nn-thumbcache-ithumbnailprovider)インタフェース(Win32 APIに存在)を実装したCOMクラスです。
```text:Shell拡張登録例 / paint.netのサムネイル
HKEY_LOCAL_MACHINE (or HEKY_CURRENT_USER)
└─SOFTWARE
   └─Classes
      ├─CLSID
      │  └─{シェル拡張のClsID}
      └─.pdn
         └─ShellEx
            └─{E357FCCD-A995-4576-B01F-234630154E96} = {シェル拡張のClsID}
```
シェル拡張の実装は`ShellEx`以下に保存されます。`{E357FCCD-A995-4576-B01F-234630154E96}`はサムネイルプロバイダーの識別のためにあらかじめ予約されているClassIDです(実際の実装を表すClass IDとは別)。このキーの"既定"にClass IDを指定して実際のシェル拡張の実装と紐づけます。

Windows Shellはサムネ表示の際にここを参照してこの機能を実現します。

## インストーラ情報の記録
先ほどWindows インストーラを使用するとそのインストーラがWindowsに保存されると書きましたが、この時のインストーラパスやその他アプリケーションの情報は`HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Installer\UserData\{?}\Products\{Product Code}`に保存されます。

```text:
HKEY_LOCAL_MACHINE (or HEKY_CURRENT_USER)
└─SOFTWARE
   └─Microsoft
      └─Windows
         └─CurrentVersion
            └─Installer
               └─UserData
                  └─{未調査}
                     └─Products
                        └─{Product Code}
                           ├─Features
                           ├─Patches
                           └─InstallProperties
```
主な情報は`InstallProperties`に保存されています。
|InstallPropertiesの値|説明|
|:-|:-|
|DisplayName|ユーザーに提示されるアプリケーション名|
|DisplayVersion|ユーザーに提示されるアプリケーションバージョン|
|Publisher|アプリケーションの提供元|
|InstallDate|最後に更新が実施された時刻|
|InstallLocation|プログラムがインストールされたフォルダのパス|
|LocalPackage|[Windowsに保存されたインストーラ](#.msiファイルのキャッシュ)のパス|
|UninstallString|Windowsインストーラに決定されるアンインストール用のコマンド|

一応御覧の通りアンインストールに使える情報もここに含まれますがアンインストール用のキーは別にあります。

## アンインストール情報の記録
[設定]や[コントロールパネル]にあるアプリ一覧の話です。
![設定のアプリと機能](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2705544/0c6afae4-a18a-fce8-27a9-df7199ba396d.jpeg)

`HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall`直下の`{Product Code}`キーに保存されています。
```
HKEY_LOCAL_MACHINE (or HEKY_CURRENT_USER)
└─SOFTWARE
   └─Windows
      └─CurrentVersion
         └─Uninstall
            ├─{Product Code or AppName}
            ├─{Product Code or AppName}
            └─...
```
Product Codeでなくて直接アプリケーション名が配置されているケースもありました。

内部の値は直前に示した[インストーラ情報](#インストーラ情報の記録)と概ね同一です。
アンインストールに用いる情報はここを参照します。

## Windows Shellへの更新通知

諸々やった後、最後にプログラムは[`SHChangeNotify`](https://learn.microsoft.com/ja-jp/windows/win32/api/shlobj_core/nf-shlobj_core-shchangenotify)関数を呼び出してWindows Shellにレジストリの変更を通知します。これが無いと変更の内容が反映されません。

以上でインストール終了です。

インストーラも深淵を除けばもっとたくさんのことをやってそうですが概ね自分の知りたかったことは理解できた~~しいい加減疲れた~~のでこの辺りにしておきます。お疲れ様でした。

# おまけ 手動インストールをやってみる

:::note warn
以下の内容にはレジストリ操作が含まれます。参考にして真似される場合も自己責任でお願いします。
:::

**EDGE**というソフトウェアがあります。Microsoft Edgeじゃないよ

https://takabosoft.com/edge

*たかぼー* 氏制作のドット絵を描くことに特化したフリーのペイントエディタです。

たまに使わせていただくのですが、このソフトウェアでは作成途中のデータの保存に`.edg`という拡張子を持つ独自のファイルフォーマットを使用します。
そんでもってこのソフトは配布されているZIPファイルを解凍するだけで済むタイプの、つまり**インストーラがない**ソフトウェアです。

すると当然ですが
![開けない.edgファイル](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2705544/2ec004ae-5f86-d4d3-cd35-a309a0eb3436.png)
＿人人人人人人人人人人人人人人人人＿
＞　ダブルクリックで直接開けない　＜
￣Y^Y^Y^Y^Y^Y^Y^Y^Y^Y^Y^Y^Y^Y￣

なので通常はEDGEを起動したうえでソフト側から開くことになるのですが、地味にめんどくさいです。

そこで今回は手動インストールと称して、**この`.edg`ファイルをエクスプローラーから直で開くこと**までを目標として挑戦してみたいと思います。

## ファイルの配置
`C:\Program Files\Edge`フォルダを作成し、配布されているZIPファイルをそこに展開します。
![ファイルの配置](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2705544/478b2864-b12b-c971-ad78-43709de1c6b4.png)

## スタートメニューへの登録
`C:\Users\{User_Name}\AppData\Roaming\Microsoft\Windows\Start Menu\Programs`にショートカットファイルを作成します。
![スタートメニュー](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2705544/135ee464-27af-2e55-19b0-20489d82ff5e.png)
表示されました、いい感じですね

## レジストリ操作
### 復元ポイントの作成
レジストリを弄る前に万が一の場合に備えて**復元ポイント**を作成します。復元ポイントを作成しておくことで後からシステムファイルをその時点の内容まで復元できます。

`[コントロールパネル]>[システム]>[復元ポイントの作成]`を開いて、ダイアログ下部の[作成]ボタンを押して名前を決めたら十秒ほど待機すると復元ポイントが作成されます。
![システムの復元](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2705544/faec604d-76b7-dfa1-eb22-9789a59be4fc.png)

### アプリケーション 登録
`HKEY_LOCAL_MACHINE\SOFTWARE\Classes\Applications`に`edge.exe`キーを作成します。
```:HKEY_LOCAL_MACHINE\SOFTWARE\Classes\Applications
edge.exe
├─shell
│  └─open
│     └─command = ["C:\Program Files\Edge\edge.exe" "%1"]
└─SupportedTypes = [".edge",".png"] <-ついでにpngも登録してみる
```
ついでに`HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\App Paths`にもキーを作成しておきます。
```:HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\App Paths
edge.exe = "C:\Program Files\Edge\edge.exe"
```
ちなみに一応ここでOS再起動をかけてみましたがまだファイルは開けませんでした。
### ProgID 登録
`paint.edge.1`というProgIDを定義して登録します。
```:HKEY_LOCAL_MACHINE\SOFTWARE\Classes
Classes
└─paint.edge.1
   └─shell
      └─open
         └─command = ["C:\Program Files\Edge\edge.exe" "%1"]
```
### 拡張子 登録
```:HKEY_LOCAL_MACHINE\SOFTWARE\Classes
Classes
└─.edg = paint.edge.1
```

結果的にこうなりました。
![スクショ](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2705544/1769272c-5c79-d190-95e9-8e19e62d9c2f.png)

### 再起動
最後に更新を反映するために再起動をかけます。
さて...。
![お..?](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2705544/d524041e-a029-058f-cd0b-2254b37c738c.png)
お...?
![Great Work](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2705544/ec988910-d41e-de1f-3b2a-fd08a43f0b3c.png)
**素晴らしい**
見事ダブルクリック一発で開くことに成功しました。

さて、さっきおまけで`SupportedTypes`に追加していた`.png`についてはどうなったかな？
![プログラムから開く](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2705544/25504c74-a1ad-088f-2962-0e6f27b5c3c5.png)
しっかりとPNGファイルにもサジェストされていますね。
# 最後に
つかれた

COMってとっくに死んだ技術だと思ってたんですけどバリバリ現役なんですね...
如何せん資料があまり多くなかったので間違ってる情報書いてたら申し訳ありません。

[^1]:`.docx`とか`.aspx`とか、多分eXtended(拡張された)とかの略だろうな
[^2]:[Stack Overflow - Windows installer MSI format](https://stackoverflow.com/questions/1951289/windows-installer-msi-format)
[^3]:[Learn.Microsoft.Com - メンテナンスのインストール](https://learn.microsoft.com/ja-jp/windows/win32/msi/maintenance-installation)
[^4]:[Wikipedia - User Account Control](https://en.wikipedia.org/wiki/User_Account_Control)
[^5]:[Learn.Microsoft.Com - レジストリの説明](https://learn.microsoft.com/ja-JP/troubleshoot/windows-server/performance/windows-registry-advanced-users)
[^6]:[Learn.Microsoft.Com - ファイルハンドラーを指定する](https://learn.microsoft.com/ja-jp/visualstudio/extensibility/specifying-file-handlers-for-file-name-extensions)
[^7]:[Learn.Microsoft.Com - シェル拡張機能の操作](https://learn.microsoft.com/ja-jp/windows/win32/shell/shell-exts)
[^8]:[Learn.Microsoft.Com - アプリケーションの登録](https://learn.microsoft.com/ja-jp/windows/win32/shell/app-registration#registering-applications)