Private/SEPPmailAPIPrivate.ps1

# Place for module - internal functions


function ConvertFrom-SMAPIFormat {
    [CmdletBinding()]
    param (
        [Parameter(
            Mandatory         = $true,
            ValueFromPipeline = $true
            )]
        [PSobject]$inputObject
    )

      # Convert Names to Umlauts
    if ($inputObject.Name) {
        $bytes = [System.Text.Encoding]::GetEncoding("ISO-8859-1").GetBytes($inputObject.Name)
        $inputObject.Name = [System.Text.Encoding]::UTF8.GetString($bytes)
    }

    # Convert comments to Umlauts
      if ($inputObject.comment) {
        $bytes = [System.Text.Encoding]::GetEncoding("ISO-8859-1").GetBytes($inputObject.comment)
        $inputObject.comment = [System.Text.Encoding]::UTF8.GetString($bytes)
    }
    # Convert description to Umlauts
    if ($inputObject.description) {
        $bytes = [System.Text.Encoding]::GetEncoding("ISO-8859-1").GetBytes($inputObject.description)
        $inputObject.description = [System.Text.Encoding]::UTF8.GetString($bytes)
    }

    # Convert strig to Date
    if ($inputObject.createdDate) {
        $inputObject.createdDate = [Datetime]::ParseExact($inputObject.createdDate, 'yyyyMMddHHmmssZ', $null)
    }
    return $inputObject
}

