public static ChangeCollection GetChanges(ClientContext ctx, Web web, DateTime since) { ChangeQuery cq = new ChangeQuery(true, true); cq.ChangeTokenStart = new ChangeToken(); cq.ChangeTokenStart.StringValue = string.Format("1;2;{0};{1};-1", web.Id, since.ToUniversalTime().Ticks.ToString()); cq.ChangeTokenEnd = new ChangeToken(); cq.ChangeTokenEnd.StringValue = string.Format("1;2;{0};{1};-1", web.Id, DateTime.Now.AddDays(10).ToUniversalTime().Ticks.ToString()); cq.GroupMembershipAdd = true; cq.GroupMembershipDelete = true; cq.RoleAssignmentAdd = true; cq.RoleAssignmentDelete = true; cq.RoleDefinitionUpdate = true; cq.RoleDefinitionAdd = true; cq.RoleDefinitionDelete = true; cq.User = true; cq.SystemUpdate = true; cq.Group = true; cq.SecurityPolicy = true; cq.Web = true; var changes = web.GetChanges(cq); ctx.Load(changes); ctx.ExecuteQuery(); return(changes); }
public static ChangeQuery GetChangeQueryNewUpdate(string ListId, TraceWriter log) { // ATENTION: Change Token made for only the last minute and for new Items !! ChangeToken lastChangeToken = new ChangeToken(); ChangeQuery myChangeQuery = new ChangeQuery(false, false); try { lastChangeToken.StringValue = string.Format("1;3;{0};{1};-1", ListId, DateTime.Now.AddMinutes(-1).ToUniversalTime().Ticks.ToString()); ChangeToken newChangeToken = new ChangeToken(); newChangeToken.StringValue = string.Format("1;3;{0};{1};-1", ListId, DateTime.Now.ToUniversalTime().Ticks.ToString()); myChangeQuery.Item = true; // Get only Item changes myChangeQuery.Add = true; // Get only new and updated Items myChangeQuery.Update = true; // Get only new and updated Items myChangeQuery.ChangeTokenStart = lastChangeToken; myChangeQuery.ChangeTokenEnd = newChangeToken; } catch (Exception ex) { log.Info($"Error in GetChangeQueryNewUpdate: " + ex.ToString()); } return(myChangeQuery); }
public override void ExecuteCmdlet() { base.ExecuteCmdlet(); try { TenantContext.EnsureProperties(tssp => tssp.RootSiteUrl); var TenantUrl = TenantContext.RootSiteUrl.EnsureTrailingSlashLowered(); // Set the Auth Realm for the Tenant Web Context using (var siteWeb = this.ClientContext.Clone(SiteUrl)) { var cq = new ChangeQuery { FetchLimit = 10, Item = true }; var webAuthRealm = siteWeb.Web.GetChanges(cq); siteWeb.Load(webAuthRealm); siteWeb.ExecuteQuery(); } var userPrincipalName = string.Format("{0}|{1}", ClaimIdentifier, this.UserName); DiscoveryGroups.Add(userPrincipalName); try { SetSiteAdmin(SiteUrl, CurrentUserName, true); using (var siteContext = this.ClientContext.Clone(SiteUrl)) { Web _web = siteContext.Web; ProcessSiteCollectionSubWeb(_web, true); var siteProperties = GetSiteProperties(SiteUrl); } } catch (Exception e) { LogError(e, "Failed to processSiteCollection with url {0}", SiteUrl); } finally { //SetSiteAdmin(_siteUrl, CurrentUserName); } } catch (Exception e) { LogError(e, "Failed in SetEveryoneGroup cmdlet {0}", e.Message); } WriteObject(Model); }
static void Main(string[] args) { ClientContext ctx = ContextHelper.GetSPContext("https://folkuniversitetetsp2016.sharepoint.com/").Result; List list = ctx.Web.GetListByTitle("CustomList"); string currentChangeTokenstr = list.GetPropertyBagValueString("customlistchangetoken", null); ChangeToken currentChangeToken = null; if (currentChangeTokenstr != null) { currentChangeToken = new ChangeToken(); currentChangeToken.StringValue = currentChangeTokenstr; } ChangeQuery q = new ChangeQuery(false, false); q.Update = true; q.Add = true; q.Item = true; q.DeleteObject = true; q.ChangeTokenStart = currentChangeToken; q.ChangeTokenEnd = list.CurrentChangeToken; ChangeCollection changes = list.GetChanges(q); ctx.Load(changes); ctx.ExecuteQueryRetry(); foreach (var change in changes) { if (change.ChangeType == ChangeType.Add) { ChangeItem itemChange = change as ChangeItem; Console.WriteLine("Item was added id = " + itemChange.ItemId); } if (change.ChangeType == ChangeType.DeleteObject) { ChangeItem itemChange = change as ChangeItem; Console.WriteLine("Item was deleted id = " + itemChange.ItemId); } if (change.ChangeType == ChangeType.Update) { ChangeItem itemChange = change as ChangeItem; Console.WriteLine("Item was updated id = " + itemChange.ItemId); } } list.SetPropertyBagValue("customlistchangetoken", q.ChangeTokenEnd.StringValue); Console.WriteLine("done"); Console.ReadLine(); }
static void GetChanges(ClientContext SPClientContext, string ListId, TraceWriter log) { // Get the List Web spWeb = SPClientContext.Site.RootWeb; List changedList = spWeb.Lists.GetById(new Guid(ListId)); SPClientContext.Load(changedList); SPClientContext.ExecuteQuery(); // Create the ChangeToken and Change Query ChangeToken lastChangeToken = new ChangeToken(); lastChangeToken.StringValue = string.Format("1;3;{0};{1};-1", ListId, DateTime.Now.AddMinutes(-1).ToUniversalTime().Ticks.ToString()); ChangeToken newChangeToken = new ChangeToken(); newChangeToken.StringValue = string.Format("1;3;{0};{1};-1", ListId, DateTime.Now.ToUniversalTime().Ticks.ToString()); ChangeQuery myChangeQuery = new ChangeQuery(false, false); myChangeQuery.Item = true; // Get only Item changes myChangeQuery.Add = true; // Get only the new Items myChangeQuery.ChangeTokenStart = lastChangeToken; myChangeQuery.ChangeTokenEnd = newChangeToken; // Get all Changes var allChanges = changedList.GetChanges(myChangeQuery); SPClientContext.Load(allChanges); SPClientContext.ExecuteQuery(); foreach (Change oneChange in allChanges) { if (oneChange is ChangeItem) { // Get what is changed ListItem changedListItem = changedList.GetItemById((oneChange as ChangeItem).ItemId); SPClientContext.Load(changedListItem); SPClientContext.ExecuteQuery(); // Create a Text Analytics client ApiKeyServiceClientCredentials myCredentials = new ApiKeyServiceClientCredentials(serviceKey); TextAnalyticsClient myClient = new TextAnalyticsClient(myCredentials) { Endpoint = serviceEndpoint }; // Call the service SentimentResult myScore = myClient.Sentiment(changedListItem["Comments"].ToString(), "en"); // Insert the values back in the Item changedListItem["Sentiment"] = myScore.Score.ToString(); changedListItem.Update(); SPClientContext.ExecuteQuery(); } } }
static void GetChanges(ClientContext SPClientContext, string ListId, TraceWriter log) { Web spWeb = SPClientContext.Web; List myList = spWeb.Lists.GetByTitle(ConfigurationManager.AppSettings["whListName"]); SPClientContext.Load(myList); SPClientContext.ExecuteQuery(); ChangeQuery myChangeQuery = GetChangeQueryNew(ListId); var allChanges = myList.GetChanges(myChangeQuery); SPClientContext.Load(allChanges); SPClientContext.ExecuteQuery(); foreach (Change oneChange in allChanges) { if (oneChange is ChangeItem) { int myItemId = (oneChange as ChangeItem).ItemId; log.Info($"---- Changed ItemId : " + myItemId); ListItem myItem = myList.GetItemById(myItemId); Microsoft.SharePoint.Client.File myFile = myItem.File; ClientResult <System.IO.Stream> myFileStream = myFile.OpenBinaryStream(); SPClientContext.Load(myFile); SPClientContext.ExecuteQuery(); byte[] myFileBytes = ConvertStreamToByteArray(myFileStream); TextAnalyzeOCRResult myResult = GetAzureTextAnalyzeOCR(myFileBytes).Result; log.Info($"---- Text Analyze OCR Result : " + JsonConvert.SerializeObject(myResult)); myItem["Language"] = myResult.language; string myText = string.Empty; for (int oneLine = 0; oneLine < myResult.regions[0].lines.Count(); oneLine++) { for (int oneWord = 0; oneWord < myResult.regions[0].lines[oneLine].words.Count(); oneWord++) { myText += myResult.regions[0].lines[oneLine].words[oneWord].text + " "; } } myItem["OCRText"] = myText; myItem.Update(); SPClientContext.ExecuteQuery(); log.Info($"---- Text Analyze OCR added to SharePoint Item"); } } }
public async Task <ChangeSet> GetListChanges(Notification notification, Microsoft.SharePoint.Client.ChangeToken lastChangeToken) { var context = GetContext(notification.SiteUrl); var webGuid = new Guid(notification.WebId); var web = context.Site.OpenWebById(webGuid); var listGuid = new Guid(notification.Resource); var changeList = web.Lists.GetById(listGuid); context.Load(web); context.Load(changeList); await context.ExecuteQueryAsync(); var changeQuery = new ChangeQuery() { Item = true, Update = true, Add = true, DeleteObject = true }; if (lastChangeToken != null) { changeQuery.ChangeTokenStart = lastChangeToken; } else { changeQuery.ChangeTokenStart = new Microsoft.SharePoint.Client.ChangeToken { StringValue = $"1;3;{notification.Resource};{DateTime.Now.AddMinutes(-5).ToUniversalTime().Ticks};-1" }; _logger.LogInformation($"Get new change token: {changeQuery.ChangeTokenStart.StringValue}"); } var changes = changeList.GetChanges(changeQuery); context.Load(changes); await context.ExecuteQueryAsync(); var changeSet = new ChangeSet() { Web = web, List = changeList, Changes = GetUniqueChanges(changes) }; PrintChanges(changeSet); return(changeSet); }
public EventChange GetEvent(string listId, string web) { using (ClientContext client = new ClientContext(siteUrl)) { client.Credentials = new SharePointOnlineCredentials(userName, GetPassword(passWord)); var list = client.Web.Lists.GetById(new Guid(listId)); client.Load(list); client.ExecuteQuery(); ChangeQuery changes = new ChangeQuery(true, true); changes.Item = true; changes.RecursiveAll = true; changes.Add = true; changes.User = true; changes.Update = true; changes.List = true; changes.Field = true; if (list != null) { var listChages = list.GetChanges(changes); client.Load(listChages); client.ExecuteQuery(); var result = listChages.LastOrDefault(); if (result != null) { var itemChange = (ChangeItem)result; var type = result.ChangeType; if (type == ChangeType.Add) { var changeInfo = new SPChangeInfo() { ItemId = itemChange?.ItemId.ToString(), ItemTitle = string.Format("{0}_{1}", itemChange.ListId.ToString(), itemChange.ItemId.ToString()), ListName = itemChange.ListId.ToString(), TypeChange = type.ToString() }; return(new EventChange() { Id = changeInfo.ItemId, Lista = list.Title, site = siteUrl }); } } } } return(null); }
public static void Run([QueueTrigger("achangeismade", Connection = "AzureWebJobsStorage")] string myQueueItem, TraceWriter log) { log.Info($"C# Queue trigger function processed: {myQueueItem}"); NotificationModel notification = JsonConvert.DeserializeObject <NotificationModel>(myQueueItem); ClientContext ctx = ContextHelper.GetContext("https://folkis2018.sharepoint.com/sites/David"); List list = ctx.Web.GetListByTitle("SampleList"); var lastCheckedString = list.GetPropertyBagValueString("lastchecked", null); ChangeQuery query = new ChangeQuery(false, false); query.Item = true; query.Add = true; query.Update = true; //query.DeleteObject = true; dont care if (lastCheckedString != null) { ChangeToken token = new ChangeToken(); token.StringValue = lastCheckedString; query.ChangeTokenStart = token; } query.ChangeTokenEnd = list.CurrentChangeToken; ChangeCollection changes = list.GetChanges(query); ctx.Load(changes); ctx.ExecuteQuery(); foreach (ChangeItem change in changes) { log.Info(change.ItemId.ToString()); log.Info(change.ChangeType.ToString()); CloudStorageAccount storageAccount = CloudStorageAccount.Parse(Environment.GetEnvironmentVariable("AzureWebJobsStorage")); // Get queue... create if does not exist. CloudQueueClient queueClient = storageAccount.CreateCloudQueueClient(); CloudQueue queue = queueClient.GetQueueReference("processitem"); queue.CreateIfNotExists(); queue.AddMessage(new CloudQueueMessage(change.ItemId.ToString())); } list.SetPropertyBagValue("lastchecked", list.CurrentChangeToken.StringValue); }
public static ChangeQuery GetChangeQueryNew(string ListId) { ChangeToken lastChangeToken = new ChangeToken(); lastChangeToken.StringValue = string.Format("1;3;{0};{1};-1", ListId, DateTime.Now.AddMinutes(-1).ToUniversalTime().Ticks.ToString()); ChangeToken newChangeToken = new ChangeToken(); newChangeToken.StringValue = string.Format("1;3;{0};{1};-1", ListId, DateTime.Now.ToUniversalTime().Ticks.ToString()); ChangeQuery myChangeQuery = new ChangeQuery(false, false); myChangeQuery.Item = true; // Get only Item changes myChangeQuery.Add = true; // Get only the new Items myChangeQuery.ChangeTokenStart = lastChangeToken; myChangeQuery.ChangeTokenEnd = newChangeToken; return(myChangeQuery); }
public static ChangeCollection GetAllChanges(List ListWebHook, ChangeQuery ListChangeQuery, ClientContext SPClientContext, TraceWriter log) { ChangeCollection chcReturn = null; try { chcReturn = ListWebHook.GetChanges(ListChangeQuery); SPClientContext.Load(chcReturn); SPClientContext.ExecuteQuery(); } catch (Exception ex) { log.Info($"Error in GetAllChanges: " + ex.ToString()); } return(chcReturn); }
public async Task GetChanges_GeneratesCorrectRequest() { // ARRANGE var query = new ChangeQuery() { Add = true }; var mockListId = Guid.NewGuid(); var expectedUri = new Uri($"{mockWebUrl}/_api/web/lists('{mockListId.ToString()}')/GetChanges"); var expectedContent = "{\"query\":{\"Add\":true}}"; using (var response = new HttpResponseMessage()) using (var gsc = GraphServiceTestClient.Create(response)) { // ACT await gsc.GraphServiceClient .SharePointAPI(mockWebUrl) .Web .Lists[mockListId] .Request() .GetChangesAsync(query); var actualContent = gsc.HttpProvider.ContentAsString; // ASSERT gsc.HttpProvider.Verify( provider => provider.SendAsync( It.Is <HttpRequestMessage>(req => req.Method == HttpMethod.Post && req.RequestUri == expectedUri && req.Headers.Authorization != null ), It.IsAny <HttpCompletionOption>(), It.IsAny <CancellationToken>() ), Times.Exactly(1) ); Assert.Equal(Microsoft.Graph.CoreConstants.MimeTypeNames.Application.Json, gsc.HttpProvider.ContentHeaders.ContentType.MediaType); Assert.Equal(expectedContent, actualContent); } }
public async Task GetChanges_ReturnsCorrectDerivedClasses() { // ARRANGE var responseContent = ResourceManager.GetHttpResponseContent("GetChangesResponse.json"); var responseMessage = new HttpResponseMessage() { StatusCode = HttpStatusCode.OK, Content = new StringContent(responseContent), }; var query = new ChangeQuery() { Add = true }; var mockAuthProvider = new MockAuthenticationProvider(); var mockHttpProvider = new MockHttpProvider(responseMessage, new Serializer()); var graphServiceClient = new GraphServiceClient(mockAuthProvider.Object, mockHttpProvider.Object); // ACT var response = await graphServiceClient .SharePointAPI(mockWebUrl) .Web .Request() .GetChangesAsync(query); var actual = response.CurrentPage; responseMessage.Dispose(); // ASSERT Assert.Equal(5, actual.Count); Assert.IsType <ChangeSite>(actual[0]); Assert.IsType <ChangeUser>(actual[1]); Assert.IsType <ChangeItem>(actual[2]); Assert.IsType <ChangeWeb>(actual[3]); Assert.IsType <ChangeList>(actual[4]); }
static void GetChanges(ClientContext SPClientContext, string ListId, TraceWriter log) { // Get the List where the WebHook is working List myListWebHook = WHHelpRoutines.GetListWebHook(SPClientContext, log); // Get the Change Query ChangeQuery myChangeQuery = WHHelpRoutines.GetChangeQueryNew(ListId, log); // Only new items and for the last one minute // Get all the Changes ChangeCollection allChanges = WHHelpRoutines.GetAllChanges(myListWebHook, myChangeQuery, SPClientContext, log); foreach (Change oneChange in allChanges) { if (oneChange is ChangeItem) { // Get what is changed ListItem itemChanged = WHHelpRoutines.GetItemChanged(myListWebHook, oneChange, SPClientContext, log); log.Info($"itemChangedID - " + itemChanged.Id.ToString()); // Do something with the Item Changed DoSomething(SPClientContext, itemChanged, log); } } }
public static async Task Run() { ///////////////////////////// // // Programmer configuration // ///////////////////////////// var sharepointDomain = "demo.sharepoint.com"; var siteCollectionPath = "/sites/GraphCommunityDemo"; //////////////////////////////// // // Azure AD Configuration // //////////////////////////////// AzureAdOptions azureAdOptions = new AzureAdOptions(); var settingsFilename = System.IO.Path.Combine(System.IO.Directory.GetCurrentDirectory(), "appsettings.json"); var builder = new ConfigurationBuilder() .AddJsonFile(settingsFilename, optional: false) .AddUserSecrets <Program>(); var config = builder.Build(); config.Bind("AzureAd", azureAdOptions); ///////////////////////////////////// // // Client Application Configuration // ///////////////////////////////////// var options = new PublicClientApplicationOptions() { AadAuthorityAudience = AadAuthorityAudience.AzureAdMyOrg, AzureCloudInstance = AzureCloudInstance.AzurePublic, ClientId = azureAdOptions.ClientId, TenantId = azureAdOptions.TenantId, RedirectUri = "http://localhost" }; // Create the public client application (desktop app), with a default redirect URI var pca = PublicClientApplicationBuilder .CreateWithApplicationOptions(options) .Build(); // Enable a simple token cache serialiation so that the user does not need to // re-sign-in each time the application is run TokenCacheHelper.EnableSerialization(pca.UserTokenCache); /////////////////////////////////////////////// // // Auth Provider - Device Code in this sample // /////////////////////////////////////////////// // Create an authentication provider to attach the token to requests var scopes = new string[] { $"https://{sharepointDomain}/AllSites.FullControl" }; IAuthenticationProvider ap = new DeviceCodeProvider(pca, scopes); //////////////////////////////////////////////////////////// // // Graph Client with Logger and SharePoint service handler // //////////////////////////////////////////////////////////// var logger = new StringBuilderHttpMessageLogger(); /* * Could also use the Console if preferred... * * var logger = new ConsoleHttpMessageLogger(); */ // Configure our client CommunityGraphClientOptions clientOptions = new CommunityGraphClientOptions() { UserAgent = "ChangeLogSample" }; var graphServiceClient = CommunityGraphClientFactory.Create(clientOptions, logger, ap); /////////////////////////////////////// // // Setup is complete, run the sample // /////////////////////////////////////// var WebUrl = $"https://{sharepointDomain}{siteCollectionPath}"; var web = await graphServiceClient .SharePointAPI(WebUrl) .Web .Request() .GetAsync(); var changeToken = web.CurrentChangeToken; Console.WriteLine($"current change token: {changeToken.StringValue}"); Console.WriteLine($"Make an update to the site {WebUrl}"); Console.WriteLine("Press enter to continue"); Console.ReadLine(); var qry = new ChangeQuery(true, true); qry.ChangeTokenStart = changeToken; var changes = await graphServiceClient .SharePointAPI(WebUrl) .Web .Request() .GetChangesAsync(qry); Console.WriteLine(changes.Count); foreach (var item in changes) { Console.WriteLine($"{item.ChangeType}"); } Console.WriteLine("Press enter to show log"); Console.ReadLine(); Console.WriteLine(); var log = logger.GetLog(); Console.WriteLine(log); }
static void Main(string[] args) { //install-Package AppForSharePointWebToolkit //Adds SharePointContext.cs, TokenHelper.cs and a whole bunch of references try { var config = (NameValueCollection)ConfigurationManager.GetSection("Sites"); foreach (var key in config.Keys) { Uri siteUri = new Uri(config.GetValues(key as string)[0]); //get the realm for the URL string realm = TokenHelper.GetRealmFromTargetUrl(siteUri); //Get the Access Token for the URL - requires this app to be registered with the //tenant (using _layouts/AppRegNew.aspx to create App Principal) and then //using _layouts/AppInv.aspx to request permissions for the App Principal string accessToken = TokenHelper.GetAppOnlyAccessToken( TokenHelper.SharePointPrincipal, siteUri.Authority, realm).AccessToken; //Once we have an Access Token we can ceate a ClientContext using it using (var clientContext = TokenHelper.GetClientContextWithAccessToken( siteUri.ToString(), accessToken)) { var site = clientContext.Site; clientContext.Load(site); ChangeQuery siteCQ = new ChangeQuery(false, false); //Object type siteCQ.Item = true; //Change Type ??? siteCQ.Add = true; siteCQ.DeleteObject = true; siteCQ.Update = true; var siteChanges = site.GetChanges(siteCQ); //get the site collection changes clientContext.Load(siteChanges); clientContext.ExecuteQuery(); foreach (Change change in siteChanges) { if (change is ChangeItem) { ChangeItem ci = change as ChangeItem; ListItem item = null; try { Web web = site.OpenWebById(ci.WebId); //get the relevant web clientContext.Load(web); List list = web.Lists.GetById(ci.ListId); //get the relevant list clientContext.Load(list); item = list.GetItemById(ci.ItemId); //get the item clientContext.Load(item, i => i["Editor"], i => i["FileRef"]); //load the right bits clientContext.ExecuteQuery(); FieldUserValue fuv = (FieldUserValue)item["Editor"]; Console.WriteLine("{0},{1},{2},{3}", item["FileRef"], ci.ChangeType.ToString(), fuv.LookupValue, change.Time); } catch (Exception e) { //Just because the item is in the change log doesn't mean it exists - //it may have been added and then deleted or just deleted if (e.Message.StartsWith("Item does not exist.") || e.Message.StartsWith("File Not Found") || e.Message.StartsWith("List does not exist")) { Console.WriteLine("{0},{1},{2},{3}", e.Message.Substring(0, e.Message.IndexOf(".")), ci.ChangeType.ToString(), "Unknown", change.Time); } else { Console.WriteLine("{0},{1},{2}", e.InnerException, e.Message, e.Source); } } } } } } }//try catch (Exception ex) { Console.WriteLine("Exception occured: {0}", ex.Message); } finally { Console.WriteLine("Press any key to exit."); Console.ReadLine(); } } //main
/// <summary> /// Processes a received notification. This typically is triggered via an Azure Web Job that reads the Azure storage queue /// </summary> /// <param name="notification">Notification to process</param> public void ProcessNotification(NotificationModel notification) { ClientContext cc = null; try { string url = null; Guid listId = new Guid(notification.Resource); Guid id = new Guid(notification.SubscriptionId); // grab last change token from database if possible using (SharePointWebHooks dbContext = new SharePointWebHooks("pnpwebhooksdemoEntities")) { var listWebHookRow = dbContext.ListWebHooks.Find(id); #region Setup an app-only client context AuthenticationManager am = new AuthenticationManager(); string startingUrl = WebConfigurationManager.AppSettings["TenantName"]; url = String.Format("https://{0}{1}", startingUrl, notification.SiteUrl); string realm = TokenHelper.GetRealmFromTargetUrl(new Uri(url)); if (listWebHookRow != null) { startingUrl = listWebHookRow.StartingUrl; url = string.Format(startingUrl + notification.SiteUrl); } string clientId = WebConfigurationManager.AppSettings["ClientId"]; string clientSecret = WebConfigurationManager.AppSettings["ClientSecret"]; if (new Uri(url).DnsSafeHost.Contains("spoppe.com")) { cc = am.GetAppOnlyAuthenticatedContext(url, realm, clientId, clientSecret, acsHostUrl: "windows-ppe.net", globalEndPointPrefix: "login"); } else { cc = am.GetAppOnlyAuthenticatedContext(url, clientId, clientSecret); } cc.ExecutingWebRequest += Cc_ExecutingWebRequest; #endregion #region Grab the list for which the web hook was triggered ListCollection lists = cc.Web.Lists; var changeList = cc.Web.Lists.GetById(listId); var listItems = changeList.GetItems(new Microsoft.SharePoint.Client.CamlQuery()); cc.Load(changeList, lst => lst.Title, lst => lst.DefaultViewUrl); cc.Load(listItems, i => i.Include(item => item.DisplayName, item => item.Id)); cc.ExecuteQuery(); if (changeList == null) { // list has been deleted inbetween the event being fired and the event being processed return; } #endregion #region Grab the list used to write the web hook history // Ensure reference to the history list, create when not available List historyList = cc.Web.GetListByTitle("WebHookHistory"); if (historyList == null) { historyList = cc.Web.CreateList(ListTemplateType.GenericList, "WebHookHistory", false); } #endregion #region Grab the list changes and do something with them // grab the changes to the provided list using the GetChanges method // on the list. Only request Item changes as that's what's supported via // the list web hooks ChangeQuery changeQuery = new ChangeQuery(false, true); changeQuery.Item = true; changeQuery.FetchLimit = 1000; // Max value is 2000, default = 1000 ChangeToken lastChangeToken = null; if (listWebHookRow != null) { lastChangeToken = new ChangeToken(); lastChangeToken.StringValue = listWebHookRow.LastChangeToken; } // Start pulling down the changes bool allChangesRead = false; do { // should not occur anymore now that we record the starting change token at // subscription creation time, but it's a safety net if (lastChangeToken == null) { lastChangeToken = new ChangeToken(); // See https://blogs.technet.microsoft.com/stefan_gossner/2009/12/04/content-deployment-the-complete-guide-part-7-change-token-basics/ lastChangeToken.StringValue = string.Format("1;3;{0};{1};-1", notification.Resource, DateTime.Now.AddMinutes(-5).ToUniversalTime().Ticks.ToString()); } // Assign the change token to the query...this determines from what point in // time we'll receive changes changeQuery.ChangeTokenStart = lastChangeToken; // Execute the change query var changes = changeList.GetChanges(changeQuery); cc.Load(changes); cc.ExecuteQueryRetry(); if (changes.Count > 0) { foreach (Change change in changes) { lastChangeToken = change.ChangeToken; if (change is ChangeItem) { // do "work" with the found change DoWork(cc, changeList, historyList, change); } } // We potentially can have a lot of changes so be prepared to repeat the // change query in batches of 'FetchLimit' untill we've received all changes if (changes.Count < changeQuery.FetchLimit) { allChangesRead = true; } } else { allChangesRead = true; } // Are we done? } while (allChangesRead == false); // Persist the last used changetoken as we'll start from that one // when the next event hits our service if (listWebHookRow != null) { // Only persist when there's a change in the change token if (!listWebHookRow.LastChangeToken.Equals(lastChangeToken.StringValue, StringComparison.InvariantCultureIgnoreCase)) { listWebHookRow.LastChangeToken = lastChangeToken.StringValue; dbContext.SaveChanges(); } } else { // should not occur anymore now that we record the starting change token at // subscription creation time, but it's a safety net dbContext.ListWebHooks.Add(new ListWebHook() { Id = id, ListId = listId, LastChangeToken = lastChangeToken.StringValue, }); dbContext.SaveChanges(); } } #endregion #region "Update" the web hook expiration date when needed // Optionally add logic to "update" the expirationdatetime of the web hook // If the web hook is about to expire within the coming 5 days then prolong it if (notification.ExpirationDateTime.AddDays(-5) < DateTime.Now) { WebHookManager webHookManager = new WebHookManager(); Task <bool> updateResult = Task.WhenAny( webHookManager.UpdateListWebHookAsync( url, listId.ToString(), notification.SubscriptionId, WebConfigurationManager.AppSettings["WebHookEndPoint"], DateTime.Now.AddMonths(3), this.accessToken) ).Result; if (updateResult.Result == false) { throw new Exception(String.Format("The expiration date of web hook {0} with endpoint {1} could not be updated", notification.SubscriptionId, WebConfigurationManager.AppSettings["WebHookEndPoint"])); } } #endregion } catch (Exception ex) { // Log error Console.WriteLine(ex.ToString()); } finally { if (cc != null) { cc.Dispose(); } } }
public ChangeCollection GetChanges(ChangeQuery query) => _list.GetChanges(query);
private static void DoWork() { Console.WriteLine(); Console.WriteLine("Url: " + url); Console.WriteLine("User Name: " + userName); Console.WriteLine("List Name: " + listName); Console.WriteLine(); try { Console.WriteLine(string.Format("Connecting to {0}", url)); Console.WriteLine(); ClientContext cc = new ClientContext(url); cc.AuthenticationMode = ClientAuthenticationMode.Default; cc.Credentials = new SharePointOnlineCredentials(userName, password); ListCollection lists = cc.Web.Lists; IEnumerable<List> results = cc.LoadQuery<List>(lists.Where(lst => lst.Title == listName)); cc.ExecuteQuery(); List list = results.FirstOrDefault(); if (list == null) { Console.WriteLine("A list named \"{0}\" does not exist. Press any key to exit...", listName); Console.ReadKey(); return; } nextRunTime = DateTime.Now; ChangeQuery cq = new ChangeQuery(false, false); cq.Item = true; cq.DeleteObject = true; cq.Add = true; cq.Update = true; // Initially set the ChangeTokenStart to 2 days ago so we don't go off and grab every item from the list since the day it was created. // The format of the string is semicolon delimited with the following pieces of information in order // Version number // A number indicating the change scope: 0 – Content Database, 1 – site collection, 2 – site, 3 – list. // GUID representing the scope ID of the change token // Time (in UTC) when the change occurred // Number of the change relative to other changes cq.ChangeTokenStart = new ChangeToken(); cq.ChangeTokenStart.StringValue = string.Format("1;3;{0};{1};-1", list.Id.ToString(), DateTime.Now.AddDays(-2).ToUniversalTime().Ticks.ToString()); Console.ForegroundColor = ConsoleColor.Red; Console.WriteLine(string.Format("Ctrl+c to terminate. Press \"r\" key to force run without waiting {0} seconds.", WaitSeconds)); Console.WriteLine(); Console.ResetColor(); do { do { if (Console.KeyAvailable && Console.ReadKey(true).KeyChar == 'r') { break; } } while (nextRunTime > DateTime.Now); Console.WriteLine(string.Format("Looking for items modified after {0} UTC", GetDateStringFromChangeToken(cq.ChangeTokenStart))); ChangeCollection coll = list.GetChanges(cq); cc.Load(coll); cc.ExecuteQuery(); DisplayChanges(coll, cq.ChangeTokenStart); // if we find any changes to the list take the last change and use the ChangeToken as the start time for our next query. // The ChangeToken will contain the Date/time of the last change to any item in the list. cq.ChangeTokenStart = coll.Count > 0 ? coll.Last().ChangeToken : cq.ChangeTokenStart; nextRunTime = DateTime.Now.AddSeconds(WaitSeconds); } while (true); } catch (Exception e) { Console.WriteLine(e.Message); Console.WriteLine("Press any key to exit..."); Console.ReadKey(); } }
public static void Run([QueueTrigger("eventhappened", Connection = "AzureWebJobsStorage")] string myQueueItem, TraceWriter log) { log.Info($"C# Queue trigger function processed: {myQueueItem}"); ConfigureBindingRedirects(); var notification = JsonConvert.DeserializeObject <Common.Models.NotificationModel>(myQueueItem); ClientContext ctx = Common.Helpers.ContextHelper.GetSPContext("https://folkuniversitetetsp2016.sharepoint.com" + notification.SiteUrl).Result; // notification.Resource is our list id List list = ctx.Web.Lists.GetById(new Guid(notification.Resource)); string currentChangeTokenstr = list.GetPropertyBagValueString("customlistchangetoken", null); ChangeToken currentChangeToken = null; if (currentChangeTokenstr != null) { currentChangeToken = new ChangeToken(); currentChangeToken.StringValue = currentChangeTokenstr; } ctx.Load(list, l => l.CurrentChangeToken); ctx.ExecuteQuery(); ChangeQuery q = new ChangeQuery(false, false); q.Update = true; q.Add = true; q.Item = true; q.DeleteObject = true; q.ChangeTokenStart = currentChangeToken; q.ChangeTokenEnd = list.CurrentChangeToken; ChangeCollection changes = list.GetChanges(q); ctx.Load(changes); ctx.ExecuteQueryRetry(); //create queue if not exists CloudStorageAccount storageAccount = CloudStorageAccount.Parse(ConfigurationManager.AppSettings["AzureWebJobsStorage"]); // Get queue... create if does not exist. CloudQueueClient queueClient = storageAccount.CreateCloudQueueClient(); CloudQueue queue = queueClient.GetQueueReference("changehappened"); queue.CreateIfNotExists(); foreach (var change in changes) { if (change.ChangeType == ChangeType.Add) { ChangeItem itemChange = change as ChangeItem; ChangeInfo changeInfo = new ChangeInfo() { SiteUrl = notification.SiteUrl, ListId = notification.Resource, ItemId = itemChange.ItemId }; queue.AddMessage(new CloudQueueMessage(JsonConvert.SerializeObject(changeInfo))); log.Info("Item was added id = " + itemChange.ItemId); } if (change.ChangeType == ChangeType.DeleteObject) { ChangeItem itemChange = change as ChangeItem; ChangeInfo changeInfo = new ChangeInfo() { SiteUrl = notification.SiteUrl, ListId = notification.Resource, ItemId = itemChange.ItemId }; queue.AddMessage(new CloudQueueMessage(JsonConvert.SerializeObject(changeInfo))); log.Info("Item was deleted id = " + itemChange.ItemId); } if (change.ChangeType == ChangeType.Update) { ChangeItem itemChange = change as ChangeItem; ChangeInfo changeInfo = new ChangeInfo() { SiteUrl = notification.SiteUrl, ListId = notification.Resource, ItemId = itemChange.ItemId }; queue.AddMessage(new CloudQueueMessage(JsonConvert.SerializeObject(changeInfo))); log.Info("Item was updated id = " + itemChange.ItemId); } } list.SetPropertyBagValue("customlistchangetoken", q.ChangeTokenEnd.StringValue); }
internal List <SharePointItem> GetChangedFiles(Metadata.MetadataStore metadataStore, Action <int, string> progressHandler) { if (string.IsNullOrEmpty(metadataStore.ChangeToken)) { Logger.LogDebug("No ChangeToken Found, downloading list of all files"); progressHandler(0, null); var listOfAllFiles = DownloadFileList(); using (var context = GetClientContext()) { var list = context.Web.Lists.GetByTitle(_configuration.DocumentLibrary); context.Load(list, p => p.DefaultViewUrl); var changeQuery = new ChangeQuery(); changeQuery.Item = true; changeQuery.Add = true; changeQuery.DeleteObject = true; changeQuery.Move = true; changeQuery.Rename = true; changeQuery.Update = true; var changes = list.GetChanges(changeQuery); context.Load(changes); context.ExecuteQuery(); if (changes.Count > 0) { var newChangeToken = changes.OrderBy(p => p.ChangeToken.StringValue).Last().ChangeToken.StringValue; Logger.LogDebug("Set ChangeToken from empty to new {0}", newChangeToken); metadataStore.ChangeToken = newChangeToken; } else { Logger.LogDebug("No initial changes found, so no ChangeToken set."); } } progressHandler(100, null); return(listOfAllFiles); } var fileList = new List <SharePointItem>(); var tempFileList = new List <SharePointItem>(); using (var context = GetClientContext()) { var list = context.Web.Lists.GetByTitle(_configuration.DocumentLibrary); context.Load(list, p => p.DefaultViewUrl); var changeQuery = new ChangeQuery(); changeQuery.Item = true; changeQuery.Add = true; changeQuery.DeleteObject = true; changeQuery.Move = true; changeQuery.Rename = true; changeQuery.Update = true; if (!string.IsNullOrEmpty(metadataStore.ChangeToken)) { changeQuery.ChangeTokenStart = new ChangeToken() { StringValue = metadataStore.ChangeToken } } ; var changes = list.GetChanges(changeQuery); context.Load(changes); try { context.ExecuteQuery(); } catch (ServerException ex) { if (ex.ServerErrorTypeName == "Microsoft.SharePoint.SPInvalidChangeTokenException") { changeQuery.ChangeTokenStart = null; changes = list.GetChanges(changeQuery); context.Load(changes); context.ExecuteQuery(); } else { throw; } } var spListItemCache = new Dictionary <int, ListItem>(); var changeCount = changes.Count; var counter = 0; progressHandler(1, null); Logger.LogDebug("Found {0} remote changes {1}", changeCount, string.IsNullOrEmpty(metadataStore.ChangeToken) ? string.Empty : "with change token " + metadataStore.ChangeToken); foreach (var change in changes) { var changedItem = (ChangeItem)change; Logger.LogDebug("ChangeItem: {0} ChangeType: {1}", changedItem.ItemId, changedItem.ChangeType); if (changedItem.ChangeType == ChangeType.DeleteObject) { tempFileList.Add(new SharePointItem(changedItem.ItemId, ItemType.File, changedItem.ChangeType, "Unknown", null, changedItem.Time, ".")); } else { try { if (!spListItemCache.ContainsKey(changedItem.ItemId)) { var tempItem = list.GetItemById(changedItem.ItemId); context.Load(tempItem, p => p["Modified"], p => p.File, p => p.File.ServerRelativeUrl, p => p.Folder, p => p.Folder.ServerRelativeUrl); context.ExecuteQuery(); spListItemCache.Add(changedItem.ItemId, tempItem); } var changedListItem = spListItemCache[changedItem.ItemId]; // File if (changedListItem.File.IsPropertyAvailable("ServerRelativeUrl")) { var folder = changedListItem.File.ServerRelativeUrl; folder = folder.Replace(changedListItem.File.Name, string.Empty); folder = folder.Replace(list.DefaultViewUrl.Substring(0, list.DefaultViewUrl.IndexOf("/Forms/")), string.Empty); folder = folder.Replace("/", "\\"); folder = folder.EndsWith("\\") ? folder : folder + "\\"; tempFileList.Add(new SharePointItem(changedItem.ItemId, ItemType.File, changedItem.ChangeType, changedListItem.File.Name, changedListItem.File.ETag, changedListItem.File.TimeLastModified, "." + folder + changedListItem.File.Name)); } // Folder else if (changedListItem.Folder.IsPropertyAvailable("ServerRelativeUrl")) { var folder = changedListItem.Folder.ServerRelativeUrl; folder = folder.Replace(changedListItem.Folder.Name, string.Empty); folder = folder.Replace(list.DefaultViewUrl.Substring(0, list.DefaultViewUrl.IndexOf("/Forms/")), string.Empty); folder = folder.Replace("/", "\\"); folder = folder.EndsWith("\\") ? folder : folder + "\\"; tempFileList.Add(new SharePointItem(changedItem.ItemId, ItemType.Folder, changedItem.ChangeType, changedListItem.Folder.Name, null, (DateTime)changedListItem["Modified"], "." + folder + changedListItem.Folder.Name)); } } catch (ServerException ex) { Logger.LogDebug("Change for item id {0} was not added. Message: {1} ServerErrorTypeName: {2}", changedItem.ItemId, ex.Message, ex.ServerErrorTypeName); } } counter++; var percent = (int)(((double)counter / (double)changeCount) * 100); progressHandler(percent, tempFileList.LastOrDefault()?.Name); } if (changes.Count > 0) { var newChangeToken = changes.OrderBy(p => p.ChangeToken.StringValue).Last().ChangeToken.StringValue; if (string.IsNullOrEmpty(metadataStore.ChangeToken)) { Logger.LogDebug("Set ChangeToken from empty to new {0}", newChangeToken); metadataStore.ChangeToken = newChangeToken; } else if (metadataStore.ChangeToken.CompareTo(newChangeToken) < 0) { Logger.LogDebug("Set ChangeToken from {0} to new {1}", metadataStore.ChangeToken, newChangeToken); metadataStore.ChangeToken = newChangeToken; } } foreach (var ti in tempFileList.GroupBy(p => p.Id)) { Logger.LogDebug("Found {0} changes for item id {1}", ti.Count(), ti.Key); SharePointItem spitem = null; if (ti.All(p => p.ChangeType == ChangeType.Update)) { spitem = ti.FirstOrDefault(p => p.ChangeType == ChangeType.Update); } else if (ti.All(p => p.ChangeType == ChangeType.Add || p.ChangeType == ChangeType.Update)) { spitem = ti.FirstOrDefault(p => p.ChangeType == ChangeType.Add); } else if (ti.Any(p => p.ChangeType == ChangeType.DeleteObject)) { spitem = ti.FirstOrDefault(p => p.ChangeType == ChangeType.DeleteObject); } else if (ti.Any(p => p.ChangeType == ChangeType.Rename)) { spitem = ti.FirstOrDefault(p => p.ChangeType == ChangeType.Rename); } fileList.Add(spitem); } progressHandler(100, null); } return(fileList); }
public static void Run([QueueTrigger("eventhappened", Connection = "AzureWebJobsStorage")] string myQueueItem, TraceWriter log) { log.Info($"C# Queue trigger function processed: {myQueueItem}"); ConfigureBindingRedirects(); var notification = JsonConvert.DeserializeObject <NotificationModel>(myQueueItem); ClientContext ctx = Common.Helpers.ContextHelper.GetSPContext("https://folkuniversitetetsp2016.sharepoint.com" + notification.SiteUrl).Result; // notification.Resource is our list id List list = ctx.Web.Lists.GetById(new Guid(notification.Resource)); string currentChangeTokenstr = list.GetPropertyBagValueString("customlistchangetoken", null); ChangeToken currentChangeToken = null; if (currentChangeTokenstr != null) { currentChangeToken = new ChangeToken(); currentChangeToken.StringValue = currentChangeTokenstr; } ctx.Load(list, l => l.CurrentChangeToken); ctx.ExecuteQuery(); ChangeQuery q = new ChangeQuery(false, false); q.Update = true; q.Add = true; q.Item = true; q.DeleteObject = true; q.ChangeTokenStart = currentChangeToken; q.ChangeTokenEnd = list.CurrentChangeToken; ChangeCollection changes = list.GetChanges(q); ctx.Load(changes); ctx.ExecuteQueryRetry(); foreach (var change in changes) { if (change.ChangeType == ChangeType.Add) { ChangeItem itemChange = change as ChangeItem; log.Info("Item was added id = " + itemChange.ItemId); } if (change.ChangeType == ChangeType.DeleteObject) { ChangeItem itemChange = change as ChangeItem; log.Info("Item was deleted id = " + itemChange.ItemId); } if (change.ChangeType == ChangeType.Update) { ChangeItem itemChange = change as ChangeItem; log.Info("Item was updated id = " + itemChange.ItemId); } } list.SetPropertyBagValue("customlistchangetoken", q.ChangeTokenEnd.StringValue); }
private static void DoWork() { Console.WriteLine(); Console.WriteLine("Url: " + url); Console.WriteLine("User Name: " + userName); Console.WriteLine("List Name: " + listName); Console.WriteLine(); try { Console.WriteLine(string.Format("Connecting to {0}", url)); Console.WriteLine(); ClientContext cc = new ClientContext(url); cc.AuthenticationMode = ClientAuthenticationMode.Default; cc.Credentials = new SharePointOnlineCredentials(userName, password); ListCollection lists = cc.Web.Lists; IEnumerable <List> results = cc.LoadQuery <List>(lists.Where(lst => lst.Title == listName)); cc.ExecuteQuery(); List list = results.FirstOrDefault(); if (list == null) { Console.WriteLine("A list named \"{0}\" does not exist. Press any key to exit...", listName); Console.ReadKey(); return; } nextRunTime = DateTime.Now; ChangeQuery cq = new ChangeQuery(false, false); cq.Item = true; cq.DeleteObject = true; cq.Add = true; cq.Update = true; // Initially set the ChangeTokenStart to 2 days ago so we don't go off and grab every item from the list since the day it was created. // The format of the string is semicolon delimited with the following pieces of information in order // Version number // A number indicating the change scope: 0 – Content Database, 1 – site collection, 2 – site, 3 – list. // GUID representing the scope ID of the change token // Time (in UTC) when the change occurred // Number of the change relative to other changes cq.ChangeTokenStart = new ChangeToken(); cq.ChangeTokenStart.StringValue = string.Format("1;3;{0};{1};-1", list.Id.ToString(), DateTime.Now.AddDays(-2).ToUniversalTime().Ticks.ToString()); Console.ForegroundColor = ConsoleColor.Red; Console.WriteLine(string.Format("Ctrl+c to terminate. Press \"r\" key to force run without waiting {0} seconds.", WaitSeconds)); Console.WriteLine(); Console.ResetColor(); do { do { if (Console.KeyAvailable && Console.ReadKey(true).KeyChar == 'r') { break; } }while (nextRunTime > DateTime.Now); Console.WriteLine(string.Format("Looking for items modified after {0} UTC", GetDateStringFromChangeToken(cq.ChangeTokenStart))); ChangeCollection coll = list.GetChanges(cq); cc.Load(coll); cc.ExecuteQuery(); DisplayChanges(coll, cq.ChangeTokenStart); // if we find any changes to the list take the last change and use the ChangeToken as the start time for our next query. // The ChangeToken will contain the Date/time of the last change to any item in the list. cq.ChangeTokenStart = coll.Count > 0 ? coll.Last().ChangeToken : cq.ChangeTokenStart; nextRunTime = DateTime.Now.AddSeconds(WaitSeconds); } while (true); } catch (Exception e) { Console.WriteLine(e.Message); Console.WriteLine("Press any key to exit..."); Console.ReadKey(); } }
/// <summary> /// Processes a received notification. This typically is triggered via an Azure Web Job that reads the Azure storage queue /// </summary> /// <param name="notification">Notification to process</param> public async Task ProcessNotification(NotificationModel notification, TraceWriter log) { ClientContext cc = null; ClientContext acc = null; string notificationStep = "1"; try { #region Setup an app-only client context AuthenticationManager am = new AuthenticationManager(); notificationStep = "1a"; string url = String.Format("https://{0}{1}", System.Environment.GetEnvironmentVariable("TenantName"), notification.SiteUrl); notificationStep = "1b" + url; string realm = TokenHelper.GetRealmFromTargetUrl(new Uri(url)); string approvalUrl = System.Environment.GetEnvironmentVariable("ApprovalSiteUrl"); log.Info("ApprovalSiteUrl: " + approvalUrl); notificationStep = "1b" + url; string approvalRealm = TokenHelper.GetRealmFromTargetUrl(new Uri(approvalUrl)); notificationStep = "1c"; string clientId = System.Environment.GetEnvironmentVariable("ClientId"); notificationStep = "1d"; string clientSecret = System.Environment.GetEnvironmentVariable("ClientSecret"); notificationStep = "2"; if (new Uri(url).DnsSafeHost.Contains("spoppe.com")) { log.Info("Government cloud login (I think)"); cc = am.GetAppOnlyAuthenticatedContext(url, realm, clientId, clientSecret, acsHostUrl: "windows-ppe.net", globalEndPointPrefix: "login"); acc = am.GetAppOnlyAuthenticatedContext(approvalUrl, approvalRealm, clientId, clientSecret, acsHostUrl: "windows-ppe.net", globalEndPointPrefix: "login"); log.Info("Government cloud login (I think) token received"); } else { log.Info("App only login"); notificationStep = "2"; cc = am.GetAppOnlyAuthenticatedContext(url, clientId, clientSecret); acc = am.GetAppOnlyAuthenticatedContext(approvalUrl, clientId, clientSecret); log.Info("App only login token received"); } notificationStep = "3"; cc.ExecutingWebRequest += Cc_ExecutingWebRequest; #endregion notificationStep = "3a"; #region Grab the list for which the web hook was triggered ListCollection lists = cc.Web.Lists; notificationStep = "3b"; Guid listId = new Guid(notification.Resource); notificationStep = "3c"; log.Info("Loaded source list"); IEnumerable <List> results = cc.LoadQuery <List>(lists.Where(lst => lst.Id == listId)); notificationStep = $"3d-{listId.ToString()},{notification.Resource.ToString()}, {notification.SiteUrl}"; cc.ExecuteQueryRetry(); log.Info("Loaded source list loaded"); notificationStep = "3e"; List notificationSourceList = results.FirstOrDefault(); if (notificationSourceList == null) { // list has been deleted inbetween the event being fired and the event being processed return; } notificationStep = "4"; #endregion #region Grab the list used to write the web hook history // Ensure reference to the history list, create when not available List approvalsList = acc.Web.GetListByTitle("Approvals"); if (approvalsList == null) { log.Info("Creating approvals list as not in place"); approvalsList = acc.Web.CreateList(ListTemplateType.GenericList, "Approvals", false); this.AddTextField(approvalsList, "ClientState", "ClientState", cc); this.AddTextField(approvalsList, "SubscriptionId", "SubscriptionId", cc); this.AddTextField(approvalsList, "ExpirationDateTime", "ExpirationDateTime", cc); this.AddTextField(approvalsList, "Resource", "Resource", cc); this.AddTextField(approvalsList, "TenantId", "TenantId", cc); this.AddTextField(approvalsList, "SiteUrl", "SiteUrl", cc); this.AddTextField(approvalsList, "WebId", "WebId", cc); this.AddTextField(approvalsList, "ItemId", "ItemId", cc); this.AddTextField(approvalsList, "ActivityId", "Activity Id", cc); this.AddTextField(approvalsList, "EditorEmail", "EditorEmail", cc); this.AddTextField(approvalsList, "Activity", "Activity", cc); approvalsList.Update(); acc.ExecuteQuery(); } notificationStep = "5"; #endregion #region Grab the list changes and do something with them // grab the changes to the provided list using the GetChanges method // on the list. Only request Item changes as that's what's supported via // the list web hooks ChangeQuery changeQuery = new ChangeQuery(false, true); changeQuery.Item = true; changeQuery.RecursiveAll = true; changeQuery.User = true; changeQuery.FetchLimit = 1000; // Max value is 2000, default = 1000 notificationStep = "6"; ChangeToken lastChangeToken = null; Guid id = new Guid(notification.SubscriptionId); string storageConnectionString = System.Environment.GetEnvironmentVariable("StorageConnectionString"); const string tableName = "crosssiteappchangetokens"; // Connect to storage account / container var storageAccount = Microsoft.WindowsAzure.Storage.CloudStorageAccount.Parse(storageConnectionString); CloudTableClient tableClient = storageAccount.CreateCloudTableClient(); CloudTable table = tableClient.GetTableReference(tableName); notificationStep = "7"; await table.CreateIfNotExistsAsync(); TableResult result = await table.ExecuteAsync(TableOperation.Retrieve <TableChangeToken>("List", id.ToString())); TableChangeToken loadedChangeToken = null; if (result.Result != null) { lastChangeToken = new ChangeToken(); loadedChangeToken = (result.Result as TableChangeToken); lastChangeToken.StringValue = loadedChangeToken.StringValue; } notificationStep = "8"; // Start pulling down the changes bool allChangesRead = false; do { // should not occur anymore now that we record the starting change token at // subscription creation time, but it's a safety net if (lastChangeToken == null) { lastChangeToken = new ChangeToken(); // See https://blogs.technet.microsoft.com/stefan_gossner/2009/12/04/content-deployment-the-complete-guide-part-7-change-token-basics/ lastChangeToken.StringValue = string.Format("1;3;{0};{1};-1", notification.Resource, DateTime.Now.AddMinutes(-5).ToUniversalTime().Ticks.ToString()); } // Assign the change token to the query...this determines from what point in // time we'll receive changes changeQuery.ChangeTokenStart = lastChangeToken; // Execute the change query var changes = notificationSourceList.GetChanges(changeQuery); cc.Load(changes); cc.ExecuteQueryRetry(); notificationStep = "9"; if (changes.Count > 0) { foreach (Change change in changes) { lastChangeToken = change.ChangeToken; if (change is ChangeItem) { // do "work" with the found change DoWork(acc, approvalsList, cc, notificationSourceList, change, notification, log); } } // We potentially can have a lot of changes so be prepared to repeat the // change query in batches of 'FetchLimit' untill we've received all changes if (changes.Count < changeQuery.FetchLimit) { allChangesRead = true; } } else { allChangesRead = true; } // Are we done? } while (allChangesRead == false); // Persist the last used changetoken as we'll start from that one // when the next event hits our service if (loadedChangeToken != null) { // Only persist when there's a change in the change token if (!loadedChangeToken.StringValue.Equals(lastChangeToken.StringValue, StringComparison.InvariantCultureIgnoreCase)) { loadedChangeToken.StringValue = lastChangeToken.StringValue; await table.ExecuteAsync(TableOperation.InsertOrReplace(loadedChangeToken)); notificationStep = "10"; } } else { // should not occur anymore now that we record the starting change token at // subscription creation time, but it's a safety net var newToken = new TableChangeToken() { PartitionKey = "List", RowKey = id.ToString(), StringValue = lastChangeToken.StringValue }; await table.ExecuteAsync(TableOperation.InsertOrReplace(newToken)); notificationStep = "11"; } #endregion #region "Update" the web hook expiration date when needed // Optionally add logic to "update" the expirationdatetime of the web hook // If the web hook is about to expire within the coming 5 days then prolong it if (notification.ExpirationDateTime.AddDays(-5) < DateTime.Now) { WebHookManager webHookManager = new WebHookManager(); Task <bool> updateResult = Task.WhenAny( webHookManager.UpdateListWebHookAsync( url, listId.ToString(), notification.SubscriptionId, System.Environment.GetEnvironmentVariable("WebHookEndPoint"), DateTime.Now.AddMonths(3), this.accessToken) ).Result; notificationStep = "12"; if (updateResult.Result == false) { throw new Exception(String.Format("The expiration date of web hook {0} with endpoint {1} could not be updated", notification.SubscriptionId, System.Environment.GetEnvironmentVariable("WebHookEndPoint"))); } } #endregion } catch (Exception ex) { // Log error //Console.WriteLine(ex.ToString()); throw new Exception("Step " + notificationStep, ex); } finally { if (cc != null) { cc.Dispose(); } } }
public static async Task RunAsync([QueueTrigger("processchanges", Connection = "AzureWebJobsStorage")] WebhookNotification notificationModel, ILogger log) { log.LogInformation($"ProcesResourceChange queue trigger function process Site:{notificationModel.SiteUrl} Resource{notificationModel.Resource} "); string tenant = System.Environment.GetEnvironmentVariable("Tenant", EnvironmentVariableTarget.Process); string siteUrl = $"https://{tenant}.sharepoint.com{notificationModel.SiteUrl}"; log.LogInformation("Getting Azure SharePoint Token"); LoginEntity loginDetails = await LoginUtil.CertificateLoginDetails(); OfficeDevPnP.Core.AuthenticationManager authManager = new OfficeDevPnP.Core.AuthenticationManager(); log.LogInformation("Connecting to SharePoint"); using (ClientContext clientContext = authManager.GetAzureADAppOnlyAuthenticatedContext(siteUrl, loginDetails.ClientId, loginDetails.Tenant, loginDetails.Certificate)) { log.LogInformation("Getting SharePoint List"); Guid listId = new Guid(notificationModel.Resource); List list = clientContext.Web.Lists.GetById(listId); // grab the changes to the provided list using the GetChanges method // on the list. Only request Item changes as that's what's supported via // the list web hooks ChangeQuery changeQuery = new ChangeQuery(false, false) { Item = true, Add = true, DeleteObject = false, Update = true, FetchLimit = 1000 }; ChangeToken lastChangeToken = null; if (null == notificationModel.ClientState) { throw new ApplicationException("Webhook doesn't contain a Client State"); } Guid id = new Guid(notificationModel.ClientState); log.LogInformation("Checking Database for Change Token"); AzureTableSPWebHook listWebHookRow = await AzureTable.GetListWebHookByID(id, listId); if (!string.IsNullOrEmpty(listWebHookRow.LastChangeToken)) { log.LogInformation("Change Token found"); lastChangeToken = new ChangeToken { StringValue = listWebHookRow.LastChangeToken }; } //Start pulling down the changes bool allChangesRead = false; do { if (lastChangeToken == null) { log.LogInformation("Change Token not found grabbing the last 5 days of changes"); //If none found, grab only the last 5 day changes. lastChangeToken = new ChangeToken { StringValue = string.Format("1;3;{0};{1};-1", notificationModel.Resource, DateTime.Now.AddDays(-5).ToUniversalTime().Ticks.ToString()) }; } //Assing the change token to the query..this determins from what point in time we'll receive changes changeQuery.ChangeTokenStart = lastChangeToken; ChangeCollection changes = list.GetChanges(changeQuery); clientContext.Load(list); clientContext.Load(changes); await clientContext.ExecuteQueryAsync(); if (changes.Count > 0) { log.LogInformation($"Found {changes.Count} changes"); foreach (Change change in changes) { lastChangeToken = change.ChangeToken; if (change is ChangeItem item) { log.LogInformation($"Change {change.ChangeType} on Item:{item.ItemId} on List:{list.Title} Site:{notificationModel.SiteUrl}"); } } if (changes.Count < changeQuery.FetchLimit) { allChangesRead = true; } } else { allChangesRead = true; } } while (allChangesRead == false); if (!listWebHookRow.LastChangeToken.Equals(lastChangeToken.StringValue, StringComparison.InvariantCultureIgnoreCase)) { listWebHookRow.LastChangeToken = lastChangeToken.StringValue; log.LogInformation("Updating change token"); await AzureTable.InsertOrReplaceListWebHook(listWebHookRow); } if (notificationModel.ExpirationDateTime.AddDays(-30) < DateTime.Now) { bool updateResult = list.UpdateWebhookSubscription(new Guid(notificationModel.SubscriptionId), DateTime.Now.AddMonths(2)); if (!updateResult) { log.LogError($"The expiration date of web hook {notificationModel.SubscriptionId} with endpoint 'https://{Environment.GetEnvironmentVariable("WEBSITE_HOSTNAME")}/SharePointWebHook' could not be updated"); } else { log.LogInformation($"The expiration date of web hook {notificationModel.SubscriptionId} with endpoint 'https://{Environment.GetEnvironmentVariable("WEBSITE_HOSTNAME")}/SharePointWebHook' successfully updated until {DateTime.Now.AddMonths(2).ToLongDateString()} "); } } } }
public static void ProcessChanges(string listID, ChangeToken lastChangeToken, DurableOrchestrationClient client, ILogger log) { using (var cc = new OfficeDevPnP.Core.AuthenticationManager().GetAppOnlyAuthenticatedContext(ConfigurationManager.AppSettings["siteUrl"], ConfigurationManager.AppSettings["clientId"], ConfigurationManager.AppSettings["clientSecret"])) { ChangeQuery changeQuery = new ChangeQuery(false, false); changeQuery.Item = true; changeQuery.Update = true; // could handle deletes too. Just need to know if approve or reject is assumed changeQuery.ChangeTokenStart = lastChangeToken; List changedList = cc.Web.GetListById(new Guid(listID)); var changes = changedList.GetChanges(changeQuery); cc.Load(changes); cc.ExecuteQuery(); foreach (Change change in changes) { if (change is ChangeItem) { ListItem task = changedList.GetItemById((change as ChangeItem).ItemId); ListItemVersionCollection taskVersions = task.Versions; cc.Load(taskVersions); cc.Load(task); cc.ExecuteQuery(); if (taskVersions.Count < 2) { return; } var currentStatus = (string)taskVersions[0]["Status"]; var priorStatus = (string)taskVersions[1]["Status"]; string wfid = (string)task["workflowId"]; Console.WriteLine($"Item # ${task.Id} current status is ${currentStatus} Prior status is ${priorStatus}"); switch ((string)task["Action"]) { case "DocOwnerApproval": if (currentStatus != priorStatus) { if (currentStatus == "Approve") { log.LogInformation("Sending event DocOwnerApproved"); client.RaiseEventAsync(wfid, "DocOwnerApproved"); } else { log.LogInformation("Sending event DocOwnerRejected"); client.RaiseEventAsync(wfid, "DocOwnerRejected"); } } break; case "StakeHolderApproval": if (currentStatus != priorStatus) { if (currentStatus == "Approve") { var eventName = "StakeHolderApproval:" + ((FieldUserValue)task["AssignedTo"]).LookupId; log.LogInformation($"Sending event '${eventName}'"); client.RaiseEventAsync(wfid, eventName, true); } else { log.LogInformation($"Sending event 'StakeHolderRejection'"); client.RaiseEventAsync(wfid, "StakeHolderRejection"); } } break; } } } }; }
/// <summary> /// Processes a received notification. This typically is triggered via an Azure Web Job that reads the Azure storage queue /// </summary> /// <param name="notification">Notification to process</param> public void ProcessNotification(NotificationModel notification) { ClientContext cc = null; try { #region Setup an app-only client context var am = new AuthenticationManager(); var url = $"https://{CloudConfigurationManager.GetSetting("TenantName")}{notification.SiteUrl}"; var realm = TokenHelper.GetRealmFromTargetUrl(new Uri(url)); var clientId = CloudConfigurationManager.GetSetting("ClientId"); var clientSecret = CloudConfigurationManager.GetSetting("ClientSecret"); cc = new Uri(url).DnsSafeHost.Contains("spoppe.com") ? am.GetAppOnlyAuthenticatedContext(url, realm, clientId, clientSecret, acsHostUrl: "windows-ppe.net", globalEndPointPrefix: "login") : am.GetAppOnlyAuthenticatedContext(url, clientId, clientSecret); cc.ExecutingWebRequest += Cc_ExecutingWebRequest; #endregion #region Grab the list for which the web hook was triggered var lists = cc.Web.Lists; var listId = new Guid(notification.Resource); var results = cc.LoadQuery(lists.Where(lst => lst.Id == listId)); cc.ExecuteQueryRetry(); var changeList = results.FirstOrDefault(); if (changeList == null) { // list has been deleted in between the event being fired and the event being processed return; } #endregion #region Grab the list changes and do something with them // grab the changes to the provided list using the GetChanges method // on the list. Only request Item changes as that's what's supported via // the list web hooks var changeQuery = new ChangeQuery(false, true) { Item = true, FetchLimit = 1000, // Max value is 2000, default = 1000 DeleteObject = false, Add = true, Update = true, SystemUpdate = false }; // grab last change token from database if possible using (var dbContext = new SharePointWebHooks()) { ChangeToken lastChangeToken = null; var id = new Guid(notification.SubscriptionId); var listWebHookRow = dbContext.ListWebHooks.Find(id); if (listWebHookRow != null) { lastChangeToken = new ChangeToken { StringValue = listWebHookRow.LastChangeToken }; } // Start pulling down the changes var allChangesRead = false; do { // should not occur anymore now that we record the starting change token at // subscription creation time, but it's a safety net if (lastChangeToken == null) { lastChangeToken = new ChangeToken { StringValue = $"1;3;{notification.Resource};{DateTime.Now.AddMinutes(-5).ToUniversalTime().Ticks.ToString()};-1" }; // See https://blogs.technet.microsoft.com/stefan_gossner/2009/12/04/content-deployment-the-complete-guide-part-7-change-token-basics/ } // Assign the change token to the query...this determines from what point in // time we'll receive changes changeQuery.ChangeTokenStart = lastChangeToken; // Execute the change query var changes = changeList.GetChanges(changeQuery); cc.Load(changes); cc.ExecuteQueryRetry(); // If item is changed more than once var uniqueChanges = changes.Cast <ChangeItem>().AsEnumerable().DistinctBy(change => change.ItemId).ToList(); if (uniqueChanges.Any()) { foreach (var change in uniqueChanges) { lastChangeToken = change.ChangeToken; try { // do "work" with the found change DoWork(cc, changeList, change); } catch (Exception) { // ignored } } // We potentially can have a lot of changes so be prepared to repeat the // change query in batches of 'FetchLimit' until we've received all changes if (changes.Count < changeQuery.FetchLimit) { allChangesRead = true; } } else { allChangesRead = true; } // Are we done? } while (allChangesRead == false); // Persist the last used change token as we'll start from that one // when the next event hits our service if (listWebHookRow != null) { // Only persist when there's a change in the change token if (!listWebHookRow.LastChangeToken.Equals(lastChangeToken.StringValue, StringComparison.InvariantCultureIgnoreCase)) { listWebHookRow.LastChangeToken = lastChangeToken.StringValue; dbContext.SaveChanges(); } } else { // should not occur anymore now that we record the starting change token at // subscription creation time, but it's a safety net dbContext.ListWebHooks.Add(new ListWebHooks() { Id = id, ListId = listId, LastChangeToken = lastChangeToken.StringValue, }); dbContext.SaveChanges(); } } #endregion #region "Update" the web hook expiration date when needed // Optionally add logic to "update" the expiration date time of the web hook // If the web hook is about to expire within the coming 5 days then prolong it if (notification.ExpirationDateTime.AddDays(-5) >= DateTime.Now) { return; } var webHookManager = new WebHookManager(); var updateResult = Task.WhenAny( webHookManager.UpdateListWebHookAsync( url, listId.ToString(), notification.SubscriptionId, CloudConfigurationManager.GetSetting("WebHookEndPoint"), DateTime.Now.AddMonths(3), _accessToken) ).Result; if (updateResult.Result == false) { throw new Exception( $"The expiration date of web hook {notification.SubscriptionId} with endpoint {CloudConfigurationManager.GetSetting("WebHookEndPoint")} could not be updated"); } #endregion } catch (Exception ex) { // Log error Console.WriteLine(ex.ToString()); } finally { // ReSharper disable once ConstantConditionalAccessQualifier cc?.Dispose(); } }
public static void ProcessNotification(ILogger log, string queueItem) { NotificationModel notification = JsonConvert.DeserializeObject <NotificationModel>(queueItem); log.LogInformation($"Processing notification: {notification.Resource}"); #region Get Context string url = string.Format($"https://{CloudConfigurationManager.GetSetting("SP_Tenant_Name")}.sharepoint.com{notification.SiteUrl}"); OfficeDevPnP.Core.AuthenticationManager am = new AuthenticationManager(); ClientContext cc = am.GetAppOnlyAuthenticatedContext( url, CloudConfigurationManager.GetSetting("SPApp_ClientId"), CloudConfigurationManager.GetSetting("SPApp_ClientSecret")); #endregion #region Grab the list for which the web hook was triggered List changeList = cc.Web.GetListById(new Guid(notification.Resource)); cc.ExecuteQueryRetry(); if (changeList == null) { // list has been deleted in between the event being fired and the event being processed log.LogInformation($"List \"{notification.Resource}\" no longer exists."); return; } #endregion #region Get the Last Change Token from the Azure table CloudStorageAccount storageAccount = CloudStorageAccount.Parse(CloudConfigurationManager.GetSetting("AzureWebJobsStorage")); CloudTableClient client = storageAccount.CreateCloudTableClient(); CloudTable table = client.GetTableReference(CloudConfigurationManager.GetSetting("LastChangeTokensTableName")); table.CreateIfNotExists(); TableOperation retrieveOperation = TableOperation.Retrieve <LastChangeEntity>("LastChangeToken", notification.Resource); TableResult query = table.Execute(retrieveOperation); ChangeToken lastChangeToken = null; if (query.Result != null) { lastChangeToken = new ChangeToken() { StringValue = ((LastChangeEntity)query.Result).LastChangeToken }; } if (lastChangeToken == null) { lastChangeToken = new ChangeToken() { StringValue = $"1;3;{notification.Resource};{DateTime.Now.AddMinutes(-60).ToUniversalTime().Ticks.ToString()};-1" }; } #endregion #region Grab Changes since Last Change Token (in batches) ChangeQuery changeQuery = new ChangeQuery(false, true) { Item = true, FetchLimit = 1000 // Max value is 2000, default = 1000 }; //List will keep track of items we have already handled List <int> handledListItems = new List <int>(); // Start pulling down the changes bool allChangesRead = false; do { //Assign the change token to the query...this determines from what point in time we'll receive changes changeQuery.ChangeTokenStart = lastChangeToken; ChangeCollection changes = changeList.GetChanges(changeQuery); cc.Load(changes); cc.ExecuteQueryRetry(); #endregion // Save last used changetoken to Azure table. We'll start from that one when the next event hits our service if (changes.Count > 0) { foreach (Change change in changes) { lastChangeToken = change.ChangeToken; } } LastChangeEntity lce = new LastChangeEntity("LastChangeToken", notification.Resource) { LastChangeToken = lastChangeToken.StringValue }; TableOperation insertOperation = TableOperation.InsertOrReplace(lce); table.Execute(insertOperation); #region Process changes log.LogInformation($"Changes found: {changes.Count}"); if (changes.Count > 0) { try { foreach (Change change in changes) { if (change is ChangeItem) { var listItemId = (change as ChangeItem).ItemId; log.LogInformation($"-Item that changed: ItemId: {listItemId}"); if (handledListItems.Contains(listItemId)) { log.LogInformation("-ListItem already handled in this batch."); } else { //DO SOMETHING WITH LIST ITEM DoWork(log, cc, changeList, change); } RecordChangeInWebhookHistory(cc, changeList, change, log, notification.Resource); handledListItems.Add(listItemId); } lastChangeToken = change.ChangeToken; } if (changes.Count < changeQuery.FetchLimit) { allChangesRead = true; } } catch (Exception ex) { throw new Exception($"ERROR: {ex.Message}"); } } else { allChangesRead = true; } // Are we done? } while (allChangesRead == false); #endregion #region "Update" the web hook expiration date when needed // Optionally add logic to "update" the expirationdatetime of the web hook // If the web hook is about to expire within the coming 5 days then prolong it try { if (notification.ExpirationDateTime.AddDays(-5) < DateTime.Now) { DateTime newDate = DateTime.Now.AddMonths(3); log.LogInformation($"Updating the Webhook expiration date to {newDate}"); WebHookManager webHookManager = new WebHookManager(); Task <bool> updateResult = Task.WhenAny( webHookManager.UpdateListWebHookAsync( url, changeList.Id.ToString(), notification.SubscriptionId, CloudConfigurationManager.GetSetting("AzureWebJobsStorage"), newDate, cc.GetAccessToken()) ).Result; if (updateResult.Result == false) { throw new Exception(string.Format("The expiration date of web hook {0} with endpoint {1} could not be updated", notification.SubscriptionId, CloudConfigurationManager.GetSetting("WebHookEndPoint"))); } } } catch (Exception ex) { log.LogInformation($"ERROR: {ex.Message}"); //throw new Exception($"ERROR: {ex.Message}"); } #endregion cc.Dispose(); log.LogInformation("Processing complete."); }
static void Main(string[] args) { //install-Package AppForSharePointWebToolkit //Adds SharePointContext.cs, TokenHelper.cs and a whole bunch of references try { var config = (NameValueCollection)ConfigurationManager.GetSection("Sites"); foreach (var key in config.Keys) { Uri siteUri = new Uri(config.GetValues(key as string)[0]); //get the realm for the URL string realm = TokenHelper.GetRealmFromTargetUrl(siteUri); //Get the Access Token for the URL - requires this app to be registered with the //tenant (using _layouts/AppRegNew.aspx to create App Principal) and then //using _layouts/AppInv.aspx to request permissions for the App Principal string accessToken = TokenHelper.GetAppOnlyAccessToken( TokenHelper.SharePointPrincipal, siteUri.Authority, realm).AccessToken; //Once we have an Access Token we can ceate a ClientContext using it using (var clientContext = TokenHelper.GetClientContextWithAccessToken( siteUri.ToString(), accessToken)) { var site = clientContext.Site; clientContext.Load(site); ChangeQuery siteCQ = new ChangeQuery(false, false); //Object type siteCQ.Item = true; //Change Type ??? siteCQ.Add = true; siteCQ.DeleteObject = true; siteCQ.Update = true; var siteChanges = site.GetChanges(siteCQ); //get the site collection changes clientContext.Load(siteChanges); clientContext.ExecuteQuery(); foreach (Change change in siteChanges) { if (change is ChangeItem) { ChangeItem ci = change as ChangeItem; ListItem item = null; try { Web web = site.OpenWebById(ci.WebId); //get the relevant web clientContext.Load(web); List list = web.Lists.GetById(ci.ListId);//get the relevant list clientContext.Load(list); item = list.GetItemById(ci.ItemId);//get the item clientContext.Load(item, i=>i["Editor"], i=>i["FileRef"]);//load the right bits clientContext.ExecuteQuery(); FieldUserValue fuv = (FieldUserValue)item["Editor"]; Console.WriteLine("{0},{1},{2},{3}", item["FileRef"], ci.ChangeType.ToString(), fuv.LookupValue, change.Time); } catch (Exception e) { //Just because the item is in the change log doesn't mean it exists - //it may have been added and then deleted or just deleted if (e.Message.StartsWith("Item does not exist.") || e.Message.StartsWith("File Not Found") || e.Message.StartsWith("List does not exist")) Console.WriteLine("{0},{1},{2},{3}", e.Message.Substring(0,e.Message.IndexOf(".")), ci.ChangeType.ToString(), "Unknown", change.Time); else Console.WriteLine("{0},{1},{2}", e.InnerException, e.Message, e.Source); } } } } } }//try catch (Exception ex) { Console.WriteLine("Exception occured: {0}", ex.Message); } finally { Console.WriteLine("Press any key to exit."); Console.ReadLine(); } }
static void Main(string[] args) { const string siteUrl = "[your-site-here]"; ChangeToken chgToken = default(ChangeToken); string tenantId = ConfigurationManager.AppSettings["TenantId"]; string clientId = ConfigurationManager.AppSettings["ClientId"]; string clientSecret = ConfigurationManager.AppSettings["ClientSecret"]; string certificatePath = ConfigurationManager.AppSettings["CertificatePath"]; string certificatePassword = ConfigurationManager.AppSettings["CertificatePassword"]; ClientContext clientContext = ContextProvider.GetAppOnlyClientContext(siteUrl, clientId, tenantId, certificatePath, certificatePassword); // ClientContext clientContext = new ClientContext(siteUrl); var site = clientContext.Site; ChangeQuery siteCQ = new ChangeQuery(true, true); var siteChanges = site.GetChanges(siteCQ); clientContext.Load(siteChanges); clientContext.ExecuteQueryRetry(); string changeType = String.Empty; string itemId = String.Empty; foreach (Change change in siteChanges) { Console.WriteLine($"{change.ChangeType}, {change.TypedObject}, {change.ChangeToken.StringValue}"); if (change is Microsoft.SharePoint.Client.ChangeItem) { ChangeItem ci = change as ChangeItem; Console.WriteLine($"{ci.ItemId}: {change.Time}"); } } var web = clientContext.Web; clientContext.Load(web); var list = web.Lists.GetByTitle("Documents"); clientContext.Load(list, l => l.Title, l => l.CurrentChangeToken); clientContext.ExecuteQuery(); Console.WriteLine($"List {list.Title}: {list.CurrentChangeToken.StringValue}"); chgToken = list.CurrentChangeToken; ChangeQuery cq = new ChangeQuery(false, false); cq.Item = true; cq.Add = true; cq.Update = true; cq.Restore = true; cq.ChangeTokenStart = chgToken; var changes = list.GetChanges(cq); clientContext.Load(changes); clientContext.ExecuteQuery(); foreach (Change change2 in changes) { if (change2 is Microsoft.SharePoint.Client.ChangeItem) { ChangeItem ci = change2 as ChangeItem; changeType = ci.ChangeType.ToString(); itemId = ci.ItemId.ToString(); Console.WriteLine("{0}: {1}", itemId, changeType); if (ci.ChangeType == ChangeType.Add) { ListItem li = list.GetItemById(ci.ItemId); clientContext.Load(li); clientContext.ExecuteQuery(); FieldUserValue author = (FieldUserValue)li["Author"]; Console.WriteLine(" {0}", author.Email); } } } }
public void ProcessChanges(string webID, string listID, string siteUrl) { ClientContext cc = null; try { #region Setup SharePoint Online ClientContext //DO NOT USE THIS IN PRODUCTION //Try to implement proper authentication based on Azure AD app instead!!! string url = String.Format("https://{0}{1}", TenantURL, siteUrl); string initString = "YOUR PASSWORD"; // Instantiate the secure string. SecureString testString = new SecureString(); // Use the AppendChar method to add each char value to the secure string. foreach (char ch in initString) { testString.AppendChar(ch); } cc = new ClientContext(url); cc.AuthenticationMode = ClientAuthenticationMode.Default; cc.Credentials = new SharePointOnlineCredentials("YOUR USER", testString); #endregion #region Grab the wikipage PublishingPageContent for the page the web hook was triggered Web web = cc.Site.OpenWebById(new Guid(webID)); cc.Load(web); List ls = web.Lists.GetById(new Guid(listID)); cc.Load(ls); ChangeQuery query = new ChangeQuery(false, false); query.Item = true; query.Update = true; ChangeCollection changes = ls.GetChanges(query); cc.Load(changes); cc.ExecuteQuery(); Console.WriteLine("Starting to process changes for List {0} in Web {1} under site {2}", ls.Title, web.Title, siteUrl); Dictionary <String, String> qna = new Dictionary <string, string>(); foreach (Change change in changes) { if (change is Microsoft.SharePoint.Client.ChangeItem) { ChangeItem ci = change as ChangeItem; var changeType = ci.ChangeType.ToString(); var itemId = ci.ItemId.ToString(); Console.WriteLine("Going to process changes for {0}: {1}", itemId, changeType); Dictionary <string, string> res = this.GetWikiPageContententForKB(ci.ItemId, new Guid(listID), siteUrl); foreach (string key in res.Keys) { if (!qna.ContainsKey(key)) { qna.Add(key, res[key]); } } } } KnowledgeBaseManager kb = new KnowledgeBaseManager("https://westus.api.cognitive.microsoft.com/qnamaker/v2.0/knowledgebases/", "CLIENT SECRETE", "KNOWLEDGEBASE ID"); kb.AddQnAToKB(qna); kb.PublishKB(); #endregion } catch (Exception ex) { // Log error Console.WriteLine(ex.ToString()); } finally { if (cc != null) { cc.Dispose(); } } }
public static async Task Run() { ///////////////////////////// // // Programmer configuration // ///////////////////////////// var sharepointDomain = "demo.sharepoint.com"; var siteCollectionPath = "/sites/GraphCommunityDemo"; ///////////////// // // Configuration // ///////////////// AzureAdOptions azureAdOptions = new AzureAdOptions(); var settingsFilename = System.IO.Path.Combine(System.IO.Directory.GetCurrentDirectory(), "appsettings.json"); var builder = new ConfigurationBuilder() .AddJsonFile(settingsFilename, optional: false) .AddUserSecrets <Program>(); var config = builder.Build(); config.Bind("AzureAd", azureAdOptions); //////////////////////////// // // Graph Client with Logger // //////////////////////////// var logger = new StringBuilderHttpMessageLogger(); /* * Could also use the Console if preferred... * * var logger = new ConsoleHttpMessageLogger(); */ var pca = PublicClientApplicationBuilder .Create(azureAdOptions.ClientId) .WithTenantId(azureAdOptions.TenantId) .Build(); var scopes = new string[] { $"https://{sharepointDomain}/AllSites.FullControl" }; IAuthenticationProvider ap = new DeviceCodeProvider(pca, scopes); using (LoggingMessageHandler loggingHandler = new LoggingMessageHandler(logger)) using (HttpProvider hp = new HttpProvider(loggingHandler, false, new Serializer())) { GraphServiceClient graphServiceClient = new GraphServiceClient(ap, hp); //////////////////////////// // // Setup is complete, run the sample // //////////////////////////// var WebUrl = $"https://{sharepointDomain}{siteCollectionPath}"; var web = await graphServiceClient .SharePointAPI(WebUrl) .Web .Request() .GetAsync(); var changeToken = web.CurrentChangeToken; Console.WriteLine($"current change token: {changeToken.StringValue}"); Console.WriteLine($"Make an update to the site {WebUrl}"); Console.WriteLine("Press enter to continue"); Console.ReadLine(); var qry = new ChangeQuery(true, true); qry.ChangeTokenStart = changeToken; var changes = await graphServiceClient .SharePointAPI(WebUrl) .Web .Request() .GetChangesAsync(qry); Console.WriteLine(changes.Count); foreach (var item in changes) { Console.WriteLine($"{item.ChangeType}"); } Console.WriteLine("Press enter to show log"); Console.ReadLine(); Console.WriteLine(); var log = logger.GetLog(); Console.WriteLine(log); } }