Virten.net.VimAutomation.psm1

Function Get-VMHostVersion {
<#
.SYNOPSIS
    Get detailed ESXi version information.
.DESCRIPTION
    This function provides detailed ESXi Version information.
    It uses an inofficial JSON based ESXi release database provided by www.virten.net
 
    The function returns:
    - ESXi Build Number
    - Version (Unambiguous friendly name used in Release Notes and https://kb.vmware.com/kb/2143832)
    - Release Date (When the installed verison has been published)
    - Minor Release (eg. 5.0, 5.1, 6.0 - https://www.vmware.com/support/policies/upgrade.html)
    - Update Release (eg. 6.0, 6.0 U1, 6.0 U2 - https://www.vmware.com/support/policies/upgrade.html)
     
.PARAMETER VMHost
    Name of the ESXi Host (returned by Get-VMHost cmdlet)
.EXAMPLE
    Get-VMHostVersion
.EXAMPLE
    Get-VMHostVersion -VMHost esx4.virten.lab
.EXAMPLE
    Get-Cluster Cluster | Get-VMHost | Get-VMHostVersion
.NOTES
    Author: Florian Grehl
    Twitter: @virten
    Website: www.virten.net
     
    Changelog:
    2017-09-04 - v1.0 - Initial Release
 
.LINK
    http://www.virten.net
#>

    [CmdletBinding()]
    Param(
        [Parameter(Mandatory=$false,ValueFromPipeline=$true)]$VMHosts
    )
    
    Begin
    {
        $ErrorActionPreference = 'Stop'
        $WarningPreference = 'SilentlyContinue'
        $esxiReleases = Invoke-WebRequest -Uri http://www.virten.net/repo/esxiReleasesV2.json | ConvertFrom-Json
    }

    Process
    {
        if (-not $VMHosts) {
            $vmHosts = Get-VMHost
        } else {
            $vmHosts = Get-VMHost -Name $VMHosts
        }
    
        Foreach ($vmHost in $vmHosts) {   
            if ($VMHost.Build){
                $release = $esxiReleases.data.esxiReleases.($vmHost.Build)
                if ($release){
                    [pscustomobject] @{
                        VMHost = $VMHost.Name
                        Build = $VMHost.Build
                        Version = $release.friendlyName
                        ReleaseDate =  $release.releaseDate
                        MinorRelease = "ESXi $($release.minorRelease)"
                        UpdateRelease = $release.updateRelease
                    }
                } else {
                    Write-Verbose "ESXi Host $($VMHost.Name): Build number ($($vmHost.Build)) not found in database."
                    [pscustomobject] @{
                        VMHost = $VMHost.Name
                        Build = $VMHost.Build
                        Version = 'Unknown'
                        ReleaseDate =  'Unknown'
                        MinorRelease = 'Unknown'
                        UpdateRelease = 'Unknown'
                    }
                }
            } 
        }
    }
}


Function Get-VMHostLatestVersion {
<#
.SYNOPSIS
    Check if an ESXi update is available.
.DESCRIPTION
    This function checks if an ESXi update is available and displays information about the latest
    version in the current Minor Release. (Minor releases are for example 5.5, 6.0 or 6.5)
    It uses an inofficial JSON based ESXi release database provided by www.virten.net
    The function returns:
    - Current ESXi Build Number
    - Current ESXi Version (Unambiguous friendly name used in Release Notes and https://kb.vmware.com/kb/2143832)
    - Current Version Release Date (When the installed verison has been published)
    - Update Available (True or False)
    - Latest available ESXi build number for that Minor Release
    - Latest available ESXi version
    - Latest available ESXi release date
     
.PARAMETER VMHost
    Name of the ESXi Host (returned by Get-VMHost cmdlet)
.EXAMPLE
    Get-VMHostLatestVersion
.EXAMPLE
    Get-VMHostLatestVersion -VMHost esx4.virten.lab
.EXAMPLE
    Get-VMHostLatestVersion -Verbose |ft -AutoSize
.NOTES
    Author: Florian Grehl
    Twitter: @virten
    Website: www.virten.net
     
    Changelog:
    2017-09-24 - v1.0 - Initial Release
.LINK
    http://www.virten.net
#>

    [CmdletBinding()]
    Param(
        [Parameter(Mandatory=$false,ValueFromPipeline=$true)]$VMHosts
    )
    
    Begin
    {
        $ErrorActionPreference = 'Stop'
        $WarningPreference = 'SilentlyContinue'
        $esxiReleases = Invoke-WebRequest -Uri http://www.virten.net/repo/esxiReleases.json | ConvertFrom-Json
    }

    Process
    {
        if (-not $VMHosts) {
            $vmHosts = Get-VMHost
        } else {
            $vmHosts = Get-VMHost -Name $VMHosts
        }
    
        Foreach ($vmHost in $vmHosts) {   
            if ($VMHost.Build){
                $buildFound = $false
                Foreach ($release in $esxiReleases.data.esxiReleases) {
                    If ($vmHost.Build -eq $release.Build) {
                        Foreach ($rel in $esxiReleases.data.esxiReleases) {
                            If ($release.minorRelease -eq $rel.minorRelease) {
                                $latestBuild = $rel
                                break
                            }
                        }
                        if($VMHost.Build -eq $latestBuild.Build){
                            $updateAvailable = $false
                            Write-Verbose "ESXi Host $($VMHost.Name) (Build: $($vmHost.Build)): running on latest version..."
                        }else {
                            $updateAvailable = $true
                            Write-Verbose "ESXi Host $($VMHost.Name) (Build: $($vmHost.Build)): Update to $($latestBuild.friendlyName) available! (Image Profile: $($latestBuild.imageProfile))"
                        }
                        [pscustomobject] @{
                            VMHost = $VMHost.Name
                            currentBuild = $VMHost.Build
                            currentVersion = $release.friendlyName
                            currentReleaseDate =  $release.releaseDate
                            updateAvailable = $updateAvailable
                            latestBuild = $latestBuild.Build
                            latestVersion = $latestBuild.friendlyName
                            latestReleaseDate = $latestBuild.releaseDate
                        }
                        $buildFound = $true
                    } 
                }
                if (-Not $buildFound) {
                    Write-Verbose "ESXi Host $($VMHost.Name) (Build: $($vmHost.Build)): Version not found in JSON database. Please contact www.virten.net/about/"
                    [pscustomobject] @{
                        VMHost = $VMHost.Name
                        currentBuild = $VMHost.Build
                        currentVersion = ''
                        currentReleaseDate = ''
                        updateAvailable = ''
                        latestBuild = ''
                        latestVersion = ''
                        latestReleaseDate = ''
                    }
                }
            }
        }
    }
}


Function Convert-ScsiCode {
<#
.SYNOPSIS
    Decode SCSI Status Codes
.DESCRIPTION
    Decodes SCSI Sense Codes found in the vmkernel.log from ESXi hosts.
    It uses a JSON based SCSI Code database provided by www.virten.net
     
    vmkernel.log Example:
    ScsiDeviceIO: [...] Cmd 0xG [...] to dev "naa.x" failed H:0xA D:0xB P:0xC Valid sense data: 0xD 0xE 0xF.
 
    - A: Host Status Code
    - B: Device Status Code
    - C: Plugin Status Code
    - D: Sense Key
    - E: Additional Sense Code (ASC)
    - F: Additional Sense Code Qualifier (ASCQ)
    - G: Operational Code (Command)
     
.PARAMETER HostStatus
    Host Status Code
.PARAMETER DeviceStatus
    Device Status Code
.PARAMETER PluginStatus
    Plugin Status Code
.PARAMETER SenseKey
    Sense Key
.PARAMETER ASC
    Additional Sense Code (ASC)
.PARAMETER ASCQ
    Additional Sense Code Qualifier (ASCQ)
.PARAMETER OpCode
    Operational Code (Command)
.EXAMPLE
    Convert-ScsiCode -HostStatus 1
.EXAMPLE
    Convert-ScsiCode -HostStatus 0 -DeviceStatus 2 -PluginStatus 4 |ft -AutoSize -Wrap
.EXAMPLE
    Convert-ScsiCode 0 2 0 5 24 0 1a |ft -AutoSize -Wrap
.NOTES
    Author: Florian Grehl
    Twitter: @virten
    Website: www.virten.net
     
    Changelog:
    2017-09-04 - v1.0 - Initial Release
 
.LINK
    http://www.virten.net
#>

    Param(
    [Parameter(Mandatory=$false)][String]$HostStatus,
    [Parameter(Mandatory=$false)][String]$DeviceStatus,
    [Parameter(Mandatory=$false)][String]$PluginStatus,
    [Parameter(Mandatory=$false)][String]$SenseKey,
    [Parameter(Mandatory=$false)][String]$ASC,
    [Parameter(Mandatory=$false)][String]$ASCQ,
    [Parameter(Mandatory=$false)][String]$OpCode
    )

    Begin
    {
        $ErrorActionPreference = 'Stop'
        $scsiCodes = Invoke-WebRequest -Uri http://www.virten.net/repo/scsicodesV2.json | ConvertFrom-Json
    }

    Process
    {
        $results = @()

        if ($HostStatus){
            $HostStatus = $HostStatus.PadLeft(2, '0');
            $HS = $scsiCodes.data.hostCode.($HostStatus)
            if ($HS){
                Write-Verbose "Host Status 0x$($HostStatus): $($HS.name) ($($HS.description))"
                $info = [pscustomobject] @{
                    Type = "Host Status"
                    Code = "0x$($HostStatus)"
                    Name = $HS.name;
                    Description = $HS.description;
                } 
            } else {
                Write-Verbose "Host Status 0x$($HostStatus): Unknown"
                $info = [pscustomobject] @{
                    Type = "Host Status"
                    Code = "0x$($HostStatus)"
                    Name = "UNKNOWN";
                    Description = "Host status code unknown.";
                } 
                        
            }
            $results+=$info
        }

        if ($DeviceStatus){
            $DeviceStatus = $DeviceStatus.PadLeft(2, '0');
            $DS = $scsiCodes.data.deviceCode.($DeviceStatus)
            if ($DS){
                Write-Verbose "Device Status 0x$($DeviceStatus): $($DS.name) ($($DS.description))" 
                $info = [pscustomobject] @{
                    Type = "Device Status"
                    Code = "0x$($DeviceStatus)"
                    Name = $DS.name;
                    Description = $DS.description;
                }
            } else {
                Write-Verbose "Device Status 0x$($DeviceStatus): Unknown"
                $info = [pscustomobject] @{
                    Type = "Device Status"
                    Code = "0x$($DeviceStatus)"
                    Name = "UNKNOWN";
                    Description = "Device status code unknown.";
                } 

            }
            $results+=$info
        }

        if ($PluginStatus){
            $PluginStatus = $PluginStatus.PadLeft(2, '0');
            $PS = $scsiCodes.data.pluginCode.($PluginStatus)
            if ($PS){
                Write-Verbose "Plugin Status 0x$($PluginStatus): $($PS.name) ($($PS.description))"
                $info = [pscustomobject] @{
                    Type = "Plugin Status"
                    Code = "0x$($PluginStatus)"
                    Name = $PS.name;
                    Description = $PS.description;
                }
            } else {
                Write-Verbose "Plugin Status 0x$($DeviceStatus): Unknown"
                $info = [pscustomobject] @{
                    Type = "Plugin Status"
                    Code = "0x$($PluginStatus)"
                    Name = "UNKNOWN";
                    Description = "Plugin status code unknown.";
                } 
            }
            $results+=$info
        }
        
        if ($SenseKey){
            $SenseKey = $SenseKey.PadLeft(2, '0');
            $SK = $scsiCodes.data.senseKey.($SenseKey)
            if ($SK){
                Write-Verbose "Sense Key 0x$($SenseKey): $($SK.name)"
                $info = [pscustomobject] @{
                    Type = "Sense Key"
                    Code = "0x$($SenseKey)"
                    Name = $SK.name;
                    Description = $SK.description;
                }
            } else {
                Write-Verbose "Sense Key 0x$($SenseKey): Unknown"
                $info = [pscustomobject] @{
                    Type = "Plugin Status"
                    Code = "0x$($SenseKey)"
                    Name = "UNKNOWN";
                    Description = "Sense key unknown.";
                } 
            }
            $results+=$info
        }

        if ($ASC -and $ASCQ){
            $ASC = $ASC.PadLeft(2, '0');
            $ASCQ = $ASCQ.PadLeft(2, '0'); 
            $ASD = $scsiCodes.data.asd.($ASC).($ASCQ)
           
            if ($ASD){
                Write-Verbose "Additional sense data $($ASC)/$($ASCQ): $($ASD.name)"
                $info = [pscustomobject] @{
                    Type = "Additional Sense Data"
                    Code = "$($ASC)/$($ASCQ)"
                    Name = $ASD.name;
                    Description = $ASD.description;
                }
            } else {
                Write-Verbose "Additional sense data $($ASC)/$($ASCQ): Unknown"
                $info = [pscustomobject] @{
                    Type = "Additional Sense Data"
                    Code = "$($ASC)/$($ASCQ)"
                    Name = "UNKNOWN";
                    Description = "Additional Sense Data unknown";
                }
            }
            $results+=$info
        }

        if ($OpCode){
            $OpCode = $OpCode.PadLeft(2, '0');
            $OC = $scsiCodes.data.opCode.($OpCode)
            if ($OC){
                Write-Verbose "Op Code 0x$($OpCode): $($OC.name)"
                $info = [pscustomobject] @{
                    Type = "OP Code"
                    Code = "0x$($OpCode)"
                    Name = $OC.name;
                    Description = $OC.description;
                }
            } else {
                Write-Verbose "Op Code 0x$($OpCode): Unknown"
                $info = [pscustomobject] @{
                    Type = "OP Code"
                    Code = "0x$($OpCode)"
                    Name = "UNKNOWN";
                    Description = "OP Code unknown.";
                } 
            }
            $results+=$info
        }
    }
       End
    {
        $results
    }
}