Public/SEPPmailAPI-Group.ps1

<#
.SYNOPSIS
    Lists SEPPmail groups.
 
.DESCRIPTION
    Retrieves a list of groups from the SEPPmail appliance via a GET request to the
    'group' API endpoint (list view). The result can optionally be limited in number
    or filtered to groups that contain a specific member e-mail address.
 
.PARAMETER limit
    Maximum number of groups to return. Valid range is 0-20000, where 0 (default)
    means no limit.
 
.PARAMETER email
    Filter the result to groups that contain the specified member e-mail address.
    Accepts pipeline input (by value and by property name).
 
.PARAMETER SMAHost
    SEPPmail API hostname. Defaults to the configured value.
 
.PARAMETER SMAPort
    SEPPmail API port. Defaults to the configured value.
 
.PARAMETER SMAVersion
    SEPPmail API version. Defaults to the configured value.
 
.PARAMETER SMACred
    API credentials (PSCredential). Defaults to the configured value.
 
.PARAMETER SMASkipCertCheck
    Skip SSL certificate validation. Use only in test environments.
 
.OUTPUTS
    System.Management.Automation.PSCustomObject
        Returns the list of group names matching the query.
 
.NOTES
    - Requires an active SEPPmail API session (New-SMAConfiguration).
    - Aliased as Find-SMAGR.
 
.LINK
    Get-SMAGroup
 
.LINK
    New-SMAGroup
 
.LINK
    Set-SMAGroup
 
.LINK
    Remove-SMAGroup
 
.EXAMPLE
    PS C:\> Find-SMAGroup
    Lists all groups.
 
.EXAMPLE
    PS C:\> Find-SMAGroup -limit 10
    Lists the first 10 groups.
 
.EXAMPLE
    PS C:\> Find-SMAGroup -email 'admin@contoso.com'
    Lists all groups that contain 'admin@contoso.com' as a member.
 
.EXAMPLE
    PS C:\> Find-SMAGroup | ForEach-Object { Get-SMAGroup -name $_ }
    Lists all group names and retrieves the detailed object for each one.
#>

function Find-SMAGroup
{
    [CmdletBinding()]
    param (
        #region REST-API path and query parameters
        [Parameter(
            Mandatory                       = $false,
            HelpMessage                     = 'Limit output to maximum number of entries'
            )]
        [ValidateRange(0, 20000)]
        [int]$limit = 0,

        [Parameter( 
            Mandatory                       = $false,
            ValueFromPipeline               = $true,
            ValueFromPipelineByPropertyName = $true,
            Position                        = 0,
            HelpMessage                     = 'filter by the members e-mail adresses'
            )]
        [string]$email,

        #endregion

        #region SMA host parameters
        [Parameter(
            Mandatory = $false
            )]
        [String]$SMAHost = $Script:activeCfg.SMAHost,

        [Parameter(
            Mandatory = $false
            )]
        [int]$SMAPort = $Script:activeCfg.SMAPort,

        [Parameter(
            Mandatory = $false
            )]
        [String]$SMAVersion = $Script:activeCfg.SMAPIVersion,

        [Parameter(
            Mandatory=$false
            )]
            [System.Management.Automation.PSCredential]$SMACred=$Script:activeCfg.SMACred,

        [Parameter(
            Mandatory=$false
            )]
        [switch]$SMASkipCertCheck=$Script:activeCfg.SMAskipCertCheck
        #endregion
    )

    begin {
        if (! (verifyVars -VarList $Script:requiredVarList))
        {
            Throw($missingVarsMessage);
        }; # end if
        $smaParams = @{
            Host    = $SMAHost;
            Port    = $SMAPort;
            Version = $SMAVersion;
        }

    }
    process {

        Write-Verbose "Creating URL path"
        $uriPath = "{0}" -f 'group'

        Write-Verbose "Building full request uri"
        $boundParam = @{
            limit = $limit  
            list = $true # Hardcode list entry
        }
        if ($PSBoundParameters.ContainsKey('email')) {$boundParam.Add('email', $email) }
        $uri = New-SMAQueryString -uriPath $uriPath -qParam $boundParam @smaParams
        
        Write-verbose "Crafting Invokeparam for Invoke-SMARestMethod"
        $invokeParam = @{
            Uri         = $uri 
            Method      = 'GET'
            Cred        =  $SMACred
            SkipCertCheck = $SMASkipCertCheck
        }

        Write-Verbose "Call Invoke-SMARestMethod $uri" 
        $groupRaw = Invoke-SMARestMethod @invokeParam

        #Write-Verbose 'Filter data and return as PSObject'
        #$getGroup = $groupRaw.Psobject.properties.value

        Write-Verbose 'Converting Umlauts from ISO-8859-1'
        $group = ConvertFrom-SMAPIFormat -inputObject $groupRaw

        # Userobject
        if ($group) {
            return $group
        }
        else {
            Write-Information 'Nothing to return'
        }
    
    }
    end {
    }
}

<#
.SYNOPSIS
    Creates a new SEPPmail group.
 
.DESCRIPTION
    Creates a new group on the SEPPmail appliance via a POST request to the 'group'
    API endpoint. The group name, a description and at least one member must be
    supplied. On success the name of the newly created group is returned.
 
.PARAMETER name
    The full name of the new group. Mandatory. Accepts pipeline input (by value and
    by property name).
 
.PARAMETER description
    A descriptive text for the group. Mandatory. Accepts pipeline input by property name.
 
.PARAMETER members
    Array of member e-mail addresses to assign to the group. Mandatory. Accepts
    pipeline input by property name.
 
.PARAMETER SMAHost
    SEPPmail API hostname. Defaults to the configured value.
 
.PARAMETER SMAPort
    SEPPmail API port. Defaults to the configured value.
 
.PARAMETER SMAVersion
    SEPPmail API version. Defaults to the configured value.
 
.PARAMETER SMACred
    API credentials (PSCredential). Defaults to the configured value.
 
.PARAMETER SMASkipCertCheck
    Skip SSL certificate validation. Use only in test environments.
 
.OUTPUTS
    System.Management.Automation.PSCustomObject
        Returns the name of the newly created group.
 
.NOTES
    - Requires an active SEPPmail API session (New-SMAConfiguration).
    - Supports -WhatIf and -Confirm.
    - Aliased as New-SMAGR.
 
.LINK
    Get-SMAGroup
 
.LINK
    Find-SMAGroup
 
.LINK
    Set-SMAGroup
 
.LINK
    Remove-SMAGroup
 
.LINK
    Add-SMAGroupMember
 
.EXAMPLE
    PS C:\> New-SMAGroup -name 'Admins' -description 'IT administrators' -members 'admin1@contoso.com','admin2@contoso.com'
    Creates a new group with two members.
 
