#### Also See...

You should also check out my non-technology blog at:

http://www.bobsbasement.net/

## Using ASX Files with Windows Media Center

Like a lot of Windows geeks and fanboys, I use Windows Media Center on a Windows 7 system as my Digital Video Recorder (DVR) and media library. My system consists of a Dell GX270 computer with a ZOTAC NVIDIA GeForce GT610 video card, and it uses an InfiniTV 6 ETH tuner to receive cable signals. This setup has served us faithfully for years, and it is the center piece of our home entertainment system. If you're not familiar with Windows Media Center, that's because it's a rather hideously under-advertised feature of Windows. Just the same, here is an official Microsoft teaser for it:

But I've done a few extra things with my Windows Media Center that are a little beyond the norm, and one of the biggest items that I spent a considerable amount of time and effort digitizing my entire collection of DVD and Blu-ray discs as MP4 files, and I store them on a Thecus NAS that's on my home network which I use for media libraries on my Windows Media Center. This allows me to have all of my movies available at all times, and I can categorize them into folders which show up under the "Videos" link on the Windows Media Center menu.

That being said, there's a cool trick that I've been using to help customize some of my movies. Some of the movies that I have encoded have some material that I'd like to cut out, (like excessive opening credits and lengthy intermissions), but I don't want to edit and re-encode each MP4 file. Fortunately, Windows Media Center supports Advanced Stream Redirector (ASX) files, which allows me to customize what parts of a video are seen without having to edit the actual video.

Here's a perfect example: I recently purchased the 50th Anniversary Collector's Edition of Lawrence of Arabia on Blu-ray. The film is one of my favorites, and this reissue on Blu-ray is phenomenal. That being said, the movie begins with a little over four minutes of a blank screen while the musical overture plays. In addition, there is an additional eight minutes of a blank screen while the music for intermission is played. This is obviously less than desirable, so I created an ASX file which skips the opening overture and intermission.

By way of explanation, ASX files are XML files which define a playlist for media types, which can be any supported audio or video media. The individual entries can define various metadata about each media file, and thankfully can be used to specify which parts of a media file will be played.

With that in mind, here's what the ASX file that I created for Lawrence of Arabia looks like:

<ASX VERSION="3.0">
<!-- Define the title for the movie. -->
<TITLE>Lawrence Of Arabia</TITLE>
<!-- Specify the movie's author. -->
<AUTHOR>Columbia Pictures</AUTHOR>
<!-- List the copyright for the movie. -->
<ENTRY>
<!-- Define the video file for this entry. -->
<REF HREF="Lawrence Of Arabia.mp4" />
<!-- Define the start time for this entry. -->
<STARTTIME VALUE="00:04:17.0"/>
<!-- Define the duration for this entry. -->
<DURATION VALUE="02:15:07.0"/>
</ENTRY>
<ENTRY>
<!-- Define the video file for this entry. -->
<REF HREF="Lawrence Of Arabia.mp4" />
<!-- Define the start time for this entry. -->
<STARTTIME VALUE="02:23:38.0"/>
</ENTRY>
</ASX>

The XML comments explain what each of the lines in the file is configuring, and it should be straight-forward. But I would like to describe a few additional details:

• Individual media entries are obviously defined in a collection of <ENTRY> elements, and in this example I have defined two entries:
• The first entry defines a <STARTTIME> and <DURATION> which skip over the overture and play up to the intermission.
• The second entry defines a <STARTTIME> which starts after the intermission and plays through the end of the movie.
• The other metadata in the file - like the <AUTHOR> and <COPYRIGHT> - is just for me. That information is optional, but I like to include it.

There are several other pieces of metadata which can be configured, and a list of those are defined in the Windows Media Metafile Elements Reference and ASX Elements Reference.

Posted: Feb 16 2014, 00:30 by Bob | Comments (0) |
• Currently 0/5 Stars.
• 1
• 2
• 3
• 4
• 5
Social Bookmarks: E-mail | Kick it! | DZone it! | del.icio.us

## Rapid PHP Deployment for IIS using a Batch File

Whenever I am delivering a presentation where I need to use PHP, I typically use a batch file that I wrote in order to rapidly deploy PHP on the system that I am using for my demos. The batch file usually takes less than a second to run, which always seems to amaze people in the audience. As a result, I usually have several people ask me for my batch file after each presentation, so I thought that it would make a good subject for today's blog.

I should mention that I have used this batch file in order to demonstrate PHP with IIS in a variety of scenarios, and one of my favorite demos is when I would borrow someone's laptop and plug in a flash drive where I had IIS Express pre-installed, and then I would run the batch file in this blog to deploy PHP. Next I would launch IIS Express, open a web browser on their system, and then browse to http://localhost/ in order to show that IIS Express was working correctly. Lastly I would write a simple PHP "Hello World" page to show that PHP was up-and-running on their system in a matter of seconds.

That being said, I have to point out that there is a very important prerequisite that you must have in order to follow the steps in the blog: you need to start with a known-good installation of PHP from one of your systems, and I'll explain what I mean by that.

My batch file expects to find a folder containing ready-to-run files for PHP in order to deploy PHP on a new system. I originally obtained my PHP files by using the Web Platform Installer (WebPI) to install PHP, and then I copied the files to my flash drive or some other repository. (Note that WebPI usually installs PHP in the "%ProgramFiles(x86)%\PHP" folder.) If you don't want to use WebPI, you can also download PHP from http://windows.php.net/, but you're on your own for configuration.

Once I have the files from a known-good installation of PHP, I create the following folder structure in the location where I will be storing the files that I use to deploy PHP on other systems:

• <root folder>
• SETUP_PHP.cmd (the batch file from this blog)
• PHP (the folder containing the PHP files)
• PHP.INI
• PHP-CGI.EXE
• etc. (all of the remaining PHP files and folders)

One thing to note is that the PHP.INI file you use may contain paths which refer to specific directories on the system from which you are copying your PHP files, so you need to make sure that those paths will exist on the system where you deploy PHP.

Here is an example: when I used WebPI to install PHP 5.5 on a system with IIS, it installed PHP into my "%ProgramFiles(x86)%\PHP\v5.5" folder. During the installation process, WebPI updated the PHP file to reflect any paths that need to be defined. At the time that I put together my notes for this blog, those updates mainly applied to the path where PHP expects to find it's extensions:

extension_dir="C:\Program Files (x86)\PHP\v5.5\ext\"

What this means is - if you want to deploy PHP to some other path on subsequent systems, you will need to update at least that line in the PHP.INI file that you are using to deploy PHP. In my particular case, I prefer to deploy PHP to the "%SystemDrive%\PHP" path, but it can be anywhere as long as you update everything accordingly.

The following batch file will deploy the PHP files in the "%SystemDrive%\PHP" folder on your system, and then it will update IIS with the necessary settings for this PHP deployment to work:

@echo off

REM Change to the installation folder
pushd "%~dp0"

REM Cheap test to see if IIS is installed
if exist "%SystemRoot%\System32\inetsrv" (
REM Check for the PHP installation files in a subfolder
if exist "%~dp0PHP" (
REM Check for an existing installation of PHP
if not exist "%SystemDrive%\PHP" (
REM Create the folder for PHP
md "%SystemDrive%\PHP"
REM Deploy the PHP files
xcopy /erhky "%~dp0PHP\*" "%SystemDrive%\PHP"
)
pushd "%SystemRoot%\System32\inetsrv"
REM Configure the IIS settings for PHP
appcmd.exe set config -section:system.webServer/fastCgi /+"[fullPath='%SystemDrive%\PHP\php-cgi.exe',monitorChangesTo='php.ini',activityTimeout='600',requestTimeout='600',instanceMaxRequests='10000']" /commit:apphost
appcmd.exe set config -section:system.webServer/fastCgi /+"[fullPath='%SystemDrive%\PHP\php-cgi.exe',monitorChangesTo='php.ini',activityTimeout='600',requestTimeout='600',instanceMaxRequests='10000'].environmentVariables.[name='PHP_FCGI_MAX_REQUESTS',value='10000']" /commit:apphost
appcmd.exe set config -section:system.webServer/fastCgi /+"[fullPath='%SystemDrive%\PHP\php-cgi.exe',monitorChangesTo='php.ini',activityTimeout='600',requestTimeout='600',instanceMaxRequests='10000'].environmentVariables.[name='PHPRC',value='%SystemDrive%\PHP']" /commit:apphost
appcmd.exe set config -section:system.webServer/handlers /+"[name='PHP_via_FastCGI',path='*.php',verb='GET,HEAD,POST',modules='FastCgiModule',scriptProcessor='%SystemDrive%\PHP\php-cgi.exe',resourceType='Either']" /commit:apphost
popd
)
)
popd

Once you have all of that in place, it usually takes less than a second to deploy PHP, which is why so many people seem interested during my presentations.

Note that you can deploy PHP for IIS Express just as easily by updating the "%SystemRoot%\System32\inetsrv" paths in the batch file to "%ProgramFiles%\IIS Express" or "%ProgramFiles(x86)%\IIS Express" paths. You can also use this batch file as part of a deployment process for PHP within a web farm; in which case, you will need to pay attention to the paths inside your PHP.INI file which I mentioned earlier.

Note: This blog was originally posted at http://blogs.msdn.com/robert_mcmurray/
Posted: Jan 30 2014, 16:05 by Bob | Comments (0) |
• Currently 0/5 Stars.
• 1
• 2
• 3
• 4
• 5
Filed under: IIS | Scripting | IIS Express | PHP
Social Bookmarks: E-mail | Kick it! | DZone it! | del.icio.us

## Updating my HTML Application for Configuring your WebDAV Redirector Settings

A couple of years ago I wrote a blog that was titled "How to create an HTML Application to configure your WebDAV Redirector settings", where I showed how to use HTMLA to create a simple editor for most of the WebDAV Redirector settings. These settings have no other user interface, so prior to my blog post users had to manually edit the registry in order to modify their WebDAV Redirector settings.

 Click image to expand

In the past two years since I wrote that blog, I have found myself using that simple application so often that I now keep it in my personal utilities folder on my SkyDrive so I can have it with me no matter where I am travelling. But that being said, I ran into an interesting situation the other day that made me want to update the application, so I thought that it was time to write a new blog with the updated changes.

