Best Visual Studio extensions and applications

The list of tools beneath are tools that I use every day! Some are extensions for Team Foundation Server but also extensions for Visual Studio, awesome Nuget packages or general tools.
It are the best Visual Studio extension and applications that I could find.

Visual Studio Extensions

  1. EnterpriseLibrary.Config
    This is a handy tool for your Enterprise Library packages. With this tool you can edit your config with an interface.
  2. GhostDoc
    I use the free version of this but you also have a payed version of this.  With this tool you can generate your summaries above your code. It will also generate documentation for parameters, properties, field, methods etc.
  3. NuGet Package Manager
    I think there is no explanation needed for this…
  4. Target Framework Migrator
    With this extension, you can change all (if you want) your projects to a specific version of the .NET Framework with just one click.
  5. Team Foundation Server Power Tools
    Extra handy things for getting some information out of your TFS server.
  6. Web Essentials
    Handy tools for developing web applications. This allows you to minify and bundle your Javascript code and gives you other things like intellisence for extra languages in Visual Studio.
  7. Wix Toolset
    There is no MSI installer anymore in Visual Studio 2012. You can still use Wix to create your MSI installers.
  8. Recent workitems
    Show a list of last 5 associated workitems in your pending changes window
  9. Last workitem
    Select your last associated workitem
  10. AutoMerge
    AutoMerge your changeset over branches in TFS.
  11. Code Review Checkin policy
    A checkin policy for checking you have requested a code review.
  12. DevExpress CodeRush
    Faster coding and shortcuts in Visual Studio
  13. My History
    Your last history items like workitems or changesets
  14. Sandcastle
    Help file generator for your code.

Applications

  1. TFS Team Project Manager
    Manage your TFS team projects.

NuGet packages

  1. StyleCop
    Create defaults for your team about documentation and code formats.
  2. StyleCop Checkin Policy
    My own checkin policy for validating your StyleCop rules.
  3. Ninject
    Very easy dependency container for fast building applications.
  4. CuttingEdge.Conditions
    Small syntax for creating validation of your objects.
  5. NBuilder
    Generate test data based on your own POCO class.
  6. AutoMapper
    Map your classes from type A to B with just own line of code.

MVC localization validation

This is not the first time that I write about mvc localization validation. The last time that I wrote about this was back in 2012 when I wrote the blog “Localization validation in MVC“. This blog post helped a lot of people with this question on StackOverflow.

In this blog post I create a website with localization for English and Dutch. I will use a DateTime as an example property to display and edit this property for validating the localization. This because the Dutch localization is dd-MM-yyyy and in English we have MM\dd\yyyy.

Technology used

Now I want to write a new post that uses all the latest technology in the MVC world at this moment. At this moment the latest version of Visual Studio is Visual Studio 2013 Update 3. The latest version of MVC is: 5.2.2. This is also the version that we use in combination with .NET Framework 4.5.1.

Setup MVC localization validation

New project

Create a new MVC 5 ASP.NET Web Application in Visual Studio with the use of the .NET Framework 4.5.1. When it is created, update all your Nuget packages about MVC.

Web.config

Some people say that you have to alter the web.config to use localization. This is true when you create a website for a particular localization. In this tutorial I want to create a website that is compatible with multiple cultures from all over the world. Of course you can create a website in English but some people prefer their own language. In this tutorial I will create a website in English and Dutch (I’m from the Netherlands).

So we don’t edit the web.config. We don’t set any culture or UI culture in the web.config. In that way, the default culture is the culture of the pc or (if set) the culture of the browser.

Culture selector

The user should be able to overwrite the default culture from his browser or pc. Maybe the user wants to use another culture than his default culture for your website. So we create some code to create a list with the cultures that you have setup to support in your website.

Code for creating the list of cultures and select a culture different to your browser culture. Add this code in your “_Layout” page in example your menu or footer:

@{
	System.Globalization.CultureInfo currentCulture;
	var supportedCultures = Website.Helpers.CultureHelper.GetSwitchCultures(out currentCulture);
	string currentCultureDisplayName = currentCulture.Parent.NativeName;
}

	<ul>
		@foreach (var culture in supportedCultures)
		{
            string url = Url.Action("SetPreferredCulture", "Culture", new { culture = culture.Name, returnUrl = Request.RawUrl });
            string urlName = culture.Parent.NativeName;
            
			if (culture.Name == currentCulture.Name)
			{
				<li class="active"><a href="@url">@urlName <i class="fa fa-check"></i></a></li>
            }
            else
            {
                <li><a href="@url">@urlName</a></li>
            }
		}
	</ul>

Create a static class called CultureHelper and past the following code:

using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Diagnostics;
using System.Globalization;
using System.Linq;
using System.Threading;
using System.Web;

namespace Website.Helpers
{
	/// <summary>
	/// Set the culture of the user for the rest of the application. The culture can be overridden by the user. 
	/// If it is overridden a cookie is set with the selected culture. For testing don't forget to remove the cookie for selecting the default browser language again.
	/// </summary>
	public static class CultureHelper
	{

		#region Constants

		/// <summary>
		/// The cookie name of the selected culture. By default there isn't any cookie but only the culture of the browser. 
		/// If the user selects a specific culture, the cookie is added.
		/// </summary>
		const string CookieName = "PreferredCulture";

		#endregion

		#region Fields

		/// <summary>
		/// The supported cultures of this application. If a new localization file is added to the application, the cultureinfo must be added as well.
		/// </summary>
		public static readonly CultureInfo[] SupportedCultures = new CultureInfo[] 
		{ 
			CultureInfo.GetCultureInfo("en-US"), 
			CultureInfo.GetCultureInfo("nl-NL"),
			//CultureInfo.GetCultureInfo("de-DE"), 
			//CultureInfo.GetCultureInfo("fr-FR"), 
			//CultureInfo.GetCultureInfo("es-ES"), 
		};

		#endregion

		#region Public Methods

		public static void ApplyUserCulture(this HttpRequest request)
		{
			ApplyUserCulture(request.Headers, request.Cookies);
		}

		public static CultureInfo GetMatch(CultureInfo[] acceptedCultures, CultureInfo[] supportedCultures, Func<CultureInfo, CultureInfo, bool> predicate)
		{
			foreach (var acceptedCulture in acceptedCultures)
			{
				var match = supportedCultures
					.Where(supportedCulture => predicate(acceptedCulture, supportedCulture))
					.FirstOrDefault();

				if (match != null)
				{
					return match;
				}
			}

			return null;
		}

		public static CultureInfo GetMatchingCulture(CultureInfo[] acceptedCultures, CultureInfo[] supportedCultures)
		{
			return
				// first pass: exact matches as well as requested neutral matching supported region 
				// supported: en-US, de-DE 
				// requested: de, en-US;q=0.8 
				// => de-DE! (de has precendence over en-US) 
				GetMatch(acceptedCultures, supportedCultures, MatchesCompletely)
				// second pass: look for requested neutral matching supported _neutral_ region 
				// supported: en-US, de-DE 
				// requested: de-AT, en-GB;q=0.8 
				// => de-DE! (no exact match, but de-AT has better fit than en-GB) 
				?? GetMatch(acceptedCultures, supportedCultures, MatchesPartly);
		}

		public static void GetSwitchCultures(out CultureInfo currentCulture, out CultureInfo nextCulture)
		{
			currentCulture = Thread.CurrentThread.CurrentUICulture;
			var currentIndex = Array.IndexOf(SupportedCultures.Select(ci => ci.Name).ToArray(), currentCulture.Name);
			int nextIndex = (currentIndex + 1) % SupportedCultures.Length;
			nextCulture = SupportedCultures[nextIndex];
		}

		public static CultureInfo[] GetSwitchCultures(out CultureInfo currentCulture)
		{
			currentCulture = Thread.CurrentThread.CurrentUICulture;

			return SupportedCultures;
		}

		public static CultureInfo GetUserCulture(NameValueCollection headers)
		{
			var acceptedCultures = GetUserCultures(headers["Accept-Language"]);
			var culture = GetMatchingCulture(acceptedCultures, SupportedCultures);

			return culture;
		}

