Dracoon.psm1

$script:ModuleRoot = $PSScriptRoot
$script:ModuleVersion = (Import-PowerShellDataFile -Path "$($script:ModuleRoot)\Dracoon.psd1").ModuleVersion

# Detect whether at some level dotsourcing was enforced
$script:doDotSource = Get-PSFConfigValue -FullName Dracoon.Import.DoDotSource -Fallback $false
if ($Dracoon_dotsourcemodule) { $script:doDotSource = $true }

<#
Note on Resolve-Path:
All paths are sent through Resolve-Path/Resolve-PSFPath in order to convert them to the correct path separator.
This allows ignoring path separators throughout the import sequence, which could otherwise cause trouble depending on OS.
Resolve-Path can only be used for paths that already exist, Resolve-PSFPath can accept that the last leaf my not exist.
This is important when testing for paths.
#>


# Detect whether at some level loading individual module files, rather than the compiled module was enforced
$importIndividualFiles = Get-PSFConfigValue -FullName Dracoon.Import.IndividualFiles -Fallback $false
if ($Dracoon_importIndividualFiles) { $importIndividualFiles = $true }
if (Test-Path (Resolve-PSFPath -Path "$($script:ModuleRoot)\..\.git" -SingleItem -NewChild)) { $importIndividualFiles = $true }
if ("<was compiled>" -eq '<was not compiled>') { $importIndividualFiles = $true }
    
function Import-ModuleFile
{
    <#
        .SYNOPSIS
            Loads files into the module on module import.
         
        .DESCRIPTION
            This helper function is used during module initialization.
            It should always be dotsourced itself, in order to proper function.
             
            This provides a central location to react to files being imported, if later desired
         
        .PARAMETER Path
            The path to the file to load
         
        .EXAMPLE
            PS C:\> . Import-ModuleFile -File $function.FullName
     
            Imports the file stored in $function according to import policy
    #>

    [CmdletBinding()]
    Param (
        [string]
        $Path
    )
    
    $resolvedPath = $ExecutionContext.SessionState.Path.GetResolvedPSPathFromPSPath($Path).ProviderPath
    if ($doDotSource) { . $resolvedPath }
    else { $ExecutionContext.InvokeCommand.InvokeScript($false, ([scriptblock]::Create([io.file]::ReadAllText($resolvedPath))), $null, $null) }
}

#region Load individual files
if ($importIndividualFiles)
{
    # Execute Preimport actions
    foreach ($path in (& "$ModuleRoot\internal\scripts\preimport.ps1")) {
        . Import-ModuleFile -Path $path
    }
    
    # Import all internal functions
    foreach ($function in (Get-ChildItem "$ModuleRoot\internal\functions" -Filter "*.ps1" -Recurse -ErrorAction Ignore))
    {
        . Import-ModuleFile -Path $function.FullName
    }
    
    # Import all public functions
    foreach ($function in (Get-ChildItem "$ModuleRoot\functions" -Filter "*.ps1" -Recurse -ErrorAction Ignore))
    {
        . Import-ModuleFile -Path $function.FullName
    }
    
    # Execute Postimport actions
    foreach ($path in (& "$ModuleRoot\internal\scripts\postimport.ps1")) {
        . Import-ModuleFile -Path $path
    }
    
    # End it here, do not load compiled code below
    return
}
#endregion Load individual files

#region Load compiled code
<#
This file loads the strings documents from the respective language folders.
This allows localizing messages and errors.
Load psd1 language files for each language you wish to support.
Partial translations are acceptable - when missing a current language message,
it will fallback to English or another available language.
#>

Import-PSFLocalizedString -Path "$($script:ModuleRoot)\en-us\*.psd1" -Module 'Dracoon' -Language 'en-US'

import-module PSFramework

Class Dracoon {
    hidden [string]$webServiceRoot
    [string]$serverRoot
    hidden static [string]$dateFormat = 'yyyy-MM-dd'
    hidden static [int]$maxRecursionLevelSSP = 50

    hidden [int]$homeRoomParentId
    hidden [int]$projectsRoomParentId
    hidden [int]$openIDConfigId = 4
    hidden [int]$adminGroupId#=6
    hidden [String] $authToken
    [String] $authenticatedUser
    [switch] $verbose
    $oauthToken_Expires_in
    $headers = @{ }
    Dracoon ([PSCredential]$credentials, [string]$baseUrl) {
        # [System.Net.ServicePointManager]::SecurityProtocol = [System.Net.SecurityProtocolType]::Tls12
        Write-PSFMessage "Verbinde Dracoon $baseUrl mittels Credentials-Objekt"

        $this.serverRoot = Get-DracoonServerRoot $baseUrl
        $this.webServiceRoot = "$($this.serverRoot)/api"
        $this.Login($credentials)
    }
    Dracoon ([String]$AccessToken, [string]$baseUrl) {
        # [System.Net.ServicePointManager]::SecurityProtocol = [System.Net.SecurityProtocolType]::Tls12
        Write-PSFMessage "Verbinde Dracoon $baseUrl mittels AccessToken $AccessToken" -functionName "[Dracoon]::new(AccessToken, baseUrl)"

        $this.serverRoot = Get-DracoonServerRoot $baseUrl
        $this.webServiceRoot = "$($this.serverRoot)/api"
        $this.Login($AccessToken)
    }
    hidden Login([PSCredential]$credentials) {

        # [void][System.Reflection.Assembly]::LoadWithPartialName("System.Web.Extensions")
        # $this.jsonserial = New-Object -TypeName System.Web.Script.Serialization.JavaScriptSerializer
        # $this.jsonserial.MaxJsonLength = 2147483647
        $parameter = @{login = $credentials.UserName; password = $credentials.GetNetworkCredential().Password; language = "1"; authType = "sql" }
        $result = Invoke-DracoonAPI -connection $this -path "/v4/auth/login"  -body $parameter -method Post -hideParameters $true
        Write-PSFMessage $result
        $this.authToken = $result.token
        $this.authenticatedUser = $credentials.UserName
        $this.headers.Add("X-Sds-Auth-Token", $this.authToken)
        Write-PSFMessage "This-Type: $($this.gettype())"
    }

    hidden Login([string]$AccessToken) {
        Write-PSFMessage "Login with AccessToken <$AccessToken>" -Level Debug
        if ($AccessToken){
            $this.authToken = "Bearer $AccessToken"
            #$this.oauthToken_Expires_in = $tokenContent.expires_in
            $this.authenticatedUser = "OAuth"
            $this.headers.Add("Authorization", $this.authToken)
        }else{throw "AccessToken is null"}
    }
# [PSCustomObject]GetGroups([string]$filter) {
# $groups = $this.InvokeGet("/v4/groups?filter=$filter")
# return $groups.items
# }
# [PSCustomObject]GetGroups() {
# return $this.GetGroups("")
# }
# [PSCustomObject]GetGroupMembers([int]$groupId, [string]$filter) {
# $groups = $this.InvokeGet("/v4/groups/$groupId/users?filter=$filter")
# return $groups.items
# }
# [PSCustomObject]GetGroupMembers([int]$groupId) {
# return $this.GetGroupMembers($groupId, "isMember:eq:true")
# }
# [PSCustomObject]AddGroupMembers([int]$groupId, [int[]]$userIdArray) {
# $parameter = @{ids = $userIdArray }
# $result = $this.InvokePost("/v4/groups/$groupId/users", $parameter)
# return $result
# }

# [PSCustomObject]CreateGroup([string]$groupName) {
# $parameter = @{name = "$groupName" }
# $result = $this.InvokePost("/v4/groups", $parameter)
# #if ($verbose) {Log-Message ("Authentication-Token: {0}" -f $result.token)}
# Write-PSFMessage $result
# return $result
# }
# [PSCustomObject]DeleteUser([int]$id) {
# $result = $this.DeleteUser($id, $false)
# return $result
# }
# [PSCustomObject]GetLastAdminRooms([int]$userId) {
# $result = $this.InvokeGet("/v4/users/$userId/last_admin_rooms")
# return $result.items
# }
# [PSCustomObject]GetUserDetails([int]$userId) {
# $result = $this.InvokeGet("/v4/users/$userId")
# return $result
# }
# [PSCustomObject]GetCurrentUserInfos() {
# $result = $this.InvokeGet("/v4/user/account")
# return $result
# }
# [PSCustomObject]UpdateUser([int]$userId, [Hashtable]$metaData) {
# $result = $this.InvokePut("/v4/users/$userId", $metaData)
# return $result
# }
# [PSCustomObject]SetUserAttributes([int]$userId, [Hashtable]$userAttributes) {
# $result = $this.SetUserAttributes($userId, $userAttributes, $false)
# return $result
# }
# [PSCustomObject]AddUserAttributes([int]$userId, [Hashtable]$userAttributes) {
# $result = $this.SetUserAttributes($userId, $userAttributes, $true)
# return $result
# }

# [Hashtable]GetUserAttributes([int]$userId) {
# $attributes = @{ }
# $userDetails = $this.GetUserDetails($userId)
# foreach ($item in $userDetails.userAttributes.items) {
# $attributes.add($item.key, $item.value)
# }
# return $attributes
# }
# [PSCustomObject]LockUser([int]$userId, [bool]$lock) {
# return $this.UpdateUser($userId, @{isLocked = $lock })
# return $result
# }
# [PSCustomObject]CreateMailUser([string]$login, [string]$firstName, [string]$lastName, [string]$title, [string]$gender, [string]$eMail) {
# return $this.CreateMailUser($login, $firstName, $lastName, $title, $gender, $eMail, $false)
# }
# [PSCustomObject]CreateMailUser([string]$login, [string]$password, [string]$firstName, [string]$lastName, [string]$title, [string]$gender, [string]$eMail, [bool]$notifyUser) {
# return $this.CreateMailUser($login, $password, $firstName, $lastName, $title, $gender, $eMail, $notifyUser, $false)
# }
# [PSCustomObject]CreateMailUser([string]$login, [string]$password, [string]$firstName, [string]$lastName, [string]$title, [string]$gender, [string]$eMail, [bool]$notifyUser, [bool]$enableOpenID) {
# $parameter = @{firstName = $firstName; lastName = $lastName; authMethods = @(@{authId = "sql"; isEnabled = "true" }); login = $login; title = $title; gender = $gender; expiration = @{enableExpiration = "false"; expireAt = "2018-01-01T00:00:00" }; receiverLanguage = "de-DE"; email = $eMail; notifyUser = "$notifyUser"; needsToChangePassword = "true"; password = $password }
# if ($enableOpenID) {
# $parameter["authMethods"] += (@{authId = "openid"; isEnabled = $true; options = @(@{key = "openid_config_id"; value = $this.openIDConfigId }; @{key = "username"; value = $eMail }) })
# }
# $result = $this.InvokePost("/v4/users", $parameter)
# return $result
# }
# [PSCustomObject]UploadFile([string]$fullFilePath, [int]$parentNodeId) {
# return $this.UploadFile((get-item $fullFilePath), $parentNodeId)
# }
# [PSCustomObject]UploadFile([System.IO.FileSystemInfo]$fullFilePath, [int]$parentNodeId) {
# $result = $null
# Write-PSFMessage "Lade Datei hoch: $fullFilePath"
# $parameter = @{"parentId" = $parentNodeId; "name" = $fullFilePath.Name; "classification" = 2; "size" = $fullFilePath.length; "expiration" = @{"enableExpiration" = $false; "expireAt" = "2018-01-01T00:00:00" }; "notes" = "" }
# $initUpload = $this.InvokePost("/v4/nodes/files/uploads", $parameter)
# try {
# $result = Invoke-RestMethod $initUpload.uploadUrl -ContentType "application/octet-stream" -Method Post -Headers $this.headers -InFile $fullFilePath.FullName

# Write-PSFMessage $result
# $result = $this.Invoke(("/v4/uploads/{0}" -f $initUpload.token), $null, [Microsoft.Powershell.Commands.WebRequestMethod]::Put, $false)
# }
# catch {
# Write-PSFMessage "Fehler $_"
# $this.InvokeDelete(("/v4/uploads/{0}" -f $initUpload.token))
# throw $_
# }
# return $result
# }
# # [PSCustomObject]GetEventLog([int]$limit, [int]$offset, [string]$filter) {
# [PSCustomObject]GetEventLog([int]$limit, [int]$offset, [hashtable]$additionalParamater) {
# $parameterTable = @{limit = $limit; offset = $offset }
# $parameterTable += $additionalParamater
# # $uri = "/v4/eventlog/events?type=6&limit=$limit&offset=$offset"
# # if ($filter) {
# # $uri += "&$filter"
# # }
# $events = $this.InvokeGet("/v4/eventlog/events", $parameterTable)
# return $events
# }
# [PSCustomObject]GetNode([int]$nodeId) {
# $node = $this.InvokeGet("/v4/nodes/$nodeId")
# return $node
# }
# [PSCustomObject]GetNodeList([hashtable]$parameterTable) {
# $nodes = $this.InvokeGet("/v4/nodes", $parameterTable)
# return $nodes
# }
# [PSCustomObject]GetParents([int]$nodeId) {
# $parents = $this.InvokeGet("/v4/nodes/$nodeId/parents")
# return $parents
# }
# [PSCustomObject]SearchNode([int]$parentNode, [string]$searchString, [string]$filter, [string]$sort) {
# $parameterTable = @{search_string = $searchString; filter = $filter; sort = $sort; parent_id = $parentNode }
# $nodes = $this.InvokeGet("/v4/nodes/search", $parameterTable)
# return $nodes
# }
# [PSCustomObject]MoveNodes([int]$targetNodeId, [int[]]$nodesToMove) {
# Write-PSFMessage "Verschiebe nach $targetNodeId : $nodesToMove"
# $parameter = @{resolutionStrategy = "overwrite"; keepShareLinks = $true; nodeIds = $nodesToMove }
# $result = $this.InvokePost("/v4/nodes/$targetNodeId/move_to", $parameter)
# return $result
# }
# [PSCustomObject]CopyNodes([int]$targetNodeId, [int[]]$nodesToMove) {
# Write-PSFMessage "Kopiere nach $targetNodeId : $nodesToMove"
# $parameter = @{resolutionStrategy = "overwrite"; keepShareLinks = $true; nodeIds = $nodesToMove }
# $result = $this.InvokePost("/v4/nodes/$targetNodeId/copy_to", $parameter)
# return $result
# }
# [PSCustomObject]CreateFolder([int]$parentNodeId, [string]$name, [string]$notes) {
# Write-PSFMessage "Lege Ordner $name unter der Node $parentNodeId an, Comment=$notes"
# $parameter = @{parentId = $parentNodeId; name = $name; notes = $notes }
# $result = $this.InvokePost("/v4/nodes/folders", $parameter)
# return $result
# }
}

