Tell Me Which Active Directory Security Groups Are Not Applying Inheritable Permissions

I encounter many Active Directory forests that were built and maintained for years by other organizations, and then through mergers or acquisitions or business reorgs, I need to help bring them into the fold with the rest of my portfolio of AD forests.

AD permissions can be a deep rabbit hole, especially when sitting down to a new directory sight unseen. Administrators make subtle changes to AD objects over the years and a lot of entropy happens.  Entropy that's not always easy to see or keep track of.

In this particular instance, we had an issue where a delegation of control was not working correctly and/or consistently.  It was the common IT task of delegating the ability to reset passwords and unlock accounts (but nothing else) to a special "help desk" sort of security group. It was allowing members of the "help desk" group to reset the passwords of certain users, but not others.  None of them were administrative users, or members of the Domain Admins group, the Account Operators group, etc. etc.

Turns out, the problem was that some security groups were not including inheritable permissions from the domain root object, so users who were members of these certain groups were immune to the effects of the delegation. 

Advanced Permissions

At first I actually thought to go and click on every single security group in the domain, checking on whether they were applying inheritable permissions or not.  Then a few seconds I realized, "Don't be an idiot Ryan. The GUI is not your friend! Don't succumb to its siren song!"

After glancing at this excellent SDDL reference here for about 5 minutes, I whipped this up:

Foreach($_ In Get-ADGroup -Filter *)
{
	[bool]$Inherits = $($(Get-ADGroup $_ -Properties *).nTSecurityDescriptor.Sddl.Contains('OI'))
	If($Inherits -EQ $False)
	{
		$_
	}
}

 And presto - a list of all security groups in the domain that are not applying inheritable permission from their parents.

PS - I admit, I still have to use a cheat sheet when reading SDDL.  :P

Finding Locked Out Domain Accounts, Or At Least, How Not To

I hadn't posted in a little while, so I thought I'd do a two-fer today.

You might see some advice on the internet about using the userAccountControl attribute to identify locked out domain accounts.  More specifically, the following LDAP filter:

(&(objectCategory=User)(userAccountControl:1.2.840.113556.1.4.803:=16))

The =16 part should mean "locked out", per the attribute's documentation, keeping in mind that 0x10 in hex is 16 in decimal.

DON'T USE IT.  It doesn't work. I don't think it has ever worked. Apparently it was just an idea that some person on the AD design team had that never got implemented. If anyone has any history on this bit, and if it has ever worked in the past, I would love to hear about it. All I know is that it does not work now.

You can easily verify for yourself that it doesn't work with Powershell:

Get-ADUser -LDAPFilter "(&(objectCategory=User)(userAccountControl:1.2.840.113556.1.4.803:=16))"

You'll probably get 0, or some other very inaccurate value. (Other userAccountControl flags however do definitely work and can be used reliably. Just not this one.)

Here is another LDAP filter you will often see on the web for finding locked out accounts:

(&(objectCategory=Person)(objectClass=User)(lockoutTime>=1))

DON'T USE THAT EITHER. That will return too many results.  The reason why is that lockoutTime is not reset until the next time that person successfully logs in. So if an account is locked out, then their lockoutTime attribute gets set, then if the domain lockout duration expires, the account is no longer technically locked out, but lockoutTime remains populated until the next time that user logs in. Now if you're thinking that we should filter this list by only the users who have a lockoutTime that is less than [domain lockout duration] minutes in the past, then you're on the right track. Those would be the users who are still really locked out.

When I type  Search-ADAccount -LockedOut , however, I am given what seems to be an accurate number of users that are currently locked out. I should point out that if working in a large AD environment, I think it's best to point directly to your PDC-emulator whenever possible, because your PDC-emulator will always have the most up-to-date information about account lockouts. From a Microsoft article about urgent replication:

... account lockout is urgently replicated to the primary domain controller (PDC) emulator role owner and is then urgently replicated to the following:

• Domain controllers in the same domain that are located in the same site as the PDC emulator.

• Domain controllers in the same domain that are located in the same site as the domain controller that handled the account lockout.

• Domain controllers in the same domain that are located in sites that have been configured to allow change notification between sites (and, therefore, urgent replication) with the site that contains the PDC emulator or with the site where the account lockout was handled. These sites include any site that is included in the same site link as the site that contains the PDC emulator or in the same site link as the site that contains the domain controller that handled the account lockout.

In addition, when authentication fails at a domain controller other than the PDC emulator, the authentication is retried at the PDC emulator. For this reason, the PDC emulator locks the account before the domain controller that handled the failed-password attempt if the bad-password-attempt threshold is reached.

If you follow my earlier instructions on how to peek inside the Search-ADAccount cmdlet itself, you'll see that Microsoft themselves is keying on the Account Lockout Time to perform this search:

Finally, I can reproduce the same behavior of  Search-ADAccount -LockedOut  with the following bit of Powershell, given that I know my domain's account lockout duration:

Get-ADUser -LDAPFilter "(&(objectCategory=Person)(objectClass=User)(lockoutTime>=1))" -Properties LockoutTime | 
Select Name, @{n="LockoutTime";e={[DateTime]::FromFileTime($_.LockoutTime)}} | 
Sort LockoutTime -Descending | ? { $_.LockoutTime -gt (Get-Date).AddMinutes($AccountLockoutDuration * -1) }