.EXAMPLE
    PS C:\> $groupInfo = @{
        name = 'Sales-EU'
        description = 'European sales team'
        members = @('john.doe@contoso.com', 'jane.smith@contoso.com')
    }
    PS C:\> New-SMAGroup @groupInfo
    Creates a group using parameter splatting.
 
.EXAMPLE
    PS C:\> New-SMAGroup -name 'TestGroup' -description 'Test only' -members 'test@contoso.com' -WhatIf
    Previews the group creation without performing it.
#>


function New-SMAGroup
{
    [CmdletBinding(SupportsShouldProcess)]
    param (
        [Parameter(
            Mandatory                       = $true,
            ValueFromPipeline               = $true,
            ValueFromPipelineByPropertyName = $true,
            HelpMessage                     = 'The groupss full name'
            )]
        [string]$name,

        [Parameter(
            Mandatory                       = $true,
            ValueFromPipelineByPropertyName = $true,
            HelpMessage                     = 'The groups description'
            )]
        [string]$description,

        [Parameter(
            Mandatory                       = $true,
            ValueFromPipelineByPropertyName = $true,
            HelpMessage                     = 'The groups members e-mail adresses'
            )]
        [string[]]$members,

        #region Host configuration parameters
        [Parameter(Mandatory = $false)]
        [String]$SMAHost = $Script:activeCfg.SMAHost,

        [Parameter(Mandatory = $false)]
        [int]$SMAPort = $Script:activeCfg.SMAPort,

        [Parameter(Mandatory = $false)]
        [String]$SMAVersion = $Script:activeCfg.SMAPIVersion,

        [Parameter(
            Mandatory=$false
            )]
            [System.Management.Automation.PSCredential]$SMACred=$Script:activeCfg.SMACred,

            [Parameter(
                Mandatory=$false
                )]
            [switch]$SMASkipCertCheck=$Script:activeCfg.SMAskipCertCheck
        #endregion
    )

    begin {
        if (! (verifyVars -VarList $Script:requiredVarList))
        {
            Throw($missingVarsMessage);
        }

        Write-Verbose "Building full request uri"
        $smaParams = @{
            Host    = $SMAHost;
            Port    = $SMAPort;
            Version = $SMAVersion;
        }
        $uri = New-SMAQueryString -uriPath 'group' @smaParams;
        
    }

    process {
        Write-Verbose 'Crafting mandatory $body JSON'
        $bodyht = @{
            name        = $name
            description = $description
            members     = @($members)
        }
        
        $body = $bodyht | ConvertTo-JSON
        Write-verbose "Crafting Invokeparam for Invoke-SMARestMethod"
        $invokeParam = @{
            Uri           = $uri 
            Method        = 'POST'
            body          = $body
            Cred          = $SMACred
            SkipCertCheck = $SMASkipCertCheck
        }
        if ($PSCmdLet.ShouldProcess($($bodyht.name), "Create group")) {
            Write-Verbose "Call Invoke-SMARestMethod $uri"
            $groupRaw = Invoke-SMARestMethod @invokeParam

            Write-Verbose 'Converting Umlauts from ISO-8859-1'
            $group = ConvertFrom-SMAPIFormat -inputObject $groupRaw
            
            Write-Verbose 'Returning groupname of new group'
            return $group.message.Split(' ')[3]
        }
    }
    end {}
}

<#
.SYNOPSIS
    Updates an existing SEPPmail group.
 
.DESCRIPTION
    Updates the description and/or the member list of an existing group via a PUT
    request to the 'group/<name>' API endpoint. Supplying the members parameter
    REPLACES the entire member list of the group. To add or remove single members
    without affecting the rest, use Add-SMAGroupMember or Remove-SMAGroupMember.
 
.PARAMETER name
    The name of the group to update. Mandatory. Accepts pipeline input (by value and
    by property name).
 
.PARAMETER description
    Updated description for the group. Optional. Accepts pipeline input by property name.
 
.PARAMETER members
    New complete list of member e-mail addresses. Optional. This REPLACES the existing
    member list entirely. Accepts pipeline input by property name.
 
.PARAMETER SMAHost
    SEPPmail API hostname. Defaults to the configured value.
 
.PARAMETER SMAPort
    SEPPmail API port. Defaults to the configured value.
 
.PARAMETER SMAVersion
    SEPPmail API version. Defaults to the configured value.
 
.PARAMETER SMACred
    API credentials (PSCredential). Defaults to the configured value.
 
.PARAMETER SMASkipCertCheck
    Skip SSL certificate validation. Use only in test environments.
 
.OUTPUTS
    System.Management.Automation.PSCustomObject
        Returns the name reported in the API confirmation message.
 
.NOTES
    - Requires an active SEPPmail API session (New-SMAConfiguration).
    - Supports -WhatIf and -Confirm.
    - Setting -members replaces all existing members; use Add-SMAGroupMember or
      Remove-SMAGroupMember for incremental changes.
    - Aliased as Set-SMAGR.
 
.LINK
    Get-SMAGroup
 
.LINK
    New-SMAGroup
 
.LINK
    Find-SMAGroup
 
.LINK
    Remove-SMAGroup
 
.LINK
    Add-SMAGroupMember
 
.LINK
    Remove-SMAGroupMember
 
.EXAMPLE
    PS C:\> Set-SMAGroup -name 'Admins' -description 'Senior IT administrators only'
    Updates only the description of the 'Admins' group.
 
.EXAMPLE
    PS C:\> Set-SMAGroup -name 'Marketing' -members @('alice@contoso.com', 'bob@contoso.com')
    Replaces the entire member list of the 'Marketing' group.
 
.EXAMPLE
    PS C:\> Get-SMAGroup -name 'Admins' | Set-SMAGroup -description 'Updated admin group'
    Pipes the group and updates its description.
#>