function Get-DracoonServerRoot {
<#
.SYNOPSIS
Cleans a given URL to be used as a server-root.
 
.DESCRIPTION
Cleans a given URL to be used as a server-root.
 
.PARAMETER Url
A FQDN/URL or the server
 
.EXAMPLE
Get-DracoonServerRoot "my.server.de"
Get-DracoonServerRoot "my.server.de/"
Get-DracoonServerRoot "http://my.server.de"
Get-DracoonServerRoot "http://my.server.de/"
All versions return "https://my.server.de"
 
.NOTES
General notes
#>

    param (
        [parameter(mandatory = $true, Position=0)]
        [string]$Url
    )
    Write-PSFMessage "Getting Dracoon Server-Root for $Url"
    # Strip leading /
    $serverRoot = $Url.Trim("/")
    # Strip Prefix protocoll
    $serverRoot = $serverRoot -replace "^.*:\/\/"
    $serverRoot="https://$serverRoot"
    Write-PSFMessage "Result: $serverRoot"
    $serverRoot
}

function Get-EncodedParameterString {
    <#
    .SYNOPSIS
    Converts a hashtable to GET Parameters
 
    .DESCRIPTION
    Converts a hashtable to GET Parameters.
 
    .PARAMETER parameter
    Hashtable with the GET Parameters
 
    .EXAMPLE
    Get-EncodedParameterString -parameter @{key1="value1";key2="value2"}
    Returns the string: key1=value1&key2=value2
 
    .NOTES
    General notes
    #>


    param (
        [parameter(Mandatory,Position=1)]
        [Hashtable]$parameter
    )
    $getParameter = @()
    if ($parameter) {
        foreach ($key in $parameter.Keys) {
            $value = $parameter[$key]
            if ($value) {
                switch ($value.GetType()) {
                    bool { $value = $value.ToString() }
                    Default { }
                }
                $valueEncoded = [System.Web.HttpUtility]::UrlEncode($value)
                $getParameter += "$key=$valueEncoded"
            }
        }
    }
    return ($getParameter -join "&")
}

function New-DracoonRandomPassword {
    <#
    .SYNOPSIS
    Creates a new random password.
 
    .DESCRIPTION
    Creates a new random password with the following rules:
    -12 characters long
    -at least one lower case character
    -at least one upper case character
    -at least one number
    -at least one special character
 
    .EXAMPLE
    $newPassword=New-DracoonRandomPassword
    Creates a new password
 
    .NOTES
    General notes
    #>

    [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseShouldProcessForStateChangingFunctions', '')]
    param()
    process {
        [int]$PasswordLength = 12

        # Specifies an array of strings containing charactergroups from which the password will be generated.
        # At least one char from each group (string) will be used.
        [String[]]$InputStrings = @('abcdefghijkmnpqrstuvwxyz', 'ABCEFGHJKLMNPQRSTUVWXYZ', '23456789', '!"#%')

        $Password = @{ }
        # Create char arrays containing groups of possible chars
        [char[][]]$CharGroups = $InputStrings

        # Create char array containing all chars
        $AllChars = $CharGroups | ForEach-Object { [Char[]]$_ }

        # Randomize one char from each group
        Foreach ($Group in $CharGroups) {
            if ($Password.Count -lt $PasswordLength) {
                $Index = Get-DracoonSeed
                While ($Password.ContainsKey($Index)) {
                    $Index = Get-DracoonSeed
                }
                $Password.Add($Index, $Group[((Get-DracoonSeed) % $Group.Count)])
            }
        }

        # Fill out with chars from $AllChars
        for ($i = $Password.Count; $i -lt $PasswordLength; $i++) {
            $Index = Get-DracoonSeed
            While ($Password.ContainsKey($Index)) {
                $Index = GetSeed
            }
            $Password.Add($Index, $AllChars[((Get-DracoonSeed) % $AllChars.Count)])
        }
        return $( -join ($Password.GetEnumerator() | Sort-Object -Property Name | Select-Object -ExpandProperty Value))
    }
}
function Get-DracoonSeed {
    # Generate a seed for randomization
    $RandomBytes = New-Object -TypeName 'System.Byte[]' 4
    $Random = New-Object -TypeName 'System.Security.Cryptography.RNGCryptoServiceProvider'
    $Random.GetBytes($RandomBytes)
    return [BitConverter]::ToUInt32($RandomBytes, 0)
}

function Remove-NullFromHashtable {
    <#
    .SYNOPSIS
    Funktion zum Entfernen von $null Werten aus HashTables.
 
    .DESCRIPTION
    Funktion zum Entfernen von $null Werten aus HashTables. Dazu wird die HashTable in einen json String umgewandelt
    und anschließend per RegEx alle null-Werte entfernt. Falls InputHashmap -eq $null dann wird nichts zurückgegeben.
 
    .PARAMETER InputHashmap
    Die Eingabe-HashTable
 
    .PARAMETER Json
    Wird der Parameter gesetzt, so wird anstatt einer neuen HashTable der modifizierte json String zurück geliefert
 
    .EXAMPLE
    $hash=@{
        Name="Max"
        Surname="Mustermann"
        MiddleName=$null
        kids=@(
            @{
                Name="Maxi"
                MiddleName=$null
                Surname="Mustermann"
            },
            @{
                Name="Maxine"
                MiddleName="Christine"
        Surname="Mustermann"
            }
        )
    }
    $newHash=$hash | Remove-NullFromHashtable
    .NOTES
    General notes
    #>

    [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseShouldProcessForStateChangingFunctions', '')]
    param (
        [Parameter(ValueFromPipeline)]
        [hashtable]$InputHashmap,
        [switch]$Json
    )

    process {
        if ($InputHashmap) {
            $tempString = $InputHashmap | ConvertTo-Json -Depth 15
            Write-PSFMessage -Level Debug "Entferne null Values aus $tempString"
            $tempString = $tempString -replace '"\w+?"\s*:\s*null,?'
            Write-PSFMessage -Level Debug "Ergebnis: $tempString"
            if ($Json) { $tempString }else {
                Write-PSFMessage -Level Debug "Erzeuge HashTable"
                $newHashmap = $tempString | ConvertFrom-Json
                $newHashmap
            }
        }
    }
}

function Write-DracoonAPICallMessage {
    <#
    .SYNOPSIS
    Writes information about an API invocation to the PFS Logging.
 
    .DESCRIPTION
    Writes information about an API invocation to the PFS Logging.
 
    .PARAMETER APICallParameter
    HashTable with all parameters forwarded from Invoke-DracoonAPI to Invoke-RestMethod
 
    .EXAMPLE
    Write-DracoonAPICallMessage $restAPIParameter
    Logs the given parameter to the PSFLogging with Tag "APICALL"
 
    .EXAMPLE
    Get-PSFMessage -Tag "APICALL" |Select-Object -Last 1 -Property TargetObject,Message|Format-List
    Retrieves the last API call.
 
    .NOTES
    Logs are compressed by default. For troubleshooting set it to uncrompressed with
    Set-PSFConfig -Module 'Dracoon' -Name 'logging.compressRequests' -Value $false
    #>

    param (
        [Parameter(Position = 0, Mandatory = $true)]
        $APICallParameter
    )
    try {
        $compress=Get-PSFConfigValue -FullName 'Dracoon.logging.compressRequests' -Fallback $true
        $modifiedAPICallParameter = $APICallParameter.Clone()
        if ($modifiedAPICallParameter.Body -is [String]) {
           $modifiedAPICallParameter.Body = $modifiedAPICallParameter.Body | ConvertFrom-Json
        }
        $modifiedAPICallParameter.Method = "$($modifiedAPICallParameter.Method)".ToUpper()
        $callStack = (Get-PSCallStack | Select-Object -SkipLast 1 -ExpandProperty Command | Select-Object -Skip 1  )
        [Array]::Reverse($callStack)
        $callStackString = $callStack -join ">"
        Write-PSFMessage "CallStack: $callStackString"
        $apiLogString = ($modifiedAPICallParameter | ConvertTo-Json -Depth 7 -Compress:$compress)
        # Remove confidental data
        $apiLogString = $apiLogString -replace '"Bearer (\w{5})\w*"', '"Bearer $1******************"'
        $apiLogString = $apiLogString -replace '("password":\s*").*"', '$1***********"'
        $apiLogString = $apiLogString -replace '("refresh_token":\s*").*"', '$1***********"'
        $apiLogString = $apiLogString -replace '("code":\s*").*"', '$1***********"'
        $apiLogString = $apiLogString -replace '("X-Sds-Auth-Token":\s*").*"', '$1***********"'
        $apiLogString = $apiLogString -replace '("Authorization":\s*"Basic ).*"', '$1[BASE64_ENCODED [CLIENT_ID]:[CLIENT_SECRET]]"'
        Write-PSFMessage -Level Verbose "$apiLogString" -Tag "APICALL" -Target "$callStackString"
    }
    catch {
        Write-PSFMessage -Level Critical "Could not log API Call $_"
    }
}

function Add-DracoonUrl {
    <#
    .SYNOPSIS
    This function allows the addition of new Server-URLs for TAB Completion.
    Each function which requires a -Url parameter will provide a TAB completer with suggested URLs.
 
    .DESCRIPTION
    This function allows the addition of new Server-URLs for TAB Completion.
    Each function which requires a -Url parameter will provide a TAB completer with suggested URLs,
    e.g. Connect-Dracoon
 
    Different from Set-DracoonUrl this command does not overwrite but append to existing settings.
 
    .PARAMETER NewUrl
    The new URLs to be added
 
    .PARAMETER whatIf
    If enabled it does not execute the backend API call.
 
    .PARAMETER confirm
    If enabled the backend API Call has to be confirmed
 
    .EXAMPLE
    Add-DracoonUrl 'https://dxi.mydomain'
    Add a single Server to the list of suggested URLs
 
    (get-adforest -ErrorAction Stop).domains | ForEach-Object { Add-DracoonUrl "https://dataexchange.$($_)" }
    If you have an on prem Dracoon server in each of your Windows Domains with the address "https://dracoon.<yourdomain>"
    it will get added to the list of suggested URLs.
 
    .NOTES
    The URLs get saved at the PSF-Config "Dracoon.tepp.urls"
    #>

    [CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = 'low')]
    param (
        [parameter(mandatory = $true, Position = 0)]
        [string[]]$NewUrl
    )
    Write-PSFMessage "Adding new Urls for the URL TEPP: $NewUrl"
    $oldUrl=Get-PSFConfigValue "Dracoon.tepp.urls"
    Write-PSFMessage "Existing Urls for the URL TEPP: $oldUrl"
    $combinedUrls=@()
    $combinedUrls+=$NewUrl
    if ($oldUrl) {
        $combinedUrls+=$oldUrl
    }
    # Adjusting format of URLs
    $combinedUrls = $combinedUrls | ForEach-Object { Get-DracoonServerRoot $_ }
    # Sorting and filtering unique
    $combinedUrls=$combinedUrls | Sort-Object | Select-Object -Unique
    Write-PSFMessage "combined Urls for the URL TEPP: $combinedUrls"
    Invoke-PSFProtectedCommand -Action "Saving new Urls for the URL TEPP" -Target "$NewUrl" -ScriptBlock {
        Set-PSFConfig -Module 'Dracoon' -Name 'tepp.urls' -Value $combinedUrls -PassThru | Register-PSFConfig
    } -PSCmdlet $PSCmdlet
}

function Connect-Dracoon {
    <#
    .SYNOPSIS
    Creates a new Connection Object to a Dracoon Server instance.
 
    .DESCRIPTION
    Creates a new Connection Object to a Dracoon Server instance.
 
    For connecting you always need the -Url as the function needs to know where the Server is located. As a
    minimum additional information you have to provide an authorization, either as -Credential or as -AccessToken.
    The usage of a credential object as the only information is *deprecated* and should be replaces in favor of
    an OAuth workflow. For OAuth you need to configure an application within the Web-UI. For more information
    see about_Dracoon.
 
    .PARAMETER Credential
    Credential-Object for direct login.
 
    .PARAMETER Url
    The server root URL.
 
    .PARAMETER RefreshToken
    Neccessary for OAuth Login: Refresh-Token. Can be created with Request-OAuthRefreshToken.
 
    .PARAMETER AccessToken
    Neccessary for OAuth Login: Access-Token. Can be created with Request-OAuthRefreshToken.
 
    .PARAMETER AuthToken
    Neccessary for OAuth Login: Auth-Token. Can be created with Request-OAuthRefreshToken.
 
    .PARAMETER ClientID
    Neccessary for OAuth Login: The Id of the OAauth Client.
 
    .PARAMETER ClientSecret
    Neccessary for OAuth Login: The Secret of the OAauth Client.
 
    .PARAMETER EnableException
    Should Exceptions been thrown?
 
    .EXAMPLE
    $connection=Connect-Dracoon -Url $url -ClientID $clientId -ClientSecret $clientSecret -Credential $cred
    Connect directly with OAuth and a Credential-Object
 
    .EXAMPLE
    # Connect Via pre-generated OAuth access token
    ## Generate accesstoken
    $accessToken=Request-DracoonOAuthToken -ClientID $clientId -ClientSecret $clientSecret -Url $url -Credential $cred -TokenType access
    ## Login with created access token
    $connection=Connect-Dracoon -Url $url -AccessToken $accessToken
 
    .EXAMPLE
    # Connect Via pre-generated OAuth refresh token
    ## Create a refresh token
    $refreshToken=Request-DracoonOAuthToken -ClientID $clientId -ClientSecret $clientSecret -Credential $cred -url $url -TokenType refresh
    ## Connect directly with the refresh token
    $connection=Connect-Dracoon -ClientID $clientId -ClientSecret $clientSecret -url $url -RefreshToken $refreshToken
 
    .EXAMPLE
    ## Second option: Create an access token from the refreh token and login with the access token.
    $accessToken=Request-DracoonOAuthToken -ClientID $clientId -ClientSecret $clientSecret -Url $url -RefreshToken $refreshToken
    $connection=Connect-Dracoon -Url $url -AccessToken $accessToken
 
    .EXAMPLE
    # Direct auth with /auth/login (**Deprecated**)
    ## If you are running an older version it maybe possible to login directly. But this option is deprecated and [will be removed in every installation in the future](https://blog.dracoon.com/en/goodbye-x-sds-auth-token-hello-oauth-2.0)
    $connection=Connect-Dracoon -Url $url -Credential $cred
 
    .NOTES
    As you have to authenticate with OAuth2.0 it is neccessary to create a client application within the admin web-page. For this
    * Go to _System Settings_ / _Apps_ in the navigation bar
    * Click on the _Add app_ button
    * Enter an application name (e.g. "Powershell Scripting")
    * enable all checkboxes (authorization code:implicit:password)
    * Copy the _Client ID_ and the _Client Secret_. Both will be referenced as `$ClientID` and `$ClientSecret`.
 
    Now it's time to open the powershell. Prepare the basic variables:
    $cred=Get-Credential -Message "Dracoon"
    $clientId="YOU JUST CREATED IT ;-)"
    $clientSecret="THIS ALSO"
    $url="dracoon.mydomain.com"
    #>


    [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSReviewUnusedParameter', '')]
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '')]
    [CmdletBinding(DefaultParameterSetName = "AccessToken")]
    Param (
        [parameter(mandatory = $true, ParameterSetName = "authorization_code")]
        [parameter(mandatory = $true, ParameterSetName = "password")]
        [parameter(mandatory = $true, ParameterSetName = "refresh_token")]
        [parameter(mandatory = $true, ParameterSetName = "deprecatedLogin")]
        [parameter(mandatory = $true, ParameterSetName = "AccessToken")]
        [PSFramework.TabExpansion.PsfArgumentCompleterAttribute("Dracoon.url")]
        [string]$Url,
        [parameter(mandatory = $true, ParameterSetName = "authorization_code")]
        [parameter(mandatory = $true, ParameterSetName = "password")]
        [parameter(mandatory = $true, ParameterSetName = "refresh_token")]
        [string]$ClientID,
        [parameter(mandatory = $true, ParameterSetName = "authorization_code")]
        [parameter(mandatory = $true, ParameterSetName = "password")]
        [parameter(mandatory = $true, ParameterSetName = "refresh_token")]
        [string]$ClientSecret,
        [parameter(mandatory = $true, ParameterSetName = "password")]
        [parameter(mandatory = $true, ParameterSetName = "deprecatedLogin")]
        [pscredential]$Credential,
        [parameter(mandatory = $true, ParameterSetName = "authorization_code")]
        [string]$AuthToken,
        [parameter(mandatory = $true, ParameterSetName = "refresh_token")]
        [string]$RefreshToken,
        [parameter(mandatory = $true, ParameterSetName = "AccessToken")]
        [string]$AccessToken,
        [switch]$EnableException
    )

    begin {
        Write-PSFMessage "Stelle Verbindung her zu $Url" -Target $Url
        if ($PSCmdlet.ParameterSetName -eq 'deprecatedLogin') {
            # $connection = [Dracoon]::new($Credential, $Url)
            Invoke-PSFProtectedCommand -ActionString "Connect-Dracoon.Connecting" -ActionStringValues $Url -Target $Url -ScriptBlock {
                # $connection = [Dracoon]::new($Credential.username, $Credential.GetNetworkCredential().password, $Url)
                $connection = [Dracoon]::new($Credential, $Url)
            } -PSCmdlet $PSCmdlet  -EnableException $EnableException
        }
        else{
            if ($PSCmdlet.ParameterSetName -ne 'AccessToken') {
                Write-PSFMessage "Aquiring AccessToken with splatting, ParameterSetName=$($PSCmdlet.ParameterSetName)"
                $AccessToken=Request-DracoonOAuthToken @PSBoundParameters
            }
            $connection = [Dracoon]::new($AccessToken,$Url)
        }
    }
    process {
        if (Test-PSFFunctionInterrupt) { return }
        Write-PSFMessage -string "Connect-Dracoon.Connected"
        $connection
    }
}

