Posts Tagged ‘Scripting’

h1

Self Installing VB Scripts

18/07/2009

I like using VB Scripts and the Windows Script Host to resolve issues as it has proved quick and flexible for most tasks.  One thing I have found problematic on occasion is the installation of the scripts on a user’s PC.  It takes time to do if it is not being deployed using logon scripts, group policy, etc.  To this end I decided to write a function to allow a script to install itself.   The script below is primarily made up from three functions/routines.

Starting from the bottom, the BuildFolderPath() function just takes a file path and adds a backslash to the end if it needs one.

The next function is a little more interesting.  ScriptPath() returns the folder in which the script is being run from.  Note that if it is being run from a shortcut it is the target folder.  This script works by removing the name of the script from the full file path of the script file.

The last chunk of code is the sub routine InstallScript() and does the majority of the work so to speak.  This checks to see if the script is in the correct location.  If it is not it asks the user for permission to copy it from the current location to the one against which it checked.  This does of course rely on the user having appropriate file permissions to copy the script there in the first place, but if we need anything more then some sort of executable or direct access by an administrator is always going to be best otherwise you’ll be handing out administrator passwords verbally or in plain text in script files.  Once installed (or if the user chose not to install) there is a bit of feedback to the user and the script ends.  If the script is running from the right location, no further action is taken and the script continues.

Option Explicit

Const INSTALL_TO = "%UserProfile%\SendTo"

InstallScript INSTALL_TO

Msgbox "The script is being run from the right place..."

'This routine checks if the script is in the right folder location and if it
'isn't it will give the user the option of copying the script to the right location
Sub InstallScript(p_strInstallTo)
 Dim strInstallToFolder
 Dim objShell, objFSO

 'Set the installation folder
 Set objShell = CreateObject( "WScript.Shell" )
 strInstallToFolder = BuildFolderPath(objShell.ExpandEnvironmentStrings(p_strInstallTo))

 If Not strInstallToFolder = ScriptPath Then
 'Copy the script to the correct folder if the user agrees
 If Msgbox ("This script is not being run from the expected location." & vbCrLf & _
 "Would you like to install it to " & strInstallToFolder & " now?", _
 vbYesNo + vbQuestion, "Install Script") = vbYes Then

 Set objFSO = CreateObject("Scripting.FileSystemObject")
 objFSO.CopyFile WScript.ScriptFullName, strInstallToFolder & WScript.ScriptName

 MsgBox "The script has been installed/updated.", vbOkOnly & vbInformation, "Install Complete"
 Else
 MsgBox "The script has not been installed and will not run.", vbOkOnly + vbExclamation, "Install Cancelled"
 End If

 'Finish the script at this point
 WScript.Quit
 End If

End Sub

'This function returns the path which the script is running from
Function ScriptPath()
 ScriptPath = Replace(WScript.ScriptFullName, WScript.ScriptName, "")
End Function

'This function just adds a trailing backslash if there isn't one
Function BuildFolderPath(p_strPath)
 If Right(p_strPath,1) = "\" Then
 BuildFolderPath = p_strPath
 Else
 BuildFolderPath = p_strPath & "\"
 End If
End Function

An interesting thing to note is that I’ve passed folder paths through the ExpandEnvironmentStrings() function.  This converts any environment variables in the string and allows them to be converted to their full path.  If you want to know more about environment variables I’d recommend a few minutes browsing this page on environment variables in Windows XP.

So all you have to do is decide on a folder where you want the script run from and then pop these functions into your script.  Personally I like to create shortcuts for scripts too (e.g. in the user’s send to folder, start menu or the desktop).  I’ll be posting a little later on how to add that to your toolbox – particularly useful for installations like this.

Advertisements
h1

VBS – Duplicate Folder Structure

28/03/2009

Armed with my folder browser script I finally finished rounding out my script to clone / duplicate a folder structure.

On occasion I produce a folder/directory structure which I’d like to be able to reuse as a template – e.g. on a project.  The problem is that it already has lots of files in and I have to copy the whole thing and then use an ‘open’ search to list the files in the new structure so that they can be deleted, but if there are lots of big files this operation can be slow and the inefficiency of it grates against my programmer’s nature.

Thus came about the writing of a little bit of VBScript to copy an existing folder structure and effectively clone it to another area on a file system.  So here’s the script … I hope you find it useful.

Option Explicit

'Defintions
Const ForReading = 1
Const ForWriting = 2
Const MAKE_FILE = "BuildFolders.txt"

