Showing posts with label XrmToolBox. Show all posts
Showing posts with label XrmToolBox. Show all posts

Friday, July 1, 2022

Enabling or Disabling All Plugin Steps In Dataverse

The Cause

Recently a bug (working by design?) with the PowerPlatform.BuildTools version 0.0.81 caused all my plugin steps to become disabled.  After looking at the Azure DevOps Pipeline output I found this lovely difference between versions .77 and .81:

0.0.77

Import-Solution: MySolution_managed.zip, HoldingSolution: True, OverwriteUnmanagedCustomizations: True, PublishWorkflows: True, SkipProductUpdateDependencies: False, AsyncOperation: True, MaxAsyncWaitTime: 01:00:00, ConvertToManaged: False, full path: D:\a\1\a\MySolution_managed.zip

0.0.81

Calling pac cli inputs: solution import --path D:\\a\\1\\a\\MySolution_managed.zip --async true --import-as-holding true --force-overwrite true --publish-changes true --skip-dependency-check false --convert-to-managed false --max-async-wait-time 60 --activate-plugins false' ]

When this solution imported, it deactivated all of my plugin steps in my solution (which had over 100). Manually updating it would have been ugly.  Luckily there is a work around…


The Fix

  1. If you haven’t already, install the XrmToolBox, and set it up to connect to your environment.
  2. Install Sql4Cds
    1. Click Tool Library:
    2. image
    3. Make sure your display tools check boxes have “Not installed” checked and install the tool:
    4. image
    5. Open the Sql 4 CDS tool, connecting to your environment.
    6. Execute the following statement to find the Id of the plugin assembly that you want to enable all plugin steps for:
      1. SELECT pluginassemblyid, name FROM pluginassembly ORDER BY name
         
        
    7. image
    8. Find and copy the plugin assembly id you want to enable (I’ve left the values needed to disable plugins but commented out, in case that is required in the future as well dear reader), and paste into the following query:
    9.  
      UPDATE sdkmessageprocessingstep
      SET statecode = 0, statuscode = 1  -- Enable
      -- SET statecode = 1, statuscode = 2 -- Disable
      WHERE sdkmessageprocessingstepid in (     SELECT sdkmessageprocessingstepid     FROM sdkmessageprocessingstep     WHERE plugintypeid IN (         SELECT plugintypeid         FROM plugintype         WHERE pluginassemblyid = '95858c14-e3c9-4ef9-b0ef-0a2c255ea6df'     )     AND statecode = 1
      )
       
      
    10. Execute the query, get a coffee/tea and let it update all of your steps for you!



Wednesday, January 13, 2021

How To Create Daily Bulk Delete Jobs in Dataverse/CDS/Power Apps/CRM As A Different User

UPDATE!

The first statement is a lie!  The UI for Dataverse/CDS/Power Apps/CRM bulk delete jobs does allow for creating a reoccurring daily Bulk Delete Job (Thanks Oliver Flint).   Even though it looks like a dropdown, you can type whatever number you’d like.  As such, this post is still helpful if you want to create a duplicate bulk delete job in multiple environments, or if you want to create it with someone else like an App User as the owner.

Original Post:

The UI for Dataverse/CDS/Power Apps/CRM bulk delete jobs does not allow for creating a reoccurring daily Bulk Delete Job.  The smallest value to choose from is weekly, which means if you want to run something daily, you’d have to create 7 jobs, one for each day of the week. Ew!  But, this can be set programatically via the SDK, and here is how (Please note, this is just code, it can be compiled and run anywhere.  When you run from the XTB though, you can either login with an Application User, or impersonate it if you have impersonation rights, which would set the owner of the bulk delete record, and help prevent any issues when a user leaves, but owns all of the Bulk Delete Jobs):

  1. Open the XrmToolBox, and connect to the environment (Bonus, connect with an application user to create the Bulk Delete Job as an application user, so that it isn’t owned by a person that leaves the company or has permissions removed.)
  2. Install the Code Now XrmToolBox Plugin if not already installed, and open it.
  3. If the logged in user is the XTB is the desired user, great, if you’d like the bulk delete request to be owned by a different user, push the Impersonate button at the top of the XTB and select the appropriate user.  (Testing has shown that impersonating the System user will not work to set the owner as system.  An Application User will be required)
  4. Copy and paste the following code into the window:
