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);
        }
示例#4
0
        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();
        }
示例#5
0
        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");
                }
            }
        }
示例#7
0
        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);
        }
示例#8
0
        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);
        }
示例#9
0
        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);
        }
示例#12
0
        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);
                }
        }
示例#13
0
        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);
        }
示例#16
0
        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();
                }
            }
        }
示例#18
0
 public ChangeCollection GetChanges(ChangeQuery query) => _list.GetChanges(query);
示例#19
0
文件: Program.cs 项目: CherifSy/PnP
        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();

            }
        }
示例#20
0
        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);
        }
示例#21
0
        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);
        }
示例#22
0
        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.");
        }
示例#29
0
        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();
            }
        }
示例#30
0
        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);
                    }
                }
            }
        }
示例#31
0
        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();
                }
            }
        }
示例#32
0
        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);
                }
        }