diff --git a/src/fdlib.c b/src/fdlib.c
index 4ba522e5a1cc5ace087381c7863ebe700173ca96..08d2d395cdae8c182241061f295c5049ad304af5 100644
--- a/src/fdlib.c
+++ b/src/fdlib.c
@@ -28,6 +28,9 @@
 
 #if defined(HAVE_WINSOCK_H)
 
+#include <shlobj.h>
+#include <objbase.h>
+
 #include <time.h>
 
 /* Old versions of the headerfiles don't have this constant... */
@@ -76,6 +79,9 @@ int fd_busy[FD_SETSIZE];
 /* Next free fd. FD_NO_MORE_FREE (-1) if all fds allocated. */
 int first_free_handle;
 
+/* The root desktop folder. */
+static LPSHELLFOLDER isf = NULL;
+
 /* #define FD_DEBUG */
 /* #define FD_STAT_DEBUG */
 
@@ -629,6 +635,10 @@ void fd_init(void)
     } while(0)
 #include "ntlibfuncs.h"
   }
+
+  if (SHGetDesktopFolder(&isf) != S_OK) {
+    Pike_fatal("fdlib: No desktop folder!\n");
+  }
 }
 
 void fd_exit(void)
@@ -1520,6 +1530,106 @@ PMOD_EXPORT char *debug_fd_get_current_dir_name(void)
   return utf8buffer;
 }
 
