<# What this script does:
1. Checks to see if an Internet connection via PowerShell exists, if not fixes it for this session
2. Adds VMware.PowerCLI module if one doesn't already exist in host system
3. Asks for vSphere Administrator credential then checks whether it's valid; then, saves that credential into a XML hash file
4. Connects to each vSphere server as specified in the header section and parse through all hosts that is attached to each cluster
5. Obtains PWWN information of all nodes
6. Skips any node that has certificate issues
7. Displays the nodes for user to chose as an index number
8. Shows an "MDS Template" configuration basing on the user's selection in the step prior
9. Exits the program upon user request
10. Asks the user whether the saved credentials be deleted or retained for future use

What it doesn't do:
1. Does not do anything that's not listed above, including NOT fixing certificate errors in vSphere
2. Does not write to ESXi hosts
3. Does not open a SSH session and automatically commit configurations into MDS switches

How to use it:
1. Copy the entire contents of this into a file onto your desktop with a name such as "mds-script.ps1"
2. Right-click and select "Run with Powershell"
3. Copy the output and paste into your targeted Cisco Nexus Operating System (NX-OS) SAN switches, not sandwiches.

#>

# Header Section: update variables only in these lines
$vSpheres="vCenter01","vCenter02"
$proxy="http://proxy:8080"
$exclusionList="localhost;*.kimconnect.com"
$vsans=("VSAN 10","001","011","111","211","311"),("VSAN 20","002","012","112","212","312")


# Ensure that script is ran in the context of an Administrator
$myWindowsID=[System.Security.Principal.WindowsIdentity]::GetCurrent()
$myWindowsPrincipal=new-object System.Security.Principal.WindowsPrincipal($myWindowsID)

# Get the security principal for the Administrator role
$adminRole=[System.Security.Principal.WindowsBuiltInRole]::Administrator

# Check to see if we are currently running "as Administrator"
if ($myWindowsPrincipal.IsInRole($adminRole))
{
# We are running "as Administrator" - so change the title and background color to indicate this
$Host.UI.RawUI.WindowTitle = $myInvocation.MyCommand.Definition + "(Elevated)"
$Host.UI.RawUI.BackgroundColor = "Black"
clear-host
}
else
{
# We are not running "as Administrator" - so relaunch as administrator

# Create a new process object that starts PowerShell
$newProcess = new-object System.Diagnostics.ProcessStartInfo "PowerShell";

# Specify the current script path and name as a parameter
$newProcess.Arguments = $myInvocation.MyCommand.Definition;

# Indicate that the process should be elevated
$newProcess.Verb = "runas";

# Start the new process
[System.Diagnostics.Process]::Start($newProcess);

# Exit from the current, unelevated, process
exit
}

