512 lines
37 KiB
PowerShell
512 lines
37 KiB
PowerShell
#region Get-HyperVInventory
|
|
Function Get-HyperVInventory
|
|
{
|
|
<#
|
|
.SYNOPSIS
|
|
Collects the compute inventory from one or more Hyper-V hosts and returns a normalized model that matches the Proxmox model.
|
|
|
|
.DESCRIPTION
|
|
Collection uses the Hyper-V module rather than hand written CIM queries so that Microsoft owns the translation of the
|
|
underlying virtualization classes. The module is required on the machine running this script; the hosts themselves are
|
|
reached through a CIM session, which every Hyper-V cmdlet accepts, so remote credentials and the DCOM fallback both work.
|
|
|
|
The following cmdlets are consumed.
|
|
|
|
Get-VMHost Host processor, memory, and path detail
|
|
Get-VMSwitch Virtual switches and their external adapter binding
|
|
Get-VM Virtual machine inventory including state, processor count, and memory
|
|
Get-VMNetworkAdapter Adapter names, MAC addresses, switch attachment, and guest reported addressing
|
|
Get-VMNetworkAdapterVlan Access VLAN identifiers per adapter
|
|
Get-VMHardDiskDrive, Get-VHD Allocated storage per virtual machine
|
|
Get-CimInstance Host chassis manufacturer, model, and serial number
|
|
|
|
Virtual switches become virtual switches within Rackpad and virtual machine network adapters become ports attached to those
|
|
virtual switches, which produces the same virtual topology shape that the Proxmox collector produces. The two collectors are
|
|
therefore interchangeable and can also run side by side.
|
|
|
|
.PARAMETER ComputerName
|
|
One or more Hyper-V host names or addresses.
|
|
|
|
.PARAMETER Credential
|
|
The credential used for the CIM session. Omit to use the credentials of the calling process.
|
|
|
|
.PARAMETER UseDCOM
|
|
Uses the DCOM protocol instead of WSMan. Useful for hosts where PowerShell remoting is unavailable.
|
|
|
|
.PARAMETER ClusterName
|
|
An optional cluster name recorded against every collected host.
|
|
|
|
.PARAMETER UseGuestIntrospection
|
|
Attempts to read the guest operating system caption over PowerShell Direct in addition to the key value pair exchange.
|
|
PowerShell Direct can only reach guests from the Hyper-V host itself, so this has no effect during a remote collection and
|
|
the key value pair exchange remains the source in that case. A guest credential is required.
|
|
|
|
.PARAMETER GuestCredential
|
|
The credential used inside the guest by PowerShell Direct. This is a guest credential, not a host credential.
|
|
|
|
.PARAMETER IncludeTemplates
|
|
Included for parity with the Proxmox collector. Hyper-V has no native template concept, so this parameter has no effect.
|
|
|
|
.EXAMPLE
|
|
$HyperVInventory = Get-HyperVInventory -ComputerName 'hypervisor01' -Credential $Credential
|
|
|
|
.NOTES
|
|
Author: Grace Solutions
|
|
Version: 2026.08.02.0000
|
|
|
|
Requires the Hyper-V module, which ships with the Hyper-V Management Tools feature.
|
|
#>
|
|
|
|
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSReviewUnusedParameter', 'IncludeTemplates', Justification = 'Present for signature parity with the Proxmox collector so that both can be invoked identically.')]
|
|
[CmdletBinding()]
|
|
Param
|
|
(
|
|
[Parameter(Mandatory=$True)]
|
|
[ValidateNotNullOrEmpty()]
|
|
[String[]]$ComputerName,
|
|
|
|
[Parameter(Mandatory=$False)]
|
|
[AllowNull()]
|
|
[System.Management.Automation.PSCredential]$Credential,
|
|
|
|
[Parameter(Mandatory=$False)]
|
|
[Switch]$UseDCOM,
|
|
|
|
[Parameter(Mandatory=$False)]
|
|
[AllowEmptyString()]
|
|
[String]$ClusterName = '',
|
|
|
|
[Parameter(Mandatory=$False)]
|
|
[Switch]$UseGuestIntrospection,
|
|
|
|
[Parameter(Mandatory=$False)]
|
|
[AllowNull()]
|
|
[System.Management.Automation.PSCredential]$GuestCredential,
|
|
|
|
[Parameter(Mandatory=$False)]
|
|
[Switch]$IncludeTemplates
|
|
)
|
|
|
|
Try
|
|
{
|
|
#region The Hyper-V module performs the heavy lifting, so its absence is a hard failure rather than a degraded collection
|
|
$HyperVModule = Try {Get-Module -Name 'Hyper-V' -ListAvailable -ErrorAction SilentlyContinue | Sort-Object -Property @('Version') -Descending | Select-Object -First 1} Catch {$Null}
|
|
|
|
Switch ($Null -ieq $HyperVModule)
|
|
{
|
|
{($_ -eq $True)}
|
|
{
|
|
Throw 'The Hyper-V module could not be located. Install the Hyper-V Management Tools feature on the machine running this script.'
|
|
}
|
|
}
|
|
|
|
$Null = Import-Module -Name "$($HyperVModule.Path)" -Global -DisableNameChecking -Force -Verbose:$False
|
|
#endregion
|
|
|
|
$NodeObjectList = New-Object -TypeName 'System.Collections.Generic.List[PSObject]'
|
|
|
|
$ComputerNameListCount = ($ComputerName | Measure-Object).Count
|
|
|
|
For ($ComputerNameIndex = 0; $ComputerNameIndex -lt $ComputerNameListCount; $ComputerNameIndex++)
|
|
{
|
|
$HyperVHostName = "$($ComputerName[$ComputerNameIndex])"
|
|
|
|
$WriteProgressParameters = New-Object -TypeName 'System.Collections.Specialized.OrderedDictionary'
|
|
$WriteProgressParameters.Activity = 'Collecting the Hyper-V inventory'
|
|
$WriteProgressParameters.Status = "Processing host `"$($HyperVHostName)`" ($($ComputerNameIndex + 1) of $($ComputerNameListCount))"
|
|
$WriteProgressParameters.PercentComplete = [System.Math]::Round((($ComputerNameIndex + 1) / $ComputerNameListCount) * 100, 2)
|
|
$WriteProgressParameters.Id = 22
|
|
|
|
$Null = Write-Progress @WriteProgressParameters
|
|
|
|
$CIMSession = $Null
|
|
|
|
#region Every Hyper-V and CIM call below is splatted with this, so a local collection can omit the session entirely
|
|
$SessionParameter = New-Object -TypeName 'System.Collections.Specialized.OrderedDictionary'
|
|
#endregion
|
|
|
|
Try
|
|
{
|
|
#region A local collection talks to the provider directly
|
|
# Requiring a CIM session for the local machine would mean requiring WinRM, which is frequently not
|
|
# enabled on a workstation that nonetheless has the Hyper-V role. A session is therefore only created
|
|
# when the target is genuinely remote or when an explicit credential has to be presented.
|
|
$LocalNameList = New-Object -TypeName 'System.Collections.Generic.List[String]'
|
|
$LocalNameList.Add('.')
|
|
$LocalNameList.Add('localhost')
|
|
$LocalNameList.Add('127.0.0.1')
|
|
$LocalNameList.Add("$($Env:ComputerName)")
|
|
$LocalNameList.Add("$([System.Net.Dns]::GetHostName())")
|
|
|
|
$TargetIsLocal = ("$($HyperVHostName)" -iin $LocalNameList) -and ($Null -ieq $Credential)
|
|
#endregion
|
|
|
|
#region Establish the CIM session, falling back to DCOM when WSMan is unavailable
|
|
Switch ($TargetIsLocal)
|
|
{
|
|
{($_ -eq $False)}
|
|
{
|
|
$ProtocolList = New-Object -TypeName 'System.Collections.Generic.List[String]'
|
|
|
|
Switch ($UseDCOM.IsPresent)
|
|
{
|
|
{($_ -eq $True)}
|
|
{
|
|
$ProtocolList.Add('DCOM')
|
|
}
|
|
|
|
{($_ -eq $False)}
|
|
{
|
|
$ProtocolList.Add('WSMan')
|
|
$ProtocolList.Add('DCOM')
|
|
}
|
|
}
|
|
|
|
:ProtocolLoop For ($ProtocolIndex = 0; $ProtocolIndex -lt $ProtocolList.Count; $ProtocolIndex++)
|
|
{
|
|
$NewCimSessionParameters = New-Object -TypeName 'System.Collections.Specialized.OrderedDictionary'
|
|
$NewCimSessionParameters.ComputerName = "$($HyperVHostName)"
|
|
$NewCimSessionParameters.SessionOption = New-CimSessionOption -Protocol "$($ProtocolList[$ProtocolIndex])"
|
|
$NewCimSessionParameters.ErrorAction = 'Stop'
|
|
|
|
Switch ($Null -ine $Credential)
|
|
{
|
|
{($_ -eq $True)}
|
|
{
|
|
$NewCimSessionParameters.Credential = $Credential
|
|
}
|
|
}
|
|
|
|
$CIMSession = Try {New-CimSession @NewCimSessionParameters} Catch {$Null}
|
|
|
|
Switch ($Null -ine $CIMSession)
|
|
{
|
|
{($_ -eq $True)}
|
|
{
|
|
Write-Verbose -Message "[$([System.DateTime]::UtcNow.ToString('yyyy/MM/dd HH:mm:ss.FFF'))] - [INFO] - Connected to `"$($HyperVHostName)`" using the $($ProtocolList[$ProtocolIndex]) protocol."
|
|
|
|
Break ProtocolLoop
|
|
}
|
|
}
|
|
}
|
|
|
|
Switch ($Null -ieq $CIMSession)
|
|
{
|
|
{($_ -eq $True)}
|
|
{
|
|
Throw "A CIM session to `"$($HyperVHostName)`" could not be established using $($ProtocolList -Join ' or '). Enable PowerShell remoting on the host, or set UseDCOM."
|
|
}
|
|
}
|
|
|
|
$SessionParameter.CimSession = $CIMSession
|
|
}
|
|
|
|
{($_ -eq $True)}
|
|
{
|
|
Write-Verbose -Message "[$([System.DateTime]::UtcNow.ToString('yyyy/MM/dd HH:mm:ss.FFF'))] - [INFO] - Collecting from the local machine directly, so no CIM session is required."
|
|
}
|
|
}
|
|
#endregion
|
|
|
|
#region Host detail
|
|
$VirtualMachineHost = Get-VMHost @SessionParameter
|
|
|
|
$HostComputerSystem = Get-CimInstance @SessionParameter -Namespace 'root\CIMv2' -ClassName 'Win32_ComputerSystem'
|
|
$HostOperatingSystem = Get-CimInstance @SessionParameter -Namespace 'root\CIMv2' -ClassName 'Win32_OperatingSystem'
|
|
$HostBios = Get-CimInstance @SessionParameter -Namespace 'root\CIMv2' -ClassName 'Win32_BIOS'
|
|
$HostProcessor = Get-CimInstance @SessionParameter -Namespace 'root\CIMv2' -ClassName 'Win32_Processor' | Select-Object -First 1
|
|
|
|
$HostStorageGB = 0
|
|
|
|
ForEach ($HostVolume In @(Get-CimInstance @SessionParameter -Namespace 'root\CIMv2' -ClassName 'Win32_LogicalDisk' -Filter 'DriveType = 3'))
|
|
{
|
|
$HostStorageGB = $HostStorageGB + [System.Math]::Round(([Double]"0$($HostVolume.Size)" / 1GB), 2)
|
|
}
|
|
|
|
$HostIPAddress = ''
|
|
|
|
ForEach ($HostNetworkConfiguration In @(Get-CimInstance @SessionParameter -Namespace 'root\CIMv2' -ClassName 'Win32_NetworkAdapterConfiguration' -Filter 'IPEnabled = True'))
|
|
{
|
|
ForEach ($HostAddress In @($HostNetworkConfiguration.IPAddress))
|
|
{
|
|
Switch (("$($HostAddress)" -imatch '^\d{1,3}(\.\d{1,3}){3}$') -and ([String]::IsNullOrWhiteSpace($HostIPAddress) -eq $True))
|
|
{
|
|
{($_ -eq $True)}
|
|
{
|
|
$HostIPAddress = "$($HostAddress)"
|
|
}
|
|
}
|
|
}
|
|
}
|
|
#endregion
|
|
|
|
#region Virtual switches
|
|
$BridgeObjectList = New-Object -TypeName 'System.Collections.Generic.List[PSObject]'
|
|
|
|
ForEach ($VirtualSwitch In @(Get-VMSwitch @SessionParameter -ErrorAction SilentlyContinue))
|
|
{
|
|
$UplinkPortList = New-Object -TypeName 'System.Collections.Generic.List[String]'
|
|
|
|
Switch ([String]::IsNullOrWhiteSpace("$($VirtualSwitch.NetAdapterInterfaceDescription)") -eq $False)
|
|
{
|
|
{($_ -eq $True)}
|
|
{
|
|
$UplinkPortList.Add("$($VirtualSwitch.NetAdapterInterfaceDescription)")
|
|
}
|
|
}
|
|
|
|
$BridgeProperties = New-Object -TypeName 'System.Collections.Specialized.OrderedDictionary'
|
|
$BridgeProperties.Name = "$($VirtualSwitch.Name)"
|
|
$BridgeProperties.Type = "$($VirtualSwitch.SwitchType)"
|
|
$BridgeProperties.UplinkPortList = $UplinkPortList.ToArray()
|
|
$BridgeProperties.CIDR = ''
|
|
$BridgeProperties.Gateway = ''
|
|
$BridgeProperties.IsVLANAware = $True
|
|
$BridgeProperties.Comment = "$($VirtualSwitch.Notes)"
|
|
$BridgeProperties.IsActive = $True
|
|
$BridgeProperties.SwitchKind = Switch ("$($VirtualSwitch.SwitchType)")
|
|
{
|
|
{($_ -ieq 'External')}
|
|
{
|
|
'external'
|
|
}
|
|
|
|
{($_ -ieq 'Private')}
|
|
{
|
|
'private'
|
|
}
|
|
|
|
Default
|
|
{
|
|
'internal'
|
|
}
|
|
}
|
|
|
|
$BridgeObjectList.Add((New-Object -TypeName 'System.Management.Automation.PSObject' -Property ($BridgeProperties)))
|
|
}
|
|
#endregion
|
|
|
|
#region Virtual machines
|
|
$GuestObjectList = New-Object -TypeName 'System.Collections.Generic.List[PSObject]'
|
|
|
|
#region The guest operating system is not a virtual machine property, so it is read from the key value pair exchange that integration services populates
|
|
$GuestOperatingSystemTable = New-Object -TypeName 'System.Collections.Generic.Dictionary[[System.String], [System.String]]'
|
|
|
|
ForEach ($KeyValuePairComponent In @(Get-CimInstance @SessionParameter -Namespace 'root\virtualization\v2' -ClassName 'Msvm_KvpExchangeComponent' -ErrorAction SilentlyContinue))
|
|
{
|
|
ForEach ($GuestExchangeItem In @($KeyValuePairComponent.GuestIntrinsicExchangeItems))
|
|
{
|
|
Switch ("$($GuestExchangeItem)" -imatch '(?s)<PROPERTY\s+NAME="Name".*?<VALUE>OSName</VALUE>.*?<PROPERTY\s+NAME="Data".*?<VALUE>(?<OperatingSystemName>[^<]+)</VALUE>')
|
|
{
|
|
{($_ -eq $True)}
|
|
{
|
|
$GuestOperatingSystemTable["$($KeyValuePairComponent.SystemName)"] = "$($Matches['OperatingSystemName'])".Trim()
|
|
}
|
|
}
|
|
}
|
|
}
|
|
#endregion
|
|
|
|
#region PowerShell Direct is only reachable from the host itself, so guest introspection is limited to a local collection
|
|
$GuestIntrospectionIsAvailable = ($UseGuestIntrospection.IsPresent -eq $True) -and ($Null -ine $GuestCredential) -and ("$($HyperVHostName)" -iin @('.', 'localhost', '127.0.0.1', "$($Env:ComputerName)", "$($VirtualMachineHost.Name)"))
|
|
|
|
Switch (($UseGuestIntrospection.IsPresent -eq $True) -and ($GuestIntrospectionIsAvailable -eq $False))
|
|
{
|
|
{($_ -eq $True)}
|
|
{
|
|
Write-Verbose -Message "[$([System.DateTime]::UtcNow.ToString('yyyy/MM/dd HH:mm:ss.FFF'))] - [INFO] - Guest introspection was requested but is unavailable for `"$($HyperVHostName)`". [Reason: PowerShell Direct requires the collection to run on the Hyper-V host itself and requires a guest credential.] The key value pair exchange remains in use."
|
|
}
|
|
}
|
|
#endregion
|
|
|
|
ForEach ($VirtualMachine In @(Get-VM @SessionParameter -ErrorAction SilentlyContinue))
|
|
{
|
|
#region Guest operating system, preferring PowerShell Direct when it is reachable and falling back to the key value pair exchange
|
|
$GuestOperatingSystem = ''
|
|
|
|
Switch ($GuestOperatingSystemTable.ContainsKey("$($VirtualMachine.Id)"))
|
|
{
|
|
{($_ -eq $True)}
|
|
{
|
|
$GuestOperatingSystem = "$($GuestOperatingSystemTable["$($VirtualMachine.Id)"])"
|
|
}
|
|
}
|
|
|
|
Switch (($GuestIntrospectionIsAvailable -eq $True) -and ("$($VirtualMachine.State)" -ieq 'Running'))
|
|
{
|
|
{($_ -eq $True)}
|
|
{
|
|
$DirectResult = Try {Invoke-Command -VMId ($VirtualMachine.Id) -Credential ($GuestCredential) -ErrorAction Stop -ScriptBlock {"$((Get-CimInstance -ClassName 'Win32_OperatingSystem').Caption)"}} Catch {$Null}
|
|
|
|
Switch ([String]::IsNullOrWhiteSpace("$($DirectResult)") -eq $False)
|
|
{
|
|
{($_ -eq $True)}
|
|
{
|
|
$GuestOperatingSystem = "$($DirectResult)".Trim()
|
|
}
|
|
}
|
|
}
|
|
}
|
|
#endregion
|
|
|
|
#region Allocated storage
|
|
$GuestStorageGB = 0
|
|
|
|
ForEach ($VirtualHardDiskDrive In @(Get-VMHardDiskDrive -VM ($VirtualMachine) -ErrorAction SilentlyContinue))
|
|
{
|
|
$VirtualHardDisk = Try {Get-VHD @SessionParameter -Path "$($VirtualHardDiskDrive.Path)" -ErrorAction SilentlyContinue} Catch {$Null}
|
|
|
|
Switch ($Null -ine $VirtualHardDisk)
|
|
{
|
|
{($_ -eq $True)}
|
|
{
|
|
$GuestStorageGB = $GuestStorageGB + [System.Math]::Round(([Double]"0$($VirtualHardDisk.Size)" / 1GB), 2)
|
|
}
|
|
}
|
|
}
|
|
#endregion
|
|
|
|
#region Network adapters, their switch attachment, and their access VLAN
|
|
$GuestInterfaceList = New-Object -TypeName 'System.Collections.Generic.List[PSObject]'
|
|
|
|
$GuestInterfacePosition = 1
|
|
|
|
ForEach ($NetworkAdapter In @(Get-VMNetworkAdapter -VM ($VirtualMachine) -ErrorAction SilentlyContinue))
|
|
{
|
|
$AdapterVlanSetting = Try {Get-VMNetworkAdapterVlan -VMNetworkAdapter ($NetworkAdapter) -ErrorAction SilentlyContinue} Catch {$Null}
|
|
|
|
$AccessVLANID = ''
|
|
|
|
Switch (($Null -ine $AdapterVlanSetting) -and ("$($AdapterVlanSetting.OperationMode)" -ieq 'Access') -and ([Int]"0$($AdapterVlanSetting.AccessVlanId)" -gt 0))
|
|
{
|
|
{($_ -eq $True)}
|
|
{
|
|
$AccessVLANID = "$($AdapterVlanSetting.AccessVlanId)"
|
|
}
|
|
}
|
|
|
|
$AdapterIPAddress = ''
|
|
|
|
ForEach ($CandidateAddress In @($NetworkAdapter.IPAddresses))
|
|
{
|
|
Switch (("$($CandidateAddress)" -imatch '^\d{1,3}(\.\d{1,3}){3}$') -and ("$($CandidateAddress)" -inotmatch '^(0\.|127\.|169\.254\.)') -and ([String]::IsNullOrWhiteSpace($AdapterIPAddress) -eq $True))
|
|
{
|
|
{($_ -eq $True)}
|
|
{
|
|
$AdapterIPAddress = "$($CandidateAddress)"
|
|
}
|
|
}
|
|
}
|
|
|
|
$InterfaceProperties = New-Object -TypeName 'System.Collections.Specialized.OrderedDictionary'
|
|
$InterfaceProperties.Name = "$($NetworkAdapter.Name)"
|
|
$InterfaceProperties.ConfigurationKey = "net$($GuestInterfacePosition - 1)"
|
|
$InterfaceProperties.MACAddress = "$($NetworkAdapter.MacAddress)".ToUpperInvariant()
|
|
$InterfaceProperties.AdapterModel = Switch ($NetworkAdapter.IsLegacy) {{($_ -eq $True)} {'legacy'} Default {'synthetic'}}
|
|
$InterfaceProperties.BridgeName = "$($NetworkAdapter.SwitchName)"
|
|
$InterfaceProperties.VLANID = "$($AccessVLANID)"
|
|
$InterfaceProperties.IPAddress = "$($AdapterIPAddress)"
|
|
$InterfaceProperties.IsFirewalled = $False
|
|
|
|
$GuestInterfaceList.Add((New-Object -TypeName 'System.Management.Automation.PSObject' -Property ($InterfaceProperties)))
|
|
|
|
$GuestInterfacePosition++
|
|
}
|
|
#endregion
|
|
|
|
$GuestTagList = New-Object -TypeName 'System.Collections.Generic.List[String]'
|
|
|
|
$GuestProperties = New-Object -TypeName 'System.Collections.Specialized.OrderedDictionary'
|
|
$GuestProperties.VMID = "$($VirtualMachine.Id)"
|
|
$GuestProperties.Name = "$($VirtualMachine.Name)"
|
|
$GuestProperties.GuestType = 'vm'
|
|
$GuestProperties.Status = Switch ("$($VirtualMachine.State)")
|
|
{
|
|
{($_ -ieq 'Running')}
|
|
{
|
|
'online'
|
|
}
|
|
|
|
{($_ -iin @('Off', 'Saved'))}
|
|
{
|
|
'offline'
|
|
}
|
|
|
|
{($_ -iin @('Paused', 'Starting', 'Stopping', 'Saving'))}
|
|
{
|
|
'warning'
|
|
}
|
|
|
|
Default
|
|
{
|
|
'unknown'
|
|
}
|
|
}
|
|
$GuestProperties.IsTemplate = $False
|
|
$GuestProperties.CPUCoreCount = [Int]"0$($VirtualMachine.ProcessorCount)"
|
|
$GuestProperties.MemoryGB = [System.Math]::Round(([Double]"0$($VirtualMachine.MemoryStartup)" / 1GB), 2)
|
|
$GuestProperties.StorageGB = Switch ($GuestStorageGB -gt 0) {{($_ -eq $True)} {$GuestStorageGB} Default {$Null}}
|
|
$GuestProperties.OperatingSystemType = "Generation $($VirtualMachine.Generation)"
|
|
$GuestProperties.GuestOperatingSystem = "$($GuestOperatingSystem)"
|
|
$GuestProperties.Description = "$($VirtualMachine.Notes)".Trim()
|
|
$GuestProperties.TagList = $GuestTagList
|
|
$GuestProperties.InterfaceList = $GuestInterfaceList
|
|
$GuestProperties.NodeName = "$($VirtualMachineHost.Name)"
|
|
|
|
$GuestObjectList.Add((New-Object -TypeName 'System.Management.Automation.PSObject' -Property ($GuestProperties)))
|
|
}
|
|
#endregion
|
|
|
|
$NodeProperties = New-Object -TypeName 'System.Collections.Specialized.OrderedDictionary'
|
|
$NodeProperties.Name = "$($VirtualMachineHost.Name)"
|
|
$NodeProperties.Status = 'online'
|
|
$NodeProperties.IPAddress = "$($HostIPAddress)"
|
|
$NodeProperties.CPUCoreCount = [Int]"0$($VirtualMachineHost.LogicalProcessorCount)"
|
|
$NodeProperties.MemoryGB = [System.Math]::Round(([Double]"0$($VirtualMachineHost.MemoryCapacity)" / 1GB), 2)
|
|
$NodeProperties.StorageGB = Switch ($HostStorageGB -gt 0) {{($_ -eq $True)} {$HostStorageGB} Default {$Null}}
|
|
$NodeProperties.Version = "$($HostOperatingSystem.Caption) $($HostOperatingSystem.Version)".Trim()
|
|
$NodeProperties.CPUModel = "$($HostProcessor.Name)".Trim()
|
|
$NodeProperties.Manufacturer = "$($HostComputerSystem.Manufacturer)"
|
|
$NodeProperties.Model = "$($HostComputerSystem.Model)"
|
|
$NodeProperties.SerialNumber = "$($HostBios.SerialNumber)"
|
|
$NodeProperties.BridgeList = $BridgeObjectList
|
|
$NodeProperties.GuestList = $GuestObjectList
|
|
|
|
$NodeObjectList.Add((New-Object -TypeName 'System.Management.Automation.PSObject' -Property ($NodeProperties)))
|
|
|
|
Write-Verbose -Message "[$([System.DateTime]::UtcNow.ToString('yyyy/MM/dd HH:mm:ss.FFF'))] - [INFO] - Collected $($BridgeObjectList.Count) virtual switch(es) and $($GuestObjectList.Count) virtual machine(s) from Hyper-V host `"$($HyperVHostName)`"."
|
|
}
|
|
Catch
|
|
{
|
|
Write-Warning -Message "[$([System.DateTime]::UtcNow.ToString('yyyy/MM/dd HH:mm:ss.FFF'))] - [WARNING] - The Hyper-V host `"$($HyperVHostName)`" could not be collected and will be skipped. Message: $($_.Exception.Message)"
|
|
}
|
|
Finally
|
|
{
|
|
Switch ($Null -ine $CIMSession)
|
|
{
|
|
{($_ -eq $True)}
|
|
{
|
|
$Null = Try {Remove-CimSession -CimSession ($CIMSession) -ErrorAction SilentlyContinue} Catch {$Null}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
$Null = Write-Progress -Activity 'Collecting the Hyper-V inventory' -Id 22 -Completed
|
|
|
|
$InventoryProperties = New-Object -TypeName 'System.Collections.Specialized.OrderedDictionary'
|
|
$InventoryProperties.SourceType = 'HyperV'
|
|
$InventoryProperties.BaseURL = $Null
|
|
$InventoryProperties.ClusterName = "$($ClusterName)"
|
|
$InventoryProperties.RetrievedAt = [System.DateTime]::UtcNow
|
|
$InventoryProperties.NodeList = $NodeObjectList
|
|
|
|
Write-Output -InputObject (New-Object -TypeName 'System.Management.Automation.PSObject' -Property ($InventoryProperties))
|
|
}
|
|
Catch
|
|
{
|
|
Throw
|
|
}
|
|
}
|
|
#endregion
|