Taeguk.co.uk Dave Shaw's Development Blog https://taeguk.co.uk/ Tue, 09 May 2017 12:38:48 +0000 Tue, 09 May 2017 12:38:48 +0000 Jekyll v3.4.3 Controlling VS2017 Developer Console Start Directory <p>At work I use <a href="https://conemu.github.io/">ConEmu</a> for my console, it’s a great console to work on Windows with. To keep things tidy I have all my code on my <code class="highlighter-rouge">X:\</code> partition. In ConEmu I have different “Tasks” setup for different configurations of Visual Studio and pass <code class="highlighter-rouge">/Dir X:\</code> as one of the task parameters so that a new Console’s current Dir is <code class="highlighter-rouge">X:\</code>.</p> <p><img src="/blog/Content/vs2017-cmd-conemu.png" alt="ConEnum Settings" /></p> <p>When running “Developer Command Prompt for VS 2017” on my work computer I noticed that the directory it was opening in wasn’t the current directory that ConEmu was setting, but <code class="highlighter-rouge">C:\Dave\Source</code>.</p> <pre><code class="language-plain">********************************************************************** ** Visual Studio 2017 Developer Command Prompt v15.0.26228.9 ** Copyright (c) 2017 Microsoft Corporation ********************************************************************** C:\Dave\Source&gt; </code></pre> <p>After a bit of digging through the batch files I found the reason for this is because of this bit code in:</p> <p><code class="highlighter-rouge">C:\Program Files (x86)\Microsoft Visual Studio\2017\Enterprise\Common7\Tools\vsdevcmd\core\vsdevcmd_end.bat</code></p> <pre><code class="language-batch">... @REM Set the current directory that users will be set after the script completes @REM in the following order: @REM 1. [VSCMD_START_DIR] will be used if specified in the user environment @REM 2. [USERPROFILE]\source if it exists @REM 3. current directory if "%VSCMD_START_DIR%" NEQ "" ( cd /d "%VSCMD_START_DIR%" ) else ( if EXIST "%USERPROFILE%\Source" ( cd /d "%USERPROFILE%\Source" ) ) ... </code></pre> <p>As you can see, it has two chances to pick a different directory before using your current one.</p> <p>In my case, I had a folder at <code class="highlighter-rouge">%USERPROFILE%\Source</code>, which was empty, so I deleted it.</p> <p>The other alternative is to set the <code class="highlighter-rouge">VSCMD_START_DIR</code> environment variable for your user account to your preferred directory.</p> Tue, 28 Mar 2017 09:20:00 +0000 https://taeguk.co.uk/blog/controlling-vs2017-developer-console-start-directory/ https://taeguk.co.uk/blog/controlling-vs2017-developer-console-start-directory/ blog Visual Studio TFS 2015 does not support 2012 build agents <p>This post is part PSA, part debugging story.</p> <p>The important bit:</p> <h1 id="team-foundation-server-2012-xaml-build-agents-do-not-work-with-tfs-2015">Team Foundation Server 2012 XAML Build Agents do not work with TFS 2015</h1> <p>I discover this fact the weekend just gone whilst performing an upgrade to TFS 2015.3 from TFS 2012.4.</p> <p>The plan was to only upgrade the TFS Server and leave the build infrastructure running on TFS 2012. This seemed like a sound idea as I know Microsoft care about compatibility, and the upgrade was more complicated than your usual one. I figured it would just keep working and that I’d upgrade the build agents later, boy was I wrong.</p> <p>I may have even checked the <a href="https://www.visualstudio.com/en-us/docs/setup-admin/requirements">documentation</a>, which does not show a compatibility, but it isn’t explicitly called out, so I could have glanced over it.</p> <p><img src="/blog/Content/tfs2015-build-compat-table.png" alt="TFS build compatibility" /> <em>Look - No 2012</em></p> <p>The problems with TFS 2012 build agents against TFS 2015 manifested as two different errors when I queued a build without a Drop Location. Queuing a build with a drop location worked just fine.</p> <h2 id="error-1---build-agents-not-using-the-fqdn">Error 1 - Build agents not using the FQDN</h2> <p>The build infrastructure runs on a different domain to the Team Foundation Server.</p> <p>We have <code class="highlighter-rouge">tfs-server.corp.com</code> for TFS and <code class="highlighter-rouge">build-server.corp-development.com</code> for builds.</p> <p>The error manifested as:</p> <p><img src="/blog/Content/tfs2015-build-fqdn-error.png" alt="FQDN error message" /></p> <p>The error that appeared twice was not very helpful.</p> <blockquote> <p>An error occurred while copying diagnostic activity logs to the drop location. Details: An error occurred while sending the request.</p> </blockquote> <p>I eventually debugged this (details later) and found out that the last task on the build agent was trying to access <code class="highlighter-rouge">tfs-server</code> with no DNS suffix of <code class="highlighter-rouge">.corp.com</code> to publish some logs. As a temporary workaround I bobbed an entry in the <a href="https://en.wikipedia.org/wiki/Hosts_(file)">hosts</a> file entry to make <code class="highlighter-rouge">tfs-server</code> point to the actual IP of the TFS server.</p> <h2 id="error-2---the-bad-request">Error 2 - the bad request</h2> <p>With the all the steps of the build resolving the server name, I came across the second error.</p> <p><img src="/blog/Content/tfs2015-build-bad-request.png" alt="Bad request error message" /></p> <p>The error message was still no more use than the last one:</p> <blockquote> <p>An error occurred while copying diagnostic activity logs to the drop location. Details: TF270002: An error occurred copying files from ‘C:\Users\tfsbuild\AppData\Local\Temp\BuildAgent\172\Logs\151436\LogsToCopy\ActivityLog.AgentScope.172.xml’ to ‘ActivityLog.AgentScope.172.xml’. Details: BadRequest: Bad Request</p> <p>An error occurred while copying diagnostic activity logs to the drop location. Details: An error occurred while sending the request.</p> </blockquote> <p>My debugging would lead me to see that this was caused by TFS returning an HTTP 400 (Bad Request) for the exact same step as the first error.</p> <p>It was at this point I figured something was really wrong and started searching for compatibility problems. In my effort to find a KB or update I re-checked the documentation and noticed the lack of support as well as finding <a href="https://social.msdn.microsoft.com/Forums/office/en-US/68d84ffc-3bcc-41cc-80f0-8fc778894ee4/tfs-online-build-fails-on-local-build-server-with-tf270016-tf270002?forum=tfsbuild">an MSDN forum post</a> from RicRak where they solved the problem by upgrading their agents off of TFS 2012.</p> <h2 id="solution">Solution</h2> <p>My solution was to upgrade our entire build infrastructure (some 9/10 servers) to TFS 2015, and discovering you <strong>must</strong> install VS2015 on the servers too to get the Test Runner to work.</p> <p>One day of diagnosis and testing to get to the point of knowing TFS 2015 build agents would solve the problem <strong>and</strong> still build our codebase. Another half-day was spend upgrading all the servers.</p> <h1 id="diagnostics">Diagnostics</h1> <p>How do you figure out when something like this goes wrong? TFS diagnostic logging did not provide any more information than minimum logging did. The error only appeared at the very end of a build, it wasn’t related to a step in the XAML workflow, nor any variables in the build process.</p> <p>The solution (as always) came from <a href="http://stackoverflow.com/questions/15143107/httpclient-httprequestexception">Charlie Kilian</a> on Stack Overflow.</p> <p>I stopped the Build Service and opened up <code class="highlighter-rouge">TFSBuildServiceHost.exe.config</code> and added the following section:</p> <div class="language-xml highlighter-rouge"><pre class="highlight"><code><span class="nt">&lt;system.diagnostics&gt;</span> <span class="nt">&lt;sources&gt;</span> <span class="nt">&lt;source</span> <span class="na">name=</span><span class="s">"System.Net"</span> <span class="na">tracemode=</span><span class="s">"includehex"</span> <span class="na">maxdatasize=</span><span class="s">"1024"</span><span class="nt">&gt;</span> <span class="nt">&lt;listeners&gt;</span> <span class="nt">&lt;add</span> <span class="na">name=</span><span class="s">"System.Net"</span><span class="nt">/&gt;</span> <span class="nt">&lt;/listeners&gt;</span> <span class="nt">&lt;/source&gt;</span> <span class="nt">&lt;/sources&gt;</span> <span class="nt">&lt;switches&gt;</span> <span class="nt">&lt;add</span> <span class="na">name=</span><span class="s">"System.Net"</span> <span class="na">value=</span><span class="s">"Verbose"</span><span class="nt">/&gt;</span> <span class="nt">&lt;/switches&gt;</span> <span class="nt">&lt;sharedListeners&gt;</span> <span class="nt">&lt;add</span> <span class="na">name=</span><span class="s">"System.Net"</span> <span class="na">type=</span><span class="s">"System.Diagnostics.TextWriterTraceListener"</span> <span class="na">initializeData=</span><span class="s">"C:\Logs\network.log"</span> <span class="nt">/&gt;</span> <span class="nt">&lt;/sharedListeners&gt;</span> <span class="nt">&lt;trace</span> <span class="na">autoflush=</span><span class="s">"true"</span><span class="nt">/&gt;</span> <span class="nt">&lt;/system.diagnostics&gt;</span> </code></pre> </div> <p>Then restarted the build service and ran the smallest build I could to produce minimal logs.</p> <p>The log folder looked something like this:</p> <p><img src="/blog/Content/tfs2015-build-log-files.png" alt="Log files on disk" /></p> <p>The <code class="highlighter-rouge">network.log</code> file had a few errors, but nothing fatal looking, so I looked in the other files for errors and finally found this line:</p> <pre><code class="language-plain">System.Net Error: 0 : [4916] Exception in HttpWebRequest#13319471:: - The remote name could not be resolved: 'tfs-server'. </code></pre> <p>That was proceeded by:</p> <pre><code class="language-plain">System.Net Verbose: 0 : [4928] HttpWebRequest#13319471::HttpWebRequest(http://tfs-server:8080/tfs/DefaultCollection/_apis/resources/containers/122598?itemPath=logs%2FActivityLog.AgentScope.172.xml#752534963) </code></pre> <p>Here you can see the server name without the necessary DNS suffix during some HTTP POST to <code class="highlighter-rouge">_apis/resources/containers</code>.</p> <p>This was the point I added the hosts file entry and then got the next error.</p> <p>For the second error I repeated the diagnostic logging steps and this time found the following errors (searching for Bad Request):</p> <pre><code class="language-plain">System.Net Information: 0 : [16628] Connection#50276392 - Received status line: Version=1.1, StatusCode=400, StatusDescription=Bad Request. </code></pre> <p>By tracing the ID (in this case <code class="highlighter-rouge">16628</code>) back up the file I found it was a call to the same endpoint, but this time a PUT:</p> <pre><code class="language-plain">System.Net Information: 0 : [16628] HttpWebRequest#9100089 - Request: PUT /tfs/DefaultCollection/_apis/resources/containers/122603?itemPath=logs%2FActivityLog.AgentScope.59.xml HTTP/1.1 </code></pre> <p>This was the point I gave up thinking this could be fixed by a configuration change.</p> <h1 id="conclusion">Conclusion</h1> <p>I wish I had read something like this before I planned the weekend. I did do testing, but because testing TFS in live is risky I had most of the test instance network isolated and that required a lot of configurations; I just thought this error was just configuration based, lesson well and truly learned.</p> <p>It would have been nice to see this called out more explicitly on MSDN. In my opinion these are two bugs that Microsoft decided not to fix in the TFS 2012 product life-cycle.</p> <p>On the plus side, I learned some really neat debugging skills I didn’t know before.</p> <p>Remember, if you’re upgrading from TFS 2012, plan to upgrade your build agents at the same time!</p> Thu, 10 Nov 2016 20:49:00 +0000 https://taeguk.co.uk/blog/tfs2015-does-not-support-2012-build-agents/ https://taeguk.co.uk/blog/tfs2015-does-not-support-2012-build-agents/ blog TFS Deployment Pipeline with VSTS and Release Management <p>Back in 2014 I wrote a <a href="/blog/unc-to-uri-path-converter/">UNC to URI Path Converter</a> using ASP MVC 4 and Visual Studio Team Services with a XAML Build process template to continuously deploy the changes to an Azure Website. This was my first Azure Website and most of it was just using the default settings from the New Project dialog in Visual Studio, all very “point and click”.</p> <p>It worked well and had an average of a few hundred page requests a week and so far, I’ve been happy with everything as it “just worked”. The other day I wanted to add a small feature and noticed that after pushing and deploying the change that Azure was warning me XAML builds would soon be deprecated. So, whilst I was making some changes I decided it would be a good opportunity for me to get up to date on a few new technologies that I have not used in anger.</p> <p>I planned to setup the following for the website:</p> <ul> <li>Rewrite in .NET Core.</li> <li>Custom VSTS Build vNext.</li> <li>Deployment Pipeline using Microsoft Release Management.</li> </ul> <h1 id="rewrite-in-net-core">Rewrite in .NET Core</h1> <p>My previous .NET Core app at this point was a console application, so I took this as an opportunity to get to grips with setting up a build and a suite of unit tests using xUnit.net. Getting this working in Visual Studio was straight forward following the <a href="https://xunit.github.io/docs/getting-started-dotnet-core.html">xUnit.net documentation</a>, but getting the build to run on VSTS was a bit hit and miss. I eventually settled on a mix-match combination of <code class="highlighter-rouge">dotnet</code> command line tools and the Visual Studio Test Runner.</p> <p><img src="/blog/Content/deployment-pipeline-build-steps.png" alt="VSTS Build Steps" /></p> <p>Using the VS Test step solved the problem with <code class="highlighter-rouge">dotnet test</code> not been able to run the xUnit.net tests on the build server. I kept the individual <code class="highlighter-rouge">dotnet restore</code>, <code class="highlighter-rouge">dotnet publish</code> (site) and <code class="highlighter-rouge">dotnet build</code> (tests) as I wanted control over the <code class="highlighter-rouge">publish</code>. I also have a suite of deployment tests that based on the Full .NET Framework which I build using VS Build. These were the building blocks of my pipeline.</p> <h1 id="custom-vsts-build-vnext">Custom VSTS Build vNext</h1> <p>By keeping control over <code class="highlighter-rouge">dotnet publish</code> I could <a href="https://docs.asp.net/en/latest/publishing/vsts-continuous-deployment.html">pack</a> the website ready to by pushed to Azure using Microsoft Release Management. I took the output of <code class="highlighter-rouge">dotnet publish</code> and zipped it up into an archive and published this as a build artifact.</p> <p>The build process also took the output of DeploymentTests build and zipped it into a separate archive and published that too.</p> <p>I now had a website and a suite of “Deployment Tests” as artifacts from my build.</p> <h1 id="deployment-pipeline-using-microsoft-release-management">Deployment Pipeline using Microsoft Release Management</h1> <p>A deployment pipeline is where code goes through various stages and <em>each stage provides increasing confidence, usually at the cost of extra time</em> (Martin Fowler: <a href="http://martinfowler.com/bliki/DeploymentPipeline.html">DeploymentPipeline</a>). My pipeline was quite simple:</p> <pre><code class="language-plain">Build -&gt; Fast Tests -&gt; Deploy to Pre-Prod -&gt; Test Via API -&gt; Deploy to Live -&gt; Test Via API </code></pre> <p>This process meant that the build was fast and only ran isolated fast unit tests against the code. Only then did it deploy onto a Pre-Production server (another Free Azure Website), and run a set of integration tests against the Website via the API, if these tests passed, then I repeated the process onto the Live website.</p> <p>Using Microsoft Release Management, I was able to orchestrate this using a single Release definition, and defining two environments to deploy to.</p> <p><img src="/blog/Content/deployment-pipeline-release.png" alt="Release Management" /></p> <p>I considered using Deployment Slots on Azure to do a deploy and then <em>swap</em> to the Slots after the tests passed, but Slots are only available on the Standard pricing tier and I wanted to keep this free, so I setup <a href="http://pathconverter-pp.azurewebsites.net">another free Website instance</a> and ran the tests on there.</p> <p>I used a Variable against each Environment in Release Management to store the Azure Website Name.</p> <p><img src="/blog/Content/deployment-pipeline-release-vars.png" alt="Environment's variables" /></p> <p>These variables had two uses, the first was to keep the steps for each environment the same, I only need to set the variable to a different value.</p> <p>The second was very cool, because the variables in TFS Build and RM are actually environment variables I could write the following method in the code of my deployment tests:</p> <pre><code class="language-C#">public static String BaseUri =&gt; $"http://{Environment.GetEnvironmentVariable("AzureWebSiteName")}.azurewebsites.net/"; </code></pre> <p>And then run the API integration tests against the value of <code class="highlighter-rouge">BaseUri</code>.</p> <p>I planned to write some User Interface tests using either Coded-UI or Selenium, but due to the Hosted Build agents not supported Interactive Mode which is needed to run User Interface tests, I made them conditional and they only run in Visual Studio locally. I do have a plan to get these running in the future.</p> <p>The whole process looks like this:</p> <p><img src="/blog/Content/deployment-pipeline-flowchart.png" alt="Deployment Pipeline Flowchart" /></p> <h1 id="conclusion">Conclusion</h1> <p>Whilst this is a massively over engineered solution for such a simple website, it was fun to learn some new tricks and understand how to put a release pipeline together using the VSTS and Azure platforms. I also used it as opportunity to tidy up my resources in Azure and consolidate all my related resources into an Azure RM Resource Group, including the Application Insights I use to monitor it.</p> Mon, 31 Oct 2016 20:34:00 +0000 https://taeguk.co.uk/blog/deployment-pipeline-with-vsts-and-release-management/ https://taeguk.co.uk/blog/deployment-pipeline-with-vsts-and-release-management/ blog VSTS .NET Core Release Management Now using SSL <p>Today I’ve changed over to using SSL by default.</p> <p><img src="/blog/Content/ssl.png" alt="SSL in Chrome" /></p> <p>The main reason for moving is that SSL <a href="http://www.troyhunt.com/2015/08/were-struggling-to-get-traction-with.html">gives better SEO</a> - and that my old blog was SSL so I’m sure there will be some SSL links scattered about the web. It also prevents any silly public networks injecting anything into any of my pages.</p> <p>I’m using CloudFlare to secure to the communications from your browser to them. Thanks to Sheharyar Naseer for his <a href="https://sheharyar.me/blog/free-ssl-for-github-pages-with-custom-domains/">excellent guide</a> that got me up and running in no time, and to <a href="https://dnsimple.com">DNSimple</a> for their excellent DNS Service that made it a piece of cake changing my Nameservers.</p> Sun, 03 Apr 2016 15:49:00 +0000 https://taeguk.co.uk/blog/now-using-ssl/ https://taeguk.co.uk/blog/now-using-ssl/ blog Meta Using SignalR in FSharp without Dynamic <p>I’ve been building an FSharp Dashboard by following along <a href="http://coding.fitness/f-powered-realtime-dashboard/">this post</a> from <a href="https://github.com/lbacaj">Louie Bacaj’s</a> which was part of last years FSharp Advent calendar. I have to say it’s a great post and has got me up and running in no time.</p> <blockquote> <p>If you want to skip the story and get to the FSharp and SignalR part scroll down to <strong>Changing the Hub</strong>.</p> </blockquote> <p>One small problem I noticed was that I could not use any of the features of FSharp Core v4. For example, the new <code class="highlighter-rouge">tryXXX</code> functions such as <code class="highlighter-rouge">Array.tryLast</code> were not available.</p> <p>After a bit of digging I happened across the Project Properties which were stuck on <code class="highlighter-rouge">3.1.2.1</code>.</p> <p><img src="/blog/Content/fsharp-signalr-project-props.png" alt="Project Properties" /></p> <p>Turns out that the <code class="highlighter-rouge">FSharp.Interop.Dynamic</code> package is dependant on <code class="highlighter-rouge">FSharp.Core v3.1.2.1</code>.</p> <p>So this turned into a challenge of how do I use SignalR without Dynamic. After a bit of googling I landed on <a href="http://www.asp.net/signalr/overview/guide-to-the-api/hubs-api-guide-server#stronglytypedhubs">this page</a> that showed Strongly Typed Hubs. So I knew it was possible…</p> <h1 id="removing-dependencies">Removing Dependencies</h1> <p>The first step to fixing this was to remove the <code class="highlighter-rouge">FSharp.Core</code> dependencies I no longer needed, these were:</p> <div class="language-posh highlighter-rouge"><pre class="highlight"><code>Uninstall-Package FSharp.Interop.Dynamic Uninstall-Package Dynamitey Uninstall-Package FSharp.Core </code></pre> </div> <p>I then just browsed through the source and removed all the <code class="highlighter-rouge">open</code> declarations.</p> <h1 id="re-adding-fsharp-core">Re-adding FSharp Core</h1> <p>Slight problem now, I no longer had any FSharp Core references, so I needed to add one in. I’m not sure if this is the best way to solve this, but I just copied and pasted these lines from a empty FSharp project I just created:</p> <div class="language-xml highlighter-rouge"><pre class="highlight"><code><span class="nt">&lt;Reference</span> <span class="na">Include=</span><span class="s">"mscorlib"</span> <span class="nt">/&gt;</span> <span class="c">&lt;!--Add this bit--&gt;</span> <span class="nt">&lt;Reference</span> <span class="na">Include=</span><span class="s">"FSharp.Core, Version=$(TargetFSharpCoreVersion), Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"</span><span class="nt">&gt;</span> <span class="nt">&lt;Private&gt;</span>True<span class="nt">&lt;/Private&gt;</span> <span class="nt">&lt;/Reference&gt;</span> <span class="c">&lt;!--End--&gt;</span> <span class="nt">&lt;Reference</span> <span class="na">Include=</span><span class="s">"Newtonsoft.Json"</span><span class="nt">&gt;</span> </code></pre> </div> <h1 id="changing-the-hub">Changing the Hub</h1> <p>Now all I had to do was update the code to use the statically typed hub.</p> <p>First step was to create an interface for the <code class="highlighter-rouge">metricsHub</code>:</p> <pre><code class="language-fsharp">type IMetricsHub = abstract member AddMessage: string -&gt; unit abstract member BroadcastPerformance: PerfModel seq -&gt; unit </code></pre> <p>Then change our <code class="highlighter-rouge">Hub</code> to inherit from the generic <code class="highlighter-rouge">Hub&lt;T&gt;</code>:</p> <pre><code class="language-fsharp">[&lt;HubName("metricsHub")&gt;] type metricsHub() = inherit Hub&lt;IMetricsHub&gt;() // &lt; Generic version of our interface. </code></pre> <p>And changed all the calls from:</p> <pre><code class="language-fsharp">Clients.All?message(message) </code></pre> <p>to</p> <pre><code class="language-fsharp">Clients.All.Message message </code></pre> <h1 id="getting-the-context">Getting the Context</h1> <p>With SignalR you cannot just <code class="highlighter-rouge">new</code> up an instance of a <code class="highlighter-rouge">Hub</code>, you have to use <code class="highlighter-rouge">GlobalHost.ConnectionManager.GetHubContext&lt;THub&gt;</code>. The problem is that this gives you and <code class="highlighter-rouge">IHubContext</code> which only exposes the dynamic interface again. A bit more googling and I found that you need to pass our interface as a second generic parameter and you will get an <code class="highlighter-rouge">IHubContext&lt;IMetricsHub&gt;</code>.</p> <p>So this:</p> <pre><code class="language-fsharp">let context = GlobalHost.ConnectionManager.GetHubContext&lt;metricsHub&gt;() </code></pre> <p>Becomes:</p> <pre><code class="language-fsharp">let context = GlobalHost.ConnectionManager.GetHubContext&lt;metricsHub, IMetricsHub&gt;() </code></pre> <p>Now you can call <code class="highlighter-rouge">Context.Clients.All.BroadcastPerformance</code> and not worry about that pesky dynamic any more.</p> <h1 id="conclusion">Conclusion</h1> <p>The documentation on SignalR isn’t very good, it was easy enough to find out about the statically typed version, but finding out how to get one out of the context was a right pain.</p> <p>I’ve published a fork of Louies GitHub repo with four commits that show the steps needed to move from dynamic to statically typed SignalR <a href="https://github.com/xdaDaveShaw/LouiesGuiAdventDash">here</a> so you can see the changes I needed to make.</p> Mon, 29 Feb 2016 21:13:00 +0000 https://taeguk.co.uk/blog/using-signalr-in-fsharp-without-dynamic/ https://taeguk.co.uk/blog/using-signalr-in-fsharp-without-dynamic/ blog FSharp Adding Cloudapp DNS to Azure VM <p>I’ve recently just deployed a new Azure Linux VM for hosting a <a href="https://discourse.org">Discourse</a> instance I run and noticed that is didn’t have a DNS entry on cloudapp.net. Last time I deployed one it was instantly given one in the format <code class="highlighter-rouge">server-name.cloudapp.net</code>, but this time it wasn’t and I had to set it up by myself.</p> <p>I suspect it is something new for <a href="https://azure.microsoft.com/en-gb/features/resource-manager/">Resource Managed</a> deployments.</p> <p>Here’s a list of the steps you need to follow if you ever need to do the same.</p> <p>Assuming you have just deployed a VM and it doesn’t have a DNS on cloudapp.net you will see something like this:</p> <p><img src="/blog/Content/azure-dns-new-vm.png" alt="newly deployed vm" /></p> <h2 id="dissociate-public-ip">Dissociate Public IP</h2> <p>First you need to Dissociate the Public IP so you can make changes.</p> <p>Click the <strong>Public IP Address</strong> to open the settings:</p> <p><img src="/blog/Content/azure-dns-public-ip.png" alt="public ip settings" /></p> <p>Then click <strong>Dissociate</strong> and confirm when prompted.</p> <p><img src="/blog/Content/azure-dns-public-ip-settings.png" alt="public ip settings dissociate" /></p> <blockquote> <p>You cannot change any settings whilst the Public IP is in use.</p> </blockquote> <h2 id="configuring-the-dns">Configuring the DNS</h2> <p>From the Public IP page, click <strong>All Settings</strong> then <strong>Configuration</strong> to open up the settings:</p> <p><img src="/blog/Content/azure-dns-public-ip-configuration.png" alt="public ip settings configuration" /></p> <p>Then you can enter a new DNS prefix for <em>datacentre</em>.cloudapp.azure.net:</p> <p><img src="/blog/Content/azure-dns-public-ip-configuration-new-dns.png" alt="public ip configuration new dns" /></p> <h2 id="reassociate-the-public-ip">Reassociate the Public IP</h2> <p>Now you need to reassociate the Public IP with the VM.</p> <p>From the VM Screen (First Image) click <strong>All Settings</strong>, then <strong>Network Interfaces</strong>:</p> <p><img src="/blog/Content/azure-dns-vm-network-interfaces.png" alt="vm network interfaces" /></p> <p>Click on the Interface listed:</p> <p><img src="/blog/Content/azure-dns-vm-network-interfaces2.png" alt="all vm network interfaces" /></p> <p>Click on <strong>IP Addresses</strong> from the <strong>Settings</strong> blade:</p> <p><img src="/blog/Content/azure-dns-nic-ip-addresses.png" alt="network interfaces ip addresses" /></p> <p>Click on <strong>Enable</strong> then click on the <strong>IP Address Configure Required…</strong> and select the default (highlighted) Public IP Address from the list.</p> <p><img src="/blog/Content/azure-dns-enable-public-ip.png" alt="select public ip" />.</p> <p>Then click <strong>Save</strong>.</p> <h1 id="validation-and-testing">Validation and Testing</h1> <p>Now if you close and re-open the VM blade you should see a new Public IP address appear.</p> <p>Click on the <strong>Public IP Address</strong> to open the blade and you will see your full DNS Entry and a <strong>Copy to clipboard</strong> button when you hover on it:</p> <p><img src="/blog/Content/azure-dns-new-dns.png" alt="vm with new dns" /></p> <p>To test, ping the VM and see if the DNS resolves:</p> <div class="highlighter-rouge"><pre class="highlight"><code>C:\&gt; ping taeguk-test-dns.northeurope.cloudapp.azure.com Pinging taeguk-test-dns.northeurope.cloudapp.azure.com [40.127.129.7] </code></pre> </div> <p>The requests will timeout because Azure has ICMP disabled, but so long as the DNS resolves, you’ve done it.</p> <h1 id="conclusion">Conclusion</h1> <p>This seems to be a change that I can’t find a source for to do with Resource Managed VM’s instead of Classic VM’s. It used to work OK on classic VM’s.</p> <p><strong>Note</strong>: I have deleted the VM in this post now.</p> Sun, 15 Nov 2015 20:26:00 +0000 https://taeguk.co.uk/blog/adding-cloudapp-dns-to-azure-vm/ https://taeguk.co.uk/blog/adding-cloudapp-dns-to-azure-vm/ blog Azure Shut the Box is Live <p>Today I’ve just published my first App into the Windows and Windows Phone Store.</p> <p><img src="https://taeguk.co.uk\shutthebox\screenshot.png" alt="Screenshot" /></p> <p>You can download using the image below, if you want to check it out. It’s 100% <strong>free</strong> and no ads.</p> <p><a href="https://www.microsoft.com/en-us/store/apps/shut-the-box/9nblggh690qb"><img src="https://taeguk.co.uk\shutthebox\store-icon.png" alt="Windows Store Download" /></a></p> <p>It is a simple version of the pub game Shut the Box, I have page <a href="https://taeguk.co.uk\shutthebox">here</a> with more information about game.</p> <p>This was my first attempt at a Windows Application and I’ve really enjoyed the experience of building it. I tried to use as many new things to me as possible to learn as much as I can through the process. A quick list of new things I’ve explorer whilst working on this are:</p> <ul> <li>Git</li> <li>Visual Studio Online Kanban for planning and tracking work (up until now I’ve only used TFS 2012.4).</li> <li>TFS Build vNext.</li> <li>Application Insights.</li> <li>Custom MSBuild Project to encapsulate all restore/build/test workflows.</li> <li>xUnit.net for Universal Apps (lots of beta’s to test).</li> </ul> <p>Working with the Windows Store was a bit of “hit and miss”, for a while I could not see get to the “Dashboard” part of the site “because of my Azure account”, or so I was told. This seemed to resolve itself eventually, but was very annoying at the time. I was not offered any explanation, only that I should create a new Microsoft Account to publish apps through, which I was not prepared to do.</p> <p>It took 3 attempts to get the application through certification. Firstly it failed because I had not run the Application Certification Kit and had a transparent Windows tile that is not allowed. The second failure was because Russia, Brazil, Korea and China require certification of anything that is a Game in the store. I decided not to publish it to those markets at the moment because I wanted it out there, and figuring out how to complete the certification seemed like too much work. I may look into it again later, but for now I am happy.</p> <p>This application has been a long time coming, mostly down to my lack of free time and/or willingness to work on it, but I’m glad it’s finally published, now to try and release some updates and add some more nice features.</p> <p>If you enjoy the game, please feel free to leave me a good rating / comment in the Store.</p> Fri, 13 Nov 2015 16:18:00 +0000 https://taeguk.co.uk/blog/shut-the-box-is-live/ https://taeguk.co.uk/blog/shut-the-box-is-live/ blog Development Roslyn Based Attribute Remover <p><strong>Major Update 1-Aug-2015</strong>: Changed <code class="highlighter-rouge">VisitAttributeList</code> to <code class="highlighter-rouge">VisitMethodDeclaration</code> to fix some bugs with the help of <a href="http://stackoverflow.com/questions/31749997/how-to-remove-all-member-attribute-but-leave-an-empty-line/31756034#31756034">Josh Varty</a>.</p> <p>I’m a big fan of <a href="http://xunit.github.io">XUnit</a> as a replacement for MSTest and use it extensively in my home projects, but I’m still struggling to find a way to integrate it into my work projects.</p> <p>This post looks at one of the obstacles I had to overcome, namely the use of <code class="highlighter-rouge">[TestCategory("Atomic")]</code> on all tests that are run on TFS as part of the build. The use of this attribute came about because the MSTest test runner did not support a concept of “run all tests without a category”, so we came up with an explicit category called “Atomic” - probably not the best decision in hindsight. The XUnit test runner does not support test categories, so I needed to find a way to remove the <code class="highlighter-rouge">TestCategory</code> attribute with the value of <code class="highlighter-rouge">Atomic</code> from any method. I’m sure I could have used regex to solve this, and I’m sure that would have caused <a href="http://blog.codinghorror.com/regular-expressions-now-you-have-two-problems/">more problems</a>:</p> <p><img src="/blog/Content/attributes-perl-problems.png" alt="To generate #1 albums, 'jay --help' recommends the -z flag." /></p> <p><em>via <a href="https://xkcd.com/1171/">xkcd</a></em></p> <p>Instead I created a Linqpad script and used the syntactic analyser from the <a href="https://www.nuget.org/packages/Microsoft.CodeAnalysis">Microsoft.CodeAnalysis</a> package.</p> <div class="highlighter-rouge"><pre class="highlight"><code>PM&gt; Install-Package Microsoft.CodeAnalysis </code></pre> </div> <p>I found that the syntactic analyser allowed me to input some C# source code, and by writing my own <code class="highlighter-rouge">CSharpSyntaxRewriter</code>, remove any attributes I didn’t want.</p> <p>I started by creating some C# that had the <code class="highlighter-rouge">TestCategory</code> attribute applied in as many different ways as possible:</p> <figure class="highlight"><pre><code class="language-c#" data-lang="c#"><span class="k">namespace</span> <span class="nn">P</span> <span class="p">{</span> <span class="k">class</span> <span class="nc">Program</span> <span class="p">{</span> <span class="k">public</span> <span class="k">void</span> <span class="nf">NoAttributes</span><span class="p">()</span> <span class="p">{</span> <span class="p">}</span> <span class="p">[</span><span class="n">TestMethod</span><span class="p">,</span> <span class="nf">TestCategory</span><span class="p">(</span><span class="s">"Atomic"</span><span class="p">)]</span> <span class="k">public</span> <span class="k">void</span> <span class="nf">OnOneLine</span><span class="p">()</span> <span class="p">{</span> <span class="p">}</span> <span class="p">[</span><span class="n">TestMethod</span><span class="p">]</span> <span class="p">[</span><span class="nf">TestCategory</span><span class="p">(</span><span class="s">"Atomic"</span><span class="p">)]</span> <span class="k">public</span> <span class="k">void</span> <span class="nf">SeparateAttribute</span><span class="p">()</span> <span class="p">{</span> <span class="p">}</span> <span class="c1">//snip... </span> <span class="c1">//And so on down to, right down to... </span> <span class="p">[</span><span class="n">TestMethod</span><span class="p">,</span> <span class="nf">TestCategory</span><span class="p">(</span><span class="s">"Atomic"</span><span class="p">),</span> <span class="nf">TestCategory</span><span class="p">(</span><span class="s">"Atomic"</span><span class="p">)]</span> <span class="k">public</span> <span class="k">void</span> <span class="nf">TwoAttributesOneLineAndOneThatDoesntMatch</span><span class="p">()</span> <span class="p">{</span> <span class="p">}</span> <span class="p">}</span> <span class="p">}</span></code></pre></figure> <p>You can see all the examples I tested against in the <a href="https://gist.github.com/xdaDaveShaw/87643170e5fa97b7da3b">Gist</a>.</p> <p>The <code class="highlighter-rouge">CSharpSyntaxRewriter</code> took a lot of messing around with to get right, but I eventually figured that by overriding the <code class="highlighter-rouge">VisitMethodDeclaration</code> method I could remove attributes from the syntax tree as they were visited.</p> <p>To get some C# code into a syntax tree, there is the obviously named <code class="highlighter-rouge">CSharpSyntaxTree.ParseText(String)</code> method. You can then get a <code class="highlighter-rouge">CSharpSyntaxRewriter</code> (in my case my own <code class="highlighter-rouge">AttributeRemoverRewriter</code> class) to visit everything by calling <code class="highlighter-rouge">Visit()</code>. Because this is all immutable, you need to grab the result, which can now be converted into a string and dumped out.</p> <figure class="highlight"><pre><code class="language-c#" data-lang="c#"><span class="kt">var</span> <span class="n">tree</span> <span class="p">=</span> <span class="n">CSharpSyntaxTree</span><span class="p">.</span><span class="nf">ParseText</span><span class="p">(</span><span class="n">code</span><span class="p">);</span> <span class="kt">var</span> <span class="n">rewriter</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">AttributeRemoverRewriter</span><span class="p">(</span> <span class="n">attributeName</span><span class="p">:</span> <span class="s">"TestCategory"</span><span class="p">,</span> <span class="n">attributeValue</span><span class="p">:</span> <span class="s">"Atomic"</span><span class="p">);</span> <span class="kt">var</span> <span class="n">rewrittenRoot</span> <span class="p">=</span> <span class="n">rewriter</span><span class="p">.</span><span class="nf">Visit</span><span class="p">(</span><span class="n">tree</span><span class="p">.</span><span class="nf">GetRoot</span><span class="p">());</span> <span class="n">rewrittenRoot</span><span class="p">.</span><span class="nf">GetText</span><span class="p">().</span><span class="nf">ToString</span><span class="p">().</span><span class="nf">Dump</span><span class="p">();</span></code></pre></figure> <p>The interesting part of the <code class="highlighter-rouge">AttributeRemoverRewriter</code> class is the <code class="highlighter-rouge">VisitMethodDeclaration</code> method which finds and removes attribute nodes that are not needed:</p> <figure class="highlight"><pre><code class="language-c#" data-lang="c#"><span class="k">public</span> <span class="k">override</span> <span class="n">SyntaxNode</span> <span class="nf">VisitMethodDeclaration</span><span class="p">(</span><span class="n">MethodDeclarationSyntax</span> <span class="n">node</span><span class="p">)</span> <span class="p">{</span> <span class="kt">var</span> <span class="n">newAttributes</span> <span class="p">=</span> <span class="k">new</span> <span class="n">SyntaxList</span><span class="p">&lt;</span><span class="n">AttributeListSyntax</span><span class="p">&gt;();</span> <span class="k">foreach</span> <span class="p">(</span><span class="kt">var</span> <span class="n">attributeList</span> <span class="k">in</span> <span class="n">node</span><span class="p">.</span><span class="n">AttributeLists</span><span class="p">)</span> <span class="p">{</span> <span class="kt">var</span> <span class="n">nodesToRemove</span> <span class="p">=</span> <span class="n">attributeList</span> <span class="p">.</span><span class="n">Attributes</span> <span class="p">.</span><span class="nf">Where</span><span class="p">(</span> <span class="n">attribute</span> <span class="p">=&gt;</span> <span class="nf">AttributeNameMatches</span><span class="p">(</span><span class="n">attribute</span><span class="p">)</span> <span class="p">&amp;&amp;</span> <span class="nf">HasMatchingAttributeValue</span><span class="p">(</span><span class="n">attribute</span><span class="p">))</span> <span class="p">.</span><span class="nf">ToArray</span><span class="p">();</span> <span class="c1">//If the lists are the same length, we are removing all attributes and can just avoid populating newAttributes. </span> <span class="k">if</span> <span class="p">(</span><span class="n">nodesToRemove</span><span class="p">.</span><span class="n">Length</span> <span class="p">!=</span> <span class="n">attributeList</span><span class="p">.</span><span class="n">Attributes</span><span class="p">.</span><span class="n">Count</span><span class="p">)</span> <span class="p">{</span> <span class="kt">var</span> <span class="n">newAttribute</span> <span class="p">=</span> <span class="p">(</span><span class="n">AttributeListSyntax</span><span class="p">)</span><span class="nf">VisitAttributeList</span><span class="p">(</span> <span class="n">attributeList</span><span class="p">.</span><span class="nf">RemoveNodes</span><span class="p">(</span><span class="n">nodesToRemove</span><span class="p">,</span> <span class="n">SyntaxRemoveOptions</span><span class="p">.</span><span class="n">KeepNoTrivia</span><span class="p">));</span> <span class="n">newAttributes</span> <span class="p">=</span> <span class="n">newAttributes</span><span class="p">.</span><span class="nf">Add</span><span class="p">(</span><span class="n">newAttribute</span><span class="p">);</span> <span class="p">}</span> <span class="p">}</span> <span class="c1">//Get the leading trivia (the newlines and comments) </span> <span class="kt">var</span> <span class="n">leadTriv</span> <span class="p">=</span> <span class="n">node</span><span class="p">.</span><span class="nf">GetLeadingTrivia</span><span class="p">();</span> <span class="n">node</span> <span class="p">=</span> <span class="n">node</span><span class="p">.</span><span class="nf">WithAttributeLists</span><span class="p">(</span><span class="n">newAttributes</span><span class="p">);</span> <span class="c1">//Append the leading trivia to the method </span> <span class="n">node</span> <span class="p">=</span> <span class="n">node</span><span class="p">.</span><span class="nf">WithLeadingTrivia</span><span class="p">(</span><span class="n">leadTriv</span><span class="p">);</span> <span class="k">return</span> <span class="n">node</span><span class="p">;</span> <span class="p">}</span></code></pre></figure> <p>The <code class="highlighter-rouge">AttributeNameMatches</code> method is implemented to find an attribute that <em>starts with</em> <code class="highlighter-rouge">TestCategory</code>, this is because attributes in .NET have <code class="highlighter-rouge">Attribute</code> at the end of their name e.g. <code class="highlighter-rouge">TestCategoryAttribute</code>, but most people never type it. I figured in this case it was more likley to exist than to have another attribute starting with <code class="highlighter-rouge">TestCategory</code>. I don’t think there is an elegant way to avoid using <code class="highlighter-rouge">StartsWith</code> in the syntactic analyser, I would have had to switch to the sematic analyser and that would have made this a much more complicated solution.</p> <p>The <code class="highlighter-rouge">HasMatchingAttributeValue</code> pretty much does what it says, it looks for the value of the attribute been just <code class="highlighter-rouge">Atomic</code> and nothing else.</p> <p>Once the nodes that match are found, it checks if the number of attributes on a method is equal to the number it wants to remove, if so the <code class="highlighter-rouge">newAttributes</code> list is not populated and the method is updated to keep its trivia, but without any attributes. This shouldn’t be the case for this specific scenario because just a <code class="highlighter-rouge">TestCategory</code> on its own doesn’t make sense.</p> <p><strong>Remove just the matching attributes</strong></p> <p>If there are some attributes that do not need removing, then just the matching one should be removed. For example:</p> <figure class="highlight"><pre><code class="language-c#" data-lang="c#"><span class="na">[TestMethod, TestCategory("Atomic")]</span> <span class="k">public</span> <span class="k">void</span> <span class="nf">OnOneLine</span><span class="p">()</span> <span class="p">{</span> <span class="p">}</span></code></pre></figure> <p>When the visitor reaches the attributes on this method, it will populate the <code class="highlighter-rouge">newAttributes</code> list with just the attributes we want to keep and then update the method so that it has just the remaining attributes its trivia.</p> <h1 id="conclusion">Conclusion</h1> <p>Using Roslyn was a bit of a steep learning curve to start with, but once I found out what I was doing, I knew I could rely on the Roslyn team to have dealt with all the different ways of implementing attributes in C#. That didn’t stop me from finding what appears to be a bug causing me to re-write bits of the script and this post, and some more edge cases when I ran it across a &gt; 500 test classes.</p> <p>However, if I were to try and use regex to find and remove some of the more complicated ones, and deal with the other edge cases, I’d have gone mad by now.</p> <ul> <li>You can get the full Gist <a href="https://gist.github.com/xdaDaveShaw/87643170e5fa97b7da3b">here</a>.</li> </ul> <p>If you paste this into a Linqpad “program” and then just install the NuGet Package you should be able to try it out. <strong>Note</strong> this was built against the 1.0.0 version of the package.</p> Sun, 21 Jun 2015 21:30:00 +0000 https://taeguk.co.uk/blog/roslyn-based-attribute-remover/ https://taeguk.co.uk/blog/roslyn-based-attribute-remover/ blog Development Linqpad Roslyn Automating the Deployment of TFS Global Lists <p>The TFS Global List is a Team Project Collection wide entity and, to the best of my knowledge, requires someone to be a member of the <a href="http://msdn.microsoft.com/en-us/library/dd547204.aspx">Collection Administrators</a> group to be able to update it – there is no explicit group or permission for “Upload Global List”. This can be quite a problem if there are a number of Lists within your Global List that are updated frequently by the users of your Collection.</p> <p>Your current options are either:</p> <ol> <li>Ask the Collection Administrators for every little change (and complain if they take too long, they have a holiday, etc.)</li> <li>Keep adding people/groups to the Collection Administrators group (and hand out way too much power to people who don’t need it).</li> </ol> <p>We went for option #1, then option #2, until neither became sustainable.</p> <p>The solution I came up with is based on post <a href="http://www.edsquared.com/2010/06/18/Deploying+Process+Template+Changes+Using+TFS+2010+Build.aspx">Deploying Process Template Changes Using TFS 2010 Build by Ed Blankenship</a>, but instead of deploying the whole process template, we just deploy the Global List. <em>(N.B. our TFSBuild account is a Collection Administrator)</em>.</p> <h1 id="building-the-template">Building the Template</h1> <p>To build the template I started by copying the DefaultTemplate.11.1.xaml file that ships with TFS 2012 and stripped out all of the activities and process parameters that were no longer required then added a new activity to invoke the <a href="https://msdn.microsoft.com/en-us/library/dd236914.aspx?f=255&amp;MSPPError=-2147217396">witadmin</a> command line tool to import the Global List.</p> <p>I won’t go into detail of the process of how I changed the activities because there were quite a lot of steps. It is quite straight forward. However, a quick overview is: remove anything to do with compiling code, running tests or gated checkins, then add a new activity to invoke the <code class="highlighter-rouge">witadmin</code> command line. It will probably be easier understood by looking at the finished template - available to download at the end. I may write a follow up post with the exact details.</p> <h1 id="using-the-template">Using the template</h1> <ul> <li>To use the tempalte you need to have the Global Lists file checked into Version Control, you can follow the advice in the <a href="http://www.amazon.co.uk/gp/product/1118836340/ref=as_li_tl?ie=UTF8&amp;camp=1634&amp;creative=19450&amp;creativeASIN=1118836340&amp;linkCode=as2&amp;tag=taeguk-21&amp;linkId=FQHQQKVQQIYAHRT6">Wrox Professional Team Foundation Server 2013</a> book to create a Team Project for your all your Process artefacts, or if you just want to keep it simple: <ul> <li>Use witadmin to export the global list file:</li> <li><code class="highlighter-rouge">witadmin exportgloballist /collection:http://tfs:8080/tfs/DefaultCollection /f:GlobalList.xml</code></li> <li>Check that file into its own folder somewhere in souce control, in this example we will use <code class="highlighter-rouge">$/TFS/GlobalList/GlobalList.xml</code> (having it in its own folder helps).</li> </ul> </li> <li>Once you have the template downloaded, you need to check it into Version Control, usually <code class="highlighter-rouge">$/MyTeamProject/BuildProcessTemplates/</code>.</li> <li>Create a new build definition.</li> <li>Fill in the <strong>General</strong> tab however you like.</li> <li>In the <strong>Trigger</strong> tab select <code class="highlighter-rouge">Continous Integration</code>.</li> <li>In the <strong>Source Settings</strong> tab select the folder with your GlobalList.xml as <strong>Active</strong> (<code class="highlighter-rouge">$/TFS/GlobalList/</code>)</li> <li>In the <strong>Build Defaults</strong> tab, select “This build does not copy output files to a drop folder”.</li> <li>In the <strong>Process</strong> tab we need to do a few steps: <ul> <li>To install the template, click <strong>Show Details</strong>:</li> <li><img src="/blog/Content/GlobalList-NewTemplate1.png" alt="Show details" /></li> <li>Click <strong>New…</strong> and browse to the template we checked in (<code class="highlighter-rouge">$/MyTeamProject/BuildProcessTemplates</code>).</li> <li>Fill in the sections as follows:</li> <li><img src="/blog/Content/GlobalList-NewTemplate2.png" alt="Process Parameters" /></li> <li>I didn’t know the best way to get the URI of the Team Project collection, so I made it a argument you need enter.</li> <li>If you are not using VS2012 on your build server, you will need to find a way to get witadmin.exe on there and then update the path to the location.</li> </ul> </li> </ul> <p>Once the above has been completed you should be able to the queue a new build using the new defintion and check the output to see if the global list has been successfully uploaded. Just open the build and check the summary, if everything went well you should see the following:</p> <p><img src="/blog/Content/GlobalList-Summary.png" alt="Build Summary" /></p> <p>If there were any problem, check the “View Log”, the build is using Detailed logging which should include enough information to figure out what went wrong.</p> <h1 id="conclusion">Conclusion</h1> <p>I’ve now stopped worrying about having to update the global list for everyone who needs something new adding and I no longer am affraid of lots of people been Collection Administrators who really shouldn’t have been. I can just grant check-in permissions to the folder that contains our global list and leave people to it.</p> <h1 id="download">Download</h1> <p>I’m keeping this on my GitHub:</p> <ul> <li><a href="https://github.com/xdaDaveShaw/TFS/blob/master/GlobalListTemplate.11.1.xaml">View Here</a></li> </ul> <p>If have any improvements (to the post / template), feel free to send me a PR.</p> Sun, 14 Jun 2015 22:35:00 +0000 https://taeguk.co.uk/blog/automating-the-deployment-tfs-global-lists/ https://taeguk.co.uk/blog/automating-the-deployment-tfs-global-lists/ blog TFS Moving from WordPress <p>This is my last post on WordPress and first post on Jekyll GitHub Pages.</p> <p>I’ve decided to abandon WordPress running on Azure Web Apps for a simpler static blog using Jekyll to convert Markdown to static content hosted on GitHub pages. I’ll go into the process I went though in a future post.</p> <p>This posts is here as a marker of when I moved everything over. I’ve tried to get the permalinks in Jekyll to match the ones in WordPress - but breaking any that were from my brief stint on DasBlog. As far as I know everything should just be the same - including the RSS feed on <code class="highlighter-rouge">/feed</code>.</p> <p>Shoot me a mail if there is a problem.</p> <p>Everything from this point on will be on the new format.</p> <p>The actual migration will occur at some point next week.</p> Mon, 11 May 2015 22:20:00 +0000 https://taeguk.co.uk/blog/moving-from-wordpress/ https://taeguk.co.uk/blog/moving-from-wordpress/ blog Meta WordPress Jekyll