The benefits of Stored Procedures

There are a number of ways to access data in SQL Server, or any enterprise DBMS. There are lots of books that discuss getting data in and out of databases and the best ways to do that. Many advocate the use of stored procedures to ensure the safety of the data.

The tree main benefits that I see on stored procedures are:

  • Abstraction
  • Security
  • Performance

Stored Procedures add an extra layer of abstraction in to the design of a software system. This means that, so long as the interface on the stored procedure stays the same, then the underlying table structure can change with no noticable consequence to the application that is using the database.

For instance, if the database has to be denormalised to get a little extra performance in certain situations then the stored procedures can handle the additional updates and inserts necessary to ensure the integrity of the data across the tables. Without this the each of the callers would have ensure that these changes had taken place. Of course, the use of stored procedures does not in anyway grant a waiver from properly designing the data model, but it can help if the perfect normalised model has to give way for performance improvements.

This layer of abstraction also helps put up an extra barrier to would be intruders. If access to the data in SQL Server is only ever permitted via stored procedures then permission does not need to be explicitly set on any of the tables. Therefore none of the tables should ever need to be exposed directly to outside applications. For an outside application to modify the database, it must go through stored procedures.

Stored procedures can be written to validate any input that is sent to them to ensure the integrity of the data beyond the simple constraints otherwise available on the tables. Parameters can be checked for valid ranges. Information can be cross checked with data in other tables.

Even if it is thought that someone attempting to crack into a website will never get this far in, from a security perspective, anything that can reduce the attack surface is beneficial. 

Performance can be improved by the use of stored procedures. They are precompiled so when they are run there is no additional lag as the SQL is parsed, compiled, execution plans drawn up and then run, they just run because all that extra work is done at the time the CREATE PROCEDURE or ALTER PROCEDURE commands are run rather than when procedures themselves are run.

Another area in which stored procedures improve performance is that is pushes all the work onto the server in one go. A stored procedure can perform a series of queries and return many tables in, what is to the outside world, one operation. This saves the calling process from making many requests and the additional time of several network roundtrips. It also means that, if the contents of one set of data being returned is dependent on the results of a previous set of data that is being retrieved through the same stored procedure, that the data only has to flow from the database server to the application. If stored procedures were not being used it would mean that the data from the first database call has to get sent back to the database for the second call in order for it to continue retrieving the information needed by the application.

For instance. Lets say that Northwind traders send out a quarterly statement to its customers, and that for each statement certain information needs to be extracted from the database. The tables Customer, Order and Order Details are used. This information could be retrieved in several steps by calling the database for each set of information as it is needed to generate the statements. First with a SELECT * FROM Customers WHERE CustomerID = @CustomerID. This gets the details for the head of the statement. Then a SELECT * FROM Orders WHERE CustomerID = @CustomerID AND OrderDate>=@StartDate AND OrderDate<=@EndDate to get the details for each individual order by that customer. And finally a series of calls (one for each of the Order records that were retrieved) like SELECT * FROM [Order Details] WHERE OrderID = @OrderID

Assuming that the customer in question is “Rattlesnake Canyon Grocery” and the period for the statement is Q1 1998 then that is 5 roundtrips to the database and 5 times the database has to parse some SQL. This could be done by a single stored procedure that takes only one trip to the database and is precompiled.

 

CREATE PROCEDURE GetQuarterlyStatement
@CustomerID nvarchar(5),
@StartDate datetime,
@EndDate datetime
AS
SELECT * FROM Customers
    WHERE CustomerID=@CustomerID
SELECT * FROM Orders
    WHERE CustomerID=@CustomerID
    AND OrderDate>=@StartDate
    AND OrderDate<=@EndDate
    ORDER BY OrderDate DESC
SELECT [Order Details].* FROM [Order Details]
    INNER JOIN Orders ON [Order Details].OrderID = Orders.OrderID
    WHERE CustomerID=@CustomerID
    AND OrderDate>=@StartDate
    AND OrderDate<=@EndDate
    ORDER BY OrderDate DESC
