<br />
<b>Deprecated</b>:  YoastSEO_Vendor\Symfony\Component\DependencyInjection\Container::__construct(): Implicitly marking parameter $parameterBag as nullable is deprecated, the explicit nullable type must be used instead in <b>/home/nubelus/sharedove/adisjugo/wp-content/plugins/wordpress-seo/vendor_prefixed/symfony/dependency-injection/Container.php</b> on line <b>60</b><br />
<br />
<b>Deprecated</b>:  YoastSEO_Vendor\League\OAuth2\Client\Provider\AbstractProvider::authorize(): Implicitly marking parameter $redirectHandler as nullable is deprecated, the explicit nullable type must be used instead in <b>/home/nubelus/sharedove/adisjugo/wp-content/plugins/wordpress-seo/vendor_prefixed/league/oauth2-client/src/Provider/AbstractProvider.php</b> on line <b>416</b><br />
<br />
<b>Deprecated</b>:  YoastSEO_Vendor\GuzzleHttp\Client::getConfig(): Implicitly marking parameter $option as nullable is deprecated, the explicit nullable type must be used instead in <b>/home/nubelus/sharedove/adisjugo/wp-content/plugins/wordpress-seo/vendor_prefixed/guzzlehttp/guzzle/src/Client.php</b> on line <b>181</b><br />
<br />
<b>Deprecated</b>:  YoastSEO_Vendor\GuzzleHttp\ClientInterface::getConfig(): Implicitly marking parameter $option as nullable is deprecated, the explicit nullable type must be used instead in <b>/home/nubelus/sharedove/adisjugo/wp-content/plugins/wordpress-seo/vendor_prefixed/guzzlehttp/guzzle/src/ClientInterface.php</b> on line <b>77</b><br />
{"id":3737,"date":"2018-05-15T08:58:30","date_gmt":"2018-05-15T08:58:30","guid":{"rendered":"https:\/\/blog.sharedove.com\/adisjugo\/?p=3737"},"modified":"2019-03-02T11:04:11","modified_gmt":"2019-03-02T11:04:11","slug":"timerjobs-in-sharepoint-online","status":"publish","type":"post","link":"https:\/\/blog.sharedove.com\/adisjugo\/index.php\/2018\/05\/15\/timerjobs-in-sharepoint-online\/","title":{"rendered":"Azure Functions as timer job equivalents in SP Online"},"content":{"rendered":"<p>Traditional SharePoint developers knew very well how to handle daemons in their daily work: Timer Jobs were the way to go. Microsoft was very specific in their guidance and documentation. We used (and sometimes misused!), Timer Jobs for all different sort of situations \u2013 from cleanup jobs, async data manipulation (sic!), to getting external data into the SharePoint context. Even with all the problems that timer jobs had, we learned to live with it, and to use them to our advantage.<\/p>\n<p>The situation got somewhat more complex with the transition to cloud, and to client-side development. First of all, there was no \u201cofficial way\u201d how to do it anymore, as in the \u201cgood ol\u2019 times\u201d. The new Microsoft is all about \u201chere is the API, you use whatever tech you want\u201d mantra. Which can be good (freedom!), and bad (bad architectural decisions!) in the same time. The trending technology was also changing over the past two or three years. Three years ago, my answer on \u201chow do you develop daemons for SharePoint Online\u201d would probably be \u201cAzure WebJobs\u201d. Now, we have even more options. In this article, I\u2019ll fully go the \u201cserverless\u201d way, and explain how to use Azure Functions for these purposes (because Functions run on hamsters, and not on servers).<\/p>\n<p>Why Functions? Because they will do the job just fine for the light-weight and short running pieces of code, which is very often the case with timer jobs. For anything more complex, using Azure WebJobs is still a legitimate option.<\/p>\n<h2>Who are you, and when yes, how much?<\/h2>\n<p>That question from the subtitle is the first question we need to answer. It is not a new question, but it was, in the \u201cgood ol\u2019 times\u201d, answered for us. SharePoint Timer Jobs would run under whatever service account that was running OWSTIMER.EXE Windows service, and sharepoint\\system would be used to access the data. Why was it like that? It is really, easy: you don\u2019t have a \u201creal\u201d user when running a daemon, nobody has authenticated against the IIS. It is a background process that does stuff, and you shouldn\u2019t use a \u201creal\u201d user\u2019s account for that purpose. Therefore, we used the service accounts.<\/p>\n<p>The same is true for SharePoint Online and Azure world. For daemons, we will need to authenticate our code with a non-personal identity, just like we did before. It\u2019s called \u201capp only\u201d nowadays.<\/p>\n<p>So how do we do that? In SharePoint Online and Azure world, it is Azure Active Directory (AAD) what we need. If you still didn\u2019t learn it, it\u2019s time for you to do it: it is probably one of the most important pieces of today\u2019s technology for developers on Microsoft platform. We will create an AAD -secured application, to wrap our code into, and set the necessary permissions for it.<\/p>\n<p>The first step is to log into the Azure Portal \u2013 \u201cAzure Active Directory\u201d Management, and to \u201cregister\u201d a new web application as \u201cWeb app \/ API\u201d through that portal.<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/blog.sharedove.com\/adisjugo\/wp-content\/uploads\/2017\/10\/TimerJobs01.png\" alt=\"TimerJobs01\" width=\"1074\" height=\"689\" class=\"alignnone size-full wp-image-3738\" srcset=\"https:\/\/blog.sharedove.com\/adisjugo\/wp-content\/uploads\/2017\/10\/TimerJobs01.png 1074w, https:\/\/blog.sharedove.com\/adisjugo\/wp-content\/uploads\/2017\/10\/TimerJobs01-600x385.png 600w, https:\/\/blog.sharedove.com\/adisjugo\/wp-content\/uploads\/2017\/10\/TimerJobs01-300x192.png 300w, https:\/\/blog.sharedove.com\/adisjugo\/wp-content\/uploads\/2017\/10\/TimerJobs01-1024x657.png 1024w\" sizes=\"(max-width: 1074px) 100vw, 1074px\" \/><\/p>\n<p>Next, we need to assign the Application permissions we need on SharePoint Online and\/or Microsoft Graph APIs (or any other standard or custom API you might have available).<\/p>\n<p>When we choose an API, we need to give it permissions. We have two types of those: Delegated permissions, with full impersonification, where user\u2019s identity is required and passed on, and Application Permissions, where user\u2019s identity is neither required nor needed, and where we want our \u201cApp\u201d being the principal to make the changes.<\/p>\n<p>We should leave delegated permissions empty here, since we are developing a daemon, and therefore we don\u2019t know anything about the user. We will go to Application Permissions, and click the appropriate checkboxes, depending on what our \u201ctimer job\u201d will have to do.<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/blog.sharedove.com\/adisjugo\/wp-content\/uploads\/2017\/10\/TimerJobs02.png\" alt=\"TimerJobs02\" width=\"1400\" height=\"796\" class=\"alignnone size-full wp-image-3739\" srcset=\"https:\/\/blog.sharedove.com\/adisjugo\/wp-content\/uploads\/2017\/10\/TimerJobs02.png 1400w, https:\/\/blog.sharedove.com\/adisjugo\/wp-content\/uploads\/2017\/10\/TimerJobs02-600x341.png 600w, https:\/\/blog.sharedove.com\/adisjugo\/wp-content\/uploads\/2017\/10\/TimerJobs02-300x171.png 300w, https:\/\/blog.sharedove.com\/adisjugo\/wp-content\/uploads\/2017\/10\/TimerJobs02-1024x582.png 1024w\" sizes=\"(max-width: 1400px) 100vw, 1400px\" \/><\/p>\n<p>If you are familiar with AAD applications, you know that securing them works on a well-known public\/secret keys principle. For each application that you register with the AAD, you will generate a Client ID, which serves as a public key (you don\u2019t have to worry about losing that one), and the set of secret keys, which you need to keep \u2013 well, secret.<\/p>\n<p>For using our newly created application for Azure Functions, however, this will not be enough. We are not going to use the secret keys, but certificates, in order to make this application really authenticate our Azure Function against SharePoint. In production, we are probably going to purchase a certificate from a relevant certificate authority, but for this test, a self-created one is going to do.<\/p>\n<p>In order to do that, you will need to create a certificate on your machine (in Windows, using the Management Console or PowerShell), and to export it as a X.509 certificate (usually with .CER extension) and the private key (usually with .PFX extension). Once you have the CER and PFX files on your computer, run the following PowerShell script to extract the base64 representation, certificate key, and certificate hash from the X.509 certificate (.cer file), since we are going to need them later.<\/p>\n<pre class=\"brush: powershell; title: Code sample:; notranslate\" title=\"Code sample:\">\n$certPath = &quot;X:\\\\&#x5B;path]\\\\&#x5B;certificatenname].cer&quot;\n$cert = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2\n$cert.Import($certPath)\n$rawCert = $cert.GetRawCertData()\n$base64Cert = &#x5B;System.Convert]::ToBase64String($rawCert)\n$rawCertHash = $cert.GetCertHash()\n$base64CertHash = &#x5B;System.Convert]::ToBase64String($rawCertHash)\n$KeyId = &#x5B;System.Guid]::NewGuid().ToString()\nWrite-Output &quot;Base 64 Certificate&quot;\nWrite-Output $base64Cert\nWrite-Output &quot;Base 64 Certificate Hash&quot;\nWrite-Output $base64CertHash\nWrite-Output &quot;Key ID&quot;\nWrite-Output $KeyId\n<\/pre>\n<p>The output from this PowerShell will look something like this:<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/blog.sharedove.com\/adisjugo\/wp-content\/uploads\/2017\/10\/TimerJobs03.png\" alt=\"TimerJobs03\" width=\"1463\" height=\"303\" class=\"alignnone size-full wp-image-3740\" srcset=\"https:\/\/blog.sharedove.com\/adisjugo\/wp-content\/uploads\/2017\/10\/TimerJobs03.png 1463w, https:\/\/blog.sharedove.com\/adisjugo\/wp-content\/uploads\/2017\/10\/TimerJobs03-600x124.png 600w, https:\/\/blog.sharedove.com\/adisjugo\/wp-content\/uploads\/2017\/10\/TimerJobs03-300x62.png 300w, https:\/\/blog.sharedove.com\/adisjugo\/wp-content\/uploads\/2017\/10\/TimerJobs03-1024x212.png 1024w\" sizes=\"(max-width: 1463px) 100vw, 1463px\" \/><\/p>\n<p>You should save these values, since we are going to need them later.<br \/>\nNext, we need to go back to our AAD Application, and to enter this value in the application manifest. Click on the \u201cManifest\u201d icon in the application properties, and the manifest editor will open. Look for the \u201ckeyCredentials\u201d section in the manifest \u2013 it should be empty. Insert the following values into it, so that it looks like this:<\/p>\n<pre class=\"brush: jscript; title: Code sample:; notranslate\" title=\"Code sample:\">\n&quot;keyCredentials&quot;: &#x5B;\n{\n&quot;customKeyIdentifier&quot;: &quot;&#x5B;enter certificate hash here]&quot;,\n&quot;startDate&quot;: &quot;&#x5B;certificate start date]&quot;,\n&quot;endDate&quot;: &quot;&#x5B;certificate end date]&quot;,\n&quot;keyId&quot;: &quot;&#x5B;enter certificate key id here]&quot;,\n&quot;type&quot;: &quot;AsymmetricX509Cert&quot;,\n&quot;usage&quot;: &quot;Verify&quot;,\n&quot;value&quot;: null\n}\n]\n<\/pre>\n<p>Please save the manifest. It should look something like this:<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/blog.sharedove.com\/adisjugo\/wp-content\/uploads\/2017\/10\/TimerJobs04.png\" alt=\"TimerJobs04\" width=\"1438\" height=\"759\" class=\"alignnone size-full wp-image-3741\" srcset=\"https:\/\/blog.sharedove.com\/adisjugo\/wp-content\/uploads\/2017\/10\/TimerJobs04.png 1438w, https:\/\/blog.sharedove.com\/adisjugo\/wp-content\/uploads\/2017\/10\/TimerJobs04-600x317.png 600w, https:\/\/blog.sharedove.com\/adisjugo\/wp-content\/uploads\/2017\/10\/TimerJobs04-300x158.png 300w, https:\/\/blog.sharedove.com\/adisjugo\/wp-content\/uploads\/2017\/10\/TimerJobs04-1024x540.png 1024w\" sizes=\"(max-width: 1438px) 100vw, 1438px\" \/><\/p>\n<p>Once we are done with this, we can move on to creating our Azure Function, which will serve as a timer job.<\/p>\n<h2>Creating an Azure Function and connecting it to SharePoint<\/h2>\n<p>First, create a new, empty Azure Function from the Azure Portal. In my case, I will choose a HTTP triggered C# function, but you can choose any other language you feel comfortable with. HTTP trigger means that we are going to invoke our function manually, through HTTP operations, so we can easier test the function. This will be changed later on, in order to use timer triggers.<\/p>\n<p>The first step you need to do is to upload the private key (PFX) to the files section of your certificate:<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/blog.sharedove.com\/adisjugo\/wp-content\/uploads\/2017\/10\/TimerJobs05.png\" alt=\"TimerJobs05\" width=\"1511\" height=\"707\" class=\"alignnone size-full wp-image-3742\" srcset=\"https:\/\/blog.sharedove.com\/adisjugo\/wp-content\/uploads\/2017\/10\/TimerJobs05.png 1511w, https:\/\/blog.sharedove.com\/adisjugo\/wp-content\/uploads\/2017\/10\/TimerJobs05-600x281.png 600w, https:\/\/blog.sharedove.com\/adisjugo\/wp-content\/uploads\/2017\/10\/TimerJobs05-300x140.png 300w, https:\/\/blog.sharedove.com\/adisjugo\/wp-content\/uploads\/2017\/10\/TimerJobs05-1024x479.png 1024w\" sizes=\"(max-width: 1511px) 100vw, 1511px\" \/><\/p>\n<p>Once you are done with that, let\u2019s start developing the function. The first thing you will need to do, is to set the dependencies. For C#, you will need to open project.json file, and to add the references to<\/p>\n<p>\u2022 Microsoft.IdentityModel.Clients.ActiveDirectory<br \/>\n\u2022 Microsoft.SharePointOnline.CSOM<\/p>\n<p>During the background compilation of this function, Nuget packages of the above-mentioned dependencies are going to be retrieved, and the references are going to be resolved properly. I used the CSOM here, depending on APIs and libraries you want to use, you might want to reference something additional or something else.<br \/>\nIn my case, project.json looks something like this:<\/p>\n<pre class=\"brush: jscript; title: Code sample:; notranslate\" title=\"Code sample:\">\n{\n&quot;frameworks&quot;: {\n&quot;net46&quot;:{\n&quot;dependencies&quot;: {\n&quot;Microsoft.IdentityModel.Clients.ActiveDirectory&quot; : &quot;3.13.7.964&quot;,\n&quot;Microsoft.SharePointOnline.CSOM&quot;: &quot;16.1.5813.1200&quot;\n}\n}\n}\n}\n<\/pre>\n<p>Now let\u2019s look at the function code itself (run.csx).<br \/>\nWe will first need to declare some environment variables:<\/p>\n<pre class=\"brush: csharp; title: Code sample:; notranslate\" title=\"Code sample:\">\nprivate static string ClientId = &quot;AAD app client id&quot;;\nprivate static string Cert = &quot;sharedovedev.pfx&quot;;\nprivate static string CertPassword = &quot;pass@word1&quot;;\nprivate static string Authority = &quot;https:\/\/login.windows.net\/&#x5B;yourtenant].onmicrosoft.com\/&quot;;\nprivate static string Resource = &quot;https:\/\/ &#x5B;yourtenant].sharepoint.com\/&quot;;\nstring siteUrl = &quot;https:\/\/&#x5B;yourtenant].sharepoint.com\/sites\/&#x5B;yoursite]\/&quot;;\nstring listName = &quot;&#x5B;listname]&quot;;\n<\/pre>\n<p>Most of these are self-explaining. Use the password that you have set during the certificate creation. Change the authority and resource to include your tenant urls.<\/p>\n<blockquote><p>Important note here: don\u2019t store your certificate passwords in the clear text in the function, there are other ways, such as Azure Key Vault, how to do that properly.<\/p><\/blockquote>\n<p>In the Run method itself, we will first need to get the authentication token from the certificate:<\/p>\n<pre class=\"brush: csharp; title: Code sample:; notranslate\" title=\"Code sample:\">\n\/\/create AuthenticationContext\nvar authenticationContext = new AuthenticationContext(Authority, false);\n\n\/\/read the certificate private key (PFX) as byte array\nvar certPath = Path.Combine(Environment.GetEnvironmentVariable(&quot;HOME&quot;), &quot;site\\\\wwwroot\\\\&#x5B;yourdirectory]\\\\&quot;, Cert);\nbyte&#x5B;] certbytes = System.IO.File.ReadAllBytes(certPath);\n\n\/\/create X.509 certificate object out of the byte array\nvar cert = new X509Certificate2(certbytes,\nCertPassword,\nX509KeyStorageFlags.Exportable |\nX509KeyStorageFlags.MachineKeySet |\nX509KeyStorageFlags.PersistKeySet);\n\n\/\/get the auth token out of that certificate\nvar authenticationResult =\nawait authenticationContext.AcquireTokenAsync(Resource, new ClientAssertionCertificate(ClientId, cert));\n\nvar token = authenticationResult.AccessToken;\n<\/pre>\n<p>With this, the most important thing has been done: we have got our access token, which we can use for the calls against our APIs, in this case, against SharePoint Online.<\/p>\n<p>In order to do that, we need to attach this token to every call we make against SP Online.<\/p>\n<pre class=\"brush: csharp; title: Code sample:; notranslate\" title=\"Code sample:\">\n\/\/create a ClientContext, and attach the auth token\n\/\/to the header of each request we are going to make.\nvar context = new ClientContext(siteUrl);\ncontext.ExecutingWebRequest += (s, e) =&amp;gt;\n{\ne.WebRequestExecutor.RequestHeaders&#x5B;&quot;Authorization&quot;] = &quot;Bearer &quot; + authenticationResult.AccessToken;\n};\n\n\/\/now finally we are ready to do\n\/\/some SharePoint magic with the CSOM here\ncontext.Load(context.Site);\ncontext.ExecuteQuery();\nWeb web = context.Web;\n<\/pre>\n<p>What did we do here? After we\u2019ve got the token, we have created a SharePoint ClientContext, and attached the auth token to each request we are going to make to the server. That way our auth token is always going to travel along.<\/p>\n<p>The full code of run.csx can be found here \u2013 please use it just as an example, it is neither clean, nor did I follow the coding best practices here (clear text passwords!). You will, however, get the idea how the things work from there: <a href=\"https:\/\/goo.gl\/M5hu22\">https:\/\/goo.gl\/M5hu22<\/a><\/p>\n<p>My code actually adds some items into a SharePoint list. If you open the list items which have been added by this Azure Function, you will see that the Author and Modified By fields are properly set to the principal which nicely identifies as our AAD App:<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/blog.sharedove.com\/adisjugo\/wp-content\/uploads\/2017\/10\/TimerJobs06.png\" alt=\"TimerJobs06\" width=\"516\" height=\"333\" class=\"alignnone size-full wp-image-3743\" srcset=\"https:\/\/blog.sharedove.com\/adisjugo\/wp-content\/uploads\/2017\/10\/TimerJobs06.png 516w, https:\/\/blog.sharedove.com\/adisjugo\/wp-content\/uploads\/2017\/10\/TimerJobs06-450x290.png 450w, https:\/\/blog.sharedove.com\/adisjugo\/wp-content\/uploads\/2017\/10\/TimerJobs06-300x194.png 300w\" sizes=\"(max-width: 516px) 100vw, 516px\" \/><\/p>\n<h2>Creating the timer job from Azure Function<\/h2>\n<p>The last step will be to change the trigger of our Azure Function from HTTP to Timer. Open the \u201cIntegrate\u201d tab of the function, and delete the existing HTTP trigger:<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/blog.sharedove.com\/adisjugo\/wp-content\/uploads\/2017\/10\/TimerJobs07.png\" alt=\"TimerJobs07\" width=\"797\" height=\"569\" class=\"alignnone size-full wp-image-3744\" srcset=\"https:\/\/blog.sharedove.com\/adisjugo\/wp-content\/uploads\/2017\/10\/TimerJobs07.png 797w, https:\/\/blog.sharedove.com\/adisjugo\/wp-content\/uploads\/2017\/10\/TimerJobs07-600x428.png 600w, https:\/\/blog.sharedove.com\/adisjugo\/wp-content\/uploads\/2017\/10\/TimerJobs07-300x214.png 300w\" sizes=\"(max-width: 797px) 100vw, 797px\" \/><\/p>\n<p>Then click on the \u201cNew Trigger\u201d button, and select the \u201cTimer\u201d:<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/blog.sharedove.com\/adisjugo\/wp-content\/uploads\/2017\/10\/TimerJobs08.png\" alt=\"TimerJobs08\" width=\"1041\" height=\"561\" class=\"alignnone size-full wp-image-3745\" srcset=\"https:\/\/blog.sharedove.com\/adisjugo\/wp-content\/uploads\/2017\/10\/TimerJobs08.png 1041w, https:\/\/blog.sharedove.com\/adisjugo\/wp-content\/uploads\/2017\/10\/TimerJobs08-600x323.png 600w, https:\/\/blog.sharedove.com\/adisjugo\/wp-content\/uploads\/2017\/10\/TimerJobs08-300x162.png 300w, https:\/\/blog.sharedove.com\/adisjugo\/wp-content\/uploads\/2017\/10\/TimerJobs08-1024x552.png 1024w\" sizes=\"(max-width: 1041px) 100vw, 1041px\" \/><\/p>\n<p>In the last step, you will have to setup the schedule, and save the function.<\/p>\n<p>When setting up the schedule, the basic format of the CRON expressions in Azure is consisted of 6 positions (placeholders), representing respectively:<br \/>\n{second} {minute} {hour} {day} {month} {day of the week}<\/p>\n<p>For example, the following value would make timer execute every minute, on the second zero:<\/p>\n<pre class=\"brush: csharp; title: Code sample:; notranslate\" title=\"Code sample:\">\n0 * * * * *\n<\/pre>\n<blockquote><p>Note: examples below are overtaken from Armin Reiter&#8217;s blog post &#8220;Azure Fuctions Time trigger cheat sheet&#8221; (<a href=\"https:\/\/codehollow.com\/2017\/02\/azure-functions-time-trigger-cron-cheat-sheet\/\" target=\"_blank\">https:\/\/codehollow.com\/2017\/02\/azure-functions-time-trigger-cron-cheat-sheet\/<\/a>) for the purpose of easier and compact reading. For <b>way more<\/b> information about setting time triggers in Functions, please visit the Armin&#8217;s <a href=\"https:\/\/codehollow.com\/2017\/02\/azure-functions-time-trigger-cron-cheat-sheet\/\" target=\"_blank\">original blog post<\/a>.<\/p><\/blockquote>\n<p>The following values are allowed for the different positions (placeholders):<\/p>\n<table>\n<tbody>\n<tr>\n<th>Position<\/th>\n<th>Placeholder<\/th>\n<th>Allowed Values<\/th>\n<th>Note<\/th>\n<\/tr>\n<tr>\n<td>1<\/td>\n<td>{second}<\/td>\n<td>0-59; *<\/td>\n<td>{second} when the trigger will be fired<\/td>\n<\/tr>\n<tr>\n<td>2<\/td>\n<td>{minute}<\/td>\n<td>0-59; *<\/td>\n<td>{minute} when the trigger will be fired<\/td>\n<\/tr>\n<tr>\n<td>3<\/td>\n<td>{hour}<\/td>\n<td>1-23; *<\/td>\n<td>{hour} when the trigger will be fired<\/td>\n<\/tr>\n<tr>\n<td>4<\/td>\n<td>{day}<\/td>\n<td>1-31; *<\/td>\n<td>{day} when the trigger will be fired<\/td>\n<\/tr>\n<tr>\n<td>5<\/td>\n<td>{month}<\/td>\n<td>1-12; *<\/td>\n<td>{month} when the trigger will be fired<\/td>\n<\/tr>\n<tr>\n<td>6<\/td>\n<td>{day of the week}<\/td>\n<td>0-6; MON-SUN; *<\/td>\n<td>{day of the week} when the trigger will be fired<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>As we see, the values in the table above are flexible enough that we can define and set all the time intervals we would like. Here are just some examples:<\/p>\n<table>\n<tbody>\n<tr>\n<th>Expression<\/th>\n<th>Description<\/th>\n<th>runs at<\/th>\n<\/tr>\n<tr>\n<td>0 * * * * *<\/td>\n<td>every minute<\/td>\n<td>09:00:00; 09:01:00; 09:02:00; \u2026 10:00:00<\/td>\n<\/tr>\n<tr>\n<td>0 *\/5 * * * *<\/td>\n<td>every 5 minutes<\/td>\n<td>09:00:00; 09:05:00<\/td>\n<\/tr>\n<tr>\n<td>0 0 * * * *<\/td>\n<td>every hour (hourly)<\/td>\n<td>09:00:00; 10:00:00; 11:00:00<\/td>\n<\/tr>\n<tr>\n<td>0 0 *\/6 * * *<\/td>\n<td>every 6 hours<\/td>\n<td>06:00:00; 12:00:00; 18:00:00; 00:00:00<\/td>\n<\/tr>\n<tr>\n<td>0 0 8-18 * * *<\/td>\n<td>every hour between 8-18<\/td>\n<td>08:00:00; 09:00:00; \u2026 18:00:00; 08:00:00<\/td>\n<\/tr>\n<tr>\n<td>0 0 0 * * *<\/td>\n<td>every day (daily)<\/td>\n<td>Oct 16, 2017 00:00:00; Oct 17, 2017 00:00:00<\/td>\n<\/tr>\n<tr>\n<td>0 0 10 * * *<\/td>\n<td>every day at 10:00:00<\/td>\n<td>Oct 16, 2017 10:00:00; Oct 17, 2017 10:00:00<\/td>\n<\/tr>\n<tr>\n<td>0 0 * * * 1-5<\/td>\n<td>every hour on workdays<\/td>\n<td>Oct 13 (FRI), 2017 22:00:00; Oct 13 (FRI), 2017 23:00:00; Oct 16 (MON), 2017 00:00:00<\/td>\n<\/tr>\n<tr>\n<td>0 0 0 * * 0<\/td>\n<td>every sunday (weekly)<\/td>\n<td>Oct 15 (SUN), 2017 00:00:00; Oct 15 (SUN), 2017 00:00:00<\/td>\n<\/tr>\n<tr>\n<td>0 0 9 * * MON<\/td>\n<td>every monday at 09:00:00<\/td>\n<td>Oct 16 (MON), 2017 09:00:00; Oct 16 (MON), 2017 09:00:00<\/td>\n<\/tr>\n<tr>\n<td>0 0 0 1 * *<\/td>\n<td>every 1st of month (monthly)<\/td>\n<td>Oct 1, 2017 00:00:00; Nov 1, 2017 00:00:00; Dec 1, 2017 00:00:00<\/td>\n<\/tr>\n<tr>\n<td>0 0 0 1 1 *<\/td>\n<td>every 1st of january (yearly)<\/td>\n<td>Jan 1, 2018 00:00:00; Jan 1, 2018 00:00:00; Jan 1, 2019 00:00:00<\/td>\n<\/tr>\n<tr>\n<td>0 0 * * * SUN<\/td>\n<td>every hour on sunday<\/td>\n<td>Oct 15 (SUN), 2017 23:00:00; Oct 22 (SUN), 2017 00:00:00; Oct 22 (SUN), 2017 01:00:00<\/td>\n<\/tr>\n<tr>\n<td>0 0 0 * * SAT,SUN<\/td>\n<td>every saturday and sunday<\/td>\n<td>Oct 15 (SUN), 2017 00:00:00; Oct 21 (SAT) 00:00:00; Oct 22 (SUN), 2017 00:00:00<\/td>\n<\/tr>\n<tr>\n<td>0 0 0 * * 6,0<\/td>\n<td>every saturday and sunday<\/td>\n<td>Oct 15 (SUN), 2017 00:00:00; Oct 21 (SAT) 00:00:00; Oct 22 (SUN), 2017 00:00:00<\/td>\n<\/tr>\n<tr>\n<td>0 0 0 1-7 * SUN<\/td>\n<td>every first sunday of the month at 00:00:00<\/td>\n<td>Oct 1 (SUN), 2017 00:00:00; Nov 5 (SUN), 2017 00:00:00<\/td>\n<\/tr>\n<tr>\n<td>11 5 23 * * *<\/td>\n<td>daily at 23:05:11<\/td>\n<td>Oct 16, 2017 23:05:11; Oct 17, 2017 23:05:11<\/td>\n<\/tr>\n<tr>\n<td>30 5 \/6 * * *<\/td>\n<td>every 6 hours at 5 minutes and 30 seconds<\/td>\n<td>06:05:30; 12:05:30; 18:05:30; 00:05:30<\/td>\n<\/tr>\n<tr>\n<td>*\/15 * * * * *<\/td>\n<td>every 15 seconds<\/td>\n<td>09:00:15; 09:00:30; \u2026 09:03:30; 09:03:45; 09:04:00<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>For more info about the Azure Function intervals, please take a look at Armin Reiter&#8217;s blog post: <a href=\"https:\/\/codehollow.com\/2017\/02\/azure-functions-time-trigger-cron-cheat-sheet\/\" target=\"_blank\">https:\/\/codehollow.com\/2017\/02\/azure-functions-time-trigger-cron-cheat-sheet\/<\/a>.<\/p>\n<h2>Conclusion<\/h2>\n<p>Executing daemon again SharePoint data, and performing actions in time intervals, was requirement in almost all medium and large SharePoint projects. With SharePoint server (on premises), we have SharePoint timer jobs, which do this task just fine. In SharePoint Online, however, we need to delegate this logic to some external system. Azure App Service, in all different flavors, is actually a legitimate way to do this, and Azure Functions (which are based on the Azure App Service) offer a nice gateway to do this. Especially if the code which needs to be run is a fairly simple and short, there is really no good reason against using the functions. One might argue that the development story around functions is not yet very mature, with all the issues with debugging, lifecycle etc, that long running operations are not really supported on the dynamic pricing plan (5 minutes timeout), and all those arguments stand. However, for simple operations, for simple code blocks, Azure Functions will do just fine as timer jobs replacement.<\/p>\n<div class=\"fb-background-color\">\n\t\t\t  <div \n\t\t\t  \tclass = \"fb-comments\" \n\t\t\t  \tdata-href = \"https:\/\/blog.sharedove.com\/adisjugo\/index.php\/2018\/05\/15\/timerjobs-in-sharepoint-online\/\"\n\t\t\t  \tdata-numposts = \"59\"\n\t\t\t  \tdata-lazy = \"true\"\n\t\t\t\tdata-colorscheme = \"light\"\n\t\t\t\tdata-order-by = \"time\"\n\t\t\t\tdata-mobile=true>\n\t\t\t  <\/div><\/div>\n\t\t  <style>\n\t\t    .fb-background-color {\n\t\t\t\tbackground:  !important;\n\t\t\t}\n\t\t\t.fb_iframe_widget_fluid_desktop iframe {\n\t\t\t    width: 100% !important;\n\t\t\t}\n\t\t  <\/style>\n\t\t  ","protected":false},"excerpt":{"rendered":"<p>Traditional SharePoint developers knew very well how to handle daemons in their daily work: Timer Jobs were the way to go. Microsoft was very specific in their guidance and documentation. We used (and sometimes misused!), Timer Jobs for all different sort of situations \u2013 from cleanup jobs, async data manipulation (sic!), to getting external data [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":3754,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_et_pb_use_builder":"","_et_pb_old_content":"","_et_gb_content_width":"","footnotes":""},"categories":[11],"tags":[],"class_list":["post-3737","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-development"],"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v25.4 - https:\/\/yoast.com\/wordpress\/plugins\/seo\/ -->\n<title>Azure Functions as timer job equivalents in SP Online - Adis Jugo blog<\/title>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/blog.sharedove.com\/adisjugo\/index.php\/2018\/05\/15\/timerjobs-in-sharepoint-online\/\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Azure Functions as timer job equivalents in SP Online - Adis Jugo blog\" \/>\n<meta property=\"og:description\" content=\"Traditional SharePoint developers knew very well how to handle daemons in their daily work: Timer Jobs were the way to go. Microsoft was very specific in their guidance and documentation. We used (and sometimes misused!), Timer Jobs for all different sort of situations \u2013 from cleanup jobs, async data manipulation (sic!), to getting external data [&hellip;]\" \/>\n<meta property=\"og:url\" content=\"https:\/\/blog.sharedove.com\/adisjugo\/index.php\/2018\/05\/15\/timerjobs-in-sharepoint-online\/\" \/>\n<meta property=\"og:site_name\" content=\"Adis Jugo blog\" \/>\n<meta property=\"article:published_time\" content=\"2018-05-15T08:58:30+00:00\" \/>\n<meta property=\"article:modified_time\" content=\"2019-03-02T11:04:11+00:00\" \/>\n<meta property=\"og:image\" content=\"https:\/\/blog.sharedove.com\/adisjugo\/wp-content\/uploads\/2017\/10\/221126218-60fb11f6-e83b-47c1-b24f-aff5231479a8.jpg\" \/>\n\t<meta property=\"og:image:width\" content=\"912\" \/>\n\t<meta property=\"og:image:height\" content=\"660\" \/>\n\t<meta property=\"og:image:type\" content=\"image\/jpeg\" \/>\n<meta name=\"author\" content=\"adis.jugo\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:label1\" content=\"Written by\" \/>\n\t<meta name=\"twitter:data1\" content=\"adis.jugo\" \/>\n\t<meta name=\"twitter:label2\" content=\"Est. reading time\" \/>\n\t<meta name=\"twitter:data2\" content=\"12 minutes\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\/\/schema.org\",\"@graph\":[{\"@type\":\"WebPage\",\"@id\":\"https:\/\/blog.sharedove.com\/adisjugo\/index.php\/2018\/05\/15\/timerjobs-in-sharepoint-online\/\",\"url\":\"https:\/\/blog.sharedove.com\/adisjugo\/index.php\/2018\/05\/15\/timerjobs-in-sharepoint-online\/\",\"name\":\"Azure Functions as timer job equivalents in SP Online - Adis Jugo blog\",\"isPartOf\":{\"@id\":\"https:\/\/blog.sharedove.com\/adisjugo\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\/\/blog.sharedove.com\/adisjugo\/index.php\/2018\/05\/15\/timerjobs-in-sharepoint-online\/#primaryimage\"},\"image\":{\"@id\":\"https:\/\/blog.sharedove.com\/adisjugo\/index.php\/2018\/05\/15\/timerjobs-in-sharepoint-online\/#primaryimage\"},\"thumbnailUrl\":\"https:\/\/blog.sharedove.com\/adisjugo\/wp-content\/uploads\/2017\/10\/221126218-60fb11f6-e83b-47c1-b24f-aff5231479a8.jpg\",\"datePublished\":\"2018-05-15T08:58:30+00:00\",\"dateModified\":\"2019-03-02T11:04:11+00:00\",\"author\":{\"@id\":\"https:\/\/blog.sharedove.com\/adisjugo\/#\/schema\/person\/a5ca63552094ce9d5a0440f3a1ac9a4c\"},\"breadcrumb\":{\"@id\":\"https:\/\/blog.sharedove.com\/adisjugo\/index.php\/2018\/05\/15\/timerjobs-in-sharepoint-online\/#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\/\/blog.sharedove.com\/adisjugo\/index.php\/2018\/05\/15\/timerjobs-in-sharepoint-online\/\"]}]},{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/blog.sharedove.com\/adisjugo\/index.php\/2018\/05\/15\/timerjobs-in-sharepoint-online\/#primaryimage\",\"url\":\"https:\/\/blog.sharedove.com\/adisjugo\/wp-content\/uploads\/2017\/10\/221126218-60fb11f6-e83b-47c1-b24f-aff5231479a8.jpg\",\"contentUrl\":\"https:\/\/blog.sharedove.com\/adisjugo\/wp-content\/uploads\/2017\/10\/221126218-60fb11f6-e83b-47c1-b24f-aff5231479a8.jpg\",\"width\":912,\"height\":660},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\/\/blog.sharedove.com\/adisjugo\/index.php\/2018\/05\/15\/timerjobs-in-sharepoint-online\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\/\/blog.sharedove.com\/adisjugo\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Azure Functions as timer job equivalents in SP Online\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\/\/blog.sharedove.com\/adisjugo\/#website\",\"url\":\"https:\/\/blog.sharedove.com\/adisjugo\/\",\"name\":\"Adis Jugo blog\",\"description\":\"The Southern Side\",\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\/\/blog.sharedove.com\/adisjugo\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"en-US\"},{\"@type\":\"Person\",\"@id\":\"https:\/\/blog.sharedove.com\/adisjugo\/#\/schema\/person\/a5ca63552094ce9d5a0440f3a1ac9a4c\",\"name\":\"adis.jugo\",\"image\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/blog.sharedove.com\/adisjugo\/#\/schema\/person\/image\/\",\"url\":\"https:\/\/secure.gravatar.com\/avatar\/cc5a23cf1bd0b9d8401c9dd65c6c141041ec0c6e37eedbb511779e4a40a198fd?s=96&d=mm&r=g\",\"contentUrl\":\"https:\/\/secure.gravatar.com\/avatar\/cc5a23cf1bd0b9d8401c9dd65c6c141041ec0c6e37eedbb511779e4a40a198fd?s=96&d=mm&r=g\",\"caption\":\"adis.jugo\"},\"url\":\"https:\/\/blog.sharedove.com\/adisjugo\/index.php\/author\/adisjugo\/\"}]}<\/script>\n<!-- \/ Yoast SEO plugin. -->","yoast_head_json":{"title":"Azure Functions as timer job equivalents in SP Online - Adis Jugo blog","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/blog.sharedove.com\/adisjugo\/index.php\/2018\/05\/15\/timerjobs-in-sharepoint-online\/","og_locale":"en_US","og_type":"article","og_title":"Azure Functions as timer job equivalents in SP Online - Adis Jugo blog","og_description":"Traditional SharePoint developers knew very well how to handle daemons in their daily work: Timer Jobs were the way to go. Microsoft was very specific in their guidance and documentation. We used (and sometimes misused!), Timer Jobs for all different sort of situations \u2013 from cleanup jobs, async data manipulation (sic!), to getting external data [&hellip;]","og_url":"https:\/\/blog.sharedove.com\/adisjugo\/index.php\/2018\/05\/15\/timerjobs-in-sharepoint-online\/","og_site_name":"Adis Jugo blog","article_published_time":"2018-05-15T08:58:30+00:00","article_modified_time":"2019-03-02T11:04:11+00:00","og_image":[{"width":912,"height":660,"url":"https:\/\/blog.sharedove.com\/adisjugo\/wp-content\/uploads\/2017\/10\/221126218-60fb11f6-e83b-47c1-b24f-aff5231479a8.jpg","type":"image\/jpeg"}],"author":"adis.jugo","twitter_card":"summary_large_image","twitter_misc":{"Written by":"adis.jugo","Est. reading time":"12 minutes"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"WebPage","@id":"https:\/\/blog.sharedove.com\/adisjugo\/index.php\/2018\/05\/15\/timerjobs-in-sharepoint-online\/","url":"https:\/\/blog.sharedove.com\/adisjugo\/index.php\/2018\/05\/15\/timerjobs-in-sharepoint-online\/","name":"Azure Functions as timer job equivalents in SP Online - Adis Jugo blog","isPartOf":{"@id":"https:\/\/blog.sharedove.com\/adisjugo\/#website"},"primaryImageOfPage":{"@id":"https:\/\/blog.sharedove.com\/adisjugo\/index.php\/2018\/05\/15\/timerjobs-in-sharepoint-online\/#primaryimage"},"image":{"@id":"https:\/\/blog.sharedove.com\/adisjugo\/index.php\/2018\/05\/15\/timerjobs-in-sharepoint-online\/#primaryimage"},"thumbnailUrl":"https:\/\/blog.sharedove.com\/adisjugo\/wp-content\/uploads\/2017\/10\/221126218-60fb11f6-e83b-47c1-b24f-aff5231479a8.jpg","datePublished":"2018-05-15T08:58:30+00:00","dateModified":"2019-03-02T11:04:11+00:00","author":{"@id":"https:\/\/blog.sharedove.com\/adisjugo\/#\/schema\/person\/a5ca63552094ce9d5a0440f3a1ac9a4c"},"breadcrumb":{"@id":"https:\/\/blog.sharedove.com\/adisjugo\/index.php\/2018\/05\/15\/timerjobs-in-sharepoint-online\/#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/blog.sharedove.com\/adisjugo\/index.php\/2018\/05\/15\/timerjobs-in-sharepoint-online\/"]}]},{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/blog.sharedove.com\/adisjugo\/index.php\/2018\/05\/15\/timerjobs-in-sharepoint-online\/#primaryimage","url":"https:\/\/blog.sharedove.com\/adisjugo\/wp-content\/uploads\/2017\/10\/221126218-60fb11f6-e83b-47c1-b24f-aff5231479a8.jpg","contentUrl":"https:\/\/blog.sharedove.com\/adisjugo\/wp-content\/uploads\/2017\/10\/221126218-60fb11f6-e83b-47c1-b24f-aff5231479a8.jpg","width":912,"height":660},{"@type":"BreadcrumbList","@id":"https:\/\/blog.sharedove.com\/adisjugo\/index.php\/2018\/05\/15\/timerjobs-in-sharepoint-online\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/blog.sharedove.com\/adisjugo\/"},{"@type":"ListItem","position":2,"name":"Azure Functions as timer job equivalents in SP Online"}]},{"@type":"WebSite","@id":"https:\/\/blog.sharedove.com\/adisjugo\/#website","url":"https:\/\/blog.sharedove.com\/adisjugo\/","name":"Adis Jugo blog","description":"The Southern Side","potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/blog.sharedove.com\/adisjugo\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"en-US"},{"@type":"Person","@id":"https:\/\/blog.sharedove.com\/adisjugo\/#\/schema\/person\/a5ca63552094ce9d5a0440f3a1ac9a4c","name":"adis.jugo","image":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/blog.sharedove.com\/adisjugo\/#\/schema\/person\/image\/","url":"https:\/\/secure.gravatar.com\/avatar\/cc5a23cf1bd0b9d8401c9dd65c6c141041ec0c6e37eedbb511779e4a40a198fd?s=96&d=mm&r=g","contentUrl":"https:\/\/secure.gravatar.com\/avatar\/cc5a23cf1bd0b9d8401c9dd65c6c141041ec0c6e37eedbb511779e4a40a198fd?s=96&d=mm&r=g","caption":"adis.jugo"},"url":"https:\/\/blog.sharedove.com\/adisjugo\/index.php\/author\/adisjugo\/"}]}},"_links":{"self":[{"href":"https:\/\/blog.sharedove.com\/adisjugo\/index.php\/wp-json\/wp\/v2\/posts\/3737","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/blog.sharedove.com\/adisjugo\/index.php\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/blog.sharedove.com\/adisjugo\/index.php\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/blog.sharedove.com\/adisjugo\/index.php\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/blog.sharedove.com\/adisjugo\/index.php\/wp-json\/wp\/v2\/comments?post=3737"}],"version-history":[{"count":14,"href":"https:\/\/blog.sharedove.com\/adisjugo\/index.php\/wp-json\/wp\/v2\/posts\/3737\/revisions"}],"predecessor-version":[{"id":3933,"href":"https:\/\/blog.sharedove.com\/adisjugo\/index.php\/wp-json\/wp\/v2\/posts\/3737\/revisions\/3933"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/blog.sharedove.com\/adisjugo\/index.php\/wp-json\/wp\/v2\/media\/3754"}],"wp:attachment":[{"href":"https:\/\/blog.sharedove.com\/adisjugo\/index.php\/wp-json\/wp\/v2\/media?parent=3737"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/blog.sharedove.com\/adisjugo\/index.php\/wp-json\/wp\/v2\/categories?post=3737"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/blog.sharedove.com\/adisjugo\/index.php\/wp-json\/wp\/v2\/tags?post=3737"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}