在新窗口中运行PowerShell自定义函数


Function Check-PC
{
$PC = Read-Host "PC Name"
If($PC -eq "exit"){EXIT}
Else{
Write-Host "Pinging $PC to confirm status..."
PING -n 1 $PC
}

这是我编写到PowerShell脚本中的函数片段。 我希望该函数在PowerShell的新实例中运行,而不是在主窗口中运行。

是否可以在PowerShell的单独进程中运行它,而无需将其编写为单独的脚本并调用脚本?像这样:

$x= Start-Process powershell | Check-PC

如果可能的话,我需要将所有内容保存在一个脚本中。

注意:正是Start-Process的参与使解决方案大大复杂化 - 见下文。如果直接从PowerShell调用powershell,则可以安全地传递脚本块,如下所示:

powershell ${function:Check-PC}  # !! Does NOT work with Start-Process

${function:Check-PC}是变量命名空间表示法的一个实例:它将函数的主体作为脚本块([scriptblock]实例)返回;它是更简洁、更快速的Get-Content Function:Check-PC等效
物。

如果需要将(仅位置)参数传递给脚本块,则必须附加-Args,后跟参数作为数组(,分隔)。


带有辅助自删除临时文件的Start-Process解决方案:

请参阅相关问题的答案的下半部分。


采用-EncodedCommand和 Base64 编码的Start-Process解决方案:

Start-Process powershell -args '-noprofile', '-noexit', '-EncodedCommand',
([Convert]::ToBase64String(
[Text.Encoding]::Unicode.GetBytes(
(Get-Command -Type Function Check-PC).Definition
)
))

新的powershell实例将看不到当前会话的定义(除非它们在您的配置文件中定义),因此您必须将函数的主体传递给它(要执行的源代码)。

(Get-Command -Type Function Check-PC).Definition字符串形式返回函数定义的主体

但是,字符串需要转义,以便传递到新的Powershell进程而不修改
"嵌入在字符串中的实例将被剥离,除非它们表示为"或三倍 (""").
( 在这种情况下,需要"而不是通常的`"来转义双引号, 因为 PowerShell 在将命令传递给powershell.exe可执行文件时需要"

类似地,如果整个字符串或函数体内的双引号字符串以(非空运行)结尾,则该将被解释为转义字符,因此必须加倍谢谢,宠物。

绕过这些引号(转义)难题的最可靠方法是将 Base64 编码的字符串与-EncodedCommand参数结合使用

  • [Convert]::ToBase64String()[byte[]]数组创建 Base64 编码的字符串。

  • [Text.Encoding]::Unicode.GetBytes()将 (内部 UTF-16 - "Unicode")字符串到[byte[]]数组。

注意:要同时传递参数,您有两个选项:

  • 你可以将它们"烘焙"到-EncodedCommand参数中,假设你可以调用一个命令将它们传递给那里 - 见下文,它显示了如何在新会话中定义你的函数,这样你就可以用参数按名称调用它。谢谢,亚伯拉罕·齐纳拉

    • 此方法的优点是可以通过这种方式传递命名参数。缺点是仅限于具有字符串文本表示形式的参数。
  • 您可以使用(当前未记录的)-EncodedArguments参数,您必须同样向其传递 Base64 编码的字符串,尽管它基于要传递的参数数组的CLIXML表示

    形式
    • 此方法的优点是,您可以在 CLIXML 序列化可以提供的类型保真度限制内传递更广泛的数据类型 - 请参阅此答案;缺点是仅以这种方式支持位置参数(尽管您可以通过传递一个哈希表来解决此问题,然后目标代码使用该哈希表与最终目标命令进行拼接)。

    • 下面是一个简化的、自包含的示例,它使用Write-Output来回显收到的(总是位置的)参数:

$command = 'Write-Output $args'
$argList = 'foo', 42
Start-Process powershell -args '-noprofile', '-noexit', 
'-EncodedCommand',
([Convert]::ToBase64String([Text.Encoding]::Unicode.GetBytes($command))),
'-EncodedArguments',
([Convert]::ToBase64String([Text.Encoding]::Unicode.GetBytes(
[System.Management.Automation.PSSerializer]::Serialize($argList)
)))

如果要传递完整的函数,以便按名称调用它以便将参数作为命令字符串的一部分传递,则需要做更多的工作。

# Simply wrapping the body in `function <name> { ... }` is enough.
$func = (Get-Command -Type Function Check-PC)
$wholeFuncDef = 'Function ' + $func.Name + " {`n" + $func.Definition + "`n}"
Start-Process powershell -args '-noprofile', '-noexit', '-EncodedCommand', `
([Convert]::ToBase64String([Text.Encoding]::Unicode.GetBytes("$wholeFuncDef; Check-PC")))

如上所述,您可以将任何参数"烘焙"到您的函数中 - 假设它们可以表示为字符串文字 - 到-EncodedCommand参数中,只需将它们附加到上面的"$wholeFuncDef; Check-PC"字符串中即可; 例如,
"$wholeFuncDef; Check-PC -Foo Bar -Baz Quux"


Start-Process解决方案,具有基于正则表达式的源代码转义以传递:

PetSerAl建议使用以下替代方法,它使用正则表达式来执行转义。 解决方案更简洁,但有些令人费解:

Start-Process powershell -args '-noprofile', '-noexit', '-Command', `
('"' + 
((Get-Command -Type Function Check-PC).Definition -replace '"|\(?=\*("|$))', '$&') +
'"')
  • "|\(?=\*("|$))匹配每个"实例和每个非空的字符运行。 - 逐个字符 - 直接在"字符或字符串末尾之前。

    • 正则表达式的上下文中需要\来转义单个文字
    • (?=\*("|$))是一个积极的前瞻断言,仅当后跟"或字符串的末尾 ($) 时,才与匹配,无论是直接匹配还是与介于两者之间的其他实例 (\*)。 请注意,由于断言对匹配没有贡献,因此字符(如果有多个字符)仍会逐个匹配。
  • $&将每个匹配的字符替换为后跟字符本身的($&) - 有关可以在-replace表达式的替换字符串中使用的结构,请参阅此答案。

  • 需要将结果括在"..."('"' + ... + '"')中以防止空格规范化;如果没有它,任何多个空格字符和/或制表符字符的运行都将规范化为单个空格,因为整个字符串不会被识别为单个参数。

    • 请注意,如果要直接调用powershell,PowerShell 通常会在后台自动将字符串括"..."中,因为它会在调用外部实用工具(本机命令行应用程序)时对包含空格的参数执行此操作,这就是powershell.exe- 与Start-Processcmdlet不同。
    • PetSerAl指出,自动双引号机制并不是那么简单(字符串的具体内容与是否应用自动双引号有关),并且具体行为在v5中发生了变化,在v6中再次(略有)发生了变化。

最新更新