You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

PowerShell上传SharePoint遇400错误:如何兼容Zip文件与普通文件夹?

修复PowerShell上传SharePoint时的400错误:兼容Zip文件与文件夹上传

我来帮你搞定这个问题!你的代码出现400错误主要是三个核心问题:错误的请求头、不分类型的强制压缩,以及硬编码的文件名后缀。下面是修复后的完整代码,我会在后面解释关键改动:

param($ClientID,$ClientSecret,$WId,$RName,$RReqFor,$SPid,$LibId)
$tempDirectory = "temp"
# arguments defined from predefined variables in argument section of powershell stage of pipeline
$workingDirectory = "$WId"
$CLIENT_ID = "$ClientID"
$CLIENT_SECRET = "$ClientSecret"
$SharePointSiteId = "$SPid"
$LibraryId = "$LibId"
$azx = "$RName"+"_"+"$RReqFor"
$adx = ($azx.Split("[")[0])
$artifactname = $adx.Substring(0,$adx.Length-1)

# Get access token
Function GET-TOKEN {
    param($clientId, $clientSecret)
    $tokenHeaders = New-Object "System.Collections.Generic.Dictionary[[String],[String]]"
    $tokenHeaders.Add("Content-Type", "application/x-www-form-urlencoded")
    $tokenBody = "client_id="+ $clientId + "&scope=https%3A//graph.microsoft.com/.default&client_secret=" + $clientSecret + "&grant_type=client_credentials"
    $tokenResponse = Invoke-RestMethod 'https://login.microsoftonline.com/test.onmicrosoft.com/oauth2/v2.0/token' -Method 'POST' -Headers $tokenHeaders -Body $tokenBody
    return $tokenResponse
}

# upload the file
Function UPLOAD-FILE {
    param($workingDir, $tempDir, $clientId, $clientSecret)
    Get-ChildItem $workingDir | ForEach-Object {
        $item = $_
        $uploadFileName = $item.Name
        $fileToUpload = $item.FullName

        # 处理文件夹:压缩成Zip
        if ($item.PSIsContainer) {
            $uploadFileName = "$($item.Name).zip"
            $zipPath = Join-Path $item.FullName $tempDir | Join-Path -ChildPath $uploadFileName
            CREATE-ARCHIVE -sourceDir $item.FullName -zipOutputPath $zipPath
            $fileToUpload = $zipPath
        }
        # 处理Zip文件:直接使用原文件;非Zip文件:直接上传原文件(如需压缩可在此添加逻辑)

        # 获取文件信息
        $fileSize = (Get-Item $fileToUpload).Length
        $tokenObject = GET-TOKEN -clientId $clientId -clientSecret $clientSecret
        
        # 获取上传链接
        $uploadURLObject = GET-UPLOADLINK -clientId $clientId -clientSecret $clientSecret -targetFileName $uploadFileName

        # 构建正确的上传请求头
        $uploadHeaders = New-Object "System.Collections.Generic.Dictionary[[String],[String]]"
        $uploadHeaders.Add("Content-Type", "application/octet-stream") # 通用二进制类型,兼容所有文件
        $uploadHeaders.Add("Content-Range", "bytes 0-$($fileSize-1)/$fileSize")
        $uploadHeaders.Add("Content-Length", $fileSize)
        $uploadHeaders.Add("Authorization", "Bearer "+ $tokenObject.access_token)

        # 读取文件内容并上传
        $uploadBody = [System.IO.File]::ReadAllBytes($fileToUpload)
        try {
            $response = Invoke-RestMethod $uploadURLObject.uploadUrl -Method 'PUT' -Headers $uploadHeaders -Body $uploadBody
            Write-Host "Successfully uploaded: $uploadFileName"
            $response | ConvertTo-Json
        }
        catch {
            Write-Error "Failed to upload $uploadFileName : $_"
        }

        # 清理临时目录(仅当处理文件夹时)
        if ($item.PSIsContainer) {
            REMOVE-TEMPDIR -workingDir $item.FullName -tempDir $tempDirectory
        }
    }
}

Function GET-UPLOADLINK {
    param($clientId, $clientSecret, $targetFileName)
    $tokenObject = GET-TOKEN -clientId $clientId -clientSecret $clientSecret
    $uploadLinkRequestHeaders = New-Object "System.Collections.Generic.Dictionary[[String],[String]]"
    $uploadLinkRequestHeaders.Add("Content-Type", "application/json")
    $uploadLinkRequestHeaders.Add("Authorization", "Bearer "+ $tokenObject.access_token)
    $uploadLinkRequestBody = @{} | ConvertTo-Json

    # 动态生成上传路径,不再硬加.zip后缀
    $fullname = "https://graph.microsoft.com/v1.0/sites/$($SharePointSiteId)/drives/$($LibraryId)/root/children/$targetFileName/createUploadSession"
    $uploadLinkResponse = Invoke-RestMethod $fullname -Method 'POST' -Headers $uploadLinkRequestHeaders -Body $uploadLinkRequestBody
    return $uploadLinkResponse
}

Function CREATE-ARCHIVE {
    param($sourceDir, $zipOutputPath)
    Write-Host "Compressing folder: $sourceDir to $zipOutputPath"
    # 创建临时目录(如果不存在)
    $tempDir = Split-Path $zipOutputPath -Parent
    if (-not (Test-Path $tempDir)) {
        New-Item -ItemType Directory -Path $tempDir | Out-Null
    }
    # 压缩文件夹内的所有内容,避免把父目录本身作为根目录
    Compress-Archive -Path "$sourceDir\*" -DestinationPath $zipOutputPath -Force
}

Function REMOVE-TEMPDIR {
    param($workingDir, $tempDir)
    if (Test-Path "$workingDir\$tempDir") {
        Remove-Item "$workingDir\$tempDir" -Recurse -Force
        Write-Host "Cleaned temporary directory: $workingDir\$tempDir"
    }
}

UPLOAD-FILE -workingDir $workingDirectory -tempDir $tempDirectory -clientId $CLIENT_ID -clientSecret $CLIENT_SECRET

关键改动说明

  1. 区分文件与文件夹处理

    • 使用$item.PSIsContainer判断当前项是文件夹还是文件:
      • 文件夹:压缩成Zip后上传
      • 文件:直接上传(Zip文件不二次压缩,非Zip文件原样上传)
    • 如果需要压缩非Zip文件,你可以在文件处理分支里添加类似文件夹的压缩逻辑。
  2. 修正Content-Type请求头

    • 把原来错误的application/json改成application/octet-stream,这是通用的二进制文件MIME类型,兼容所有文件格式,SharePoint能正确识别。
  3. 动态生成上传文件名

    • 修改GET-UPLOADLINK函数,接受targetFileName参数,不再硬加.zip后缀,确保上传的文件名和实际文件一致(比如原Zip文件上传时不会变成xxx.zip.zip)。
  4. 优化压缩逻辑

    • 将Compress-Archive的路径从$workingDir改成$sourceDir\*,这样只会压缩文件夹内的内容,而不是把文件夹本身作为根目录打包,生成的Zip结构更合理。
    • 添加了临时目录的存在性检查,避免创建目录时出错。
  5. 增加错误处理

    • 在上传步骤添加try/catch块,方便排查单个文件上传失败的问题,不会因为一个文件出错导致整个脚本终止。

内容的提问来源于stack exchange,提问作者Ankit Kumar

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.11 07:55:47