FrontPage Versions and Timeline

November, 1995 - Vermeer FrontPage 1.0
(Version 1.0)



Mini Review: Believe it or not, FrontPage 1.0 ran on Windows 3.x and Windows NT 3.5.1. This required installing a Win32 subsystem for Windows 3.x, which was fraught with installation errors.

This version was very limited, and it didn't even support tables. The program also had a nasty little issue - if this version saw some HTML that it didn't like, it just deleted it!

 

June, 1996 - Microsoft FrontPage 1.1
(Version 1.1)

Mini Review: FrontPage 1.1 was Microsoft's first release for the FrontPage family of products. It thankfully supported tables, and it supported frames, even though Microsoft's version of Internet Explorer at the time did not support frames.

 

October, 1996 - Microsoft FrontPage 97
(Version 2)

Mini Review: FrontPage 97 dropped the need for the _vti_shm folder and started inserting FrontPage bot code into the HTML. (This was an important update.)

 

January, 1997 - Microsoft FrontPage 1.0 for Macintosh
(Version 2)

Mini Review: Microsoft FrontPage 1.0 for Macintosh was basically a port of FrontPage 97 for Apple computers. This didn't do all that well in the Apple market because FrontPage faced a deeply entrenched customer base of Apple users that were already using Adobe's products, and subsequently it was the only version of FrontPage that Microsoft created for the Macintosh.

 

September, 1997 - Microsoft FrontPage Express 2.0

Mini Review: This was a version of FrontPage that shipped with Internet Explorer 4.0; it was essentially an editor-only version of FrontPage; all of the web management features were removed. Microsoft did not make another version of FrontPage Express.

 

December, 1997 - Microsoft FrontPage 98
(Version 3)

Mini Review: This was the last version of FrontPage that featured a separate editor and explorer, but it was arguably a very popular version and it signaled the beginning of FrontPage's short-lived reign as one of the most-used HTML authoring tools.

 

March, 1999 - Microsoft FrontPage 2000
(Version 4)

Mini Review: This was the first version of FrontPage that integrated the editor and web management features, which was a huge milestone. This was also an extremely popular version, and it continued FrontPage's short-lived reign as one of the most-used HTML authoring tools.

 

June, 2001 - Microsoft FrontPage 2002
(Version 5 [Office 10])

Mini Review: This version marked the beginning of FrontPage's demise as one of the most-used HTML authoring tools. Tools like Dreamweaver began to seriously eat away at FrontPage's customer base as Dreamweaver and other tools became more powerful and developer-friendly, while FrontPage suffered from an identity crisis by sticking to simpler, novice-friendly authoring that alienated web developers.

 

October, 2003 - Microsoft Office FrontPage 2003
(Version 6 [ Office 11])

Mini Review: On a sad note, this was the last of the FrontPage family of products - Microsoft dropped FrontPage in favor of Expression Web. FrontPage 2003 is still my all-time-favorite version of FrontPage; there's a great balance of powerful functionality and ease-of-use. (Note: Several years later, Microsoft cancelled the Expression Web family, thereby ending this line of products from Microsoft.)

IIS 6: FTP User Isolation with Multiple User Accounts

In IIS 4.0 and IIS 5.0, if you created a virtual directory that had a name that was identical to a user name, when the user logged in to the FTP site they would automatically be changed to their folder. When multiple users will access the same FTP content, you could create another virtual directory that is identical to the other user name and point it to the same content.

This allowed sharing a single FTP site across several users and content sets without advertising user names or content folders. Even though a user could type "CD /" from an FTP prompt, they would not be able to see the virtual directories from other user accounts on that server because virtual directories are not listed when a user types "ls -l" or "dir" from an FTP prompt at the root. That being said, this security feature still doesn't go far enough from a security perspective.

One of the great IIS 6.0 features is User Isolation, which is discussed in the Hosting Multiple FTP Sites with FTP User Isolation (IIS 6.0) topic on MSDN. As a quick review, there are three different isolation modes that you can choose when creating an IIS 6.0 FTP site:

  • "Do Not Isolate Users"
    No user isolation; FTP works like IIS 4.0 or IIS 5.0.
    (Not covered in this post.)
  • "Isolate Users"
    Simple user isolation through folders.
    (Described below.)
  • "Isolate Users Using Active Directory"
    Requires A.D. configuration.
    (Not covered in this post.)

