CommonUtil.ps1

<#
Common Utility functions.
#>


. $PSScriptRoot/PureStorage.CBS.VVolUtil.ps1

function Get-PCBSHostGroup  {
    <#
    .SYNOPSIS
    Get all host groups from a Flash Array

    .DESCRIPTION
    Get list of host groups from Flash Array included IP Ranges

    .PARAMETER FlasharrayAddress
    Flash Array IP address of FQDN

    .PARAMETER FlasharrayUsername
    Flash Array username

    .PARAMETER FlasharrayPassword
    Flash Array password
    #>

    Param(
        [Parameter(mandatory=$true)]
        [String]$FlasharrayAddress,

        [Parameter(mandatory=$true)]
        [String]$FlasharrayUsername,

        [Parameter(mandatory=$true)]
        [SecureString]$FlasharrayPassword
    )
    $hgroup_result =  Invoke-Pfa2CLICommand -Endpoint $FlasharrayAddress -Username $FlasharrayUsername -Password $FlasharrayPassword -CommandText "purehgroup list --csv"
    $result = ConvertFrom-Csv -InputObject $hgroup_result
    # TODO: Verify that "IP Range" is in result
    foreach ($r in $result) {
        $members = $r | Get-Member | Where {$_.Name -eq "IP Range"}
        if ($members.Count -le 0) {
            throw "Could not find 'IP Range'. PLease make sure to use a supported FlashArray"
        }
    }
    return $result
}

function Get-PCBSHostGroupByIPRange {
    <#
    .SYNOPSIS
    Get host groups from a Flash Array with a specific IP Range

    .DESCRIPTION
    Get host groups from a Flash Array with a specific IP Range (inclusive of start and end).
    Will return an empty list if no host groups are found with the specified IP Range

     .PARAMETER FlasharrayAddress
    Flash Array IP address of FQDN

    .PARAMETER FlasharrayUsername
    Flash Array username

    .PARAMETER FlasharrayPassword
    Flash Array password

    .PARAMETER IPRangeStart
    IP Range start

    .PARAMETER IPRangeEnd
    IP Range end

    #>

    Param(
        [Parameter(mandatory=$true)]
        [String]$FlasharrayAddress,

        [Parameter(mandatory=$true)]
        [String]$FlasharrayUsername,

        [Parameter(mandatory=$true)]
        [SecureString]$FlasharrayPassword,

        [Parameter(mandatory=$true)]
        [String] $IPRangeStart,

        [Parameter(mandatory=$true)]
        [String] $IPRangeEnd
    )
    $result = @()
    $hgroups = Get-PCBSHostGroup -FlasharrayAddress $FlasharrayAddress -FlasharrayUsername $FlasharrayUsername -FlasharrayPassword $FlasharrayPassword
    foreach ($hgroup in $hgroups) {
        # If more than one IP Range is listed, they will be separated by "/"
        $ip_ranges = $hgroup."IP Range" -Split "/" | ConvertFrom-Json
        foreach ($ip_range in $ip_ranges) {
            if ($ip_range."start" -eq $IPRangeStart -and $ip_range."start" -eq $IPRangeStart) {
                $result += $hgroup
            }
        }
    }
    return $result
}

function New-PCBSHostGroup {
    <#
    .SYNOPSIS
    Create a new host group on Flash Array

    .DESCRIPTION
    Create a new host group on with a specified Ip Range Flash Array

     .PARAMETER FlasharrayAddress
    Flash Array IP address of FQDN

    .PARAMETER FlasharrayUsername
    Flash Array username

    .PARAMETER FlasharrayPassword
    Flash Array password

    .PARAMETER HostGroupName
    Host group name

    .PARAMETER HostGroupNameSpace
    Host group namespace

    .PARAMETER IPRangeStart
    IP Range start

    .PARAMETER IPRangeEnd
    IP Range end

    .EXAMPLE
    An example

    .NOTES
    General notes
    #>

    Param(
        [Parameter(mandatory=$true)]
        [String]$FlasharrayAddress,

        [Parameter(mandatory=$true)]
        [String]$FlasharrayUsername,

        [Parameter(mandatory=$true)]
        [SecureString]$FlasharrayPassword,

        [Parameter(mandatory=$true)]
        [String]$HostGroupName,

        [Parameter(mandatory=$false)]
        [String]$HostGroupNameSpace,

        [Parameter(mandatory=$true)]
        [String] $IPRangeStart,

        [Parameter(mandatory=$true)]
        [String] $IPRangeEnd
    )

    $command = "purehgroup create --iprangelist $IPRangeStart-$IPRangeEnd --personality esxi $HostGroupName"
    $result = Invoke-Pfa2CLICommand -Endpoint $FlasharrayAddress -Username $FlasharrayUsername -Password $FlasharrayPassword -CommandText $command
    return $result
}