function Convert-DracoonGetSetRoomAcl {
    <#
    .SYNOPSIS
    Converts Information retrieved from Get-DracoonRoomAcl to be used for Set-DracoonRoomAcl.
 
    .DESCRIPTION
    Converts Information retrieved from Get-DracoonRoomAcl to be used for Set-DracoonRoomAcl.
    Get-DracoonRoomAcl returns an array in the following format:
    {
      "userInfo": {
        "id": 0,
        "userType": "internal",
        "avatarUuid": "string",
        "displayName": "string",
        "firstName": "string",
        "lastName": "string",
        "email": "string",
        "title": "string"
      },
      "isGranted": true,
      "id": 0,
      "login": "string",
      "displayName": "string",
      "email": "john.doe@email.com",
      "permissions": {
        "manage": true,
        "read": true,
        "create": true,
        "change": true,
        "delete": true,
        "manageDownloadShare": true,
        "manageUploadShare": true,
        "readRecycleBin": true,
        "restoreRecycleBin": true,
        "deleteRecycleBin": true
      },
      "publicKeyContainer": {
        "version": "A",
        "publicKey": "string"
      }
    }
 
    Set-DracoonRoomAcl needs only a sub-part as an array:
    {
      "id": 0,
      "permissions": {
        "manage": true,
        "read": true,
        "create": true,
        "change": true,
        "delete": true,
        "manageDownloadShare": true,
        "manageUploadShare": true,
        "readRecycleBin": true,
        "restoreRecycleBin": true,
        "deleteRecycleBin": true
      }
    }
 
    This function converts the different formats.
 
    .PARAMETER ExistingPermissions
    Array of the exisiting Permission Items.
 
    .EXAMPLE
    To be added
    in the Future
 
    .NOTES
    General notes
    #>

    param (
        [parameter(Mandatory)]
        [array]$ExistingPermissions
    )
    Write-PSFMessage "Converting Permissions "
    Write-PSFMessage "Current permissions: $($ExistingPermissions|convertto-json -depth 10)"
    $permissionList = @()
    foreach ($item in $ExistingPermissions) {
        $permissionList += @{id = $item.id; permissions = $item.permissions }
    }
    Write-PSFMessage "Neue Berechtigungen: $($permissionList|convertto-json -depth 10)"
    $permissionList
}

function Get-DracoonAPILog {
    <#
    .SYNOPSIS
    Retrieves the log entries from Write-DracoonAPICallMessage.
 
    .DESCRIPTION
    Retrieves the log entries from Write-DracoonAPICallMessage.
 
    .PARAMETER Raw
    If set then the raw PSFMessages are returned.
 
    .PARAMETER Last
    If set only the Last entries are returned.
 
    .EXAMPLE
    Get-DracoonAPILog -Last 5
    Retrieves the last 5 logs.
 
    .NOTES
    General notes
    #>

    param (
        [switch]$Raw,
        [int]$Last = 0
    )
    $selectParam = @{    }
    if ($Last -gt 0){
        $selectParam.Last=$Last
    }
    if (!$Raw){
        $selectParam.Property = @("TargetObject", "Message")
    }
    $messages = Get-PSFMessage -Tag "APICALL" | Select-Object @selectParam
    if ($Raw) {
        $messages
    }else
    {
        $messages | Format-List
    }
}

function Get-DracoonAuditDataroom {


    <#
    .SYNOPSIS
    Searches Datarooms by given filter. API-GET /v4/eventlog/audits/nodes
 
    .DESCRIPTION
    Retrieve a list of all nodes of type room, and the room assignment users with permissions.
 
    .PARAMETER Connection
    Object of Class [Dracoon], stores the authentication Token and the API Base-URL
 
    .PARAMETER Filter
    All filter fields are connected via logical conjunction (AND)
    Except for userName, userFirstName and userLastName - these are connected via logical disjunction (OR)
    Filter string syntax: FIELD_NAME:OPERATOR:VALUE[:VALUE...]
    userName:cn:searchString_1|userFirstName:cn:searchString_2|nodeId:eq:2
 
    Possible combinations:
    'nodeId:eq:[positive Integer]'
    'nodeName:[cn/eq]:[search String]'
    'nodeParentId:eq:[positive Integer]'
    'Parent ID 0 is the root node.:[]:[]'
    'userId:eq:[positive Integer]'
    'userName:[cn/eq]:[search String]'
    'userFirstName:[cn/eq]:[search String]'
    'userLastName:[cn/eq]:[search String]'
    'permissionsManage:eq:[true or false]'
    'nodeIsEncrypted:eq:[true or false]'
    'nodeHasActivitiesLog:eq:[true or false]'
 
    .PARAMETER Limit
    Range limit. Maximum 500.
    For more results please use paging (offset + limit).
 
    .PARAMETER Offset
    Range offset
 
    .PARAMETER Sort
    Sort string syntax: FIELD_NAME:ORDER
    ORDER can be asc or desc.
    Multiple sort fields are NOT supported.
    Nodes are sorted by type first, then by sent sort string.
    Example:
 
    name:desc
 
    .PARAMETER HideSpecialRooms
    Filters any room which has a GUID as roomName oder Parent-Path
 
    .EXAMPLE
    Get-DracoonAuditDataroom -Connection $connection
    Lists all available Datarooms
 
    .NOTES
    Right "read audit log" required.
 
    #>


    param (
        [parameter(Mandatory)]
        [Dracoon]$Connection,
        [PSFramework.TabExpansion.PsfArgumentCompleterAttribute("Dracoon.filter")]
        [string]$Filter,
        [int]$Limit,
        [int]$Offset,
        [string]$Sort,
        [bool]$HideSpecialRooms=$true
    )
    $apiCallParameter = @{
        Connection   = $Connection
        method       = "Get"
        Path         = "/v4/eventlog/audits/nodes"
        EnablePaging = $true
        UrlParameter = @{
            filter       = $Filter
            limit        = $Limit
            sort         = $Sort
            offset       = $offset
        }
    }
    $datarooms = Invoke-DracoonAPI @apiCallParameter
    if($HideSpecialRooms){
        Write-PSFMessage "Entferne Datenräume, deren Parent einer GUID entsprechen"
        $regex = '[{]?[0-9a-fA-F]{8}-([0-9a-fA-F]{4}-){3}[0-9a-fA-F]{12}[}]?'
        return $datarooms | Where-Object {( $_.nodeParentPath -notmatch $regex) -and ( $_.nodeName -notmatch $regex)}
    }else{
        return $datarooms
    }
}


function Get-DracoonAuthConfigAD{
    <#
    .SYNOPSIS
    Retrieve a list of configured Active Directories. API-GET /v4/system/config/auth/ads
 
    .DESCRIPTION
    Retrieve a list of configured Active Directories. API-GET /v4/system/config/auth/ads
    Right “read global config” required.
    Role Config Manager of the Provider Customer.
 
    .PARAMETER Connection
    Parameter description
 
    .PARAMETER Alias
    Returns only the configuration whose "alias" attribute matches the parameter value
    .EXAMPLE
    To be added
    in the Future
    #>

        param (
        [parameter(Mandatory)]
        [Dracoon]$Connection,
        [string]$Alias
        )
        $apiCallParameter = @{
        Connection   = $Connection
        method       = "Get"
        Path         ="/v4/system/config/auth/ads"

    }

    $result=Invoke-DracoonAPI @apiCallParameter
    if ($Alias){
        $result.items|where-object {$_.Alias -eq $Alias}
    }else{
        $result.items
    }
}

function Get-DracoonCurrentAccount {
    <#
    .SYNOPSIS
    Retrieves all information regarding the current user’s account. API-GET /v4/user/account
 
    .DESCRIPTION
    Retrieves all information regarding the current user’s account. API-GET /v4/user/account
 
    .PARAMETER Connection
    Parameter description
 
    .EXAMPLE
    To be added
    in the Future
 
    .NOTES
    General notes
    #>

    param (
        [parameter(Mandatory)]
        [Dracoon]$Connection
    )
    $apiCallParameter = @{
        Connection   = $Connection
        method       = "Get"
        Path         ="/v4/user/account"
    }

    Invoke-DracoonAPI @apiCallParameter
}

function Get-DracoonGroup {
    <#
    .SYNOPSIS
    Returns a list of user groups. API-GET /v4/groups
 
    .DESCRIPTION
    Returns a list of user groups.
 
    .PARAMETER Connection
    Parameter description
 
    .PARAMETER Filter
    All filter fields are connected via logical conjunction (AND)
    Filter string syntax: FIELD_NAME:OPERATOR:VALUE
    Possible combinations:
    'name:cn:[search String]'
 
    .PARAMETER Limit
    Range limit. Maximum 500.
    For more results please use paging (offset + limit).
 
    .PARAMETER Offset
    Range offset
 
    .PARAMETER Sort
    Sort string syntax: FIELD_NAME:ORDER
    ORDER can be asc or desc.
    Multiple sort fields are supported.
    Example:
 
    name:asc|expireAt:desc
    Sort by name ascending AND by expireAt descending.
 
    Possible fields:
    name Group name
    createdAt Creation date
    expireAt Expiration date
    cntUsers Amount of users
 
    .PARAMETER EnablePaging
    Wenn die API mit Paging arbeitet, kann über diesn Parameter ein automatisches Handling aktivieren.
    Dann werden alle Pages abgehandelt und nur die items zurückgeliefert.
 
    .EXAMPLE
    To be added
    in the Future
 
    .NOTES
    General notes
    #>

    param (
        [parameter(Mandatory)]
        [Dracoon]$Connection,
        [PSFramework.TabExpansion.PsfArgumentCompleterAttribute("Dracoon.filter")]
        [string]$Filter,
        [int]$Limit=500,
        [int]$Offset=0,
        [string]$Sort,
        [bool]$EnablePaging=$true
    )
    $apiCallParameter = @{
        Connection   = $Connection
        method       = "Get"
        Path         ="/v4/groups"
        EnablePaging = $EnablePaging
        UrlParameter = @{
            filter=$Filter
            limit=$Limit
            sort=$Sort
            offset=$offset
        }
    }

    Invoke-DracoonAPI @apiCallParameter
}

    # [PSCustomObject]GetLastAdminRooms([int]$userId) {
    # $result = $this.InvokeGet("/v4/users/$userId/last_admin_rooms")
    # return $result.items
    # }

