Friday, June 29, 2012
Ruby error "invalid multibyte character (US-ASCII)"
If you try to include non-ASCII characters in the source code of a Ruby program, you may get the error "invalid multibyte character (US-ASCII)". This one-line program illustrates the problem, using the right angle quote character:
foo = "»"
If you Google the error message, you'll find a lot of advice about placing "# encoding: UTF-8" or "# coding UTF-8" or "$KCODE = 'u'" at the top of the file. None of these worked for me.
However, I found a simple fix. My environment is Ruby 1.9.3p194 on Windows. I opened my .rb file in Notepad, selected Save As, chose "UTF-8" from the "Encoding" dropdown, and clicked "Save". This -- with no other changes made -- solved the problem.
Monday, June 25, 2012
Enumerating the rows and columns for an arbitrary SQL query using Dapper ORM
The Dapper object-relational mapper makes it easy to execute SQL queries and manipulate the results in a .NET program. I thought it would be nice to be able to execute an arbitrary query, without knowing in advance the number, names and data types of the resulting columns, and output the results. The C# program below does exactly that.
Before using this in the real world, consider:
Before using this in the real world, consider:
- Error handling. What happens if the connection string is wrong? If the query times out? If the query returns no results? Etc.
- Security. Do you want everyone to be able to execute any query against your database?!
- Performance: What if someone constructs a query that takes an hour to execute?
Here's the code, which assumes you're building a C# Console Application and have added Dapper's SqlMapper.cs to the project:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Dapper;
using System.Data.SqlClient;
using System.Reflection;
using System.Collections;
static void Main(string[] args)
static void Main(string[] args)
{
string connectionString = "YOUR CONNECTION STRING GOES HERE";
using (SqlConnection conn = new SqlConnection(connectionString))
{
conn.Open();
var rows = (IEnumerable<IDictionary<string, object>>)conn.Query("ANY SQL QUERY YOU LIKE"); // e.g. select top 10 * from FooBar
foreach (var row in rows)
foreach (var row in rows)
{
foreach (var column in row)
{
Console.WriteLine(column.Key + " = " + column.Value);
}
}
}
}
Tuesday, May 22, 2012
Increasing an AWS instance's disk space by adding an EBS volume
I wanted to provide more free disk space for my AWS instance. I had previously discovered that restoring a snapshot to a larger EBS volume doesn't easily accomplish this, so instead I added a second EBS volume to the instance. Here's how to do so using the AWS Management Console:
Click Volumes.
Click the Create Volume button.
Type in the desired size (I entered 30 GiB). Select the Availability Zone, making sure it's the same one used by your instance. From the Snapshot dropdown, select --- No Snapshot ---.
Right-click the newly created volume and select Attach Volume. Select your instance. Accept the default value for Device. (If the state doesn't change immediately from attaching to attached, try refreshing the page.)
At first, when I used Windows Explorer to view the disks in a Remote Desktop session on my instance, I didn't see the new disk. Logging off and back on didn't help. Stopping and restarting the instance did the trick. I'm not actually sure if that's what helped, or if I just needed to wait a few minutes, or what. But in the end, it worked. In addition to the C: drive, I now had an empty D: drive with a capacity of 29.9 GB.
Click Volumes.
Click the Create Volume button.
Type in the desired size (I entered 30 GiB). Select the Availability Zone, making sure it's the same one used by your instance. From the Snapshot dropdown, select --- No Snapshot ---.
Right-click the newly created volume and select Attach Volume. Select your instance. Accept the default value for Device. (If the state doesn't change immediately from attaching to attached, try refreshing the page.)
At first, when I used Windows Explorer to view the disks in a Remote Desktop session on my instance, I didn't see the new disk. Logging off and back on didn't help. Stopping and restarting the instance did the trick. I'm not actually sure if that's what helped, or if I just needed to wait a few minutes, or what. But in the end, it worked. In addition to the C: drive, I now had an empty D: drive with a capacity of 29.9 GB.
Restoring an AWS instance from a snapshot
I had created an Amazon Web Services instance. As is the default when creating an instance, it used EBS as the root device.
A few days ago, when I had it set up just the way I liked it, I created a snapshot. Then I did some experimental work, didn't like the results, and wanted to restore the snapshot to revert. Here's how to do that using the AWS Management Console:
Another tip: I wanted more disk space, so I tried making the new volume larger -- 60 GiB rather than 30 GiB -- in step 4. This didn't work. I ended up with a 60 GiB volume, but only 30 GiB was in a usable boot partition. The rest was in an unallocated partition, and I couldn't find a convenient way to merge the two partitions without losing data. A better alternative might be to create a second volume and attach it to the same instance.
A few days ago, when I had it set up just the way I liked it, I created a snapshot. Then I did some experimental work, didn't like the results, and wanted to restore the snapshot to revert. Here's how to do that using the AWS Management Console:
- In the AWS Control Panel, click Instances, then click on your instance. Instance details will be displayed at the bottom of the page. Note the instance's name (Steve-Jenkins in my example; yours will be different), instance ID (i-4b33962d), zone (us-east-1c), and root device (sda1).
- If the instance is running, stop it.
- Click Volumes. Look at the Attachment Information column to locate the volume currently attached to your instance. Right-click this volume and select Detach Volume. (The volume's state might not change from in-use to available right away. If it doesn't, try refreshing the page.)
- Click Snapshots. Right-click your snapshot and select Create Volume from Snapshot. Set the size (30 GiB in my example) and Availability Zone. Make sure the Availability Zone matches the one you noted in step 1.
- Return to Volumes. Right-click the newly created volume and select Attach Volume. Select your instance. For Device, type in the root device you noted in step 1, but be sure to prepend /dev/. In my example, I entered /dev/sda1.
- To avoid charges for the old volume that's no longer attached to your instance, right-click it and select Delete Volume.
- Click Instances, right-click your instance, and select Start Instance. If you created and attached the volume correctly, the instance should start without error.
Another tip: I wanted more disk space, so I tried making the new volume larger -- 60 GiB rather than 30 GiB -- in step 4. This didn't work. I ended up with a 60 GiB volume, but only 30 GiB was in a usable boot partition. The rest was in an unallocated partition, and I couldn't find a convenient way to merge the two partitions without losing data. A better alternative might be to create a second volume and attach it to the same instance.
Wednesday, May 9, 2012
Using Seleniun WebDriver with Windows and .NET
Wanting to write a C# program to do some automated functional testing in multiple browsers, I downloaded Selenium WebDriver version 2.21.0.
I also downloaded the Selenium Client Drivers for C#. At the time I first downloaded these, the version was 2.16.0.
I proceeded to create a Selenium hello-world program by following the C# example provided here. With all the comments and fluff stripped out, my program looked something like this:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using OpenQA.Selenium;
using OpenQA.Selenium.Firefox;
using OpenQA.Selenium.Support.UI;
namespace WebDriver1
{
class Program
{
static void Main(string[] args)
{
IWebDriver driver = new FirefoxDriver();
driver.Navigate().GoToUrl("http://www.google.com/");
IWebElement query = driver.FindElement(By.Name("q"));
query.SendKeys("Cheese");
query.Submit();
WebDriverWait wait = new WebDriverWait(driver, TimeSpan.FromSeconds(10));
wait.Until((d) => { return d.Title.ToLower().StartsWith("cheese"); });
System.Console.WriteLine("Page title is: " + driver.Title);
driver.Quit();
}
}
}
One note right off the bat: The example code at seleniumhq.com doesn't include using OpenQA.Selenium.Support.UI. This is needed, or else WebDriverWait can't be resolved.
When I tried to run this program, it threw an exception: OpenQA.Selenium.WebDriverException : Failed to start up socket within 45000. I found some stuff about this online, but none of it helped. What solved the problem was downloading the latest version, 2.21.0, of the Selenium Client Drivers.
With those two problems solved, the program compiled and ran, launching Firefox, doing a Google search for "cheese," and outputting the page title. Pretty cool!
Next, I wanted to do the same thing using IE.
The first stumbling block was when I added this line to my code: driver = new InternetExplorerDriver();
This class wasn't found. Easily remedied by adding this as well: using OpenQA.Selenium.IE;
My whole program now looked like this, executing a loop twice, once for each browser:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using OpenQA.Selenium;
using OpenQA.Selenium.Firefox;
using OpenQA.Selenium.IE;
using OpenQA.Selenium.Support.UI;
namespace WebDriver1
{
class Program
{
static void Main(string[] args)
{
for (int i = 0; i < 2; i++)
{
string browserName = "";
IWebDriver driver = null;
switch (i)
{
case 0:
browserName = "FireFox";
driver = new FirefoxDriver();
break;
case 1:
browserName = "IE";
driver = new InternetExplorerDriver();
break;
}
driver.Navigate().GoToUrl("http://www.google.com/");
IWebElement query = driver.FindElement(By.Name("q"));
query.SendKeys("Cheese");
query.Submit();
WebDriverWait wait = new WebDriverWait(driver, TimeSpan.FromSeconds(10));
wait.Until((d) => { return d.Title.ToLower().StartsWith("cheese"); });
System.Console.WriteLine(browserName + ": Page title is: " + driver.Title);
driver.Quit();
}
}
}
}
This worked for Firefox but threw an exception when it opened Internet Explorer.
System.InvalidOperationException: Unexpected error launching Internet Explorer. Protected Mode must be set to the same value (enabled or disabled) for all zones. (NoSuchDriver)
Surprisingly, this meant exactly what it said, and was corrected by opening IE, selecting Tools | Internet Options | Security, clicking on all four security zones (Internet, Local Intranet, Trusted Sites and Restricted), ensuring the Enable Protected Mode checkbox was checked for each zone, and restarting IE. (Unchecking the checkbox would work, too. The point is that the setting for all four zones must be the same.) A huge tip of the hat to Tom Dupont for that.
With IE's Protected Mode settings properly configured, the above program ran successfully, automating both browsers.
I also downloaded the Selenium Client Drivers for C#. At the time I first downloaded these, the version was 2.16.0.
I proceeded to create a Selenium hello-world program by following the C# example provided here. With all the comments and fluff stripped out, my program looked something like this:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using OpenQA.Selenium;
using OpenQA.Selenium.Firefox;
using OpenQA.Selenium.Support.UI;
namespace WebDriver1
{
class Program
{
static void Main(string[] args)
{
IWebDriver driver = new FirefoxDriver();
driver.Navigate().GoToUrl("http://www.google.com/");
IWebElement query = driver.FindElement(By.Name("q"));
query.SendKeys("Cheese");
query.Submit();
WebDriverWait wait = new WebDriverWait(driver, TimeSpan.FromSeconds(10));
wait.Until((d) => { return d.Title.ToLower().StartsWith("cheese"); });
System.Console.WriteLine("Page title is: " + driver.Title);
driver.Quit();
}
}
}
One note right off the bat: The example code at seleniumhq.com doesn't include using OpenQA.Selenium.Support.UI. This is needed, or else WebDriverWait can't be resolved.
When I tried to run this program, it threw an exception: OpenQA.Selenium.WebDriverException : Failed to start up socket within 45000. I found some stuff about this online, but none of it helped. What solved the problem was downloading the latest version, 2.21.0, of the Selenium Client Drivers.
With those two problems solved, the program compiled and ran, launching Firefox, doing a Google search for "cheese," and outputting the page title. Pretty cool!
Next, I wanted to do the same thing using IE.
The first stumbling block was when I added this line to my code: driver = new InternetExplorerDriver();
This class wasn't found. Easily remedied by adding this as well: using OpenQA.Selenium.IE;
My whole program now looked like this, executing a loop twice, once for each browser:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using OpenQA.Selenium;
using OpenQA.Selenium.Firefox;
using OpenQA.Selenium.IE;
using OpenQA.Selenium.Support.UI;
namespace WebDriver1
{
class Program
{
static void Main(string[] args)
{
for (int i = 0; i < 2; i++)
{
string browserName = "";
IWebDriver driver = null;
switch (i)
{
case 0:
browserName = "FireFox";
driver = new FirefoxDriver();
break;
case 1:
browserName = "IE";
driver = new InternetExplorerDriver();
break;
}
driver.Navigate().GoToUrl("http://www.google.com/");
IWebElement query = driver.FindElement(By.Name("q"));
query.SendKeys("Cheese");
query.Submit();
WebDriverWait wait = new WebDriverWait(driver, TimeSpan.FromSeconds(10));
wait.Until((d) => { return d.Title.ToLower().StartsWith("cheese"); });
System.Console.WriteLine(browserName + ": Page title is: " + driver.Title);
driver.Quit();
}
}
}
}
This worked for Firefox but threw an exception when it opened Internet Explorer.
System.InvalidOperationException: Unexpected error launching Internet Explorer. Protected Mode must be set to the same value (enabled or disabled) for all zones. (NoSuchDriver)
Surprisingly, this meant exactly what it said, and was corrected by opening IE, selecting Tools | Internet Options | Security, clicking on all four security zones (Internet, Local Intranet, Trusted Sites and Restricted), ensuring the Enable Protected Mode checkbox was checked for each zone, and restarting IE. (Unchecking the checkbox would work, too. The point is that the setting for all four zones must be the same.) A huge tip of the hat to Tom Dupont for that.
With IE's Protected Mode settings properly configured, the above program ran successfully, automating both browsers.
Friday, April 6, 2012
Database trigger can cause problems with @@IDENTITY
My .NET web application with a SQL Server back end was working fine, until...
I tested a feature that added a new record to one of the database tables, and I got an exception saying that a primary key constraint had been violated.
Using SQL Server Profiler, I was able to isolate the stored procedure call that violated the constraint.
Examining the SQL code in that sproc, I saw something similar to this:
DECLARE @newID INT
INSERT INTO Table1 (Foo, Goo) VALUES ('abc', 'def')
SET @newID = @@IDENTITY
INSERT INTO Table2 (Bar, Car) VALUES (@newID, 'ghi')
The problem was that someone had recently added a trigger on Table1. The trigger fired upon inserting a row into Table1, and executed some code that did an insert into some other table, changing the value of @@IDENTITY. The new value of @@IDENTITY happened to be one that was already in use as the primary key of a row in Table2, violating the constraint.
The fix was simple: use SCOPE_IDENTITY() in place of @@IDENTITY. Thanks to this article for that tip.
I tested a feature that added a new record to one of the database tables, and I got an exception saying that a primary key constraint had been violated.
Using SQL Server Profiler, I was able to isolate the stored procedure call that violated the constraint.
Examining the SQL code in that sproc, I saw something similar to this:
DECLARE @newID INT
INSERT INTO Table1 (Foo, Goo) VALUES ('abc', 'def')
SET @newID = @@IDENTITY
INSERT INTO Table2 (Bar, Car) VALUES (@newID, 'ghi')
The problem was that someone had recently added a trigger on Table1. The trigger fired upon inserting a row into Table1, and executed some code that did an insert into some other table, changing the value of @@IDENTITY. The new value of @@IDENTITY happened to be one that was already in use as the primary key of a row in Table2, violating the constraint.
The fix was simple: use SCOPE_IDENTITY() in place of @@IDENTITY. Thanks to this article for that tip.
Tuesday, December 27, 2011
DOS command-line tricks
Remember DOS? It's still with us, in the form of the Windows command prompt, and it still has some useful tricks up its sleeve. I recently needed to automate the daily copying of some database backup files from one server to another. I combined several DOS, Windows and Windows task schedule techniques to make this work.
The goal: At 7:30 AM each day, copy all files named ynot_backup_*.bak from the folder G:\sqlbackup on the database server to the folder G:\ynot_backup on one of the web servers. Both servers are running Windows Server 2003.
First, I came up with an xcopy command to copy the appropriate files: xcopy /Y g:\SQLBackup\ynot_backup_*.bak \\10.2.66.30\g$\ynot_backup.A few points of interest:
The above xcopy command works fine when executed in a DOS window on the database server. But to be able to schedule it, two changes are needed.
The final step is to go to Control Panel | Scheduled Tasks and create a new scheduled task to execute the above command at 7:30 AM each day. The only trick here is choosing an appropriate account under which to run the task. I chose an administrator account that has sufficient permissions and whose password doesn't change too frequently -- because each time the password changes, we must inform the scheduled task of the new password.
Now all I need to do is keep an eye on the web server and make sure the whole G: drive doesn't fill up with old database backups!
The goal: At 7:30 AM each day, copy all files named ynot_backup_*.bak from the folder G:\sqlbackup on the database server to the folder G:\ynot_backup on one of the web servers. Both servers are running Windows Server 2003.
First, I came up with an xcopy command to copy the appropriate files: xcopy /Y g:\SQLBackup\ynot_backup_*.bak \\10.2.66.30\g$\ynot_backup.A few points of interest:
- This command uses a UNC path (a path starting with two backslashes) to access the web server across the LAN. Specifically, \\10.2.66.30\g$ refers to the default share of the G: drive on the web server, and I've configured the permissions, both in the share and in the file system -- to give write access to authenticated users. (This is safe, because the share is accessible only to authenticated users using a non-routable private IP address on a local area network.)
- The /Y flag tells xcopy to overwrite any existing files without prompting the user for confirmation.
The above xcopy command works fine when executed in a DOS window on the database server. But to be able to schedule it, two changes are needed.
- First, we need to give the full path to xcopy.
- Second, we need to execute the command shell, cmd.exe, passing it the /c flag and the command to execute.
The final step is to go to Control Panel | Scheduled Tasks and create a new scheduled task to execute the above command at 7:30 AM each day. The only trick here is choosing an appropriate account under which to run the task. I chose an administrator account that has sufficient permissions and whose password doesn't change too frequently -- because each time the password changes, we must inform the scheduled task of the new password.
Now all I need to do is keep an eye on the web server and make sure the whole G: drive doesn't fill up with old database backups!
Subscribe to:
Posts (Atom)