Azure Functions:– Using PowerShell to automatically Tweet a random Blog Post from Table Storage

I have now got 30+ Blog posts, all of which I would want to share via Twitter on a repetitive basis, say for example 3 times a week.

Yes, I could use TweetDeck or potentially another third-party tool but this is manual configuration as to what Tweet to send and when, I just want to post a random blog post several times a week without any interaction.

For this, I have decided to create an Azure Function and post a random Blog Post from data stored in Azure Table Storage. For both, it is literally pence per month to have both running – sounds good? Lets get configuring!

To start, familiarise yourself with three previous Blog Posts I created in line with this:-

As mentioned, Azure Table storage will be used to hold some data that I will pull from the WordPress.com API using Powershell. Azure Table storage is a No-SQL keyvalue store offering, super cheap! Microsoft snippet describes it well “Use Azure Table storage to store petabytes of semi-structured data and keep costs down”

I will be created two Functions:-

  • Function1: AddBlogPosts
  • Function2: BlogPostToTwitter

What does each Function do?

Function1: AddBlogPosts

  • Function will be ran weekly
  • Uses WordPress API to take blog data and store in Azure Table Storage
  • Blog data will be both URL and Title, allowing the second function to take this data and upload a random Tweet to Twitter
  • Additional columns will be populated with each new blog post being updated to a row “Tweeted = No” and “PartitionKey = twitterPartKey”
  • On a weekly basis reviews what is on each TableRow against current about of WordPress blogs, if there is a difference it will add in the missing.

Function2: BlogPostToTwitter

  • Function will be ran three times a week (Monday, Wednesday and Friday)
  • Takes a random row from the Table Storage and uses this to post a blog to Twitter
  • Each time it does this, it updates the column in row Tweeted to Yes, if no available rows left with No – it will update all rows to No and continue with Tweeting
  • Posts Tweet in format: “From the Blog:”WordPress Blog Post Title,
    WordPress Blog Post URL,”#Microsoft #Azure #AzureFamily #Blog

Some Pre-Requisites

  1. Create a Resource Group & Storage Account Table
    #Create Resource Group & Storage Account Table
    $ResourceGroupName = "tamopsFunctionsTwitter"
    $StorageAccountName = "tamopsfunctionstwittersa"
    $TableName = "tamopsFuncTableBlog"
   
    
        az group create --name $ResourceGroupName --location eastus
        az storage account create --name $StorageAccountName --resource-group $ResourceGroupName --location eastus --sku Standard_LRS

        $saContext = (Get-AzureRmStorageAccount -ResourceGroupName $ResourceGroupName -Name $StorageAccountName).Context
        New-AzureStorageTable –Name $tableName –Context $saContext

2. Create a Service Principle within AzureAD to reference the Azure Table Storage inside Azure Function, for this example I have also applied Contributor role to the service principle

    #Create a service principle within AzureAD
    $AzureAdAppPassword = ConvertTo-SecureString '<PASSWORD>' -AsPlainText -Force
    $AzureAdApp = New-AzureRmADApplication -DisplayName "TamOpsFuncADApp" -Password $AzureAdAppPassword -HomePage "https://www.thomasthornton.cloud" -IdentifierUris "https://www.thomasthornton.cloud"


    # Create new service principle and apply permissions

        New-AzureRmADServicePrincipal -ApplicationId $AzureAdApp.ApplicationId | New-AzureRmRoleAssignment -RoleDefinitionName Contributor -ServicePrincipalName $AzureAdApp.ApplicationId.Guid

Next create the Function App with runtime PowerShell

    # Create Function App Service and change to version 1

    $FunctionName = "tamopsfunctwitter"
    $ResourceGroupName = "tamopsFunctionsTwitter"
    $StorageAccountName = "tamopsfunctionstwittersa"
    $region = "eastus"

        az functionapp create -n $FunctionName -g $ResourceGroupName --consumption-plan-location $region --storage-account "$StorageAccountName" --runtime powershell

Now update the Function App Application settings with a list of environment variables that will be referenced in both Functions

    $ResourceGroupName = "tamopsFunctionsTwitter"
    $FunctionName = "tamopsfunctwitter"

    $AppSettingsHash = @{
    ResourceGroupName = $ResourceGroupName
    StorageAccountName = "tamopsfunctionstwittersa"
    AzureTableName = "tamopsFuncTable"
    partitionKey = "twitterPartKey"
    BlogSiteName = "thomasthornton.cloud"
    twitterID= "tamstar1234"
    twitterAccessToken = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
    twitterAccessTokenSecret = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
    twitterAPIKey = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
    twitterAPISecret = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
    AzureADTenantId = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
    AzureADAppUser = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
    AzureADAppPassword = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
    AzureTableAzureTablePartitionKey = "twitterPartKey"
    }

    foreach ($AppSettingAdd in $AppSettingsHash.GetEnumerator()) {
    az functionapp config appsettings set --resource-group $ResourceGroupName --name $FunctionName --settings "$($AppSettingAdd.Name) = $($AppSettingAdd.Value)"
    }

