My project at work has finally advanced to the stage that I am writing automated UI tests for Asp.Net MVC 2 using the WatiN UI testing framework. I wanted to be able to run these tests as part of my automated build process using Team Build (TFS) 2010. It turned out to be easier then I thought for once. - Verify the account your Team Build service is running as. Mine is domain\tfsbuild
- Run dcomcnfg to grant COM activation permissions for domain\tfsbuild
- Expand Component Services, Computers and right lick on My Computer choosing properties
- Click on the COM security tab
- Under Launch and Activate Permissions, click edit default
- Add domain\tfsbuild and give allow permissions for local launch and local activation
- Log onto the build server as domain\tfsbuild
- Fire up IE and browse to the websites you will be testing against. Verify that you can get to the websites, and there are no IE windows or warnings getting in your way. Common ones include IE enhanced security warning, and certification errors.
- I have my servers SSL certs signed by a private cert that is trusted by all computers on the domain
- I added our internal domain name (i.e. domain.local) to the intranet sites zone in IE. This got rid of the IE enhanced security popup.
- I did NOT have to change the TFS Build configure to run the build agent as a interactive process. It is still configured to run as a service.
I should add that this is for TFS 2010 RC. I’ll be upgrading to the RTM this weekend, and I hope everything still works afterwards!
In TFS 2010, one of the key reports is the burn down report. This report shows up on the home page of the project portal (SharePoint) and is also available as a standard report. In reality, it’s actually two separate reports, one is under the Project Management folder, the other in the dashboards folder. In order to keep from having to re-enter the current iteration data, and so the burn down chart on the project portal reflects the current iteration, you need to update the default parameters. The tricky part is, the iteration and area data is pulled from Analysis Services and finding the value or id for a specific iteration or area was not easy for me the first time. - Using Sql Server Management Studio, connect to the TFS Analysis Services Server.
- Expand the databases node and right click on Tfs_Analysis and click new query, mdx
- Select Work Item from the Cube drop down
- Drill down to Work Item\Work Item.Iteration Hierarchy\Iteration#, where # is the depth of the iteration you are looking for. Iteration0 is the project level iteration. Since our iterations are structured as Project\Release\Iteration 1-n, I selected the Iteration2 node to find my iteration name “Iteration 2”.
- Drag and drop the iteration you are looking for into the query window. This will give you something like: [Work Item].[Iteration Hierarchy].[Iteration2].&[5051278939187782728]&[-6074279628322632998]
- Copy and paste that into the default parameter value in Sql Server Reporting Services.
- You will also want to update the iteration start and end date.
I’m finishing up my rest week for March before kicking it into high gear for a bike focused month in April. I will have finished March with over 60 hours of training, with even more scheduled for April. With over 5 months until Ironman, I am really starting to get excited about the possibilities. Last year, my first year of racing, I did not train nearly enough for how fast I was trying to go in my two half-iron races. This year, I’m close to doubling my training, and I have started much earlier then last year. I just realized that the Pigman half-iron race is week later this year, which means I might be able to fit it in as a final race check-up before Ironman. If I race Pigman, I’ll still have two full weeks of rest and recovery before Ironman, and I can always back it off a little during the Pigman race if need be. That’s how excited I am right now, looking for a way to fit another race in to take advantage of this huge investment in training. I’m already planning my off season or winter training routine. My wife and I both took advantage of the end of the season sales and picked up some cross country ski gear (skate style). I managed to get out 3 times before the snow melted and wow, it’s a lot of fun and a great work out. Gear What fun would it be to train for Ironman without buying lots of gear and toys? I know in my last post I said I had most everything already purchased, and I do have the big ticket items done, but there are so many little things out there. My Garmin Forerunner 305 started giving my problems last week. The start button was sticking, and it always took way longer to acquire satellites then my wife’s 305. I wanted to upgrade to a 310xt to get power meter support, so I went ahead and purchased that tonight. I already like it over the 305 in the few short minutes I’ve had it out of the box, although the clip charging cable is kind of finicky. While I was at my favorite store, Endurance House, I also picked up a pair of Tifosi Scatto sun glasses. These sun glasses have the variable tint lenses, and are a smaller frame size to fit my small narrow face/head. I think I’ll really like the variable tint, as I do a lot of my workouts in the evening, where you have the setting sun hitting you right in the face followed by an increasing dark sky. However, I still like to have the glasses on, especially on the bike to cut down on the wind in the eyes. I’m looking at another piece of high tech gadgetry to help with training which will be the subject of a future post. I found a local shop that is an Authorized Argon 18 dealer, so I’m hoping I’ll be able to get a few replacement parts I need. Also working on sizing up a set of race wheels. Training Swim, bike, run, what else is there? Swimming is going pretty well, I’m about as fast as I have ever been but I’ve discovered the occasional session with good form. For me, good form doesn’t mean faster, but less work. Not that I’ve abandoned all hope of going faster in the swim, but this year it’s more about endurance then speed. On the bike, I feel like I’m doing really well, but it’s hard to tell since I didn’t have a Power Tap last year. I did manage to get outside once when it was warm (yeah, I’m a fair weather rider only) and recorded a NP of 217 which matches up with my FTP of 215 from the trainer, so I’m pretty happy with that. Once I can get out on the IM loop I’ll be in a better position to gauge how my fitness really is since I rode it so often last year. One thing I have noticed is that my heart rate is much lower on my longer rides compared to February which is a good inidcator that my fitness level is improving. Surprisingly my running is going really well, on account of all the biking I’m doing I guess. I’ve only been running 3 times per week (45min and 2x1hr) and I’m up to running 8.5 miles in an hour, and 8.3 in an hour off the bike. I’m still having little bits of pain, not while running but afterwards that have me a bit concerned. I was hoping to run the Madison Marathon with my wife, but I think I will stick with the half marathon to keep the total miles on my legs down (yet I want to sneak in an extra half-iron race).
While I usually run as a normal user on Windows and elevate permissions as needed, sometimes this just doesn’t cut it. Today I needed to be able to start and stop a windows service as a normal user running a piece of code (actually it’s a integration test). With UAC enabled, even though I was an administrator, I still couldn’t access the service. After a bit of searching, I came across the following Server Fault question, which had an accepted answer. 1) Get the existing security descriptor using “sc sdshow Servername” from an Administrative command prompt. If the service has spaces in it, surround it in quotes. This will output something like: D:(A;;CCLCSWRPWPDTLOCRRC;;;SY)(A;;CCDCLCSWRPWPDTLOCRSDRCWDWO;;;BA)(A;;CCLCSWLOCRRC;;;IU)(A;;CCLCSWLOCRRC;;;SU)S:(AU;FA;CCDCLCSWRPWPDTLOCRSDRCWDWO;;;WD) I bolded the D: and S: to denote their importance for step 3 and 4. 2) Get the SID for the account you want to grant access to. I was able to use the same command prompt because I was running it as me, just with elevated permissions. If you need to do this for a different user, you will either have to log in as them, or use a different tool if you can’t log in as them. c:\>whoami /all 3) Insert the following into the string you got in step 1. Make sure to replace the sid below with the sid you got in step 2. Also, this must be inserted before the S: in the string obtained in step 1. (A;;RPWPDT;;;S-1-5-21-0000000000-0000000000-0000000000-0000) 4) Run from an administrative command prompt sc setsd “servicename” sc sdset "servicename" D:(A;;CCLCSWRPWPDTLOCRRC;;;SY)(A;;CCDCLCSWRPWPDTLOCRSDRCWDWO;;;BA)(A;;CCLCSWLOCRRC;;;IU)(A;;CCLCSWLOCRRC;;;SU)(A;;RPWPDT;;;S-1-5-21-0000000000-0000000000-0000000000-0000)S:(AU;FA;CCDCLCSWRPWPDTLOCRSDRCWDWO;;;WD) I’ve bolded the string from step 3 that was inserted. There is a space after “servicename” but none after that. This page talks about the different options you can set.
In some TFS 2010 work items, there is a drop down that lists the automated builds that have been run. An example field would be the “Integrated in Build” field on the Task Work Item under the Implementation tab. Even when you delete a build, the build number still shows up in these drop downs. While playing around with the TFS 2010 RC Power Tools, I came across the global list editor, and saw that all those build numbers I had been wanting to delete where listed. After deleting a few and going back to my work items, I confirmed that they no longer showed up. To delete the build numbers from the work item drop down lists: 1.) Get the Power Tools Install the TFS 2010 Power Tools for your version (RC or Beta2) 2.) Open the Global List In Visual Studio, go to Tools\Process Editor\Global List\Open Global List from Server. 3.) Delete - Expand the root folder, which should be named “Builds – ProjectName”.
- Right click on the build and then click delete. It looks like you have to select each item individually, but it goes pretty fast.
- When you have deleted everything you want, click OK.
Even if you delete the item from the list, if you have already selected the build in a work item, the value will remain for that work item.
This is my first attempt at editing a build template in Team Build 2010. Previously I have expressed a desire to see Team Build move away from cryptic XML files and go to a GUI. Well I got my wish, but instead of cryptic XML, we get a cryptic GUI in the form of Windows Workflow foundation. Now maybe I’m not supposed to go in a edit the default build template, but that’s how I learn. I start with what I know works and start tweaking stuff. How bad is it? Well the entire workflow will not fit on my screen at 25% zoom. Here are a couple of screen shots. Now to be fair, the default build template allows you to configure a lot of settings using a property grid like GUI so you never have to look at the WF diagram. It’s only when you need to add additional functionality that you have to go into the land of WF. I guess I, along with Microsoft are expecting the community to step up and provide some new build templates. I also found out that if you double click on the blocks in the WF designer view, you jump “into” the block and that’s all you see. None the less, I set about the task of modifying the default build template to execute XUnit tests instead of MS Tests. Before I go into that, let me explain why I am setting myself up for this torture. My immediate alternatives are to use a different build server, such as Team City, or to use MSTest. If I use Team City, I loose the tight integration with TFS and I do not wish to explain to people about multiple tools. If I use MSTest, I’ll still have to edit the build template to get automated deployment working, as so far I haven't seen a MS Deploy activity. I’m not sure if this is the best solution. Maybe I’ll get some feedback, but more then likely, I’ll end up borrowing someone else's solution down the road. So what do I have to start with? The default build template has an option to scan for assemblies that match a certain naming convention (**\*test*.dll) which does match my naming convention (this pattern is also configurable in the build definition GUI under Basic\Automated Tests\Test Assembly). Looking at the log file for the build, I can see that my two test assembly projects are loaded, but no tests are executed. Once I found where in the WF sequence this was occurring, I realized I had a pretty good starting point. Where to Start First I had to find where I could switch out the call to MSTest with a call to Xunit using the list of test assemblies found. It’s buried pretty deep, so keep double clicking along the following path. - Sequence
- Run On Agent
- Try Compile, Test, and Associate Changesets and Work items
- Sequence
- Compile, Test, and Associate Changesets and Work items
- Try Compile and Test
- Compile and test
- For Each Configuration in BuildSettings.PlatformConfigurations
- Compile and Test for Configuration
- If Not DisableTests
- Run Tests
- If Not TestSpecs Is Nothing
- For Each TestSpec in TestSpecs
- Try Run Tests
- If spec Is TestMetadataFileSpec
- Run MSTest for Test Assemblies
- If Test Assemblies Found (started inserting here)
- If testAssembly.HasTestSettingsFile (deleted)
Run XUnit on all test assemblies Since I don’t have a test settings file with XUnit, I delete the check for HasTestSettingsFile and added an ForEach with an Invoke Process activity to use the xunit console runner. So my “If Test Assemblies Found” block now looks like: The syntax to use the console runner is xunit.console Assembly.dll /nunit <filename>, which looks pretty easy to use. To get the Xunit console on my build server, I could either add it to source control, or put it in a known location on the build server. I opted for the later because it was the easiest. To get the XUnitPath into the build template, I created a new Argument called XUnitPath and took the easy way out by specifying the default as where I had installed XUnit. I did this because I couldn’t figure out how to get it to show in the GUI initially. You need to check your build template in and re-open the build definition. After doing this, my new argument showed up under section 4. Misc. I then went back to my Invoke Process activity and set the file name property to my argument “variable” XUnitPath.
For the arguments, I specified item, which is the ForEach variable for the assembly, as well as an option to output the results as an nUnit formatted xml file. Before I attempt to merge the XML results file, I need to be able to trigger my build to fail if my tests fail. In the invoke process, you can grab the output using the stdOut variable, which I did. I then added an If statement to check for the text “Tests failed”, which the XUnit console outputs to when it encounters a failed test. Unfortunately, every line output fires off my If statement, so my build log file looks a little verbose. However, this works, and after fixing my tests, my build went from partially succeeded to succeeded. At this point, here is what my “If Test Assemblies Found” block looks like. The Assign blocks under Standard Output and Standard error assign BuildDetail.TestStatus to Microsoft.TeamFoundation.Build.Client.BuildPhaseStatus.Failed. Publish XUnit results to TFS A couple of months ago I went about integrating XUnit tests and results into TFS 2008, so I figured that those same steps would work more or less. After going down that path initially, I decided it would be cleaner to just create a new XSLT template based on the NUnit4MSBuild template that takes an XUnit results file and transforms it into a MSTest trx file directly. This cuts out an extra step in my sequence, and eliminates the need for an external dependency on nxslt3. I wrote up a separate blog post on how to Transform XUnit to MSTest. Like the information presented in this post, it’s a work in progress. After getting the results file in a compatible format I still needed to get them published to TFS. You can use an Invoke activity to MSTest (which requires Visual Studio to be installed on your build server) to publish your results. The filename argument I used was: """" & System.Environment.GetFolderPath(Environment.SpecialFolder.ProgramFilesX86) & "\Microsoft Visual Studio 10.0\Common7\IDE\MSTest.exe""" and the arguments I used where: "/publish:""" & BuildDetail.BuildServer.TeamProjectCollection.Uri.ToString() & """" & _ " /publishbuild:""" & BuildDetail.BuildNumber & """" & _ " /publishresultsfile:" & """" & item & "_XunitResults.trx" & """" & _ " /teamproject:""" & BuildDetail.TeamProject & """" & _ " /platform:""" & platformConfiguration.Platform & """" & _ " /flavor:""" & platformConfiguration.Configuration & """" Final Thoughts and What’s Next Well it works, but it’s what I consider to be a hack. However, I’m going to invoke YAGNI in that this is manageable given the number of projects, builds and workflow I am working with. I expect that I will have to work on this as my Greenfield project matures. I also expect that once TFS 2010 is released and more people start using it, there will be more community contributions. I would like to wrap this all up into an XUnit workflow activity, but it depends on how much extra time I have. Since this works, the next thing on my to do list is to get automated web deployments working using MS Deploy. Right click and choose Save As:
This is part one of a two part series in how to get XUnit test results into Team Foundation Server 2010. Originally I had started out using the NUnit to MSTest transform that was part of NUnit4TeamBuild, but it left me with an extra step to perform in my build script. Since the XUnit console runner has support for adding new transform options, I figured I could write my own XSLT and eliminate the extra transform step in my build script. If I decided to move ahead with my XUnit WF activity idea, I could also reuse this custom transform for that. Setup First I created two test projects, one based on MSTest, the other on XUnit. From these two test projects, I can create the source (XUnit) and destination (MSTest) files and then work on an XSLT transformation. When using the XUnit console runner against assemblies compiled for .net 4.0, you have to add the following to it’s app.config file: <startup> <requiredRuntime version="v4.0.20506" safemode="true"/> </startup> I also added a line under xunit\transforms to give me the option to use my new XSLT: <add commandline="mstest" xslfile="MSTestXml.xslt" description="output results to MSTest-style XML file"/> Transformation I started out with the XSLT fom the NUnit4TeamBuild project, and began modifying it until the the output from it (transforming the Xunit sample results file) matched the output from my MSTest results file. - Changes for TFS 2010
- Updated Namespace to http://microsoft.com/schemas/VisualStudio/TeamTest/2010
- Changed TestRunConfiguration element to TestSettings
- Updated adapterTypeName
- Removed CodeCoverage element
- Added default text for computerName attribute (required to open in visual studio)
- Removed leading \ from paths
- Changes for XUnit XML
- Create $startDateTime variable to store start time in correct format
- Update times element to use $startDateTime
- Updated template secondsToDuration to show fractions of a second correctly
- Change element references
- /test-results to /assembly
- /test-suit to /class
- //test-case to //test
- Change template getTestClassName to return className as AssemblyName.Class, AssemblyName
- Remove template getTestName
- Change totals to just use values in the XUnit assembly element. Executed = Total – Skipped, NotExecuted = Skip
What’s Missing - Username: Hard coded to TeamBuildUser
- Computername: Hard coded to TeamBuildServer
- Correct Date/Time for certain attributes, like finishing times
- Stack trace info does not link on machines other then the build machine (not sure if it’s possible to fix this)
- I do not use the Name or DisplayName properties on the XUnit Fact attribute so I didn’t test or look into working with those.
What’s Next Well, I skipped over the testing of the XSLT, which you can read about in my next post, where I modify the default build template in TFS 2010 to use XUnit instead of MS Test. I’m doing all this work for a Greenfield project, so I’m just getting things setup and working, and don’t have an extensive test library to try this out on yet. I’m also kind of excited to take a stab at writing a WF activity for Team Build 2010 to wrap this all up into one neat little package. The Template Here is the template in all it’s glory. It works for my brand new project consisting of 3 tests spread across 2 assemblies which I created just for testing my build. So your mileage may very. Remember, credit for the original template which is mainly intact goes to NUnit4TeamBuild. <?xml version="1.0" encoding="UTF-8" ?> <xsl:transform version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> <xsl:output indent="yes" /> <xsl:variable name="guidStub"> <xsl:call-template name="testRunGuid"> <xsl:with-param name="date" select="/assembly/@run-date"/> <xsl:with-param name="time" select="/assembly/@run-time"/> </xsl:call-template> </xsl:variable> <xsl:variable name="startDateTime"> <xsl:value-of select="concat(/assembly/@run-date, 'T', /assembly/@run-time)"/> </xsl:variable> <!--Set computer name and userName once so we can re-use. Hard coded until we can get the values passed --> <xsl:variable name="computerName"> <xsl:value-of select="'TeamBuildServer'"/> </xsl:variable> <xsl:variable name="userName"> <xsl:value-of select="'TeamBuildUser'"/> </xsl:variable> <xsl:template match="/"> <TestRun xmlns="http://microsoft.com/schemas/VisualStudio/TeamTest/2010"> <xsl:attribute name="id"> <xsl:value-of select="concat($guidStub,'30db1d215203')"/> </xsl:attribute> <xsl:attribute name="runUser"> <xsl:value-of select="concat($computerName,'\',$userName)"/> </xsl:attribute> <xsl:attribute name="name"> <xsl:value-of select="concat($userName,'@',$computerName,' ',$startDateTime)"/> </xsl:attribute> <TestSettings name="Local Test Run" id="c136642c-2e64-4f99-9ec3-30db1d215203"> <Description>This is a default test run configuration for a local test run.</Description> <Deployment> <xsl:attribute name="runDeploymentRoot"> <xsl:value-of select="//environment/@cwd" /> </xsl:attribute> <DeploymentItem filename="C:\temp\powerlink\Trunk\Rhino\Rhino.Mocks.dll"> <xsl:attribute name="filename"> <xsl:value-of select="/assembly/@name"/> </xsl:attribute> </DeploymentItem> </Deployment> </TestSettings> <ResultSummary> <xsl:attribute name="outcome"> <xsl:choose> <xsl:when test="/assembly/@failed > 0">Failed</xsl:when> <xsl:otherwise>Completed</xsl:otherwise> </xsl:choose> </xsl:attribute> <Counters error="0" timeout="0" aborted="0" passedButRunAborted="0" notRunnable="0" disconnected="0" warning="0" completed="0" inProgress="0" pending="0"> <xsl:attribute name="total"> <xsl:value-of select="/assembly/@total"/> </xsl:attribute> <xsl:attribute name="executed"> <xsl:value-of select="/assembly/@total - /assembly/@skipped"/> </xsl:attribute> <xsl:attribute name="notExecuted"> <xsl:value-of select="/assembly/@skipped"/> </xsl:attribute> <xsl:attribute name="passed"> <xsl:value-of select="/assembly/@passed"/> </xsl:attribute> <xsl:attribute name="failed"> <xsl:value-of select="/assembly/@failed"/> </xsl:attribute> <xsl:attribute name="inconclusive"> <xsl:value-of select="'0'"/> </xsl:attribute> </Counters> <RunInfos /> </ResultSummary> <Times> <xsl:attribute name="creation"> <xsl:value-of select="$startDateTime"/> </xsl:attribute> <xsl:attribute name="queuing"> <xsl:value-of select="$startDateTime"/> </xsl:attribute> <xsl:attribute name="start"> <xsl:value-of select="$startDateTime"/> </xsl:attribute> <xsl:attribute name="finish"> <xsl:value-of select="$startDateTime"/> </xsl:attribute> </Times> <TestDefinitions> <xsl:for-each select="//test"> <xsl:variable name="pos" select="position()" /> <UnitTest> <xsl:attribute name="name"> <xsl:value-of select="@method"/> </xsl:attribute> <xsl:attribute name="storage"> <xsl:value-of select="concat(//environment/@cwd,/assembly/@name)"/> </xsl:attribute> <xsl:attribute name="id"> <xsl:call-template name="testIdGuid"> <xsl:with-param name="value" select="$pos"/> </xsl:call-template> </xsl:attribute> <Css projectStructure="" iteration="" /> <xsl:if test="@description"> <Description><xsl:value-of select="@description" /></Description> </xsl:if> <Owners> <Owner name="" /> </Owners> <Execution> <xsl:attribute name="id"> <xsl:call-template name="executionIdGuid"> <xsl:with-param name="value" select="$pos"/> </xsl:call-template> </xsl:attribute> </Execution> <TestMethod adapterTypeName="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestAdapter, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.Adapter, Version=10.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" > <xsl:attribute name="name"> <xsl:value-of select="@method"/> </xsl:attribute> <xsl:attribute name="codeBase"> <xsl:value-of select="concat(//environment/@cwd,/assembly/@name)"/> </xsl:attribute> <xsl:attribute name="className"> <xsl:variable name="testClassName"> <xsl:call-template name="getTestClassName"> <xsl:with-param name="type" select="@type"/> </xsl:call-template> </xsl:variable> <xsl:value-of select="$testClassName" /> </xsl:attribute> </TestMethod> </UnitTest> </xsl:for-each> </TestDefinitions> <TestLists> <TestList name="Results Not in a List" id="8c84fa94-04c1-424b-9868-57a2d4851a1d" /> <TestList name="All Loaded Results" id="19431567-8539-422a-85d7-44ee4e166bda" /> </TestLists> <TestEntries> <xsl:for-each select="//test"> <xsl:variable name="pos" select="position()" /> <TestEntry testListId="8c84fa94-04c1-424b-9868-57a2d4851a1d"> <xsl:attribute name="testId"> <xsl:call-template name="testIdGuid"> <xsl:with-param name="value" select="$pos"/> </xsl:call-template> </xsl:attribute> <xsl:attribute name="executionId"> <xsl:call-template name="executionIdGuid"> <xsl:with-param name="value" select="$pos"/> </xsl:call-template> </xsl:attribute> </TestEntry> </xsl:for-each> </TestEntries> <Results> <xsl:for-each select="//test"> <xsl:variable name="pos" select="position()" /> <UnitTestResult testType="13cdc9d9-ddb5-4fa4-a97d-d965ccfc6d4b" testListId="8c84fa94-04c1-424b-9868-57a2d4851a1d"> <xsl:attribute name="startTime"> <xsl:value-of select="$startDateTime"/> </xsl:attribute> <xsl:attribute name="endTime"> <xsl:value-of select="$startDateTime"/> </xsl:attribute> <xsl:attribute name="testName"> <xsl:value-of select="@method"/> </xsl:attribute> <xsl:attribute name="computerName"> <xsl:value-of select="$computerName"/> </xsl:attribute> <xsl:attribute name="duration"> <xsl:call-template name="secondsToDuration"> <xsl:with-param name="seconds" select="@time"/> </xsl:call-template> </xsl:attribute> <xsl:attribute name="testId"> <xsl:call-template name="testIdGuid"> <xsl:with-param name="value" select="$pos"/> </xsl:call-template> </xsl:attribute> <xsl:attribute name="executionId"> <xsl:call-template name="executionIdGuid"> <xsl:with-param name="value" select="$pos"/> </xsl:call-template> </xsl:attribute> <xsl:attribute name="outcome"> <xsl:choose> <xsl:when test="@result='Pass'"> <xsl:value-of select="'Passed'"/> </xsl:when> <xsl:when test="@result='Fail'"> <xsl:value-of select="'Failed'"/> </xsl:when> <xsl:otherwise> <xsl:value-of select="'NotExecuted'"/> </xsl:otherwise> </xsl:choose> </xsl:attribute> <Output> <xsl:for-each select="./failure"> <ErrorInfo> <Message> <xsl:value-of select="./message"/> </Message> <StackTrace> <xsl:value-of select="./stack-trace"/> </StackTrace> </ErrorInfo> </xsl:for-each> </Output> </UnitTestResult> </xsl:for-each> </Results> </TestRun> </xsl:template> <xsl:template name="substring-after-last"> <xsl:param name="string" /> <xsl:param name="delimiter" /> <xsl:choose> <xsl:when test="contains($string, $delimiter)"> <xsl:call-template name="substring-after-last"> <xsl:with-param name="string" select="substring-after($string, $delimiter)" /> <xsl:with-param name="delimiter" select="$delimiter" /> </xsl:call-template> </xsl:when> <xsl:otherwise> <xsl:value-of select="$string" /> </xsl:otherwise> </xsl:choose> </xsl:template> <xsl:template name="getTestClassName"> <!--Takes type in the form of Assembly.Class and returns Assembly.ClassName, Assembly--> <xsl:param name="type" /> <xsl:value-of select="concat($type, ', ', substring-before($type, '.'))" /> </xsl:template> <xsl:template name="testIdGuid"> <xsl:param name="value" /> <xsl:variable name="id"> <xsl:call-template name="dec_to_hex"> <xsl:with-param name="value" select="$value"/> </xsl:call-template> </xsl:variable> <xsl:value-of select="concat($guidStub,substring(concat('000000000000', $id),string-length($id) + 1, 12))"/> </xsl:template> <xsl:template name="executionIdGuid"> <xsl:param name="value" /> <xsl:variable name="id"> <xsl:call-template name="dec_to_hex"> <xsl:with-param name="value" select="$value"/> </xsl:call-template> </xsl:variable> <xsl:value-of select="concat($guidStub,substring(concat('000000000000', $id),string-length($id) + 1, 12))"/> </xsl:template> <xsl:template name="testRunGuid"> <xsl:param name="date" /> <xsl:param name="time" /> <xsl:variable name="year"> <xsl:value-of select="substring($date,1,4)"/> </xsl:variable> <xsl:variable name="month"> <xsl:value-of select="substring($date,6,2)"/> </xsl:variable> <xsl:variable name="day"> <xsl:value-of select="substring($date,9,2)"/> </xsl:variable> <xsl:variable name="hour"> <xsl:value-of select="substring($time,1,2)"/> </xsl:variable> <xsl:variable name="minute"> <xsl:value-of select="substring($time,4,2)"/> </xsl:variable> <xsl:variable name="second"> <xsl:value-of select="substring($time,7,2)"/> </xsl:variable> <xsl:variable name="hexYear"> <xsl:call-template name="dec_to_hex"> <xsl:with-param name="value" select="$year"/> </xsl:call-template> </xsl:variable> <xsl:variable name="hexMonth"> <xsl:call-template name="dec_to_hex"> <xsl:with-param name="value" select="$month"/> </xsl:call-template> </xsl:variable> <xsl:variable name="hexDay"> <xsl:call-template name="dec_to_hex"> <xsl:with-param name="value" select="$day"/> </xsl:call-template> </xsl:variable> <xsl:variable name="hexHour"> <xsl:call-template name="dec_to_hex"> <xsl:with-param name="value" select="$hour"/> </xsl:call-template> </xsl:variable> <xsl:variable name="hexMinute"> <xsl:call-template name="dec_to_hex"> <xsl:with-param name="value" select="$minute"/> </xsl:call-template> </xsl:variable> <xsl:variable name="hexSecond"> <xsl:call-template name="dec_to_hex"> <xsl:with-param name="value" select="$second"/> </xsl:call-template> </xsl:variable> <xsl:variable name="padYear"> <xsl:value-of select="substring(concat('0000', $hexYear),string-length($hexYear) + 1, 4)"/> </xsl:variable> <xsl:variable name="padMonth"> <xsl:value-of select="substring(concat('00', $hexMonth),string-length($hexMonth) + 1, 2)"/> </xsl:variable> <xsl:variable name="padDay"> <xsl:value-of select="substring(concat('00', $hexDay),string-length($hexDay) + 1, 2)"/> </xsl:variable> <xsl:variable name="padHour"> <xsl:value-of select="substring(concat('00', $hexHour),string-length($hexHour) + 1, 2)"/> </xsl:variable> <xsl:variable name="padMinute"> <xsl:value-of select="substring(concat('00', $hexMinute),string-length($hexMinute) + 1, 2)"/> </xsl:variable> <xsl:variable name="padSecond"> <xsl:value-of select="substring(concat('00', $hexSecond),string-length($hexSecond) + 1, 2)"/> </xsl:variable> <xsl:value-of select="concat($padYear,$padMonth,$padDay,'-',$padHour,$padMinute,'-',$padSecond,'00-91c4-')"/> </xsl:template> <xsl:variable name="hex_digits" select="'0123456789ABCDEF'" /> <xsl:template name="dec_to_hex"> <xsl:param name="value" /> <xsl:if test="$value >= 16"> <xsl:call-template name="dec_to_hex"> <xsl:with-param name="value" select="floor($value div 16)" /> </xsl:call-template> </xsl:if> <xsl:value-of select="substring($hex_digits, ($value mod 16) + 1, 1)" /> </xsl:template> <xsl:template name="secondsToDuration"> <xsl:param name="seconds" /> <xsl:variable name="duration"> <xsl:choose> <xsl:when test="$seconds"> <xsl:variable name="hours" select="floor($seconds div 3600)" /> <xsl:variable name="mins" select="floor(($seconds - ($hours * 3600)) div 60)" /> <xsl:variable name="secs" select="floor($seconds - ($hours * 3600) - ($mins * 60))" /> <xsl:variable name="frac" select="substring($seconds - floor($seconds), 3, 7)" /> <xsl:value-of select="substring(concat('00', $hours), string-length($hours) + 1, 2)" /> <xsl:text>:</xsl:text> <xsl:value-of select="substring(concat('00', $mins) ,string-length($mins) + 1, 2)" /> <xsl:text>:</xsl:text> <xsl:value-of select="substring(concat('00', $secs), string-length($secs) + 1, 2)" /> <xsl:if test="$frac > 0"> <xsl:value-of select="concat('.', $frac)" /> </xsl:if> </xsl:when> <xsl:otherwise>00:00:00.0000000</xsl:otherwise> </xsl:choose> </xsl:variable> <xsl:value-of select="$duration" /> </xsl:template> </xsl:transform>
With the release of the VS2010 Release Candidate, I went about uninstalling VS2010 beta 2 and ran into an issue. I started out by going to Add/Remove programs to uninstall VS2010 Ultimate Beta 2, but during the uninstall process, it hung at the point of running VSTestConfig_UnInstall_def. At the same time, my network adapter was set to disabled, and could not be enabled. I ended up having to do a hard reset on the machine. Upon reboot, the entry for Visual Studio was not in Add/Remove programs, however, running the setup off the Beta 2 ISO allowed me to pick up where I left off. The VS2010 RC Readme contains instructions for uninstalling the RC, and a link to the VS 2010 B2 readme for the uninstall instructions for that version. So after uninstalling VS 2010 Ultimate, I uninstalled the remaining components in the following order: - Visual Studio 2010 Tools for Office Runtime Beta 2
- The .NET Framework version 4 Language Pack
- This was not present on my computer but included in the uninstall instructions
- The .NET Framework version 4 Extended
- This removed 2 “.Net Framework 4” items from the list
- I was prompted to reboot and did so
- The .NET Framework version 4 Client
- I was prompted to reboot and did so
At this point, I felt that I had uninstalled everything I needed to, so I proceeded to mount the RC .iso and proceed with the installation. It looks like we’ll be getting the premium edition at work when it RTM’s, so I went with that edition for the Release Candidate. I figured there is no sense in playing around with features that I won’t have in a couple of months. After a quick reboot during the install (happens after .Net 4 is installed), the rest of the installation went flawlessly. I fired up Visual Studio, and it did open noticeably faster for my small solution. My TDD.Net plugin for XUnit was still fully functional which I was not expecting. All unit tests passed for my Windows Service application I’ve been working on. On the web side of things, just about everything is working, and all tests pass on my Asp.Net MVC application. The one exception is the client side Asp.Net MVC validation on my logon page. The other pages that have the client side validation work fine. I’ve tried updating the .js files from a newly created MVC project, as well as trying the Microsoft Ajax script files instead of the JQuery files. At this point I’m not going to waste any more time on it. Next up, TFS 2010 RC install.
I just started my 3rd week of training (week 6 on my training plan) and am starting to feel fully recovered from my ruptured appendix incident. My power and endurance is still not where it was before my hospital stay, but I’m on a definite upward trend. I suffered a running injury around the area of my right knee, due to going out to long too soon. This has prevented me from doing my running workouts. I’ve been substituting in the elliptical trainer to at least get the cardio aspect. I think I’m making some good progress though, as the pain is showing up less and less, and some activities that used to bring the pain on, no longer do. We’ll see how Friday’s run goes. As I mentioned in my year in review post, I’m training with a Powertap this year. Last year I had an iBike iAero, but was not very happy with it. The iBike power meters don’t measure power directly (and I guess the Powertap doesn’t either, but it’s a lot closer), rather it calculates all of the opposing forces working against you (wind, gravity, etc). I was never happy with the numbers I got out of it until they released a major firmware update. maybe I didn’t do the calibration correct early in the season, but I don’t have to second guess my self with the Powertap. So what does training with power give me? It allows me to set specific goals, track progress and adjust my workouts based on the power data collected. I’m currently reading thru “Training and Racing with a Power Meter” by Hunter Allen and Andrew Coggan, which is the book that is always recommended on the forums. I came across another book that is supposed to be targeted towards triathletes who are training with power, and will probably take a look at that. The Allen/Coggan book has a lot of references to road racing which don’t translate directly to triathlon. One of the first rides I did with my new Powertap was to do a Functional Threshold Power (FTP) test. I used the testing protocol in the Allen/Coggan book on my Kurt Kinetic Trainer. My result, an abysmal 190 watts. While I didn’t have the Powertap pre appendix rupture, I did have some ride files with my average speed on the KK trainer. Based on this I can tell that I dropped almost 30 watts due to my appendix, surgery and hospital stay. I’ve been hoping that I get some of this back for “free”, and with a jump in FTP of 4 watts in just one week, I think I will get some of it back for “free”. With around 7 months of training left, I’m not all that concerned right now. I know what I was able to do last year, and I’m already exceeding my weekly training volume from last year and it’s only February. Last week I had about 12.5 hours of training, and this week I should be closer to 14, even with a busted swim session thanks to a little kid throwing up in the pool. I’d like to bump up the bike hours a bit on my training plan, and I’m working in some VO2max and threshold workouts to help make up for my current FTP. I have a very ambitious goal of raising my FTP to 275 before Ironman. I don’t know if this is a realistic goal, but you need to have something to shoot for. I will re-evaluate this goal once I can get riding outside once again. With the power tap purchase, I’m pretty well set on all the big ticket items. I want to get a wheel cover for my rear wheel, and will probably look to rent an aero wheel for the front. I’ll go thru a couple of pairs of shoes I’m guessing, and could stand a new set of bike shoes and pedals. I may trade in the Garmin Forerunner 305 for a 310xt. The increased battery life and the ability to record power data is very nice. I’ll also be trying various nutritional products to satisfy my race day fueling needs. My plan is to try to be able to hit 300 calories an hour on the bike using a highly concentrated “sports” drink supplemented by gel. I’d like to be able to get 6 hours worth of calories into a single 24oz bottle, but I’m not sure it can be done. I think the long bike rides are as much about figuring out your race day nutrition as it is for fitness. Until next time.
Scenario Locally developed Asp.Net MVC 2 application which needs to be deployed to a remote web server. The web server, running IIS 7.5 is hosted by a 3rd party, but we have full administrative access (Virtual Machine). In order to meet our deployment needs, we will install and configure MSDeploy. MSDeploy is a new add-on for IIS 6 and above which enables the package and installation of web applications, and optionally remote deployment and administration. You can also use it to synchronize settings between web servers, for example in a Web Farm. It is the remote deployment that we are most interested in, which is built upon the packaging and installation piece. For this post I will be assuming a deployment source of Visual Studio 2010 Beta 2. MSDeploy ships with an API which can be used to do the deployment from say a build task on a Build Server (stay tuned for a follow-up post on that). Installation First you need to download MSDeploy to your remote web server and install it. Here is the direct link for the 64 bit version of MSDeploy. Other version can be downloaded from the MSDeploy website. Once downloaded, run the MSI installer and select the custom option. You need to select the IIS 6 Deployment Hanlder to get the remote deployment feature. I also select the UI Module and Management Service Delegation UI (See screen shot below). After selecting the desire components, hit next, then install. Configuration Overview If you had IIS Manager open during the installation, you will need to restart it. Once restarted, you can right click on the Default Web Site and you should see a deploy option listed. Next we will be configuring some accounts and permissions for our remote deployment, following the instructions provided by Microsoft, which can be viewed in their entirety here. Following these rules we will be able to create a specific account for our application which limits our external exposure. I’m happy to see we can use the new IIS Users/Management feature instead of having to create a local or domain windows account. Here is an overview of the steps we will be doing: -
Create an IIS Manager user account for the customer. -
Give the WMSvc account access to the customer’s directory. -
Configure delegation for the customer. -
Add delegation rules for the customer. -
Restart the Web Management Service. -
Test the Web Deployment Handler. Step 1 – Create an IIS Manager User In IIS manager, select the root server level (this is usually named after the name of the computer IIS is running on, it’s the node just below Start Page). Once selected scroll down in the middle pane to the Management section and select IIS Manager Users. - Double click on IIS Manger Users
- Click Add User from the actions list on the far right side
- Enter a username, and a strong password
- Click OK
I used the naming convention, [AppName]User, and a randomly generated password from KeePass. Once created you get additional options for managing the user, like Change Password, and Disable. Step 2 – Grant the WMSvc account access to our directory The Service name (as shown in the Services MMC) is Web Management Service, and is not started by default. Using the Services MMC, start the Web Management Service and set it’s start-up type to Automatic, or Automatic-Delayed. Note the user account that the service is running under. By default it runs as Local Service. You will need to Grant the Local Service account full permissions to the targeted deployment directory. Since I’d like to look into running the Web Management Service as a lower privileged account in the future, I took the extra steps to create a local user group called MSDeplyUsers, added Local Service to it, and granted permissions to the group. This will allow me to more easily switch accounts in the future. Before proceeding, you may need to make an additional change to your IIS server configuration. By default, the Management Service is not configured to accept remote connections. While the Web Management Service is stopped, in IIS Manager, click your top level server node on the left, and then select Management Service under Management in the center pane. Check the box for Enable remote connections, and select a SSL certificate. I’m using a self signed SSL cert for now. I also added IP restrictions to restrict remote deployments from my office only. You can use www.whatismyip.com to determine you IP, but unless you have a static IP, you may have to come back and change this often. You will also need to add the port specified to your allow section in your firewall rules. Step 3 – Configure Delegation In order for our user account we created in Step 1 to have access to their folder, we need to grant permissions on the folder (or site). You can only set folder level permissions if the folder is setup as an Application. Once you have selected the level (using the navigation tree on the left) at which to grant permissions, you can select the IIS Manger Permissions option in the central pane. - Click on IIS Manger Permissions
- Click on Allow User on the far right pane
- Select IIS User and then select the user you created in Step 1
Step 4 – Add delegation rules We now need to grant some specific rights to our user so they can deploy. This is done using the Management tools at the server level. - Double click on Management Service Delegation
- Click on Add rule on the actions pane
- Select the Deploy Content Only Template (I, as an administrator will handle the configuration of the application directory and advanced configuration).
- Accept all default
- Providers: contentPath
- Actions: *
- Path Type: Path Prefix
- Path: {userScope}
- Run As: Current User
- When you click OK, you will be asked to provide the name of a user to add to the rule. This is where you specify your user you created in Step 1.
The steps for IIS 7.5 may differ from IIS 7. Step 6 – Restart Web Management Service Pretty self explanatory, using the Services restart, or start the Web Management Service. Step 7 – Test In visual study, you need to publish your web application to your newly configured remote server. Right click on your web project and choose Publish. In the screen that comes up, select MSDeploy Publish as your Publish Method and fill in the settings to match what we did in Steps 1 – 6. Assuming everything is setup correctly, you should be able to publish you application to your remote server. Based on the rules we setup in Step 4, this will only publish content. It will not deploy database scripts, or create the application for you (mark the folder as an application). You can follow the more detailed instructions to set this up, but I wanted to keep this simple for now. Trouble Shooting Nothing ever works right on the first time does it? It took me a couple of tries (by couple I mean hundreds) to get all the configuration setup on the client and server. Here are some of the errors I got and what I did to fix them: Connection Timeout Could not complete the request to remote agent URL 'https://Server:8172/msdeploy.axd?site=<site>'. Unable to connect to the remote server A connection attempt failed because the connected party did not properly respond after a period of time, or established connection failed because connected host has failed to respond <Server>:8172 If you get connection timeouts, make sure you can browse to the Service URL entered above in the publish web screen. If you can’t, you hosting provider may be blocking port 8172. You should at least get prompted for credentials when using a Web Browser. If using a self signed cert, you will also get a certificate warning in the browser. Finally, you should see you attempt in the Log Files for the Management Service. The default path for the log files is: %SystemDrive%\Inetpub\logs\WMSvc Error 400 Bad Request Remote agent (URL https://Server:8172/MsDeploy.axd:8172/msdeploy.axd?site=<site>) could not be contacted. Make sure the remote agent service is installed and started on the target computer. An unsupported response was received. The response header 'MSDeploy.Response' was '' but 'v1' was expected. The remote server returned an error: (400) Bad Request. This one was driving me nuts, and the fix/cause is absolutely ridiculous. Notice the two /MsDeploy.axd, one pascal cased, and the other all lower case. Now take a look at the following screen shot and compare have I have in the ServiceUrl and the example service URL provided in the same dialog. Looks like it should work right? Nope, doesn’t work. What fixed it, changing it so that the /MsDeploy was all lower case as in https://Server:8172/msdeploy.axd. This got rid of the first MsDeploy.axd. Error 401 Remote agent (URL https://Server:8172/msdeploy.axd?site=Default Web Site) could not be contacted. Make sure the remote agent service is installed and started on the target computer. An unsupported response was received. The response header 'MSDeploy.Response' was '' but 'v1' was expected. The remote server returned an error: (401) Unauthorized. The first question I had was, is the site “Default Web Site” correct? I was worried about the spaces in the name, but after reviewing the log files, the correct escape characters for a space where present. The full result code was a 401.2. I saw that my user account I created in Step 1 was being passed thru, and assumed that it had something to do with that. I changed my credentials to an Windows Administrative account and gave that a go, and it worked. This means the service itself works, but either my authentication or authorization for the IIS user account is incorrect. If you are having problems with an administrative account, you need to make sure that you have the option enabled for Administrators to bypass rules. This is under Management Service Delegate, Edit Features. If you change this setting, you need to restart the Management Service.
When I had the Allow Administrators to bypass rules box unchecked, I got a different error message, one that said I was unauthorized to perform an action, not a 401. This led me to believe that my IIS User was suffering from an Authentication issue, not authorization. Since the IIS Users are for management, I decided to try connecting to my Application using the IIS Management tool. Running this as a non-administrator you presented with a “empty” view. Right clicking on clicking on Start Page allows you to Connect to a Server, Web Site or Application. Selecting Application, and entering my credentials left me with a 401 Unauthorized error, confirming my belief that its a problem with the IIS User and not MSDeploy.
After playing around with the IIS settings, the IIS User account started working when connecting via IIS Manager, but not via VS. I do not believe I changed anything (without changing it back) to explain why it started working in IIS manager. The result codes when it was failing was the same 401.2 as I was getting when connecting via IIS. For now I have to leave this as an open issue, and maybe try again on a local server. UPDATE: I got it working more or less. Check out the comments for what I had to do.
|