App Actions on Windowsを試す(実験中)

もくじ
https://tera1707.com/entry/2022/02/06/144447

情報源
https://tera1707.com/entry/2025/08/04/223637

やりたいこと

App Actions on Windowsというのが今(2025夏)熱いらしい。
調べる。

やったことメモ

基本、これのまんまをやってみたときのメモをする。

learn.microsoft.com

App Actionsには、

の2つがあるらしいが、👆のページではCOMアクティベーションをやってみるとのこと。
(もしや、アクションセンターのトーストから自前アプリを起動するときのアレと同じか?)

準備

VS2022開発者コマンドプロンプトを起動する

下記コマンドを実行する

winget configure https://raw.githubusercontent.com/microsoft/winget-dsc/refs/heads/main/samples/Configuration%20files/Learn%20tutorials/Windows%20AI/app_actions_cs.winget

こんなおことわりがでる。yesを選ぶ。

画面取れなかったが、管理者権限を要求する画面のあとに、

Windowsパッケージマネージャークライアントというのがインストールされた。

下記のようになり、いんすとーる完了。

Visual Studio で新しい Windows アプリ プロジェクトを作成する

VS2022を起動

このテンプレートでPJを作成

解説ページの案内の通り、「ExampleAppActionProvider」という名前のslnにし、Windowsのバージョンの10.0.26100を選ぶ。

プロジェクトのファイルの編集を選んで、プロパティグループの中に、下記のプロパティを追加する

<WindowsSdkPackageVersion>10.0.26100.59-preview</WindowsSdkPackageVersion>

※いっぺんビルドして、エラーなくビルドできることを見ておく。
(今回実験時、うまくビルドできた)

アクション定義JSONファイルを追加する

新しい項目の追加で、「registration.json」というファイルをテキストファイルとして追加する。

そのファイルの内容を、下記にする
※MS公式のこの内容のまま👇

{
  "version": 2,
  "actions": [
    {
      "id": "ExampleAppActionProvider.SendMessage",
      "description": "Send a message",
      "icon": "ms-resource://Files/Assets/StoreLogo.png",
      "usesGenerativeAI": false,
      "inputs": [
        {
          "name": "message",
          "kind": "Text"
        }
      ],
      "inputCombinations": [
        {
          "inputs": [
            "message"
          ],
          "description": "Send message '${message.Text}'"
        }
      ],
      "outputs": [
        {
          "name": "response",
          "kind": "Text"
        }
      ],
      "invocation": {
        "type": "COM",
        "clsid": "00001111-aaaa-2222-bbbb-3333cccc4444"
      }
    }
  ]
}

アクション操作を処理するためのActionProviderクラスを追加する

ExampleAppActionProviderのプロジェクトに、新規項目でクラスを追加する。 クラス名を「ActionProvider」にする。

そのクラスに、下記のように書く。

using System;
using Windows.AI.Actions;
using Windows.AI.Actions.Provider;
using Windows.Foundation;

namespace ExampleAppActionProvider;

[System.Runtime.InteropServices.GuidAttribute("00001111-aaaa-2222-bbbb-3333cccc4444")]
public partial class AppActionProvider : IActionProvider
{
    public IAsyncAction InvokeAsync(ActionInvocationContext context)
    {
        throw new NotImplementedException();
    }
}

ただの感想だが、Windows.AI.Actionsを使うらしい。
現時点では、App Actionsのどこに「AI」が関係しているのか、全然わかってない。

あと、ActionInvocationContextには緑波線が出ていて、カーソルを充てると下記のように表示される。

どうやら、現時点ではまだまだ実験段階なAPIらしい。

で、NotImplのところをサンプル通りに書く。

using System;
using System.Threading.Tasks;
using Windows.AI.Actions;
using Windows.AI.Actions.Provider;
using Windows.Foundation;

namespace ExampleAppActionProvider;

[System.Runtime.InteropServices.GuidAttribute("00001111-aaaa-2222-bbbb-3333cccc4444")]
public partial class AppActionProvider : IActionProvider
{
    public IAsyncAction InvokeAsync(ActionInvocationContext context)
    {
        return InvokeAsyncHelper(context).AsAsyncAction();
    }