Dim objFSO, objFolder, objFile
Dim strSourceFolder, strDestinationFolder, strBuildFoldersFile
Dim intFolders

'Initialise and capture folder paths
intFolders = 0
Set objFSO = CreateObject("Scripting.FileSystemObject")
strSourceFolder = SelectFolder("Select a source folder")
strDestinationFolder = SelectFolder("Select a destination folder")
strBuildFoldersFile = strDestinationFolder & "\" & MAKE_FILE

'Read, store and then build the new folder struture
CreatestrDestinationFolder
CreateBuildFile
MakeFromBuildFile

'Finalise
DeleteBuildFile
MsgBox "Created " & intFolders & " folders", vbOKOnly & vbInformation, "Folder Generation complete"

'-------------
'SUB ROUTINES
'-------------

'Read the source folder structure and store it
Sub CreateBuildFile()
	objFSO.CreateTextFile(strBuildFoldersFile)
	Set objFile = objFSO.CreateTextFile(strBuildFoldersFile, True)
	ReadstrSourceFolders(strSourceFolder)
	objFile.Close
End Sub

'Create the destination folder - should exist from the selection ... but just in case
'I (or someone else) wants to parameterise this script later on...
Sub CreatestrDestinationFolder()
	If Not objFSO.FolderExists(strDestinationFolder) Then
		objFSO.CreateFolder(strDestinationFolder)
		intFolders = intFolders +1
	End If
End Sub

'Create the new folder structure
Sub MakeFromBuildFile()
	Set objFile = objFSO.OpenTextFile(strBuildFoldersFile, ForReading)
	Do While Not objFile.AtEndOfStream
		objFSO.CreateFolder(strDestinationFolder & objFile.ReadLine)
		intFolders = intFolders +1
	Loop
	objFile.Close
End Sub

'Remove the file that was holiding the data structure
Sub DeleteBuildFile()
	objFSO.DeleteFile(strBuildFoldersFile)
End Sub

'Write a folder structure to the build file
Sub ReadstrSourceFolders(p_strSource)
	Dim colSubFolders
	Dim objSubFolder

	Set objFolder = objFSO.GetFolder(p_strSource)
	Set colSubfolders = objFolder.Subfolders
	For Each objSubfolder in colSubfolders
		objFile.WriteLine(StripstrSourceFolder(objSubfolder.Path))
		ReadstrSourceFolders(objSubfolder.Path)
	Next
End Sub

'Remove the source folder path from a string (i.e. a sub folder's path)
Function StripstrSourceFolder(p_strFolder)
	StripstrSourceFolder = Right(p_strFolder,(Len(p_strFolder)-Len(strSourceFolder)))
End Function

'----------
'FUNCTIONS
'----------
'Select a folder
Function SelectFolder(pstrDialogLabel)
	'Select a folder
	Const BIF_returnonlyfsdirs   = &H0001
	Const BIF_editbox            = &H0010

	Dim objBrowseFolderDialog, objFolder, objFSO, objSelection
	Dim bBrowseForFolder

	Set objBrowseFolderDialog = WScript.CreateObject("Shell.Application")

	bBrowseForFolder = true

	While bBrowseForFolder
		Set objFolder = objBrowseFolderDialog.BrowseForFolder (&H0, pstrDialogLabel, BIF_editbox + BIF_returnonlyfsdirs)

		'Check that something has been returned
		If IsValidFolder(objFolder) Then
			Set objFSO = CreateObject("Scripting.FileSystemObject")

			Set objSelection = objFolder.Self
			If objFSO.FolderExists(objSelection.Path) Then
				'A valid folder has been selected
				SelectFolder = objSelection.Path
				bBrowseForFolder = false
			Else
				'The selection is not a valid folder, try again...
				MsgBox objFolder.Title & " is not a valid folder, please select another folder" _
					, vbOKOnly & vbExclamation, "Invalid Selection"
			End If
		Else
			'Nothing was selected, so return a null string
			SelectFolder = ""
			bBrowseForFolder = false
		End If
	Wend
End Function

Function IsValidFolder(pobjFolder)
	'Check that we have a valid value
	'i.e. you can concatenate it to a string
	Dim strTest

	On Error Resume Next

	strTest = " " & pobjFolder

	If Err  0 Then
		IsValidFolder = false
	Else
		IsValidFolder = true
	End If

	On Error GoTo 0
End Function
h1

VBScript – Select a Folder

28/03/2009

This post has now been migrated to ThoughtAsylum.com.

Follow this link to go directly to the article.

h1

AutoHotKey – Portable Drive Menu

21/02/2009

