この記事では、Windows 上のアプリ アクション用のプロバイダー アプリが実装できるいくつかのメカニズムについて説明します。これにより、アクションのクエリまたは呼び出しを実行できるアプリのセットを制限できます。 これは、サブスクリプション ベースの LLM サービスなど、使用ごとのコストを持つリソースを使用するアクションなどのシナリオをサポートするように設計されたオプションの実装手順です。
アクション定義 JSON ファイルで allowedAppInvokers を設定する
アクションをシステムに登録するために使用されるアクション定義 JSON ファイルには、アクション定義ごとに allowedAppInvokers フィールドが含まれています。 このフィールドの値は、 GetActionsForInputs または GetAllActions の呼び出しを通じてアクションを検出できるアプリケーション ユーザー モデル ID (AUMID) の一覧です。 ワイルドカードを利用できます。 "*" はすべての AppUserModelID と一致します。 allowedAppInvokers が省略されているか、空のリストである場合、アプリはアクションを検出できません。 AppUserModelID の詳細については、「 アプリケーション ユーザー モデル ID」を参照してください。 アクション定義 JSON ファイルの詳細については、「 Windows 上のアプリ アクションのアクション定義 JSON スキーマ」を参照してください。
allowedAppInvokers フィールドでは、アクションに対してクエリを実行できるアプリのみが制限されることに注意してください。 アクション プロバイダーがシステムに登録される方法により、 アプリは allowedAppInvokers によるクエリを制限されている場合でも、アクションを呼び出すことができます。 この記事の残りの部分では、実行時にアクションの呼び出し元を制限するさまざまな方法について説明します。
URI 起動のアクティブ化を使用するときに呼び出し元を検出する
次のセクションでは、URI 起動のアクティブ化を使用するアクション プロバイダー アプリについて、実行時にアクションの呼び出し元を検出するさまざまな方法について説明します。
呼び出し元パッケージ ファミリ名を取得する (リッチ アクティベーションのみ)
最新のリッチ アクティブ化を使用するプロバイダー アプリは、起動時にプロバイダー アプリに渡される ProtocolForResultsActivatedEventArgs クラスの CallerPackageFamilyName プロパティの値を確認できます。 値が文字列 "_cw5n1h2txyewy" で終わる場合、Windows によってアクションが呼び出されました。 リッチ アクティブ化の詳細については、 アプリ ライフサイクル API を使用したリッチ アクティブ化に関する情報を参照してください。
const string windowsPFN = "_cw5n1h2txyewy";
protected override void OnLaunched(Microsoft.UI.Xaml.LaunchActivatedEventArgs args)
{
var eventargs = AppInstance.GetCurrent().GetActivatedEventArgs();
if ((eventargs != null) && (eventargs.Kind == ExtendedActivationKind.ProtocolForResults))
{
ProtocolForResultsActivatedEventArgs? protocolForResultsArgs = eventargs.Data as ProtocolForResultsActivatedEventArgs;
if (protocolForResultsArgs.CallerPackageFamilyName.EndsWith(windowsPFN))
{
// Perform the action if invoked by Windows
}
}
_window = new MainWindow();
_window.Activate();
}
$.Token URI クエリ文字列パラメーターを使用します。
URI アクティブ化を使用するアクション プロバイダーは、アクション定義 JSON マニフェスト ファイルでアクションを起動する URI を宣言します。 アプリの呼び出し時に起動 URI のクエリ文字列に一意の識別子トークンを渡すようにアクション ランタイムに指示する特殊なクエリ文字列パラメーター $.Token があります。 次の例は、 $.Token パラメーターを使用して URI を宣言するための構文を示しています。
"invocation": {
"type": "Uri",
"uri": "urilaunchaction-protocol://messageaction?token=${$.Token}",
"inputData": {
"message": "${message.Text}"
}
}
アクションが起動したら、アクティブ化 URI から Token クエリ文字列パラメーターを取得します。 このトークンを GetActionInvocationContextFromToken の呼び出しに渡します。 アクション ランタイムは、アクション プロバイダーの起動時に指定したトークンに対してトークンを検証します。 値が一致する場合は、 有効な ActionInvocationContext オブジェクトが返され、アクションがアクション ランタイムによって呼び出されたことを検証します。
protected override void OnLaunched(Microsoft.UI.Xaml.LaunchActivatedEventArgs args)
{
var eventargs = AppInstance.GetCurrent().GetActivatedEventArgs();
if ((eventargs != null) && (eventargs.Kind == ExtendedActivationKind.ProtocolForResults))
{
ProtocolForResultsActivatedEventArgs? protocolForResultsArgs = eventargs.Data as ProtocolForResultsActivatedEventArgs;
using (ActionRuntime runtime = ActionRuntimeFactory.CreateActionRuntime())
{
var launchUri = protocolForResultsArgs.Uri;
var query = HttpUtility.ParseQueryString(launchUri.Query);
var token = query["Token"];
if(token != null)
{
var context = runtime.GetActionInvocationContextFromToken(token);
if(context == null)
{
// Caller is not Action Runtime
return;
}
}
この例では、Microsoft.AI.Actions NuGet パッケージによって提供されるヘルパー メソッドを使用します。 アクション プロバイダーからこのパッケージを参照する方法については、「 Windows でのアプリ アクションの URI 起動の実装」を参照してください。
COM のアクティブ化から実行時に呼び出し元を検出する
次のセクションでは、実行時にアクションを呼び出したアプリの AUMID を取得するプロセスについて説明します。 このセクションの例では、アクション ランタイムの AUMID を確認します。 アプリは、他のアプリの AUMID でフィルター処理するか、パッケージ ファミリ名 (PFN) を使用して、特定のパッケージに含まれるすべてのアプリをフィルター処理することを選択できます。
CsWin32 Nuget パッケージへの参照を追加する
このセクションに示すコード例では、Win32 API を使用して、呼び出し元のパッケージ ファミリ名を取得します。 この例では、C# アプリから Win32 API をより簡単に呼び出すために、ビルド時に Win32 API の C# ラッパーを生成する CsWin32 NuGet パッケージを使用します。 詳細については、 CsWin32 github リポジトリを参照してください。
- ソリューション エクスプローラーでプロジェクトを右クリックし、[NuGet パッケージの管理...] を選択します。
- [ 参照 ] タブで、"Microsoft.Windows.CsWin32" を検索します。
- このパッケージを選択し、[ インストール] をクリックして、すべてのプロンプトに従います。
このパッケージをインストールすると、C# ラッパーを生成する Win32 API を指定する方法を詳しく説明した readme が表示されます。 そのためには、使用するメソッドを一覧表示するテキスト ファイルを作成する必要があります。
- ソリューション エクスプローラーで、プロジェクトを右クリックし、[Add->New Item...] を選択します。
- TextFile テンプレートを選択します。
- ファイルに "NativeMethods.txt" という名前を付け、[追加] をクリックします。
- NativeMethods.txt をダブルクリックしてファイルを開き、次の行を追加します。
CoRegisterClassObject
CoRevokeClassObject
CoImpersonateClient
OpenThreadToken
GetCurrentThread
CoRevertToSelf
GetPackageFamilyNameFromToken
GetLastError
ActionProvider クラスを更新して、アクション呼び出し元 PFN を確認する
次のヘルパー メソッドは、NativeMethods.txt で指定された Win32 API を使用して呼び出し元プロセスの AUMID を取得し、渡された AUMID と一致する場合は true を返します。 このコードのしくみの詳細については、「 クライアントの偽装」を参照してください。
static bool WasInvokedByAUMID(string aumid)
{
Windows.Win32.Foundation.HRESULT hr = PInvoke.CoImpersonateClient();
var RPC_E_CALL_COMPLETE = unchecked((int)0x80010117);
if (hr.Value == RPC_E_CALL_COMPLETE)
{
return false;
}
Microsoft.Win32.SafeHandles.SafeFileHandle hThreadTok;
bool bRes = PInvoke.OpenThreadToken(
new Microsoft.Win32.SafeHandles.SafeFileHandle(PInvoke.GetCurrentThread(), true),
Windows.Win32.Security.TOKEN_ACCESS_MASK.TOKEN_QUERY,
true,
out hThreadTok
);
if (bRes == false)
{
var e = Marshal.GetLastWin32Error();
Console.WriteLine("Unable to read thread token. " + e);
hThreadTok.Close();
return false;
}
PInvoke.CoRevertToSelf();
var invokerAumid = new Span<char>();
uint aumidLength = 0;
PInvoke.GetApplicationUserModelIdFromToken(hThreadTok, ref aumidLength, invokerAumid);
invokerAumid = new char[aumidLength].AsSpan();
PInvoke.GetApplicationUserModelIdFromToken(hThreadTok, ref aumidLength, invokerAumid);
hThreadTok.Close();
if (invokerAumid.Trim('\0').EndsWith(aumid))
{
return true;
}
else
{
return false;
}
}
CoImpersonateClient を実装する方法のため、アプリの起動プロシージャ内ではなく、アクションの呼び出し内からヘルパー メソッドを呼び出す必要があります。
次の例は、Microsoft.AI.Actions コード生成フレームワークを使用して実装されたアクションのアクション ランタイム AUMID のチェックを示しています。
const string actionRuntimeAUMID = "_cw5n1h2txyewy!ActionRuntime";
[WindowsActionInputCombination(
Inputs = ["Contact", "Message"],
Description = "Send '${Message.Text}' to '${Contact.Text}'"
)]
public async Task<SendMessageResult> SendMessage(
[Entity(Name = "Contact")] string contact,
[Entity(Name = "Message")] string? message,
InvocationContext context)
{
if (!WasInvokedByAUMID(actionRuntimeAUMID))
{
context.Result = ActionInvocationResult.Unavailable;
return new SendMessageResult
{
Text = context.EntityFactory.CreateTextEntity("")
};
}
// Your action logic here
string result = await ProcessMessageAsync(contact, message);
return new SendMessageResult
{
Text = context.EntityFactory.CreateTextEntity(result)
};
}