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.

37 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
    3. Facing same issue please elaborate how to fix. Thanks

      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
  10. Greetings - I am so close. However, I get an error message under the 'Apply to Each' that states: "ExpressionEvauationFaile. The execution of template action 'Apply_to_each' failed: the result of the evaluation of 'foreach' expression '@outputs("ProcessPhotos') is a type of 'string'. The result must be a valid array.

    Any thoughts on how to resolve? I really appreciate the help.
    Thank you!

    ReplyDelete
  11. Greetings – I am so close. However, I get an error message under the ‘Apply to Each’ that states: “ExpressionEvauationFaile. The execution of template action ‘Apply_to_each’ failed: the result of the evaluation of ‘foreach’ expression ‘@outputs(“ProcessPhotos’) is a type of ‘string’. The result must be a valid array.

    Any thoughts on how to resolve?
    Thank you!

    ReplyDelete
  12. Very informative tutorial ! May I ask, if I am sharing this app to users and for them to upload photos, I would like to store everyone's photos in a common Onedrive for Business folder. How should I configure the flow then ? Thank you !

    ReplyDelete
    Replies
    1. You're in luck as the flow will use the credentials of the connector within the Flow :)

      Delete
    2. Does that mean all the photos uploaded by users will be saved in my Onedrive folder?

      Delete
  13. Hi Mikael. I shared the app with my colleague for him to upload few images. However, all his images were stored in his Onedrive folder instead of mine. Could you tell me how to resolve this ?

    ReplyDelete
    Replies
    1. Hmm.. so you created a Flow using a connector to your OneDrive, then used that Flow in your PowerApp? I haven't tested this, but could be that when using a Flow which has a PowerApp first step, then the user auths to all the connectors - thus using their own credentials. If you consumed the Flow as a custom connector via HTTP - this should not be the case.

      Delete
  14. This is just what I need for a couple of Apps that have been requested. I cannot seem to get around the 'Invalid number of arguments' error no matter what I try. I tried not selecting the Ask powerapps as mentioned above but I still get the error. Can anyone elaborate further what the issue might be?

    ReplyDelete
  15. Just as a side note. A colleague was under the impression that images couldn't be attached as a List attachment. Adding an Attach to list step in the Loop and using the Filename and File content variables works too ;) (It requires a List ID value, I just used a static number 1 in my test)

    ReplyDelete
    Replies
    1. New functionality is added all the time :)

      Delete
    2. I read somewhere they are adding the adding the saving of images as a native function. Could have been on one of your other pages. This flow is doing the trick for me at the moment anyway.

      Delete
    3. I heard from the team in the past that it's underway, but haven't gotten an update in a long while - so who know :)

      Delete
    4. I am struggling to pass the List ID from the Powerapp as a second input in the Split Files Compose action. Can you provide any advice on how I can do this to enable me to dynamically attach the file to the List item please?

      Delete
    5. I got the flow to accept the file info and also the List ID but am unable to reference the Input 'Compose_Inputs_1' in the ID field of the attach Action. Sorry, I am new to powerapps/flow so am on a steep learning curve.

      Delete
    6. Sorry, but haven't time to test this. You might want to ask in https://powerusers.microsoft.com

      Delete
    7. I have signed up on the forum, thanks. The penny has dropped. When I click 'Ask Powerapps' that adds the parameter to the Run() command for the flow to use in the field I selected. I was trying to add to inputs to the first flow action thinking thats how it got the variables. Newbie mistake I guess.

      Delete