-
-
Notifications
You must be signed in to change notification settings - Fork 179
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Fix win execution with whitespace in username #351
This commit addresses the issue where scripts fail to execute on Windows environments with usernames containing spaces. The problem stemmed from PowerShell and cmd shell's handling of spaces in quoted arguments. The solution involves encoding PowerShell commands before execution, which mitigates the quoting issues previously causing script failures. This approach is now integrated into the execution flow, ensuring that commands are correctly handled irrespective of user names or other variables that may include spaces. Changes: - Implement encoding for PowerShell commands to handle spaces in usernames and other similar scenarios. - Update script documentation URLs to reflect changes in directory structure. Fixes #351
- Loading branch information
1 parent
1d7cafc
commit a334320
Showing
13 changed files
with
241 additions
and
50 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
32 changes: 32 additions & 0 deletions
32
...n/CommandDefinition/Commands/PowerShellInvoke/EncodedPowerShellInvokeCmdCommandCreator.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
import type { PowerShellInvokeShellCommandCreator } from './PowerShellInvokeShellCommandCreator'; | ||
|
||
/** | ||
Encoding PowerShell commands resolve issues with quote handling. | ||
There are known problems with PowerShell's handling of double quotes in command line arguments: | ||
- Quote stripping in PowerShell command line arguments: https://web.archive.org/web/20240507102706/https://stackoverflow.com/questions/6714165/powershell-stripping-double-quotes-from-command-line-arguments | ||
- privacy.sexy double quotes issue when calling PowerShell from command line: https://web.archive.org/web/20240507102841/https://github.com/undergroundwires/privacy.sexy/issues/351 | ||
- Challenges with single quotes in PowerShell command line: https://web.archive.org/web/20240507102047/https://stackoverflow.com/questions/20958388/command-line-escaping-single-quote-for-powershell | ||
Using the `EncodedCommand` parameter is recommended by Microsoft for handling | ||
complex quoting scenarios. This approach helps avoid issues by encoding the entire | ||
command as a Base64 string: | ||
- Microsoft's documentation on using the `EncodedCommand` parameter: https://web.archive.org/web/20240507102733/https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_powershell_exe?view=powershell-5.1#-encodedcommand-base64encodedcommand | ||
*/ | ||
export class EncodedPowerShellInvokeCmdCommandCreator | ||
implements PowerShellInvokeShellCommandCreator { | ||
public createCommandToInvokePowerShell(powerShellScript: string): string { | ||
return generateEncodedPowershellCommand(powerShellScript); | ||
} | ||
} | ||
|
||
function generateEncodedPowershellCommand(powerShellScript: string): string { | ||
const encodedCommand = encodeForPowershellExecution(powerShellScript); | ||
return `PowerShell -EncodedCommand ${encodedCommand}`; | ||
} | ||
|
||
function encodeForPowershellExecution(script: string): string { | ||
// The string must be formatted using UTF-16LE character encoding, see: https://web.archive.org/web/20240507102733/https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_powershell_exe?view=powershell-5.1#-encodedcommand-base64encodedcommand | ||
const buffer = Buffer.from(script, 'utf16le'); | ||
return buffer.toString('base64'); | ||
} |
3 changes: 3 additions & 0 deletions
3
...cution/CommandDefinition/Commands/PowerShellInvoke/PowerShellInvokeShellCommandCreator.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
export interface PowerShellInvokeShellCommandCreator { | ||
createCommandToInvokePowerShell(powerShellCommand: string): string; | ||
} |
14 changes: 0 additions & 14 deletions
14
.../CodeRunner/Execution/CommandDefinition/Commands/ShellArgument/CmdShellArgumentEscaper.ts
This file was deleted.
Oops, something went wrong.
15 changes: 15 additions & 0 deletions
15
...odeRunner/Execution/CommandDefinition/Commands/ShellArgument/PowerShellArgumentEscaper.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
import type { ShellArgumentEscaper } from './ShellArgumentEscaper'; | ||
|
||
export class PowerShellArgumentEscaper implements ShellArgumentEscaper { | ||
public escapePathArgument(pathArgument: string): string { | ||
return powerShellPathArgumentEscape(pathArgument); | ||
} | ||
} | ||
|
||
function powerShellPathArgumentEscape(pathArgument: string): string { | ||
// - Encloses the path in single quotes to handle spaces and most special characters. | ||
// - Single quotes are used in PowerShell to ensure the string is treated as a literal string. | ||
// - Paths in Windows can include single quotes ('), so any internal single quotes are escaped | ||
// using double quotes. | ||
return `'${pathArgument.replace(/'/g, "''")}'`; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
51 changes: 51 additions & 0 deletions
51
...mandDefinition/Commands/PowerShellInvoke/EncodedPowerShellInvokeCmdCommandCreator.spec.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
import { describe, it, expect } from 'vitest'; | ||
import { EncodedPowerShellInvokeCmdCommandCreator } from '@/infrastructure/CodeRunner/Execution/CommandDefinition/Commands/PowerShellInvoke/EncodedPowerShellInvokeCmdCommandCreator'; | ||
|
||
describe('EncodedPowerShellInvokeCmdCommandCreator', () => { | ||
describe('createCommandToInvokePowerShell', () => { | ||
it('starts with PowerShell base command', () => { | ||
// arrange | ||
const sut = new EncodedPowerShellInvokeCmdCommandCreator(); | ||
// act | ||
const command = sut.createCommandToInvokePowerShell('non-important-command'); | ||
// assert | ||
expect(command.startsWith('PowerShell ')).to.equal(true); | ||
}); | ||
it('includes encoded command as parameter', () => { | ||
// arrange | ||
const expectedParameterName = '-EncodedCommand'; | ||
const sut = new EncodedPowerShellInvokeCmdCommandCreator(); | ||
// act | ||
const command = sut.createCommandToInvokePowerShell('non-important-command'); | ||
// assert | ||
const args = parsePowerShellArgs(command); | ||
const parameterNames = [...args.keys()]; | ||
expect(parameterNames).to.include(expectedParameterName); | ||
}); | ||
it('correctly encode the command as utf16le base64', () => { | ||
// arrange | ||
const givenCode = 'Write-Output "Today is $(Get-Date -Format \'dddd, MMMM dd\')."'; | ||
const expectedEncodedCommand = 'VwByAGkAdABlAC0ATwB1AHQAcAB1AHQAIAAiAFQAbwBkAGEAeQAgAGkAcwAgACQAKABHAGUAdAAtAEQAYQB0AGUAIAAtAEYAbwByAG0AYQB0ACAAJwBkAGQAZABkACwAIABNAE0ATQBNACAAZABkACcAKQAuACIA'; | ||
const sut = new EncodedPowerShellInvokeCmdCommandCreator(); | ||
// act | ||
const command = sut.createCommandToInvokePowerShell(givenCode); | ||
// assert | ||
const args = parsePowerShellArgs(command); | ||
const actualEncodedCommand = args.get('-EncodedCommand'); | ||
expect(actualEncodedCommand).to.equal(expectedEncodedCommand); | ||
}); | ||
}); | ||
}); | ||
|
||
function parsePowerShellArgs(command: string): Map<string, string | undefined> { | ||
const argsMap = new Map<string, string | undefined>(); | ||
const argRegex = /(-\w+)(\s+([^ ]+))?/g; | ||
let match = argRegex.exec(command); | ||
while (match !== null) { | ||
const arg = match[1]; | ||
const value = match[3]; | ||
argsMap.set(arg, value); | ||
match = argRegex.exec(command); | ||
} | ||
return argsMap; | ||
} |
13 changes: 0 additions & 13 deletions
13
...er/Execution/CommandDefinition/Commands/ShellArgument/CmdShellPathArgumentEscaper.spec.ts
This file was deleted.
Oops, something went wrong.
Oops, something went wrong.