How to sign assembly with as strong name in Visual Studio Team Services?

I have self generated password protected key.pfx. That key.pfx file is used to sign some assemblies in my solution. I tried to build that solution in Visual Studio Team Services and it failed right away with the following error:

Error MSB3325: Cannot import the following key file: key.pfx. The key 
file may be password protected. To correct this, try to import the certificate again or manually 
install the certificate to the Strong Name CSP with the following key container name: 
VS_KEY_9000008CC1777

Quick googling shows that I need to run:

sn -i key.pfx VS_KEY_9000008CC1777

However this command will prompt a password, so this would not work for on a build server. I tried to use automated solution for the above command using following powershell script:

Param(
    [Parameter(Mandatory=$True,Position=1)]
    [string] $PfxFilePath,
    [string] $PfxPassword
)

# The path to the snk file we're creating
[string] $snkFilePath = [IO.Path]::GetFileNameWithoutExtension($PfxFilePath) + ".snk";

# Read in the bytes of the pfx file
[byte[]] $pfxBytes = Get-Content $PfxFilePath -Encoding Byte;

# Get a cert object from the pfx bytes with the private key marked as exportable
$cert = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2(
    $pfxBytes,
    $PfxPassword,
    [Security.Cryptography.X509Certificates.X509KeyStorageFlags]::Exportable);

# Export a CSP blob from the cert (which is the same format as an SNK file)
[byte[]] $snkBytes = ([Security.Cryptography.RSACryptoServiceProvider]$cert.PrivateKey).ExportCspBlob($true);

# Write the CSP blob/SNK bytes to the snk file
[IO.File]::WriteAllBytes([IO.Path]::Combine([IO.Path]::GetDirectoryName($PfxFilePath), $snkFilePath), $snkBytes);

However this didn't work for Visual Studio Teams Services hosted build agent. I suspect that hosted build server has some kind of environment protection and above certificate installation fails silently. Then I decided to extract key.snk file from key.pfx and use that file to sign assemblies. This approach works, but it is not secure, since I would need to store private unprotected key file in source control. So, I came up with the idea to dynamically extract key.snk using following powershell script:

Param(
    [Parameter(Mandatory=$True,Position=1)]
    [string] $PfxFilePath,
    [string] $PfxPassword
)

# The path to the snk file we're creating
[string] $snkFilePath = [IO.Path]::GetFileNameWithoutExtension($PfxFilePath) + ".snk";

# Read in the bytes of the pfx file
[byte[]] $pfxBytes = Get-Content $PfxFilePath -Encoding Byte;

# Get a cert object from the pfx bytes with the private key marked as exportable
$cert = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2(
    $pfxBytes,
    $PfxPassword,
    [Security.Cryptography.X509Certificates.X509KeyStorageFlags]::Exportable);

# Export a CSP blob from the cert (which is the same format as an SNK file)
[byte[]] $snkBytes = ([Security.Cryptography.RSACryptoServiceProvider]$cert.PrivateKey).ExportCspBlob($true);

# Write the CSP blob/SNK bytes to the snk file
[IO.File]::WriteAllBytes([IO.Path]::Combine([IO.Path]::GetDirectoryName($PfxFilePath), $snkFilePath), $snkBytes);

Then I added two variables to build definition:

CertPath = $(Build.SourcesDirectory)\key.pfx
CertPass = password (clicked on a lock to secure value of variable)

After that I added powershell task with following arguments:

-PfxFilePath "$(CertPath)" -PfxPassword "$(CertPass)"

This time the build passed and assemblies were signed successfully.

Posted on Monday, July 24, 2017 by | Add Comment

How to integrate CI/CD with Visual Studio Team Services?

Microsoft provides free Visual Studio Team Services for small team. It has free unlimited Git repositories and 4 hours per month to run builds, which should be more than enough for my projects.

I already integrated karpach.com front end and admin site. In this post I'll document how I integrated redboxnewreleases.com.

Step 1 Created build definition

My favorite browser is Google Chrome, but for below process I used Microsoft Edge, since some things didn't work in Google Chrome (e.g. reordering of build tasks).

I didn't use any templates and started with an empty process. First of all lets tried to build my solution. I used following tasks as is:

  1. Get sources (point to git repository)
  2. NuGet Restore **/*.sln (default settings)
  3. Build solution **/*.sln (default setting)

In my case the build failed with following error:

Error MSB4226: The imported project  "C:\Program Files (x86)\Microsoft Visual Studio\2017\
Enterprise\MSBuild\ExtensionPack\4.0\MSBuild.ExtensionPack.tasks" was not found.

In my previous CI system I used ExtensionPack for assembly version modification. This is old approach, so I just removed that task reference and used different approach to specify version later in the post.

I pushed a change, but it didn't trigger any build. I added a trigger then:

  1. Opened a Triggers tab.
  2. Enabled continuos integration trigger.
  3. Set repository setting to build on any master branch change.
  4. Went to options tab and set "Default agent queue": Hosted VS2017.
  5. Pressed Save & queue

