EtherAssist.psm1

<#
.SYNOPSIS
This module contains functions for managing and interacting with the EtherAssist API, including setting and retrieving API keys, and sending queries to the API.

.DESCRIPTION
The EtherAssist module provides a set of PowerShell functions for securely managing the EtherAssist API key and querying the EtherAssist API. It includes functions to set and get the API key, and to send questions to the API with customizable response options.

.NOTES
File Name : EtherAssist.psm1
Author : Ryan Mangan
Prerequisite : PowerShell V5.1
Copyright 2024 - EfficientEther Ltd

# Functions
1. Set-EAApiConfig: Sets and securely stores the EtherAssist API URL and Key.
2. Get-EAApiConfig: Retrieves the stored EtherAssist API URL and Key.
3. Send-EARequest: Sends a question to the EtherAssist API and processes the response.
4. Invoke-EAQuery: Prompts for a user question and sends it to the EtherAssist API with options to mute certain parts of the response.
5. Send-EACompletions: Sends a basic question to the EtherAssist API and processes the response.

#>


# Requires -Version 5.1

# Function: Set-EAApiConfig
function Set-EAApiConfig
{
    <#
    .SYNOPSIS
    Sets and securely stores the EtherAssist API URL and Key.
    
    .DESCRIPTION
    Stores the API key and URL securely in an encrypted format within the user's profile directory.
    
    .PARAMETER ApiKey
    The API key to be securely stored.
    
    .PARAMETER ApiUrl
    The URL of the EtherAssist API to be stored.

    .EXAMPLE
    Set-EAApiConfig -ApiKey "your_api_key_here" -ApiUrl "https://<your_api_url_here>"
    #>

    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [string]$ApiKey,
        [Parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [string]$ApiUrl
    )
    try
    {
        $KeyFilePath = Join-Path $env:USERPROFILE "EtherAssistApiKey.xml"
        $secureApiKey = ConvertTo-SecureString $ApiKey -AsPlainText -Force
        $settings = @{
            ApiKey = $secureApiKey
            ApiUrl = $ApiUrl
        }
        $settings | Export-Clixml -Path $KeyFilePath
    }
    catch
    {
        Write-Error "Error storing API Key: $_"
    }
}

# Function: Get-EAApiConfig
function Get-EAApiConfig
{
    <#
    .SYNOPSIS
    Retrieves the stored EtherAssist API Key and URL.
    
    .DESCRIPTION
    Fetches and decrypts the stored API Key and URL from the user's profile directory.

    .OUTPUTS
    Hashtable. Returns a hashtable containing the API key and URL.

    .EXAMPLE
    $settings = Get-EAApiConfig
    #>

    [CmdletBinding()]
    $KeyFilePath = Join-Path $env:USERPROFILE "EtherAssistApiKey.xml"
    if (Test-Path $KeyFilePath)
    {
        try
        {
            $settings = Import-Clixml -Path $KeyFilePath
            $settings.ApiKey = [System.Runtime.InteropServices.Marshal]::PtrToStringAuto([System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($settings.ApiKey))
            return $settings
        }
        catch
        {
            Write-Error "Error retrieving API Key: $_"
        }
    }
    else
    {
        Write-Error "API Key and URL are not set. Use Set-EAApiConfig to configure."
    }
}

# Function: Invoke-EtherAssistApi
function Invoke-EtherAssistApi
{
    <#
    .SYNOPSIS
    Invokes a query to the EtherAssist API.
    
    .DESCRIPTION
    Sends a request to the specified EtherAssist API endpoint with the given body.
    
    .PARAMETER Endpoint
    The specific API endpoint to send the request to.
    
    .PARAMETER Body
    The body of the request to be sent to the API.

    .EXAMPLE
    $response = Invoke-EtherAssistApi -Endpoint "/question" -Body $body
    #>

    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [string]$Endpoint,
        [Parameter(Mandatory = $true)]
        [ValidateNotNull()]
        [hashtable]$Body
    )
    
    $settings = Get-EAApiConfig
    if (-not $settings)
    {
        Write-Error "API settings are not set. Use Set-EAApiConfig to set the key and URL."
        return
    }
    
    $uri = "$($settings.ApiUrl)$Endpoint"
    $headers = @{
        Authorization = "Bearer $($settings.ApiKey)"
    }
    
    Write-Verbose "URI: $uri"
    Write-Verbose "Headers: $headers"
    Write-Verbose "Body: $(ConvertTo-Json $Body)"
    
    try
    {
        $response = Invoke-RestMethod -Uri $uri -Method Post -Headers $headers -Body ($Body | ConvertTo-Json) -ContentType "application/json"
        return $response
    }
    catch
    {
        Write-Error "Error in calling EtherAssist API: $_"
    }
}

