the following class implements a .reg file as object, allowing for key/subkey population and string/file output.
Generated files try to be as close as possible to .regs exported by the Registry Editor and can be imported.
There is currently no ReadFile function, because I don't need a parser.
String manipulation is basic and kinda slow, please feel free to improve.
Code: Select all
; ----------------------------------------------------------------------------------------------------------------------
; Class ........: RegFile
; Description ..: Class representing a .reg file. Allows managing keys/subkeys and writing the object to string or file.
; Constructor ..: RegFile(sFileName)
; Description ..: Construct the RegFile instance object (referred next as "objRegFile").
; Parameters ...: sFileName - Full path to the desired file.
; Return .......: RegFile instance.
; Pub. Method ..: objRegFile.Add(sKey, sSubKey, sType, sVal)
; Description ..: Add a subkey to the specified key. A validity check will be performed, then Keys will be saved as
; ..............: items in the "RegRoot" Map instance variable and contain SubKeys defined as simple objects with Name,
; ..............: Type and Value properties. "Defs" properties (NameDef, TypeDef, ValueDef) are the definitions used in
; ..............: the .reg file and will be processed and assigned by the script.
; Parameters ...: sKey - Full name of the Key (eg: HKEY_CURRENT_USER\SOFTWARE\Microsoft\Edge).
; ..............: sSubKey - Name of the SubKey (eg: TaskbarAutoPin). "Default" SubKey must be assigned with "@" and use
; ..............: the "REG_SZ" Type.
; ..............: sType - One of the possible accepted value types:
; ..............: * REG_SZ = String value.
; ..............: * REG_MULTI_SZ = MultiString value. Strings must be newline (`n) separated. Trailing
; ..............: newlines are omitted.
; ..............: * REG_EXPAND_SZ = Expandable string. Can't contain newlines (`n).
; ..............: * REG_DWORD = 32 bit Integer.
; ..............: * REG_QWORD = 64 bit Integer.
; ..............: * REG_BINARY = Binary buffer. Must be an AutoHotkey Buffer object.
; ..............: sValue - SubKey Value, reflecting the specified Type.
; Pub. Method ..: objRegFile.Del(sKey, sSubKey:="")
; Description ..: Delete a Key or a SubKey.
; Parameters ...: sKey - Key to be deleted or where to locate the SubKey.
; ..............: sSubKey - SubKey to be deleted. Leave it empty to delete the Key..
; Pub. Method ..: objRegFile.WriteFile()
; Description ..: Write the object to file, creating a valid .reg file that can be imported by the registry editor.
; ..............: The file is UTF-16 with a Little Endian BOM. Every call to WriteFile() overrides the file in place.
; Pub. Method ..: objRegFile.ToString()
; Description ..: Write the object to string, with the same format used to create the .reg file.
; Return .......: A string representing the .reg file.
; AHK Version ..: AHK v2 x32/64 Unicode
; Author .......: cyruz - http://ciroprincipe.info
; License ......: WTFPL - http://www.wtfpl.net/txt/copying/
; Changelog ....: Jan. 28, 2023 - v0.0.1 - First version.
; ..............: Jan. 30, 2023 - v0.0.2 - Fixed variable wording (Def >> NameDef).
; ----------------------------------------------------------------------------------------------------------------------
class RegFile
{
; Map of registry types and their file definition counterpart.
static TYPEDEFS := Map( "REG_SZ", ""
, "REG_MULTI_SZ", "hex(7):"
, "REG_EXPAND_SZ", "hex(2):"
, "REG_DWORD", "dword:"
, "REG_QWORD", "hex(b):"
, "REG_BINARY", "hex:" )
; Array of registry types not requiring a column break in the file.
static NOBREAKTYPES := [ "REG_SZ", "REG_DWORD", "REG_QWORD" ]
; Regular expression to validate keys.
static REGVALIDATOR := "(HK(?:EY_CLASSES_ROOT|EY_LOCAL_MACHINE|EY_USERS|EY_CURRENT_(?:USER|CONFIG))(?:64)?)((?:\\[^\\]+)+)"
; Registry Version to be used as header in the file.
static REGVERSION := "Windows Registry Editor Version 5.00"
; Newlines in the registry files are CRLF.
static NEWLINE := "`r`n"
regRoot := Map()
__New(sFileName)
{
if !FileExist(RegExReplace(sFileName, "[^\\]+$"))
throw Error("File path not valid: " sFileName)
this.FileName := sFileName
return this
}
Add(sKey, sSubKey, sType, sVal)
{
RegFile.__Validate(&sKey, &sSubKey, &sType, &sVal)
if !this.regRoot.Has(sKey)
{
this.RegRoot[sKey] := Map()
this.RegRoot[sKey].Name := sKey
this.RegRoot[sKey].NameDef := "[" sKey "]"
}
; Windows Registry doesn't allow for REG_MULTI_SZ values to contain empty strings
; and, due to the trailing newlines being treated as such, we remove all of them.
(sType = "REG_MULTI_SZ") && RegFile.__StrRemTrailNewLines(&sVal)
this.regRoot[sKey][sSubKey] := { Name: sSubKey
, NameDef: sSubKey = "@" ? sSubKey : "`"" sSubKey "`""
, Type: sType
, TypeDef: RegFile.TYPEDEFS[sType]
, Value: sVal
, ValueDef: RegFile.__GetValueDef(&sType, &sVal) }
}
Del(sKey, sSubKey:="")
{
(sSubKey == "")
? this.regRoot.Delete(sKey)
: this.regRoot[sKey].Delete(sSubKey)
}
WriteFile()
{
f := FileOpen(this.FileName, "w", "UTF-16")
f.Write(RegFile.REGVERSION RegFile.NEWLINE)
for ,okey in this.regRoot
{
f.Write(RegFile.NEWLINE okey.NameDef RegFile.NEWLINE)
for ,osub in okey
f.Write(RegFile.__StrPrintRecord(&osub) RegFile.NEWLINE)
}
f.Write(RegFile.NEWLINE)
f.Close()
}
ToString()
{
str := RegFile.REGVERSION RegFile.NEWLINE
for ,okey in this.regRoot
{
str .= RegFile.NEWLINE okey.NameDef RegFile.NEWLINE
for ,osub in okey
str .= RegFile.__StrPrintRecord(&osub) RegFile.NEWLINE
}
return str .= RegFile.NEWLINE
}
static __Validate(&sKey, &sSubKey, &sType, &sVal)
{
if !RegExMatch(sKey, RegFile.REGVALIDATOR)
throw Error("Key not valid!")
if !RegFile.TYPEDEFS.Has(sType)
throw Error("sType must be REG_SZ | REG_MULTI_SZ | REG_EXPAND_SZ | REG_DWORD | REG_QWORD | REG_BINARY")
if sSubKey = "@" && sType != "REG_SZ"
throw Error("Default key must be of REG_SZ type.")
switch sType
{
case "REG_SZ", "REG_MULTI_SZ", "REG_EXPAND_SZ":
{
if Type(sVal) != "String"
throw Error("REG_SZ/REG_MULTI_SZ/REG_EXPAND_SZ value must be a string.")
if (sType = "REG_SZ" || sType = "REG_EXPAND_SZ") && InStr(sVal, "`n")
throw Error("REG_SZ/REG_EXPAND_SZ values can't contain newlines.")
}
case "REG_DWORD", "REG_QWORD":
{
if !IsInteger(sVal)
throw Error("REG_DWORD/REG_QWORD value must be an integer.")
}
case "REG_BINARY":
{
if !IsObject(sVal) || Type(sVal) != "Buffer"
throw Error("REG_BINARY value must be a buffer object.")
}
default: throw Error("Undefined type!")
}
}
; Create a Value Definition for each Type, defining how the value will be saved in the .reg file.
; More specifically:
; REG_SZ = Simple quoted strings.
; REG_MULTI_SZ = MultiString, 1 per line. Saved in hex format, adding an additional final string terminator
; and replacing all newlines with specific string terminators.
; REG_EXPAND_SZ = String that can be expanded. Saved in hex format. Does not allow for newlines.
; REG_DWORD = 32 bit integer, saved as a DWORD (UInt).
; REG_QWORD = 64 bit integer, saved in hex format.
; REG_BINARY = Binary buffer, saved in hex format.
; All hex data will be written in a comma separated list of bytes in hex format without the "0x" prefix.
static __GetValueDef(&sType, &sVal)
{
switch sType
{
case "REG_SZ": valueDef := "`"" sVal "`""
case "REG_MULTI_SZ": valueDef := RegFile.__StrToCsvHex(&sVal) ",00,00" ; +1 string terminator.
case "REG_EXPAND_SZ": valueDef := RegFile.__StrToCsvHex(&sVal)
case "REG_DWORD": valueDef := Format("{:08x}", sVal)
case "REG_QWORD": valueDef := RegFile.__Int64ToCsvHex(&sVal)
case "REG_BINARY": valueDef := RegFile.__BufToCsvHex(sVal.Ptr, sVal.Size)
}
return valueDef
}
static __StrToCsvHex(&sVal)
{
; For REG_MULTI_SZ values, replace newlines with string terminators.
return StrReplace(RegFile.__BufToCsvHex(StrPtr(sVal), StrLen(sVal)*2+2), "0a,00", "00,00")
}
static __Int64ToCsvHex(&sVal)
{
NumPut("UInt64", sVal, buf:=Buffer(8))
return RegFile.__BufToCsvHex(buf.Ptr, buf.Size)
}
static __BufToCsvHex(pBuf, nSz)
{
VarSetStrCapacity(&str:="", nSz*4)
loop nSz
str .= Format("{:02x}", NumGet(pBuf, A_Index-1, "UChar")) ","
return SubStr(str, 1, -1)
}
static __StrRemTrailNewLines(&sVal)
{
ptr := StrPtr(sVal), len := StrLen(sVal)
loop
{
if InStr(RegFile.NEWLINE, SubStr(sVal, -(A_Index), 1)) && NumPut("UShort", 0, ptr, (len-A_Index)*2)
continue
else break
}
VarSetStrCapacity(&sVal, -1)
}
; Print a full record (.reg line) in the following format:
; "SubKey"=TypeDef:ValueDef
; Apply a column break for the following types: REG_MULTI_SZ, REG_EXPAND_SZ, REG_BINARY.
static __StrPrintRecord(&oSubKey, nColBreak:=77)
{
; No column break for no break types.
for ,v in RegFile.NOBREAKTYPES
if RegFile.TYPEDEFS[v] = oSubKey.TypeDef
return oSubKey.NameDef "=" oSubKey.TypeDef oSubKey.ValueDef
str := ""
row := oSubKey.NameDef "=" oSubKey.TypeDef
idx := (len:=StrLen(row)) < nColBreak ? len : nColBreak
cur := 1
len := StrLen(oSubKey.ValueDef)
; Each token is "xx,", aside from last, being only "xx".
while cur < len
{
if idx >= nColBreak
str .= row "\" RegFile.NEWLINE " "
, row := ""
, idx := 2 ; The 2 spaces in the next line.
else row .= (tkn:=SubStr(oSubKey.ValueDef, cur, 3))
, idx += 3
, cur += 3
}
return str .= row
}
}
/* Test Code:
#SingleInstance force
#Include <Class_RegFile>
a := RegFile("c:\Users\itoff\Desktop\test.reg")
a.Add("HKEY_CURRENT_USER\Test", "@", "REG_SZ", "Ziocagnaccio")
a.Add("HKEY_CURRENT_USER\Test", "AString", "REG_SZ", "asdjhjka hkj jkh ajkhs jkdhajksh djkasdjkhaskjdha sjkda sd")
a.Add("HKEY_CURRENT_USER\Test", "ADwordVal", "REG_DWORD", 123)
a.Add("HKEY_CURRENT_USER\Test", "AQwordVal", "REG_QWORD", 10953338411770462354)
a.Add("HKEY_CURRENT_USER\Test", "AMultiString", "REG_MULTI_SZ", "jksfdka jksdjf kljas dkfljasjd fljk jsadflk;djsfljfd`nkljsl;kdfjklsdfj;lk ajkfka sjdfkjasdfj;kljkf`nkljask;dfjpowiopierpowiporiweopriweproweporwe")
a.Add("HKEY_CURRENT_USER\Test", "AExpandableString", "REG_EXPAND_SZ", "asdf;oipo iwipoerpowei poopi23p4832i4;l;dlgkk;l34wk;l543 536436 45 ")
bin := "81 39 80 13 09 81 03 21 73 98 76 19 78 39 87 12 39 81 27 3B 98 12 73 71 37 98 17 39 17 38 87 18 91 78 98 99 81 78 99 8A 98 A1 98 A7 23 98 71 A8 39 71 87 38 A1 E8 A1 A1 20 18 91 74 81 A9 84"
arr := StrSplit(bin, A_Space)
buf := Buffer(arr.Length)
for ,v in arr
NumPut("UChar", "0x" v, buf.Ptr, A_Index-1)
a.Add("HKEY_CURRENT_USER\Test", "Binary", "REG_BINARY", buf)
a.WriteFile()
*/
Changelog:
- Jan. 30, 2023 - v0.0.2 - Fixed variable wording (Def >> NameDef).
Cheers