## Copyright 2014 by Jonas Nagel, LANexpert
## Extracts vSphere VM I/O values including Datastore ID, which allows for correlation of performance data  
## to HDS storage LUNs within Hitachi IOPortal.
##
## Prerequisites: The script assumes that the default statistics interval duration for 1 day is 5 min (300 sec).
##                It retrieves statistics from vCenter, which are only available if the 5 minute interval stats 
##                are set to Level 3 (which is the default) or higher.
##                Install PowerShell version 2 or above, then install vSphere PowerCLI 5.8 or above, make sure
##                you run the command 'Set-ExecutionPolicy RemoteSigned' before executing the script.
##
## Output note: Both CSV files contain the UID of the VMs; this can become the primary key to join both tables
##              within IOPortal. Alternatively EntityID can be used, but it might become duplicate across 
##              multiple vCenter instances.
#
# Execution: extractVMIOstats.ps1 -interval <hours>
#    Parameter 'interval' defines how many hours back of stats are written to the VMIOReport.csv file.
#    Allowed values 1 through 24.
#    A 1 hour interval allows for 20 seconds samples while anything above will fall back to 5 min samples.
#
# Version 1.0
param($interval)

# Define parameters below:

# VMware vCenter Server FQDN/IP
#$vcenter    = "vcserver.lab.zz"
$vcenter    = "vwbulgaria.democenter.zh"

# Define $true for predefined credentials, define $false to log on with current user
$fixedCreds = $true

# Define vCenter admin username
$user    = "Democenter\vmioportal"

# Password will be prompted the first time and written as hash to cred.txt in the script directory.
# Delete the cred.txt file and re-run the script once manually to change the password.

# CSV file output directory (leave "." for script directory)
$outdir    = "G:\development\HiCHperfpkg\data\vmiooutput"

##### Do not modify below this line #####
#
if ($interval -lt 1 -or $interval -gt 24)  {
    Write-Error "Error: -interval must be a value between 1 and 24!"
    Exit
}

if ($fixedCreds) {
    # Password will be prompted the first time and written as hash to cred.txt; delete this file to change the password
    $pass = get-content cred.txt -ErrorAction SilentlyContinue
    if ($pass -eq $null) {
        read-host -prompt "Please enter password for vCenter account" -assecurestring | convertfrom-securestring | out-file cred.txt
        $pass = get-content cred.txt | ConvertTo-SecureString
    } else { 
        $pass = $pass | ConvertTo-SecureString
    }
    # if $creds is not defined, script will use the logged on user to authenticate
    $creds = new-object -typename System.Management.Automation.PSCredential -argumentlist $user,$pass
}

# ZIP function
function out-zip {
    param([string]$path,[string]$file)
    
    if (-not $path.EndsWith('.zip')) {$path += '.zip'}

    if (-not (test-path $path)) {
        set-content $path ("PK" + [char]5 + [char]6 + ("$([char]0)" * 18))
    }
    $ZipFile = (new-object -com shell.application).NameSpace($path)
    get-item $file | foreach {$ZipFile.CopyHere($_.fullname)}
} 

# Load vSphere PowerCLI SnapIn
if ((Get-PSSnapin -Name VMware.VimAutomation.Core -ErrorAction SilentlyContinue) -eq $null) {
    Add-PSsnapin VMware.VimAutomation.Core
}

# connect vCenter server session
if ($creds -ne $null) {
    Connect-VIServer $vcenter -credential $creds -Force -WarningAction SilentlyContinue | Out-Null
} else {
    Connect-VIServer $vcenter -Force -WarningAction SilentlyContinue | Out-Null
}

# get all Hitachi datastores
$datastores = Get-Datastore
$dsList     = @()
    
foreach ($ds in $datastores) {
    # identify device id of datastore
    $ErrorActionPreference="SilentlyContinue"
    $dsId = ($ds | Get-View).info.vmfs.extent[0].diskname
    $ErrorActionPreference="Continue"

    # old HDS WWN vendor ID used to be 50060e8, current vendor ID is 60060e8
    if ($dsID -Match "naa.50060e8" -or $dsID -Match "naa.60060e8") {
        $dsList += $ds
    }
}

# Reference values for sample amount 
# number of samples = amount of time (with intervalSecs = 20)
# 1    = 20 sec
# 180  = 1  hour
# number of samples = amount of time (with intervalSecs = 300)
# 1    = 5 min
# 12   = 1  hour

# real-time stats not supported, lacks desired metrics for unknown reasons
# if ($interval -eq 1) {
    # $intervalSecs = 20
    # $samples = 180
# } else {
    $intervalSecs = 300
    $samples = $interval * 12
# }

# dataArray returns performance stats per VM for final export
$dataArray  = @()

# vm2dsArray returns a table to match VMs to Datastore Names and Datastore IDs
$vm2dsArray = @()

foreach ($ds in $dsList) {
    $vms = Get-VM -Datastore $ds | Where {$_.PowerState -eq "PoweredOn"}
    $dsId = ($ds | Get-View).info.vmfs.extent[0].diskname 

    foreach ($vm in $vms) {
        $vm2ds = New-Object System.Object
        $vm2ds | Add-Member -MemberType NoteProperty -Name "UID" -Value $vm.Uid
        $vm2ds | Add-Member -MemberType NoteProperty -Name "EntityID" -Value $vm.Id
        $vm2ds | Add-Member -MemberType NoteProperty -Name "VMName" -Value $vm.Name
        $vm2ds | Add-Member -MemberType NoteProperty -Name "DatastoreID" -Value $dsId
        $vm2ds | Add-Member -MemberType NoteProperty -Name "Datastore" -Value $ds.Name
        $vm2dsArray += $vm2ds

        $dataArray += get-stat -entity $vm -stat disk.read.average -IntervalSecs $intervalSecs -maxSamples $samples
        $dataArray += get-stat -entity $vm -stat disk.write.average -IntervalSecs $intervalSecs -maxSamples $samples
        $dataArray += get-stat -entity $vm -stat disk.provisioned.latest -IntervalSecs $intervalSecs -maxSamples $samples
        $dataArray += get-stat -entity $vm -stat disk.used.latest -IntervalSecs $intervalSecs -maxSamples $samples
        $dataArray += get-stat -entity $vm -stat disk.maxTotalLatency.latest -IntervalSecs $intervalSecs -maxSamples $samples
        $dataArray += get-stat -entity $vm -stat disk.numberReadAveraged.average -IntervalSecs $intervalSecs -maxSamples $samples
        $dataArray += get-stat -entity $vm -stat disk.numberWriteAveraged.average -IntervalSecs $intervalSecs -maxSamples $samples
    }
}

# Generate time stamp and file name
$CurrentDate = Get-Date
$CurrentDate = $CurrentDate.ToString('MM-dd-yyyy_hh-mm-ss')

# If no path specified assume script path
if ($outdir -eq "." -or $outdir -eq "") {
    $outdir = Split-Path -Parent -Path $MyInvocation.MyCommand.Definition
}

$file1 = $outdir + "\VM2DSTable_" + $CurrentDate + ".csv"
$file2 = $outdir + "\VMIOReport_" + $CurrentDate + ".csv"

$vm2dsArray | Export-CSV $file1 -NoType
$dataArray  | Export-CSV $file2 -NoType

# Create ZIP files and remove CSV
$files = @($file1, $file2)

ForEach ($file In $files) {
    $zfile = $file.Substring(0,$file.Length-3) + "zip"
    out-zip -path $zfile -file $file

    # no other way to avoid race condition of remove-item with asynchronous ZIP function
    sleep 10 
    remove-item $file
}