GO

 

The stored procedure is now doing in one trip what previously took 5 trips. Of course, this example is somewhat contrived for brevity, in a real application there would be joins to the product tables, and the columns would be listed rather than using SELECT * and so on.

NOTE: This was rescued from the Wayback Machine. The original date was Friday, 1st October, 2004.

Tags:


Original comments:

A drawback using stored procedures is portability. For each targetted DBMS, you may have to rewrite SP code. If a client side abstraction such as ODBC or ADO is used, one can keep a common code base, making CM and Release Engineering activities easier. Of course it comes with a price – performance.

10/12/2004 12:23 AM | Jörgen

 

Jörgen, While that is a potential drawback, in my experience when moving from one DBMS to another the client side code has to change anyway regardless of client side abstraction. However, with a well structured DAL, the impact can be kept to a minumum.

10/12/2004 12:28 AM | Colin Angus Mackay

SQL Injection Attacks

Every day I see messages on various forums asking for help with SQL. Nothing wrong with that. People want to understand how something works, or have a partial understanding but something is keeping them from completing their task. However, I frequently also see messages that have SQL statements being built in C# or VB.NET that are extremely susceptible to injection attack. Sometimes it is from the original poster and, while they really need to learn to defend their systems, that is fine as they are trying to learn. Nevertheless there is also a proportion of people responding to these questions that give advice that opens up gaping security holes in the original poster’s system, if they follow that advice.

Consider this following example:

C#

static DataSet GetCustomersFromCountry(string countryName)
{
    SqlConnection conn = new SqlConnection("Persist Security Info=False;"+
        "Integrated Security=SSPI;database=northwind;server=(local)");
    string commandText = string.Format("SELECT * FROM Customers WHERE Country='{0}'",
        countryName);
    SqlCommand cmd = new SqlCommand(commandText, conn);
    SqlDataAdapter da = new SqlDataAdapter(cmd);
    DataSet ds = new DataSet();
    da.Fill(ds);
    return ds;
}

VB.NET

Function GetCustomersFromCountry(ByVal countryName As String) As DataSet
    Dim conn As SqlConnection = New SqlConnection("Persist Security Info=False;" + _
        "Integrated Security=SSPI;database=northwind;server=(local)")
    Dim commandText As String = String.Format( _
        "SELECT * FROM Customers WHERE Country='{0}'", _
        countryName)
    Dim cmd As SqlCommand = New SqlCommand(commandText, conn)
    Dim da As SqlDataAdapter = New SqlDataAdapter(cmd)
    GetCustomersFromCountry = New DataSet
    da.Fill(GetCustomersFromCountry)
End Function

What happens here is that what ever the value of countryName is will be inserted (injected, if you prefer) directly into the SQL string. More often than not I see examples of code on forums where there has been absolutely no checking done and the developer has used countryNameTextBox.Text directly in the string format or concatenation statement. In these cases just imagine what the effect of various unrestricted text box entries might be.

For instance, imagine the values a malicious user might put in the text box on a web form. What if they type ';DROP TABLE Customers;-- ?

That would expand the full SQL Statement passed by the .NET application to be

SELECT * FROM Customers WHERE Country='';DROP TABLE Customers; -- '

So, no more customers (at least in the database… But how long in real life?)

Some people might then say, sure, but who in their right mind would give that kind of access on a SQL Server to the ASP.NET account? If you ask that question then you cannot have seen the number of people who post code with the connection strings clearly showing that, firstly, they are using the sa account for their web application and, secondly, by posting their problem to a forum they have given to the world the password of their sa account.

Some others might say, yes yes yes, but wouldn’t an attacker would have to know what the overall SQL statement is before they can successfully inject something? Not so, I say. If you look at code posted on forums it becomes obvious that the vast majority of values from textboxes are inserted right after an opening apostrophe, like the example above. Based on that assumption, all an attacker needs to do is close the apostrophe, add a semi-colon and then inject the code they want. Finally, just to make sure that any remaining SQL from the original statement is ignored they add a couple of dashes (comment markers in SQL)

