skip to content
Null by Nature

Working on Reggie

/ 7 min read

Who or what is Reggie?

Reggie was the project I decided to start working on about a year ago at the time of writing this, to overcome some shortfalls I had when dealing with the Windows Registry on a project our company was working on for the support team.

Your next question might be, why are you using GoLang to utilise such a task?

Well, we needed a small, compact CLI tool that our team could run repetitive tasks from to retrieve and interact with information quickly.

The registry wasn’t something we were planning to interact with in the beginning, but as the scope of the project grew so did the interaction. By this point the project was 3,000 lines deep and we didn’t feel the necessary need to rewrite the whole thing in another language. We wanted to see how far we could push Go at a system level rather than what it focuses and excels at: Web.

Surprisingly, it worked extremely well and most implementations of logic were very simple, with thanks to the std library being so well thought out.

The problem

So, on to the interesting part, our first mission was to implement fixes that commonly occurred on customer machines with MS Office. The most talked about problem: OST/PST files exceeding 50GB, Outlook’s max file size for mailbox storage. Yep, why and how were these mailboxes so large? We knew the resolution, which was applying a patch into the registry. Our first thought was, well, you can easily deploy registry data via Group Policy for domain joined machines, but for the most part, a lot of customers didn’t need or want AD infrastructure.

The second option is to use a PowerShell script, like so:

Terminal window
$global:OutlookPstMaxFileSize = 0
$global:ComputerName = [System.Net.Dns]::GetHostName()
$global:Registry = [Microsoft.Win32.RegistryKey]::OpenRemoteBaseKey('CurrentUser', $global:ComputerName)
$global:OutlookDataSizeRegistryPath = $global:Registry.OpenSubKey("Software\Microsoft\Office\16.0\Outlook\PST", $true)
$FileSize = Read-Host "Write file size in GB for max PST size"
$global:OutlookDataSizeRegistryPath.SetValue("MaxLargeFileSize", ([int]$FileSize * 1024))

Third option, well, you guessed it, a REG file to just copy and open. But after discussing with the team, neither of these were preferred options because they require you to always have them on hand and constantly copying them to client’s machines and deleting after. So this was the first problem we tackled, and we got it working perfectly, let’s call our tool ctl:

ctl office outlook setpstsize 70

That command will set Outlook’s max PST size to 70GB and patch it into the registry. It’s not flamboyant, but it works. The problem, however, was how we had to access the Registry:

func SetOutlookPstSize(size int) {
const fileSizeKey = "MaxFileSize"
const largeFileSizeKey = "MaxLargeFileSize"
const outlookKey = "Software\\Microsoft\\Office\\16.0\\Outlook\\PST"
size = size * 1024
Logger.Info("Opening " + outlookKey)
key, err := registry.OpenKey(registry.CURRENT_USER, outlookKey, registry.QUERY_VALUE|registry.ENUMERATE_SUB_KEYS|registry.SET_VALUE)
if err != nil {
Logger.Error(err.Error())
}
currValue, _, err := key.GetIntegerValue(fileSizeKey)
if currValue == uint64(size) {
Logger.Error("Value given is already set! Nothing to do")
return
}
Logger.Info("Setting " + fileSizeKey + " pst size " + strconv.Itoa(size) + "MB")
err = key.SetDWordValue(fileSizeKey, uint32(size))
if err != nil {
Logger.Error(err.Error())
}
Logger.Info("Setting " + largeFileSizeKey + " pst size " + strconv.Itoa(size) + "MB")
err = key.SetDWordValue(largeFileSizeKey, uint32(size))
if err != nil {
Logger.Error(err.Error())
}
v, _, err := key.GetIntegerValue(fileSizeKey)
if err != nil {
Logger.Error(err.Error())
}
if uint32(v) != uint32(size) {
Logger.Fatal("Value of the key seems to remain unchanged, check registry manually")
return
} else if uint32(v) == uint32(size) {
Logger.Info("Successfully set " + fileSizeKey + " & " + largeFileSizeKey + " to " + strconv.Itoa(size/1024) + "GB")
}
}

Anyone familiar with Go can instantly see why this becomes a problem, especially if you’re accessing the Registry multiple times for other patches, which we did end up doing, quite a lot. I didn’t like having to constantly type the same thing over and over:

key, err := registry.OpenKey(registry.CURRENT_USER, ..., ...)

And then having to get whatever value, manipulate, maybe swap some keys, duplicate, set whatever you needed to. Point is, the implementation was always the same, and the usage was so barebones it was hard to write an interface around it, because we needed more functionality than just getting and setting. We needed ease of use, ways to traverse the registry, easy and clear definitions, preferably pointers to registry locations, so we could have clearly defined objects we would use within various scopes of the project without making it too hacky and reduce a lot of boilerplate.

