Recently I needed to query registry values from within Emacs. Emacs, being a cross-platform application mostly tailored to work on UNIX-like operating systems, lacks this functionality. As you can expect, I ended up writing a code which accomplishes the task.

One might need this functionality to configure Emacs to automatically find installation paths of the other tools. For example, I use it to get the git installation path to configure Magit properly.

Although I needed to query only string values (REG_SZ), there is support for the other registry value types as well. The provided code uses standard reg.exe command line utility to read values from the registry.

(defun win-reg-read (regkey &optional value access-64-bit)
  "Read a value in the Windows registry. If the value does not exist, it returns nil. For an empty REG_BINARY value it returns T."
  (when (string-equal system-type "windows-nt")
    (let* ((regkey (encode-coding-string regkey locale-coding-system))
           (value (and value (encode-coding-string value locale-coding-system)))
           (reg-exe (concat (getenv "WinDir") "\\System32\\reg.exe"))
           (bits-64-param (if access-64-bit
                              "/reg:64"
                            "/reg:32"))
           (reg-value (let ((output (with-output-to-string
                                      (with-current-buffer
                                          standard-output
                                        (if value
                                            (process-file reg-exe nil t nil
                                                          "query"
                                                          regkey
                                                          "/v"
                                                          value
                                                          bits-64-param)
                                          (process-file reg-exe nil t nil
                                                        "query"
                                                        regkey
                                                        "/ve"
                                                        bits-64-param))))))
                        (set-text-properties 0 (length output) nil output)
                        output)))
      (let* ((lines (split-string reg-value "\n" nil nil))
             (result-line (nth 2 lines)))
        (when (> (length lines) 4)
          (let* ((result-line (if value ; skip value name
                                  (cl-subseq result-line (+ 8 (length value)))
                                (replace-regexp-in-string "^[ ]+[\(][^\)]+[\)][ ]+" ; default value
                                                          ""
                                                          result-line)))
                 (tokens (split-string result-line "    " t))
                 (type-string (car tokens)) ; get registry value type
                 (result (cl-subseq result-line (+ 4 (length type-string))))) ; get registry value
            ;; decode the registry value string into a lisp object
            (cond
             ((or (string-equal type-string "REG_DWORD")
                  (string-equal type-string "REG_QWORD"))
              (string-to-number (cl-subseq result 2) 16))
             ((or (string-equal type-string "REG_SZ")
                  (string-equal type-string "REG_EXPAND_SZ"))
              result)
             ((string-equal type-string "REG_MULTI_SZ")
              (split-string result (regexp-quote "\\0") t)) ; will not work if there are '\0' sequences in the string
             ((string-equal type-string "REG_BINARY")
              (let ((res)
                    (pos 0)
                    (size (/ (length result) 2)))
                (if (zerop size)
                    t ; value is present, but empty
                  (progn
                    (dotimes (i size)
                      (push (string-to-number (cl-subseq result pos (+ pos 2)) 16) res)
                      (incf pos 2))
                    (reverse res)))))
             (t nil))))))))

The function above accepts three arguments:

  • Registry key name;
  • Registry value name (optional). If omitted, the default value of the registry key is queried;
  • The last parameter helps to handle registry virtualisation on 64-bit versions of Windows. If true, it queries the 64-bit branch of the registry, 32-bit one otherwise.

The code returns the value on success, NIL otherwise. For values, which by their nature hold multiple data items (REG_MULTISTRING_SZ, REG_BINARY) the lists of values are returned.

N.B. Eli Zaretskii pointed out that there were some problems with handling text encodings in the older version of the code. Hopefully, they are fixed now.

Limitations

I should make a couple of notes about accessing the multi-string (REG_MULTISTRING_SZ) values and binary values (REG_BINARY). The reg.exe utility reports information for multi-string values in such a way that the strings are separated by the "\0" sequence. If there is this sequence somewhere in the data the code below will return incorrect results.

Empty binary registry values are reported in a somewhat weird way: the code returns T for them.

I doubt that these limitations would bite you in practice.

Dealing with Registry Virtualisation

On 64-bit versions of Windows, the access to some registry keys is virtualised for 32-bit applications. As many applications exist in both 32-bit and 64-bit variants often it makes sense to check for both values in the registry.

The code below does just that. On 64-bit versions of Windows, it tries to query value in the 64-bit section of the registry at first and, if it is absent, it tries to query the 32-bit one. I guess that in most cases it is what you want.

(defun win-reg-read-64-or-32 (regkey &optional value)
  "Read a value in the Windows registry.
   If the value does not exist, it returns nil.
   Checks both 32-bit and 64-nit keys,
   considers system bitness when searching for a value."
  (let ((check (getenv "ProgramW6432")))
    (if (or (null check) (zerop (length check)))
        (win-reg-read regkey value nil) ; 32-bit Windows
      (or (win-reg-read regkey value t) ; 64-bit Windows
          (win-reg-read regkey value nil)))))

A Couple of Examples

You might try to get the Git For Windows installation path this way:

;; Installation Path
(win-reg-read-64-or-32 "HKLM\\SOFTWARE\\GitForWindows" "InstallPath")
"C:\\Tools\\Git"
;; Git core. Magit Expects it to be in the EXEC-PATH
(win-reg-read-64-or-32 "HKLM\\SOFTWARE\\GitForWindows" "LibexecPath")
"C:\\Tools\\Git\\mingw64\\libexec\\git-core"

The same thing for LibreOffice (useful if you want to configure exporting org-mode files to ODT):

(win-reg-read-64-or-32 "HKEY_LOCAL_MACHINE\\SOFTWARE\\LibreOffice\\UNO\\InstallPath")
"C:\\Program Files\\LibreOffice\\program"

You could obtain the Arduino Integrated Development Environment installation path in a similar way:

(win-reg-read-64-or-32 "HKEY_LOCAL_MACHINE\\SOFTWARE\\Arduino" "Install_Dir")
"C:\\Tools\\Arduino"

I hope you got the idea. Not every application keeps its installation path in the registry though, but many do. In any case you should do your research using the Windows Registry Editor.

Off course you could use this code not only for the purpose of querying of the installation paths. I believe that the ability to read registry values from within Emacs makes it possible to configure it in such a way that it would barely need any tailoring the configuration for the specific machine.

I hope you will find the code provided above to be useful or, at least, interesting.

P.S. Future versions of Emacs will directly support reading from registry. There are a couple of limitations though. Thanks to skeeto for pointing out.