适用于 Windows 的自定义脚本扩展

自定义脚本扩展在 Azure 虚拟机 (VM) 上下载和运行脚本。 使用此扩展进行部署后配置、软件安装或其他任何配置或管理任务。 可以从 Azure 存储或 GitHub 中下载脚本,或在扩展运行时将脚本提供给 Azure 门户。

将自定义脚本扩展与 Azure 资源管理器模板集成。 也可使用 Azure CLI、Azure PowerShell、Azure 门户或 Azure 虚拟机 REST API 来运行它。

本文说明了如何通过使用 Azure PowerShell 模块和 Azure 资源管理器模板来使用自定义脚本扩展。 还介绍 Windows 系统的故障排除步骤。

先决条件

注意

不要使用自定义脚本扩展以相同的 VM 作为参数来运行 Update-AzVM。 该扩展将进行自我等待。

支持的 Windows 操作系统

Windows OS X64
Windows 10 支持
Windows 11 支持
Windows Server 2008 SP2 支持
Windows Server 2008 R2 支持
Windows Server 2012 支持
Windows Server 2012 R2 支持
Windows Server 2016 支持
Windows Server 2016 Core 支持
Windows Server 2019 支持
Windows Server 2019 Core 支持
Windows Server 2022 支持
Windows Server 2022 Core 支持

脚本位置

可以将该扩展配置为使用 Azure Blob 存储凭据来访问 Azure Blob 存储。 脚本位置可以是任何位置,只要 VM 可以路由到该终结点(例如 GitHub 或内部文件服务器)即可。

Internet 连接

要从外部(例如 GitHub 或 Azure 存储)下载脚本,则需要打开其他防火墙和网络安全组 (NSG) 端口。 例如,如果脚本位于 Azure 存储中,可以使用 Azure NSG 存储服务标记来允许访问。

自定义脚本扩展无法绕过证书验证。 如果使用自签名证书等从安全位置进行下载,则可能会收到“根据验证过程,远程证书无效”等错误。 请确保证书已正确安装在 VM 上“受信任的根证书颁发机构”存储中。

如果脚本位于本地服务器上,可能仍然需要打开其他防火墙或 NSG 端口。

提示

  • 输出限制为最后的 4,096 个字节。
  • 正确转义字符将有助于确保正确解析字符串。 例如,在处理文件路径时,始终需要两个反斜杠来转义单个文本反斜杠。 示例:{"commandToExecute": "C:\\Windows\\System32\\systeminfo.exe >> D:\\test.txt"}
  • 此扩展的最高失败率是由于脚本中的语法错误。 验证脚本是否运行且没有错误。 将其他日志记录放入脚本,以便更轻松地查找失败。
  • 编写幂等脚本,确保意外多次运行这些脚本不会导致系统更改。
  • 确保在运行这些脚本时不需要用户输入。
  • 脚本允许运行 90 分钟。 任何更长时间都会导致该扩展预配失败。
  • 不要将重启置于脚本内。 此操作会导致正在安装的其他扩展出现问题,并且重启后该扩展不会继续工作。
  • 如果脚本会导致在安装应用程序和运行脚本之前重启,则使用 Windows 计划任务或 DSC、Chef 或 Puppet 扩展等工具计划重启。
  • 不要运行会导致 VM 代理停止或更新的脚本。 这可能会使该扩展处于“正在转换”状态,导致超时。
  • 该扩展只运行一次脚本。 如果要在每次启动时运行脚本,请使用扩展创建 Windows 计划任务。
  • 如果想要计划脚本何时运行,可使用扩展创建 Windows 计划任务。
  • 脚本运行时,Azure 门户或 Azure CLI 中只会显示“正在转换”扩展状态。 如果希望更频繁地更新正在运行的脚本的状态,请创建自己的解决方案。
  • 自定义脚本扩展不能原生支持代理服务器。 但是,可以使用支持脚本中的代理服务器的文件传输工具,例如 Invoke-WebRequest
  • 请注意脚本或命令可能依赖的非默认目录位置。 将按逻辑对其进行处理。
  • 自定义脚本扩展在 LocalSystem 帐户下运行。
  • 如果计划使用 storageAccountNamestorageAccountKey 属性,则这些属性必须在 protectedSettings 中并置。
  • 只能向 VM 应用一个扩展版本。 若要运行另一个自定义脚本,可以使用新配置更新现有扩展。 也可以删除自定义脚本扩展,然后使用已更新的脚本重新应用该扩展

扩展架构

自定义脚本扩展配置指定脚本位置和要运行命令等设置。 可将此配置存储在配置文件中、在命令行中指定,或者在 Azure 资源管理器模板中指定该配置。

