Tip #802: PowerApps vs Dynamics 365

I have a scenario for a line of business application. Should I use Dynamics 365, or should I build a Power App?

This is a question that a growing number of companies are asking. The marketing message around PowerApps is very similar to the xrm message–build powerful line of business applications without having to worry about the “plumbing” (security, authentication, data model) and with minimal custom development. 

 When approaching this question, I would first look at what business problem you are trying to solve. If you are doing anything like sales opportunity management, customer service, field service, Dynamics is a better place to start because it has rich and mature functionality in these areas that would be very difficult to duplicate with a PowerApp, along with the infrastructure to handle complex security requirements.

But if you are looking to just build a simple focused business mobile app, PowerApps are a good option. And Dynamics and PowerApps are not mutually exclusive, since Dynamics can be a data source for a PowerApp. PowerApps are also a great home when there are needs for mashup experiences where CRM and other systems come in one app.

If you want full Dynamics functionality on a mobile device, the best choice is probably going to be Dynamics 365 mobile apps. If you want a more limited focus mobile app that includes Dynamics data but also includes data and functionality from other sources, PowerApps may be a better choice. And the right answer may well be “both.”

To access Dynamics data from PowerApps, users have to be licensed for Dynamics, so users will have access to both applications.

Thanks to Nikita Polyakov for contributing to this tip!

Do you think that Field Service is the top Dynamics news of 2016? Vote now for the top Dynamics new story of the year, then tune in to CRM Audio to hear the results next week for our year in review episode.

Back

Tip #801: How to get Dynamics 365 now

So you licensed Dynamics CRM Online prior to the November release of Dynamics 365, and you want to go to Dynamics 365 now, without waiting for the upgrades to happen in January (and you haven’t really done much configuration in your environment).

Here’s how you can have that Dynamics 365 goodness right now:

  1. Go to the instance selector and select your sandbox organization and click the “reset” button.

reset1

2. Select “Dynamics 365” as the target version and reset your organization.

reset2

Your sandbox will be wiped out and replaced with a brand new shiny Dynamics 365 organization. Note this will do away with any data or configuration you may have in your environment. If you want to keep what you have, you will need to schedule your upgrade.

But what about production environments? I don’t see the reset option.

Production environments do not have the same reset and restore options as sandbox environments do. This is to protect you from yourself accidentally wiping out your production environment. However, you can change the organization type on your production organization to sandbox, reset it, and then set it back to production.

Do you think that online backup and restore is the top Dynamics news of 2016? Vote now for the top Dynamics new story of the year, then tune in to CRM Audio to hear the results next week for our year in review episode.

Tip #800: When read only is not read only

You have a field that you want to make read only. So you check the box on the form field properties to make the field read only.

read-only

If you have Dynamics 365, keep in mind that there are now more places than the form that fields may be updated–editable grids.

Just setting the field on the form to read only will not make the field read only from editable grids. Keep in mind that there can be multiple forms per entity, and a field could be read only on one form but not read only on other forms. Just setting the form field behavior to read only doesn’t really make the field read only.

So if you want to really make a field read only, use field security.

Do you think that editable grids are the top Dynamics news of 2016? Vote now for the top Dynamics new story of the year, then tune in to CRM Audio to hear the results next week for our year in review episode.

 

 

Tip #799: Video Guide to Dynamics 365 App Designer

Lego titanicIn this video, we take a more in depth look at Dynamics 365 Apps Modules. We look at how they are presented in the application, how to create them, how to control security, how to work with the different components, and more.

YouTube player

Give us your feedback, all of it: good, bad, and ugly, I’m sure we can take it. Suggest new topics either in comments or by sending your ideas to jar@crmtipoftheday.com.

Don’t forget to subscribe to http://youtube.com/crmtipoftheday!

Tip #798: CrmServiceClient and multiple instances

