Citrix VDA Public Cloud Power Maintenance Script

Citrix VDA Public Cloud Power Maintenance Script

Citrix VDA Public Cloud Power Maintenance Script

This post has already been read 815 times!

I was recently asked by someone to look at automating the power management of their Citrix Virtual Apps and Desktop VDA’s in the Microsoft Azure Public Cloud.

Why you ask?

If you think about the difference between running VDA’s in your own data centre and running them in the public cloud then the differences are vast. One of the big ones is that when running your VDA’s in your own data centre you have already paid for all the compute (to an extent). Compare that to the public cloud and you are paying for each and every second that your VDA’s are up and running.

If you think of a typical business running 9-5 then you are paying for those VDA’s running 16 out of 24 hours even though your not using them – that adds up fast!

I was asked to create a script that you can put in times for Powering Up the VDA’s (so they are ready for your users) as well as times to power down the VDA’s.

Some of the requirements are:

  • The script must have programmable times for Powering Up and Down
  • The script must work both on-premises and in the Cloud
  • The script must use the Citrix SDK so that when running in the public cloud it properly de-allocates the Virtual Machine
  • The script must not power down a machine with users logged in

Now please don’t mock my PowerShell skills – I am not a programmer and can just about get this to do what I want it to, but it works and should save you some money in the process.

To get it working you need to copy the below 2 scripts onto your Citrix Controller or Cloud Connector and save them into the folder C:\Scripts. Also create a LOGS folder within the Scripts Folder.

You will end up with 2 files

  • Start-PowerMaintenance.ps1
  • Configuration.json
{
    "Global": {
        "logpath": "c:\\scripts\\logs",
        "starttimedown": "10:00",
        "endtimedown": "11:00",
        "starttimeup": "20:00",
        "endtimeup": "23:00",
        "domain": "bretty.me.uk",
        "processtag": "yes",
        "tag": "PowerManage"
    },
    "machines": {
        "vdas": [
            "vda-10-01",
            "vda-19-01"
        ]
    }
}

This is an example json file. You can either process your VDA’s by listing them all in the JSON file (remember to leave a couple on to serve your users overnight) or you can process them by Tag. If you want to use the list then just set the “process tag” option to “no” and list your VDA’s. If you prefer to use a Tag then set the “process tag” option to “yes” and put the name of the Tag you want to use to specify VDA’s for Power Management.

Save both those files into the C:\Scripts directory and run it every 15 minutes or so as a Scheduled Task with rights to your environment.

Thats it – quick post for today but thought this script might help some of you out and save you some money on those VDA’s running up in the Public Cloud.

Thanks,

Dave

Configuration.json

{
    "Global": {
        "logpath": "c:\\scripts\\logs",
        "starttimedown": "20:00",
        "endtimedown": "23:30",
        "starttimeup": "06:00",
        "endtimeup": "08:00",
        "domain": "bretty.me.uk",
        "processtag": "yes",
        "tag": "PowerManage"
    },
    "machines": {
        "vdas": [
            "vda-10-01",
            "vda-19-01"
        ]
    }
}

Start-PowerMaintenance.ps1