+PMOD_EXPORT char *debug_fd_normalize_path(const char *path)
+{
+  p_wchar1 *pname = pike_dwim_utf8_to_utf16(path);
+  p_wchar1 *buffer;
+  char *res;
+  size_t len;
+  PIDLIST_ABSOLUTE idl;
+  HRESULT hres;
+
+  if (!pname) {
+    errno = ENOMEM;
+    return NULL;
+  }
+
+  /* First convert to an absolute path. */
+  len = MAX_PATH;
+  do {
+    size_t ret;
+
+    buffer = malloc(len * sizeof(p_wchar1));
+    if (!buffer) {
+      errno = ENOMEM;
+      return NULL;
+    }
+
+    ret = GetFullPathNameW(pname, len, buffer, NULL);
+
+    if (!ret) {
+      set_errno_from_win32_error(GetLastError());
+      return NULL;
+    }
+    if (ret*2 < len) break;
+
+    /* Buffer too small. Reallocate.
+     * NB: We over allocate 100% to be able to reuse the
+     *     buffer safely with SHGetPathFromIDListW() below.
+     */
+    free(buffer);
+    len = (ret+1)*2;
+  } while(1);
+  free(pname);
+
+  if ((hres = isf->lpVtbl->ParseDisplayName(isf, NULL, NULL, buffer,
+					    NULL, &idl, NULL)) != S_OK) {
+    free(buffer);
+    set_errno_from_win32_error(hres);
+    return NULL;
+  }
+
+  if (!SHGetPathFromIDListW(idl, buffer)) {
+    /* FIXME: Use SHGetPathFromIDListExW() (Vista/2008 and later),
+     *        to ensure that we don't overrun the buffer.
+     *        It however doesn't seem to be documented how
+     *        to detect whether it fails due to a too small
+     *        buffer or for some other reason, so we couldn't
+     *        use it to grow the res buffer and retry.
+     */
+    free(buffer);
+    CoTaskMemFree (idl);
+    errno = EINVAL;
+    return NULL;
+  }
+  CoTaskMemFree (idl);
+
+  /* Remove trailing slashes, except after a drive letter. */
+  len = wcslen(buffer);
+  while(len && buffer[len]=='\\') {
+    len--;
+  }
+  if (!len || (len == 1 && buffer[len] == ':')) len++;
+  buffer[len] = '\\';	/* Paranoia. */
+  buffer[len + 1] = 0;
+
+  /* Convert host and share in an UNC path to lowercase since Windows
+   * Shell doesn't do that consistently.
+   */
+  if (buffer[0] == '\\' && buffer[1] == '\\') {
+    size_t i;
+    int segments;
+    p_wchar1 c;
+
+    for (i = segments = 2; (c = buffer[i]) && segments; i++) {
+      if (c >= 256) continue;
+      buffer[i] = tolower(buffer[i]);
+      if (c == '\\') {
+	segments--;
+      }
+    }
+  }
+
+  res = pike_utf16_to_utf8(buffer);
+  free(buffer);
+  if (!res) {
+    errno = ENOMEM;
+    return NULL;
+  }
+
+  return res;
+}
+
 PMOD_EXPORT FD debug_fd_open(const char *file, int open_mode, int create_mode)
 {
   HANDLE x;
diff --git a/src/fdlib.h b/src/fdlib.h
index 65bf51fd3a44cb2ff746c24f974b6c25a61079da..a2988c85d75e20351afd0c57b43cc356c56808b4 100644
--- a/src/fdlib.h
+++ b/src/fdlib.h
@@ -89,6 +89,7 @@ typedef off_t PIKE_OFF_T;
 #define fd_rename(O,N)	debug_fd_rename(O,N)
 #define fd_chdir(DIR)	debug_fd_chdir(DIR)
 #define fd_get_current_dir_name()	debug_fd_get_current_dir_name()
+#define fd_normalize_path(PATH)	debug_fd_normalize_path(PATH)
 #define fd_open(X,Y,Z) dmalloc_register_fd(debug_fd_open((X),(Y)|fd_BINARY,(Z)))
 #define fd_socket(X,Y,Z) dmalloc_register_fd(debug_fd_socket((X),(Y),(Z)))
 #define fd_pipe(X) debug_fd_pipe( (X) DMALLOC_POS )
@@ -139,6 +140,7 @@ PMOD_EXPORT int debug_fd_mkdir(const char *dir, int mode);
 PMOD_EXPORT int debug_fd_rename(const char *old, const char *new);
 PMOD_EXPORT int debug_fd_chdir(const char *dir);
 PMOD_EXPORT char *debug_fd_get_current_dir_name(void);
+PMOD_EXPORT char *debug_fd_normalize_path(const char *path);
 PMOD_EXPORT FD debug_fd_open(const char *file, int open_mode, int create_mode);
 PMOD_EXPORT FD debug_fd_socket(int domain, int type, int proto);
 PMOD_EXPORT int debug_fd_pipe(int fds[2] DMALLOC_LINE_ARGS);
diff --git a/src/modules/system/nt.c b/src/modules/system/nt.c
index 6d719caeec4150b9acbb79e4000d2aa76e402528..5bc9646ea18b8fa585f837678792852a7c3e3f81 100644
--- a/src/modules/system/nt.c
+++ b/src/modules/system/nt.c
@@ -34,9 +34,6 @@
 #include <security.h>
 #endif
 
-#include <shlobj.h>
-#include <objbase.h>
-
 /* These are defined by winerror.h in recent SDKs. */
 #ifndef SEC_E_INSUFFICIENT_MEMORY
 #include <issperr.h>
@@ -2746,132 +2743,17 @@ static void f_NetWkstaUserEnum(INT32 args)
  */
 static void f_normalize_path(INT32 args)
 {
-  struct pike_string *str;
-  struct string_builder res;
-  size_t file_len;
-  p_wchar1 *pname, *file;
-  char *utf8;
-  ONERROR res_uwp, file_uwp;
-  DWORD ret;
-
-  get_all_args("normalize_path", args, "%S", &str);
-
-  pname = pike_dwim_utf8_to_utf16(str->str);
-  if (!pname) SIMPLE_OUT_OF_MEMORY_ERROR("normalize_path", str->len * 2);
-  SET_ONERROR(file_uwp, free, pname);
+  char *path = NULL;
 
-  /* Haven't got Emulate_GetLongPathNameW(). We essentially do what it
-   * does. This appears to be the only reliable way to normalize a
-   * path. (GetLongPathNameW() doesn't always correct upper/lower case
-   * differences, and opening the file to use e.g.
-   * GetFinalPathNameByHandle() on it might not work if it's already
-   * opened for exclusive access.)
-   */
+  get_all_args("normalize_path", args, "%s", &path);
 
-  /* First convert to an absolute path. */
-  file_len = MAX_PATH;
-  do {
-    file = xalloc (file_len * sizeof(p_wchar1));
-    ret = GetFullPathNameW (pname, file_len, file, NULL);
-    if (ret >= file_len) {
-      /* Buffer to small. Reallocate. */
-      free(file);
-      file_len = ret+1;
-      continue;
-    }
-    break;
-  } while(1);
-  CALL_AND_UNSET_ONERROR(file_uwp);
-  SET_ONERROR (file_uwp, free, file);
-
-  if (!ret) {
-    unsigned int err = GetLastError();
-    set_errno_from_win32_error (err);
-    throw_nt_error ("normalize_path", err);
+  path = fd_normalize_path(path);
+  if (!path) {
+    throw_nt_error("normalize_path", errno);
   }
 
-  {
-    LPSHELLFOLDER isf;
-    PIDLIST_ABSOLUTE idl;
-    HRESULT hres;
-
-    if (SHGetDesktopFolder (&isf) != S_OK)
-      /* Use a nondescript error code. */
-      throw_nt_error ("normalize_path", errno = ERROR_INVALID_DATA);
-
-    hres = isf->lpVtbl->ParseDisplayName (isf, NULL, NULL, file,
-					  NULL, &idl, NULL);
-    if (hres != S_OK) {
-      errno = (HRESULT_FACILITY (hres) == FACILITY_WIN32 ?
-	       HRESULT_CODE (hres) :
-	       /* Use a nondescript code if the error isn't a Win32 one. */
-	       ERROR_INVALID_DATA);
-      throw_nt_error ("normalize_path", errno);
-    }
-
-    /* NB: Ensure that the buffer isn't likely to be overrun
-     *     by the call to SHGetPathFromIDListW() below.
-     */
-    file_len = (wcslen(file) + 1) * 2;
-    if (file_len < MAX_PATH) file_len = MAX_PATH;
-    init_string_builder_alloc(&res, file_len, 1);
-    SET_ONERROR (res_uwp, free_string_builder, &res);
-
-    if (!SHGetPathFromIDListW(idl, STR1(res.s))) {
-      /* FIXME: Use SHGetPathFromIDListExW() (Vista/2008 and later),
-       *        to ensure that we don't overrun the buffer.
-       *        It however doesn't seem to be documented how
-       *        to detect whether it fails due to a too small
-       *        buffer or for some other reason, so we couldn't
-       *        use it to grow the res buffer and retry.
-       */
-      CoTaskMemFree (idl);
-      throw_nt_error ("normalize_path", errno = ERROR_INVALID_DATA);
-    }
-    res.s->len = wcslen(STR1(res.s));
-
-    CoTaskMemFree (idl);
-  }
-
-  file = STR1(res.s);
-
-  /* Remove trailing slashes, except after a drive letter. */
-  {
-    ptrdiff_t l = (ptrdiff_t) res.s->len-1;
-
-    if(l >= 0 && file[l]=='\\')
-    {
-      do l--;
-      while(l && file[l]=='\\');
-      if (l == 1 && file[l] == ':') l++;
-      file[l + 1]=0;
-    }
-    res.s->len = l + 1;
-  }
-
-  /* Convert host and share in an UNC path to lowercase since Windows
-   * Shell doesn't do that consistently.
-   */
-  if (file[0] == '\\' && file[1] == '\\') {
-    size_t i;
-    for (i = 2; file[i] && file[i] != '\\'; i++)
-      if (file[i] < 256)
-	file[i] = tolower (file[i]);
-    if (file[i] == '\\')
-      for (i++; file[i] && file[i] != '\\'; i++)
-	if (file[i] < 256)
-	  file[i] = tolower (file[i]);
-  }
-
-  pop_n_elems(args);
-
-  utf8 = pike_utf16_to_utf8(file);
-  if (!utf8) SIMPLE_OUT_OF_MEMORY_ERROR("normalize_path", res.s->len * 2);
-  CALL_AND_UNSET_ONERROR (res_uwp);
-  CALL_AND_UNSET_ONERROR (file_uwp);
-
-  push_text(utf8);
-  free(utf8);
+  push_text(path);
+  free(path);
 }
 
 /*! @decl int GetFileAttributes(string filename)