RestPS.psm1

Write-Verbose 'Importing from [C:\projects\restps\RestPS\private]'
# .\RestPS\private\Invoke-AvailableRouteSet.ps1
function Invoke-AvailableRouteSet
{
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidUsingInvokeExpression", '')]
    $script:Routes = @(
        @{
            'RequestType'    = 'GET'
            'RequestURL'     = '/proc'
            'RequestCommand' = 'get-process | select-object ProcessName'
        }
        @{
            'RequestType'    = 'GET'
            'RequestURL'     = '/process'
            'RequestCommand' = "endpoints\GET\Invoke-GetProcess.ps1"
        }
        @{
            'RequestType'    = 'PUT'
            'RequestURL'     = '/Service'
            'RequestCommand' = "endpoints\PUT\Invoke-GetProcess.ps1"
        }
        @{
            'RequestType'    = 'POST'
            'RequestURL'     = '/data'
            'RequestCommand' = "endpoints\POST\Invoke-GetProcess.ps1"
        }
        @{
            'RequestType'    = 'DELETE'
            'RequestURL'     = '/data'
            'RequestCommand' = "endpoints\DELETE\Invoke-GetProcess.ps1"
        }            
    )
}
# .\RestPS\private\Invoke-GetContext.ps1
function Invoke-GetContext
{
    $script:context = $listener.GetContext()
    $Request = $Context.Request
    return $Request
}
# .\RestPS\private\Invoke-RequestRouter.ps1
function Invoke-RequestRouter
{
    [CmdletBinding()]
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidUsingInvokeExpression", '')]
    [OutputType([boolean])]
    [OutputType([Hashtable])]
    param(
        [Parameter(Mandatory = $true)][String]$RequestType,
        [Parameter(Mandatory = $true)][String]$RequestURL,
        [Parameter(Mandatory = $false)][String]$RequestArgs,
        [Parameter()][String]$RoutesFilePath
    )
    # Import Routes each pass, to include new routes.
    Write-Output "Processing Request type: $RequestType on URL: $RequestURL Args: $RequestArgs"
    #Import the Endpoint Routes
    . $RoutesFilePath
    $Route = ($Routes | Where-Object {$_.RequestType -eq $RequestType -and $_.RequestURL -eq $RequestURL})

    if ($null -ne $Route)
    {
        # Process Request
        $RequestCommand = $Route.RequestCommand
        $Command = $RequestCommand + " " + $RequestArgs
        set-location $PSScriptRoot
        Write-Output "Attempting process Request type: $RequestType on URL: $RequestURL"
        $CommandReturn = Invoke-Expression -Command "$Command" -ErrorAction SilentlyContinue
        if ($null -eq $CommandReturn)
        {
            # Not a valid response
            $script:result = "Invalid Command"
        }
        else
        {
            # Valid response
            $script:result = $CommandReturn
        }
    }
    else
    {
        # No matching Routes
        $ErrorMessage = "No Matching Routes"
        Write-Output $ErrorMessage
        $script:result = $ErrorMessage
    }
    $script:result
}
# .\RestPS\private\Invoke-StartListener.ps1
function Invoke-StartListener
{
    param(
        [Parameter()][String]$Port = 8080
    ) 
    $listener.Prefixes.Add("http://+:$Port/") 
    $listener.Start()
    Write-Output "Starting HTTP Listener on Port: $Port"

}
# .\RestPS\private\Invoke-StopListener.ps1
function Invoke-StopListener
{
    param(
        [Parameter()][String]$Port = 8080
    ) 
    Write-Output "Stopping HTTP Listener on port: $Port ..."
    $listener.Stop()
}
# .\RestPS\private\Invoke-StreamOutput.ps1
function Invoke-StreamOutput
{
    # Process the Return data to send Json message back.
    # Convert the data to UTF8 bytes
    $message = $script:result | ConvertTo-Json
    [byte[]]$buffer = [System.Text.Encoding]::UTF8.GetBytes($message)
    # Set length of response
    $response.ContentLength64 = $buffer.length
    # Write response out and close
    $output = $response.OutputStream
    $output.Write($buffer, 0, $buffer.length)
    $output.Close()
}
Write-Verbose 'Importing from [C:\projects\restps\RestPS\public]'
# .\RestPS\public\Start-RestPSListener.ps1
function Start-RestPSListener
{
    <#
    .DESCRIPTION
        Start a HTTP listener on a specified port.
    .EXAMPLE
        Start-Listener
    .EXAMPLE
        Start-Listener -Port 8081
    .EXAMPLE
        Start-Listener -Port 8081 -RoutesFilePath C:\temp\customRoutes.ps1
    .EXAMPLE
        Start-Listener -RoutesFilePath C:\temp\customRoutes.ps1
    .PARAMETER Port
        A Port can be specified, but is not required, Default is 8080.
    .PARAMETER RoutesFilePath
        A Custom Routes file can be specified, but is not required, default is included in the module.
    .NOTES
        No notes at this time.
    #>
    
    [CmdletBinding(
        SupportsShouldProcess = $true,
        ConfirmImpact = "Low"
    )]
    [OutputType([boolean])]
    [OutputType([Hashtable])]
    [OutputType([String])]
    param(
        [Parameter()][String]$RoutesFilePath = "null",
        [Parameter()][String]$Port = 8080
    )    
    # No pre-task
    $script:Status = $true
    if ($pscmdlet.ShouldProcess("Starting HTTP Listener."))
    { 
        $script:listener = New-Object System.Net.HttpListener
        Invoke-StartListener -Port $Port
        # Run until you send a GET request to /end
        Do
        {
            # Capture requests as they come in (not Asyncronous)
            $script:Request = Invoke-GetContext

            # Request Handler Data
            $RequestType = $Request.HttpMethod
            $RawRequestURL = $Request.RawUrl
            $RequestURL, $RequestArgs = $RawRequestURL.split("?")

            # Setup a placeholder to deliver a response
            $script:response = $context.Response
            $result = $null
        
            # Break from loop if GET request sent to /shutdown
            if ($RequestURL -match '/EndPoint/Shutdown$')
            {
                Write-Output "Received Request to shutdown Endpoint."
                $script:result = "Shutting down ReST Endpoint."
                $script:Status = $false
            }
            else
            {
                # Attempt to process the Request.
                Write-Output "Processing RequestType: $RequestType URL: $RequestURL Args: $RequestArgs"
                if ($RoutesFilePath -eq "null")
                {
                    $RoutesFilePath = "Invoke-AvailableRouteSet"
                }
                $script:result = Invoke-RequestRouter -RequestType $RequestType -RequestURL $RequestURL -RoutesFilePath $RoutesFilePath -RequestArgs $RequestArgs
            }
            # Convert the returned data to JSON and set the HTTP content type to JSON
            Write-Output "The result is $script:result"
            $script:response.ContentType = 'application/json'
            # Stream the output back to requestor.
            Invoke-StreamOutput
        } while ($script:Status -eq $true)
        #Terminate the listener
        Invoke-StopListener -Port $Port
        return "Listener Stopped"
    } 
    else
    {
        # -WhatIf was used.
        return $false
    }         
}
Write-Verbose 'Importing from [C:\projects\restps\RestPS\classes]'