function Start-PowerMaintenance {

    [CmdletBinding(SupportsShouldProcess)]
    Param (
        [Parameter(ValuefromPipelineByPropertyName = $true, Mandatory=$true)]
        [ValidateNotNullOrEmpty()]
        [System.String]$JSONFile
    )
    
    BEGIN {
        Set-StrictMode -Version Latest  
    } # Begin

    PROCESS {  
            try {
                $ConfigObject = Get-Content -Raw -Path $JSONFile | ConvertFrom-Json -ErrorAction Stop             
                $LogPath = $ConfigObject.Global.logpath
                $LogFile = $LogPath + "\" + (Get-Date -Format MMddyyyy) + "_POWER_MAINTENANCE.log"

                New-LogEntry -LogFile $LogFile -LogText "Sucessfully read JSON File"
                New-LogEntry -LogFile $LogFile -LogText "Reading in Script Variables"

                $StartTimeDown = $ConfigObject.Global.starttimedown
                $EndTimeDown = $ConfigObject.Global.endtimedown
                $StartTimeUp = $ConfigObject.Global.starttimeup
                $EndTimeUp = $ConfigObject.Global.endtimeup

                $Domain = $ConfigObject.Global.Domain

                $ProcessTag = $ConfigObject.Global.processtag
                $Tag = $ConfigObject.Global.tag

                $CurrentTime = Get-Date -Format HH:mm
                if(($CurrentTime -gt $StartTimeDown) -and ($CurrentTime -lt $EndTimeDown)) {
                    $Process = "PowerDown"
                } else {
                    if(($CurrentTime -gt $StartTimeUp) -and ($CurrentTime -lt $EndTimeUp)) {
                        $Process = "PowerUp"
                    } 
                }

                if($Process -eq "PowerDown") {
                    New-LogEntry -LogFile $LogFile -LogText "Starting power down execution as within time parameters"
                    New-LogEntry -LogFile $LogFile -LogText "Checking for VDA List or Tag Processing"

                    if ($ProcessTag -eq "yes") {
                        New-LogEntry -LogFile $LogFile -LogText "Looping through VDA's Found by Tag and putting them into maintenance mode / powering them down"
                        Process-Tag -Tag $Tag -LogFile $LogFile -Action "Down"
                    } else {
                        New-LogEntry -LogFile $LogFile -LogText "Looping through VDA's in Configuration File and putting them into maintenance mode / powering them down"
                        Process-List -ConfigObject $ConfigObject -LogFile $LogFile -Action "Down"
                    }      
                } else {
                    if ($Process -eq "PowerUp") {
                        New-LogEntry -LogFile $LogFile -LogText "Starting power up execution as within time parameters"
                        New-LogEntry -LogFile $LogFile -LogText "Checking for VDA List or Tag Processing"
                       
                        if ($ProcessTag -eq "yes") {
                            New-LogEntry -LogFile $LogFile -LogText "Looping through VDA's Found by Tag and putting them into maintenance mode / powering them down"
                            Process-Tag -Tag $Tag -LogFile $LogFile -Action "Up"
                        } else {
                            New-LogEntry -LogFile $LogFile -LogText "Looping through VDA's in Configuration File and putting them into maintenance mode / powering them down"
                            Process-List -ConfigObject $ConfigObject -LogFile $LogFile -Action "Up"
                        } 
                    } else {
                        New-LogEntry -LogFile $LogFile -LogText "The Script has run and does not meet either of the Power Up or Down Time Schedules"
                    }   
                }
            } catch {
                throw "Error reading JSON.  Please Check File and try again."
            }       
    } #Process

    END { } #End

}

function New-LogEntry {

    [CmdletBinding(SupportsShouldProcess)]
    Param (
        [Parameter(ValuefromPipelineByPropertyName = $true, Mandatory=$true)]
        [ValidateNotNullOrEmpty()]
        [System.String]$LogFile,
        [Parameter(ValuefromPipelineByPropertyName = $true, Mandatory=$true)]
        [ValidateNotNullOrEmpty()]
        [System.String]$LogText
    )
    
    BEGIN {
        Set-StrictMode -Version Latest  
    } # Begin

    PROCESS { 
        #Display details back to the user
        Write-Verbose "LogText: $LogText"
        try {
            "$(Get-TimeStamp) $LogText" | Out-File -FilePath $LogFile -Append
        }
        catch {
            Write-Warning "There was an error writing to the log file. The error is $_"
        }
    } #Process

    END { } #End
}

function Get-TimeStamp {

    [CmdletBinding(SupportsShouldProcess)]
    Param (
    )
    
    BEGIN {
        Set-StrictMode -Version Latest  
    } # Begin

    PROCESS {    
        return "[{0:MM/dd/yy} {0:HH:mm:ss}]" -f (Get-Date)
    } #Process

    END { } #End
}

