Category Archives: Projects

Posts concerning freelance web development projects.

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.

I just picked up an old Dell Precision 690 workstation, which I intend to develop into a file server, a Windows IIS server, and an Ubuntu LAMP server. This monster was built in 2006, but it still has some neat specs and tons of capacity (7 PCI slots, 4 hard drive bays, etc…), should I want to expand further.

Dell Precision 690

Dell Precision 690 Workstation

The main specs

CPU: Dual Core Intel Xeon 5060 3.2GHz, 4M Cache, 1066 MHz FSB
RAM: 2GB DDR2 PC2-5300, CL=5, Fully Buffered, ECC, DDR2-667
HD: SAS Fujitsu MAX3073RC 73GB, 15000 RPM, 16MB Cache
Video: Nvidia Quadro NVS 285 PCI-Express, 128MB

This is not a normal tower

Right away, the size of this thing suggests it isn’t a normal tower. It’s about up to my knee and weights 70 lbs. It feels like it’s made with heavier gauge steel than the typical chassis, but that may be me projecting.

I immediately shopped around for more RAM, obviously. 2GB seems a little thin, even by 2006 standards, when considering the way everything else is high-end. The mainboard has 8 slots and supports up to 32GB, but I figure 6GB is a safe place to start.

The workstation has three enormous fans, like, big-as-your-hand big. Running it with the chassis open causes some sort of thermal protection system to kick in and it spins the fans up to the point that they were blowing stuff on the floor half-way across the room.

The CPU has a big, passive heat sink with six copper pipes and sits between two of those fans. I’m tempted to buy a second CPU, but I’ll hold off.

I’m still on the fence about the SCSI drive. It should be super fast, but I’m a little spoiled by the SSD in my machine at work, so it’s hard to get excited about a mechanical drive, even one running at 15k RPM.

The Nvidia Quadro card is also fanless, and has a bizarre DMS-59 connector. An adapter converts the DMS-59 connector into two DVI outputs.

For some time, I’ve felt that Ardamis.com was being pulled in two directions. It started out as something of a business card, then it landed me a job, and so the focus changed and it became more of a personal blog. A few years later, I found myself posting mainly code snippets from personal projects and announcements of new site launches.

So, I’ve determined that I’ll keep Ardamis.com as a place for experimentation and create a new site to handle the web development business – Aleph Studios. It’s so new that I have to think sometimes about how Aleph is spelled as I type it.

I’ll be migrating some of the pages away from Ardamis over the next week or two.

A friend who does a lot of selling on eBay asked me to develop a web application for generating HTML templates that could be copied and pasted into his auctions. He wanted to be able to add a title, a description, and some terms and conditions, and also to be able to upload images so the auction template would display thumbnails that could be clicked to display full-sized versions in a new window or tab. The more we talked, the more it sounded like something that would be both useful for a good number of people, and potentially a source of ad revenue for us. And so was born Simple Auction Wizard, an online HTML template generator for auction websites like eBay.

I was looking for just this kind of a project because I wanted a reason to play with TinyMCE, an Open Source, platform independent, web-based JavaScript HTML WYSIWYG editor control, and SWFUpload, a small JavaScript/Flash library that does very neat things with file uploads.

The most difficult part was actually getting the templates to look good in a variety of browsers. Because the template HTML appears inside of a larger page, I couldn’t rely on Doctype switching to place IE into standards mode. This meant the templates would have to be developed so that they would look approximately the same in standards-compliant browsers like Firefox, Chrome, and Safari, as well as horrible browsers like IE6 in quirks-mode. I tried very hard to minimize the use of tables, but they eventually crept in. I was able to use CSS, for the most part, though if any more issues come up, I’m going to throw in the towel and just do inline styles.

The web app is open to the public, but realize that it’s very early in its development and I intend to continue making changes.