SessionManager.ps1

using namespace system;
using namespace system.net;
using namespace system.net.http;
using namespace microsoft.powershell.commands;

. $PSScriptRoot\Exception.ps1

Function GetSession {
    <#
    .synopsis
    Create a web session with the atom server
    .parameter AtomUrl
    URL to AtoM web application
    #>

    Param(
        [Parameter(Mandatory=$True, Position=0)]
        [String]
        $AtomUrl
    )
    $Response = $NULL
    $OldPreference = $ProgressPreference
    Try {
        $ProgressPreference = 'SilentlyContinue'
        $Response = Invoke-WebRequest -Uri $AtomUrl -Method GET -SessionVariable 'NewSession'
        ThrowIfUnsuccessful -Response $Response -RequestUri $AtomUrl
    }
    Catch [Exception] {
        If ($Response) {
            ThrowIfUnsuccessful -RequestUri $AtomUrl -Response $Response
        }
        ElseIf ($_.Exception.Response) {
            ThrowIfUnsuccessful -RequestUri $AtomUrl -Response $_.Exception.Response
        }
        Else {
            ThrowIfUnsuccessful -RequestUri $AtomUrl -ErrorMessage $_.Exception.Message
        }
    }
    Finally {
        $ProgressPreference = $OldPreference
    }
    Return $NewSession
}

Function LoginToF5LoadBalancer {
    <#
    .synopsis
    Get an authenticated web session with an F5 load balancer
    .description
    This function should be used when one must first log in to an F5 load balancer before being able
    to access the AtoM web application.
    .parameter AtomUrl
    URL to AtoM web application. The url plus /my.policy is used to log in to the load balancer
    #>

    Param(
        [Parameter(Mandatory=$True, Position=0)]
        [String]
        $AtomUrl
    )
    $OldPreference = $ProgressPreference
    $ProgressPreference = 'SilentlyContinue'

    $Session = GetSession -AtomUrl $AtomUrl
    $LoginUrl = $AtomUrl.TrimEnd('/') + '/my.policy'
    $CredentialsAccepted = $False
    $OperationCancelled = $False
    $ErrorMessage = ''

    While (-Not $CredentialsAccepted -And -Not $OperationCancelled) {
        If ($ErrorMessage) {
            $Prompt = "$($ErrorMessage.TrimEnd('.')). Try re-entering your F5 credentials"
        }
        Else {
            $Prompt = "Enter your F5 credentials for $LoginUrl"
        }

        If ($PSVersionTable.PSVersion.Major -ge 6) {
            $Credentials = Get-Credential -Title $Prompt
        }
        Else {
            $Credentials = Get-Credential -Message $Prompt
        }

        If (-Not $Credentials) {
            $OperationCancelled = $True
            Continue
        }

        $NetworkCredentials = $Credentials.GetNetworkCredential()
        $LoginData = @{
            username=$NetworkCredentials.UserName;
            password=$NetworkCredentials.Password;
            vhost='standard';
        }

        If (-Not $LoginData['password']) {
            $ErrorMessage = 'You must enter your password'
            Continue
        }

        Try {
            $Response = Invoke-WebRequest -Uri $LoginUrl -Body $LoginData -Method POST -WebSession $Session
            ThrowIfUnsuccessful -Response $Response -RequestUri $LoginUrl
        }
        Catch [Exception] {
            If ($Response) {
                ThrowIfUnsuccessful -RequestUri $LoginUrl -Response $Response
            }
            ElseIf ($_.Exception.Response) {
                ThrowIfUnsuccessful -RequestUri $LoginUrl -Response $_.Exception.Response
            }
            Else {
                ThrowIfUnsuccessful -RequestUri $LoginUrl -ErrorMessage $_.Exception.Message
            }
        }

        $ResponseText = $Response.RawContent
        If ($ResponseText -Match 'too many users are logged in. Could not establish connection') {
            $Msg = 'Too many users are logged in to the server. Could not establish connection.'
            Throw [LoginException]::new($Msg)
        }
        ElseIf ($ResponseText -Match 'Access was denied by the access policy') {
            $Msg = "Access to the server was denied. Make sure you have access to $LoginUrl before trying again."
            Throw [LoginException]::new($Msg)
        }
        ElseIf ($ResponseText -Match 'password is not correct') {
            $ErrorMessage = 'The username or password was not correct'
            Continue
        }
        Else {
            $CredentialsAccepted = $True
        }
    }
    $ProgressPreference = $OldPreference

    If ($OperationCancelled) {
        Throw [LoginException]::new('Login cancelled.')
    }

    Return $Session
}