To configure the "Isolate Users" mode, you first need to create your FTP site and choose the "Isolate Users" option when prompted for FTP User Isolation. Once the FTP site has been created, you need to create a physical folder named "LocalUser" for local user accounts or named after your domain under your FTP server's root folder. To isolate users to a specific folder, you use these rules that I copied from the MSDN topic that I listed earlier in this post:

  • For anonymous users, the home directory is LocalUser\Public under the FTP root directory.
  • For local users, the home directory is LocalUser\UserName under the FTP root directory.
  • For users that log on with Domain\UserName, the home directory is Domain\UserName under the FTP root directory.

This is very easy to configure, and when a user logs in to your FTP server they will be restricted to their physical folder under the FTP root. Typing "CD /" from an FTP prompt will always restrict the user within their own site.

That being said, because physical directories are required for this configuration it may seem like a step backward when you consider that you used to be able to create multiple virtual directories that pointed to content in varying locations and for multiple user accounts. Not to worry, however, because Windows provides a way around this limitation using NTFS junctions.

For those of you that are not familiar with NTFS junctions, there are several topics that discuss this. (For example, see Inside Win2K NTFS, Part 1.) A junction is somewhat like a symbolic directory link in the UNIX world, where a junction looks like a folder but points to content that is physically located somewhere else. There are two tools that you can use to create junctions, LINKD from the Windows Resource Kit, and JUNCTION from www.sysinternals.com. Using these tools with IIS 6.0 can allow you the freedom to deploy FTP folder structures much like you did with IIS 4/5 while utilizing the user isolation features in IIS 6.

Here's an example - when configuring an IIS 6.0 FTP site, I used the following steps:

  1. I chose the "Isolate Users" option when creating my FTP site.
  2. I created the "LocalUser" physical folder under my FTP site's root folder.
  3. I created junctions under the "LocalUser" physical folder that were named after user accounts and pointed to various content folders.

When a user logs in to my FTP site using their user account, they are automatically dropped in their content folder via the junction. Since you can create multiple junctions that point to the same content folder, you can create junctions for every user account that will work with a set of content.

I hope this helps!


Note: This blog was originally posted at http://blogs.msdn.com/robert_mcmurray/

IIS 6: Reverting log files back to the default W3C fields

Recently I had to work with a customer that was trying to use a 3rd-party utility that read W3C log files and it was failing to complete processing. I had the customer send me his log files, and upon examination I discovered that the trouble was occuring because the customer had been experimenting with adding and removing the different fields from their log files and this was causing the log parsing utility to crash.

As luck would have it, IIS provides a useful logging utility object that you can read more about at the following URL:

I had used this logging utility object for an earlier project, so I was familiar with how it worked. With that knowledge in mind, I wrote the following script that loops through all of the log files in a folder and creates new log files in a subfolder that contain only the default W3C fields. (BTW - I sent this script to the customer and he was able to parse all of his log files successfully. ;-] )

Option Explicit
Randomize Timer

' Declare variables.

Dim objIISLog
Dim objFSO, objFolder, objFile
Dim objOutputFile, strInputFile
Dim strOutputFile, strOutputPath
Dim strLogRecord
Dim blnExists

' Create file system object.
Set objFSO = WScript.CreateObject("Scripting.FileSystemObject")
' Retrieve an object For the current folder.
Set objFolder = objFSO.GetFolder(".")

' Create a subfolder with a random name.
blnExists = True
Do While blnExists = True
strOutputPath = objFolder.Path & "\" & CreateRandomName(20)
blnExists = objFSO.FolderExists(strOutputPath)
Loop
objFSO.CreateFolder strOutputPath

' Loop through the log files in the current folder.
For Each objFile In objFolder.Files

' Test for a log file.
If Right(LCase(objFile.Name),4) = ".log" Then

' Format the file names/paths.
strInputFile = objFolder.Path & "\" & objFile.Name
strOutputFile = strOutputPath & "\" & objFile.Name