function ConvertFrom-SMASecureString {
    param(
        [Parameter(
            Mandatory           = $true,
            ValueFromPipeline   = $true
        )]
        [SecureString]$securePassword
    )

    try {
        [string]$plainpassword = $null
        if ($psversiontable.PsEdition -eq 'Desktop') {
            $BSTR = [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($securePassword)
            $plainPassword = [System.Runtime.InteropServices.Marshal]::PtrToStringAuto($BSTR)
        } else {
            $plainPassword = $securePassword|ConvertFrom-SecureString -AsPlainText
        }
        return $plainPassword
    }
    catch {
        Write-Error "Error $_ occured!"
    }    
}

function New-SMAQueryString {
    [CmdletBinding()]
    param 
    (
        [Parameter(Mandatory = $false)]
        [Alias('Host')]
        [String]$SMAHost = $SMAHost,

        [Parameter(Mandatory = $false)]
        [Alias('Port')]
        [int]$SMAPort = $SMAPort,

        [Parameter(Mandatory = $false)]
        [String]$schema = 'https',

        [Parameter(Mandatory = $false)]
        [Alias('Version')]
        [String]$SMAVersion = $SMAPIVersion,

        [Parameter(Mandatory = $true)]
        [String]$uriPath,

        [Parameter(Mandatory = $false)]
        [Hashtable]$qParam
    )

    try {
        # Add System.Web
        Add-Type -AssemblyName System.Web

        # Create a http name value collection from an empty string
        Write-Verbose "Build the uri based on $schema and $SMAHost"
        $schemeHost = "{0}://{1}/ " -f $schema, $SMAHost
        $queryString = [System.UriBuilder]$schemeHost

        Write-Verbose "Add path based on parameters from $($qparam)"
        $ParamCollection = [System.Web.HttpUtility]::ParseQueryString([String]::Empty) 
        if ($qparam) {
            $qparam.GetEnumerator()|ForEach-Object {
                $ParamCollection.Add("$($_.Key)","$($_.Value)")
            }
            $queryString.Query = $ParamCollection.ToString().Replace('=True','=true').Replace('=False','=false')
        }

        Write-Verbose "Finally building Querystring"
        $queryString.Port = $SMAPort
        $queryString.Path = "/$SMAVersion/" + $uriPath
        return $queryString.Uri.OriginalString
    }
    catch {
        Write-Error "Error $_ occured!"
    }
}

<#
.SYNOPSIS
    Calls the REST interface with Parameters
.DESCRIPTION
    Depending on the PS Version/Edition, calls the REST Method with proper settings and valid parameters.
    For Module internal use only.
#>

function Invoke-SMARestMethod {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory=$true)]
        [string]$uri,

        [Parameter(
            Mandatory=$true
            )]
        [ValidateSet('GET','POST','PUT','DELETE','PATCH')]
        [string]$method,

        [Parameter(
            Mandatory=$false
            )]
        [string[]]$body,

        [Parameter(
            Mandatory=$false
            )]
            [Alias('Cred')]
            [System.Management.Automation.PSCredential]$SMACred=$SMACred,

            [Parameter(
                Mandatory=$false
                )]
            [Alias('SkipCertCheck')]
            [switch]$SMASkipCertCheck=$SMAskipCertCheck    
    )

    begin {
        Write-Verbose "Crafting Header-JSON"
        $headers = @{
             'X-SM-API-TOKEN' = $SMACred.UserName;
            'X-SM-API-SECRET' = (ConvertFrom-SMASecureString -securePassword $SMACred.Password)
                     'accept' = 'application/json'
        }
        
        Write-Verbose "Crafting the parameters for invoke-RestMethod"
        $SMinvokeParam = @{
            Uri         = $uri
            Method      = $Method
            header      = $headers
        }
        if ($null -ne $body) { $SMinvokeParam.body = $body }

    }
    process {
        # Core and Skip
        if (($PSversionTable.PSEdition -like 'Core') -and ($SMASkipCertCheck)) {
            Write-verbose 'Calling Invoke-RestMethod on Core edition with skip Certificate'
            try {
                #Invoke-RestMethod @SMinvokeParam -SkipCertificateCheck -ContentType 'application/json; charset=utf-8'
                Invoke-RestMethod @SMinvokeParam -SkipCertificateCheck -ContentType 'application/json'
            }
            catch {
                Get-SMARestError -ErrorRecord $_
            }
        }
        # Desktop and skip
        elseif (($PSversionTable.PSedition -like 'Desktop') -and ($SMASkipCertCheck)) {
            Write-Verbose "Change endpoint to skipCertificateCheck and call url"
            if ([System.Net.ServicePointManager]::CertificatePolicy -like 'System.Net.DefaultCertPolicy') {
                $DefaultPolicy = [System.Net.ServicePointManager]::CertificatePolicy
                add-type @"
                using System.Net;
                using System.Security.Cryptography.X509Certificates;
                public class IDontCarePolicy : ICertificatePolicy {
                    public IDontCarePolicy() {}
                    public bool CheckValidationResult(
                        ServicePoint sPoint, X509Certificate cert,
                        WebRequest wRequest, int certProb) {
                            return true;
                    }
                }
"@

                [System.Net.ServicePointManager]::CertificatePolicy = new-object IDontCarePolicy 
                Write-verbose 'Calling Invoke-RestMethod on Dektop edition with skip Certificate'
                try {
                    Invoke-RestMethod @SMinvokeParam -ContentType 'application/json'
                }
                catch {
                    #$RestErr = ($_.ErrorDetails.Message|convertfrom-JSON).errorMessage
                    #Write-Error "$RestErr"
                    Get-SMARestError -ErrorRecord $_
                }
                [System.Net.ServicePointManager]::CertificatePolicy = $DefaultPolicy
            }
        }
        # Valid Certificate
        else {
            Write-verbose 'Calling Invoke-RestMethod with valid Certificate'
            try {
                Invoke-RestMethod @SMinvokeParam -ContentType 'application/json'
            }
            catch {
                Get-SMARestError -ErrorRecord $_
            }
        }
    }
    end {
        #if ($result -notlike '0') {
        # $textError = Convert-SMRestError -interror $result.error
        # Write-Error "SEPPmail REST-API returned Error $textError"
        #}
    }
}

<#
.SYNOPSIS
    Converts (and sorts) a hashtable to an ordered hashtable
.DESCRIPTION
    If indexing is needed on a hashtable, a conversion to [ordered] is needed. This function simply does this.
#>

function ConvertTo-OrderedDictionary {
    [CmdletBinding()]
    [OutputType([Collections.Specialized.OrderedDictionary])]
    Param (
        [parameter(Mandatory = $true, ValueFromPipeline = $true)]
        $HashTable
    )
    $OrderedDictionary = [ordered]@{ }
    if ($HashTable -is [System.Collections.IDictionary]) {
        $Keys = $HashTable.Keys | Sort-Object
        foreach ($_ in $Keys) {
            $OrderedDictionary.Add($_, $HashTable[$_])
        }
    } elseif ($HashTable -is [System.Collections.ICollection]) {
        for ($i = 0; $i -lt $HashTable.count; $i++) {
            $OrderedDictionary.Add($i, $HashTable[$i])
        }
    } else {
        Write-Error "ConvertTo-OrderedDictionary - Wrong input type."
    }
    return $OrderedDictionary
}

