Public/Add-OSLogPS.ps1

function Add-OSLogPS {
    <#
    .SYNOPSIS
        Adds PowerShell log to a log_ps_* data stream.

    .DESCRIPTION
        Specialized function for logging data from various PowerShell scripts. Success returns nothing.
        Any non-reserved paramters passed to the function will turn into fields passed to OpenSearch.

    .PARAMETER Index
        Name of the PowerShell index to add to. Must be log_ps_misc OR follow format: log_ps_{SCRIPT NAME}_{OPTIONAL HOSTNAME}-{OPTIONAL EXTRA INFO}

    .PARAMETER DisableLocalLog
        Use to disable using the local LogFile option. Disabling will speed up the function.

    .PARAMETER LogFile
        Path to a local log file to save a copy of what's sent to OpenSearch. Defaults to: $PSScriptRoot/Logs/$ScriptName_OpenSearch_yyyy-MM-dd.json

    .PARAMETER LogLevel
        The severity of the log. Must be one of the following: Information, Warning, Error, Fatal Error

    .PARAMETER Message
        Log message.

    .PARAMETER DocumentId
        Optionally include an _id to index the document at. Do not specify to have OpenSearch generate one.

    .PARAMETER Credential
        PSCredential for basic authentication to OpenSearch.

    .PARAMETER Certificate
        User certificate for certificate authentication to OpenSearch.

    .PARAMETER OpenSearchURL
        URL(s) to OpenSearch instance. Do not include any path or api endpoint.

    .EXAMPLE
        PS>$Request = @{
        PS>'Index' = 'log_ps_example'
        PS>'LogLevel' = 'Information'
        PS>'Message' = "Script is starting"
        PS>'AD.SamAccountName' = 'MyUser'
        PS>'LogFile' = './MyPath/ThisIsALog.json'
        PS>}
        PS>Add-OSLogPS @Request

        Use hashtable to add parameters with a period in the name.
    #>

    [CmdletBinding()]
    param(
        [Parameter(Mandatory)]
        [string]$Index,

        [switch]$DisableLocalLog,

        [string]$LogFile,

        [Parameter(Mandatory)]
        [ValidateSet('Trace', 'Verbose', 'Information', 'Warning', 'Error', 'Critical')]
        [string]$LogLevel,

        [string]$Message,

        [string]$DocumentId,

        [Parameter(ValueFromRemainingArguments=$true)]
        $AdditionalParams,

        [System.Management.Automation.Credential()]
        [PSCredential]$Credential=[PSCredential]::Empty,

        [System.Security.Cryptography.X509Certificates.X509Certificate2]$Certificate,

        $OpenSearchURL
    )

    # Handle the arbitrary params
    if ($null -ne $AdditionalParams){
        # If it's already a hashtable, don't do additional processing
        if ($AdditionalParams.GetType().Name -eq 'HashTable'){
            $AdditionalParamsHash = $AdditionalParams
        }
        else {
            $AdditionalParamsHash = Convert-OSAdditionalParam -AdditionalParams $AdditionalParams
        }

        # Verify field names
        Confirm-OSFieldNamingStandard -FieldNames $AdditionalParamsHash
    }

    # Only lowercase index names are allowed
    $Index = $Index.ToLower()
    # Verify format of IndexName
    if ($Index -notmatch 'log_ps_'){
        throw [System.ArgumentException] 'IndexName must be: log_ps_misc OR match the format: log_ps_{SCRIPT NAME}_{OPTIONAL HOSTNAME}-{OPTIONAL EXTRA INFO}'
    }

    # Script name is used for OpenSearch log and path to save local copy of log
    $ScriptName = $(Get-PSCallStack | Where-Object {$_.Command -match '.ps1'})[0].Command
    if ('' -eq $LogFile){
        $LogFile = "$global:PSScriptRoot\Logs\$($ScriptName.replace('.ps1',''))_OpenSearch_$(Get-Date -Format yyyy-MM-dd).json"
    }

    # Build the request
    $Body = @{
        'LogLevel' = $LogLevel
        'Message' = $Message
        'Hostname' = $env:COMPUTERNAME
        'PoSH.Script' = $ScriptName
        '@timestamp' = $(Get-Date)
    }
    if ($null -ne $AdditionalParamsHash){
        $Body += $AdditionalParamsHash
    }

    # Add log content to local log file
    if ($False -eq $DisableLocalLog){
        if (-not (Test-Path $LogFile)){
            # Redirect to $null, otherwise if there's no other return response it returns the Path object to the log
            New-Item -Path $LogFile -Force > $null
            $LogEntries = @($Body)
        }
        else {
            [Array]$LogEntries = Get-Content $LogFile | ConvertFrom-Json -Depth 100
            $LogEntries += $Body
        }

        $LogEntries | ConvertTo-Json -Depth 100 -AsArray | Out-File -FilePath $LogFile
    }

    $Body = $Body | ConvertTo-Json -Depth 100
    # PUT needed for custom _id fields. Otherwise POST
    if ($DocumentId -ne ''){
        $Request = $Index + '/_doc/' + $DocumentId
        $Response = Invoke-OSCustomWebRequest -Method 'PUT' -Request $Request -OpenSearchUrls $OpenSearchURL -Credential $Credential -Certificate $Certificate -Body $Body
    }
    else {
        $Request = $Index + '/_doc'
        $Response = Invoke-OSCustomWebRequest -Method 'POST' -Request $Request -OpenSearchUrls $OpenSearchURL -Credential $Credential -Certificate $Certificate -Body $Body
    }

    if ($Response.StatusCode -eq '201'){
        return
    }
    else {
        throw $Response
    }

}

Export-ModuleMember -Function Add-OSLogPS