' Create and open an IIS logging object.
Set objIISLog = CreateObject("MSWC.IISLog")
' Open the input log file.
objIISLog.OpenLogFile strInputFile, 1, "", 0, ""
' Open the output log file.
Set objOutputFile = objFSO.CreateTextFile(strOutputFile)

' Read the initial record from the log file.
objIISLog.ReadLogRecord

' Write the headers to the output log file.
objOutputFile.WriteLine "#Software: Microsoft Internet Information Services 5.0"
objOutputFile.WriteLine "#Version: 1.0"
objOutputFile.WriteLine "#Date: " & BuildDateTime(objIISLog.DateTime)
objOutputFile.WriteLine "#Fields: date time c-ip cs-username s-ip s-port " & _
"cs-method cs-uri-stem cs-uri-query sc-status cs(User-Agent)"

' Loop through the records in the log file.
Do While Not objIISLog.AtEndOfLog

' Format the log file fields.
strLogRecord = BuildDateTime(objIISLog.DateTime)
strLogRecord = strLogRecord & _
" " & FormatField(objIISLog.ClientIP) & _
" " & FormatField(objIISLog.UserName) & _
" " & FormatField(objIISLog.ServerIP) & _
" " & FormatField(objIISLog.ServerPort) & _
" " & FormatField(objIISLog.Method) & _
" " & FormatField(objIISLog.URIStem) & _
" " & FormatField(objIISLog.URIQuery) & _
" " & FormatField(objIISLog.ProtocolStatus) & _
" " & FormatField(objIISLog.UserAgent)

' Write the output log file record.
objOutputFile.WriteLine strLogRecord

' Read the next record from the log file.
objIISLog.ReadLogRecord

Loop

' Close the input log file.
objIISLog.CloseLogFiles 1
objIISLog = Null

End If

Next

' Inform the user that the operation has completed.
MsgBox "Finished!"

' Format a log file field.
Function FormatField(tmpField)
On Error Resume Next
FormatField = "-"
If Len(tmpField) > 0 Then FormatField = Trim(tmpField)
End Function

' Format a log file date.
Function BuildDateTime(tmpDateTime)
On Error Resume Next
tmpDateTime = CDate(tmpDateTime)
BuildDateTime = Year(tmpDateTime) & "-" & _
Right("0" & Month(tmpDateTime),2) & "-" & _
Right("0" & Day(tmpDateTime),2) & " " & _
Right("0" & Hour(tmpDateTime),2) & ":" & _
Right("0" & Minute(tmpDateTime),2) & ":" & _
Right("0" & Second(tmpDateTime),2)
End Function

' Create a random name.
Function CreateRandomName(intNameLength)
Const strValidChars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
Dim tmpX, tmpY, tmpZ
For tmpX = 1 To intNameLength
tmpY = Mid(strValidChars,Int(Rnd(1)*Len(strValidChars))+1,1)
tmpZ = tmpZ & tmpY
Next
CreateRandomName = tmpZ
End Function

Happy coding!


Note: This blog was originally posted at http://blogs.msdn.com/robert_mcmurray/

IIS 6: Creating an Itemized List of Server Bindings

One of my servers has a large number of individual web sites on it, and each of these web sites has several server bindings for different IP addresses, Port Assignments, and Host Headers. As I continue to add more web sites on the server, it becomes increasingly difficult to keep track of all the details using the IIS user interface.

With that in mind, I wrote the following ADSI script which creates a text file that contains an itemized list of all server bindings on a server.

Option Explicit
On Error Resume Next

Dim objBaseNode, objChildNode
Dim objBindings, intBindings
Dim objFSO, objFile, strOutput

' get a base object
Set objBaseNode = GetObject("IIS://LOCALHOST/W3SVC")
Set objFSO = WScript.CreateObject("Scripting.FileSystemObject")
Set objFile = objFSO.CreateTextFile("ServerBindings.txt")

' check if if have an error ...
If (Err.Number <> 0) Then

    ' ... and output the error.
    strOutput = "Error " & Hex(Err.Number) & "("
    strOutput = strOutput & Err.Description & ") occurred."

