Deploy API App using Powershell and FTP

Deploying an API App to Azure to a large extent resembles deploying a Logic App. See link: Deploy Logic App. The difference is that an API App contains executable code that needs to be built. The trick here is to build a web deploy package in Visual Studio. From that web deploy package we get the files that need to be uploaded to (kudu). We upload these files using FTP. For FTP to work we need to know the FTP URL to publish to, the FTP userand the FTP password. This information is contained in the so-called Publish Profile of the API App. We can get the publish profile of the API App via Powershell.

For reusability I have created a Powershell module (*.psm) with a function named UploadToFTP. This function contains functionality to retrieve the publish profile, to create the necessary folders and upload the dll’s, configs and other content files. The code is shown below:

function UploadToFTP
param (
[parameter(Mandatory = $true)][string] $webAppName,
[parameter(Mandatory = $true)][string] $rg,
[parameter(Mandatory = $true)][string] $sourceDir

# Get publishing profile for the web app
[xml]$xml = (Get-AzureRmWebAppPublishingProfile -Name $webAppName -ResourceGroupName $rg -OutputFile null)
#Write-Host “PublishingProfile: ” $xml –fore gray;

# Extract connection information from publishing profile
$username = $xml.SelectSingleNode(“//publishProfile[@publishMethod=’FTP’]/@userName”).value
$password = $xml.SelectSingleNode(“//publishProfile[@publishMethod=’FTP’]/@userPWD”).value
$url = $xml.SelectSingleNode(“//publishProfile[@publishMethod=’FTP’]/@publishUrl”).value
Write-Host “FTP Url: ” $url –fore gray;

# Upload files recursively
Write-Host “Source Directory: ” $sourceDir –fore gray;
Set-Location $sourceDir
$webclient = New-Object -TypeName System.Net.WebClient
$webclient.Credentials = New-Object System.Net.NetworkCredential($username,$password)

# Get folders and files
$SrcEntries = Get-ChildItem $sourceDir -Recurse
$Srcfolders = $SrcEntries | Where-Object{$_.PSIsContainer}
$SrcFiles = $SrcEntries | Where-Object{!$_.PSIsContainer}

# Create subdirectories
foreach($folder in $Srcfolders)

$SrcFolderPath = $sourceDir -replace “\\”,”\\” -replace “\:”,”\:”
$DesFolder = $folder.Fullname -replace $SrcFolderPath,($url+’\’)
Write-Host “FTP folder fullname: ” $folder.Fullname –fore gray;
$DesFolder = $DesFolder -replace “\\”, “/”
Write-Host “FTP folder: ” $DesFolder –fore gray;

$makeDirectory = [System.Net.WebRequest]::Create($DesFolder);
$makeDirectory.Credentials = New-Object System.Net.NetworkCredential($username,$password);
$makeDirectory.Method = [System.Net.WebRequestMethods+FTP]::MakeDirectory;
Write-Host “FTP folder: ” $DesFolder –fore gray;
catch [Net.WebException] {
Write-Host “FTP folder not created. Possibly it already existed: ” $DesFolder –fore gray;

# Upload files from deployment package
foreach($file in $SrcFiles)
$SrcFullname = $file.fullname
$SrcName = $file.Name
$SrcFilePath = $sourceDir -replace “\\”,”\\” -replace “\:”,”\:”
$DesFile = $SrcFullname -replace $SrcFilePath,($url+’\’)
$DesFile = $DesFile -replace “\\”, “/”
Write-Host “SrcFile: ” $SrcFullname –fore yellow;
Write-Host “DesFile: ” $DesFile –fore yellow;

$uri = New-Object System.Uri($DesFile)
$webclient.UploadFile($uri, $SrcFullname)
#Write-Host “File uploaded: ” $SrcFullname –fore yellow;


return ‘true’


Another thing that is important for API Apps is to automatically set the App Settings. Below is the necessary Powershell script:

# Add ApplicationSettings
$appSettingsNav4PSGatewayClient = @{“WEBSITE_NODE_DEFAULT_VERSION”=$WEBSITE_NODE_DEFAULT_VERSION;”Nav4PSGatewayUrl”=$nav4PSGatewayUrl;”Nav4PSUser”=$nav4PSGatewayUser;”Nav4PSPassword”=$nav4PSGatewayPassword}
Set-AzureRmWebApp -Name $nameNav4PSGatewayClient -AppSettings $appSettingsNav4PSGatewayClient -ResourceGroupName $resourcegroup
Write-Host $(Get-Date).ToString(“yyyyMMdd_HHmss”) ” App settings ” $nameNav4PSGatewayClient” updated” –fore green

Beware. You have to use unique names for Appsetting variables. In my case I had two apps: a DSPGateway (for incoming traffic) and a DSPConnector (for outgoing traffic). Both apps needed a username/password for authentication. I named these variabeles DSPUserName and DSPPassword for both apps. That’s wrong. You have to use unique names. So, for example: DSPGatewayUser, DSPConnectorUser, DSPGatewayPassword and DSPConnectorPassword.