Just a short, simple blog for Bob to share his thoughts.
08 September 2011 • by Bob • Scripting
This past weekend I was writing a quick piece of Windows Script Host (WSH) code to clean up some files on one of my servers, and I had populated a Scripting.Dictionary object with a bunch of string data that I was going to write to a log file. Obviously it's much easier to read through the log file if the data is sorted, but the Scripting.Dictionary object does not have a built-in Sort() method.
With this in mind, I set out to write a sorting function for my script, when I decided that it would might be more efficient to see if someone out in the community had already written such a function. I quickly discovered that someone had - and it turns out, that particular someone was me!
Way back in 1999 I published Microsoft Knowledge Base (KB) article 246067, which was titled "Sorting a Scripting Dictionary Populated with String Data." This KB article contained the following code, which took care of everything for me:
Const dictKey = 1 Const dictItem = 2 Function SortDictionary(objDict,intSort) ' declare our variables Dim strDict() Dim objKey Dim strKey,strItem Dim X,Y,Z ' get the dictionary count Z = objDict.Count ' we need more than one item to warrant sorting If Z > 1 Then ' create an array to store dictionary information ReDim strDict(Z,2) X = 0 ' populate the string array For Each objKey In objDict strDict(X,dictKey) = CStr(objKey) strDict(X,dictItem) = CStr(objDict(objKey)) X = X + 1 Next ' perform a a shell sort of the string array For X = 0 to (Z - 2) For Y = X to (Z - 1) If StrComp(strDict(X,intSort),strDict(Y,intSort),vbTextCompare) > 0 Then strKey = strDict(X,dictKey) strItem = strDict(X,dictItem) strDict(X,dictKey) = strDict(Y,dictKey) strDict(X,dictItem) = strDict(Y,dictItem) strDict(Y,dictKey) = strKey strDict(Y,dictItem) = strItem End If Next Next ' erase the contents of the dictionary object objDict.RemoveAll ' repopulate the dictionary with the sorted information For X = 0 to (Z - 1) objDict.Add strDict(X,dictKey), strDict(X,dictItem) Next End If End Function
Sometimes I make my day.
01 June 2011 • by Bob • IIS, Scripting
I had a question recently where someone was trying to add <clear />
or <remove />
elements to a collection in their IIS 7 configuration settings. With that in mind, for today's blog I thought that I would discuss a couple of ways to add <clear />
and <remove />
elements by using two specific scripting methods: AppCmd and VBScript.
It should be noted that you can also use JavaScript or PowerShell, but I'm not covering those because the syntax for those is available elsewhere. (JavaScript syntax is available in the Configuration Editor in IIS Manager, and the PowerShell syntax is available through the Web Server (IIS) Administration Cmdlet Reference.) You can also use Managed-Code, and the syntax for that is also available in the Configuration Editor in IIS Manager; but compiled code isn't scripting, is it? :-)
Here's the scenario, IIS makes it possible to modify the contents of an inherited collection in two ways:
<configuration> <system.webServer> <defaultDocument enabled="true"> <files> <clear /> </files> </defaultDocument> </system.webServer> </configuration>
<configuration> <system.webServer> <defaultDocument enabled="true"> <files> <remove value="index.html" /> </files> </defaultDocument> </system.webServer> </configuration>
With that in mind, let's look at scripting those settings.
AppCmd.exe is a great utility that ships with IIS 7, which allows editing the configuration settings for IIS from a command line. This also allows you to create batch scripts that automate large numbers of configuration changes. For example, the following batch file enables ASP session state, sets the maximum number of ASP sessions to 1000, and then sets the session time-out to 10 minutes for the Default Web Site:
appcmd.exe set config "Default Web Site" -section:system.webServer/asp /session.allowSessionState:"True" /commit:apphost
appcmd.exe set config "Default Web Site" -section:system.webServer/asp /session.max:"1000" /commit:apphost
appcmd.exe set config "Default Web Site" -section:system.webServer/asp
I'm a big fan of IIS 7's AppCmd.exe, but unfortunately it has two rather large limitations:
These limitations have caused me some grief from time to time, because I often want to script the modification of collections, and I would love to remove items or clear a collection.
<clear />
element using AppCmd:Although it's kind of a hack, there is a way to force AppCmd.exe to add a <clear />
element.
Here's what you need to do in order to clear the list of default documents for the Default Web Site:
<?xml version="1.0" encoding="UTF-8"?> <appcmd> <CONFIG CONFIG.SECTION="system.webServer/defaultDocument" path="MACHINE/WEBROOT/APPHOST" overrideMode="Allow" locked="false"> <system.webServer-defaultDocument enabled="true"> <files> <clear /> </files> </system.webServer-defaultDocument> </CONFIG> </appcmd>
appcmd.exe set config /in "Default Web Site" < CLEAR.xml
Unfortunately this technique does not work for <remove />
elements. :-( But that being said, you can add a <remove />
element through VBScript; for more information, see the Using VBScript section.
Fortunately, VBScript doesn't have AppCmd.exe's limitations, so you can add both <clear />
and <remove />
elements.
<clear />
element in VBScript:The following steps will clear the list of default documents for the Default Web Site:
Set adminManager = WScript.CreateObject("Microsoft.ApplicationHost.WritableAdminManager") adminManager.CommitPath = "MACHINE/WEBROOT/APPHOST/Default Web Site" Set defaultDocumentSection = adminManager.GetAdminSection("system.webServer/defaultDocument", _ "MACHINE/WEBROOT/APPHOST/Default Web Site") Set filesCollection = defaultDocumentSection.ChildElements.Item("files").Collection filesCollection.Clear adminManager.CommitChanges
<remove />
element in VBScript:The following steps will remove a single item from the list of default documents for the Default Web Site:
Set adminManager = WScript.CreateObject("Microsoft.ApplicationHost.WritableAdminManager") adminManager.CommitPath = "MACHINE/WEBROOT/APPHOST/Default Web Site" Set defaultDocumentSection = adminManager.GetAdminSection("system.webServer/defaultDocument", _ "MACHINE/WEBROOT/APPHOST/Default Web Site") Set filesCollection = defaultDocumentSection.ChildElements.Item("files").Collection addElementPos = FindElement(filesCollection, "add", Array("value", "index.html")) If (addElementPos = -1) Then WScript.Echo "Element not found!" WScript.Quit End If filesCollection.DeleteElement(addElementPos) adminManager.CommitChanges Function FindElement(collection, elementTagName, valuesToMatch) For i = 0 To CInt(collection.Count) - 1 Set element = collection.Item(i) If element.Name = elementTagName Then matches = True For iVal = 0 To UBound(valuesToMatch) Step 2 Set property = element.GetPropertyByName(valuesToMatch(iVal)) value = property.Value If Not IsNull(value) Then value = CStr(value) End If If Not value = CStr(valuesToMatch(iVal + 1)) Then matches = False Exit For End If Next If matches Then Exit For End If End If Next If matches Then FindElement = i Else FindElement = -1 End If End Function
For more information about scripting and IIS configuration settings, see the following:
Note: This blog was originally posted at http://blogs.msdn.com/robert_mcmurray/
27 July 2009 • by Bob • IIS, Scripting
I have mentioned in previous blog posts that I tend to write many of my blog posts and walkthroughs for IIS.NET based on code that I've written for myself, and today's blog post is the story of how one of my samples saved my rear over this past weekend.
One of the servers that I manage is used to host web sites for several friends of mine. (It's their hobby to have a web site and it's my hobby to host it for them.) Anyway, sometime on Sunday someone let me know that one of my sites didn't seem to be behaving correctly, so I browsed it with Internet Explorer and saw that I was getting an HTTP 503 error. I've seen this error when an application pool goes offline for some reason, so I didn't panic - yet - because I knew that the web site was in a separate application pool. With that in mind, I browsed to a web site that is in a different application pool. Same thing - HTTP 503 error. This was beginning to concern me.
I logged into the web server and ran iisreset from a command-line - this threw the following error - and now I was really starting to become agitated:
CMD>iisreset
Attempting stop...
Internet services successfully stopped
Attempting start...
Restart attempt failed.
The IIS Admin Service or the World Wide Web Publishing Service, or a service dependent on them failed to start. The service, or dependent services, may had an error during its startup or may be disabled.
CMD>
I knew that the cause of the error should be in the Windows Event Viewer, so I opened the System log in Event Viewer and saw the following error:
Log Name: | System |
---|---|
Source: | Microsoft-Windows-WAS |
Date: | 7/26/2009 10:59:52 AM |
Event ID: | 5172 |
Task Category: | None |
Level: | Error |
Keywords: | Classic |
User: | N/A |
Computer: | MYSERVER |
Description: | The Windows Process Activation Service encountered an error trying to read configuration data from file '\\?\C:\Windows\system32\inetsrv\config\applicationHost.config', line number '308'. The error message is: 'Configuration file is not well-formed XML'. The data field contains the error number. |
Event Xml: | |
<Event xmlns="http://schemas.microsoft.com/win/2004/08/events/event"> |
Now that I was armed with the file name and line number of the failure in my configuration settings, I was able to go straight to the source of the problem. (I love IIS 7's descriptive error messages - don't you?) Once I opened the file and jumped to the correct location, I saw several lines of unintelligible garbage. For reasons that are still unknown to me - my applicationHost.config file had become corrupted and IIS was dead in the water until I fixed the problem. I looked through the file and removed most of the garbage and saved the edited file to IIS - this got the web sites working, but only partially. Some necessary settings had obviously been removed while I was clearing all of out the unintelligible garbage, and it might take me a long time to discover what those settings were.
The next thing that I did was to take a look in my two readily-accessible backup drives; I have two external hard drives that keep a backup of the web server - one hard drive is directly plugged into the web server via a USB cable, and the other hard drive is plugged into a physically separate server that rotates drives with off-site storage on a monthly basis. The problem is, my weekly backups had just run, so the copy in each backup location had been overwritten with the corrupted version. (I'm going to have to rethink my backup strategy after this - but that's another story.) The backup copy in my off-site storage location should be intact, but that copy would be a few weeks old so I would be missing some settings, and I would have to drive an hour or so round-trip in order to pick up the drive. This wasn't an ideal solution - but it was definitely a feasible strategy.
It was at this point that I remembered that I had written following blog post some time ago:
Automating IIS 7 Backups
/post/automating-iis-7-backups
I wrote the script in that blog post for the server that I was currently managing, and because of this preventative measure I had dozens of backups going back several weeks to choose from. So I was able to quickly find a copy with no corruption and I restored that copy to my IIS config directory. At this point all of my web sites came online with all of their functionality. Having fixed the major issues, I used WinDiff to verify any settings that might have been changed between the restored copy and the corrupted copy.
So in conclusion, this story had a happy ending, and it left me with a few lessons learned:
That sums it up for today's post.
Note: This blog was originally posted at http://blogs.msdn.com/robert_mcmurray/
24 December 2008 • by Bob • Windows
Some time ago a friend of mine gave me a bunch of JPG files, but for some reason she had two copies of every image in the collection. The names of the images had all been randomized, and since there were hundreds of files in the collection it would have taken hours to find and delete the duplicates. With that in mind, I wrote the following batch file that loops through the collection of files and does a binary comparison to find and delete duplicate files.
To use the example code, copy the batch file code from below into Notepad and save it as "_del_dupes.cmd" in the folder where you have duplicate files
Note: As with many utilities that I write - this is a destructive operation, meaning that it will delete files without prompting, so you should always make a backup just in case something goes terribly wrong... ;-]
@echo off dir *.jpg /b > _del_dupes.1.txt for /f "delims=|" %%a in (_del_dupes.1.txt) do ( if exist "%%a" ( dir *.jpg /b > _del_dupes.2.txt for /f "delims=|" %%b in (_del_dupes.2.txt) do ( if not "%%a"=="%%b" ( echo Comparing "%%a" to "%%b"... fc /b "%%a" "%%b">NUL if errorlevel 1 ( echo DIFFERENT ) else ( echo SAME del "%%b" ) ) ) ) ) del _del_dupes.?.txt
08 March 2008 • by Bob • IIS, Scripting
Many years ago I wrote the following KB article:
Truth be told, I wrote the script in that article to help me manage several servers that I controlled. Once I finished the script, I found myself routinely giving it out to customers in order for them to automate their backups, so I decided to turn it into a KB. When IIS 6 came out, Microsoft shipped the IIsBack.vbs script to help customers automate backups.
One of the great things in IIS 7 is the deprecation of the metabase, which has been replaced by applicationHost.config, but the need for backing up your configuration settings is still there. With this in mind, I wrote a small batch file that I schedule to create backups of my configuration settings using the APPCMD utility. Since I've been giving this to customers at Microsoft TechEd, I thought it might make a nice blog post for everyone that can't make it to TechEd.
To use the script, copy the code below into Windows Notepad, then save it to your computer as "BackupIIS.cmd". (I usually save it in "%WinDir%\System32\Inetsrv", but you could save it to your executable search path as well.)
@echo off cls pushd "%WinDir%\System32\inetsrv" echo.| date | find /i "current">datetime1.tmp echo.| time | find /i "current">datetime2.tmp for /f "tokens=1,2,3,4,5,6" %%i in (datetime1.tmp) do ( echo %%n>datetime1.tmp ) for /f "tokens=1,2,3,4,5,6" %%i in (datetime2.tmp) do ( echo %%m>datetime2.tmp ) for /f "delims=/ tokens=1,2,3" %%i in (datetime1.tmp) do ( set TMPDATETIME=%%k%%i%%j ) for /f "delims=:. tokens=1,2,3,4" %%i in (datetime2.tmp) do ( set TMPDATETIME=D%TMPDATETIME%T%%i%%j%%k%%l ) appcmd add backups %TMPDATETIME% del datetime1.tmp del datetime2.tmp set TMPDATETIME= popd echo.
You can use Task Scheduler in Windows Server 2008's Server Manager to schedule this script to run at whatever interval you choose, although I usually schedule it to run once a week.
Backups will be created in the following path:
%WinDir%\System32\Inetsrv\Backups\DyyyymmddThhmmssii
Where yyyymmdd is the year, month, day, and hhmmssii is the hour, minute, second, millisecond for the time of the backup.
I hope this helps!
Note: This blog was originally posted at http://blogs.msdn.com/robert_mcmurray/
25 October 2007 • by Bob • Scripting
I had a great question in follow up to the "Secure, Simplified Web Publishing using Microsoft Internet Information Services 7.0" webcast that I delivered the other day, "How you can you programmatically access the quota usage information from the File Server Resource Manager (FSRM)?"
First of all, there is a native API for writing code to access FSRM data detailed at the following URL:
http://msdn2.microsoft.com/en-us/library/bb625489.aspx
That's a bit of overkill if you're just looking to script something.
There is a WMI interface as well, but it’s only for FSRM events.
So that leaves you with a pair of command-line tools that you can script in order to list your quota usage information:
Right out of the box the first command-line tool, storrept.exe, can generate a detailed XML report using a user-definable scope. To see this in action, take the following example syntax and modify the scope parameter to your desired paths:
storrept.exe reports generate /Report:QuotaUsage /Format:XML /Scope:"C:\"
You can also specify multiple paths in your scope using a pipe-delimited format like:
/Scope:"C:\Inetpub|D:\Inetpub"
When the command has finished, it will tell you the path to your report like the following example:
Storage reports generated successfully in "C:\StorageReports\Interactive".
The XML-based information in the report can then be consumed with whatever method you usually use to parse XML. It should be noted that storrept.exe also supports the following formats: CSV, DHTML, HTML, and TXT.
This XML might be okay for most applications, but for some reason I wanted to customize the information that I received, so I experimented with the second command-line tool, dirquota.exe, to get the result that I was looking for.
First of all, using dirquota.exe quota list returns information in the following format:
Quotas on machine SERVER: Quota Path: C:\inetpub\ftproot Source Template: 100 MB Limit (Matches template) Quota Status: Enabled Limit: 100.00 MB (Hard) Used: 1.00 KB (0%) Available: 100.00 MB Peak Usage: 1.00 KB (10/25/2007 2:15 PM) Thresholds: Warning ( 85%): E-mail Warning ( 95%): E-mail, Event Log Limit (100%): E-mail, Event Log
This information is formatted nicely and is therefore easily parsed, so I wrote the following batch file called "dirquota.cmd" to start things off:
@echo off echo Processing the report... dirquota.exe quota list > dirquota.txt cscript.exe //nologo dirquota.vbs
Next, I wrote the following vbscript application called "dirquota.vbs" to parse the output into some easily-usable XML code:
Option Explicit Dim objFSO, objFile1, objFile2 Dim strLine, strArray(2) Dim blnQuota,blnThreshold ' create objects Set objFSO = WScript.CreateObject("Scripting.FileSystemObject") Set objFile1 = objFSO.OpenTextFile("dirquota.txt") Set objFile2 = objFSO.CreateTextFile("dirquota.xml") ' start the XML output file objFile2.WriteLine "<?xml version=""1.0""?>" objFile2.WriteLine "<Quotas>" ' set the runtime statuses to off blnQuota = False blnThreshold = False ' loop through the text file Do While Not objFile1.AtEndOfStream ' get a line from the file strLine = objFile1.ReadLine ' only process lines with a colon character If InStr(strLine,":") Then ' split the string manually at the colon character strArray(1) = Trim(Left(strLine,InStr(strLine,":")-1)) strArray(2) = Trim(Mid(strLine,InStr(strLine,":")+1)) ' filter on strings with parentheses strLine = strArray(1) If InStr(strLine,"(") Then strLine = Trim(Left(strLine,InStr(strLine,"(")-1)) & "*" End If ' process the inidivdual entries Select Case UCase(strLine) ' a quota path signifies a new record Case UCase("Quota Path") ' close any open threshold collections If blnThreshold = True Then objFile2.WriteLine "</Thresholds>" End If ' close an open quota element If blnQuota= True Then objFile2.WriteLine "</Quota>" End If ' signify a new quota element objFile2.WriteLine "<Quota>" ' output the relelvant information objFile2.WriteLine FormatElement(strArray(1),strArray(2)) ' set the runtime statuses blnQuota= True blnThreshold = False ' these bits of informaiton are parts of a quota Case UCase("Source Template"), UCase("Quota Status"), _ UCase("Limit"), UCase("Used"), _ UCase("Available"), UCase("Peak Usage") ' close any open threshold collections If blnThreshold = True Then objFile2.WriteLine "</Thresholds>" End If ' set the runtime status blnThreshold = False ' output the relelvant information objFile2.WriteLine FormatElement(strArray(1),strArray(2)) ' these bits of informaiton are thresholds Case UCase("Warning*"), UCase("Limit*") ' open a threshold collection if not already open If blnThreshold = False Then objFile2.WriteLine "<Thresholds>" End If ' output the relelvant information objFile2.WriteLine FormatElement( _ Left(strLine,Len(strLine)-1), _ Replace(Mid(strArray(1), _ Len(strLine))," ","") & " " & strArray(2)) ' set the runtime status blnThreshold = True End Select End If Loop ' close any open threshold collections If blnThreshold = True Then objFile2.WriteLine "</Thresholds>" End If ' close an open quota element If blnQuota= True Then objFile2.WriteLine "</Quota>" End If ' end the XML output file objFile2.WriteLine "</Quotas>" objFile1.Close objFile2.Close Set objFSO = Nothing ' format data into an XML element Function FormatElement(tmpName,tmpValue) FormatElement = "<" & Replace(tmpName," ","") & _ ">" & tmpValue & "</" & Replace(tmpName,Chr(32),"") & ">" End Function
When the batch file and vbscript are run, they will create a file named "dirquota.xml" which will resemble the following example XML:
<?xml version="1.0"?> <Quotas> <Quota> <QuotaPath>C:\inetpub\ftproot</QuotaPath> <SourceTemplate>100 MB Limit (Matches template)</SourceTemplate> <QuotaStatus>Enabled</QuotaStatus> <Limit>100.00 MB (Hard)</Limit> <Used>1.00 KB (0%)</Used> <Available>100.00 MB</Available> <PeakUsage>1.00 KB (10/25/2007 2:15 PM)</PeakUsage> <Thresholds> <Warning>(85%) E-mail</Warning> <Warning>(95%) E-mail, Event Log</Warning> <Limit>(100%) E-mail, Event Log</Limit> </Thresholds> </Quota> </Quotas>
I found the above XML much easier to use than the XML that came from the storrept.exe report, but I'm probably comparing apples to oranges. In any event, I hope this helps someone with questions about FSRM reporting.
Have fun!
Note: This blog was originally posted at http://blogs.msdn.com/robert_mcmurray/
06 July 2007 • by Bob • FTP, Scripting
A few weeks ago my friend Jaroslav posted a blog entry about viewing the current FTP7 sessions using Javascript, and I followed that up with a blog post about viewing the current FTP7 sessions using VBScript.
This blog entry follows up on those postings by showing you how to view the current FTP7 sessions using C#. To do so, start a new Windows Console Application project using C# in Visual Studio 2005 on a computer running Windows Server 2008 with the new FTP7 server installed. You will need to add a reference to the AppHostAdminLibrary by manually browsing to the nativerd.dll file that's located in the %WinDir%\System32\InetSrv folder. After you've added the reference, replace all of the C# code from the project template with the following C# code:
using System; using System.Collections.Generic; using System.Text; using AppHostAdminLibrary; namespace FtpDumpSessions { class FtpDumpSessions { static void Main(string[] args) { AppHostWritableAdminManager objAdminManager = new AppHostWritableAdminManager(); // get the collection of sites IAppHostElement objSitesElement = objAdminManager.GetAdminSection( "system.applicationHost/sites", "MACHINE/WEBROOT/APPHOST"); uint intSiteCount = objSitesElement.Collection.Count; Console.WriteLine( "Site count: {0}", intSiteCount); try { // loop through the sites collection for (int intSite = 0; intSite < intSiteCount; ++intSite) { // get a site IAppHostElement objFtpSite = objSitesElement.Collection[intSite]; // get the FTP section IAppHostElement objFtpSiteElement = objFtpSite.ChildElements["ftpServer"]; // get the sessions collection IAppHostElement objFtpSessions = objFtpSiteElement.ChildElements["sessions"]; uint intSessionCount = objFtpSessions.Collection.Count; Console.WriteLine( "\tFTP sessions for {0}: {1}", objFtpSite.Properties["name"].Value, intSessionCount); // loop through the sessions for (int intSession = 0; intSession < intSessionCount; ++intSession) { IAppHostElement objFtpSession = objFtpSessions.Collection[intSession]; // loop through each session's properties for (int intProperty = 0; intProperty < objFtpSession.Properties.Count; ++intProperty) { Console.WriteLine( "\t\t{0}: {1}", objFtpSession.Properties[intProperty].Name, objFtpSession.Properties[intProperty].Value); } } } } catch (System.Exception ex) { Console.WriteLine( "\r\nError: {0}", ex.Message); } } } }
When you compile and run the project, you should see a listing of all users connected to your FTP7 sites.
That's about it for this post - have fun!
06 July 2007 • by Bob • FTP, Scripting
A few weeks ago my friend Jaroslav posted a blog entry about viewing the current FTP7 sessions using Javascript, and I followed that up with a blog post about viewing the current FTP7 sessions using C#.
This blog entry follows up on those postings by showing you how to view the current FTP7 sessions using VBScript. To do so, copy the following VBScript code to Windows Notepad and save the file as "ftp_sessions.vbs" on a computer running Windows Server 2008 with the new FTP7 server installed:
Option Explicit Dim objAdminManager, objSiteCollection, objFtpSiteElement Dim objSite, objFtpSession, objFtpSessions, objFtpProperty Dim intSite, intFtpSession, intFtpProperty Dim intSiteCount, intFtpSessionCount, intFtpPropertyCount Set objAdminManager = WScript.CreateObject("Microsoft.ApplicationHost.AdminManager") ' get the collection of sites Set objSiteCollection = objAdminManager.GetAdminSection( _ "system.applicationHost/sites", "MACHINE/WEBROOT/APPHOST" ) intSiteCount = CInt(objSiteCollection.Collection.Count) WScript.Echo String(40,"*") WScript.Echo "Site count: " & intSiteCount WScript.Echo String(40,"*") ' loop through the sites collection For intSite = 0 To intSiteCount-1 ' get a site Set objSite = objSiteCollection.Collection.Item(intSite) ' get the FTP section Set objFtpSiteElement = objSite.ChildElements.Item("ftpServer") ' get the sessions collection Set objFtpSessions = objFtpSiteElement.ChildElements.Item("sessions") intFtpSessionCount = CInt(objFtpSessions.Collection.Count) WScript.Echo String(40,"=") WScript.Echo "FTP sessions for " & _ objSite.Properties.Item("name").Value & _ ": " & intFtpSessionCount WScript.Echo String(40,"=") ' loop through the sessions For intFtpSession = 0 To intFtpSessionCount - 1 Set objFtpSession = objFtpSessions.Collection.Item(intFtpSession) intFtpPropertyCount = CInt(objFtpSession.Properties.Count) ' loop through each session's properties For intFtpProperty = 0 To intFtpPropertyCount - 1 Set objFtpProperty = objFtpSession.Properties.Item(intFtpProperty) WScript.Echo CStr(objFtpProperty.Name) & ": " & CStr(objFtpProperty.Value) Next WScript.Echo String(40,"-") Next Next
To make sure that you don't see any message box pop-ups, run the script from the command-line using the following syntax:
cscript.exe ftp_sessions.vbs
That's about it for this post - have fun!
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/