# Put error log in same directory as script
$scriptName=$MyInvocation.MyCommand.Path
$scriptPath=(Get-Item -Path ".\").FullName
$errorLogPath=($scriptPath+"\$scriptName`_Errors.txt")

function checkProxy{
try{
$connectionTest=iwr download.microsoft.com
#$connectionSucceeds=Test-NetConnection -Computername download.microsoft.com -Port 443 -InformationLevel Quiet
if ($connectionTest){
return $True;
}
}
catch{
return $False
}
}

function fixProxy{
# Check if proxy is enabled on the system and fix it
$proxyKey=(Get-ItemProperty -Path "Registry::HKCU\Software\Microsoft\Windows\CurrentVersion\Internet Settings")
if ($proxyKey.ProxyEnable){
# Set http proxy for browsers
Set-Itemproperty -path "Registry::HKCU\Software\Microsoft\Windows\CurrentVersion\Internet Settings" -Name 'ProxyServer' -value $proxy

# Set winhttp proxy for PowerShell
netsh winhttp set proxy $proxy $exclusionList

[system.net.webrequest]::defaultwebproxy = New-Object system.net.webproxy($proxy)
[system.net.webrequest]::defaultwebproxy.credentials = [System.Net.CredentialCache]::DefaultNetworkCredentials
[system.net.webrequest]::defaultwebproxy.BypassProxyOnLocal = $true
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
}

if (checkProxy){
"Proxy is now good to go..."
}
else{
"Proxy problems..."
break;
}
}

if(!(checkProxy)){"Internet issues detected. Fixing now..."; fixProxy;}

# Set PowerShell Gallery as Trusted to bypass prompts
$trustPSGallery=(Get-psrepository -Name 'PSGallery').InstallationPolicy
If($trustPSGallery -ne 'Trusted'){
Set-PSRepository -Name 'PSGallery' -InstallationPolicy Trusted -Confirm:$false
}

# Include the required WMWare PowerCLI module from the PowerShell Gallery
if (!(Get-InstalledModule -Name VMware.PowerCLI)) {

Install-Module -Name VMware.PowerCLI -AllowClobber -Force; #Warning: this module will clobber some commmands from Microsoft SQL PowerShell module

# Ignore cert errors and other messages
Set-PowerCLIConfiguration -ParticipateInCeip $False -Confirm:$False;
Set-PowerCLIConfiguration -InvalidCertificateAction Ignore -Confirm:$False;
}
else{
# Ignore cert errors and other messages
Set-PowerCLIConfiguration -ParticipateInCeip $False -Confirm:$False;
Set-PowerCLIConfiguration -InvalidCertificateAction Ignore -Confirm:$False;
cls;
}

<#
# Check if proxy is enabled, then assign the proper proxy server
if((Get-ItemProperty -Path "Registry::HKCU\Software\Microsoft\Windows\CurrentVersion\Internet Settings").ProxyEnable) {
Set-ItemProperty -Path "Registry::HKCU\Software\Microsoft\Windows\CurrentVersion\Internet Settings" ProxyServer -value $proxy
$PSDefaultParameterValues = @{ "*:Proxy"=$proxy}}
#>

<#
# Future development:
# function validateAdmin
# function checkIfRecordsExist
# Apply script to targets
#>


# Save Credentials into XML file for future use
$domain=$env:USERDOMAIN
$goodCredential=$False
$credentialsFolder="$scriptPath\Credentials"
$credentialsFolderExists=[System.IO.Directory]::Exists($credentialsFolder)
if(!($credentialsFolderExists)){mkdir $credentialsFolder;}

# Obtain username to check whether such credential has been saved prior
$user=(Read-Host -Prompt 'Input a vSphere Administrator Username');
$credentialFile="$credentialsFolder\"+"$user`.clixml"
$credentialFileExists=[System.IO.File]::Exists($credentialFile)
if(!($credentialFileExists)){
"This credential has not been saved previously.";
$GLOBAL:reaskUsername=$False;
$goodCredential=$False;
}

function getCredential{
if ($reaskUsername){
$GLOBAL:user=(Read-Host -Prompt 'Input a vSphere Administrator Username');
}
$GLOBAL:credentialFile="$credentialsFolder\"+"$user`.clixml"
$userID = "$domain\"+"$user"
$securedValue = (Read-Host -AsSecureString -Prompt "Input the password for account $userID")
$password = [System.Runtime.InteropServices.Marshal]::PtrToStringAuto([System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($securedValue))
$pass = ConvertTo-SecureString -AsPlainText $Password -Force
$GLOBAL:cred = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList $userID,$pass
$GLOBAL:credentialFile="$credentialsFolder\"+"$user`.clixml"
$GLOBAL:credentialFileExists=[System.IO.File]::Exists($credentialFile)
}

function testCredential{
$connection=connect-viserver $vSpheres[0] -Protocol https -Credential $cred -ErrorAction SilentlyContinue
if($connection -eq $null) {
write-host "No connected servers or credential doesnt work."
$GLOBAL:goodCredential=$false;
$GLOBAL:reaskUsername=$True;
}
else{
#"Credential works. Thus, it has been saved at $credentialFile for future use."
Disconnect-VIServer -Server $global:DefaultVIServers -Force -Confirm:$false
cls;
$cred | Export-Clixml $credentialFile;
#$GLOBAL:goodCredential=$True;
break;
}
}

if(!($credentialFileExists)){
# Test credential and reprompt if it doesn't work
while ($goodCredential -eq $False){
getCredential;
testCredential;
}
}

function selectRecord{
$display
$count=$collection.count-1

# Require user input with a loop
$index="";
while ($index.ToLower() -ne "exit"){
try {
[string]$index=Read-Host -Prompt "Please type the index number from 0 to $count`. To Exit, type 'exit' or press Ctrl+C";
if ($index.ToLower() -eq "exit"){break;}
if ([int]$index -gt $count -or [int]$index -lt 0){
"Please pick a number within the range of 0 to $count";
}
else{
generateScript $collection[$index];
}
}
catch {
#$_.Exception.Message;
}
}#end while
}

