Author: Rene Modery

Scheduling Twitter Content with Azure Functions

A few months ago, I was looking at ways to schedule my Twitter content: basically, I wanted to define when a specific tweet should be posted. The reason behind this? Mostly, I wanted to promote content from my blog for people in different timezones. I live in Singapore, which means that when I use Twitter in the morning, people in Europe are still asleep, and people in the US are likely going to bed soon as well. So I was looking for a way to post the same content multiple times during different times of the day to reach different people. Additionally, I wanted to prepare some content for the monthly CollabTalk, which happens to take place at 1am Singapore time. Not really a time where I plan to be active on Twitter, especially during the week.

While there are nice free and paid tools out there, I wanted to see if I can use Azure Functions for my purpose. With a little help of Azure Table Storage and a 3rd party library, I was able to create the tool fairly quickly, and have been using it for a while now (around 3 months). Here’s how I set it up:

Creating a Twitter application

To post to Twitter as a specific account, we need to set up a new application on After you’ve followed all the needed steps, write down the Consumer Key, Consumer Secret, Access Token, and Access Token Secret.



Setting Up your Table Storage

We will use Azure Table Storage to store our tweets and their status. I set up a new table called ‘scheduledcontent’ with the following schema:

As for the PartitionKey, I’m using “Scheduled” for any content that still needs to be posted, and “Posted” for content that has been processed already. That way, my queries against the table are quite easy to do, as I can use this column to filter.

Content contains the actual content I want to tweet, scheduledDate the date and time on which it should happen. Lastly, postedDate is for logging purposes, this field will be updated to store the actual date and time of the tweet.


Creating your Function App

Moving on, create your Function App. What you need to do here is to go to your App Service Settings and add the twitter keys and secrets from the previous step as key/value pairs:

I named my as follows:


Configuring your Function

Now comes to fun part: configuring our Function and adding the relevant plumbing and code. To connect to Twitter and post content, I’m going to use a fork of TweetSharp available at, and include the corresponding nuget package in my Function.

