commit 8f6b1072a328a7e9d1e50598947bd370e64a529a
Author: Federico Lillacci <65031947+tsmagnum@users.noreply.github.com>
Date: Fri Oct 17 13:03:35 2025 +0200
Add files via upload
diff --git a/Functions.ps1 b/Functions.ps1
new file mode 100644
index 0000000..ca33609
--- /dev/null
+++ b/Functions.ps1
@@ -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
+}
\ No newline at end of file
diff --git a/GlobalVariables.ps1 b/GlobalVariables.ps1
new file mode 100644
index 0000000..e642737
--- /dev/null
+++ b/GlobalVariables.ps1
@@ -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"
diff --git a/HtmlCode.ps1 b/HtmlCode.ps1
new file mode 100644
index 0000000..84bf574
--- /dev/null
+++ b/HtmlCode.ps1
@@ -0,0 +1,15 @@
+$preContent = "
Hyper-V Status Report
"
+$postContent = "Creation Date: $($today | Out-String) -
+ Hyper-V-Report ($scriptVersion) by F. Lillacci
"
+
+$title = "Hyper-V Status Report"
+$breakHtml = ""
+
+$titleHtmlHosts = "
Hyper-V Server
"
+$titleHtmlVms = "Virtual Machines
"
+$titleHtmlSnapshots = "Snapshots
"
+$titleHtmlReplication = "Replication
"
+$titleHtmlVhdx = "VHDX Disks
"
+$titleHtmlVmnetAdapter = "VM Network Adatpers
"
+$titleHtmlOsNetAdapter = "Management OS Network Adatpers
"
+$titleHtmlVswitch = "Virtual Switches
"
\ No newline at end of file
diff --git a/Hyper-V-Report.ps1 b/Hyper-V-Report.ps1
new file mode 100644
index 0000000..fca7c2e
--- /dev/null
+++ b/Hyper-V-Report.ps1
@@ -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"}
+ }
+
+ }
\ No newline at end of file
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..addd5e7
--- /dev/null
+++ b/README.md
@@ -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`).
\ No newline at end of file
diff --git a/StyleCSS.ps1 b/StyleCSS.ps1
new file mode 100644
index 0000000..9a5038b
--- /dev/null
+++ b/StyleCSS.ps1
@@ -0,0 +1,40 @@
+$header = @"
+
+"@
\ No newline at end of file