/// <summary>
        /// Returns all the active Microsoft Graph subscriptions
        /// </summary>
        /// <param name="accessToken">The OAuth 2.0 Access Token to use for invoking the Microsoft Graph</param>
        /// <param name="startIndex">First item in the results returned by Microsoft Graph to return</param>
        /// <param name="endIndex">Last item in the results returned by Microsoft Graph to return</param>
        /// <param name="retryCount">Number of times to retry the request in case of throttling</param>
        /// <param name="delay">Milliseconds to wait before retrying the request. The delay will be increased (doubled) every retry.</param>
        /// <param name="azureEnvironment">Defines the Azure Cloud Deployment. This is used to determine the MS Graph EndPoint to call which differs per Azure Cloud deployments. Defaults to Production (graph.microsoft.com).</param>
        /// <returns>List with Subscription objects</returns>
        public static List <Model.Subscription> ListSubscriptions(string accessToken, int startIndex = 0, int endIndex = 999, int retryCount = 10, int delay = 500, AzureEnvironment azureEnvironment = AzureEnvironment.Production)
        {
            if (String.IsNullOrEmpty(accessToken))
            {
                throw new ArgumentNullException(nameof(accessToken));
            }

            List <Model.Subscription> result = null;

            try
            {
                // Use a synchronous model to invoke the asynchronous process
                result = Task.Run(async() =>
                {
                    List <Model.Subscription> subscriptions = new List <Model.Subscription>();

                    var graphClient = GraphUtility.CreateGraphClient(accessToken, retryCount, delay, azureEnvironment: azureEnvironment);

                    var pagedSubscriptions = await graphClient.Subscriptions
                                             .Request()
                                             .GetAsync();

                    int pageCount    = 0;
                    int currentIndex = 0;

                    while (true)
                    {
                        pageCount++;

                        foreach (var s in pagedSubscriptions)
                        {
                            currentIndex++;

                            if (currentIndex >= startIndex)
                            {
                                var subscription = MapGraphEntityToModel(s);
                                subscriptions.Add(subscription);
                            }
                        }

                        if (pagedSubscriptions.NextPageRequest != null && currentIndex < endIndex)
                        {
                            pagedSubscriptions = await pagedSubscriptions.NextPageRequest.GetAsync();
                        }
                        else
                        {
                            break;
                        }
                    }

                    return(subscriptions);
                }).GetAwaiter().GetResult();
            }
            catch (ServiceException ex)
            {
                Log.Error(Constants.LOGGING_SOURCE, CoreResources.GraphExtensions_ErrorOccured, ex.Error.Message);
                throw;
            }
            return(result);
        }
        /// <summary>
        /// Returns the subscription with the provided subscriptionId from Microsoft Graph
        /// </summary>
        /// <param name="accessToken">The OAuth 2.0 Access Token to use for invoking the Microsoft Graph</param>
        /// <param name="subscriptionId">The unique identifier of the subscription to return from Microsoft Graph</param>
        /// <param name="startIndex">First item in the results returned by Microsoft Graph to return</param>
        /// <param name="endIndex">Last item in the results returned by Microsoft Graph to return</param>
        /// <param name="retryCount">Number of times to retry the request in case of throttling</param>
        /// <param name="delay">Milliseconds to wait before retrying the request. The delay will be increased (doubled) every retry.</param>
        /// <param name="azureEnvironment">Defines the Azure Cloud Deployment. This is used to determine the MS Graph EndPoint to call which differs per Azure Cloud deployments. Defaults to Production (graph.microsoft.com).</param>
        /// <returns>Subscription object</returns>
        public static Model.Subscription GetSubscription(string accessToken, Guid subscriptionId, int startIndex = 0, int endIndex = 999, int retryCount = 10, int delay = 500, AzureEnvironment azureEnvironment = AzureEnvironment.Production)
        {
            try
            {
                // Use a synchronous model to invoke the asynchronous process
                var result = Task.Run(async() =>
                {
                    var graphClient = GraphUtility.CreateGraphClient(accessToken, retryCount, delay, azureEnvironment: azureEnvironment);

                    var subscription = await graphClient.Subscriptions[subscriptionId.ToString()]
                                       .Request()
                                       .GetAsync();

                    var subscriptionModel = MapGraphEntityToModel(subscription);
                    return(subscriptionModel);
                }).GetAwaiter().GetResult();

                return(result);
            }
            catch (ServiceException ex)
            {
                Log.Error(Constants.LOGGING_SOURCE, CoreResources.GraphExtensions_ErrorOccured, ex.Error.Message);
                throw;
            }
        }
        /// <summary>
        /// Deletes an existing Microsoft Graph Subscription
        /// </summary>
        /// <param name="subscriptionId">Unique identifier of the Microsoft Graph subscription</param>
        /// <param name="accessToken">The OAuth 2.0 Access Token to use for invoking the Microsoft Graph</param>
        /// <param name="retryCount">Number of times to retry the request in case of throttling</param>
        /// <param name="delay">Milliseconds to wait before retrying the request. The delay will be increased (doubled) every retry</param>
        public static void DeleteSubscription(string subscriptionId,
                                              string accessToken, int retryCount = 10, int delay = 500)
        {
            if (String.IsNullOrEmpty(subscriptionId))
            {
                throw new ArgumentNullException(nameof(subscriptionId));
            }

            try
            {
                // Use a synchronous model to invoke the asynchronous process
                Task.Run(async() =>
                {
                    var graphClient = GraphUtility.CreateGraphClient(accessToken, retryCount, delay);

                    await graphClient.Subscriptions[subscriptionId]
                    .Request()
                    .DeleteAsync();
                }).GetAwaiter().GetResult();
            }
            catch (ServiceException ex)
            {
                Log.Error(Constants.LOGGING_SOURCE, CoreResources.GraphExtensions_ErrorOccured, ex.Error.Message);
                throw;
            }
        }
        /// <summary>
        /// Creates a new Microsoft Graph Subscription
        /// </summary>
        /// <param name="changeType">The event(s) the subscription should trigger on</param>
        /// <param name="notificationUrl">The URL that should be called when an event matching this subscription occurs</param>
        /// <param name="resource">The resource to monitor for changes. See https://docs.microsoft.com/graph/api/subscription-post-subscriptions#permissions for the list with supported options.</param>
        /// <param name="expirationDateTime">The datetime defining how long this subscription should stay alive before which it needs to get extended to stay alive. See https://docs.microsoft.com/graph/api/resources/subscription#maximum-length-of-subscription-per-resource-type for the supported maximum lifetime of the subscriber endpoints.</param>
        /// <param name="clientState">Specifies the value of the clientState property sent by the service in each notification. The maximum length is 128 characters. The client can check that the notification came from the service by comparing the value of the clientState property sent with the subscription with the value of the clientState property received with each notification.</param>
        /// <param name="latestSupportedTlsVersion">Specifies the latest version of Transport Layer Security (TLS) that the notification endpoint, specified by <paramref name="notificationUrl"/>, supports. If not provided, TLS 1.2 will be assumed.</param>
        /// <param name="accessToken">The OAuth 2.0 Access Token to use for invoking the Microsoft Graph</param>
        /// <param name="retryCount">Number of times to retry the request in case of throttling</param>
        /// <param name="delay">Milliseconds to wait before retrying the request. The delay will be increased (doubled) every retry</param>
        /// <returns>The just created Microsoft Graph subscription</returns>
        public static Model.Subscription CreateSubscription(Enums.GraphSubscriptionChangeType changeType, string notificationUrl, string resource, DateTimeOffset expirationDateTime, string clientState,
                                                            string accessToken, Enums.GraphSubscriptionTlsVersion latestSupportedTlsVersion = Enums.GraphSubscriptionTlsVersion.v1_2, int retryCount = 10, int delay = 500)
        {
            if (String.IsNullOrEmpty(notificationUrl))
            {
                throw new ArgumentNullException(nameof(notificationUrl));
            }

            if (String.IsNullOrEmpty(resource))
            {
                throw new ArgumentNullException(nameof(resource));
            }

            Model.Subscription result = null;

            try
            {
                // Use a synchronous model to invoke the asynchronous process
                result = Task.Run(async() =>
                {
                    var graphClient = GraphUtility.CreateGraphClient(accessToken, retryCount, delay);

                    // Prepare the subscription resource object
                    var newSubscription = new Subscription
                    {
                        ChangeType         = changeType.ToString().Replace(" ", ""),
                        NotificationUrl    = notificationUrl,
                        Resource           = resource,
                        ExpirationDateTime = expirationDateTime,
                        ClientState        = clientState
                    };

                    var subscription = await graphClient.Subscriptions
                                       .Request()
                                       .AddAsync(newSubscription);

                    if (subscription == null)
                    {
                        return(null);
                    }

                    var subscriptionModel = MapGraphEntityToModel(subscription);
                    return(subscriptionModel);
                }).GetAwaiter().GetResult();
            }
            catch (ServiceException ex)
            {
                Log.Error(Constants.LOGGING_SOURCE, CoreResources.GraphExtensions_ErrorOccured, ex.Error.Message);
                throw;
            }
            return(result);
        }
        /// <summary>
        /// Updates an existing Microsoft Graph Subscription
        /// </summary>
        /// <param name="subscriptionId">Unique identifier of the Microsoft Graph subscription</param>
        /// <param name="expirationDateTime">The datetime defining how long this subscription should stay alive before which it needs to get extended to stay alive. See https://docs.microsoft.com/graph/api/resources/subscription#maximum-length-of-subscription-per-resource-type for the supported maximum lifetime of the subscriber endpoints.</param>
        /// <param name="accessToken">The OAuth 2.0 Access Token to use for invoking the Microsoft Graph</param>
        /// <param name="retryCount">Number of times to retry the request in case of throttling</param>
        /// <param name="delay">Milliseconds to wait before retrying the request. The delay will be increased (doubled) every retry</param>
        /// <returns>The just updated Microsoft Graph subscription</returns>
        public static Model.Subscription UpdateSubscription(string subscriptionId, DateTimeOffset expirationDateTime,
                                                            string accessToken, int retryCount = 10, int delay = 500)
        {
            if (String.IsNullOrEmpty(subscriptionId))
            {
                throw new ArgumentNullException(nameof(subscriptionId));
            }

            Model.Subscription result = null;

            try
            {
                // Use a synchronous model to invoke the asynchronous process
                result = Task.Run(async() =>
                {
                    var graphClient = GraphUtility.CreateGraphClient(accessToken, retryCount, delay);

                    // Prepare the subscription resource object
                    var updatedSubscription = new Subscription
                    {
                        ExpirationDateTime = expirationDateTime
                    };

                    var subscription = await graphClient.Subscriptions[subscriptionId]
                                       .Request()
                                       .UpdateAsync(updatedSubscription);

                    if (subscription == null)
                    {
                        return(null);
                    }

                    var subscriptionModel = MapGraphEntityToModel(subscription);
                    return(subscriptionModel);
                }).GetAwaiter().GetResult();
            }
            catch (ServiceException ex)
            {
                Log.Error(Constants.LOGGING_SOURCE, CoreResources.GraphExtensions_ErrorOccured, ex.Error.Message);
                throw;
            }
            return(result);
        }
