Use Remote PowerShell to Manage Your Azure PaaS Compute Instances

 

Update: Please find complete source for this solution here: WinRM-Azure-PaaS

The Windows Remote Management (WinRM) service implements the WS-Management protocol in Windows.  Many remote management tasks are made possible with WinRM, as I.T. administrators are well aware.  There are both command line and PowerShell variants on how to accomplish most of these tasks. But what does this have to do with Azure web roles and Azure worker roles?

Imagine for a moment that your API is running in an Azure web role.  You just updated it, and for some odd reason it’s not working right.  But you’ve seen this before.  The AppPool needs to be restarted.  On each instance.  There are 10 of them.  You don’t want to, but you’re going to have to log in to each instance and manually restart the AppPools.  Or, maybe your app manages some local storage.  Periodically you need to check to see how full it is, perhaps delete old log files, whatever.  Wouldn’t it be great if you could write a script that runs on your desktop that would loop through each instance and do the task?

Here’s my technical tooling configuration:

  • Visual Studio 2013 Update 4
  • Azure SDK 2.4
  • Windows 8.1 laptop
  • Azure web role is OS Family 4 = Windows Server 2012 R2

Here are the steps to accomplish this:

  • Add a startup task to your web or worker role that does the following:
    • create a user with administrator rights (so your workstation can authenticate into the instance)
    • create a WinRM listener on HTTPS and the default port 5986
  • Add an SSL certificate (if you don’t already have one) to the solution
  • Add an InstanceInputEndpoint to the Service Definition of your role.
  • Deploy your service to Azure
  • From PowerShell, Enter-PSSession to each instance by port number, execute whatever task you need to, and Exit-PSSession.

A key feature of the Azure platform that makes this possible is the InstanceInputEndpoint.  This feature makes it possible to direct communications to a specific instance of a multi-instanced PaaS role – either web or worker role.  Here’s what it looks like:

InstanceInputEndpoint

And note: the InstanceInputEndpoint is in addition to other endpoints that you have defined.  So, you can have port 80 or 443 for your web traffic, PLUS you can define an InstanceInputEndpoint that arrives at port 5986 of each compute instance.  Which is great, because it allows me to use WinRM in its default configuration.

And finally, the details:  (Indentation to accommodate long lines.)

Startup task.  Add the following lines to the Service Definition file.  The <WebRole line is only present for context. 

 <WebRole name="WebRole1" vmsize="Small">
  <Startup>
    <Task commandLine="EnableWinRM.cmd" executionContext="elevated" 
         taskType="simple" />
  </Startup>

EnableWinRM.cmd:

 net user winrm_user > nul && 
  echo User Exists || (net user winrm_user S3cr3tl7 /add >> EnableWinRMLog.txt
net localgroup administrators winrm_user /add  >> EnableWinRMLog.txt
echo User Created >> EnableWinRMLog.txt)
PowerShell -command 
  Set-ExecutionPolicy -ExecutionPolicy Unrestricted >> EnableWinRMLog.txt
PowerShell .\EnableWinRM.ps1 >> EnableWinRMLog.txt
exit /B 0

EnableWinRM.ps1:  (the thumbprint is that of the SSL cert that you uploaded to the Azure cloud service)

 $thumbprint = 'XXXXXXXC8131944706F7DDD7001DCA8F'
$certId = '<your hostname>'
winrm create winrm/config/listener?Address=*+Transport=HTTPS 
  `@`{Hostname=`"($certId)`"`;
  CertificateThumbprint=`"($thumbprint)`"`}
Set-Item WSMan:\localhost\Shell\MaxMemoryPerShellMB 2000

And now the Service Definition for the InstanceInputEndpoint.

 <Endpoints>
  <InputEndpoint name="Endpoint1" 
      protocol="http" port="80" />
  <InstanceInputEndpoint name="WinRM" 
      localPort="5986" protocol="tcp">
    <AllocatePublicPortFrom>
      <FixedPortRange min="30000" 
          max="30100"/>
    </AllocatePublicPortFrom>
  </InstanceInputEndpoint>
</Endpoints>

Note that I show the definition of Endpoint1 on port 80, but just for context. It's not necessary for the InstanceInputEndpoint definition to be valid.  And the name ‘WinRM’ is arbitrary.  What’s not arbitrary is the localPort, nor the FixedPortRange.  Be sure you have enough ports defined for all of your instances.  After deploying your service to Azure, use RDP to verify that all’s well.  In e:\approot\bin\EnableWinRMLog.txt you should see favorable looking messages.

Now for the fun part!

Here’s a PowerShell script that will restart the web role’s AppPool.  Run it on your workstation. 

 # run this line by itself.  A dialog will ask for the 
# userid/password in EnableWinRM.cmd
$cred = Get-Credential

# then run this loop
for ($i=0; $i -lt 3; $i++) {
    $port = 30000 + $i
    $uri = "https://golive-XXXX.cloudapp.net:$port"
    
    $psopt = New-PSSessionOption -SkipCACheck -SkipCNCheck -SkipRevocationCheck
    Enter-PSSession -ConnectionUri $uri -Credential $cred `
      -SessionOption $psopt `
      -Authentication Negotiate
    import-module WebAdministration
    $pool = Get-ChildItem IIS:\AppPools | where-object {$_.Name -NotLike '.NET*'}
    Restart-WebAppPool $pool.Name
    Exit-PSSession
}

Cheers!