function Get-Users {

    [CmdletBinding(SupportsShouldProcess)]
    Param (
        [Parameter(ValuefromPipelineByPropertyName = $true, Mandatory=$true)]
        [ValidateNotNullOrEmpty()]
        [System.String]$VDA,
        [Parameter(ValuefromPipelineByPropertyName = $true, Mandatory=$true)]
        [ValidateNotNullOrEmpty()]
        [System.String]$LogFile
    )
    
    BEGIN {
        Set-StrictMode -Version Latest  
    } # Begin

    PROCESS {
        $Users = Get-BrokerSession -DNSName $VDA 
        $Count = 0
        foreach($User in $Users) {
            $Count++
        }    
        New-LogEntry -LogFile $LogFile -LogText "Getting User Count for $VDA - Current Users: $Count"
        return $Count
    } #Process

    END { } #End
}

function Process-Tag {

    [CmdletBinding(SupportsShouldProcess)]
    Param (
        [Parameter(ValuefromPipelineByPropertyName = $true, Mandatory=$true)]
        [ValidateNotNullOrEmpty()]
        [System.String]$Tag,
        [Parameter(ValuefromPipelineByPropertyName = $true, Mandatory=$true)]
        [ValidateNotNullOrEmpty()]
        [System.String]$LogFile,
        [Parameter(ValuefromPipelineByPropertyName = $true, Mandatory=$true)]
        [ValidateNotNullOrEmpty()]
        [System.String]$Action
    )
    
    BEGIN {
        Set-StrictMode -Version Latest  
    } # Begin

    PROCESS {
        New-LogEntry -LogFile $LogFile -LogText "Getting VDA's based on Tag"
        $VDAs = Get-BrokerMachine | Where-Object {$_.Tags -contains $Tag}
        foreach($VDA in $VDAs) {
            $DNSName = $VDA.DNSName
            if($Action -eq "Up") {
                New-LogEntry -LogFile $LogFile -LogText "Starting Power Up Routine for $DNSName"
                Power-Up -VDA $DNSName -LogFile $LogFile
            } else {
                New-LogEntry -LogFile $LogFile -LogText "Starting Power Off Routine for $DNSName"
                Power-Down -VDA $DNSName -LogFile $LogFile
            }
        }
    } #Process

    END { } #End
}

function Process-List {

    [CmdletBinding(SupportsShouldProcess)]
    Param (
        [Parameter(ValuefromPipelineByPropertyName = $true, Mandatory=$true)]
        [ValidateNotNullOrEmpty()]
        $ConfigObject,
        [Parameter(ValuefromPipelineByPropertyName = $true, Mandatory=$true)]
        [ValidateNotNullOrEmpty()]
        [System.String]$LogFile,
        [Parameter(ValuefromPipelineByPropertyName = $true, Mandatory=$true)]
        [ValidateNotNullOrEmpty()]
        [System.String]$Action
    )
    
    BEGIN {
        Set-StrictMode -Version Latest  
    } # Begin

    PROCESS {
        New-LogEntry -LogFile $LogFile -LogText "Getting Domain Information"
        $Domain = $ConfigObject.Global.domain
        New-LogEntry -LogFile $LogFile -LogText "Getting VDA's based on JSON File"
        foreach($VDANetBios in $ConfigObject.machines.vdas) {
            $DNSName = $VDANetBios + "." + $Domain
            try {
                $Target = Get-BrokerMachine -DNSName $DNSName
                New-LogEntry -LogFile $LogFile -LogText "Targetted VDA: $DNSName"
                if($Action -eq "Up") {
                    New-LogEntry -LogFile $LogFile -LogText "Starting Power Up Routine for $DNSName"
                    Power-Up -VDA $DNSName -LogFile $LogFile
                } else {
                    New-LogEntry -LogFile $LogFile -LogText "Starting Power Off Routine for $DNSName"
                    Power-Down -VDA $DNSName -LogFile $LogFile
                }
            } catch {
                New-LogEntry -LogFile $LogFile -LogText "There was an error connecting to: $DNSName"
            }
        }
    } #Process

    END { } #End
}