These defenders-of-bad-SQL-because-you-can-never-completely-secure-your-system-anyway-so-why-bother will often follow up with, okay okaay! But the attacker would have to know the structure of the database as well! Well, maybe not. Normally there are common table names. I’m sure most people that have been dealing with databases for a few years will have come across many with tables with the same names. Customers, Users, Contacts, Orders, Suppliers are common business table names. If that doesn’t work it may be possible to inject an attack on sysobjects. Often an attacker just gets lucky or notices a quirky output when entering something unusual and uses that to work on cracking the web site or database.

So here I present three tips for improving the security of your SQL Server database. In no particular order, they are: Use parameterised queries. Login using an appropriate account and grant only the permissions necessary. Use stored procedures.

* Using parameterised queries is really very simple, and it can make your code easier to read, and therefore to maintain. Parameters also have other advantages too (for instance you can receive values back from parameters, not just use them for sending information into the query). The previous code example can be changed very easily to use parameters. For instance:

C#

static DataSet GetCustomersFromCountry(string countryName)
{
    SqlConnection conn = new SqlConnection("Persist Security Info=False;"+
        "Integrated Security=SSPI;database=northwind;server=(local)");
    string commandText = "SELECT * FROM Customers WHERE Country=@CountryName";
    SqlCommand cmd = new SqlCommand(commandText, conn);
    cmd.Parameters.Add("@CountryName",countryName);
    SqlDataAdapter da = new SqlDataAdapter(cmd);
    DataSet ds = new DataSet();
    da.Fill(ds);
    return ds;
}

VB.NET

Function GetCustomersFromCountry(ByVal countryName As String) As DataSet
    Dim conn As SqlConnection = New SqlConnection("Persist Security Info=False;" + _
        "Integrated Security=SSPI;database=northwind;server=(local)")
    Dim commandText As String = "SELECT * FROM Customers WHERE Country=@CountryName"
    Dim cmd As SqlCommand = New SqlCommand(commandText, conn)
    cmd.Parameters.Add("@CountryName", countryName)
    Dim da As SqlDataAdapter = New SqlDataAdapter(cmd)
    GetCustomersFromCountry = New DataSet
    da.Fill(GetCustomersFromCountry)
End Function

* The application should be set up to use a specific account when accessing the SQL Server. That account should then be given access to only the things it needs. For instance:

GRANT SELECT ON Customers TO AspNetAccount

It is generally unwise to GRANT permission ON someObject TO PUBLIC because then everyone has the permission.

* My final tip is to use only stored procedures for selecting and modifying data, because then the code that accesses the tables is controlled on SQL server. You then do not need to grant access directly to the tables, only the stored procedures that are called. The extra protection then comes by virtue of the fact that the only operations that can be performed are those that the stored procedures allow. They can perform additional checks and ensure that relevant related tables are correctly updated.

NOTE: This was rescued from the Wayback Machine. The original post was dated Saturday, 25th September 2004.

Tags:


Original posts:

Excellent post Colin. I’d always wondered what a SQL Injection attack was without actually bothering to Google for it – and now I know.

9/26/2004 6:08 AM | Rob

this is great job, colin. i’ve just know the words ‘SQl injection’ but dont know what it exactly means. now i’ve got it. i can defend my own database.

thanks

10/3/2004 3:41 PM | Fired Dragon

I’ve seen people who’ve read this article thinking they can’t do it because you’ve only given .NET syntax. You should probably emphasize that ‘classic’ ADO also has Command objects with a Parameters collection, as does the more obscure underlying OLE DB object model. You can also use parameters with ODBC. There’s no excuse – parameters are cleaner, more secure, and less prone to error than building a SQL string. The slight disadvantage is that the code becomes a little less portable – SQL Server uses the @param notation, Oracle uses :param, and the OleDbCommand and OdbcCommand both use positional parameters marked with a ?

12/18/2004 1:01 AM | Mike Dimmick

