PowerShell: Shutdown Azure VMs Outside Working Hours

I run some Azure Virtual Machine’s for work stuff (testing, remote access) and non-work stuff (game servers).  The specification is pretty low but I like to shut it down from Azure to reduce any charges from it.  This might lose their currently allocated IP address (they might get a different one on restart) but that’s not an issue for me.  Over the weekend is a good time as I almost never use Azure then.

Unfortunately my memory is pretty rubbish especially during the Friday afternoon excitement/rush.  What I want is a script that will run on my home machine, check the time and then shut my VMs down if we’re out of working hours.

The script follows with the explanation afterwards.I want a script that constantly loops, checking the time.  If it’s outside of work hours I want to check all my VMs and then shut them down if they’re running.  I also want the reverse to happen during working hours.

Here’s the script;

[CmdletBinding()]
param
()
Write-Verbose "Started Check-AzureVMState Script"
$SleepSeconds=30
$AzureModuleName="Azure"
$StartRunningTime="8:00"
$EndRunningTime="17:00"
$StoppedDays=@("Saturday","Sunday")
$ComputerHashTables=@(@{"Name"="test";"ServiceName"="test";"DNSName"="test.cloudapp.net";"Port"="443"})
$ComputerObjects=$ComputerHashTables | Foreach {New-Object PSObject -Property $_}
Write-Verbose "Importing Azure Module and Connecting"
if (!($AzureModuleName -in (Get-Module).Name))
{
    Import-Module $AzureModuleName
}
While ((Get-AzureAccount) -eq $Null)
{
    Add-AzureAccount
}
while ($true)
{
    $CurrentDate=Get-Date
    if (!($CurrentDate.DayOfWeek -in $StoppedDays) -and (($CurrentDate -gt (Get-Date $StartRunningTime)) -and
            ($CurrentDate -lt (Get-Date $EndRunningTime))))
    {
        Write-Verbose "Valid Time"
        #VM Should be Running
        Foreach ($ComputerObject in $ComputerObjects)
        {            
            if (!(Test-NetConnection -ComputerName $ComputerObject.DNSName -Port $ComputerObject.Port -InformationLevel Quiet))
            {
                Write-Verbose "Start VM"
                Start-AzureVM -Name $ComputerObject.Name -ServiceName $ComputerObject.ServiceName
            }else
            {
                Write-Verbose "$($ComputerObject.DNSName) already running"
            }
        }
    }else
    {
        Write-Verbose "Should not be running"
        #VM Should not be running
        Foreach ($ComputerObject in $CommputerObjects)
        {
            if ((Test-NetConnection -ComputerName $ComputerObject.DNSName -Port $ComputerObject.Port -InformationLevel Quiet))
            {
                Write-Verbose "Stop VM"
                Stop-AzureVM -Name $ComputerObject.Name -ServiceName $ComputerObject.ServiceName -Force
            }else
            {
                Write-Verbose "$($ComputerObject.DNSName) already stopped"
            }
        }
    }
    Sleep $SleepSeconds
}

Here’s a breakdown of what I did;

[CmdletBinding()]
param
()
Write-Verbose "Started Check-AzureVMState Script"
$SleepSeconds=30000
$AzureModuleName="Azure"
$StartRunningTime="8:00"
$EndRunningTime="17:00"
$StoppedDays=@("Saturday","Sunday")
$ComputerHashTables=@(@{"Name"="test";"ServiceName"="test";"DNSName"="test.cloudapp.net";"Port"="443"})
$ComputerObjects=$ComputerHashTables | Foreach {New-Object PSObject -Property $_}
Write-Verbose "Importing Azure Module and Connecting"

In the beginning part of the script, I turn on CmdletBinding so I can use Write-Verbose and set variables for the script.

$SleepSeconds is how long the script waits (30000 milleseconds).

$AzureModuleName is what I check for to see if the Azure module is already loaded.

$StartRunningTime, $EndRunningTime and $StoppedDays define working hours and the weekend.

$ComputerHashTables is an array of hashtables, each of which defines a computer.  For each I define its VM name, the DNS name, the name of the Azure service it uses and the port I want to test to see if it’s up.

$ComputerObjects is an array of objects made from the hashtables in $ComputerHashTables.

if (!($AzureModuleName -in (Get-Module).Name))
{
    Import-Module $AzureModuleName
}
While ((Get-AzureAccount) -eq $Null)
{
    Add-AzureAccount
}

The first of these two statements check if the Azure module is imported (by checking if the name of the module exists in the list of imported modules) and import it if necessary.

Second, I loop until there is a valid Azure account.  If one doesn’t exist it is added (at this point the user will be prompted for an id and password).  I loop here because I noticed that sometimes the prompt doesn’t come up correctly.

while ($true)
{
    $CurrentDate=Get-Date
    if (!($CurrentDate.DayOfWeek -in $StoppedDays) -and (($CurrentDate -gt (Get-Date $StartRunningTime)) -and
            ($CurrentDate -lt (Get-Date $EndRunningTime))))
    {
        Write-Verbose "Valid Time"
        #VM Should be Running
        Foreach ($ComputerObject in $ComputerObjects)
        {       
            if (!(Test-NetConnection -ComputerName $ComputerObject.DNSName -Port $ComputerObject.Port -InformationLevel Quiet))
            {
                Write-Verbose "Start VM"
                Start-AzureVM -Name $ComputerObject.Name -ServiceName $ComputerObject.ServiceName
            }else
            {
                Write-Verbose "$($ComputerObject.DNSName) already running"
            }
        }

From this point, the script loops forever (while ($true)).  The script then checks if todays day is listed in $StoppedDays and that the current time is within $StartRunningTime and $EndRunningTime.  If that’s the case, all the VMs should be running.

I then loop through all the defined systems in $ComputerObjects.  I use the DNSName and Port defined on each object in Test-NetworkConnection to see if the system is up;  if it isn’t I start the VM using the $ComputerObject Name and ServiceName.

If the VM is already running I just display that with Write-Verbose.

    }else
    {
        Write-Verbose "Should not be running"
        #VM Should not be running
        Foreach ($ComputerObject in $CommputerObjects)
        {
            if ((Test-NetConnection -ComputerName $ComputerObject.DNSName -Port $ComputerObject.Port -InformationLevel Quiet))
            {
                Write-Verbose "Stop VM"
                Stop-AzureVM -Name $ComputerObject.Name -ServiceName $ComputerObject.ServiceName -Force
            }else
            {
                Write-Verbose "$($ComputerObject.DNSName) already stopped"
            }
        }
    }
    Sleep $SleepSeconds
}

If the time isn’t inside working hours, the same tests are performed but all my VMs are stopped instead.  This will de-allocate them (they’ll lose their current IP address and maybe get a new one when they restart) so I won’t get charged.

At the end of the loop, the script pauses before starting again.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s