Reduce reuse recycleIf you are using Microsoft.Xrm.Tooling.Connector.CrmServiceClient and relying on nuget to reference the assembly in your project (as you should) then you need to be aware of what seems to be an odd behavior but is, in fact, a bug fix.

Run the following code:

var s1 = $@"Url=https://foobar.crm.dynamics.com;
   AuthType=Office365;
   UserName=user@foobar.onmicrosoft.com;
   Password=password";
var s2 = $@"Url=https://barbaz.crm.dynamics.com;
   AuthType=Office365;
   UserName=user@barbaz.onmicrosoft.com;
   Password=password";

using (var crmSvc = new CrmServiceClient(s1))
{
  Console.WriteLine($"Ready={crmSvc.IsReady}");
  Console.WriteLine($"User={crmSvc.GetMyCrmUserId()}");
  Console.WriteLine(
    $"Org={crmSvc.ConnectedOrgFriendlyName}");
}
using (var crmSvc = new CrmServiceClient(s2))
{
  Console.WriteLine($"Ready={crmSvc.IsReady}");
  Console.WriteLine($"User={crmSvc.GetMyCrmUserId()}");
  Console.WriteLine(
    $"Org={crmSvc.ConnectedOrgFriendlyName}");
}

And get:

Ready=True
User=8fa1eeae-dead-beef-dead-b6b5240774e4
Org=Foobar
Ready=True
User=8fa1eeae-dead-beef-dead-b6b5240774e4
Org=Foobar

Wait a minute, what? Same user and org? Despite the use of the disposable object, connection didn’t change at all?!

Yes, this is correct and, if you were relying on similar code before, you were riding on top of the unintended buggy behavior. Correct and documented behavior is for the connection to be cached and reused unless you specify RequireNewInstance flag either as part of the connection string:

var s2 = $@"RequireNewInstance=True;
  Url=https://barbaz.crm.dynamics.com;
  AuthType=Office365;
  UserName=user@barbaz.onmicrosoft.com;
  Password=password";

or as part of a constructor (useUniqueInstance parameter):

CrmServiceClient crmSvc = new CrmServiceClient(
    "user@barbaz.onmicrosoft.com",
    CrmServiceClient.MakeSecureString("password"),
    "NorthAmerica", "barbaz", 
    useUniqueInstance: true,
    useSsl: true, isOffice365: true);

The change is reflected in the notes on nuget package, along with a list of other fixes and updates:

RequireNewInstance=True will now properly create a unique connection instance, RequireNewInstance=false, will now properly reuse the cached instance of the connection, default is ‘false’

If you are connecting to more than one instance in your code or, if you’d like to maintain two separate connections with the separate instances of metadata, cache, etc, make sure that you use this flag.

Tip #797: When expanding, bring the id with you

IdentityThe week of “just read your mailbox and publish what other smart people found”, continues. Derek Finlinson has the microphone:

Ran into an interesting issue with the Web API and expanding navigation properties today that I thought I would pass along.

I needed to retrieve an attribute from a lookup value related to an entity so ran this lovely query (details changed obviously) (we tried – definitely fake org – t.j.):

https://fakeorg.crm.dynamics.com/api/data/v8.1/msdyn_workorderproducts?$select=msdyn_workorderproductid&
$expand=msdyn_workorderincident($select=new_customfield)

It worked great when new_customfield contained data. However, if it was null, the msdyn_workorderincident entity itself came back as null, even though the work order product was in fact related to an incident. To get around this, just make sure to always include the id field in your expanded $select (msdyn_workorderincidentid in this case).

https://fakeorg.crm.dynamics.com/api/data/v8.1/msdyn_workorderproducts?$select=msdyn_workorderproductidid&
$expand=msdyn_workorderincident($select=new_customfield,msdyn_workorderincident)

That way you still get your expanded entity returned with a null new_customfield. Not sure if this is common knowledge but was new to me. Hopefully it helps someone else out.

Tip #796: Long live ExecuteFetchRequest!

Love those days when people send their gems and discoveries to jar@crmtipoftheday.com – other people’s cleverness tend to rub off on us, making us a bit smarter every single post.

