NinjaRmmApiTool.psm1

Function Set-NinjaSecrets {
    [OutputType('void')]
    Param(
        [AllowNull()]
        [String] $AccessKeyId,

        [AllowNull()]
        [String] $SecretAccessKey
    )

    $global:NinjaRmmAccessKeyID = $AccessKeyId
    $global:NinjaRmmSecretAccessKey = $SecretAccessKey
    Write-Host "Your secrets have been set."
}

Function Set-NinjaServerLocation {
    [OutputType('void')]
    Param(
        [ValidateSet('US', 'EU')]
        [String] $Location = 'US'
    )

    $global:NinjaRmmServerLocation = $Location
}

Function Set-NinjaOAuthEndpoint {
    [OutputType('void')]
    Param(
        [Parameter(Mandatory = $true)]
        [String] $OAuthEndpoint
    )

    $global:NinjaRmmOAuthEndpoint = $OAuthEndpoint
    Write-Host "Your Oauth endpoint has been set."
}

Function Set-NinjaScope {
    [OutputType('void')]
    Param(
        [Parameter(Mandatory = $true)]
        [String] $Scope
    )

    $global:NinjaRmmScope = $Scope
}

Function Send-NinjaRequest {
    [CmdletBinding()]
    Param(
        [Parameter(Mandatory)]
        [ValidateNotNullOrEmpty()]
        [String] $RequestToSend,

        [ValidateSet('GET', 'PUT', 'POST', 'DELETE')]
        [String] $Method = 'GET',

        [Hashtable] $Headers = @{},
        
        [Parameter(Mandatory = $false)]
        [AllowEmptyString()]
        [String] $Body = ''
    )

    If ($null -eq $global:NinjaRmmSecretAccessKey) {
        Throw [Data.NoNullAllowedException]::new('No secret access key has been provided. Please run Set-NinjaSecrets.')
    }
    If ($null -eq $global:NinjaRmmAccessKeyID) {
        Throw [Data.NoNullAllowedException]::new('No access key ID has been provided. Please run Set-NinjaSecrets.')
    }

    $AccessToken = Get-AccessToken -TokenUrl $global:NinjaRmmOAuthEndpoint -ClientID $global:NinjaRmmAccessKeyID -ClientSecret $global:NinjaRmmSecretAccessKey

    If ($null -eq $AccessToken) {
        Throw [System.Exception]::new('Failed to retrieve access token.')
    }

    If (($global:NinjaRmmServerLocation -eq 'US') -or ($null -eq $global:NinjaRmmServerLocation)) {
        $HostName = 'api.ninjarmm.com'
    }
    ElseIf ($global:NinjaRmmServerLocation -eq 'EU') {
        $HostName = 'eu-api.ninjarmm.com'
    }
    Else {
        Throw [ArgumentException]::new("The server location ${global:NinjaRmmServerLocation} is not valid. Please run Set-NinjaServerLocation.")
    }

    $UserAgent = "PowerShell/$($PSVersionTable.PSVersion) "
    $UserAgent += "NinjaRmmApiTool/$((Get-Module -Name 'NinjaRmmApiTool').Version) "
    $UserAgent += '(implementing API version 0.1.2)'

    [Net.ServicePointManager]::SecurityProtocol = [Net.ServicePointManager]::SecurityProtocol -bor [Net.SecurityProtocolType]::Tls12

    If ([Net.SecurityProtocolType].GetMembers() -Contains 'Tls13') {
        [Net.ServicePointManager]::SecurityProtocol = [Net.ServicePointManager]::SecurityProtocol -bor [Net.SecurityProtocolType]::Tls13
    }

    Write-Debug -Message ("Will send the request:`n`n" `
            + "$Method $RequestToSend HTTP/1.1`n" `
            + "Host: $HostName`n" `
            + "Authorization: Bearer $AccessToken`n" `
            + "User-Agent: $UserAgent")

    $Arguments = @{
        'Method'  = $Method
        'Uri'     = "https://$HostName$RequestToSend"
        'Headers' = @{
            'Authorization' = "Bearer $AccessToken"
            'Host'          = $HostName
            'User-Agent'    = $UserAgent
        }
    }

    If ($Headers) {
        $Arguments['Headers'] += $Headers
    }

    If ($Body) {
        $Arguments['Body'] = $Body
    }

    Return (Invoke-RestMethod @Arguments)
}

