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:

 # Ensure PS remoting is enabled, although this is enabled by default for Azure VMs
Enable-PSRemoting -Force

# Create rule in Windows Firewall
New-NetFirewallRule -Name "WinRM HTTPS" -DisplayName "WinRM HTTPS" -Enabled True -Profile Any -Action Allow -Direction Inbound -LocalPort 5986 -Protocol TCP

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

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

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, { }.

 # define a temporary file in the users TEMP directory
$file = $env:TEMP + "\ConfigureWinRM_HTTPS.ps1"

#Create the file containing the PowerShell

{

# POWERSHELL TO EXECUTE ON REMOTE SERVER BEGINS HERE

# Ensure PS remoting is enabled, although this is enabled by default for Azure VMs
Enable-PSRemoting -Force

# Create rule in Windows Firewall
New-NetFirewallRule -Name "WinRM HTTPS" -DisplayName "WinRM HTTPS" -Enabled True -Profile Any -Action Allow -Direction Inbound -LocalPort 5986 -Protocol TCP

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

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

# POWERSHELL TO EXECUTE ON REMOTE SERVER ENDS HERE

} | out-file $file

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:

 # Get storage account name
$storageaccountname = $vm.StorageProfile.OsDisk.Vhd.Uri.Split('.')[0].Replace('https://','')

# get storage account key
$key = (Get-AzureRmStorageAccountKey -Name $storageaccountname -ResourceGroupName $rgname).Key1

# create storage context
$storagecontext = New-AzureStorageContext -StorageAccountName $storageaccountname -StorageAccountKey $key

# create a container called scripts
New-AzureStorageContainer -Name "scripts" -Context $storagecontext

#upload the file
Set-AzureStorageBlobContent -Container "scripts" -File $file -Blob "ConfigureWinRM_HTTPS.ps1" -Context $storagecontext

Finally, I create the custom script extension:

 # Create custom script extension from uploaded file
Set-AzureRmVMCustomScriptExtension -ResourceGroupName $rgname -VMName $vmname -Name "EnableWinRM_HTTPS" -Location $vm.Location -StorageAccountName $storageaccountname -StorageAccountKey $key -FileName "ConfigureWinRM_HTTPS.ps1" -ContainerName "scripts"

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:

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

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

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