使用 PowerShell 创建快捷方式时设置控制台颜色

使用 PowerShell 创建快捷方式时设置控制台颜色

我可以使用如下代码在 PowerShell 中创建快捷方式文件:

$WshShell = New-Object -comObject WScript.Shell
$Shortcut = $WshShell.CreateShortcut("$PSScriptRoot/myshortcut.lnk")
$Shortcut.Arguments = "./myscript.py"
$Shortcut.TargetPath = $myexepath
$Shortcut.WorkingDirectory = "$PSScriptRoot"
$Shortcut.Save()

我想调整此 Powershell 代码以更改创建的输出 .lnk 文件的控制台颜色。从此处的答案开始,https://superuser.com/a/1309729,我知道控制台颜色直接存储在 .lnk 文件中(在通过 GUI 更改颜色之前和之后对 .lnk 文件进行二进制比较时,我自己也观察到了这一点)。

使用上述方法创建快捷方式文件时,如何指定控制台颜色?希望我不必逐字节更改创建的 .lnk 文件即可实现此目的。我当然也不想更改注册表来执行此操作;我希望颜色特定于创建的 .lnk 文件。

答案1

当您创建控制台应用程序的快捷方式时,Properties生成的快捷方式的对话框有四个特定于控制台外观的选项卡: 在此处输入图片描述

不幸的是,这些设置不是由wscript.shellcom 对象公开的,因此如果您想修改.lnk文件指定的控制台颜色,您必须对文件进行一些字节级编辑。如果您能够从具有所需颜色的现有快捷方式中复制,那么这应该相对容易 ---一旦你知道要复制什么字节。

查看规格表[MS-SHLLINK]: Shell 链接 (.LNK) 二进制文件格式揭示了:

2.5.1 控制台数据块

ConsoleDataBlock 结构指定当链接目标指定在控制台窗口中运行的应用程序时要使用的显示设置。

...并且块具有恒定的大小并且始终以特定的字节序列开头:

块大小(4 个字节):一个 32 位无符号整数,指定 ConsoleDataBlock 结构的大小。此值必须是 0x000000CC。

区块签名(4 个字节):一个 32 位无符号整数,指定 ConsoleDataBlock 额外数据部分的签名。此值必须是 0xA0000002。

作为原始字节序列,它将是:

CC 00 00 00 02 00 00 A0

编辑#2---代码大修

我最初的代码是基于字节操作的,而且有很多例子。但有一种方法可以搜索和操作字节数组作为字符串,并利用正则表达式的强大功能,详见使用 PowerShell 和正则表达式搜索二进制数据

不幸的是,Get-Content 中允许的任何编码方案都无法提供字符与其相应字节值的一对一映射。不过,有一种神奇的编码方案可以做到这一点:ISO-8859-1(代码页:28591)。

要创建神奇的编码字符串,.net 类的实例文件流流读取器必须创建。生成的字符串可以用正则表达式进行操作。

$BlockSource  = 'C:\Path\to\LinkWithCustomizedConsoleSettings.lnk'
$Lnk2Edit     = 'C:\Path\to\LinkToBeCustomized.lnk'

### -------------------------- Code based on -------------------------- ###
### ----------------- Use PowerShell and Regular Expressions to Search Binary Data ----------------- ###
###   https://devblogs.microsoft.com/scripting/use-powershell-and-regular-expressions-to-search-binary-data/   ###

$DataSize    = 0xCC - 8 
$BlockHeader = '\xCC\x00{3}\x02\x00{2}\xA0'

Function Get-BytesAsText ( [String] $Path )
{ Process {
    $Stream = [IO.FileStream]::new($Path, 'Open', 'Read' )
    $Reader = [IO.StreamReader]::new( $Stream , ([Text.Encoding]::GetEncoding(28591)))
    $Reader.ReadToEnd()
    $Reader.Close()
    $Stream.Close()
}}

Function Get-BlockOffset ( [String] $ByteString )
    { Process { [Regex]::Match( $ByteString , $BlockHeader ).Index }}

Function Get-ConsoleBlock ( [String] $ByteString )
    { Process { [Regex]::Match( $ByteString , "$BlockHeader.{$DataSize}" ).Value }}

###   Main   ###

$ByteText           = Get-BytesAsText $BlockSource    
$CustomConsoleBlock = Get-ConsoleBlock $ByteText

$ByteText           = Get-BytesAsText $Lnk2Edit
$Offset             = Get-BlockOffset $ByteText

