Monthly Archives: February 2012

7:21 PM 2/26/2012

I recently ran the spider at www.xml-sitemaps.com against www.ardamis.com and it returned a list of URLs that included a few pages with some suspicious-looking parameters. This is the second time I’ve come across these URLs, so I decided to document what was going on. The first time, I just cleared the cache, spidered the site to preload the cache, and confirmed that the spider didn’t encounter the pages. And then I forgot all about it. But now I’m mad.

Normally, a URL list for a WordPress site includes the various pages of the site, like so:

//ardamis.com/
//ardamis.com/page/2/
//ardamis.com/page/3/

But in the suspicious URL list, there are additional URLs for the pages directly off of the site’s root.

//ardamis.com/
//ardamis.com/?option=com_google&controller=..%2F%2F..%2F%2F..%2F%2F..%2F%2F..%2F%2F..%2F%2F..%2F%2F..%2F%2F%2Fproc%2Fself%2Fenviron%0000
//ardamis.com/page/2/
//ardamis.com/page/2/?option=com_google&controller=..%2F%2F..%2F%2F..%2F%2F..%2F%2F..%2F%2F..%2F%2F..%2F%2F..%2F%2F%2Fproc%2Fself%2Fenviron%0000
//ardamis.com/page/3/
//ardamis.com/page/3/?option=com_google&controller=..%2F%2F..%2F%2F..%2F%2F..%2F%2F..%2F%2F..%2F%2F..%2F%2F..%2F%2F%2Fproc%2Fself%2Fenviron%0000

This occurs only for the pagination of the main site’s pages. I did not find URLs containing the parameter ?option=com_google&controller= for any pages that exist under a category or tag, but that also use the /page/2/ convention.

The parameter is the urlencoded version of the text:

?option=com_google&controller=..//..//..//..//..//..//..//..///proc/self/environ00

Exploration

I compared the source code of the pages at the clean URLs vs that of the pages at the bad URLs and found that there was a difference in the pagination code generated by the WP-Paginate plugin.

The good pages had normal-looking pagination links.

<div class="navigation">
<ol class="wp-paginate">
<li><span class="title">Navigation:</span></li>
<li><a href="//ardamis.com/page/2/" class="prev">&laquo;</a></li>
<li><a href='//ardamis.com/' title='1' class='page'>1</a></li>
<li><a href='//ardamis.com/page/2/' title='2' class='page'>2</a></li>
<li><span class='page current'>3</span></li>
<li><a href='//ardamis.com/page/4/' title='4' class='page'>4</a></li>
<li><a href='//ardamis.com/page/5/' title='5' class='page'>5</a></li>
<li><a href='//ardamis.com/page/6/' title='6' class='page'>6</a></li>
<li><a href='//ardamis.com/page/7/' title='7' class='page'>7</a></li>
<li><span class='gap'>...</span></li>
<li><a href='//ardamis.com/page/17/' title='17' class='page'>17</a></li>
<li><a href="//ardamis.com/page/4/" class="next">&raquo;</a></li>
</ol>
</div>    

The bad pages had the suspicious URLs, but were otherwise identical. Other than the URLs in the navigation, there was nothing alarming about the HTML on the bad pages.

I downloaded the entire site and ran a malware scan against the files, which turned up nothing. I also did some full-text searching of the files for the usual base64 decode eval type stuff, but nothing was found. I searched through the tables in my database, but didn’t see any instances of com_google or proc or environ that I could connect to the suspicious URLs.

Google it

Google has turned up a few good links about this problem, including:

  1. http://www.exploitsdownload.com/search/com_/36 – AntiSecurity/Joomla Component Contact Us Google Map com_google Local File Inclusion Vulnerability
  2. http://forums.oscommerce.com/topic/369813-silly-hacker/ – “On a poorly-secured LAMP stack, that would read out your server’s environment variables. That is one step in a process that would grant the hacker root access to your box. Be thankful it’s not working. Hacker is a bad term for this. This is more on the Script Kiddie level.”

    The poster also provided a few lines of code for blocking these URLs in an .htaccess file.

    # Block another hacker
    RewriteCond %{QUERY_STRING} ^(.*)/self/(.*)$ [NC]
    RewriteRule ^.* - [F]
    
  3. http://forums.oscommerce.com/topic/369813-silly-hacker/ – “This was trying for Local File Inclusion vulnerabilities via the Joomla/Mambo script.”
  4. http://core.trac.wordpress.org/ticket/14556 – a bug ticket submitted to WordPress over a year earlier identifying a security hole if the function that generates the pagination isn’t wrapped in a url_esc function that sanitizes the URL. WP-Paginate’s author submits a comment to the thread, and the plugin does use url_esc.

