sFunctions.psm1

using module .\sClass.psm1

Function MakeTobj
{
    <# Helper Function used by Protect-SRMVM and UnProtect-SRMVM #>

    param($tinfo , $VMname, $VMmoref )

    $lo = [pscustomobject]@{
        VM = $VMname
        VMMoRef = $VMmoref
        Status = $tinfo.State
        Error = $tinfo.Error.LocalizedMessage
        Task = $tinfo.Name
        TaskMoRef = $tinfo.TaskMoRef
    }
    $lo.PSObject.TypeNames.Insert(0,'SupSkiFun.SRM.Protect.Info')
    $lo
}

<#
.SYNOPSIS
Retrieves SRM Protection Groups
.DESCRIPTION
Retrieves SRM Protection Groups. Can be run on recovery or protected site.
.OUTPUTS
VMware.VimAutomation.Srm.Views.SrmProtectionGroup
.EXAMPLE
Place all the SRM Protection Groups into a variable:
$allPG = Get-SRMProtectionGroup
.EXAMPLE
Place SRM Protection Groups matching a criteria into a variable:
$myPG = Get-SRMProtectionGroup | Where-Object -Property Name -Match "DS1"
#>

function Get-SRMProtectionGroup
{
    Begin
    {
        $srmED =  $DefaultSrmServers.ExtensionData
        if(!$srmED)
        {
            Write-Output "Terminating. Session is not connected to a SRM server."
            break
        }
        $pgrps = $srmED.Protection.ListProtectionGroups()
    }

    Process
    {
        foreach ($pgrp in $pgrps)
        {
            $pnom = $pgrp.GetInfo().Name
            Add-Member -InputObject $pgrp -MemberType NoteProperty -Name "Name" -Value $pnom
        }
    }

    End
    {
        $pgrps
    }
}

<#
.SYNOPSIS
Returns the State of Protection Groups
.DESCRIPTION
Returns the State of Protection Groups as an object of Name, State, ConfigOK, and ConfigNeeded.
Can be run on recovery or protected site.
.PARAMETER ProtectionGroup
[VMware.VimAutomation.Srm.Views.SrmProtectionGroup] Protection Group Object. See Examples.
.INPUTS
[VMware.VimAutomation.Srm.Views.SrmProtectionGroup]
.OUTPUTS
[pscustomobject] SupSkiFun.SRM.Protection.Group.State
.EXAMPLE
Return Protection Group State from specific Protection Group(s) into the myInfo variable:
$myPG = Get-SRMProtectionGroup | Where-Object -Property Name -Match "DS1"
$myInfo = $myPG | Get-SRMProtectionGroupState
.EXAMPLE
Return Protection Group State from all Protection Groups into the myInfo variable:
$myPG = Get-SRMProtectionGroup
$myInfo = $myPG | Get-SRMProtectionGroupState
#>

function Get-SRMProtectionGroupState
{
    [CmdletBinding()]
    param
    (
        [Parameter(Mandatory = $true , ValueFromPipeline = $true)]
        [VMware.VimAutomation.Srm.Views.SrmProtectionGroup[]] $ProtectionGroup
    )

    Process
    {
        foreach ($pgrp in $ProtectionGroup)
        {
            $pgst = $null
            $pgst = $pgrp.ListProtectedVms().where({$_.NeedsConfiguration -eq "True"}).VmName
            $lo = [pscustomobject]@{
                Name = $pgrp.GetInfo().Name.ToString()
                State = $pgrp.GetProtectionState().ToString()
                ConfigOK = $pgrp.CheckConfigured()
                ConfigNeeded = $pgst
            }
            $lo.PSObject.TypeNames.Insert(0,'SupSkiFun.SRM.Protection.Group.State')
            $lo
        }
    }
}

<#
.SYNOPSIS
Retrieves SRM Recovery Plans
.DESCRIPTION
Retrieves SRM Recovery Plans. Can be run on recovery or protected site.
.OUTPUTS
VMware.VimAutomation.Srm.Views.SrmRecoveryPlan
.EXAMPLE
Place all the SRM Recovery Plans into a variable:
$allRP = Get-SRMRecoveryPlan
.EXAMPLE
Place SRM Recovery Plans matching a criteria into a variable:
$myRP = Get-SRMRecoveryPlan | Where-Object -Property Name -Match "CL07"
#>


