Comprehensive Guide to Monitoring Hyper-V Shared Disk Space with PowerShell

Effective disk space management is a critical aspect of maintaining an efficient and reliable IT infrastructure. One powerful tool in the IT professional’s arsenal is PowerShell, a task automation framework from Microsoft. Today, we’ll delve into a PowerShell script designed to monitor the free space on shared volumes in a Hyper-V cluster environment. This script is especially valuable for IT professionals and Managed Service Providers (MSPs) who need to ensure their systems operate smoothly and avoid downtime due to storage issues.

Background

Monitoring disk space is crucial to prevent unexpected outages and ensure that applications and services run without interruption. Shared volumes in a Hyper-V cluster can house numerous virtual machines, and running out of space can have severe consequences. This PowerShell script simplifies the monitoring process, allowing administrators to keep an eye on free space and take action before problems arise.

The Script:

Requires -Version 5.1

<#
.SYNOPSIS
    Hyper-V Monitor shared volume disk free space. Must be ran as a Local or Domain Admin user.
.DESCRIPTION
    Hyper-V Monitor shared volume disk free space. Must be ran as a Local or Domain Admin user.

.EXAMPLE
    (No Parameters)
    ## EXAMPLE OUTPUT WITHOUT PARAMS ##
Name                       Path                  Size(GB) FreeSpace(GB) UsedSpace(GB) PercentFree
----                       ----                  -------- ------------- ------------- -----------
Cluster Virtual Disk (vd1) C:\ClusterStorage\vd1 3,068.98 168.77        2900.21       9.99

PARAMETER: -MinimumPercentage 20
    Only errors when any shared volume disk is below the specified percentage.
    Defaults to 10 percent.
.EXAMPLE
    -MinimumPercentage 20
    ## EXAMPLE OUTPUT WITH MinimumPercentage ##
Name                       Path                  Size(GB) FreeSpace(GB) UsedSpace(GB) PercentFree
----                       ----                  -------- ------------- ------------- -----------
Cluster Virtual Disk (vd1) C:\ClusterStorage\vd1 3,068.98 168.77        2900.21       9.99

PARAMETER: -MinimumFreeBytes 1073741824
    Only errors when any shared volume disk is below the specified percentage.
    Defaults to 1GB or 1073741824 bytes.
.EXAMPLE
    -MinimumFreeBytes 1073741824
    ## EXAMPLE OUTPUT WITH MinimumFreeBytes ##
Name                       Path                  Size(GB) FreeSpace(GB) UsedSpace(GB) PercentFree
----                       ----                  -------- ------------- ------------- -----------
Cluster Virtual Disk (vd1) C:\ClusterStorage\vd1 3,068.98 168.77        2900.21       9.99

PARAMETER: -ExcludeDrivesByName MyDisk
    Excludes drives that contains the text MyDisk in its name.
.EXAMPLE
    -ExcludeDrivesByName 1073741824
    ## EXAMPLE OUTPUT WITH ExcludeDrivesByName ##
Name                       Path                  Size(GB) FreeSpace(GB) UsedSpace(GB) PercentFree
----                       ----                  -------- ------------- ------------- -----------
Cluster Virtual Disk (vd1) C:\ClusterStorage\vd1 3,068.98 168.77        2900.21       9.99

PARAMETER: -ExcludeDrivesByPath C:\ClusterStorage\MyDisk
    Excludes drives that contains the text MyDisk in its name.
.EXAMPLE
    -ExcludeDrivesByPath C:\ClusterStorage\MyDisk
.EXAMPLE
    -ExcludeDrivesByPath MyDisk
    ## EXAMPLE OUTPUT WITH ExcludeDrivesByPath ##
Name                       Path                  Size(GB) FreeSpace(GB) UsedSpace(GB) PercentFree
----                       ----                  -------- ------------- ------------- -----------
Cluster Virtual Disk (vd1) C:\ClusterStorage\vd1 3,068.98 168.77        2900.21       9.99

PARAMETER: -CustomFieldParam "ReplaceMeWithAnyMultilineCustomField"
    Saves the results to a multi-line string custom field.
.EXAMPLE
    -CustomFieldParam "ReplaceMeWithAnyMultilineCustomField"
    ## EXAMPLE OUTPUT WITH CustomFieldParam ##
Name                       Path                  Size(GB) FreeSpace(GB) UsedSpace(GB) PercentFree
----                       ----                  -------- ------------- ------------- -----------
Cluster Virtual Disk (vd1) C:\ClusterStorage\vd1 3,068.98 168.77        2900.21       9.99
.OUTPUTS
    None
.NOTES
    Minimum OS Architecture Supported: Windows Server 2016
    Release Notes: Initial Release
