public static IEnumerable <OperationResult <User> > DeleteAllUsers(RequestManager requestManager) { throw new NotImplementedException("You do not want to execute this by accident!"); // Step 1: start downloading user objects using partitioning // This will return users as they become available from concurrent response handlers IEnumerable <User> users; using (var builder = GraphRequestBuilder <User> .GetBuilder <UserCollectionResponseHandler>(requestManager, out users)) { foreach (var filter in GenericHelpers.GenerateFilterRangesForAlphaNumProperties("userPrincipalName")) { builder.Users.Request().Select("id, userPrincipalName").Top(999).Filter(filter).GetAsync().Wait(); } } IEnumerable <OperationResult <User> > results; using (var builder = GraphRequestBuilder <User> .GetBuilderForSingleOperation(requestManager, out results)) { foreach (var user in users.Where(u => !u.Id.Equals("383d113a-4967-41a4-9d98-3dc9c255db2b", StringComparison.OrdinalIgnoreCase))) { if (!user.UserPrincipalName.EndsWith("petersgraphtest.onmicrosoft.com", StringComparison.OrdinalIgnoreCase)) { throw new NotImplementedException("You do not want to execute this by accident!"); } builder.Users[user.Id].Request().ReturnNoContent().DeleteAsync().Wait(); } } return(results); }
/// <summary> /// Get all users in the organization, as fast as possible. /// </summary> /// <param name="requestManager">You can modify batchSize and concurrencyLevel in requestManager to change how requests are sent. /// Currently, it turns out that setting batchSize to 1 (essentially disabling batching), combined with a high concurrencyLevel (say 16), /// results in best performance for GET operations /// </param> /// <remarks> /// Graph permissions required: /// https://developer.microsoft.com/en-us/graph/docs/api-reference/v1.0/api/user_list /// </remarks> /// <returns></returns> public static IEnumerable <User> GetAllUsers(RequestManager requestManager) { // This collection will be populated with results as they become available. RequestManager executes requests in the background and asynchronously // adds them to this collection. The users are added to the collection immediately after they are returned and can be consumed via enumeration. // The underlying collection will block enumeration until the RequestManager is done processing all queued results IEnumerable <User> users; // The builder allows us to construct requests using standard Graph SDK syntax. However, instead of sending the requests it queues them with the RequestManager // for later execution in the background. The manager may batch multiple requests together for better performance. using (var builder = GraphRequestBuilder <User> .GetBuilder <UserCollectionResponseHandler>(requestManager, out users)) { // We use filters to partition the large user collection into individual "streams". Graph supports efficient, indexed, queries on userPrinicpal name, so we use that property. foreach (var filter in GenericHelpers.GenerateFilterRangesForAlphaNumProperties("userPrincipalName")) { // This is standard syntax of the Graph SDK. We "pretend" to send the request and wait for it, but in reality the GraphRequestBuilder class does not execute the request here. // Instead, it queues it up with the RequestManager for background execution. The reason we call GetAsync().Wait() is to force the code internal to the Graph SDK // to fully execute and build the final request. // The Top() expression is used to maximize the size of each result page. 999 is the maximum size for the User collection. // Other query parameters could be added here, too, for example we could use Select() to control which properties will be returned. builder.Users.Request().Top(999).Filter(filter).GetAsync().Wait(); // Note that with normal SDK usage the above call would only give us the first page of results. // However, this particular builder is specialized to handling collections. The internal response handler will receive the first page and automatically // queue up another request (internally) until all pages are retrieved. } } // Note that at this point we have not fully fetched all results. This collection can be enumerated and will block if more results are incoming. // E.g. you could say users.ToArray() to wait for all results, or use foreach and gradually process results as they become available. return(users); }
/// <summary> /// Gets all groups in the tenant, with all their members. /// Today, Graph's $expand parameter cannot be used to download all members in a group, it just gets the top N members. /// The approach taken here is to download group objects first (which do not contain members), and then get all members of each group. /// We can optimize this futher by parallelizing requests to get groups with requests to get members - this way we don't have to wait /// for all groups to download before we start fetching members. /// </summary> /// <returns></returns> public static IEnumerable <Group> GetAllGroupsWithMembers(RequestManager requestManager) { // Step 1: start downloading group objects using partitioning // This collection will be gradually populated with group objects, as they are fetched in the background by RequestManager IEnumerable <Group> groups; using (var builder = GraphRequestBuilder <Group> .GetBuilder <GroupCollectionResponseHandler>(requestManager, out groups)) { // We use filters to split the group collection into streams. Graph supports indexed queries on the mailNickname property so we can use that. // This is useful if the tenat contains many groups. foreach (var filter in GenericHelpers.GenerateFilterRangesForAlphaNumProperties("mailNickname")) { // This is standard syntax of the Graph SDK. We "pretend" to send the request and wait for it, but in reality the GraphRequestBuilder class does not execute the request here. // Instead, it queues it up with the RequestManager for background execution. The reason we call GetAsync().Wait() is to force the code internal to the Graph SDK // to fully execute and build the final request. // The Top() expression is used to maximize the size of each result page. 999 is the maximum size for the Group collection. // Other query parameters could be added here, too, for example we could use Select() to control which properties will be returned. builder.Groups.Request().Top(999).Filter(filter).GetAsync().Wait(); } } // Step 2: start downloading group members // At this point, groups are already trickling in from the background thread managed by Requestmanager. // As groups objects come in, we immediately create a request to download members // This collection will contain groups with fully populated Members property IEnumerable <Group> groupsWithMembers; // This builder supports creation of requests for collections embedded in the Group object, such as Members using (var builder = GroupNestedCollectionsRequestBuilder.GetBuilder(requestManager, out groupsWithMembers)) { // For each group object that has been downloaded we can now create a request. Note that the "groups" collection will block the thread // and wait if there are more results that have not been fetched yet. foreach (var group in groups) { // Again, we queue up more requests to execute in the background. // The request is only for the first page, but the specialized builder knows how to handle responses and will queue up more requests // until the full group membership list is fetched and added to the group object. builder.Members(group).Request().Top(999).GetAsync().Wait(); } } // Note that at this point we have not fully fetched all results. This collection can be enumerated and will block if more results are incoming. // E.g. you could say groupsWithMembers.ToArray() to wait for all results, or use foreach and gradually process results as they become available. // Any group objects that show up in this collection will already have a full list of members populated. return(groupsWithMembers); }
public static string GetDeviceReport(RequestManager requestManager) { IEnumerable <Device> devices; using (var builder = GraphRequestBuilder <Device> .GetBuilder <DeviceCollectionResponseHandler>(requestManager, out devices)) { builder.Devices.Request().Select("operatingSystem, isManaged, isCompliant").Top(999).GetAsync().Wait(); foreach (var filter in GenericHelpers.GenerateFilterRangesForAlphaNumProperties("displayName")) { builder.Devices.Request().Select("operatingSystem, isManaged, isCompliant").Top(999).Filter(filter).GetAsync().Wait(); } } var report = devices.Distinct(new GenericHelpers.EntityComparer()).Cast <Device>() .GroupBy(d => $"OS: {d.OperatingSystem ?? "null"}, isManaged: {d.IsManaged ?? false}, isCompliant: {d.IsCompliant ?? false}") .Select(g => new { DeviceCategory = g.Key, DeviceCount = g.Count() }); StringBuilder sb = new StringBuilder(); foreach (var line in report.OrderByDescending(l => l.DeviceCount)) { sb.AppendLine($"{line.DeviceCategory} - Count: {line.DeviceCount}"); } return(sb.ToString()); }