Azure AppService Deployment Error 400 When Using Zip Deploys
Posted on February 16, 2025
- and tagged as
- azure,
- appservice,
- powershell
Had an interesting issue where Azure AppService (.NET Core Runtime) zip deployments via Azure CLI were failing with An error occurred during deployment. Status Code: 400
, and a link to the deployment log. The log didn’t have any particularly useful information:
Running build. Project type: OneDeploy
Copying the manifest
Incrementally deploying to /home/site/wwwroot
Deployment Failed. deployer = OneDeploy deploymentPath = OneDeploy
However, viewing the trace logs (~/LogFiles/kudu/trace
) via Kudu showed something interesting:
Error occurred, type: error, text: Invalid argument : '/home/site/wwwroot/wwwroot\favicon.png', stackTrace: at Microsoft.Win32.SafeHandles.SafeFileHandle.Open(String , OpenFlags , Int32 )
at Microsoft.Win32.SafeHandles.SafeFileHandle.Open(String , FileMode , FileAccess , FileShare , FileOptions , Int64 )
at System.IO.FileSystem.CopyFile(String , String , Boolean )
at System.IO.File.Copy(String , String , Boolean )
at System.IO.Abstractions.FileWrapper.Copy(String sourceFileName, String destFileName, Boolean overwrite)
at Kudu.Core.Infrastructure.FileSystemHelpers.CopyDirectoryRecursive(String sourceDirPath, String destinationDirPath, Boolean overwrite) in /tmp/KuduLite/Kudu.Core/Infrastructure/FileSystemHelpers.cs:line 239
at Kudu.Core.Deployment.Generator.OneDeployBuilder.Build(DeploymentContext context, CancellationToken token) in /tmp/KuduLite/Kudu.Core/Deployment/Generator/OneDeployBuilder.cs:line 92
at Kudu.Core.Deployment.DeploymentManager.Build(ChangeSet changeSet, ITracer tracer, IDisposable deployStep, IRepository repository, DeploymentInfoBase deploymentInfo, DeploymentAnalytics deploymentAnalytics, Boolean fullBuildByDefault, CancellationToken cancellationToken) in /tmp/KuduLite/Kudu.Core/Deployment/DeploymentManager.cs:line 762
at Kudu.Core.Deployment.DeploymentManager.Build(ChangeSet changeSet, ITracer tracer, IDisposable deployStep, IRepository repository, DeploymentInfoBase deploymentInfo, DeploymentAnalytics deploymentAnalytics, Boolean fullBuildByDefault, CancellationToken cancellationToken) in /tmp/KuduLite/Kudu.Core/Deployment/DeploymentManager.cs:line 831
You may notice the path /home/site/wwwroot/wwwroot\favicon.png
looks a little funky. It’s all forward slashes until the filename and then it changes to a backslash. That is what is causing the deployment issue.
For a little context on my environment as it is relevant here, I’m doing development on Windows, but deploying to a Linux AppService instance. The deployment is being done via Az CLI though a PowerShell deployment function:
function BuildAndDeploy {
$ProjectPath = "C:\Path\To\Project"
$BinRelativePath = "bin\Release\net8.0\linux-x64\publish"
$FullArchivePath = "C:\Path\To\Deployment.zip"
$RGName = "RGName"
$AppName = "AppServiceName"
cd $ProjectPath
dotnet build --no-restore --configuration Release --os linux
dotnet publish --no-build --configuration Release --os linux
Compress-Archive -Path "$ProjectPath\$BinRelativePath\*" -DestinationPath $FullArchivePath -Force
az webapp deploy --resource-group $RGName --name $AppName --src-path $FullArchivePath --type zip --async false --restart
}
The issue lies with how Compress-Archive
creates zip files. Let’s examine one in a hex editor:
You’ll notice the backslash just before the highlighted favicon
text in the image.
Here’s the same view but with the zip file generated manually by right clicking in Explorer and clicking Send to → Compressed (zipped) folder:
Now we’ve got a forward slash and our deployment works. However, I don’t want to be manually clicking around to create a zip file every time, so it may be good to know that PowerShell 7 also generates the zip file correctly using forward slashes:
There is also the option to use the static method [System.IO.Compression.ZipFile]::CreateFromDirectory
but this suffers from the same issue if you’re on PowerShell 5.1 (which uses .NET Framework), and works fine with PowerShell 7 (which is built on .NET Core). Compress-Archive
may be using this method under the hood 🤷♂️.
[System.IO.Compression.ZipFile]::CreateFromDirectory($FullSourcePath, $FullDestinationPath)
There is also some Microsoft documentation on this issue here.
In short - Use PowerShell 7 if generating zip files for AppService deployments on Windows for a Linux instance. To ensure your deployment script isn’t accidentally ran from PowerShell 5.1 you can add a requires statement to the top of the file:
#requires -version 7.0
This will generate an error if attempting to run the script on an older version of PowerShell and may save you some time having to dig into deployment failures.
PS > .\deploy.ps1
.\deploy.ps1 : The script 'deploy.ps1' cannot be run because it contained a "#requires" statement for Windows PowerShell 7.0. The version of Windows PowerShell that is required by the script does not match the
currently running version of Windows PowerShell 5.1.19041.4412.