Aron Fischman from xRM Edge LLC made this intricate discovery while working on a product that calculates aggregates on CRM data. In his travels he’s noticed that with ExecuteFetchRequest we can run aggregates on most entities, but with RetrieveMultiple, certain entities are excluded.

For example, the postrole entity does not support RetrieveMultiple, but we can still run aggregates against it using ExecuteFetchRequest. The behavior is the same using CrmServiceClient.

Here is some sample code:

using Microsoft.Crm.Sdk.Messages;
using Microsoft.Xrm.Client;
using Microsoft.Xrm.Client.Services;
using Microsoft.Xrm.Sdk.Query;
using Microsoft.Xrm.Tooling.Connector;

namespace Lab_ExecuteFetchVsRetrieveMultiple
{
  class Program
  {
    static void Main(string[] args)
    {
      var fetch = @"<fetch aggregate='true' mapping='logical'>
                <entity name='postrole'>
                <attribute name='postroleid' alias='aggregate_result' aggregate='count' />
                </entity>
              </fetch>";
      var connectionString = "Url=https://foobar.crm.dynamics.com; Username=user@foobar.onmicrosoft.com; Password=myPass; AuthType=Office365";
      var connection = CrmConnection.Parse(connectionString);
      var executeFetchReq = new ExecuteFetchRequest { FetchXml = fetch };

      //Try with IOrganizationService
      var orgService = new OrganizationService(connection);
      //Works
      var orgSvcExecuteFetchResponse = (ExecuteFetchResponse)orgService.Execute(executeFetchReq);
      //Doesn't work
      var orgSvcRetrieveMultipleResponse = orgService.RetrieveMultiple(new FetchExpression(fetch));

      //Try with CrmServiceClient:
      var crmSvcClient = new CrmServiceClient(connectionString);
      //Works
      var crmSvcExecuteFetchResponse = crmSvcClient.Execute(executeFetchReq);
      //Doesn't work
      var crmSvcRetrieveMultipleResponse = crmSvcClient.RetrieveMultiple(new FetchExpression(fetch));
    }
  }
}

The ExecuteFetchRequest works fine, but the RetrieveMultiple throws an exception:
Long live fetch

When pressed, Aron dug out the whole list of entities with the same behavior.

  • AuthorizationServer
  • BusinessDataLocalizedLabel
  • BusinessProcessFlowInstance
  • Calendar
  • CalendarRule
  • ChildIncidentCount
  • ComplexControl
  • DependencyFeature
  • ImageDescriptor
  • LookUpMapping
  • MailboxStatistics
  • MetadataDifference
  • MultiEntitySearch
  • MultiEntitySearchEntities
  • OrganizationUI
  • PartnerApplication
  • PostRegarding
  • PrincipalAttributeAccessMap
  • PrincipalEntityMap
  • PrincipalObjectAccessReadSnapshot
  • PrincipalSyncAttributeMap
  • QueueItemCount
  • QueueMemberCount
  • RecordCountSnapshot
  • RollupJob
  • RollupProperties
  • SharePointDocument
  • SqlEncryptionAudit
  • Subscription
  • SubscriptionClients
  • SubscriptionManuallyTrackedObject
  • SubscriptionSyncInfo
  • SyncAttributeMapping
  • SyncAttributeMappingProfile
  • SystemApplicationMetadata
  • SystemForm
  • SystemUserManagerMap
  • SystemUserSyncMappingProfiles
  • TeamSyncAttributeMappingProfiles
  • TimeZoneLocalizedName
  • TraceAssociation
  • TraceRegarding
  • UserApplicationMetadata
  • WorkflowWaitSubscription
None of these strike him as “major” entities that users should be concerned with, but he could see an Admin possibly wanting to count some of these entities. And, it is interesting that deprecating ExecuteFetchRequest removes some functionality – however trivial it may be.

Tip #795: How to change your portal audience

