Note
Access to this page requires authorization. You can try signing in or changing directories.
Access to this page requires authorization. You can try changing directories.
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
Administrator level access to a Microsoft Dataverse environment
A model-driven app that includes the account and task tables
Tip
For help building such an app, go to Build your first model-driven app from scratch.
Visual Studio 2017 (or later version)
Knowledge of the Visual C# programming language
-
Tip
Go to Download tools from NuGet for steps to use a PowerShell script to download the latest tools from NuGet.
Create a new Dataverse plug-in in Visual Studio
Launch Visual Studio.
Select Create a new Project.
Choose the Class Library (.NET Framework) templates and select Next.
Name your Project, choose the .NET Framework 4.6.2, and select Create.
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
In the top toolbar, select Tools > NuGetPackageManager > Manage NuGet Packages for Solutions.
On the Browse tab, search for Microsoft.CrmSdk.CoreAssemblies, and install the latest stable version for your project.
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.
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
In Solution Explorer, on the context menu for your new plug-in select Properties.
On the Signing tab, select Sign the assembly, then choose New… from the dropdown options.
Complete the Create Strong Name Key dialog.
On your project's context menu, select Build.
Register your Dataverse plug-in
Launch the Plugin Registration Tool.
Select Create New Connection, and then enter the account from which you want to register the plug-in to Dataverse.
Select Register > Register New Assembly.
Load the assembly from the .DLL file in your project’s Debug folder.
Select Register Selected Plugins.
In the list of Registered Plugins, select Register New Step from your plugin assembly’s context menu.
Register the Pre-Validation Step to make the plug-in run when someone tries to run a Delete operation on an msdyn_project entity:
Test your new plug-in
- Open Project for the web using an account that isn't in the group who can Delete (that you built into the plug-in).
- In the Project table, attempt to delete a row.
- 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...").