Symm.psm1

# Module Declarations

  [String[]]$flogiHeader  = "Interface", "VSANID", "FCID", "PortWWN", "NodeWWN", "Flags", "DeviceAlias";
  [String[]]$LogInHeader  = "Array", "DirPort", "WWN", "NodeName", "PortName", "FCID", "LoggedIN", "OnFabric";

#

# Non-Exported Functions

  . $PSScriptRoot\Local\Get-ArrayInfoXML.ps1
  . $PSScriptRoot\Local\Export-PSCredentials.ps1
  . $PSScriptRoot\Local\Import-PSCredentials.ps1

  function Set-SYMCLI-Options {
    
    if ($env:SYMCLI_WAIT_ON_GK -eq $null) {
      New-Item -Path env:SYMCLI_WAIT_ON_GK -Value "1" | Out-Null
    } else { Set-Item -Path env:SYMCLI_WAIT_ON_GK -Value "1" | Out-Null; }

    if ($env:SYMCLI_WAIT_ON_DB -eq $null) {
      New-Item -Path env:SYMCLI_WAIT_ON_DB -Value "1" | Out-Null
    } else { Set-Item -Path env:SYMCLI_WAIT_ON_DB -Value "1" | Out-Null; }

    if ($env:SYMCLI_CTL_ACCESS -eq $null) {
      New-Item -Path env:SYMCLI_CTL_ACCESS -Value "PARALLEL" | Out-Null;
    } else { Set-Item -Path env:SYMCLI_CTL_ACCESS -Value "PARALLEL" | Out-Null; }

  }

  function Set-symapi_db {
    if ($env:SYMCLI_OFFLINE -eq $null) {
      New-Item -Path env:SYMCLI_OFFLINE -Value "0" | Out-Null
    } else { Set-Item -Path env:SYMCLI_OFFLINE -Value "0" | Out-Null; }

    Set-SYMCLI-Options
  }

  function Set-SymcliConnect {
    param ([string]$connectName)
    
      if ($env:SYMCLI_CONNECT -eq $null) {
        New-Item -Path env:SYMCLI_CONNECT -Value $connectName | Out-Null
        New-Item -Path env:SYMCLI_CONNECT_TYPE -Value "REMOTE" | Out-Null
        Write-Verbose "New-Item $env:SYMCLI_CONNECT & $env:SYMCLI_CONNECT_TYPE";
        #symcfg discover
      } elseif ($env:SYMCLI_CONNECT -match $connectName) {
      } else {
        Set-Item -Path env:SYMCLI_CONNECT -Value $connectName | Out-Null
        Write-Verbose "Set-Item $env:SYMCLI_CONNECT";
        #symcfg discover
      }
  }

  function Get-SymcliConnect {
    param ([string]$cliServer)
      
      if ($env:COMPUTERNAME -notmatch $hostnm) { # processing against remote symapi_db
        Set-SymcliConnect $cliServer
      } elseif (($env:COMPUTERNAME -match $hostnm) -and ($remote -match $cliServer)) {
        # symcli processed locally on this host
        if ($env:SYMCLI_CONNECT -ne $null) {
          Remove-Item -Path env:SYMCLI_CONNECT | Out-Null
          Write-Verbose "Remove-Item SYMCLI_CONNECT"
        }
        if ($env:SYMCLI_CONNECT_TYPE -ne $null) {
          Remove-Item -Path env:SYMCLI_CONNECT_TYPE | Out-Null
          Write-Verbose "Remove-Item SYMCLI_CONNECT_TYPE"
        }    
      } else {
        Set-SymcliConnect $cliServer
      }
  }

  function Remove-FALogin {
    param ([string]$symmID, [string]$faPort, [string]$wwn)

    #$yn = Read-Host (" symaccess -sid $symmID -wwn $wwn -dirport $faPort -login remove -nopr");
    #if ($yn -match "y") {

      $rslt = symaccess -sid $symmID -wwn $wwn -dirport $faPort -login remove -nopr;
      if ($LastExitCode -eq 0) { # last command executed without error
        Write-Verbose " $wwn Login removed from $faPort on $symmID";
      } else {
        Write-Host " Error: $wwn Login removed from $faPort on $symmID" -ForegroundColor Red;
      }

    #}

  }

  function Set-PortValue {
    param ([string]$pv)

    ($blade,$port) = $pv.Split("/");
    if ($blade.Length -eq 4) { $blade = $blade.Substring(2,2) } else { $blade = "0" + $blade.Substring(2,1) }
    if ($port.Length -eq 1) { $port = "0" + $port };
    $tmp = $blade + $port;
    return $tmp;
  }

  function Set-WwnAlias {
    param ([string]$symmID, [string]$wwn)

    $pwwn = $wwn -replace "([0-9A-Fa-f]{2})",'$1:' # Insert ':'
    $pwwn = $pwwn.TrimEnd(":",1);  # Remove trailing ':'

    # This code snippet searches all the fabric folders searching for a matching
    # flogi entry in the files named, "*_flogi.csv".

    [bool]$flogiFound = $false;
    Get-ChildItem $Script:SwOrgPath |
      ForEach-Object { # Processing flogi for each folder in <execution path>\SWInfo-<org>
        $FBPath = $_.PSPath;
        if (!($flogiFound)) {
          Get-ChildItem $FBPath -Filter "*_flogi.csv" |
            Where-Object { $_.Attributes -ne "Directory"} |
            ForEach-Object {
              If (Get-Content $_.FullName | Select-String -List -SimpleMatch -Encoding ascii -Pattern $pwwn ) {
                # $_.FullName; # <execution path>\SWInfo-<org>\<fabricname>\<switchname>_flogi.csv
                # $_.PSChildName; # <switchname>_flogi.csv
                $flogiFound = $True;
                ($switch,$s1) = $_.PSChildName.Split("_");  # GET SWITCH NAME
                $swChars = $switch.ToCharArray();
                # EXTRACT THE RECORD FROM THE FILE
                  $obj = Select-String -Path $_.FullName -List -SimpleMatch -Encoding ascii -Pattern $pwwn
                  # REMOVE SQUARE BRACKETS SURROUNDING THE DEVICE-ALIAS NAME
                  $found = $obj.Line
                  $found = $found -replace "\[", "";
                  $found = $found -replace "\]", "";
                  Write-Verbose " found: $found"
                #
                # CONVERT RECORD INTO CSV OBJECT
                  $flogiObj = ConvertFrom-CSV -InputObject $found -Header $flogiHeader;
                #
                # EXTRACT HOSTNAME or StorageArray FROM DEVICE-ALIAS
                $DeviceAlias = $flogiObj.DeviceAlias;
                if ($DeviceAlias -eq $null) { #
                  $dvAlias = $flogiObj.Flags;
                  if ($swChars -eq "s") { # it's a storage edge switch
                    $hostn = $dvAlias;
                  } else {
                    ($hostn,$hostHba ) = $dvAlias.Split("_");
                  }
                } else { 
                  if ($swChars -eq "s") { # it's a storage edge switch
                    $hostn = $DeviceAlias;
                  } else {
                    ($hostn,$hostHba) = $DeviceAlias.Split("_");
                  }
                };

                <#
                  # take first numeric to end of HBA string.
                  $sz = $hostHba.Length - 1;
                  $hary = $hostHba.ToCharArray();
                  for ($i=0;$i -le $sz;$i++) { if ($hary[$i] -match "[0-9]") {$offset = $i; continue;} }
                  $len = ($sz - $offset) + 1;
                  $intnum = $hostHba.Substring($offset,$len);
                #>

                
                if ( ($hostn -ne "VP") -and ($hostn -ne "V") -and ($hostn -ne "P") ) {
                  # ADD LEADING ZEROS TO Blade and port if needed
                  $portname = Set-PortValue $flogiObj.Interface;
                  $hostn = $hostn.ToLower();

                  $alias = "$hostn" + "/" + "$switch" + "_" + "$portname";

                  if ($alias.Length -gt 29) { # Max size of the alias field
                    $hostn = $hostn.Replace("0",""); # Remove ZEROS from hostname
                    $alias = "$hostn" + "/" + "$switch" + "_" + "$portname";
                  }

                  $symmAlias = '"' + $alias + '"';

                  symaccess -sid $symmid rename -wwn $wwn -alias $symmAlias 2> $errFile;

                  if ($LastExitCode -eq 0) { # last command executed without error
                    Write-Host " $wwn set to $symmAlias on $symmID";
                  } else {
                    $errRslt = Get-Content -Path $errFile;
                    $errLine = $errRslt -match "already assigned";
                    if ($errLine) { # found line indicating duplicate name
                      $hbanum = $hostHba -replace '\D+(\d+)','$1'; # extract numeric value
                      if ($hbnum -eq $hostHba) { $hbanum = "9"}; # no numeric in string;
                      $alias = "$hostn" + "_" + "$hbanum" +  "/" + "$switch" + "_" + "$portname";
                      $symmAlias = '"' + $alias + '"';
                      symaccess -sid $symmid rename -wwn $wwn -alias $symmAlias 2> $errFile;
                      if (Test-Path $errFile) { Remove-Item -Path $errFile };
                      if ($LastExitCode -eq 0) { Write-Host " $wwn set to $symmAlias on $symmID"; };
                    } else {
                      Write-Host " Error: $wwn setting $symmAlias on $symmID" -ForegroundColor Red;
                      Write-Host " Flogi Entry: $found" -ForegroundColor Magenta
                      #Read-Host " Waiting on Error Result @285"
                    }
                  }
                } else {
                  Write-Host " $pwwn missing device-alias assignment" -ForegroundColor Red;
                }
              
              } else {
                # Flogi entry not found is this fabric
              } 
            }
            if (Test-Path $errFile) { Remove-Item -Path $errFile };
        }
      }
  }

  function Update-FALogin {
    param ([string]$symmID, [string]$faPort)
    
    begin { }
    
    process {
      $wwn    = $_.originator_port_wwn;
      $node   = $_.awwn_node_name;
      $pname  = $_.awwn_port_name;
      $onFab  = $_.on_fabric;
      $onFA   = $_.logged_in;

      if ($onFA -match "No") {
        Remove-FaLogin $symmID $faPort $wwn
        <# to delete selected NOT logged in initiators
          if ( ($node -eq "NULL") -or ($node -eq $wwn) -or ($node -match "vmx") ) {
            Remove-FaLogin $symmID $faPort $wwn
          }
        #>

      } elseif (($node -eq "NULL") -or ($node -eq $wwn) -or ($node -match "not") ) {
        $Script:initCnt++;
        Set-WwnAlias $symmID $wwn
      } else {
        $Script:initCnt++
      }

    }
    end { }

  }

  function Set-CSVFALogin {
    param ([string]$symmID, [string]$faPort)
    begin { }
    process {
      $wwn    = $_.originator_port_wwn;
      $node   = $_.awwn_node_name;
      $pname  = $_.awwn_port_name;
      $fcid   = $_.fcid;
      $onFab  = $_.on_fabric;
      $onFA   = $_.logged_in;

      if ($onFA -eq "Yes") { $Script:initCnt++; } else { $Script:notCnt++ };

      if ($SetLogin) {
        $csvRec = $symmID + "," + $faPort + "," + $wwn + "," + $node + "," + $pname + ",";
        $csvRec += $fcid + "," + $onFA + "," + $onFab;

        $csvRec | Out-File -Append -Encoding ascii -FilePath $Script:csvFile

      }

    }
    end { }
  }

  function Get-FALogins {
    param ([string]$symmID, [string]$faPort)

    symaccess -sid $symmID list logins -dirport $faPort 2>$1 > $rstFile;  # suppressing error output
    if ($LastExitCode -eq 0) {
      [xml]$faLogin = symaccess -sid $symmID list logins -dirport $faPort -out xml_element;
      if ($LastExitCode -eq 0) { # last command executed without error
        if (($SetLogin) -or ($SumLogin)) {
          $faLogin.SelectNodes("//Login") | Set-CSVFALogin $symmID $faPort
        } else {
          $faLogin.SelectNodes("//Login") | Update-FALogin $symmID $faPort
        }
      } else {

      }
    } else {
      if (!($SetLogin) -and (!($SumLogin))) {
        Write-Host " No device masking login entries for $faPort on $symmID" -ForegroundColor Magenta
      }
    }
  }

  function Get-DirectorInfo {
    param([string]$symmID)
    
    begin { };

    process {
      $faID = $_.Dir_Info.id;
      ($f1,$fa) = $faID.Split("-");
      [int]$portCnt = $_.Dir_Info.ports;
      Write-Verbose " Processing FA $fa";

      ForEach-Object -InputObject $_.Port {
        for ($i=0; $i -lt $portCnt; $i++) {
          $port = $_.Port_Info.port[$i];
          $faPort = $fa + ":" + $port;
          if (($SetLogin) -or ($SumLogin)) {
            Write-Host " Processing FA Port - $faPort" -ForegroundColor Green -NoNewline
          } else {
            Write-Host " Processing FA Port - $faPort" -ForegroundColor Green
          }
          $Script:initCnt = 0; $Script:notCnt = 0;
          Get-FALogins $symmID $faPort;
          if (($SetLogin) -or ($SumLogin)) {
            Write-Host " $Script:initCnt logged in $faPort Not Logged In $Script:notCnt" -ForegroundColor Magenta
          } else {
            Write-Host " $Script:initCnt initiators logged into $faPort" -ForegroundColor Magenta
            Write-Host " "
          }
       
        }
      }
    };

    end { };

  }

  function Get-Symm-ArrayInfo {
    begin { }

    process {
      $remote = $_.remote;
      $symm = $_.Model
      $symmSid = $_.sid;

      $Script:csvFile = $Global:FAPath + "\" +  $symmSid + "_ListLogins-" + $Script:org + ".csv";
      if (Test-Path $Script:csvFile) {
        Remove-Item -Path $Script:csvFile | Out-Null
      }
      
      if ([string]::IsNullOrEmpty($remote)) {
        Write-Host "Skipping $symm : $symmSid no symcli server available" -ForegroundColor Red;
      } else {
        Get-SymcliConnect $remote
        Write-Host "Processing $symm : $symmSid on $remote" -ForegroundColor Green;

        [xml]$faXml = symcfg -sid $symmSid list -FA all -out xml_element
        if ($LastExitCode -eq 0) { # last command executed without error
          $faXml.SelectNodes("//Director") | Get-DirectorInfo $symmSid
        } else {
          Write-Host "Error Getting FA Info for $symmSid" -ForegroundColor Red
        }

      }
    }

    end { }
  }

  function Show-Logins {

    $FileIn = $Global:FAPath + "\" + "Current_ListLogins-" + $Script:org + ".csv";
    if (Test-Path $FileIn) {
      Import-CSV -Path $FileIn -Header $LogInHeader | Out-GridView -Title $FileIn
    } else {
      Write-Host "$FileIn NOT FOUND" -ForegroundColor Red
    }

  }

  function Merge-Logins {

    $mergeFile = $Global:FAPath + "\" + "Current_ListLogins-" + $Script:org + ".csv";
    if (Test-Path $mergeFile) {
        Remove-Item -Path $mergeFile | Out-Null
    }

    $matchMe = "[0-9][0-9][0-9][0-9]_ListLogins-" + $Script:org + ".csv"
    Get-ChildItem -Path $Global:FAPath |
        Where-Object {$_.PSChildName -Like $matchME} |
      ForEach-Object {
        Get-Content $_.FullName -ReadCount 0 | Out-File $mergeFile -Append -Encoding Ascii
      } 
  }


  function Set-SymmDefaults {
    
    if (!(Test-Path $Global:FAPath)) {
      New-Item $Global:FADir -ItemType Directory | Out-Null;
    }

    # Load XMLDB & Validate Org Param
      $Script:xmlDB = Get-ArrayInfoXML;

      if ([string]::IsNullOrEmpty($org)){
        $unique = $Script:xmlDB.SelectNodes("//Array") |
          Select-Object -Property Org, Name -Unique;
        $Script:org = $unique[0].Org
      } else {
        $unique = $Script:xmlDB.SelectNodes("//Array") |
          Where-Object {$_.Org -match $Org } |
          Select-Object -Property Org, Name -Unique;
        if ($unique -is [Object]) {
          $Script:org = $unique[0].Org  
        } else {
          Write-Host "$Org not not found in the XMLDB" -ForegroundColor Red
          $unique = $Script:xmlDB.SelectNodes("//Array") |
            Select-Object -Property Org -Unique;
          Write-Host "Valid Org name values are:" -ForegroundColor Green
          $unique | Format-Table -AutoSize
          return $null
        }
      }
    #

    $Script:SwOrgPath = $Global:SWPath + "-" + $Script:org;
    $noErrors = "0"
    
    return $noErrors

  }
