<# This script provides a countdown timer with progress bar in a nice UI that will automatically logoff the session when the countdown reaches 0. It was primarily written to help manage the post reboot process of Citrix VDA Session Hosts. When a Citrix VDA restarts as part of scheduled reboots, for example, or when non-persistent desktops reboot to reset, the first logon is generally always the longest. So we use an autologon account to prime the VDA when it restarts. This script will logoff the auto logged on session when the countdown reaches 0. I have found it more reliable to use shutdown.exe instead of logoff.exe so that we can forcibly logoff the session as logoff.exe sometimes gets stuck due to running processes. So use... shutdown.exe /l /f where... /l = logoff /f = force Note that the "Last Run Result" of the scheduled task will be 0x40010004 because we forcefully logoff the session which kills the process (this script) causing it to exit with a code of 0x40010004. Based on a script written by Maurice Daly on 04/10/2016 - https://modalyitblog.wordpress.com/2016/10/03/powershell-gui-reboot-prompt/ Script name: AutoLogonLogoffTimer.ps1 Release 1.2 Modified by Jeremy Saunders (jeremy@jhouseconsulting.com) 20th June 2017 #> #------------------------------------------------------------- [cmdletbinding()] param ( [int]$Seconds=15 ) # Set Powershell Compatibility Mode Set-StrictMode -Version 2.0 # Enable verbose, warning and error mode $VerbosePreference = 'Continue' $WarningPreference = 'Continue' $ErrorPreference = 'Continue' #------------------------------------------------------------- # Hide the PowerShell console window without hiding the other child windows that it spawns # - http://powershell.cz/2013/04/04/hide-and-show-console-window-from-gui/ $Code = @" [DllImport("Kernel32.dll")] public static extern IntPtr GetConsoleWindow(); [DllImport("user32.dll")] public static extern bool ShowWindow(IntPtr hWnd, Int32 nCmdShow); "@ # Create new types as per the definition above. Add-Type -Namespace Console -Name Window -MemberDefinition $code -PassThru | out-null Function Show-Console { $consolePtr = [Console.Window]::GetConsoleWindow() #5 show [Console.Window]::ShowWindow($consolePtr, 5) } Function Hide-Console { $consolePtr = [Console.Window]::GetConsoleWindow() #0 hide [Console.Window]::ShowWindow($consolePtr, 0) } Hide-Console | out-null #------------------------------------------------------------- #---------------------------------------------- #region Import Assemblies #---------------------------------------------- [void][Reflection.Assembly]::Load('System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089') [void][Reflection.Assembly]::Load('System.Data, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089') [void][Reflection.Assembly]::Load('System.Drawing, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a') #endregion Import Assemblies function Main { <# .SYNOPSIS The Main function starts the project application. .PARAMETER Seconds $Seconds is the number of seconds for the countdown timer. .NOTES Use this function to initialize your script and to call GUI forms. .NOTES To get the console output in the Packager (Forms Engine) use: $ConsoleOutput (Type: System.Collections.ArrayList) #> Param ([int]$Seconds) #-------------------------------------------------------------------------- #TODO: Add initialization script here (Load modules and check requirements) #-------------------------------------------------------------------------- if((Call-MainForm_psf($Seconds)) -eq 'OK') { } $global:ExitCode = 0 #Set the exit code for the Packager } function Call-MainForm_psf { Param ([int]$Seconds) #---------------------------------------------- #region Import the Assemblies #---------------------------------------------- [void][reflection.assembly]::Load('System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089') [void][reflection.assembly]::Load('System.Data, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089') [void][reflection.assembly]::Load('System.Drawing, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a') #endregion Import Assemblies #---------------------------------------------- #region Generated Form Objects #---------------------------------------------- [System.Windows.Forms.Application]::EnableVisualStyles() $MainForm = New-Object 'System.Windows.Forms.Form' $panel2 = New-Object 'System.Windows.Forms.Panel' $ButtonCancel = New-Object 'System.Windows.Forms.Button' $ButtonLogoffNow = New-Object 'System.Windows.Forms.Button' $panel1 = New-Object 'System.Windows.Forms.Panel' $labelITSystemsMaintenance = New-Object 'System.Windows.Forms.Label' $labelSecondsLeftToLogoff = New-Object 'System.Windows.Forms.Label' $labelTime = New-Object 'System.Windows.Forms.Label' $labelDescription = New-Object 'System.Windows.Forms.Label' $timerUpdate = New-Object 'System.Windows.Forms.Timer' $InitialFormWindowState = New-Object 'System.Windows.Forms.FormWindowState' $progressBar1 = New-Object 'System.Windows.Forms.ProgressBar' #endregion Generated Form Objects #---------------------------------------------- # User Generated Script #---------------------------------------------- $TotalTime = $Seconds #in seconds $MainForm_Load={ #TODO: Initialize Form Controls here $labelTime.Text = "{0:D2}" -f $TotalTime #$TotalTime #Add TotalTime to current time $script:StartTime = (Get-Date).AddSeconds($TotalTime) #Start the timer and progress bar $timerUpdate.Start() $progressBar1.PerformStep() } $timerUpdate_Tick={ # Define countdown timer [TimeSpan]$span = $script:StartTime - (Get-Date) #Update the display $labelTime.Text = "{0:N0}" -f $span.TotalSeconds $timerUpdate.Start() if ($span.TotalSeconds -le 0) { $timerUpdate.Stop() shutdown.exe /l /f } $progressBar1.PerformStep() } $ButtonLogoffNow_Click = { # Logoff the computer immediately shutdown.exe /l /f } $ButtonCancel_Click={ $MainForm.Add_Closing({$_.Cancel = $false}) # Re-enable closing the form $MainForm.Close() } $labelITSystemsMaintenance_Click={ #TODO: Place custom script here } $panel2_Paint=[System.Windows.Forms.PaintEventHandler]{ #Event Argument: $_ = [System.Windows.Forms.PaintEventArgs] #TODO: Place custom script here } $labelTime_Click={ #TODO: Place custom script here } # --End User Generated Script-- #---------------------------------------------- #region Generated Events #---------------------------------------------- $Form_StateCorrection_Load= { #Correct the initial state of the form to prevent the .Net maximized form issue $MainForm.WindowState = $InitialFormWindowState } $Form_StoreValues_Closing= { #Store the control values } $Form_Cleanup_FormClosed= { #Remove all event handlers from the controls try { $ButtonCancel.remove_Click($buttonCancel_Click) $ButtonLogoffNow.remove_Click($ButtonLogoffNow_Click) $panel2.remove_Paint($panel2_Paint) $labelITSystemsMaintenance.remove_Click($labelITSystemsMaintenance_Click) $labelTime.remove_Click($labelTime_Click) $MainForm.remove_Load($MainForm_Load) $timerUpdate.remove_Tick($timerUpdate_Tick) $MainForm.remove_Load($Form_StateCorrection_Load) $MainForm.remove_Closing($Form_StoreValues_Closing) $MainForm.remove_FormClosed($Form_Cleanup_FormClosed) } catch [Exception] { } } #endregion Generated Events #---------------------------------------------- #region Generated Form Code #---------------------------------------------- $MainForm.SuspendLayout() $panel2.SuspendLayout() $panel1.SuspendLayout() # # MainForm # $MainForm.Controls.Add($panel2) $MainForm.Controls.Add($panel1) $MainForm.Controls.Add($labelSecondsLeftToLogoff) $MainForm.Controls.Add($labelTime) $MainForm.Controls.Add($labelDescription) $MainForm.Controls.Add($progressBar1) $MainForm.AutoScaleDimensions = '6, 13' $MainForm.AutoScaleMode = 'Font' $MainForm.BackColor = 'White' $MainForm.ClientSize = '373, 400' $MainForm.MaximizeBox = $False $MainForm.MinimizeBox = $False $MainForm.Add_Closing({$_.Cancel = $true}) # Disable closing the form using the X button $MainForm.WindowState = 'Normal' $MainForm.Name = 'MainForm' $MainForm.ShowIcon = $False $MainForm.ShowInTaskbar = $False $MainForm.StartPosition = 'CenterScreen' $MainForm.Text = 'Citrix VDA Autologon Process' $MainForm.TopMost = $True $MainForm.add_Load($MainForm_Load) # # panel2 # $panel2.Controls.Add($ButtonCancel) $panel2.Controls.Add($ButtonLogoffNow) $panel2.BackColor = 'ScrollBar' $panel2.Location = '0, 326' $panel2.Name = 'panel2' $panel2.Size = '378, 80' $panel2.TabIndex = 9 $panel2.add_Paint($panel2_Paint) # # ButtonCancel # $ButtonCancel.Location = '250, 17' $ButtonCancel.Name = 'ButtonCancel' $ButtonCancel.Size = '77, 45' $ButtonCancel.TabIndex = 7 $ButtonCancel.Text = 'Cancel' $ButtonCancel.UseVisualStyleBackColor = $True $ButtonCancel.add_Click($buttonCancel_Click) # # ButtonLogoffNow # $ButtonLogoffNow.Font = 'Microsoft Sans Serif, 8.25pt, style=Bold' $ButtonLogoffNow.ForeColor = 'DarkRed' $ButtonLogoffNow.Location = '42, 17' $ButtonLogoffNow.Name = 'ButtonLogoffNow' $ButtonLogoffNow.Size = '91, 45' $ButtonLogoffNow.TabIndex = 0 $ButtonLogoffNow.Text = 'Logoff Now' $ButtonLogoffNow.UseVisualStyleBackColor = $True $ButtonLogoffNow.add_Click($ButtonLogoffNow_Click) # # panel1 # $panel1.Controls.Add($labelITSystemsMaintenance) $panel1.BackColor = '0, 114, 198' $panel1.Location = '0, 0' $panel1.Name = 'panel1' $panel1.Size = '375, 67' $panel1.TabIndex = 8 # # labelITSystemsMaintenance # $labelITSystemsMaintenance.Font = 'Microsoft Sans Serif, 14.25pt' $labelITSystemsMaintenance.ForeColor = 'White' $labelITSystemsMaintenance.Location = '11, 18' $labelITSystemsMaintenance.Name = 'labelITSystemsMaintenance' $labelITSystemsMaintenance.Size = '310, 23' $labelITSystemsMaintenance.TabIndex = 1 $labelITSystemsMaintenance.Text = 'Citrix VDA Autologon Logoff Timer' $labelITSystemsMaintenance.TextAlign = 'MiddleLeft' $labelITSystemsMaintenance.add_Click($labelITSystemsMaintenance_Click) # # labelSecondsLeftToLogoff # $labelSecondsLeftToLogoff.AutoSize = $True $labelSecondsLeftToLogoff.Font = 'Microsoft Sans Serif, 9pt, style=Bold' $labelSecondsLeftToLogoff.Location = '87, 283' $labelSecondsLeftToLogoff.Name = 'labelSecondsLeftToLogoff' $labelSecondsLeftToLogoff.Size = '155, 15' $labelSecondsLeftToLogoff.TabIndex = 5 $labelSecondsLeftToLogoff.Text = 'Seconds left to logoff :' # # labelTime # $labelTime.AutoSize = $True $labelTime.Font = 'Microsoft Sans Serif, 9pt, style=Bold' $labelTime.ForeColor = '192, 0, 0' $labelTime.Location = '237, 283' $labelTime.Name = 'labelTime' $labelTime.Size = '43, 15' $labelTime.TabIndex = 3 $labelTime.Text = '00:60' $labelTime.TextAlign = 'MiddleCenter' $labelTime.add_Click($labelTime_Click) # # labelDescription # $labelDescription.Font = 'Microsoft Sans Serif, 9pt' $labelDescription.Location = '12, 76' $labelDescription.Name = 'labelDescription' $labelDescription.Size = '350, 204' $labelDescription.TabIndex = 2 $labelDescription.Text = 'When a Citrix VDA restarts as either part of scheduled reboots, or when non-persistent desktops reboot to reset, the first logon is generally always the longest. So we use an autologon account to prime the VDA when it restarts. This script is designed to logoff the auto logged on session when the countdown reaches 0. If using Power Management with non-persistent desktops, you must ensure this script runs before the Citrix Desktop Service (BrokerAgent) service starts, or you will end up in a reboot loop. If you do not wish to logoff at this time, please click on the cancel button below.' # # progressBar1 # $progressBar1.Location = '12, 306' $progressBar1.Size = '350, 15' $progressBar1.Name = 'progressBar1' $progressBar1.Minimum = 0 $progressBar1.Maximum = $TotalTime $progressBar1.Step = 1 $progressBar1.Value = 0 $progressBar1.Style = 'continuous' # # timerUpdate # $timerUpdate.Interval = 1000 # 1 second $timerUpdate.add_Tick($timerUpdate_Tick) $panel1.ResumeLayout() $panel2.ResumeLayout() $MainForm.ResumeLayout() #endregion Generated Form Code #---------------------------------------------- #Save the initial state of the form $InitialFormWindowState = $MainForm.WindowState #Init the OnLoad event to correct the initial state of the form $MainForm.add_Load($Form_StateCorrection_Load) #Clean up the control events $MainForm.add_FormClosed($Form_Cleanup_FormClosed) #Store the control values when form is closing $MainForm.add_Closing($Form_StoreValues_Closing) #Show the Form return $MainForm.ShowDialog() } #Start the application Main ($Seconds)