Monday, 28 November 2005

Removing TFS Zombie Projects

Today I was testing the Team Foundation Server part of VSTS. I was adding and removing projects to get the screenshots for the course I'm in the process of authoring. At some point I added a project then deleted it using TFSDeleteProject (TFSDeleteProject ships with Visual Studio 2005 Team Explorer, it's in the c:\program files\Microsoft Visual Studio 8\Common7\IDE\PrivateAssemblies\ directory).

Having deleted the project I tried to re-add it. The wizard started to run the add task and failed towards the end telling me that it could not rollback the addition. This is an omimous message as it means you have a "zombie" project. You can't remove the project from TFS and you can't list the project in the team explorer.

Taking my courage in my hands I looked at the SQLServer 2005 TFS tables (there are dozens of them), for a clue as to what is going on. If found a couple of things. In the TfsIntegration databse there is a table called tbl_projects and this has a column called 'state'. For projects that had been correctly installed the value of this was 'WellFormed' for my zombie project it was 'New' (IIRC, I've sinced chenged the value and my memory isn't what it used to be). Changing this value to 'WellFormed' for my zombie project lets me add the project to the Team Explorer but I still couldn't delete it using TFSDeleteProject.

My next thought was to run the SQL Server profiler to see what TFSDeleteProject was sending to the database to delete the project. It appears that TFSDeleteProject first checks that this user has permissions to delete the project. It then calls this "exec ObjectExists @Path=N'/Foobar',@AuthType=1" on SQL Server's Report Server, i.e. does the Report Server have an entry for the project. It turned out in my case that Report Server did not have an entry. It looks like the Add Project Wizard failed before adding the reports to the Report Server.

Opening Report Server in SQL Server Management Studio and adding a new Folder called 'FooBar' (the name of the project) fixed the problem. Now, running TFSDeleteProject deleted the project. I had to run TFSDelete /force to make sure that TFSDelete continues even if it finds errors. I no longer have a zombie project.

I've also seen zombie projects when TFS seems not to be correctly installed, not sure if this has the same root cause but hopefully this post will save people some wasted time.

* Edited * Thanks to James Manning for pointing out that the that name of the exe used to delete projects is TFSDeleteProject and not TFSDelete which I had used originally

Posted by kevin at 1:57 PM in Net

Thursday, 17 November 2005

More SQLExpress Problems

This time on an installation!

So I've just taken delivery of a brand new laptop, a Dell D810. I put in the VS2005 disk and install, thinking that nothing can go wrong as it's a clean box. How wrong I was, SQLExpress failed to install, and I get the really useful log entry "Component Microsoft SQL Server 2005 Express Edition x86 returned an unexpected value"

Googling I found out that you can re-install SQLExpress from the VS2005 DVD, it's in [driver]:\vs\wcu\SSE\sqlexpr32.exe. Running this command causes the installer to run, and again it failed but this time I got a much more meaningful message.

I had compressed by C drive when I set this new machine up (I need the disk space) and it turns out the SQLExpress (and I guess SQL Server) can't be installed into a compressed directory. Uncompressing the directory, hitting re-try and I finally have a SQLExpress install. Wahoo! (I think)

Posted by kevin at 8:18 PM in Net

Tuesday, 15 November 2005

SQLExpress Problems

I've recently ran into a strange problem with SQLExpress where I couldn't create a database. This typically showed itself when I was running an ASP.Net 2 app for the first time and either creating a DB to hold profile or security data.

The error something like
"Failed to generate a user instance of SQL Server due to a failure in starting the process for the user instance. The connection will be closed."
This was using the default LocalSqlServer connection string (from machine.config), changing that string to say 'User Instance=false' then gave me a different error
"A connection was successfully established with the server, but then an error occurred during the login process. (provider: Shared Memory Provider, error: 0 - No process is on the other end of the pipe.)"

Also if I tried to use the SQL Server Management Studio to connect to SQLExpress and then try to attach to an existing MDF file I get the following error:

