PwSh.Fw.Web.psm1

<#
#>



<#
.SYNOPSIS
Download a file from the Internet.
 
.Description
Download a file using System.Net.WebClient class.
This function will not test of the web resource exist.
 
.PARAMETER Url
URL from where to fetch file
 
.PARAMETER To
Destination folder to write file
 
.PARAMETER Filename
Optional filename to rename downloaded file on-the-fly.
 
.PARAMETER UseWebClient
Instruct Download-File to use the .Net webclient object
 
.PARAMETER UseInvokeWebRequest
Instruct Download-File to use the slower but safer Invoke-WebRequest method
 
#>

function Download-File {
    [CmdletBinding()]
    [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseApprovedVerbs", "", Justification="Download is a more intuitive verb for this function because Get- is too generic.")]
    [OutputType([Boolean])]Param(
        [Parameter(Mandatory = $true, ValueFromPipeLine = $true)][System.Uri]$Url,
        [string]$To = (Get-Location).providerpath,
        [string]$Filename = [system.uri]::UnEscapeDataString($URL.Segments[-1]),
        [switch]$UseWebClient,
        [switch]$UseInvokeWebRequest,
        [hashtable]$Headers,
        [switch]$PassThru
    )
    Begin {
        # eenter($MyInvocation.MyCommand)
        # if (!$Filename) { $FileName = $URL.Segments[-1] }
        # if ([string]::IsNullOrEmpty($To)) { $To = Get-Location }
        # edevel("Url = $Url")
        # edevel("To = $To")
        # edevel("Filename = $Filename")
        # edevel("UseWebClient = $UseWebClient")
        # edevel("UseInvokeWebRequest = $UseInvokeWebRequest")
        if (-not (dirExist $To)) {
            Write-Error "Folder '$To' does not exist."
            return $false
        }
        # compute default download method
        if ($UseWebClient -eq $UseInvokeWebRequest) {
            $UseWebClient = $false
            $UseInvokeWebRequest = $true
        }

    }

    Process {
        $webrc = Get-UriWebRc -Url $Url.AbsoluteUri -AsInt
        Write-Devel "[$webrc] $URL"
        if ($webrc -eq 200) {
            # edebug "Downloading $Url to $To/$Filename"
            if ($UseWebClient) {
                # edevel "Download using WebClient .Net object."
                if (Test-IsUNCPath -Path $To) {
                    Write-Warning "$To is a UNC Path. WebClient does not support UNC Path. Falling back to InvokeWebRequest method."
                    $UseInvokeWebRequest = $true
                }
                $webClient = New-Object System.Net.WebClient
                if ($Headers) { $webClient.Headers = $Headers }
                $webClient.DownloadFile($Url, "$To/$Filename")
                $rc = $?
            }
            if ($UseInvokeWebRequest) {
                # edevel "Download using Invoke-WebRequest method."
                $null = Invoke-WebRequest -Uri $Url.AbsoluteUri -OutFile "$To/$Filename" -PassThru -UseBasicParsing -Headers $Headers
                $rc = $?
            }
        } else {
            $rc = $false
        }
        if ($Passthru) {
            return (Resolve-Path "$To/$Filename").Path
        } else {
            return $rc
        }
    }

    End {
        # eleave($MyInvocation.MyCommand)
    }
}

<#
.SYNOPSIS
Download a file from the Internet.
 
.Description
Download a file using Invoke-WebRequest method
 
.PARAMETER Url
URL from where to fetch file
 
.PARAMETER To
Destination folder to write file
 
.PARAMETER Filename
Optional filename to rename downloaded file on-the-fly.
 
#>

function Download-FileWithRC {
    [CmdletBinding()][OutputType([Int])]Param (
        [System.Uri]$Url,
        [string]$To,
        [string]$Filename
    )
    Begin {
        # eenter($MyInvocation.MyCommand)

        if (!$Filename) { $FileName = $URL.Segments[-1] }
    }

    Process {
        $outFile = $To + "/" + $Filename
        $webrc = Get-UriWebRc -Url $Url.AbsoluteUri -AsInt
        if ($webrc -eq 200) {
            $webRequest = Invoke-WebRequest -Uri $Url.AbsoluteUri -OutFile $outFile -PassThru
            return $webRequest.StatusCode
        } else {
            return $webrc
        }
    }

    End {
        # eleave($MyInvocation.MyCommand)
    }
}

<#
.SYNOPSIS
Get the HTTP response (or status) code from a web URI
 
.DESCRIPTION
Only get the HTTP response code, do not download any content
 
.PARAMETER Url
Full URL to test
 
.PARAMETER AsText
If true, output the status message text (e.g. OK)
 
.PARAMETER AsInt
If true, output the status message code (e.g. 200)
 
.OUTPUTS
Valid HTTP status code or status message.
If hostname cannot be resolved, then an error message is returned accordingly (-AsText) or -1 (-AsInt)
 
.EXAMPLE
$httpCode = Get-UriWebRc -Url "https://www.google.com" -AsText
Will output 'OK' (I hope so !)
 
.EXAMPLE
$httpCode = Get-UriWebRc -Url "http://www.google.com/nonexistentfile" -AsInt
Will output 404
 
.NOTES
General notes
 
.LINK
https://stackoverflow.com/questions/795751/can-i-get-detailed-exception-stacktrace-in-powershell
https://www.reddit.com/r/PowerShell/comments/30r1e9/any_way_to_get_just_the_response_code_using/
#>

function Get-UriWebRc {
    [CmdletBinding()]Param (
        [System.Uri]$Url,
        [switch]$AsText,
        [switch]$AsInt
    )
    Begin {
        # eenter($MyInvocation.MyCommand)

        if ($AsText -eq $false -and $AsInt -eq $false) { $AsInt = $true }
    }

    Process {
        # $request = [System.Net.WebRequest]::Create($Url.AbsoluteUri)
        $response = @{}
        $response.StatusCode = -1
        $response.StatusMsg = $null
        try {
            # $request = invoke-webrequest -method head -uri $Url.AbsoluteUri -UseBasicParsing
            $request = invoke-webrequest -uri $Url.AbsoluteUri -UseBasicParsing
            # $response = $request.GetResponse()
            $response.StatusCode = $request.StatusCode
            $response.StatusMsg = $request.StatusDescription
        } catch {
            $response.StatusCode = $_.Exception.Response.StatusCode.value__
            $response.StatusMsg = $_.Exception.Response.StatusCode
        }
        if ($null -eq $response) {
            if ($AsText) { $Global:Error[0].ToString() }
            if ($AsInt)  { -1 }
        }
        if ($AsText) { $response.StatusMsg } # This is a System.Net.HttpStatusCode enum value (@see https://msdn.microsoft.com/fr-fr/library/system.net.httpstatuscode(v=vs.110).aspx )
        if ($AsInt) { $response.StatusCode } # This is the numeric version.
    }

    End {
        # eleave($MyInvocation.MyCommand)
    }
}

<#
.SYNOPSIS
Test if a web resource exist
 
.DESCRIPTION
Wrapper of Get-UriWebRc to quickly know if a web resource is available or not.
 
.PARAMETER URL
The URL to test. Better use it to check for a file
 
.EXAMPLE
Test-WebFileExist -URL http://www.example.com/file.txt
 
#>

function Test-WebFileExist {
    [CmdletBinding()]
    [OutputType([Boolean])]
    Param (
        [Parameter(Mandatory = $true, ValueFromPipeLine = $true)][System.Uri]$URL
    )
    Begin {
        Write-EnterFunction
    }

    Process {
        $int = Get-UriWebRc -Url $URL -AsInt
        switch ($int) {
            200 {
                $bool = $true
            }
            default {
                $bool = $false
            }
        }
        return $bool
    }

    End {
        Write-LeaveFunction
    }
}

<#
.SYNOPSIS
Convert an object to a query string.
 
.DESCRIPTION
Convert an object to a single string to be used with HTTP GET method.
 
.PARAMETER InputObject
Object to convert
 
.PARAMETER variableName
Main variable name to use
 
.PARAMETER CurrentDepth
Current depth into InputObject. This parameter is used internally for recursivity. Do not assign.
 
.PARAMETER MaxDepth
Maximum depth of object to convert into a string
 
.PARAMETER CurrentPath
Parameter used internally for recursivity. Do not assign.
 
.EXAMPLE
$q = ConvertTo-QueryString -InputObject @{"one" = 1, "two" = 2} -variableName "query"
Invoke-RestMethod -Method Get -ContentType "application/json" -Uri $("https://www.example.com/search?" + $q)
The example above will convert the hashtable into the query string : "query[one]=1&query[two]=2" and then invoke a REST method
using this query.
 
.NOTES
General notes
#>


function ConvertTo-QueryString {
    [CmdletBinding(
        DefaultParameterSetName="OBJECT"
    )][OutputType([String])]Param (
        [AllowNull()]
        [Parameter(ParameterSetName = 'OBJECT', Mandatory = $true, ValueFromPipeLine = $true)]$InputObject,
        [Parameter()][string]$variableName,
        [int16]$CurrentDepth = -1,
        [uint16]$MaxDepth = 16,
        [string]$CurrentPath
    )
    Begin {
        # eenter($MyInvocation.MyCommand)
        $queryString = ""
    }

    Process {
        switch ($PSCmdlet.ParameterSetName) {
            'OBJECT' {
                $object = $InputObject | ConvertTo-Json | ConvertFrom-Json
                break
            }
            default {
                $object = Get-Variable -Name $variableName -ValueOnly | ConvertTo-Json | ConvertFrom-Json
                break
            }
        }

        if ($null -eq $object) { return }
        if ($CurrentDepth -gt $MaxDepth) { return $queryString += $("&" + $CurrentPath + "=" + $object) }

        $CurrentDepth++
        if ($CurrentDepth -eq 0) {
            $CurrentPath = $variableName
        }

        # edevel("CurrentDepth = " + $CurrentDepth + " / " + $MaxDepth)
        # edevel("CurrentPath = " + $CurrentPath)

        if ($object -is [array]) {
            # edevel("array / " + $object.GetType())

            for ($index = 0; $index -lt $object.count; $index++) {
                $queryString += ConvertTo-QueryString -CurrentDepth $CurrentDepth -InputObject $object[$index] -CurrentPath $($CurrentPath + "[$index]")
            }
        } elseif ($object -is [hashtable]) {
            # edevel("object / " + $object.GetType())
            # list properties of class System.Object
            $ObjectClassProperties = @( Get-ObjectProperties -obj $object )
            # extract custom properties of $object, without properties of class of object
            $properties = $object.PsObject.Properties | Where-Object { $ObjectClassProperties -notcontains $_.Name }
            foreach($obj in $properties) {
                $queryString += ConvertTo-QueryString -CurrentDepth $CurrentDepth -InputObject $obj.Value -CurrentPath $($CurrentPath + "[" + $obj.Name + "]")
            }
        } else {
            # edevel("fallback / " + $object.GetType())
            switch ($object.GetType()) {
                'bool' {
                    $queryString += $("&" + $CurrentPath + "=" + $object)
                    break
                }
                'int' {
                    $queryString += $("&" + $CurrentPath + "=" + $object)
                    break
                }
                'long' {
                    $queryString += $("&" + $CurrentPath + "=" + $object)
                    break
                }
                'String' {
                    $queryString += $("&" + $CurrentPath + "=" + $object)
                    break
                }
                'PSCustomObject' {
                    # edevel("PSCustomObject / " + $object.GetType())
                    $ObjectClassProperties = @( Get-ObjectProperties -obj $object )
                    $properties = $object.PsObject.Properties | Where-Object { $ObjectClassProperties -notcontains $_.Name }
                    foreach($obj in $properties) {
                            $queryString += ConvertTo-QueryString -CurrentDepth $CurrentDepth -InputObject $obj.Value -CurrentPath $($CurrentPath + "[" + $obj.Name + "]")
                    }
                }
                default {
                    break
                }
            }
        }

        # edevel("queryString = " + $queryString)
        $CurrentDepth--
        if ($CurrentDepth -eq -1) {
            return $queryString.Trim("&")
        } else {
            return $queryString
        }
    }

    End {
        # eleave($MyInvocation.MyCommand)
    }
}

<#
.SYNOPSIS
Trim leading and trailing characters
 
.DESCRIPTION
Trim leading and trailing additional non-wanted characters like
- slashes '/'
 
.EXAMPLE
Trim-Url -URL "https://localhost:8000/"
 
.NOTES
General notes
#>

function Trim-Url {
    [CmdletBinding()]
    [OutputType([String])]
    Param (
        [Parameter(Mandatory = $true, ValueFromPipeLine = $true)][string]$URL
    )
    Begin {
        # Write-EnterFunction
    }

    Process {
        return $URL.Trim('/')
    }

    End {
        # Write-LeaveFunction
    }
}