Here's what happened - I had opened my application for modifying my WebDAV Redirector settings, but then something happened which distracted me, and then I headed off to lunch before I committed my changes to the registry. When I came back to my office, I noticed that my WebDAV Redirector settings application was still open and I clicked the Exit Application button. The application popped up a dialog which informed me that I had changes that hadn't been saved to the registry, but I forgot what they were. This put me in a quandary - I could simply click Yes and hope for the best, or I could click No and lose whatever changes that I had made and re-open the application to start over.

It was at that time that I thought to myself, "If only I had a Reset Values button..."

By now you can probably see where this blog is going, and here's what the new application looks like - it's pretty much the same as the last application, with the additional button that allows you to reset your values without exiting the application. (Note - the application will prompt you for confirmation if you attempt to reset the values and you have unsaved changes.)

 Click image to expand

#### Creating the Updated HTML Application

To create this HTML Application, you need to use the same steps as my last blog: save the following HTMLA code as "WebDAV Redirector Settings.hta" to your computer, and then double-click its icon to run the application.

<html>

<title>WebDAV Redirector Settings</title>
<HTA:APPLICATION
APPLICATIONNAME="WebDAV Redirector Settings"
ID="WebDAV Redirector Settings"
VERSION="1.0"
BORDER="dialog"
BORDERSTYLE="static"
INNERBORDER="no"
MAXIMIZEBUTTON="no"
MINIMIZEBUTTON="no"
SCROLL="no"
SCROLLFLAT="yes"
SINGLEINSTANCE="yes"
SELECTION="no"/>

<script language="vbscript">

' ----------------------------------------
' Start of main code section.
' ----------------------------------------

Option Explicit

Const intDialogWidth = 700
Const intDialogHeight = 620
Const HKEY_LOCAL_MACHINE = &H80000002
Const strWebClientKeyPath = "SYSTEM\CurrentControlSet\Services\WebClient\Parameters"
Const strLuaKeyPath = "Software\Microsoft\Windows\CurrentVersion\Policies\System"
Dim objRegistry
Dim blnHasChanges

' ----------------------------------------
' Start the application.
' ----------------------------------------

On Error Resume Next
' Set up the UI dimensions.
Self.resizeTo intDialogWidth,intDialogHeight
Self.moveTo (Screen.AvailWidth - intDialogWidth) / 2, _
(Screen.AvailHeight - intDialogHeight) / 2
' Retrieve the current settings.
Document.all.TheBody.ClassName = "hide"
Set objRegistry = GetObject( _
"winmgmts:{impersonationLevel=impersonate}!\\.\root\default:StdRegProv")
Call CheckForLUA()
Call GetValues()
Document.All.TheBody.ClassName = "show"
End Sub

' ----------------------------------------
' Check for User Access Control
' ----------------------------------------

Sub CheckForLUA()
If GetRegistryDWORD(strLuaKeyPath,"EnableLUA",1)<> 0 Then
MsgBox "User Access Control (UAC) is enabled on this computer." & _
vbCrLf & vbCrLf & "UAC must be disabled in order to edit " & _
"the registry and restart the service for the WebDAV Redirector. " & _
"Please disable UAC before running this application again. " & _
"This application will now exit.", _
vbCritical, "User Access Control"
Self.close
End If
End Sub

' ----------------------------------------
' Exit the application.
' ----------------------------------------

Sub ExitApplication()
If blnHasChanges = False Then
If MsgBox("Are you sure you want to exit?", _
vbQuestion Or vbYesNo Or vbDefaultButton2, _
"Exit Application") = vbNo Then
Exit Sub
End If
Else
Dim intRetVal
intRetVal = MsgBox("You have unsaved changes. " & _
"Do you want to save them before you exit?", _
vbQuestion Or vbYesNoCancel Or vbDefaultButton1, _
"Reset Application")
If intRetVal = vbYes Then
Call SetValues()
ElseIf intRetVal = vbCancel Then
Exit Sub
End If
End If
Self.close
End Sub

' ----------------------------------------
' Reset the application.
' ----------------------------------------

Sub ResetApplication()
If blnHasChanges = True Then
Dim intRetVal
intRetVal = MsgBox("You have unsaved changes. " & _
"Do you want to save them before you reset the values?", _
vbQuestion Or vbYesNoCancel Or vbDefaultButton1, _
"Reset Application")
If intRetVal = vbYes Then
Call SetValues()
ElseIf intRetVal = vbCancel Then
Exit Sub
End If
End If
Call GetValues()
End Sub

' ----------------------------------------
' Flag the application as having changes.
' ----------------------------------------

Sub FlagChanges()
blnHasChanges = True
End Sub

' ----------------------------------------
' Retrieve the settings from the registry.
' ----------------------------------------

