cancel
Showing results for 
Search instead for 
Did you mean: 
Create Post
Level 10

DISK SPACE UTILIZATION alert with LARGE FILES DETAILS

Hello IT folks,

Can we create a high disk space utilization alert which also give information that which top 5 -10 file or directories consuming the most space on that system drive ?

Can this be achieved by

- powershell script monitor in SAM,

- volume alert with external script execution on its action

- or is there any other way around ? how can we do this if go with above approaches ?

ref : Send disk usage reports on “disk full” alerts in SCOM – 4sysops

Cheers,

Anil Singh

0 Kudos
2 Replies
Level 12

Anil,

I have taken a stab at this but unfortunately, came up short.  Perhaps someone else with more experience can help us out?  Below is the script you referred to in the link.  I've added comments to the original script.  This script can be run on the Orion server without an issue locally or remotely using the syntax in the Comments.  Be sure to update the variables first.  I'd recommend saving this file in the same location on all your polling engines.

<# 

.SYNOPSIS 

    Identifies Largest $numberOfTopDirectories & $numberOfTopFiles for a designated volume and sends an e-mail

.DESCRIPTION 

    This script requires the volume argument to properly run.  Use .\Get-LargeDirectoriesandFiles.ps1 <Volume Letter>

    to run locally or run remotely use Invoke-Command -ComputerName <Insert single computer name here or use (Get-Content hosts.txt)>

    -FilePath <Insert File path to Get-LargeDirectoriesandFiles.ps1> -ArgumentList <Insert Drive letter to check> to run remotely.  Update

    $numberOfTopDirectories, $numberOfTopFiles, $emailTo, $smtpSrv, $emailFrom variables

.NOTES 

    File Name : Get-LargeDirectoriesandFiles.ps1 

    Author       : Ruben Zimmerman

    URL          : https://4sysops.com/archives/send-disk-usage-reports-on-disk-full-alerts-in-scom#create-the-diagnost...

    Requires   : PowerShell V2 CTP3

#>

param([string]$Arguments = $args[0])

$startDirectory         = $Arguments + ':\'

# Update Variables to receive e-mail from script

$numberOfTopDirectories = 5

$numberOfTopFiles       = 5

$emailTo                = '<Enter Your E-mail or Distribution List here>'

$smtpSrv                = '<Enter your SMTP Server here>'

$emailFrom              = 'diskSpaceDetail@<Enter your Domain here>'

#region Get-Metadata

$timeZone               =  ([TimeZoneInfo]::Local).Id

$scanDate               = Get-Date -Format 'yyyy-MM-dd hh:MM:ss'

$WindowsVersion         = Get-WmiObject -Class Win32_OperatingSystem | Select-Object -ExpandProperty Caption

try {

    $computerDescription  = Get-ItemProperty -Path HKLM:\SYSTEM\CurrentControlSet\services\LanmanServer\Parameters | Select-Object -ExpandProperty srvcomment

} catch {

    $computerDescription  = ''

}

try {

    $adSearcher           = New-Object System.DirectoryServices.DirectorySearcher

    $adSearcher.Filter    = "(&(objectCategory=computer)(cn=$env:computername))"

    $adComputer           = $adSearcher.FindOne()

    $adComputerProperties = $adComputer | Select-Object -ExpandProperty Properties

    $adComputerLdapPath   = $adComputerProperties.distinguishedname

} catch {

    $adComputerLdapPath   = 'Failed to extract AD Information.' + $_.StackTrace

}

$diskDetails    = Get-WMIObject -Namespace root/cimv2 -Class Win32_LogicalDisk | Where-Object {$_.DeviceID -match "$($Arguments)" } | Select-Object -Property Size, FreeSpace, VolumeName, DeviceID

$DriveLetter    = $diskDetails.DeviceID

$DriveName      = $diskDetails.VolumeName

$SizeInGB       = "{0:0}" -f ($diskDetails.Size/1GB)

