使用PowerShell迁移Azure WebApp日志至存储账户遇问题求助
Azure WebApp日志备份至存储账户并删除源文件的问题解决
问题概述
尝试通过PowerShell将Azure WebApp的日志文件复制到Azure存储账户容器,完成后删除源日志,但遇到以下两个问题:
- 复制到Blob时出现警告,无文件被复制:
WARNING: Ignore mismatch source storage context.. The source uri is https://wa01.scm.azurewebsites.net/api/vfs/LogFiles/kudu/, the end point is https://sa01.blob.core.windows.net/.
- 删除操作报错:
Invoke-RestMethod : {"Message":"Cannot delete directory. It is either not empty or access is not allowed."}
需求:将WebApp中的文件和目录完整复制到存储容器,完成后删除源文件和目录。
问题1:复制Blob失败的解决方案
Start-AzStorageBlobCopy仅支持Azure存储服务之间的直接复制,或带有SAS授权的外部URL,无法直接从Kudu的HTTP API URL复制文件。正确做法是先通过Kudu API下载日志文件到本地临时目录,再上传至存储账户。同时需补充缺失的$cutoffDate定义和存储账户上下文获取逻辑。
问题2:删除目录失败的解决方案
Kudu API无法直接删除非空目录,必须先递归删除目录内的所有文件,再删除空目录。需修改删除逻辑,先处理文件,再从最深层开始删除空目录。
修正后的完整代码
$webAppName = "WA01" $resourceGroupName = "RG01" $storageAccountName = "SA01" $containerName = "container01" $numberOfDays = 90 $tempDir = Join-Path $env:TEMP "WebAppLogsBackup" # 创建临时目录 if (-not (Test-Path $tempDir)) { New-Item -ItemType Directory -Path $tempDir | Out-Null } # 计算截止日期 $cutoffDate = (Get-Date).AddDays(-$numberOfDays) # 获取存储账户上下文 $storageAccountContext = (Get-AzStorageAccount -ResourceGroupName $resourceGroupName -Name $storageAccountName).Context # Generate Kudu API URL for log files $kuduApiUrl = "https://${webAppName}.scm.azurewebsites.net/api/vfs/LogFiles/" # Retrieve publishing profile for the Azure Web App Write-Host "Retrieving publishing profile for $webAppName..." $publishingProfile = Get-AzWebAppPublishingProfile -ResourceGroupName $resourceGroupName -Name $webAppName # Parse publishing profile XML to get credentials [xml]$publishingProfileXml = $publishingProfile $publishingUserName = $publishingProfileXml.publishData.publishProfile[0].userName $publishingPassword = $publishingProfileXml.publishData.publishProfile[0].userPWD # Convert credentials to base64 $base64AuthInfo = [Convert]::ToBase64String([Text.Encoding]::UTF8.GetBytes("${publishingUserName}:${publishingPassword}")) $headers = @{Authorization=("Basic $base64AuthInfo")} # 递归获取所有符合条件的文件和目录 function Get-KuduItems { param( [string]$uri, [DateTime]$cutoffDate ) $items = Invoke-RestMethod -Uri $uri -Headers $headers -Method GET $result = @() foreach ($item in $items) { if ($item.type -eq "file") { $mtimeValue = $item.mtime.Split('.')[0].Substring(0, 19) $fileDate = [DateTime]::ParseExact($mtimeValue, "yyyy-MM-dd'T'HH:mm:ss", [System.Globalization.CultureInfo]::InvariantCulture) if ($fileDate -lt $cutoffDate) { $result += $item } } elseif ($item.type -eq "directory") { # 递归获取子目录中的文件 $result += Get-KuduItems -uri $item.href -cutoffDate $cutoffDate # 记录目录本身,后续删除 $result += $item } } return $result } Write-Host "Retrieving log files and directories from $webAppName..." $allItems = Get-KuduItems -uri $kuduApiUrl -cutoffDate $cutoffDate # 分离文件和目录(文件先处理,目录最后删除,按路径长度倒序确保从最深目录开始删) $filesToBackup = $allItems.Where({$_.type -eq "file"}) $dirsToDelete = $allItems.Where({$_.type -eq "directory"}) | Sort-Object {$_.href.Length} -Descending # Perform backup actions... Write-Host "Performing backup actions for $webAppName..." foreach ($file in $filesToBackup) { $logFileName = $file.name $logFileUrl = $file.href $localFilePath = Join-Path $tempDir $logFileName Write-Host "Downloading log file: $logFileName" Invoke-RestMethod -Uri $logFileUrl -Headers $headers -Method GET -OutFile $localFilePath # 构造Blob路径,保留原目录结构 $relativePath = $logFileUrl.Replace($kuduApiUrl, "") $blobName = "$webAppName/$relativePath" Write-Host "Uploading to blob: $blobName" Set-AzStorageBlobContent -File $localFilePath -Container $containerName -Blob $blobName -Context $storageAccountContext -Force | Out-Null # 删除临时文件 Remove-Item $localFilePath -Force | Out-Null } # Perform deletion actions... Write-Host "Deleting log files older than $numberOfDays days from $webAppName..." foreach ($file in $filesToBackup) { $logFileName = $file.name Write-Host "Deleting log file: $logFileName" Invoke-RestMethod -Uri $file.href -Headers $headers -Method DELETE | Out-Null } Write-Host "Deleting empty directories from $webAppName..." foreach ($dir in $dirsToDelete) { $dirName = $dir.name Write-Host "Deleting directory: $dirName" try { Invoke-RestMethod -Uri $dir.href -Headers $headers -Method DELETE | Out-Null } catch { Write-Warning "Failed to delete directory $dirName (may still contain files)" } } # 清理临时目录 Remove-Item $tempDir -Recurse -Force | Out-Null
关键修改说明
- 补充
$cutoffDate定义,计算90天前的日期,实现按时间筛选日志 - 添加存储账户上下文
$storageAccountContext的获取逻辑,确保上传Blob时权限正常 - 新增递归函数
Get-KuduItems,遍历WebApp日志目录下所有文件和子目录,筛选符合时间条件的项 - 替换
Start-AzStorageBlobCopy为本地下载+Blob上传的流程,解决Kudu URL无法直接复制的问题 - 调整删除顺序:先删除所有符合条件的文件,再从最深层目录开始删除空目录,避免非空目录删除报错
- 添加临时目录的创建与清理逻辑,避免本地文件残留
内容的提问来源于stack exchange,提问作者SKumar
相关产品推荐
相关产品推荐

