Der Team Foundation Server gibt leider nicht wie erwartet immer gleich den Speicher auf der SQL Datenbank frei wenn Dateien gelöscht werden.
Ich habe mich gewundert warum die SQL Datenbank so viel Platz braucht.
Daher werde ich hier einige Erkenntnisse die ich bei der Suche nach der Ursache herausgefunden habe mit euch teilen.

Heute also der erste Teil zu TFS Destroy und dem Test Attachment Cleaner

Erste wichtige Erkenntnis war hierbei das ein Löschen nicht ausreicht um die Daten aus der Datenbank-Tabelle „tbl_Content“ zu bekommen.
Man muss explizit die Dateien per TFS Destroy dauerhaft löschen.

Um dies jedoch nicht über die Konsole erledigen zu müssen gibt es hierfür ein praktisches Plugin:
TFS Source Control Explorer Extension

TFSExtension

Aber selbst dann dauert es einige Tage bis ein Job im Hintergrund die Dateien auch wirklich aus der Datenbank entfernt.
Bei mir hat das teilweise bis zu 7 Tage gedauert. Schnelle Ergebnisse sieht man hier leider nicht.

Eine weitere Erkenntnis war das „Test Attachments“ auch auf der SQL Datenbank viel Platz aufbrauchen können.

Lösung:
Verwendung des „Test Attachment Cleaner“ in den TFS Power Tools

Die Power Tools für Visual Studio 2015 findet man hier:
https://visualstudiogallery.msdn.microsoft.com/898a828a-af00-42c6-bbb2-530dc7b8f2e1

Beispiel für eine Konfigurationsdatei (OlderThan6Months.xml) [von Quelle 1]:


<DeletionCriteria>
<TestRun>
<AgeInDays OlderThan="180" />
</TestRun>
<Attachment />
<LinkedBugs>
<Exclude state="Active" />
</LinkedBugs>
</DeletionCriteria>

Nun muss TCMPT.exe ausgeführt werden um die den Kriterien entsprechenden Test Anhänge zu löschen:

Die TCMPT.exe findet man unter folgendem Pfad:

C:\Program Files (x86)\Microsoft Team Foundation Server 2015 Power Tools

Über die Konsole (CMD) kann nun der folgende Befehl ausgeführt werden [von Quelle 1]::


TCMPT.exe attachmentCleanup /collection:http://localhost:8080/tfs/CollectionName /teamproject:TeamProjectName /settingsFile:OlderThan6Months.xml /mode:delete

Quellen Angabe / Links zu ähnlichen Themen:

1) http://blogs.msdn.com/b/granth/archive/2011/02/12/tfs2010-test-attachment-cleaner-and-why-you-should-be-using-it.aspx

2) http://geekswithblogs.net/terje/archive/2011/11/15/guide-to-reduce-tfs-database-growth-using-the-test-attachment.aspx

docker

Wenn man von Virtualisierung spricht denkt man häufig an schwergewichtige Systeme. Doch seit es Docker gibt muss man hier umdenken.
Bei Container Virtualisierung wie Docker sie verwendet werden die Prozesse lediglich virtuell in einen geschützten Raum gestellt. Sie benötigen dafür aber kein eigenen Betriebssystems, sondern begnügen sich mit den Ressourcen die vom Host-Betriebssystem zur Verfügung gestellt werden.

Das interessante an Docker war für mich vorallem das man Images aufeinander aufbauen kann nach dem Zwiebel Prinzip.
Einzelne Images bauen aufeinader auf und sparen dadurch sehr viel Platz. Dadurch hat man jede Menge Vorteile. Einen Docker-Container mit Webserver kann in unter einer Sekunde gestartet werden.

Anwendungsgebiete kann es viele geben:

– Als Testumgebung kann nun einmal ein Image erstellt werden welches die Rahmenbedingungen für meinen Test abbildet. So kann ein Test in einer isolierten Umgebung so häufig wie gewünscht ausgeführt werden.

