PowerShell 7.1 でWinRTが死んだのでなんとかしていく

結論から先にいうとそこまでどうにかなってはいない

PowerShellでSSID選んでWiFi繋ぐやつ書いた
これとか死んだわけです
記事にはしてないけどWeb資格情報の読み書きするModuleもやられた

どうするか

だいたいぐぐるとここに行き着くわけです
“Loading” of Windows Runtime assemblies fails in 7.1 Preview 4 · Issue #13042 · PowerShell/PowerShell

読んでいくと答えが書いてあるのでそれをやっていきます

やっていく手順

C#/WinRtだとかそういうのを使えるようにしていきます

Find-Package -ProviderName NuGet -Source https://www.nuget.org/api/v2 -Name Microsoft.Windows.CsWinRT | Install-Package
Find-Package -ProviderName NuGet -Source https://www.nuget.org/api/v2 -Name Microsoft.Windows.SDK.NET.Ref | Install-Package

nugetしていく
これやると $env:ProgramFiles\PackageManagement\NuGet\Packages にインストールされるよ
その後はAdd-Typeする

Add-Type -Path 'C:\Program Files\PackageManagement\NuGet\Packages\Microsoft.Windows.CsWinRT.1.0.1\lib\net5.0\WinRT.Runtime.dll'
Add-Type -Path 'C:\Program Files\PackageManagement\NuGet\Packages\Microsoft.Windows.SDK.NET.Ref.10.0.19041.10\lib\Microsoft.Windows.SDK.NET.dll'

WinRT.Runtime.dllとMicrosoft.Windows.SDK.NET.dllをAdd-TypeするとWinRT使えるようになります
基本的には
(やる前よりかは動くようになる)

モジュールに手を入れる

まず

[Windows.Devices.WiFi.WiFiAdapter,Windows.Devices.WiFi,ContentType=WindowsRuntime] | Out-Null

こういうおまじないめいたやつは消してよい
Add-Typeした時点で[Windows.Devices.WiFi.WiFiAdapter]とかもう使えるからです

その上でImport-Moduleしなおして動かしてみよう
エラーが出たらその都度対応だッッッ!!!

困ったこと

[Windows.Security.Credentials.PasswordVault]::new().FindAllByResource($Resource)

これはWeb資格情報から保存してある資格情報を読み出すやつ
前まではSystem.__ComObjectが返ってたけど
[Windows.Security.Credentials.PasswordCredential]にキャストして使えてたのね

でも今回の方法でやってるとキャストできない
戻り値の型がWinRT.IInspectableになっててどうしたもんだかわかない

同じことがWifiのやつでも起きてて

Get-WifiAdapter | Get-WiFiAvailableNetwork

で無事死ねる
OutputType指定しててもどうにもならんね

でもまぁ資格情報のやつなら

$Creds = [Windows.Security.Credentials.PasswordVault]::new().FindAllByResource($Resource)
$Creds.AdditionalTypeData.Values

とやると必要な値は取れるので別にキャストしなくてもいいのでは?
というとこまではわかった

あとは7.1より前と以降とでどうするか?ということも考えなくちゃいけないね
めんどうなことになったよ…

追記: 2020/11/24

nugetについて

WinRT.Runtime.dllMicrosoft.Windows.SDK.NET.Refにも入ってたのでそれだけnugetすればいいのかも?

バージョン別対応について
  • 7.1より前ならおまじないする
  • 7.1以降ならdllをAdd-Typeする
    • dllがなかったらnugetしてからAdd-Type

ParameterSetNameで従来のパラメータの他に[WinRT.IInspectable]を受けるパラメータを作ることでだいたい対応できる
厳密にはWinRT.IInspectableの実態がなんの型か調べるとかしたほうが良い
(じゃないと何でも受け取れちゃうので)
けど面倒ならそれは運用回避