    async Task InvokeAsyncHelper(ActionInvocationContext context)
    {
        NamedActionEntity[] inputs = context.GetInputEntities();

        //var actionId = context.ActionId;
        var actionId = context.ActionName;
        switch (actionId)
        {
            case "ExampleActionProvider.SendMessage":
                foreach (NamedActionEntity inputEntity in inputs)
                {
                    if (inputEntity.Name.Equals("message", StringComparison.Ordinal))
                    {
                        TextActionEntity entity = (TextActionEntity)(inputEntity.Entity);
                        string message = entity.Text;

                        // TODO: Process the message and generate a response

                        string response = "This is the message response";
                        TextActionEntity result = context.EntityFactory.CreateTextEntity(response);
                        context.SetOutputEntity("response", result);
                    }
                }
                break;
            default:
                break;

        }
    }
}

ここで一旦ビルドしとく。

InvokeAsyncHelper()内のvar actionId = context.ActionId;が、25/08/02時点ではActionIdが無いというエラーになったので、一旦上のコードのように適当に直した。(まだそれが正しいのか不明。のちに試す)

現時点では、多くのクラスに「現状評価目的です」表示が出てる。

この「InvokeAsyncHelper()」メソッドでやっていることは、登録のために作ったregistration.jsonと深くかかわってるっぽい。

下の図のように関連づいてる様子。

アプリパッケージのマニフェストファイルを更新する

今日はここまで。

明日この項目から試す。

https://learn.microsoft.com/en-us/windows/ai/app-actions/actions-get-started?tabs=winget#update-the-app-package-manifest-file

続き。

Package.appxmanifest のコードを開く。

MSのdocに従って、下記のように直す。
左側に黄色のラインがついている部分が追加したところ。

<?xml version="1.0" encoding="utf-8"?>

<Package
  xmlns="http://schemas.microsoft.com/appx/manifest/foundation/windows10"
  xmlns:mp="http://schemas.microsoft.com/appx/2014/phone/manifest"
  xmlns:uap="http://schemas.microsoft.com/appx/manifest/uap/windows10"
  xmlns:rescap="http://schemas.microsoft.com/appx/manifest/foundation/windows10/restrictedcapabilities"
  xmlns:uap3="http://schemas.microsoft.com/appx/manifest/uap/windows10/3"
  xmlns:com="http://schemas.microsoft.com/appx/manifest/com/windows10"
  xmlns:com2="http://schemas.microsoft.com/appx/manifest/com/windows10/2"
  xmlns:com3="http://schemas.microsoft.com/appx/manifest/com/windows10/3"
  IgnorableNamespaces="uap rescap">

  <Identity
    Name="0542c349-0636-47e5-87b0-9f7482ed57e1"
    Publisher="CN=masa"
    Version="1.0.0.0" />

  <mp:PhoneIdentity PhoneProductId="0542c349-0636-47e5-87b0-9f7482ed57e1" PhonePublisherId="00000000-0000-0000-0000-000000000000"/>

  <Properties>
    <DisplayName>ExampleAppActionProvider (Package)</DisplayName>
    <PublisherDisplayName>masa</PublisherDisplayName>
    <Logo>Images\StoreLogo.png</Logo>
  </Properties>

  <Dependencies>
    <TargetDeviceFamily Name="Windows.Universal" MinVersion="10.0.17763.0" MaxVersionTested="10.0.19041.0" />
    <TargetDeviceFamily Name="Windows.Desktop" MinVersion="10.0.17763.0" MaxVersionTested="10.0.19041.0" />
  </Dependencies>

  <Resources>
    <Resource Language="x-generate"/>
  </Resources>

  <Applications>
    <Application Id="App"
      Executable="$targetnametoken$.exe"
      EntryPoint="$targetentrypoint$">
      <uap:VisualElements
        DisplayName="ExampleAppActionProvider (Package)"
        Description="ExampleAppActionProvider (Package)"
        BackgroundColor="transparent"
        Square150x150Logo="Images\Square150x150Logo.png"
        Square44x44Logo="Images\Square44x44Logo.png">
        <uap:DefaultTile Wide310x150Logo="Images\Wide310x150Logo.png" />
        <uap:SplashScreen Image="Images\SplashScreen.png" />
      </uap:VisualElements>

      <Extensions>
        <com2:Extension Category="windows.comServer">
            <com2:ComServer>
                <com3:ExeServer Executable="ExampleAppActionProvider.exe" DisplayName="ExampleAppActionProvider">
                    <com:Class Id="00001111-aaaa-2222-bbbb-3333cccc4444" DisplayName="ExampleAppActionProvider" />
                </com3:ExeServer>
            </com2:ComServer>
        </com2:Extension>
        <uap3:Extension Category="windows.appExtension">
            <uap3:AppExtension Name="com.microsoft.windows.ai.actions" DisplayName="Example App Action Provider" Id="appactionprovider" PublicFolder="Assets">
                <uap3:Properties>
                    <Registration xmlns="">registration.json</Registration>
                </uap3:Properties>
            </uap3:AppExtension>
        </uap3:Extension>
      </Extensions>
        
    </Application>
  </Applications>

  <Capabilities>
    <rescap:Capability Name="runFullTrust" />
  </Capabilities>