function Get-PurityIdentifiefFromVMWareIdentifier {
    Param (
        [String] $name
    )
    $updated_name = $name
    if (-not $name -match "^[a-zA-Z0-9\-]+$")
    {
        $updated_name = $updated_name -replace "[^\w\-]", ""
        $updated_name = $updated_name -replace "[_]", ""
        $updated_name = $updated_name -replace " ", ""
    }

    return $updated_name
}

function Convert-EpochToDateTime {
    Param (
        [int64] $EpochMillseconds
    )
    $start_date = New-Object -Type DateTime -ArgumentList 1970, 1, 1, 0, 0, 0, 0
    return $start_date.AddMilliseconds($EpochMillseconds)
}

function Update-ESXCertificates
{
    <#
    .SYNOPSIS
    Refresh certificates for all ESXi hosts

    .DESCRIPTION
    Refresh certificates for all ESXi hosts

    #>


    $service_instance = Get-View ServiceInstance
    $certMgr = Get-View -Id $service_instance.Content.CertificateManager

    Get-VMHost | ForEach-Object -Process {
        Write-Host "Refreshing certificates for $($_.Name).."
        $certMgr.CertMgrRefreshCACertificatesAndCRLs($_.Id)
    }
}

function Remove-PCBSStaticiSCSITargets {
    <#
    .SYNOPSIS
    Remove statis iSCSI Targets

    .DESCRIPTION
    Remove statis iSCSI Targets

    .PARAMETER Esxi
   ESXi VMware object

    #>

    Param(
        [Parameter(Position=0,mandatory=$true)]
        [VMware.VimAutomation.ViCore.Types.V1.Inventory.VMHost]$Esxi
    )
    $ESXi | Get-VMHostHba -Type iScsi | Get-IScsiHbaTarget | Where {$_.Type -eq "Static"} | Remove-IScsiHbaTarget -Confirm:$false
}

function New-PCBSDynamicHostGroup {
    Param (
        [String]$ClusterName,
        [String]$iSCSIRangeStart,
        [String]$iSCSIRangeEnd,
        [String]$FlasharrayAddress,
        [String]$FlasharrayUsername,
        [SecureString]$FlasharrayPassword
    )
     # Create dynmaic host group if it does not exist
     $hgroups = Get-PCBSHostGroupByIPRange -FlasharrayAddress $FlasharrayAddress -FlasharrayUsername $FlasharrayUsername -FlasharrayPassword $FlasharrayPassword -IPRangeStart $iSCSIRangeStart -IPRangeEnd $iSCSIRangeEnd
     if ($hgroups.Count -eq 0) {
         # HostGgroup does not exist. We need to create it
         $HostGroupName = Get-PurityIdentifiefFromVMWareIdentifier -Name $ClusterName
         Write-Host "Creating host group $HostGroupName ..."
         $hgroup = New-PCBSHostGroup -FlasharrayAddress $FlasharrayAddress -FlasharrayUsername $FlasharrayUsername -FlasharrayPassword $FlasharrayPassword -IPRangeStart $iSCSIRangeStart -IPRangeEnd $iSCSIRangeEnd -HostGroupName $HostGroupName
         Write-Host "$HostGroupName was created successfully."
     }
     else {
         $HostGroupName = $hgroups[0].Name
         Write-Host "HostGroup ($HostGroupName) for iSCSI Range ($iSCSIRangeStart -> $iSCSIRangeEnd) already exists."
     }

     # Create software iSCSI adaptor for each ESXi host
     $fa = Connect-Pfa2Array -EndPoint $FlasharrayAddress -Username $FlasharrayUsername -Password $FlasharrayPassword -IgnoreCertificateError
     $ESXHosts = $Cluster | Get-VMHost
     foreach ($ESXHost in $ESXHosts) {
         Write-Host "Configuring iSCSI target for $ESXHost ..."
         Set-VmHostPfaiSCSI -esxi $ESXHost -flasharray $fa
         Write-Host "Removing static iSCSI targets for $ESXHost ..."
         Remove-StaticiSCSITargets -esxi $ESXHost
         Write-Host "Rescanning HBA for $ESXHost ..."
         Get-VMHostStorage -VMHost $ESXHosts -RescanAllHba | Out-Null
     }
}