Function Get-SRMRecoveryPlan
{
    [cmdletbinding()]
    param()

    Begin
    {
        $srmED =  $DefaultSrmServers.ExtensionData
        if(!$srmED)
        {
            Write-Output "Terminating. Session is not connected to a SRM server."
            break
        }
        $plans = $srmED.Recovery.ListPlans()
    }

    Process
    {
        foreach ($plan in $plans)
        {
            $pnom = $plan.GetInfo().Name
            Add-Member -InputObject $plan -MemberType NoteProperty -Name "Name" -Value $pnom
        }
    }

    End
    {
        $plans
    }
}

<#
.SYNOPSIS
Returns current state of SRM Test.
.DESCRIPTION
Returns current state of SRM Test and running tasks. Can be run on the recovery or protected site.
.PARAMETER RecoveryPlan
SRM Recovery Plan. VMware.VimAutomation.Srm.Views.SrmRecoveryPlan
.INPUTS
VMware.VimAutomation.Srm.Views.SrmRecoveryPlan
.OUTPUTS
[pscustomobject] SupSkiFun.SRM.Test.Status
.EXAMPLE
$p = Get-SRMRecoveryPlan | Where-Object -Property Name -eq "PlanXYZ"
$p | Get-SRMTestState
#>

Function Get-SRMTestState
{
    [cmdletbinding()]
    [Alias("Get-SRMRecoveryPlanState")]
    Param
    (
        [Parameter (Mandatory = $true , ValueFromPipeline = $true )]
        [VMware.VimAutomation.Srm.Views.SrmRecoveryPlan[]] $RecoveryPlan
    )

    Process
    {
        foreach ($rp in $RecoveryPlan)
        {

            $lo = [pscustomobject]@{
                Name = $rp.Name
                State = $rp.GetInfo().State.ToString()
                RunningTask = $rp.RecoveryPlanHasRunningTask().ToString()
            }
            $lo.PSObject.TypeNames.Insert(0,'SupSkiFun.SRM.Test.Status')
            $lo
        }
    }
}

<#
.SYNOPSIS
Obtains SRM VM Protection Information
.DESCRIPTION
Returns an object of VM, VMMoRef, Status, DataStore, ProtectionGroup, RecoveryPlan, ProtectedVM and PeerProtectedVm.
Run on protected site to obtain full information. Can be run on recovery site, but information is limited.
.NOTES
Works optimally for one to many VMs. To retrieve all VMs in a protection group, Show-SRMProtectedVM will work better.
.PARAMETER VM
Output from VMWare PowerCLI Get-VM. See Examples.
[VMware.VimAutomation.ViCore.Types.V1.Inventory.VirtualMachine]
.INPUTS
VMWare PowerCLI VM from Get-VM:
[VMware.VimAutomation.ViCore.Types.V1.Inventory.VirtualMachine]
.OUTPUTS
[pscustomobject] SupSkiFun.SRM.VM.Info
.EXAMPLE
Returns object for one VM to the screen:
Get-VM -Name Server01 | Get-SRMVM
.EXAMPLE
Places an object of several VMs into a variable:
$myVar = Get-VM -Name Test* | Get-SRMVM
.LINK
Show-SRMProtectedVM
#>

function Get-SRMVM
{
    [CmdletBinding()]
    param
    (
        [Parameter(Mandatory = $true , ValueFromPipeline = $true)]
        [VMware.VimAutomation.ViCore.Types.V1.Inventory.VirtualMachine[]] $VM
    )

    Begin
    {
        $srmED = $DefaultSrmServers.ExtensionData
        if(!$srmED)
        {
            Write-Output "Terminating. Session is not connected to a SRM server."
            break
        }
        $pgroups = $srmED.Protection.ListProtectionGroups()
        $pghash = [sClass]::MakePgHash($pgroups)
        $dshash = [sClass]::MakeHash('ds')
        $nd = "No Data"
    }

    Process
    {
        foreach ($v in $vm)
        {

            $VMdsID = $v.ExtensionData.DataStore
            $VMmoref = $v.ExtensionData.Moref
            $VMname = $v.Name

            foreach ($vmd in $VMdsID)
            {
                $targetpg = $pghash.GetEnumerator().where({ $_.Name -eq $($vmd).ToString() })
                $VMdsName = $dshash.($($vmd).ToString())

                if ($targetpg)
                {
                        $protstat = $targetpg.Value.QueryVmProtection($VMmoref)
                        $lo = [sClass]::MakeObj( $protstat , $VMname , $VMmoref , $VMdsName )
                        $lo
                }

                else
                {
                    $reason = "Protection Group not found for DataStore $VMdsName."
                    $lo = [sClass]::MakeObj( $reason , $VMname , $VMmoref , $VMdsName , $nd )
                    $lo
                }

                $protstat , $lo  = $null
            }
        }
    }
}