I just can’t get this to work. I do exactly as the example shows, and the query doesn’t replace the parameters with the values to search for. The query came up with nothing, until I entered a row in the database with the name of the parameter as a field. It finds that, so it just doesn’t parse the command. I don’t get it.

7/27/2005 10:46 AM | Vesa Ahola

Since I cannot see your code I cannot see what is going wrong. However, I wrote a longer article about the subject over on codeproject.com. Perhaps that may help. See:

http://www.codeproject.com/cs/database/SqlInjectionAttacks.asp

7/27/2005 4:28 PM | Colin Angus Mackay

UPDATE: Sql Injection Attacks

As a follow up to my post on preventing SQL Injection Attacks a couple of months ago I just found this little nugget, I Made a Difference[^], and it shows what can be achieved if you don’t secure against SQL Injection attacks – and with only 3 hours of effort. Obviously, if you have access to the source code you will be able to launch an attack much quicker.

The original link seems to have disappeared. See the Wayback Machine for an archived copy, or the quoted section below:

Two weeks ago, I taught a Guerilla .NET course for DevelopMentor in Boston. Two or three days ago, a student who listened to me rant about SQL Injection attacks during the Code Access Security module lecture sent us (myself and the other two instructors) the following. It’s obviously been edited to protect the guilty:

“Hi, Ted. I want to thank you for the short primer on SQL injection attacks at the Guerrilla course in Woburn this month. We have a vendor who supplies us with electronic billing and payment services. (We send them billing data, and they present the bills to our customers and take the payments for us.) The week after the Guerrilla class I began to lose confidence in their application for various reasons, like seeing errors that included partial SQL statements, and in one case, a complete SQL statement that was accidentally left on a page from a debugging session. I told our company’s business manager that I was 80% confident that I could hack into their site using SQL injection. He called the vendor, who swore up and down that after spending $83,000 on firewalls that no one could ever hack into their site, and that we should go ahead and try.

“After three hours and a Google search on SQL injection, I was running successful queries from their login page and I had their server emailing us the query results via xp_sendmail. I was also able to confirm that the SQL Server login they use for their application has sa rights. I got a list of their clients, and was able to create tables in their databases.

“The vendor promised that by the next morning everything would be fixed. So the next morning at 8:00 am I tried again. I was no longer able to get results via xp_sendmail, but I was able to shutdown their SQL Server service by sending a shutdown command. I followed that up with a friendly call to their tech support line to let them know that they needed to restart SQL Server–I didn’t want to be too malicious. The guy at the other end of the line apparently had been there the entire night changing code and rolling out pages. He threatened to get on a plane, come to my office, and beat me up.”

“The disturbing thing about the incident is that there is enough data in the vendor’s database to allow someone to commit identity fraud or steal credit card and bank account numbers. And they are not a mom and pop shop either–their client list includes F—-, D—-, D—-, and V—-. [These are names that you would recognize, dear reader.] If I had been malicious I could have stolen data from any of those companies.”

Hunh. Three hours and a Google search was all it took. Anybody still think firewalls are the answer? “Security is a process, not a product.” — Bruce Schneier, Secrets and Lies.

NOTE: This was rescued from the Google Cache. The original date was Wednesday 17th November, 2004.

Tags:

Please please please learn about SQL Injection Attacks

Here are two more great blog entries about preventing SQL Injection Attacks

NOTE: This was rescued from the Wayback Machine. The original date was Tuesday, 30th November 2004.

Tags:


Original comments:

I think one of the problems is that there is too many source code archives and books that called their code “best practices” that are targets for SQL Injection. We will probably still see the code used for years to come.

11/30/2004 8:03 AM | Rocky Moore

Protecting Tables from SQL Injection Attack

A recent question in a forum that I view asked about how to ensure that even if one layer of security was compromised that the table would only ever return one row at a time so that an attacker would have to do more work to get a list of the users and passwords out of the database.

The way I see it, the best solution is not just to set up constraints, assuming that your database can add a constraint to only ever return the first row in a query, but to protect the table by not granting access to it directly. Then set up stored procedures to perform all the operations that you permit on the table. That way, if an attacker should get through the “outer defences” they cannot access the tables directly, and must use the stored procedures.

