{"id":3389,"date":"2023-10-04T13:21:50","date_gmt":"2023-10-04T18:21:50","guid":{"rendered":"https:\/\/microsoftgeek.com\/?p=3389"},"modified":"2026-07-09T16:07:11","modified_gmt":"2026-07-09T21:07:11","slug":"copy-blobs-between-storage-accounts-with-an-azure-function","status":"publish","type":"post","link":"https:\/\/microsoftgeek.com\/?p=3389","title":{"rendered":"Copy blobs between Storage Accounts using an Azure Function"},"content":{"rendered":"\n<h2 class=\"wp-block-heading\" id=\"azure-function-setup-trigger\">Azure Function setup &amp; trigger<\/h2>\n\n\n\n<p>Let&#8217;s create an Azure Function&nbsp;using runtime of PowerShell in a Code publish method. Use a consumption plan type to reduce costs.<\/p>\n\n\n\n<p><em>I\u2019ve selected an existing Storage Account under \u2018Hosting\u2019 when creating my function which holds my current container I want to copy from for simplicity.<\/em><\/p>\n\n\n\n<figure class=\"wp-block-image\"><img decoding=\"async\" src=\"https:\/\/rios.engineer\/wp-content\/uploads\/2021\/12\/Create-Func.png\" alt=\"\" class=\"wp-image-231\"\/><\/figure>\n\n\n\n<p>Once the deployment completes we want to create a trigger so the function will run when a new blob is uploaded to the container.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"trigger-setup-and-config\">Trigger setup and config<\/h3>\n\n\n\n<p>There\u2019s a handy&nbsp;event trigger for Azure Blob Storage&nbsp;which is perfect for this solution.<\/p>\n\n\n\n<figure class=\"wp-block-image\"><img decoding=\"async\" src=\"https:\/\/rios.engineer\/wp-content\/uploads\/2023\/01\/Blob-Copy-Func-Trigger-1.png\" alt=\"\" class=\"wp-image-595\"\/><\/figure>\n\n\n\n<p>When selecting a blob storage trigger we\u2019re presented with some additional options for the trigger criteria:<\/p>\n\n\n\n<p><strong>Name:<\/strong>&nbsp;This is the name of the trigger<\/p>\n\n\n\n<p><strong>Path:&nbsp;<\/strong>This will be the path to the container\/blob, in my example you can see I\u2019m using \u2018<em><strong>test\/{name}.csv<\/strong><\/em>\u2018. This is so it only triggers on filenames with a .csv extension that is uploaded into the container \u2018test\u2019.<\/p>\n\n\n\n<p><\/p>\n\n\n\n<p><strong>Storage account connection:<\/strong>&nbsp;My connection is \u2018<em><strong>AzureWebJobsStorage<\/strong><\/em>\u2018 as I\u2019ve linked my Function to an existing Storage Account for this (as mentioned in the intro). But if you want to select a different storage, click on \u2018New\u2019 and select the Storage Account you want for the trigger source. More on that here&nbsp;<a href=\"https:\/\/learn.microsoft.com\/en-us\/azure\/azure-functions\/functions-bindings-storage-blob-trigger?tabs=in-process%2Cextensionv5&amp;pivots=programming-language-powershell#connections\" target=\"_blank\" rel=\"noreferrer noopener\">Azure Blob storage trigger for Azure Functions | Microsoft Learn<\/a><\/p>\n\n\n\n<p>Finally, create the trigger and wait for deployment to complete.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"uploading-azcopy\">Uploading AzCopy<\/h2>\n\n\n\n<p>Next we need to upload AzCopy executable into the Function so it can be invoked when the function is triggered. <\/p>\n\n\n\n<p>We will be using AzCopy v10. This can be downloaded from the Microsoft website&nbsp;<a href=\"https:\/\/learn.microsoft.com\/en-us\/azure\/storage\/common\/storage-use-azcopy-v10#download-azcopy\" target=\"_blank\" rel=\"noreferrer noopener\">here<\/a>.<\/p>\n\n\n\n<p>A quick recap on how to do this:<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li>Go to your Azure Function and locate Deployment on the left navigation pane<\/li>\n\n\n\n<li>Click Deployment Center and locate the FTPS credentials tab<\/li>\n\n\n\n<li>Copy the endpoint, username &amp; password to connect to the Azure Function via an FTP client such as\u00a0FileZilla<\/li>\n\n\n\n<li>Lastly, lets drag &amp; drop azcopy.exe from your machine to \/site\/wwwroot\/BlobTrigger1 (or what the trigger name is set to)<\/li>\n<\/ol>\n\n\n\n<figure class=\"wp-block-image\"><img decoding=\"async\" src=\"https:\/\/rios.engineer\/wp-content\/uploads\/2023\/01\/Blob-Copy-Func-FTP.png\" alt=\"\" class=\"wp-image-596\"\/><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"azcopy\">AzCopy<\/h2>\n\n\n\n<p>Then there is the AzCopy copy command so when the Function is triggered it actions the copy required. AzCopy version 10 natively supports blob storage copy which makes this quite simple to script.<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li>In the Azure Function, go to Functions and select the blog storage trigger that has been created.<\/li>\n\n\n\n<li>Once in the trigger go to Code + Test with a dropdown to select the \u2018run.ps1\u2019 file \u2013 this is what executes when the function is triggered.<\/li>\n<\/ol>\n\n\n\n<figure class=\"wp-block-image\"><img decoding=\"async\" src=\"https:\/\/rios.engineer\/wp-content\/uploads\/2023\/01\/Blob-Copy-Func-Runps1-1024x396.png\" alt=\"\" class=\"wp-image-598\"\/><figcaption class=\"wp-element-caption\">run.ps1<\/figcaption><\/figure>\n\n\n\n<p>3. Below the default bindings and log output we can enter our AzCopy command.<\/p>\n\n\n\n<p>My working example looks like this:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>C:\\home\\site\\wwwroot\\BlobTrigger1\\azcopy.exe copy \n'https:\/\/&lt;source-storage-account-name&gt;.blob.core.windows.net\/&lt;container-name&gt;\/&lt;SAS-token&gt;' \n'https:\/\/&lt;destination-storage-account-name&gt;.blob.core.windows.net\/&lt;container-name&gt;\/&lt;SAS-token&gt;' \n--recursive --include-pattern *.csv\n<\/code><\/pre>\n\n\n\n<p>In my example, I\u2019m using two Blob SAS URLs that I have generated on each Storage Account (<a href=\"https:\/\/learn.microsoft.com\/en-us\/azure\/applied-ai-services\/form-recognizer\/create-sas-tokens?view=form-recog-3.0.0#use-the-azure-portal\" target=\"_blank\" rel=\"noreferrer noopener\">here\u2019s a guide on how to do this<\/a>). I\u2019ve selected the allowed resource types: container and left the rest of the defaults.<\/p>\n\n\n\n<p>On my AzCopy command I\u2019m using&nbsp;<strong><em>\u2013recursive<\/em><\/strong>&nbsp;to ensure I copy across the same folder structure from the source. The&nbsp;<strong><em>\u2013include-pattern *.csv&nbsp;<\/em><\/strong>instructs only the CSV files within the structure to be the files that get copied across.<\/p>\n\n\n\n<p>Alternatively, you can use many different other pattern combos to match different requirements, such as filename matches and more.&nbsp;<a href=\"https:\/\/learn.microsoft.com\/en-us\/azure\/storage\/common\/storage-use-azcopy-blobs-copy\" target=\"_blank\" rel=\"noreferrer noopener\">Copy blobs between Azure storage accounts with AzCopy v10 | Microsoft Learn<\/a><\/p>\n\n\n\n<p><mark>Note: Shared Access Tokens is the only supported method for this AzCopy functionality. It appears I cannot utilize a managed identity from my testing. There is no way to authenticate in AzCopy within the Function like how you can with the Az PS modules. Let me know if you know a way around this, I was unsuccessful!<\/mark><\/p>\n\n\n\n<p>More info on how to create a Shared Access Token for a storage account:&nbsp;<a href=\"https:\/\/learn.microsoft.com\/en-us\/azure\/storage\/blobs\/sas-service-create?tabs=dotnet\" target=\"_blank\" rel=\"noreferrer noopener\">Create a service SAS for a container or blob \u2013 Azure Storage | Microsoft Learn<\/a><\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"testing\">Testing<\/h2>\n\n\n\n<p>Lets start by testing the functionality.<\/p>\n\n\n\n<p>I\u2019m matching only against .csv files for both trigger events &amp; the AzCopy copy command.<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Upload a test.csv to Storage Account 1 (source).<\/li>\n\n\n\n<li>The function will trigger.<\/li>\n\n\n\n<li>This then executes our run.ps1 AzCopy command which will perform the copy.<\/li>\n<\/ul>\n\n\n\n<p>Upload test.csv in the Azure Portal GUI to Storage Account 1:<\/p>\n\n\n\n<figure class=\"wp-block-image\"><img decoding=\"async\" src=\"https:\/\/rios.engineer\/wp-content\/uploads\/2023\/01\/Blob-Copy-Func-CSVUpload2.png\" alt=\"\" class=\"wp-image-610\"\/><\/figure>\n\n\n\n<p>After a few minutes I can see that the function has executed on the overview page (under Function Execution count). In my destination Storage Account Container I can review and check the copy was successful as the test.csv now appears.<\/p>\n\n\n\n<figure class=\"wp-block-image\"><img decoding=\"async\" src=\"https:\/\/rios.engineer\/wp-content\/uploads\/2023\/01\/Blob-Copy-Func-CSVCopy1.png\" alt=\"\" class=\"wp-image-611\"\/><\/figure>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"checking-the-logs\">Checking the logs<\/h3>\n\n\n\n<p>Under the Function, select the BlobTrigger1 (or your trigger name you specified) and locate the Monitor section under Developer.<\/p>\n\n\n\n<figure class=\"wp-block-image\"><img decoding=\"async\" src=\"https:\/\/rios.engineer\/wp-content\/uploads\/2023\/01\/Blob-Copy-Func-SuccessLog.png\" alt=\"\" class=\"wp-image-605\"\/><\/figure>\n\n\n\n<p>Here we can review the execution logs and review any error outputs. In my example you can see the Event Trigger output and then the copy output which was successful.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Azure Function setup &amp; trigger Let&#8217;s create an Azure Function&nbsp;using runtime of PowerShell in a Code publish method. Use a consumption plan type to reduce costs. I\u2019ve selected an existing Storage Account under \u2018Hosting\u2019 when creating my function which holds my current container I want to copy from for simplicity. Once the deployment completes we [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[69,35],"tags":[],"class_list":["post-3389","post","type-post","status-publish","format-standard","hentry","category-azure","category-cloud-computing"],"_links":{"self":[{"href":"https:\/\/microsoftgeek.com\/index.php?rest_route=\/wp\/v2\/posts\/3389","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/microsoftgeek.com\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/microsoftgeek.com\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/microsoftgeek.com\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/microsoftgeek.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=3389"}],"version-history":[{"count":7,"href":"https:\/\/microsoftgeek.com\/index.php?rest_route=\/wp\/v2\/posts\/3389\/revisions"}],"predecessor-version":[{"id":3725,"href":"https:\/\/microsoftgeek.com\/index.php?rest_route=\/wp\/v2\/posts\/3389\/revisions\/3725"}],"wp:attachment":[{"href":"https:\/\/microsoftgeek.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=3389"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/microsoftgeek.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=3389"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/microsoftgeek.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=3389"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}