Create a Custom Script Extension for an Azure Resource Manager VM using PowerShell

Following on from my previous two posts regarding WinRM over HTTPS the next stage was to automate the steps that needed to be carried out inside the Azure VM. In my original post I had a mix of PowerShell, command prompt and copy and paste! Andy Slowey provided me with the following PowerShell to optimize the WinRM over HTTPS server side configuration:

<br />
	# Ensure PS remoting is enabled, although this is enabled by default for Azure VMs<br />
	Enable-PSRemoting -Force
</p>

<p>
	# Create rule in Windows Firewall<br />
	New-NetFirewallRule -Name &quot;WinRM HTTPS&quot; -DisplayName &quot;WinRM HTTPS&quot; -Enabled True -Profile Any -Action Allow -Direction Inbound -LocalPort 5986 -Protocol TCP
</p>

<p>
	# Create Self Signed certificate and store thumbprint<br />
	$thumbprint = (New-SelfSignedCertificate -DnsName $env:COMPUTERNAME -CertStoreLocation Cert:\LocalMachine\My).Thumbprint
</p>

<p>
	# Run WinRM configuration on command line. DNS name set to computer hostname.<br />
	$cmd = &quot;winrm create winrm/config/Listener?Address=*+Transport=HTTPS @{Hostname=&quot;&quot;$env:computername&quot;&quot;; CertificateThumbprint=&quot;&quot;$thumbprint&quot;&quot;}&quot;
</p>

<p>
	cmd.exe /C $cmd<br />
	

In search of going one better, I decided to find a way to avoid the need to use RDP so that the whole process could be automated. Scripts can be executed within an Azure VM without logging into the server using Custom Script extensions. Initially the script needs to be created locally. I have used the PowerShell above, but if you wanted to do something different within the VM, just replace the PowerShell between the curly brackets, { }.

<br />
	# define a temporary file in the users TEMP directory<br />
	$file = $env:TEMP + &quot;\ConfigureWinRM_HTTPS.ps1&quot;
</p>

<p>
	#Create the file containing the PowerShell
</p>

<p>
	{
</p>

<p>
	# POWERSHELL TO EXECUTE ON REMOTE SERVER BEGINS HERE
</p>

<p>
	# Ensure PS remoting is enabled, although this is enabled by default for Azure VMs<br />
	Enable-PSRemoting -Force
</p>

<p>
	# Create rule in Windows Firewall<br />
	New-NetFirewallRule -Name &quot;WinRM HTTPS&quot; -DisplayName &quot;WinRM HTTPS&quot; -Enabled True -Profile Any -Action Allow -Direction Inbound -LocalPort 5986 -Protocol TCP
</p>

<p>
	# Create Self Signed certificate and store thumbprint<br />
	$thumbprint = (New-SelfSignedCertificate -DnsName $env:COMPUTERNAME -CertStoreLocation Cert:\LocalMachine\My).Thumbprint
</p>

<p>
	# Run WinRM configuration on command line. DNS name set to computer hostname, you may wish to use a FQDN<br />
	$cmd = &quot;winrm create winrm/config/Listener?Address=*+Transport=HTTPS @{Hostname=&quot;&quot;$env:computername&quot;&quot;; CertificateThumbprint=&quot;&quot;$thumbprint&quot;&quot;}&quot;<br />
	cmd.exe /C $cmd
</p>

<p>
	# POWERSHELL TO EXECUTE ON REMOTE SERVER ENDS HERE
</p>

<p>
	} | out-file $file<br />
	

Once the script is created it needs to be uploaded to Azure Blob storage. To do this I found the storage account that the VM OS disk was created in, created a container within that account and uploaded the script:

<br />
	# Get the VM we need to configure<br />
	$vm = Get-AzureRmVM -ResourceGroupName $rgname -Name $VMName
</p>