For example, say you have a database that has the user details for a website, this includes the user name and password. You don’t want an attacker to get a list of passwords or even one password. So you design the stored procedures so that you can pass a password in, but it will never put a password in a result set. The stored procedures for registering and authenticating a user for the website might be:

  • RegisterUser
  • VerifyCredentials
  • ChangePassword

RegisterUser takes the user name and password as parameters (possibly along with other information that would be useful for your website) and returns the UserID

VerifyCredentials would be used for logging into the site by accepting the user name and the password. If there was a match the UserID is returned, if not then a NULL value.

ChangePassword would take the UserID, the old password and the new password. If the userID and password match the password can be changed. A value that indicates success or failure is returned.

As you can see that the password is always contained in the database and is never exposed. The stored procedure could potentially generate a salted hash of the original password too so that should some layer of the database security be compromised that the password is still not readable.

You must also be careful when you call the stored procedure and ensure that you use parameterised queries. SQL Injection attacks are also possible when calling stored procedures if they are called by building up a SQL statement dynamically and executing it.

NOTE: This was rescued from the Google cache. The original date was Thursday, 6th January, 2005.

Tags:


Original comments:

Salted hash is a pretty tricky thing to do in SQL Server 2000, but should be very simple in SQL 2005 with a CLR function that wraps System.Security.Cryptography. For the moment you should probably hash the password on the client, i.e. in the web application code.

The downside, of course, to salted hashes is that you can’t ever tell the user what their password was. You have to have a facility to allow them to reset their password by supplying some other information instead (the ‘password reset question’ technique).

1/10/2005 12:04 AM | Mike Dimmick

Friendly Error Messages (or not)

Microsoft are normally quite good at producing friendly error messages when things don’t work out. However today I rebooted my machine after installing security updates, I fired up Visual Studio and then attempted to open the solution I was working on. Visual Studio then complained that IIS wasn’t running ASP.NET 1.1. So I went to IIS to check that it hadn’t reset my default website to ASP.NET 2.0, but it had. I changed it over to ASP.NET 1.1 and attempted to open my solution again. Same error message.

Curious I went back to the IIS admin tool and expanded the tree further to see if the Virtual Directory needed changing too. However, I then saw that IIS was stopped, so I attempted to restart it. Nope. Nada. It reported “Unexpected error 0x8ffe2740”. What the heck is error 0x8ffe2740?!

A quick Google found me a forum that discussed this and told me that it was because something else was listening on port 80, the default HTTP port. So, why didn’t the error message tell me this. Why the cryptic hex value?

Anyway, once I knew something else was using the port, I needed to find out what. I have a very useful piece of freeware called TCPView from Sysinternals and it is quite interesting to see all the processes with an open network connection. I quickly found the offending application (Skype, if you are curious) and closed it down.

NOTE: This was rescued from the Google Cahe. The original date was Monday, 17th January 2005.

Tags:


Original comments:

If you want to keep using Skype, go to File > Options > Connection tab and uncheck ‘Use port 80 as an alternative for incoming connections’.

1/18/2005 4:16 PM | Mike Dimmick

Tenets of Transparency

Eric Sink, Software Craftsman (and I really love that title) for SourceGear has written an article for MSDN about how ISVs can increase transparency and improve trust in their customers.

I especially like his comments on product licence enforcement as I used to work with a piece of enterprise software that had to be re-licenced each year. Usually a couple of weeks before the old licence would expire we’d get a fax with the new licence keys. The first time I came across this I was on my sandwich year at university so I got the great job of entering this information into server to re-licence the two-dozen or so components that were in use. Then I got to use one of the pool cars and go round to all the remote offices and repeat the process (with different keys – they were tied to IP addresses). This process took ages because often it would be difficult from the fax output to tell the difference between a 6 and a G, or 1 and I and so on.

A few years later I came in contact with this system again, but at least technology had moved on and the licence keys were sent by email. They still had to be entered by hand though.