– Skalierbarkeit in der Cloud: Sollte ein Container nicht genug Resourcen zur Verfügung stellen könnte ich automatisch in der Cloud einen weiteren Container starten und über einen Loadbalancer die Lasten zwischen den verschiedenen Containern verteilen.

Interessante Links zu dem Thema:
Renaissance der Container-Virtualisierung mit Docker – Admin Magazin
Microsoft bringt Docker auf Windows-Server – Golem.de

Today I experienced a problem with WCF. Dictionaries could not be transfered via WCF.
Dictionaries are not serializable by default. That is the problem.
The solution is quite simple. You simply have to create a new DataType and add a CollectionDataContract.
Now you use the new DataType for transfering your data.

[CollectionDataContract
(Name = "LanguageTexts",
ItemName = "languageitem",
KeyName = "key",
ValueName = "value")]
public class CountriesOrRegionsWithCapitals : Dictionary { }

While looking into some resources for the MTA Certification (Exam 98-361) I came across the following information.

When you add an installer to a Windows Service project two classes are added to the project: The ServiceProcessInstaller Class and the ServiceInstaller Class.

The ServiceProcessInstaller is responsible for tasks that are common to all Windows services including setting the login account for the Windows Service.
The Account Property of the ServiceProcessInstaller defines the type of account under which the service runs. It has 4 possible values LocalService (nonprivileged user), LocalSystem (highly privileged account), NetworkService and User.

The ServiceInstaller Class performs the tasks specific to this one service such as ServiceName and StartType.

installutil.exe can be used to install the service.

I did some research how to to log something with the AOP Framework PostSharp 3.0. Here is the result of the research. A sample programm that simply logs an function call (Enter/Exit etc…) into a file in the Windows ‘%tmp%’ dir.

You can download the sample project vor Visual Studio 2012 here:
AOPLogToFile VS2012 Solution

using System;
using System.Diagnostics;
using System.IO;
using System.Reflection;
using PostSharp.Aspects;

namespace AOP
{
 /// <summary>
 /// Attribute for Logging Entering and Exit out of Methods as well as Exceptions
 /// </summary>
 [Serializable]
 public class LogToFileAttribute : OnMethodBoundaryAspect
 {
 /// <summary>
 /// Gets or sets the enter message.
 /// </summary>
 /// <value>
 /// The enter message.
 /// </value>
 public string EnterMessage { get; set; }

 /// <summary>
 /// Gets or sets the exit message.
 /// </summary>
 /// <value>
 /// The exit message.
 /// </value>
 public string ExitMessage { get; set; }

 /// <summary>
 /// Gets or sets the exit message.
 /// </summary>
 /// <value>
 /// The exit message.
 /// </value>
 public string SuccessMessage { get; set; }

 /// <summary>
 /// Gets or sets the exit message.
 /// </summary>
 /// <value>
 /// The exit message.
 /// </value>
 public string EnterAndExitName { get; set; }

 /// <summary>
 /// Gets the application path.
 /// </summary>
 /// <value>
 /// The application path.
 /// </value>
 private static string ApplicationPath
 {
 get
 {
 Assembly entryPoint = Assembly.GetEntryAssembly();

 if (entryPoint == null)
 {
 return null;
 }

 return Path.GetFileName(entryPoint.Location);
 }
 }

 /// <summary>
 /// Method executed <b>before</b> the body of methods to which this aspect is applied.
 /// </summary>
 /// <param name="args">Event arguments specifying which method
 /// is being executed, which are its arguments, and how should the execution continue
 /// after the execution of <see cref="M:PostSharp.Aspects.IOnMethodBoundaryAspect.OnEntry(PostSharp.Aspects.MethodExecutionArgs)" />.</param>
 public override void OnEntry(MethodExecutionArgs args)
 {
 if (!string.IsNullOrEmpty(this.EnterMessage))
 {
 BuildAndLogMessage(args, this.EnterMessage);
 }
 else if (!string.IsNullOrEmpty(this.EnterAndExitName))
 {
 BuildAndLogMessage(args, string.Format("Entered {0}", this.EnterAndExitName));
 }

 base.OnEntry(args);
 }

