How to migrate from CS2007 to WordPress, Movable Type (or any other blog engine, supports XML-RPC) with C#

Today we’ll speak about migration from community server 2007 to another blog engine, when you have no access to CS and/or other database.

Let’s set targets first:

  • You want to migrate all posts
  • You want to migrate all comments
  • You want to transfer all hosted images and media files
  • You should update all internal links

Looks complicated? not really. First of all, grab any XML-RPC framework (for example ). Then create a proxy to CS2007 – it uses Metablog API. You can see all defined methods by accessing /blogs/metablog.ashx

[XmlRpcUrl("http://blogs.microsoft.co.il/blogs/metablog.ashx")] public interface ICommunityServer {    [XmlRpcMethod("blogger.deletePost")]    bool deletePost(string appKey, string postid, string username, string password, bool publish);

    [XmlRpcMethod("blogger.getUsersBlogs")]    BlogInfo[] getUsersBlogs(string appKey, string username, string password);

   [XmlRpcMethod("metaWeblog.editPost")]    bool editPost(string postid, string username, string password, Post post, bool publish);

   [XmlRpcMethod("metaWeblog.getCategories")]    CategoryInfo[] getCategories(string blogid, string username, string password);

   [XmlRpcMethod("metaWeblog.getPost")]    Post getPost(string postid, string username, string password);

   [XmlRpcMethod("metaWeblog.getRecentPosts")]    Post[] getRecentPosts(string blogid, string username, string password, int numberOfPosts);

   [XmlRpcMethod("metaWeblog.newPost")]    string newPost(string blogid, string username, string password, Post post, Boolean publish); }

As you can see, you can read and update posts, but there is no methods for comments. What to do? Community Server supports comments rss syndication. So why not to use it? Also, if you want to fix links later, save all retrieved urls

posts = csblog.getRecentPosts(csBlogid, csUsername, csPassword, toFetch); for (int i = 0; i < posts.Length; i++) { if (!_postsIndex.ContainsKey(posts[i].link)) _postsIndex.Add(posts[i].link, string.Empty); … var commentsRSSURL = string.Concat(", posts[i].postid); _rssReader = new XmlTextReader(commentsRSSURL);

while (_rssReader.Read) {                   _rssReader.MoveToContent;                   if (_rssReader.NodeType == XmlNodeType.Element) {                      if (_rssReader.Name == "pubDate") { date = DateTime.Parse(_rssReader.ReadElementContentAsString); }                      if (_rssReader.Name == "dc:creator") { author = _rssReader.ReadElementContentAsString; }                      if (_rssReader.Name == "description") {                         if (!shouldSkip) {                            content = _rssReader.ReadElementContentAsString;                            comments.Add(new Comment {                               author = author,                               date_created_gmt = date,                               status = true

As you can see, now you have all comments. Next step is to detect and reupload all images to the new host.

private const string imgRX = "<img[^>]*src=\"?([^\"]*)\"?([^>]*alt=\"?([^\"]*)\"?)?[^>]*>"; var matches = Regex.Matches(posts[i].description, imgRX);                Console.WriteLine("Fixing {0} images...", matches.Count);                for (int j = 0; j < matches.Count; j++) {                   Console.WriteLine("Retriving image #{0}", j);                   var url = matches[j].Groups[1].Value;                   if (url.Contains(baseURL)) {                      try {                         var data = wc.DownloadData(url);                         Console.WriteLine("Uploading image #{0}", j);                         var uf = newblog.uploadFile(newblogid, newUsername, newPassword, new MediaObject {                            bits = data,                            name = matches[j].Groups[1].Value.Substring(matches[j].Groups[1].Value.LastIndexOf('/') + 1)                         });                         posts[i].description = posts[i].description.Replace(url, uf.url);                      } catch { }                   }                }

Now all images are stored in the new location and all image links are updated within stored posts. Next step is to upload all posts to the new location. CS stores tags as categories, which is wrong. Why? Because categories can be hierarchical, while tags cannot. So we have to convert all categories within retrieved posts into real tags. After it we can post everything

for (int i = posts.Length - 1; i >= 0; i--) {            posts[i].mt_keywords = string.Join(",", posts[i].categories);            var pid = newblog.newPost(newblogid, newUsername, newPassword, posts[i], true);            foreach (var comment in posts[i].comments) {               try {                  var cid = newblog.newComment(newblogid, newUsername, newPassword, pid, comment);               } catch { }            }

Now we have to update all internal links within new locations. For this we should grab all posts back to learn new URLs.

var newPosts = newblog.getRecentPosts(newblogid, newUsername, newPassword, toFetch);          for (int i = 0; i < newPosts.Length; i++) {             foreach (var pi in _postsIndex) {                if (newPosts[i].description.Contains(pi.Key)) newPosts[i].description = newPosts[i].description.Replace(string.Concat(baseURL,pi.Key), pi.Value);             }              wpblog.editPost((string)newPosts[i].postid, newUsername, newPassword, newPosts[i], true);             if (!refereces.ContainsKey(newPosts[i].link)) refereces.Add(newPosts[i].link, posts[i].link);

         }

We done. Last, but not the least, is to update old posts with new URL to make visitors able to forward into new location.

csposts = csblog.getRecentPosts(csBlogid, csUsername, csPassword, toFetch);             for(int i=0;i< csposts.Length;i++) {                if (_postsIndex.ContainsKey(csposts[i].link)) {                   string write = string.Format("

[This blog was migrated. You will not be able to comment here.
The new URL of this post is <a href=\"{0}\">{0}]


", _postsIndex[csposts[i].link]);                   csposts[i].description = string.Concat(write, csposts[i].description);                   csblog.editPost((string)csposts[i].postid, csUsername, csPassword, csposts[i], true);                   Console.WriteLine("Post {0} was updated",i);                }             }

Have a nice day and be good people!

This article is part of the GWB Archives. Original Author: tamir

New on Geeks with Blogs

  • We Won The One Award I Actually Care About

    Full Scale made the Inc. 5000 for the fifth year straight, the 12th listing across my three companies. Here is why the one award you cannot buy is worth stopping for.

  • Your Customers Build the Features Now

    I let a tool I liked sit dead for a year rather than build the features I wanted. An MCP server meant I never had to, and your customers can do the same to your product.

  • Get the Size of a Directory in Linux the Easy Way

    du -sh for the quick answer, ncdu for the cleanup, df for the disk itself: every command for checking directory size in Linux, plus why du and df never agree.

  • Vim Search and Replace: The Ultimate Guide

    One :%s command replaces every match in a file before a find dialog would even open. The Vim substitute patterns worth the muscle memory: flags, ranges, capture groups, and multi-file edits.