# Function: Send-EARequest
function Send-EARequest
{
    <#
    .SYNOPSIS
    Sends a question to the EtherAssist API and processes the response.
    
    .DESCRIPTION
    Interacts with the EtherAssist API, sending a user-defined question and allowing response customization.
    The MuteAnswer switch only removes the 'Answer:' label from the response, keeping the answer text.
    
    .PARAMETER Question
    The question to be sent to the EtherAssist API.
    
    .PARAMETER UseAdvancedModel
    If set, uses the advanced model for generating the response.
    
    .PARAMETER MuteQuestion
    If set, omits the question from the response.
    
    .PARAMETER MuteDateTime
    If set, omits the date/time from the response.
    
    .PARAMETER MuteAnswer
    If set, removes the 'Answer:' label from the response, keeping the answer text.

    .PARAMETER SummarizeText
    If set, summarizes the provided text.

    .PARAMETER GenerateErrorCodeDescription
    If set, generates an error code description and possible solutions.

    .PARAMETER ConvertVbsToPs
    If set, converts VBS scripts to PowerShell scripts.

    .PARAMETER GetAppDescription
    If set, provides a description for an application.

    .PARAMETER AnalyzeLogs
    If set, analyzes log files and provides insights.

    .PARAMETER GetInstallerArgs
    If set, provides the installation command for the application.

    .PARAMETER AnalyzeMsix
    If set, analyzes a MSIX application manifest.

    .PARAMETER Title
    If set, provides a title for the given text.

    .PARAMETER OutputAsJson
    If set, returns the response as a JSON string.

    .PARAMETER OutputAsObject
    If set, returns the response as a structured PowerShell object.

    .NOTE
    Only one of the following switches can be used at a time:
    -UseAdvancedModel, -SummarizeText, -GenerateErrorCodeDescription, -ConvertVbsToPs, -GetAppDescription, -AnalyzeLogs, -GetInstallerArgs, -AnalyzeMsix, -Title
    
    .EXAMPLE
    Send-EARequest -Question "What is Cyber Essentials?" -OutputAsJson
    
    .EXAMPLE
    Send-EARequest -Question "Tell me a joke" -MuteQuestion -MuteDateTime -MuteAnswer
    #>

    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [string]$Question,
        [switch]$UseAdvancedModel,
        [switch]$MuteQuestion,
        [switch]$MuteDateTime,
        [switch]$MuteAnswer,
        [switch]$SummarizeText,
        [switch]$GenerateErrorCodeDescription,
        [switch]$ConvertVbsToPs,
        [switch]$GetAppDescription,
        [switch]$AnalyzeLogs,
        [switch]$GetInstallerArgs,
        [switch]$AnalyzeMsix,
        [switch]$Title,
        [switch]$OutputAsJson,
        [switch]$OutputAsObject
    )
    
    # Ensure only one processing option is selected
    $optionsSelected = @($SummarizeText, $GenerateErrorCodeDescription, $ConvertVbsToPs, $GetAppDescription, $AnalyzeLogs, $GetInstallerArgs, $AnalyzeMsix, $Title) | Where-Object { $_.IsPresent }
    if ($optionsSelected.Count -gt 1)
    {
        Write-Error "You can only select one of -SummarizeText, -GenerateErrorCodeDescription, -ConvertVbsToPs, -GetAppDescription, -AnalyzeLogs, -GetInstallerArgs, -AnalyzeMsix, or -Title at a time."
        return
    }
    
    $endpoint = "/question"
    $body = @{
        question         = $Question
        useAdvancedModel = $UseAdvancedModel.IsPresent
    }
    
    if ($SummarizeText.IsPresent)
    {
        $endpoint = "/utils/Summarize"
    }
    elseif ($Title.IsPresent)
    {
        $endpoint = "/utils/title"
    }
    elseif ($GenerateErrorCodeDescription.IsPresent)
    {
        $endpoint = "/gen/errorcode"
    }
    elseif ($ConvertVbsToPs.IsPresent)
    {
        $endpoint = "/gen/convert-vbs-to-ps"
    }
    elseif ($GetAppDescription.IsPresent)
    {
        $endpoint = "/apps/app-description"
    }
    elseif ($AnalyzeLogs.IsPresent)
    {
        $endpoint = "/gen/log-analyze"
    }
    elseif ($GetInstallerArgs.IsPresent)
    {
        $endpoint = "/apps/installer-args"
    }
    elseif ($AnalyzeMsix.IsPresent)
    {
        $endpoint = "/apps/msix-analysis"
    }
    
    $responseObject = Invoke-EtherAssistApi -Endpoint $endpoint -Body $body
    
    if ($responseObject.success -eq $true)
    {
        if ($OutputAsJson.IsPresent)
        {
            # Create a custom object and then convert it to a JSON string
            $resultObject = [PSCustomObject]@{
                Question = $Question
                Answer   = $responseObject.answer
                TimeOfQuery = Get-Date -Format 'o'
                AdditionalData = $responseObject | Select-Object * -ExcludeProperty question, answer
            }
            
            return $resultObject | ConvertTo-Json -Depth 10
        }
        elseif ($OutputAsObject.IsPresent)
        {
            # Create a custom object to hold the desired properties
            $resultObject = [PSCustomObject]@{
                Question = $Question
                Answer   = $responseObject.answer
                TimeOfQuery = Get-Date
                AdditionalData = $responseObject | Select-Object * -ExcludeProperty question, answer
            }
            
            return $resultObject
        }
        else
        {
            $responseMessage = @()
            if (-not $MuteQuestion)
            {
                $responseMessage += "Question: $Question"
            }
            if (-not $MuteDateTime)
            {
                $responseMessage += "Date/Time: $(Get-Date)"
            }
            
            $answerText = $responseObject.answer
            if (-not $MuteAnswer)
            {
                $responseMessage += "Answer: $answerText"
            }
            else
            {
                $responseMessage += $answerText
            }
            
            $responseMessage -join "`n"
        }
    }
    else
    {
        Write-Error "API request was not successful: $($responseObject.errorMessage)"
    }
}

