I love the images from Bing. I want them as my desktop background images, and that is possible by installing the Bing Desktop application. But I don’t want to install another app just for that and I love to build stuff myself. So I decided to create a small workflow that gets the Bing image of the day and puts it somewhere that my desktop background can use. In this post, I show you what I did.

The basic flow that I’ve build is this:

  • An Azure Logic App that runs every 24 hours and kicks off an Azure Function App
    • Download the Bing image of the day with an Azure Function App
    • Put the Image in OneDrive
    • Windows background is set to pick up images from OneDrive and show them in a slideshow

Please note that I use Windows 10.

Azure Logic Apps and Function Apps

If you are not familiar with Azure Logic Apps or Azure Function Apps, this might help:

Azure Logic Apps

These run in Azure and can be used to create workflows. A Logic App contains a workflow that consists of steps and conditions. Everything is based on calling API’s and weaving them together into a workflow. A step could be something like: Upload a file to OneDrive, which uses the OneDrive API.

Logic Apps connects to many API’s out of the box, like: GitHub, OneDrive, Azure Storage (Blob, Table), DropBox, Facebook, Google Drive, Instagram, CRM and so on. You can also write your own API’s and use them with Logic Apps. And last but not least, you can use Function Apps in Logic Apps.

Azure Function Apps

These are sometimes called Azure Functions as well and that is confusing. Here is how I see it: a Function App is an app just like a Web App. A Fuction App can contain one or more Azure Functions.

You can use Function Apps to execute small pieces of code that optionally have input and output parameters. An example of a Function App is a small piece of code that is triggered every time a new blob is uploaded to Azure Blob Storage and that resizes the image and puts it back into Blob Storage. Its similar to what Azure WebJobs do, only now your code is independant from anything else; you don’t have to have a Web App or something else to use as a “server to run” in. Function Apps are an evolution of Azure WebJobs.

Bing Images

So how do I get the images from Bing? There is actually an API that provides the URL’s to the images:

http://www.bing.com/HPImageArchive.aspx?format=xml&n=1&mkt=en-US

You can play with it by tweaking the query string:

  • format
    • You can use xml, js for JSON or rss for RSS
  • n=1
    • This tells the API how many images you want. n=5 returns 5 images. I think this is restricted to a maximum of 8 images
  • mkt
    • This is your region. For instance en-US or nl-NL. I haven’t found that it produces diffferent results, but people online seem to get different images in different regions

