### Define a basic XrmToolBox plugin stub Source: https://github.com/mscrmtools/xrmtoolbox/wiki/Develop-your-own-custom-plugin-for-XrmToolBox The starting point for any plugin is inheriting from PluginControlBase within a Windows Forms user control. ```C# using System; using System.Windows.Forms; using XrmToolBox.Extensibility; using XrmToolBox.Extensibility.Interfaces; namespace MyFirstPlugin { public partial class PluginControl : PluginControlBase { // Your code here } } ``` -------------------------------- ### Implement a Complete XrmToolBox Plugin Source: https://context7.com/mscrmtools/xrmtoolbox/llms.txt A full implementation demonstrating the plugin entry point, interface support for GitHub, Help, and PayPal, and asynchronous data retrieval using WorkAsync. ```csharp using System; using System.ComponentModel.Composition; using System.Windows.Forms; using Microsoft.Crm.Sdk.Messages; using Microsoft.Xrm.Sdk; using Microsoft.Xrm.Sdk.Query; using XrmToolBox.Extensibility; using XrmToolBox.Extensibility.Args; using XrmToolBox.Extensibility.Interfaces; namespace MyCompany.AccountAnalyzer { // Plugin entry point [Export(typeof(IXrmToolBoxPlugin)), ExportMetadata("Name", "Account Analyzer"), ExportMetadata("Description", "Analyze and report on Dynamics 365 accounts"), ExportMetadata("SmallImageBase64", null), ExportMetadata("BigImageBase64", null), ExportMetadata("BackgroundColor", "#2C3E50"), ExportMetadata("PrimaryFontColor", "White"), ExportMetadata("SecondaryFontColor", "LightGray")] public class Plugin : PluginBase { public override IXrmToolBoxPluginControl GetControl() { return new AccountAnalyzerControl(); } } // Main plugin control public partial class AccountAnalyzerControl : PluginControlBase, IGitHubPlugin, IHelpPlugin, IPayPalPlugin, IStatusBarMessenger, IShortcutReceiver { private AccountAnalyzerSettings _settings; #region Interface Implementations public string RepositoryName => "account-analyzer"; public string UserName => "mycompany"; public string HelpUrl => "https://docs.mycompany.com/account-analyzer"; public string EmailAccount => "support@mycompany.com"; public string DonationDescription => "Support Account Analyzer development"; public event EventHandler SendMessageToStatusBar; #endregion public AccountAnalyzerControl() { InitializeComponent(); ConnectionUpdated += OnConnectionUpdated; LoadSettings(); } private void OnConnectionUpdated(object sender, ConnectionUpdatedEventArgs e) { LogInfo($"Connected to {e.ConnectionDetail.OrganizationFriendlyName}"); ExecuteMethod(LoadAccountStatistics); } private void LoadSettings() { if (!SettingsManager.Instance.TryLoad(typeof(AccountAnalyzerControl), out _settings)) { _settings = new AccountAnalyzerSettings(); } } private void LoadAccountStatistics() { WorkAsync(new WorkAsyncInfo { Message = "Loading account statistics...", Work = (worker, args) => { var stats = new AccountStats(); // Get total count var countQuery = new QueryExpression("account") { ColumnSet = new ColumnSet(false) }; stats.TotalAccounts = Service.RetrieveMultiple(countQuery).Entities.Count; worker.ReportProgress(50, "Calculating revenue..."); // Get accounts with revenue var revenueQuery = new QueryExpression("account") { ColumnSet = new ColumnSet("revenue") }; revenueQuery.Criteria.AddCondition("revenue", ConditionOperator.NotNull); var accounts = Service.RetrieveMultiple(revenueQuery); stats.TotalRevenue = accounts.Entities .Sum(a => a.GetAttributeValue("revenue")?.Value ?? 0); args.Result = stats; }, ProgressChanged = e => { SendMessageToStatusBar?.Invoke(this, new StatusBarMessageEventArgs(e.ProgressPercentage, e.UserState?.ToString())); }, PostWorkCallBack = e => { SendMessageToStatusBar?.Invoke(this, new StatusBarMessageEventArgs(null, "Ready")); if (e.Error != null) { ShowErrorDialog(e.Error, "Failed to Load Statistics"); return; } var stats = (AccountStats)e.Result; lblTotalAccounts.Text = stats.TotalAccounts.ToString("N0"); lblTotalRevenue.Text = stats.TotalRevenue.ToString("C"); }, IsCancelable = true }); } public void ReceiveKeyDownShortcut(KeyEventArgs e) { if (e.KeyCode == Keys.F5) { ExecuteMethod(LoadAccountStatistics); e.Handled = true; } } public void ReceiveKeyPressShortcut(KeyPressEventArgs e) { } ``` -------------------------------- ### Add External Help Documentation Link Source: https://context7.com/mscrmtools/xrmtoolbox/llms.txt Implement the IHelpPlugin interface to add a 'Help' menu option that redirects users to your plugin's external documentation URL. ```csharp using XrmToolBox.Extensibility; using XrmToolBox.Extensibility.Interfaces; public partial class MyPluginControl : PluginControlBase, IHelpPlugin { // Help documentation URL - adds "Help" menu option public string HelpUrl => "https://docs.mycompany.com/plugins/account-manager"; } ``` -------------------------------- ### Plugin Closing Logic Source: https://github.com/mscrmtools/xrmtoolbox/wiki/Develop-your-own-custom-plugin-for-XrmToolBox Override the ClosingPlugin method to handle custom logic when a plugin is closing. This example includes a confirmation dialog for closing a single tab. ```C# public virtual void ClosingPlugin(PluginCloseInfo info) { if (info.FormReason != CloseReason.None || info.ToolBoxReason == ToolBoxCloseReason.CloseAll || info.ToolBoxReason == ToolBoxCloseReason.CloseAllExceptActive) { return; } info.Cancel = MessageBox.Show(@"Are you sure you want to close this tab?", @"Question", MessageBoxButtons.YesNo, MessageBoxIcon.Question) != DialogResult.Yes; } ``` -------------------------------- ### Implement Plugin Lifecycle and Shortcut Methods Source: https://context7.com/mscrmtools/xrmtoolbox/llms.txt Handles keyboard shortcuts and plugin closing events. Use ClosingPlugin to persist settings before the plugin shuts down. ```csharp public void ReceiveKeyUpShortcut(KeyEventArgs e) { } public void ReceivePreviewKeyDownShortcut(PreviewKeyDownEventArgs e) { } public override void ClosingPlugin(PluginCloseInfo info) { SettingsManager.Instance.Save(typeof(AccountAnalyzerControl), _settings); base.ClosingPlugin(info); } ``` -------------------------------- ### Create New Plugin Description Class Source: https://github.com/mscrmtools/xrmtoolbox/wiki/Migrate-your-plugin-to-be-compatible-with-July-release Create a new class inheriting from `PluginBase` to describe the plugin and return the UserControl. ```csharp using System.ComponentModel.Composition; using XrmToolBox.Extensibility; using XrmToolBox.Extensibility.Interfaces; namespace MsCrmTools.SampleTool { public class Plugin : PluginBase { public override IXrmToolBoxPluginControl GetControl() { return new SampleTool(); } } } ``` -------------------------------- ### Complete Sample Tool Class Source: https://github.com/mscrmtools/xrmtoolbox/wiki/Develop-your-own-custom-plugin-for-XrmToolBox The final C# code for a sample XrmToolBox plugin, including event handlers and asynchronous processing. ```C# public partial class SampleTool : XrmToolBox.PluginBase { private void ProcessWhoAmI() { WorkAsync("Retrieving your user id...", (e) => // Work To Do Asynchronously { var request = new WhoAmIRequest(); var response = (WhoAmIResponse)Service.Execute(request); e.Result = response.UserId; }, e => // Cleanup when work has completed { MessageBox.Show(string.Format("You are {0}", (Guid)e.Result)); } ); } #region Events private void BtnWhoAmI_Click(object sender, EventArgs e) { ExecuteMethod(ProcessWhoAmI); } private void BtnClose_Click(object sender, EventArgs e) { base.CloseTool(); } #endregion Events } ``` -------------------------------- ### Implement IPayPalPlugin for Donations Source: https://context7.com/mscrmtools/xrmtoolbox/llms.txt Implement this interface to add a donation menu option to your plugin. Requires defining the email account and a description for the donation. ```csharp using XrmToolBox.Extensibility; using XrmToolBox.Extensibility.Interfaces; public partial class MyPluginControl : PluginControlBase, IPayPalPlugin { // PayPal donation integration - adds "Donate" menu option public string EmailAccount => "developer@example.com"; public string DonationDescription => "Support Account Manager plugin development"; } ``` -------------------------------- ### Create and Push NuGet Package using Batch Script Source: https://github.com/mscrmtools/xrmtoolbox/wiki/Building-NuGet-Package-Without-NuGet-Package-Explorer This batch script automates the deletion of old packages, packing a new NuGet package using a .nuspec file, and pushing it to a NuGet repository. Ensure you have nuget.exe in your PATH or the same directory, and replace placeholders with your actual paths and NuGet key. ```batch echo Deleting Previous NuGet Packages del MYPLUGIN*.nupkg nuget.exe pack "C:\YourPath\MYPLUGIN.nuspec" echo Press Enter to Push pause nuget.exe push MYPLUGIN*.nupkg pause ``` -------------------------------- ### Configure Post-build Event Source: https://github.com/mscrmtools/xrmtoolbox/wiki/Debug-your-plugins-during-development Use this script in the project post-build event to move plugin assemblies into a Plugins sub-folder. ```batch if $(ConfigurationName) == Debug ( IF NOT EXIST Plugins mkdir Plugins move /Y MyLittleCompany.*.dll Plugins move /Y MyLittleCompany.*.pdb Plugins ) ``` -------------------------------- ### Set Debug Path and Arguments Source: https://github.com/mscrmtools/xrmtoolbox/wiki/Debug-your-plugins-during-development Specify the path to the XrmToolBox executable and the required command line arguments for debugging. ```text C:\Dev\Labb\MyXTBPlugin\MyXTBPlugin\bin\Debug\XrmToolBox.exe ``` ```text /overridepath:. ``` -------------------------------- ### Plugin Class Implementation for XrmToolBox Source: https://github.com/mscrmtools/xrmtoolbox/wiki/Develop-your-own-custom-plugin-for-XrmToolBox Implement the main plugin class by inheriting from PluginBase and decorating it with Export and ExportMetadata attributes. This class defines the plugin's metadata and provides the GetControl method to return the user interface. ```C# using System.ComponentModel.Composition; using XrmToolBox.Extensibility; using XrmToolBox.Extensibility.Interfaces; namespace MyFirstPlugin { [Export(typeof(IXrmToolBoxPlugin)), ExportMetadata("BackgroundColor", "MediumBlue"), ExportMetadata("PrimaryFontColor", "White"), ExportMetadata("SecondaryFontColor", "LightGray"), ExportMetadata("SmallImageBase64", "a base64 encoded image"), ExportMetadata("BigImageBase64", "a base64 encoded image"), ExportMetadata("Name", "My First Plugin name"), ExportMetadata("Description", "My First Plugin description")] public class Plugin : PluginBase { public override IXrmToolBoxPluginControl GetControl() { return new PluginControl(); } } } ``` -------------------------------- ### Define Plugin Settings and Data Models Source: https://context7.com/mscrmtools/xrmtoolbox/llms.txt Serializable classes for storing plugin configuration and data results. Ensure settings are marked as Serializable for the SettingsManager. ```csharp [Serializable] public class AccountAnalyzerSettings { public bool IncludeInactive { get; set; } = false; public int RefreshInterval { get; set; } = 300; } public class AccountStats { public int TotalAccounts { get; set; } public decimal TotalRevenue { get; set; } } ``` -------------------------------- ### Configure XrmToolBox Tracing Source: https://github.com/mscrmtools/xrmtoolbox/wiki/Enable-tracing-in-XrmToolBox Add this configuration to your XrmToolBox.exe.config file to enable verbose tracing for connection issues. The trace file will be generated in the XrmToolBox folder. ```XML ``` -------------------------------- ### Implement IShortcutReceiver for Keyboard Shortcuts Source: https://context7.com/mscrmtools/xrmtoolbox/llms.txt Allows plugins to intercept and handle keyboard events when the plugin is active. ```csharp using System.Windows.Forms; using XrmToolBox.Extensibility; using XrmToolBox.Extensibility.Interfaces; public partial class MyPluginControl : PluginControlBase, IShortcutReceiver { public void ReceiveKeyDownShortcut(KeyEventArgs e) { // Handle Ctrl+S for save if (e.Control && e.KeyCode == Keys.S) { SaveChanges(); e.Handled = true; } // Handle Ctrl+R for refresh else if (e.Control && e.KeyCode == Keys.R) { RefreshData(); e.Handled = true; } // Handle F5 for reload else if (e.KeyCode == Keys.F5) { ReloadPlugin(); e.Handled = true; } } public void ReceiveKeyPressShortcut(KeyPressEventArgs e) { } public void ReceiveKeyUpShortcut(KeyEventArgs e) { } public void ReceivePreviewKeyDownShortcut(PreviewKeyDownEventArgs e) { } ``` -------------------------------- ### Implement IMessageBusHost for Plugin Communication Source: https://context7.com/mscrmtools/xrmtoolbox/llms.txt Enables plugins to send and receive messages via the XrmToolBox message broker. ```csharp using System; using XrmToolBox.Extensibility; using XrmToolBox.Extensibility.Interfaces; public partial class SenderPlugin : PluginControlBase, IMessageBusHost { public event EventHandler OnOutgoingMessage; private void SendDataToOtherPlugin() { // Create message targeting another plugin by name var message = new MessageBusEventArgs("Entity Browser", newInstance: false) { TargetArgument = new { EntityName = "account", RecordId = Guid.Parse("12345678-1234-1234-1234-123456789012"), Action = "Open" } }; // Send message through the broker OnOutgoingMessage?.Invoke(this, message); } public void OnIncomingMessage(MessageBusEventArgs message) { // Receive messages from other plugins MessageBox.Show($"Received from {message.SourcePlugin}: {message.TargetArgument}"); } } public partial class ReceiverPlugin : PluginControlBase, IMessageBusHost { public event EventHandler OnOutgoingMessage; public void OnIncomingMessage(MessageBusEventArgs message) { // Handle incoming message dynamic data = message.TargetArgument; string entityName = data.EntityName; Guid recordId = data.RecordId; // Load the requested record LoadEntity(entityName, recordId); } } ``` -------------------------------- ### Define XrmToolBox Plugin Class Source: https://context7.com/mscrmtools/xrmtoolbox/llms.txt Implement the plugin class by inheriting from PluginBase and using MEF export attributes to define metadata for the XrmToolBox plugins list. Ensure correct namespaces and image formats for metadata. ```csharp using System.ComponentModel.Composition; using XrmToolBox.Extensibility; using XrmToolBox.Extensibility.Interfaces; namespace MyCustomPlugin { [Export(typeof(IXrmToolBoxPlugin)), ExportMetadata("Name", "Account Manager"), ExportMetadata("Description", "Bulk manage Dynamics 365 accounts"), ExportMetadata("SmallImageBase64", "iVBORw0KGgoAAAANSUhEUgAAACAAAA..."), // 32x32 PNG ExportMetadata("BigImageBase64", "iVBORw0KGgoAAAANSUhEUgAAAGQAAABk..."), // 80x80 PNG ExportMetadata("BackgroundColor", "#3498DB"), ExportMetadata("PrimaryFontColor", "White"), ExportMetadata("SecondaryFontColor", "LightGray")] public class Plugin : PluginBase { public override IXrmToolBoxPluginControl GetControl() { return new AccountManagerControl(); } } } ``` -------------------------------- ### Implement asynchronous CRM operations and tool lifecycle Source: https://github.com/mscrmtools/xrmtoolbox/wiki/Develop-your-own-custom-plugin-for-XrmToolBox Demonstrates the use of WorkAsync for background processing and ExecuteMethod to ensure a valid CRM connection exists before execution. ```C# namespace MsCrmTools.SampleTool { public partial class SampleTool : PluginControlBase { #region Base tool implementation public SampleTool() { InitializeComponent(); } public void ProcessWhoAmI() { WorkAsync(new WorkAsyncInfo { Message = "Retrieving your user id...", Work = (w, e) => { var request = new WhoAmIRequest(); var response = (WhoAmIResponse)Service.Execute(request); e.Result = response.UserId; }, ProgressChanged = e => { // If progress has to be notified to user, use the following method: SetWorkingMessage("Message to display"); }, PostWorkCallBack = e => { MessageBox.Show(string.Format("You are {0}", (Guid)e.Result)); }, AsyncArgument = null, IsCancelable = true, MessageWidth = 340, MessageHeight = 150 }); } private void BtnCloseClick(object sender, EventArgs e) { CloseTool(); // PluginBaseControl method that notifies the XrmToolBox that the user wants to close the plugin // Override the ClosingPlugin method to allow for any plugin specific closing logic to be performed (saving configs, canceling close, etc...) } private void BtnWhoAmIClick(object sender, EventArgs e) { ExecuteMethod(ProcessWhoAmI); // ExecuteMethod ensures that the user has connected to CRM, before calling the call back method } #endregion Base tool implementation private void btnCancel_Click(object sender, EventArgs e) { CancelWorker(); // PluginBaseControl method that calls the Background Workers CancelAsync method. MessageBox.Show("Cancelled"); } } } ``` -------------------------------- ### Implement IStatusBarMessenger for Progress Updates Source: https://context7.com/mscrmtools/xrmtoolbox/llms.txt Use this interface to communicate progress to the XrmToolBox status bar during long-running background tasks. ```csharp using System; using XrmToolBox.Extensibility; using XrmToolBox.Extensibility.Args; using XrmToolBox.Extensibility.Interfaces; public partial class MyPluginControl : PluginControlBase, IStatusBarMessenger { public event EventHandler SendMessageToStatusBar; private void ProcessRecords() { WorkAsync(new WorkAsyncInfo { Message = "Processing...", Work = (worker, args) => { for (int i = 0; i <= 100; i += 10) { // Update status bar with progress worker.ReportProgress(i, $"Step {i / 10} of 10"); System.Threading.Thread.Sleep(500); } }, ProgressChanged = e => { // Send progress to XrmToolBox status bar SendMessageToStatusBar?.Invoke(this, new StatusBarMessageEventArgs( e.ProgressPercentage, e.UserState?.ToString() )); }, PostWorkCallBack = e => { // Clear status bar SendMessageToStatusBar?.Invoke(this, new StatusBarMessageEventArgs(null, "Ready")); } }); } } ``` -------------------------------- ### Define NuSpec configuration for XrmToolBox plugin Source: https://github.com/mscrmtools/xrmtoolbox/wiki/Distribute-your-plugins-through-XrmToolBox-and-NuGet Use this structure to define metadata and file locations for a NuGet package. Ensure all plugin files are placed under the 'Plugins' folder within the target directory. ```xml Contoso.Xrm.SweetestPlugin 1.2016.4.123 The Sweetest Plugin for XrmToolBox Jane and John Doe Contoso https://github.com/Contoso/SweetestPlugin http://contoso.com/sweetest.ico false Really sweet plugin that does awesome things Really sweet plugin that does awesome things With this your life will be a whole lot sweeter. 1.2016.4.123: New life-enhancer functionality. 1.2016.3.112: New fantastic button. Copyright 2016 Contoso Enterprises XrmToolBox Plugins SweetestPlugin ``` -------------------------------- ### Display Error Dialog with GitHub Issue Option Source: https://context7.com/mscrmtools/xrmtoolbox/llms.txt Use ShowErrorDialog within a try-catch block to display exceptions. If IGitHubPlugin is implemented, it offers a 'Create Issue' button. Provide relevant details via the 'heading' and 'extrainfo' parameters. ```csharp using System; using Microsoft.Xrm.Sdk; using XrmToolBox.Extensibility; public partial class MyPluginControl : PluginControlBase, IGitHubPlugin { public string RepositoryName => "my-plugin"; public string UserName => "developer"; private void PerformRiskyOperation() { WorkAsync(new WorkAsyncInfo { Message = "Performing operation...", Work = (worker, args) => { try { // Attempt CRM operation var entity = new Entity("account"); entity["name"] = "Test Account"; args.Result = Service.Create(entity); } catch (Exception ex) { throw; // Let PostWorkCallBack handle it } }, PostWorkCallBack = e => { if (e.Error != null) { // Display detailed error dialog // If IGitHubPlugin is implemented, offers "Create Issue" button ShowErrorDialog( exception: e.Error, heading: "Failed to Create Account", extrainfo: $"Entity: account\nOperation: Create\nTime: {DateTime.Now}", allownewissue: true ); } else { var newId = (Guid)e.Result; MessageBox.Show($"Created account: {newId}"); } } }); } } ``` -------------------------------- ### Persist Plugin Settings with SettingsManager Source: https://context7.com/mscrmtools/xrmtoolbox/llms.txt Use the SettingsManager singleton for XML serialization to persist plugin settings across sessions. Ensure your settings class is marked with [Serializable]. ```csharp using System; using XrmToolBox.Extensibility; // Define settings class - must be serializable [Serializable] public class MyPluginSettings { public string DefaultEntityName { get; set; } = "account"; public int MaxRecords { get; set; } = 5000; public bool AutoRefresh { get; set; } = true; public List RecentQueries { get; set; } = new List(); public DateTime LastUsed { get; set; } } public partial class MyPluginControl : PluginControlBase { private MyPluginSettings _settings; public MyPluginControl() { InitializeComponent(); LoadSettings(); } private void LoadSettings() { // Try to load existing settings if (!SettingsManager.Instance.TryLoad(typeof(MyPluginControl), out _settings)) { // Create default settings if none exist _settings = new MyPluginSettings(); LogInfo("Created new settings with defaults"); } // Apply settings to UI txtDefaultEntity.Text = _settings.DefaultEntityName; nudMaxRecords.Value = _settings.MaxRecords; chkAutoRefresh.Checked = _settings.AutoRefresh; } private void SaveSettings() { // Update settings from UI _settings.DefaultEntityName = txtDefaultEntity.Text; _settings.MaxRecords = (int)nudMaxRecords.Value; _settings.AutoRefresh = chkAutoRefresh.Checked; _settings.LastUsed = DateTime.Now; // Save to disk SettingsManager.Instance.Save(typeof(MyPluginControl), _settings); LogInfo("Settings saved successfully"); } // Save settings with custom name (e.g., per-connection settings) private void SaveConnectionSpecificSettings(string connectionName) { SettingsManager.Instance.Save(typeof(MyPluginControl), _settings, connectionName); } public override void ClosingPlugin(PluginCloseInfo info) { SaveSettings(); base.ClosingPlugin(info); } } ``` -------------------------------- ### Implement IMsCrmToolsPluginUserControl Interface Source: https://github.com/mscrmtools/xrmtoolbox/wiki/Develop-your-own-custom-plugin-for-XrmToolBox This interface defines the contract for user controls in XrmToolBox plugins. It includes properties for the organization service and plugin logo, as well as events for connection requests and tool closing. ```C# using System; using System.Drawing; using Microsoft.Xrm.Sdk; namespace XrmToolBox { public interface IMsCrmToolsPluginUserControl { /// /// Gets the organization service used by the tool /// IOrganizationService Service { get; } /// /// Gets the logo to display in the tools list /// Image PluginLogo { get; } /// /// EventHandler to request a connection to an organization /// event EventHandler OnRequestConnection; /// /// EventHandler to close the current tool /// event EventHandler OnCloseTool; /// /// Method to allow plugin to Cancel a closing event, or perform any save events required before closing. /// void ClosingPlugin(PluginCloseInfo info); /// /// Updates the organization service used by the tool /// /// Organization service /// Action that requested a service update /// Parameter passed when requesting a service update void UpdateConnection(IOrganizationService newService, string actionName = "", object parameter = null); } } ``` -------------------------------- ### Update Plugin Interface Source: https://github.com/mscrmtools/xrmtoolbox/wiki/Migrate-your-plugin-to-be-compatible-with-July-release Rename `IMsCrmToolsPluginUserControl` to `IXrmToolBoxPluginControl` when implementing the interface directly. ```csharp public partial class MyPlugin : UserControl, IMsCrmToolsPluginUserControl { // Your code } ``` ```csharp public partial class MyPlugin : UserControl, IXrmToolBoxPluginControl { // Your code } ``` -------------------------------- ### Integrate GitHub Issue Reporting Source: https://context7.com/mscrmtools/xrmtoolbox/llms.txt Implement the IGitHubPlugin interface to add a 'Report Issue' option to the GitHub menu, allowing users to report issues directly to your plugin's repository. ```csharp using XrmToolBox.Extensibility; using XrmToolBox.Extensibility.Interfaces; public partial class MyPluginControl : PluginControlBase, IGitHubPlugin { // GitHub integration - adds "Report Issue" menu option public string RepositoryName => "my-xrmtoolbox-plugin"; public string UserName => "myusername"; } // Users can click Help > Report Issue to open: // https://github.com/myusername/my-xrmtoolbox-plugin/issues ``` -------------------------------- ### Implement INoConnectionRequired for Offline Plugins Source: https://context7.com/mscrmtools/xrmtoolbox/llms.txt Implement the INoConnectionRequired marker interface for plugins that can function without an active CRM connection, such as utility tools or settings editors. The Service property will be null until the user connects. ```csharp using XrmToolBox.Extensibility; using XrmToolBox.Extensibility.Interfaces; // Plugin class - allows tool to open without forcing connection [Export(typeof(IXrmToolBoxPlugin)), ExportMetadata("Name", "FetchXML Builder Offline")] public class Plugin : PluginBase, INoConnectionRequired { public override IXrmToolBoxPluginControl GetControl() { return new FetchXmlBuilderControl(); } } public partial class FetchXmlBuilderControl : PluginControlBase { public FetchXmlBuilderControl() { InitializeComponent(); // Plugin works without connection // Service property will be null until user connects } private void btnExecute_Click(object sender, EventArgs e) { if (Service == null) { MessageBox.Show("Please connect to CRM to execute queries."); return; } ExecuteMethod(ExecuteFetchXml); } } ``` -------------------------------- ### Button Click Event Handlers Source: https://github.com/mscrmtools/xrmtoolbox/wiki/Develop-your-own-custom-plugin-for-XrmToolBox Implement click event handlers for buttons to trigger plugin logic or close the tool. Ensure these are generated by double-clicking the buttons in the design window. ```C# private void BtnWhoAmI_Click(object sender, EventArgs e) { ExecuteMethod(ProcessWhoAmI); } private void BtnClose_Click(object sender, EventArgs e) { base.CloseTool(); } ``` -------------------------------- ### Implement Plugin Control Base Class Source: https://context7.com/mscrmtools/xrmtoolbox/llms.txt Inherit from PluginControlBase for your plugin's UserControl to leverage built-in connection management, async work execution, and logging. Use the Service property for CRM operations and CloseTool() to close the plugin tab. ```csharp using System; using System.Windows.Forms; using Microsoft.Crm.Sdk.Messages; using Microsoft.Xrm.Sdk; using Microsoft.Xrm.Sdk.Query; using XrmToolBox.Extensibility; namespace MyCustomPlugin { public partial class AccountManagerControl : PluginControlBase { public AccountManagerControl() { InitializeComponent(); // Log messages for debugging LogInfo("Plugin initialized successfully"); LogWarning("This is a warning message"); LogError("This is an error message"); } private void btnLoadAccounts_Click(object sender, EventArgs e) { // ExecuteMethod ensures CRM connection exists before calling the method ExecuteMethod(LoadAccounts); } private void LoadAccounts() { // Service property provides IOrganizationService for CRM operations var query = new QueryExpression("account") { ColumnSet = new ColumnSet("name", "accountnumber", "revenue"), TopCount = 100 }; var accounts = Service.RetrieveMultiple(query); foreach (var account in accounts.Entities) { listBoxAccounts.Items.Add(account.GetAttributeValue("name")); } } private void btnClose_Click(object sender, EventArgs e) { // Notify XrmToolBox to close this plugin tab CloseTool(); } public override void ClosingPlugin(PluginCloseInfo info) { // Handle plugin close - save settings, prompt for unsaved changes, etc. if (HasUnsavedChanges) { info.Cancel = MessageBox.Show( "You have unsaved changes. Close anyway?", "Confirm Close", MessageBoxButtons.YesNo, MessageBoxIcon.Warning) != DialogResult.Yes; } base.ClosingPlugin(info); } } } ``` -------------------------------- ### Asynchronous SDK Call with Result Handling Source: https://github.com/mscrmtools/xrmtoolbox/wiki/Develop-your-own-custom-plugin-for-XrmToolBox A simplified WorkAsync signature for performing a single SDK call and processing its result. This method ensures a valid connection before executing the SDK call. ```C# WorkAsync("Retrieving your user id...", (e) => // Work To Do Asynchronously { var request = new WhoAmIRequest(); var response = (WhoAmIResponse)Service.Execute(request); e.Result = response.UserId; }, e => // Cleanup when work has completed { MessageBox.Show(string.Format("You are {0}", (Guid)e.Result)); } ); ``` -------------------------------- ### Asynchronous Work with Progress Reporting Source: https://github.com/mscrmtools/xrmtoolbox/wiki/Develop-your-own-custom-plugin-for-XrmToolBox Utilize the WorkAsync method to perform complex asynchronous operations, report progress, and handle results. This signature is suitable for multi-step background tasks. ```C# WorkAsync("Message To Display...", (w, e) => // Work To Do Asynchronously { w.ReportProgress(0, "Doing Something"); //Do something w.ReportProgress(50, "Doing Something Else"); //Do something else // Populate whatever the results that need to be returned to the Results Property e.Result = new object(); w.ReportProgress(99, "Finishing"); }, e => // Finished Async Call. Cleanup { // Handle e.Result }, e => // Logic wants to display an update. This gets called when ReportProgress Gets Called { SetWorkingMessage(e.UserState.ToString()); } ); ``` -------------------------------- ### Declare plugin metadata with Export attribute Source: https://github.com/mscrmtools/xrmtoolbox/wiki/Migrate-your-plugin-to-be-compatible-with-July-release Apply the Export and ExportMetadata attributes to the plugin class to define display properties in the XrmToolBox interface. ```csharp [Export(typeof(IXrmToolBoxPlugin)), ExportMetadata("Name", "The name of your tool"), ExportMetadata("Description", "The description of your tool"), ExportMetadata("SmallImageBase64", null), // null for "no logo" image or base64 image content ExportMetadata("BigImageBase64", null), // null for "no logo" image or base64 image content ExportMetadata("BackgroundColor", "Lavender"), // Use a HTML color name ExportMetadata("PrimaryFontColor", "#000000"), // Or an hexadecimal code ExportMetadata("SecondaryFontColor", "DarkGray")] public partial class SampleTool : PluginBase { // Your code here } ``` -------------------------------- ### Update Plugin Base Class Source: https://github.com/mscrmtools/xrmtoolbox/wiki/Migrate-your-plugin-to-be-compatible-with-July-release Rename `PluginBase` to `PluginControlBase` when updating existing plugins. ```csharp public partial class ScriptsFinder : PluginBase { // Your code } ``` ```csharp public partial class ScriptsFinder : PluginControlBase { // Your code } ```