</Package>

リクエストに応じてIActionProviderをインスタンス化するクラスファクトリーを実装する

FactoryHelper.csという名前でクラスを作る。

MSのページの通りにコードを書く。

// FactoryHelper.cs

using Microsoft.Windows.Widgets.Providers;
using System;
using System.Runtime.InteropServices;
using Windows.AI.Actions.Provider;
using WinRT;

namespace COM
{
    static class Guids
    {
        public const string IClassFactory = "00000001-0000-0000-C000-000000000046";
        public const string IUnknown = "00000000-0000-0000-C000-000000000046";
    }

    /// 
    /// IClassFactory declaration
    /// 
    [ComImport, ComVisible(false), InterfaceType(ComInterfaceType.InterfaceIsIUnknown), Guid(COM.Guids.IClassFactory)]
    internal interface IClassFactory
    {
        [PreserveSig]
        int CreateInstance(IntPtr pUnkOuter, ref Guid riid, out IntPtr ppvObject);
        [PreserveSig]
        int LockServer(bool fLock);
    }

    [ComVisible(true)]
    class WidgetProviderFactory<T> : IClassFactory
    where T : IActionProvider, new()
    {
        public int CreateInstance(IntPtr pUnkOuter, ref Guid riid, out IntPtr ppvObject)
        {
            ppvObject = IntPtr.Zero;

            if (pUnkOuter != IntPtr.Zero)
            {
                Marshal.ThrowExceptionForHR(CLASS_E_NOAGGREGATION);
            }

            if (riid == typeof(T).GUID || riid == Guid.Parse(COM.Guids.IUnknown))
            {
                // Create the instance of the .NET object
                ppvObject = MarshalInspectable<IActionProvider>.FromManaged(new T());
            }
            else
            {
                // The object that ppvObject points to does not support the
                // interface identified by riid.
                Marshal.ThrowExceptionForHR(E_NOINTERFACE);
            }

            return 0;
        }

        int IClassFactory.LockServer(bool fLock)
        {
            return 0;
        }

        private const int CLASS_E_NOAGGREGATION = -2147221232;
        private const int E_NOINTERFACE = -2147467262;

    }
}

カスタムMainメソッドを実装する

csprojファイルに下記を追加する

<DefineConstants>$(DefineConstants);DISABLE_XAML_GENERATED_MAIN</DefineConstants>

Program.csを新規で追加し、内容を下記のようにする。
※ここは、サンプルコードのまんまではビルドできなかった。↓は少し変えてる。

// Program.cs

using COM;
using ExampleAppActionProvider;
//using ExampleWidgetProvider;
using Microsoft.UI.Dispatching;
using Microsoft.UI.Xaml;
using Microsoft.Windows.Widgets;
using System;
using System.Runtime.InteropServices;
using System.Threading;
using ComTypes = System.Runtime.InteropServices.ComTypes;


[DllImport("ole32.dll")]

static extern int CoRegisterClassObject(
            [MarshalAs(UnmanagedType.LPStruct)] Guid rclsid,
            [MarshalAs(UnmanagedType.IUnknown)] object pUnk,
            uint dwClsContext,
            uint flags,
            out uint lpdwRegister);

[DllImport("ole32.dll")] static extern int CoRevokeClassObject(uint dwRegister);