Function LoginToAtom {
    <#
    .synopsis
    Log in to an AtoM instance
    .description
    This function should be used when one does not need any other credentials than their AtoM
    credentials to access the AtoM web application (e.g., no load balancer exists).
    .parameter AtomUrl
    URL to AtoM web application
    .parameter WebSession
    An intialized web session
    #>

    Param(
        [Parameter(Mandatory=$True, Position=0)]
        [String]
        $AtomUrl,

        [Parameter(Mandatory=$True, Position=1)]
        [Microsoft.PowerShell.Commands.WebRequestSession]
        $WebSession
    )
    $OldPreference = $ProgressPreference
    $ProgressPreference = 'SilentlyContinue'

    $LoginUrl = $AtomUrl.TrimEnd('/') + '/index.php/user/login'
    $CredentialsAccepted = $False
    $OperationCancelled = $False
    $ErrorMessage = ''

    While (-Not $CredentialsAccepted -And -Not $OperationCancelled) {
        If ($ErrorMessage) {
            $Prompt = "$($ErrorMessage.TrimEnd('.')). Try entering your AtoM credentials again."
        }
        Else {
            $Prompt = 'Enter your AtoM credentials.'
        }

        If ($PSVersionTable.PSVersion.Major -ge 6) {
            $Credentials = Get-Credential -Title $Prompt
        }
        Else {
            $Credentials = Get-Credential -Message $Prompt
        }

        If (-Not $Credentials) {
            $OperationCancelled = $True
            Continue
        }

        $NetworkCredentials = $Credentials.GetNetworkCredential()
        $LoginData = @{
            email=$NetworkCredentials.UserName;
            password=$NetworkCredentials.Password;
            next=$AtomUrl.TrimEnd('/');
        }

        If (-Not $LoginData['password']) {
            $ErrorMessage = 'You must enter your password'
            Continue
        }

        Try {
            $Response = Invoke-WebRequest -Uri $LoginUrl -Body $LoginData -Method POST -WebSession $WebSession
            ThrowIfUnsuccessful -Response $Response -RequestUri $LoginUrl
            $ResponseText = $Response.RawContent
            If ($ResponseText -Match 'unrecognized email or password') {
                $ErrorMessage = 'The email or password was not correct'
                Continue
            }
            ElseIf ($ResponseText -Match 'isn.t a valid email address') {
                $ErrorMessage = 'The email address was not valid'
                Continue
            }
            Else {
                $CredentialsAccepted = $True
            }
        }
        Catch [Exception] {
            If ($Response) {
                ThrowIfUnsuccessful -RequestUri $LoginUrl -Response $Response
            }
            ElseIf ($_.Exception.Response) {
                ThrowIfUnsuccessful -RequestUri $LoginUrl -Response $_.Exception.Response
            }
            Else {
                ThrowIfUnsuccessful -RequestUri $LoginUrl -ErrorMessage $_.Exception.Message
            }
        }
    }
    $ProgressPreference = $OldPreference

    If ($OperationCancelled) {
        Throw [LoginException]::new('Login cancelled.')
    }
}

Function ThrowIfUnsuccessful {
    <#
    .synopsis
    Throws a Net.Http.HttpRequestException for an HTTP request if the status is not 200, or if an
    error message is supplied
    .parameter RequestUri
    The URI used in the web request
    .parameter Response
    The response object retrieved from the web request
    .parameter ErrorMessage
    An error message to place in the exception. When this parameter is used, this function is
    guaranteed to throw an exception
    #>

    [CmdletBinding(DefaultParameterSetName='Response')]
    Param(
        [Parameter(Mandatory=$True, ParameterSetName='Response')]
        [Parameter(Mandatory=$True, ParameterSetName='ErrorMessage')]
        [Parameter(Mandatory=$True)]
        [String]
        $RequestUri,

        [Parameter(Mandatory=$True, ParameterSetName='Response')]
        [Object]
        $Response,

        [Parameter(Mandatory=$True, ParameterSetName='ErrorMessage')]
        [String]
        $ErrorMessage
    )
    If ($PSCmdlet.ParameterSetName -eq 'ErrorMessage') {
        $Message = ("Could not connect to server at $($RequestUri):`n" +
                    "$ErrorMessage")
        Throw [HttpRequestException]::new($Message)
    }
    Else {
        $Result = ExtractStatusCodeAndDescription -Response $Response
        $Code = $Result.Code
        $Description = $Result.Description
        If ($Code -ne 200) {
            $Message = ("Could not connect to server at $($RequestUri):`n" +
                        "Server responded with: $Code $Description")
            Throw [HttpRequestException]::new($Message)
        }
    }
}

Function ExtractStatusCodeAndDescription {
    <#
    .synopsis
    Retrieves the status code and description from multiple different types of HTTP response objects
    .parameter Response
    Either a System.Net.HttpWebResponse, a Microsoft.PowerShell.Commands.HtmlWebResponseObject, or
    a System.Net.Http.HttpResponseMessage
    #>

    Param(
        [Parameter(Mandatory=$True)]
        [Object]
        $Response
    )
    If ($Response -is [WebResponseObject] -or $Response -is [HttpWebResponse]) {
        $Code = [Int] $Response.StatusCode
        $Description = [String] $Response.StatusDescription
    }
    ElseIf ($Response -is [HttpResponseMessage]) {
        $Code = [Int] $Response.StatusCode
        $Description = [String] $Response.ReasonPhrase
    }
    Else {
        $Type = $Response.GetType().FullName
        Throw "Unrecognized Response object found with type $Type"
    }
    Return [PSCustomObject] @{
        Code = $Code;
        Description = $Description;
    }
}