Functions/Public/EPCAuth.ps1

# EPC (Endpoint Client Controller) monitoring functions

Function Connect-EPCController {
    <#
        .SYNOPSIS
        Connects to EPC Controller and store the credentials for later use.
 
        .DESCRIPTION
        Connects to an Endpoint Client Controller and store the credentials for later use.
         
        .PARAMETER ControllerFQDN
        The FQDN of the Endpoint Client Controller
 
        .PARAMETER Credential
        The credentials used to access the EPC Controller.
 
        .PARAMETER CredSecret
        Use stored credentials saved via Set-Secret. Requires prior installation of Microsoft.PowerShell.SecretManagement PS module and an appropriate
        secret vault, such as Microsoft.PowerShell.SecretStore. Locally, the Microsoft.PowerShell.SecretStore can be used to store secrets securely on
        the local machine. This is the minimum requirement for using this feature.
        Install the modules by running:
        Install-Module Microsoft.PowerShell.SecretManagement
        Install-Module Microsoft.PowerShell.SecretStore
 
        Register a credential secret by doing something like: Set-Secret -Name NectarCreds -Vault SecretStore -Secret (Get-Credential)
         
        .PARAMETER EnvFromFile
        Use a CSV file called EPCEnvList.csv located in the user's default Documents folder to show a list of environments to select from.
        Run [Environment]::GetFolderPath("MyDocuments") to find your default document folder.
        This parameter is only available if EPCEnvList.csv is found in the user's default Documents folder (ie: C:\Users\username\Documents)
        Also sets the default credentials to use for the selected environment. This feature uses the Microsoft.PowerShell.SecretManagement PS module,
        which must be installed and configured with a secret store prior to using this option.
        N10EnvList.csv must have a header with three columns defined as "Environment, DefaultTenant, Secret".
        Each environment and Secret (if used) should be on their own separate lines
         
        .EXAMPLE
        $Cred = Get-Credential
        Connect-EPControllerFQDN -Credential $cred -ControllerFQDN contoso.nectar.services
        Connects to the contoso.nectar.services EPC Controller using the credentials supplied to the Get-Credential command
         
        .EXAMPLE
        Connect-EPControllerFQDN -ControllerFQDN contoso.nectar.services -CredSecret MyEPCCreds
        Connects to contoso.nectar.services EPC Controller using previously stored credentials called MyEPCCreds
         
        .NOTES
        Version 1.0
    #>

    
    Param (
        [Parameter(ValueFromPipeline, Mandatory=$False)]
        # [ValidateScript ({
            # If ($_ -Match "^(?:http(s)?:\/\/)?[\w.-]+(?:\.[\w\.-]+)+[\w\-\._~:/?#[\]@!\$&'\(\)\*\+,;=.]+$") {
                # $True
            # }
            # Else {
                # Throw "ERROR: Endpoint Client Controller name must be in FQDN format."
            # }
        # })]
        [string] $ControllerFQDN,
        [Parameter(ValueFromPipelineByPropertyName)]
        [System.Management.Automation.Credential()]
        [PSCredential] $Credential,
        [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)]
        [string]$CredSecret,
        [switch]$UseXML
    )
    DynamicParam {
        $DefaultDocPath = [Environment]::GetFolderPath("MyDocuments")
        $EnvPath = "$DefaultDocPath\EPCEnvList.csv"
        If (Test-Path $EnvPath -PathType Leaf) {
            # Set the dynamic parameters' name
            $ParameterName = 'EnvFromFile'
            
            # Create the dictionary
            $RuntimeParameterDictionary = New-Object System.Management.Automation.RuntimeDefinedParameterDictionary
         
            # Create the collection of attributes
            $AttributeCollection = New-Object System.Collections.ObjectModel.Collection[System.Attribute]
                    
            # Create and set the parameters' attributes
            $ParameterAttribute = New-Object System.Management.Automation.ParameterAttribute
            $ParameterAttribute.Mandatory = $False
            $ParameterAttribute.Position = 1
         
            # Add the attributes to the attributes collection
            $AttributeCollection.Add($ParameterAttribute)
         
            # Generate and set the ValidateSet
            $EnvSet = Import-Csv -Path $EnvPath
            $ValidateSetAttribute = New-Object System.Management.Automation.ValidateSetAttribute($EnvSet.Environment)
         
            # Add the ValidateSet to the attributes collection
            $AttributeCollection.Add($ValidateSetAttribute)
         
            # Create and return the dynamic parameter
            $RuntimeParameter = New-Object System.Management.Automation.RuntimeDefinedParameter($ParameterName, [string], $AttributeCollection)
            $RuntimeParameterDictionary.Add($ParameterName, $RuntimeParameter)
            Return $RuntimeParameterDictionary
        }
    }

    Begin {
        # Bind the dynamic parameter to a friendly variable
        If (Test-Path $EnvPath -PathType Leaf) {
            If ($PsBoundParameters[$ParameterName]) {
                $ControllerFQDN = $PsBoundParameters[$ParameterName]
                Write-Verbose "ControllerFQDN: $ControllerFQDN"
                
                # Get the array position of the selected environment
                $EnvPos = $EnvSet.Environment.IndexOf($ControllerFQDN)
                
                # Check for secret in EPCEnvList.csv and use if available
                $CredSecret = $EnvSet[$EnvPos].CredSecret
                Write-Verbose "Secret: $CredSecret"
            }
        }
        <#
        The New-WebServiceProxy command is not supported on PS versions higher than 5.1.
        On PS 6.0+, we have to use Invoke-WebRequest, which works, but is more verbose and doesn't utilize the
        WSDL files which creates specific objects for the results.
        Do a check and set a global variable which will determine if the function will use New-WebServiceProxy or Invoke-WebRequest
        #>
 
        If ($PSVersionTable.PSVersion.Major -gt 5 -Or $UseXML -Or $Global:EPC_UseWSDL -eq $False) {
            $Global:EPC_UseWSDL = $FALSE
        }
        Else {
            $Global:EPC_UseWSDL = $TRUE
        }
        Write-Verbose "Setting global UseWSDL variable to $Global:EPC_UseWSDL"
    }    
    Process {
        # Need to force TLS 1.2, if not already set
        If ([Net.ServicePointManager]::SecurityProtocol -ne 'Tls12') { [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 }
        
        # Ask for the tenant name if global EPCController tenant variable not available and not entered on command line
        If ((-not $Global:EPControllerFQDN) -And (-not $ControllerFQDN)) {
            $ControllerFQDN = Read-Host "Enter the Endpoint Client Controller FQDN"
        }
        ElseIf (($Global:EPControllerFQDN) -And (-not $ControllerFQDN)) {
            $ControllerFQDN = $Global:EPControllerFQDN
        }
    
        $RegEx = "^(?:http(s)?:\/\/)?([\w.-]+(?:\.[\w\.-]+)+[\w\-\._~:/?#[\]@!\$&'\(\)\*\+,;=.]+)$"
        $FQDNMatch = Select-String -Pattern $Regex -InputObject $ControllerFQDN
        $EPControllerFQDN = $FQDNMatch.Matches.Groups[2].Value
    
        # Ask for credentials if global EPCController creds aren't available
        If (((-not $Global:EPControllerCred) -And (-not $Credential)) -Or (($Global:EPControllerFQDN -ne $EPControllerFQDN) -And (-Not $Credential)) -And (-Not $CredSecret)) {
            $Credential = Get-Credential
        }
        ElseIf ($Global:EPControllerCred -And (-not $Credential)) {
            $Credential = $Global:EPControllerCred
        }
        
        # Pull credentials from secret if specified
        If ($CredSecret) {
            Try {
                $Credential = Get-Secret $CredSecret
            }
            Catch {
                Throw "Cannot find secret: $CredSecret"
            }
        }
        
        # Get the WSDL
        If ($Global:EPC_UseWSDL -And $UseXML -eq $False) {
            Write-Verbose 'Using WSDL'
            If ($Global:EPCWSDL_ActiveCtrl -and $Global:EPControllerFQDN -eq $EPControllerFQDN) {
                Write-Verbose 'Using WSDL from global variable'
                $EPC_ActiveCtrl = $Global:EPCWSDL_ActiveCtrl
                $EPC_TestGrpMgmt = $Global:EPCWSDL_TestGrpMgmt
                $EPC_ResGrpMgmt = $Global:EPCWSDL_ResGrpMgmt
                $EPC_SvcMgmt = $Global:EPCWSDL_SvcMgmt
                $EPC_BulkExport = $Global:EPCWSDL_BulkExport
                $EPC_GetData = $Global:EPCWSDL_GetData
            }
            ElseIf ((!$Global:EPCWSDL_ActiveCtrl -and $Global:EPControllerFQDN -eq $EPControllerFQDN) -Or !$Global:EPCWSDL_ActiveCtrl) {
                Write-Verbose "Loading WSDL from $EPControllerFQDN"
                $EPC_ActiveCtrl = New-WebServiceProxy "https://$EPControllerFQDN/telchemywebservices/services/telchemyActiveCtrlService?wsdl" -Namespace EPC.ActiveCtrl -Class ActiveCtrl
                $EPC_TestGrpMgmt = New-WebServiceProxy "https://$EPControllerFQDN/telchemywebservices/services/telchemyTestGroupManagementService?wsdl" -Namespace EPC.TestGrpMgmt -Class TestGrpMgmt
                $EPC_ResGrpMgmt = New-WebServiceProxy "https://$EPControllerFQDN/telchemywebservices/services/telchemyRsrcGroupMgmtService?wsdl" -Namespace EPC.ResGrpMgmt -Class ResGrpMgmt
                $EPC_SvcMgmt = New-WebServiceProxy "https://$EPControllerFQDN/telchemywebservices/services/telchemySvcMgmtService?wsdl" -Namespace EPC.SvcMgmt -Class SvcMgmt
                $EPC_BulkExport = New-WebServiceProxy "https://$EPControllerFQDN/telchemywebservices/services/telchemyBulkExportService?wsdl" -Namespace EPC.BulkExport -Class BulkExport
                $EPC_GetData = New-WebServiceProxy "https://$EPControllerFQDN/telchemywebservices/services/telchemyGetDataService?wsdl" -Namespace EPC.GetData -Class GetData
            }
            Else {
                Write-Error "There is already an active connection to $($Global:EPControllerFQDN). Please open a new PowerShell window to connect to a new controller."
                Return
            }
        }
                
        If ((-not $Global:EPControllerCred) -Or (-not $Global:EPControllerFQDN) -Or ($Global:EPControllerFQDN -ne $EPControllerFQDN)) {
            # Validate login credentials by running a simple API query
            If ($Global:EPC_UseWSDL -And $UseXML -eq $False) {
                # Store creds in EPC credential object
                $EPCCred = New-Object -TypeName EPC.ActiveCtrl.CredentialsType
                $EPCCred.username = $Credential.UserName
                $EPCCred.password = $Credential.GetNetworkCredential().Password

                Write-Verbose 'Using WSDL to test access'
                $EPCAPIVersionParams = New-Object -TypeName EPC.ActiveCtrl.GetAPIVersionParametersType
                $EPCAPIVersionParams.credentials = $EPCCred
                $EPCAPIVersion = $EPC_ActiveCtrl.getAPIVersion($EPCAPIVersionParams)
                
                $LoginResult = $EPCAPIVersion.result
            }
            Else {
                $ProgressPreference = 'SilentlyContinue'
                Write-Verbose 'Using WebXML to test access'

                # Escape special characters in password for XML
                $PwdEscaped = [System.Security.SecurityElement]::Escape($Credential.GetNetworkCredential().Password)

                [xml]$SOAPReq = "<soapenv:Envelope xmlns:soapenv='http://schemas.xmlsoap.org/soap/envelope/'
                    xmlns:urn='urn:telchemyActiveCtrl'>
                     <soapenv:Header/>
                     <soapenv:Body>
                     <urn:getAPIVersionParameters>
                    <credentials>
                    <username>$($Credential.UserName)</username>
                    <password>$PwdEscaped</password>
                    </credentials>
                     </urn:getAPIVersionParameters>
                     </soapenv:Body>
                    </soapenv:Envelope>"


                Write-Verbose $SOAPReq.OuterXML
                $SOAPFQDN = "https://$EPControllerFQDN/telchemywebservices/services/telchemyActiveCtrlService"

                $Headers = @{
                    'SOAPAction' = 'urn:telchemyActiveCtrl/getAPIVersionParameters'
                }

                Write-Verbose $SOAPFQDN
                
                # Older versions of Telchemy require the SOAPAction header, while newer versions fail if it is included.
                # Try with the header first, and if it fails, try again without it.
                Try {
                    Write-Verbose 'Trying SOAP request with SOAPAction header'
                    [xml]$XMLResponse = (Invoke-WebRequest -Method POST -URI $SOAPFQDN -UseBasicParsing -Headers $Headers -Body $SOAPReq -ContentType 'text/xml').Content
                    $LoginResult = $XMLResponse.Envelope.Body.getAPIVersionResults.result
                    $Global:EPC_UseSOAPHeader = $True
                }
                Catch {
                    Write-Verbose 'Trying SOAP request without SOAPAction header'
                    [xml]$XMLResponse = (Invoke-WebRequest -Method POST -URI $SOAPFQDN -UseBasicParsing -Body $SOAPReq -ContentType 'text/xml').Content
                    $LoginResult = $XMLResponse.Envelope.Body.getAPIVersionResults.result
                    $Global:EPC_UseSOAPHeader = $False
                }            
            }
            
            If ($LoginResult -ne 'success') {
                Write-Error "Could not connect to $EPControllerFQDN using $($Credential.UserName)"
                Throw $LoginResult
            }
            Else {
                Write-Host -ForegroundColor Green "Successful API connection to " -NoNewLine
                Write-Host -ForegroundColor Yellow "https://$EPControllerFQDN" -NoNewLine
                Write-Host -ForegroundColor Green " using " -NoNewLine
                Write-Host -ForegroundColor Yellow ($Credential).UserName
                $Global:EPControllerFQDN = $EPControllerFQDN
                $Global:EPControllerCred = $Credential
                $Global:EPCWSDL_ActiveCtrl = $EPC_ActiveCtrl
                $Global:EPCWSDL_TestGrpMgmt = $EPC_TestGrpMgmt
                $Global:EPCWSDL_ResGrpMgmt = $EPC_ResGrpMgmt
                $Global:EPCWSDL_SvcMgmt = $EPC_SvcMgmt
                $Global:EPCWSDL_BulkExport = $EPC_BulkExport
                $Global:EPCWSDL_GetData = $EPC_GetData

                If ($PSVersionTable.PSVersion.Major -gt 5) {
                    # Check for the PowerHTML module and install if not present. Needed for parsing web pageSize
                    If (!(Get-InstalledModule -Name PowerHTML)) { 
                        $Null = Install-Module -Name PowerHTML -Scope CurrentUser -Force
                    }
                }
                Write-Verbose "Setting global UseWSDL variable to $Global:EPC_UseWSDL"
            }
        }
    }
}    


