我有一个web应用程序,可以处理这样的html表单的POST:
<form action="x" method="post" enctype="multipart/form-data">
<input name="xfa" type="file">
<input name="pdf" type="file">
<input type="submit" value="Submit">
</form>
请注意,有两个type="file"
<input>
元素。
如何从Powershell脚本编写POST脚本?我计划这样做,为服务创建一个简单的测试框架。
我找到了WebClient.UploadFile(),但这只能处理一个文件。
感谢您抽出宝贵时间。
答案 0 :(得分:23)
我今天一直在使用PowerShell制作多部分HTTP POST。我希望下面的代码对您有所帮助。
Invoke-RestMethod
需要PowerShell 3.0,但上面链接中后面的代码显示了如何直接使用.NET进行HTTP POST,允许您在Windows XP中运行它。function Send-Results {
param (
[parameter(Mandatory=$True,Position=1)] [ValidateScript({ Test-Path -PathType Leaf $_ })] [String] $ResultFilePath,
[parameter(Mandatory=$True,Position=2)] [System.URI] $ResultURL
)
$fileBin = [IO.File]::ReadAllBytes($ResultFilePath)
$computer= $env:COMPUTERNAME
# Convert byte-array to string (without changing anything)
#
$enc = [System.Text.Encoding]::GetEncoding("iso-8859-1")
$fileEnc = $enc.GetString($fileBin)
<#
# PowerShell does not (yet) have built-in support for making 'multipart' (i.e. binary file upload compatible)
# form uploads. So we have to craft one...
#
# This is doing similar to:
# $ curl -i -F "file=@file.any" -F "computer=MYPC" http://url
#
# Boundary is anything that is guaranteed not to exist in the sent data (i.e. string long enough)
#
# Note: The protocol is very precise about getting the number of line feeds correct (both CRLF or LF work).
#>
$boundary = [System.Guid]::NewGuid().ToString() #
$LF = "`n"
$bodyLines = (
"--$boundary",
"Content-Disposition: form-data; name=`"file`"$LF", # filename= is optional
$fileEnc,
"--$boundary",
"Content-Disposition: form-data; name=`"computer`"$LF",
$computer,
"--$boundary--$LF"
) -join $LF
try {
# Returns the response gotten from the server (we pass it on).
#
Invoke-RestMethod -Uri $URL -Method Post -ContentType "multipart/form-data; boundary=`"$boundary`"" -TimeoutSec 20 -Body $bodyLines
}
catch [System.Net.WebException] {
Write-Error( "FAILED to reach '$URL': $_" )
throw $_
}
}
答案 1 :(得分:4)
我对此事感到困扰,并没有找到满意的解决方案。虽然这里提出的要点可以做yob,但是在大文件传输的情况下效率不高。我写了一篇博客文章,提出了一个解决方案,将我的cmdlet基于.NET 4.5中的HttpClient类。如果这不是您的问题,您可以在以下地址http://blog.majcica.com/2016/01/13/powershell-tips-and-tricks-multipartform-data-requests/
查看我的解决方案编辑:
function Invoke-MultipartFormDataUpload
{
[CmdletBinding()]
PARAM
(
[string][parameter(Mandatory = $true)][ValidateNotNullOrEmpty()]$InFile,
[string]$ContentType,
[Uri][parameter(Mandatory = $true)][ValidateNotNullOrEmpty()]$Uri,
[System.Management.Automation.PSCredential]$Credential
)
BEGIN
{
if (-not (Test-Path $InFile))
{
$errorMessage = ("File {0} missing or unable to read." -f $InFile)
$exception = New-Object System.Exception $errorMessage
$errorRecord = New-Object System.Management.Automation.ErrorRecord $exception, 'MultipartFormDataUpload', ([System.Management.Automation.ErrorCategory]::InvalidArgument), $InFile
$PSCmdlet.ThrowTerminatingError($errorRecord)
}
if (-not $ContentType)
{
Add-Type -AssemblyName System.Web
$mimeType = [System.Web.MimeMapping]::GetMimeMapping($InFile)
if ($mimeType)
{
$ContentType = $mimeType
}
else
{
$ContentType = "application/octet-stream"
}
}
}
PROCESS
{
Add-Type -AssemblyName System.Net.Http
$httpClientHandler = New-Object System.Net.Http.HttpClientHandler
if ($Credential)
{
$networkCredential = New-Object System.Net.NetworkCredential @($Credential.UserName, $Credential.Password)
$httpClientHandler.Credentials = $networkCredential
}
$httpClient = New-Object System.Net.Http.Httpclient $httpClientHandler
$packageFileStream = New-Object System.IO.FileStream @($InFile, [System.IO.FileMode]::Open)
$contentDispositionHeaderValue = New-Object System.Net.Http.Headers.ContentDispositionHeaderValue "form-data"
$contentDispositionHeaderValue.Name = "fileData"
$contentDispositionHeaderValue.FileName = (Split-Path $InFile -leaf)
$streamContent = New-Object System.Net.Http.StreamContent $packageFileStream
$streamContent.Headers.ContentDisposition = $contentDispositionHeaderValue
$streamContent.Headers.ContentType = New-Object System.Net.Http.Headers.MediaTypeHeaderValue $ContentType
$content = New-Object System.Net.Http.MultipartFormDataContent
$content.Add($streamContent)
try
{
$response = $httpClient.PostAsync($Uri, $content).Result
if (!$response.IsSuccessStatusCode)
{
$responseBody = $response.Content.ReadAsStringAsync().Result
$errorMessage = "Status code {0}. Reason {1}. Server reported the following message: {2}." -f $response.StatusCode, $response.ReasonPhrase, $responseBody
throw [System.Net.Http.HttpRequestException] $errorMessage
}
$responseBody = [xml]$response.Content.ReadAsStringAsync().Result
return $responseBody
}
catch [Exception]
{
$PSCmdlet.ThrowTerminatingError($_)
}
finally
{
if($null -ne $httpClient)
{
$httpClient.Dispose()
}
if($null -ne $response)
{
$response.Dispose()
}
}
}
END { }
}
干杯
答案 2 :(得分:3)
我已将@akauppi's answer重新混合到更通用的解决方案中,该cmdlet:
Get-ChildItem
获取要上传的文件的管道输入-Credential
参数-FilesKey
参数指定文件上传部分的formdata键-WhatIf
-Verbose
正在记录可以像这样调用:
$url ="http://localhost:12345/home/upload"
$form = @{ description = "Test 123." }
$pwd = ConvertTo-SecureString "s3cr3t" -AsPlainText -Force
$creds = New-Object System.Management.Automation.PSCredential ("john", $pwd)
Get-ChildItem *.txt | Send-MultiPartFormToApi $url $form $creds -Verbose -WhatIf
以下是完整cmdlet的代码:
function Send-MultiPartFormToApi {
# Attribution: [@akauppi's post](https://stackoverflow.com/a/25083745/419956)
# Remixed in: [@jeroen's post](https://stackoverflow.com/a/41343705/419956)
[CmdletBinding(SupportsShouldProcess = $true)]
param (
[Parameter(Position = 0)]
[string]
$Uri,
[Parameter(Position = 1)]
[HashTable]
$FormEntries,
[Parameter(Position = 2, Mandatory = $false)]
[System.Management.Automation.Credential()]
[System.Management.Automation.PSCredential]
$Credential,
[Parameter(
ParameterSetName = "FilePath",
Mandatory = $true,
ValueFromPipeline = $true,
ValueFromPipelineByPropertyName = $true
)]
[Alias("Path")]
[string[]]
$FilePath,
[Parameter()]
[string]
$FilesKey = "files"
);
begin {
$LF = "`n"
$boundary = [System.Guid]::NewGuid().ToString()
Write-Verbose "Setting up body with boundary $boundary"
$bodyArray = @()
foreach ($key in $FormEntries.Keys) {
$bodyArray += "--$boundary"
$bodyArray += "Content-Disposition: form-data; name=`"$key`""
$bodyArray += ""
$bodyArray += $FormEntries.Item($key)
}
Write-Verbose "------ Composed multipart form (excl files) -----"
Write-Verbose ""
foreach($x in $bodyArray) { Write-Verbose "> $x"; }
Write-Verbose ""
Write-Verbose "------ ------------------------------------ -----"
$i = 0
}
process {
$fileName = (Split-Path -Path $FilePath -Leaf)
Write-Verbose "Processing $fileName"
$fileBytes = [IO.File]::ReadAllBytes($FilePath)
$fileDataAsString = ([System.Text.Encoding]::GetEncoding("iso-8859-1")).GetString($fileBytes)
$bodyArray += "--$boundary"
$bodyArray += "Content-Disposition: form-data; name=`"$FilesKey[$i]`"; filename=`"$fileName`""
$bodyArray += "Content-Type: application/x-msdownload"
$bodyArray += ""
$bodyArray += $fileDataAsString
$i += 1
}
end {
Write-Verbose "Finalizing and invoking rest method after adding $i file(s)."
if ($i -eq 0) { throw "No files were provided from pipeline." }
$bodyArray += "--$boundary--"
$bodyLines = $bodyArray -join $LF
# $bodyLines | Out-File data.txt # Uncomment for extra debugging...
try {
if (!$WhatIfPreference) {
Invoke-RestMethod `
-Uri $Uri `
-Method Post `
-ContentType "multipart/form-data; boundary=`"$boundary`"" `
-Credential $Credential `
-Body $bodyLines
} else {
Write-Host "WHAT IF: Would've posted to $Uri body of length " + $bodyLines.Length
}
} catch [Exception] {
throw $_ # Terminate CmdLet on this situation.
}
Write-Verbose "Finished!"
}
}
答案 3 :(得分:1)
在研究了如何构建multipart / form-data之后,我找到了解决问题的方法。很多帮助以http://www.paraesthesia.com/archive/2009/12/16/posting-multipartform-data-using-.net-webrequest.aspx的形式出现。
然后,解决方案是根据该约定手动构建请求的主体。我留下了像正确的内容长度等的细节。
以下是我现在使用的摘录:
$path = "/Some/path/to/data/"
$boundary_id = Get-Date -Format yyyyMMddhhmmssfffffff
$boundary = "------------------------------" + $boundary_id
$url = "http://..."
[System.Net.HttpWebRequest] $req = [System.Net.WebRequest]::create($url)
$req.Method = "POST"
$req.ContentType = "multipart/form-data; boundary=$boundary"
$ContentLength = 0
$req.TimeOut = 50000
$reqst = $req.getRequestStream()
<#
Any time you write a file to the request stream (for upload), you'll write:
Two dashes.
Your boundary.
One CRLF (\r\n).
A content-disposition header that tells the name of the form field corresponding to the file and the name of the file. That looks like:
Content-Disposition: form-data; name="yourformfieldname"; filename="somefile.jpg"
One CRLF.
A content-type header that says what the MIME type of the file is. That looks like:
Content-Type: image/jpg
Two CRLFs.
The entire contents of the file, byte for byte. It's OK to include binary content here. Don't base-64 encode it or anything, just stream it on in.
One CRLF.
#>
<# Upload #1: XFA #>
$xfabuffer = [System.IO.File]::ReadAllBytes("$path\P7-T.xml")
<# part-header #>
$header = "--$boundary`r`nContent-Disposition: form-data; name=`"xfa`"; filename=`"xfa`"`r`nContent-Type: text/xml`r`n`r`n"
$buffer = [Text.Encoding]::ascii.getbytes($header)
$reqst.write($buffer, 0, $buffer.length)
$ContentLength = $ContentLength + $buffer.length
<# part-data #>
$reqst.write($xfabuffer, 0, $xfabuffer.length)
$ContentLength = $ContentLength + $xfabuffer.length
<# part-separator "One CRLF" #>
$terminal = "`r`n"
$buffer = [Text.Encoding]::ascii.getbytes($terminal)
$reqst.write($buffer, 0, $buffer.length)
$ContentLength = $ContentLength + $buffer.length
<# Upload #1: PDF template #>
$pdfbuffer = [System.IO.File]::ReadAllBytes("$path\P7-T.pdf")
<# part-header #>
$header = "--$boundary`r`nContent-Disposition: form-data; name=`"pdf`"; filename=`"pdf`"`r`nContent-Type: application/pdf`r`n`r`n"
$buffer = [Text.Encoding]::ascii.getbytes($header)
$reqst.write($buffer, 0, $buffer.length)
$ContentLength = $ContentLength + $buffer.length
<# part-data #>
$reqst.write($pdfbuffer, 0, $pdfbuffer.length)
$ContentLength = $ContentLength + $pdfbuffer.length
<# part-separator "One CRLF" #>
$terminal = "`r`n"
$buffer = [Text.Encoding]::ascii.getbytes($terminal)
$reqst.write($buffer, 0, $buffer.length)
$ContentLength = $ContentLength + $buffer.length
<#
At the end of your request, after writing all of your fields and files to the request, you'll write:
Two dashes.
Your boundary.
Two more dashes.
#>
$terminal = "--$boundary--"
$buffer = [Text.Encoding]::ascii.getbytes($terminal)
$reqst.write($buffer, 0, $buffer.length)
$ContentLength = $ContentLength + $buffer.length
$reqst.flush()
$reqst.close()
# Dump request to console
#$req
[net.httpWebResponse] $res = $req.getResponse()
# Dump result to console
#$res
# Dump result-body to filesystem
<#
$resst = $res.getResponseStream()
$sr = New-Object IO.StreamReader($resst)
$result = $sr.ReadToEnd()
$res.close()
#>
$null = New-Item -ItemType Directory -Force -Path "$path\result"
$target = "$path\result\P7-T.pdf"
# Create a stream to write to the file system.
$targetfile = [System.IO.File]::Create($target)
# Create the buffer for copying data.
$buffer = New-Object Byte[] 1024
# Get a reference to the response stream (System.IO.Stream).
$resst = $res.GetResponseStream()
# In an iteration...
Do {
# ...attemt to read one kilobyte of data from the web response stream.
$read = $resst.Read($buffer, 0, $buffer.Length)
# Write the just-read bytes to the target file.
$targetfile.Write($buffer, 0, $read)
# Iterate while there's still data on the web response stream.
} While ($read -gt 0)
# Close the stream.
$resst.Close()
$resst.Dispose()
# Flush and close the writer.
$targetfile.Flush()
$targetfile.Close()
$targetfile.Dispose()