Several years ago, and inspired by an article written by George Spiers to reduce login times, where “the second logon is quicker”, together with some code from Maurice Daly, I created a methodology and scripts that is designed to Autologon a non-persistent Session Host (both VDI and RDS), and then log it off again before another script will Start the Citrix Desktop Service (BrokerAgent).

It has been working flawlessly for years. However, I was never 100% happy with it because the process was using a domain (service) account for the Autologon process. The main challenge here was trying to change the password on a regular basis to stay compliant when managing multiple images. You cannot realistically do it without an outage. And in a 24×7 environment, it becomes difficult and onerous. I also felt that using a domain account can be “heavy” during a boot storm as you need to ensure you are excluding this account from profile management and policies where possible. Sometimes that is easier said than done. There is a level of risk here, as someone can easily make a change that will cause issues. The ability to roll the password and stay compliant was my biggest concern and where I got stuck for quite some time.
I spent a lot of time whiteboarding the process flow and all the moving parts (the scripts, registry values involved, etc). I realised that the only way to address this was to have a local account with a strong secure password (stored as an LSA Secret) that changes every time you rebuild the image, or re-run the script. You never need to record or know the password, and it can be different for each image. The local account is added to the local Users group by default, as there is no need for it to be a local Administrator. If you have the “Allow log on locally” policy set that excludes the local Users group, you’ll either need to adjust this policy or add the user to the appropriate local group that allows them to logon locally. If needed there is a variable you can set that will add the account to the local Administrators group. The only constant needs to be the name of the local account so that it works in conjunction with my Start the Citrix Desktop Service (BrokerAgent) script.
Then I got caught out by the Winlogon DefaultDomainName registry value without even realising that since Windows Vista and Server 2008 R1 it was no longer used to set the default logon Domain for the computer. Silly me! I had a Group Policy that would apply the Domain Name to this value by a registry preference, as that was once a best practice. So this would fill the DefaultDomainName registry value, breaking the Autologon of a local account. Together with these scripts and changes I made to the Start the Citrix Desktop Service (BrokerAgent) script, I was finally able to get the process working as needed.
Maybe these scenarios is also why Microsoft moved this Winlogon DefaultDomainName dependency starting from Windows Vista and Server 2008 R1 to a different registry location controlled by the “Assign a default domain for logon” group policy setting.
Now that I’ve overcome these challenges, I’m happy to share the scripts and process.
Here is a video of a Session Host starting up, auto logging on using a local account named PrimeMySystem, and then auto logging off gracefully.
Cool, isn’t it? 🙂
This process uses two PowerShell scripts and a batch script to make it simple to deploy.
- AutoLogonLogoffTimer.ps1 (825 downloads ) – This PowerShell script is the UI with the countdown started by the Scheduled Task when the PrimeMySystem account logs on, which then gracefully logs off the session.
- AutoLogonScheduledTask.ps1 (720 downloads ) – This PowerShell script creates the local account, generates a strong secure password, sets the Autologon registry values, stores the password as an LSA Secret, copies the AutoLogonLogoffTimer.ps1 to the C:\Scripts folder, and creates the “Session Host Autologon Logoff Task” Scheduled Task.
- AutoLogonScheduledTask.cmd (755 downloads ) – This batch script can be used to run the AutoLogonScheduledTask.ps1 script with the required parameters.
To deploy, download all 3 scripts and place them in the same folder. Run the batch script as administrator to complete the configuration and copy the AutoLogonLogoffTimer.ps1 script into place.
As always, my scripts are well documented to help make them easy to follow.
Here is the full code view of the AutoLogonScheduledTask.cmd (755 downloads ) script
@Echo Off
cls
:: This script will deploy the AutoLogon process
SetLocal
Set LocalLocation=%WinDir%\Temp
copy /y "%~dp0AutoLogonScheduledTask.ps1" "%LocalLocation%"
copy /y "%~dp0AutoLogonLogoffTimer.ps1" "%LocalLocation%"
PUSHD "%LocalLocation%"
IF /I "%1"=="MDTBuildAccount" GOTO MDT
IF /I "%1"=="" GOTO LocalAccount
:MDT
powershell.exe -ExecutionPolicy Bypass -Command "& '"%LocalLocation%\AutoLogonScheduledTask.ps1"' -MDT -RemoveLegalPrompt -WaitForNetwork -RemoveDynamicSiteName"
GOTO Finish
:LocalAccount
powershell.exe -ExecutionPolicy Bypass -Command "& '"%LocalLocation%\AutoLogonScheduledTask.ps1"' -NewLocalAdmin:'PrimeMySystem' -PasswordLength:20 -SpecialCharacters:5 -RemoveLegalPrompt -RemoveDynamicSiteName"
:Finish
POPD
del /q "%LocalLocation%\AutoLogonScheduledTask.ps1"
del /q "%LocalLocation%\AutoLogonLogoffTimer.ps1"
EndLocal
Exit /b 0
Here is the full code view of the AutoLogonScheduledTask.ps1 (720 downloads ) script
<#
This script sets the Citrix VDA autologon process by creating a scheduled task to logoff the auto logged on
session and copy the script into place. LSA secrets is used to store the password securely.
The script was first written in May 2017 and inspired by the "Autologon account..." section of an article
written by George Spiers:
- https://jgspiers.com/citrix-director-reduce-logon-times/
To secrure the password I have used a PowerShell function, written by Andy Arismendi called Set-SecureAutoLogon,
to manage the LSA secrets:
- https://andyarismendi.blogspot.com/2011/10/powershell-set-secureautologon.html
There are also other references to a similar process here:
- https://www.onevinn.com/blog/windows-10-secure-autologon-powershell
- https://github.com/Ccmexec/MEMCM-OSD-Scripts/tree/master/Kiosk%20scripts
In April 2022 it became clear that using a Domain account became too onerous for rolling password changes between
multiple images due to Cyber and/or password policies, etc. So I started to work on a process that uses a local
account with a strong randomly generated password that is NOT exposed (recorded) and ONLY stored as an LSA secret.
I ran into a few challenges when using a local account for the Autologon process mainly due to the legacy
DefaultDomainName value being set by a Group Policy Preference. So I parked it for a while whilst I considered the
best approach. I enhanced the StartCitrixDesktopService.ps1 script on 31st January 2023 allowing for the
DefaultDomainName value to be managed, and have only just returned to complete the local account enhancements as
of 16th July 2025. In order for this to work you must NOT set the DefaultDomainName by Group Policy under the
"HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon" registry key. Note that the "Assign a
default domain for logon" Group Policy setting sets the DefaultLogonDomain value under the
"HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System" registry key, which does not affect
the Autologon process. If you want to set the DefaultDomainName value, use the additional functionality that I
have added into the StartCitrixDesktopService.ps1 script.
Reference:
- https://www.jhouseconsulting.com/2019/03/04/controlling-the-starting-of-the-citrix-desktop-service-brokeragent-1894
This script uses the Get-LocalUser, New-LocalUser, Set-LocalUser, Get-LocalGroupMember and Add-LocalGroupMember
cmdlets, which require PowerShell 5.1 (WMF 5.1) or later for the most part, so I have included functions that
support older versions of PowerShell.
Syntax Examples:
- To create and set the Autologon credentials and Scheduled Task as a new local user account
called "PrimeMySystem", including removal of the legal prompts and the DynamicSiteName.
AutoLogonScheduledTask.ps1 -NewLocalUser:"PrimeMySystem" -PasswordLength:20 -SpecialCharacters:5 -RemoveLegalPrompt -RemoveDynamicSiteName
- To set the Autologon credentials and Scheduled Task as the MDT Domain Join account, including
removal of the legal prompts, the DynamicSiteName, and set the always wait for the network policy.
AutoLogonScheduledTask.ps1 -MDT -RemoveLegalPrompt -WaitForNetwork -RemoveDynamicSiteName
Where...
-NewLocalUser = New local user to create that will be used for the AutoLogon process.
-MakeUserAnAdmin = Add the user to the local Administrators group. Defaults to false.
-PasswordLength = Length of the randomly generated strong password. Defaults to 20.
-SpecialCharacters = Number of special characters to use in the password. Defaults to 5.
-DomainAdminDomain = Domain in FQDN format preferrably
-DomainAdmin = Username that has permissions to move computer objects in AD.
You would typically use the Domain join account here.
-DomainAdminPassword = Password
-Decode = Decode (Optional). This is needed if you pass the DomainAdminDomain,
DomainAdmin and DomainAdminPassword variables from MDT.
-MDT = Get the DomainAdminDomain DomainAdmin DomainAdminPassword variables and
automatically decrypt them. Note that the "Microsoft.SMS.TSEnvironment"
object is only available during the Task Sequences. You cannot use this
parameter to test this script outside of MDT.
-RemoveLegalPrompt = Remove Legal Banner
-WaitForNetwork = Enable Wait For Network
-RemoveDynamicSiteName = Remove the DynamicSiteName value from registry. This is important if
building an image that will be deployed across multiple Active Directory
Sites.
The registry values Keys for Autologon are found under the following key:
- HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\Current Version\Winlogon
- The values are:
- DefaultUserName (REG_SZ) the account name used for the automatic logon.
- DefaultPassword (REG_SZ) the password for the account specified by the DefaultUserName. We
do not use this, as it is stored in plain text. Instead we store it securly as an LSA Secret.
- DefaultDomainName (REG_SZ) the domain name to which the account specified above is a member.
We leave this blank (empty) in order to use a local account.
- AutoAdminLogon (REG_SZ) Setting will cause the automatic logon to occur with the above
credentials, which includes the stored LSA Secret.
- AutoLogonCount (REG_DWORD) is set to the number of times you want the AutoAdminLogon to take
place. We don't need to set this here, as the AutoAdminLogon setting will allow the Session
Host to autologon, which is all we need.
The system will logon automatically with the specified credentials and decrement the AutoLogonCount
value until it reaches zero. When it reaches zero, the DefaultPassword, stored LSA Secret, and
AutoLogonCount values are deleted and the AutoAdminLogon value is set to 0. However, if the
AutoLogonCount value is missing altogether, the AutoAdminLogon value will remain set to 1 and the
system will continue to autologon after every reboot. This is okay for image management. But if
you want to use this for other purposes, setting the AutoLogonCount to 1 will achieve the same
outcome.
Script name: AutoLogonScheduledTask.ps1
Release 1.8
Written by Jeremy Saunders (jeremy@jhouseconsulting.com) 12th May 2017
Modified by Jeremy Saunders (jeremy@jhouseconsulting.com) 28th July 2025
#>
#-------------------------------------------------------------
[cmdletbinding()]
param (
[string]$NewLocalUser,
[switch]$MakeUserAnAdmin,
[int]$PasswordLength = 20,
[int]$SpecialCharacters = 5,
[string]$DomainAdmin,
[string]$DomainAdminPassword,
[System.Security.SecureString]$SecurePassword,
[string]$DomainAdminDomain,
[Int]$AutoLogonCount,
[switch]$RemoveLegalPrompt,
[switch]$WaitForNetwork,
[switch]$RemoveDynamicSiteName,
[switch]$Backup
)
# Set Powershell Compatibility Mode
Set-StrictMode -Version 2.0
# Enable verbose, warning and error mode
$VerbosePreference = 'Continue'
$WarningPreference = 'Continue'
$ErrorPreference = 'Continue'
#-------------------------------------------------------------
Function IsTaskSequence([switch] $verbose) {
# This code was taken from a discussion on the CodePlex PowerShell App Deployment Toolkit site.
# It was posted by mmashwani.
# The only issue with this function is that it writes terminating errors to the transcription log, which looks
# rather ugly. So this function will eventually be updated so that it checks for the existenace of the relevant
# snapins (Get-PSSnapin) and assemblies ([AppDomain]::CurrentDomain.GetAssemblies()) that have been loaded.
# References:
# - https://richardspowershellblog.wordpress.com/2007/09/30/assemblies-loaded-in-powershell/
# - http://learningpcs.blogspot.com.au/2012/06/powershell-v2-test-if-assembly-is.html
Try {
[__ComObject]$SMSTSEnvironment = New-Object -ComObject Microsoft.SMS.TSEnvironment -ErrorAction 'SilentlyContinue' -ErrorVariable SMSTSEnvironmentErr
}
Catch {
# The Microsoft.SMS.TSEnvironment assembly is not present.
}
If ($SMSTSEnvironmentErr) {
Write-Verbose "Unable to load ComObject [Microsoft.SMS.TSEnvironment]. Therefore, script is not currently running from an MDT or SCCM Task Sequence." -verbose:$verbose
Return $false
}
ElseIf ($null -ne $SMSTSEnvironment) {
Write-Verbose "Successfully loaded ComObject [Microsoft.SMS.TSEnvironment]. Therefore, script is currently running from an MDT or SCCM Task Sequence." -verbose:$verbose
Return $true
}
}
#-------------------------------------------------------------
$invalidChars = [io.path]::GetInvalidFileNamechars()
$datestampforfilename = ((Get-Date -format s).ToString() -replace "[$invalidChars]","-")
# Get the current script path
$ScriptPath = {Split-Path $MyInvocation.ScriptName}
$ScriptPath = $(&$ScriptPath)
$ScriptName = [System.IO.Path]::GetFilenameWithoutExtension($MyInvocation.MyCommand.Path.ToString())
$Logfile = "$ScriptName-$($datestampforfilename).txt"
$logPath = "$($env:windir)\Temp"
If (IsTaskSequence) {
$tsenv = New-Object -COMObject Microsoft.SMS.TSEnvironment
$logPath = $tsenv.Value("LogPath")
}
$logfile = "$logPath\$Logfile"
try {
# The Microsoft.BDD.TaskSequencePSHost.exe (TSHOST) does not support
# transcription, so we wrap this in a try catch to prevent errors.
Start-Transcript $logFile
}
catch {
Write-Verbose "This host does not support transcription"
}
#-------------------------------------------------------------
Function New-RandomPassword {
# This function was posted on Reddit by OPconfused: https://www.reddit.com/r/PowerShell/comments/17wz2xh/powershell_generate_random_password/
param(
[Parameter(Mandatory)]
[Alias('length')]
[ValidateRange(0,30)]
[int]$PasswordLength
)
$validCharacters = 48..57 + 65..90 + 97..122 + (
'!', '@', '#', '$', '%', '^', '&', '*'
) | ForEach-Object { [char]$_ }
return (1..$PasswordLength | ForEach-Object {
Get-Random $validCharacters
}) -join ''
}
Function GenerateStrongPassword {
# http://woshub.com/generating-random-password-with-powershell/
param (
[parameter(Mandatory=$true)][int]$PasswordLength,
[parameter(Mandatory=$true)][int]$SpecialCharacters
)
Add-Type -AssemblyName System.Web
$PassComplexCheck = $false
do {
$newPassword = [System.Web.Security.Membership]::GeneratePassword($PasswordLength,$SpecialCharacters)
If ( ($newPassword -cmatch "[A-Z\p{Lu}\s]") `
-and ($newPassword -cmatch "[a-z\p{Ll}\s]") `
-and ($newPassword -match "[\d]") `
-and ($newPassword -match "[^\w]")
) {
$PassComplexCheck = $True
}
} While ($PassComplexCheck -eq $false)
return $newPassword
}
function Test-RegistryValue {
param (
[parameter(Mandatory=$true)]
[ValidateNotNullOrEmpty()]$Path,
[parameter(Mandatory=$true)]
[ValidateNotNullOrEmpty()]$Value
)
# Create a drive to HKEY_CLASSES_ROOT & HKEY_CURRENT_USER. By default these two
# registry geys are not available for mounting and using in PowerShell.
if (!(get-psdrive hkcr -ea 0)){New-PSDrive -Name HKCR -PSProvider Registry -Root HKEY_CLASSES_ROOT | out-null}
if (!(get-psdrive hku -ea 0)){New-PSDrive -Name HKU -PSProvider Registry -Root HKEY_USERS | out-null}
$ErrorActionPreference = "stop"
try {
If ((Get-ItemProperty -Path "$Path" | Select-Object -ExpandProperty "$Value") -ne $null) {
return $true
} Else {
return $false
}
}
catch {
return $false
}
finally { $ErrorActionPreference = "Continue" }
}
function Test-RegistryPath {
param (
[parameter(Mandatory=$true)]
[ValidateNotNullOrEmpty()]$Path
)
# Create a drive to HKEY_CLASSES_ROOT & HKEY_CURRENT_USER. By default these two
# registry geys are not available for mounting and using in PowerShell.
if (!(get-psdrive hkcr -ea 0)){New-PSDrive -Name HKCR -PSProvider Registry -Root HKEY_CLASSES_ROOT | out-null}
if (!(get-psdrive hku -ea 0)){New-PSDrive -Name HKU -PSProvider Registry -Root HKEY_USERS | out-null}
$ErrorActionPreference = "stop"
try {
Get-Item -Path "$Path" | Out-Null
return $true
}
catch {
return $false
}
finally { $ErrorActionPreference = "Continue" }
}
function createLocalUser {
# This function was written for backward PowerShell compatibility.
# - It creates a new users and adds it to the default "Users" group.
# - It only supports passing a clear text password (no SecureString compatibility).
# - UserFlags is a bitmask. The 0x10000 flag corresponds to "Password never expires".
# - AccountExpirationDate is not supported by the WinNT ADSI Provider.
param (
[string]$Username,
[string]$Password,
[string]$Description,
[switch]$PasswordNeverExpires
)
try {
# Check if user already exists
$existingUser = [ADSI]"WinNT://$env:COMPUTERNAME/$Username,user"
if ($existingUser.Name -eq $Username) {
$existingUser.SetPassword($Password)
$existingUser.SetInfo()
# Set 'password never expires' if requested
if ($PasswordNeverExpires) {
#DONT_EXPIRE_PASSWORD 0x10000 65536
$existingUser.UserFlags = 65536
$existingUser.SetInfo()
}
return $True
}
} catch {
# User does not exist, continue
}
try {
# Create new user
$computer = [ADSI]"WinNT://$env:COMPUTERNAME"
$user = $computer.Create("User", $Username)
$user.SetPassword($Password)
$user.SetInfo()
$user.Description = $Description
$user.SetInfo()
# Set 'password never expires' if requested
if ($PasswordNeverExpires) {
#DONT_EXPIRE_PASSWORD 0x10000 65536
$user.UserFlags = 65536
$user.SetInfo()
}
# Add user to 'Users' group
$group = [ADSI]"WinNT://$env:COMPUTERNAME/Users,group"
$group.Add("WinNT://$env:COMPUTERNAME/$Username,user")
return $True
} catch {
return $False
}
}
function addUser2Group
{
# This function was written by Ethol Palmer and posted to stackoverflow:
# - https://stackoverflow.com/questions/13929960/add-user-to-local-group/14262326#14262326
Param(
[string]$user,
[string]$group
)
$cname = gc env:computername
$objUser = [ADSI]("WinNT://$user")
$objGroup = [ADSI]("WinNT://$cname/$group,group")
$members = $objGroup.Invoke('Members')
$found = $false
foreach($m in $members)
{
if($m.GetType().InvokeMember('Name', 'GetProperty', $null, $m, $null) -eq $user)
{
$found = $true
}
}
if(-not $found)
{
$objGroup.PSBase.Invoke('Add',$objUser.PSBase.Path)
}
$members = $objGroup.PSBase.Invoke('Members')
$found = $false
foreach($m in $members)
{
if($m.GetType().InvokeMember('Name', 'GetProperty', $null, $m, $null) -eq $user)
{
$found = $true
}
}
return $found
}
#-------------------------------------------------------------
$ExitCode = 0
$GoodToProceed = $False
If ([string]::IsNullOrEmpty($NewLocalUser)) {
If ($MDT) {
If (IsTaskSequence) {
Write-Verbose "Reading Task Sequence variables" -verbose
$Decode = $True
$DomainAdminDomain = $tsenv.Value("UserDomain")
$DomainAdmin = $tsenv.Value("UserID")
$DomainAdminPassword = $tsenv.Value("UserPassword")
} Else {
Write-Verbose "This script is not running from a task sequence" -verbose
}
}
If (![string]::IsNullOrEmpty($DomainAdminDomain) -AND ![string]::IsNullOrEmpty($DomainAdmin) -AND (![string]::IsNullOrEmpty($DomainAdminPassword) -OR ![string]::IsNullOrEmpty($SecurePassword))) {
$GoodToProceed = $True
If ($Decode) {
# Decode the base64 encoded blob using UTF-8
$DomainAdminDomain = [System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($DomainAdminDomain))
$DomainAdmin = [System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($DomainAdmin))
$DomainAdminPassword = [System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($DomainAdminPassword))
}
If (![string]::IsNullOrEmpty($DomainAdminPassword)) {
$DomainAdminPasswordSecureString = ConvertTo-SecureString -String $DomainAdminPassword -AsPlainText -Force
} Else {
$DomainAdminPasswordSecureString = $SecurePassword
}
$DefaultUserName = $DomainAdmin
$DefaultDomainName = $DomainAdminDomain
$PasswordSecureString = $DomainAdminPasswordSecureString
$SecurityPrincipal = "$DefaultDomainName\$DefaultUserName"
}
}
If (![string]::IsNullOrEmpty($NewLocalUser)) {
$GoodToProceed = $True
#$StrongPassword = New-RandomPassword -PasswordLength:$PasswordLength
$StrongPassword = GenerateStrongPassword -PasswordLength:$PasswordLength -SpecialCharacters:$SpecialCharacters
$StrongPasswordAsSecureString = ConvertTo-SecureString "$StrongPassword" -AsPlainText -Force
$CreateLocalUser = $True
# I have included the createLocalUser function if you are running a PowerShell version less than 5.1.
If (($PSVersionTable.PSVersion.Major -eq 5 -AND $PSVersionTable.PSVersion.Minor -ge 1) -OR $PSVersionTable.PSVersion.Major -gt 5) {
$ErrorActionPreference = "stop"
Try {
Get-LocalUser -Name "$NewLocalUser" | out-null
$CreateLocalUser = $False
}
Catch {
# Local Admin not found
}
$ErrorActionPreference = "Continue"
If ($CreateLocalUser) {
write-verbose "Creating the `"$NewLocalUser`" account with a randomly generated strong password" -verbose
New-LocalUser "$NewLocalUser" -Password $StrongPasswordAsSecureString -FullName "$NewLocalUser" -Description "$NewLocalUser Account" -PasswordNeverExpires -AccountNeverExpires | out-null
} Else {
write-verbose "The `"$NewLocalUser`" account already exists" -verbose
write-verbose "Setting its password to a randomly generated strong password" -verbose
Set-LocalUser -Name "$NewLocalUser" -Password $StrongPasswordAsSecureString
}
} Else {
write-verbose "Creating the `"$NewLocalUser`" account with a randomly generated strong password" -verbose
createLocalUser -Username "$NewLocalUser" -Password $StrongPassword -Description "$NewLocalUser Account" -PasswordNeverExpires | out-null
}
# I have included the addUser2Group function if you are running a PowerShell version less than 5.1.
$LocalGroupName = "Users"
If ($MakeUserAnAdmin) {
$LocalGroupName = "Administrators"
}
If (($PSVersionTable.PSVersion.Major -eq 5 -AND $PSVersionTable.PSVersion.Minor -ge 1) -OR $PSVersionTable.PSVersion.Major -gt 5) {
$isInGroup = (Get-LocalGroupMember -Group "$LocalGroupName").Name -contains "$env:COMPUTERNAME\$NewLocalUser"
If ($isInGroup -eq $False) {
write-verbose "Adding the `"$NewLocalUser`" account to the local `"$LocalGroupName`" group" -verbose
Add-LocalGroupMember -Group "$LocalGroupName" -Member "$NewLocalUser"
} Else {
write-verbose "The `"$NewLocalUser`" account is already a member of the local `"$LocalGroupName`" group" -verbose
}
} Else {
write-verbose "Adding the `"$NewLocalUser`" account to the local `"$LocalGroupName`" group" -verbose
addUser2Group -user "$NewLocalUser" -group "$LocalGroupName" | out-null
}
$DefaultUserName = "$NewLocalUser"
$DefaultDomainName = ""
$PasswordSecureString = $StrongPasswordAsSecureString
#$SecurityPrincipal = "$ENV:COMPUTERNAME\$DefaultUserName"
$SecurityPrincipal = "$DefaultUserName"
}
If ($GoodToProceed) {
[string] $WinlogonPath = "HKLM:\Software\Microsoft\Windows NT\CurrentVersion\Winlogon"
[string] $WinlogonPolicyPath = "HKLM:\Software\Policies\Microsoft\Windows NT\CurrentVersion\Winlogon"
[string] $WinlogonBannerPolicyPath = "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System"
[string] $NetlogonParametersPath = "HKLM:\System\CurrentControlSet\Services\Netlogon\parameters"
[string] $Enable = 1
[string] $Disable = 0
#region C# Code to P-invoke LSA LsaStorePrivateData function.
Add-Type @"
using System;
using System.Collections.Generic;
using System.Text;
using System.Runtime.InteropServices;
namespace ComputerSystem
{
public class LSAutil
{
[StructLayout(LayoutKind.Sequential)]
private struct LSA_UNICODE_STRING
{
public UInt16 Length;
public UInt16 MaximumLength;
public IntPtr Buffer;
}
[StructLayout(LayoutKind.Sequential)]
private struct LSA_OBJECT_ATTRIBUTES
{
public int Length;
public IntPtr RootDirectory;
public LSA_UNICODE_STRING ObjectName;
public uint Attributes;
public IntPtr SecurityDescriptor;
public IntPtr SecurityQualityOfService;
}
private enum LSA_AccessPolicy : long
{
POLICY_VIEW_LOCAL_INFORMATION = 0x00000001L,
POLICY_VIEW_AUDIT_INFORMATION = 0x00000002L,
POLICY_GET_PRIVATE_INFORMATION = 0x00000004L,
POLICY_TRUST_ADMIN = 0x00000008L,
POLICY_CREATE_ACCOUNT = 0x00000010L,
POLICY_CREATE_SECRET = 0x00000020L,
POLICY_CREATE_PRIVILEGE = 0x00000040L,
POLICY_SET_DEFAULT_QUOTA_LIMITS = 0x00000080L,
POLICY_SET_AUDIT_REQUIREMENTS = 0x00000100L,
POLICY_AUDIT_LOG_ADMIN = 0x00000200L,
POLICY_SERVER_ADMIN = 0x00000400L,
POLICY_LOOKUP_NAMES = 0x00000800L,
POLICY_NOTIFICATION = 0x00001000L
}
[DllImport("advapi32.dll", SetLastError = true, PreserveSig = true)]
private static extern uint LsaRetrievePrivateData(
IntPtr PolicyHandle,
ref LSA_UNICODE_STRING KeyName,
out IntPtr PrivateData
);
[DllImport("advapi32.dll", SetLastError = true, PreserveSig = true)]
private static extern uint LsaStorePrivateData(
IntPtr policyHandle,
ref LSA_UNICODE_STRING KeyName,
ref LSA_UNICODE_STRING PrivateData
);
[DllImport("advapi32.dll", SetLastError = true, PreserveSig = true)]
private static extern uint LsaOpenPolicy(
ref LSA_UNICODE_STRING SystemName,
ref LSA_OBJECT_ATTRIBUTES ObjectAttributes,
uint DesiredAccess,
out IntPtr PolicyHandle
);
[DllImport("advapi32.dll", SetLastError = true, PreserveSig = true)]
private static extern uint LsaNtStatusToWinError(
uint status
);
[DllImport("advapi32.dll", SetLastError = true, PreserveSig = true)]
private static extern uint LsaClose(
IntPtr policyHandle
);
[DllImport("advapi32.dll", SetLastError = true, PreserveSig = true)]
private static extern uint LsaFreeMemory(
IntPtr buffer
);
private LSA_OBJECT_ATTRIBUTES objectAttributes;
private LSA_UNICODE_STRING localsystem;
private LSA_UNICODE_STRING secretName;
public LSAutil(string key)
{
if (key.Length == 0)
{
throw new Exception("Key lenght zero");
}
objectAttributes = new LSA_OBJECT_ATTRIBUTES();
objectAttributes.Length = 0;
objectAttributes.RootDirectory = IntPtr.Zero;
objectAttributes.Attributes = 0;
objectAttributes.SecurityDescriptor = IntPtr.Zero;
objectAttributes.SecurityQualityOfService = IntPtr.Zero;
localsystem = new LSA_UNICODE_STRING();
localsystem.Buffer = IntPtr.Zero;
localsystem.Length = 0;
localsystem.MaximumLength = 0;
secretName = new LSA_UNICODE_STRING();
secretName.Buffer = Marshal.StringToHGlobalUni(key);
secretName.Length = (UInt16)(key.Length * UnicodeEncoding.CharSize);
secretName.MaximumLength = (UInt16)((key.Length + 1) * UnicodeEncoding.CharSize);
}
private IntPtr GetLsaPolicy(LSA_AccessPolicy access)
{
IntPtr LsaPolicyHandle;
uint ntsResult = LsaOpenPolicy(ref this.localsystem, ref this.objectAttributes, (uint)access, out LsaPolicyHandle);
uint winErrorCode = LsaNtStatusToWinError(ntsResult);
if (winErrorCode != 0)
{
throw new Exception("LsaOpenPolicy failed: " + winErrorCode);
}
return LsaPolicyHandle;
}
private static void ReleaseLsaPolicy(IntPtr LsaPolicyHandle)
{
uint ntsResult = LsaClose(LsaPolicyHandle);
uint winErrorCode = LsaNtStatusToWinError(ntsResult);
if (winErrorCode != 0)
{
throw new Exception("LsaClose failed: " + winErrorCode);
}
}
public void SetSecret(string value)
{
LSA_UNICODE_STRING lusSecretData = new LSA_UNICODE_STRING();
if (value.Length > 0)
{
//Create data and key
lusSecretData.Buffer = Marshal.StringToHGlobalUni(value);
lusSecretData.Length = (UInt16)(value.Length * UnicodeEncoding.CharSize);
lusSecretData.MaximumLength = (UInt16)((value.Length + 1) * UnicodeEncoding.CharSize);
}
else
{
//Delete data and key
lusSecretData.Buffer = IntPtr.Zero;
lusSecretData.Length = 0;
lusSecretData.MaximumLength = 0;
}
IntPtr LsaPolicyHandle = GetLsaPolicy(LSA_AccessPolicy.POLICY_CREATE_SECRET);
uint result = LsaStorePrivateData(LsaPolicyHandle, ref secretName, ref lusSecretData);
ReleaseLsaPolicy(LsaPolicyHandle);
uint winErrorCode = LsaNtStatusToWinError(result);
if (winErrorCode != 0)
{
throw new Exception("StorePrivateData failed: " + winErrorCode);
}
}
}
}
"@
#endregion
try {
$ErrorActionPreference = "Stop"
$decryptedPass = [Runtime.InteropServices.Marshal]::PtrToStringAuto(
[Runtime.InteropServices.Marshal]::SecureStringToBSTR($PasswordSecureString))
$ErrorActionPreference = "Continue"
If ($Backup) {
try {
$WinlogonPathProps = Get-ItemProperty -Path $WinlogonPath
write-verbose "Backing up settings from: $($WinlogonPath)" -verbose
write-verbose "- AutoAdminLogon: $($WinlogonPathProps.AutoAdminLogon)" -verbose
If ($WinlogonPathProps -match "^ForceAutoLogon$") {
write-verbose "- ForceAutoLogon: $($WinlogonPathProps.ForceAutoLogon)" -verbose
}
write-verbose "- DefaultUserName: $($WinlogonPathProps.DefaultUserName)" -verbose
write-verbose "- DefaultDomainName: $($WinlogonPathProps.DefaultDomainName)" -verbose
If ($WinlogonPathProps -match "^DefaultPassword$") {
write-verbose "- DefaultPassword: $($WinlogonPathProps.DefaultPassword)" -verbose
}
If ($WinlogonPathProps -match "^AutoLogonCount$") {
write-verbose "- AutoLogonCount: $($WinlogonPathProps.AutoLogonCount)" -verbose
}
# The winlogon logon banner settings.
write-verbose "- LegalNoticeCaption: $($WinlogonPathProps.LegalNoticeCaption)" -verbose
write-verbose "- LegalNoticeText: $($WinlogonPathProps.LegalNoticeText)" -verbose
# The system policy logon banner settings.
$WinlogonBannerPolicyPathProps = Get-ItemProperty -Path $WinlogonBannerPolicyPath
write-verbose "Backing up settings from: $($WinlogonBannerPolicyPath)" -verbose
write-verbose "- legalnoticecaption: $($WinlogonBannerPolicyPathProps.legalnoticecaption)" -verbose
write-verbose "- legalnoticetext: $($WinlogonBannerPolicyPathProps.legalnoticetext)" -verbose
}
catch {
#$_.Exception.Message
}
}
# Store the password securely.
$ErrorActionPreference = "Stop"
write-verbose "Storing the password securely as an LSA Secret." -verbose
$lsaUtil = New-Object ComputerSystem.LSAutil -ArgumentList "DefaultPassword"
$lsaUtil.SetSecret($decryptedPass)
$ErrorActionPreference = "Continue"
# Store the autologon registry settings.
write-verbose "Setting the AutoAdminLogon registry value." -verbose
Set-ItemProperty -Path $WinlogonPath -Name AutoAdminLogon -Value $Enable -Type STRING -Force
write-verbose "Setting the DefaultUserName registry value." -verbose
Set-ItemProperty -Path $WinlogonPath -Name DefaultUserName -Value $DefaultUserName -Type STRING -Force
write-verbose "Setting the DefaultDomainName registry value." -verbose
Set-ItemProperty -Path $WinlogonPath -Name DefaultDomainName -Value $DefaultDomainName -Type STRING -Force
if ($AutoLogonCount) {
write-verbose "Setting the AutoLogonCount registry value." -verbose
Set-ItemProperty -Path $WinlogonPath -Name AutoLogonCount -Value $AutoLogonCount -Type DWORD -Force
} else {
write-verbose "Removing the AutoLogonCount registry value." -verbose
Remove-ItemProperty -Path $WinlogonPath -Name AutoLogonCount -ErrorAction SilentlyContinue
}
# Remove an existing DefaultPassword value which will break the secure-autologon
write-verbose "Removing the DefaultPassword registry value." -verbose
Remove-ItemProperty -Path $WinlogonPath -Name DefaultPassword -ErrorAction SilentlyContinue
if ($RemoveDynamicSiteName) {
# Remove the DynamicSiteName registry value
write-verbose "Removing the DynamicSiteName registry value." -verbose
If (Test-RegistryValue -Path "$NetlogonParametersPath" -Value "DynamicSiteName") {
Remove-ItemProperty -Path $NetlogonParametersPath -Name DynamicSiteName -Force
}
}
if ($WaitForNetwork) {
# Always wait for the network at computer startup and logon. The autologon could fail if
# the network isn't ready.
write-verbose "Setting the Always wait for the network at computer startup and logon (SyncForegroundPolicy) registry value." -verbose
If (!(Test-RegistryPath -Path "$WinlogonPolicyPath")) {
New-Item -Path "$WinlogonPolicyPath" -Force | Out-Null
}
Set-ItemProperty -Path $WinlogonPolicyPath -Name SyncForegroundPolicy -Value 1 -Type DWORD -Force
}
if ($RemoveLegalPrompt) {
write-verbose "Removing the LegalNoticeCaption and LegalNoticeText registry values" -verbose
Set-ItemProperty -Path $WinlogonPath -Name LegalNoticeCaption -Value $null -Force
Set-ItemProperty -Path $WinlogonPath -Name LegalNoticeText -Value $null -Force
If (Test-RegistryPath -Path "$WinlogonPolicyPath") {
Set-ItemProperty -Path $WinlogonBannerPolicyPath -Name legalnoticecaption -Value $null -Force
Set-ItemProperty -Path $WinlogonBannerPolicyPath -Name legalnoticetext -Value $null -Force
}
}
write-verbose "Successfully set autologon" -verbose
} catch {
#$_.Exception.Message
$ExitCode = 1
}
# Copy the auto logoff script into place
$Scripts = "$env:SystemDrive\Scripts"
If (-not(Test-Path -Path "$Scripts")) {
New-Item -Path "$Scripts" -ItemType Directory | Out-Null
}
# Push the current location onto a location stack and then change the current location to the location specified
Push-Location "$ScriptPath"
If (Test-Path -path "$ScriptPath\AutoLogonLogoffTimer.ps1") {
write-verbose "Copying the AutoLogonLogoffTimer.ps1 script into place." -verbose
copy-item -path ".\AutoLogonLogoffTimer.ps1" -Destination "$Scripts\" -Recurse -Force -Verbose
} Else {
write-warning "The AutoLogonLogoffTimer.ps1 script is missing!" -verbose
}
# Change the current location back to the location most recently pushed onto the stack
Pop-Location
# Create the Scheduled Task
write-verbose "Creating the Scheduled Task." -verbose
# The name of the scheduled task
$taskName = "Session Host Autologon Logoff Task"
# The task description
$taskDescription = "This task is created to logoff the autologon session after a Session Host has been restarted"
# We can delay the logoff task by x seconds if needed to give the autologon process a chance to complete
$AddDelayTrigger = $True
$DelayedStartInSeconds = 30
# The Task Action command
#$TaskCommand = "${env:SystemRoot}\system32\WindowsPowerShell\v1.0\powershell.exe"
$TaskCommand = @(Get-Command powershell.exe)[0].Definition
# The script to be executed
$TaskScript = "$Scripts\AutoLogonLogoffTimer.ps1"
# The Task Action command argument
#$TaskArguments = '-Executionpolicy bypass -WindowStyle Minimized -Command "& ' + " '" + $TaskScript + "'"
$TaskArguments = '-Executionpolicy bypass -WindowStyle Minimized -Command "& ' + " '" + $TaskScript + "'" + '"'
$taskSecurityPrincipal = $SecurityPrincipal
# Create the TaskService object.
Try {
[Object] $service = new-object -com("Schedule.Service")
If (!($service.Connected)){
Try {
$service.Connect()
# Get a folder to create a task definition in
# This is actually the %SystemRoot%\System32\Tasks folder.
$rootFolder = $service.GetFolder("\")
# Delete the task if already present
$ScheduledTasks = $rootFolder.GetTasks(0)
$Task = $ScheduledTasks | Where-Object{$_.Name -eq "$TaskName"}
If ($Task -ne $Null){
Try {
$rootFolder.DeleteTask($Task.Name,0)
# 'Success'
}
Catch [System.Exception]{
# 'Exception Returned'
}
} Else {
# "Task Not Found"
}
# Create the new task
$taskDefinition = $service.NewTask(0)
$taskPrincipal = $taskDefinition.Principal
# InteractiveToken
$taskPrincipal.LogonType = 3
# Must be a valid user account
$taskPrincipal.UserID = $taskSecurityPrincipal
$taskPrincipal.RunLevel = 0
# Create a registration trigger with a trigger type of (9) LogonTrigger
$triggers = $taskDefinition.Triggers
$trigger = $triggers.Create(9)
$trigger.ExecutionTimeLimit = "PT30M"
If ($AddDelayTrigger) {
# The delay time in seconds before the task runs once it's been triggered
$trigger.Delay = "PT${DelayedStartInSeconds}S"
}
# Begin the task only when the autologon user logs on
$trigger.UserID = $taskSecurityPrincipal
$trigger.Enabled = $true
# Create the action for the task to execute.
$Action = $taskDefinition.Actions.Create(0)
$Action.Path = $TaskCommand
$Action.Arguments = $TaskArguments
$Action.WorkingDirectory = ""
# Register (create) the task.
$Settings = $taskDefinition.Settings
# Set the Task Compatibility to V2 (Windows 7/2008R2)
$Settings.Compatibility = 3
# The default task priority 7 (below normal), so we set this back to normal
$Settings.Priority = 6
$Settings.AllowDemandStart = $true
$Settings.StopIfGoingOnBatteries = $false
$Settings.DisallowStartIfOnBatteries = $false
$regInfo = $taskDefinition.RegistrationInfo
$regInfo.Description = $taskDescription
$regInfo.Author = $Env:Username
# Note that the task is created as an XML file under the %SystemRoot%\System32\Tasks folder
# 6 == Task Create or Update
# 3 == LogonTypeInteractive
$rootFolder.RegisterTaskDefinition($taskName, $taskDefinition, 6, '', '', 3) | Out-Null
write-verbose "- Scheduled Task Created Successfully" -verbose
}
Catch [System.Exception]{
write-warning "- Scheduled Task Creation Failed" -verbose
$ExitCode = 1
}
}
}
Catch [System.Exception]{
write-warning "- Scheduled Task Creation Failed" -verbose
$ExitCode = 1
}
} Else {
write-warning "Missing credentials." -verbose
$ExitCode = 1
}
try {
# The Microsoft.BDD.TaskSequencePSHost.exe (TSHOST) does not support
# transcription, so we wrap this in a try catch to prevent errors.
Stop-Transcript
}
catch {
Write-Verbose "This host does not support transcription"
}
Exit $ExitCode
Here is the full code view of the AutoLogonLogoffTimer.ps1 (825 downloads ) script
<#
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)
Enjoy!