I’m a big fan of portable applications and particularly the work driven by PortableApps.com.  The PortableApps Menu has been undergoing a revamp for some time now to work towards a number of enhancements and in the mean time I’ve taken up using a branch of this known as Geek Menu.  Unfortunately they are all quite slow as I have a huge number of portable applications on my portable hard drive.

I usually have a small number of applications I use regularly and so I actually want a fast loading light menu for automatic loading and then I load Geek Menu from that should I need access to lots of applications or ones that I use infrequently.

I’ve created a basic little application menu using AutoHotKey that is driven from a simple INI file menu system (that you manually edit) to fill this need.  When run it sits in the system tray and when you click on it it displays a set of menu items (including sub menus) and executes the item associated with it using the path for it in the INI file.

The script for this is as follows:

; Copyright flagit.wordpress.com 2009
;;;;;;;;;;;;;
;MAIN SCRIPT;
;;;;;;;;;;;;;

;;;;;;;;;;;;
;Initialise;
;;;;;;;;;;;;
#Persistent
#SingleInstance

AppName = PUMAS
AppIni = %A_ScriptDir%pumas.ini
IconFile = %A_ScriptDir%pumas.ico

;;;;;;;;;;;;
;Build Menu;
;;;;;;;;;;;;
;Set-up Base Menu Settings
menu, tray, Icon, %IconFile%
menu, tray, tip, %AppName%
menu, tray, nostandard
menu, tray, add, %AppName%, Heading
menu, tray, Default, %AppName%
menu, tray, disable, %AppName%
menu, tray, click, 1
menu, tray, add ; separator

;Build the base menu
BuildMenu("Tray", AppIni)

;Exit app entry
menu, tray, add ; separator
menu, tray, add, Exit, ExitApplication
return

;;;;;;;;;;;
;FUNCTIONS;
;;;;;;;;;;;

;Recursive function to build the menu
BuildMenu(MenuSection, INIFile)
{
    ;Build menus from INI file
    Counter=1
    Loop
    {
        IniRead, MenuEntry, %INIFile%, %MenuSection%, Item%Counter%, *
        If (MenuEntry="*")
            Break
        IniRead, MenuEntryPath, %INIFile%, %MenuSection%, %MenuEntry%, *
        If (MenuEntryPath="*")
        {
            BuildMenu(MenuEntry, INIFile)
            menu, %MenuSection%, add, %MenuEntry%, :%MenuEntry%
        }
        Else
        {
            menu, %MenuSection%, add, %MenuEntry%, ExecuteItem
        }
        Counter += 1
    }
}

;;;;;;;;;;;;;;;;;
;MENU PROCESSING;
;;;;;;;;;;;;;;;;;

;The heading is disabled and simply allows the left click to produce
;the right click context menu
Heading:
menu, tray, show
return

;Open the specified path for the menu item generated from the INI file
ExecuteItem:
;Open the item specified for this in the ini file
IniRead, MenuEntryPath, %AppIni%, %A_ThisMenu%, %A_ThisMenuItem%
SplitPath, A_ScriptDir, , , , , ScriptDrive
Run, %ScriptDrive%%MenuEntryPath%
return

;Close the application
ExitApplication:
ExitApp

