Using FrontPage 2003 to Consolidate Images in a Common Folder

A few months ago I wrote a blog titled Using FrontPage 2003 to Bulk Rename Images Using VBA, in which I shared a VBA macro that renamed all of the images in a website to a common file-naming syntax. In that blog I explained my reasoning behind my use of the long-outdated FrontPage 2003, and that reason was that FrontPage's "Link Fix Up" feature replicates file renames across your entire website. This single feature can greatly reduce your development time for websites when you have a lot of renaming to do.

Recently I ran into another interesting situation where combining with FrontPage's VBA and "Link Fix Up" features saved me an incredible amount of time, so I thought that I would share that in today's blog.

Problem Description and Solution

I recently inherited a large website with thousands of images that were spread across dozens of folders throughout the website. Unfortunately, this website was created by several developers, so there were a large number of duplicate images scattered throughout the website.

It would have taken me several days to remove all of the duplicates and edit all of the HTML in the web pages, so this seemed like a task that was better suited for automation in FrontPage 2003.

VBA Bulk Image Moving Macro

The following VBA macro for FrontPage 2003 will locate every image in a website, and it will move all images to the website's root-level "images" folder if they are not already located in that folder:

Public Sub MoveImagesToImagesFolder()
    Dim objFolder As WebFolder
    Dim objWebFile As WebFile
    Dim intCount As Integer
    Dim strExt As String
    Dim strRootUrl As String
    Dim strImagesUrl As String
    Dim blnFound As Boolean
    
    ' Define the file extensions for image types.
    Const strValidExt = "jpg|jpeg|gif|bmp|png"
    ' Define the images folder name.
    Const strImagesFolder = "images"

    With Application
        ' Retrieve the URL of the website's root folder.
        strRootUrl = LCase(.ActiveWeb.RootFolder.Url)
        ' Define the root-level images folder URL.
        strImagesUrl = LCase(strRootUrl & "/" & strImagesFolder)
        
        ' Set the initial search status to not found.
        blnFound = False
        ' Loop through the root-level folders.
        For Each objFolder In .ActiveWeb.RootFolder.Folders
            ' Search for the images folder.
            If StrComp(objFolder.Url, strImagesUrl, vbTextCompare) = 0 Then
                ' Exit the loop if the images folder is found.
                blnFound = True
                Exit For
            End If
        Next
        
        ' Test if the images folder is missing...
        If blnFound = False Then
            ' ... and create it if necessary.
            .ActiveWeb.RootFolder.Folders.Add strImagesFolder
        End If
     
        ' Loop through the collection of images.
        For Each objWebFile In .ActiveWeb.AllFiles
            ' Retrieve the file extension.
            strExt = LCase(objWebFile.Extension)
            ' Test if the file extension is for an image type.
            If InStr(1, strValidExt, strExt, vbTextCompare) Then
                ' Test if the image is in the root-level images folder...
                If StrComp(objWebFile.Parent, strImagesUrl, vbTextCompare) <> 0 Then
                    ' ... and move the file if it is not.
                    objWebFile.Move strImagesUrl & "/" & objWebFile.Name, True, True
                End If
            End If
        Next
    End With

End Sub

In Closing...

This macro is pretty straight-forward, but there are a couple of parameters that I pass to the WebFile.Move() method which I would like to point out. The first parameter for the Move() is the destination URL, which should be obvious, but the second and third parameters should be explained:

  • The second parameter is set to True in order to update hyperlinks during the move process; this is the "Link Fix Up" feature.
  • The third parameter is set to True in order to overwrite duplicate files; this has the potential to be a destructive operation if you are not careful. In my situation that was acceptable, but you might want to double-check your content first.

Another thing to note is that you can easily update this macro to move other file types. For example, you could move all of the JavaScript files in your website to a common root-level "scripts" folder by changing the values of the strValidExt and strImagesFolder constants.

As always, have fun... ;-]

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