By using this script, you indicate your acceptance of the following legal terms as well as our Terms of Use at https://www.ninjaone.com/terms-of-use.
    Ownership Rights: NinjaOne owns and will continue to own all right, title, and interest in and to the script (including the copyright). NinjaOne is giving you a limited license to use the script in accordance with these legal terms. 
    Use Limitation: You may only use the script for your legitimate personal or internal business purposes, and you may not share the script with another party. 
    Republication Prohibition: Under no circumstances are you permitted to re-publish the script in any script library or website belonging to or under the control of any other software provider. 
    Warranty Disclaimer: The script is provided “as is” and “as available”, without warranty of any kind. NinjaOne makes no promise or guarantee that the script will be free from defects or that it will meet your specific needs or expectations. 
    Assumption of Risk: Your use of the script is at your own risk. You acknowledge that there are certain inherent risks in using the script, and you understand and assume each of those risks. 
    Waiver and Release: You will not hold NinjaOne responsible for any adverse or unintended consequences resulting from your use of the script, and you waive any legal or equitable rights or remedies you may have against NinjaOne relating to your use of the script. 
    EULA: If you are a NinjaOne customer, your use of the script is subject to the End User License Agreement applicable to you (EULA).
#>

[CmdletBinding()]
param (
    [int]$MinimumPercentage = 10,
    $MinimumFreeBytes = 1GB,
    [String]$ExcludeDrivesByName,
    [String]$ExcludeDrivesByPath,
    [string]$CustomFieldParam
)

