TimeCockpit.psm1

$tokenUri = "https://api.timecockpit.com/token";
$baseUrl = "https://api.timecockpit.com/odata";
$mgmntBaseUrl = "https://management.timecockpit.com";

$tcDBServer="gjmyvebfqv.database.windows.net";
$tcDBDatabase="tcedhrqnfxua";

Add-Type -AssemblyName "System.Web"

<#
.SYNOPSIS
Connects to TimeCockpit.
 
.DESCRIPTION
Credentials can be persisted using https://www.powershellgallery.com/packages/CredentialsManager/ by setting the parameter UseCredentialsManager to $True.
o $Credential -eq $Null -and $UseCredentialsManager -eq $Null -> credentials are requested and are not persisted
o $Credential -ne $Null -and $UseCredentialsManager -eq $Null -> passed credentials are used and not persisted
o $Credential -ne $Null -and $UseCredentialsManager -ne $Null -> passed credentials are used and persisted
o $Credential -eq $Null -and $UseCredentialsManager -ne $Null -> Persisted credentials are used
 
.PARAMETER Credential
Credential to access the OData interface with. I not provided they get requested.
 
.PARAMETER UseCredentialsManager
If $True, credentials are read and written using the CredentialsManager.
#>

function Connect-TC
{
    [CmdletBinding()]
    Param(
       [PSCredential]$Credential,
       [Boolean]$UseCredentialsManager = $False
    ) #end param

    # check module prerequisites
    if($UseCredentialsManager)
    {
        $module = Get-Module -ListAvailable -Name "CredentialsManager";
        if (!$module) { throw "Module 'CredentialsManager' needed. Please install executing 'Install-Module -Name CredentialsManager' as Administrator."; }
    }
    
    $webclient = new-object System.Net.WebClient
    if($UseCredentialsManager -and $Credential -eq $Null)
    {
        $Credential = Read-Credential -ListAvailable | Where { $_.Environment -eq "TimeCockpit" }
    }
    if(!$Credential) { $Credential = Get-Credential -Message "Please enter Credentials for Timecockpit."; }
    if($UseCredentialsManager) 
    { 
        Write-Credential "TimeCockpit" -Credential $Credential; 
    }

    $webclient.Credentials = $Credential;
    $script:token = $webclient.DownloadString($tokenUri);
}

<#
.SYNOPSIS
Connects to TimeCockpit Management API.
 
.DESCRIPTION
Credentials can be persisted using https://www.powershellgallery.com/packages/CredentialsManager/ by setting the parameter UseCredentialsManager to $True.
o $Credential -eq $Null -and $UseCredentialsManager -eq $Null -> credentials are requested and are not persisted
o $Credential -ne $Null -and $UseCredentialsManager -eq $Null -> passed credentials are used and not persisted
o $Credential -ne $Null -and $UseCredentialsManager -ne $Null -> passed credentials are used and persisted
o $Credential -eq $Null -and $UseCredentialsManager -ne $Null -> Persisted credentials are used
 
.PARAMETER Credential
Credential to access the TC Mangement interface with. I not provided they get requested.
 
.PARAMETER UseCredentialsManager
If $True, credentials are read and written using the CredentialsManager.
#>

function Connect-TCMgmnt
{
    [CmdletBinding()]
    Param(
       [PSCredential]$Credential,
       [Boolean]$UseCredentialsManager = $False
    ) #end param

    # check module prerequisites
    if($UseCredentialsManager)
    {
        $module = Get-Module -ListAvailable -Name "CredentialsManager";
        if (!$module) { throw "Module 'CredentialsManager' needed. Please install executing 'Install-Module -Name CredentialsManager' as Administrator."; }
    }
    
    $webclient = new-object System.Net.WebClient
    if($UseCredentialsManager -and $Credential -eq $Null)
    {
        $Credential = Read-Credential -ListAvailable | Where { $_.Environment -eq "TimeCockpit.Mgmnt" }
    }
    if(!$Credential) { $Credential = Get-Credential -Message "Please enter Credentials for Timecockpit Management API."; }
    if($UseCredentialsManager) 
    { 
        Write-Credential "TimeCockpit.Mgmnt" -Credential $Credential; 
    }

    $tokenRequest = "grant_type=client_credentials&client_id=$($Credential.UserName)&client_secret=$($Credential.GetNetworkCredential().password)&scope=management.timecockpit.com mgmt.users"
    
    $script:mgmntToken = Invoke-RestMethod -Method Post -Uri "https://auth.timecockpit.com/connect/token" -Body $tokenRequest -ContentType 'application/x-www-form-urlencoded';
}