When you call the webservice, you get a result back like this:

   1: {
   2: images: [
   3: {
   4: startdate: "20160825",
   5: fullstartdate: "201608250700",
   6: enddate: "20160826",
   7: url: "/az/hprichbg/rb/GreatSandDunes_EN-US10806878209_1920x1080.jpg",
   8: urlbase: "/az/hprichbg/rb/GreatSandDunes_EN-US10806878209",
   9: copyright: "Great Sand Dunes National Park and Preserve, Colorado (© Ian Shive/Tandem Stills + Motion)",
  10: copyrightlink: "http://www.bing.com/search?q=Great+Sand+Dunes+National+Park+and+Preserve+Bing+road+trip&form=hpcapt&filters=HpDate:%2220160825_0700%22",
  11: wp: true,
  12: hsh: "95a15b330b2c60393b8f8581fec4343d",
  13: drk: 1,
  14: top: 1,
  15: bot: 1,
  16: hs: [ ]
  17: }
  18: ],

On line 7 you can see the URL to the image that you want. You can alter this URL to reflect the image size that you need, currently, this URL returns an image with a resolution of 1920x1080.

Function App

Knowing where to get the images, I wrote some code in an Azure Function App that retrieves the Image’s URL to be processed further.

To be able to make a function App work in Logic Apps, for now your Function Apps have to be of the type Generic Webhook or HttpTrigger.

I wrote this piece of code for the Function App in the file run.csx:

run.csx

   1: using System;
   2: using System.Net;
   3: using System.Net.Http;
   4: using System.Threading.Tasks;
   5: using Newtonsoft.Json.Linq;
   6: using System.Text;
   7:  
   8:  
   9: public static async Task<HttpResponseMessage> Run(HttpRequestMessage req)
  10: {
  11:     
  12:     string url = await GetBingImageUrlAsync();
  13:  
  14:  
  15:     HttpResponseMessage res = null;
  16:  
  17:     res = new HttpResponseMessage(HttpStatusCode.OK)
  18:     {
  19:         //Content = new StringContent(url)
  20:         Content = new StringContent(url)
  21:     };
  22:  
  23:    return res;
  24: }
  25:  
  26: static async Task<string> GetBingImageUrlAsync()
  27: {
  28:     //use the HTTPclient, so that it is disposed after usage
  29:     using (HttpClient client = new HttpClient())
  30:     {
  31:         //same goes for the HTTPresponse, make sure resources are cleaned after usage
  32:         using (HttpResponseMessage response = await client.GetAsync("http://www.bing.com/HPImageArchive.aspx?format=js&idx=0&n=1&mkt=nl-NL"))
  33:         {
  34:             //and even the content is IDisposable, so use a using
  35:             using (HttpContent content = response.Content)
  36:             {
  37:                  //read the string from the response   
  38:                 string result = await content.ReadAsStringAsync();
  39:  
  40:                 //parse it using JSON.NET in a dynamic as we don't know exactly what is coming back
  41:                 dynamic jsonresult = JValue.Parse(result);
  42:                 
  43:                 return "http://www.bing.com/" + jsonresult.images[0].url;                       
  44:  
  45:             }
  46:         }
  47:     }
  48: }

This does the following:

  • It uses HttpClient to call the Bing URL for the JSON format (line 32)
  • It reads the response content as a string (line 38)
  • Next, it parses the JSON in the string result to a dynamic object using JValue, which is part of the Newtonsoft.Json library (line 41)
    • This creates an object that you can navigate through as you would the JSON tree. I use the dynamic object, because I don’t know for sure what the format of the reponse is
  • Return the URL of the image as string (line 43)
  • Wrap the URL up in a HttpResponse message and return it (line 20)

Function Apps can use libraries from NuGet. In this example, I use the Newtonsoft.Json package. You can import NuGet projects into your Function by adding a project.json file to the Function. Mine looks like this:

project.json

   1: {
   2:   "frameworks": {
   3:     "net46":{
   4:       "dependencies": {
   5:         "Newtonsoft.Json": "9.0.1"
   6:       }
   7:     }
   8:    }
   9: }

Logic App

The Function App does the work, but the Logic App ties it together with things like OneDrive.

You can create and manage Logic Apps in the Azure Portal. Mine looks like this:
It does the following:

  1. Run every 24 hour. The timer Trigger takes care of that
  2. Call the Function App
  3. Write the result, based on the output of the Function App

Lets look at how to call the Function App from the Logic App.

When you add a new step to a logic app, you can choose from a number of Connectors from diffferent categories, including from Azure Functions in the same region:

Note that it says that it can choose from Functions in the same region. That means that your Function App must be deployed within the same Azure *region *as the Logic App.

Next, you can choose which Function App you want to select:

After that, you can select the Azure Function that you want to call or wite a new one straight from the Logic App.

Notice that Logic Apps also keeps to the terminology of Function Apps and Functions.

In the next step, you can provide input parameters for the Function, as you are going to fire a HttpRequest at it.

We do not use any input parameters in our Function, but we do have to specify empty brackets { } to make it work.

This is because the Function will try to read the input as a JSON string and can’t seem to do that if nothing is passed, surely this will be solved in the future.

Thats it, this step now calls the Function every 24 hours. Now, lets look at how to deal with the result of the Function.

I use the OneDrive copy file connector for this. this can copy a file to a destionation based upon a URL. This looks like this:

The source URL comes directly from our Function output. I selected ‘Body’ but it renamed the parameter to key-body-output. The designer view provides easy ways to select the output from a previous step.

The destination file path is the path in my OneDrive. In my case it is Wallpapers*imagename*.jpg. To make this work, I had to remove the part of the URL that contains http://www.bing.com/az/hprichbg/rb/. Luckily, Logic Apps provides lots of scripting options like string operations that you can use to programmatcally do things.

This results in a the following code view of my Logic App:

   1: {
   2:     "$schema": "https://schema.management.azure.com/providers/Microsoft.Logic/schemas/2016-06-01/workflowdefinition.json#",
   3:     "actions": {
   4:         "GetBingImageOfTheDay": {
   5:             "inputs": {
   6:                 "body": {},
   7:                 "function": {
   8:                     "id": "/subscriptions/987bc0a8-a060-494f-bda2-09ae4f25cb5b/resourceGroups/bingimages/providers/Microsoft.Web/sites/bingimagedownloader/functions/GetBingImageOfTheDay"
   9:                 }
  10:             },
  11:             "runAfter": {},
  12:             "type": "Function"
  13:         },
  14:         "Write_image_to_OneDrive": {
  15:             "inputs": {
  16:                 "host": {
  17:                     "api": {
  18:                         "runtimeUrl": "https://logic-apis-westeurope.azure-apim.net/apim/onedrive"
  19:                     },
  20:                     "connection": {
  21:                         "name": "@parameters('$connections')['onedrive']['connectionId']"
  22:                     }
  23:                 },
  24:                 "method": "post",
  25:                 "path": "/datasets/default/copyFile",
  26:                 "queries": {
  27:                     "destination": "@{ concat('Wallpapers\\', replace(body('GetBingImageOfTheDay'), 'http://www.bing.com//az/hprichbg/rb/', ''))}",
  28:                     "overwrite": true,
  29:                     "source": "@{body('GetBingImageOfTheDay')}"
  30:                 }
  31:             },
  32:             "runAfter": {
  33:                 "GetBingImageOfTheDay": [
  34:                     "Succeeded"
  35:                 ]
  36:             },
  37:             "type": "ApiConnection"
  38:         }
  39:     },
  40:     "contentVersion": "1.0.0.0",
  41:     "outputs": {},
  42:     "parameters": {
  43:         "$connections": {
  44:             "defaultValue": {},
  45:             "type": "Object"
  46:         }
  47:     },
  48:     "triggers": {
  49:         "Run_every_24_hours": {
  50:             "recurrence": {
  51:                 "frequency": "Hour",
  52:                 "interval": 24,
  53:                 "timeZone": "Central European Standard Time"
  54:             },
  55:             "type": "Recurrence"
  56:         }
  57:     }
  58: }

Desktop background

The last thing that I had to do was to point my Windows Background settings to use the Wallpapers folder in OneDrive.

You can simply do this in the Configuration/Personalization/Background section like this:

Enjoy!