mirror of
https://github.com/tsmagnum/Hyper-V-Report.git
synced 2026-04-17 15:43:26 +02:00
Add files via upload
This commit is contained in:
committed by
GitHub
parent
5e31775980
commit
97a4703253
17
CHANGELOG.md
17
CHANGELOG.md
@@ -6,7 +6,22 @@ The format is based on https://keepachangelog.com/en/1.0.0/, and this project ad
|
||||
|
||||
Placeholder for upcoming changes.
|
||||
|
||||
## [1.0.0] - 2025-10-23
|
||||
## [2.0.0] - 2025-10-27
|
||||
|
||||
### Added
|
||||
Support for clustered environments (currently only AD clusters).
|
||||
Option to securely store SMTP credentials.
|
||||
CSS templates for styling the report.
|
||||
|
||||
### Fixed
|
||||
N/A
|
||||
|
||||
### Changed
|
||||
Information gathering features moved to the Functions.ps1 file.
|
||||
CSS stylesheets moved to the Style folder.
|
||||
|
||||
|
||||
## [1.0.0] - 2025-10-17
|
||||
|
||||
### Added
|
||||
Initial release of the project.
|
||||
|
||||
464
Functions.ps1
464
Functions.ps1
@@ -1,12 +1,357 @@
|
||||
#DO NOT MODIFY
|
||||
function Get-VmHostInfo
|
||||
{
|
||||
[CmdletBinding()]
|
||||
Param(
|
||||
[Parameter(Mandatory = $true)] $vmHosts
|
||||
)
|
||||
|
||||
$vmHostsList = [System.Collections.ArrayList]@()
|
||||
|
||||
foreach ($vmHost in $vmHosts)
|
||||
{
|
||||
|
||||
$hostInfos = Get-CimInstance Win32_OperatingSystem -ComputerName $vmHost.ComputerName
|
||||
$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 -CimSession $vmHost.ComputerName).SizeRemaining/1GB),2)
|
||||
LiveMig = $vmHost.VirtualMachineMigrationEnabled
|
||||
Last_Boot = $hostInfos.LastBootUpTime.ToString('dd/MM/yy HH:mm')
|
||||
OsBuild = Get-OsBuildLevel -vmHost $vmHost.ComputerName
|
||||
}
|
||||
|
||||
[void]$vmHostsList.Add($hypervInfo)
|
||||
}
|
||||
|
||||
return $vmHostsList
|
||||
}
|
||||
|
||||
function Get-VHDXInfo {
|
||||
[CmdletBinding()]
|
||||
Param(
|
||||
[Parameter(Mandatory = $true)] $vms
|
||||
)
|
||||
|
||||
$vhdxList = [System.Collections.ArrayList]@()
|
||||
|
||||
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
|
||||
VM = $vm.VMName
|
||||
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
|
||||
}
|
||||
|
||||
[void]$vhdxList.Add($vhdxInfo)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return $vhdxList
|
||||
|
||||
}
|
||||
|
||||
function Get-VmnetInfo {
|
||||
[CmdletBinding()]
|
||||
Param(
|
||||
[Parameter(Mandatory = $true)] $vms
|
||||
)
|
||||
|
||||
$vmnetAdapterList = [System.Collections.ArrayList]@()
|
||||
|
||||
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
|
||||
|
||||
}
|
||||
|
||||
[void]$vmnetAdapterList.Add($vmnetAdaptInfo)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return $vmnetAdapterList
|
||||
|
||||
}
|
||||
function Get-CsvHealth
|
||||
{
|
||||
[CmdletBinding()]
|
||||
Param(
|
||||
[Parameter(Mandatory = $true)] $clusterNodes
|
||||
)
|
||||
|
||||
$csvHealthList = [System.Collections.ArrayList]@()
|
||||
|
||||
foreach ($clusterNode in $clusterNodes)
|
||||
{
|
||||
$csvHealth = Get-ClusterSharedVolumeState -Node $clusterNode.NodeName |`
|
||||
Select-Object Node,VolumeFriendlyName,Name,StateInfo
|
||||
|
||||
foreach ($csv in $csvHealth)
|
||||
{
|
||||
$csvHealthVol = [PSCustomObject]@{
|
||||
Node = $csv.Node
|
||||
Volume = $csv.VolumeFriendlyName
|
||||
Disk = $csv.Name
|
||||
State = $csv.StateInfo
|
||||
}
|
||||
|
||||
[void]$csvHealthList.Add($csvHealthVol)
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
return $csvHealthList
|
||||
}
|
||||
|
||||
function Get-CsvSpaceUtilization {
|
||||
|
||||
[CmdletBinding()]
|
||||
|
||||
$csvSpaceList = [System.Collections.ArrayList]@()
|
||||
|
||||
$csvVolumes = Get-Volume | Where-Object {$_.FileSystem -match "CSVFS"} |`
|
||||
Select-Object FileSystemLabel,HealthStatus,Size,SizeRemaining
|
||||
|
||||
foreach ($csvVolume in $csvVolumes)
|
||||
{
|
||||
$csvInfo = [PSCustomObject]@{
|
||||
Volume = $csvVolume.FileSystemLabel
|
||||
Health = $csvVolume.HealthStatus
|
||||
Size_GB = [System.Math]::round(($csvVolume.Size/1GB),2)
|
||||
Free_GB = [System.Math]::round(($csvVolume.SizeRemaining/1GB),2)
|
||||
Used_Perc = [System.Math]::round(((($csvVolume.Size - $csvVolume.SizeRemaining)/$csvVolume.Size)*100),2)
|
||||
}
|
||||
|
||||
[void]$csvSpaceList.Add($csvInfo)
|
||||
}
|
||||
|
||||
return $csvSpaceList
|
||||
|
||||
}
|
||||
|
||||
function Get-VmInfo
|
||||
{
|
||||
[CmdletBinding()]
|
||||
Param(
|
||||
[Parameter(Mandatory = $true)] $vm
|
||||
)
|
||||
|
||||
$vmInfo = [PSCustomObject]@{
|
||||
Name = $vm.Name
|
||||
Host = $vm.ComputerName
|
||||
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
|
||||
Clustered = $vm.IsClustered
|
||||
State = $vm.State
|
||||
Heartbeat = $vm.Heartbeat
|
||||
Uptime = $vm.Uptime.ToString("dd\.hh\:mm")
|
||||
Replication = $vm.ReplicationState
|
||||
Creation_Time = $vm.CreationTime
|
||||
}
|
||||
|
||||
return $vmInfo
|
||||
}
|
||||
|
||||
function Get-SnapshotInfo {
|
||||
[CmdletBinding()]
|
||||
Param(
|
||||
[Parameter(Mandatory = $true)] $vms
|
||||
)
|
||||
|
||||
$vmSnapshotsList = [System.Collections.ArrayList]@()
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
[void]$vmSnapshotsList.Add($snapInfo)
|
||||
}
|
||||
}
|
||||
|
||||
else
|
||||
{
|
||||
$snapInfo = [PSCustomObject]@{
|
||||
VM = $vm.VMName
|
||||
Name = "No snapshots found"
|
||||
Creation_Time = "N/A"
|
||||
Age_Days = "N/A"
|
||||
Parent_Snap = "No snapshots found"
|
||||
}
|
||||
|
||||
[void]$vmSnapshotsList.Add($snapInfo)
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
return $vmSnapshotsList
|
||||
}
|
||||
|
||||
function Get-ReplicationInfo {
|
||||
|
||||
[CmdletBinding()]
|
||||
Param(
|
||||
[Parameter(Mandatory = $true)] $vms
|
||||
)
|
||||
|
||||
$replicationsList = [System.Collections.ArrayList]@()
|
||||
#Getting the Replication status
|
||||
$replicatedVms = ($vms | Where-Object {$_.ReplicationState -ne "Disabled"} | Select-Object -Unique)
|
||||
if (($replicatedVms).Count -gt 0)
|
||||
{
|
||||
foreach ($vm in $replicatedVms)
|
||||
{
|
||||
$replication = Get-VmReplication -Vmname $vm.VMName |`
|
||||
Select-Object Name,State,Health,LastReplicationTime,PrimaryServer,ReplicaServer,AuthType
|
||||
[void]$replicationsList.Add($replication)
|
||||
}
|
||||
}
|
||||
|
||||
#Creating a dummy object to correctly format the HTML report with no replications
|
||||
else
|
||||
{
|
||||
$statusMsg = "No replicated VMs found!"
|
||||
$noReplicationInfo = [PSCustomObject]@{
|
||||
Replication_Infos = $statusMsg
|
||||
}
|
||||
[void]$replicationsList.Add($noReplicationInfo)
|
||||
}
|
||||
|
||||
return $replicationsList
|
||||
}
|
||||
|
||||
function Get-OsNetAdapterInfo
|
||||
{
|
||||
[CmdletBinding()]
|
||||
Param(
|
||||
[Parameter(Mandatory = $true)] $vmHosts
|
||||
)
|
||||
|
||||
$osNetadaptsList = @()
|
||||
|
||||
foreach ($vmHost in $vmHosts)
|
||||
{
|
||||
$osNetadaptsArray = @()
|
||||
|
||||
$osNetadapts = Get-VMNetworkAdapter -ManagementOS -ComputerName $vmHost.Name |`
|
||||
Select-Object ComputerName,Name,MacAddress,IPAddresses,SwitchName,Status,VlanSetting
|
||||
|
||||
foreach ($osNetadapt in $osNetadapts)
|
||||
{
|
||||
$osNetAdaptInfo = [PSCustomObject]@{
|
||||
Host = $osNetadapt.ComputerName
|
||||
Name = $osNetadapt.Name
|
||||
MAC = $osNetadapt.MacAddress
|
||||
IP_Addr = Get-MgmtOsNicIpAddr -adapterName $osNetadapt.Name -vmHost $vmHost
|
||||
vSwitch = $osNetadapt.SwitchName
|
||||
Status = $osNetadapt.Status | Out-String
|
||||
Vlan_Mode = $osNetadapt.VlanSetting.OperationMode
|
||||
Vlan_Id = $osNetadapt.VlanSetting.AccessVlanId
|
||||
}
|
||||
|
||||
$osNetadaptsArray += $osNetAdaptInfo
|
||||
}
|
||||
|
||||
$osNetadaptsList += $osNetadaptsArray
|
||||
|
||||
}
|
||||
|
||||
return $osNetadaptsList
|
||||
}
|
||||
|
||||
function Get-VswitchInfo
|
||||
{
|
||||
[CmdletBinding()]
|
||||
Param(
|
||||
[Parameter(Mandatory = $true)] $vmHosts
|
||||
)
|
||||
|
||||
$vswitchesList = @()
|
||||
|
||||
foreach ($vmHost in $vmHosts)
|
||||
{
|
||||
$vswitches = Get-VMSwitch -ComputerName $vmHost.Name | `
|
||||
Select-Object ComputerName,Name,EmbeddedTeamingEnabled,SwitchType,AllowManagementOS
|
||||
|
||||
$vswitchesArray = @()
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
$vswitchesArray += $vswitchInfo
|
||||
}
|
||||
|
||||
$vswitchesList += $vswitchesArray
|
||||
}
|
||||
|
||||
return $vswitchesList
|
||||
}
|
||||
|
||||
function Get-MgmtOsNicIpAddr
|
||||
{
|
||||
[CmdletBinding()]
|
||||
Param(
|
||||
[Parameter(Mandatory = $true)] $adapterName
|
||||
[Parameter(Mandatory = $true)] $adapterName,
|
||||
[Parameter(Mandatory = $true)] $vmHost
|
||||
)
|
||||
|
||||
$ipAddr = (Get-NetIPAddress | Where-Object {$_.InterfaceAlias -like "*$($adapterName)*" -and $_.AddressFamily -eq 'IPv4'}).IPAddress
|
||||
$ipAddr = (Get-NetIPAddress -CimSession $vmHost.Name |`
|
||||
Where-Object {$_.InterfaceAlias -like "*($($adapterName))" -and $_.AddressFamily -eq 'IPv4'}).IPAddress
|
||||
|
||||
return $ipAddr
|
||||
}
|
||||
@@ -22,12 +367,12 @@ function Get-VswitchMember
|
||||
|
||||
$vswitchMembers = ($targetSwitch.NetAdapterInterfaceDescriptions)
|
||||
|
||||
$physNics = @()
|
||||
$physNics = [System.Collections.ArrayList]@()
|
||||
|
||||
foreach ($vswitchMember in $vswitchMembers)
|
||||
{
|
||||
$physNic = (Get-Netadapter -InterfaceDescription $vswitchMember).Name
|
||||
$physNics += $physNic
|
||||
[void]$physNics.Add($physNic)
|
||||
}
|
||||
|
||||
$vswitchPhysNics = $physNics | Out-String
|
||||
@@ -36,6 +381,101 @@ function Get-VswitchMember
|
||||
|
||||
}
|
||||
|
||||
function Get-ClusterConfigInfo {
|
||||
|
||||
[CmdletBinding()]
|
||||
Param(
|
||||
[Parameter(Mandatory = $false)] $cluster = "."
|
||||
)
|
||||
|
||||
$cluster = Get-Cluster -Name $cluster | Select-Object Name,Domain
|
||||
|
||||
$clusterConfigInfoList = [PSCustomObject]@{
|
||||
|
||||
Cluster_Name = $cluster.Name
|
||||
Cluster_Domain = $cluster.Domain
|
||||
}
|
||||
|
||||
return $clusterConfigInfoList
|
||||
|
||||
}
|
||||
|
||||
function Get-ClusterNetworksInfo {
|
||||
|
||||
[CmdletBinding()]
|
||||
Param(
|
||||
[Parameter(Mandatory = $false)] $cluster = "."
|
||||
)
|
||||
|
||||
$clusterNets = Get-ClusterNetwork -Cluster $cluster | Select-Object Name,Role,State,Address,Autometric,Metric
|
||||
|
||||
$clusterNetworksList = [System.Collections.ArrayList]@()
|
||||
|
||||
foreach ($clusterNet in $clusterNets)
|
||||
{
|
||||
$clusterNetInfo = [PSCustomObject]@{
|
||||
Name = $clusterNet.Name
|
||||
Role = $clusterNet.Role
|
||||
State = $clusterNet.State
|
||||
Address = $clusterNet.Address
|
||||
Autometric = $clusterNet.Autometric
|
||||
Metric = $clusterNet.Metric
|
||||
}
|
||||
|
||||
[void]$clusterNetworksList.Add($clusterNetInfo)
|
||||
}
|
||||
|
||||
return $clusterNetworksList
|
||||
|
||||
}
|
||||
|
||||
function Get-OsBuildLevel
|
||||
{
|
||||
[CmdletBinding()]
|
||||
Param(
|
||||
[Parameter(Mandatory = $true)] $vmHost
|
||||
)
|
||||
|
||||
$code = {
|
||||
|
||||
Try
|
||||
{
|
||||
$osBuildLevel = (Get-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion" -Name LCUver -Verbose).LCUVer
|
||||
return $osBuildLevel
|
||||
}
|
||||
Catch
|
||||
{
|
||||
$osBuildLevel = "N/A"
|
||||
return $osBuildLevel
|
||||
}
|
||||
}
|
||||
|
||||
if ($vmHost -eq $env:COMPUTERNAME)
|
||||
{
|
||||
$osBuildLevel = Invoke-Command -ScriptBlock $code
|
||||
}
|
||||
|
||||
else
|
||||
{
|
||||
$osBuildLevel = Invoke-Command -ComputerName $vmHost -ScriptBlock $code -Verbose
|
||||
}
|
||||
|
||||
|
||||
|
||||
return $osBuildLevel
|
||||
}
|
||||
|
||||
function Import-SafeCreds {
|
||||
[CmdletBinding()]
|
||||
Param(
|
||||
[Parameter(Mandatory = $true)] $encryptedSMTPCredsFile
|
||||
)
|
||||
|
||||
$credentials = Import-Clixml -Path $encryptedSMTPCredsFile
|
||||
return $credentials
|
||||
|
||||
}
|
||||
|
||||
function SendEmailReport-MSGraph
|
||||
{
|
||||
[CmdletBinding()]
|
||||
@@ -124,9 +564,17 @@ function SendEmailReport-Mailkit
|
||||
|
||||
$UseSecureConnectionIfAvailable = $true
|
||||
|
||||
$credential = `
|
||||
[System.Management.Automation.PSCredential]::new($smtpServerUser, `
|
||||
(ConvertTo-SecureString -String $smtpServerPwd -AsPlainText -Force))
|
||||
if ($encryptedSMTPCreds)
|
||||
{
|
||||
$Credentials = Import-SafeCreds -encryptedSMTPCredsFile $encryptedSMTPCredsFile
|
||||
}
|
||||
|
||||
else
|
||||
{
|
||||
$credentials = `
|
||||
[System.Management.Automation.PSCredential]::new($smtpServerUser, `
|
||||
(ConvertTo-SecureString -String $smtpServerPwd -AsPlainText -Force))
|
||||
}
|
||||
|
||||
$from = [MimeKit.MailboxAddress]$reportSender
|
||||
|
||||
@@ -141,7 +589,7 @@ function SendEmailReport-Mailkit
|
||||
|
||||
$Parameters = @{
|
||||
"UseSecureConnectionIfAvailable" = $UseSecureConnectionIfAvailable
|
||||
"Credential" = $credential
|
||||
"Credential" = $credentials
|
||||
"SMTPServer" = $smtpServer
|
||||
"Port" = $smtpServerPort
|
||||
"From" = $from
|
||||
|
||||
@@ -1,28 +1,31 @@
|
||||
#Script Info - do not modify the following line
|
||||
$scriptVersion = "1.0"
|
||||
#Script Info - please do not modify the following line
|
||||
$scriptVersion = "2.0"
|
||||
|
||||
#Cluster Environment - Coming soon...
|
||||
$clusterDeployment = $false
|
||||
$vmHosts = "hv1", "hv2"
|
||||
#Cluster Environment
|
||||
$clusterDeployment = $false #Set to $true for a clustered environment
|
||||
|
||||
#Reporting section
|
||||
$reportHtmlRequired = $true #Set to $true to generate an HTML report
|
||||
$reportStyle = "prodark" #Choose between "minimal", "pro", "prodark" or "colorful"
|
||||
$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
|
||||
$csvHealthInfoNeeded = $true #Set to true to include CSV health details
|
||||
$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
|
||||
$clusterConfigInfoNeeded = $true #Set to true to include cluster config details
|
||||
$clusterNetworksInfoNeeded = $true #Set to true to include cluster networks 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)
|
||||
$reportSender = "mySender@domain.com" #Sender email address (use quotes)
|
||||
$reportRecipient = "myrecipient@domain.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
|
||||
|
||||
@@ -30,12 +33,22 @@ $subject = "Hyper-V Status Report" #Email subject line
|
||||
$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"
|
||||
#MailKit-specific email configuration
|
||||
$smtpServer = "mySmtp.server.com"
|
||||
$smtpServerPort = 587
|
||||
$smtpAuthRequired = $true
|
||||
#If SMTP authentication is required, set a username and password below.
|
||||
#It is recommended to use an encrypted XML file for SMTP credentials.
|
||||
#Run the Save-SafeCreds.ps1 script to store your credentials in an encrypted XML file.
|
||||
#Save the encrypted XML file in the script directory.
|
||||
#Set the following variable to $true and enter the path to the XML file.
|
||||
#Please note: only the user encrypting the creds will be able to decrypt them!
|
||||
$encryptedSMTPCreds = $true #set to true to use the encrypted XML file for the creds.
|
||||
$encryptedSMTPCredsFileName = "EncryptedCreds.xml" #name of the encrypted creds file.
|
||||
#If you prefer to store the credentials in plain text, set the username and password below.
|
||||
#and set $encryptedSMTPCreds to $false
|
||||
#
|
||||
#DO NOT USE A SENSITIVE OR PRIVILEGED ACCOUNT HERE!!!
|
||||
#This poses a security risk — use these credentials only for testing purposes.
|
||||
$smtpServerUser = "smtpserver.user"
|
||||
$smtpServerPwd = "mySecretPwd"
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
$preContent = "<h2>Hyper-V Status Report</h2>"
|
||||
$preContent = "<h1>Hyper-V Status Report</h1>"
|
||||
$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>"
|
||||
|
||||
@@ -6,10 +6,14 @@ $title = "Hyper-V Status Report"
|
||||
$breakHtml = "</br>"
|
||||
|
||||
$titleHtmlHosts = "<h3>Hyper-V Server</h3>"
|
||||
$titleHtmlcsvHealth = "<h3>CSV Health</h3>"
|
||||
$titleHtmlcsvSpace = "<h3>CSV Space Utilization</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>"
|
||||
$titleHtmlVswitch = "<h3>Virtual Switches</h3>"
|
||||
$titleHtmlClusterConfig = "<h3>Cluster Config</h3>"
|
||||
$titleHtmlClusterNetworks = "<h3>Cluster Networks</h3>"
|
||||
@@ -42,67 +42,106 @@ $launchTime = $today.ToString('ddMMyyyy-hhmm')
|
||||
|
||||
#Importing required assets
|
||||
. ("$($ScriptPath)\GlobalVariables.ps1")
|
||||
. ("$($ScriptPath)\StyleCSS.ps1")
|
||||
. ("$($ScriptPath)\HtmlCode.ps1")
|
||||
. ("$($ScriptPath)\Functions.ps1")
|
||||
|
||||
#Setting the report style
|
||||
switch ($reportStyle) {
|
||||
minimal
|
||||
{ $styleSheet = "StyleCSS-Minimal.ps1" }
|
||||
pro
|
||||
{ $styleSheet = "StyleCSS-Pro.ps1" }
|
||||
prodark
|
||||
{ $styleSheet = "StyleCSS-ProDark.ps1" }
|
||||
colorful
|
||||
{ $styleSheet = "StyleCSS-Colorful.ps1" }
|
||||
Default
|
||||
{ $styleSheet = "StyleCSS-Professional.ps1" }
|
||||
}
|
||||
|
||||
. ("$($ScriptPath)\Style\$styleSheet")
|
||||
|
||||
#Setting the report filename
|
||||
$reportHtmlFile = $reportHtmlDir+"\$($reportHtmlName)_"+$launchTime+".html"
|
||||
|
||||
#Setting the encrypted creds file path
|
||||
if ($emailReport)
|
||||
{
|
||||
$encryptedSMTPCredsFile = "$($ScriptPath)\$encryptedSMTPCredsFileName"
|
||||
}
|
||||
|
||||
#Region Hosts
|
||||
$vmHosts = [System.Collections.ArrayList]@()
|
||||
|
||||
#Getting Host infos
|
||||
if ($clusterDeployment)
|
||||
{
|
||||
#Doing stuff for cluster env - Coming soon...
|
||||
$clusterNodes = Get-ClusterNode -Cluster .
|
||||
|
||||
foreach ($clusterNode in $clusterNodes)
|
||||
{
|
||||
$vmHost = Get-VMHost -ComputerName $clusterNode.NodeName
|
||||
|
||||
[void]$vmHosts.Add($vmHost)
|
||||
}
|
||||
|
||||
$vmHostsList = Get-VmHostInfo -vmHosts $vmHosts
|
||||
}
|
||||
|
||||
#Non clustered deployments
|
||||
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
|
||||
$vmHostsList = Get-VmHostInfo -vmHosts $vmHost
|
||||
}
|
||||
|
||||
Write-Host -ForegroundColor Cyan "###### Hyper-V Hosts infos ######"
|
||||
$vmHostsList | Format-Table
|
||||
|
||||
#endregion
|
||||
|
||||
#Region CSV Health - Only for clustered environments
|
||||
if (($clusterDeployment) -and ($csvHealthInfoNeeded))
|
||||
{
|
||||
$csvHealthList = Get-CsvHealth -clusterNodes $clusterNodes
|
||||
|
||||
Write-Host -ForegroundColor Cyan "###### CSV Health Info ######"
|
||||
$csvHealthList | Format-Table
|
||||
}
|
||||
#endregion
|
||||
|
||||
#Region CSV Space Utilization - Only for clustered environments
|
||||
if ($clusterDeployment)
|
||||
{
|
||||
$csvSpaceList = Get-CsvSpaceUtilization
|
||||
|
||||
Write-Host -ForegroundColor Cyan "###### CSV Space Utilization ######"
|
||||
$csvSpaceList | Format-Table
|
||||
}
|
||||
#endregion
|
||||
|
||||
#Region VMs
|
||||
#Getting VMs detailed infos
|
||||
$vmsList =@()
|
||||
$vms = Get-VM
|
||||
|
||||
if ($clusterDeployment)
|
||||
{
|
||||
$vms = foreach ($clusterNode in $clusterNodes)
|
||||
{
|
||||
Get-VM -ComputerName $clusterNode.NodeName
|
||||
}
|
||||
}
|
||||
|
||||
#Non clustered deployments
|
||||
else
|
||||
{
|
||||
$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
|
||||
}
|
||||
$vmInfo = Get-VmInfo -vm $vm
|
||||
|
||||
$vmsList += $vmInfo
|
||||
}
|
||||
@@ -114,108 +153,28 @@ $vmsList | Format-Table
|
||||
|
||||
#Region Snapshots
|
||||
#Getting Snapshots
|
||||
$vmSnapshotsList = @()
|
||||
$vmSnapshotsList = Get-SnapshotInfo -vms $vms
|
||||
|
||||
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
|
||||
}
|
||||
$replicationsList = Get-ReplicationInfo -vms $vms
|
||||
|
||||
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
|
||||
VM = $vm.VMName
|
||||
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
|
||||
}
|
||||
|
||||
}
|
||||
$vhdxList = Get-VHDXInfo -vms $vms
|
||||
|
||||
Write-Host -ForegroundColor Cyan "###### VHDX infos ######"
|
||||
$vhdxList | Format-Table
|
||||
}
|
||||
@@ -224,31 +183,8 @@ if ($vhdxInfoNeeded)
|
||||
#Region VMNetworkAdapter
|
||||
if ($vmnetInfoNeeded)
|
||||
{
|
||||
$vmnetAdapterList = @()
|
||||
$vmnetAdapterList = Get-VmnetInfo -vms $vms
|
||||
|
||||
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
|
||||
|
||||
@@ -258,27 +194,15 @@ if ($vmnetInfoNeeded)
|
||||
#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
|
||||
}
|
||||
if ($clusterDeployment)
|
||||
{
|
||||
$osNetAdapterList = Get-OsNetAdapterInfo -vmHost $vmHosts
|
||||
}
|
||||
|
||||
else
|
||||
{
|
||||
$osNetAdapterList = Get-OsNetAdapterInfo -vmHost $vmHost
|
||||
}
|
||||
|
||||
Write-Host -ForegroundColor Cyan "###### Management OS Adapters infos ######"
|
||||
$osNetAdapterList | Format-Table
|
||||
@@ -289,73 +213,110 @@ if ($osNetInfoNeeded)
|
||||
#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
|
||||
}
|
||||
|
||||
if ($clusterDeployment)
|
||||
{
|
||||
$vswitchesList = Get-VswitchInfo -vmHost $vmhosts
|
||||
}
|
||||
|
||||
else
|
||||
{
|
||||
$vswitchesList = Get-VswitchInfo -vmHost $vmhost
|
||||
}
|
||||
|
||||
Write-Host -ForegroundColor Cyan "###### Virtual Switches infos ######"
|
||||
$vswitchList | Format-Table
|
||||
$vswitchesList | Format-Table
|
||||
}
|
||||
#endregion
|
||||
|
||||
#Region Cluster Configuration Info
|
||||
if ($clusterDeployment -and $clusterConfigInfoNeeded)
|
||||
{
|
||||
$clusterConfigInfoList = Get-ClusterConfigInfo
|
||||
|
||||
Write-Host -ForegroundColor Cyan "###### Cluster config infos ######"
|
||||
$clusterConfigInfoList | Format-Table
|
||||
}
|
||||
#endregion
|
||||
|
||||
#Region Cluster Networks Info
|
||||
if ($clusterDeployment -and $clusterNetworksInfoNeeded)
|
||||
{
|
||||
$clusterNetworksList = Get-ClusterNetworksInfo
|
||||
|
||||
Write-Host -ForegroundColor Cyan "###### Cluster networks infos ######"
|
||||
$clusterNetworksList | Format-Table
|
||||
}
|
||||
#endregion
|
||||
|
||||
############### Report and Email ###############
|
||||
|
||||
#Creating the HTML report
|
||||
if ($reportHtmlRequired)
|
||||
{
|
||||
$dataHTML =@()
|
||||
$dataHTML = [System.Collections.ArrayList]@()
|
||||
|
||||
$vmhostsHTML = $preContent + $titleHtmlHosts + ($vmHostsList | ConvertTo-Html -Fragment)
|
||||
$dataHTML += $vmhostsHTML
|
||||
[void]$dataHTML.Add($vmhostsHTML)
|
||||
|
||||
if (($clusterDeployment) -and ($csvHealthInfoNeeded))
|
||||
{
|
||||
$csvHealthHTML = $titleHtmlcsvHealth + ($csvHealthList | ConvertTo-Html -Fragment)
|
||||
[void]$dataHTML.Add($csvHealthHTML)
|
||||
}
|
||||
|
||||
if ($clusterDeployment)
|
||||
{
|
||||
$csvSpaceHTML = $titleHtmlcsvSpace + ($csvSpaceList | ConvertTo-Html -Fragment)
|
||||
[void]$dataHTML.Add($csvSpaceHTML)
|
||||
}
|
||||
|
||||
$vmsHTML = $titleHtmlVms + ($vmsList | ConvertTo-Html -Fragment)
|
||||
$dataHTML += $vmsHTML
|
||||
[void]$dataHTML.Add($vmsHTML)
|
||||
|
||||
$snapshotsHTML = $titleHtmlSnapshots + ($vmSnapshotsList | ConvertTo-Html -Fragment)
|
||||
$dataHTML += $snapshotsHTML
|
||||
[void]$dataHTML.Add($snapshotsHTML)
|
||||
|
||||
if ($replicationInfoNeeded)
|
||||
{
|
||||
$replicationHTML = $titleHtmlReplication + ($replicationsList | ConvertTo-Html -Fragment)
|
||||
$dataHTML += $replicationHTML
|
||||
[void]$dataHTML.Add($replicationHTML)
|
||||
}
|
||||
|
||||
if ($vhdxList)
|
||||
{
|
||||
$vhdxListHTML = $titleHtmlVhdx + ($vhdxList | ConvertTo-Html -Fragment)
|
||||
$dataHTML += $vhdxListHTML
|
||||
[void]$dataHTML.Add($vhdxListHTML)
|
||||
}
|
||||
|
||||
if ($vmnetInfoNeeded)
|
||||
{
|
||||
$vmnetAdapterListHTML = $titleHtmlVmnetAdapter + ($vmnetAdapterList | ConvertTo-Html -Fragment)
|
||||
$dataHTML += $vmnetAdapterListHTML
|
||||
[void]$dataHTML.Add($vmnetAdapterListHTML)
|
||||
}
|
||||
|
||||
if ($osNetInfoNeeded)
|
||||
{
|
||||
$osNetAdapterListHTML = $titleHtmlOsNetAdapter + ($osNetAdapterList | ConvertTo-Html -Fragment)
|
||||
$dataHTML += $osNetAdapterListHTML
|
||||
[void]$dataHTML.Add($osNetAdapterListHTML)
|
||||
}
|
||||
|
||||
if ($vswitchInfoNeeded)
|
||||
{
|
||||
$vswitchListHTML = $titleHtmlVswitch + ($vswitchList | ConvertTo-Html -Fragment)
|
||||
$dataHTML += $vswitchListHTML
|
||||
$vswitchesListHTML = $titleHtmlVswitch + ($vswitchesList | ConvertTo-Html -Fragment)
|
||||
[void]$dataHTML.Add($vswitchesListHTML)
|
||||
}
|
||||
|
||||
if ($clusterDeployment -and $clusterConfigInfoNeeded)
|
||||
{
|
||||
$clusterConfigInfoListHTML = $titleHtmlClusterConfig + ($clusterConfigInfoList | ConvertTo-Html -Fragment)
|
||||
[void]$dataHTML.Add($clusterConfigInfoListHTML)
|
||||
}
|
||||
|
||||
if ($clusterDeployment -and $clusterNetworksInfoNeeded)
|
||||
{
|
||||
$clusterNetworksListHTML = $titleHtmlClusterNetworks + ($clusterNetworksList | ConvertTo-Html -Fragment)
|
||||
[void]$dataHTML.Add($clusterNetworksListHTML)
|
||||
}
|
||||
|
||||
$htmlReport = ConvertTo-Html -Head $header -Title $title -PostContent $postContent -Body $dataHTML
|
||||
|
||||
82
README.md
82
README.md
@@ -3,14 +3,11 @@
|
||||
|
||||
## 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.
|
||||
Support for clustered environments will be introduced in a future release.
|
||||
This PowerShell script automates the collection of detailed information about a Hyper-V environment, including hosts, virtual machines, snapshots, replication status, VHDX files, network adapters, virtual switches, and—new in this version—CSV health and space utilization, cluster configuration, and cluster network details. It generates a customizable HTML report and can optionally send it via email using MS Graph or MailKit.
|
||||
|
||||
The script can be scheduled like any other PowerShell script using the Task Scheduler in Windows.
|
||||
The script supports both standalone and clustered deployments.
|
||||
|
||||
Notice: Please be aware that the script installs the MS Graph or Send-MailKitMessage PowerShell modules if they are not already present. Do not run the script if installing these modules could cause issues in your environment.
|
||||
|
||||
---
|
||||
> ⚠️ Note: The script installs required modules (MS Graph or MailKit) if not already present. Avoid running it if module installation could impact your environment.
|
||||
|
||||
## Features
|
||||
|
||||
@@ -21,52 +18,52 @@ Notice: Please be aware that the script installs the MS Graph or Send-MailKitMes
|
||||
- Gathers VHDX file details
|
||||
- Extracts VM and management OS network adapter data
|
||||
- Lists virtual switch configurations
|
||||
- Generates a comprehensive HTML report
|
||||
- NEW: Reports CSV health and space utilization (clustered only)
|
||||
- NEW: Includes cluster configuration and network details
|
||||
- Generates a comprehensive HTML report with selectable styles
|
||||
- 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`
|
||||
- Hyper-V
|
||||
- CimCmdlets
|
||||
- External scripts in the same directory:
|
||||
- `GlobalVariables.ps1`
|
||||
- `StyleCSS.ps1`
|
||||
- `HtmlCode.ps1`
|
||||
- `Functions.ps1`
|
||||
|
||||
---
|
||||
- GlobalVariables.ps1
|
||||
- StyleCSS.ps1 (or variants like StyleCSS-Pro.ps1, StyleCSS-Colorful.ps1)
|
||||
- HtmlCode.ps1
|
||||
- Functions.ps1
|
||||
|
||||
## Script Parameters
|
||||
|
||||
These are expected to be defined in `GlobalVariables.ps1`:
|
||||
Defined in `GlobalVariables.ps1`:
|
||||
|
||||
- `$reportHtmlDir` – Directory to save the HTML report
|
||||
- `$reportHtmlName` – Base name for the report file
|
||||
- `$reportStyle` – Style of the HTML report (`minimal`, `pro`, `prodark`, `colorful`, `professional`)
|
||||
- `$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
|
||||
- `$csvHealthInfoNeeded` – Include CSV health info (clustered only)
|
||||
- `$csvSpaceInfoNeeded` – Include CSV space utilization (clustered only)
|
||||
- `$clusterConfigInfoNeeded` – Include cluster configuration details
|
||||
- `$clusterNetworksInfoNeeded` – Include cluster network details
|
||||
- `$replicationInfoNeeded` – Include replication info
|
||||
- `$vhdxInfoNeeded` – Include VHDX info
|
||||
- `$vmnetInfoNeeded` – Include VM network adapter info
|
||||
- `$osNetInfoNeeded` – Include management OS network adapter info
|
||||
- `$vswitchInfoNeeded` – Include virtual switch info
|
||||
- `$reportHtmlRequired` – Generate HTML report
|
||||
- `$emailReport` – Send report via email
|
||||
- `$emailSystem` – Email system to use (`msgraph` or `mailkit`)
|
||||
|
||||
---
|
||||
- `$encryptedSMTPCredsFileName` – Filename for encrypted SMTP credentials
|
||||
|
||||
## Output
|
||||
|
||||
- **HTML Report**: Saved in `$reportHtmlDir` with timestamped filename.
|
||||
- **HTML Report**: Saved in `$reportHtmlDir` with a timestamped filename.
|
||||
- **Console Output**: Displays formatted tables for each section.
|
||||
- **Email**: Sent if `$emailReport` is enabled and `$reportHtmlRequired` is true.
|
||||
|
||||
---
|
||||
|
||||
## Usage
|
||||
|
||||
```powershell
|
||||
@@ -75,22 +72,23 @@ These are expected to be defined in `GlobalVariables.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
|
||||
|
||||
---
|
||||
2. **CSV Health Info** – CSV status and health (clustered only)
|
||||
3. **CSV Space Utilization** – CSV volume usage (clustered only)
|
||||
4. **VM Info** – Name, generation, memory, IP, state, uptime, replication
|
||||
5. **Snapshots** – Snapshot name, age, parent snapshot
|
||||
6. **Replication** – Status, health, last replication time
|
||||
7. **VHDX Info** – Format, type, size, fragmentation
|
||||
8. **VM Network Adapters** – MAC, IP, vSwitch, VLAN
|
||||
9. **Management OS Adapters** – IP, MAC, vSwitch, VLAN
|
||||
10. **Virtual Switches** – Name, type, uplinks, SET status
|
||||
11. **Cluster Configuration** – Cluster settings and roles
|
||||
12. **Cluster Networks** – Cluster network topology and status
|
||||
|
||||
## Notes
|
||||
|
||||
- Cluster support is marked as "Coming soon".
|
||||
- Cluster support is now implemented.
|
||||
- Email system must be explicitly selected (`msgraph` or `mailkit`).
|
||||
- Only the user who encrypted SMTP credentials can decrypt them.
|
||||
|
||||
65
Save-SafeCreds.ps1
Normal file
65
Save-SafeCreds.ps1
Normal file
@@ -0,0 +1,65 @@
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Securely prompts for SMTP credentials and stores them in an encrypted XML file for later use.
|
||||
|
||||
.DESCRIPTION
|
||||
This PowerShell script, authored by Federico Lillacci, is designed to be run interactively to collect and securely store SMTP server credentials. It performs the following steps:
|
||||
|
||||
Credential File Check: Verifies if an encrypted credentials file (EncryptedCreds.xml) already exists in the current working directory.
|
||||
User Confirmation: If the file exists, prompts the user to confirm whether they want to overwrite it.
|
||||
Credential Input: Prompts the user to enter their SMTP username and password using the Get-Credential cmdlet.
|
||||
Encryption and Export: Converts the secure password to an encrypted string and exports the credentials to an XML file using Export-Clixml. The credentials are encrypted using the current user's Windows Data Protection API (DPAPI), meaning only the same user on the same machine can decrypt them.
|
||||
Validation and Feedback: Confirms successful storage by checking the presence of the username in the saved file and provides appropriate feedback.
|
||||
|
||||
This script is useful for securely storing credentials for later use in automated scripts or scheduled tasks that require SMTP authentication.
|
||||
#>
|
||||
|
||||
#Region Credits
|
||||
#Author: Federico Lillacci
|
||||
#Github: https://github.com/tsmagnum
|
||||
#endregion
|
||||
|
||||
#Run this script interactively to securely store SMTP server credentials
|
||||
$credsFileName = "EncryptedCreds.xml"
|
||||
$credsFile = "$(pwd)\$($credsFileName)"
|
||||
|
||||
#Checking if a creds file already exists
|
||||
if (Test-Path -Path $credsFile)
|
||||
{
|
||||
Write-Host -ForegroundColor Yellow "An encrypted XML file with creds already exists at this location"
|
||||
$userChoice = Read-Host `
|
||||
-Prompt "Do you want to overwrite it? Press Y and ENTER to continue, any other key to abort"
|
||||
}
|
||||
|
||||
if ($userChoice -ne "y")
|
||||
{
|
||||
exit
|
||||
}
|
||||
|
||||
#Prompting for username and password
|
||||
Write-Host -ForegroundColor Yellow "Enter the credentials you want to save"
|
||||
$Credentials = Get-Credential
|
||||
$Credentials.Password
|
||||
$Credentials.Password | ConvertFrom-SecureString
|
||||
|
||||
#Exporting creds
|
||||
#Please note: only the user encrypting the creds will be able to decrypt them!
|
||||
Write-Host -ForegroundColor Yellow "Saving credentials to an encrypted XML file..."
|
||||
Write-Host "Your cred will be stored in $credsFile"
|
||||
try {
|
||||
$Credentials | Export-Clixml -Path $credsFile -Force
|
||||
if (Get-Content $credsFile | Select-String -Pattern $($Credentials.UserName))
|
||||
{
|
||||
Write-Host -ForegroundColor Green "Success - Credentials securely stored in $credsFile"
|
||||
}
|
||||
else
|
||||
{
|
||||
throw "There was a problem saving your credentials"
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
catch
|
||||
{
|
||||
Write-Host -ForegroundColor Red "There was a problem saving your credentials"
|
||||
}
|
||||
Reference in New Issue
Block a user