function Get-EmailFlat {
    [CmdletBinding()]
    [OutputType([string])]
    Param (
        [parameter(Mandatory = $true, ValueFromPipeline = $true)]
        $InputObj
    )
    $emails = @()
    if ($null -eq $InputObj) { return $emails }
    foreach ($prop in $InputObj.PSObject.Properties) {
        $val = $prop.Value
        if ($val -is [PSCustomObject]) {
            foreach ($subProp in $val.PSObject.Properties) {
                $subVal = $subProp.Value
                if (($subVal -is [PSCustomObject]) -and ($subVal.PSObject.Properties["email"])) {
                    $emailValue = $subVal.email
                    if (($emailValue -is [string]) -and ($emailValue -ne "")) {
                        $emails += $emailValue
                    }
                } elseif ($subVal -is [PSCustomObject]) {
                    $emails += Get-EmailFlat $subVal
                }
            }
        }
    }
    return $emails
}

# Write-Error "API call failed: $_.Exception.Message"
# Write-Debug "Called $_.Exception.Response.RequestMessage"
<#
.SYNOPSIS
    Extracts and formats REST API error messages.
 
.DESCRIPTION
    Parses error responses from the SEPPmail API and extracts the human-readable
    error message. Handles JSON error responses and converts Unicode escape sequences.
     
.NOTES
    For module internal use only.
#>

<#
.SYNOPSIS
    Extracts and formats REST API error messages.
 
.DESCRIPTION
    Parses error responses from the SEPPmail API and extracts the human-readable
    error message. Handles JSON error responses and converts Unicode escape sequences.
     
.NOTES
    For module internal use only.
#>

function Get-SMARestError {
    [CmdletBinding()]
    param(
        [Parameter(
            Mandatory = $true,
            ValueFromPipeline = $true
        )]
        [System.Management.Automation.ErrorRecord]$ErrorRecord
    )
    
    # Versuche, den Fehlerinhalt zu extrahieren
    $errorDetails = $ErrorRecord.ErrorDetails.Message
    
    if ($errorDetails) {
        try {
            # Parse JSON-Antwort
            if ($errorDetails.count -gt 1) {
                $errorJson = $errorDetails | ConvertFrom-Json
            }
            
            # Extrahiere errorMessage
            if ($errorJson.errorMessage) {
                $message = $errorJson.errorMessage
                
                # Konvertiere Unicode-Escapes (\u0027 = ')
                $message = [System.Text.RegularExpressions.Regex]::Unescape($message)
                
                throw $message
            }
            elseif ($errorJson.message) {
                $message = [System.Text.RegularExpressions.Regex]::Unescape($errorJson.message)
                throw $message
            }
            else {
                # Kein bekanntes Fehlerfeld gefunden
                throw "API Error: $errorDetails"
            }
        }
        catch {
            # Wenn JSON-Parsing fehlschlägt, rohe Meldung verwenden
            if ($_.Exception.Message -ne $errorDetails) {
                # Es ist unsere eigene throw-Message
                throw
            }
            else {
                throw "API Error: $errorDetails"
            }
        }
    }
    elseif ($ErrorRecord.Exception.Message) {
        throw $ErrorRecord.Exception.Message
    }
    else {
        throw "Unknown API error occurred"
    }
}

function Find-SMAFilteredParameters ## DEPRECATE, replaced by Get-SMAParameterArray
{
    [CmdletBinding()]
    [OutputType([hashtable])]
    param (
        [Parameter(Mandatory = $false)]
        [hashtable]$ParentPSBoundParameters,

        [Parameter(Mandatory = $true)]
        $ParentInvocation,
        
        [Parameter(Mandatory = $true)]
        [string]$FilterName
    )
    begin{
        $allStringBoolParams = @{}
    }
    process {
        
        Write-Warning "Change to new function Get-SMAParameterArray" #TODO:
        foreach ($filterParam in $ParentInvocation) {
            $filterAttr = $filterParam.Attributes | Where-Object { 
                $_ -is [SMAParamFilterAttribute] -and $_.Filter -eq "StringBool" -and ($ParentPSBoundParameters.ContainsKey($filterParam.Name))
            }
            if ($filterAttr) {
                $allStringBoolParams[$filterParam.Name] = $ParentPSBoundParameters[$filterParam.Name]
            }
        }            
    }
    end {
        return $allStringBoolParams
    }
}

