powershell invoke-restmethod multipart/form-data

后端 未结 6 585
不思量自难忘°
不思量自难忘° 2020-12-01 09:50

I\'m currently trying to upload a file to a Webserver by using a REST API. And as mentioned I\'m using PowerShell for this. With curl this is no problem. The call looks like

相关标签:
6条回答
  • 2020-12-01 10:24

    @Bacon-Bits answer didn't seem to work for me. My server rejected it with a potentially malformed form-data body :-(

    I found this gist, and trimmed it up a bit for my purposes. Here's my end result:

    $FilePath = 'c:\temp\temp.txt';
    $URL = 'http://your.url.here';
    
    $fileBytes = [System.IO.File]::ReadAllBytes($FilePath);
    $fileEnc = [System.Text.Encoding]::GetEncoding('UTF-8').GetString($fileBytes);
    $boundary = [System.Guid]::NewGuid().ToString(); 
    $LF = "`r`n";
    
    $bodyLines = ( 
        "--$boundary",
        "Content-Disposition: form-data; name=`"file`"; filename=`"temp.txt`"",
        "Content-Type: application/octet-stream$LF",
        $fileEnc,
        "--$boundary--$LF" 
    ) -join $LF
    
    Invoke-RestMethod -Uri $URL -Method Post -ContentType "multipart/form-data; boundary=`"$boundary`"" -Body $bodyLines
    
    0 讨论(0)
  • 2020-12-01 10:27

    It should be pretty straight forward. Taking from this answer:

    $Uri = 'https://server/api/';
    $Headers = @{'Auth_token'=$AUTH_TOKEN};
    $FileContent = [IO.File]::ReadAllText('C:\test\test.test');
    $Fields = @{'appInfo'='{"name": "test","description": "test"}';'uploadFile'=$FileContent};
    
    Invoke-RestMethod -Uri $Uri -ContentType 'multipart/form-data' -Method Post -Headers $Headers -Body $Fields;
    

    You may want to use [IO.File]::ReadAllBytes() if the file isn't a text file.

    This also may not work well if you're uploading a huge file.

    0 讨论(0)
  • 2020-12-01 10:29

    With PowerShell Core this should work out of the box with the new -Form parameter.

    See: https://docs.microsoft.com/en-us/powershell/module/microsoft.powershell.utility/invoke-restmethod?view=powershell-7

    $Uri = 'https://api.contoso.com/v2/profile'
    $Form = @{
        firstName  = 'John'
        lastName   = 'Doe'
        email      = 'john.doe@contoso.com'
        avatar     = Get-Item -Path 'c:\Pictures\jdoe.png'
        birthday   = '1980-10-15'
        hobbies    = 'Hiking','Fishing','Jogging'
    }
    $Result = Invoke-RestMethod -Uri $Uri -Method Post -Form $Form
    
    0 讨论(0)
  • 2020-12-01 10:33

    So, I've battled with this quite a bit lately and discovered it is indeed possible to match curl functionality, but it's not immediately obvious how to do multipart/form-data correctly. All the responses above have covered important pieces of the puzzle, but I'm going to try and tie it all together here for the next sorry fellow who is trying to implement curl functionality in native Powershell.

    @jklemmack's solution is the one that put me on the right track, and is the most flexible, because it allows you to construct the form-data content specifically, controlling both the boundaries, along with how the data gets formatted within it.

    For anyone trying to do this, I think it's important that you arm yourself with a proper web debugging proxy like Fiddler (.net) or Burp Suite (java), so that you can inspect each of the REST calls in detail to understand the specific format of the data being passed across to the API.

    In my specific case, I noticed that curl was inserting a blank line above each part of the form data - so to extend @jklemmack's example, it would look like the following:

        $bodyLines = (
            "--$boundary",
            "Content-Disposition: form-data; name=`"formfield1`"",
            '',
            $formdata1,
            "--$boundary",
            "Content-Disposition: form-data; name=`"formfield2`"",
            '',
            $formdata2,
            "--$boundary",
            "Content-Disposition: form-data; name=`"formfield3`"; filename=`"$name_of_file_being_uploaded`"",
            "Content-Type: application/json",
            '',
            $content_of_file_being_uploaded,
            "--$boundary--"
        ) -join $LF
    
    

    Hope this saves someone a lot of time in the future!

    I also still agree that if you need to do this from scratch, and have the option of using the curl native binary directly (while ensuring due-diligence around security and compliance), that you take advantage of it's maturity and the conveniences that it provides. Use curl. It is better that this multipart logic be vigourously tested and maintained by the curl community at large, vs the onus being on your internal dev or operations teams.

    0 讨论(0)
  • 2020-12-01 10:36

    I had some troubles trying to do the following curl command using Invoke-RestMethod:

    curl --request POST \
      --url https://example.com/upload_endpoint/ \
      --header 'content-type: multipart/form-data' \
      --form 'file=@example.csv'
      -v
    

    In my case, it turned out to be much easier to just use curl directly inside powershell.

    $FilePath = "C:\example.csv"
    $CurlExecutable = "C:\curl-7.54.1-win64-mingw\bin\curl.exe"
    
    $CurlArguments = '--request', 'POST', 
                    'https://example.com/upload_endpoint/',
                    '--header', "'content-type: multipart/form-data'",
                    '--form', "file=@$FilePath"
                    '-v',
    
    # Debug the above variables to see what's going to be executed
    Write-Host "FilePath" $FilePath
    Write-Host "CurlExecutable" $FilePath
    Write-Host "CurlArguments" $CurlArguments
    
    # Execute the curl command with its arguments
    & $CurlExecutable @CurlArguments
    

    Just grab the executable for your os on curl's website.

    Why should I use curl instead of powershell's invoke-restmethod?

    • Curl is free and open source software
    • Easy to debug and supports many operating systems.
    • Can be used in other shells as well
    • Supports multiple protocols
    • Many of the tools out there can generate curl commands
    • Supports uploading files larger than 2GB (thanks for heads up Shukri Adams)
    0 讨论(0)
  • 2020-12-01 10:37

    I needed to pass both the header and some more parameters (insert=true and debug=true) along with the file content. Here's my version which extends the script by @jklemmack.

    param([string]$path)
    
    $Headers = @{Authorization = "Bearer ***************"}
    $Uri = 'https://host:8443/api/upload'
    
    $fileBytes = [System.IO.File]::ReadAllBytes($path);
    $fileEnc = [System.Text.Encoding]::GetEncoding('ISO-8859-1').GetString($fileBytes);
    $boundary = [System.Guid]::NewGuid().ToString(); 
    $LF = "`r`n";
    
    $bodyLines = ( 
        "--$boundary",
        "Content-Disposition: form-data; name=`"insert`"$LF",
        "true$LF",
        "--$boundary",
        "Content-Disposition: form-data; name=`"debug`"$LF",
        "true$LF",    
        "--$boundary",
        "Content-Disposition: form-data; name=`"file`"; filename=`"$path`"",
        "Content-Type: application/octet-stream$LF",
        $fileEnc,
        "--$boundary--$LF" 
    ) -join $LF
    
    Invoke-RestMethod -Uri $Uri -Headers $Headers -Method Post -ContentType "multipart/form-data; boundary=`"$boundary`"" -Body $bodyLines
    
    0 讨论(0)
提交回复
热议问题