Monday
Dec072009

« Tips and Tricks with Visual Studio »

Setup and deployment projects were a welcome addition to the Visual Studio project templates. They have allowed many a developer an easy way to quickly produce installers for their applications. However, as with most of Microsoft's offerings, you only get so far out of the box and most real-world use cases require one to delve a little further into the workings. So, let us delve!

Having recently created an installer for a web application using the Setup and Deployment project template and after spending much time on Google searching for ways to do certain things, I thought it might be helpful to collect some of my findings together so that others may benefit (and so I have somewhere central to find these answers again next time!).

There are two main areas with respect with respect to deployment projects that I would like to focus on - the use of Custom Actions and Bootstrapping. For an introduction to and basic information regarding Setup and Deployment projects, you can take a look here and here.

1. Custom Actions

Briefly, custom actions allow you to hook into the installation pipeline of the Microsoft Installer. They allow developers to add customized logic (such as creating a database) as part of the installation process and it is through them that you will enhance your installer. Through custom actions, you can add custom logic to any or all of the following phases of installation:
  • Install
  • Commit
  • Rollback
  • Uninstall
Each phase corresponds to a public method you can override in your custom actions class and insert required logic. See this for information on creating custom actions and adding to your Setup and Deployment project.

Now that we understand that custom actions are the means by which we can extend Setup and Deployment projects and add additional actions to be performed during installation, let's take a look at some of the basic tasks I have needed to do within an installer that were enabled through custom actions:

  1. Creating a database
  2. Importing data
  3. Adding a desktop shortcut
  4. Updating web.config
  5. Adding ASP.NET services
  6. Handling errors
  7. Debugging your custom actions

Creating a Database

While creating a SQL Server database does not require large amounts of math, here I mainly wanted to highlight the awesomeness of the SqlConnectionStringBuilder class. The existence of www.connectionstrings.com proves that I'm not the only one who draws a blank sometimes on proper connection string format! The SqlConnectionStringBuilder class builds it for you (surprise!) which can be very helpful (especially for SQL Express databases). Note the creation of a Trusted connection (connStrBuilder.IntegratedSecurity = true;)

private void CreateDB(string dbServer, string dbName)
{
	SqlConnectionStringBuilder connStrBuilder = new SqlConnectionStringBuilder();
	connStrBuilder.InitialCatalog = "master";
	connStrBuilder.DataSource = dbServer;
	connStrBuilder.ConnectTimeout = 5;
	connStrBuilder.IntegratedSecurity = true;
	string sql = string.Format("CREATE DATABASE {0}", dbName);
	SqlCommand cmd = new SqlCommand(sql, conn);
	//Init connection, open and set db
	conn.ConnectionString = connStrBuilder.ConnectionString;
	cmd.Connection.Open();
	try
	{
		cmd.ExecuteNonQuery();
	}
	catch (Exception ex)
	{
		throw ex;
	}
	finally
	{
		cmd.Connection.Close();
	}
}
                                         

You would call the CreateDB from the Install method as follows:

public override void Install(IDictionary stateSaver)
{
	serverName = this.Context.Parameters["dbServer"];
	databaseName = this.Context.Parameters["dbName"];
	CreateDB(serverName, databaseName);
}
										  

You'll note the references to Context.Parameters - the Parameters collection is how information collected by the installer is made available to you in your custom actions. In this example, I had added a custom dialog box to the installer with two textboxes for gathering the names of the database server and database to create. On the Custom Actions view of my Setup and Deployment project, I have set the CustomActionData property to indicate that I want the value from textbox CUSTOMTEXTA1 to be available as dbServer and the value from textbox CUSTOMTEXTA2 to be available as dbName. I have also set the selected target virtual directory name as TGTVDIR. These values are then made available to me in Context.Parameters in my custom action.

Custom_action_primary_output
    /dbServer=[CUSTOMTEXTA1] /dbName=[CUSTOMTEXTA2] /TGTVDIR=[TARGETVDIR]
                                              

Importing Data

Any developer who has experience with MySQL, specifically with backing up (bless you, mysqldump) and restoring a database, simply cannot understand why the task is not as smooth with SQL Server. One will quite often wish to create a database and fill it with data as part of the installation process, which with MySQL is dead simple.

