Last active
October 16, 2018 17:46
-
-
Save AlexSen/39c966cc2576c65e6373721fd345657a to your computer and use it in GitHub Desktop.
Azure PowerShell Runbook script which iterates through Virtual Machines in all Automation Accounts and performs Stop-VM based in case if VM is not active for predefined amount of time
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<# | |
.SYNOPSIS | |
TBD | |
.DESCRIPTION | |
TBD | |
.URL https://gist.github.com/Forket/39c966cc2576c65e6373721fd345657a | |
#> | |
param( | |
[Parameter (Mandatory= $false, HelpMessage="Name of Azure RunAs Connection of the account where runbook works.")] | |
[String] | |
$AzureRunAsConnectionName = "AzureRunAsConnection", | |
[Parameter (Mandatory= $false, HelpMessage="Name of Azure Automation Account where runbook works.")] | |
[String] | |
$AutomationAccountName = "DefaultAutomationAccount", | |
[Parameter (Mandatory= $false, HelpMessage="Name of Azure Resource Group where runbook works.")] | |
[String] | |
$ResourceGroupName = "defaultresourcegroup", | |
[Parameter (Mandatory= $false, HelpMessage="Number of minutes which will consider DISCONNECTED RDP session as non active and will grant script right to shutdown VM.")] | |
[Int32] | |
$RDPsessionsIDLEtimeoutMin = 120, | |
[Parameter (Mandatory= $false, HelpMessage="OWNER tag of VM should contain email address which will be used for notification.")] | |
[boolean] | |
$NotifyVMOwner = $true, | |
[Parameter (Mandatory= $false, HelpMessage="CC email address which will be used for notification.")] | |
[String] | |
$CC = $null, | |
[Parameter (Mandatory= $false, HelpMessage="Name of Azure automation connection which contains SMTP settings. EMAIL module (in modules gallery) could be used.")] | |
[string] | |
$SMTPAutomationConnectionName = "Gmail" | |
) | |
# By default, STOP script in case of any error. | |
$ErrorActionPreference = 'Stop' | |
# Global Variables | |
filter timestamp {"$(Get-Date -Format G): $_"} # filter used to add timestamp for console output | |
$CassiaDllLocation = "C:\Windows\System32\Cassia.dll" # location of Cassia dll library on remote computer | |
# Get service principal account based on Runbook provided name of Azure RunAs connection name | |
# and login to Azure RM Account | |
$ServicePrincipalConnection = Get-AutomationConnection -Name $AzureRunAsConnectionName | |
$LoginAzureRmAccountOptions = @{ | |
TenantId = $ServicePrincipalConnection.TenantId | |
ApplicationId = $ServicePrincipalConnection.ApplicationId | |
ServicePrincipal = $true | |
CertificateThumbprint = $ServicePrincipalConnection.CertificateThumbprint | |
} | |
Login-AzureRmAccount @LoginAzureRmAccountOptions | Out-Null | |
# Get All Azure automation Connections. | |
# Here you can include connections which have access to all other Azure Accounts/Subscriptions | |
$GetAzureRmAutomationConnectionOptions = @{ | |
ResourceGroupName = $ResourceGroupName | |
AutomationAccountName = $AutomationAccountName | |
ConnectionTypeName = "AzureServicePrincipal" | |
} | |
$AzureAccounts = Get-AzureRmAutomationConnection @GetAzureRmAutomationConnectionOptions | |
ForEach ( $Account in $AzureAccounts ) { # Iterate through all Azure Automation connections in order to Log In into them and get all VMs | |
$AutomationConnection = Get-AutomationConnection -Name $Account.Name | |
# Login to Azure Account | |
Write-Output "" ; $("#" * 100); "Logging in to Azure Account: $($Account.Name)" | |
$LoginAzureRmAccountOptions = @{ | |
TenantId = $AutomationConnection.TenantId | |
ApplicationId = $AutomationConnection.ApplicationId | |
ServicePrincipal = $true | |
CertificateThumbprint = $AutomationConnection.CertificateThumbprint | |
} | |
try {Login-AzureRmAccount @LoginAzureRmAccountOptions | Out-Null} catch { continue } | |
#Get Azure VMs and iterate through | |
$AzureVMs = Get-AzureRmVm -WarningAction Ignore | |
"Found $(( $AzureVMs | measure ).Count) VMs." | timestamp | |
ForEach ( $VM in $AzureVMs ) { | |
""; "===== Processing $($VM.Name) VM" | |
$VMName = $VM.Name | |
$VMfqdn = $null | |
$VMowner = $VM.Tags['owner'] | |
$VMStatus = ( ( Get-AzureRmVm -ResourceGroupName $VM.ResourceGroupName -Name $VM.Name -Status -WarningAction Ignore ).Statuses )[-1].Code | |
$VMStatusesToMonitor = @( "PowerState/running", | |
"PowerState/stopped") | |
# Define SMTP settings | |
$SendEmailOptions = @{ | |
SMTPSettings = Get-AutomationConnection -Name "Gmail" | |
To = $VMowner | |
HTMLBody = $true | |
CC = $CC | |
ErrorAction = "Ignore" | |
} | |
if ( $VMStatusesToMonitor -contains $VMStatus ) | |
{ | |
if ( $VMStatus -eq "PowerState/stopped") | |
{ | |
Write-Output "$VMName is stopped, but not deallocated. Deallocating..." | timestamp | |
$shutdownStatus = ($VM | Stop-AzureRmVm -Force -ErrorAction SilentlyContinue).Status | |
Write-Output "$VMName deallocated with status $shutdownStatus" | timestamp | |
if ( $VMowner -and $NotifyVMOwner ) | |
{ | |
Send-Email @SendEmailOptions -Subject "$VMName deallocated" -Body "<b>Reason</b>: VM was stopped, but not deallocated." | |
} | |
continue # There is nothing to do with current VM, go to next one | |
} | |
Write-Output "$VMName has status $VMStatus. Going to get DNS name for remote connection to VM." | timestamp | |
# Get VM FQDN | |
$nics = Get-AzureRmNetworkInterface -WarningAction Ignore | where VirtualMachine -NE $null #skip Nics with no VM | |
foreach( $nic in $nics ) | |
{ | |
if ( $nic.VirtualMachine.Id -eq $VM.Id ) | |
{ | |
$PublicAddress = Get-AzureRmPublicIpAddress | where Id -EQ $nic.IpConfigurations.publicIPaddress.Id | |
$VMfqdn = $PublicAddress.DnsSettings.Fqdn | |
} | |
} | |
# If we don't have VM FQDN than inform and go to next VM | |
if ( $VMfqdn -eq $null ) | |
{ | |
Write-Output "Couldn't find DNS name for $VMName. Skip." | timestamp | |
continue | |
} | |
# Get VM credentials from Azure Automation | |
$VMcredentials = Get-AutomationPSCredential -Name $VMName | |
Write-Output "Connecting to <$VMName> using <$VMfqdn>" | timestamp | |
Write-Output "Got credentials ""$($VMcredentials.UserName)"" for $VMName." | timestamp | |
# Create Session object and connect to VM | |
# Enter-PSSession -ComputerName "epm-kasia.northeurope.cloudapp.azure.com" -Credential (Get-Credential -UserName "Kasia") -UseSSL -SessionOption (New-PSSessionOption -SkipCACheck -SkipCNCheck -SkipRevocationCheck); Exit-PSSession | |
$session = New-PSSession -ComputerName $VMfqdn -Credential $VMcredentials -UseSSL -SessionOption (New-PSSessionOption -SkipCACheck -SkipCNCheck -SkipRevocationCheck) | |
Write-Output "Opened session for $($VM.Name). Going to run main script in order to know if we can shutdown VM" | timestamp | |
# Perform main script | |
Invoke-Command -Session $session -ScriptBlock { | |
$result = [PSCustomObject]@{ | |
status = $false | |
message = '' | |
sessions = $null | |
} | |
# Get VM uptime | |
$VMuptime = (get-date) - (gcim Win32_OperatingSystem).LastBootUpTime | |
if ( $VMuptime -le ($RDPsessionsIDLEtimeoutMin / 2) ) | |
{ | |
$result.status = $false | |
$result.message = "VM uptime is less than $($RDPsessionsIDLEtimeoutMin / 2) mins. Skipping." | |
} else { | |
# Get all sessions to the variable | |
[reflection.assembly]::loadfile("$CassiaDllLocation") | Out-Null | |
$manager = new-object Cassia.TerminalServicesManager | |
$RDsessions = $manager.GetSessions() | Select-Object -Property ClientIPAddress,UserAccount,ClientName,ConnectionState,CurrentTime,ConnectTime,DisconnectTime,LastInputTime,LoginTime,IdleTime,UserName | |
# Filter sessions which have usernames | |
$RDsessions = $RDsessions | Where-Object -Property UserName -ne -Value "" | |
# Get active sessions | |
$RDactiveSessions = $RDsessions | Where-Object -Property ConnectionState -eq -Value "Active" | |
# Get disconnected sessions | |
$RDdisconnectedSessions = $RDsessions | Where-Object -Property ConnectionState -eq -Value "Disconnected" | |
# If we have active sessions than set shutdown to TRUE but also check for disconnected sessions | |
if ( ( $RDactiveSessions | measure ).Count -eq 0 ) | |
{ | |
$result.status = $true; $result.message = "We have no active sessions" | |
ForEach ($disconnectedSession in $RDdisconnectedSessions) | |
{ | |
# If we found disconnected session but IDLE time less then 120 mins - than don't run shutdown | |
if ( $disconnectedSession.IdleTime.TotalMinutes -le $RDPsessionsIDLEtimeoutMin ) { | |
$result.status = $false | |
$result.message = "There are no active sessions, but we have disconnected session with IDLE time $($disconnectedSession.IdleTime.TotalMinutes) minutes, wich is less than 120 mins." | |
$result.sessions = $disconnectedSession | |
break | |
} | |
} | |
} else { | |
$result.message = "We have $(( $RDactiveSessions | measure ).Count) active sessions" | |
$result.sessions = $RDactiveSessions | |
} | |
} | |
} | |
# return result of our check | |
$shutdown = Invoke-Command -Session $session -ScriptBlock { return $result } | |
Write-Output "Can we shutdown $($VM.Name)?: $(If ($shutdown.status -eq $true) {"Yes"} Else {"No. Reason: $($shutdown.message)"})" | timestamp | |
if ( $shutdown.status -eq $true ) | |
{ | |
Write-Output "Stopping $($VM.Name)..." | timestamp | |
$shutdownStatus = $VM | Stop-AzureRmVm -Force | |
Write-Output "$($VM.Name) stopped with status $($shutdownStatus.Status)" | timestamp | |
if ($VMowner) {Send-Email -SMTPSettings (Get-AutomationConnection -Name "Gmail") -To $VMowner -Subject ("$($VM.Name) Shuttedown") -Body "via PowerShell" -HTMLBody $true -ErrorAction Ignore} | |
} else { | |
Write-Output $shutdown.sessions | |
} | |
} else { | |
Write-Output "$($VM.Name) is in status $($vmStatus[-1].Code). Skipping." | timestamp | |
} | |
} | |
} | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment