#requires -version 3.0
<#
.Synopsis
Create an HTML Hyper-V health report.
.Description
This command will create an HTML-based Hyper-V health report. It is designed
to report on Hyper-V 3.0 servers or even Client Hyper-V on Windows 8. This
script requires the Hyper-V, Storage and NetAdapter modules. It can be run on
the Hyper-V server use PowerShell remoting or you can specify a remote computer
for the report with the -Computername parameter. But the machine you are running
from must have the required modules.
Another option is to use PowerShell remoting and run the script on the remote
computer. See examples.
The report only shows virtual machine information for any virtual machine that is
not powered off. If you include performance counters, you will only get data on
counters with a value other than 0. Data from resource metering will only be
available for running virtual machines with resource metering enabled.
If you don't specify a file name, the command will create a file in your Documents
folder called HyperV-Health.htm.
.Parameter Computername
The name of the Hyper-V server. You must have rights to administer the server.
.Parameter RecentCreated
The number of days to check for recently created virtual machines.
.Parameter Hours
The number of hours to check for recent event log entries. The default is 24.
.Parameter LastUsed
The number of days to check for last used virtual machines. The default is 30.
.Parameter Performance
Specify if you do want performance counters in the report.
.Parameter Path
.Parameter Metering
Specify if you do want to include resource metering in the report.
The path and filename for the HTML report.
.Example
PS C:\Scripts> .\New-HVHealthReport.ps1 -computer HV01
Create a report for server HV01 with default values. The report will be saved locally
in the documents folder as HyperV-Health.htm
.Example
PS C:\Scripts> .\New-HVHealthReport.ps1 -computer HV01 -performance -metering
Create a report for server HV01 with default values including performance and
resource meter data. The report will be saved locally in the documents folder
as HyperV-Health.htm.
.Example
PS C:\> c:\scripts\New-HVHealthReport.ps1 -path c:\reports\HV01-health.htm -Recent 7 -performance -computername HV01
This will execute the script and report on server HV01. This command is also finding
virtual machines created in the last 7 days and including performance counter information.
.Link
Get-VM
Get-VHD
Measure-VM
Get-CimInstance
Get-Counter
Get-Eventlog
.Inputs
This command does not accept pipelined input.
.Outputs
an HTML file
.Notes
Version 0.9.5
****************************************************************
* DO NOT USE IN A PRODUCTION ENVIRONMENT UNTIL YOU HAVE TESTED *
* THOROUGHLY IN A LAB ENVIRONMENT. USE AT YOUR OWN RISK. IF *
* YOU DO NOT UNDERSTAND WHAT THIS SCRIPT DOES OR HOW IT WORKS, *
* DO NOT USE IT OUTSIDE OF A SECURE, TEST SETTING. *
****************************************************************
#>
[cmdletbinding()]
Param(
[Parameter(Position=0,HelpMessage="The name of the Hyper-V server. You must have rights to administer the server.")]
[ValidateNotNullorEmpty()]
[String]$Computername=$env:computername,
[Parameter(HelpMessage="The path and filename for the HTML report.")]
[ValidateNotNullorEmpty()]
[ValidateScript({
if (Test-Path (Split-Path $_)) {
$True
}
else {
Throw "Can't validate part of the path $_"
}
})]
[String]$Path= (
Join-path -path ([environment]::GetFolderPath("mydocuments")) -child "HyperV-Health.htm"
),
[Parameter(HelpMessage="The number of days to check for recently created virtual machines.")]
[ValidateScript({$_ -ge 0})]
[int]$RecentCreated=30,
[Parameter(HelpMessage="The number of days to check for last used virtual machines.")]
[ValidateScript({$_ -ge 0})]
[int]$LastUsed=30,
[Parameter(HelpMessage="The number of hours to check for recent event log entries.")]
[ValidateScript({$_ -ge 0})]
[int]$Hours=24,
[Parameter(HelpMessage="Specify if you do want performance counters in the report.")]
[switch]$Performance,
[Parameter(HelpMessage="Specify if you do want resource metering in the report.")]
[switch]$Metering
)
#region initialize
$reportversion="0.9.5"
Import-Module Hyper-V,Storage,NetAdapter
#parameters for Write-Progress
$progParam=@{
Activity="Hyper-V Health Report: $($computername.ToUpper())"
Status="initializing"
PercentComplete=0
}
Write-Progress @progParam
#initialize a variable for HTML fragments
$fragments=@()
$fragments+="+/-"
#endregion
#region get server information
$progParam.Status="Getting VM Host"
$progParam.currentOperation=$computername
Write-Progress @progParam
$vmhost = Get-VMHost -ComputerName $computername |
Select @{Name="Name";Expression={$_.name.toUpper()}},
@{Name="Domain";Expression={$_.FullyQualifiedDomainName}},
@{Name="MemGB";Expression={$_.MemoryCapacity/1GB -as [int]}},
@{Name="Max Migrations";Expression={$_.MaximumStorageMigrations}},
@{Name="Numa Spanning";Expression={$_.NumaSpanningEnabled}},
@{Name="IoV";Expression={$_.IoVSupport}},
@{Name="VHD Path";Expression={$_.VirtualHardDiskPath}},
@{Name="VM Path";Expression={$_.VirtualMachinePath}}
$Text="VM Host"
$div=$Text.Replace(" ","_")
$fragments+= "$Text
"
$fragments+= $vmhost | ConvertTo-Html -Fragment
$fragments+="
"
$progParam.Status="Getting Server information"
$progParam.currentOperation="Operating System"
Write-Progress @progParam
$os = Get-CimInstance -ClassName Win32_OperatingSystem -ComputerName $computername
$osdetail = $os |
Select @{Name="OS";Expression={$_.caption}},
@{Name="ServicePack";Expression={$_.CSDVersion}},
LastBootUptime,
@{Name="Uptime";Expression={(Get-Date) - $_.LastBootUpTime}}
$Text="Operating System"
$div=$Text.Replace(" ","_")
$fragments+= "$Text
"
$fragments+= $osdetail | Convertto-html -Fragment
$fragments+="
"
$progparam.PercentComplete= 5
$progParam.currentOperation="Computer System"
Write-Progress @progParam
$cs = Get-CimInstance -ClassName Win32_ComputerSystem -ComputerName $computername |
Select Manufacturer,Model,@{Name="TotalMemoryGB";Expression={[int]($_.TotalPhysicalMemory/1GB)}},
NumberOfProcessors,NumberOfLogicalProcessors
$Text="Computer System"
$div=$Text.Replace(" ","_")
$fragments+= "$Text
"
$fragments+= $cs | ConvertTo-HTML -Fragment
$fragments+="
"
#endregion
#region memory
$text="Memory"
$fragments+= "$Text
"
$mem = $os |
Select @{Name="FreeGB";Expression={[math]::Round(($_.FreePhysicalMemory/1MB),2)}},
@{Name="TotalGB";Expression={[math]::Round(($_.TotalVisibleMemorySize/1MB),2)}},
@{Name="Percent Free";Expression={[math]::Round(($_.FreePhysicalMemory/$_.TotalVisibleMemorySize)*100,2)}},
@{Name="FreeVirtualGB";Expression={[math]::Round(($_.FreeVirtualMemory/1MB),2)}},
@{Name="TotalVirtualGB";Expression={[math]::Round(($_.TotalVirtualMemorySize/1MB),2)}}
[xml]$html = $mem | ConvertTo-Html -fragment
#check each row, skipping the TH header row
for ($i=1;$i -le $html.table.tr.count-1;$i++) {
$class = $html.CreateAttribute("class")
#check the value of the percent free MB column and assign a class to the row
if (($html.table.tr[$i].td[2] -as [double]) -le 10) {
$class.value = "memalert"
$html.table.tr[$i].ChildNodes[2].Attributes.Append($class) | Out-Null
}
elseif (($html.table.tr[$i].td[2] -as [double]) -le 20) {
$class.value = "memwarn"
$html.table.tr[$i].ChildNodes[2].Attributes.Append($class) | Out-Null
}
}
$fragments+= $html.innerXML
$fragments+="
"
#endregion
#region network adapters
$progParam.currentOperation="Network Adapters"
$progparam.PercentComplete= 10
Write-Progress @progParam
$Text="Network Adapters"
$div=$Text.Replace(" ","_")
$fragments+= "$Text
"
$fragments+= Get-NetAdapterStatistics -CimSession $computername |
Select Name,
@{Name="RcvdUnicastMB";Expression={[math]::Round(($_.ReceivedUnicastBytes/1MB),2)}},
@{Name="SentUnicastMB";Expression={[math]::Round(($_.SentUnicastBytes/1MB),2)}},
ReceivedUnicastPackets,SentUnicastPackets,
ReceivedDiscardedPackets,OutboundDiscardedPackets | ConvertTo-HTML -Fragment
$fragments+="
"
#endregion
#region check disk space
$progParam.Status="Getting Server Details"
$progParam.currentOperation="checking volumes"
$progparam.PercentComplete= 15
Write-Progress @progParam
$vols = Get-Volume -CimSession $computername |
Where drivetype -eq 'fixed' | Sort DriveLetter |
Select @{Name="Drive";Expression={
if ($_.DriveLetter) { $_.driveletter} else {"none"}
}},Path,HealthStatus,
@{Name="SizeGB";Expression={[math]::Round(($_.Size/1gb),2)}},
@{Name="FreeGB";Expression={[math]::Round(($_.SizeRemaining/1gb),4)}},
@{Name="PercentFree";Expression={[math]::Round((($_.SizeRemaining/$_.Size)*100),2)}}
$Text="Volumes"
$div=$Text.Replace(" ","_")
$fragments+= "$Text
"
[xml]$html = $vols | ConvertTo-Html -Fragment
<#
I don't know why, but I can't add attributes to two different nodes
at the same time so we have to go through all the volumes once to
look at health and then a second time to look at percent free space.
#>
#check each row, skipping the TH header row
#add alert class if volume is not healthy
for ($i=1;$i -le $html.table.tr.count-1;$i++) {
$class = $html.CreateAttribute("class")
if ($html.table.tr[$i].td[2] -ne "Healthy") {
$class.value = "alert"
$html.table.tr[$i].ChildNodes[2].Attributes.Append($class) | Out-Null
}
else {
$class.value = "green"
$html.table.tr[$i].ChildNodes[2].Attributes.Append($class) | Out-Null
}
}
#go through rows again and add class depending on % free space
for ($i=1;$i -le $html.table.tr.count-1;$i++) {
$class = $html.CreateAttribute("class")
if (($html.table.tr[$i].td[-1] -as [double]) -le 10) {
$class.value = "memalert"
$html.table.tr[$i].ChildNodes[5].Attributes.Append($class) | Out-Null
}
elseif (($html.table.tr[$i].td[-1] -as [double]) -le 20) {
$class.value = "memwarn"
$html.table.tr[$i].ChildNodes[5].Attributes.Append($class) | Out-Null
}
} #for
$fragments+= $html.innerXML
$fragments+="
"
#endregion
#region check services
$progParam.currentOperation="Checking Hyper-V Services"
$progparam.PercentComplete= 20
Write-Progress @progParam
$services = Get-CimInstance win32_service -filter "name like 'vmi%' or name ='vmms'" -ComputerName $computername |
Select Name,Displayname,StartMode,State,Startname
$Text="Services"
$div=$Text.Replace(" ","_")
$fragments+= "$Text
"
[xml]$html= $services | ConvertTo-HTML -Fragment
#find stopped services and add Alert style
for ($i=1;$i -le $html.table.tr.count-1;$i++) {
$class = $html.CreateAttribute("class")
#check the value of the State column and assign a class to the row
if ($html.table.tr[$i].td[3] -eq 'running') {
$class.value = "green"
$html.table.tr[$i].Attributes.Append($class) | Out-Null
}
}
#add the revised html to the fragment
$fragments+= $html.InnerXml
$fragments+="
"
#endregion
#region enum VM
$progParam.Status="Getting Virtual Machine information"
$progParam.currentOperation="Enumerating VMs"
$progparam.PercentComplete= 25
Write-Progress @progParam
$Text="Virtual Machines"
$div=$Text.Replace(" ","_")
$fragments+= "$Text
"
Try {
#get all VMs that are not turned off
$allVMs = Get-VM -ComputerName $computername -ErrorAction Stop
$runningVMs= $allVMS | Where State -ne 'off'
$vmGroup = $runningVMs | sort State,Name | Group-Object -Property State | Sort Count
#define a set of properties to display for each VM
$vmProps="Name","Uptime","Status","CPUUsage","MemoryAssigned",
"MemoryDemand","MemoryStatus","MemoryStartup","MemoryMiniumum",
"MemoryMaximum","DynamicMemoryEnabled"
foreach ($item in $vmGroup) {
[xml]$html= $item.Group | Select $vmProps | ConvertTo-HTML -Fragment
$caption = $html.CreateElement("caption")
$html.table.AppendChild($caption) | Out-Null
$html.table.caption= $item.Name
for ($i=1;$i -le $html.table.tr.count-1;$i++) {
$class = $html.CreateAttribute("class")
#check the value of the MemoryStatus column and assign a class to the row
if ($html.table.tr[$i].td[6] -eq "Low") {
$class.value = "memalert"
$html.table.tr[$i].ChildNodes[6].Attributes.Append($class) | Out-Null
}
elseif ($html.table.tr[$i].td[6] -eq "Warning") {
$class.value = "memwarn"
$html.table.tr[$i].ChildNodes[6].Attributes.Append($class) | Out-Null
}
} #for
$fragments+= $html.InnerXml
} #foreach
} #try
Catch {
$fragments+="
No virtual machines detected
"
}
#region created in the last 30 days
$progParam.currentOperation="Virtual Machines Created in last $RecentCreated Days"
$progparam.PercentComplete= 28
Write-Progress @progParam
if ($allVMs) {
$recent = $allVMS | where CreationTime -ge (Get-Date).AddDays(-$RecentCreated) |
Select Name,CreationTime,Notes
if ($recent) {
[xml]$html= $recent | ConvertTo-HTML -Fragment
$caption = $html.CreateElement("caption")
$html.table.AppendChild($caption) | Out-Null
$html.table.caption= "Created in last $RecentCreated days"
$fragments+= $html.InnerXml
}
else {
$fragments+="
Created in last $RecentCreated daysNo virtual machines created recently |
"
}
}
else {
$fragments+="
No virtual machines detected
"
}
#endregion
#region last use
$progParam.currentOperation="Virtual Machines not used within last $LastUsed Days"
$progparam.PercentComplete= 30
Write-Progress @progParam
#nested function to get last use information. This
#uses PowerShell remoting
Function Get-VMLastUse {
[cmdletbinding()]
Param (
[Parameter(Position=0,
HelpMessage="Enter a Hyper-V virtual machine name",
ValueFromPipeline,ValueFromPipelinebyPropertyName)]
[ValidateNotNullorEmpty()]
[alias("vm")]
[object]$Name="*",
[Parameter(ValueFromPipelinebyPropertyname)]
[alias("cn")]
[string]$Computername
)
Begin {
Write-Verbose -Message "Starting $($MyInvocation.Mycommand)"
#define a hashtable of parameters to splat to Get-VM
$vmParams = @{
ErrorAction="Stop"
}
#if computername is not the local host add it to the parameter set
if ($Computername -AND ($Computername -ne $env:COMPUTERNAME)) {
Write-Verbose "Searching on $computername"
$vmParams.Add("Computername",$Computername)
#create a PSSession for Invoke-Command
Try {
Write-Verbose "Creating temporary PSSession"
$tmpSession = New-PSSession -ComputerName $Computername -ErrorAction Stop
}
Catch {
Throw "Failed to create temporary PSSession to $computername."
}
}
} #begin
Process {
if ($name -is [string]) {
Write-Verbose -Message "Getting virtual machine(s)"
$vmParams.Add("Name",$name)
Try {
$vms = Get-VM @vmParams
}
Catch {
Write-Warning "Failed to find a VM or VMs with a name like $name"
#bail out
Return
}
}
elseif ($name -is [Microsoft.HyperV.PowerShell.VirtualMachine] ) {
#otherwise we'll assume $Name is a virtual machine object
Write-Verbose "Found one or more virtual machines matching the name"
$vms = $name
}
else {
#invalid object type
Write-Error "The input object was invalid."
#bail out
return
}
foreach ($vm in $vms) {
#if VM is on a remote machine using PowerShell remoting to get the information
Write-Verbose "Processing $($vm.name)"
$sb = {
param([string]$Path,[string]$vmname)
Try {
$diskfile = Get-Item -Path $Path -ErrorAction Stop
$diskFile | Select-Object @{Name="LastUse";Expression={$diskFile.LastWriteTime}},
@{Name="LastUseAge";Expression={(Get-Date) - $diskFile.LastWriteTime}}
}
Catch {
Write-Warning "$($vmname): Could not find $path."
}
} #end scriptblock
#get first drive file
$diskpath= $vm.HardDrives[0].Path
#only proceed if a hard drive path was found
if ($diskpath) {
$icmParam=@{
ScriptBlock=$sb
ArgumentList= @($diskpath,$vm.name)
}
Write-Verbose "Getting details for $(($icmParam.ArgumentList)[0])"
if ($vmParams.computername) {
$icmParam.Add("Session",$tmpSession)
}
$details = Invoke-Command @icmParam
#write a custom object to the pipeline
$objHash=[ordered]@{
VMName=$vm.name
CreationTime=$vm.CreationTime
LastUse=$details.LastUse
LastUseAge=$details.LastUseAge
}
#if VM is running set the LastUseAge to 0:00:00
if ($vm.state -eq 'running') {
$objHash.LastUseAge= New-TimeSpan -hours 0
}
#write the object to the pipeline
New-Object -TypeName PSObject -Property $objHash
} #if $diskpath
Else {
Write-Warning "$($vm.name): No hard drives defined."
}
}#foreach
} #process
End {
#remove temp PSSession if found
if ($tmpSession) {
Write-Verbose "Removing temporary PSSession"
$tmpSession | Remove-PSSession
}
Write-Verbose -Message "Ending $($MyInvocation.Mycommand)"
} #end
} #end function
$last= New-Timespan -Days $LastUsed
$data = Get-VMLastUse -Computername $Computername | where {$_.lastuseage -gt $last } | sort LastUseAge
if ($data) {
[xml]$html= $data | ConvertTo-HTML -Fragment
$caption = $html.CreateElement("caption")
$html.table.AppendChild($caption) | Out-Null
$html.table.caption= "Not used in last $lastused days"
$fragments+= $html.InnerXml
}
else {
$fragments+="
Not used in last $lastused daysNo unused virtual machines detected for the last $lastused days. |
"
}
#endregion
#region Integrated Services Version
$progParam.currentOperation="Integrated Services Version"
$progparam.PercentComplete= 35
Write-Progress @progParam
if ($runningVMs) {
$isv = $runningVMS | Sort IntegrationServicesVersion | Select Name,IntegrationServicesVersion
[xml]$html= $isv | ConvertTo-HTML -Fragment
$caption = $html.CreateElement("caption")
$html.table.AppendChild($caption) | Out-Null
$html.table.caption= "Integration Services Version"
$fragments+=$html.InnerXml
}
else {
$fragments+="
No virtual machines detected
"
}
#endregion
#endregion
#region VHD Utilization
$progParam.currentOperation="Analyzing Virtual Disks"
$progparam.PercentComplete= 40
Write-Progress @progParam
$fragments+= "
Virtual Disk Detail
"
if ($runningVMs) {
$progParam.Status="Getting Virtual Disk Detail"
foreach ($vm in $runningVMs) {
$progParam.currentOperation=$vm.name
Write-Progress @progParam
#get VHD details
$vhdDetail = foreach ($drive in $vm.harddrives) {
Try {
$detail = Get-VHD -ComputerName $computername -path $drive.path -ErrorAction Stop
$vhdHash=[ordered]@{
ControllerType= $drive.ControllerType
ControllerNumber= $drive.ControllerNumber
ControllerLocation= $drive.ControllerLocation
VHDFormat= $detail.VHDFormat
VHDType= $detail.VHDType
FileSizeMB= [math]::Round(($detail.FileSize/1MB),2)
SizeMB= [math]::Round(($detail.Size/1MB),2)
MinSizeMB= [math]::Round(($detail.MinimumSize/1MB),2)
FragPercent= $detail.FragmentationPercentage
Path= $drive.path
}
New-Object -TypeName PSObject -Property $vhdhash
} #try
Catch {
$fragments+="
$($_.Exception.Message)
"
}
} #foreach drive
if ($vhdDetail) {
[xml]$html= $vhdDetail | ConvertTo-HTML -Fragment
$caption = $html.CreateElement("caption")
$html.table.AppendChild($caption) | Out-Null
$html.table.caption= $vm.Name
$fragments+= $html.InnerXml
}
} #foreach vm
}
else {
$fragments+="
No virtual disk files found
"
}
#endregion
#region Resource Metering
if ($Metering) {
$progParam.currentOperation="Gathering Resource Metering Data"
$progparam.PercentComplete= 43
Write-Progress @progParam
#region Resource Pool
$fragments+= "
Resource Pool Metering
"
#turn off error handling. There might be some resource pool data for some
#types
$data = Measure-VMResourcePool -name * -computer $computername -ErrorAction SilentlyContinue |
Select ResourcePoolname,AvgCPU,AvgRam,MinRam,MaxRam,TotalDisk,
@{Name="NetworkInbound(M)";
Expression= { ($_.NetworkMeteredTrafficReport |
where direction -Eq 'inbound' | measure TotalTraffic -sum).Sum
}},MeteringDuration
if ($data) {
$fragments+= $data | ConvertTo-Html -Fragment
}
else {
$fragments+="
No VM Resource Pool data found
"
}
#endregion
#region VM metering
$fragments+= "
VM Resource Metering
"
if ($runningVMs) {
$data = $runningVMs | where {$_.ResourceMeteringEnabled} |
foreach {
Measure-VM -name $_.vmname -ComputerName $computername |
Select VMName,AvgCPU,AvgRAM,MinRam,MaxRam,TotalDisk,
@{Name="NetworkInbound(M)";
Expression= { ($_.NetworkMeteredTrafficReport |
where direction -Eq 'inbound' | measure TotalTraffic -sum).Sum
}},
@{Name="NetworkOutbound(M)";
Expression= { ($_.NetworkMeteredTrafficReport |
where direction -Eq 'outbound' | measure TotalTraffic -sum).Sum
}}, MeteringDuration
} #foreach
$fragments+= $data | ConvertTo-Html -Fragment
}
else {
$fragments+="
No virtual machines detected
"
}
#endregion
}
$fragments+="
"
#endregion
#region check for recent event log errors and warnings
$progParam.currentOperation="Checking System Event Log"
$progparam.PercentComplete= 60
Write-Progress @progParam
#hashtable of parameters for Get-Eventlog
$logParam=@{
Computername= $Computername
LogName= "System"
EntryType= "Error","Warning"
After= (Get-Date).AddHours(-$Hours)
}
$sysLog = Get-EventLog @logparam
<#
only get errors and warnings from these sources
vmicheartbeat
vmickvpexchange
vmicrdv
vmicshutdown
vmictimesync
vmicvss
#>
$progParam.currentOperation="Checking Application Event log"
$progparam.PercentComplete= 65
Write-Progress @progParam
$logParam.logName="Application"
$appLog = Get-EventLog @logparam -Source vmic*
$Text="Event Logs"
$div=$Text.Replace(" ","_")
$fragments+= "$Text
"
$fragments+= "
System
"
if ($syslog) {
$syslog | Group-Object -Property Source |
Sort Count -Descending | Foreach {
[xml]$html = $_.Group | Sort TimeWritten -Descending |
Select TimeWritten,EntryType,InstanceID,Message |
ConvertTo-Html -Fragment
$caption = $html.CreateElement("caption")
$html.table.AppendChild($caption) | Out-Null
$html.table.caption= $_.Name
#find errors and add Alert style
for ($i=1;$i -le $html.table.tr.count-1;$i++) {
$class = $html.CreateAttribute("class")
#check the value of the entry type column and assign a class to the row
if ($html.table.tr[$i].td[1] -eq 'error') {
$class.value = "alert"
$html.table.tr[$i].Attributes.Append($class) | Out-Null
}
} #for
#add the revised html to the fragment
$fragments+= $html.InnerXml
} #foreach
} #if System entries
else {
$fragments+= "
No relevant system errors or warnings found. |
"
}
$fragments+= "
Application
"
if ($applog) {
$applog | Group-Object -Property Source |
Sort Count -Descending | Foreach {
$fragments+="
$($_.Name)
"
[xml]$html= $_.Group | Sort TimeWritten -Descending |
Select TimeWritten,EntryType,InstanceID,Message |
ConvertTo-Html -Fragment
$caption = $html.CreateElement("caption")
$html.table.AppendChild($caption) | Out-Null
$html.table.caption= $_.Name
#find errors and add Alert style
for ($i=1;$i -le $html.table.tr.count-1;$i++) {
$class = $html.CreateAttribute("class")
#check the value of the entry type column and assign a class to the row
if ($html.table.tr[$i].td[1] -eq 'error') {
$class.value = "alert"
$html.table.tr[$i].Attributes.Append($class) | Out-Null
}
} #for
#add the revised html to the fragment
$fragments+= $html.InnerXml
} #foreach
} #if
else {
$fragments+= "
No relevant application errors or warnings found. |
"
}
#region check operational logs
$progParam.currentOperation="Checking operational event logs"
$progparam.PercentComplete= 68
Write-Progress @progParam
$fragments+= "
Operational logs
"
#define a hash table of parameters to splat to Get-WinEvent
$paramHash=@{
ErrorAction="Stop"
ErrorVariable="MyErr"
Computername=$Computername
}
$start = (Get-Date).AddHours(-$hours)
#construct a hash table for the -FilterHashTable parameter in Get-WinEvent
$filter= @{
Logname= "Microsoft-Windows-Hyper-V*"
Level=2,3
StartTime= $start
}
#add it to the parameter hash table
$paramHash.Add("FilterHashTable",$filter)
#search logs for errors and warnings
Try {
#add a property for each entry that translates the SID into
#the account name
#hash table of parameters for Get-WSManInstance
$script:newHash=@{
ResourceURI="wmicimv2/win32_SID"
SelectorSet=$null
Computername=$Computername
ErrorAction="Stop"
ErrorVariable="myErr"
}
$oplogs = Get-WinEvent @paramHash |
Add-Member -MemberType ScriptProperty -Name Username -Value {
Try {
#resolve the SID
$script:newHash.SelectorSet=@{SID="$($this.userID)"}
$resolved = Get-WSManInstance @script:newhash
}
Catch {
Write-Warning $myerr.ErrorRecord
}
if ($resolved.accountname) {
#write the resolved name to the pipeline
"$($Resolved.ReferencedDomainName)\$($Resolved.Accountname)"
}
else {
#re-use the SID
$this.userID
}
} -PassThru
}
Catch {
Write-Warning $MyErr.errorRecord
}
if ($oplogs) {
$oplogs | Group-Object -Property Logname |
Sort Count -Descending | Foreach {
[xml]$html= $_.Group | Sort TimeCreated -Descending |
Select TimeCreated,@{Name="EntryType";Expression={$_.levelDisplayname}},
ID,Username,Message |
ConvertTo-Html -Fragment
$caption = $html.CreateElement("caption")
$html.table.AppendChild($caption) | Out-Null
$html.table.caption= $_.Name
#find errors and add Alert style
for ($i=1;$i -le $html.table.tr.count-1;$i++) {
$class = $html.CreateAttribute("class")
#check the value of the entry type column and assign a class to the row
if ($html.table.tr[$i].td[1] -eq 'error') {
$class.value = "alert"
$html.table.tr[$i].Attributes.Append($class) | Out-Null
}
} #for
#add the revised html to the fragment
$fragments+= $html.InnerXml
} #foreach
}
else {
$fragments+= "
No relevant application errors or warnings found. |
"
}
$fragments+="
"
#endregion
#endregion
#region get performance data
if ($Performance) {
$progParam.status="Gathering Performance Data"
$progparam.PercentComplete= 70
$progParam.currentOperation="..System"
Write-Progress @progParam
$Text="Performance"
$div=$Text.Replace(" ","_")
$fragments+= "$Text
"
#system
$ctrs = "\System\Processes","\System\Threads","\System\Processor Queue Length"
$sysCounters = Get-Counter -counter $ctrs -computername $Computername
[xml]$html= $sysCounters | Select -expand CounterSamples |
Select Path,@{Name="Value";Expression={$_.CookedValue}} |
ConvertTo-HTML -Fragment
$caption = $html.CreateElement("caption")
$html.table.AppendChild($caption) | Out-Null
$html.table.caption= "System"
$fragments+=$html.InnerXml
#memory
$progParam.currentOperation="..Memory"
$progparam.PercentComplete= 72
Write-Progress @progParam
$ctrs="\Memory\Page Faults/sec",
"\Memory\% Committed Bytes In Use",
"\Memory\Available MBytes"
$memCounters = Get-Counter -counter $ctrs -computername $Computername
[xml]$html= $memCounters | Select -expand CounterSamples |
Select Path,@{Name="Value";Expression={$_.CookedValue}} |
ConvertTo-HTML -Fragment
$caption = $html.CreateElement("caption")
$html.table.AppendChild($caption) | Out-Null
$html.table.caption= "Memory"
$fragments+=$html.InnerXml
#cpu
$progParam.currentOperation="..Processor"
$progparam.PercentComplete= 75
Write-Progress @progParam
$ctrs="\Processor(*)\% Processor Time"
$procCounters = Get-Counter -counter $ctrs -computername $Computername
[xml]$html= $procCounters | Select -expand CounterSamples |
Select Path,@{Name="Value";Expression={$_.CookedValue}} |
ConvertTo-HTML -Fragment
$caption = $html.CreateElement("caption")
$html.table.AppendChild($caption) | Out-Null
$html.table.caption= "Processor"
$fragments+=$html.InnerXml
#physicaldisk
$progParam.currentOperation="..PhysicalDisk"
$progparam.PercentComplete= 77
Write-Progress @progParam
$ctrs="\PhysicalDisk(*)\Current Disk Queue Length",
"\PhysicalDisk(*)\Avg. Disk Queue Length",
"\PhysicalDisk(*)\Avg. Disk Read Queue Length",
"\PhysicalDisk(*)\Avg. Disk Write Queue Length",
"\PhysicalDisk(*)\% Disk Time",
"\PhysicalDisk(*)\% Disk Read Time",
"\PhysicalDisk(*)\% Disk Write Time"
Try {
$diskCounters = Get-Counter -counter $ctrs -computername $Computername -ErrorAction Stop
$data = $diskCounters | Select -ExpandProperty CounterSamples |
Where CookedValue -gt 0
}
Catch {
$fragments+= "
$($counterset.CounterSetName)$($_.Exception.Message) |
"
}
if ($data) {
#non zero data found
[xml]$html= $data |
Select Path,@{Name="Value";Expression={$_.CookedValue}} |
ConvertTo-HTML -Fragment
$caption = $html.CreateElement("caption")
$html.table.AppendChild($caption) | Out-Null
$html.table.caption= "Physical Disk"
$fragments+=$html.InnerXml
}
else {
$fragments+= "
$($counterset.CounterSetName)No non-zero values for this counter set. |
"
}
#Hyper-V Perf counters
$progParam.status="Getting Hyper-V Performance Counters"
$progparam.PercentComplete= 80
Write-Progress @progParam
$hvCounters = Get-Counter -ListSet Hyper-V* -ComputerName $computername
$data = foreach ($counterset in $hvcounters) {
$progParam.currentOperation=$counterset.countersetname
Write-Progress @progParam
#create reports for any counter with a value greater than 0
try {
$data = Get-Counter -Counter $counterset.counter -Computername $computername -ErrorAction Stop |
Select -ExpandProperty CounterSamples |
Where CookedValue -gt 0 |
Sort Path | Select Path,@{Name="Value";Expression={$_.CookedValue}}
if ($data) {
[xml]$html= $data | ConvertTo-HTML -Fragment
$caption= $html.CreateElement("caption")
$html.table.AppendChild($caption) | Out-Null
$html.table.caption= $counterset.CounterSetName
$fragments+=$html.InnerXml
}
else {
$fragments+= "
$($counterset.CounterSetName)No non-zero values for this counter set. |
"
}
} #try
Catch {
$fragments+= "
$($counterset.CounterSetName)$($_.Exception.Message) |
"
}
}
$fragments+="
"
} #if not $NoPerformance
#endregion
#region create HTML report
$progParam.status="Creating HTML Report"
$progParam.currentOperation=$Path
$progParam.percentcomplete=90
Write-Progress @progParam
$title = "$($os.CSName) Hyper-V Health Report"
$head = @"
$($Title)
Health Report: $($Computername.ToUpper())
"@
$footer=@"
Created $(Get-Date) by $($env:userdomain)\$($env:username)
Brought to you by
Altaro
v$reportversion
"@
$paramHash=@{
Head= $head
Body = $fragments
Postcontent= $footer
}
ConvertTo-Html @paramHash | Out-File -FilePath $path -encoding ASCII
$progParam.status="Creating HTML Report"
$progParam.currentOperation="Finished"
$progParam.percentcomplete=100
Write-Progress @progParam -Completed
Write-Host "Report complete. Please see $(Resolve-Path $path)" -ForegroundColor Green
#endregion