<#
.SYNOPSIS
    Categorizes function parameters based on their custom attributes.
 
.DESCRIPTION
    Analyzes parameters from a calling function and groups them into arrays based on their
    SMAParamFilterAttribute and SMARestTypeAttribute values. Returns a hashtable containing
    separate arrays for each attribute category.
 
.PARAMETER ParentPSBoundParameters
    The $PSBoundParameters hashtable from the calling function containing actual parameter values.
 
.PARAMETER ParentInvocation
    The parameter metadata from the calling function ($MyInvocation.MyCommand.Parameters.Values).
 
.OUTPUTS
    Hashtable with keys: 'StringBool', 'Path', 'Body', 'Query'
    Each key contains an array of parameter names that match the respective attribute.
    Returns $null if no categorized parameters are found.
 
.EXAMPLE
    $paramArrays = Get-SMAParameterArray -ParentPSBoundParameters $PSBoundParameters -ParentInvocation $MyInvocation.MyCommand.Parameters.Values
    # Returns: @{ StringBool = @('param1','param2'); Body = @('param3'); Query = @('param4'); Path = @() }
 
.NOTES
    For module internal use only.
#>

function Get-SMAParameterArray {
    [CmdletBinding()]
    [OutputType([hashtable])]
    param (
        [Parameter(Mandatory = $true)]
        [hashtable]$ParentPSBoundParameters,

        [Parameter(Mandatory = $true)]
        $ParentInvocation
    )
    
    begin {
        # Initialize result hashtable with empty arrays
        $result = @{
            password   = @()
            StringBool = @()
            Path       = @()
            Body       = @()
            Query      = @()
        }
        
        $hasResults = $false
    }
    
    process {
        # Iterate through all parameters from the calling function
        foreach ($param in $ParentInvocation) {
            # Only process parameters that were actually passed
            if ($ParentPSBoundParameters.ContainsKey($param.Name)) {
                
                # Check for SMAParamFilterAttribute with Filter = "StringBool"
                $filterAttr = $param.Attributes | Where-Object { 
                    $_ -is [SMAParamFilterAttribute] -and $_.Filter -eq "StringBool"
                }
                if ($filterAttr) {
                    $result.StringBool += $param.Name
                    $hasResults = $true
                    Write-Verbose "Parameter '$($param.Name)' categorized as StringBool"
                }

                # Check for SMAParamFilterAttribute with Filter = "password"
                $passwordAttr = $param.Attributes | Where-Object { 
                    $_ -is [SMAParamFilterAttribute] -and $_.Filter -eq "password"
                }
                if ($passwordAttr) {
                    $result.password += $param.Name
                    $hasResults = $true
                    Write-Verbose "Parameter '$($param.Name)' categorized as password"
                }
                
                # Check for SMARestTypeAttribute values
                $restTypeAttr = $param.Attributes | Where-Object { 
                    $_ -is [SMARestTypeAttribute]
                }
                
                foreach ($attr in $restTypeAttr) {
                    switch ($attr.RestType) {
                        'path' {
                            $result.Path += $param.Name
                            $hasResults = $true
                            Write-Verbose "Parameter '$($param.Name)' categorized as Path"
                        }
                        'optPath' {
                            $result.Path += $param.Name
                            $hasResults = $true
                            Write-Verbose "Parameter '$($param.Name)' categorized as Path (optPath)"
                        }
                        'body' {
                            $result.Body += $param.Name
                            $hasResults = $true
                            Write-Verbose "Parameter '$($param.Name)' categorized as Body"
                        }
                        'query' {
                            $result.Query += $param.Name
                            $hasResults = $true
                            Write-Verbose "Parameter '$($param.Name)' categorized as Query"
                        }
                    }
                }
            }
        }
    }
    
    end {
        # Return null if no parameters were categorized
        if (-not $hasResults) {
            Write-Verbose "No categorized parameters found"
            return $null
        }
        
        return $result
    }
}