<#
.SYNOPSIS
Connects to TimeCockpit Database.
 
.DESCRIPTION
Credentials can be persisted using https://www.powershellgallery.com/packages/CredentialsManager/ by setting the parameter UseCredentialsManager to $True.
Queries are executed using https://www.powershellgallery.com/packages/Invoke-SqlCmd2/
o $Credential -eq $Null -and $UseCredentialsManager -eq $Null -> credentials are requested and are not persisted
o $Credential -ne $Null -and $UseCredentialsManager -eq $Null -> passed credentials are used and not persisted
o $Credential -ne $Null -and $UseCredentialsManager -ne $Null -> passed credentials are used and persisted
o $Credential -eq $Null -and $UseCredentialsManager -ne $Null -> Persisted credentials are used
 
.PARAMETER Credential
Credential to access the database with. I not provided they get requested.
 
.PARAMETER UseCredentialsManager
If $True, credentials are read and written using the CredentialsManager.
#>

function Connect-TCDB
{
    [CmdletBinding()]
    Param(
       [PSCredential]$Credential,
       [Boolean]$UseCredentialsManager = $False
    ) #end param

    # check module prerequisites
    $sqlCmd2Module = Get-Module -ListAvailable -Name "Invoke-SqlCmd2";
    if (!$sqlCmd2Module) { throw "Module 'Invoke-SqlCmd2' needed. Please install executing 'Install-Module -Name Invoke-SqlCmd2' as Administrator."; }
    if($UseCredentialsManager)
    {
        $module = Get-Module -ListAvailable -Name "CredentialsManager";
        if (!$module) { throw "Module 'CredentialsManager' needed. Please install executing 'Install-Module -Name CredentialsManager' as Administrator."; }
    }
    
    if($UseCredentialsManager -and $Credential -eq $Null)
    {
        $Credential = Read-Credential -ListAvailable | Where { $_.Environment -eq "TimeCockpitDB" }
    }
    if(!$Credential) { $Credential = Get-Credential -Message "Please enter Credentials for Timecockpit DB."; }
    if($UseCredentialsManager) 
    { 
        Write-Credential "TimeCockpitDB" -Credential $Credential; 
    }

    $script:DBCredentials = $Credential;
}

function Invoke-TCDBSqlCmd
{
    [CmdletBinding()]
    Param(
       [string]$Query
    ) #end param

    if(!$script:DBCredentials) { Connect-TCDB -UseCredentialsManager $True; }
    
    return (Invoke-SqlCmd2 -ServerInstance $tcDBServer -Database $tcDBDatabase -Credential $script:DBCredentials -Query $Query)
}

########################################
### Mgmnt API ###
########################################

<#
.SYNOPSIS
Returns all users from Management API.
 
#>

function Get-TCMgmntUsers
{
    [CmdletBinding()]
    Param(
    ) #end param

    if(!$script:mgmntToken) { Connect-TCMgmnt -UseCredentialsManager $True; }
    $uri = "${mgmntBaseUrl}/Users";

    return $(Invoke-RestMethod -Uri $uri -Method Get -Headers @{Authorization = "Bearer " + $script:mgmntToken.access_token});
}

########################################
### TC API ###
########################################

<#
.SYNOPSIS
Returns all Global Settings.
#>

function Get-TCGlobalSettings
{
    [CmdletBinding()]
    Param(
    ) #end param

    if(!$script:token) { Connect-TC -UseCredentialsManager $True; }
    
    $uri = "${baseUrl}/APP_GlobalSettings()";

    return $(Invoke-RestMethod -Uri $uri -Method Get -Headers @{Authorization=("Bearer {0}" -f $script:token)}).value;
}

<#
.SYNOPSIS
Sets the Global Settings
 
.DESCRIPTION
 
