Monday, May 15, 2017

Saving a collection of images from PowerApps to SharePoint using Microsoft Flow

Yesterday I wrote about how you can save an image from PowerApps to a SharePoint library using Microsoft Flow – after reading about Paul Culmsee’s approach using Azure Functions. Paul challenged me to save a whole collection of images, and not only one image, so here goes!

This solution loops the images in Flow, but you could easily have looped them in PowerApps as well, saving one image at a time upon submit.

I’ll start with the Flow, and this time we’ll use one input only. And this is where it get’s a bit nasty. A Flow can only accept strings as input from PowerApps, not a collection of items. To overcome this, I’m concatenating the data into one string in the PowerApp, and then splitting the data in the Flow.

The data being passed in looks like this:

Monday, May 15, 2017LT.jpg|data:image/jpeg;base64,<data>#
Monday, May 15, 2017AB.jpg|data:image/jpeg;base64,<data>#


First is the filename with a readable date (borrowed code from Paul), next a pipe | to separate the filename from the base64 encoded image. And at the end a hash # to separate each image from each other.

In Microsoft flow create a new Flow named “Save Images”

Add a “PowerApps” step, followed by a “Compose” step.

image

In the input field for the “Compose” step pick “Ask in PowerApps”. Next change the formula to:

"@split(triggerBody()['Compose_Inputs'], '#')"

and rename the compose action to “Split images” for readability. This will split the incoming string on the character #, creating an array with one item per image.

Note: Remember to include the double quotes around the formula.

image

Below the “Split Images” action, add a “Filter array” action. This action is used to remove the empty element we get at the end when creating the data string in PowerApps. Use the Output from the previous action as the “From” field, click “Edit in advanced mode” and use the following formula to remove empty values. item() will yield one item in an array.

@not(equals(item(), ''))

Note: No quotes here :)

image

Below the Filter array action, add a for each item loop, so that we can process each image sent over.

image

Pick the Body output from the Filter array step as the input in the for each.

image

In the for each action, add two compose steps. Rename one to “Filename” and the other to “File content”.

For filename add the expression:
"@split(item(),'|')[0]"

For file content add the expression:
"@base64ToBinary(replace(split(item(),'|')[1],'data:image/jpeg;base64,',''))"
"@dataUriToBinary(split(item(),'|')[1])"

The effect is that for each item, split on the character |, and assign the left side as the filename, and base64 decode the right side of the split.
Note: Remember to include the double quotes around the formulas.
image

The last part is adding an action step to create a file in SharePoint. Pick a site and library, and assign the values from the previous compose steps.

image

Setting up the PowerApp

image

For the purpose of this demo I have four controls on in my app. A camera control, a button to send it all to flow, a clear button and a gallery to see the images taken, and which will be sent over to SharePoint.

To grab an image I have the following formula on the OnSelect property of the camera control:

Collect(PictureColl,Camera1.Photo)

This stores each image in a collection named PictureColl.

The gallery control’s “Items” property it bound to the PictureColl collection.

The “OnSelect” property of the submit button contains the following formula:

ForAll(PictureColl,Collect(SubmitData, { filename: Concatenate(Text( Now(), DateTimeFormat.LongDate ),Mid("0123456789ABCDEFGHIJKLMNOPQRTSTIUVWXYZ", 1 + RoundDown(Rand() * 36, 0), 1),Mid("0123456789ABCDEFGHIJKLMNOPQRTSTIUVWXYZ", 1 + RoundDown(Rand() * 36, 0), 1),".jpg"), filebody: Url }));

SaveImages.Run(Concat(SubmitData, filename & "|" & filebody & "#"))


The formula creates random filenames with dates and assigns the filename and image date to a collection named SubmitData (borrowed from Paul’s post). Then the file data is concatenated using a pipe | between the filename and the file contents, and concatenated using a hash # between each image. This is then passed into the SaveImages flow I have added to the PowerApp.

