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 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.
 
    .PARAMETER Credential
    Credential-Object zur direkten Anmeldung an Dracoon.
 
    .PARAMETER Url
    Die Base-URL des Servers
 
    .PARAMETER RefreshToken
    Bei Anmeldung per OAuth: Refresh-Token. Kann per Request-OAuthRefreshToken erzeugt werden.
 
    .PARAMETER AccessToken
    Bei Anmeldung per OAuth: Access-Token. Kann per Request-OAuthRefreshToken erzeugt werden.
 
    .PARAMETER ClientID
    Bei Anmeldung per OAuth: Die ID des OAauth Clients
 
    .PARAMETER ClientSecret
    Bei Anmeldung per OAuth: Das Secret des OAuth Clients
 
    .PARAMETER EnableException
    Sollen Exceptions geworfen werden?
 
    .EXAMPLE
    connect-dracoon -Url https://dataexchange.mydomain.com -Credential $cred
    Stellt eine Verbindung zu https://dataexchange.mydomain.com mit den angegebenen Credentials her
 
    $authToken=Request-OAuthRefreshToken -Url https://dataexchange.mydomain.com -Credential (Get-StoredCredential -Target "dataexchange.mydomain.com") -ClientID "0O6WWKp********i2B6yxk8" -ClientSecret "aySR8X***********ei"
    $connection = Connect-Dracoon -Url https://dataexchange.mydomain.com -RefreshToken $authToken -ClientID "0O6WWKp********i2B6yxk8" -ClientSecret "aySR8X***********ei"
    Meldet sich per OAuth an
 
 
    .NOTES
    General notes
    #>


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

    begin {
        Write-PSFMessage "Stelle Verbindung her zu $Url" -Target $Url
        if ($Credential) {
            # $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
        }
        elseif ($RefreshToken) {
            # Invoke-PSFProtectedCommand -ActionString "Connect-Dracoon.Connecting" -ActionStringValues $Url -Target $Url -ScriptBlock {
            # } -PSCmdlet $PSCmdlet -EnableException $EnableException
            $AccessToken=Request-DracoonOAuthToken -Url $Url -ClientID $ClientID -ClientSecret $ClientSecret -RefreshToken $RefreshToken
            $connection = [Dracoon]::new($AccessToken,$Url)
        }else{
            # Connection with an access token
            $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-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
 
    .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
    Example:
 
    name:cn:searchString
    Filter by group name containing searchString.
 
    .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...]
    Example:
    type:eq:room:folder|perm:eq:read
 
    .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
}

# [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.
 
    .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.
 
    .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.
 
    .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.
 
    .PARAMETER Connection
    Object of Class [Dracoon], stores the authentication Token and the API Base-URL
 
    .PARAMETER Path
    API Path to the REST function
 
    .PARAMETER Body
    Parameter for the API call; Converted to the POST body
 
    .PARAMETER UrlParameter
    Parameter for the API call; Converted to the GET URL parameter set
 
    .PARAMETER Method
    HTTP Method
 
    .PARAMETER HideParameters
    If set to $true the password is hidden from logging
 
    .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
    $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,
        [switch]$EnablePaging
    )
    $uri = $connection.webServiceRoot + $path
    if ($UrlParameter) {
        Write-PSFMessage "Wandle URL Parameter in String um und hänge diese an die URI"
        $parameterString = (Get-EncodedParameterString($UrlParameter))
        $uri = $uri + '?' + $parameterString.trim("?")
    }
    $restAPIParameter = @{
        Uri         = $Uri
        method      = $Method
        body        = ($Body | Remove-NullFromHashtable -Json)
        Headers     = $connection.headers
        ContentType = "application/json;charset=UTF-8"
    }
    Write-PSFMessage -Message "Rufe $uri auf" -Target $connection

    if ($method -ne [Microsoft.Powershell.Commands.WebRequestMethod]::Get) {
        $restAPIParameter.body = ($Body | Remove-NullFromHashtable -Json)
    }
    $tempBody = $body
    if ($hideParameters) {
        if ($tempBody.ContainsKey("password")) { $tempBody.set_Item("password", "*****") }
    }
    if ($tempBody) {
        Write-PSFMessage ("Rufe {0} mit {1} auf" -f $uri, ($tempBody  | Remove-NullFromHashtable -Json))
    }
    else {
        Write-PSFMessage ("Rufe {0} auf" -f $uri)
    }

    try {
        Write-PSFMessage -Level Debug "restAPIParameter= $($restAPIParameter|ConvertTo-Json -Depth 5)"
        $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-psfmessage "Rufe API auf mit $($nextParameter|convertto-json -depth 10)" -Level Debug
                write-psfmessage "Rufe API auf mit URL Params auf $($UrlParameter|convertto-json -depth 10)"

                $result = Invoke-DracoonAPI @nextParameter
                $allItems += ($result.items)
            }
            return $allItems
        }
    }
    catch {
        $result = $_.errordetails
        Write-PSFMessage "$result" -Level Critical
        throw $_#$result.Message
    }
    #---------------------------------------------------------------------------------
    # Ist das Objekt zu groß wird es als String und nicht als PSCustomObject
    # ausgegeben. Dieses wird dann mit dieser Methode in ein .DictionaryEntry-Object
    # umgewandelt, um damit arbeiten zu können.
    #---------------------------------------------------------------------------------
    # if ($result.gettype().name -eq "String") {
    # $jsonresult = $connection.jsonserial.DeserializeObject($result)
    # return $jsonresult
    # }
    # else {
    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
}