function vConnect ($vCenterName,$credential) {

#Connect-Viserver $vCenterName -Credential $cred
$hosts=Connect-Viserver $vCenterName -Protocol https -Credential $credential -ErrorAction SilentlyContinue
if($hosts -eq $null) {
# Newer version of PowerCLI doesn't work with vCenter 5.5; thus PowerCLI 6.5R1 is required
"Unable to connect to $vCenterName. That vSphere and its associated clusters scanning have been skipped...";
$GLOBAL:skip = $True;
}
else{
"Scanning $vCenterName..."
$GLOBAL:skip = $False;
}
}

function retrieveRecords{
$records=@()

# Ensure that TLS 1.2 is used
[Net.ServicePointManager]::Expect100Continue = $true;
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
[System.Net.ServicePointManager]::ServerCertificateValidationCallback = {$true}

$cred=(Import-clixml $credentialFile)

try{
foreach ($vSphere in $vSpheres){
try{
#Connect-VIServer -Server $vSphere -Protocol https -credential $cred
vConnect $vSphere $cred

if (!($skip)){
$GLOBAL:esxHosts = Get-VMHost # All hosts connected in vCenter
foreach ($esx in $esxHosts){
$hbas = Get-VMHostHba -VMHost $esx -Type FibreChannel
$esxName=($esx.Name -split '\.')[0]

$portNames=@()
$record=@()

foreach ($hba in $hbas){
$wwpn = ("{0:x}" -f $hba.PortWorldWideName) -replace '..(?!$)', '$&:'
#$wwnn = ("{0:x}" -f $hba.NodeWorldWideName) -replace '..(?!$)', '$&:'
#$portNames += $wwnn,$wwpn
$portNames += $wwpn
}
$record += @($esxName,@($portNames))
$records+=,@($record)
}
Disconnect-VIServer -Server $global:DefaultVIServers -Force -Confirm:$false
}

}
catch{continue;}
}
} #closure of try
catch{
$errorMsg = (Get-Date -Format g)+": "+ $_.Exception.Message
$errorMsg
# Add-Content $errorLogPath $errorMsg
# "There was an error, and the log is updated at this location: '$errorLogPath'."
Break;
}
finally{
$count=$records.count
$show="`n--------------------------------------------------`nThere are $count ESXi hosts with HBA connection records: `n--------------------------------------------------`n"
for ($row=0;$row -le $records.count-1;$row++){
$server=$records[$row][0]
$show += "$row" + ": " + "$server" + "`n"
}
}
$GLOBAL:collection=$records
$GLOBAL:display=$show
}

