GithubHelp home page GithubHelp logo

blue-infosec / regtools Goto Github PK

View Code? Open in Web Editor NEW

This project forked from gnumonik/regtools

0.0 1.0 0.0 173 KB

Window Registry Hive File Exploration Tools

License: BSD 3-Clause "New" or "Revised" License

Haskell 100.00%

regtools's Introduction

Notice

At the moment this is alpha software. While it passes basic unit tests and everything appears to work (on my Windows 10 registry at least), there is no guarantee that it will work with the registry hive you are examining. The binary format for the registry is not public, can change without notice, and may behave in ways that are not documented anywhere. If you run into an issue, please submit a bug report.

Known issues:

  • At the moment regtools does not support deserializing bd or "Big Data" values. None of the registry hives I have examined have any big data values as far as I can tell, so while it would be very easy to implement this, I need to find a registry file that definitely does contain them in order to test/develop the feature.
  • Timestamps might be off by a number of leap seconds. Need to find a way to check this.
  • Error messages aren't very helpful.

If you find regtools useful, or have a suggestion for a new feature or change, please open an issue and let me know. (If it's feasible and doesn't require an asinine amount of work, I will probably add a feature if you request it, even if you're the only one that uses this!)

regtools

regtools is a command-line utility + tiny, simple scripting language (written in Haskell) for forensic exploration of Windows registry hive bins (binary) files. Its features include an interactive shell for live exploration of registry keys and values, a plugin system to facilitate sharing and rerunning queries, the ability to output the results of queries (or the entire registry, if you really hate having free disk space) to JSON, and a tool for hashing select registry keys.

You should be able to compile + run regtools on any modern linux platform. It may compile on Windows (there are no platform-specific libraries) but is not actively targeted at Windows at this time.

Requirements

regtools attempts to parse the entirety of a windows registry hive and load it into memory before running queries on it. You'll need an amount of free RAM equal to (the size of the file you are loading) * 10-20-ish. This will come down with optimization, but you probably have more than enough RAM, but don't try to run it on a toaster.

It scales pretty well with the number of cores you have. On my Ryzen 3900x, it takes about 3 seconds to deserialize a 45mb hive file with all 12 cores, and around 30 with only one core in GHCI.

Installation

To install regtools you will first need a version of the Glasgow Haskell Compiler (GHC) and the Haskell Stack (stack) package manager.

It is highly suggested that you obtain a copy of stack via the ghcup Haskell installer. (Especially if you are on Arch Linux, I believe.) After you've installed, run ghcup tui, highlight any version of "Stack" that you see (if it doesn't appear, enter t to show all tools), then enter i to install.

Once you have that installed, clone the reposity and then:

cd regtools
stack install

You might want to go make a sandwich or something - it'll take a while to compile the dependencies. If you don't see any error messages when you get back, you should now have a copy of regtools installed to ~/.local/bin. Either move the regtools binary to a directory on your path, or add ~/.local/bin to your path.

(If you run into any issues with stack, check here for assistance.)

Usage

regtools has three modes of operation:

  1. Shell mode, which provides an interactive shell for exploring the registry, and can be accesed via:
regtools -i "/path/to/hive/bin" shell 
  1. Plugin mode, which reads a regtools plugin and runs it:
regtools -i "/path/to/hive/bin" plugin "/path/to/plugin/file" 
  1. Hash check mode, which reads a hash of registry keys generated by regtools and prints the result to the terminal:
regtools -i "/path/to/hive/bin" checkHash "/path/to/hash/file"

Queries & View Functions

regtools provides a (very) small DSL for querying a registry hive, printing the results. The syntax is inspired by bash style pipes. Here's an example of a regtools query:

query root (subkeys | select (keyNameHas "Software") | concatMap (subkeys) | select (keyNameHas "Adobe") )
     a        b               c                            d                      e                    

This is a query expression. All query expressions have the form:

query <focus> (viewer1 arg1 | viewer2 arg2 arg3 | <etc>)

The <focus> is either a variable (see the section on Assignments), or root. (Most of the time it will be root.)

viewer1, viewer2, etc are view functions, which are chained together with | in the manner of bash scripts. The output of viewer1 is fed into viewer2, and the output of that is fed into viewer3 if there is one, etc.

(Terminological note: arg1, arg2 etc are the arguments to the view function, whereas the output of the previous view function (or the focus if it is the first view function) are input.)

Here's what the example query expression does:

a) query root indicates that the query will be run against the root (first, top-level) key of a registry. Because this "focuses" on the root query, we call root the focus.

b) subkeys focus on a list of all of the subkeys of its input. Since we are focusing on the root key, it gives us all of the (immediate) subkeys of the root key.

c) select (keyNameHas "Software") (parentheses are mandatory with select) operates on a list of keys and removes every key from the list that does not satisfy the condition of its argument.

keyName takes a string as its argument and matches on any key in the input list which contains the string in its name. I.e. it performs substring search on the key names it is inspecting.

keyName's argument is case sensitive

d) concatMap takes a view function that produces a list as its argument, and a list as its input, and produces a list as its output. The main point of this is to avoid having to deal with lists of lists of lists (of lists...). It might seem complicated at first but it's really not so bad.

Here, concatMap (subkeys) (the parentheses aren't optional for concatMap) takes the subkeys view function as an argument, which has a single key as its input and produces a list of keys as its output, and applies subkeys to every key in its input list, then "squishes" the list of lists into a list.

e) Same thing as C. Remember: Performs a substring search on all keys in a list of keys and filters out those that do not contain the string "Adobe"

(See the reference at the end of this file for an explanation of all of the commands.)

Commands & Variable Assignment

If you run the example query in the previous section, you will notice that it does not produce any output to the terminal. This is intentional! Some queries may return a very large number of results, and it would take far longer to print them to the terminal than it takes the computer to calculate them.

To print the output to the terminal, you can use the (appropriately named) print command like so:

> print (query root (subkeys | select (keyNameHas "Software") | concatMap (subkeys) | select (keyNameHas "Adobe") ) )

[
| Key Name: Adobe
| Path: S-1-5-21-3199533274-2294187411-1904285961-1001_Classes\Software\
| TimeStamp:  Sat, 13 Mar 2021 14:01:34 UTC
| Values: <NONE>
| Subkeys: <TRUNCATED>
|------]

But that's kind of ugly and the parentheses make it hard to read, so, alternatively, you can assign the result of a query to a variable with a let expression, like so:

> let x = query root (subkeys | select (keyNameHas "Software") | concatMap (subkeys) | select (keyNameHas "Adobe") )

> print x

[
| Key Name: Adobe
| Path: S-1-5-21-3199533274-2294187411-1904285961-1001_Classes\Software\
| TimeStamp:  Sat, 13 Mar 2021 14:01:34 UTC
| Values: <NONE>
| Subkeys: <TRUNCATED>
|------]

Two things to note here:

First, *a variable can only be assigned once in a single shell session or script file. If you know an imperative programming language, you can think of regtools variables as constants. (They're really immutable variables, a la Haskell/Rust or Scala's 'val's, but the difference doesn't really matter here.)

One thing to note here is that the regtools scripting language is strongly/statically typed. If you don't know what the means (or are scared of types), don't worry! There are only 5 types:

  1. REGKEY, which represents a registry key

  2. VAL, which represents a registry value

  3. BOOL (i.e. True/False)

  4. LISTs, e.g. LIST REGKEY or LIST VAL. Lists in regtools must contain elements which are all the same type.

The 5th type is called EFFECT and you can mostly ignore it. (If you're familiar with functional programming, it's equivalent to () in Haskell or unit in purescript or... I forget what it's called in Scala. regtools commands are just expressions that return EFFECT.) This is the return type of commands and exists for the purposes of keeping the internal type system coherent.

You do not ever have to annotate the type. Indeed, it is impossible to do so! regtools can deduce the type of every valid expression. If you aren't sure what the type of an expression is , you can use the showType command in the shell, and regtools will tell you the type:

> let x = query root (subkeys | select (keyNameHas "Software") | concatMap (subkeys) | select (keyNameHas "Adobe") )

> showType x

LIST REGKEY

This tells you that the variable x refers to a list of RegistryKey values. Note that checking the type like this does not "run" (evaluate) the expression.

For the most part, all you need to keep track of is whether your expressions return a Registry Key, a list of Registry Keys, or a list of Registry Values.

Plugins

As mentioned above, regtools allows users to create plugins, which execute a series of queries and (potentially) print the results to the terminal or output them to a file. To define a plugin, simply create a text file with the following format:

PLUGIN {
    <Your queries/commands>
}

Plugins can only be run from the command line, a la the above example (repeated here):

regtools -i "/path/to/hive/bin" plugin "/path/to/plugin/file" 

Technically speaking, the regtools scripting language is completely insensitive to indentation / extra whitespace. But you should probably only put one expression or command on each line (for your own sanity).

As an example of a plugin file, here's the in-language test suite I run when making changes to regtools (it should give you a decent idea of how these work):

PLUGIN {
  printStr "Test #1 - Basic Query (subkeys, select, keyNameHas)"
  let someKeys = query root (subkeys | select (keyNameHas "mhtml"))

  printStr "Test #2 - `print`"  
  print someKeys 

  printStr "Test #3 - `writeJSON`" 
  writeJSON someKeys "/home/gsh/testJSON"

  printStr "Test #4 - `writeHash`" 
  writeHash someKeys "/home/gsh/testHASH"

  printStr "Test #5 - `checkHash`" 
  checkHash "/home/gsh/testHASH"

  printStr "Test #6 - `showType`" 
  showType someKeys 

  printStr "Test #7 - `key` (no <$ROOT$>)"
  print query root (key ".mhtml")

  printStr "Test #8 - `key` (just <$ROOT$>)"
  print query root (key "<$ROOT$>")

  printStr "Test #8 - `key` (with <$ROOT$>)"
  let atMostOneKey = query root (key "<$ROOT$>\.mhtml")
  print atMostOneKey

  printStr "Test #9 - `values`, `valNameHas`, `concatMap`" 
  print query root (subkeys | concatMap (values) | select (valNameHas "backup") )

  printStr "Test #10 - `valDataHas`" 
  print query root (subkeys | concatMap (values) | select (valDataHas "audio") )

  printStr "Test #12 - `expand`, var queries, line-break insensitivity, `map`" 
  print (
    query atMostOneKey (map (expand))
  )

  printStr "Test #13 - `append`" 
  let hasMHTML = query root (subkeys | select (keyNameHas "mhtml"))
  let hasPHTML = query root (subkeys | select (keyNameHas "phtml"))
  print (append hasMHTML hasPHTML)

  printStr "Test #14 - `concat`"
  let toConcat = query atMostOneKey (map (subkeys))
  print (concat toConcat)

  printStr "Test #15 - `isEmpty`, if-then" 
  if isEmpty toConcat 
    then printStr "Empty toConcat" 
    else printStr "Non-empty toConcat" 

  printStr "All tests succeeded"
}

Note that parentheses are largely optional outside of view-function expressions.

It is probably good practice to use relative paths when writing a plugin if you plan on sharing it, as it is quite unlikely that the person running it has the same home directory path as you!

(If enough people end up using regtools and request the feature, I will enable calling plugins from within the shell. There is no technical reason why this couldn't be done, but I am not sure that there is much use for it at the moment.)

View Function Reference

This section of the readme contains all of the view functions regtools supports, the input/output type of each view function, and examples of their use. The input output type will be designated with an arrow - e.g. REGKEY -> LIST REGKEY indicates that the view function takes a Registry Key as input and outputs a list of Registry Keys.

values

Type: REGKEY -> LIST VAL

No arguments

Example:

> let vsCodePhtmlVals = query root (subkeys | select (keyNameHas "VSCode.phtml") | map (values) )

> print vsCodePhtmlVals

[[|-----------
 |- Value Path: <$ROOT$>\VSCode.phtml
 |- Value Name: AppUserModelID
 |- Value Data: REG_SZ [Microsoft.VisualStudioCode]
 ,|-----------
 |- Value Path: <$ROOT$>\VSCode.phtml
 |- Value Name: 
 |- Value Data: REG_SZ [PHP HTML Source File]]]

values does not take any arguments. It receives a Registry Key as its input and outputs a list of the values the key contains.

expand

Type: REGKEY -> REGKEY

One optional integer argument

Before explaining this, note the <TRUNCATED> in the following example:

> let myRootKey = query root (key "<$ROOT$>")

> print myRootKey

[
| Key Name: S-1-5-21-3199533274-2294187411-1904285961-1001_Classes
| Path: \
| TimeStamp:  Tue, 24 Aug 2021 01:30:41 UTC
| Values: <NONE>
| Subkeys: <TRUNCATED>
|------]

(The key view function returns a list for reasons that will be explained in its section.)

Note: <$ROOT$> is the special path to the root key of a hive.

The Windows registry has a tree-like (logical) structure. Many higher-level keys contain an incredibly large number of subkeys. Because of this, regtools does not, by default, include subkeys in the results of queries or in the output to files or the terminal.

expand takes a Registry Key and fetches its subkeys recursively up to the depth of the optional argument. (Unfortunately I can't find a reasonably sized example so hopefully the following helps!) For instance, the query:

query myRootKey (map (expand 1))

Will evaluate to the root node of our registry as before, but instead of <TRUNCATED>, the Subkeys field now contains all of the root key's immediate children (each of which will have <TRUNCATED> in its Subkeys field, because we only expanded to a depth of 1).

If you call expand without an argument, it expands a key as much as it can. So, if you wanted to write an entire hive bin to JSON for instance, you could do so with:

let theWholeHive = query myRootKey (map (expand))
writeJSON theWholeHive "/hope/u/have/lotsa/free/space" "don't do this" 

(I strongly discourage this; the file size is disgustingly large compared to the binary hive. I cannot think of a good reason to dump a whole hive to JSON.)

subkeys

Type: REGKEY -> LIST REGKEY

No arguments

Gets a list of subkeys of the query target. Another example here probably wouldn't serve much purpose since this is used in most of the other ones!

key

Type: REGKEY -> LIST REGKEY

One argument: A string literal, enclosed in quotes

Example:

> print (query root (key ".mhtml"))

[
| Key Name: .mhtml
| Path: <$ROOT$>\
| TimeStamp:  Sat,  7 Dec 2019 09:51:11 UTC
| Values:
  |-----------
  |- Value Name: Content Type
  |- Value Data: REG_SZ [message/rfc822]
  |-----------
  |- Value Name: 
  |- Value Data: REG_SZ [mhtmlfile]
| Subkeys: <TRUNCATED>
|------]

key takes a quoted string that represents a path to one of its child (or grandchild, or great-great-grandchild, etc) keys as an argument and a Registry Key as input, and searches for the key at the given path. Two quirks:

  1. If you call key as the first part of a query expression that starts at the root (i.e. query root (key "KEYPATH")), as in the above example, then the first part of the path should not be the root key of the registry but the subkey that you wish to search for. You can use the special path placeholder <$ROOT$> to target the root key itself if you wish. In short, the queries
(query root (key ".mhtml"))

and

(query root (key "<$ROOT$>\.mhtml"))

are equivalent.

  1. The second quirk is that this returns a list rather than a specific key, even though it will never return more than one key. I apologize for this, but it has to be this way. The brief reason why is that regtools uses some fairly advanced type-level programming tricks (if you know Haskell, look at the Magic.hs source file in the Explore folder) to "borrow" the Haskell compiler's type checker, and a consequence of those tricks is that the regtools MUST crash if an expression in the DSL doesn't return some value. Which means the shell would crash every time a key you're looking for isn't found. Which is bad. So key returns an empty list if the key is not found.

keyNameHas

Type: REGKEY -> BOOL

One argument: A string literal enclosed between quotes

Example:

> print query root (subkeys | select (keyNameHas "blue") )

[
| Key Name: ms-settings-bluetooth
| Path: <$ROOT$>\
| TimeStamp:  Sat,  7 Dec 2019 09:15:36 UTC
| Values:
  |-----------
  |- Value Name: URL Protocol
  |- Value Data: REG_SZ []
  |-----------
  |- Value Name: 
  |- Value Data: REG_SZ [URL:ms-settings-bluetooth]
| Subkeys: <TRUNCATED>
|------]

keyNameHas is generally used only as an argument to select. It performs a (pretty fast) substring search on a Registry Key's name and returns true if its argument is contaiend in the name, or false if it's not.

NOTE: I have assumed that all key names are encoded in UTF8. If that is wrong, this won't work properly. I have not discovered a counterexample but if you are aware of one please let me know.

valNameHas

Type: VAL -> BOOL

One argument: A string literal enclosed between quotes

Same thing as keyNameHas but for values instead of keys so I'm not bothering with an example here. Same encoding problem. I believe the encoding problem might be fixable in this context but I need to do some more research.

valDataHas

Type: VAL -> BOOL

One argument: A string literal enclosed between quotes

Unlike keyNameHas, this view function converts the input string to the correct format depending on the value. That being said, there is no guarantee that value data is actually encoded in the format that the VK record says it is, so this might fail in cases where it seems like it should work if the software that created the key was poorly written (or well written, but malicious!).

Note that while the substring search algorithm used is efficient enough for small values, I am not sure how well this function will handle very large pieces of value data.

map

Type: LIST a -> LIST b (See the arguments + explanation)

One argument: A view function of type a -> b, where a and b are any of the types in the language. (Except EFFECT)

Reusing a previous example:

query root (subkeys | map (expand 1))

map is a higher-order view function. It takes one argument: Any other view function. When it receives a LIST of type a, it applies its a -> b view function argument to each element of LIST a and yields a LIST b.

If it helps, you can think of it as a kind of for loop that does something to every element of a list. If the type stuff confused you, it's used in several examples in this document, and looking at those might be better than starting at the types.

The parentheses around the argument are mandatory.

select

Type: LIST a -> LIST a (See the arguments + explanation)

One argument: A view function of type a -> Bool, where a is any of the types in the language. (Except EFFECT)

Example:

query root (subkeys | select (keyNameHas "yolo") )

This is pretty self explanatory even if the types are confusing. Again, it's in most of the examples, which are probably more useful for getting a feel for how it works.

The parentheses around the argument are mandatory.

concatMap

Type: LIST a -> LIST a (See the arguments + explanation)

One argument: A view function of type a -> LIST b, where a and b are any of the types in the language. (Except EFFECT)

Example (this is probably what you'll use it for 99% of the time):

query root (subkeys | concatMap (values) )

subkeys spits out a list of REGKEYs. values takes a REGKEY and spits out a list of values. Suppose that we want a list of all of the values in our entire list of subkeys, we can use concatMap to apply our values view function to the list of values subkeys gave us, then "squish" the result from a list to a list-of-lists.

Again, the example should make this clear even if the types look weird or strange.

The parentheses around the argument are mandatory.

Helper Functions / Control Flow

In order to facilitate very basic scripting, regtools offers a few helper functions and control expressions. These are probably useful primarily if you are writing plugins.

Note that these are not view functions. You cannot use them inside a query expression. I.e. query root (<ANYTHING IN THIS SECTION>) will throw an error.

append

Example:

> let someKeys = query root (subkeys | select (keyNameHas "VSCode.phtml"))

> print someKeys

[
| Key Name: VSCode.phtml
| Path: <$ROOT$>\
| TimeStamp:  Tue, 24 Aug 2021 01:30:38 UTC
| Values:
  |-----------
  |- Value Name: AppUserModelID
  |- Value Data: REG_SZ [Microsoft.VisualStudioCode]
  |-----------
  |- Value Name: 
  |- Value Data: REG_SZ [PHP HTML Source File]
| Subkeys: <TRUNCATED>
|------]

> print (append someKeys someKeys)

[
| Key Name: VSCode.phtml
| Path: <$ROOT$>\
| TimeStamp:  Tue, 24 Aug 2021 01:30:38 UTC
| Values:
  |-----------
  |- Value Name: AppUserModelID
  |- Value Data: REG_SZ [Microsoft.VisualStudioCode]
  |-----------
  |- Value Name: 
  |- Value Data: REG_SZ [PHP HTML Source File]
| Subkeys: <TRUNCATED>
|------
,
| Key Name: VSCode.phtml
| Path: <$ROOT$>\
| TimeStamp:  Tue, 24 Aug 2021 01:30:38 UTC
| Values:
  |-----------
  |- Value Name: AppUserModelID
  |- Value Data: REG_SZ [Microsoft.VisualStudioCode]
  |-----------
  |- Value Name: 
  |- Value Data: REG_SZ [PHP HTML Source File]
| Subkeys: <TRUNCATED>
|------]

I suppose you won't usually use append to combine two copies of the same list, but hopefully the example still works!

append combines two lists. The contents of each list must have the same type. The primary use of append is to combine the results of various queries before writing JSON / hash-JSON / printing.

Note that in this example, the variable someKeys has been assigned to a List of Registry Keys. (subkeys outputs a list and select filters a list to a potentially smaller list.) That list just happens to contain only one element.

concat

Example:

> let someOtherKeys = query root (subkeys | select (keyNameHas "VSCode.phtml") | map (values) )

> print someOtherKeys

[[|-----------
 |- Value Path: <$ROOT$>\VSCode.phtml
 |- Value Name: AppUserModelID
 |- Value Data: REG_SZ [Microsoft.VisualStudioCode]
 ,|-----------
 |- Value Path: <$ROOT$>\VSCode.phtml
 |- Value Name: 
 |- Value Data: REG_SZ [PHP HTML Source File]]]


> showType someOtherKeys

LIST (LIST VAL)

> print (concat someOtherKeys)

[|-----------
|- Value Path: <$ROOT$>\VSCode.phtml
|- Value Name: AppUserModelID
|- Value Data: REG_SZ [Microsoft.VisualStudioCode]
,|-----------
|- Value Path: <$ROOT$>\VSCode.phtml
|- Value Name: 
|- Value Data: REG_SZ [PHP HTML Source File]]
> showType (concat anotherKey)

LIST VAL

concat takes a LIST OF LISTS OF SOMETHING and turns it into a LIST OF SOMETHING. I think the example more or less speaks for itself.

isEmpty

Example (using the same variable as concat)

> print (isEmpty someOtherKeys)

False

isEmpty takes a list of any kind of value and returns True if the list is empty and False if it is not empty.

if-then-else

Example:

> if (isEmpty anotherKey) then printStr "Key not found" else writeJSON anotherKey "/home/gsh/testJSON2" 

(... prints out the JSON representation because the first argument evaluates to False ...)

if-then-else expressions provide the sole mechanism for control flow in regtools scripts. They mostly work like how you'd expect, with one caveat if your primary experience is in a dynamically typed language:

The then branch and the else branch must both have the same type!

In the above example, both branches are commands, which have the special EFFECT type. You can use if-then-else expressions with things other than commands, but you will get an error if the branches differ in type. You probably don't need this unless you are writing plugins.

Command Reference


Other than print and showType, the other regtools commands are:

Reminder: Commands and view functions are distinct from each other and cannot be used interchangable. E.g. this is WRONG!!!!!: query root (subkeys | print)

exit

exit closes the shell or stops execution of a plugin script. I assume that an example would be superfluous.

printStr

Example:

> printStr "hello"

hello

Prints a string (which must be enclosed in quotes) to the terminal. This is mainly useful for writing plugins (e.g. to indicate to the plugin user whether a key exists or not).

writeJSON

Examples:

> let someKeys = query root (subkeys | select (keyNameHas "mhtml"))

> writeJSON someKeys "/home/me/testJSON"

Array
    [ Object
        ( fromList
            [
                ( "Key Name"
                , String "mhtmlfile"
                )
            ,
                ( "Path"
                , String "<$ROOT$>"
                )
            ,
                ( "Subkeys"
(...etc)


> writeJSON someKeys "/home/me/testJSON" "myTag"


Object
    ( fromList
        [
            ( "myTag"
            , Array
                [ Object
                    ( fromList
                        [
                            ( "Key Name"
                            , String "mhtmlfile"
                            )
                        ,
                            ( "Path"
                            , String "<$ROOT$>"
                            )
                        ,
                            ( "Subkeys"
(... etc)

writeJSON, which takes two mandatory arguments and one optional argument. The first (mandatory) argument is a variable or expression of type REGKEY, LIST REGKEY, or LIST VAL. (Technically you can also write BOOLs to a file, but I'm not sure what the point would be.) The second mandatory argument is the output file path to write the JSON representation of a Key/List of Keys/List of Values. The third (optional) argument is a string which you can use to tag the JSON object. (The example should make the use of the optionaal third argument clear.)

writeHash

Example:

let someKeys = query root (subkeys | select (keyNameHas "mhtml"))
> writeHash someKeys "/home/me/myHash"

ManyKeys
    [ KeyHash
        { _hshKeyName = "<$ROOT$>\mhtmlfile"
        , _timeHash = "89e6358b107557efbce5a6579ef556db"
        , _valuesHash =
            [ ValHash
                { _vHashName = "FriendlyTypeName"
                , _vHashPath = "<$ROOT$>\mhtmlfile"
                , _vHashData = "9dc82a59684ff57d72e381e68a731e63"
                }
        , (... etc ...)

writeHash writes a JSON representation of an MD5-hashed version of the first argument to the file at the second argument. The first argument must have the type REGKEY, LIST REGKEY, or LIST VAL or you will get an error. As with writeJSON, the output you see in the terminal is a Haskell representation of JSON, but it should give you enough of an idea of the structure to work with it yourself.

NOTE: This overwrites any existing files.

I will probably remove the terminal output for writeHash/writeJSON at some point, or provide an option to silence it.

checkHash

Example:

> checkHash "/home/gsh/testHASH"

<$ROOT$>\mhtmlfile - Values changed
The value named FriendlyTypeName located at <$ROOT$>\mhtmlfile has changed.

Hash check complete (If you didn't get any output then everything matched).

checkHash checks a hash file generated by writeHash and reports any mismatches. (Should be pretty self explanatory.)

Alternate Syntax

On the off chance that anyone using this knows Haskell/Purescript, you can use >>> (the left-to-right categorical composition operator) instead of |. E.g.:

query root (subkeys >>> select (keyNameHas "\f x -> f (f x)"))

There's no real reason for this other than the fact that I think it looks better.

regtools's People

Contributors

gnumonik avatar

Watchers

James Cloos avatar

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.