Their next product release allowed a text file to be read in and the process was almost completely automated – Receive email, download attachment, open licence manager, open licence file, wait 5 minutes for it to go through the file and register the licences.

However it was still prone to problems. The licence manager code freaked out each October for one hour when the clocks changed. Some users of this system used it in emergency situations where an hour of downtime was unacceptable. Eventually the licence manager was changed to work on UTC times rather than local time. But a one hour downtime could have made the difference between life-and-death in certain situations. A power utility not being able to provide an emergency service to its affected customers, a water company not knowing quickly enough who is downstream of a contaminated supply and so on.

To read the article go to the MSDN website to read Tenets of Transparency

NOTE: This was rescued from the Google Cache. The original date was Saturday 5th February 2005.

Lean Software Development: An Agile Toolkit

Lean Software Development: An Agile Toolkit
by Mary Poppendieck and Tom Poppendieck
with Forewords by Jim Highsmith and Ken Schwaber

A review of sorts

The book defines a number of tools to assist you in implementing an agile approach to software development and uses a number of studies from both software development and manufacturing to get the points across.

On the whole the book is fairly easy to read and presents the ideas clearly with examples. However, in some areas I felt that the examples were too geared towards the manufacturing industry and not towards software development. This is, in itself, somewhat surprising as the authors have pointed out that you shouldn’t try and directly take a idea from one industry and apply it to software development without some element of thought. From the final page of the book comes a warranty that contains: “This warranty is invalid if practices are transferred directly from other discipline or domains without thinking, or if the principles of empower the team and build integrity in are ignored.” (p186)

The warranty quote above highlights two of the three areas that I feel are the most important. The other area was the “Contracts” tool. The remainder of this text are the things that I found most interesting, or provided one of those “a-ha!” moments as a realisation of how a new idea might make things work better occurred. Some of the things may seem obvious, but a gentle reminder of them is never bad.

Empowering the team means that everyone has a clearer idea of what is going on and, most importantly, why. It means that the team have access to the information to do their job rather than be hidden away from the client and given a point-by-point specification to work through. While it is easy to work through a point-by-point specification it does not allow the developer to highlight potential errors or conflicts if they don’t understand why the specification says what it does. There may be some missing information or an assumption may be wrong, or common sense doesn’t turn out to be as common as once thought.

Another point raised by the chapter on empowering the team was that “software development cannot be successful without disciplined, motivated people”. Motivation is not only the key to keeping good employees, but also for keeping them energised. The authors give the example of 3M who, for over 75 years, have allowed the “entrepreneurial spirit to flourish” by permitting “small, self-organising groups that become passionate about a possibility and are allowed to make it a reality… Scientists are expected to spend 15% of their time on projects of their own choosing” (p104). Many of 3M’s new product lines comes from this. In the software development world the equivalent is Google, who permit their developers to experiment with new ideas which Google then bring to the market place. This is very successful. A current example is Google Suggestion.

Going hand-in-hand with motivation is purpose. People need to have a purpose, a set of goals to work towards. This manifests itself in understanding why the software is needed. If this sense of purpose is to prevail then beware of the sceptics. “Nothing kills a purpose faster than someone who knows it can’t be done and has plenty of good reasons why” (p106)

Building integrity in ensures that the customer stays happy (perceived integrity) because the software appears to work as expected without errors or problems and it also ensures that the developers stay happy (conceptual integrity) that the code base stays well ordered and easy to maintain.

For a software application to have perceived integrity (p127-134) it needs to be usable, reliable, economical, and it must function as expected. In terms of developing an application in this way domain models are built, which are models of the software that the customer understands. The authors go on to say that eventually the models created will fall into disuse, and that they should not be maintained just because it “seems like a good idea”

In terms of conceptual integrity (p135-149), the “system’s central concepts work together as a smooth, cohesive whole.” Specifically “particular attention should be paid to the presentation layer, since conceptual integrity in the interface design is a primary driver of perceived integrity”. In order to maintain the conceptual integrity the system must be simple (many software patterns aim to bring simplicity to complex designs), clear (so that everyone in the software development team can understand), suitable (so that it fits its purpose), D.R.Y (Don’t Repeat Yourself – removing duplication means that if something has to be changed it only needs changed in one place), with no extra features (“Anticipating the future is usually futile and consumes resources”)