function Get-DracoonLastAdminRoom {
    <#
    .SYNOPSIS
    Get rooms where the user is last admin. API-GET /v4/users/$id/last_admin_rooms
 
    .DESCRIPTION
    Retrieve a list of all rooms where the user is last admin (except homeroom and its subordinary rooms).
 
    .PARAMETER Connection
    Object of Class [Dracoon], stores the authentication Token and the API Base-URL.
 
    .PARAMETER Id
    ID of the user
 
    .EXAMPLE
    To be added
    in the Future
 
    .NOTES
    General notes
    #>

    param (
        [parameter(Mandatory)]
        [Dracoon]$Connection,
        [parameter(Mandatory)]
        [int]$Id
    )
    $apiCallParameter = @{
        Connection   = $Connection
        method       = "Get"
        Path         = "/v4/users/$Id/last_admin_rooms"
    }

    $result=Invoke-DracoonAPI @apiCallParameter
    $result.items
}

function Get-DracoonNode {
    <#
    .SYNOPSIS
    Provides a hierarchical list of file system nodes (rooms, folders or
    files) of a given parent that are *accessible* by the current user.
 
    .DESCRIPTION
    Provides a hierarchical list of file system nodes (rooms, folders or
    files) of a given parent that are accessible by the current user.
    GET /v4/nodes
 
    .PARAMETER connection
    Object of Class [Dracoon], stores the authentication Token and the API Base-URL
 
    .PARAMETER Filter
    All filter fields are connected via logical conjunction (AND)
    Filter string syntax: FIELD_NAME:OPERATOR:VALUE[:VALUE...]
 
    Possible combinations:
    'type:eq:[room/folder/file]'
    'perm:eq:[manage/read/change/create/delete/manageDownloadShare/manageUploadShare/canReadRecycleBin/canRestoreRecycleBin/canDeleteRecycleBin]'
    'childPerm:eq:[cf perm]'
    'name:[cn/eq]:[Node name]'
    'encrypted:eq:[true/false]'
    'branchVersion:[ge/le]:[Branch version]'
 
 
    .PARAMETER Limit
    Range limit. Maximum 500.
    For more results please use paging (offset + limit).
 
    .PARAMETER Offset
    Range offset
 
    .PARAMETER Sort
    Sort string syntax: FIELD_NAME:ORDER
    ORDER can be asc or desc.
    Multiple sort fields are NOT supported.
    Nodes are sorted by type first, then by sent sort string.
    Example:
 
    name:desc
 
    .PARAMETER ParentID
    Parent node ID.
    Only rooms and folders can be parents.
    Parent ID 0 or empty is the root node.
 
    .PARAMETER RoomManager
    boolean. Show all rooms for management perspective.
    Only possible for Rooms Managers.
    For all other users, it will be ignored.
 
    .EXAMPLE
    To be added
    in the Future
 
    .NOTES
    General notes
    #>

    param (
        [parameter(Mandatory)]
        [Dracoon]$connection,
        [PSFramework.TabExpansion.PsfArgumentCompleterAttribute("Dracoon.filter")]
        [string]$Filter,
        [int]$Limit=500,
        [int]$Offset=0,
        [string]$Sort,
        [int]$ParentID=0,
        $RoomManager
    )
    $apiCallParameter = @{
        Connection   = $Connection
        method       = "Get"
        Path         ="/v4/nodes"
        EnablePaging = $true
        UrlParameter = @{
            filter=$Filter
            limit=$Limit
            sort=$Sort
            offset=$offset
            parent_id=$ParentID
            room_manager=$RoomManager
        }
        # EnablePaging=$true
    }
    $result = Invoke-DracoonAPI @apiCallParameter
    $result
}

function Get-DracoonNodeAsZip {
    <#
    .SYNOPSIS
    Creates a ZIP archive from given NodeIDs and downloads it. API-POST /v4/nodes/zip
 
    .DESCRIPTION
    Creates a ZIP archive from given NodeIDs and downloads it.
 
    .PARAMETER Connection
    Object of Class [Dracoon], stores the authentication Token and the API Base-URL
 
    .PARAMETER Id
    Array of NodeIDs which should be included into the ZIP file.
 
    .PARAMETER FileName
    Name of the downloaded ZIP file.
 
    .PARAMETER EnableException
    If set to true, inner exceptions will be rethrown. Otherwise the an empty result will be returned.
 
    .EXAMPLE
    Get-DracoonNode -connection $connection -ParentID $roomId | Get-DracoonNodeAsZip -Connection $connection -FileName "myArchive.zip"
    Creates a ZIP archive containing all files of the given room.
 
    .NOTES
    General notes
    #>

    param (
        [parameter(Mandatory)]
        [Dracoon]$Connection,
        [parameter(Mandatory, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)]
        [int[]]$Id,
        [parameter(Mandatory)]
        $FileName,
        $EnableException = $false
    )
    begin {
        $idArray = @()
    }
    process {
        $idArray += $id
    }
    end {
        Write-PSFMessage "Download ID: $($idArray -join ",")"
        $apiCallParameter = @{
            Connection = $Connection
            method     = "Post"
            Path       = "/v4/nodes/zip"
            Body       = @{
                nodeIds = $idArray
            }
        }

        $result = Invoke-DracoonAPI @apiCallParameter
        if ($result) {
            Invoke-PSFProtectedCommand -Action "Downloading" -Target $FileName -ScriptBlock {
                Invoke-WebRequest -Uri $result.downloadUrl -OutFile $FileName -ErrorAction Stop
            } -PSCmdlet $PSCmdlet -EnableException $EnableException -RetryCount 4 -RetryWait 5
        }
    }
}

# [PSCustomObject]GetRoomAcl([int]$roomId) {
# $result = $this.InvokeGet("/v4/nodes/rooms/$roomId/users")
# return $result
# }
function Get-DracoonRoomAcl {
    <#
    .SYNOPSIS
    Retrieve a list of users that are and / or can be granted to the room. API-GET /v4/nodes/rooms/$NodeId/users
 
    .DESCRIPTION
    Retrieve a list of users that are and / or can be granted to the room.
 
    .PARAMETER Connection
    Object of Class [Dracoon], stores the authentication Token and the API Base-URL
 
    .PARAMETER NodeId
    ID of the room
 
    .PARAMETER Filter
    All filter fields are connected via logical conjunction (AND)
    Filter string syntax: FIELD_NAME:OPERATOR:VALUE
    Example:
 
    permissionsManage:eq:true|user:cn:searchString
    Get all users that have manage permissions to this room AND whose (firstname OR lastname OR email) is like searchString.
 
    Possible combinations:
    'user:cn:[search String]',
    'userId:eq:[positive Integer]'
    'isGranted:eq:[true/false/any]'
    'permissionsManage:eq:[true/false]'
    'effectivePerm:eq:[true/false]'
 
    .PARAMETER Limit
    Range limit. Maximum 500.
    For more results please use paging (offset + limit).
 
    .PARAMETER Offset
    Range offset
 
    .PARAMETER Sort
    Sort string syntax: FIELD_NAME:ORDER
    ORDER can be asc or desc.
    Multiple sort fields are NOT supported.
    Nodes are sorted by type first, then by sent sort string.
    Example:
 
    name:desc
 
    .PARAMETER EnablePaging
    Wenn die API mit Paging arbeitet, kann über diesn Parameter ein automatisches Handling aktivieren.
    Dann werden alle Pages abgehandelt und nur die items zurückgeliefert.
 
    .EXAMPLE
    To be added
    in the Future
 
    .NOTES
    General notes
    #>

    param (
        [parameter(Mandatory)]
        [Dracoon]$Connection,
        [parameter(Mandatory)]
        [int]$NodeId,
        [PSFramework.TabExpansion.PsfArgumentCompleterAttribute("Dracoon.filter")]
        [string]$Filter,
        [int]$Limit = 500,
        [int]$Offset = 0,
        [bool]$EnablePaging = $true
    )
    $apiCallParameter = @{
        Connection   = $Connection
        method       = "Get"
        Path         = "/v4/nodes/rooms/$NodeId/users"
        EnablePaging = $EnablePaging
        UrlParameter = @{
            filter = $Filter
            limit  = $Limit
            offset = $offset
        }
    }

    Invoke-DracoonAPI @apiCallParameter
}

function Get-DracoonUser {
    <#
    .SYNOPSIS
    Query of all Users. API-GET /v4/users oder /v4/users/$Id
 
    .DESCRIPTION
    Function has two modes: Single or Multi-Users.
    If using in Multi-User Mode (without Id parameter) it returns an array of all users.
 
    .PARAMETER Connection
    Object of Class [Dracoon], stores the authentication Token and the API Base-URL.
 
    .PARAMETER Filter
    All filter fields are connected via logical conjunction (AND)
    Except for login, firstName and lastName - these are connected via logical disjunction (OR)
    Filter string syntax: FIELD_NAME:OPERATOR:VALUE[:VALUE...]
    Example:
    login:cn:searchString_1|firstName:cn:searchString_2|lockStatus:eq:2
    Filter users by login contains searchString_1 OR firstName contains searchString_2 AND those who are NOT locked.
 
    effectiveRoles
    Filter users with DIRECT or DIRECT AND EFFECTIVE roles
        false: DIRECT roles
        true: DIRECT AND EFFECTIVE roles
        DIRECT means: e.g. user gets role directly granted from someone with grant permission right.
        EFFECTIVE means: e.g. user gets role through group membership.
 
    Possible combinations:
    'login:cn:[search String]'
    'firstName:cn:[search String]'
    'lastName:cn:[search String]'
    'isLocked:eq:[true/false]'
    'effectiveRoles:eq:[true/false]'
 
 
    .PARAMETER IncludeAttributes
    Parameter description
 
    .PARAMETER Limit
    Range limit. Maximum 500.
    For more results please use paging (offset + limit).
 
    .PARAMETER Offset
    Range offset
 
    .PARAMETER Sort
    Sort string syntax: FIELD_NAME:ORDER
    ORDER can be asc or desc.
    Multiple sort fields are NOT supported.
    Nodes are sorted by type first, then by sent sort string.
    Example:
 
    name:desc
 
    .PARAMETER EnablePaging
    Wenn die API mit Paging arbeitet, kann über diesn Parameter ein automatisches Handling aktivieren.
    Dann werden alle Pages abgehandelt und nur die items zurückgeliefert.
 
    .PARAMETER Id
    Id of a specific user.
 
    .PARAMETER EffectiveRoles
    Filter users with DIRECT or DIRECT AND EFFECTIVE roles.
        false: DIRECT roles
        true: DIRECT AND EFFECTIVE roles
    DIRECT means: e.g. user gets role directly granted from someone with grant permission right.
    EFFECTIVE means: e.g. user gets role through group membership.
 
    .EXAMPLE
    To be added
    in the Future
 
    .NOTES
    General notes
    #>

    [CmdletBinding(DefaultParameterSetName = "SingleUser")]
    Param (
        [parameter(Mandatory)]
        [Dracoon]$Connection,
        [PSFramework.TabExpansion.PsfArgumentCompleterAttribute("Dracoon.filter")]
        [parameter(Mandatory = $false, ParameterSetName = "MultipleUsers")]
        [string]$Filter,
        [parameter(Mandatory = $false, ParameterSetName = "MultipleUsers")]
        [int]$Limit=500,
        [parameter(Mandatory = $false, ParameterSetName = "MultipleUsers")]
        [int]$Offset=0,
        [parameter(Mandatory = $false, ParameterSetName = "MultipleUsers")]
        [switch]$IncludeAttributes,
        [parameter(Mandatory = $false, ParameterSetName = "MultipleUsers")]
        [string]$Sort,
        [parameter(Mandatory = $false, ParameterSetName = "MultipleUsers")]
        [bool]$EnablePaging=$true,
        [parameter(Mandatory = $false, ParameterSetName = "SingleUser")]
        [int]$Id,
        [parameter(Mandatory = $false, ParameterSetName = "SingleUser")]
        [bool]$EffectiveRoles=$true
    )
    if ($id -eq 0){
        Write-PSFMessage "Ermittle mehrere User"
        $apiCallParameter = @{
            Connection   = $Connection
            method       = "Get"
            Path         ="/v4/users"
            EnablePaging = $EnablePaging
            UrlParameter = @{
                filter=$Filter
                include_attributes=$IncludeAttributes
                limit=$Limit
                sort=$Sort
                offset=$offset
            }
        }
    }else{
        Write-PSFMessage "Requesting detailed user info for #$Id"
        $apiCallParameter = @{
            Connection   = $Connection
            method       = "Get"
            Path         ="/v4/users/$Id"
            UrlParameter = @{
                effective_roles    = $EffectiveRoles
            }
        }

    }

    Invoke-DracoonAPI @apiCallParameter
}

