This commit is contained in:
Christian Stein 2020-07-31 10:20:56 +02:00
commit 39df724157
8 changed files with 379 additions and 14 deletions

View file

@ -20,6 +20,7 @@ General Handling
There are some general guidelines, functions and toolsets available making the development in general easier. Here you can find a list of topics
* [Cim-Instance/Wmi-Object fetching](developerguide/50-Fetching-Cim_and_Wmi_Data.md)
* [Working with Performance Counters](developerguide/51-Working-with-Performance-Counters.md)
Example Usage
---
@ -30,4 +31,4 @@ To get started easier you can find detailed examples for developing modules/inte
* [Custom Plugins](developerguide/11-Custom-Plugins.md)
* [Custom API-Endpoints](developerguide/12-Custom-API-Endpoints.md)
* [Using Console Outputs](developerguide/20-Using-Console-Outputs.md)
* [Using EventLog Output](developerguide/20-Using-EventLog-Outputs.md)
* [Using EventLog Output](developerguide/21-Using-EventLog-Outputs.md)

View file

@ -15,12 +15,18 @@ Released closed milestones can be found on [GitHub](https://github.com/Icinga/ic
* [#80](https://github.com/Icinga/icinga-powershell-framework/issues/80) Adds wrapper function `Get-IcingaWindowsInformation` for WMI and CIM calls to properly handle config/permission errors
### Enhancements
* Adds new Cmdlet `Show-IcingaPerformanceCounterInstances` to display all available instances for Performance Counters
* [#76](https://github.com/Icinga/icinga-powershell-framework/issues/76) Adds support to test for required .NET Framework Version 4.6.0 or above before trying to install the Icinga Agent
### Bugfixes
* [#78](https://github.com/Icinga/icinga-powershell-framework/issues/78) Fix Icinga Agent package fetching for x86 architecture
* [#79](https://github.com/Icinga/icinga-powershell-framework/issues/79) Fix ConvertTo-Seconds to output valid numeric data with multiple digits
* [#81](https://github.com/Icinga/icinga-powershell-framework/issues/81), [#82](https://github.com/Icinga/icinga-powershell-framework/issues/82) Fix error on EventLog initialising in case `Icinga for Windows` application is not registered on new machines
* [#84](https://github.com/Icinga/icinga-powershell-framework/issues/84), Fix conversion of `ConvertTo-Seconds` and `ConvertTo-SecondsFromIcingaThresholds` while the input value is `$null`
* [#85](https://github.com/Icinga/icinga-powershell-framework/issues/85), Fix incorrect handling to empty service user password which was configured as empty `String` instead of `$null` `SecureString` object
## 1.1.2 (2020-07-01)

View file

@ -0,0 +1,253 @@
# Developer Guide: Working with Performance Counters
For every Windows system there are Performance Counters available which will deliver information and details about the state of the general health of certain components of Windows itself but also disks, drivers, software and network interfaces for example.
To get to know more about them in general, you can have a look on the [Microsoft Documentation for Performance Counters](https://docs.microsoft.com/en-us/windows/win32/perfctrs/performance-counters-portal).
## Structure of Performance Counters
In order to fetch Performance Counters data by using the Icinga PowerShell Framework, we have to first clarify on how to access counters. Every counter is split into two mandatory components:
* Category
* Counter
Each `Category` can provide a various amount of different `Counter` objects you can access to read data out. An example counter is
```powershell
'\Memory\available mbytes'
```
The notation is always a leading `\` followed by the `Category`, another `\` and the `Counter` itself.
In this example, `Memory` is the `Category` and `available mbytes` is the counter we can fetch data from. It contains available Megabytes for allocation to a process or for system use.
Now in addition to the previous mentioned structure, there is one exception when a counter contains multiple `Instances`. An `Instance` is used for example by processor, disk or network interfaces, on which multiple values are stored for different disks, processor cores or network interfaces.
To access a certain `Instance` you will have to extend the `Category` with brackets `()` and write the name of the `Instance` into it. You can use `*` to fetch data for all `Instances`.
```powershell
'\Processor(*)\% processor time'
```
Please keep in mind that not all counters will require instances.
## Fetching available Performance Counters Categories
The way the Icinga PowerShell Framework is designed allows the global usage of the counters with their english name. This means it doesn't matter which language your Windows system is running it. How ever, localised systems will often print localised names of the counters, making it hard to use them on systems running a different language.
To fetch a list of available counters on a specific system with their english names, there is a Cmdlet avaialble in the Framework itself:
```powershell
Show-IcingaPerformanceCounterCategories;
```
```text
System
Memory
Browser
Cache
Process
Thread
PhysicalDisk
LogicalDisk
...
```
## Fetching available Performance Counters from Categories
As we now know which `Categories` are available on our system, we can have a look on them which `Counter` are present. For this there is also a Cmdlet avaialble directly within the Framework:
```powershell
Show-IcingaPerformanceCounters 'Memory';
```
```text
\Memory\committed bytes
\Memory\pool nonpaged bytes
\Memory\page writes/sec
\Memory\transition faults/sec
...
```
Note that the Cmdlet will output the counters with the correct notation already which means you can simply copy & paste them into your code for usage. The same applies for counters with instances:
```powershell
Show-IcingaPerformanceCounters 'Processor';
```
```text
\Processor(*)\dpcs queued/sec
\Processor(*)\% c1 time
\Processor(*)\% idle time
\Processor(*)\c3 transitions/sec
...
```
## Fetching available Performance Counters Instances
Last but not least you can fetch all available `Instances` for a Performance Counter with a custom Cmdlet introduced with Icinga PowerShell Framework version 1.2.0:
```powershell
Show-IcingaPerformanceCounterInstances '\Processor(*)\% processor time';
```
```text
Name Value
---- -----
_Total \Processor(_Total)\% processor time
0 \Processor(0)\% processor time
1 \Processor(1)\% processor time
```
Not only will it print you the name of the `Instance` but also the full path to use for fetching the value of a specific Performance Counter.
## Access Performance Counters
By using `New-IcingaPerformanceCounterArray`, you can provide multiple Performance Counters and fetch them all at once. This is the recommended way, as some counters will require a certain `Sleep` interval to have valid data available. By using the array handling, all counters will be loaded and initialised at once, reducing the overall required sleep duration to an absolute minimum.
### Example 1: Fetch multiple Performance Counters
You can add multiple Performance Counter paths to the Cmdlet. All of them will be fetched at once;
```powershell
New-IcingaPerformanceCounterArray '\Processor(*)\% processor time', '\Memory\committed bytes', '\Memory\available mbytes';
```
```text
Name Value
---- -----
\Processor(*)\% processor time {\Processor(_Total)\% processor time, \Processor(0)\% processor time, \Processor(10)\...
\Memory\available mbytes {error, sample, type, value...}
\Memory\committed bytes {error, sample, type, value...}
```
You can access the values directly by using the specific Performance Counter as index:
```powershell
$Counter = New-IcingaPerformanceCounterArray '\Processor(*)\% processor time', '\Memory\committed bytes', '\Memory\available mbytes';
$Counter['\Processor(*)\% processor time'];
```
```text
Name Value
---- -----
\Processor(_Total)\% proces... {error, sample, type, value...}
\Processor(0)\% processor time {error, sample, type, value...}
\Processor(10)\% processor ... {error, sample, type, value...}
\Processor(20)\% processor ... {error, sample, type, value...}
...
```
Now you can access the values with their `Instance` path:
```powershell
$Counter['\Processor(*)\% processor time']['\Processor(_Total)\% processor time'];
```
```text
Name Value
---- -----
error
sample System.Diagnostics.CounterSample
type Timer100NsInverse
value 0
help % Processor Time is the percentage of elapsed time that the processor spends to execu...
```
And of course the value itself
```powershell
$Counter['\Processor(*)\% processor time']['\Processor(_Total)\% processor time'].value;
```
### Example 2: Fetch Single Instance Performance Counter
Of course you can also access a single `Instance` of a Performance Counter or using a Performance Counter not having any `Instances` at all:
```powershell
$Counter = New-IcingaPerformanceCounterArray '\Processor(_Total)\% processor time';
$Counter['\Processor(_Total)\% processor time'].value;
```
## Create a structured Performance Counter output
### The Problem: Messy output with multiple Instances
Plenty of `Counters` provide `Instances`, which are required to allow the specific assignment of values to an object. Such examples are Network Interface `Counters` for different interfaces or PhysicalDisk/LogicalDisk counters for each installed disk.
By simply fetching all informations from a counter, like `\LogicalDisk(*)\free megabytes`, we receive each disk value within our output. This is fine in general, is however problematic in the following scenario:
```powershell
$Counter = New-IcingaPerformanceCounterArray '\LogicalDisk(*)\free megabytes', '\LogicalDisk(*)\% idle time';
```
Of course we will receive the correct amount of data, but the output is not something we can effectively work with, because the parent of our access is always the specified counter with all instances below. You will have to access both of these objects to receive informations of your C disk for example:
```powershell
$Counter['\LogicalDisk(*)\free megabytes']['\LogicalDisk(C:)\free megabytes'].value;
$Counter['\LogicalDisk(*)\% idle time']['\LogicalDisk(C:)\% idle time'].value;
```
As you can see, the *problem* is not that big, but imagine you have 10 counters you wish to include for your disk. This is not something you wish to handle manually.
### The Solution: Structured Output
As we just learned in general this usage is fine, requires how ever some effort to display and use `Counter` properly. To resolve this, we will create a structured object now with automatic sorting of the data.
To make the example a little more interesting, lets do as much automation as possible.
At first we will create a variable and store all Counters inside the system provides:
```powershell
$CounterList = Show-IcingaPerformanceCounters 'LogicalDisk';
```
The `Show-IcingaPerformanceCounters` Cmdlet is returning all available Performance Counters. We can now use this output and create an array with our `New-IcingaPerformanceCounterArray` Cmdlet:
```powershell
$Counters = New-IcingaPerformanceCounterArray $CounterList;
```
Now we have initialised all `Counters` and `Instances` and loaded them into our variable. This is quite fine, but we still have the same result as described within our problem scenario:
```text
Name Value
---- -----
\LogicalDisk(*)\free megabytes {\LogicalDisk(HarddiskVolume30)\free megabytes, \LogicalDisk(R:)\free megabytes, \Log...
\LogicalDisk(*)\% disk read... {\LogicalDisk(HarddiskVolume1)\% disk read time, \LogicalDisk(_Total)\% disk read tim...
\LogicalDisk(*)\avg. disk w... {\LogicalDisk(HarddiskVolume13)\avg. disk write queue length, \LogicalDisk(R:)\avg. d...
\LogicalDisk(*)\disk transf... {\LogicalDisk(_Total)\disk transfers/sec, \LogicalDisk(G:)\disk transfers/sec, \Logic...
...
```
Now let the Framework do the magic and organice the entire `Counter` hashtable based on a group parent. As we fetch `LogicalDisk` data, lets group them by the `Instances` of the category and add our previous loaded counters to the call
```powershell
$Result = New-IcingaPerformanceCounterStructure -CounterCategory 'LogicalDisk' -PerformanceCounterHash $Counters;
```
Inside our `$Result` variable we have not sorted all Performance Counters properly based on their `Instance` and every single `Counter` is now available by the `name of the Counter` itself:
```powershell
Name Value
---- -----
Name Value
---- -----
HarddiskVolume13 {avg. disk queue length, % free space, avg. disk sec/transfer, avg. disk bytes/read...}
HarddiskVolume15 {avg. disk queue length, % free space, avg. disk sec/transfer, avg. disk bytes/read...}
HarddiskVolume30 {avg. disk queue length, % free space, avg. disk sec/transfer, avg. disk bytes/read...}
_Total {avg. disk queue length, % free space, avg. disk sec/transfer, avg. disk bytes/read...}
HarddiskVolume1 {avg. disk queue length, % free space, avg. disk sec/transfer, avg. disk bytes/read...}
G: {avg. disk queue length, % free space, avg. disk sec/transfer, avg. disk bytes/read...}
R: {avg. disk queue length, % free space, avg. disk sec/transfer, avg. disk bytes/read...}
HarddiskVolume24 {avg. disk queue length, % free space, avg. disk sec/transfer, avg. disk bytes/read...}
C: {avg. disk queue length, % free space, avg. disk sec/transfer, avg. disk bytes/read...}
```
Our `$Result` variable is a simple `hashtable` which contains all above mentioned data. By using the `Instance` as first index and the `Counter` as second index, you can directly access the values:
```powershell
$Result['C:']['avg. disk queue length'].value;
$Result['C:']['% disk time'].value;
$Result['C:']['avg. disk bytes/transfer'].value;
```

View file

@ -472,8 +472,8 @@ function Start-IcingaAgentInstallWizard()
$ServicePass = (Get-IcingaAgentInstallerAnswerInput -Prompt 'Please enter the password for your service user' -Secure -Default 'v').answer;
$InstallerArguments += "-ServicePass $ServicePass";
} else {
$ServicePass = '';
$InstallerArguments += "-ServicePass ''";
$ServicePass = $null
$InstallerArguments += '-ServicePass $null';
}
}
} else {
@ -544,6 +544,11 @@ function Start-IcingaAgentInstallWizard()
}
if ($RunInstaller) {
if ((Test-IcingaAgentNETFrameworkDependency) -eq $FALSE) {
Write-IcingaConsoleError -Message 'You cannot install the Icinga Agent on this system as the required .NET Framework version is not installed. Please install .NET Framework 4.6.0 or later and use the above provided install arguments to try again.'
return;
}
if ((Install-IcingaAgent -Version $AgentVersion -Source $PackageSource -AllowUpdates $AllowVersionChanges -InstallDir $InstallDir) -Or $Reconfigure) {
Reset-IcingaAgentConfigFile;
Move-IcingaAgentDefaultConfig;

View file

@ -0,0 +1,49 @@
<#
.SYNOPSIS
Test if .NET Framework 4.6.0 or above is installed which is required by
the Icinga Agent. Returns either true or false - depending on if the
.NET Framework 4.6.0 or above is installed or not
.DESCRIPTION
Test if .NET Framework 4.6.0 or above is installed which is required by
the Icinga Agent. Returns either true or false - depending on if the
.NET Framework 4.6.0 or above is installed or not
.FUNCTIONALITY
Test if .NET Framework 4.6.0 or above is installed
.EXAMPLE
PS>Test-IcingaAgentNETFrameworkDependency;
.OUTPUTS
System.Boolean
.LINK
https://github.com/Icinga/icinga-powershell-framework
https://docs.microsoft.com/en-us/dotnet/framework/migration-guide/how-to-determine-which-versions-are-installed
#>
function Test-IcingaAgentNETFrameworkDependency()
{
$RegistryContent = Get-ItemProperty -Path 'HKLM:SOFTWARE\Microsoft\NET Framework Setup\NDP\v4\Full' -ErrorAction SilentlyContinue;
# We require at least .NET Framework 4.6.0 to be installed on the system
# Version on Windows 10: 393295
# Version on any other system: 393297
# We do only require to check for the Windows 10 version, as the other Windows verions
# do not cause an issue there then because of how the next versions are iterated
if ($null -eq $RegistryContent -Or $RegistryContent.Release -lt 393295) {
if ($null -eq $RegistryContent) {
$RegistryContent = @{
'Version' = 'Unknown'
};
}
Write-IcingaConsoleError `
-Message 'To install the Icinga Agent you will require .NET Framework 4.6.0 or later to be installed on the system. Current installed version: {0}' `
-Objects $RegistryContent.Version;
return $FALSE;
}
Write-IcingaConsoleNotice `
-Message 'Found installed .NET Framework version {0}' `
-Objects $RegistryContent.Version;
return $TRUE;
}

View file

@ -29,16 +29,9 @@
$pc_instance | Add-Member -membertype ScriptMethod -name 'Init' -value {
# TODO: Re-Implement debug logging
<#$Icinga2.Log.Write(
$Icinga2.Enums.LogState.Debug,
[string]::Format('Creating new Counter for Category {0} with Instance {1} and Counter {2}. Full Name "{3}"',
$this.Category,
$this.Instance,
$this.Counter,
$this.FullName
)
);#>
Write-IcingaConsoleDebug `
-Message 'Creating new Counter for Category "{0}" with Instance "{1}" and Counter "{2}". Full Name "{3}"' `
-Objects $this.Category, $this.Instance, $this.Counter, $this.FullName;
# Create the Performance Counter object we want to access
$this.PerfCounter = New-Object System.Diagnostics.PerformanceCounter;

View file

@ -0,0 +1,58 @@
<#
.SYNOPSIS
Displays all available instances for a provided Performance Counter
.DESCRIPTION
Displays all available instances for a provided Performance Counter
.FUNCTIONALITY
Displays all available instances for a provided Performance Counter
.PARAMETER Counter
The name of the Performance Counter to fetch data for
.EXAMPLE
PS>Show-IcingaPerformanceCounterInstances -Counter '\Processor(*)\% processor time';
Name Value
---- -----
_Total \Processor(_Total)\% processor time
0 \Processor(0)\% processor time
1 \Processor(1)\% processor time
2 \Processor(2)\% processor time
3 \Processor(3)\% processor time
...
.LINK
https://github.com/Icinga/icinga-powershell-framework
#>
function Show-IcingaPerformanceCounterInstances()
{
param (
[string]$Counter
);
[hashtable]$Instances = @{};
if ([string]::IsNullOrEmpty($Counter)) {
Write-IcingaConsoleError 'Please enter a Performance Counter';
return;
}
$PerfCounter = New-IcingaPerformanceCounter -Counter $Counter -SkipWait $TRUE;
foreach ($entry in $PerfCounter.Counters) {
$Instances.Add(
$entry.Instance,
($Counter.Replace('(*)', ([string]::Format('({0})', $entry.Instance))))
);
}
if ($Instances.Count -eq 0) {
Write-IcingaConsoleNotice `
-Message 'No instances were found for Performance Counter "{0}". Please ensure the provided counter has instances and you are using "*" for the instance name.' `
-Objects $Counter;
return;
}
return (
$Instances.GetEnumerator() | Sort-Object Name
);
}