$FreeSpaceInGB  = "{0:0}" -f ($diskDetails.FreeSpace/1GB)

$PercentFree    = "{0:0}" -f (($diskDetails.FreeSpace / $diskDetails.Size) * 100)

$diskMessage    = "<table><tr><td>$($DriveLetter)</td><td> ($($DriveName))</td></tr>"

$diskMessage   += "<tr><td>Total: $($SizeInGB) GB |</td><td>Free: $($FreeSpaceInGB) GB ($($PercentFree) %)</td></tr></table>"

#endregion Get-Metadata

Function Get-BigDirectories {

    param(

        [string]$startDirectory,

        [ref]$sortedDirectories

    )

    $bigDirList = New-Object -TypeName System.Collections.ArrayList

    if (Test-Path -Path $startDirectory) {

        & "$env:ComSpec" /c dir $startDirectory /-c /s | ForEach-Object {

                $null      = $_ -match 'Directory\s{1}of\s{1}(?<dirName>[\w:\\\s\.\-\(\)_#{}\$\%\+\[\]]{1,})'

                $dirName   = $Matches.dirName

                $null      = $_ -match '\s{1,}\d{1,}\sFile\(s\)\s{1,}(?<lengh>\d{1,})'

                $dirLength = $Matches.lengh

                if ($dirName -and $dirLength) {

                    $dirLength = [float]::Parse($dirLength)

                    $myFileHsh = @{'Name'=([string]$dirName)}

                    $myFileHsh.Add('Length',$dirLength)

                    $myFileObj = New-Object -TypeName PSObject -Property $myFileHsh

                    $null = $bigDirList.Add($myFileObj)

                }

                $dirName = ''

                $dirLength = 0

        } #END cmd /c dir C:\windows /-c /s | ForEach-Object

    } else {

        if ($startDirectory) {

            $dirName   = 'Error'

            $dirLength = 'No directory passed in.'

        } else  {

            $dirName   = $startDirectory

            $dirLength = $error.Message.ToString()

        }

        $dirLength = [float]::Parse($dirLength)

        $myFileHsh = @{'Name'=([string]$dirName)}

        $myFileHsh.Add('Length',$dirLength)

        $myFileObj = New-Object -TypeName PSObject -Property $myFileHsh

        $null = $bigDirList.Add($myFileObj)

    } #END if (Test-Path -Path $startDirectory)

    $sortedDirectories.Value = $bigDirList

} #End Function Get-BigDirectories

Function Convert-LengthToReadable {

    param(

        [System.Collections.ArrayList]$lengthList,

        [ref]$readableList

    )

    $allFiles = New-Object -TypeName System.Collections.ArrayList

    $lengthList | ForEach-Object {

        $sizeRaw  = $_.Length

        $sizeUnit = 'KB'

        $fileSize = 0

        if ($sizeRaw -gt 1kb -and $sizeRaw -lt 1mb ) {

            $fileSize = $sizeRaw / 1mb

            $sizeUnit = 'MB'

        } elseif ($sizeRaw -gt 1mb -and $sizeRaw -lt 1gb ) {

            $fileSize = $sizeRaw / 1mb

            $sizeUnit = 'MB'

        } elseif ($sizeRaw -gt 1gb -and $sizeRaw -lt 1tb ) {

            $fileSize = $sizeRaw / 1gb

            $sizeUnit = 'GB'

        } elseif ($sizeRaw -gt 1tb ) {

            $fileSize = $sizeRaw / 1tb

            $sizeUnit = 'TB'

        } else {

            $fileSize = $sizeRaw

            $sizeUnit = 'KB?'

        }

        $fileSize = [Math]::Round($fileSize,2)

        $myFileHsh = @{'Name'=([string]$_.Name)}

        $myFileHsh.Add('fileSize',([float]$fileSize))

        $myFileHsh.Add('sizeUnit',([string]$sizeUnit))

        $myFileHsh.Add('Length',([float]$sizeRaw))

        $myFileHsh.Add('LastWriteTime',$_.LastWriteTime)

        $myFileObj = New-Object -TypeName PSObject -Property $myFileHsh

        $null = $allFiles.Add($myFileObj)

    }

    $readableList.Value = $allFiles

} #End Function Convert-LengthToReadable