.PARAMETER Uuid
Uuid of the GlobalSettings to modify
 
.PARAMETER ShowOvertimeInTimesheetCalendar
 
.PARAMETER ShowRemainingVacationsInTimesheetCalendar
 
#>

function Edit-TCGlobalSettings
{
    [CmdletBinding()]
    Param(
       [Parameter(Mandatory=$true)][Guid]$Uuid,
       [Nullable[boolean]]$ShowOvertimeInTimesheetCalendar,
       [Nullable[boolean]]$ShowRemainingVacationsInTimesheetCalendar
    ) #end param

    if(!$script:token) { Connect-TC -UseCredentialsManager $True; }
    
    $body = @{};
    if($ShowOvertimeInTimesheetCalendar -ne $Null) { $body.Add("APP_ShowOvertimeInTimesheetCalendar", $ShowOvertimeInTimesheetCalendar); }
    if($ShowRemainingVacationsInTimesheetCalendar -ne $Null) { $body.Add("APP_ShowRemainingVacationsInTimesheetCalendar", $ShowRemainingVacationsInTimesheetCalendar); }

    $bodyAsJson = ConvertTo-Json($body);
    
    return $(Invoke-RestMethod -Uri "${baseUrl}/APP_GlobalSettings(guid'${Uuid}')" -Method Patch -Body $bodyAsJson -ContentType "application/json; charset=utf-8" -Headers @{Authorization=("Bearer {0}" -f $script:token)});
}

<#
.SYNOPSIS
Adds a country to Timecockpit.
 
.DESCRIPTION
 
.PARAMETER IsoCode
2 Digits ISO Code of the country
 
.PARAMETER CountryName
Name of the country
#>

function Add-TCCountry
{
    [CmdletBinding()]
    Param(
       [Parameter(Mandatory=$true)][string]$IsoCode,
       [Parameter(Mandatory=$true)][string]$CountryName
    ) #end param

    if(!$script:token) { Connect-TC -UseCredentialsManager $True; }
    
    $bodyAsJson = ConvertTo-JSON( @{ APP_CountryName=${CountryName}; APP_IsoCode=${IsoCode}; });

    return $(Invoke-RestMethod -Uri "${baseUrl}/APP_Country" -Method Post -Body $bodyAsJson -ContentType "application/json; charset=utf-8" -Headers @{Authorization=("Bearer {0}" -f $script:token)});
}

<#
.SYNOPSIS
Returns customers from Timecockpit.
 
.DESCRIPTION
All parameters are optional filters. Executing with no parameter returns all customers.
 
.PARAMETER Code
Code of Timecockpit-Customer to filter for
 
.PARAMETER Uuid
Uuid of Timecockpit-Customer to filter for
 
#>

function Get-TCCustomer
{
    [CmdletBinding()]
    Param(
       [string]$Code,
       [Guid]$Uuid
    ) #end param

    if(!$script:token) { Connect-TC -UseCredentialsManager $True; }
    
    $uri = "${baseUrl}/APP_Customer()";
    $filterParams = @{};
    if($code) { $filterParams.Add("APP_Code", $Code); }
    if($uuid) { $filterParams.Add("APP_CustomerUuid", $Uuid); }

    if($filterParams.Count -gt 0) { $uri = "${uri}?`$filter=$(CreateFilter($filterParams))"; }
    return $(Invoke-RestMethod -Uri $uri -Method Get -Headers @{Authorization=("Bearer {0}" -f $script:token)}).value;
}

<#
.SYNOPSIS
Returns projects from Timecockpit.
 
.DESCRIPTION
All parameters are optional filters. Executing with no parameter returns all projects.
 
.PARAMETER CustomerCode
Code of Timecockpit-Customer to filter projects for
 
.PARAMETER CustomerUuid
Uuid of Timecockpit-Customer to filter projects for
 
.PARAMETER Code
Code of Timecockpit-Project to filter projects for
 
.PARAMETER Uuid
Uuid of Timecockpit-Customer to filter projects for
 
.PARAMETER Closed
By default only open projects are returned. (Closed=False) Set to null to return closed and open projects.
#>

