Showing posts with label Azure functions. Show all posts
Showing posts with label Azure functions. Show all posts

Tuesday, March 1, 2022

Return image stored in Sharepoint Online doclib from Azure function and show it in SPFx web part

Imagine that we need to display image which is stored e.g. in Style library doclib of Sharepoint Online site collection (SiteA) on another site collection (SiteB) and that users from SiteB may not have permissions on SiteA. One solution is to return this image in binary form from Azure function (which in turn will read it via CSOM and app permissions) and display in SPFx web part in base64 format. With this approach way we can avoid SPO permissions limitation (assuming that Azure functions are secured via AAD: Call Azure AD secured Azure functions from C#).

At first we need to implement http-triggered Azure function (C#) which will return requested image (we will send image url in query string param). It may look like this (for simplicity I removed errors handling):

[FunctionName("GetImage")]
public static async Task<HttpResponseMessage> Run([HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = null)]HttpRequestMessage req, TraceWriter log)
{
	string url = req.GetQueryNameValuePairs().FirstOrDefault(q => string.Compare(q.Key, "url", true) == 0).Value;
	var bytes = ImageHelper.GetImage(url);
	
	var content = new StreamContent(new MemoryStream(bytes));
	content.Headers.ContentType = new MediaTypeHeaderValue(ImageHelper.GetMediaTypeByFileUrl(url));

	return new HttpResponseMessage(HttpStatusCode.OK)
	{
		Content = content
	};
}

Here 2 helper functions are used: one which gets actual image as bytes array and another which returns media type based on file extension:

public static class ImageHelper
{
	public static byte[] GetImage(string url)
	{
		using (var ctx = ...) // get ClientContext
		{
			var web = ctx.Web;
			var file = web.GetFileByServerRelativeUrl(new Uri(url).AbsolutePath);
			ctx.Load(file);
			
			var fileStream = file.OpenBinaryStream();
			ctx.ExecuteQuery();
			
			byte[] bytes = new byte[fileStream.Value.Length];
			fileStream.Value.Read(bytes, 0, (int)fileStream.Value.Length);
			return bytes;
		}
	}

	public static string GetMediaTypeByFileUrl(string url)
	{
		string ext = url.Substring(url.LastIndexOf(".") + 1);
		switch (ext.ToLower())
		{
			case "jpg":
				return "image/jpeg";
			case "png":
				return "image/png";
			... // enum all supported media types here
			default:
				return string.Empty;
		}
	}
}

Now we can call this AF from SPFx:

return await this.httpClient.get(url, SPHttpClient.configurations.v1,
	{
		headers: ..., // add necessary headers to request
		method: "get"
	}
).then(async (result: SPHttpClientResponse) => {
	if (result.ok) {
		let binaryResult = await result.arrayBuffer();
		return new Promise((resolve) => {
			resolve({ data: binaryResult, type: result.headers.get("Content-Type") });
	});
})

And the last step is to encode it to base64 and add to img src attribute:

let img = ...; // get image element from DOM
let result = await getImage(url);
img.setAttribute("src", `data:${result.type};base64,${Buffer.from(result.data, "binary").toString("base64")}`)

After that image from SiteA will be shown on SiteB even if users don't have access to SiteA directly.

Friday, January 14, 2022

How to get Azure storage account associated with Azure function app via PowerShell

As you probably know Azure function app is provisioned with Storage account behind it which is used for internal needs of function app (e.g. storing compiled dlls of Azure functions in blob storage of this storage account or storing App insights logs in tables). If we need to get instance of this storage account associated with Azure function app we may use the following PowerShell:

$app = Get-AzWebApp -Name "MyFunctionApp" -ResourceGroupName "MyResourceGroup"
$kv = $app.SiteConfig.AppSettings | Where-Object { $_.Name -eq "AzureWebJobsDashboard" }
$found = $kv.Value -match ".*;AccountName=(.+?);"
$storageAccountName = $matches[1]
$storageAccount = Get-AzStorageAccount -StorageAccountName $storageAccountName -ResourceGroupName "MyResourceGroup"

In above code we first get instance of function app using Get-AzWebApp cmdlet and find AzureWebJobsDashboard application setting which contains connection string of associated storage account. After that we retrieve storage account name from connection string using regex and finally call Get-AzStorageAccount to get actual storage account instance. Hope it will help someone.

Monday, November 22, 2021

Strange problem with remote event receivers not firing in Sharepoint Online sites which urls/titles ends with digits

Some time ago I wrote about Sharepoint Online remote event receivers (RERs) and how to use Azure functions with them: Use Azure function as remote event receiver for Sharepoint Online list and debug it locally with ngrok. When I tested RERs on another SPO sites I've faced with very strange problem: on those sites which urls/titles end with digits remote event receivers were not fired. E.g. I often create modern Team/Communication sites with datetime stamp at the end:

https://{tenant}.sharepoint.com/sites/{Prefix}{yyyyMMddHHmm}
e.g.
https://{tenant}.sharepoint.com/sites/Test202111221800

I noticed that on such sites RER was not called because of some reason. I've used the same PowerShell script for attaching RER to SPO site as described in above article and the same Azure function app running locally with the same ports and ngrok tunneling.

After I created site without digits at the end (https://{tenant}.sharepoint.com/sites/test) - RER started to work (without restarting AF or ngrok - i.e. I used the same running instances of AF/ngrok for all tests which ran all the time). Didn't find any explanation of this problem so far. If you faced with this issue and know the reason please share it.

Friday, November 12, 2021

Fix Azure functions error "Repository has more than 10 non-decryptable secrets backups (host)"

Today I've faced with strange issue: after update of Azure function app which was made from Visual Studio Azure functions stopped working. The following error was shown in the logs:

Repository has more than 10 non-decryptable secrets backups (host)

In order to fix this error perform the following steps:

1. In Azure portal go to Azure function app > Deployment center > FTPS credentials and copy credentials for connecting to function app by FTP:


2. Then connect to Azure function app by FTP e.g. using WinSCP client.

3. Go to /data/functions/secrets folder and remove all files which have name in the following form:
*.snapshot.{timestamp}.json

4. After that go to Azure portal > Function app and restart it. It should be started now.

Thursday, September 30, 2021

Speaking on online IT community event

Today I was speaking on online IT community event about development and debugging of Azure functions: https://tulaitcommunity.ru. On this event I wanted to show how to efficiently develop Azure functions and use them in Sharepoint Online. Development of Azure functions for Sharepoint Online require skills and experience of usage of many tools like Visual Studio, Postman, ngrok, PnP PowerShell, SPO browser. They were used in my demo. Presentation from speech is available from Slideshare here: https://www.slideshare.net/sadomovalex/azure-sharepoint-online. Recording from online meeting is available on youtube (on Russian language):


 

Hope that it was interesting :)

Thursday, July 22, 2021

Use Azure function as remote event receiver for Sharepoint Online list and debug it locally with ngrok

If you worked with Sharepoint on-prem you probably know what are event receivers: custom handlers which you may subscribe on different types of events. They are available for different levels (web, list, etc). In this article we will talk about list event receivers.

In Sharepoint Online we can't use old event receivers because they should be installed as farm solutions which are not available in SPO. Instead we have to use remote event receivers. The concept is very similar but instead of class and assembly names we should provide end point url where SPO will send HTTP POST request when event will happen.

Let's see how it works on practice. For event receiver end point I will use Azure function and will run it locally. For debugging it I will use ngrok tunneling (btw ngrok is great service which makes developers life much easier. If you are not familiar with it yet I hardly suggest you to do that :) ). But let's go step by step.

First of all we need to implement our Azure function:

[FunctionName("ItemUpdated")]
public static async Task<HttpResponseMessage> Run([HttpTrigger(AuthorizationLevel.Anonymous, "post", Route = null)]HttpRequestMessage req, TraceWriter log)
{
    log.Info("Start ItemUpdated.Run");
    var request = req.Content.ReadAsStringAsync().Result;
    return req.CreateResponse(HttpStatusCode.OK);
}

It doesn't do anything except reading body payload as string - we will examine it later. Then we run it locally - by default it will use http://localhost:7071/api/ItemUpdated url.

Next step is to create ngrok tunnel so we will get public https end point which can be used by SPO. It is done by the following command:

ngrok http -host-header=localhost 7071

After this command you should see something like that:

Now everything is ready for attaching remote event receiver to our list. It can be done by using Add-PnPEventReceiver cmdlet from PnP.Powershell. Note however that beforehand is it important to connect to target site with Connect-PnPOnline with UseWebLogin parameter:

Connect-PnPOnline -Url https://{tenant}.sharepoint.com/sites/{url} -UseWebLogin

Without UseWebLogin remote event receiver won't be triggered. Here is the issue on github which explains why: RemoteEventReceivers are not fired when added via PnP.Powershell.

When ngrok is running we need to copy forwarding url: those which uses https and looks like https://{randomId}.ngrok.io (see image above). We will use this url when will attach event receiver to target list:

Connect-PnPOnline -Url https://mytenant.sharepoint.com/sites/Test -UseWebLogin
$list = Get-PnPList TestList
Add-PnPEventReceiver -List $list.Id -Name TestEventReceiver -Url https://{...}.ngrok.io/api/ItemUpdated -EventReceiverType ItemUpdated -Synchronization Synchronous

Here I attached remote event receiver to TestList on site Test and subscribed it to ItemUpdated event. For end point I specified url of our Azure function using ngrok host. Also I created it as synchronous event receiver so it will be triggered immediately when item got updated in the target list. If everything went Ok you should see your event receiver attached to the target list using Sharepoint Online Client Browser:

Note that ReceiverUrl property will contain ngrok end point url which we passed to Add-PnPEventReceiver.

Now all pieces are set and we may test our remote event receiver: go to Test list and try to edit list item there. After saving changes event receiver should be triggered immediately. If you will check body payload you will see that it contains information about properties which have been changed and id of list item which we just modified:

<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
	<s:Body>
		<ProcessEvent xmlns="http://schemas.microsoft.com/sharepoint/remoteapp/">
			<properties xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
				<AppEventProperties i:nil="true"/>
				<ContextToken/>
				<CorrelationId>c462dd9f-604a-2000-ea96-f4bacc80aa84</CorrelationId>
				<CultureLCID>1033</CultureLCID>
				<EntityInstanceEventProperties i:nil="true"/>
				<ErrorCode/>
				<ErrorMessage/>
				<EventType>ItemUpdated</EventType>
				<ItemEventProperties>
					<AfterProperties xmlns:a="http://schemas.microsoft.com/2003/10/Serialization/Arrays">
						<a:KeyValueOfstringanyType>
							<a:Key>TimesInUTC</a:Key>
							<a:Value i:type="b:string" xmlns:b="http://www.w3.org/2001/XMLSchema">TRUE</a:Value>
						</a:KeyValueOfstringanyType>
						<a:KeyValueOfstringanyType>
							<a:Key>Title</a:Key>
							<a:Value i:type="b:string" xmlns:b="http://www.w3.org/2001/XMLSchema">item01</a:Value>
						</a:KeyValueOfstringanyType>
						<a:KeyValueOfstringanyType>
							<a:Key>ContentTypeId</a:Key>
							<a:Value i:type="b:string" xmlns:b="http://www.w3.org/2001/XMLSchema">...</a:Value>
						</a:KeyValueOfstringanyType>
					</AfterProperties>
					<AfterUrl i:nil="true"/>
					<BeforeProperties xmlns:a="http://schemas.microsoft.com/2003/10/Serialization/Arrays"/>
					<BeforeUrl/>
					<CurrentUserId>6</CurrentUserId>
					<ExternalNotificationMessage i:nil="true"/>
					<IsBackgroundSave>false</IsBackgroundSave>
					<ListId>96c8d1c1-de22-47bf-9f70-aeeefa349856</ListId>
					<ListItemId>1</ListItemId>
					<ListTitle>TestList</ListTitle>
					<UserDisplayName>...</UserDisplayName>
					<UserLoginName>...</UserLoginName>
					<Versionless>false</Versionless>
					<WebUrl>https://mytenant.sharepoint.com/sites/Test</WebUrl>
				</ItemEventProperties>
				<ListEventProperties i:nil="true"/>
				<SecurityEventProperties i:nil="true"/>
				<UICultureLCID>1033</UICultureLCID>
				<WebEventProperties i:nil="true"/>
			</properties>
		</ProcessEvent>
	</s:Body>
</s:Envelope>

This technique allows to use Azure function as remote event receiver and debug it locally. Hope it will help someone.

Update 2021-11-22: see also one strange problem about using of remote event receivers in SPO sites: Strange problem with remote event receivers not firing in Sharepoint Online sites which urls/titles ends with digits.

Friday, April 9, 2021

How to test certificate-based authentication in Azure functions for Sharepoint Online on local PC

If you develop Azure function you probably often run them locally on dev PC rather than in Azure. It simplifies debugging and development. In this post I will show how to test certificate-based authentication for Sharepoint Online in Azure functions running locally. First of all we need to register AAD app in Azure portal and grant it Sharepoint permissions:

Don't forget to grant Admin consent after adding permissions.

After that generate self-signed certificate using Create-SelfSignedCertificate.ps1 script from here: Granting access via Azure AD App-Only:

.\Create-SelfSignedCertificate.ps1 -CommonName "MyCertificate" -StartDate 2021-04-08 -EndDate 2031-04-09

It will generate 2 files:

  • private key: .pfx
  • public key: .cer

Go to registered AAD app > Certificates & secrets > Certificates > Upload certificate and upload generated .cer file. After upload copy certificate thumbprint - it will be needed for Azure functions below.

In Azure function certificate-based authentication for Sharepoint Online can be done by the following code (using OfficeDevPnP.Core):

using (var authMngr = new OfficeDevPnP.Core.AuthenticationManager())
{
    using (var ctx = authMngr.GetAzureADAppOnlyAuthenticatedContext(siteUrl, clientId, tenant, StoreName.My, StoreLocation.CurrentUser, certificateThumbprint))
    {
        ...
    }
}

Here we specified clientId of our AAD app, copied certificate thumbprint and tenant in the form {tenant}.onmicrosoft.com.

Before to run it we need to perform one extra step: install certificate to local PC certificates store. It can be done by double click on .pfx file. After that Windows will open Certificate import wizard:


Since our code is using Personal store use Store Location = Current User. Then specify password and import your certificate to the store. You may check that certificate is installed properly by opening MMC console > Add/Remove snapin > Certificates. Imported certificate should appear under Personal > Certificates:

After that you will be able to run Azure functions locally which communicate with Sharepoint Online using certificate-based authentication.

Friday, February 5, 2021

Use GraphServiceClient with HttpClient in muti-thread apps

If you use GraphServiceClient from Graph SDK for .Net in multi-thread app consider reusing it as static instance instead of creating own instances inside each thread. GraphServiceClient itself is thread safe (see here) and can be reused within multiple threads as is (without locks, synchronizations, etc).

Otherwise it will internally create multiple HttpClient instances and you may face with reaching sockets limit. Alternatively you may create static HttpClient and then pass it to the GraphServiceClient instances created per thread as it has special constructor for that:

public GraphServiceClient(HttpClient httpClient)
  : base("https://graph.microsoft.com/v1.0", httpClient)
{
}

If you create threads intensively there may be too many instances of HttpClient created and you will reach sockets limit. E.g. this is how it looked in Azure function app with queue-triggered Azure function when GraphServiceClient instances were created inside function call (own instance per thread):


I.e. there were 2K connection peaks and the following errors in the logs:

An operation on a socket could not be performed because the system lacked sufficient buffer space or because a queue was full

When I changed the code and reused static instance of GraphServiceClient connections count got stabilized:



Monday, January 25, 2021

Get access token for calling AAD secured Azure functions via PowerShell

Some time ago I posted PowerShell example which lists all Azure AD groups via Rest API: List Azure AD groups via Rest Graph API in Powershell. However if you have custom Azure functions app which is secured via AAD (see Call Azure AD secured Azure functions from C#) you may use similar approach to get access token based on client id/secret (i.e. which will use app permissions) for calling your Azure functions from PowerShell or from 3rd party clients like Postman.

In order to get access token for calling AAD secured Azure functions you may use the following PowerShell script:

param
(
	[Parameter(Mandatory=$true)]
	[string]$Tenant,
	[Parameter(Mandatory=$true)]
	[string]$ClientId,
	[Parameter(Mandatory=$true)]
	[string]$ClientSecret
)

$currentDir = $PSScriptRoot
$dllCommonDir = resolve-path($currentDir + "\Common\")

[System.Reflection.Assembly]::LoadFile([System.IO.Path]::Combine($dllCommonDir, "Microsoft.Identity.Client.dll"))

function GetAccessToken($tenant, $clientId, $ClientSecret)
{
	$appCredentials = New-Object Microsoft.Identity.Client.ClientCredential -ArgumentList $ClientSecret
	$aadLoginUri = New-Object System.Uri -ArgumentList "https://login.microsoftonline.com/"
	$authorityUri = New-Object System.Uri -ArgumentList $aadLoginUri, $Tenant
	$authority = $authorityUri.AbsoluteUri
	$redirectUri = "urn:ietf:wg:oauth:2.0:oob"
	$clientApplication = New-Object Microsoft.Identity.Client.ConfidentialClientApplication($ClientId, $authority, $redirectUri, $appCredentials, $null, $null)
	[string[]]$defaultScope = @("https://graph.microsoft.com/.default")
	$authenticationResult = $clientApplication.AcquireTokenForClientAsync($defaultScope).Result
	return $authenticationResult.AccessToken
}

Write-Host "Acquiring access token..." -foregroundcolor green
$accessToken = GetAccessToken $Tenant $ClientId $ClientSecret
if ([System.String]::IsNullOrEmpty($accessToken))
{
	Write-Host "Can't acquire access token" -foregroundcolor red
	return
}
Write-Host "Access token successfully acquired" -foregroundcolor green
$filePath = [System.IO.Path]::Combine($currentDir, [System.String]::Format("_accesstoken_{0}.txt", [System.DateTime]::Now.ToString("yyyyMMddHHmmss")))
($accessToken) | Out-File $filePath -Append

It will fetch access token and will log it to text file. For calling this script you will need Microsoft.Identity.Client.dll assembly located in Common subfolder.

Monday, November 16, 2020

Remote debugging of Azure functions in Visual Studio

When developing for Azure you should definitely take a look on Visual Studio’s Cloud Explorer (Views > Cloud Explorer) as it has features which are missing in basic Azure portal. E.g. when working with Notification hubs it allows to manage existing subscriptions for push notifications (view and delete if needed). There is one more useful feature if you develop Azure functions which I explored recently: remote debugging. It allows you to attach debugger to remote process of your Azure function app running in Azure and debug it in your Visual Studio.

In order to do that open Cloud explorer > choose subscription > App services > right click on Azure function app > Attach debugger:

If it will show error:

System.Runtime.InteropServices.COMException (0x89710023): Unable to connect to the Microsoft Visual Studio Remote Debugger named '{tenant}.azurewebsites.net'.  The Visual Studio 2017 Remote Debugger (MSVSMON.EXE) does not appear to be running on the remote computer. This may be because a firewall is preventing communication to the remote computer. Please see Help for assistance on configuring remote debugging.

ensure that port 4022 which is used by remote debugger is opened in firewall. After that you should be able to debug remote Azure functions running on Azure.

Monday, September 21, 2020

Call AAD secured Azure functions from Postman

It is often needed to test our AAD secured Azure functions from client applications like Postman. In one of my previous articles I showed how to call secured Azure functions from C# and PowerShell (see Call Azure AD secured Azure functions from C#). Let’s see now to to call them from Postman app – in this case we won’t need to write any code for testing our functions.

First of all we need to obtain access token. If app permissions are used we may use the same approach as in article mentioned above. For delegated user permissions you may go to SPFx page which calls Azure function, open browser Network tab, find call to some Azure function there and copy access token from Authorization header (value after “Bearer” part).

After that go to Postman and create new request using url of Azure function (it can be copied from Azure portal > Functions app) and correct http verb. On Autorization tab set Type = “Bearer Token” and copy access token to the Token field:

After that on Headers tab set correct content type if needed (e.g. application/json):

Then press Send. After that we should get results from AAD secured Azure function in Postman.

Monday, July 6, 2020

Run Azure function as Web job without code change

Sometime it may be needed to test existing Azure function as Web job (e.g. if you decide to move some long-running process from AF to web job). In order to do that you need to create App service first. After that create new console app project in Visual Studio, reference your Azure functions project as it would be regular class library and from Program.cs call necessary Azure function (it is possible since AFs are created as static methods of the class).

The problem however is that if you read some app settings in Azure function code – you most probably do it like that:

string foo = Environment.GetEnvironmentVariable("Foo", EnvironmentVariableTarget.Process);

But if you will just copy AF app settings to appSettings section of app.config of the new console project then above code will return empty string for any app setting. So in order to call our Azure function from console application and run it as web job we need to perform additional step: iterate through all app settings and fill environment variables for the process:

var appSettings = ConfigurationManager.AppSettings;
foreach (var key in appSettings.AllKeys)
{
 Environment.SetEnvironmentVariable(key, appSettings[key],
  EnvironmentVariableTarget.Process);
}

After that app settings will be successfully read and Azure function may run as web job without changing its code.

Friday, June 12, 2020

Problem with threads count grow when use OfficeDevPnP AuthenticationManager

Recently we faced with the following problem in our Azure function app: after some time of functioning Azure functions got stuck and the only way to make them work again was to restart them manually from Azure portal UI. Research showed that problem was related with threads count: because of some reason threads count permanently grew to 6K thread after which Azure function app became unresponsive:

We reviewed our code, made some optimizations, change some async calls to sync equivalents, but it didn’t fix the problem. Then we continued troubleshooting and found the following: in the code we used OfficeDevPnP AuthenticationManager in order to get access token for communicating with Sharepoint Online like that:

using (var ctx = new OfficeDevPnP.Core.AuthenticationManager().GetAppOnlyAuthenticatedContext(url, clientId, clientSecret))
{
    ...
}

Note that in this example new instance of AuthenticationManager is created each time when above code was executed. In our case it was inside queue-triggered Azure function which was called quite frequently. Elio Struyf (we worked over this problem together so credits for this finding go to him Smile) tried to make AuthenticationManager static – i.e. only one instance was created for the whole Azure function worker process:

public static class Helper
{
 private static OfficeDevPnP.Core.AuthenticationManager authMngr = new OfficeDevPnP.Core.AuthenticationManager();

 public static void QueueTriggeredFunc()
 {
  using (var ctx = authMngr.GetAppOnlyAuthenticatedContext(url, clientId, clientSecret))
  {
  ...
  }
 }
}

After that threads count got stabilized with avg 60 threads:

We reported this problem to OfficeDevPnP team and they confirmed that there is problem in AuthenticationManager which internally creates thread for monitoring access token’s lifetime, but this thread is not released when AuthenticationManager got recycled by garbage collector (also need to mention that AuthenticationManager itself was not disposable at the moment when this problem was found. We used OfficeDevPnP 3.20.2004.0 when found this problem). For now this workaround with making AuthenticationManager static was Ok and as far as I know currently OfficeDevPnP team works over correct fix for this problem. So hopefully it will be available soon globally.

Update 2020-06-15: from latest news I've got (thanks to Yannick Plenevaux :) ) OfficeDevPnP team addressed this issue in 202006.2 version and made AuthenticationManager disposable. So you may need to review your code in order to add using() around it.

Friday, January 24, 2020

How to set Always On for Azure Function app via PowerShell

If you run Azure functions on dedicated App service plan (vs Consumption plan) of Basic pricing tier and above you may speed up warmup time of Azure functions by enabling Always On setting (Azure Function app > Configuration > General settings):

If you need to automate this process here is the PowerShell script which can be used in order to set Always On for Azure Function app:

$resourceGroupName = ...
$functionAppName = ...
$webAppPropertiesObject = @{"siteConfig" = @{"AlwaysOn" = $true}}
$webAppResource = Get-AzureRmResource -ResourceType "microsoft.web/sites" -ResourceGroupName $resourceGroupName -ResourceName $functionAppName
$webAppResource | Set-AzureRmResource -PropertyObject $webAppPropertiesObject -Force

After that you Function app will have Always On setting enabled.

Friday, December 20, 2019

Configurable schedule of timer triggered Azure functions

As you probably know it is possible to create Azure functions which will be triggered automatically by schedule specified by CRON expression. In order to do that create timer-triggered Azure function and specify CRON expression like this:

public static class MyFunc
{
 [FunctionName("MyFunc")]
 public static void Run([TimerTrigger("0 */15 * * * *")] TimerInfo myTimer, TraceWriter log)
 {
  ...
 }
} 

Here we defined function which will run every 15 minutes. But with this approach if you will want to change schedule on production env you will need to recompile the package and re-upload it to Azure. It is more convenient to create schedule configurable. In order to do that use the following syntax:

public static class MyFunc
{
 [FunctionName("MyFunc")]
 public static void Run([TimerTrigger("%MyFuncSchedule%")] TimerInfo myTimer, TraceWriter log)
 {
  ...
 }
}

At the same time in AF app settings you need to have MyFuncSchedule app setting:

{
  "IsEncrypted": false,
  "Values": {
    "MyFuncSchedule": "0 */15 * * * *",
  },
  "Host": {
    "CORS": "*"
  }
}

In this case it will be possible to change timer schedule for your timer-triggered Azure function without re-compiling and re-uploading the package.

Note 1. App setting with schedule should be added before to upload package with compiled Azure functions. Otherwise function won’t run.

Note 2. When you change Azure function app settings – currently running instances will be terminated. After that new instances will use updated app settings.

Monday, December 9, 2019

Configure permissions for Azure Function app to access secrets from Azure Key vault

In order to be able to use Azure Key vault from Azure functions at first you need to grant permissions to Azure Function app to read data (in our example we will use Secrets i.e. passwords, app secrets, etc. But you also may use the same technique to access keys and certificates which also may be stored there) from Azure Key vault. At first you need to create System assigned identify for your Azure function from Platform features > Identify:

On this page under System assigned tab set status to On:

After that go to Azure Key vault (if you don’t have it yet than create it first) and select Access policies > Add Access Policy. In opened page select Secret permissions > Get:

(if you store keys or certificates in Key vault you have to select appropriate Key or Certificate permissions).

In Select principal choose name of your Azure Function app. Principal will be available in this field only after creation of Function app principal which we made above.

After that your Azure functions will be able to read values from Azure Key vault. Note that you have to keep it in the following format in app settings:

@Microsoft.KeyVault(SecretUri=https://{key-vault-name}.vault.azure.net/secrets/{secret-name}/{id})

Then you may just read this param from app setting and it will be automatically expanded to the actual secret value stored in Key vault.

Wednesday, November 27, 2019

How to specify BindingRedirects in Azure functions app settings via ARM template in automatic provisioning scenarios

If you face with famous problem with Newtonsoft.Json version mismatch in Azure functions (when Azure functions SDK requires one specific version and e.g. OfficeDevPnP requires other – you get error in runtime that Newtonsoft.Json.dll of version x.x.x.x is not found). There is solution for this issue posted here: Performing a binding redirect in Azure Functions. With this solution we have to add new app setting for Azure function which is string representation of JSON array:

{
    "BindingRedirects": "[ { \"ShortName\": \"Newtonsoft.Json\", \"RedirectToVersion\": \"11.0.0.0\", \"PublicKeyToken\": \"30ad4fe6b2a6aeed\" } ]"
}

It works but if you use automatic provision of Azure functions using template json file (see Azure Resource Manager template functions) and will want to specify it there like this:

{
    "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
    "contentVersion": "1.0.0.0",
    "parameters": {
        "BindingRedirects": {
            "type": "string",
            "defaultValue": "[{ \"ShortName\": \"Newtonsoft.Json\", \"RedirectToVersion\": \"12.0.0.0\", \"PublicKeyToken\": \"30ad4fe6b2a6aeed\" }]"
        }
    }
}

you will see the following error:

Error occured when try to create Function app (error code 1):
Error: Code=InvalidTemplate; Message=Deployment template language expression evaluation failed: 'The language expression '{ "ShortName": "Newtonsoft.Json", "RedirectToVersion": "12.0.0.0", "PublicKeyToken": "30ad4fe6b2a6aeed" }
' is not valid: the string character '{' at position '0' is not expected.'. Please see https://aka.ms/arm-template-expressions for usage details.

The problem here is that BindingRedirects app setting use square brackets which at the same time are used by ARM template engine for specifying calculated values. Solution for this problem is to escape square brackets by double them i.e. instead of [ use [[ and instead of ] use ]]. Final version will look like this:

{
    "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
    "contentVersion": "1.0.0.0",
    "parameters": {
        "BindingRedirects": {
            "type": "string",
            "defaultValue": "[[{ \"ShortName\": \"Newtonsoft.Json\", \"RedirectToVersion\": \"12.0.0.0\", \"PublicKeyToken\": \"30ad4fe6b2a6aeed\" }]]"
        }
    }
}

After that provision of ARM template for Azure functions will go well.

Friday, October 4, 2019

How to test and debug Timer triggered Azure functions running locally on localhost

With Azure function project type we may create many types of Azure functions. One of them is Timer triggered Azure function which is triggered by scheduler (CRON expression is specified during function creation and may be modified later in Azure function attribute):

When you will run this function locally (on locahost) you will notice thatt it won’t be listed under

http://localhost:7071/api/*

address together with Http triggered Azure function. Runtime will call your function automatically when schedule time will come. But this is not very convenient since during development you would probably like to have possibility to call it synchronously on demand.

In order to call Timer triggered Azure function you may use Postman tool. You have to construct URL little bit different from Http triggered functions: instead of /api you should send POST request to http://localhost:7071/admin/functions/* base url and send the following request body:

{
    "input": ""
}

Using this technique you may debug your Timer triggered Azure functions synchronously.

Monday, July 15, 2019

Problem with not editable host.json file for Azure function app

Some time we need to manually modify content of host.json file of Azure function app (e.g. to change logging settings). In order to do that you need to go to Azure function app > Platform features > Function app settings > host.json. However you may face with situation that textarea which stores content of host.json will be readonly:

In this case change Function app edit mode setting on the same page from “Read only” to “Read/Write”. After that you will be able to edit content of host.json file for you Azure function app:

Thursday, December 27, 2018

Call Azure AD secured Azure functions from C#

In this post I will show how to call Azure functions which are secured with AAD from C#. Let’s assume that you already created Function app and secured it with AAD. In this example I will use Advanced AAD authentication configuration of the Function app which may be used e.g. when Function app is created in one tenant but it’s Azure functions supposed to be used in another tenant (and thus should be secured against this other tenant):

image

Briefly in order to secure Azure function with AAD we need to register new app in the directory against which we want to authenticate users which will use Azure function (Azure Active Directory > App registrations > New app registration); in this example let’s call it test-secured-functions for reference. Then in authentication settings of the Function app: in Client ID specify app id of test-secured-functions, Client Secret – client secret of test-secured-functions (can be generated from app’s Settings > Keys) and in Issuer Url – url of the form https://sts.windows.net/{tenantId} where tenantId is id of the tenant where test-secured-functions is registered (can be found in Azure Active Directory > Properties > Directory ID).

After that copy base url of any Azure function (e.g. https://{azurefuncname}.azurewebsites.net – those which comes before /api/… part) in the Function app and add it to Allowed token audiences. Otherwise you will get HTTP 401 Unauthorized when will try to get access token for calling Azure function from C#.

Now the tricky part; go to test-secured-functions > Settings > Properties and change “Home page URL” and “App ID URI” properties to the base url of Azure function – same which was added to Allowed token audiences on previous step (https://{azurefuncname}.azurewebsites.net). It is more important to set “Home page URL”  - it will be used as app id when we will get access token:

image

Now we may call our AAD secured Azure function from C#:

string aadInstance = "https://login.windows.net/{0}";
string tenant = "{tenant}.onmicrosoft.com";
string serviceResourceId = "https://{azurefuncname}.azurewebsites.net";
string clientId = "{clientId}";
string appKey = "{clientSecret}";

var authContext = new AuthenticationContext(string.Format(CultureInfo.InvariantCulture, aadInstance, tenant));
var clientCredential = new ClientCredential(clientId, appKey);
AuthenticationResult result = authContext.AcquireTokenAsync(serviceResourceId, clientCredential).Result;
Console.WriteLine(result.AccessToken);

Here you need to replace {tenant} with your tenant name, {azurefuncname} with name of your Azure function, {clientId} and {clientSecret} are from test-secured-functions app – same which were used in Fucntion app > Active Directory Authentication above. Notice that serviceResourceId variable contains base url of our Azure functions. I.e. we ask access token for scope = url of our Azure function. If we won’t set this property we will get the following error when try to call above code:

AADSTS50001: The application named https://{azurefuncname}.azurewebsites.net was not found in the tenant named {tenant}.onmicrosoft.com.  This can happen if the application has not been installed by the administrator of the tenant or consented to by any user in the tenant.  You might have sent your authentication request to the wrong tenant

If we will decode token (e.g. on https://jwt.io/) we will see that it’s aud property is set exactly to requested serviceResourceId, i.e. base url of Azure function. This is why we set this base url to the app’s “Home page url” above – API searched for the app by this url during the call of AuthenticationContext.AcquireTokenAsync() method:

image

Once token is obtained we may call Azure function using regular way by providing Authorization HTTP header with Bearer token:

var request = WebRequest.Create("https://{azurefuncname}.azurewebsites.net/api/test") as HttpWebRequest;
request.Method = "POST";
request.ContentType = "application/json";
request.Headers.Add("Authorization", "Bearer" + " " + accessToken);

string data = ...;
var byteData = Encoding.UTF8.GetBytes(data);
request.ContentLength = byteData.Length;
var stream = request.GetRequestStream();
stream.Write(byteData, 0, byteData.Length);

using (var response = request.GetResponse() as HttpWebResponse)
{
    ...
}

Using this method you will be able to call your AAD secured Azure functions from C# or PowerShell. Hope this information will help someone.