9

I have been trying to access a URL with a / character in it from powershell, using the following command (it's a query to a gitlab server to retrieve a project called "foo/bar"):

Invoke-WebRequest https://server.com/api/v3/projects/foo%2Fbar -Verbose

Now, the odd thing is that using the PowerShell ISE or Visual Studio, the request is OK. When using PowerShell itself, the URL is automatically un-escaped and the request fails. E.g.

In ISE/VS:

$> Invoke-WebRequest https://server.com/api/v3/projects/foo%2Fbar -Verbose
VERBOSE: GET https://server.com/api/v3/projects/foo%2Fbar with 0-byte payload
VERBOSE: received 19903-byte response of content type application/json

StatusCode        : 200
StatusDescription : OK
Content           : .... data ....

In Powershell:

$> Invoke-WebRequest https://server.com/api/v3/projects/foo%2Fbar -Verbose
VERBOSE: GET https://server.com/api/v3/projects/foo/bar with 0-byte payload
Invoke-WebRequest : {"error":"404 Not Found"}
At line:1 char:1
+ Invoke-WebRequest 'https://server.com/api/v3/projects/foo%2Fbar ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : InvalidOperation: (System.Net.HttpWebRequest:HttpWebRequest) [Invoke-WebRequest], WebException
    + FullyQualifiedErrorId : WebCmdletWebResponseException,Microsoft.PowerShell.Commands.InvokeWebRequestCommand

I have tried adding single and double quotes around the URL, but nothing is helping.

What could be the reason for this behaviour, and how do I make PS not un-escape the URL string?


Environment: Windows 7, also tested on Server 2012R2 with same results.

$> $PSVersionTable

Name                           Value
----                           -----
PSVersion                      4.0
WSManStackVersion              3.0
SerializationVersion           1.1.0.1
CLRVersion                     4.0.30319.42000
BuildVersion                   6.3.9600.16406
PSCompatibleVersions           {1.0, 2.0, 3.0, 4.0}
PSRemotingProtocolVersion      2.2
2
  • 4
    Right here the code calls [System.Net.WebRequest]::CreateUri() which does not allow escaped slashes as a known problem, and is allegedly fixed in .Net 4.5. Two workarounds mentioned in that question, not sure if either is easily usable with PowerShell - what should the result of the request be? Commented Mar 31, 2017 at 0:21
  • Thanks for the links. I'd seen those, I also wasn't quite sure how to apply them in a powershell context. The result of the request should be some json data. Commented Apr 3, 2017 at 2:04

3 Answers 3

12

Try the URL through this function

function fixuri($uri){
  $UnEscapeDotsAndSlashes = 0x2000000;
  $SimpleUserSyntax = 0x20000;

  $type = $uri.GetType();
  $fieldInfo = $type.GetField("m_Syntax", ([System.Reflection.BindingFlags]::Instance -bor [System.Reflection.BindingFlags]::NonPublic));

  $uriParser = $fieldInfo.GetValue($uri);
  $typeUriParser = $uriParser.GetType().BaseType;
$fieldInfo = $typeUriParser.GetField("m_Flags", ([System.Reflection.BindingFlags]::Instance -bor [System.Reflection.BindingFlags]::NonPublic -bor [System.Reflection.BindingFlags]::FlattenHierarchy));
$uriSyntaxFlags = $fieldInfo.GetValue($uriParser);

$uriSyntaxFlags = $uriSyntaxFlags -band (-bnot $UnEscapeDotsAndSlashes);
$uriSyntaxFlags = $uriSyntaxFlags -band (-bnot $SimpleUserSyntax);
$fieldInfo.SetValue($uriParser, $uriSyntaxFlags);
}

$uri = New-Object System.Uri -ArgumentList ("https://server.com/api/v3/projects/foo%2Fbar")
fixuri $uri
Sign up to request clarification or add additional context in comments.

4 Comments

Just to note, this is essentially the same solution as in various .net solutions, although I had not seen a powershell implementation of it until this.
Lol, an utterly ridiculous problem and a fix to match. We totally deserve this.
Absolute waste of my time to have to stumble upon a hackabout like this.. way to go System.Uri!
And it seems that through recent updates of PowerShell, this doesn't work any more. I'm on 6.2 and $fileInfo is null, so the whole function fails. I have not yet found any way to get it to work; ditching PowerShell!
4

Here is an alternate port of https://stackoverflow.com/a/784937/2864740 - it accepts a string and returns a new URI.

function CreateUriWithoutIncorrectSlashEncoding {
    param(
        [Parameter(Mandatory)][string]$uri
    )

    $newUri = New-Object System.Uri $uri

    [void]$newUri.PathAndQuery # need to access PathAndQuery (presumably modifies internal state)
    $flagsFieldInfo = $newUri.GetType().GetField("m_Flags", [System.Reflection.BindingFlags]::Instance -bor [System.Reflection.BindingFlags]::NonPublic)
    $flags = $flagsFieldInfo.GetValue($newUri)
    $flags = $flags -band (-bnot 0x30) # remove Flags.PathNotCanonical|Flags.QueryNotCanonical (private enum)
    $flagsFieldInfo.SetValue($newUri, $flags)

    $newUri
}

Usage:

$uri = CreateUriWithoutIncorrectSlashEncoding "https://server.com/api/v3/projects/foo%2Fbar"

1 Comment

I really like this solution. For those that read it and are confused, you would then use this in the Uri parameter of Invoke-RestMethod/Invoke-WebRequest Invoke-RestMethod -Uri $uri
2

I have encountered similar issue in PowerShell 5.1. My purpose was to get a single project by Git Lab Web API. As Web API described:

Get single project
Get a specific project, identified by project ID or NAMESPACE/PROJECT_NAME , which is owned by the authentication user. If using namespaced projects call make sure that the NAMESPACE/PROJECT_NAME is URL-encoded, eg. /api/v3/projects/diaspora%2Fdiaspora (where / is represented by %2F).

What different with nik was that my Invoke-WebRequest call was successful by directly invoke but failed inside a Job. Here's the code:

Start-Job -ScriptBlock {
    try{
        Invoke-WebRequest https://server.com/api/v3/projects/foo%2Fbar -verbose
    } catch {
        Write-Output $_.Exception
    }
}

To Get the output inside a Job. Run command:

> Get-Job | Receive-Job -Keep

And exception as below:

VERBOSE: GET https://server.com/api/v3/projects/foo/bar with 0-byte payload
System.Net.WebException: The remote server returned an error: (404) Not Found.
  at Microsoft.PowerShell.Commands.WebRequestPSCmdlet.GetResponse(WebRequest request)
  at Microsoft.PowerShell.Commands.WebRequestPSCmdlet.ProcessRecord()

And thanks Oleg SH's answer. My problem was solved. But I think there might be a bug in the Start-Job cmdlet


Environment: Windows 7

>$PSVersionTable

Name                           Value
----                           -----
PSVersion                      5.1.14409.1005
PSEdition                      Desktop
PSCompatibleVersions           {1.0, 2.0, 3.0, 4.0...}
BuildVersion                   10.0.14409.1005
CLRVersion                     4.0.30319.42000
WSManStackVersion              3.0
PSRemotingProtocolVersion      2.3
SerializationVersion           1.1.0.1

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.