可将敏感数据存储在受保护的配置中,此配置经过加密,只能在 VM 内部解密。 当执行命令包含机密(如密码)或共享访问签名 (SAS) 文件引用时,受保护的配置相当有用。 下面是一个示例:

{
    "apiVersion": "2018-06-01",
    "type": "Microsoft.Compute/virtualMachines/extensions",
    "name": "virtualMachineName/config-app",
    "location": "[resourceGroup().location]",
    "dependsOn": [
        "[concat('Microsoft.Compute/virtualMachines/', variables('vmName'),copyindex())]",
        "[variables('musicstoresqlName')]"
    ],
    "tags": {
        "displayName": "config-app"
    },
    "properties": {
        "publisher": "Microsoft.Compute",
        "type": "CustomScriptExtension",
        "typeHandlerVersion": "1.10",
        "autoUpgradeMinorVersion": true,
        "settings": {
            "timestamp":123456789
        },
        "protectedSettings": {
            "commandToExecute": "myExecutionCommand",
            "storageAccountName": "myStorageAccountName",
            "storageAccountKey": "myStorageAccountKey",
            "managedIdentity" : {},
            "fileUris": [
                "script location"
            ]
        }
    }
}

注意

managedIdentity 属性不能与 storageAccountNamestorageAccountKey 属性结合使用。

一个 VM 上一次只能安装一个扩展版本。 在同一 Azure 资源管理器模板中为同一 VM 两次指定同一个自定义脚本会失败。

可以在 VM 资源内部使用此架构,也可将其用作独立资源使用。 如果在 Azure 资源管理器模板中将此扩展用作独立的资源,则该资源的名称必须采用 virtualMachineName/extensionName 格式。

属性值

名称 值或示例 数据类型
apiVersion 2015-06-15 date
publisher Microsoft.Compute string
type CustomScriptExtension 字符串
typeHandlerVersion 1.10 int
fileUris https://raw.githubusercontent.com/Microsoft/dotnet-core-sample-templates/master/dotnet-core-music-windows/scripts/configure-music-app.ps1 array
timestamp 123456789 32-bit integer
commandToExecute powershell -ExecutionPolicy Unrestricted -File configure-music-app.ps1 字符串
storageAccountName examplestorageacct string
storageAccountKey TmJK/1N3AbAZ3q/+hOXoi/l73zOqsaxXDhqa9Y83/v5UpXQp2DQIBuv2Tifp60cE/OaHsJZmQZ7teQfczQj8hg== string
managedIdentity { }{ "clientId": "31b403aa-c364-4240-a7ff-d85fb6cd7232" }{ "objectId": "12dd289c-0583-46e5-b9b4-115d5c19ef4b" } JSON 对象

注意

这些属性名称区分大小写。 要避免部署问题,请使用如下所示的名称。

属性值详细信息

属性 可选或必需 详细信息
fileUris 可选 要下载的文件的 URL。 如果 URL 是敏感的(例如,其中包含键),则应在 protectedSettings 中指定此字段。
commandToExecute 必须 要运行的入口点脚本。 如果命令包含机密(例如密码)或如果文件 URI 敏感,请使用此属性。
timestamp 可选 仅更改此值以触发脚本的重新运行。 任何整数值都是可接受的,只要它与之前的值不同。
storageAccountName 可选 存储帐户的名称。 如果指定存储凭据,则所有 fileUris 值都必须是 Azure blob 的 URL。
storageAccountKey 可选 存储帐户的访问密钥。
managedIdentity 可选 用于下载文件的托管标识。 有效值包括 clientId(可选,字符串)和 objectId(可选,字符串),前者是托管标识的客户端 ID,后者是托管标识的对象 ID。

公共设置会以明文形式发送到运行脚本的 VM。 受保护设置使用只有 Azure 和 VM 知道的密钥进行加密。 这些设置在发送时保存到 VM。 也就是说,如果设置已加密,它们会以加密方式保存在 VM 上。 用于解密加密值的证书存储在 VM 上。 该证书还用于在运行时解密设置(如有必要)。

虽然使用公共设置可能对调试很有用,但建议使用受保护设置。

可以在公共设置或受保护设置中设置以下值。 该扩展拒绝在公共和受保护设置中设置这些值的任何配置。

  • commandToExecute
  • fileUris

属性:managedIdentity

注意

此属性只能在受保护的设置中指定。

自定义脚本扩展(1.10 及更高版本)支持托管标识,以便从 fileUris 设置中提供的 URL 下载文件。 该属性允许自定义脚本扩展访问 Azure 存储私有 blob 或容器,而无需用户传递 SAS 令牌或存储帐户密钥等机密。