-----------------------------------------------------
The file "D:\home\kevinj\Course\dotnet\ASP2.Net\prep.2\Security\App_Data\aspnetdb.mdf" is compressed but does not reside in a read-only database or filegroup. The file must be decompressed.
Could not open new database 'C:\HOME\KEVINJ\COURSE\DOTNET\ASP2.NET\PREP\SECURITY\APP_DATA\ASPNETDB.MDF'. CREATE DATABASE is aborted. (Microsoft SQL Server, Error: 5118)

For help, click: http://go.microsoft.com/fwlink?ProdName=Microsoft+SQL+Server&ProdVer=09.00.1399&EvtSrc=MSSQLServer&EvtID=5118&LinkId=20476
-----------------------------------------------------

These problems left me stranded for days, then today, on another machine, I hit a different set of problems I kept getting the following
"Failed to generate user instance of SQL Server due to a failure in starting the process for the user instance. The connection will be closed."
and if I set "User Instance" to false the following
"An attempt to attach an auto-named database for file C:\[app_path]\ASPNetDB.mdf failed. A database with the same name exists, or specified file cannot be opened, or it is located on UNC share."

I figured first of all this may have been something to do with the fact that I run as non-admin, so I tried running VS2005 as admin and executed the web site and sure enough it worked. So I thought I'd need to run as admin. I added myself to the admin group, logged back in and tried the app again, and got the same error! This was too weird. I had two users, both admins one for whom the app worked and one where it didn't.

In these circumstances Google is your friend. I found this post on a Microsoft forum. If you don't want to follow the link, the short answer is that SQLExpress creates a directory per user in "c:\Documents and Settings\[user]\Local Settings\Application Data\Microsoft\Microsoft SQL Server Data\SQLEXPRESS" that it uses to store information. Deleting this directory has fixed both of my problems.

Posted by kevin at 2:32 PM in Net

Monday, 14 November 2005

Managing Test Databases for VSTS

I'm having fun playing with data driven testing in VSTS. In theory data driven testing is easy, but there are some nuances that you have to think about, in particular where do you store the data to run the data driven test. Before we look at that let's look at how to setup a DataDriven test.

You need a couple of things to do this, firstly your test class needs a property of type TestContext, the context holds references to various things that the data driven test needs. We'll talk about the details in a sec. You then need to write your test. The test looks something like:

 [TestMethod] [DataSource("System.Data.SqlClient",
   "server=.\\SQLExpress;Initial Catalog=StarshipTestData;Integrated Security=True",
   "FireTorpedoesTest",
 DataAccessMethod.Sequential)]
public void TestFirePhotonTorpedoe()
{
The TestMethod attribute marks the method as a test, and the DataSource attribute provides the location of data needed for a data driven test. Here we specify that the test database is managed by SQLExpress; the database is 'TestData' and the database is FireTorpedoesTest.

The table for this test looks like this

CREATE TABLE dbo.FireTorpedoesTest
(
   TestId int NOT NULL PRIMARY KEY,
   MaxTorpedoes int NOT NULL,
   TorpedoesToFire int NOT NULL,
   ShouldPass bit NOT NULL
)  ON [PRIMARY]

The test method would be called once per test. Within the body the test uses the TestContext's DataRow property to access the current row in the database (these will be presented sequentially or rnadmoyl (sic!) depending on the datasource parameters). The test would look something like:

public void TestFirePhotonTorpedoe()
{
    int id = (int)TestContext.DataRow[(int)Column.TestId];
    int maxTorpedoes = (int)TestContext.DataRow[(int)Column.MaxTorpedoes];
    int torpedoes = (int)TestContext.DataRow[(int)Column.TorpedoesToFire];
    bool shouldPass = (bool)TestContext.DataRow[(int)Column.ShouldPass];

    // test the class here
}

This is relatively easy to setup and manage. However the problem is with the DataSource attribute. At the moment this relies on the database being available within SQL Server or SQLExpress (or whatever other database you chhose to use), and in particular requires the data set be referenced directly through that server. This is an issue if you work in a team. maybe not everybody has access to a full SQL Server instance, or if they do maybe they don't have the rights to create and manage a database. A better solution would be to be able to pass the database around with the test code, and simply have the DataSource reference the MDF file (assuming you are using SQLExpress) that contains the data. (in fact the best solution is to create the database at the start of the test process, something I'll talk about at some other time). To pass the databse around you need to create it in a local directory, relative to the project), something like:

