# Windows PowerShell Checks with Icinga2

**URL:** <https://community.icinga.com/t/windows-powershell-checks-with-icinga2/712>\
**Category:** Tutorials and Guides\
**Tags:** powershell\
**Created:** [March 25, 2019, 2:59pm UTC](https://community.icinga.com/t/windows-powershell-checks-with-icinga2/712 "2019-03-25T14:59:30Z")\
**Posts on this page:** 18\
**Page:** 1

<div class="post-metadata">

**Author:** ![dnsmichi](https://community.icinga.com/user_avatar/community.icinga.com/dnsmichi/32/7721_2.png) [@dnsmichi](https://community.icinga.com/u/dnsmichi)\
**Post date:** [March 25, 2019, 2:59pm UTC](https://community.icinga.com/t/windows-powershell-checks-with-icinga2/712/1 "2019-03-25T14:59:30Z")

</div>

> Author: @GordonCole
> 
> Revision: v0.1
> 
> Tested with:
> 
> - Icinga 2 v2.6.3-1
> - Icinga Web 2 v2.4.1
> - Windows Server 2012 R2

# Introduction

A vanilla Windows Icinga2 installation provides access to a number of standard server health and performance metrics. For example hard disk space, CPU, free RAM, or accessing the value of a Windows Performance Counter. Each of these is metrics is measured using a “check”. These are programs called by the main Icinga2 service.

Users may write their own “checks”, as long as they return a result in the expected format (status, performance data, text).

PowerShell has established itself as a powerful way of automating tasks and accessing information on a Windows machine. We can use PowerShell to access server metrics and return data to Icinga2 by writing a suitable PowerShell script.

# Configuration

## Create a Check Command

Here we define a **CheckCommand** so that Icinga2 knows the path of the executable to call, in this case the powershell.exe interpreter (a PowerShell session).

This should be defined in **commands.conf**

```auto
object CheckCommand "powershell_check" {
  import "plugin-check-command"
  command = ["C:\\Windows\\system32\\WindowsPowerShell\\v1.0\\powershell.exe"]
  arguments = {
    "-command" = {
    value = "$ps_command$"
    order = -1
    }
    "-warn" = {
    value = "$ps_warn$"
    }
    "-crit" = {
    value = "$ps_crit$"
    }
    ";exit" = {
    value = "$$LastExitCode"
    }
  }
}

```

This will run the 32-bit version of PowerShell. If you want to use the 64-bit version use the following command instead:

```auto
  command = ["C:\\Windows\\sysnative\\WindowsPowerShell\\v1.0\\powershell.exe"]

```

Let’s consider the arguments:

- **-command** - this contains a variable which will contain the path to our PowerShell script (our check)

- **-warn** - this contains a variable which may be set (elsewhere) to contain a **warning** threshold value (optional - your script needs to be written to be able to accept arguments)

- **-crit** - this contains a variable which may be set (elsewhere) to contain a **critical** threshold value (optional - your script needs to be written to be able to accept arguments)

- **;exit** - we always pass this argument the value “$LastExitCode”. This is not an Icinga2 variable. This argument tells the powershell.exe session to take the exit code generated by your PowerShell script, and use this for the exit code when the session exits. This exit code is important as the plug-in’s service “ **status** ” is taken from the exit code (0=OK, 1=Warning, 2=Critical, 3=Unknown). Note also the “;” in the above code. If you omit this, you powershell.exe will aways exit with code “0” and your check wont have the correct status.

- “\” is used to escape the “\” so we see “\\” in the path name

- “$” is used to escape the “$” so we see “$$” in the string “$LastExitCode”

If you find the above confusing, just remember that Icinga2 needs to construct a command that you could yourself enter at the command prompt in order to run a PowerShell script. For example:

```auto
C:\Windows\system32\WindowsPowerShell\v1.0\powershell.exe -command "&'C:\Program Files\ICINGA2\sbin\check_reboot.ps1' ;exit $LastExitCode

```

This is very useful as you can manually test your scripts (“plug ins”) from the command line without involving Icinga2. You cannot, however, see the exit code.

The above puts in place the underlying ability to call PowerShell from Icinga2.

## Define a Service

We can define an Icinga2 “service” that references a PowerShell scripts or “plug-ins”. It relies on the **check command** we defined in the previous step.

For example:

```auto
apply Service "reboot_status_check" {
  import "generic-service"
  display_name = "Reboot Check"
  check_command = "powershell_check"
  vars.ps_command = "& 'C:\\Program Files\\ICINGA2\\sbin\\check_reboot.ps1'"
  command_endpoint = host.address
  assign where host.vars.os == "windows"
}

```

In this case we are using the variable **vars.os** in the **host** object definition to apply this **service** to all our “ **windows** ” hosts.

## Write PowerShell script (Plug In)

We now require a suitable PowerShell script to act as our “plug in”. Note that monitoring plug-ins should adhere to guidelines regarding what you can pass them, and what they should return. This example may not be fully compliant in this regard.

You should consult [Monitoring Plugins Development Guidelines](https://www.monitoring-plugins.org/doc/guidelines.html) for detailed information on writing plugins.

```auto
	# Checks if RebootRequired key exists, if so returns a warning.
	# This key is deleted upon a successful reboot.
	# This may indicate that Windows patching has taken place, without a reboot.
		 
	# Checks if RebootRequired reg path exists
	$value = test-path -path "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate\Auto Update\RebootRequired"
	 
	# If path does not exist, return OK status
	if ($value -match "False") {
	echo "OK - No reboot required"
	$returnCode=0
	}
	 
	# Else return WARNING status
	else {
	echo "WARNING - Reboot required"
	$returnCode=1
	}
	 
	exit ($returnCode)

```

To note:

- return code is passed as the “exit” code. This in turn is passed back to Icinga2. This is the “status” of the service check
- This is a simple example and does not include any error handling, which you would normally consider
- We need to save this script on our Windows server in the correct path, as shown in our **service** definition. This is best placed in the same location as the other Icinga2 Windows checks:

`C:\Program Files\ICINGA2\sbin\`

The exit codes from the plug-in relate to Icinga2 service status as follows:

- 0 = OK
- 1 = Warning
- 2 = Critical
- 3 = Unknown

# Permissions

It should be noted that the ability to run PowerShell may be restricted by group policy.

You should consider the user that will be used to run these scripts and what rights they will need. For example, an Icinga2 service run as “Local System” will therefore run the script as the user “local system”. This user can check the registry but likely wont have rights over an SQL database, for example. Care must be taken when assigning rights.

# Conclusion

Once you can harness PowerShell, your abilities to check Windows server health and vital statistics is limited only by your scripting knowledge. You may, for example:

- Check registry keys (some applications store their status, for example “primary” or “stand-by” in the registry)
- Check files are present and updated in particular folders (useful in file processing systems)
- Parse application log files for error codes and generate Icinga2 warnings

You may be able to use a combination of Icinga2 and PowerShell and remove the requirement for other agents such as NSClient++.

# FAQ

- Where do I get more information on installing and using PowerShell?  
[PowerShell Documentation - PowerShell | Microsoft Learn](https://docs.microsoft.com/en-us/powershell/)

- Where can I see some more examples of PowerShell plug-ins?  
[http://it-wiki.eu/monitoring/icinga2/windows](http://it-wiki.eu/monitoring/icinga2/windows)

---

<div class="post-metadata">

**Author:** ![csmall](https://community.icinga.com/user_avatar/community.icinga.com/csmall/32/617_2.png) [@csmall](https://community.icinga.com/u/csmall)\
**Post date:** [April 25, 2019, 7:03pm UTC](https://community.icinga.com/t/windows-powershell-checks-with-icinga2/712/2 "2019-04-25T19:03:33Z")

</div>

> [@dnsmichi](#):
>
> C:\Windows\system32\WindowsPowerShell\v1.0\powershell.exe

I added the command via director and I get this error when trying to use the command check.

execvpe(C:\Windows\system32\WindowsPowerShell\v1.0\powershell.exe) failed: No such file or directory

I am using the Icinga2 Windows Agent.

What could be the problem? The command/path works fine locally on the server.

---

<div class="post-metadata">

**Author:** ![tatoosh](https://community.icinga.com/letter_avatar_proxy/v4/letter/t/e79b87/32.png) [@tatoosh](https://community.icinga.com/u/tatoosh)\
**Post date:** [April 28, 2019, 10:14pm UTC](https://community.icinga.com/t/windows-powershell-checks-with-icinga2/712/3 "2019-04-28T22:14:38Z")

</div>

Hi Colin,

i solved it by extending the command-plugins-windows.conf (on Windows Client configuration!!!) by the given commands.conf code above.  
Also the endpoint has to point to the client i learned, so my Service looks:

```
apply Service "reboot_status_check" {
  import "generic-service"
  display_name = "Reboot Check"
  check_command = "powershell_check"
  vars.ps_command = "& 'C:\\Program Files\\ICINGA2\\sbin\\check_reboot.ps1'"
  command_endpoint = host.vars.client_endpoint
  assign where host.vars.os == "Windows" && host.vars.client_endpoint
}
```

---

<div class="post-metadata">

**Author:** ![2g4y1](https://community.icinga.com/letter_avatar_proxy/v4/letter/2/4bbf92/32.png) [@2g4y1](https://community.icinga.com/u/2g4y1)\
**Post date:** [May 2, 2019, 1:26pm UTC](https://community.icinga.com/t/windows-powershell-checks-with-icinga2/712/4 "2019-05-02T13:26:27Z")

</div>

Hi @dnsmichi,

thanks four your howto.  
I run into the issue where my Icinga2web says “Check command ‘powershell\_check’ does not exist.”  
I created the CheckCommand in the commands.conf on the Server (and the client.)  
Created the service through the icinga director and set them to run on the agent.

My envirement is a debian server with icinga2 r2.10.4-1  
I tried to run a script on a Server 2019

thanks for your efforts  
Manuel

---

<div class="post-metadata">

**Author:** ![rsx](https://community.icinga.com/user_avatar/community.icinga.com/rsx/32/2094_2.png) [@rsx](https://community.icinga.com/u/rsx)\
**Post date:** [May 3, 2019, 5:04am UTC](https://community.icinga.com/t/windows-powershell-checks-with-icinga2/712/5 "2019-05-03T05:04:09Z")

</div>

After adding commands in any conf file you need to run **Kickstart Wizard** to inform the director about the changes.

---

<div class="post-metadata">

**Author:** ![voddIt](https://community.icinga.com/letter_avatar_proxy/v4/letter/v/51bf81/32.png) [@voddIt](https://community.icinga.com/u/voddIt)\
**Post date:** [May 3, 2019, 7:12am UTC](https://community.icinga.com/t/windows-powershell-checks-with-icinga2/712/6 "2019-05-03T07:12:37Z")

</div>

hmmm… just keep writing ps-scripts and call them via nrpe…

---

<div class="post-metadata">

**Author:** ![mkuerschner](https://community.icinga.com/letter_avatar_proxy/v4/letter/m/858c86/32.png) [@mkuerschner](https://community.icinga.com/u/mkuerschner)\
**Post date:** [June 18, 2019, 7:43am UTC](https://community.icinga.com/t/windows-powershell-checks-with-icinga2/712/7 "2019-06-18T07:43:32Z")

</div>

Hi Manuel,

if you created the objects in director, define the CheckCommand in the zone director-global. this publish the configuration to the endpoints. So no additional work is necessary on endpoints.

regards,  
Michael

---

<div class="post-metadata">

**Author:** ![bitboy](https://community.icinga.com/user_avatar/community.icinga.com/bitboy/32/1699_2.png) [@bitboy](https://community.icinga.com/u/bitboy)\
**Post date:** [July 9, 2019, 9:02am UTC](https://community.icinga.com/t/windows-powershell-checks-with-icinga2/712/8 "2019-07-09T09:02:10Z")

</div>

Is it possible to give additional arguments to the script?

---

<div class="post-metadata">

**Author:** ![Coffe](https://community.icinga.com/user_avatar/community.icinga.com/coffe/32/1173_2.png) [@Coffe](https://community.icinga.com/u/Coffe)\
**Post date:** [August 28, 2019, 11:59am UTC](https://community.icinga.com/t/windows-powershell-checks-with-icinga2/712/9 "2019-08-28T11:59:27Z")

</div>

object CheckCommand “check\_ms\_iis\_application\_pool” {

```
import "plugin-check-command"

command = ["C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe"]

arguments = {

    "-command" = {

    value = "& 'C:\\scripts\\check_ms_iis_application_pool.ps1' "

    order = -1

    }

    "-A" = {

    value = "$windows_appPool$"

    }

}

vars.windows_appPool = "$windows_appPool$"

```

}

Would render:

‘C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe’ ‘-command’ ‘& ‘’‘C:\scripts\check\_ms\_iis\_application\_pool.ps1’’’ ’  
and vars.windows\_appPool=BANANA

But

object CheckCommand “check\_ms\_iis\_application\_pool” {

```
import "plugin-check-command"

command = ["C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe"]

arguments = {

    "-command" = {

    value = "& 'C:\\scripts\\check_ms_iis_application_pool.ps1' "

    order = -1

    }

    "-A" = {

    value = "BANANA"

    }

}

vars.windows_appPool = "$windows_appPool$"

```

}

Vould render:

‘C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe’ ‘-command’ ‘& ‘’‘C:\scripts\check\_ms\_iis\_application\_pool.ps1’’’ ’ ‘-A’ ‘BANANA’

---

<div class="post-metadata">

**Author:** ![julianze](https://community.icinga.com/letter_avatar_proxy/v4/letter/j/e47c2d/32.png) [@julianze](https://community.icinga.com/u/julianze)\
**Post date:** [December 9, 2019, 4:09pm UTC](https://community.icinga.com/t/windows-powershell-checks-with-icinga2/712/10 "2019-12-09T16:09:26Z")

</div>

Hi,  
i have the problem, that the rendered Commands are not executeable, because the powershell.exe is qouted.

It´s the same as @Coffe described.

Are there any solutions?

---

<div class="post-metadata">

**Author:** ![balazs](https://community.icinga.com/letter_avatar_proxy/v4/letter/b/cc9497/32.png) [@balazs](https://community.icinga.com/u/balazs)\
**Post date:** [November 18, 2022, 10:37am UTC](https://community.icinga.com/t/windows-powershell-checks-with-icinga2/712/11 "2022-11-18T10:37:53Z")

</div>

Hi,

I tried this out and created a check command with Icinga Director.  
It’s all fine, expect one thing. The exit code is not interpreted by Icinga. The result returned by the plugin is critical, but it’s show as Ok. I set the semicolon before the exit as suggested.

The command preview from the director is:

```auto
object CheckCommand "check-win-firewall-public-profile-enabled" {
    import "plugin-check-command"
    command = [
        "C:\\Windows\\system32\\WindowsPowerShell\\v1.0\\powershell.exe"
    ]
    arguments += {
        "-Profile" = "Public"
        "-command" = {
            order = -1
            value = "Invoke-IcingaCheckFirewall"
        }
        ";exit" = "$$LastExitCode"
    }
}

```

The executed command:  
‘C:\Windows\system32\WindowsPowerShell\v1.0\powershell.exe’ ‘-command’ ‘Invoke-IcingaCheckFirewall’ ‘-Profile’ ‘Public’ ‘;exit’ ‘$LastExitCode’

and the result on the dashboard:  
 ![image](https://community.icinga.com/uploads/default/original/2X/0/0272dd377c01c047df91deb9afeb9bfbeaa1ea3c.png)

Any idea what’s wrong?

---

<div class="post-metadata">

**Author:** ![Julia](https://community.icinga.com/letter_avatar_proxy/v4/letter/j/2bfe46/32.png) [@Julia](https://community.icinga.com/u/Julia)\
**Post date:** [December 22, 2022, 9:26am UTC](https://community.icinga.com/t/windows-powershell-checks-with-icinga2/712/12 "2022-12-22T09:26:33Z")

</div>

Hi

Not sure if you got this solved but looks like you had the wrong format at the end, should be something like this instead:

```auto
";exit" = {
    value = "$$LastExitCode"
    }

```

Not like the one you did:

```auto
";exit" = "$$LastExitCode"

```

---

<div class="post-metadata">

**Author:** ![balazs](https://community.icinga.com/letter_avatar_proxy/v4/letter/b/cc9497/32.png) [@balazs](https://community.icinga.com/u/balazs)\
**Post date:** [January 9, 2023, 10:25am UTC](https://community.icinga.com/t/windows-powershell-checks-with-icinga2/712/13 "2023-01-09T10:25:54Z")

</div>

Yes, in the mean time I realized it 🙂  
Thank you!

---

<div class="post-metadata">

**Author:** ![log1c](https://community.icinga.com/user_avatar/community.icinga.com/log1c/32/2071_2.png) [@log1c](https://community.icinga.com/u/log1c)\
**Post date:** [January 9, 2023, 10:38am UTC](https://community.icinga.com/t/windows-powershell-checks-with-icinga2/712/14 "2023-01-09T10:38:43Z")

</div>

In case someone wants the check to not be in an OK state when the script to be called by the PowerShell is not existing, I came up with this monstrosity 😃

```auto
object CheckCommand "PowerShell Custom Script" {
    import "plugin-check-command"
    command = [
        "C:\\Windows\\system32\\WindowsPowerShell\\v1.0\\powershell.exe",
        "-executionpolicy",
        "bypass",
        "-noprofile"
    ]
    timeout = 3m
    arguments += {
        "-command" = {
            description = "This will contain the path to our PowerShell script"
            order = 0
            required = true
            value = "if(!(Test-Path \"$ps_script_path$\")){ Write-Host \"Script $ps_script_path$ not found.\" ; exit 1 } else { & $ps_script_path$"
        }
        "};exit" = {
            order = 99
            required = true
            value = "$$LastExitCode"
        }
    }
}

```

This will first check if the script, given via the variable `$ps_schript_path$`, exists. If not, it will exit with exit code 1 (WARNING), which is then reflected by the check.

The `order = 99` on the exit “argument” is to be able to create other commands that import this one and make arguments for those scripts possible.

Example:

```auto
object CheckCommand "mdatp-status_win" {
    import "plugin-check-command"
    import "PowerShell Custom Script"

    arguments += {
        "-crit" = {
            description = "Critical threshold in hours for definition age"
            order = 97
            required = true
            value = "$mdatp_win_definition_crit$"
        }
        "-warn" = {
            description = "Warning threshold in hours for definition age"
            order = 98
            required = true
            value = "$mdatp_win_definition_warn$"
        }
    }
}

```

---

<div class="post-metadata">

**Author:** ![headlex](https://community.icinga.com/letter_avatar_proxy/v4/letter/h/ce73a5/32.png) [@headlex](https://community.icinga.com/u/headlex)\
**Post date:** [December 12, 2024, 9:09am UTC](https://community.icinga.com/t/windows-powershell-checks-with-icinga2/712/15 "2024-12-12T09:09:22Z")

</div>

Sorry for bumping this…

I am fairly new to Icinga2 and trying to dig into the system.

Is there a possibility to make the use of the “ **command** ” argument obsolete for the special checks?

for example i just can pass the **crit** and **warn** arguments to the **mdatp-status\_win**

---

<div class="post-metadata">

**Author:** ![log1c](https://community.icinga.com/user_avatar/community.icinga.com/log1c/32/2071_2.png) [@log1c](https://community.icinga.com/u/log1c)\
**Post date:** [December 12, 2024, 10:32am UTC](https://community.icinga.com/t/windows-powershell-checks-with-icinga2/712/16 "2024-12-12T10:32:45Z")

</div>

What is your intention?  
Omitting the command argument does not make sense, because this holds the path to the script that should be executed

---

<div class="post-metadata">

**Author:** ![headlex](https://community.icinga.com/letter_avatar_proxy/v4/letter/h/ce73a5/32.png) [@headlex](https://community.icinga.com/u/headlex)\
**Post date:** [December 12, 2024, 6:02pm UTC](https://community.icinga.com/t/windows-powershell-checks-with-icinga2/712/17 "2024-12-12T18:02:26Z")

</div>

for example your **“mdatp-status\_win”** would always call the same script across all hosts correct?  
is there a way to hard code that path to the script in the CheckCommand object?

---

<div class="post-metadata">

**Author:** ![log1c](https://community.icinga.com/user_avatar/community.icinga.com/log1c/32/2071_2.png) [@log1c](https://community.icinga.com/u/log1c)\
**Post date:** [December 13, 2024, 6:41am UTC](https://community.icinga.com/t/windows-powershell-checks-with-icinga2/712/18 "2024-12-13T06:41:08Z")

</div>

Sure.  
As the `"PowerShell Custom Script"` check command is imported, you just have to set the `$ps_script_path$` variable.  
You can do that either in the check command itself or in the service template using the check command.

Or you create you own check command for mdatp without importing the “PowerShell Custom Script” and basically combine them into one and then hardcode the path there.