Finally there is the Contracts section (p161-177). For me, this was the key to the whole book. For many areas in the book I was left wondering how this would ever work in an organisation that exists to service clients. For many of the concepts I could see how they could gain a hold inside a software product company, but not a software services company. They gave examples of the traditional time-and-materials and fixed-price contracts and how they don’t really work well. Then they presented three examples of contracts that would fit into the Agile development process.

NOTE: This was rescued from the Googe Cache. The original date was Saturday, 11th December, 2004.

Tags:

What I've been up to

I’ve not posted anything for a while so I thought I’d show of some of my better photos that I have uploaded to Flickr (just to show you what I’ve been up to). These are my ten favourite photos that I’ve uploaded. These are in no particular order:

Cannon
This is a cannon on Calton Hill that points towards Edinburgh Castle.

There wasn’t a plaque or anything to explain it so I am assuming that it might have something to do with the one O’clock time signal. On Calton Hill there is the time ball that drops from the top of Neilson’s Monument so that ships in the Firth of Forth can see see it. At Edinburgh Castle a cannon is fired at one O’clock.


Edinburgh's Folly
Rear view of Edinburgh’s Folly, also known as “Edinburgh’s Disgrace”.

On the top of Calton hill is this is a 19th Century replica of the Parthaenon. Well, more accurately it is an unfinished replica – the money ran out before they could finish building it.


Industry at Grangemouth
Looking west over the Firth of Forth towards Grangemouth.


Helmsdale, Scotland
Helmsdale, Scotland.


Snow Dusted Edinburgh
Snow Dusted Edinburgh

Princes Street Gardens, Edinburgh Castle, National Gallery of Scotland


Edinburgh Castle
Edinburgh Castle at the start of Twilight


The Two Bridges
Looking west to the two bridges over the Firth of Forth


Sunrise Silhouette
Sunrise over Edinburgh’s Old Town


Forth Bridge (1)
Taken from the promenade at South Queensferry looking NE. From Wikipedia: The Forth Bridge is a railway bridge over the Firth of Forth in the east of Scotland, to the east of the Forth Road Bridge, and 14km (9 miles) west of Edinburgh.

The bridge is, even today, regarded as an engineering marvel. It is 2.5km (1.5 miles) in length, and the track is elevated 46m (approx. 150 feet) above high tide. It consists of two main spans of 1,710 feet, two side spans of 675 feet, fifteen approach spans of 168 feet and five of 25 feet. The main spans comprise two 680 feet cantilever arms supporting a central 350 feet span girder bridges. The three great four-tower cantilever structures are 340 feet (104m) tall, each 70 feet diameter foot resting on separate foundations.


Declaration of Independence
The quote is from the Declaration of Arbroath, commonly regarded as Scotland’s Declaration of Independence. Sir James Fergusson, formerly Keeper of the Records of Scotland, produced this translation to English.

The photo is of what is displayed on the wall at the Museum of Scotland: “As long as only one hundred of us remain alive we will never on any conditions be brought under English rule.” The original quote continues: “It is in truth not for glory, nor riches, nor honours that we are fighting, but for freedom — for that alone, which no honest man gives up but with life itself.

NOTE: This post was rescued from the Google Cache. The original date was Sunday, 13th March, 2005

Tags:

Things I keep forgetting about FileInfo

This is going to sound like a real newbie post. But I keep forgetting this particular bit of information and I keep having to write little throw away applications to find out the answer. FileInfo has a number of properties and the MSDN description on them are almost next to useless.

Given the file C:\folder\file.ext

So there we have it. The things that I keep forgetting about FileInfo that MSDN just does not explain (except the Extension property, it at least does explain that one)

NOTE: This was rescued from the Google Cache. The original was dated Monday, 28th March 2005.