<#
.SYNOPSIS
Protects SRM VMs
.DESCRIPTION
Protects SRM VMs. Must be run on the protected site.
Attempts only if VM Protection State is "CanBeProtected" and an affiliated Protection Group can be located.
.PARAMETER VM
Output from VMWare PowerCLI Get-VM. See Examples.
[VMware.VimAutomation.ViCore.Types.V1.Inventory.VirtualMachine]
.INPUTS
VMWare PowerCLI VM from Get-VM:
[VMware.VimAutomation.ViCore.Types.V1.Inventory.VirtualMachine]
.OUTPUTS
[pscustomobject] SupSkiFun.SRM.Protect.Info
.EXAMPLE
Protect one VM:
Get-VM -Name SYS01 | Protect-SRMVM
.EXAMPLE
Protect multiple VMS, returning the object into a variable:
$myVar = Get-VM -Name WEB* | Protect-SRMVM
#>

Function Protect-SRMVM
{
    [cmdletbinding()]
    Param
    (
        [Parameter(Mandatory = $true , ValueFromPipeline = $true)]
        [VMware.VimAutomation.ViCore.Types.V1.Inventory.VirtualMachine[]] $VM
    )

    Begin
    {
        $srmED = $DefaultSrmServers.ExtensionData
        if(!$srmED)
        {
            Write-Output "Terminating. Session is not connected to a SRM server."
            break
        }
        $stat = "CanBeProtected"
        $pgroups = $srmED.Protection.ListProtectionGroups()
        $pghash = [sClass]::MakePgHash($pgroups)
        $dshash = [sClass]::MakeHash('ds')
    }


    Process
    {
        Function ProtVM
        {
            param($targetpg,$VMmoref)

            $vspec = [VMware.VimAutomation.Srm.Views.SrmProtectionGroupVmProtectionSpec]::new()
            $vspec.Vm = $VMmoref
            $ptask = $targetpg.ProtectVms($vspec)

            while(-not $ptask.IsComplete())
            {
                Start-Sleep -Seconds 1
            }

            $pinfo = $ptask.getresult()
            $pinfo
        }

        foreach ($v in $vm)
        {
            $VMdsID = $v.ExtensionData.DataStore
            $VMmoref = $v.ExtensionData.Moref
            $VMname = $v.Name

            foreach ($vmd in $VMdsID)
            {
                $targetpg = $pghash.GetEnumerator().where({ $_.Name -eq $($vmd).ToString() })

                if ($targetpg)
                {
                    $targetpg = $targetpg.Value
                    $protstat = $targetpg.QueryVmProtection($VMmoref)

                    if ($protstat.Status -match $stat)
                    {
                        $tinfo = ProtVM -targetpg $targetpg -VMmoref $VMmoref
                        $lo = MakeTObj -tinfo $tinfo -VMname $VMname -VMmoref $VMmoref
                        $lo
                    }

                    else
                    {
                        $reason = "State is $($protstat.Status). State should be $stat."
                        $lo = [sClass]::MakeObj( $reason , $VMname , $VMmoref )
                        $lo
                    }
                    break
                }

                else
                {
                    $VMdsName = $dshash.($($vmd).ToString())
                    $reason = "Protection Group not found for DataStore $VMdsName."
                    $lo = [sClass]::MakeObj( $reason , $VMname , $VMmoref )
                    $lo
                }
            }

            $tinfo , $lo  = $null
        }
    }
}

