TaskDaddy: a script and executable to add tasks via command line or GUI to Outlook, in AutoIt3

As I said when I wrote it, “TaskDaddy takes a single line of input from you and turns it into an Outlook task without having to open the full interface of Outlook. It uses a simple syntax to input tasks quickly.” The experience was very similar to writing in Outlook VBA (you are referencing the same Office object model).

TaskDaddy has a home page which includes help, screenshots, and download links. TaskDaddy was a NANY 2011 Challenge Participant on DonationCoder.

The source code is at the bottom of the first post in this announcement thread. Look for the file TaskDaddySource.2010.09.19.zip. The zip contains five source code files.


I fixed the Calibre recipe for scraping the Friday Times in Python

The Calibre recipe for scraping the Friday Times to produce an ebook had become out of date due to web site changes. I submitted this fix and the Calibre team (Kovid Goyal is the lead developer) were able to update the official build and they were kind enough to give me co-author credit in the recipe comments.

(On the Calibre forums and in the credit, my user name is ireadtheinternet.)

The corrected recipe follows.

from calibre.web.feeds.news import BasicNewsRecipe

class TheFridayTimes(BasicNewsRecipe):
    language       = 'en_PK'
    encoding       = 'utf8'
    version        = 1.1
    title          = u'The Friday Times'
    category       = u'news, Pakistan'
    description    = u"Pakistan's First Independent Weekly Paper"

    no_stylesheets            = True
    no_javascript             = True
    ignore_duplicate_articles = {'url'}

    keep_only_tags = [
        dict(name='div', attrs={'class':'sidebar_content'}),
        dict(name='div', attrs={'class':'comment_inner'})  

    remove_tags = [
        dict(name='p', attrs={'class':'no-break'}),
        dict(name='div', attrs={'class':'related_posts'}),
        dict(name='div', attrs={'id':'respond'})

    def parse_index(self):      
        toc_page = self.index_to_soup('http://www.thefridaytimes.com/tft/') 
        toc = toc_page.find('div', attrs={'class':'sidebar_left_home_wrapper'})

        articles = []
        for story in toc.findAll('a'):
            # skip the links with an image, they are repeated further down
            if story.find('img') is not None:
            url = story['href']
            # If no title, use url as title
            title = story.get('title', url)
            self.log('Found article:', story)
            self.log('\t', url)
            articles.append({'title':title, 'url':url, 'date':'','description':''})

        return [('Current Issue', articles)]

Data conversion example in Perl

This is a one-off to convert Dinesh Prabhu’s Romanized Urdu-English dictionary into comma separated value format (.CSV) to be opened in Microsoft Excel and then pasted into the Microsoft Excel workbook included with Günther’s Vocabulary Cards for Palm.

open (INPUT, "< $ARGV[0]") || die "Can't open file $ARGV[0].\n";

while () {

	if ($_ =~ /([^\:]+)\:\s*([PATHSG])\s+(n.f.|n.m.|pron.|adj.|v.|adv.|intj.|prep.|suff.|pref.)\s+(.*)/)
		($urdu, $origin, $part, $english) = ($1, $2, $3, $4);
		print "\"$urdu \[$part\] \[$origin\]\",\"\",\"\",\"\",\"$english\"\n";


Batch files to convert mp3 podcasts to space-saving spx files

If you Google for something to convert mp3 to speex format, you won’t find much. I couldn’t find a method on the internet to automatically and easily convert them, so maybe my batch files below are a first of sorts. It has worked for me with Windows 2000 and Windows Vista, however it does have some issues with certain characters in podcast names, such as ®, which I can fix if there is any interest.

Why would you want to convert one lossy audio format to another lossy audio format, with the resulting noticeable loss of quality that results? Because podcasts aren’t really available in .wav format, and .spx format is more efficient than .mp3 for the spoken word and saves a little space on my Palm Tungsten T3’s SD card.

If you want to get this working yourself, you’ll need the Juice podcast receiver, ffmpeg Windows binaries and speexenc (look for the speexw installer):
I will assume you extract ffmpeg to C:\TOOLS, and install speex to the default C:\SPEEXW, and that you copy and paste the batch files into Notepad and save to C:\BAT. Make sure that you append ;C:\TOOLS;C:\SPEEXW;C:\BAT to your existing PATH environment variable. You will also have to make sure the folders C:\PODCASTS and C:\PDA2GO exist.

Look for ipodder.cfg. On Vista, it should be in C:\Users\YOUR LOGIN NAME\AppData\Roaming\iPodder. Change the download directory to C:\\PODCASTS (double backslashes intentional) which is used in the batch files below. (In Windows 2000 or XP, you can also change this in the GUI, but in Vista you can’t even open the GUI until you change it because the line refers to My Documents which in Vista has changed to simply Documents.)

From Juice, click File > Preferences, go to the Advanced tab, and check the box which says “Run this command after each download” and type the following in the text box below it:
podwork "%f" "c:\pda2go"

If you are running Windows Vista, you will need to go to properties of Juice.exe and set it to run in Windows XP SP2 compatibility mode (this is a requirement of being able to run Juice 2.2 on Vista, not a requirement of podwork.cmd):

The podwork.cmd script below converts a single file from mp3 to speex format. You will want to experiment with the quality variable (10 is the maximum, and at 7 the lesser quality is noticeable).


@echo off
rem podwork.cmd
rem thanks to 1CMDFAQ.TXT by Prof. Timo Salmi for
rem bringing my DOS skills into the 21st Century :)
rem See http://www.netikka.net/tsneti/info/tscmd.htm
setlocal enableextensions
set quality=7

rem set pod to your source folder (no trailing slash)
set pod=c:\podcasts

if [%2]==[] goto :EOF
if [%tmp%]==[] goto :EOF

set ptmp=%tmp%\
set ptmp=%ptmp:\\=\%
set ptmp=%ptmp:~0,-1%

if not exist "%ptmp%\*.*" goto :EOF

set pfol=%~dp1
set pfol=%pfol%\
set pfol=%pfol:\\=\%
set pfol=%pfol:~0,-1%
for %%a in ("%pfol%") do set pfol=%%~nxa

set tful=%~2
set tful=%tful%\
set tful=%tful:~0,-1%

if not exist "%tful%\*.*" goto :EOF
for %%a in ("%tful%") do set tfol=%%~nxa

rem Make necessary tree structures
xcopy "%pod%" "%ptmp%\podwork\" /t
xcopy "%pod%" "%tful%" /t

rem Skip if Speex file already exists
if exist "%tful%\%pfol%\%~n1.spx" goto :EOF

@echo on
ffmpeg -i "%~1" "%ptmp%\podwork\%pfol%\%~n1.wav"
if not exist "%ptmp%\podwork\%pfol%\%~n1.wav" goto :EOF
speexenc --quality %quality% "%ptmp%\podwork\%pfol%\%~n1.wav" "%tful%\%pfol%\%~n1.spx"
@echo off

IMPORTANT NOTE: WordPress.com gets confused by double hyphens: Line 44 should start off “speexenc –quality” not “speexenc —quality” or “speexenc -quality”.

That works for new podcast downloads, but since I had downloaded some podcasts already, I wrote a quick podsweep.cmd script to run podwork.cmd for each pre-existing mp3 file. Make sure Juice is closed before running it. It is hard-coded for what I use as my Juice top-level download folder and my output folder:


cd \podcasts
sweep.cmd for %%f in (*.mp3) do podwork "%%~ff" "c:\pda2go"

podsweep.cmd depends on sweep.cmd to run the command in every directory. With sweep.cmd, I mimic an ancient MS-DOS tool from the 80’s called SWEEP.COM:


@echo off
for /R %%D in (.) do (
  cd "%%D"
cd /d %cd%

Thanks to Chris Pirillo for the information on getting Juice to work in Windows Vista.

Drivercab Helper (AutoIT3 example)

This is a utility I wrote that works in conjunction with HFSLIP to slipstream additional drivers into a Windows 2000 or XP install. My announcement thread on MSFN has instructions and a screen shot. It is written in AutoIT3, which is similar to BASIC. The GUI work was done in Koda form designer.

#Region ;**** Directives created by AutoIt3Wrapper_GUI ****
#EndRegion ;**** Directives created by AutoIt3Wrapper_GUI ****
; Drivercab Helper
; Version 1.2
; by David Eason

; Thanks to (in no order)
; TommyP and Tomcat76 for HFSLIP
; Tim Fehlman for sample code described in "Folder Recursion in Autoit" at http://dailycupoftech.com/folder-recursion-in-autoit/
; Lookfar, LazyCat, and JosBe for Koda Form Designer for AutoIt
; Jon Bennett for Autoit3 and adding Unicode support for version v3.2.4.0

; Requires Autoit v3.2.4.0 build to compile due to Unicode support

; History
; 1.1 First  version ;)
; 1.2 Added support for Unicode .INF files


If @AutoItVersion < "" Then
	MsgBox(0, "Version Check", "This AutoIt script requires v. or later")
	Exit 1

If @Unicode <> 1 Then
	MsgBox(0, "Unicode Check", "This AutoIt script requires the Unicode build")
	Exit 2

; Globals
Dim $SourceFolder, $DestFolder, $StopProcessingFiles, $dir

#Region ### START Koda GUI section ### Form=d:\drivercabhelper\aform1.kxf
$Form1_1 = GUICreate("Drivercab Helper 1.1 for HFSLIP", 591, 507, 229, 78)
$Group1 = GUICtrlCreateGroup("Log", 13, 189, 560, 299)
GUICtrlSetData(-1, "")
GUICtrlCreateGroup("", -99, -99, 1, 1)
$Group2 = GUICtrlCreateGroup("&Folders", 13, 20, 560, 117)
$Input1 = GUICtrlCreateInput("", 137, 40, 361, 21)
GUICtrlSetTip(-1, "Specify the folder containing existing INF files and driver files")
$Button1 = GUICtrlCreateButton("...", 512, 40, 36, 20, 0)
$Label1 = GUICtrlCreateLabel("&Source folder:", 33, 46, 76, 17)
$Input2 = GUICtrlCreateInput("", 137, 85, 361, 21)
GUICtrlSetTip(-1, "Specify the folder to be deleted and populated with modified INF files and existing driver files")
$Button2 = GUICtrlCreateButton("...", 512, 85, 36, 20, 0)
$Label2 = GUICtrlCreateLabel("&Destination folder:", 33, 85, 95, 17)
$Label3 = GUICtrlCreateLabel("Destination folder will be deleted and overwritten", 137, 111, 282, 17)
GUICtrlSetFont(-1, 8, 800, 0, "MS Sans Serif")
GUICtrlCreateGroup("", -99, -99, 1, 1)
$Button3 = GUICtrlCreateButton("Delete and &regenerate destination folder now!", 253, 150, 297, 20, 0)
$Label4 = GUICtrlCreateLabel("&ISOTITLE", 32, 152, 58, 17)
$Input3 = GUICtrlCreateInput("HFSLIPCD", 88, 152, 121, 21)
#EndRegion ### END Koda GUI section ###

If $CmdLine[0] > 0 Then
	GUICtrlSetData($Input1, $CmdLine[1])

If $CmdLine[0] > 1 Then
	GUICtrlSetData($Input2, $CmdLine[2])

While 1
	$nMsg = GUIGetMsg()
	Switch $nMsg
		Case $Button1
			$dir = FileSelectFolder("Source folder", "", 6)
			If @error = 0 Then
				GUICtrlSetData($Input1, $dir)
		Case $Button2
			$dir = FileSelectFolder("Destination folder (will be overwritten)", "", 7)
			If @error = 0 Then
				GUICtrlSetData($Input2, $dir)
		Case $Button3
			If StringStripWS(GUICtrlRead($Input3), 1) = "" Then
				GUICtrlSetData($Input3, "HFSLIPCD")
			$IsoTitle = GUICtrlRead($Input3)
			$CabLine = '1 = "' & $IsoTitle & '",driver.cab,,"\I386"'
			$SourceFolder = GUICtrlRead($Input1)
			$DestFolder = GUICtrlRead($Input2)
			If ValidateFolders($SourceFolder, $DestFolder) Then
				$StopProcessingFiles = False
				DirRemove($DestFolder, 1)
				ScanFolder($SourceFolder, $DestFolder)
				If Not $StopProcessingFiles Then
					LogToWindow('Completed reading "' & $SourceFolder & '" and writing "' & $DestFolder & '".')
					MsgBox(262144, "Drivercab Helper", "Drivercab Helper has finished.")

Func ValidateFolders($SourceFolder, $DestFolder)

	If StringRight($SourceFolder, 1) = "\" Or StringRight($SourceFolder, 1) = "/" Then
		$SourceFolder = StringLeft($SourceFolder, StringLen($SourceFolder) - 1)
		GUICtrlSetData($Input1, $SourceFolder)

	If StringRight($DestFolder, 1) = "\" Or StringRight($SourceFolder, 1) = "/" Then
		$SourceFolder = StringLeft($DestFolder, StringLen($DestFolder) - 1)
		GUICtrlSetData($Input2, $DestFolder)

	If $SourceFolder = "" Then
		ShowError('Please specify a source folder.')
		Return False

	If _PathFull($SourceFolder) <> $SourceFolder Then
		ShowError('Source folder "' & $SourceFolder & '" cannot be a relative path.')
		Return False

	; Source folder must exist
	If DirGetSize($SourceFolder) = -1 Then
		ShowError('Source folder "' & $SourceFolder & '"does not exist.')
		Return False

	If $DestFolder = "" Then
		ShowError('Please specify a destination folder.')
		Return False

	If _PathFull($DestFolder) <> $DestFolder Then
		ShowError('Destination folder "' & $DestFolder & '" cannot be a relative path.')
		Return False

	If DirGetSize($DestFolder) <> -1 Then
		LogToWindow('Destination folder "' & $DestFolder & '"exists and will be overwritten.')
		; not an error

	If StringInStr($SourceFolder, $DestFolder) > 0 Then
		ShowError('Destination folder "' & $DestFolder & '" must not contain source folder "' & $SourceFolder & '".')
		Return False

	Return True
EndFunc   ;==>ValidateFolders

Func LogToWindow($s)
	GUICtrlSetData($Edit1, $s & @CRLF, 1)
EndFunc   ;==>LogToWindow

Func ModifyInfFile($SourceFileName, $DestFileName)

	Local $line, $comment, $in_sourcedisksfiles, $section_name_this_line
	Local $comment_pos, $equals_pos, $left_bracket_pos, $right_bracket_pos
	Local $BOMCheck

	$BOMCheck = _BOMCheck($SourceFileName)

	If $BOMCheck = -1 Then
		ShowError('Unable to determine if "' & $SourceFileName & '" is ANSI or Unicode.')
		Return False

	$SourceInfFile = FileOpen($SourceFileName, 0) ;Read

	If $SourceInfFile = -1 Then
		ShowError('Unable to open file "' & $SourceFileName & '" for reading.')
		Return False

	$DestInfFile = FileOpen($DestFileName, 10 + $BOMCheck)  ; Erase + Write + Unicode Mode

	If $DestInfFile = -1 Then
		ShowError('Unable to open file "' & $DestFileName & '" for writing.')
		Return False

	LogToWindow('Reading ' & '"' & $SourceFileName & '",' & @CRLF & 'Writing "' & $DestFileName & '".')

	$in_sourcedisksfiles = False
	$line_num = 0
	While 1
		$line = FileReadLine($SourceInfFile)
		If @error = -1 Then ExitLoop
		$line_num = $line_num + 1
		$comment_pos = StringInStr($line, ";")
		If $comment_pos > 0 Then
			$comment = StringMid($line, $comment_pos, StringLen($line))
			$line = StringLeft($line, $comment_pos - 1)
			$comment = ""

		StringStripWS($line, 8)
		$left_bracket_pos = StringInStr($line, "[")
		If $left_bracket_pos > 0 Then
			$right_bracket_pos = StringInStr($line, "]")
			$section_name_this_line = StringMid($line, $left_bracket_pos, $right_bracket_pos)
			$section_name = $section_name_this_line;
			Switch StringUpper($section_name_this_line)
					FileWriteLine($DestInfFile, $line & $comment)
					FileWriteLine($DestInfFile, $CabLine)
					LogToWindow($SourceFileName & '(' & $line_num & '): Inserted "' & $CabLine & '" in section "' & $section_name & '".')
					$in_sourcedisksfiles = False
					FileWriteLine($DestInfFile, $line & $comment)
					$in_sourcedisksfiles = True
				Case Else
					;Still a section header
					FileWriteLine($DestInfFile, $line & $comment)
					$in_sourcedisksfiles = False
			;Not a section header
			If $in_sourcedisksfiles = True Then
				$equals_pos = StringInStr($line, "=", 0, -1)
				If $equals_pos > 0 Then
					$old_line = $line
					$line = StringLeft($line, $equals_pos) & "1"
					LogToWindow($SourceFileName & '(' & $line_num & '):  Changed "' & $old_line & '" to "' & $line & '" in section "' & $section_name & '".')
			FileWriteLine($DestInfFile, $line & $comment)
		$section_name_this_line = "";

	Return True
EndFunc   ;==>ModifyInfFile

Func ScanFolder($SourceFolder, $DestFolder)

	If $StopProcessingFiles Then

	Local $Search
	Local $File
	Local $FileAttributes
	Local $SourceFullFilePath
	Local $DestFullFilePath

	$Search = FileFindFirstFile($SourceFolder & "\*.*")

	While Not $StopProcessingFiles
		If $Search = -1 Then

		$File = FileFindNextFile($Search)
		If @error Then ExitLoop

		$SourceFullFilePath = $SourceFolder & "\" & $File
		$DestFullFilePath = $DestFolder & "\" & $File
		$FileAttributes = FileGetAttrib($SourceFullFilePath)

		If StringInStr($FileAttributes, "D") Then
			ScanFolder($SourceFullFilePath, $DestFullFilePath)
			LogFile($SourceFullFilePath, $DestFullFilePath)


EndFunc   ;==>ScanFolder

Func LogFile($SourceFileName, $DestFileName)
	If StringLen($SourceFileName) >= 4 And StringUpper(StringRight($SourceFileName, 4)) = ".INF" Then
		If Not ModifyInfFile($SourceFileName, $DestFileName) Then
			$StopProcessingFiles = True
		$FCStatus = FileCopy($SourceFileName, $DestFileName, 8)
		If $FCStatus = 1 Then
			LogToWindow('Copied "' & $SourceFileName & '" to "' & $DestFileName & '".')
			ShowError('Unable to copy "' & $SourceFileName & '" to "' & $DestFileName & '".')
			$StopProcessingFiles = True
EndFunc   ;==>LogFile

Func ShowError($ErrStr)
	MsgBox(16, "Error", $ErrStr)
EndFunc   ;==>ShowError

; Function Name:   _BOMCheck()
; Description:     Determines whether a given file is ANSI,
;                  UTF-16 Little Endian, UTF-16 Big Endian, or UTF-8
; Syntax:          _BOMCheck ( $filename )
; Parameter(s):    $filename   = The file to check

; Requirement(s):  Must be Unicode build of AutoIt v3.2.4.0 or later.
; Return Value(s): ANSI or Unsupported:    Returns 0
;                  UTF-16 Little Endian:   Returns 32
;                  UTF-16 Big Endian:      Returns 64
;                  UTF-8:                  Returns 128
;                  Problem opening file:   Returns -1
;                  The general idea with the return value is that it can
;                  be used in the calculation of the FileOpen mode

; Author:          David Eason

; Sample usage:
; While 1
;     Local $file = FileOpenDialog("Choose file", @DesktopDir & "\", "Text files (*.txt;*.inf)", 3)
;     if @error = 1 Then ExitLoop
;     MsgBox(0,"", _BOMCheck($file))
; Wend

Func _BOMCheck(ByRef $filename)

	; Supported Byte Order Markers for non-ANSI
	Local $BOMS[3]
	$BOMS[0] = Binary("0xFFFE")     ;UTF-16 Little Endian
	$BOMS[1] = Binary("0xFEFF")     ;UTF-16 Big Endian
	$BOMS[2] = Binary("0xEFBBBF")   ;UTF-8

	; Corresponding mode bit for FileOpen
	Local $FileModes[3]
	$FileModes[0] = 32
	$FileModes[1] = 64
	$FileModes[2] = 128

	Local $FH = FileOpen($filename, 4)

	If $FH = -1 Then
		Return -1
	Local $FirstBytes = FileRead($FH, 3)

	If @error = -1 Then
		Return -1

	Local $I
	For $I = 0 To 2
		If BinaryMid($FirstBytes, 1, BinaryLen($BOMS[$I])) = BinaryMid($BOMS[$I], 1, BinaryLen($BOMS[$I])) Then
			Return $FileModes[$I]

	; If still here, then it is presumed ANSI
	Return 0

EndFunc   ;==>_BOMCheck