begin {
    function Test-IsElevated {
        $id = [System.Security.Principal.WindowsIdentity]::GetCurrent()
        $p = New-Object System.Security.Principal.WindowsPrincipal($id)
        $p.IsInRole([System.Security.Principal.WindowsBuiltInRole]::Administrator)
    }
    function Test-IsSystem {
        $id = [System.Security.Principal.WindowsIdentity]::GetCurrent()
        return $id.Name -like "NT AUTHORITY*" -or $id.IsSystem
    }
    function Get-FriendlySize {
        param($Bytes)
        # Converts Bytes to the highest matching unit
        $Sizes = 'Bytes,KB,MB,GB,TB,PB,EB,ZB' -split ','
        for ($i = 0; ($Bytes -ge 1kb) -and ($i -lt $Sizes.Count); $i++) { $Bytes /= 1kb }
        $N = 2
        if ($i -eq 0) { $N = 0 }
        if ($Bytes) { "$([System.Math]::Round($Bytes,$N)) $($Sizes[$i])" }else { "0 B" }
    }
    function Get-Size {
        param (
            [string]$String,
            [ValidateSet("PB", "TB", "GB", "MB", "KB", "B", "Bytes")][string]$DefaultSize = "GB"
        )
        switch -wildcard ($String) {
            '*PB' { [int64]$($String -replace '[^\d+]+') * 1PB; break }
            '*TB' { [int64]$($String -replace '[^\d+]+') * 1TB; break }
            '*GB' { [int64]$($String -replace '[^\d+]+') * 1GB; break }
            '*MB' { [int64]$($String -replace '[^\d+]+') * 1MB; break }
            '*KB' { [int64]$($String -replace '[^\d+]+') * 1KB; break }
            '*B' { [int64]$($String -replace '[^\d+]+') * 1; break }
            '*Bytes' { [int64]$($String -replace '[^\d+]+') * 1; break }
            Default { Get-Size -String "$String $DefaultSize" }
        }
    }
    function Invoke-FilterDisks {
        [CmdletBinding()]
        param(
            [parameter(ValueFromPipeline = $true)]
            $Disks
        )
        process {
            $Disks | ForEach-Object {
                # Check if exclude by name is needed
                if ($([string]::IsNullOrEmpty($ExcludeDrivesByName) -or [string]::IsNullOrWhiteSpace($ExcludeDrivesByName))) {
                    $_
                }
                else {
                    if (
                        $_.Name -like "*$ExcludeDrivesByName*"
                    ) {
                        # Output Nothing
                    }
                    else {
                        $_
                    }
                }
            } | ForEach-Object {
                # Check if exclude by name is needed
                if ($([string]::IsNullOrEmpty($ExcludeDrivesByPath) -or [string]::IsNullOrWhiteSpace($ExcludeDrivesByPath))) {
                    $_
                }
                else {
                    if (
                        $_.Path -like "*$ExcludeDrivesByPath*"
                    ) {
                        # Output Nothing
                    }
                    else {
                        $_
                    }
                }
            }
        }
    }
    if ($env:MinimumPercentage) {
        $MinimumPercentage = $env:MinimumPercentage
    }
    if ($env:minimumFreeSpace) {
        $MinimumFreeBytes = Get-Size -String $env:minimumFreeSpace
    }
    if ($env:ExcludeDrivesByName) {
        $ExcludeDrivesByName = $env:ExcludeDrivesByName
    }
    if ($env:ExcludeDrivesByPath) {
        $ExcludeDrivesByPath = $env:ExcludeDrivesByPath
    }
    if ($env:CustomFieldParam) {
        $CustomFieldParam = $env:CustomFieldParam
    }
}
process {
    if (Test-IsSystem) {
        Write-Error -Message "Access Denied. Please run with a Domain Account or a Local Account that has permissions to access this node."
        exit 1
    }

    if (-not (Test-IsElevated)) {
        Write-Error -Message "Access Denied. Please run with Administrator privileges."
        exit 1
    }

    Import-Module FailoverClusters -ErrorAction SilentlyContinue

    if (-not $(Get-Command -Name "Get-Cluster" -ErrorAction SilentlyContinue)) {
        Write-Error "[Error] Must run this script on a server that is apart of a Cluster or can communicate with a Cluster."
        exit 1
    }

    try {
        Get-ClusterNode -ErrorAction Stop | Out-Null
    }
    catch {
        Write-Error "[Error] Failed to get Cluster Nodes."
        exit 1
    }

    # Get Cluster Shared Volume Info
    $Volumes = foreach ( $csv in $(Get-ClusterSharedVolume) ) {
        foreach ( $csvinfo in $($csv | Select-Object -Property Name -ExpandProperty SharedVolumeInfo) ) {
            [PSCustomObject]@{
                Name        = $csv.Name
                Path        = $csvinfo.FriendlyVolumeName
                Size        = $csvinfo.Partition.Size
                FreeSpace   = $csvinfo.Partition.FreeSpace
                UsedSpace   = $csvinfo.Partition.UsedSpace
                PercentFree = $csvinfo.Partition.PercentFree
            }
        }
    }

    # Prep Format-Table substitutions
    $Size = @{ Label = "Size(GB)" ; Expression = { (Get-FriendlySize -Bytes $_.Size) } }
    $FreeSpace = @{ Label = "FreeSpace(GB)" ; Expression = { (Get-FriendlySize -Bytes $_.FreeSpace) } }
    $UsedSpace = @{ Label = "UsedSpace(GB)" ; Expression = { (Get-FriendlySize -Bytes $_.UsedSpace) } }
    $PercentFree = @{ Label = "PercentFree" ; Expression = { ($_.PercentFree) } }
    # Sort disks by FreeSpace
    $Disks = $Volumes | Sort-Object FreeSpace
    # Save results as a string
    $DisksFormattedString = $Disks | Format-Table -AutoSize Name, Path, $Size, $FreeSpace, $UsedSpace, $PercentFree | Out-String

    # If using a custom field sent that to the specified custom field, should be a multi-line
    if ($CustomFieldParam) {
        Ninja-Property-Set -Name $CustomFieldParam -Value $DisksFormattedString
    }

    # Loop through each disk
    $DiskUnderPercentage = $Disks | Invoke-FilterDisks | Where-Object { $_.PercentFree -lt $MinimumPercentage }
    $DiskUnderFreeBytes = $Disks | Invoke-FilterDisks | Where-Object { $_.FreeSpace -lt $MinimumFreeBytes }

    if ($DiskUnderPercentage -or $DiskUnderFreeBytes) {
        if ($DiskUnderPercentage) {
            Write-Host "[Issue] One or more Disks under $MinimumPercentage % free!"
        }
        if ($DiskUnderFreeBytes) {
            Write-Host "[Issue] One or more Disks under $(Get-FriendlySize -Bytes $MinimumFreeBytes) free!"
        }
        $DisksFormattedString | Write-Host
        exit 1
    }

    # List all shared volumes
    if (-not $DiskUnderPercentage) {
        Write-Host "[Info] One or more Disks over $MinimumPercentage % free."
    }
    if (-not $DiskUnderFreeBytes) {
        Write-Host "[Info] One or more Disks over $(Get-FriendlySize -Bytes $MinimumFreeBytes) free."
    }
    $DisksFormattedString | Write-Host
    exit 0
}
end {
    
    
    
}

 

Access over 300+ scripts in the NinjaOne Dojo

Get Access

Detailed Breakdown

Let’s break down the script step-by-step to understand its functionality and how it can be used effectively.

1. Prerequisites and Initial Setup The script requires PowerShell version 5.1 and must be run with administrative privileges. It starts by defining various parameters such as MinimumPercentage, MinimumFreeBytes, ExcludeDrivesByName, ExcludeDrivesByPath, and CustomFieldParam.

