Here’s a quick tip that I recently ran across. Maybe it’s old news but I haven’t seen it before.

Sometimes you want to pause or sleep a few seconds in a command/batch script. By default Windows doesn’t have any form of a “sleep” command installed that you can use in script files. Sure there’s versions of sleep.exe or similar programs that you can install but there’s another utility that’s already installed by default that you can use*. It’s called “choice” and it’s normally used to prompt a user to make a choice between several options. The trick is, it has a default choice timeout that can be set in seconds.

So here’s how to use choice.exe to pause or sleep for 10 seconds in a command script or batch file:

choice /T:10 /D N /N > Nul

Here we’re telling choice.exe to select the default choice, set by /D, in 10 seconds as set by the /T option. The /N and redirection to Nul just keeps the console output clean. I’ve tried with with fairly large timeouts and it seems to work for me. The upper limit appears to be 9999 seconds, which is roughly 2.75 hours.

btw, another common way that I’ve seen used to pause or sleep in a batch file is to use “ping” to repeatedly ping an IP with a timeout value but this method seems much cleaner to me.

* Note: Unfortunately, choice.exe isn’t available by default on Windows XP but it is on Windows Server 2003, Vista, and presumably all later versions of Windows.


I use iTunes on Windows only because I have to. I don't really care for it as a music organizer and besides, I rarely listen to music on my computer. I have an iPod though and what I do like about iTunes is that I can just plug in my iPod and it will automatically sync everything without any intervention from me. I also like that iTunes allows me to subscribe to podcasts. It annoys me though that unless I have iTunes running it will not update my podcast subscriptions. Since I only use iTunes for syncing my iPod, iTunes is never running.

Luckily iTunes has an COM automation interface that you can use to force it to update your podcasts. With a bit of PowerShell scripting I am now able to automatically update all of my podcasts in the middle of the night when I don't have to be bothered with the fact that iTunes is running. I used PowerShell for this scripting task as again, it seems perfect for a task like this.

My script is a little more complicated than just starting up iTunes and then starting the podcast downloads. Since I am scheduling this process to run nightly I also wanted to be able to close iTunes once it was finished. Unfortunately the iTunes COM automation interface provides no way to tell whether or not iTunes is busy downloading new podcasts. However it seems that iTunes creates a pretty predictable temporary download folder structure, which it then removes when it has finished downloading all of the podcasts that it updates. I used this bit of knowledge in my script to detect when iTunes was busy downloading new podcasts. I just watch for those directories, waiting for iTunes to finish and then I shut down iTunes. So far this has worked pretty well although I'm sure it is not complete robust under all circumstances.

Here is the PowerShell script I developed. If you want to use it you'll have to tweak the directory path that is checked in the script to match your computer. I should also mention that on at least one of my machines iTunes creates the temporary 'downloads\podcast' directory in a different place under the iTunes music folder. I have not found a setting in iTunes that determines where the temporary folder gets created so you may have to poke around a little while iTunes is downloading podcast updates to figure out where it is on your machine if this does not work.

 

# update iTunes pod casts

function test-Downloading
{
    if(test-path 'C:\Documents and Settings\MusicBox\My Documents\My Music\iTunes\iTunes Music\Downloads\Podcasts')
    {
        return $True
    }
    return $False
}


$iTunes = new-Object -comobject iTunes.Application
if($iTunes -ne $null)
{
    'iTunes started' | out-Host

    # start iTunes podcasts update
    'updating podcasts' | out-Host
    $iTunes.UpdatePodcastFeeds()

    # set a time out time
    $TimeOut = (get-Date).AddMinutes(30)

    # wait a few minutes and check if it is downloading
    'waiting for downloads to start...' | out-Host
    start-sleep (30)

    # loop while iTunes seems to be downloading (presence of temporary download folder)
    'checking for download activity' | out-Host
    while(test-Downloading -and ((get-Date) -lt $TimeOut))
    {
        # give it some more time
        'download activity detected, waiting...' | out-Host
        start-sleep (60)
    }

    if((get-Date) -ge $TimeOut)
    {
        'downloading timed-out' | out-Host
    }

    # now quit iTunes after a little settling time
    start-Sleep (30)
    'quitting iTunes' | out-Host
    $iTunes.Quit()
    $iTunes = $null
}

I'm certainly not a PowerShell expert but I have been finding my way around it lately. Once I got a few interesting scripts put together and saved to a file the next thing I ran up against was how do I launch these without loading them into the interactive environment by hand? By default when you install PowerShell it associates script files (.ps1 files) with notepad. Great for editing, not so great if you want to execute them. My guess is that after the 'Monad/PowerShell virus' story a year back or so that Microsoft got a little too freaked out to just let these things launch when clicked on. Unfortunately this decision also makes it more difficult to schedule a PowerShell script in the task scheduler too. To top it all off it's not quite as simple as just passing your script as a command line argument to PowerShell either. There are two separate steps necessary to enable you to launch PowerShell scripts from the Windows shell.