Function Get-EPCConnectedController {
    <#
        .SYNOPSIS
        Returns the FQDN of the connected controller
 
        .DESCRIPTION
        Returns the FQDN of the connected controller
         
        .NOTES
        Version 1.0
    #>

    
    If ($Global:EPControllerFQDN) {
        Return $Global:EPControllerFQDN
    }
    Else {
        Return 'Not connected to a controller' 
    }
}


Function Get-EPCWebSessionCookie {
    <#
        .SYNOPSIS
        Creates a web session cookie for use with commands that can't use the EPC API
 
        .DESCRIPTION
        Connects to an Endpoint Client Controller and store the credentials for later use.
        Must have already successfully connected via Connect-EPCController
         
        .NOTES
        Version 1.0
    #>

    [cmdletbinding()]
    param ()

    Connect-EPCController
    $ProgressPreference = 'SilentlyContinue'

    #URLEncode the password
    $EncodedPassword = [System.Web.HttpUtility]::UrlEncode($Global:EPControllerCred.GetNetworkCredential().Password)
    
    # Check for existing global session cookie. Connect if not available and save the web session variable in a global variable
    If (!$Global:EPCSessionCookie) {
        Write-Verbose 'Global session cookie does not exist'
        $Dashboard = Invoke-WebRequest -UseBasicParsing -Uri "https://$EPControllerFQDN/dashboard.htm" -SessionVariable 'EPCSession'
        $NULL = Invoke-WebRequest -UseBasicParsing -Uri "https://$EPControllerFQDN/j_security_check" -Method POST -Body "j_username=$($Global:EPControllerCred.UserName)&j_password=$EncodedPassword" -WebSession $EPCSession
        
        # Verify that session is active. We do this by looking for the existence of a single link on the dashboard page. If there is, this means the session expired and the base login page is being shown.
        # If there are numerous links, this indicates a successful login.
        $Dashboard = Invoke-WebRequest -UseBasicParsing -Uri "https://$EPControllerFQDN/dashboard.htm" -WebSession $EPCSession
        
        If ($Dashboard.Links.Count -gt 1) {
            Write-Host -ForegroundColor Green "Successful web session connection to " -NoNewLine
            Write-Host -ForegroundColor Yellow "https://$EPControllerFQDN" -NoNewLine
            Write-Host -ForegroundColor Green " using " -NoNewLine
            Write-Host -ForegroundColor Yellow ($Global:EPControllerCred).UserName
            $Global:EPCSessionCookie = $EPCSession
        }
        Else {
            Throw "Could not connect to $EPControllerFQDN web session using $($Credential.UserName)"
        }                
    }
    Else {
        # Verify that session is active. We do this by looking for the existence of a single link on the dashboard page. If there is, this means the session expired and the base login page is being shown.
        # If there are numerous links, this indicates the session is still active
        
        $Dashboard = Invoke-WebRequest -UseBasicParsing -Uri "https://$EPControllerFQDN/dashboard.htm" -WebSession $Global:EPCSessionCookie
        Write-Verbose "Session verify against $EPControllerFQDN"
        If ($Dashboard.Links.Count -eq 1) {
            # Re-authenticate and update global session cookie
            Write-Verbose 'Global session cookie expired'
            $Dashboard = Invoke-WebRequest -UseBasicParsing -Uri "https://$EPControllerFQDN/dashboard.htm" -SessionVariable 'EPCSession'
            
            $NULL = Invoke-WebRequest -UseBasicParsing -Uri "https://$EPControllerFQDN/j_security_check" -Method POST -Body "j_username=$($Global:EPControllerCred.UserName)&j_password=$EncodedPassword" -WebSession $EPCSession
            $Dashboard = Invoke-WebRequest -UseBasicParsing -Uri "https://$EPControllerFQDN/dashboard.htm" -WebSession $EPCSession
            
            If ($Dashboard.Links.Count -gt 1) {
                Write-Verbose "Successful auth cookie regeneration against $EPControllerFQDN"
                $Global:EPCSessionCookie = $EPCSession
            }
            Else {
                Throw "Could not regenerate auth cookie against $EPControllerFQDN using $($Credential.UserName)"
            }
        }
        Else {
            Write-Verbose "Session verified against $EPControllerFQDN"
        }
    }
}


Function Disconnect-EPCController {
    <#
        .SYNOPSIS
        Disconnects from any active Endpoint Client Controller
         
        .DESCRIPTION
        Essentially deletes any stored credentials and FQDN from global variables.
        If PS version is less than 6, this doesn't currently work because there apparently isn't a way to remove the WSDL namespace from memory.
        Thiss means that connecting to a new EPC controller will still try to use the WSDL associated with the original controller.
        Any API commands will fail. Therefore, there is no useful reason to disconnect.
 
        .EXAMPLE
        Disconnect-EPCController
        Disconnects from all active connections to an EPC Controller
 
        .NOTES
        Version 1.0
    #>


    [cmdletbinding()]
    param ()

    $VariableNames = 'EPControllerCred','EPControllerFQDN','EPCSessionCookie','EPCWSDL_ActiveCtrl','EPCWSDL_ResGrpMgmt','EPCWSDL_SvcMgmt','EPC_UseWSDL','EPC_UseSOAPHeader'

    ForEach ($Variable in $VariableNames) {
        Remove-Variable $Variable -Scope Global -ErrorAction:SilentlyContinue
    }
}