' ... otherwise, continue processing.
Else
    ' loop through the child nodes
    For Each objChildNode In objBaseNode
        ' is this node for a web site?
        If objChildNode.class = "IIsWebServer" Then
            ' get the name of the node
            strOutput = strOutput & "LM/W3SVC/" & _
                objChildNode.Name
            ' get the server comment
            strOutput = strOutput & " (" & _
                objChildNode.ServerComment & ")" & vbCrLf
            ' get the bindings
            objBindings = objChildNode.ServerBindings
            ' loop through the bindings
            For intBindings = 0 To UBound(objBindings)
                strOutput = strOutput & vbTab & _
                    Chr(34) & objBindings(intBindings) & _
                    Chr(34) & vbCrLf
            Next
        End If
    ' try not to be a CPU hog
    Wscript.Sleep 10
    Next
End If

objFile.Write strOutput
objFile.Close

Set objBaseNode = Nothing
Set objFSO = Nothing

Hope this helps!

To geek, or not to geek...

Like most of the computer-obsessed friends that I hang out with, I am often called a geek by the non-techie types that I have to interact with. (That list of non-techies includes my wife, by the way. ;-])

I wondered if that was a label that I should wear with pride, then I saw a great quote the other day:

"A Geek is someone who is passionate about some particular area or subject, often an obscure or difficult one."

Given my over-interest in computers, maybe the "Geek" title fits.

When discussing several subjects with some other geeks the other day, one of my friends asked, "Is it possible that there is anything that you're not a geek in?"

I had to give that some thought, but then I replied, "Shoes."

To be honest, I wear a set of sneakers just about everywhere. I wouldn't know a good set of dress shoes if they snuck up behind me and kicked me in the rear.

Maybe that's why I have such a hard time understanding why my wife owns 40 pairs of shoes... To me, I figure what's the point?

To be honest, I actually do own a couple of other pairs of shoes, but my wife bought them for me. When all is said and done, all I ever wear is the sneakers. (Even to church... ;-])

I think my wife buys my other shoes for me in the hopes that I will either: 1) empathize with her plight, 2) convert to the dark side, or 3) develop some form of shoe-fetish gland. I think the fact that I work for Microsoft has still escaped her.

I've told my daughters that I'm making them a simple deal: I'll wear a tuxedo for their weddings. If I foot the bill for the wedding, then I'm wearing sneakers with the tux. If they pay for their own wedding, then I'll wear whatever shoes they like.

Seems fair to me.  ;-]

But the question remains, am I a socially hopeless cause? I hope not. (After all, I do get out a lot.)

Am I a geek? Hmm... I guess I should consider the old adage:

"If the shoe fits..."

Well, you know the rest. ;-]

Updating several IP addresses using ADSI

(Note: I had originally posted this information on a blog that I kept on http://weblogs.asp.net, but it makes more sense to post it here. [:)] )

Like many web programmers, I host several hobby web sites for fun. (They make a wonderful test bed for new code. ;-] )

And like many computer enthusiasts, I sometimes change my ISP for one reason or another. If you are hosting web sites in a similar situation, I'm sure that you can identify the pain of trying to manually update each old IP address to your new IP address. This situation can be made even more difficult when any number of your web sites are using several host headers because the user interface for the IIS administration tool only lists the first host header. This means that you have to manually view the properties for every site just to locate the IP addresses that you are required to change.

Well, I'm a big believer in replacing any repetitive task with code when it is possible, and a recent change of ISP provided just the right level of inspiration for me to write a simple Active Directory Service Interfaces (ADSI) script that locates IP addresses that have to be changed and updates them to their new values.

To use the example script, I would first suggest that you make a backup copy of your metabase. (The script works fine, but it is always better to have a backup. ;-] ) As soon as your metabase has been backed up, copy the example script into notepad or some other text editor, update the old and new IP addresses that are defined as constants, and then run the script.

Option Explicit
On Error Resume Next
 
Dim objIIS
Dim objSite
Dim varBindings
Dim intBindings
Dim blnChanged
 
Const strOldIP = "10.0.0.1"
Const strNewIP = "192.168.0.1"
 
Set objIIS = GetObject("IIS://LOCALHOST/W3SVC")
 
