Skip to content

ADMU usrClass Dat, Update if missing required permissions#262

Merged
jworkmanjc merged 1 commit into
v2.13.1from
CUT-5193_usrClassDatPermission
Jun 10, 2026
Merged

ADMU usrClass Dat, Update if missing required permissions#262
jworkmanjc merged 1 commit into
v2.13.1from
CUT-5193_usrClassDatPermission

Conversation

@jworkmanjc

@jworkmanjc jworkmanjc commented Jun 4, 2026

Copy link
Copy Markdown
Contributor

Issues

  • CUT-5193 - Set missing usrClass ntfs permissions

What does this solve?

If some user is missing their usrClass.dat required permissions they'll get a temp profile. It's something we've seen before but not identified a cause in our migration tooling. This change accounts for customers who find themselves in this case by proactively setting the required permissions during migration.

Is there anything particularly tricky?

How should this be tested?

Perform a migration for some user. Before signing into that user's profile:

  • Go to C:\Users\some.user\AppData\Local\Microsoft\Windows
  • Modify the usrclass.dat file's permissions. Most likely you'll have to disable inheritance to do this
  • remove some.user (or the acl for the user you migrated)
  • Switch profiles from administrator to the some.user account and attempt to sign in. They user will have a temp profile (which is what is expected)
  • Sign off and log back into the administrator's account.
  • Open PowerShell as an admin and run:
function Set-DATFilePermission {
    param (
        [Parameter(Mandatory = $true)]
        [System.String]
        $Path,
        [Parameter(Mandatory = $true)]
        [System.String]
        $Username,
        [Parameter(Mandatory = $true)]
        [ValidateSet("registry", "ntfs")]
        [System.String]
        $Type
    )

    begin {
        $aclUser = "$($Env:ComputerName)\$Username"
        $requiredIdentities = @(
            "NT AUTHORITY\SYSTEM",
            "BUILTIN\Administrators",
            "$aclUser"
        )
    }

    process {
        try {
            $acl = Get-Acl -Path $Path
            $isProtected = $acl.AreAccessRulesProtected
            $modified = $false

            foreach ($identity in $requiredIdentities) {
                $existingRules = @($acl.Access | Where-Object { $_.IdentityReference -eq $identity })
                $hasValidAllow = $false

                foreach ($rule in $existingRules) {
                    if ($rule.AccessControlType -eq 'Deny') {
                        $acl.RemoveAccessRule($rule) | Out-Null
                        $modified = $true
                        # Write-ToLog -Message "Set-DATFilePermission: Removed Deny rule for $identity on $Path" -Level Verbose
                        continue
                    }

                    $rightsValid = if ($Type -eq 'registry') {
                        $rule.RegistryRights -contains 'FullControl'
                    } else {
                        $rule.FileSystemRights -contains 'FullControl'
                    }

                    if ($rightsValid) {
                        $hasValidAllow = $true
                    } else {
                        $acl.RemoveAccessRule($rule) | Out-Null
                        $modified = $true
                        # Write-ToLog -Message "Set-DATFilePermission: Removed insufficient Allow rule for $identity on $Path" -Level Verbose
                    }
                }

                if (-not $hasValidAllow) {
                    if ($Type -eq 'registry') {
                        $newRule = New-Object System.Security.AccessControl.RegistryAccessRule(
                            $identity, 'FullControl', 'Allow'
                        )
                    } else {
                        $newRule = New-Object System.Security.AccessControl.FileSystemAccessRule(
                            $identity, 'FullControl', 'Allow'
                        )
                    }
                    $acl.SetAccessRule($newRule)
                    $modified = $true
                    # Write-ToLog -Message "Set-DATFilePermission: Added Allow FullControl for $identity on $Path" -Level Verbose
                }
            }

            if ($modified) {
                $acl.SetAccessRuleProtection($isProtected, $false)
                Set-Acl -Path $Path -AclObject $acl
            }
        } catch {
            # Write-ToLog -Message "Set-DATFilePermission: Failed to update permissions on $Path : $($_.Exception.Message)" -Level Warning
            return $false
        }

        $valid, $null = Test-DATFilePermission -Path $Path -Username $Username -Type $Type
        return $valid
    }
}
# Function to validate if NTUser.dat has SYSTEM, Administrators, and the specified user as full control
function Test-DATFilePermission {
    param (
        [Parameter(Mandatory = $true)]
        [System.String]
        $path,
        [Parameter(Mandatory = $true)]
        [System.String]
        $username,
        [Parameter(Mandatory = $true)]
        [ValidateSet("registry", "ntfs")]
        [System.String]
        $type

    )
    begin {
        $aclUser = "$($Env:ComputerName)\$username"
        # ACL naming differs on registry/ ntfs file system, set the correct type
        switch ($type) {
            'registry' {
                $FilePermissionType = 'RegistryRights'
            }
            'ntfs' {
                $FilePermissionType = 'FileSystemRights'
            }
        }
        # define empty list
        $permissionsHash = @{}
        # define required list to test
        $requiredAccess = @{
            "NT AUTHORITY\SYSTEM"    = @{
                name = "System"
            };
            "BUILTIN\Administrators" = @{
                name = "Administrators"
            };
            "$($aclUser)"            = @{
                name = "$username"
            }
        }
        # Get the path
        $ACL = Get-Acl $path
    }
    process {
        # Using AccessControlType to check if it's a deny rule instead of allow since, with NTFS permissions, even if a user/admin is denied, there will still be an allow rule for them and not null
        foreach ($requiredRule in $requiredAccess.keys) {
            # foreach ($requiredRule in $systemRule, $administratorsRule, $specifiedUserRule) {
            # write-ToLog "Begin testing: $($requiredRule)"
            $FileACLs = $acl.Access | Where-Object { $_.IdentityReference -eq "$($requiredRule)" }
            # write-ToLog "$($requiredRule) access count: $($FileACLs.Count)"
            foreach ($fileACL in $FileACLs) {
                $rulePermissions = [PSCustomObject]@{
                    access            = $FileACL.AccessControlType
                    permissionType    = $FileACL.$($FilePermissionType)
                    identityReference = $FileACL.IdentityReference
                    ValidPermissions  = $true
                }
                # There will sometimes be multiple FileACLs if an identity is denied access, in which case just break
                if ($FileACL.AccessControlType -contains 'Deny') {
                    $rulePermissions.ValidPermissions = $false
                    $permissionsHash.Add("$($requiredAccess["$($requiredRule)"].name)", $rulePermissions) | Out-Null
                    break
                }
                # if fullControl access is not grated, just break
                if ($FileACL.$($FilePermissionType) -notcontains 'FullControl') {
                    $rulePermissions.ValidPermissions = $false
                    $permissionsHash.Add("$($requiredAccess["$($requiredRule)"].name)", $rulePermissions) | Out-Null
                    break
                }
                # else record the access rule and assume it's valid
                if ("$($requiredAccess["$($requiredRule)"].name)" -notin $permissionsHash.Keys) {
                    $permissionsHash.Add("$($requiredAccess["$($requiredRule)"].name)", $rulePermissions) | Out-Null
                }
            }
            # if the access is not explicitly granted, record the missing value so we can make use of it later
            if (-not $FileACLs) {
                $rulePermissions = [PSCustomObject]@{
                    access            = $null
                    permissionType    = $null
                    identityReference = $requiredRule
                    ValidPermissions  = $false
                }
                if ("$($requiredAccess["$($requiredRule)"].name)" -notin $permissionsHash.Keys) {
                    $permissionsHash.Add("$($requiredAccess["$($requiredRule)"].name)", $rulePermissions) | Out-Null
                }
            }
        }

    }
    end {
        # if the validPermission block contains any 'false' entries, return false + values, else return true + values
        if (($permissionsHash.Values.ValidPermissions -contains $false)) {
            return $false, $permissionsHash.Values
        } else {
            return $true, $permissionsHash.Values
        }
    }
}
  • with the function to set permissions imported, run:
  • Set-DATFilePermission -Path "C:\Users\some.user\AppData\Local\Microsoft\Windows\usrclass.dat" -Username "some.user" -Type 'ntfs'
  • switch users back into some.user
  • the profile should login as normal showing the JumpCloud logo and finish migration