要使用此功能,请将系统分配的标识用户分配的标识添加到运行自定义脚本扩展的 VM 或虚拟机规模集。 然后,授予托管标识访问 Azure 存储容器或 blob 的权限

要在目标 VM 或虚拟机规模集上使用系统分配的标识,请将 managedidentity 设置为空 JSON 对象。

{
  "fileUris": ["https://mystorage.blob.core.chinacloudapi.cn/privatecontainer/script1.ps1"],
  "commandToExecute": "powershell.exe script1.ps1",
  "managedIdentity" : {}
}

要在目标 VM 或虚拟机规模集上使用用户分配的标识,请使用托管标识的客户端 ID 或对象 ID 配置 managedidentity 字段。

{
  "fileUris": ["https://mystorage.blob.core.chinacloudapi.cn/privatecontainer/script1.ps1"],
  "commandToExecute": "powershell.exe script1.ps1",
  "managedIdentity" : { "clientId": "31b403aa-c364-4240-a7ff-d85fb6cd7232" }
}
{
  "fileUris": ["https://mystorage.blob.core.chinacloudapi.cn/privatecontainer/script1.ps1"],
  "commandToExecute": "powershell.exe script1.ps1",
  "managedIdentity" : { "objectId": "12dd289c-0583-46e5-b9b4-115d5c19ef4b" }
}

注意

managedIdentity 属性不能与 storageAccountNamestorageAccountKey 属性结合使用。

模板部署

可使用 Azure 资源管理器模板部署 Azure VM 扩展。 可以在 Azure 资源管理器模板中使用上一部分中详细介绍的 JSON 架构,以便在部署过程中运行自定义脚本扩展。 以下示例显示如何使用自定义脚本扩展:

PowerShell 部署

可以使用 Set-AzVMCustomScriptExtension 命令将自定义脚本扩展添加到现有虚拟机。 有关详细信息,请参阅 Set-AzVMCustomScriptExtension

Set-AzVMCustomScriptExtension -ResourceGroupName <resourceGroupName> `
    -VMName <vmName> `
    -Location myLocation `
    -FileUri <fileUrl> `
    -Run 'myScript.ps1' `
    -Name DemoScriptExtension

示例

使用多个脚本

此示例使用三个脚本来生成服务器。 commandToExecute 属性调用第一个脚本。 然后可以选择如何调用其他方法。 例如,可以使用引导脚本来控制执行,其中包含正确的错误处理、日志记录和状态管理。 将脚本下载到本地计算机运行。

例如,在 1_Add_Tools.ps1 中,是通过将 .\2_Add_Features.ps1 添加到脚本来调用 2_Add_Features.ps1。 为 $settings 中定义的其他脚本重复此过程。

$fileUri = @("https://xxxxxxx.blob.core.chinacloudapi.cn/buildServer1/1_Add_Tools.ps1",
"https://xxxxxxx.blob.core.chinacloudapi.cn/buildServer1/2_Add_Features.ps1",
"https://xxxxxxx.blob.core.chinacloudapi.cn/buildServer1/3_CompleteInstall.ps1")

$settings = @{"fileUris" = $fileUri};

$storageAcctName = "xxxxxxx"
$storageKey = "1234ABCD"
$protectedSettings = @{"storageAccountName" = $storageAcctName; "storageAccountKey" = $storageKey; "commandToExecute" = "powershell -ExecutionPolicy Unrestricted -File 1_Add_Tools.ps1"};

#run command
Set-AzVMExtension -ResourceGroupName <resourceGroupName> `
    -Location <locationName> `
    -VMName <vmName> `
    -Name "buildserver1" `
    -Publisher "Microsoft.Compute" `
    -ExtensionType "CustomScriptExtension" `
    -TypeHandlerVersion "1.10" `
    -Settings $settings `
    -ProtectedSettings $protectedSettings;

从本地共享运行脚本

在此示例中,你可能希望使用本地服务器消息块 (SMB) 服务器作为脚本位置。 之后不需要提供任何其他设置,除了 commandToExecute

$protectedSettings = @{"commandToExecute" = "powershell -ExecutionPolicy Unrestricted -File \\filesvr\build\serverUpdate1.ps1"};

Set-AzVMExtension -ResourceGroupName <resourceGroupName> `
    -Location <locationName> `
    -VMName <vmName> `
    -Name "serverUpdate"
    -Publisher "Microsoft.Compute" `
    -ExtensionType "CustomScriptExtension" `
    -TypeHandlerVersion "1.10" `
    -ProtectedSettings $protectedSettings

使用 CLI 多次运行自定义脚本

如果传递了完全相同的设置,则自定义脚本扩展处理程序会阻止重新运行脚本。 此行为可防止意外重新运行,如果脚本不是幂等的,意外重新运行可能会导致意外行为。 若要确认处理程序是否已阻止重新运行,请查看 C:\WindowsAzure\Logs\Plugins\Microsoft.Compute.CustomScriptExtension\<HandlerVersion>\CustomScriptHandler.log*。 搜索如下所示的警告:

Current sequence number, <SequenceNumber>, is not greater than the sequence number
of the most recently executed configuration. Exiting...

如果想要多次运行自定义脚本扩展,则只能在以下条件下执行该操作:

  • 扩展的 Name 参数与之前的扩展部署中的相同。
  • 已更新配置。 可以将动态属性添加到命令中,如时间戳。 如果处理程序检测到配置设置中发生更改,则该程序会将更改视为明确的重新执行脚本的意愿。

或者,可以将 ForceUpdateTag 属性设置为 true

使用 Invoke-WebRequest

如果在脚本中使用 Invoke-WebRequest,则必须指定参数 -UseBasicParsing。 如果未指定参数,则在检查详细状态时会出现以下错误:

The response content cannot be parsed because the Internet Explorer engine
is not available, or Internet Explorer's first-launch configuration
is not complete. Specify the UseBasicParsing parameter and try again.

虚拟机规模集

如果从 Azure 门户部署自定义脚本扩展,则不能控制用于访问存储帐户中的脚本的 SAS 令牌的过期时间。 初始部署正常,但当存储帐户的 SAS 令牌过期时,任何后续的缩放操作都将失败,因为自定义脚本扩展不能再访问存储帐户。

在虚拟机规模集上部署自定义脚本扩展时,建议使用 PowerShellAzure CLI 或 Azure 资源管理器模板。 这样,你可以选择使用托管标识,或者直接控制 SAS 令牌的过期时间,以便根据需要随时访问存储帐户中的脚本。

故障排除和支持

有关扩展部署状态的数据可以从 Azure 门户和使用 Azure PowerShell 模块进行检索。 若要查看某个 VM 的扩展部署状态,请运行以下命令:

Get-AzVMExtension -ResourceGroupName <resourceGroupName> `
    -VMName <vmName> -Name myExtensionName

