Just a short, simple blog for Bob to share his thoughts.
07 November 2006 • by Bob • IIS, LogParser
Around a year ago I wrote a blog entry titled "Converting NCSA log files to W3C format", which showed how to use the MSWC.IISLog object to convert log files in the NCSA format back to W3C format. I wrote that blog entry to make up for the fact that the CONVLOG.EXE utility only converts log files to NCSA format, which some older log analysis software packages require. So what happens if you have a bunch of log files in W3C format and you don't have a copy of CONVLOG.EXE on your computer?
This blog entry is something of a reverse direction on my previous post, and shows you how to use the MSUtil.LogQuery object to convert W3C log files to NCSA format. The MSUtil.LogQuery object is shipped with LogParser, which you can download from one of the following URLs:
Once you've downloaded and installed the LogParser package, you will need to manually register the LogParser.dll file in order to use the MSUtil.LogQuery object. Having done so, you can use the Windows Script Host (WSH) code in this blog article to convert a folder filled with W3C log files to NCSA 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 your W3C log files, (named "ex*.log"), then double-click it.
Option Explicit Dim objFSO Dim objFolder Dim objInputFile Dim objOutputFile Dim objLogQuery Dim objLogRecordSet Dim objLogRecord Dim strInputPath Dim strOutputPath Dim strLogRecord Dim strLogTemp Set objFSO = WScript.CreateObject("Scripting.FileSystemObject") Set objFolder = objFSO.GetFolder(".") For Each objInputFile In objFolder.Files strInputPath = LCase(objInputFile.Name) If Left(strInputPath,2) = "ex" And Right(strInputPath,4) = ".log" Then strOutputPath = objFolder.Path & "\" & "nc" & Mid(strInputPath,3) strInputPath = objFolder.Path & "\" & strInputPath Set objLogQuery = CreateObject("MSUtil.LogQuery") Set objLogRecordSet = objLogQuery.Execute("SELECT * FROM " & strInputPath) Set objOutputFile = objFSO.CreateTextFile(strOutputPath) Do While Not objLogRecordSet.atEnd Set objLogRecord = objLogRecordSet.getRecord strLogRecord = FormatField(objLogRecord.getValue("c-ip")) strLogRecord = strLogRecord & " " & FormatField("") strLogRecord = strLogRecord & " " & FormatField(objLogRecord.getValue("cs-username")) strLogTemp = BuildDateTime(objLogRecord.getValue("date"),objLogRecord.getValue("time")) strLogRecord = strLogRecord & " " & FormatField(strLogTemp) strLogRecord = strLogRecord & " """ & FormatField(objLogRecord.getValue("cs-method")) strLogRecord = strLogRecord & " " & FormatField(objLogRecord.getValue("cs-uri-stem")) strLogTemp = FormatField(objLogRecord.getValue("cs-version")) If strLogTemp = "-" Then strLogRecord = strLogRecord & " HTTP/1.0""" Else strLogRecord = strLogRecord & " " & strLogTemp & """" End If strLogRecord = strLogRecord & " " & FormatField(objLogRecord.getValue("sc-status")) strLogRecord = strLogRecord & " " & FormatField(objLogRecord.getValue("sc-bytes")) objOutputFile.WriteLine strLogRecord objLogRecordSet.moveNext Loop Set objLogQuery = Nothing objOutputFile.Close End If Next Function FormatField(tmpField) On Error Resume Next FormatField = "-" If Len(tmpField) > 0 Then FormatField = Trim(tmpField) End Function Function BuildDateTime(tmpDate,tmpTime) On Error Resume Next BuildDateTime = "[" & _ Right("0" & Day(tmpDate),2) & "/" & _ Left(MonthName(Month(tmpDate)),3) & "/" & _ Year(tmpDate) & ":" & _ Right("0" & Hour(tmpTime),2) & ":" & _ Right("0" & Minute(tmpTime),2) & ":" & _ Right("0" & Second(tmpTime),2) & _ " +0000]" End Function
I hope this helps!
30 October 2006 • by Bob • IIS
The momentum for IIS 7 is gradually building, and I keep seeing great things in the press and several blogs about it. You can read a few details below:
IIS 7 contains a number of great features, and there are a couple of ways that you can get your hands on it for testing without installing the Vista or Longhorn Beta:
Have fun!
20 September 2006 • by Bob • IIS News Item
Okay, it's somewhat self-promoting, but I was cornered by Brett Hill the other day, who is the IIS 7 Evangelist for Microsoft, and he interviewed me for the IIS Show on MSDN's Channel 9:
http://channel9.msdn.com/Shows/IIS_Show
We discuss several topics like IIS history, HTTP modules in IIS 7, componentization in IIS 7, etc.
12 July 2006 • by Bob • FrontPage, Scripting
I had a great question from a customer the other day: "How do you programmatically enumerate how many web sites on a server have the FrontPage Server Extensions installed?" Of course, that's one of those questions that sounds so simple at first, and then you start to think about how to actually go about it and it gets a little more complicated.
The first thought that came to mind was to just look for all the "W3SVCnnnn" subfolders that are located in the "%ALLUSERSPROFILE%\Application Data\Microsoft\Web Server Extensions\50" folder. (These folders contain the "ROLES.INI" files for each installation.) The trouble with this solution is that some folders and files do not get cleaned up when the server extensions are uninstalled, so you'd get erroneous results.
The next thought that came to mind was to check the registry, because each installation of the server extensions will create a string value and subkey named "Port /LM/W3SVC/nnnn:" under the "[HKLM\SOFTWARE\Microsoft\Shared Tools\Web Server Extensions\Ports]" key. Enumerating these keys will give you the list of web sites that have the server extensions or SharePoint installed. The string values that are located under the subkey contain some additional useful information, so I thought that as long as I was enumerating the keys, I might as well enumerate those values.
The resulting script is listed below, and when run it will create a log file that lists all of the web sites that have the server extensions or SharePoint installed on the server that is specified by the "strComputer" constant.
Option Explicit Const strComputer = "localhost" Dim objFSO, objFile Dim objRegistry Dim strRootKeyPath, strSubKeyPath, strValue Dim arrRootValueTypes, arrRootValueNames Dim arrSubValueTypes, arrSubValueNames Dim intLoopA, intLoopB Const HKEY_LOCAL_MACHINE = &H80000002 Const REG_SZ = 1 strRootKeyPath = "Software\Microsoft\" & _ "Shared Tools\Web Server Extensions\Ports" Set objFSO = WScript.CreateObject("Scripting.FileSystemObject") Set objFile = objFSO.CreateTextFile("ServerExtensions.Log") objFile.WriteLine String(40,"-") objFile.WriteLine "Report for server: " & UCase(strComputer) objFile.WriteLine String(40,"-") Set objRegistry = GetObject(_ "winmgmts:{impersonationLevel=impersonate}!\\" & _ strComputer & "\root\default:StdRegProv") objRegistry.EnumValues HKEY_LOCAL_MACHINE, strRootKeyPath, _ arrRootValueNames, arrRootValueTypes For intLoopA = 0 To UBound(arrRootValueTypes) If arrRootValueTypes(intLoopA) = REG_SZ Then objFile.WriteLine arrRootValueNames(intLoopA) strSubKeyPath = strRootKeyPath & _ "\" & arrRootValueNames(intLoopA) objRegistry.EnumValues HKEY_LOCAL_MACHINE, _ strSubKeyPath, arrSubValueNames, arrSubValueTypes For intLoopB = 0 To UBound(arrSubValueTypes) If arrSubValueTypes(intLoopB) = REG_SZ Then objRegistry.GetStringValue HKEY_LOCAL_MACHINE, _ strSubKeyPath, arrSubValueNames(intLoopB), strValue objFile.WriteLine vbTab & _ arrSubValueNames(intLoopB) & "=" & strValue End If Next objFile.WriteLine String(40,"-") End If Next objFile.Close
The script should be fairly easy to understand, and you can customize it to suit your needs. For example, you could change the "strComputer" constant to a string array and loop through an array of servers.
Note: More information about the WMI objects used in the script can be found on the following pages:
Hope this helps!
30 May 2006 • by Bob • FrontPage
30 May 2006 • by Bob • FrontPage
November, 1995 - Vermeer FrontPage 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. |
|
June, 1996 - Microsoft FrontPage 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
|
|
|
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
|
|
|
|
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
|
|
|
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
|
|
|
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
|
|
|
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
|
|
|
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.) |
|
09 May 2006 • by Bob • FTP, IIS
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:
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:
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:
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/
27 March 2006 • by Bob • IIS, Scripting
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/
21 February 2006 • by Bob • IIS, Scripting
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!
01 February 2006 • by Bob • Random Thoughts
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. ;-]