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-Process
cmdlet不同。 - PetSerAl指出,自动双引号机制并不是那么简单(字符串的具体内容与是否应用自动双引号有关),并且具体行为在v5中发生了变化,在v6中再次(略有)发生了变化。
- 请注意,如果要直接调用