Deprecated: Required parameter $post follows optional parameter $content in /customers/3/5/1/about-powershell.com/httpd.www/chen/wp-includes/functions.php on line 840
Warning: Private methods cannot be final as they are never overridden by other classes in /customers/3/5/1/about-powershell.com/httpd.www/chen/wp-includes/class-wp-session-tokens.php on line 69
Deprecated: Required parameter $tt_id follows optional parameter $object_id in /customers/3/5/1/about-powershell.com/httpd.www/chen/wp-includes/nav-menu.php on line 1060
Deprecated: Required parameter $taxonomy follows optional parameter $object_id in /customers/3/5/1/about-powershell.com/httpd.www/chen/wp-includes/nav-menu.php on line 1060
Deprecated: Required parameter $block_attributes follows optional parameter $block_name in /customers/3/5/1/about-powershell.com/httpd.www/chen/wp-includes/blocks.php on line 405
Deprecated: Required parameter $block_content follows optional parameter $block_name in /customers/3/5/1/about-powershell.com/httpd.www/chen/wp-includes/blocks.php on line 405
Warning: Cannot modify header information - headers already sent by (output started at /customers/3/5/1/about-powershell.com/httpd.www/chen/wp-includes/functions.php:840) in /customers/3/5/1/about-powershell.com/httpd.www/chen/wp-includes/feed-rss2.php on line 8
References:
A virtual conversation with a coffee is always my favorite. This morning, I had a chat with my friend in the UK. I was excited to know about an issue in the Azure portal, “Life Cycle Management” settings of a storage account with ADLS Gen 2 (hierarchy namespace enabled) is not appearing. I shared an ARM template way back to the team to set a few properties as part of the cost-saving.
I got a note that the development team couldn’t confirm the solution to close the case. Upon successful execution of the ARM template through Azure pipelines, the result (Life Cycle Management) isn’t visible under blob service.
AFAIK, it’s a glitch in the portal, and PowerShell is the way to confirm. Take a look at the gist of the ARM template to create a storage account with HNS (Hirerchial Namespace) enabled.
{ "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", "contentVersion": "1.0.0.0", "parameters": { }, "variables": { }, "resources": [ { "name": "[concat('istg003', '/default')]", "type": "Microsoft.Storage/storageAccounts/managementPolicies", "apiVersion": "2019-04-01", "dependsOn": [ "[resourceId('Microsoft.Storage/storageAccounts' , 'istg003')]" ], "properties": { "policy": { "rules": [ { "name": "MyCustomRule-CostSaving", "enabled": true, "type": "Lifecycle", "definition": { "filters": { "blobTypes": [ "blockBlob" ] }, "actions": { "baseBlob": { "tierToCool": { "daysAfterModificationGreaterThan": 20 }, "tierToArchive": { "daysAfterModificationGreaterThan": 90 }, "delete": { "daysAfterModificationGreaterThan": 2555 } } } } } ] } } }, { "name": "istg003", "type": "Microsoft.Storage/storageAccounts", "apiVersion": "2019-06-01", "location": "[resourceGroup().location]", "tags": { "displayName": "istg003" }, "properties": { "accountType": "STANDARD_RAGRS", "isHnsEnabled": true }, "sku": { "name": "Standard_RAGRS", "tier": "Standard" }, "kind": "StorageV2" } ], "outputs": { }, "functions": [ ] }
To check the storage account management policy, use the below snippet
Get-AzStorageAccountManagementPolicy -ResourceGroupName iContoso -StorageAccountName istg003
Output
]]>Off late, I built a solution using PowerShell with Azure Functions for simple and complex requirements at my workplace. In this blog series, I would like to cover the most exciting experiences while deploying the solution. The simple and complex issues are defined based on the environment and use cases, considering that this blog series has no justification for it!
To begin with, let me share a few how-tos!
How to change the port?
If you are running the Azure Functions in the localhost, the port is set to 7071 by default. The below settings allow us to change the port to the desired one.
{ "IsEncrypted": false, "Values": { "FUNCTIONS_WORKER_RUNTIME": "powershell", "AzureWebJobsStorage": "{AzureWebJobsStorage}" }, "Host": { "LocalHttpPort": 8080 } }
Is there any alternative approach? Yes, we can start the function using “–port” parameter
func start -port 8081
> Video for your reference!
How to change the route prefix?
Route prefix is nothing but a piece of the string associated with routes by design in the attribute, which is common in the entire controller. For example, look at the URL below where API is the route prefix
http://localhost:7071/api/iGreet
Here is the solution to change the route prefix!
{ "version": "2.0", "managedDependency": { "enabled": false }, "extensionBundle": { "id": "Microsoft.Azure.Functions.ExtensionBundle", "version": "[1.*, 2.0.0)" }, "extensions": { "http": { "routePrefix": "iAPI" } } }
> Video for your reference!
How to extend function time out?
By default, Azure Functions run for 5 minutes, and then timeout occurs. However, it is feasible to extend to 10 minutes—the host.json needs a modification like illustrated below.
{ "version": "2.0", "managedDependency": { "enabled": false }, "extensionBundle": { "id": "Microsoft.Azure.Functions.ExtensionBundle", "version": "[1.*, 2.0.0)" }, "extensions": { "http": { "routePrefix": "iAPI" } }, "functionTimeout": "00:10:00" }
> Video for your reference!
]]>Credits To: Wriju Ghosh – For sharing the https://azure.microsoft.com/en-us/updates/ and RSS Feed URL https://azurecomcdn.azureedge.net/en-us/updates/feed/
It was a simple ask “How do we know Azure Service Updates?” The answer is to use the link (Azure Service Updates)! But, in this blog post I will show how to retrieve the feed information programmatically, store in Azure Table Storage and send weekly newsletter to business users. Yes, Azure Table Storage will be used to store the service update news once a day (preferably Seattle Local time)
Prerequisites
Solution
Fetch Azure Service Updates
function Get-WeekNumber { param ( [Parameter(Mandatory, ValueFromPipeline, ValueFromPipelineByPropertyName)] $DateTime ) process { $CurrentCulture = [cultureinfo]::CurrentCulture $CurrentCulture.Calendar.GetWeekOfYear([datetime]"$($DateTime)", [System.Globalization.CalendarWeekRule]::FirstDay, [System.DayOfWeek]::Monday) } } Install-Module -Name AzTable -Force -Verbose -Scope CurrentUser $Config = Get-Content .\config\config.json | ConvertFrom-Json Set-AzContext -SubscriptionId $($Config.Subscription) | Out-Null $StorageContext = New-AzStorageContext -StorageAccountName $($Config.StorageAccountName) -SasToken $($Config.StorageAccountKey) $CloudTable = (Get-AzStorageTable -Name $($Config.TableName) -Context $($StorageContext)).CloudTable $AzureUpdates = Invoke-RestMethod -Uri "https://azurecomcdn.azureedge.net/en-us/updates/feed/" $AzureUpdates | . { process { Add-AzTableRow -Table $CloudTable -PartitionKey "Microsoft.Azure.Updates.Feed" -RowKey $($_.guid.'#text') -property @{ WeekNumber = (Get-WeekNumber -DateTime $_.pubDate) Title = $_.Title Category = $_.Category -split "," -Join "," PublishedDate = ([datetime]($_.pubDate)).ToShortDateString() ReferenceLink = $_.link } -UpdateExisting } }
{ "Subscription": "SUBCRIPTION ID", "StorageAccountName": "STG ACC NAME", "TableName": "TABLE NAME", "StorageAccountKey": "SECRET KEY", "TenantID": "TENANT ID", "ClientID": "CLIENT ID", "ClientSecret": "CLIENT SECRET", "O365Admin": "ADMIN", "O365Password": "Password" }
With no doubt you have to replace the parameter values in config.json. For a demo purpose I used secrets in config file, please plan as per the security standards at your work place.
Now, we need to send consolidated weekly report. The below snippet will help us – Replace the user name and password for O365.
Install-Module -Name AzTable -Force -Verbose -Scope CurrentUser $Config = Get-Content .\config\config.json | ConvertFrom-Json Set-AzContext -SubscriptionId $($Config.Subscription) | Out-Null $StorageContext = New-AzStorageContext -StorageAccountName $($Config.StorageAccountName) -SasToken $($Config.StorageAccountKey) $CloudTable = (Get-AzStorageTable -Name $($Config.TableName) -Context $($StorageContext)).CloudTable $WeekNumber = Get-Date -UFormat %V $AzureUpdates = Get-AzTableRow -Table $CloudTable -CustomFilter "(WeekNumber eq $WeekNumber)" Install-Module PSHTML -Scope CurrentUser -Verbose -Force $EmailBody = html -Content { head -Content { } body -Content { table -Content { table -Content { tr -Content { th -Content "Week Number" th -Content "Published Date" th -Content "Title" th -Content "ReferenceLink" } tr -Content { $AzureUpdates | . { process { tr -Content { td -Content $_.WeekNumber td -Content $_.PublishedDate td -Content $_.Title td -Content { a -href $($_.ReferenceLink) -Content "Reference Link" } } } } } } } } } $UserName = $($Config.O365Admin) $Password = $($Config.O365Password) | ConvertTo-SecureString -AsPlainText -Force $Credential = [pscredential]::new($UserName , $Password) $MailParams = @{ From = "AzureServiceUpdate@Microsoft.com" To = "Chendrayan.Exchange@hotmail.com" Subject = "Azure Service Updates - Weekly | Week ($(Get-Date -UFormat %V)) | Year $((Get-Date).Year)" SmtpServer = "smtp.office365.com" Body = $EmailBody BodyAsHtml = $true Port = 587 UseSSL = $true } Send-MailMessage @MailParams -Credential $Credential
In my next blog post I will share the Az Pipeline information and other interesting requirements.
]]>
Environment:
Note: I added only the Az Functions Code SYNC snippet – Not full ARM template (In my next blog I will cover all the steps including the nuances)
Of late I became a great fan of serverless and worked on few projects at work place using Azure Functions. I got stuck in an Az functions deployment deployment issue because Azure DevOps and Azure Cloud are in two different tenants. I can’t use the deployment center to deploy the solution at ease. Authentication fails!
All the team projects in Azure DevOps are private and has restrictions. Oh well, GitHub enterprise is not an option for me due to other reasons (It’s not in scope of this article). So, what’s next? PAT is my best bet!
Yes, I used Personal Access Token! It’s that simple and git credentials is an alternate! Before showing the ARM template let me show the challenges I faced!
Manually creating Azure Functions is not a big deal, but I have multiple environments. I want to get rid of manual intervention. No more cribbing right? ? – Developer missed to enable a function, Ops missed to enabled Identity – Those days are gone long back right! ???
With no doubt you can choose any ARM templates – available in web (GitHub, Blogs etc). In “Microsoft.Web/sites” resource add below snippet to get your code as “External Git”
"resources": [ { "apiVersion": "2018-11-01", "name": "web", "type": "sourcecontrols", "dependsOn": [ "[resourceId('Microsoft.Web/Sites/', parameters('functionAppName'))]" ], "properties": { "RepoUrl": "https://anything:{PAT}@ORG.visualstudio.com/TEAM PROJECT/_git/REPOSITORY", "branch": "master", "publishRunbook": true, "IsManualIntegration": true } } ]
Checkout the REPOURL – It uses a format as shown below
https://anything:{PAT}@ORG.visualstudio.com/TEAM PROJECT/_git/REPOSITORY
You can opt like
https://PAT:PAT@ORG.visualstudio.com/TEAM PROJECT/_git/REPOSITORY
Note: The below format fails with 401 error – Leads to other confusion!
https://PAT@ORG.visualstudio.com/TEAM PROJECT/_git/REPOSITORY
Upon success, don’t expect Azure functions to get updated when your source code gets updated – One with permission needs to press SYNC button in deployment center.
In my next blog I will walk through steps to build ARM template which provisions az function apps, get functions from azure devops (as external git). #Serverless #IaC
]]>Team showed me the pain points to pull out the azure virtual machine information which are missing the security and critical updates. I tried my best and found a way to pull the report using PowerShell. Indeed, there can be alternative – But, this solution gave us the data we need!
Code
param ( $SUBSCRIPTIONID, $AUTOMATIONACCOUNTNAME, $RESOURCEGROUPNAME, $WORKSPACE ) #region - Generate a bearer token $azureRmProfile = [Microsoft.Azure.Commands.Common.Authentication.Abstractions.AzureRmProfileProvider]::Instance.Profile $currentAzureContext = Get-AzContext $profileClient = New-Object Microsoft.Azure.Commands.ResourceManager.Common.RMProfileClient($azureRmProfile) $token = $profileClient.AcquireAccessToken($currentAzureContext.Subscription.TenantId) #endregion $Query = @" { "top": 1000000000, "query": "Heartbeat\n| where TimeGenerated>ago(12h) and OSType==\"Linux\" and notempty(Computer) | where ComputerEnvironment=~\"Azure\"\n| summarize arg_max(TimeGenerated, Solutions, Computer, ResourceId, ComputerEnvironment, VMUUID) by SourceComputerId\n| where Solutions has \"updates\" \n| extend vmuuId=VMUUID, azureResourceId=ResourceId, osType=1,\nenvironment=iff(ComputerEnvironment=~\"Azure\", 1, 2),\nscopedToUpdatesSolution=true, lastUpdateAgentSeenTime=\"\"\n| join kind=leftouter (Update\n| where TimeGenerated>ago(5h) and OSType==\"Linux\" and SourceComputerId in ((Heartbeat\n| where TimeGenerated>ago(12h) and OSType==\"Linux\" and notempty(Computer)\n| summarize arg_max(TimeGenerated, Solutions) by SourceComputerId\n| where Solutions has \"updates\" | distinct SourceComputerId)) | where ComputerEnvironment=~\"Azure\"\n| summarize hint.strategy=partitioned arg_max(TimeGenerated, UpdateState, Classification, Product, Computer, ComputerEnvironment) by SourceComputerId, Product, ProductArch \n| summarize Computer=any(Computer), ComputerEnvironment=any(ComputerEnvironment), missingCriticalUpdatesCount=countif(Classification has \"Critical\" and UpdateState=~\"Needed\"),\nmissingSecurityUpdatesCount=countif(Classification has \"Security\" and UpdateState=~\"Needed\"),\nmissingOtherUpdatesCount=countif(Classification !has \"Critical\" and Classification !has \"Security\" and UpdateState=~\"Needed\"),\nlastAssessedTime=max(TimeGenerated), lastUpdateAgentSeenTime=\"\" by SourceComputerId\n| extend compliance=iff(missingCriticalUpdatesCount > 0 or missingSecurityUpdatesCount > 0, 2, 1)\n| extend ComplianceOrder=iff(missingCriticalUpdatesCount > 0 or missingSecurityUpdatesCount > 0 or missingOtherUpdatesCount > 0, 1, 3)) on SourceComputerId\n| project id=SourceComputerId, displayName=Computer ,sourceComputerId=SourceComputerId, azureResourceId, scopedToUpdatesSolution=true,\nmissingCriticalUpdatesCount=coalesce(missingCriticalUpdatesCount, -1), missingSecurityUpdatesCount=coalesce(missingSecurityUpdatesCount, -1), missingOtherUpdatesCount=coalesce(missingOtherUpdatesCount, -1), compliance=coalesce(compliance, 4), lastAssessedTime, lastUpdateAgentSeenTime, osType=1, environment=iff(ComputerEnvironment=~\"Azure\", 1, 2), ComplianceOrder=coalesce(ComplianceOrder, 2)\n | where compliance in (2) | union(Heartbeat\n| where TimeGenerated>ago(12h) and OSType=~\"Windows\" and notempty(Computer) | where ComputerEnvironment=~\"Azure\"\n| summarize arg_max(TimeGenerated, Solutions, Computer, ResourceId, ComputerEnvironment, VMUUID) by SourceComputerId\n| where Solutions has \"updates\" \n| extend vmuuId=VMUUID, azureResourceId=ResourceId, osType=2,\nenvironment=iff(ComputerEnvironment=~\"Azure\", 1, 2),\nscopedToUpdatesSolution=true, lastUpdateAgentSeenTime=\"\"\n| join kind=leftouter (Update\n| where TimeGenerated>ago(14h) and OSType!=\"Linux\" and SourceComputerId in ((Heartbeat\n| where TimeGenerated>ago(12h) and OSType=~\"Windows\" and notempty(Computer)\n| summarize arg_max(TimeGenerated, Solutions) by SourceComputerId\n| where Solutions has \"updates\" | distinct SourceComputerId)) | where ComputerEnvironment=~\"Azure\"\n| summarize hint.strategy=partitioned arg_max(TimeGenerated, UpdateState, Classification, Title, Optional, Approved, Computer, ComputerEnvironment) by Computer, SourceComputerId, UpdateID \n| summarize Computer=any(Computer), ComputerEnvironment=any(ComputerEnvironment), missingCriticalUpdatesCount=countif(Classification has \"Critical\" and UpdateState=~\"Needed\" and Approved!=false),\nmissingSecurityUpdatesCount=countif(Classification has \"Security\" and UpdateState=~\"Needed\" and Approved!=false),\nmissingOtherUpdatesCount=countif(Classification !has \"Critical\" and Classification !has \"Security\" and UpdateState=~\"Needed\" and Optional==false and Approved!=false),\nlastAssessedTime=max(TimeGenerated), lastUpdateAgentSeenTime=\"\" by SourceComputerId\n| extend compliance=iff(missingCriticalUpdatesCount > 0 or missingSecurityUpdatesCount > 0, 2, 1)\n| extend ComplianceOrder=iff(missingCriticalUpdatesCount > 0 or missingSecurityUpdatesCount > 0 or missingOtherUpdatesCount > 0, 1, 3)) on SourceComputerId\n| project id=SourceComputerId, displayName=Computer, sourceComputerId=SourceComputerId, azureResourceId, scopedToUpdatesSolution=true,\nmissingCriticalUpdatesCount=coalesce(missingCriticalUpdatesCount, -1), missingSecurityUpdatesCount=coalesce(missingSecurityUpdatesCount, -1), missingOtherUpdatesCount=coalesce(missingOtherUpdatesCount, -1), compliance=coalesce(compliance, 4), lastAssessedTime, lastUpdateAgentSeenTime, osType=2, environment=iff(ComputerEnvironment=~\"Azure\", 1, 2), ComplianceOrder=coalesce(ComplianceOrder, 2)\n | where compliance in (2)) | order by ComplianceOrder asc, missingCriticalUpdatesCount desc, missingSecurityUpdatesCount desc, missingOtherUpdatesCount desc, displayName asc" } "@ $result = Invoke-RestMethod -Uri "https://management.azure.com/subscriptions/$($SUBSCRIPTIONID)/resourcegroups/$($RESOURCEGROUPNAME)/microsoft.operationalinsights/workspaces/$($WORKSPACE)/query?api-version=2017-10-01&q_OrchestratorExtension.DataModels.Computer" -Headers @{ Authorization = "Bearer {0}" -f ($token.AccessToken) } -Method Post -Body ($Query) -ContentType 'application/json' $Collection = @() $result.tables.rows | %{ $Collection += [pscustomobject]@{ VMName = $_[1] CriticalUpdateMissing = $_[5] SecurityUpdateMissing = $_[6] } } $Collection
Try it! Let me know if you have alternate solution!
]]>I was developing a solution to fetch azure VM information as part of the update management process. Indeed, there are many ways to pull the reports. But, I thought of doing it in az cli is best options! Here is the script which pulls out the azure virtual machines information with power status.
$virtualMachines = az.cmd graph query -q "where type =~ 'Microsoft.Compute/virtualMachines' | extend aliases | project id, sku = tostring(aliases['Microsoft.Compute/imageSku']), offer = tostring(aliases['Microsoft.Compute/imageOffer']) , publisher = tostring(aliases['Microsoft.Compute/imagePublisher']) | where offer == 'WindowsServer'" --first 200 | ConvertFrom-Json $groupBy = 40 $resultCollection = @() $group = [math]::Ceiling($virtualMachines.Count / $groupBy) for ($i = 0; $i -le $group; $i++) { $start = $i * $groupBy $end = (($i + 1) * $groupBy) - 1 "Processing for $($start) .. $($end)" $resultCollection += @(az.cmd vm get-instance-view --ids $($virtualMachines[$start..$end].id)) | ConvertFrom-Json } $resultCollection
That’s the first cut of scripts which worked as expected – There are some good and bad in the preceding code.
Good: It just works!
Bad: It’s ugly
Working on alternatives!
]]>
I got a requirement which is to fetch MSRC report. If you are new to Microsoft Security Center API like me refer here. Using your Microsoft ID generate an api key.
The ask is very simple – Retrieve the report which should contain below information
Indeed, team came up with the MsrcSecurityUpdates PowerShell module which is available in the gallery. But we don’t use that – Our need is to fetch report which meets old MS vulnerability report format.
So, the REST API is our friend and below simple script meet the need.
$monthofinterest = @('2017-Apr' , '2017-May', '2017-Jun', '2017-Jul', '2017-Aug', '2017-Sep', '2017-Oct', '2017-Nov', '2017-Dec', '2018-Jan', '2018-Feb', '2018-Mar', '2018-Apr', '2018-May', '2018-Jun', '2018-Jul', '2018-Aug', '2018-Sep', '2018-Oct', '2018-Nov', '2018-Dec', '2019-Jan', '2019-Feb', '2019-Mar', '2019-Apr', '2019-May', '2019-Jun', '2019-Jul' ) $colls = @() $monthofinterest | . { process { $pcdReport = Invoke-RestMethod -Method Get -Uri "https://api.msrc.microsoft.com/cvrf/$($_)?api-version=2018" -Headers @{ 'api-key' = '<APIKey>' } $pcdReport.cvrfdoc.Vulnerability.cve | . { process { $results = Invoke-RestMethod -Uri "https://portal.msrc.microsoft.com/api/security-guidance/en-US/CVE/$($_)" $results $colls += $results } } } } $colls | Select-Object cve* -ExpandProperty affectedproducts | Export-Csv C:\Temp\MSRCRawReport.csv -NoTypeInformation
Refer this blog post if you need data in different format.
]]>Yes, it’s straight forward approach and documentation is self-explanatory. While doing the installation @ravikanth showed me how to do remote development on WSL using VSCode. Indeed, that made me to explore the WSL a bit more. Again, it’s very simple – Just install remote development extension on VSCode which includes REMOTE – SSH , REMOTE – CONTAINERS , REMOTE – WSL.
REMOTE DEVELOPMENT – VSCode
What I observed is no intellisense support in Remote Development atleast for PowerShell. Anyways, here is a quick Hello World Web Server running on WSL as a sandbox – Accessible from Windows 10
]]>Output
Simple steps are as follows
iAbout
using namespace System.Net param($Request, $TriggerMetadata) $html = html -Content { head -Content { link -rel "stylesheet" -href "https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css" -integrity "sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm" -crossorigin "anonymous" link -rel "stylesheet" -href "https://cdn.metroui.org.ua/v4/css/metro-all.min.css" link -rel "stylesheet" -href "https://cdn.metroui.org.ua/v4/css/metro.min.css" link -rel "stylesheet" -href "https://cdn.metroui.org.ua/v4/css/metro-colors.min.css" link -rel "stylesheet" -href "https://cdn.metroui.org.ua/v4/css/metro-rtl.min.css" link -rel "stylesheet" -href "https://cdn.metroui.org.ua/v4/css/metro-icons.min.css" script -src "https://code.jquery.com/jquery-3.3.1.min.js" script -src "https://cdn.metroui.org.ua/v4/js/metro.min.js" script -src "https://code.jquery.com/jquery-3.2.1.slim.min.js" -integrity "sha384-KJ3o2DKtIkvYIK3UENzmM7KCkRr/rE9/Qpg6aAZGJwFDMVNA/GpGFF93hXpG5KkN" -crossorigin "anonymous" script -src "https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.12.9/umd/popper.min.js" -integrity "sha384-ApNbgh9B+Y1QKtv3Rn7W3mgPxhU9K/ScQsAP7hUibX39j7fakFPskvXusvfa0b4Q" -crossorigin "anonymous" script -src "https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/js/bootstrap.min.js" -integrity "sha384-JZR6Spejh4U02d8jOt6vLEHfe/JQGiRRSQQxSfFWpi1MquVdAyjUar5+76PVCmYl" -crossorigin "anonymous" title -Content "iContact" } body -Content { div -Class 'jumbotron' -Content { h1 -Class 'display-4' -Content "O365 Email..." } form -action '/api/iSendEmail' -method 'post' -enctype 'application/x-www-form-urlencoded' -target _blank -Content { div -Class 'form-group' -Content { input -type email -name email -Class 'form-control' } button -Attributes @{type = 'submit' } -Class 'btn btn-primary' -Content 'Send Email' } } } Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{ headers = @{'content-type' = 'text/html' } StatusCode = [HttpStatusCode]::OK Body = $html })
iSendEmail
using namespace System.Net; using namespace System.Web; param($Request, $TriggerMetadata) $formdata = ([ordered]@{ }) $DecodedBody = [System.Web.HttpUtility]::UrlDecode($Request.Body) ($($DecodedBody) -split "&").ForEach( { $value = $_.split("="); $formdata.Add($value[0], $value[1]) }) $O365UserName = [System.Environment]::GetEnvironmentVariable('O365AdminUserName') $O365Password = [System.Environment]::GetEnvironmentVariable('O365AdminPassword') | ConvertTo-SecureString -AsPlainText -Force $Credential = [pscredential]::new($O365UserName, $O365Password) $Body | ConvertTo-Html $MailParams = @{ From = 'chendrayan@chensoffice365.onmicrosoft.com' To = $($formdata.email) Subject = 'Test mail from Az Function App...' Body = @" Hi Chen V, This is a test mail from Azure Function app using the Office 365 SMTP server. Regards, Chen V "@ SMTPServer = 'smtp.office365.com' Usessl = $true Credential = $Credential } Send-MailMessage @MailParams Push-OutputBinding -name Response -Value ([HttpResponseContext]@{ headers = @{'content-type' = 'application/json' } StatusCode = [HttpStatusCode]::OK Body = [pscustomobject]@{ formdata = $formdata UserName = $O365UserName Message = "Message Sent from O365" } | ConvertTo-Json })
Enjoy PowerShell – In my next blog we will discuss and demo the event driven automation using functions app with PowerShell.
]]>In my previous blog post I demoed a simple example – GET and POST in Azure Function App. Here, comes the second part of it which answers the below questions (ASKS from readers)
Worth to read the documentation for creating your first azure functions using PowerShell. Assuming your environment setup is ready to follow the instructions and you should be here now!
Create a folder and name it is as modules – Upload all the required PowerShell modules here and azure functions imports with no additional line of snippet. Yes, no need to use Import-Module , Install-Module etc. We are building a serverless web application – So, I need PSHTML PowerShell module which I copied from my local machine
Now, its time for us to create a PowerShell function and test it locally – Here is the quick start!
After a successful test in local machine – Publish it in Azure Functions and below snippet in az func core tools will do that!
func azure functionapp publish iAutomate
Use –force if required
So, we now know how to use the modules in Az Functions App – Let me show using the bootstrap in PSHTML to beautify your web application. It’s very simple to use CDN
The below four lines of the snippet does the magic for us and you can add more than one number of styling and scripts.
head -Content { link -rel "stylesheet" -href "https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css" -integrity "sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm" -crossorigin "anonymous" script -src "https://code.jquery.com/jquery-3.2.1.slim.min.js" -integrity "sha384-KJ3o2DKtIkvYIK3UENzmM7KCkRr/rE9/Qpg6aAZGJwFDMVNA/GpGFF93hXpG5KkN" -crossorigin "anonymous" script -src "https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.12.9/umd/popper.min.js" -integrity "sha384-ApNbgh9B+Y1QKtv3Rn7W3mgPxhU9K/ScQsAP7hUibX39j7fakFPskvXusvfa0b4Q" -crossorigin "anonymous" script -src "https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/js/bootstrap.min.js" -integrity "sha384-JZR6Spejh4U02d8jOt6vLEHfe/JQGiRRSQQxSfFWpi1MquVdAyjUar5+76PVCmYl" -crossorigin "anonymous" title -Content "iHome" }
Now see the result of Jumbotron! (Landing Page)
Here is the full code!
using namespace System.Net param($Request, $TriggerMetadata) $html = html -Content { head -Content { link -rel "stylesheet" -href "https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css" -integrity "sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm" -crossorigin "anonymous" script -src "https://code.jquery.com/jquery-3.2.1.slim.min.js" -integrity "sha384-KJ3o2DKtIkvYIK3UENzmM7KCkRr/rE9/Qpg6aAZGJwFDMVNA/GpGFF93hXpG5KkN" -crossorigin "anonymous" script -src "https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.12.9/umd/popper.min.js" -integrity "sha384-ApNbgh9B+Y1QKtv3Rn7W3mgPxhU9K/ScQsAP7hUibX39j7fakFPskvXusvfa0b4Q" -crossorigin "anonymous" script -src "https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/js/bootstrap.min.js" -integrity "sha384-JZR6Spejh4U02d8jOt6vLEHfe/JQGiRRSQQxSfFWpi1MquVdAyjUar5+76PVCmYl" -crossorigin "anonymous" title -Content "iHome" } body -Content { div -Class 'jumbotron' -Content { h1 -Class 'display-4' -Content 'iHome' p -Class 'lead' -Content { "This is a Azure Function App built using PowerShell..." p -Content { "Only PowerShell and nothing else... CSS and JS are from CDN!" } -Style 'font-style:italic;font-weight:bold' } hr -Class 'my-1' p -Class 'lead' -Content { "PowerShell ♥ Azure Functions App" } -style 'text-align:center;font-weight:bold' } } } Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{ headers = @{'content-type' = 'text/html' } StatusCode = [HttpStatusCode]::OK Body = $html })
In my next blog post I will demo more PSHTML and event driven automation.
]]>