# Function: Invoke-EAQuery
function Invoke-EAQuery
{
    <#
    .SYNOPSIS
    Prompts for a user question and sends it to the EtherAssist API with options to mute certain parts of the response.
    
    .DESCRIPTION
    This function prompts the user to enter a question, which is then sent to the EtherAssist API.
    The user can choose to mute the question, date/time, and/or answer label in the response.

    .PARAMETER UseAdvancedModel
    If set, uses the advanced model for generating the response.

    .PARAMETER MuteQuestion
    If set, omits the question from the response.

    .PARAMETER MuteDateTime
    If set, omits the date/time from the response.

    .PARAMETER MuteAnswer
    If set, removes the 'Answer:' label from the response, keeping only the answer text.

    .EXAMPLE
    Invoke-EAQuery -MuteQuestion -MuteDateTime -MuteAnswer
    #>

    [CmdletBinding()]
    param (
        [switch]$UseAdvancedModel,
        [switch]$MuteQuestion,
        [switch]$MuteDateTime,
        [switch]$MuteAnswer
    )
    
    try
    {
        # Prompt the user to input the question
        $question = Read-Host "Please enter the question for EtherAssist"
        
        # Call the Send-EARequest function with the user's question and muting options
        Send-EARequest -Question $question -UseAdvancedModel:$UseAdvancedModel -MuteQuestion:$MuteQuestion -MuteDateTime:$MuteDateTime -MuteAnswer:$MuteAnswer
    }
    catch
    {
        Write-Error "An error occurred while invoking EtherAssist query: $_"
    }
}