 /// <summary>
 /// Method executed <b>after</b> the body of methods to which this aspect is applied,
 /// in case that the method resulted with an exception.
 /// </summary>
 /// <param name="args">Event arguments specifying which method
 /// is being executed and which are its arguments.</param>
 public override void OnException(MethodExecutionArgs args)
 {
 if (ApplicationPath == null)
 {
 return;
 }

 ////MessageBox.Show(string.Format("Ausnahmefehler bitte melden sie sich bei einem Admin. Fehler wurde geloggt! {0}", args.Exception.Message) );

 string message = string.Format("Exception captured: {0}", args.Exception);
 LogToFile.LogMessage(message, EventLogEntryType.Error, ApplicationPath);
 base.OnException(args);
 }

 /// <summary>
 /// Method executed <b>after</b> the body of methods to which this aspect is applied,
 /// even when the method exists with an exception (this method is invoked from
 /// the <c>finally</c> block).
 /// </summary>
 /// <param name="args">Event arguments specifying which method
 /// is being executed and which are its arguments.</param>
 public override void OnExit(MethodExecutionArgs args)
 {
 if (!string.IsNullOrEmpty(this.ExitMessage))
 {
 BuildAndLogMessage(args, this.ExitMessage);
 }
 else if (!string.IsNullOrEmpty(this.EnterAndExitName))
 {
 BuildAndLogMessage(args, string.Format("Exited {0}", this.EnterAndExitName));
 }

 base.OnExit(args);
 }

 /// <summary>
 /// Method executed <b>after</b> the body of methods to which this aspect is applied,
 /// but only when the method successfully returns (i.e. when no exception flies out
 /// the method.).
 /// </summary>
 /// <param name="args">Event arguments specifying which method
 /// is being executed and which are its arguments.</param>
 public override void OnSuccess(MethodExecutionArgs args)
 {
 if (!string.IsNullOrEmpty(this.SuccessMessage))
 {
 BuildAndLogMessage(args, this.SuccessMessage);
 }
 else if (!string.IsNullOrEmpty(this.EnterAndExitName))
 {
 BuildAndLogMessage(args, string.Format("Successfully ran through {0}", this.EnterAndExitName));
 }

 base.OnSuccess(args);
 }

 /// <summary>
 /// Builds the and log message.
 /// </summary>
 /// <param name="args">The args of the Method.</param>
 /// <param name="message">The message.</param>
 private static void BuildAndLogMessage(MethodExecutionArgs args, string message)
 {
 if (string.IsNullOrEmpty(message))
 {
 return;
 }

 string messagetoWrite = string.Format(message, args.Arguments.ToArray());
 LogToFile.LogMessage(messagetoWrite, EventLogEntryType.Information, ApplicationPath);
 }
 }
using System;
using System.Diagnostics;

namespace AOP
{
	/// <summary>
	/// Logs AOP Actions to a file
	/// </summary>
	public class LogToFile
	{
		/// <summary>
		/// Logs the message to a file in the users temp directory (%tmp%).
		/// </summary>
		/// <param name="message">The message.</param>
		/// <param name="entryType">Type of the entry.</param>
		/// <param name="ApplicationPath">The application path.</param>
		public static void LogMessage(string message, EventLogEntryType entryType, string applicationPath)
		{
			string pathToTempDir = System.Environment.GetEnvironmentVariable("TMP") + "\\YourAppName";

			if (!System.IO.Directory.Exists(pathToTempDir))
			{
				System.IO.Directory.CreateDirectory(pathToTempDir);
			}

			using (System.IO.StreamWriter file = new System.IO.StreamWriter(pathToTempDir + "\\YourAppLog.txt", true))
			{
				file.WriteLine(DateTime.Now.ToString() + "--" + message);
			}
		}
	}
}
using System;
using AOP;

namespace AOPLogToFileConsole
{
	class Program
	{
		static void Main()
		{
			myTestMethod();
			Console.ReadKey();
		}

		[LogToFile(EnterAndExitName = "myTestMethod")]
		public static void myTestMethod()
		{
			Console.WriteLine("Hello World!");
		}
	}
}