As a solution, Microsoft now has their Database Publishing Wizard, which makes things much easier - here at last is an easy way to script a database. However, I ran into an issue in using (finding) the tool and in my search engine travels I noticed I wasn't the only one so I'll call out the obvious now. The original Database Publishing Wizard was introduced as an easily found and downloadable add-on to SQL Server 2005. Since the introduction of SQL Server 2008, the downloadable version was apparently done away with and the functionality is now an included feature. Thus you will find many Microsoft blogs/articles etc. insisting that Database Publishing Wizard should show up as a context menu item in both Visual Studio 2008 and within SQL Management Studio when you right-click on a database node - it does not. At least not for myself and a host of other developers with access to google (see here for an example). However, right-clicking on a database node and selecting Tasks->Generate Scripts will give you the payoff you're looking for. I never did find out why the discrepancy and I lost interest after I found the indicated solution.

My main reason for wanting to talk about importing data is due to an issue I encountered with running my database install script. It seems to be specific to SQL Server 2008, but I am not 100% certain of that. The problem I encountered is that SQL Server would run out of memory while running my import script! The exact error I was getting is "There is insufficient system memory in resource pool 'internal' to run this query". I was trying to execute the script in one go using the ExecuteNonQuery() method of SqlCommand class and even though it wasn't a particularly long script, I kept encountering the problem (there appears to be some known issue with SQL Server in this regard - see this article for more information). Suffice it to say that my interest lay (and lies) not in tracking down or even understanding SQL Server issues but in writing code so I needed a solution that didn't involve me tinkering with or reading any more about SQL Server 2008. The long and short of it is that in order to run the data import script, I had to break my script into individual commands and run them one by one (oh, and restarting the SQL Server service is also helpful). The code snippet below shows this process:

private void ExecuteSql(string dbServer, string dbName, string sql)
{
	SqlConnectionStringBuilder connStrBuilder = new SqlConnectionStringBuilder();
	connStrBuilder.UserID = "MyDBUserId";
	connStrBuilder.Password = "MyDBUserPassword";
	connStrBuilder.InitialCatalog = "master";
	connStrBuilder.DataSource = dbServer;
	connStrBuilder.ConnectTimeout = 5;
	SqlCommand cmd = new SqlCommand(sql, conn);
	//Init connection, open and set db
	conn.ConnectionString = connStrBuilder.ConnectionString;
	cmd.Connection.Open();
	cmd.Connection.ChangeDatabase(dbName);
	try
	{
		string[] commands = sql.Split(new string[] { "GO\r\n", "GO", "GO\t" }, StringSplitOptions.RemoveEmptyEntries);
		foreach (string c in commands)
		{
			cmd.CommandText = c;
			cmd.ExecuteNonQuery();
		}
	}
	catch (Exception ex)
	{
		throw ex;
	}
	finally
	{
		cmd.Connection.Close();
	}
}
                                         

Hope this is helpful to someone.

Adding a Desktop Shortcut

For desktop applications, creating a shortcut using the Setup and Deployment project template is quite simple. However, to create a shortcut to a web application is a little more involved (or can be). For a recently developed web application, I created an installer that allowed the user to set the web server and the virtual directory into which to install the application. I then needed to dynamically create a shortcut to the application using the user-set values. I accomplished this through a custom action as shown below.

private void CreateShortcut()
{
	string vdirPath = this.Context.Parameters["TGTVDIR"];
	System.Configuration.Configuration config = WebConfigurationManager.OpenWebConfiguration(string.Format(@"/{0}", vdirPath));
	string path = config.FilePath.Replace("web.config","");
	string shortcutFileName = string.Format(@"{0}\MyWebApplication.lnk", Environment.GetFolderPath(Environment.SpecialFolder.DesktopDirectory));
                     
	WshShellClass shell = new WshShellClass();
	IWshRuntimeLibrary.IWshShortcut shortcut = (IWshRuntimeLibrary.IWshShortcut)shell.CreateShortcut(shortcutFileName);
	shortcut.TargetPath = string.Format("http://localhost/{0}", vdirPath);
	shortcut.Description = "Launch MyWebApplication";
	shortcut.IconLocation = string.Format(@"{0}MyWebApplication.ico",path);
                       
	shortcut.Save();
}
                                             

NOTE: In order to use the WshShellClass class, you will need to add a reference to the Windows Script Host Object Model COM object.

Scripthost

Updating web.config