# [PSCustomObject]CreateADUser([string]$userPrincipalName, [string]$firstName, [string]$lastName, [string]$title, [string]$gender, [string]$domain, [string]$samAccountName) {
# $randomPassword = [Dracoon]::GenerateRandomPassword()
# $adConfigs = $this.getAuthConfigAD()
# $adId = $adConfigs | Where-Object { $_.alias -ieq $domain } | Select-Object -ExpandProperty id
# if (-not ($adId)) {
# throw "Unbekannter AD-Alias $domain"
# }
# $parameter = @{firstName = $firstName; lastName = $lastName; authMethods = @(@{authId = "active_directory"; isEnabled = "true"; options = @(@{key = "ad_config_id"; value = $adId }; @{key = "username"; value = $samAccountName }) }); login = $userPrincipalName; title = $title; gender = $gender; expiration = @{enableExpiration = "false"; expireAt = "2018-01-01T00:00:00" }; receiverLanguage = "de-DE"; email = $userPrincipalName; notifyUser = "false"; needsToChangePassword = "false"; password = $randomPassword }
# $result = $this.InvokePost("/v4/users", $parameter)
# return $result
# }
# [PSCustomObject]CreateMailUser([string]$login, [string]$firstName, [string]$lastName, [string]$title, [string]$gender, [string]$eMail, [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 = "true"; needsToChangePassword = "true" }
# 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
# }

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 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 an OAuth Token.
 
    .DESCRIPTION
    The function uses OAuth for creating an refresh token which can be used for login to a dracoon instance.
 
    .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 ClientID
    OAuth client ID
 
    .PARAMETER ClientSecret
    OAuth client secret
 
    .PARAMETER TokenType
    Defines the type of token to be returned.
 
    .EXAMPLE
    $authToken=Request-OAuthRefreshToken -Url $serverURL -Credential $credential -ClientID "0O6WWKpp0n***********xk8" -clientSecret "aySR8XB*********99Jj7DFgei"
    $connection = Connect-Dracoon -Url $serverURL -RefreshToken $authToken -ClientID "0O6WWKpp0n***********xk8" -clientSecret "aySR8XB*********99Jj7DFgei"
 
    .NOTES
    General notes
    #>

    param (
        [parameter(mandatory = $true, ParameterSetName = "Refresh")]
        [parameter(mandatory = $true, ParameterSetName = "Access")]
        [PSFramework.TabExpansion.PsfArgumentCompleterAttribute("Dracoon.url")]
        [string]$Url,
        [parameter(mandatory = $true, ParameterSetName = "Refresh")]
        [pscredential]$Credential,
        [parameter(mandatory = $true, ParameterSetName = "Access")]
        [string]$RefreshToken,
        [parameter(mandatory = $true, ParameterSetName = "Refresh")]
        [parameter(mandatory = $true, ParameterSetName = "Access")]
        [string]$ClientID,
        [parameter(mandatory = $true, ParameterSetName = "Refresh")]
        [parameter(mandatory = $true, ParameterSetName = "Access")]
        [string]$ClientSecret,
        [ValidateSet('refresh', 'access')]
        [System.String]$TokenType = 'access'
    )

    $serverRoot = Get-DracoonServerRoot $Url
    $Base64AuthInfo = [Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes(("{0}:{1}" -f $ClientID, $ClientSecret)))
    if ($Credential) {
        # Set Username and Password for first login, escape special characters since we use them in URI parameters
        Write-PSFMessage "OAuth-Anmeldung für User $($Credential.UserName) beim Server $serverRoot"
        $parameter = @{ "grant_type" = "password"; "username" = $Credential.UserName; "password" = $Credential.GetNetworkCredential().password }
    }
    elseif ($RefreshToken) {
        Write-PSFMessage "Create AccessToken from RefreshToken"
        write-psfmessage -Level Debug -Message "Login per refreshToken $RefreshToken, Client-ID/Secret: $($ClientId), $($ClientSecret)"
        $parameter = @{ "grant_type" = "refresh_token"; "refresh_token" = "$RefreshToken" }
    }
    $tokenResponse = Invoke-WebRequest  -URI "$serverRoot/oauth/token" -Method Post -ContentType "application/x-www-form-urlencoded" -Body $parameter -Headers @{Authorization = ("Basic {0}" -f $Base64AuthInfo) }
    Write-PSFMessage $tokenResponse
    if (($TokenType -eq 'access') -or $RefreshToken) {
        $token = (ConvertFrom-Json $tokenResponse.Content).access_token
    }
    else {
        $token = (ConvertFrom-Json $tokenResponse.Content).refresh_token
    }
    return $token
}

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.
 
    .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 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."

<#
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 {
    switch ($commandName) {
        'Get-DracoonAuditDataroom' {
            '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]'
        }
        'Get-DracoonNode' {
            '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 contains / equals value.]',
            'encrypted:eq:[true or false]',
            'branchVersion:[ge, le]:[Branch version is greater / less equals than value.]'
        }
        'Get-DracoonUser'{
            'login:cn:[search String]',
            'firstName:cn:[search String]',
            'lastName:cn:[search String]',
            'isLocked:eq:[true or false]',
            'effectiveRoles:eq:[true or false]'
        }
        'Get-DracoonGroup' {
            'name:cn:[search String]'
        }
        'Get-DracoonUserAttribute' {
            'key:[cn, eq, sw]:[Attribute key contains / equals / starts with value. ]',
            'value:[cn, eq, sw]:[Attribute value contains / equals / starts with value. ]'
        }
        'Get-DracoonRoomAcl' {
            'user:cn:[search String]',
            'userId:eq:[positive Integer]',
            'isGranted:eq:[true/false/any]',
            'permissionsManage:eq:[true or false]',
            'effectivePerm:eq:[true or false]'
        }
        'Search-DracoonNode' {
            '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]'
        }
        Default {}
    }
    # $staticList=@(
    # 'https://murks.mydomain.com',
    # 'https://dataexchange.tkds.mydomain.com',
    # 'https://dataexchange.tkdo.mydomain.com',
    # 'https://dataexchange.tkbe.mydomain.com',
    # 'https://dataexchange.tkbr.mydomain.com',
    # 'https://dataexchange.tkme.mydomain.com',
    # 'https://dxi.mydomain.com'
    # )
    # if (-not $env:USERDNSDOMAIN) {
    # return $staticList
    # }
    # try {
    # (get-adforest -ErrorAction Stop).domains | ForEach-Object { "https://dataexchange.$($_)" }
    # 'https://dxi.mydomain.com'
    # }
    # catch {
        # return $staticList
    # }
}


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