TimeCockpit.psm1

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

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

<#
.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 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)
}

<#
.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
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);
    Write-Host $bodyAsJson;
    
    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 a timesheet 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
Modifies a timesheet
 
.DESCRIPTION
 
.PARAMETER Uuid
Uuid of the timesheet to modify
 
.PARAMETER Description
Description to set in the timesheet
 
#>

function Edit-TCTimesheet
{
    [CmdletBinding()]
    Param(
       [Parameter(Mandatory=$true)][Guid]$Uuid,
       [PSCustomObject]$Task,
       [string]$Description
    ) #end param

    if(!$script:token) { Connect-TC -UseCredentialsManager $True; }
    
    $body = @{};
    if($Description) { $body.Add("APP_Description", $Description); }
    if($Task) { $body.Add("APP_TaskUuid", $Task.APP_TaskUuid); }
    $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)});
}

## Private Functions

function CreateFilter($parameters)
{
    $first = $True;
    foreach($parameter in $parameters.Keys)
    {
        $value = $parameters[$parameter];
        
        if(!$First) { $filter = "${filter} and "; } 
        if($value.GetType() -eq [string]) { $filter = "${filter}${parameter} eq '${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;
}