Sometime you want to update the web.config your application ships with to reflect the user's environment without having the users edit the file themselves. For example, after asking the user for the name of the database server/database and creating the database, you will want to then update the web.config with new connection string information. How/where should this be done? You guessed it! In a custom action:

private void UpdateWebConfig(string connectionString)
{
	//virtual directory passed in from installer in CustomActionData
	string vdirPath = this.Context.Parameters["TGTVDIR"];
	try
	{
		//open the web.config
		System.Configuration.Configuration config = WebConfigurationManager.OpenWebConfiguration(string.Format(@"/{0}", vdirPath));
		//change the connectionString for the DB
		config.ConnectionStrings.ConnectionStrings["MyAppsConnectionStrings"].ConnectionString = connectionString;
		config.Save();
	}
	catch (Exception ex)
	{
		throw ex;
	}
}
                                          

Adding ASP.NET Services

Adding ASP.NET services (membership, profiles etc.) to your SQL Server database is normally accomplished using the aspnet_reqsql.exe command line tool, but you can also accomplish this programmatically within your custom action. Instead of trying to execute the actual aspnet_regsql.exe using System.Diagnostics.Process.Start(), there is an easier way built into the .NET framework:

      //Add ASP.NET membership service
   SqlConnectionStringBuilder connStrBuilder = new SqlConnectionStringBuilder();
      connStrBuilder.InitialCatalog = "master";
        connStrBuilder.DataSource = serverName;
    connStrBuilder.IntegratedSecurity = true;
  SqlServices.Install(dbName, SqlFeatures.All, connStrBuilder.ConnectionString);
                                            

Where the SqlFeatures enum has the following values:

  • None
  • Membership
  • Profile
  • RoleManager
  • Personalization
  • SqlWebEventProvider
  • All

Handling Errors

When something unexpected goes wrong during the installation process, the installer automatically rolls back any changes it has made during the incomplete installation. However, this does not extend to any custom actions performed by you. If there are any changes that your custom action code performed that should be undone, you must undo them yourself by hooking into the installer's Rollback phase. To hook into the Rollback phase, you must override and implement the Rollback method in your custom actions.

For example, the following custom action Rollback code deletes the database that was created in the Install method:

public override void Rollback(IDictionary savedState)
{
	base.Rollback(savedState);
	//Remove database if we are rolling back for
	//any reason other than because the selected 
	//DB exists (if it does, we don't want to delete an existing DB -
	//it might have useful data
	if (!((bool)savedState["dbExists"]))
	{
		SqlConnection.ClearAllPools();
		Server server = new Server((string)savedState["serverName"]);
		try
		{
			server.Databases[(string)savedState["dbName"]].Drop();
		}
		catch (Exception ex)
		{
			MessageBox.Show(ex.Message);
		}
	}
}
                                            

Debugging Your Custom Actions