uint cookie;

Guid CLSID_Factory = Guid.Parse("00001111-aaaa-2222-bbbb-3333cccc4444");
CoRegisterClassObject(CLSID_Factory, new WidgetProviderFactory<AppActionProvider>(), 0x4, 0x1, out cookie);

Application.Start((p) =>
{
    var context = new DispatcherQueueSynchronizationContext(
        DispatcherQueue.GetForCurrentThread());
    SynchronizationContext.SetSynchronizationContext(context);
    _ = new App();
});

//PInvoke.CoRevokeClassObject(cookie);
CoRevokeClassObject(cookie);

return 0;

Windows アプリアクションをテストする

ビルドしたものを動かす前に一度App Actions Testing Playgroundを開く。下記のようになった。
(たぶん、PCによって、すでにApp Actionが登録されてたりすると、見た目(登録されてるAction)が変わると思う)

動かしても、👆の画面から変わらなかった。
また、「登録」のページの「アクション定義」も何もない状態になった。

確認したら、registration.jsonのビルドアクションを「コンテンツ」にしていなかった。やる。 「新しい場合はコピー」にも直す。

まだアクション定義がなしになる。

Publicフォルダがない、的なエラーなので、確認したら、Package.appxmanifestに書いてあるPublicFolderに「Assets」フォルダと書いてるが、実際に出力されたフォルダの中に「Assets」が無かった。

AssetsフォルダをPJに追加してあげて、そこにregistration.jsonを入れた。

そうすると、下図のようにアクション定義が出てきた。

が、「アクションカタログ」のほうにはまだなにも出てこない。

今日はここまで。明日なんでか調べる。

👇これをビルドしてみてもいいかも

https://github.com/microsoft/App-Actions-On-Windows-Samples/tree/main/samples/com/cs/MyMessagingApp


ビルドしてみた。そのアプリも、アクションカタログにはなにも出てこない。

どうも、カタログに何も出ないのはそれでよいっぽい?

アクションカタログに出てくるサンプルアプリは下のような感じで、Playgroundアプリでallowするよ、という設定がされてるっぽく、それで出てくるのかも?

→今一度、そのjsonの書き方を確認してみる。この辺?

https://learn.microsoft.com/en-us/windows/ai/app-actions/actions-availability

https://learn.microsoft.com/en-us/windows/ai/app-actions/actions-json

この辺か。。。

====

そういえば、情報源の方に挙げた、App Actionsの解説動画の中で、App Actionsには、

  • Providerと
  • Consumerがある

というのを見た。

で、今まで👆で作ってきたのはProviderだと思うが、カタログに出ないと言っているTesting PlaygroundアプリはConsumerだと思う。で、たぶんPlaygroundの中では、

  • Windowsに登録されているActionProviderを取得
  • それをカタログに出す

ということをしていると言ってたと思うので、Consumerの作り方を見てみたら、なんでPlaygourndに👆で作ったアプリがでないのかわかるかもしれない。

===

じしんはないが、たぶん、、、

InvocationのタイプがCOMなら、レジストリ登録さえしてやれば、パッケージしてないexeでもIActionProviderを実装すれば、AppActionsを利用できるのでは?(自信なし)←追記:違うっぽい?
(MSDocには、comもuriも、パッケージIDが必要、とは書いてあるが、、、トーストのときもそういうこと書いてたが、ただのexeでも、パスとclsid登録すれば使えたし...)

URIはパッケージアプリなら簡単に実装できる(IActionProviderを実装しなくてもよい?)ものなのでは?(自信なし)

→一度、両方を作ってみる。

===

やっぱり、allowedAppInvokersをregistration.jsonに追加して、PlaygroundアプリのIDを書いてやると、Playgroundのアクションカタログに出てきた。おそらく、Consumer(Playgroundアプリ)側で、allowされてるものだけwhereとかで出すようにしてるか、そもそもの仕組みで、allowedAppInvokersで指定されたConsumer側にしかActionの情報が渡されないんだろうと思う。

まだActionProvider側も不明点があるが、一旦形にはなったので、 Cosumer側を次に作ってみて、お互いの関係を見ようと思う。

※ざっとCosumer側の作り方の情報を探してみたが、見当たらず。。
→もう一度、youtubeの動画を見直してみることにする。


