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-EtherAssistApiKey: Sets and securely stores the EtherAssist API Key.
2. Get-EtherAssistApiKey: Retrieves the stored EtherAssist API Key.
3. Get-EtherAssistResponse: Sends a question to the EtherAssist API and processes the response.
4. Invoke-EtherAssistQuery: Prompts for a user question and sends it to the EtherAssist API with options to mute certain parts of the response.
5. Get-EtherAssistResponseAsObject: Sends a question to the EtherAssist API and returns the response as a structured PowerShell object.
6. Get-EtherAssistResponseAsJson: Sends a question to the EtherAssist API and returns the response as a JSON string with additional details.
7. Get-EtherAssistBasicResponse: Sends a basic question to the EtherAssist API and processes the response.
 
#>


# Requires -Version 5.1

# Function: Set-EtherAssistApiKey
function Set-EtherAssistApiKey
{
    <#
    .SYNOPSIS
    Sets and securely stores the EtherAssist API Key and URL.
     
    .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-EtherAssistApiKey -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-EtherAssistApiKey
function Get-EtherAssistApiKey
{
    <#
    .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-EtherAssistApiKey
    #>

    [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-EtherAssistApiKey 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-EtherAssistApiKey
    if (-not $settings)
    {
        Write-Error "API settings are not set. Use Set-EtherAssistApiKey to set the key and URL."
        return
    }
    
    $uri = "$($settings.ApiUrl)$Endpoint"
    $headers = @{
        Authorization = "Bearer $($settings.ApiKey)"
    }
    
    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: Get-EtherAssistResponse
function Get-EtherAssistResponse
{
    <#
    .SYNOPSIS
    Sends a query 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 into a title.
 
    .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 Title
    If set, provides a title for the given text.
 
    .NOTE
    Only one of the following switches can be used at a time:
    -UseAdvancedModel, -SummarizeText, -GenerateErrorCodeDescription, -ConvertVbsToPs, -GetAppDescription, -Title
     
    .EXAMPLE
    Get-EtherAssistResponse -Question "What is Cyber Essentials?"
     
    .EXAMPLE
    Get-EtherAssistResponse -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]$Title
    )
    
    # Ensure only one processing option is selected
    $optionsSelected = @($SummarizeText, $GenerateErrorCodeDescription, $ConvertVbsToPs, $GetAppDescription, $Title) | Where-Object { $_.IsPresent }
    if ($optionsSelected.Count -gt 1)
    {
        Write-Error "You can only select one of -SummarizeText, -GenerateErrorCodeDescription, -ConvertVbsToPs, -GetAppDescription, 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" # Correct endpoint for title generation
    }
    elseif ($GenerateErrorCodeDescription.IsPresent)
    {
        $endpoint = "/gen/errorcode"
    }
    elseif ($ConvertVbsToPs.IsPresent)
    {
        $endpoint = "/gen/convert-vbs-to-ps"
    }
    elseif ($GetAppDescription.IsPresent)
    {
        $endpoint = "/apps/app-description"
    }
    
    
    $responseObject = Invoke-EtherAssistApi -Endpoint $endpoint -Body $body
    
    if ($responseObject.success -eq $true)
    {
        $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-EtherAssistQuery
function Invoke-EtherAssistQuery
{
    <#
    .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-EtherAssistQuery -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 Get-EtherAssistResponse function with the user's question and muting options
        Get-EtherAssistResponse -Question $question -UseAdvancedModel:$UseAdvancedModel -MuteQuestion:$MuteQuestion -MuteDateTime:$MuteDateTime -MuteAnswer:$MuteAnswer
    }
    catch
    {
        Write-Error "An error occurred while invoking EtherAssist query: $_"
    }
}

# Function: Get-EtherAssistResponseAsObject
function Get-EtherAssistResponseAsObject
{
    <#
    .SYNOPSIS
    Sends a question to the EtherAssist API and returns the response as a structured PowerShell object.
 
    .PARAMETER Question
    The question to be sent to the EtherAssist API.
 
    .PARAMETER UseAdvancedModel
    If set, uses the advanced model for generating the response.
 
    .PARAMETER SummarizeText
    If set, summarizes the provided text into a title.
 
    .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 Title
    If set, provides a title for the given text.
 
    .NOTE
    Only one of the following switches can be used at a time:
    -UseAdvancedModel, -SummarizeText, -GenerateErrorCodeDescription, -ConvertVbsToPs, -GetAppDescription, -Title
 
    .EXAMPLE
    Get-EtherAssistResponseAsObject -Question "What is Cyber Essentials?"
    #>

    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [string]$Question,
        [switch]$UseAdvancedModel,
        [switch]$SummarizeText,
        [switch]$GenerateErrorCodeDescription,
        [switch]$ConvertVbsToPs,
        [switch]$GetAppDescription,
        [switch]$Title
    )
    
    # Ensure only one processing option is selected
    $optionsSelected = @($SummarizeText, $GenerateErrorCodeDescription, $ConvertVbsToPs, $GetAppDescription, $Title) | Where-Object { $_.IsPresent }
    if ($optionsSelected.Count -gt 1)
    {
        Write-Error "You can only select one of -SummarizeText, -GenerateErrorCodeDescription, -ConvertVbsToPs, -GetAppDescription, 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" # Correct endpoint for title generation
    }
    elseif ($GenerateErrorCodeDescription.IsPresent)
    {
        $endpoint = "/gen/errorcode"
    }
    elseif ($ConvertVbsToPs.IsPresent)
    {
        $endpoint = "/gen/convert-vbs-to-ps"
    }
    elseif ($GetAppDescription.IsPresent)
    {
        $endpoint = "/apps/app-description"
    }
    
    $responseObject = Invoke-EtherAssistApi -Endpoint $endpoint -Body $body
    
    if ($responseObject.success -eq $true)
    {
        # 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
    {
        Write-Error "API request was not successful: $($responseObject.errorMessage)"
        return $null
    }
}

# Function: Get-EtherAssistResponseAsJson
function Get-EtherAssistResponseAsJson
{
    <#
    .SYNOPSIS
    Sends a question to the EtherAssist API and returns the response as a JSON string with additional details.
 
    .PARAMETER Question
    The question to be sent to the EtherAssist API.
 
    .PARAMETER UseAdvancedModel
    If set, uses the advanced model for generating the response.
 
    .PARAMETER SummarizeText
    If set, summarizes the provided text into a title.
 
    .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 Title
    If set, provides a title for the given text.
 
    .NOTE
    Only one of the following switches can be used at a time:
    -UseAdvancedModel, -SummarizeText, -GenerateErrorCodeDescription, -ConvertVbsToPs, -GetAppDescription, -Title
 
    .EXAMPLE
    Get-EtherAssistResponseAsJson -Question "What is Cyber Essentials?"
    #>

    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [string]$Question,
        [switch]$UseAdvancedModel,
        [switch]$SummarizeText,
        [switch]$GenerateErrorCodeDescription,
        [switch]$ConvertVbsToPs,
        [switch]$GetAppDescription,
        [switch]$Title
    )
    
    # Ensure only one processing option is selected
    $optionsSelected = @($SummarizeText, $GenerateErrorCodeDescription, $ConvertVbsToPs, $GetAppDescription, $Title) | Where-Object { $_.IsPresent }
    if ($optionsSelected.Count -gt 1)
    {
        Write-Error "You can only select one of -SummarizeText, -GenerateErrorCodeDescription, -ConvertVbsToPs, -GetAppDescription, 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" # Correct endpoint for title generation
    }
    elseif ($GenerateErrorCodeDescription.IsPresent)
    {
        $endpoint = "/gen/errorcode"
    }
    elseif ($ConvertVbsToPs.IsPresent)
    {
        $endpoint = "/gen/convert-vbs-to-ps"
    }
    elseif ($GetAppDescription.IsPresent)
    {
        $endpoint = "/apps/app-description"
    }
    
    
    $responseObject = Invoke-EtherAssistApi -Endpoint $endpoint -Body $body
    
    if ($responseObject.success -eq $true)
    {
        # Create a custom object and then convert it to a JSON string
        $resultObject = [PSCustomObject]@{
            Question = $Question
            Answer   = $responseObject.answer
            TimeOfQuery = Get-Date -Format 'o' # ISO 8601 format
            AdditionalData = $responseObject | Select-Object * -ExcludeProperty question, answer
        }
        
        return $resultObject | ConvertTo-Json -Depth 10
    }
    else
    {
        Write-Error "API request was not successful: $($responseObject.errorMessage)"
        return $null
    }
}

# Function: Get-EtherAssistBasicResponse
function Get-EtherAssistBasicResponse
{
    <#
    .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.
     
    .EXAMPLE
    Get-EtherAssistBasicResponse -Question "What is Cyber Essentials?"
     
    .EXAMPLE
    Get-EtherAssistBasicResponse -Question "Tell me a joke" -MuteQuestion -MuteDateTime -MuteAnswer
     
    .EXAMPLE
    Get-EtherAssistBasicResponse -Question "Explain the benefits of PowerShell" -Advanced
    #>

    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [string]$Question,
        [switch]$MuteQuestion,
        [switch]$MuteDateTime,
        [switch]$MuteAnswer,
        [switch]$Advanced
    )
    
    $body = @{
        question         = $Question
        useAdvancedModel = $Advanced.IsPresent
    }
    
    $responseObject = Invoke-EtherAssistApi -Endpoint "/question-basic" -Body $body
    
    if ($responseObject.success -eq $true)
    {
        $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-EtherAssistApiKey, Get-EtherAssistApiKey, Get-EtherAssistResponse, Invoke-EtherAssistQuery, Get-EtherAssistResponseAsObject, Get-EtherAssistResponseAsJson, Get-EtherAssistBasicResponse