That gives the exact same results as  Search-ADAccount -LockedOut .

Accessing HKEY_USERS With Powershell

If you run  Get-PSDrive  in Powershell, you will see that you are given two registry providers: HKEY_LOCAL_MACHINE (HKLM) and HKEY_CURRENT_USER (HKCU).

Given that HKCU doesn't actually exist per se, but is rather just a "projection" or "mapped view" of HKEY_USERS\<Your SID>, that means that the HKCU provider can be problematic in certain remote registry or network logon situations, and of course it's no good at all if you want to view or manipulate the registry of another user.

So how do we access HKEY_USERS in Powershell?  Here are three ways that will work just fine:

#
Set-Location Registry::\HKEY_USERS
#
#
New-PSDrive HKU Registry HKEY_USERS
Set-Location HKU:
#

And in this last example, I will set the desktop wallpaper for my own user account to cats.jpg, without using HKCU. This way I can run it at system boot, before I ever even log on:

#
Set-ItemProperty -Path Microsoft.PowerShell.Core\Registry::HKEY_USERS\S-1-5-21-2381515279-1434780867-6353740611-13738\Software\Microsoft\Windows\CurrentVersion\Policies\System -Name Wallpaper -Value C:\Pictures\cats.jpg
#

Taking a Peek Inside Powershell Cmdlets

Have you ever wondered how a particular Powershell Cmdlet works under the hood?  Maybe you're trying to mimic a certain behavior of a Cmdlet, and you'd like to see how Microsoft did it.

Turns out, it's surprisingly easy. The first thing you need is a .NET decompiler. There are many to choose from, but I like DotPeek.

Next, pick a Cmdlet, such as  Get-ADUser . To find the DLL that the Cmdlet comes from, do this:

Cmdlet DLL

If you add a  | clip  on the end there, the output will go straight to your clipboard.

(Did you know the hexadecimal color code for the Powershell background color is 012456?)

Anyhow, now that we know in what DLL the Cmdlet resides, we need to find out what method(s) within that DLL the Cmdlet is actually calling.  We can do that with  Trace-Command :

Trace-Command

There's a little more output after that, but this last line here is what we want. Microsoft.ActiveDirectory.Management.Commands.GetADUser.

Now we know the actual .NET method being called, and which DLL it's in. All that's left to do is fire up your .NET decompiler and disassemble!

Get-ADUser DotPeek

Psst, You Want A Script To Backup Your Lab VMs?

I can hook you up...

So I'm always doing a lot of lab work with Hyper-V virtual machines. Every once in a while I want to just save the state of the entire lab all at once and back it up to a safe storage volume.  I suppose I could set up Windows Server Backup on each of the VMs, and find some disk to use as a pass-through disk for one of the virtual machines and then share that so that the VMs could back up to the network share... but that's a ton of hassle.

How about I just save the state of all the VMs, and export them directly to my backup volume, then resume the VMs, all from the hypervisor?  As a scheduled task, perhaps?

About 10 minutes in the Powershell ISE and I've done just that.  A couple things to be warned of - First, you can't do this in production. The virtual machines are frozen while they're being exported, and it can take several minutes to export a VM. Secondly, make sure you are running with full Administrator privileges, or else cmdlets such as Get-VM will silently return nothing.

 

# Ryan Ries, 2013
# Backs up some lab VMs. Takes several minutes at least.

[String]$BackupPath = "D:\Backups\Hyper-V"
[String]$ErrorLog   = "D:\Backups\Hyper-VBackups.log"
$Start = Get-Date
"$(Get-Date) - Hyper-V Backups Starting." | Out-File $ErrorLog -Append
Try
{
    Get-Childitem $BackupPath -Recurse -Force | Remove-Item -Recurse -Force -ErrorAction Stop
}
Catch
{
    "$(Get-Date) - Error during Get-Childitem or Remove-Item: $($_.Exception.Message)" | Out-File $ErrorLog -Append
    Return
}
Try
{
    Get-VM -ErrorAction Stop | Stop-VM -ErrorAction Stop -Save
}
Catch
{
    "$(Get-Date) - Error during Get-VM Stop-VM -Save: $($_.Exception.Message)" | Out-File $ErrorLog -Append
    Return
}
Try
{
    Get-VM -ErrorAction Stop | Export-VM -ErrorAction Stop -Path $BackupPath
}
Catch
{
    "$(Get-Date) - Error during Export-VM: $($_.Exception.Message)" | Out-File $ErrorLog -Append
    Return
}
Try
{
    Get-VM -ErrorAction Stop | Start-VM -ErrorAction Stop
}
Catch
{    
    "$(Get-Date) - Error during Start-VM: $($_.Exception.Message)" | Out-File $ErrorLog -Append
    Return
}
$End = Get-Date
"$(Get-Date) - Hyper-V Backups completed in $([Math]::Round((New-TimeSpan $Start $End).TotalMinutes)) minutes." | Out-File $ErrorLog -Append