How to Check If Credential Guard Is Active Using PowerShell for Enterprise Systems

As enterprise security strategies continue evolving, protecting sensitive credentials on endpoints has never been more important. Credential Guard, a feature of Microsoft Windows, plays a pivotal role in defending against credential theft by isolating secrets using virtualization-based security. Yet, determining if Credential Guard is not only configured but actively running can be non-trivial—especially at scale. For IT professionals and Managed Service Providers (MSPs), automation is key. A well-designed PowerShell script can bridge this gap effectively, ensuring visibility and compliance across a fleet of devices.

Background

Credential Guard was introduced with Windows 10 Enterprise and has since become a standard component in securing domain-joined devices. It leverages hardware virtualization to protect derived domain credentials from theft, essentially making credential harvesting techniques like Pass-the-Hash attacks significantly harder to execute.

But knowing that Credential Guard should be enabled isn’t the same as verifying that it is. In enterprise environments where device policies, BIOS configurations, and OS versions vary, automated validation becomes essential. This is where the PowerShell script in question becomes a crucial tool. Developed with compatibility, clarity, and NinjaOne RMM integration in mind, it allows IT admins to not only check Credential Guard status but also report results back to a central system for further action.

The Script

#Requires -Version 5.1

<#
.SYNOPSIS
    Reports on whether Credential Guard is configured and running on a given device.
.DESCRIPTION
    Reports on whether Credential Guard is configured and running on a given device.
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).

.EXAMPLE
    (No Parameters)

    CredentialGuardConfiguration CredentialGuardRunning
    ---------------------------- ----------------------
    Enabled without UEFI lock    Running

.EXAMPLE
    -TextCustomFieldName "Test"

    [Info] Attempting to set Ninja custom field Text...
    [Info] Successfully set Ninja custom field Text to value 'Enabled without UEFI lock | Running'.

    CredentialGuardConfiguration CredentialGuardRunning
    ---------------------------- ----------------------
    Enabled without UEFI lock    Running

.PARAMETER -TextCustomFieldName
    Name of the text custom field where Credential Guard information will be stored.

.NOTES
    Minimum OS Architecture Supported: Windows 10, Windows 11, Windows Server 2016+
    Release Notes: Initial Release
#>

[CmdletBinding()]
param (
    [Parameter()]
    [string]$TextCustomFieldName
)