public static void CodeNow()
{
    var bulkDeleteRequest = new Microsoft.Crm.Sdk.Messages.BulkDeleteRequest
    {
        JobName = "Daily 3am Delete Job",
        QuerySet = new [] {
            new QueryExpression {
                ColumnSet = new ColumnSet("acme_tableid", "acme_tablename", "createdon"),
                EntityName = "acme_table",
                Criteria = {
                    Filters = {
                        new FilterExpression {
                            FilterOperator = LogicalOperator.And,
                            Conditions = {
                                new ConditionExpression("acme_delete_me", ConditionOperator.Equal, true)
                            }
                        }
                    }
                }
            }
        },
        StartDateTime = new DateTime(2021, 1, 8, 8, 0, 0, DateTimeKind.Utc),
        RecurrencePattern = "FREQ=DAILY;INTERVAL=1;",
        ToRecipients = new Guid[] { },
        CCRecipients = new Guid[] { },
        SendEmailNotification = false
    };

    Service.Execute(bulkDeleteRequest);
}

 

Update the following values

  1. Update the JobName to what ever your preference is.
  2. QuerySet is a collection of QueryExpressions.  Add at least one (I don’t know what happens if you add two, my guess is that all records returned from all Query Expressions will get deleted.  My guess is it is expecting to always have just the Primary Id of the table, the Primary Name Column, and the “Created On” column.
  3. Update the StartDateTime a future date to start. It's format is new DateTime(YYYY, MM, DD, ... ). Please note, the time is UTC, so the current value is Jan. 8, 2021 at 3am EST (Not EDT). 
  4. Update RecurrencePattern to your liking.  FREQ=DAILY;INTERVAL=1; means it will be ran every day.  (Not sure of other FREQ values could be, but you could use the FetchXmlBuilder to query for other existing values)
  5. Update ToRecipients and CCRecipients to what I believe are the SystemUser Ids that would be notified when the job runs.  (I’ve never used it, so don’t quote me on this one)
  6. Update SendEmailNotification to true to send out emails to the To and CC Recipients if desired.

Run the Code in Code Now… err… now!

Verify that the record was created correctly by navigating to the Bulk Delete Jobs:

  1. Open Advanced Settings by clicking the gearbox icon in the top right-hand corner
  2. Navigate to the "Data Management" area
  3. Click "Bulk Record Deletion"
  4. Select Recurring Bulk Delete System Jobs
  5. Verify that the job is created with the correct Owner and the Next Run values, and that it is in a "Waiting" status reason.
  6. You can verify the history of this job by using the Completed Bulk Deletion System Jobs view once the next run time has passed.

Wednesday, September 12, 2018

Tell Your Code To Debug When You’re Good And Ready!

When doing dev work for the Early Bound Generator I frequently have to kick off a process in the XrmToolBox, and then attempt to attach my debugger as quickly as possible.  This is a really annoying race condition, and if I’m using local metadata, a race that I don’t usually win.  I can’t believe I hadn’t thought of this before, and for all I know the pattern exists else where in the wild, but it made my “attach to process to debug” workflow a guarantee, rather than a race:

if (ConfigHelper.GetAppSettingOrDefault("WaitForAttachedDebugger", false))
{
    while (!Debugger.IsAttached)
    {
        Console.WriteLine("[**** Waiting For Debugger ****]");
        Thread.Sleep(3000);
    }
}

The ConfigHelper is a helper class in the DLaB.Common that will look for the given AppSettings in the Config file, and if it doesn’t exist, default to the given value (“false” in this case).  So if I add an AppSetting to “WaitForAttachedDebugger” in my config, then my app will wait until a debugger is attached, checking indefinitely every 3 seconds to see if I’ve finally attached it. 

If you found this helpful, make sure to check out, my Raise The Bar video series

Happy coding!

Saturday, September 3, 2016

An Evening with Me and #XRMToolbox Development

Image result for SocialI’ve got a couple GitHub issues on for my XrmToolBox Plugins that have been gnawing at me for a while.  In an effort to have a more enjoyable time, I thought I’d attempt to do a social programming event.  This will be unscripted event, open to anyone.  There will be 0 Slides, and 0 prep work.  It will offer people an opportunity to ask questions, see what’s involved with XTB development, and just generally hang out, with an underlying theme of attempting to knock out as many GitHub issues as I can.  It could be fun!  On the other hand, it could be incredibly boring.  Tons of people could log in!  On the other hand maybe no one will show up.  Whatever the case, we won’t know until we try!

  • Who: Myself and anyone that wants to join.  
  • What: Social programming (if that’s not already I thing, I’m making it one now!)  Feel free to un-mute, ask questions, make suggestions, or tell me I'm missing a semi-colon!
  • Where: Skype For Business Link.
  • When: September 8, 2016 from 9pm to midnight EDT
  • Why:  I’ve got stuff I want to do, and thought this could be a fun-er way of doing it! :-)

