# Monday, July 23, 2007

This weekend I spent some time playing with Silverlight and Orcas.  Needless to say, I am very impressed with both.  Here's a sample Hangman application I created in about three hours.

I thought I'd go ahead and document the steps I took when I setup my development environment.  I'll try to come back here and update the content, so that it stays pertinent as updates are released.

Setting up a Silverlight development environment

  1. Make sure to read everything you can on Silverlight, as well as the Get Started Silverlight page (I've listed a number of good blogs at the bottom of this post).
  2. Install Microsoft Silverlight 1.0 Beta (for Windows).  This is the runtime that's required to experience Silverlight applications.
  3. Install Microsoft Silverlight 1.1 Alpha (for Windows).  This is the runtime that's required to experience Silverlight applications written with .NET.
  4. Reboot.
  5. Install Microsoft Visual Studio codename "Orcas" Beta 1.  Soon to be Visual Studio 2008, this is the next evolution of Visual Studio 2005.  It's pretty sweet.
  6. Reboot.
  7. Optional: Install Microsoft MSDN Library for Visual Studio codename "Orcas".  Watch out - during the installation of the MSDN Library, it took about five minutes for it to complete this step.  Be patient, and let it finish.
    MSDN Library install
  8. Reboot.
  9. Install Microsoft ASP.NET Futures (May 2007).  This provides you with ASP.NET controls for Silverlight.
  10. Install Expression Blend 2 May Preview.  This is a design tool that allows a user to interact with Silverlight.  Note: this is different from Expression Blend that can be found on MSDN.  Make sure to get this download.  Also, Tim Heuer pointed me to http://www.microsoft.com/Expression/products/download.aspx?key=blend2maypreview, which provides a product key and longer trial (180-day evaluation).  Note: when I went to install Expression Blend 2, I was only given two choices: Vista, or Windows XP.  My development VM is Windows Server 2003 Standard R2.  I chose Windows XP, and haven't had any problems.
    Expression Blend 2 choice
  11. Install Microsoft Silverlight Tools Alpha for Visual Studio codename "Orcas" Beta 1.  This is an add-on that allows you to create Silverlight applications using .NET.
  12. Download Microsoft Silverlight 1.0 Beta Software Development Kit (SDK).  This is a zip file that contains documentation, samples along with templates for Visual Studio, and has also a “Go Live” license that enables building commercial applications.  I unzipped it to C:\Program Files\Microsoft Silverlight\SDKs.
  13. Download Microsoft Silverlight 1.1 Alpha Software Development Kit (SDK).  This is another zip file that contains documentation and samples Silverlight Web experiences that target Silverlight 1.1 Alpha.

That should be enough to get your environment up and running.  Once that's complete, watch this Silverlight walk-through: http://silverlight.net/quickstarts/silverlight10/xaml.aspx.

Here are some good Silverlight blogs I've found (send me email, or leave me a comment, if you know of more): 

Also, here are a few interesting posts:

I hope someone finds this useful!  Leave a comment if you've developed something cool, so that I can check it out!

posted on Monday, July 23, 2007 9:17:41 AM (Central Standard Time, UTC-06:00)  #    Comments [4] Trackback
# Sunday, July 22, 2007

I had no idea that this game would be so popular!  Despite the crappy graphics, it's been referred half a dozen times, and received almost 300 unique hits in the last 24-hours.  Pretty amazing!

Here's the link to Hangman.  Give it a try.  Here's the original Hangman announcement.

There have been a few updates to the original program, including:

  • Includes the definition of the word; calls a .NET web service that parses a web response from Dictionary.com (please let me know if you find problems!)
  • Tracks statistics (e.g. total times played, won, and lost); so far we lose a LOT more 
  • Frees the man from the noose if you win

Yes, they're all silly little updates, but hopefully it increases the enjoyment of the game.

Here are a couple screen shots with the updates.  The first one shows what happens when you die, err, lose.

Hangman; lose

This next one shows what happens when you win.  Doesn't that look a lot more pleasant?

Hangman; winning

This sure is fun stuff to play with.  Now that I feel more comfortable with Silverlight and WPF, there are all kinds of things I'd like to write.  It's a shame I'm not more artistic.

Anyone out there have some mad artistic skills that would like to team up with me?

Best of luck!

posted on Sunday, July 22, 2007 3:08:13 PM (Central Standard Time, UTC-06:00)  #    Comments [2] Trackback
# Saturday, July 21, 2007

Unfortunately I don't have enough time at the moment to write-up a full post that outlines how this works (we have friends coming over for dinner), but I wanted to at least share my very first attempt at a Silverlight and WPF web application.

Check out Hangman!

It's a pretty slick (albeit graphically retarded) Silverlight application.  It uses Microsoft Silverlight 1.1 Alpha (for Windows), and I built it on the work I did devising a strategy to (consistently) win hangman.

Hangman with Silverlight and .NET 3.5

I plan to eventually write a post explaining exactly how I setup my development environment, how the technology within this application functions, and I'll post the source code (once I refactor it).

What do you think?  Have you built any Silverlight applications?

posted on Saturday, July 21, 2007 4:49:47 PM (Central Standard Time, UTC-06:00)  #    Comments [2] Trackback

I was writing a Silverlight application today, when I came across the following error when trying to invoke a web service method from my Silverlight project:

"Error invoking service"

After digging around a little bit, I noticed the following above my web method:

// To allow this Web Service to be called from script, using ASP.NET AJAX, uncomment the following line.
// [System.Web.Script.Services.ScriptService]

"Ahh!" I said, as I uncommented the class attribute.

Unfortunately, when I tried to compile I was told:

The type or namespace name 'Script' does not exist in the namespace System.Web' (are you missing an assembly reference?)

So, I went to "Add Reference", added "System.Web.Extensions" to my project, and now it compiles!  And, after testing my web service call from my Silverlight project, the data from the web services is returned to my Silverlight project as expected.

Nice!

posted on Saturday, July 21, 2007 3:24:10 PM (Central Standard Time, UTC-06:00)  #    Comments [0] Trackback
# Sunday, July 15, 2007

Edit: Try my own version of Hangman, written with Silverlight and WPF!

In an effort to distract myself from more productive endeavors, I started playing Hangman on The Free Dictionary Web site today.  They have an outstanding plugin that you can also add to your Google homepage (which is actually how I discovered it).  The interface allows you to simply type the letter into the guess box (if you look below you'll see that my last guess was the letter "G"), and it updates it as you go:

image

During game play, the plugin constructs the man you are desperately trying to save.  You can only make ten mistakes before the man is hanged:

image

So, as I was wasting my time today, I started wondering if there's a strategy to winning Hangman.  Assuming that there is, I decided to try and figure it out.

I started with a few basic ideas:

  1. Certain letters are used more frequently than other letters.
  2. The frequency of letters are probably different given the total number of letters in the word.
  3. You can maximize your ability to win Hangman by playing the most common letter first, followed by the next most common letter, and so on.

I realize that there are probably more complicated theories (such as the most common letters changing based on discovered letters), but for this first part I decided that I wanted to keep it somewhat simple.  I can complicate it more later on.

(If you aren't interested in how I explored these ideas than jump to the end of this post to see my findings!)

My first order of business was to find a list of English words.  After performing a few searches, I found what I was looking for: Word-List.com.  They have word lists in many languages, and the lists are available as zipped text files separated by a newline.  This made it very easy to parse.

I unzipped the file and added it to a new Visual Studio 2005 Console Application project.  So that the file is always available in the root of the folder, I changed the Copy to Output Directory property to Copy always.  Next, I wrote a method that loads all the words into a string array and saves each word to a particular file, based on the length of the word.  For example, the words "dog" and "cat" are saved to the file "03.txt" whereas the words "father" and "mother" are saved to the file "06.txt".  Here's the code:

using (StreamReader input = new StreamReader("words.english.txt"))
{
    string contents = input.ReadToEnd().Trim();
    string[] wordArray = contents.Split('\n');

    foreach (string word in wordArray)
    {
        appendWord(word);
    }
}

Using the StreamReader, I loaded the file and split it into a string array based on the character '\n' (note the single, rather than double, quotes).  This allows me to iterate through an array of words.  I pass each word to the appendWord method, which calls the appendFile method.  Passed in is a file name that's based on the length of the word, as well as the word itself.

private static string wordByLength = "{0}.txt";
public static void appendWord(string word)
{
    appendFile(String.Format(wordByLength, word.Length.ToString().PadLeft(2, '0')), word);
}

The appendFile method simply creates an instance of a StreamWriter and appends the word to the file (if it exists) or creates a new file (if it doesn't exist).

public static void appendFile(string file, string text)
{
    using (StreamWriter writer = File.AppendText(file))
    {
        writer.WriteLine(text);
        writer.Flush();
    }
}

Once I wrote all this and let it run, it took about 20-30 minutes to complete (I don't know exactly because I went to eat dinner).  When I cam back, I had a directory of 23 files filed with sorted and ordered data:

image 

Pretty cool, but not quite there!

Before I moved on, I wanted to know how many words had only two characters, three characters, and so on, all the way to 24.  So, I wrote another method that loaded each of the aforementioned files, loaded them into an array, and determined the length of the array.

foreach (string fileByLength in Directory.GetFiles(wordByLengthDirectory))
{
    using (StreamReader input = new StreamReader(fileByLength))
    {
        string contents = input.ReadToEnd().Trim().Replace("\r\n", "\n");
        string[] wordArray = contents.Split('\n');
        int wordLength = wordArray[0].Length;

        appendFile(wordCountByLength, wordLength.ToString().PadLeft(2, '0') + ": " + wordArray.Length);
    }
}

Nothing really complicated here.  I loaded the files into a file array by using the Directory.GetFiles() method, replaced "\r\n" with "\n" since the Environment.NewLine creates carriage return and a newline, split the array, and appended the length of the array (along with the number of characters in the word) into a file.  Here were the results:

02: 61
03: 627
04: 2988
05: 7198
06: 14163
07: 20452
08: 27015
09: 29824
10: 29220
11: 25021
12: 19966
13: 14683
14: 9672
15: 5890
16: 3363
17: 1808
18: 838
19: 428
20: 197
21: 81
22: 40
23: 17
24: 5

Kind of interesting that there are more 9-letter words than any other words, eh?  It also makes a really pretty looking graph:

While it's interesting, it doesn't really help me with my stated goal.

So, with the twenty-three independent files, I decided to find the most common letters, and sort them according to popularity.  This, my friends, is the best part.  Not only did I decide to use a generic list to contain the details of these files, but I also decided to use predicates and delegates to help me with my task!  What fun!?!

First, I needed a collection to start the characters and counts in.  For example, when I load the 61 words that contain two characters, I need a collection that keeps track of the frequency each character is used.  To do this I defined a class called CharacterCounter, and then created a generic list.  First, here's the class I created.

public class CharacterCounter
{
    private char character;
    private int count;

    public char Character
    {
        get { return character; }
        set { character = value; }
    }

    public int Count
    {
        get { return count; }
        set { count = value; }
    }
}

Very simple class.  It has two public properties: Character and Count.  When used as a generic list, it allows me to create an object of characters with their associated counts (e.g. frequency used).

In determining the frequency each character is used based on the number of characters in the words, I first iterate through each of the files that contain the sorted words.  This allows me to first find the ordered frequency of characters for words with two characters all the way to twenty-four characters.  I then load the contents of each file into a string array, so that I can iterate through each of the words.  Next, I create a character array out of the word, and iterate through each of the characters.  It looks something like this:

foreach (string fileByLength in Directory.GetFiles(wordByLengthDirectory))
{
    List<CharacterCounter> characterCounters = new List<CharacterCounter>();
    int fileLength = 0;

    using (StreamReader input = new StreamReader(fileByLength))
    {
        string contents = input.ReadToEnd().Trim().Replace("\r\n", "\n");
        string[] wordArray = contents.Split('\n');
        if (fileLength == 0)
        {
            fileLength = wordArray[0].Length;
        }

        foreach (string word in wordArray)
        {
            char[] characterArray = word.ToCharArray();

            foreach (char character in characterArray)
            {
                //...
            }
        }
    }
}

Once I am iterating through each of the characters, I need to start keeping track of the number of times the character appears.  Notice that I created a generic list for my CharacterCounter class called characterCounters.  Before I increment the character count, I first check to see if the character in question already exists within my list.  This is performed using the following code:

CharacterCounter characterCounter = characterCounters.Find(delegate(CharacterCounter d) 
{ 
    return d.Character == character; 
});

If the character exists, then the characterCounter list returns the CharacterCounter object that has a Character that equals the character I specified.  For instance, if the specified character is "a", and it exists in the CharacterCounter list, then the characterCounter object is returned with an "a" for the Character and the number of times it has been found in the Count property.

If the character does not exist in the characterCounters list, then the characterCounter object is null.  If it's null, I create a new instance of a CharacterCounter, set my values accordingly, and add it to the list.  If it's not null, I increment the count:

if (characterCounter == null)
{
    characterCounter = new CharacterCounter();
    characterCounter.Character = character;
    characterCounter.Count = 1;

    characterCounters.Add(characterCounter);
}
else
{
    characterCounter.Count += 1;
}

This continues until I've gone through every word in the file.  At this point, I have a generic CharacterCounter list filled with the number of times a character is found.  Since I want to know the most commonly used characters, I need to sort my list so that the most common characters are first.

The following code will sort the characterCounters list by descending count:

characterCounters.Sort(delegate(CharacterCounter cc0, CharacterCounter cc1)
{
    return cc1.Count.CompareTo(cc0.Count);
});

Using an anonymous delegate, you can tell the list to sort itself from the largest number to the smallest.  Changing the code to the following will sort it from smallest to largest:

characterCounters.Sort(delegate(CharacterCounter cc0, CharacterCounter cc1)
{
    return cc0.Count.CompareTo(cc1.Count);
});

All this is done without having to use the IComparable interface!  Amazing!

At this point, our list is sorted.  All we need to do is create a string based on the ordered characters, and write it to a file:

foreach (CharacterCounter characterCounter in characterCounters)
{
    orderedCharacters += characterCounter.Character.ToString();
}
appendFile(orderedFrequencyByLength, fileLength.ToString().PadLeft(2, '0') + ": " + orderedCharacters);

The code simply iterates through the characterCounters list, appends the value of the Character property to a string, and then appends the data to a string.

After all of this work, we can finally show the list. 

# of characters in word: list of characters, from most frequent to least frequent

02: aoueyidbrtfslnmhgwzkvjcp
03: aoeuirstdylmghpknbwfczvxj
04: aeoirutslndpkymbhgcwfzvjxq
05: aeriosnltuycdmhpbgkwfvzxjq
06: earinoltsucdmphgbykfwvzxjq
07: eairnoltsucdmpghbykfwvzxjq
08: eaironlstucdmphgybfkwvzxjq
09: eiarontslcudmphygbfkvwzxqj
10: eiaorntslcupdmhygbfvkwzxqj
11: eiaorntslcupmdhygbfvkwzxqj
12: eiaontrslcupmhdygbfvzkxwqj
13: eiaontrslcpumhdygbvfzxkqwj
14: eioantsrlcpuhmdygbvfzxqkwj
15: ieoantsrlcpuhmydgbvfzxqkwj
16: eioatnrslcphumydgbvfzxqkwj
17: ieoatnrsclphumydgbvfzxqkwj
18: oeiatrnsclphmuydgbvfzxqkjw
19: oiaetnrsclhpmyudgbvzfxqkwj
20: oieatrnlchspymugdbvzfxjqk
21: oietanlcrshpymgdubzfvjx
22: oeticrahnslpymdugbxvzjq
23: oiaetlhscnprmdyugbfx
24: oihletapcyrdsnfgmzux

Going from the left to the right, we can tell the frequency of a letter in a word based on the number of characters in the word.  For example, in a nine-letter word, the most frequently used characters are: e, i, a, r, o, n, etc.

Now, here's the real question: does knowing all of this help us to be a better Hangman player?

Well, what don't you try for yourself.  Go to The Free Dictionary and play a game of Hangman.  Based on the number of characters in the word, try the list characters above in their given order.  From what I've seen, it doesn't work 100% of the time, but it certainly gets it correct more often than not.

In Part II (goodness, a Part II?!?!), I plan to write a test framework that uses the dictionary I downloaded to randomly calculate the accuracy of this method.  I'll use a large set of sample data, and see how often I can guess the word correctly based on the rules above without specifying ten incorrect answers.

In Part III (holy smokes!), I'd like to make the rules more intelligent by changing the most common letters used based on the known letters in the word.  For instance, if the word has an S and T, I'd like to know if an E or I is more likely to also be in the word.

Chances are it'll be awhile before I get to Part II and Part III, but I think I'll get there eventually.

I truly hope that at least someone else out there has fond some value in this post!  Here's the full source-code, along with all the parsed files (look in the debug directory).

Dictionary.zip (2.15 MB)

Please let me know if you have any thoughts!

posted on Sunday, July 15, 2007 8:28:23 PM (Central Standard Time, UTC-06:00)  #    Comments [0] Trackback

Years ago I made a serious mistake when I installed, and briefly used, iTunes -- I gave it the ability to reorganize my music folders!  Since then, all my nicely organized and sorted directories of (legally obtained) digital music have been disorganized and confused.

For example, I used to have a directory named "U2" where all my music for the group U2 was stored.  However, iTunes decided that my organization structure was too simple, and broke it out into folders derived from the contributing artists.  Now I have "Bono & The MDH Band", "Bono_Daniel Lanois", "Bono_Gavin Friday_Maurice Seezer", etc. It's a real mess.  Additionally, it left the folder "U2" with a bunch of empty subdirectories!  How rude!

I've made an effort, as of late, to try and clean-up this mess.  In doing so, I've ended up with a lot of empty directories that previously contained music and/or subdirectories.  After manually deleting a couple directories, I decided I needed to automate the process.  I was surprised to find that this process is not nearly as straightforward as I thought it would.

In going about this process, I decided to do the following:

  • Allow it to scan the directories first, and tell me which folders are empty (allows me to double-check!)
  • Ignore all hidden files (this is because Windows Media Player and iTunes create hidden album art which I don't want to preserve)
  • Log empty directories, deleted directories and errors
  • Scan first, delete second
  • Ignore read-only attributes (all my music is marked as read-only to prevent someone from doing what I'm doing)
  • Delete a directory if it is made empty because it's subdirectories are deleted

Let me break down how I accomplished these things.

To scan for empty directories, I created a method that starts with a predefined directory, and recursively iterates through all the subdirectories.  During this iteration, I scan the folder to see if it contains any files using the GetFiles method.  Rather than specify files with a specific file extension (like .mp3 or .wma) I decided to get all files by using the "*.*" pattern (directory happens to be DirectoryInfo object).

using System.IO;

...

FileInfo[] files;
files = directory.GetFiles("*.*");

However, many of the files are hidden (because WMP and iTunes creates album art that's hidden), so I decided to exclude hidden files.  I did this by checking the hidden attribute of the file, and if it was turned on I ignored the file.  If non-hidden files were found, I marked a local boolean value as true, indicating the folder contains files.

foreach (FileInfo file in files)
{
     // only look for files that are not marked as hidden
     if (!((file.Attributes & FileAttributes.Hidden) == FileAttributes.Hidden))
     {
         notEmpty = true;
     }
}

Next, I want to check to see if the directory contains subdirectories, and if those subdirectories are empty.  To do this I get all the subdirectories and call the same method that is currently executing.  This provides a level of recursion that allows me to iteratively go through every subdirectory in a directory.  Since the existence of a subdirectory means that the directory is not empty, I automatically mark the current directory as not empty.

// look through directories recursively
DirectoryInfo[] dirs = directory.GetDirectories("*.*");
foreach (DirectoryInfo dir in dirs)
{
     scanForEmptyDirectories(dir);
     notEmpty = true;
}

Next, I add the directory path to a generic string collection so that I can later iterate through and delete the directories.  The code here is very simple:

private static List<string> directoriesToDelete;
directoriesToDelete = new List<string>();

...

if (!notEmpty)
{
    // add folder to collection
    directoriesToDelete.Add(directory.FullName);
}

This accomplished, I needed to write a method that logs empty directories, the deletion of directories, and errors.  I created a method that uses the StreamWriter and appends text to a file.  Very simple.

public static void logMessage(string logMessage)
{
    using (StreamWriter writer = File.AppendText(logFile))
    {
        writer.WriteLine(logMessage);
        writer.Flush();
    }
}

This method is called throughout the application, and writes any pertinent information to a log file (as you'll see below).

Now, to delete the directories, I iterate through my generic string collection.  Since some of my directories are marked as read-only, I elected to change the attributes of all directories marked for deletion to normal.  This removes the read-only flag, if it exists.  Next, I performed a similar operation on the files left in the directory (e.g. hidden files) and removed the read-only flag.  Once these two steps are complete, I can then delete the folder using the Delete method on the Directory object.

// iterate through the empty directories
foreach (string directory in directoriesToDelete)
{
    try
    {
        DirectoryInfo dir = new DirectoryInfo(directory);
        // set the attributes to normal; you cannot delete a readonly folder
        dir.Attributes = FileAttributes.Normal;

        string[] files = Directory.GetFiles(directory);
        // iterate through the files
        foreach (string file in files)
        {
            FileAttributes attributes = File.GetAttributes(file);
            if ((attributes & FileAttributes.ReadOnly) != 0)
            {
                // remove the readonly attribute from the files
                File.SetAttributes(file, ~FileAttributes.ReadOnly);
            }
        }

        // make sure to make the delete as recursive
        Directory.Delete(directory, true);
        // log the deletion
        logMessage(string.Format("Deleted: {0} ", directory));
    }
    catch (Exception e)
    {
        // log the error
        logMessage(string.Format("Error deleting: {0} ", directory));
        logMessage(string.Format("Error message: {0} ", e.Message));
        logMessage("Exiting application");
        // exit the console application
        Environment.Exit(0);
    }
}

Notice that I catch errors, log them, and then exit the application.  Also, the try/catch is embedded in the directory loop, so that the directory path can be included in the log (if it were the other way around, the directory path would be out of scope and unavailable).

Finally, given the above code, it is possible to make a parent directory empty by deleting its child directories.  To resolve this, I wrapped the execution of my code in a while statement that only exists when a given condition returns false.  This condition value returns false only if there hasn't been a single directory marked as empty.  If any directory is marked as empty, the application performs an additional scan following the deletion of the directories it found empty.  (Note: the runScan boolean value is a class-level variable that is set to true elsewhere in the code.)

while (runScan)
{
     // assume that we won't have to run again
     runScan = false;

     // if the value returns true, then run it again
     // it would return true 
     scanForEmptyDirectories(new DirectoryInfo(startingDirectory));
}

All in all, this was a very quick and efficient way to eliminate my empty music folders.  Your needs may be slightly different, so make changes accordingly.

I've gone ahead and included a zip file with the full source code for this console appliation.  Let me know if you have thoughts and/or suggestions!

EmptyFolders.zip (20.81 KB)

posted on Sunday, July 15, 2007 1:18:41 PM (Central Standard Time, UTC-06:00)  #    Comments [1] Trackback

My cousin Kai just got married yesterday in Maine.  I wasn't able to make it, but my parents and sister were able to fly up and attend.  By all accounts, it was a fantastic wedding and I wish we could have made it, but with a month old baby (not to mention her two-year-old sister) we just didn't feel like we could handle the trip.  And, given the multiple layovers and missed flights my parents encountered along the way, I think we made the right choice.

Still up in Maine, my dad called and said that he wanted to know the best way for all the attendees to share the great multitude of pictures that were taken.  He offered to use his corporate server to host the pictures, but I suggested that it would take a lot of work to write an interface to these pictures that was slick, intuitive, and made it easy for people to post, share, and view pictures.  Plus, there are a lot of great services out there, such as flickr, photobucket, and webshots (incidentally, doesn't anyone believe in capitalizing any more?).

Since we want to share both pictures AND video, I decided to go with webshots.  I can't say that I really love the interface, but it does seem to have all the necessary bells and whistles.

One thing I immediately noticed is that there does not appear to be any Web site that allows a community of users to post and manipulate the pictures of an album.  For instance, given my cousin's wedding as an example, I don't know of a Web site that would allow multiple people to register for the same album so that they could all upload and share pictures together.  The best solution I can think of is to create a new account, share the username and password with those interested in posting pictures, and distribute the URL to those that simply wish to view the pictures.

That said, I was asked to create said community, and share some simple instructions on how to both view the pictures, as well as upload new pictures.

View the pictures

  1. Open up your browser of choice.
  2. Browse to: http://community.webshots.com/user/kaisky2007/
  3. Select your album of choice (e.g. Wedding).

Upload new pictures

  1. Open up your browser of choice.
  2. Browse to: http://www.webshots.com/
  3. Enter the username and password in the upper right-hand corner, and click the "log in" button.  Feel free to  contact me if you need the login information.

    image
  4. After you are logged in, click the "upload" menu button.

    image
  5. Select an upload destination (or create a new album).  Personally, I'd recommend just using the existing "Wedding" album to keep it simple.

    image
  6. Click the "select files" button.  This will open a file dialog box.

    image
  7. Browse to the location of your pictures and/or videos.  Select the files you want to upload, and click the "Open" button.  You should see the selected files added to your file list.

    image
  8. Repeat steps 6 and 7 to add additional files.
  9. Once have added all your files, click the "upload" button.

    image
  10. You can now add a title and description to the pictures and videos you have uploaded.  The more information the better, so add some comments!  If you don't, then people that missed the wedding (like me!) won't know what I'm looking at.  Click the "save" button to save the title and description (or click "save all changes" below).

    image
  11. When you are done, you can click "view album" to look at your handiwork!

Pretty simple stuff!  I hope that someone in the family finds this information useful!

Congratulations, Kai and Sky!

posted on Sunday, July 15, 2007 11:00:48 AM (Central Standard Time, UTC-06:00)  #    Comments [0] Trackback

It was a slower-than-normal week in the world of Commerce Server; that is, slow for everyone except Max.  The man is a Commerce Server animal!

The general slowness and lack of blog posts can probably be attributed to the Worldwide Partner Conference in Denver, CO., this past week.  Lots of activity.  Unfortunately, I was unable to attend.  I've been swamped with BizTalk projects and dirty diapers.

Here's what I read regarding Commerce Server this past week ...

Blogs (posts you should read)

07/10/2007 - Max Akbar - Endeavor Commerce to unveil SmartCatalog TM for CRM 6.1 at Microsoft Worldwide Partner Conference - Max mentioned that Endeavor Commerce announced version 6.1 of their SmartCatalog for CRM application.  You can learn more and SmartCatalog, and all the great stuff they're doing at Endeavor Commerce, here.

07/11/2007 - Max Akbar - Microsoft Commerce Server Webcasts on TechNet - Max has put together two new webcasts: How to Configure Commerce Server 2007 and Schemas for Commerce Server 2007 BizTalk Adapters.  His first webcast shows specific instructions on how to configure Commerce Server applications, and the second provides detailed information how how to use the BizTalk schemas for the Commerce Server adapters.  Be sure and take a look at these two screen casts.  As with most everything Max does, these webcasts are great.

07/13/2007 - CS Team Blog [MSFT] - "Invalid Viewstate" With Load Balancers - Joe Wasson, a member of the Commerce Server product team, provides a solution to a problem that may occur when a client goes through the checkout process.  If you receive the error"Viewstate verification failed. Reason: Viewstate was invalid." during the checkout process, be sure and take a look at this post.

07/14/2007 - Max Akbar - Commerce Server 2007 SP1 Partner SDK - Max reminded us all that the Partner SDK has also been updated: Commerce Server 2007 SP1 Partner SDK.

07/14/2007 - Max Akbar - Catalog Web Service and SetJoin API Configuration - Max discovered a problem with the Set Join API documentation, with regards to the Catalog Web Server web.config file.  Along with the product group, Max was able to find a solution to the issue.

Forum / Newsgroup (answered questions that you might find useful to peruse)

07/09/2007 - CS2007 now fails - An additional tip on how to resolve the "DirectMailer already exists" problem that occurs when configuring a renamed Commerce Server computer.

07/09/2007 - Error while using the Site Connection - Jared Hodges provides two steps that must be taken to make sure that the management applications can connect to the orders service.  These both relate to whether or not your certificate is trusted.

07/10/2007 - Migrating profile data from CS2002 to CS2007 - Max provides two useful links describing how to migrate profile data from Commerce Server 2002 to Commerce Server 2007.

07/10/2007 - Unit Test failing for Starter Site - William was having problems with the BasketHelperTest and CatalogHelperTest for the starter site.  He resolved the problem by installing Service Pack 1 for Visual Studio 2005.

07/10/2007 - HTTP 500 Error When Launching Business User Application - David Lott provides tips on how to configure Commerce Server 2007 on Windows Vista.

07/10/2007 - Product Relationships Question - Describes how to link the product definition data to the product relationships returned from the CatalogRelationshipsDataTable.

07/11/2007 - Error in Getting Profile - Some guidance on how to retrieve profile information.

07/11/2007 - How to determine is user authenticated in current session [sic] - Again, William answered his own question, and discovered the HttpContext.Current.User.Identity.IsAuthenticated property.

Have a great week!

posted on Sunday, July 15, 2007 8:51:41 AM (Central Standard Time, UTC-06:00)  #    Comments [0] Trackback
# Saturday, July 14, 2007

I was performing an advanced search on Google Blog Search (posts that include "commerce server 2007" over the past week) when allow of a sudden I started getting the following error:

We're sorry ... but your query looks similar to automated requests from a computer virus or spyware application

Que?!  What is it with me and search engines?!

Something very similar happened awhile back when I was searching Yahoo.  I got the infamous "error 999", which evidently is most often caused by bandwidth limiting that Yahoo! invokes to prevent DoS (Denial of Service) attacks and automated processes that overload their systems.

Come on now, is it too much to be able to perform some advanced searches?  While I wasn't surprised to see this happen with Yahoo!, I've come to expect more from Google.

Sheesh!

posted on Saturday, July 14, 2007 7:33:11 PM (Central Standard Time, UTC-06:00)  #    Comments [0] Trackback
# Wednesday, July 11, 2007

So, I've finally joined the 21st century, and purchased a Samsung Blackjack.  After waiting a two agonizing days, it finally arrived.  Man, it's a beauty!

Samsung Blackjack

I'll spare you a review of all it's features (this is covered in agonizing detail elsewhere), but there are a couple tidbits I'd like to share.

The Blackjack runs Windows Mobile 5.0 with the Messaging and Security Feature Pack.  One of the neatest features that this supports is Exchange ActiveSync w/ Direct Push Technology.  Fortunately, the company I work for has Exchange 2007 setup to push emails directly to smart phones, and this was the first thing I setup.  Worked like a charm!  Very easy.

Note: I know that Windows Mobile 6.0 is out.  If you have an application that runs Windows Mobile 6.0, then good for you!  Unfortunately, the Blackjack is still on 5.0, which is why this post is geared towards 5.0.

The second thing I looked into was how to setup a development environment that allows me to develop applications that I can deploy and run on my Blackjack.  Turns out, it's pretty simple.

My searches lead me to the Windows Mobile 5.0 Developer Resource Kit.  This kit contains (almost) everything you need to develop Windows Mobile 5.0 applications for a SmartPhone and Pocket PC.  I suggest you review the details surrounding the resource kit, and then download it and try it yourself.

As I said, my goal here is to setup a development environment and deploy a simple application to my Blackjack.  In order to consider this a successful test, I don't need the application to actually do anything except run.  Here are the steps I took.

Note: while this is written specifically for the Blackjack, the following steps will (largely) work for any kind of SmartPhone running Windows Mobile 5.0.

  1. Install all the prerequisites.  I already had all the required prerequisites installed (e.g. Visual Studio 2005 and ActiveSync).  I haven't yet tried any of the optional installations.
  2. Download the Windows Mobile 5.0 Developer Resource Kit.  This kit has everything you need to get going.
  3. Run the MSI and install the kit.
  4. Once the kit is installed, run the Windows Mobile 5.0 Developer Resource Kit application.  You can run this by clicking Start --> All Programs --> Windows Mobile 5.0 Developer Resource Kit --> Windows Mobile 5.0 Developer Resource Kit.
  5. Click Install the Developer Tools --> Install the Tools, and then in the detail pane slide down and click Windows Mobile 5.0 SDK for Smartphone (or Pocket PC, if you prefer).

    Windows Mobile 5.0 Developer Resource Kit
  6. This opens up File Explorer in the folder C:\Program Files\Windows Mobile 5.0 Developer Resource Kit\content\Developer Tools\Windows Mobile 5.0 SDKs\.  From here, click Windows Mobile 5.0 SDK for Smartphone.msi or Windows Mobile 5.0 SDK for Pocket PC.msi.
  7. Once the installation completes, Visual Studio will have been updated to include a new project type called "Windows Mobile 5.0 Smartphone" under "Visual Basic" and "Visual C#".  Open Visual Studio 2005, and click File --> New --> Project.

    Windows Mobile 5.0 Smartphone project
  8. Select "Device Application" and click OK.  Yes, I know the name and location are poor.  Change them if you want; remember, this is just a quick test/demo.
  9. The project template includes a Form1.cs file.  The designer shows a form embedded in a phone shell.  Drop a label on the form that says something (e.g. "Hello world!").

    image
  10. Make sure your Blackjack is connected to your computer via your USB cable, and confirm that ActiveSync is able to communicate and synchronize with your Blackjack.
  11. Samsung and Microsoft lock the Blackjack phone so that applications from unsigned publishers (like us!) will not run or deploy to the Blackjack phone.  In order to resolve this, take a look at these two discussions: Application Unlock Your Blackjack and Samsung Blackjack tips and tricks (the latter has some great tips).  I chose the method in the first link, and ran AppUnlock.cab.  Worked great.
  12. Go to Build --> Deploy DeviceApplication1.  A dialog box asks you where to deploy your application.  Choose Windows Mobile 5.0 Smartphone Device.  Click Deploy.

    Deploy Smartphone device
  13. The initial deployment will push a lot of CAB files used to run your .NET application.  Make sure you approve all these cabs from your Blackjack.  You will only have to do this the first time.
  14. Since our application was very simple, we'll have to browse to the executable through the file explorer.  Click Start --> Applications --> File Explorer.  Browse to \Program Files\DeviceApplication1, and click DeviceApplication1.
  15. As we have not signed the application, you will have to confirm that you want to run the application (I'll write a post some other time explaining how to sign the application).  Click Yes to continue.
  16. Lo and behold, our application runs and displays "Hello World!".

Yes, a completely useless and standard example, but it does serve to highlight how easy it is to setup a development environment that can publish applications to your SmartPhone (e.g. your Blackjack).

Now all I need to do is figure out a useful tool to build ...

I hope this helps!

posted on Wednesday, July 11, 2007 2:58:36 AM (Central Standard Time, UTC-06:00)  #    Comments [0] Trackback
# Sunday, July 08, 2007

I've been meaning to start posting an aggregation of notable Commerce Server blog and forum postings.  I've never found that Google or Technorati do a great job of searching or summarizing blog topic discussions, so I'll try to do my best to relate what's going on in the Commerce Server community.  Mostly, these posts come from people listed in my Commerce Server blogroll, as well as the Commerce Server forums.

Please let me know if I've missed any notable Commerce Server posts or announcements!

Blogs (posts you should read)

07/01/2007 - Ryan Donovan [MSFT] - The Best is Yet To Come - Ryan reflects on the long-term product roadmap for Commerce Server.

07/01/2007 - Max Akbar - Looking for Commerce Server Training? - Max announces the completion of his first video training tutorial.  This tutorial focuses on the Commerce Server Catalog System.  I  have had the opportunity to review this tutorial, and I can attest that it is great stuff!  Definitely worth the $$.

07/01/2007 - Jeff Lynch [MVP] - E-Commerce News: Commerce Server 2007 & BizTalk Server 2006 R2 - Jeff posts a summary of some news in the CS world, and explains that BizTalk Server 2006 R2 and Commerce Server 2006 play well together.

07/02/2007 - Nick Mayhew [MSFT] - Commerce Server SP1 available and introducing the Commerce Server team blog - A little behind the times <grin>, Nick announced SP1 and the CS team blog.

07/06/2007 - Ryan Donovan [MSFT] - http://blogs.msdn.com/rdonovan/archive/2007/07/06/speaking-in-denver-wpc-next-week.aspx - Ryan mentions that he'll be here in Denver to talk at the WPC (damn, I can't make it!) about Commerce Server.

07/06/2007 - CS Team Blog [MSFT] - http://blogs.msdn.com/commerce/archive/2007/07/06/commerce-server-worldwide-partner-conference-in-denver-next-week.aspx - The CS Team (via Ryan) discusses their presentation at the Worldwide Partner Conference in Denver, CO.

Forum / Newsgroup (answered questions that you might find useful to peruse)

07/02/2007 - StarterSite HelperClasses Source Code [Urgent!!!] - I've seen this one a lot.  People cannot find the source code for the Commerce Components, and only see the assembly (CommerceComponents.dll).  The source code is available in the CommerceComponentsSource.zip file, which is part of the Starter Site zip.

07/02/2007 - Data warehouse - Resolution to the error "Trans-Ctlg DTS task provider wrapper : Cannot Init timezone object"

07/03/2007 - InventoryQuantityDelta and the out of stock mystery - An explanation as to why orders occasionally come through for a product that is out-of-stock.

07/05/2007 - 'Out of Memory Exception' using CatalogWebService for Inventory - Memory leak when using BizTalk 2006 and Commerce Server 2007 Catalog adapter to update inventory.  The leak is fixed with SP1, and can be resolved by restarting the Catalog Import Host COM+ service.

07/06/2007 - Password Strength - Decreasing the password strength required for a profile.

07/06/2007 - Failed to delete inventory skus / Execute permission denied on 'inv_ValidateSkuType' - A potential bug in the inventory system.

posted on Sunday, July 08, 2007 3:18:35 PM (Central Standard Time, UTC-06:00)  #    Comments [0] Trackback
# Thursday, July 05, 2007

I've started working on a project that integrates into Great Plains 9.0 using eConnect.  Not having done this using BizTalk Server 2006, I thought I'd document some of my observations and the steps I took.

To begin, I was able to get eConnect for GP (en_econnect_for_gp.zip) through my MSDN subscription.  If you have Great Plains 9.0, then you should be able to get your hands on this package.  When you unzip the package, you'll notice two files:

  • eConnectInstallAdminGuide.pdf - This is a great document that, if you are going to interact and develop with eConnect, I highly encourage you to read.  There are three parts: eConnect Basics, Installation, and Administration.  At the very least, developers should reach the first part, which includes an overview and architecture chapter.
  • Microsoft_Business_Solutions_eConnect.msi - This is the actual installation file.  Note: it contains MUCH more than just the BizTalk Server adapters.

After reading through the eConnect guide, I ran the installation MSI file.  A few highlights / comments:

  • I chose the "Custom" setup.  I never choose Standard or Complete.
  • My sole purpose at this point is to install the BizTalk adapter with the eConnect schemas; follow the administration guide, instead of this post, if you're setting up for production.
  • There are a lot of features, including:
    • BizTalk Components - both 2004 and 2006
    • Business Objects - installs the Business Objects into SQL Server
    • COM+ Components - installs COM components and .NET assemblies
    • eConnect Incoming Service - incoming eConnect Win32 service
    • eConnect Outgoing Service - outgoing eConnect Win32 service
    • eConnect Replication Service - eConnect Win32 replication service
    • eConnect Help - help
    • eConnect Samples - .NET and VB6 examples
    • Queue Control - view documents in MSMQ queue
    • Schemas - eConnect XSD and XDR schemas
  • Not knowing any better, I decided to only include the following features:
    • BizTalk Components
    • eConnect Help
    • eConnect Samples
    • Schemas

Once you have installed eConnect 9.0, you must still install the adapters for BizTalk Server.  Browser to the following folder: C:\Program Files\Microsoft Great Plains\eConnect9\BizTalk\BizTalk 2006\ (obviously, choose \BizTalk 2004\ if  you are using 2004).  From here you can run the BTS_eConnectAdapter.msi file and install the BizTalk Server 2006 adapter.

Once the installation for the adapter is complete, make sure you update your platform settings and add the adapter.  Follow these steps:

  1. Open the BizTalk Server 2006 Administration Console.
  2. Go to BizTalk Group -> Platform Settings -> Adapters.
  3. Right-click the Adapters folder, and select New -> Adapter.
  4. Enter a name for the adapter (e.g. eConnect) and select the "Dynamics GP eConnect" adapter from the adapter list.
  5. Click OK.  Make sure you restart your host instances.

Pretty simple and straightforward.

Now, browse to the following folder: C:\Program Files\Microsoft Great Plains\eConnect9\XML Schemas\.  Here you will notice all the XDR and XSD schemas.  In particular, the "Incoming XSD Individual Schemas" and the "Incoming XSD Schemas" folders.  The first has every schema in an individual XSD, whereas the second has them all in an eConnect.xsd file.  Depending on you circumstances, you can import these schemas into your BizTalk projects, so that you can utilize them in Orchestrations, maps, or whatever else you choose.  Additionally, once you deploy your projects assemblies, the schemas will be available within your BizTalk solution.

And finally, one other folder you may want to look at is C:\Program Files\Microsoft Great Plains\eConnect9\XML Sample Documents\Incoming\.  This folder has a number of sample XML documents that you can use for testing.  VERY useful stuff here!

Just a few things I've learned and observed while getting all this configured.  I'm sure I'll post more as I continue to use the eConnect adapter.

I hope this helps!

posted on Thursday, July 05, 2007 11:34:14 AM (Central Standard Time, UTC-06:00)  #    Comments [1] Trackback

I just recently started a new BizTalk Server 2006 project in which I created a custom pipeline component to assist in the compression and decompression of messages.  The plan is to have a receive location monitor a folder via the File adapter, and when a zip file is uploaded via FTP from a 3rrd party process, the receive location will use the custom pipeline component , via a receive pipeline, to decompress the file and publish the message to the MessageBox.  Pretty slick!

See the WingtipToys solution in the SDK folder for a great heads-start to creating a compression / decompression pipeline component.  You can find this solution in the following folder: C:\Program Files\Microsoft BizTalk Server 2006\SDK\Scenarios\PM\.

So, to get this rolling, I added a C# project to my BizTalk solution.  There are few things I configure on these C# library projects, as I need to utilize the output assembly in my Pipelines BizTalk project.

  1. Set the build output path to the following location (for the Debug configuration): C:\Program Files\Microsoft BizTalk Server 2006\Pipeline Components\.  This way, every time the project is built, the debug assembly is placed in the Pipeline Components folder.  From here, you can then reference the pipeline component appropriate through both Visual Studio 2005 (for your receive pipeline) and when you deploy it to BizTalk Server.
  2. Uncheck the "Build" checkbox, for the C# library project, in the Solution Configuration.  This way, when you rebuild or deploy the solution, the C# library project isn't also rebuilt.  The reason is that you will most likely get errors or warnings indicating that the file is in use, and cannot be rebuilt.  This is fine - you can simply do this manually.

Having made these changes, you now have a pretty flexible setup for moving forward.

When you reference the custom pipeline component assembly, you'll want to reference the assembly that has been built in the C:\Program Files\Microsoft BizTalk Server 2006\Pipeline Components\ folder.  This way, when you deploy your receive or send pipelines, BizTalk will continue to reference the same assembly.

However, having referenced this assembly, you will see these warnings the next time you build your solution:

------ Build started: Project: BizTalkApp, Configuration: Development .NET ------
Updating references...
The dependency 'Microsoft.BizTalk.Tracing' could not be found.
The dependency 'Microsoft.BizTalk.Bam.EventObservation' could not be found.
The dependency 'Microsoft.BizTalk.Streaming' could not be found.
The dependency 'Microsoft.BizTalk.XPathReader' could not be found.
Performing main compilation...

While these warnings are harmless, they are annoying.  In order to "suppress" these warnings, select the referenced custom pipeline component assembly, and set Copy Local from True to False.  Once you do this, these warnings will disappear.

I hope this helps!

posted on Thursday, July 05, 2007 9:32:51 AM (Central Standard Time, UTC-06:00)  #    Comments [0] Trackback