<#
.SYNOPSIS
Sends a Dismiss command to a SRM Recovery Plan
.DESCRIPTION
Sends a Dismiss command to a SRM Recovery Plan Prompt to continue plan execution.
Does not attempt if submitted plan is not in a Prompting state. Must be run on the recovery site.
.PARAMETER RecoveryPlan
SRM Recovery Plan. VMware.VimAutomation.Srm.Views.SrmRecoveryPlan
.INPUTS
VMware.VimAutomation.Srm.Views.SrmRecoveryPlan
.EXAMPLE
$p = Get-SRMRecoveryPlan | Where-Object -Property Name -eq "PlanXYZ"
$p | Send-SRMDismiss
#>

Function Send-SRMDismiss
{
    [cmdletbinding(SupportsShouldProcess = $True , ConfirmImpact = "High")]
    Param
    (
        [Parameter (Mandatory = $true , ValueFromPipeline = $true)]
        [VMware.VimAutomation.Srm.Views.SrmRecoveryPlan[]] $RecoveryPlan
    )

    Begin
    {
        $ReqState = "Prompting"
    }
    Process
    {
        foreach ($rp in $RecoveryPlan)
        {
            $rpinfo = $rp.GetInfo()

            if ($pscmdlet.ShouldProcess( $rpinfo.Name , "Dismiss Prompt" ))
            {
                if ($rpinfo.State -eq $ReqState)
                {
                    $rp.AnswerPrompt($rp.ListPrompts().key , $false, "Dismiss")
                }

                else
                {
                    $mesg = "Not Sending Dismissal for $($rpinfo.Name). State is $($rpinfo.State). State should be $ReqState."
                    Write-Output "`n`t`t$mesg`n"
                }
            }
        }
    }
}

<#
.SYNOPSIS
Returns an object of Protected VMs.
.DESCRIPTION
Returns an object of Protected VMs from submitted Protection Groups.
Returns an object of VM, VMMoRef, ProtectedVM, PeerProtectedVm, ProtectionGroup, VMState,
PeerState, NeedsConfig and Faults. Can be run on recovery or protected site.
.NOTES
Works optimally for VMs in a Protection Group. For a few VMs not in the same protection group, Get-SRMVM may work better.
.PARAMETER ProtectionGroup
[VMware.VimAutomation.Srm.Views.SrmProtectionGroup] Protection Group Object. See Examples.
.INPUTS
[VMware.VimAutomation.Srm.Views.SrmProtectionGroup]
.OUTPUTS
[pscustomobject] SupSkiFun.SRM.Protect.Info
.EXAMPLE
Return an object of VMs from specific Protection Group(s) into the myInfo variable:
$myPG = Get-SRMProtectionGroup | Where-Object -Property Name -Match "DS1"
$myInfo = $myPG | Show-SRMProtectedVM
.EXAMPLE
Return an object of VMs from all Protection Groups into the myInfo variable:
$myPG = Get-SRMProtectionGroup
$myInfo = $myPG | Show-SRMProtectedVM
.LINK
Get-SRMVM
#>

function Show-SRMProtectedVM
{
    [CmdletBinding()]
    param
    (
        [Parameter(Mandatory = $true , ValueFromPipeline = $true)]
        [VMware.VimAutomation.Srm.Views.SrmProtectionGroup[]] $ProtectionGroup
    )

    Process
    {
        foreach ($pgrp in $ProtectionGroup)
        {
            $pvms = $pgrp.ListProtectedVms()
            foreach ($pvm in $pvms)
            {
                $lo = [pscustomobject]@{
                    VM = $pvm.VmName.ToString()
                    VMMoRef = $pvm.Vm.Moref.ToString()
                    ProtectedVM = $pvm.ProtectedVm.ToString()
                    PeerProtectedVm = $pvm.PeerProtectedVm.ToString()
                    ProtectionGroup = $pgrp.Name.ToString()
                    VMState = $pvm.State.toString()
                    PeerState = $pvm.PeerState.ToString()
                    NeedsConfig = $pvm.NeedsConfiguration
                    Faults = $pvm.Faults
                }
            $lo.PSObject.TypeNames.Insert(0,'SupSkiFun.SRM.Protect.Info')
            $lo
            }
        }
    }
}