function Get-TCProject
{
    [CmdletBinding()]
    Param(
       [string]$CustomerCode,
       [Guid]$CustomerUuid,
       [string]$Code,
       [Guid]$Uuid,
       [Nullable[boolean]]$Closed=$False
    ) #end param

    if(!$script:token) { Connect-TC -UseCredentialsManager $True; }
    
    $uri = "${baseUrl}/APP_Project()";
    $filterParams = @{};
    if($CustomerCode) { $filterParams.Add("APP_Customer/APP_Code", $CustomerCode); }
    if($CustomerUuid) { $filterParams.Add("APP_Customer/APP_CustomerUuid", $CustomerUuid); }
    if($Code) { $filterParams.Add("APP_Code", $Code); }
    if($Uuid) { $filterParams.Add("APP_ProjectUuid", $Uuid); }
    if($Closed -ne $Null) { $filterParams.Add("APP_Closed", $Closed); }

    if($filterParams.Count -gt 0) { $uri = "${uri}?`$filter=$(CreateFilter($filterParams))"; }
    return $(Invoke-RestMethod -Uri $uri -Method Get -Headers @{Authorization=("Bearer {0}" -f $script:token)}).value;
}

<#
.SYNOPSIS
Adds a Project
 
.DESCRIPTION
 
.PARAMETER Code
Code of the new project
 
.PARAMETER ProjectName
ProjectName of the new project
 
.PARAMETER StartDate
StartDate of project. Defaults to current date
 
.PARAMETER EndDate
EndDate of project. Defaults to current date
 
.PARAMETER Budget
Budget of new project
 
.PARAMETER BudgetInHours
Budget in hours of new project
 
.PARAMETER HourlyRate
HourlyRate to set in the project. Leave empty to not set and inherit from customer
 
.PARAMETER FixedPrice
If Project is fixed price or not. Defaults to $False
 
#>

function Add-TCProject
{
    [CmdletBinding()]
    Param(
       [Parameter(Mandatory=$true)][Guid]$CustomerUuid,
       [Parameter(Mandatory=$true)][string]$Code,
       [Parameter(Mandatory=$true)][string]$ProjectName,
       [DateTime]$StartDate,
       [DateTime]$EndDate,
       [float]$Budget,
       [float]$BudgetInHours,
       [float]$HourlyRate,
       [boolean]$FixedPrice
    ) #end param

    if(!$script:token) { Connect-TC -UseCredentialsManager $True; }
    
    $culture = New-Object System.Globalization.CultureInfo("en-US");

    $body = @{ APP_CustomerUuid=$CustomerUuid; APP_Code=$Code; APP_ProjectName=$ProjectName};
    if($StartDate) { $body.Add("APP_StartDate", $StartDate.ToString('yyyy-MM-dd')); }
    if($EndDate) { $body.Add("APP_EndDate", $EndDate.ToString('yyyy-MM-dd')); }
    if($Budget) { $body.Add("APP_Budget", $Budget.ToString($culture)); }
    if($BudgetInHours) { $body.Add("APP_BudgetInHours", $BudgetInHours.ToString($culture)); }
    if($HourlyRate) { $body.Add("APP_HourlyRate", $HourlyRate.ToString($culture)); }
    if($FixedPrice) { $body.Add("APP_FixedPrice", $FixedPrice); }

    $bodyAsJson = ConvertTo-Json($body);

    return $(Invoke-RestMethod -Uri "${baseUrl}/APP_Project" -Method Post -Body $bodyAsJson -ContentType "application/json; charset=utf-8" -Headers @{Authorization=("Bearer {0}" -f $script:token)});
}

<#
.SYNOPSIS
Modifies a Project
 
.DESCRIPTION
 
.PARAMETER Uuid
Uuid of the project to modify
 
.PARAMETER Code
Code to set in the project
 
.PARAMETER Description
Description to set in the project
 
.PARAMETER Closed
Set the closed status of the project
 
.PARAMETER HourlyRate
HourlyRate to set in the project
#>