If (Err <> 0) Then
  WScript.Echo "Error " & Hex(Err.Number) & "(" & _
    Err.Description & ") occurred."
  WScript.Quit
Else
  For Each objSite In objIIS
    blnChanged = False
    If objSite.class = "IIsWebServer" Then
      varBindings = objSite.ServerBindings
      For intBindings = 0 To UBound(varBindings)
        If InStr(varBindings(intBindings),strOldIP) Then
          blnChanged = True
          varBindings(intBindings) = Replace(varBindings(intBindings),strOldIP,strNewIP)
        End If
      Next
    End If
    If blnChanged = True Then
      objSite.ServerBindings = varBindings      
      objSite.Setinfo
    End If
  Next
End If
MsgBox "Finished!"

That's all for now. Happy coding!


Note: This blog was originally posted at http://blogs.msdn.com/robert_mcmurray/

Merry Christmas 2005!

Okay, it's a couple of days late because I took the past few days off to celebrate Christmas with my family, but I wanted to make sure that I wished everyone a Merry Christmas...! [:)]

Веселое Рождество!

Christmas Alegre!

¡Feliz Navidad!

Frohe Weihnachten!

Joyeux Noël!

Καλά Χριστούγεννα!

Natale allegro!

Vrolijke Kerstmis!

Converting NCSA log files to W3C format

One of the great utilities that ships with IIS is the CONVLOG.EXE application, which converts W3C or MS Internet Standard log files to NCSA format, where they can be processed by any of the applications that only parse NCSA log file information. The trouble is, what happens when you already have NCSA log files and you want W3C log files? You can't use the CONVLOG.EXE application, it only works in the opposite direction.

With that in mind, I wrote the following Windows Script Host (WSH) script that will read the current directory and convert all NCSA-formatted log files to W3C format. To use this code, just copy the code into notepad, and save it with a ".vbs" file extension on your system. To run it, copy the script to a folder that contains NCSA log files, (named "nc*.log"), then double-click it.

Option Explicit

Dim objIISLog
Dim objFSO
Dim objFolder
Dim objFile
Dim objOutputFile
Dim strInputPath
Dim strOutputPath
Dim strLogRecord

Set objFSO = WScript.CreateObject("Scripting.FileSystemObject")

Set objFolder = objFSO.GetFolder(".")

For Each objFile In objFolder.Files

 strInputPath = LCase(objFile.Name)

 If Left(strInputPath,2) = "nc" And Right(strInputPath,4) = ".log" Then

  strOutputPath = objFolder.Path & "\" & "ex" & Mid(strInputPath,3)
  strInputPath = objFolder.Path & "\" & strInputPath

  Set objIISLog = CreateObject("MSWC.IISLog")
  objIISLog.OpenLogFile strInputPath, 1, "", 0, ""
  Set objOutputFile = objFSO.CreateTextFile(strOutputPath)

  objIISLog.ReadLogRecord

  objOutputFile.WriteLine "#Software: Microsoft Internet Information Services 5.0"
  objOutputFile.WriteLine "#Version: 1.0"
  objOutputFile.WriteLine "#Date: " & BuildDateTime(objIISLog.DateTime)
  objOutputFile.WriteLine "#Fields: date time c-ip cs-username s-ip s-port cs-method cs-uri-stem cs-uri-query sc-status cs(User-Agent)"

  Do While Not objIISLog.AtEndOfLog

   strLogRecord = BuildDateTime(objIISLog.DateTime)
   strLogRecord = strLogRecord & " " & FormatField(objIISLog.ClientIP)
   strLogRecord = strLogRecord & " " & FormatField(objIISLog.UserName)
   strLogRecord = strLogRecord & " " & FormatField(objIISLog.ServerIP)
   strLogRecord = strLogRecord & " " & FormatField(objIISLog.ServerPort)
   strLogRecord = strLogRecord & " " & FormatField(objIISLog.Method)
   strLogRecord = strLogRecord & " " & FormatField(objIISLog.URIStem)
   strLogRecord = strLogRecord & " " & FormatField(objIISLog.URIQuery)
   strLogRecord = strLogRecord & " " & FormatField(objIISLog.ProtocolStatus)
   strLogRecord = strLogRecord & " " & FormatField(objIISLog.UserAgent)
   objOutputFile.WriteLine strLogRecord

   objIISLog.ReadLogRecord

  Loop

  objIISLog.CloseLogFiles 1
  objIISLog = Null
 
 End If