function Set-SMAGroup {
    [CmdletBinding(SupportsShouldProcess)]
    param (
        #region
        [Parameter(
            Mandatory                       = $true,
            ValueFromPipeline               = $true,
            ValueFromPipelineByPropertyName = $true,
            HelpMessage = 'The groups name'
        )]
        [string]$name,

        [Parameter(
            Mandatory                       = $false,
            ValueFromPipelineByPropertyName = $true,
            HelpMessage = 'The groups description'
        )]
        [string]$description,

        [Parameter(
            Mandatory                       = $false,
            ValueFromPipelineByPropertyName = $true,
            HelpMessage = 'Array of group members'
        )]
        [string[]]$members,
        #endregion data parameter

        #region Host parameter
        [Parameter(Mandatory = $false)]
        [String]$SMAHost = $Script:activeCfg.SMAHost,

        [Parameter(Mandatory = $false)]
        [int]$SMAPort = $Script:activeCfg.SMAPort,

        [Parameter(Mandatory = $false)]
        [String]$SMAVersion = $Script:activeCfg.SMAPIVersion,

        [Parameter(
            Mandatory = $false
        )]
        [System.Management.Automation.PSCredential]$SMACred = $Script:activeCfg.SMACred,

        [Parameter(
            Mandatory = $false
        )]
        [switch]$SMASkipCertCheck = $Script:activeCfg.SMAskipCertCheck 
        #endregion Host parameter
    )
    begin {
        if (! (verifyVars -VarList $Script:requiredVarList)) {
            Throw($missingVarsMessage);
        }
        $smaParams = @{
            Host    = $SMAHost;
            Port    = $SMAPort;
            Version = $SMAVersion;
        }
    }
    process {
        Write-Verbose "Creating URL path"
        $uriPath = "{0}/{1}" -f 'group', $name
        Write-Verbose "Building full request uri"
        $uri = New-SMAQueryString -uriPath $uriPath @smaParams;
        
        Write-Verbose 'Crafting mandatory $body JSON'
        $bodyht = @{name = $name}
        if ($PSBoundParameters.ContainsKey('members')) {
            $bodyht.Add('members', @($members))
        }
        if ($PSBoundParameters.ContainsKey('description')) {
            $bodyht.Add('description', $description)
        }

        
        $body = $bodyht | ConvertTo-JSON
        Write-verbose "Crafting Invokeparam for Invoke-SMARestMethod"
        $invokeParam = @{
            Uri           = $uri 
            Method        = 'PUT'
            body          = $body
            Cred          = $SMACred
            SkipCertCheck = $SMASkipCertCheck
        }
        #debug $uri
        if ($PSCmdLet.ShouldProcess($name, "Change group")) {
            Write-Verbose "Call Invoke-SMARestMethod $uri" 
            $groupRaw = Invoke-SMARestMethod @invokeParam
        }

        if ($groupRaw) {
            Write-Verbose 'Converting Umlauts from ISO-8859-1'
            $group = ConvertFrom-SMAPIFormat -inputObject $groupRaw
            return $group.message.Split(' ')[3]
        }
        else {
            Write-Information 'Nothing to return'
        }
    }
}

<#
.SYNOPSIS
    Retrieves a specific SEPPmail group.
 
.DESCRIPTION
    Fetches the detailed properties of a single group identified by its name via a
    GET request to the 'group/<name>' API endpoint. To list multiple groups, use
    Find-SMAGroup and pipe the result to this cmdlet.
 
.PARAMETER name
    The full name of the group to retrieve. Mandatory. Accepts pipeline input
    (by value and by property name).
 
.PARAMETER SMAHost
    SEPPmail API hostname. Defaults to the configured value.
 
.PARAMETER SMAPort
    SEPPmail API port. Defaults to the configured value.
 
.PARAMETER SMAVersion
    SEPPmail API version. Defaults to the configured value.
 
.PARAMETER SMACred
    API credentials (PSCredential). Defaults to the configured value.
 
.PARAMETER SMASkipCertCheck
    Skip SSL certificate validation. Use only in test environments.
 
.OUTPUTS
    System.Management.Automation.PSCustomObject
        Returns the group object with its properties such as name, description and members.
 
.NOTES
    - Requires an active SEPPmail API session (New-SMAConfiguration).
    - Aliased as Get-SMAGR.
 
.LINK
    Find-SMAGroup
 
.LINK
    New-SMAGroup
 
.LINK
    Set-SMAGroup
 
.LINK
    Remove-SMAGroup
 
.LINK
    Add-SMAGroupMember
 
.LINK
    Remove-SMAGroupMember
 
.EXAMPLE
    PS C:\> Get-SMAGroup -name 'Admins'
    Retrieves the detailed object of the 'Admins' group.
 
.EXAMPLE
    PS C:\> 'Admins' | Get-SMAGroup
    Pipes the group name to retrieve the group.
 
.EXAMPLE
    PS C:\> Find-SMAGroup | ForEach-Object { Get-SMAGroup -name $_ }
    Retrieves the detailed object for every group.
#>

function Get-SMAGroup
{
    [CmdletBinding()]
    param (
        #region REST-API path and query parameters
        [Parameter(
            Mandatory                       = $true,
            ValueFromPipeline               = $true,
            ValueFromPipelineByPropertyName = $true,
            HelpMessage                     = 'The groups full name'
            )]
        [string]$name,

        # 'email' parameter not used because GET only emits one object, so name makes most sense for unique output
        # to fetch multiple groups, use Find-SMAGroup and pipe to Get-SMAGroup

        #endregion

        #region SMA host parameters
        [Parameter(
            Mandatory = $false
            )]
        [String]$SMAHost = $Script:activeCfg.SMAHost,

        [Parameter(
            Mandatory = $false
            )]
        [int]$SMAPort = $Script:activeCfg.SMAPort,

        [Parameter(
            Mandatory = $false
            )]
        [String]$SMAVersion = $Script:activeCfg.SMAPIVersion,

        [Parameter(Mandatory=$false)]
        [System.Management.Automation.PSCredential]$SMACred=$Script:activeCfg.SMACred,

        [Parameter(Mandatory=$false)]
        [switch]$SMASkipCertCheck=$Script:activeCfg.SMAskipCertCheck
        #endregion
    )

    begin {
        if (! (verifyVars -VarList $Script:requiredVarList))
        {
            Throw($missingVarsMessage);
        }
        $smaParams = @{
            Host    = $SMAHost;
            Port    = $SMAPort;
            Version = $SMAVersion;
        }        
    }
    process {
        Write-Verbose "Creating URL path"
        $uriPath = "{0}/{1}" -f 'group',$name

        $uri = New-SMAQueryString -uriPath $uriPath -qParam $boundParam @smaParams
        
        Write-verbose "Crafting Invokeparam for Invoke-SMARestMethod"
        $invokeParam = @{
            Uri           = $uri 
            Method        = 'GET'
            Cred          = $SMACred
            SkipCertCheck = $SMASkipCertCheck
        }

        Write-Verbose "Call Invoke-SMARestMethod $uri" 
        $groupRaw = Invoke-SMARestMethod @invokeParam

        #Write-Verbose 'Filter data and return as PSObject'
        #$getGroup = $groupRaw.Psobject.properties.value

        Write-Verbose 'Converting Umlauts from ISO-8859-1'
        $group = ConvertFrom-SMAPIFormat -inputObject $groupRaw

        # Userobject
        if ($group) {
            return $group.psobject.properties.value
        }
        else {
            Write-Information 'Nothing to return'
        }
    }
    end {

    }
}

<#
.SYNOPSIS
    Removes a SEPPmail group.
 
