• 5 min read

Azure Automation: Run tasks on Azure Virtual Machines – without opening ports

This post explains how to use an Azure Automation runbook to push a script or scriptblock to an Azure VM using the Custom Script extension component of the Azure VM Agent.

One great thing about Azure Automation is its ability to interact with your Azure resources using the included Azure PowerShell module. Querying, adding, changing and deleting resources is never more than a few lines of PowerShell-in-a-Runbook away.

One of the most common things we as IT Pros will want to do is run tasks on our Azure virtual machines. By default, whenever an Azure VM is provisioned an endpoint is defined for allowing PowerShell access from remote clients, as shown below:

Run tasks on Azure Virtual Machines - without opening ports

Previously we have provided a sample runbook which enabled you to run a PowerShell command on an Azure VM from Azure Automation. This sample relied on the Connect-AzureVM runbook, which relied on the use of a credential to remotely access the Azure VM via WinRM PowerShell Remoting.

There may be times, however, when a corporate or security policy does not allow you to have an open PowerShell endpoint on your Azure VMs. For example, if you are using Azure VMs for bursting or extending your enterprise datacenter over ExpressRoute, you may not want your Azure VMs’ PowerShell endpoints open for access, even if it is only authenticated access, to the Internet. Or you may have a Group Policy applied to your Azure VMs which controls WinRM settings and does not allow PowerShell Remoting. In either case, you will not have the ability to run tasks on your Azure VMs using PowerShell Remoting.

So, how can you run tasks on your Azure VMs when you can’t use PowerShell Remoting? Well, when you deployed your Azure VMs you would have noticed during the setup the option to install the Azure VM Agent:

Run tasks on Azure Virtual Machines - without opening ports

The Azure VM Agent enables the Azure Fabric Controller to see inside your VM, allowing you to interact with the VM in other ways. One method of access the VM Agent provides is the ability to use the Azure VM Custom Script extension.

The Custom Script extension lets you tell the Azure VM Agent inside an Azure VM to download a script file(s) from an Azure Storage blob(s) and run the script in the VM. You may have seen the Custom Script option when provisioning a new Azure VM, which lets you run a script after the VM has been created, but it is also possible to run a script at any time on an Azure VM using the Custom Script Extension. This provides us with another means by which we can run scripts on our Azure VMs, without open ports or PowerShell Remoting, which we can leverage from Azure Automation!

Using the Custom Script Extension with Azure Automation

To take advantage of the Custom Script extension in Azure Automation, we’ve created a runbook called Push-AzureVMCommand which you can find in the Script Center, or you can import it directly into your Azure Automation account through the Azure Automation Runbook Gallery in the Azure Management Portal. With this runbook, you can run any script against an Azure VM, without the need to have the PowerShell endpoint enabled or PowerShell Remoting configured inside the VM.

Run tasks on Azure Virtual Machines - without opening ports

The Push-AzureVMCommand runbook does the following:

  1. Takes a script block that you provide and writes it to a .ps1 file, then uploads that file as an Azure Storage blob. Or optionally, if you have already stored an existing script on Azure Storage, you can use that directly.
  2. Configures the Custom Script extension on the VM you specify to use the script in Azure Storage.
  3. If requested, the runbook will wait for completion of the script and return any output the script produced.

This runbook requires an Azure Automation credential asset, containing an Azure Active Directory OrgID user, for authenticating to Azure. For details on creating an OrgID and a credential asset please refer to this previous post.

To use the Push-AzureVMCommand runbook, once it has been imported into your Azure Automation account and published, simply invoke the runbook inline in another runbook, and provide the required parameters. Here are two examples of calling the Push-AzureVMCommand runbook and the response you will receive.

Run tasks on Azure Virtual Machines - without opening ports

If your script contains input parameters, these can also be passed to the Push-AzureVMCommand runbook using the ScriptArguments parameter.

If you have set the WaitForCompletion flag to $true, Push-AzureVMCommand will return the output of your script. When WaitForCompletion is set to $false however, Push-AzureVMCommand will exit with success while your script continues to be run by the Custom Script extension.

The output of the script is returned via the StdOut stream of the PowerShell session on your Azure VM to the Custom Script extension, and stored into a status value of the VM object. This output is passed as a string value, however you may want to output an object from your script, and use that object in your runbook. To do this you can serialize your object as XML within the script, then de-serialize it back into an object within your runbook.

For example, in your script serialize your object and output it, taking care to only output the $SerializedOutput object from your script. Then, when calling the Push-AzureVMCommand, capture the output of the script and prepare it for use by deserializing it. The screenshot below demonstrates an example of this process:

Run tasks on Azure Virtual Machines - without opening ports

Some things to note when using this runbook:

  • The runbook works only against Azure VMs
  • The runbook requires the target virtual machine to have the Azure VM Agent installed, and the Custom Script extension enabled.
  • The Custom Script extension runs sequentially, so only one script can be submitted to it at a time.
  • The Custom Script extension stores the output of the last run script only, previous output data is removed when a new script is submitted.
  • A script run using the Custom Script extension will interact with the target Virtual Machine only. To interact with other Virtual Machines in the same Virtual Network, appropriate credentials and WinRM settings must be configured (e.g. CredSSP).
  • When passing the ScriptBlock parameter to Push-AzureVMCommand, since the parameter type is string, if the script block contains any parameters or variables (i.e. $a = Get-Process), ensure each parameter / variable in the scriptblock is escaped by putting the grave character (`) in front of it (i.e. `$a = Get-Process). This tells the PowerShell engine not to attempt to lookup/replace the variable with one defined outside the scriptblock. Any un-escaped variables in a scriptblock will result in an exception when running the Runbook.
  • The CSE StdOut output field is somewhat limited in how much output it can store. This can affect how many objects you can serialize / deserialize from the script back to the runbook. Since a serialized object is usually a very long string, the serialize/deserialize example above will only work with “small” objects.


Now you have what you need to run scripts against your Azure VMs without the use of the PowerShell endpoint or PowerShell Remoting, by either providing your scripts dynamically at runtime, or from a collection of scripts in an Azure Storage container.

Happy automating!

Just getting started with Azure Automation? Learn about the service here, and follow Azure Automation on Twitter.