129 lines
3.8 KiB
PowerShell
129 lines
3.8 KiB
PowerShell
|
# readlineSync
|
||
|
# https://github.com/anseki/readline-sync
|
||
|
#
|
||
|
# Copyright (c) 2019 anseki
|
||
|
# Licensed under the MIT license.
|
||
|
|
||
|
Param(
|
||
|
[string] $display,
|
||
|
[switch] $displayOnly,
|
||
|
[switch] $keyIn,
|
||
|
[switch] $hideEchoBack,
|
||
|
[string] $mask,
|
||
|
[string] $limit,
|
||
|
[switch] $caseSensitive
|
||
|
)
|
||
|
|
||
|
$ErrorActionPreference = 'Stop' # for cmdlet
|
||
|
trap {
|
||
|
# `throw $_` and `Write-Error $_` return exit-code 0
|
||
|
$Host.UI.WriteErrorLine($_)
|
||
|
exit 1
|
||
|
}
|
||
|
|
||
|
function decodeArg ($arg) {
|
||
|
[Regex]::Replace($arg, '#(\d+);', { [char][int] $args[0].Groups[1].Value })
|
||
|
}
|
||
|
|
||
|
$options = @{}
|
||
|
foreach ($arg in @('display', 'displayOnly', 'keyIn', 'hideEchoBack', 'mask', 'limit', 'caseSensitive')) {
|
||
|
$options.Add($arg, (Get-Variable $arg -ValueOnly))
|
||
|
}
|
||
|
$argList = New-Object string[] $options.Keys.Count
|
||
|
$options.Keys.CopyTo($argList, 0)
|
||
|
foreach ($arg in $argList) {
|
||
|
if ($options[$arg] -is [string] -and $options[$arg])
|
||
|
{ $options[$arg] = decodeArg $options[$arg] }
|
||
|
}
|
||
|
|
||
|
[string] $inputTTY = ''
|
||
|
[bool] $silent = -not $options.display -and
|
||
|
$options.keyIn -and $options.hideEchoBack -and -not $options.mask
|
||
|
[bool] $isCooked = -not $options.hideEchoBack -and -not $options.keyIn
|
||
|
|
||
|
# Instant method that opens TTY without CreateFile via P/Invoke in .NET Framework
|
||
|
# **NOTE** Don't include special characters of DOS in $command when $getRes is True.
|
||
|
# [string] $cmdPath = $Env:ComSpec
|
||
|
# [string] $psPath = 'powershell.exe'
|
||
|
function execWithTTY ($command, $getRes = $False, $throwError = $False) {
|
||
|
if ($getRes) {
|
||
|
$res = (cmd.exe /C "<CON powershell.exe -Command $command")
|
||
|
if ($LastExitCode -ne 0) {
|
||
|
if ($throwError) { throw $LastExitCode }
|
||
|
else { exit $LastExitCode }
|
||
|
}
|
||
|
return $res
|
||
|
} else {
|
||
|
$command | cmd.exe /C ">CON powershell.exe -Command -"
|
||
|
if ($LastExitCode -ne 0) {
|
||
|
if ($throwError) { throw $LastExitCode }
|
||
|
else { exit $LastExitCode }
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
function writeTTY ($text) {
|
||
|
execWithTTY ('Write-Host (''' +
|
||
|
(($text -replace '''', '''''') -replace '[\r\n]', '''+"`n"+''') + ''') -NoNewline')
|
||
|
}
|
||
|
|
||
|
if ($options.display) {
|
||
|
writeTTY $options.display
|
||
|
}
|
||
|
if ($options.displayOnly) { return "''" }
|
||
|
|
||
|
if (-not $options.keyIn -and $options.hideEchoBack -and $options.mask -eq '*') {
|
||
|
# It fails when it's not ready.
|
||
|
try {
|
||
|
$inputTTY = execWithTTY ('$text = Read-Host -AsSecureString;' +
|
||
|
'$bstr = [Runtime.InteropServices.Marshal]::SecureStringToBSTR($text);' +
|
||
|
'[Runtime.InteropServices.Marshal]::PtrToStringAuto($bstr)') $True $True
|
||
|
return '''' + $inputTTY + ''''
|
||
|
} catch {} # ignore
|
||
|
}
|
||
|
|
||
|
if ($options.keyIn) { $reqSize = 1 }
|
||
|
|
||
|
if ($options.keyIn -and $options.limit) {
|
||
|
$limitPtn = '[^' + $options.limit + ']'
|
||
|
}
|
||
|
|
||
|
while ($True) {
|
||
|
if (-not $isCooked) {
|
||
|
$chunk = [char][int] (execWithTTY '[int] [Console]::ReadKey($True).KeyChar' $True)
|
||
|
} else {
|
||
|
$chunk = execWithTTY 'Read-Host' $True
|
||
|
$chunk += "`n"
|
||
|
}
|
||
|
|
||
|
if ($chunk -and $chunk -match '^(.*?)[\r\n]') {
|
||
|
$chunk = $Matches[1]
|
||
|
$atEol = $True
|
||
|
} else { $atEol = $False }
|
||
|
|
||
|
# other ctrl-chars
|
||
|
if ($chunk) { $chunk = $chunk -replace '[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]', '' }
|
||
|
if ($chunk -and $limitPtn) {
|
||
|
if ($options.caseSensitive) { $chunk = $chunk -creplace $limitPtn, '' }
|
||
|
else { $chunk = $chunk -ireplace $limitPtn, '' }
|
||
|
}
|
||
|
|
||
|
if ($chunk) {
|
||
|
if (-not $isCooked) {
|
||
|
if (-not $options.hideEchoBack) {
|
||
|
writeTTY $chunk
|
||
|
} elseif ($options.mask) {
|
||
|
writeTTY ($options.mask * $chunk.Length)
|
||
|
}
|
||
|
}
|
||
|
$inputTTY += $chunk
|
||
|
}
|
||
|
|
||
|
if ((-not $options.keyIn -and $atEol) -or
|
||
|
($options.keyIn -and $inputTTY.Length -ge $reqSize)) { break }
|
||
|
}
|
||
|
|
||
|
if (-not $isCooked -and -not $silent) { execWithTTY 'Write-Host ''''' } # new line
|
||
|
|
||
|
return "'$inputTTY'"
|