How To Port WordBasic Macros To VBA
Transcription
How To Port WordBasic Macros To VBA
Applications:Programming How To Port WordBasic Macros To VBA WordBasic, the macro language found in previous versions of Microsoft Word for Windows, is no more. It has been replaced in Word 97 with Visual Basic for Applications, or VBA. While VBA is very similar to WordBasic, it is sufficiently different that almost all non-trivial macros will need some slight conversion work. Thankfully, this is not as difficult as many users and support staff have feared. By Tim Nott W ith the release of Office 97, Microsoft finally completed the implementation of Visual Basic for Applications (VBA) as a suite-wide programming language. Whereas this is undoubtedly a great step forward, it can come as something of a shock for those who have been happily automating away in WordBasic since version 1 of Word for Windows. This article has three parts. First, we look at the short-term problems of upgrading WordBasic macros to work in Word 97. Next comes an overview of VBA, highlighting the conceptual and practical differences, and finally a hands-on session illustrating some of the features and commands. For the sake of clarity, note that Microsoft’s idiosyncratic version numbering for Word for Windows runs 1, 2, 6, 7, 97. want to save the changes to the template. If you answer No then next time you open this document - or any other based on the same template - the process will be repeated. If you answer yes, then you have the choice of overwriting the existing template or creating a new file. As we shall see, there are several compelling reasons why you might not want to overwrite the existing template. If you save the template under a different filename, then you will still get the conversion process on opening the document and the “Save changes to template” prompt. The same happens if you save the same filename to a different folder. The only way to get rid of this infuriating behaviour is to go to the Tools/Options/File Locations dialog and specify a different folder for Word 97 templates. Saving the converted templates, with their original filenames, to this new location will cure the problem. The second bit of bad news is that Word doesn’t convert the macros to native Visual Basic code. If you load a converted macro into the Visual Basic Editor you’ll see a number of changes - any previously undeclared variables will be declared and some variable Existing Macros The good news, at least in the short term, is that Word 97 will convert the macros in your existing Word 7 or Word 6 templates. This happens whenever you open a template, start a new document based on a template, or open an existing document based on one. Helpful though this may be, it does conceal a great deal of bad news. Firstly, the conversion happens automatically. If, in Word 97, you open a document based on a Word 6 or 7 template there will be a delay while the macros are converted. When you exit the document, you’ll be asked if you Update 105 (July 1997) Page 15 Figure 1 - Try to open an encrypted macro and you’ll be told that it can’t be done. PC Support Advisor File: A0523.1 Applications:Programming Sub MAIN tempname$ = LCase$(GetDocumentProperty$(“Template”)) If tempname$ = “normal.dot” Then nummacros = CountMacros(0, 0, 0) / isnotnormal = 0 Else nummacros = CountMacros(1, 0, 0) isnotnormal = 1 End If EditSelectAll EditClear For n = 1 To nummacros mname$ = MacroName$(n, isnotnormal) If IsExecuteOnly(tempname$ + “:” + mname$) Then Bold 1 Insert tempname$ + “:” + mname$ + “ is execute-only” Bold 0 InsertPara Else Insert tempname$ + “:” + mname$ + “ is editable” InsertPara End If Next n End Sub Figure 2 - Testing for the presence of execute-only macros in Word 7 templates. WordBasic Visual Basic FileNew .Template = \“BIZFAX.DOT”, \NewTemplate = 0 Documents.Add Template:=_ “FAX.DOT”, NewTemplate:= False WordRight 2, 1 Selection.MoveRight Unit:=wd_ Word, Count:=2,Extend:=wdExtend StartOfDocument Selection.HomeKey Unit:=wdStory RightPara Selection.ParagraphFormat._ Alignment = wdAlignParagraph_ Right Figure 3 - WordBasic and VBA equivalents. File: A0523.2 PC Support Advisor names may be changed. The bulk of the existing WordBasic commands and functions, however, won’t be replaced by their VBA equivalents but are treated as WordBasic “objects”. For example, the VBA equivalent of the WordBasic command “Insert string” is “Selection.TypeText Text:=string”. But the command is converted to “WordBasic.Insert string”. Similarly, WordBasic functions will be enclosed in square brackets. Despite this “pidgin VBA” approach, converted macros in general work well enough, though there can sometimes be unexpected behaviour. In particular, WordBasic macros may contain syntax errors that are never spotted because the code is never executed - it may, for example, form part of an error-testing routine where the error has never arisen. Visual Basic, on the other hand, first compiles the entire code into compressed statements that are in turn executed by the interpreter, so any such skeletons in the cupboard will be exposed immediately when the macro is run. The third episode of the bad-news saga is that there is a rather nasty bug with execute-only (also known as encrypted) macros. An execute-only macro is one that can’t be edited by the user - the idea being that end-users can’t meddle with the source code. To make a macro execute-only, you have to create another macro to copy it to another template with the “ExecuteOnly” switch. Typically, the WordBasic code would run: CopyMacro ”source:macro1", ”user:macro1", ExecuteOnly which copies the existing macro1 from SOURCE.DOT to USER.DOT and encrypts it. If the user then selects the macro from the list, they won’t get an Edit button. Note that the process is not reversible - you can’t unencrypt an execute-only macro. When Word 97 converts earlier macros it preserves the encryption - try to open one in the Visual Basic editor and you’ll get a message box saying the project is locked and unviewable - see Figure 1. The bug is that if you convert a tem- Update 105 (July 1997) Page 16 Applications:Programming WordBasic Macros plate containing any encrypted macros, all macros in that template will be encrypted. VBA code can only be protected at project level - ie, a document or template; individual macros can’t be encrypted. Note also this doesn’t apply to NORMAL.DOT - this is backed up to NORMAL.WBK the first time you run Word 97, and any execute-only macros are not converted. It would be difficult to accidentally encrypt a macro, but many Microsoft or third-party add-on macros come in encrypted form. Although you can check for execute-only macros by scrolling through the list of macros and “Although both VBA and Wordbasic are dialects of Basic, there are considerable differences, both in concepts and in practice.” seeing which are editable, this is a tedious and error-prone business. Fortunately, WordBasic provides the “IsExecuteOnly()” function to test for these. If you create a Global macro (ie, WordBasic Visual Basic MsgBox “Hello” + \Chr$(9) + “World” MsgBox “Hello” & vbTab &_ “World” Insert “Name: ” + \Chr(13) + Chr$(10) \+ “Address: ” Selection.InsertAfter_ Text:="Name: “ & vbCrLf &_ ”Address: “ Figure 4 - Strings and constants. stored in NORMAL.DOT) with the code in Figure 2, this provides a way of checking. Create a new document based on the template you wish to examine and run the macro: it will clear the contents of the new document, if any, and create a list of all macros noting whether they are editable or not. Apart from the highlighting of execute-only ones in bold, all bells, whistles and batch processing has been left as an exercise for the reader. Preparation All of this makes upgrading a potentially hazardous business, especially so if you are servicing a mixed environment of Word 97 and previous versions. It is essential, therefore, to take preventative measures before upgrading or running both versions side by side. The first essential is to back up existing templates. The second is to ensure that templates for Word 97 are stored in a different folder to those for 6 or 7. The third is to weed out all execute-only macros before doing any conversion. Objects, Properties, Methods Figure 5 - Word 97’s new VBA macro editor in action. Update 105 (July 1997) Page 17 PC Support Advisor Although both VBA and Wordbasic are dialects of Basic, there are considerable differences, both in concepts and in practice. Whereas WordBasic consists of a flat set of commands, VBA is structured as a hierarchy of objects. Objects can exist on their own, or in collections. For instance “Selection” is a unique object and “Documents(”Myletter.doc”)” a member of the collection of open document objects. Objects have properties - a document has a title and an author, a selec- File: A0523.3 Applications:Programming tion has a font style and size. A property of an object can return another object, which itself has properties that return objects. A full-stop is used to drill down through objects. For instance, “Selection.Font.Bold” returns the bold property of the font object, which is in turn a property of the selection object. In WordBasic, statements and functions are used to perform related tasks. For example, the statement “Bold 1” turns bold formatting on, whereas the function ”Bold()” returns the state of boldness. In VBA, read/write properties can be read or set via the same object hierarchy. There is no need for a separate function and statement: “Selection.Font.Bold = True” turns on bold formatting, whilst “boldstate = Selection.Font.Bold” returns the state. Objects aren’t just confined to documents and their parts. For example, the Office Assistant is an object that has properties including animation, sounds and visibility. Options is another object that has properties such as AutoWordSelection and BackgroundSave that correspond to the choices in Word’s Tools/Options menu. You’ll “Whereas WordBasic consists of a flat set of commands, VBA is structured as a hierarchy of objects. Objects can exist on their own, or in collections.” find a map of the complete Word object hierarchy in the MS Word Visual Basic Help file (VBAWRD8.HLP). Manipulation As well as having properties that can be changed, objects can be manipulated using methods. The TypeText method, for instance, is equivalent to the WordBasic “Insert” command but the syntax is very different. The latter is free-ranging - “Insert “Hello world”” - does just that, at the current insertion point in the document. In VBA we need to specify an object (the target for the action) and a method (the action itself) together with any ar- guments. So the above example becomes “Selection.TypeText Text:= “Hello world””. Note that to assign a property to an object you use an equals sign, but to pass an argument to a method you use a space between the method and the argument name, and a colon followed by an equals sign between the argument name and its value. Commas separate additional arguments. Figure 3 shows some WordBasic statements alongside their VBA equivalents. Note that the underscore character replaces the backslash when breaking an instruction over more than one line. The Word Visual Basic Help file contains a complete list of WordBasic to VBA equivalents, which you will find in the “Converting from WordBasic...” subsection of the “Getting started...” chapter. Beware, though, as some commands are more equal than others. Consider the following WordBasic one-liner: ChDir “C:\myfiles\letters\bank” I have a variant of this in an Autonew macro for every template, pointing to the appropriate folder. It helps ensure documents get saved to the correct folder, and saves the trouble of clicking up and down the tree in the File/Save dialog. The list of equivalents shows the command unchanged, but it doesn’t work - you need to use: ChangeFileOpenDirectory “C:\myfiles\letters\bank” Figure 6 - The form for our simple billing system example. File: A0523.4 PC Support Advisor It’s immediately apparent that VBA code is considerably more verbose than WordBasic. Fortunately, there are Update 105 (July 1997) Page 18 Applications:Programming WordBasic Macros a number of labour-saving devices at our disposal. One such is the With...End With block. This lets you specify an object, then carry out a series of commands without having to reidentify the object each time. For example, to change multiple font options in WordBasic you might use: FormatFont .Font = “Courier New”, .Bold = 1, .Points = 12 In VBA, this becomes three statements, each assigning a different property to the Font object: Selection.Font.Name = “CourierNew” Selection.Font.Bold = True Selection.Font.Size = 12 Using the With...End With statement eliminates the repetition. With Selection.Font .Name = “Courier New” .Bold = True .Size = 12 End With Another time-saving control is the For Each...Next loop, which repeats a set of statements for each element in a collection or array. Unlike a standard For...Next loop, you don’t need to specify the number of iterations. You can also exit the loop, say from an If statement, with Exit For. Variables And Constants Whereas WordBasic has just two types of variables (or arrays), string and numeric, Visual Basic has 10, ranging from Boolean (2 bytes) through high-precision currency (8 bytes), and date. The latter ranges from 1 January 100 to 31 December 9999, so shouldn’t cause any millennium worries for the next 8000 years or so. The 32,000-character limit on string variables is expanded to around two billion characters. You don’t have to specify a data type when declaring a variable - VBA will default to the Variant type, which encompasses string, numeric and date forms. Nor do you have to end a string variable with a dollar sign. VBA also makes substan- Update 105 (July 1997) Page 19 tial use of constants in a variety of ways. One example is in building strings - although you can still use the Chr$() function to insert tabs and carriage returns, VBA provides the constants vbTab and vbCrLf to do this. Figure 4 shows some examples note that the ampersand replaces the plus sign for concatenating strings. Another use of constants is in returning or setting properties of an object instead of using a numeric value. For example, the property of a toolbar button being visually “pushed in” is returned by: CommandBars(“MyToolbar”). Controls(n).State% 0 This evaluates to 0 for up, -1 for down. Though more longwinded it makes code much easier to understand if you use the constants msoButtonUp and msoButtonDown instead. Practicalities You can still record and play back macros in the time-honoured way. This is often a useful way to generate code - especially when you are not sure of the commands - that can be refined by later editing. The in-place WordBasic editor is a thing of the past, however, as is the concept of macro code as one, continuous piece of text. You launch the Visual Basic editor from Word’s Tools/Macro menu. Though there’s a far more crowded environment than the WordBasic equivalent, it isn’t as daunting as it looks. By default, there are two small panes on the left of the screen, with the remainder available for editing code and objects - see Figure 5. The Project Explorer shows a project for each open Word document (or Excel worksheet or PowerPoint presentation), any templates on which open documents are based, plus the normal template. Each project can contain a variety of objects, grouped into folders. Of these, the Microsoft Word Objects folder contains the ThisDocument object. This can store code for the document that responds to events, such as opening or closing. The References folder, unsurprisingly, shows the references to other projects - for example, a document’s template. For now, the folders that concern us are Modules and Forms. Each module Private Sub UserForm_Initialize() With Billform.txtClient .AddItem “Smith and Jones” .AddItem “Barnard’s Bank” .AddItem “Robinson Assurance” .ListIndex = 0 End With End Sub Figure 7 - Adding some events to the sample billing system. Private Sub amtHours_Exit(ByVal Cancel As MS_ Forms.ReturnBoolean) If Not IsNumeric(amtHours.Value) Then MsgBox “Please enter a number for the hours” Cancel = True End If End Sub Figure 8 - Add some idiot-proofing. PC Support Advisor File: A0523.5 Applications:Programming can contain one or more subroutines or functions, known as procedures. Each form corresponds to a custom dialog box. Note that, unlike WordBasic, documents as well as templates can contain macros. Some, but not all, objects have properties, which are shown in the Properties Window. Select ThisDocument and you’ll see a list of properties corresponding to the option settings of the document (eg, whether spelling errors are underlined) and its current state (eg, whether it has been saved since the last editing action). To create a new procedure, go to the Project Explorer, right-click anywhere in the project, and select Insert - you’ll get the choice of Module, Class Module or User Form. To edit an existing module or form, right-click on it, then choose View Code. Boxing Clever The separate WordBasic visual dialog editor, with its clumsy paste-tocode approach, is another thing of the past. Instead, custom dialog boxes, known as User Forms, are stored as Public Public Public Public Public Public Public independent objects in the Forms folder for the project, and you can create and test dialogs independently of procedures that call them. Designing a dialog involves switching between the Object Window, where you create and edit controls visually, the Properties Window, where you set properties for the controls, and the Code Window, where you write code that defines how the box and its controls respond to events. Try this. Create a new form, add a text label and a button with the appropriate tools from the palette. Click anywhere on the form, but outside the two controls you have added, then scroll through the list in the Properties Window - you’ll see a complete list of all the properties pertaining to the form itself. Find Caption and replace the default value of UserForm1 with some other text - you’ll see the title bar of the form change as you type. Select the text label, and you’ll find the contents of the Properties Window changes. Note that with the label or the button you can change the caption either from the Properties Window or by typing di- forgetit As Boolean clientname As String jobdescript As String hoursbilled As Variant hourlyrate As Variant billtotal As Variant Sub autobill() Figure 9 - Creating a module. Private Sub btnCreate_Click() forgetit = False clientname = txtClient.Text jobdescript = txtJob.Text hoursbilled = amtHours.Value hourlyrate = amtRate.Value billtotal = amtTotal.Value Unload Me End Sub Figure 10 - The Click procedure for the btnCreate button. File: A0523.6 PC Support Advisor rectly into the control. With the button selected, change the value of the Top property to 80 and the Height to 20. It should then be roughly two-thirds of the way down the box. Next, get the box to respond to an event. Double-click on the button, or click on the View Code button at the top of the Project Explorer, and the Code Window will appear, with an empty procedure for the default action - Click - already in place. At the top of the Code Window are two drop-down lists, one for the controls, and the other for the events applicable for each. In the first make sure CommandButton1 is selected. From the second list select MouseMove. A new, empty subroutine will be created - add a second line so it reads: Private Sub CommandButton1_MouseMove... CommandButton1.Top = 200 CommandButton1.Top End Sub If you’ve renamed, rather than recaptioned, the control then substitute accordingly. You are now ready to test the dialog - switch back to object view, and hit the Run button on the toolbar. Word will grab back the focus and the dialog box will appear. All it does at this stage is frustrate the user - the button responds to the event of the mouse moving over it by dodging out of the way. If you click the Close button on the title bar, the box will disappear and you’ll return to the VBA editor. It’s a fairly pointless example in itself, but it does show how a dialog can respond to events and alter itself dynamically at run time. A more useful example might be an Advanced... button that expands the box to reveal further controls. It is way beyond the scope of this article to consider all the properties, controls and events of custom dialogs. The best way to get familiar with these is to experiment with the controls and the Properties Window. Note that you can create multi-paged dialogs, you can group controls in a frame grouped option buttons, for instance, will be mutually exclusive - and generally emulate the look and feel of the built-in Office dialog boxes. Update 105 (July 1997) Page 20 Applications:Programming WordBasic Macros Experimenting with properties reveals ways of customising the look and font of controls, the behaviour of the Enter and Tab keys in text boxes, the order in which the Tab key steps through controls, whether controls are initially hidden and so on. For the rest of this article, we’ll concentrate on cre- Public forgetit As Boolean Public clientname As String Public jobdescript As String Public hoursbilled As Variant Public hourlyrate As Variant Public billtotal As Variant Private Found As Boolean Private Typeitin As String Public Sub autobill() Billform.Show If forgetit = True Then MsgBox “Operation cancelled”, vbExclamation Else Documents.Add Template:="billtemplate" ActiveDocument.Bookmarks(“ad dress”).Select Found = False For Each i In ActiveDocument.AttachedTemplate._ AutoTextEntries If i.Name = clientname Then Found = True ActiveDocument.AttachedTemplate.AutoTextEntries _ (clientname).Insert Where:=Selection.Range End If Next i If Found = False Then MsgBox (“No autotext entry, please add address_ manually”) Selection.InsertAfter (clientname) End If ActiveDocument.Bookmarks(“details”).Select With Selection .InsertAfter Text:=jobdescript & “ - ” &_ hoursbilled & “hours at $” & hourlyrate & “_ per hour.” .InsertParagraphAfter .InsertAfter Text:="Total: $" & billtotal .Collapse (wdCollapseEnd) End With End If End Sub Figure 11 - Complete code for the Autobill module. Update 105 (July 1997) Page 21 PC Support Advisor ating a custom solution that uses just a few of the features. Our Billing System Let us suppose that you want to automate a simple consultancy billing system. You want a macro to prompt for a client name, hours chargeable, hourly rate and description of service, then automatically create an invoice. You have a few regular clients, so you want their names to appear in a drop down list. In Figure 6 we’ve created a simple dialog and named its parts by selecting each one then changing the name field in the properties window. The dialog itself is named Billform, then the first control is a combo box named txtClient. Below that is a text box named txtJob, then three more text boxes - amtHours, amtRate, amtTotal. Below these come two buttons btnCreate and btnCancel. The captions were typed in-place on the buttons, and the text labels added above each control. Since the labels take no active part in the proceedings, they retain their default names. The next step is to set the tab order, which reveals a rather nifty feature of the VBA editor. Select the txtClient combo box then scroll through the properties to TabIndex. This should be zero - ie, this is the control that will have the focus when the dialog starts. Check that the TabStop property is set to True. Click on the txtJob text box, and you’ll find that the property window has kept your place at TabStop. Make sure again that this is True - ie, the focus stops here rather than bypassing - and set TabIndex to 1, creating the next stop on the journey. Check that TabKeyBehaviour is False - so pressing the tab key will move on from the box rather than inserting a literal tab character. Repeat for the amtHours and amtRate boxes with TabIndex values of 2 and 3. The amtTotal box won’t normally be filled in by the user, so set TabStop to False. Set the two buttons’ Tabstop values to 4 and 5. All the labels will default to false TabStops, so they need not concern us further. Test the dialog with the Run button - you should find tabbing and entering File: A0523.7 Applications:Programming values works from left to right, top to bottom, skipping the Total box. Now it’s time to add some events to the box. First, let’s give it some initial values. Double-click on a blank part of the form (or use the button on the Project Explorer) to open the code window for the form. Select UserForm from the object list on the left, and Initialize from the procedure list. Complete the procedure by entering the code in Figure 7. You’ll find that when you test the dialog, the names appear in a list when the button in the combo box is clicked. The default name will correspond to the value of .ListIndex, the first name being zero. The user still has the option of typing in any other name. If you want a blank box rather than a default name, omit the .ListIndex line. Next, we’ll add a little idiot-proofing. In the code window, select the amtHour object and the Exit procedure. Complete it as shown in Figure 8. This ensures the user types a numeric value in the Hours box: if not, a message is displayed and the focus won’t shift until they do. Repeat this, with a corresponding message for the amtRate control’s Exit procedure, but here, add: Else amtTotal.Value = Format (amt-Rate.Value * amtHours.Value, , “##,##0.00") before the End If statement. This will automatically update the total, formatted to two decimal places, when the user tabs or clicks out of the Rate box. Test the dialog again. Note that the enter key performs in the same way as the tab key, moving between fields. Let’s modify this, supposing that the job description might need to run over more than one line. Go to txtJob properties, then set the Multiline, Wordwrap and EnterKeyBehaviour properties to True. The Job box will then behave like a text-editing window and accept returns as literal. You can even add scroll bars if you wish, but we’ll skip the bells and whistles and move on to the buttons. Although we’ve been testing the mechanics of the dialog, in real use it will be called from, and pass data back to, a macro (or module, to use VBA terminology). So first let’s create that module and declare some public variables that the dialog and module can exchange. Right-click in the Project Explorer and create a new module in the same project. Name it Autobill. An empty code window will appear with General and Declarations in the object and procedure boxes. Type the code shown in Figure 9. The editor will automatically complete this by creating a new section for the Sub and adding an End Sub. Now go back to the Billform code (remember you can use the Window menu in the VBA editor), and create the code for the buttons. If the user cancels, then all we need to do is to close the dialog and return an indication that he or she has cancelled. So, complete the Click procedure of the btnCancel object to read: Private Sub btnCancel_Click() forgetit = True Unload Me End Sub is shown in Figure 11. This calls the form with the Show method. If the user cancels, then a message box appears (note the use of the vbExclamation constant instead of the numeric value 48) and nothing further happens. If the Create button is pressed, then a new document is created based on BILLTEMPLATE.DOT. The For Each... Next loop scans the Autotext entries for a match for the client’s name. If found, the entry is pasted in at the Address bookmark. If not, the name is pasted in and the user is asked to add the rest of the address. Next, the selection is moved to the Details bookmark and the rest of the billing details passed from the dialog box are inserted into the document, using a With block. The Collapse method deselects the text that has just been inserted, so it isn’t overwritten. And that concludes this simple example, and the article. Though both can only offer a brief dip into VBA programming, I hope that they give you the impetus to take the plunge. The Unload statement, unsurprisingly, closes its object. The Me keyword is another timesaver - when used in the code for a form or control it represents the code name of the user form. We can now complete the form by writing the Click procedure for the btnCreate - see Figure 10. Going back to the module code, all we now need to do is call the dialog, and do something with the data returned. Since we’re rapidly running out of space, I’m going to cheat a little here and make the following assumptions. There is a user template in the default template folder named BILLTEMPLATE.DOT that contains: 1. Autotext entries that expand the names used in the client list to full names and addresses. 2. Bookmarks in the template named address and details. 3. Suitable boilerplate text to encase the invoice data. 4. A mechanism for inserting an invoice number and the date - this is left as an exercise for the reader. The complete code for the module PCSA The Author Tim Nott (timn@cix.co.uk) is an IT writer based in France. Did you like this tech support article? Then click here for lots more, all FREE! New Reviews from Tech Support Alert Anti-Trojan Software Reviews A detailed review of six of the best anti trojan software programs. Two products were impressive with a clear gap between these and other contenders in their ability to detect and remove dangerous modern trojans. Inkjet Printer Cartridge Suppliers Everyone gets inundated by hundreds of ads for inkjet printer cartridges, all claiming to be the cheapest or best. But which vendor do you believe? Our editors decided to put them to the test by anonymously buying printer cartridges and testing them in our office inkjet printers. Many suppliers disappointed but we came up with several web sites that offer good quality cheap inkjet cartridges with impressive customer service. Windows Backup Software In this review we looked at 18 different backup software products for home or SOHO use. In the end we could only recommend six though only two were good enough to get our “Editor’s Choice” award The 46 Best Freeware Programs There are many free utilities that perform as well or better than expensive commercial products. Our Editor Ian Richards picks out his selection of the very best freeware programs and he comes up with some real gems. Tech Support Alert http://www.techsupportalert.com