Edit

Share via


Prevent deletes of projects in a Project environment by creating a Microsoft Dataverse Plugin

Microsoft Project for the web will soon become Microsoft Planner, which is currently rolling out to customers. To learn more about setting up the new Planner for your organization, see Microsoft Planner for admins.

Project for the Web is collaborative by design and allows all licensed team members full access to the entire project. But sometimes, you want to keep some teammates from deleting projects. This article explains how to use a Dataverse plug-in to block deletion of projects, even in Dynamics 365 Project Operations.

You can modify this plugin to grant the delete permission solely to the project owner, manager, an Admin, or otherwise. Furthermore, Dataverse plug-ins can be used to validate or modify any other operation that is performed on Dataverse Entities (Projects, Tasks, Teams, etc.) and their relationship to other entities.

Prerequisites

Create a new Dataverse plug-in in Visual Studio

  1. Launch Visual Studio.

  2. Select Create a new Project.

  3. Choose the Class Library (.NET Framework) templates and select Next.

    Choose your class library template.

  4. Name your Project, choose the .NET Framework 4.6.2, and select Create.

    Create your class library.

  5. Name the class in your project solution ProjectBlockDeletePlugin.cs. This will make it easier to read in code and understand its purpose.

Add a NuGet Package

  1. In the top toolbar, select Tools > NuGetPackageManager > Manage NuGet Packages for Solutions.

  2. On the Browse tab, search for Microsoft.CrmSdk.CoreAssemblies, and install the latest stable version for your project.

    Install Microsoft.CrmSdk.CoreAssemblies.

Register your plug-in

Note

  • You’re creating a Pre-Validation plug-in. It runs when an operation (Delete) is called on the Dataverse Entity (Project).
  • When someone tries to delete a project, the plug-in checks the GUID of the user who is trying to delete a project in Project for the Web. It then checks Dataverse to see whether the user belongs to a Teams group that is allowed to delete projects. If not, the plug-in cancels the Delete operation.
  • For more context and documentation on what you can do with Dataverse Plug-ins, please visit the Official Dataverse Plug-In Documentation.
  1. Open your new class file (ProjectBlockDeletePlugin.cs), and paste in the following class code, but replace the GUID for the Team ID who gets delete privilege. You can find the GUID in PowerApps Data Explorer.

     using System;
     using Microsoft.Xrm.Sdk;
     using Microsoft.Xrm.Sdk.Query;
     using System.ServiceModel;
     using Microsoft.Xrm.Sdk.Messages;
     namespace PluginTest.Plugins
    {
     public class ProjectBlockDeletePlugin : IPlugin
       {
         public void Execute(IServiceProvider serviceProvider)
         {
           // Obtain the tracing service
           ITracingService tracingService =
           (ITracingService)serviceProvider.GetService(typeof(ITracingService));
    
           // Obtain the execution context from the service provider.  
           IPluginExecutionContext context = (IPluginExecutionContext)
               serviceProvider.GetService(typeof(IPluginExecutionContext));
    
           // The InputParameters collection contains all the data passed in the message request.  
           if (context.InputParameters.Contains("Target") &&
               context.InputParameters["Target"] is EntityReference)
           {
               // Obtain the target entity from the input parameters.  
               EntityReference projectEntityRef = context.InputParameters["Target"] as EntityReference;
    
               // Obtain the organization service reference which you will need for  
               // web service calls.  
               IOrganizationServiceFactory serviceFactory = (IOrganizationServiceFactory)serviceProvider.GetService(typeof(IOrganizationServiceFactory));
               IOrganizationService service = serviceFactory.CreateOrganizationService(context.InitiatingUserId);
    
               try
               {
                   // TODO: Manually set which team has Delete privileges
                   Guid TeamWithDeletePrivilegeId = new Guid("0f96b130-c72c-ec11-b6e5-000d3a59eeac");
    
                   // Create the queryExpression for the team entity query
                   QueryExpression query = new QueryExpression("team");
                   query.ColumnSet = new ColumnSet("teamid");
    
                   // Create a Relationship collection query for the user <-> team relationship
                   Relationship teamMembershipRelationship = new Relationship("teammembership_association");
                   RelationshipQueryCollection relationshipCollectionQuery = new RelationshipQueryCollection();
                   relationshipCollectionQuery.Add(teamMembershipRelationship, query);
    
                   // Create a service RetrieveRequest with the system user - the user who launches the plugin - and all the teams the user is related to.
                   RetrieveRequest request = new RetrieveRequest();
                   request.RelatedEntitiesQuery = relationshipCollectionQuery;
                   request.Target = new EntityReference("systemuser", context.InitiatingUserId);
                   request.ColumnSet = new ColumnSet(true);
    
                   // Execute the RetrieveRequest
                   RetrieveResponse userWithTeamRelationships = (RetrieveResponse)service.Execute(request);
    
                   // Loop through all teams that the user is related to find if they belong the team with delete permissions
                   bool userCanDelete = false;
                   foreach (Entity entity in userWithTeamRelationships.Entity.RelatedEntities[teamMembershipRelationship].Entities)
                   {
                       if ((Guid)entity.Attributes["teamid"] == TeamWithDeletePrivilegeId)
                       {
                           userCanDelete = true;
                           break;
                       };
                   }
    
                   // Throw an exception if the user doesn't have permission to delete - aborting the Delete Operation.
                   if (!userCanDelete)
                   {
    
                       // Optional: retrieve the team name with delete privilege for error message,
                       string TeamWithDeletePrivilegeName = service.Retrieve("team", TeamWithDeletePrivilegeId, new ColumnSet("name")).Attributes["name"].ToString();
    
                       throw new InvalidPluginExecutionException("You do not have permission to delete projects. Delete permission is only granted to members of the \"" + TeamWithDeletePrivilegeName + "\" team.");
                   }
    
               }
    
               catch (FaultException<OrganizationServiceFault> ex)
               {
                   throw new InvalidPluginExecutionException("An error occurred in ProjectBlockDeletePlugin.", ex);
               }
    
               catch (Exception ex)
               {
                   tracingService.Trace("ProjectBlockDeletePlugin: {0}", ex.ToString());
                   throw;
               }
           }
         }
       }
    }
    
    

Important

Make sure that your class is public, and named ProjectBlockDeletePlugin.cs.

Sign your assembly

  1. In Solution Explorer, on the context menu for your new plug-in select Properties.

  2. On the Signing tab, select Sign the assembly, then choose New… from the dropdown options.

    {alt-text}

  3. Complete the Create Strong Name Key dialog.

  4. On your project's context menu, select Build.

Register your Dataverse plug-in

  1. Launch the Plugin Registration Tool.

  2. Select Create New Connection, and then enter the account from which you want to register the plug-in to Dataverse.

    Create New Connection to Dataverse

  3. Select Register > Register New Assembly.

    Register your`new assembly

  4. Load the assembly from the .DLL file in your project’s Debug folder.

  5. Select Register Selected Plugins.

  6. In the list of Registered Plugins, select Register New Step from your plugin assembly’s context menu.

    Register an assembly step

  7. Register the Pre-Validation Step to make the plug-in run when someone tries to run a Delete operation on an msdyn_project entity:

    Finish registration

Test your new plug-in

  1. Open Project for the web using an account that isn't in the group who can Delete (that you built into the plug-in).
  2. In the Project table, attempt to delete a row.
  3. If your plug-in is working, your attempt will fail with a warning ("You do not have permission to delete projects. Delete permission is only...").

Next steps