		public static CultureInfo[] GetUserCultures(string acceptLanguage)
		{
			// Accept-Language: fr-FR , en;q=0.8 , en-us;q=0.5 , de;q=0.3 
			if (string.IsNullOrWhiteSpace(acceptLanguage))
				return new CultureInfo[] { };

			var cultures = acceptLanguage
				.Split(',')
				.Select(s => WeightedLanguage.Parse(s))
				.OrderByDescending(w => w.Weight)
				 .Select(w => GetCultureInfo(w.Language))
				 .Where(ci => ci != null)
				 .ToArray();

			return cultures;
		}

		public static void SetPreferredCulture(this HttpResponseBase response, string cultureName)
		{
			SetPreferredCulture(response.Cookies, cultureName);
		}

		#endregion

		#region Private Methods

		private static void ApplyUserCulture(NameValueCollection headers, HttpCookieCollection cookies)
		{
			var culture = GetPreferredCulture(cookies)
				?? GetUserCulture(headers)
				?? SupportedCultures[0];

			var t = Thread.CurrentThread;
			t.CurrentCulture = culture;
			t.CurrentUICulture = culture;

			Debug.WriteLine("Culture: " + culture.Name);
		}

		private static CultureInfo GetCultureInfo(string language)
		{
			try
			{
				return CultureInfo.GetCultureInfo(language);
			}
			catch (CultureNotFoundException)
			{
				return null;
			}
		}

		private static CultureInfo GetPreferredCulture(HttpCookieCollection cookies)
		{
			var cookie = cookies[CookieName];
			if (cookie == null)
				return null;

			var culture = GetCultureInfo((string)cookie.Value);
			if (culture == null)
				return null;

			if (!SupportedCultures.Where(ci => ci.Name == culture.Name).Any())
				return null;

			return culture;
		}

		private static bool MatchesCompletely(CultureInfo acceptedCulture, CultureInfo supportedCulture)
		{
			if (supportedCulture.Name == acceptedCulture.Name)
			{
				return true;
			}

			// acceptedCulture could be neutral and supportedCulture specific, but this is still a match (de matches de-DE, de-AT, …) 
			if (acceptedCulture.IsNeutralCulture)
			{
				if (supportedCulture.Parent.Name == acceptedCulture.Name)
				{
					return true;
				}
			}

			return false;
		}

		private static bool MatchesPartly(CultureInfo acceptedCulture, CultureInfo supportedCulture)
		{
			supportedCulture = supportedCulture.Parent;
			if (!acceptedCulture.IsNeutralCulture)
			{
				acceptedCulture = acceptedCulture.Parent;
			}

			if (supportedCulture.Name == acceptedCulture.Name)
			{
				return true;
			}

			return false;
		}

		private static void SetPreferredCulture(HttpCookieCollection cookies, string cultureName)
		{
			var cookie = new HttpCookie(CookieName, cultureName)
			{
				Expires = DateTime.Now.AddDays(30)
			};

			cookies.Set(cookie);

			Debug.WriteLine("SetPreferredCulture: " + cultureName);
		}

		#endregion

	}

	[DebuggerDisplay("Language = {Language} Weight = {Weight}")]
	internal class WeightedLanguage
	{
		public string Language { get; set; }

		public double Weight { get; set; }

		public static WeightedLanguage Parse(string weightedLanguageString)
		{
			// de 
			// en;q=0.8 
			var parts = weightedLanguageString.Split(';');
			var result = new WeightedLanguage { Language = parts[0].Trim(), Weight = 1.0 };

			if (parts.Length > 1)
			{
				parts[1] = parts[1].Replace("q=", "").Trim();
				double d;
				if (double.TryParse(parts[1], NumberStyles.AllowDecimalPoint, CultureInfo.InvariantCulture, out d))
					result.Weight = d;
			}

			return result;
		}
	}
}

The “SupportedCultures” field contains the list of supported cultures for your website.

We must also make the Culture of the client available in your project. You have to do this in every request. So go to your Global.asax and add the following event:

/// <summary>
/// Fire on each request.
/// </summary>
protected void Application_OnBeginRequest()
{
    // Get the culture of the client
    CultureHelper.ApplyUserCulture(Request);
}

