Skip to content

Commit b0179fa

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

4 files changed

Lines changed: 147 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: 103 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -48,99 +48,126 @@ 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+
$VolumeNames = 'None';
61+
$VolumeIndex = [string]::Format('MPIO Disk {0}', $DiskObjects.Data.DiskId);
62+
$MpioInstance = '';
63+
$DriveReferences = 'None';
64+
65+
# 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
66+
if ($null -eq $DiskObjects.Data.MPIO) {
67+
continue;
68+
}
69+
70+
$FoundMpioData = $true;
71+
$MpioInstance = $DiskObjects.Data.MPIO.InstanceName;
72+
73+
if ($null -ne $DiskObjects.Data.VolumeNames -and $DiskObjects.Data.VolumeNames.Count -gt 0) {
74+
$VolumeIndex = $DiskObjects.Data.VolumeNames[0];
75+
$VolumeNames = $DiskObjects.Data.VolumeNames -join '; ';
76+
}
77+
78+
if ($null -ne $DiskObjects.Data.DriveReference -and $DiskObjects.Data.DriveReference.Count -gt 0) {
79+
$DriveReferences = $DiskObjects.Data.DriveReference.Keys -join '; ';
80+
}
81+
82+
# Only add these once, as the data is identical for every disk
83+
if (-not $AddedBasePackages) {
84+
$DrivePackage = New-IcingaCheckPackage -Name ([string]::Format('{0} Drives Package', $MpioInstance)) -OperatorAnd -Verbose $Verbosity;
85+
86+
$IsActive = New-IcingaCheck `
87+
-Name ([string]::Format('{0} Active', $MpioInstance)) `
88+
-Value $DiskObjects.Data.MPIO.Active `
89+
-NoPerfData;
90+
91+
$NumberOfDrives = New-IcingaCheck `
92+
-Name ([string]::Format('{0} NumberDrives', $MpioInstance)) `
93+
-Value $DiskObjects.Data.MPIO.NumberDrives `
94+
-Unit 'c' `
95+
-MetricIndex $MpioInstance `
96+
-MetricName 'numberofdrives';
97+
98+
$CheckPackage.AddCheck($IsActive);
99+
$CheckPackage.AddCheck($NumberOfDrives);
100+
$AddedBasePackages = $true;
101+
}
89102

