Initial commit of VMware.VCGChecker module

This commit is contained in:
Zhoulin Dai
2018-11-19 22:41:26 +08:00
parent ed5b8eaf5e
commit 0743c67799
7 changed files with 1795 additions and 0 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,310 @@
<#
Copyright 2018 VMware, Inc. All rights reserved.
#>
# Class to manage Host resources
Class HostResource {
[VMware.VimAutomation.Types.VMHost] $vmhost
[string] $vcname
[string] $clustername
[string] $dcname
[string] $hostname
[string] $apitype
[string] $powerstatus
[string] $productname
[string] $version
[string] $fullname
[string] $connectionstatus
[string] $checkRelease
[int] $port
[Array] $ComponentResource = @()
[Array] $JsonProperties = @('__type__', 'dcname', 'vcname','clustername','hostname', 'apitype',
'powerstatus', 'productname', 'version', 'fullname', 'connectionstatus','checkRelease')
HostResource(
[VMware.VimAutomation.Types.VMHost] $vmhost) {
$this.vmhost = $vmhost
$view =$vmhost|Get-View
$vCenter_IP = $view.Summary.ManagementServerIp
if($vCenter_IP){
$this.vcname =$vCenter_IP
$this.dcname = (Get-Datacenter -VMHost $vmhost).Name
$this.clustername = (Get-Cluster -VMHost $vmhost).Name
}else{
$this.vcname =$this.vmhost.Name
}
$this.hostname = $this.vmhost.Name
$summary = $this.vmhost.ExtensionData.Summary
$this.powerstatus = $summary.runtime.powerState
$this.connectionstatus = $summary.runtime.connectionState
$this.apitype = $summary.Config.Product.apiType
$this.fullname = $summary.Config.Product.FullName
$this.version = $summary.Config.Product.version
$this.productname = $summary.Config.Product.licenseProductName
$this.port = 443
}
[Array] query_components() {
if ($this.ComponentResource.Count -eq 0) {
# Get server info
for($count_retry=0;$count_retry -lt 3;$count_retry ++){
try{
$svrResoure = [ServerResource]::new()
$svrResoure.set_data($this.vmhost)
$this.ComponentResource += $svrResoure
break
}catch{
error('query components server for '+$this.vmhost.Name +' error, retry it ' +($count_retry+1) +' times')
}
}
# Get PCI devices
for($count_retry=0;$count_retry -lt 3;$count_retry ++){
try{
$this.query_pcidevices()
break
}catch{
error('query components pcidevice for '+$this.vmhost.Name +' error, retry it ' +($count_retry+1) +' times')
if($count_retry -eq 2){
error('query components pcidevice for '+$this.vmhost.Name +' faild')
}
}
}
}
return $this.ComponentResource
}
[void] query_pcidevices() {
$EsxCliV2 = Get-EsxCli -V2 -VMHost $this.vmhost
$AllPciDevice = $EsxCliV2.hardware.pci.list.invoke()
foreach ($Pci in $AllPciDevice) {
# Ignore USB controllers, iLO/iDRAC devices
if ($Pci.DeviceName -like "*USB*" -or $Pci.DeviceName -like "*iLO*" -or $Pci.DeviceName -like "*iDRAC*") {
continue
}
# Get the NICs and storage adapters.
# We found NIC and storage adapters usually have module ID other than 0 or 1
$pciDevice = [IoDeviceResource]::new()
if ($Pci.ModuleID -ne 0 -and $Pci.ModuleID -ne -1) {
if (!$this.is_pcidevice_exist($Pci)) {
$pciDevice.set_data($Pci, $EsxCliV2)
$this.ComponentResource += $pciDevice
}
}
}
}
[boolean] is_pcidevice_exist($device) {
foreach ($pci in $this.ComponentResource) {
if ($pci.psobject.TypeNames[0] -eq "IoDeviceResource") {
$vid = [String]::Format("{0:x4}", [int]$device.VendorID)
$did = [String]::Format("{0:x4}", [int]$device.DeviceID)
$svid = [String]::Format("{0:x4}", [int]$device.SubVendorID)
$ssid = [String]::Format("{0:x4}", [int]$device.SubDeviceID)
if ($pci.vid -eq $vid -and $pci.did -eq $did -and
$pci.svid -eq $svid -and $pci.ssid -eq $ssid) {
return $true
}
}
}
return $false
}
[object] to_jsonobj() {
$Json = $this | Select-Object -Property $this.JsonProperties
$ComponentChildren = @()
$this.ComponentResource | ForEach-Object {$ComponentChildren += $_.to_jsonobj()}
$Json | Add-Member -Name "ComponentResource" -Value $ComponentChildren -MemberType NoteProperty
return $Json
}
[string] get_host_status() {
if ($this.powerstatus -and $this.powerstatus -ne 'unknown') {
return $this.powerstatus
}
if ($this.connectionstatus) {
return ("Server " + $this.connectionstatus)
}
else {
return "Server status is unknown"
}
}
[string] get_prompt_name() {
if ($this.apitype) {
$start = $this.apitype
}
else {
$start = "Host"
}
return $start + " " + $this.hostname
}
}
# Class to manage server resources
Class ServerResource {
[string] $type
[string] $model
[string] $vendor
[string] $biosversion
[string] $cpumodel
[string] $cpufeatureid
[string] $uuid
[string] $status
[array] $matchResult
[array] $warnings
[string] $vcgLink
[array] $updateRelease
[VMware.VimAutomation.Types.VMHost] $vmhost
[Array] $JsonProperties = @('__type__','type', 'model', 'vendor', 'biosversion',
'cpumodel', 'cpufeatureid', 'uuid','status','matchResult','warnings','vcgLink','updateRelease')
[void] set_data(
[VMware.VimAutomation.Types.VMHost] $vmhost) {
$this.vmhost = $vmhost
$this.type = "Server"
$this.model = $this.vmhost.Model
$this.vendor = $this.vmhost.Manufacturer
$this.biosversion = $this.vmhost.ExtensionData.Hardware.BiosInfo.BiosVersion
$this.cpumodel = $this.vmhost.ProcessorType
$cpuFeature = $this.vmhost.ExtensionData.Hardware.CpuFeature
if ($cpuFeature -and $cpuFeature.Count -gt 2) {
$this.cpufeatureid = $this.vmhost.ExtensionData.Hardware.CpuFeature[1].Eax
}
$this.uuid = $this.vmhost.ExtensionData.Hardware.systeminfo.uuid
}
[object] to_jsonobj() {
return $this | Select-Object -Property $this.JsonProperties
}
}
# Class to manage each IO device
Class IoDeviceResource {
[string] $type
[string] $model
[string] $deviceid
[string] $device
[string] $comptype
[string] $vid
[string] $did
[string] $svid
[string] $ssid
[string] $pciid
[string] $vendor
[string] $driver
[string] $driverversion
[string] $firmware
[string] $status
[array] $matchResult
[array] $warnings
[string] $vcgLink
[array] $updateRelease
[Array] $JsonProperties = @('__type__','type', 'model', 'deviceid', 'device',
'comptype', 'vid', 'did', 'svid', 'ssid', 'pciid',
'vendor', 'driver', 'driverversion', 'firmware','status','matchResult','warnings','vcgLink','updateRelease')
[void] set_data(
[object] $pci,
[object] $EsxCli) {
$this.type = "IO Device"
$this.model = $Pci.DeviceName
$this.deviceid = $pci.Address
$this.device = $pci.VMKernelName
$this.vid = [String]::Format("{0:x4}", [int]$Pci.VendorID)
$this.did = [String]::Format("{0:x4}", [int]$Pci.DeviceID)
$this.svid = [String]::Format("{0:x4}", [int]$Pci.SubVendorID)
$this.ssid = [String]::Format("{0:x4}", [int]$Pci.SubDeviceID)
$this.pciid = $this.vid + ":" + $this.did + ":" + $this.svid + ":" + $this.ssid
$this.vendor = $pci.VendorName
$this.driver = $Pci.ModuleName
$this.driverversion = "N/A"
$this.firmware = "N/A"
# Set component type and driverversion, firmware
if ($this.device -match 'nic') {
$arg = @{}
$arg['nicname'] = $this.device
$nic = $EsxCli.network.nic.get.invoke($arg)
$this.comptype = "Physical NIC"
$this.driverversion = $nic.driverinfo.Version
$this.firmware = $nic.driverinfo.FirmwareVersion
}
elseif ($this.device -match 'hba') {
$arg = @{}
$arg['module'] = $this.driver
$module = $EsxCli.system.module.get.invoke($arg)
$this.comptype = "Storage Adapter"
$this.driverversion = $module.Version
}
}
[object] to_jsonobj() {
return $this | Select-Object -Property $this.JsonProperties
}
[string] get_id_detail() {
return $this.driver + " (PCIId:" + $this.pciid + ")"
}
}
# Class to manage IO device group
Class IoDeviceResourceGroup {
[Array] $iodevices = @()
[Array] $nics = @()
[Array] $adapters = @()
[void] append_nic([IODeviceResource] $nic) {
$this.iodevices += $nic
$this.nics += $nic
}
[void] append_storage_adapter([IODeviceResource] $adapter) {
$this.iodevices += $adapter
$this.adapters += $adapter
}
[boolean] has_nics() {
return $this.nics.Count > 0
}
[boolean] has_storage_adapters() {
return $this.adapters.Count > 0
}
}
#
# Collect hardware inventory data from all the hosts
#
Function Get-VCGHWInfo {
Param(
[Parameter(Mandatory=$true)] $vmHosts
)
# Collect the hardware data
$Data = @()
foreach($vmHost in $vmHosts) {
$vm = [HostResource]::new($vmHost)
try {
info ("Collecting hardware data from " + $vm.hostname)
$null = $vm.query_components()
if($vm.powerstatus -eq 'poweredOn' -and $vm.connectionstatus -eq 'connected'){
$Data += $vm
info ("Collecting hardware data from " + $vm.hostname +' success')
}
}
catch {
error ("Failed to collect hardware data from " + $vm.hostname)
}
}
return $Data
}

