是否可以在脚本范围内执行包含脚本的字符串?

brb*_*sby 2 powershell

记住XY 问题,这是我的 X 和 Y 问题。

X 问题

有一些 PowerShell 脚本,我已经对其内容进行了混淆。不过,我仍然可以运行该脚本。

其工作原理是脚本存储在可执行文件内。PowerShell 可以执行可执行文件,通过将脚本名称作为 CLI 参数传递来请求特定脚本。然后,可执行文件的进程将通过 stdout 转发 PowerShell 脚本的内容。PowerShell 将 stdout 存储在字符串变量中,然后调用Invoke-Expression,传入字符串,运行混淆的脚本。

问题是$PSScriptRoot当它出现在混淆脚本中时,被评估为$null

是的问题

当 PowerShell 脚本script.ps1将 PowerShell 脚本作为字符串执行时$scriptString,如何$PSScriptRoot像在script.ps1.

测试1

# script.ps1

$PSScriptRoot

# Output: Path to directory containing `script.ps1`.
Run Code Online (Sandbox Code Playgroud)

这表明至少在一种情况下$PSScriptRoot是非的。$null

测试2

# script.ps1

Invoke-Expression '$PSScriptRoot'

# Output: Nothing
Run Code Online (Sandbox Code Playgroud)

表明我当前执行字符串的方式不起作用。

测试3

# script.ps1

. { $PSScriptRoot }

# Output: Path to directory containing `script.ps1`.
Run Code Online (Sandbox Code Playgroud)

使用语法创建脚本块{ ... }确实有效。然而,这需要实际的命令。我拥有的是一个包含命令的字符串。将该字符串放在大括号之间将导致命令被输出而不是运行。

测试4

# script.ps1

. [ScriptBlock]::Create('$PSScriptRoot')

# Output: Nothing
Run Code Online (Sandbox Code Playgroud)

创建脚本块的另一种方法是使用 C#[ScriptBlock]::Create(...)方法。这实际上允许从字符串创建脚本块。不幸的是,它不起作用。

测试5

# script.ps1

Invoke-Command -ScriptBlock { $PSScriptRoot }

# Output: Path to directory containing `script.ps1`.
Run Code Online (Sandbox Code Playgroud)

与测试 3 相同,只是调用Invoke-Command脚本块而不是点源脚本块。

测试6

# script.ps1

$scriptBlock = [ScriptBlock]::Create('$PSScriptRoot')
Invoke-Command -ScriptBlock $scriptBlock

# Output: Nothing.
Run Code Online (Sandbox Code Playgroud)

与测试 3 相同,只是调用Invoke-Command脚本块而不是点源脚本块。

目前对这个问题的看法。

  • 据我所知,直接将字符串脚本作为字符串执行的唯一方法是使用Invoke-Expressioncmdlet,但它不起作用。也许还有另一种方法可以直接执行字符串,但我不知道。
  • 执行字符串脚本的另一种方法是将字符串转换为脚本块,然后执行脚本块。创建脚本块似乎有两种方法,一种有效,一种无效。
    • 使用语法创建脚本块{ ... }似乎可以工作,但它似乎不适用于字符串。
    • 使用该方法创建脚本块[ScriptBlock]::Create(...)不起作用,但可以使用字符串。

问题:

  • 有没有一种方法可以直接执行字符串而无需调用Invoke-Expression
  • 有没有办法使用语法从字符串创建脚本块{ ... }
  • 测试场景有效/无效背后的原因/机制是什么?
  • 最重要的是,我们如何执行字符串脚本并计算$PSScriptRoot出正确的非$null值?

Mat*_*sen 6

您可以让 PowerShell 将存储在字符串中的脚本视为已从磁盘读取,也就是说,$PSScriptRoot通过$PSCommandPath手动解析它,它将正确解析:

# Prepare script + path
$script = 'Write-Host "Script root:  $PSScriptRoot`nCommand path: $PSCommandPath"'
$fileName = "C:\my\path\to\script.ps1"

# Make the parser generate an AST from the input script + path
$AST = [System.Management.Automation.Language.Parser]::ParseInput($script, $fileName, [ref]$null, [ref]$null)

# Get an executable scriptblock from AST
$block = $AST.GetScriptBlock()

# Now invoke it
& $block
Run Code Online (Sandbox Code Playgroud)

此时您会发现$PS*变量已正确解析:

# Prepare script + path
$script = 'Write-Host "Script root:  $PSScriptRoot`nCommand path: $PSCommandPath"'
$fileName = "C:\my\path\to\script.ps1"

# Make the parser generate an AST from the input script + path
$AST = [System.Management.Automation.Language.Parser]::ParseInput($script, $fileName, [ref]$null, [ref]$null)

# Get an executable scriptblock from AST
$block = $AST.GetScriptBlock()

# Now invoke it
& $block
Run Code Online (Sandbox Code Playgroud)