Function Send-TopDirectoryMailReport {

    param(

        [string]$diskMetaData,

        [string]$runInfo,

        [System.Array]$tDirectories,

        [System.Collections.Hashtable]$tDirsAndFiles,

        [System.Collections.Hashtable]$nDirsAndFiles

    )

    $directoryDetails   = ''

    $dirAndFilesDetails = ''

    $dirAndNewFilesDetails = ''

    foreach ($dirItem in $tDirectories) {

        $dirAndFilesDetails    += "<tr><td style=`"font-weight: bold; text-align:center`"><br /> $($dirItem.Name)</td></tr>"

        $dirAndNewFilesDetails += "<tr><td style=`"font-weight: bold; text-align:center`"><br /> $($dirItem.Name)</td></tr>"

        $directoryDetails      += "<tr><td>$($dirItem.Name)</td><td>$($dirItem.fileSize)</td><td>$($dirItem.sizeUnit)</td><tr>"

        $matchEntry = $tDirsAndFiles.($dirItem.Name)

        $matchEntry | ForEach-Object {

            $dirAndFilesDetails += "<tr><td>$($_.Name)</td><td>$($_.fileSize)</td><td>$($_.sizeUnit)</td><td>$($_.LastWriteTime)</td><tr>"

        }

        $matchEntry = $nDirsAndFiles.($dirItem.Name)

        $matchEntry | ForEach-Object {

            $dirAndNewFilesDetails += "<tr><td>$($_.Name)</td><td>$($_.fileSize)</td><td>$($_.sizeUnit)</td><td>$($_.LastWriteTime)</td><tr>"

        }

    } #End     foreach ($dirItem in $tDirectories)

    $htmlBegin   = "<!DOCTYPE html><html><head><title>DISK FULL - Troubleshooting Assistance on $($env:Computername) -  $($computerDescription)</title>"

    $htmlBegin  += "<h1><span style=`"background-color:#D3D3D3`">DISK FULL - Troubleshooting Assistance on $($env:Computername)</span></h1></head>"

    $htmlBegin  += "<h3><span style=`"background-color:#D3D3D3`"> $($computerDescription) </span></h3></head>"

    $htmlMiddle  = '<body style="color:#000000; font-size:12pt;"><p> </p><span style="color:#B22222; font-weight: bold; background-color:#D3D3D3; font-size:14pt;">Disk details:</span><br />' + $diskMetaData + '<p> </p>'

    $htmlMiddle += '<span style="color:#000080; font-weight: bold; background-color:#D3D3D3; font-size:14pt;">Largest Directories:</span><br /><br /> <table>' + $directoryDetails + '</table><p> </p>'

    $htmlMiddle += '<span style="color:#000080; font-weight: bold; background-color:#D3D3D3; font-size:14pt;">Newest files:</span><br /><table>' + $dirAndNewFilesDetails + '</table><p> </p>'

    $htmlMiddle += '<span style="color:#000080; font-weight: bold; background-color:#D3D3D3; font-size:14pt;">Largest files:</span><br /><table>' + $dirAndFilesDetails + '</table><p> </p>'

    $htmlMiddle += '<span style="color:#FF8C00; font-weight: bold; background-color:#D3D3D3; font-size:14pt;">Meta Information:<br /></span>' + $runInfo

    $htmlEnd     = '</body></html>'

    $htmlCode = $htmlBegin + $htmlMiddle + $htmlEnd

    $mailMessageParms = @{

        To          = $emailTo

        From        = $emailFrom

        Subject     = "DISK Full - Troubleshooting Assistance on $($env:computername) . $($computerDescription)"

        Body        = $htmlCode

        Smtpserver  = $smtpSrv

        ErrorAction = "SilentlyContinue"

        BodyAsHTML  = $true

    }

    Send-MailMessage @mailMessageParms

} #End Send-TopDirectoryMailReport

