Skip to content

Instantly share code, notes, and snippets.

@Jaykul
Last active March 19, 2023 13:39
Show Gist options
  • Save Jaykul/f9aac8753b5fe39fa24a96bf7f4dc6b7 to your computer and use it in GitHub Desktop.
Save Jaykul/f9aac8753b5fe39fa24a96bf7f4dc6b7 to your computer and use it in GitHub Desktop.
Virtual terminal queries (DECRQSS) return values as INPUT
function Get-ColorMode {
<#
.SYNOPSIS
Tests for FullColor (RGB) mode and X11/XTerm (XColor) modes by writing SGR and verifying it with a DECRQSS
Returns "Uknown" if there's no DECRQSS support, or "FullColor" and/or "XColor" otherwise
#>
[CmdletBinding()]
param()
$ColorMode = @{
'48:2:255:0:255' = 'FullColor'
'48:5:254' = 'XColor'
'48;2;255;0;255' = 'FullColorCompatible'
'48;5;254' = 'XColorCompatible'
}
$SupportedModes = @(foreach($SGR in $ColorMode.Keys) {
$DECRQSS = -join @(
# Set the background color
"`e[0m`e[$($SGR)m"
# Output the DECRQSS query
"`eP`$qm`e`\"
# Reset the background
"`e[49m"
)
# strip the DCS and ST from the ends of the response and return just the SGR value as the Response
$Result = Get-VtResponse $DECRQSS | Select-CapturedString '(?:\u001BP|\u0090)(?<Result>\d+)\$r(?:0;)?(?<Code>.*)m(?:\u001B[\\\t])'
Write-Verbose "$($Result.Result)r$($Result.Code)m"
# the result code is supposed to be 1 no matter what, no idea what other values represent, really
if ($Result.Result -ne 1) {
Write-Verbose "Request Status String (DECRQSS) not supported (returned $($Result.Result))"
continue
}
if ($Result.Code -ne $SGR) {
Write-Verbose "Received unexpected result, our '$($SGR)' color mode wasn't supported (returned $($Result.Result))"
continue
}
$SGR
})
$ColorMode[$SupportedModes]
}
function Get-VtResponse {
<#
.SYNOPSIS
Write a VT ANSI escape sequence to the host and capture the response
.EXAMPLE
$Row, $Col = (Get-VtResponse "`e[6n") -split ';' -replace "[`e\[R]"
Gets the current cursor position into $Row and $Col
#>
[CmdletBinding()]
param(
[Parameter(Mandatory)]
$Sequence
)
[console]::write($sequence)
@(while ([console]::KeyAvailable) {
[console]::ReadKey($true).KeyChar
}) -join ""
}
function Select-CapturedString {
<#
.Synopsis
Collect named capture groups from regular expression matches
(I still don't like the name of this function)
.Description
Takes string data and a regular expression containing named captures,
and outputs all of the resulting captures in one (or more) hashtable(s)
.Example
netstat | Select-CapturedString "(?<Protocol>\w{3})\s+(?<LocalIP>(\d{1,3}\.){3}\d{1,3}):(?<LocalPort>\d+)\s+(?<ForeignIP>.*):(?<ForeignPort>\d+)\s+(?<State>\w+)?"
This is an example of how to use it for parsing when all the values are on one line
.Example
"Revoked Certificates:
Serial Number: 011F63068E6BCD8CABF644026B80A903
Revocation Date: Jul 8 06:22:01 2012 GMT
Serial Number: 01205F0018B6758D741B3DB43CFB26C2
Revocation Date: Feb 18 06:11:14 2013 GMT
Serial Number: 012607175D820413ED0750E96B833A8F
Revocation Date: Jun 11 03:12:11 2015 GMT
" | Select-CapturedString "(?m)Serial Number:\s+(?<SerialNumber>.*)\s*$|Revocation Date:\s+(?<RevocationDate>.*)\s*$" -Auto
SerialNumber RevocationDate
------------ --------------
011F63068E6BCD8CABF644026B80A903 Jul 8 06:22:01 2012 GMT
01205F0018B6758D741B3DB43CFB26C2 Feb 18 06:11:14 2013 GMT
012607175D820413ED0750E96B833A8F Jun 11 03:12:11 2015 GMT
When your values are on multiple lines, you can use the -AutoGroup switch to automatically collect sets of matches.
#>
param(
# The text to search for captures
[Parameter(ValueFromPipeline=$true)]
[string]$text,
# A regular expression containing named capture groups (see examples)
[Parameter(Position=1)]
[regex]$re,
# By default, each match is returned as a single object.
# When set, empty captures are ignored, and properties are collected until the capture groups repeat, allowing the collection of many lines using an OR regex (see Example 2)
[switch]$AutoGroup,
# If set, hide properties with empty values (default to the same as $AutoGroup)
[switch]$HideEmpty = $AutoGroup
)
begin {
[string[]]$FullData = $text
}
process {
[string[]]$FullData += $text
}
end {
$text = $FullData -join "`n"
if ($VerbosePreference -eq "Continue") {
Write-Verbose "Regex $re"
Write-Verbose "Data $(-join $text.GetEnumerator().ForEach{ if (27 -ge $_) { [char](0x2400 + $_) } else { "$_" } })"
}
$names = $re.GetGroupNames().Where{ $_ -ne 0 }
$result = [ordered]@{}
foreach ($match in $re.Matches($text).Where{$_.Success}) {
Write-Verbose (-join $match.Value.GetEnumerator().ForEach{ if (27 -ge $_) { [char](0x2400 + $_) } else { "$_" } })
foreach ($name in $names) {
if (-not $HideEmpty -or $match.Groups[$name].Value) {
if ($AutoGroup -and $result.ContainsKey($name)) {
[PSCustomObject]$result
$result = [ordered]@{}
}
$result.$name = $match.Groups[$name].Value
}
}
if (!$AutoGroup) {
[PSCustomObject]$result
$result = [ordered]@{}
}
}
if ($result) {
[PSCustomObject]$result
}
}
}
function Test-RgbMode {
<#
.SYNOPSIS
Writes an RGB color using 48;2;;;m and verifies it with a DECRQSS for SGR
#>
[CmdletBinding()]
param()
$RGBMagentaBackground = '48;2;255;0;255'
[Console]::Write( -join @(
# Set the background color
"`e[0;${RGBMagentaBackground}m"
# Output the DECRQSS query
"`eP`$qm`e`\"
# Reset the background
"`e[49m"
))
# Terminal responses show up as fake console input
$Response = @(while ([console]::KeyAvailable) {
[console]::ReadKey($true).KeyChar
}) -join ""
# strip the DCS and ST from the ends of the response and return just the SGR value as the Response
$Result, $Response = $Response -replace '^(?:\u001BP|\u0090)(\d+)\$r(?:0;)?(.*)m(?:\u001B[\\\t])$','$1,$2' -split ','
Write-Verbose "$($Result)r$($Response)m"
# the result code is supposed to be 1 (no idea what other values represent, really)
if ($Result -ne 1) {
Write-Warning "Request Status String (DECRQSS) not supported (returned $Result)"
$False
} else {
# If the response isn't what we put in, then there's no RGB support
$Response -eq $RGBMagentaBackground
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment