Show ‘n Tell Thursday: Finding the “My Documents”-folder (17 August 2006)


Finding the correct path to write the file to when writing export routines or other routines that write files to the active users computer has always been a problem. Normally I have ended up using a path from a user profile document or by simply using the root of the C-drive. This has served me well but the optimal solution would be using the “My Documents”-folder of the user instead. This alliviates a number of problems that might otherwise occur:

  • The user may not have a C-drive
  • The user may not have permissions to write to the C-drive
  • The user may expect the file to go some place else than the root of the C-drive

Using the “My Documents”-folder is now the preferred way to doing it from my perspective. So how do we get at the actual path of the folder? You could use the HOMEDRIVE and HOMEPATH environment variables like this:

Dim export_path As String
export_path = Environ(|HOMEDRIVE|) + Environ(|HOMEPATH|) + "My Documents"

This will work for many users but not on my machine since I have remapped the location of the “My Documents”-folder to C:Data (the above will result in “C:Documents and Settingslekkim.HQMy Documents”). It also wouldn’t work if the users “My Documents”-folder has been mapped to a network location via Active Directory policies.

As it turns out it is a little difficult to get the location of the users “My Documents”-folder since there isn’t an environment variable that holds this piece of information. The solution is to use the SHGetFolderPath function of the Windows API.

Start by declaring the function in the Declarations section of your code. Also declare a number of constants for the “My Documents” and “My Pictures”-folders:

Declare Function SHGetFolderPath Lib "shfolder.dll" Alias "SHGetFolderPathA" (Byval hwndOwner As Long, Byval nFolder As Long, Byval hToken As Long, Byval dwReserved As Long, Byval lpszPath As String) As Long
Private Const MY_DOCUMENTS& = &H5
Private Const MY_PICTURES& = &H27
Private Const HWND_CURRENT_WINDOW& = &H0

Since the returned string will be 0-terminated as C-strings are we need a utility function to crop the returned result:

Private Function TrimNull(startstr As String) As String
   Dim i As Integer
   Dim char As String
   For i=Len(startstr) To 1 Step -1
      char = Mid$(startstr, i, 1)
      If Asc(char) = 0 Then
         TrimNull = Mid(startstr, 1, i-1)
      End If
   Next
End Function

Finally we need a utility function to make it easier to call the SHGetFolderPath function:

Private Function GetFolderPath(folder As Long) As String
   'declarations
   Dim buff As String

   'fill buffer with the specified folder item
   buff = Space$(256)
   If SHGetFolderPath(-1, folder, -1, &H27, buff) = 0    Then
      GetFolderPath = TrimNull(buff)
   End If
End Function

Putting it all together means that we can get the “My Documents” or “My Pictures” folder as easy as the following:

Dim export_path As String
export_path = GetFolderPath(MY_DOCUMENTS)
Msgbox "My Documents is at: " & export_path

export_path = GetFolderPath(MY_PICTURES)
Msgbox "My Pictures is at: " & export_path

That’s it! You can get more constants for the SHGetFolderPath function by looking up the function in a Windows API reference.

AutoPurge for OpenLog

We are using the OpenLog logging framework written by Julian extensively in our applications but in logging intensive applications or when running with our custom DEBUG-level enabled you can generate a lot of logging documents. Cleaning these up should preferably be done automatically on a scheduled basis so I wrote a small agent to purge log documents older than 6 months and thought I would share:

Sub Initialize
   Dim session As New NotesSession
   Dim db As NotesDatabase
   Dim dc As NotesDocumentCollection
   Dim dt As NotesDateTime

   Set dt = New NotesDateTime(session.International.Today)
   Call dt.AdjustMonth(-6)
   Set db = session.CurrentDatabase
   Set dc = db.Search(|Form="LogEvent" & LogEventTime<@TextToTime("| + dt.DateOnly + |")|, Nothing, 0)

   Call dc.RemoveAll(True)
End Sub

Really simple and nothing much to it…

Google Analytics

The by-invitation-only entry to Google Analytics has been removed so anyone who wants an account may have one. For those who do not know what Google Analytics is, it’s a free, totally cool web statictics package from Google. Installing it takes 2 minutes and is done by copy/pasting some JavaScript into your page layout. I highly recommend it.

To read more stop by the Google Analytics blog.

FeedDemon helps you manage your feeds

Just noticed a (new) feature in FeedDemon (I’m running version 2.0.0.24) that allows you to automatically unsubscribe from a feed if there are no updated within the defined number of days. That’s really cool for comment feeds which is becoming increasingly popular. Nice! If only I would be able to set this on entire feed folders and/or have feeds added to a specific folder automatically inherit the setting. See – that would be totally cool! At present I have to remember setting the flag on all comment feeds I subscribe to.

Security threats of syndicated content

There are a number of interesting discussions going on at the moment about the inherent threats of syndicated content sparked by a presentation at the Black Hat 2006 event. I like the way Don Park puts it in his “Comment on Microsoft Embracing RSS“-post:

"If you subscribe to 1000 feeds, you are hanging on a chain with 1000 links. Each of those 1000 links (feeds) are potential targets for hackers to attack to gain control over its content. All they need is one vulnerable feed hosting server to change what is delivered to your desktop."

Something to think about – especially when thinking about how Notes/Domino 7.0.2 will be able to deliver RSS feeds to your employees. I recommend the above post or the Dons other post (Syndicated Vulnerability) to get started.