First you have to set up a file association in the Windows shell to change the default behavior for .ps1 files. However the PowerShell command line doesn't know what to do with a script file. By default it simple takes statements to execute. We can leverage this to our advantage though and construct a statement to cause our script file to be loaded and executed. That statement looks something like this:

powershell -command "& 'MyScript.psa1' "

If you update the file association with this you can then launch script files from the command line or by clicking on them. You can use the Tools | Folder Options dialog to do this but why not use PowerShell instead? Here are two simple lines of code to update your system registry to tell it how to execute .ps1 script files.

Note: This of course updates your system registry so you should back things up first.

new-item Registry::hkey_classes_root\microsoft.powershellscript.1\shell

new-item Registry::hkey_classes_root\microsoft.powershellscript.1\shell\open

new-item Registry::hkey_classes_root\microsoft.powershellscript.1\shell\open
\command -value ('"' + $PSHOME + '\powershell.exe" -command "& ''%1''"')

If you run these two PowerShell lines and then try and click on a .ps1 script file you'll see that we're not quite there yet. PowerShell has an execution policy that by default is set to "Restricted." In restricted mode no scripts are allowed to execute, only interactive commands may be executed. By using the PowerShell "set-executionpolicy" cmdlet you can change it to something more sensible like RemoteSigned which allows all locally generated scripts to execute but will only allow downloaded scripts to execute if they have been signed by a trusted source. In PowerShell you can execute the "help  about_signing" command for more info on this.

Once these two steps are done you can now launch PowerShell scripts directly without having to start up the interactive environment first. In addition you can also use this method to schedule PowerShell scripts in the task scheduler.


Many larger applications having scripting capabilities built in to allow you to automate repetitive tasks but what do you do if you need to script a repetitive task for an application that doesn't have a scripting or macro language built in?  You could use VBScript and the SendKeys function. This works for simple things but it isn't always robust enough. What if you need to do something more complex, for instance: launch an application, wait for the main window to open, resize the window, send some keystrokes, and then click on a specific UI element? Unfortunately VBScript and its set of built-in functions don't always cut it when you need to move beyond SendKeys.