Twitter access for the below variables are generated by created by Twitter Access Tokens, I used this tutorial to create an app within the Twitter API

    twitterAccessToken = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
    twitterAccessTokenSecret = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
    twitterAPIKey = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
    twitterAPISecret = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"

The AzureAD References are:-

  • AzureADTenantId :- The Azure AD Tenant ID of the subscription
  • AzureADAppUser :- Created above -IdentifierUris
  • AzureADAppPassword :- Created above
    AzureADTenantId = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
    AzureADAppUser = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
    AzureADAppPassword = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"

Azure Storage Tables use Partition Keys to assist with uniquely identifying every entity within a table. For this, I have used “twitterPartKey”

   AzureTableAzureTablePartitionKey = "twitterPartKey"

You can see within the Function App settings these environment variables

Lets create the Functions, starting with AddBlogPosts the function app structure within the app service will be (each function will have its own folder):

tamopsfunctwitter
| –AddBlogPosts
| | -function.json
| | -AddBlogPosts.ps1
| | -Modules
| –BlogPostToTwitter
| | -function.json
| | -BlogPostToTwitter.ps1
| | -Modules

AddBlogPosts Function

Both Functions will be the same format as below, modules can be downloaded from GitHub Repo

function.json (Note:- cron configured to run weekly on Monday at 9.30am)

{
  "bindings": [
    {
      "name": "myTimer",
      "type": "timerTrigger",
      "direction": "in",
      "schedule": "0 30 9 * * 1"
    }
  ],
  "disabled": false
}

AddBlogPosts.ps1

    $Username = $env:AzureADAppUsername
    $AzureADTenantId  = $env:AzureTenantId
    $AzureADAppPassword = ConvertTo-SecureString $env:AzureADAppPassword -AsPlainText -Force
    $Credential = new-object -typename System.Management.Automation.PSCredential -argumentlist $Username, $AzureADAppPassword

        Login-AzureRmAccount -ServicePrincipal -Credential $Credential -TenantId $AzureADTenantId
 
    $BlogSiteName = $env:BlogSiteName
    $ResourceGroupName = $env:ResourceGroupName
    $StorageAccountName = $env:StorageAccountName
    $AzureTableName = $env:AzureTableName
    $AzureTableAzureTablePartitionKey = $env:AzureTableAzureTablePartitionKey

#Get Storage Context to add Blog URL/details to
    $saContext = (Get-AzureRmStorageAccount -ResourceGroupName $ResourceGroupName -Name $StorageAccountName).Context   
    $TableContext = Get-AzureStorageTable -Name $AzureTableName -Context $saContext


    ## Adds Blogs to Table Storage
    $BlogPostsSearch = Invoke-RestMethod -uri "https://public-api.wordpress.com/rest/v1/sites/$BlogSiteName/posts/?number=100"

        foreach ( $BlogPostsAdd in $BlogPostsSearch.posts) {

            $urlcheck = $BlogPostsAdd.URL
            $CheckURLColumn = Get-AzureStorageTableRowByCustomFilter -table $TableContext -customFilter "(URL eq '$urlcheck')"

            if ($CheckURLColumn -eq $null){
            
            Write-Output "Adding Blog Post: $urlcheck"
            Add-StorageTableRow -table $TableContext -partitionKey $AzureTableAzureTablePartitionKey -rowKey ([guid]::NewGuid().tostring()) -property @{"Update"=$BlogPostsAdd.Title;"URL"=$BlogPostsAdd.URL;"Tweeted"="No"}

            }
        }

Zip the folder and upload the Function App Service

    # Compress folder and deploy to Function App

    $FunctionName = "tamopsfunctwitter"
    $ResourceGroupName = "tamopsFunctionsTwitter"
    
    Compress-Archive -Path * -DestinationPath AddBlogPosts.zip

    az functionapp deployment source config-zip -n $FunctionName -g $ResourceGroupName --src ./AddBlogPosts.zip

BlogPostToTwitter Function

function.json (Note:- cron configured to run Monday,Wednesday and Friday at 11am)

{
   "bindings": [
     {
       "name": "myTimer",
       "type": "timerTrigger",
       "direction": "in",
       "schedule": "0 11 * * 1,3,5"
     }
   ],
   "disabled": false
 }