function Get-DracoonUserAttribute {
    <#
    .SYNOPSIS
    Retrieve a list of user attributes. API-GET /v4/users/{user_id}/userAttributes
 
    .DESCRIPTION
    Retrieve a list of user attributes.
 
    .PARAMETER Connection
    Object of Class [Dracoon], stores the authentication Token and the API Base-URL
 
    .PARAMETER Id
    ID of the User.
 
    .PARAMETER ReturnHashTable
    If set to true (default), results are returned as a HashTable. Otherwise an array of PSCustomObjects is returned
 
    .PARAMETER Filter
    All filter fields are connected via logical conjunction (AND)
    Filter string syntax: FIELD_NAME:OPERATOR:VALUE[:VALUE...]
    Example:
 
    key:cn:searchString_1|value:cn:searchString_2
    Filter by attribute key contains searchString_1 AND attribute value contains searchString_2.
 
    Possible combinations:
    'key:[cn/eq/sw]:[Attribute key]'
    'value:[cn/eq/sw]:[Attribute value]'
 
 
 
    .PARAMETER Limit
    Range limit. Maximum 500.
    For more results please use paging (offset + limit).
 
    .PARAMETER Offset
    Range offset
 
    .PARAMETER Sort
    Sort string syntax: FIELD_NAME:ORDER
    ORDER can be asc or desc.
    Multiple sort fields are NOT supported.
    Nodes are sorted by type first, then by sent sort string.
    Example:
 
    name:desc
 
    .PARAMETER EnablePaging
    Wenn die API mit Paging arbeitet, kann über diesn Parameter ein automatisches Handling aktivieren.
    Dann werden alle Pages abgehandelt und nur die items zurückgeliefert.
 
 
    .EXAMPLE
    To be added
    in the Future
    .NOTES
    General notes
    #>

    param (
        [parameter(Mandatory)]
        [Dracoon]$connection,
        [parameter(Mandatory)]
        [int]$Id,
        [bool]$ReturnHashTable = $true,
        [PSFramework.TabExpansion.PsfArgumentCompleterAttribute("Dracoon.filter")]
        [string]$Filter,
        [int]$Limit = 500,
        [int]$Offset = 0,
        [string]$Sort,
        [bool]$EnablePaging = $true

    )
    $apiCallParameter = @{
        Connection = $Connection
        method     = "Get"
        Path       = "/v4/users/$Id/userAttributes"
        EnablePaging = $EnablePaging
        UrlParameter = @{
            filter             = $Filter
            limit              = $Limit
            sort               = $Sort
            offset             = $offset
        }
    }
    Write-PSFMessage "Ermittele UserAttribute zu User $Id"
    $results = Invoke-DracoonAPI @apiCallParameter
    if ($EnablePaging) { $items = $results }else { $items = $results.items}
    if ($ReturnHashTable) {
        $attributes = @{}
        foreach ($item in $items ) {
            $attributes.add($item.key, $item.value)
        }
        $attributes
    }
    else {
        $items
    }
}

function Invoke-DracoonAPI {
    <#
    .SYNOPSIS
    Generic API Call to the Dracoon API.
 
    .DESCRIPTION
    Generic API Call to the Dracoon API. This function is a wrapper for the usage of Invoke-WebRequest. It handles some annoying repetitive tasks which occur in most use cases. This includes (list may be uncompleted)
    - Connecting to a server with authentication
    - Parsing API parameter
    - Handling $null parameter
    - Paging for API endpoints which do only provide limited amounts of datasets
 
    .PARAMETER Connection
    Object of Class [Dracoon], stores the authentication Token and the API Base-URL. Can be obtained with Connect-Dracoon.
 
    .PARAMETER Path
    API Path to the REST function, starting *after* /api.
    Example: "/v4/users"
 
    .PARAMETER Body
    Parameter for the API call; The hashtable is Converted to the POST body by using ConvertTo-Json
 
    .PARAMETER URLParameter
    Parameter for the API call; Converted to the GET URL parameter set.
    Example:
    {
        id=4
        name=Jon Doe
    }
    will result in "?id=4&name=Jon%20Doe" being added to the URL Path
 
    .PARAMETER Method
    HTTP Method, Get/Post/Delete/Put/...
 
    .PARAMETER ContentType
    HTTP-ContentType, defaults to "application/json;charset=UTF-8"
    See Publish-DracoonFile for usage.
 
    .PARAMETER InFile
    File which should be transferred during the Request.
    See Publish-DracoonFile for usage.
 
    .PARAMETER HideParameters
    If set to $true the password is hidden from logging
 
    .PARAMETER EnablePaging
    If the API makes use of paging (therefor of limit/offset URLParameter) setting EnablePaging to $true will not return the raw data but a combination of all data sets.
 
    .PARAMETER EnableException
    If set to true, inner exceptions will be rethrown. Otherwise the an empty result will be returned.
 
    .EXAMPLE
    $result = Invoke-DracoonAPI -connection $this -path "/v4/auth/login" -method POST -body @{login = $credentials.UserName; password = $credentials.GetNetworkCredential().Password; language = "1"; authType = "sql" } -hideparameters $true
    Login to the service
 
    .NOTES
    General notes
    #>


    param (
        [parameter(Mandatory)]
        $Connection,
        [parameter(Mandatory)]
        [string]$Path,
        [Hashtable] $Body,
        [Hashtable] $URLParameter,
        [parameter(Mandatory)]
        [Microsoft.Powershell.Commands.WebRequestMethod]$Method,
        [bool] $HideParameters = $false,
        [string]$ContentType = "application/json;charset=UTF-8",
        [string]$InFile,
        [bool]$EnableException=$true,
        [switch]$EnablePaging
    )
    $uri = $connection.webServiceRoot + $path
    if ($URLParameter) {
        Write-PSFMessage "Converting UrlParameter to a Request-String and add it to the path"
        Write-PSFMessage "$($UrlParameter|ConvertTo-Json)"
        $parameterString = (Get-EncodedParameterString($URLParameter))
        $uri = $uri + '?' + $parameterString.trim("?")
    }
    $restAPIParameter = @{
        Uri         = $Uri
        method      = $Method
        body        = ($Body | Remove-NullFromHashtable)
        Headers     = $connection.headers
        ContentType = $ContentType
    }
    If ($Body) {
        $restAPIParameter.body = ($Body | Remove-NullFromHashtable -Json)
    }
    If ($InFile) {
        $restAPIParameter.InFile = $InFile
    }

    try {
        Write-DracoonAPICallMessage $restAPIParameter
        $result = Invoke-RestMethod @restAPIParameter
        Write-PSFMessage -Level Debug "result= $($result|ConvertTo-Json -Depth 5)"
        if ($EnablePaging -and ($result -is [array])) {
            Write-PSFMessage "Paging enabled, aber keine Range zurückgeliefert" -Level Warning
        }elseif ($EnablePaging) {
            Write-PSFMessage "Paging enabled, starte Schleife, result.range=$($result.range)"
            $allItems = ($result.items)
            write-psfmessage "Anzahl ermittelter Items: $($allItems.count)"
            $URLParameter.limit = $result.range.limit
            $URLParameter.offset = $result.range.offset
            while ($result.range.total -gt $allItems.count) {
                Write-PSFMessage "result.range.total=$($result.range.total) -gt allItems.count=$($allItems.count)"
                $URLParameter.offset = $allItems.count
                $nextParameter = @{
                    Connection     = $Connection
                    Path           = $Path
                    Body           = $Body
                    URLParameter   = $URLParameter
                    Method         = $Method
                    HideParameters = $HideParameters
                }
                Write-DracoonAPICallMessage $nextParameter
                $result = Invoke-DracoonAPI @nextParameter
                $allItems += ($result.items)
            }
            return $allItems
        }
    }
    catch {
        $result = $_.errordetails
        Write-PSFMessage "$result" -Level Critical
        If ($EnableException){
            throw $_#$result.Message
        }else{
            return
        }
    }
    return $result
}

function New-DracoonDataroom {
    <#
    .SYNOPSIS
    Creates a new room at the provided parent node.
    Creation of top level rooms provided.
 
    .DESCRIPTION
    API-POST /v4/nodes/rooms
 
    .PARAMETER connection
    Object of Class [Dracoon], stores the authentication Token and the API Base-URL
 
    .PARAMETER RoomName
    Name of the new room
 
    .PARAMETER ParentNodeId
    Node-ID of the parent-node, 0 for Creation of top level rooms
 
    .PARAMETER AdminUserId
    Array of user-ids of room admins
 
    .PARAMETER AdminGroupId
    Array of user-ids of room admin groups
 
    .PARAMETER RecycleBinRetentionPeriod
    How many days should files be kept in the recycle bin.
 
    .PARAMETER InheritPermissions
    Room inherits the permissions of the parent room
 
    .PARAMETER Notes
    Description notes for the room
 
    .PARAMETER NewGroupMemberAcceptance
    Do new admin group members have to be released? Default is "autoallow"
 
    .PARAMETER Quota
    Quota for the new room in bytes. 0 for no quota.
 
    .PARAMETER HasActivitiesLog
    Is the activity log enabled for the room?
 
    .PARAMETER Classification
    Nummerical classification.
 
    .PARAMETER whatIf
    If enabled it does not execute the backend API call.
 
    .PARAMETER confirm
    If enabled the backend API Call has to be confirmed
 
    .EXAMPLE
    To be added
    in the Future
 
    .NOTES
    Precondition:
    User has “manage” permissions in the parent room.
    #>


    [CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = 'Medium')]

    param (
        [parameter(Mandatory)]
        [Dracoon]$Connection,
        [parameter(Mandatory)]
        [string]$RoomName,
        [int]$ParentNodeId,
        [parameter(Mandatory)]
        [array]$AdminUserId,
        [array]$AdminGroupId,
        [int]$RecycleBinRetentionPeriod = 14,
        [bool]$InheritPermissions = $false,
        [string]$Notes = "",
        [ValidateSet("autoallow", "pending")]
        [String]$NewGroupMemberAcceptance = "autoallow",
        [int]$Quota = 0,
        [bool]$HasActivitiesLog = $true,
        $Classification=2
    )
    Write-PSFMessage "Erzeuge Datenraum $RoomName"
    $apiCallParameter = @{
        Connection = $Connection
        method     = "Post"
        Path       = "/v4/nodes/rooms"
        Body       = @{
            name                      = "$RoomName"
            recycleBinRetentionPeriod = $RecycleBinRetentionPeriod
            quota                     = $Quota
            inheritPermissions        = $InheritPermissions
            adminIds                  = $AdminUserId
            adminGroupIds             = $adminGroupId
            newGroupMemberAcceptance  = $NewGroupMemberAcceptance
            notes                     = $Notes
            hasActivitiesLog          = $HasActivitiesLog
            classification            = $Classification
            hasRecycleBin             = $true
        }
        # EnablePaging=$true
    }
    if ($parentNodeId -gt 0) {
        $apiCallParameter.body.parentId = $parentNodeId
    }
    Invoke-PSFProtectedCommand -Action "Creating new dataroom" -Target "$RoomName" -ScriptBlock {
        $result = Invoke-DracoonAPI @apiCallParameter
        Write-PSFMessage "User erfolgreich angelegt"
        $result
    } -PSCmdlet $PSCmdlet
}

function New-DracoonDownloadShare {
    <#
    .SYNOPSIS
    Creates a Download Share for an existing file node
 
    .DESCRIPTION
    API-POST /v4/shares/downloads
 
    .PARAMETER connection
    Object of Class [Dracoon], stores the authentication Token and the API Base-URL
 
    .PARAMETER NodeId
    Node-ID of the file
 
    .PARAMETER Notes
    Description notes for the share
 
    .PARAMETER InternalNotes
    Internal Description notes for the share
 
    .PARAMETER ShareName
    Descriptive Name of the download share.
 
    .PARAMETER MaxDownloads
    How often should the file be downloadable? 0 for infinity.
 
    .PARAMETER ShowCreatorName
    Should the Share-Creator-Name be displayed? Defaults to true
 
    .PARAMETER ShowCreatorUsername
    Should the Share-Creator-Username be displayed? Defaults to false
 
    .PARAMETER NotifyCreator
    Should the creator be informed about any download? Defaults to true
 
    .PARAMETER Password
    Password for accessing the shared file. See .PARAMETER RandomPassword
 
    .PARAMETER RandomPassword
    If used the password is generated randomly.
 
    .PARAMETER TextMessageRecipients
    Optional Array of text message recipients
 
    .PARAMETER whatIf
    If enabled it does not execute the backend API call.
 
    .PARAMETER confirm
    If enabled the backend API Call has to be confirmed
 
    .PARAMETER ExpirationDate
    Sets a date when the user will expire
 
    .PARAMETER EnableException
    If set to true, inner exceptions will be rethrown. Otherwise the an empty result will be returned.
 
    .EXAMPLE
    New-DracoonDownloadShare -Connection $connection -NodeId $NodeId -MaxDownloads 2
    Creates a download share which lasts for 2 downloads.
 
    .EXAMPLE
    New-DracoonDownloadShare -Connection $connection -NodeId $NodeId -Password "fsdjfdsfhj8934234****"
    Creates a download share with set password
 
    .EXAMPLE
    New-DracoonDownloadShare -Connection $connection -NodeId $NodeId -RandomPassword
    Creates a download share with a random access password.
 
    .EXAMPLE
    New-DracoonDownloadShare -Connection $connection -NodeId $NodeId -RandomPassword -TextMessageRecipients "0123456789"
    Creates a download share with a random access password, sends the password to the given mobile number
 
    .NOTES
    #>


    [CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = 'Medium')]
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingPlainTextForPassword', '')]
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingUsernameAndPasswordParams', '')]
    [CmdletBinding(DefaultParameterSetName = "Default")]
    param (
        [parameter(Mandatory = $true)]
        [Dracoon]$Connection,
        [parameter(Mandatory = $true)]
        [int]$NodeId,
        [string]$Notes = "",
        [string]$InternalNotes = "",
        [string]$ShareName = "Download-Share",
        [bool]$ShowCreatorName = $true,
        [bool]$ShowCreatorUsername = $false,
        [bool]$NotifyCreator = $true,
        [string[]]$TextMessageRecipients,
        [int]$MaxDownloads=0,
        [string]$Password,
        [switch]$RandomPassword,
        [datetime]$ExpirationDate,
        [bool]$EnableException = $false
    )
    Write-PSFMessage "Creating Download Share for Node $NodeId"
    if ($RandomPassword){
        $Password=(New-DracoonRandomPassword)
    }
    $apiCallParameter = @{
        Connection = $Connection
        method     = "Post"
        Path       = "/v4/shares/downloads"
        Body       = @{
            "nodeId"                = $NodeId
            "name"                  = $ShareName
            # "password" = $Password
            "expiration"            = @{
                "enableExpiration" = $false
                "expireAt"         = "2018-01-01T00:00:00"
            }
            "internalNotes"         = $InternalNotes
            "notes"                 = $notes
            "showCreatorName"       = $ShowCreatorName
            "showCreatorUsername"   = $ShowCreatorUsername
            "notifyCreator"         = $NotifyCreator
            # "maxDownloads" = 0
            "creatorLanguage"       = "de-DE"
            "receiverLanguage"      = "de-DE"
            # "textMessageRecipients" = @(
            # "string"
            # )
            "sendMail"              = $false
            # "sendSms" = $true
            # "smsRecipients" = "string"
        }
        # EnablePaging=$true
    }
        if ($ExpirationDate) {
        $apiCallParameter.Body.expiration.enableExpiration = 'true'
        $apiCallParameter.Body.expiration.expireAt = $ExpirationDate.ToString('yyyy-MM-ddT00:00:00')
    }
    if ($TextMessageRecipients){
        $apiCallParameter.Body.textMessageRecipients = @($TextMessageRecipients)
    }
    if ($Password){
        $apiCallParameter.Body.password = $Password
    }
    if ($MaxDownloads -gt 0){
        $apiCallParameter.Body.maxDownloads = $MaxDownloads
    }

    Invoke-PSFProtectedCommand -Action "Creating new download-share" -Target "$NodeId" -ScriptBlock {
        $result = Invoke-DracoonAPI @apiCallParameter
        # Write-PSFMessage "User erfolgreich angelegt"
        $result
    } -PSCmdlet $PSCmdlet -EnableException $EnableException
}