# Function: Send-EACompletions
function Send-EACompletions
{
    <#
    .SYNOPSIS
    Sends a basic question to the EtherAssist API and processes the response.
    
    .DESCRIPTION
    Interacts with the EtherAssist API, sending a user-defined question and allowing response customization.
    The MuteAnswer switch only removes the 'Answer:' label from the response, keeping the answer text.
    
    .PARAMETER Question
    The question to be sent to the EtherAssist API.
    
    .PARAMETER MuteQuestion
    If set, omits the question from the response.
    
    .PARAMETER MuteDateTime
    If set, omits the date/time from the response.
    
    .PARAMETER MuteAnswer
    If set, removes the 'Answer:' label from the response, keeping the answer text.
    
    .PARAMETER Advanced
    If set, uses the advanced model for generating the response.

    .PARAMETER OutputAsJson
    If set, returns the response as a JSON string.

    .PARAMETER OutputAsObject
    If set, returns the response as a structured PowerShell object.

    .EXAMPLE
    Send-EACompletions -Question "What is Cyber Essentials?" -OutputAsJson
    
    .EXAMPLE
    Send-EACompletions -Question "Tell me a joke" -MuteQuestion -MuteDateTime -MuteAnswer
    
    .EXAMPLE
    Send-EACompletions -Question "Explain the benefits of PowerShell" -Advanced
    #>

    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [string]$Question,
        [switch]$MuteQuestion,
        [switch]$MuteDateTime,
        [switch]$MuteAnswer,
        [switch]$Advanced,
        [switch]$OutputAsJson,
        [switch]$OutputAsObject
    )
    
    $body = @{
        question         = $Question
        useAdvancedModel = $Advanced.IsPresent
    }
    
    $responseObject = Invoke-EtherAssistApi -Endpoint "/completions" -Body $body
    
    if ($responseObject.success -eq $true)
    {
        if ($OutputAsJson.IsPresent)
        {
            # Create a custom object and then convert it to a JSON string
            $resultObject = [PSCustomObject]@{
                Question = $Question
                Answer   = $responseObject.answer
                TimeOfQuery = Get-Date -Format 'o'
                AdditionalData = $responseObject | Select-Object * -ExcludeProperty question, answer
            }
            
            return $resultObject | ConvertTo-Json -Depth 10
        }
        elseif ($OutputAsObject.IsPresent)
        {
            # Create a custom object to hold the desired properties
            $resultObject = [PSCustomObject]@{
                Question = $Question
                Answer   = $responseObject.answer
                TimeOfQuery = Get-Date
                AdditionalData = $responseObject | Select-Object * -ExcludeProperty question, answer
            }
            
            return $resultObject
        }
        else
        {
            $responseMessage = @()
            if (-not $MuteQuestion)
            {
                $responseMessage += "Question: $Question"
            }
            if (-not $MuteDateTime)
            {
                $responseMessage += "Date/Time: $(Get-Date)"
            }
            
            $answerText = $responseObject.answer
            if (-not $MuteAnswer)
            {
                $responseMessage += "Answer: $answerText"
            }
            else
            {
                $responseMessage += $answerText
            }
            
            $responseMessage -join "`n"
        }
    }
    else
    {
        Write-Error "API request was not successful: $($responseObject.errorMessage)"
    }
}

Export-ModuleMember -Function Set-EAApiConfig, Get-EAApiConfig, Send-EARequest, Invoke-EAQuery, Send-EACompletions