Create a new CultureController in your project and paste the following code:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web.Mvc;
using Website.Helpers;

namespace Website.Controllers
{
    public class CultureController : Controller
    {
		// GET: /SetPreferredCulture/de-DE 
		[AllowAnonymous]
		public ActionResult SetPreferredCulture(string culture, string returnUrl)
		{
			Response.SetPreferredCulture(culture);
			
			if (string.IsNullOrEmpty(returnUrl))
				return RedirectToAction("Index", "Home");

			return Redirect(returnUrl);
		}
				
    }
}

Add a new route above the default route in your RouteConfig.cs.

// Set culture route
routes.MapRoute(
    name: "SetPreferredCulture",
    url: "SetPreferredCulture/{culture}",
    defaults: new { controller = "Culture", action = "SetPreferredCulture", culture = UrlParameter.Optional }
);

Create a folder in the root of your project called “Localizations”. Add a Resource file called “Labels.resx” in that folder. This is your English resource file so the default language is the English language. Set the Access Modifier to Public. Copy the Labels resource file for the other languages. For Dutch is this: Labels.nl-NL.resx. So when the user switches from culture or the browser has another culture set, the right resource file is automatically chosen.

DateTime Example

Now we have the right culture of the user, we need an example so we can prove it actually works. So we create an object with a DateTime property. In that way we can display and edit that property in the different cultures. Remember that the Dutch language has a different date format than the English language.

Because all the browser are displaying date textboxes different, we create our own until the HTML 5 specification is finally done. Also, the older browsers don’t support HTML 5 so we must create our own textbox with jQuery UI datepicker.

To do this, we create a new EditorTemplate. If you never done this before, don’t worry it is very easy. Create a new folder called “EditorTemplates” in your “Shared” folder in the “Views” folder. In the EditorTemplates folder, create a new view called “Date”.

Now when a Date field is used for editing, the editortemplate is automatically used. Paste the following code in the Date editortemplate.

@model DateTime?

@if (Model.HasValue)
{
    @Html.TextBox("", Model.Value.ToShortDateString(), new { @class = "date form-control" })
}
else
{
    @Html.TextBox("", Model, new { @class = "date form-control" })
}

Initialize the Datepicker

Now we have created an editor for editing, the jQuery datepicker should be created. Because we also create the datepicker for a specific culture, the right language file of the jQuery datepicker should be selected as well. You can find the url of the language file in the documentation/source code from the jQuery datepicker documentation page.

Add the following code in the _Layout page just after setting up jQuery and jQuery UI.

@if (!string.IsNullOrWhiteSpace(UICulture))
{
	string shortCulture = UICulture.Substring(0, 2);
	if (shortCulture != "en")
	{
		// Only set culture script if it isn't en because en is the default
		string url = string.Format("/Scripts/datepicker-{0}.js", shortCulture);
		<script src="@url"></script>
	}
}

This code finds the right language file and add it to the page for the current (selected) culture.

<script type="text/javascript">
	$(document).ready(function () {
		$('.date').datepicker({
			showOtherMonths: true,
			selectOtherMonths: true,
			changeMonth: true,
			changeYear: true,
			numberOfMonths: 1,
			showButtonPanel: true
		});
	});
</script>

This code creates the datepicker in the right culture.

Test the editor

To test the editor, create an object with a DateTime property. See the following example for the class “Member”:

public class Member
{
    [Key]
    public int Id { get; set; }
		
    [Display(Name = "Voornaam")]
    [Required]
    [StringLength(50)]
    public string FirstName { get; set; }

    [Display(Name = "Achternaam")]
    [Required]
    [StringLength(50)]
    public string LastName { get; set; }
        
    [Display(Name = "Geboortedatum")]
    [Required]
    [DataType(DataType.Date)]
    public DateTime BirthDay { get; set; }
}

The EditorTemplate is selected because we have used the “DataType” attribute with the “DataType.Date” value. You could also use the “UIHint” attribute.

If you generate (scaffold) a new Edit or Create view, change the code for the Birthday property to:

<div class="form-group">
    @Html.LabelFor(model => model.BirthDay, new { @class = "control-label col-md-2" })
    <div class="col-md-10">
        @Html.EditorFor(model => model.BirthDay, new { htmlAttributes = new { @class = "form-control" }, })
        @Html.ValidationMessageFor(model => model.BirthDay)
    </div>
</div>

Now run your project and try to edit or create a member object. You now should see a textbox with a jQuery UI datepicker inside. When you select a date from the datepicker, the format of the date should be the format of the culture you requested. Also the validation should be right for the Culture you have selected.

Conclusion

Now you have a culture independent website with a jQuery datepicker in the culture that the user wants. You can upload this website to every server you want. You don’t have to set any culture on the server because this solution is culture independent. This is easy for me in the Netherlands because now I can create a Dutch website that I can upload to Microsoft Azure (in azure you have en-US culture) without any problems.

Trigger a CI build after a gated checkin

We want to trigger a CI build after a gated checkin because we have two builds in our company. One with a gated checkin for fast building and very important test. The other build is for code analysis and long running tests. Because we don’t want to wait very long on our CI build we have created that QM build. The problem is that the QM isn’t triggered anymore after changing the CI build to a Gated checkin build.

I found a blogpost for TFS 2010. Because TFS 2013 is slightly different, I thought I would give it an update.

This is how to fix it for TFS 2013

  1. Create an argument called “NoCIOption” of the type boolean with default True.
    CreateDisableCiArgument
  2. Set the metadata information so you understand the argument in your build definition.
    MetadataDisableCiBuild
  3. Use the argument in the TFS 2013 build template. You can find the property under “Run on agent” and then “Get sources from Team Foundation Version Control”. Go to the properties window and change the hard coded “True” in the NoCIOption property to the argument you just created.
    SetDisableCiArgument

Now checkin your changes of the teamplate and use (if you don’t already have) the template in your CI build (the one with the gated checkin). Change the new argument to “false”.

Now your second build (our QM) is also triggered again.

Add image to sandcastle

I wanted to know how to add image to sandcastle. The documentation of sandcastle is long but I didn’t found a good sample of how to add an image to the welcome page.

I use Sandcastle Help File Builder to generate documentation for my applications. I also have a nice welcome page called “Welcome.aml”.

In the welcome page I have a section of the architecture of the application. I wanted to add an image of the architecture schema. The problem was that I didn’t found anywhere how to do this. The problem was that I didn’t use the id of the image but the name of the image. The solution was just to just use the id.

The solution

So to add an image from the “Media” folder into a new section:

<section>
	<title>Architectuur</title>
	<content>
		<mediaLink>
			<caption>Architectuurschema</caption>
			<image placement="center" xlink:href="Architectuurschema"/>
		</mediaLink>				
	</content>
</section>

 

Edit outlook.com contacts in android

Updated 2017-01-17: Updated the blogpost to the new outlook.com experience (on the office 365 platform).

Edit outlook.com contacts in android is a real pain. But in this post I will explain to you have to fix this.

Because Google and Microsoft are in some kind of war in what the best way is to edit contacts, you could be having troubling with editing your Outlook.com contacts in Android. Follow these steps to edit your Outlook.com contacts again.

  1. If you have installed the Outlook.com application out of the play store, remove it from your device. It has been replaced by the new Outlook application some time ago.
  2. Setup the new Outlook application and disable contact sync. Contacts do get synced to your device very nice but you can’t edit them. So they are useless to use. We disable the sync so we can sync them another way. If you leave this enabled you will end up with dupplicates on your phone.
  3. On your phone, go to Settings -> Accounts & Sync
  4. Add an Exchange ActiveSync account. Do not use the outlook.com account. some phones will display an outlook.com sync possibility but that is an old obsolete one. If you can’t find an Exchange account, try to use the default email application on your phone. It should have one build in.
    1. Account name: whatever you want
    2. email adres: your outlook.com email adres (could also be hotmail, msn, live etc)
    3. Server adres: eas.outlook.com
    4. username: again your full email-address
    5. password: your outlook.com email-address password
    6. Enable SSL
    7. Leave the rest default
  5. Click next and select only to sync the Contacts.
  6. Finish the setup (wait a while to finish the syncing of your contacts). Now you can edit your contacts and they are synced to your outlook.com account.