function Edit-TCProject
{
    [CmdletBinding()]
    Param(
       [Parameter(Mandatory=$true)][Guid]$Uuid,
       [string]$Code,
       [string]$Description,
       [Nullable[boolean]]$Closed,
       [float]$HourlyRate
    ) #end param

    if(!$script:token) { Connect-TC -UseCredentialsManager $True; }
    
    $body = @{};
    if($Code) { $body.Add("APP_Code", $Code); }
    if($Description) { $body.Add("APP_Description", $Description); }
    if($Closed -ne $Null) { $body.Add("APP_Closed", $Closed); }
    if($HourlyRate) 
    { 
        $culture = New-Object System.Globalization.CultureInfo("en-US");
        $body.Add("APP_HourlyRate", $HourlyRate.ToString($culture)); 
    }
    $bodyAsJson = ConvertTo-Json($body);
    
    return $(Invoke-RestMethod -Uri "${baseUrl}/APP_Project(guid'${Uuid}')" -Method Patch -Body $bodyAsJson -ContentType "application/json; charset=utf-8" -Headers @{Authorization=("Bearer {0}" -f $script:token)});
}

<#
.SYNOPSIS
Closes a Project
 
.DESCRIPTION
If the project is already closed, no error is created, its just kept closed.
 
.PARAMETER Uuid
Uuid of the project to close
#>

function Close-TCProject
{
    [CmdletBinding()]
    Param(
       [Parameter(Mandatory=$true)][Guid]$Uuid
    ) #end param

    if(!$script:token) { Connect-TC -UseCredentialsManager $True; }
    
    $body = "{ 'APP_Closed' : 1 }";

    return $(Invoke-RestMethod -Uri "${baseUrl}/APP_Project(guid'${Uuid}')" -Method Patch -Body $body -ContentType "application/json; charset=utf-8" -Headers @{Authorization=("Bearer {0}" -f $script:token)});
}

<#
.SYNOPSIS
Opens a Project
 
.DESCRIPTION
If the project is already open, no error is created, its just kept open.
 
.PARAMETER Uuid
Uuid of the project to open
#>

function Open-TCProject
{
    [CmdletBinding()]
    Param(
       [Parameter(Mandatory=$true)][Guid]$Uuid
    ) #end param

    if(!$script:token) { Connect-TC -UseCredentialsManager $True; }
    
    $body = "{ 'APP_Closed' : 0 }";

    return $(Invoke-RestMethod -Uri "${baseUrl}/APP_Project(guid'${Uuid}')" -Method Patch -Body $body -ContentType "application/json; charset=utf-8" -Headers @{Authorization=("Bearer {0}" -f $script:token)});
}

<#
.SYNOPSIS
Adds a Task to a project.
 
.DESCRIPTION
Use Get-TCProject for receiving the Uuid of a project.
 
.PARAMETER ProjectUuid
Uuid of the project
 
.PARAMETER Code
The code of the task to add
 
.PARAMETER Description
The Description to set in the task
#>

function Add-TCTask
{
    [CmdletBinding()]
    Param(
       [Parameter(Mandatory=$true)][Guid]$ProjectUuid,
       [Parameter(Mandatory=$true)][string]$Code,
       [string]$Description = ""
    ) #end param

    if(!$script:token) { Connect-TC -UseCredentialsManager $True; }
    
    $bodyAsJson = ConvertTo-JSON( @{ APP_ProjectUuid=${ProjectUuid}; APP_Code=${Code}; APP_Description=${Description} });

    return $(Invoke-RestMethod -Uri "${baseUrl}/APP_Task" -Method Post -Body $bodyAsJson -ContentType "application/json; charset=utf-8" -Headers @{Authorization=("Bearer {0}" -f $script:token)});
}

<#
.SYNOPSIS
Returns tasks from a project.
 
.DESCRIPTION
All parameters are optional filters. Executing with no parameter returns all tasks.
Use Get-TCProject for receiving the Uuid of a project.
 
.PARAMETER ProjectUuid
Uuid of project to get tasks from
 
.PARAMETER Code
Code of the task
 
.PARAMETER Uuid
Uuid of the task
#>