90-
$MpioWarningThreshold = $null;
91-
$MpioCriticalThreshold = $null;
103+
foreach ($entry in $NumberOfPathWarning) {
104+
$parts = $entry.Split('=', 2);
105+
[bool]$VolumeMatch = $false;
92106

93-
foreach ($entry in $NumberOfPathWarning) {
94-
$parts = $entry.Split('=', 2);
95-
if ($mpio.Volume -like $parts[0]) {
107+
foreach ($assignedVolume in $DiskObjects.Data.VolumeNames) {
108+
if ($assignedVolume -like $parts[0]) {
96109
$MpioWarningThreshold = $parts[1];
110+
$VolumeMatch = $true;
97111
break;
98112
}
99113
}
100114

101-
foreach ($entry in $NumberOfPathCritical) {
102-
$parts = $entry.Split('=', 2);
103-
if ($mpio.Volume -like $parts[0]) {
115+
if ($VolumeMatch) {
116+
break;
117+
}
118+
}
119+
120+
foreach ($entry in $NumberOfPathCritical) {
121+
$parts = $entry.Split('=', 2);
122+
[bool]$VolumeMatch = $false;
123+
124+
foreach ($assignedVolume in $DiskObjects.Data.VolumeNames) {
125+
if ($assignedVolume -like $parts[0]) {
104126
$MpioCriticalThreshold = $parts[1];
127+
$VolumeMatch = $true;
105128
break;
106129
}
107130
}
108131

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-
);
132+
if ($VolumeMatch) {
133+
break;
134+
}
124135
}
125136

126-
foreach ($check in $MpioPackages.Keys) {
127-
$MpioCheckPackage = $MpioPackages[$check]['MpioPackage'];
128-
$DriverPackage = $MpioPackages[$check]['DrivePackage'];
137+
$MPIODiskPackage = New-IcingaCheckPackage -Name ([string]::Format('MPIO Disk {0}', $DiskObjects.Data.DiskId)) -OperatorAnd -Verbose $Verbosity;
129138

130-
if ($MpioCheckPackage.HasChecks()) {
131-
# Add Driver Package to MPIO Package
132-
$MpioCheckPackage.AddCheck($DriverPackage);
133-
}
139+
$MpioDrive = New-IcingaCheck `
140+
-Name 'Number Paths' `
141+
-Value $DiskObjects.Data.MPIO.NumberPaths `
142+
-Unit 'c' `
143+
-MetricIndex $VolumeIndex `
144+
-MetricName 'numberofpaths';
134145

135-
$CheckPackage.AddCheck($MpioCheckPackage);
136-
}
146+
$MpioDrive.WarnOutOfRange($MpioWarningThreshold).CritOutOfRange($MpioCriticalThreshold) | Out-Null;
147+
148+
$MpioVolumes = New-IcingaCheck `
149+
-Name 'Assigned Volumes' `
150+
-Value $VolumeNames `
151+
-NoPerfData;
152+
153+
$MpioLetters = New-IcingaCheck `
154+
-Name 'Assigned Letters' `
155+
-Value $DriveReferences `
156+
-NoPerfData;
157+
158+
$MPIODiskPackage.AddCheck($MpioDrive);
159+
$MPIODiskPackage.AddCheck($MpioVolumes);
160+
$MPIODiskPackage.AddCheck($MpioLetters);
161+
$DrivePackage.AddCheck($MPIODiskPackage);
162+
}
163+
164+
if (-not $FoundMpioData) {
165+
$Check = New-IcingaCheck -Name 'MultiPath-IO Check Status' -NoPerfData;
166+
$Check.SetCritical('No MPIO devices were found on this machine', $TRUE) | Out-Null;
167+
# Enforce the checks to critical in case we get an exception
168+
$CheckPackage.AddCheck($Check);
137169
} 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-
}
170+
$CheckPackage.AddCheck($DrivePackage);
144171
}
145172

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

provider/disks/Get-IcingaPhysicalDiskInfo.psm1

Lines changed: 18 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) {
@@ -75,6 +76,7 @@ function Global:Get-IcingaPhysicalDiskInfo()
7576
}
7677

7778
$DiskInfo = @{
79+
'DiskId' = $DiskId;
7880
'PartitionStyle' = ''; # Set later on partition check
7981
'PartitionLayout' = @{ }; # Set later on partition check
8082
'DriveReference' = @{ };
@@ -135,6 +137,22 @@ function Global:Get-IcingaPhysicalDiskInfo()
135137
'SectorsPerTrack' = $physical_disk.SectorsPerTrack;
136138
};
137139

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

provider/mpio/Get-IcingaMPIOData.psm1

Lines changed: 18 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -6,89 +6,37 @@
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;
13+
# Test Data for debugging
14+
<#
15+
return [PSCustomObject]@{
16+
'DriveInfo' = [PSCustomObject]@{
17+
'Name' = 'MPIO Disk 0';
18+
'NumberPaths' = 8;
19+
};
20+
'InstanceName' = 'Test Instance';
21+
'NumberDrives' = 1;
22+
'Active' = $true;
23+
}
24+
#>
1425
}
1526

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

2332
# We return a empty hashtable if for some reason no data from the WMI classes can be retrieved
2433
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-
}
34+
if ($TestClasses -eq $TestIcingaWindowsInfoEnums.TestIcingaWindowsInfo.Ok) {
35+
$MPIOData = Get-IcingaWindowsInformation -ClassName MPIO_DISK_INFO -Namespace 'Root\WMI';
7736

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-
}
37+
return $MPIOData;
9038
}
9139
}
9240

93-
return $VolumeObject;
41+
return $null;
9442
}

0 commit comments

Comments
 (0)