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.