Next

Function FormatField(tmpField)
 On Error Resume Next
 FormatField = "-"
 If Len(tmpField) > 0 Then FormatField = Trim(tmpField)
End Function

Function BuildDateTime(tmpDateTime)
 On Error Resume Next
 tmpDateTime = CDate(tmpDateTime)
 BuildDateTime = Year(tmpDateTime) & "-" & _
  Right("0" & Month(tmpDateTime),2) & "-" & _
  Right("0" & Day(tmpDateTime),2) & " " & _
  Right("0" & Hour(tmpDateTime),2) & ":" & _
  Right("0" & Minute(tmpDateTime),2) & ":" & _
  Right("0" & Second(tmpDateTime),2)
End Function

I hope this helps!

Domestic Terrorists Come in Many Forms

As an honorably-discharged veteran, it should come as no surprise that an opinion piece with a title of "Why America needs to be Defeated in Iraq" would catch my attention. When I first read this ludicrous pile of drivel from a gentleman whom I shall henceforth refer to as "Mr. Whit," I was merely offended. His errant ramblings seemed to be another entry in a long line of deranged brain dumps from scores of deranged imbeciles that I seemed to discover whenever I ventured into another dark corner within the vast wastelands of west coast propaganda. I lived in Seattle for a decade or so, and I've seen this type of close-minded lunacy before. To paraphrase the Bard, the author of that particular op-ed, Mr. Whit, has no more intelligence in him than in a stewed prune. (Methinks he doth possess a great deal less.)

After a bit of time had passed, I pondered more about the context of Mr. Whit's article, and I was appalled by the abhorrent vulgarity of what his brief manifesto actually represents: Mr. Whit is a US Citizen who is outspoken and unapologetic about his desire that some form of harm should come to other US citizens. That admission makes him, using today's parlance, a domestic terrorist. Plain and simple. When one American's wish is that other Americans must suffer in order to make a political point, then that American is no better than the abomination that was Timothy McVeigh. After all, the late Mr. McVeigh only wanted to make a political statement when he and his accomplice bombed the Federal Building in Oklahoma City, right?

Mr. McVeigh's vicious manifestation of domestic terrorism was responsible for the deaths of 168 innocent men, women, and children. However, for Mr. McVeigh, those people's fates were secondary to his cause. He believed that his principles were more important than his victims' lives. In the same way, Mr. Whit regards his self-appointed role as an oracle of truth as more beneficial to society than the meager value of average peasants, and therefore he believes that he has the right to condemn other lives to death in order to satisfy his misguided philosophies. The fates of those whom Mr. Whit would send to their graves are secondary to his gargantuan ego, and their lives are worth less than his sanctimonious convictions.

On the one hand, Mr. Whit accuses the US government of believing that "might is right" when it decides who gets to live or die, while on the other hand he freely chooses (from the sanctity of his word-processor) who else gets to live or die. Like Mr. McVeigh before him, Mr. Whit's deplorable appetite for others to suffer for his wretched aberration of morality is fully-vindicated within the boundaries of his twisted, little worldview. I am sure that somewhere inside his hollow, rat-infested cranium, Mr. Whit believes that the "ends justify the means." However, Mr. Whit doesn't have to face the consequences of his brain-dead decrees, whereas the innocent lives that he has condemned to death and their unfortunate families are left to suffer.

History has had more than its fair share of sociopaths who fail to take responsibility for their murderous actions, and Mr. Whit follows their example to the letter when he wishes death upon US troops while skirting away from any personal culpability by laying the blame for their deaths on the government. What Mr. Whit does not realize is that he doesn't get to have it both ways; he cannot pronounce a death sentence on others without being found guilty for his own crimes. He cannot claim that our government is guilty of terrorism, then advocate for the deaths of other US citizens and not be found guilty of his own brand of repugnant and unctuous terrorism. His self-righteous delusions do not grant him the title of judge, jury, or executioner.

