Add files via upload

This commit is contained in:
Federico Lillacci
2025-10-17 13:03:35 +02:00
committed by GitHub
commit 8f6b1072a3
6 changed files with 719 additions and 0 deletions

155
Functions.ps1 Normal file
View File

@@ -0,0 +1,155 @@
#DO NOT MODIFY
function Get-MgmtOsNicIpAddr
{
[CmdletBinding()]
Param(
[Parameter(Mandatory = $true)] $adapterName
)
$ipAddr = (Get-NetIPAddress | Where-Object {$_.InterfaceAlias -like "*$($adapterName)*" -and $_.AddressFamily -eq 'IPv4'}).IPAddress
return $ipAddr
}
function Get-VswitchMember
{
[CmdletBinding()]
Param(
[Parameter(Mandatory = $true)] $vswitch
)
$targetSwitch = Get-VMSwitch -Name $vswitch
$vswitchMembers = ($targetSwitch.NetAdapterInterfaceDescriptions)
$physNics = @()
foreach ($vswitchMember in $vswitchMembers)
{
$physNic = (Get-Netadapter -InterfaceDescription $vswitchMember).Name
$physNics += $physNic
}
$vswitchPhysNics = $physNics | Out-String
return $vswitchPhysNics
}
function SendEmailReport-MSGraph
{
[CmdletBinding()]
Param(
[Parameter(Mandatory = $true)] $body
)
#Checking if required module is present; if not, install it
if (!(Get-Module -Name Microsoft.Graph -ListAvailable))
{
Write-Host -ForegroundColor Yellow "MS Graph module missing, installing..."
Install-Module -Name Microsoft.Graph -Scope CurrentUser -Force
Import-Module -Name Microsoft.Graph
}
#Connect interactively to Microsoft Graph with required permissions
Connect-MgGraph -NoWelcome -Scopes 'Mail.Send', 'Mail.Send.Shared'
if ($ccrecipient)
{
$params = @{
Message = @{
Subject = $subject
Body = @{
ContentType = $type
Content = $body
}
ToRecipients = @(
@{
EmailAddress = @{
Address = $reportRecipient
}
}
)
CcRecipients = @(
@{
EmailAddress = @{
Address = $ccrecipient
}
}
)
}
SaveToSentItems = $save
}
}
else
{
$params = @{
Message = @{
Subject = $subject
Body = @{
ContentType = $type
Content = $body
}
ToRecipients = @(
@{
EmailAddress = @{
Address = $reportRecipient
}
}
)
}
SaveToSentItems = $save
}
}
# Send message
Send-MgUserMail -UserId $reportSender -BodyParameter $params
}
function SendEmailReport-Mailkit
{
[CmdletBinding()]
Param(
[Parameter(Mandatory = $true)] $body
)
#Checking if required module is present; if not, install it
if (!(Get-Module -ListAvailable -Name "Send-MailKitMessage"))
{
Write-Host -ForegroundColor Yellow "Send-MailKitMessage module missing, installing..."
Install-Module -Name "Send-MailKitMessage" -Scope CurrentUser -Force
Import-Module -Name "Send-MailKitMessage"
}
$UseSecureConnectionIfAvailable = $true
$credential = `
[System.Management.Automation.PSCredential]::new($smtpServerUser, `
(ConvertTo-SecureString -String $smtpServerPwd -AsPlainText -Force))
$from = [MimeKit.MailboxAddress]$reportSender
$recipientList = [MimeKit.InternetAddressList]::new()
$recipientList.Add([MimeKit.InternetAddress]$reportRecipient)
if ($ccrecipient)
{
$ccList = [MimeKit.InternetAddressList]::new();
$ccList.Add([MimeKit.InternetAddress]$ccrecipient);
}
$Parameters = @{
"UseSecureConnectionIfAvailable" = $UseSecureConnectionIfAvailable
"Credential" = $credential
"SMTPServer" = $smtpServer
"Port" = $smtpServerPort
"From" = $from
"RecipientList" = $recipientList
"CCList" = $ccList
"Subject" = $subject
"HTMLBody" = $body
}
Send-MailKitMessage @Parameters
}