Empty seatsWe all make poor choices in life. Selecting the right audience for Dynamics 365 Portals is one of them. You made a mistake, you wanted Partner but selected Employee and now you seem to be stuck. You even tried importing all portal solutions by hand and, while they do light up the site selector, audience remains grey and unhappy with the big stop sign hovering over it:

Portal binding selection

Getting out of this is very easy (once you know how to).

  1. Go to Dynamics 365 Admin page, go to Applications, select your portal, click Manage
  2. On portal management page click Manage Dynamics 365 Instance then click Update Dynamics 365 Instance
    Manage Dynamics 365 instance
  3. A fair warning is displayed. Since different portals can have different moving parts, templates, and modules, there is no way to sensibly move the data across. If you have multi-lingual portal setup, that will be lost as well.
    Portal redeploy loses data
  4. Magic screen is displayed. Me – happy!
    Portal and audience select
  5. Select what you need, click the checkbox (errr, I know you’re trying to be cute but what’s wrong with the plain BIG button, portal folks?!). The message advising you to take a nap or go for a long lunch is displayed
    Portal redeploy will take a few hours
    In other words, this is not a migration, this is redeployment. No need to import solutions prior to this process – that will be taken care of for you.

Your new audience awaits you.

Tip #794: Connect to Dynamics 365 in Azure Functions

Something usefulDelegating some work from plugins to Azure Functions turned out to be not as complex as we expected. Making function to do something useful (like updating Dynamics 365 records) was entirely different story.

After running some NEL (naked eye learning) algorithms against our sales pipeline we calculated that an average opportunity value is inflated by 20%. What would be a better candidate for a function than our freshly baked AutOpAd™ (Automatic Opportunity Adjuster). We have the basic plumbing worked out, these are the steps to turn it into the BS (Business Solutions) sales machine.

Opportunity Helper

In Azure UI for the function click View Files, then +Add, name the file opportunity.csx and add the following content:

using Microsoft.Xrm.Sdk;
using Microsoft.Xrm.Tooling.Connector;

public static void ChangeOpportunity(CrmServiceClient crmSvc, Entity opportunity, decimal howmuch, TraceWriter log)
{
  log.Info($"Changing opportunity {opportunity.GetAttributeValue<string>("name")} ({opportunity.Id}) by {howmuch}");
  
  var estvalue = opportunity.GetAttributeValue<Money>("estimatedvalue");

  if(estvalue == null)
  {
    log.Info("Opportunity is priceless - nothing to do");
  }
  else
  {
    Entity oppUpd = new Entity("opportunity");
    oppUpd["opportunityid"] = opportunity.Id;
    oppUpd["estimatedvalue"] = new Money(estvalue.Value * (1m + howmuch));
    crmSvc.Update(oppUpd);
    
    if(howmuch != 0m) 
    {
      log.Info($"Opportunity value {(howmuch < 0m ? "de" : "in")}creased");
    }
  }
}

Don’t worry about any compilation errors just yet – we are not done.

Main code

Let’s now use the above soul of AutOpAd™ and change the heart, i.e. run.csx to match it:

#r "Microsoft.ServiceBus"

#load "opportunity.csx"

using System;
using System.Threading.Tasks;
using Microsoft.ServiceBus.Messaging;
using Microsoft.Xrm.Sdk;
using Microsoft.Xrm.Tooling.Connector;

