Monday, May 15, 2017

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

Download the sample Flow and PowerApp

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.

67 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?
    Thank you!

    ReplyDelete
  11. 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
  12. 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
    2. Mikael, any idea of how can I view these saved images back in PowerApps using gallery?

      Delete
    3. You add a data connection to the library you have images in (might have to type it manually if it doesn't show). Then bind the image card to ThisItem.'{Link}' and it will show.

      Delete
    4. Oh I forgot to mention that my app is working using the Add Picture control instead of Camera control..Are you able to give a quick guide on how to save a collection of images using Add Picture control and view it back in PowerApps upon saving? Thanks !

      Delete
  13. 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
  14. 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
    8. I have added a link to the top of this post with a sample Flow and PowerApps which works.

      Delete
  15. The on submit button is giving errors. Red squiggle under "URL" as invalid name and at the end "filebody" as invalid argument type, expecting text, boolean or number.

    ReplyDelete
  16. The only way to remove the squiggles for me is using:
    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:Name }));SaveImages.Run().

    Trying to add arguments in the Run cmd gives the squiggles. Also, I had to change 'URL' to 'Name'.

    Run the submit and the dots float, go into SP and still no files.????

    Checking Flow history, they're all Failed. Split Images: "Unable to process template language expressions in action 'Split_Images' inputs at line '1' and column '1649': 'The template language expression 'split(triggerBody()['Compose_Inputs'], '#')' cannot be evaluated because property 'Compose_Inputs' cannot be selected. Please see https://aka.ms/logicexpressions for usage details.'."

    Filter array: "ActionConditionFailed. The execution of template action 'Filter_array' is skipped: the 'runAfter' condition for action 'Split_Images' is not satisfied. Expected status values 'Succeeded' and actual value 'Failed'"

    Apply to each: "ActionConditionFailed. The execution of template action 'Apply_to_each' is skipped: the 'runAfter' condition for action 'Filter_array' is not satisfied. Expected status values 'Succeeded' and actual value 'Skipped'."

    ReplyDelete
  17. Going back into the flow, all steps have lost their expression as input earlier, whether quotes required or not. Although I tested without quotes, updated the flow and went back in. Removing quotes seems to maintain the expression, otherwise they're all replaced with one of those compose icons. You know the ones that appearing as [{V} Body x]. Quotes seems to blow away the expression.

    All this being said, still no files in my library.

    Don't you love PowerApps and Microsoft in general?

    ReplyDelete
    Replies
    1. Wow...you are struggling. Hard to say what exactly. I'll try create a sample with download for Flow and the App which can be imported. Might save ppl some time. And hopefully we get proper image saving support soon.

      Delete
    2. That's for sure. I know you are not obliged to answer or anything so I do very much appreciate your response!

      I even tried Paul's angle but he was using SQL so I couldn't get his option to work.

      I've found in general, anytime you try to accomplish anything with Microsoft products it's pretty much guaranteed you're going to run into an obstacle or two or three.

      Cheers

      CW

      Delete
  18. I tried to create exactly everything, but my flow failed at SharePoint create file.
    Am I missing anything?
    http://i64.tinypic.com/mmaa83.png

    ReplyDelete
    Replies
    1. Not sure.. it says request URL is too long - maybe too long a filename? You should click the step which fails and see what the command was.

      Delete
    2. I managed to fix previous mistake, and successfully created the flow and the files were shown in the SharePoint
      However I cannot open the image , both on SP or when downloaded to the computer.
      What is wrong? the file format is JPEG but it doesnt seem to be a photo ?
      http://i64.tinypic.com/30rtwfo.png

      Delete
    3. In what environment are you testing the app? Open the file in a text editor and check the contents.

      Delete
    4. I am using my Iphone 7.
      This is when i open the jpeg file with notepad on windows.
      This is shown:

      "@base64ToBinary(replace(split(item(),'|')[1],'data:image/jpeg;base64,',''))"
      "@dataUriToBinary(split(item(),'|')[1])"

      Delete
    5. Seems to be a quote issue where the command has not been executed, but is passed as a string.

      Delete
    6. Do you have any idea why this happen?
      I am just testing a dummy app from scratch to follow your instruction.
      Not sure why it is not a Photo but a String,

      Delete
    7. I have added a link at the top of the article with a sample Flow and PowerApp. Import these in your environment and take a look :)

      Delete
  19. Minh. I had/have that issue as well. The strangest think I've seen.

    The image displays as a proper JPG in the list but clicking it opens a browser window with no image contrary to what we always see. Opening from Windows explorer does display the image ok though.

    Maybe there's some extra code in the file header that prevents it from opening in a browser? I don't know.

    But,somehow SP or Flow or Powerapps sullied the file.

    ReplyDelete
  20. Thanks Mikael, I'll take a look.

    Cheers.

    ReplyDelete
  21. Greeting- I tried your process but when gets to the 'create file' in flow under filename and file-content it does not come up with 'output' like yours. It only have 'output' from split photos part. Is there something I did wrong? I used exactly the same code for 'apply to each' as your.
    Thank you

    ReplyDelete
    Replies
    1. Download the sample files and take a look.

      Delete
  22. I am unable t download form the above sample file,Kindly guide me how to download the sample file.

    I tried to create exactly everything, but my flow failed at SharePoint create file.

    Can you please provide if you have any document

    Thank you,
    Balaji k

    ReplyDelete
    Replies
    1. Wow!
      1. click the link
      2. dismiss the registration form for Dropbox
      3. click the blue download button
      4. unzip and import

      Delete
  23. Unable to create the Flow..
    When trying to save, it says (translated from german):

    "The value "inputs" for the Workflow execution action "Filter Array" of the type "Query" is invalid. The Property "where" has to be a Template language expression."

    What can I do to overcome this??

    ReplyDelete
    Replies
    1. Good question.. you can open the action and see if you get a better error or help there.

      Delete
    2. Simply switched to the non-advanced mode and entered "not equal to" + none in the value field, then switched back to the advanced editor and it worked :)

      Delete
    3. none means leaving it empty, sry ;D

      Delete
  24. Okay I made it so far.. but I'm struggling with running the Flow..
    SaveImages.Run(Concat(SubmitData, filename & "|" & filebody & "#"))
    PowerApps says: Awaiting "Paren close" found: "Error"

    what am I doing wrong? Please help me :(

    ReplyDelete