This time build succeeded. Then I added assembly versioning back:

  1. Downloaded ApplyVersionToAssemblies.ps1 from https://github.com/tfsbuildextensions/CustomActivities.
  2. Deleted "$Env:TF_BUILD -and -not" from that powershell script.
  3. Replaced TF_BUILD with just BUILD in ApplyVersionToAssemblies.ps1.
  4. Added ApplyVersionToAssemblies.ps1 to Git repository.
  5. Went back to build definition web editor, switched to Variables tab. Added MajorVersion and MinorVersion variables there.
  6. Switched to the Options tab and filled "Build number format" field with $(BuildDefinitionName)_$(MajorVersion).$(MinorVersion).$(Year:yy)$(DayOfYear)$(Rev:.rr)).
  7. Made sure that AssemblyVersion and AssemblyFileVersion lines present in AssemblyInfo files (powershell script does replacement in those files).
  8. Added powershell task to run ApplyVersionToAssemblies.ps1.
  9. Moved up powershell task to run after Get sources task.

After that I modified msbuild task to produce a package for web deployment:

  1. Change build to build web csproj file instead of solution file
  2. Specified configuration: Release
  3. Used following MSBuild Arguments: /T:Package /P:PackageLocation=..\Artifacts\package.zip
  4. Added Publish Build Artifacts task: Path to Publish = Artifacts\package.zip, Artifact Name = Redbox

Build definition

Step 2 Created release definition

  1. Started from Empty template.
  2. Picked my build from previous step.
  3. Added a deployment group phase task.
  4. Added "IIS Web App Deploy (Preview)" task.
  5. Created deployment group.
  6. Ran generated powershell script on my VPS hosting server.
  7. Specified website name: redboxnewrleases.com.
  8. Specified package folder using browse button to point to package.zip: $(System.DefaultWorkingDirectory)/Redbox-CI/Redbox/package.zip).
  9. Set trigger for continuous deployment.
  10. Optionally modified release name format in General tab: Release-$(Build.BuildNumber))

Release definition

Posted on Saturday, July 8, 2017 by | Add Comment

How to get latest tweets from Twitter REST API?

My blog uses twitter REST API to pull up my latest tweets to the right side navigation. About a year ago twitter deprecated 1.0 API and now every request needs to be authenticated. According to twitter's documentation I can use application-only authentication, since I am only getting tweets and don't post anything.

Application-only authentication approach consists of two steps:


  1. Get access token from https://api.twitter.com/oauth2/token
  2. Use access token for any read only operations (get posts, friends, followers, user information or search tweets)

I didn't find any .NET bare-bones example that suits my needs. The code below don't need any third party libraries. It is .NET 4.5 (I used some dynamic and async / await).

Here is how you get access token (step 1 from the above):

public async Task<string> GetAccessToken()
{           
	var httpClient = new HttpClient();
	var request = new HttpRequestMessage(HttpMethod.Post, "https://api.twitter.com/oauth2/token ");
	var customerInfo = Convert.ToBase64String(new UTF8Encoding()
							  .GetBytes(OAuthConsumerKey + ":" + OAuthConsumerSecret));
	request.Headers.Add("Authorization", "Basic " + customerInfo);
	request.Content = new StringContent("grant_type=client_credentials", 
                                            Encoding.UTF8, "application/x-www-form-urlencoded");
															  
	HttpResponseMessage response = await httpClient.SendAsync(request);

	string json = await response.Content.ReadAsStringAsync();
	var serializer = new JavaScriptSerializer();
	dynamic item = serializer.Deserialize<object>(json);
	return  item["access_token"];            
}

Here is how you get tweets:

public async Task<IEnumerable<string>> GetTweets(string userName,int count, string accessToken = null)
{
	if (accessToken == null)
	{
		accessToken = await GetAccessToken();   
	}

	var requestUserTimeline = new HttpRequestMessage(HttpMethod.Get, 
		string.Format("https://api.twitter.com/1.1/statuses/user_timeline.json?
                               count={0}&screen_name={1}&trim_user=1&exclude_replies=1", count, userName));
	requestUserTimeline.Headers.Add("Authorization", "Bearer " + accessToken);
	var httpClient = new HttpClient();
	HttpResponseMessage responseUserTimeLine = await httpClient.SendAsync(requestUserTimeline);
	var serializer = new JavaScriptSerializer();
	dynamic json = serializer.Deserialize<object>(await responseUserTimeLine.Content.ReadAsStringAsync());
	var enumerableTweets = (json as IEnumerable<dynamic>);

	if (enumerableTweets == null)
	{
		return null;
	}
	return enumerableTweets.Select(t => (string)(t["text"].ToString()));                        
}

See complete console application at github repository Twitter .NET C# Sample Application

Posted on Monday, August 18, 2014 by | Comments (12) | Add Comment

Categories

Recent Tweets

  • Simon Ince's Blog: Hierarchies with HierarchyID in SQL 2008 http://t.co/xSDwiF6rRS.
  • Visual Studio 2010 WAS painfully slow - CodeProject http://t.co/Usba1x6CZy

Valid HTML5