Function Get-AccessToken {
    param (
        [Parameter(Mandatory = $true)]
        [String] $TokenUrl,

        [Parameter(Mandatory = $true)]
        [String] $ClientID,

        [Parameter(Mandatory = $true)]
        [String] $ClientSecret
    )

    # Use the global scope variable
    $Scope = $global:NinjaRmmScope

    $Body = "grant_type=client_credentials&client_id=$([uri]::EscapeDataString($ClientID))&client_secret=$([uri]::EscapeDataString($ClientSecret))&scope=$([uri]::EscapeDataString($Scope))"
    $Headers = @{
        'Content-Type' = 'application/x-www-form-urlencoded'
    }

    Write-Verbose "Request Body: $Body"
    Write-Verbose "Request Headers: $($Headers | Out-String)"

    try {
        $Response = Invoke-RestMethod -Uri $TokenUrl -Method Post -Headers $Headers -Body $Body
        Write-Verbose "Authentication successful. Access token retrieved."
        return $Response.access_token
    }
    catch {
        Write-Error "Error in getting access token: $($_.Exception.Response.StatusCode) $($_.Exception.Response.StatusDescription)"
        Write-Error "Detailed Error: $($_.Exception.Message)"
        return $null
    }
}

Function Get-NinjaAlerts {
    [CmdletBinding(DefaultParameterSetName = 'AllAlerts')]
    Param(
        [Parameter(ParameterSetName = 'OneAlert')]
        [UInt32] $AlertId,

        [Parameter(ParameterSetName = 'AllAlertsSince')]
        [UInt32] $Since
    )
    $global:NinjaRmmScope = 'monitoring'
    $Request = '/v2/alerts'
    If ($PSCmdlet.ParameterSetName -eq 'OneAlert') {
        $Request += "/$AlertId"
    }
    ElseIf ($PSCmdlet.ParameterSetName -eq 'AllAlertsSince') {
        $Request += "/since/$Since"
    }

    $alerts = Send-NinjaRequest -RequestToSend $Request

    # Function to convert Unix epoch time to human-readable format
    Function Convert-UnixTime {
        [CmdletBinding()]
        Param(
            [Parameter(Mandatory = $true, Position = 0)]
            [Double] $UnixTime
        )

        $epoch = [DateTime]"1970-01-01 00:00:00Z"
        $humanReadableTime = $epoch.AddSeconds($UnixTime).ToLocalTime()
        return $humanReadableTime
    }

    # Convert the timestamps
    foreach ($alert in $alerts) {
        $alert.createTime = Convert-UnixTime -UnixTime $alert.createTime
        $alert.updateTime = Convert-UnixTime -UnixTime $alert.updateTime
    }

    return $alerts
}