Reggie

Now you can see why I decided I wanted a wrapper over the standard registry package. I’ll take some notes from the projects README to kickstart how Reggie works extremely well. Let’s take the standard definitions of the Registry package:

func main() {
key, _, err := registry.CreateKey(registry.CURRENT_USER, `Control Panel`, registry.ALL_ACCESS)
if err != nil {
log.Fatal(err)
}
subkeys, err := key.ReadSubKeyNames(0)
for _, subkey := range subkeys {
key, _, err := registry.CreateKey(registry.CURRENT_USER, `Control Panel\`+subkey, registry.ALL_ACCESS)
if err != nil {
log.Fatal(err)
}
moreKeys, err := key.ReadSubKeyNames(0)
if err != nil {
log.Fatal(err)
}
for _, k := range moreKeys {
fmt.Println(subkey, "->", k)
}
}
}

It’s long, a lot of error checking, having to manually get all the subkeys within the parent key you define, it’s a whole thing. However, Reggie’s definition is much simpler:

func main() {
controlPanel := reggie.NewReg(registry.ALL_ACCESS)
controlPanel.RootKey = registry.CURRENT_USER
controlPanel.Path = `Control Panel`
err := controlPanel.FillKeysValues()
if err != nil {
log.Fatal(err)
}
}

Beautiful work if I do say so myself. Let’s break this down, first I’ll show you the struct Reggie uses:

type Reg struct {
RootKey registry.Key // The key in which to access (HKLM, HKCU, etc). Can also be a subkey
ActiveKey registry.Key // The key currently opened, if different from the root key (i.e parent key)
Path string // The path inside RootKey to use
Permission uint32 // The access type for given RootKey and Path
SubKeyMap map[string]*SubKey // Holds the subkeys underneath RootKey
}

Starting out, we initialise:

controlPanel := reggie.NewReg(registry.ALL_ACCESS)

The NewReg function will initialise a struct containing the permission and an initialised SubKeyMap which is just an empty make(map[string]*SubKey). Once defined, you then supply the path and permission you want to start navigating:

controlPanel.RootKey = registry.CURRENT_USER
controlPanel.Path = `Control Panel`

Now we have a few possibilities as I wrote Reggie to have some utility, but in the provided example we use FillKeysValues():

err := controlPanel.FillKeysValues()

This is propbably one of my favourite functions that I wrote. It’s ridiculously simple, you can see the full function by clicking here if you would like to see it. I’ll break it down quickly to save time though:

  1. Enumerate through all the subkeys in the parent key
  2. Generate an absolute path to every subkey
  3. Open each subkey
  4. Initialise a sub key map and push the data into a nested &Reg pointed struct
  5. Read and get all values for each subkey and store them into the pointed struct

This can nest as deep as you want to go, it basically replicates the tree structure of the Registry. It looks like this:

Control Panel\Accessibility -> ToggleKeys &{0xc0003fd620 map[Flags:62]}
Control Panel\Accessibility -> HighContrast &{0xc0003fce40 map[Flags:126 High Contrast Scheme: Previous High Contrast Scheme MUI Value:]}
Control Panel\Accessibility -> On &{0xc0003fd1a0 map[Locale:0 On:0]}
Control Panel\Accessibility -> SlateLaunch &{0xc0003fd320 map[ATapp:narrator LaunchAT:1]}
Control Panel\Accessibility -> Blind Access &{0xc0003fcd80 map[On:0]}
Control Panel\Accessibility -> ShowSounds &{0xc0003fd260 map[On:0]}
Control Panel\Accessibility -> SoundSentry &{0xc0003fd3e0 map[FSTextEffect:0 Flags:2 TextEffect:0 WindowsEffect:1]}
Control Panel\Accessibility -> Keyboard Preference &{0xc0003fcf30 map[On:0]}

So within just a few lines I’ve achieved pretty much everything without doing much of anything thanks to Reggie. This seriously came in handy with the cli tool we developed, and shortened our boiler plate code for Registry interactions by a huge margin, and made it far more readable.

I did think on writing a complete registry package from the ground up, but the standard one was just fine, it was dead simple, I just needed a better way to access everything, and Reggie provides that. It also retains all of the original package functions, so you can utilise both Reggie and the std package. All functions inside the package are exported, so any developer using the package can fine-tune it’s behaviour or do some magic via dependency injection if they wanted, hell it’s even open source so take it and do with it as they wish!

If you want to check out this project in full, you can head over to my projects and check it out for yourself.