.DESCRIPTION
    Deletes a group identified by its name via a DELETE request to the 'group/<name>'
    API endpoint. The -deleteUsers parameter controls whether the group's members are
    also deleted and whether their keys are preserved.
 
.PARAMETER name
    The name of the group to remove. Mandatory. Accepts pipeline input (by value and
    by property name).
 
.PARAMETER deleteUsers
    Controls what happens to the group's members. Valid values are:
    - 'no' (default): only the group is removed, the users remain unchanged.
    - 'allButKeepKeys': the members are deleted but their certificates and keys are preserved.
    - 'all': the members are permanently deleted including their certificates and keys.
    Accepts pipeline input by property name.
 
.PARAMETER SMAHost
    SEPPmail API hostname. Defaults to the configured value.
 
.PARAMETER SMAPort
    SEPPmail API port. Defaults to the configured value.
 
.PARAMETER SMAVersion
    SEPPmail API version. Defaults to the configured value.
 
.PARAMETER SMACred
    API credentials (PSCredential). Defaults to the configured value.
 
.PARAMETER SMASkipCertCheck
    Skip SSL certificate validation. Use only in test environments.
 
.OUTPUTS
    System.Management.Automation.PSCustomObject
        Returns the name reported in the API confirmation message.
 
.NOTES
    - Requires an active SEPPmail API session (New-SMAConfiguration).
    - Supports -WhatIf and -Confirm.
    - The 'all' value of -deleteUsers permanently deletes all members and their data.
    - Aliased as Remove-SMAGR.
 
.LINK
    Get-SMAGroup
 
.LINK
    Find-SMAGroup
 
.LINK
    New-SMAGroup
 
.LINK
    Set-SMAGroup
 
.LINK
    Remove-SMAGroupMember
 
.EXAMPLE
    PS C:\> Remove-SMAGroup -name 'TestGroup'
    Removes the group 'TestGroup' but keeps all its members as users.
 
.EXAMPLE
    PS C:\> Remove-SMAGroup -name 'TempGroup' -deleteUsers 'allButKeepKeys'
    Removes the group and its members, preserving their certificates and keys.
 
.EXAMPLE
    PS C:\> Remove-SMAGroup -name 'ObsoleteTeam' -deleteUsers 'all' -Confirm
    Permanently deletes the group and all its members, prompting for confirmation.
 
.EXAMPLE
    PS C:\> Remove-SMAGroup -name 'Marketing' -WhatIf
    Previews the removal without performing it.
#>

function Remove-SMAGroup
{
    [CmdletBinding(SupportsShouldProcess)]
    param (
        #region API Params
        [Parameter(
            Mandatory                       = $true,
            ValueFromPipeline               = $true,
            ValueFromPipelineByPropertyName = $true,
            HelpMessage                     = 'User E-Mail address',
            Position                        = 0
            )]
        [string]$name,

        [Parameter(
            Mandatory                       = $false,
            ValueFromPipelineByPropertyName = $true,
            HelpMessage                     = 'If all also users in this group will be deleted, if -allButKeepKeys- is selected the keys are preserved'
            )]
        [validateSet('no', 'allButKeepKeys', 'all')]
        [string]$deleteUsers = 'no',
        #endregion

        #region Hostparmeters
        [Parameter(Mandatory = $false)]
        [String]$SMAHost = $Script:activeCfg.SMAHost,

        [Parameter(Mandatory = $false)]
        [int]$SMAPort = $Script:activeCfg.SMAPort,

        [Parameter(Mandatory = $false)]
        [String]$SMAVersion = $Script:activeCfg.SMAPIVersion,

        [Parameter(
            Mandatory=$false
            )]
            [System.Management.Automation.PSCredential]$SMACred=$Script:activeCfg.SMACred,

            [Parameter(
                Mandatory=$false
                )]
            [switch]$SMASkipCertCheck=$Script:activeCfg.SMAskipCertCheck 
        #endregion
    )

    begin {
        if (! (verifyVars -VarList $Script:requiredVarList))
        {
            Throw($missingVarsMessage);
        }

        $smaParams = @{
            Host    = $SMAHost;
            Port    = $SMAPort;
            Version = $SMAVersion;
        }

    }
    process {
        Write-Verbose "Creating URL path"
        $uriPath = "{0}/{1}" -f 'group', $name

        Write-Verbose "Building full request uri"
        $boundParam = @{
            deleteUsers  = $deleteUsers
        }
        $uri = New-SMAQueryString -uriPath $uriPath -qParam $boundParam @smaParams;

        Write-verbose "Crafting Invokeparam for Invoke-SMARestMethod"
        $invokeParam = @{
            Uri         = $uri 
            Method      = 'DELETE'
            Cred        =  $SMACred
            SkipCertCheck = $SMASkipCertCheck
            }
        
        if ($PSCmdLet.ShouldProcess($name,"Remove User")) {
            Write-Verbose "Call Invoke-SMARestMethod $uri"
            $groupRaw = Invoke-SMARestMethod @invokeParam

            # Gina-Userobject
            if ($groupRaw) {
                Write-Verbose 'Converting Umlauts from ISO-8859-1'
                $group = ConvertFrom-SMAPIFormat -inputObject $groupRaw #|convertfrom-Json -AsHashtable
                return $group.message.Split(' ')[3]
            }
            else {
                Write-Information 'No matching group found, nothing to return'
            }
        }
    }
}
#>

<#
.SYNOPSIS
    Adds members to an existing SEPPmail group.
 
.DESCRIPTION
    Adds one or more users to an existing SEPPmail group via a POST request to the
    'group/<name>/member' API endpoint. This is an incremental operation: only the
    specified members are added; existing group members remain unchanged.
 
.PARAMETER name
    The name of the group to which the members are added. Accepts pipeline input
    (by value and by property name).
 
.PARAMETER members
    Array of user e-mail addresses to add to the group. At least one address is
    required. Accepts pipeline input by property name.
 
.PARAMETER SMAHost
    SEPPmail API hostname. Defaults to the configured value.
 
.PARAMETER SMAPort
    SEPPmail API port. Defaults to the configured value.
 
.PARAMETER SMAVersion
    SEPPmail API version. Defaults to the configured value.
 
.PARAMETER SMACred
    API credentials (PSCredential). Defaults to the configured value.
 
.PARAMETER SMASkipCertCheck
    Skip SSL certificate validation. Use only in test environments.
 
.OUTPUTS
    System.Management.Automation.PSCustomObject
        Returns the name reported in the API confirmation message.
 
.NOTES
    - Requires an active SEPPmail API session (New-SMAConfiguration).
    - Supports -WhatIf and -Confirm.
    - Aliased as Add-SMAGRM.
 