function New-DracoonFolder {
    <#
    .SYNOPSIS
    Creates a new folder at the provided parent node.
 
    .DESCRIPTION
    API-POST /v4/nodes/folders
 
    .PARAMETER connection
    Object of Class [Dracoon], stores the authentication Token and the API Base-URL
 
    .PARAMETER Name
    Name of the new room
 
    .PARAMETER ParentNodeId
    Node-ID of the parent-node.
 
    .PARAMETER Notes
    Description notes for the folder
 
    .PARAMETER whatIf
    If enabled it does not execute the backend API call.
 
    .PARAMETER confirm
    If enabled the backend API Call has to be confirmed
 
    .PARAMETER EnableException
    If set to true, inner exceptions will be rethrown. Otherwise the an empty result will be returned.
 
    .EXAMPLE
    New-DracoonDataroom -Connection $connection -Name "MyFolder" -ParentRoomId $room.id
    Creates a folder within the defined room
 
    .NOTES
    Precondition:
    User has “create” permissions in current room.
    #>


    [CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = 'low')]

    param (
        [parameter(Mandatory)]
        [Dracoon]$Connection,
        [parameter(Mandatory)]
        [string]$Name,
        [int]$ParentNodeId,
        [string]$Notes = "",
        [bool]$EnableException = $false
    )
    Write-PSFMessage "Create folder $Name in room $ParentNodeID"
    $apiCallParameter = @{
        Connection = $Connection
        method     = "Post"
        Path       = "/v4/nodes/folders"
        Body       = @{
            name                      = "$Name"
            parentId                  = $ParentNodeID
            notes                     = $Notes
        }
    }
    Invoke-PSFProtectedCommand -Action "Creating new folder" -Target "$RoomName" -ScriptBlock {
        $result = Invoke-DracoonAPI @apiCallParameter
        Write-PSFMessage "Folder successful created"
        $result
    } -PSCmdlet $PSCmdlet -EnableException $EnableException
}

function New-DracoonUser {
    <#
    .SYNOPSIS
    Create a new user.
 
    .DESCRIPTION
    Create a new user. Two option sets are possible: Mail-User (internal authentication) or Active Directory based authentification.
 
    .PARAMETER Connection
    Object of Class [Dracoon], stores the authentication Token and the API Base-URL
 
    .PARAMETER Login
    Unique login for the user, UPN/MAIL format expected
 
    .PARAMETER FirstName
    First name of the user.
 
    .PARAMETER LastName
    Last name of the user.
 
    .PARAMETER Title
    Title of the user.
 
    .PARAMETER Gender
    Gender of the user.
 
    .PARAMETER Mail
    Mail address of the user.
 
    .PARAMETER Domain
    Only needed for Domain based Authentication.
 
    .PARAMETER SamAccountName
    Login Name Only needed for Domain based Authentication.
 
    .PARAMETER ExpirationDate
    Sets a date when the user will expire
 
    .PARAMETER NotifyUser
    If set to true the user is notified by mail.
 
    .PARAMETER NeedsToChangePassword
    If set to true the user has to change the password on first login.
 
    .PARAMETER whatIf
    If enabled it does not execute the backend API call.
 
    .PARAMETER confirm
    If enabled the backend API Call has to be confirmed
 
    .EXAMPLE
    To be added
    in the Future
 
    .NOTES
    General notes
    #>

    [CmdletBinding(DefaultParameterSetName = "Mailuser", SupportsShouldProcess = $true, ConfirmImpact = 'Medium')]

    param (
        [parameter(mandatory = $true, ParameterSetName = "Mailuser")]
        [parameter(mandatory = $true, ParameterSetName = "ADuser")]
        [Dracoon]$Connection,
        [parameter(mandatory = $true, ParameterSetName = "Mailuser")]
        [parameter(mandatory = $true, ParameterSetName = "ADuser")]
        [PSFValidateScript( { $_ -as [mailaddress] }, ErrorMessage = "{0} - is not a valid mail address")]
        [string]$Login,
        [parameter(mandatory = $true, ParameterSetName = "Mailuser")]
        [parameter(mandatory = $true, ParameterSetName = "ADuser")]
        [string]$FirstName,
        [parameter(mandatory = $true, ParameterSetName = "Mailuser")]
        [parameter(mandatory = $true, ParameterSetName = "ADuser")]
        [string]$LastName,
        [string]$Title = "",
        [ValidateSet('m', 'f', 'n')]
        [string]$Gender = 'n',
        [string]$Mail,
        [parameter(mandatory = $true, ParameterSetName = "ADuser")]
        [string]$Domain,
        [parameter(mandatory = $true, ParameterSetName = "ADuser")]
        [string]$SamAccountName,
        [parameter(mandatory = $false, ParameterSetName = "Mailuser")]
        [parameter(mandatory = $false, ParameterSetName = "ADuser")]
        [datetime]$ExpirationDate,
        [parameter(mandatory = $false, ParameterSetName = "Mailuser")]
        [bool]$NotifyUser = $false,
        [parameter(mandatory = $false, ParameterSetName = "Mailuser")]
        [bool]$NeedsToChangePassword = $false
    )
    $apiCallParameter = @{
        Connection = $Connection
        method     = "Post"
        Path       = "/v4/users"
        Body       = @{
            firstName             = $FirstName
            lastName              = $LastName
            authMethods           = @()
            login                 = $Login
            title                 = $Title
            gender                = $Gender
            expiration            = @{
                enableExpiration = "false"
                expireAt         = "2018-01-01T00:00:00"
            }
            receiverLanguage      = "de-DE"
            email                 = $Mail
            notifyUser            = ("$NotifyUser").ToLower()
            needsToChangePassword = ("$NeedsToChangePassword").ToLower()
            password              = New-DracoonRandomPassword
        }
    }
    if ($ExpirationDate) {
        $apiCallParameter.Body.expiration.enableExpiration = 'true'
        $apiCallParameter.Body.expiration.expireAt = $ExpirationDate.ToString('yyyy-MM-ddT00:00:00')
    }
    if ($Domain) {
        $adId = (Get-DracoonAuthConfigAD -Connection $Connection -Alias $Domain).id
        if (-not ($adId)) {
            throw "Unbekannter AD-Alias $domain"
        }
        Write-PSFMessage "Lege einen AD-User an ($Domain/$adId)"
        $apicallparameter.Body.authMethods += @{
            authId    = "active_directory"
            isEnabled = "true"
            options   = @(
                @{
                    key   = "ad_config_id"
                    value = $adId
                }
                @{
                    key   = "username"
                    value = $samAccountName
                })
        }
    }
    else {
        Write-PSFMessage "Lege einen SQL-User an ($Domain/$adId)"
        $apicallparameter.Body.authMethods += @{
            authId    = "sql"
            isEnabled = "true"
        }
    }
    write-psfmessage "($apiCallParameter|convertfrom-json -depth 10)"
    Invoke-PSFProtectedCommand -Action "Lege User an" -Target $Login -ScriptBlock {
        $result = Invoke-DracoonAPI @apiCallParameter
        Write-PSFMessage "User erfolgreich angelegt"
        $result
    } -PSCmdlet $PSCmdlet -Confirm:$false -Verbose
    if (Test-PSFFunctionInterrupt) { return }
}

function Publish-DracoonFile {
    <#
    .SYNOPSIS
    Uploads a file to an already existing dataroom.
 
    .DESCRIPTION
    Uploads a file to an already existing dataroom.
 
    .PARAMETER Connection
    Object of Class [Dracoon], stores the authentication Token and the API Base-URL
 
    .PARAMETER FilePath
    Filepath of the file which should get uploaded.
 
    .PARAMETER ParentNodeId
    ID of the target room.
 
    .PARAMETER ExpirationDate
    Optional expiration date for the file.
 
    .PARAMETER Classification
    Classification of the file.
 
    .PARAMETER Notes
    Notes for the file
 
    .PARAMETER ResolutionStrategy
    If the file already exists: Should it be overwritten (overwrite) ord should it be uploaded with an automatic name (autorename)
 
    .PARAMETER EnableException
    If set to $true errors throw an exception
 
    .PARAMETER whatIf
    If enabled it does not execute the backend API call.
 
    .PARAMETER confirm
    If enabled the backend API Call has to be confirmed
 
    .EXAMPLE
    Publish-DracoonFile -Connection $connection -FilePath $fileName -ParentNodeId $roomId
    Performs an upload of $fileName
 
    .NOTES
    Uploads consist of three steps:
    -Initialization - Announces the upload and creates a placeholder
    -Upload - Binary transfer of the file
    -Closing the upload - Tell Dracoon that the data has completely transfered
    #>

    [CmdletBinding(DefaultParameterSetName = "Upload", SupportsShouldProcess = $true, ConfirmImpact = 'Medium')]

    param (
        [parameter(mandatory = $true)]
        [Dracoon]$Connection,
        [parameter(mandatory = $true)]
        [string]$FilePath,
        [parameter(mandatory = $true)]
        [int]$ParentNodeId,
        [datetime]$ExpirationDate,
        [int]$Classification = 2,
        [string]$Notes = "",
        [ValidateSet("overwrite", "autorename")]
        [string]$ResolutionStrategy = "autorename",
        [bool]$EnableException = $false
    )
    $fullFilePath = Get-Item $FilePath -ErrorAction SilentlyContinue
    Write-PSFMessage "Upload of $FilePath ($fullFilePath), ResolutionStrategy=$ResolutionStrategy"
    if ($fullFilePath) {
        $apiCallParameter = @{
            Connection = $Connection
            method     = "Post"
            Path       = "/v4/nodes/files/uploads"
            Body       = @{"parentId" = $parentNodeId
                "name"                = $fullFilePath.Name
                "classification"      = $Classification
                "size"                = $fullFilePath.length
                "expiration"          = @{
                    "enableExpiration" = $false
                    "expireAt"         = "2018-01-01T00:00:00"
                }
                "notes"               = $Notes
            }
        }
        if ($ExpirationDate) {
            $apiCallParameter.Body.expiration.enableExpiration = 'true'
            $apiCallParameter.Body.expiration.expireAt = $ExpirationDate.ToString('yyyy-MM-ddT00:00:00')
        }

        Write-PSFMessage "Init: $($apiCallParameter|convertTo-json -depth 10)" -Level Debug
        Invoke-PSFProtectedCommand -Action "Initialize Upload, Open Upload Channel" -Target $fullFilePath.Name -ScriptBlock {
            $initUpload = Invoke-DracoonAPI @apiCallParameter
            Write-PSFMessage "initUpload=$($initUpload|ConvertTo-Json -Depth 5)" -Level Debug
            Invoke-PSFProtectedCommand -Action "Upload File-Data" -Target $initUpload.token -ScriptBlock {
                $apiCallParameter = @{
                    Connection  = $Connection
                    method      = "Post"
                    Path        = "/v4/uploads/$($initUpload.token)"
                    ContentType = "application/octet-stream"
                    InFile      = $fullFilePath.FullName
                }
                $result = Invoke-DracoonAPI @apiCallParameter
                # $result = Invoke-RestMethod $initUpload.uploadUrl -ContentType "application/octet-stream" -Method Post -Headers $connection.headers -InFile $fullFilePath.FullName

                Write-PSFMessage $result
                Invoke-PSFProtectedCommand -Action "Close Upload Channel" -Target $initUpload.token -ScriptBlock {
                    $apiCallParameter = @{
                        Connection = $Connection
                        method     = "Put"
                        Path       = "/v4/uploads/$($initUpload.token)"
                        Body=@{
                            resolutionStrategy = $ResolutionStrategy
                        }
                    }
                    $result = Invoke-DracoonAPI @apiCallParameter
                    # $result = $this.Invoke(("/v4/uploads/{0}" -f $initUpload.token), $null, [Microsoft.Powershell.Commands.WebRequestMethod]::Put, $false)
                    Write-PSFMessage "Upload successfull closed"
                    return $result
                } -PSCmdlet $PSCmdlet -Verbose -EnableException $EnableException
            } -PSCmdlet $PSCmdlet -Verbose -EnableException $EnableException
            if (Test-PSFFunctionInterrupt) {
                Write-PSFMessage "Error uploading the file"
                Invoke-PSFProtectedCommand -Action 'Cleanup $initUpload.token' -Target $initUpload.token -ScriptBlock {
                    $apiCallParameter = @{
                        Connection = $Connection
                        method     = "Delete"
                        Path       = "/v4/uploads/$($initUpload.token)"
                    }
                    Invoke-DracoonAPI @apiCallParameter
                } -PSCmdlet $PSCmdlet -Verbose -EnableException $EnableException
            }
        } -PSCmdlet $PSCmdlet -Verbose -EnableException $EnableException
        if (Test-PSFFunctionInterrupt) { return }
    }
    elseif ($EnableException) {
        Write-PSFMessage "File not found: $FilePath"
        throw "File not found: $FilePath"
    }
}

