{"id":7288,"date":"2016-10-06T15:36:57","date_gmt":"2016-10-06T13:36:57","guid":{"rendered":"https:\/\/p0wershell.com\/?p=7288"},"modified":"2016-10-07T16:32:59","modified_gmt":"2016-10-07T14:32:59","slug":"automate-discount-coupons-with-azure-automation-and-microsoft-flow","status":"publish","type":"post","link":"https:\/\/p0wershell.com\/?p=7288","title":{"rendered":"Automate discount coupons with Azure Automation and Microsoft Flow"},"content":{"rendered":"<p><strong>Some background&#8230;<\/strong><br \/>\nWe&#8217;ve been buying our groceries online for a few years now. We find it super convenient and it saves us a lot of time. I even <a href=\"https:\/\/p0wershell.com\/?p=2031\" target=\"_blank\">created a PowerShell module<\/a> for it some time ago!<\/p>\n<p>There is one (very minor) annoyance with it though, remembering to use the discount coupons you get after you&#8217;ve bought groceries for a certain amount. These coupons or codes get&#8217;s sent out before your current order has been delivered which means that you cant add them for your next order (can&#8217;t reach checkout while you have an active order waiting for delivery).<\/p>\n<p>This means I have to wait for my order to be delivered and then add to it at the checkout step for my next order, at which point I&#8217;ve forgot all about it and maybe even deleted\/archived the e-mail containing the pdf-file with the coupon.<\/p>\n<p>I thought of this as the perfect scenario to check out a (relatively) new service from Microsoft called <a href=\"https:\/\/flow.microsoft.com\" target=\"_blank\">Flow<\/a>, the idea behind Flow is to make it simple to automate things without the need of writing any code, but that doesn&#8217;t mean you can&#8217;t do that as well \ud83d\ude42<\/p>\n<p><strong>How to achieve this?<br \/>\n<\/strong>When building automation I usually try to write down the steps needed to achieve the &#8220;end-to-end automation&#8221;. In this case that would be:<\/p>\n<ol>\n<li>Make sure the e-mails containing the coupons can be found automatically<\/li>\n<li>Get the coupon from the e-mail moved somewhere where it can be accessed by a PowerShell runbook in Azure Automation<\/li>\n<li>Create a PowerShell function that can parse pdf-files so the code inside can be retrieved<\/li>\n<li>Create another PowerShell function that can post the code to the online grocery store<\/li>\n<li>Profit! \ud83d\ude42<\/li>\n<\/ol>\n<p>These steps have now been achieved, and here&#8217;s how I did it:<\/p>\n<p><strong>Fetching the E-mail and the attachments (Step 1 and 2)<\/strong><br \/>\nThis is amazingly simple using Microsoft Flow. After you&#8217;ve signed up and logged in, just go to &#8220;My Flows&#8221; and click &#8220;Create from template&#8221;. There are quite a few to pick from so the easiest way to achieve this is to use the search function at the top of page, since I&#8217;m using Outlook.com as my personal e-mail provider, and thought the simplest way to store the attachments was using blob storage, I simply searched for &#8220;outlook blob&#8221; and found these templates I could use:<\/p>\n<p><a href=\"https:\/\/p0wershell.com\/wp-content\/uploads\/2016\/10\/SerachForOutlookBlob.png\"><img loading=\"lazy\" decoding=\"async\" class=\"alignnone size-large wp-image-7289\" src=\"https:\/\/p0wershell.com\/wp-content\/uploads\/2016\/10\/SerachForOutlookBlob-1024x686.png\" alt=\"serachforoutlookblob\" width=\"725\" height=\"486\" srcset=\"https:\/\/p0wershell.com\/wp-content\/uploads\/2016\/10\/SerachForOutlookBlob-1024x686.png 1024w, https:\/\/p0wershell.com\/wp-content\/uploads\/2016\/10\/SerachForOutlookBlob-300x201.png 300w, https:\/\/p0wershell.com\/wp-content\/uploads\/2016\/10\/SerachForOutlookBlob-768x515.png 768w, https:\/\/p0wershell.com\/wp-content\/uploads\/2016\/10\/SerachForOutlookBlob-624x418.png 624w, https:\/\/p0wershell.com\/wp-content\/uploads\/2016\/10\/SerachForOutlookBlob.png 1061w\" sizes=\"(max-width: 725px) 100vw, 725px\" \/><\/a><\/p>\n<p>In my case, the first one fits perfectly so that&#8217;s the one I chose as a starting point. Click on it, pick &#8220;choose this template&#8221;\u00a0and first connect your Azure storage account (needs to be created in advance):<\/p>\n<p><a href=\"https:\/\/p0wershell.com\/wp-content\/uploads\/2016\/10\/ConnectAzureStorage.png\"><img loading=\"lazy\" decoding=\"async\" class=\"alignnone wp-image-7290 size-medium\" src=\"https:\/\/p0wershell.com\/wp-content\/uploads\/2016\/10\/ConnectAzureStorage-300x231.png\" alt=\"connectazurestorage\" width=\"300\" height=\"231\" srcset=\"https:\/\/p0wershell.com\/wp-content\/uploads\/2016\/10\/ConnectAzureStorage-300x231.png 300w, https:\/\/p0wershell.com\/wp-content\/uploads\/2016\/10\/ConnectAzureStorage-624x480.png 624w, https:\/\/p0wershell.com\/wp-content\/uploads\/2016\/10\/ConnectAzureStorage.png 706w\" sizes=\"(max-width: 300px) 100vw, 300px\" \/><\/a><\/p>\n<p>Then connect your e-mail account by logging in:<\/p>\n<p><a href=\"https:\/\/p0wershell.com\/wp-content\/uploads\/2016\/10\/ConnectOutlookAccount.png\"><img loading=\"lazy\" decoding=\"async\" class=\"alignnone wp-image-7292 size-medium\" src=\"https:\/\/p0wershell.com\/wp-content\/uploads\/2016\/10\/ConnectOutlookAccount-217x300.png\" alt=\"connectoutlookaccount\" width=\"217\" height=\"300\" srcset=\"https:\/\/p0wershell.com\/wp-content\/uploads\/2016\/10\/ConnectOutlookAccount-217x300.png 217w, https:\/\/p0wershell.com\/wp-content\/uploads\/2016\/10\/ConnectOutlookAccount-624x862.png 624w, https:\/\/p0wershell.com\/wp-content\/uploads\/2016\/10\/ConnectOutlookAccount.png 635w\" sizes=\"(max-width: 217px) 100vw, 217px\" \/><\/a><\/p>\n<p>If everything worked, you can go on and press &#8220;Continue&#8221;<\/p>\n<p>&nbsp;<\/p>\n<p><a href=\"https:\/\/p0wershell.com\/wp-content\/uploads\/2016\/10\/ConnectedAccounts.png\"><img loading=\"lazy\" decoding=\"async\" class=\"alignnone wp-image-7291 size-full\" src=\"https:\/\/p0wershell.com\/wp-content\/uploads\/2016\/10\/ConnectedAccounts.png\" alt=\"connectedaccounts\" width=\"873\" height=\"524\" srcset=\"https:\/\/p0wershell.com\/wp-content\/uploads\/2016\/10\/ConnectedAccounts.png 873w, https:\/\/p0wershell.com\/wp-content\/uploads\/2016\/10\/ConnectedAccounts-300x180.png 300w, https:\/\/p0wershell.com\/wp-content\/uploads\/2016\/10\/ConnectedAccounts-768x461.png 768w, https:\/\/p0wershell.com\/wp-content\/uploads\/2016\/10\/ConnectedAccounts-624x375.png 624w\" sizes=\"(max-width: 873px) 100vw, 873px\" \/><\/a><\/p>\n<p>You&#8217;ll then arrive at the page where you can configure the different steps in your flow, and if you want to, add some conditions. After you&#8217;ve clicked &#8220;edit&#8221; on both steps\u00a0and updated them they should look something like this:<br \/>\n<a href=\"https:\/\/p0wershell.com\/wp-content\/uploads\/2016\/10\/FlowEditModeUpdated.png\"><img loading=\"lazy\" decoding=\"async\" class=\"alignnone size-large wp-image-7296\" src=\"https:\/\/p0wershell.com\/wp-content\/uploads\/2016\/10\/FlowEditModeUpdated-645x1024.png\" alt=\"floweditmodeupdated\" width=\"645\" height=\"1024\" srcset=\"https:\/\/p0wershell.com\/wp-content\/uploads\/2016\/10\/FlowEditModeUpdated-645x1024.png 645w, https:\/\/p0wershell.com\/wp-content\/uploads\/2016\/10\/FlowEditModeUpdated-189x300.png 189w, https:\/\/p0wershell.com\/wp-content\/uploads\/2016\/10\/FlowEditModeUpdated-624x991.png 624w, https:\/\/p0wershell.com\/wp-content\/uploads\/2016\/10\/FlowEditModeUpdated.png 719w\" sizes=\"(max-width: 645px) 100vw, 645px\" \/><\/a><\/p>\n<p>As you can see, I changed the folder this flow should look in from &#8220;Inbox&#8221; to &#8220;Flow&#8221; to prevent it from harvesting all the attachments I receive. I can then simply add a mail rule to\u00a0put the e-mails I want in that folder.<\/p>\n<p>Same thing for the &#8220;Create file&#8221;-step, &#8220;mailattachments&#8221; should correspond to a container on your storage account.<\/p>\n<p>That&#8217;s it for parsing the e-mails. If you would like to, you could also add a http request after these steps to trigger the runbook automatically (<a href=\"https:\/\/azure.microsoft.com\/en-us\/documentation\/articles\/automation-webhooks\/\" target=\"_blank\">webhook<\/a>) as soon as a new attachment has been saved to the blob storage, but in this case, I&#8217;ll just schedule that to run at a regular intervall.<\/p>\n<p><strong>Parsing the pdf-file and posting the discount code (step 3, 4 and 5!)<\/strong><br \/>\nTo be able to get text out of the pdf-file I used <a href=\"https:\/\/sourceforge.net\/projects\/itextsharp\/\" target=\"_blank\">the iTextSharp library<\/a>. Then wrap that up in a PowerShell function, which in it&#8217;s simplest form might look something like this:<\/p>\n<p>(Code example found at: <a href=\"https:\/\/powershell.org\/forums\/topic\/convertfrom-pdf-powershell-cmdlet\/\" target=\"_blank\">https:\/\/powershell.org\/forums\/topic\/convertfrom-pdf-powershell-cmdlet\/<\/a>)<\/p>\n<p>[PowerShell]<br \/>\nAdd-Type -Path &#8220;$PSScriptRoot\\itextsharp.dll&#8221;<\/p>\n<p>function Get-PdfText<br \/>\n{<br \/>\n    [CmdletBinding()]<br \/>\n    [OutputType([string])]<br \/>\n    param (<br \/>\n        [Parameter(Mandatory = $true)]<br \/>\n        [string]<br \/>\n        $Path<br \/>\n    )<\/p>\n<p>    $Path = $PSCmdlet.GetUnresolvedProviderPathFromPSPath($Path)<\/p>\n<p>    try<br \/>\n    {<br \/>\n        $reader = New-Object iTextSharp.text.pdf.pdfreader -ArgumentList $Path<br \/>\n    }<br \/>\n    catch<br \/>\n    {<br \/>\n        throw<br \/>\n    }<\/p>\n<p>    $stringBuilder = New-Object System.Text.StringBuilder<\/p>\n<p>    for ($page = 1; $page -le $reader.NumberOfPages; $page++)<br \/>\n    {<br \/>\n        $text = [iTextSharp.text.pdf.parser.PdfTextExtractor]::GetTextFromPage($reader, $page)<br \/>\n        $null = $stringBuilder.AppendLine($text)<br \/>\n    }<\/p>\n<p>    $reader.Close()<\/p>\n<p>    return $stringBuilder.ToString()<br \/>\n}<br \/>\n[\/PowerShell]<\/p>\n<p>I&#8217;ve also added a function called &#8220;Add-MatHemBonusCode&#8221; to my &#8220;<a href=\"https:\/\/p0wershell.com\/?p=2031\" target=\"_blank\">Grocery shopping PowerShell module<\/a>&#8220;, because that got to exist, right? \ud83d\ude09<\/p>\n<p>Finally, it&#8217;s time to wrap those functions up in a runbook.<\/p>\n<p>The runbook could look something like this (dont look at this as a runbook best practice template, it&#8217;s not \ud83d\ude42 ):<\/p>\n<p>[PowerShell]<br \/>\n# Load the credentials needed<br \/>\n$AzureCredential = Get-AutomationPSCredential -Name &#8216;AzureCred&#8217;<br \/>\n$MatHemCredential = Get-AutomationPSCredential -Name &#8216;MatHem&#8217;<\/p>\n<p># Log into to Azure<br \/>\nAdd-AzureRmAccount -Credential $AzureCredential<\/p>\n<p># Set a few parameters and fetch the storage information<br \/>\n$ResourceGroupName = &#8216;MyResourceGroup&#8217;<br \/>\n$StorageAccountName = &#8216;MyStorageAccount&#8217;<br \/>\n$ContainerName = &#8216;mailattachments&#8217;<br \/>\n$StorageAccountKey = Get-AzureRmStorageAccountKey -ResourceGroupName $ResourceGroupName -Name $StorageAccountName<\/p>\n<p>$StorageContext = New-AzureStorageContext -StorageAccountName $StorageAccountName -StorageAccountKey $StorageAccountKey[0].Value<\/p>\n<p>$Blobs = Get-AzureStorageBlob -Context $StorageContext -Container $ContainerName<\/p>\n<p># Filter out the attachment needed for this specific flow, only<br \/>\n# needed if you run multiple flows that look at attachments in the<br \/>\n# same container<br \/>\n$TargetedBlobs = $Blobs | Where-Object { $_.Name -match &#8216;^kvitto|^bonus&#8217; }<\/p>\n<p>foreach ($MatHemBlob in $TargetedBlobs) {<\/p>\n<p>    if ($MatHemBlob.Name -match &#8216;^kvitto&#8217;) {<br \/>\n        # These are not needed so let&#8217;s just remove them<br \/>\n        Remove-AzureStorageBlob -Blob $MatHemBlob.Name -Context $StorageContext -Container $ContainerName -Force<br \/>\n    }<br \/>\n    elseif ($MatHemBlob.Name -match &#8216;^bonus&#8217;) {<br \/>\n        # These contain the actual bonus or discount codes, so lets download those<br \/>\n        $LocalFileName = [System.IO.Path]::GetTempFileName()<br \/>\n        Get-AzureStorageBlobContent -Blob $MatHemBlob.Name -Container $ContainerName -Context $StorageContext -Destination $LocalFileName -Force<\/p>\n<p>        # Fetch the text from the file<br \/>\n        $BonusPdfText = (Get-PdfText -Path $LocalFileName) -split &#8220;`n&#8221;<\/p>\n<p>        # parse out the code itself<br \/>\n        $BonusCode = ((($BonusPdfText -match &#8216;^V\u00e4rdekod:&#8217;) -split &#8216;V\u00e4rdekod: &#8216;)[1]).trim()<\/p>\n<p>        # Connect to the online grocery store and post the code (+some error handling and notifications)<br \/>\n        if (-not $Global:MathemSession) {<br \/>\n            Connect-Mathem -Credential $MatHemCredential<br \/>\n        }<\/p>\n<p>        if ($BonusCode) {<br \/>\n            try {<br \/>\n                $Results = Add-MatHemBonusCode -BonusCode $BonusCode -ErrorAction Stop<br \/>\n            }<br \/>\n            catch {<br \/>\n                if ($_.ToString() -like &#8216;*Bonuskoden har redan anv\u00e4nts i en annan order*&#8217; ) {<br \/>\n                    Write-Warning &#8220;Bonus code $BonusCode have already been used. Cleaning up blob&#8230;&#8221;<br \/>\n                    Send-PushNotification -Message &#8220;Bonus code $BonusCode from $($MatHemBlob.Name) have already been used. I&#8217;m cleaning up the blob.&#8221;<br \/>\n                    Remove-AzureStorageBlob -Blob $MatHemBlob.Name -Context $StorageContext -Container $ContainerName -Force<br \/>\n                    Continue<br \/>\n                }<br \/>\n                elseif ($_.ToString() -like &#8216;*Felaktig bonuskod*&#8217; ) {<br \/>\n                    Write-Warning &#8220;Bonus code $BonusCode is invalid. Notifying master&#8230;&#8221;<br \/>\n                    Send-PushNotification -Message &#8220;The bonus code $BonusCode from $($MatHemBlob.Name) was invalid. Please take care of this for me!&#8221;<br \/>\n                    Remove-AzureStorageBlob -Blob $MatHemBlob.Name -Context $StorageContext -Container $ContainerName -Force<br \/>\n                    Continue<br \/>\n                }<br \/>\n                else {<br \/>\n                    Write-Warning &#8220;Failed to add bonus code $BonusCode from $($MatHemBlob.Name). The error was $($_.ToString())&#8221;<br \/>\n                    Send-PushNotification -Message &#8220;Failed to add bonus code $BonusCode from $($MatHemBlob.Name). The error was: $($_.ToString())&#8221;<br \/>\n                    Continue<br \/>\n                }<br \/>\n            }<\/p>\n<p>            Send-PushNotification -Message &#8220;Bonus code $BonusCode from $($MatHemBlob.Name) have been added with the response: $Results&#8221;<br \/>\n        }<br \/>\n    }<br \/>\n}<br \/>\n[\/PowerShell]<\/p>\n<p>Time to schedule that in Azure Automation, and make sure all the modules needed are available for it when it runs! (I run this on a hybrid worker)<\/p>\n<p><strong>Conclusion<\/strong><br \/>\nWhile I have had a few issues with Microsoft Flow along the way (it is still in preview after all), it seems like a really cool service. And since you can make a http request to a webhook in Azure Automation, and\/or just integrate them through some other service like the blob storage example in this post, the possibilities are pretty much endless.<\/p>\n<p>So, as always\u2026 Keep automating <em>anything<\/em>!<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Some background&#8230; We&#8217;ve been buying our groceries online for a few years now. We find it super convenient and it saves us a lot of time. I even created a PowerShell module for it some time ago! There is one (very minor) annoyance with it though, remembering to use the discount coupons you get after [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"jetpack_post_was_ever_published":false,"_jetpack_newsletter_access":"","_jetpack_dont_email_post_to_subs":true,"_jetpack_newsletter_tier_id":0,"footnotes":"","jetpack_publicize_message":"","jetpack_publicize_feature_enabled":true,"jetpack_social_post_already_shared":true,"jetpack_social_options":{"image_generator_settings":{"template":"highway","enabled":false}}},"categories":[1171,231,21,431,841],"tags":[],"jetpack_publicize_connections":[],"jetpack_featured_media_url":"","jetpack_sharing_enabled":true,"jetpack_shortlink":"https:\/\/wp.me\/p3Zj0A-1Ty","_links":{"self":[{"href":"https:\/\/p0wershell.com\/index.php?rest_route=\/wp\/v2\/posts\/7288"}],"collection":[{"href":"https:\/\/p0wershell.com\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/p0wershell.com\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/p0wershell.com\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/p0wershell.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=7288"}],"version-history":[{"count":0,"href":"https:\/\/p0wershell.com\/index.php?rest_route=\/wp\/v2\/posts\/7288\/revisions"}],"wp:attachment":[{"href":"https:\/\/p0wershell.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=7288"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/p0wershell.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=7288"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/p0wershell.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=7288"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}