From eb2fde6a5a39e7835a4e10e283778204ac78668d Mon Sep 17 00:00:00 2001 From: Nestor Fedyk Date: Fri, 4 Dec 2020 15:31:26 -0500 Subject: [PATCH 01/14] Added two init/destroy methods to use key based Exchange authentication --- o365Utils.js | 614 +++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 444 insertions(+), 170 deletions(-) diff --git a/o365Utils.js b/o365Utils.js index 3f82154..c7ffb32 100644 --- a/o365Utils.js +++ b/o365Utils.js @@ -1,34 +1,33 @@ - /** -* getO365PSInitCommands() -* -* Returns an array of Powershell initialization commands suitable -* for setting up shells spawned with StatefulProcessCommandProxy -* to be able to establish a remote PSSession with o365 -* -* @see https://github.com/bitsofinfo/powershell-credential-encryption-tools -* -* This function takes the full path to: -* - decryptUtil.ps1 from the project above -* - path the encrypted credentials file generated with decryptUtil.ps1 -* - path to the secret key needed to decrypt the credentials -* -* In addition there are parameter to define the PSSessionOption timeouts -* -* Note this is just an example (which works) however you may want to -* replace this with your own set of init command tailored to your specific -* use-case -* -* @see the getO365PSDestroyCommands() below for the corresponding cleanup -* commands for these init commands -*/ -module.exports.getO365PSInitCommands = function(pathToDecryptUtilScript, - pathToCredsFile, - pathToKeyFile, - openTimeout, - operationTimeout, - idleTimeout) { - return [ + * getO365PSInitCommands() + * + * Returns an array of Powershell initialization commands suitable + * for setting up shells spawned with StatefulProcessCommandProxy + * to be able to establish a remote PSSession with o365 + * + * @see https://github.com/bitsofinfo/powershell-credential-encryption-tools + * + * This function takes the full path to: + * - decryptUtil.ps1 from the project above + * - path the encrypted credentials file generated with decryptUtil.ps1 + * - path to the secret key needed to decrypt the credentials + * + * In addition there are parameter to define the PSSessionOption timeouts + * + * Note this is just an example (which works) however you may want to + * replace this with your own set of init command tailored to your specific + * use-case + * + * @see the getO365PSDestroyCommands() below for the corresponding cleanup + * commands for these init commands + */ +module.exports.getO365PSInitCommands = function (pathToDecryptUtilScript, + pathToCredsFile, + pathToKeyFile, + openTimeout, + operationTimeout, + idleTimeout) { + return [ // #0 Encoding UTF8 'chcp 65001', '$OutputEncoding = [System.Text.Encoding]::GetEncoding(65001)', @@ -45,7 +44,7 @@ module.exports.getO365PSInitCommands = function(pathToDecryptUtilScript, ('$PSCredential = decrypt2PSCredential ' + pathToCredsFile + ' ' + pathToKeyFile), // #4+ establish the session to o365 - ('$sessionOpt = New-PSSessionOption -OpenTimeout '+openTimeout+' -OperationTimeout '+operationTimeout+' -IdleTimeout ' + idleTimeout), + ('$sessionOpt = New-PSSessionOption -OpenTimeout ' + openTimeout + ' -OperationTimeout ' + operationTimeout + ' -IdleTimeout ' + idleTimeout), '$session = New-PSSession -ConfigurationName Microsoft.Exchange -ConnectionUri https://outlook.office365.com/powershell-liveid/ -Credential $PSCredential -Authentication Basic -AllowRedirection -SessionOption $sessionOpt', // #5 import the relevant cmdlets (TODO: make this configurable) @@ -57,119 +56,336 @@ module.exports.getO365PSInitCommands = function(pathToDecryptUtilScript, // #7 cleanup 'Remove-Variable -Force -ErrorAction SilentlyContinue $PSCredential' - ] -} + ]; +}; + + +/** + * Destroy commands that correspond to the session + * established w/ the initCommands above + */ +module.exports.getO365PSDestroyCommands = function () { + return [ + 'Get-PSSession | Remove-PSSession -ErrorAction SilentlyContinue', + 'Remove-PSSession -Session $session', + 'Remove-Module MsOnline' + ]; +}; + +/** + * getO365PSKeyInitCommands() + * + * Returns an array of Powershell initialization commands suitable + * for setting up shells spawned with StatefulProcessCommandProxy + * to be able to establish a remote PSSession with o365 + * + * @see https://github.com/bitsofinfo/powershell-credential-encryption-tools + * @see https: //docs.microsoft.com/en-us/powershell/module/exchange/connect-exchangeonline?view=exchange-ps + * @see https: //adamtheautomator.com/exchange-online-powershell-mfa/#Authenticating_Using_Local_PFX_Certificate + * + * This function takes the full path to: + * - decryptUtil.ps1 from the project above + * - path the encrypted credentials file generated with decryptUtil.ps1 + * - path to the secret key needed to decrypt the credentials + * - path to the certificate configured for Exchange app authentication + * - certificate password + * - application id created for the Exchange app integration + * - tenant/organizationId for the Exchange + * - comma separatged list of commands to import, widcard supported. + * Everything is imported if empty + * + * In addition there are parameter to define the PSSessionOption timeouts + * + * Note this is just an example (which works) however you may want to + * replace this with your own set of init command tailored to your specific + * use-case + * + * @see the getO365PSKeyDestroyCommandsgetO365PSKeyDestroyCommands() below + for the corresponding cleanup + * commands for these init commands + */ +module.exports.getO365PSKeyInitCommands = function (pathToDecryptUtilScript, + pathToCredsFile, + pathToKeyFile, + pathToAuthCertificate, + authCertificatePassword, + applicationId, + organizationId, + openTimeout, + operationTimeout, + idleTimeout, + commandsToImport = '') { + + let psCommandsToImport = ''; + if (commandsToImport != '') { + psCommandsToImport = '-CommandName ' + commandsToImport; + } + return [ + // #0 Encoding UTF8 + 'chcp 65001', + '$OutputEncoding = [System.Text.Encoding]::GetEncoding(65001)', + + // #1 import some basics + 'Import-Module MSOnline', + 'Import-Module ExchangeOnlineManagement', + + // #2 source the decrypt utils script + // https://github.com/bitsofinfo/powershell-credential-encryption-tools/blob/master/decryptUtil.ps1 + ('. ' + pathToDecryptUtilScript), + + // #3 invoke decrypt2PSCredential to get the PSCredential object + // this function is provided by the sourced file above + ('$PSCredential = decrypt2PSCredential ' + pathToCredsFile + ' ' + pathToKeyFile), + + // #4 connect to azure as well + 'Connect-MsolService -Credential $PSCredential', + + // #5 get session options, certificate file and secure password + ('$sessionOpt = New-PSSessionOption -OpenTimeout ' + openTimeout + ' -OperationTimeout ' + operationTimeout + ' -IdleTimeout ' + idleTimeout), + '$CertificateFilePath = (Resolve-Path "' + pathToAuthCertificate + '").Path', + '$CertificatePassword = (ConvertTo-SecureString -String "' + authCertificatePassword + '" -AsPlainText -Force)', + + // #6 connect to exchange + 'Connect-ExchangeOnline -CertificateFilePath $CertificateFilePath -CertificatePassword $CertificatePassword -AppID ' + applicationId + ' -Organization ' + organizationId + psCommandsToImport, + + // #7 cleanup + 'Remove-Variable -Force -ErrorAction SilentlyContinue $PSCredential' + ]; +}; + +/** + * Destroy commands that correspond to the session + * established w/ the KeyinitCommands above + */ +module.exports.getO365PSKeyDestroyCommands = function () { + return [ + 'Get-PSSession | Remove-PSSession -ErrorAction SilentlyContinue', + 'Remove-Module MsOnline', + 'Remove-Module ExchangeOnlineManagement', + ]; +}; + +/** + * getO365PSKeyInitCommands() + * + * Returns an array of Powershell initialization commands suitable + * for setting up shells spawned with StatefulProcessCommandProxy + * to be able to establish a remote PSSession with o365 + * + * @see https://github.com/bitsofinfo/powershell-credential-encryption-tools + * @see https: //docs.microsoft.com/en-us/powershell/module/exchange/connect-exchangeonline?view=exchange-ps + * @see https: //adamtheautomator.com/exchange-online-powershell-mfa/#Authenticating_Using_Certificate_Thumbprint + * + * This function takes the full path to: + * - decryptUtil.ps1 from the project above + * - path the encrypted credentials file generated with decryptUtil.ps1 + * - path to the secret key needed to decrypt the credentials + * - certificate thumbprint configured for Exchange app authentication + * - application id created for the Exchange app integration + * - tenant/organizationId for the Exchange + * - comma separatged list of commands to import, widcard supported. + * Everything is imported if empty + * + * In addition there are parameter to define the PSSessionOption timeouts + * + * Note this is just an example (which works) however you may want to + * replace this with your own set of init command tailored to your specific + * use-case + * + * @see the getO365PSKeyDestroyCommandsgetO365PSKeyDestroyCommands() below + for the corresponding cleanup + * commands for these init commands + */ +module.exports.getO365PSThumbprintInitCommands = function (pathToDecryptUtilScript, + pathToCredsFile, + pathToKeyFile, + certificateThumbPrint, + applicationId, + organizationId, + openTimeout, + operationTimeout, + idleTimeout, + commandsToImport = '') { + + let psCommandsToImport = ''; + if (commandsToImport != '') { + psCommandsToImport = '-CommandName ' + commandsToImport; + } + return [ + // #0 Encoding UTF8 + 'chcp 65001', + '$OutputEncoding = [System.Text.Encoding]::GetEncoding(65001)', + + // #1 import some basics + 'Import-Module MSOnline', + 'Import-Module ExchangeOnlineManagement', + + // #2 source the decrypt utils script + // https://github.com/bitsofinfo/powershell-credential-encryption-tools/blob/master/decryptUtil.ps1 + ('. ' + pathToDecryptUtilScript), + + // #3 invoke decrypt2PSCredential to get the PSCredential object + // this function is provided by the sourced file above + ('$PSCredential = decrypt2PSCredential ' + pathToCredsFile + ' ' + pathToKeyFile), + + // #4 connect to azure as well + 'Connect-MsolService -Credential $PSCredential', + + // #5 get session options + ('$sessionOpt = New-PSSessionOption -OpenTimeout ' + openTimeout + ' -OperationTimeout ' + operationTimeout + ' -IdleTimeout ' + idleTimeout), + + // #6 connect to exchange + 'Connect-ExchangeOnline -CertificateThumbPrint ' + certificateThumbPrint + ' -AppID ' + applicationId + ' -Organization ' + organizationId + psCommandsToImport, + + // #7 cleanup + 'Remove-Variable -Force -ErrorAction SilentlyContinue $PSCredential' + ]; +}; /** -* Destroy commands that correspond to the session -* established w/ the initCommands above -*/ -module.exports.getO365PSDestroyCommands = function() { + * Destroy commands that correspond to the session + * established w/ the KeyinitCommands above + */ +module.exports.getO365PSThumbprintDestroyCommands = function () { return [ - 'Get-PSSession | Remove-PSSession', - 'Remove-PSSession -Session $session', - 'Remove-Module MsOnline' - ] - } + 'Get-PSSession | Remove-PSSession -ErrorAction SilentlyContinue', + 'Remove-Module MsOnline', + 'Remove-Module ExchangeOnlineManagement', + ]; +}; /** -* Some example blacklisted commands -*/ -module.exports.getO365BlacklistedCommands = function() { - return [ - {'regex':'.*Invoke-Expression.*', 'flags':'i'}, - {'regex':'.*ScriptBlock.*', 'flags':'i'}, - {'regex':'.*Get-Acl.*', 'flags':'i'}, - {'regex':'.*Set-Acl.*', 'flags':'i'}, - {'regex':'.*Get-Content.*', 'flags':'i'}, - {'regex':'.*-History.*', 'flags':'i'}, - {'regex':'.*Out-File.*', 'flags':'i'} - ] -} + * Some example blacklisted commands + */ +module.exports.getO365BlacklistedCommands = function () { + return [{ + 'regex': '.*Invoke-Expression.*', + 'flags': 'i' + }, + { + 'regex': '.*ScriptBlock.*', + 'flags': 'i' + }, + { + 'regex': '.*Get-Acl.*', + 'flags': 'i' + }, + { + 'regex': '.*Set-Acl.*', + 'flags': 'i' + }, + { + 'regex': '.*Get-Content.*', + 'flags': 'i' + }, + { + 'regex': '.*-History.*', + 'flags': 'i' + }, + { + 'regex': '.*Out-File.*', + 'flags': 'i' + } + ]; +}; /** -* Configuration auto invalidation, checking PSSession availability -* @param checkIntervalMS -*/ -module.exports.getO365AutoInvalidationConfig = function(checkIntervalMS) { - return { - 'checkIntervalMS': checkIntervalMS, - 'commands': [ - // no remote pssession established? invalid! - { 'command': 'Get-PSSession', + * Configuration auto invalidation, checking PSSession availability + * @param checkIntervalMS + */ +module.exports.getO365AutoInvalidationConfig = function (checkIntervalMS) { + return { + 'checkIntervalMS': checkIntervalMS, + 'commands': [ + // no remote pssession established? invalid! + { + 'command': 'Get-PSSession', 'regexes': { - 'stdout' : [ {'regex':'.*Opened.*', 'flags':'i', 'invalidOn':'noMatch'}] + 'stdout': [{ + 'regex': '.*Opened.*', + 'flags': 'i', + 'invalidOn': 'noMatch' + }] } - }] - }; - } + } + ] + }; +}; /** -* Defines a registry of Powershell commands -* that can be injected into the PSCommandService -* instance. -* -* Note these are just some example configurations specifically for a few -* o365 functions and limited arguments for each, (they work) however you may want to -* replace this with your own set of init command tailored to your specific -* use-case -*/ + * Defines a registry of Powershell commands + * that can be injected into the PSCommandService + * instance. + * + * Note these are just some example configurations specifically for a few + * o365 functions and limited arguments for each, (they work) however you may want to + * replace this with your own set of init command tailored to your specific + * use-case + */ var o365CommandRegistry = { /******************************* - * - * o365 Powershell Command registry - * - * argument properties (optional): - * - quoted: true|false, default true - * - valued: true|false, default true - * - default: optional default value (only if valued..) - * - * return properties: - * type: none, text or json are valid values - * - ********************************/ + * + * o365 Powershell Command registry + * + * argument properties (optional): + * - quoted: true|false, default true + * - valued: true|false, default true + * - default: optional default value (only if valued..) + * + * return properties: + * type: none, text or json are valid values + * + ********************************/ /******************************* - * MsolUser - ********************************/ + * MsolUser + ********************************/ 'getMsolUser': { - 'command': 'Get-MsolUser {{{arguments}}} | ConvertTo-Json', - 'arguments': { - 'UserPrincipalName': {} - }, - 'return': { type: 'json' } + 'command': 'Get-MsolUser {{{arguments}}} | ConvertTo-Json', + 'arguments': { + 'UserPrincipalName': {} + }, + 'return': { + type: 'json' + } }, 'newMsolUser': { - 'command': 'New-MsolUser {{{arguments}}} | ConvertTo-Json', - 'arguments': { - 'DisplayName': {}, - 'UserPrincipalName': {} - }, - 'return': { type: 'json' } + 'command': 'New-MsolUser {{{arguments}}} | ConvertTo-Json', + 'arguments': { + 'DisplayName': {}, + 'UserPrincipalName': {} + }, + 'return': { + type: 'json' + } }, 'removeMsolUser': { - 'command': 'Remove-MsolUser -Force {{{arguments}}} ', - 'arguments': { - 'UserPrincipalName': {} - }, - 'return': { type: 'none' } + 'command': 'Remove-MsolUser -Force {{{arguments}}} ', + 'arguments': { + 'UserPrincipalName': {} + }, + 'return': { + type: 'none' + } }, /******************************* - * DistributionGroups - ********************************/ + * DistributionGroups + ********************************/ 'getDistributionGroup': { 'command': 'Get-DistributionGroup {{{arguments}}} | ConvertTo-Json', 'arguments': { 'Identity': {} }, - 'return': { type: 'json' } + 'return': { + type: 'json' + } }, 'newDistributionGroup': { @@ -177,20 +393,35 @@ var o365CommandRegistry = { 'command': 'New-DistributionGroup -Confirm:$False {{{arguments}}} | ConvertTo-Json', 'arguments': { - 'Name': {}, - 'DisplayName': {}, - 'Alias': {}, + 'Name': {}, + 'DisplayName': {}, + 'Alias': {}, 'PrimarySmtpAddress': {}, - 'Type': {'quoted':false, 'default':'Security'}, - 'ManagedBy': {}, - 'Members': {}, // specifying members on create does not seem to work - 'ModerationEnabled': { 'default':'0', 'quoted':false}, - 'MemberDepartRestriction': { 'default':'Closed'}, - 'MemberJoinRestriction': { 'default':'Closed'}, - 'SendModerationNotifications': { 'default':'Never', 'quoted':false}, + 'Type': { + 'quoted': false, + 'default': 'Security' + }, + 'ManagedBy': {}, + 'Members': {}, // specifying members on create does not seem to work + 'ModerationEnabled': { + 'default': '0', + 'quoted': false + }, + 'MemberDepartRestriction': { + 'default': 'Closed' + }, + 'MemberJoinRestriction': { + 'default': 'Closed' + }, + 'SendModerationNotifications': { + 'default': 'Never', + 'quoted': false + }, }, - 'return': { type: 'json' } + 'return': { + type: 'json' + } }, 'setDistributionGroup': { @@ -198,21 +429,35 @@ var o365CommandRegistry = { 'command': 'Set-DistributionGroup -Confirm:$False {{{arguments}}}', 'arguments': { - 'Identity': {}, - 'Name': {}, - 'DisplayName': {}, - 'Alias': {}, + 'Identity': {}, + 'Name': {}, + 'DisplayName': {}, + 'Alias': {}, 'PrimarySmtpAddress': {}, - 'ManagedBy': {}, - 'Members': {}, - 'MailTip': {}, - 'ModerationEnabled': { 'default':'0', 'quoted':false}, - 'MemberDepartRestriction': { 'default':'Closed'}, - 'MemberJoinRestriction': { 'default':'Closed'}, - 'SendModerationNotifications': { 'default':'Never', 'quoted':false}, - 'BypassSecurityGroupManagerCheck': {'valued': false} + 'ManagedBy': {}, + 'Members': {}, + 'MailTip': {}, + 'ModerationEnabled': { + 'default': '0', + 'quoted': false + }, + 'MemberDepartRestriction': { + 'default': 'Closed' + }, + 'MemberJoinRestriction': { + 'default': 'Closed' + }, + 'SendModerationNotifications': { + 'default': 'Never', + 'quoted': false + }, + 'BypassSecurityGroupManagerCheck': { + 'valued': false + } }, - 'return': { type: 'none' } + 'return': { + type: 'none' + } }, @@ -221,11 +466,15 @@ var o365CommandRegistry = { 'command': 'Remove-DistributionGroup {{{arguments}}} -Confirm:$false', 'arguments': { - 'Identity': {}, + 'Identity': {}, // needed if invoking as global admin who is not explicitly a group admin.. stupid... yes. - 'BypassSecurityGroupManagerCheck': {'valued': false} + 'BypassSecurityGroupManagerCheck': { + 'valued': false + } }, - 'return': { type: 'none' } + 'return': { + type: 'none' + } }, @@ -234,9 +483,11 @@ var o365CommandRegistry = { 'command': 'Get-DistributionGroupMember {{{arguments}}} -ResultSize Unlimited | ConvertTo-Json', 'arguments': { - 'Identity': {} + 'Identity': {} }, - 'return': { type: 'json' } + 'return': { + type: 'json' + } }, @@ -245,12 +496,16 @@ var o365CommandRegistry = { 'command': 'Add-DistributionGroupMember {{{arguments}}}', 'arguments': { - 'Identity': {}, - 'Member': {}, + 'Identity': {}, + 'Member': {}, // needed if invoking as global admin who is not explicitly a group admin.. stupid... yes. - 'BypassSecurityGroupManagerCheck': {'valued': false} + 'BypassSecurityGroupManagerCheck': { + 'valued': false + } }, - 'return': { type: 'none' } + 'return': { + type: 'none' + } }, // members specified w/ this are a full overwrite.. @@ -259,12 +514,16 @@ var o365CommandRegistry = { 'command': 'Update-DistributionGroupMember -Confirm:$false {{{arguments}}}', 'arguments': { - 'Identity': {}, - 'Members': {}, + 'Identity': {}, + 'Members': {}, // needed if invoking as global admin who is not explicitly a group admin.. stupid... yes. - 'BypassSecurityGroupManagerCheck': {'valued': false} + 'BypassSecurityGroupManagerCheck': { + 'valued': false + } }, - 'return': { type: 'none' } + 'return': { + type: 'none' + } }, 'removeDistributionGroupMember': { @@ -272,27 +531,33 @@ var o365CommandRegistry = { 'command': 'Remove-DistributionGroupMember {{{arguments}}} -Confirm:$false', 'arguments': { - 'Identity': {}, - 'Member': {}, + 'Identity': {}, + 'Member': {}, // needed if invoking as global admin who is not explicitly a group admin.. stupid... yes. - 'BypassSecurityGroupManagerCheck': {'valued': false} + 'BypassSecurityGroupManagerCheck': { + 'valued': false + } }, - 'return': { type: 'none' } + 'return': { + type: 'none' + } }, /******************************* - * MailContacts - ********************************/ + * MailContacts + ********************************/ 'getMailContact': { 'command': 'Get-MailContact {{{arguments}}} | ConvertTo-Json', 'arguments': { 'Identity': {} }, - 'return': { type: 'json' } + 'return': { + type: 'json' + } }, 'newMailContact': { @@ -300,11 +565,13 @@ var o365CommandRegistry = { 'command': 'New-MailContact -Confirm:$False {{{arguments}}} | ConvertTo-Json', 'arguments': { - 'Name': {}, - 'ExternalEmailAddress': {} + 'Name': {}, + 'ExternalEmailAddress': {} }, - 'return': { type: 'json' } + 'return': { + type: 'json' + } }, 'setMailContact': { @@ -312,13 +579,15 @@ var o365CommandRegistry = { 'command': 'Set-MailContact -Confirm:$False {{{arguments}}}', 'arguments': { - 'Identity': {}, - 'Name': {}, - 'DisplayName': {}, + 'Identity': {}, + 'Name': {}, + 'DisplayName': {}, 'ExternalEmailAddress': {} }, - 'return': { type: 'none' } + 'return': { + type: 'none' + } }, @@ -327,25 +596,30 @@ var o365CommandRegistry = { 'command': 'Remove-MailContact {{{arguments}}} -Confirm:$false', 'arguments': { - 'Identity': {} + 'Identity': {} }, - 'return': { type: 'none' } + 'return': { + type: 'none' + } } }; module.exports.o365CommandRegistry = o365CommandRegistry; /** -* Some example whitelisted commands -* (only permit) what is in the registry -*/ -module.exports.getO365WhitelistedCommands = function() { + * Some example whitelisted commands + * (only permit) what is in the registry + */ +module.exports.getO365WhitelistedCommands = function () { var whitelist = []; for (var cmdName in o365CommandRegistry) { var config = o365CommandRegistry[cmdName]; - var commandStart = config.command.substring(0,config.command.indexOf(' ')).trim(); - whitelist.push({'regex':'^'+commandStart+'\\s+.*', 'flags':'i'}); + var commandStart = config.command.substring(0, config.command.indexOf(' ')).trim(); + whitelist.push({ + 'regex': '^' + commandStart + '\\s+.*', + 'flags': 'i' + }); } return whitelist; -} +}; \ No newline at end of file From 92f601717874b19ff29a2e30b0dc453f5e88ef48 Mon Sep 17 00:00:00 2001 From: Nestor Fedyk Date: Fri, 4 Dec 2020 16:31:28 -0500 Subject: [PATCH 02/14] Added examples, tests and readme. --- README.md | 9 +- example.js | 1 - example_key_auth.js | 81 +++++ example_key_thumb_auth.js | 80 +++++ test/all.js | 690 +++++++++++++++++++------------------- 5 files changed, 513 insertions(+), 348 deletions(-) create mode 100644 example_key_auth.js create mode 100644 example_key_thumb_auth.js diff --git a/README.md b/README.md index 3ad1e3f..30aa5ea 100644 --- a/README.md +++ b/README.md @@ -34,15 +34,18 @@ This script simply exports a few useful pre-defined parameter sets (that one wou 3) From within this project install the necessary npm dependencies for this module, including [stateful-process-command-proxy](https://github.com/bitsofinfo/stateful-process-command-proxy). You can checkout the latter manually and do a ```npm install stateful-process-command-proxy``` -4) Configure ```example.js``` appropriately, in particular the ```initCommands``` for the StatefulProcessCommandProxy; the paths to the items you created via the second step above +4) Configure ```example.js```/```example_key_auth.js```/```examplekey_thumb_auth.js``` appropriately, in particular the ```initCommands``` for the StatefulProcessCommandProxy; the paths to the items you created via the second step above -5) Tweak the group that is fetched at the bottom of ```example.js``` +5) Tweak the group that is fetched at the bottom of ```example.js```/```example_key_auth.js```/```examplekey_thumb_auth.js``` -7) There is also a unit-test (```test\all.js```) for the command registry in ```o365Utils.js``` which gives an example of usage. +7) There is also a unit-test (```test\all.js```) for the command registry in ```o365Utils.js``` which gives an example of usage for all thre possible Exchange connect variations. ### History ``` +v1.1.0 - 2020-12-03 + - Added option for key and thumbprint based Exchange authentication + v1.0.0 - 2016-06-08 - Get-DistributionGroupMember - added "-ResultSize Unlimited" diff --git a/example.js b/example.js index c709275..04f6a16 100644 --- a/example.js +++ b/example.js @@ -1,4 +1,3 @@ -var Promise = require('promise'); var StatefulProcessCommandProxy = require("stateful-process-command-proxy"); var PSCommandService = require('./psCommandService'); var o365Utils = require('./o365Utils'); diff --git a/example_key_auth.js b/example_key_auth.js new file mode 100644 index 0000000..1d471aa --- /dev/null +++ b/example_key_auth.js @@ -0,0 +1,81 @@ +var StatefulProcessCommandProxy = require("stateful-process-command-proxy"); +var PSCommandService = require('./psCommandService'); +var o365Utils = require('./o365Utils'); + + + + +var statefulProcessCommandProxy = new StatefulProcessCommandProxy({ + name: "StatefulProcessCommandProxy", + max: 1, + min: 1, + idleTimeoutMS:120000, + log: function(severity,origin,msg) { + console.log(severity.toUpperCase() + " " +origin+" "+ msg); + }, + + processCommand: 'C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe', + processArgs: ['-Command','-'], + + + processRetainMaxCmdHistory : 20, + processInvalidateOnRegex : { + 'any':[], + 'stdout':[], + 'stderr':[{'regex':'.*error.*'}] + }, + processCwd : null, + processEnvMap : null, + processUid : null, + processGid : null, + + initCommands: o365Utils.getO365PSKeyInitCommands( + 'C:\\pathto\\decryptUtil.ps1', + 'C:\\pathto\\encrypted.credentials', + 'C:\\pathto\\secret.key', + 'C:\\pathto\\certificate', + 'certificatePassword', + '00000000-00000000-00000000-00000000', + 'your.exhange.domain.name', + 10000,30000,60000), + + + validateFunction: function(processProxy) { + var isValid = processProxy.isValid(); + if(!isValid) { + console.log("ProcessProxy.isValid() returns FALSE!"); + } + return isValid; + }, + + + preDestroyCommands: o365Utils.getO365PSKeyDestroyCommands(), + + processCmdWhitelistRegex: o365Utils.getO365WhitelistedCommands(), + + processCmdBlacklistRegex: o365Utils.getO365BlacklistedCommands(), + + autoInvalidationConfig: o365Utils.getO365AutoInvalidationConfig(30000) + +}); + +var myLogFunction = function(severity,origin,message) { + console.log(severity.toUpperCase() + ' ' + origin + ' ' + message); +} + + +/** +* Fetch a group! +*/ +var psCommandService = new PSCommandService(statefulProcessCommandProxy, + o365Utils.o365CommandRegistry, + myLogFunction); + +psCommandService.execute('getDistributionGroup',{'Identity':"someGroupName"}) + .then(function(groupJson) { + console.log(groupJson); + }).catch(function(error) { + console.log(error); + }); + +setTimeout(function(){statefulProcessCommandProxy.shutdown()},80000); diff --git a/example_key_thumb_auth.js b/example_key_thumb_auth.js new file mode 100644 index 0000000..788fbc1 --- /dev/null +++ b/example_key_thumb_auth.js @@ -0,0 +1,80 @@ +var StatefulProcessCommandProxy = require("stateful-process-command-proxy"); +var PSCommandService = require('./psCommandService'); +var o365Utils = require('./o365Utils'); + + + + +var statefulProcessCommandProxy = new StatefulProcessCommandProxy({ + name: "StatefulProcessCommandProxy", + max: 1, + min: 1, + idleTimeoutMS:120000, + log: function(severity,origin,msg) { + console.log(severity.toUpperCase() + " " +origin+" "+ msg); + }, + + processCommand: 'C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe', + processArgs: ['-Command','-'], + + + processRetainMaxCmdHistory : 20, + processInvalidateOnRegex : { + 'any':[], + 'stdout':[], + 'stderr':[{'regex':'.*error.*'}] + }, + processCwd : null, + processEnvMap : null, + processUid : null, + processGid : null, + + initCommands: o365Utils.getO365PSThumbprintInitCommands( + 'C:\\pathto\\decryptUtil.ps1', + 'C:\\pathto\\encrypted.credentials', + 'C:\\pathto\\secret.key', + 'certificatethumbprint', + '00000000-00000000-00000000-00000000', + 'your.exhange.domain.name', + 10000,30000,60000), + + + validateFunction: function(processProxy) { + var isValid = processProxy.isValid(); + if(!isValid) { + console.log("ProcessProxy.isValid() returns FALSE!"); + } + return isValid; + }, + + + preDestroyCommands: o365Utils.getO365PSThumbprintDestroyCommands(), + + processCmdWhitelistRegex: o365Utils.getO365WhitelistedCommands(), + + processCmdBlacklistRegex: o365Utils.getO365BlacklistedCommands(), + + autoInvalidationConfig: o365Utils.getO365AutoInvalidationConfig(30000) + +}); + +var myLogFunction = function(severity,origin,message) { + console.log(severity.toUpperCase() + ' ' + origin + ' ' + message); +} + + +/** +* Fetch a group! +*/ +var psCommandService = new PSCommandService(statefulProcessCommandProxy, + o365Utils.o365CommandRegistry, + myLogFunction); + +psCommandService.execute('getDistributionGroup',{'Identity':"someGroupName"}) + .then(function(groupJson) { + console.log(groupJson); + }).catch(function(error) { + console.log(error); + }); + +setTimeout(function(){statefulProcessCommandProxy.shutdown()},80000); diff --git a/test/all.js b/test/all.js index 044a59c..56e5630 100644 --- a/test/all.js +++ b/test/all.js @@ -1,74 +1,79 @@ var assert = require('assert'); -var Promise = require('promise'); -var fs = require('fs'); var o365Utils = require('../o365Utils'); var PSCommandService = require('../psCommandService'); /** -* IMPORTANT! -* To run this test, you need to configure -* the following 4 variables! -* -* The credentials you are using to access o365 should -* be for a user that is setup as follows @: -* https://bitsofinfo.wordpress.com/2015/01/06/configuring-powershell-for-azure-ad-and-o365-exchange-management/ -* -* @see https://github.com/bitsofinfo/powershell-credential-encryption-tools -*/ + * IMPORTANT! + * To run this test, you need to configure + * the following 4 variables! + * + * The credentials you are using to access o365 should + * be for a user that is setup as follows @: + * https://bitsofinfo.wordpress.com/2015/01/06/configuring-powershell-for-azure-ad-and-o365-exchange-management/ + * + * @see https://github.com/bitsofinfo/powershell-credential-encryption-tools + */ var PATH_TO_DECRYPT_UTIL_SCRIPT = 'C:\\pathto\\decryptUtil.ps1'; var PATH_TO_ENCRYPTED_CREDENTIALS = 'C:\\pathto\\encrypted.credentials'; var PATH_TO_SECRET_KEY = 'C:\\pathto\\secret.key'; var O365_TENANT_DOMAIN_NAME = "somedomain.com"; -describe('test PSCommandService w/ o365CommandRegistry', function() { - - it('Should test all group and mail contact commands then cleanup', function(done) { - - this.timeout(120000); - - var Promise = require('promise'); +/** + * Following variables needed to test Certificate based connection to Exchange server + * + * @see https: //adamtheautomator.com/exchange-online-powershell-mfa/ + * for setup instructions + */ +var PATH_TO_AUTH_CERTIFICATE = 'C:\\pathto\\certificate'; +var CERTIFICATE_PASSWORD = 'xxxxxx'; +var CERTIFICATE_THUMBPRINT = 'xxxxxxxxxx'; +var APPLICATION_ID = '00000000-00000000-00000000-00000000'; +var TENANT_ID = 'your.exhange.domain.name'; + +var testRun = function (done, initCommands, preDestroyCommands) { var StatefulProcessCommandProxy = require("stateful-process-command-proxy"); // configure our proxy/pool of processes - var statefulProcessCommandProxy = new StatefulProcessCommandProxy( - { + var statefulProcessCommandProxy = new StatefulProcessCommandProxy({ name: "o365 RemotePSSession powershell pool", max: 1, min: 1, idleTimeoutMS: 30000, - logFunction: function(severity,origin,msg) { - if (origin != 'Pool') { - console.log(severity.toUpperCase() + " " +origin+" "+ msg); - } + logFunction: function (severity, origin, msg) { + if (origin != 'Pool') { + console.log(severity.toUpperCase() + " " + origin + " " + msg); + } }, processCommand: 'C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe', - processArgs: ['-Command','-'], + processArgs: ['-Command', '-'], + + processRetainMaxCmdHistory: 30, + processInvalidateOnRegex: { + 'any': [{ + 'regex': '.*nomatch.*', + 'flags': 'i' + }], + 'stdout': [{ + 'regex': '.*nomatch.*' + }], + 'stderr': [{ + 'regex': '.*nomatch.*' + }] + }, + processCwd: null, + processEnvMap: null, + processUid: null, + processGid: null, + initCommands: initCommands, - processRetainMaxCmdHistory : 30, - processInvalidateOnRegex : { - 'any':[{'regex':'.*nomatch.*','flags':'i'}], - 'stdout':[{'regex':'.*nomatch.*'}], - 'stderr':[{'regex':'.*nomatch.*'}] - }, - processCwd : null, - processEnvMap : null, - processUid : null, - processGid : null, - - initCommands: o365Utils.getO365PSInitCommands( - PATH_TO_DECRYPT_UTIL_SCRIPT, - PATH_TO_ENCRYPTED_CREDENTIALS, - PATH_TO_SECRET_KEY, - 10000,30000,60000), - - validateFunction: function(processProxy) { + validateFunction: function (processProxy) { return processProxy.isValid(); }, - preDestroyCommands: o365Utils.getO365PSDestroyCommands(), + preDestroyCommands: preDestroyCommands, processCmdBlacklistRegex: o365Utils.getO365BlacklistedCommands(), @@ -76,400 +81,397 @@ describe('test PSCommandService w/ o365CommandRegistry', function() { autoInvalidationConfig: o365Utils.getO365AutoInvalidationConfig(30000) - }); + }); - var myLogFunction = function(severity,origin,message) { - console.log(severity.toUpperCase() + ' ' + origin + ' ' + message); - } + var myLogFunction = function (severity, origin, message) { + console.log(severity.toUpperCase() + ' ' + origin + ' ' + message); + }; - // create our PSCommandService - var psCommandService = new PSCommandService(statefulProcessCommandProxy, - o365Utils.o365CommandRegistry, - myLogFunction); + // create our PSCommandService + var psCommandService = new PSCommandService(statefulProcessCommandProxy, + o365Utils.o365CommandRegistry, + myLogFunction); - // random seed for generated data - var random = "unitTest"+Math.abs(Math.floor(Math.random() * (1000 - 99999 + 1) + 1000)); + // random seed for generated data + var random = "unitTest" + Math.abs(Math.floor(Math.random() * (1000 - 99999 + 1) + 1000)); - var testUserName = "auser-"+random; - var testUserEmail = testUserName+"@"+O365_TENANT_DOMAIN_NAME; + var testUserName = "auser-" + random; + var testUserEmail = testUserName + "@" + O365_TENANT_DOMAIN_NAME; - var testUser2Name = "auser2-"+random; - var testUser2Email = testUser2Name+"@"+O365_TENANT_DOMAIN_NAME; + var testUser2Name = "auser2-" + random; + var testUser2Email = testUser2Name + "@" + O365_TENANT_DOMAIN_NAME; - var testMailContactName = "amailContact-"+random; - var testMailContactEmail = testMailContactName+"@"+O365_TENANT_DOMAIN_NAME; + var testMailContactName = "amailContact-" + random; + var testMailContactEmail = testMailContactName + "@" + O365_TENANT_DOMAIN_NAME; - var testGroupName = "agroup-"+random; - var testGroupEmail = testGroupName+"@"+O365_TENANT_DOMAIN_NAME; + var testGroupName = "agroup-" + random; + var testGroupEmail = testGroupName + "@" + O365_TENANT_DOMAIN_NAME; - // total hack, needed due to deplays on ms side - var sleep = function(milliseconds) { + // total hack, needed due to deplays on ms side + var sleep = function (milliseconds) { var start = new Date().getTime(); var c = 0; for (var i = 0; i < 1e7; i++) { - if ((new Date().getTime() - start) > milliseconds){ - break; + if ((new Date().getTime() - start) > milliseconds) { + break; - } else { - console.log("SLEEP...."); - } + } else { + console.log("SLEEP...."); + } } - } + }; - var evalCmdResult = function(cmdResult, doWithCmdResult) { - if (cmdResult.stderr && cmdResult.stderr.length > 0) { + var evalCmdResult = function (cmdResult, doWithCmdResult) { + if (cmdResult.stderr && cmdResult.stderr.length > 0) { console.log("Stderr received: " + cmdResult.stderr); assert(false); + // otherwise assume ok + } else { + return doWithCmdResult(cmdResult); + } + }; - // otherwise assume ok - } else { - return doWithCmdResult(cmdResult); - } - } - - var evalCmdResults = function(cmdResults, doWithCmdResults) { + var evalCmdResults = function (cmdResults, doWithCmdResults) { var hasErrors = false; - for (var i=0; i 0) { - console.log("Stderr received: " + cmdResult.stderr); - hasErrors = true; + console.log("Stderr received: " + cmdResult.stderr); + hasErrors = true; } } if (hasErrors) { - assert(false); - - // otherwise assume ok + assert(false); + // otherwise assume ok } else { - return doWithCmdResults(cmdResults); + return doWithCmdResults(cmdResults); } - } + }; - var cleanupAndShutdown = function(done,error) { - psCommandService.execute('removeMsolUser', {'UserPrincipalName':testUserEmail }); - psCommandService.execute('removeMsolUser', {'UserPrincipalName':testUser2Email }); - psCommandService.execute('removeDistributionGroup', {'Identity':testGroupEmail }); - psCommandService.execute('removeMailContact', {'Identity':testMailContactEmail }); + var cleanupAndShutdown = function (done, error) { + psCommandService.execute('removeMsolUser', { + 'UserPrincipalName': testUserEmail + }); + psCommandService.execute('removeMsolUser', { + 'UserPrincipalName': testUser2Email + }); + psCommandService.execute('removeDistributionGroup', { + 'Identity': testGroupEmail + }); + psCommandService.execute('removeMailContact', { + 'Identity': testMailContactEmail + }); - // shut it all down - setTimeout(function() { - statefulProcessCommandProxy.shutdown(); - },5000); + // shut it all down + setTimeout(function () { + statefulProcessCommandProxy.shutdown(); + }, 5000); - setTimeout(function() { - if (error) { + setTimeout(function () { + if (error) { done(error); - } else { + } else { done(); - } - - },10000); - - // throw, it will stop the rest of the execution. - if (error) { - throw error; - } - } - - - // #1 create test users that we will use - var promise = psCommandService.executeAll( - [ - {'commandName':'newMsolUser', - 'argMap': { - 'DisplayName':testUserName, - 'UserPrincipalName':testUserEmail - } - }, - {'commandName':'newMsolUser', - 'argMap': { - 'DisplayName':testUser2Name, - 'UserPrincipalName':testUser2Email - } - }, - ]) + } + }, 10000); + + // throw, it will stop the rest of the execution. + if (error) { + throw error; + } + }; + + // #1 create test users that we will use + var promise = psCommandService.executeAll( + [{ + 'commandName': 'newMsolUser', + 'argMap': { + 'DisplayName': testUserName, + 'UserPrincipalName': testUserEmail + } + }, + { + 'commandName': 'newMsolUser', + 'argMap': { + 'DisplayName': testUser2Name, + 'UserPrincipalName': testUser2Email + } + }, + ]) // handle newMsolUsers results... if ok getMsolUsers - .then(function(cmdResults) { - - return evalCmdResults(cmdResults, function(cmdResults) { - try { - assert.equal(2,cmdResults.length); - } catch(e) { - cleanupAndShutdown(done,e); - } - console.log("msolUsers added OK: " + testUserEmail + " & " + testUser2Email); - return psCommandService.executeAll( - [ - {'commandName':'getMsolUser', 'argMap': {'UserPrincipalName':testUserEmail }}, - {'commandName':'getMsolUser', 'argMap': {'UserPrincipalName':testUser2Email }} + .then(function (cmdResults) { + + return evalCmdResults(cmdResults, function (cmdResults) { + try { + assert.equal(2, cmdResults.length); + } catch (e) { + cleanupAndShutdown(done, e); + } + console.log("msolUsers added OK: " + testUserEmail + " & " + testUser2Email); + return psCommandService.executeAll( + [{ + 'commandName': 'getMsolUser', + 'argMap': { + 'UserPrincipalName': testUserEmail + } + }, + { + 'commandName': 'getMsolUser', + 'argMap': { + 'UserPrincipalName': testUser2Email + } + } ]); - }); + }); }) - // handle getMsolUsers result... if ok create distributionGroup - .then(function(cmdResults) { - - return evalCmdResults(cmdResults, function(cmdResults) { + .then(function (cmdResults) { + return evalCmdResults(cmdResults, function (cmdResults) { try { - assert.equal(2,cmdResults.length); - } catch(e) { - cleanupAndShutdown(done,e); + assert.equal(2, cmdResults.length); + } catch (e) { + cleanupAndShutdown(done, e); } - - for (var i=0; i Date: Mon, 7 Dec 2020 10:42:30 -0500 Subject: [PATCH 03/14] Updated readme with Exchange authentication docs --- README.md | 14 ++++++++++++++ o365Utils.js | 8 ++++---- 2 files changed, 18 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 30aa5ea..1f87708 100644 --- a/README.md +++ b/README.md @@ -26,6 +26,20 @@ This provides the PSCommandService class which is a wrapper around [StatefulProc This script simply exports a few useful pre-defined parameter sets (that one would pass to the constructor of StatefulProcessComamndProxy) for the initialization, destruction and auto-invalidation of "powershell" processes who connect to o365 and establish a remote PSSession that will be long lived. (and validate that the session is still legit) +#### Exchange authentication + +`o365Utils.js` init command `getO365PSInitCommands` is using a deprecated authentication [method](https://techcommunity.microsoft.com/t5/exchange-team-blog/modern-auth-and-unattended-scripts-in-exchange-online-powershell/ba-p/1497387) + +Mictosoft has added [Exchange Online PowerShell V2](https://techcommunity.microsoft.com/t5/exchange-team-blog/announcing-general-availability-of-the-exchange-online/ba-p/1436623) that supports cerificate based authentication. + +Full setup is descibed [here](https://adamtheautomator.com/exchange-online-powershell-mfa/) + +Three sets of init commands are availiable as of version `1.1.0`: + +* `getO365PSInitCommands` - backward compatible old basic authentication +* `getO365PSKeyInitCommands` - new Exchange authentication with private key and password +* `getO365PSThumbprintInitCommands` - new Exchange authentication with the thumb print for the certificate + ### Usage 1) Configure your o365 tenant with a user with the appropriate permissions to manage o365 via Powershell. [See this article to get going](https://bitsofinfo.wordpress.com/2015/01/06/configuring-powershell-for-azure-ad-and-o365-exchange-management/) diff --git a/o365Utils.js b/o365Utils.js index c7ffb32..e46aded 100644 --- a/o365Utils.js +++ b/o365Utils.js @@ -80,8 +80,8 @@ module.exports.getO365PSDestroyCommands = function () { * to be able to establish a remote PSSession with o365 * * @see https://github.com/bitsofinfo/powershell-credential-encryption-tools - * @see https: //docs.microsoft.com/en-us/powershell/module/exchange/connect-exchangeonline?view=exchange-ps - * @see https: //adamtheautomator.com/exchange-online-powershell-mfa/#Authenticating_Using_Local_PFX_Certificate + * @see https://docs.microsoft.com/en-us/powershell/module/exchange/connect-exchangeonline?view=exchange-ps + * @see https://adamtheautomator.com/exchange-online-powershell-mfa/#Authenticating_Using_Local_PFX_Certificate * * This function takes the full path to: * - decryptUtil.ps1 from the project above @@ -173,8 +173,8 @@ module.exports.getO365PSKeyDestroyCommands = function () { * to be able to establish a remote PSSession with o365 * * @see https://github.com/bitsofinfo/powershell-credential-encryption-tools - * @see https: //docs.microsoft.com/en-us/powershell/module/exchange/connect-exchangeonline?view=exchange-ps - * @see https: //adamtheautomator.com/exchange-online-powershell-mfa/#Authenticating_Using_Certificate_Thumbprint + * @see https://docs.microsoft.com/en-us/powershell/module/exchange/connect-exchangeonline?view=exchange-ps + * @see https://adamtheautomator.com/exchange-online-powershell-mfa/#Authenticating_Using_Certificate_Thumbprint * * This function takes the full path to: * - decryptUtil.ps1 from the project above From 2b93b4f5431ddfeb3ebf4aed52f2e4cc0f44690a Mon Sep 17 00:00:00 2001 From: bitsofinfo Date: Mon, 7 Dec 2020 09:16:08 -0700 Subject: [PATCH 04/14] package-lock update --- package-lock.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/package-lock.json b/package-lock.json index f75519d..e7ad341 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "powershell-command-executor", - "version": "1.0.0", + "version": "1.1.0", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -464,9 +464,9 @@ "dev": true }, "mustache": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/mustache/-/mustache-4.0.1.tgz", - "integrity": "sha512-yL5VE97+OXn4+Er3THSmTdCFCtx5hHWzrolvH+JObZnUYwuaG7XV+Ch4fR2cIrcYI0tFHxS7iyFYl14bW8y2sA==" + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/mustache/-/mustache-4.1.0.tgz", + "integrity": "sha512-0FsgP/WVq4mKyjolIyX+Z9Bd+3WS8GOwoUTyKXT5cTYMGeauNTi2HPCwERqseC1IHAy0Z7MDZnJBfjabd4O8GQ==" }, "nanoid": { "version": "3.1.12", From bb28b923a0a727b3439025a5d10d199676a66a95 Mon Sep 17 00:00:00 2001 From: Nestor Fedyk Date: Mon, 7 Dec 2020 16:56:46 -0500 Subject: [PATCH 05/14] Added spaces to separate init commands when importing commands into session --- README.md | 3 +++ o365Utils.js | 4 ++-- package-lock.json | 2 +- package.json | 6 +++--- 4 files changed, 9 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 1f87708..8f48ea1 100644 --- a/README.md +++ b/README.md @@ -57,6 +57,9 @@ Three sets of init commands are availiable as of version `1.1.0`: ### History ``` +v1.1.1 - 2020-12-07 + - Fixed bug import of custom commands if provided for certificate based auth + v1.1.0 - 2020-12-03 - Added option for key and thumbprint based Exchange authentication diff --git a/o365Utils.js b/o365Utils.js index e46aded..9707a4d 100644 --- a/o365Utils.js +++ b/o365Utils.js @@ -146,7 +146,7 @@ module.exports.getO365PSKeyInitCommands = function (pathToDecryptUtilScript, '$CertificatePassword = (ConvertTo-SecureString -String "' + authCertificatePassword + '" -AsPlainText -Force)', // #6 connect to exchange - 'Connect-ExchangeOnline -CertificateFilePath $CertificateFilePath -CertificatePassword $CertificatePassword -AppID ' + applicationId + ' -Organization ' + organizationId + psCommandsToImport, + 'Connect-ExchangeOnline -CertificateFilePath $CertificateFilePath -CertificatePassword $CertificatePassword -AppID ' + applicationId + ' -Organization ' + organizationId + ' ' + psCommandsToImport, // #7 cleanup 'Remove-Variable -Force -ErrorAction SilentlyContinue $PSCredential' @@ -235,7 +235,7 @@ module.exports.getO365PSThumbprintInitCommands = function (pathToDecryptUtilScri ('$sessionOpt = New-PSSessionOption -OpenTimeout ' + openTimeout + ' -OperationTimeout ' + operationTimeout + ' -IdleTimeout ' + idleTimeout), // #6 connect to exchange - 'Connect-ExchangeOnline -CertificateThumbPrint ' + certificateThumbPrint + ' -AppID ' + applicationId + ' -Organization ' + organizationId + psCommandsToImport, + 'Connect-ExchangeOnline -CertificateThumbPrint ' + certificateThumbPrint + ' -AppID ' + applicationId + ' -Organization ' + organizationId + ' ' + psCommandsToImport, // #7 cleanup 'Remove-Variable -Force -ErrorAction SilentlyContinue $PSCredential' diff --git a/package-lock.json b/package-lock.json index e7ad341..5245c52 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "powershell-command-executor", - "version": "1.1.0", + "version": "1.1.1", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 1a18673..0f5e3d7 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "powershell-command-executor", - "version": "1.1.0", + "version": "1.1.1", "description": "Provides a registry and gateway for execution powershell commands through long-lived established remote PSSessions via a stateful-process-command-proxy pool of powershell processes", "main": "psCommandService.js", "directories": { @@ -23,9 +23,9 @@ "exec" ], "dependencies": { - "stateful-process-command-proxy": "latest", + "mustache": "^4.1.0", "promise": "latest", - "mustache": "latest" + "stateful-process-command-proxy": "latest" }, "devDependencies": { "mocha": "latest" From 09318fa937e378bfcc38c38ddff48fa3e1505218 Mon Sep 17 00:00:00 2001 From: Nestor Fedyk Date: Wed, 6 Jul 2022 21:18:05 -0400 Subject: [PATCH 06/14] Added support for usage of reserved powershell variables in commands [$null, $true, $false] --- README.md | 3 +++ package.json | 2 +- psCommandService.js | 9 +++++++++ 3 files changed, 13 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 8f48ea1..0e3a6e0 100644 --- a/README.md +++ b/README.md @@ -57,6 +57,9 @@ Three sets of init commands are availiable as of version `1.1.0`: ### History ``` +v1.1.2 - 2022-07-06 + - Added support for usage of reserved powershell variables in commands [$null, $true, $false] + v1.1.1 - 2020-12-07 - Fixed bug import of custom commands if provided for certificate based auth diff --git a/package.json b/package.json index 0f5e3d7..8442f39 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "powershell-command-executor", - "version": "1.1.1", + "version": "1.1.2", "description": "Provides a registry and gateway for execution powershell commands through long-lived established remote PSSessions via a stateful-process-command-proxy pool of powershell processes", "main": "psCommandService.js", "directories": { diff --git a/psCommandService.js b/psCommandService.js index 64e9896..5b7f0f3 100644 --- a/psCommandService.js +++ b/psCommandService.js @@ -3,6 +3,12 @@ module.exports = PSCommandService; var Promise = require('promise'); var Mustache = require('mustache'); +/** + * Reserved variables in Powershell to allow as arguments + * @see https://docs.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_automatic_variables?view=powershell-7.2 + */ +const reservedVariableNames = ['$null', '$false', '$true']; + /** * PSCommandService * @@ -366,6 +372,9 @@ PSCommandService.prototype._sanitize = function(toSanitize,isQuoted) { if (isQuoted) { toSanitize = toSanitize.replace(/(['])/g, "'$1"); + // skip if this is reserved variable name + } else if (reservedVariableNames.includes(toSanitize)) { + // skip it // if not quoted, stop $ and | } else { toSanitize = toSanitize.replace(/([;\$\|\(\)\{\}\[\]\\])/g, "`$1"); From 79555c497ad256b3cbbfc8c5100a6275b64c5a94 Mon Sep 17 00:00:00 2001 From: Nestor Fedyk Date: Thu, 7 Jul 2022 10:51:03 -0400 Subject: [PATCH 07/14] Updated package-lock file --- package-lock.json | 630 ++++++++++++++++------------------------------ 1 file changed, 221 insertions(+), 409 deletions(-) diff --git a/package-lock.json b/package-lock.json index 5245c52..0d1e833 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "powershell-command-executor", - "version": "1.1.1", + "version": "1.1.2", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -17,9 +17,9 @@ "dev": true }, "ansi-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", - "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", "dev": true }, "ansi-styles": { @@ -32,9 +32,9 @@ } }, "anymatch": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.1.tgz", - "integrity": "sha512-mM8522psRCqzV+6LhomX5wgp25YVibjh8Wj23I5RPkPppSVSjyKD2A2mBJmWGa+KN7f2D6LNh9jkBCeyLktzjg==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", + "integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==", "dev": true, "requires": { "normalize-path": "^3.0.0", @@ -42,29 +42,26 @@ } }, "argparse": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", - "dev": true, - "requires": { - "sprintf-js": "~1.0.2" - } + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true }, "asap": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", - "integrity": "sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY=" + "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==" }, "balanced-match": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", - "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", "dev": true }, "binary-extensions": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.1.0.tgz", - "integrity": "sha512-1Yj8h9Q+QDF5FzhMs/c9+6UntbD5MkRfRwac8DoEm9ZfUBZ7tZ55YcGVAzEe4bXsdQHEk+s9S5wsOKVdZrw0tQ==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", + "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", "dev": true }, "brace-expansion": { @@ -92,80 +89,58 @@ "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", "dev": true }, - "buffer-builder": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/buffer-builder/-/buffer-builder-0.2.0.tgz", - "integrity": "sha1-MyLNMH2Cltqx9gRhhZOyYaP63o8=" - }, "camelcase": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", "dev": true }, "chalk": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", - "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "dev": true, "requires": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" + }, + "dependencies": { + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } } }, "chokidar": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.4.3.tgz", - "integrity": "sha512-DtM3g7juCXQxFVSNPNByEC2+NImtBuxQQvWlHunpJIS5Ocr0lG306cC7FCi7cEA0fzmybPUIl4txBIobk1gGOQ==", + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", + "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", "dev": true, "requires": { - "anymatch": "~3.1.1", + "anymatch": "~3.1.2", "braces": "~3.0.2", - "fsevents": "~2.1.2", - "glob-parent": "~5.1.0", + "fsevents": "~2.3.2", + "glob-parent": "~5.1.2", "is-binary-path": "~2.1.0", "is-glob": "~4.0.1", "normalize-path": "~3.0.0", - "readdirp": "~3.5.0" + "readdirp": "~3.6.0" } }, "cliui": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz", - "integrity": "sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA==", + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", "dev": true, "requires": { - "string-width": "^3.1.0", - "strip-ansi": "^5.2.0", - "wrap-ansi": "^5.1.0" - }, - "dependencies": { - "ansi-regex": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", - "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", - "dev": true - }, - "string-width": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", - "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", - "dev": true, - "requires": { - "emoji-regex": "^7.0.1", - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^5.1.0" - } - }, - "strip-ansi": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", - "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", - "dev": true, - "requires": { - "ansi-regex": "^4.1.0" - } - } + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" } }, "color-convert": { @@ -186,34 +161,48 @@ "concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", "dev": true }, "debug": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.2.0.tgz", - "integrity": "sha512-IX2ncY78vDTjZMFUdmsvIRFY2Cf4FnD0wRs+nQwJU8Lu99/tPFdb0VybiiMTPe3I6rQmwsqQqRBvxU+bZ/I8sg==", + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", "dev": true, "requires": { "ms": "2.1.2" + }, + "dependencies": { + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + } } }, "decamelize": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", - "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-4.0.0.tgz", + "integrity": "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==", "dev": true }, "diff": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", - "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-5.0.0.tgz", + "integrity": "sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==", "dev": true }, "emoji-regex": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", - "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "escalade": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", + "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", "dev": true }, "escape-string-regexp": { @@ -222,17 +211,6 @@ "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", "dev": true }, - "esprima": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", - "dev": true - }, - "fifo": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/fifo/-/fifo-2.3.0.tgz", - "integrity": "sha1-GC3o3QYyqkfPaBbZvEMjMX1SWdw=" - }, "fill-range": { "version": "7.0.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", @@ -261,20 +239,20 @@ "fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", "dev": true }, "fsevents": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.1.3.tgz", - "integrity": "sha512-Auw9a4AxqWpa9GUfj370BMPzzyncfBABW8Mab7BGWBYDj4Isgq+cDKtx0i6u9jcX9pQDnswsaaOTgTmA5pEjuQ==", + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", "dev": true, "optional": true }, "generic-pool": { "version": "2.4.4", "resolved": "https://registry.npmjs.org/generic-pool/-/generic-pool-2.4.4.tgz", - "integrity": "sha1-SdkMXo0Tit7woSeAqG5Ky6SIktI=" + "integrity": "sha512-9GicuYkryPAwzKW2Vpfus1zC28ER7mwWEZgY7F+6RqpPMK4N8XpkxzAr4CuPf10NrzW0f/kQ4AmeDVgFhdwjAQ==" }, "get-caller-file": { "version": "2.0.5", @@ -283,9 +261,9 @@ "dev": true }, "glob": { - "version": "7.1.6", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", - "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", + "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==", "dev": true, "requires": { "fs.realpath": "^1.0.0", @@ -294,23 +272,28 @@ "minimatch": "^3.0.4", "once": "^1.3.0", "path-is-absolute": "^1.0.0" + }, + "dependencies": { + "minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + } } }, "glob-parent": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.1.tgz", - "integrity": "sha512-FnI+VGOpnlGHWZxthPGR+QhR78fuiK0sNLkHQv+bL9fQi57lNNdquIbna/WrfROrolq8GK5Ek6BiMwqL/voRYQ==", + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", "dev": true, "requires": { "is-glob": "^4.0.1" } }, - "growl": { - "version": "1.10.5", - "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz", - "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==", - "dev": true - }, "has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", @@ -326,7 +309,7 @@ "inflight": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", "dev": true, "requires": { "once": "^1.3.0", @@ -351,19 +334,19 @@ "is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", "dev": true }, "is-fullwidth-code-point": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", "dev": true }, "is-glob": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", - "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==", + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", "dev": true, "requires": { "is-extglob": "^2.1.1" @@ -381,20 +364,19 @@ "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", "dev": true }, - "isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", + "is-unicode-supported": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", + "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", "dev": true }, "js-yaml": { - "version": "3.14.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.0.tgz", - "integrity": "sha512-/4IbIeHcD9VMHFqDR/gQ7EdZdLimOvW2DdcxFjdyyZ9NsbS+ccrXqVWDtab/lRl5AlUqmpBx8EhPaWR+OtY17A==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", "dev": true, "requires": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" + "argparse": "^2.0.1" } }, "locate-path": { @@ -407,60 +389,69 @@ } }, "log-symbols": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.0.0.tgz", - "integrity": "sha512-FN8JBzLx6CzeMrB0tg6pqlGU1wCrXW+ZXGH481kfsBqer0hToTIiHdjH4Mq8xJUbvATujKCvaREGWpGUionraA==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", + "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", "dev": true, "requires": { - "chalk": "^4.0.0" + "chalk": "^4.1.0", + "is-unicode-supported": "^0.1.0" } }, "minimatch": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.0.1.tgz", + "integrity": "sha512-nLDxIFRyhDblz3qMuq+SoRZED4+miJ/G+tdDrjkkkRnjAsBexeGpgjLEQ0blJy7rHhR2b93rhQY4SvyWu9v03g==", "dev": true, "requires": { - "brace-expansion": "^1.1.7" + "brace-expansion": "^2.0.1" + }, + "dependencies": { + "brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0" + } + } } }, "mocha": { - "version": "8.2.1", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-8.2.1.tgz", - "integrity": "sha512-cuLBVfyFfFqbNR0uUKbDGXKGk+UDFe6aR4os78XIrMQpZl/nv7JYHcvP5MFIAb374b2zFXsdgEGwmzMtP0Xg8w==", + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-10.0.0.tgz", + "integrity": "sha512-0Wl+elVUD43Y0BqPZBzZt8Tnkw9CMUdNYnUsTfOM1vuhJVZL+kiesFYsqwBkEEuEixaiPe5ZQdqDgX2jddhmoA==", "dev": true, "requires": { "@ungap/promise-all-settled": "1.1.2", "ansi-colors": "4.1.1", "browser-stdout": "1.3.1", - "chokidar": "3.4.3", - "debug": "4.2.0", - "diff": "4.0.2", + "chokidar": "3.5.3", + "debug": "4.3.4", + "diff": "5.0.0", "escape-string-regexp": "4.0.0", "find-up": "5.0.0", - "glob": "7.1.6", - "growl": "1.10.5", + "glob": "7.2.0", "he": "1.2.0", - "js-yaml": "3.14.0", - "log-symbols": "4.0.0", - "minimatch": "3.0.4", - "ms": "2.1.2", - "nanoid": "3.1.12", - "serialize-javascript": "5.0.1", + "js-yaml": "4.1.0", + "log-symbols": "4.1.0", + "minimatch": "5.0.1", + "ms": "2.1.3", + "nanoid": "3.3.3", + "serialize-javascript": "6.0.0", "strip-json-comments": "3.1.1", - "supports-color": "7.2.0", - "which": "2.0.2", - "wide-align": "1.1.3", - "workerpool": "6.0.2", - "yargs": "13.3.2", - "yargs-parser": "13.1.2", + "supports-color": "8.1.1", + "workerpool": "6.2.1", + "yargs": "16.2.0", + "yargs-parser": "20.2.4", "yargs-unparser": "2.0.0" } }, "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "dev": true }, "mustache": { @@ -469,9 +460,9 @@ "integrity": "sha512-0FsgP/WVq4mKyjolIyX+Z9Bd+3WS8GOwoUTyKXT5cTYMGeauNTi2HPCwERqseC1IHAy0Z7MDZnJBfjabd4O8GQ==" }, "nanoid": { - "version": "3.1.12", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.1.12.tgz", - "integrity": "sha512-1qstj9z5+x491jfiC4Nelk+f8XBad7LN20PmyWINJEMRSf3wcAjAWysw1qaA8z6NSKe2sjq1hRSDpBH5paCb6A==", + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.3.tgz", + "integrity": "sha512-p1sjXuopFs0xg+fPASzQ28agW1oHD7xDsd9Xkf3T15H3c/cifrFHVwrh74PdoklAPi+i7MdRsE47vm2r6JoB+w==", "dev": true }, "normalize-path": { @@ -483,7 +474,7 @@ "once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", "dev": true, "requires": { "wrappy": "1" @@ -507,12 +498,6 @@ "p-limit": "^3.0.2" } }, - "p-try": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", - "dev": true - }, "path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", @@ -522,13 +507,13 @@ "path-is-absolute": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", "dev": true }, "picomatch": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.2.2.tgz", - "integrity": "sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg==", + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", "dev": true }, "promise": { @@ -549,9 +534,9 @@ } }, "readdirp": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.5.0.tgz", - "integrity": "sha512-cMhu7c/8rdhkHXWsY+osBhfSy0JikwpHK/5+imo+LpeasTF8ouErHrlYkwT0++njiyuDvc7OFY5T3ukvZ8qmFQ==", + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", "dev": true, "requires": { "picomatch": "^2.2.1" @@ -560,13 +545,7 @@ "require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", - "dev": true - }, - "require-main-filename": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", - "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", "dev": true }, "safe-buffer": { @@ -576,54 +555,55 @@ "dev": true }, "serialize-javascript": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-5.0.1.tgz", - "integrity": "sha512-SaaNal9imEO737H2c05Og0/8LUXG7EnsZyMa8MzkmuHoELfT6txuj0cMqRj6zfPKnmQ1yasR4PCJc8x+M4JSPA==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.0.tgz", + "integrity": "sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag==", "dev": true, "requires": { "randombytes": "^2.1.0" } }, - "set-blocking": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", - "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", - "dev": true - }, - "sprintf-js": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", - "dev": true - }, "stateful-process-command-proxy": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/stateful-process-command-proxy/-/stateful-process-command-proxy-1.0.1.tgz", - "integrity": "sha1-dZbKKOyAS6UdoULqLEeWsbsFkoA=", + "integrity": "sha512-G2Hz3LPNfCnJOmZeEHz466cqsfsfIzDUDdCr+C9BFM2TEDpju4a8slFDSZ+B2inaiRCX/uQxCVnL8tb399AYrw==", "requires": { "buffer-builder": "^0.2.0", - "fifo": "^2.3.0", + "fifo": "^2.4.1", "generic-pool": "2.4.4", "promise": "^8.1.0" + }, + "dependencies": { + "buffer-builder": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/buffer-builder/-/buffer-builder-0.2.0.tgz", + "integrity": "sha512-7VPMEPuYznPSoR21NE1zvd2Xna6c/CloiZCfcMXR1Jny6PjX0N4Nsa38zcBFo/FMK+BlA+FLKbJCQ0i2yxp+Xg==" + }, + "fifo": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/fifo/-/fifo-2.4.1.tgz", + "integrity": "sha512-XTbUCNmo54Jav0hcL6VxDuY4x1eCQH61HEF80C2Oww283pfjQ2C8avZeyq4v43sW2S2403kmzssE9j4lbF66Sg==" + } } }, "string-width": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", - "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", "dev": true, "requires": { - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^4.0.0" + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" } }, "strip-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", - "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "dev": true, "requires": { - "ansi-regex": "^3.0.0" + "ansi-regex": "^5.0.1" } }, "strip-json-comments": { @@ -633,9 +613,9 @@ "dev": true }, "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", "dev": true, "requires": { "has-flag": "^4.0.0" @@ -650,209 +630,55 @@ "is-number": "^7.0.0" } }, - "which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, - "requires": { - "isexe": "^2.0.0" - } - }, - "which-module": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", - "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=", - "dev": true - }, - "wide-align": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz", - "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==", - "dev": true, - "requires": { - "string-width": "^1.0.2 || 2" - } - }, "workerpool": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.0.2.tgz", - "integrity": "sha512-DSNyvOpFKrNusaaUwk+ej6cBj1bmhLcBfj80elGk+ZIo5JSkq+unB1dLKEOcNfJDZgjGICfhQ0Q5TbP0PvF4+Q==", + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.2.1.tgz", + "integrity": "sha512-ILEIE97kDZvF9Wb9f6h5aXK4swSlKGUcOEGiIYb2OOu/IrDU9iwj0fD//SsA6E5ibwJxpEvhullJY4Sl4GcpAw==", "dev": true }, "wrap-ansi": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-5.1.0.tgz", - "integrity": "sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q==", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", "dev": true, "requires": { - "ansi-styles": "^3.2.0", - "string-width": "^3.0.0", - "strip-ansi": "^5.0.0" - }, - "dependencies": { - "ansi-regex": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", - "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", - "dev": true - }, - "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "requires": { - "color-convert": "^1.9.0" - } - }, - "color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dev": true, - "requires": { - "color-name": "1.1.3" - } - }, - "color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", - "dev": true - }, - "string-width": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", - "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", - "dev": true, - "requires": { - "emoji-regex": "^7.0.1", - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^5.1.0" - } - }, - "strip-ansi": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", - "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", - "dev": true, - "requires": { - "ansi-regex": "^4.1.0" - } - } + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" } }, "wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", "dev": true }, "y18n": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.1.tgz", - "integrity": "sha512-wNcy4NvjMYL8gogWWYAO7ZFWFfHcbdbE57tZO8e4cbpj8tfUcwrwqSl3ad8HxpYWCdXcJUCeKKZS62Av1affwQ==", + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", "dev": true }, "yargs": { - "version": "13.3.2", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-13.3.2.tgz", - "integrity": "sha512-AX3Zw5iPruN5ie6xGRIDgqkT+ZhnRlZMLMHAs8tg7nRruy2Nb+i5o9bwghAogtM08q1dpr2LVoS8KSTMYpWXUw==", + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", + "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", "dev": true, "requires": { - "cliui": "^5.0.0", - "find-up": "^3.0.0", - "get-caller-file": "^2.0.1", + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", "require-directory": "^2.1.1", - "require-main-filename": "^2.0.0", - "set-blocking": "^2.0.0", - "string-width": "^3.0.0", - "which-module": "^2.0.0", - "y18n": "^4.0.0", - "yargs-parser": "^13.1.2" - }, - "dependencies": { - "ansi-regex": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", - "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", - "dev": true - }, - "find-up": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", - "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", - "dev": true, - "requires": { - "locate-path": "^3.0.0" - } - }, - "locate-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", - "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", - "dev": true, - "requires": { - "p-locate": "^3.0.0", - "path-exists": "^3.0.0" - } - }, - "p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dev": true, - "requires": { - "p-try": "^2.0.0" - } - }, - "p-locate": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", - "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", - "dev": true, - "requires": { - "p-limit": "^2.0.0" - } - }, - "path-exists": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", - "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", - "dev": true - }, - "string-width": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", - "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", - "dev": true, - "requires": { - "emoji-regex": "^7.0.1", - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^5.1.0" - } - }, - "strip-ansi": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", - "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", - "dev": true, - "requires": { - "ansi-regex": "^4.1.0" - } - } + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" } }, "yargs-parser": { - "version": "13.1.2", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.1.2.tgz", - "integrity": "sha512-3lbsNRf/j+A4QuSZfDRA7HRSfWrzO0YjqTJd5kjAq37Zep1CEgaYmrH9Q3GwPiB9cHyd1Y1UwggGhJGoxipbzg==", - "dev": true, - "requires": { - "camelcase": "^5.0.0", - "decamelize": "^1.2.0" - } + "version": "20.2.4", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.4.tgz", + "integrity": "sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA==", + "dev": true }, "yargs-unparser": { "version": "2.0.0", @@ -864,20 +690,6 @@ "decamelize": "^4.0.0", "flat": "^5.0.2", "is-plain-obj": "^2.1.0" - }, - "dependencies": { - "camelcase": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.2.0.tgz", - "integrity": "sha512-c7wVvbw3f37nuobQNtgsgG9POC9qMbNuMQmTCqZv23b6MIz0fcYpBiOlv9gEN/hdLdnZTDQhg6e9Dq5M1vKvfg==", - "dev": true - }, - "decamelize": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-4.0.0.tgz", - "integrity": "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==", - "dev": true - } } }, "yocto-queue": { From 184181b6589eab719ea434d229af583864dae5a8 Mon Sep 17 00:00:00 2001 From: bitsofinfo Date: Thu, 7 Jul 2022 09:06:05 -0600 Subject: [PATCH 08/14] notes --- README.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/README.md b/README.md index 0e3a6e0..67e67b7 100644 --- a/README.md +++ b/README.md @@ -98,3 +98,10 @@ Have a look at these related projects which support and build on top of this mod * https://github.com/bitsofinfo/stateful-process-command-proxy - The core dependency of this module, provides the actual bridging between node.js and a pool of external shell processes * https://github.com/bitsofinfo/powershell-command-executor-ui - Builds on top of powershell-command-executor to provide a simple Node REST API and AngularJS interface for testing the execution of commands in the registry * https://github.com/bitsofinfo/meteor-shell-command-mgr - Small Meteor app that lets you manage/generate a command registry for powershell-command-executor + +## notes + +``` +npm login +npm publish +``` From 9de719076b8892ee47d404b13e74fab4e6e24152 Mon Sep 17 00:00:00 2001 From: Nestor Fedyk Date: Thu, 14 Nov 2024 13:18:13 -0500 Subject: [PATCH 09/14] Added multivalue params support to the powershell commands --- README.md | 3 + package-lock.json | 678 +++++++++++++++++++++++++++++++------------- package.json | 2 +- psCommandService.js | 60 ++-- 4 files changed, 522 insertions(+), 221 deletions(-) diff --git a/README.md b/README.md index 67e67b7..e691eba 100644 --- a/README.md +++ b/README.md @@ -57,6 +57,9 @@ Three sets of init commands are availiable as of version `1.1.0`: ### History ``` +v1.1.3 - 2024-11-14 + - Added support for [multivalued parameters](https://learn.microsoft.com/en-us/exchange/modifying-multivalued-properties-exchange-2013-help) in commands + v1.1.2 - 2022-07-06 - Added support for usage of reserved powershell variables in commands [$null, $true, $false] diff --git a/package-lock.json b/package-lock.json index 0d1e833..a11abd3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,271 +1,382 @@ { "name": "powershell-command-executor", - "version": "1.1.2", - "lockfileVersion": 1, + "version": "1.1.3", + "lockfileVersion": 3, "requires": true, - "dependencies": { - "@ungap/promise-all-settled": { + "packages": { + "": { + "name": "powershell-command-executor", + "version": "1.1.3", + "license": "ISC", + "dependencies": { + "mustache": "^4.1.0", + "promise": "latest", + "stateful-process-command-proxy": "latest" + }, + "devDependencies": { + "mocha": "latest" + } + }, + "node_modules/@ungap/promise-all-settled": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@ungap/promise-all-settled/-/promise-all-settled-1.1.2.tgz", "integrity": "sha512-sL/cEvJWAnClXw0wHk85/2L0G6Sj8UB0Ctc1TEMbKSsmpRosqhwj9gWgFRZSrBr2f9tiXISwNhCPmlfqUqyb9Q==", "dev": true }, - "ansi-colors": { + "node_modules/ansi-colors": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==", - "dev": true + "dev": true, + "engines": { + "node": ">=6" + } }, - "ansi-regex": { + "node_modules/ansi-regex": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true + "dev": true, + "engines": { + "node": ">=8" + } }, - "ansi-styles": { + "node_modules/ansi-styles": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, - "requires": { + "dependencies": { "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "anymatch": { + "node_modules/anymatch": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", "integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==", "dev": true, - "requires": { + "dependencies": { "normalize-path": "^3.0.0", "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" } }, - "argparse": { + "node_modules/argparse": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", "dev": true }, - "asap": { + "node_modules/asap": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==" }, - "balanced-match": { + "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", "dev": true }, - "binary-extensions": { + "node_modules/binary-extensions": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", - "dev": true + "dev": true, + "engines": { + "node": ">=8" + } }, - "brace-expansion": { + "node_modules/brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", "dev": true, - "requires": { + "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, - "braces": { + "node_modules/braces": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", "dev": true, - "requires": { + "dependencies": { "fill-range": "^7.0.1" + }, + "engines": { + "node": ">=8" } }, - "browser-stdout": { + "node_modules/browser-stdout": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", "dev": true }, - "camelcase": { + "node_modules/camelcase": { "version": "6.3.0", "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", - "dev": true + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } }, - "chalk": { + "node_modules/chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "dev": true, - "requires": { + "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/chalk/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, "dependencies": { - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" } }, - "chokidar": { + "node_modules/chokidar": { "version": "3.5.3", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", "dev": true, - "requires": { + "funding": [ + { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + ], + "dependencies": { "anymatch": "~3.1.2", "braces": "~3.0.2", - "fsevents": "~2.3.2", "glob-parent": "~5.1.2", "is-binary-path": "~2.1.0", "is-glob": "~4.0.1", "normalize-path": "~3.0.0", "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" } }, - "cliui": { + "node_modules/cliui": { "version": "7.0.4", "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", "dev": true, - "requires": { + "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.0", "wrap-ansi": "^7.0.0" } }, - "color-convert": { + "node_modules/color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "dev": true, - "requires": { + "dependencies": { "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" } }, - "color-name": { + "node_modules/color-name": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, - "concat-map": { + "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", "dev": true }, - "debug": { + "node_modules/debug": { "version": "4.3.4", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", "dev": true, - "requires": { + "dependencies": { "ms": "2.1.2" }, - "dependencies": { - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true } } }, - "decamelize": { + "node_modules/debug/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "node_modules/decamelize": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-4.0.0.tgz", "integrity": "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==", - "dev": true + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } }, - "diff": { + "node_modules/diff": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/diff/-/diff-5.0.0.tgz", "integrity": "sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==", - "dev": true + "dev": true, + "engines": { + "node": ">=0.3.1" + } }, - "emoji-regex": { + "node_modules/emoji-regex": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", "dev": true }, - "escalade": { + "node_modules/escalade": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", - "dev": true + "dev": true, + "engines": { + "node": ">=6" + } }, - "escape-string-regexp": { + "node_modules/escape-string-regexp": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "dev": true + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } }, - "fill-range": { + "node_modules/fill-range": { "version": "7.0.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", "dev": true, - "requires": { + "dependencies": { "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" } }, - "find-up": { + "node_modules/find-up": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", "dev": true, - "requires": { + "dependencies": { "locate-path": "^6.0.0", "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "flat": { + "node_modules/flat": { "version": "5.0.2", "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", - "dev": true + "dev": true, + "bin": { + "flat": "cli.js" + } }, - "fs.realpath": { + "node_modules/fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", "dev": true }, - "fsevents": { + "node_modules/fsevents": { "version": "2.3.2", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", "dev": true, - "optional": true + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } }, - "generic-pool": { + "node_modules/generic-pool": { "version": "2.4.4", "resolved": "https://registry.npmjs.org/generic-pool/-/generic-pool-2.4.4.tgz", - "integrity": "sha512-9GicuYkryPAwzKW2Vpfus1zC28ER7mwWEZgY7F+6RqpPMK4N8XpkxzAr4CuPf10NrzW0f/kQ4AmeDVgFhdwjAQ==" + "integrity": "sha512-9GicuYkryPAwzKW2Vpfus1zC28ER7mwWEZgY7F+6RqpPMK4N8XpkxzAr4CuPf10NrzW0f/kQ4AmeDVgFhdwjAQ==", + "engines": { + "node": ">= 0.2.0" + } }, - "get-caller-file": { + "node_modules/get-caller-file": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", - "dev": true + "dev": true, + "engines": { + "node": "6.* || 8.* || >= 10.*" + } }, - "glob": { + "node_modules/glob": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", "dev": true, - "requires": { + "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", "inherits": "2", @@ -273,157 +384,214 @@ "once": "^1.3.0", "path-is-absolute": "^1.0.0" }, - "dependencies": { - "minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "requires": { - "brace-expansion": "^1.1.7" - } - } + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "glob-parent": { + "node_modules/glob-parent": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", "dev": true, - "requires": { + "dependencies": { "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" } }, - "has-flag": { + "node_modules/glob/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true + "dev": true, + "engines": { + "node": ">=8" + } }, - "he": { + "node_modules/he": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", - "dev": true + "dev": true, + "bin": { + "he": "bin/he" + } }, - "inflight": { + "node_modules/inflight": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", "dev": true, - "requires": { + "dependencies": { "once": "^1.3.0", "wrappy": "1" } }, - "inherits": { + "node_modules/inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", "dev": true }, - "is-binary-path": { + "node_modules/is-binary-path": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", "dev": true, - "requires": { + "dependencies": { "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" } }, - "is-extglob": { + "node_modules/is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", - "dev": true + "dev": true, + "engines": { + "node": ">=0.10.0" + } }, - "is-fullwidth-code-point": { + "node_modules/is-fullwidth-code-point": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true + "dev": true, + "engines": { + "node": ">=8" + } }, - "is-glob": { + "node_modules/is-glob": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", "dev": true, - "requires": { + "dependencies": { "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" } }, - "is-number": { + "node_modules/is-number": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true + "dev": true, + "engines": { + "node": ">=0.12.0" + } }, - "is-plain-obj": { + "node_modules/is-plain-obj": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", - "dev": true + "dev": true, + "engines": { + "node": ">=8" + } }, - "is-unicode-supported": { + "node_modules/is-unicode-supported": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", - "dev": true + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } }, - "js-yaml": { + "node_modules/js-yaml": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", "dev": true, - "requires": { + "dependencies": { "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" } }, - "locate-path": { + "node_modules/locate-path": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", "dev": true, - "requires": { + "dependencies": { "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "log-symbols": { + "node_modules/log-symbols": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", "dev": true, - "requires": { + "dependencies": { "chalk": "^4.1.0", "is-unicode-supported": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "minimatch": { + "node_modules/minimatch": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.0.1.tgz", "integrity": "sha512-nLDxIFRyhDblz3qMuq+SoRZED4+miJ/G+tdDrjkkkRnjAsBexeGpgjLEQ0blJy7rHhR2b93rhQY4SvyWu9v03g==", "dev": true, - "requires": { + "dependencies": { "brace-expansion": "^2.0.1" }, + "engines": { + "node": ">=10" + } + }, + "node_modules/minimatch/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, "dependencies": { - "brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "dev": true, - "requires": { - "balanced-match": "^1.0.0" - } - } + "balanced-match": "^1.0.0" } }, - "mocha": { + "node_modules/mocha": { "version": "10.0.0", "resolved": "https://registry.npmjs.org/mocha/-/mocha-10.0.0.tgz", "integrity": "sha512-0Wl+elVUD43Y0BqPZBzZt8Tnkw9CMUdNYnUsTfOM1vuhJVZL+kiesFYsqwBkEEuEixaiPe5ZQdqDgX2jddhmoA==", "dev": true, - "requires": { + "dependencies": { "@ungap/promise-all-settled": "1.1.2", "ansi-colors": "4.1.1", "browser-stdout": "1.3.1", @@ -446,225 +614,320 @@ "yargs": "16.2.0", "yargs-parser": "20.2.4", "yargs-unparser": "2.0.0" + }, + "bin": { + "_mocha": "bin/_mocha", + "mocha": "bin/mocha.js" + }, + "engines": { + "node": ">= 14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mochajs" } }, - "ms": { + "node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "dev": true }, - "mustache": { + "node_modules/mustache": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/mustache/-/mustache-4.1.0.tgz", - "integrity": "sha512-0FsgP/WVq4mKyjolIyX+Z9Bd+3WS8GOwoUTyKXT5cTYMGeauNTi2HPCwERqseC1IHAy0Z7MDZnJBfjabd4O8GQ==" + "integrity": "sha512-0FsgP/WVq4mKyjolIyX+Z9Bd+3WS8GOwoUTyKXT5cTYMGeauNTi2HPCwERqseC1IHAy0Z7MDZnJBfjabd4O8GQ==", + "bin": { + "mustache": "bin/mustache" + } }, - "nanoid": { + "node_modules/nanoid": { "version": "3.3.3", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.3.tgz", "integrity": "sha512-p1sjXuopFs0xg+fPASzQ28agW1oHD7xDsd9Xkf3T15H3c/cifrFHVwrh74PdoklAPi+i7MdRsE47vm2r6JoB+w==", - "dev": true + "dev": true, + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } }, - "normalize-path": { + "node_modules/normalize-path": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "dev": true + "dev": true, + "engines": { + "node": ">=0.10.0" + } }, - "once": { + "node_modules/once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", "dev": true, - "requires": { + "dependencies": { "wrappy": "1" } }, - "p-limit": { + "node_modules/p-limit": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", "dev": true, - "requires": { + "dependencies": { "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "p-locate": { + "node_modules/p-locate": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", "dev": true, - "requires": { + "dependencies": { "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "path-exists": { + "node_modules/path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true + "dev": true, + "engines": { + "node": ">=8" + } }, - "path-is-absolute": { + "node_modules/path-is-absolute": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", - "dev": true + "dev": true, + "engines": { + "node": ">=0.10.0" + } }, - "picomatch": { + "node_modules/picomatch": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "dev": true + "dev": true, + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } }, - "promise": { + "node_modules/promise": { "version": "8.1.0", "resolved": "https://registry.npmjs.org/promise/-/promise-8.1.0.tgz", "integrity": "sha512-W04AqnILOL/sPRXziNicCjSNRruLAuIHEOVBazepu0545DDNGYHz7ar9ZgZ1fMU8/MA4mVxp5rkBWRi6OXIy3Q==", - "requires": { + "dependencies": { "asap": "~2.0.6" } }, - "randombytes": { + "node_modules/randombytes": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", "dev": true, - "requires": { + "dependencies": { "safe-buffer": "^5.1.0" } }, - "readdirp": { + "node_modules/readdirp": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", "dev": true, - "requires": { + "dependencies": { "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" } }, - "require-directory": { + "node_modules/require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", - "dev": true + "dev": true, + "engines": { + "node": ">=0.10.0" + } }, - "safe-buffer": { + "node_modules/safe-buffer": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "dev": true + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] }, - "serialize-javascript": { + "node_modules/serialize-javascript": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.0.tgz", "integrity": "sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag==", "dev": true, - "requires": { + "dependencies": { "randombytes": "^2.1.0" } }, - "stateful-process-command-proxy": { + "node_modules/stateful-process-command-proxy": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/stateful-process-command-proxy/-/stateful-process-command-proxy-1.0.1.tgz", "integrity": "sha512-G2Hz3LPNfCnJOmZeEHz466cqsfsfIzDUDdCr+C9BFM2TEDpju4a8slFDSZ+B2inaiRCX/uQxCVnL8tb399AYrw==", - "requires": { - "buffer-builder": "^0.2.0", - "fifo": "^2.4.1", - "generic-pool": "2.4.4", - "promise": "^8.1.0" - }, "dependencies": { - "buffer-builder": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/buffer-builder/-/buffer-builder-0.2.0.tgz", - "integrity": "sha512-7VPMEPuYznPSoR21NE1zvd2Xna6c/CloiZCfcMXR1Jny6PjX0N4Nsa38zcBFo/FMK+BlA+FLKbJCQ0i2yxp+Xg==" - }, - "fifo": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/fifo/-/fifo-2.4.1.tgz", - "integrity": "sha512-XTbUCNmo54Jav0hcL6VxDuY4x1eCQH61HEF80C2Oww283pfjQ2C8avZeyq4v43sW2S2403kmzssE9j4lbF66Sg==" - } + "buffer-builder": "latest", + "fifo": "latest", + "generic-pool": "2.4.4", + "promise": "latest" } }, - "string-width": { + "node_modules/stateful-process-command-proxy/node_modules/buffer-builder": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/buffer-builder/-/buffer-builder-0.2.0.tgz", + "integrity": "sha512-7VPMEPuYznPSoR21NE1zvd2Xna6c/CloiZCfcMXR1Jny6PjX0N4Nsa38zcBFo/FMK+BlA+FLKbJCQ0i2yxp+Xg==" + }, + "node_modules/stateful-process-command-proxy/node_modules/fifo": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/fifo/-/fifo-2.4.1.tgz", + "integrity": "sha512-XTbUCNmo54Jav0hcL6VxDuY4x1eCQH61HEF80C2Oww283pfjQ2C8avZeyq4v43sW2S2403kmzssE9j4lbF66Sg==" + }, + "node_modules/string-width": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", "dev": true, - "requires": { + "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" } }, - "strip-ansi": { + "node_modules/strip-ansi": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "dev": true, - "requires": { + "dependencies": { "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" } }, - "strip-json-comments": { + "node_modules/strip-json-comments": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", - "dev": true + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } }, - "supports-color": { + "node_modules/supports-color": { "version": "8.1.1", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", "dev": true, - "requires": { + "dependencies": { "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" } }, - "to-regex-range": { + "node_modules/to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", "dev": true, - "requires": { + "dependencies": { "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" } }, - "workerpool": { + "node_modules/workerpool": { "version": "6.2.1", "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.2.1.tgz", "integrity": "sha512-ILEIE97kDZvF9Wb9f6h5aXK4swSlKGUcOEGiIYb2OOu/IrDU9iwj0fD//SsA6E5ibwJxpEvhullJY4Sl4GcpAw==", "dev": true }, - "wrap-ansi": { + "node_modules/wrap-ansi": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", "dev": true, - "requires": { + "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, - "wrappy": { + "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", "dev": true }, - "y18n": { + "node_modules/y18n": { "version": "5.0.8", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", - "dev": true + "dev": true, + "engines": { + "node": ">=10" + } }, - "yargs": { + "node_modules/yargs": { "version": "16.2.0", "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", "dev": true, - "requires": { + "dependencies": { "cliui": "^7.0.2", "escalade": "^3.1.1", "get-caller-file": "^2.0.5", @@ -672,31 +935,46 @@ "string-width": "^4.2.0", "y18n": "^5.0.5", "yargs-parser": "^20.2.2" + }, + "engines": { + "node": ">=10" } }, - "yargs-parser": { + "node_modules/yargs-parser": { "version": "20.2.4", "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.4.tgz", "integrity": "sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA==", - "dev": true + "dev": true, + "engines": { + "node": ">=10" + } }, - "yargs-unparser": { + "node_modules/yargs-unparser": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-2.0.0.tgz", "integrity": "sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==", "dev": true, - "requires": { + "dependencies": { "camelcase": "^6.0.0", "decamelize": "^4.0.0", "flat": "^5.0.2", "is-plain-obj": "^2.1.0" + }, + "engines": { + "node": ">=10" } }, - "yocto-queue": { + "node_modules/yocto-queue": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", - "dev": true + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } } } } diff --git a/package.json b/package.json index 8442f39..8e0df32 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "powershell-command-executor", - "version": "1.1.2", + "version": "1.1.3", "description": "Provides a registry and gateway for execution powershell commands through long-lived established remote PSSessions via a stateful-process-command-proxy pool of powershell processes", "main": "psCommandService.js", "directories": { diff --git a/psCommandService.js b/psCommandService.js index 5b7f0f3..b12f89c 100644 --- a/psCommandService.js +++ b/psCommandService.js @@ -360,25 +360,45 @@ PSCommandService.prototype._finalizeParameterValue = function(valueToSet, applyQ return valueToSet; } -PSCommandService.prototype._sanitize = function(toSanitize,isQuoted) { - toSanitize = toSanitize.replace(/([\n\r])/g, ""); // kill true newlines/feeds - - toSanitize = toSanitize.replace(/(\\n)/g, "\\$1"); // kill string based newline attempts - - // escape stuff that could screw up variables - toSanitize = toSanitize.replace(/([`#])/g, "`$1"); - - // if quoted, escape all quotes - if (isQuoted) { - toSanitize = toSanitize.replace(/(['])/g, "'$1"); - - // skip if this is reserved variable name - } else if (reservedVariableNames.includes(toSanitize)) { - // skip it - // if not quoted, stop $ and | - } else { - toSanitize = toSanitize.replace(/([;\$\|\(\)\{\}\[\]\\])/g, "`$1"); +PSCommandService.prototype._sanitize = function (toSanitize, isQuoted) { + toSanitize = toSanitize + .replace(/[\n\r]/g, "") // kill true newlines/feeds + .replace(/\\n/g, "\\$&") // kill string based newline attempts + .replace(/[`#]/g, "`$&"); // escape stuff that could screw up variables + + if (isQuoted) { // if quoted, escape all quotes + toSanitize = toSanitize.replace(/'/g, "'$&"); + } else if ( + !reservedVariableNames.includes(toSanitize) && // skip if this is reserved variable name + multiValuedRegex.test(toSanitize) // process is this is multi-valued parameter + ) { + const extractParams = (str, key) => { + const match = str.match(new RegExp(`${key}="([^;]+)(?:";|"})`, "i")); + return match + ? match[1] + .split(",") + .map((param) => + param.trim().replace(sanitizeRegex, "`$&").replace(/^"|"$/g, "") + ) + : []; + }; + + const addItemsSanitized = extractParams(toSanitize, "Add"); + const removeItemsSanitized = extractParams(toSanitize, "Remove"); + + let result = "@{"; + if (addItemsSanitized.length > 0) { + result += `Add="${addItemsSanitized.join('","')}"`; + } + if (removeItemsSanitized.length > 0) { + if (addItemsSanitized.length > 0) result += "; "; + result += `Remove="${removeItemsSanitized.join('","')}"`; } + result += "}"; + toSanitize = result; + } else { + toSanitize = toSanitize.replace(sanitizeRegex, "`$&"); + } - return toSanitize; -} + return toSanitize; +}; From d529dac6d76eb639fa4eef7f4dd20430ba8e39f7 Mon Sep 17 00:00:00 2001 From: Nestor Fedyk Date: Fri, 22 Nov 2024 10:47:40 -0500 Subject: [PATCH 10/14] Extended testing --- Dockerfile | 24 ++ README.md | 12 + docker-compose.yml | 13 + o365Utils.js | 8 +- package-lock.json | 4 +- package.json | 5 +- psCommandService.js | 33 +-- test/all.js | 1 + test/unit.js | 583 ++++++++++++++++++++++++++++++++++++++++++++ 9 files changed, 663 insertions(+), 20 deletions(-) create mode 100644 Dockerfile create mode 100644 docker-compose.yml create mode 100644 test/unit.js diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..85c1f2d --- /dev/null +++ b/Dockerfile @@ -0,0 +1,24 @@ +FROM mcr.microsoft.com/powershell:7.4-mariner-2.0-arm64 + +ENV APP_ROOT_DIR="/app" + +RUN pwsh -Command Set-PSRepository -Name PSGallery -InstallationPolicy Trusted && \ + pwsh -Command Install-Module -Name ExchangeOnlineManagement -Scope AllUsers -RequiredVersion 3.5.0 && \ + pwsh -Command Set-PSRepository -Name PSGallery -InstallationPolicy Untrusted + +RUN yum install -y nodejs npm + +# Set the working directory in the container +WORKDIR /app + +# Copy package.json and package-lock.json +COPY package*.json ./ + +# Install dependencies +RUN npm install + +# Copy the rest of the application code +COPY . . + +# Command to run tests +CMD ["npm", "run", "test-docker"] \ No newline at end of file diff --git a/README.md b/README.md index e691eba..e4b1084 100644 --- a/README.md +++ b/README.md @@ -7,6 +7,7 @@ Node.js module that provides a registry and gateway for execution of pre-defined * [Overview](#overview) * [Concepts](#concepts) * [Usage](#usage) +* [Testing](#testing) * [History](#history) * [Related tools](#related) @@ -54,9 +55,20 @@ Three sets of init commands are availiable as of version `1.1.0`: 7) There is also a unit-test (```test\all.js```) for the command registry in ```o365Utils.js``` which gives an example of usage for all thre possible Exchange connect variations. +### Testing +Project test can be executed by running `npm test` command on Windows machine. Connection to Exchange Online is required for the tests to pass. + +There is also option to run Docker based tests. You need to configure `environment` variables in `docker-compose.yml` file in order to define connection parameters. To run tests in Docker container, execute `docker-compose run test` command once the configuration is done. + +Exchange online tests will be skipped if the connection is not available. + + ### History ``` +v1.1.4 - 2024-11-22 + - Extended testing and fixed escaping reserved variables and special characters in commands + v1.1.3 - 2024-11-14 - Added support for [multivalued parameters](https://learn.microsoft.com/en-us/exchange/modifying-multivalued-properties-exchange-2013-help) in commands diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..a37e359 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,13 @@ +version: '3' +services: + test: + build: . + volumes: + - .:/app + - /app/node_modules + environment: + - APPLICATION_ID=xxxxxxxxxxxxxxxxxxxxxxxxxxxx + - TENANT=XXXXXXXXXXXXXXXXXXXXXXXXXXXX + - CERTIFICATE_PASSWORD=XXXXXXXXXXXXXXXXXXXXXXXXXXXX + - CERTIFICATE=XXXXXXXXXXXXXXXXXXXXXXXXXXXX + - O365_TENANT_DOMAIN_NAME=sample.com \ No newline at end of file diff --git a/o365Utils.js b/o365Utils.js index 9707a4d..6b0477a 100644 --- a/o365Utils.js +++ b/o365Utils.js @@ -602,7 +602,13 @@ var o365CommandRegistry = { 'return': { type: 'none' } - } + }, + getStatus: { + command: 'Get-ConnectionInformation | ConvertTo-Json', + return: { + type: 'json' + } + }, }; module.exports.o365CommandRegistry = o365CommandRegistry; diff --git a/package-lock.json b/package-lock.json index a11abd3..03f427f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "powershell-command-executor", - "version": "1.1.3", + "version": "1.1.4", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "powershell-command-executor", - "version": "1.1.3", + "version": "1.1.4", "license": "ISC", "dependencies": { "mustache": "^4.1.0", diff --git a/package.json b/package.json index 8e0df32..6e1f02f 100644 --- a/package.json +++ b/package.json @@ -1,13 +1,14 @@ { "name": "powershell-command-executor", - "version": "1.1.3", + "version": "1.1.4", "description": "Provides a registry and gateway for execution powershell commands through long-lived established remote PSSessions via a stateful-process-command-proxy pool of powershell processes", "main": "psCommandService.js", "directories": { "test": "test" }, "scripts": { - "test": "mocha test/all.js" + "test": "mocha test/all.js", + "test-docker": "mocha test/unit.js" }, "keywords": [ "command", diff --git a/psCommandService.js b/psCommandService.js index b12f89c..e195368 100644 --- a/psCommandService.js +++ b/psCommandService.js @@ -366,13 +366,15 @@ PSCommandService.prototype._sanitize = function (toSanitize, isQuoted) { .replace(/\\n/g, "\\$&") // kill string based newline attempts .replace(/[`#]/g, "`$&"); // escape stuff that could screw up variables + const sanitizeRegex = /[;\$\|\(\)\{\}\[\]\\]/g; + const multiValuedRegex = /@\{([^}]*)\}/g; + if (isQuoted) { // if quoted, escape all quotes toSanitize = toSanitize.replace(/'/g, "'$&"); - } else if ( - !reservedVariableNames.includes(toSanitize) && // skip if this is reserved variable name - multiValuedRegex.test(toSanitize) // process is this is multi-valued parameter - ) { + } else if (multiValuedRegex.test(toSanitize)) { + // process is this is multi-valued parameter const extractParams = (str, key) => { + // values must be wrapped in double quotes, so we can split them by comma const match = str.match(new RegExp(`${key}="([^;]+)(?:";|"})`, "i")); return match ? match[1] @@ -385,18 +387,19 @@ PSCommandService.prototype._sanitize = function (toSanitize, isQuoted) { const addItemsSanitized = extractParams(toSanitize, "Add"); const removeItemsSanitized = extractParams(toSanitize, "Remove"); - - let result = "@{"; - if (addItemsSanitized.length > 0) { - result += `Add="${addItemsSanitized.join('","')}"`; - } - if (removeItemsSanitized.length > 0) { - if (addItemsSanitized.length > 0) result += "; "; - result += `Remove="${removeItemsSanitized.join('","')}"`; + if (addItemsSanitized.length > 0 || removeItemsSanitized.length > 0) { + let result = "@{"; + if (addItemsSanitized.length > 0) { + result += `Add="${addItemsSanitized.join('","')}"`; + } + if (removeItemsSanitized.length > 0) { + if (addItemsSanitized.length > 0) result += "; "; + result += `Remove="${removeItemsSanitized.join('","')}"`; + } + result += "}"; + toSanitize = result; } - result += "}"; - toSanitize = result; - } else { + } else if (!reservedVariableNames.includes(toSanitize)) { // skip if this is reserved variable name toSanitize = toSanitize.replace(sanitizeRegex, "`$&"); } diff --git a/test/all.js b/test/all.js index 56e5630..c5dc463 100644 --- a/test/all.js +++ b/test/all.js @@ -463,6 +463,7 @@ describe('test PSCommandService w/ o365CommandRegistry', function () { TENANT_ID, 10000, 30000, 60000), o365Utils.getO365PSDestroyCommands()); }); + // The CertificateThumbprint parameter is supported only in Microsoft Windows. it('Should test all group and mail contact commands then cleanup with Certificate Thumb Print based auth', function (done) { this.timeout(120000); testRun(done, o365Utils.getO365PSInitCommands( diff --git a/test/unit.js b/test/unit.js new file mode 100644 index 0000000..e274f79 --- /dev/null +++ b/test/unit.js @@ -0,0 +1,583 @@ +var assert = require("assert"); +var o365Utils = require("../o365Utils"); +var PSCommandService = require("../psCommandService"); + +/** + * IMPORTANT! + * To run this test, you need to configure + * the following 4 variables! + * + * The credentials you are using to access o365 should + * be for a user that is setup as follows @: + * https://bitsofinfo.wordpress.com/2015/01/06/configuring-powershell-for-azure-ad-and-o365-exchange-management/ + * + * @see https://github.com/bitsofinfo/powershell-credential-encryption-tools + */ +var O365_TENANT_DOMAIN_NAME = + process.env.O365_TENANT_DOMAIN_NAME || "somedomain.com"; + +/** + * Following variables needed to test Certificate based connection to Exchange server + * + * @see https: //adamtheautomator.com/exchange-online-powershell-mfa/ + * for setup instructions + */ +var CERTIFICATE = process.env.CERTIFICATE || "xxxxxxxxxx"; +var CERTIFICATE_PASSWORD = process.env.CERTIFICATE_PASSWORD || "xxxxxxxxxx"; +var APPLICATION_ID = + process.env.APPLICATION_ID || "00000000-00000000-00000000-00000000"; +var TENANT = process.env.TENANT || "your.exhange.domain.name"; + +const initCommands = [ + "$OutputEncoding = [System.Text.Encoding]::GetEncoding(65001)", + '$ErrorView = "NormalView"', // works for powershell 7.1 + '$PSStyle.OutputRendering = "PlainText"', // works for powershell 7.2 and above + '$PSDefaultParameterValues["*:Encoding"] = "utf8"', +]; + +const initExchangeCommands = [ + "$OutputEncoding = [System.Text.Encoding]::GetEncoding(65001)", + '$ErrorView = "NormalView"', // works for powershell 7.1 + '$PSStyle.OutputRendering = "PlainText"', // works for powershell 7.2 and above + '$PSDefaultParameterValues["*:Encoding"] = "utf8"', + + // #1 import some basics + "Import-Module ExchangeOnlineManagement", + // #2 create certificate password + `$CertificatePassword = (ConvertTo-SecureString -String "${CERTIFICATE_PASSWORD}" -AsPlainText -Force)`, + // #3 Import certificate from base64 string + `$Certificate = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2([Convert]::FromBase64String("${CERTIFICATE}"), $CertificatePassword, [System.Security.Cryptography.X509Certificates.X509KeyStorageFlags]"PersistKeySet")`, + // #4 connect to exchange + `Connect-ExchangeOnline -ShowBanner:$false -ShowProgress:$false -Certificate $Certificate -CertificatePassword $CertificatePassword -AppID ${APPLICATION_ID} -Organization ${TENANT}`, +]; + +const preDestroyCommands = [ + "Disconnect-ExchangeOnline -Confirm:$false", + "Remove-Module ExchangeOnlineManagement -Force", +]; + +const myLogFunction = (severity, origin, message) => { + console.log(severity.toUpperCase() + " " + origin + " " + message); +}; +const logFunction = (severity, origin, msg) => { + if (origin != "Pool") { + console.log(severity.toUpperCase() + " " + origin + " " + msg); + } +}; + +const commandRegistry = { + setClipboard: { + command: "Set-Clipboard {{{arguments}}}", + arguments: { + 'Value': { + quoted: false, + }, + }, + return: { + type: 'none' + }, + }, + getClipboard: { + command: "Get-Clipboard", + arguments: {}, + return: { + type: "text", + }, + }, +}; + + +const StatefulProcessCommandProxy = require("stateful-process-command-proxy"); + +const testRun = async (done, initCommands, preDestroyCommands) => { + const statefulProcessCommandProxy = new StatefulProcessCommandProxy({ + name: "o365 RemotePSSession powershell pool", + max: 1, + min: 1, + idleTimeoutMS: 30000, + + logFunction: logFunction, + + processCommand: "pwsh", + processArgs: ["-Command", "-"], + + processRetainMaxCmdHistory: 30, + processInvalidateOnRegex: { + any: [ + { + regex: ".*nomatch.*", + flags: "i", + }, + ], + stdout: [ + { + regex: ".*nomatch.*", + }, + ], + stderr: [ + { + regex: ".*nomatch.*", + }, + ], + }, + processCwd: null, + processEnvMap: null, + processUid: null, + processGid: null, + + initCommands: initCommands, + + validateFunction: (processProxy) => processProxy.isValid(), + + preDestroyCommands: preDestroyCommands, + + processCmdBlacklistRegex: o365Utils.getO365BlacklistedCommands(), + + processCmdWhitelistRegex: o365Utils.getO365WhitelistedCommands(), + + autoInvalidationConfig: o365Utils.getO365AutoInvalidationConfig(30000), + }); + + const psCommandService = new PSCommandService( + statefulProcessCommandProxy, + o365Utils.o365CommandRegistry, + myLogFunction + ); + + const statusResponse = await psCommandService.execute("getStatus", {}); + if (statusResponse.stderr == '' && statusResponse.stdout == '') { + console.log('Skipping test as getStatus command failed'); + statefulProcessCommandProxy.shutdown(); + done(); + } + + const random = + "unitTest" + + Math.abs(Math.floor(Math.random() * (1000 - 99999 + 1) + 1000)); + + const testMailContactName = "amailContact-" + random; + const testMailContactEmail = + testMailContactName + "@" + O365_TENANT_DOMAIN_NAME; + + const testOwnerGroupName = "owneragroup-" + random; + const testOwnerGroupEmail = + testOwnerGroupName + "@" + O365_TENANT_DOMAIN_NAME; + + const testGroupName = "agroup-" + random; + const testGroupEmail = testGroupName + "@" + O365_TENANT_DOMAIN_NAME; + + const testGroupName2 = "agroup-2" + random; + const testGroupEmail2 = testGroupName2 + "@" + O365_TENANT_DOMAIN_NAME; + + const cleanupAndShutdown = async (done, error) => { + await psCommandService.execute("removeDistributionGroup", { + Identity: testOwnerGroupEmail, + }); + await psCommandService.execute("removeDistributionGroup", { + Identity: testGroupEmail, + }); + await psCommandService.execute("removeDistributionGroup", { + Identity: testGroupEmail2, + }); + await psCommandService.execute("removeMailContact", { + Identity: testMailContactEmail, + }); + + setTimeout(() => { + statefulProcessCommandProxy.shutdown(); + }, 5000); + + setTimeout(() => { + if (error) { + done(error); + } else { + done(); + } + }, 10000); + + if (error) { + throw error; + } + }; + + try { + const ownerGroupCreateResult = await psCommandService.execute( + "newDistributionGroup", + { + Name: testOwnerGroupName, + DisplayName: testOwnerGroupName, + PrimarySmtpAddress: testOwnerGroupEmail, + } + ); + assert.equal(ownerGroupCreateResult.stderr, ""); + + const testGroupCreateResult = await psCommandService.execute( + "newDistributionGroup", + { + Name: testGroupName, + DisplayName: testGroupName, + PrimarySmtpAddress: testGroupEmail, + ManagedBy: testOwnerGroupEmail, + } + ); + + assert.equal(testGroupCreateResult.stderr, ""); + assert.equal(testGroupCreateResult.commandName, "newDistributionGroup"); + + const distributionGroup = JSON.parse(testGroupCreateResult.stdout); + try { + assert.equal(testGroupEmail, distributionGroup.PrimarySmtpAddress); + } catch (e) { + cleanupAndShutdown(done, e); + } + console.log( + "distributionGroup created OK: " + distributionGroup.PrimarySmtpAddress + ); + + const testGroup2CreateResult = await psCommandService.execute( + "newDistributionGroup", + { + Name: testGroupName2, + DisplayName: testGroupName2, + PrimarySmtpAddress: testGroupEmail2, + ManagedBy: testOwnerGroupEmail, + } + ); + + assert.equal(testGroup2CreateResult.stderr, ""); + assert.equal(testGroup2CreateResult.commandName, "newDistributionGroup"); + + const distributionGroup2 = JSON.parse(testGroup2CreateResult.stdout); + try { + assert.equal(testGroupEmail2, distributionGroup2.PrimarySmtpAddress); + } catch (e) { + cleanupAndShutdown(done, e); + } + console.log( + "distributionGroup created OK: " + distributionGroup2.PrimarySmtpAddress + ); + + await psCommandService.executeAll([ + { + commandName: "addDistributionGroupMember", + argMap: { + Identity: testGroupEmail, + Member: testGroupEmail2, + BypassSecurityGroupManagerCheck: null, + }, + }, + { + commandName: "addDistributionGroupMember", + argMap: { + Identity: testGroupEmail, + Member: testOwnerGroupEmail, + BypassSecurityGroupManagerCheck: null, + }, + }, + ]); + console.log("distributionGroupMembers added OK"); + + const groupMembersResult = await psCommandService.execute( + "getDistributionGroupMember", + { + Identity: testGroupEmail, + } + ); + + assert.equal(groupMembersResult.stderr, ""); + assert.equal(groupMembersResult.commandName, "getDistributionGroupMember"); + + var members = JSON.parse(groupMembersResult.stdout); + try { + assert.equal(members.length, 2); + } catch (e) { + cleanupAndShutdown(done, e); + } + console.log("distributionGroup members fetched OK: " + members.length); + const removeResult = await psCommandService.execute( + "removeDistributionGroupMember", + { + Identity: testGroupEmail, + Member: testGroupEmail2, + } + ); + assert.equal(removeResult.stderr, ""); + assert.equal(removeResult.commandName, "removeDistributionGroupMember"); + + console.log(`distributionGroupMember (${testGroupEmail2}) removed OK`); + + const refetchGroupMembersResult = await psCommandService.execute( + "getDistributionGroupMember", + { + Identity: testGroupEmail, + } + ); + var members = JSON.parse("[" + refetchGroupMembersResult.stdout + "]"); + try { + assert.equal(members.length, 1); + assert.equal(members[0].PrimarySmtpAddress, testOwnerGroupEmail); + } catch (e) { + return cleanupAndShutdown(done, e); + } + console.log( + "getDistributionGroupMember fetched OK: only owner group remains " + + members.length + ); + const contactResult = await psCommandService.execute("newMailContact", { + Name: testMailContactName, + ExternalEmailAddress: testMailContactEmail, + }); + + assert.equal(contactResult.stderr, ""); + assert.equal(contactResult.commandName, "newMailContact"); + + console.log("newMailContact added OK: " + testMailContactEmail); + const getContactResult = await psCommandService.execute("getMailContact", { + Identity: testMailContactEmail, + }); + + var contact = JSON.parse(getContactResult.stdout); + try { + assert.equal(testMailContactEmail, contact.PrimarySmtpAddress); + } catch (e) { + cleanupAndShutdown(done, e); + } + console.log("getMailContact fetched OK: " + testMailContactEmail); + await psCommandService.execute("addDistributionGroupMember", { + Identity: testGroupEmail, + Member: testMailContactEmail, + }); + + console.log( + "addDistributionGroupMember mailContact added OK: " + testMailContactEmail + ); + const getGroupMembersResult = await psCommandService.execute( + "getDistributionGroupMember", + { + Identity: testGroupEmail, + } + ); + + var members = JSON.parse(getGroupMembersResult.stdout); + try { + assert.equal(members.length, 2); + } catch (e) { + cleanupAndShutdown(done, e); + } + console.log( + "getDistributionGroupMember fetched OK: one mail contact and one group exist " + + members.length + ); + await psCommandService.execute("removeDistributionGroup", { + Identity: testGroupEmail, + }); + + console.log("distributionGroup removed OK: " + testGroupEmail); + + done(); + } catch (error) { + cleanupAndShutdown(done, error); + } +}; + +describe("test PSCommandService w/ o365CommandRegistry", function () { + it("Should test all group and mail contact commands then cleanup with Certificate based auth", function (done) { + this.timeout(120000); + testRun(done, initExchangeCommands, preDestroyCommands); + }); + it("Should test whitelist", async function () { + this.timeout(10000); + const statefulProcessCommandProxy = new StatefulProcessCommandProxy({ + name: "Powershell pool", + max: 1, + min: 1, + idleTimeoutMS: 30000, + + logFunction: logFunction, + processCommand: "pwsh", + processArgs: ["-Command", "-"], + processRetainMaxCmdHistory: 30, + processCwd: null, + processEnvMap: null, + processUid: null, + processGid: null, + initCommands: initCommands, + processCmdWhitelistRegex: [{ regex: '^Set-Clipboard\\s+.*', flags: 'i' }], + validateFunction: (processProxy) => processProxy.isValid(), + }); + + const psCommandService = new PSCommandService( + statefulProcessCommandProxy, + commandRegistry, + myLogFunction + ); + + try { + const value = "'test clipboard value'"; + const setResult = await psCommandService.execute("setClipboard", { + Value: value, + }); + assert.equal(setResult.stderr, ""); + try { + await psCommandService.execute("getClipboard", {}); + } catch (e) { + assert.match(e.message, /Command cannot be executed it does not match our set of whitelisted commands/); + } + + setTimeout(() => { + statefulProcessCommandProxy.shutdown(); + }, 5000); + + return; + } catch (e) { + setTimeout(() => { + statefulProcessCommandProxy.shutdown(); + }, 5000); + throw e; + } + }); + it("Should test blacklist", async function () { + this.timeout(10000); + const statefulProcessCommandProxy = new StatefulProcessCommandProxy({ + name: "Powershell pool", + max: 1, + min: 1, + idleTimeoutMS: 30000, + + logFunction: logFunction, + processCommand: "pwsh", + processArgs: ["-Command", "-"], + processRetainMaxCmdHistory: 30, + processCwd: null, + processEnvMap: null, + processUid: null, + processGid: null, + initCommands: initCommands, + processCmdBlacklistRegex: o365Utils.getO365BlacklistedCommands(), + validateFunction: (processProxy) => processProxy.isValid(), + }); + const extendedCommandRegistry = {...commandRegistry, ...{ + getHistory: { + command: "Get-History", + arguments: {}, + return: { + type: "text", + }, + }, + }}; + + const psCommandService = new PSCommandService( + statefulProcessCommandProxy, + extendedCommandRegistry, + myLogFunction + ); + + const allowResult = await psCommandService.execute("getClipboard", {}); + assert.equal(allowResult.stderr, ""); + assert.equal(allowResult.stdout, ""); + try { + await psCommandService.execute("getHistory", {}); + } catch (e) { + assert.match(e.message, /Command cannot be executed as it matches a blacklist regex pattern/); + } + + try { + setTimeout(() => { + statefulProcessCommandProxy.shutdown(); + }, 5000); + + return; + } catch (e) { + setTimeout(() => { + statefulProcessCommandProxy.shutdown(); + }, 5000); + throw e; + } + }); + it("Should test validation", async function () { + this.timeout(10000); + const statefulProcessCommandProxy = new StatefulProcessCommandProxy({ + name: "Powershell pool", + max: 1, + min: 1, + idleTimeoutMS: 30000, + + logFunction: logFunction, + processCommand: "pwsh", + processArgs: ["-Command", "-"], + processRetainMaxCmdHistory: 30, + processCwd: null, + processEnvMap: null, + processUid: null, + processGid: null, + initCommands: initCommands, + validateFunction: (processProxy) => processProxy.isValid(), + }); + + const psCommandService = new PSCommandService( + statefulProcessCommandProxy, + commandRegistry, + myLogFunction + ); + + const assertClipboard = async (value) => { + const setResult = await psCommandService.execute("setClipboard", { + Value: value, + }); + assert.equal(setResult.stderr, ""); + const getResult = await psCommandService.execute("getClipboard", {}); + assert.equal(getResult.stderr, ""); + return getResult; + } + + try { + // non quoted value + var value = "plain text in clipboard"; + var setResult = await psCommandService.execute("setClipboard", { + Value: value, + }); + assert.equal(setResult.stdout, ""); + assert.match(setResult.stderr, /A positional parameter cannot be found that accepts argument/); + await psCommandService.execute("getClipboard", {}); + // simple multi param value + var res = await assertClipboard('@{add="test","test2";remove="test3","test4"}'); + assert.equal(res.stdout, "System.Collections.Hashtable"); + // multi params value with unsupported keys + value = '@{add="test","test2";remove="test3","test4";fake="test5","test6"}'; + setResult = await psCommandService.execute("setClipboard", { + Value: value, + }); + assert.equal(setResult.command, 'Set-Clipboard -Value @{Add="test","test2"; Remove="test3","test4"} '); + assert.equal(setResult.stderr, ""); + getResult = await psCommandService.execute("getClipboard", {}); + assert.equal(getResult.stderr, ""); + assert.equal(getResult.stdout, "System.Collections.Hashtable"); + // sample quoted test + res = await assertClipboard("'sample text'"); + assert.equal(res.stdout, "sample text"); + + // espcaped quotes + value = "'; Get-ChildItem C:\; '"; + setResult = await psCommandService.execute("setClipboard", { + Value: value, + }); + assert.equal(setResult.stderr, ""); + getResult = await psCommandService.execute("getClipboard", {}); + assert.equal(getResult.stdout, "`; Get-ChildItem C:`;"); + // reserved variable + var res = await assertClipboard('$true'); + assert.equal(res.stdout, "True"); + + setTimeout(() => { + statefulProcessCommandProxy.shutdown(); + }, 5000); + + return; + } catch (e) { + setTimeout(() => { + statefulProcessCommandProxy.shutdown(); + }, 5000); + throw e; + } + }); +}); From ad374a063d7864a4ae8cac7549bd6a9c9228285d Mon Sep 17 00:00:00 2001 From: Nestor Fedyk Date: Fri, 19 Sep 2025 11:30:07 -0400 Subject: [PATCH 11/14] Fixed argument value bleed into the next empty argument (#19) * Fixed bleeding value of the command argument into the next empty valued argument --- psCommandService.js | 2 +- test/unit.js | 72 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 73 insertions(+), 1 deletion(-) diff --git a/psCommandService.js b/psCommandService.js index e195368..12cd4ef 100644 --- a/psCommandService.js +++ b/psCommandService.js @@ -304,7 +304,7 @@ PSCommandService.prototype._generateCommand = function(commandConfig, argument2V var passedArgValue = passedArgValues[i]; - var valueToSet; + var valueToSet = ""; if (passedArgValue && passedArgValue != 'undefined') { valueToSet = passedArgValue; diff --git a/test/unit.js b/test/unit.js index e274f79..9638f9f 100644 --- a/test/unit.js +++ b/test/unit.js @@ -84,6 +84,29 @@ const commandRegistry = { type: "text", }, }, + setContent: { + command: "Set-Content {{{arguments}}}", + arguments: { + 'Path': {}, + 'Value': {}, + 'Filter': {}, + }, + }, + getContent: { + command: "Get-Content {{{arguments}}}", + arguments: { + 'Path': {}, + }, + return: { + type: "text", + }, + }, + removeItem: { + command: "Remove-Item {{{arguments}}}", + arguments: { + 'Path': {}, + }, + }, }; @@ -580,4 +603,53 @@ describe("test PSCommandService w/ o365CommandRegistry", function () { throw e; } }); + it("Should test value bleeding", async function () { + this.timeout(10000); + const statefulProcessCommandProxy = new StatefulProcessCommandProxy({ + name: "Powershell pool", + max: 1, + min: 1, + idleTimeoutMS: 30000, + + logFunction: logFunction, + processCommand: "pwsh", + processArgs: ["-Command", "-"], + processRetainMaxCmdHistory: 30, + processCwd: null, + processEnvMap: null, + processUid: null, + processGid: null, + initCommands: initCommands, + validateFunction: (processProxy) => processProxy.isValid(), + }); + + const psCommandService = new PSCommandService( + statefulProcessCommandProxy, + commandRegistry, + myLogFunction + ); + try { + const newResult = await psCommandService.execute("setContent", { + Path: "./test.txt", + Value: "Test", + Filter: "" + }); + assert.equal(newResult.command.trim(), "Set-Content -Path './test.txt' -Value 'Test'"); + assert.equal(newResult.stderr, ""); + const getResult = await psCommandService.execute("getContent", { + Path: "./test.txt", + }); + assert.equal(getResult.stderr, ""); + assert.equal(getResult.stdout, "Test"); + } catch (e) { + assert.fail(e); + } finally { + await psCommandService.execute("removeItem", { + Path: "./test.txt", + }); + setTimeout(() => { + statefulProcessCommandProxy.shutdown(); + }, 5000); + } + }); }); From 2c07456cfd874f956ba52674d4080dffd0bbac3e Mon Sep 17 00:00:00 2001 From: Nestor Fedyk Date: Fri, 19 Sep 2025 11:48:16 -0400 Subject: [PATCH 12/14] Added support for the empty arguments passed to the command --- README.md | 39 ++++++++++++++++++++++++++++++++ psCommandService.js | 3 ++- test/unit.js | 54 +++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 95 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index e4b1084..01d5f7f 100644 --- a/README.md +++ b/README.md @@ -62,10 +62,48 @@ There is also option to run Docker based tests. You need to configure `environme Exchange online tests will be skipped if the connection is not available. +### Empty Argument Values Support + +As of version 1.1.5, the module now supports passing empty string values to PowerShell command arguments when explicitly configured. This is useful for optional parameters that need to be passed as empty values rather than omitted entirely. + +To enable empty value support for a command argument, set the `empty` property to `true` in the argument configuration: + +```javascript +const commandRegistry = { + 'myCommand': { + command: "Get-Content {{{arguments}}}", + arguments: { + 'Path': {}, + 'Filter': { + empty: true, // Allow empty string values + }, + }, + return: { + type: "text", + } + } +}; +``` + +When `empty: true` is set, the argument will accept empty string values and include them in the generated PowerShell command: + +```javascript +// This will generate: Get-Content -Path './test.txt' -Filter '' +await psCommandService.execute("myCommand", { + Path: "./test.txt", + Filter: "" // Empty string value is now allowed +}); +``` + + ### History ``` +v1.1.5 - 2025-09-19 + - Added support for empty argument values in commands via 'empty' property + - Fixed argument value bleed into the next empty argument + v1.1.4 - 2024-11-22 - Extended testing and fixed escaping reserved variables and special characters in commands @@ -75,6 +113,7 @@ v1.1.3 - 2024-11-14 v1.1.2 - 2022-07-06 - Added support for usage of reserved powershell variables in commands [$null, $true, $false] + v1.1.1 - 2020-12-07 - Fixed bug import of custom commands if provided for certificate based auth diff --git a/psCommandService.js b/psCommandService.js index 12cd4ef..11bac6d 100644 --- a/psCommandService.js +++ b/psCommandService.js @@ -282,6 +282,7 @@ PSCommandService.prototype._generateCommand = function(commandConfig, argument2V if ((argument.hasOwnProperty('valued') ? argument.valued : true)) { var isQuoted = (argument.hasOwnProperty('quoted') ? argument.quoted : true); + var isEmpty = (argument.hasOwnProperty('empty') ? argument.empty : false); var passedArgValues = argument2ValueMap[argumentName]; if (!(passedArgValues instanceof Array)) { @@ -314,7 +315,7 @@ PSCommandService.prototype._generateCommand = function(commandConfig, argument2V } // append the value - if (valueToSet && valueToSet.trim().length > 0) { + if (valueToSet != null && valueToSet != undefined && (isEmpty || valueToSet.trim().length > 0)) { // sanitize valueToSet = this._sanitize(valueToSet,isQuoted); diff --git a/test/unit.js b/test/unit.js index 9638f9f..54867e0 100644 --- a/test/unit.js +++ b/test/unit.js @@ -96,6 +96,9 @@ const commandRegistry = { command: "Get-Content {{{arguments}}}", arguments: { 'Path': {}, + 'Filter': { + empty: true, + }, }, return: { type: "text", @@ -652,4 +655,55 @@ describe("test PSCommandService w/ o365CommandRegistry", function () { }, 5000); } }); + it("Should test empty value support", async function () { + this.timeout(10000); + const statefulProcessCommandProxy = new StatefulProcessCommandProxy({ + name: "Powershell pool", + max: 1, + min: 1, + idleTimeoutMS: 30000, + + logFunction: logFunction, + processCommand: "pwsh", + processArgs: ["-Command", "-"], + processRetainMaxCmdHistory: 30, + processCwd: null, + processEnvMap: null, + processUid: null, + processGid: null, + initCommands: initCommands, + validateFunction: (processProxy) => processProxy.isValid(), + }); + + const psCommandService = new PSCommandService( + statefulProcessCommandProxy, + commandRegistry, + myLogFunction + ); + try { + const newResult = await psCommandService.execute("setContent", { + Path: "./test.txt", + Value: "Test", + Filter: "" + }); + assert.equal(newResult.command.trim(), "Set-Content -Path './test.txt' -Value 'Test'"); + assert.equal(newResult.stderr, ""); + const getResult = await psCommandService.execute("getContent", { + Path: "./test.txt", + Filter: "" + }); + assert.equal(getResult.command.trim(), "Get-Content -Path './test.txt' -Filter ''"); + assert.equal(getResult.stderr, ""); + assert.equal(getResult.stdout, "Test"); + } catch (e) { + assert.fail(e); + } finally { + await psCommandService.execute("removeItem", { + Path: "./test.txt", + }); + setTimeout(() => { + statefulProcessCommandProxy.shutdown(); + }, 5000); + } + }); }); From 3a02d0f369d0d1df4e2da86575c5851809c0d789 Mon Sep 17 00:00:00 2001 From: Nestor Fedyk Date: Fri, 19 Sep 2025 11:59:55 -0400 Subject: [PATCH 13/14] Update psCommandService.js Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- psCommandService.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/psCommandService.js b/psCommandService.js index 11bac6d..05aee60 100644 --- a/psCommandService.js +++ b/psCommandService.js @@ -315,7 +315,7 @@ PSCommandService.prototype._generateCommand = function(commandConfig, argument2V } // append the value - if (valueToSet != null && valueToSet != undefined && (isEmpty || valueToSet.trim().length > 0)) { + if (valueToSet !== null && valueToSet !== undefined && (isEmpty || valueToSet.trim().length > 0)) { // sanitize valueToSet = this._sanitize(valueToSet,isQuoted); From 73abe7c1dbb692e1465dca3842a8a15ec39a3785 Mon Sep 17 00:00:00 2001 From: Nestor Fedyk Date: Fri, 19 Sep 2025 12:00:04 -0400 Subject: [PATCH 14/14] Update test/unit.js Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- test/unit.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/unit.js b/test/unit.js index 54867e0..8eeb25d 100644 --- a/test/unit.js +++ b/test/unit.js @@ -700,7 +700,7 @@ describe("test PSCommandService w/ o365CommandRegistry", function () { } finally { await psCommandService.execute("removeItem", { Path: "./test.txt", - }); + }); setTimeout(() => { statefulProcessCommandProxy.shutdown(); }, 5000);