<p>
	# Get storage account name<br />
	$storageaccountname = $vm.StorageProfile.OsDisk.Vhd.Uri.Split(&#39;.&#39;)[0].Replace(&#39;https://&#39;,&#39;&#39;)
</p>

<p>
	# get storage account key<br />
	$key = (Get-AzureRmStorageAccountKey -Name $storageaccountname -ResourceGroupName $rgname).Key1
</p>

<p>
	# create storage context<br />
	$storagecontext = New-AzureStorageContext -StorageAccountName $storageaccountname -StorageAccountKey $key
</p>

<p>
	# create a container called scripts<br />
	New-AzureStorageContainer -Name &quot;scripts&quot; -Context $storagecontext
</p>

<p>
	#upload the file<br />
	Set-AzureStorageBlobContent -Container &quot;scripts&quot; -File $file -Blob &quot;ConfigureWinRM_HTTPS.ps1&quot; -Context $storagecontext<br />
	

Finally, I create the custom script extension:

<br />
	# Create custom script extension from uploaded file<br />
	Set-AzureRmVMCustomScriptExtension -ResourceGroupName $rgname -VMName $vmname -Name &quot;EnableWinRM_HTTPS&quot; -Location $vm.Location -StorageAccountName $storageaccountname -StorageAccountKey $key -FileName &quot;ConfigureWinRM_HTTPS.ps1&quot; -ContainerName &quot;scripts&quot;<br />
	

This, along with the previous post regarding creating the network security group rule successfully configures WinRM over HTTPS within the VM without the need to do anything outside of a local PowerShell prompt. I understand this needs to be easier to use so my next post I will bring it all together into a function that can easily be reused.

For reference the PowerShell to connect to the remote server is as follows:

<br />
	# Disable, CA and CN check. If use correct FQDN and install certificate locally as is a best practice this is not required<br />
	$so = New-PsSessionOption -SkipCACheck -SkipCNCheck
</p>

<p>
	# Disable, CA and CN check. If use correct FQDN and install certificate locally as is a best practice this is not required<br />
	Enter-PSSession -ComputerName &lt;server_ip_or_fqdn&gt;&nbsp;&nbsp; -Credential &lt;admin_username&gt; -UseSSL -SessionOption $so<br />
	

Note: We have noticed some differences between different versions of the Azure RM PowerShell modules. For reference, here is the module versions that I tested the scripts on:

Name                               Version

Azure                                1.0.4
Azure.Storage                  1.0.4
AzureRM.Compute           1.2.2
AzureRM.Network            1.0.4
AzureRM.Profile               1.0.4
AzureRM.Resources        1.0.4
AzureRM.Storage             1.0.4

16 Comments

  1. Ahmed IG

    As always, your posts come first whenever am searching for any techie stuff regarding Azure.
    I was thinking of having a look at your blog, until I realized that am actually in your blog! 🙂

    Keep going Marcus!

    Cheers

    Reply
  2. Marcus (Post author)

    Thanks! Do let me know what you would like to read about!

    Reply
  3. Soniya

    Hi Marcus, Can you please guide me to use the custom extension script via ARM template? I have stored my script in Azure blob storage and i have added my customscript extension in ARM template. I am trying to deploy this on existing  load balanced VMs via Powershell. However I am getting this error:   New-AzureRmResourceGroupDeployment : 4:27:43 PM – Resource Microsoft.Compute/virtualMachines 'myVM1' failed with message 'The resource operation completed with terminal provisioning state 'Failed'.' At line:1 char:1 + New-AzureRmResourceGroupDeployment -ResourceGroupName MyResUS -TemplateFile "C:\ … + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~     + CategoryInfo          : NotSpecified: (:) [New-AzureRmResourceGroupDeployment], Exception     + FullyQualifiedErrorId : Microsoft.Azure.Commands.Resources.NewAzureResourceGroupDeploymentCommand   New-AzureRmResourceGroupDeployment : 4:27:43 PM – VM has reported a failure when processing extension ‘keysExtension’. Error message: “Failed to download all specified files. Exiting. Error Message: The remote server returned an error: (400) Bad Request.”. At line:1 char:1 + New-AzureRmResourceGroupDeployment -ResourceGroupName MyResUS -TemplateFile “C:\ … + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~     + CategoryInfo          : NotSpecified: (:) [New-AzureRmResourceGroupDeployment], Exception     + FullyQualifiedErrorId : Microsoft.Azure.Commands.Resources.NewAzureResourceGroupDeploymentCommand   New-AzureRmResourceGroupDeployment : 4:27:58 PM – Resource Microsoft.Compute/virtualMachines ‘myVM0’ failed with message ‘The resource operation completed with terminal provisioning state ‘Failed’.’ At line:1 char:1 + New-AzureRmResourceGroupDeployment -ResourceGroupName MyResUS -TemplateFile “C:\ … + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~     + CategoryInfo          : NotSpecified: (:) [New-AzureRmResourceGroupDeployment], Exception     + FullyQualifiedErrorId : Microsoft.Azure.Commands.Resources.NewAzureResourceGroupDeploymentCommand

    Reply
    1. Marcus (Post author)

      Hello,

      You will need to paste the template for me to review. It is likely the URL to the script isn’t valid.

      Marcus

      Reply
  4. Serg

    Set-AzureRmVMCustomScriptExtension -ResourceGroupName $ResourceGroupName -VMName $VMname -Name "RunMyTest" -Location $VM.location -StorageAccountName $storageAccountName -StorageAccountKey $storagekey -FileName "killallrdp00.ps1" -Run "killallrdp00.ps1" -ContainerName $StorageContainer

    Run this in the: Azure / Automation/ RunBooks.

    In the Target server TS2 i see – Script Run. This is OK.

    But, in the RunBook i see : Script work, work,work………..  Command Set-AzureRmVMCustomScriptExtension  not stop ;((
    Please, Help me.

    Target server TS2 is: Win2008R2

     

    Reply
  5. Justin Thomas

    Hi, thanks for writing this, I have been using it to try and create my own custom script execution process.

    Unfortunately, the line:

     

    Set-AzureRmVMCustomScriptExtension -ResourceGroupName $rgname -VMName $vmname -Name "EnableWinRM_HTTPS" -Location $vm.Location -StorageAccountName $storageaccountname -StorageAccountKey $key -FileName "ConfigureWinRM_HTTPS.ps1" -ContainerName "scripts"

    Doesn't seem to work for me, PowerShell / Azure complains about this:

    + … Settings @{"workspaceKey"= "workspaceID"} -StorageAccountName "scriptstorageacc …
    +                                               ~~~~~~~~~~~~~~~~~~~
        + CategoryInfo          : InvalidArgument: (:) [Set-AzureRmVMExtension], ParameterBindingException
        + FullyQualifiedErrorId : NamedParameterNotFound,Microsoft.Azure.Commands.Compute.SetAzureVMExtensionCommand

    Basically it thinks the -StorageAccountName parameter is invalid.  Now I can get the extension to install without specifying this, but then it has a failed status because no script file has been specified.  Do you have any idea what I might be doing wrong?

     

    thanks!

    Reply
    1. Marcus (Post author)

      The storage account name must be unique and.. "The name of the storage account within the specified resource group. Storage account names must be between 3 and 24 characters in length and use numbers and lower-case letters only."

      Reply
  6. Igor

    Is there any way how to use custom script while provisioning machines? This is showing how to use custom script on already provisioned VM. It was possible in old Azure, but seems like its not possible in new one.

    Reply
    1. Marcus (Post author)
  7. Viresh Doshi

    some of the commands may not work due to Powershell compatability. 

     

    For example, I use this line instead to grab the key1 value:

    (Get-AzureRmStorageAccountKey -ResourceGroupName "RG01" -AccountName "MyStorageAccount").Value[0]

    Reply
  8. Rajeev

    Hi Marcus, Is there a way I can get the output of my script? For example if I run a script with just "whoami", where can I see the output of the script?

    Regards

    Reply
    1. Marcus (Post author)

      You can use Get-AzureRMVMDiagnosticsExtension as per https://msdn.microsoft.com/en-us/library/mt603678.aspx . there is a good example here: http://stackoverflow.com/questions/38152873/how-can-i-get-the-output-of-a-customscriptextenstion-when-using-azure-resource-m . Or alternatively browse to the extension in the portal.

      Reply
  9. md m haque

    i get error running the  $cmd = "winrm create winrm/config/Listener?Address=*+Transport=HTTPS @{Hostname=""$env:computername""; CertificateThumbprint=""$thumbprint""}" cmd.exe /C $cmd

    Error:

    At line:1 char:144
    + … thumbprint""}" cmd.exe /C $cmd
    +                    ~~~~~~~
    Unexpected token 'cmd.exe' in expression or statement.
        + CategoryInfo          : ParserError: (:) [], ParentContainsErrorRecordException
        + FullyQualifiedErrorId : UnexpectedToken

     

     

    Reply
    1. Marcus (Post author)

      Looks like a line break was missing, before cmd.exe, have updated the post. Let me know if that helps.

      Reply
  10. Ramesh Kanagaraj

    Hi Marcus,

    I have followed your steps its working good up to Set-AzureRmVMCustomScriptExtension, I got the file into my new VM. But when I tried to run Enter-PSSession its showing below error.

    Enter-PSSession : Connecting to remote server xxxxx.cloudapp.net failed with the following error message : The WinRM client
    cannot process the request because the server name cannot be resolved. For more information, see the about_Remote_Troubleshooting Help topic.
    At line:1 char:1
    + Enter-PSSession -ComputerName "xxxxxxx …
    + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
        + CategoryInfo          : InvalidArgument: (xxxxxxx:String) [Enter-PSSession], PSRemotingTransportException
        + FullyQualifiedErrorId : CreateRemoteRunspaceFailed

    I have enabled WinRM local machine also.

    Another doubt is currently am using Azure Free Trial subscribtion. I have used nslookup <VM name> to see the VM full name to use Enter-PSSession.

    Regards,

    Ramesh

    Reply
    1. Marcus (Post author)

      As you suggest looks like a DNS issue. Does the DNS name resolve to an IP address?

      Reply

Leave a Comment

Your email address will not be published. Required fields are marked *