うっかり削除した Microsoft Store アプリの実行エイリアスを無理やり復活させた話

<font color=gray>ふつうの人はアプリをいったんアンインストールしてから再インストールしたほうが早い。</font>

# 0. 概要

UWP あるいは Microsoft Store アプリと呼ぶべきなのか知らないが，`winget.exe` などのアプリ本体は大抵の場合

```
C:\Program Files\WindowsApps\...
```

にある。しかし，その実行エイリアスは

```
C:\Users\****\AppData\Local\Microsoft\WindowsApps\...
```

に置かれていて，ファイルサイズが 0 バイトとなっている。また，通常のシンボリックリンクやジャンクションと異なり，リンク先が見えない謎の存在でもある。先日，その実行エイリアスをうっかり削除してしまい，専用のツールを自作してようやく復旧できたという話である。

# 1. はじめに

Python をインストールするとき，ユーザ別にインストールするか，全てのユーザで使えるようにするか選ぶことができることに気づかず，前者すなわち自分のアカウントにインストールしてしまった。いったんアンインストールし，今度は「Install for all users」を選んで再インストールすればよい。

ところが Python をアンインストールしたにも関わらず python.exe にパスが通っていることに気づいた。なお `which` コマンドについては[過去の記事](https://qiita.com/TETSURO1999/items/137f7c04296b3b4ad56a)を参照されたい。

```
C:\>which python
C:\Users\****\AppData\Local\Microsoft\WindowsApps\python.exe
```

で，該当フォルダに移動してみると，サイズが 0 バイトの実行ファイルの存在（複数）に気づいた。

```:謎の 0 バイトの実行ファイルたち
C:\Users\****\AppData\Local\Microsoft\WindowsApps>dir
 ドライブ C のボリューム ラベルは Windows です
 ボリューム シリアル番号は ****-**** です

 C:\Users\****\AppData\Local\Microsoft\WindowsApps のディレクトリ

2024/08/16  00:18    <DIR>          Microsoft.DesktopAppInstaller_8wekyb3d8bbwe
2024/08/16  00:39    <DIR>          Microsoft.GetHelp_8wekyb3d8bbwe
2024/08/16  00:23    <DIR>          Microsoft.ZuneMusic_8wekyb3d8bbwe
2024/08/16  00:39                 0 GetHelp.exe
2024/08/16  00:23                 0 MediaPlayer.exe
2024/08/16  00:18                 0 python.exe
2024/08/16  00:18                 0 python3.exe
2024/08/16  00:18                 0 WindowsPackageManagerServer.exe
2024/08/16  00:18                 0 winget.exe
```

アンインストールに失敗したのかと思い，これも拙作の `remove` コマンド（詳しくは[過去の記事](https://qiita.com/TETSURO1999/items/fe24a232412ba8d626e7)を参照されたい）でゴミ箱に送ろうとしたところ，何故か失敗してしまう。

```:remove コマンドでは消せない・・・だと？
C:\Users\****\AppData\Local\Microsoft\WindowsApps>remove python*.exe
ファイル・フォルダの削除に失敗しました!!
```

なので，つい `delete` コマンドで全部の実行ファイルを消してしまうという大失態を犯してしまった。とくに `winget.exe` を消してしまったのが痛い・・・本来，こういうミスを犯さないようにするための `remove` コマンドなんだけどね。
※`remove` コマンドはファイルをゴミ箱に送るだけなので簡単に復活できるのだ。

```:delete コマンドならば消せるが・・・あ！
C:\Users\****\AppData\Local\Microsoft\WindowsApps>del *.exe
```

とうことで，うっかり削除してしまった 6 つの実行ファイルの復活が本記事のテーマである。

# 2. サブ PC による解析

<font color=red><b>ここから先は同じ Windows10 22H2 のサブ PC での調査結果になる。</b></font>

不思議なことに Python をインストールしたことのないサブ PC にも同じサイズ 0 バイトの実行ファイルがあった。

実際に `attrib` コマンドで見てみると，どうやらシンボリックリンクが貼られているようなのだが，ターゲットが見つからないと表示される。にも関わらず `winget.exe` は使えるのだ。つまり，本体はどこかにあるはず。NTFS ならシンボリックリンクやハードリンクが貼れるので，サイズ 0 バイトの実行ファイルがあっても不思議ではない。

```:シンボリックリンクは貼られているようだがターゲットが無いだと？
C:\Users\****\AppData\Local\Microsoft\WindowsApps>attrib
シンボリック リンク C:\Users\****\AppData\Local\Microsoft\WindowsApps\GetHelp.exe のターゲットがありません
シンボリック リンク C:\Users\****\AppData\Local\Microsoft\WindowsApps\MediaPlayer.exe のターゲットがありません
シンボリック リンク C:\Users\****\AppData\Local\Microsoft\WindowsApps\python.exe のターゲットがありません
シンボリック リンク C:\Users\****\AppData\Local\Microsoft\WindowsApps\python3.exe のターゲットがありません
シンボリック リンク C:\Users\****\AppData\Local\Microsoft\WindowsApps\WindowsPackageManagerServer.exe のターゲットがありません
シンボリック リンク C:\Users\****\AppData\Local\Microsoft\WindowsApps\winget.exe のターゲットがありません
```

コマンドプロンプトだとアレだが，PowerShell だとリンクが貼られているのが一目で分かる。

```:PowerShell だとリンクが張られているのが一目瞭然
PS C:\Users\****\AppData\Local\Microsoft\WindowsApps> ls

    ディレクトリ: C:\Users\****\AppData\Local\Microsoft\WindowsApps

Mode             LastWriteTime     Length Name
----             -------------     ------ ----
d-----    2024/08/16      0:18            Microsoft.DesktopAppInstaller_8wekyb3d8bbwe
d-----    2024/08/16      0:39            Microsoft.GetHelp_8wekyb3d8bbwe
d-----    2024/08/16      0:23            Microsoft.ZuneMusic_8wekyb3d8bbwe
-a---l    2024/08/16      0:39          0 GetHelp.exe
-a---l    2024/08/16      0:23          0 MediaPlayer.exe
-a---l    2024/08/16      0:18          0 python.exe
-a---l    2024/08/16      0:18          0 python3.exe
-a---l    2024/08/16      0:18          0 WindowsPackageManagerServer.exe
-a---l    2024/08/16      0:18          0 winget.exe
```

しかも ReparsePoint（再解析ポイント）という属性が付けられていると判明した。

```:NTFS の ReparsePoint という機能を使っている
PS C:\Users\****\AppData\Local\Microsoft\WindowsApps> get-item * | select name,attributes

Name                                                   Attributes
----                                                   ----------
Microsoft.DesktopAppInstaller_8wekyb3d8bbwe             Directory
Microsoft.GetHelp_8wekyb3d8bbwe                         Directory
Microsoft.ZuneMusic_8wekyb3d8bbwe                       Directory
GetHelp.exe                                 Archive, ReparsePoint
MediaPlayer.exe                             Archive, ReparsePoint
python.exe                                  Archive, ReparsePoint
python3.exe                                 Archive, ReparsePoint
WindowsPackageManagerServer.exe             Archive, ReparsePoint
winget.exe                                  Archive, ReparsePoint
```

なので `fsutil` コマンドで ReparsePoint のデータを見てみる。参考文献[3]によれば，再解析タグ値 `0x8000001b` は `IO_REPARSE_TAG_APPEXECLINK`，すなわち Universal Windows Platform (UWP) アプリへのリンクであることを意味する。タグ値については Microsoft とサードパーティの二択らしいが，NTFS の仕様が公開されていない以上，通常は Microsoft 一択だろう。再解析データについて，先頭の 4 バイト `03 00 00 00` の意味は不明であるが，その後のバイト列はリンク先へのパスを示す Unicode 文字列のように思える。

```:winget.exe の ReparsePoint（再解析ポイント）データ
C:\Users\****\AppData\Local\Microsoft\WindowsApps>fsutil reparsepoint query winget.exe
再解析タグ値 : 0x8000001b
タグ値: Microsoft

再解析データの長さ: 0x190
再解析データ:
0000:  03 00 00 00 4d 00 69 00  63 00 72 00 6f 00 73 00  ....M.i.c.r.o.s.
0010:  6f 00 66 00 74 00 2e 00  44 00 65 00 73 00 6b 00  o.f.t...D.e.s.k.
0020:  74 00 6f 00 70 00 41 00  70 00 70 00 49 00 6e 00  t.o.p.A.p.p.I.n.
0030:  73 00 74 00 61 00 6c 00  6c 00 65 00 72 00 5f 00  s.t.a.l.l.e.r._.
0040:  38 00 77 00 65 00 6b 00  79 00 62 00 33 00 64 00  8.w.e.k.y.b.3.d.
0050:  38 00 62 00 62 00 77 00  65 00 00 00 4d 00 69 00  8.b.b.w.e...M.i.
0060:  63 00 72 00 6f 00 73 00  6f 00 66 00 74 00 2e 00  c.r.o.s.o.f.t...
0070:  44 00 65 00 73 00 6b 00  74 00 6f 00 70 00 41 00  D.e.s.k.t.o.p.A.
0080:  70 00 70 00 49 00 6e 00  73 00 74 00 61 00 6c 00  p.p.I.n.s.t.a.l.
0090:  6c 00 65 00 72 00 5f 00  38 00 77 00 65 00 6b 00  l.e.r._.8.w.e.k.
00a0:  79 00 62 00 33 00 64 00  38 00 62 00 62 00 77 00  y.b.3.d.8.b.b.w.
00b0:  65 00 21 00 77 00 69 00  6e 00 67 00 65 00 74 00  e.!.w.i.n.g.e.t.
00c0:  00 00 43 00 3a 00 5c 00  50 00 72 00 6f 00 67 00  ..C.:.\.P.r.o.g.
00d0:  72 00 61 00 6d 00 20 00  46 00 69 00 6c 00 65 00  r.a.m. .F.i.l.e.
00e0:  73 00 5c 00 57 00 69 00  6e 00 64 00 6f 00 77 00  s.\.W.i.n.d.o.w.
00f0:  73 00 41 00 70 00 70 00  73 00 5c 00 4d 00 69 00  s.A.p.p.s.\.M.i.
0100:  63 00 72 00 6f 00 73 00  6f 00 66 00 74 00 2e 00  c.r.o.s.o.f.t...
0110:  44 00 65 00 73 00 6b 00  74 00 6f 00 70 00 41 00  D.e.s.k.t.o.p.A.
0120:  70 00 70 00 49 00 6e 00  73 00 74 00 61 00 6c 00  p.p.I.n.s.t.a.l.
0130:  6c 00 65 00 72 00 5f 00  31 00 2e 00 32 00 33 00  l.e.r._.1...2.3.
0140:  2e 00 31 00 39 00 31 00  31 00 2e 00 30 00 5f 00  ..1.9.1.1...0._.
0150:  78 00 36 00 34 00 5f 00  5f 00 38 00 77 00 65 00  x.6.4._._.8.w.e.
0160:  6b 00 79 00 62 00 33 00  64 00 38 00 62 00 62 00  k.y.b.3.d.8.b.b.
0170:  77 00 65 00 5c 00 77 00  69 00 6e 00 67 00 65 00  w.e.\.w.i.n.g.e.
0180:  74 00 2e 00 65 00 78 00  65 00 00 00 30 00 00 00  t...e.x.e...0...
```

同フォルダの全ての実行ファイルの ReparsePoint データについて，手作業で編集して見易くしたものを以下に示す。
※ちなみに `C:\Program Files\WindowsApps` は隠しフォルダ属性になっている。

```:全実行ファイルの ReparsePoint データ（編集後）
GetHelp.exe
----
Microsoft.GetHelp_8wekyb3d8bbwe
Microsoft.GetHelp_8wekyb3d8bbwe!App
C:\Program Files\WindowsApps\Microsoft.GetHelp_10.2403.20861.0_x64__8wekyb3d8bbwe\GetHelp.exe
0

MediaPlayer.exe
----
Microsoft.ZuneMusic_8wekyb3d8bbwe
Microsoft.ZuneMusic_8wekyb3d8bbwe!Microsoft.ZuneMusic
C:\WINDOWS\system32\SystemUWPLauncher.exe
1

python.exe
----
Microsoft.DesktopAppInstaller_8wekyb3d8bbwe
Microsoft.DesktopAppInstaller_8wekyb3d8bbwe!PythonRedirector
C:\Program Files\WindowsApps\Microsoft.DesktopAppInstaller_1.23.1911.0_x64__8wekyb3d8bbwe\AppInstallerPythonRedirector.exe
0

python3.exe
----
Microsoft.DesktopAppInstaller_8wekyb3d8bbwe
Microsoft.DesktopAppInstaller_8wekyb3d8bbwe!PythonRedirector
C:\Program Files\WindowsApps\Microsoft.DesktopAppInstaller_1.23.1911.0_x64__8wekyb3d8bbwe\AppInstallerPythonRedirector.exe
0

WindowsPackageManagerServer.exe
----
Microsoft.DesktopAppInstaller_8wekyb3d8bbwe
Microsoft.DesktopAppInstaller_8wekyb3d8bbwe!WinGetComServer
C:\Program Files\WindowsApps\Microsoft.DesktopAppInstaller_1.23.1911.0_x64__8wekyb3d8bbwe\WindowsPackageManagerServer.exe
0

winget.exe
----
Microsoft.DesktopAppInstaller_8wekyb3d8bbwe
Microsoft.DesktopAppInstaller_8wekyb3d8bbwe!winget
C:\Program Files\WindowsApps\Microsoft.DesktopAppInstaller_1.23.1911.0_x64__8wekyb3d8bbwe\winget.exe
0
```

で，実際にその隠しフォルダに行ってみると確かに実体が存在する。ちなみに `Python.exe` も `Python3.exe` も実体は同じで Python のインストーラへのリダイレクトのようだ。

```:WindowsApp という隠しフォルダに実体がある
C:\Program Files\WindowsApps>dir /s /b gethelp.exe
C:\Program Files\WindowsApps\Microsoft.GetHelp_10.2403.20861.0_x64__8wekyb3d8bbwe\GetHelp.exe

C:\Program Files\WindowsApps>dir /s /b AppInstallerPythonRedirector.exe
C:\Program Files\WindowsApps\Microsoft.DesktopAppInstaller_1.23.1911.0_x64__8wekyb3d8bbwe\AppInstallerPythonRedirector.exe

C:\Program Files\WindowsApps>dir /s /b WindowsPackageManagerServer.exe
C:\Program Files\WindowsApps\Microsoft.DesktopAppInstaller_1.23.1911.0_x64__8wekyb3d8bbwe\WindowsPackageManagerServer.exe

C:\Program Files\WindowsApps>dir /s /b winget.exe
C:\Program Files\WindowsApps\Microsoft.DesktopAppInstaller_1.23.1911.0_x64__8wekyb3d8bbwe\winget.exe
```

もちろん残りのファイル `MediaPlayer.exe` の実体もある。

```:MediaPlayer.exe の実体もある
C:\Windows\System32>dir /s /b SystemUWPLauncher.exe
C:\Windows\System32\SystemUWPLauncher.exe
```

# 3. 再解析ポイントのデータがコピーできない

二台の PC のディレクトリ構造が同じであれば，サイズ 0 バイトの実行ファイルをコピーして持っていけばいい・・・と思ったのだが実際にはコピーできない！

```:USB メモリ（D ドライブ）にコピーできない
C:\Users\****\AppData\Local\Microsoft\WindowsApps>copy winget.exe d:
ファイルにアクセスできません。
        0 個のファイルをコピーしました。
```

管理者権限で `robocopy` コマンドを使用すればファイルのコピー自体は可能だが，残念ながらサイズ 0 バイトのファイルが作られるだけで ReparsePoint のデータまではコピーできない。いやあ，なかなか手強いな・・・

# 4. 再解析ポイントデータを移植するツールを作る

残念ながら再解析ポイントデータの仕様は公開されていない。ごく一部，シンボリックリンクやジャンクションなどを作成するためのデータ構造は公開されているが，Microsoft Store アプリの実行エイリアスのデータ構造は公開されていないのだ。

ちなみに参考文献[9]によれば，Microsoft Store アプリの実行エイリアスのデータ構造は 32bit のバージョン番号（00000003h）のあと，Unicode のヌル終端文字列が 4 つ続き，それぞれ

- パッケージ ID
- エントリーポイント
- ターゲットパス
- アプリケーションタイプ

という定義のようだ。だが，肝心のパッケージ ID やらエントリーポイントが分からなければ設定できないので，ゼロからデータ構造を作成することは諦めた。

そういう訳で再解析ポイントデータを移植，すなわち再解析ポイントデータのエクスポートおよびインポートするツールを作ることにした。Win32 API を直接扱うので開発言語は C となる。

# 4.1 再解析ポイントデータをエクスポートする

エクスポートするほうはさほど難しくない。まずデータを保存するバッファ `buffer` を用意する。サイズは 16kB である。

```c:再解析ポイントデータのバッファ
static	DWORD	length;
static	BYTE	buffer[MAXIMUM_REPARSE_DATA_BUFFER_SIZE];
```

次に，念のためファイルに再解析ポイントが設定されているか確認する。

```c:ファイルの属性をチェックする
DWORD	attr = GetFileAttributesW( filename );
if( attr == INVALID_FILE_ATTRIBUTES ) {
	fwprintf( stderr, L"ファイル %s の属性取得に失敗しました!!\n", filename );
	return( -1 );
}
if( ( attr & FILE_ATTRIBUTE_REPARSE_POINT ) == 0 ) {
	fwprintf( stderr, L"ファイル %s に再解析ポイントは設定されていません!!\n", filename );
	return( -1 );
}
```

そしてファイルをオープンして再解析ポイントデータを取得する。

```c:再解析ポイントデータを取得する
HANDLE	hfile = CreateFileW(
	filename, 0, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING,
	FILE_FLAG_OPEN_REPARSE_POINT | FILE_FLAG_BACKUP_SEMANTICS, NULL );
if( hfile == INVALID_HANDLE_VALUE ) {
	fwprintf( stderr, L"ファイル %s のオープンに失敗しました!!\n", filename );
	return( -1 );
}
int	ret = ( 0 != DeviceIoControl( hfile, FSCTL_GET_REPARSE_POINT, NULL, 0,
	buffer, sizeof(buffer), &length, NULL ) ) ? 0 : -1;
CloseHandle( hfile );
if( ret != 0 )
	fwprintf( stderr, L"ファイル %s のデバイス I/O コントロールに失敗しました!!\n", filename );
```

後は適当なファイル名を付けてバッファ `buffer` のデータを保存すれば良い。

なお `CreateFileW` の引数であるが，ファイルのみを対象とするのであれば表１の値で良いが，ディレクトリも含める場合は表２の値にする必要があった。

<table>
<caption>表１　ファイルのみをオープンするときの引数</caption>
<thead>
<tr><th style="text-align:center">引数</th>
    <th style="text-align:center">値</th></tr>
</thead>
<tbody>
<tr><td>第1引数</td><td>filename</td></tr>
<tr><td>第2引数</td><td>GENERIC_READ</td></tr>
<tr><td>第3引数</td><td>0</td></tr>
<tr><td>第4引数</td><td>NULL</td></tr>
<tr><td>第5引数</td><td>OPEN_EXISTING</td></tr>
<tr><td>第6引数</td><td>FILE_FLAG_OPEN_REPARSE_POINT</td></tr>
<tr><td>第7引数</td><td>NULL</td></tr>
</tbody>
</table>

<table>
<caption>表２　ファイル／ディレクトリ両方をオープンするときの引数</caption>
<thead>
<tr><th style="text-align:center">引数</th>
    <th style="text-align:center">値</th></tr>
</thead>
<tbody>
<tr><td>第1引数</td><td>filename</td></tr>
<tr><td>第2引数</td><td>0</td></tr>
<tr><td>第3引数</td><td>FILE_SHARE_READ | FILE_SHARE_WRITE</td></tr>
<tr><td>第4引数</td><td>NULL</td></tr>
<tr><td>第5引数</td><td>OPEN_EXISTING</td></tr>
<tr><td>第6引数</td><td>FILE_FLAG_OPEN_REPARSE_POINT | FILE_FLAG_BACKUP_SEMANTICS</td></tr>
<tr><td>第7引数</td><td>NULL</td></tr>
</tbody>
</table>

ちなみに本記事は Microsoft Store アプリの実行エイリアスの復活が目的，すなわちあくまでファイルのみを復旧の対象としているが，僅かな変更でディレクトリの情報（ジャンクションなど）も得られることが分かったので，オマケで追加に過ぎない。

# 4.2 再解析ポイントデータをインポートする

一方，インポートする場合は多少手間がかかる。シンボリックリンクを作成可能な特権が必要なので，実行には管理者権限が必要である。加えて管理者権限でもシンボリックリンクを作成可能な特権はデフォルトでは無効化されているので，これを有効化する手続きが必要になる。

```c:シンボリックリンク作成特権を有効化する
HANDLE	htoken;
TOKEN_PRIVILEGES	token;
if( 0 == LookupPrivilegeValueW(
	NULL, SE_CREATE_SYMBOLIC_LINK_NAME, &token.Privileges[0].Luid ) ) {
	fwprintf( stderr, L"シンボリックリンクを作成可能な特権が存在しません!!\n" );
	return( -1 );
}
token.PrivilegeCount = 1;
token.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
if( 0 == OpenProcessToken( GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES, &htoken ) ) {
	fwprintf( stderr, L"アクセストークンの取得に失敗しました!!\n" );
	return( -1 );
}
int	ret = ( 0 != AdjustTokenPrivileges( htoken, FALSE, &token, 0, NULL, NULL )
	&& GetLastError() == ERROR_SUCCESS ) ? 0 : -1;
CloseHandle( htoken );
if( ret != 0 ) {
	fwprintf( stderr, L"特権の有効化に失敗しました!!\n" );
	return( -1 );
}
```

こうしてシンボリックリンクを作成可能な特権を得られた後に，事前に読み込んでいた再解析ポイントデータを設定することができる。

```c:再解析ポイントデータを設定する
HANDLE	hfile = CreateFileW( filename, GENERIC_WRITE, 0, NULL, CREATE_NEW, 0, NULL );
if( hfile == INVALID_HANDLE_VALUE ) {
	fwprintf( stderr, L"ファイル %s のオープンに失敗しました!!\n", filename );
	return( -1 );
}
ret = ( 0 != DeviceIoControl(
	hfile, FSCTL_SET_REPARSE_POINT, buffer, length, NULL, 0, NULL, NULL ) ) ? 0 : -1;
CloseHandle( hfile );
if( ret != 0 )
	fwprintf( stderr, L"ファイル %s のデバイス I/O コントロールに失敗しました!!\n", filename );
```

# 4.3 実装コード

実装コードを以下に示す。ちなみに Visual Studio 2022 Community Edition でビルドした。

<details><summary><b>RPUtil.c のソースコードはコチラ</b></summary>

```c:RPUtil.c
#define	UNICODE
#include <windows.h>
#include <shlwapi.h>
#include <stdio.h>
#include <stdlib.h>
#include <locale.h>
#pragma	comment( lib, "advapi32" )
#pragma	comment( lib, "shlwapi" )
//------------------------------------------------------------------------------
// マクロ定義
//------------------------------------------------------------------------------
#define	TOWPRINT(c)		((L' '<=(c)&&(c)<=L'~')?(c):L'.')
//------------------------------------------------------------------------------
// 再解析ポイントデータバッファ
//------------------------------------------------------------------------------
typedef struct {
	ULONG	ReparseTag;
	USHORT	ReparseDataLength;
	USHORT	Reserved;
	WCHAR	DataBuffer[1];
} REPARSE_DATA_BUFFER;
//------------------------------------------------------------------------------
// タグテーブル
//------------------------------------------------------------------------------
typedef struct {
	int		tag;
	wchar_t	*name;
} TAG_TABLE;
//------------------------------------------------------------------------------
// グローバル変数
//------------------------------------------------------------------------------
static	DWORD	length;
static	BYTE	buffer[MAXIMUM_REPARSE_DATA_BUFFER_SIZE];
//------------------------------------------------------------------------------
// 再解析ポイントタグの文字列を返す
//------------------------------------------------------------------------------
static	wchar_t	*get_tag_string( int tag ) {
static	TAG_TABLE	table[] = {
	{ IO_REPARSE_TAG_MOUNT_POINT,		L"Mount Point" },
	{ IO_REPARSE_TAG_HSM,				L"HSM" },
	{ IO_REPARSE_TAG_HSM2,				L"HSM2" },
	{ IO_REPARSE_TAG_SIS,				L"SIS" },
	{ IO_REPARSE_TAG_WIM,				L"WIM" },
	{ IO_REPARSE_TAG_CSV,				L"CSV" },
	{ IO_REPARSE_TAG_DFS,				L"DFS" },
	{ IO_REPARSE_TAG_SYMLINK,			L"Symbolic Link" },
	{ IO_REPARSE_TAG_DFSR,				L"DFSR" },
	{ IO_REPARSE_TAG_DEDUP,				L"DEDUP" },
	{ IO_REPARSE_TAG_NFS,				L"NFS" },
	{ IO_REPARSE_TAG_FILE_PLACEHOLDER,	L"File PlaceHolder" },
	{ IO_REPARSE_TAG_WOF,				L"WOF" },
	{ IO_REPARSE_TAG_WCI,				L"WCI" },
	{ IO_REPARSE_TAG_WCI_1,				L"WCI_1" },
	{ IO_REPARSE_TAG_GLOBAL_REPARSE,	L"Global Reparse" },
	{ IO_REPARSE_TAG_CLOUD,				L"Cloud" },
	{ IO_REPARSE_TAG_CLOUD_1,			L"Cloud 1" },
	{ IO_REPARSE_TAG_CLOUD_2,			L"Cloud 2" },
	{ IO_REPARSE_TAG_CLOUD_3,			L"Cloud 3" },
	{ IO_REPARSE_TAG_CLOUD_4,			L"Cloud 4" },
	{ IO_REPARSE_TAG_CLOUD_5,			L"Cloud 5" },
	{ IO_REPARSE_TAG_CLOUD_6,			L"Cloud 6" },
	{ IO_REPARSE_TAG_CLOUD_7,			L"Cloud 7" },
	{ IO_REPARSE_TAG_CLOUD_8,			L"Cloud 8" },
	{ IO_REPARSE_TAG_CLOUD_9,			L"Cloud 9" },
	{ IO_REPARSE_TAG_CLOUD_A,			L"Cloud_A" },
	{ IO_REPARSE_TAG_CLOUD_B,			L"Cloud B" },
	{ IO_REPARSE_TAG_CLOUD_C,			L"Cloud C" },
	{ IO_REPARSE_TAG_CLOUD_D,			L"Cloud D" },
	{ IO_REPARSE_TAG_CLOUD_E,			L"Cloud E" },
	{ IO_REPARSE_TAG_CLOUD_F,			L"Cloud F" },
	{ IO_REPARSE_TAG_CLOUD_MASK,		L"Cloud Mask" },
	{ IO_REPARSE_TAG_APPEXECLINK,		L"AppExecLink" },
	{ IO_REPARSE_TAG_PROJFS,			L"Projected FS" },
	{ IO_REPARSE_TAG_STORAGE_SYNC,		L"Storage Sync" },
	{ IO_REPARSE_TAG_WCI_TOMBSTONE,		L"WCI TombStone" },
	{ IO_REPARSE_TAG_UNHANDLED,			L"Unhandled" },
	{ IO_REPARSE_TAG_ONEDRIVE,			L"One Drive" },
	{ IO_REPARSE_TAG_PROJFS_TOMBSTONE,	L"Projected FS TombStone" },
	{ IO_REPARSE_TAG_AF_UNIX,			L"AF UNIX" }
};
#define	TABLE_NUM	(sizeof(table)/sizeof(table[0]))
	for( int i = 0; i < TABLE_NUM; i++ )
		if( table[i].tag == tag ) return( table[i].name );
	return( (wchar_t*)NULL );
}
//------------------------------------------------------------------------------
// ヘルプメッセージ
//------------------------------------------------------------------------------
static	int		usage( void ) {
	fwprintf( stderr, L"再解析ポイント・ユーティリティ\n" );
	fwprintf( stderr, L"\n" );
	fwprintf( stderr, L"RPUtil(.EXE) [コマンド] ...\n" );
	fwprintf( stderr, L"\n" );
	fwprintf( stderr, L"＜コマンド＞\n" );
	fwprintf( stderr, L"  dump [ファイル名]\n" );
	fwprintf( stderr, L"export [ファイル名] [データファイル名]\n" );
	fwprintf( stderr, L"import [ファイル名] [データファイル名]\n" );
	return( -1 );
}
//------------------------------------------------------------------------------
// データのエクスポート
//------------------------------------------------------------------------------
static	int		export_data( wchar_t *filename ) {
	HANDLE	hfile = CreateFileW(
		filename, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, 0, NULL );
	if( hfile == INVALID_HANDLE_VALUE ) {
		fwprintf( stderr, L"ファイル %s のオープンに失敗しました!!\n", filename );
		return( -1 );
	}
	DWORD	len;
	int	ret = ( 0 != WriteFile( hfile, buffer, length, &len, NULL ) && len == length ) ? 0 : -1;
	CloseHandle( hfile );
	if( ret != 0 )
		fwprintf( stderr, L"ファイル %s の書き込みに失敗しました!!\n", filename );
	return( 0 );
}
//------------------------------------------------------------------------------
// データのインポート
//------------------------------------------------------------------------------
static	int		import_data( wchar_t *filename ) {
	HANDLE	hfile = CreateFileW(
		filename, GENERIC_READ, 0, NULL, OPEN_EXISTING, 0, NULL );
	if( hfile == INVALID_HANDLE_VALUE ) {
		fwprintf( stderr, L"ファイル %s のオープンに失敗しました!!\n", filename );
		return( -1 );
	}
	int	ret = ( 0 != ReadFile( hfile, buffer, sizeof(buffer), &length, NULL ) ) ? 0 : -1;
	CloseHandle( hfile );
	if( ret != 0 )
		fwprintf( stderr, L"ファイル %s の読み出しに失敗しました!!\n", filename );
	return( ret );
}
//------------------------------------------------------------------------------
// 再解析ポイントの設定
//------------------------------------------------------------------------------
static	int		set_reparse_point( wchar_t *filename ) {
	HANDLE	htoken;
	TOKEN_PRIVILEGES	token;
	if( 0 == LookupPrivilegeValueW( NULL, SE_CREATE_SYMBOLIC_LINK_NAME, &token.Privileges[0].Luid ) ) {
		fwprintf( stderr, L"シンボリックリンクを作成可能な特権が存在しません!!\n" );
		return( -1 );
	}
	token.PrivilegeCount = 1;
	token.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
	if( 0 == OpenProcessToken( GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES, &htoken ) ) {
		fwprintf( stderr, L"アクセストークンの取得に失敗しました!!\n" );
		return( -1 );
	}
	int	ret = ( 0 != AdjustTokenPrivileges( htoken, FALSE, &token, 0, NULL, NULL )
		&& GetLastError() == ERROR_SUCCESS ) ? 0 : -1;
	CloseHandle( htoken );
	if( ret != 0 ) {
		fwprintf( stderr, L"特権の有効化に失敗しました!!\n" );
		return( -1 );
	}
	HANDLE	hfile = CreateFileW( filename, GENERIC_WRITE, 0, NULL, CREATE_NEW, 0, NULL );
	if( hfile == INVALID_HANDLE_VALUE ) {
		fwprintf( stderr, L"ファイル %s のオープンに失敗しました!!\n", filename );
		return( -1 );
	}
	ret = ( 0 != DeviceIoControl(
		hfile, FSCTL_SET_REPARSE_POINT, buffer, length, NULL, 0, NULL, NULL ) ) ? 0 : -1;
	CloseHandle( hfile );
	if( ret != 0 )
		fwprintf( stderr, L"ファイル %s のデバイス I/O コントロールに失敗しました!!\n", filename );
	return( ret );
}
//------------------------------------------------------------------------------
// 再解析ポイントの取得
//------------------------------------------------------------------------------
static	int		get_reparse_point( wchar_t *filename ) {
	DWORD	attr = GetFileAttributesW( filename );
	if( attr == INVALID_FILE_ATTRIBUTES ) {
		fwprintf( stderr, L"ファイル %s の属性取得に失敗しました!!\n", filename );
		return( -1 );
	}
	if( ( attr & FILE_ATTRIBUTE_REPARSE_POINT ) == 0 ) {
		fwprintf( stderr, L"ファイル %s に再解析ポイントは設定されていません!!\n", filename );
		return( -1 );
	}
	HANDLE	hfile = CreateFileW(
		filename, 0, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL,
		OPEN_EXISTING, FILE_FLAG_OPEN_REPARSE_POINT | FILE_FLAG_BACKUP_SEMANTICS, NULL );
	if( hfile == INVALID_HANDLE_VALUE ) {
		fwprintf( stderr, L"ファイル %s のオープンに失敗しました!!\n", filename );
		return( -1 );
	}
	int	ret = ( 0 != DeviceIoControl(
		hfile, FSCTL_GET_REPARSE_POINT, NULL, 0, buffer, sizeof(buffer), &length, NULL ) ) ? 0 : -1;
	CloseHandle( hfile );
	if( ret != 0 )
		fwprintf( stderr, L"ファイル %s のデバイス I/O コントロールに失敗しました!!\n", filename );
	return( ret );
}
//------------------------------------------------------------------------------
// コマンド DUMP の処理
//------------------------------------------------------------------------------
static	int		command_dump( int argc, wchar_t *argv[] ) {
	if( argc < 3 ) {
		fwprintf( stderr, L"コマンド DUMP の引数が不足しています!!\n" );
		return( -1 );
	}
	if( 0 != get_reparse_point( argv[2] ) ) return( -1 );
	REPARSE_DATA_BUFFER	*p = (REPARSE_DATA_BUFFER*)buffer;
	fwprintf( stdout, L"再解析タグ値：%08Xh\n", p->ReparseTag );
	if( IsReparseTagMicrosoft( p->ReparseTag ) )
		fwprintf( stdout, L"タグ値：Microsoft\n" );
	if( IsReparseTagNameSurrogate( p->ReparseTag ) )
		fwprintf( stdout, L"タグ値：Name Surrogate\n" );
	if( IsReparseTagNameSurrogate( p->ReparseTag ) )
		fwprintf( stdout, L"タグ値：Directory\n" );
	wchar_t	*s = get_tag_string( p->ReparseTag );
	if( (wchar_t*)NULL != s )
		fwprintf( stdout, L"タグ値：%s\n", s );
	fwprintf( stdout, L"再解析データの長さ：%d bytes\n", p->ReparseDataLength );
	fwprintf( stdout, L"再解析データ：\n" );
	int	count = p->ReparseDataLength / 2;
	for( int i = 0, j = 0; i < count; i += 16 ) {
		for( j = i; j < i + 16 && j < count; j++ )
			fwprintf( stdout, L"%04X ", p->DataBuffer[j] );
		for( ; j < i + 16; j++ )
			fwprintf( stdout, L"     " );
		for( j = i; j < i + 16 && j < count; j++ )
			fwprintf( stdout, L"%C", TOWPRINT( p->DataBuffer[j] ) );
		fwprintf( stdout, L"\n" );
	}
	return( 0 );
}
//------------------------------------------------------------------------------
// コマンド EXPORT の処理
//------------------------------------------------------------------------------
static	int		command_export( int argc, wchar_t *argv[] ) {
	if( argc < 4 ) {
		fwprintf( stderr, L"コマンド EXPORT の引数が不足しています!!\n" );
		return( -1 );
	}
	if( 0 != get_reparse_point( argv[2] ) ) return( -1 );
	if( 0 != export_data( argv[3] ) ) return( -1 );
	fwprintf( stderr, L"ファイル %s のデータをファイル %s にエクスポートしました。\n",
		argv[2], argv[3] );
	return( 0 );
}
//------------------------------------------------------------------------------
// コマンド IMPORT の処理
//------------------------------------------------------------------------------
static	int		command_import( int argc, wchar_t *argv[] ) {
	if( argc < 4 ) {
		fwprintf( stderr, L"コマンド IMPORT の引数が不足しています!!\n" );
		return( -1 );
	}
	if( 0 != import_data( argv[3] ) ) return( -1 );
	if( 0 != set_reparse_point( argv[2] ) ) return( -1 );
	fwprintf( stderr, L"ファイル %s にファイル %s のデータをインポートしました。\n",
		argv[2], argv[3] );
	return( 0 );
}
//------------------------------------------------------------------------------
// メイン関数
//------------------------------------------------------------------------------
int		wmain( int argc, wchar_t *argv[] ) {
	setlocale( LC_ALL, "" );
	if( argc < 2 ) return usage();
	/*--*/ if( !StrCmpIW( argv[1], L"DUMP"   ) ) { return command_dump  ( argc, argv );
	} else if( !StrCmpIW( argv[1], L"EXPORT" ) ) { return command_export( argc, argv );
	} else if( !StrCmpIW( argv[1], L"IMPORT" ) ) { return command_import( argc, argv );
	} else {
		fwprintf( stderr, L"コマンド %s には対応していません!!\n", argv[1] );
		return( -1 );
	}
}
```

</details>

# 5. 実行例

## 5.1 サブ PC での実行

<font color=red><b>コチラはサブ PC での実行である。</b></font>

引数なしで実行するとヘルプメッセージを表示する。

```:引数なしで実行した場合
C:\Users\****\AppData\Local\Microsoft\WindowsApps>rputil
再解析ポイント・ユーティリティ

RPUtil(.EXE) [コマンド] ...

＜コマンド＞
  dump [ファイル名]
export [ファイル名] [データファイル名]
import [ファイル名] [データファイル名]
```

確認のため DUMP コマンドを作成した。見易いように Unicode（16bit）単位で表示するようにした。

```:DUMP コマンド
C:\Users\****\AppData\Local\Microsoft\WindowsApps>rputil dump winget.exe
再解析タグ値：8000001Bh
タグ値：Microsoft
タグ値：AppExecLink
再解析データの長さ：400 bytes
再解析データ：
0003 0000 004D 0069 0063 0072 006F 0073 006F 0066 0074 002E 0044 0065 0073 006B ..Microsoft.Desk
0074 006F 0070 0041 0070 0070 0049 006E 0073 0074 0061 006C 006C 0065 0072 005F topAppInstaller_
0038 0077 0065 006B 0079 0062 0033 0064 0038 0062 0062 0077 0065 0000 004D 0069 8wekyb3d8bbwe.Mi
0063 0072 006F 0073 006F 0066 0074 002E 0044 0065 0073 006B 0074 006F 0070 0041 crosoft.DesktopA
0070 0070 0049 006E 0073 0074 0061 006C 006C 0065 0072 005F 0038 0077 0065 006B ppInstaller_8wek
0079 0062 0033 0064 0038 0062 0062 0077 0065 0021 0077 0069 006E 0067 0065 0074 yb3d8bbwe!winget
0000 0043 003A 005C 0050 0072 006F 0067 0072 0061 006D 0020 0046 0069 006C 0065 .C:\Program File
0073 005C 0057 0069 006E 0064 006F 0077 0073 0041 0070 0070 0073 005C 004D 0069 s\WindowsApps\Mi
0063 0072 006F 0073 006F 0066 0074 002E 0044 0065 0073 006B 0074 006F 0070 0041 crosoft.DesktopA
0070 0070 0049 006E 0073 0074 0061 006C 006C 0065 0072 005F 0031 002E 0032 0033 ppInstaller_1.23
002E 0031 0037 0039 0031 002E 0030 005F 0078 0036 0034 005F 005F 0038 0077 0065 .1791.0_x64__8we
006B 0079 0062 0033 0064 0038 0062 0062 0077 0065 005C 0077 0069 006E 0067 0065 kyb3d8bbwe\winge
0074 002E 0065 0078 0065 0000 0030 0000                                         t.exe.0.
```

次に EXPORT コマンドで再解析ポイントデータを保存する。D ドライブは USB メモリである。

```:EXPORT コマンド
C:\Users\****\AppData\Local\Microsoft\WindowsApps>rputil export winget.exe D:\winget.dat
ファイル winget.exe のデータをファイル D:\winget.dat にエクスポートしました。
```

念のため `certutil -dump` コマンドで内容を確認する。エクスポートしたデータの先頭には，再解析タグ値や再解析データの長さなどの情報が付与されている。

```:certutil -dump コマンドで内容を確認
C:\Users\****\AppData\Local\Microsoft\WindowsApps>certutil -dump d:\winget.dat
  0000  ...
  0198
    0000  1b 00 00 80 90 01 00 00  03 00 00 00 4d 00 69 00   ............M.i.
    0010  63 00 72 00 6f 00 73 00  6f 00 66 00 74 00 2e 00   c.r.o.s.o.f.t...
    0020  44 00 65 00 73 00 6b 00  74 00 6f 00 70 00 41 00   D.e.s.k.t.o.p.A.
    0030  70 00 70 00 49 00 6e 00  73 00 74 00 61 00 6c 00   p.p.I.n.s.t.a.l.
    0040  6c 00 65 00 72 00 5f 00  38 00 77 00 65 00 6b 00   l.e.r._.8.w.e.k.
    0050  79 00 62 00 33 00 64 00  38 00 62 00 62 00 77 00   y.b.3.d.8.b.b.w.
    0060  65 00 00 00 4d 00 69 00  63 00 72 00 6f 00 73 00   e...M.i.c.r.o.s.
    0070  6f 00 66 00 74 00 2e 00  44 00 65 00 73 00 6b 00   o.f.t...D.e.s.k.
    0080  74 00 6f 00 70 00 41 00  70 00 70 00 49 00 6e 00   t.o.p.A.p.p.I.n.
    0090  73 00 74 00 61 00 6c 00  6c 00 65 00 72 00 5f 00   s.t.a.l.l.e.r._.
    00a0  38 00 77 00 65 00 6b 00  79 00 62 00 33 00 64 00   8.w.e.k.y.b.3.d.
    00b0  38 00 62 00 62 00 77 00  65 00 21 00 77 00 69 00   8.b.b.w.e.!.w.i.
    00c0  6e 00 67 00 65 00 74 00  00 00 43 00 3a 00 5c 00   n.g.e.t...C.:.\.
    00d0  50 00 72 00 6f 00 67 00  72 00 61 00 6d 00 20 00   P.r.o.g.r.a.m. .
    00e0  46 00 69 00 6c 00 65 00  73 00 5c 00 57 00 69 00   F.i.l.e.s.\.W.i.
    00f0  6e 00 64 00 6f 00 77 00  73 00 41 00 70 00 70 00   n.d.o.w.s.A.p.p.
    0100  73 00 5c 00 4d 00 69 00  63 00 72 00 6f 00 73 00   s.\.M.i.c.r.o.s.
    0110  6f 00 66 00 74 00 2e 00  44 00 65 00 73 00 6b 00   o.f.t...D.e.s.k.
    0120  74 00 6f 00 70 00 41 00  70 00 70 00 49 00 6e 00   t.o.p.A.p.p.I.n.
    0130  73 00 74 00 61 00 6c 00  6c 00 65 00 72 00 5f 00   s.t.a.l.l.e.r._.
    0140  31 00 2e 00 32 00 33 00  2e 00 31 00 37 00 39 00   1...2.3...1.7.9.
    0150  31 00 2e 00 30 00 5f 00  78 00 36 00 34 00 5f 00   1...0._.x.6.4._.
    0160  5f 00 38 00 77 00 65 00  6b 00 79 00 62 00 33 00   _.8.w.e.k.y.b.3.
    0170  64 00 38 00 62 00 62 00  77 00 65 00 5c 00 77 00   d.8.b.b.w.e.\.w.
    0180  69 00 6e 00 67 00 65 00  74 00 2e 00 65 00 78 00   i.n.g.e.t...e.x.
    0190  65 00 00 00 30 00 00 00                            e...0...
CertUtil: -dump コマンドは正常に完了しました。
```

## 5.2 メイン PC での実行

<font color=red><b>ここから PC を移動してメイン PC での実行になる。</b></font>

管理者権限でコマンドプロンプトを開き，`whoami /priv` コマンドで特権を確認する。「シンボリックリンク作成」の特権が一覧の中にあることを確認する。ここでは「無効」状態でも構わない。ツールの中で「有効」状態に遷移させるからだ。

```:管理者権限の確認
C:\Users\****\AppData\Local\Microsoft\WindowsApps>whoami /priv
PRIVILEGES INFORMATION
----------------------
特権名                                     説明　　　　　　　　　　　　　　　　　　　　　　　　　　状態
=========================================　======================================================　====
SeIncreaseQuotaPrivilege                   プロセスのメモリクォータの増加　　　　　　　　　　　　　無効
SeSecurityPrivilege                        監査とセキュリティログの管理　　　　　　　　　　　　　　無効
SeTakeOwnershipPrivilege                   ファイルとその他のオブジェクトの所有権の取得　　　　　　無効
SeLoadDriverPrivilege                      デバイスドライバーのロードとアンロード　　　　　　　　　無効
SeSystemProfilePrivilege                   システムパフォーマンスのプロファイル　　　　　　　　　　無効
SeSystemtimePrivilege                      システム時刻の変更　　　　　　　　　　　　　　　　　　　無効
SeProfileSingleProcessPrivilege            単一プロセスのプロファイル　　　　　　　　　　　　　　　無効
SeIncreaseBasePriorityPrivilege            スケジューリング優先順位の繰り上げ　　　　　　　　　　　無効
SeCreatePagefilePrivilege                  ページ ファイルの作成　　　　　　　　　　　　　　　　　 無効
SeBackupPrivilege                          ファイルとディレクトリのバックアップ　　　　　　　　　　無効
SeRestorePrivilege                         ファイルとディレクトリの復元　　　　　　　　　　　　　　無効
SeShutdownPrivilege                        システムのシャットダウン　　　　　　　　　　　　　　　　無効
SeDebugPrivilege                           プログラムのデバッグ　　　　　　　　　　　　　　　　　　無効
SeSystemEnvironmentPrivilege               ファームウェア環境値の修正　　　　　　　　　　　　　　　無効
SeChangeNotifyPrivilege                    走査チェックのバイパス　　　　　　　　　　　　　　　　　有効
SeRemoteShutdownPrivilege                  リモートコンピューターからの強制シャットダウン　　　　　無効
SeUndockPrivilege                          ドッキング ステーションからコンピューターを削除　　　　 無効
SeManageVolumePrivilege                    ボリュームの保守タスクを実行　　　　　　　　　　　　　　無効
SeImpersonatePrivilege                     認証後にクライアントを偽装　　　　　　　　　　　　　　　有効
SeCreateGlobalPrivilege                    グローバルオブジェクトの作成　　　　　　　　　　　　　　有効
SeIncreaseWorkingSetPrivilege              プロセスワーキングセットの増加　　　　　　　　　　　　　無効
SeTimeZonePrivilege                        タイムゾーンの変更　　　　　　　　　　　　　　　　　　　無効
SeCreateSymbolicLinkPrivilege              シンボリックリンクの作成　　　　　　　　　　　　　　　　無効
SeDelegateSessionUserImpersonatePrivilege  同じセッションで別のユーザーの偽装トークンを取得します　無効
```

:::note
もしもここで「シンボリックリンク作成」の特権が一覧にない場合，グループポリシーエディタ `gpedit.msc` を用いて設定する必要がある。詳しくは参考文献[8]を参照されたい。
:::

そしてようやく D ドライブ（USBメモリ）から再解析ポイントデータをインポートする。実行前に `winget.exe` が存在しているとエラーになってしまうので注意されたい。

```:IMPORT コマンド
C:\Users\****\AppData\Local\Microsoft\WindowsApps>rputil import winget.exe d:\winget.dat
ファイル winget.exe にファイル d:\winget.dat のデータをインポートしました。
```

こうするとサイズ 0 バイトの `winget.exe` が作られる。実体へのリンクは貼られており，無事，実行することはできた。

```:winget.exe が復活したぞ！
C:\Users\****\AppData\Local\Microsoft\WindowsApps>winget
v1.8.1791 の Windows パッケージ マネージャー
Copyright (c) Microsoft Corporation. All rights reserved.

WinGet コマンド ライン ユーティリティを使用すると、コマンド ラインからアプリケーションやその他のパッケージをインストールできます。

使用法: winget  [<コマンド>] [<オプション>]

使用できるコマンドは次のとおりです:
  install    指定されたパッケージをインストール
  show       パッケージに関する情報を表示します
  source     パッケージのソースの管理
  search     アプリの基本情報を見つけて表示
  list       インストール済みパッケージを表示する
  upgrade    利用可能なアップグレードの表示と実行
  uninstall  指定されたパッケージをアンインストール
  hash       インストーラー ファイルをハッシュするヘルパー
  validate   マニフェスト ファイルを検証
  settings   設定を開くか、管理者設定を設定する
  features   試験的な機能の状態を表示
  export     インストールされているパッケージのリストをエクスポート
  import     ファイル中のすべてのパッケージをインストール
  pin        パッケージ ピンの管理
  configure  システムを適切な状態に構成します
  download   指定されたパッケージからインストーラをダウンロードする
  repair     選択したパッケージを修復します

特定のコマンドの詳細については、そのコマンドにヘルプ引数を渡します。 [-?]

次のオプションを使用できます。
  -v,--version                ツールのバージョンを表示
  --info                      ツールの一般情報を表示
  -?,--help                   選択したコマンドに関するヘルプを表示
  --wait                      終了する前に任意のキーを押すプロンプトをユーザーに表示します
  --logs,--open-logs          既定のログの場所を開く
  --verbose,--verbose-logs    WinGet の詳細ログを有効にする
  --nowarn,--ignore-warnings  警告出力を非表示にする
  --disable-interactivity     対話型プロンプトを無効にします
  --proxy                     この実行に使用するプロキシを設定します
  --no-proxy                  この実行に対するプロキシの使用を無効にする

その他のヘルプについては、次を参照してください: https://aka.ms/winget-command-help
```

:::note
本ツールを用いて再解析ポイントの一種であるシンボリックリンクも作ることができるが，同じく同種のジャンクション（ディレクトリを対象にしたリンク）は作ることができない。
:::

# 6. まとめ

Microsoft Store アプリの実行エイリアスは Windows の NTFS というファイルシステムにおける再解析ポイント ReparsePoint という技術で作られている。再解析ポイントはシンボリックリンクやジャンクションなどのリンクを貼る機能の基盤技術で，技術自体は Windows2000 の頃から存在していたようだが，それを扱う API であったり，コマンドラインツールが整備されてきたのは最近である。しかも Windows10 ですら完全には対応し切れていない（たとえば `attrib.exe` コマンドなど）という状況である。Windows10 では再解析ポイントを作成する API は提供されているとはいえ，肝心の `REPARSE_DATA_BUFFER` 構造体の仕様が明らかにされていないため，構造体のデータをゼロから作ることはできず，同様の環境を持つ PC から構造体のデータを移植するという手段を取らざるを得なかったのだ。

以下は反省点である。

- グループポリシーエディタ `gpedit.msc` を弄って「シンボリックリンクの作成」権限を与えることができるのなら，`robocopy` コマンドで（もしかしたら `copy` コマンドでも）普通にコピーできたかもしれない。
- 完全復旧ではないが，リンク先が分かっているのであれば普通に `mklink` コマンドで通常のシンボリックリンクを貼っても良かったような気がする。
- 今回はたまたま環境の近い PC を二台持っていたので助かったが，一台しか持っていなかったら冒頭で述べたようにアプリを再インストールするしかなかっただろう。

参考文献[10]によると，PowerShell にて Microsoft Store アプリの実行エイリアス（AppExecLink リンク）のターゲットを表示しないのは仕様のようだ。ターゲットパス `C:\Program Files\WindowsApps` が隠し属性なのも含めて，マイクロソフトは積極的に公開したくないのかもしれない。

# 7. 参考文献

0. [Windowsにおけるアプリ実行エイリアスとは？ - ascii](https://ascii.jp/elem/000/004/130/4130104/)

0. [WindowsのNTFS／ReFSに搭載されている「再解析ポイント」とは何か？ - ascii](https://ascii.jp/elem/000/004/148/4148673/#eid3582446)

0. [Reparse Tags - microsoft](https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-fscc/c8e77b37-3909-4fe6-a4ea-2b9d423b1ee4?redirectedfrom=MSDN)

0. [再解析ポイントの操作 - microsoft](https://learn.microsoft.com/ja-jp/windows/win32/fileio/reparse-point-operations)

0. [[Win32] [C++] CreateProcessAsUser - #1 特権編 - github](https://msmania.github.io/2011/12/31/createprocessasuser-1.html)

0. [Git for Windowsでシンボリックリンクを扱えるようにする - Qiita](https://qiita.com/ucho/items/c5ea0beb8acf2f1e4772)

0. [リンク/ジャンクション作成ツール - emk](https://emk.name/symlink.html)

0. [Windows 10/11 にてシンボリックリンクを利用可能にする - zenn](https://zenn.dev/nobonobo/scraps/a8018206f39bdb)

0. [Cmake/Source/kwsys/SystemTools.cxx- kitware](https://gitlab.kitware.com/cmake/cmake/-/blob/v3.27.0/Source/kwsys/SystemTools.cxx)

0. [PowerShell/about_FileSystem_Provider - microsoft](https://learn.microsoft.com/ja-jp/powershell/module/microsoft.powershell.core/about/about_filesystem_provider?view=powershell-7.4)