<#
.SYNOPSIS
Shows Relationship amongst Recovery Plans, Protection Groups, and DataStores.
.DESCRIPTION
Shows Relationship amongst Recovery Plans, Protection Groups, and DataStores. Returns an object of RecoveryPlan,
ProtectionGroup, DataStore, RecoveryPlanMoRef, ProtectionGroupMoref, DataStoreMoRef, PeerMoRef and PeerState.
Can be run on recovery or protected site.
Note: DataStore Name is Not Available from the Recovery Site; it is only available from the Protection Site.
.PARAMETER RecoveryPlan
SRM Recovery Plan. VMware.VimAutomation.Srm.Views.SrmRecoveryPlan. See Examples.
.INPUTS
VMware.VimAutomation.Srm.Views.SrmRecoveryPlan
.OUTPUTS
PSCUSTOMOBJECT SupSkiFun.SRM.Info
.EXAMPLE
Show Relationship of all SRM Recovery Plans returning object into a variable:
$allRP = Get-SRMRecoveryPlan
$MyVar = $allRP | Show-SRMRelationship
.EXAMPLE
Show Relationship of specific SRM Recovery Plan(s) matching a criteria, returning object into a variable:
$myRP = Get-SRMRecoveryPlan | Where-Object -Property Name -Match "CL07*"
$MyVar = $myRP | Show-SRMRelationship
#>

Function Show-SRMRelationship
{
    [cmdletbinding()]
    Param
    (
        [Parameter (Mandatory = $true, ValueFromPipeline = $true)]
        [VMware.VimAutomation.Srm.Views.SrmRecoveryPlan[]] $RecoveryPlan
    )

    Begin
    {
        $nota = "Not Available on Recovery Site; only available from the Protection Site"
    }

    Process
    {
        foreach ($plan in $RecoveryPlan)
        {
            $ap = $plan.GetInfo()
            $pg = $ap.ProtectionGroups.GetInfo().Name
            $ar = $ap.ProtectionGroups.ListProtectedDatastores().Moref.ForEach({$_.ToString()})
            $pi = $plan.GetPeer()

            if ($ap.State -match "Protecting")
            {
                $ds = (get-datastore -id $ar).Name
            }
            else
            {
                $ds = $nota
            }

            $lo = [pscustomobject]@{
                RecoveryPlan = $ap.Name
                RecoveryPlanState = $ap.State.ToString()
                ProtectionGroup = $pg
                Datastore = $ds
                RecoveryPlanMoRef = $plan.MoRef.ToString()
                ProtectionGroupMoRef = $ap.ProtectionGroups.Moref.ForEach({$_.ToString()})
                DataStoreMoRef = $ar
                PeerMoRef = $pi.PlanMoRef.ToString()
                PeerState = $pi.State.ToString()
            }
            $lo.PSObject.TypeNames.Insert(0,'SupSkiFun.SRM.Info')
            $lo
        }
    }
}

<#
.SYNOPSIS
Starts the SRM Cleanup Process.
.DESCRIPTION
Starts the SRM Cleanup Process for specified SRM Recovery Plans.
Does not attempt if submitted plan is not in a NeedsCleanup state. Must be run on the recovery site.
.PARAMETER RecoveryPlan
SRM Recovery Plan. VMware.VimAutomation.Srm.Views.SrmRecoveryPlan
.INPUTS
VMware.VimAutomation.Srm.Views.SrmRecoveryPlan
.EXAMPLE
$p = Get-SRMRecoveryPlan | Where-Object -Property Name -eq "PlanXYZ"
$p | Start-SRMCleanUp
#>