Custom Post-Build Events for Compiling FTP Providers

I've written a lot of walkthroughs and blog posts about creating custom FTP providers over the past several years, and I usually include instructions like the following example for adding a custom post-build event that will automatically register your extensibility provider in the Global Assembly Cache (GAC) on your development computer:

  • Click Project, and then click the menu item your project's properties.
  • Click the Build Events tab.
  • Enter the following in the Post-build event command line dialog box:
    net stop ftpsvc
    call "%VS100COMNTOOLS%\vsvars32.bat">nul
    gacutil.exe /if "$(TargetPath)"
    net start ftpsvc

And I usually include instructions like the following example for determining the assembly information for your extensibility provider:

  • In Windows Explorer, open your "C:\Windows\assembly" path, where C: is your operating system drive.
  • Locate the FtpXmlAuthorization assembly.
  • Right-click the assembly, and then click Properties.
  • Copy the Culture value; for example: Neutral.
  • Copy the Version number; for example: 1.0.0.0.
  • Copy the Public Key Token value; for example: 426f62526f636b73.
  • Click Cancel.

Over time I have changed the custom post-build event that I use when I am creating custom FTP providers, and my changes make it easier to register custom FTP providers. With that in mind, I thought that my changes would make a good blog subject.

First of all, if you take a look at my How to Use Managed Code (C#) to Create a Simple FTP Authentication Provider walkthrough, you will see that I include instructions like my earlier examples to create a custom post-build event and retrieve the assembly information for your extensibility provider.

That being said, instead of using the custom post-build event in that walkthrough, I have started using the following custom post-build event:

net stop ftpsvc
call "$(DevEnvDir)..\Tools\vsvars32.bat"
gacutil.exe /uf "$(TargetName)"
gacutil.exe /if "$(TargetPath)"
gacutil.exe /l "$(TargetName)"
net start ftpsvc

This script should resemble the following example when entered into Visual Studio:

This updated script performs the following actions:

  1. Stops the FTP service (this will allow any copies of your DLL to unload)
  2. Loads the Visual Studio environment variables (this will add gacutil.exe to the path)
  3. Calls gacutil.exe to forcibly unregister any previous version of your FTP provider
  4. Calls gacutil.exe to forcibly register the newly-compiled version of your FTP provider
  5. Calls gacutil.exe to list the GAC information for your FTP provider (this will be used to register your DLL with IIS)
  6. Starts the FTP service

Let's say that you created a simple FTP authentication provider which contained code like the following example:

using System;
using System.Text;
using Microsoft.Web.FtpServer;

public class FtpTestProvider :
    BaseProvider,
    IFtpAuthenticationProvider
{
    private string _username = "test";
    private string _password = "password";
    
    public bool AuthenticateUser(
        string sessionId,
        string siteName,
        string userName,
        string userPassword,
        out string canonicalUserName)
    {
        canonicalUserName = userName;
        if (((userName.Equals(_username,
            StringComparison.OrdinalIgnoreCase)) == true) &&
            userPassword == _password)
        {
            return true;
        }
        else
        {
            return false;
        }
    }
}

When you compile your provider in Visual Studio, the output window should show the results of the custom post-build event:

When you examine the output information in detail, the highlighted area in the example below should be of particular interest, because it contains the assembly information for your extensibility provider:

------ Rebuild All started: Project: FtpTestProvider, Configuration: Debug Any CPU ------
FtpTestProvider -> c:\users\foobar\documents\visual studio 2012\Projects\FtpTestProvider\bin\Debug\FtpTestProvider.dll
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.17929
Copyright (c) Microsoft Corporation. All rights reserved.

Assembly successfully added to the cache
Microsoft (R) .NET Global Assembly Cache Utility. Version 4.0.30319.17929
Copyright (c) Microsoft Corporation. All rights reserved.

The Global Assembly Cache contains the following assemblies:
FtpTestProvider, Version=1.0.0.0, Culture=neutral, PublicKeyToken=eb763c2ec0efff75, processorArchitecture=MSIL

Number of items = 1
The Microsoft FTP Service service is starting.
The Microsoft FTP Service service was started successfully.

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

Once you have that information, you simply need to reformat it as "FtpTestProvider, FtpTestProvider, Version=1.0.0.0, Culture=neutral, PublicKeyToken=eb763c2ec0efff75" in order to enter it into the FTP Custom Authentication Providers dialog box in the IIS Manager, or by following the steps in my FTP Walkthroughs or my Adding Custom FTP Providers with the IIS Configuration Editor blogs.

That wraps it up for today's post. As always, let me know if you have any questions. ;-]

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

Being Sick in the Military

One of my family members posted the following picture to Facebook:

I'm not a teacher so I can't speak about the veracity of that statement, but nevertheless I felt compelled to post the following response:

"Not true - when you're in the military, it's much worse. Here's just one example from my years in the service:

"I had food poisoning and I spent the night throwing up so much that I lost 10 pounds in one day. (Seriously.) But the military owns you, so you can't just call in sick. Despite feeling like I was about to die, I had to drive 30 minutes to the military post and show up for a morning formation, where I stood at-attention or at-ease for a 30-minute summary of the days' news and events. After that formation ended, all of people who wanted to go on sick call were ordered to fall out to a separate formation, where I had to describe my symptoms to the NCOIC, who was tasked with determining if anyone should actually be allowed to head to the clinic or go back to work.

"Bear in mind that the clinic that I was sent to was not a hospital where I would see an actual doctor, but a tiny building where no one had any formal medical education. For that matter, sick call is a horrible experience where you eventually get to meet up with a disgruntled E-4 who's sorry that he volunteered for his MOS and generally wants to take out that frustration on every person who shows up; since he has no formal education, he is only capable of reading symptoms from a book to diagnose you, so it's a miracle that more deaths do not occur in military clinics due to gross negligence. (Although I have long stories about deaths and permanent injuries that were the direct result of misdiagnoses in military clinics, but I'm getting ahead of myself.)

"Before I got to see said disgruntled E-4, I had to sit in a waiting room for around an hour, so by the time I was finally sent to an examining room I had been on post for several hours and it was probably approaching noon. The E-4 was able to figure out that I was seriously ill pretty quickly - all of the vomiting was an easy clue. He decided by taking my blood pressure that I was severely dehydrated, (duh), so I spent the next few hours lying on a cot with IV bags in my arms until he decided that I was sick enough to be put on quarters for the rest of the day and I was sent home.

"By the time that I finally arrived at [my wife's and my] apartment it was sometime in the late afternoon, which is when the normal workday would probably have been over for most civilians. But when you're in the military they try to make your illness so unbearable that you'd rather show up to work, so here's the worst part: when you have something like the flu that lasts more than a day, you have to repeat the process that I just described until your illness has passed or you are admitted to a hospital because you are not recovering. Of course, having to sit in a clinic with a score of other sick people means that everyone is trading illnesses, so you have this great breeding ground of diseases in the military, which undoubtedly helped cause a great deal of the military fatalities during the great influenza outbreak in the early 20th century.

"So being sick as a teacher may be awful, but trust me - you could have it a lot worse."

As a parting thought, there may be some qualified people in Army medicine, but I have always pointed out that people who graduate at the top of their class in medical school do not become Army doctors; they take prestigious positions at world-class hospitals. Who usually winds up as military doctors? Medical students with barely passing grades and large student loans to pay off.

Given a choice between a doctor with a 4.0 GPA from Harvard Medical School or a doctor with a 2.5 GPA from The Podunk Medical Academy for the Emotionally Challenged, who would you pick? Well, when you're in the Army, you don't get to pick. And unless you're a general, it's usually the latter of those two choices.

I have always summarized Army medicine as follows: "You get what you pay for when you see an Army doctor; you pay nothing, and you get nothing."