GV

GeezerVison

The World Wide Web, restored to 800 pixels of glory.

Home|About|Gaming cabinet|Archives|How it works|Starter sites

On this site

  • Sign in
  • ActionsCI/CD and automation
  • CodespacesCloud development environments
  • All Features
  • Documentation
  • Training
  • Certifications
  • Build with Copilot
  • Customize Copilot
  • Integrations
  • Trending repos
  • GitHub Sponsors
  • Developer articles
  • Enterprises

This view

  • Original page
  • New address

GeezerVison is showing github.com — Simplify usage of Multipart Uploads with WebRequestPSCmdlet · Issue #2112 · PowerShell/PowerShell

Simplify usage of Multipart Uploads with WebRequestPSCmdlet #2112

New issue
Copy link
New issue
Copy link
Closed
#5972
Closed
Simplify usage of Multipart Uploads with WebRequestPSCmdlet#2112
#5972
Copy link
Assignees
markekraus
Labels
Committee-ReviewedPS-Committee has reviewed this and made a decisionPS-Committee has reviewed this and made a decisionIssue-Enhancementthe issue is more of a feature request than a bugthe issue is more of a feature request than a bugResolution-FixedThe issue is fixed.The issue is fixed.WG-Cmdletsgeneral cmdlet issuesgeneral cmdlet issues
Milestone
6.1.0-Consider

Description

@ffeldhaus
Florian Feldhaus (ffeldhaus)
opened on Aug 28, 2016
Issue body actions

Doing multipart requests with PowerShell is quite complicated and requires several extra steps for large file uploads. An easy example for a small file can be found on StackOverflow. An in depth discussion can be found in this blog post.

It would be a huge improvement if the WebRequestPSCmdlets (Invoke-RestMethod and Invoke-WebRequest) could be enhanced so that they support Multipart messages directly.

For an implementation I would expect the following parameters:

  • MultipartFile (Path)
  • MultipartName (Name to be used in Multipart message)

.NET Core seems to have support for MultipartContent which may simplify the implementation.

Reacted by Keith Hill, Florian Feldhaus, Andreas Hunkeler and Joshua Todd Cowper

Activity

