kessler / node-regedit

Read, Write, List and do all sorts of funky stuff to the windows registry using node.js and windows script host
MIT License
279 stars 45 forks source link

Exceedingly slow execution? #43

Open Pomax opened 6 years ago

Pomax commented 6 years ago

I'm using regedit to look up the x86 and x64 uninstall lists (writing a software manager that needs to know if certain things are installed already) and so I use the following code:

const regedit = require('regedit')

const keys = [
  'HKLM\\Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall',
  'HKLM\\Software\\Wow6432Node\\Microsoft\\Windows\\CurrentVersion\\Uninstall'
];

// the actual list of individual products
const realkeys = [];

// the list of resolved product entries
const entries = [];

// a list of just product names
const names = [];

/**
 * Expand the x86 and x64 "uninstall" keys to a full list of
 * registry keys we'll need to consult.
 */
function findRegKeys() {
  console.log("Resolving x86 and x64 keys...");
  let processed = 0;
  keys.forEach(uninstallKey => {
    regedit.list(uninstallKey, (err,list) => {
      list[uninstallKey].keys.forEach(key => {
        realkeys.push(`${uninstallKey}\\${key}`);
      });

      // are we done here?
      if (++processed === keys.length) {
        process.nextTick(processKeys);
      }
    });
  });
}

This then continues in the processKeys function:

/**
 * Process all keys found by the previous function
 */
function processKeys() {
  console.log(`Processing ${realkeys.length} registry keys...`);

  // We want this as sync as possible, because it seems
  // like the regedit package will return results *before*
  // they are actually done resolving to real data.
  //
  // That means if we don't wait for each entry to be
  // dereferenced by JSON.stringify first, we're going to
  // have an array of entries that might, if we run through
  // them fast enough (say, a subsequent .forEach) we're
  // going to hit shitloads of CRITICIAL ERROR notices
  // because there isn't actually any real data to work with...

  (function next() {
    if (realkeys.length === 0) {
      return processEntries();
    }

    let loc = realkeys.shift();

    regedit.list(loc, (err, data) => {
      if (err) return next();
      if (!data[loc]) return next();

      let entry = data[loc].values;

      if (!entry) return next();
      if (!entry.DisplayName) return next();

      // Force-resolve this object before moving on,
      // so that we have REAL data for each entry.
      let json = JSON.stringify(entry)
      entries.push(json);

      process.nextTick(next);
    });
  }(realkeys.slice()));
}

Which yields the actual data I care about, and hands that off for a final process pass before returning:

/**
 * Process all entries found. We turned them into strings
 * in the previous function, and we want them to be
 * normal objects, instead. So: map that.
 */
function processEntries() {
  console.log(`Processing ${entries.length} registry entries...`);
  let parsedEntries = [];
  entries.forEach(v => parsedEntries.push(JSON.parse(v)));

  // Then, we do a test. There has to be a vc++ redistributable
  // somewhere, because this is a Windows machine, and almost
  // everything comes with an install of one of those things:
  parsedEntries.forEach(entry => {
    let name = entry.DisplayName.value;
    if (name.toLowerCase().indexOf('redistributable')>-1) {
      console.log(entry);
    }
  });
}

// alright let's get this party started
findRegKeys();

On my system, this finds 690 entries, and then the processKeys function takes, literally, a minute. It needs 60 seconds to properly consult and resolve all the data, which seems inordinately long, and this seems caused by the fact that regedit.list doesn't return "real data", it seems to return early with some placeholder object that has getters for what will be real properties, but accessing them too soon will cause a "CRITICAL ERROR" notice. The only way around this I've found so far is to force full access to the object property-by-property (by using JSON.stringify) and not calling regedit.list for a next key until we're done with the current one.

So is there a way to speed this up? Or, is there a faster way to get an entire subtree associated with a key, rather than "just that key"? (and then I'll happily write my own key to run through that tree in JS. It's going to be way faster than waiting that full minute just to get the list of installed applications in the system. I initially tried to use wmic but that can't actually report on anything that wasn't installed from an .msi package, registry is the only way to get this data)