function generateScript($item){
$output=""
$hostName=$item[0]
$wppns=$item[1]
# $firstChars=(-join ($hostName.ToCharArray() | Select-Object -First 2)).ToUpper()
$firstChars=[string]$hostName.Substring(0,2)


for ($i=0;$i -le $specialConfigs.count-1; $i++){
if ($firstChars -eq $SpecialConfigs[$i][0]){
$writeConfig=$specialConfigs[$i][1];
}
}

<#
Note: This is for an environment without iSCSI
vmhba0 is the internal Smart Array controller; this its PWWN shall not be used to configure the Cisco SAN switches.
vmhba64 is associated with VSAN10 and vmhba65 with VSAN20.
#>
"`n############################# Configuration Script for $($hostName.ToString().ToUpper()) ########################################"
foreach ($vsan in $vsans) {
$firstElement=$vsan[0]
$secondElement=$vsan[1]
$thirdElement=$vsan[2]
$fourthElement=$vsan[3]
$fifthElement=$vsan[4]
$sixthElement=$vsan[5]

# There should be 2 items in the wppns array: first item will associate with VSAN 10, and second with VSAN 20
if($firstElement -eq "VSAN 10"){$thisWPPN=$wppns[0];}
else{$thisWPPN=$wppns[1];}

# Different versions of firmware may require varying last commit lines
Switch ($firstChars){
"mp"{$lastLines="copy running-config startup-config`ny"}
"lp"{$lastLines="zone commit $firstElement`ncopy running-config startup-config fabric"}
"rp"{$lastLines="copy running-config startup-config`ny"}
}

# The first characters of hostname will correspond to its regional 3PAR SAN storage name
$sanName="h3pss001"
Switch ($firstChars){
"mp"{$sanName="mp"+"$sanName"+"_";}
"lp"{$sanName="lp"+"$sanName"+"_";}
"rp"{$sanName="mp"+"$sanName"+"_";}
}

$output += "
##################################################################################################################################
######### SAN Name: $sanName`: $firstElement #########
config t

fcalias name $hostName`_"+"$secondElement $firstElement
member pwwn $thisWPPN
exit

zone name $hostName`_"+"$secondElement`-$sanName"+"$thirdElement $firstElement
member fcalias $hostName`_"+"$secondElement
member fcalias $sanName"+"$thirdElement
exit

zone name $hostName`_"+"$secondElement`-$sanName"+"$fourthElement $firstElement
member fcalias $hostName`_"+"$secondElement
member fcalias $sanName"+"$fourthElement
exit

zone name $hostName`_"+"$secondElement`-$sanName"+"$fifthElement $firstElement
member fcalias $hostName`_"+"$secondElement`
member fcalias $sanName"+"$fifthElement
exit

zone name $hostName`_"+"$secondElement`-$sanName"+"$sixthElement $firstElement
member fcalias $hostName`_"+"$secondElement`
member fcalias $sanName"+"$sixthElement
exit

zoneset name ZoneSet01 $firstElement
member $hostName`_"+"$secondElement`-$sanName"+"$thirdElement
member $hostName`_"+"$secondElement`-$sanName"+"$fourthElement
member $hostName`_"+"$secondElement`-$sanName"+"$fifthElement
member $hostName`_"+"$secondElement`-$sanName"+"$sixthElement
exit

zoneset activate name ZoneSet01 $firstElement
$lastLines

### Useful show commands #########################################################################################################
# show zoneset | inc '$hostName' # To check zonesets for matches of the new servername
# show zoneset active | inc '$thisWPPN' # To check active zoneset for matches of a specific wppn
# show flogi database | inc '$thisWPPN' # To show the Fabric Login database for matches of a specific wppn
# show fcalias name $hostName`_"+"$secondElement $firstElement # To check VSAN 10 for any entries of the specific fcalias
# show fcalias $firstElement # To display the long output of all VSAN 10 configs. Useful to perform verification holistically
##################################################################################################################################
"
}
$output
selectRecord
}

function askRemoveCredential{
$GLOBAL:cleanCred=Read-Host -Prompt "`nRecords have been retrieved using a saved credential on this computer. Would you like to remove that credential file now? 'Yes' or 'No'"
if ($cleanCred.ToLower() -eq 'yes' -or $cleanCred.ToLower() -eq 'y' ){
Remove-Item -path $credentialFile;
}
}

retrieveRecords
askRemoveCredential
selectRecord