Next
  1. Steve Lee (SteveL-MSFT)
    added
    Issue-Enhancementthe issue is more of a feature request than a bugthe issue is more of a feature request than a bug
    WG-Cmdletsgeneral cmdlet issuesgeneral cmdlet issues
    on Sep 1, 2016
  2. Steve Lee (SteveL-MSFT)
    added
    Up-for-GrabsUp-for-grabs issues are not high priorities, and may be opportunities for external contributorsUp-for-grabs issues are not high priorities, and may be opportunities for external contributors
    on Nov 1, 2016
  3. swinster commented on Aug 22, 2017

    @swinster
    swinster
    on Aug 22, 2017
    More actions

    This is horrendous in Powershell and hasn't moved in an age. I personally would love to see some movement here, as uploading large file is very, very difficult.

    As some pointer around the topic see my comments here (albeit for PS 5.1) which like to some other sites, namely http://blog.majcica.com/2016/01/13/powershell-tips-and-tricks-multipartform-data-requests/

  4. iSazonov commented on Aug 23, 2017

    @iSazonov
    Ilya (iSazonov)
    on Aug 23, 2017
    Collaborator
    More actions

    Mark Kraus (@markekraus) Could you please look this if you have free time?

  5. markekraus commented on Aug 25, 2017

    @markekraus
    Mark Kraus (markekraus)
    on Aug 25, 2017
    Contributor
    More actions

    Ilya (@iSazonov) sure. I will take a look at it. I have seen other requests in the past few years for multipart forms in general as several IoT APIs seem to only work with multipart forms.

  6. iSazonov commented on Aug 25, 2017

    @iSazonov
    Ilya (iSazonov)
    on Aug 25, 2017
    Collaborator
    More actions

    Feel free to ping MSFT experts here if there is compatibility issues.

  7. swinster commented on Aug 25, 2017

    @swinster
    swinster
    on Aug 25, 2017
    More actions

    FWIW, we mainly Python, Django and Apache for our Web interfaace stuff. This is all RESTfull APIs. I am not developer, just attempting to interact with the API as we are seeing more sysadmins wanting to do this.

  8. markekraus commented on Aug 25, 2017

    @markekraus
    Mark Kraus (markekraus)
    on Aug 25, 2017
    Contributor
    More actions

    swinster if you could provide me an exact scenario that would be very useful. Right now, all I have to go off of is to build a out a test API that takes multipart forms and files and hope that fits most scenarios.

  9. swinster commented on Aug 25, 2017

    @swinster
    swinster
    on Aug 25, 2017
    More actions

    Mark Kraus (@markekraus) sure. I work for Pexip, and the entire platform a can be automated via API calls. This specific example is required with regard to uploading an upgrade file to the platform, which will be in excess of 1GB. The API documentation can be found here - https://docs.pexip.com/api_manage/api_configuration.htm?Highlight=upgrade

    I can even post here the resultant function created to achieve this, although this is based on the information post by Mario Majčica in his blog above.

  10. markekraus commented on Aug 25, 2017

    @markekraus
    Mark Kraus (markekraus)
    on Aug 25, 2017
    Contributor
    More actions

    swinster awesome. If I need more I'll reach out.

  11. markekraus commented on Aug 25, 2017

    @markekraus
    Mark Kraus (markekraus)
    on Aug 25, 2017
    Contributor
    More actions

    swinster Can you share your function with me? i want to make sure I'm on the right path.

  12. markekraus commented on Aug 26, 2017

    @markekraus
    Mark Kraus (markekraus)
    on Aug 26, 2017
    Contributor
    More actions

    This presents some interesting implementation issues. To truly support Multipart form data, the Cmdlets would need to accept multiple files, a field name for each file, possibly a content type for each file, and the ability to accept a dictionary of field/value pairs at the same time. Additionally, It appears some APIs are particular about the boundary used, so an optional boundary would need to be supplied. Attempting to make that mesh with the current Cmdlets would be a significant rework almost to the point of justifying separate cmdlets for Multipart support.

    The issue is in Multipart form data being extremely flexible and radically different from other requests. It would be easy to tack on support for a single file or to convert a -Body dictionary, but it would not be simple to mix a file with form data or to support multiple files. Fitting all the Multipart use-cases into the current cmdlets would touch a large portion of code to accommodate a valid but somewhat less common usage of the cmdlets.

    IMO, It would also lead to a somewhat confusing UX. If -InFile was used for Multipart files and only accepted multiple files on a Multipart request it would lead to issues with users supplying multiple paths on a standard request, getting errors, and wondering why it accepts an array of paths. Also, explaining that -Body and -InFile are mutually exclusive except when doing Multipart requests would be somewhat confusing confusing. If files were split into a separate and new parameter like -MultipartFile, that might cause confusion as to why -InFile doesn't work on Multipart requests or why there needs to be a difference in the first place.

    The problem I see with simplifying this is that many of the requests I've read for sending Multipart form data through PowerShell have only their own use-case in mind. It might seem simple from that perspective, but when you look a dozen or so of these requests you begin to see they almost all are distinctly different needs falling under the broad category of Multipart form data. A Narrow implementation would leave many use-cases in their current state, a broad implementation would impact a lot of code but for feature addition that is less commonly used.

    With that in mind, I think a fair compromise and better approach would be to accept a -Body object/collection that can be used by the Cmdlets to create the Multipart Form-Data Request. This could be simplified by exposing new cmdlet(s) and new classes to simplify generating that object/collection. Body could simply be adapted to accept MultipartFormDataContent at least easing the burden of the user managing an HttpClient.

    I could start by adding support for MultipartFormDataContent in -Body. From there it could be decided to add convenience cmdlets or possibly even wrapper classes to easy creation.

    James Truher (@JamesWTruher) Dan Travison (@dantraMSFT) Paul Higinbotham (@PaulHigin) Steve Lee (@SteveL-MSFT) What are your thoughts?

  13. swinster commented on Aug 26, 2017

    @swinster
    swinster
    on Aug 26, 2017
    More actions

    Mark Kraus (@markekraus) FWIW, here is the modified function from Mario's blog.

    The main addition was the Basic Authentication headers which also required a Base64 encoded version of the authentication credentials. Initially, I decrypted the PSCredential password via a separate function, however this was changed to a single line. The Basic Authroziation header is required as although you can provide simple credentials to the the HTTPClient (and indeed the Invoke-WebRequest), the first request is sent without this header, which is then challenged, thus a second request is made. This means that then entire 1 GB file is uploaded twice - a huge waste of time and resources.

    As an aside, I have a similar issue (WRT the Basic Authorisation Header) when downloading a large file (3-4 GB), again again leads to a two request issue, and the download of the file is actioned twice), However the Invoke-WebRequest is even worse in that case as the entire file is streamed into memory and cause the remote server to lock up when I tried to use this CmdLet. I eventually changed to use the WebClient (I couldn't get the HTTPClient to work with my lacking programming knowledge) , however, WebClient doesn't implement a timeout property so you have to build a class to inherit and extend the WebClient. All very, very complex and confusing, especially to someone like me with little programming expertise. As mentioned, this is an aside to this particular issue, but is similar in nature.

    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
    			$httpClientHandler.PreAuthenticate = $true
    			$httpClient = New-Object System.Net.Http.Httpclient $httpClientHandler
    			#$password = Get-PlainText -SecureString $Credential.Password
    			$Base64Auth = [System.Convert]::ToBase64String([System.Text.Encoding]::GetEncoding("iso-8859-1").GetBytes([String]::Format( "{0}:{1}", $Credential.UserName, $Credential.GetNetworkCredential().Password)))			
    			#$Base64Auth = [Convert]::ToBase64String([Text.Encoding]::GetEncoding("iso-8859-1").Getbytes("$($Credential.UserName):$($password)"))
    			$httpClient.DefaultRequestHeaders.Add("Authorization", "Basic $Base64Auth")
    		}
    		else {
    			$httpClient = New-Object System.Net.Http.Httpclient $httpClientHandler
    		}
    
    		$httpClient.Timeout = 18000000000
    		#$httpClient.DefaultRequestHeaders.Add("AUTHORIZATION", "Basic YTph")
    
    		$packageFileStream = New-Object System.IO.FileStream @($InFile, [System.IO.FileMode]::Open)
    
    		$contentDispositionHeaderValue = New-Object System.Net.Http.Headers.ContentDispositionHeaderValue "form-data"
    		$contentDispositionHeaderValue.Name = "package"
    		$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
    			}
    
    			#return $response.Content.ReadAsStringAsync().Result
    			return $response
    
    		}
    		catch [Exception]
    		{
    			$PSCmdlet.ThrowTerminatingError($_)
    			return $response
    		}
    		finally
    		{
    			if($null -ne $httpClient)
    			{
    				$httpClient.Dispose()
    			}
    
    			if($null -ne $response)
    			{
    				$response.Dispose()
    			}
    		}
    	}
    	END { }
    }
    
  14. swinster commented on Aug 26, 2017

    @swinster
    swinster
    on Aug 26, 2017
    More actions

    I have also hardcoded the ContentDispositionHeaderValue value (package) which is all that was required for my needs.

  15. 47 remaining items

  16. lzybkr commented on Dec 7, 2017

    @lzybkr
    Jason Shirk (lzybkr)
    on Dec 7, 2017
    Contributor
    More actions

    if [file] -eq [system.io.fileinfo], I think that will cause confusion, e.g.

    using namespace System.IO
    [File]::WriteAllLines($path, $line)

    Clearly that's not meant to be FileInfo, but if you don't see the using, you might be confused if you get used to see [File] in other places.

  17. markekraus commented on Dec 7, 2017

    @markekraus
    Mark Kraus (markekraus)
    on Dec 7, 2017
    Contributor
    More actions

    For me, the name isn't so important as just having easy accelerators for files and directories. I'm not a huge fan of [system.io.fileinfo] not throwing on the file not existing. ideally this accelerator would be used like this

    function Get-Widget {
        param([PSFile]$File)
        # Do something with $File
    }
    Get-Widget -File 'x:\no\lo\existo.nope'
    # parameter binding error for non existent file
    Get-Widget -File 'c:\some\real\file.txt'
    # works with existing file
    
    $form = @{
        StringField = 'stringValue'
        FileField = [PSFile]'x:\no\lo\existo.nope'
    }
    # type conversion error on non existing file
    $form = @{
        StringField = 'stringValue'
        FileField = [PSFile]'c:\some\real\file.txt'
    }
    # no error
  18. markekraus commented on Dec 7, 2017

    @markekraus
    Mark Kraus (markekraus)
    on Dec 7, 2017
    Contributor
    More actions

    also... I'd like to note that the accelerators should not be blocking to the multipart implementation. I can implement this now with out [file] and add support for that later. The accelerators should have been tied to a separate issue. while having them would make the UX for the -Form feature easier, for now the user can do

    $form = @{
        FileField = Get-Item 'c:\some\real\file.txt'
    }
    iwr -Form $form -Method POST -Uri $uri
  19. SteveL-MSFT commented on Dec 7, 2017

    @SteveL-MSFT
    Steve Lee (SteveL-MSFT)
    on Dec 7, 2017
    Member
    More actions

    The accelerators should be a separate issue and PR. You can still use [System.IO.FileInfo] for now.

  20. swinster commented on Dec 28, 2017

    @swinster
    swinster
    on Dec 28, 2017
    More actions

    Just a quick question. with regard to this issue, has anything made it to PowerShell v6 as yet ?

    Not sure if the underlying mechanism to retrieve a file will be altered, but it came to light the other day that the way I have the "working" at the moment in v5 is a little on the slow side. An HTTP download of a 4GB file from a browser from a server based on the same network takes approximately 30 seconds. In PowerShell v5, this takes around 1 hour !!!

  21. markekraus commented on Dec 28, 2017

    @markekraus
    Mark Kraus (markekraus)
    on Dec 28, 2017
    Contributor
    More actions

    swinster PowerShell 6.0.0 includes the ability to supply a System.Net.Http.MultipartFormDataContent object to the -Body parameter. You can read about this in detail here https://get-powershellblog.blogspot.com/2017/09/multipartform-data-support-for-invoke.html . The simplified -Form parameter will be added in 6.1.0 (assuming nothing comes up or blocks it). It is the first feature on my TODO list, but I have a few bugs and some code refactoring I would like to resolve first.

  22. swinster commented on Dec 28, 2017

    @swinster
    swinster
    on Dec 28, 2017
    More actions

    Awesome work, thanks Mark Kraus (@markekraus) . Can't wait to be able to retrieve files with -outfile

  23. markekraus commented on Dec 28, 2017

    @markekraus
    Mark Kraus (markekraus)
    on Dec 28, 2017
    Contributor
    More actions

    Can't wait to be able to retrieve files with -outfile

    swinster Can you elaborate on this? You can already download files and use the -OutFile parameter to save them. This has been around for several versions at least.

    The blog post I linked has an earlier version of what was planned for the simplified multipart/form-data support. The planned implimentation has since been revised. You can see a "demo" of the currently planned simplified multipart/form-data support here https://get-powershellblog.blogspot.com/2017/12/powershell-core-web-cmdlets-in-depth_24.html#L21 and you can see the proposal here #2112 (comment)

  24. swinster commented on Dec 28, 2017

    @swinster
    swinster
    on Dec 28, 2017
    More actions

    Mark Kraus (@markekraus) apologies, the comment above was not in the correct place as it related to the underlying method of data retrieval from a simple GET method on the Invoke-WebRequest, which was in the order of 200 time slower that a browser request, but actually nothing to do the MultiPart uploads. However, I have just run the same basic tests using Invoke-WebRequest to GET a large file in v6 and v5 simultaneously and honestly there is a HUGE difference - whoever made these changes to the implementation here need some applause as well - even if this is the wrong place!

  25. markekraus commented on Jan 20, 2018

    @markekraus
    Mark Kraus (markekraus)
    on Jan 20, 2018
    Contributor
    More actions

    An update for anyone following this issue. I am currently working on implementing the solution as approved by the PowerShell Committee with some minor adjustments. I hope to have a PR submitted in the coming days.

    After reading RFC-7578 it became clear that a single field name can be supplied multiple times with different field values. The use case for this is an array of files (multiple file selection) or values for a single form field. To accommodate that, I'm adjusting the implementation slightly to treat collections as multiple field values for the same field name.

    for example:

    $Form = @{
        Files = Get-ChildItem c:\temp -File
        Names = "Bill", "Sue", "Jane"
        Cars = [System.Collections.Generic.List[String]]::new([String[]]@("Smart", "Honda"))
        Description = "Multiple and single value support."
        Image = [System.IO.FileInfo]"c:\picture\me.png"
    }
    Invoke-WebRequest -Method POST -Uri $uri -Form $Form

    In the approved implementation, this would result in a collection being converted to a single string. In the implementation I am working on, Names would be supplied 3 times with Bill as the first value, Sue as the second, and Jane as the third. It doesn't add much complexity and initial testing shows it works for endpoints that support it. It provides more flexibility at minimal cost. After checking, I personally have an internal use case for this in a form that requires multiple values for a single field.

  26. Ilya (iSazonov)
    closed this as completedin #5972on Jan 25, 2018
  27. Ilya (iSazonov)
    added
    Resolution-FixedThe issue is fixed.The issue is fixed.
    on May 26, 2018
  28. Ryan Yates (kilasuit)
    added
    WG-Cmdletsgeneral cmdlet issuesgeneral cmdlet issues
    and removed
    WG-Cmdlets-Utility
    on Jul 17, 2026
  29. Thatgfsj (Thatgfsj)
    added a commit that references this issue on Aug 6, 2026

    Add Multipart Support to Web Cmdlets (PowerShell#4782)

    3be5b76
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

  • @markekraus
    Mark Kraus (markekraus)

Labels

Committee-ReviewedPS-Committee has reviewed this and made a decisionPS-Committee has reviewed this and made a decisionIssue-Enhancementthe issue is more of a feature request than a bugthe issue is more of a feature request than a bugResolution-FixedThe issue is fixed.The issue is fixed.WG-Cmdletsgeneral cmdlet issuesgeneral cmdlet issues

Type

No type

Projects

No projects

    Milestone

    • 6.1.0-Consider

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions

    You are visitor 01402520 — GeezerVison does not store the pages you look up.
    Best viewed with Netscape Navigator 4.7 or Internet Explorer 6 at 800×600. No cookies required.