Before we leave our discussion of useful tasks to perform in a Custom Action, you may want to know how you go about debugging your custom actions (remember, your custom action code is executing within the context of Microsoft Installer, so you can't simply press F5 in Visual Studio and start debugging). Like debugging any other external code, you can attach directly to the running installer process using the 'Attach to Process' command from the Visual Studio Debug menu, or (simpler) you can force a debugger break in your custom action code which will invoke the debugger and start debugging from the point of the break. See below:

public override void Install(IDictionary stateSaver)
{
	System.Diagnostics.Debugger.Break();
	//Custom action code...
}
                                         

So there you have it - hopefully the explanation of some of these common installation activities that can be performed using custom actions has been useful for you. Now let's talk a little about installer prerequisites.

2. Prerequisites and Bootstrapping

Most applications developed with Visual Studio will have some sort of dependency upon one component or another and will require that said components are installed on the machine before the application will run/install (e.g. a particular version of the .NET framework, Crystal Reports, Sql Server etc.). Setup and Deployment projects allow you to install other components as part of the installation of your application (a process known as 'bootstrapping'). Visual Studio comes with several components to get you going and, with a little effort you can create your own bootstrap packages for components not included with Visual Studio.

Visual Studio 2008 comes with the following prerequisites:

  • .NET Framework 2.0
  • Visual C++ Runtime Libraries
  • Windows Installer 3.1
  • .NET Framework Client Profile
  • .NET Framework 3.0
  • .NET Framework 3.5
  • Crystal Reports Basic for Visual Studio 2008
  • Microsoft Office 2007 Primary Interop Assemblies
  • Microsoft Visual Studio 2008 Report Viewer
  • Sql Server Compact 3.5
  • Microsoft Visual Basic PowerPacks 1.2
  • Visual Studio Tools for the Office system 3.0 Runtime Service Pack 1
  • SQL Server 2005 Express Edition SP2

All the required information for the various Bootstrapper packages are defined using xml files that Visual Studio consumes to find and understand everything about a package (what files are included, any related products or prerequisites, what to do on install error etc.).

Specifying Prerequisites

To specify prerequisites for your application, select Properties for your Setup and Deployment project and click the "Prerequisites" button.

Show_prerequisites

This will bring up a list of available bootstrapper packages on your machine.

Prerequisites_list

Note the section for specifying install location. If you wish to include the prerequisite binaries as part of your installer instead of the user needing to download them as needed (e.g. think of an installation that must be accomplished without internet access), then you can tell Visual Studio to download them from the same location as your application, which will package the prerequisite with your installer (and potentially bloat the size tremendously - e.g. the .NET Framework 3.5 SP1 package adds 191MB to the installer!).

Custom Prerequisites

It is possible for a developer to create their own bootstrap packages that will be detected by Visual Studio and thereafter added as a prerequisite to a Setup and Deployment project.

The list of boostrapper packages available to Visual Studio is generated based upon available packages in the \Program Files\Microsoft SDKs\Windows\v6.0A\Bootstrapper\Packages\ directory.

Bootstrapper_packages

In order to make new prerequisites available to your Setup and Deployment projects, you must create your own bootstrapper packages and deploy them into this directory. While it is possible to create the requisite xml describing the package by hand, you will make your life so much easier if you make use of a Microsoft tool called the Bootstrapper Manifest Generator (BMG), available here. The BMG tool guides you through various options for your package using an easy GUI and once you have made various selections regarding your package, it will then compile the results for you into xml files (product.xml and package.xml). See the included help file for more information. Oh, by the way make sure you explicitly set the exit code for success - I had a package that kept failing with the message "Installation successful" (very confusing, that!). It turns out it was because relying on the default treatment of system exit codes was not sufficient and I had to explicitly set exit code 0 as a success code.

As an example, here is the product.xml and package.xml code for a bootstrapper package I created for the ASP.NET MVC runtime:

product.xml:
<?xml version="1.0" encoding="utf-8"?>
<Product ProductCode="ASP.NET.MVC" xmlns="http://schemas.microsoft.com/developer/2004/01/bootstrapper">
<RelatedProducts>
	<DependsOnProduct Code="Microsoft.Net.Framework.3.5.SP1" />
	<DependsOnProduct Code="Microsoft.Windows.Installer.3.1" />
</RelatedProducts>
                                          

package.xml:

<?xml version="1.0" encoding="utf-8"?>
<Package Name="DisplayName" Culture="Culture" xmlns="http://schemas.microsoft.com/developer/2004/01/bootstrapper">
	<PackageFiles CopyAllPackageFiles="false">
		<PackageFile Name="aspnetmvc1.msi" HomeSite="http://www.asp.net/mVC/download/" PublicKey="3082010A0282010100BD72B489E71C9F85C774B8605C03363D9CFD997A9A294622B0A78753EDEE463AC75B050B57A8B7CA05CCD34C77477085B3E5CBDF67E7A3FD742793679FD78A034430C6F7C9BAC93A1D0856444F17080DF9B41968AA241CFB055785E9C54E072137A7EBCE2C2FB642CD2105A7D6E6D32857C71B7ACE293607CD9E55CCBBF122EBA823A40D29C2FBD0C35A3E633DC72C490B7B7985F088EF71BD435AE3A3B30DF355FB25E0E220D3E79A5E94A5332D287F571B556A0C3244EF666C6FF0389CEF02AD9AA1DD9807100E3C1869E2794E4614E0B98CD0756D9CAC009C2D42F551B85AF4784583E92E7C2BBB5DCD196128AD94430AC56A42FFB532AEA42922DE16E8D30203010001" />
	</PackageFiles>
	<Commands Reboot="Defer">
		<Command PackageFile="aspnetmvc1.msi" EstimatedInstallSeconds="30">
			<ExitCodes>
				<ExitCode Value="0" Result="Success" />
				<DefaultExitCode Result="Fail" String="Anunexpectedexitcodewasr" FormatMessageFromSystem="true" />
			</ExitCodes>
		</Command>
	</Commands>
	<Strings>
		<String Name="Culture">en</String>
		<String Name="DisplayName">ASP.NET MVC 1.0</String>
		<String Name="Anunexpectedexitcodewasr">An unexpected exit code was returned from the installer. The installation failed.</String>
	</Strings>
</Package>
                                           

This article showing how to include the .NET Framework 3.5 SP1 as a prerequisite is a good reference for the process.

I hope these tips will be helpful for you in extending the usefulness of Setup and Deployment Projects and the breadth of available prerequisite packages.

References (1)

References allow you to track sources for this article, as well as articles that were written in response to this article.
  • Response
    Response: fickmaschinen
    [...]Codesta - Blog - Tips and Tricks with Visual Studio[...]

Reader Comments (10)

jimmy choo replica handbags
Christ, have a sense of humor why don't you? And you're complaining about the level of civil discourse on the left when you have people like Jim Hoft, Pam Geller, and Lori Ziganto, just to name a few, on your side? Come on Dana, lighten up.
alain silberstein replica

September 24, 2011 | Unregistered Commenteralain silberstein replica

Your article is pretty nice and I like it very much.But at the same time, I want to share something which is pretty nice too.That is cheap dvds and I just want you to enjoy watching dvds in your recreational time.archer season 1

November 10, 2011 | Unregistered Commenterarcher season 1

No matter what type of uggs on sale

you are looking for, there is no doubt one available for you. They have come a long way from

their days of being in World War I aircrafts. And, thanks to their manufacturer, ugg boots outlet Australia, you can

feel good while wearing these shoes while still being in style! You will simply love the

options in color, styles, and designs that you have. The whole family will be wearing ugg outlet because they just are so

darn cute and comfortable!The ugg boots

sale is one of the a lot of accepted types of cossack on the bazaar appropriate now.

Anyone who is not accustomed with them should accede bottomward one on! They are appreciably

bendable and luxurious. They action an accomplished akin of abundance and a admirable

appearance as well. They are actual balmy and cozy. Your anxiety will be in shoe heaven! Let's

yield a afterpiece attending at what is accessible in the ugg boots clearance and uggs for cheap see if we can't

acquisition something that you will enjoy!

November 26, 2011 | Unregistered Commenterugg boots sale

In this modern and fashionable society, people are pursuing for links of London cool, unique, stylish and innovative. Whether it is links of london uk or fashion accessories all means a lot for modern society of today. Same is the case with trendy looking superdry outlet. When these are superdry clothes, the excitement just gets doubled. Most chic looking superdry hoodies are in fashion now. These are one of the favorite fashion accessories for men and women long time ago. If you have not yet tried superdry uk, it's time to own one and feel the difference it can make to your personality. These are just brilliant and fabulous superdry sale. They are most iconic and can provide you with a new feeling and enhance confidence. The quality of superdry shop is just superior to what you have dreamt of. Today owning a new and trendy looking superdry sale are not only meant for the wealthy people. These are now made luxurious and affordable superdry uk to reach out to every budget and range.

November 30, 2011 | Unregistered Commenterlinks of London

At the same time, the investigation<h1>Coach Outlet</h1> that led the United States to <h1>Coach Outlet</h1>the bank, the Lebanese Canadian<h1>Chanel Handbags</h1> Bank, provides new insights <h1>Coach Outlet</h1>into the murky sources of<h1>Coach Outlet</h1> Hezbollah’s money. While law enforcement <h1>Chanel Bags</h1>agencies around the world <h1>Coach Factory Outlet</h1>have long believed that <h1>Coach Outlet Online</h1>Hezbollah is a passive beneficiary <h1>Coach Outlet</h1>of contributions from loyalists <h1>Coach Outlet</h1>abroad involved in drug trafficking <h1>Coach Outlet Online</h1>and a grab bag of other<h1>Louis Vuitton Bags</h1> criminal enterprises, intelligence <h1>Coach Factory Outlet</h1>from several countries points <h1>Louis Vuitton Bags</h1>to the direct involvement of <h1>Coach Factory Store</h1>high-level Hezbollah officials <h1>Coach Outlet</h1>in the South American cocaine<h1>Coach Factory Outlet</h1> trade.One agent involved in the<h1>Coach Outlet Store</h1> investigation compared Hezbollah <h1>Coach Factory Outlet</h1>to the Mafia, saying, “They <h1>Coach Outlet</h1>operate like the Gambinos on <h1>Chanel Bags</h1>steroids.”On Tuesday, federal <h1>Chanel Bags</h1>prosecutors in Virginia announced <h1>Louis Vuitton</h1>the indictment of the man <h1>Coach Factory Store</h1>at the center of the Lebanese<h1>Chanel Handbags</h1> Canadian Bank case, charging <h1>Coach Outlet</h1>that he had trafficked drugs <h1>Chanel Bags</h1>and laundered money not only <h1>Louis Vuitton</h1>for Colombian cartels, but also for<h1>Coach Factory Online</h1> the murderous Mexican gang <h1>Coach Outlet</h1>Los Zetas.The revelations about <h1>Chanel Bags</h1>Hezbollah and the Lebanese Canadian<h1>Coach Outlet</h1> Bank reflect the changing <h1>Louis Vuitton Bags</h1>political and military dynamics<h1>Coach Factory Outlet</h1> of Lebanon and the Middle East. <h1>Chanel Bags</h1>American intelligence analysts believe<h1>Coach Factory</h1> that for years Hezbollah

December 14, 2011 | Unregistered CommenterCoach Factory Outlet

<h1>discount designer bags</h1> cheap and fashion
<h1>designer inspired handbags</h1> Acclaimed
<h1>air max 2011</h1> high quality
<h1>nike shox tl3</h1> wholesale nike
<h1>women puma shoes</h1> Particular style
<h1>air max tn</h1> Different
<h1>puma shoes</h1> Very famous
<h1>puma shoes online</h1> Popular brands
<h1>women timberland boots</h1> A great feeling
<h1>wholesale gucci shoes</h1> Online Sales

December 19, 2011 | Unregistered Commenterfsdq

When purchasing for particulars gucci outlet you have to isn't prone to bodily Gucci totes, whether hobo moving cases, handbags, handbags, elbow designer handbags or any other household, outlet gucci will often become difficult for several 100 funds. While you might uncover naturally and several useful deals available available, the truth would be the fact yet , just in case your bank card merchant purports to provide take advantage from the gucci sale to buy although nicely inside the store financial probability might be the how will you could be a fake, gucci sales considering since you can really phony options needed those of it's not worth that heavily lessen cost service.

December 29, 2011 | Unregistered Commenterkaren millen outlet

When purchasing for particulars gucci outlet you have to isn't prone to bodily Gucci totes, whether hobo moving cases, handbags, handbags, elbow designer handbags or any other household, outlet gucci will often become difficult for several 100 funds. While you might uncover naturally and several useful deals available available, the truth would be the fact yet , just in case your bank card merchant purports to provide take advantage from the gucci sale to buy although nicely inside the store financial probability might be the how will you could be a fake, gucci sales considering since you can really phony options needed those of it's not worth that heavily lessen cost service.

December 29, 2011 | Unregistered Commenterugg bailey button triplet

2011 can be a thriving year for your stripe karen millen coat, in the event you can't help you to get one, just kill that thought immediately. Only the white-colored blue karen millen coats series are high quality. Blue is probably the three primary colors, so choose blue to enhance with any color won't make a few mistakes. coat karen millen work for people office ladies or perhaps the party queens. coats karen millen reminds you when you are already thirty years old, please not stay with the sex hot wind in the fashion show.

December 29, 2011 | Unregistered Commentercoach outlet

If you are intrigued to get a giubbotti belstaff jacket within the practical really worth there can be just one location that could provide a person belstaff giubbotti items through pretty affordable furthermore to decreased cost tag. Really, several retail companies, specific stores furthermore to magazines perform just like a giubbotti belstaff sale store. To be capable of create a fantastic acquire, they need to supply their unique clearance items onto several giubbotti belstaff outlet to be capable of discharge a lot more floor house for the present kinds. In lots of via the key factory warehouses again stuffed items might display upwards that in no way produced this unique within the factory.

December 29, 2011 | Unregistered Commenterjuicy couture sale

PostPost a New Comment

Enter your information below to add a new comment.

My response is on my own website »
Author Email (optional):
Author URL (optional):
Post:
 
Some HTML allowed: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <code> <em> <i> <strike> <strong>