PowerTribeloo.psm1

function Join-UriQuery { 
    <#
    .SYNOPSIS
    Provides ability to join two Url paths together including advanced querying
 
    .DESCRIPTION
    Provides ability to join two Url paths together including advanced querying which is useful for RestAPI/GraphApi calls
 
    .PARAMETER BaseUri
    Primary Url to merge
 
    .PARAMETER RelativeOrAbsoluteUri
    Additional path to merge with primary url (optional)
 
    .PARAMETER QueryParameter
    Parameters and their values in form of hashtable
 
    .EXAMPLE
    Join-UriQuery -BaseUri 'https://evotec.xyz/' -RelativeOrAbsoluteUri '/wp-json/wp/v2/posts' -QueryParameter @{
        page = 1
        per_page = 20
        search = 'SearchString'
    }
 
    .EXAMPLE
    Join-UriQuery -BaseUri 'https://evotec.xyz/wp-json/wp/v2/posts' -QueryParameter @{
        page = 1
        per_page = 20
        search = 'SearchString'
    }
 
    .EXAMPLE
    Join-UriQuery -BaseUri 'https://evotec.xyz' -RelativeOrAbsoluteUri '/wp-json/wp/v2/posts'
 
    .NOTES
    General notes
    #>

    [alias('Join-UrlQuery')]
    [CmdletBinding()]
    param ([parameter(Mandatory)][uri] $BaseUri,
        [parameter(Mandatory = $false)][uri] $RelativeOrAbsoluteUri,
        [Parameter()][System.Collections.IDictionary] $QueryParameter)
    if ($BaseUri -and $RelativeOrAbsoluteUri) { $Url = Join-Uri -BaseUri $BaseUri -RelativeOrAbsoluteUri $RelativeOrAbsoluteUri } else { $Url = $BaseUri }
    if ($QueryParameter) {
        $Collection = [System.Web.HttpUtility]::ParseQueryString([String]::Empty)
        foreach ($key in $QueryParameter.Keys) { $Collection.Add($key, $QueryParameter.$key) }
    }
    $uriRequest = [System.UriBuilder] $Url
    if ($Collection) { $uriRequest.Query = $Collection.ToString() }
    return $uriRequest.Uri.AbsoluteUri
}
function Remove-EmptyValue {  
    [alias('Remove-EmptyValues')]
    [CmdletBinding()]
    param([alias('Splat', 'IDictionary')][Parameter(Mandatory)][System.Collections.IDictionary] $Hashtable,
        [string[]] $ExcludeParameter,
        [switch] $Recursive,
        [int] $Rerun,
        [switch] $DoNotRemoveNull,
        [switch] $DoNotRemoveEmpty,
        [switch] $DoNotRemoveEmptyArray,
        [switch] $DoNotRemoveEmptyDictionary)
    foreach ($Key in [string[]] $Hashtable.Keys) { if ($Key -notin $ExcludeParameter) { if ($Recursive) { if ($Hashtable[$Key] -is [System.Collections.IDictionary]) { if ($Hashtable[$Key].Count -eq 0) { if (-not $DoNotRemoveEmptyDictionary) { $Hashtable.Remove($Key) } } else { Remove-EmptyValue -Hashtable $Hashtable[$Key] -Recursive:$Recursive } } else { if (-not $DoNotRemoveNull -and $null -eq $Hashtable[$Key]) { $Hashtable.Remove($Key) } elseif (-not $DoNotRemoveEmpty -and $Hashtable[$Key] -is [string] -and $Hashtable[$Key] -eq '') { $Hashtable.Remove($Key) } elseif (-not $DoNotRemoveEmptyArray -and $Hashtable[$Key] -is [System.Collections.IList] -and $Hashtable[$Key].Count -eq 0) { $Hashtable.Remove($Key) } } } else { if (-not $DoNotRemoveNull -and $null -eq $Hashtable[$Key]) { $Hashtable.Remove($Key) } elseif (-not $DoNotRemoveEmpty -and $Hashtable[$Key] -is [string] -and $Hashtable[$Key] -eq '') { $Hashtable.Remove($Key) } elseif (-not $DoNotRemoveEmptyArray -and $Hashtable[$Key] -is [System.Collections.IList] -and $Hashtable[$Key].Count -eq 0) { $Hashtable.Remove($Key) } } } }
    if ($Rerun) { for ($i = 0; $i -lt $Rerun; $i++) { Remove-EmptyValue -Hashtable $Hashtable -Recursive:$Recursive } }
}
function Join-Uri { 
    <#
    .SYNOPSIS
    Provides ability to join two Url paths together
 
    .DESCRIPTION
    Provides ability to join two Url paths together
 
    .PARAMETER BaseUri
    Primary Url to merge
 
    .PARAMETER RelativeOrAbsoluteUri
    Additional path to merge with primary url
 
    .EXAMPLE
    Join-Uri 'https://evotec.xyz/' '/wp-json/wp/v2/posts'
 
    .EXAMPLE
    Join-Uri 'https://evotec.xyz/' 'wp-json/wp/v2/posts'
 
    .EXAMPLE
    Join-Uri -BaseUri 'https://evotec.xyz/' -RelativeOrAbsoluteUri '/wp-json/wp/v2/posts'
 
    .EXAMPLE
    Join-Uri -BaseUri 'https://evotec.xyz/test/' -RelativeOrAbsoluteUri '/wp-json/wp/v2/posts'
 
    .NOTES
    General notes
    #>

    [alias('Join-Url')]
    [cmdletBinding()]
    param([parameter(Mandatory)][uri] $BaseUri,
        [parameter(Mandatory)][uri] $RelativeOrAbsoluteUri)
    return ($BaseUri.OriginalString.TrimEnd('/') + "/" + $RelativeOrAbsoluteUri.OriginalString.TrimStart('/'))
}
function Convert-TribelooUser {
    [cmdletBinding()]
    param(
        [Array] $Users
    )
    foreach ($User in $Users) {
        $WorkEmail = $null
        $Emails = $User.'emails'
        foreach ($Email in $Emails) {
            if ($Email.Type -eq 'work') {
                $WorkEmail = $Email.Value
            }
        }

        [PSCustomObject] @{
            Id           = $User.'id'
            UserName     = $User.'userName'
            GivenName    = $User.'name'.'givenName'
            FamilyName   = $User.'name'.'familyName'
            DisplayName  = $User.'displayName'
            NickName     = $User.'nickName'
            EmailAddress = $WorkEmail
            Active       = $User.'active'
            Formatted    = $User.'addresses'[0].'formatted'
            Created      = $User.Meta.Created
            LastModified = $User.Meta.Updated
            Location     = $User.Meta.location
        }
    }
}
function Connect-Tribeloo {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory)][string] $Token,
        [Parameter(Mandatory)][alias('Url', 'BaseUri')][string] $Uri,
        [switch] $Suppress
    )

    $Script:AuthorizationTribeloo = @{
        'Authorization' = "Bearer $Token"
        'Content-Type'  = 'application/json; charset=utf-8'
        'BaseUri'       = $Uri
    }
    if (-not $Suppress) {
        return $Script:AuthorizationTribeloo
    }
}
function Get-TribelooUser {
    [CmdletBinding()]
    param(
        [string] $Id,
        [Alias('UserName')][string] $SearchUserName,
        [Alias('ExternalID')][string] $SearchExternalID,
        [string] $Search,
        [ValidateSet(
            'id',
            'userName',
            'givenName',
            'familyName',
            'displayName',
            'nickName',
            'emails',
            'addresses',
            'meta'
        )]
        [string] $SearchProperty = 'userName',
        [string] $SearchOperator = 'eq',
        [int] $MaxResults,
        #[int] $StartIndex = 1,
        #[int] $Count = 1000,
        [string] $Filter,
        [ValidateSet(
            'id',
            'userName',
            'givenName',
            'familyName',
            'displayName',
            'nickName',
            'emails',
            'addresses',
            'meta'
        )]
        [string] $SortBy,
        [ValidateSet('ascending', 'descending')][string] $SortOrder,
        [switch] $Native
    )
    if (-not $Script:AuthorizationTribeloo) {
        return
    }

    $ConvertAttributes = @{
        'id'          = 'id'
        'userName'    = 'userName'
        'givenName'   = 'name.givenName'
        'familyName'  = 'name.familyName'
        'displayName' = 'di%splayName'
        'nickName'    = 'nickName'
        'emails'      = 'emails'
        'active'      = 'active'
        'meta'        = 'meta'
    }

    if ($SortBy) {
        $SortByConverted = $ConvertAttributes[$SortBy]
    }

    $QueryParameter = @{
        #count = if ($Count) { $Count } else { $null }
        #startIndex = if ($StartIndex) { $StartIndex } else { $null }
        filter    = if ($SearchUserName) {
            "userName eq `"$SearchUserName`""
        } elseif ($SearchExternalID) {
            "externalId eq `"$SearchExternalID`""
        } elseif ($Search -and $SearchProperty) {
            "$($ConvertAttributes[$SearchProperty]) $SearchOperator `"$Search`""
        } else {
            $Filter
        }
        sortBy    = $SortByConverted
        sortOrder = $SortOrder
    }
    Remove-EmptyValue -Hashtable $QueryParameter

    if ($ID) {
        $Uri = Join-UriQuery -BaseUri $Script:AuthorizationTribeloo.BaseUri -RelativeOrAbsoluteUri "Users/$ID" -QueryParameter $QueryParameter
    } else {
        $Uri = Join-UriQuery -BaseUri $Script:AuthorizationTribeloo.BaseUri -RelativeOrAbsoluteUri 'Users' -QueryParameter $QueryParameter
    }
    Write-Verbose -Message "Get-TribelooUser - Using query: $Uri"

    $invokeRestMethodSplat = @{
        Method      = 'Get'
        Uri         = $Uri
        Headers     = [ordered]  @{
            'Content-Type'  = 'application/json; charset=utf-8'
            'Authorization' = $Script:AuthorizationTribeloo.Authorization
            'Cache-Control' = 'no-cache'
        }
        ErrorAction = 'Stop'
        ContentType = 'application/json; charset=utf-8'
    }

    try {
        $BatchObjects = Invoke-RestMethod @invokeRestMethodSplat
    } catch {
        if ($PSBoundParameters.ErrorAction -eq 'Stop') {
            throw
        } else {
            $ErrorDetails = $_.ErrorDetails.Message | ConvertFrom-Json -ErrorAction SilentlyContinue
            if ($ErrorDetails.Detail -like '*already exists*directory*') {
                Write-Warning -Message "Get-TribelooUser - $($ErrorDetails.Detail) [UserName: $UserName / ID: $ID]"
                return
            } else {
                Write-Warning -Message "Get-TribelooUser - Error $($_.Exception.Message), $($ErrorDetails.Detail)"
                return
            }
        }
    }
    if ($BatchObjects.Resources) {
        Write-Verbose -Message "Get-TribelooUser - Got $($BatchObjects.Resources.Count) users (StartIndex: $StartIndex, Count: $Count). Starting to process them."

        if ($MaxResults -gt 0 -and $BatchObjects.Resources.Count -ge $MaxResults) {
            # return users if amount of users available is more than we wanted
            if ($Native) {
                $BatchObjects.Resources | Select-Object -First $MaxResults
            } else {
                Convert-TribelooUser -Users ($BatchObjects.Resources | Select-Object -First $MaxResults)
            }
            # $LimitReached = $true
        } else {
            # return all users that were given in a batch
            if ($Native) {
                $BatchObjects.Resources
            } else {
                Convert-TribelooUser -Users $BatchObjects.Resources
            }
        }
    } elseif ($BatchObjects.Schemas -and $BatchObjects.id) {
        if ($Native) {
            $BatchObjects
        } else {
            Convert-TribelooUser -Users $BatchObjects
        }
    } else {
        Write-Verbose "Get-TribelooUser - No users found"
        return
    }
    # if (-not $Count -and -not $StartIndex) {
    # # paging is disabled, we don't do anything
    # } elseif (-not $LimitReached -and $BatchObjects.TotalResults -gt $BatchObjects.StartIndex + $Count) {
    # # lets get more users because there's more to get and user wanted more
    # $MaxResults = $MaxResults - $BatchObjects.Resources.Count
    # Write-Verbose "Get-TribelooUser - Processing more pages (StartIndex: $StartIndex, Count: $Count)."
    # $getFederatedDirectoryUserSplat = @{
    # Authorization = $Authorization
    # StartIndex = $($BatchObjects.StartIndex + $Count)
    # Count = $Count
    # MaxResults = $MaxResults
    # Filter = $Filter
    # SortBy = $SortBy
    # SortOrder = $SortOrder
    # Native = $Native
    # }
    # Remove-EmptyValue -Hashtable $getFederatedDirectoryUserSplat
    # Get-TribelooUser @getFederatedDirectoryUserSplat
    # }
}
function New-TribelooUser {
    [CmdletBinding(SupportsShouldProcess)]
    param(
        [parameter(Mandatory)][string] $UserName,
        [string] $FamilyName,
        [string] $GivenName,
        [parameter(Mandatory)][string] $DisplayName,
        [string] $NickName,
        [string] $EmailAddress,
        [string] $Formatted,
        [switch] $Active
    )
    if (-not $Script:AuthorizationTribeloo) {
        return
    }


    $Body = [ordered] @{
        schemas       = @(
            "urn:ietf:params:scim:schemas:core:2.0:User"
        )
        "userName"    = $UserName
        "name"        = [ordered] @{
            "familyName" = $FamilyName
            "givenName"  = $GivenName
        }
        "displayName" = $DisplayName
        "nickName"    = $NickName
        "emails"      = @(
            if ($EmailAddress) {
                @{
                    "value"   = $EmailAddress
                    "type"    = "work"
                    "primary" = $true
                }
            }
        )
        "addresses"   = @(
            if ($Formatted) {
                [ordered]@{
                    "formatted" = $Formatted
                    "type"      = "work"
                }
            }
        )
        "active"      = if ($PSBoundParameters.Keys -contains ('Active')) { $Active.IsPresent } else { $Null }
    }

    Try {
        Remove-EmptyValue -Hashtable $Body -Recursive -Rerun 2

        # for troubleshooting
        if ($VerbosePreference -eq 'Continue') {
            $Body | ConvertTo-Json -Depth 10 | Write-Verbose
        }

        if ($BulkProcessing) {
            # Return body is used for using Invoke-FederatedDirectory to add/set/remove users in bulk

            $ReturnObject = [ordered] @{
                data   = $Body
                method = 'POST'
                bulkId = $Body.userName
            }
            # for troubleshooting
            if ($VerbosePreference -eq 'Continue') {
                $ReturnObject | ConvertTo-Json -Depth 10 | Write-Verbose
            }
            return $ReturnObject
        }
        $Uri = Join-UriQuery -BaseUri $Script:AuthorizationTribeloo.BaseUri -RelativeOrAbsoluteUri 'Users'
        $invokeRestMethodSplat = [ordered] @{
            Method      = 'POST'
            Uri         = $Uri
            Headers     = [ordered]  @{
                'Content-Type'  = 'application/json; charset=utf-8'
                'Authorization' = $Script:AuthorizationTribeloo.Authorization
                'Cache-Control' = 'no-cache'
            }
            Body        = $Body | ConvertTo-Json -Depth 10
            ErrorAction = 'Stop'
            ContentType = 'application/json; charset=utf-8'
        }
        if ($DirectoryID) {
            $invokeRestMethodSplat['Headers']['directoryId'] = $DirectoryID
        }

        Write-Verbose -Message "Get-TribelooUser - Using query: $Uri"

        if ($PSCmdlet.ShouldProcess("username $UserName, displayname $DisplayName", "Adding user")) {
            $ReturnData = Invoke-RestMethod @invokeRestMethodSplat -Verbose:$false
            # don't return data as we trust it's been created
            if (-not $Suppress) {
                $ReturnData
            }
        }

        # # for troubleshooting
        # if ($VerbosePreference -eq 'Continue') {
        # $invokeRestMethodSplat.Remove('body')
        # $invokeRestMethodSplat | ConvertTo-Json -Depth 10 | Write-Verbose
        # }
    } catch {
        if ($PSBoundParameters.ErrorAction -eq 'Stop') {
            throw
        } else {
            $ErrorDetails = $_.ErrorDetails.Message | ConvertFrom-Json -ErrorAction SilentlyContinue
            if ($ErrorDetails.Detail -like '*already exists*directory*') {
                Write-Warning -Message "Add-TribelooUser - $($ErrorDetails.Detail) [UserName: $UserName / DisplayName: $DisplayName]"
            } else {
                Write-Warning -Message "Add-TribelooUser - Error $($_.Exception.Message), $($ErrorDetails.Detail)"
            }
        }

    }
}
function Remove-TribelooUser {
    [CmdletBinding(SupportsShouldProcess)]
    param(
        [parameter(Position = 0, ValueFromPipeline, Mandatory, ParameterSetName = 'User')][PSCustomObject[]] $User,
        [parameter(Mandatory, ParameterSetName = 'Id')][string[]] $Id,
        [parameter(Mandatory, ParameterSetName = 'UserName')][string[]] $SearchUserName,
        [switch] $Suppress,
        [parameter(ParameterSetName = 'All')][switch] $All
    )
    Begin {
        if (-not $Script:AuthorizationTribeloo) {
            if ($PSBoundParameters.ErrorAction -eq 'Stop') {
                throw "No authorization found. Please run 'Connect-Tribeloo' first."
            } else {
                Write-Warning -Message "Remove-TribelooUser - No authorization found. Please run 'Connect-Tribeloo' first."
                return
            }
            return
        }
    }
    Process {
        if ($All) {
            # lets simplify this for all
            $Users = Get-TribelooUser
            foreach ($U in $Users) {
                Remove-TribelooUser -Id $U.id
            }
        } else {
            if ($Id) {
                $RemoveID = $Id
            } elseif ($User) {
                $RemoveID = $User.Id
            } elseif ($SearchUserName) {
                $RemoveID = Foreach ($U in $SearchUserName) {
                (Get-TribelooUser -UserName $U).Id
                }
            } else {
                return
            }
            foreach ($I in $RemoveID) {
                Try {
                    if ($BulkProcessing) {
                        # Return body is used for using Invoke-FederatedDirectory to add/set/remove users in bulk
                        return [ordered] @{
                            data   = @{
                                schemas = @("urn:ietf:params:scim:schemas:core:2.0:User")
                                id      = $I
                            }
                            method = 'DELETE'
                            bulkId = $I
                        }
                    }
                    $Uri = Join-UriQuery -BaseUri $Script:AuthorizationTribeloo.BaseUri -RelativeOrAbsoluteUri "Users/$I"
                    $invokeRestMethodSplat = [ordered] @{
                        Method      = 'DELETE'
                        Uri         = $Uri
                        Headers     = [ordered]  @{
                            'Content-Type'  = 'application/json; charset=utf-8'
                            'Authorization' = $Script:AuthorizationTribeloo.Authorization
                            'Cache-Control' = 'no-cache'
                        }
                        ErrorAction = 'Stop'
                        ContentType = 'application/json; charset=utf-8'
                    }
                    Remove-EmptyValue -Hashtable $invokeRestMethodSplat -Recursive

                    if ($VerbosePreference -eq 'Continue') {
                        $invokeRestMethodSplat | ConvertTo-Json -Depth 10 | Write-Verbose
                    }
                    if ($PSCmdlet.ShouldProcess($I, "Removing user")) {
                        $ReturnData = Invoke-RestMethod @invokeRestMethodSplat
                        if (-not $Suppress) {
                            $ReturnData
                        }
                    }
                } catch {
                    $ErrorDetails = $_.ErrorDetails.Message | ConvertFrom-Json -ErrorAction SilentlyContinue
                    if ($ErrorDetails.Detail -like "*not found*") {
                        Write-Warning -Message "Remove-TribelooUser - $($ErrorDetails.Detail)."
                    } else {
                        Write-Warning -Message "Remove-TribelooUser - Error $($_.Exception.Message), $($ErrorDetails.Detail)"
                    }
                }
            }
        }
    }
}
function Set-TribelooUser {
    [CmdletBinding(SupportsShouldProcess)]
    param(
        [string] $SearchUserName,
        [string] $Id,
        [string] $UserName,
        [string] $FamilyName,
        [string] $GivenName,
        [string] $DisplayName,
        [string] $NickName,
        [string] $EmailAddress,
        [string] $Formatted,
        [bool] $Active,
        [switch] $Suppress,
        [ValidateSet('Overwrite', 'Update')][string] $Action = 'Update',
        [System.Collections.IDictionary] $ActionPerProperty = @{},
        [switch] $BulkProcessing
    )

    if (-not $Script:AuthorizationTribeloo) {
        if ($PSBoundParameters.ErrorAction -eq 'Stop') {
            throw "No authorization found. Please run 'Connect-Tribeloo' first."
        } else {
            Write-Warning -Message "Remove-TribelooUser - No authorization found. Please run 'Connect-Tribeloo' first."
            return
        }
        return
    }

    if ($Id) {
        $SetID = $Id
    } elseif ($User) {
        $SetID = $User.Id
    } elseif ($SearchUserName) {
        $SetID = Foreach ($U in $SearchUserName) {
                (Get-TribelooUser -UserName $U).Id
        }
    } else {
        Write-Warning -Message "Set-TribelooUser - No ID or UserName specified."
        return
    }
    if ($ManagerUserName) {
        $ManagerID = (Get-TribelooUser -UserName $ManagerUserName).Id
    }

    if ($Action -eq 'Update') {
        $TranslatePath = @{
            UserName     = "userName"
            FamilyName   = "name.familyName"
            GivenName    = 'name.givenName'
            DisplayName  = 'displayName'
            NickName     = 'nickName'
            EmailAddress = 'emails[type eq "work"].value'
            Active       = 'active'
            Formatted    = 'addresses[type eq "work"].formatted'
        }

        $Body = [ordered] @{
            schemas    = @(
                "urn:ietf:params:scim:api:messages:2.0:PatchOp"
            )
            Operations = @(
                foreach ($Key in $PSBoundParameters.Keys) {
                    if ($Key -in $TranslatePath.Keys) {
                        if ($TranslatePath[$Key]) {
                            $Path = $TranslatePath[$Key]
                        } else {
                            $Path = $Key
                        }
                        if ($null -ne $PSBoundParameters[$Key]) {
                            $Value = $PSBoundParameters[$Key]
                        } else {
                            $Value = $null
                        }
                        if ($ActionPerProperty) {
                            if ($ActionPerProperty[$Key]) {
                                $ActionProperty = $ActionPerProperty[$Key]
                            } else {
                                $ActionProperty = 'replace'
                            }
                        } else {
                            $ActionProperty = 'replace'
                        }
                        if ($null -ne $Value) {
                            [ordered] @{
                                op    = $ActionProperty
                                path  = $Path
                                value = $Value
                            }
                        }
                    }
                }
            )
        }
    } else {

        $Body = [ordered] @{
            schemas       = @(
                "urn:ietf:params:scim:schemas:core:2.0:User"
                "urn:ietf:params:scim:schemas:extension:enterprise:2.0:User"
                "urn:ietf:params:scim:schemas:extension:fd:2.0:User"
            )
            "id"          = $Id
            "userName"    = $UserName
            "name"        = [ordered] @{
                "familyName" = $FamilyName
                "givenName"  = $GivenName
            }
            "displayName" = $DisplayName
            "nickName"    = $NickName
            "emails"      = @(
                if ($EmailAddress) {
                    [ordered]@{
                        "value"   = $EmailAddress
                        "type"    = "work"
                        "primary" = $true
                    }
                }
            )
            "addresses"   = @(
                if ($Formatted) {
                    [ordered]@{
                        "formatted" = $Formatted
                        "type"      = "work"
                    }
                }
            )
            "active"      = if ($PSBoundParameters.Keys -contains ('Active')) { $Active } else { $Null }
        }
    }
    Try {
        Remove-EmptyValue -Hashtable $Body -Recursive -Rerun 3

        $MethodChosen = if ($Action -eq 'Update') { 'PATCH' } else { 'PUT' }
        if ($BulkProcessing) {
            # Return body is used for using Invoke-FederatedDirectory to add/set/remove users in bulk
            if ($Action -eq 'Update') {
                Write-Warning -Message "Bulk processing is not supported for Update action. Only Overwrite action is supported. Change action to Overwrite or don't use bulk processing for updates."
            } else {
                return [ordered] @{
                    data   = $Body
                    method = $MethodChosen
                    bulkId = $SetID
                }
            }
        }
        $Uri = Join-UriQuery -BaseUri $Script:AuthorizationTribeloo.BaseUri -RelativeOrAbsoluteUri "Users/$SetID"

        $invokeRestMethodSplat = [ordered] @{
            Method      = $MethodChosen
            Uri         = $Uri
            Headers     = [ordered]  @{
                'Content-Type'  = 'application/json'
                'Authorization' = $Script:AuthorizationTribeloo.Authorization
                'Cache-Control' = 'no-cache'
            }
            Body        = $Body | ConvertTo-Json -Depth 10
            ErrorAction = 'Stop'
            ContentType = 'application/json; charset=utf-8'
        }
        if ($DirectoryID) {
            $invokeRestMethodSplat['Headers']['directoryId'] = $DirectoryID
        }
        # for troubleshooting
        if ($VerbosePreference -eq 'Continue') {
            $Body | ConvertTo-Json -Depth 10 | Write-Verbose
        }
        if ($PSCmdlet.ShouldProcess($SetID, "Updating user using $Action method")) {
            $ReturnData = Invoke-RestMethod @invokeRestMethodSplat
            # don't return data as we trust it's been updated
            if (-not $Suppress) {
                $ReturnData
            }
        }
        # # for troubleshooting
        # if ($VerbosePreference -eq 'Continue') {
        # $invokeRestMethodSplat.Remove('body')
        # $invokeRestMethodSplat | ConvertTo-Json -Depth 10 | Write-Verbose
        # }
    } catch {
        if ($PSBoundParameters.ErrorAction -eq 'Stop') {
            throw
        } else {
            $ErrorDetails = $_.ErrorDetails.Message | ConvertFrom-Json -ErrorAction SilentlyContinue
            if ($ErrorDetails.Detail -like '*userName is mandatory*') {
                Write-Warning -Message "Set-TribelooUser - $($ErrorDetails.Detail) [Id: $SetID]"
            } else {
                Write-Warning -Message "Set-TribelooUser - Error $($_.Exception.Message), $($ErrorDetails.Detail) [Id: $SetID]"
            }
        }
    }

}



# Export functions and aliases as required
Export-ModuleMember -Function @('Connect-Tribeloo', 'Get-TribelooUser', 'New-TribelooUser', 'Remove-TribelooUser', 'Set-TribelooUser') -Alias @()
# SIG # Begin signature block
# MIInPgYJKoZIhvcNAQcCoIInLzCCJysCAQExDzANBglghkgBZQMEAgEFADB5Bgor
# BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG
# KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCCry1RACxvBhKO4
# 80JsMKBl0dxvEQpGFQnV7wLyKY58DqCCITcwggO3MIICn6ADAgECAhAM5+DlF9hG
# /o/lYPwb8DA5MA0GCSqGSIb3DQEBBQUAMGUxCzAJBgNVBAYTAlVTMRUwEwYDVQQK
# EwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xJDAiBgNV
# BAMTG0RpZ2lDZXJ0IEFzc3VyZWQgSUQgUm9vdCBDQTAeFw0wNjExMTAwMDAwMDBa
# Fw0zMTExMTAwMDAwMDBaMGUxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2Vy
# dCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xJDAiBgNVBAMTG0RpZ2lD
# ZXJ0IEFzc3VyZWQgSUQgUm9vdCBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC
# AQoCggEBAK0OFc7kQ4BcsYfzt2D5cRKlrtwmlIiq9M71IDkoWGAM+IDaqRWVMmE8
# tbEohIqK3J8KDIMXeo+QrIrneVNcMYQq9g+YMjZ2zN7dPKii72r7IfJSYd+fINcf
# 4rHZ/hhk0hJbX/lYGDW8R82hNvlrf9SwOD7BG8OMM9nYLxj+KA+zp4PWw25EwGE1
# lhb+WZyLdm3X8aJLDSv/C3LanmDQjpA1xnhVhyChz+VtCshJfDGYM2wi6YfQMlqi
# uhOCEe05F52ZOnKh5vqk2dUXMXWuhX0irj8BRob2KHnIsdrkVxfEfhwOsLSSplaz
# vbKX7aqn8LfFqD+VFtD/oZbrCF8Yd08CAwEAAaNjMGEwDgYDVR0PAQH/BAQDAgGG
# MA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFEXroq/0ksuCMS1Ri6enIZ3zbcgP
# MB8GA1UdIwQYMBaAFEXroq/0ksuCMS1Ri6enIZ3zbcgPMA0GCSqGSIb3DQEBBQUA
# A4IBAQCiDrzf4u3w43JzemSUv/dyZtgy5EJ1Yq6H6/LV2d5Ws5/MzhQouQ2XYFwS
# TFjk0z2DSUVYlzVpGqhH6lbGeasS2GeBhN9/CTyU5rgmLCC9PbMoifdf/yLil4Qf
# 6WXvh+DfwWdJs13rsgkq6ybteL59PyvztyY1bV+JAbZJW58BBZurPSXBzLZ/wvFv
# hsb6ZGjrgS2U60K3+owe3WLxvlBnt2y98/Efaww2BxZ/N3ypW2168RJGYIPXJwS+
# S86XvsNnKmgR34DnDDNmvxMNFG7zfx9jEB76jRslbWyPpbdhAbHSoyahEHGdreLD
# +cOZUbcrBwjOLuZQsqf6CkUvovDyMIIFMDCCBBigAwIBAgIQBAkYG1/Vu2Z1U0O1
# b5VQCDANBgkqhkiG9w0BAQsFADBlMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGln
# aUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMSQwIgYDVQQDExtE
# aWdpQ2VydCBBc3N1cmVkIElEIFJvb3QgQ0EwHhcNMTMxMDIyMTIwMDAwWhcNMjgx
# MDIyMTIwMDAwWjByMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5j
# MRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMTEwLwYDVQQDEyhEaWdpQ2VydCBT
# SEEyIEFzc3VyZWQgSUQgQ29kZSBTaWduaW5nIENBMIIBIjANBgkqhkiG9w0BAQEF
# AAOCAQ8AMIIBCgKCAQEA+NOzHH8OEa9ndwfTCzFJGc/Q+0WZsTrbRPV/5aid2zLX
# cep2nQUut4/6kkPApfmJ1DcZ17aq8JyGpdglrA55KDp+6dFn08b7KSfH03sjlOSR
# I5aQd4L5oYQjZhJUM1B0sSgmuyRpwsJS8hRniolF1C2ho+mILCCVrhxKhwjfDPXi
# TWAYvqrEsq5wMWYzcT6scKKrzn/pfMuSoeU7MRzP6vIK5Fe7SrXpdOYr/mzLfnQ5
# Ng2Q7+S1TqSp6moKq4TzrGdOtcT3jNEgJSPrCGQ+UpbB8g8S9MWOD8Gi6CxR93O8
# vYWxYoNzQYIH5DiLanMg0A9kczyen6Yzqf0Z3yWT0QIDAQABo4IBzTCCAckwEgYD
# VR0TAQH/BAgwBgEB/wIBADAOBgNVHQ8BAf8EBAMCAYYwEwYDVR0lBAwwCgYIKwYB
# BQUHAwMweQYIKwYBBQUHAQEEbTBrMCQGCCsGAQUFBzABhhhodHRwOi8vb2NzcC5k
# aWdpY2VydC5jb20wQwYIKwYBBQUHMAKGN2h0dHA6Ly9jYWNlcnRzLmRpZ2ljZXJ0
# LmNvbS9EaWdpQ2VydEFzc3VyZWRJRFJvb3RDQS5jcnQwgYEGA1UdHwR6MHgwOqA4
# oDaGNGh0dHA6Ly9jcmw0LmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydEFzc3VyZWRJRFJv
# b3RDQS5jcmwwOqA4oDaGNGh0dHA6Ly9jcmwzLmRpZ2ljZXJ0LmNvbS9EaWdpQ2Vy
# dEFzc3VyZWRJRFJvb3RDQS5jcmwwTwYDVR0gBEgwRjA4BgpghkgBhv1sAAIEMCow
# KAYIKwYBBQUHAgEWHGh0dHBzOi8vd3d3LmRpZ2ljZXJ0LmNvbS9DUFMwCgYIYIZI
# AYb9bAMwHQYDVR0OBBYEFFrEuXsqCqOl6nEDwGD5LfZldQ5YMB8GA1UdIwQYMBaA
# FEXroq/0ksuCMS1Ri6enIZ3zbcgPMA0GCSqGSIb3DQEBCwUAA4IBAQA+7A1aJLPz
# ItEVyCx8JSl2qB1dHC06GsTvMGHXfgtg/cM9D8Svi/3vKt8gVTew4fbRknUPUbRu
# pY5a4l4kgU4QpO4/cY5jDhNLrddfRHnzNhQGivecRk5c/5CxGwcOkRX7uq+1UcKN
# JK4kxscnKqEpKBo6cSgCPC6Ro8AlEeKcFEehemhor5unXCBc2XGxDI+7qPjFEmif
# z0DLQESlE/DmZAwlCEIysjaKJAL+L3J+HNdJRZboWR3p+nRka7LrZkPas7CM1ekN
# 3fYBIM6ZMWM9CBoYs4GbT8aTEAb8B4H6i9r5gkn3Ym6hU/oSlBiFLpKR6mhsRDKy
# ZqHnGKSaZFHvMIIFPTCCBCWgAwIBAgIQBNXcH0jqydhSALrNmpsqpzANBgkqhkiG
# 9w0BAQsFADByMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkw
# FwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMTEwLwYDVQQDEyhEaWdpQ2VydCBTSEEy
# IEFzc3VyZWQgSUQgQ29kZSBTaWduaW5nIENBMB4XDTIwMDYyNjAwMDAwMFoXDTIz
# MDcwNzEyMDAwMFowejELMAkGA1UEBhMCUEwxEjAQBgNVBAgMCcWabMSFc2tpZTER
# MA8GA1UEBxMIS2F0b3dpY2UxITAfBgNVBAoMGFByemVteXPFgmF3IEvFgnlzIEVW
# T1RFQzEhMB8GA1UEAwwYUHJ6ZW15c8WCYXcgS8WCeXMgRVZPVEVDMIIBIjANBgkq
# hkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAv7KB3iyBrhkLUbbFe9qxhKKPBYqDBqln
# r3AtpZplkiVjpi9dMZCchSeT5ODsShPuZCIxJp5I86uf8ibo3vi2S9F9AlfFjVye
# 3dTz/9TmCuGH8JQt13ozf9niHecwKrstDVhVprgxi5v0XxY51c7zgMA2g1Ub+3ti
# i0vi/OpmKXdL2keNqJ2neQ5cYly/GsI8CREUEq9SZijbdA8VrRF3SoDdsWGf3tZZ
# zO6nWn3TLYKQ5/bw5U445u/V80QSoykszHRivTj+H4s8ABiforhi0i76beA6Ea41
# zcH4zJuAp48B4UhjgRDNuq8IzLWK4dlvqrqCBHKqsnrF6BmBrv+BXQIDAQABo4IB
# xTCCAcEwHwYDVR0jBBgwFoAUWsS5eyoKo6XqcQPAYPkt9mV1DlgwHQYDVR0OBBYE
# FBixNSfoHFAgJk4JkDQLFLRNlJRmMA4GA1UdDwEB/wQEAwIHgDATBgNVHSUEDDAK
# BggrBgEFBQcDAzB3BgNVHR8EcDBuMDWgM6Axhi9odHRwOi8vY3JsMy5kaWdpY2Vy
# dC5jb20vc2hhMi1hc3N1cmVkLWNzLWcxLmNybDA1oDOgMYYvaHR0cDovL2NybDQu
# ZGlnaWNlcnQuY29tL3NoYTItYXNzdXJlZC1jcy1nMS5jcmwwTAYDVR0gBEUwQzA3
# BglghkgBhv1sAwEwKjAoBggrBgEFBQcCARYcaHR0cHM6Ly93d3cuZGlnaWNlcnQu
# Y29tL0NQUzAIBgZngQwBBAEwgYQGCCsGAQUFBwEBBHgwdjAkBggrBgEFBQcwAYYY
# aHR0cDovL29jc3AuZGlnaWNlcnQuY29tME4GCCsGAQUFBzAChkJodHRwOi8vY2Fj
# ZXJ0cy5kaWdpY2VydC5jb20vRGlnaUNlcnRTSEEyQXNzdXJlZElEQ29kZVNpZ25p
# bmdDQS5jcnQwDAYDVR0TAQH/BAIwADANBgkqhkiG9w0BAQsFAAOCAQEAmr1sz4ls
# LARi4wG1eg0B8fVJFowtect7SnJUrp6XRnUG0/GI1wXiLIeow1UPiI6uDMsRXPHU
# F/+xjJw8SfIbwava2eXu7UoZKNh6dfgshcJmo0QNAJ5PIyy02/3fXjbUREHINrTC
# vPVbPmV6kx4Kpd7KJrCo7ED18H/XTqWJHXa8va3MYLrbJetXpaEPpb6zk+l8Rj9y
# G4jBVRhenUBUUj3CLaWDSBpOA/+sx8/XB9W9opYfYGb+1TmbCkhUg7TB3gD6o6ES
# Jre+fcnZnPVAPESmstwsT17caZ0bn7zETKlNHbc1q+Em9kyBjaQRcEQoQQNpezQu
# g9ufqExx6lHYDjCCBY0wggR1oAMCAQICEA6bGI750C3n79tQ4ghAGFowDQYJKoZI
# hvcNAQEMBQAwZTELMAkGA1UEBhMCVVMxFTATBgNVBAoTDERpZ2lDZXJ0IEluYzEZ
# MBcGA1UECxMQd3d3LmRpZ2ljZXJ0LmNvbTEkMCIGA1UEAxMbRGlnaUNlcnQgQXNz
# dXJlZCBJRCBSb290IENBMB4XDTIyMDgwMTAwMDAwMFoXDTMxMTEwOTIzNTk1OVow
# YjELMAkGA1UEBhMCVVMxFTATBgNVBAoTDERpZ2lDZXJ0IEluYzEZMBcGA1UECxMQ
# d3d3LmRpZ2ljZXJ0LmNvbTEhMB8GA1UEAxMYRGlnaUNlcnQgVHJ1c3RlZCBSb290
# IEc0MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAv+aQc2jeu+RdSjww
# IjBpM+zCpyUuySE98orYWcLhKac9WKt2ms2uexuEDcQwH/MbpDgW61bGl20dq7J5
# 8soR0uRf1gU8Ug9SH8aeFaV+vp+pVxZZVXKvaJNwwrK6dZlqczKU0RBEEC7fgvMH
# hOZ0O21x4i0MG+4g1ckgHWMpLc7sXk7Ik/ghYZs06wXGXuxbGrzryc/NrDRAX7F6
# Zu53yEioZldXn1RYjgwrt0+nMNlW7sp7XeOtyU9e5TXnMcvak17cjo+A2raRmECQ
# ecN4x7axxLVqGDgDEI3Y1DekLgV9iPWCPhCRcKtVgkEy19sEcypukQF8IUzUvK4b
# A3VdeGbZOjFEmjNAvwjXWkmkwuapoGfdpCe8oU85tRFYF/ckXEaPZPfBaYh2mHY9
# WV1CdoeJl2l6SPDgohIbZpp0yt5LHucOY67m1O+SkjqePdwA5EUlibaaRBkrfsCU
# tNJhbesz2cXfSwQAzH0clcOP9yGyshG3u3/y1YxwLEFgqrFjGESVGnZifvaAsPvo
# ZKYz0YkH4b235kOkGLimdwHhD5QMIR2yVCkliWzlDlJRR3S+Jqy2QXXeeqxfjT/J
# vNNBERJb5RBQ6zHFynIWIgnffEx1P2PsIV/EIFFrb7GrhotPwtZFX50g/KEexcCP
# orF+CiaZ9eRpL5gdLfXZqbId5RsCAwEAAaOCATowggE2MA8GA1UdEwEB/wQFMAMB
# Af8wHQYDVR0OBBYEFOzX44LScV1kTN8uZz/nupiuHA9PMB8GA1UdIwQYMBaAFEXr
# oq/0ksuCMS1Ri6enIZ3zbcgPMA4GA1UdDwEB/wQEAwIBhjB5BggrBgEFBQcBAQRt
# MGswJAYIKwYBBQUHMAGGGGh0dHA6Ly9vY3NwLmRpZ2ljZXJ0LmNvbTBDBggrBgEF
# BQcwAoY3aHR0cDovL2NhY2VydHMuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0QXNzdXJl
# ZElEUm9vdENBLmNydDBFBgNVHR8EPjA8MDqgOKA2hjRodHRwOi8vY3JsMy5kaWdp
# Y2VydC5jb20vRGlnaUNlcnRBc3N1cmVkSURSb290Q0EuY3JsMBEGA1UdIAQKMAgw
# BgYEVR0gADANBgkqhkiG9w0BAQwFAAOCAQEAcKC/Q1xV5zhfoKN0Gz22Ftf3v1cH
# vZqsoYcs7IVeqRq7IviHGmlUIu2kiHdtvRoU9BNKei8ttzjv9P+Aufih9/Jy3iS8
# UgPITtAq3votVs/59PesMHqai7Je1M/RQ0SbQyHrlnKhSLSZy51PpwYDE3cnRNTn
# f+hZqPC/Lwum6fI0POz3A8eHqNJMQBk1RmppVLC4oVaO7KTVPeix3P0c2PR3WlxU
# jG/voVA9/HYJaISfb8rbII01YBwCA8sgsKxYoA5AY8WYIsGyWfVVa88nq2x2zm8j
# LfR+cWojayL/ErhULSd+2DrZ8LaHlv1b0VysGMNNn3O3AamfV6peKOK5lDCCBq4w
# ggSWoAMCAQICEAc2N7ckVHzYR6z9KGYqXlswDQYJKoZIhvcNAQELBQAwYjELMAkG
# A1UEBhMCVVMxFTATBgNVBAoTDERpZ2lDZXJ0IEluYzEZMBcGA1UECxMQd3d3LmRp
# Z2ljZXJ0LmNvbTEhMB8GA1UEAxMYRGlnaUNlcnQgVHJ1c3RlZCBSb290IEc0MB4X
# DTIyMDMyMzAwMDAwMFoXDTM3MDMyMjIzNTk1OVowYzELMAkGA1UEBhMCVVMxFzAV
# BgNVBAoTDkRpZ2lDZXJ0LCBJbmMuMTswOQYDVQQDEzJEaWdpQ2VydCBUcnVzdGVk
# IEc0IFJTQTQwOTYgU0hBMjU2IFRpbWVTdGFtcGluZyBDQTCCAiIwDQYJKoZIhvcN
# AQEBBQADggIPADCCAgoCggIBAMaGNQZJs8E9cklRVcclA8TykTepl1Gh1tKD0Z5M
# om2gsMyD+Vr2EaFEFUJfpIjzaPp985yJC3+dH54PMx9QEwsmc5Zt+FeoAn39Q7SE
# 2hHxc7Gz7iuAhIoiGN/r2j3EF3+rGSs+QtxnjupRPfDWVtTnKC3r07G1decfBmWN
# lCnT2exp39mQh0YAe9tEQYncfGpXevA3eZ9drMvohGS0UvJ2R/dhgxndX7RUCyFo
# bjchu0CsX7LeSn3O9TkSZ+8OpWNs5KbFHc02DVzV5huowWR0QKfAcsW6Th+xtVhN
# ef7Xj3OTrCw54qVI1vCwMROpVymWJy71h6aPTnYVVSZwmCZ/oBpHIEPjQ2OAe3Vu
# JyWQmDo4EbP29p7mO1vsgd4iFNmCKseSv6De4z6ic/rnH1pslPJSlRErWHRAKKtz
# Q87fSqEcazjFKfPKqpZzQmiftkaznTqj1QPgv/CiPMpC3BhIfxQ0z9JMq++bPf4O
# uGQq+nUoJEHtQr8FnGZJUlD0UfM2SU2LINIsVzV5K6jzRWC8I41Y99xh3pP+OcD5
# sjClTNfpmEpYPtMDiP6zj9NeS3YSUZPJjAw7W4oiqMEmCPkUEBIDfV8ju2TjY+Cm
# 4T72wnSyPx4JduyrXUZ14mCjWAkBKAAOhFTuzuldyF4wEr1GnrXTdrnSDmuZDNIz
# tM2xAgMBAAGjggFdMIIBWTASBgNVHRMBAf8ECDAGAQH/AgEAMB0GA1UdDgQWBBS6
# FtltTYUvcyl2mi91jGogj57IbzAfBgNVHSMEGDAWgBTs1+OC0nFdZEzfLmc/57qY
# rhwPTzAOBgNVHQ8BAf8EBAMCAYYwEwYDVR0lBAwwCgYIKwYBBQUHAwgwdwYIKwYB
# BQUHAQEEazBpMCQGCCsGAQUFBzABhhhodHRwOi8vb2NzcC5kaWdpY2VydC5jb20w
# QQYIKwYBBQUHMAKGNWh0dHA6Ly9jYWNlcnRzLmRpZ2ljZXJ0LmNvbS9EaWdpQ2Vy
# dFRydXN0ZWRSb290RzQuY3J0MEMGA1UdHwQ8MDowOKA2oDSGMmh0dHA6Ly9jcmwz
# LmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydFRydXN0ZWRSb290RzQuY3JsMCAGA1UdIAQZ
# MBcwCAYGZ4EMAQQCMAsGCWCGSAGG/WwHATANBgkqhkiG9w0BAQsFAAOCAgEAfVmO
# wJO2b5ipRCIBfmbW2CFC4bAYLhBNE88wU86/GPvHUF3iSyn7cIoNqilp/GnBzx0H
# 6T5gyNgL5Vxb122H+oQgJTQxZ822EpZvxFBMYh0MCIKoFr2pVs8Vc40BIiXOlWk/
# R3f7cnQU1/+rT4osequFzUNf7WC2qk+RZp4snuCKrOX9jLxkJodskr2dfNBwCnzv
# qLx1T7pa96kQsl3p/yhUifDVinF2ZdrM8HKjI/rAJ4JErpknG6skHibBt94q6/ae
# sXmZgaNWhqsKRcnfxI2g55j7+6adcq/Ex8HBanHZxhOACcS2n82HhyS7T6NJuXdm
# kfFynOlLAlKnN36TU6w7HQhJD5TNOXrd/yVjmScsPT9rp/Fmw0HNT7ZAmyEhQNC3
# EyTN3B14OuSereU0cZLXJmvkOHOrpgFPvT87eK1MrfvElXvtCl8zOYdBeHo46Zzh
# 3SP9HSjTx/no8Zhf+yvYfvJGnXUsHicsJttvFXseGYs2uJPU5vIXmVnKcPA3v5gA
# 3yAWTyf7YGcWoWa63VXAOimGsJigK+2VQbc61RWYMbRiCQ8KvYHZE/6/pNHzV9m8
# BPqC3jLfBInwAM1dwvnQI38AC+R2AibZ8GV2QqYphwlHK+Z/GqSFD/yYlvZVVCsf
# gPrA8g4r5db7qS9EFUrnEw4d2zc4GqEr9u3WfPwwggbAMIIEqKADAgECAhAMTWly
# S5T6PCpKPSkHgD1aMA0GCSqGSIb3DQEBCwUAMGMxCzAJBgNVBAYTAlVTMRcwFQYD
# VQQKEw5EaWdpQ2VydCwgSW5jLjE7MDkGA1UEAxMyRGlnaUNlcnQgVHJ1c3RlZCBH
# NCBSU0E0MDk2IFNIQTI1NiBUaW1lU3RhbXBpbmcgQ0EwHhcNMjIwOTIxMDAwMDAw
# WhcNMzMxMTIxMjM1OTU5WjBGMQswCQYDVQQGEwJVUzERMA8GA1UEChMIRGlnaUNl
# cnQxJDAiBgNVBAMTG0RpZ2lDZXJ0IFRpbWVzdGFtcCAyMDIyIC0gMjCCAiIwDQYJ
# KoZIhvcNAQEBBQADggIPADCCAgoCggIBAM/spSY6xqnya7uNwQ2a26HoFIV0Mxom
# rNAcVR4eNm28klUMYfSdCXc9FZYIL2tkpP0GgxbXkZI4HDEClvtysZc6Va8z7GGK
# 6aYo25BjXL2JU+A6LYyHQq4mpOS7eHi5ehbhVsbAumRTuyoW51BIu4hpDIjG8b7g
# L307scpTjUCDHufLckkoHkyAHoVW54Xt8mG8qjoHffarbuVm3eJc9S/tjdRNlYRo
# 44DLannR0hCRRinrPibytIzNTLlmyLuqUDgN5YyUXRlav/V7QG5vFqianJVHhoV5
# PgxeZowaCiS+nKrSnLb3T254xCg/oxwPUAY3ugjZNaa1Htp4WB056PhMkRCWfk3h
# 3cKtpX74LRsf7CtGGKMZ9jn39cFPcS6JAxGiS7uYv/pP5Hs27wZE5FX/NurlfDHn
# 88JSxOYWe1p+pSVz28BqmSEtY+VZ9U0vkB8nt9KrFOU4ZodRCGv7U0M50GT6Vs/g
# 9ArmFG1keLuY/ZTDcyHzL8IuINeBrNPxB9ThvdldS24xlCmL5kGkZZTAWOXlLimQ
# prdhZPrZIGwYUWC6poEPCSVT8b876asHDmoHOWIZydaFfxPZjXnPYsXs4Xu5zGcT
# B5rBeO3GiMiwbjJ5xwtZg43G7vUsfHuOy2SJ8bHEuOdTXl9V0n0ZKVkDTvpd6kVz
# HIR+187i1Dp3AgMBAAGjggGLMIIBhzAOBgNVHQ8BAf8EBAMCB4AwDAYDVR0TAQH/
# BAIwADAWBgNVHSUBAf8EDDAKBggrBgEFBQcDCDAgBgNVHSAEGTAXMAgGBmeBDAEE
# AjALBglghkgBhv1sBwEwHwYDVR0jBBgwFoAUuhbZbU2FL3MpdpovdYxqII+eyG8w
# HQYDVR0OBBYEFGKK3tBh/I8xFO2XC809KpQU31KcMFoGA1UdHwRTMFEwT6BNoEuG
# SWh0dHA6Ly9jcmwzLmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydFRydXN0ZWRHNFJTQTQw
# OTZTSEEyNTZUaW1lU3RhbXBpbmdDQS5jcmwwgZAGCCsGAQUFBwEBBIGDMIGAMCQG
# CCsGAQUFBzABhhhodHRwOi8vb2NzcC5kaWdpY2VydC5jb20wWAYIKwYBBQUHMAKG
# TGh0dHA6Ly9jYWNlcnRzLmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydFRydXN0ZWRHNFJT
# QTQwOTZTSEEyNTZUaW1lU3RhbXBpbmdDQS5jcnQwDQYJKoZIhvcNAQELBQADggIB
# AFWqKhrzRvN4Vzcw/HXjT9aFI/H8+ZU5myXm93KKmMN31GT8Ffs2wklRLHiIY1UJ
# RjkA/GnUypsp+6M/wMkAmxMdsJiJ3HjyzXyFzVOdr2LiYWajFCpFh0qYQitQ/Bu1
# nggwCfrkLdcJiXn5CeaIzn0buGqim8FTYAnoo7id160fHLjsmEHw9g6A++T/350Q
# p+sAul9Kjxo6UrTqvwlJFTU2WZoPVNKyG39+XgmtdlSKdG3K0gVnK3br/5iyJpU4
# GYhEFOUKWaJr5yI+RCHSPxzAm+18SLLYkgyRTzxmlK9dAlPrnuKe5NMfhgFknADC
# 6Vp0dQ094XmIvxwBl8kZI4DXNlpflhaxYwzGRkA7zl011Fk+Q5oYrsPJy8P7mxNf
# arXH4PMFw1nfJ2Ir3kHJU7n/NBBn9iYymHv+XEKUgZSCnawKi8ZLFUrTmJBFYDOA
# 4CPe+AOk9kVH5c64A0JH6EE2cXet/aLol3ROLtoeHYxayB6a1cLwxiKoT5u92Bya
# UcQvmvZfpyeXupYuhVfAYOd4Vn9q78KVmksRAsiCnMkaBXy6cbVOepls9Oie1FqY
# yJ+/jbsYXEP10Cro4mLueATbvdH7WwqocH7wl4R44wgDXUcsY6glOJcB0j862uXl
# 9uab3H4szP8XTE0AotjWAQ64i+7m4HJViSwnGWH2dwGMMYIFXTCCBVkCAQEwgYYw
# cjELMAkGA1UEBhMCVVMxFTATBgNVBAoTDERpZ2lDZXJ0IEluYzEZMBcGA1UECxMQ
# d3d3LmRpZ2ljZXJ0LmNvbTExMC8GA1UEAxMoRGlnaUNlcnQgU0hBMiBBc3N1cmVk
# IElEIENvZGUgU2lnbmluZyBDQQIQBNXcH0jqydhSALrNmpsqpzANBglghkgBZQME
# AgEFAKCBhDAYBgorBgEEAYI3AgEMMQowCKACgAChAoAAMBkGCSqGSIb3DQEJAzEM
# BgorBgEEAYI3AgEEMBwGCisGAQQBgjcCAQsxDjAMBgorBgEEAYI3AgEVMC8GCSqG
# SIb3DQEJBDEiBCAK+xcB08DZPPjCFVs51ma/Lu5xa7crnh3hjXjeVwIwZzANBgkq
# hkiG9w0BAQEFAASCAQCEjm6czdyF7YilG/TZ2b/DYjlppbwTObld/Lk7GvJX2LXZ
# fG3xvhDQknqdi3GT39zQJKuAoHKu5JELYtIYGqHbRXrMIe3XVRM2fLQ8Xx7VutAO
# kswbpO+u9cPmRPLms17PJhbTNBYmdiY5KwmGdd97mepJSlgCXxrNvMv/cy0YbQ1r
# 9hfJhoDsBDW6QhX5Ej0LgP6DGxQ6JfWdlwN+WexcgnVJmeN2BuNUnFYHr9UIXkBW
# KLPTijWx88H0bHuJIRGBPmjvDMfC5KkmW/IgPrvLF/OwLAsXXHxgLICZyjz5WN3k
# l+UuIWjFi++lE8cHqoCH6Hhg3xAg6YP8UhvPcEHVoYIDIDCCAxwGCSqGSIb3DQEJ
# BjGCAw0wggMJAgEBMHcwYzELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDkRpZ2lDZXJ0
# LCBJbmMuMTswOQYDVQQDEzJEaWdpQ2VydCBUcnVzdGVkIEc0IFJTQTQwOTYgU0hB
# MjU2IFRpbWVTdGFtcGluZyBDQQIQDE1pckuU+jwqSj0pB4A9WjANBglghkgBZQME
# AgEFAKBpMBgGCSqGSIb3DQEJAzELBgkqhkiG9w0BBwEwHAYJKoZIhvcNAQkFMQ8X
# DTIzMDMwODE2NTQxNVowLwYJKoZIhvcNAQkEMSIEIFBFYrZjyqQdgW6x1tH+uiS2
# SQF4c8P9nSePsHA4PuuBMA0GCSqGSIb3DQEBAQUABIICAKCSBvXod/4MCZyPBE+F
# HErRK2gfmk8bWhq6pxCdwMEmwSxeOBjiPZ8t5/9fIe/eJMAk1PH4ca3e6eTXGEhH
# 0CEvTXpcei6TISBWCcFmUMz/SsOPX7FIXYcpKtrXE7P6YvizpSNnOYAonqitzdQ6
# 2qtc1Lqkq7vnFzSIapA2UFsrf9QUFLoDiKC/E2I4J35PQGvR2cUfD498cLuGrfUZ
# 1wMKUa7sxLqK/uxyHLtxhjKLQhC2C36nEmPzWEEzvcreYNkbjTHJ7CBAH9u46bg/
# kHz69ZFosB8fqih6mAhiLys0oPwm4ii6hVeP7hkW1AMLljp3zeKWuFKzHzCkxDoJ
# ef2ftOdbEo3HFNkDUPT1T10+sRtcrMx/WRXJsRh+N/APudhcAEDbFr4JHhEhTrfu
# 4Z/gc1IBTtGVWLweumdrZUmxP26ct6e9TxO01HCATvUqpfcn6TbJvY0CHdRnaMA9
# A2tmTQxkh7lz4xogN/tfnzmThNiVSlQBWeKHDmDGvicFS8s0t/jZ6k18rTtK/MMV
# l2czL9ioqNRS+MBbU2sarp0bhihXo9T0eTe2on8i3EyEr6Y8gf7xmRZu+a7N0FUU
# IZCNpLxbjE8xkzLS55PHJfKqoOmW62bn1rskiRDJ41LscD9UQbRXJ/RGcdacow2L
# ersMEhVWF/rdHWaVsaWr8oGK
# SIG # End signature block