It is ironic that short-sighted morons like Mr. Whit are quick to exercise their first amendment right to freedom of speech, while overlooking the sacrifices that were made on his behalf in order to guarantee his right to speak his mind without fear of reprisal. Long before Mr. Whit's feckless mortal coil ventured forth upon the country that he despises and condemns, the same sort of men and women on whom he passes his misguided judgment fought and died so that he might one day have the freedom to spit on their collective memories. Liberals like Mr. Whit never seem to realize that no one can have freedom in this world unless someone is willing to fight for it. It is clear that Mr. Whit will never personally fight for his freedom; he will continue to sit in the shadows and dispatch his putrid, little missives whenever a contrary wind ruffles his delicate feathers.

To be clear, I do not mind when someone exercises their freedom of speech. I do not mind when someone protests the war. (I have my own misgivings about the directions we are taking - or not taking.) I do not mind when someone calls the President a "war-monger." I do not mind when someone wants our troops out of the Middle East, and organizes a "million-man-march" on the capitol to demand that Congress should bring our brave men and women home. When you get right down to it, I do not mind when someone informs me that something I believe to be right or wrong might be true or false.

However, I damn sure mind when some hypocritical, warthog-faced buffoon signs a death warrant for members of our country's armed forces from the safe haven of his computer keyboard, tucked safely away in an office where no harm will come to him. And yet, despite my personal loathing for Mr. Whit, and in deference to my belief that simpletons like Mr. Whit are utterly useless to society, I do not wish that any harm should come to him. I served in our nation's armed forces so that even a miserable, vomitus mass like Mr. Whit has the right to share his pathetic sentiments in a public forum. And to be honest, if push came to shove, I would do so again. And this is the paradox that makes our country great: the strong and the brave will thanklessly sacrifice their personal safety to fight and defend the rights of the weak and ungrateful cowards who condemn them.

I'll get off my soapbox now.


UPDATE: This post is one of several that I had written, which I later discovered had never been set to "public."

IIS 6: Listing the Host Headers of all Web Sites using ADSI

Note: I originally wrote the following script for a friend, but as every good programmer often does, I kept the script around because I realized that it could come in handy. I've found myself using the script quite often with several of the servers that I manage, so I thought that I'd share it here.

When managing a large web server with dozens of web sites, it's hard to keep track of all the host headers that you have configured in your settings. With that in mind, I wrote the following script that lists the host headers that are assigned on an IIS web server. To use the example script, copy the script into notepad or some other text editor, save it to your server as "HostHeaders.vbs", and then double-click the script to run it. The script will create a text file named "HostHeaders.txt" that contains all the host headers listed by site for your server.

Option Explicit
On Error Resume Next

Dim objBaseNode, objChildNode
Dim objBindings, intBindings
Dim objFSO, objFile, strOutput

' get a base object
Set objBaseNode = GetObject("IIS://LOCALHOST/W3SVC")
Set objFSO = WScript.CreateObject("Scripting.FileSystemObject")
Set objFile = objFSO.CreateTextFile("HostHeaders.txt")

' check if we have an error ...
If (Err.Number <> 0) Then

' ... and output the error.
strOutput = "Error " & Hex(Err.Number) & "("
strOutput = strOutput & Err.Description & ") occurred."

' ... otherwise, continue processing.
Else

' loop through the child nodes
For Each objChildNode In objBaseNode

' is this node for a web site?
If objChildNode.class = "IIsWebServer" Then

' get the name of the node
strOutput = strOutput & "LM/W3SVC/" & _
objChildNode.Name

' get the server comment
strOutput = strOutput & " (" & _
objChildNode.ServerComment & ")" & vbCrLf
' get the bindings
objBindings = objChildNode.ServerBindings
' loop through the bindings
For intBindings = 0 To UBound(objBindings)
strOutput = strOutput & vbTab & _
Chr(34) & objBindings(intBindings) & _
Chr(34) & vbCrLf
Next
End If
' try not to be a CPU hog
Wscript.Sleep 10
Next
End If
objFile.Write strOutput
objFile.Close
Set objBaseNode = Nothing
Set objFSO = Nothing

If you feel adventurous, you could easily modify the script to return the text in a tab-separated or comma-separated format.

Enjoy!