provider側のメモp

  • registration.json(上のサンプルでいうところの)が、ProviderパッケージアプリとWindowsの接点になる。
  • なので、Providerパッケージアプリのappxpackageファイルに書いてあるパッケージIDとregistration.jsonに書いてあるIDが一致してる必要がある。(この辺を図示したい)
  • COM形式の場合は、COMのGUIDも、その2つのファイル間で一致してないといけない。(あれ?こっちだけだったか?)

8/10

Consumerをつくるための情報があった。

上で見たApp Actionの動画の関連動画として出ていた「LAB370」の動画で、Provider側とConsumer側の両方を作っていたっぽい。
※私が見た時点で、動画の硬貨が終わっていたので、動画は見れず。ただサンプルコードは見れたのでそれを順番にやっていってみる。

動画(見れず)

https://build.microsoft.com/en-US/sessions/LAB370

サンプルコード(上の動画ページに、リンクが張ってあった)

https://github.com/microsoft/Build25-LAB370/tree/main

このサンプルのreadmeから順番に辿っていくと、ActionProvier→Consumerと順番につくらせてもらえるっぽい。まずはこれを順番にやってみる。

...やってみようとしたのだが、このサンプルリポジトリよりもコードが古いのか(コミット的には1か月くらい古い?)、ActionProvider側のコードをビルドしようとすると、「2-dev-setup.md」の中にある説明「Lab370DevTools.zip」をダウンロードせよ、のそのzipがなかったり、「Microsoft.ActionsTestingTool.zip」がなかったり、ビルドしてみても「Microsoft.AI.Actions.Annotations.0.1.0.nupkg 」が無いせいでビルドが通らなかったりした。
→いったんあきらめる。

このサンプルの中に、Consumerのサンプルがあった。
「experimental」という名前のフォルダだったのでなんとなく避けてたが、そこにあった。(まだ、Consumer自体が実験段階?)

とりあえず、試してみる。

https://github.com/microsoft/App-Actions-On-Windows-Samples/tree/508d6ff123e034e88e34e84157b3ecc29d07a497/samples/experimental

→こちらのサンプルは、とりあえずビルドは通った。

ビルドしたものを動かして、Actionのリストに、作ったActionProviderのアクションが出るか見たが、でなかった。

※👇をおしたときに、アクションの一覧がフライアウトで出るはずがでない。

で、テスト用Consumerとして使っていたApp Actions Testing Playgroundのときも、AllowedAppInvokersに、アプリのパッケージIDをいれないとでてこなかったなというのを思い出し、入れてみた。

※入れる前に、ConsumerのサンプルのパッケージIDが、ただの「App」だけになっていたので、そこだけ変えた。

※アプリのパッケージIDが何なのかの見方は、以前調べた👇の記事参照。

http://インストールされてるアプリのAUMID(アプリケーションユーザーモデルID)を列挙する & Start-Processでそのアプリを起動する

そうすると、自分でビルドしたActionProviderが、Consumerアプリ上に出てきた。

とりあえず動いた....

どうも、今試した限りだと、

  • AllowedAppInvokerにアプリのパッケージIDを入れられてるアプリのうち、
  • コードで指定したEntityを持ってる奴を、
  • ActionManager.Instance.ActionRuntime.ActionCatalog.GetActionsForInputs()メソッドで取ってくる

感じのコードになっている様子。

GetActionsForInputs()でInputで選ぶ形ではなく、他にも取ってくる方法があるのでは?と思う。(まだ試してないので推測。問答無用で全部取ってくる、とかもあるのかも?)

明日は、もうちょっといろいろConsumerを試してみる。

====

上のサンプルの、このあたりのコードをいじってみた。

https://github.com/microsoft/App-Actions-On-Windows-Samples/blob/508d6ff123e034e88e34e84157b3ecc29d07a497/samples/experimental/consumer/cs/ActionConsumerSampleApp/MainWindow.xaml.cs#L47

