Using PowerShell scripts

PowerShell scripts can be used with NXLog for generating, processing, and forwarding logs, as well as for generating configuration content.

By default, Windows services run under the NT AUTHORITY\SYSTEM user account. Depending on the purpose of a PowerShell script, its operations may require additional permissions. In this case, either change the NXLog service account (see Running under a custom account on Windows) or add permissions as required to the SYSTEM account.

Generating logs with PowerShell

For generating logs, a PowerShell script can be used with the im_exec module. For another example, see Collecting audit logs via the SharePoint API.

Example 1. Using PowerShell to generate logs

This configuration uses the im_exec module to execute powershell.exe with the specified arguments, including the path to the script. The script creates an event and writes it to standard output in JSON format. The xm_json parse_json() procedure is used to parse the JSON so all the fields are available in the event record.

The script shows header examples for running the script under a different architecture than the NXLog agent. Also, a simple file-based position cache is included to demonstrate how a script can resume from the previous position when the agent or module instance is stopped and started again.

Because the end value of one poll and the start value of the next poll are equal, an actual source read should not include exact matches for both start and end values (to prevent reading duplicate events). For example, either the start value should be excluded ($start < $event ≤ $end) or the end value ($start ≤ $event < $end).

This example requires PowerShell 3 or later to transport structured data in JSON format. If structured data is required with an earlier version of PowerShell, CSV format could be used instead; see the next example.
nxlog.conf
<Extension _json>
    Module  xm_json
</Extension>

envvar systemroot
<Input powershell>
    Module  im_exec
    Command "%systemroot%\System32\WindowsPowerShell\v1.0\powershell.exe"
    # Use "-Version" to select a specific PowerShell version.
    #Arg     "-Version"
    #Arg     "3"
    # Bypass the system execution policy for this session only.
    Arg     "-ExecutionPolicy"
    Arg     "Bypass"
    # Skip loading the local PowerShell profile.
    Arg     "-NoProfile"
    # This specifies the path to the PowerShell script.
    Arg     "-File"
    Arg     "C:\ps_input.ps1"
    # Any additional arguments are passed to the PowerShell script.
    Arg     "arg1"
    <Exec>
        # Parse JSON
        parse_json();

        # Convert $EventTime field to datetime
        $EventTime = parsedate($EventTime);
    </Exec>
</Input>
ps_input.ps1
#Requires -Version 3

# Use this if you need 64-bit PowerShell (has no effect on 32-bit systems).
#if ($env:PROCESSOR_ARCHITEW6432 -eq "AMD64") {
#    Write-Debug "Running 64-bit PowerShell."
#    &"$env:SYSTEMROOT\SysNative\WindowsPowerShell\v1.0\powershell.exe" `
#    -NonInteractive -NoProfile -ExecutionPolicy Bypass `
#    -File "$($myInvocation.InvocationName)" $args
#    exit $LASTEXITCODE
#}

# Use this if you need 32-bit PowerShell.
#if ($env:PROCESSOR_ARCHITECTURE -ne "x86") {
#    Write-Debug "Running 32-bit PowerShell."
#    &"$env:SYSTEMROOT\SysWOW64\WindowsPowerShell\v1.0\powershell.exe" `
#    -NonInteractive -NoProfile -ExecutionPolicy Bypass `
#    -File "$($myInvocation.InvocationName)" $args
#    exit $LASTEXITCODE
#}

# The position is saved in this file for resuming.
$CacheFile = 'C:\nxlog_position_cache.txt'

# The source is queried at this interval in seconds.
$PollInterval = 1
################################################################################

# Get position from the cache file (on the first run, create the file)
# This example uses a timestamp position, but some sources may use an id, etc.
function Get-Position {
    param( $file )
    if (Test-Path $file) {
        # Read timestamp from file
        $time = (Get-Date (Get-Content $file -First 1))
        # Convert timestamp to UTC
        $time = $time.ToUniversalTime()
        # Round down to nearest second
        $time = $time.AddTicks(-($time.Ticks % 10000000))
    }
    else {
        # Get current time in UTC
        $time = [System.DateTime]::UtcNow
        # Round down to nearest second
        $time = $time.AddTicks(-($time.Ticks % 10000000))
        # Create position cache file with specified time
        Save-Position $file $time
    }
    return $time
}

# Save position to cache file
function Save-Position {
    param( $file, $time )
    Out-File -FilePath $file -InputObject $time.ToString('o')
}

# Main
Try {
    $start = Get-Position $CacheFile
    $count = 0
    $cpu = Get-WmiObject Win32_Processor | Select -First 1 -Expand datawidth
    $os = [int]::Parse(((gwmi Win32_OperatingSystem).OSArchitecture -Split "-")[0])
    $process = [IntPtr]::Size * 8

    # Repeatedly read from the source
    while($true) {
        $count += 1

        # Get current time in UTC and round down to the nearest second
        $now = [System.DateTime]::UtcNow
        $end = $now.AddTicks(-($now.Ticks % 10000000))

        # Set event fields
        $event = @{
            Arguments = [system.String]::Join(", ", $args);
            StartTime = $start.ToString('o');
            EndTime = $end.ToString('o');
            CPUBitness = $cpu;
            OSBitness = $os;
            ProcessBitness = $process;
            Message = "Test event $count for this run";
        }

        # Return event as JSON
        $event | ConvertTo-Json -Compress | Write-Output

        # Save position to cache file and sleep until the next iteration
        Save-Position $CacheFile $end
        Start-Sleep -Seconds $PollInterval
        $start = $end
    }
}
Catch {
    Write-Error "An unhandled exception occurred!"
    exit 1
}