View File

@@ -0,0 +1,168 @@
$Uuid = [guid]::NewGuid()
$Headers = New-Object "System.Collections.Generic.Dictionary[[String],[String]]"
$Headers.add("x-request-id", $Uuid)
$Headers.add("x-api-toolid", "180209100001")
$Headers.add("x-api-key", "SJyb8QjK2L")
$Url_Perfix = 'https://apigw.vmware.com/m4/compatibility/v1'
$Url = $Url_Perfix + "/compatible/servers/search?"
$UrlPci = $Url_Perfix + "/compatible/iodevices/search?"
$apiQurryDict=@{}
#
# Ping remote api server.
#
Function PingApiServer(){
$apiServerIp='apigw.vmware.com'
$results =Test-NetConnection $apiServerIp -InformationLevel 'Quiet'
if($results -ne $true){
error ("Failed to access VMware Compatibility API,
Unable to use comparison function, only view basic hardware information;
you can use 'Get-VCGHWInfo -g <fileName>' create hardware json,
then use 'Check-VCGStatus -f <fileName>' load hardware json file to comapre when connect an available network")
Exit(1)
}
}
#
# Get the web request.
#
Function Get-WebRequest($VCGurl) {
try {
$req = Invoke-WebRequest -Headers $Headers -Uri $VCGUrl -ErrorVariable $err -UseBasicParsing
}
catch {
if ($err[0].errorrecord.exception.response) {
error ("WebReponse code:" + $err[0].errorrecord.exception.response.statuscode.value__)
error ($exitScript)
Exit(1)
}
else {
error ("Failed to check " + $type + " data for " + $HostResource.hostname)
error ("Failed to access VMware Compatibility API, please check your Internet connection or contact VMware Compatibility API administrator")
error ("Exit the script")
Exit(1)
}
}
return $req
}
Function Get-RemoteApiTitleString([object]$device,$EsxiVersion){
if ($device.type -eq 'Server') {
$Title = $device.model + $device.vendor + $device.cpufeatureid + $device.biosversion +$EsxiVersion
}
else{
$Title = $device.vid + $device.did + $device.Svid + $device.Ssid + $EsxiVersion
}
return $Title
}
Function Get-ResponseFromApi([object]$device,$EsxiVersion){
if ($device.type -eq 'Server') {
$VCGUrl = $Url + "model=" + $device.model + "&releaseversion=" + $EsxiVersion `
+ "&vendor=" + $device.vendor + "&cpuFeatureId=" + $device.cpufeatureid `
+ "&bios=" + $device.biosversion
debug ("Model:" + $device.model)
debug ("VCG Url:" + $VCGUrl)
$Headers.GetEnumerator() | ForEach-Object {debug ("Req Header:" + $_.key + ":" + $_.value)}
$request = Get-WebRequest $VCGUrl
$Response = ConvertFrom-Json -InputObject $request -Erroraction 'silentlycontinue'
}
elseif ($device.type -eq 'IO Device') {
$VCGUrl = $UrlPci + "vid=0X" + $device.vid + "&did=0X" + $device.did + "&svid=0X" + $device.Svid `
+ "&ssid=0X" + $device.Ssid + "&releaseversion=" + $EsxiVersion `
+ "&driver=" + $device.Driver + "&driverversion=" + $device.driverversion + "&firmware=N/A"
debug ("Model:" + $device.model)
debug ("VCG Url:" + $VCGUrl)
$Headers.GetEnumerator() | ForEach-Object {debug ("Req Header:" + $_.key + ":" + $_.value)}
$request = Get-WebRequest $VCGUrl
$Response = ConvertFrom-Json -InputObject $request -Erroraction 'silentlycontinue'
}
return $Response
}
#
# Get the data from api
#
Function Get-VCGData($HostResource) {
foreach ($device in $HostResource.ComponentResource) {
if ($HostResource.checkRelease) {
$EsxiVersion = $HostResource.checkRelease
}
else {
$EsxiVersion = $HostResource.version
}
$temp=0
$title=Get-RemoteApiTitleString $device $EsxiVersion
if($apiQurryDict.Count -eq 0){
$Response= Get-ResponseFromApi $device $EsxiVersion
$apiQurryDict.Add($title,$Response)
}else{
foreach($onetitle in $apiQurryDict.keys){
if($onetitle -eq $title){
$Response= $apiQurryDict[$onetitle]
$temp=1
break
}
}
if($temp -eq 0){
$Response= Get-ResponseFromApi $device $EsxiVersion
$apiQurryDict.Add($title,$Response)
}
}
if ($Response.matches) {
foreach ($match in $Response.matches) {
$device.vcgLink += [string]$match.vcgLink
}
}
else {
foreach ($potentialMatche in $Response.potentialMatches) {
$device.vcgLink += [string]$potentialMatche.vcgLink
}
}
$device.status = [string]$Response.searchResult.status
$device.matchResult = [string]$Response.searchResult.matchResult
$device.warnings = $Response.searchResult.warnings
$device.updateRelease = [string]$Response.searchOption.foundRelease
}
}
#
# Send the hardware data to VCG API and handle returned result
#
Function Get-DataFromRemoteApi([object]$servers) {
info ("Checking hardware compatibility result with VMware Compatibility Guide API...")
info ("This may take a few minutes depending on your network.")
for ($idx = 0; $idx -lt $servers.Count; $idx++) {
$server = $servers[$idx]
$i = $idx + 1
info ([string]$i + "/" + [string]$servers.Count + " - Checking hardware compatibility results for " + $server.hostname)
if (!$server -or $server.ComponentResource.Count -eq 0) {
error('Failed to get the hardware info.')
Exit(1)
}
Get-VCGData $server
}
return $servers
}
Function Get-VCGStatus{
Param(
[Parameter(Mandatory=$true)] $Data,
[Parameter(Mandatory=$false)] $Version
)
$checkRelease = $Version
PingApiServer
foreach ($vmHost in $Data) {
# $vmHost|add-member -Name "checkRelease" -value $checkRelease -MemberType NoteProperty -Force
$vmHost.checkRelease=$checkRelease
}
$results = Get-DataFromRemoteApi($Data)
if ($results.Count -eq 0) {
error ("Failed to get compatibility results. No report will be generated")
error ("Exit the script")
Exit(1)
}
return $results
}

View File

@@ -0,0 +1,41 @@
# About
This module is designed to ease the work of collecting hardware inventory data and compare it with VMware's official VCG data. Before deploying vSphere, it is required to validate your physical machines with VCG to make sure all the devices in your physical machines are compatible with the vSphere version you want to install. It is a time-consuming and painful experience to collect hardware/driver/firmware data from all of the machines, especially when you have a huge number of machines in your data center.
# How It Works
By using this module, it will automate the data collection and comparison work.
When running the module, it will connect to the target vCenter or ESXi to read the hardware data. It is a read-only operation and nothing on the target hosts will be changed. There is almost no impact on the machine's performance as the read operation takes just few seconds.
The module will then send your hardware inventory data to VMware's offical website to conduct a compatibility check. The result will be 'Compatible', 'May not be compatible' or 'Not compatible'.
* Compatible: the hardware is compatible with the given vSphere release. Link to that VCG page will be provided.
* May not be compatible: manual check is required to confirm the compatibility status of this hardware. A few potential matching VCG records will be provided.
* Not compatible: the hardware is not compatible with the given vSphere release.
After the checking is completed, the module will generate reports in different formats for your review and future use. A summary view in html will give you an overview of your machines compatibility status; an html file with device details to view each device/driver/firmware you have, their compatibility with vSphere version you specified and links to the corresponding VCG documents online; a csv file with device details to allow customization on report in Excel or by your own tool.
# Usage
Considering many data center may have control on internet access, we create 3 cmdlets to meet various situations.
* Get-VCGHWInfo: cmdlet to collect hardware info
* Get-VCGStatus: cmdlet to check hardware compatibility by query VCG website
* Export-VCGReport: cmdlet to export the summary/html/csv reports
1. You need to first import this module after you import PowerCLI module
PS> Import-Module <path_to_VMware.VCGChecker.psd1>
2. Connect to the target vSphere hosts using Connect-VIServer and get VMHosts
PS> Connect-VIServer -Server <server> -User <username> -Password <password>
PS> $vmhosts = Get-VMHost
3. Collect the hardware data
PS> $hwdata = Get-VCGHWInfo -vmHosts $vmhosts
Note: if you don't have internet access, you need to connect your client to internet before proceeding to the next step.
4. Specify the target vSphere release you want to check and submit the hardware data to VMware website
PS> $vcgdata= Get-VCGStatus -Data $hwdata -Version '<release>'
5. Save the compatibility reports
PS> Export-VCGReport -Data $vcgdata -Dir <dir>
# Known Limitation
* The module is not able to get the firmware version for HBA devices.
* The module is not able to get the HDD/SSD data.

View File

@@ -0,0 +1,17 @@
Function Save-VCGJsonFile{
Param(
[Parameter(Mandatory=$true)] $FileName,
[Parameter(Mandatory=$true)] $Data,
[Parameter(Mandatory=$true)] $Dir
)
$json = @()
$Data | ForEach-Object { $json += $_.to_jsonobj()}
if (!(Test-Path $Dir)) {
New-Item -Type directory -Confirm:$false -Path $Dir -Force |Out-Null
}
$Path= $Dir + '\' + $FileName + '.json'
info ("Saving data to " + $Path)
ConvertTo-Json -Depth 10 -Compress $json | Out-File -encoding 'UTF8' -FilePath $Path
}

View File

@@ -0,0 +1,90 @@
#
# Module manifest for module 'VMware.VCGChecker'
#
# Generated by: fdai@vmware.com, zhangta@vmware.com, linweij@vmware.com
#
# Generated on: 11/15/18
#
@{
# Script module or binary module file associated with this manifest.
# RootModule = 'VMware.VCGChecker.psm1'
# Version number of this module.
ModuleVersion = '1.0.0'
# Supported PSEditions
# CompatiblePSEditions = @()
# ID used to uniquely identify this module
# GUID = ''
# Author of this module
Author = 'Frank Dai, Tao Zhang, Linwei Jiang'
# Company or vendor of this module
CompanyName = 'VMware'
# Copyright statement for this module
Copyright = '(c) 2018 VMware. All rights reserved.'
# Description of the functionality provided by this module
Description = 'PowerShell Module for Checking Hardware Compatibility Status'
RequiredModules = @('VMware.VimAutomation.Core')
# Modules to import as nested modules of the module specified in RootModule/ModuleToProcess
NestedModules = 'Get-VCGHWInfo.ps1','Get-VCGStatus.ps1','Export-VCGReport.ps1', 'Save-VCGJsonFile.ps1', 'logger.ps1'
# Functions to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no functions to export.
FunctionsToExport = 'Get-VCGHWInfo', 'Get-VCGStatus', 'Export-VCGReport', 'Save-VCGJsonFile'
# Cmdlets to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no cmdlets to export.
CmdletsToExport = @()
# Variables to export from this module
VariablesToExport = '*'
# Aliases to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no aliases to export.
AliasesToExport = @()
# DSC resources to export from this module
# DscResourcesToExport = @()
# List of all modules packaged with this module
# ModuleList = @()
# List of all files packaged with this module
# FileList = @()
# Private data to pass to the module specified in RootModule/ModuleToProcess. This may also contain a PSData hashtable with additional module metadata used by PowerShell.
PrivateData = @{
PSData = @{
# Tags applied to this module. These help with module discovery in online galleries.
# Tags = @()
# A URL to the license for this module.
# LicenseUri = ''
# A URL to the main website for this project.
# ProjectUri = ''
# A URL to an icon representing this module.
# IconUri = ''
# ReleaseNotes of this module
# ReleaseNotes = ''
} # End of PSData hashtable
} # End of PrivateData hashtable
# HelpInfo URI of this module
# HelpInfoURI = ''
# Default prefix for commands exported from this module. Override the default prefix using Import-Module -Prefix.
# DefaultCommandPrefix = ''
}

View File

@@ -0,0 +1,112 @@
<#
Copyright 2018 VMware, Inc. All rights reserved.
#>
# Messages
$HEADER_OK = "[OK] "
$HEADER_INFO = "[INFO] "
$HEADER_WARNING = "[WARNING] "
$HEADER_ERR = "[ERROR] "
Class DebugLog
{
# Static variables of the logger class
static [string] $CAFILE_PATH = "./.certs/"
[boolean] $debug
[string] $logfile
DebugLog()
{
$this.debug = $false
$this.logfile = $null
if (!(Test-Path $this::CAFILE_PATH))
{
New-Item -Type directory -Confirm:$false -Path $this::CAFILE_PATH
}
}
[void] SetDebug(
[boolean] $debug,
[string] $hostname
){
if (!$hostname) {$hostname = ''}
$this.debug = $debug
if ($this.debug)
{
$this.logfile = $this::CAFILE_PATH + $hostname + [DateTime]::Now.ToString("_yyyy-MM-dd_HH-mm") + ".log"
}else{
$this.logfile = $null
}
}
[void] log_vars(
[string] $message,
[object] $var
){
$this.log($message + $var)
}
[void] log(
[string] $message
){
if (!$this.debug -or !$this.logfile) {return}
try
{
$message | Out-File $this.logfile -Append
}catch {
Out-Host -InputObject ("[Exception] Failed to write to a logfile: " + $this.logfile)
Out-Host -InputObject $_
}
}
}
Function debug_vars(
[string] $message,
[object] $var)
{
$logger.log_vars($message, $var)
}
Function debug(
[string] $message)
{
$logger.log($message)
}
Function vcglog(
[string] $message,
[string] $header="")
{
$msg = $header + $message
$logger.log($msg)
Out-Host -InputObject $msg
}
Function ok(
[string] $message)
{
vcglog $message $HEADER_OK
}
Function warning(
[string] $message)
{
vcglog $message $HEADER_WARNING
}
Function info(
[string] $message)
{
vcglog $message $HEADER_INFO
}
Function error(
[string] $message)
{
vcglog $message $HEADER_ERR
}
$logger = [DebugLog]::new()
$logger.SetDebug($true, "vcc-debug")