If you run the PowerApp (except in the Windows desktop version), tap the camera a couple of times to grab images, and then hit the submit data, you should end up with some images in your library.

image

See my previous post about on how to add a Flow to the PowerApp using PowerApps studio.

17 comments:

  1. Out-frigging-standing! Lots of fruitful possibilities with Flow and PowerApps from this recent flurry of work...

    ReplyDelete
  2. I'm following your example, however still getting an error on uploading just the image to the library:
    "nvalidTemplate. Unable to process template language expressions in action 'File_Content' inputs at line '1' and column '1552': 'The template language function 'dataUriToBinary' expects its parameter to be formatted as a valid data URI. The provided value 'blob:B24CAAD2-A8B2-4696-9C85-838E40879996' was not formatted correctly. Please see https://aka.ms/logicexpressions#dataUriToBinary for usage details.'."

    ReplyDelete
    Replies
    1. Ok, I was using the desktop powerapps so went back to the article you reference (http://www.cleverworkarounds.com/2017/05/14/a-clever-workaround-to-saving-photos-to-sharepoint-from-powerapps/) and he mentions the following:
      "Important! Before we go any further, I strongly suggest you use the PowerApps web based authoring environment and not the desktop application. I have seen a problem where the desktop application does not encode images using the Data URI format, whereas the web based tool (and PowerApps clients on android and IOS) work fine"

      Delete
  3. This is all very cool, thank you for posting, definitely learning how to implement the Azure functions will be key to maximize this Microsoft "NO CODE" solution.

    ReplyDelete
    Replies
    1. Glad you got it working - getting that issue with PowerApps desktop version :)

      Delete
  4. Excellent post. Works really well for me on PowerApps web studio and IOS device.

    ReplyDelete
  5. Great post! I am struggling with "SaveImages.Run". I receive a "Invalid number of arguments" error message when I add arguments. It expects no arguments "SaveImages.Run()" to remove the error message. What am I doing wrong and why does it not allow me to send any arguments?

    ReplyDelete
    Replies
    1. Seems you flow is not asking for input? Or that you haven't refreshed the flow in your powerapp to a version which has this.

      Delete
    2. I have found the issue. Instead of choosing "Ask PowerApps" in the Split images compose step, I just added the expression. This is why it did not accept parameters on PowerApps.

      Delete
  6. I believe that it could be the flow not asking for an input. Where can I set that on the flow?

    ReplyDelete
  7. Hi, your code works very well, thanks, I'm trying to get a little further, but seems to be blocked.
    In my Camera OnSelect, I have Collect(Collection1,{Photo:Camera1.Photo,Position:Dropdown1.Selected.Value})
    How do I get the ThisItem.Url in the gallery then ? I only have access to Photo and Position, but not Url anymore.
    My datasource on the gallery is still Collection1.
    Thanks !

    ReplyDelete
    Replies
    1. Can you access ThisItem.Photo.Url? If not use two collections and get the URL from the other based on the index of the first.

      Delete
    2. ..or assign Url:Camera1.Photo.Url in the collect.

      Delete
  8. Hi, your code works very well, thanks, I'm trying to get a little further, but seems to be blocked.
    In my Camera OnSelect, I have Collect(Collection1,{Photo:Camera1.Photo,Position:Dropdown1.Selected.Value})
    How do I get the ThisItem.Url in the gallery then ? I only have access to Photo and Position, but not Url anymore.
    My datasource on the gallery is still Collection1.
    Thanks !

    ReplyDelete
  9. Im getting the following error,

    The template validation failed: 'The template action 'Split_Image' at line '1' and column '1139' is not valid: "The template language expression 'split(triggerBody()@{triggerBody()['SplitImage_Inputs']}, '#')' is not valid: the string character '@' at position '19' is not expected.".'.

    ReplyDelete
    Replies
    1. Did you include the double quotes around it?

      Delete