When your needs go beyond what VBScript can do natively you might want to take a look at AutoIt. AutoIt is a small suite of tools, the main component being a stand-alone scripting language. What sets AutoIt apart from other scripting languages is that it is targeted at GUI application scripting and its built-in set of functions enable the finer level of control lacking in VBScript and other scripting languages. The AutoIt scripting language is very VB-like and easy to pick up if you're already familiar with VB. AutoIt has a rich feature set including (taken from the AutoIt web site):

  • Provide a general-purpose scripting language for all Windows versions
  • Simulate keystrokes (supports most keyboard layouts)
  • Simulate mouse movements and clicks
  • Move, resize and manipulate windows
  • Interact directly with "controls" on a window (set/get text from edit controls, check boxes and radio buttons, select items in drop-down lists, etc.)
  • Create complex user interfaces (GUI's)
  • Work with the clipboard to cut/paste text items
  • Provide a scriptable RunAs function for Windows 2000/XP/2003

However what is really interesting to me is that AutoIt also includes a scriptable COM object essentially making its core application scripting capabilities available to any other COM capable languages including VBA, VBScript, PowerShell, or .Net. The COM interface on this object seems well thought out and is very easy to use. I started using AutoIt for this feature as I needed to automate an application from within Outlook's VBA scripting. Using the AutoIt COM object I was able to write Outlook VBA code to control another application including starting the application, waiting for its main window to launch, and intelligently sending data to the right parts of its UI.

And best of all, AutoIt is free. It can be found here.

Note: PowerShell is on the horizon and by all accounts it should be a very powerful tool but it also seems to lack some of this finer level of control for scripting GUI applications. You can however access the AutoIt COM object as well as the full .Net library.


First, a little context on why I wanted to do this in the first place. If you just want to get the source code then just scroll down to the "The source code".

A fair number of the things that make it on to my ToDo list are messages that I receive in Outlook. I've been using Outlook's flags to mark these items for follow up so that I could keep track of them. I use a different colored flag that denotes its own meaning as defined by me. This way I can flag an item using a action type (ToDo, Deferred, Waiting For, etc...). This has worked fairly well for me but once I had a large number of items flagged in Outlook (did I mention that I am a procrastinator?) it didn't really help me keep track of the things I needed to keep track of in any meaningful way. All I could even see was the complete list of flagged items but I couldn't break it down further into projects or context.

To overcome this limitation I've recently started managing my ToDo list outside of Outlook in a program called MyLifeOrganized (aka MLO) . MLO allows you to drag/drop Outlook items into MLO's task list. When you do this it creates a new MLO task using the subject of the dropped Outlook item for the task name. However MLO does something else really smart when you drag/drop an Outlook item. It not only put the text of the Outlook item into the notes, it will also create a hyperlink that will open up the original Outlook item when clicked. It does this by using Outlook's URL syntax which looks something like this:

Outlook:<entry_id> where <entry_id> is an Outlook Entry ID

Windows naturally understands this form of URLs. If you click on one it will cause Outlook to open the item referenced. This has allowed me to take Outlook items and create MLO items simply by dragging them to the MLO task list. In most cases this feature in MLO does exactly what I want, take an Outlook message that I need to follow up on and place it into my ToDo list. However sometimes this isn't exactly what I want. Sometimes I just want to place a link to Outlook items in the notes of an existing MLO item. Unfortunately MLO doesn't support this but there is a way to do it if you willing to do a little macro work in Outlook.

Update 6/4/2007: If you are using Office 2007 then you will probably need to enable the Outlook URL protocol handler so that hyperlinks to mail messages work.To do this requires editing the registry. You simple need to create these registry keys (substituting your installation paths of course):

  • HKEY_CLASSES_ROOT
    • outlook
      (Default) = URL:Outlook Folders
      URL Protocol=""
      • DefaultIcon
        (Default) = "C:\PROGRA~1\MICROS~2\OFFICE12\OUTLLIB.DLL,-9403"
      • shell
        • open
          • command
            (Default) = "C:\PROGRA~1\MICROS~2\OFFICE12\OUTLOOK.EXE" /select "%1"

The source code

Below is the source for a two VB macros that can be added to Outlook. These macros will loop over all of the currently selected messages, getting the subject and Outlook EntryID for each message. With these two pieces of information they then build a string of text with the message's subject and its Outlook URL, each on their own line. It extends this text string for each selected message and places the resulting text string on the clipboard. The end result is one block of text that contains the message subject followed by the Outlook URL for each selected message. This text can then be pasted into any document that understands hyperlinks. This includes the notes of MLO items as well as all of the other Microsoft Office applications. This macro will work with multiple items selected in the main Outlook window as well as from the opened window of a single Outlook message. To use it, simple invoke the CopyItemIDs() macro. You can bind this macro to a menu or toolbar button for easier access within Outlook.

Note: I should mention that if you are using Microsoft Exchange server, the message Entry ID can change on you and break any existing Outlook URLs. This unfortunately always happens if you move a message to another folder so if you plan on using this, only invoke this macro after you have moved the message to a new folder.

Update: There is just one more thing you must do before you run this script. You need to include a reference to FM20.dll, which is the Forms 2.0 library. This will allow you to use the DataObject to manipulate the clipboard. Thanks to 'Some Guy' who pointed this omission out.

Sub CopyItemIDs()
    Dim myOLApp As Application
    Dim myNameSpace As NameSpace
    Dim currentMessage As MailItem
    Dim ClipBoard As String
    Dim DataO As DataObject
    
    ' Housekeeping: set up the macro environment
    Set myOLApp = CreateObject("Outlook.Application")
    Set myNameSpace = myOLApp.GetNamespace("MAPI")
    
    ' Figure out if the active window is a list of messages or one message
    ' in its own window
    On Error GoTo QuitIfError    ' But if there's a problem, skip it
    Select Case myOLApp.ActiveWindow.Class
        ' The active window is a list of messages (folder); this means there
        ' might be several selected messages
        Case olExplorer
            ' build the clipboard string
            For Each currentMessage In myOLApp.ActiveExplorer.Selection
                ClipBoard = GetMsgDetails(currentMessage, ClipBoard)
            Next
             
        ' The active window is a message window, meaning there will only
        ' be one selected message (the one in this window)
        Case olInspector
            ' build the clipboard string
            ClipBoard = GetMsgDetails(myOLApp.ActiveInspector.CurrentItem, _
                                         ClipBoard)
        ' can't handle any other kind of window; anything else will be ignored
    End Select
    
QuitIfError:       ' Come here if there was some kind of problem
    Set myOLApp = Nothing
    Set myNameSpace = Nothing
    Set currentMessage = Nothing

    Set DataO = New DataObject
    DataO.Clear
    DataO.SetText ClipBoard
    DataO.PutInClipboard
    
    Set DataO = Nothing

End Sub

Function GetMsgDetails(Item As MailItem, Details As String) As String

    If Details <> "" Then
        Details = Details + vbCrLf
    End If
    Details = Details + Item.Subject + vbCrLf
    Details = Details + "Outlook:" + Item.EntryID + vbCrLf

    GetMsgDetails = Details

End Function

Flux and Mutability

The mutable notebook of David Jade