Screenshots

Some user might encounter this issue if their NTUSER.DAT or usrClass.dat file looked something like this:

Screenshot 2026-06-04 at 5 20 43 PM

Repairing the permissions with the code above (or what's called when migrating the account)

Screenshot 2026-06-04 at 5 29 13 PM

Note

Cursor Bugbot is generating a summary for commit f81633d. Configure here.

@jworkmanjc jworkmanjc requested a review from a team as a code owner June 4, 2026 23:20
@jworkmanjc jworkmanjc added ADMU ADMU Module Release patch Patch version release labels Jun 4, 2026
@jworkmanjc jworkmanjc requested a review from shashisinghjc June 4, 2026 23:20
@jworkmanjc

jworkmanjc commented Jun 4, 2026

Copy link
Copy Markdown
Contributor Author

Tested locally on new exe build:

  • Migration completed successfully
  • After logging in for the first time, the migrated user was able to login as expected (meaning no change, backwards compatible)
  • Reviewing the logs, that set of new code never was executed for me, my user hive permissions were correct to begin with. In my opinion this is a low level change, not much risk since the permissions are always tested prior to being changed. And in my testing the code we use to change the permissions works to "unlock" the profile.
Screenshot 2026-06-04 at 5 45 20 PM

If we get passing tests, let's merge, & get 2.13.1 out ASAP

@gweinjc gweinjc left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Migrated user -> removed inherited permissions from migrated user on usrclass.dat -> attempted to sign in and signed into temp profile -> ran file permission script -> user signed into correct profile
Image

@jworkmanjc jworkmanjc changed the title autogen initial commit for setting file permissions on userHive files ADMU usrClass Dat, Update if missing required permissions Jun 8, 2026

@shashisinghjc shashisinghjc left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

good to go.

@jworkmanjc jworkmanjc merged commit e4d4d30 into v2.13.1 Jun 10, 2026
13 of 19 checks passed
@jworkmanjc jworkmanjc deleted the CUT-5193_usrClassDatPermission branch June 10, 2026 14:19
jworkmanjc added a commit that referenced this pull request Jun 10, 2026
* Cut 5167 system context changes v2 (#260)

* patch for system context

* changelog patch update

* restore progressForm

* remove extra write-hosts

* regenerate from build

* autogen initial commit for setting file permissions on userHive files (#262)

* changelog datetime

* Changes for datetime/ systemContextAPI
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ADMU ADMU Module Release patch Patch version release

Development

Successfully merging this pull request may close these issues.

3 participants