Thursday, August 20, 2015

How To Fix Workplace And Resource Center Icons In CRM 2015.1

CRM 2015 Update 1 removed the workplace and Resource Center from the site map.  It also replaced all the existing icons that were still in use.  By default this gives a very poor looking sitemap:


Here is how to update the icons to give a much more polished look:

  1. Create or find the icons that you want to use.  The new CRM Icons are 85x71, so you’ll want to follow that standard for optimal display.  (You can download my icons here.  I made them myself.  Feel free to donate using the Donate button on the right.)
  2. Upload the icons as web resources (The XrmToolBox has a great plugin for this, or you can do it manually).  Record the name you give for each icon.
  3. Download and run the XrmToolBox.
  4. Open the SiteMap Editor plugin, and login to the CRM instance to be updated.
  5. Click the “Load SiteMap” button to load the site map from CRM.
  6. -Optional- Click “Save SiteMap” to make a backup incase the changes made need to be reverted.
  7. Click on the “Area (Workplace)” node and update the Icon path to the path of the appropriate web resource. image
  8. Click Save.
  9. Click on the “Area (ResourceCenter)” node and update the Icon path to the path of the appropriate web resource.
  10. Click Save.
  11. Click “Update SiteMap” to push the changes.
  12. Stop staring at the old nasty blown up 32x32 icons in your SiteMap.

Wednesday, August 27, 2014

How To Make It Easier To Create A Plugin For The XrmToolBox

The XrmToolBox is an awesome collection of tools for CRM.  If you haven’t used it before, stop reading this and download it now.  Go ahead.  I’ll wait.

Welcome back…

To date I’ve created 2 plugins for XrmToolBox, Option Set Converter and Early Bound Generator.  While developing them I discovered that there really needed to be a base plugin control to make it simpler to perform CRM calls and perform asynchronous calls.  The base class that I ended up with made development a lot easier for me, and could be very helpful for the rest of the community, so I’m going to highlight it now, the PluginUserControlBase.

UpdateConnection

The first improvement the base makes is simply to implement the required IMsCrmToolsPluginUserControl interface.  The documentation shows UpdateConnection has to check the string name being being passed in to determine which method to call:

public void UpdateConnection(IOrganizationService newService,
                     string actionName = "",
                     object parameter = null)
{
    Service = newService;

    if (actionName == "WhoAmI")
    {
        ProcessWhoAmI();
    }
}


This is a really painful implementation since each method that you need a connection to CRM, will require an if statement for the method to actually call.  It also allows for late bound exceptions if the action name is updated.  Here is my implementation:



public virtual void UpdateConnection(IOrganizationService newService, ConnectionDetail detail, string actionName, object parameter)
{
    Service = newService;
    ConnectionDetail = detail;
    OnConnectionUpdated(new ConnectionUpdatedEventArgs(newService, detail));
    if (actionName == String.Empty)
    {
        return;
    }

    MethodInfo method;
    if (parameter == null)
    {
        method = GetType().GetMethod(actionName);
        if (method == null)
        {
            throw new Exception("Unable to find method " + GetType().Name + "." + actionName);
        }
        method.Invoke(this, null);
    }
    else
    {
        var externalCaller = parameter as ExternalMethodCallerInfo;
        if (externalCaller == null)
        {
            method = GetType().GetMethod(actionName, new[] {parameter.GetType()});
            if (method == null)
            {
                throw new Exception("Unable to find method " + GetType().Name + "." + actionName);
            }
            method.Invoke(this, new[] {parameter});
        }
        else
        {
            externalCaller.ExternalAction();
        }
    }
}