Function Get-NinjaCustomers {
    [CmdletBinding(DefaultParameterSetName = 'AllCustomers')]
    Param(
        [Parameter(ParameterSetName = 'OneCustomer')]
        [UInt32] $CustomerId,

        [Parameter(ParameterSetName = 'AllCustomers')]
        [UInt32] $PageSize = 50
    )
    $global:NinjaRmmScope = 'monitoring'
    $Request = "/v2/organizations?pageSize=$PageSize"
    If ($PSCmdlet.ParameterSetName -eq 'OneCustomer') {
        $Request = "/v2/organizations/$CustomerId"
    }

    Return (Send-NinjaRequest -RequestToSend $Request)
}
Function Get-NinjaDevices {
    [CmdletBinding(DefaultParameterSetName = 'AllDevices')]
    Param(
        [Parameter(ParameterSetName = 'OneDevice')]
        [UInt32] $DeviceId,

        [Parameter(ParameterSetName = 'AllDevices')]
        [UInt32] $PageSize = 500
    )
    $global:NinjaRmmScope = 'monitoring'
    $Request = "/v2/devices?pageSize=$PageSize"
    If ($PSCmdlet.ParameterSetName -eq 'OneDevice') {
        $Request = "/v2/devices/$DeviceId"
    }

    $devices = Send-NinjaRequest -RequestToSend $Request

    # Function to convert Unix epoch time to human-readable format
    Function Convert-UnixTime {
        [CmdletBinding()]
        Param(
            [Parameter(Mandatory = $true, Position = 0)]
            [Double] $UnixTime
        )

        $epoch = [DateTime]"1970-01-01 00:00:00Z"
        $humanReadableTime = $epoch.AddSeconds($UnixTime).ToLocalTime()
        return $humanReadableTime
    }

    # Convert the timestamps for each device
    foreach ($device in $devices) {
        if ($device.createTime) {
            $device.createTime = Convert-UnixTime -UnixTime $device.createTime
        }
        if ($device.lastContact) {
            $device.lastContact = Convert-UnixTime -UnixTime $device.lastContact
        }
        if ($device.lastUpdate) {
            $device.lastUpdate = Convert-UnixTime -UnixTime $device.lastUpdate
        }
    }

    return $devices
}

Function Get-NinjaSoftwareInventory {
    [CmdletBinding()]
    Param()

    # Ensure the scope is set to 'monitoring' for this function
    $global:NinjaRmmScope = 'monitoring'

    $Request = "/v2/software-products"
    $softwareInventory = Send-NinjaRequest -RequestToSend $Request

    return $softwareInventory
}
Function Reset-NinjaAlert {
    [CmdletBinding()]
    [Alias('Remove-NinjaAlert')]
    Param(
        [Parameter(Mandatory)]
        [String] $AlertId
    )
    $global:NinjaRmmScope = 'management'
    $Request = "/v2/alert/$AlertId/reset"
    $Headers = @{
        'accept'       = '*/*'
        'Content-Type' = 'application/json'
    }
    $Body = '{}' # Empty JSON body

    Write-Verbose "Sending request to: $Request"
    Return (Send-NinjaRequest -Method 'POST' -RequestToSend $Request -Headers $Headers -Body $Body)
    Write-Host "The selected alert has been reset."
}

Function Reset-NinjaSecrets {
    [Alias('Remove-NinjaSecrets')]
    [OutputType('void')]
    Param()

    Remove-Variable -Name 'NinjaRmmAccessKeyID' -Scope Global -ErrorAction SilentlyContinue
    Remove-Variable -Name 'NinjaRmmSecretAccessKey' -Scope Global -ErrorAction SilentlyContinue
    Remove-Variable -Name 'NinjaRmmScope' -Scope Global -ErrorAction SilentlyContinue
    Write-Host "Secrets have been reset"
}

Function Reboot-NinjaDevice {
    [CmdletBinding()]
    Param(
        [Parameter(Mandatory = $true)]
        [UInt32] $DeviceId,

        [Parameter(Mandatory = $true)]
        [String] $Reason,

        [Parameter()]
        [ValidateSet('NORMAL', 'FORCED')]
        [String] $RebootType = 'NORMAL'
    )
    $global:NinjaRmmScope = 'management'
    $Request = "/v2/device/$DeviceId/reboot/$RebootType"
    $Headers = @{
        'Content-Type' = 'application/json'
    }
    $Body = @{
        reason = $Reason
    } | ConvertTo-Json

    Write-Verbose "Sending request to: $Request"
    Return (Send-NinjaRequest -Method 'POST' -RequestToSend $Request -Headers $Headers -Body $Body)
    Write-Host 'Devivce has been rebooted.'
}