So, what would evidence of an old Joomla exploit be doing on my WordPress site? And what is happening within the WP-Paginate plugin to cause these parameters to appear?

Plugins

It seemed prudent to take a closer look at two of the plugins used on the site.

Ardamis uses the WP-Paginate plugin. The business of generating the /page/2/, /page/3/ URLs is a native WordPress function, so it’s strange to see how those URLs become subject to some sort of injection by way of the WP-Paginate plugin. I tried passing a nonsense parameter in a URL (//ardamis.com/page/3/?foobar) and confirmed that the navigation links created by WP-Paginate contained that ?foobar parameter within each link. This happens on category pages, too. This behavior of adding any parameters passed in the URL to the links it is writing into the page, even if they are urlencoded, is certainly unsettling.

The site also uses the WP Super Cache plugin. While this plugin seems to have been acting up lately, in that it’s not reliably preloading the cache, I can’t make a connection between it and the problem. I also downloaded the cache folder and didn’t see cached copies of these URLs. I turned off caching in WP Super Cache but left the plugin activated, cleared the cache, and then sent the spider against the site again. This time, the URL list didn’t contain any of the bad URLs. Otherwise, the lists were identical. I re-enabled the plugin, attempted to preload the cache (it got through about 70 pages and then stopped), and then ran a few spiders against the site to finish up the preloading. I generated another URL list and the bad URLs didn’t appear in it, either.

A simple fix for the WP-Paginate behavior

The unwanted behavior of the WP-Paginate plugin can be corrected by changing a few lines of code to strip off the GET parameters from the URL. The lines to be changed all reference the function get_pagenum_link. I’m wrapping that function in the string tokenizing function strtok to strip the question mark and everything that follows.

The relevant snippets of the plugin are below.

			
$prevlink = ($this->type === 'posts')
? esc_url(strtok(get_pagenum_link($page - 1), '?'))
: get_comments_pagenum_link($page - 1);
$nextlink = ($this->type === 'posts')
? esc_url(strtok(get_pagenum_link($page + 1), '?'))
: get_comments_pagenum_link($page + 1);
			
function paginate_loop($start, $max, $page = 0) {
    $output = "";
    for ($i = $start; $i <= $max; $i++) {
        $p = ($this->type === 'posts') ? esc_url(strtok(get_pagenum_link($i), '?')) : get_comments_pagenum_link($i);
        $output .= ($page == intval($i))
        ? "<li><span class='page current'>$i</span></li>"
        : "<li><a href='$p' title='$i' class='page'>$i</a></li>";
    }
    return $output;
}

Once these changes are made, WP-Paginate will no longer insert any passed GET parameters into the links it’s writing into that page.

Bandaid

The change to the WP-Paginate plugin is what we tend to call a bandaid – it doesn’t fix the problem, it just suppresses the symptom.

I’ve found that once the site picks up the bad URLs, they can be temporarily cleaned by clearing the cache and then using a spider to recreate it. The only thing left to do is determine where they are coming from in the first place.

The facts

Let’s pause to review the facts.

  1. The http://www.xml-sitemaps.com spider sent against //ardamis.com discovers pages with odd parameters that shouldn’t be naturally occurring on the pages
  2. The behavior of the WP-Paginate plugin is to accept any parameters passed and tack them onto the URLs it is generating
  3. Deleting the cached pages created by WP Super Cache and respidering produces a clean list – the bad URLs are absent

So how is the spider finding pages with these bad URLs? How are they first getting added to a page on the site? It would seem likely that they are originating only on the home page, and the absence of the parameters on other pages that use pagination seems to support that theory.

An unsatisfying ending

Well, the day is over. I’ve added my updated WP-Paginate plugin to the site, so hopefully Ardamis has seen the last of the problem, but I’m deeply unsatisfied that I haven’t been able to get to the root cause. I’ve scoured the site and the database, and I can’t find any evidence of the URLs anywhere. If the bad URLs come back again, I’ll not be so quick to clean up the damage, and will instead try to preserve it long enough to make a determination as to their origin.

Update 07 April 2012: It’s happened again. When I spider the site, two pages have the com_google URL. These page have the code appended to the end of the URL created by the WordPress function cancel_comment_reply_link(). This function generates the anchor link in the comments area with an ID of cancel-comment-reply-link. This time, though, I see the hijacked URL used in the link even when I visit the clean URL of the page.

This code is somehow getting onto the site in such a way that it only shows up in the WP Super Cache’d pages. Clearing the cache and revisiting the page returns a clean page. My suspicion is that someone is visiting my pages with the com_google code as part of the URL. WordPress puts the code into a self-referencing link in the comment area. WP Super Cache then updates the cache with this page. I don’t think WordPress can help but work this way with nested comments, but WP Super Cache should know better than to create a cached page from anything but the content from the server.

In the end, because I wasn’t using nested comments to begin with, I chose to remove the block of code that was inserting the link from my theme’s comments.php file.

    <div class="cancel_comment_reply">
        <small><?php cancel_comment_reply_link(); ?></small>
    </div>

I expect that this will be the last time I find this type of exploit on ardamis.com, as I don’t think there is any other mechanism that will echo out on the page the contents of a parameter passed in the URL.

Am I twelve years old? I must be, for there is no other explanation why these Rally Fighter cars by Local Motors make me drool so much. I want like five of them.

The Rally Fighter is the first production car from the new American car company, Local Motors. LM builds its cars in regional micro-factories, breaking ground with advanced small-volume manufacturing and amazing ownership experiences.

Rally Fighter - Sunfire

Rally Fighter - Sunfire

Rally Fighter - Drool

Rally Fighter - Drool

Rally Fighter - Grey Beauty

Rally Fighter - Grey Beauty

Rally Fighter - Dirt Destroyer

Rally Fighter - Dirt Destroyer

Rally Fighter - Orange Stripe

Rally Fighter - Orange Stripe

Rally Fighter - Sharp

Rally Fighter - Sharp

Rally Fighter - Rad Red

Rally Fighter - Rad Red

And you help build your own car, and you get to bring a friend to help.

With your purchase you also buy a Local Motors “Build Experience”. The Local Motors Build Experience is a first for the automotive industry. You join Local Motors experts in the Phoenix Micro-Factory for two 3-day weekends to build your very own Rally Fighter.

The last of the V8 Interceptors has some new competition.

Google, what is up with your Google+ profile badges and your Google +1 buttons being different sizes?

There is a neat wizard for creating the code snippet for a Google+ profile badge. We get to pick an image size from one of four options, if we want the image to be hosted on Google’s server. (And why wouldn’t we?)

Small (16px)
Standard (32px)
Medium (44px)
Tall (64px)

OK, those are pretty acceptable, I guess, but I would really like to see something in the 20 to 24 pixel range.

What about the wizard for the Google +1 button?

Small (15px)
Standard (24px)
Medium (20px)
Tall (60px)

Wait, what?!? You’re using the same labeling, but the sizes are totally different. Not only that, but none of the sizes are shared between the two buttons. The Standard profile button is 75% larger than the +1 button. Grrr.

The Google +1 button wizard has more options, including a field where you may specify the path to an image, and the button itself is more dynamic. The profile button wizard is very basic, but it is easy to edit the HTML output to use any image. If forced to make my own image and host it, coming up with a custom profile button is clearly less involved.

As a third option, the configuration tool for the Google+ brand page badge makes it possible to essentially combine the functions of both buttons. While the badge takes up a large chunk of real estate, it is probably the best choice, as it looks good, has some bold colors, and adds some extra Google+ stuff (thumbnail images, a counter) that you can’t get from the basic generator.

My Google Reader feed is primarily Mashable, SEOmoz, and Smashing Magazine, with a few other sources that tend to come and go. Ideally, I’d like to come up with a way of displaying my starred items on a dedicated page here at ardamis.com, but until then, I guess I’ll just have to do it the old fashioned way.

Here are a few SEO articles that really are worth reading.

seomoz.org: Find Your Site’s Biggest Technical Flaws in 60 Minutes is a collection of tools and methods suitable for the non-technical site owner who wants to be a little more self-sufficient when it comes to identifying crawling, indexing and potential Panda-threatening issues.

seomoz.org: A New Way of Looking at Ranking Factors includes the really neat Periodic Table of SEO Ranking Factors and some explanation of the thought process behind it, and a short video on SEO basics.

searchengineland.com: The Periodic Table of SEO Ranking Factors is the original table, at full size.

seomoz.org: Set It and Forget It SEO: Chasing the Elusive Passive SEO Dream is a terrific article, both funny and technical, with two scripts to improve your tracking of inbound links and your site’s handling of requests that would normally 404.

seomoz.org: 12 Creative Design Elements Inspiring the Next Generation of UX is a randfish article with some really neat design examples.

sem-group.net: How To Optimize 7 Popular Social Media Profiles For SEO would be a good article to share with someone responsible for setting up profiles, but who doesn’t have a great deal of familiarity with things like H1 tags and nofollow links and their importance.

www.distilled.net: 7 Technical SEO Wins for Web Developers identifies areas where the developer, rather than the designer or content writer, can make improvements to a page’s SEO potential.

smashingmagazine.com: Clear Indications That It’s Time To Redesign isn’t really an SEO article at all, but it could be helpful when making an argument to change the site with the intention of improving bounce rate or other things related to visitor satisfaction.

smashingmagazine.com: Introduction To URL Rewriting does a good job of explaining what URL rewriting is and why you might want to do it.

Techcrunch just posted a story about BuiltWith – a startup that reports on the technology behind the web site. I’ve written before about ways to ID the server, the scripting language, and the CMS technology behind a site, but BuiltWith goes further and provides analysis of that data.

The BuiltWith report for ardamis.com knows that the site is running WordPress (no surprise there) on an Apache server, with the WP Super Cache plugin and Google Analytics and Google Plus One.

For each item it recognizes that a site is using, WordPress as the CMS, for example, it provides statistical data on the number of sites on the web also using that technology, along with trend information showing whether its use is increasing or decreasing. It’s really pretty sad to see that Apache use continues to steadily trend down, even while it continues to account for the majority of web servers. HTML5 as a Doctype is on the rise, though, along with the use of microdata.

BuiltWith can also tell what other sites are using that technology, but this information is a paid service, and an expensive one, too.

I’m in the middle of troubleshooting a printing problem that has arisen with our in-development Windows 7 image. We’re running 64-bit Windows 7 Enterprise with Office 2010 and using the HP Universal Print Driver for Windows PCL6 version 5.4.0 dated 1 Dec 2011 (the current version). The printer driver is installed on a Windows server using default settings and the printer connections on the workstations are created either as per-machine connections by running printui.exe /ga or as per-user connections by running the Find Printers wizard in an Office application. The printers themselves are HP 4250n and HP P4015 models with relatively up-to-date firmware.

The problem is that certain print jobs produce many pages of apparent gibberish instead of the intended file or email message. The gibberish pages begin like this:

"
 @PJL SET JOBATTR="JobAcct8=USERNAME"
                                     @PJL SET JOBATTR="JobAcct9="
                                                                 @PJL SET RET=OFF

I’ve done some research into the lines beginning @PJL, and my understanding is that PJL (Printer Job Language) commands are part of the standard job header output from the Universal Printer Driver, and that when everything is working normally, they are processed by the printer as instructions instead of printed as text.

For more reading about PJL commands, I can recommend the page at: http://www.sxlist.com/techref/language/pcl/lj1686.htm

Almost immediately, I was able to rule out the per-machine connections as being the cause, as the problem also occurred on per-user connections. The same files that printed problematically to the networked HP printers printed normally to locally-installed printers using non-UPD PCL 6 drivers. It seemed logical to pursue this as a driver-related problem.

What is PCL 6

It’s probably worth pausing here for a bit of explanation of PCL 6.

The Enhanced PCL XL or PCL6 driver that is included with the HP LaserJet printers provides enhanced WYSIWYG and enhanced performance with application support over the Standard (PCL5e) driver. PCL XL is a new page description language by HP that is part of PCL6 and is closer to GDI, which many applications use. Less translation takes place by the driver, which means increased WYSIWYG capabilities and better performance with applications that support escapes implemented by the Enhanced driver. The output from the Enhanced (PCL XL) driver may not be the same as the output from the Standard driver. If the output is not as expected, choose the Standard (PCL5e) driver instead.
What is the Enhanced PCL XL or PCL6 Driver?

The part that catches my eye is “better performance with applications that support escapes implemented by the Enhanced driver”. Are we encountering applications that do not support escapes?

Eliminating possible causes

Possible causes of the PJL commands being output as text include the driver not prefixing the PJL statements (at the beginning of each job) with a Universal Exit Language (UEL) escape sequence. (http://www.tek-tips.com/viewthread.cfm?qid=1618494)

To rule this out, one can use the “print to file” option in the print dialog box to produce a file that contains the instructions that would be sent to the printer.

Choose File | Print in your application, then check the “Print to file” box in the print dialog box. (In Office 2010, the Print Options button under the available printers menu displays the print dialog box.) Choose a name for the .prn file and save it somewhere, then open the resultant *.prn file in something that displays escape characters, such as Notepad++ (or even Notepad). The first character should be an escape character, and the first line of text will begin something like this:

{ESC}%-12345X

If the PJL initialization command looks correct, it’s possible that the printer is not properly configured to accept PJL commands. Older printers may not be PJL-aware, but I knew our printers to work fine with older 32-bit HP UPDs installed on our Windows XP machines. The ‘Personality’ attribute on HP printers can be checked by going to the printer’s web admin panel and browsing to Settings | Configure Device | System Setup. Setting the Personality to PS is probably going to cause problems, but either Auto or PCL should work. I confirmed that our printers were set to Auto, further ruling out the printers themselves as the cause of the problem.

I next looked at disabling the advanced features of the driver (a little skeptically, I’ll admit). This can be done by going into the printer’s properties and unchecking the “Enable advanced printing features” box on the Advanced tab. (http://h30499.www3.hp.com/t5/Print-Servers-Network-Storage/12345X-PJL-Printing-on-Dot-Matrix-Printers/td-p/1132391) I was curious about how this affected the job sent to the printer – would the entire series of JPL commands be removed?

To test, I unchecked the “Enable advanced printing features” and printed an email message to a *.prn file, then checked the box and printed the same email to a second *.prn file, then compared the two files. The only difference in the PJL commands was that “@PJL SET SEPARATORPAGE=OFF” was present with advanced printing features enabled, and absent with advanced printing features disabled.

I found the separator page line to be an interesting difference, as banner pages/separator pages had been suggested as a possible cause, but our drivers were not configured to print separator pages. (http://www.oasq.com/PJL-SET-JOBATTR-thread-252568-1-1.html)

So, that’s where the issue currently stands. I’m waiting to see if turning off advanced printing features has any effect. To be thorough, I need to test whether the UPD PS driver prints without error and whether the problem continues with a printer connected via TCP/IP and with a manually installed driver. I can also bring the printers up to the latest version of the firmware, although this would be a less satisfactory resolution, as we have a variety of printer models and not all of them have firmware updates available.

Update 17 Feb. 2012

The Application event log on the print server contains a number of errors, though we’re unsure of whether there is a direct correlation between the errors and attempts by Windows 7 users to print, or jobs in the spooler being processed, or any other activity.

Description:
Faulting application name: PrintIsolationHost.exe, version: 6.1.7600.16385, time stamp: 0x4a5bd3b1
Faulting module name: hpzuiwn7.dll, version: 0.3.7071.0, time stamp: 0x4a5bdfcb
Exception code: 0xc0000005
Fault offset: 0x00000000000d6971
Faulting process id: 0x900
Faulting application start time: 0x01ccea9860e17377
Faulting application path: C:\Windows\system32\PrintIsolationHost.exe
Faulting module path: C:\Windows\system32\spool\DRIVERS\x64\3\hpzuiwn7.dll
Report Id: 3eaa21e6-568c-11e1-b7a4-005056a50027

It certainly does look like the 64-bit HP driver is at fault here. More searching has turned up a number of reports of this error with HP’s UPD PCL6 driver, going back to 2010.

Because we have a small number of Windows 7 users, we’re removing the network printers from the Windows 7 machines temporarily, to see if the server stabilizes.

Update 21 Feb. 2012

We were able to take a closer look at the print server today. We searched the registry for hpzuiwn7.dll and noted the printers that had this DLL listed among the supporting files. Many, but not all, of the printers included this DLL. We also reviewed the printers in Print Management and made an odd discovery. There seemed to be two varieties of the model-specific PCL 6 driver in use: one is named “HP LaserJet 4250 PCL 6” and the other is “HP LaserJet 4250 PCL6“. The difference in the naming is that the later driver has a space between PCL 6. While most of the printers used the UPD, a handful were using one of the model-specific drivers. When we looked at the Additional Drivers, we found that one of them had only the 64-bit version available. I expect that only 32-bit workstations are printing to those printers, so I’m not sure how they even functioned, but it would seem that the next step would be to either add the matching 32-bit drivers for that model printer or change the assigned driver to UPD PCL 6. I suspect that we were not diligent enough about exactly matching the printer driver names (let alone the version numbers) when we were installing drivers on the server.

Update 1 Mar. 2012

After installing the missing 32-bit driver that complemented the stray 64-bit driver, all of the printing problems, including the error messages in the Application log on the server, have subsided.

In Windows, the Num Lock key state (on or off) can be set in the registry. It can be set in either of two locations, depending on when you wish to set the state, or both, if you want to force the state at boot and change it after the user logs in.

The Num Lock key state after logon can be set as a registry value for the current user in the key HKEY_CURRENT_USER\Control Panel\Keyboard. To set the state of the Num Lock key before logon (the key state will be set before the Ctrl+Alt+Del screen), change the registry value in the key HKEY_USERS\.Default\Control Panel\Keyboard. For both keys, the data for the InitialKeyboardIndicators value determines the state of the NumLock key. There are three possible settings.

0 = Num Lock is turned OFF after/at logon.
1 = Disable Num Lock.
2 = Num Lock is turned ON after/at logon.

A registry merge file that sets Num Lock on for the current user after logon is below.

Windows Registry Editor Version 5.00

[HKEY_CURRENT_USER\Control Panel\Keyboard]
"InitialKeyboardIndicators"="2"

It’s also (typically) possible to configure the Num Lock key state in the BIOS, although I personally don’t see the advantage of setting in the BIOS something that is configurable in Windows.

Note that users of full-sized keyboards with distinct Num keypads generally prefer to have the Num Lock key enabled and laptop users typically have Num Lock disabled. Much like the Function key is used to change the behavior of certain laptop keys, laptop keyboards without dedicated Num keypads map numbers to regular character keys. This could potentially cause some confusion, when users begin hitting the letter keys and get numbers instead.

In the VBScript example below, I’m using the Icacls.exe utility to assign modify permissions to the D:\Test folder for the user Oliver on the LOOMER domain (or local machine). The script includes as comments some good resources on the subject.

' http://support.microsoft.com/kb/919240
' http://technet.microsoft.com/en-us/magazine/2009.07.geekofalltrades.aspx
' http://timbolton.net/2010/06/23/icacls-changing-permissions-on-files-and-folders/

Dim strFolder, strUser, strDomain

strFolder = "D:\Test"
strUser = "Oliver"
strDomain = "LOOMER"

SetPermissions
	
Function SetPermissions()
	Dim intRunError, objShell, objFSO

	Set objShell = CreateObject("Wscript.Shell")
	Set objFSO = CreateObject("Scripting.FileSystemObject")
	If objFSO.FolderExists(strFolder) Then
		intRunError = objShell.Run("icacls " & strFolder & " /inheritance:r /grant:r " & strDomain &"\" & strUser & ":(OI)(CI)M ", 2, True)
		
		If intRunError <> 0 Then
			Wscript.Echo "Error assigning permissions for user " & strUser & " to folder " & strFolder
		End If
	Else
		Wscript.Echo "Error: folder " & strFolder & " does not exist"
	End If
End Function

This script is a work-in-progress. To be considered complete, I want it to be able to create multiple directories and assign them permissions. For extra credit, I want it to be able to accept as input a list of usernames from a text file and iterate through them, creating folders where necessary and assigning them permissions.

As part of a migration from Windows XP to Windows 7, I was asked to come up with a way to export the network printers installed on the XP machines such that they could be reinstalled on the Windows 7 machines. We did not want to capture local printers (printers installed via TCP/IP or connected via USB) or virtual printers (like the Adobe PDF virtual printer or the Microsoft XPS Document Writer). I thought that migrating the printers was less attractive because the source machines are 32-bit Windows XP but the destination machines are 64-bit Windows 7 and the drivers are therefore different.

There are a number of ways to export print queues, printer settings, and printer ports, but for my purposes, I decided that all I wanted was to determine the name of each printer (eg.: \\SERVER\Printer) on the XP machine, export that to a text file on a network share, and then run PrintUI.exe /ga on the Windows 7 machine, looping through the lines from the text file as input.

(Check out the Printer Migration wizard by launching PrintBrmUI.exe, or the command line version %WINDIR%\System32\Spool\Tools\Printbrm /?, for alternatives to printui.exe.)

As an added benefit, I’m also exporting the name of the default printer so that it can be set programatically in the new environment.

The code is a work in progress, but I hope it helps get your started.

The VBScripts

Here are the scripts I’ve pieced together.

exportPrinters.vbs

The exportPrinters.vbs script creates two text files in H:\PRINTERS, so adjust your path accordingly.

Const ForWriting = 2

Set objNetwork = CreateObject("Wscript.Network")

strName = objNetwork.UserName
strDomain = objNetwork.UserDomain
strUser = strDomain & "\" & strName

'strText = strUser & vbCrLf

strComputer = "."

Set objWMIService = GetObject("winmgmts:\\" & strComputer & "\root\cimv2")


' Export a list of network printers to a text file

Set colPrinters = objWMIService.ExecQuery _
    ("Select * From Win32_Printer Where Local = FALSE")

For Each objPrinter in colPrinters
    strText = strText & objPrinter.Name & vbCrLf
Next

Set objFSO = CreateObject("Scripting.FileSystemObject")

strFolder = "H:\PRINTERS"

If Not objFSO.FolderExists(strFolder) Then
    objFSO.CreateFolder(strFolder)
End If

Set objFile = objFSO.CreateTextFile _
    ("H:\PRINTERS\printers.txt", ForWriting, False)

objFile.Write strText

objFile.Close


' Export the default printer separately

Set colPrinters = objWMIService.ExecQuery _
    ("Select * From Win32_Printer Where Default = TRUE")

For Each objPrinter in colPrinters
    strText = objPrinter.Name
Next

Set objFSO = CreateObject("Scripting.FileSystemObject")

Set objFile = objFSO.CreateTextFile _
    ("H:\PRINTERS\default.txt", ForWriting, False)

objFile.Write strText

objFile.Close

importPrinters.vbs

Note that, if your users in Windows 7 are not administrators, you will need to run the script as an administrator (there are a few different ways) or else you’ll get a UAC prompt for each printer installation and the restarting of the print spooler.

Again, watch the paths. This is a home-grown script for my specific environment.

Option Explicit

'This script must be run with administor privileges
'If it is not run with administrator privileges, it will launch a UAC prompt for each printer as it loops through the list
'Here's an interesting article about running VBS as a different user:
'http://blogs.technet.com/b/heyscriptingguy/archive/2006/04/28/how-can-i-use-the-runas-command-to-run-a-script-under-alternate-user-credentials.aspx

'For example:
'runas /profile /user:[username]\[password] "cscript.exe \"F:\Printer Driver Research\Automation\printers-import.vbs"\"

Dim objNetwork, strComputer, strName, strFolder, objFSO, strTextFile, strData, strLine, arrLines, strRunCmd, WshShell
CONST ForReading = 1

'Create a Network Object
Set objNetwork = CreateObject("Wscript.Network") 

'Get the local machine name from the Network Object
strComputer = objNetwork.ComputerName 

'Get the user's username from the Network Object
strName = objNetwork.UserName

'Create a File System Object
Set objFSO = CreateObject("Scripting.FileSystemObject")

'Save the name of the text file as a variable 
'Note that the script must be run as an administrator and that invoking the script with
'Run As causes the script to run as though it's located in the same directory as "%SystemRoot%\System32\WScript.exe"
'Hence the need to pass the full path to the printers.txt file
strTextFile = "H:\PRINTERS\printers.txt"

'Open the text file - strData now contains the whole file
strData = objFSO.OpenTextFile(strTextFile,ForReading).ReadAll

'Split the text file into lines
arrLines = Split(strData,vbCrLf)

'Initialize the wshShell
Set WshShell = WScript.CreateObject("WSCript.shell")

'Step through the lines
For Each strLine in arrLines

    If Len(strLine) > 0 Then
		'Only run the process on lines that aren't blank
		
'		strRunCmd = "rundll32 printui.dll,PrintUIEntry /ga /c\\" & strComputer & " /n" & strLine & ""
		strRunCmd = """printui.exe"" /ga /q /c\\" & strComputer & " /n" & strLine & ""
		
		'Echo back the command to be run
'		WScript.Echo strRunCmd

		'This launches printui.exe
'		strRunCmd = """printui.exe"""

		'Run the command, display the window, and wait for the command to complete before continuing
		Dim result
		result = WshShell.Run(strRunCmd, 1, True)
		
'		WScript.Echo result
		
		'Write to the application log that the printer was installed
		If result = 0 Then 
			WshShell.LogEvent 0, "User: " & strName & " - Event: attempted to install printer " & strLine & " with [" & strRunCmd & "] (success unknown)"
		End If
		
'		WScript.Echo "Processed printer: " & strLine
    End If

Next

'Cleanup
Set objFSO = Nothing

'Wait 10 seconds
WScript.Sleep 10000

'Restart the Print Spooler
RestartService "Print Spooler", True

Sub RestartService( myService, blnQuiet )
' This subroutine restarts a service
' Arguments:
' myService     use the service's DisplayName
' blnQuiet      if False, the state of the service is displayed
'               every second during the restart procedure
'
' Written by Rob van der Woude
' http://www.robvanderwoude.com

    ' Standard housekeeping
    Dim colServices, colServicesTest, objService
    Dim objServiceTest, objWMIService, strQuery, strTest

    ' Create a WMI object
    Set objWMIService = GetObject( "winmgmts:\\.\root\CIMV2" )

    ' Query the services for "our" service
    strQuery = "SELECT * FROM Win32_Service WHERE DisplayName='" & myService & "'"
    Set colServices = objWMIService.ExecQuery( strQuery, "WQL", 48 )

    ' Loop through the "collection" of returned services
    For Each objService In colServices
        ' See if we need to tell the user we're going to stop the service
        If Not blnQuiet Then
            WScript.Echo "Stopping " & myService
        End If

        ' Stop the service
        objService.StopService

        ' Wait until the service is stopped
        Do Until strTest = "Stopped"
            ' Create a new object for our service; this work-around is required
            ' since otherwise the service's state information isn't properly updated
            Set colServicesTest = objWMIService.ExecQuery( strQuery, "WQL", 48 )

            ' Loop through the "collection" of returned services
            For Each objServiceTest In colServicesTest
                ' Check the service's state
                strTest = objServiceTest.State
                ' See if we need to show the progress
                If Not blnQuiet Then
                    WScript.Echo "State: " & strTest
                End If
                ' Wait 1 second
                WScript.Sleep 1000
            Next

            ' Clear the temporary object
            Set colServicesTest = Nothing
        Loop

        ' See if we need to tell the user we're going to (re)start the service
        If Not blnQuiet Then
            WScript.Echo "Starting " & myService
        End If

        ' Start the service
        objService.StartService

        ' Wait until the service is running again
        Do Until strTest = "Running"
            ' Create a new object for our service; this work-around is required
            ' since otherwise the service's state information isn't properly updated
            Set colServicesTest = objWMIService.ExecQuery( strQuery, "WQL", 48 )

            ' Loop through the "collection" of returned services
            For Each objServiceTest In colServicesTest
                ' Check the service's state
                strTest = objServiceTest.State
                ' See if we need to show the progress
                If Not blnQuiet Then
                    WScript.Echo "State: " & strTest
                End If
                ' Wait 1 second
                WScript.Sleep 1000
            Next

            ' Clear the temporary object
            Set colServicesTest = Nothing
        Loop
    Next
End Sub

setDefaultPrinter.vbs

Finally, we want to set the default printer. The printer will have to already exist (obviously). If you’ve just fired off the printui.exe /ga command to install the printer, it won’t be available until the print spooler is restarted. So wait a minute or two before running the script.

Option Explicit

Dim objNetwork, strComputer, objFSO, strTextFile, strData, strLine, arrLines, strRunCmd, wshShell
CONST ForReading = 1

'Create a Network Object
Set objNetwork = CreateObject("Wscript.Network") 

'Get the local machine name from the Network Object
strComputer = objNetwork.ComputerName 

'Save the name of the text file as a variable 
strTextFile = "H:\PRINTERS\default.txt"

'Create a File System Object
Set objFSO = CreateObject("Scripting.FileSystemObject")

'Open the text file - strData now contains the whole file
strData = objFSO.OpenTextFile(strTextFile,ForReading).ReadAll

'Split the text file into lines
arrLines = Split(strData,vbCrLf)

'Initialize the wshShell
Set wshShell = WScript.CreateObject ("WSCript.shell")

'Step through the lines
For Each strLine in arrLines

    If Len(strLine) > 0 Then
		'Only run the process on lines that aren't blank
		
'		strRunCmd = "rundll32 printui.dll,PrintUIEntry /y /c\\" & strComputer & " /n" & strLine & ""
		strRunCmd = """printui.exe"" /y /c \" & strComputer & " /n """ & strLine & """"
'		wscript.echo strRunCmd
		wshShell.Run strRunCmd
		
'		wscript.echo "Processed printer: " & strLine
    End If

Next

'Cleanup
Set objFSO = Nothing

As the migration proceeds, I’ll come back and tweak the scripts. Please feel free to leave comments and suggestions.

Thanks go to Rob van der Woude for his terrific Command Line Printer Control page, as well as many others.

Google’s Panda update and Google+ has motivated me to start using more cutting-edge technology at ardamis.com, starting with a new theme that makes better use of HTML5 and microformats.

I rather like the look of the current theme, but one of the metrics that Panda is supposedly weighting is bounce rate. Google Analytics indicates that the vast majority of my visitors arrive via organic search on Google while looking for answers to a particular problem. Whether or not they find their answer at ardamis.com, they tend not to click to other pages on the site. This isn’t bad, it’s just the way it works. I happen to be the same sort of user – generally looking for specific information and not casually surfing around a web site.

In the prior WordPress theme, I moved my navigation from the traditional location of along side the article to the bottom of the page, below the article. This cleaned up the layout tremendously and focused all the attention on the article, but it also made it even more likely that a visitor would bounce.

For the 2012 redesign, I moved the navigation back to the side and really concentrated on providing more obvious links to the About, Portfolio, Colophon and Contact pages.

I’ve been a fan of the HTML5 Boilerplate template for starting off hand-coded sites, and I’ve once again cherry-picked elements from it to use as a foundation. If you’re interested in a running start, you may try out the very nice Boilerplate WordPress theme by Aaron T. Grogg.

The latest version of the theme also faithfully follows the sometimes idiosyncratic whims of Google Webmaster Tools’ Rich Text Snippet Testing Tool. Look, no warnings.