-
-
Save jdhitsolutions/d23086d1365bd332ffef96c5ef2de9dd to your computer and use it in GitHub Desktop.
#requires -version 3.0 | |
Function New-HTMLDiskReport { | |
<# | |
.Synopsis | |
Create a disk utilization report with gradient coloring | |
.Description | |
This command will create an HTML report depicting drive utilization through a gradient color bar. | |
.Parameter Computername | |
The name(s) of computers to query. They must be running PowerShell 3.0 or later and support CIM queries. | |
This parameter has an alias of CN. | |
.Parameter ReportTitle | |
The HTML title to be for your report. This parameter has an alias of Title. | |
.Parameter Path | |
The filename and path for your html report. | |
.Parameter PreContent | |
Add any HTML text to insert before the drive utilization table. | |
.Parameter PostContent | |
Add any HTML text to insert after the drive utilization table. | |
.Parameter LogoPath | |
Specify the path to a PNG or JPG file to use as a logo image. The image will be embedded into the html file. | |
.Example | |
PS C:\> New-HTMLDiskReport -passthru | |
Directory: C:\Users\Jeff\AppData\Local\Temp | |
Mode LastWriteTime Length Name | |
---- ------------- ------ ---- | |
-a---- 10/10/2018 3:58 PM 2493 utilization.htm | |
Create a report for the local host using default settings. | |
.Example | |
PS C:\> get-content c:\work\computers.txt | New-HTMLDiskReport -path c:\work\diskreport.htm | |
Create a single report for every computer listed in computers.txt. The report will be saved to c:\work\diskreport.htm | |
.Example | |
PS C:\> New-HTMLDiskReport -Path c:\work\report.htm -Computername SRV1,SRV2,SRV3 -Precontent "<h3>Company Confidential</h3>" -PostContent "This report is offered as-is. You can verify results with a command like <b>Get-Volume</b>." -logopath c:\scripts\logo.png | |
Create a report for the specified servers and insert pre- and post-content. | |
.Notes | |
Learn more about PowerShell: http://jdhitsolutions.com/blog/essential-powershell-resources/ | |
.Link | |
Get-CimInstance | |
.Link | |
Get-Volume | |
.Link | |
Get-Disk | |
.Inputs | |
System.String | |
#> | |
[cmdletbinding(SupportsShouldProcess)] | |
[OutputType("None", "System.IO.FileInfo")] | |
Param( | |
[Parameter(Position = 0, ValueFromPipeline, ValueFromPipelineByPropertyName)] | |
[ValidateNotNullorEmpty()] | |
[Alias("cn")] | |
[string[]]$Computername = $env:Computername, | |
[ValidateNotNullorEmpty()] | |
[Parameter(HelpMessage = "The report title")] | |
[Alias("title")] | |
[string]$ReportTitle = "Drive Utilization Report", | |
[Parameter(HelpMessage = "The filename and path for the finished HTML report.")] | |
[ValidateNotNullorEmpty()] | |
[ValidateScript( { | |
#get parent | |
$parent = Split-Path $_ | |
if (Test-Path $parent) { | |
$True | |
} | |
else { | |
Throw "Can't verify part of the file path $_" | |
} | |
})] | |
[string]$Path = "$env:temp\utilization.htm", | |
[string[]]$PreContent, | |
[string[]]$PostContent, | |
[string]$LogoPath, | |
[switch]$Passthru | |
) | |
Begin { | |
Write-Verbose "Starting $($MyInvocation.Mycommand)" | |
#define HTML header with style elements. If using a header the title must be inserted here. | |
#here strings must be left justified | |
$head = @" | |
<style> | |
body { | |
background-color: #FFFFFF; | |
font-family: Tahoma; | |
font-size: 12pt; | |
} | |
td, | |
th { | |
border: 0px solid black; | |
border-collapse: collapse; | |
} | |
th { | |
color: white; | |
background-color: black; | |
} | |
table, | |
tr, | |
td, | |
th { | |
padding: 2px; | |
margin: 0px; | |
} | |
tr:nth-child(even) { | |
background-color: lightgray | |
} | |
table { | |
width: 95%; | |
margin-left: 10px; | |
margin-bottom: 20px; | |
table-layout: fixed; | |
} | |
.meta { | |
width: 25%; | |
font-size: 8pt; | |
margin-left: 0px; | |
table-layout: auto; | |
} | |
.top { | |
width: 50%; | |
margin-left: 0px; | |
table-layout: auto; | |
} | |
tr.meta { | |
background-color: #FFFFFF; | |
} | |
.right { | |
text-align: right; | |
width: 20%; | |
} | |
caption { | |
background-color: #FFFF66; | |
text-align: left; | |
font-weight: bold; | |
font-size: 14pt; | |
} | |
td[tip]:hover { | |
color: #ff2283; | |
position: relative; | |
} | |
td[tip]:hover:after { | |
content: attr(tip); | |
left: 0; | |
top: 100%; | |
margin-left: 80px; | |
margin-top: 10px; | |
width: 400px; | |
padding: 3px 8px; | |
position: absolute; | |
color: #85003a; | |
font-family: 'Courier New', Courier, monospace; | |
font-size: 10pt; | |
background-color: gainsboro; | |
white-space: pre-wrap; | |
} | |
</style> | |
<Title>$reportTitle</Title> | |
"@ | |
<# | |
Define a here string for coloring percentage cells. | |
The starting and ending percents will need to provided | |
using the -f operator. | |
#> | |
$gradient = @" | |
filter:progid:DXImageTransform.Microsoft.Gradient(GradientType=0, | |
StartColorStr=#0A802D, EndColorStr=#FF0011) | |
background-color: #376C46; | |
background-image: -mso-linear-gradient(left, #0A802D {0}%, #FF0011 {1}%); | |
background-image: -ms-linear-gradient(left, #0A802D {0}%, #FF0011 {1}%); | |
background-image: -moz-linear-gradient(left, #0A802D {0}%, #FF0011 {1}%); | |
background-image: -o-linear-gradient(left, #0A802D {0}%, #FF0011 {1}%); | |
background-image: -webkit-linear-gradient(left, #0A802D {0}%, #FF0011 {1}%); | |
background-image: linear-gradient(left, #0A802D {0}%, #FF0011 {1}%); | |
color:white; | |
"@ | |
If ($LogoPath) { | |
if (Test-Path $LogoPath) { | |
#insert a graphic | |
$ImageBits = [Convert]::ToBase64String((Get-Content $LogoPath -Encoding Byte)) | |
$ImageFile = Get-Item $LogoPath | |
$ImageType = $ImageFile.Extension.Substring(1) #strip off the leading . | |
$ImageTag = "<Img src='data:image/$ImageType;base64,$($ImageBits)' Alt='$($ImageFile.Name)' style='float:left' width='120' height='120' hspace=10>" | |
} | |
else { | |
Write-Warning "Could not find image file $LogoPath" | |
} | |
} | |
if ($ImageTag) { | |
$top = "<table class='top'><tr><td>$ImageTag</td><td><H1>$ReportTitle</H1></td></table><br>" | |
} | |
else { | |
$top = "<H1>$ReportTitle</H1><br>" | |
} | |
#define an array to hold HTML fragments | |
$fragments = @($top) | |
$fragments += $Precontent | |
$fragments += "<br><br>" | |
#define a parameter hashtable for Write-Progress | |
$progParam = @{ | |
Activity = $MyInvocation.MyCommand | |
Status = "Gathering disk data" | |
CurrentOperation = "" | |
} | |
} #begin | |
Process { | |
#get the data for the report | |
foreach ($computer in $computername) { | |
Write-Verbose "Getting disk information for $Computer" | |
$progParam.CurrentOperation = $computer.toUpper() | |
Write-Progress @progParam | |
Try { | |
#create a temporary CIMSession | |
$cs = New-CimSession -ComputerName $computer -ErrorAction stop | |
#hashtable of parameters to splat to Get-Ciminstance | |
$paramHash = @{ | |
Classname = "win32_logicaldisk" | |
filter = "drivetype=3" | |
CimSession = $cs | |
ErrorAction = "Stop" | |
} | |
if ($pscmdlet.ShouldProcess($Computer, "Get Disk Information")) { | |
$data = Get-CimInstance @paramHash | |
Write-Verbose "Formatting data" | |
#initialize a hashtable of for phsyical media | |
$hash = @{} | |
#Create a custom object for each drive | |
$drives = foreach ($item in $data) { | |
$Physical = $item | Get-CimAssociatedInstance -ResultClassName Win32_DiskPartition | Get-CimAssociatedInstance -ResultClassName Win32_DiskDrive | |
$hash.Add($item.DeviceID,$physical) | |
$prophash = [ordered]@{ | |
Drive = $item.DeviceID | |
Volume = $item.VolumeName | |
SizeGB = $item.size / 1GB -as [int] | |
FreeGB = "{0:N4}" -f ($item.Freespace / 1GB) | |
PercentFree = [math]::Round(($item.Freespace / $item.size) * 100, 2) | |
} | |
New-Object PSObject -Property $prophash | |
} #foreach item | |
#convert drive objects to HTML but as an XML document | |
Write-Verbose "Converting to XML" | |
[xml]$html = $drives | ConvertTo-Html -Fragment | |
#add the computer name as the table caption | |
$caption = $html.CreateElement("caption") | |
$html.table.AppendChild($caption) | Out-Null | |
$html.table.caption = $data[0].SystemName | |
$pop = $html.CreateAttribute("title") | |
$pop.value = (Get-Ciminstance -ClassName Win32_OperatingSystem -Property caption -cimsession $cs).caption | |
$html.table.item("caption").attributes.append($pop) | Out-Null | |
#add physical media as a popup for each device | |
for ($i=1; $i -le $html.table.tr.count -1;$i++) { | |
$id = $html.table.tr[$i].ChildNodes[0]."#text" | |
$pop = $html.CreateAttribute("tip") | |
$props = ($hash.Item($id) | Select-Object -property Caption,SerialNumber,FirmwareRevision,Size,InterfaceType,SCSI* | Out-String).trim() | |
$pop.Value = $props | |
$html.table.tr[$i].ChildNodes[0].Attributes.append($pop) | Out-Null | |
} | |
#go through rows again and add gradient | |
Write-Verbose "Inserting gradient" | |
for ($i = 1; $i -le $html.table.tr.count - 1; $i++) { | |
$class = $html.CreateAttribute("style") | |
[int]$start = $html.table.tr[$i].td[-1] | |
#create the gradient using starting and ending values | |
#based on %free | |
$class.value = $Gradient -f $start, (100 - $start) | |
$html.table.tr[$i].ChildNodes[4].Attributes.Append($class) | Out-Null | |
} #for | |
#add the html to the fragments | |
$fragments += $html.InnerXml | |
} #should process | |
} #Try | |
Catch { | |
Write-Warning "Failed to get disk information for $computer. $($_.exception.message)" | |
} #Catch | |
remove-cimsession $cs | |
} #foreach computer | |
} #process | |
End { | |
$progParam.currentOperation = "Finalizing report" | |
Write-Progress @progParam | |
#only proceed if there is data in $fragments | |
if ($fragments) { | |
#add some metadata about this report | |
[xml]$metadata = [pscustomobject]@{ | |
"Report Run" = "$((Get-Date).ToUniversalTime()) UTC" | |
"Run By" = "$($env:USERDOMAIN)\$env:username" | |
Originated = $env:Computername | |
Command = $($myinvocation.invocationname) | |
Version = "2.0" | |
} | ConvertTo-html -as List -Fragment | |
#insert css tags into this table | |
$class = $metadata.CreateAttribute("class") | |
$class.value = 'meta' | |
$metadata.table.Attributes.Append($class) | Out-Null | |
for ($i = 0; $i -le $metadata.table.tr.count - 1; $i++) { | |
$class = $metadata.CreateAttribute("class") | |
$class.value = 'meta' | |
$metadata.table.tr[$i].attributes.append($class) | Out-Null | |
$class = $metadata.CreateAttribute("class") | |
$class.value = 'right' | |
$metadata.table.tr[$i].item("td").attributes.append($class) | Out-Null | |
} | |
$postcontent += "<br><br>$($metadata.InnerXml)" | |
#create the final report | |
Write-Verbose "Creating HTML report" | |
$paramHash = @{ | |
head = $head | |
Body = $($fragments | Out-String) | |
PostContent = $PostContent | |
} | |
if ($pscmdlet.ShouldProcess($path, "Creating HTML file")) { | |
ConvertTo-Html @paramHash | Out-File -FilePath $path -Encoding ascii | |
} | |
Write-Verbose "Report created to $path" | |
#if -Passthru write the file object to the pipeline | |
if ($Passthru) { | |
Get-Item $Path | |
} | |
} | |
Write-Verbose "Ending $($MyInvocation.Mycommand)" | |
} #end | |
} #close New-HTMLDiskReport |
I get access denied on the image. I think if you comment out lines 315..323 that will keep the data without any fancy formatting. By the way, you might try to see if you can open the html file with a different browser.
I do not know why it says access denied; I get the same. I commented out the two lines and I still see gradients. I have not tried another browser but I will when I have time tomorrow. Thank you for the quick response.
You need to comment out the range of lines from 315 to 232.
Got back to it, commenting out 315 to 332 caused errors. Ended up commenting out 315 through 323 and it worked, however, now there are no bars of red and green at all and just the listing of free space as a number. I'll keep trying but wanted to give you an update.
On a side note, I am in awe of this. I've tried so many times in the past to learn PowerShell and it has been futile. I can generally look at other people's scripts and sort of figure out what the intentions are, but I have never been able to just sit down and write one of these from scratch. Can you give me any insight as to how I would get to anywhere near where you are at with PowerShell?
I re-read your initial comment. I mistook it that you wanted to hide the colors. I'll have to revisit the code to see how to keep the red and green but as blocks and not using a gradient. As for getting better with PowerShell, you just have to do it. And take the time to understand how and why it works. It is more than a scripting language. The only reason I'm good is because I have been using it constantly for 18+ years.
Thanks for the advice. I found this in another script that creates a bar...maybe it can help?
`$newHtmlFragment = [System.Collections.ArrayList]::new()
foreach ($computer in $computers)
{
$disks = Get-DiskDetails -Computer $computer
$diskinfo = @()
foreach ($disk in $disks) {
[int]$percentUsage = ((($disk.Size - $disk.FreeSpace)/1gb -as [int]) / ($disk.Size/1gb -as [int])) * 100 #(50/100).tostring("P")
$bars = "
$diskInfo += [PSCustomObject]@{
Volume = $disk.DeviceID
VolumeName = $disk.VolumeName
TotalSize_GB = $disk.Size / 1gb -as [int]
UsedSpace_GB = ($disk.Size - $disk.FreeSpace)/1gb -as [int]
FreeSpace_GB = [System.Math]::Round($disk.FreeSpace/1gb)
Usage = "usage {0}" -f $bars #, $percentUsage
}
}
$htmlFragment = $diskInfo | ConvertTo-Html -Fragment
$newHtmlFragment += $htmlFragment[0]
$newHtmlFragment += "$($computer.ToUpper())"
$newHtmlFragment += $htmlFragment[2].Replace('',"")
$diskData = $htmlFragment[3..($htmlFragment.count -2)]
for ($i = 0; $i -lt $diskData.Count; $i++) {
if ($($i % 2) -eq 0)
{
$newHtmlFragment += $diskData[$i].Replace('<td>',"<td class='td0'>")
}
else
{
$newHtmlFragment += $diskData[$i].Replace('<td>',"<td class='td1'>")
}
}
$newHtmlFragment += $htmlFragment[-1]
}
$newHtmlFragment = $newHtmlFragment.Replace("usage ", "")
$newHtmlFragment = $newHtmlFragment.Replace("usage ", "")
$newHtmlFragment = $newHtmlFragment.Replace('<', '<')
$newHtmlFragment = $newHtmlFragment.Replace('>', '>')
$newHtmlFragment = $newHtmlFragment.Replace(''', "'")
`
I thought it might be helpful to put the whole thing in and not just the section I thought would help. You will have to change the variable on line one for your environment. This script does everything I want, but it doesn't have your pop-up functions for server name and drive meta data.
`$computers = Get-Content "c:\bills\servers.txt"
function Get-DiskDetails
{
[CmdletBinding()]
param (
[string]$Computer = $env:COMPUTERNAME
)
$cimSessionOptions = New-CimSessionOption -Protocol Default
$query = "SELECT DeviceID, VolumeName, Size, FreeSpace FROM Win32_LogicalDisk WHERE DriveType = 3"
$cimsession = New-CimSession -Name $Computer -ComputerName $Computer -SessionOption $cimSessionOptions
Get-CimInstance -Query $query -CimSession $cimsession
}
$newHtmlFragment = [System.Collections.ArrayList]::new()
foreach ($computer in $computers)
{
$disks = Get-DiskDetails -Computer $computer
$diskinfo = @()
foreach ($disk in $disks) {
[int]$percentUsage = ((($disk.Size - $disk.FreeSpace)/1gb -as [int]) / ($disk.Size/1gb -as [int])) * 100 #(50/100).tostring("P")
$bars = "
$diskInfo += [PSCustomObject]@{
Volume = $disk.DeviceID
VolumeName = $disk.VolumeName
TotalSize_GB = $disk.Size / 1gb -as [int]
UsedSpace_GB = ($disk.Size - $disk.FreeSpace)/1gb -as [int]
FreeSpace_GB = [System.Math]::Round($disk.FreeSpace/1gb)
Usage = "usage {0}" -f $bars #, $percentUsage
}
}
$htmlFragment = $diskInfo | ConvertTo-Html -Fragment
$newHtmlFragment += $htmlFragment[0]
$newHtmlFragment += "$($computer.ToUpper())"
$newHtmlFragment += $htmlFragment[2].Replace('',"")
$diskData = $htmlFragment[3..($htmlFragment.count -2)]
for ($i = 0; $i -lt $diskData.Count; $i++) {
if ($($i % 2) -eq 0)
{
$newHtmlFragment += $diskData[$i].Replace('<td>',"<td class='td0'>")
}
else
{
$newHtmlFragment += $diskData[$i].Replace('<td>',"<td class='td1'>")
}
}
$newHtmlFragment += $htmlFragment[-1]
}
$newHtmlFragment = $newHtmlFragment.Replace("usage ", "")
$newHtmlFragment = $newHtmlFragment.Replace("usage ", "")
$newHtmlFragment = $newHtmlFragment.Replace('<', '<')
$newHtmlFragment = $newHtmlFragment.Replace('>', '>')
$newHtmlFragment = $newHtmlFragment.Replace(''', "'")
$html = @"
<title>Disk Usage Report</title> <style> body { font-family: Calibri, sans-serif, 'Gill Sans', 'Gill Sans MT', 'Trebuchet MS'; background-color: whitesmoke; } .mainhead { margin: auto; width: 100%; text-align: center; font-size: xx-large; font-weight: bolder; } table { margin: 10px auto; width: 70%; } .ServerName { font-size: x-large; margin: 10px; text-align: left; padding: 10 0; color: BlueViolet; } .tableheader { background-color: black; color: white; padding: 10px; text-align: left; /* font-size: large; */ border-bottom-style: solid; border-bottom-color: darkgray; } td { background-color: white; border-bottom: 1px; border-bottom-style: solid; border-bottom-color: #404040; } .usage {
background-color: Lavender ;
width: 70%;
color: black;
}
span {
color: black;
}
.td1 {
background-color: #F0F0F0;
}
</style>
</head>
<body>
<div class='mainhead'>
<b><u>Production Servers Disk Usage Report</u></b>
</div>
<br>
<div><i><b>Generated on: </b> $((Get-Date).DateTime)</i></div>
$newHtmlFragment
</body>
"@
$html > disk_usage_report.html`
Late to the show...what a fantastic script/function this is and thank you so much for the time you put into it. The only "issue" I'm having with it is in the gradient display; some of my drives show the gradient effect while others it's just red and green bar. For consistency, is there a way the gradient effect can be removed without destroying anything else in the script/function? I don't mind not having the gradient, or it can work across all my drives that would be great too, either way but I need it to be consistent. I've attached an image showing what I mean.

Edit: unsure why the image is not loading