如何在 Azure DevOps 的 Powershell 内联脚本中正确连接字符串?

Posted

技术标签:

【中文标题】如何在 Azure DevOps 的 Powershell 内联脚本中正确连接字符串?【英文标题】:How to correctly concatenate string in Powershell inline script in Azure DevOps? 【发布时间】:2020-05-08 16:04:05 【问题描述】:

我尝试连接字符串来构造路径:

$SourceDirectoryPath = $(System.DefaultWorkingDirectory) + "/solution/project/bin/Debug"
$TargetFilePath = $(System.DefaultWorkingDirectory) + "/solution/project/bin/Debug/" + $(Release.ReleaseName) +$(Release.EnvironmentName)

但不是将字符串连接起来,而是第二行出现错误:

d:\a\r1\a : 术语 'd:\a\r1\a' 未被识别为 a 的名称 cmdlet、函数、脚本文件或可运行的程序。检查 名称的拼写,或者如果包含路径,请验证路径 是正确的,然后再试一次。在 D:\a_temp\9de874c9-3acd-4a19-a4dd-763074d38e40.ps1:2 char:25

d:\a\r1\a 显然是 $(System.DefaultWorkingDirectory),但为什么它抛出这个错误而不是仅仅连接字符串?

【问题讨论】:

【参考方案1】:

tl;dr

Azure 扩展了$(System.DefaultWorkingDirectory) before PowerShell 看到了结果命令;如果扩展的$(...) 值被PowerShell 视为字符串,则必须将其括在引号中('$(...)'):

使用 $(...)(Azure 宏语法) 将 Azure 变量的逐字值嵌入到 PowerShell 最终解释的命令文本中。

注意Azure 的宏语法 - 在 PowerShell 看到生成的命令文本之前评估 - 不要与 PowerShell 混淆 自己的subexpression operator,$(...)

对于 string 值,这意味着您必须用引号将宏括起来,以使其在 PowerShell 代码中的语法上工作, '...'-quoting(单引号)最好'$(System.DefaultWorkingDirectory)'


Shayki Abramczyk's answer 提供了一个有效的解决方案,但让我提供一些背景信息

Azure 通过宏语法 ($(...)) 执行的变量扩展(替换)类似于预处理器:它将引用的变量替换为其逐字值

您需要确保此逐字值在语法上在目标命令的上下文中起作用

如目前所写:

$SourceDirectoryPath = $(System.DefaultWorkingDirectory) + "/solution/project/bin/Debug"

变成下面的命令PowerShell看到的,假设Azure属性System.DefaultWorkingDirectory的值为d:\a\r1\a

$SourceDirectoryPath = d:\a\r1\a + "/solution/project/bin/Debug"

这是一个损坏的 PowerShell 命令,因为 d:\a\r1\a - 由于缺少引用 - 被解释为 命令名称或路径;也就是说,尝试执行假定的可执行文件d:\a\r1\a - 请参阅about_Parsing

因此,为了让 PowerShell 将 Azure 扩展值 d:\a\r1\a 识别为 字符串,您需要 引用 -见about_Quoting_Rules

由于按 Azure 扩展的值不需要进一步插值,引号是最佳选择(实际上对于两个操作数):

$SourceDirectoryPath = '$(System.DefaultWorkingDirectory)' + '/solution/project/bin/Debug'

事实上,您根本不需要字符串连接 (+)

$SourceDirectoryPath = '$(System.DefaultWorkingDirectory)/solution/project/bin/Debug'

您甚至可以将其与可扩展的 PowerShell 字符串 ("...") 结合使用,只要 Azure 扩展值不包含 PowerShell 最终可能解释的带有 $ 前缀的标记(除非这是您的(不寻常的)意图)。

警告类似于"$(System.DefaultWorkingDirectory)/$projectRoot/bin/Debug"(将 Azure 扩展值与 PowerShell 变量引用混合)是 Azure 的 macro syntax ($(...)) 看起来与 PowerShell 的相同拥有subexpression operator,通常(但不完全)用于将表达式嵌入到可扩展字符串中(例如,在纯 PowerShell 代码中,"1 + 1 equals $(1 + 1)")。

在撰写本文时,Define variables Azure 帮助主题并未详细说明,但基于 official comment in a GitHub docs issue,避免歧义如下

没有转义机制;相反,不引用 Azure 变量的 $(...) 构造保持不变,因此通过传递给 PowerShell。

在典型情况下,PowerShell 表达式不会看起来像 Azure 变量引用(例如,$($foo.bar) 而不是 $(foo.bar)),尽管假设可能存在歧义:$(hostname),其中是一个有效的 PowerShell 子表达式,如果定义了 hostname Azure 变量,则可以被 Azure 抢占。

在这种极端情况下,解决方案是避免使用内联脚本,而是将代码放在外部脚本文件中。

【讨论】:

对于变量可以包含 $ 或 ' 字符的情况有什么建议吗?我们使用单引号来解决 $ 问题,但现在我们遇到了与单引号字符类似的问题。 @Robba,我意识到我从未回应:对于嵌入 ' 字符的值,您可以使用 "..." 引用(假设它不包含 " 字符。),不过如果值 also 包含 $,这可能会导致 PowerShell 端出现不需要的插值。如果两者都不适合您,请尝试在 PowerShell 中将变量作为 - 未引用的 - environment 变量访问,从而绕过引用问题(我无法亲自验证这一点,但据说所有系统和用户 Azure 变量也定义为环境变量,全大写,名称中的任何.字符。替换为_) 感谢您的提示。我从没想过将值作为环境变量访问。在这种情况下,我只是更改了密码,但我确实想知道密码/秘密是否也作为环境变量传入,或者这是否仅适用于常规变量。 @Robba, the docs 说秘密变量默认情况下不会导出为环境变量(出于安全原因),但您可以将它们显式映射到环境变量。 【参考方案2】:

需要在变量中加上引号" "

$SourceDirectoryPath = "$(System.DefaultWorkingDirectory)" + "/solution/project/bin/Debug"
$TargetFilePath = "$(System.DefaultWorkingDirectory)" + "/solution/project/bin/Debug/" + "$(Release.ReleaseName)" + "$(Release.EnvironmentName)"

【讨论】:

这行得通,但您能解释一下为什么行得通吗?我来自 OOP 语言,这没有任何意义。 在 PowerShell 中,你必须为字符串添加引号,没有它 PowerShell 认为它是一个命令。【参考方案3】:

这应该也可以。双引号之外的 $( ) 仅用于组合两个或多个语句。大多数人甚至都不知道。

这实际上是不正确的。我不知道 Azure Pipeline 语法。它只是显示了将 Powershell 和 Azure Pipeline 结合起来是多么令人困惑。如果 $System 是 Powershell 对象,而不是 Azure 宏,这将起作用。

$SourceDirectoryPath = $System.DefaultWorkingDirectory + '/solution/project/bin/Debug'

【讨论】:

以上是关于如何在 Azure DevOps 的 Powershell 内联脚本中正确连接字符串?的主要内容,如果未能解决你的问题,请参考以下文章

如何从 Azure DevOps Pipeline 读取 Azure 文件共享文件

如何从 Azure Devops 服务器迁移到 Azure Devops 服务中的现有组织

如何在 Azure DevOps 变量组中使用 SecureFile?

如何将 Azure Policy 与 Azure DevOps 集成?

如何在 azure devops 中解析 yaml 文件

如何在 Azure DevOps 中自动触发构建拉取请求?