#
This script will manage the start of the Citrix Desktop Service.
This script will read the values under the following registry key to determine how long to wait before
starting the Citrix Desktop Service.
- Key: HKEY_LOCAL_MACHINE\SOFTWARE\Policies\Citrix\VDAHelper
- Key: HKEY_LOCAL_MACHINE\SOFTWARE\Citrix\VDAHelper
- Note that the values set under the Policies key have a higher priority.
The VDAHelperSettingsEnabled tells the script if it should action or ignore all other settings under this
registry key
- Type: REG_DWORD
- Value: VDAHelperSettingsEnabled
- Data: 0=False; 1=True
The DelayDesktopServiceTime value tells this script, in seconds, how long to wait before starting the
Citrix Desktop Service
- Type: REG_DWORD
- Value: DelayDesktopServiceTime
The TriggerOnTaskEndEvent value tells this script to wait for a task to end before starting the Citrix
Desktop Service
- Type: REG_DWORD
- Value: TriggerOnTaskEndEvent
- Data: 0=False; 1=True
The TaskNameFullPath value tells this script to which task to base the trigger event off before starting
the Citrix Desktop Service
- Type: REG_SZ
- Value: TaskNameFullPath
The MaximumTaskWaitTime value tells this script, in seconds, how long to wait for the task to start/end
before starting the Citrix Desktop Service. This ensures that any failure in the task trigger events
does not ultimately prevent the Citrix Desktop Service from starting.
- Type: REG_DWORD
- Value: MaximumTaskWaitTime
The LogonEventUserName value tells this script which username will be in the logon event 4624 so that it
can collect the "Logon ID" value to match it with a logoff event.
- Type: REG_SZ
- Value: LogonEventUserName
The DefaultDomainName value tells this script what to set the Winlogon DefaultDomainName value to once
the autologon process has started. This allows us to use a local account for the Autologon process.
- Type: REG_SZ
- Value: DefaultDomainName
IMPORTANT NOTES:
- The TriggerOnTaskEndEvent value (0 or 1) will determine if the script waits for the specified task to
start and end or uses the DelayDesktopServiceTime value instead.
- During the initial implementation I found that the logoff event for the autologon account may occur a few
seconds after the task ends. When this happens, and depending on how quickly the the VDA registers with
the Delivery Controller, it may cause a power managed VDI machine to reboot again; creating a reboot loop.
This is simply a timing issue. So to work around this, we also wait for the logoff event that tells us
that the session has been destroyed/terminated. This presented me with a challenge as I was unable tie
events 4624 (logon) and 4634 (logoff) together. We can, however, tie the 4624 (logon) event to the 4647
(logoff) event. However, this event just tells us that the logoff was initiated and not complete. A 4634
(logoff) event will follow, but there is no information in that event which allows us to formally tie
them together. Therefore, we make the assumption that the 4634 event that follows the 4647 event is the
actual complete termination of the logoff, which is when the logon session is actually destroyed. At this
point it's then safe to start the Citrix Desktop Service without the risk of a reboot loop occuring.
- I have found that it may take some time for the 4634 (logoff) event to come through that tells you that
the session has been destroyed/terminated. This is typically due to antivirus software.
Future Improvements:
- Delete the SavedListOfDDCsSids.xml as per https://support.citrix.com/article/CTX216883
- The Get-DeliveryControllers function needs to be updated to look in the C:\Personality.ini for MCS deployments
- Can take different actions based on image type or manual deployment.
https://github.com/megamorf/CitrixImagingTools/issues/13
Script name: StartCitrixDesktopService.ps1
Release 3.2
Written by Jeremy Saunders (jeremy@jhouseconsulting.com) 16th October 2017
Modified by Jeremy Saunders (jeremy@jhouseconsulting.com) 31st January 2023
#>
#-------------------------------------------------------------
# Set Powershell Compatibility Mode
Set-StrictMode -Version 2.0
# Enable verbose, warning and error mode
$VerbosePreference = 'Continue'
$WarningPreference = 'Continue'
$ErrorPreference = 'Continue'
$StartDTM = (Get-Date)
#-------------------------------------------------------------
$invalidChars = [io.path]::GetInvalidFileNamechars()
$datestampforfilename = ((Get-Date -format s).ToString() -replace "[$invalidChars]","-")
# Get the TEMP path
$logPath = [System.IO.Path]::GetTempPath()
$logPath = $logPath.Substring(0,$logPath.Length-1)
$ScriptName = [System.IO.Path]::GetFilenameWithoutExtension($MyInvocation.MyCommand.Path.ToString())
$logFile = "$logPath\$ScriptName-$($datestampforfilename).log"
try {
Start-Transcript "$logFile"
}
catch {
write-verbose "$(Get-Date): This host does not support transcription"
}
#-------------------------------------------------------------
# Set to true if you want to check if there are valid Delivery Controller(s) set
# under the following registry values:
# - HKLM\SOFTWARE\Policies\Citrix\VirtualDesktopAgent\ListOfDDCs
# - HKLM\SOFTWARE\Citrix\VirtualDesktopAgent\ListOfDDCs
# Note that the Policies value is checked first.
$CheckForValidity = $True
# Set the number of seconds to wait between validity checks of the ListOfDDCs
# registry value.
$interval = 10
# Set the number of times to repeat the validity before continuing.
$RepeatValidityCheck = 25
# Set to true if you want to restart the service(s) if already started.
$StopServiceIfAlreadyStarted = $True
# Create the hashtable for services to be started
$ServicesToStart = @{}
# Create an object for each service that needs to be started
$objService = New-Object -TypeName PSObject -Property @{
"Name" = "BrokerAgent"
"DisplayName" = "Citrix Desktop Service"
}
# Add the object to the hashtable
$ServicesToStart.Add($objService.Name,$objService)
#-------------------------------------------------------------
Function Get-VDAHelperSettings {
# This function read the values under the following registry key to determine how long to wait
# before starting the Citrix Desktop Service.
# - Key: HKEY_LOCAL_MACHINE\SOFTWARE\Policies\Citrix\VDAHelper
# - Key: HKEY_LOCAL_MACHINE\SOFTWARE\Citrix\VDAHelper
# Note that the values set under the Policies key have a higher priority.
$regKey1 = "SOFTWARE\Citrix\VDAHelper"
$regKey2 = "SOFTWARE\Policies\Citrix\VDAHelper"
$ResultProps = @{
VDAHelperSettingsEnabled = 0
DelayDesktopServiceTime = 0
TriggerOnTaskEndEvent = 0
TaskNameFullPath = ""
MaximumTaskWaitTime = 0
LogonEventUserName = ""
DefaultDomainName = ""
}
$results = @()
# Create an instance of the Registry Object and open the HKLM base key
$reg = [microsoft.win32.registrykey]::OpenRemoteBaseKey('LocalMachine',$env:computername)
Try {
$thisRegKey = $reg.OpenSubKey($regKey1)
ForEach ($Key in $($ResultProps.Keys)) {
Try {
If ($null -ne $thisRegKey.GetValue($Key)) {
If ($thisRegKey.GetValueKind($Key) -eq "String") {
$ResultProps.$Key = $thisRegKey.GetValue($Key).Trim()
} Else {
$ResultProps.$Key = $thisRegKey.GetValue($Key)
}
}
}
Catch [System.Exception] {
#$($_.Exception.Message)
}
}
}
Catch [System.Exception] {
#$($_.Exception.Message)
}
Try {
$thisRegKey = $reg.OpenSubKey($regKey2)
ForEach ($Key in $($ResultProps.Keys)) {
Try {
If ($null -ne $thisRegKey.GetValue($Key)) {
If ($thisRegKey.GetValueKind($Key) -eq "String") {
$ResultProps.$Key = $thisRegKey.GetValue($Key).Trim()
} Else {
$ResultProps.$Key = $thisRegKey.GetValue($Key)
}
}
}
Catch [System.Exception] {
#$($_.Exception.Message)
}
}
}
Catch [System.Exception] {
#$($_.Exception.Message)
}
$results += New-Object PsObject -Property $ResultProps
return $results
}
Function IsTaskValid {
param([string]$TaskNameFullPath)
If ($TaskNameFullPath -Match "\\") {
$TaskName = $TaskNameFullPath.Split('\')[1]
$TaskRootFolder = $TaskNameFullPath.Split('\')[0] + "\"
} Else {
$TaskName = $TaskNameFullPath
$TaskRootFolder = "\"
}
$TaskExists = $False
# Create the TaskService object.
Try {
[Object] $service = new-object -com("Schedule.Service")
If (!($service.Connected)){
Try {
$service.Connect()
$rootFolder = $service.GetFolder("$TaskRootFolder")
$rootFolder.GetTasks(0) | ForEach-Object {
If ($_.Name -eq $TaskName) {
$TaskExists = $True
} #If
} #ForEach
} #Try
Catch [System.Exception]{
"Scheduled Task Connection Failed"
}
} #If
} #Try
Catch [System.Exception]{
"Scheduled Task Object Creation Failed"
} #Catch
return $TaskExists
}
Function IsEventLogValid {
param([string]$EventLog)
$EventLogExists = $False
Try {
Get-WinEvent -ListLog "$EventLog" -ErrorAction Stop | out-null
$EventLogExists = $True
}
Catch [System.Exception]{
#$($_.Exception.Message)
}
return $EventLogExists
}
function Get-TaskEventRunTime {
param (
[switch]$start,
[switch]$end,
[string]$taskPath
)
$results = @()
$ResultProps = @{
EventFound = $False
TimeCreated = [datetime]"1/1/1600"
ErrorMessage = ""
}
# in case taskpath contains quotes, have to escape (double) them
$taskPath = $taskPath.Replace("'","''")
If ($Start) {
# fetch the most recent start event (100) event
$XPath = "*[System[(EventID=100)]] and *[EventData[Data[1]='" + $taskPath + "']]"
}
If ($End) {
# fetch the most recent success event (102) or failed (111) event
$XPath = "*[System[((EventID=102) or (EventID=111))]] and *[EventData[Data[1]='" + $taskPath + "']]"
}
Try {
get-winevent -LogName 'Microsoft-Windows-TaskScheduler/Operational' -FilterXPath $XPath -MaxEvents 1 -ErrorAction Stop | ForEach-Object{
$ResultProps.EventFound = $True
$ResultProps.TimeCreated = $_.TimeCreated
}
}
Catch {
$ResultProps.ErrorMessage = $($_.Exception.Message)
#$($_.Exception.Message)
}
$results += New-Object PsObject -Property $ResultProps
return $results
}
function Get-LastBootTime {
return [Management.ManagementDateTimeConverter]::ToDateTime((Get-WmiObject Win32_OperatingSystem).LastBootUpTime)
}
function Get-LogonLogoffEvent {
param (
[switch]$Logon,
[switch]$Logoff,
[string]$UserName,
[string]$Event,
[string]$LogonID=""
)
$results = @()
$ResultProps = @{
EventFound = $False
TimeCreated = [datetime]"1/1/1600"
LogonID = ""
}
If ($Logon) {
# Query to check that the session has logged on.
$xmlquery=@"
"@
}
If ($Logoff -AND $Event -eq "4647") {
# Query to check that the logoff has been initiated
$xmlquery=@"
"@
}
If ($Logoff -AND $Event -eq "4634") {
# Query to check that the session has been terminated/destroyed
$xmlquery=@"
"@
}
Try {
Get-WinEvent -MaxEvents 1 -FilterXml $xmlquery -ErrorAction Stop | ForEach-Object{
$ResultProps.EventFound = $True
$ResultProps.TimeCreated = $_.TimeCreated
if ($_.ID -eq "4624") {
$ResultProps.LogonID = $_.Properties[7].Value
}
if ($_.ID -eq "4647") {
$ResultProps.LogonID = $_.Properties[3].Value
}
if ($_.ID -eq "4634") {
#
}
}
}
Catch {
#$($_.Exception.Message)
}
$results += New-Object PsObject -Property $ResultProps
return $results
}
Function XDPing {
# To test if the Broker service is reachable, listening and processing requests on its configured port,
# you can issue blank HTTP POST requests at the Broker's Registrar service, which is located at
# /Citrix/CdsController/IRegistrar. If the first line displayed is "HTTP/1.1 100 Continue", then the
# Broker service responded and deemed to be healthy.
param(
[Parameter(Mandatory=$True)][String]$ComputerName,
[Parameter(Mandatory=$True)][Int32]$Port,
[String]$ProxyServer="",
[Int32]$ProxyPort
)
$URI = "http://$ComputerName/Citrix/CdsController/IRegistrar"
$InitialDataToSend = "POST $URI HTTP/1.1`r`nContent-Type: application/soap+xml; charset=utf-8`r`nHost: ${ComputerName}:${Port}`r`nContent-Length: 1`r`nExpect: 100-continue`r`nConnection: Close`r`n`r`n"
$FinalDataToSend = "X"
$IsServiceListening = $False
If ($ProxyServer -eq "" -OR $ProxyServer -eq $NULL) {
$ConnectToHost = $ComputerName
[int]$ConnectOnPort = $Port
} Else {
$ConnectToHost = $ProxyServer
[int]$ConnectOnPort = $ProxyPort
#write-verbose "Connecting via a proxy" -verbose
}
$Saddrf = [System.Net.Sockets.AddressFamily]::InterNetwork
$Stype = [System.Net.Sockets.SocketType]::Stream
$Ptype = [System.Net.Sockets.ProtocolType]::TCP
$socket = New-Object System.Net.Sockets.Socket $saddrf, $stype, $ptype
Try {
$socket.Connect($ConnectToHost,$ConnectOnPort)
#$socket.Connected
$initialbytes = [System.Text.Encoding]::ASCII.GetBytes($InitialDataToSend)
#write-verbose "Sending initial data..." -verbose
#$InitialDataToSend
$NumBytesSent = $socket.Send($initialbytes, $initialbytes.length,[net.sockets.socketflags]::None)
#write-verbose "Sent $NumBytesSent bytes" -verbose
$numArray = new-object byte[] 21
$socket.ReceiveTimeout = 5000
$NumBytesReceived = $socket.Receive($numArray)
#write-verbose "Received $NumBytesReceived bytes" -verbose
$output = [System.Text.Encoding]::ASCII.GetString($numArray)
$finalBytes = [System.Text.Encoding]::ASCII.GetBytes($FinalDataToSend)
#write-verbose "Sending final data..." -verbose
#$FinalDataToSend
$NumBytesSent = $socket.Send($finalBytes, $finalBytes.length,[net.sockets.socketflags]::None)
#write-verbose "Sent $NumBytesSent bytes" -verbose
$socket.Close() | out-null
#write-verbose "Received data..." -verbose
#$output
if ($output -eq "HTTP/1.1 100 Continue") {
$IsServiceListening = $True
}
}
Catch {
#
}
return $IsServiceListening
}
Function Get-DeliveryControllers {
param (
[switch]$Registry
)
$DeliveryControllers = ""
$Port = 80
$IsServiceListening = $False
$RegPath1 = "HKLM:\SOFTWARE\Policies\Citrix\VirtualDesktopAgent"
$RegPath2 = "HKLM:\SOFTWARE\Citrix\VirtualDesktopAgent"
$ListOfDDCsValue = "ListOfDDCs"
$ListOfDDCsValueExist = $False
$ControllerRegistrarPortValue = "ControllerRegistrarPort"
$ControllerRegistrarPortValueExists = $False
$RegPath = ""
If ($Registry) {
$ErrorActionPreference = "stop"
try {
If ((Get-ItemProperty -Path "$RegPath1" | Select-Object -ExpandProperty "$ListOfDDCsValue") -ne $null) {
$ListOfDDCsValueExist = $True
$RegPath = $RegPath1
}
If ((Get-ItemProperty -Path "$RegPath1" | Select-Object -ExpandProperty "$ControllerRegistrarPortValue") -ne $null) {
$ControllerRegistrarPortValueExists = $True
}
}
catch {
#
}
$ErrorActionPreference = "Continue"
If ([String]::IsNullOrEmpty($RegPath)) {
$ErrorActionPreference = "stop"
try {
If ((Get-ItemProperty -Path "$RegPath2" | Select-Object -ExpandProperty "$ListOfDDCsValue") -ne $null) {
$ListOfDDCsValueExist = $True
$RegPath = $RegPath2
}
If ((Get-ItemProperty -Path "$RegPath2" | Select-Object -ExpandProperty "$ControllerRegistrarPortValue") -ne $null) {
$ControllerRegistrarPortValueExists = $True
}
}
catch {
#
}
$ErrorActionPreference = "Continue"
}
}
If ($ListOfDDCsValueExist) {
If ($Registry) {
$DeliveryControllers = Get-ItemProperty -Path "$RegPath" | Select-Object -ExpandProperty "$ListOfDDCsValue"
If ($ControllerRegistrarPortValueExists) {
$Port = Get-ItemProperty -Path "$RegPath" | Select-Object -ExpandProperty "$ControllerRegistrarPortValue"
}
}
If ($DeliveryControllers -ne "") {
$DeliveryControllers.Split(" ") | ForEach{
$charCount = ($_.ToCharArray() | Where-Object {$_ -eq '.'} | Measure-Object).Count
If ($charCount -gt 0) {
If (XDPing -ComputerName "$_" -Port $Port) { $IsServiceListening = $True }
}
}
}
}
$results = @()
$ResultProps = @{
UseRegistry = $Registry
RegPath = $RegPath.replace("HKLM:\","HKLM\")
DeliveryControllers = $DeliveryControllers
Port = $Port
IsServiceListening = $IsServiceListening
}
$results += New-Object PsObject -Property $ResultProps
return $results
}
# Get the Operating System Major, Minor, Build and Revision Version Numbers
$OSVersion = [System.Environment]::OSVersion.Version
[int]$OSMajorVer = $OSVersion.Major
[int]$OSMinorVer = $OSVersion.Minor
[int]$OSBuildVer = $OSVersion.Build
# Get the UBR (Update Build Revision) from the registry so that the full build number
# is the same as the output of of the ver command line.
[int]$OSRevisionVer = 0
$Path = "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion"
$ValueExists = $False
$ErrorActionPreference = "stop"
try {
If ((Get-ItemProperty -Path "$Path" | Select-Object -ExpandProperty "UBR") -ne $null) {
$ValueExists = $True
}
}
catch {
#
}
$ErrorActionPreference = "Continue"
If ($ValueExists) {
[int]$OSRevisionVer = (Get-ItemProperty -Path "$Path" -Name UBR).UBR
}
#-------------------------------------------------------------
If ($CheckForValidity) {
$DeliveryControllers = ""
$i = 0
Do {
write-verbose "$(Get-Date): Checking to see if there is a valid Deliver Controller set under the following registry values:" -verbose
write-verbose "$(Get-Date): - `"HKLM\SOFTWARE\Policies\Citrix\VirtualDesktopAgent\ListOfDDCs`"" -verbose
write-verbose "$(Get-Date): - `"HKLM\SOFTWARE\Citrix\VirtualDesktopAgent\ListOfDDCs`"" -verbose
$IsValid = Get-DeliveryControllers -Registry
$DeliveryControllers = $IsValid.DeliveryControllers
$Port = $IsValid.Port
If ([String]::IsNullOrEmpty($DeliveryControllers)) { $DeliveryControllers = "none set" }
write-verbose "$(Get-Date): - Values found under `"$($IsValid.RegPath)`"" -verbose
write-verbose "$(Get-Date): - Attempting an XDPing to Delivery Controller(s): $DeliveryControllers" -verbose
write-verbose "$(Get-Date): - Using TCP port: $Port" -verbose
If (!$IsValid.IsServiceListening) {
write-verbose "$(Get-Date): - XDPing failed" -verbose
write-verbose "$(Get-Date): - Will check again in $interval seconds" -verbose
$i++
If ($i -eq $RepeatValidityCheck) { break }
Start-Sleep -Seconds $interval
}
} Until ($IsValid.IsServiceListening -eq $True)
If ($IsValid.IsServiceListening) {
write-verbose "$(Get-Date): - XDPing successful" -verbose
} Else {
write-verbose "$(Get-Date): - Continuing with invalid Delivery Controller(s): $DeliveryControllers" -verbose
}
}
$VDAHelperSettings = Get-VDAHelperSettings
write-verbose "$(Get-Date): The VDA Helper settings are:" -verbose
$VDAHelperSettingsEnabled = $VDAHelperSettings.VDAHelperSettingsEnabled
write-verbose "$(Get-Date): - VDAHelperSettingsEnabled: $VDAHelperSettingsEnabled (0=False,1=True)" -verbose
$TriggerOnTaskEndEvent = $VDAHelperSettings.TriggerOnTaskEndEvent
write-verbose "$(Get-Date): - TriggerOnTaskEndEvent: $TriggerOnTaskEndEvent (0=False,1=True)" -verbose
$TaskNameFullPath = $VDAHelperSettings.TaskNameFullPath
write-verbose "$(Get-Date): - TaskNameFullPath: $TaskNameFullPath" -verbose
$DelayDesktopServiceTime = $VDAHelperSettings.DelayDesktopServiceTime
write-verbose "$(Get-Date): - DelayDesktopServiceTime: $DelayDesktopServiceTime seconds" -verbose
$MaximumTaskWaitTime = $VDAHelperSettings.MaximumTaskWaitTime
write-verbose "$(Get-Date): - MaximumTaskWaitTime: $MaximumTaskWaitTime seconds" -verbose
$LogonEventUserName = $VDAHelperSettings.LogonEventUserName
write-verbose "$(Get-Date): - LogonEventUserName: $LogonEventUserName" -verbose
$DefaultDomainName = $VDAHelperSettings.DefaultDomainName
write-verbose "$(Get-Date): - DefaultDomainName: $DefaultDomainName" -verbose
$LastBootTime = Get-LastBootTime
write-verbose "$(Get-Date): This host was last booted on $LastBootTime" -verbose
If ($VDAHelperSettingsEnabled) {
$TaskExists = IsTaskValid -TaskName:"$TaskNameFullPath"
$EventLog = "Microsoft-Windows-TaskScheduler/Operational"
$EventLogExists = IsEventLogValid -EventLog:"$EventLog"
If ($LastBootTime -gt (Get-Date)) {
If ($TriggerOnTaskEndEvent) {
$TriggerOnTaskEndEvent = $False
write-warning "$(Get-Date -format "dd/MM/yyyy HH:mm:ss"): The last boot up time is greater than the current time. This means that we are unable to trigger on a task event because we cannot accurately correlate the Event logs due to not having a valid starting point." -verbose
}
}
If ($TriggerOnTaskEndEvent -AND $TaskExists -AND $EventLogExists) {
write-verbose "$(Get-Date): The `"$TaskNameFullPath`" task is valid" -verbose
$StartPhase = (Get-Date)
$ValidStart = $False
$ValidEnd = $False
$ValidLogoffInitiated = $False
$ValidLogoffTerminated = $False
Do {
$TaskLastStartTime = (Get-TaskEventRunTime -Start -Taskpath "$TaskNameFullPath").TimeCreated
write-verbose "$(Get-Date): The task last started on $TaskLastStartTime" -verbose
If ($LastBootTime -lt $TaskLastStartTime) {
write-verbose "$(Get-Date): - The task has started." -verbose
$ValidStart = $True
} Else {
write-verbose "$(Get-Date): - Waiting $(((Get-Date)-$StartPhase).TotalSeconds) seconds for the task to start after the reboot..." -verbose
}
If ($(((Get-Date)-$StartPhase).TotalSeconds) -ge $MaximumTaskWaitTime) {
$ValidStart = $True
$ValidEnd = $True
}
Start-Sleep -Seconds 1
} Until ($ValidStart -eq $True)
Do {
$TaskLastEndTime = (Get-TaskEventRunTime -End -Taskpath "$TaskNameFullPath").TimeCreated
write-verbose "$(Get-Date): The task last finished on $TaskLastEndTime" -verbose
If ($LastBootTime -lt $TaskLastEndTime -AND $TaskLastStartTime -lt $TaskLastEndTime){
write-verbose "$(Get-Date): - The task has ended." -verbose
$ValidEnd = $True
} Else {
write-verbose "$(Get-Date): - Waiting $(((Get-Date)-$StartPhase).TotalSeconds) seconds for the task to end..." -verbose
}
If ($(((Get-Date)-$StartPhase).TotalSeconds) -ge $MaximumTaskWaitTime) {
$ValidEnd = $True
}
Start-Sleep -Seconds 1
} Until ($ValidEnd -eq $True)
# Confirming that the session has logged on.
$LogonEvent = Get-LogonLogoffEvent -Logon -UserName:"$LogonEventUserName"
If ($LogonEvent.EventFound) {
write-verbose "$(Get-Date): A valid logon event was found for the `"$LogonEventUserName`" user account on $($LogonEvent.TimeCreated)" -verbose
Do {
# Confirming that the logoff has been initiated.
$LogoffEvent4647 = Get-LogonLogoffEvent -Logoff -Event:"4647" -UserName:"$LogonEventUserName" -LogonID:"$($LogonEvent.LogonID)"
If ($LogonEvent.TimeCreated -lt $LogoffEvent4647.TimeCreated){
write-verbose "$(Get-Date): - The logoff was initiated on $($LogoffEvent4647.TimeCreated)" -verbose
$ValidLogoffInitiated = $True
} Else {
write-verbose "$(Get-Date): - Waiting $(((Get-Date)-$StartPhase).TotalSeconds) seconds for the account to logoff..." -verbose
}
If ($(((Get-Date)-$StartPhase).TotalSeconds) -ge $MaximumTaskWaitTime) {
$ValidLogoffInitiated = $True
$ValidLogoffTerminated = $True
}
Start-Sleep -Seconds 1
} Until ($ValidLogoffInitiated -eq $True)
Do {
# Confirming that the session has been terminated/destroyed.
For ($i=1; $i -le 10) {
If ($OSMajorVer -eq 6 -AND $OSMinorVer -eq 1) {
$LogoffEvent4634 = Get-LogonLogoffEvent -Logoff -Event:"4634" -UserName:"${env:computername}$"
$i = 10
} Else {
$LogoffEvent4634 = Get-LogonLogoffEvent -Logoff -Event:"4634" -UserName:"DWM-$i"
}
If ($LogoffEvent4647.TimeCreated -le $LogoffEvent4634.TimeCreated){
write-verbose "$(Get-Date): - The logoff for user DWM-$i was terminated on $($LogoffEvent4634.TimeCreated)" -verbose
$ValidLogoffTerminated = $True
} Else {
write-verbose "$(Get-Date): - Waiting $(((Get-Date)-$StartPhase).TotalSeconds) seconds for the account to logoff..." -verbose
}
If ($(((Get-Date)-$StartPhase).TotalSeconds) -ge $MaximumTaskWaitTime) {
$ValidLogoffTerminated = $True
}
If ($ValidLogoffTerminated) {
Break
}
$i++
}
Start-Sleep -Seconds 1
} Until ($ValidLogoffTerminated -eq $True)
} Else {
write-verbose "$(Get-Date): No valid logon event was found for the `"$LogonEventUserName`" user account." -verbose
}
} Else {
If ($TriggerOnTaskEndEvent) {
If ($TaskExists -eq $False) {
write-warning "The `"$TaskNameFullPath`" task does not exist" -verbose
}
If ($EventLogExists -eq $False) {
write-warning "The `"$EventLog`" Event Log does not" -verbose
write-warning "exist. This may be because there's a new disk attached to the virtual" -verbose
write-warning "machine and the Event Logs haven't finished being moved to the new" -verbose
write-warning "location. If this is the case, restarting the Virtual Machine again" -verbose
write-warning "should address this issue." -verbose
}
}
write-verbose "$(Get-Date): Delaying the starting of the Citrix Desktop Service for $DelayDesktopServiceTime seconds" -verbose
Start-Sleep -s $DelayDesktopServiceTime
}
}
If (!([String]::IsNullOrEmpty($DefaultDomainName))) {
Write-Verbose "$(Get-Date): Setting the DefaultDomainName to `"$DefaultDomainName`"" -verbose
Set-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon" -Name "DefaultDomainName" -Type STRING -Value "$DefaultDomainName" –Force
}
ForEach ($key in $ServicesToStart.keys) {
$ServiceName = $ServicesToStart.$key.Name
$ServiceDisplayName = $ServicesToStart.$key.DisplayName
write-verbose "$(Get-Date): Setting the `"$ServiceDisplayName`" service to Manual start type..." -verbose
# Possible results using the sc.exe command line tool:
# [SC] ChangeServiceConfig SUCCESS
# [SC] OpenSCManager FAILED 5: Access is denied.
# [SC] OpenSCManager FAILED 1722: The RPC server is unavailable." --> Computer shutdown
# [SC] OpenService FAILED 1060: The specified service does not exist as an installed service." --> Service not installed
Invoke-Command {cmd /c sc.exe config "$ServiceName" start= demand} | out-null
If ($StopServiceIfAlreadyStarted) {
# Stop the service
$objservice = Get-Service "$ServiceName" -ErrorAction SilentlyContinue
If ($objService) {
If ($objService.Status -eq "Running") {
write-verbose "$(Get-Date): Stopping the service..." -verbose
stop-service -name "$ServiceName" -verbose
do {
Start-sleep -s 5
write-verbose "$(Get-Date): - waiting for the service to stop" -verbose
}
until ((get-service "$ServiceName").Status -eq "Stopped")
write-verbose "$(Get-Date): - the service has stopped" -verbose
} Else {
If ($objService.Status -eq "Stopped") {
write-verbose "$(Get-Date): The service is not running" -verbose
}
}
} Else {
write-verbose "$(Get-Date): The `"$ServiceDisplayName`" service does not exist" -verbose
}
}
# Start the Service
$objservice = Get-Service "$ServiceName" -ErrorAction SilentlyContinue
If ($objService) {
If ($objService.Status -eq "Stopped") {
write-verbose "$(Get-Date): Starting the service..." -verbose
start-service -name "$ServiceName" -verbose
do {
Start-sleep -s 5
write-verbose "$(Get-Date): - waiting for the service to start" -verbose
}
until ((get-service "$ServiceName").Status -eq "Running")
write-verbose "$(Get-Date): - the service has started" -verbose
} Else {
If ($objService.Status -eq "Running") {
write-verbose "$(Get-Date): The service is already running" -verbose
}
}
} Else {
write-verbose "$(Get-Date): The `"$ServiceDisplayName`" service does not exist" -verbose
}
}
#-------------------------------------------------------------
$EndDTM = (Get-Date)
write-verbose "$(Get-Date): Elapsed Time: $(($EndDTM-$StartDTM).TotalSeconds) Seconds" -Verbose
write-verbose "$(Get-Date): Elapsed Time: $(($EndDTM-$StartDTM).TotalMinutes) Minutes" -Verbose
try {
Stop-Transcript
}
catch {
write-verbose "$(Get-Date): This host does not support transcription"
}