function Power-Up {

    [CmdletBinding(SupportsShouldProcess)]
    Param (
        [Parameter(ValuefromPipelineByPropertyName = $true, Mandatory=$true)]
        [ValidateNotNullOrEmpty()]
        [System.String]$VDA,
        [Parameter(ValuefromPipelineByPropertyName = $true, Mandatory=$true)]
        [ValidateNotNullOrEmpty()]
        [System.String]$LogFile
    )
    
    BEGIN {
        Set-StrictMode -Version Latest  
    } # Begin

    PROCESS {
        $Target = Get-BrokerMachine -DNSName $VDA
        if($Target.InMaintenanceMode -eq $false) {
            New-LogEntry -LogFile $LogFile -LogText "$VDA is already out of maintenance mode"
            if((Get-BrokerMachine -DNSName $VDA).PowerState -eq "On") {
                New-LogEntry -LogFile $LogFile -LogText "$VDA is already powered on"
            } else {
                New-LogEntry -LogFile $LogFile -LogText "Powering On $VDA"
                New-BrokerHostingPowerAction -MachineName $VDA -Action TurnOn
            }
        } else {
            Set-BrokerMachine $Target -InMaintenanceMode $False
            New-LogEntry -LogFile $LogFile -LogText "$VDA is now out of maintenance mode"
            if((Get-BrokerMachine -DNSName $VDA).PowerState -eq "On") {
                New-LogEntry -LogFile $LogFile -LogText "$VDA is already powered on"
            } else {
                New-LogEntry -LogFile $LogFile -LogText "Powering On $VDA"
                New-BrokerHostingPowerAction -MachineName $VDA -Action TurnOn
            }
        }
    } #Process

    END { } #End
}

function Power-Down {

    [CmdletBinding(SupportsShouldProcess)]
    Param (
        [Parameter(ValuefromPipelineByPropertyName = $true, Mandatory=$true)]
        [ValidateNotNullOrEmpty()]
        [System.String]$VDA,
        [Parameter(ValuefromPipelineByPropertyName = $true, Mandatory=$true)]
        [ValidateNotNullOrEmpty()]
        [System.String]$LogFile
    )
    
    BEGIN {
        Set-StrictMode -Version Latest  
    } # Begin

    PROCESS {
        $Target = Get-BrokerMachine -DNSName $VDA
        if($Target.InMaintenanceMode -eq $true) {
            New-LogEntry -LogFile $LogFile -LogText "$VDA is already in maintenance mode"
            if((Get-BrokerMachine -DNSName $VDA).PowerState -eq "Off") {
                New-LogEntry -LogFile $LogFile -LogText "$VDA is already powered off"
            } else {
                New-LogEntry -LogFile $LogFile -LogText "$VDA is powered on - checking user count"
                $Count = Get-Users -VDA $VDA -LogFile $LogFile
                New-LogEntry -LogFile $LogFile -LogText "Number of users on the VDA - $Count"  
                if ($Count -eq 0) {
                    New-BrokerHostingPowerAction -MachineName $VDA -Action Shutdown
                    New-LogEntry -LogFile $LogFile -LogText "There were no users logged into $VDA - shutting down"
                } else {
                    New-LogEntry -LogFile $LogFile -LogText "There are users still logged into $VDA skipping shutdown"
                }  
            }
        } else {
            Set-BrokerMachine $Target -InMaintenanceMode $True
            New-LogEntry -LogFile $LogFile -LogText "$VDA is now in maintenance mode"
            if((Get-BrokerMachine -DNSName $VDA).PowerState -eq "Off") {
                New-LogEntry -LogFile $LogFile -LogText "$VDA is already powered off"
            } else {
                New-LogEntry -LogFile $LogFile -LogText "$VDA is powered on - checking user count"
                $Count = Get-Users -VDA $VDA -LogFile $LogFile
                New-LogEntry -LogFile $LogFile -LogText "Number of users on the VDA - $Count"     
                if ($Count -eq 0) {
                    New-BrokerHostingPowerAction -MachineName $VDA -Action Shutdown
                    New-LogEntry -LogFile $LogFile -LogText "There were no users logged into $VDA - shutting down"
                } else {
                    New-LogEntry -LogFile $LogFile -LogText "There are users still logged into $VDA skipping shutdown"
                }  
            }
        }
    } #Process

    END { } #End
}

Start-PowerMaintenance -JSONFile "C:\Scripts\configuration.json" -Verbose

Leave a Reply

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

This site uses Akismet to reduce spam. Learn how your comment data is processed.