BlogEngine.Net has served us well as a blogging platform for a good few years. However, it is no longer under active support, so it is time to move on, too much risk of future security issues to ignore the lack of support. After a bit of thought we decided on WordPress as a replacement. OK this has had its own history of problems, but it has an active community and is well supported and in the Azure Marketplace.

The process to move content from BlogEngine to WordPress requires a few steps, and the available community documentation is a bit out of date, mostly due to change in multi-blog support in BlogEngine.NET. So these are the steps I followeded

Steps

Setting up a WordPress Network

The first step is to create a standard WordPress App Service site on Azure. I used the option to create a MySQL DB in the App Service instance.

Once this was created I needed to make some WordPress setting changes to enable multi blog (network) usage.

  • First run the standard WordPress setup to create a single site

  • Apply any upgrades available

  • Next I needed to update the settings, this involves editing text files on the instance so I used FTP (Filezilla) and a text editor to edit the required files

  • First I needed to update the php execute timeout to give the content import enough time to run for our larger blogs, this means a custom config file. (Actually not sure this is 100% required as the import retry, as discussed below, would probably have been enough)

  • In the Azure portal set the AppSetting PHP_INI_SCAN_DIR to D:homesitewwwroot

  • In the root of the site create a text file phpconfig.ini

  • In the file set max_execution_time = 600 and save and upload the file

  • As the linked post notes you can check these edit have worked checking with the phpinfo()  function on a PHP page on the site.

  • Next create the WordPress network, edit wp-config.php and add  define( ‘WP_ALLOW_MULTISITE’, true );

  • Once the site reloads you can now create the network, as the wizard completes it gives instructions to edit the wp-config.php and web.config, I found note that web.config settings the documentation/wizard gives are missing one element, I needed to use file following

<?xml version="1.0" encoding="UTF-8"?>  
<configuration>
    <system.webServer>
        <rewrite>
            <rules>
                <rule name="WordPress Rule 1 Identical" stopProcessing="true"> <match url="^index.php$" ignoreCase="false" />
                    <action type="None" />
                </rule>
                <rule name="WordPress Rule 3 Identical" stopProcessing="true">
                    <match url="^(\[\_0-9a-zA-Z-\]+/)?wp-admin$" ignoreCase="false" />
                    <action type="Redirect" url="{R:1}wp-admin/" redirectType="Permanent" />
                </rule>
                <rule name="WordPress Rule 4 Identical" stopProcessing="true">
                    <match url="^" ignoreCase="false" />
                    <conditions logicalGrouping="MatchAny">
                        <add input="{REQUEST\_FILENAME}" matchType="IsFile" ignoreCase="false" />
                        <add input="{REQUEST\_FILENAME}" matchType="IsDirectory" ignoreCase="false" />
                    </conditions>
                    <action type="None" />
                </rule>
                <rule name="WordPress Rule 5 R2" stopProcessing="true">
                    <match url="^(\[\_0-9a-zA-Z-\]+/)?(wp-(content|admin|includes).\*)" ignoreCase="false" />
                    <action type="Rewrite" url="{R:2}" />
                </rule>
                <rule name="WordPress Rule 6 Shorter" stopProcessing="true">
                    <match url="^(\[\_0-9a-zA-Z-\]+/)?(.\*.php)$" ignoreCase="false" />
                    <action type="Rewrite" url="{R:2}" />
                </rule>
                <rule name="WordPress Rule 7 Identical" stopProcessing="true">
                    <match url="." ignoreCase="false" />
                    <action type="Rewrite" url="index.php" />
                </rule>
            </rules>
        </rewrite>
    </system.webServer>
</configuration> 

Once this all done you should have a WordPress network and can start to import the old BlogEngine content into sub sites

Create Sites

Next step is to login as a WordPress network admin (the original account you created in the wizard) and create a sub site.

When you do this a numeric folder will be created in the form /site/wwwroot/wp-content/uploads/sites/123 , make a note of this number as you need it for fixing the import content in the next step, you might need to create a test post with an image to force this folder creation.

Import the BlogEngine Contents

Next we needed to import the content our BlogEngine.NET. This is done using the basic process document in this blog post, but it needs a few updates due to the posts age and the fact we had a multi-blog setup.

The only export option in BlogEbgine.NET is BlogML and if you are running in multisite mode this appears to be broken. The fix is to edit  /admin/themes/standard/sidebar.cshtml around line 90 to remove the if test logic blocking showing the export options in multisite mode. Once this is done you can log into to a sub blog and export its contents as a BlogML XML file.