function Get-TCTask
{
    [CmdletBinding()]
    Param(
       [Guid]$ProjectUuid,
       [string]$Code,
       [Guid]$Uuid
   ) #end param

    if(!$script:token) { Connect-TC -UseCredentialsManager $True; }
    
    $uri = "${baseUrl}/APP_Task()";
    $filterParams = @{};
    if($ProjectUuid) { $filterParams.Add("APP_Project/APP_ProjectUuid", $ProjectUuid); }
    if($Code) { $filterParams.Add("APP_Code", $Code); }
    if($Uuid) { $filterParams.Add("APP_TaskUuid", $Uuid); }

    if($filterParams.Count -gt 0) { $uri = "${uri}?`$filter=$(CreateFilter($filterParams))"; }
    return $(Invoke-RestMethod -Uri $uri -Method Get -Headers @{Authorization=("Bearer {0}" -f $script:token)}).value;
}

<#
.SYNOPSIS
Modifies a Task
 
.DESCRIPTION
 
.PARAMETER Uuid
Uuid of the task to modify
 
.PARAMETER Code
Code to set in the task
 
.PARAMETER Description
Description to set in the task
#>

function Edit-TCTask
{
    [CmdletBinding()]
    Param(
       [Parameter(Mandatory=$true)][Guid]$Uuid,
       [string]$Code,
       [string]$Description,
       [Nullable[boolean]]$Closed
    ) #end param

    if(!$script:token) { Connect-TC -UseCredentialsManager $True; }
    
    $body = @{};
    if($Code) { $body.Add("APP_Code", $Code); }
    if($Description) { $body.Add("APP_Description", $Description); }
    if($Closed -ne $Null) { $body.Add("APP_Closed", $Closed); }
    $bodyAsJson = ConvertTo-Json($body);
    
    return $(Invoke-RestMethod -Uri "${baseUrl}/APP_Task(guid'${Uuid}')" -Method Patch -Body $bodyAsJson -ContentType "application/json; charset=utf-8" -Headers @{Authorization=("Bearer {0}" -f $script:token)});
}

<#
.SYNOPSIS
Closes a Task
 
.DESCRIPTION
If the task is already closed, no error is created, its just kept closed.
 
.PARAMETER Uuid
Uuid of the task to close
#>

function Close-TCTask
{
    [CmdletBinding()]
    Param(
       [Parameter(Mandatory=$true)][Guid]$Uuid
    ) #end param

    if(!$script:token) { Connect-TC -UseCredentialsManager $True; }
    
    $body = "{ 'APP_Closed' : 1 }";

    return $(Invoke-RestMethod -Uri "${baseUrl}/APP_Task(guid'${Uuid}')" -Method Patch -Body $body -ContentType "application/json; charset=utf-8" -Headers @{Authorization=("Bearer {0}" -f $script:token)});
}

<#
.SYNOPSIS
Opens a Task
 
.DESCRIPTION
If the task is already open, no error is created, its just kept open.
 
.PARAMETER Uuid
Uuid of the task to open
#>

function Open-TCTask
{
    [CmdletBinding()]
    Param(
       [Parameter(Mandatory=$true)][Guid]$Uuid
    ) #end param

    if(!$script:token) { Connect-TC -UseCredentialsManager $True; }
    
    $body = "{ 'APP_Closed' : 0 }";

    return $(Invoke-RestMethod -Uri "${baseUrl}/APP_Task(guid'${Uuid}')" -Method Patch -Body $body -ContentType "application/json; charset=utf-8" -Headers @{Authorization=("Bearer {0}" -f $script:token)});
}

<#
.SYNOPSIS
Returns matching timesheet entries from timecockpit.
 
.DESCRIPTION
 
.PARAMETER ProjectUuid
Optional Filter, Uuid of to project to get all timesheet entries for.
 
.PARAMETER BeginTime
Optional Filter, get timesheet entries having a BeginTime later than the parameter.
 
.PARAMETER EndTime
Optional Filter, get timesheet entries having a EndTime before the parameter.
 
.PARAMETER TaskUuid
Optional Filter, Uuid of to task to get all timesheet entries for.
 
#>