function ConvertTo-SMAStringBoolBody {
    [CmdletBinding()]
    [OutputType([hashtable])]
    param (
        [Parameter(Mandatory = $false)]
        [hashtable]$paramInputHT
    )
    begin {
        $paramOutputHt = @{}
    }

    # $($hashtable[$key])
    process {
        try {
            Write-Verbose 'Adding optional values to $body JSON'
            foreach ($key in $paramInputHT.Keys) {
                Write-Verbose "Adding $param to bodyHt"
                if (($null -eq $($paramInputHt[$key])) -or ($($paramInputHt[$key]).length -eq 0)) {
                    $paramOutputHt.$key = $null
                }
                else {
                    $paramOutputHt.$key = $($paramInputHt[$key])
                }
            }
            return $paramOutputHt
        }
        catch {
            Write-Error "Fehler beim Erstellen des param Hasttables: $_"
            return $null
        }
    }
    end {
        # Optional: Cleanup
    }
}

function Format-SMARestResult {
    [CmdletBinding(DefaultParameterSetName = 'object')]
    [OutputType([string], ParameterSetName = 'String-pos3')]
    [OutputType([string], ParameterSetName = 'String-pos2-4')]
    [OutputType([string], ParameterSetName = 'String-pos2-6')]
    [OutputType([string], ParameterSetName = 'String-pos0-9')]
    [OutputType([string], ParameterSetName = 'String-pos0-3')]
    [OutputType([PSCustomObject], ParameterSetName = 'object')]
    [OutputType([array], ParameterSetName = 'objectaArray')]
    [OutputType([string], ParameterSetName = 'nativeJSON')]
    param (
        [Parameter(
            Mandatory = $true,
            ValueFromPipeline = $true,
            Position = 0
        )]
        [AllowNull()]
        $RestResult,

        [Parameter(
            Mandatory = $true,
            ParameterSetName = 'String-pos3',
            HelpMessage = 'Returns position 3 from space-separated string'
        )]
        [switch]$StringPos3,

        [Parameter(
            Mandatory = $true,
            ParameterSetName = 'String-pos2-4',
            HelpMessage = 'Returns positions 2-4 from space-separated string joined with spaces'
        )]
        [switch]$StringPos2to4,

        [Parameter(
            Mandatory = $true,
            ParameterSetName = 'String-pos2-6',
            HelpMessage = 'Returns positions 2-6 from space-separated string joined with spaces'
        )]
        [switch]$StringPos2to6,

        [Parameter(
            Mandatory = $true,
            ParameterSetName = 'String-pos0-9',
            HelpMessage = 'Returns positions 1-10 from space-separated string joined with spaces'
        )]
        [switch]$StringPos0to9,

        [Parameter(
            Mandatory = $true,
            ParameterSetName = 'String-pos0-3',
            HelpMessage = 'Returns positions 0-3 from space-separated string joined with spaces'
        )]
        [switch]$StringPos0to3,

        [Parameter(
            Mandatory = $true,
            ParameterSetName = 'object',
            HelpMessage = 'Returns the result as PowerShell object'
        )]
        [switch]$Object,

        [Parameter(
            Mandatory = $true,
            ParameterSetName = 'objectarray',
            HelpMessage = 'Returns the result as array of PowerShell objects'
        )]
        [switch]$ObjectArray,

        [Parameter(
            Mandatory = $true,
            ParameterSetName = 'nativeJSON',
            HelpMessage = 'Returns the result as native JSON string'
        )]
        [switch]$NativeJSON
    )

    begin {
        Write-Verbose "Format-SMARestResult called with parameter set: $($PSCmdlet.ParameterSetName)"
    }

    process {
        # Handle null or empty results
        if ($null -eq $RestResult) {
            Write-Verbose "RestResult is null, returning null"
            return $null
        }

        try {
            switch ($PSCmdlet.ParameterSetName) {
                'String-pos3' {
                    Write-Verbose "Formatting as String-pos3: extracting position 3"
                    if ($RestResult.message -is [string]) {
                        $splitResult = $RestResult.message -split ' '
                        if ($splitResult.Count -gt 3) {
                            return $splitResult[2]
                        }
                        else {
                            Write-Warning "String has less than 4 elements, returning full string"
                            return $RestResult
                        }
                    }
                    else {
                        Write-Warning "RestResult is not a string, converting to string first"
                        $stringResult = $RestResult.message.ToString()
                        $splitResult = $stringResult -split ' '
                        if ($splitResult.Count -gt 3) {
                            return $splitResult[3]
                        }
                        else {
                            return $stringResult
                        }
                    }
                }

                'String-pos2-4' {
                    Write-Verbose "Formatting as String-pos2-4: extracting positions 2-4"
                    if ($RestResult.message -is [string]) {
                        $splitResult = $RestResult.message -split ' '
                        if ($splitResult.Count -gt 5) {
                            return ($splitResult[2..4] -join ' ')
                        }
                        elseif ($splitResult.Count -gt 3) {
                            Write-Warning "String has less than 5 elements, returning available positions"
                            return ($splitResult[2..($splitResult.Count - 1)] -join ' ')
                        }
                        else {
                            Write-Warning "String has less than 3 elements, returning full string"
                            return $RestResult
                        }
                    }
                    else {
                        Write-Warning "RestResult is not a string, converting to string first"
                        $stringResult = $RestResult.message.ToString()
                        $splitResult = $stringResult -split ' '
                        if ($splitResult.Count -gt 5) {
                            return ($splitResult[2..4] -join ' ')
                        }
                        else {
                            return $stringResult
                        }
                    }
                }

                'String-pos2-6' {
                    Write-Verbose "Formatting as String-pos2-6: extracting positions 2-6"
                    if ($RestResult.message -is [string]) {
                        $splitResult = $RestResult.message -split ' '
                        if ($splitResult.Count -gt 7) {
                            return ($splitResult[2..6] -join ' ')
                        }
                        elseif ($splitResult.Count -gt 3) {
                            Write-Warning "String has less than 7 elements, returning available positions"
                            return ($splitResult[2..($splitResult.Count - 1)] -join ' ')
                        }
                        else {
                            Write-Warning "String has less than 3 elements, returning full string"
                            return $RestResult
                        }
                    }
                    else {
                        Write-Warning "RestResult is not a string, converting to string first"
                        $stringResult = $RestResult.message.ToString()
                        $splitResult = $stringResult -split ' '
                        if ($splitResult.Count -gt 7) {
                            return ($splitResult[2..6] -join ' ')
                        }
                        else {
                            return $stringResult
                        }
                    }
                }

                'String-pos0-9' {
                    Write-Verbose "Formatting as String-pos0-9: extracting positions 0-9"
                    if ($RestResult.message -is [string]) {
                        $splitResult = $RestResult.message -split ' '
                        if ($splitResult.Count -eq 10) {
                            return ($splitResult[0..9] -join ' ')
                        }
                        else {
                            Write-Warning "String has less than 10 elements, returning full string"
                            return $RestResult
                        }
                    }
                    else {
                        Write-Warning "RestResult is not a string, converting to string first"
                        $stringResult = $RestResult.message.ToString()
                        $splitResult = $stringResult -split ' '
                        if ($splitResult.Count -gt 9) {
                            return ($splitResult[0..9] -join ' ')
                        }
                        else {
                            return $stringResult
                        }
                    }
                }

                'String-pos0-3' {
                    Write-Verbose "Formatting as String-pos0-3: extracting positions 0-3"
                    if ($RestResult.message -is [string]) {
                        $splitResult = $RestResult.message -split ' '
                        if ($splitResult.Count -gt 3) {
                            return ($splitResult[0..3] -join ' ')
                        }
                        else {
                            Write-Warning "String has less than 4 elements, returning full string"
                            return $RestResult
                        }
                    }
                    else {
                        Write-Warning "RestResult is not a string, converting to string first"
                        $stringResult = $RestResult.message.ToString()
                        $splitResult = $stringResult -split ' '
                        if ($splitResult.Count -gt 3) {
                            return ($splitResult[0..3] -join ' ')
                        }
                        else {
                            return $stringResult
                        }
                    }
                }


                'object' {
                    Write-Verbose "Formatting as Object: returning raw object"
                    return $RestResult
                }

                'objectarray' {
                    Write-Verbose "Formatting as ObjectArray: ensuring array output"
                    if ($RestResult -is [array]) {
                        return $RestResult
                    }
                    else {
                        return @($RestResult)
                    }
                }

                'nativeJSON' {
                    Write-Verbose "Formatting as NativeJSON: converting to JSON string"
                    if ($RestResult -is [string]) {
                        # Already a string, assume it's JSON
                        return $RestResult
                    }
                    else {
                        # Convert object to JSON
                        return ($RestResult | ConvertTo-Json -Depth 10 -Compress:$false)
                    }
                }

                default {
                    Write-Warning "Unknown parameter set: $($PSCmdlet.ParameterSetName), returning raw object"
                    return $RestResult
                }
            }
        }
        catch {
            Write-Error "Error formatting REST result: $_"
            return $RestResult
        }
    }

    end {
        Write-Verbose "Format-SMARestResult completed"
    }
}

