11 December 2022
I’m continuing my adventures using Go on Windows. Today: accessing the Windows Registry!
The Registry is a database that stores settings both for Windows itself and for applications. Most users won’t use it directly, but as a programmer you might need to. In this article I’ll go through a sample program to show how to do that in Go.
The full sample code is here: main.go. It’s a command-line program that prints a list of the shared folders exported by Windows. This was the use case that made me look into using the Windows registry from Go: I wanted to get the list of shared folders on a Windows PC to help me debug some issues related to network drives. It turns out the easiest way to get that (from Go, at least) is to read the list from the Registry.
Before we delve into the Go code, let’s look at how to get the shared
folders manually using the Registry Editor. In case you’re not familiar:
the Registry Editor, also known as regedit.exe
because it
comes from the days when filenames were limited to 8 characters plus an
extension, is a GUI program included with Windows that you can use to
poke at the Registry. Here’s what it looks like:
The Registry is organized in a hierarchy of keys, like folders in the file system. The key we need for the example program is:
Computer\HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\LanmanServer\Shares
Under this key there’s an entry for each shared folder on the
computer. To test the code I’ve set up two shared folders called
foo
and bar
, pointing to C:\foo
and C:\bar
; here’s what that looks like in the Registry
Editor:
You can see that the data stored for each contains several
key=value
mappings, for example
Path=C:\bar
.
We’ll be using the Go package golang.org/x/sys/windows/registry:
import "golang.org/x/sys/windows/registry"
The first step is to call OpenKey
to “open” the key we
want to read from:
path := `SYSTEM\CurrentControlSet\Services\LanmanServer\Shares`
key, err := registry.OpenKey(registry.LOCAL_MACHINE, path, registry.QUERY_VALUE)
if err != nil {
fmt.Printf("error opening Windows Registry key: %v\n", err)
os.Exit(1)
}
defer key.Close()
We pass the first part of the path
(Computer\HKEY_LOCAL_MACHINE
) as a constant, then the rest
of the path as a string, and another constant that defines what we want
to do with the key.
Next we read the names of all the values stored under this key:
names, err := key.ReadValueNames(-1)
if err != nil {
fmt.Printf("error reading Windows Registry value names: %v\n", err)
os.Exit(1)
}
ReadValueNames
takes the maximum number of names to
return; -1 means to return all of them. We’ll iterate over those names
and get the value stored for each:
for _, name := range names {
values, _, err := key.GetStringsValue(name)
if err != nil {
fmt.Printf("error reading Windows Registry value: %v\n", err)
os.Exit(1)
}
Values in the Registry can have different types. You can see the type
for the shared-folder entries in the screenshot above:
REG_MULTI_SZ
, which means “a sequence of null-terminated
strings”. Fortunately for us the GetStringsValue
method
takes care of splitting the strings and returns a simple
[]string
.
As we’ve seen, the entries here are a set of key=value
mappings. I wrote a little helper function called
mapFromStrings
to turn that into a Go map (you can see it
in the
source, it’s pretty straightforward); we’ll use that to make the
values easier to work with:
m := mapFromStrings(values)
We’ll check the Type
to make sure the entry is really
for a shared drive, and then print the name and the
Path
:
if m["Type"] != "0" {
// Type=0 means network drives
// if it's not 0 it could be, for example, a shared printer
continue
}
fmt.Printf("%s -> %s\n", name, m["Path"])
That’s it! We’ll compile the program with
GOOS=windows GOARCH=amd64 go build -o shares.exe main.go
and run it in the terminal:
C:\Program Files>.\shares.exe
foo -> C:\foo
bar -> C:\bar
You can see an overview of possible types on Microsoft’s website: Registry
Value Types. The registry
package includes methods to retrieve the various types called
GetBinaryValue
, GetIntegerValue
, etc. It also
has ways to get a key’s subkeys, to modify values, and so on – just
remember to change that third argument for OpenKey
,
otherwise you’ll get a confusing Access is denied
error
later on.