function Remove-DracoonNode {
    <#
    .SYNOPSIS
    Delete node (room, folder or file). API-DELETE /v4/nodes/{node_id}
 
    .DESCRIPTION
    Delete node (room, folder or file).
 
    Precondition:
    Authenticated user with “delete” permissions on:
 
    supplied nodes (for folders or files)
    superordinated node (for rooms)
    Effects:
    Node gets deleted.
 
    .PARAMETER Connection
    Object of Class [Dracoon], stores the authentication Token and the API Base-URL
 
    .PARAMETER NodeId
    ID of the node which should be deleted.
 
    .PARAMETER whatIf
    If enabled it does not execute the backend API call.
 
    .PARAMETER confirm
    If enabled the backend API Call has to be confirmed
 
    .EXAMPLE
    $rooms = Get-DracoonAuditDataroom -connection $connection -filter "nodeName:cn:DEMO"
    $hubbaRooms| Remove-DracoonNode -connection $connection
    Queries all rooms with "DEMO" within the nodeName and deletes them.
 
    Remove-DracoonNode -connection $connection -NodeId 15
    Deletes the node with ID 15.
 
    .NOTES
    General notes
    #>

    [CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = 'Medium')]
    param (
        [parameter(Mandatory)]
        [Dracoon]$connection,
        [parameter(Mandatory, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)]
        [Alias("ID")]
        [int]$NodeId
    )
    process {
        $apiCallParameter = @{
            Connection = $Connection
            method     = "Delete"
            Path       = "/v4/nodes/$NodeId"
        }
        Invoke-PSFProtectedCommand -Action "Removing Node" -Target "$Id" -ScriptBlock {
            $result = Invoke-DracoonAPI @apiCallParameter
            Write-PSFMessage "Node removed"
            $result
        } -PSCmdlet $PSCmdlet
    }
}

# [PSCustomObject]DeleteUser([int]$id, [bool]$deleteLastAdminRooms) {
# if ($deleteLastAdminRooms) {
# $LastAdminRooms = $this.getLastAdminRooms($id)
# if ($LastAdminRooms) {
# Write-PSFMessage ("Lösche {0} LastAdminRooms" -f $LastAdminRooms.count)
# $LastAdminRooms | Format-Table | Out-String | Write-PSFMessage
# foreach ($room in $LastAdminRooms) {
# $this.DeleteRoom($room.id)
# }
# }
# }
# $result = $this.InvokeDelete("/v4/users/$id")
# return $result
# }
function Remove-DracoonUser {
    <#
    .SYNOPSIS
    Delete a user. API-DELETE /v4/users/{user_id}
 
    .DESCRIPTION
    Delete a user. API-DELETE /v4/users/{user_id}
 
    .PARAMETER Connection
    Object of Class [Dracoon], stores the authentication Token and the API Base-URL
 
    .PARAMETER Id
    ID of the User which should be deleted.
 
    .PARAMETER DeleteLastAdminRooms
    If true, the function will check if the user is the last admin of any data room. If yes, the rooms will be removed first.
 
    .PARAMETER whatIf
    If enabled it does not execute the backend API call.
 
    .PARAMETER confirm
    If enabled the backend API Call has to be confirmed
 
    .EXAMPLE
    To be added
    in the Future
 
    .NOTES
    General notes
    #>

    [CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = 'Medium')]
    param (
        [parameter(Mandatory)]
        [Dracoon]$connection,
        [parameter(Mandatory, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)]
        [int]$Id,
        [bool]$DeleteLastAdminRooms = $false
    )
    process {
        $apiCallParameter = @{
            Connection = $Connection
            method     = "Delete"
            Path       = "/v4/users/$Id"
        }

        Write-PSFMessage "Lösche User $Id"
        if ($DeleteLastAdminRooms) {
            Write-PSFMessage "Check if the user is last admin of some rooms"
            $lastAdminRooms = Get-DracoonLastAdminRoom -Connection $connection -id $id
            if ($lastAdminRooms) {
                Write-PSFMessage "Removing $($lastAdminRooms.count) rooms"
                $lastAdminRooms | Remove-DracoonNode -Connection $connection
            }
        }
        Invoke-PSFProtectedCommand -Action "Removing User" -Target "$Id" -ScriptBlock {
            $result = Invoke-DracoonAPI @apiCallParameter
            Write-PSFMessage "User removed"
            $result
        } -PSCmdlet $PSCmdlet
    }
}

function Request-DracoonOAuthToken {
    <#
    .SYNOPSIS
    Helper-Function for creation of OAuth Tokens.
 
    .DESCRIPTION
    The function uses OAuth for creating an refresh token which can be used for login to a dracoon instance.
    For connecting to Dracoon via OAuth you always need the -Url as the function needs to know where the Server is located.
    Furthermore you need an AccessToken which can be requested with this function.
    Besides the ClientId/ClientSecret you need to provide one of the following:
    -Credential
    -RefreshToken
    -AuthToken
 
    The RefreshToken and AuthToken can be generated with this function, too.
    For more information about OAuth see about_Dracoon.
 
    .PARAMETER Url
    Base-URL of the Dracoon Server
 
    .PARAMETER Credential
    Credential object used for login.
 
    .PARAMETER RefreshToken
    As an alternative a refresh token can be used instead of a credential Object
 
    .PARAMETER AuthToken
    Authorization Token/Code from Three Legged OAuth Workflow
 
    .PARAMETER ClientID
    OAuth client ID
 
    .PARAMETER ClientSecret
    OAuth client secret
 
    .PARAMETER TokenType
    Defines the type of token to be returned, default "access"
 
    .EXAMPLE
    $accessToken=Request-DracoonOAuthToken -ClientID $clientId -ClientSecret $clientSecret -Url $url -Credential $cred -TokenType access
    #Creates an AccessToken which can be used for simple connection-
    $connection=Connect-Dracoon -Url $url -AccessToken $accessToken
 
    .EXAMPLE
    $refreshToken=Request-DracoonOAuthToken -ClientID $clientId -ClientSecret $clientSecret -Credential $cred -url $url -TokenType refresh
    # Creates a refresh token which can be exchanged for an accessToken.
    $accessToken=Request-DracoonOAuthToken -ClientID $clientId -ClientSecret $clientSecret -Url $url -RefreshToken $refreshToken
 
    .EXAMPLE
    Request-DracoonOAuthToken -url $url -ClientID $ClientID
    Opens the default browser for aquiring an authorization code.
 
    .EXAMPLE
    #Read authorization code and generate an access code from it.
    $tempCred = Get-Credential -Message "Please perform browser login" -UserName "Enter AuthorizationCode as PW"
    $authToken = $tempCred.GetNetworkCredential().Password
    $accessToken = Request-DracoonOAuthToken -url $url -ClientID $ClientID -clientSecret $clientSecret -AuthToken $authToken
 
 
    .NOTES
    General notes
    #>

    param (
        [parameter(mandatory = $true, ParameterSetName = "ThreeLeggedOAuth")]
        [parameter(mandatory = $true, ParameterSetName = "authorization_code")]
        [parameter(mandatory = $true, ParameterSetName = "password")]
        [parameter(mandatory = $true, ParameterSetName = "refresh_token")]
        [PSFramework.TabExpansion.PsfArgumentCompleterAttribute("Dracoon.url")]
        [string]$Url,
        [parameter(mandatory = $true, ParameterSetName = "ThreeLeggedOAuth")]
        [parameter(mandatory = $true, ParameterSetName = "authorization_code")]
        [parameter(mandatory = $true, ParameterSetName = "password")]
        [parameter(mandatory = $true, ParameterSetName = "refresh_token")]
        [string]$ClientID,
        [parameter(mandatory = $true, ParameterSetName = "authorization_code")]
        [parameter(mandatory = $true, ParameterSetName = "password")]
        [parameter(mandatory = $true, ParameterSetName = "refresh_token")]
        [string]$ClientSecret,
        [parameter(mandatory = $true, ParameterSetName = "password")]
        [pscredential]$Credential,
        [parameter(mandatory = $true, ParameterSetName = "authorization_code")]
        [string]$AuthToken,
        [parameter(mandatory = $true, ParameterSetName = "refresh_token")]
        [string]$RefreshToken,
        [ValidateSet('refresh', 'access')]
        [System.String]$TokenType = 'access'
    )
    $serverRoot = Get-DracoonServerRoot $Url
    $callbackUrl = "$serverRoot/oauth/callback"
    Write-PSFMessage "ParameterSet: $($PSCmdlet.ParameterSetName)"
    if ($PSCmdlet.ParameterSetName -eq 'ThreeLeggedOAuth') {
        # Open Browser to request the accesstoken
        $callbackUrl = [System.Web.HttpUtility]::UrlEncode($callbackUrl)
        $openUrl = "$serverRoot/oauth/authorize?response_type=code&client_id=$ClientId&state=xyz&redirect_uri=$callbackUrl"
        Start-Process $openUrl
        Write-PSFMessage -Level Host "Starting Default Browser for Access Token Generation"
    }
    else {
        $Base64AuthInfo = [Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes(("{0}:{1}" -f $ClientID, $ClientSecret)))
        switch ($PSCmdlet.ParameterSetName) {
            "password" { $parameter = @{ "grant_type" = "password"; "username" = $Credential.UserName; "password" = $Credential.GetNetworkCredential().password } }
            "refresh_token" { $parameter = @{ "grant_type" = "refresh_token"; "refresh_token" = "$RefreshToken" } }
            "authorization_code" {
                $parameter = @{  grant_type = "authorization_code"; code = $AuthToken ; redirect_uri = $callbackUrl }
                # $parameter="foo=bar&grant_type=authorization_code&code=$AuthToken&redirect_uri=$callbackUrl&bar=foo"
                # Write-PSFMessage "parameter=$($parameter|convertto-json)"
            }
            Default { Write-PSFMessage -Level Critical "Unknown ParameterSetName $($PSCmdlet.ParameterSetName)" }
        }
        # Write-PSFMessage "parameter=$($parameter|convertto-json)"
        $tokenParameter = @{
            URI         = "$serverRoot/oauth/token"
            Method      = "Post"
            ContentType = "application/x-www-form-urlencoded"
            Body        = $parameter  #($parameter | convertto-json)
            Headers     = @{
                Authorization = ("Basic {0}" -f $Base64AuthInfo)
            }
        }
        # Write-PSFMessage "tokenParameter=$($tokenParameter|convertto-json)"
        try {
            Write-DracoonAPICallMessage $tokenParameter
            $tokenResponse = Invoke-WebRequest @tokenParameter
            # Write-PSFMessage "tokenResponse=$tokenResponse"
            if (($TokenType -eq 'access') -or $RefreshToken) {
                $token = (ConvertFrom-Json $tokenResponse.Content).access_token
            }
            else {
                $token = (ConvertFrom-Json $tokenResponse.Content).refresh_token
            }
            return $token
        }
        catch {
            Write-PSFMessage "Exception: $_"
            $streamReader = [System.IO.StreamReader]::new($_.Exception.Response.GetResponseStream())
            $errResp = $streamReader.ReadToEnd() | ConvertFrom-Json
            $streamReader.Close()
            Write-PSFMessage "Error-Response=$ErrResp"
        }
    }
}

function Search-DracoonNode {
    <#
    .SYNOPSIS
    Provides a flat list of file system nodes (rooms, folders or files) of a given parent that are accessible by the current user.
    API-GET /v4/nodes/search
 
    .DESCRIPTION
    Provides a flat list of file system nodes (rooms, folders or files) of a given parent that are accessible by the current user.
 
    .PARAMETER Connection
    Object of Class [Dracoon], stores the authentication Token and the API Base-URL
 
    .PARAMETER Filter
    All filter fields are connected via logical conjunction (AND)
    Filter string syntax: FIELD_NAME:OPERATOR:VALUE[:VALUE...]
    Example:
 
    type:eq:file|createdAt:ge:2015-01-01
    Get nodes where type equals file AND file creation date is >= 2015-01-01.
 
    Possible combinations:
    'type:[eq]:[room/folder/file]'
    'fileType:[cn/eq]:[search String]'
    'classification:[eq]:[1 - public/2 - internal/3 - confidential/4 - strictly confidential]'
    'createdBy:[cn/eq]:[search String]'
    'createdAt:[ge/le]:[Date (yyyy-MM-dd)]'
    'updatedBy:[cn/eq]:[search String]'
    'updatedAt:[ge/le]:[Date (yyyy-MM-dd)]'
    'expireAt:[ge/le]:[Date (yyyy-MM-dd)]'
    'size:[ge/le]:[size in bytes]'
    'isFavorite:[eq]:[true or false]'
    'branchVersion:[ge/le]:[version number]'
    'parentPath:[cn/eq]:[search String]'
    .PARAMETER Limit
    Range limit. Maximum 500.
    For more results please use paging (offset + limit).
 
    .PARAMETER Offset
    Range offset
 
    .PARAMETER Sort
    Sort string syntax: FIELD_NAME:ORDER
    ORDER can be asc or desc.
    Multiple sort fields are NOT supported.
    Nodes are sorted by type first, then by sent sort string.
    Example:
 
    name:desc
 
    .PARAMETER ParentID
    Parent node ID.
    Only rooms and folders can be parents.
    Parent ID 0 or empty is the root node.
 
    .PARAMETER DepthLevel
    0 - top level nodes only (default)
    -1 - full tree
    n (any positive number) - include n levels starting from the current node
 
    .PARAMETER SearchString
    String to be searched in the NodeName
 
    .EXAMPLE
    To be added
    in the Future
 
    .NOTES
    General notes
    #>

    param (
        [parameter(Mandatory)]
        [Dracoon]$Connection,
        [PSFramework.TabExpansion.PsfArgumentCompleterAttribute("Dracoon.filter")]
        [string]$Filter,
        [int]$Limit=500,
        [int]$Offset=0,
        [string]$Sort,
        [int]$ParentID = 0,
        [int]$DepthLevel = 0,
        [parameter(Mandatory)]
        [string]$SearchString
    )
    $apiCallParameter = @{
        Connection   = $Connection
        method       = "Get"
        Path         ="/v4/nodes/search"
        EnablePaging = $true
        UrlParameter = @{
            filter=$Filter
            limit=$Limit
            sort=$Sort
            offset=$offset
            depth_level=$DepthLevel
            parent_id=$ParentID
            room_manager=$RoomManager
            search_string = $SearchString
        }
        # EnablePaging=$true
    }
    $result = Invoke-DracoonAPI @apiCallParameter
    $result
}

