2015-04-01 09:34:31 +02:00
|
|
|
# readlineSync
|
|
|
|
# https://github.com/anseki/readline-sync
|
|
|
|
#
|
|
|
|
# Copyright (c) 2015 anseki
|
|
|
|
# Licensed under the MIT license.
|
2015-03-25 13:22:44 +01:00
|
|
|
|
|
|
|
Param(
|
|
|
|
[string] $display,
|
2015-03-31 13:27:02 +02:00
|
|
|
[switch] $keyIn,
|
2015-03-25 13:22:44 +01:00
|
|
|
[switch] $noEchoBack,
|
2015-03-27 12:25:59 +01:00
|
|
|
[string] $mask,
|
2015-03-31 13:27:02 +02:00
|
|
|
[string] $exclude,
|
|
|
|
[switch] $cs,
|
2015-03-25 13:22:44 +01:00
|
|
|
[switch] $encoded
|
|
|
|
)
|
|
|
|
|
|
|
|
$ErrorActionPreference = 'Stop' # for cmdlet
|
|
|
|
trap {
|
|
|
|
# `throw $_` and `Write-Error $_` return exit-code 0
|
|
|
|
$Host.UI.WriteErrorLine($_)
|
|
|
|
exit 1
|
|
|
|
}
|
|
|
|
|
2015-03-27 12:25:59 +01:00
|
|
|
function decodeDOS ($arg) {
|
2015-03-25 13:22:44 +01:00
|
|
|
[Regex]::Replace($arg, '#(\d+);', { [char][int] $args[0].Groups[1].Value })
|
|
|
|
}
|
|
|
|
|
2015-03-27 12:25:59 +01:00
|
|
|
$options = @{}
|
2015-03-31 13:27:02 +02:00
|
|
|
foreach ($arg in @('display', 'keyIn', 'noEchoBack', 'mask', 'exclude', 'cs', 'encoded')) {
|
2015-03-27 12:25:59 +01:00
|
|
|
$options.Add($arg, (Get-Variable $arg -ValueOnly))
|
|
|
|
}
|
|
|
|
if ($options.encoded) {
|
|
|
|
$argList = New-Object string[] $options.Keys.Count
|
|
|
|
$options.Keys.CopyTo($argList, 0);
|
|
|
|
foreach ($arg in $argList) {
|
|
|
|
if (($options[$arg] -is [string]) -and ($options[$arg] -ne ''))
|
|
|
|
{ $options[$arg] = decodeDOS $options[$arg] }
|
|
|
|
}
|
2015-03-25 13:22:44 +01:00
|
|
|
}
|
|
|
|
|
2015-03-27 12:25:59 +01:00
|
|
|
[string] $inputTTY = ''
|
|
|
|
[bool] $isInputLine = $False
|
2015-03-29 16:45:55 +02:00
|
|
|
[bool] $isCooked = (-not $options.noEchoBack) -and (-not $options.keyIn)
|
2015-03-27 12:25:59 +01:00
|
|
|
|
|
|
|
function writeTTY ($text) {
|
2015-04-01 09:34:31 +02:00
|
|
|
execWithTTY ('Write-Host ''' + ($text -replace '''', '''''') + ''' -NoNewline')
|
2015-03-27 12:25:59 +01:00
|
|
|
$script:isInputLine = $True
|
|
|
|
}
|
2015-03-25 13:22:44 +01:00
|
|
|
|
|
|
|
# Instant method that opens TTY without CreateFile via P/Invoke in .NET Framework
|
2015-03-27 12:25:59 +01:00
|
|
|
# **NOTE** Don't include special characters of DOS in $command when $getRes is True.
|
2015-04-01 09:34:31 +02:00
|
|
|
# [string] $cmdPath = $Env:ComSpec
|
|
|
|
# [string] $psPath = 'powershell.exe'
|
2015-03-25 13:22:44 +01:00
|
|
|
function execWithTTY ($command, $getRes = $False) {
|
|
|
|
if ($getRes) {
|
|
|
|
$res = (cmd.exe /C "<CON powershell.exe -Command $command")
|
|
|
|
if ($LastExitCode -ne 0) { exit 1 }
|
2015-04-01 09:34:31 +02:00
|
|
|
return $res
|
2015-03-25 13:22:44 +01:00
|
|
|
} else {
|
|
|
|
$command | cmd.exe /C ">CON powershell.exe -Command -"
|
|
|
|
if ($LastExitCode -ne 0) { exit 1 }
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-03-27 12:25:59 +01:00
|
|
|
if ($options.display -ne '') {
|
|
|
|
writeTTY $options.display
|
|
|
|
$options.display = ''
|
2015-03-25 13:22:44 +01:00
|
|
|
}
|
|
|
|
|
2015-03-27 12:25:59 +01:00
|
|
|
if ($options.noEchoBack -and (-not $options.keyIn) -and ($options.mask -eq '*')) {
|
2015-03-25 13:22:44 +01:00
|
|
|
$inputTTY = execWithTTY ('$inputTTY = Read-Host -AsSecureString;' +
|
|
|
|
'$bstr = [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($inputTTY);' +
|
|
|
|
'[System.Runtime.InteropServices.Marshal]::PtrToStringAuto($bstr)') $True
|
2015-03-27 12:25:59 +01:00
|
|
|
return '''' + $inputTTY + ''''
|
2015-03-25 13:22:44 +01:00
|
|
|
}
|
|
|
|
|
2015-03-27 12:25:59 +01:00
|
|
|
if ($options.keyIn) { $reqSize = 1 }
|
2015-03-25 13:22:44 +01:00
|
|
|
|
|
|
|
while ($True) {
|
2015-03-30 12:36:31 +02:00
|
|
|
if (-not $isCooked) {
|
2015-03-25 13:22:44 +01:00
|
|
|
$chunk = execWithTTY '[System.Console]::ReadKey($True).KeyChar' $True
|
2015-03-31 13:27:02 +02:00
|
|
|
# ReadKey() may returns [System.Array], then don't cast data.
|
|
|
|
if ($chunk -isnot [string]) { $chunk = '' }
|
2015-03-30 12:36:31 +02:00
|
|
|
$chunk = $chunk -replace '[\r\n]', ''
|
2015-04-01 09:34:31 +02:00
|
|
|
if ($chunk -eq '') { $atEol = $True } # NL or empty-text was input
|
2015-03-30 12:36:31 +02:00
|
|
|
} else {
|
|
|
|
$chunk = execWithTTY 'Read-Host' $True
|
|
|
|
$chunk = $chunk -replace '[\r\n]', ''
|
2015-04-01 09:34:31 +02:00
|
|
|
$atEol = $True
|
2015-03-25 13:22:44 +01:00
|
|
|
}
|
|
|
|
|
2015-03-27 12:25:59 +01:00
|
|
|
# other ctrl-chars
|
2015-03-25 13:22:44 +01:00
|
|
|
$chunk = $chunk -replace '[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]', ''
|
|
|
|
|
2015-03-30 12:36:31 +02:00
|
|
|
if ($chunk -ne '' -and (-not $isCooked)) {
|
|
|
|
if (-not $options.noEchoBack) {
|
|
|
|
writeTTY $chunk
|
|
|
|
} elseif ($options.mask -ne '') {
|
|
|
|
writeTTY ($options.mask * $chunk.Length)
|
2015-03-25 13:22:44 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
$inputTTY += $chunk
|
2015-04-01 09:34:31 +02:00
|
|
|
if ($atEol -or ($options.keyIn -and ($inputTTY.Length -ge $reqSize))) { break }
|
2015-03-25 13:22:44 +01:00
|
|
|
}
|
|
|
|
|
2015-03-29 16:45:55 +02:00
|
|
|
if ((-not $isCooked) -and (-not ($options.keyIn -and (-not $isInputLine))))
|
2015-04-01 09:34:31 +02:00
|
|
|
{ execWithTTY 'Write-Host ''''' } # new line
|
2015-03-25 13:22:44 +01:00
|
|
|
|
2015-03-27 12:25:59 +01:00
|
|
|
return '''' + $inputTTY + ''''
|