Beispiel #6
0
        /// <summary>
        /// Returns all the Users in the current domain filtered out with a custom OData filter
        /// </summary>
        /// <param name="accessToken">The OAuth 2.0 Access Token to use for invoking the Microsoft Graph</param>
        /// <param name="filter">OData filter to apply to retrieval of the users from the Microsoft Graph</param>
        /// <param name="orderby">OData orderby instruction</param>
        /// <param name="selectProperties">Allows providing the names of properties to return regarding the users. If not provided, the standard properties will be returned.</param>
        /// <param name="startIndex">First item in the results returned by Microsoft Graph to return</param>
        /// <param name="endIndex">Last item in the results returned by Microsoft Graph to return</param>
        /// <param name="retryCount">Number of times to retry the request in case of throttling</param>
        /// <param name="delay">Milliseconds to wait before retrying the request. The delay will be increased (doubled) every retry.</param>
        /// <returns>List with User objects</returns>
        public static List <Model.User> ListUsers(string accessToken, string filter, string orderby, string[] selectProperties = null, int startIndex = 0, int endIndex = 999, int retryCount = 10, int delay = 500)
        {
            if (String.IsNullOrEmpty(accessToken))
            {
                throw new ArgumentNullException(nameof(accessToken));
            }

            List <Model.User> result = null;

            try
            {
                // Use a synchronous model to invoke the asynchronous process
                result = Task.Run(async() =>
                {
                    List <Model.User> users = new List <Model.User>();

                    var graphClient = GraphUtility.CreateGraphClient(accessToken, retryCount, delay);

                    IGraphServiceUsersCollectionPage pagedUsers;

                    pagedUsers = selectProperties != null ?
                                 await graphClient.Users
                                 .Request()
                                 .Select(string.Join(",", selectProperties))
                                 .Filter(filter)
                                 .OrderBy(orderby)
                                 .GetAsync() :
                                 await graphClient.Users
                                 .Request()
                                 .Filter(filter)
                                 .OrderBy(orderby)
                                 .GetAsync();

                    int pageCount    = 0;
                    int currentIndex = 0;

                    while (true)
                    {
                        pageCount++;

                        foreach (var u in pagedUsers)
                        {
                            currentIndex++;

                            if (currentIndex >= startIndex)
                            {
                                var user = new Model.User
                                {
                                    Id                   = Guid.TryParse(u.Id, out Guid idGuid) ? (Guid?)idGuid : null,
                                    DisplayName          = u.DisplayName,
                                    GivenName            = u.GivenName,
                                    JobTitle             = u.JobTitle,
                                    MobilePhone          = u.MobilePhone,
                                    OfficeLocation       = u.OfficeLocation,
                                    PreferredLanguage    = u.PreferredLanguage,
                                    Surname              = u.Surname,
                                    UserPrincipalName    = u.UserPrincipalName,
                                    BusinessPhones       = u.BusinessPhones,
                                    AdditionalProperties = u.AdditionalData
                                };

                                users.Add(user);
                            }
                        }

                        if (pagedUsers.NextPageRequest != null && currentIndex < endIndex)
                        {
                            pagedUsers = await pagedUsers.NextPageRequest.GetAsync();
                        }
                        else
                        {
                            break;
                        }
                    }

                    return(users);
                }).GetAwaiter().GetResult();
            }
            catch (ServiceException ex)
            {
                Log.Error(Constants.LOGGING_SOURCE, CoreResources.GraphExtensions_ErrorOccured, ex.Error.Message);
                throw;
            }
            return(result);
        }