<#
.SYNOPSIS
Generates a unique report filename based on the current time and the default domain name.
 
.DESCRIPTION
The `New-SelfGeneratedReportName` function creates a unique, self-generated report filename.
The filename includes the current time in `HHm-ddMMyyy` format and the default email domain name,
retrieved from the output of the `Get-AcceptedDomain` cmdlet. The filename is appended with `.html`.
 
.EXAMPLE
PS> New-SelfGeneratedReportName
 
This will return a string similar to `1507-10112024defaultdomain.com.html`, where:
- `1507` represents the current time in hours and minutes.
- `10112024` represents the date in `ddMMyyyy` format.
- `defaultdomain.com` is the default email domain.
 
.PARAMETER None
The function does not accept parameters.
 
.RETURNS
String
A string representing the self-generated filename.
 
.NOTES
- Ensure the `Get-AcceptedDomain` cmdlet is available and provides a `default` property to identify the default domain.
- The function requires the `-ExpandProperty` flag in `Select-Object` to retrieve the `Domainname` property.
 
.REQUIREMENTS
- PowerShell 5.1 or later
- Exchange Online PowerShell module or other modules providing the `Get-AcceptedDomain` cmdlet.
#>

function New-SelfGeneratedReportName {
    Write-Verbose "Creating self-generated report filename."
    return ("{0:HHm-ddMMyyy}" -f (Get-Date)) + '.html'
}