.LINK
    Remove-SMAGroupMember
 
.LINK
    Get-SMAGroup
 
.LINK
    Set-SMAGroup
 
.EXAMPLE
    PS C:\> Add-SMAGroupMember -name 'myGroup' -members 'admin1@contoso.de','admin2@contoso.de'
    Adds two members to the group 'myGroup'.
 
.EXAMPLE
    PS C:\> Get-SMAGroup -name 'Admins' | Add-SMAGroupMember -members 'newadmin@contoso.com'
    Pipes the group name and adds a single member.
#>

function Add-SMAGroupMember {
    [CmdletBinding(SupportsShouldProcess)]
    param (
        #region
        [Parameter(
            Mandatory                       = $true,
            ValueFromPipeline               = $true,
            ValueFromPipelineByPropertyName = $true,
            HelpMessage                     = 'The group name'
        )]
        [string]$name,

        [Parameter(
            Mandatory                       = $true,
            ValueFromPipelineByPropertyName = $true,
            HelpMessage                     = 'Array of email addresses to add members'
        )]
        [string[]]$members,
        #endregion data parameter

        #region Host parameter
        [Parameter(Mandatory = $false)]
        [String]$SMAHost = $Script:activeCfg.SMAHost,

        [Parameter(Mandatory = $false)]
        [int]$SMAPort = $Script:activeCfg.SMAPort,

        [Parameter(Mandatory = $false)]
        [String]$SMAVersion = $Script:activeCfg.SMAPIVersion,

        [Parameter(
            Mandatory = $false
        )]
        [System.Management.Automation.PSCredential]$SMACred = $Script:activeCfg.SMACred,

        [Parameter(
            Mandatory = $false
        )]
        [switch]$SMASkipCertCheck = $Script:activeCfg.SMAskipCertCheck 
        #endregion Host parameter
    )
    begin {
        if (! (verifyVars -VarList $Script:requiredVarList)) {
            Throw($missingVarsMessage);
        }

        Write-Verbose "Building full request uri"
        $smaParams = @{
            Host    = $SMAHost;
            Port    = $SMAPort;
            Version = $SMAVersion;
        }
    }
    process {
        Write-Verbose "Creating URL path"
        $uriPath = "{0}/{1}/{2}" -f 'group', $name, 'member'
        
        $uri = New-SMAQueryString -uriPath $uriPath -qParam $boundParam @smaParams;
        
        Write-Verbose 'Crafting mandatory $body JSON'
        $bodyht = @{
            members     = @($members)
        }
        
        $body = $bodyht | ConvertTo-JSON
        Write-verbose "Crafting Invokeparam for Invoke-SMARestMethod"
        $invokeParam = @{
            Uri           = $uri 
            Method        = 'POST'
            body          = $body
            Cred          = $SMACred
            SkipCertCheck = $SMASkipCertCheck
        }
        #debug $uri
        if ($PSCmdLet.ShouldProcess($($bodyht.Email), "Change user")) {
            Write-Verbose "Call Invoke-SMARestMethod $uri" 
            $groupRaw = Invoke-SMARestMethod @invokeParam
        }
        if ($groupRaw) {
            $group = ConvertFrom-SMAPIFormat -inputObject $groupRaw
            return $group.message.Split(' ')[3]
        }
        else {
            Write-Information 'Nothing to return'
        }
    }
}

<#
.SYNOPSIS
    Removes members from an existing SEPPmail group.
 
.DESCRIPTION
    Removes one or more users from an existing group via a PUT request to the
    'group/<name>/member' API endpoint. This is an incremental operation: only the
    specified members are removed from the group; all other members remain. The users
    themselves remain in the system, only their group membership is removed.
 
.PARAMETER name
    The name of the group to modify. Mandatory. Accepts pipeline input by value.
 
.PARAMETER members
    Array of user e-mail addresses to remove from the group. At least one address is
    required. Accepts pipeline input by property name.
 
.PARAMETER SMAHost
    SEPPmail API hostname. Defaults to the configured value.
 
.PARAMETER SMAPort
    SEPPmail API port. Defaults to the configured value.
 
.PARAMETER SMAVersion
    SEPPmail API version. Defaults to the configured value.
 
.PARAMETER SMACred
    API credentials (PSCredential). Defaults to the configured value.
 
.PARAMETER SMASkipCertCheck
    Skip SSL certificate validation. Use only in test environments.
 
.OUTPUTS
    System.Management.Automation.PSCustomObject
        Returns the name reported in the API confirmation message.
 
.NOTES
    - Requires an active SEPPmail API session (New-SMAConfiguration).
    - Supports -WhatIf and -Confirm.
    - Aliased as Remove-SMAGRM.
 
.LINK
    Add-SMAGroupMember
 
.LINK
    Get-SMAGroup
 
.LINK
    Set-SMAGroup
 
.LINK
    Remove-SMAGroup
 
.EXAMPLE
    PS C:\> Remove-SMAGroupMember -name 'Admins' -members 'oldadmin@contoso.com'
    Removes a single member from the 'Admins' group.
 
.EXAMPLE
    PS C:\> Remove-SMAGroupMember -name 'Marketing' -members @('alice@contoso.com', 'bob@contoso.com')
    Removes two members from the 'Marketing' group at once.
 
.EXAMPLE
    PS C:\> Get-SMAGroup -name 'IT-Team' | Remove-SMAGroupMember -members 'oldtech@contoso.com'
    Pipes the group name and removes a member from it.
#>

