Cached credentials are a critical component of the Windows login process in enterprise environments. When a machine cannot reach a domain controller—due to network issues, VPN disconnection, or remote work conditions—it relies on previously cached logins to authenticate users. Managing these credentials can significantly impact security posture and user experience, especially for IT departments and Managed Service Providers (MSPs) tasked with maintaining hybrid or remote environments.
This post explores a PowerShell script designed to enable or disable cached credentials. We’ll examine its inner workings, real-world use cases, and how it compares to other solutions. This script offers IT professionals a direct, reliable method to enforce credential caching policies across fleets of devices—something that becomes increasingly essential in today’s dynamic work environments.
Background
The ability to control cached credentials is often overlooked, yet it’s a powerful lever in managing remote access and preventing unauthorized login attempts. By default, Windows caches the last ten user logins. While this feature improves accessibility when offline, it also poses a security risk—especially if the endpoint falls into the wrong hands.
For MSPs and corporate IT teams, having a way to programmatically manage this setting ensures compliance with organizational policies, aids in onboarding/offboarding workflows, and tightens security for sensitive environments. This script is particularly valuable because it supports automated deployment, integrates with RMM tools like NinjaOne, and doesn’t rely on group policy changes, which can be slower to propagate.
The Script
#Requires -Version 5.1 <# .SYNOPSIS Enable or Disable Cached Credentials. This controls if the machine is allowed to login with cached credentials when it cannot contact a domain controller. .DESCRIPTION Enable or Disable Cached Credentials. This controls if the machine is allowed to login with cached credentials when it cannot contact a domain controller. By using this script, you indicate your acceptance of the following legal terms as well as our Terms of Use at https://www.ninjaone.com/terms-of-use. Ownership Rights: NinjaOne owns and will continue to own all right, title, and interest in and to the script (including the copyright). NinjaOne is giving you a limited license to use the script in accordance with these legal terms. Use Limitation: You may only use the script for your legitimate personal or internal business purposes, and you may not share the script with another party. Republication Prohibition: Under no circumstances are you permitted to re-publish the script in any script library or website belonging to or under the control of any other software provider. Warranty Disclaimer: The script is provided “as is” and “as available”, without warranty of any kind. NinjaOne makes no promise or guarantee that the script will be free from defects or that it will meet your specific needs or expectations. Assumption of Risk: Your use of the script is at your own risk. You acknowledge that there are certain inherent risks in using the script, and you understand and assume each of those risks. Waiver and Release: You will not hold NinjaOne responsible for any adverse or unintended consequences resulting from your use of the script, and you waive any legal or equitable rights or remedies you may have against NinjaOne relating to your use of the script. EULA: If you are a NinjaOne customer, your use of the script is subject to the End User License Agreement applicable to you (EULA). .PARAMETER Enable Enable Cached Credentials. .PARAMETER Disable Disable Cached Credentials. .PARAMETER Count Number of previous logins to cache. Default is 10. Minimum is 1. Maximum is 50. .EXAMPLE -Enable [Info] Enable Cached Credentials .EXAMPLE -Disable [Info] Disable Cached Credentials .NOTES Minimum OS Architecture Supported: Windows 10, Windows Server 2016 Release Notes: Initial Release #> param ( [switch]$Enable, [switch]$Disable, [int]$Count = 10 ) begin { function Test-IsDomainController { # Determine the method to retrieve the operating system information based on PowerShell version try { $OS = if ($PSVersionTable.PSVersion.Major -lt 3) { Get-WmiObject -Class Win32_OperatingSystem -ErrorAction Stop } else { Get-CimInstance -ClassName Win32_OperatingSystem -ErrorAction Stop } } catch { Write-Host -Object "[Error] Unable to validate whether or not this device is a domain controller." Write-Host -Object "[Error] $($_.Exception.Message)" exit 1 } # Check if the ProductType is "2", which indicates that the system is a domain controller if ($OS.ProductType -eq "2") { return $true } } function Test-IsDomainJoined { # Check the PowerShell version to determine the appropriate cmdlet to use try { if ($PSVersionTable.PSVersion.Major -lt 3) { return $(Get-WmiObject -Class Win32_ComputerSystem).PartOfDomain } else { return $(Get-CimInstance -Class Win32_ComputerSystem).PartOfDomain } } catch { Write-Host -Object "[Error] Unable to validate whether or not this device is a part of a domain." Write-Host -Object "[Error] $($_.Exception.Message)" exit 1 } } function Test-IsElevated { # Get the current Windows identity of the user running the script $id = [System.Security.Principal.WindowsIdentity]::GetCurrent() # Create a WindowsPrincipal object based on the current identity $p = New-Object System.Security.Principal.WindowsPrincipal($id) # Check if the current user is in the Administrator role # The function returns $True if the user has administrative privileges, $False otherwise $p.IsInRole([System.Security.Principal.WindowsBuiltInRole]::Administrator) } function Set-RegKey { param ( $Path, $Name, $Value, [ValidateSet("DWord", "QWord", "String", "ExpandedString", "Binary", "MultiString", "Unknown")] $PropertyType = "DWord" ) # Check if the specified registry path exists if (!(Test-Path -Path $Path)) { try { # If the path does not exist, create it New-Item -Path $Path -Force -ErrorAction Stop | Out-Null } catch { # If there is an error creating the path, output an error message and exit Write-Host "[Error] Unable to create the registry path $Path for $Name. Please see the error below!" Write-Host "[Error] $($_.Exception.Message)" exit 1 } } # Check if the registry key already exists at the specified path if (Get-ItemProperty -Path $Path -Name $Name -ErrorAction SilentlyContinue) { # Retrieve the current value of the registry key $CurrentValue = (Get-ItemProperty -Path $Path -Name $Name -ErrorAction SilentlyContinue).$Name if ($CurrentValue -eq $Value) { Write-Host "$Path\$Name is already the value '$Value'." } else { try { # Update the registry key with the new value Set-ItemProperty -Path $Path -Name $Name -Value $Value -Force -Confirm:$false -ErrorAction Stop | Out-Null } catch { # If there is an error setting the key, output an error message and exit Write-Host "[Error] Unable to set registry key for $Name at $Path. Please see the error below!" Write-Host "[Error] $($_.Exception.Message)" exit 1 } # Output the change made to the registry key Write-Host "$Path\$Name changed from $CurrentValue to $((Get-ItemProperty -Path $Path -Name $Name -ErrorAction SilentlyContinue).$Name)" } } else { try { # If the registry key does not exist, create it with the specified value and property type New-ItemProperty -Path $Path -Name $Name -Value $Value -PropertyType $PropertyType -Force -Confirm:$false -ErrorAction Stop | Out-Null } catch { # If there is an error creating the key, output an error message and exit Write-Host "[Error] Unable to set registry key for $Name at $Path. Please see the error below!" Write-Host "[Error] $($_.Exception.Message)" exit 1 } # Output the creation of the new registry key Write-Host "Set $Path\$Name to $((Get-ItemProperty -Path $Path -Name $Name -ErrorAction SilentlyContinue).$Name)" } } if (Test-IsDomainController) { Write-Host "[Error] This device is a domain controller. Cached credentials cannot be set." exit 1 } elseif (!(Test-IsElevated)) { Write-Host "[Error] This script must be run with elevated privileges to disable cached credentials." exit 1 } if (!(Test-IsDomainJoined)) { Write-Host "[Error] This device is not part of a domain. Cached credentials are not applicable." exit 1 } if ($env:numberOfPreviousLoginsToCache) { try { $Count = [int]::Parse($env:numberOfPreviousLoginsToCache) } catch { Write-Host "[Error] Invalid value. Please provide a number ranging from 1 to 50." exit 1 } } if ($env:action -and $env:action -eq "Enable") { $Enable = $true } elseif ($env:action -and $env:action -eq "Disable") { $Disable = $true $env:numberOfPreviousLoginsToCache = 0 } elseif ($Enable -and $Disable) { Write-Host "[Error] Please specify either Enable or Disable." exit 1 } else { Write-Host "[Error] Please specify either Enable or Disable." exit 1 } if ($Disable) { $Count = 0 } elseif ($Enable -and $($Count -le 0 -or $Count -gt 50)) { Write-Host "[Error] Invalid value. Please provide a number ranging from 1 to 50." exit 1 } } process { try { Set-RegKey -Path "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon" -Name "CachedLogonsCount" -Value $Count switch ($Count) { 0 { Write-Host "[Info] Cached credentials disabled." } default { Write-Host "[Info] Cached credentials enabled with $Count previous logins cached." } } } catch { switch ($Count) { 0 { Write-Host "[Error] Unable to disable cached credentials." } default { Write-Host "[Error] Unable to enable cached credentials." } } exit 1 } } end { }
Detailed Breakdown
This script operates in three phases: Validation, Processing, and Registry Modification.
1. Parameter Input
The script accepts three parameters:
- -Enable: Activates caching of credentials
- -Disable: Deactivates it
- -Count: Specifies how many previous logins should be cached (defaults to 10)
Environment variables ($env:action, $env:numberOfPreviousLoginsToCache) can override these for integration into deployment pipelines.
2. Validation Checks
Before any changes are made, the script verifies the following:
- Domain Controller Check: It ensures the script is not being run on a Domain Controller (ProductType -eq “2”), where such changes are not appropriate.
- Elevation Check: Verifies the script is executed with administrative privileges.
- Domain Membership: Ensures the machine is domain-joined.
If any of these checks fail, the script exits gracefully with descriptive error messages.
3. Registry Key Modification
The script modifies the CachedLogonsCount registry key
It either sets the value to 0 to disable caching or a value from 1 to 50 to enable it. The Set-RegKey function handles both creation and updating of this key safely and cleanly.
Potential Use Cases
Hypothetical Case Study
Scenario: A company transitions to a zero-trust security model. The IT team wants to disable cached credentials on executive laptops due to their access to sensitive data.
Solution: The MSP deploys this PowerShell script across targeted devices using NinjaOne. By passing the -Disable flag, all selected machines are updated in seconds, minimizing the risk of offline access in case a device is lost or stolen.
FAQs
Q1: What happens if I set the count above 50 or below 1?
The script will block the operation and return an error. Windows only supports 1–50 cached logins.
Q2: Can I use this script in non-domain environments?
No. The script will terminate if it detects that the machine is not domain-joined.
Q3: Does this change take effect immediately?
Yes, the registry key is updated in real-time. No reboot is required, though logoff/login may be necessary to experience the change.
Q4: Is this compatible with older versions of PowerShell?
Yes. The script supports PowerShell 5.1 and includes version-aware logic for legacy environments.
Implications
While enabling cached credentials improves user convenience, it also introduces potential attack vectors. An attacker with physical access to a machine could attempt credential extraction. Disabling this feature can harden security but may frustrate users who need to log in without constant domain controller access. This trade-off underscores the need for a tailored policy—one that balances usability with risk tolerance.
Recommendations
- Use Centralized Deployment: Run the script via RMM platforms like NinjaOne for consistency.
- Test in Staging First: Before wide deployment, validate the script behavior in a test group.
- Document Changes: Keep logs of systems modified, especially if auditing is required.
- Combine with BitLocker: When disabling cached credentials, encrypt drives to further protect data.
- Monitor User Feedback: Gauge the impact of disabling caching to ensure it doesn’t hinder productivity.
Final Thoughts
Controlling cached credentials might seem like a small tweak, but it plays a big role in endpoint security. Whether you need to enable cached credentials with a PowerShell script or disable cached credentials with a PowerShell script, this tool offers flexibility, precision, and compliance assurance.
NinjaOne enhances this capability by offering a centralized platform to push scripts, monitor execution results, and automate repetitive tasks. Combining this script with NinjaOne’s policy engine empowers MSPs and IT teams to enforce configuration standards across their entire device fleet with confidence and ease.