function New-PfaVmfsFromSnapshot {
    <#
    .SYNOPSIS
      Mounts a copy of a VMFS datastore to a VMware cluster from a FlashArray snapshot.
    .DESCRIPTION
      Takes in a snapshot name, the corresponding FlashArray, and a cluster. The VMFS copy will be resignatured and mounted.
    .INPUTS
      FlashArray connection, a snapshotName, and a cluster.
    .OUTPUTS
      Returns the new datastore.
    #>


    [CmdletBinding()]
    Param(
            [Parameter(Mandatory=$true,ValueFromPipeline=$True)]
            [VMware.VimAutomation.ViCore.Types.V1.Inventory.Cluster]$cluster,

            [Parameter(ValueFromPipeline=$True)]
            $FlashArray,

            [Parameter(Mandatory=$true)]
            [string]$snapName,

            [Parameter(Mandatory=$false)]
            [string]$volumeName
    )
    $volumeSnapshot = Get-Pfa2VolumeSnapshot -Name $snapName -ErrorAction Ignore
    if (-not $volumeSnapshot) {
        throw "Could not find snapshot $snapName."
    }

    if ([string]::IsNullOrWhiteSpace($volumeName))
    {
      $volumeName = $volumeSnapshot.Source.Name + "-"+(Get-Random -Minimum 10000 -Maximum 99999)
    }
    Write-Host "Creating a volume $volumeName..."
    $newVol = New-Pfa2Volume -Array $FlashArray -Name $volumeName -SourceName $snapName
    $hostGroup = Get-PCBSHostGroupfromVcCluster -cluster $cluster -FlashArray $FlashArray
    Write-Host "Connecting volume $volumeName to host group $($hostGroup.Name)..."
    New-Pfa2Connection -VolumeName $volumeName -HostGroupName $hostGroup.name

    $esxi = $cluster | Get-VMHost| where-object {($_.ConnectionState -eq 'Connected')} |Select-Object -last 1
    Write-Host "Rescanning HBA..."
    $esxi | Get-VMHostStorage -RescanAllHba -RescanVMFS -ErrorAction stop |Out-Null
    $hostStorage = get-view -ID $esxi.ExtensionData.ConfigManager.StorageSystem
    $resigVolumes= $hostStorage.QueryUnresolvedVmfsVolume()
    $newNAA =  "naa.624a9370" + $newVol.serial.toLower()
    $deleteVol = $false
    foreach ($resigVolume in $resigVolumes)
    {
        if ($deleteVol -eq $true)
        {
            break
        }
        foreach ($resigExtent in $resigVolume.Extent)
        {
            if ($resigExtent.Device.DiskName -eq $newNAA)
            {
                if ($resigVolume.ResolveStatus.Resolvable -eq $false)
                {
                    if ($resigVolume.ResolveStatus.MultipleCopies -eq $true)
                    {
                        Write-Error "The volume cannot be resignatured as more than one unresignatured copy is present. Deleting and ending."
                        Write-Error "The following volume(s) are presented and need to be removed/resignatured first:"
                        $resigVolume.Extent.Device.DiskName |where-object {$_ -ne $newNAA}
                    }
                    $deleteVol = $true
                    break
                }
                else {
                    $volToResignature = $resigVolume
                    break
                }
            }
        }
    }
    if (($null -eq $volToResignature) -and ($deleteVol -eq $false))
    {
        Write-Error "No unresolved volume found on the created volume. Deleting and ending."
        $deleteVol = $true
    }
    if ($deleteVol -eq $true)
    {
        Write-Host "Removing volume $($newVol.Name)..."
        Remove-Pfa2Connection -Array $FlashArray -HostgroupNames $hostGroup.name -VolumeNames $newVol.name -ErrorAction SilentlyContinue
        Remove-Pfa2Volume  -Array $FlashArray -Name $newVol.name -ErrorAction SilentlyContinue
        Remove-Pfa2Volume  -Array $FlashArray -Name $newVol.name -Eradicate -ErrorAction SilentlyContinue
        throw "Failed to create datastore $volumeName from snapshot $snapName"
    }
    Write-Host "Starting resignature for $volumeName..."
    $esxcli=get-esxcli -VMHost $esxi -v2 -ErrorAction stop
    $resigOp = $esxcli.storage.vmfs.snapshot.resignature.createargs()
    $resigOp.volumelabel = $volToResignature.VmfsLabel
    $esxcli.storage.vmfs.snapshot.resignature.invoke($resigOp) |out-null
    Start-sleep -s 5
    $esxi |  Get-VMHostStorage -RescanVMFS -ErrorAction stop |Out-Null
    $datastores = $esxi| Get-Datastore -ErrorAction stop
    foreach ($ds in $datastores)
    {
        $naa = $ds.ExtensionData.Info.Vmfs.Extent.DiskName
        if ($naa -eq $newNAA)
        {
            $resigds = $ds | Set-Datastore -Name $newVol.name -ErrorAction stop
            return $resigds
        }
    }
  }