Function Start-SRMCleanUp
{
    [cmdletbinding(SupportsShouldProcess = $True , ConfirmImpact = "High")]
    Param
    (
        [Parameter (Mandatory = $true , ValueFromPipeline = $true )]
        [VMware.VimAutomation.Srm.Views.SrmRecoveryPlan[]] $RecoveryPlan
    )

    Begin
    {
        [VMware.VimAutomation.Srm.Views.SrmRecoveryPlanRecoveryMode] $RecoveryMode = [VMware.VimAutomation.Srm.Views.SrmRecoveryPlanRecoveryMode]::CleanUpTest
        $ReqState = "NeedsCleanup"
    }

    Process
    {
        foreach ($rp in $RecoveryPlan)
        {
            $rpinfo = $rp.GetInfo()

            if ($pscmdlet.ShouldProcess($rpinfo.Name, $RecoveryMode))
            {
                if ($rpinfo.State -eq $ReqState)
                {
                    $rp.Start($RecoveryMode)
                }

                else
                {
                    $mesg = "Not Starting Cleanup for $($rpinfo.Name). State is $($rpinfo.State). State should be $ReqState."
                    Write-Output "`n`t`t$mesg`n"
                }
            }
        }
    }
}

<#
.SYNOPSIS
Starts a Test SRM Recovery Plan.
.DESCRIPTION
Starts a Test SRM Recovery Plan, optionally synching data. Default is to not synch data.
Does not attempt if submitted plan is not in a Ready state. Must be run on the recovery site.
.PARAMETER RecoveryPlan
SRM Recovery Plan. VMware.VimAutomation.Srm.Views.SrmRecoveryPlan
.PARAMETER SyncData
If specified, the test will execute Step 1 Synchronize Storage.
If not specified Step 1 Synchronize Storage is skipped.
.INPUTS
VMware.VimAutomation.Srm.Views.SrmRecoveryPlan
.EXAMPLE
Start SRM Test meeting a selection criteria:
$p = Get-SRMRecoveryPlan | Where-Object -Property Name -eq "PlanXYZ"
$p | Start-SRMTest
.EXAMPLE
Start SRM Test(s) meeting a selection criteria, synchronizing storage:
$p = Get-SRMRecoveryPlan | Where-Object -Property Name -match "ProdWeb*"
$p | Start-SRMTest -SyncData
#>


Function Start-SRMTest
{
    [cmdletbinding(SupportsShouldProcess = $True , ConfirmImpact = 'High')]
    Param
    (
        [Parameter (Mandatory = $true, ValueFromPipeline = $true, Position = 1)]
        [VMware.VimAutomation.Srm.Views.SrmRecoveryPlan[]] $RecoveryPlan,

        [switch] $SyncData
    )

    Begin
    {
        [VMware.VimAutomation.Srm.Views.SrmRecoveryPlanRecoveryMode] $RecoveryMode = [VMware.VimAutomation.Srm.Views.SrmRecoveryPlanRecoveryMode]::Test
        $ReqState = "Ready"
        $rpOpt = [VMware.VimAutomation.Srm.Views.SrmRecoveryOptions]::new()

        if ($SyncData)
        {
            $rpOpt.SyncData = $true
        }
        else
        {
            $rpOpt.SyncData = $false
        }
    }

    Process
    {
        foreach ($rp in $RecoveryPlan)
        {
            $rpinfo = $rp.GetInfo()

            if ($pscmdlet.ShouldProcess($rpinfo.Name, $RecoveryMode))
            {
                if ($rpinfo.State -eq $ReqState)
                {
                    $rp.Start($RecoveryMode,$rpOpt)
                }

                else
                {
                    $mesg = "Not Starting Test of $($rpinfo.Name). State is $($rpinfo.State). State should be $ReqState."
                    Write-Output "`n`t`t$mesg`n"
                }
            }
        }
    }
}
<#
.SYNOPSIS
Stops / cancels an SRM Test.
.DESCRIPTION
Stops / cancels an SRM Test for specified SRM Recovery Plans.
Does not attempt if submitted plan is not in a Running or Prompting state. Must be run on the recovery site.
.PARAMETER RecoveryPlan
SRM Recovery Plan. VMware.VimAutomation.Srm.Views.SrmRecoveryPlan
.INPUTS
VMware.VimAutomation.Srm.Views.SrmRecoveryPlan
.EXAMPLE
$p = Get-SRMRecoveryPlan | Where-Object -Property Name -eq "PlanXYZ"
$p | Stop-SRMTest
#>

