How to write icinga2 plugins for Windows (Part 2: Icinga Powershell Framework)

This is the second part of a two part series.

Powershell works differently than e.g. Bash with structured objects. Objects can contain properties, methods and events. If you do not know this structure, you will encounter error messages very quickly.

Of course, this also has advantages, e.g. it makes filtering much easier.

Icinga PowerShell Framework

Icinga delivers its own Powershell framework to its own Powershell plugins since the end of 2020.
The framework makes it a little easier for developers to write their own plugins, but also restricts them somewhat.

The output of the error messages is only slightly customizable.
However, performance data, which can be used as metrics for Granfana and Graphite, are automatically included.

Install and configure framework

The installation can either be automatically clicked via Icinga for Windows or is done via the following powershell command:

Install-IcingaComponent -Name 'plugins';

The plugins are usually located in the following path:

C:\Program Files\WindowsPowerShell\Modules\

In principle, it is possible to create your own plugin in the existing plugin folder. However, this is expressly warned, because the folder may be "cleaned" with the next update.

We create our own configuration folder using the Powershell command:

Use-Icinga;
New-IcingaForWindowsComponent -Name 'checkprocsbynameoruser' -ComponentType 'plugins';

The output reveals the module path:

[Notice]: New component "checkprocsbynameoruser" has been created as module "icinga-powershell-checkprocsbynameoruser" at location "C:\Program Files\WindowsPowerShell\Modules\icinga-powershell-checkprocsbynameoruser"

Go to the module path, edit the file icinga-powershell-checkprocsbynameoruser.psd1 and add the line starting with 'Invoke-IcingaCheck...:

FunctionsToExport     = @(
  'Import-IcingaPowerShellComponentCheckprocsbynameoruser'
  'Invoke-IcingaCheckProcsbyNameorUser'
C:\Program Files\WindowsPowerShell\Modules\icinga-powershell-checkprocsbynameoruser\icinga-powershell-checkprocsbynameoruser.psd1

Invoke-IcingaCheckProcsbyNameorUser is the name of the plugin we will create in the next step.

Creating the plugin

A naming convention applies here. All plugins must start with the prefix "Invoke-IcingaCheck". The name of the plugin file must be the same.

Our plugin file is named accordingly:
Invoke-IcingaCheckProcsbyNameorUser.psm1

You create the file in the "Plugins" subdirectory which was automatically generated with the module creation.

Your plugin always starts with the parameterization, the actual check, the check evaluation and the return.

But since the structure is almost always identical, you can take the template from the official documentation and adapt it.

If you should introduce your own parameters, use unique ones. Otherwise there may be overlaps with already used ones.

According to part 1, we introduce the following three parameters:

# ProcessName
$UserMonProcess = $null,
# Desired User for Process
$UserMonDesired = '*',
# Maximum allowed process count (default: 1)
[int]$MaxProcsAllowed = 1

Each of these variables can be overwritten by calling the plugin.
Since we want to make the username only optional, we set in first to everything, thus "*" (Kleene star).

Now we need the check:
We create two:

  1. check the number count of process:
$Check  = New-IcingaCheck `
  -Name 'Count desired process' `
  -Value (
 $countProcs = (Get-Process -Name $UserMonProcess -IncludeUserName -ErrorAction SilentlyContinue| Measure-Object | Select -Expand count)
  );

2. check the number of processes started by the correct user:

$Check2 = New-IcingaCheck `
  -Name 'Count non desired user' `
  -Value (
  $userProcs = Get-Process -Name $UserMonProcess -IncludeUserName -ErrorAction SilentlyContinue| Where-Object UserName -Notlike *$UserMonDesired* |  Measure-Object | Select -Expand count
  );

Both checks return a number that we can evaluate. The functions used are described here.:

If the process does not run at all, we return Critical:

$Check.CritIfLowerThan(1)  | Out-Null;

If too many processes are running, critical should be returned:

$Check.CritIfGreaterThan($MaxProcsAllowed) | Out-Null;

If too few processes have been started than desired, a warning should be issued:

$Check.WarnIfLowerThan($MaxProcsAllowed)  | Out-Null;

If an unauthorized user has started a process, critical should be returned (here check2 is applied):

$Check2.CritIfGreaterThan(0) | Out-Null;

The checks are initiated via the checkpackage function:

  $CheckPackage = New-IcingaCheckPackage `
   -Name 'ProcessandUserCheck Package' `
   -Checks @(
   $Check,
   $Check2
   ) `
   -OperatorAnd `
   Verbose $Verbosity;

We use OperatorAnd because we want all checks to return OK.

You can see the full code on Github.

Test the plugin

To test the plugin, it must be imported as a Powershell module:

# Indirectly via the module path:
Import-Module C:\Program Files\WindowsPowerShell\Modules\icinga-powershell-checkprocsbynameoruser\icinga-powershell-checkprocsbynameoruser

# Directly via the plugin path:
Import-Module .\Invoke-IcingaCheckProcsbyNameorUser.psm1

After the successful import you should be able to test the plugin directly:

Invoke-IcingaCheckProcsbyNameorUser

The plugin should have the correct output for all edge cases.

Invoke-IcingaCheckProcsbyNameorUser -UserMonProcess Signal -UserMonDesired okko -MaxProcsAllowed 5
[OK] ProcessandUserCheck Package
| 'default::ifw_procsbynameoruser::countdesiredprocess'=5;5:;~:5 'default::ifw_procsbynameoruser::countnondesireduser'=0;;~:0
0
Invoke-IcingaCheckProcsbyNameorUser -UserMonProcess Signal -UserMonDesired herbert -MaxProcsAllowed 5
[CRITICAL] ProcessandUserCheck Package [CRITICAL] Count non desired user (5)
\_ [CRITICAL] Count non desired user: 5 is greater than threshold 0
| 'default::ifw_procsbynameoruser::countdesiredprocess'=5;5:;~:5 'default::ifw_procsbynameoruser::countnondesireduser'=5;;~:0
2
OK
 Invoke-IcingaCheckProcsbyNameorUser -UserMonProcess THISdoesNOTexist -UserMonDesired herbert -MaxProcsAllowed 5
[CRITICAL] ProcessandUserCheck Package [CRITICAL] Count desired process (0)
\_ [CRITICAL] Count desired process: 0 is lower equal than threshold 1
| 'default::ifw_procsbynameoruser::countdesiredprocess'=0;5:;~:5 'default::ifw_procsbynameoruser::countnondesireduser'=0;;~:0
2
User does not exist
Invoke-IcingaCheckProcsbyNameorUser -UserMonProcess Signal -MaxProcsAllowed 2
[CRITICAL] ProcessandUserCheck Package [CRITICAL] Count desired process (5)
\_ [CRITICAL] Count desired process: 5 is greater than threshold 2
| 'default::ifw_procsbynameoruser::countdesiredprocess'=5;2:;~:2 'default::ifw_procsbynameoruser::countnondesireduser'=0;;~:0
2
Too many processes