#

# Exported Functions

  # Set-VeSymmLogin

    <#
      .SYNOPSIS
        Creates Symmetrix alias for each initiator logged into a FA port.
 
        Or records the login entries to a csv file and reports on the csv file
        entries in a Out-GridView.
 
      .DESCRIPTION
        Each login entry for a FA port is examined for existance of an alias.
         
        If the initiator is logged into the FA port and the an alias name does not
        exist, a new alias name is established. The alias naming convention is:
 
        <initiator-name> / <switch name>_<xxyy>
 
        The <initiator-name> is determined by looking up the device-alias name found in flogi
        database. Only the first part of the device-alias name is used to construct the
        <initiator-name>. The <xxyy> value is the blade and port value of the switch that the
        initiator is connected to.
         
        If the initiator entry is not logged into the FA port, the initiator entry is removed from
        the FA port.
         
      .PARAMETER sid
        The four digit Symmetric ID value
 
      .PARAMETER SetLogin
        Adds the storage port login entries from the specified sid to a csv file found in the folder:
 
        <execution path>\FAInfo\<sid>_ListLogins-<Org>.csv
 
      .PARAMETER ListLogin
        Displays the contents of the most recent merged login csv file named:
 
        <execution path>\FAInfo\Current_ListLogins-<Org>.csv
 
      .PARAMETER SumLogin
        Displays a count of the initiators logged in and not logged in per storage port.
 
      .PARAMETER MergeLogin
        Merges the individual array login csv files into the csv file, Current_ListLogins-<Org>.csv
 
      .PARAMETER Org
        Each XMLDB record has an Org value. By default the first record in the XMLDB
        determines the default Org value to be used.
 
        To process logins for the non-default Org, the parameter must be specified a valid
        Org value.
       
      .PARAMETER Api
        Determines which communication access method used to access the array.
 
        symcli - Solution Enabler (command line interface)
        restapi - RESTAPI protocol (The restapi is not implemented at this time)
 
      .INPUTS
        The folders named, SWInfo/<fabric name> each contain an extracted copy of the flogi
        database from each switch in a CSV file format. This folder structure is maintained
        by Set-DeviceAlias.ps1.
 
        The WWN value found in the FA login entry is used to search for the flogi entry located
        in the SWInfo/<fabric name> directory structure.
 
      .OUTPUTS
        Out-Gridview of login entries
         
      .EXAMPLE
        Set-VeSymmLogin -sid 0153
 
        Remove initiators not logged into the FA ports and for initiators missing
        a symm alias a value, an alias value is added in the following form:
 
          <WWN name>\<switch_xxpp>
 
          Where <WWN name> is extracted from the device-alias.
          Where <switch> is the switch name where WWN is logged in.
          Where <xx> is the switch blade.
          Where <pp> is the switch port.
 
      .EXAMPLE
        Set-VeSymmLogin -sid 1136 -SetLogin
 
        Adds storage port login entries, to a csv file named,
        <execution path>\FAInfo\1136_ListLogins-<Org>.csv for VMAX array 1136.
 
        Set-SymmLogin -sid 0000 -SetLogin
 
        For each symmetric array in the XMLDB, the storage port login entries will
        be appended to the csv file named:
 
        <execution path>\FAInfo\<sid>_ListLogins-<Org>.csv
 
      .EXAMPLE
        Set-VeSymmLogin -sid 0134 -ListLogin
 
        Displays the storage port login entries in the most recent merged csv file.
 
        Set-VeSymmLogin -sid 0000
 
        Displays the storage port login entries for all arrays.
 
      .EXAMPLE
        Set-VeSymmLogin -sid 0000 -MergeLogin
 
        The set of individual array login csv files are merged into the csv file,
        <execution path>\FAInfo\Current_ListLogins-<Org>.csv using the default Org.
 
        Set-VeSymmLogin -MergoLogin -Org Mojo
 
        The set of individual array login csv files for the 'Mojo' organization are
        merged into the csv file, <execution path>\FAInfo\Current_ListLogins-Mojo.csv
 
      .EXAMPLE
        Set-VeSymmAlias -sid 0955 -SumLogin
 
        Displays a count of the initiators login status per storage port.
 
      .NOTES
 
        Author: Craig Dayton
        0.0.2.4 07/27/2017 : Array Logins track in individual files & merged to master
        0.0.2.3 07/20/2017 : cadayton : Converted to cmdlet, Set-VeSymmLogin
        Updated: 02/11/2017 - initial release.
         
      .LINK
        https://github.com/cadayton/Venom
 
      .LINK
        http://venom.readthedocs.io
 
    #>


    function Set-VeSymmLogin {
      # Set-VeSymmLogin Params
        [cmdletbinding()]
          Param(
            [Parameter(Position=0,
              Mandatory=$false,
              HelpMessage = "Enter four digit Symm ID",
              ValueFromPipeline=$True)]
              [ValidatePattern("^[0-9]{4}$")]
            [string]$sid,
            [Parameter(Position=1,
              Mandatory=$false,
              HelpMessage = "Either symcli or restapi",
              ValueFromPipeline=$True)]
              [ValidateSet("symcli","restapi")]
            [string]$api = "symcli",
            [Parameter(Position=2,
              Mandatory=$false,
              HelpMessage = "Create FA Login CSV file",
              ValueFromPipeline=$True)]
              [ValidateNotNullorEmpty()]
            [switch]$SetLogin,
            [Parameter(Position=3,
              Mandatory=$false,
              HelpMessage = "View FA Login CSV file",
              ValueFromPipeline=$True)]
              [ValidateNotNullorEmpty()]
            [switch]$ListLogin,
            [Parameter(Position=4,
              Mandatory=$false,
              HelpMessage = "View FA Login CSV file",
              ValueFromPipeline=$True)]
              [ValidateNotNullorEmpty()]
            [switch]$SumLogin,
            [Parameter(Position=5,
              Mandatory=$false,
              HelpMessage = "View FA Login CSV file",
              ValueFromPipeline=$True)]
              [ValidateNotNullorEmpty()]
            [switch]$MergeLogin,
            [Parameter(Position=6,
              Mandatory=$false,
              HelpMessage = "Organization Name",
              ValueFromPipeline=$True)]
            [string]$Org = $null         
        )
      #

      Write-Host "Set-VeSymmLogin version 0.0.2.4" -ForegroundColor Green

      $rdom       = Get-Random -Maximum 999 -Minimum 101;

      $rstFile    = "$PWD\rslt-" + "$rdom" + ".txt";
      $errFile    = "$PWD\errRslt-" + "$rdom" + ".txt";
      $hostnm     = "Mojo69";

      $rslt = Set-SymmDefaults;

      if ([string]::IsNullOrEmpty($rslt)) {
        return 
      }

      switch ($api) {
        "symcli" {
          Set-symapi_db;
          if ($sid) {
            if ($ListLogin) {
              Show-Logins;
            } elseif ($MergeLogin) {
               Merge-Logins;
            } elseif ($sid -ne "0000") {
              $Script:xmlDB.SelectNodes("//Array") | Where-Object {$_.sid -match $sid -and $_.Org -match $Script:org} | Get-Symm-ArrayInfo
            } else {
              $Script:xmlDB.SelectNodes("//Array") | Where-Object {$_.Model -match "VMAX" -and $_.Org -match $Script:org} | Get-Symm-ArrayInfo
            }
          }
          if (Test-Path $rstFile) { Remove-Item -Path $rstFile; }
        }
        "restapi" {
          Write-Host "Sorry RESTAPI not implemented yet!" -ForegroundColor Green
        }
        default {
          Write-Host "$api is not a valid choice" -ForegroundColor Red
        }
      }
    }

  #

  # Find-VeSymmLogin

    <#
      .SYNOPSIS
        List initiators logged into storage arrays based on specified search criteria.
 
      .DESCRIPTION
        Find Initiator login on storage ports.
 
        The csv file named, Current_ListLogins-<org>.csv is searched based on the specified search criteria.
        If no search criteria is specified, then all login entries are displayed in an Out-GridView.
 
        The search criteria is anyone or combination of sid, WWN, Name, switch, FCID, or Login value.
 
        Reports on the differences between the current login entries and prior login entries.
 
      .PARAMETER sid
        Limits the search to a specific symmetrix array four digit serial number or a
        specific array name.
 
      .PARAMETER dirport
        Limits the search to a specific director port.
 
      .PARAMETER WWN
        Limits the search to a specific WWN value.
 
      .PARAMETER Name
        Limits the search to a specific initiator name. A initiator name can be either
        a host name or an array name.
 
      .PARAMETER switch
        Limits the search to a specific switch name.
 
      .PARAMETER fcid
        Limits the search to a FCID value.
 
      .PARAMETER Login
        Limits the search to a Login value of Yes or No.
 
      .PARAMETER compare
        Compare the current login entries to the prior login entries
        and list the differences.
 
      .PARAMETER Obj
        The selected entries are returned as an object rather than console output.
 
      .INPUTS
        The csv file, FAInfo/Current_ListLogins-<org>.csv.
 
      .OUTPUTS
        Console Output, Out-Gridview or object returned
         
      .EXAMPLE
        Find-VeSymmLogin -sid 0153 -dirport 2e:0
 
        Lists all the login entries for VMAX array 0153 on director 2e port 0.
 
      .EXAMPLE
        Find-VeSymmLogin -name mojo69
 
        Lists all login entries for host, 'mojo69' on all storage arrays.
 
      .EXAMPLE
        Find-VeSymmLogin -wwn 1000c4346b200498
 
        or
 
        Find-VeSymmLogin -wwn 10:00:c4:34:6b:20:04:98
 
        Lists all login entries associated with the WWN value.
 
      .EXAMPLE
        Find-VeSymmLogin -login No
 
        List all login entries NOT logged into a storage array port.
 
      .EXAMPLE
        Find-VeSymmLogin -switch mdsmojo01_04
 
        List all login entries from switch pxbh102 on blade 04.
 
      .EXAMPLE
        Find-VeSymmLogin -compare
 
        List the differences between the current Login DB and the prior Login DB.
 
      .NOTES
 
        Author: Craig Dayton
        0.0.2.3 07/20/2017 : cadayton : Converted to cmdlet, Find-VeSymmLogin
        Updated: 02/11/2017 - initial release.
         
      .LINK
        https://github.com/cadayton/Venom
 
      .LINK
        http://venom.readthedocs.io
 
    #>


    function Find-VeSymmLogin {
      
      # Find-VeSymmAlias Params
        [cmdletbinding()]
          Param(
            [Parameter(Position=0,
              Mandatory=$false,
              HelpMessage = "Enter four digit Symm ID",
              ValueFromPipeline=$True)]
              [ValidatePattern("^[0-9]{4}$")]
            [string]$sid,
            [Parameter(Position=1,
              Mandatory=$false,
              HelpMessage = "Enter a director and port number (i.e. 2e:0)",
              ValueFromPipeline=$True)]
              [ValidateNotNullorEmpty()]
            [string]$dirport,
            [Parameter(Position=2,
              Mandatory=$false,
              HelpMessage = "Enter a WWN",
              ValueFromPipeline=$True)]
              [ValidateNotNullorEmpty()]
            [string]$wwn,
            [Parameter(Position=3,
              Mandatory=$false,
              HelpMessage = "Enter a host or array name",
              ValueFromPipeline=$True)]
              [ValidateNotNullorEmpty()]
            [string]$name,
            [Parameter(Position=4,
              Mandatory=$false,
              HelpMessage = "Enter a switch name value",
              ValueFromPipeline=$True)]
              [ValidateNotNullorEmpty()]
            [string]$switch,
            [Parameter(Position=5,
              Mandatory=$false,
              HelpMessage = "Enter a FCID value",
              ValueFromPipeline=$True)]
              [ValidateNotNullorEmpty()]
            [string]$fcid,
            [Parameter(Position=6,
              Mandatory=$false,
              HelpMessage = "Enter a FCID value",
              ValueFromPipeline=$True)]
              [ValidateNotNullorEmpty()]
            [string]$loginHost,
            [Parameter(Position=7,
              Mandatory=$false,
              HelpMessage = "Enter a FCID value",
              ValueFromPipeline=$True)]
              [ValidateNotNullorEmpty()]
            [string]$loginAll,
            [Parameter(Position=8,
              Mandatory=$false,
              HelpMessage = "Enter a FCID value",
              ValueFromPipeline=$True)]
              [ValidateNotNullorEmpty()]
            [switch]$compare,
            [Parameter(Position=9,
              Mandatory=$false,
              HelpMessage = "Output returned as object",
              ValueFromPipeline=$True)]
              [ValidateNotNullorEmpty()]
            [switch]$obj,
            [Parameter(Position=10,
              Mandatory=$false,
              HelpMessage = "Organization Name",
              ValueFromPipeline=$True)]
            [string]$Org = $null
        )
      #

      # Main Routine

        Write-Host "Find-VeSymmLogin version 0.0.2.3" -ForegroundColor Green

        $rslt = Set-SymmDefaults;

        if ([string]::IsNullOrEmpty($rslt)) {
          return 
        }

        $matchMe = "*_ListLogins-" + $Script:org  + ".csv";
        $loginFile = Get-ChildItem -Path $Global:FAPath |
          Where-Object {$_.PSChildName -Like $matchME} |
          Sort-object -property @{Expression={$_.LastWriteTime}; Ascending=$false};
        if ($loginFile.count -gt 1) {
          $FileIn = $loginFile.FullName[0];
          $FileIn1 = $loginFile.FullName[1];
        } else {
          $FileIn = $loginFile.FullName;
        }

        $logins = Import-CSV -Path $FileIn -Header $LogInHeader; # All logins

        if ($compare) { # Difference between Login entries DBs

          if ($loginFile.count -gt 1) {
            Write-Host "";
            Write-Host "Listing the difference between:" -ForegroundColor Green;
            Write-Host " $FileIn and " -ForegroundColor Green
            Write-Host " $FileIn1" -ForegroundColor Green;
            Write-Host "";

            $logins1 = Import-CSV -Path $FileIn1 -Header $LogInHeader; # All Prior logins
            $diff = Compare-Object $logins $logins1;

            if ($diff -ne $null) {
              if ($diff.InputObject.gettype().name -eq "String") { # single record difference
                  if ($diff.SideIndicator -eq "=>") { # missing login entry
                    $dateTS = Get-Date -Format "yyMMdd-HHmm"
                    $loginentry = $diff.InputObject;
                    Write-Host " Missing: $logientry" -ForegroundColor Red
                  } else {
                    $logientry = $diff.InputObject;
                    Write-Host " New: $flogientry" -ForegroundColor Blue
                  }
              } else {
                  [int]$sz = $diff.InputObject.Length;
                  for ($i=0; $i -lt $sz; $i++) {
                    if ($diff.SideIndicator[$i] -eq "=>") { # missing flogi entry
                      $logientry = $diff.InputObject[$i];
                      Write-Host " Missing: $logientry" -ForegroundColor Red;
                    } else {
                      $logientry = $diff.InputObject[$i];
                      Write-Host " New: $logientry" -ForegroundColor Blue
                    }
                  }
              }
            } else {
              Write-Host "No differences Found" -ForegroundColor Green;
            }
          } else {
            Write-Host "There is only one file in $Script:FAPath so nothing to compare" -ForegroundColor Red
            return $null
          }

        } else { # Process filter criteria

          [bool]$filtered = $false;

          # search filters
            if ($sid) { 
              $logins = $logins | Where-Object { $_.Array -eq $sid }
              $filtered = $true;
            }

            if ($dirport) { 
              $logins = $logins | Where-Object { $_.DirPort -eq $dirport }
              $filtered = $true;
            }

            if ($wwn) {
              if ($wwn -match ":") {
                $wwn = $wwn -replace ":", "";
              }
              $logins = $logins | Where-Object { $_.WWN -eq $wwn }
              $filtered = $true;
            }

            if ($name) {
              $logins = $logins | Where-Object { $_.NodeName -match $name }
              $filtered = $true;
            }

            if ($switch) {
              $logins = $logins | Where-Object { $_.PortName -match $switch }
              $filtered = $true;
            }

            if ($fcid) {
              $logins = $logins | Where-Object { $_.FCID -eq $fcid }
              $filtered = $true;
            }

            if ($loginHost) {
              $logins = $logins | Where-Object { $_.LoggedIN -match $loginHost } |
                Where-Object { $_.NodeName -notmatch "vmx"};
                # Where-Object { $_.NodeName -notmatch "NULL"}
              $filtered = $true;
            } elseif ($loginAll) {
              $logins = $logins | Where-Object { $_.LoggedIN -match $loginAll }
              $filtered = $true;
            }
          
          #

          if ($obj) {
            return $login
          } elseif ($logins -ne $null) {
            Write-Host "";
            $finfo = Get-ChildItem -Path $FileIn;
            $lwrite = $finfo.LastWriteTime;
            $lup = Get-Date -Date $lwrite -Format g;
            Write-Host "Search Results from $FileIn" -ForegroundColor Green;
            write-Host " Last Updated: $lup" -ForegroundColor Magenta;
            if ($filtered) {
              $logins | Format-Table -AutoSize
              Write-Host "Output search results to GridView GUI? (Y or N)" -ForegroundColor Green -NoNewline
              $rslt = Read-Host " ";
              if ($rslt -match "Y") {
                Write-Host " Building Out-GridView Table" -ForegroundColor Green
                $logins | Out-GridView -Title $FileIn
              }
            } else {
              Write-Host " Building Out-GridView Table" -ForegroundColor Green
              $logins | Out-GridView -Title $FileIn
            }
          } else {
            Write-Host " Found no login entries matching the specified criteria" -ForegroundColor Red
          }
        }
      
      #
    }
  
  #

  # Start-VeUnisphere

    <#
      .SYNOPSIS
        Starts the Unisphere Manager interface for the specified <sid>
 
      .PARAMETER sid
        Four digit Symmetrix ID of the Unisphere Manager to launch.
         
      .EXAMPLE
        Start-VeUnisphere -sid 0153
 
        Starts the Unisphere Manager managing VMAX array 0153.
 
      .NOTES
        Author: Craig Dayton
        0.0.2.3 07/20/2017 : cadayton : initial release.
         
      .LINK
        https://github.com/cadayton/Venom
 
      .LINK
        http://venom.readthedocs.io
 
    #>


    function Start-VeUnisphere {
      param ([string][ValidatePattern("^[0-9]{4}$")]$sid )

      Write-Host "Start-VeUnisphere version 0.0.2.3" -ForegroundColor Green

      $xmlDB = Get-ArrayInfoXML;
          
      $unique = $xmlDB.SelectNodes("//Array") |
        Where-Object { $_.Model -match "VMAX" -and $_.sid -eq $sid} |
        Select-Object -Property restapi -Unique;
          
      if ($unique -is [Object]) {
        $userver = $unique[0].restapi;
        $URL = "https://" + $userver + ":8443/univmax/#"
        Write-Host "Starting Unisphere: $URL" -ForegroundColor Green
        Start-Process $URL
      } else {
        Write-Host "$sid " -ForegroundColor Red -NoNewline
        Write-Host "is not a valid " -ForegroundColor Blue -NoNewline
        Write-Host "VMAX " -ForegroundColor Red -NoNewline
        Write-Host "array in the XMLDB" -ForegroundColor Blue
        (new-object Media.SoundPlayer "$PSScriptRoot\Data\LOL.wav").play();
      }

    }

  #

  # Start-VeVappManager

    <#
      .SYNOPSIS
        Starts the VappManager interface for the specified <sid>
 
      .PARAMETER sid
        Four digit Symmetrix ID of the VappManager to launch.
         
      .EXAMPLE
        Start-VeVppManager -sid 0153
 
        Starts the VppManager for VMAX array 0153.
 
      .NOTES
        Author: Craig Dayton
        0.0.2.3 07/20/2017 : cadayton : initial release.
         
      .LINK
        https://github.com/cadayton/Venom
 
      .LINK
        http://venom.readthedocs.io
 
    #>


    function Start-VeVappManager {
      param ([string][ValidatePattern("^[0-9]{4}$")]$sid )

      Write-Host "Start-VeVappManager version 0.0.2.3" -ForegroundColor Green

      $xmlDB = Get-ArrayInfoXML;
          
      $unique = $xmlDB.SelectNodes("//Array") |
        Where-Object { $_.Model -match "VMAX3" -and $_.sid -eq $sid} |
        Select-Object -Property restapi -Unique;
          
      if ($unique -is [Object]) {
        $userver = $unique[0].restapi;
        $URL = "https://" + $userver + ":5480/"
        Write-Host "Starting VappManager: $URL" -ForegroundColor Green
        Start-Process $URL
      } else {
        Write-Host "$sid " -ForegroundColor Red -NoNewline
        Write-Host "is not a valid " -ForegroundColor Blue -NoNewline
        Write-Host "VMAX3 " -ForegroundColor Red -NoNewline
        Write-Host "array in the XMLDB" -ForegroundColor Blue
        (new-object Media.SoundPlayer "$PSScriptRoot\Data\LOL.wav").play();
      }

    }

  #

  # Start-VeEcomConfig

    <#
      .SYNOPSIS
        Starts the EcomConfig interface for the specified <sid>
 
      .PARAMETER sid
        Four digit Symmetrix ID of the EcomConfig to launch.
         
      .EXAMPLE
        Start-VeEcomConfig -sid 0153
 
        Starts the VeEcomConfig for VMAX array 0153.
 
      .NOTES
        Author: Craig Dayton
        0.0.2.3 07/20/2017 : cadayton : initial release.
         
      .LINK
        https://github.com/cadayton/Venom
 
      .LINK
        http://venom.readthedocs.io
 
    #>


    function Start-VeEcomConfig {
      param ([string][ValidatePattern("^[0-9]{4}$")]$sid )

      Write-Host "Start-VeEcomConfig version 0.0.2.3" -ForegroundColor Green

      $xmlDB = Get-ArrayInfoXML;
          
      $unique = $xmlDB.SelectNodes("//Array") |
        Where-Object { $_.Model -match "VMAX3" -and $_.sid -eq $sid} |
        Select-Object -Property restapi -Unique;
          
      if ($unique -is [Object]) {
        $userver = $unique[0].restapi;
        $URL = "https://" + $userver + ":5889/ECOMConfig"
        Write-Host "Starting EcomConfig: $URL" -ForegroundColor Green
        Start-Process $URL
      } else {
        Write-Host "$sid " -ForegroundColor Red -NoNewline
        Write-Host "is not a valid " -ForegroundColor Blue -NoNewline
        Write-Host "VMAX3 " -ForegroundColor Red -NoNewline
        Write-Host "array in the XMLDB" -ForegroundColor Blue
        (new-object Media.SoundPlayer "$PSScriptRoot\Data\LOL.wav").play();
      }

    }
  
  #

#