diff --git a/.gitattributes b/.gitattributes
new file mode 100644
index 0000000000000000000000000000000000000000..1ff0c423042b46cb1d617b81efb715defbe8054d
--- /dev/null
+++ b/.gitattributes
@@ -0,0 +1,63 @@
+###############################################################################
+# Set default behavior to automatically normalize line endings.
+###############################################################################
+* text=auto
+
+###############################################################################
+# Set default behavior for command prompt diff.
+#
+# This is need for earlier builds of msysgit that does not have it on by
+# default for csharp files.
+# Note: This is only used by command line
+###############################################################################
+#*.cs     diff=csharp
+
+###############################################################################
+# Set the merge driver for project and solution files
+#
+# Merging from the command prompt will add diff markers to the files if there
+# are conflicts (Merging from VS is not affected by the settings below, in VS
+# the diff markers are never inserted). Diff markers may cause the following 
+# file extensions to fail to load in VS. An alternative would be to treat
+# these files as binary and thus will always conflict and require user
+# intervention with every merge. To do so, just uncomment the entries below
+###############################################################################
+#*.sln       merge=binary
+#*.csproj    merge=binary
+#*.vbproj    merge=binary
+#*.vcxproj   merge=binary
+#*.vcproj    merge=binary
+#*.dbproj    merge=binary
+#*.fsproj    merge=binary
+#*.lsproj    merge=binary
+#*.wixproj   merge=binary
+#*.modelproj merge=binary
+#*.sqlproj   merge=binary
+#*.wwaproj   merge=binary
+
+###############################################################################
+# behavior for image files
+#
+# image files are treated as binary by default.
+###############################################################################
+#*.jpg   binary
+#*.png   binary
+#*.gif   binary
+
+###############################################################################
+# diff behavior for common document formats
+# 
+# Convert binary document formats to text before diffing them. This feature
+# is only available from the command line. Turn it on by uncommenting the 
+# entries below.
+###############################################################################
+#*.doc   diff=astextplain
+#*.DOC   diff=astextplain
+#*.docx  diff=astextplain
+#*.DOCX  diff=astextplain
+#*.dot   diff=astextplain
+#*.DOT   diff=astextplain
+#*.pdf   diff=astextplain
+#*.PDF   diff=astextplain
+#*.rtf   diff=astextplain
+#*.RTF   diff=astextplain
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..8d72a0b63cf3c83397169fc7d3561717c916b488
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,273 @@
+## Ignore Visual Studio temporary files, build results, and
+## files generated by popular Visual Studio add-ons.
+
+# User-specific files
+*.suo
+*.user
+*.userosscache
+*.sln.docstates
+
+# User-specific files (MonoDevelop/Xamarin Studio)
+*.userprefs
+
+# Database connection for t4 class generation from database
+**/*.generated.cs
+
+# Build results
+[Dd]ebug/
+[Dd]ebugPublic/
+[Rr]elease/
+[Rr]eleases/
+x64/
+x86/
+bld/
+[Bb]in/
+[Oo]bj/
+[Ll]og/
+
+# Visual Studio 2015 cache/options directory
+.vs/
+# Uncomment if you have tasks that create the project's static files in wwwroot
+#wwwroot/
+
+# MSTest test Results
+[Tt]est[Rr]esult*/
+[Bb]uild[Ll]og.*
+
+# NUNIT
+*.VisualState.xml
+TestResult.xml
+
+# Build Results of an ATL Project
+[Dd]ebugPS/
+[Rr]eleasePS/
+dlldata.c
+
+# DNX
+project.lock.json
+project.fragment.lock.json
+artifacts/
+
+*_i.c
+*_p.c
+*_i.h
+*.ilk
+*.meta
+*.obj
+*.pch
+*.pdb
+*.pgc
+*.pgd
+*.rsp
+*.sbr
+*.tlb
+*.tli
+*.tlh
+*.tmp
+*.tmp_proj
+*.log
+*.vspscc
+*.vssscc
+.builds
+*.pidb
+*.svclog
+*.scc
+
+# Chutzpah Test files
+_Chutzpah*
+
+# Visual C++ cache files
+ipch/
+*.aps
+*.ncb
+*.opendb
+*.opensdf
+*.sdf
+*.cachefile
+*.VC.db
+*.VC.VC.opendb
+
+# Visual Studio profiler
+*.psess
+*.vsp
+*.vspx
+*.sap
+
+# TFS 2012 Local Workspace
+$tf/
+
+# Guidance Automation Toolkit
+*.gpState
+
+# ReSharper is a .NET coding add-in
+_ReSharper*/
+*.[Rr]e[Ss]harper
+*.DotSettings.user
+Resharper
+
+# JustCode is a .NET coding add-in
+.JustCode
+
+# TeamCity is a build add-in
+_TeamCity*
+
+# DotCover is a Code Coverage Tool
+*.dotCover
+
+# NCrunch
+_NCrunch_*
+.*crunch*.local.xml
+nCrunchTemp_*
+
+# MightyMoose
+*.mm.*
+AutoTest.Net/
+
+# Web workbench (sass)
+.sass-cache/
+
+# Installshield output folder
+[Ee]xpress/
+
+# DocProject is a documentation generator add-in
+DocProject/buildhelp/
+DocProject/Help/*.HxT
+DocProject/Help/*.HxC
+DocProject/Help/*.hhc
+DocProject/Help/*.hhk
+DocProject/Help/*.hhp
+DocProject/Help/Html2
+DocProject/Help/html
+
+# Click-Once directory
+publish/
+
+# Publish Web Output
+*.[Pp]ublish.xml
+*.azurePubxml
+# TODO: Comment the next line if you want to checkin your web deploy settings
+# but database connection strings (with potential passwords) will be unencrypted
+#*.pubxml
+*.publishproj
+
+# Microsoft Azure Web App publish settings. Comment the next line if you want to
+# checkin your Azure Web App publish settings, but sensitive information contained
+# in these scripts will be unencrypted
+PublishScripts/
+
+# NuGet Packages
+*.nupkg
+# The packages folder can be ignored because of Package Restore
+**/packages/*
+# except build/, which is used as an MSBuild target.
+!**/packages/build/
+# Uncomment if necessary however generally it will be regenerated when needed
+#!**/packages/repositories.config
+# NuGet v3's project.json files produces more ignoreable files
+*.nuget.props
+*.nuget.targets
+
+# Microsoft Azure Build Output
+csx/
+*.build.csdef
+
+# Microsoft Azure Emulator
+ecf/
+rcf/
+
+# Windows Store app package directories and files
+AppPackages/
+BundleArtifacts/
+Package.StoreAssociation.xml
+_pkginfo.txt
+
+# Visual Studio cache files
+# files ending in .cache can be ignored
+*.[Cc]ache
+# but keep track of directories ending in .cache
+!*.[Cc]ache/
+
+# Others
+ClientBin/
+~$*
+*~
+*.dbmdl
+*.dbproj.schemaview
+*.jfm
+*.pfx
+*.publishsettings
+node_modules/
+orleans.codegen.cs
+
+# Since there are multiple workflows, uncomment next line to ignore bower_components
+# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
+#bower_components/
+
+# RIA/Silverlight projects
+Generated_Code/
+
+# Backup & report files from converting an old project file
+# to a newer Visual Studio version. Backup files are not needed,
+# because we have git ;-)
+_UpgradeReport_Files/
+Backup*/
+UpgradeLog*.XML
+UpgradeLog*.htm
+
+# SQL Server files
+*.mdf
+*.ldf
+
+# Business Intelligence projects
+*.rdl.data
+*.bim.layout
+*.bim_*.settings
+
+# Microsoft Fakes
+FakesAssemblies/
+
+# GhostDoc plugin setting file
+*.GhostDoc.xml
+
+# Node.js Tools for Visual Studio
+.ntvs_analysis.dat
+
+# Visual Studio 6 build log
+*.plg
+
+# Visual Studio 6 workspace options file
+*.opt
+
+# Visual Studio LightSwitch build output
+**/*.HTMLClient/GeneratedArtifacts
+**/*.DesktopClient/GeneratedArtifacts
+**/*.DesktopClient/ModelManifest.xml
+**/*.Server/GeneratedArtifacts
+**/*.Server/ModelManifest.xml
+_Pvt_Extensions
+
+# Paket dependency manager
+.paket/paket.exe
+paket-files/
+
+# FAKE - F# Make
+.fake/
+
+# JetBrains Rider
+.idea/
+*.sln.iml
+
+# CodeRush
+.cr/
+
+# Python Tools for Visual Studio (PTVS)
+__pycache__/
+*.pyc
+
+#cake
+tools/*
+!tools/packages.config
+dist/
+
+#linq2db
+src/.tools/
\ No newline at end of file
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
new file mode 100644
index 0000000000000000000000000000000000000000..b339a45c43a1dcb4b80ab99e6224bc7232abd723
--- /dev/null
+++ b/.gitlab-ci.yml
@@ -0,0 +1,55 @@
+stages:
+  - test
+  - release
+  - releasetrigger
+
+cake:Test:
+  stage: test
+  script:
+    - PowerShell .\build.ps1 -Target Test -Configuration Debug
+  variables:
+    GIT_STRATEGY: clone
+  artifacts:
+    reports:
+      junit: "./Artifacts/TestResults.xml"
+    paths:
+      - "./Artifacts/*"
+  except:
+    - master
+    - tags
+
+cake:Release:
+  stage: release
+  script:
+    - PowerShell .\build.ps1 -Target Release -Configuration Release --nugetApiKey="${NUGET_API_KEY}"
+  variables:
+    GIT_STRATEGY: clone
+  dependencies:
+    - cake:Test
+  artifacts:
+    paths:
+      - "./Artifacts/*"
+  only:
+    - tags
+
+cake:Prerelease:
+  stage: release
+  script:
+    - PowerShell .\build.ps1 -Target Prerelease -Configuration Release
+  variables:
+    GIT_STRATEGY: clone
+  dependencies:
+    - cake:Test
+  artifacts:
+    paths:
+      - "./Artifacts/*"
+  except:
+    - tags
+    - master
+
+cake:GitlabRelease:
+    stage: releasetrigger
+    script:
+      - PowerShell .\build.ps1 -Target GitlabRelease --GitlabProjectPath="${CI_PROJECT_PATH}" --gitlabProjectId="${CI_PROJECT_ID}" --gitlabToken="${GITLAB_TOKEN}"
+    only:
+      - master
\ No newline at end of file
diff --git a/GitVersion.yml b/GitVersion.yml
new file mode 100644
index 0000000000000000000000000000000000000000..1bdc0fe219f5f35d8a024a66d9fd715f44539640
--- /dev/null
+++ b/GitVersion.yml
@@ -0,0 +1,7 @@
+mode: ContinuousDeployment
+next-version: 1.0.0
+major-version-bump-message: 'Breaking:'
+minor-version-bump-message: '(Update|New):'
+patch-version-bump-message: 'Fix:'
+no-bump-message: '.*'
+commit-message-incrementing: Enabled
\ No newline at end of file
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000000000000000000000000000000000000..1cacbda86167c5f5629e9d3f8605295ad009460f
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2019 RWTH Aachen University
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
\ No newline at end of file
diff --git a/README.md b/README.md
index 06db01c5482fc6c2f4372a99f6dca0fd72a4a610..36ca3bfcbff4045a7130eac8c8d49aa561d9d377 100644
--- a/README.md
+++ b/README.md
@@ -1,3 +1,28 @@
-# Kpi Generator
+## ProxyApi
 
-Generator gathering data for the KPIs for sending them to the web hamster.
\ No newline at end of file
+This Api can access the RWTHAppProxy and OAuth2 of the RWTH Aachen University.
+
+Furthermore some helper classes exists which can verify tokens for accessing certain Api methods.
+
+The following configuration keys are currently settable:
+
+* "coscine/global/context/metadata/scope"
+* "coscine/global/context/metadata/serviceid"
+* "coscine/global/context/coscine/scope"
+* "coscine/global/context/coscine/serviceid"
+* "coscine/local/proxytest/refreshtoken" <= A valid refresh token for testing
+* "coscine/global/epic/prefix"
+* "coscine/global/epic/url"
+* "coscine/global/epic/user"
+* "coscine/global/epic/password"
+
+## Building
+
+Build this project by running either the build.ps1 or the build<span></span>.sh script.
+The project will be build and tested.
+
+### Links 
+
+*  [Commit convention](docs/ESLintConvention.md)
+*  [Everything possible with markup](docs/testdoc.md)
+*  [Adding NUnit tests](docs/nunit.md)
\ No newline at end of file
diff --git a/build.cake b/build.cake
new file mode 100644
index 0000000000000000000000000000000000000000..81648ff61bbee492e1bc7b4390eb8e0cc33d3056
--- /dev/null
+++ b/build.cake
@@ -0,0 +1,351 @@
+#tool nuget:?package=NUnit.ConsoleRunner&version=3.10.0
+#tool nuget:?package=vswhere&version=2.8.4
+#tool nuget:?package=GitVersion.CommandLine&version=5.1.3
+
+#addin nuget:https://api.nuget.org/v3/index.json?package=Cake.Json&version=4.0.0
+#addin nuget:https://api.nuget.org/v3/index.json?package=Newtonsoft.Json&version=11.0.2
+#addin nuget:https://api.nuget.org/v3/index.json?package=Cake.FileHelpers&version=3.2.1
+
+using System.Net;
+using System.Net.Http;
+
+// Commandline arguments
+var target = Argument("target", "Default");
+var configuration = Argument("configuration", "Release");
+var nugetApiKey = Argument<string>("nugetApiKey", null);
+var version = Argument("nugetVersion", "");
+var gitlabProjectPath = Argument("gitlabProjectPath", "");
+var gitlabProjectId = Argument("gitlabProjectId", "");
+var gitlabToken = Argument("gitlabToken", "");
+
+// Define directories
+var projects = GetFiles("./**/*.csproj");
+var artifactsDir = Directory("./Artifacts");
+string nupkgDir;
+var solutionFile = GetFiles("./**/*.sln").First();
+var projectName = solutionFile.GetFilenameWithoutExtension().ToString();
+var nugetSource = "https://api.nuget.org/v3/index.json";
+var assemblyInfoSubPath = "Properties/AssemblyInfo.cs";
+var semanticVersion = "";
+string localNugetFeed;
+
+// get latest MSBuild version
+var vsLatest  = VSWhereLatest();	
+var msBuildPathX64 = (vsLatest == null) ? null : vsLatest.CombineWithFilePath("./MSBuild/Current/Bin/MSBuild.exe");
+
+Setup(context =>{
+	nupkgDir = $"{artifactsDir.ToString()}/nupkg";
+	var branch = GitVersion(new GitVersionSettings {
+			UpdateAssemblyInfo = false
+		}).BranchName.Replace("/", "-");
+
+	localNugetFeed = $"C:\\coscine\\LocalNugetFeeds\\{branch}";
+	Information("{0}", branch);
+	Information("Started at {0}", DateTime.Now);
+});
+
+Teardown(context =>{
+	Information("Finished at {0}", DateTime.Now);
+});
+
+Task("Clean")
+.Description("Cleans all build and artifacts directories")
+.Does(() =>{
+	var settings = new DeleteDirectorySettings {
+		Recursive = true,
+		Force = true
+	};
+	
+	var directoriesToClean = new List<DirectoryPath>();
+	
+	foreach(var project in projects) {
+		directoriesToClean.Add(Directory($"{project.GetDirectory()}/obj"));
+		directoriesToClean.Add(Directory($"{project.GetDirectory()}/bin"));
+	}
+	
+	directoriesToClean.Add(artifactsDir);
+
+	foreach(var dir in directoriesToClean) {
+		Information("Cleaning {0}", dir.ToString());
+		if (DirectoryExists(dir)) {
+			DeleteDirectory(dir, settings);
+			CreateDirectory(dir);
+		} else {
+			CreateDirectory(dir);
+		}
+	}
+});
+
+Task("Restore")
+.Does(() =>{
+	NuGetRestore(solutionFile, new NuGetRestoreSettings {
+		NoCache = true,
+		FallbackSource = new List<string>{ localNugetFeed },
+	});
+});
+
+Task("Test")
+.IsDependentOn("Build")
+.Does(() =>{
+	NUnit3($"./src/**/bin/{configuration}/*.Tests.dll", new NUnit3Settings {
+		// generate the xml file
+		NoResults = false,
+		Results = new NUnit3Result[] {
+			new NUnit3Result() {
+				FileName = $"{artifactsDir}/TestResults.xml",
+				Transform = $"{Context.Environment.WorkingDirectory}/nunit3-junit.xslt"
+			}
+		}
+	});
+});
+
+Task("GitVersion")
+.Does(() => {
+	if(string.IsNullOrWhiteSpace(version)) {
+		version = GitVersion(new GitVersionSettings {
+			UpdateAssemblyInfo = false
+		}).NuGetVersionV2;
+	}
+	var index = version.IndexOf("-");
+	semanticVersion = index > 0 ? version.Substring(0, index) : version;
+	Information("Version: {0}, SemanticVersion: {1}", version, semanticVersion);
+});
+
+Task("UpdateAssemblyInfo")
+.Does(() =>{
+	var index = version.IndexOf("-");
+	var semanticVersion = index > 0 ? version.Substring(0, index) : version;
+
+	foreach(var project in projects) {
+		CreateAssemblyInfo($"{project.GetDirectory()}/{assemblyInfoSubPath}", new AssemblyInfoSettings {
+			Product = project.GetFilenameWithoutExtension().ToString(),
+			Title = project.GetFilenameWithoutExtension().ToString(),
+			Company = "IT Center, RWTH Aachen University",
+			Version = semanticVersion,
+			FileVersion = semanticVersion,
+			InformationalVersion = version,
+			Copyright = $"{DateTime.Now.Year} IT Center, RWTH Aachen University",
+			Description = $"{project.GetFilenameWithoutExtension().ToString()} is a part of the CoScInE group."
+		});
+	}
+});
+
+Task("GitlabRelease")
+.IsDependentOn("GitVersion")
+.Does(() => {
+	var client = new HttpClient();
+	client.DefaultRequestHeaders.Add("PRIVATE-TOKEN", gitlabToken);
+	
+	// get the latest tag
+	var result = client.GetAsync($"https://git.rwth-aachen.de/api/v4/projects/{gitlabProjectId}/repository/tags").Result;
+	if(!result.IsSuccessStatusCode) {
+		throw new Exception("Tag query failed.");
+	}
+
+	var tagList = result.Content.ReadAsStringAsync().Result;
+	var jArray = JArray.Parse(tagList);
+	// null if not tags exists yet
+	var lastTag = jArray.Select(x => x["name"]).FirstOrDefault();
+
+	var url = $"https://git.rwth-aachen.de/{gitlabProjectPath}";
+	
+	if(url.EndsWith(".git")) {
+		url = url.Substring(0, url.Length - ".git".Length);
+	}
+
+	if(url.EndsWith("/")) {
+		url = url.Substring(0, url.Length - 1);
+	}
+
+	var description = "";
+	// First line of description
+	// Gitlab compare url, if something can be compared
+	if(lastTag == null) {
+		description = $"# {semanticVersion} ({DateTime.Now.Year}-{DateTime.Now.Month}-{DateTime.Now.Day})\n\n\n";
+	} else {
+		description = $"# [{semanticVersion}]({url}/compare/{lastTag}...v{semanticVersion}) ({DateTime.Now.Year}-{DateTime.Now.Month}-{DateTime.Now.Day})\n\n\n";
+	}
+
+	// From when will messages be parsed, null results in all messages
+	var logParam = "";
+	if(lastTag != null) {
+		logParam = $"{lastTag}..Head";
+	}
+
+	Information(lastTag);
+
+	IEnumerable<string> redirectedStandardOutput;
+	var exitCodeWithArgument =
+		StartProcess(
+			"git",
+			new ProcessSettings {
+				Arguments = $"log {logParam} --pretty=format:HASH%h:%B",
+				RedirectStandardOutput = true
+			},
+			out redirectedStandardOutput
+		);
+
+	var prefixList = new Dictionary<string, List<string>>{
+		{"Fix", new List<string>()},
+		{"Update", new List<string>()},
+		{"New", new List<string>()},
+		{"Breaking", new List<string>()},
+		{"Docs", new List<string>()},
+		{"Build", new List<string>()},
+		{"Upgrade", new List<string>()},
+		{"Chore", new List<string>()},
+	};
+
+	var currentHash = "";
+	// Output last line of process output.
+	foreach(var line in redirectedStandardOutput) {
+		var commitMessage = "";
+		if(line.StartsWith("HASH")) {
+			currentHash = line.Substring("HASH".Length);
+			currentHash = currentHash.Substring(0, currentHash.IndexOf(":"));
+			commitMessage = line.Substring(currentHash.Length + line.IndexOf(currentHash) + 1);
+		} else {
+			commitMessage = line;
+		}
+
+		foreach(var kv in prefixList) {
+			if(commitMessage.StartsWith($"{kv.Key}:")) {
+				kv.Value.Add($"* {commitMessage.Substring(kv.Key.Length + 1).Trim()} {currentHash}");
+				break;
+			}
+		};
+	}
+	
+	foreach(var kv in prefixList) {
+		if(kv.Value.Any()) {
+			description += $" ### {kv.Key}\n\n";
+			foreach(var line in kv.Value) {
+				description += $"{line}\n";
+			}
+			description += "\n";
+		}
+	}
+	// correctly escape the json newlines
+	description = description.Replace("\n", "\\n");
+	Information("Description: {0}", description);
+
+	// create tag
+	result = client.PostAsync($"https://git.rwth-aachen.de/api/v4/projects/{gitlabProjectId}/repository/tags?tag_name=v{semanticVersion}&ref=master", null).Result;
+	Information("Create tag: {0}", result.Content.ReadAsStringAsync().Result);
+	if(!result.IsSuccessStatusCode) {
+		throw new Exception("Tag creation failed.");
+	}
+
+	// create release
+	var json = $"{{\"name\": \"v{semanticVersion}\", \"tag_name\": \"v{semanticVersion}\", \"description\": \"{description}\"}}";
+	var content = new StringContent(json, Encoding.UTF8, "application/json");
+	result = client.PostAsync($"https://git.rwth-aachen.de/api/v4/projects/{gitlabProjectId}/releases", content).Result;
+	Information("Create release: {0}", result.Content.ReadAsStringAsync().Result);
+	if(!result.IsSuccessStatusCode) {
+		throw new Exception("Release creation failed.");
+	}
+});
+
+Task("Build")
+.IsDependentOn("Clean")
+.IsDependentOn("GitVersion")
+.IsDependentOn("UpdateAssemblyInfo")
+.IsDependentOn("Restore")
+.Does(() =>{
+		var frameworkSettingsWindows = new MSBuildSettings {
+			Configuration = configuration
+		};
+		
+		frameworkSettingsWindows.ToolPath = msBuildPathX64;
+		frameworkSettingsWindows.WorkingDirectory = Context.Environment.WorkingDirectory;
+
+		if (configuration.Equals("Release")) {
+			frameworkSettingsWindows.WithProperty("DebugSymbols", "false");
+			frameworkSettingsWindows.WithProperty("DebugType", "None");
+		}
+
+		// Use MSBuild
+		Information("Building {0}", solutionFile);
+		MSBuild(solutionFile, frameworkSettingsWindows);
+});
+
+Task("NugetPack")
+.IsDependentOn("Build")
+.Does(() =>{
+	foreach(var project in projects) {
+			var nuspec = $"{project.GetDirectory()}/{project.GetFilenameWithoutExtension()}.nuspec";
+			if(!project.ToString().EndsWith(".Tests") && FileExists(nuspec))
+			{
+				var settings = new NuGetPackSettings 
+				{
+					OutputDirectory = nupkgDir,
+					Version = version,
+					Properties = new Dictionary<string, string>
+					{
+						{ "Configuration", configuration}
+					}
+				};				
+				NuGetPack(project.ToString(), settings);
+			}
+		}
+});
+
+Task("NugetPush")
+.IsDependentOn("NugetPack")
+.Does(() =>{
+	var nupkgs = GetFiles($"{nupkgDir}/*.nupkg");
+	Information("Need to push {0} packages", nupkgs.Count);
+	if(!String.IsNullOrWhiteSpace(nugetApiKey)) {
+		foreach(var nupkg in nupkgs) {
+			Information("Pushing {0}", nupkg);
+			NuGetPush(nupkg, new NuGetPushSettings {
+				Source = nugetSource,
+				ApiKey = nugetApiKey
+			});
+		}
+	} else {
+		Information("NugetApiKey is not set. Can't push.");
+		throw new Exception("NugetApiKey is not set. Can't push.");
+	}
+});
+
+Task("CopyToArtifacts")
+.Does(() =>{
+	foreach(var project in projects) {
+		if(!project.GetDirectory().ToString().EndsWith(".Tests")
+		&& !FileExists($"{project.GetDirectory()}/{project.GetFilenameWithoutExtension()}.nuspec")
+		&& DirectoryExists(project.GetDirectory()))
+		{
+			Information("Copying {0}/* to {1}", $"{project.GetDirectory()}/bin/{configuration}", artifactsDir);
+			CopyDirectory($"{project.GetDirectory()}/bin/{configuration}/", artifactsDir);
+		}
+	}
+});
+
+Task("NugetPushLocal")
+.IsDependentOn("NugetPack")
+.Does(() =>{	
+	var nupkgs = GetFiles($"{nupkgDir}/*.nupkg");
+	foreach(var nupkg in nupkgs) {
+		if(!DirectoryExists(localNugetFeed)) {
+			CreateDirectory(localNugetFeed);
+		}
+		CopyFile(nupkg.ToString(), $"{localNugetFeed}\\{nupkg.GetFilename()}");
+	}
+});
+
+Task("Prerelease")
+.IsDependentOn("Build")
+.IsDependentOn("CopyToArtifacts")
+.IsDependentOn("NugetPushLocal");
+
+Task("Release")
+.IsDependentOn("NugetPack")
+.IsDependentOn("CopyToArtifacts")
+.IsDependentOn("NugetPushLocal")
+.IsDependentOn("NugetPush");
+
+Task("Default")
+.IsDependentOn("Test");
+
+RunTarget(target);
diff --git a/build.ps1 b/build.ps1
new file mode 100644
index 0000000000000000000000000000000000000000..f83382e8e4092ba6cf32a47f9dfa4e211c878430
--- /dev/null
+++ b/build.ps1
@@ -0,0 +1,255 @@
+#The MIT License (MIT)
+#
+#Copyright (c) 2014 - 2016 Patrik Svensson, Mattias Karlsson, Gary Ewan Park and contributors
+#
+#Permission is hereby granted, free of charge, to any person obtaining a copy of
+#this software and associated documentation files (the "Software"), to deal in
+#the Software without restriction, including without limitation the rights to
+#use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
+#the Software, and to permit persons to whom the Software is furnished to do so,
+#subject to the following conditions:
+#
+#The above copyright notice and this permission notice shall be included in all
+#copies or substantial portions of the Software.
+#
+#THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+#IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+#FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+#COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+#IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+#CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+##########################################################################
+# This is the Cake bootstrapper script for PowerShell.
+# This file was downloaded from https://github.com/cake-build/resources
+# Feel free to change this file to fit your needs.
+##########################################################################
+
+<#
+
+.SYNOPSIS
+This is a Powershell script to bootstrap a Cake build.
+
+.DESCRIPTION
+This Powershell script will download NuGet if missing, restore NuGet tools (including Cake)
+and execute your Cake build script with the parameters you provide.
+
+.PARAMETER Script
+The build script to execute.
+.PARAMETER Target
+The build script target to run.
+.PARAMETER Configuration
+The build configuration to use.
+.PARAMETER Verbosity
+Specifies the amount of information to be displayed.
+.PARAMETER ShowDescription
+Shows description about tasks.
+.PARAMETER DryRun
+Performs a dry run.
+.PARAMETER Experimental
+Uses the nightly builds of the Roslyn script engine.
+.PARAMETER Mono
+Uses the Mono Compiler rather than the Roslyn script engine.
+.PARAMETER SkipToolPackageRestore
+Skips restoring of packages.
+.PARAMETER ScriptArgs
+Remaining arguments are added here.
+
+.LINK
+https://cakebuild.net
+
+#>
+
+[CmdletBinding()]
+Param(
+    [string]$Script = "build.cake",
+    [string]$Target,
+    [string]$Configuration,
+    [ValidateSet("Quiet", "Minimal", "Normal", "Verbose", "Diagnostic")]
+    [string]$Verbosity,
+    [switch]$ShowDescription,
+    [Alias("WhatIf", "Noop")]
+    [switch]$DryRun,
+    [switch]$Experimental,
+    [switch]$Mono,
+    [switch]$SkipToolPackageRestore,
+    [Parameter(Position=0,Mandatory=$false,ValueFromRemainingArguments=$true)]
+    [string[]]$ScriptArgs
+)
+
+[Reflection.Assembly]::LoadWithPartialName("System.Security") | Out-Null
+function MD5HashFile([string] $filePath)
+{
+    if ([string]::IsNullOrEmpty($filePath) -or !(Test-Path $filePath -PathType Leaf))
+    {
+        return $null
+    }
+
+    [System.IO.Stream] $file = $null;
+    [System.Security.Cryptography.MD5] $md5 = $null;
+    try
+    {
+        $md5 = [System.Security.Cryptography.MD5]::Create()
+        $file = [System.IO.File]::OpenRead($filePath)
+        return [System.BitConverter]::ToString($md5.ComputeHash($file))
+    }
+    finally
+    {
+        if ($file -ne $null)
+        {
+            $file.Dispose()
+        }
+    }
+}
+
+function GetProxyEnabledWebClient
+{
+    $wc = New-Object System.Net.WebClient
+    $proxy = [System.Net.WebRequest]::GetSystemWebProxy()
+    $proxy.Credentials = [System.Net.CredentialCache]::DefaultCredentials        
+    $wc.Proxy = $proxy
+    return $wc
+}
+
+Write-Host "Preparing to run build script..."
+
+if(!$PSScriptRoot){
+    $PSScriptRoot = Split-Path $MyInvocation.MyCommand.Path -Parent
+}
+
+$TOOLS_DIR = Join-Path $PSScriptRoot "tools"
+$ADDINS_DIR = Join-Path $TOOLS_DIR "Addins"
+$MODULES_DIR = Join-Path $TOOLS_DIR "Modules"
+$NUGET_EXE = Join-Path $TOOLS_DIR "nuget.exe"
+$CAKE_EXE = Join-Path $TOOLS_DIR "Cake/Cake.exe"
+$NUGET_URL = "https://dist.nuget.org/win-x86-commandline/latest/nuget.exe"
+$PACKAGES_CONFIG = Join-Path $TOOLS_DIR "packages.config"
+$PACKAGES_CONFIG_MD5 = Join-Path $TOOLS_DIR "packages.config.md5sum"
+$ADDINS_PACKAGES_CONFIG = Join-Path $ADDINS_DIR "packages.config"
+$MODULES_PACKAGES_CONFIG = Join-Path $MODULES_DIR "packages.config"
+
+# Make sure tools folder exists
+if ((Test-Path $PSScriptRoot) -and !(Test-Path $TOOLS_DIR)) {
+    Write-Verbose -Message "Creating tools directory..."
+    New-Item -Path $TOOLS_DIR -Type directory | out-null
+}
+
+# Make sure that packages.config exist.
+if (!(Test-Path $PACKAGES_CONFIG)) {
+    Write-Verbose -Message "Downloading packages.config..."    
+    try {        
+        $wc = GetProxyEnabledWebClient
+        $wc.DownloadFile("https://cakebuild.net/download/bootstrapper/packages", $PACKAGES_CONFIG) } catch {
+        Throw "Could not download packages.config."
+    }
+}
+
+# Try find NuGet.exe in path if not exists
+if (!(Test-Path $NUGET_EXE)) {
+    Write-Verbose -Message "Trying to find nuget.exe in PATH..."
+    $existingPaths = $Env:Path -Split ';' | Where-Object { (![string]::IsNullOrEmpty($_)) -and (Test-Path $_ -PathType Container) }
+    $NUGET_EXE_IN_PATH = Get-ChildItem -Path $existingPaths -Filter "nuget.exe" | Select -First 1
+    if ($NUGET_EXE_IN_PATH -ne $null -and (Test-Path $NUGET_EXE_IN_PATH.FullName)) {
+        Write-Verbose -Message "Found in PATH at $($NUGET_EXE_IN_PATH.FullName)."
+        $NUGET_EXE = $NUGET_EXE_IN_PATH.FullName
+    }
+}
+
+# Try download NuGet.exe if not exists
+if (!(Test-Path $NUGET_EXE)) {
+    Write-Verbose -Message "Downloading NuGet.exe..."
+    try {
+        $wc = GetProxyEnabledWebClient
+        $wc.DownloadFile($NUGET_URL, $NUGET_EXE)
+    } catch {
+        Throw "Could not download NuGet.exe."
+    }
+}
+
+# Save nuget.exe path to environment to be available to child processed
+$ENV:NUGET_EXE = $NUGET_EXE
+
+# Restore tools from NuGet?
+if(-Not $SkipToolPackageRestore.IsPresent) {
+    Push-Location
+    Set-Location $TOOLS_DIR
+
+    # Check for changes in packages.config and remove installed tools if true.
+    [string] $md5Hash = MD5HashFile($PACKAGES_CONFIG)
+    if((!(Test-Path $PACKAGES_CONFIG_MD5)) -Or
+      ($md5Hash -ne (Get-Content $PACKAGES_CONFIG_MD5 ))) {
+        Write-Verbose -Message "Missing or changed package.config hash..."
+        Remove-Item * -Recurse -Exclude packages.config,nuget.exe
+    }
+
+    Write-Verbose -Message "Restoring tools from NuGet..."
+    $NuGetOutput = Invoke-Expression "&`"$NUGET_EXE`" install -ExcludeVersion -OutputDirectory `"$TOOLS_DIR`""
+
+    if ($LASTEXITCODE -ne 0) {
+        Throw "An error occurred while restoring NuGet tools."
+    }
+    else
+    {
+        $md5Hash | Out-File $PACKAGES_CONFIG_MD5 -Encoding "ASCII"
+    }
+    Write-Verbose -Message ($NuGetOutput | out-string)
+
+    Pop-Location
+}
+
+# Restore addins from NuGet
+if (Test-Path $ADDINS_PACKAGES_CONFIG) {
+    Push-Location
+    Set-Location $ADDINS_DIR
+
+    Write-Verbose -Message "Restoring addins from NuGet..."
+    $NuGetOutput = Invoke-Expression "&`"$NUGET_EXE`" install -ExcludeVersion -OutputDirectory `"$ADDINS_DIR`""
+
+    if ($LASTEXITCODE -ne 0) {
+        Throw "An error occurred while restoring NuGet addins."
+    }
+
+    Write-Verbose -Message ($NuGetOutput | out-string)
+
+    Pop-Location
+}
+
+# Restore modules from NuGet
+if (Test-Path $MODULES_PACKAGES_CONFIG) {
+    Push-Location
+    Set-Location $MODULES_DIR
+
+    Write-Verbose -Message "Restoring modules from NuGet..."
+    $NuGetOutput = Invoke-Expression "&`"$NUGET_EXE`" install -ExcludeVersion -OutputDirectory `"$MODULES_DIR`""
+
+    if ($LASTEXITCODE -ne 0) {
+        Throw "An error occurred while restoring NuGet modules."
+    }
+
+    Write-Verbose -Message ($NuGetOutput | out-string)
+
+    Pop-Location
+}
+
+# Make sure that Cake has been installed.
+if (!(Test-Path $CAKE_EXE)) {
+    Throw "Could not find Cake.exe at $CAKE_EXE"
+}
+
+
+
+# Build Cake arguments
+$cakeArguments = @("$Script");
+if ($Target) { $cakeArguments += "-target=$Target" }
+if ($Configuration) { $cakeArguments += "-configuration=$Configuration" }
+if ($Verbosity) { $cakeArguments += "-verbosity=$Verbosity" }
+if ($ShowDescription) { $cakeArguments += "-showdescription" }
+if ($DryRun) { $cakeArguments += "-dryrun" }
+if ($Experimental) { $cakeArguments += "-experimental" }
+if ($Mono) { $cakeArguments += "-mono" }
+$cakeArguments += $ScriptArgs
+
+# Start Cake
+Write-Host "Running build script..."
+&$CAKE_EXE $cakeArguments
+exit $LASTEXITCODE
diff --git a/build.sh b/build.sh
new file mode 100644
index 0000000000000000000000000000000000000000..d088917ed78538ff57cf654cf71aad6bf045d655
--- /dev/null
+++ b/build.sh
@@ -0,0 +1,122 @@
+#!/usr/bin/env bash
+
+#The MIT License (MIT)
+#
+#Copyright (c) 2014 - 2016 Patrik Svensson, Mattias Karlsson, Gary Ewan Park and contributors
+#
+#Permission is hereby granted, free of charge, to any person obtaining a copy of
+#this software and associated documentation files (the "Software"), to deal in
+#the Software without restriction, including without limitation the rights to
+#use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
+#the Software, and to permit persons to whom the Software is furnished to do so,
+#subject to the following conditions:
+#
+#The above copyright notice and this permission notice shall be included in all
+#copies or substantial portions of the Software.
+#
+#THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+#IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+#FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+#COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+#IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+#CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+##########################################################################
+# This is the Cake bootstrapper script for Linux and OS X.
+# This file was downloaded from https://github.com/cake-build/resources
+# Feel free to change this file to fit your needs.
+##########################################################################
+
+# Define directories.
+SCRIPT_DIR=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )
+TOOLS_DIR=$SCRIPT_DIR/tools
+NUGET_EXE=$TOOLS_DIR/nuget.exe
+CAKE_EXE=$TOOLS_DIR/Cake/Cake.exe
+PACKAGES_CONFIG=$TOOLS_DIR/packages.config
+PACKAGES_CONFIG_MD5=$TOOLS_DIR/packages.config.md5sum
+
+# Define md5sum or md5 depending on Linux/OSX
+MD5_EXE=
+if [[ "$(uname -s)" == "Darwin" ]]; then
+    MD5_EXE="md5 -r"
+else
+    MD5_EXE="md5sum"
+fi
+
+# Define default arguments.
+SCRIPT="build.cake"
+TARGET="Default"
+CONFIGURATION="Release"
+VERBOSITY="verbose"
+DRYRUN=
+SHOW_VERSION=false
+SCRIPT_ARGUMENTS=()
+
+# Parse arguments.
+for i in "$@"; do
+    case $1 in
+        -s|--script) SCRIPT="$2"; shift ;;
+        -t|--target) TARGET="$2"; shift ;;
+        -c|--configuration) CONFIGURATION="$2"; shift ;;
+        -v|--verbosity) VERBOSITY="$2"; shift ;;
+        -d|--dryrun) DRYRUN="-dryrun" ;;
+        --version) SHOW_VERSION=true ;;
+        --) shift; SCRIPT_ARGUMENTS+=("$@"); break ;;
+        *) SCRIPT_ARGUMENTS+=("$1") ;;
+    esac
+    shift
+done
+
+# Make sure the tools folder exist.
+if [ ! -d "$TOOLS_DIR" ]; then
+  mkdir "$TOOLS_DIR"
+fi
+
+# Make sure that packages.config exist.
+if [ ! -f "$TOOLS_DIR/packages.config" ]; then
+    echo "Downloading packages.config..."
+    curl -Lsfo "$TOOLS_DIR/packages.config" https://cakebuild.net/download/bootstrapper/packages
+    if [ $? -ne 0 ]; then
+        echo "An error occurred while downloading packages.config."
+        exit 1
+    fi
+fi
+
+# Download NuGet if it does not exist.
+if [ ! -f "$NUGET_EXE" ]; then
+    echo "Downloading NuGet..."
+    curl -Lsfo "$NUGET_EXE" https://dist.nuget.org/win-x86-commandline/latest/nuget.exe
+    if [ $? -ne 0 ]; then
+        echo "An error occurred while downloading nuget.exe."
+        exit 1
+    fi
+fi
+
+# Restore tools from NuGet.
+pushd "$TOOLS_DIR" >/dev/null
+if [ ! -f $PACKAGES_CONFIG_MD5 ] || [ "$( cat $PACKAGES_CONFIG_MD5 | sed 's/\r$//' )" != "$( $MD5_EXE $PACKAGES_CONFIG | awk '{ print $1 }' )" ]; then
+    find . -type d ! -name . | xargs rm -rf
+fi
+
+mono "$NUGET_EXE" install -ExcludeVersion
+if [ $? -ne 0 ]; then
+    echo "Could not restore NuGet packages."
+    exit 1
+fi
+
+$MD5_EXE $PACKAGES_CONFIG | awk '{ print $1 }' >| $PACKAGES_CONFIG_MD5
+
+popd >/dev/null
+
+# Make sure that Cake has been installed.
+if [ ! -f "$CAKE_EXE" ]; then
+    echo "Could not find Cake.exe at '$CAKE_EXE'."
+    exit 1
+fi
+
+# Start Cake
+if $SHOW_VERSION; then
+    exec mono "$CAKE_EXE" -version
+else
+    exec mono "$CAKE_EXE" $SCRIPT -verbosity=$VERBOSITY -configuration=$CONFIGURATION -target=$TARGET $DRYRUN "${SCRIPT_ARGUMENTS[@]}"
+fi
\ No newline at end of file
diff --git a/nunit3-junit.xslt b/nunit3-junit.xslt
new file mode 100644
index 0000000000000000000000000000000000000000..bf69bdb1d2cd6fc2fdbeb915b4eb6732f720848e
--- /dev/null
+++ b/nunit3-junit.xslt
@@ -0,0 +1,71 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Taken from: https://github.com/nunit/nunit-transforms/tree/master/nunit3-junit
+-->
+<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
+  <xsl:output method="xml" indent="yes"/>
+
+  <xsl:template match="/test-run">
+    <testsuites tests="{@testcasecount}" failures="{@failed}" disabled="{@skipped}" time="{@duration}">
+      <xsl:apply-templates/>
+    </testsuites>
+  </xsl:template>
+
+  <xsl:template match="test-suite">
+    <xsl:if test="test-case">
+      <testsuite tests="{@testcasecount}" time="{@duration}" errors="{@testcasecount - @passed - @skipped - @failed}" failures="{@failed}" skipped="{@skipped}" timestamp="{@start-time}">
+        <xsl:attribute name="name">
+          <xsl:for-each select="ancestor-or-self::test-suite/@name">
+            <xsl:value-of select="concat(., '.')"/>
+          </xsl:for-each>
+        </xsl:attribute>
+        <xsl:apply-templates select="test-case"/>
+      </testsuite>
+      <xsl:apply-templates select="test-suite"/>
+    </xsl:if>
+    <xsl:if test="not(test-case)">
+      <xsl:apply-templates/>
+    </xsl:if>
+  </xsl:template>
+
+  <xsl:template match="test-case">
+    <testcase name="{@name}" assertions="{@asserts}" time="{@duration}" status="{@result}" classname="{@classname}">
+      <xsl:if test="@runstate = 'Skipped' or @runstate = 'Ignored'">
+        <skipped/>
+      </xsl:if>
+      
+      <xsl:apply-templates/>
+    </testcase>
+  </xsl:template>
+
+  <xsl:template match="command-line"/>
+  <xsl:template match="settings"/>
+
+  <xsl:template match="output">
+    <system-out>
+      <xsl:value-of select="."/>
+    </system-out>
+  </xsl:template>
+
+  <xsl:template match="stack-trace">
+  </xsl:template>
+
+  <xsl:template match="test-case/failure">
+    <failure message="{./message}">
+      <xsl:value-of select="./stack-trace"/>
+    </failure>
+  </xsl:template>
+
+  <xsl:template match="test-suite/failure"/>
+
+  <xsl:template match="test-case/reason">
+    <skipped message="{./message}"/>
+  </xsl:template>
+  
+  <xsl:template match="test-case/assertions">
+  </xsl:template>
+
+  <xsl:template match="test-suite/reason"/>
+
+  <xsl:template match="properties"/>
+</xsl:stylesheet>
\ No newline at end of file
diff --git a/src/KpiGenerator.sln b/src/KpiGenerator.sln
new file mode 100644
index 0000000000000000000000000000000000000000..0a3ff8558f5f587c17470a2361913351c7cab28e
--- /dev/null
+++ b/src/KpiGenerator.sln
@@ -0,0 +1,25 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 16
+VisualStudioVersion = 16.0.30413.136
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "KpiGenerator", "KpiGenerator\KpiGenerator.csproj", "{009557C6-E057-4524-8037-821C4445B2D4}"
+EndProject
+Global
+	GlobalSection(SolutionConfigurationPlatforms) = preSolution
+		Debug|Any CPU = Debug|Any CPU
+		Release|Any CPU = Release|Any CPU
+	EndGlobalSection
+	GlobalSection(ProjectConfigurationPlatforms) = postSolution
+		{009557C6-E057-4524-8037-821C4445B2D4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{009557C6-E057-4524-8037-821C4445B2D4}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{009557C6-E057-4524-8037-821C4445B2D4}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{009557C6-E057-4524-8037-821C4445B2D4}.Release|Any CPU.Build.0 = Release|Any CPU
+	EndGlobalSection
+	GlobalSection(SolutionProperties) = preSolution
+		HideSolutionNode = FALSE
+	EndGlobalSection
+	GlobalSection(ExtensibilityGlobals) = postSolution
+		SolutionGuid = {594F2ED9-B7AE-4CE6-B319-F08DAF6329A6}
+	EndGlobalSection
+EndGlobal
diff --git a/src/KpiGenerator/App.config b/src/KpiGenerator/App.config
new file mode 100644
index 0000000000000000000000000000000000000000..7039cbe2788b9da5837c73c79ad949fc73511498
--- /dev/null
+++ b/src/KpiGenerator/App.config
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<configuration>
+  <configSections>
+        <!-- For more information on Entity Framework configuration, visit http://go.microsoft.com/fwlink/?LinkID=237468 -->
+        <section name="entityFramework" type="System.Data.Entity.Internal.ConfigFile.EntityFrameworkSection, EntityFramework, Version=6.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" requirePermission="false" />
+    </configSections>
+    <startup> 
+        <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.6.1" />
+    </startup>
+    <entityFramework>
+        <providers>
+            <provider invariantName="System.Data.SqlClient" type="System.Data.Entity.SqlServer.SqlProviderServices, EntityFramework.SqlServer" />
+        </providers>
+    </entityFramework>
+  <runtime>
+    <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
+      <dependentAssembly>
+        <assemblyIdentity name="linq2db" publicKeyToken="e41013125f9e410a" culture="neutral" />
+        <bindingRedirect oldVersion="0.0.0.0-3.1.1.0" newVersion="3.1.1.0" />
+      </dependentAssembly>
+      <dependentAssembly>
+        <assemblyIdentity name="dotNetRDF" publicKeyToken="6055ffe4c97cc780" culture="neutral" />
+        <bindingRedirect oldVersion="0.0.0.0-2.6.0.0" newVersion="2.6.0.0" />
+      </dependentAssembly>
+      <dependentAssembly>
+        <assemblyIdentity name="Coscine.Database" publicKeyToken="767d77427707b70a" culture="neutral" />
+        <bindingRedirect oldVersion="0.0.0.0-1.26.0.0" newVersion="1.26.0.0" />
+      </dependentAssembly>
+    </assemblyBinding>
+  </runtime>
+</configuration>
diff --git a/src/KpiGenerator/Generators/GenerateProjectsPerIkz.cs b/src/KpiGenerator/Generators/GenerateProjectsPerIkz.cs
new file mode 100644
index 0000000000000000000000000000000000000000..c50e3a8d5529f3b01afeff3bfb738e2b9850148e
--- /dev/null
+++ b/src/KpiGenerator/Generators/GenerateProjectsPerIkz.cs
@@ -0,0 +1,59 @@
+using Coscine.Configuration;
+using Coscine.Database.DataModel;
+using Coscine.Database.Models;
+using Coscine.Database.Util;
+using Coscine.Metadata;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Coscine.KpiGenerator.Generators
+{
+    class GenerateProjectsPerIkz : KpiGenerator
+    {
+        public GenerateProjectsPerIkz(IConfiguration configuration, string measurementID) : base(configuration, measurementID)
+        {
+        }
+
+        public override List<Kpi> GenerateKpis()
+        {
+            var rdfStoreConnector = new RdfStoreConnector(_configuration.GetStringAndWait("coscine/local/virtuoso/additional/url"));
+            var organizationToIkzDict = rdfStoreConnector.GetOrganizationToIkzMap();
+            var projectModel = new ProjectModel();
+
+            var result = new List<Kpi>();
+
+            var orgForProjects = projectModel.GetProjectCountByOrganization();
+
+            var countForeignOrganizations = 0;
+            var countRawRWTHOrganizations = 0;
+            foreach (var r in orgForProjects)
+            {
+                if (r.Url == null)
+                {
+                    continue;
+                }
+                if (organizationToIkzDict.ContainsKey(r.Url))
+                {
+                    result.Add(CreateKpi(organizationToIkzDict[r.Url], r.Count));
+                }
+                else
+                {
+                    if (r.Url == "https://ror.org/04xfq0f34")
+                    {
+                        countRawRWTHOrganizations += r.Count;
+                    }
+                    else
+                    {
+                        countForeignOrganizations += r.Count;
+                    }
+                }
+            }
+            result.Add(CreateKpi("000000", countForeignOrganizations));
+            result.Add(CreateKpi("000001", countRawRWTHOrganizations));
+            return result;
+        }
+    }
+}
diff --git a/src/KpiGenerator/Generators/GenerateRDSBucketsPerIkz.cs b/src/KpiGenerator/Generators/GenerateRDSBucketsPerIkz.cs
new file mode 100644
index 0000000000000000000000000000000000000000..4bfa0bc3c554d329d174d5cda1dd39cac078192d
--- /dev/null
+++ b/src/KpiGenerator/Generators/GenerateRDSBucketsPerIkz.cs
@@ -0,0 +1,59 @@
+using Coscine.Configuration;
+using Coscine.Database.DataModel;
+using Coscine.Database.Models;
+using Coscine.Database.Util;
+using Coscine.Metadata;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Coscine.KpiGenerator.Generators
+{
+    class GenerateRDSBucketsPerIkz : KpiGenerator
+    {
+        public GenerateRDSBucketsPerIkz(IConfiguration configuration, string measurementID) : base(configuration, measurementID)
+        {
+        }
+
+        public override List<Kpi> GenerateKpis()
+        {
+            var rdfStoreConnector = new RdfStoreConnector(_configuration.GetStringAndWait("coscine/local/virtuoso/additional/url"));
+            var organizationToIkzDict = rdfStoreConnector.GetOrganizationToIkzMap();
+            var resourceModel = new ResourceModel();
+
+            var result = new List<Kpi>();
+
+            var bucketForProjects = resourceModel.GetRDSBucketCountByOrganization();
+
+            var countForeignOrganizations = 0;
+            var countRawRWTHOrganizations = 0;
+            foreach (var r in bucketForProjects)
+            {
+                if (r.Url == null)
+                {
+                    continue;
+                }
+                if (organizationToIkzDict.ContainsKey(r.Url))
+                {
+                    result.Add(CreateKpi(organizationToIkzDict[r.Url], r.Count));
+                }
+                else
+                {
+                    if (r.Url == "https://ror.org/04xfq0f34")
+                    {
+                        countRawRWTHOrganizations += r.Count;
+                    }
+                    else
+                    {
+                        countForeignOrganizations += r.Count;
+                    }
+                }
+            }
+            result.Add(CreateKpi("000000", countForeignOrganizations));
+            result.Add(CreateKpi("000001", countRawRWTHOrganizations));
+            return result;
+        }
+    }
+}
diff --git a/src/KpiGenerator/Generators/GenerateRDSQuotasPerIkz.cs b/src/KpiGenerator/Generators/GenerateRDSQuotasPerIkz.cs
new file mode 100644
index 0000000000000000000000000000000000000000..32ad835289e2db27ed4e488379eaf56b9f71cfde
--- /dev/null
+++ b/src/KpiGenerator/Generators/GenerateRDSQuotasPerIkz.cs
@@ -0,0 +1,80 @@
+using Amazon.S3;
+using Amazon.S3.Model;
+using Coscine.Configuration;
+using Coscine.Database.DataModel;
+using Coscine.Database.Models;
+using Coscine.Database.Util;
+using Coscine.Metadata;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Coscine.KpiGenerator.Generators
+{
+    class GenerateRDSQuotasPerIkz : KpiGenerator
+    {
+        public GenerateRDSQuotasPerIkz(IConfiguration configuration, string measurementID) : base(configuration, measurementID)
+        {
+        }
+
+        public override List<Kpi> GenerateKpis()
+        {
+            var rdfStoreConnector = new RdfStoreConnector(_configuration.GetStringAndWait("coscine/local/virtuoso/additional/url"));
+            var organizationToIkzDict = rdfStoreConnector.GetOrganizationToIkzMap();
+            var rdsResourceTypeModel = new RDSResourceTypeModel();
+            var resourceModel = new ResourceModel();
+
+            var result = new List<Kpi>();
+
+            var bucketForProjects = resourceModel.GetRDSResourceListByOrganization();
+
+            var dictQuota = new Dictionary<string, double>();
+
+            var countForeignOrganizations = 0.0;
+            var countRawRWTHOrganizations = 0.0;
+            foreach (var res in bucketForProjects)
+            {
+                if (res.Url == null)
+                {
+                    continue;
+                }
+                foreach (var r in res.Resources)
+                {
+                    var rdsResourceType = rdsResourceTypeModel.GetById(resourceModel.GetById(r.Id).ResourceTypeOptionId.Value);
+                    var bucketname = rdsResourceType.BucketName;
+
+                    var subject = res.Url;
+                    if (dictQuota.ContainsKey(subject))
+                    {
+                        dictQuota[subject] = dictQuota[subject] + (int)(rdsResourceType.Size);
+                    }
+                    else if (subject.Contains("https://ror.org/04xfq0f34#"))
+                    {
+                        dictQuota.Add(subject, (int)(rdsResourceType.Size));
+                    }
+                    else if (subject == "https://ror.org/04xfq0f34")
+                    {
+                        countRawRWTHOrganizations += (int)(rdsResourceType.Size);
+                    }
+                    else
+                    {
+                        countForeignOrganizations += (int)(rdsResourceType.Size);
+                    }
+                }
+            }
+
+            foreach (var quota in dictQuota)
+            {
+                if (organizationToIkzDict.ContainsKey(quota.Key))
+                {
+                    result.Add(CreateKpi(organizationToIkzDict[quota.Key], dictQuota[quota.Key]));
+                }
+            }
+            result.Add(CreateKpi("000000", countForeignOrganizations));
+            result.Add(CreateKpi("000001", countRawRWTHOrganizations));
+            return result;
+        }
+    }
+}
diff --git a/src/KpiGenerator/Generators/GenerateUsersPerIkz.cs b/src/KpiGenerator/Generators/GenerateUsersPerIkz.cs
new file mode 100644
index 0000000000000000000000000000000000000000..166530efc1e5869859ea7d3cdb033c25dc4f9821
--- /dev/null
+++ b/src/KpiGenerator/Generators/GenerateUsersPerIkz.cs
@@ -0,0 +1,106 @@
+using Coscine.Configuration;
+using Coscine.Database.DataModel;
+using Coscine.Database.Models;
+using Coscine.Database.Util;
+using Coscine.Metadata;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Coscine.KpiGenerator.Generators
+{
+    class GenerateUsersPerIkz : KpiGenerator
+    {
+        public GenerateUsersPerIkz(IConfiguration configuration, string measurementID) : base(configuration, measurementID)
+        {
+        }
+
+        public override List<Kpi> GenerateKpis()
+        {
+            var externalIdModel = new ExternalIdModel();
+            var rdfStoreConnector = new RdfStoreConnector(_configuration.GetStringAndWait("coscine/local/virtuoso/additional/url"));
+            var organizationToIkzDict = rdfStoreConnector.GetOrganizationToIkzMap();
+
+            var dictUser = new Dictionary<string, int>();
+            var userModel = new UserModel();
+
+            var result = new List<Kpi>();
+
+            var allUsersThatAcceptedTOS = userModel.GetActiveUserIds();
+
+            var countForeignOrganizations = 0;
+            var countRawRWTHOrganizations = 0;
+            foreach (var userId in allUsersThatAcceptedTOS)
+            {
+                var externalIds = externalIdModel.GetAllWhere((externalId) => externalId.UserId == userId);
+                var externalIdList = new List<string>();
+                foreach (var externalId in externalIds)
+                {
+                    externalIdList.Add(externalId.ExternalIdColumn);
+                }
+                var memberData = rdfStoreConnector.GetTriples(new Uri("https://ror.org/04xfq0f34"), null, null, 1, externalIdList);
+
+                // data available in virtuoso
+                if (memberData.Count() != 0)
+                {
+                    foreach (var member in memberData)
+                    {
+                        var subject = member.Subject.ToString();
+                        if (dictUser.ContainsKey(subject))
+                        {
+                            dictUser[subject] = dictUser[subject] + 1;
+                        }
+                        else
+                        {
+                            dictUser.Add(subject, 1);
+                        }
+                    }
+                }
+                else
+                {
+                    var user = userModel.GetById(userId);
+                    if (user.Institute != null)
+                    {
+                        if (dictUser.ContainsKey(user.Institute))
+                        {
+                            dictUser[user.Institute] += 1;
+                        }
+                        else
+                        {
+                            dictUser.Add(user.Institute, 1);
+                        }
+                    }
+                }
+            }
+
+
+            foreach (var user in dictUser)
+            {
+                if (dictUser.ContainsKey("https://ror.org/04xfq0f34"))
+                {
+                    countRawRWTHOrganizations += dictUser["https://ror.org/04xfq0f34"];
+                }
+                else if (organizationToIkzDict.ContainsKey(user.Key))
+                {
+                    result.Add(new Kpi()
+                    {
+                        MeasurementID = _measurementID,
+                        Ikz = organizationToIkzDict[user.Key],
+                        Value = dictUser[user.Key],
+                        Start = DateTime.UtcNow
+                    });
+                }
+                else
+                {
+                    countForeignOrganizations += dictUser[user.Key];
+                }
+            }
+
+            result.Add(CreateKpi("000000", countForeignOrganizations));
+            result.Add(CreateKpi("000001", countRawRWTHOrganizations));
+            return result;
+        }
+    }
+}
diff --git a/src/KpiGenerator/KpiGenerator.cs b/src/KpiGenerator/KpiGenerator.cs
new file mode 100644
index 0000000000000000000000000000000000000000..57e70019a85744b70cebdb0eb6abd166a10f1777
--- /dev/null
+++ b/src/KpiGenerator/KpiGenerator.cs
@@ -0,0 +1,35 @@
+using Coscine.Configuration;
+using Coscine.Database.DataModel;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Coscine.KpiGenerator
+{
+    abstract class KpiGenerator
+    {
+        protected IConfiguration _configuration;
+        protected string _measurementID;
+
+        public KpiGenerator(IConfiguration configuration, string measurementID)
+        {
+            _configuration = configuration;
+            _measurementID = measurementID;
+        }
+
+        public abstract List<Kpi> GenerateKpis();
+
+        public Kpi CreateKpi(string ikz, double value)
+        {
+            return new Kpi()
+            {
+                MeasurementID = _measurementID,
+                Ikz = ikz,
+                Value = value,
+                Start = DateTime.UtcNow
+            };
+        }
+    }
+}
diff --git a/src/KpiGenerator/KpiGenerator.csproj b/src/KpiGenerator/KpiGenerator.csproj
new file mode 100644
index 0000000000000000000000000000000000000000..a7cc124d857c8e27856bd5e62826237c342162be
--- /dev/null
+++ b/src/KpiGenerator/KpiGenerator.csproj
@@ -0,0 +1,134 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+  <Import Project="..\packages\EntityFramework.6.4.4\build\EntityFramework.props" Condition="Exists('..\packages\EntityFramework.6.4.4\build\EntityFramework.props')" />
+  <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
+  <PropertyGroup>
+    <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
+    <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
+    <ProjectGuid>{009557C6-E057-4524-8037-821C4445B2D4}</ProjectGuid>
+    <OutputType>Exe</OutputType>
+    <RootNamespace>Coscine.KpiGenerator</RootNamespace>
+    <AssemblyName>Coscine.KpiGenerator</AssemblyName>
+    <TargetFrameworkVersion>v4.6.1</TargetFrameworkVersion>
+    <FileAlignment>512</FileAlignment>
+    <AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
+    <Deterministic>true</Deterministic>
+    <NuGetPackageImportStamp>
+    </NuGetPackageImportStamp>
+    <TargetFrameworkProfile />
+  </PropertyGroup>
+  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
+    <PlatformTarget>AnyCPU</PlatformTarget>
+    <DebugSymbols>true</DebugSymbols>
+    <DebugType>full</DebugType>
+    <Optimize>false</Optimize>
+    <OutputPath>bin\Debug\</OutputPath>
+    <DefineConstants>DEBUG;TRACE</DefineConstants>
+    <ErrorReport>prompt</ErrorReport>
+    <WarningLevel>4</WarningLevel>
+  </PropertyGroup>
+  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
+    <PlatformTarget>AnyCPU</PlatformTarget>
+    <DebugType>pdbonly</DebugType>
+    <Optimize>true</Optimize>
+    <OutputPath>bin\Release\</OutputPath>
+    <DefineConstants>TRACE</DefineConstants>
+    <ErrorReport>prompt</ErrorReport>
+    <WarningLevel>4</WarningLevel>
+  </PropertyGroup>
+  <ItemGroup>
+    <Reference Include="AWSSDK.Core, Version=3.3.0.0, Culture=neutral, PublicKeyToken=885c28607f98e604, processorArchitecture=MSIL">
+      <HintPath>..\packages\AWSSDK.Core.3.5.1.32\lib\net45\AWSSDK.Core.dll</HintPath>
+    </Reference>
+    <Reference Include="AWSSDK.S3, Version=3.3.0.0, Culture=neutral, PublicKeyToken=885c28607f98e604, processorArchitecture=MSIL">
+      <HintPath>..\packages\AWSSDK.S3.3.5.3.10\lib\net45\AWSSDK.S3.dll</HintPath>
+    </Reference>
+    <Reference Include="Consul, Version=0.7.2.6, Culture=neutral, PublicKeyToken=20a6ad9a81df1d95, processorArchitecture=MSIL">
+      <HintPath>..\packages\Consul.0.7.2.6\lib\net45\Consul.dll</HintPath>
+    </Reference>
+    <Reference Include="Coscine.Configuration, Version=1.5.0.0, Culture=neutral, PublicKeyToken=ce3d7a32d7dc1e5a, processorArchitecture=MSIL">
+      <HintPath>..\packages\Coscine.Configuration.1.5.0\lib\net461\Coscine.Configuration.dll</HintPath>
+    </Reference>
+    <Reference Include="Coscine.Database, Version=1.26.0.0, Culture=neutral, PublicKeyToken=767d77427707b70a, processorArchitecture=MSIL">
+      <HintPath>..\packages\Coscine.Database.1.26.0\lib\net461\Coscine.Database.dll</HintPath>
+    </Reference>
+    <Reference Include="Coscine.Database.T4, Version=1.26.0.0, Culture=neutral, PublicKeyToken=84b4c404a0696261, processorArchitecture=MSIL">
+      <HintPath>..\packages\Coscine.Database.1.26.0\lib\net461\Coscine.Database.T4.dll</HintPath>
+    </Reference>
+    <Reference Include="Coscine.Metadata, Version=1.4.0.0, Culture=neutral, processorArchitecture=MSIL">
+      <HintPath>..\packages\Coscine.Metadata.1.4.0\lib\net461\Coscine.Metadata.dll</HintPath>
+    </Reference>
+    <Reference Include="dotNetRDF, Version=2.6.0.0, Culture=neutral, PublicKeyToken=6055ffe4c97cc780, processorArchitecture=MSIL">
+      <HintPath>..\packages\dotNetRDF.2.6.0\lib\net40\dotNetRDF.dll</HintPath>
+    </Reference>
+    <Reference Include="dotNetRDF.Data.Virtuoso, Version=2.6.0.0, Culture=neutral, PublicKeyToken=6055ffe4c97cc780, processorArchitecture=MSIL">
+      <HintPath>..\packages\dotNetRDF.Data.Virtuoso.2.6.0\lib\net40\dotNetRDF.Data.Virtuoso.dll</HintPath>
+    </Reference>
+    <Reference Include="EntityFramework, Version=6.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089, processorArchitecture=MSIL">
+      <HintPath>..\packages\EntityFramework.6.4.4\lib\net45\EntityFramework.dll</HintPath>
+    </Reference>
+    <Reference Include="EntityFramework.SqlServer, Version=6.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089, processorArchitecture=MSIL">
+      <HintPath>..\packages\EntityFramework.6.4.4\lib\net45\EntityFramework.SqlServer.dll</HintPath>
+    </Reference>
+    <Reference Include="HtmlAgilityPack, Version=1.11.24.0, Culture=neutral, PublicKeyToken=bd319b19eaf3b43a, processorArchitecture=MSIL">
+      <HintPath>..\packages\HtmlAgilityPack.1.11.24\lib\Net45\HtmlAgilityPack.dll</HintPath>
+    </Reference>
+    <Reference Include="linq2db, Version=3.1.1.0, Culture=neutral, PublicKeyToken=e41013125f9e410a, processorArchitecture=MSIL">
+      <HintPath>..\packages\linq2db.3.1.1\lib\net46\linq2db.dll</HintPath>
+    </Reference>
+    <Reference Include="LinqKit, Version=1.1.17.0, Culture=neutral, PublicKeyToken=bc217f8844052a91, processorArchitecture=MSIL">
+      <HintPath>..\packages\LinqKit.1.1.17\lib\net45\LinqKit.dll</HintPath>
+    </Reference>
+    <Reference Include="Newtonsoft.Json, Version=12.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
+      <HintPath>..\packages\Newtonsoft.Json.12.0.3\lib\net45\Newtonsoft.Json.dll</HintPath>
+    </Reference>
+    <Reference Include="System" />
+    <Reference Include="System.Collections.NonGeneric, Version=4.0.2.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
+      <HintPath>..\packages\System.Collections.NonGeneric.4.3.0\lib\net46\System.Collections.NonGeneric.dll</HintPath>
+    </Reference>
+    <Reference Include="System.ComponentModel.DataAnnotations" />
+    <Reference Include="System.Configuration" />
+    <Reference Include="System.Core" />
+    <Reference Include="System.Net.Http.WebRequest" />
+    <Reference Include="System.Security" />
+    <Reference Include="System.Web" />
+    <Reference Include="System.Xml.Linq" />
+    <Reference Include="System.Data.DataSetExtensions" />
+    <Reference Include="Microsoft.CSharp" />
+    <Reference Include="System.Data" />
+    <Reference Include="System.Net.Http" />
+    <Reference Include="System.Xml" />
+    <Reference Include="VDS.Common, Version=1.10.0.0, Culture=neutral, PublicKeyToken=ab5f4eb908061bf0, processorArchitecture=MSIL">
+      <HintPath>..\packages\VDS.Common.1.10.0\lib\net40-client\VDS.Common.dll</HintPath>
+    </Reference>
+    <Reference Include="virtado4, Version=4.0.0.0, Culture=neutral, PublicKeyToken=391bf132017ae989, processorArchitecture=MSIL">
+      <HintPath>..\packages\OpenLink.Data.Virtuoso.7.20.3214.1\lib\net40\virtado4.dll</HintPath>
+    </Reference>
+  </ItemGroup>
+  <ItemGroup>
+    <Compile Include="Generators\GenerateRDSQuotasPerIkz.cs" />
+    <Compile Include="Generators\GenerateProjectsPerIkz.cs" />
+    <Compile Include="Generators\GenerateRDSBucketsPerIkz.cs" />
+    <Compile Include="Generators\GenerateUsersPerIkz.cs" />
+    <Compile Include="KpiGenerator.cs" />
+    <Compile Include="Program.cs" />
+    <Compile Include="Properties\AssemblyInfo.cs" />
+    <Compile Include="Reporter\HamsterReporter.cs" />
+  </ItemGroup>
+  <ItemGroup>
+    <None Include="App.config" />
+    <None Include="packages.config" />
+  </ItemGroup>
+  <ItemGroup>
+    <Analyzer Include="..\packages\AWSSDK.S3.3.5.3.10\analyzers\dotnet\cs\AWSSDK.S3.CodeAnalysis.dll" />
+  </ItemGroup>
+  <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
+  <Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
+    <PropertyGroup>
+      <ErrorText>This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them.  For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
+    </PropertyGroup>
+    <Error Condition="!Exists('..\packages\EntityFramework.6.4.4\build\EntityFramework.props')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\EntityFramework.6.4.4\build\EntityFramework.props'))" />
+    <Error Condition="!Exists('..\packages\EntityFramework.6.4.4\build\EntityFramework.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\EntityFramework.6.4.4\build\EntityFramework.targets'))" />
+  </Target>
+  <Import Project="..\packages\EntityFramework.6.4.4\build\EntityFramework.targets" Condition="Exists('..\packages\EntityFramework.6.4.4\build\EntityFramework.targets')" />
+</Project>
\ No newline at end of file
diff --git a/src/KpiGenerator/Program.cs b/src/KpiGenerator/Program.cs
new file mode 100644
index 0000000000000000000000000000000000000000..f5172c359e83e41712491c445f89e8c0438f6d56
--- /dev/null
+++ b/src/KpiGenerator/Program.cs
@@ -0,0 +1,55 @@
+using Coscine.Configuration;
+using Coscine.Database.Settings;
+using Coscine.Database.Models;
+using LinqToDB.Data;
+using System;
+using System.Collections.Generic;
+using VDS.RDF.Storage;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using Coscine.Metadata;
+using Coscine.KpiGenerator.Reporter;
+using Coscine.KpiGenerator.Generators;
+using Coscine.Database.DataModel;
+
+namespace Coscine.KpiGenerator
+{
+    class Program
+    {
+        static void Main(string[] args)
+        {
+            ConsulConfiguration configuration = new ConsulConfiguration();
+            DataConnection.DefaultSettings = new CoscineSettings(configuration);
+
+            // Run all Generators for the KPI
+            // each Generator returns a list of KPI, it can have zero entries.
+            List<Kpi> newKpis = new List<Kpi>();
+            newKpis.AddRange(new GenerateProjectsPerIkz(configuration, "2969").GenerateKpis());
+            newKpis.AddRange(new GenerateRDSBucketsPerIkz(configuration, "2971").GenerateKpis());
+            newKpis.AddRange(new GenerateRDSQuotasPerIkz(configuration, "2973").GenerateKpis());
+            newKpis.AddRange(new GenerateUsersPerIkz(configuration, "2967").GenerateKpis());
+
+            //when all generators are done
+            var kpiModel = new KpiModel();
+            // each value is stored within the database
+            foreach (Kpi kpi in newKpis)
+            {
+                kpiModel.Insert(kpi);
+            }
+
+            // get all values from the database that have not been successfully sent yet
+            var kpiToProcess = kpiModel.GetAllWhere((kpi) => (kpi.SentSuccessfully == false)).ToList();
+
+            // try to sent them as a bundle with the csv method
+            HamsterReporter hamsterReporter = new HamsterReporter(configuration);
+            hamsterReporter.Send(kpiToProcess);
+
+            // if the kpi were sent successful mark all values within the database as successfully sent
+            foreach (Kpi kpi in kpiToProcess)
+            {
+                kpiModel.MarkAsSent(kpi);
+            }
+        }
+    }
+}
diff --git a/src/KpiGenerator/Properties/AssemblyInfo.cs b/src/KpiGenerator/Properties/AssemblyInfo.cs
new file mode 100644
index 0000000000000000000000000000000000000000..05ab20d445814b24cf49b9ef98d264bc59fc144c
--- /dev/null
+++ b/src/KpiGenerator/Properties/AssemblyInfo.cs
@@ -0,0 +1,16 @@
+//------------------------------------------------------------------------------
+// <auto-generated>
+//     This code was generated by Cake.
+// </auto-generated>
+//------------------------------------------------------------------------------
+using System.Reflection;
+
+[assembly: AssemblyTitle("KpiGenerator")]
+[assembly: AssemblyDescription("KpiGenerator is a part of the CoScInE group.")]
+[assembly: AssemblyCompany("IT Center, RWTH Aachen University")]
+[assembly: AssemblyProduct("KpiGenerator")]
+[assembly: AssemblyVersion("1.0.0")]
+[assembly: AssemblyFileVersion("1.0.0")]
+[assembly: AssemblyInformationalVersion("1.0.0-topic-1051-basic0005")]
+[assembly: AssemblyCopyright("2020 IT Center, RWTH Aachen University")]
+
diff --git a/src/KpiGenerator/Reporter/HamsterReporter.cs b/src/KpiGenerator/Reporter/HamsterReporter.cs
new file mode 100644
index 0000000000000000000000000000000000000000..dd87b32f06433847613e5be1fcb5a5aacd365914
--- /dev/null
+++ b/src/KpiGenerator/Reporter/HamsterReporter.cs
@@ -0,0 +1,83 @@
+using Coscine.Configuration;
+using Coscine.Database.DataModel;
+using Newtonsoft.Json;
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Linq;
+using System.Net.Http;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Coscine.KpiGenerator.Reporter
+{
+    class HamsterReporter
+    {
+        private readonly HttpClient httpClient;
+        private readonly string hamsterUrl = @"http://datahamster.itc.rwth-aachen.de/put.php";
+
+        private readonly string _name = "messdaten_lieferant";
+        private readonly string _password = "";
+        private readonly string _liveMode = "0";
+
+        public HamsterReporter(IConfiguration configuration)
+        {
+            _password = configuration.GetStringAndWait("coscine/global/reporting/password");
+            _liveMode = configuration.GetStringAndWait("coscine/global/reporting/live");
+            httpClient = new HttpClient();
+        }
+
+        /**
+         * Expects three parameters:
+         * login
+         * password
+         * csv
+         * Optional:
+         * debug
+         */
+        public async void Send(List<Kpi> kpis)
+        {
+            var csv = "";
+            for (var i = 0; i < kpis.Count; i++)
+            {
+                csv += FormatKpi(kpis[i]);
+                if (i < kpis.Count - 1)
+                {
+                    csv += "|";
+                }
+            }
+
+            var values = $"login={_name}&password={_password}&csv={csv}";
+            if (_liveMode != "1")
+            {
+                values += "&debug=1";
+            }
+
+            var content = JsonConvert.SerializeObject(values);
+            await SendData(content);
+        }
+
+        private async Task<HttpResponseMessage> SendData(string data)
+        {
+            HttpContent content = new StringContent(data, Encoding.UTF8, "application/json");
+            return await httpClient.PostAsync(hamsterUrl, content);
+        }
+
+        private string FormatKpi(Kpi kpi)
+        {
+            return $"{kpi.MeasurementID};{kpi.Ikz};{FormatDate(kpi.Start)};{FormatDate(kpi.End)};{kpi.AdditionalInfo};{kpi.AdditionalInfo1};{kpi.AdditionalInfo2};{kpi.AdditionalInfo3};{kpi.AdditionalInfo4};{kpi.AdditionalInfo5};";
+        }
+
+        private string FormatDate(DateTime? date)
+        {
+            if(date == null)
+            {
+                return "";
+            }
+            else
+            {
+                return ((DateTime) date).ToString("yyyy-MM-dd-HH-mm-ss");
+            }            
+        }
+    }
+}
diff --git a/src/KpiGenerator/packages.config b/src/KpiGenerator/packages.config
new file mode 100644
index 0000000000000000000000000000000000000000..18d183e82745746b87a90310016ffc7fc02cc506
--- /dev/null
+++ b/src/KpiGenerator/packages.config
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<packages>
+  <package id="AWSSDK.Core" version="3.5.1.32" targetFramework="net461" />
+  <package id="AWSSDK.S3" version="3.5.3.10" targetFramework="net461" />
+  <package id="Consul" version="0.7.2.6" targetFramework="net472" />
+  <package id="Coscine.Configuration" version="1.5.0" targetFramework="net472" />
+  <package id="Coscine.Database" version="1.26.0" targetFramework="net461" />
+  <package id="Coscine.Metadata" version="1.4.0" targetFramework="net461" />
+  <package id="dotNetRDF" version="2.6.0" targetFramework="net472" />
+  <package id="dotNetRDF.Data.Virtuoso" version="2.6.0" targetFramework="net472" />
+  <package id="EntityFramework" version="6.4.4" targetFramework="net472" />
+  <package id="HtmlAgilityPack" version="1.11.24" targetFramework="net472" />
+  <package id="linq2db" version="3.1.1" targetFramework="net472" />
+  <package id="LinqKit" version="1.1.17" targetFramework="net472" />
+  <package id="Newtonsoft.Json" version="12.0.3" targetFramework="net472" />
+  <package id="OpenLink.Data.Virtuoso" version="7.20.3214.1" targetFramework="net472" />
+  <package id="System.Collections.NonGeneric" version="4.3.0" targetFramework="net461" />
+  <package id="VDS.Common" version="1.10.0" targetFramework="net472" />
+</packages>
\ No newline at end of file
diff --git a/tools/packages.config b/tools/packages.config
new file mode 100644
index 0000000000000000000000000000000000000000..14aef3bf00189e621993b61ccea2e4a203e73b9b
--- /dev/null
+++ b/tools/packages.config
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<packages>
+    <package id="Cake" version="0.36.0" />
+</packages>