$startTime = Get-Date

$bigDirList = New-Object -TypeName System.Collections.ArrayList

Get-BigDirectories -startDirectory $startDirectory -sortedDirectories ([ref]$bigDirList)

$bigDirListReadable = New-Object -TypeName System.Collections.ArrayList

Convert-LengthToReadable -lengthList $bigDirList -readableList ([ref]$bigDirListReadable)

$topDirectories  = $bigDirListReadable | Sort-Object -Property Length -Descending | Select-Object -First $numberOfTopDirectories

$topDirsAndFiles = New-Object -TypeName System.Collections.Hashtable

$topDirsAndNewestFiles = New-Object -TypeName System.Collections.Hashtable

foreach ($tDirectory in $topDirectories) {

    $tDirName       = $tDirectory.Name

    $tmpFileList    = New-Object -TypeName System.Collections.ArrayList

    $tmpNewFileList = New-Object -TypeName System.Collections.ArrayList

    $newFilesInDir  = New-Object -TypeName System.Collections.ArrayList

    $filesInTDir    = Get-ChildItem -Path $tDirName | Where-Object { $_.PSIsContainer -eq $false } | Select-Object -Property DirectoryName, Name, LastWriteTime, Length

    $filesInTDir      | ForEach-Object {

        $null = $newFilesInDir.Add($_)

    }

    $filesInTDir    = $filesInTDir   | Sort-Object -Property Length        -Descending | Select-Object -First $numberOfTopFiles

    $newFilesInDir  = $newFilesInDir | Sort-Object -Property LastWriteTime -Descending | Select-Object -First $numberOfTopFiles

    $filesInTDir | Select-Object -Property DirectoryName, Name, LastWriteTime, Length | ForEach-Object {

        $null = $tmpFileList.Add($_)

    }

    $newFilesInDir | Select-Object -Property DirectoryName, Name, LastWriteTime, Length | ForEach-Object {

        $null = $tmpNewFileList.Add($_)

    }

    $bigFileList = New-Object -TypeName System.Collections.ArrayList

    Convert-LengthToReadable -lengthList $tmpFileList -readableList ([ref]$bigFileList)

    $newFileList = New-Object -TypeName System.Collections.ArrayList

    Convert-LengthToReadable -lengthList $tmpNewFileList -readableList ([ref]$newFileList)

    $topDirsAndFiles.Add($tDirName,$bigFileList)

    $topDirsAndNewestFiles.Add($tDirName,$newFileList)

} #End foreach ($tDirectory in $topDirectories)

$endTime      = Get-Date

$requiredTime = New-TimeSpan -Start $startTime -End $endTime

$reqHours     = [Math]::Round($requiredTime.TotalHours,2)

$reqMinutes   = [Math]::Round($requiredTime.TotalMinutes,2)

$reqSeconds   = [Math]::Round($requiredTime.TotalSeconds,2)

$metaInfo     = "<table><tr><td>Gathering details took:</td><td>$($reqHours) Hours / $($reqMinutes) Minutes / $($reqSeconds) Seconds.</td></tr>"

$metaInfo    += "<tr><td>Checking time:</td><td>$($scanDate), $($timeZone)</td></tr>"

$metaInfo    += "<tr><td>LDAP Path:</td><td>$($adComputerLdapPath)</td></tr>"

$metaInfo    += "<tr><td>Operating System:</td><td>$($WindowsVersion)</td></tr></table>"