40
GlobalVariables.ps1 Normal file
View File

@@ -0,0 +1,40 @@
#Script Info - do not modify
$scriptVersion = "1.0"
#Cluster Environment - Coming soon...
$clusterDeployment = $false
$vmHosts = "hv1", "hv2"
#Reporting section
$reportHtmlRequired = $true #Set to $true to generate an HTML report
$reportHtmlName = "Hyper-V_Status_Report" #Report file name without extension, e.g., "MyReport"
$reportHtmlDir = "C:\Temp" #Directory to save the report (no trailing slash), e.g., "C:\Temp"
#Information to be included in the report
$replicationInfoNeeded = $true # Set to true to include replication details
$vhdxInfoNeeded = $true #Set to true to include detailed VHDX information
$vmnetInfoNeeded = $true #Set to true to include VM network adapter details
$osNetInfoNeeded = $true #Set to true to include Management OS network adapter details
$vswitchInfoNeeded = $true #Set to true to include virtual switch details
#Email Configuration
$emailReport = $true #Set to true to send the report via email
$emailSystem = "mailkit" #Choose the email system: "msgraph" or "mailkit"
$reportSender = "mySender@mydomain.com" #Sender email address (use quotes)
$reportRecipient = "myRecipient@mydomain.com" #Recipient email address (use quotes)
$ccrecipient = $null #CC email address (use quotes); leave as $null if not used
$subject = "Hyper-V Status Report" #Email subject line
#MS Graph Email specific configuration
$type = "HTML" #Choose between "HTML" or "TXT"
$save = $false #Set to true to save the email in the Sent Items folder
#MailKit Email specific configuration
$smtpServer = "mysmtpserver.domain.com"
$smtpServerPort = 587
$smtpAuthRequired = $true
#If SMTP authentication is required, set a username and password below.
#DO NOT USE A SENSITIVE OR PRIVILEGED ACCOUNT HERE!!!
$smtpServerUser = "smtpserver.user"
$smtpServerPwd = "mySecretPwd"

15
HtmlCode.ps1 Normal file
View File

@@ -0,0 +1,15 @@
$preContent = "<h2>Hyper-V Status Report</h2>"
$postContent = "<p>Creation Date: $($today | Out-String) -
<a href='https://github.com/tsmagnum/Hyper-V_Report' target='_blank'>Hyper-V-Report ($scriptVersion) by F. Lillacci</a><p>"
$title = "Hyper-V Status Report"
$breakHtml = "</br>"
$titleHtmlHosts = "<h3>Hyper-V Server</h3>"
$titleHtmlVms = "<h3>Virtual Machines</h3>"
$titleHtmlSnapshots = "<h3>Snapshots</h3>"
$titleHtmlReplication = "<h3>Replication</h3>"
$titleHtmlVhdx = "<h3>VHDX Disks</h3>"
$titleHtmlVmnetAdapter = "<h3>VM Network Adatpers</h3>"
$titleHtmlOsNetAdapter = "<h3>Management OS Network Adatpers</h3>"
$titleHtmlVswitch = "<h3>Virtual Switches</h3>"

378
Hyper-V-Report.ps1 Normal file
View File