First of all, we need to include the required Azure Storage and TweetSharp nuget packages. Create a Project.json file and add the following content:

  "frameworks": {
      "dependencies": {
        "WindowsAzure.Storage": "7.0.0",
        "TweetMoaSharp" : "*"

Once the packages have been installed, you can configure your Function to trigger on a schedule. I initially had it set to run every 15 minutes, but changed it at some point to run every minute ( 0 */1 * * * *):


Let’s move on to configuring the Azure Table Storage connection as an input. Give it a name (inputTable here), enter the Table name you defined earlier, and create the correct Storage account connection.


We’ve now finished the configuration, let’s add some code!

using System;
using System.Configuration;
using TweetSharp;
using Microsoft.WindowsAzure.Storage.Table;

public static void Run(TimerInfo myTimer, CloudTable inputTable, TraceWriter log)
    log.Info($"C# Timer trigger function executed at: {DateTime.Now}"); 
    //fetch all Scheduled tweets
    //Filters: PartitionKey = Scheduled and scheduledDate is in the past
    TableQuery<ScheduledContentEntity> rangeQuery = new TableQuery<ScheduledContentEntity>().Where(
            TableQuery.GenerateFilterCondition("PartitionKey", QueryComparisons.Equal, "Scheduled"),
            TableQuery.GenerateFilterConditionForDate("scheduledDate", QueryComparisons.LessThanOrEqual, DateTime.Now)));
    log.Info($"Fetching all scheduled tweets at: {DateTime.Now}");
    List<ScheduledContentEntity> ScheduledEntities = inputTable.ExecuteQuery(rangeQuery).ToList();
    //if there is a scheduled tweet that needs to get posted, let's do it
    if(ScheduledEntities.Count > 0) {
        log.Info($"{ScheduledEntities.Count} new tweets to post at: {DateTime.Now}");

        //authenticating with Twitter
        var _consumerKey = ConfigurationManager.AppSettings["TwitterConsumerKey"];
        var _consumerSecret = ConfigurationManager.AppSettings["TwitterConsumerSecret"];
        var _accessToken = ConfigurationManager.AppSettings["TwitterAccessToken"];
        var _accessTokenSecret = ConfigurationManager.AppSettings["TwitterAccessTokenSecret"];
        var service = new TwitterService(_consumerKey, _consumerSecret);
        service.AuthenticateWith(_accessToken, _accessTokenSecret);

        //post all the scheduled tweets
        foreach (ScheduledContentEntity entity in ScheduledEntities)
            TwitterStatus result = service.SendTweet(new SendTweetOptions
                Status = entity.Content
            log.Info($"{entity.Content} at: {DateTime.Now}");
            log.Info($"{result.CreatedDate.ToString()} at: {DateTime.Now}");
            //It was not possible to update table rows, so we have to create a new entry and delete the old one.
            //create new entry
            ScheduledContentEntity content = new ScheduledContentEntity(Guid.NewGuid().ToString(), entity.Content, entity.scheduledDate, DateTime.Now);
            TableOperation insertOperation = TableOperation.Insert(content);
            //delete old one
            TableOperation deleteOperation = TableOperation.Delete(entity);
    } else {
        log.Info($"Nothing to tweet at: {DateTime.Now}");

public class ScheduledContentEntity : TableEntity
    public ScheduledContentEntity() {         

    public ScheduledContentEntity(string rowkey, string c, DateTime sd, DateTime pd) {
        this.PartitionKey = "Posted";
        this.RowKey = rowkey;        
        this.Content = c;
        this.scheduledDate = sd;
        this.postedDate = pd;
    public string Content { get; set; }
    public DateTime scheduledDate {get; set;}
    public DateTime postedDate {get; set;}

This code here should be pretty straight-forward to read and understand. Every minute, it checks if there are any entries in our table storage which are set as ‘Scheduled’ (PartitionKey), and if so, tweets them.


Creating an extremely fancy ultra-modern frontend

Well, not really. Sorry, nothing great to show here, the backend was more important to me than the frontend. I created an extremely simple MVC app with a form that creates new entries in my Azure table (RowKey is a random GUID, by the way), and shows scheduled tweets in a standard table on the page. I also use Azure Storage Explorer from time to time, mostly for editing. I’ll leave you up to you to create a fancy site with all the features you want – URL shortening, editing of scheduled tweets, …


The Result

As I mentioned, I’ve been using this set up for some time now, but only scheduled around 50 tweets so far. In case you’re wondering how fast it is, most of the time the tweet gets posted 1-2 seconds after I scheduled it:

Any errors so far? None:


I was also asked to provide some information on the costs. Here are the costs for the past 30 days:


What’s Next?

For my purpose, this is already good enough. While it lacks a lot of things (as mentioned, editing of tweets, shortening of URLs, error handling, maybe a bit more security), it does its job quite well, so I’ll keep it as it is for the moment

Office 365 Advent Calendar – 24 Office 365: Overview of Services and Applications


I’ll finish this year’s Office 365 Advent Calendar with a small overview of the Office 365 Services and Applications. Basically, the following document contains all current services and applications with a short description. You can use this document as a starting point for your organization, your customer, or anyone else with whom you want to discuss the Office 365 suite.

Office 365 – Overview of Services and Applications (pdf)

Office 365 Advent Calendar – 23 Bulk-updating File Extensions in SharePoint Online with PowerShell



A short while ago, I had to update a fairly large number of documents in multiple libraries in a SharePoint Online site. Problem was that their file extension was .html which doesn’t display in the browser if stored in SharePoint Online, but rather these files get downloaded upon opening. The workaround – rename them to .aspx. The following scripts does this for all .html files and renames them to .aspx


$cred = Get-Credential
Connect-PnPOnline -Url "" -Credentials $cred
$ctx = Get-PnPContext
$libraries = Get-PnPlist | Where{$_.BaseTemplate -eq 101}
foreach($lib in $libraries) {
	write-host "Getting items from $($lib.Title)"
	$items = Get-PnPListItem -List $lib -Fields "Title", "FileLeafRef" | where {$_["FileLeafRef"] -like "*.html"}
	write-host "Looping through the items"
	foreach($item in $items) {
		$file = $item.File
		write-host "Renaming file $($file.Name)"
		$newName = $file.Name -Replace "\.html",".aspx"
		Move-PnPFile -ServerRelativeUrl $file.ServerRelativeUrl -TargetUrl "$($file.ListItemAllFields['FileDirRef'])/$($newName)" -Force

Office 365 Advent Calendar – 22 Modern and Classic Experience in SharePoint Online


Earlier this year, Microsoft announced and rolled out the modern experience for lists. They didn’t stop at that, but also introduced modern pages and sites. But what are the differences to the ‘classic experiences’, so the type of lists, libraries, pages, and sites that we’ve been using now for many years? How can you switch between these two experiences? And how can you customise the modern experience items? Luckily, there’s some good guidance available from Microsoft.

First of all, it should be noted that the classic experience won’t disappear anytime soon. While Microsoft is rolling out the modern experience, the classic experience will still remain available. However, it should be noted that the functionality set between these two can be quite different. Review the links listed further below for details.

How do I switch between experiences?

For lists and libraries, please have a look at Switch the default experience for lists or document libraries from new or classic.

For modern sites and pages, the situation is a bit more different. You can’t switch a site between the classic and the modern experience. You also can’t upgrade a ‘classic’ site to a ‘modern’ site, however you can enable the corresponding capabilities to make a ‘classic’ site make use of the ‘modern’ features.


What are the differences between ‘classic’ and ‘modern’ lists/libraries?

This article provides some details on which functionality is currently only available in the ‘classic experience’. As you can see, there is quite a range of useful functionality that is not yet in the ‘modern experience’. Note that not all differences are listed. For example, the new smart filters are only available in modern lists. Also, existing customizations will most likely not work anymore.


What kind of customization possibilities do I have for ‘modern experiences’?

Have a look at this great article by Vesa Juvonen, and the other pages mentioned in it.


Office 365 Advent Calendar – 21 Get all Instances of a Web Part in a SharePoint Online Site



Sometimes you may need to figure out where a specific type of web part has been used. Today, I’ll show you how you can check all pages in your Site Pages library for a specific web part.

I’ve had this code for a while as I was looking for some specific web parts (Content Editor) before. Yesterday, I briefly saw a blog post on announcing the deprecation of the Visio Web Access web part on SharePoint Online (as Visio Online is coming!), though that blog post has disappeared again (I suspect we’ll see it again soon). So I decided to modify it slightly to look for all instances of Visio Web Access web parts and save the results to a CSV file.


The following code checks all pages in the given site’s Site Pages library for instance of the ‘Visio Web Access’ web part. You could easily modify it to loop through all sites in a site collection, or even all sites within your whole tenant.

$cred = Get-Credential
Connect-PnPOnline -Url -Credentials $cred

$visioWebParts = @()

$ctx = Get-PnPContext
$lists = (Get-PnPWeb).Lists
#Note: I'm not accessing the Site Pages library by URL or title, as I had sites in different languages
$sitePages = $lists.EnsureSitePagesLibrary()

$pages = Get-PnPListItem -List $sitePages[0].Id
foreach($page in $pages) {
	$webparts = Get-PnPWebPart -ServerRelativePageUrl $page.File.ServerRelativeUrl
	foreach($webpart in $webparts) {
		$wpxml = Get-PnPWebPartXml -ServerRelativePageUrl $page.File.ServerRelativeUrl -Identity $webpart.Id
		# The Visio Web Access part is referenced in the XML definition of the web part
		# with type Microsoft.Office.Visio.Server.WebControls.VisioWebAccess. Change this for
		# any other types of web parts you want to find 
		if($wpxml -Match '<type name="Microsoft.Office.Visio.Server.WebControls.VisioWebAccess,') {
			$visioWebParts += New-Object PSObject -Property @{
				'PageUrl' = $page.File.ServerRelativeUrl
				'WebPartTitle' = $webpart.WebPart.Title

$visioWebParts | select PageUrl, WebPartTitle | Export-Csv C:\visio.csv

Office 365 Advent Calendar – 20 Get notified about new SharePoint Online Site Collection Administrators


If your organization provides business users with the possibility to manage sites themselves by assigning them site collection administrator rights, you may want to know if they add anyone else as site collection administrator. While you could run a script regularly to retrieve a list of all site collection administrators on all sites and then compare this data with previous information, there is also an alternative that you can use. Office 365 provides you with the option to get alerted when someone gets added as a site collection administrator.

First, open the Office 365 Security & Compliance portal. Select Alerts and ‘Manage alerts’, then click on ‘Add an alert’ in the right content pane


Setting up the alert is fairly easy. Start by specifying a name and optionally a description. The most important setting is “Send this alert when…” where you specify which activities you want to get alerted on. Select ‘Added site collection admin’ here. Sadly, there is no way to monitor removal of site collection admins.

Lastly, specify who should be alerted, and save your alert.

Please note the following: “It can take up to 30 minutes or up to 24 hours after an event occurs for the corresponding audit log entry to be displayed in the search results.” (Search the audit log in the Office 365 Security & Compliance Center). This means that notifications are not immediate, but may take some time until the corresponding event appears in the audit logs and the alert gets triggered.

Office 365 Advent Calendar – 18 Adding a Web Part to a SharePoint Online Page with PowerShell



You’ve got a Web Part which you want to add to one, but more likely multiple web part pages in a SharePoint Online Site. Or maybe even on multiple pages across multiple sites. You could do this manually, but adding the same web part with the same configuration over and over again may not be a productive use of it. Let’s make use of PowerShell to do this!


The code itself is quite simple, as the PnP PowerShell cmdlets contain a cmdlet named Add-PnPWebPartToWebPartPage. All you need is the page where you want to add the web part as well as the XML of your web part.

Let’s say you’ve got a “This SharePoint site will be archived on 31/12/2016” message which you want to add to the default.aspx page on a list of sites. You can create the Web Part with the desired message and formatting once, then export it:

Once exported, you can use the following script to add the same web part to all default.aspx pages on all sites listed in SitesToArchive.csv. The Web Part will be added to the Zone named “Left” with an index of 0, that is at the top of it:

$cred = Get-Credential
$websToUpdate = Import-Csv SitesToArchive.csv
foreach($web in $websToUpdate) {
	write-host "Connecting to $($web.Url)"
	Connect-PnPOnline -Url $web.Url -Credentials $cred
	Add-PnPWebPartToWebPartPage -ServerRelativePageUrl "default.aspx"`
	-Path "c:\users\rmodery\Desktop\ArchiveMessageWebPart.dwp" -ZoneId "Left" -ZoneIndex 0


How about wiki pages in SharePoint Online? Add-PnPWebPartToWikiPage may help you here!

Office 365 Advent Calendar – 17 Overview of the new OneDrive for Business Admin Portal


At the Microsoft Ignite conference in September, it was announced that OneDrive for Business would get its own Admin Portal in Office 365. Yesterday, Microsoft finally made it available, even though it’s in Preview at the moment. See the official announcement here, and access it at

So what does it include? Some existing settings, such as configuring your sharing settings, as well as new settings like the possibility to restrict access to certain IP addresses. Here’s a detailed overview of the current status:



In this section, you can control if sharing with externals is enabled, and if so, with which configuration. These are the same settings which currently exist in the SharePoint Admin Portal.



In the Sync section, you can define a few settings related to the OneDrive for Business client. First of all, whether user can install the client from the website. You can also define if syncing should be restricted to PCs that are joined to specific domains –  to retrieve the GUIDs you need to enter here follow these instructions. This setting is extremely helpful if you want to ensure that your users don’t use for example their personal PCs to synchronise content. Lastly, you can also restrict which file types can be synced.



This is a simple section – define how muich default storage is available for new users, and define how long you want to keep files in a user’s OneDrive after his account is scheduled for deletion. This setting could previously be set via PowerShell only, and allows for values between 30 and 3650 days (yes, the maximum retention period is 10 years).


Device access

If you want to control from which devices and locations content can be accessed, you can define specific IP addresses here or define your Intune settings.


Lastly, the Compliance section provides you with a range of links to existing pages where you can define auditing, DLP, retention, eDiscovery, and Alerts settings.

Office 365 Advent Calendar – 16 Adoption Guide for Office 365


Having access to a technology is not enough, the users and the company should see some benefits from it in order to achieve some value out of this investment. This is something that is often forgotten when we work with SharePoint or Office 365 – the wonderful new feature that was just released might not be useful to your employees at all, and trying to force it on them will make things rather worse than better. Additionally, just because a new functionality could be used for a lot of purposes doesn’t mean that your users will actually do so – they may not know how to use it, they may have a preference for a familiar, older technology, or they may not even know what the new functionality can do for them. This is where adoption comes in.

“At Microsoft, we want you to make the most of your investment in Office 365 and planning is key to achieving this.”

The statement above is taken directly from Microsoft’s brand new Office 365 Adoption Guide which they quietly released this week. While the guide feels a little bit like marketing material for Microsoft’s FastTrack, it is still full of good content that you can leverage. The purpose of this document is to guide you along the way to make the rollout of Office 365 in your organization a success.

Microsoft divides their adoption guide into three different phases:

Phase 1: Envision Identify key stakeholders, assemble your team, identify and prioritize Business Scenarios, and collaborate with key stakeholders to create a Success Plan for the adoption of Office 365 in your organization. Phase 2: Onboard Execute your Success Plan to onboard your organization to the technology, and help your people work in a new way. Start with an early adoption program and then expand to cover your whole organization. Phase 3: Drive Value Realize ongoing value from your Office 365 investment by continuing to boost user engagement and drive adoption. Manage and prepare for change by measuring and sharing the success of Office 365, while understanding where to iterate for future improvements

Obviously, this is based on and influenced by their FastTrack experiences:



I recommend that you review the adoption guide even if you’re not planning to use FastTrack – the content inside will surely help you to plan how you can tackle Office 365 adoption. Additionally, the Productivity Library provides a nice range of examples on how Office 365 can be used in various industries and scenarios. Another resource to review if you want to hold a discussion with business stakeholders on the potential benefits they may see.