public void RegisterServiceEndpoints(PluginManifest manifest, IOrganizationService client, TracingHelper t)
        {
            t.Debug($"Entering PluginWrapper.RegisterServiceEndpoints");

            if (manifest.Clobber)
            {
                t.Warning($"Manifest has 'Clobber' set to true. Deleting referenced Plugins before re-registering");
                this.UnregisterServiceEndPoints(manifest, client, t);
            }

            // 1. Register Service Endpoints
            foreach (var serviceEndpoint in manifest.ServiceEndpoints)
            {
                t.Debug($"Registering Assembly {serviceEndpoint.Name}");
                var createdEndpoint = serviceEndpoint.Register(client);
                t.Info($"Assembly {serviceEndpoint.Name} registered with ID {createdEndpoint.Id}");

                SolutionWrapper.AddSolutionComponent(client, manifest.SolutionName, createdEndpoint.Id, ComponentType.ServiceEndpoint);
                t.Debug($"Plugin Step {serviceEndpoint.Name} added to solution {manifest.SolutionName}");

                // 2. Register Steps
                foreach (var step in serviceEndpoint.Steps)
                {
                    t.Debug($"Processing Step = {step.FriendlyName}");

                    var sdkMessage       = GetSdkMessageQuery(step.Message).RetrieveSingleRecord(client);
                    var sdkMessageFilter = GetSdkMessageFilterQuery(step.PrimaryEntity, sdkMessage.Id).RetrieveSingleRecord(client);

                    var createdStep = step.Register(client, createdEndpoint, sdkMessage.ToEntityReference(), sdkMessageFilter.ToEntityReference());
                    t.Info($"Plugin step {step.FriendlyName} registered with ID {createdStep.Id}");

                    if (manifest.SolutionName != null)
                    {
                        SolutionWrapper.AddSolutionComponent(client, manifest.SolutionName, createdStep.Id, ComponentType.SdkMessageProcessingStep);
                        t.Debug($"Plugin Step {step.FriendlyName} added to solution {manifest.SolutionName}");
                    }

                    // 3. register Entity Images
                    if (step.EntityImages == null)
                    {
                        continue;
                    }
                    foreach (var entityImage in step.EntityImages)
                    {
                        t.Debug($"Processing Entity Image = {entityImage.Name}");
                        var createdImage = entityImage.Register(client, createdStep);
                        t.Info($"Entity image {entityImage.Name} registered with ID {createdImage}");
                    }
                }
            }

            t.Debug($"Exiting PluginWrapper.RegisterServiceEndpoints");
        }
        /// <summary>
        /// Create or update security roles
        /// </summary>
        /// <param name="manifest"></param>
        /// <param name="client"></param>
        /// <param name="t"></param>
        /// <param name="publisherPrefix"></param>
        public void CreateSecurityRoles(ConfigurationManifest manifest, IOrganizationService client, TracingHelper t, string publisherPrefix)
        {
            if (manifest.SecurityRoles == null)
            {
                t.Debug($"No security roles in manifest to be processed");
            }
            else
            {
                var rootBusinessUnit = XrmClient.GetRootBusinessUnit(client);

                foreach (var securityRole in manifest.SecurityRoles)
                {
                    var existingRole = GetExistingSecurityRoleQuery(securityRole.Name, rootBusinessUnit)
                                       .RetrieveSingleRecord(client);

                    var role = new Role()
                    {
                        Name           = securityRole.Name,
                        BusinessUnitId = rootBusinessUnit
                    }.CreateOrUpdate(client, GetExistingSecurityRoleQuery(securityRole.Name, rootBusinessUnit));

                    SolutionWrapper.AddSolutionComponent(client, manifest.SolutionName, role.Id, ComponentType.Role);

                    if (securityRole.Privileges == null)
                    {
                        continue;
                    }
                    foreach (var rolePrivilege in securityRole.Privileges)
                    {
                        var retrievePrivilegesByName = new QueryExpression("privilege")
                        {
                            ColumnSet = new ColumnSet(true),
                            Criteria  = new FilterExpression
                            {
                                Conditions =
                                {
                                    new ConditionExpression("name", ConditionOperator.Equal, rolePrivilege)
                                }
                            }
                        };
                        var privilegeId = retrievePrivilegesByName.RetrieveSingleRecord(client);
                        if (privilegeId == null)
                        {
                            t.Warning($"Privilege with name {rolePrivilege} cannot be found. " +
                                      $"{rolePrivilege} for role {securityRole.Name} has been skipped");
                            continue;
                        }

                        var addedPrivilege = new AddPrivilegesRoleRequest()
                        {
                            RoleId     = role.Id,
                            Privileges = new []
                            {
                                new RolePrivilege()
                                {
                                    PrivilegeId = privilegeId.Id,
                                    Depth       = PrivilegeDepth.Global
                                }
                            }
                        };
                        client.Execute(addedPrivilege);
                    }
                }
            }
        }
        /// <summary>
        /// Create or update global option sets from ConfigurationManifest
        /// </summary>
        /// <param name="manifest"></param>
        /// <param name="client"></param>
        /// <param name="t"></param>
        /// <param name="publisherPrefix"></param>
        public void CreateGlobalOptionSets(ConfigurationManifest manifest, IOrganizationService client, TracingHelper t, string publisherPrefix)
        {
            if (manifest.OptionSets == null)
            {
                t.Debug($"No global optionsets in manifest to be processed");
            }
            else
            {
                foreach (var optionSet in manifest.OptionSets)
                {
                    t.Debug($"Processing global option set: {optionSet.DisplayName}");

                    var targetOptionSet = new OptionSetMetadata
                    {
                        Name          = string.IsNullOrEmpty(optionSet.SchemaName) ? optionSet.DisplayName.GenerateLogicalNameFromDisplayName(publisherPrefix) : optionSet.SchemaName,
                        DisplayName   = new Label(optionSet.DisplayName, 1033),
                        IsGlobal      = true,
                        OptionSetType = OptionSetType.Picklist,
                    };
                    foreach (var option in optionSet.Items)
                    {
                        targetOptionSet.Options.Add(new OptionMetadata(option.CreateLabelFromString(), null));
                    }

                    bool existingOptionSet;
                    try
                    {
                        var retrieveOptionSet = (RetrieveOptionSetResponse)client.Execute(new RetrieveOptionSetRequest()
                        {
                            Name = optionSet.SchemaName
                        });
                        var retrievedMetadataAttribute = retrieveOptionSet.OptionSetMetadata;
                        targetOptionSet.DisplayName = optionSet.DisplayName == null
                            ? retrievedMetadataAttribute.DisplayName
                            : optionSet.DisplayName.CreateLabelFromString();
                        targetOptionSet.MetadataId = retrievedMetadataAttribute.MetadataId;

                        existingOptionSet = true;
                    }
                    catch (FaultException)
                    {
                        existingOptionSet = false;
                    }

                    if (existingOptionSet)
                    {
                        client.Execute(new UpdateOptionSetRequest()
                        {
                            MergeLabels = true,
                            OptionSet   = targetOptionSet
                        });
                        if (targetOptionSet.MetadataId != null)
                        {
                            SolutionWrapper.AddSolutionComponent(client, manifest.SolutionName, targetOptionSet.MetadataId.Value, ComponentType.OptionSet);
                        }
                    }
                    else
                    {
                        var response = (CreateOptionSetResponse)client.Execute(new CreateOptionSetRequest()
                        {
                            OptionSet = targetOptionSet
                        });
                        SolutionWrapper.AddSolutionComponent(client, manifest.SolutionName, response.OptionSetId, ComponentType.OptionSet);
                    }

                    t.Info($"Successfully processed global option set: {optionSet.DisplayName}");
                }
            }
        }
        /// <summary>
        /// Loops through each PluginAssembly in the manifest and registers all assemblies, plugins, steps and images
        /// </summary>
        /// <param name="manifest">XML plugin manifest</param>
        /// <param name="client">IOrganizationService client reference</param>
        /// <param name="t">Tracing helpers for logging</param>
        public void RegisterPlugins(PluginManifest manifest, IOrganizationService client, TracingHelper t)
        {
            t.Debug($"Entering PluginWrapper.RegisterPlugins");

            // 0. Validate manifest before continuing
            var manifestValidation = this.Validate(manifest);

            if (!manifestValidation.IsValid)
            {
                t.Critical($"Manifest is invalid and has {manifestValidation.Errors.Count()} errors");
                throw new InvalidManifestException(manifestValidation.ToString());
            }

            if (manifest.Clobber)
            {
                t.Warning($"Manifest has 'Clobber' set to true. Deleting referenced Plugins before re-registering");
                this.UnregisterServiceEndPoints(manifest, client, t);
                this.UnregisterPlugins(manifest, client, t);
            }

            // 1. Register Assemblies
            foreach (var pluginAssembly in manifest.PluginAssemblies)
            {
                t.Debug($"Processing Assembly FriendlyName {pluginAssembly.FriendlyName}");

                if (!File.Exists(pluginAssembly.Assembly))
                {
                    t.Critical($"Assembly {pluginAssembly.Assembly} cannot be found!");
                    continue;
                }

                var targetSolutionName = string.Empty;
                if (!string.IsNullOrEmpty(pluginAssembly.SolutionName))
                {
                    targetSolutionName = pluginAssembly.SolutionName;
                }
                else if (!string.IsNullOrEmpty(manifest.SolutionName))
                {
                    targetSolutionName = manifest.SolutionName;
                }

                t.Debug($"Registering Assembly {pluginAssembly.FriendlyName}");
                var createdAssembly = pluginAssembly.Register(client);
                t.Info($"Assembly {pluginAssembly.FriendlyName} registered with ID {createdAssembly.Id}");

                SolutionWrapper.AddSolutionComponent(client, targetSolutionName, createdAssembly.Id, ComponentType.PluginAssembly);
                t.Debug($"Assembly '{pluginAssembly.FriendlyName}' added to solution {targetSolutionName}");

                if (manifest.UpdateAssemblyOnly)
                {
                    continue;
                }

                // 2a. Register Plugins
                foreach (var plugin in pluginAssembly.Plugins)
                {
                    t.Debug($"PluginType FriendlyName = {plugin.FriendlyName}");
                    var createdPluginType = plugin.Register(client, createdAssembly);
                    t.Info($"Plugin {plugin.FriendlyName} registered with ID {createdPluginType.Id}");

                    if (plugin.Steps != null)
                    {
                        // 2b. Register Plugin Steps
                        foreach (var pluginStep in plugin.Steps)
                        {
                            t.Debug($"Processing Step = {pluginStep.FriendlyName}");

                            var sdkMessage       = GetSdkMessageQuery(pluginStep.Message).RetrieveSingleRecord(client);
                            var sdkMessageFilter = GetSdkMessageFilterQuery(pluginStep.PrimaryEntity, sdkMessage.Id).RetrieveSingleRecord(client);

                            var createdStep = pluginStep.Register(client, createdPluginType, sdkMessage.ToEntityReference(), sdkMessageFilter.ToEntityReference());
                            t.Info($"Plugin step {pluginStep.FriendlyName} registered with ID {createdStep.Id}");

                            SolutionWrapper.AddSolutionComponent(client, targetSolutionName, createdStep.Id, ComponentType.SdkMessageProcessingStep);
                            t.Debug($"Plugin Step '{pluginStep.FriendlyName}' added to solution {targetSolutionName}");

                            // 2c. Register Entity Images
                            if (pluginStep.EntityImages == null)
                            {
                                continue;
                            }
                            foreach (var entityImage in pluginStep.EntityImages)
                            {
                                t.Debug($"Processing Entity Image = {entityImage.Name}");
                                var createdImage = entityImage.Register(client, createdStep);
                                t.Info($"Entity image {entityImage.Name} registered with ID {createdImage.Id}");
                            }
                        }
                    }

                    // 3a. Register Custom APIs
                    if (plugin.CustomApis != null)
                    {
                        // debug: view all apis and children with query - /api/data/v9.1/customapis?$select=uniquename,allowedcustomprocessingsteptype,bindingtype,boundentitylogicalname,description,displayname,executeprivilegename,isfunction,isprivate&$expand=CustomAPIRequestParameters($select=uniquename,name,description,displayname,type,logicalentityname,isoptional),CustomAPIResponseProperties($select=uniquename,name,description,displayname,type,logicalentityname),PluginTypeId($select=plugintypeid,typename,version,name,assemblyname)
                        foreach (var api in plugin.CustomApis)
                        {
                            t.Debug($"Processing Step = {api.FriendlyName}");

                            var createdApi = api.Register(client, createdPluginType);
                            t.Info($"CustomApi {api.FriendlyName} created with ID {createdApi.Id}");

                            SolutionWrapper.AddSolutionComponent(client, targetSolutionName, createdApi.Id, ComponentType.CustomApi);
                            t.Debug($"Custom API '{api.FriendlyName}' added to solution {targetSolutionName}");

                            // 3b. Create/Update Request Parameters
                            if (api.RequestParameters != null)
                            {
                                foreach (var requestParameter in api.RequestParameters)
                                {
                                    t.Debug($"Processing Custom API request parameter '{requestParameter.FriendlyName}'");
                                    var createdParameter = requestParameter.Register(client, createdApi);
                                    t.Info($"Request parameter '{requestParameter.FriendlyName} created with ID {createdParameter.Id}'");

                                    SolutionWrapper.AddSolutionComponent(client, targetSolutionName, createdParameter.Id, ComponentType.CustomApiRequestParameter);
                                    t.Debug($"Request parameter '{requestParameter.FriendlyName}' added to solution {targetSolutionName}");
                                }
                            }

                            // 3c. Create/Update Response Properties
                            if (api.ResponseProperties != null)
                            {
                                foreach (var responseProperty in api.ResponseProperties)
                                {
                                    t.Debug($"Processing Custom API response property '{responseProperty.FriendlyName}'");
                                    var createdProperty = responseProperty.Register(client, createdApi);
                                    t.Info($"Response property '{responseProperty.FriendlyName} created with ID {createdProperty.Id}'");

                                    SolutionWrapper.AddSolutionComponent(client, targetSolutionName, createdProperty.Id, ComponentType.CustomApiResponseProperty);
                                    t.Debug($"Response property '{responseProperty.FriendlyName}' added to solution {targetSolutionName}");
                                }
                            }
                        }
                    }
                }
            }

            t.Debug($"Exiting PluginWrapper.RegisterPlugins");
        }