<# 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