Public/Connect-NinjaOne.ps1

#Requires -Version 7
function Connect-NinjaOne {
    <#
        .SYNOPSIS
            Creates a new connection to a NinjaOne instance.
        .DESCRIPTION
            Creates a new connection to a NinjaOne instance and stores this in a PowerShell Session.
        .EXAMPLE
            PS C:\> Connect-NinjaOne -Instance "eu"
            This logs into NinjaOne using the Authorisation Code authorisation flow.
        .OUTPUTS
            Sets two script-scoped variables to hold connection and authentication information.
    #>

    [CmdletBinding( DefaultParameterSetName = 'Authorisation Code' )]
    [OutputType([System.Void])]
    Param (
        # Use the "Authorisation Code" flow with your web browser.
        [Parameter( ParameterSetName = 'Authorisation Code', Mandatory = $True )]
        [Switch]$UseWebAuth,
        # Use the "Token Authentication" flow - useful if you already have a refresh token.
        [Parameter( ParameterSetName = 'Token Authentication', Mandatory = $True )]
        [switch]$UseTokenAuth,
        # Use the "Client Credentials" flow - useful if you already have a client ID and secret.
        [Parameter( ParameterSetName = 'Client Credentials', Mandatory = $True )]
        [switch]$UseClientAuth,
        # The NinjaOne instance to connect to. Choose from 'eu', 'oc' or 'us'.
        [Parameter( ParameterSetName = 'Authorisation Code', Mandatory = $True )]
        [Parameter( ParameterSetName = 'Token Authentication', Mandatory = $True )]
        [Parameter( ParameterSetName = 'Client Credentials', Mandatory = $True )]
        [ValidateSet('eu', 'oc', 'us')]
        [string]$Instance,
        # The Client ID for the application configured in NinjaOne.
        [Parameter( ParameterSetName = 'Authorisation Code', Mandatory = $True )]
        [Parameter( ParameterSetName = 'Token Authentication', Mandatory = $True )]
        [Parameter( ParameterSetName = 'Client Credentials', Mandatory = $True )]
        [String]$ClientID,
        # The Client Secret for the application configured in NinjaOne.
        [Parameter( ParameterSetName = 'Authorisation Code', Mandatory = $True )]
        [Parameter( ParameterSetName = 'Token Authentication', Mandatory = $True )]
        [Parameter( ParameterSetName = 'Client Credentials', Mandatory = $True )]
        [String]$ClientSecret,
        # The API scopes to request, if this isn't passed the scope is assumed to be "all". Pass a string or array of strings. Limited by the scopes granted to the application in NinjaOne.
        [Parameter( ParameterSetName = 'Authorisation Code' )]
        [Parameter( ParameterSetName = 'Token Authentication' )]
        [Parameter( ParameterSetName = 'Client Credentials' )]
        [ValidateSet('monitoring', 'management', 'control', 'offline_access')]
        [String[]]$Scopes = $PSCmdlet.ParameterSetName -eq 'Client Credentials' ? @('monitoring', 'management', 'control') : @('monitoring', 'management', 'control', 'offline_access'),
        # The redirect URI to use. If not set defaults to 'http://localhost'. Should be a full URI e.g. https://redirect.example.uk:9090/auth
        [Parameter( ParameterSetName = 'Authorisation Code' )]
        [URI]$RedirectURL,
        # The port to use for the redirect URI. Must match with the configuration set in NinjaOne. If not set defaults to '9090'.
        [Parameter( ParameterSetName = 'Authorisation Code' )]
        [Int]$Port = 9090,
        # The refresh token to use for "Token Authentication" flow.
        [Parameter( ParameterSetName = 'Token Authentication' )]
        [String]$RefreshToken,
        # Output the tokens - useful when using "Authorisation Code" flow - to use with "Token Authentication" flow.
        [Parameter( ParameterSetName = 'Authorisation Code' )]
        [Parameter( ParameterSetName = 'Token Authentication' )]
        [Parameter( ParameterSetName = 'Client Credentials' )]
        [Switch]$ShowTokens
    )
    # Convert scopes to space separated string if it's an array.
    if ($Scopes -is [System.Array]) {
        $AuthScopes = $Scopes -Join ' '
    } else {
        $AuthScopes = $Scopes
    }
    # Get the NinjaOne instance URL.
    if ($Instance) {
        Write-Verbose "Using instance $($Instance) with URL $($Script:NRAPIInstances[$Instance])"
        $URL = $Script:NRAPIInstances[$Instance]
    }
    # Generate a GUID to serve as our state validator.
    $GUID = ([GUID]::NewGuid()).Guid
    # Build the redirect URI, if we need one.
    if ($RedirectURL) {
        $RedirectURI = [System.UriBuilder]$RedirectURL
    } else {
        $RedirectURI = New-Object System.UriBuilder -ArgumentList 'http', 'localhost', $Port
    }
    # Build a script-scoped variable to hold the connection information.
    $ConnectionInformation = @{
        AuthMode = $PSCmdlet.ParameterSetName
        URL = $URL
        Instance = $Instance
        ClientID = $ClientID
        ClientSecret = $ClientSecret
        AuthListenerPort = $Port
        AuthScopes = $AuthScopes
        RedirectURI = $RedirectURI
    }
    Set-Variable -Name 'NRAPIConnectionInformation' -Value $ConnectionInformation -Visibility Private -Scope Script -Force
    Write-Debug "Connection information set to: $($Script:NRAPIConnectionInformation | Out-String)"
    $AuthenticationInformation = [HashTable]@{}
    # Set a script-scoped variable to hold authentication information.
    Set-Variable -Name 'NRAPIAuthenticationInformation' -Value $AuthenticationInformation -Visibility Private -Scope Script -Force
    if ($UseWebAuth) {
        # NinjaOne authorisation request query params.
        $AuthRequestParams = @{
            response_type = 'code'
            client_id = $Script:NRAPIConnectionInformation.ClientID
            client_secret = $Script:NRAPIConnectionInformation.ClientSecret
            redirect_uri = $Script:NRAPIConnectionInformation.RedirectURI.ToString()
            state = $GUID
        }
        if ($Script:NRAPIConnectionInformation.AuthScopes) {
            $AuthRequestParams.scope = $Script:NRAPIConnectionInformation.AuthScopes
        }
        # Build the authentication URI.
        # Start with the query string.
        $AuthRequestQuery = [System.Web.HttpUtility]::ParseQueryString([String]::Empty)
        $AuthRequestParams.GetEnumerator() | ForEach-Object {
            $AuthRequestQuery.Add($_.Key, $_.Value)
        }
        # Now the authentication URI
        $AuthRequestURI = [System.UriBuilder]$URL
        $AuthRequestURI.Path = 'oauth/authorize'
        $AuthRequestURI.Query = $AuthRequestQuery.ToString()
        Write-Debug "Authentication request query string is $($AuthRequestQuery.ToString())"
        try {
            # Get our authorisation code.
            Write-Verbose 'Opening browser to authenticate.'
            Write-Debug "Authentication URL: $($AuthRequestURI.ToString())"
            $HTTP = [System.Net.HttpListener]::new()
            $HTTP.Prefixes.Add("http://localhost:$Port/")
            $HTTP.Start()
            Start-Process $AuthRequestURI.ToString() 
            while ($HTTP.IsListening) {
                $Context = $HTTP.GetContext()
                Write-Debug $Context.Request.QueryString
                if ($Context.Request.QueryString -and $Context.Request.QueryString['Code']) {
                    $Script:NRAPIAuthenticationInformation.Code = $Context.Request.QueryString['Code']
                    Write-Debug "Authorisation code received: $($Script:NRAPIAuthenticationInformation.Code)"
                    if ($null -ne $Script:NRAPIAuthenticationInformation.Code) {
                        $GotAuthorisationCode = $True
                    }
                    [string]$HTML = '<h1>NinjaOne PowerShell Module</h1><br /><p>An authorisation code has been received. You can close this window now. The HTTP listener will stop in 5 seconds.</p>'
                    $Response = [System.Text.Encoding]::UTF8.GetBytes($HTML)
                    $Context.Response.ContentLength64 = $Response.Length
                    $Context.Response.OutputStream.Write($Response, 0, $Response.Length)
                    $Context.Response.OutputStream.Close()
                    Start-Sleep -Seconds 5
                    $HTTP.Stop()
                }
            }
        } catch {
            New-NinjaOneError -ErrorRecord $_
        }
    }
    if (($UseTokenAuth) -or ($GotAuthorisationCode) -or ($UseClientAuth)) {
        Write-Verbose 'Getting authentication token.'
        try {
            if ($GotAuthorisationCode) {
                Write-Verbose 'Using token authentication.'
                $TokenRequestBody = @{
                    grant_type = 'authorization_code'
                    client_id = $Script:NRAPIConnectionInformation.ClientID
                    client_secret = $Script:NRAPIConnectionInformation.ClientSecret
                    code = $Script:NRAPIAuthenticationInformation.Code
                    redirect_uri = $Script:NRAPIConnectionInformation.RedirectURI.toString()
                    scope = $Script:NRAPIConnectionInformation.AuthScopes
                }
            } elseif ($UseTokenAuth) {
                Write-Verbose 'Using refresh token.'
                $TokenRequestBody = @{
                    grant_type = 'refresh_token'
                    client_id = $Script:NRAPIConnectionInformation.ClientID
                    client_secret = $Script:NRAPIConnectionInformation.ClientSecret
                    refresh_token = $RefreshToken
                    scope = $Script:NRAPIConnectionInformation.AuthScopes
                }
            } elseif ($UseClientAuth) {
                Write-Verbose 'Using client authentication.'
                $TokenRequestBody = @{
                    grant_type = 'client_credentials'
                    client_id = $Script:NRAPIConnectionInformation.ClientID
                    client_secret = $Script:NRAPIConnectionInformation.ClientSecret
                    redirect_uri = $Script:NRAPIConnectionInformation.RedirectURI.toString()
                    scope = $Script:NRAPIConnectionInformation.AuthScopes
                }
            }
            Write-Debug "Token request body is $($TokenRequestBody | Out-String)"
            # Using our authorisation code or refresh token let's get an auth token.
            $TokenRequestUri = [System.UriBuilder]$URL
            $TokenRequestUri.Path = 'oauth/token'
            Write-Verbose "Making token request to $($TokenRequestUri.ToString())"
            $TokenRequestParams = @{
                Uri = $TokenRequestUri.ToString()
                Method = 'POST'
                Body = $TokenRequestBody
                ContentType = 'application/x-www-form-urlencoded'
            }
            $TokenResult = Invoke-WebRequest @TokenRequestParams
            $TokenPayload = $TokenResult.Content | ConvertFrom-Json
            # Update our script-scoped NRAPIAuthenticationInformation variable with the token.
            $Script:NRAPIAuthenticationInformation.Type = $TokenPayload.token_type
            $Script:NRAPIAuthenticationInformation.Access = $TokenPayload.access_token
            $Script:NRAPIAuthenticationInformation.Expires = Get-TokenExpiry -ExpiresIn $TokenPayload.expires_in
            $Script:NRAPIAuthenticationInformation.Refresh = $TokenPayload.refresh_token
            Write-Verbose 'Got authentication token information from NinjaOne.'
            Write-Debug "Authentication information set to: $($Script:NRAPIAuthenticationInformation | Out-String -Width 2048)"
            if ($ShowTokens) {
                Write-Output '================ Auth Tokens ================'
                Write-Output $($Script:NRAPIAuthenticationInformation | Format-Table)
                Write-Output ' SAVE THESE IN A SECURE LOCATION '
            }
        } catch {
            throw $_
        }
    }
}