public static void Run(BrokeredMessage myQueueItem, TraceWriter log)
{
  log.Info($"C# ServiceBus queue trigger function processed message: {myQueueItem}");
  log.Info($"Message ID: {myQueueItem.CorrelationId}");

  var rec = myQueueItem.GetBody<RemoteExecutionContext>();
  if(rec.PrimaryEntityName == "opportunity")
  {
    Entity entity = rec.InputParameters["Target"] as Entity;
    var name = entity.GetAttributeValue<string>("name");
    var est = entity.GetAttributeValue<Money>("estimatedvalue");
    string value = est == null ? "unknown value": string.Format("{0:C2}", est.Value);
    log.Info($"Opportunity {name} for {value} was created");

	// New stuff!
    log.Info($"Let's decrease the opportunity value by 20%");

    CrmServiceClient crmSvc = new CrmServiceClient(
      "foo@barbaz.onmicrosoft.com",
      CrmServiceClient.MakeSecureString("pass@word1 is not a strong password"),
      "NorthAmerica", "barbaz",
      useSsl: true, isOffice365: true);

    log.Info($"Connection result: {crmSvc.IsReady}");
    if(crmSvc.IsReady)
    {
      log.Info($"User id: {crmSvc.GetMyCrmUserId()}");
      ChangeOpportunity(crmSvc, entity, -0.2m, log);
    }
    else 
    {
      log.Info("Couldn't connect to CRM!");
    }
  }
}

Painful stuff

Now, the important stuff, otherwise it wouldn’t be a tip, just a bunch code, right? We need to get a reference for Microsoft.CrmSdk.XrmTooling.CoreAssembly into project.json file, right? Sounds easy but, as it turned out, due to the way SDK assemblies refer to each other combined with some idiosyncrasies of Azure Function runtime binding, referring to the assemblies neither by the full version as 8.2.0.1, nor as a short 8.2 would work. Only 8.2.0 and nothing else. Go figure. Content of project.json is as following:

{
  "frameworks": {
    "net46":{
      "dependencies": {
        "Microsoft.CrmSdk.CoreAssemblies": "8.2.0",
        "Microsoft.CrmSdk.XrmTooling.CoreAssembly": "8.2.0"
      }
    }
  }
}

Now your AutOpAd™ should compile successfully and when new opportunity gets created, you’ll see the following output:

2016-12-12T23:07:26.773 Function started (Id=50a61122-a8f4-4822-8648-d5638385b88e)
2016-12-12T23:07:27.196 C# ServiceBus queue trigger function processed message: Microsoft.ServiceBus.Messaging.BrokeredMessage{MessageId:e0df93163d334e8dbe055e2ed816ba12}
2016-12-12T23:07:27.196 Message ID: {3b36858e-b709-4e0c-8dde-b2a0df0c89ba}
2016-12-12T23:07:27.196 Opportunity I am a salesperson of the year for $100.00 was created
2016-12-12T23:07:27.196 Let's decrease the opportunity value by 20%
2016-12-12T23:07:27.196 Connection result: True
2016-12-12T23:07:27.196 User id: d17c298b-e7bb-e611-80ff-c4346bac3ac4
2016-12-12T23:07:27.213 Changing opportunity I am a salesperson of the year (d3d576bd-bfc0-e611-8101-c4346bac8af8) by -0.2
2016-12-12T23:07:27.337 Opportunity value decreased
2016-12-12T23:07:27.337 Function completed (Success, Id=50a61122-a8f4-4822-8648-d5638385b88e)

While you are at it, in Function App UI, click Function app settings on the left, then click Configure app settings, and change Platform to 64-bit. We ain’t doing no 32-bit with Dynamics 365 no more.

Tip #793: Enabled app does not mean installed

Gun lockedDepending on the licensing plan, Dynamics 365 instances can have an option to provision Dynamics 365 Portal. In Dynamics 365 Administration Center navigate to Applications and, if you have portal add-on in your subscription, you will see coveted Not Configured against the Portal Add-On app. Click Manage and off you go with the portal provisioning.

During the provisioning, depending on the portal selection, you may see the following message: “Cannot select this portal as the Voice of the Customer (VoC) has not been installed in your Dynamics 365-organisation”. Wait what? I just saw VoC enabled?!
VoC Enabled
As Gustaf “Surströmming” Westerlund discovered, enabled does not mean installed. Go back to your instances, select the instance, click Solutions, then select Voice of the Customer and click Install. Enabled means that your license allow installation of that particular app, installed means that you actually installed the app.