There are a few variables in the initialise section that specify the name of the application, the INI file that defines the menu (which is convenient to store in the same folder as the menu script/executable and the icon file for the application.

The INI file has a “Tray” section which defines the root content of the tray item’s context menu.  Each item is specified by “Item#” in numeric order.  Each item specified will either have an entry in the same section (making it an item for execution) or a section of the same name (indicating it is a sub-menu).

An example menu might look something like:


[Tray]
Item1=Utilities
Item2=Tools
Item3=Help

Help=Helppumas.chm

[Utilities]
Item1=Defrag
Item2=Format Floppy

Defrag=File HelpersUnfrag.exe
Format Floppy=batchformatFD.bat

[Tools]
Item1=Tools Guide
Item2=Mega Kit

Tools Guide=ToolsGuide.doc
Mega Kit=Toolsmegakit.exe

This produces a menu system with two sub menus (one for utilities and one for tools) each with two items and a help item on the root menu.  This configuration file specifies all paths in relation to the root of the drive it is stored on.  You can tailor this INI file to your needs and even the menu system itself by modifying the script to what you want – just recompile it with the AutoHotKey compilation tool and you’ve got it.

h1

NoteTab Clip – Day of Week

19/02/2009

I seem to be doing rather a lot with NoteTab at the moment and again I thought I’d share something useful I scripted.

NoteTab has limited date related functionality but I needed to get the day of the week from a date.  I chose the Doomsday algorithm to base my clip on and the clips below simply generate an integer that represents the day of the week and then translate that into text.  The test clip simply takes a date from the user (defaults to today’s date) and then returns a prompt box informing the user what the day of the week is.

H=”Day of Week Test”
^!Prompt ^$DayOfWeekLong(^$DayOfWeekInt(^?{(M=”00/00/0000″)Enter date=^$GetDate(dd/mm/yyyy)$})$)$

H=”_DayOfWeekInt”
;Sunday=0/Monday=1/Tuesday=2/Wednesday=3/Thursday=4/Friday=5/Saturday=6
^!SetListDelimiter /
^!SetArray %TheDate%=^&
^!Set %Day%=^%TheDate1%
^!Set %Month%=^$Calc(((^%TheDate2%+9) MOD 12)+1)$
^!Set %YearFull%=^%TheDate3%
;If we are in January or February (month = 11 or 12), reduce the year by 1
^!If ^%Month%<10 SKIP ^!Dec %YearFull% ;Extract the year components ^!Set %YearShort%=^$StrCopyRight("^%YearFull%";2)$ ^!Set %Century%=^$StrCopyLeft("^%YearFull%";2)$ :Calculate ^!Result ^$Calc((^%Day% + FLOOR(2.6*^%Month% - 0.2) - 2*^%Century% + ^%YearShort% + FLOOR(^%YearShort%/4) + FLOOR(^%Century%/4)) MOD 7)$ H="_DayOfWeekLong" ^!SetListDelimiter / ^!SetArray %Days%=Sunday/Monday/Tuesday/Wednesday/Thursday/Friday/Saturday ^!Set %Key%=^$Calc(^$DayOfWeekInt(^%TheDate%)$+1)$ ^!Result ^%Days^%Key%% [/sourcecode]

h1

NoteTab – Calculating Text

17/02/2009

In my latest foray into NoteTab arithmetic I came across a curious fact around performing arithmetic calculations on a variable containing text.  Whilst in purely mathematical terms an arithmetic operation on something that has a numeric value is a bit nonsensical, but NoteTab is a text editor…

H=”Inc Text”
^!Set %Val%=FooBar
^!Prompt >^%Val%^%Val%< [/sourcecode] This clip displays two prompts when run.  The first prompt as one might expect displays ">FooBar<".  The second prompt however displays ">1<"! Apparently any text is evaluated to zero when you try and perform a purely arithmetic operation on it.  Which broadly speaking makes sense if you just think about a piece of text as having no arithmetic value (or "zero"). So I came across this interesting point when I was taking a masked numeric input from a clip wizard.  Take a look at the following clip. Running this clip and accepting the default input of two you mjight expect should give you a final prompt of  "3", except you've spotted where I'm going with this and you know that it's probably going to be something to do with adding numbers to text and so turn out to be "1" instead.  Well ... you're absolutely right. The result is 1 as the input mask (even though it is using optional numeric place holders after the first compulsory numeric place holder) simply pads the input value with spaces. i.e. The calculation should be of the form: 2+1=3 But we actually get: "2  "+1=0+1=1 The answer is therefore quite simple - remove the spaces... So the astute amongst you will have noticed that we are now trimming the input to remove spaces which should now nicely produce the desired result when we use the default of 2 giving us a final result of 3. Errr.... except we still get a result of 1. It seems that there's either a bit of a bug or an evaluation order at work here that means we don't actually end up trimming off the spaces.  It seems we need an extra assignment step.  The following clip does produce the desired result of 3. So the moral of the story is ensure that your variables are purely numeric before you start performing arithmetic operations on them and make sure that if you need to trim user input for a numeric you assign it to a variable first before trying to trim.

h1

NoteTab Numeric Accuracy

15/02/2009

I’ve been working on something to help me with some logging activity in NoteTab and I’ll be posting a variety of useful bits and pieces from this in both this micro blog (FlagIT) and my main blog (RebootIT).  As part of this I was looking at producing some date related clips which necessitated looking at some mathematical clips and notably one to determine if a number is an Integer.  NoteTab is a text editor and numeric manipulations are definitely not its forte, so I needed to produce a clip function to do this.

Integers are whole numbers.  They have no decimal part.  Fortunately the ^$Calc()$ function does expose a few mathematical functions to a clipper such as myself and one of these is INT.

As an aside at this point the range of mathematical functions available is quite deeply hidden.  The clip help file does provide one link back into the main NoteTab help file where there is a section called “Calculate in NoteTab” which I’d recommend taking a look at.  To use the functions in there you’ll need to understand the function and the number of operands.  INT has one operand (or parameter) that is passed to it,  and this is illustrated below.

So if we take a number and evaluate it to X decimal places, then subtract its integer part (i.e. anything to the left of the decimal point), we are left with the decimal part.  This remainder will be zero if the original number was an integer and non-zero if the original number was a non-integer.

e.g. (all to 3 decimal places)

  • 3.000 – 3.000 = 0.000 => Integer
  • 3.123 – 3.000 = 0.123 => Non-Integer
  • (-3.123)-(-3.000) = -0.123 => Non-Integer

To ensure that no matter what number was being passed, I had to be sure to evaluate to an appropriate number of decimal places otherwise I could get into trouble…

e.g. (all to 3 decimal places)

  • 3.0001 would give 3.000 – 3.000 = 0.000 => Integer … which is wrong.

Therefore if the level of accuracy is equal to the length of the number of characters being passed in I’m always assured of the correct level of accuracy.

e.g.

3 would use an accuracy of 1 decimal place

  • 3.0 – 3.0 = 0.0 => Integer

3.123 would use an accuracy of 5 decimal places

  • 3.12300 – 3.00000 = 0.12300 => Non-Integer

So the clip function for IsInteger becomes the following:

H="IsInteger"
^!Set %DPofAccuracy%=^$StrSize("^&")$
^!Result=True
^!If ^$Calc(INT(^&);^%DPofAccuracy%)$=^$Calc(^&;^%DPofAccuracy%)$ END
^!Result=False

This however got me to thinking.  Every programming language using numeric variables specifies a level of accuracy in decimal places, significant figures, etc. based upon the memory allocation internally managed for the variable.  I couldn’t find anything stating what this was in NoteTab (which isn’t to say it doesn’t exist) and so I thought I’d write a quick clip to work it out.

H="Numeric Variable Accuracy"
^!ClearVariables
^!Set %Accuracy%=0
:LOOP
^!If ^%Accuracy%=0 SKIP
^!Set %CurrentValue%=1.^$StrFill("0";^$Calc(^%Accuracy%-1)$)$1
^!Set %Output%=^%Output^%NL%(^%Accuracy%) ^%CurrentValue% : ^$Calc(^%CurrentValue%;^%Accuracy%)$
^!Inc %Accuracy%
^!If ^%Accuracy%=20 FINISH
^!Goto LOOP
:FINISH
^!Info ^%Output%

The clip simply builds an ever smaller decimal part and then displays it to a specified number of decimal places.  I set it to display the results after 20 iterations from a starting point of no decimal places and these are the results (structured here in a nice table format for easier reading off the page).

Decimal Places Value Evaluation
0 1 1
1 1.1 1.1
2 1.01 1.01
3 1.001 1.001
4 1.0001 1.0001
5 1.00001 1.00001
6 1.000001 1.000001
7 1.0000001 1.0000001
8 1.00000001 1.00000001
9 1.000000001 1.000000001
10 1.0000000001 1.0000000001
11 1.00000000001 1.00000000001
12 1.000000000001 1.000000000001
13 1.0000000000001 1.0000000000001
14 1.00000000000001 1.00000000000001
15 1.000000000000001 1.000000000000001
16 1.0000000000000001 1.0000000000000000
17 1.00000000000000001 1.00000000000000000
18 1.000000000000000001 1.000000000000000000
19 1.0000000000000000001 1.0000000000000000000

As you can see at 16 decimal places there is a difference between the original value and the evaluated value. Therefore the IsInteger function (as well as any other mathematical calculations using NoteTab directly) will be subject to a 15 decimal places of accuracy limit.

Now this means that we need to ensure that the maximum number of decimal places used is also limited for the IsInteger function as if we then try to use a number with a total character length that exceeds 15 we start to get unpredictable results.

For example during the following evaluation^$IsInteger(10203040506070.1)$ the function actually ends up evaluating the following equality in the ^!If statement (line 4 of the clip).

10203040506070.0000000000000000=10203040506070.0996100000000000

Whilst it happens to give the right result there is scope here for failure.

Therefore we need to amend the clip to only allow up to 15 decimal places of accuracy.

H="IsInteger"
^!Set %DPofAccuracy%=^$Calc(MIN(^$StrSize("^&")$;15))$
^!Result=True
^!If ^$Calc(INT(^&);^%DPofAccuracy%)$=^$Calc(^&;^%DPofAccuracy%)$ END
^!Result=False