function Get-TCTimesheets
{
    [CmdletBinding()]
    Param(
       [Guid]$ProjectUuid,
       [Nullable[DateTime]] $BeginTime,
       [Nullable[DateTime]] $EndTime,
       [Guid]$TaskUuid
   ) #end param

    if(!$script:token) { Connect-TC -UseCredentialsManager $True; }
    
    $uri = "${baseUrl}/APP_Timesheet()";
    $filterParams = @();
    if($ProjectUuid) 
    { 
        $filter = [pscustomobject][ordered]@{ 
            Criteria="APP_Project/APP_ProjectUuid"; 
            Operation="eq";
            Value=$ProjectUuid
        }
        $filterParams += $filter; 
    }
    if($BeginTime) 
    { 
        $filter = [pscustomobject][ordered]@{ 
            Criteria="APP_BeginTime"; 
            Operation="gt";
            Value=$BeginTime
        }
        $filterParams += $filter; 
    }
    if($EndTime) 
    { 
        $filter = [pscustomobject][ordered]@{ 
            Criteria="APP_EndTime"; 
            Operation="lt";
            Value=$EndTime
        }
        $filterParams += $filter; 
    }
    if($TaskUuid) 
    { 
        $filter = [pscustomobject][ordered]@{ 
            Criteria="APP_Task/APP_TaskUuid"; 
            Operation="eq";
            Value=$TaskUuid
        }
        $filterParams += $filter; 
    }

    if($filterParams.Count -gt 0) { $uri = "${uri}?`$filter=$(CreateFilterEx($filterParams))"; }
    
    return $(Invoke-RestMethod -Uri $uri -Method Get -Headers @{Authorization=("Bearer {0}" -f $script:token)}).value;
}

<#
.SYNOPSIS
Returns a timesheet entry from timecockpit.
 
.DESCRIPTION
 
.PARAMETER Uuid
Uuid of to timesheet
 
#>

function Get-TCTimesheet
{
    [CmdletBinding()]
    Param(
       [Guid]$Uuid
   ) #end param

    if(!$script:token) { Connect-TC -UseCredentialsManager $True; }
    
    $uri = "${baseUrl}/APP_Timesheet()";
    $filterParams = @{};
    if($Uuid) { $filterParams.Add("APP_TimesheetUuid", $Uuid); }

    if($filterParams.Count -gt 0) { $uri = "${uri}?`$filter=$(CreateFilter($filterParams))"; }
    return $(Invoke-RestMethod -Uri $uri -Method Get -Headers @{Authorization=("Bearer {0}" -f $script:token)}).value;
}

<#
.SYNOPSIS
Adds a timesheet entry.
 
.DESCRIPTION
 
.PARAMETER ProjectUuid
Optional. Uuid of Project to create entry in
 
.PARAMETER TaskUuid
Optional. Uuid of Task to create entry in
 
.PARAMETER UserDetailUuid
Uuid of User to create entry for
 
.PARAMETER BeginTime
DateTime to start entry
 
.PARAMETER EndTime
DateTime to end entry
 
.PARAMETER Description
Description text of entry
#>

function Add-TCTimesheet
{
    [CmdletBinding()]
    Param(
       [Guid]$ProjectUuid,
       [Guid]$TaskUuid,
       [Parameter(Mandatory=$true)][Guid]$UserDetailUuid,
       [Parameter(Mandatory=$true)][datetime]$BeginTime,
       [Parameter(Mandatory=$true)][datetime]$EndTime,
       [Parameter(Mandatory=$true)][string]$Description
    ) #end param

    if(!$script:token) { Connect-TC -UseCredentialsManager $True; }
    
    $body = @{ APP_UserDetailUuid=$UserDetailUuid; APP_BeginTime=$BeginTime.ToString('yyyy-MM-ddTHH:mm'); APP_EndTime=$EndTime.ToString('yyyy-MM-ddTHH:mm'); APP_Description=$Description; }
    if($ProjectUuid) { $body.Add("APP_ProjectUuid", $ProjectUuid) }
    if($TaskUuid) { $body.Add("APP_TaskUuid", $TaskUuid) }

    $bodyAsJson = ConvertTo-JSON( $body );

    return $(Invoke-RestMethod -Uri "${baseUrl}/APP_Timesheet" -Method Post -Body $bodyAsJson -ContentType "application/json; charset=utf-8" -Headers @{Authorization=("Bearer {0}" -f $script:token)});
}

<#
.SYNOPSIS
Modifies a timesheet entry
 
