Deploy an Azure Function app with PowerShell

Deploying updated code to an Azure Function App can be done through a variety of methods, and one of those methods is by uploading a zip file to the Function App via the Kudu REST API.

To start, go to your Function App in the Azure Portal and download the publish profile. This contains the ZipDeploy URL and the username and password that will be needed. You can do this from the Overview page of the Function App:

Location showing where the publish profile can be downloaded
Download the publish profile for your Function App

This will download an xml file that contains a few different publish profiles that you can use. In this case, look for the ZipDeploy profile and take note of the publishURL, userName and userPWD. The URL will take the form of FunctionAppName.scm.azurewebsites.net, the username will be $FunctionAppName and the password will be a long, randomised string.

The ZipDeploy publish profile
The ZipDeploy publish profile

Next you will need to make sure that SCM Basic Auth Publishing Credentials are enabled in the General Settings for your Function App. This can be found under Settings > Configuration > General Settings:

Updating the configuration of your Function App to enable SCM Basic Auth Publishing Credentials
Enable SCM Basic Auth Publishing Credentials

With this done let’s start coding the deployment, starting with the creation of the zip file. The root directory that you zip should contain the files that you want in the wwwroot folder of your Function App. Typically this will include your functions, a Modules folder, and a host.json file (and perhaps other files depending on the scripting language you selected for your Function App).

Let’s also say you have a .funcignore file that has a list of files or folders that are in the folder structure of the Function App but should not be included in the deployment.

So, your Function App source folder might look something like this:

Typical Function App folder structure
Typical Function App folder structure

Here’s the code to create the zip file:

$FolderPath = "C:\FunctionApps\KevinStreetsFunctionApp"
$Exclude = Import-Csv -Path "$FolderPath\.funcignore" -Header "Exclude"
$Destination = "$FolderPath\KevinStreetsFunctionApp.zip"
$Files = Get-ChildItem -Path $FolderPath -Exclude $Exclude.Exclude

# Create the zip file
Compress-Archive -Path $Files -DestinationPath $Destination -CompressionLevel Fastest

Now that the zip file has been created it is time to deploy it. Start by defining some variables with the publishURL, userName and userPWD you noted down earlier. Also, update the publishURL to include the correct API and include “isAsync=true” which will immediately return a response that you can use to monitor the deployment status.

$DeploymentUrl = 'https://kevinstreetsfunctionapp.scm.azurewebsites.net'
$Username = '$KevinStreetsFunctionApp'
$Password = 'Hnsl114TmGlpmEis9afDe5KEZoqvJDpcNZyri8ucAJx2b2uSoKhd4ovlCg9Q'
$ApiUrl = "$($DeploymentUrl):443/api/zipdeploy?isAsync=true"

Create the base-64 encoded string to pass in the Authorization header when we make the request:

$Base64AuthInfo = [Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes(("{0}:{1}" -f $Username, $Password)))

Next create a variable to define the zip file location:

$ZipFileLocation = "C:\FunctionApps\KevinStreetsFunctionApp\KevinStreetsFunctionApp.zip"

And finally deploy!

$Result = Invoke-WebRequest -Uri $ApiUrl -Headers @{Authorization=("Basic {0}" -f $Base64AuthInfo)} -Method POST -InFile $ZipFileLocation -ContentType "multipart/form-data"

As mentioned above, this should return immediately, and you can this by checking the content of the $Result variable. If all has gone well, you will get a StatusCode 202 meaning it has been accepted.

Feedback from the deployment showing that the deployment has been accepted and the URL that can be used to monitor the deployment
Deployment has been accepted

As previously mentioned, you can now monitor the status of the deployment. The URL you use to monitor the deployment is contained in the Headers in the return of the previous request. You can view it by looking in $Result.Headers.Location:

The URL to monitor the deployment
$Result.Headers.Location

To query the status of the deployment, run the following command:

$Complete = Invoke-WebRequest -Uri $($Result.Headers.Location) -Headers @{Authorization=("Basic {0}" -f $base64AuthInfo)} -Method GET
($Complete.Content | ConvertFrom-Json).provisioningState

This will likely return one of two statuses: InProgress or Succeeded. In my experience it takes very little time to complete the deployment. I like to create a small loop that checks the status every 5 seconds until the status is completed:

do {
    $Complete = Invoke-WebRequest -Uri $($Result.Headers.Location) -Headers @{Authorization=("Basic {0}" -f $Base64AuthInfo)} -Method GET
    Write-Host -Object "Current deployment progress: $(($Complete.Content | ConvertFrom-Json).provisioningState)"
    if ($(($Complete.Content | ConvertFrom-Json).provisioningState) -ne "Succeeded") {
        Start-Sleep -Seconds 5
    }
}
until ($(($Complete.Content | ConvertFrom-Json).provisioningState) -eq "Succeeded")

This gives me a nice output that shows when my deployment has completed:

Monitoring status of deployment by querying the monitoring URL every 5 seconds until the deployment has succeeded
Monitoring status of deployment

That is the foundation needed to deploy a Function App with PowerShell. If you have multiple Function Apps you could wrap this code around a loop to deploy each in turn or include other configuration changes as part of a deployment pipeline.

Leave a comment