I have a server that basically has only volumes mounted from a Windows Server Cluster Shared Volume. I could use the WMI Volume Sensor, but it will fail as soon as I move volumes within the cluster and thus their GUIDs change. While the ID selection would work in other situations, I also have the volumes mounted into subdirectories. Is there any way to monitor this properly with PRTG?
Can I Monitor Mounted Windows Volumes with PRTG?
Votes:
0
7 Replies
Votes:
2
Monitoring Mounted Windows Volumes with PRTG
Yes, you can monitor mounted Windows Volumes as asked above with PRTG. We wrote a little script for you, just read on and see below.
Scope of the Script
The following PowerShell script allows you to monitor the available size (MB/GB/TB and percentage) of the Windows mounts on the target host. The script can also cope with GUID changes of the actual volume because it works with labels rather than GUIDs.
The sensor also features a Mounts channel that resembles the number of mounts at the time of the creation of the sensor. If one gets unmounted, the sensor will go into a down status. Remember to modify the limit accordingly if the amount of volumes changes!
The installed sensor, monitoring the Windows volumes. Click here to enlarge.
Requirements
- WMI has to work in general between PRTG and the target host.
- You'll need administrative Windows credentials entered in the parent device.
- PowerShell sensors need to work in general within PRTG.
See also our HowTo-Guide for PowerShell based custom sensors.
Installation
- Save the script to %programfiles(x86)%\PRTG Network Monitor\Custom Sensors\EXEXML as PRTG-WindowsClusterVolumes.ps1.
- Create a new EXE/Script (Advanced) Sensor and configure it as follows:
- Program/Script: PRTG-WindowsClusterVolumes.ps1
- Parameter: %host
- Security Context: Use the Windows credentials of the parent device.
Script
### Copyright (c) 2012-2014, Svendsen Tech ### Author: Joakim Svendsen ### Get-MountPointData v1.2 # ___ ___ _____ ___ #| _ \ _ \_ _/ __| #| _/ / | || (_ | #|_| |_|_\ |_| \___| # Windows Mounts Sensor # "Change history": 1.0 -> 1.1 = -Credential, -PromptForCredentials and -NoFormat (the latter allows math operations) # --- " ---: 1.1 -> 1.2 = -IncludeDiskInfo to show physical disk index number (this was not trivial). # ---------: 1.1 -> 1.3 = Added PRTG output # Convert from one device ID format to another. param($computerName) $channelTemplateDiskFree = @" <result> <channel>{0}</channel> <value>{1}</value> <unit>{2}</unit> {3} <Float>0</Float> </result> "@ $channelTemplatePercent = @" <result> <channel>{0}</channel> <value>{1}</value> <unit>{2}</unit> {3} <Float>1</Float> <LimitMode>1</LimitMode> <LimitMinWarning>25</LimitMinWarning> <LimitMinError>15</LimitMinError> </result> "@ $channelTemplateMountedDisks = @" <result> <channel>{0}</channel> <value>{1}</value> <unit>CustomUnit</unit> <customunit>Mounts</customunit> <Float>0</Float> <LimitMode>1</LimitMode> <LimitMinError>{2}</LimitMinError> </result> "@ #<VolumeSize>GigaByte</VolumeSize> function Get-DeviceIDFromMP { param([Parameter(Mandatory=$true)][string] $VolumeString, [Parameter(Mandatory=$true)][string] $Directory) if ($VolumeString -imatch '^\s*Win32_Volume\.DeviceID="([^"]+)"\s*$') { # Return it in the wanted format. $Matches[1] -replace '\\{2}', '\' } else { # Return a presumably unique hashtable key if there's no match. "Unknown device ID for " + $Directory } } # Thanks to Justin Rich (jrich523) for this C# snippet. # https://jrich523.wordpress.com/2015/02/27/powershell-getting-the-disk-drive-from-a-volume-or-mount-point/ $STGetDiskClass = @" using System; using Microsoft.Win32.SafeHandles; using System.IO; using System.Runtime.InteropServices; public class STGetDisk { private const uint IoctlVolumeGetVolumeDiskExtents = 0x560000; [StructLayout(LayoutKind.Sequential)] public struct DiskExtent { public int DiskNumber; public Int64 StartingOffset; public Int64 ExtentLength; } [StructLayout(LayoutKind.Sequential)] public struct DiskExtents { public int numberOfExtents; public DiskExtent first; } [DllImport("Kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)] private static extern SafeFileHandle CreateFile( string lpFileName, [MarshalAs(UnmanagedType.U4)] FileAccess dwDesiredAccess, [MarshalAs(UnmanagedType.U4)] FileShare dwShareMode, IntPtr lpSecurityAttributes, [MarshalAs(UnmanagedType.U4)] FileMode dwCreationDisposition, [MarshalAs(UnmanagedType.U4)] FileAttributes dwFlagsAndAttributes, IntPtr hTemplateFile); [DllImport("Kernel32.dll", SetLastError = false, CharSet = CharSet.Auto)] private static extern bool DeviceIoControl( SafeFileHandle hDevice, uint IoControlCode, [MarshalAs(UnmanagedType.AsAny)] [In] object InBuffer, uint nInBufferSize, ref DiskExtents OutBuffer, int nOutBufferSize, ref uint pBytesReturned, IntPtr Overlapped ); public static string GetPhysicalDriveString(string path) { //clean path up path = path.TrimEnd('\\'); if (!path.StartsWith(@"\\.\")) path = @"\\.\" + path; SafeFileHandle shwnd = CreateFile(path, FileAccess.Read, FileShare.Read | FileShare.Write, IntPtr.Zero, FileMode.Open, 0, IntPtr.Zero); if (shwnd.IsInvalid) { //Marshal.ThrowExceptionForHR(Marshal.GetLastWin32Error()); Exception e = Marshal.GetExceptionForHR(Marshal.GetLastWin32Error()); } uint bytesReturned = new uint(); DiskExtents de1 = new DiskExtents(); bool result = DeviceIoControl(shwnd, IoctlVolumeGetVolumeDiskExtents, IntPtr.Zero, 0, ref de1, Marshal.SizeOf(de1), ref bytesReturned, IntPtr.Zero); shwnd.Close(); if (result) return @"\\.\PhysicalDrive" + de1.first.DiskNumber; return null; } } "@ try { Add-Type -TypeDefinition $STGetDiskClass -ErrorAction Stop } catch { if (-not $Error[0].Exception -like '*The type name * already exists*') { Write-Warning -Message "Error adding [STGetDisk] class locally." } } function Get-MountPointData { [CmdletBinding( DefaultParameterSetName='NoPrompt' )] param( [Parameter(Mandatory=$true)][string[]] $ComputerName, [Parameter(ParameterSetName='Prompt')][switch] $PromptForCredentials, [Parameter(ParameterSetName='NoPrompt')][System.Management.Automation.Credential()] $Credential = [System.Management.Automation.PSCredential]::Empty, [switch] $IncludeRootDrives, [switch] $NoFormat, [switch] $IncludeDiskInfo ) foreach ($Computer in $ComputerName) { $WmiHash = @{ ComputerName = $Computer ErrorAction = 'Stop' } #if ($PromptForCredentials -and $Credential.Username) { # Write-Warning "You specified both -PromptForCredentials and -Credential. Prompting overrides." #} if ($PSCmdlet.ParameterSetName -eq 'Prompt') { $WmiHash.Credential = Get-Credential } elseif ($Credential.Username) { $WmiHash.Credential = $Credential } try { # Collect mount point device IDs and populate a hashtable with IDs as keys $MountPointData = @{} Get-WmiObject @WmiHash -Class Win32_MountPoint | Where-Object { if ($IncludeRootDrives) { $true } else { $_.Directory -NotMatch '^\s*Win32_Directory\.Name="[a-z]:\\{2}"\s*$' } } | ForEach-Object { $MountPointData.(Get-DeviceIDFromMP -VolumeString $_.Volume -Directory $_.Directory) = $_.Directory } $Volumes = @(Get-WmiObject @WmiHash -Class Win32_Volume | Where-Object { if ($IncludeRootDrives) { $true } else { -not $_.DriveLetter } } | Select-Object Label, Caption, Capacity, FreeSpace, FileSystem, DeviceID, @{n='Computer';e={$Computer}} ) } catch { Write-Error "${Computer}: Terminating WMI error (skipping): $_" continue } if (-not $Volumes.Count) { Write-Error "${Computer}: No mount points found. Skipping." continue } if ($PSBoundParameters['IncludeDiskInfo']) { $DiskDriveWmiInfo = Get-WmiObject @WmiHash -Class Win32_DiskDrive } $Volumes | ForEach-Object { if ($MountPointData.ContainsKey($_.DeviceID)) { # Let's avoid dividing by zero, it's so disruptive. if ($_.Capacity) { $PercentFree = $_.FreeSpace*100/$_.Capacity } else { $PercentFree = 0 } $_ | Select-Object -Property DeviceID, Computer, Label, Caption, FileSystem, @{n='Size (GB)';e={$_.Capacity/1GB}}, @{n='Free space';e={$_.FreeSpace}}, @{n='Percent free';e={$PercentFree}} } } | Sort-Object -Property 'Percent free', @{Descending=$true;e={$_.'Size (GB)'}}, Label, Caption | Select-Object -Property $(if ($NoFormat) { @{n='ComputerName'; e={$_.Computer}}, @{n='Label'; e={$_.Label}}, @{n='Caption'; e={$_.Caption}}, @{n='FileSystem'; e={$_.FileSystem}}, @{n='Size (GB)'; e={$_.'Size (GB)'}}, @{n='Free space'; e={$_.'Free space'}}, @{n='Percent free'; e={$_.'Percent free'}}, $(if ($PSBoundParameters['IncludeDiskInfo']) { @{n='Disk Index'; e={ try { $ScriptBlock = { param($GetDiskClass, $DriveString) try { Add-Type -TypeDefinition $GetDiskClass -ErrorAction Stop } catch { #Write-Error -Message "${Computer}: Error creating class [STGetDisk]" return "Error creating [STGetDisk] class: $_" } return [STGetDisk]::GetPhysicalDriveString($DriveString) } if ($Credential.Username) { $PhysicalDisk = Invoke-Command -ComputerName $Computer -Credential $Credential -ScriptBlock $ScriptBlock -ArgumentList $STGetDiskClass, $(if ($_.Caption -imatch '\A[a-z]:\\\z') { $_.Caption } else { $_.DeviceID.TrimStart('\?') }) } else { $PhysicalDisk = Invoke-Command -ComputerName $Computer -ScriptBlock $ScriptBlock -ArgumentList $STGetDiskClass, $(if ($_.Caption -imatch '\A[a-z]:\\\z') { $_.Caption } else { $_.DeviceID.TrimStart('\?') }) } if ($PhysicalDisk -like 'Error*') { "Error: $PhysicalDisk" } else { ($DiskDriveWmiInfo | Where-Object { $PhysicalDisk } | Where-Object { $PhysicalDisk.Trim() -eq $_.Name } | Select-Object -ExpandProperty Index) -join '; ' } } catch { "Error: $_" } } # end of disk index expression } # end of if disk index hashtable }) # end of if includediskinfo parameter subexpression and if } else { @{n='ComputerName'; e={$_.Computer}}, @{n='Label'; e={$_.Label}}, @{n='Caption'; e={$_.Caption}}, @{n='FileSystem'; e={$_.FileSystem}}, @{n='Size (GB)'; e={$_.'Size (GB)'.ToString('N')}}, @{n='Free space'; e={$_.'Free space'.ToString('N')}}, @{n='Percent free'; e={$_.'Percent free'.ToString('N')}}, $(if ($PSBoundParameters['IncludeDiskInfo']) { @{n='Disk Index'; e={ try { $ScriptBlock = { param($GetDiskClass, $DriveString) try { Add-Type -TypeDefinition $GetDiskClass -ErrorAction Stop } catch { #Write-Error -Message "${Computer}: Error creating class [STGetDisk]" return "Error creating [STGetDisk] class: $_" } return [STGetDisk]::GetPhysicalDriveString($DriveString) } if ($Credential.Username) { $PhysicalDisk = Invoke-Command -ComputerName $Computer -Credential $Credential -ScriptBlock $ScriptBlock -ArgumentList $STGetDiskClass, $(if ($_.Caption -imatch '\A[a-z]:\\\z') { $_.Caption } else { $_.DeviceID.TrimStart('\?') }) } else { $PhysicalDisk = Invoke-Command -ComputerName $Computer -ScriptBlock $ScriptBlock -ArgumentList $STGetDiskClass, $(if ($_.Caption -imatch '\A[a-z]:\\\z') { $_.Caption } else { $_.DeviceID.TrimStart('\?') }) } if ($PhysicalDisk -like 'Error*') { "Error: $PhysicalDisk" } else { ($DiskDriveWmiInfo | Where-Object { $PhysicalDisk } | Where-Object { $PhysicalDisk.Trim() -eq $_.Name } | Select-Object -ExpandProperty Index) -join '; ' } } catch { "Error: $_" } } # end of disk index expression } # end of if disk index hashtable }) # end of if includediskinfo parameter subexpression and if }) # end of if $NoFormat } } function This-PRTGOut ($computerName) { [System.Globalization.NumberFormatInfo]::CurrentInfo.NumberGroupSeparator = ""; [System.Globalization.NumberFormatInfo]::CurrentInfo.NumberDecimalSeparator = "."; $Channels = ""; $DiskCount = 0; $Disks = (Get-MountPointData -ComputerName $ComputerName -IncludeDiskInfo) $Disks | ft -AutoSize foreach($Disk in $Disks) { if($Disk.Label.Length -ne 0){ $DiskCount++; $Channels += ([string]::Format($channelTemplateDiskFree,"[$($Disk.Label)] Free",[double]$Disk."Free Space","BytesDisk","<VolumeSize>GigaByte</VolumeSize>")); $Channels += ([string]::Format($channelTemplatePercent,"[$($Disk.Label)] Free %",[int]$Disk."Percent free","Percent","")); } } $channels += ([string]::Format($channelTemplateMountedDisks,"Mounts",$DiskCount,$DiskCount)); write-host "<prtg>"; write-host $Channels; write-host "</prtg>"; } This-PRTGOut -computerName $computername;
Thanks a lot to Joakim Svendsen and Justin Rich!
Note
This article is provided for your information only. The steps described here have been tested carefully. However, we cannot offer deep technical support for customizing this script nor for writing your own scripts.
More
Are you looking for more custom scripts and sensors than the native sensors of PRTG? Then have a look at our PRTG Sensor Hub!
Created on May 26, 2017 7:48:32 AM by
Stephan Linke [Paessler Support]
Last change on Sep 18, 2020 10:04:49 AM by
Maike Guba [Paessler Support]
(2,404)
●2
●1
Votes:
0
Could someone please help updating this script which will make it work in PRTG ver. 19.2.50.2842? As now it is returning a "XML: The returned XML does not match the expected schema. (code: PE233) -- JSON: The returned JSON does not match the expected structure (Invalid JSON.). (code: PE231)". It seems like it has to do with usage of "" for parameters and some other changes since this script was released. Thank you.
Votes:
0
Dear CWWsysop,
in order to debug this, please go to the sensor's "Settings" tab, and enable "Write Exe result to disk". After the next sensor scan, find the logs in "C:\ProgramData\Paessler\PRTG Network Monitor\Logs (Sensors)".
One of the files contains the script output. Probably the script threw an error, so that no valid XML result was created.
Votes:
0
In PRTG Version 19.4.52.3515 we have inexplicable issues with the script.
- When the monitored Volume Mountpoint goes down, it is not reported by PRTG
- When it comes up again, it is reported.
Does anyone else has this problem?
Votes:
0
Did this occur in previous PRTG versions as well? Not really sure how the script copes with volumes that went missing. Say the volumes are down, is the mount point still listed in the output of
Get-WmiObject @WmiHash -Class Win32_MountPoint
...once it's down? What exactly do you mean with "when it comes up again, it is reported?
Votes:
0
Hi, thanks for this helpful script!
I can't monitor the free space, i gave me just free percentage. The Value returned for free % has to be divided by hundred, so i changed the code:
from
$Volumes | ForEach-Object { if ($MountPointData.ContainsKey($_.DeviceID)) { # Let's avoid dividing by zero, it's so disruptive. if ($_.Capacity) { $PercentFree = $_.FreeSpace*100/$_.Capacity } else { $PercentFree = 0 } $_ | Select-Object -Property DeviceID, Computer, Label, Caption, FileSystem, @{n='Size (GB)';e={$_.Capacity/1GB}}, @{n='Free space';e={$_.FreeSpace}}, @{n='Percent free';e={$PercentFree}} }
to
$Volumes | ForEach-Object { if ($MountPointData.ContainsKey($_.DeviceID)) { # Let's avoid dividing by zero, it's so disruptive. if ($_.Capacity) { $PercentFree = $_.FreeSpace/$_.Capacity } else { $PercentFree = 0 } $_ | Select-Object -Property DeviceID, Computer, Label, Caption, FileSystem, @{n='Size (GB)';e={$_.Capacity/1GB}}, @{n='Free space';e={$_.FreeSpace}}, @{n='Percent free';e={$PercentFree}} }
Why can't i see the free space in GB?
The System is HPE MSA2060 with HP DL360 Gen10 and Windows Server 2022 Datacenter. Hyper-V Failover Cluster is installed.
Created on May 4, 2023 8:24:27 AM
Last change on May 5, 2023 7:05:42 AM by
Felix Wiesneth [Paessler Support]
Votes:
0
@Jochen,
Do you get the values when you run the script manually?
Kind regards,
Sasa Ignjatovic, Tech Support Team
Add comment