CREATE DATABASE StarshipTest ON PRIMARY (NAME=N'StarshipTest',
        FILENAME = 'C:\project\StarshipTest\data\StarShipTest.mdf') 
then in the DataSource do this
[DataSource("System.Data.SqlClient",
   "server=.\\SQLExpress;AttachDBFilename=C:\\project\\StarShipTest\\data\\StarShipTest.mdf;Integrated Security=True",
   "FireTorpedoesTest",
 DataAccessMethod.Sequential)]
The problem here is that, while this will work, the path used in the DataSource is absolute. Ideally you want a relative path as the project location on your machine is likely different to mine, so the first thought would be to try this
[DataSource("System.Data.SqlClient",
   "server=.\\SQLExpress;AttachDBFilename=data\\StarShipTest.mdf;Integrated Security=True",
   "FireTorpedoesTest",
 DataAccessMethod.Sequential)]
Unfortunately this doesn't work, connection string paths cannot be relative!

After scratching my head I came up with various solutions to this, based on the fact that ADO.Net 2.0 has a 'substitution string' called DataDirectory, you see this used by ASP.Net 2.0 (look in machine.config for an example) and until today I thought it was only used there, this entry should me how it can be used by any app.

This means the DataSource can look like this

[DataSource("System.Data.SqlClient",
   "server=.\\SQLExpress;AttachDBFilename=|DataDirectory|\\StarShipTest.mdf;Integrated Security=True",
   "FireTorpedoesTest",
 DataAccessMethod.Sequential)]
But that begs another question, where is |DataDirectory| in a VSTS test run? It turns out that VSTS creates a new directory for each run of the tests (called the test deployment directory), on my machine this directory is in <solution base>\TestResults\[username_machinename_datetimestring], where username is the currently logged in user, machine name is the name of this windows machine, the datatimestring is the time the test was started. So to use the |DataDirectory| syntax you have to copy the MDF and LDF files into the current deployment directory. This is easy to achieve, you simply annotate the test method with a DeploymentItem attribute. This attribute references a file or a directory, and whatever is referenced is copied into the deployment directory. It is a way of copying something needed for every test. Our method now looks like
[TestMethod]
[DeploymentItem("StarshipTest\\data\\")]
[DataSource("System.Data.SqlClient",
   "server=.\\SQLExpress;AttachDBFilename=|DataDirectory|\\StarShipTest.mdf;Integrated Security=True",
   "FireTorpedoesTest",
 DataAccessMethod.Sequential)]