ここで、以下のようなことをしている。

  • アクションのカタログを取得するためのCOMのローカルサーバーを作成
    • IIDは206EFA2C-C909-508A-B4B0-9482BE96DB9C
    • classidはC36FEF7E-35F3-4192-9F2C-AF1FD425FB85
    • これらは開発中なので今後かわるかも?とコードにコメントでかかれてた
  • そいつがうまく作成できてなかったらアクション取得失敗でおわる
  • そのCOMポインタをWindows.AI.Actions.ActionRuntimeとする
  • アクションにinputしたいEntityを、型を指定して作成する(例だとTextだが、Photoなどがあるらしい)
  • ActionRuntimeActionCatalogGetActionsForInputs()に、作成したEntityを渡す
  • 渡したEntityの型に合わせて、そのEntityのために使えそう、かつ、このアプリに使用許可があるActionが取得されてくる。
    • 使用許可があるかは、そのアクションのregistration.json(アクション側で書いた、アクションの定義json)の
      allowedAppInvokersにSoncumer側のアプリIDが書かれてるかで決まるっぽい
  • 取れてきたアクションの名前を画面のフライアウトに一覧表示する

で、そのフライアウトのクリック処理で、アクションを実行してる。

  • 取得したアクションのActionInstanceInvokeAsync()を実行する
  • アクション実行後、item.Context.GetOutputEntities()で、アクションの結果を取得する。
    • 結果はNamedActionEntity[]として取得される。
  • 取得した結果もEntityで帰ってくる。そのentity.Entityを、しかるべきEntityのタイプ(例だとTextActionEntity)にキャストし受ける。
  • テキストの場合はそのEntity.Textで受けれる。
  • それを画面表示

という感じ。

後ろの方は分かりやすいが、入口のActionを取得する部分でCOMが見えてるのがなんとも使いにくい。 WinUI3のようだと思った。
Windowsの「Experimental」的なAPIはすべてここから始まるのか)

が、とりあえず、一通りAction登録からそれをConsumeするところまではできた。

これをもう少しわかりやすく自分の中で整理してコードを書いて、簡単にまとめたい。


25/08/13

Providerのほうが、アクションカタログには出てきていたが、実行したときにActionProvider側のIActionProviderを実装したクラスのInvokeActionが実行されていないことがわかった。

これが、どうしてそうなるか全然わからない。 うまくいっているサンプルと見比べると、COMに登録するときにOSSを使っているなどの違いがあったので、そのOSSを使うように改造してみてもうまくいかない。

以前、トーストを押したときにアプリが起動してくれるようにしたときに、COMをあれこれいじったときと似たようなことになっている。

明日、うまくいっている方のActionProvider側を真似て、0から自作Providerを作ってみる。 (しょーもないミスというか、COM周りのレジストリの登録がうまくいってない、とかが原因なきがする)

25/08/13-2

上でInvokeActionが実行されていない原因が分かった。2つあった。

  • WindowsSdkPackageVersionが古かった
  • Package.appxmanifestWindows.UniversalWindows.Desktop のバージョンが古かった

それぞれ、

<!-- これが「10.0.26100.59-preview」になっていた -->
<WindowsSdkPackageVersion>10.0.26100.67-preview</WindowsSdkPackageVersion> 
<!-- これがプロジェクト作成時のままになっていた -->
<TargetDeviceFamily Name="Windows.Universal" MinVersion="10.0.22000.0" MaxVersionTested="10.0.22621.0" />
<TargetDeviceFamily Name="Windows.Desktop" MinVersion="10.0.22000.0" MaxVersionTested="10.0.22621.0" />

これを直すと、InvokeActionを通るようになった。


おそらくこれが、コンシューマーの作り方の公式docっぽい。

https://learn.microsoft.com/en-us/windows/ai/app-actions/actions-consume


ActionProviderのinputCombinationsに、テキストのエンティティを2つとるようなcombinationをp書いているときに、

コンシューマ側で、下図のようにテキストエンティティを2つ、

GetActionsForInputs()に食わせると、 下図のように、2つのエンティティ枠に3つのエンティティの組み合わせで渡したパターンの6通りがActionInstance[]として取れてきた。

※下の図では、そのCombi以外に、1つのテキストEntityを取るCombiが1つあるので、そいつに3つのエンティティの組み合わせを渡したパターンの3つと合わせて、9個の一覧が取れてきた。

うーん、凄く直感的でない、変な取れ方、、、(現状、experimentalだからしょうがないのか。)