Sub GetValues()
On Error Resume Next
Dim tmpCount,tmpArray,tmpString
' Get the radio button values
GetRegistryDWORD(strWebClientKeyPath, _
"BasicAuthLevel",1))
GetRegistryDWORD(strWebClientKeyPath, _
"SupportLocking",1))
' Get the text box values
Document.all.InternetServerTimeoutInSec.Value = _
GetRegistryDWORD(strWebClientKeyPath, _
"InternetServerTimeoutInSec",30)
Document.all.FileAttributesLimitInBytes.Value = _
GetRegistryDWORD(strWebClientKeyPath, _
"FileAttributesLimitInBytes",1000000)
Document.all.FileSizeLimitInBytes.Value = _
GetRegistryDWORD(strWebClientKeyPath, _
"FileSizeLimitInBytes",50000000)
Document.all.LocalServerTimeoutInSec.Value = _
GetRegistryDWORD(strWebClientKeyPath, _
"LocalServerTimeoutInSec",15)
GetRegistryDWORD(strWebClientKeyPath, _
GetRegistryDWORD(strWebClientKeyPath, _
' Get the text area values
tmpArray = GetRegistryMULTISZ( _
strWebClientKeyPath,"AuthForwardServerList")
For tmpCount = 0 To UBound(tmpArray)
tmpString = tmpString & tmpArray(tmpCount) & vbTab
Next
If Len(tmpString)>0 Then
Document.all.AuthForwardServerList.Value = _
Replace(Left(tmpString,Len(tmpString)-1),vbTab,vbCrLf)
End If
blnHasChanges = False
End Sub

' ----------------------------------------
' Save the settings in the registry.
' ----------------------------------------

Sub SetValues()
On Error Resume Next
' Set the radio button values
Call SetRegistryDWORD( _
strWebClientKeyPath, _
"BasicAuthLevel", _
Call SetRegistryDWORD( _
strWebClientKeyPath, _
"SupportLocking", _
' Set the text box values
Call SetRegistryDWORD( _
strWebClientKeyPath, _
"InternetServerTimeoutInSec", _
Document.all.InternetServerTimeoutInSec.Value)
Call SetRegistryDWORD( _
strWebClientKeyPath, _
"FileAttributesLimitInBytes", _
Document.all.FileAttributesLimitInBytes.Value)
Call SetRegistryDWORD( _
strWebClientKeyPath, _
"FileSizeLimitInBytes", _
Document.all.FileSizeLimitInBytes.Value)
Call SetRegistryDWORD( _
strWebClientKeyPath, _
"LocalServerTimeoutInSec", _
Document.all.LocalServerTimeoutInSec.Value)
Call SetRegistryDWORD( _
strWebClientKeyPath, _
Call SetRegistryDWORD( _
strWebClientKeyPath, _
' Set the text area values
Call SetRegistryMULTISZ( _
strWebClientKeyPath, _
"AuthForwardServerList", _
Split(Document.all.AuthForwardServerList.Value,vbCrLf))
' Prompt to restart the WebClient service
If MsgBox("Do you want to restart the WebDAV Redirector " & _
"service so your settings will take effect?", _
vbQuestion Or vbYesNo Or vbDefaultButton2, _
"Restart WebDAV Redirector") = vbYes Then
' Restart the WebClient service.
Call RestartWebClient()
End If
Call GetValues()
End Sub

' ----------------------------------------
' Start the WebClient service.
' ----------------------------------------

Sub RestartWebClient()
On Error Resume Next
Dim objWMIService,colServices,objService
Document.All.TheBody.ClassName = "hide"
Set objWMIService = GetObject( _
"winmgmts:{impersonationLevel=impersonate}!\\.\root\cimv2")
Set colServices = objWMIService.ExecQuery( _
"Select * from Win32_Service Where Name='WebClient'")
For Each objService in colServices
objService.StopService()
objService.StartService()
Next
Document.All.TheBody.ClassName = "show"
End Sub

' ----------------------------------------
' Retrieve a DWORD value from the registry.
' ----------------------------------------

Function GetRegistryDWORD( _
ByVal tmpKeyPath, _
ByVal tmpValueName, _
ByVal tmpDefaultValue)
On Error Resume Next
Dim tmpDwordValue
If objRegistry.GetDWORDValue( _
HKEY_LOCAL_MACHINE, _
tmpKeyPath, _
tmpValueName, _
tmpDwordValue)=0 Then
GetRegistryDWORD = CLng(tmpDwordValue)
Else
GetRegistryDWORD = CLng(tmpDefaultValue)
End If
End Function

' ----------------------------------------
' Set a DWORD value in the registry.
' ----------------------------------------

Sub SetRegistryDWORD( _
ByVal tmpKeyPath, _
ByVal tmpValueName, _
ByVal tmpDwordValue)
On Error Resume Next
Call objRegistry.SetDWORDValue( _
HKEY_LOCAL_MACHINE, _
tmpKeyPath, _
tmpValueName, _
CLng(tmpDwordValue))
End Sub

' ----------------------------------------
' Retrieve a MULTISZ value from the registry.
' ----------------------------------------

Function GetRegistryMULTISZ( _
ByVal tmpKeyPath, _
ByVal tmpValueName)
On Error Resume Next
Dim tmpMultiSzValue
If objRegistry.GetMultiStringValue( _
HKEY_LOCAL_MACHINE, _
tmpKeyPath, _
tmpValueName, _
tmpMultiSzValue)=0 Then
GetRegistryMULTISZ = tmpMultiSzValue
Else
GetRegistryMULTISZ = Array()
End If
End Function

' ----------------------------------------
' Set a MULTISZ value in the registry.
' ----------------------------------------

Sub SetRegistryMULTISZ( _
ByVal tmpKeyPath, _
ByVal tmpValueName, _
ByVal tmpMultiSzValue)
On Error Resume Next
Call objRegistry.SetMultiStringValue( _
HKEY_LOCAL_MACHINE, _
tmpKeyPath, _
tmpValueName, _
tmpMultiSzValue)
End Sub

' ----------------------------------------
' Retrieve the value of a radio button group.
' ----------------------------------------

On Error Resume Next
Dim tmpCount
For tmpCount = 0 To (tmpRadio.Length-1)
Exit For
End If
Next
End Function

' ----------------------------------------
' Set the value for a radio button group.
' ----------------------------------------

On Error Resume Next
Dim tmpCount
For tmpCount = 0 To (tmpRadio.Length-1)
Exit For
End If
Next
End Sub

' ----------------------------------------
'
' ----------------------------------------

Sub Validate(tmpField)
Dim tmpRegEx, tmpMatches
Set tmpRegEx = New RegExp
tmpRegEx.Pattern = "[0-9]"
tmpRegEx.IgnoreCase = True
tmpRegEx.Global = True
Set tmpMatches = tmpRegEx.Execute(tmpField.Value)
If tmpMatches.Count = Len(CStr(tmpField.Value)) Then
If CDbl(tmpField.Value) => 0 And _
CDbl(tmpField.Value) =< 4294967295 Then
Exit Sub
End If
End If
MsgBox "Please enter a whole number between 0 and 4294967295.", _
vbCritical, "Validation Error"
tmpField.Focus
End Sub

' ----------------------------------------
'
' ----------------------------------------

Sub BasicAuthWarning()
MsgBox "WARNING:" & vbCrLf  & vbCrLf & _
"Using Basic Authentication over non-SSL connections can cause " & _
"in clear text, therefore the use of Basic Authentication with " & _
"WebDAV is disabled by default for non-SSL connections. That " & _
"being said, this setting can override the default behavior for " & _
"Basic Authentication, but it is strongly discouraged.", _
vbCritical, "Basic Authentication Warning"
End Sub

' ----------------------------------------
' End of main code section.
' ----------------------------------------

</script>
<style>
body { color:#000000; background-color:#cccccc;
font-family:'Segoe UI',Tahoma,Verdana,Arial; font-size:9pt; }
.button { width:150px; }
.textbox { width:200px; height:22px; text-align:right; }
.textarea { width:300px; height:50px; text-align:left; }
.radio { margin-left:-5px; margin-top: -2px; }
.hide { display:none; }
.show { display:block; }
select { width:300px; text-align:left; }
table { border-collapse:collapse; empty-cells:hide; }
h1 { font-size:14pt; }
th { font-size:9pt; text-align:left; vertical-align:top; padding:2px; }
td { font-size:9pt; text-align:left; vertical-align:top; padding:2px; }
big { font-size:11pt; }
small { font-size:8pt; }
</style>

<body id="TheBody" class="hide">

<h1 align="center" id="TheTitle" style="margin-bottom:-20px;">WebDAV Redirector Settings</h1>
<div align="center">
<form>
<center>
<tr>
<td style="width:600px;text-align:left"><fieldset title="Security Settings">
<legend>&nbsp;<b>Security Settings</b>&nbsp;</legend>
These values affect the security behavior for the WebDAV Redirector.<br>
<table style="width:600px;">
<tr title="Specifies whether the WebDAV Redirector can use Basic Authentication to communicate with a server.">
<td style="width:300px">
<table border="0">
<tr>
<td style="width:300px"><b>Basic Authentication Level</b></td>
</tr>
<tr>
<td style="width:300px;"><span style="width:280px;padding-left:20px;"><small><i><b>Note</b>: Using basic authentication can cause <u>serious security issues</u> as the username and password are transmitted in clear text, therefore the use of basic authentication over WebDAV is disabled by default unless the connection is using SSL.</i></small></span></td>
</tr>
</table>
</td>
<td style="width:300px">
<table style="width:300px">
<tr>
<td style="width:280px"><label for="BasicAuthLevel0">Basic Authentication is disabled</label></td>
</tr>
<tr>
<td style="width:280px"><label for="BasicAuthLevel1">Basic Authentication is enabled for SSL web sites only</label></td>
</tr>
<tr>
<td style="width:280px"><label for="BasicAuthLevel2">Basic Authentication is enabled for SSL and non-SSL web sites</label></td>
</tr>
</table>
</td>
</tr>
<tr title="Specifies a list of local URLs for forwarding credentials that bypasses any proxy settings. (Note: This requires Windows Vista SP1 or later.)">
<td style="width:300px">
<table border="0">
<tr>
<td style="width:300px"><b>Authentication Forwarding Server List</b></td>
</tr>
<tr>
<td style="width:300px;"><span style="width:280px;padding-left:20px;"><small><i><b>Note</b>: Include one server name per line.</i></small></span></td>
</tr>
</table>
</td>
<td style="width:300px"><textarea class="textarea" name="AuthForwardServerList" onchange="VBScript:FlagChanges()"></textarea></td>
</tr>
<tr title="Specifies whether the WebDAV Redirector supports locking.">
<td style="width:300px"><b>Support for WebDAV Locking</b></td>
<td style="width:300px">
<table style="width:300px">
<tr>
<td style="width:280px"><label for="SupportLocking1">Enable Lock Support</label></td>
</tr>
<tr>
<td style="width:280px"><label for="SupportLocking0">Disable Lock Support</label></td>
</tr>
</table>
</td>
</tr>
</table>
</fieldset> </td>
</tr>
<tr>
<td style="width:600px;text-align:left"><fieldset title="Time-outs">
<legend>&nbsp;<b>Time-outs and Maximum Sizes</b>&nbsp;</legend>
These values affect the behavior for WebDAV Client/Server operations.<br>
<table border="0" style="width:600px;">
<tr title="Specifies the connection time-out for the WebDAV Redirector uses when communicating with non-local WebDAV servers.">
<td style="width:300px"><b>Internet Server Time-out</b> <small>(In Seconds)</small></td>
<td style="width:300px"><input class="textbox" type="text" name="InternetServerTimeoutInSec" onchange="VBScript:FlagChanges()" onblur="VBScript:Validate(Me)" value="30"></td>
</tr>
<tr title="Specifies the connection time-out for the WebDAV Redirector uses when communicating with a local WebDAV server.">
<td style="width:300px"><b>Local Server Time-out</b> <small>(In Seconds)</small></td>
<td style="width:300px"><input class="textbox" type="text" name="LocalServerTimeoutInSec" onchange="VBScript:FlagChanges()" onblur="VBScript:Validate(Me)" value="15"></td>
</tr>
<tr title="Specifies the time-out in seconds that the WebDAV Redirector uses after issuing a request.">
<td style="width:300px"><input class="textbox" type="text" name="SendReceiveTimeoutInSec" onchange="VBScript:FlagChanges()" onblur="VBScript:Validate(Me)" value="60"></td>
</tr>
<tr title="Specifies the period of time that a server is cached as non-WebDAV by the WebDAV Redirector. If a server is found in this list, a fail is returned immediately without attempting to contact the server.">
<td style="width:300px"><input class="textbox" type="text" name="ServerNotFoundCacheLifeTimeInSec" onchange="VBScript:FlagChanges()" onblur="VBScript:Validate(Me)" value="60"></td>
</tr>
<tr title="Specifies the maximum size in bytes that the WebDAV Redirector allows for file transfers.">
<td style="width:300px"><b>Maximum File Size</b> <small>(In Bytes)</small></td>
<td style="width:300px"><input class="textbox" type="text" name="FileSizeLimitInBytes" onchange="VBScript:FlagChanges()" onblur="VBScript:Validate(Me)" value="50000000"></td>
</tr>
<tr title="Specifies the maximum size that is allowed by the WebDAV Redirector for all properties on a specific collection.">
<td style="width:300px"><b>Maximum Attributes Size</b> <small>(In Bytes)</small></td>
<td style="width:300px"><input class="textbox" type="text" name="FileAttributesLimitInBytes" onchange="VBScript:FlagChanges()" onblur="VBScript:Validate(Me)" value="1000000"></td>
</tr>
</table>
</fieldset> </td>
</tr>
<tr>
<td style="text-align:center">
<table border="0">
<tr>
<td style="text-align:center"><input class="button" type="button" value="Apply Settings" onclick="VBScript:SetValues()">
<td style="text-align:center"><input class="button" type="button" value="Reset Values" onclick="VBScript:ResetApplication()">
<td style="text-align:center"><input class="button" type="button" value="Exit Application" onclick="VBScript:ExitApplication()">
</tr>
</table>
</td>
</tr>
</table>
</center>
</form>
</div>

</body>

</html>

As with the last version of this HTML Application, you will need to run this application as an administrator in order to save the settings to the registry and restart the WebDAV Redirector service.

Have fun! ;-]

Note: This blog was originally posted at http://blogs.msdn.com/robert_mcmurray/
Posted: Sep 20 2013, 16:21 by Bob | Comments (0) |
• Currently 0/5 Stars.
• 1
• 2
• 3
• 4
• 5
Filed under: IIS | Scripting | WebDAV
Social Bookmarks: E-mail | Kick it! | DZone it! | del.icio.us

## IntelliSense for jQuery in WebMatrix

I recently had the opportunity to take a day-long class about jQuery from the good folks at Wintellect. The class went great, and I wrote all of my code for the class in WebMatrix. You might recall from my previous blogs that I am a big fan of WebMatrix, but at first there was one thing that was missing from WebMatrix's arsenal of cool features; in order for WebMatrix to really be useful as an editor for jQuery, I really wanted to have IntelliSense support for jQuery. Thankfully, even though IntelliSense support for jQuery is not built-in, adding IntelliSense for jQuery is extremely easy, and I thought that would make a great subject for today's blog.

To start things off, let's take a look at a jQuery sample that is little more than a Hello World sample:

<!DOCTYPE html>
<html lang="en">
<meta charset="utf-8" />
<title>jQuery Test Page</title>
<body>
<script src="http://ajax.microsoft.com/ajax/jQuery/jquery-2.0.0.min.js" type="text/javascript"></script>
<script>
$(function() {$("#bar").text($("#foo").text());$("#foo").text("This is some custom text");
});        </script>
<h1 id="foo">This is the first line</h1>
<h2 id="bar">This is the second line</h2>
</body>
</html>

This example does very little: it loads the jQuery library from Microsoft's AJAX Content Delivery Network (CDN), and it uses jQuery to replace the text in a couple of HTML tags. (The example isn't really important - getting IntelliSense to work is the topic du jour.) This sample would look like the following illustration if you opened it in WebMatrix 3:

When you are using a JavaScript library for which there is no built-in support, Microsoft's developer tools allow you to add IntelliSense support by adding Reference Directives to your page, and the files that you would use for your reference directives are available at the same Microsoft CDN where you can get the jQuery library:

http://www.asp.net/ajaxlibrary/cdn.ashx

In order to use IntelliSense for jQuery, you need to download the appropriate jquery-n.n.n-vsdoc.js file for the version of jQuery that you are using and store that in your website. For example, if you are using jQuery version 2.0.0, you would add a script reference to the CDN path for http://ajax.aspnetcdn.com/ajax/jQuery/jquery-2.0.0.min.js, and you would download the http://ajax.aspnetcdn.com/ajax/jQuery/jquery-2.0.0-vsdoc.js file for your website.

Like many developers, I usually add a folder named scripts in the root of my website, and this is where I will typically store the jquery-n.n.n-vsdoc.js file that I am using. Once you have added the appropriate jquery-n.n.n-vsdoc.js file to your website, all that you need to do is add the appropriate reference directive to your script, as I demonstrate in the highlighted section of the following code sample:

<!DOCTYPE html>
<html lang="en">
<meta charset="utf-8" />
<title>jQuery Test Page</title>
<body>
<script src="http://ajax.microsoft.com/ajax/jQuery/jquery-2.0.0.min.js" type="text/javascript"></script>
<script>
/// <reference path="scripts/jquery-2.0.0-vsdoc.js" />
$(function() {$("#bar").text($("#foo").text());$("#foo").text("This is some custom text");
});        </script>
<h1 id="foo">This is the first line</h1>
<h2 id="bar">This is the second line</h2>
</body>
</html>

Once you have added the reference directive for your jquery-n.n.n-vsdoc.js file, IntelliSense will begin working for jQuery in WebMatrix, as shown in the following illustration:

### In Closing...

One last thing that I would like to mention is that is always a good idea to load JavaScript libraries like jQuery from a CDN, and there are lots of CDNs to choose from. There are some additional steps that you can take to ensure that your website works with jQuery even if the CDN is down, but that subject is outside the scope of this blog. ;-]

Note: This blog was originally posted at http://blogs.msdn.com/robert_mcmurray/
Posted: Jun 28 2013, 06:38 by Bob | Comments (0) |
• Currently 0/5 Stars.
• 1
• 2
• 3
• 4
• 5
Filed under: Scripting | WebMatrix
Social Bookmarks: E-mail | Kick it! | DZone it! | del.icio.us

## Automating the Creation of FTP User Isolation Folders

A customer asked me a question a little while ago that provided me the opportunity to recycle some code that I had written many years ago. In so doing, I also made a bunch of updates to the code to make it considerably more useful, and I thought that it would make a great blog.

Here's the scenario: a customer had hundreds of user accounts created, and he wanted to use the FTP service's User Isolation features to restrict each user to a specific folder on his FTP site. Since it would take a long time to manually create a folder for each user account, the customer wanted to know if there was a way to automate the process. As it turns out, I had posted a very simple script in the IIS.net forums several years ago that did something like what he wanted; and that script was based off an earlier script that I had written for someone else back in the IIS 6.0 days.

One quick reminder - FTP User Isolation uses a specific set of folders for user accounts, which are listed in the table below.

User Account TypesHome Directory Syntax
Anonymous users %FtpRoot%\LocalUser\Public
Local Windows user accounts

(Requires Basic authentication.)

Windows domain accounts

(Requires Basic authentication.)

Note: %FtpRoot% is the root directory for your FTP site: for example, C:\Inetpub\Ftproot.

That being said, I'm a big believer in recycling code, so I found the last version of that script that I gave to someone and I made a bunch of changes to it so it would be more useful for the customer. What that in mind, here's the resulting script, and I'll explain a little more about what it does after the code sample.

Option Explicit

' Define the root path for the user isolation folders.
' This should be the root directory for your FTP site.
Dim strRootPath : strRootPath = "C:\Inetpub\wwwroot\"

' Define the name of the domain or the computer to use.
' Leave this blank for the local computer.
Dim strComputerOrDomain : strComputerOrDomain = ""

' Define the remaining script variables.
Dim objFSO, objCollection, objUser, objNetwork, strContainerName

' Create a network object; used to query the computer name.
Set objNetwork = WScript.CreateObject("WScript.Network")

' Create a file system object; used to creat folders.
Set objFSO = CreateObject("Scripting.FileSystemObject")

' Test if the computer name is null.
If Len(strComputerOrDomain)=0 Or strComputerOrDomain="." Then
' If so, define the local computer name as the account repository.
strComputerOrDomain = objNetwork.ComputerName
End If

' Verify that the root path exists.
If objFSO.FolderExists(strRootPath) Then

' Test if the script is using local users.
If StrComp(strComputerOrDomain,objNetwork.ComputerName,vbTextCompare)=0 Then
' If so, define the local users container path.
strContainerName = "LocalUser"
' And define the users collection as local.
Set objCollection = GetObject("WinNT://.")
Else
' Otherwise, use the source name as the path.
strContainerName = strComputerOrDomain
' And define the users collection as remote.
Set objCollection = GetObject("WinNT://" & strComputerOrDomain & "")
End If

' Append trailing backslash if necessary.
If Right(strRootPath,1)<>"\" Then strRootPath = strRootPath & "\"
' Define the adjusted root path for the container folder.
strRootPath = strRootPath & strContainerName & "\"

' Test if the container folder already exists.
If objFSO.FolderExists(strRootPath)=False Then
' Create the container folder if necessary.
objFSO.CreateFolder(strRootPath)
End If

' Specify the collection filter for user objects only.
objCollection.Filter = Array("user")

' Loop through the users collection.
For Each objUser In objCollection
' Test if the user's account is enabled.
If objUser.AccountDisabled = False Then
' Test if the user's folder already exists.
If objFSO.FolderExists(strRootPath & "\" & objUser.Name)=False Then
' Create the user's folder if necessary.
objFSO.CreateFolder(strRootPath & "\" & objUser.Name)
End If
End If
Next

End If

I documented this script in great detail, so it should be self-explanatory for the most part. But just to be on the safe side, here's an explanation of what this script is doing when you run it on your FTP server:

• Defines two user-updatable variables:
• strRootPath - which specifies the physical path to the root of your FTP site.
• strComputerOrDomain - which specifies the computer name or the domain name where your user accounts are located. (Note: You can leave this blank if you are using local user accounts on your FTP server.)
• Creates a few helper objects and determines the local computer name if necessary.
• Checks to see if the physical path to the root of your FTP site actually exists before continuing.
• Creates a connection to the user account store (local or domain).
• Determines the container folder name that be the parent directory of user account folders, and creates it if necessary. (See my earlier note about the folder names.)
• Defines a filter for user objects in the specifies account repository. (This removes computer accounts and such from the operation.)
• Loops through the collection of user accounts, checks each account to see if it is enabled, and creates a folder for each user account if it does not already exist.

That's all for now. ;-]

Note: This blog was originally posted at http://blogs.msdn.com/robert_mcmurray/
Posted: Mar 28 2013, 16:29 by Bob | Comments (0) |
• Currently 0/5 Stars.
• 1
• 2
• 3
• 4
• 5
Filed under: IIS | Scripting | FTP
Social Bookmarks: E-mail | Kick it! | DZone it! | del.icio.us

## Advanced Log Parser Part 7 - Creating a Generic Input Format Plug-In

In Part 6 of this series, I showed how to create a very basic COM-based input format provider for Log Parser. I wrote that blog post as a follow-up to an earlier blog post where I had written a more complex COM-based input format provider for Log Parser that worked with FTP RSCA events. My original blog post had resulted in several requests for me to write some easier examples about how to get started writing COM-based input format providers for Log Parser, and those appeals led me to write my last blog post:

Advanced Log Parser Part 6 - Creating a Simple Custom Input Format Plug-In

The example in that blog post simply returns static data, which was the easiest example that I could demonstrate.

For this follow-up blog post, I will illustrate how to create a simple COM-based input format plug-in for Log Parser that you can use as a generic provider for consuming data in text-based log files. Please bear in mind that this is just an example to help developers get started writing their own COM-based input format providers; you might be able to accomplish some of what I will demonstrate in this blog post by using the built-in Log Parser functionality. That being said, this still seems like the best example to help developers get started because consuming data in text-based log files was the most-often-requested example that I received.

### In Review: Creating COM-based plug-ins for Log Parser

In my earlier blog posts, I mentioned that a COM plug-in has to support several public methods. You can look at those blog posts when you get the chance, but it is a worthwhile endeavor for me to copy the following information from those blog posts since it is essential to understanding how the code sample in this blog post is supposed to work.

Method NameDescription
OpenInput Opens your data source and sets up any initial environment settings.
GetFieldCount Returns the number of fields that your plug-in will provide.
GetFieldName Returns the name of a specified field.
GetFieldType Returns the datatype of a specified field.
GetValue Returns the value of a specified field.
CloseInput Closes your data source and cleans up any environment settings.

Once you have created and registered a COM-based input format plug-in, you call it from Log Parser by using something like the following syntax:

logparser.exe "SELECT * FROM FOO" -i:COM -iProgID:BAR

In the preceding example, FOO is a data source that makes sense to your plug-in, and BAR is the COM class name for your plug-in.

### Creating a Generic COM plug-in for Log Parser

As I have done in my previous two blog posts about creating COM-based input format plug-ins, I'm going to demonstrate how to create a COM component by using a scriptlet since no compilation is required. This generic plug-in will parse any text-based log files where records are delimited by CRLF sequences and fields/columns are delimited by a separator that is defined as a constant in the code sample.

To create the sample COM plug-in, copy the following code into a text file, and save that file as "Generic.LogParser.Scriptlet.sct" to your computer. (Note: The *.SCT file extension tells Windows that this is a scriptlet file.)

<SCRIPTLET>
<registration
Description="Simple Log Parser Scriptlet"
Progid="Generic.LogParser.Scriptlet"
Classid="{4e616d65-6f6e-6d65-6973-526f62657274}"
Version="1.00"
Remotable="False" />
<comment>
EXAMPLE: logparser "SELECT * FROM 'C:\foo\bar.log'" -i:COM -iProgID:Generic.LogParser.Scriptlet
</comment>
<implements id="Automation" type="Automation">
<method name="OpenInput">
<parameter name="strFileName"/>
</method>
<method name="GetFieldCount" />
<method name="GetFieldName">
<parameter name="intFieldIndex"/>
</method>
<method name="GetFieldType">
<parameter name="intFieldIndex"/>
</method>
<method name="GetValue">
<parameter name="intFieldIndex"/>
</method>
<method name="CloseInput">
<parameter name="blnAbort"/>
</method>
</implements>
<SCRIPT LANGUAGE="VBScript">

Option Explicit

' Define the column separator in the log file.
Const strSeparator = "|"

' Define whether the first row contains column names.

' Define the field type constants.
Const TYPE_INTEGER   = 1
Const TYPE_REAL      = 2
Const TYPE_STRING    = 3
Const TYPE_TIMESTAMP = 4
Const TYPE_NULL      = 5

' Declare variables.
Dim objFSO, objFile, blnFileOpen
Dim arrFieldNames, arrFieldTypes
Dim arrCurrentRecord

' Indicate that no file has been opened.
blnFileOpen = False

' --------------------------------------------------------------------------------
' Open the input session.
' --------------------------------------------------------------------------------

Public Function OpenInput(strFileName)
Dim tmpCount
' Test for a file name.
If Len(strFileName)=0 Then
' Return a status that the parameter is incorrect.
OpenInput = 87
blnFileOpen = False
Else
' Test for single-quotes.
If Left(strFileName,1)="'" And Right(strFileName,1)="'" Then
' Strip the single-quotes from the file name.
strFileName = Mid(strFileName,2,Len(strFileName)-2)
End If
' Open the file system object.
Set objFSO = CreateObject("Scripting.Filesystemobject")
' Verify that the specified file exists.
If objFSO.FileExists(strFileName) Then
' Open the specified file.
Set objFile = objFSO.OpenTextFile(strFileName,1,False)
' Set a flag to indicate that the specified file is open.
blnFileOpen = true
' Retrieve an initial record.
' Redimension the array of field names.
ReDim arrFieldNames(UBound(arrCurrentRecord))
' Loop through the record fields.
For tmpCount = 0 To (UBound(arrFieldNames))
' Test for a header row.
arrFieldNames(tmpCount) = arrCurrentRecord(tmpCount)
Else
arrFieldNames(tmpCount) = "Field" & (tmpCount+1)
End If
Next
' Test for a header row.
' Retrieve a second record.
End If
' Redimension the array of field types.
ReDim arrFieldTypes(UBound(arrCurrentRecord))
' Loop through the record fields.
For tmpCount = 0 To (UBound(arrFieldTypes))
' Test if the current field contains a date.
If IsDate(arrCurrentRecord(tmpCount)) Then
' Specify the field type as a timestamp.
arrFieldTypes(tmpCount) = TYPE_TIMESTAMP
' Test if the current field contains a number.
ElseIf IsNumeric(arrCurrentRecord(tmpCount)) Then
' Test if the current field contains a decimal.
If InStr(arrCurrentRecord(tmpCount),".") Then
' Specify the field type as a real number.
arrFieldTypes(tmpCount) = TYPE_REAL
Else
' Specify the field type as an integer.
arrFieldTypes(tmpCount) = TYPE_INTEGER
End If
' Test if the current field is null.
ElseIf IsNull(arrCurrentRecord(tmpCount)) Then
' Specify the field type as NULL.
arrFieldTypes(tmpCount) = TYPE_NULL
' Test if the current field is empty.
ElseIf IsEmpty(arrCurrentRecord(tmpCount)) Then
' Specify the field type as NULL.
arrFieldTypes(tmpCount) = TYPE_NULL
' Otherwise, assume it's a string.
Else
' Specify the field type as a string.
arrFieldTypes(tmpCount) = TYPE_STRING
End If
Next
' Temporarily close the log file.
objFile.Close
' Re-open the specified file.
Set objFile = objFSO.OpenTextFile(strFileName,1,False)
' Test for a header row.
' Skip the first row.
objFile.SkipLine
End If
' Return success status.
OpenInput = 0
Else
OpenInput = 2
End If
End If
End Function

' --------------------------------------------------------------------------------
' Close the input session.
' --------------------------------------------------------------------------------

Public Function CloseInput(blnAbort)
' Free the objects.
Set objFile = Nothing
Set objFSO = Nothing
' Set a flag to indicate that the specified file is closed.
blnFileOpen = False
End Function

' --------------------------------------------------------------------------------
' Return the count of fields.
' --------------------------------------------------------------------------------

Public Function GetFieldCount()
' Specify the default value.
GetFieldCount = 0
' Test if a file is open.
If (blnFileOpen = True) Then
' Test for the number of field names.
If UBound(arrFieldNames) > 0 Then
' Return the count of fields.
GetFieldCount = UBound(arrFieldNames) + 1
End If
End If
End Function

' --------------------------------------------------------------------------------
' Return the specified field's name.
' --------------------------------------------------------------------------------

Public Function GetFieldName(intFieldIndex)
' Specify the default value.
GetFieldName = Null
' Test if a file is open.
If (blnFileOpen = True) Then
' Test if the index is valid.
If intFieldIndex<=UBound(arrFieldNames) Then
' Return the specified field name.
GetFieldName = arrFieldNames(intFieldIndex)
End If
End If
End Function

' --------------------------------------------------------------------------------
' Return the specified field's type.
' --------------------------------------------------------------------------------

Public Function GetFieldType(intFieldIndex)
' Specify the default value.
GetFieldType = Null
' Test if a file is open.
If (blnFileOpen = True) Then
' Test if the index is valid.
If intFieldIndex<=UBound(arrFieldTypes) Then
' Return the specified field type.
GetFieldType = arrFieldTypes(intFieldIndex)
End If
End If
End Function

' --------------------------------------------------------------------------------
' Return the specified field's value.
' --------------------------------------------------------------------------------

Public Function GetValue(intFieldIndex)
' Specify the default value.
GetValue = Null
' Test if a file is open.
If (blnFileOpen = True) Then
' Test if the index is valid.
If intFieldIndex<=UBound(arrCurrentRecord) Then
' Return the specified field value based on the field type.
Select Case arrFieldTypes(intFieldIndex)
Case TYPE_INTEGER:
GetValue = CInt(arrCurrentRecord(intFieldIndex))
Case TYPE_REAL:
GetValue = CDbl(arrCurrentRecord(intFieldIndex))
Case TYPE_STRING:
GetValue = CStr(arrCurrentRecord(intFieldIndex))
Case TYPE_TIMESTAMP:
GetValue = CDate(arrCurrentRecord(intFieldIndex))
Case Else
GetValue = Null
End Select
End If
End If
End Function

' --------------------------------------------------------------------------------
' Read the next record, and return true or false if there is more data.
' --------------------------------------------------------------------------------

' Specify the default value.
' Test if a file is open.
If (blnFileOpen = True) Then
' Test if there is more data.
If objFile.AtEndOfStream Then
' Flag the log file as having no more data.
Else
' Flag the log file as having more data to process.
End If
End If
End Function

</SCRIPT>

</SCRIPTLET>

After you have saved the scriptlet code to your computer, you register it by using the following syntax:

regsvr32 Generic.LogParser.Scriptlet.sct

At the very minimum, you can now use the COM plug-in with Log Parser by using syntax like the following:

logparser "SELECT * FROM 'C:\Foo\Bar.log'" -i:COM -iProgID:Generic.LogParser.Scriptlet

Next, let's analyze what this sample does.

### Examining the Generic Scriptlet in Detail

Here are the different parts of the scriptlet and what they do:

• The <registration> section of the scriptlet sets up the COM registration information; you'll notice the COM component class name and GUID, as well as version information and a general description. (Note that you should generate your own GUID for each scriptlet that you create.)
• The <implements> section declares the public methods that the COM plug-in has to support.
• The <script>section contains the actual implementation:
• The first part of the script section declares the global variables that will be used:
• The strSeparator  constant defines the delimiter that is used to separate the data between fields/columns in a text-based log file.
• The blnHeaderRow  constant defines whether the first row in a text-based log file contains the names of the fields/columns:
• If set to True, the plug-in will use the data in the first line of the log file to name the fields/columns.
• If set to False, the plug-in will define generic field/column names like "Field1", "Field2", etc.
• The second part of the script contains the required methods:
• The OpenInput()  method performs several tasks:
• Locates and opens the log file that you specify in your SQL statement, or returns an error if the log file cannot be found.
• Determines the number, names, and data types of fields/columns in the log file.
• The CloseInput()  method cleans up the session by closing the log file and destroying objects.
• The GetFieldCount()  method returns the number of fields/columns in the log file.
• The GetFieldName()  method returns the name of a field/column in the log file.
• The GetFieldType()  method returns the data type of a field/column in the log file. As a reminder, Log Parser supports the following five data types for COM plug-ins: TYPE_INTEGER, TYPE_REAL, TYPE_STRING, TYPE_TIMESTAMP, and TYPE_NULL.
• The GetValue()  method returns the data value of a field/column in the log file.
• The ReadRecord()  method moves to the next line in the log file. This method returns True if there is additional data to read, or False when the end of data is reached.

Next, let's look at how to use the sample.

### Using the Generic Scriptlet with Log Parser

As a sample log file for this blog, I'm going to use the data in the Sample XML File (books.xml) from MSDN. By running a quick Log Parser query that I will show later, I was able to export data from the XML file into text file named "books.log" that represents an example of a simple log file format that I have had to work with in the past:

id|publish_date|author|title|price
bk101|2000-10-01|Gambardella, Matthew|XML Developer's Guide|44.950000
bk102|2000-12-16|Ralls, Kim|Midnight Rain|5.950000
bk103|2000-11-17|Corets, Eva|Maeve Ascendant|5.950000
bk104|2001-03-10|Corets, Eva|Oberon's Legacy|5.950000
bk105|2001-09-10|Corets, Eva|The Sundered Grail|5.950000
bk106|2000-09-02|Randall, Cynthia|Lover Birds|4.950000
bk107|2000-11-02|Thurman, Paula|Splish Splash|4.950000
bk108|2000-12-06|Knorr, Stefan|Creepy Crawlies|4.950000
bk110|2000-12-09|O'Brien, Tim|Microsoft .NET: The Programming Bible|36.950000
bk111|2000-12-01|O'Brien, Tim|MSXML3: A Comprehensive Guide|36.950000
bk112|2001-04-16|Galos, Mike|Visual Studio 7: A Comprehensive Guide|49.950000

In this example, the data is pretty easy to understand - the first row contains the list of field/column names, and the fields/columns are separated by the pipe ("|") character throughout the log file. That being said, you could easily change my sample code to use a different delimiter that your custom log files use.

With that in mind, let's look at some Log Parser examples.

#### Example #1: Retrieving Data from a Custom Log

The first thing that you should try is to simply retrieve data from your custom plug-in, and the following query should serve as an example:

logparser "SELECT * FROM 'C:\sample\books.log'" -i:COM -iProgID:Generic.LogParser.Scriptlet

The above query will return results like the following:

idpublish_dateauthortitleprice
-----------------------------------------------------------------------------------------
bk101 10/1/2000 0:00:00 Gambardella, Matthew XML Developer's Guide 44.950000
bk102 12/16/2000 0:00:00 Ralls, Kim Midnight Rain 5.950000
bk103 11/17/2000 0:00:00 Corets, Eva Maeve Ascendant 5.950000
bk104 3/10/2001 0:00:00 Corets, Eva Oberon's Legacy 5.950000
bk105 9/10/2001 0:00:00 Corets, Eva The Sundered Grail 5.950000
bk106 9/2/2000 0:00:00 Randall, Cynthia Lover Birds 4.950000
bk107 11/2/2000 0:00:00 Thurman, Paula Splish Splash 4.950000
bk108 12/6/2000 0:00:00 Knorr, Stefan Creepy Crawlies 4.950000
bk109 11/2/2000 0:00:00 Kress, Peter Paradox Lost 6.950000
bk110 12/9/2000 0:00:00 O'Brien, Tim Microsoft .NET: The Programming Bible 36.950000
bk111 12/1/2000 0:00:00 O'Brien, Tim MSXML3: A Comprehensive Guide 36.950000
bk112 4/16/2001 0:00:00 Galos, Mike Visual Studio 7: A Comprehensive Guide 49.950000

 Statistics: ----------- Elements processed: 12 Elements output: 12 Execution time: 0.16 seconds

While the above example works a good proof-of-concept for functionality, it's not overly useful, so let's look at additional examples.

#### Example #2: Reformatting Log File Data

Once you have established that you can retrieve data from your custom plug-in, you can start taking advantage of Log Parser's features to process your log file data. In this example, I will use several of the built-in functions to reformat the data:

logparser "SELECT id AS ID, TO_DATE(publish_date) AS Date, author AS Author, SUBSTR(title,0,20) AS Title, STRCAT(TO_STRING(TO_INT(FLOOR(price))),SUBSTR(TO_STRING(price),INDEX_OF(TO_STRING(price),'.'),3)) AS Price FROM 'C:\sample\books.log'" -i:COM -iProgID:Generic.LogParser.Scriptlet

The above query will return results like the following:

IDDateAuthorTitlePrice
------------------------------------------------------------
bk101 10/1/2000 Gambardella, Matthew XML Developer's Guid 44.95
bk102 12/16/2000 Ralls, Kim Midnight Rain 5.95
bk103 11/17/2000 Corets, Eva Maeve Ascendant 5.95
bk104 3/10/2001 Corets, Eva Oberon's Legacy 5.95
bk105 9/10/2001 Corets, Eva The Sundered Grail 5.95
bk106 9/2/2000 Randall, Cynthia Lover Birds 4.95
bk107 11/2/2000 Thurman, Paula Splish Splash 4.95
bk108 12/6/2000 Knorr, Stefan Creepy Crawlies 4.95
bk109 11/2/2000 Kress, Peter Paradox Lost 6.95
bk110 12/9/2000 O'Brien, Tim Microsoft .NET: The 36.95
bk111 12/1/2000 O'Brien, Tim MSXML3: A Comprehens 36.95
bk112 4/16/2001 Galos, Mike Visual Studio 7: A C 49.95

 Statistics: ----------- Elements processed: 12 Elements output: 12 Execution time: 0.02 seconds

This example reformats the dates and prices a little nicer, and it truncates the book titles at 20 characters so they fit a little better on some screens.

#### Example #3: Processing Log File Data

In addition to simply reformatting your data, you can use Log Parser to group, sort, count, total, etc., your data. The following example illustrates how to use Log Parser to count the number of books by author in the log file:

logparser "SELECT author AS Author, COUNT(Title) AS Books FROM 'C:\sample\books.log' GROUP BY Author ORDER BY Author" -i:COM -iProgID:Generic.LogParser.Scriptlet

The above query will return results like the following:

AuthorBooks
-------------------------
Corets, Eva 3
Galos, Mike 1
Gambardella, Matthew 1
Knorr, Stefan 1
Kress, Peter 1
O'Brien, Tim 2
Ralls, Kim 1
Randall, Cynthia 1
Thurman, Paula 1

 Statistics: ----------- Elements processed: 12 Elements output: 9 Execution time: 0.03 seconds

The results are pretty straight-forward: Log Parser parses the data and presents you with a list of alphabetized authors and the total number of books that were written by each author.

#### Example #4: Creating Charts

You can also use data from your custom log file to create charts through Log Parser. If I modify the above example, all that I need to do is add a few parameters to create a chart:

logparser "SELECT author AS Author, COUNT(Title) AS Books INTO Authors.gif FROM 'C:\sample\books.log' GROUP BY Author ORDER BY Author" -i:COM -iProgID:Generic.LogParser.Scriptlet -fileType:GIF -groupSize:800x600 -chartType:Pie -categories:OFF -values:ON -legend:ON

The above query will create a chart like the following:

I admit that it's not a very pretty-looking chart - you can look at the other posts in my Log Parser series for some examples about making Log Parser charts more interesting.

### Summary

In this blog post and my last post, I have illustrated a few examples that should help developers get started writing their own custom input format plug-ins for Log Parser. As I mentioned in each of the blog posts where I have used scriptlets for the COM objects, I would typically use C# or C++ to create a COM component, but using a scriptlet is much easier for demos because it doesn't require installing Visual Studio and compiling a DLL.

There is one last thing that I would like to mention before I finish this blog; I mentioned earlier that I had used Log Parser to reformat the sample Books.xml file into a generic log file that I could use for the examples in this blog. Since Log Parser supports XML as an input format and it allows you to customize your output, I wrote the following simple Log Parser query to reformat the XML data into a format that I had often seen used for text-based log files:

logparser.exe "SELECT id,publish_date,author,title,price INTO books.log FROM books.xml" -i:xml -o:tsv -headers:ON -oSeparator:"|"

Actually, this ability to change data formats is one of the hidden gems of Log Parser; I have often used Log Parser to change the data from one type of log file to another - usually so that a different program can access the data. For example, if you were given the log file with a pipe ("|") delimiter like I used as an example, you could easily use Log Parser to convert that data into the CSV format so you could open it in Excel:

logparser.exe "SELECT id,publish_date,author,title,price INTO books.csv FROM books.log" -i:tsv -o:csv -headers:ON -iSeparator:"|" -oDQuotes:on

I hope these past few blog posts help you to get started writing your own custom input format plug-ins for Log Parser.

That's all for now. ;-)

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

Posted: Feb 28 2013, 10:34 by Bob | Comments (0) |
• Currently 0/5 Stars.
• 1
• 2
• 3
• 4
• 5
Filed under: LogParser | Scripting | XML
Social Bookmarks: E-mail | Kick it! | DZone it! | del.icio.us

## Advanced Log Parser Part 6 - Creating a Simple Custom Input Format Plug-In

In Part 4 of this series, I illustrated how to create a new COM-based input provider for Log Parser from a custom input format:

For the sample that I published in that blog, I wrote a plug-in that consumed FTP RSCA events, which is highly structured data, and it added a lot of complexity to my example. In the past ten months or so since I published my original blog, I've had several requests for additional information about how to get started writing COM-based input formats for Log Parser, so it occurred to me that perhaps I could have shown a simpler example to get people started instead of diving straight into parsing RSCA data. ;-)

With that in mind, I thought that I would write a couple of blog posts with simpler examples to help anyone who wants to get started writing custom input formats for Log Parser.

For this blog post, I will show you how to create a very basic COM-based input format provider for Log Parser that simply returns static data; you could use this sample as a template to quickly get up-and-running with the basic concepts. (I promise to follow this blog with another real-world example that is still easier-to-use than my RSCA example.)

### A Reminder about Creating COM-based plug-ins for Log Parser

In the blog that I referred to earlier, I mentioned that a COM plug-in has to support the following public methods:

Method NameDescription
OpenInput Opens your data source and sets up any initial environment settings.
GetFieldCount Returns the number of fields that your plug-in will provide.
GetFieldName Returns the name of a specified field.
GetFieldType Returns the datatype of a specified field.
GetValue Returns the value of a specified field.
ReadRecord Reads the next record from your data source.
CloseInput Closes your data source and cleans up any environment settings.

Once you have created and registered a COM plug-in, you call it by using something like the following syntax:

logparser.exe "SELECT * FROM FOO" -i:COM -iProgID:BAR

In the preceding example, FOO is a data source that makes sense to your plug-in, and BAR is the COM class name for your plug-in.

### Creating a Simple COM plug-in for Log Parser

Once again, I'm going to demonstrate how to create a COM component by using a scriptlet, which I like to use for demos because they are quick to design, they're easily portable, and updates take place immediately since no compilation is required. (All of that being said, if I were writing a real COM plug-in for Log Parser, I would use C# or C++.)

To create the sample COM plug-in, copy the following code into a text file, and save that file as "Simple.LogParser.Scriptlet.sct" to your computer. (Note: The *.SCT file extension tells Windows that this is a scriptlet file.)

<SCRIPTLET>
<registration
Description="Simple Log Parser Scriptlet"
Progid="Simple.LogParser.Scriptlet"
Classid="{4e616d65-6f6e-6d65-6973-526f62657274}"
Version="1.00"
Remotable="False" />
<comment>
EXAMPLE: logparser "SELECT * FROM FOOBAR" -i:COM -iProgID:Simple.LogParser.Scriptlet
</comment>
<implements id="Automation" type="Automation">
<method name="OpenInput">
<parameter name="strValue"/>
</method>
<method name="GetFieldCount" />
<method name="GetFieldName">
<parameter name="intFieldIndex"/>
</method>
<method name="GetFieldType">
<parameter name="intFieldIndex"/>
</method>
<method name="GetValue">
<parameter name="intFieldIndex"/>
</method>
<method name="CloseInput">
<parameter name="blnAbort"/>
</method>
</implements>
<SCRIPT LANGUAGE="VBScript">

Option Explicit

Const MAX_RECORDS = 5
Dim intRecordCount

' --------------------------------------------------------------------------------
' Open the input session.
' --------------------------------------------------------------------------------

Public Function OpenInput(strValue)
intRecordCount = 0
End Function

' --------------------------------------------------------------------------------
' Close the input session.
' --------------------------------------------------------------------------------

Public Function CloseInput(blnAbort)
End Function

' --------------------------------------------------------------------------------
' Return the count of fields.
' --------------------------------------------------------------------------------

Public Function GetFieldCount()
GetFieldCount = 5
End Function

' --------------------------------------------------------------------------------
' Return the specified field's name.
' --------------------------------------------------------------------------------

Public Function GetFieldName(intFieldIndex)
Select Case CInt(intFieldIndex)
Case 0:
GetFieldName = "INTEGER"
Case 1:
GetFieldName = "REAL"
Case 2:
GetFieldName = "STRING"
Case 3:
GetFieldName = "TIMESTAMP"
Case 4:
GetFieldName = "NULL"
Case Else
GetFieldName = Null
End Select
End Function

' --------------------------------------------------------------------------------
' Return the specified field's type.
' --------------------------------------------------------------------------------

Public Function GetFieldType(intFieldIndex)
' Define the field type constants.
Const TYPE_INTEGER   = 1
Const TYPE_REAL      = 2
Const TYPE_STRING    = 3
Const TYPE_TIMESTAMP = 4
Const TYPE_NULL      = 5
Select Case CInt(intFieldIndex)
Case 0:
GetFieldType = TYPE_INTEGER
Case 1:
GetFieldType = TYPE_REAL
Case 2:
GetFieldType = TYPE_STRING
Case 3:
GetFieldType = TYPE_TIMESTAMP
Case 4:
GetFieldType = TYPE_NULL
Case Else
GetFieldType = Null
End Select
End Function

' --------------------------------------------------------------------------------
' Return the specified field's value.
' --------------------------------------------------------------------------------

Public Function GetValue(intFieldIndex)
Select Case CInt(intFieldIndex)
Case 0:
GetValue = 1
Case 1:
GetValue = 1.0
Case 2:
GetValue = "One"
Case 3:
GetValue = Now
Case Else
GetValue = Null
End Select
End Function

' --------------------------------------------------------------------------------
' Read the next record, and return true or false if there is more data.
' --------------------------------------------------------------------------------

intRecordCount = intRecordCount + 1
If intRecordCount <= MAX_RECORDS Then
Else
End If
End Function

</SCRIPT>

</SCRIPTLET>

After you have saved the scriptlet code to your computer, you register it by using the following syntax:

regsvr32 Simple.LogParser.Scriptlet.sct

At the very minimum, you can now use the COM plug-in with Log Parser by using syntax like the following:

logparser "SELECT * FROM FOOBAR" -i:COM -iProgID:Simple.LogParser.Scriptlet

This will return results like the following:

INTEGERREALSTRINGTIMESTAMPNULL
-------------------------------------------
1 1.000000 One 2/26/2013 19:42:12 -
1 1.000000 One 2/26/2013 19:42:12 -
1 1.000000 One 2/26/2013 19:42:12 -
1 1.000000 One 2/26/2013 19:42:12 -
1 1.000000 One 2/26/2013 19:42:12 -

Statistics:
-----------
Elements processed: 5
Elements output: 5
Execution time: 0.01 seconds

Next, let's analyze what this sample does.

### Examining the Sample Scriptlet Contents in Detail

Here are the different parts of the scriptlet and what they do:

• The <registration> section of the scriptlet sets up the COM registration information; you'll notice the COM component class name and GUID, as well as version information and a general description. (Note that you should generate your own GUID for each scriptlet that you create.)
• The <implements> section declares the public methods that the COM plug-in has to support.
• The <script>section contains the actual implementation:
• The OpenInput() method opens your data source, although in this example it only initializes the record count. (Note that the value that is passed to the method will be ignored in this example.)
• The CloseInput() method would normally clean up your session, (e.g. close a data file or database, etc.), but it doesn't do anything in this example.
• The GetFieldCount() method returns the number of data fields in each record of your data, which is static in this example.
• The GetFieldName() method returns the name of a field that is passed to the method as a number; the names are static in this example.
• The GetFieldType() method returns the data type of a field that is passed to the method as a number, which are statically-defined in this example. As a reminder, Log Parser supports the following five data types for COM plug-ins: TYPE_INTEGER, TYPE_REAL, TYPE_STRING, TYPE_TIMESTAMP, and TYPE_NULL.
• The GetValue() method returns the data value of a field that is passed to the method as a number. Once again, the data values are statically-defined in this example.
• The ReadRecord() method moves to the next record in your data set; this method returns True if there is data to read, or False when the end of data is reached. In this example, the method increments the record counter and sets the status based on whether the maximum number of records has been reached.

### Summary

That wraps up the simplest example that I could put together of a COM-based input provider for Log Parser. In my next blog, I'll show how to create a generic COM-based input provider for Log Parser that you can use to parse text-based log files.

Note: This blog was originally posted at http://blogs.msdn.com/robert_mcmurray/
Posted: Feb 26 2013, 17:38 by Bob | Comments (0) |
• Currently 0/5 Stars.
• 1
• 2
• 3
• 4
• 5
Filed under: LogParser | Scripting
Social Bookmarks: E-mail | Kick it! | DZone it! | del.icio.us

## Restarting the FTP Service Orphans a DLLHOST.EXE Process

I was recently creating a new authentication provider using FTP extensibility, and I ran into a weird behavior that I had seen before. With that in mind, I thought my situation would make a great blog subject because someone else may run into it.

Here are the details of the situation: let's say that you are developing a new FTP provider for IIS, and your code changes never seem to take effect. Your provider appears to be working, it's just that any new functionality is not reflected in your provider's behavior. You restart the FTP service as a troubleshooting step, but that does not appear to make any difference.

I'll bypass mentioning any other troubleshooting tasks and cut to the chase - if you read my Changing the Identity of the FTP 7 Extensibility Process blog post a year ago, you will recall that I mentioned that all custom FTP extensibility providers are executed through COM+ in a DLLHOST.exe process. When you restart the FTP service, that should clean up the DLLHOST.EXE process that is being used for FTP extensibility. However, if you are developing custom FTP providers and the DLLHOST.EXE process is not terminated by the FTP service, you may find yourself in a situation where you have a DLLHOST.EXE process in memory that contains an older copy of your provider, which will not be removed from memory until the DLLHOST.EXE process for FTP extensibility has been forcibly terminated.

If you have read some of my earlier blog posts or walkthroughs on IIS.NET, you may have noticed that I generally like to use a few pre-build and post-build commands in my FTP projects; usually I add these commands in order to to automatically register/unregister my FTP providers in the Global Assembly Cache (GAC).

With a little modification and some command-line wizardry, you can automate the termination of any orphaned DLLHOST.EXE processes that are being used for FTP extensibility. With that in mind, here are some example pre-build/post-build commands that will unregister/reregister your provider in the GAC, restart the FTP service, and terminate any orphaned FTP extensibility DLLHOST.EXE processes.

Note: The following syntax was written using Visual Studio 2010; you would need to change "%VS100COMNTOOLS%" to "%VS90COMNTOOLS%" for Visual Studio 2008 or "%VS110COMNTOOLS%" for Visual Studio 2012.

Pre-build Commands:

net stop ftpsvc

call "%VS100COMNTOOLS%\vsvars32.bat">nul

cd /d "$(TargetDir)" gacutil.exe /uf "$(TargetName)"

for /f "usebackq tokens=1,2* delims=," %%a in (tasklist /fi "MODULES eq Microsoft.Web.FtpServer.*" /fi "IMAGENAME eq DLLHOST.EXE" /fo csv ^| find /i "dllhost.exe") do taskkill /f /pid %%b

Post-build Commands:

call "%VS100COMNTOOLS%\vsvars32.bat">nul

gacutil.exe /if "\$(TargetPath)"

net start ftpsvc

The syntax is a little tricky for the FOR statement, so be carefully when typing or copying/pasting that into your projects. For example, you need to make sure that all of the code from the FOR statement through the TASKKILL command are on the same line in your project's properties.

When you compile your provider, Visual Studio should display something like the following:

------ Rebuild All started: Project: FtpBlogEngineNetAuthentication, Configuration: Release Any CPU ------
The Microsoft FTP Service service is stopping.
The Microsoft FTP Service service was stopped successfully.

Microsoft (R) .NET Global Assembly Cache Utility. Version 4.0.30319.1

Assembly: FtpBlogEngineNetAuthentication, Version=1.0.0.0, Culture=neutral, PublicKeyToken=426f62526f636b73, processorArchitecture=MSIL
Uninstalled: FtpBlogEngineNetAuthentication, Version=1.0.0.0, Culture=neutral, PublicKeyToken=426f62526f636b73, processorArchitecture=MSIL
Number of assemblies uninstalled = 1
Number of failures = 0
SUCCESS: The process with PID 12656 has been terminated.
FtpBlogEngineNetAuthentication -> C:\Users\dude\Documents\Visual Studio 2010\Projects\FtpBlogEngineNetAuthentication\FtpBlogEngineNetAuthentication\bin\Release\FtpBlogEngineNetAuthentication.dll
Microsoft (R) .NET Global Assembly Cache Utility. Version 4.0.30319.1

Assembly successfully added to the cache
The Microsoft FTP Service service is starting.
The Microsoft FTP Service service was started successfully.

========== Rebuild All: 1 succeeded, 0 failed, 0 skipped ==========

If you analyze the output from the build process, you will see that the commands in my earlier samples stopped the FTP service, removed the existing assembly from the GAC, terminated any orphaned DLLHOST.EXE processes, registered the newly-built DLL in the GAC, and then restarted the FTP service.

By utilizing these pre-build/post-build commands, I have been able to work around situations where a DLLHOST.EXE process is being orphaned and caching old assemblies in memory.

Note: This blog was originally posted at http://blogs.msdn.com/robert_mcmurray/
Posted: Jan 30 2013, 18:13 by Bob | Comments (0) |
• Currently 0/5 Stars.
• 1
• 2
• 3
• 4
• 5
Filed under: Scripting | FTP | Extensibility
Social Bookmarks: E-mail | Kick it! | DZone it! | del.icio.us

## Programmatically Starting and Stopping FTP Sites in IIS 7 and IIS 8

I was recently contacted by someone who was trying to use Windows Management Instrumentation (WMI) code to stop and restart FTP websites by using code that he had written for IIS 6.0; his code was something similar to the following:

Option Explicit
On Error Resume Next

Dim objWMIService, colItems, objItem

' Attach to the IIS service.
Set objWMIService = GetObject("winmgmts:\root\microsoftiisv2")
' Retrieve the collection of FTP sites.
Set colItems = objWMIService.ExecQuery("Select * from IIsFtpServer")
' Loop through the sites collection.
For Each objItem in colItems
' Restart one single website.
If (objItem.Name = "MSFTPSVC/1") Then
Err.Clear
objItem.Stop
If (Err.Number <> 0) Then WScript.Echo Err.Number
objItem.Start
If (Err.Number <> 0) Then WScript.Echo Err.Number
End If
Next

The problem that the customer was seeing is that this query did not return the list of FTP-based websites for IIS 7.0 or IIS 7.5 (called IIS7 henceforth), although changing the class in the query from IIsFtpServer to IIsWebServer would make the script work with HTTP-based websites those versions of IIS7.

The problem with the customer's code was that he is using WMI to manage IIS7; this relies on our old management APIs that have been deprecated, although part of that model is partially available through the metabase compatibility feature in IIS7. Here's what I mean by "partially": only a portion of the old ADSI/WMI objects are available, and unfortunately FTP is not part of the objects that can be scripted through the metabase compatibility feature in IIS7.

That being said, what the customer wants to do is still possible through scripting in both IIS7 and IIS8, and the following sample shows how to loop through all of the sites, determine which sites have FTP bindings, and then stop/start FTP for each site. To use this script, copy the code into a text editor like Windows Notepad and save it with a name like "RestartAllFtpSites.vbs" to your system, then double-click the file to run it.

' Create an Admin Manager object.

' Retrieve the sites collection.
Set sitesCollection = sitesSection.Collection

' Loop through the sites collection.
For siteCount = 0 To CInt(sitesCollection.Count)-1
isFtpSite = False
' Determine if the current site is an FTP site by checking the bindings.
Set siteElement = sitesCollection(siteCount)
Set bindingsCollection = siteElement.ChildElements.Item("bindings").Collection
For bindingsCount = 0 To CInt(bindingsCollection.Count)-1
Set bindingElement = bindingsCollection(bindingsCount)
If StrComp(CStr(bindingElement.Properties.Item("protocol").Value),"ftp",vbTextCompare)=0 Then
isFtpSite = True
Exit For
End If
Next
' If it's an FTP site, start and stop the site.
If isFtpSite = True Then
Set ftpServerElement = siteElement.ChildElements.Item("ftpServer")
' Create an instance of the Stop method.
Set stopFtpSite = ftpServerElement.Methods.Item("Stop").CreateInstance()
' Execute the method to stop the FTP site.
stopFtpSite.Execute()
' Create an instance of the Start method.
Set startFtpSite = ftpServerElement.Methods.Item("Start").CreateInstance()
' Execute the method to start the FTP site.
startFtpSite.Execute()
End If
Next

And the following code sample shows how to stop/start a single FTP site. To use this script, copy the code into a text editor like Windows Notepad, rename the site name appropriately for one of your FTP sites, save it with a name like "RestartContosoFtpSite.vbs" to your system, then double-click the file to run it.

' Create an Admin Manager object.

' Retrieve the sites collection.
Set sitesCollection = sitesSection.Collection

' Locate a specific site.
siteElementPos = FindElement(sitesCollection, "site", Array("name", "ftp.contoso.com"))
If siteElementPos = -1 Then
WScript.Quit
End If

' Determine if the selected site is an FTP site by checking the bindings.
Set siteElement = sitesCollection(siteElementPos)
Set bindingsCollection = siteElement.ChildElements.Item("bindings").Collection
For bindingsCount = 0 To CInt(bindingsCollection.Count)-1
Set bindingElement = bindingsCollection(bindingsCount)
If StrComp(CStr(bindingElement.Properties.Item("protocol").Value),"ftp",vbTextCompare)=0 Then
isFtpSite = True
Exit For
End If
Next

' If it's an FTP site, start and stop the site.
If isFtpSite = True Then
Set ftpServerElement = siteElement.ChildElements.Item("ftpServer")
' Create an instance of the Stop method.
Set stopFtpSite = ftpServerElement.Methods.Item("Stop").CreateInstance()
' Execute the method to stop the FTP site.
stopFtpSite.Execute()
' Create an instance of the Start method.
Set startFtpSite = ftpServerElement.Methods.Item("Start").CreateInstance()
' Execute the method to start the FTP site.
startFtpSite.Execute()
End If

' Locate and return the index for a specific element in a collection.
Function FindElement(collection, elementTagName, valuesToMatch)
For i = 0 To CInt(collection.Count) - 1
Set elem = collection.Item(i)
If elem.Name = elementTagName Then
matches = True
For iVal = 0 To UBound(valuesToMatch) Step 2
Set prop = elem.GetPropertyByName(valuesToMatch(iVal))
value = prop.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

I hope this helps!

Note: This blog was originally posted at http://blogs.msdn.com/robert_mcmurray/
Posted: Oct 03 2012, 08:57 by Bob | Comments (0) |
• Currently 0/5 Stars.
• 1
• 2
• 3
• 4
• 5
Filed under: Extensibility | FTP | IIS | Scripting
Tags: , , ,
Social Bookmarks: E-mail | Kick it! | DZone it! | del.icio.us

## Advanced Log Parser Charts Part 5 - Creating a Generic Chart Color Script

In Part 5 of this series, I'll show you how to create a generic script that you can use to add some color to your Log Parser charts. As I mentioned in Part 1 of this series, the default colors for Log parser charts are really dull and boring. For example, if I parse one month's worth of log files from one of my low-volume websites with the following query:

logparser.exe "SELECT date,COUNT(*) AS Hits INTO HITS.gif FROM *.log GROUP BY date ORDER BY date" -i:w3c -o:CHART -chartType:ColumnClustered -chartTitle:"" -q:ON

Log Parser will create the following ugly daily hits chart:

### Generic Color Change Script

Here's the background story for this blog: I have a collection of scripts that I use to format my charts, several of which have faithfully served as the fodder for this blog series. With that in mind, I had a situation recently where I was querying logs with a series of data just like this, and of course the resulting charts were kind of hideous to look at. In one of the scripts that I often use, I create an array of colors to use, and then I apply the various colors to the individual data points in the series.

In the past I have always hard-coded the length for the array of colors based on the data that I am working with, but in this situation I had no idea how many data points I would have, so I decided to put together a quick script with an array that would work with a series of any size.

Here's the resulting script:

// Set a default color for the chart's data.
chart.SeriesCollection(0).Interior.Color = "#ffcccc";

// Define a short array of colors.
var colors = [
"#ffff99", "#ff99ff", "#ff9999",
"#99ffff", "#99ff99", "#9999ff",
"#ffffcc", "#ffccff", "#ffcccc",
"#ccffff", "#ccffcc", "#ccccff"
];

// Loop through the data points in the series.
for (x=0;x<chart.SeriesCollection(0).Points.Count;++x)
{
// Set the color for the data point based on modulo division of the array length.
chart.SeriesCollection(0).Points(x).Interior.Color = colors[x % colors.length ];
}

That's all that there is to the script - it's pretty simple. If I take the above script and save it as "FormatChart.js", I can use that script with my Log Parser query from earlier by adding an extra parameter to the command:

logparser.exe "SELECT date,COUNT(*) AS Hits INTO HITS.gif FROM *.log GROUP BY date ORDER BY date" -i:w3c -o:CHART -chartType:ColumnClustered -chartTitle:"" -q:ON -config:FormatChart.js

Now Log Parser will create the following daily hits chart with a great deal more color to it:

Okay - perhaps that's not the best color palette, but you get the idea. It looks even better when I change the query to use 3D charts:

logparser.exe "SELECT date,COUNT(*) AS Hits INTO HITS.gif FROM *.log GROUP BY date ORDER BY date" -i:w3c -o:CHART -chartType:Column3D -chartTitle:"" -q:ON -config:FormatChart.js

The above query creates the following chart:

### Color Changing Pie Charts

I'd like to make a quick change to the script in order to make it work a little better with a pie chart:

// Set a default color for the chart's data.
chart.SeriesCollection(0).Interior.Color = "#cccccc";

// Define a short array of colors.
var colors = [
"#cc3333", "#3333cc", "#33cc33",
"#33cccc", "#cccc33", "#cc33cc"
];

// Loop through the data points in the series.
for (x=0;x<chart.SeriesCollection(0).Points.Count;++x)
{
// Set the color for the data point based on modulo division of the array length.
chart.SeriesCollection(0).Points(x).Interior.Color = colors[x % colors.length ];
}
// Rotate the chart 180 degrees - just so it looks a little better.
chartSpace.Charts(0).PlotArea.RotateClockwise();
chartSpace.Charts(0).PlotArea.RotateClockwise();

For this query I'd like to see a break down by HTTP status, and this necessitates some small change to the Log parser query:

logparser.exe "SELECT sc-status AS Status,COUNT(*) AS Hits INTO HITS.gif FROM *.log GROUP BY Status ORDER BY Status" -i:w3c -o:CHART -chartType:PieExploded3D -chartTitle:"" -q:ON -config:FormatChart.js

The above query creates the following chart:

### Summary

That wraps it up for this blog - I hope that I've given you some ideas for ways that you can easily add some colors to some dull-looking Log Parser charts.

Note: This blog was originally posted at http://blogs.msdn.com/robert_mcmurray/
Posted: Aug 24 2012, 18:53 by Bob | Comments (0) |
• Currently 0/5 Stars.
• 1
• 2
• 3
• 4
• 5
Filed under: LogParser | Scripting
Social Bookmarks: E-mail | Kick it! | DZone it! | del.icio.us