2. Functions for Privilege Checks and Size Calculations

  • Test-IsElevated checks if the script is running with administrator privileges.
  • Test-IsSystem ensures the script is not running as a system account.
  • Get-FriendlySize and Get-Size convert byte values to more readable units like KB, MB, GB, etc.

3. Filtering and Environment Variables The script sets up functions to filter out drives based on specified names or paths. It also checks for environment variables that might override the script’s parameters.

4. Cluster and Volume Information Retrieval The script imports the FailoverClusters module and verifies that it is running on a server that is part of a cluster. It then retrieves information about the shared volumes in the cluster, including their size, free space, used space, and percentage of free space.

5. Formatting and Output The script formats the disk information for readability and checks if any volumes are below the specified free space thresholds. If issues are detected, it outputs the details and exits with an error status. Otherwise, it confirms that all volumes have sufficient free space.

Potential Use Cases

Imagine an IT administrator responsible for a Hyper-V cluster hosting critical virtual machines. This administrator can use the script to automate the monitoring of disk space, ensuring they are alerted before any volumes run critically low on space. This proactive approach allows them to allocate additional storage or clean up unnecessary files, preventing potential outages and maintaining system performance.

Comparisons

Compared to manual monitoring or using basic built-in tools, this script provides a more automated and detailed approach. It not only checks for free space but also allows for customization based on volume names, paths, and specific free space requirements. Other methods, such as third-party monitoring tools, can offer similar functionality but often come at a higher cost and require additional setup.

FAQs

Q: Can this script be run on any version of Windows Server?

A: The script supports Windows Server 2016 and later versions due to the requirements of the FailoverClusters module.

Q: What permissions are needed to run this script?

A: The script must be run with local or domain administrator privileges to access the necessary cluster information.

Q: How can I exclude specific volumes from monitoring?

A: You can use the ExcludeDrivesByName or ExcludeDrivesByPath parameters to filter out specific volumes based on their names or paths.

Implications

The results of this script can significantly impact IT operations. By identifying volumes with insufficient free space, administrators can take preventive measures to avoid service disruptions. This proactive monitoring also enhances overall system reliability and performance, contributing to a more stable IT environment.

Recommendations

When using this script, consider the following best practices:

  • Schedule the script to run at regular intervals using Task Scheduler or similar tools.
  • Customize the parameters based on your environment’s specific needs.
  • Ensure that you have sufficient permissions and are running the script on appropriate servers.

Final Thoughts

PowerShell scripts like this one are invaluable tools for IT professionals and MSPs, offering powerful automation capabilities to manage and monitor critical systems. By incorporating such scripts into their workflows, administrators can maintain better control over their infrastructure, ensure optimal performance, and prevent potential issues before they escalate.

NinjaOne, a leading IT operations software, can further enhance this process by integrating script execution with its monitoring and management capabilities, providing a comprehensive solution for IT infrastructure management.

Next Steps

Building an efficient and effective IT team requires a centralized solution that acts as your core service deliver tool. NinjaOne enables IT teams to monitor, manage, secure, and support all their devices, wherever they are, without the need for complex on-premises infrastructure.

Learn more about NinjaOne Remote Script Deployment, check out a live tour, or start your free trial of the NinjaOne platform.

Categories:

You might also like

Watch Demo×
×

See NinjaOne in action!

By submitting this form, I accept NinjaOne's privacy policy.

NinjaOne Terms & Conditions

By clicking the “I Accept” button below, you indicate your acceptance of the following legal terms as well as our Terms of Use:

  • Ownership Rights: NinjaOne owns and will continue to own all right, title, and interest in and to the script (including the copyright). NinjaOne is giving you a limited license to use the script in accordance with these legal terms.
  • Use Limitation: You may only use the script for your legitimate personal or internal business purposes, and you may not share the script with another party.
  • Republication Prohibition: Under no circumstances are you permitted to re-publish the script in any script library belonging to or under the control of any other software provider.
  • Warranty Disclaimer: The script is provided “as is” and “as available”, without warranty of any kind. NinjaOne makes no promise or guarantee that the script will be free from defects or that it will meet your specific needs or expectations.
  • Assumption of Risk: Your use of the script is at your own risk. You acknowledge that there are certain inherent risks in using the script, and you understand and assume each of those risks.
  • Waiver and Release: You will not hold NinjaOne responsible for any adverse or unintended consequences resulting from your use of the script, and you waive any legal or equitable rights or remedies you may have against NinjaOne relating to your use of the script.
  • EULA: If you are a NinjaOne customer, your use of the script is subject to the End User License Agreement applicable to you (EULA).