PSTANSS.psm1

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

# Detect whether at some level dotsourcing was enforced
$script:doDotSource = Get-PSFConfigValue -FullName PSTANSS.Import.DoDotSource -Fallback $false
if ($PSTANSS_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 PSTANSS.Import.IndividualFiles -Fallback $false
if ($PSTANSS_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 'PSTANSS' -Language 'en-US'

function Assert-CacheRunspaceRunning {
    <#
    .Synopsis
        Assert-CacheRunspaceRunning
 
    .DESCRIPTION
        Check cache validation runspace on status
 
    .EXAMPLE
        PS C:\> Assert-CacheRunspaceRunning
 
        Check cache validation runspace on status
 
    .NOTES
        Author: Andreas Bellstedt
 
    .LINK
        https://github.com/AndiBellstedt/PSTANSS
    #>

    [CmdletBinding(
        SupportsShouldProcess = $false,
        ConfirmImpact = 'Low'
    )]
    Param(
    )

    Write-PSFMessage -Level Debug -Message "Check cache validationRunspace"

    if ([TANSS.Cache]::StopValidationRunspace -eq $true) {
        Write-PSFMessage -Level Debug -Message "ValidationRunspace is stopped. Going to start the runspace again"

        # force to stop the runspace
        [TANSS.Cache]::StopValidationRunspace = $true
        Get-PSFRunspace -Name "TANSS.LookupValidation" | Stop-PSFRunspace

        # Restart the runspace
        try {
            [TANSS.Cache]::StopValidationRunspace = $false
            Start-PSFRunspace -Name "TANSS.LookupValidation" -ErrorAction Stop -ErrorVariable invokeError
        } catch {
            Stop-PSFFunction -Message "Error Starting ValidationRunspace. Unknown module behaviour. Please restart your powershell console!" -EnableException $true -Exception $invokeError -Tag "RunSpace"
            throw $invokeError
        }
    }

}


function ConvertFrom-Base64StringWithNoPadding( [string]$Data ) {
    <#
    .SYNOPSIS
        Helper function build valid Base64 strings from JWT access tokens
 
    .DESCRIPTION
        Helper function build valid Base64 strings from JWT access tokens
 
    .PARAMETER Data
        The Token to convert
 
    .EXAMPLE
        PS C:\> ConvertFrom-Base64StringWithNoPadding -Data $data
 
        build valid base64 string the content from variable $data
    #>

    $Data = $Data.Replace('-', '+').Replace('_', '/')
    switch ($Data.Length % 4) {
        0 { break }
        2 { $Data += '==' }
        3 { $Data += '=' }
        default { throw New-Object ArgumentException('data') }
    }
    [System.Convert]::FromBase64String($Data)
}

function ConvertFrom-JWTtoken {
    <#
    .SYNOPSIS
        Converts access tokens to readable objects
 
    .DESCRIPTION
        Converts access tokens to readable objects
 
    .PARAMETER TokenText
        The Token to convert
 
    .EXAMPLE
        PS C:\> ConvertFrom-JWTtoken -Token $TokenText
 
        Converts the content from variable $TokenText to an object
    #>

    [cmdletbinding()]
    param(
        [Parameter(Mandatory = $true)]
        [string]
        $TokenText
    )

    # Validate as per https://tools.ietf.org/html/rfc7519 - Access and ID tokens are fine, Refresh tokens will not work
    if ((-not $TokenText.Contains(".")) -or (-not $TokenText.StartsWith("eyJ"))) {
        $msg = "Invalid data or not an access/refresh token. $($TokenText)"
        Stop-PSFFunction -Message $msg -Tag "JWT" -EnableException $true -Exception ([System.Management.Automation.RuntimeException]::new($msg))
    }

    # Split the token in its parts
    $tokenParts = $TokenText.Split(".")

    # Work on header
    $tokenHeader = [System.Text.Encoding]::UTF8.GetString( (ConvertFrom-Base64StringWithNoPadding $tokenParts[0]) )
    $tokenHeaderJSON = $tokenHeader | ConvertFrom-Json

    # Work on payload
    $tokenPayload = [System.Text.Encoding]::UTF8.GetString( (ConvertFrom-Base64StringWithNoPadding $tokenParts[1]) )
    $tokenPayloadJSON = $tokenPayload | ConvertFrom-Json

    # Work on signature
    $tokenSignature = ConvertFrom-Base64StringWithNoPadding $tokenParts[2]

    # Output
    $resultObject = [PSCustomObject]@{
        "alg"       = $tokenHeaderJSON.alg
        "typ"       = $tokenHeaderJSON.typ
        "kid"       = $tokenHeaderJSON.kid
        "sub"       = $tokenPayloadJSON.sub
        "exp"       = [datetime]::new(1970, 1, 1, 0, 0, 0, 0, [DateTimeKind]::Utc).AddSeconds($tokenPayloadJSON.exp).ToLocalTime()
        "type"      = $tokenPayloadJSON.type
        "signature" = $tokenSignature
    }

    $resultObject
}

function ConvertFrom-NameCache {
    <#
    .Synopsis
        ConvertFrom-NameCache
 
    .DESCRIPTION
        Convert Name to ID from cached TANSS.Lookup values
 
    .PARAMETER Name
        Name to convert into ID
 
    .PARAMETER Id
        Id to convert into Name
 
    .PARAMETER Type
        Lookup type where the name should convert from
 
    .EXAMPLE
        PS C:\> ConvertFrom-NameCache -Name "User X" -Type "Employee"
 
        Example
 
    .NOTES
        Author: Andreas Bellstedt
 
    .LINK
        https://github.com/AndiBellstedt/PSTANSS
    #>

    [CmdletBinding(
        DefaultParameterSetName = "FromName",
        SupportsShouldProcess = $false,
        ConfirmImpact = 'Low'
    )]
    Param(
        [Parameter(
            ParameterSetName = "FromName",
            Mandatory = $true
        )]
        [string]
        $Name,

        [Parameter(
            ParameterSetName = "FromId",
            Mandatory = $true
        )]
        [int]
        $Id,

        [Parameter(Mandatory = $true)]
        [ValidateSet("Companies", "Contracts", "CostCenters", "Departments", "Employees", "OrderBys", "Phases", "Tags", "Tickets", "TicketStates", "TicketTypes", "VacationAbsenceSubTypes", "VacationTypesPredefinedApi")]
        [string]
        $Type

    )

    $parameterSetName = $pscmdlet.ParameterSetName
    Write-PSFMessage -Level Debug -Message "ParameterNameSet: $($parameterSetName)"

    switch ($parameterSetName) {
        "FromName" {
            Write-PSFMessage -Level Verbose -Message "Start converting '$($Name)' of type '$($Type)' to ID"
            if ( ([TANSS.Lookup]::$Type).ContainsValue($Name) ) {
                foreach ($key in [TANSS.Lookup]::$Type.Keys) {
                    if ([TANSS.Lookup]::$Type[$key] -like $Name) {
                        Write-PSFMessage -Level Verbose -Message "Found ID '$key' for name '$($Name)' of type '$($Type)'"
                        return $key
                    }
                }
            } else {
                Write-PSFMessage -Level Error -Message "Unable to convert '$($Name)' of type '$($Type)' in ID. Name is not in present in cache."
            }
        }

        "FromId" {
            Write-PSFMessage -Level Verbose -Message "Start converting ID '$($Id)' of type '$($Type)' to name"
            if ( ([TANSS.Lookup]::$Type).ContainsKey("$($Id)") ) {
                $output = [TANSS.Lookup]::$Type["$($Id)"]
                Write-PSFMessage -Level Verbose -Message "Found '$output' with ID '$($Id)' of type '$($Type)'"
                return $output
            } else {
                Write-PSFMessage -Level Error -Message "Unable to convert '$($Id)' of type '$($Type)' into Name. Id is not in present in cache."
            }
        }

        Default {
            Stop-PSFFunction -Message "Unhandeled ParameterSetName. Developers mistake." -EnableException $true
            throw
        }
    }

}


Function ConvertFrom-UnixEpochTime {
    <#
    .SYNOPSIS
        Converts UNIX Epoch Time to DateTime object
 
    .DESCRIPTION
        Converts UNIX Epoch Time to DateTime object
 
    .PARAMETER EpochTime
        The time value to convert
 
    .PARAMETER UTC
        convert the given Epoch without following the lcoal timezone
 
    .EXAMPLE
        PS C:\> ConvertFrom-UnixEpochTime -EpochTime "1641769200"
 
        Converts the content from variable $TokenText to an object
    #>

    param(
        # Parameter help description
        [Parameter(
            ValueFromPipeline = $true,
            ValueFromPipelineByPropertyName = $true,
            Mandatory = $true
        )]
        [int[]]
        $EpochTime,

        [switch]
        $UTC
    )

    Process {

        foreach ($item in $EpochTime) {
            if ($UTC) {
                ([datetime]'1/1/1970').AddSeconds($item)
            } else {
                [timezone]::CurrentTimeZone.ToLocalTime(([datetime]'1/1/1970').AddSeconds($item))
            }
        }

    }
}


function Format-ApiPath {
    <#
    .Synopsis
        Format-ApiPath
 
    .DESCRIPTION
        Ensure the right format and the existense of api prefix in the given path
 
    .PARAMETER Path
        Path to format
 
    .PARAMETER QueryParameter
        A hashtable for all the parameters to the api route
 
    .EXAMPLE
        PS C:\> Format-ApiPath -Path $ApiPath
 
        Api path data from variable $ApiPath will be tested and formatted.
 
    .NOTES
        Author: Andreas Bellstedt
 
    .LINK
        https://github.com/AndiBellstedt/PSTANSS
    #>

    [CmdletBinding(
        SupportsShouldProcess = $false,
        ConfirmImpact = 'Low'
    )]
    Param(
        [Parameter(Mandatory = $true)]
        [string]
        $Path,

        [hashtable]
        $QueryParameter
    )

    # Start Function
    Write-PSFMessage -Level System -Message "Formatting API path '$($Path)'"

    # receive module cental configuration for prefix on api path (default is 'backend/')
    $apiPrefix = Get-PSFConfigValue -FullName 'PSTANSS.API.RestPathPrefix' -Fallback ""

    # remove no more need slashes
    $apiPath = $Path.Trim('/')


    # check on API path prefix
    if (-not $ApiPath.StartsWith($apiPrefix)) {
        $ApiPath = $apiPrefix + $ApiPath
        Write-PSFMessage -Level Debug -Message "Add API prefix, formatting path to '$($ApiPath)'"
    } else {
        Write-PSFMessage -Level Debug -Message "Prefix API path already present"
    }


    # If specified, process hashtable QueryParameters to valid parameters into uri
    if ($MyInvocation.BoundParameters['QueryParameter'] -and $QueryParameter) {
        Write-PSFMessage -Level Debug -Message "Add query parameters '$([string]::Join("' ,'", $QueryParameter.Keys))'"

        $apiPath = "$($apiPath)?"
        $i = 0

        foreach ($key in $QueryParameter.Keys) {
            if ($i -gt 0) {
                $apiPath = "$($apiPath)&"
            }

            if ("System.Array" -in ($QueryParameter[$Key]).psobject.TypeNames) {
                $parts = $QueryParameter[$Key] | ForEach-Object { "$($key)=$($_)" }
                $apiPath = "$($apiPath)$([string]::Join("&", $parts))"
            } else {
                $apiPath = "$($apiPath)$($key)=$($QueryParameter[$Key])"
            }

            $i++
        }
    }


    # Output Result
    $ApiPath = $ApiPath.TrimEnd("?")
    $ApiPath
}


function Invoke-CacheRefresh {
    <#
    .Synopsis
        Invoke-CacheRefresh
 
    .DESCRIPTION
        Invokes api calls to fill mostly used lookup values
 
    .PARAMETER Token
        AccessToken object to register as default connection for TANSS
 
    .EXAMPLE
        PS C:\> Invoke-CacheRefresh -Token $token
 
        Example
 
    .NOTES
        Author: Andreas Bellstedt
 
    .LINK
        https://github.com/AndiBellstedt/PSTANSS
    #>

    [CmdletBinding(
        SupportsShouldProcess = $false,
        ConfirmImpact = 'Low'
    )]
    Param(
        [Parameter(Mandatory = $true)]
        [TANSS.Connection]
        $Token
    )

    Write-PSFMessage -Level Verbose -Message "Start updating lookup cache from current tickets in TANSS" -Tag "Cache"

    $tickets = @()
    $tickets += Get-TANSSTicket -MyTickets -Token $token
    $tickets += Get-TANSSTicket -NotAssigned -Token $token
    $tickets += Get-TANSSTicket -AllTechnician -Token $token
    Write-PSFMessage -Level Verbose -Message "Built cache from $($tickets.count) tickets" -Tag "Cache"

    $null = Get-TANSSVacationAbsenceSubType -Token $token
    $null = Get-TANSSDepartment -Token $token
    $null = Get-TANSSTicketStatus -Token $token
    $null = Get-TANSSTicketType -Token $token
}


function Invoke-TANSSTokenCheck {
    <#
    .Synopsis
        Test a TANSS connection- oder service-token
 
    .DESCRIPTION
        Tests validity for a TANSS.Connection object
 
    .PARAMETER Token
        TANSS.Connection Token object to check on
 
    .PARAMETER NoRefresh
        Indicates that the function will not try to update the specified token
 
    .PARAMETER DoNotRegisterConnection
        Do not register the connection as default connection
 
    .PARAMETER PassThru
        Outputs the token to the console, even when the register switch is set
 
    .EXAMPLE
        PS C:\> Invoke-TANSSTokenCheck -Token $Token
 
        Test the TANSS.Connection object from variable $Token for validity
        If the token has a lifetime under 5 percent, the function will try to update the token.
        If the token matches the registered token within the module, the updated token will also be registered.
 
    .EXAMPLE
        PS C:\> Invoke-TANSSTokenCheck -Token $Token -NoRefresh
 
        Test the TANSS.Connection object from variable $Token for validity, but will NOT try to update the token.
        Considered for testing ServiceTokes, that can't be updated
 
    .EXAMPLE
        PS C:\> Invoke-TANSSTokenCheck -Token $Token -DoNotRegisterConnection -PassThru
 
        Test the TANSS.Connection object from variable $Token for validity.
        If the token has a lifetime under 5 percent, the function will try to update the token,
        but not registered as the standard token for the module. Instead, the token will be outputted to the console.
 
    .NOTES
        Author: Andreas Bellstedt
 
    .LINK
        https://github.com/AndiBellstedt/PSTANSS
    #>

    [CmdletBinding(
        DefaultParameterSetName = "Default",
        SupportsShouldProcess = $false,
        ConfirmImpact = 'Low'
    )]
    [OutputType([TANSS.Connection])]
    Param(
        [Parameter(
            Mandatory = $true,
            ValueFromPipeline = $true,
            ValueFromPipelineByPropertyName = $true
        )]
        [TANSS.Connection]
        $Token,

        [Parameter(ParameterSetName = "NoRefresh")]
        [switch]
        $NoRefresh,

        [Parameter(ParameterSetName = "Default")]
        [switch]
        $DoNotRegisterConnection,

        [switch]
        $PassThru
    )

    begin {
        $registeredToken = Get-TANSSRegisteredAccessToken
    }

    process {
        # General validity check
        if (-not $Token.IsValid) {
            Stop-PSFFunction -Message "$($Token.EmployeeType) token for '$($Token.UserName)' on $($Token.Server) is not valid" -Tag "AccessToken", "InvalidToken" -EnableException $true -PSCmdlet $pscmdlet
        }

        # Lifetime check
        if ($Token.PercentRemaining -lt 5) {
            Write-PSFMessage -Level Warning -Message "$($Token.EmployeeType) token for '$($Token.UserName)' on $($Token.Server) is about to expire in $($Token.TimeRemaining.Minutes) min" -Tag "AccessToken", "InvalidToken"

            if ((-not $NoRefresh) -and $Token.RefreshToken) {
                Write-PSFMessage -Level Verbose -Message "Going to try a token refresh" -Tag "AccessToken"

                # Compile parameters for Token refresh
                $paramUpdateTANSSAccessToken = @{
                    "Token"          = $Token
                    "NoCacheRefresh" = $true
                    "PassThru"       = $true
                }
                if ((([System.Runtime.InteropServices.Marshal]::PtrToStringAuto([System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($registeredToken.AccessToken))) -notlike [System.Runtime.InteropServices.Marshal]::PtrToStringAuto([System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($Token.AccessToken))) -or $DoNotRegisterConnection) {
                    $paramUpdateTANSSAccessToken.add("DoNotRegisterConnection", $false)
                } else {
                    $paramUpdateTANSSAccessToken.add("DoNotRegisterConnection", $true)
                }

                $newToken = Update-TANSSAccessToken @paramUpdateTANSSAccessToken

                # Output result
                if ($PassThru) {
                    $newToken
                }
            } else {
                Write-PSFMessage -Level Important -Message "Please aquire a new token as soon as possible" -Tag "AccessToken", "NoAccessTokenRefresh"
            }
        }

        # Output if
        if((-not $newToken) -and $PassThru) {
            $Token
        }
    }

    end {}
}


function Push-DataToCacheRunspace {
    <#
    .Synopsis
        Push-DataToCacheRunspace
 
    .DESCRIPTION
        Push meta information to runspace cache
 
    .PARAMETER MetaData
        The metadata PSCusomobject to push to Cache
 
    .EXAMPLE
        PS C:\> Push-DataToCacheRunspace -MetaData $response.meta
 
        Push meta information to runspace cache
 
    .NOTES
        Author: Andreas Bellstedt
 
    .LINK
        https://github.com/AndiBellstedt/PSTANSS
    #>

    [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseShouldProcessForStateChangingFunctions", "")]
    [CmdletBinding(
        SupportsShouldProcess = $false,
        PositionalBinding = $true,
        ConfirmImpact = 'Low'
    )]
    Param(
        [Alias("Meta", "Data")]
        $MetaData
    )

    Write-PSFMessage -Level Debug -Message "Pushing data to cache validationRunspace"

    [TANSS.Cache]::Data.Add((New-Guid), $MetaData)

}


function Update-CacheLookup {
    <#
    .Synopsis
        Update-CacheLookup
 
    .DESCRIPTION
        Update a cache lookup hashtable with an object
 
    .PARAMETER LookupName
        Name of LokkupClass where to update
 
    .PARAMETER Id
        The Id to of the record to cache
 
    .PARAMETER Name
        The name of the record to cache
 
    .EXAMPLE
        PS C:\> Update-CacheLookup -LookupName "Departments" -Id $department.Id -Name $department.Name
 
        Update or insert the key from variable $department.Id of the cache-lookup-hashtable [TANSS.Lookup]::Departments with the name $department.Name
 
    .NOTES
        Author: Andreas Bellstedt
 
    .LINK
        https://github.com/AndiBellstedt/PSTANSS
    #>

    [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseShouldProcessForStateChangingFunctions", "")]
    [CmdletBinding(
        SupportsShouldProcess = $false,
        PositionalBinding = $true,
        ConfirmImpact = 'Low'
    )]
    Param(
        [string]
        $LookupName,

        [int]
        $Id,

        [string]
        $Name
    )

    if ([TANSS.Lookup]::$LookupName["$($Id)"] -notlike $Name) {
        if ([TANSS.Lookup]::$LookupName["$($Id)"]) {
            Write-PSFMessage -Level Debug -Message "Update existing id '$($Id)' in [TANSS.Lookup]::$($LookupName) with value '$($Name)'" -Tag "Cache", $LookupName
            [TANSS.Lookup]::$LookupName["$($Id)"] = $Name
        } else {
            Write-PSFMessage -Level Debug -Message "Insert in [TANSS.Lookup]::$($LookupName): $($Id) - '$($($Name))'" -Tag "Cache", $LookupName
            ([TANSS.Lookup]::$LookupName).Add("$($Id)", $Name)
        }
    }

}


function ConvertFrom-TANSSTimeStampParameter {
    <#
    .Synopsis
        ConvertFrom-TANSSTimeStampParameter
 
    .DESCRIPTION
        Convert display names for Type & State parameter into api texts
 
    .PARAMETER Text
        If not specified, the registered default token from within the module is going to be used
 
    .PARAMETER TextType
        Specifies if the text is a timestampe "state" or "type"
 
    .EXAMPLE
        PS C:\> ConvertFrom-TANSSTimeStampParameter -Text "Coming" -TextType "State"
 
        Outputs "On" as a "comming state for TANSS api
 
    .NOTES
        Author: Andreas Bellstedt
 
    .LINK
        https://github.com/AndiBellstedt/PSTANSS
    #>

    [CmdletBinding(
        PositionalBinding = $true,
        ConfirmImpact = 'Low'
    )]
    Param(
        [Parameter(
            Mandatory=$true,
            ValueFromPipeline=$true
            )]
        [ValidateSet("Coming", "Leaving", "StartPause", "EndPause", "Work", "Inhouse", "Errand", "Vacation", "Illness", "PaidAbsence", "UnpaidAbsence", "Overtime", "Support")]
        [string]
        $Text,

        [Parameter(
            Mandatory=$true
        )]
        [ValidateSet("State", "Type")]
        [String]
        $TextType
    )

    begin {}

    process {
        Write-PSFMessage -Level Debug -Message "Start converting '$($Text)' as '$($TextType)' to ApiText value"
        $apiText = ""

        switch ($TextType) {
            "State" {
                switch ($Text) {
                    "Coming" { $apiText = "ON" }
                    "Leaving" { $apiText = "OFF" }
                    "StartPause" { $apiText = "PAUSE_START" }
                    "EndPause" { $apiText = "PAUSE_END" }
                    Default {
                        Stop-PSFFunction -Message "Unhandeled pattern for parameter Text. Developers mistake." -EnableException $true -Cmdlet $pscmdlet
                    }
                }
            }

            "Type" {
                switch ($Text) {
                    "Work" { $apiText = "WORK" }
                    "Inhouse" { $apiText = "INHOUSE" }
                    "Errand" { $apiText = "ERRAND" }
                    "Vacation" { $apiText = "VACATION" }
                    "Illness" { $apiText = "ILLNESS" }
                    "PaidAbsence" { $apiText = "ABSENCE_PAID" }
                    "UnpaidAbsence" { $apiText = "ABSENCE_UNPAID" }
                    "Overtime" { $apiText = "OVERTIME" }
                    "Support" { $apiText = "DOCUMENTED_SUPPORT" }
                    Default {
                        Stop-PSFFunction -Message "Unhandeled pattern for parameter Text. Developers mistake." -EnableException $true -Cmdlet $pscmdlet
                    }
                }
            }

            Default {
                Stop-PSFFunction -Message "Unhandeled pattern for parameter TextType. Developers mistake." -EnableException $true -Cmdlet $pscmdlet
            }
        }

        # Output
        Write-PSFMessage -Level Debug -Message "'$($Text)' as '$($TextType)' converted into ApiText value: $($apiText) done"
        $apiText
    }

    end {}
}


function Connect-TANSS {
    <#
    .Synopsis
        Connect-TANSS
 
    .DESCRIPTION
        Connect to TANSS Service
 
    .PARAMETER Server
        Name of the service to connect to
 
    .PARAMETER Credential
        The credentials to login
 
    .PARAMETER LoginToken
        If the user needs an -application specific- login token for MFA, this field must be set as well
 
    .PARAMETER Protocol
        Specifies if the connection is done with http or https
 
    .PARAMETER DoNotRegisterConnection
        Do not register the connection as default connection
 
    .PARAMETER NoCacheInit
        Do not query current existing tickets and various types to fill cache data for lookup types
 
    .PARAMETER PassThru
        Outputs the token to the console, even when the register switch is set
 
    .PARAMETER Confirm
        If this switch is enabled, you will be prompted for confirmation before executing any operations that change state.
 
    .EXAMPLE
        PS C:\> Connect-TANSS -Server "tanss.company.com" -Credential (Get-Credential "username")
 
        Connects to "tanss.company.com" via HTTPS protocol and the specified credentials.
        Connection will be set as default connection for any further action.
 
    .NOTES
        Author: Andreas Bellstedt
 
    .LINK
        https://github.com/AndiBellstedt/PSTANSS
    #>

    [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidUsingConvertToSecureStringWithPlainText", "")]
    [CmdletBinding(
        DefaultParameterSetName = 'Credential',
        SupportsShouldProcess = $false,
        PositionalBinding = $true,
        ConfirmImpact = 'Medium'
    )]
    [OutputType([TANSS.Connection])]
    Param(
        [Parameter(
            Mandatory = $true,
            ValueFromPipeline = $true,
            ValueFromPipelineByPropertyName = $true
        )]
        [Alias("ComputerName", "Hostname", "Host", "ServerName")]
        [String]
        $Server,

        [Parameter(
            Mandatory = $true,
            ParameterSetName = 'Credential'
        )]
        [System.Management.Automation.PSCredential]
        $Credential,

        [Parameter(ParameterSetName = 'Credential')]
        [string]
        $LoginToken,

        [ValidateSet("HTTP", "HTTPS")]
        [ValidateNotNullOrEmpty()]
        [String]
        $Protocol = "HTTPS",

        [Alias('NoRegistration')]
        [Switch]
        $DoNotRegisterConnection,

        [switch]
        $NoCacheInit,

        [switch]
        $PassThru
    )

    begin {
        $ApiPath = Format-ApiPath -Path "api/v1/user/login"
    }

    process {
        if ($protocol -eq 'HTTP') {
            Write-PSFMessage -Level Important -Message "Unsecure $($protocol) connection with possible security risk detected. Please consider switch to HTTPS!" -Tag "Connection"
            $prefix = 'http://'
        } else {
            Write-PSFMessage -Level System -Message "Using secure $($protocol) connection." -Tag "Connection"
            $prefix = 'https://'
        }

        if ($Server -match '//') {
            if ($Server -match '\/\/(?<Server>(\w+|\.)+)') { $Server = $Matches["Server"] }
            Remove-Variable -Name Matches -Force -Verbose:$false -Debug:$false -Confirm:$false
        }

        if ($PsCmdlet.ParameterSetName -eq 'Credential') {
            if (($credential.UserName.Split('\')).count -gt 1) {
                $userName = $credential.UserName.Split('\')[1]
            } else {
                $userName = $credential.UserName
            }

            Write-PSFMessage -Level Verbose -Message "Authenticate user '$($userName)' to service '$($Prefix)$($server)'" -Tag "Connection", "Authentication"
            $param = @{
                "Uri"           = "$($prefix)$($server)/$($ApiPath)"
                "Headers"       = @{
                    "user"       = $userName
                    "password"   = $credential.GetNetworkCredential().Password
                    "logintoken" = "$($LoginToken)"
                }
                "Verbose"       = $false
                "Debug"         = $false
                "ErrorAction"   = "Stop"
                "ErrorVariable" = "invokeError"
            }
            try {
                $response = Invoke-RestMethod @param
            } catch {
                Stop-PSFFunction -Message "Error invoking rest call on service '$($Prefix)$($server)'. $($invokeError)" -Tag "Connection", "Authentication" -EnableException $true -Cmdlet $pscmdlet
            }

            if ($response.meta.text -like "Unsuccesful login attempt") {
                $msgText = "$($response.meta.text) to service '$($Prefix)$($server)'. Maybe wrong password"
                if (-not $LoginToken) {
                    $msgText = "$($msgText) or LoginToken (OTP) is needed"
                } else {
                    $msgText = "$($msgText) or LoginToken (OTP) wrong/expired"
                }
                Stop-PSFFunction -Message $msgText -Tag "Connection", "Authentication" -EnableException $true -Cmdlet $pscmdlet
            }

            if (-not $response.content.apiKey) {
                Stop-PSFFunction -Message "Something went wrong on authenticating user $($userName). No apiKey found in response. Unable login to service '$($Prefix)$($server)'" -Tag "Connection", "Authentication" -EnableException $true -Cmdlet $pscmdlet
            }
        }

        Write-PSFMessage -Level System -Message "Creating TANSS.Connection" -Tag "Connection"
        $token = [TANSS.Connection]@{
            Server            = "$($Prefix)$($Server)"
            UserName          = $userName
            EmployeeId        = $response.content.employeeId
            EmployeeType      = $response.content.employeeType
            AccessToken       = ($response.content.apiKey | ConvertTo-SecureString -AsPlainText -Force)
            RefreshToken      = ($response.content.refresh | ConvertTo-SecureString -AsPlainText -Force)
            Message           = $response.meta.text
            TimeStampCreated  = Get-Date
            TimeStampExpires  = [datetime]::new(1970, 1, 1, 0, 0, 0, 0, [DateTimeKind]::Utc).AddSeconds($response.content.expire).ToLocalTime()
            TimeStampModified = Get-Date
        }

        if (-not $NoCacheInit) { Invoke-CacheRefresh -Token $token }

        if (-not $DoNotRegisterConnection) {
            # Make the connection the default connection for further commands
            Register-TANSSAccessToken -Token $token

            Write-PSFMessage -Level Significant -Message "Connected to service '($($token.Server))' as '$($token.UserName)' as default connection" -Tag "Connection"

            if ($PassThru) {
                Write-PSFMessage -Level System -Message "Outputting TANSS.Connection object" -Tag "Connection"
                $token
            }
        } else {
            Write-PSFMessage -Level Significant -Message "Connected to service '($($token.Server))' as '$($token.UserName)', outputting TANSS.Connection" -Tag "Connection"
            $token
        }
    }

    end {
    }
}


function Find-TANSSObject {
    <#
    .Synopsis
        Find-TANSSObject
 
    .DESCRIPTION
        Find a object via global search in TANSS
        The search has to be initiated on one of three areas. (Company, Employees, Tickets)
 
    .PARAMETER Company
        Initiate a search in the company area of TANSS
 
    .PARAMETER Employee
        Initiate a search within the employee/person database of TANSS
 
    .PARAMETER TicketPreview
        Initiate a search in the tickets of TANSS
 
    .PARAMETER Text
        The Text (id or name) to seach for
 
    .PARAMETER ShowInactive
        Search company records that are marked as inactive
        By default, only companies that are marked as "active"
 
        This is bound to company search only
 
    .PARAMETER ShowLocked
        Search company records that are marked as locked
        By default, only companies that are marked as "Unlocked"
 
        This is bound to company search only
 
    .PARAMETER CompanyId
        Return tickets or employees of the specified company id
 
        This is bound to ticket- and employee-search only
 
    .PARAMETER CompanyName
        Return tickets or employees of the specified company name
 
        This is bound to ticket- and employee-search only
 
    .PARAMETER Status
        Return "All", only "Active" or only "Inactive" employees.
 
    .PARAMETER GetCategories
        If true, categories will be fetches as well.
        The names are given in the "linked entities"-"employeeCategories"
 
        Default is $false
 
    .PARAMETER GetCallbacks
        If true, expected callbacks will be fetched as well
 
        Default is $false
 
    .PARAMETER PreviewContentMaxChars
        If defined, it overrides the to preview the content
 
        Default values within the api is 60
 
    .PARAMETER ResultSize
        The amount of objects the query will return
        To avoid long waitings while query a large number of items, the api
        by default only query an amount of 100 items within one call
 
    .PARAMETER Token
        The TANSS.Connection token to access api
 
        If not specified, the registered default token from within the module is going to be used
 
    .EXAMPLE
        PS C:\> Find-TANSSObject -Company -Text "Customer X"
 
        Search for "Customer X" within all company data
 
    .EXAMPLE
        PS C:\> Find-TANSSObject -TicketPreview -Text "Issue Y"
 
        Search for "Issue Y" within all tickets
 
    .EXAMPLE
        PS C:\> "Mister T" | Find-TANSSObject -Employee
 
        Search "Mister T" in the employee records of all companies
 
    .NOTES
        Author: Andreas Bellstedt
 
    .LINK
        https://github.com/AndiBellstedt/PSTANSS
    #>

    [CmdletBinding(
        DefaultParameterSetName = "Company",
        PositionalBinding = $true,
        SupportsShouldProcess = $false,
        ConfirmImpact = 'Low'
    )]
    [OutputType([TANSS.TicketPreview], [TANSS.Company], [TANSS.EmployeeSearched])]
    Param(
        [Parameter(ParameterSetName = "Company")]
        [switch]
        $Company,

        [Parameter(ParameterSetName = "Employee-UserFriendly")]
        [Parameter(ParameterSetName = "Employee-ApiNative")]
        [switch]
        $Employee,

        [Parameter(ParameterSetName = "Ticket-UserFriendly")]
        [Parameter(ParameterSetName = "Ticket-ApiNative")]
        [Alias("Ticket")]
        [switch]
        $TicketPreview,

        [Parameter(ParameterSetName = "Company", Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)]
        [Parameter(ParameterSetName = "Employee-UserFriendly", Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)]
        [Parameter(ParameterSetName = "Employee-ApiNative", Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)]
        [Parameter(ParameterSetName = "Ticket-UserFriendly", Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)]
        [Parameter(ParameterSetName = "Ticket-ApiNative", Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)]
        [ValidateLength(2, [int]::MaxValue)]
        [string[]]
        $Text,

        [Parameter(ParameterSetName = "Company")]
        [switch]
        $ShowInactive,

        [Parameter(ParameterSetName = "Company")]
        [switch]
        $ShowLocked,

        [Parameter(ParameterSetName = "Employee-ApiNative")]
        [Parameter(ParameterSetName = "Ticket-ApiNative")]
        [int]
        $CompanyId,

        [Parameter(ParameterSetName = "Employee-UserFriendly", Mandatory = $true)]
        [Parameter(ParameterSetName = "Ticket-UserFriendly", Mandatory = $true)]
        [string]
        $CompanyName,

        [Parameter(ParameterSetName = "Employee-UserFriendly")]
        [Parameter(ParameterSetName = "Employee-ApiNative")]
        [ValidateSet("All", "Active", "Inactive")]
        [string]
        $Status = "All",

        [Parameter(ParameterSetName = "Employee-UserFriendly")]
        [Parameter(ParameterSetName = "Employee-ApiNative")]
        [bool]
        $GetCategories = $false,

        [Parameter(ParameterSetName = "Employee-UserFriendly")]
        [Parameter(ParameterSetName = "Employee-ApiNative")]
        [bool]
        $GetCallbacks = $false,

        [Parameter(ParameterSetName = "Ticket-UserFriendly")]
        [Parameter(ParameterSetName = "Ticket-ApiNative")]
        [int]
        $PreviewContentMaxChars,

        [Parameter(ParameterSetName = "Company")]
        [Parameter(ParameterSetName = "Employee-UserFriendly")]
        [Parameter(ParameterSetName = "Employee-ApiNative")]
        [Parameter(ParameterSetName = "Ticket-UserFriendly")]
        [Parameter(ParameterSetName = "Ticket-ApiNative")]
        [int]
        $ResultSize,

        [Parameter(ParameterSetName = "Company")]
        [Parameter(ParameterSetName = "Employee-UserFriendly")]
        [Parameter(ParameterSetName = "Employee-ApiNative")]
        [Parameter(ParameterSetName = "Ticket-UserFriendly")]
        [Parameter(ParameterSetName = "Ticket-ApiNative")]
        [TANSS.Connection]
        $Token
    )

    begin {
        if (-not $Token) { $Token = Get-TANSSRegisteredAccessToken }

        Assert-CacheRunspaceRunning

        $apiPath = Format-ApiPath -Path "api/v1/search"

        if ((-not $ResultSize) -or ($ResultSize -eq 0)) { $ResultSize = 100 }

        if ($Status) {
            switch ($Status) {
                "All" { $inactive = $true }
                "Active" { $inactive = $false }
                "Inactive" { $inactive = $true }
                Default {
                    Stop-PSFFunction -Message "Unhandeled Status. Developers mistake." -EnableException $true -Cmdlet $pscmdlet
                }
            }
        }

        if($MyInvocation.BoundParameters['CompanyName'] -and $CompanyName) {
            $CompanyId = ConvertFrom-NameCache -Name CompanyName -Type "Companies"
            if(-not $CompanyId) {
                Write-PSFMessage -Level Warning -Message "No Id for company '$($Company)' found. Ticket will be created with blank value on CompanyId"
            }
        }

    }

    process {
        $parameterSetName = $pscmdlet.ParameterSetName
        Write-PSFMessage -Level Debug -Message "ParameterNameSet: $($parameterSetName)"

        switch ($parameterSetName) {
            "Company" {
                foreach ($textItem in $Text) {
                    $body = @{
                        areas   = @("COMPANY")
                        query   = $textItem.replace("*", "")
                        configs = @{
                            company = @{
                                maxResults = $ResultSize
                            }
                        }
                    }

                    $response = Invoke-TANSSRequest -Type PUT -ApiPath $apiPath -Body $body -Token $Token

                    if ($response.content.companies) {
                        $countCompanyAll = ([array]($response.content.companies)).count
                        Write-PSFMessage -Level Verbose -Message "API response: $($response.meta.text) - $($countCompanyAll) records returned"

                        if (-not $ShowInactive) {
                            $countCompanyFiltered = ([array]($response.content.companies | Where-Object { $_.inactive -like "False" })).Count
                            Write-PSFMessage -Level Verbose -Message "Filtering companies marked as inactive - keeping $($countCompanyFiltered) of $($countCompanyAll) records"
                        } else {
                            $countCompanyFiltered = $countCompanyAll
                        }

                        if (-not $ShowLocked) {
                            $countCompanyFiltered = $countCompanyFiltered - ([array]($response.content.companies | Where-Object { $_.lockout -like "True" })).Count
                            Write-PSFMessage -Level Verbose -Message "Filtering companies marked as locked - keeping $($countCompanyFiltered) of $($countCompanyAll) records"
                        }

                        foreach ($companyItem in $response.content.companies) {
                            # Filtering
                            if(-not $ShowInactive) { if($companyItem.inactive -like "True") { continue } }
                            if(-not $ShowLocked) { if($companyItem.lockout -like "True") { continue } }

                            # Output data
                            [TANSS.Company]@{
                                BaseObject = $companyItem
                                Id         = $companyItem.id
                            }
                        }
                    } else {
                        Write-PSFMessage -Level Verbose -Message "API response: $($response.meta.text) - no records returned from global search"
                    }
                }
            }

            {$_ -like "Employee-ApiNative" -or $_ -like "Employee-UserFriendly"} {
                foreach ($textItem in $Text) {
                    $body = @{
                        areas   = @("EMPLOYEE")
                        query   = $textItem
                        configs = @{
                            employee = @{
                                maxResults = $ResultSize
                            }
                        }
                    }
                    if($CompanyId) { $body.configs.employee.Add("companyId", $CompanyId) }
                    if($Status) { $body.configs.employee.Add("inactive", $inactive) }
                    if("GetCategories" -in $PSCmdlet.MyInvocation.BoundParameters.Keys) { $body.configs.employee.Add("categories", $GetCategories) }
                    if("GetCallbacks" -in $PSCmdlet.MyInvocation.BoundParameters.Keys) { $body.configs.employee.Add("callbacks", $GetCallbacks) }

                    $response = Invoke-TANSSRequest -Type PUT -ApiPath $apiPath -Body $body -Token $Token

                    if ($response.content.employees) {
                        $countEmployeeAll = ([array]($response.content.employees)).count
                        Write-PSFMessage -Level Verbose -Message "API response: $($response.meta.text) - $($countEmployeeAll) records returned"

                        Push-DataToCacheRunspace -MetaData $response.meta

                        foreach ($employeeItem in $response.content.employees) {
                            # Output data
                            [TANSS.EmployeeSearched]@{
                                BaseObject = $employeeItem
                                Id         = $employeeItem.id
                            }
                        }
                    } else {
                        Write-PSFMessage -Level Verbose -Message "API response: $($response.meta.text) - no records returned from global search"
                    }
                }
            }

            {$_ -like "Ticket-ApiNative" -or $_ -like "Ticket-UserFriendly"} {
                foreach ($textItem in $Text) {
                    $body = @{
                        areas   = @("TICKET")
                        query   = $textItem
                        configs = @{
                            employee = @{
                                maxResults = $ResultSize
                            }
                        }
                    }
                    if($CompanyId) { $body.configs.employee.Add("companyId", $CompanyId) }
                    if($PreviewContentMaxChars) { $body.configs.employee.Add("previewContentMaxChars", $PreviewContentMaxChars) }

                    $response = Invoke-TANSSRequest -Type PUT -ApiPath $apiPath -Body $body -Token $Token

                    if ($response.content.Tickets) {
                        $countTicketsAll = ([array]($response.content.Tickets)).count
                        Write-PSFMessage -Level Verbose -Message "API response: $($response.meta.text) - $($countTicketsAll) records returned"

                        Push-DataToCacheRunspace -MetaData $response.meta

                        foreach ($ticketItem in $response.content.Tickets) {
                            # Output data
                            [TANSS.TicketPreview]@{
                                BaseObject = $ticketItem
                                Id         = $ticketItem.id
                            }
                        }
                    } else {
                        Write-PSFMessage -Level Verbose -Message "API response: $($response.meta.text) - no records returned from global search"
                    }
                }
            }

            Default {
                Stop-PSFFunction -Message "Unhandeled ParameterSetName. Developers mistake." -EnableException $true -Cmdlet $pscmdlet
            }
        }
    }

    end {}
}


function Get-TANSSDepartment {
    <#
    .Synopsis
        Get-TANSSDepartment
 
    .DESCRIPTION
        Get department from TANSS
 
    .PARAMETER Id
        Id of the department to get
 
    .PARAMETER Name
        Name of the department to get
 
    .PARAMETER IncludeEmployeeId
        Include assigned employees
 
    .PARAMETER Token
        The TANSS.Connection token to access api
 
        If not specified, the registered default token from within the module is going to be used
 
    .EXAMPLE
        PS C:\> Get-TANSSDepartment
 
        Get departments from TANSS
 
    .NOTES
        Author: Andreas Bellstedt
 
    .LINK
        https://github.com/AndiBellstedt/PSTANSS
    #>

    [CmdletBinding(
        DefaultParameterSetName = "All",
        SupportsShouldProcess = $false,
        PositionalBinding = $true,
        ConfirmImpact = 'Low'
    )]
    [OutputType([TANSS.Department])]
    Param(
        [Parameter(
            Mandatory = $true,
            ValueFromPipeline = $true,
            ValueFromPipelineByPropertyName = $true,
            ParameterSetName = "ById"
        )]
        [int[]]
        $Id,

        [Parameter(
            Mandatory = $true,
            ValueFromPipeline = $true,
            ValueFromPipelineByPropertyName = $true,
            ParameterSetName = "ByName"
        )]
        [string[]]
        $Name,

        [switch]
        $IncludeEmployeeId,

        [TANSS.Connection]
        $Token
    )

    begin {
        if (-not $Token) { $Token = Get-TANSSRegisteredAccessToken }

        Assert-CacheRunspaceRunning

        if ($IncludeEmployeeId) {
            Write-PSFMessage -Level Verbose -Message "IncludeEmployeeId switch is specified, going to ask for linked IDs" -Tag "Department", "IncludeEmployeeId"

            $deparmentsWithEmployeeId = Invoke-TANSSRequest -Type GET -ApiPath "api/v1/companies/departments?withEmployees=true" -Token $Token | Select-Object -ExpandProperty content

            $departments = foreach ($department in $departments) {
                [array]$_employeeIds = $deparmentsWithEmployeeId | Where-Object id -like $department.id | Select-Object -ExpandProperty employeeIds

                $department | Add-Member -MemberType NoteProperty -Name employeeIds -Value $_employeeIds

                $department
            }
            Remove-Variable -Name deparmentsWithEmployeeId, _employeeIds, deparment -Force -WhatIf:$false -Confirm:$false -Verbose:$false -Debug:$false -ErrorAction:Ignore -WarningAction:Ignore -InformationAction:Ignore
        } else {
            $departments = Invoke-TANSSRequest -Type GET -ApiPath "api/v1/employees/departments" -Token $Token | Select-Object -ExpandProperty content
        }

        [array]$filteredDepartments = @()
    }

    process {
        $parameterSetName = $pscmdlet.ParameterSetName
        Write-PSFMessage -Level Debug -Message "ParameterNameSet: $($parameterSetName)"

        switch ($parameterSetName) {
            "ById" {
                foreach ($item in $Id) {
                    $filteredDepartments += $departments | Where-Object id -eq $item
                }
            }

            "ByName" {
                foreach ($item in $Name) {
                    $filteredDepartments += $departments | Where-Object name -like $item
                }
            }

            "All" {
                $filteredDepartments = $departments
            }

            Default {
                Stop-PSFFunction -Message "Unhandled ParameterSet '$($parameterSetName)', developers mistake" -EnableException $true -Cmdlet $pscmdlet -Tag "Department", "SwitchException", "ParameterSet"
            }
        }
    }

    end {
        $filteredDepartments = $filteredDepartments | Sort-Object name, id -Unique
        Write-PSFMessage -Level Verbose -Message "Going to return $($filteredDepartments.count) departments" -Tag "Department", "Output"

        foreach ($department in $filteredDepartments) {
            Write-PSFMessage -Level System -Message "Working on department '$($department.name)' with id '$($department.id)'" -Tag "Department"

            # put id and name to cache lookups
            Update-CacheLookup -LookupName "Departments" -Id $department.Id -Name $department.Name

            # output result
            [TANSS.Department]@{
                Baseobject = $department
                Id = $department.id
            }
        }
    }
}


function Get-TANSSRegisteredAccessToken {
    <#
    .Synopsis
        Get-TANSSRegisteredAccessToken
 
    .DESCRIPTION
        Retrieve the registered LoginToken for default TANSS connection
 
    .EXAMPLE
        PS C:\> Get-TANSSRegisteredAccessToken
 
        Retrieve the registered LoginToken for TANSS
 
    .NOTES
        Author: Andreas Bellstedt
 
    .LINK
        https://github.com/AndiBellstedt/PSTANSS
    #>

    [CmdletBinding(
        SupportsShouldProcess = $false,
        ConfirmImpact = 'Low'
    )]
    Param(
    )

    begin {}

    process {
        Write-PSFMessage -Level Verbose -Message "Retrieving the registered LoginToken for '$($script:TANSSToken.UserName)' on '$($script:TANSSToken.Server)'" -Tag "AccessToken"
        $script:TANSSToken
    }

    end {}
}


function Invoke-TANSSRequest {
    <#
    .Synopsis
        Invoke-TANSSRequest
 
    .DESCRIPTION
        Invoke a API request to TANSS Server
 
    .PARAMETER Type
        Type of web request
 
    .PARAMETER ApiPath
        Uri path for the REST call in the API
 
    .PARAMETER QueryParameter
        A hashtable for all the parameters to the api route
 
    .PARAMETER AdditionalHeader
        Hashtable with additional values to put in the header of the request
 
    .PARAMETER Body
        The body as a hashtable for the request
 
    .PARAMETER Pdf
        if a PDF should be queried, this switch must be specified
 
    .PARAMETER BodyForceArray
        Tells the function always to invoke the data in the body as a JSON array formatted string
 
    .PARAMETER Token
        The TANSS.Connection token to access api
 
        If not specified, the registered default token from within the module is going to be used
 
    .PARAMETER WhatIf
        If this switch is enabled, no actions are performed but informational messages will be displayed that explain what would happen if the command were to run.
 
    .PARAMETER Confirm
        If this switch is enabled, you will be prompted for confirmation before executing any operations that change state.
 
    .EXAMPLE
        PS C:\> Invoke-TANSSRequest -Type GET -ApiPath "api/v1/something"
 
        Invoke a GET request to API with path api/v1/something by using the default registered token within the module
 
    .EXAMPLE
        PS C:\> Invoke-TANSSRequest -Type GET -ApiPath "api/v1/something" -Token $Token
 
        Invoke a GET request to API with path api/v1/something by using the explicit token from the variale $Token
 
    .NOTES
        Author: Andreas Bellstedt
 
    .LINK
        https://github.com/AndiBellstedt/PSTANSS
    #>

    [CmdletBinding(
        DefaultParameterSetName = 'Default',
        SupportsShouldProcess = $true,
        PositionalBinding = $true,
        ConfirmImpact = 'Medium'
    )]
    param (
        [Parameter(Mandatory = $true)]
        [ValidateSet("GET", "POST", "PUT", "DELETE")]
        [string]
        $Type,

        [Parameter(Mandatory = $true)]
        [string]
        $ApiPath,

        [hashtable]
        $QueryParameter,

        [hashtable[]]
        $Body,

        [hashtable]
        $AdditionalHeader,

        [switch]
        $Pdf,

        [switch]
        $BodyForceArray,

        [TANSS.Connection]
        $Token
    )

    begin {
    }

    process {
    }

    end {
        if (-not $Token) { $Token = Get-TANSSRegisteredAccessToken }
        Invoke-TANSSTokenCheck -Token $Token

        $ApiPath = Format-ApiPath -Path $ApiPath -QueryParameter $QueryParameter

        # Body
        if ($Body) {
            $bodyData = $Body | ConvertTo-Json -Compress
        } else {
            $bodyData = $null
        }
        if ($BodyForceArray -and (-not ([string]$bodyData).StartsWith("["))) {
            $bodyData = "[$($bodyData)]"
        }


        # Header
        $header = @{
            "apiToken" = [System.Runtime.InteropServices.Marshal]::PtrToStringAuto([System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($Token.AccessToken))
        }


        if ($Pdf) { $header.Add("Accept", "pdf") }


        if ($MyInvocation.BoundParameters['AdditionalHeader'] -and $AdditionalHeader) {
            foreach ($key in $AdditionalHeader.Keys) {
                $header.Add($key, $AdditionalHeader[$key])
            }
        }


        # Invoke request
        $param = [ordered]@{
            "Uri"           = "$($Token.Server)/$($ApiPath)"
            "Headers"       = $header
            "Body"          = $bodyData
            "Method"        = $Type
            "ContentType"   = 'application/json; charset=UTF-8'
            "Verbose"       = $false
            "Debug"         = $false
            "ErrorAction"   = "Stop"
            "ErrorVariable" = "invokeError"
        }

        if ($pscmdlet.ShouldProcess("$($Type) web REST call against URL '$($param.Uri)'", "Invoke")) {
            Write-PSFMessage -Level Verbose -Message "Invoke $($Type) web REST call against URL '$($param.Uri)'" -Tag "TANSSApiRequest" -Data @{ "body" = $bodyData; "Method" = $Type }

            try {
                $response = Invoke-RestMethod @param
                Write-PSFMessage -Level System -Message "API Response: $($response.meta.text)" -Tag "TANSSApiRequest", "SuccessfulRequest" -Data @{ "response" = $response }
            } catch {
                if ($invokeError[0].Message.StartsWith("{")) {
                    $response = $invokeError[0].Message | ConvertFrom-Json -ErrorAction SilentlyContinue
                }

                if ($response) {
                    Write-PSFMessage -Level Error -Message "$($response.Error.text) - $($response.Error.localizedText)" -Exception $response.Error.type -Tag "TANSSApiRequest", "FailedRequest", "REST call $($Type)" -Data @{ "Message" = $invokeError[0].Message } -PSCmdlet $pscmdlet -ErrorRecord $invokeError[0].ErrorRecord
                } else {
                    Write-PSFMessage -Level Error -Message "$($invokeError[0].Source) ($($invokeError[0].HResult)): $($invokeError[0].Message)" -Exception $invokeError[0].InnerException -Tag "TANSSApiRequest", "FailedRequest", "REST call $($Type)" -ErrorRecord $invokeError[0].ErrorRecord  -PSCmdlet $pscmdlet -Data @{ "Message" = $invokeError[0].Message }
                }

                return
            }

            # Output
            $response
        }
    }
}

function New-TANSSServiceToken {
    <#
    .Synopsis
        Create a new (user unspecific) api service access token object
 
    .DESCRIPTION
        Create a new api service access token object.
        Apart from the regular user login, there are aspects and api routes, that are
        only available via explicit service token.
 
        This function allows you to create a service token to give to other functions,
        that require such a token.
 
    .PARAMETER Server
        Name of the service the token is generated from
 
    .PARAMETER ServiceToken
        A API token generated within Tanss to access specific TANSS modules explicit via
        API service as a non-employee-account.
 
        For security reaons, the parameter only accept secure strings.
        Please avoid plain-text for sensitive informations!
        To generate secure strings use:
        $ServiceTokenSecureString = Read-Host -AsSecureString
 
    .PARAMETER Protocol
        Specifies if the service connection is done with http or https
 
    .PARAMETER WhatIf
        If this switch is enabled, no actions are performed but informational messages will be displayed that explain what would happen if the command were to run.
 
    .PARAMETER Confirm
        If this switch is enabled, you will be prompted for confirmation before executing any operations that change state.
 
    .EXAMPLE
        PS C:\> $tanssServiceToken = New-TANSSServiceToken -Server "tanss.corp.company.com" -ServiceToken $ServiceTokenSecureString
 
        Outputs a ServiceToken as a TANSS.Connection object for "tanss.corp.company.com" with the api key from the variable $ServiceTokenSecureString
 
        API variable $ServiceTokenSecureString hast to be a securestring.
        ($ServiceTokenSecureString = Read-Host -AsSecureString)
 
    .NOTES
        Author: Andreas Bellstedt
 
    .LINK
        https://github.com/AndiBellstedt/PSTANSS
    #>

    [CmdletBinding(
        DefaultParameterSetName = "Default",
        SupportsShouldProcess = $true,
        PositionalBinding = $true,
        ConfirmImpact = 'Medium'
    )]
    [OutputType([TANSS.Connection])]
    Param(
        [Parameter(
            Mandatory = $true,
            ValueFromPipeline = $true,
            ValueFromPipelineByPropertyName = $true
        )]
        [Alias("ComputerName", "Hostname", "Host", "ServerName")]
        [ValidateNotNull()]
        [String]
        $Server,

        [Parameter(
            Mandatory = $true,
            ValueFromPipeline = $true
        )]
        [Alias("ApiKey", "Password", "AccessToken", "Token")]
        [securestring]
        $ServiceToken,

        [ValidateSet("HTTP", "HTTPS")]
        [ValidateNotNullOrEmpty()]
        [String]
        $Protocol = "HTTPS"
    )

    begin {
    }

    process {
        $parameterSetName = $pscmdlet.ParameterSetName
        Write-PSFMessage -Level Debug -Message "ParameterNameSet: $($parameterSetName)"

        # Ensure Prefix
        if ($protocol -eq 'HTTP') {
            Write-PSFMessage -Level Important -Message "Unsecure $($protocol) connection with possible security risk detected. Please consider switch to HTTPS!" -Tag "ServiceToken"
            $prefix = 'http://'
        } else {
            Write-PSFMessage -Level System -Message "Using secure $($protocol) connection." -Tag "ServiceToken"
            $prefix = 'https://'
        }

        # Validate Server Parameter to avoid accidentally input bearer token information in $Server
        try {
            $null = ConvertFrom-JWTtoken -TokenText $Server
            $serverIsTokenObject = $true
        } catch {
            $serverIsTokenObject = $false
        }
        if (($Server.StartsWith("Bearer")) -or ($Server.Length -gt 256) -or ($serverIsTokenObject)) {
            if ($Server.Length -gt 10) {
                $textlength = $Server.Length / 2
            } elseif ($Server.Length -gt 5) {
                $textlength = 4
            } else {
                $textlength = 2
            }
            Stop-PSFFunction -Message "The specified Server '$($Server.Substring(0, $textlength))****' looks like a service token. ServiceToken has to be piped in as a SecureString or has to be specified via parameter '-ServiceToken'. For security reason, please don't use plaintext for sensitive information." -EnableException $true -Cmdlet $pscmdlet -Tag "ServiceToken"
        }

        # Read JWT from service token
        $TokenText = [System.Runtime.InteropServices.Marshal]::PtrToStringAuto([System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($ServiceToken))
        if ($TokenText.StartsWith("Bearer")) {
            Write-PSFMessage -Level System -Message "Found Bearer token information. Going to extract JWT" -Tag "ServiceToken"
            $TokenText = $TokenText.split(" ")[-1]
        }
        Write-PSFMessage -Level Verbose -Message "Reading JWT information from serviceToken" -Tag "ServiceToken"
        $tokenInfo = ConvertFrom-JWTtoken -TokenText $TokenText

        # Create ServiceToken
        if (($tokenInfo.typ -like "JWT") -and $Server -and $prefix) {
            if ($pscmdlet.ShouldProcess("Service token for '$($UserName)'", "New")) {
                Write-PSFMessage -Level System -Message "Creating TANSS.Connection with service token" -Tag "ServiceToken"

                $serviceTokenObject = [TANSS.Connection]@{
                    Server            = "$($Prefix)$($Server)"
                    UserName          = $tokenInfo.sub
                    EmployeeId        = 0
                    EmployeeType      = "ServiceAccessToken"
                    AccessToken       = $ServiceToken
                    RefreshToken      = $null
                    Message           = "Explizit specified API token. May not work with all functions!"
                    TimeStampCreated  = (Get-Date)
                    TimeStampExpires  = $tokenInfo.exp
                    TimeStampModified = (Get-Date)
                }

                Invoke-TANSSTokenCheck -Token $serviceTokenObject -NoRefresh

                # output result
                $serviceTokenObject
            }
        } else {
            Write-PSFMessage -Level Important -Message "Unable to create TANSS ServiceToken object with specified ServiceToken '$($TokenText.Substring(0,10))*****'" -Tag "ServiceToken"
        }
    }

    end {}
}


function Register-TANSSAccessToken {
    <#
    .Synopsis
        Register-TANSSAccessToken
 
    .DESCRIPTION
        Register the AccessToken as default connection setting for TANSS
 
    .PARAMETER Token
        AccessToken object to register as default connection for TANSS
 
    .PARAMETER WhatIf
        If this switch is enabled, no actions are performed but informational messages will be displayed that explain what would happen if the command were to run.
 
    .PARAMETER Confirm
        If this switch is enabled, you will be prompted for confirmation before executing any operations that change state.
 
    .EXAMPLE
        PS C:\> Register-TANSSAccessToken -Token $Token
 
        Register the LoginToken from variable $Token as a default connection for TANSS
 
    .NOTES
        Author: Andreas Bellstedt
 
    .LINK
        https://github.com/AndiBellstedt/PSTANSS
    #>

    [CmdletBinding(
        SupportsShouldProcess = $true,
        ConfirmImpact = 'Medium'
    )]
    Param(
        [Parameter(Mandatory = $true)]
        [TANSS.Connection]
        $Token
    )

    begin {}

    process {

        if ($pscmdlet.ShouldProcess("AccessToken for $($Token.UserName) on '$($Token.Server)'", "Register")) {
            Write-PSFMessage -Level Verbose -Message "Registering AccessToken for $($Token.UserName) on '$($Token.Server)' valid until '$($Token.TimeStampExpires)'" -Tag "AccessToken"

            $script:TANSSToken = $Token
        }
    }

    end {}
}


function Update-TANSSAccessToken {
    <#
    .Synopsis
        Update-TANSSAccessToken
 
    .DESCRIPTION
        Updates the AccessToken from a refreshToken for TANSS connection
        By defaault, the new Access is registered to as default connection
 
    .PARAMETER NoCacheRefresh
        Do not requery tickets and various types to fill cache data for lookup types
 
    .PARAMETER DoNotRegisterConnection
        Do not register the connection as default connection
 
    .PARAMETER Token
        The TANSS.Connection token to access api
 
        If not specified, the registered default token from within the module is going to be used
 
    .PARAMETER PassThru
        Outputs the new token to the console
 
    .PARAMETER WhatIf
        If this switch is enabled, no actions are performed but informational messages will be displayed that explain what would happen if the command were to run.
 
    .PARAMETER Confirm
        If this switch is enabled, you will be prompted for confirmation before executing any operations that change state.
 
    .EXAMPLE
        PS C:\> Update-TANSSAccessToken
 
        Updates the AccessToken from the default connection and register it as new
        AccessToken on default Connection
 
    .NOTES
        Author: Andreas Bellstedt
 
    .LINK
        https://github.com/AndiBellstedt/PSTANSS
    #>

    [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidUsingConvertToSecureStringWithPlainText", "")]
    [CmdletBinding(
        SupportsShouldProcess = $true,
        PositionalBinding = $true,
        ConfirmImpact = 'Medium'
    )]
    Param(
        [TANSS.Connection]
        $Token,

        [Alias('NoRegistration')]
        [Switch]
        $DoNotRegisterConnection,

        [switch]
        $NoCacheRefresh,

        [switch]
        $PassThru
    )

    begin {
        if (-not $Token) { $Token = Get-TANSSRegisteredAccessToken }
        Assert-CacheRunspaceRunning
    }

    process {
        if ($Token.RefreshToken) {
            $refreshTokenInfo = ConvertFrom-JWTtoken -TokenText ([System.Runtime.InteropServices.Marshal]::PtrToStringAuto([System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($Token.RefreshToken))).split(" ")[1]
        } else {
            Stop-PSFFunction -Message "Invalid Token specified. No refreshToken found" -Tag "Connection", "Authentication" -EnableException $true -Cmdlet $pscmdlet
        }

        Write-PSFMessage -Level Verbose -Message "Checking RefreshToken from TANSS.Connection of $($Token.UserName) on '$($Token.Server)'" -Tag "AccessToken", "Connection", "Authentication"
        if ( (Get-Date) -ge $refreshTokenInfo.exp ) {
            Stop-PSFFunction -Message "RefreshToken expired. Unable to refresh with current token. Please use Connect-TANSS to login again" -Tag "Connection", "Authentication"
            return
        }

        if ($pscmdlet.ShouldProcess("AccessToken from TANSS.Connection of $($Token.UserName) on '$($Token.Server)' with RefreshToken valid until '$($refreshTokenInfo.exp)'", "Update")) {
            $apiPath = Format-ApiPath -Path "api/v1/tickets/own"

            Write-PSFMessage -Level Verbose -Message "Updating AccessToken from TANSS.Connection of $($Token.UserName) on '$($Token.Server)' with RefreshToken valid until '$($refreshTokenInfo.exp)'" -Tag "AccessToken"
            $param = @{
                "Uri"           = "$($Token.Server)/$($ApiPath)"
                "Headers"       = @{
                    "refreshToken" = [System.Runtime.InteropServices.Marshal]::PtrToStringAuto([System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($Token.RefreshToken))
                }
                "Verbose"       = $false
                "Debug"         = $false
                "ErrorAction"   = "Stop"
                "ErrorVariable" = "invokeError"
            }
            try {
                $response = Invoke-RestMethod @param
            } catch {
                Stop-PSFFunction -Message "Error invoking rest call on service '$($Token.Server)'. $($invokeError)" -Tag "Connection", "Authentication" -EnableException $true -Cmdlet $pscmdlet
            }

            if ($response.meta.text -notlike "Welcome, your ApiToken is 4 hours valid.") {
                Stop-PSFFunction -Message "$($response.meta.text) to service '$($Token.Server)'. Apperantly, refreshToken is not valid" -Tag "Connection", "Authentication" -EnableException $true -Cmdlet $pscmdlet
            }

            if (-not $response.content.apiKey) {
                Stop-PSFFunction -Message "Something went wrong on authenticating user $($Token.UserName). No apiKey found in response. Unable to refresh token from connection '$($Token.Server)'" -Tag "Connection", "Authentication" -EnableException $true -Cmdlet $pscmdlet
            }

            Write-PSFMessage -Level System -Message "Creating TANSS.Connection from refreshed AccessToken" -Tag "Connection"
            $token = [TANSS.Connection]@{
                Server            = $Token.Server
                UserName          = $Token.UserName
                EmployeeId        = $response.content.employeeId
                EmployeeType      = $response.content.employeeType
                AccessToken       = ($response.content.apiKey | ConvertTo-SecureString -AsPlainText -Force)
                RefreshToken      = ($response.content.refresh | ConvertTo-SecureString -AsPlainText -Force)
                Message           = $response.meta.text
                TimeStampCreated  = $Token.TimeStampCreated
                TimeStampExpires  = [datetime]::new(1970, 1, 1, 0, 0, 0, 0, [DateTimeKind]::Utc).AddSeconds($response.content.expire).ToLocalTime()
                TimeStampModified = Get-Date
            }

            if (-not $NoCacheRefresh) { Invoke-CacheRefresh -Token $token }

            if (-not $DoNotRegisterConnection) {
                # Make the connection the default connection for further commands
                Write-PSFMessage -Level Significant -Message "Updating AccessToken for service '($($token.Server))' as '$($token.UserName)' and register it as default connection" -Tag "Connection"

                Register-TANSSAccessToken -Token $token

                if ($PassThru) {
                    Write-PSFMessage -Level System -Message "Outputting TANSS.Connection object" -Tag "Connection"
                    $token
                }
            } else {
                Write-PSFMessage -Level Significant -Message "Updating AccessToken for service '($($token.Server))' as '$($token.UserName)'" -Tag "Connection"

                $token
            }
        }
    }

    end {}
}


function Get-TANSSEmployee {
    <#
    .Synopsis
        Get-TANSSEmployee
 
    .DESCRIPTION
        Get employees out of TANSS service.
 
        You can pipe in IDs o employee objects to get refreshed data out of the service
 
        You can also pipe in company objects to receive employees of that company
 
    .PARAMETER EmployeeId
        The ID of the employee to get from TANSS service
 
    .PARAMETER Employee
        A TANSS.Employee object to query again from the service
 
    .PARAMETER CompanyId
        The ID of the company to get employees from
 
    .PARAMETER Company
        A passed in TANSS.Company object to query employees from
 
    .PARAMETER CompanyName
        The name of the company to query employees from
 
    .PARAMETER Token
        The TANSS.Connection token to access api
 
        If not specified, the registered default token from within the module is going to be used
 
    .EXAMPLE
        PS C:\> Get-TANSSEmployee -EmployeeId 2
 
        Get the employee with ID 2 (usually the first employee created in TANSS)
 
    .EXAMPLE
        PS C:\> $employee | Get-TANSSEmployee
 
        Query the employee from variable $employee again
 
    .EXAMPLE
        PS C:\> Get-TANSSEmployee -CompanyId 100000
 
        Get all employees from company ID 100000 (your own company)
 
    .EXAMPLE
        PS C:\> $company | Get-TANSSEmployee
 
        Get all employees from company $company
 
    .EXAMPLE
        PS C:\> Get-TANSSEmployee -CompanyName "Company X"
 
        Get all employees from "Company X"
 
    .NOTES
        Author: Andreas Bellstedt
 
    .LINK
        https://github.com/AndiBellstedt/PSTANSS
    #>

    [CmdletBinding(
        DefaultParameterSetName = "Employee_ApiNative",
        SupportsShouldProcess = $false,
        PositionalBinding = $true,
        ConfirmImpact = 'Low'
    )]
    [OutputType([TANSS.Employee])]
    Param(
        [Parameter(
            ParameterSetName = "Employee_ApiNative",
            ValueFromPipelineByPropertyName = $true,
            ValueFromPipeline = $true,
            Mandatory = $true
        )]
        [Alias("Id")]
        [int[]]
        $EmployeeId,

        [Parameter(
            ParameterSetName = "Employee_UserFriendly",
            ValueFromPipelineByPropertyName = $true,
            ValueFromPipeline = $true,
            Mandatory = $true
        )]
        [tanss.employee]
        $Employee,

        [Parameter(
            ParameterSetName = "Company_ApiNative",
            ValueFromPipelineByPropertyName = $true,
            Mandatory = $true
        )]
        [int[]]
        $CompanyId,

        [Parameter(
            ParameterSetName = "Company_UserFriendly",
            ValueFromPipelineByPropertyName = $true,
            ValueFromPipeline = $true,
            Mandatory = $true
        )]
        [tanss.company]
        $Company,

        [Parameter(
            ParameterSetName = "Company_UserFriendlyByName",
            ValueFromPipelineByPropertyName = $true,
            ValueFromPipeline = $true,
            Mandatory = $true
        )]
        [string[]]
        $CompanyName,

        [TANSS.Connection]
        $Token
    )

    begin {
        if (-not $Token) { $Token = Get-TANSSRegisteredAccessToken }
        Assert-CacheRunspaceRunning
    }

    process {
        $parameterSetName = $pscmdlet.ParameterSetName
        Write-PSFMessage -Level Debug -Message "ParameterNameSet: $($parameterSetName)"

        # If objects are piped in -> collect IDs
        switch ($parameterSetName) {
            "Employee_UserFriendly" { $EmployeeId = $Employee.Id }
            "Company_UserFriendly" { $CompanyId = $Company.Id }
            "Company_UserFriendlyByName" {
                $CompanyId = foreach ($_name in $CompanyName) {
                    Find-TANSSObject -Company -Text $_name -Token $Token -ShowLocked | Where-Object name -like $_name | Select-Object -ExpandProperty Id
                }
            }
        }

        switch ($parameterSetName) {
            { $_ -like "Employee_*" } {
                Write-PSFMessage -Level System -Message "Query personal employee record. $(([array]$EmployeeId).count) record(s)"

                foreach ($id in $EmployeeId) {
                    Write-PSFMessage -Level Verbose -Message "Working on employee id $($id)"

                    $response = Invoke-TANSSRequest -Type GET -ApiPath "api/v1/employees/$($id)" -Token $Token

                    if ($response.meta.text -like "Object found") {

                        # Cache refresh
                        Push-DataToCacheRunspace -MetaData $response.meta

                        Write-PSFMessage -Level Verbose -Message "Output employee $($response.content.name)"
                        $output = [TANSS.Employee]@{
                            BaseObject = $response.content
                            Id         = $response.content.id
                        }

                        # ToDo: Filtering - add parameters (Isactive, FilterName, )

                        # Output result
                        $output

                    } else {
                        Write-PSFMessage -Level Error -Message "API returned no data"
                    }

                }
            }

            { $_ -like "Company_*" } {
                Write-PSFMessage -Level System -Message "Query corporate employee record. $(([array]$CompanyId).count) record(s)"

                foreach ($id in $CompanyId) {
                    Write-PSFMessage -Level Verbose -Message "Query employee(s) for company id $($id)"

                    $response = Invoke-TANSSRequest -Type GET -ApiPath "api/v1/companies/$($id)/employees" -Token $Token

                    if ($response.meta.text -like "Object found") {

                        # Cache refresh
                        Push-DataToCacheRunspace -MetaData $response.meta

                        foreach ($responseItem in $response.content) {

                            Write-PSFMessage -Level Verbose -Message "Output corporate employee $($responseItem.name)"
                            $output = [TANSS.Employee]@{
                                BaseObject = $responseItem
                                Id         = $responseItem.id
                            }

                            # ToDo: Filtering - add parameters (Isactive, FilterName, )

                            # Output result
                            $output

                        }

                    } else {
                        Write-PSFMessage -Level Error -Message "API returned no data"
                    }
                }
            }

            Default {
                Stop-PSFFunction -Message "Unhandeled ParameterSetName. Developers mistake." -EnableException $true -Cmdlet $pscmdlet
            }
        }
    }

    end {}
}


function Get-TANSSTechnician {
    <#
    .Synopsis
        Get-TANSSTechnician
 
    .DESCRIPTION
        Gets all technicians of this system from default TANSS connection
 
    .PARAMETER Id
        ID of the technician to get
        (client side filtering)
 
    .PARAMETER Name
        Name of the technician to get
        (client side filtering)
 
    .PARAMETER FreelancerCompanyId
        If this parameter is given, also fetches the freelancers of this company.
        By default all users with a license are treated as "TANSS technicians".
 
    .PARAMETER ExcludeRestrictedLicenseUser
        Do not show account/ users / technicians with limited licenses
 
    .PARAMETER Token
        The TANSS.Connection token to access api
 
        If not specified, the registered default token from within the module is going to be used
 
    .EXAMPLE
        PS C:\> Get-TANSSTechnician
 
        Gets all technicians of this system
 
    .NOTES
        Author: Andreas Bellstedt
 
    .LINK
        https://github.com/AndiBellstedt/PSTANSS
    #>

    [CmdletBinding(
        SupportsShouldProcess = $false,
        ConfirmImpact = 'Low'
    )]
    [OutputType([TANSS.Employee])]
    Param(
        [String[]]
        $Name,

        [int[]]
        $Id,

        [int[]]
        $FreelancerCompanyId,

        [switch]
        $ExcludeRestrictedLicenseUser,

        [TANSS.Connection]
        $Token
    )

    begin {
        if (-not $Token) { $Token = Get-TANSSRegisteredAccessToken }
        $apiPath = Format-ApiPath -Path "api/v1/employees/technicians"
        Assert-CacheRunspaceRunning
        if (-not $FreelancerCompanyId) { $FreelancerCompanyId = 0 }
    }

    process {
        $response = @()

        $response += foreach ($companyId in $FreelancerCompanyId) {
            $queryParameter = @{}

            if($MyInvocation.BoundParameters['ExcludeRestrictedLicenseUser'] -and $ExcludeRestrictedLicenseUser) {
                $queryParameter.Add("restrictedLicenses", $false)
            } else {
                $queryParameter.Add("restrictedLicenses", $true)
            }

            if ($companyId -ne 0) {
                Write-PSFMessage -Level System -Message "FreelancerCompanyId specified, compiling body to query freelancers of company '$($companyId)'" -Tag "Technician", "Freelancer"
                $queryParameter.Add("FreelancerCompanyId", $companyId)
            }

            $invokeParam = @{
                "Type"    = "GET"
                "ApiPath" = (Format-ApiPath -Path $apiPath -QueryParameter $queryParameter)
                "Token"   = $Token
            }

            Invoke-TANSSRequest @invokeParam

            Remove-Variable -Name queryParameter, invokeParam -Force -WhatIf:$false -Confirm:$false -Verbose:$false -Debug:$false -ErrorAction Ignore -WarningAction Ignore -InformationAction Ignore
        }


        if ($response) {
            Write-PSFMessage -Level Verbose -Message "Found $(($response.content).count) technicians" -Tag "Technician"

            foreach ($responseItem in $response) {

                # Output result
                foreach ($technician in $responseItem.content) {

                    # Do filtering on name
                    if ($MyInvocation.BoundParameters['Name'] -and $Name) {
                        $filterSuccess = $false
                        foreach ($filterName in $Name) {
                            if ($technician.Name -like $filterName) {
                                $filterSuccess = $true
                            }
                        }

                        # if filter does not hit, continue with next technician
                        if ($filterSuccess -eq $false) { continue }
                    }


                    # Do filtering on id
                    if ($MyInvocation.BoundParameters['Id'] -and $Id) {
                        $filterSuccess = $false
                        foreach ($filterId in $Id) {
                            if ([int]($technician.id) -eq $filterId) {
                                $filterSuccess = $true
                            }
                        }

                        # if filter does not hit, continue with next technician
                        if ($filterSuccess -eq $false) { continue }
                    }


                    # Query details
                    Write-PSFMessage -Level Verbose -Message "Getting details of '$($technician.name)' (Id $($technician.id))" -Tag "Technician"

                    $invokeParam = @{
                        "Type"    = "GET"
                        "ApiPath" = (Format-ApiPath -Path "api/v1/employees/$($technician.id)")
                        "Token"   = $Token
                    }

                    $employeeResponse = Invoke-TANSSRequest @invokeParam

                    if ($employeeResponse) {
                        Push-DataToCacheRunspace -MetaData $employeeResponse.meta

                        foreach ($employeeItem in $employeeResponse.content) {
                            Write-PSFMessage -Level Debug -Message "Found '$($employeeItem.Name)' with id $($employeeItem.id)"

                            # Output data
                            [TANSS.Employee]@{
                                BaseObject = $employeeItem
                                Id         = $employeeItem.id
                            }
                        }
                    } else {
                        Stop-PSFFunction -Message "Unexpected error searching '$($employeeResponse.content.name)' with ID '$($technician.id)'. TANSS is unable to find details of employee" -EnableException $true -Cmdlet $pscmdlet
                    }
                }
            }
        } else {
            Write-PSFMessage -Level Warning -Message "No technicians found." -Tag "Technician"
        }
    }

    end {
    }
}


function New-TANSSEmployee {
    <#
    .Synopsis
        New-TANSSEmployee
 
    .DESCRIPTION
        Create a new employee in TANSS
 
    .PARAMETER Token
        The TANSS.Connection token to access api
 
        If not specified, the registered default token from within the module is going to be used
 
    .PARAMETER WhatIf
        If this switch is enabled, no actions are performed but informational messages will be displayed that explain what would happen if the command were to run.
 
    .PARAMETER Confirm
        If this switch is enabled, you will be prompted for confirmation before executing any operations that change state.
 
    .EXAMPLE
        New-TANSSEmployee -Name "User, Test"
 
        Create a new employee (as technician) in your own company
 
    .NOTES
        Author: Andreas Bellstedt
 
    .LINK
        https://github.com/AndiBellstedt/PSTANSS
    #>

    [CmdletBinding(
        DefaultParameterSetName = "Userfriendly",
        SupportsShouldProcess = $true,
        PositionalBinding = $true,
        ConfirmImpact = 'Medium'
    )]
    [OutputType([TANSS.Employee])]
    param (
        # Fullname of the employee
        [Parameter(
            ParameterSetName = "ApiNative",
            Mandatory = $true,
            ValueFromPipeline = $true,
            ValueFromPipelineByPropertyName = $true
        )]
        [Parameter(
            ParameterSetName = "Userfriendly",
            Mandatory = $true,
            ValueFromPipeline = $true,
            ValueFromPipelineByPropertyName = $true
        )]
        [Alias("Fullname", "Displayname")]
        [string]
        $Name,

        # first name of the employee
        [Parameter(ParameterSetName = "ApiNative")]
        [Parameter(ParameterSetName = "Userfriendly")]
        [Alias("Givenname")]
        [string]
        $FirstName,

        # Last name of the employee
        [Parameter(ParameterSetName = "ApiNative")]
        [Parameter(ParameterSetName = "Userfriendly")]
        [Alias("Surname")]
        [string]
        $LastName,


        # Id of the salutation for this employee
        [Parameter(ParameterSetName = "ApiNative")]
        [Alias("IdSalutation")]
        [Int]
        $SalutationId = 0,


        # Id of the department which tis employee is assigned to
        [Parameter(ParameterSetName = "ApiNative")]
        [Alias("IdDepartment")]
        [Int]
        $DepartmentId,


        # Name of the department which tis employee is assigned to
        [Parameter(ParameterSetName = "Userfriendly")]
        [string]
        $Department,

        # location / room of the employee
        [Parameter(ParameterSetName = "ApiNative")]
        [Parameter(ParameterSetName = "Userfriendly")]
        [Alias("Location")]
        [string]
        $Room,

        # Main telephone number
        [Parameter(ParameterSetName = "ApiNative")]
        [Parameter(ParameterSetName = "Userfriendly")]
        [Alias("TelephoneNumber")]
        [string]
        $Phone,

        # e-Mail address
        [Parameter(ParameterSetName = "ApiNative")]
        [Parameter(ParameterSetName = "Userfriendly")]
        [Alias("EmailAddress")]
        [string]
        $Email,

        # if this employee is assigned to a specific car, the id goes here
        [Parameter(ParameterSetName = "ApiNative")]
        [Alias("IdCar")]
        $CarId = 0,

        # mobile telephone number
        [Parameter(ParameterSetName = "ApiNative")]
        [Parameter(ParameterSetName = "Userfriendly")]
        [Alias("MobilePhone")]
        [string]
        $Mobile,

        # initials for this employee
        [Parameter(ParameterSetName = "ApiNative")]
        [Parameter(ParameterSetName = "Userfriendly")]
        [string]
        $Initials,

        # Working hour model for this employee
        [Parameter(ParameterSetName = "ApiNative")]
        [Alias("IdWorkingHourModel")]
        [int]
        $WorkingHourModelId = 0,

        # Id of the accounting type for this employee
        [Parameter(ParameterSetName = "ApiNative")]
        [Alias("IdAccountingType")]
        [int]
        $accountingTypeId = 0,

        # Private telephone number
        [Parameter(ParameterSetName = "ApiNative")]
        [Parameter(ParameterSetName = "Userfriendly")]
        [Alias("PrivatePhoneNumber")]
        [string]
        $PrivateNumber,

        # True if the employee is active
        [Parameter(ParameterSetName = "ApiNative")]
        [Parameter(ParameterSetName = "Userfriendly")]
        [Alias("Active")]
        [bool]
        $IsActive = $true,

        # Identification number in foreign ERP system
        [Parameter(ParameterSetName = "ApiNative")]
        [Parameter(ParameterSetName = "Userfriendly")]
        [string]
        $ERPNumber,

        # Fax number
        [Parameter(ParameterSetName = "ApiNative")]
        [Parameter(ParameterSetName = "Userfriendly")]
        [Alias("PersonalFaxNumber")]
        [string]
        $Fax,

        # Role for this employee
        [Parameter(ParameterSetName = "ApiNative")]
        [Parameter(ParameterSetName = "Userfriendly")]
        [string]
        $Role,

        # if the employee uses a title, the id goes here
        [Parameter(ParameterSetName = "ApiNative")]
        [Alias("IdTitle")]
        [int]
        $TitleId = 0,

        # Language which this employee uses
        [Parameter(ParameterSetName = "ApiNative")]
        [Parameter(ParameterSetName = "Userfriendly")]
        [string]
        $Language,

        # Second telephone number
        [Parameter(ParameterSetName = "ApiNative")]
        [Parameter(ParameterSetName = "Userfriendly")]
        [Alias("TelephoneNumberTwo")]
        [string]
        $Phone2,

        # second mobile telephone number
        [Parameter(ParameterSetName = "ApiNative")]
        [Parameter(ParameterSetName = "Userfriendly")]
        [Alias("MobileNumberTwo")]
        [string]
        $Mobile2,


        # Employee birthday date
        [Parameter(ParameterSetName = "ApiNative")]
        [Parameter(ParameterSetName = "Userfriendly")]
        [Alias("BirthdayDate", "DateBirthday", "DateOfBirth")]
        [datetime]
        $Birthday,

        # Company id of the ticket. Name is stored in the "linked entities" - "companies". Can only be set if the user has access to the company
        [Parameter(ParameterSetName = "ApiNative")]
        [Alias("CompanyAssignments", "IdCompany")]
        [int[]]
        $CompanyId,

        # Company name where the ticket should create for. Can only be set if the user has access to the company
        [Parameter(ParameterSetName = "Userfriendly")]
        [Alias("Company")]
        [String[]]
        $CompanyName,

        [Parameter(ParameterSetName = "ApiNative")]
        [Parameter(ParameterSetName = "Userfriendly")]
        [TANSS.Connection]
        $Token
    )

    begin {
        if (-not $Token) { $Token = Get-TANSSRegisteredAccessToken }
        Assert-CacheRunspaceRunning
        $apiPath = Format-ApiPath -Path "api/v1/employees"

        if ($Department) {
            $DepartmentId = ConvertFrom-NameCache -Name $Department -Type "Departments"
            if (-not $DepartmentId) {
                Write-PSFMessage -Level Warning -Message "No Id for department '$($Department)' found. Employee will be created with blank value on departmentId"
                #todo implement API call for departments
                $DepartmentId = 0
            }
        }

        if ($CompanyName) {
            $CompanyId = foreach ($companyItem in $CompanyName) {
                $_id = ConvertFrom-NameCache -Name $companyItem -Type Companies
                if (-not $_id) {
                    Write-PSFMessage -Level Warning -Message "No Id for company '$($companyItem)' found. Employee will be created without assignment to this company"
                } else {
                    $_id
                }
            }
            Remove-Variable -Name _id -Force -Confirm:$false -WhatIf:$false -Verbose:$false -Debug:$false -ErrorAction Ignore -WarningAction Ignore
        }

        if (-not $CompanyId) {
            $_name = ConvertFrom-NameCache -Id 100000 -Type "Companies"
            Write-PSFMessage -Level Important -Message "No company specified. Employee will be created within your own company $(if($_name) { "($($_name)) "})as a technician"

            $CompanyId = 100000

            Remove-Variable -Name _name -Force -Confirm:$false -WhatIf:$false -Verbose:$false -Debug:$false -ErrorAction Ignore -WarningAction Ignore
        }

    }

    process {
        $parameterSetName = $pscmdlet.ParameterSetName
        Write-PSFMessage -Level Debug -Message "ParameterNameSet: $($parameterSetName)"

        if ($parameterSetName -like "Userfriendly" -and (-not $Name)) {
            Write-PSFMessage -Level Error -Message "No name specified"
            continue
        }

        #region rest call prepare
        if ($Birthday) {
            $_birthday = Get-Date -Date $Birthday -Format "yyyy-MM-dd"
        } else {
            $_birthday = "0000-00-00"
        }

        [array]$_companies = foreach ($CompanyIdItem in $CompanyId) {
            @{
                "companyId" = $CompanyIdItem
            }
        }

        if (-not $LastName) {
            $nameParts = $Name.split(",").Trim()
            if ($nameParts.Count -eq 2) {
                # Assuming schema "<Lastname>, <Firstname>"
                $LastName = $nameParts[0]
            } elseif ($nameParts.Count -eq 1) {
                $nameParts = $Name.split(" ").Trim()
                if ($nameParts.Count -eq 2) {
                    # Assuming schema "<Firstname> <Lastname>"
                    $LastName = $nameParts[1]
                } else {
                    $LastName = $Name
                }
            } else {
                $LastName = $Name
            }
        }
        Remove-Variable -Name nameParts -Force -Confirm:$false -WhatIf:$false -Verbose:$false -Debug:$false -ErrorAction Ignore -WarningAction Ignore

        if (-not $FirstName) {
            $nameParts = $Name.split(",").Trim()
            if ($nameParts.Count -eq 2) {
                # Assuming schema "<Lastname>, <Firstname>"
                $FirstName = $nameParts[1]
            } elseif ($nameParts.Count -eq 1) {
                $nameParts = $Name.split(" ").Trim()
                if ($nameParts.Count -eq 2) {
                    # Assuming schema "<Firstname> <Lastname>"
                    $FirstName = $nameParts[0]
                } else {
                    $FirstName = ""
                }
            } else {
                $FirstName = ""
            }
        }
        Remove-Variable -Name nameParts -Force -Confirm:$false -WhatIf:$false -Verbose:$false -Debug:$false -ErrorAction Ignore -WarningAction Ignore

        if (-not $Initials -and $FirstName -and $LastName) {
            $_initials = "$(([string]$FirstName)[0])$(([string]$LastName)[0])"
        } else {
            $_initials = $Initials
        }

        $body = [ordered]@{
            "id"                 = 0
            "name"               = "$Name"
            "firstName"          = "$FirstName"
            "lastName"           = "$LastName"
            "salutationId"       = $SalutationId
            "departmentId"       = $DepartmentId
            "room"               = "$Room"
            "telephoneNumber"    = "$Phone"
            "emailAddress"       = "$Email"
            "carId"              = $CarId
            "mobilePhone"        = "$Mobile"
            "initials"           = "$_initials"
            "workingHourModelId" = $WorkingHourModelId
            "accountingTypeId"   = $accountingTypeId
            "privatePhoneNumber" = "$PrivateNumber"
            "active"             = $IsActive
            "erpNumber"          = "$ERPNumber"
            "personalFaxNumber"  = "$Fax"
            "role"               = "$Role"
            "titleId"            = $TitleId
            "language"           = "$Language"
            "telephoneNumberTwo" = "$Phone2"
            "mobileNumberTwo"    = "$Mobile2"
            "birthday"           = "$_birthday"
            "companyAssignments" = $_companies
        }
        #endregion rest call prepare

        if ($pscmdlet.ShouldProcess("Employee '$($Name)' on companyID '$([string]::Join(", ", $CompanyId))'", "New")) {
            Write-PSFMessage -Level Verbose -Message "Creating new employee '$($Name)' on companyID '$([string]::Join(", ", $CompanyId))'" -Tag "Employee" -Data $body

            $response = Invoke-TANSSRequest -Type POST -ApiPath $apiPath -Body $body -Token $Token

            if ($response) {
                Write-PSFMessage -Level Verbose -Message "API Response: $($response.meta.text)"

                Push-DataToCacheRunspace -MetaData $response.meta

                [TANSS.Employee]@{
                    BaseObject = $response.content
                    Id         = $response.content.id
                }
            } else {
                Write-PSFMessage -Level Error -Message "Error creating employee, no response from API"
            }
        }
    }

    end {
    }
}

function Get-TANSSProject {
    <#
    .Synopsis
        Get-TANSSProject
 
    .DESCRIPTION
        Get all projects from TANSS
 
    .PARAMETER Token
        The TANSS.Connection token to access api
 
        If not specified, the registered default token from within the module is going to be used
 
    .EXAMPLE
        PS C:\> Get-TANSSProject
 
        Get all projects from TANSS
 
    .NOTES
        Author: Andreas Bellstedt
 
    .LINK
        https://github.com/AndiBellstedt/PSTANSS
    #>

    [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidMultipleTypeAttributes", "")]
    [CmdletBinding(
        SupportsShouldProcess = $false,
        ConfirmImpact = 'Low'
    )]
    [OutputType([TANSS.Ticket])]
    param (
        [TANSS.Connection]
        $Token
    )
    begin {
        try {
            $outBuffer = $null
            if ($PSBoundParameters.TryGetValue('OutBuffer', [ref]$outBuffer)) {
                $PSBoundParameters['OutBuffer'] = 1
            }
            $wrappedCmd = $ExecutionContext.InvokeCommand.GetCommand('Get-TANSSTicket', [System.Management.Automation.CommandTypes]::Function)
            $scriptCmd = {& $wrappedCmd -Project @PSBoundParameters }
            $steppablePipeline = $scriptCmd.GetSteppablePipeline()
            $steppablePipeline.Begin($PSCmdlet)
        } catch {
            throw
        }
    }

    process {
        try {
            $steppablePipeline.Process($_)
        } catch {
            throw
        }
    }

    end {
        try {
            $steppablePipeline.End()
        } catch {
            throw
        }
    }
}

function Get-TANSSProjectPhase {
    <#
    .Synopsis
        Get-TANSSProjectPhase
 
    .DESCRIPTION
        Get phases of a project in TANSS
 
    .PARAMETER ProjectID
        The ID of the poject to receive phases from
 
    .PARAMETER Project
        TANSS.Project object to receive phases from
 
    .PARAMETER Token
        The TANSS.Connection token to access api
 
        If not specified, the registered default token from within the module is going to be used
 
    .EXAMPLE
        PS C:\> Get-TANSSProjectPhase -ProjectId 10
 
        Get phases out of project 10
 
    .EXAMPLE
        PS C:\> $projects | Get-TANSSTicketActivity
 
        Get all phases of projects in variable $tickets
 
    .NOTES
        Author: Andreas Bellstedt
 
    .LINK
        https://github.com/AndiBellstedt/PSTANSS
    #>

    [CmdletBinding(
        DefaultParameterSetName = "ByProject",
        SupportsShouldProcess = $false,
        PositionalBinding = $true,
        ConfirmImpact = 'Low'
    )]
    [OutputType([TANSS.ProjectPhase])]
    Param(
        [Parameter(
            ParameterSetName = "ByProjectId",
            ValueFromPipelineByPropertyName = $true,
            ValueFromPipeline = $true,
            Mandatory = $true
        )]
        [Alias("Id")]
        [int[]]
        $ProjectID,

        [Parameter(
            ParameterSetName = "ByProject",
            ValueFromPipelineByPropertyName = $true,
            ValueFromPipeline = $true,
            Mandatory = $true
        )]
        [TANSS.Project[]]
        $Project,

        [TANSS.Connection]
        $Token
    )

    begin {
        if (-not $Token) { $Token = Get-TANSSRegisteredAccessToken }
        Assert-CacheRunspaceRunning
    }

    process {
        $parameterSetName = $pscmdlet.ParameterSetName
        Write-PSFMessage -Level Debug -Message "ParameterNameSet: $($parameterSetName)"


        if ($parameterSetName -like "ByProject") {
            $inputobjectProjectCount = ([array]$Project).Count
            Write-PSFMessage -Level System -Message "Getting IDs of $($inputobjectProjectCount) project$(if($inputobjectProjectCount -gt 1){'s'})"  -Tag "ProjectPhase", "CollectInputObjects"
            [array]$ProjectID = $Project.id
        }


        foreach ($projectIdItem in $ProjectID) {
            Write-PSFMessage -Level Verbose -Message "Working on project ID $($projectIdItem)"  -Tag "ProjectPhase", "Query"

            # build api path
            $apiPath = Format-ApiPath -Path "api/v1/projects/$projectIdItem/phases"

            # query content
            $response = Invoke-TANSSRequest -Type GET -ApiPath $apiPath -Token $Token
            Push-DataToCacheRunspace -MetaData $response.meta
            Write-PSFMessage -Level Verbose -Message "$($response.meta.text): Received $($response.meta.properties.extras.count) VacationEntitlement records in year $($Year)" -Tag "ProjectPhase", "Query"

            # create output
            foreach ($phase in $response.content) {
                # output object
                [TANSS.ProjectPhase]@{
                    BaseObject = $phase
                    Id         = $phase.id
                }

                # cache Lookup refresh
                [TANSS.Lookup]::Phases[$phase.id] = $phase.name
            }
        }
    }

    end {}
}


function New-TANSSProject {
    <#
    .Synopsis
        New-TANSSProject
 
    .DESCRIPTION
        Creates a project in TANSS, that can contain multiple tickets
 
    .PARAMETER Token
        The TANSS.Connection token to access api
 
        If not specified, the registered default token from within the module is going to be used
 
    .PARAMETER WhatIf
        If this switch is enabled, no actions are performed but informational messages will be displayed that explain what would happen if the command were to run.
 
    .PARAMETER Confirm
        If this switch is enabled, you will be prompted for confirmation before executing any operations that change state.
 
    .EXAMPLE
        PS C:\> New-TANSSProject -Title "A new project"
 
        Create a project with title "A new project"
 
    .NOTES
        Author: Andreas Bellstedt
 
    .LINK
        https://github.com/AndiBellstedt/PSTANSS
    #>

    [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidMultipleTypeAttributes", "")]
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSShouldProcess", "")]
    [CmdletBinding(
        DefaultParameterSetName = "Userfriendly",
        SupportsShouldProcess = $true,
        PositionalBinding = $true,
        ConfirmImpact = 'Medium'
    )]
    [OutputType([TANSS.Ticket])]
    param (
        # Company id of the ticket. Name is stored in the "linked entities" - "companies". Can only be set if the user has access to the company
        [Parameter(ParameterSetName="ApiNative")]
        [int]
        $CompanyId,

        # Company name where the ticket should create for. Can only be set if the user has access to the company
        [Parameter(ParameterSetName="Userfriendly")]
        [String]
        $Company,

        # If the ticket has a remitter, the id goes here. Name is stored in the "linked entities" - "employees"
        [Parameter(ParameterSetName="ApiNative")]
        [Alias('ClientId')]
        [int]
        $RemitterId,

        # If the ticket has a remitter/client, the name of the client
        [Parameter(ParameterSetName="Userfriendly")]
        [String]
        $Client,

        # gives infos about how the remitter gave the order. Infos are stored in the "linked entities" - "orderBys"
        [Parameter(ParameterSetName="ApiNative")]
        [int]
        $OrderById,

        # gives infos about how the Client gave the order.
        [Parameter(ParameterSetName="Userfriendly")]
        [string]
        $OrderBy,

        # The title / subject of the ticket
        [Parameter(
            ParameterSetName="Userfriendly",
            Mandatory = $true,
            ValueFromPipeline = $true,
            ValueFromPipelineByPropertyName = $true

        )]
        [Parameter(
            ParameterSetName="ApiNative",
            Mandatory = $true,
            ValueFromPipeline = $true,
            ValueFromPipelineByPropertyName = $true

        )]
        [string]
        $Title,

        # The content / description of the ticket
        [Parameter(ParameterSetName="ApiNative")]
        [Parameter(ParameterSetName="Userfriendly")]
        [Alias('content')]
        [string]
        $Description,

        # External ticket id (optional)
        [Parameter(ParameterSetName="ApiNative")]
        [Parameter(ParameterSetName="Userfriendly")]
        [Alias('extTicketId')]
        [string]
        $ExternalTicketId,

        # id of employee which ticket is assigned to. Name is stored in "linked entities" - "employees"
        [Parameter(ParameterSetName="ApiNative")]
        [Alias('assignedToEmployeeId')]
        [int]
        $EmployeeIdAssigned,

        # Name of the employee the ticket is assigned to
        [Parameter(ParameterSetName="Userfriendly")]
        [String]
        $EmployeeAssigned,

        # id of department the ticket is assigned to. Name is stored in "linked entities" - "departments"
        [Parameter(ParameterSetName="ApiNative")]
        [Alias('assignedToDepartmentId')]
        [int]
        $DepartmentIdAssigned,

        # Name of the department the ticket is assigned to
        [Parameter(ParameterSetName="Userfriendly")]
        [String]
        $Department,

        # id of the ticket state. Name is give in "linked entities" - "ticketStates"
        [Parameter(ParameterSetName="ApiNative")]
        [int]
        $StatusId,

        # The name of the ticket status
        [Parameter(ParameterSetName="Userfriendly")]
        [String]
        $Status,

        # id of the ticket type. Name is give in "linked entities" - "ticketTypes"
        [Parameter(ParameterSetName="ApiNative")]
        [int]
        $TypeId,

        # The name of the ticket type
        [Parameter(ParameterSetName="Userfriendly")]
        [String]
        $Type,

        # if ticket is assigned to device / employee, linktype is given here
        [Parameter(ParameterSetName="ApiNative")]
        [Parameter(ParameterSetName="Userfriendly")]
        [Alias('LinkTypeId')]
        [int]
        $AssignmentId,

        # If ticket has a deadline, the date is given here
        [Parameter(ParameterSetName="ApiNative")]
        [Parameter(ParameterSetName="Userfriendly")]
        [Alias('deadlineDate')]
        [datetime]
        $Deadline,

        # if true, this ticket is a "repair ticket"
        [Parameter(ParameterSetName="ApiNative")]
        [Parameter(ParameterSetName="Userfriendly")]
        [Alias('repair')]
        [bool]
        $IsRepair = $false,

        # if ticket has a due date, the timestamp is given here
        [Parameter(ParameterSetName="ApiNative")]
        [Parameter(ParameterSetName="Userfriendly")]
        [datetime]
        $DueDate,

        # Determines the "attention" flag state of a ticket
        [Parameter(ParameterSetName="ApiNative")]
        [Parameter(ParameterSetName="Userfriendly")]
        [ValidateSet("NO", "YES", "RESUBMISSION", "MAIL")]
        [string]
        $Attention = "NO",

        # If the ticket has an installation fee, this value is true
        [Parameter(ParameterSetName="ApiNative")]
        [Parameter(ParameterSetName="Userfriendly")]
        [ValidateSet("NO", "YES", "NO_PROJECT_INSTALLATION_FEE")]
        [string]
        $InstallationFee = "NO",

        # Sets the installation fee drive mode. If it is set to NONE then the system config parameter "leistung.ip.fahrzeit_berechnen" will be used. If the company from the ticket has an installation fee drive mode set then that will be used instead of the system config parameter.
        [Parameter(ParameterSetName="ApiNative")]
        [Parameter(ParameterSetName="Userfriendly")]
        [ValidateSet("NONE", "DRIVE_INCLUDED", "DRIVE_EXCLUDED")]
        [string]
        $InstallationFeeDriveMode = "NONE",

        #Amount for the installation fee
        [Parameter(ParameterSetName="ApiNative")]
        [Parameter(ParameterSetName="Userfriendly")]
        [int]
        $InstallationFeeAmount,

        # If true, the ticket shall be billed separately
        [Parameter(ParameterSetName="ApiNative")]
        [Parameter(ParameterSetName="Userfriendly")]
        [bool]
        $SeparateBilling = $false,

        # if the ticket has a service cap, here the amount is given
        [Parameter(ParameterSetName="ApiNative")]
        [Parameter(ParameterSetName="Userfriendly")]
        [int]
        $ServiceCapAmount,

        # linkId of the relationship (if ticket has a relation)
        [Parameter(ParameterSetName="ApiNative")]
        [Parameter(ParameterSetName="Userfriendly")]
        [int]
        $RelationshipLinkId,

        # linkTypeId of the relationship (if ticket has a relation)
        [Parameter(ParameterSetName="ApiNative")]
        [Parameter(ParameterSetName="Userfriendly")]
        [int]
        $RelationshipLinkTypeId,

        # if the ticket as a resubmission date set, this is given here
        [Parameter(ParameterSetName="ApiNative")]
        [Parameter(ParameterSetName="Userfriendly")]
        [datetime]
        $ResubmissionDate,

        # If a resubmission text is set, this text is returned here
        [Parameter(ParameterSetName="ApiNative")]
        [Parameter(ParameterSetName="Userfriendly")]
        [string]
        $ResubmissionText,

        # Number of estimated minutes which is planned for the ticket
        [Parameter(ParameterSetName="ApiNative")]
        [Parameter(ParameterSetName="Userfriendly")]
        [int]
        $EstimatedMinutes,

        # Determines wether the ticket is assigned to a local ticket admin or not
        # NONE: "normal" ticket
        # LOCAL_ADMIN: ticket is assigned to a local ticket admin
        # TECHNICIAN: local ticket admin has forwarded the ticket to a technician
        [Parameter(ParameterSetName="ApiNative")]
        [Parameter(ParameterSetName="Userfriendly")]
        [ValidateSet("NONE", "LOCAL_ADMIN", "TECHNICIAN")]
        [string]
        $LocalTicketAdminFlag = "NONE",

        # if the ticket is assigned to a local ticket admin, this represents the employee (local ticket admin) who is assigned for this ticket
        [Parameter(ParameterSetName="ApiNative")]
        [int]
        $LocalTicketAdminEmployeeId,

        # if the ticket is assigned to a local ticket admin, this represents the name of the employee (local ticket admin) who is assigned for this ticket
        [Parameter(ParameterSetName="Userfriendly")]
        [ValidateNotNullOrEmpty]
        [string]
        $EmployeeTicketAdmin,

        # Sets the order number
        [Parameter(ParameterSetName="ApiNative")]
        [Parameter(ParameterSetName="Userfriendly")]
        [string]
        $OrderNumber,

        # If the ticket has a reminder set, the timestamp is returned here
        [Parameter(ParameterSetName="ApiNative")]
        [Parameter(ParameterSetName="Userfriendly")]
        [datetime]
        $Reminder,

        # When persisting a ticket, you can also send a list of tag assignments which will be assigned to the ticket
        [Parameter(ParameterSetName="ApiNative")]
        [Parameter(ParameterSetName="Userfriendly")]
        [string[]]
        $Tags,

        [Parameter(ParameterSetName="ApiNative")]
        [Parameter(ParameterSetName="Userfriendly")]
        [TANSS.Connection]
        $Token
    )
    begin {
        try {
            $outBuffer = $null
            if ($PSBoundParameters.TryGetValue('OutBuffer', [ref]$outBuffer)) {
                $PSBoundParameters['OutBuffer'] = 1
            }
            $wrappedCmd = $ExecutionContext.InvokeCommand.GetCommand('New-TANSSTicket', [System.Management.Automation.CommandTypes]::Function)
            $scriptCmd = {& $wrappedCmd -IsProject $true @PSBoundParameters }
            $steppablePipeline = $scriptCmd.GetSteppablePipeline()
            $steppablePipeline.Begin($PSCmdlet)
        } catch {
            throw
        }
    }

    process {
        try {
            $steppablePipeline.Process($_)
        } catch {
            throw
        }
    }

    end {
        try {
            $steppablePipeline.End()
        } catch {
            throw
        }
    }
}

function New-TANSSProjectPhase {
    <#
    .Synopsis
        Add a project phase into a project
 
    .DESCRIPTION
        Add a project phase into a project
 
    .PARAMETER ProjectID
        The ID of the poject where to create the specified phase
 
    .PARAMETER Project
        TANSS.Project object where to create the specified phase
 
    .PARAMETER Name
        The name of the phase
 
    .PARAMETER Rank
        The rank of the phase, when there are other phases in the project
 
    .PARAMETER StartDate
        The (planned) starting date of the phase
 
    .PARAMETER EndDate
        The (planned) ending date of the phase
 
    .PARAMETER RequiredPrePhasesComplete
        Indicates that all tickets in the previous phase has to be finished to open this phase
 
        Default: $false
 
    .PARAMETER BillingType
        Set the behavour how to bill tickets.
        Values are available via TabCompletion.
        Possible are:
            "InstantBillSupportActivities" = Support activities of sub-tickets are immediately billable
            "BillClosedTicketOnly" = Only Support activities of completed sub-tickets are billable
            "BillOnlyWhenAllTicketsClosed" = Phase may only be billed when all sub-tickets have been completed
 
        Default: "InstantBillSupportActivities"
 
    .PARAMETER ClearanceMode
        Set the behavour how support activities in tickets for the phase are treated.
        Values are available via TabCompletion.
        Possible are:
            "Default" = Setting from TANSS application base preferences is used
            "ReleaseSupportOfUnresolvedTickets" = support activities in open tickets can be released
            "LockSupportsOfUnresolvedTickets" = support activities in open tickets can not be released
 
        Default: "Default"
 
    .PARAMETER Token
        The TANSS.Connection token to access api
 
        If not specified, the registered default token from within the module is going to be used
 
    .PARAMETER WhatIf
        If this switch is enabled, no actions are performed but informational messages will be displayed that explain what would happen if the command were to run.
 
    .PARAMETER Confirm
        If this switch is enabled, you will be prompted for confirmation before executing any operations that change state.
 
    .EXAMPLE
        PS C:\> New-TANSSProjectPhase -Project $project -Name "Phase X"
 
        Creates "Phase X" within the project in the variable $project
 
    .EXAMPLE
        PS C:\> $project | New-TANSSProjectPhase -Name "Phase X"
 
        Creates "Phase X" within the project in the variable $project
 
    .EXAMPLE
        PS C:\> New-TANSSProjectPhase -ProjectId $project.id -Name "Phase X", "Phase Y", "Phase Z"
 
        Creates 3 phases ("Phase X", "Phase Y", "Phase Z") within the project in the variable $project
 
        .EXAMPLE
        PS C:\> "Phase X", "Phase Y", "Phase Z" | New-TANSSProjectPhase -ProjectId $project.id
 
        Creates 3 phases ("Phase X", "Phase Y", "Phase Z") within the project in the variable $project
 
    .NOTES
        Author: Andreas Bellstedt
 
    .LINK
        https://github.com/AndiBellstedt/PSTANSS
    #>

    [CmdletBinding(
        DefaultParameterSetName = "ByProject",
        SupportsShouldProcess = $true,
        PositionalBinding = $true,
        ConfirmImpact = 'Medium'
    )]
    [OutputType([TANSS.ProjectPhase])]
    Param(
        [Parameter(
            ParameterSetName = "ByPhaseName",
            Mandatory = $true
        )]
        [Alias("Id")]
        [int]
        $ProjectID,

        [Parameter(
            ParameterSetName = "ByProject",
            ValueFromPipelineByPropertyName = $true,
            ValueFromPipeline = $true,
            Mandatory = $true
        )]
        [TANSS.Project]
        $Project,

        [Parameter(
            ValueFromPipeline = $true,
            Mandatory = $true
        )]
        [string[]]
        $Name,

        [int]
        $Rank,

        [datetime]
        $StartDate,

        [datetime]
        $EndDate,

        [bool]
        $RequiredPrePhasesComplete = $false,

        [ValidateSet("InstantBillSupportActivities", "BillClosedTicketOnly", "BillOnlyWhenAllTicketsClosed")]
        [string]
        $BillingType = "InstantBillSupportActivities",

        [ValidateSet("Default", "ReleaseSupportOfUnresolvedTickets", "LockSupportsOfUnresolvedTickets")]
        [String]
        $ClearanceMode = "Default",

        [TANSS.Connection]
        $Token
    )

    begin {
        if (-not $Token) { $Token = Get-TANSSRegisteredAccessToken }
        Assert-CacheRunspaceRunning

        $apiPath = Format-ApiPath -Path "api/v1/projects/phases"

        # Translate BillingType into api value
        if ($BillingType) {
            switch ($BillingType) {
                "InstantBillSupportActivities" { $_billingType = "DEFAULT" }
                "BillClosedTicketOnly" { $_billingType = "TICKET_MUST_BE_CLOSED" }
                "BillOnlyWhenAllTicketsClosed" { $_billingType = "ALL_TICKETS_MUST_BE_CLOSED" }
                Default {
                    Stop-PSFFunction -Message "Unhandeled Value in BillingType. Developers mistake." -EnableException $true -Cmdlet $pscmdlet
                }
            }
        }

        # Translate BillingType into api value
        if ($ClearanceMode) {
            switch ($ClearanceMode) {
                "Default" { $_clearanceMode = "DEFAULT" }
                "ReleaseSupportOfUnresolvedTickets" { $_clearanceMode = "DONT_CLEAR_SUPPORTS" }
                "LockSupportsOfUnresolvedTickets" { $_clearanceMode = "MAY_CLEAR_SUPPORTS" }
                Default {
                    Stop-PSFFunction -Message "Unhandeled Value in ClearanceMode. Developers mistake." -EnableException $true -Cmdlet $pscmdlet
                }
            }
        }
    }

    process {
        $parameterSetName = $pscmdlet.ParameterSetName
        Write-PSFMessage -Level Debug -Message "ParameterNameSet: $($parameterSetName)"


        if ($parameterSetName -like "ByProject") {
            $ProjectID = $Project.id
            Write-PSFMessage -Level System -Message "Identified ID '$($ProjectID) for project $($Project.Title)"  -Tag "ProjectPhase", "CollectInputObjects"
        }


        # Create body object for api call
        foreach ($nameItem in $Name) {
            Write-PSFMessage -Level Verbose -Message "Working new phase '$($nameItem)' for project ID $($ProjectID)"  -Tag "ProjectPhase", "New"

            $phaseToCreate = [ordered]@{
                projectId               = $ProjectID
                name                    = $nameItem
                closedPrePhasesRequired = $RequiredPrePhasesComplete
                billingType             = $_billingType
                clearanceMode           = $_clearanceMode
            }

            if ($Rank) { $phaseToCreate.add("rank", $Rank) }

            if ($StartDate) {
                $_startDate = [int32][double]::Parse((Get-Date -Date $StartDate.ToUniversalTime() -UFormat %s))
                $phaseToCreate.add("startDate", $_startDate)
            }

            if ($EndDate) {
                $_endDate = [int32][double]::Parse((Get-Date -Date $EndDate.ToUniversalTime() -UFormat %s))
                $phaseToCreate.add("endDate", $_endDate)
            }


            # Create phase
            if ($pscmdlet.ShouldProcess("phase '$($nameItem)' in project id $($ProjectID)", "New")) {
                Write-PSFMessage -Level Verbose -Message "New phase '$($nameItem)' in project id $($ProjectID)" -Tag "ProjectPhase", "New"

                # invoke api call
                $response = Invoke-TANSSRequest -Type POST -ApiPath $apiPath -Body $phaseToCreate -Token $Token

                Write-PSFMessage -Level Verbose -Message "$($response.meta.text): $($response.content.name) (Rank: $($response.content.rank), Id: $($response.content.id))" -Tag "ProjectPhase", "New", "CreatedSuccessfully"

                # create output
                [TANSS.ProjectPhase]@{
                    BaseObject = $response.content
                    Id         = $response.content.id
                }

                # cache Lookup refresh
                [TANSS.Lookup]::Phases[$response.content.id] = $response.content.name
            }
        }
    }

    end {}
}


function Remove-TANSSProject {
    <#
        .Synopsis
            Remove-TANSSProject
 
        .DESCRIPTION
            Delete a project in TANSS
 
    .PARAMETER Token
        The TANSS.Connection token to access api
 
        If not specified, the registered default token from within the module is going to be used
 
    .PARAMETER WhatIf
        If this switch is enabled, no actions are performed but informational messages will be displayed that explain what would happen if the command were to run.
 
    .PARAMETER Confirm
        If this switch is enabled, you will be prompted for confirmation before executing any operations that change state.
 
    .EXAMPLE
        PS C:\> Remove-TANSSProject -ID 100
 
        Remove project with ticketID 100 from TANSS
 
    .NOTES
        Author: Andreas Bellstedt
 
    .LINK
        https://github.com/AndiBellstedt/PSTANSS
    #>

    [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidMultipleTypeAttributes", "")]
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSShouldProcess", "")]
    [CmdletBinding(
        DefaultParameterSetName = 'ById',
        SupportsShouldProcess = $true,
        PositionalBinding = $true,
        ConfirmImpact = 'High'
    )]
    [OutputType([TANSS.Ticket])]
    param (
        # TANSS Ticket object to remove
        [Parameter(
            ParameterSetName = "ByInputObject",
            Mandatory = $true,
            ValueFromPipeline = $true,
            ValueFromPipelineByPropertyName = $true
        )]
        [TANSS.Ticket[]]
        $InputObject,

        # Id of the ticket to remove
        [Parameter(
            ParameterSetName = "ById",
            Mandatory = $true,
            ValueFromPipeline = $true,
            ValueFromPipelineByPropertyName = $true
        )]
        [Alias("ProjectId", "Project","TicketId", "Ticket")]
        [int[]]
        $Id,

        [TANSS.Connection]
        $Token
    )
    begin {
        try {
            $outBuffer = $null
            if ($PSBoundParameters.TryGetValue('OutBuffer', [ref]$outBuffer)) {
                $PSBoundParameters['OutBuffer'] = 1
            }
            $wrappedCmd = $ExecutionContext.InvokeCommand.GetCommand('Remove-TANSSTicket', [System.Management.Automation.CommandTypes]::Function)
            $scriptCmd = {& $wrappedCmd @PSBoundParameters }
            $steppablePipeline = $scriptCmd.GetSteppablePipeline()
            $steppablePipeline.Begin($PSCmdlet)
        } catch {
            throw
        }
    }

    process {
        try {
            $steppablePipeline.Process($_)
        } catch {
            throw
        }
    }

    end {
        try {
            $steppablePipeline.End()
        } catch {
            throw
        }
    }
}

function Remove-TANSSProjectPhase {
    <#
    .Synopsis
        Remove-TANSSProjectPhase
 
    .DESCRIPTION
        Removes a project phase from TANSS
 
    .PARAMETER InputObject
        TANSS.ProjectPhase object to remove
 
    .PARAMETER Force
        Process the removal quietly.
 
    .PARAMETER Token
        The TANSS.Connection token to access api
 
        If not specified, the registered default token from within the module is going to be used
 
    .PARAMETER WhatIf
        If this switch is enabled, no actions are performed but informational messages will be displayed that explain what would happen if the command were to run.
 
    .PARAMETER Confirm
        If this switch is enabled, you will be prompted for confirmation before executing any operations that change state.
 
    .EXAMPLE
        PS C:\> $phase | Remove-TANSSProjectPhase
 
        Remove project phase in variable $phase from TANSS
 
    .NOTES
        Author: Andreas Bellstedt
 
    .LINK
        https://github.com/AndiBellstedt/PSTANSS
    #>

    [CmdletBinding(
        SupportsShouldProcess = $true,
        PositionalBinding = $true,
        ConfirmImpact = 'High'
    )]
    Param(
        [Parameter(
            Mandatory = $true,
            ValueFromPipeline = $true,
            ValueFromPipelineByPropertyName = $true
        )]
        [Alias("Phase", "ProjectPhase")]
        [TANSS.ProjectPhase[]]
        $InputObject,

        [switch]
        $Force,

        [TANSS.Connection]
        $Token
    )

    begin {
        if (-not $Token) { $Token = Get-TANSSRegisteredAccessToken }
        Assert-CacheRunspaceRunning
    }

    process {
        $processRemoval = $false

        foreach ($projectPhase in $InputObject) {
            # Check on Force parameter, otherwise process shouldprocess
            if ($Force) {
                $processRemoval = $true
            } else {
                if ($pscmdlet.ShouldProcess("ProjectPhase Id $($projectPhase.Id) '$($projectPhase.Name)' from project '$($projectPhase.Project)' (Id $($projectPhase.ProjectId))", "Remove")) {
                    $processRemoval = $true
                }
            }

            if ($processRemoval) {
                Write-PSFMessage -Level Verbose -Message "Remove ProjectPhase Id $($projectPhase.Id) '$($projectPhase.Name)' from project '$($projectPhase.Project)' (Id $($projectPhase.ProjectId))" -Tag "ProjectPhase", "Remove"

                # Remove projectPhase
                $apiPath = Format-ApiPath -Path "api/v1/projects/phases/$($projectPhase.Id)"
                Invoke-TANSSRequest -Type DELETE -ApiPath $apiPath -Token $Token -Confirm:$false -ErrorVariable "invokeError"

                # cache Lookup refresh
                [TANSS.Lookup]::Phases.Remove($($projectPhase.Id))
            }
        }
    }

    end {}
}


function Set-TANSSProjectPhase {
    <#
    .Synopsis
        Modify a phase inside project
 
    .DESCRIPTION
        Modify a phase inside project
 
    .PARAMETER Phase
        TANSS.ProjectPhase object to set
 
    .PARAMETER PhaseID
        The ID of the poject phase to set
 
    .PARAMETER Project
        TANSS.Project where to modify a phase
 
    .PARAMETER PhaseName
        The name of the phase to modify
 
    .PARAMETER Name
        The (new) name for the phase to set
 
    .PARAMETER Rank
        The order of the phase in relation to other project phases
 
    .PARAMETER StartDate
        The (planned) starting date of the phase
 
    .PARAMETER EndDate
        The (planned) ending date of the phase
 
    .PARAMETER RequiredPrePhasesComplete
        Indicates that all tickets in the previous phase has to be finished to open this phase
 
        Default: $false
 
    .PARAMETER BillingType
        Set the behavour how to bill tickets.
        Values are available via TabCompletion.
        Possible are:
            "InstantBillSupportActivities" = Support activities of sub-tickets are immediately billable
            "BillClosedTicketOnly" = Only Support activities of completed sub-tickets are billable
            "BillOnlyWhenAllTicketsClosed" = Phase may only be billed when all sub-tickets have been completed
 
        Default: "InstantBillSupportActivities"
 
    .PARAMETER ClearanceMode
        Set the behavour how support activities in tickets for the phase are treated.
        Values are available via TabCompletion.
        Possible are:
            "Default" = Setting from TANSS application base preferences is used
            "ReleaseSupportOfUnresolvedTickets" = support activities in open tickets can be released
            "LockSupportsOfUnresolvedTickets" = support activities in open tickets can not be released
 
        Default: "Default"
 
    .PARAMETER Token
        The TANSS.Connection token to access api
 
        If not specified, the registered default token from within the module is going to be used
 
    .PARAMETER PassThru
        Outputs the result to the console
 
    .PARAMETER WhatIf
        If this switch is enabled, no actions are performed but informational messages will be displayed that explain what would happen if the command were to run.
 
    .PARAMETER Confirm
        If this switch is enabled, you will be prompted for confirmation before executing any operations that change state.
 
    .EXAMPLE
        PS C:\> Set-TANSSProjectPhase -PhaseID $phase.Id -Name "NewPhaseName X"
 
        Rename the phase from variable $phase to "NewPhaseName X"
        $Phase has to be filled with a
 
    .EXAMPLE
        PS C:\> $phase | Set-TANSSProjectPhase -Name "NewPhaseName X"
 
        Rename the phase from variable $phase to "NewPhaseName X"
        $Phase has to be filled with a
 
    .NOTES
        Author: Andreas Bellstedt
 
    .LINK
        https://github.com/AndiBellstedt/PSTANSS
    #>

    [CmdletBinding(
        DefaultParameterSetName = "ByPhase",
        SupportsShouldProcess = $true,
        PositionalBinding = $true,
        ConfirmImpact = 'Medium'
    )]
    [OutputType([TANSS.ProjectPhase])]
    Param(
        [Parameter(
            ParameterSetName = "ByPhaseId",
            ValueFromPipeline = $true,
            Mandatory = $true
        )]
        [Alias("Id")]
        [int]
        $PhaseID,

        [Parameter(
            ParameterSetName = "ByPhase",
            ValueFromPipelineByPropertyName = $true,
            ValueFromPipeline = $true,
            Mandatory = $true
        )]
        [TANSS.ProjectPhase]
        $Phase,

        [Parameter(
            ParameterSetName = "ByProject",
            ValueFromPipelineByPropertyName = $true,
            ValueFromPipeline = $true,
            Mandatory = $true
        )]
        [TANSS.Ticket]
        $Project,

        [Parameter(
            ParameterSetName = "ByProject",
            Mandatory = $true
        )]
        [string]
        $PhaseName,

        [ValidateNotNullOrEmpty()]
        [Alias("NewName")]
        [string]
        $Name,

        [ValidateNotNullOrEmpty()]
        [int]
        $Rank,

        [ValidateNotNullOrEmpty()]
        [datetime]
        $StartDate,

        [ValidateNotNullOrEmpty()]
        [datetime]
        $EndDate,

        [bool]
        $RequiredPrePhasesComplete,

        [ValidateSet("InstantBillSupportActivities", "BillClosedTicketOnly", "BillOnlyWhenAllTicketsClosed")]
        [string]
        $BillingType,

        [ValidateSet("Default", "ReleaseSupportOfUnresolvedTickets", "LockSupportsOfUnresolvedTickets")]
        [String]
        $ClearanceMode,

        [switch]
        $PassThru,

        [TANSS.Connection]
        $Token
    )

    begin {
        if (-not $Token) { $Token = Get-TANSSRegisteredAccessToken }
        Assert-CacheRunspaceRunning

        # Translate BillingType into api value
        if ($BillingType) {
            switch ($BillingType) {
                "InstantBillSupportActivities" { $_billingType = "DEFAULT" }
                "BillClosedTicketOnly" { $_billingType = "TICKET_MUST_BE_CLOSED" }
                "BillOnlyWhenAllTicketsClosed" { $_billingType = "ALL_TICKETS_MUST_BE_CLOSED" }
                Default {
                    Stop-PSFFunction -Message "Unhandeled Value in BillingType. Developers mistake." -EnableException $true -Cmdlet $pscmdlet
                }
            }
        }

        # Translate BillingType into api value
        if ($ClearanceMode) {
            switch ($ClearanceMode) {
                "Default" { $_clearanceMode = "DEFAULT" }
                "ReleaseSupportOfUnresolvedTickets" { $_clearanceMode = "DONT_CLEAR_SUPPORTS" }
                "LockSupportsOfUnresolvedTickets" { $_clearanceMode = "MAY_CLEAR_SUPPORTS" }
                Default {
                    Stop-PSFFunction -Message "Unhandeled Value in ClearanceMode. Developers mistake." -EnableException $true -Cmdlet $pscmdlet
                }
            }
        }
    }

    process {
        $parameterSetName = $pscmdlet.ParameterSetName
        Write-PSFMessage -Level Debug -Message "ParameterNameSet: $($parameterSetName)"


        # ParameterSet handling
        if ($parameterSetName -like "ByPhase") {
            $PhaseID = $Phase.id
            $projectID = $Phase.ProjectId
            Write-PSFMessage -Level System -Message "Identified ID '$($PhaseID) for project phase $($Phase.Name) within project '$($Phase.Project)' (Id: $($projectID))" -Tag "ProjectPhase", "CollectInputObjects"
        }

        if ($parameterSetName -like "ByProject") {
            $projectID = $Project.Id
            $Phase = Get-TANSSProjectPhase -ProjectID $Project.Id -Token $Token | Where-Object Name -Like $PhaseName
            if (-not $Phase) {
                Write-PSFMessage -Level Error -Message "Phase '$($PhaseName)' not found in project '$($Project.Title)' (Id: $($Project.Id))" -Tag "ProjectPhase", "CollectInputObjects" -Data @{"Project" = $Project }
                continue
            }
            $PhaseID = $Phase.Id
            Write-PSFMessage -Level System -Message "Identified ID '$($PhaseID) for project phase $($Phase.Name) within project $($Phase.Project)"  -Tag "ProjectPhase", "CollectInputObjects"
        }


        # Create body object for api call
        Write-PSFMessage -Level Verbose -Message "Going to set phase ID $($PhaseID)"  -Tag "ProjectPhase", "Set"

        $paramFormatApiPath = @{
            "Path" = "api/v1/projects/phases/$($PhaseID)"
        }
        $paramFormatApiPathQueryParameter = @{}

        $phaseToSet = [ordered]@{
            Id = $PhaseID
            #projectId = $ProjectID
        }
        if ($ProjectID) { $phaseToSet.add("projectId", $ProjectID) }
        if ($Name) { $phaseToSet.add("name", $Name) }
        if ($RequiredPrePhasesComplete) { $phaseToSet.add("closedPrePhasesRequired", $RequiredPrePhasesComplete) }
        if ($BillingType) { $phaseToSet.add("billingType", $_billingType) }
        if ($ClearanceMode) { $phaseToSet.add("clearanceMode", $_clearanceMode) }
        if ($Rank) { $phaseToSet.add("rank", $Rank) }
        if ($StartDate) {
            $_startDate = [int32][double]::Parse((Get-Date -Date $StartDate.ToUniversalTime() -UFormat %s))
            $phaseToSet.add("startDate", $_startDate)

            $paramFormatApiPathQueryParameter["adjustStart"] = $true
            $paramFormatApiPathQueryParameter["adjustEnd"] = $true
        }
        if ($EndDate) {
            $_endDate = [int32][double]::Parse((Get-Date -Date $EndDate.ToUniversalTime() -UFormat %s))
            $phaseToSet.add("endDate", $_endDate)

            $paramFormatApiPathQueryParameter["adjustStart"] = $true
            $paramFormatApiPathQueryParameter["adjustEnd"] = $true
        }

        if ($paramFormatApiPathQueryParameter["adjustStart"] -or $paramFormatApiPathQueryParameter["adjustEnd"]) {
            $paramFormatApiPath.Add("QueryParameter", $paramFormatApiPathQueryParameter)
        }

        $apiPath = Format-ApiPath @paramFormatApiPath

        # Create phase
        $response = Invoke-TANSSRequest -Type PUT -ApiPath $apiPath -Body $phaseToSet -Token $Token
        Write-PSFMessage -Level Verbose -Message "$($response.meta.text): $($response.content.adjustedPhases.name) (Rank: $($response.content.adjustedPhases.rank), Id: $($response.content.adjustedPhases.id))" -Tag "ProjectPhase", "Set", "Modify"


        # create output
        i($PassThru) {
            foreach($_phase in $response.content.adjustedPhases) {
                # object
                [TANSS.ProjectPhase]@{
                    BaseObject = $_phase
                    Id         = $_phase.id
                }

                # cache Lookup refresh
                [TANSS.Lookup]::Phases[$_phase.id] = $_phase.name
            }
        }
    }

    end {
        $null = Get-TANSSProject -Token $Token
    }
}


function Get-TANSSTicket {
    <#
    .Synopsis
        Get-TANSSTicket
 
    .DESCRIPTION
        Gat a ticket from TANSS service
 
    .PARAMETER Id
        The ticket Id (one or more) to query
 
    .PARAMETER CompanyId
        Get all tickets of company Id
 
    .PARAMETER MyTickets
        Get all tickets assigned to the authenticated account
 
    .PARAMETER NotAssigned
        Get all tickets not assigned to somebody
 
    .PARAMETER AllTechnician
        Get all tickets assigned to somebody
 
    .PARAMETER RepairTickets
        Get tickets marked as repair tickets
 
    .PARAMETER NotIdentified
        Get all unidentified tickets
 
    .PARAMETER Project
        Get tickets marked as a project
 
    .PARAMETER LocalTicketAdmin
        Get all tickets specified for a ticket admin
 
    .PARAMETER TicketWithTechnicianRole
        Get all tickets with a technician role
 
    .PARAMETER Token
        The TANSS.Connection token to access api
 
        If not specified, the registered default token from within the module is going to be used
 
    .EXAMPLE
        PS C:\> Get-TANSSTicket
 
        Get all tickets assinged to the authenticated user
 
    .EXAMPLE
        PS C:\> Get-TANSSTicket -Id 100
 
        Get ticket with Id 100
 
    .EXAMPLE
        PS C:\> Get-TANSSTicket -CompanyId 12345
 
        Get all tickets of company Id 12345
 
    .NOTES
        Author: Andreas Bellstedt
 
    .LINK
        https://github.com/AndiBellstedt/PSTANSS
    #>

    [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSReviewUnusedParameter", "")]
    [CmdletBinding(
        DefaultParameterSetName = 'MyTickets',
        SupportsShouldProcess = $false,
        ConfirmImpact = 'Low'
    )]
    [OutputType([TANSS.Ticket], [TANSS.Project])]
    Param(
        [Parameter(
            Mandatory = $true,
            ValueFromPipeline = $true,
            ValueFromPipelineByPropertyName = $true,
            ParameterSetName = "TicketId"
        )]
        [int[]]
        $Id,

        [Parameter(
            Mandatory = $true,
            ValueFromPipeline = $false,
            ValueFromPipelineByPropertyName = $true,
            ParameterSetName = "CompanyId"
        )]
        [int[]]
        $CompanyId,


        [Parameter(
            Mandatory = $false,
            ParameterSetName = "MyTickets"
        )]
        [Alias("Own", "OwnTickets", "MyOwn", "NyOwnTickets")]
        [switch]
        $MyTickets,

        [Parameter(
            Mandatory = $true,
            ParameterSetName = "NotAssigned"
        )]
        [Alias("General", "GeneralTickets")]
        [switch]
        $NotAssigned,

        [Parameter(
            Mandatory = $true,
            ParameterSetName = "AllTechnician"
        )]
        [Alias("TicketsAllTechnicians", "Assigned", "AssignedTickets")]
        [switch]
        $AllTechnician,

        [Parameter(
            Mandatory = $true,
            ParameterSetName = "RepairTickets"
        )]
        [switch]
        $RepairTickets,

        [Parameter(
            Mandatory = $true,
            ParameterSetName = "NotIdentified"
        )]
        [switch]
        $NotIdentified,

        [Parameter(
            Mandatory = $true,
            ParameterSetName = "Projects"
        )]
        [switch]
        [Alias("AllProjects")]
        $Project,

        [Parameter(
            Mandatory = $true,
            ParameterSetName = "LocalTicketAdmin"
        )]
        [Alias("AssignedToTicketAdmin")]
        [switch]
        $LocalTicketAdmin,

        [Parameter(
            Mandatory = $true,
            ParameterSetName = "TicketWithTechnicianRole"
        )]
        [switch]
        $TicketWithTechnicianRole,

        [TANSS.Connection]
        $Token
    )

    begin {
        if (-not $Token) { $Token = Get-TANSSRegisteredAccessToken }
        Assert-CacheRunspaceRunning
    }

    process {
        $parameterSetName = $pscmdlet.ParameterSetName
        Write-PSFMessage -Level Debug -Message "ParameterNameSet: $($parameterSetName)"
        $response = @()

        # Construct query
        switch ($parameterSetName) {
            "TicketId" {
                # in case of "Query by Id" -> Do the query now
                $response += foreach ($ticketId in $Id) {
                    Invoke-TANSSRequest -Type GET -ApiPath "api/v1/tickets/$($ticketId)" -Token $Token
                }
            }

            "CompanyId" {
                # in case of "Query by CompanyId" -> Do the query now
                $response += foreach ($_companyId in $CompanyId) {
                    Invoke-TANSSRequest -Type GET -ApiPath "api/v1/tickets/company/$($_companyId)" -Token $Token
                }
            }

            "MyTickets" {
                $apiPath = Format-ApiPath -Path "api/v1/tickets/own"
            }

            "NotAssigned" {
                $apiPath = Format-ApiPath -Path "api/v1/tickets/general"
            }

            "AllTechnician" {
                $apiPath = Format-ApiPath -Path "api/v1/tickets/technician"
            }

            "RepairTickets" {
                $apiPath = Format-ApiPath -Path "api/v1/tickets/repair"
            }

            "NotIdentified" {
                $apiPath = Format-ApiPath -Path "api/v1/tickets/notIdentified"
            }

            "Projects" {
                $apiPath = Format-ApiPath -Path "api/v1/tickets/projects"
            }

            "LocalTicketAdmin" {
                $apiPath = Format-ApiPath -Path "api/v1/tickets/localAdminOverview"
            }

            "TicketWithTechnicianRole" {
                $apiPath = Format-ApiPath -Path "api/v1/tickets/withRole"
            }

            Default {
                Stop-PSFFunction -Message "Unhandeled ParameterSetName. Developers mistake." -EnableException $true -Cmdlet $pscmdlet
            }
        }

        # Do the constructed query, as long, as variable apiPath has a value
        if ($apiPath) {
            $response += Invoke-TANSSRequest -Type GET -ApiPath $apiPath -Token $Token
        }

        if ($response) {
            Write-PSFMessage -Level Verbose -Message "Found $(($response.content).count) tickets"

            # Push meta to cache runspace
            foreach ($responseItem in $response) {
                Push-DataToCacheRunspace -MetaData $responseItem.meta

                # Output result
                foreach($ticket in $responseItem.content) {
                    if($parameterSetName -like "Projects") {
                        [TANSS.Project]@{
                            BaseObject = $ticket
                            Id = $ticket.id
                        }
                    } else {
                        [TANSS.Ticket]@{
                            BaseObject = $ticket
                            Id = $ticket.id
                        }
                    }
                }

            }
        } else {
            Write-PSFMessage -Level Warning -Message "No tickets found." -Tag "Ticket"
        }
    }

    end {
    }
}


function Get-TANSSTicketActivity {
    <#
    .Synopsis
        Get-TANSSTicketActivity
 
    .DESCRIPTION
        Retreive support activities within a ticket
 
    .PARAMETER TicketID
        The ID of the ticket to receive activities of
 
    .PARAMETER Ticket
        TANSS.Ticket object to receive activities of
 
    .PARAMETER Token
        The TANSS.Connection token to access api
 
        If not specified, the registered default token from within the module is going to be used
 
    .EXAMPLE
        PS C:\> Get-TANSSTicketActivity -TicketID 555
 
        Get all support activities from ticket 555
 
    .EXAMPLE
        PS C:\> $tickets | Get-TANSSTicketActivity
 
        Get all support activities from all tickets from variable $tickets
 
    .NOTES
        Author: Andreas Bellstedt
 
    .LINK
        https://github.com/AndiBellstedt/PSTANSS
 
    #>

    [CmdletBinding(
        DefaultParameterSetName = "ByTicketId",
        SupportsShouldProcess = $false,
        PositionalBinding = $true,
        ConfirmImpact = 'Low'
    )]
    Param(
        [Parameter(
            ParameterSetName = "ByTicketId",
            ValueFromPipelineByPropertyName = $true,
            ValueFromPipeline = $true,
            Mandatory = $true
        )]
        [int[]]
        $TicketID,

        [Parameter(
            ParameterSetName = "ByTicket",
            ValueFromPipelineByPropertyName = $true,
            ValueFromPipeline = $true,
            Mandatory = $true
        )]
        [TANSS.Ticket[]]
        $Ticket,

        [TANSS.Connection]
        $Token
    )

    begin {
        if (-not $Token) { $Token = Get-TANSSRegisteredAccessToken }
        Assert-CacheRunspaceRunning
    }

    process {
        $parameterSetName = $pscmdlet.ParameterSetName
        Write-PSFMessage -Level Debug -Message "ParameterNameSet: $($parameterSetName)"

        if ($parameterSetName -like "ByTicket") {
            $inputobjectTicketCount = ([array]$Ticket).Count
            Write-PSFMessage -Level System -Message "Getting IDs of $($inputobjectTicketCount) ticket$(if($inputobjectTicketCount -gt 1){'s'})"  -Tag "TicketComment", "CollectInputObjects"
            [array]$TicketID = $Ticket.id
        }

        foreach ($ticketIdItem in $TicketID) {
            Write-PSFMessage -Level Verbose -Message "Working on ticket ID $($ticketIdItem)"  -Tag "TicketComment", "Query"

            Get-TANSSTicketContent -TicketID $ticketIdItem -Type "Activity" -Token $Token | Select-Object -ExpandProperty Object
        }

    }

    end {
    }
}


function Get-TANSSTicketComment {
    <#
    .Synopsis
        Get-TANSSTicketComment
 
    .DESCRIPTION
        Retreive the ticket comments
 
    .PARAMETER TicketID
        The ID of the ticket to receive comments of
 
    .PARAMETER Ticket
        TANSS.Ticket object to receive comments of
 
    .PARAMETER Token
        The TANSS.Connection token to access api
 
        If not specified, the registered default token from within the module is going to be used
 
    .EXAMPLE
        PS C:\> Get-TANSSTicketComment -TicketID 555
 
        Get all comments from ticket 555
 
    .EXAMPLE
        PS C:\> $tickets | Get-TANSSTicketComment
 
        Get all comments from all tickets from variable $tickets
 
    .NOTES
        Author: Andreas Bellstedt
 
    .LINK
        https://github.com/AndiBellstedt/PSTANSS
 
    #>

    [CmdletBinding(
        DefaultParameterSetName = "ByTicketId",
        SupportsShouldProcess = $false,
        PositionalBinding = $true,
        ConfirmImpact = 'Low'
    )]
    Param(
        [Parameter(
            ParameterSetName = "ByTicketId",
            ValueFromPipelineByPropertyName = $true,
            ValueFromPipeline = $true,
            Mandatory = $true
        )]
        [int[]]
        $TicketID,

        [Parameter(
            ParameterSetName = "ByTicket",
            ValueFromPipelineByPropertyName = $true,
            ValueFromPipeline = $true,
            Mandatory = $true
        )]
        [TANSS.Ticket[]]
        $Ticket,

        [TANSS.Connection]
        $Token
    )

    begin {
        if (-not $Token) { $Token = Get-TANSSRegisteredAccessToken }
        Assert-CacheRunspaceRunning
    }

    process {
        $parameterSetName = $pscmdlet.ParameterSetName
        Write-PSFMessage -Level Debug -Message "ParameterNameSet: $($parameterSetName)"

        if ($parameterSetName -like "ByTicket") {
            $inputobjectTicketCount = ([array]$Ticket).Count
            Write-PSFMessage -Level System -Message "Getting IDs of $($inputobjectTicketCount) ticket$(if($inputobjectTicketCount -gt 1){'s'})"  -Tag "TicketComment", "CollectInputObjects"
            [array]$TicketID = $Ticket.id
        }

        foreach ($ticketIdItem in $TicketID) {
            Write-PSFMessage -Level Verbose -Message "Working on ticket ID $($ticketIdItem)"  -Tag "TicketComment", "Query"

            Get-TANSSTicketContent -TicketID $ticketIdItem -Type "Comment" -Token $Token | Select-Object -ExpandProperty Object
        }

    }

    end {
    }
}


function Get-TANSSTicketContent {
    <#
    .Synopsis
        Get-TANSSTicketContent
 
    .DESCRIPTION
        Retreive the various entries from a ticket.
        Entries can be a comment, activity, mail, document, image
 
    .PARAMETER TicketID
        The ID of the ticket to receive comments of
 
    .PARAMETER Ticket
        TANSS.Ticket object to receive comments of
 
    .PARAMETER Type
        Specifies the type of content you want to get
 
        Available types:
            "All" = everthing within the ticket
            "Comment" = only comments within the ticket
            "Activity" = only activites within the ticket
            "Mail" = = only mails within the ticket
            "Document" = uploaded documents within the ticket
            "Image" = uploaded images within the ticket
 
    .PARAMETER Token
        The TANSS.Connection token to access api
 
        If not specified, the registered default token from within the module is going to be used
 
    .EXAMPLE
        PS C:\> Get-TANSSTicketContent -TicketID 555
 
        Get everthing out of ticket 555
 
    .EXAMPLE
        PS C:\> $tickets | Get-TANSSTicketContent -Type "Mail"
 
        Get only malis out of all tickets from variable $tickets
 
    .NOTES
        Author: Andreas Bellstedt
 
    .LINK
        https://github.com/AndiBellstedt/PSTANSS
    #>

    [CmdletBinding(
        DefaultParameterSetName = "ByTicketId",
        SupportsShouldProcess = $false,
        PositionalBinding = $true,
        ConfirmImpact = 'Low'
    )]
    Param(
        [Parameter(
            ParameterSetName = "ByTicketId",
            ValueFromPipelineByPropertyName = $true,
            ValueFromPipeline = $true,
            Mandatory = $true
        )]
        [int[]]
        $TicketID,

        [Parameter(
            ParameterSetName = "ByTicket",
            ValueFromPipelineByPropertyName = $true,
            ValueFromPipeline = $true,
            Mandatory = $true
        )]
        [TANSS.Ticket[]]
        $Ticket,

        [ValidateNotNullOrEmpty()]
        [ValidateSet("All", "Comment", "Activity", "Mail", "Document", "Image")]
        [string[]]
        $Type = "All",

        [TANSS.Connection]
        $Token
    )

    begin {
        if (-not $Token) { $Token = Get-TANSSRegisteredAccessToken }
        Assert-CacheRunspaceRunning

    }

    process {
        $parameterSetName = $pscmdlet.ParameterSetName
        Write-PSFMessage -Level Debug -Message "ParameterNameSet: $($parameterSetName)"

        if ($parameterSetName -like "ByTicket") {
            $inputobjectTicketCount = ([array]$Ticket).Count
            Write-PSFMessage -Level System -Message "Getting IDs of $($inputobjectTicketCount) ticket$(if($inputobjectTicketCount -gt 1){'s'})"  -Tag "TicketContent", "CollectInputObjects"
            [array]$TicketID = $Ticket.id
        }

        foreach ($ticketIdItem in $TicketID) {
            Write-PSFMessage -Level Verbose -Message "Working on ticket ID $($ticketIdItem)"  -Tag "TicketContent", "Query"
            $content = @()

            # Get documents, if included in Type filter
            if ( ($Type | Where-Object { $_ -in @("All", "Document") }) ) {
                Write-PSFMessage -Level Verbose -Message "Getting documents from ticket $($ticketIdItem)"  -Tag "TicketContent", "Query", "QueryDocuments"

                $apiPath = Format-ApiPath -Path "api/v1/tickets/$ticketIdItem/documents"
                $response = Invoke-TANSSRequest -Type GET -ApiPath $apiPath -Token $Token
                Push-DataToCacheRunspace -MetaData $response.meta

                if ($response.content) {
                    Write-PSFMessage -Level Verbose -Message "Found $($response.content.count) documents"  -Tag "TicketContent", "Query", "QueryDocuments"
                    $content += foreach ($document in $response.content) {
                        # create output objects, but first query download uri

                        # build api path
                        $apiPath = Format-ApiPath -Path "api/v1/tickets/$ticketIdItem/documents/$($document.id)"