Just a short, simple blog for Bob to share his thoughts.
11 May 2007 • by Bob • Scripting, MIDI
OK - I have to admit, when you realize that you are making software choices based on scripting language support you start to get the feeling that there are times when you just have to accept the fact that you are a geek.
Here's a case in point: I write music as a hobby, and when shopping for a program to write sheet music with, I chose Sibelius because I discovered that they have a really cool scripting language called "ManuScript". OK - so the name is kind of silly, but it's pretty cool to write code with.
The way it works is that you create what Sibelius calls a "plug-in", and you assign it to a category that will be used as the menu under which your plug-in will be displayed. Once you've done all that, you can start writing code.
For example, I needed to add sustain pedal MIDI events to an entire piano score, and doing so manually would have been a tedious exercise. So I made my life easier and created a quick plug-in that adds the MIDI events to apply the sustain pedal at full level to the beginning of every measure, and then adds the MIDI events to lift the sustain pedal at the end of every measure:
// Verify that a score is open. if (Sibelius.ScoreCount=0) { Sibelius.MessageBox("Please open a score."); return false; } // Retrieve a score object for the active score. score = Sibelius.ActiveScore; // Retrieve an object for the current selection. selection = score.Selection; if (selection.IsPassage) { // Loop through the highlighted measures. for each Bar b in selection { // Add MIDI sustain pedal events. b.AddText(1,"~C64,127",TechniqueTextStyle); b.AddText(b.Length,"~C64,0",TechniqueTextStyle); }
// Return a status message. Sibelius.MessageBox("Finished."); }
I should point out, however, that this is meant to be a brief example of what you can do. Running this same plug-in on the same selection will re-add the sustain pedal events to your score; I didn't add any advanced logic to check for the existence of any prior sustain pedal events. If anyone wants to take on that challenge, have fun and don't forget to share your results!
02 January 2007 • by Bob • FrontPage, Scripting
As the old adage says, "Necessity is the mother of invention." With that in mind, I had a friend drop by my office the other day and ask me a question that started me on another quest for code.
What he asked me was whether there was a way where he could create an off-line backup of his web site. Of course, there are whole sections of the industry these days that are devoted to such things, but he wanted a simple way to create a backup on his home or work computer of his web site that is hosted at an ISP. Some time ago I wrote a FrontPage VBA macro for another friend that could be used to automate publishing, but only from within the FrontPage application itself. Since the FrontPage application exists as a COM object, I theorized that I could rewrite the code from the macro into a Windows Script Host (WSH) application that should do the trick. The code that you see below is the results of my little 'experiment'.
Usage Notes:
Once you have taken the above items into account, copy & paste the script code into Notepad and save it to your computer with a "*.vbs" file extension. To execute the code, just double-click the script. The script will pop-up a message box when it has finished publishing a copy of the web site to your computer.
Option Explicit ' -------------------------------------------------- ' ' Declare our constants. ' ' -------------------------------------------------- Const fpPublishAddToExistingWeb = 2 Const fpPublishCopySubwebs = 4 Const fpPublishLogInTempDir = 8 Const fpPublishCopyAllFiles = 64 ' -------------------------------------------------- ' ' This section defines the publishing variables. ' ' -------------------------------------------------- Dim strSourceUrl, strDestinationFolder Dim strUsername, strPassword Dim strBackupDate, strBackupTime strBackupDate = Cstr(Year(Date())) & _ Right("00" & Cstr(Month(Date())),2) & _ Right("00" & Cstr(Day(Date())),2) strBackupTime = Right("00" & Cstr(Hour(Time())),2) & _ Right("00" & Cstr(Minute(Time())),2) & _ Right("00" & Cstr(Second(Time())),2) strSourceUrl = "https://www.example.com/" strDestinationFolder = "c:\Backups\www.example.com\" & _ strBackupDate & "_" & strBackupTime strUsername = "server\administrator" strPassword = "Password1" ' -------------------------------------------------- ' ' This section checks to see if FrontPage is ' installed, and exits if it is not installed. ' ' -------------------------------------------------- ' wait 10 seconds to "debounce" the server WScript.Sleep 10000 ' get a FrontPage Application object Dim objFP: Set objFP = WScript.CreateObject("FrontPage.Application") ' exit if the object does not exist If Err.Number = -2147352567 Then WScript.Quit ' -------------------------------------------------- ' ' This section publishes the webs. ' ' -------------------------------------------------- ' sanitize the publishing path strDestinationFolder = CleanPath(strDestinationFolder) ' only continue the path can actually be created If MakePath(strDestinationFolder) = True Then ' open the root web on the source objFP.Webs.Open strSourceUrl, strUsername, strPassword ' publish the root web to the destination objFP.ActiveWeb.Publish strDestinationFolder, _ fpPublishAddToExistingWeb + fpPublishCopySubwebs + fpPublishCopyAllFiles + fpPublishLogInTempDir ' close the root web objFP.ActiveWeb.Close End If ' -------------------------------------------------- ' ' This section cleans up and exits. ' ' -------------------------------------------------- Set objFP = Nothing WScript.Quit ' ---------------------------------------- ' ' This function builds a path ' ' PASS: File path to construct ' RETURN: TRUE/FALSE for success/failure ' ' ---------------------------------------- Function MakePath(tmpText) On Error Resume Next Dim tx,ty,tz Dim tmpFSO Dim blnTempStatus Set tmpFSO = WScript.CreateObject("Scripting.FileSystemObject") blnTempStatus = True ty = Split(tmpText,"\") For tx = 0 To UBound(ty) tz = tz & ty(tx) & "\" If tmpFSO.FolderExists(tz) = False Then tmpFSO.CreateFolder(tz) If Err.Number <> 0 Then blnTempStatus = False End If Next MakePath = blnTempStatus End Function ' ---------------------------------------- ' ' This function sanitizes a path for valid characters ' ' PASS: File path to construct ' RETURN: New path ' ' ---------------------------------------- Function CleanPath(tmpText) On Error Resume Next Const tmpValid = "\.-1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" Dim tx,ty,tz For tx = 1 To Len(tmpText) ty = Mid(tmpText,tx,1) If (InStr(tmpValid,ty)>0) Or (tx=2 and ty=":") Then tz = tz & ty Else tz = tz & "_" End If Next CleanPath = tz End Function
Happy coding!
Note: This blog was originally posted at http://blogs.msdn.com/robert_mcmurray/
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!
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!
11 January 2006 • by Bob • IIS, Scripting
(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/
01 December 2005 • by Bob • IIS, Scripting
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!
04 October 2005 • by Bob • IIS, Scripting, IIS 6
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!