function Remove-SMAGroupMember {
    [CmdletBinding(SupportsShouldProcess)]
    param (
        #region
        [Parameter(
            Mandatory = $true,
            ValueFromPipeline = $true,
            HelpMessage = 'The group name'
        )]
        [string]$name,

        [Parameter(
            Mandatory = $true,
            ValueFromPipelineByPropertyName = $true,
            HelpMessage = 'Array of group members'
        )]
        [string[]]$members,
        #endregion data parameter

        #region Host parameter
        [Parameter(Mandatory = $false)]
        [String]$SMAHost = $Script:activeCfg.SMAHost,

        [Parameter(Mandatory = $false)]
        [int]$SMAPort = $Script:activeCfg.SMAPort,

        [Parameter(Mandatory = $false)]
        [String]$SMAVersion = $Script:activeCfg.SMAPIVersion,

        [Parameter(
            Mandatory = $false
        )]
        [System.Management.Automation.PSCredential]$SMACred = $Script:activeCfg.SMACred,

        [Parameter(
            Mandatory = $false
        )]
        [switch]$SMASkipCertCheck = $Script:activeCfg.SMAskipCertCheck 
        #endregion Host parameter
    )
    begin {
        if (! (verifyVars -VarList $Script:requiredVarList)) {
            Throw($missingVarsMessage);
        }
        $smaParams = @{
            Host    = $SMAHost;
            Port    = $SMAPort;
            Version = $SMAVersion;
        }
    }
    process {
        Write-Verbose "Creating URL path"
        $uriPath = "{0}/{1}/{2}" -f 'group', $name, 'member'
        Write-Verbose "Building full request uri"
        $uri = New-SMAQueryString -uriPath $uriPath -qParam $boundParam @smaParams;
        
        Write-Verbose 'Crafting mandatory $body JSON'
        $bodyht = @{
            members     = @($members)
        }
        
        $body = $bodyht | ConvertTo-JSON
        Write-verbose "Crafting Invokeparam for Invoke-SMARestMethod"
        $invokeParam = @{
            Uri           = $uri 
            Method        = 'PUT'
            body          = $body
            Cred          = $SMACred
            SkipCertCheck = $SMASkipCertCheck
        }
        #debug $uri
        if ($PSCmdLet.ShouldProcess($($bodyht.Email), "Change user")) {
            Write-Verbose "Call Invoke-SMARestMethod $uri" 
            $groupRaw = Invoke-SMARestMethod @invokeParam
        }
        if ($groupRaw) {
            $group = ConvertFrom-SMAPIFormat -inputObject $groupRaw
            return $group.message.Split(' ')[3]
        }
        else {
            Write-Information 'Nothing to return'
        }
    }
}


Write-Verbose 'Create CmdLet Alias for groups' 
$custVerbs = ('New','Remove','Get','Find','Set')

Foreach ($custverb in $custVerbs) {
    $aliasname1 = $custverb + '-SMAGR'
    $cmdName = $custverb + '-SMAGroup'
    New-Alias -Name $aliasName1 -Value $cmdName
}

Write-Verbose 'Create CmdLet Alias for Group members' 
$custVerbs = ('Remove','Add')

Foreach ($custverb in $custVerbs) {
    $aliasname1 = $custverb + '-SMAGRM'
    $cmdName = $custverb + '-SMAGroupMember'
    New-Alias -Name $aliasName1 -Value $cmdName
}