The Service and ConnectionDetail get set, then an Event is raised for any plugins that need to know when the connection is updated.  Then, rather than having an if statement, reflection is used to lookup the correct method to execute.  The ExternalMethodCallerInfo check also allows an action to be used, incase a method needs to be called with more than one parameter.



 



ExecuteMethod



The documentation shows a button clicked event handler having to check if the Service is populated, and then passing in the string name of the method to call:



private void BtnWhoAmIClick(object sender, EventArgs e)
{
    if (Service == null)
    {
        if (OnRequestConnection != null)
        {
            var args = new RequestConnectionEventArgs {ActionName = "WhoAmI", Control = this};
            OnRequestConnection(this, args);
        }
    }
    else
    {
        ProcessWhoAmI();
    }
}


This is also really annoying, having to check each time if the service is populated, and calling it differently depending on it being populated or not.  If the plugin has 3 or 4 actions that require the Crm Service, the OnRequestConnection logic will have to be duplicated multiple times.  I created an ExecuteMethod method to do all of that automagically, using actions rather than strings:



/// <summary>
/// Checks to make sure that the Plugin has an IOrganizationService Connection, before calling the action.
/// </summary>
/// <param name="action"></param>
public void ExecuteMethod(Action action)
{
    if (Service == null)
    {
        var name = action.GetMethodInfo().Name;
        if (name.Contains("__"))
        {
            throw new ArgumentOutOfRangeException("action",
                @"The Action of an Execute Method must not be a lambda.  Use the ExecuteAction(action, parameter) Method.");
        }

        OnRequestConnection(this, new RequestConnectionEventArgs
        {
            ActionName = action.GetMethodInfo().Name,
            Control = this
        });
    }
    else
    {
        action();
    }
}

/// <summary>
/// Checks to make sure that the Plugin has an IOrganizationService Connection, before calling the action.
/// </summary>
/// <param name="action"></param>
/// <param name="parameter"></param>
public void ExecuteMethod<T>(Action<T> action, T parameter)
{
    var caller = parameter as ExternalMethodCallerInfo;
    if (Service == null)
    {
        if (caller == null)
        {
            OnRequestConnection(this, new RequestConnectionEventArgs
            {
                ActionName = action.GetMethodInfo().Name,
                Control = this,
                Parameter = parameter
            });
        }
        else
        {
            OnRequestConnection(this, new RequestConnectionEventArgs
            {
                ActionName = "Recaller",
                Control = this,
                Parameter = parameter
            });
        }
    }
    else if (caller == null)
    {
        action(parameter);
    }
    else
    {
        caller.ExternalAction.Invoke();
    }
}

private void Recaller(ExternalMethodCallerInfo info)
{
    info.ExternalAction.Invoke();
}


WorkAsync



The final improvement that I wanted to highlight was WorkAsync methods which eliminate the need to have duplicated code to create a background worker and update statuses: 



WorkAsync(
    "Message to Display when Starting a CRM Call",
    (w, e) => { /* Work To Do Asynchronously */ },
         e => { /* Cleanup when work has completed */ });


The end result is a much much more simplified plugin:



public partial class SampleTool : PluginUserControlBase
{


    private void BtnWhoAmIClick(object sender, EventArgs e)
    {
        ExecuteMethod(ProcessWhoAmI);
    }

    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));
            }
            );
    }


    private void BtnCloseClick(object sender, EventArgs e)
    {
        base.CloseToolPrompt();
    }
}


Interested in creating a plugin for the XrmToolBox?  Just download the Early Bound Generator and reference the DLab.XrmToolboxCommon dll.



Happy Coding!