PowerShellでインストールされたAppxPackageの表示名を取得する

PowerShell でインストールされた AppxPackage の表示名を取得するには、以下の2つを試す必要があります。

- アプリパッケージのマニフェストに記載されている表示名を取得
- SHLoadIndirectString 関数を通して取得する

# 方法1: アプリパッケージのマニフェストに記載されている表示名を取得
Framework のみ Get-AppxPackageManifest コマンドレットでは取得できないため、インストール場所にある AppxManifest.xml を直接参照します。

```PowerShell:アプリパッケージのマニフェストに記載されている表示名を取得
# Microsoft.LanguageExperiencePackja-JP で検証
(Get-AppxPackage -Name Microsoft.LanguageExperiencePackja-JP | Get-AppxPackageManifest).Package.Applications.Application.VisualElements.DisplayName
# "日本語 ローカル エクスペリエンス パック" と返ってくる

# Framework を除外したらすべての名前を取得できる (一部は SHLoadIndirectString 関数を通す必要がある)
(Get-AppxPackage | Where-Object IsFramework -eq $False) | Select-Object Name, @{Name="DisplayNameFromManifest";Expression={(($_ | Get-AppxPackageManifest).Package.Applications.Application.VisualElements.DisplayName)}}


# Microsoft.VCLibs.140.00 で検証 (Architecture ごとにインストールされるため、X64 のみ取得した)
$ManifestPath = (Join-Path (Get-AppxPackage -Name Microsoft.VCLibs.140.00 | Where-Object Architecture -eq "X64").InstallLocation "AppxManifest.xml")
$Manifest = ([Xml](Get-Content -Path $ManifestPath -Encoding utf8))
$Manifest.Package.Properties.DisplayName
# "Microsoft Visual C++ 2015 UWP Runtime Package" と返ってくる

# Frameworkのみ
(Get-AppxPackage | Where-Object IsFramework) | Select-Object Name, @{Name="DisplayNameFromManifest";Expression={([Xml](Get-Content -Path (Join-Path $_.InstallLocation "AppxManifest.xml") -Encoding utf8)).Package.Properties.DisplayName}}

```


# 方法2: SHLoadIndirectString 関数を通して取得する
前者の方法ですと、```ms-resource:DisplayName```といったテキストが返ってくることがあります。
これは多言語化されているため、パッケージ リソース インデックス (PRI) から SHLoadIndirectString 関数を通して取得します。

```powershell:SHLoadIndirectString関数をPowerShellで使用する
Add-Type -AssemblyName System.Runtime.InteropServices

$signature = @"
[DllImport("shlwapi.dll", BestFitMapping = false, CharSet = CharSet.Unicode, ExactSpelling = true, SetLastError = false, ThrowOnUnmappableChar = true)]
public static extern int SHLoadIndirectString(string pszSource, System.Text.StringBuilder pszOutBuf, int cchOutBuf, IntPtr ppvReserved); 
"@
$ShellLightweightUtilityFunctions = Add-Type -memberDefinition $signature -name "Win32SHLoadIndirectString" -passThru
[System.Text.StringBuilder]$outBuff = 1024
```

SHLoadIndirectString 関数でリソースを解析する方法はいくつかあるようですが、容易に取得できるのはパッケージ名とリソースIDの組み合わせです。
```HKEY_CLASSES_ROOT\Local Settings\Software\Microsoft\Windows\CurrentVersion\AppModel\Repository\Packages``` のキーに ```DisplayName``` が定義されていますが、一部のアプリで取得できない・スタートメニューなどで見る名前が表示されないことがあります。

### 取得したURIを加工する
```ms-resource:DisplayName``` といったテキストのままでは、一部のアプリで取得することができませんでしたが、```ms-resource:Resources/DisplayName``` のように ```Resources/``` を追加することで取得できるようになります。

```PowerShell
# Microsoft.WindowsCamera_2020.504.40.0_x64__8wekyb3d8bbwe で検証
$PackageFullName = "Microsoft.WindowsCamera_2020.504.40.0_x64__8wekyb3d8bbwe"
$Manifest = (Get-AppxPackageManifest -Package $PackageFullName)
# "ms-resource:LensSDK/Resources/AppTitle" と返ってくる
$DisplayName = $Manifest.Package.Applications.Application.VisualElements.DisplayName
If ($ShellLightweightUtilityFunctions::SHLoadIndirectString("@{$PackageFullName`?$DisplayName}", $outBuff ,$outBuff.Capacity, [System.IntPtr]::Zero) -eq 0){
    $outBuff.ToString()
    # "カメラ" と返ってくる
}


# windows.immersivecontrolpanel_10.0.2.1000_neutral_neutral_cw5n1h2txyewy で検証
$PackageFullName = "windows.immersivecontrolpanel_10.0.2.1000_neutral_neutral_cw5n1h2txyewy"
$Manifest = (Get-AppxPackageManifest -Package $PackageFullName)
# "ms-resource:DisplayName" と返ってくる
$DisplayName = $Manifest.Package.Applications.Application.VisualElements.DisplayName
If ($ShellLightweightUtilityFunctions::SHLoadIndirectString("@{$PackageFullName`?$DisplayName}", $outBuff ,$outBuff.Capacity, [System.IntPtr]::Zero) -eq 0){
    $outBuff.ToString()
    # 返ってこない
}
# この場合は Resources/ が必要なパターン
$DisplayName = ((($DisplayName -replace "ms-resource://", "") -replace "ms-resource:/", "") -replace "ms-resource:", "")
If ($ShellLightweightUtilityFunctions::SHLoadIndirectString("@{$PackageFullName`?ms-resource:Resources/$DisplayName}", $outBuff ,$outBuff.Capacity, [System.IntPtr]::Zero) -eq 0){
    $outBuff.ToString()
    # "設定" と返ってくる
}
```


# サンプル
https://github.com/rin309/Scripting/blob/master/System/Get-AppxPackageDisplayName/MainScript.ps1 にあります。

### 20H1 における実行サンプル
https://github.com/rin309/Scripting/blob/master/System/Get-AppxPackageDisplayName/Sample--Get-AppxPackage.csv
https://github.com/rin309/Scripting/blob/master/System/Get-AppxPackageDisplayName/Sample--Get-AppxProvisionedPackage.csv




# 備考
- SHLoadIndirectString function
https://docs.microsoft.com/ja-jp/windows/win32/api/shlwapi/nf-shlwapi-shloadindirectstring
- DllImportAttribute クラス
https://docs.microsoft.com/ja-jp/dotnet/api/system.runtime.interopservices.dllimportattribute?view=netframework-4.8