function ConvertTo-PfxBase64 {
    param (
        [Parameter(Mandatory=$true)]
        [string]$PfxPath,

        [Parameter(Mandatory=$true)]
        [securestring]$Password
    )

    try {
        # Zertifikat aus der PKCS12-Datei laden
        $cert = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2(
            $PfxPath,
            (Convertfrom-Securestring -SecureString $Password -AsPlainText),
            [System.Security.Cryptography.X509Certificates.X509KeyStorageFlags]::Exportable
        )

        # Zertifikat in ein Byte-Array exportieren (als PFX)
        $pfxBytes = $cert.Export(
            [System.Security.Cryptography.X509Certificates.X509ContentType]::Pfx,
            (Convertfrom-Securestring -SecureString $Password -AsPlainText)
        )

        # Byte-Array als Base64-codierten String zurückgeben
        $base64String = [System.Convert]::ToBase64String($pfxBytes)

        return $base64String
    }
    catch {
        Write-Error "Fehler beim Lesen oder Konvertieren der PKCS12-Datei: $_"
        return $null
    }
}

<#
Experiment ob verschachtelte PSObjects gut dargestellt werden könnten.
 
function ConvertTo-SMAFlatObject {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true, ValueFromPipeline = $true)]
        $InputObject,
 
        [Parameter(Mandatory = $false)]
        [string]$Delimiter = '.',
 
        [Parameter(Mandatory = $false)]
        [int]$MaxDepth = 10,
 
        [Parameter(Mandatory = $false)]
        [switch]$AsKeyValue,
 
        [Parameter(Mandatory = $false)]
        [switch]$IncludeNull
    )
 
    begin {
        function Add-FlatEntry {
            param (
                [hashtable]$Table,
                [string]$Key,
                $Value
            )
            $finalKey = $Key
            $i = 1
            while ($Table.ContainsKey($finalKey)) {
                $i++
                $finalKey = "{0}_{1}" -f $Key, $i
            }
            $Table[$finalKey] = $Value
        }
 
        function Flatten-Object {
            param (
                $Obj,
                [string]$Prefix,
                [int]$Depth,
                [hashtable]$Table
            )
 
            if ($Depth -gt $MaxDepth) {
                Add-FlatEntry -Table $Table -Key $Prefix -Value ($Obj | ConvertTo-Json -Depth 5 -Compress)
                return
            }
 
            if ($null -eq $Obj) {
                if ($IncludeNull) {
                    Add-FlatEntry -Table $Table -Key $Prefix -Value $null
                }
                return
            }
 
            if ($Obj -is [System.Collections.IDictionary]) {
                foreach ($key in $Obj.Keys) {
                    $nextPrefix = if ($Prefix) { "$Prefix$Delimiter$key" } else { "$key" }
                    Flatten-Object -Obj $Obj[$key] -Prefix $nextPrefix -Depth ($Depth + 1) -Table $Table
                }
                return
            }
 
            if (($Obj -is [System.Collections.IEnumerable]) -and -not ($Obj -is [string])) {
                $idx = 0
                foreach ($item in $Obj) {
                    $nextPrefix = if ($Prefix) { "$Prefix[$idx]" } else { "[$idx]" }
                    Flatten-Object -Obj $item -Prefix $nextPrefix -Depth ($Depth + 1) -Table $Table
                    $idx++
                }
                return
            }
 
            $props = $Obj.PSObject.Properties
            if ($props -and $props.Count -gt 0) {
                foreach ($prop in $props) {
                    $nextPrefix = if ($Prefix) { "$Prefix$Delimiter$($prop.Name)" } else { "$($prop.Name)" }
                    Flatten-Object -Obj $prop.Value -Prefix $nextPrefix -Depth ($Depth + 1) -Table $Table
                }
                return
            }
 
            Add-FlatEntry -Table $Table -Key $Prefix -Value $Obj
        }
    }
 
    process {
        $flat = @{}
        Flatten-Object -Obj $InputObject -Prefix '' -Depth 0 -Table $flat
 
        if ($AsKeyValue) {
            return $flat.GetEnumerator() | Sort-Object Name | ForEach-Object {
                [pscustomobject]@{
                    Name = $_.Name
                    Value = $_.Value
                }
            }
        }
 
        return [pscustomobject]$flat
    }
}
#>


