Creating a changelog file in TeamCity

As part of the deployment process it’s good to know what changes is in every build. The first step to accomplish this is to create a changelog file.
To-do that as part of the build process in TeamCity we need to write some custom PowerShell scripts.

Step 1 Create an authorization head that we can use to call the TeamCity REST API. This can be one time off task or be part of the final script.

$acctname = "username";
$password = "password";
$header = @{Authorization = 'Basic ' + [Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes("$($acctname):$($password)"));

Step 2 Add an parameter named “ChangelogFile” with the full file path for the new changelog file. For example “%env.TMP%\Changelog.txt”

Step 3 Add a new build PowerShell build step for the build.

Step 4 Add bellow script

$buildId = %teamcity.build.id%
$teamCityUrl = "http://fsdfdsfsd:8088"
$header = @{ "Authorization" = "Basic cWc1KT1SZXbbb3c4" }
$buildChangesUrl = $teamCityUrl + "/httpAuth/app/rest/changes?build=id:" + $buildId
 
$buildChangesResponse = Invoke-WebRequest $buildChangesUrl -Headers $header | select -ExpandProperty Content
$changesList = ([Xml]$buildChangesResponse).GetElementsByTagName("change")
 
$stream = [System.IO.StreamWriter] "%ChangelogFile%"
Foreach ($buildChange in $changesList)
{    
    $changeUrl = $teamCityUrl  + $buildChange.href
    $changeResponse = Invoke-WebRequest $changeUrl -Headers $header | select -ExpandProperty Content
    $change = ([Xml]$changeResponse).GetElementsByTagName("change")
    $version = $change.version
    $user =$change.username
    $comment = $change.GetElementsByTagName("comment").InnerXml
    $stream.WriteLine("Commit #" +$version + " :: " + $user)
    $stream.WriteLine("Comment:" + $comment)
 
    $stream.WriteLine("Files:")
    $files = $change.GetElementsByTagName("file")
    Foreach ($file in $files)
    {
        $stream.WriteLine($file.file)
    }
    $stream.WriteLine("")
}
$stream.Flush()
$stream.close()

Step 5 Add this file to the artefact output, nuget package or attach the data to an Octopus Deploy release note for example.

5.00 avg. rating (92% score) - 1 vote

Change vmsize for a azure cloud service in octopus deploy

A common cases when running a test/staging and production environment using Azure Cloud Service is that you don’t want to use the same vmsize for all environments. It can actual be very expensive. To solve this we need to-do some variable substitution in the azure cloud service package. Doing variable substitution on ServiceConfiguration.XXX.cscfg is done by default when using the built in template “Deploy an Azure Cloud Service” but it doesn’t support variable substitution on ServiceDefinition.csde by default where the vmsize is defined. To accomplish this we need to create a pre-deployment PowerShell script.

1. In you process step based on “Deploy an Azure Cloud Service” click on enable features
2. Check mark “Custom deployment scripts” and apply the change
3. You can now add script for pre-deployment, deployment, and post-deployment.
4. The last thing is to add the variable Deploy.WebApiRoleVMSize setting the vmsize you want for different scope or environments.

Add this scrip to the “Pre-deployment” section and replace “NNN” with the name of the webrole.

write-host "Starting Service Definition rewrite" 
 
$def = "ServiceDefinition\ServiceDefinition.csdef"
 
$csdeffile = Get-Item $def 
[xml]$csdefcontent = get-content $csdeffile 
 
#Find the webrole we want in a multi-webrole cloud service. Assume this exists 
$webrole = $csdefcontent.ServiceDefinition.WebRole | where {$_.name -eq "NNNN"}
 
##Update the vmsize. Add if not available 
$webrole.SetAttribute("vmsize",$OctopusParameters['Deploy.WebApiRoleVMSize']) 
write-host "Using VMSize '" $OctopusParameters['Deploy.WebApiRoleVMSize'] "' for NNNNN"
 
#Save the changes 
$csdefcontent.Save($csdeffile.FullName)
 
write-host "Service Definition rewrite successful"
5.00 avg. rating (92% score) - 1 vote

Code challenge from lantmateriet.se

Yesterday I found a code challenge on http://www.lantmateriet.se/knackkoden that I decide to take a look on. We first need to decode the base 64 encoded text to get the actual instructions.

Problem
Gör ett program som skriver ut talen i mängden {1, 2, …, 10500} i stigande ordning.
Om talet är jämnt delbart med 3 så ska www. skrivas ut istället för själva talet.
Om talet är jämnt delbart med 5 så ska lantmateriet skrivas ut istället för själva talet.
Om talet är jämnt delbart med 7 så ska .se skrivas ut istället för själva talet.
Om talet är jämnt delbart med 3, 5 och 7 så ska www.lantmateriet.se skrivas ut istället för själva talet.
Inga nyradstecken ska skrivas ut, utan allt ska skrivas ut på en och samma rad.
Utskriften inleds enligt nedan:
1 2 www. (och så vidare…)
Inget mellanrum som sista tecken.
MD5-summan av utskriften är 402087a86f4fbd5d01d80baec13fddc5
Men vad är SHA-1-summan?

Solution
Anyone that can come up with another or better solution?

public static void Main()
{
    const int NumberOfIterations = 10500;
    var sb = new StringBuilder();
    for (var i = 1; i <= NumberOfIterations; i++)
    {
        if (i % 3 != 0 && i % 5 != 0 && i % 7 != 0)
        {
            sb.Append(i + " ");
            continue;
        }
 
        if (i % 3 == 0 && i % 5 == 0 && i % 7 == 0)
        {
            sb.Append("www.lantmateriet.se ");
            continue;
        }
 
        if (i % 3 == 0)
        {
            sb.Append("www. ");
        }
        else if (i % 5 == 0)
        {
            sb.Append("lantmateriet ");
        }
        else if (i % 7 == 0)
        {
            sb.Append(".se ");
        }
    }
 
    var resultString = sb.ToString().TrimEnd();
    if ("402087a86f4fbd5d01d80baec13fddc5" != GetMD5HashData(resultString, Encoding.UTF8))
    {
        Console.WriteLine("Error missmatch MD5 hash");
        return;
    }
 
    Console.WriteLine("The SHA1 hash is:" + GetSHA1HashData(resultString, Encoding.UTF8));
}

You can download the complete solution here: LantmaterietKnackkoden

0.00 avg. rating (0% score) - 0 votes

Dear Engineers, your job is not to write code!

I recently found a blog post by Laura Klein that did really speak to me. It simple state that our job is not to produce as much code as possible, we are suppose to add value for the customer. This implies that it’s more important to know the customer and to add metrics that proves that we are actually adding value instead of simple hacking according to specification.

You can read the full blog here.

0.00 avg. rating (0% score) - 0 votes

Get full qualified host name in WIX

In one of our install we need to know the full qualified computer name during installation. For that purpose I did create an custom action with a simple vbscript that simple get the computer name and domain name and create the full qualified string and set property FQDN accordingly.

<CustomAction Id="FindHostDomain" Script="vbscript" Return="check">
  <![CDATA[
  Dim Info
  Set Info = CreateObject("AdSystemInfo")
  Dim InfoNT
  Set InfoNT = CreateObject("WinNTSystemInfo")
 
  Session.Property("FQDN") = lcase(InfoNT.ComputerName & "." & Info.DomainDNSName)
  ]]>
</CustomAction>

To execute this script and set FQDN so we can use it in our component’s add this to the install execution sequence.

<InstallExecuteSequence>
  <Custom Action="FindHostDomain" Before="CostInitialize" >Not Installed</Custom>
</InstallExecuteSequence>
0.00 avg. rating (0% score) - 0 votes

Finding compilation error for aspx files

Some time ago I did a bigger refactoring in many of our *.aspx files. The big problem was not the refactoring itself but to find all small typos and similar. If you make a typo or a syntax error in a *.cs file you will get a compilation error when building the application, but that is not the case for *.aspx files. So to increase confidence of my work I decided to use the aspnet_compiler that is used to pre compile a site for performance reasons. It was so successful that we decided to add it to our build infrastructure to catch all of this errors early.
 
Example (just replace the C:\apps\MySite to your output directory when building your site/app.
C:\Program Files (x86)\Microsoft Visual Studio 11.0>aspnet_compiler -p C:\apps\MySite -v /

0.00 avg. rating (0% score) - 0 votes

Dynamically load and execute JavaScript method

This is a simple solution to load a JavaScript dynamically and call a method when done. For more advance solution take a look at requirejs.
 
1. Check what protocol we are currently using to ensure we are using a secure connection if current page is using secure connection to load our new JavaScript file.
2. Create a new script element
3. Create a callback function with a simple counter that will ensure that we only execute the new method once
4. Attach the new created callback function to onreadystatechange to be used be Internet Explorer and onload for other browsers. In onreadystatechange we add additional checks of the state to ensure we are only calling the callback when the dynamically script is completely loaded.
5. The last thing that is need is to add the new created script element to the header of current page.

var src;
if (document.location.protocol === ""https:"")
{
  src = 'https://mysite.com/thescript.js';}
else
{
  src = 'https//mysite.com/thescript.js';
}
        
var script = document.createElement('script');
script.src = src;
script.type = 'text/javascript';
        
var head = document.getElemenscriptByTagName('head')[0];
var called= 0;
var callback = function()
{
  if( called== 0)
  {
    called++;
    MethodInTheScript();
  }
};
 
// Inter explorer 
script.onreadystatechange = function()
{
  if (this.readyState == 'complete')
  {
    callback();
  }
}
 
//Other browsers
script.onload = callback;       
head.appendChild(script);
4.67 avg. rating (91% score) - 3 votes

System.UnauthorizedAccessException: Access to the path is denied when using System.OI.FileStream

During a recent deployment to production environment we started to get System.UnauthorizedAccessException on places we hade not seen it before. After some debugging a pattern appeared. The problem is that if you don’t use the FileStream constructor with FileAccess parameter you will get FileAccess.ReadWrite selected by default. So in our cases the User account running our service did only have read access to the file with is correct since we are only expecting to read from the file.

using (FileStream fs = new FileStream(filePath, FileMode.Open))
{}

Make sure, you ask for what you really need. If your application needs only Read access to a file, make sure to specify that in your FileStream constructor.

using (FileStream fs = new FileStream(filePath, FileMode.Open, FileAccess.Read))
{}
0.00 avg. rating (0% score) - 0 votes

HowTo add new key to appsettings with WIX

When adding a new key for appsettings you need to split the action in 3 steps.
1. Create the new empty “add” element
2. Create and set the “key” attribute. The tricky part here is how to add the “key” to new created “add” element from step one. One way is to assume that the new created “add” element is the only element without a “key” attribute
3. Create and set the “value” attribute.

<util:XmlFile 
  Id='conf1' 
  Action="createElement" 
  ElementPath="//configuration/appSettings" 
  Name="add" 
  File="[INSTALLDIR]Web.config" 
  Sequence="1" />
<util:XmlFile 
  Id='conf2' 
  Action="setValue" 
  ElementPath="//configuration/appSettings/add[\[]not(@key)[\]]"
  Name="key" 
  Value="NewConfig" 
  File="[INSTALLDIR]Web.config" 
  Sequence="2" />
<util:XmlFile 
  Id='conf3' 
  Action="setValue" 
  ElementPath="//configuration/appSettings/add[\[]@key='NewConfig'[\]]"
  Name="value" 
  Value="the new value" 
  File="[INSTALLDIR]Web.config"
  Sequence="3" />
0.00 avg. rating (0% score) - 0 votes

HowTo to set appsettings during installation using WIX

The point where most people have a hard time is remembering is to escape brackets. This to ensure WIX will not think it a property. If you forget to-do this you may end up in a situation where “config2” value will bet set on “config1” instead. See a correct example bellow

[ should be [\[]
] =should be [\]]

<Component Id='UpdateConfig' Guid='A89D47AF-7DBE-4a8d-9848-F35C78FD95ED' DiskId='1' KeyPath="yes">
  <util:XmlFile Id='config1' 
                Action="setValue" 
                ElementPath="//configuration/appSettings/add[\[]@key='FirstKey'[\]]/@value"
                File="[INSTALLDIR]Web.config" 
                Value="[NEWVALUE1]" />
  <util:XmlFile Id='config2' 
                Action="setValue" 
                ElementPath="//configuration/appSettings/add[\[@key='SecondKey'[\]]/@value"
                File="[INSTALLDIR]Web.config" 
                Value="[NEWVALUE2]" />
</Component>

And the example web.config

<?xml version="1.0"?>
<configuration>
  <appSettings>
    <add key="FirstKey" value="oldvalue"/>
    <add key="SecondKey" value="oldvalue"/>
  </appSettings>
</configuration>
2.40 avg. rating (53% score) - 5 votes

Next Page »