Ever needed an automated
disk statistics report for your Windows domain? I did. I was able
to gather bits and pieces from around the Internet and create the script below.
It's designed for my
needs but can be easily edited to suit yours. The basic functionality is
to use WMI via PowerShell to poll servers, PCs, or both and grab disk usage for
any system within the targeting parameters and generate an easy to read HTML
report that is emailed to anyone needed.
Our environment is
comprised of both physical and virtual systems. I need to be able to spot
systems running low on disk space with expandable disks (virtual) that can be
quickly grown if needed. The output color codes C: drives below 60 GB in
red, that's my low threshold. It also color codes physical verses
virtual. You can select between a full report of every system, or a brief
option for only systems below threshold. You can also filter by server or
PC and turn console output on and off for debugging.
The script is internally
documented. It emails an HTML formatted report that shows each disk, its
size, how much is used, and how much is left.
I run it as a daily
scheduled job from a service account with domain admin rights.
I also have a variation
of this report that details the disk block size. Very handy when tuning
your virtual disks for use on a SAN. I'll publish that one later.
UPDATE: I altered the commandline arguments to be named.
Below is an example of the output as seen in email:
UPDATE: I altered the commandline arguments to be named.
Below is an example of the output as seen in email:
<#====================================================================
File Name : DomainDiskReport.ps1
Original Author : Kenneth C. Mazie
:
Description : This script uses WMI to poll the current domain and extract
: disk statistics. Output is gathered in HTML format and emailed
: to a list of recipients. Output can be all systems (full)
: or only systems that are below preset threshold (brief).
:
Arguments : Named commandline parameters: (all are optional)
: "-debug" - Displays console output during run. Switches email recipient.
: "-mode" - Can be set to "full" or "brief". Default to "brief".
: "-detail" - Can be "all", "pc", or "server". Defaults to "all" which polls both.
:
Notes : Allows setting a threshold of the lowest remaining space. Colors all
: others in red.
: Numerous parameters are adjustable from within the main function.
: !!! -- Best run from a scheduled job as a domain admin user -- !!!
:
Warnings : None
:
Legal : Public Domain. Modify and redistribute freely. No rights reserved.
: SCRIPT PROVIDED "AS IS" WITHOUT WARRANTIES OR GUARANTEES OF
: ANY KIND. USE AT YOUR OWN RISK. NO TECHNICAL SUPPORT PROVIDED.
:
Credits : Code snippets and/or ideas came from many sources around the web.
:
Last Update by : Kenneth C. Mazie (email kcmjr AT kcmjr.com for comments or to report bugs)
Version History : v1.0 - 05-03-14 - Original
Change History : v2.0 - 11-01-14 - Changed HTML formatting. Added commandline options.
: v3.0 - 12-10-14 - Changed input arguments to be named
:
====================================================================#>
File Name : DomainDiskReport.ps1
Original Author : Kenneth C. Mazie
:
Description : This script uses WMI to poll the current domain and extract
: disk statistics. Output is gathered in HTML format and emailed
: to a list of recipients. Output can be all systems (full)
: or only systems that are below preset threshold (brief).
:
Arguments : Named commandline parameters: (all are optional)
: "-debug" - Displays console output during run. Switches email recipient.
: "-mode" - Can be set to "full" or "brief". Default to "brief".
: "-detail" - Can be "all", "pc", or "server". Defaults to "all" which polls both.
:
Notes : Allows setting a threshold of the lowest remaining space. Colors all
: others in red.
: Numerous parameters are adjustable from within the main function.
: !!! -- Best run from a scheduled job as a domain admin user -- !!!
:
Warnings : None
:
Legal : Public Domain. Modify and redistribute freely. No rights reserved.
: SCRIPT PROVIDED "AS IS" WITHOUT WARRANTIES OR GUARANTEES OF
: ANY KIND. USE AT YOUR OWN RISK. NO TECHNICAL SUPPORT PROVIDED.
:
Credits : Code snippets and/or ideas came from many sources around the web.
:
Last Update by : Kenneth C. Mazie (email kcmjr AT kcmjr.com for comments or to report bugs)
Version History : v1.0 - 05-03-14 - Original
Change History : v2.0 - 11-01-14 - Changed HTML formatting. Added commandline options.
: v3.0 - 12-10-14 - Changed input arguments to be named
:
====================================================================#>
#--[ Set input variables ]--
Param (
[switch]$Debug = $False, #--[ Debug mode off unless specified ]--
[string]$Detail = "brief", #--[ Defaults to brief output ]--
[string]$Mode = "all" #--[ Defaults to
reporting both server and PC ]--
)
Clear-Host
new-variable -force -name startupVariables -value ( Get-Variable | % { $_.Name } )
$startupVariables = ""
$ErrorActionPreference = "silentlycontinue"
$Datetime = Get-Date -Format "MM-dd-yyyy_HH:mm"
$Global:eMailRecipient = "your full email here"
$Global:eMailRecipient = "your full email here"
$DebugEmailTarget = "your full email for debugging"
$Global:SMTPServer = "your-email-server" #--[ Email server
address ]-
if (!(Get-Module -Name ActiveDirectory)){Import-Module ActiveDirectory} #--[ Load the AD module if needed ]--
If ($Debug){$Global:eMailRecipient = $DebugEmailTarget} #--[ Destination email address during debug. ]--
Function Cleanup-Variables {
Get-Variable | Where-Object { $startupVariables -notcontains $_.Name } | % { Remove-Variable -Name "$($_.Name)" -Force -Scope "global" }
}
function Hide-Console { #--[ Stops
console from displaying ]--
$consolePtr =
[Console.Window]::GetConsoleWindow()
[void][Console.Window]::ShowWindow($consolePtr, 0)
}
hide-console
Function SendEmail {
$email = New-Object System.Net.Mail.MailMessage
$email.From = "DiskReport@$eMailDomain"
$email.IsBodyHtml = $True
$email.To.Add($Global:eMailRecipient)
$email.Subject = "PowerShell Daily Disk Status Report"
$email.Body = $Global:ReportBody
$smtp = new-object Net.Mail.SmtpClient($Global:SMTPServer)
$smtp.Send($email)
If ($Debug){Write-Host "`n`rEmail sent..."}
}
Function Process ($Global:Target){
$Flag = $False
$Size = "";$Freespace="";$PercentFree="";$SizeGB="";$FreeSpaceGB="";$Win32_Hardware = "";$Win32_OS = ""
$Global:Target=$Global:Target.Name
$PercentWarning = "10"
#--[ Issue warning if free disk space is below this % ]--
$TargetHash = @{"Target" = $Global:Target}
If(Test-Connection -ComputerName $Global:Target -count 1 -BufferSize 16 -ErrorAction SilentlyContinue ) {
#--[ Gather system data
]----------------------------------------------------------------------
$WMIJob = Get-WMIObject win32_logicaldisk -Filter "DriveType =
3" -ComputerName $Global:Target -AsJob
Wait-Job -ID $WMIJob.ID -Timeout 5 | Out-Null
$Disks = Receive-Job $WMIJob.ID -ErrorAction SilentlyContinue
#--------------------------------------------------------------------------------------------
$WMIJob = Get-WMIObject Win32_OperatingSystem -computer $Global:Target -AsJob
Wait-Job -ID $WMIJob.ID -Timeout 5 | Out-Null
$Win32_OS = Receive-Job $WMIJob.ID -ErrorAction SilentlyContinue | select Caption
$Win32_OS = ($Win32_OS.caption -replace("\(R\)","")).Replace("Microsoft
","")
$TargetHash.Add("OS",$Win32_OS)
#--------------------------------------------------------------------------------------------
$WMIJob = Get-WMIObject -ComputerName $Global:Target -class Win32_ComputerSystemProduct -AsJob
Wait-Job -ID $WMIJob.ID -Timeout 5 | Out-Null
$Win32_Hardware = Receive-Job $WMIJob.ID -ErrorAction SilentlyContinue
if(([string]::IsNullOrEmpty($Win32_Hardware.Name)) -or ($Win32_Hardware.Name -eq " ")){
$MFG = "Unknown"
}ElseIf ($Win32_Hardware.Name -eq "VMware
Virtual Platform"){
$MFG = "VMware Virtual"
}Else{
$MFG = $Win32_Hardware.Name.trimend()
}
$TargetHash.Add("MFG",$MFG)
#--------------------------------------------------------------------------------------------
$Count = 0
foreach($Disk in $Disks){
$DeviceID = $Disk.DeviceID;
[float]$Size = $Disk.Size;
[float]$Freespace = $Disk.FreeSpace;
[string]$PercentFree = [Math]::Round(($Freespace / $Size) * 100, 0);
[string]$SizeGB = [Math]::Round($Size / 1073741824, 0);
[string]$FreeSpaceGB = [Math]::Round($Freespace / 1073741824,
2);
$DriveHash = @{"Drive" = $DeviceID;"Size" = $SizeGB;"PercFree" = $PercentFree;"FreeSpace" = $FreeSpaceGB}
$TargetHash.Add($Count,$DriveHash)
$Count++
} #--[End - ForEach
disk ]--
#-----------------------------------------
START OF OUTPUT -----------------------------------------------------
If(($Global:Target -like "*whatever*") -and ($TargetHash.Item(0).Item("PercFree") -gt 10 )){ #--[ Bypass a name pattern ]--
If ($Debug){
Write-Host "`r`n--[
"
-ForegroundColor White -NoNewline
write-host $Global:Target.Toupper() -ForegroundColor Yellow -NoNewline
Write-Host "
]-------------------------------------------------------------------".PadRight((110-$Global:Target.length),"-") -ForegroundColor White
Write-Host $Global:Target " BYPASS
MATCH. c: %free " ($TargetHash.Item(0).Item("PercFree"))-ForegroundColor Magenta}
}else{
If ($Debug){
Write-Host "`r`n--[
"
-ForegroundColor White -NoNewline
write-host $Global:Target.Toupper() -ForegroundColor Yellow -NoNewline
Write-Host "
]-------------------------------------------------------------------".PadRight((110-$Global:Target.length),"-") -ForegroundColor White
}
$HtmlData += '<td><font
color="blue">' + $Global:Target + '</td>'
If([string]::IsNullOrEmpty($Win32_OS)){$Win32_OS = "Unknown"}
If (($Win32_OS -eq "Unknown") -and ($MFG -eq "Unknown")){
If ($Debug){Write-Host "Failed to
extract any information from target system..." -ForegroundColor cyan}
$HtmlData += '<td
colspan=2><center><font color="darkcyan">Failed to
extract any information from target
system.</center></td><td> </td><td> </td><td> </td><td> </td></tr>'
}Else{
If (($Win32_OS -like "*R2*") -or ($Win32_OS -like "*2012*") -or ($Win32_OS -like "*Windows
7*") -or ($Win32_OS -like "*Embedded*")){
If ($Debug){Write-Host "OS =
$Win32_OS".PadRight(50," ") -ForegroundColor Green -NoNewline }
$HtmlData += '<td><font
color="green">' + $Win32_OS + "</font></td>"
}Else{
#If([string]::IsNullOrEmpty($Win32_OS)){$Win32_OS
= "Unknown"}
If ($Debug){Write-Host "OS =
$Win32_OS".PadRight(50," ") -ForegroundColor Red -NoNewline }
$HtmlData += '<td><font
color="red">' + $Win32_OS + "</font></td>"
}
If($MFG -eq "VMware Virtual") #--[ Unique coloring for VM ]--
If ($Debug){Write-Host "Hardware =
$MFG" -ForegroundColor cyan}
$HtmlData += '<td><font
color="green">VMware Virtual</font></td>'
}Else{
If ($Debug){Write-Host "Hardware =
$MFG" -ForegroundColor Magenta}
$HtmlData += '<td><font
color="red">' + $MFG + '
</font></td>'
}
$Count = 0
While ($Count -lt $TargetHash.count-3 ){
#$TargetHash.Item($Count).Item("Drive") #--[ Extra items if needed ]--
#$TargetHash.Item($Count).Item("Size")
#$TargetHash.Item($Count).Item("FreeSpace")
#$TargetHash.Item($Count).Item("PercFree")
If ($Debug){Write-Host "Drive
=" $TargetHash.Item($Count).Item("Drive").PadRight(15," ") -NoNewline }
If ($Count -gt 0){
$HtmlData += "<td> </td><td> </td><td> </td><td>" + $TargetHash.Item($Count).Item("Drive") +"</td>"
}Else{
$HtmlData += "<td>" + $TargetHash.Item($Count).Item("Drive") + "</td>"
}
#--[ report if server C: drive is less
than 60 gb - #$TargetHash.Item($Count).Item("Size") ]--
If (($DeviceID -eq "C:") -and ([int]$TargetHash.Item($Count).Item("Size") -lt 59) -and ($Win32_OS -like "*Server*")){
If ($Debug){Write-Host "Size =
"($TargetHash.Item($Count).Item("Size").PadRight(20," ")) -NoNewline -ForegroundColor red}
$HtmlData += '<td><font
color="red">' + $TargetHash.Item($Count).Item("Size") + '</font></td>'
}Else{
If ($Debug){Write-Host "Size =
"($TargetHash.Item($Count).Item("Size").PadRight(20," ")) -NoNewline -ForegroundColor green}
$HtmlData += '<td><font
color="green">' + $TargetHash.Item($Count).Item("Size") + '</font></td>'
}
#--[
$TargetHash.Item($Count).Item("SpaceFree") ]--
If ($Debug){Write-Host "FreeSpace
=" $TargetHash.Item($Count).Item("FreeSpace").PadRight(20," ") -NoNewline }
$HtmlData += "<td>" + $TargetHash.Item($Count).Item("FreeSpace") + "</td>"
#--[
$TargetHash.Item($Count).Item("PercFree") ]--
If(([int]$TargetHash.Item($Count).Item("PercFree")) -lt ([int]$PercentWarning)){
$Flag = $True
If ($Debug){Write-Host "Percent
Free = "(($TargetHash.Item($Count).Item("PercFree").PadLeft(2,"0")) + " %").PadRight(10," ") -ForegroundColor Red} # -NoNewline }
$HtmlData += '<td><font
color="red">' + $TargetHash.Item($Count).Item("PercFree") + ' %</font></td></tr>'
}Else{
If ($Debug){Write-Host "Percent
Free = "(($TargetHash.Item($Count).Item("PercFree").PadLeft(2,"0")) + " %").PadRight(10," ") -ForegroundColor Green} # -NoNewline }
$HtmlData += '<td><font
color="green">' + $TargetHash.Item($Count).Item("PercFree") + ' %</font></td></tr>'
}
$Count++
}
}
}#------------------ END OF OUTPUT ----------------------
If (($Flag -and ($Detail -eq "brief")) -or ($Detail -ne "brief")){$Global:ReportBody += $HtmlData}
}Else{
If ($Debug){
Write-Host "`r`n--[
"
-ForegroundColor White -NoNewline
write-host $Global:Target.Toupper() -ForegroundColor Yellow -NoNewline
Write-Host "
]-------------------------------------------------------------------".PadRight((110-$Global:Target.length),"-") -ForegroundColor White
Write-Host "Failed to
sucessfully ping target system..." -ForegroundColor Cyan
}
If($Detail -ne "brief"){
$Global:ReportBody += '<tr><td><font
color="blue">' + $Global:Target + '</td><td colspan=2><center><font
color="darkcyan">Failed to successfully ping target
system.</center></td><td> </td><td> </td><td> </td><td> </td></tr>'
}
$Global:ReportBody += $HtmlData
} #--[ End - test-connection ]--
Return
}
#--[ End of Functions
]---------------------------------------------------------
$ExclusionList = @( #--[ Add systems
here that come up as false positives or need to be excluded ]--
"server1",
"server2",
"pc1"
)
#--[ Add header to html log file
]--
$Global:ReportBody = @() | Select
Target,Drive,SizeGB,FreeSpaceGB,PercentFree,Manufacturer
$Global:ReportBody += '<style
type="text/css">
table.myTable { border:5px solid
black;border-collapse:collapse; }
table.myTable td { border:2px
solid black;padding:5px;background: #E6E6E6 }, table.myTable th { border:2px
solid black;padding:5px;background: #A4A4A4 }
#table.bottomBorder {
border-collapse:collapse; }
#table.bottomBorder td,
table.bottomBorder th { border-bottom:1px dotted black;padding:5px; }
</style>
The following report displays
disks configured on every server and PC in the domain.
<ul>
<li>If the "brief" option was used only systems with
disks in need of attention are listed.</li>
<li>Operating systems that can be dynamically grown are shown in
green, all others in red.</li>
<li>Under the Hardware heading physical systems are noted in red
since the disks cannot be grown.</li>
<li>Any C: drives smaller than 60 GB are flagged in
red.</li>
<li>All other disks are red only if the remaining free disk space
is below 10%.</li>
<li>Systems that are not reachable for whatever reason are noted
as such.</li>'
If($Detail -eq "brief"){
$Global:ReportBody += '<li><strong>Note: Script was executed
with the "brief" option enabled.</strong></li><br><br></ul>'
}Else{
$Global:ReportBody += '</ul>'
}
$Global:ReportBody += 'Script
executed on '+$Datetime+'<br><br>'
If (($Mode -eq "server") -or ($Mode -eq "all")){
$Computers = Get-ADComputer -Filter {operatingsystem -like "*server*" -and name -notlike "*esx*"} | sort name
$Global:ReportBody += '<table
class="myTable"><tr><th>Target</th><th>Operating
System</th><th>Hardware</th><th>Drive</th><th>SizeGB</th><th>FreeSpaceGB</th><th>PercentFree</th></tr>'
ForEach($Global:Target in $Computers ){
If ($ExclusionList -notcontains $Global:Target.name){
Process $Global:Target
}
}
$Global:ReportBody += '</table><br><br>'
}
If (($Mode -eq "pc") -or ($Mode -eq "all")){
$Computers = Get-ADComputer -Filter {operatingsystem -notlike "*server*" -and name -notlike "*esx*"} | sort name
$Global:ReportBody += '<table
class="myTable"><tr><th>Target</th><th>Operating
System</th><th>Hardware</th><th>Drive</th><th>SizeGB</th><th>FreeSpaceGB</th><th>PercentFree</th></tr>'
ForEach($Global:Target in $Computers ){
If ($ExclusionList -notcontains $Global:Target.name){
Process $Global:Target
}
}
$Global:ReportBody += "</table>"
}
If ($Debug){$Global:ReportBody | Out-File c:\Scripts\temp.html}
SendEmail
#endregion
If ($Debug){Write-Host "---
COMPLETED ---" -ForegroundColor Red}
#Cleanup-Variables #--[ Uncomment to reset pre-run variables
]--