Beispiel #7
0
        /// <summary>
        /// Returns all the Users in the current domain filtered out with a custom OData filter
        /// </summary>
        /// <param name="accessToken">The OAuth 2.0 Access Token to use for invoking the Microsoft Graph</param>
        /// <param name="filter">OData filter to apply to retrieval of the users from the Microsoft Graph</param>
        /// <param name="orderby">OData orderby instruction</param>
        /// <param name="selectProperties">Allows providing the names of properties to return regarding the users. If not provided, the standard properties will be returned.</param>
        /// <param name="startIndex">First item in the results returned by Microsoft Graph to return</param>
        /// <param name="endIndex">Last item in the results returned by Microsoft Graph to return. Provide NULL to return all results that exist.</param>
        /// <param name="retryCount">Number of times to retry the request in case of throttling</param>
        /// <param name="delay">Milliseconds to wait before retrying the request. The delay will be increased (doubled) every retry.</param>
        /// <returns>List with User objects</returns>
        public static List <Model.User> ListUsers(string accessToken, string filter, string orderby, string[] selectProperties = null, int startIndex = 0, int?endIndex = 999, int retryCount = 10, int delay = 500)
        {
            if (String.IsNullOrEmpty(accessToken))
            {
                throw new ArgumentNullException(nameof(accessToken));
            }
            // Rewrite AdditionalProperties to Additional Data
            var propertiesToSelect = new List <string> {
                "BusinessPhones", "DisplayName", "GivenName", "JobTitle", "Mail", "MobilePhone", "OfficeLocation", "PreferredLanguage", "Surname", "UserPrincipalName", "Id", "AccountEnabled"
            };

            selectProperties = selectProperties?.Select(p => p == "AdditionalProperties" ? "AdditionalData" : p).ToArray();

            if (selectProperties != null)
            {
                foreach (var property in selectProperties)
                {
                    if (!propertiesToSelect.Contains(property))
                    {
                        propertiesToSelect.Add(property);
                    }
                }
            }

            List <Model.User> result = null;

            try
            {
                // Use a synchronous model to invoke the asynchronous process
                result = Task.Run(async() =>
                {
                    List <Model.User> users = new List <Model.User>();

                    var graphClient = GraphUtility.CreateGraphClient(accessToken, retryCount, delay);

                    IGraphServiceUsersCollectionPage pagedUsers;

                    // Retrieve the first batch of users. 999 is the maximum amount of users that Graph allows to be trieved in 1 go. Use maximum size batches to lessen the chance of throttling when retrieving larger amounts of users.
                    pagedUsers = await graphClient.Users.Request()
                                 .Select(string.Join(",", propertiesToSelect))
                                 .Filter(filter)
                                 .OrderBy(orderby)
                                 .Top(!endIndex.HasValue ? 999 : endIndex.Value >= 999 ? 999 : endIndex.Value)
                                 .GetAsync();

                    int pageCount    = 0;
                    int currentIndex = 0;

                    while (true)
                    {
                        pageCount++;

                        foreach (var u in pagedUsers)
                        {
                            currentIndex++;

                            if (endIndex.HasValue && endIndex.Value < currentIndex)
                            {
                                break;
                            }

                            if (currentIndex >= startIndex)
                            {
                                var user = new Model.User
                                {
                                    Id                   = Guid.TryParse(u.Id, out Guid idGuid) ? (Guid?)idGuid : null,
                                    DisplayName          = u.DisplayName,
                                    GivenName            = u.GivenName,
                                    JobTitle             = u.JobTitle,
                                    MobilePhone          = u.MobilePhone,
                                    OfficeLocation       = u.OfficeLocation,
                                    PreferredLanguage    = u.PreferredLanguage,
                                    Surname              = u.Surname,
                                    UserPrincipalName    = u.UserPrincipalName,
                                    BusinessPhones       = u.BusinessPhones,
                                    AdditionalProperties = u.AdditionalData,
                                    Mail                 = u.Mail,
                                    AccountEnabled       = u.AccountEnabled,
                                };

                                // If additional properties have been provided, ensure their output gets added to the AdditionalProperties dictonary of the output
                                if (selectProperties != null)
                                {
                                    // Ensure we have the AdditionalProperties dictionary available to fill, if necessary
                                    if (user.AdditionalProperties == null)
                                    {
                                        user.AdditionalProperties = new Dictionary <
                                            string, object>();
                                    }

                                    foreach (var selectProperty in selectProperties)
                                    {
                                        // Ensure the requested property has been returned in the response
                                        var property = u.GetType().GetProperty(selectProperty, BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Instance);
                                        if (property != null)
                                        {
                                            // First check if we have the property natively on the User model
                                            var userProperty = user.GetType().GetProperty(selectProperty, BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Instance);
                                            if (userProperty != null)
                                            {
                                                // Set the property on the User model
                                                userProperty.SetValue(user, property.GetValue(u), null);
                                            }
                                            else
                                            {
                                                // Property does not exist on the User model, add the property to the AdditionalProperties dictionary
                                                user.AdditionalProperties.Add(selectProperty, property.GetValue(u));
                                            }
                                        }
                                    }
                                }

                                users.Add(user);
                            }
                        }

                        if (pagedUsers.NextPageRequest != null && (!endIndex.HasValue || currentIndex < endIndex.Value))
                        {
                            // Retrieve the next batch of users. The possible oData instructions such as select and filter are already incorporated in the nextLink provided by Graph and thus do not need to be specified again.
                            pagedUsers = await pagedUsers.NextPageRequest.GetAsync();
                        }
                        else
                        {
                            break;
                        }
                    }

                    return(users);
                }).GetAwaiter().GetResult();
            }
            catch (ServiceException ex)
            {
                Log.Error(Constants.LOGGING_SOURCE, CoreResources.GraphExtensions_ErrorOccured, ex.Error.Message);
                throw;
            }
            return(result);
        }
        /// <summary>
        /// Returns all the Users in the current domain filtered out with a custom OData filter
        /// </summary>
        /// <param name="accessToken">The OAuth 2.0 Access Token to use for invoking the Microsoft Graph</param>
        /// <param name="filter">OData filter to apply to retrieval of the users from the Microsoft Graph</param>
        /// <param name="orderby">OData orderby instruction</param>
        /// <param name="selectProperties">Allows providing the names of properties to return regarding the users. If not provided, the standard properties will be returned.</param>
        /// <param name="startIndex">First item in the results returned by Microsoft Graph to return</param>
        /// <param name="endIndex">Last item in the results returned by Microsoft Graph to return. Provide NULL to return all results that exist.</param>
        /// <param name="retryCount">Number of times to retry the request in case of throttling</param>
        /// <param name="delay">Milliseconds to wait before retrying the request. The delay will be increased (doubled) every retry.</param>
        /// <returns>List with User objects</returns>
        public static List <Model.User> ListUsers(string accessToken, string filter, string orderby, string[] selectProperties = null, int startIndex = 0, int?endIndex = 999, int retryCount = 10, int delay = 500)
        {
            if (String.IsNullOrEmpty(accessToken))
            {
                throw new ArgumentNullException(nameof(accessToken));
            }
            // Rewrite AdditionalProperties to Additional Data
            var propertiesToSelect = new List <string> {
                "BusinessPhones", "DisplayName", "GivenName", "JobTitle", "Mail", "MobilePhone", "OfficeLocation", "PreferredLanguage", "Surname", "UserPrincipalName", "Id", "AccountEnabled"
            };

            selectProperties = selectProperties?.Select(p => p == "AdditionalProperties" ? "AdditionalData" : p).ToArray();

            if (selectProperties != null)
            {
                foreach (var property in selectProperties)
                {
                    if (!propertiesToSelect.Contains(property))
                    {
                        propertiesToSelect.Add(property);
                    }
                }
            }

            List <Model.User> result = null;

            try
            {
                // Use a synchronous model to invoke the asynchronous process
                result = Task.Run(async() =>
                {
                    List <Model.User> users = new List <Model.User>();

                    var graphClient = GraphUtility.CreateGraphClient(accessToken, retryCount, delay);

                    IGraphServiceUsersCollectionPage pagedUsers;

                    pagedUsers = await graphClient.Users
                                 .Request()
                                 .Select(string.Join(",", propertiesToSelect))
                                 .Filter(filter)
                                 .OrderBy(orderby)
                                 .GetAsync();

                    int pageCount    = 0;
                    int currentIndex = 0;

                    while (true)
                    {
                        pageCount++;

                        foreach (var u in pagedUsers)
                        {
                            currentIndex++;

                            if (endIndex.HasValue && endIndex.Value < currentIndex)
                            {
                                break;
                            }

                            if (currentIndex >= startIndex)
                            {
                                var user = new Model.User
                                {
                                    Id                   = Guid.TryParse(u.Id, out Guid idGuid) ? (Guid?)idGuid : null,
                                    DisplayName          = u.DisplayName,
                                    GivenName            = u.GivenName,
                                    JobTitle             = u.JobTitle,
                                    MobilePhone          = u.MobilePhone,
                                    OfficeLocation       = u.OfficeLocation,
                                    PreferredLanguage    = u.PreferredLanguage,
                                    Surname              = u.Surname,
                                    UserPrincipalName    = u.UserPrincipalName,
                                    BusinessPhones       = u.BusinessPhones,
                                    AdditionalProperties = u.AdditionalData,
                                    Mail                 = u.Mail,
                                    AccountEnabled       = u.AccountEnabled,
                                };

                                users.Add(user);
                            }
                        }

                        if (pagedUsers.NextPageRequest != null && (!endIndex.HasValue || currentIndex < endIndex.Value))
                        {
                            pagedUsers = await pagedUsers.NextPageRequest.GetAsync();
                        }
                        else
                        {
                            break;
                        }
                    }

                    return(users);
                }).GetAwaiter().GetResult();
            }
            catch (ServiceException ex)
            {
                Log.Error(Constants.LOGGING_SOURCE, CoreResources.GraphExtensions_ErrorOccured, ex.Error.Message);
                throw;
            }
            return(result);
        }
        /// <summary>
        /// Returns all the Users in the current domain filtered out with a custom OData filter
        /// </summary>
        /// <param name="accessToken">The OAuth 2.0 Access Token to use for invoking the Microsoft Graph</param>
        /// <param name="filter">OData filter to apply to retrieval of the users from the Microsoft Graph</param>
        /// <param name="orderby">OData orderby instruction</param>
        /// <param name="selectProperties">Allows providing the names of properties to return regarding the users. If not provided, the standard properties will be returned.</param>
        /// <param name="startIndex">First item in the results returned by Microsoft Graph to return</param>
        /// <param name="endIndex">Last item in the results returned by Microsoft Graph to return. Provide NULL to return all results that exist.</param>
        /// <param name="retryCount">Number of times to retry the request in case of throttling</param>
        /// <param name="delay">Milliseconds to wait before retrying the request. The delay will be increased (doubled) every retry.</param>
        /// <param name="useBetaEndPoint">Indicates if the v1.0 (false) or beta (true) endpoint should be used at Microsoft Graph to query for the data</param>
        /// <param name="ignoreDefaultProperties">If set to true, only the properties provided through selectProperties will be loaded. The default properties will not be. Optional. Default is that the default properties will always be retrieved.</param>
        /// <returns>List with User objects</returns>
        public static List <Model.User> ListUsers(string accessToken, string filter, string orderby, string[] selectProperties = null, int startIndex = 0, int?endIndex = 999, int retryCount = 10, int delay = 500, bool useBetaEndPoint = false, bool ignoreDefaultProperties = false)
        {
            if (String.IsNullOrEmpty(accessToken))
            {
                throw new ArgumentNullException(nameof(accessToken));
            }
            // Rewrite AdditionalProperties to Additional Data
            var propertiesToSelect = ignoreDefaultProperties ? new List <string>() : new List <string> {
                "BusinessPhones", "DisplayName", "GivenName", "JobTitle", "Mail", "MobilePhone", "OfficeLocation", "PreferredLanguage", "Surname", "UserPrincipalName", "Id", "AccountEnabled"
            };

            selectProperties = selectProperties?.Select(p => p == "AdditionalProperties" ? "AdditionalData" : p).ToArray();

            if (selectProperties != null)
            {
                foreach (var property in selectProperties)
                {
                    if (!propertiesToSelect.Contains(property))
                    {
                        propertiesToSelect.Add(property);
                    }
                }
            }

            List <Model.User> result = null;

            try
            {
                // Use a synchronous model to invoke the asynchronous process
                result = Task.Run(async() =>
                {
                    List <Model.User> users = new List <Model.User>();

                    var graphClient = GraphUtility.CreateGraphClient(accessToken, retryCount, delay, useBetaEndPoint: useBetaEndPoint);

                    IGraphServiceUsersCollectionPage pagedUsers;

                    // Retrieve the first batch of users. 999 is the maximum amount of users that Graph allows to be trieved in 1 go. Use maximum size batches to lessen the chance of throttling when retrieving larger amounts of users.
                    pagedUsers = await graphClient.Users.Request()
                                 .Select(string.Join(",", propertiesToSelect))
                                 .Filter(filter)
                                 .OrderBy(orderby)
                                 .Top(!endIndex.HasValue ? 999 : endIndex.Value >= 999 ? 999 : endIndex.Value)
                                 .GetAsync();

                    int pageCount    = 0;
                    int currentIndex = 0;

                    while (true)
                    {
                        pageCount++;

                        foreach (var pagedUser in pagedUsers)
                        {
                            currentIndex++;

                            if (endIndex.HasValue && endIndex.Value < currentIndex)
                            {
                                break;
                            }

                            if (currentIndex >= startIndex)
                            {
                                users.Add(MapUserEntity(pagedUser, selectProperties));
                            }
                        }

                        if (pagedUsers.NextPageRequest != null && (!endIndex.HasValue || currentIndex < endIndex.Value))
                        {
                            // Retrieve the next batch of users. The possible oData instructions such as select and filter are already incorporated in the nextLink provided by Graph and thus do not need to be specified again.
                            pagedUsers = await pagedUsers.NextPageRequest.GetAsync();
                        }
                        else
                        {
                            break;
                        }
                    }

                    return(users);
                }).GetAwaiter().GetResult();
            }
            catch (ServiceException ex)
            {
                Log.Error(Constants.LOGGING_SOURCE, CoreResources.GraphExtensions_ErrorOccured, ex.Error.Message);
                throw;
            }
            return(result);
        }
        /// <summary>
        /// Returns the users delta in the current domain filtered out with a custom OData filter. If no <paramref name="deltaToken"/> has been provided, all users will be returned with a deltatoken for a next run. If a <paramref name="deltaToken"/> has been provided, all users which were modified after the deltatoken has been generated will be returned.
        /// </summary>
        /// <param name="accessToken">The OAuth 2.0 Access Token to use for invoking the Microsoft Graph</param>
        /// <param name="deltaToken">DeltaToken to indicate requesting changes since this deltatoken has been created. Leave NULL to retrieve all users with a deltatoken to use for subsequent queries.</param>
        /// <param name="filter">OData filter to apply to retrieval of the users from the Microsoft Graph</param>
        /// <param name="orderby">OData orderby instruction</param>
        /// <param name="selectProperties">Allows providing the names of properties to return regarding the users. If not provided, the standard properties will be returned.</param>
        /// <param name="startIndex">First item in the results returned by Microsoft Graph to return</param>
        /// <param name="endIndex">Last item in the results returned by Microsoft Graph to return. Provide NULL to return all results that exist.</param>
        /// <param name="retryCount">Number of times to retry the request in case of throttling</param>
        /// <param name="delay">Milliseconds to wait before retrying the request. The delay will be increased (doubled) every retry.</param>
        /// <param name="useBetaEndPoint">Indicates if the v1.0 (false) or beta (true) endpoint should be used at Microsoft Graph to query for the data</param>
        /// <param name="ignoreDefaultProperties">If set to true, only the properties provided through selectProperties will be loaded. The default properties will not be. Optional. Default is that the default properties will always be retrieved.</param>
        /// <returns>List with User objects</returns>
        public static Model.UserDelta ListUserDelta(string accessToken, string deltaToken, string filter, string orderby, string[] selectProperties = null, int startIndex = 0, int?endIndex = 999, int retryCount = 10, int delay = 500, bool useBetaEndPoint = false, bool ignoreDefaultProperties = false)
        {
            if (String.IsNullOrEmpty(accessToken))
            {
                throw new ArgumentNullException(nameof(accessToken));
            }
            // Rewrite AdditionalProperties to Additional Data
            var propertiesToSelect = ignoreDefaultProperties ? new List <string>() : new List <string> {
                "BusinessPhones", "DisplayName", "GivenName", "JobTitle", "Mail", "MobilePhone", "OfficeLocation", "PreferredLanguage", "Surname", "UserPrincipalName", "Id", "AccountEnabled"
            };

            selectProperties = selectProperties?.Select(p => p == "AdditionalProperties" ? "AdditionalData" : p).ToArray();

            if (selectProperties != null)
            {
                foreach (var property in selectProperties)
                {
                    if (!propertiesToSelect.Contains(property))
                    {
                        propertiesToSelect.Add(property);
                    }
                }
            }

            var queryOptions = new List <QueryOption>();

            if (!string.IsNullOrWhiteSpace(deltaToken))
            {
                queryOptions.Add(new QueryOption("$skiptoken", deltaToken));
            }

            Model.UserDelta result = null;
            try
            {
                // Use a synchronous model to invoke the asynchronous process
                result = Task.Run(async() =>
                {
                    var usersDelta   = new Model.UserDelta();
                    usersDelta.Users = new List <Model.User>();

                    var graphClient = GraphUtility.CreateGraphClient(accessToken, retryCount, delay, useBetaEndPoint: useBetaEndPoint);

                    IUserDeltaCollectionPage pagedUsers;

                    // Retrieve the first batch of users. 999 is the maximum amount of users that Graph allows to be trieved in 1 go. Use maximum size batches to lessen the chance of throttling when retrieving larger amounts of users.
                    pagedUsers = await graphClient.Users.Delta()
                                 .Request(queryOptions)
                                 .Select(string.Join(",", propertiesToSelect))
                                 .Filter(filter)
                                 .OrderBy(orderby)
                                 .Top(!endIndex.HasValue ? 999 : endIndex.Value >= 999 ? 999 : endIndex.Value)
                                 .GetAsync();

                    int pageCount    = 0;
                    int currentIndex = 0;

                    while (true)
                    {
                        pageCount++;

                        foreach (var pagedUser in pagedUsers)
                        {
                            currentIndex++;

                            if (endIndex.HasValue && endIndex.Value < currentIndex)
                            {
                                break;
                            }

                            if (currentIndex >= startIndex)
                            {
                                usersDelta.Users.Add(MapUserEntity(pagedUser, selectProperties));
                            }
                        }

                        if (pagedUsers.NextPageRequest != null && (!endIndex.HasValue || currentIndex < endIndex.Value))
                        {
                            // Retrieve the next batch of users. The possible oData instructions such as select and filter are already incorporated in the nextLink provided by Graph and thus do not need to be specified again.
                            pagedUsers = await pagedUsers.NextPageRequest.GetAsync();
                        }
                        else
                        {
                            // Check if the deltaLink is provided in the response
                            if (pagedUsers.AdditionalData.TryGetValue("@odata.deltaLink", out object deltaLinkObject))
                            {
                                // Use a regular expression to fetch just the deltatoken part from the deltalink. The base of the URL will thereby be cut off. This is the only part we need to use it in a subsequent run.
                                var deltaLinkMatch = System.Text.RegularExpressions.Regex.Match(deltaLinkObject.ToString(), @"(?<=\$deltatoken=)(.*?)(?=$|&)", System.Text.RegularExpressions.RegexOptions.IgnoreCase);

                                if (deltaLinkMatch.Success && !string.IsNullOrWhiteSpace(deltaLinkMatch.Value))
                                {
                                    // Successfully extracted the deltatoken part from the link, assign it to the return variable
                                    usersDelta.DeltaToken = deltaLinkMatch.Value;
                                }
                            }
                            break;
                        }
                    }

                    return(usersDelta);
                }).GetAwaiter().GetResult();
            }
            catch (ServiceException ex)
            {
                Log.Error(Constants.LOGGING_SOURCE, CoreResources.GraphExtensions_ErrorOccured, ex.Error.Message);
                throw;
            }
            return(result);
        }