windows-fs
Version:
Windows utilities when working with the file system
347 lines (296 loc) • 8.02 kB
JavaScript
const splitEvery = require('ramda/src/splitEvery')
const findIndex = require('ramda/src/findIndex')
const remove = require('ramda/src/remove')
const reduce = require('ramda/src/reduce')
const append = require('ramda/src/append')
const ps = require('windows-powershell')
const split = require('ramda/src/split')
const spawn = require('buffered-spawn')
const pipe = require('ramda/src/pipe')
const { relative } = require('path')
const walk = require('fswalk')
const wmicArgs = [
'freeSpace',
'size'
]
/**
* Gets various information about all the drives mounted on a given
* `computer`
*
* @param {String} computer - Computer name
* @return {Promise} - Resolves to `{ json, stdout, stderr }`
*
* @example
* statDrives('computer-name').then(log)
* // -> [{ deviceID: 'C:', freeSpace: 4324564, ...}, ...]
*/
function statDrives (computer) {
const cmd = ps.pipe(
`get-wmiobject Win32_LogicalDisk -computerName ${computer}`,
'where -property DriveType -eq 3'
)
return ps.shell(ps.toJson(cmd))
}
/**
* Mounts a network drive to the next available drive letter and returns a
* the drive letter which it was mounted on.
*
* @param {String} unc - A UNC path like `//server`
* @param {String} path - A path like `some/path/to/folder`
* @param {Object} [credentials] - `user` and `password` to log into a network
* @returns {Promise} - Resolves to the drive letter which the path was
* mounted on, rejects when the command fails
*
* @example
* // (given letter Y: is free)
* mount('server', 'c$')
* .then(log)
* .catch((err) => console.error('failed to mount', err))
* // -> Y:
*/
function mount (unc, path, credentials) {
if (!unc && !path) {
throw new Error('No `unc` or `path` specified')
}
let args = ['use', '*', toUncPath(unc, path)]
if (credentials) {
if (!credentials.user || !credentials.password) {
throw new Error(
'Please specifiy both `user` and `password` keys for credentials'
)
}
args.push(`/user:${credentials.user}`)
args.push(credentials.password)
}
return spawn('net', args)
.then((io) => parseDriveLetter(io.stdout))
}
/**
* Unmounts a network drive given a `letter` and returns the `letter`.
*
* @param {String} letter - Network drive letter
* @returns {Promise} - Resolves to the drive letter when successful, rejects
* when the command fails
*
* @example
* // (given letter Z: is mounted)
* unmount('Z:')
* .then(log)
* .catch((err) => console.error('failed to unmount', err))
* // -> Z:
*/
function unmount (letter) {
if (!letter) {
throw new Error('No Letter specified')
}
const proc = spawn(
'net',
['use', letter, '/delete']
)
return proc.then(() => letter)
}
/**
* Gets a list of mounted drive letters and their respective unc paths.
*
* @return {Array} - Array of { unc, letter } tuples
*/
function mountedDrives () {
return spawn('net', ['use'])
.then((io) => {
return pipe(
//split into lines
split('\n'),
// remove header
remove(0, 6),
// parse command
reduce((acc, line) => {
const status = parseStatus(line)
// remove if no status
if (status == null) return acc
const letter = parseDriveLetter(line)
// remove `Microsoft Windows Network` lines
if (letter == null) return acc
const unc = parseUNC(line)
return append({ letter, unc }, acc)
}, [])
)(io.stdout)
})
}
/**
* Checks if a given `unc` path is already mounted. If it's mounted returns
* the drive letter otherwise returns undefined.
*
* @param {String} unc - Unc like path `server/share$`
* @return {String|undefined} - The drive letter or if not found `undefined`
*
* @example
* isMounted('server/share$')
* .then((letter) => {
* // mounted network drive hasn't been found
* if (!letter) return
* // found
* })
*/
function isMounted (unc) {
unc = toWindowsPath(toUnc(unc))
return mountedDrives()
.then((drives) => {
const i = findIndex((el) => el.unc == unc, drives)
return i != -1
? drives[i].letter
: undefined
})
}
/**
* Gets stats by a given drive `letter` like the current size of the hdd etc.
* This also works for drives in a network. Use `statDrives()` when their are
* no login credentials, otherwise use this function to avoid firewall
* settings.
*
* @param {String} letter - The drive letter which the hdd was mounted on
* @return {Promise} - A promise which resolves to `{ freeSpace, size }`
*
* @example
* statByDriveLetter('Z:')
* // -> { freeSpace: 10700152832, size: 53579083776 }
*/
function statByDriveLetter (letter) {
const procs = wmicArgs.map(
(arg) => {
return spawn(
'wmic',
['logicaldisk', 'where', `DeviceID="${letter}"`, 'get', arg]
).then((io) => {
let obj = {}
obj[arg] = Number(parseNumber(io.stdout))
return obj
})
}
)
return Promise.all(procs)
.then((stats) => {
return stats.reduce((acc, stat) => Object.assign(acc, stat), {})
})
}
/**
* Gets various metadata about the directory and the files in it using
* a recursive walk.
*
* @param {String} path - Absolute path
* @returns {Object} - Object with `size` (directory size in bytes),
* `count` (file count) and `files` (list of files in the directory and their
* respective metadata)
*
* @example
* statDirectory('c:/temp/log')
* // -> { count: 4, size: 32636, files: [{ name: '...' }, ...]}
*/
function statDirectory (path) {
path = toWindowsPath(path)
return new Promise((resolve, reject) => {
let count = 0
let size = 0
let files = []
walk(path, onFile, onFinish)
function onFile (file, stats) {
files.push(Object.assign({name: relative(path, file)}, stats))
count += 1
size += stats.size
}
function onFinish (err) {
if (err) return reject(err)
resolve({root: path, size, count, files})
}
})
}
/**
* Replaces `/` with `\` so we can use an unix path and convert it to a
* windows path.
*
* @param {String} path - Unix style path
* @returns {String} - Windows style path
*
* @example
* toWindowsPath('some/random/folder')
* // -> some\random\folder
*/
function toWindowsPath (path) {
return path.replace(/\//g, '\\')
}
/**
* Creates a windows path given a `server` name and a unix `path`.
*
* @param {String} server - Server name
* @param {String} share - Path
*
* @example
* toUncPath('server', 'some/path/to/a/log')
* // -> `\\server\some\path\to\a\log`
*/
function toUncPath (server, share) {
return toWindowsPath(`${toUnc(server)}/${share}`)
}
/**
* Parses a number out of a given `str`.
*
* @private
*/
function parseNumber (str) {
return /\d+/.exec(str)[0]
}
/**
* Parses a given `str` for drive letters like `Z:`. Returns null if not found.
*
* @private
*
* @example
* parseDriveLetter('a String with the letter Y: in it')
* // -> Y:
*/
function parseDriveLetter (str) {
const re = /([A-Z]):/
if (re.test(str)) {
return re.exec(str)[0]
} else {
return null
}
}
/**
* Parses the status out of a `net use` command. Returns null if not found.
*
* @private
*/
function parseStatus (str) {
const re = /^\w+/
if (re.test(str)) {
return re.exec(str)[0]
} else {
return null
}
}
/**
* Parses a given `str` for UNC paths like `\\server\share$\user`.
*
* @private
*/
function parseUNC (str) {
return /\\\\[^\s]+/.exec(str)[0]
}
/**
* Converts a server name to an unc path.
*
* @private
*/
function toUnc (server) {
return `//${server}`
}
exports.statByDriveLetter = statByDriveLetter
exports.mountedDrives = mountedDrives
exports.toWindowsPath = toWindowsPath
exports.statDirectory = statDirectory
exports.statDrives = statDrives
exports.toUncPath = toUncPath
exports.isMounted = isMounted
exports.unmount = unmount
exports.mount = mount