function Send-DracoonDownloadShareMail {
    <#
    .SYNOPSIS
    Sends an E-Mail for an existing Download Share.
 
    .DESCRIPTION
    API-POST /v4/shares/downloads/{share_id}/email
 
    .PARAMETER connection
    Object of Class [Dracoon], stores the authentication Token and the API Base-URL
 
    .PARAMETER Id
    ID of the existing Download Share
 
    .PARAMETER MailBody
    Body for the generated Mail
 
    .PARAMETER Recipients
    Optional Array of text message recipients
 
    .PARAMETER ReceiverLanguage
    Language of the receiver
 
    .PARAMETER whatIf
    If enabled it does not execute the backend API call.
 
    .PARAMETER confirm
    If enabled the backend API Call has to be confirmed
 
    .PARAMETER EnableException
    If set to true, inner exceptions will be rethrown. Otherwise the an empty result will be returned.
 
    .EXAMPLE
    $newShare=New-DracoonDownloadShare -Connection $connection -NodeId $NodeId -MaxDownloads 2
    $currentUser=Get-DracoonCurrentAccount -Connection $connection
    Send-DracoonDownloadShareMail -Connection $connection -Id $newShare.id -Recipient $currentUser.email -MailBody "This is the body"
 
    Sends an E-Mail to the current user mail address.
 
    .NOTES
    #>


    [CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = 'Medium')]
    param (
        [parameter(Mandatory = $true)]
        [Dracoon]$Connection,
        [parameter(Mandatory = $true)]
        [int]$Id,
        [parameter(Mandatory = $true)]
        [string]$MailBody,
        [parameter(Mandatory = $true)]
        [string[]]$Recipients,
        [string]$ReceiverLanguage = "de-DE",
        [bool]$EnableException = $false
    )
    Write-PSFMessage "Creating E-Mail for Download Share $Id"
    $apiCallParameter = @{
        Connection = $Connection
        method     = "Post"
        Path       = "/v4/shares/downloads/$Id/email"
        Body       = @{
            "recipients"       = @($Recipients)
            "body"             = $MailBody
            "receiverLanguage" = $ReceiverLanguage
        }
    }
    Invoke-PSFProtectedCommand -Action "Creating E-Mail for Download Share" -Target "$Id" -ScriptBlock {
        $result = Invoke-DracoonAPI @apiCallParameter -EnableException $EnableException
        $result
    } -PSCmdlet $PSCmdlet -EnableException $EnableException
}

function Set-DracoonRoomAcl {
    <#
    .SYNOPSIS
    Add or change room granted user(s). API-PUT /v4/nodes/rooms/$NodeId/users
 
    .DESCRIPTION
    Batch function. All existing user permissions will be overwritten.
 
    .PARAMETER Connection
    Object of Class [Dracoon], stores the authentication Token and the API Base-URL
 
    .PARAMETER NodeId
    ID of the room
 
    .PARAMETER NewPermission
    Array of the new Permission Items.
    [
        {
            "id": 0,
            "permissions": {
                "manage": true,
                "read": true,
                "create": true,
                "change": true,
                "delete": true,
                "manageDownloadShare": true,
                "manageUploadShare": true,
                "readRecycleBin": true,
                "restoreRecycleBin": true,
                "deleteRecycleBin": true
            }
        }
    ]
 
    .PARAMETER whatIf
    If enabled it does not execute the backend API call.
 
    .PARAMETER confirm
    If enabled the backend API Call has to be confirmed
 
    .EXAMPLE
    To be added
    in the Future
 
    .NOTES
    General notes
    #>

    [CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = 'Medium')]

    param (
        [parameter(Mandatory)]
        [Dracoon]$Connection,
        [parameter(Mandatory)]
        [int]$NodeId,
        [array]$NewPermission
    )
    $apiCallParameter = @{
        Connection   = $Connection
        method       = "Put"
        Path         = "/v4/nodes/rooms/$NodeId/users"
        Body=@{
            items=@()
        }
    }
    $apiCallParameter.Body.items += $NewPermission
    Invoke-PSFProtectedCommand -Action "Setting permissions on node" -Target "$NodeId" -ScriptBlock {
        $result = Invoke-DracoonAPI @apiCallParameter
        Write-PSFMessage "Permissions set"
        $result
    } -PSCmdlet $PSCmdlet
}

function Set-DracoonUrl {
    <#
    .SYNOPSIS
    This function allows to set new Server-URLs for TAB Completion.
    Each function which requires a -Url parameter will provide a TAB completer with suggested URLs.
 
    .DESCRIPTION
    This function allows to set new Server-URLs for TAB Completion.
    Each function which requires a -Url parameter will provide a TAB completer with suggested URLs,
    e.g. Connect-Dracoon
 
    Different from Add-DracoonUrl this command overwrites existing settings.
 
    .PARAMETER NewUrl
    The new URLs to be added
 
    .PARAMETER whatIf
    If enabled it does not execute the backend API call.
 
    .PARAMETER confirm
    If enabled the backend API Call has to be confirmed
 
    .EXAMPLE
    Add-DracoonUrl 'https://dxi.mydomain'
    Add a single Server to the list of suggested URLs
 
    (get-adforest -ErrorAction Stop).domains | ForEach-Object { Add-DracoonUrl "https://dataexchange.$($_)" }
    If you have an on prem Dracoon server in each of your Windows Domains with the address "https://dracoon.<yourdomain>"
    it will get added to the list of suggested URLs.
 
    .NOTES
    The URLs get saved at the PSF-Config "Dracoon.tepp.urls"
    #>

    [CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = 'medium')]
    param (
        [parameter(mandatory = $true, Position = 0)]
        [string[]]$NewUrl
    )
    Write-PSFMessage "Saving new Urls for the URL TEPP: $NewUrl"
    # Adjusting format of URLs
    $NewUrl = $NewUrl | ForEach-Object { Get-DracoonServerRoot $_}
    Invoke-PSFProtectedCommand -Action "Saving new Urls for the URL TEPP" -Target "$NewUrl" -ScriptBlock {
        Set-PSFConfig -Module 'Dracoon' -Name 'tepp.urls' -Value $NewUrl -AllowDelete -PassThru | Register-PSFConfig
    } -PSCmdlet $PSCmdlet
}

# [PSCustomObject]SetUserAttributes([int]$userId, [Hashtable]$userAttributes, [bool]$keepExisting) {
# $items = @()

# foreach ($key in $userAttributes.Keys) {
# $items += @{ key = $key ; value = $userAttributes[$key] }
# }
# $parameter = @{items = $items }
# if ($keepExisting) {
# $result = $this.InvokePut("/v4/users/$userId/userAttributes", $parameter)
# }
# else {
# $result = $this.InvokePost("/v4/users/$userId/userAttributes", $parameter)
# }
# return $result
# }

function Set-DracoonUserAttribute {
    <#
    .SYNOPSIS
    Set custom user attributes. API-(POST/PUT) /v4/users/{user_id}/userAttributes
 
    .DESCRIPTION
    Set custom user attributes. Uses POST for overwriting the userAttributes or PUT for updating the userAttributes.
 
    .PARAMETER Connection
    Object of Class [Dracoon], stores the authentication Token and the API Base-URL
 
    .PARAMETER id
    ID of the user to be changed.
 
    .PARAMETER UserAttributes
    HashTable wit the UserAttributes.
 
    .PARAMETER Overwrite
    Shall all exisiting attributes be overwritten? Default False.
 
    .PARAMETER whatIf
    If enabled it does not execute the backend API call.
 
    .PARAMETER confirm
    If enabled the backend API Call has to be confirmed
 
    .EXAMPLE
    To be added
    in the Future
 
    .NOTES
    If the operation fails the function throws the exception
    #>

    [CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = 'Medium')]
    param (
        [parameter(mandatory = $true)]
        [Dracoon]$Connection,
        [parameter(Mandatory)]
        [int]$Id,
        [Hashtable]$UserAttributes,
        [bool]$Overwrite = $false
    )
    $itemArray = @()
    Write-PSFMessage "Setze User-Attribute für User $id auf $UserAttributes"
    foreach ($key in $UserAttributes.Keys) {
        $itemArray += @{ key = $key ; value = $userAttributes[$key] }
    }
    if ($Overwrite) { $method = "Post" }else { $method = "Put" }

    $apiCallParameter = @{
        Connection = $Connection
        method     = $method
        Path       = "/v4/users/$Id/userAttributes"
        Body       = @{items = $itemArray }
    }

    Invoke-PSFProtectedCommand -Action "Setting attributes on user" -Target "$Id" -ScriptBlock {
        $result = Invoke-DracoonAPI @apiCallParameter
        Write-PSFMessage "Attribute set"
        $result
    } -PSCmdlet $PSCmdlet -EnableException $true
}

function Test-DracoonConnection {
    <#
    .SYNOPSIS
    Test connection to DRACOON Server. API-GET /v4/auth/ping
 
    .DESCRIPTION
    Test connection to DRACOON Server. API-GET /v4/auth/ping
 
    .PARAMETER Connection
    Object of Class [Dracoon], stores the authentication Token and the API Base-URL
 
    .EXAMPLE
    Test-DracoonConnection $connection
    Throws a [System.Net.NetworkInformation.PingException] if connection does not succeed, otherwise
    it returns $true
 
    .NOTES
    General notes
    #>

    param (
        [parameter(Mandatory)]
        [Dracoon]$Connection
    )
    $apiCallParameter = @{
        Connection   = $Connection
        method       = "Get"
        Path         ="/v4/auth/ping"
    }
try {
    $result=Invoke-DracoonAPI @apiCallParameter
    Write-PSFMessage "Ping result: $result"
    if ($result -notmatch '^OK'){
        throw [System.Net.NetworkInformation.PingException]::new("API not pingable, $($connection.serverRoot)/v4/auth/ping")
    }
}
catch {
    throw [System.Net.NetworkInformation.PingException]::new("API not pingable, $($connection.serverRoot)/v4/auth/ping")
}
    $true
}

<#
This is an example configuration file
 
By default, it is enough to have a single one of them,
however if you have enough configuration settings to justify having multiple copies of it,
feel totally free to split them into multiple files.
#>


<#
# Example Configuration
Set-PSFConfig -Module 'Dracoon' -Name 'Example.Setting' -Value 10 -Initialize -Validation 'integer' -Handler { } -Description "Example configuration setting. Your module can then use the setting using 'Get-PSFConfigValue'"
#>


Set-PSFConfig -Module 'Dracoon' -Name 'Import.DoDotSource' -Value $false -Initialize -Validation 'bool' -Description "Whether the module files should be dotsourced on import. By default, the files of this module are read as string value and invoked, which is faster but worse on debugging."
Set-PSFConfig -Module 'Dracoon' -Name 'Import.IndividualFiles' -Value $false -Initialize -Validation 'bool' -Description "Whether the module files should be imported individually. During the module build, all module code is compiled into few files, which are imported instead by default. Loading the compiled versions is faster, using the individual files is easier for debugging and testing out adjustments."
Set-PSFConfig -Module 'Dracoon' -Name 'tepp.urls' -Value "https://dracoon.team" -Initialize -Description "List of URLs for TabCompletion."
Set-PSFConfig -Module 'Dracoon' -Name 'logging.compressRequests' -Value $true -Initialize -Validation 'bool' -Description "Every API call is logged with the tag APICALL. If this setting is $true, the corresponding JSON String will be compressed. Set to $false for better readability."

<#
Stored scriptblocks are available in [PsfValidateScript()] attributes.
This makes it easier to centrally provide the same scriptblock multiple times,
without having to maintain it in separate locations.
 
It also prevents lengthy validation scriptblocks from making your parameter block
hard to read.
 
Set-PSFScriptblock -Name 'Dracoon.ScriptBlockName' -Scriptblock {
     
}
#>


Register-PSFTeppScriptblock -Name "Dracoon.filter" -ScriptBlock {
    try {
        # Get inline help of configured command
        $help = get-help $commandName
        # Get help for the parameter -Filter
        $parameterHelp = $Help.parameters.parameter | Where-Object Name -EQ 'Filter'
        $filterDescription = $parameterHelp.Description.Text
        # Extract filter examples, format:
        # 'attribute:operator:[possible Values]'
        $pattern = "'(\w*?):\[?([\w, \/]*?)\]?:\[?([\w, \/]*?)\]?',?"
        $results = $filterDescription | Select-String $pattern -AllMatches
        foreach ($match in $results.Matches) {
            $attribute = $match.Groups[1]
            # Inline help may provide multiple operators, divided by '/'
            $operators = $match.Groups[2] -split '/'
            # Inline help may provide multiple value examples, divided by '/'
            $valueExamples = $match.Groups[3] -split '/'
            foreach ($operator in $operators) {
                foreach ($value in $valueExamples) {
                    "'$($attribute):$($operator):$($value)'"
                }
            }
        }
    }
    catch {
        Write-PSFMessage -Level Debug "Could not load filter information from Get-Help $commandName"
    }
}


Register-PSFTeppScriptblock -Name "Dracoon.url" -ScriptBlock {
    $staticList=@('https://dracoon.team')
    try {
        $urlList=Get-PSFConfigValue "Dracoon.tepp.urls" -Fallback $staticList
        return $urlList
    }
    catch {
        return $staticList
    }
}


<#
# Example:
Register-PSFTeppScriptblock -Name "Dracoon.alcohol" -ScriptBlock { 'Beer','Mead','Whiskey','Wine','Vodka','Rum (3y)', 'Rum (5y)', 'Rum (7y)' }
#>


<#
# Example:
Register-PSFTeppArgumentCompleter -Command Get-Alcohol -Parameter Type -Name Dracoon.alcohol
#>


New-PSFLicense -Product 'Dracoon' -Manufacturer 'Sascha Spiekermann' -ProductVersion $script:ModuleVersion -ProductType Module -Name MIT -Version "1.0.0.0" -Date (Get-Date "2020-09-21") -Text @"
Copyright (c) 2020 Sascha Spiekermann
 
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
 
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
 
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
"@


Export-PSFModuleClass -ClassType ([Dracoon])
[System.Net.ServicePointManager]::SecurityProtocol = "Tls12"
#endregion Load compiled code