function Get-WiFiAvailableNetwork {
    [OutputType([Windows.Devices.WiFi.WiFiAvailableNetwork])]
    [CmdletBinding()]
    param(
        [Parameter(Mandatory,ValueFromPipeline,ParameterSetName='before71')]
        [Windows.Devices.WiFi.WiFiAdapter] $WifiAdapter,
        [Parameter(Mandatory,ValueFromPipeline,ParameterSetName='after71')]
        [WinRT.IInspectable] $WifiAdapter_,
        [Parameter()]
        [string] $Ssid
    )
    process {
        $AvailableNetworks = if ($WifiAdapter_) {
            $WifiAdapter_.AdditionalTypeData.Values[0] | % {
                $_.NetworkReport.AvailableNetworks.AdditionalTypeData.Values[0]
            }
        } else {
            $WifiAdapter.NetworkReport.AvailableNetworks
        }
        if ($Ssid) {
            $AvailableNetworks | Where-Object Ssid -Like $Ssid
        } else {
            $AvailableNetworks
        }
    }
}

やった例
Values[0]なのはそうしないとどう見ても同じものが2つ返ってるから
今のとこ不都合はなさそうだけど正しい対応なのかはわからない

WinRT.IInspectableの出力

ps1xmlでとりあえず中身がなんだかくらいわかるようにした

    <Type>
        <Name>WinRT.IInspectable</Name>
        <Members>
            <ScriptProperty>
                <Name>IInspectable</Name>
                <GetScriptBlock>
                    "{0} ({1})" -f @(
                        $this.AdditionalTypeData.Values[0]
                        ($ada.AdditionalTypeData.Values[0] | gm -MemberType Properties | ? Name -NotIn ('AdditionalTypeData','HasUnwrappableNativeObject','NativeObject') | select -exp Name) -join ', '
                    )
                </GetScriptBlock>
            </ScriptProperty>
            <MemberSet>
                <Name>PSStandardMembers</Name>
                <Members>
                    <PropertySet>
                        <Name>DefaultDisplayPropertySet</Name>
                        <ReferencedProperties>
                            <Name>IInspectable</Name>
                        </ReferencedProperties>
                    </PropertySet>
                </Members>
            </MemberSet>
        </Members>
    </Type>

追記: 2021/03/18

素敵なコメントを頂きましたよ

That’s how it works now
> https://github.com/PowerShell/PowerShell/issues/14991

これにより AdditionalTypeData.Values[0] みたいな記述しないでもよくなりますね

$Creds = [Windows.Security.Credentials.PasswordVault]::new().FindAllByResource($Resource)
$Names = $Creds.AdditionalTypeData.Item([System.Collections.IEnumerable].TypeHandle).UserName

AdditionalTypeDataがなんか辞書っぽいのでキーがわかれば然るべき値が取れるという感じのようです
で、そのキーが [System.Collections.IEnumerable].TypeHandle だったと
リンク先だと
AdditionalTypeData[[System.Collections.IEnumerable].TypeHandle] という書き方してますが
Item() 使わないとだめでした

Values[0]だと良くない場合もあるかもしれんので今後はこれでやっていきましょう

追記: 2021/03/19

WinRTモジュールに以下を書いた

Update-TypeData -MemberType ScriptProperty -MemberName TypeData -Value {
    $this.AdditionalTypeData.Item([System.Collections.IEnumerable].TypeHandle)
} -Force -TypeName WinRT.IInspectable

これはWinRT.IInspectable にTypeDataというプロパティを生やして、本来欲しかった型のデータを取るためのものです
ので機能の追記のであれば

$Names = $Creds.TypeData.UserName

と書けます
そんでまぁ本来欲しかった型が取れるんだから7.1前後対応であれば

function Get-WifiAdapter {
    [OutputType([Windows.Devices.WiFi.WiFiAdapter])]
    [CmdletBinding()]
    param()
    process {
        $adapter = Wait-IAsyncOperation -Method ([Windows.Devices.WiFi.WiFiAdapter]::FindAllAdaptersAsync)
        if ($adapter.GetType() -eq [WinRT.IInspectable]) {
            $adapter.TypeData
        } else {
            $adapter
        }
    }
}

みたいにすればいいよねってことになります
なりました
これでモジュールがいろいろすっきりした

PowerShell 7.1 でWinRTが死んだのでなんとかしていく」への2件のフィードバック

  1. ピンバック: PowerShellでSSID選んでWiFi繋ぐやつ書いた | たっぷす庵

コメントを残す