Note: This is not without its problems, when you are logged into the sub site as an admin and select the settings>advanced>export option you get an error as it tries to load the page http://yoursite/blogml.axd, this is due to the simple hack used to enable the export features, you need to manually edit this URL to http://yoursite/[blogname]/blogml.axd and the export works OK

You now move the media files associated with the blog posts. The only difference from moving a single blog setup is you need to place them under the /site/wwwroot/wp-content/uploads/sites/123 previously created. I suggest creating a folder for all the historic post media e.g. /site/wwwroot/wp-content/uploads/sites/123/historic and FTPing up all you old images from blogengine/App_Data/blogs/[name]/files

I next hit the major issues and that is that the BlogML plugin (which you need to install as the WordPress network administrator) is 7 years old, and won’t activate on current versions of WordPress. The issue is changes in the PHP language. The fix is to use the edit option for the plugin and replace all the references to break $parseBlock to break 1 in the file xpath.class.php. Once this is done the plugin activates at the network level, so can be used in each sub site

But before we try the import we need to edit exported BlogML file as the blog post says. However, we can improve on the documented process. The blog post says the tags and categories are lost in the import process, this is true for tags, but it is possible to fix the categories. To do this, and fix the images paths, I have written some PowerShell to do the required updates, it is ugly but works opening the file as text and XML separately

 param  
  (  
     $filein = "C:UsersfezDownloadsBlogML (5).xml",  
     $outfile = "C:UsersfezDownloadsBlogML rfennell.xml",  
     $blogname = "rfennell",  
     $blogid = "2"  
  )  
  # Fix the image path  
\[string\]$document = Get-Content -Path $filein

write-output "Replacing name in $oldstring with BlogId $blogid"

$oldstring = "[http://blogs.blackmarble.co.uk/blogs/](http://blogs.blackmarble.co.uk/blogs/)$blogname/image.axd?picture="  
$document =  $document -Replace \[regex\]::Escape($oldstring) ,"/wp-content/uploads/sites/$blogid/historic/"

#seems to have image URLs with missing slashs in path, take the chance to fix them  
$oldstring = "[http://blogs.blackmarble.co.uk/blogs/](http://blogs.blackmarble.co.uk/blogs/)$($blogname)image.axd?picture="  
$document =  $document -Replace \[regex\]::Escape($oldstring) ,"/wp-content/uploads/sites/$blogid/historic/"

Set-Content -Value $document -Path $outFile

\[xml\]$XmlDocument = Get-Content -Path $outFile

\# fix the categories block  
foreach ($item in $xmlDocument.blog.categories.category) {  
    try {  
        Write-output "Setting $($item.id) to $($item.title.'#cdata-section')"  
        # fix all the categories on the post  
        $XmlDocument = $XmlDocument.OuterXml.Replace($($item.id),$($item.title.'#cdata-section'))

        # fix the categories block  
        $item.id = $item.title.'#cdata-section'  
    } catch {}  
  }

$xmlDocument.Save($outfile)  

Make sure the parameters are correct for your blog export and target site then process you BlogEngine Export.

You can now login to your newly create WordPress subsite and using the Tools/Import option to run the BlogML wizard. You are prompted to remap the authors of the posts as needed. I unified them to the site owner if needed, the started the import. Now we did edit of the PHP timeout, but I found that for my largest 7Mb export file I still got Error 500 timeouts. The good news is that you can just rerun the import (maybe a few time) and it is clever enough to pickup where it left off and will eventually finish, with no duplications. Now there maybe a different timeout you need to set but I did not find it.

You should now have imported post content into you sub site. Unfortunately, you will have to handle static pages manually.  

Finishing Up

You are now in realm of WordPress, so you can add users, plug-ins and themes as needed to style your set of blogs.

One I found very useful was the WDS Multisite Aggregator which allowed the root site to be an aggregation of all the sub sites, just the same as I had on BlogEngine.NET multisite.

Also as I was running on Azure I needed some special handling for email to use SMTP with the plug-in WP Mail. Once this change was done it could configure the network root site’s email to allow user password resets. For comments each individual sub sites email needs configuring.

I had had concerns over links in old post (as the URL structure had changed), but WordPress seems to sort most of this out, the remained were sorted with redirection rules in the web.config.

Conclusion

This whole process took some experimentation, but once done the rest was a ‘handle turning process’. Lets hope WordPress works as well for us as BlogEngine.NET did in the past.