Download files from TFS server with PowerShell

If you want to download files from TFS with PowerShell, you will need to write a script that can access the TFS Server and access the folder on your drive.

This script uses a server path in the TFS server and download some files under that server path to the drop folder of your build. If you don’t use a build, you can change then environment variables. This script is created because of an original question on StackOverflow.

# The deploy directory for all the msi, zip etc.
$AutoDeployDir = "Your TFS Directory Server Path"
$deployDirectory = $($Env:TF_BUILD_DROPLOCATION + "\Deploy\" + $Env:TF_BUILD_BUILDNUMBER)

# Add TFS 2013 dlls so we can download some files
Add-Type -AssemblyName 'Microsoft.TeamFoundation.Client, Version=12.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a'
Add-Type -AssemblyName 'Microsoft.TeamFoundation.VersionControl.Client, Version=12.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a'
$tfsCollectionUrl = 'http://YourServer:8080/tfs/YourCollection' 
$tfsCollection = New-Object -TypeName Microsoft.TeamFoundation.Client.TfsTeamProjectCollection -ArgumentList $tfsCollectionUrl
$tfsVersionControl = $tfsCollection.GetService([Microsoft.TeamFoundation.VersionControl.Client.VersionControlServer])

# Register PowerShell commands
Add-PSSnapin Microsoft.TeamFoundation.PowerShell

# Get all directories and files in the AutoDeploy directory
$items = Get-TfsChildItem $AutoDeployDir -Recurse

# Download each item to a specific destination
foreach ($item in $items) {
    # Serverpath of the item
    Write-Host "TFS item to download:" $($item.ServerItem) -ForegroundColor Blue

    $destinationPath = $item.ServerItem.Replace($AutoDeployDir, $deployDirectory)
    Write-Host "Download to" $([IO.Path]::GetFullPath($destinationPath)) -ForegroundColor Blue

    if ($item.ItemType -eq "Folder") {
        New-Item $([IO.Path]::GetFullPath($destinationPath)) -ItemType Directory -Force
    }
    else {
        # Download the file (not folder) to destination directory
        $tfsVersionControl.DownloadFile($item.ServerItem, $([IO.Path]::GetFullPath($destinationPath)))
    }
}

 

TFS Power tools 2013 update 2 are released

The TFS Power tools 2013 update 2 are released a few days ago. Strange that there isn’t any notification in your Visual Studio 2013 environment. This update gives you better support for the new features in TFS 2013 Update 2.

You first have to uninstall the old version before installing the new version. If you want PowerShell support, you have to choose the custom installer in the wizard.

Download

You can download the update in the Visual Studio gallery by following this link:
http://visualstudiogallery.msdn.microsoft.com/f017b10c-02b4-4d6d-9845-58a06545627f

TFS delete build definition timeout

When you do a tfs delete on a build definition and you receive a timeout, you probably have to many builds in your TFS server. Even if you delete all the build for your build definition, tfs still stores all the builds in your TFS Server. Just like your TFS Team projects, you have to delete and then destroy the builds. After that, you could delete the build definition completely.

The only way to do this is, is to delete and destroy the builds in pieces. You can only do that by the command prompt.

I created a PowerShell script that loops through the builds for a specific build definition and deletes all the builds before a specific date. So if your build is maybe a year old, you could start the script a year back from now and loop through all the builds until let’s say two months ago. You could skip the number of days you want. I set the days to skip to 15 because I have a lot of builds each month and otherwise the TFS server has trouble to delete the bigger chunks.

$TfsCollectionUrl = "http://YourTfsServer:8080/tfs/YourTeamCollection"
$teamProject = "YourTeamProject"
$BuildDefinition = "YourTeamBuildDefinitionName"

Function CountForward {
    Param([datetime]$startDate,[int]$daysToSkip,[datetime]$endDate)

    Write-Host "Count forward from:" $startDate.ToString("yyyy-MM-dd") -foregroundcolor "magenta"
    Write-Host "Count forward until:" $endDate.ToString("yyyy-MM-dd")-foregroundcolor "magenta"
    Write-Host "Count every" $daysToSkip "day(s)" -foregroundcolor "magenta"

    while ($startDate -le $endDate) {
        $BuildDefinitionFull = $teamProject + "\" + $BuildDefinition
        $dateToQuery = $startDate.ToString("yyyy-MM-dd")

        Write-Host "Delete and destroy Builds before" $startDate.ToString("yyyy-MM-dd") "for build definition" $BuildDefinitionFull -foregroundcolor "magenta"
        
        tfsbuild.exe delete /server:$TfsCollectionUrl /builddefinition:"$BuildDefinitionFull" /daterange:~$dateToQuery /deleteoptions:All /noprompt /silent
        tfsbuild.exe destroy /server:$TfsCollectionUrl /builddefinition:"$BuildDefinitionFull" /daterange:~$dateToQuery /noprompt /silent
        
        $startDate = $startDate.AddDays($daysToSkip)
    }
}
CountForward -startDate (get-Date).AddDays(-300) -daysToSkip 15 -endDate (get-Date).AddDays(-60)

Save the PowerShell file as “DeleteBuildDefinition.ps1” and execute it in your Visual Studio command prompt. You can execute the PowerShell file in your VS command prompt with the following command:

PowerShell -Command "& {D:\DeleteBuildDefinition.ps1}"

tfs delete build definition timeout

Looping in PowerShell forward and backwards through days

I was searching for a way for looping in PowerShell forward and backwards through days every 15 days. So I thought I would share it with you guys.

I have two different samples. Both samples have the option to specify the number of the days to skip in each loop.

The first one is counting back in days until a specific day and the second is counting forward.

Countback

Function CountBack {
    Param([datetime]$startDate,[int]$daysToSkip,[datetime]$endDate)

    Write-Host "Count back from:" $startDate.ToString("yyyy-MM-dd")    
    Write-Host "Count back until:" $endDate.ToString("yyyy-MM-dd")
    Write-Host "Count every" $daysToSkip "day(s)"

    while ($startDate -ge $endDate) {
        Write-Host $startDate.ToString("yyyy-MM-dd")

        # Execute your code here
        
        $startDate = $startDate.AddDays(-$daysToSkip)
    }
}

Execute the command with:

CountBack -startDate (get-Date).AddDays(-12) -daysToSkip 15 -endDate (get-Date).AddDays(-100)

Countforward

Function CountForward {
    Param([datetime]$startDate,[int]$daysToSkip,[datetime]$endDate)

    Write-Host "Count forward from:" $startDate.ToString("yyyy-MM-dd")    
    Write-Host "Count forward until:" $endDate.ToString("yyyy-MM-dd")
    Write-Host "Count every" $daysToSkip "day(s)"

    while ($startDate -le $endDate) {
        Write-Host $startDate.ToString("yyyy-MM-dd")

        # Execute your code here
        
        $startDate = $startDate.AddDays($daysToSkip)
    }
}

Execute the command with:

CountForward -startDate (get-Date).AddDays(-300) -daysToSkip 15 -endDate (get-Date).AddDays(-40)

Custom domain url in IIS Express with Visual Studio 2013

If your creating a web application in Visual Studio 2013 (VS2013) and run it, your site is hosted in IIS express. Your url is localhost with a random portnumber.

If you want integration with Facebook, other services or just want a custom domain in your browser then you can follow these steps.

  1. Go to the properties of your (MVC) web application
  2. Go to the web tab on the left
  3. Under Servers check the Override application root URL and fill in http://YourSubDomain.YourDomain.com
  4. Hit Create Virutal Directory
  5. Change the start url above under Start Action to http://YourSubDomain.YourDomain.com
  6. Go to your IIS Express settings under C:\Users\Ralph\Documents\IISExpress\config and open the applicationhost.config file.
  7. Find your site and adjust the binding<bindings>
    <binding protocol=”http” bindingInformation=”*:80:YourSubDomain.YourDomain.com” />
    </bindings>
  8. Optionally add the binding for https (443)
  9. Go to your host file under C:\Windows\System32\drivers\etc and add 127.0.0.1 YourSubDomain.YourDomain.com
  10. Run your site

If you get an error. Try to run your Visual Studio instance as Administrator.