Function Stop-SRMTest
{
    [cmdletbinding(SupportsShouldProcess = $True , ConfirmImpact = "High")]
    Param
    (
        [Parameter (Mandatory = $true , ValueFromPipeline = $true )]
        [VMware.VimAutomation.Srm.Views.SrmRecoveryPlan[]] $RecoveryPlan
    )

    Begin
    {
        $ReqState = "Running" , "Prompting"
    }

    Process
    {
        foreach ($rp in $RecoveryPlan)
        {
            $rpinfo = $rp.GetInfo()

            if ($pscmdlet.ShouldProcess($rpinfo.Name, 'Cancel'))
            {
                if ($rpinfo.State -in $ReqState)
                {
                    $rp.Cancel()
                }

                else
                {
                    $mesg = "Not Stopping $($rpinfo.Name). State is $($rpinfo.State). State should be $($ReqState -join " or ")."
                    Write-Output "`n`t`t$mesg`n"
                }
            }
        }
    }
}

<#
.SYNOPSIS
UnProtects SRM VMs
.DESCRIPTION
UnProtects SRM VMs. Must be run on the protected site.
Attempts only if VM Protection State is "IsProtected" and an affiliated Protection Group can be located.
.PARAMETER VM
Output from VMWare PowerCLI Get-VM. See Examples.
[VMware.VimAutomation.ViCore.Types.V1.Inventory.VirtualMachine]
.INPUTS
VMWare PowerCLI VM from Get-VM:
[VMware.VimAutomation.ViCore.Types.V1.Inventory.VirtualMachine]
.OUTPUTS
[pscustomobject] SupSkiFun.SRM.Protect.Info
.EXAMPLE
UnProtect one VM:
Get-VM -Name SYS01 | UnProtect-SRMVM
.EXAMPLE
UnProtect multiple VMS, returning the object into a variable:
$myVar = Get-VM -Name WEB* | UnProtect-SRMVM
#>

Function UnProtect-SRMVM
{
    [cmdletbinding()]
    Param
    (
        [Parameter(Mandatory = $true , ValueFromPipeline = $true)]
        [VMware.VimAutomation.ViCore.Types.V1.Inventory.VirtualMachine[]] $VM
    )

    Begin
    {
        $srmED = $DefaultSrmServers.ExtensionData
        if(!$srmED)
        {
            Write-Output "Terminating. Session is not connected to a SRM server."
            break
        }
        $stat = "IsProtected"
        $pgroups = $srmED.Protection.ListProtectionGroups()
        $pghash = [sClass]::MakePgHash($pgroups)
        $dshash = [sClass]::MakeHash('ds')
    }

    Process
    {
        Function UnProtVM
        {
            param($targetpg,$VMmoref)

            $ptask = $targetpg.UnProtectVms($VMmoref)

            while(-not $ptask.IsComplete())
            {
                Start-Sleep -Seconds 1
            }

            $pinfo = $ptask.getresult()
            $pinfo
        }

        foreach ($v in $vm)
        {
            $VMdsID = $v.ExtensionData.DataStore
            $VMmoref = $v.ExtensionData.Moref
            $VMname = $v.Name

            foreach ($vmd in $VMdsID)
            {
                $targetpg= $pghash.GetEnumerator().where({ $_.Name -eq $($vmd).ToString() })

                if ($targetpg)
                {
                    $targetpg = $targetpg.Value
                    $protstat = $targetpg.QueryVmProtection($VMmoref)

                    if ($protstat.Status -match $stat)
                    {
                        $tinfo = UnProtVM -targetpg $targetpg -VMmoref $VMmoref
                        $lo = MakeTObj -tinfo $tinfo -VMname $VMname -VMmoref $VMmoref
                        $lo
                    }

                    else
                    {
                        $reason = "State is $($protstat.Status). State should be $stat."
                        $lo = [sClass]::MakeObj( $reason , $VMname , $VMmoref )
                        $lo
                    }
                    break

                }

                else
                {
                    $VMdsName = $dshash.($($vmd).ToString())
                    $reason = "Protection Group not found for DataStore $VMdsName."
                    $lo = [sClass]::MakeObj( $reason , $VMname , $VMmoref )
                    $lo
                }
            }

            $tinfo , $lo  = $null
        }
    }
}

Export-ModuleMember -Function * -Alias * -Cmdlet * -Variable *