public void TestFirePhotonTorpedoe()
{
And this works!

However, this is not the end of the story. The MDF file I use for this test contains one table of four columns with five rows of data, yet is 2 1/4 MB in size. Running the test suite hundreds of times would soon eat up gigabytes of disk space, it would be better to keep the data in one place and have the test reference it there. The article I referenced earlier points out that it is possible to change the location of the DataDirectory. To take advantage of this I added a ClassInitialize method. This is a static method that gets called once when the code for the test is first loaded. The method looked like

public static void ClassSetup(TestContext testContext)
{
    AppDomain.CurrentDomain.SetData("DataDirectory", @"C:\project\StarShipTest\data\");
}
which puts us back where we started, i.e. we've hardcoded the path again.

VSTS to the rescue. In VSTS you can add an app.config file to the project, this file (post beta2) is copied into the build and deploy directories and renamed to [mytestdll].dll.config and is read and parsed by the runtime (NUnit has a very similar mechanism), which means that our code can read this file. Creating an app.config file that looked like this

<configuration>
  <appSettings>
    <add key="DataDirectory" value="C:\home\kevinj\Course\dotnet\VSTS\demos\TDD\Data\"/>
  </appSettings>
</configuration>
means our code can look like this:
public static void ClassSetup(TestContext testContext)
{
    AppDomain.CurrentDomain.SetData("DataDirectory", ConfigurationManager.AppSettings["DataDirectory"]);
}

This is much better, we have an app.config that contains the settings and code that reads it. This is fine if every developer has thier own app.config, i.e. if the app.config file is not part of version control. If it is part of version control then every developer will need to change the DataDirectory setting, which is not a great solution. What is needed is a way to get a version of app.config that can be version controlled but to allow every developer to have an independent configuration section. This can be done by using the file attribute of app.config giving us this

<configuration>
  <appSettings file="mysettings.config"/>
</configuration>
and a file called mysettings.config that looks like
<appSettings>
  <add key="DataDirectory" value="C:\home\kevinj\Course\dotnet\VSTS\demos\TDD\Data\"/>
</appSettings>
However, we're now back to the problem of getting mysettings.config into the deployment directory. The file has to be copied before the ClassInitialize method runs. Adding the DeploymentItem to ClassInitialize doesn't work, neither does adding it to TestInitialize, seems it only works with a TestMethod. According to MSDN DeploymentItem can be added to the class, but according to the compiler it can't :)

To solve this you have to use the test configuration framework. Open the configuration file (by default localtestrun.testrunconfig), in its default editor and there is a 'Deployment' page. In here you can specify that the files to be copied when the test starts to execute. Add the mysettings.config file here (it will be saved with a relative file name) and you can finally run the tests with the database file where you want it and not being copied to the deployment directory each time you execute the test. Of course the downside now is that if you have multiple configurations you need to edit each one, and you need to create mysettings.config for the test server.

Posted by kevin at 7:45 PM in Net

Friday, 11 November 2005

Drawing - how do they do that?

This animation amazed me because I've always wanted to be able to draw and I'm completely useless.

Posted by kevin at 5:58 PM in General

Friday, 4 November 2005

Creating Databases in SQLExpress

As much for my benefit as I tend to forget these details when I don't use the software frequently.

I'm working with VSTS and in particular the unit testing bits. Last night I went to create a database to store the tables for doing data driven testing. As I was working in Visual Studio I wanted to create the databse there because when I teach this stuff I want to show the students how to use the tools availlable. I also wanted to use SQLExpress as that will be available to most folks when they take away demos or examples whereas SQLServer may not be. Eventually I discovered several ways of creating SQLExpress databases and I wanted to document them here.

In VS2005 you open the server explorer and right click on the 'Data Connections' then choose 'Create New SQL Server Database...'

This gives the following dialog

.

Notice that the name given to the server is ".\SQLExpress" i.e. the version of SQLExpress running on this machine, after entering the filename you can think click on OK.

This will create the database on this machine. This is fine, but not quite what I wanted. The created database files are stored in "C:\Program Files\Microsoft SQL Server\MSSQL.4\MSSQL\Data" which is a problem for two reasons. First: I run as non-admin on my machine and this directory is not available to me; Second: I would like the database files (the .mdf and .ldf files) to be in my application directory so that I can distribute them easily. To do this I would have to first copy the files to the directory I was using, then drop the database from SQLExpress and re-attach to the files in the new location. I can't find anyway to drop files from VS2005 server explorer, if anybody knows of a way then let me know.

Option two was to use "SQL Server Management Studio" that ships with SQLServer 2005, there will be a SQLExpress "Express Manager" but this is not yet available. Sure enough in the Management Studio I can attach to SQLExpress and create a databse whereever I want

.

Again this is fine, so log as my system has SQL Server 2005 installed, but again I see two problems. The first is I may not have the full SQL Server installed, just SQLExpress, and secondly, I like to see what is going on and so understand how to create these databases myself without help from a graphical tool (I'm a console kind of guy!).

With both SQL Server 2005 and SQLExpress Microsoft ship a new command line tool called sqlcmd. This is installed at "C:\Program Files\Microsoft SQL Server\90\Tools\Binn" on my install. You can start a command prompt and use the fully qualifed path name, if you install SQL Server you should have a entry off the start menu for this. The entry will be under "All Programs..Microsoft SQL Server 2005..Configuration Tools" and is called the "Notification Services Command Prompt". This is a command prompt with the path set correctly so that the SQL tools can be used.

To use sqlcmd to access SQLExpress you run it like this sqlcmd -S .\SQLExpress note that the switches are case sensitive.
To run as a specific user you use

sqlcmd -U sa -S .\SQLExpress
You should see a prompt (afer entering a password) that looks like
1>
You can type
exit
to exit SQLCommand or you can enter you SQL code.

To create a databse at a specific location the SQL looks something like

USE [master] 
GO
CREATE DATABASE Test ON PRIMARY (NAME=N'Test', FILENAME = 'c:\temp\data\Test.mdf') 
GO
or with a specific log file name
USE [master]
GO
CREATE DATABASE TEST ON PRIMARY (NAME=N'TEST', FILENAME = 'c:\temp\data\Test.mdf') 
 LOG ON ( NAME = N'Test_log', FILENAME = N'C:\temp\DATA\Test.ldf' )
GO

Any SQL guys reading this is either going "d'uh! obvious", or "d'uh" moron, didn't you know you could do XXX". In the first case, this entry is for users new to SQLExpress that may only be using it for simple work, in the second case please correct me :)

Posted by kevin at 9:57 AM in Net

DRM and Rootkits

As my friends would tell you I really hate DRM. I like to buy CDs and I also think that the CDs I buy belong to me, I'm not licensing the content, I've paid and I own it. I like to rip the CDs to my PC at home and then onto my IPod. I don't give copies of the CDs to my friends, I don't use Kazaa or other file sharing software, and I don't sell bootlegs on street corners. I like to think I am an honest person and from this point of view DRM hurts me. DRM also has the bizarre side effect of making honest people dis-honest.

For example, myelf and my family are fans of Lost, but, being in the UK, we can't follow series 2. I bought a copy of the series 1 DVD in the US when I was there in September, and we watched the DVDs inside a week a so. My kids wanted to watch Series 2, so I thought I'd be go get the episodes on-line. I go to the Apple music store and try and download the episodes, turns out I need a US billing address to get the episodes, as I'm in the UK they're not available to me.

I have lots of friends in the US and I ask one of them to download the first episode for me, which they do, so I grab the episode and of course it's DRM'd and I can't unlock it on my PC. This is an episode that I've paid for and only my family will watch. At the moment nothing is available to remove this DRM so I have to either wait until Lost Series 2 becomes available in the UK, or I have to get an illegal copy of Lost using BitTorrent or something similar!

Which brings me to the real point of the post. Sony have released a DRM'd CD which installs a root kit on your PC when you install the software on the CD. This was brought to the world's attention by Mark Russinovich here, and has been discussed in many places. I just wanted to point at this excellent Inquirer article which starts

SONY SCREWED UP WITH its rights removal to protect its profit margins philosophy and there is no way the use of rootkits can be justified.

Caught with its pants down, what did it do? Make things right? Heck no, it blamed the user, and doesn't do anything more than window dressing to deflect what are valid criticisms.

Being an optimist by nature I hope that the furore surrounding this will bring companies like Sony to their senses, but realistically I doubt it!

Posted by kevin at 7:45 AM in General

« November »
SunMonTueWedThuFriSat
  12345
6789101112
13141516171819
20212223242526
27282930