# Beispielaufruf:
# $securePassword = "DeinPasswort" | ConvertTo-SecureString -AsPlainText -Force
# $base64Pfx = ConvertTo-PfxBase64 -PfxPath "C:\Pfad\zu\datei.p12" -Password $securePassword
# Write-Output $base64Pfx


# Optional: Cleanup
# SIG # Begin signature block
# MIIVyAYJKoZIhvcNAQcCoIIVuTCCFbUCAQExDzANBglghkgBZQMEAgEFADB5Bgor
# BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG
# KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCAgHQeQP+c2UtiU
# v99HmrnfHvbKlpD7j53ST2PdCyrmfqCCEgQwggVvMIIEV6ADAgECAhBI/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
# AQQBgjcCARUwLwYJKoZIhvcNAQkEMSIEIHcVrKpy41sP4TufZw3hbxcPz3Si1CUY
# 0GnLx9X2lNWkMA0GCSqGSIb3DQEBAQUABIICAAgvh2rhFcw5TlU5nDXJr0wNmwEF
# nMImHdhuNGmfGcPN+ry2o5pzDUd3EBuOq5joIyLTMDCPucJHRB8rnxZyd5gBfwhz
# BIE0IST5mmTGmOWWtgb0WFfj4aGKKMBJFTC0JhRVKHz/GHSk+8tEnw8/gRy/CEjQ
# wcueM4RpdBL1LzBDS6Ta163PFaL6TydQZl+zc/9Bnk65CfAsSK3bK+h83TlKTrXL
# FHiJyQzt40m5egT63Fzj+cM3QvajJbeuGC7MSSRxq/xG8q/R3K9FiyNl57dHy54Y
# nlCqi5tCv3vnkt3IuVpL6NIeU1yb8eKBC9H8XEcY1Fh1XbZsth9kIlpVmeLgR7gC
# QNZCbkOR+70PHEfJeLidY9zVodQnEs93b4Xpb7o2FjSQDFmViTjYvKekT/MpMoo+
# j3pfmQJu9k8ZoCTDNK9Av6s91PrifNCvakoEfBtTb/GkIlNU0juSnD7EWZZ+rg/W
# X/pAb/stMocXnoTqwxtaCB8ABjcwwPYqBWMGQWPLXsfQ6+Wy3Q59FSqmPsZu/wxM
# 68vKnJOpJESeTsW1wexM2KTgfsIbe+ajwH5AZCjrF7NQPc6V1MyabKv29YnK3ICK
# MQrFE6/B+rM4Ign0wyEObr6jjnqvMevLyd/nLGgYCGrLvED+b5EN8sfBpZ/jFDs6
# LHT+R9QbChSlonmZ
# SIG # End signature block