Skip to content

Commit 8e121f3

Browse files
committed
Fixes MPIO plugin to properly map MPIO drives to correct disks
1 parent 310f5be commit 8e121f3

4 files changed

Lines changed: 110 additions & 146 deletions

File tree

doc/31-Changelog.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,14 @@ documentation before upgrading to a new release.
77

88
Released closed milestones can be found on [GitHub](https://github.com/Icinga/icinga-powershell-plugins/milestones?state=closed).
99

10+
## 1.15.0 (2026-06-30)
11+
12+
[Issue and PRs](https://github.com/Icinga/icinga-powershell-plugins/milestone/24)
13+
14+
### Bugfixes
15+
16+
* [#476](https://github.com/Icinga/icinga-powershell-plugins/pull/476) Fixes mapping of MPIO drives to the correct LUN on the host for `Invoke-IcingaCheckMPIO`
17+
1018
## 1.14.1 (2026-03-31)
1119

1220
[Issue and PRs](https://github.com/Icinga/icinga-powershell-plugins/milestone/23)

plugins/Invoke-IcingaCheckMPIO.psm1

Lines changed: 79 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -48,99 +48,102 @@ function Invoke-IcingaCheckMPIO()
4848
);
4949

5050
$CheckPackage = New-IcingaCheckPackage -Name 'Multipath-IO Package' -OperatorAnd -Verbose $Verbosity -AddSummaryHeader;
51-
$MpioData = Get-IcingaMPIOData;
52-
[hashtable]$MpioPackages = @{ };
53-
54-
if ($MpioData -is [array]) {
55-
foreach ($mpio in $MpioData) {
56-
[string]$MpioInstance = $mpio.InstanceName;
57-
58-
# Create a new MPIO Package if it does not exist yet for each instance
59-
if (-Not $MpioPackages.ContainsKey($MpioInstance)) {
60-
$MpioPackages.Add(
61-
$MpioInstance,
62-
@{
63-
'MpioPackage' = New-IcingaCheckPackage -Name ([string]::Format('{0} Package', $MpioInstance)) ` -OperatorAnd -Verbose $Verbosity;
64-
'DrivePackage' = New-IcingaCheckPackage -Name ([string]::Format('{0} Drivers Package', $MpioInstance)) ` -OperatorAnd -Verbose $Verbosity;
65-
}
66-
);
67-
68-
# Add instance specific checks (rquired only once, because they are identical for all drives of an instance)
69-
$MpioPackages[$MpioInstance]['MpioPackage'].AddCheck(
70-
(
71-
New-IcingaCheck `
72-
-Name ([string]::Format('{0} Active', $MpioInstance)) `
73-
-Value $mpio.Active `
74-
-NoPerfData
75-
)
76-
);
77-
78-
$MpioPackages[$MpioInstance]['MpioPackage'].AddCheck(
79-
(
80-
New-IcingaCheck `
81-
-Name ([string]::Format('{0} NumberDrives', $MpioInstance)) `
82-
-Value $mpio.NumberDrives `
83-
-Unit 'c' `
84-
-MetricIndex $MpioInstance `
85-
-MetricName 'numberofdrives'
86-
)
87-
);
88-
}
51+
$DrivePackage = $null;
52+
$DiskData = Join-IcingaPhysicalDiskDataPerfCounter;
53+
[bool]$AddedBasePackages = $false;
54+
[bool]$FoundMpioData = $false;
55+
56+
foreach ($DiskPart in $DiskData.Keys) {
57+
$DiskObjects = $DiskData[$DiskPart];
58+
$MpioWarningThreshold = $null;
59+
$MpioCriticalThreshold = $null;
60+
$VolumeName = '';
61+
$MpioInstance = '';
62+
63+
# Don't add non-mpio disks to the check package, but continue with the next one, as there might be multiple disks and only some of them are MPIO enabled
64+
if ($null -eq $DiskObjects.Data.MPIO) {
65+
continue;
66+
}
67+
68+
$FoundMpioData = $true;
69+
$MpioInstance = $DiskObjects.Data.MPIO.InstanceName;
70+
71+
# Only add these once, as the data is identical for every disk
72+
if (-not $AddedBasePackages) {
73+
$DrivePackage = New-IcingaCheckPackage -Name ([string]::Format('{0} Drives Package', $MpioInstance)) -OperatorAnd -Verbose $Verbosity;
74+
75+
$IsActive = New-IcingaCheck `
76+
-Name ([string]::Format('{0} Active', $MpioInstance)) `
77+
-Value $DiskObjects.Data.MPIO.Active `
78+
-NoPerfData;
79+
80+
$NumberOfDrives = New-IcingaCheck `
81+
-Name ([string]::Format('{0} NumberDrives', $MpioInstance)) `
82+
-Value $DiskObjects.Data.MPIO.NumberDrives `
83+
-Unit 'c' `
84+
-MetricIndex $MpioInstance `
85+
-MetricName 'numberofdrives';
86+
87+
$CheckPackage.AddCheck($IsActive);
88+
$CheckPackage.AddCheck($NumberOfDrives);
89+
$AddedBasePackages = $true;
90+
}
8991

90-
$MpioWarningThreshold = $null;
91-
$MpioCriticalThreshold = $null;
92+
foreach ($entry in $NumberOfPathWarning) {
93+
$parts = $entry.Split('=', 2);
94+
[bool]$VolumeMatch = $false;
9295

93-
foreach ($entry in $NumberOfPathWarning) {
94-
$parts = $entry.Split('=', 2);
95-
if ($mpio.Volume -like $parts[0]) {
96+
foreach ($assignedVolume in $DiskObjects.Data.VolumeNames) {
97+
if ($assignedVolume -like $parts[0]) {
9698
$MpioWarningThreshold = $parts[1];
99+
$VolumeMatch = $true;
100+
$VolumeName = $assignedVolume;
97101
break;
98102
}
99103
}
100104

101-
foreach ($entry in $NumberOfPathCritical) {
102-
$parts = $entry.Split('=', 2);
103-
if ($mpio.Volume -like $parts[0]) {
105+
if ($VolumeMatch) {
106+
break;
107+
}
108+
}
109+
110+
foreach ($entry in $NumberOfPathCritical) {
111+
$parts = $entry.Split('=', 2);
112+
[bool]$VolumeMatch = $false;
113+
114+
foreach ($assignedVolume in $DiskObjects.Data.VolumeNames) {
115+
if ($assignedVolume -like $parts[0]) {
104116
$MpioCriticalThreshold = $parts[1];
117+
$VolumeMatch = $true;
118+
$VolumeName = $assignedVolume;
105119
break;
106120
}
107121
}
108122

109-
# Add all drive specific checks from the MPIO instance
110-
$MpioPackages[$MpioInstance]['DrivePackage'].AddCheck(
111-
(
112-
New-IcingaCheck `
113-
-Name ([string]::Format('{0} Number Paths', $mpio.Volume)) `
114-
-Value $mpio.NumberOfPaths `
115-
-Unit 'c' `
116-
-MetricIndex $mpio.Volume `
117-
-MetricName 'numberofpaths'
118-
).WarnOutOfRange(
119-
$MpioWarningThreshold
120-
).CritOutOfRange(
121-
$MpioCriticalThreshold
122-
)
123-
);
123+
if ($VolumeMatch) {
124+
break;
125+
}
124126
}
125127

126-
foreach ($check in $MpioPackages.Keys) {
127-
$MpioCheckPackage = $MpioPackages[$check]['MpioPackage'];
128-
$DriverPackage = $MpioPackages[$check]['DrivePackage'];
128+
$MpioDrive = New-IcingaCheck `
129+
-Name ([string]::Format('{0} Number Paths', $VolumeName)) `
130+
-Value $DiskObjects.Data.MPIO.NumberPaths `
131+
-Unit 'c' `
132+
-MetricIndex $VolumeName `
133+
-MetricName 'numberofpaths';
129134

130-
if ($MpioCheckPackage.HasChecks()) {
131-
# Add Driver Package to MPIO Package
132-
$MpioCheckPackage.AddCheck($DriverPackage);
133-
}
135+
$MpioDrive.WarnOutOfRange($MpioWarningThreshold).CritOutOfRange($MpioCriticalThreshold) | Out-Null;
134136

135-
$CheckPackage.AddCheck($MpioCheckPackage);
136-
}
137+
$DrivePackage.AddCheck($MpioDrive);
138+
}
139+
140+
if (-not $FoundMpioData) {
141+
$Check = New-IcingaCheck -Name 'MultiPath-IO Check Status' -NoPerfData;
142+
$Check.SetCritical('No MPIO devices were found on this machine', $TRUE) | Out-Null;
143+
# Enforce the checks to critical in case we get an exception
144+
$CheckPackage.AddCheck($Check);
137145
} else {
138-
if ($MpioData -is [hashtable] -and $MpioData.ContainsKey('Exception')) {
139-
$Check = New-IcingaCheck -Name 'MultiPath-IO Check Status' -NoPerfData;
140-
$Check.SetCritical($TestIcingaWindowsInfoEnums.TestIcingaWindowsInfoText[[int]$MpioData.Exception], $TRUE) | Out-Null;
141-
# Enforce the checks to critical in case we get an exception
142-
$CheckPackage.AddCheck($Check);
143-
}
146+
$CheckPackage.AddCheck($DrivePackage);
144147
}
145148

146149
return (New-IcingaCheckResult -Check $CheckPackage -NoPerfData $NoPerfData -Compile);

provider/disks/Get-IcingaPhysicalDiskInfo.psm1

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ function Global:Get-IcingaPhysicalDiskInfo()
3838
$PhysicalDiskData = @{ };
3939
$PartitionMapping = @{ };
4040
$VolumeData = Get-Volume;
41+
[array]$MPIOData = Get-IcingaMPIOData;
4142

4243
# This will allow us to map the label of a volume to a certain disk later on
4344
foreach ($volume in $VolumeData) {
@@ -135,6 +136,22 @@ function Global:Get-IcingaPhysicalDiskInfo()
135136
'SectorsPerTrack' = $physical_disk.SectorsPerTrack;
136137
};
137138

139+
if ($null -ne $MPIOData) {
140+
foreach ($mpio in $MPIOData.DriveInfo) {
141+
if ([int]($mpio.Name.Replace('MPIO Disk', '').Trim()) -eq [int]$DiskId) {
142+
$DiskInfo.Add(
143+
'MPIO', @{
144+
'InstanceName' = $MPIOData.InstanceName;
145+
'NumberDrives' = $MPIOData.NumberDrives;
146+
'Active' = $MPIOData.Active;
147+
'NumberPaths' = $mpio.NumberPaths;
148+
}
149+
);
150+
break;
151+
}
152+
}
153+
}
154+
138155
$Partitions = Get-CimAssociatedInstance -InputObject $physical_disk -ResultClass Win32_DiskPartition;
139156
$MaxBlocks = 0;
140157

provider/mpio/Get-IcingaMPIOData.psm1

Lines changed: 6 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -6,89 +6,25 @@
66
.OUTPUTS
77
System.Collections.Hashtable
88
#>
9-
109
function Get-IcingaMPIOData()
1110
{
12-
if (-Not (Test-IcingaMPIOInstalled)) {
13-
Exit-IcingaThrowException -ExceptionType 'Custom' -CustomMessage 'MPIO not installed' -InputString 'The Multipath-IO feature is not installed on this system.' -Force;
11+
if (-not (Test-IcingaMPIOInstalled)) {
12+
return $null;
1413
}
1514

1615
# Check whether MPIO_DISK_INFO exists on the targeted system
1716
$TestClasses = Test-IcingaWindowsInformation -ClassName 'MPIO_DISK_INFO' -NameSpace 'Root\WMI';
1817
# Check for error Ids with Binary operators
1918
$BitWiseCheck = Test-IcingaBinaryOperator -Value $TestClasses -Compare @($TestIcingaWindowsInfoEnums.TestIcingaWindowsInfoExceptionType.Values) -Namespace $TestIcingaWindowsInfoEnums.TestIcingaWindowsInfo;
20-
# Get the lasth throw exception id
21-
$ExceptionId = Get-IcingaLastExceptionId;
2219

2320
# We return a empty hashtable if for some reason no data from the WMI classes can be retrieved
2421
if ($BitWiseCheck) {
25-
if ($TestClasses -ne $TestIcingaWindowsInfoEnums.TestIcingaWindowsInfo.Ok) {
26-
return @{'Exception' = $TestClasses; };
27-
}
28-
}
29-
30-
# Throw an exception when the exception ID is not OK, NotSpecified and PermissionError
31-
if ($TestClasses -ne $TestIcingaWindowsInfoEnums.TestIcingaWindowsInfo.Ok) {
32-
Exit-IcingaThrowException `
33-
-CustomMessage ($TestIcingaWindowsInfoEnums.TestIcingaWindowsInfoExceptionType[[int]$TestClasses]) `
34-
-InputString ($TestIcingaWindowsInfoEnums.TestIcingaWindowsInfoText[[int]$TestClasses]) `
35-
-ExceptionType Custom `
36-
-Force;
37-
}
38-
39-
$VolumeData = Get-Volume;
40-
$PartitionData = Get-Partition;
41-
$MpioData = Get-IcingaWindowsInformation -ClassName MPIO_DISK_INFO -Namespace 'Root\WMI';
42-
$VolumeObject = @();
43-
44-
foreach ($vol in $VolumeData) {
45-
[string]$VolumeName = $vol.FileSystemLabel;
46-
47-
if ([string]::IsNullOrEmpty($VolumeName)) {
48-
$VolumeName = 'NoLabel';
49-
}
50-
51-
$VolumeObject += [PSCustomObject]@{
52-
'Volume' = $VolumeName;
53-
'DriveLetter' = $vol.DriveLetter;
54-
'FileSystem' = $vol.FileSystem;
55-
'Health' = $vol.HealthStatus;
56-
'SizeRemaining' = $vol.SizeRemaining;
57-
'InstanceName' = '';
58-
'DiskNumber' = -1;
59-
'Partition' = -1;
60-
'Size' = 0;
61-
'NumberOfPaths' = 0;
62-
'NumberDrives' = 0;
63-
'Active' = 0;
64-
'Type' = '';
65-
'DriveType' = $vol.DriveType;
66-
};
67-
68-
foreach ($partition in $PartitionData) {
69-
if ($partition.AccessPaths -contains $vol.Path) {
70-
$VolumeObject[-1].DiskNumber = $partition.DiskNumber;
71-
$VolumeObject[-1].Partition = $partition.PartitionNumber;
72-
$VolumeObject[-1].Size = $partition.Size;
73-
$VolumeObject[-1].Type = $partition.Type;
74-
}
75-
}
76-
}
22+
if ($TestClasses -eq $TestIcingaWindowsInfoEnums.TestIcingaWindowsInfo.Ok) {
23+
$MPIOData = Get-IcingaWindowsInformation -ClassName MPIO_DISK_INFO -Namespace 'Root\WMI';
7724

78-
foreach ($obj in $VolumeObject) {
79-
$diskId = $obj.DiskNumber;
80-
foreach ($MpioInstance in $MpioData) {
81-
foreach ($drive in $MpioInstance.DriveInfo) {
82-
if ($drive.Name.replace('MPIO Disk', '') -eq $diskid) {
83-
$obj.InstanceName = $MpioInstance.InstanceName;
84-
$obj.NumberDrives = $MpioInstance.NumberDrives;
85-
$obj.Active = $MpioInstance.Active;
86-
$obj.NumberOfPaths = $drive.NumberPaths;
87-
break;
88-
}
89-
}
25+
return $MPIOData;
9026
}
9127
}
9228

93-
return $VolumeObject;
29+
return $null;
9430
}

0 commit comments

Comments
 (0)