@@ -0,0 +1,378 @@
<#
.SYNOPSIS
Generates a comprehensive HTML report of the Hyper-V environment, including host details,
virtual machines, snapshots, replication status, VHDX files, network adapters,
and virtual switches. Optionally sends the report via email.
.DESCRIPTION
The Hyper-V-Report.ps1 script is designed to automate the collection and reporting of key metrics
and configuration details from a Hyper-V infrastructure. It supports both standalone and
(future) clustered deployments and provides detailed insights into:
Host system resources and configuration
Virtual machine specifications and states
Snapshot inventory and age
Replication status and health
VHDX file properties and fragmentation
VM and management OS network adapter configurations
Virtual switch topology and uplinks
The script generates an HTML report and can send it via email using either MS Graph or MailKit,
depending on the configuration.
It relies on external modular scripts (GlobalVariables.ps1, StyleCSS.ps1, HtmlCode.ps1, Functions.ps1)
for customization and formatting.
.EXAMPLE
.\Hyper-V-Report.ps1
#>
#Region Credits
#Author: Federico Lillacci
#Github: https://github.com/tsmagnum
#endregion
# Setup all paths required for script to run
#Scripted execution
$ScriptPath = (Split-Path ((Get-Variable MyInvocation).Value).MyCommand.Path)
#Getting date (logging,timestamps, etc.)
$today = Get-Date
$launchTime = $today.ToString('ddMMyyyy-hhmm')
#Setting the report filename
$reportHtmlFile = $reportHtmlDir+"\$($reportHtmlName)_"+$launchTime+".html"
#Importing required assets
. ("$($ScriptPath)\GlobalVariables.ps1")
. ("$($ScriptPath)\StyleCSS.ps1")
. ("$($ScriptPath)\HtmlCode.ps1")
. ("$($ScriptPath)\Functions.ps1")
#Region Hosts
#Getting Host infos
if ($clusterDeployment)
{
#Doing stuff for cluster env - Coming soon...
}
else
{
$vmHostsList = @()
$vmHost = Get-VMHost
$hostInfos = Get-CimInstance Win32_OperatingSystem
$vhdPathDrive = (Get-VMHost).VirtualHardDiskPath.Split(":")[0]
$hypervInfo = [PSCustomObject]@{
Name = $vmHost.ComputerName
LogicalCPU = $vmHost.LogicalProcessorCount
RAM_GB = [System.Math]::round((($vmHost.MemoryCapacity)/1GB),2)
Free_RAM_GB = [System.Math]::round((($hostInfos).FreePhysicalMemory/1MB),2)
VHD_Volume = $vhdPathDrive+":"
Free_VHD_Vol_GB = [System.Math]::round(((Get-Volume $vhdPathDrive).SizeRemaining/1GB),2)
OsVersion = $hostInfos.Version
}
$vmHostsList += $hypervInfo
}
Write-Host -ForegroundColor Cyan "###### Hyper-V Hosts infos ######"
$vmHostsList | Format-Table
#endregion
#Region VMs
#Getting VMs detailed infos
$vmsList =@()
$vms = Get-VM
foreach ($vm in $vms)
{
$vmInfo = [PSCustomObject]@{
Name = $vm.Name
Gen = $vm.Generation
Version = $vm.Version
vCPU = $vm.ProcessorCount
RAM_Assigned = [System.Math]::round((($vm.MemoryAssigned)/1GB),2)
RAM_Demand = [System.Math]::round((($vm.MemoryDemand)/1GB),2)
Dyn_Memory = $vm.DynamicMemoryEnabled
IP_Addr_NIC0 = $vm.NetworkAdapters[0].IPAddresses[0]
Snapshots = (Get-VMSnapshot -VMName $vm.Name).Count
State = $vm.State
Heartbeat = $vm.Heartbeat
Uptime = $vm.Uptime.ToString("dd\.hh\:mm")
Replication = $vm.ReplicationState
Creation_Time = $vm.CreationTime
}
$vmsList += $vmInfo
}
Write-Host -ForegroundColor Cyan "###### Virtual Machines infos ######"
$vmsList | Format-Table
#endregion
#Region Snapshots
#Getting Snapshots
$vmSnapshotsList = @()
foreach ($vm in $vms)
{
$foundSnapshots = Get-VMSnapshot -VMName $vm.name
if (($foundSnapshots).Count -gt 0)
{
foreach ($foundSnapshot in $foundSnapshots)
{
$snapInfo = [PSCustomObject]@{
VM = $foundSnapshot.VMName
Name = $foundSnapshot.Name
Creation_Time = $foundSnapshot.CreationTime
Age_Days = $today.Subtract($foundSnapshot.CreationTime).Days
Parent_Snap = $foundSnapshot.ParentSnapshotName
}
$vmSnapshotsList += $snapInfo
}
}
else
{
$snapInfo = [PSCustomObject]@{
VM = $vm.VMName
Name = "No snapshots found"
Creation_Time = "N/A"
Age_Days = "N/A"
Parent_Snap = "No snapshots found"
}
$vmSnapshotsList += $snapInfo
}
}
Write-Host -ForegroundColor Cyan "###### Snapshots infos ######"
$vmSnapshotsList | Format-Table
#endregion
#Region replication
if($replicationInfoNeeded)
{
$replicationsList = @()
#Getting the Replication status
$replicatedVms = ($vm | Where-Object {$_.ReplicationState -ne "Disabled"})
if (($replicatedVms).Count -gt 0)
{
foreach ($vm in $replicatedVms)
{
$replication = Get-VmReplication -Vmname $vm.VMName |`
Select-Object Name,State,Health,LastReplicationTime,PrimaryServer,ReplicaServer,AuthType
$replicationsList += $replication
}
Write-Host -ForegroundColor Cyan "###### Replication infos ######"
$replicationsList | Format-Table
}
#Creating a dummy object to correctly format the HTML report with no replications
else
{
$statusMsg = "No replicated VMs found!"
Write-Host -ForegroundColor Cyan "###### Replication infos ######"
Write-Host -ForegroundColor Yellow $statusMsg
$noReplicationInfo = [PSCustomObject]@{
Replication_Infos = $statusMsg
}
$replicationsList += $noReplicationInfo
}
}
#endregion
#Region VHDX
if ($vhdxInfoNeeded)
{
$vhdxList = @()
foreach ($vm in $vms)
{
$vhdxs = Get-VHD -VMId $vm.VMId |`
Select-Object ComputerName,Path,VhdFormat,VhdType,FileSize,Size,FragmentationPercentage
foreach ($vhdx in $vhdxs)
{
$vhdxInfo = [PSCustomObject]@{
Host = $vhdx.ComputerName
Path = $vhdx.Path
Format = $vhdx.VhdFormat
Type = $vhdx.VhdType
File_Size_GB = [System.Math]::round(($vhdx.FileSize/1GB),2)
Size_GB = $vhdx.Size/1GB
Frag_Perc = $vhdx.FragmentationPercentage
}
$vhdxList += $vhdxInfo
}
}
Write-Host -ForegroundColor Cyan "###### VHDX infos ######"
$vhdxList | Format-Table
}
#endregion
#Region VMNetworkAdapter
if ($vmnetInfoNeeded)
{
$vmnetAdapterList = @()
foreach ($vm in $vms)
{
$vmnetadapts = Get-VMNetworkAdapter -vm $vm |`
Select-Object MacAddress,Connected,VMName,IsSynthetic,IPAddresses,SwitchName,Status,VlanSetting
foreach ($vmnetadapt in $vmnetadapts)
{
$vmnetAdaptInfo = [PSCustomObject]@{
VM = $vmnetadapt.VMName
MAC = $vmnetadapt.MacAddress
IP_Addr = $vmnetadapt.IPAddresses | Out-String
Connected = $vmnetadapt.Connected
vSwitch = $vmnetadapt.SwitchName
#Status = $vmnetadapt.Status.
Vlan_Mode = $vmnetadapt.VlanSetting.OperationMode
Vlan_Id = $vmnetadapt.VlanSetting.AccessVlanId
}
$vmnetAdapterList += $vmnetAdaptInfo
}
}
Write-Host -ForegroundColor Cyan "###### VM Net Adapters infos ######"
$vmnetAdapterList | Format-Table
}
#endregion
#Region Management OS NetworkAdapter
if ($osNetInfoNeeded)
{
$osNetAdapterList = @()
$osNetadapts = Get-VMNetworkAdapter -ManagementOS |`
Select-Object Name,MacAddress,IPAddresses,SwitchName,Status,VlanSetting
foreach ($osNetadapt in $osNetadapts)
{
$osNetAdaptInfo = [PSCustomObject]@{
Name = $osNetadapt.Name
MAC = $osNetadapt.MacAddress
IP_Addr = Get-MgmtOsNicIpAddr -adapterName $osNetadapt.Name
vSwitch = $osNetadapt.SwitchName
Status = $osNetadapt.Status | Out-String
Vlan_Mode = $osNetadapt.VlanSetting.OperationMode
Vlan_Id = $osNetadapt.VlanSetting.AccessVlanId
}
$osNetAdapterList += $osNetAdaptInfo
}
Write-Host -ForegroundColor Cyan "###### Management OS Adapters infos ######"
$osNetAdapterList | Format-Table
}
#endregion
#Region VirtualSwitch
if ($vswitchInfoNeeded)
{
$vswitchList = @()
$vswitches = Get-VMSwitch | `
Select-Object ComputerName,Name,EmbeddedTeamingEnabled,SwitchType,AllowManagementOS
foreach ($vswitch in $vswitches)
{
$vswitchInfo = [PSCustomObject]@{
Host = $vswitch.ComputerName
Virtual_Switch = $vswitch.Name
SET = $vswitch.EmbeddedTeamingEnabled
Uplinks = Get-VswitchMember -vswitch $vswitch.Name
Type = $vswitch.SwitchType
Mgmt_OS_Allowed = $vswitch.AllowManagementOS
}
$vswitchList += $vswitchInfo
}
Write-Host -ForegroundColor Cyan "###### Virtual Switches infos ######"
$vswitchList | Format-Table
}
#endregion
#Creating the HTML report
if ($reportHtmlRequired)
{
$dataHTML =@()
$vmhostsHTML = $preContent + $titleHtmlHosts + ($vmHostsList | ConvertTo-Html -Fragment)
$dataHTML += $vmhostsHTML
$vmsHTML = $titleHtmlVms + ($vmsList | ConvertTo-Html -Fragment)
$dataHTML += $vmsHTML
$snapshotsHTML = $titleHtmlSnapshots + ($vmSnapshotsList | ConvertTo-Html -Fragment)
$dataHTML += $snapshotsHTML
if ($replicationInfoNeeded)
{
$replicationHTML = $titleHtmlReplication + ($replicationsList | ConvertTo-Html -Fragment)
$dataHTML += $replicationHTML
}
if ($vhdxList)
{
$vhdxListHTML = $titleHtmlVhdx + ($vhdxList | ConvertTo-Html -Fragment)
$dataHTML += $vhdxListHTML
}
if ($vmnetInfoNeeded)
{
$vmnetAdapterListHTML = $titleHtmlVmnetAdapter + ($vmnetAdapterList | ConvertTo-Html -Fragment)
$dataHTML += $vmnetAdapterListHTML
}
if ($osNetInfoNeeded)
{
$osNetAdapterListHTML = $titleHtmlOsNetAdapter + ($osNetAdapterList | ConvertTo-Html -Fragment)
$dataHTML += $osNetAdapterListHTML
}
if ($vswitchInfoNeeded)
{
$vswitchListHTML = $titleHtmlVswitch + ($vswitchList | ConvertTo-Html -Fragment)
$dataHTML += $vswitchListHTML
}
$htmlReport = ConvertTo-Html -Head $header -Title $title -PostContent $postContent -Body $dataHTML
$htmlReport | Out-File $reportHtmlFile
}
#Sending the report via email
if ($emailReport -and $reportHtmlRequired)
{
switch ($emailSystem) {
msgraph
{ SendEmailReport-MSGraph -body (Out-String -InputObject $htmlReport) }
mailkit
{ SendEmailReport-Mailkit -body (Out-String -InputObject $htmlReport) }
Default {Write-Host -ForegroundColor Yellow "You must select an email system, msgraph or mailkit"}
}
}

91
README.md Normal file
View File

@@ -0,0 +1,91 @@
# Hyper-V Report Script Documentation
## Overview
This PowerShell script automates the collection of detailed information about a Hyper-V environment, including hosts, virtual machines, snapshots, replication status, virtual hard disks (VHDX), network adapters, and virtual switches. It generates an HTML report and optionally sends it via email.
---
## Features
- Collects host system information
- Enumerates all virtual machines and their configurations
- Lists VM snapshots and calculates their age
- Reports replication status for VMs
- Gathers VHDX file details
- Extracts VM and management OS network adapter data
- Lists virtual switch configurations
- Generates a comprehensive HTML report
- Sends the report via email using MS Graph or MailKit
---
## Prerequisites
- PowerShell 5.1 or later
- Hyper-V role installed
- Required modules:
- `Hyper-V`
- `CimCmdlets`
- External scripts in the same directory:
- `GlobalVariables.ps1`
- `StyleCSS.ps1`
- `HtmlCode.ps1`
- `Functions.ps1`
---
## Script Parameters
These are expected to be defined in `GlobalVariables.ps1`:
- `$reportHtmlDir` Directory to save the HTML report
- `$reportHtmlName` Base name for the report file
- `$clusterDeployment` Boolean flag for cluster support
- `$replicationInfoNeeded` Boolean flag to include replication info
- `$vhdxInfoNeeded` Boolean flag to include VHDX info
- `$vmnetInfoNeeded` Boolean flag to include VM network adapter info
- `$osNetInfoNeeded` Boolean flag to include management OS network adapter info
- `$vswitchInfoNeeded` Boolean flag to include virtual switch info
- `$reportHtmlRequired` Boolean flag to generate HTML report
- `$emailReport` Boolean flag to send report via email
- `$emailSystem` Email system to use (`msgraph` or `mailkit`)
---
## Output
- **HTML Report**: Saved in `$reportHtmlDir` with timestamped filename.
- **Console Output**: Displays formatted tables for each section.
- **Email**: Sent if `$emailReport` is enabled and `$reportHtmlRequired` is true.
---
## Usage
```powershell
.\Hyper-V-Report.ps1
```
Ensure all required variables and modules are properly configured before execution.
---
## Sections in the Report
1. **Host Info** CPU, RAM, OS version, VHD volume stats
2. **VM Info** Name, generation, memory, IP, state, uptime, replication
3. **Snapshots** Snapshot name, age, parent snapshot
4. **Replication** Status, health, last replication time
5. **VHDX Info** Format, type, size, fragmentation
6. **VM Network Adapters** MAC, IP, vSwitch, VLAN
7. **Management OS Adapters** IP, MAC, vSwitch, VLAN
8. **Virtual Switches** Name, type, uplinks, SET status
---
## Notes
- Cluster support is marked as "Coming soon".
- Email system must be explicitly selected (`msgraph` or `mailkit`).

40
StyleCSS.ps1 Normal file
View File

@@ -0,0 +1,40 @@
$header = @"
<style>
body
{
background-color: White;
font-size: 12px;
font-family: Arial, Helvetica, sans-serif;
}
table {
border: 0.5px solid;
border-collapse: collapse;
width: 100%;
}
th {
background-color: CornflowerBlue;
color: white;
padding: 6px;
border: 0.5px solid;
border-color: #000000;
}
tr:nth-child(even) {
background-color: #f5f5f5;
}
td {
padding: 6px;
margin: 0px;
border: 1px solid;
}
h2{
background-color: CornflowerBlue;
color:white;
text-align: center;
}
</style>
"@