# SIG # Begin signature block
# MIIVyAYJKoZIhvcNAQcCoIIVuTCCFbUCAQExDzANBglghkgBZQMEAgEFADB5Bgor
# BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG
# KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCBKO3aMrq0RTZRR
# lOFSkZ2+dKoYwk8FrYqXYazI4FIlrqCCEgQwggVvMIIEV6ADAgECAhBI/JO0YFWU
# jTanyYqJ1pQWMA0GCSqGSIb3DQEBDAUAMHsxCzAJBgNVBAYTAkdCMRswGQYDVQQI
# DBJHcmVhdGVyIE1hbmNoZXN0ZXIxEDAOBgNVBAcMB1NhbGZvcmQxGjAYBgNVBAoM
# EUNvbW9kbyBDQSBMaW1pdGVkMSEwHwYDVQQDDBhBQUEgQ2VydGlmaWNhdGUgU2Vy
# dmljZXMwHhcNMjEwNTI1MDAwMDAwWhcNMjgxMjMxMjM1OTU5WjBWMQswCQYDVQQG
# EwJHQjEYMBYGA1UEChMPU2VjdGlnbyBMaW1pdGVkMS0wKwYDVQQDEyRTZWN0aWdv
# IFB1YmxpYyBDb2RlIFNpZ25pbmcgUm9vdCBSNDYwggIiMA0GCSqGSIb3DQEBAQUA
# A4ICDwAwggIKAoICAQCN55QSIgQkdC7/FiMCkoq2rjaFrEfUI5ErPtx94jGgUW+s
# hJHjUoq14pbe0IdjJImK/+8Skzt9u7aKvb0Ffyeba2XTpQxpsbxJOZrxbW6q5KCD
# J9qaDStQ6Utbs7hkNqR+Sj2pcaths3OzPAsM79szV+W+NDfjlxtd/R8SPYIDdub7
# P2bSlDFp+m2zNKzBenjcklDyZMeqLQSrw2rq4C+np9xu1+j/2iGrQL+57g2extme
# me/G3h+pDHazJyCh1rr9gOcB0u/rgimVcI3/uxXP/tEPNqIuTzKQdEZrRzUTdwUz
# T2MuuC3hv2WnBGsY2HH6zAjybYmZELGt2z4s5KoYsMYHAXVn3m3pY2MeNn9pib6q
# RT5uWl+PoVvLnTCGMOgDs0DGDQ84zWeoU4j6uDBl+m/H5x2xg3RpPqzEaDux5mcz
# mrYI4IAFSEDu9oJkRqj1c7AGlfJsZZ+/VVscnFcax3hGfHCqlBuCF6yH6bbJDoEc
# QNYWFyn8XJwYK+pF9e+91WdPKF4F7pBMeufG9ND8+s0+MkYTIDaKBOq3qgdGnA2T
# OglmmVhcKaO5DKYwODzQRjY1fJy67sPV+Qp2+n4FG0DKkjXp1XrRtX8ArqmQqsV/
# AZwQsRb8zG4Y3G9i/qZQp7h7uJ0VP/4gDHXIIloTlRmQAOka1cKG8eOO7F/05QID
# AQABo4IBEjCCAQ4wHwYDVR0jBBgwFoAUoBEKIz6W8Qfs4q8p74Klf9AwpLQwHQYD
# VR0OBBYEFDLrkpr/NZZILyhAQnAgNpFcF4XmMA4GA1UdDwEB/wQEAwIBhjAPBgNV
# HRMBAf8EBTADAQH/MBMGA1UdJQQMMAoGCCsGAQUFBwMDMBsGA1UdIAQUMBIwBgYE
# VR0gADAIBgZngQwBBAEwQwYDVR0fBDwwOjA4oDagNIYyaHR0cDovL2NybC5jb21v
# ZG9jYS5jb20vQUFBQ2VydGlmaWNhdGVTZXJ2aWNlcy5jcmwwNAYIKwYBBQUHAQEE
# KDAmMCQGCCsGAQUFBzABhhhodHRwOi8vb2NzcC5jb21vZG9jYS5jb20wDQYJKoZI
# hvcNAQEMBQADggEBABK/oe+LdJqYRLhpRrWrJAoMpIpnuDqBv0WKfVIHqI0fTiGF
# OaNrXi0ghr8QuK55O1PNtPvYRL4G2VxjZ9RAFodEhnIq1jIV9RKDwvnhXRFAZ/ZC
# J3LFI+ICOBpMIOLbAffNRk8monxmwFE2tokCVMf8WPtsAO7+mKYulaEMUykfb9gZ
# pk+e96wJ6l2CxouvgKe9gUhShDHaMuwV5KZMPWw5c9QLhTkg4IUaaOGnSDip0TYl
# d8GNGRbFiExmfS9jzpjoad+sPKhdnckcW67Y8y90z7h+9teDnRGWYpquRRPaf9xH
# +9/DUp/mBlXpnYzyOmJRvOwkDynUWICE5EV7WtgwggYaMIIEAqADAgECAhBiHW0M
# UgGeO5B5FSCJIRwKMA0GCSqGSIb3DQEBDAUAMFYxCzAJBgNVBAYTAkdCMRgwFgYD
# VQQKEw9TZWN0aWdvIExpbWl0ZWQxLTArBgNVBAMTJFNlY3RpZ28gUHVibGljIENv
# ZGUgU2lnbmluZyBSb290IFI0NjAeFw0yMTAzMjIwMDAwMDBaFw0zNjAzMjEyMzU5
# NTlaMFQxCzAJBgNVBAYTAkdCMRgwFgYDVQQKEw9TZWN0aWdvIExpbWl0ZWQxKzAp
# BgNVBAMTIlNlY3RpZ28gUHVibGljIENvZGUgU2lnbmluZyBDQSBSMzYwggGiMA0G
# CSqGSIb3DQEBAQUAA4IBjwAwggGKAoIBgQCbK51T+jU/jmAGQ2rAz/V/9shTUxjI
# ztNsfvxYB5UXeWUzCxEeAEZGbEN4QMgCsJLZUKhWThj/yPqy0iSZhXkZ6Pg2A2NV
# DgFigOMYzB2OKhdqfWGVoYW3haT29PSTahYkwmMv0b/83nbeECbiMXhSOtbam+/3
# 6F09fy1tsB8je/RV0mIk8XL/tfCK6cPuYHE215wzrK0h1SWHTxPbPuYkRdkP05Zw
# mRmTnAO5/arnY83jeNzhP06ShdnRqtZlV59+8yv+KIhE5ILMqgOZYAENHNX9SJDm
# +qxp4VqpB3MV/h53yl41aHU5pledi9lCBbH9JeIkNFICiVHNkRmq4TpxtwfvjsUe
# dyz8rNyfQJy/aOs5b4s+ac7IH60B+Ja7TVM+EKv1WuTGwcLmoU3FpOFMbmPj8pz4
# 4MPZ1f9+YEQIQty/NQd/2yGgW+ufflcZ/ZE9o1M7a5Jnqf2i2/uMSWymR8r2oQBM
# dlyh2n5HirY4jKnFH/9gRvd+QOfdRrJZb1sCAwEAAaOCAWQwggFgMB8GA1UdIwQY
# MBaAFDLrkpr/NZZILyhAQnAgNpFcF4XmMB0GA1UdDgQWBBQPKssghyi47G9IritU
# pimqF6TNDDAOBgNVHQ8BAf8EBAMCAYYwEgYDVR0TAQH/BAgwBgEB/wIBADATBgNV
# HSUEDDAKBggrBgEFBQcDAzAbBgNVHSAEFDASMAYGBFUdIAAwCAYGZ4EMAQQBMEsG
# A1UdHwREMEIwQKA+oDyGOmh0dHA6Ly9jcmwuc2VjdGlnby5jb20vU2VjdGlnb1B1
# YmxpY0NvZGVTaWduaW5nUm9vdFI0Ni5jcmwwewYIKwYBBQUHAQEEbzBtMEYGCCsG
# AQUFBzAChjpodHRwOi8vY3J0LnNlY3RpZ28uY29tL1NlY3RpZ29QdWJsaWNDb2Rl
# U2lnbmluZ1Jvb3RSNDYucDdjMCMGCCsGAQUFBzABhhdodHRwOi8vb2NzcC5zZWN0
# aWdvLmNvbTANBgkqhkiG9w0BAQwFAAOCAgEABv+C4XdjNm57oRUgmxP/BP6YdURh
# w1aVcdGRP4Wh60BAscjW4HL9hcpkOTz5jUug2oeunbYAowbFC2AKK+cMcXIBD0Zd
# OaWTsyNyBBsMLHqafvIhrCymlaS98+QpoBCyKppP0OcxYEdU0hpsaqBBIZOtBajj
# cw5+w/KeFvPYfLF/ldYpmlG+vd0xqlqd099iChnyIMvY5HexjO2AmtsbpVn0OhNc
# WbWDRF/3sBp6fWXhz7DcML4iTAWS+MVXeNLj1lJziVKEoroGs9Mlizg0bUMbOalO
# hOfCipnx8CaLZeVme5yELg09Jlo8BMe80jO37PU8ejfkP9/uPak7VLwELKxAMcJs
# zkyeiaerlphwoKx1uHRzNyE6bxuSKcutisqmKL5OTunAvtONEoteSiabkPVSZ2z7
# 6mKnzAfZxCl/3dq3dUNw4rg3sTCggkHSRqTqlLMS7gjrhTqBmzu1L90Y1KWN/Y5J
# KdGvspbOrTfOXyXvmPL6E52z1NZJ6ctuMFBQZH3pwWvqURR8AgQdULUvrxjUYbHH
# j95Ejza63zdrEcxWLDX6xWls/GDnVNueKjWUH3fTv1Y8Wdho698YADR7TNx8X8z2
# Bev6SivBBOHY+uqiirZtg0y9ShQoPzmCcn63Syatatvx157YK9hlcPmVoa1oDE5/
# L9Uo2bC5a4CH2RwwggZvMIIE16ADAgECAhBIqMP3CCLHOHtOKuaWNyeFMA0GCSqG
# SIb3DQEBDAUAMFQxCzAJBgNVBAYTAkdCMRgwFgYDVQQKEw9TZWN0aWdvIExpbWl0
# ZWQxKzApBgNVBAMTIlNlY3RpZ28gUHVibGljIENvZGUgU2lnbmluZyBDQSBSMzYw
# HhcNMjYwNDE1MDAwMDAwWhcNMjcwNzE0MjM1OTU5WjBmMQswCQYDVQQGEwJERTEP
# MA0GA1UECAwGQmF5ZXJuMSIwIAYDVQQKDBlTRVBQbWFpbCBEZXV0c2NobGFuZCBH
# bWJIMSIwIAYDVQQDDBlTRVBQbWFpbCBEZXV0c2NobGFuZCBHbWJIMIICIjANBgkq
# hkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAvAFzE8MbJpvQt+IdIh1M+bKYsJBFDk4b
# 9ySe25IrCi00B9o5XmQtIw42MqyIKbUq1tDARtp9KTQedEP9W+rflAF2l+0Z046J
# kiqumU9/enbqWLDyln1aS/p7HOgwZFMhnsR9zH0MfFckiklUmkzJO+vmzYAK7ZmD
# xajNLJs0gkGRU2/BecAx/TSvLXMaKONsKZCyMKQCnwo1mCY/tFl5EgUz7YQFrPOR
# BQGfQke/hkdBfQDqNRsi/J6+KhJWc6LvgQihdRg/INQbQsTxlow18NWvyFsjjueH
# 7kG6HR4YKfbv07xgrsIh8xvq9ZJ1SBhLXmkg4SdoQGASjqR6o3keAX+bDRFf+hml
# WWJp/FqVHR5QomF3vbK2/bbz4jAclYSPx/sPasNJ0YnKFkgmowZ7Ysa0KA0/egBg
# tI4gJ+8V7zrqIVEG3rMQh9KCdMnJqP2aM9o4gUzQvE1M4x606liX9EWwdLLS+fe7
# 9o+Fzo5oH4wBE/En6hQQkzseHHu+TXCDd6zUUZ/PlTK0gTaDIRXt6UzPNqJ4RiRC
# W2pNFcPt078qqVTuwKUXoE4ufxGgXKFrZlCYST/9eG1TnW2oq19nz8A333GCsL3g
# poNIKvfmDyGMMNzvx2aeqn2v6e75z8kH19iGSNZ51xT+WgS9F1aIvjz08/T7XAv7
# iDPF1/gPIp8CAwEAAaOCAakwggGlMB8GA1UdIwQYMBaAFA8qyyCHKLjsb0iuK1Sm
# KaoXpM0MMB0GA1UdDgQWBBS30/Tq+alF3j2BY5up8n5zpAU23DAOBgNVHQ8BAf8E
# BAMCB4AwDAYDVR0TAQH/BAIwADATBgNVHSUEDDAKBggrBgEFBQcDAzBKBgNVHSAE
# QzBBMDUGDCsGAQQBsjEBAgEDAjAlMCMGCCsGAQUFBwIBFhdodHRwczovL3NlY3Rp
# Z28uY29tL0NQUzAIBgZngQwBBAEwSQYDVR0fBEIwQDA+oDygOoY4aHR0cDovL2Ny
# bC5zZWN0aWdvLmNvbS9TZWN0aWdvUHVibGljQ29kZVNpZ25pbmdDQVIzNi5jcmww
# eQYIKwYBBQUHAQEEbTBrMEQGCCsGAQUFBzAChjhodHRwOi8vY3J0LnNlY3RpZ28u
# Y29tL1NlY3RpZ29QdWJsaWNDb2RlU2lnbmluZ0NBUjM2LmNydDAjBggrBgEFBQcw
# AYYXaHR0cDovL29jc3Auc2VjdGlnby5jb20wHgYDVR0RBBcwFYETc3VwcG9ydEBz
# ZXBwbWFpbC5jaDANBgkqhkiG9w0BAQwFAAOCAYEAi7fmb5UYoemWG3CC4K2UZWVr
# R6GOfi8gbJKgjPbKO4zrCrU/x6cOdyp6scKZfUEGFDf8KH6pP4pAQv1Hsbi49gU2
# kxoUWLlCiipn05qJY663DHx9hlStej/ZdEatou0wyCDiG5xD7kmG+1t6iLyyCBgE
# B88tJpzTjI61qXmBTS/FGEOAsB4SDEW1ngA7bc5FOv4IUKA43hp8M+N3GeYFzDqw
# JELYEfVVYheBW3o7q4VrCdfFEuaQihOtvfDfYpP6ANgekNn8HdsMT8rx9D1I50Rl
# i/qQFo2BOuPyb2SIQPzJvCs5wgi5qgp1nHiN6igumu2Cz7BmGjOazGUgCSUY5Qwy
# E8+F+R2tVM+2O15rfX01+e56ZfojBEiEjMwfPHs3fa3V3gokWWNwUMkton/v0R/n
# l2zjmOr2okohOINZEDh9frg21zUCN5ZD8Y4zQWuiJLCvvvBZs0JR4c9xl2k2wtw/
# QLPhGU69zM3smGpRoLE8M6zvUvSU7jXjvefazUniMYIDGjCCAxYCAQEwaDBUMQsw
# CQYDVQQGEwJHQjEYMBYGA1UEChMPU2VjdGlnbyBMaW1pdGVkMSswKQYDVQQDEyJT
# ZWN0aWdvIFB1YmxpYyBDb2RlIFNpZ25pbmcgQ0EgUjM2AhBIqMP3CCLHOHtOKuaW
# NyeFMA0GCWCGSAFlAwQCAQUAoIGEMBgGCisGAQQBgjcCAQwxCjAIoAKAAKECgAAw
# GQYJKoZIhvcNAQkDMQwGCisGAQQBgjcCAQQwHAYKKwYBBAGCNwIBCzEOMAwGCisG
# AQQBgjcCARUwLwYJKoZIhvcNAQkEMSIEIAvnDbD8fKqR0qeLa9/pbVP1Rp7PuecJ
# vy3rYXKn18jWMA0GCSqGSIb3DQEBAQUABIICAH8qL4iFbdoSX5ZtLxgyOMPKjgtg
# erKuVx91padU756oCUgGayBTiBbPS0MZ7JqYeJL9bXlCMA/7w1hOfflHPQsm3s+p
# a3BSX3DyjWD3HJUjTGBMAEm+1RScdBzHHvd0K7FUTM3lcWHHDQW2m4/zjC9RxuyT
# xr9rYrB3y7qWTMG9G464tKgPfpi4iliXn/18/Ro8hsyHOKBP1J9vmItjxa3NBgH1
# knj2s8YP4ptw0zSi6kG6yyKtCSPlIQGUBzlhbuMYG6Lrkrx345UOaQb91iQbnhcV
# A97q6Qo1XAqvY93s0sDi/P52BoQXNql8qbW+mgPaALNkzeSTrcbYMrKU/wTzc9y1
# Y//MhQPTPKCjYG8jFd4M5jXPRcsicYYW28BXofiCpa9wsMrFYopOobcHEQqctHd3
# wLFQCSZJPZMfqmA6/bJQJJm/f4bpl1tJQLzR0qnjVhiqGy78JhMkXYvtHXzm0jCb
# qFX6oDyui3JkguIzXXcSh0hdGGVVf6nt3CGLF0Cy7KkznYeHPDNnz3u9Ac8AJX7C
# Yg5Q2og99HY+R1HEsjHAan+NluXieEKrejeSzPRCR9cYGP6XmBFqe0hBlL/gZ8Wm
# uxutWBHVWhtaFy4rmyy5F5DpmzlxyBV3UEg6fGGwEitxHxLmFMQH099dAs15TgMl
# NXM2xfp7EL/yxlcT
# SIG # End signature block