.DESCRIPTION
 
.PARAMETER Uuid
Uuid of the timesheet to modify
 
.PARAMETER ProjectUuid
Optional. Uuid of project to set in the timesheet
 
.PARAMETER TaskUuid
Optional. Uuid of task to set in the timesheet. Must be within the current project of the task or the project given in $ProjectUuid.
 
.PARAMETER Description
Description to set in the timesheet
 
#>

function Edit-TCTimesheet
{
    [CmdletBinding()]
    Param(
       [Parameter(Mandatory=$true)][Guid]$Uuid,
       [Guid]$ProjectUuid,
       [Guid]$TaskUuid,
       [string]$Description,
       [Nullable[float]]$HourlyRate
    ) #end param

    if(!$script:token) { Connect-TC -UseCredentialsManager $True; }
    
    $culture = New-Object System.Globalization.CultureInfo("en-US");

    $body = @{};
    if($Description) { $body.Add("APP_Description", $Description); }
    if($ProjectUuid) { $body.Add("APP_ProjectUuid", $ProjectUuid); }
    if($TaskUuid) { $body.Add("APP_TaskUuid", $TaskUuid); }
    if($HourlyRate -ne $Null) { $body.Add("APP_HourlyRate", $HourlyRate.ToString($culture)); }
    $bodyAsJson = ConvertTo-Json($body);
    
    return $(Invoke-RestMethod -Uri "${baseUrl}/APP_Timesheet(guid'${Uuid}')" -Method Patch -Body $bodyAsJson -ContentType "application/json; charset=utf-8" -Headers @{Authorization=("Bearer {0}" -f $script:token)});
}

<#
.SYNOPSIS
Removes a timesheet entry
 
.DESCRIPTION
 
.PARAMETER Uuid
Uuid of the timesheet to remove
 
#>

function Remove-TCTimesheet
{
    [CmdletBinding()]
    Param(
       [Parameter(Mandatory=$true)][Guid]$Uuid
    ) #end param

    if(!$script:token) { Connect-TC -UseCredentialsManager $True; }
    
    return $(Invoke-RestMethod -Uri "${baseUrl}/APP_Timesheet(guid'${Uuid}')" -Method Delete -ContentType "application/json; charset=utf-8" -Headers @{Authorization=("Bearer {0}" -f $script:token)});
}

## Private Functions

function CreateFilter($parameters)
{
    $first = $True;
    $filter = "";
    foreach($parameter in $parameters.Keys)
    {
        $value = $parameters[$parameter];
        
        if(!$First) { $filter = "${filter} and "; } 
        if($value.GetType() -eq [string]) { $filter = "${filter}${parameter} eq '$([System.Web.HTTPUtility]::UrlEncode($value))'"; }
        elseif($value.GetType() -eq [Guid]) { $filter = "${filter}${parameter} eq guid'${value}'"; }
        elseif($value.GetType() -eq [Boolean]) { $filter = "${filter}${parameter} eq $(([string]$value).ToLower())"; }
        else { $filter = "${filter}${parameter} eq ${value}"; }

        $First = $False;
    }
    return $filter;
}

function CreateFilterEx($parameters)
{
    $first = $True;
    $filter = "";
    foreach($parameter in $parameters)
    {
        $criteria = $parameter.Criteria;
        $operation = $parameter.Operation;
        $value = $parameter.Value;
        
        if(!$First) { $filter = "${filter} and "; } 
        if($value.GetType() -eq [string]) { $filter = "${filter}${criteria} ${operation} '$([System.Web.HTTPUtility]::UrlEncode($value))'"; }
        elseif($value.GetType() -eq [Guid]) { $filter = "${filter}${criteria} ${operation} guid'${value}'"; }
        elseif($value.GetType() -eq [Boolean]) { $filter = "${filter}${criteria} ${operation} $(([string]$value).ToLower())"; }
        elseif($value.GetType() -eq [DateTime]) { $filter = "${filter}${criteria} ${operation} datetime'$($value.ToString('yyyy-MM-ddTHH:mm:ss'))'"; }
        else { $filter = "${filter}${criteria} ${operation} ${value}"; }

        $First = $False;
    }
    return $filter;
}