扩展输出将记录到可在目标虚拟机上的以下目录中找到的文件中:

C:\WindowsAzure\Logs\Plugins\Microsoft.Compute.CustomScriptExtension

指定的文件将下载到目标虚拟机上的以下文件夹中:

C:\Packages\Plugins\Microsoft.Compute.CustomScriptExtension\1.*\Downloads\<n>

在前面的路径中,<n> 是一个十进制整数,在不同的扩展执行中可能会有所不同。 1.* 值与扩展的 typeHandlerVersion 的当前实际值匹配。 例如,实际目录可能是 C:\Packages\Plugins\Microsoft.Compute.CustomScriptExtension\1.8\Downloads\2

运行 commandToExecute 命令时,扩展会将该目录(例如 ...\Downloads\2)设置为当前工作目录。 此过程使得可以通过 fileURIs 属性使用相对路径查找下载的文件。 下面是下载的文件的示例:

fileUris 中的 URI 相对下载位置 绝对下载位置
https://someAcct.blob.core.chinacloudapi.cn/aContainer/scripts/myscript.ps1 ./scripts/myscript.ps1 C:\Packages\Plugins\Microsoft.Compute.CustomScriptExtension\1.8\Downloads\2\scripts\myscript.ps1
https://someAcct.blob.core.chinacloudapi.cn/aContainer/topLevel.ps1 ./topLevel.ps1 C:\Packages\Plugins\Microsoft.Compute.CustomScriptExtension\1.8\Downloads\2\topLevel.ps1

绝对目录路径可在 VM 的生存期内更改,但不可在自定义脚本扩展的某次执行期间更改。

由于绝对下载路径可能会随时间而变化,因此在可能情况下,最好是在 commandToExecute 字符串中选择使用相对的脚本/文件路径。 例如:

"commandToExecute": "powershell.exe . . . -File \"./scripts/myscript.ps1\""

通过 fileUris 属性列表下载的文件的第一个 URI 段之后的路径信息将予以保留。 如之前的表格中所示,下载的文件映射到下载子目录中,以便反映 fileUris 值的结构。

支持

如果对本文中的任何观点存在疑问,可以联系 Azure 支持上的 Azure 专家。 还可以提出 Azure 支持事件。 请转到 Azure 支持站点提交请求。 有关使用 Azure 支持的信息,请阅读 Azure 支持常见问题