begin {
    if ($env:TextCustomFieldName -and $TextCustomFieldName -ne 'null'){
        $TextCustomFieldName = $env:TextCustomFieldName
    }

    function Test-IsElevated {
        $id = [System.Security.Principal.WindowsIdentity]::GetCurrent()
        $p = New-Object System.Security.Principal.WindowsPrincipal($id)
        $p.IsInRole([System.Security.Principal.WindowsBuiltInRole]::Administrator)
    }

    function Set-NinjaProperty {
        [CmdletBinding()]
        Param(
            [Parameter(Mandatory = $True)]
            [String]$Name,
            [Parameter()]
            [String]$Type,
            [Parameter(Mandatory = $True, ValueFromPipeline = $True)]
            $Value,
            [Parameter()]
            [String]$DocumentName,
            [Parameter()]
            [Switch]$Piped
        )
        # Remove the non-breaking space character
        if ($Type -eq "WYSIWYG") {
            $Value = $Value -replace ' ', '&nbsp;'
        }
        
        # Measure the number of characters in the provided value
        $Characters = $Value | ConvertTo-Json | Measure-Object -Character | Select-Object -ExpandProperty Characters
    
        # Throw an error if the value exceeds the character limit of 200,000 characters
        if ($Piped -and $Characters -ge 200000) {
            throw [System.ArgumentOutOfRangeException]::New("Character limit exceeded: the value is greater than or equal to 200,000 characters.")
        }
    
        if (!$Piped -and $Characters -ge 45000) {
            throw [System.ArgumentOutOfRangeException]::New("Character limit exceeded: the value is greater than or equal to 45,000 characters.")
        }
        
        # Initialize a hashtable for additional documentation parameters
        $DocumentationParams = @{}
    
        # If a document name is provided, add it to the documentation parameters
        if ($DocumentName) { $DocumentationParams["DocumentName"] = $DocumentName }
        
        # Define a list of valid field types
        $ValidFields = "Attachment", "Checkbox", "Date", "Date or Date Time", "Decimal", "Dropdown", "Email", "Integer", "IP Address", "MultiLine", "MultiSelect", "Phone", "Secure", "Text", "Time", "URL", "WYSIWYG"
    
        # Warn the user if the provided type is not valid
        if ($Type -and $ValidFields -notcontains $Type) { Write-Warning "$Type is an invalid type. Please check here for valid types: https://ninjarmm.zendesk.com/hc/en-us/articles/16973443979789-Command-Line-Interface-CLI-Supported-Fields-and-Functionality" }
        
        # Define types that require options to be retrieved
        $NeedsOptions = "Dropdown"
    
        # If the property is being set in a document or field and the type needs options, retrieve them
        if ($DocumentName) {
            if ($NeedsOptions -contains $Type) {
                $NinjaPropertyOptions = Ninja-Property-Docs-Options -AttributeName $Name @DocumentationParams 2>&1
            }
        }
        else {
            if ($NeedsOptions -contains $Type) {
                $NinjaPropertyOptions = Ninja-Property-Options -Name $Name 2>&1
            }
        }
        
        # Throw an error if there was an issue retrieving the property options
        if ($NinjaPropertyOptions.Exception) { throw $NinjaPropertyOptions }
            
        # Process the property value based on its type
        switch ($Type) {
            "Checkbox" {
                # Convert the value to a boolean for Checkbox type
                $NinjaValue = [System.Convert]::ToBoolean($Value)
            }
            "Date or Date Time" {
                # Convert the value to a Unix timestamp for Date or Date Time type
                $Date = (Get-Date $Value).ToUniversalTime()
                $TimeSpan = New-TimeSpan (Get-Date "1970-01-01 00:00:00") $Date
                $NinjaValue = $TimeSpan.TotalSeconds
            }
            "Dropdown" {
                # Convert the dropdown value to its corresponding GUID
                $Options = $NinjaPropertyOptions -replace '=', ',' | ConvertFrom-Csv -Header "GUID", "Name"
                $Selection = $Options | Where-Object { $_.Name -eq $Value } | Select-Object -ExpandProperty GUID
            
                # Throw an error if the value is not present in the dropdown options
                if (!($Selection)) {
                    throw [System.ArgumentOutOfRangeException]::New("Value is not present in dropdown options.")
                }
            
                $NinjaValue = $Selection
            }
            default {
                # For other types, use the value as is
                $NinjaValue = $Value
            }
        }
            
        # Set the property value in the document if a document name is provided
        if ($DocumentName) {
            $CustomField = Ninja-Property-Docs-Set -AttributeName $Name -AttributeValue $NinjaValue @DocumentationParams 2>&1
        }
        else {
            try {
                # Otherwise, set the standard property value
                if ($Piped) {
                    $CustomField = $NinjaValue | Ninja-Property-Set-Piped -Name $Name 2>&1
                }
                else {
                    $CustomField = Ninja-Property-Set -Name $Name -Value $NinjaValue 2>&1
                }
            }
            catch {
                Write-Host -Object "[Error] Failed to set custom field."
                throw $_.Exception.Message
            }
        }
            
        # Throw an error if setting the property failed
        if ($CustomField.Exception) {
            throw $CustomField
        }
    }

    function Test-IsCredentialGuardRunning {
        if ($PSVersionTable.PSVersion.Major -lt 3) {
            $CGRunning = (Get-WmiObject -Class Win32_DeviceGuard -Namespace root\Microsoft\Windows\DeviceGuard -ErrorAction SilentlyContinue).SecurityServicesRunning
        }
        else {
            $CGRunning = (Get-CimInstance -ClassName Win32_DeviceGuard -Namespace root\Microsoft\Windows\DeviceGuard -ErrorAction SilentlyContinue).SecurityServicesRunning
        }

        # if 1 is present, Credential Guard is running per https://learn.microsoft.com/en-us/windows/security/hardware-security/enable-virtualization-based-protection-of-code-integrity?tabs=security
        if ($CGRunning -contains 1){
            return $true
        }
        else{
            return $false
        }
    }
}
process {
    if (-not (Test-IsElevated)) {
        Write-Host -Object "[Error] Access Denied. Please run with Administrator privileges."
        exit 1
    }
    
    $ExitCode = 0

    # check if running on supported OS
    $OS = try{
        if ($PSVersionTable.PSVersion.Major -lt 3) {
            Get-WmiObject -Class Win32_OperatingSystem -ErrorAction Stop
        }
        else {
            Get-CimInstance -ClassName Win32_OperatingSystem -ErrorAction Stop
        }   
    }
    catch{
        Write-Host "[Error] Error retrieving operating system information."
        Write-Host "$($_.Exception.Message)"
        exit 1
    }

    # assume supported OS, below checks will be used to negate it if needed
    $supportedOS = $true

    if ($OS.Caption -match "Windows (10|11)" -and $OS.Caption -notmatch "Enterprise|Education"){
        # if this registry value is not null on Windows 10/11 Pro, then this may have been a downgrade from Enterprise/Education, and the OS is supported in that case
        # see the note here: https://learn.microsoft.com/en-us/windows/security/identity-protection/credential-guard/
        $regKeyValue = (Get-ItemProperty "Registry::HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Lsa\MSV1_0\" -ErrorAction SilentlyContinue).IsolatedCredentialsRootSecret

        if ([string]::IsNullOrWhiteSpace($regKeyValue)){
            $supportedOS = $false
        }
    }
    elseif ($OS.Caption -notmatch "Windows.+(Enterprise|Education|Server (2016|2019|[2-9]0[2-9][0-9]))"){
        # otherwise, if device is not Enterprise/Education/Server 2016+, the OS is not supported
        $supportedOS = $false
    }

    # error if not running on supported OS
    if (-not $supportedOS){
        Write-Host "[Error] Credential Guard is not supported on this OS."
        Write-Host "Script supports:"
        Write-Host " - Windows 10 and 11, Enterprise or Education edition"
        Write-Host " - Windows Server 2016 and above"
        Write-Host "See more info on prerequisites here: https://learn.microsoft.com/en-us/windows/security/identity-protection/credential-guard/"

        # write to custom field if specified
        if ($TextCustomFieldName){
            $value = "Incompatible with System"
            # attempt custom field write
            try {
                Write-Host "`n[Info] Attempting to set Ninja custom field $TextCustomFieldName..."
                Set-NinjaProperty -Name $TextCustomFieldName -Type "Text" -Value $value -ErrorAction Stop
                Write-Host "[Info] Successfully set Ninja custom field $TextCustomFieldName to value '$value'."
            }
            catch {
                Write-Host "[Error] Error setting custom field $TextFieldCustomName to value '$value'."
                Write-Host "$($_.Exception.Message)"
                $ExitCode = 1
            }
        }
        exit $ExitCode
    }

    # if OS is supported, continue with checks
    # check if Credential Guard is running
    try {
        if (Test-IsCredentialGuardRunning){
            $CGRunningStatus = "Running"
        }
        else{
            $CGRunningStatus = "Not running"
        }
    }
    catch {
        Write-Host "[Error] Error getting Credential Guard running status."
        Write-Host "$($_.Exception.Message)"
        $CGRunningStatus = "Error"
        $ExitCode = 1
    }

    # check if Credential Guard is configured
    try {
        $CGConfiguration = (Get-ItemProperty "Registry::HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Lsa" -ErrorAction Stop).LsaCfgFlags

        # if nothing present for custom regkey, check default regkey
        if ($null -eq $CGConfiguration){
            $CGConfiguration = (Get-ItemProperty "Registry::HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Lsa" -ErrorAction Stop).LsaCfgFlagsDefault
        }
    }
    catch{
        Write-Host "[Error] Error when testing if Credential Guard is enabled in the registry."
        Write-Host "$($_.Exception.Message)"
        $ExitCode = 1
    }
    
    # translate value into readable text for output
    $CGConfigurationStatus = switch ($CGConfiguration){
        0 { "Disabled" }
        1 { "Enabled with UEFI lock" }
        2 { "Enabled without UEFI lock" }
        default { "Unable to Determine" }
    }

    # write result to custom field if specified
    if ($TextCustomFieldName){
        $value = "$CGConfigurationStatus | $CGRunningStatus"
        # attempt custom field write
        try {
            Write-Host "`n[Info] Attempting to set Ninja custom field $TextCustomFieldName..."
            Set-NinjaProperty -Name $TextCustomFieldName -Type "Text" -Value $value -ErrorAction Stop
            Write-Host "[Info] Successfully set Ninja custom field $TextCustomFieldName to value '$value'."
        }
        catch {
            Write-Host "[Error] Error setting custom field $TextFieldCustomName to value '$value'."
            Write-Host "$($_.Exception.Message)"
            $ExitCode = 1
        }
    }

    # warn if CG is configured to be disabled but is still running
    if ($CGConfigurationStatus -eq "Disabled" -and $CGRunningStatus -eq "Running"){
        Write-Host "`n[Warning] Credential Guard is disabled in the registry but currently running."
        Write-Host "You may need to restart $env:computername, or Credential Guard is UEFI locked and needs to be reset."
        Write-Host "See more information here: https://learn.microsoft.com/en-us/windows/security/identity-protection/credential-guard/configure?tabs=intune#disable-credential-guard-with-uefi-lock"
    }

    [PSCustomObject]@{
        "CredentialGuardConfiguration" = $CGConfigurationStatus
        "CredentialGuardRunning" = $CGRunningStatus
    } | Format-Table -AutoSize | Out-String | Write-Host

    exit $ExitCode
}
end {
    
    
    
}

 

Detailed Breakdown

The script is divided into logical segments that ensure functionality, error-handling, and compatibility with multiple Windows versions. Here’s a visual and functional walkthrough:

  1. Parameter Input and Privilege Check

  • The script accepts an optional parameter -TextCustomFieldName, enabling output to be stored in a NinjaOne custom field.
  • It immediately verifies if it is running with administrative privileges using the Test-IsElevated function. Without these rights, it exits with an error.
  1. OS Validation

  • The script checks whether the host OS is supported (Windows 10/11 Enterprise or Education, and Windows Server 2016+).
  • It includes a specific check for Pro machines that might have been downgraded from Enterprise editions.
  1. Credential Guard Running Check

  • Using either Get-WmiObject or Get-CimInstance, it queries Win32_DeviceGuard to determine if Credential Guard is actively running (SecurityServicesRunning includes a value of 1).
  1. Credential Guard Configuration Check

  • The script reads registry values (LsaCfgFlags and fallback LsaCfgFlagsDefault) to determine how Credential Guard is configured:
    • 0: Disabled
    • 1: Enabled with UEFI lock
    • 2: Enabled without UEFI lock
  1. Results Output and Integration

  • The configuration and runtime status are compiled into a readable string and optionally written to a custom NinjaOne field using the Set-NinjaProperty helper function.
  1. Warnings and Logging

  • If Credential Guard is running but registry values suggest it’s disabled, a warning is raised, guiding admins to reboot the machine or reconfigure UEFI.

Potential Use Cases

Case Study:

A mid-sized MSP manages over 300 endpoints for a legal services client. They need to confirm all domain-joined laptops are running Credential Guard before renewing their cybersecurity insurance policy. Manually checking each system isn’t feasible.

Using this script, the MSP deploys it via NinjaOne to all applicable endpoints. Results are written back into a custom field. Within minutes, the MSP generates a report identifying devices that either lack Credential Guard configuration or where it’s not running, enabling immediate remediation.

Comparisons

Manual Checks:

  • Involve navigating Windows Security Settings or running multiple isolated PowerShell commands.
  • Time-consuming and not scalable.

Group Policy Reports:

  • Show policy settings but don’t confirm whether Credential Guard is actually running.

This Script:

  • Automates the check Credential Guard status process.
  • Confirms both configuration and runtime state.
  • Integrates with NinjaOne, allowing centralized tracking and action.

FAQs

Q: Will this work on Windows 10 Pro?

A: Not officially. The script checks for Enterprise/Education/Server editions. Windows 10 Pro lacks Credential Guard support unless it was downgraded.

Q: What if Credential Guard is running but the registry says it’s disabled?

A: This usually means it was locked via UEFI. A reboot or BIOS-level change may be required.

Q: Can I run this in a non-NinjaOne environment?

A: Yes, but NinjaOne integration enhances its value by centralizing results. Without it, the script still outputs results locally.

Q: Does the script modify system settings?

A: No. It only reads system and registry information.

Implications

Credential Guard is a frontline defense against modern credential-based attacks. Organizations that fail to verify its status across their environment may unknowingly leave themselves exposed. This script provides a tangible way to enforce policy adherence and improve compliance with standards like CIS or NIST.

Furthermore, in regulated industries—healthcare, finance, legal—being able to prove systems are protected becomes as important as the protection itself.

Recommendations

  • Run the script as part of regular audit cycles, ideally monthly.
  • Integrate with NinjaOne or your RMM platform to streamline tracking.
  • Pair this check with TPM and Secure Boot verifications for full security posture insights.
  • Document devices flagged as misconfigured and follow up with remediation steps.

Final Thoughts

Ensuring Credential Guard is both configured and running across all devices is not just best practice—it’s essential. This PowerShell script offers IT professionals a precise, scalable way to monitor compliance, reducing manual work and increasing security.

For those using NinjaOne, the integration is seamless. You can set custom fields, automate reporting, and build workflows that respond to misconfigurations. With threats evolving, tools like this give you an edge in maintaining a secure and resilient endpoint environment.

Next Steps

Building an efficient and effective IT team requires a centralized solution that acts as your core service delivery tool. NinjaOne enables IT teams to monitor, manage, secure, and support all their devices, wherever they are, without the need for complex on-premises infrastructure.

Learn more about NinjaOne Remote Script Deployment, check out a live tour, or start your free trial of the NinjaOne platform.

Categories:

You might also like

×

See NinjaOne in action!

By submitting this form, I accept NinjaOne's privacy policy.

NinjaOne Terms & Conditions

By clicking the “I Accept” button below, you indicate your acceptance of the following legal terms as well as our 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 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).