PowerShell 2 does not support JSON. Instead, events can be formatted as CSV and parsed with an xm_csv module instance.

Example 2. Using PowerShell 2 as input

In this example, the PowerShell script generates output strings in CSV format. The xm_csv parse_csv() procedure is used to parse the CSV strings into fields in the event record. Note that the fields must be provided, sorted by name, in the xm_csv Fields directive (and corresponding types should be provided via the FieldTypes directive).

For best results with structured data, use JSON with PowerShell 3 or later (see the previous example).
nxlog.conf
<Extension csv_parser>
    Module      xm_csv
    Fields      Arguments, EventTime, Message
    FieldTypes  string, datetime, string
</Extension>

envvar systemroot
<Input powershell>
    Module  im_exec
    Command "%systemroot%\System32\WindowsPowerShell\v1.0\powershell.exe"
    Arg     "-Version"
    Arg     "2"
    Arg     "-ExecutionPolicy"
    Arg     "Bypass"
    Arg     "-NoProfile"
    Arg     "-File"
    Arg     "C:\ps2_input.ps1"
    Exec    csv_parser->parse_csv();
</Input>
ps2_input.ps1
#Requires -Version 2

$count = 0

while($true) {
    $count += 1
    $now = [System.DateTime]::UtcNow

    # Set event fields
    $event = @{
        Arguments = [system.String]::Join(", ", $args);
        EventTime = $now.ToString('o');
        Message = "event$count";
    }

    # Return event as CSV
    $row = New-Object PSObject
    $event.GetEnumerator() | Sort-Object -Property Name | ForEach-Object {
        $row | Add-Member -MemberType NoteProperty -Name $_.Name -Value $_.Value
    }
    $row | ConvertTo-Csv -NoTypeInformation | Select-Object -Skip 1 | Write-Output

    Start-Sleep -Seconds 5
}

Forwarding logs with PowerShell

For forwarding logs, a PowerShell script can be used with the om_exec module.

Example 3. Using PowerShell to forward logs

This configuration uses om_exec to execute powershell.exe with the specified arguments, including the path to the script. The script reads events on standard input.

This configuration requires PowerShell 3 or later for its JSON support and to correctly read lines from standard input.
See the Using PowerShell to generate logs example above for more details about powershell.exe arguments and PowerShell code for explicitly specifying a 32-bit or 64-bit environment.
nxlog.conf
<Extension _json>
    Module  xm_json
</Extension>

envvar systemroot
<Output powershell>
    Module  om_exec
    Command "%systemroot%\System32\WindowsPowerShell\v1.0\powershell.exe"
    Arg     "-ExecutionPolicy"
    Arg     "Bypass"
    Arg     "-NoProfile"
    Arg     "-File"
    Arg     "C:\ps_output.ps1"
    Exec    to_json();
</Output>
ps_output.ps1
#Requires -Version 3

while($line = [Console]::In.ReadLine()) {
    # Convert from JSON
    $record = $line | ConvertFrom-Json

    # Write out to file
    $record | Out-File -FilePath 'C:\out.log' -Append
}

Generating configuration with PowerShell

A PowerShell script can be used with the include_stdout directive to generate dynamic NXLog configuration. NXLog will execute the script during parsing of the configuration file.

Because include_stdout does not support arguments, it is simplest to use a batch/PowerShell polyglot script for this purpose. For another example, see Automatic retrieval of IIS site log locations.

The Command Prompt may print '@' is not recognized if a Unicode byte order mark (BOM) is included in the batch file. To fix this, use Notepad and select the ANSI encoding when saving the file.
Example 4. Using PowerShell and include_stdout

This configuration uses PowerShell code to generate the File directive for the Input instance.

nxlog.conf
<Input in>
    Module          im_file
    include_stdout  C:\include.cmd
</Input>
include.cmd
@( Set "_= (
REM " ) <#
)
@Echo Off
SetLocal EnableExtensions DisableDelayedExpansion
set powershell=powershell.exe

REM Use this if you need 64-bit PowerShell (has no effect on 32-bit systems).
REM if defined PROCESSOR_ARCHITEW6432 (
REM set powershell=%SystemRoot%\SysNative\WindowsPowerShell\v1.0\powershell.exe
REM )

REM Use this if you need 32-bit PowerShell.
REM if NOT %PROCESSOR_ARCHITECTURE% == x86 (
REM set powershell=%SystemRoot%\SysWOW64\WindowsPowerShell\v1.0\powershell.exe
REM )

%powershell% -ExecutionPolicy Bypass -NoProfile ^
-Command "iex ((gc '%~f0') -join [char]10)"
EndLocal & Exit /B %ErrorLevel%
#>

# PowerShell code starts here.

# To make NXLog return an error, write to standard error and exit 1
if ($false) {
    [Console]::Error.WriteLine("This is an error")
    exit 1
}
else {
# Anything written to standard output is used as configuration content
    Write-Output "File 'C:\in.log'"
}
Disclaimer

While we endeavor to keep the information in this topic up to date and correct, NXLog makes no representations or warranties of any kind, express or implied about the completeness, accuracy, reliability, suitability, or availability of the content represented here.

Last revision: 28 March 2019