If ( $Offset )   ### Replace existing console block
{
    $Pattern        = "$BlockHeader.{$DataSize}"
    $NewBytes       = [Regex]::Replace( $ByteText , $Pattern , $CustomConsoleBlock )
}
Else             ### Insert before terminal block
{
    Write-Verbose 'No existing Console Block'
    $Pattern        = '\x00{4}$'
    $NewBytes       = [Regex]::Replace( $ByteText , $Pattern , ($CustomConsoleBlock + "$([string][char]0x00 * 4)") )
}
[Byte[]][Char[]]$NewBytes | Set-Content $Lnk2Edit -Encoding Byte -Force


编辑 #2 结束



这是原始帖子和第一次编辑 - 仅用于历史目的!:D

因此,要将控制台数据块从现有快捷方式复制到字节数组:

$BlockSize       = [Byte]0xCC
$HeaderAsUint64  = [system.BitConverter]::ToUInt64([Byte[]](0xCC,0,0,0,2,0,0,0xA0),0)

$SourcePath      = 'C:\CustomizedShortut.lnk'

$SourceBytes     = [Byte[]](Get-Content $SourcePath -Encoding Byte)

$BlockOffset     = $SourceBytes.IndexOf($BlockSize)

If ( [System.BitConverter]::ToUInt64($SourceBytes,$BlockOffset) -eq $HeaderAsUint64 )
{
    $ConsoleBytes = [Byte[]]($SourceBytes[$BlockOffset..($BlockOffset + $BlockSize - 1)])
    # Create an assignment string to paste into shortcut creattion code
    '$ConsoleBytes = [Byte[]]@({0})' -f ($ConsoleBytes -join ',') | Set-Clipboard
}
Else
{
    # Modify code to loop on byte search + sequence verification
    # until header is found. In my limited testing, the 0xCC byte
    # only occured once, so this wasn't necessary.
}

然后在您的快捷方式创建代码中,将先前复制的代码的表达式粘贴到剪贴板并使用它来编辑您新创建的快捷方式:

编辑:修改代码以将块添加到原来没有的快捷方式。

# <ctrl>+V
$ConsoleBytes    = [Byte[]]@(204,0,0,0,2,0,0,160,101,0,202,0,120,0,184,11,120,0,47,0,96,0,108,0,0,0,0,0,0,0,0,0,13,0,28,0,54,0,0,0,144,1,0,0,67,0,111,0,110,0,115,0,111,0,108,0,97,0,115,0,0,0,101,0,49,0,53,0,0,0,0,0,249,127,0,0,140,176,8,154,213,73,0,0,0,0,55,1,250,127,0,0,169,232,245,31,0,0,0,0,145,71,151,231,249,127,0,0,25,0,0,0,0,0,0,0,1,0,0,0,1,0,0,0,1,0,0,0,50,0,0,0,4,0,0,0,0,0,0,0,0,0,0,0,0,0,128,0,0,128,0,0,0,128,128,0,128,0,0,0,1,36,86,0,238,237,240,0,192,192,192,0,128,128,128,0,0,0,255,0,0,255,0,0,0,255,255,0,255,0,0,0,255,0,255,0,255,255,0,0,255,255,255,0)
$BlockSize       = [Byte]0xCC

$MyShortCutPath  = "$PSScriptRoot\myshortcut.lnk"

$MyShortCutBytes = Get-Content $MyShortCutPath -Encoding Byte

$BlockOffset     = $MyShortCutBytes.IndexOf($BlockSize)

### Added code for shortcuts created without a Console Data Block
### As Extra Data Blocks are identified by signature, the order of
### the blocks doesn't matter within the Extra Data section. Thus,
### The custom Console Data Block can be appended just prior to the
### 4-byte Terminal Block.

If ($BlockOffset -eq -1)
{
    $BlockOffset = $MyShortCutBytes.Length - 4
    $BlockSize   = 0
}

[Byte[]]($MyShortCutBytes[0..($BlockOffset - 1)]) +
    $ConsoleBytes +
    [Byte[]]($MyShortCutBytes[($BlockOffset + $BlockSize)..($MyShortCutBytes.Length - 1)]) |
        Set-Content $MyShortCutPath -Encoding Byte -Force
  • 请注意,此代码将复制整个控制台块,其中包含链接文档中指定的所有设置。如果您想要更“精确”,您可以仅提取/修改控制台块的FillAttributesPopupFillAttributesColorTable部分。由于它们的偏移量从 cosole 数据块的开头开始是恒定的,因此这将是对代码的相对简单的修改。

相关内容