I want to attach a file to an jira issue , i am able to do it with postman, but have tried several ways without break through.
my code looks like this
function ConvertTo-Base64($string) {
$bytes = [System.Text.Encoding]::UTF8.GetBytes($string);
$encoded = [System.Convert]::ToBase64String($bytes);
return $encoded;
}
function Get-HttpBasicHeader([string]$username, [string]$password, $Headers = #{}) {
$b64 = ConvertTo-Base64 "$($username):$($Password)"
$Headers["Authorization"] = "Basic $b64"
$Headers["X-Atlassian-Token"] = "nocheck"
return $Headers
}
$restapiuri = "https://xxxx.xxxx.com/rest/api//2/issue/test-8442/attachments"
$headers = Get-HttpBasicHeader "xxxxxx" "xxxxxxxx"
$myfile = "C:\TEMP\out.txt"
$fileBytes = [System.IO.File]::ReadAllBytes($myfile);
$fileEnc = [System.Text.Encoding]::GetEncoding('UTF-8').GetString($fileBytes);
$boundary = [guid]::NewGuid().ToString()
$LF = "`r`n";
$body = '(
"--$boundary",
"Content-Disposition: form-data; name=`"fil`"; filename=`"out.txt`"",
"Content-Type: application/octet-stream$LF",
$fileEnc,
"--$boundary--$LF"
) -join $LF
'
Invoke-RestMethod -uri $restapiuri -Headers $headers -Method POST -ContentType "multipart/form-data; boundary=`"$boundary`"" -Body $body
In postman i am doing a post request with
Authorization
Basic Auth
headers
header X-Atlassian-Token = o-check
Content-Type = multipart/form-data
Body form-data
key value
file filepath
The powershell doesn't return any errors, but it does not attach any file either
I have tried several examples without luck so if any of you have any ideas on have to do this in powershell i would be glad.
I am on powershell version
Major Minor Build Revision
5 1 14409 1018
I found the following that looks to be a good fit for this scenario.
function Upload-JiraFile($jiraTicket, $filepath, $authorization)
{
$wc = new-object System.Net.WebClient
$wc.Headers.Add("Authorization", $authorization)
$wc.Headers.Add("X-Atlassian-Token", "nocheck")
$wc.UploadFile("$URIPath/issue/$jiraTicket/attachments", $filepath)
}
Here is how you could use it with the bare minimal modification to the code you have today:
$URIPath = "https://xxxx.xxxx.com/rest/api/2"
Upload-JiraFile -JiraTicket test-8442 -FilePath c:\temp\MyJpg.jpg `
-Authorization $Headers["Authorization"]
Excerpted from this thread.
Related
I'm trying to upload a file to an ASP.NET Core API using PowerShell. My problem is that the API returns a status code 400 saying that form values are missing. Here's my PowerShell code:
add-type -AssemblyName System.Net.Http
$boundary = [System.Guid]::NewGuid().ToString()
$multipartContent = [System.Net.Http.MultipartFormDataContent]::new($boundary)
$multipartContent.Headers.Remove("Content-Type")
$multipartContent.Headers.TryAddWithoutValidation("Content-Type", "multipart/form-data; boundary=`"$boundary`"")
$stringContent = [System.Net.Http.StringContent]::new("This is a file I'm uploading")
$multipartContent.Add($stringContent, "Description")
$FilePath = "c:\path\to\file.json"
$FileStream = [System.IO.File]::OpenRead($FilePath)
$streamContent = [System.Net.Http.StreamContent]::new($FileStream)
$streamContent.Headers.ContentType = "application/json"
$multipartContent.Add($streamContent, "TheFile", "file.json")
$body = $multipartContent
$response = Invoke-RestMethod 'https://myapi.com/uploadfile' -Method 'POST' -Body $body
The code is pretty much migrated from a C# sample I have and is working. Can anyone see what I am doing wrong there?
As per my comment above, the Web cmdlets in PowerShell did not directly support mutlipart/form-data last I checked and most of the working examples create some kind of http template for the -Body parameter.
Here's a working sample:
$boundary = [System.Guid]::NewGuid().ToString()
$FilePath = "c:\path\to\file.json"
$TheFile = [System.IO.File]::ReadAllBytes($FilePath)
$TheFileContent = [System.Text.Encoding]::GetEncoding('iso-8859-1').GetString($TheFile)
$LF = "`r`n"
$bodyLines = (
"--$boundary",
"Content-Disposition: form-data; name=`"Description`"$LF",
"This is a file I'm uploading",
"--$boundary",
"Content-Disposition: form-data; name=`"TheFile`"; filename=`"file.json`"",
"Content-Type: application/json$LF",
$TheFileContent,
"--$boundary--$LF"
) -join $LF
Invoke-RestMethod 'https://myapi.com/uploadfile' -Method POST -ContentType "multipart/form-data; boundary=`"$boundary`"" -Body $bodyLines
Your other option if you want to keep it as close as possible to the C# code you have is to use HttpClient or whatever you are currently using instead of the web cmdlets in PowerShell.
Update: PowerShell 7+ does include a -Form parameter on both Web Cmdlets. Please see the linked examples from the official documentation:
Invoke-RestMethod
Invoke-WebRequest
Using Postman the API call works. I used their code snippet feature to get the PowerShell equivalent. But when I try it on PowerShell ISE I get "Empty request body not allowed". Why does it think the body is empty? How can I confirm/look at the contents of $body?
using assembly System.Net.Http
using namespace System.Net.Http
$headers = New-Object "System.Collections.Generic.Dictionary[[String],[String]]"
$headers.Add("Authorization", "Bearer token")
$multipartContent = [System.Net.Http.MultipartFormDataContent]::new()
$multipartFile = 'C:\Users\xxx\Desktop\msg.json'
$FileStream = [System.IO.FileStream]::new($multipartFile, [System.IO.FileMode]::Open)
$fileHeader = [System.Net.Http.Headers.ContentDispositionHeaderValue]::new("form-data")
$fileHeader.Name = "ParameterRequest"
$fileContent = [System.Net.Http.StreamContent]::new($FileStream)
$fileContent.Headers.ContentDisposition = $fileHeader
$multipartContent.Add($fileContent)
$FileStream.Flush()
$FileStream.Close()
$multipartFile = 'C:\Users\xxx\Desktop\edm_payload'
$FileStream = [System.IO.FileStream]::new($multipartFile, [System.IO.FileMode]::Open)
$fileHeader = [System.Net.Http.Headers.ContentDispositionHeaderValue]::new("form-data")
$fileHeader.Name = "MessagePayload"
$fileContent = [System.Net.Http.StreamContent]::new($FileStream)
$fileContent.Headers.ContentDisposition = $fileHeader
$multipartContent.Add($fileContent)
$FileStream.Flush()
$FileStream.Close()
$body = $multipartContent
$response = Invoke-RestMethod -Method Post -Uri 'https://APIsite' -Headers $headers -Body $body -ContentType "multipart/form-data"
When I look at the contents of $body this is what I get. How can I see the data in each Content?
PS C:\Users\user> $body
Headers
-------
{[Content-Disposition, System.String[]]}
{[Content-Disposition, System.String[]]}
Added -Verbose to the Invoke-RestMethod and I see
VERBOSE: POST https://APIsite with -1-byte payload
I cannot find what is the meaning of "-1-byte payload". Anybody knows?
Solved. Part of the problem was that I was using PowerShell v5, the MultipartFormDataContent has been improved since then. Updated to version 7 of PowerShell, made a few tweaks... Success!
$formContent = New-Object -TypeName 'System.Net.Http.MultipartFormDataContent'
$filePath = "C:\Users\xxx\Desktop\msg.json"
$fileStream1 = [System.IO.File]::Open($filePath, [System.IO.FileMode]::Open, [System.IO.FileAccess]::Read)
$formContent.Add([System.Net.Http.StreamContent]::new($fileStream1), "ParameterRequest", (Split-Path $filePath -leaf))
$filePath = "C:\Users\xxx\Desktop\edm_payload"
$fileStream2 = [System.IO.File]::Open($filePath, [System.IO.FileMode]::Open, [System.IO.FileAccess]::Read)
$formContent.Add([System.Net.Http.StreamContent]::new($fileStream2), "MessagePayload", (Split-Path $filePath -leaf))
Invoke-RestMethod -Method Post -Uri 'https://APIsite' -Headers $requestHeader -Body $formContent -ContentType 'multipart/form-data'
Also - close your streams after the API call
When i am creating confluence paging using below powershell code, it works for smaller html codes but word documents converted to HTML throws error.
Here is the powershell code
$ConfluenceURL = "https://wiki-test.company.com/confluence/rest/api/content"
$cred = Get-Credential
$BODYVALUE=Get-Content 'C:\Users\test.html'
$Headers = #{'Authorization' = "Basic "+[System.Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes(($cred.UserName+":"+[System.Runtime.InteropServices.marshal]::PtrToStringAuto([System.Runtime.InteropServices.marshal]::SecureStringToBSTR($cred.Password)) )))
'X-Atlassian-Token' = 'nocheck'
}
$pageTitle = "convert1"
$space = "DEMO"
$body =
"{
`"type`":`"page`",
`"title`":`"$pageTitle`",
`"space`": {`"key`":`"$space`"},
`"body`":{
`"storage`":{
`"representation`":`"storage`",
`"value`":`"<p>$BODYVALUE</p>`"
}
}
}"
Invoke-WebRequest -Method POST -Headers $Headers -Uri $ConfluenceURL -Body $body -ContentType "application/json" | ConvertFrom-Json
I'm trying to use the Invoke-RestMethod cmdlet in PowerShell 3 and 4, to upload a large binary file using a REST API's multipart/form-data upload. Here is a working cURL example on how to perform what I want to do in PowerShell:
curl -i -k -H "accept: application/json" -H "content-type: multipart/form-data" -H "accept-language: en-us" -H "auth: tokenid" -F file="#Z:\large_binary_file.bin" -X POST "https://server/rest/uri2"
I would love to see a working example on how to use Invoke-RestMethod to POST a multipart/form-data. I found a blog post from the PowerShell team showing how to use Invoke-RestMethod to upload to OneDrive (aka SkyDrive), but doesn't work well. I'd also like to avoid using System.Net.WebClient if at all possible. I also found another thread here on Stackoverflow, but it really didn't help much.
[System.Net.ServicePointManager]::ServerCertificateValidationCallback = { $true }
$server = "https://server"
uri = "/rest/uri1"
$headers = #{"accept" = "application/json"; "content-type" = "application/json";"accept-language" = "en-us"}
$body = #{"userName" = "administrator"; "password" = "password"}
$method = "POST"
#Get Session ID
$resp = Invoke-RestMethod -Method $method -Headers $headers -Uri ($server+$uri) -body (convertto-json $Body -depth 99)
$sessionID = $resp.sessionID
#Upload file
$uri = "/rest/uri2"
$headers = #{"accept" = "application/json";"content-type" = "multipart/form-data"; "accept- language" = "en-us"; "auth" = $sessionID}
$medthod = "POST"
$largeFile = "Z:\large_binary_file.bin"
I have tried both ways of using Invoke-RestMethod:
Invoke-RestMethod -Method $method -Headers $headers -Uri ($server+$uri) -InFile $largeFile
or
$body = "file=$(get-content $updateFile -Enc Byte -raw)"
Invoke-RestMethod -Method $method -Headers $headers -Uri ($server+$uri) -body $body
I notice couple of mistakes in your invoke statement. First, you need to use POST keyword instead of string value. Second, make sure that Content-Type is set to multipart/form-data. So this is my revised statement -
$uri = "http://blahblah.com"
$imagePath = "c:/justarandompic.jpg"
$upload= Invoke-RestMethod -Uri $uri -Method Post -InFile $imagePath -ContentType 'multipart/form-data'
You can check the response from the server by checking $upload.
I'm trying to send a file via Invoke-RestMethod in a similar context as curl with the -F switch.
Curl Example
curl -F FileName=#"/path-to-file.name" "https://uri-to-post"
In powershell, I've tried something like this:
$uri = "https://uri-to-post"
$contentType = "multipart/form-data"
$body = #{
"FileName" = Get-Content($filePath) -Raw
}
Invoke-WebRequest -Uri $uri -Method Post -ContentType $contentType -Body $body
}
If I check fiddler I see that the body contains the raw binary data, but I get a 200 response back showing no payload has been sent.
I've also tried to use the -InFile parameter with no luck.
I've seen a number of examples using a .net class, but was trying to keep this simple with the newer Powershell 3 commands.
Does anyone have any guidance or experience making this work?
The accepted answer won't do a multipart/form-data request, but rather a application/x-www-form-urlencoded request forcing the Content-Type header to a value that the body does not contain.
One way to send a multipart/form-data formatted request with PowerShell is:
$ErrorActionPreference = 'Stop'
$fieldName = 'file'
$filePath = 'C:\Temp\test.pdf'
$url = 'http://posttestserver.com/post.php'
Try {
Add-Type -AssemblyName 'System.Net.Http'
$client = New-Object System.Net.Http.HttpClient
$content = New-Object System.Net.Http.MultipartFormDataContent
$fileStream = [System.IO.File]::OpenRead($filePath)
$fileName = [System.IO.Path]::GetFileName($filePath)
$fileContent = New-Object System.Net.Http.StreamContent($fileStream)
$content.Add($fileContent, $fieldName, $fileName)
$result = $client.PostAsync($url, $content).Result
$result.EnsureSuccessStatusCode()
}
Catch {
Write-Error $_
exit 1
}
Finally {
if ($client -ne $null) { $client.Dispose() }
if ($content -ne $null) { $content.Dispose() }
if ($fileStream -ne $null) { $fileStream.Dispose() }
if ($fileContent -ne $null) { $fileContent.Dispose() }
}
The problem here was what the API required some additional parameters. Initial request required some parameters to accept raw content and specify filename/size. After setting that and getting back proper link to submit, I was able to use:
Invoke-RestMethod -Uri $uri -Method Post -InFile $filePath -ContentType "multipart/form-data"
I found this post and changed it a bit
$fileName = "..."
$uri = "..."
$currentPath = Convert-Path .
$filePath="$currentPath\$fileName"
$fileBin = [System.IO.File]::ReadAlltext($filePath)
$boundary = [System.Guid]::NewGuid().ToString()
$LF = "`r`n"
$bodyLines = (
"--$boundary",
"Content-Disposition: form-data; name=`"file`"; filename=`"$fileName`"",
"Content-Type: application/octet-stream$LF",
$fileBin,
"--$boundary--$LF"
) -join $LF
Invoke-RestMethod -Uri $uri -Method Post -ContentType "multipart/form-data; boundary=`"$boundary`"" -Body $bodyLines
For anyone wondering (like Jelphy) whether David's answer can be used with cookies/credentials, the answer is yes.
First set the session with Invoke-WebRequest:
Invoke-WebRequest -Uri "$LoginUri" -Method Get -SessionVariable 'Session'
Then POST to the Login URL, which stores the authentication cookie in $Session:
$Response = Invoke-WebRequest -Uri "$Uri" -Method Post -Body $Body -WebSession $Session
The steps above are the standard way to deal with session in Powershell. But here is the important part. Before creating the HttpClient, create an HttpClientHandler and set it's CookieContainer property with the cookies from the session:
$ClientMessageHandler = New-Object System.Net.Http.HttpClientHandler
$ClientMessageHandler.CookieContainer = $Session.Cookies
Then pass this object to the HttpClient constructor
$Client = [System.Net.Http.HttpClient]::new($ClientMessageHandler)
Voila, you now have an HttpClient with session cookies set automatically via Invoke-WebRequest. The rest of David's example should work (copied here for completeness):
$MultipartFormData = New-Object System.Net.Http.MultipartFormDataContent
$FileStream = [System.IO.File]::OpenRead($FilePath)
$FileName = [System.IO.Path]::GetFileName($FilePath)
$FileContent = New-Object System.Net.Http.StreamContent($FileStream)
$MultipartFormData.Add($FileContent, $FieldName, $FileName)
$Result = $Client.PostAsync($url, $content).Result
$Result.EnsureSuccessStatusCode()
I had many files to upload with each request, so I factored out this last bit into a lambda function:
function Add-FormFile {
param ([string]$Path, [string]$Name)
if ($Path -ne "")
{
$FileStream = [System.IO.File]::OpenRead($Path)
$FileName = [System.IO.Path]::GetFileName($Path)
$FileContent = [System.Net.Http.StreamContent]::new($FileStream)
$MultipartFormData.Add($FileContent, $Name, $FileName)
}
}