-
メールを作成
UpdateContext( {mailBody: $"※メールの自動作成です。 ================================== アプリ内容を確認してください。 ■テストアプリ {appUrl}?itemId={itemInfo.ID} ■コメント {txtCooment.Text}" } ); Lanch($"mailto:{itemInfo.テストユーザー.Email}", { cc: $"test@test.onmicrosoft.com;{Concat(itemInfo.テストユーザー2.Email, ";"}{Concat(itemInfo.テストユーザー3.Email, ";"}", subject: $"【テストメール】{itemInfo.タイトル}_{itemInfo.ID}", body: mailBody } );
【Power Apps】Teamsのチームの特定チャネルに投稿
-
Teamsのチームの特定チャネルに投稿
UpdateContext({threadId: MicrosoftTeams.PostMessageToChanelV3( teamID, chanelID, { content:$"<at>{userInfo.userPrincipalName}</at> <at>{Concat(itemInfo.テストユーザー, Substitute(Claims, "i:0#.f|membership|", ""), "</at><at>")}</at> <p>{itemInfo.タイトル}についてこちらのスレッドでやり取りさせてください。</p><br> <a href = ""{appUrl}?itemid={itemInfo.ID}"">アプリへのリンク</a>", contentType:"html" }, { subject:$"{itemInfo.タイトル}について" } ).id} ); Patch('テストテーブル', itemInfo, {スレッドID: threadId});
【Dataverse】Visual StudioによるDataverse Plugin(Post Operation)
accountテーブルのtelephone1に登録後、関連するcontactのtelephone1に値を非同期でコピーする場合の手順は以下の通りです。
クラスファイルはプロジェクト内に2つあっても問題ありません。
1つのAssembly(dllファイル)に2つのPluginが出来上がる形になります。
クラスファイルの中身以外は以前の手順踏襲となります。
-
クラスファイルの編集
クラス名を変更し、開発を行います。using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using System.ServiceModel; using Microsoft.Xrm.Sdk; using System.Text.RegularExpressions; using Microsoft.Xrm.Sdk.Query; namespace MyDataversePluginVS462 { public class FollowupPlugin3 : IPlugin { public void Execute(IServiceProvider serviceProvider) { // プラグイン実行コンテキストの取得 IPluginExecutionContext context = serviceProvider.GetService(typeof(IPluginExecutionContext)) as IPluginExecutionContext; if (context == null) { throw new InvalidPluginExecutionException("プラグイン実行コンテキストが取得できませんでした。"); } // 組織サービスファクトリの取得 IOrganizationServiceFactory serviceFactory = serviceProvider.GetService(typeof(IOrganizationServiceFactory)) as IOrganizationServiceFactory; if (serviceFactory == null) { throw new InvalidOperationException("IOrganizationServiceFactory が取得できませんでした。"); } // 組織サービスの取得(データ操作用) IOrganizationService service = serviceFactory.CreateOrganizationService(context.UserId); // トレーシングサービスの取得 ITracingService tracingService = serviceProvider.GetService(typeof(ITracingService)) as ITracingService; if (tracingService == null) { throw new InvalidPluginExecutionException("トレーシングサービスが取得できませんでした。"); } // PostOperationの場合、InputParametersにTargetは存在しないため、OutputParameterから取得する。 // ①Createの場合、context.Outputparameters["id"]に作成したレコードのGUIDが含まれている。 // ②Updateの場合、context.InputParameters["Target"]に更新したレコードが含まれているが、 // PostOperationでは、更新後のデータが必要となるため、通常は取得したレコードを再度、サービスで取得し直す。 // ③Deleteの場合、context.InputParameters["Target"]に削除したレコードが含まれている。 // 今回はCreateとUpdateを利用します。 Entity account = null; if (context.MessageName == "Create" && context.OutputParameters.Contains("id")) { // 作成したレコードのGUIDを取得 Guid accountId = (Guid)context.OutputParameters["id"]; tracingService.Trace($"Account created with GUID:{accountId}"); // 作成したAccountを再取得 account = service.Retrieve("account", accountId, new ColumnSet("telephone1", "primarycontactid")); } else if (context.MessageName == "Update" && context.InputParameters.Contains("Target") && context.InputParameters["Target"] is Entity) { // 更新したレコードを取得 // ※InputParametersのTargetは更新項目のみしか含まないため、再取得 Entity updateAccount = (Entity)context.InputParameters["Target"]; tracingService.Trace($"Account update with GUID:{updateAccount.Id}"); account = service.Retrieve("account", updateAccount.Id, new ColumnSet("telephone1", "primarycontactid")); } if (account == null) { tracingService.Trace("Account entity not found in context. Exiting plugin."); return; } // Accountの電話番号が空でないかチェック if (account.Contains("telephone1") && !string.IsNullOrWhiteSpace(account.GetAttributeValue<string>("telephone1"))) { string accountPhoneNumber = account.GetAttributeValue<string>("telephone1"); tracingService.Trace($"Account Phone Number: {accountPhoneNumber}"); // Primary Contanct(主取引先担当者)が設定されているかチェック if (account.Contains("primarycontactid") && account.GetAttributeValue<EntityReference>("primarycontactid") != null) { EntityReference primaryContactRef = account.GetAttributeValue<EntityReference>("primarycontactid"); tracingService.Trace($"Primary Contanct GUID: {primaryContactRef.Id}"); try { // ContactレコードをRetrieveして、電話番号が既に設定されているかチェック Entity contact = service.Retrieve("contact", primaryContactRef.Id, new ColumnSet("telephone1")); // Contactの電話番号が空欄の場合、または、Accountの電話番号と異なる場合、更新 if (!contact.Contains("telephone1") || contact.GetAttributeValue<string>("telephone1") != accountPhoneNumber) { Entity contactToUpdate = new Entity("contact", primaryContactRef.Id); contactToUpdate["telephone1"] = accountPhoneNumber; service.Update(contactToUpdate); tracingService.Trace($"Successfully updated Primary Cntact {primaryContactRef.Name} phone number to: {accountPhoneNumber}"); } else { tracingService.Trace($"Primary Contact phone number is alredy set or matches Account's. No update needed."); } } catch (Exception ex) { tracingService.Trace($"Error updating Primary Cotact phone number: {ex.Message}"); // 本番環境では、InvalidPluginExecutionExceptionをスローしてロールバックするのが良い。 //throw new InvalidPluginExecutionException("An error occurred during phone number synchronization.", ex); } } else { tracingService.Trace("No Primary Contact associated with this Account."); } } else { tracingService.Trace("Account does not have a telephone number or its's empty."); } // 処理全体をtry-catchで囲って、catchでInvalidPluginExecutionExceptionするのもあり。 } } }
【Dataverse】Visual StudioによるDataverse Plugin(Pre Validation)
accountテーブルのtelephone1の入力チェックを作成する場合の手順は以下の通りです。
入力チェックは正規表現で「^[0-9]+$」とします。
クラスファイルはプロジェクト内に2つあっても問題ありません。
1つのAssembly(dllファイル)に2つのPluginが出来上がる形になります。
クラスファイルの中身以外は以前の手順踏襲となります。
-
クラスファイルの編集
クラス名を変更し、開発を行います。using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using System.ServiceModel; using Microsoft.Xrm.Sdk; using System.Text.RegularExpressions; namespace MyDataversePluginVS462 { public class FollowupPlugin2 : IPlugin { public void Execute(IServiceProvider serviceProvider) { // プラグイン実行コンテキストの取得 IPluginExecutionContext context = serviceProvider.GetService(typeof(IPluginExecutionContext)) as IPluginExecutionContext; if (context == null) { throw new InvalidPluginExecutionException("プラグイン実行コンテキストが取得できませんでした。"); } // 組織サービスファクトリの取得 IOrganizationServiceFactory serviceFactory = serviceProvider.GetService(typeof(IOrganizationServiceFactory)) as IOrganizationServiceFactory; if (serviceFactory == null) { throw new InvalidOperationException("IOrganizationServiceFactory が取得できませんでした。"); } // トレーシングサービスの取得 ITracingService tracingService = serviceProvider.GetService(typeof(ITracingService)) as ITracingService; if (tracingService == null) { throw new InvalidPluginExecutionException("トレーシングサービスが取得できませんでした。"); } // ターゲットエンティティがAccountであるか if (context.InputParameters.Contains("Target") && context.InputParameters["Target"] is Entity account && account.LogicalName == "account") { tracingService.Trace("PhoneCheckプラグインが実行されました。"); // ターゲットエンティティのCreateかどうか if (context.MessageName == "Create") { // 電話番号フィールドが存在するか if (account.Attributes.Contains("telephone1")) { string phoneNumber = account.Attributes["telephone1"] as string; if (!string.IsNullOrEmpty(phoneNumber) && Regex.IsMatch(phoneNumber, "^[0-9]+$")) { tracingService.Trace("電話番号の書式は問題ありません。"); } else { throw new InvalidPluginExecutionException("新規登録時は電話番号の書式は数値のみで入力してください。"); } } else { throw new InvalidPluginExecutionException("電話番号(telephone1)フィールドが存在しません。"); } } else { throw new InvalidPluginExecutionException("プラグインが実行できませんでした。(メッセージ違い)"); } } else { throw new InvalidPluginExecutionException("プラグインが実行できませんでした。(テーブル違い)"); } // 処理全体をtry-catchで囲って、catchでInvalidPluginExecutionExceptionするのもあり。 } } }
【Dataverse】Visual StudioによるDataverse Plugin(Pre Operation)
accountテーブルのtelephone1に「81-」を付与するPluginを作成する場合の手順は以下の通りです。
-
Visual Studioの導入
Visual Studio 2022の無料版をインストールします。
「Visual Studio 2022」「Visual Studio Installer」「Blend for Visual Studio 2022」の3つがアプリとして表示されます。Visual Studio 2022 →通常の開発用 Visual Studio Installer →.NetFrameworkやWEBアプリ開発に必要なパッケージのインストールツール Blend for Visual Studio 2022 →アニメーション、storyboards、behaviorsの開発用 Visual Studio 2022のプロジェクトファイルと互換性あり
-
プロジェクト作成
「クラスライブラリ(.NetFramework)」のプロジェクトを作成し、
フレームワークに「.NetFramework4.6.2」を選択します。
-
NuGetパッケージの管理
ソリューションエクスプローラーでプロジェクトを右クリックし、「NuGetパッケージの管理」を選択します。
「Microsoft.CrmSdk.CoreAssemblies」の最新版をインストールします。
-
クラスファイルの編集
クラス名を変更し、開発を行います。using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using System.ServiceModel; using Microsoft.Xrm.Sdk; namespace MyDataversePluginVS462 { public class FollowupPlugin : IPlugin { public void Execute(IServiceProvider serviceProvider) { // プラグイン実行コンテキストの取得 IPluginExecutionContext context = serviceProvider.GetService(typeof(IPluginExecutionContext)) as IPluginExecutionContext; if (context == null) { throw new InvalidPluginExecutionException("プラグイン実行コンテキストが取得できませんでした。"); } // 組織サービスファクトリの取得 IOrganizationServiceFactory serviceFactory = serviceProvider.GetService(typeof(IOrganizationServiceFactory)) as IOrganizationServiceFactory; if (serviceFactory == null) { throw new InvalidOperationException("IOrganizationServiceFactory が取得できませんでした。"); } // トレーシングサービスの取得 ITracingService tracingService = serviceProvider.GetService(typeof(ITracingService)) as ITracingService; if (tracingService == null) { throw new InvalidPluginExecutionException("トレーシングサービスが取得できませんでした。"); } // イベントの種類がCreateであり、ターゲットエンティティがAccountであるか if (context.InputParameters.Contains("Target") && context.InputParameters["Target"] is Entity account && account.LogicalName == "account") { tracingService.Trace("AddPhonePrefixプラグインが実行されました。"); // 電話番号フィールドが存在するか if (account.Attributes.Contains("telephone1")) { string phoneNumber = account.Attributes["telephone1"] as string; if (!string.IsNullOrEmpty(phoneNumber)) { // 電話番号にプレフィックスを追加 account.Attributes["telephone1"] = "+81-" + phoneNumber; tracingService.Trace($"電話暗号を'{phoneNumber}'から'+81-{phoneNumber}'に変更しました。"); } } } else { throw new InvalidPluginExecutionException("プラグインが実行できませんでした。"); } //throw new NotImplementedException(); } } }
-
アセンブラの署名
ソリューションエクスプローラーでプロジェクトを右クリックし、「プロパティ」を選択します。
左メニューから「署名」を選択し、キーファイルに任意の名前を入力します。
「キーファイルをパスワードで保護します」のチェックを外します。
署名アルゴリズムはデフォルトのままで署名を行います。
-
ソリューションのビルド
ソリューションのビルドを行います。
プロジェクトファイルの場所にもよりますが、デフォルトの場合、以下にdllファイルが出力されます。
C:\Users\ユーザー名\source\repos\MyDataversePluginVS462\MyDataversePluginVS462\bin\Debug
【Dataverse】Dataverse Pluginの追加
accountテーブルのtelephone1に「81-」を付与するPluginを登録する場合の手順は以下の通りです。
-
Plugin Registration Tool起動
Plugin Registration Toolを起動し、「CREATE NEW CONNECTION」を選択します。
「Display list of available organizations」を選択するとMicorosft 365に接続するウィンドウが起動するのでサインインを行います。
※この時、VSCode等で対象のテナントに接続済みである必要があるかもしれません。
-
dllファイルの登録
画面上部の「Register」-「Register New Assembly」を選択します。
コンパイル後のdllファイルを選択します。
Sandbox、Dataverseが選択されていることを確認し、「Register Selected Plugins」を押下します。
AssemblyとPluginが登録されます。
-
PluginのStep登録
登録されたPluginを選択し、右クリックし、「Register New Step」を選択します。
以下をそれぞれ設定し、「Register New Step」を押下します。
Message:Create
Primary Entity:account
Event Pipeline Stage of Execution:Pre Operation
Exection Mode:Synchronous(同期)
-
トレースログON
D365詳細設定画面を開き、「システム」-「管理」-「カスタマイズ」を開く。
「プラグインおよびユーザー定義ワークフロー活動の追跡」を「Off」から「All」に変更する。
トレースログは「システム」-「カスタマイズ」-「プラグイントレースログ」で確認できる。
-
動作確認
Power Apps(キャンパスアプリ)、Power Apps(モデル駆動型アプリ)、Power Automateのどれで登録しても
telephone1に登録後に「81-」が付与される。
【VisualStudio】Windowsフォームアプリケーションの違い
-
Windowsフォームアプリケーションの作成
Visuaul Studio 2022でWindowsフォームアプリケーションを作成しようとすると
4種類ほど種類があったので、整理します。
-
C# Windowsフォームアプリ
.NET Core系のWindowsフォームアプリケーションを作成できる。
クロスプラットフォーム用。
.NET CoreはWindowsに標準添付されていないため、アプリケーションを配布する際には、
.NET Coreの事前インストールを要求する必要がある。
-
C# Windowsフォームアプリケーション(.NET Framework)
.NET Framework系のWindowsフォームアプリケーションを作成できる。
Windows専用。
Windowsに標準添付されているが、4.8で開発が完了している。
-
C# WPFアプリケーション
最もモダンなWindowsアプリケーションの開発形態。
※今後は「Windows App SDK」に進もうとしている。
WPF (Windows Presentation Foundation)では、XAML (eXtensible Application Markup Language)でGUIを定義し、
.NET Coreを活用してロジックを構築する。別の言い方をすると、MVVM (Model View ViewModel)アーキテクチャに基づいて設計、実装する。
従来まではFormにアプリケーションのGUIとロジックを記載していたが、
GUIとロジックを分離する設計、実装方式となる。
-
VB Windowsフォームアプリケーション(.NET Framework)
VBで.NET Framework系のWindowsフォームアプリケーションを作成できる。
Windows専用。
古い。
【VisualStudio】各種ツールやFrameworkの導入
-
各種ツールやFrameworkの導入
Visual Studioの初回導入後にツールや.NETFrameworkを追加、変更、削除する場合、
Visual Studio Installerを使うのが楽でした。
【Dataverse】VSCodeによるDataverse Plugin
-
VScodeのPlugin
Power Platform Tools
Japanese Language Pack for Visual Studio Code
C# Dev Kit
C# ※自動?
.NET Install Tool ※自動?
-
VScodeの開発1(失敗例)
ターミナル mkdir MyDataversePlugin cd MyDataversePlugin pac plugin init --skip-signing ※これでプロジェクトが自動作成 ただし、この方法は署名なし dotnet add package Microsoft.CrmSdk.CoreAssemblies dotnet add package Microsoft.CrmSdk.XrmTooling.CoreAssembly
C#で開発します。
ターミナル dotnet build -c Release
ビルドが成功すると以下にdllファイルが作成される。
C:\Users\ユーザ名\MyDataversePlugin\bin\Release\net462\publish\MyDataversePlugin.dllPlug-in Registration Tool (PRT) を起動します。
ターミナル pac tool prt ※コマンド実行後にWindowsに自動インストール サインインは「Office365」、「Display list of avaiable organizations」を選択
この後にアセンブリを登録しようとすると署名エラーとなってしまう。
-
VScodeの開発2(失敗例)
ターミナル mkdir MyDataversePlugin cd MyDataversePlugin dotnet new classlib -lang C# -o . ※失敗例1との違いはココ dotnet add package Microsoft.CrmSdk.CoreAssemblies dotnet add package Microsoft.CrmSdk.XrmTooling.CoreAssembly
C#で開発します。
ターミナル dotnet build -c Release
ビルドが成功すると以下にdllファイルが作成される。
C:\Users\ユーザ名\MyDataversePlugin\bin\Release\net462\publish\MyDataversePlugin.dllPlug-in Registration Tool (PRT) を起動します。
ターミナル pac tool prt ※コマンド実行後にWindowsに自動インストール サインインは「Office365」、「Display list of avaiable organizations」を選択
この後にアセンブリを登録しようとすると参照ライブラリのバージョンでエラーとなってしまう。
-
失敗原因
1
ビルド時に作成されるフォルダが「net462」となっているように
.NETFrameworkが4.62になっている必要があるようです。
VScodeインストールした際に.NET9.0をインストールしていました。.NETFrameworkはWindows専用であるのに対し、
.NETはクロスプラットフォーム(Windows, Linux, macOS)の違いがあるようです。2
またアセンブリにもパスワードなしの署名が必要なようです。3
以下の参照は不要。
dotnet add package Microsoft.CrmSdk.XrmTooling.CoreAssembly
-
参考サイト
https://learn.microsoft.com/ja-jp/power-apps/developer/data-platform/build-and-package
「プラグインおよびカスタム ワークフロー活動アセンブリ プロジェクトは、.NET Framework 4.6.2 をターゲットにする必要があります」の記述あり。https://learn.microsoft.com/ja-jp/power-apps/developer/data-platform/tutorial-write-plug-in
上記のサイトを見るとVisual Studioを使っていたのでVisual Studio2022を使うことにしました。
プロジェクトを作成する際、.NETクラスライブラリではなく、
.NETFrameworkクラスライブラリプロジェクトを作成することに注意。
【PowerShell】オレオレ証明書の作成
- オレオレ証明書作成
Windows11のPower Shellを使ってオレオレ証明書を作成します。
以下の構文だと特定のドメインに対して有効期間10年。
「Cert:\CurrentUser\My」は本当にそのままです。New-SelfSignedCertificate -Subject "CN=example.com" -CertStoreLocation Cert:\CurrentUser\My -DnsName example.com -NotAfter (Get-Date).AddYears(10)
-
作成した証明書の確認方法
ファイル名を指定して実行(Windows+R)を開き、「certmgr.msc」を実行します。
「証明書」-「個人」に作成した証明書が保存されています。
-
pfxファイルの作成方法
証明書を選択し、右クリックし、「すべてのタクス」-「エクスポート」を選択します。
「はい、秘密キーをエクスポートします」を選択します。
「証明のパスにある証明書を可能であればすべてを含む」を選択します。
「証明書のプライバシーを有効にする」を選択します。ファイル名とパスワードを入力します。
例:zarea0614cert、ユーザー名数字記号
エクスポート後は「C:\Windows\System32」に「zarea0614cert.pfx」が出力されます。