$sendTopDirectoryMailReport = @{

    tDirectories  = $topDirectories

    tDirsAndFiles = $topDirsAndFiles

    nDirsAndFiles = $topDirsAndNewestFiles

    diskMetaData  = $diskMessage

    runInfo       = $metaInfo

}

Send-TopDirectoryMailReport @sendTopDirectoryMailReport

The next step is to get it to work with SolarWinds.  According to SolarWinds Support, in order to execute this as an Alert action, you'll need to create a BAT file that calls the .ps1 file.  Below is the BATCH file I've created.

::<#

::

::************************************************************

::

:: Author:         <R Primmer>

:: Script Name:    <Get-LargeDirectoriesandFiles.bat>

:: Script Purpose: <Calls like-named PS1 script which performs the work.>

::

:: Change Record:

:: Changed By, Changed On, Change Made

:: <R Primmer>, <20190508>, <Initial Creation>

::

:: General Information:

::

::*************************************************************

:: 

::#>

@echo on

set Arguments=%~1

set execpath=%~dp0

set execFname=%~n0

echo INFO: Calling %execpath%%execFname%.ps1

powershell -executionpolicy Bypass -Command {Invoke-Command -FilePath %execpath%%execFname%.ps1 -ArgumentList %Arguments% -ComputerName '${N=SwisEntity;M=Node.Caption}'}

echo INFO: END %0

The BAT file identifies the Computer Name using the '${N=SwisEntity;M=Node.Caption}' variable and the '%Arguments%' section permits you to include the <Volume Letter> when the batch file is run.  Name it the same as your .ps1 file name and make sure both files are in the same directory.

The next step is to create an alert to notify you when a Volume is over capacity.  As a Trigger Action (you may want others as well), add 'Execute an External Program' action and add the following Network path to external program.

<Directory location of .PS1 & .BAT file on your Primary/Additional Polling Engine>\Get-LargeDirectoriesandFiles.bat '${SQL: Select substring(caption,1,1) from volumes where volumetype in ('Fixed Disk' ,'Compact Disk') and caption not like '/%'}'

I'm using a SQL variable to append the Volume letter to the end and the host name is identified within the BATCH file.  When I run this, I get Action executed successfully but I don't get an e-mail.  Hoping someone can help us get this working as I can see value in it as well.  Thanks in advance!

Cheers,

Robert

0 Kudos

I've made some progress on this and updated the BATCH file to accept two arguments.  The first argument is the drive letter of the system and the second argument is the device name to run the script against.

::<#

::

::************************************************************

::

:: Author:         <R Primmer>

:: Script Name:    <Get-LargeDirectoriesandFiles.bat>

:: Script Purpose: <Calls like-named PS1 script which performs the work.>

::

:: Change Record:

:: Changed By, Changed On, Change Made

:: <R Primmer>, <20190508>, <Initial Creation>

::

:: General Information:

::

::*************************************************************

:: 

::#>

@echo on

set Arguments=%1

set Arguments2=%2

set execpath=%~dp0

set execFname=%~n0

echo INFO: Calling %execpath%%execFname%.ps1

powershell -command "Start-Process -verb runas powershell" "'invoke-command -File "%execpath%%execFname%.ps1" -ArgumentList %Arguments% -ComputerName %Arguments2%'"

echo INFO: END %0

This works fine on the orion server if you provide the two arguments when running the BATCH from an elevated command prompt.  This issue I'm having is running it from within SolarWinds when adding 'Execute an External Program' action and adding the following command line to Network path to external program.  I don't get any errors when running it but I don't get an e-mail for the system either and the ActionsExecution.log is blank.   tdanner - Any help would be appreciated.

<Directory location of .PS1 & .BAT file on your Primary/Additional Polling Engine>\Get-LargeDirectoriesandFiles.bat '${SQL: Select substring(caption,1,1) from volumes where volumetype in ('Fixed Disk' ,'Compact Disk') and caption not like '/%'}' ${N=SwisEntity;M=Node.Caption}

0 Kudos