Search code examples
powershelluri

Extract left part of HTTP URI containing scheme, host and port (everything before the path)


PS C:\Users\user> Split-Path "http://localhost:9200/index" -Parent
http:\\localhost:9200

In above example, I am trying to extract everything before the /index. I thought Split-Path would do the job but it changes the forward slashes in http:// to back slashes.

"http://localhost:9200/index" -replace ([Uri]"http://localhost:9200/index").AbsolutePath

I've worked out the above alternative which works but Split-Path just seems cleaner. Just wondering why it does that with the forward slashes?


Solution

  • Split-Path works with provider paths only. Instead, use the .NET class Uri that is specifically made for parsing URIs.

    Using the method Uri.GetLeftPart(), a partial URI can be extracted from the URI. This is much easier than building it "manually" by concatenating the URI components.

    $uri = [uri] 'http://localhost:9200/index'
    $uri.GetLeftPart('Authority')
    
    # or as a one-liner:
    ([uri] 'http://localhost:9200/index').GetLeftPart('Authority')
    

    Output:

    http://localhost:9200
    

    By passing other values from the UriPartial enum, different parts of the URI can be returned:

    $uri = [uri] 'http://localhost:9200/index?param=value#anchor'
    
    foreach( $part in 'Scheme','Authority','Path','Query' ) {
        [pscustomobject]@{
            Part = $part
            URI  = $uri.GetLeftPart( $part )
        }
    }
    

    Output:

    Part      URI
    ----      ---
    Scheme    http://
    Authority http://localhost:9200
    Path      http://localhost:9200/index
    Query     http://localhost:9200/index?param=value
    

    As noted by this related C# answer, there is one catch with Uri.GetLeftPart():

    If the port is the default port for the scheme, it will strip it out. E. g., the URI http://localhost:80/index gets turned into http://localhost/index, because port 80 is the default for the HTTP scheme.

    If this is a problem, then you may use the UriBuilder class to manually build the new URI from the parts of the original URI, while avoiding direct string manipulation:

    Function Get-UriBase( [Parameter(Mandatory, ValueFromPipeline)] [Uri] $Uri ) {
        process {
            $uriBase = [UriBuilder]::new( $uri.Scheme, $uri.Host, $uri.Port ).ToString()
            # Output $uriBase if original URI starts with this part
            if( $uri.OriginalString.StartsWith( $uriBase ) ) { 
                return $uriBase 
            }
            # Otherwise original URI didn't contain a port, so use GetLeftPart() which omits it
            $uri.GetLeftPart('Authority')
        }
    }
    
    foreach( $uri in 'http://localhost:80/index', 'http://localhost:1234/index', 'http://localhost/index' ) {
        # For output in table format
        [PSCustomObject]@{ URI = $uri; URI_Base = Get-UriBase $uri }
    }
    
    

    Output:

    URI                         URI_Base
    ---                         --------
    http://localhost:80/index   http://localhost:80/  
    http://localhost:1234/index http://localhost:1234/
    http://localhost/index      http://localhost