BlogPostToTwitter .ps1

    $AzureADTenantId  = $env:AzureTenantId
    $Username = $env:AzureADAppUsername
    $AzureADTenantId  = $env:AzureTenantId
    $AzureADAppPassword = ConvertTo-SecureString $env:AzureADAppPassword -AsPlainText -Force
    $Credential = new-object -typename System.Management.Automation.PSCredential -argumentlist $Username, $AzureADAppPassword

        Login-AzureRmAccount -ServicePrincipal -Credential $Credential -TenantId $AzureADTenantId

            
    $ResourceGroupName = $env:ResourceGroupName
    $StorageAccountName = $env:StorageAccountName
    $AzureTableName = $env:AzureTableName
    $twitterID = $env:twitterID
    $twitterAccessToken = $env:twitterAccessToken
    $twitterAccessTokenSecret = $env:twitterAccessTokenSecret
    $twitterAPIKey = $env:twitterAPIKey
    $twitterAPISecret = $env:twitterAPISecret

    #Get Storage Context to add Blog URL/details to
    $saContext = (Get-AzureRmStorageAccount -ResourceGroupName $ResourceGroupName -Name $StorageAccountName).Context   
    $TableContext = Get-AzureStorageTable -Name $AzureTableName -Context $saContext


    #Check for Available Tweets

    $AvailableTweets = Get-AzureStorageTableRowByCustomFilter -table $TableContext -customFilter "(Tweeted eq 'No')"

        if ($AvailableTweets -eq $null){

            $AvailableTweetsCHECK = Get-AzureStorageTableRowByCustomFilter -table $TableContext -customFilter "(Tweeted eq 'Yes')"

        foreach ($tweetedReplace in $AvailableTweetsCHECK) {

            "No Available Tweets, refreshing tweeted column to No"
            $tweetedReplace.tweeted = "No"
            $tweetedReplace | Update-AzureStorageTableRow -table $TableContext
           
            }
        }

    #Blog Post To Twitter
    $TweetUpdate = Get-AzureStorageTableRowAll -table $TableContext 

    $TweetToSend = $TweetUpdate | ?{$_.tweeted -ne 'Yes'} | get-random 

    $IDtoChange = $TweetToSend.URL
    $IDChange = Get-AzureStorageTableRowByCustomFilter -table $TableContext -customFilter "(URL eq '$IDtoChange')"
    $IDChange.tweeted = "Yes"
    $IDChange | Update-AzureStorageTableRow -table $TableContext
    
    # Setup Twitter OAuth Hashtable
    $TwitterOAuthConfig = @{ 'ApiKey' = $twitterAPIKey; 'ApiSecret' = $twitterAPISecret; 'AccessToken' = $twitterAccessToken; 'AccessTokenSecret' = $twitterAccessTokenSecret}

        if($TwitterPost.length -le 230) {
    
            $CombineTweet = ("From the Blog:",$TweetToSend.Update,$TweetToSend.URL,"#Microsoft #Azure #AzureFamily #Blog") 
   
            Write-Output "Posting Tweet: $CombineTweet" 
            
            $Parameters = @{ 'status' = $CombineTweet} 

            Invoke-TwitterRestMethod -ResourceURL 'https://api.twitter.com/1.1/statuses/update.json' -RestVerb 'POST' -Parameters $Parameters -OAuthSettings $TwitterOAuthConfig


    }

You should now have two folders “AddBlogPosts” and “BlogPostsToTwitter”, create a .zip with both these folders inside

Please note that when you upload this to a function using below CLI, whatever is not in the folder will be removed.

# Compress folder and deploy to Function App

$FunctionName = "tamopsfunctwitter"
$ResourceGroupName = "tamopsFunctionsTwitter"

az functionapp deployment source config-zip -n $FunctionName -g $ResourceGroupName --src ./upload.zip

Once upload has been completed, these functions will now be viewable within Azure Portal

Lets run a test!

Running Function: AddBlogPosts….
Select AddBlogPosts Function and select Run and view Log Output:-

Review the Azure Storage Table, you can see it is now populated

That’s it, you now have Two Azure Functions to post a random Blog post from your WordPress Blog to Twitter!

Running Function: BlogPostToTwitter….
Select BlogPostToTwitter Function select Run and view Log Output:-

Lets check Twitter

Now check the Table Row to see if Tweeted has been updated to Yes

All working 🙂

Success!


You will have two Functions to assist you in posting your WordPress Blogs!


No editing is required for the Functions to work, all you need to ensure is all your Application settings are correct

GitHub Repo URL

Thanks for MeshkDevs for the Twitter PowerShell Module

5 comments

  1. Hello again Thomas. I spent all day yesterday trying to replicate this Twitter function – without luck.Would you be able to share the contents of the hosts.json file from your function with me? Cheers. Jason.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s