Output:

PS C:\Scripts> C:\Scripts\MDS-Zoning.ps1
Input the Admin Username: kim

Name Port User
---- ---- ----
vcenter01.kimconnect.com 443 KIMCONNECT\kim

--------------------------------------------------
There are 8 ESXi hosts with HBA connection records:
--------------------------------------------------
0: irv-esxi02b
1: irv-esxi01b
2: aws-esxi01c
3: irv-esxi01d
4: irv-esxi02d
5: irv-esxi01a
6: irv-esxi02a
7: irv-esxi03a

Please type the index number corresponding to the desired ESXi Host to generate a MDS Zoning Configuration template.
To end program, please type 'exit' and press [Enter]: 1

############################# Configuration Script for irv-ESXI01B #############################

##############################################################
## VSAN01 ##
config t

fcalias name irv-esxi01b_001 VSAN01
member pwwn 88:88:88:88:4e:d0:00:20
exit

zone name irv-esxi01b_001-fl-3par01_011 VSAN01
member fcalias irv-esxi01b_001
member fcalias fl-3par01_011
exit

zone name irv-esxi01b_001-fl-3par01_111 VSAN01
member fcalias irv-esxi01b_001
member fcalias fl-3par01_111
exit

zone name irv-esxi01b_001-fl-3par01_211 VSAN01
member fcalias irv-esxi01b_001
member fcalias fl-3par01_211
exit

zone name irv-esxi01b_001-fl-3par01_311 VSAN01
member fcalias irv-esxi01b_001
member fcalias fl-3par01_311
exit

zoneset name ZoneSet01 VSAN01
member irv-esxi01b_001-fl-3par01_011
member irv-esxi01b_001-fl-3par01_111
member irv-esxi01b_001-fl-3par01_211
member irv-esxi01b_001-fl-3par01_311
exit

zoneset activate name ZoneSet01 VSAN01
copy running-config startup-config
show fcalias VSAN01

### Useful show commands ###
# show fcalias vsan VSAN01
# show zoneset active
# show flogi database | inc '88:88:88:88:4e:d0:00:20'
##############################################################

##############################################################
## VSAN02 ##
config t

fcalias name irv-esxi01b_002 VSAN02
member pwwn 88:88:88:88:4e:d0:00:22
exit

zone name irv-esxi01b_002-fl-3par01_012 VSAN02
member fcalias irv-esxi01b_002
member fcalias fl-3par01_012
exit

zone name irv-esxi01b_002-fl-3par01_112 VSAN02
member fcalias irv-esxi01b_002
member fcalias fl-3par01_112
exit

zone name irv-esxi01b_002-fl-3par01_212 VSAN02
member fcalias irv-esxi01b_002
member fcalias fl-3par01_212
exit

zone name irv-esxi01b_002-fl-3par01_312 VSAN02
member fcalias irv-esxi01b_002
member fcalias fl-3par01_312
exit

zoneset name ZoneSet01 VSAN02
member irv-esxi01b_002-fl-3par01_012
member irv-esxi01b_002-fl-3par01_112
member irv-esxi01b_002-fl-3par01_212
member irv-esxi01b_002-fl-3par01_312
exit

zoneset activate name ZoneSet01 VSAN02
copy running-config startup-config
show fcalias VSAN02

### Useful show commands ###
# show fcalias vsan VSAN02
# show zoneset active
# show flogi database | inc '88:88:88:88:4e:d0:00:22'
##############################################################


--------------------------------------------------
There are 8 ESXi hosts with HBA connection records:
--------------------------------------------------
0: irv-esxi02b
1: irv-esxi01b
2: aws-esxi01c
3: irv-esxi01d
4: irv-esxi02d
5: irv-esxi01a
6: irv-esxi02a
7: irv-esxi03a

Please type the index number corresponding to the desired ESXi Host to generate a MDS Zoning Configuration template.
To end program, please 'exit' and press [Enter]: exit
Program Exited.