If you’ve configured the Server Authentication Certificate Template GPO option, which determines the certificate that the machine uses for Remote Desktop connections, and applied it to 2008 R2 or older servers then you may find that you’re getting a lot of duplicate certificates being issued. It’s a problem with an easy solution but it’s not an obvious one.

You see, if you read the documentation for the setting (something which is helpfully not included in the GPO explanation text in the GPMC) you’ll soon discover that:

Important
You must set the certificate template’s attributes Template display name and Template name to the same value.

Due to a disparity in the way the API checks to see if a certificate already exists on the machine for this purpose if the Template Name is not the same as the Template Display name it fails to identify that it already has a matching certificate and so requests a new one.

This problem is resolved in Server 2012 R2. It’s possibly resolved in Server 2012 as well but I don’t have a box to hand I can test with.

As far as I can tell the “Do not automatically reenroll if a duplicate certificate exists in Active Directory” option has no impact on this issue.



I’m amazed that I haven’t previously had a need for something like this, but I was looking for some way to visualise AD group memberships, specifically to take into account fairly deeply nested groups. After a fair bit of searching, a lot of dead-ends and some products that seriously over-sold themselves, I came across this little beauty:

https://gallery.technet.microsoft.com/scriptcenter/Graph-Nested-AD-Security-eaa01644

It’s a Powershell module which extracts group memberships for a User, Group or OU (well, everything in that OU anyway) and creates a Graphviz file that gives a functional, if not very pretty, visualisation of the group membership hierarchy. The output looks something like this:

Draw-ADSecurityGroupNesting
Sample output

Extremely handy if you’re trying to get a better idea of how your group nesting shakes out or where you may have circular memberships or redundant groups.



Quick and easy one-liner – if you need to know at a glance which DCs in a domain are GCs and which aren’t:

[System.DirectoryServices.ActiveDirectory.Domain]::GetCurrentDomain().DomainControllers | %{"$($_.Name) : $($_.isglobalcatalog())"}


You know how it is, you don’t pay attention to the management of your domain for just 5 or 6 years and suddenly you have hundreds of GPOs with no idea what half of them do or even if they’re actually linked somewhere. For some reason, the Powershell GPO module doesn’t have a simple cmdlet or property that lets you tell if a GPO is linked or not, because that would be far too helpful, but it’s not too hard to do if you don’t mind parsing some XML.

This code is based on a much more complicated script from here, designed to let you search for individual settings within a GPO. It will accept a number of arguments, but run without any it will simply output to the console a list of all of the unlinked GPOs in the current domain.

<#
Copyright (c) 2014, Adam Beardwood
All rights reserved.
 
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met: 
 
1. Redistributions of source code must retain the above copyright notice, this
   list of conditions and the following disclaimer. 
2. Redistributions in binary form must reproduce the above copyright notice,
   this list of conditions and the following disclaimer in the documentation
   and/or other materials provided with the distribution. 
 
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#>
 
#Find Unlinked Linked GPOs in a domain
#Adam Beardwood 20/05/2014
#v1.0 - Initial Release
 
param (
[Parameter(Mandatory=$false)]
[boolean] $outfile=$false,
[Parameter(Mandatory=$false)]
[string] $filename="UnlinkedGPO-$(get-date -f HHmmss).txt",
[Parameter(Mandatory=$false)]  
[string] $DomainName = [System.DirectoryServices.ActiveDirectory.Domain]::GetCurrentDomain()
)
 
Import-Module GroupPolicy;
 
[string] $Extension="Enabled"
 
$allGposInDomain = Get-GPO -All -Domain $DomainName | Sort DisplayName;
 
$xmlnsGpSettings = "http://www.microsoft.com/GroupPolicy/Settings";
$xmlnsSchemaInstance = "http://www.w3.org/2001/XMLSchema-instance";
$xmlnsSchema = "http://www.w3.org/2001/XMLSchema";
 
$QueryString = "gp:LinksTo";
 
$host.UI.WriteLine();
 
foreach ($Gpo in $allGposInDomain)
{				
	$xmlDoc = [xml] (Get-GPOReport -Guid $Gpo.Id -ReportType xml -Domain $Gpo.DomainName);		
	$xmlNameSpaceMgr = New-Object System.Xml.XmlNamespaceManager($xmlDoc.NameTable);
 
	$xmlNameSpaceMgr.AddNamespace("", $xmlnsGpSettings);
	$xmlNameSpaceMgr.AddNamespace("gp", $xmlnsGpSettings);
	$xmlNameSpaceMgr.AddNamespace("xsi", $xmlnsSchemaInstance);
	$xmlNameSpaceMgr.AddNamespace("xsd", $xmlnsSchema);
 
	$extensionNodes = $xmlDoc.DocumentElement.SelectNodes($QueryString, $XmlNameSpaceMgr);
 
	$stringToPrint = $($Gpo.DisplayName) + " is not linked in this domain";
 
	if($extensionNodes[0] -eq $null){
		if($outfile -eq $true){
			$stringToPrint | Out-File $filename -Append
		}else{
			write-host $stringToPrint -foregroundcolor red
		}
	}
}


Firstly, download the RSAT tools for Windows 7 or Windows 8 of the appropriate bittiness.

Then install them using: wusa <RSAT Installer Filename>.msu /quiet

You can then enable the Powershell AD cmdlets (or indeed any other RSAT components) from the command line using dism.exe thus (careful, the featurenames are case-sensitive):

dism /online /enable-feature /featurename:RemoteServerAdministrationTools /featurename:RemoteServerAdministrationTools-Roles /featurename:RemoteServerAdministrationTools-Roles-AD /featurename:RemoteServerAdministrationTools-Roles-AD-Powershell

For some reason it won’t install “dependent” parent components unless you explicitly tell it to install them, which is a pain but workaroundable.



This handy little script will pull all of the users from the specified AD group and then grab the LastLogon time from each specified DC (or you could use [DirectoryServices.ActiveDirectory.Domain]::GetCurrentDomain().DomainControllers to get all of them in the current domain) as well as grabbing the LastLogonTimeStamp for good measure (see my previous post for the caveats on that one). You can also specify which attribute you want to sort the results on; I recommend samaccountname because it’s usually the most useful.

Obviously it’s much quicker and simpler to do this with the ActiveDirectory cmdlets, but sometimes you’re stuck working with a bunch of 2003 DCs and have to make do with ADSI.

################################################################################################
#This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;    #
#without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.     #
#THERE IS NO WARRANTY FOR THE SOFTWARE, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT      #
#WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS PROVIDE THE SOFTWARE "AS IS" WITHOUT   #
#WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE         #
#IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK   #
#AS TO THE QUALITY AND PERFORMANCE OF THE SOFTWARE IS WITH YOU. SHOULD THE SOFTWARE PROVE      #
#DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. IN NO EVENT  #
#UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, BE       #
#LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES#
#ARISING OUT OF THE USE OR INABILITY TO USE THE SOFTWARE (INCLUDING BUT NOT LIMITED TO LOSS    #
#OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A    #
#FAILURE OF THE SOFTWARE TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER HAS BEEN     #
#ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.                                                   #
################################################################################################
 
#AD Group Last Logon Checker (ADSI Edition)
#Adam Beardwood 20/08/2012
#v1.0 - Initial Release
#v1.5 - Added Datagrid & CSV output
 
$dcs = ("DC1","DC2","DC3")
$group = "CN=Domain Admins,CN=Users,DC=domain,dc=local"
$basedn = "DC=domain,DC=local"
$sorton = "samaccountname"
$useroutput = @()
 
foreach($dc in $dcs){
	$Search = New-Object DirectoryServices.DirectorySearcher([ADSI]"LDAP://$dc/$basedn")
	$Search.Filter = "(&(memberof=$group)(objectclass=user))"
	$Search.Sort = New-Object System.DirectoryServices.SortOption($sorton,"Ascending")
	$SearchRes1 = $Search.Findall()
 
	foreach($result in $searchres1){
 
		$user = $result.Properties.samaccountname
		$Search.Filter = "(&(objectclass=user)(samaccountname=$user))"
		$SearchRes2 = $Search.Findall()
 
		New-Variable -Name "$user" -value (New-Object System.Object) -ErrorAction Silentlycontinue
		(Get-Variable "$user").value | Add-Member -type NoteProperty -name Name -value $user[0] -ErrorAction Silentlycontinue
		foreach($adc in $dcs){
			(Get-Variable "$user").value | Add-Member -type NoteProperty -name $adc -value "" -ErrorAction Silentlycontinue
		}
		(Get-Variable "$user").value| Add-Member -type NoteProperty -name LLTS -value "" -ErrorAction Silentlycontinue
 
		if($SearchRes2[0].Properties.lastlogon -ne $null){
			(Get-Variable "$user").value.$dc = ([DateTime]::FromFileTime([Int64]::Parse($SearchRes2[0].Properties.lastlogon)))	
		}
 
		if($SearchRes2[0].Properties.lastlogontimestamp -ne $null){
			(Get-Variable "$user").value.LLTS = ([DateTime]::FromFileTime([Int64]::Parse($SearchRes2[0].Properties.lastlogontimestamp)))
		}
	}
}
 
foreach($result in $searchres1){
	$user = $result.Properties.samaccountname
	$useroutput += (Get-Variable "$user").value
}
 
$useroutput | Sort-Object Name | Out-GridView -Title "Last Logon Times"
#$useroutput | Sort-Object Name | Export-CSV C:\logons.csv


So today I discovered that the LastLogonTimestamp attribute of an account in AD (also known as LLTS) is only updated on logon if the old value is more than 14 days in the past. That means the value can only be trusted if it is more than 14 days in the past.

This, as it turns out, is very annoying if you’re trying to do some semi-half-assed auditing. Now, you can use the LastLogon attribute, which is always up to date, but that isn’t replicated between DCs so you have to query all of them.

Note to my future self: Do not rely on LLTS for anything more recent than 14 days.



If you have a 2008/R2 failover cluster then it’s possible you may come across the following errors at some point after an unplanned failover:

Unable to obtain the primary cluster name identity token

An attempt has been made to operate on an impersonation token by a thread that is not currently impersonating a client

As a result, your clustered resources may refuse to start.

Now, assuming that you haven’t done something stupid, like deleting the cluster computer account from AD, you should be able to resolve this problem by right-clicking on the failed CNO on the Summary page of the Failover Cluster Manager, go to “More Options” and then “Repair Active Directory Object”. You should then be able to start all the cluster resources successfully. Thanks to wonhchoi.com for pointing me in the right direction.

As to what might have caused this problem in the first place? Well that’s still something of a mystery to me at the moment…



One of the most annoying things when working with Powershell and AD accounts is the UserAccountControl value. This value is what determines settings such as whether or not the account is locked out, disabled, requires a smartcard for authentication, uses reversible encryption for its password, etc. The default is 512 (NORMAL_ACCOUNT) but there are all kinds of weird and wonderful combinations that can turn up depending on how the account is configured and when you’re trying to (for example) find all the accounts that are set to USE_DES_KEY_ONLY then having so many different possible values (any number that could have 2097152 as part of its makeup) makes it a pain to work out.

Thankfully, someone has solved this problem in a relatively simple manner; I first came across it here: http://bsonposh.com/archives/288 and it is listed in all its glory below. It takes a single argument, which is the UAC value for an account and will return a list of all the flags that apply to that value. So, for example, plugging in a value of 66048 will give an output of:
NORMAL_ACCOUNT
DONT_EXPIRE_PASSWORD
.

param
([int]$value)
$flags = @("","ACCOUNTDISABLE","", "HOMEDIR_REQUIRED",
"LOCKOUT", "PASSWD_NOTREQD","PASSWD_CANT_CHANGE", "ENCRYPTED_TEXT_PWD_ALLOWED",
"TEMP_DUPLICATE_ACCOUNT", "NORMAL_ACCOUNT", "","INTERDOMAIN_TRUST_ACCOUNT", "WORKSTATION_TRUST_ACCOUNT",
"SERVER_TRUST_ACCOUNT", "", "", "DONT_EXPIRE_PASSWORD", "MNS_LOGON_ACCOUNT", "SMARTCARD_REQUIRED",
"TRUSTED_FOR_DELEGATION", "NOT_DELEGATED","USE_DES_KEY_ONLY", "DONT_REQ_PREAUTH",
"PASSWORD_EXPIRED", "TRUSTED_TO_AUTH_FOR_DELEGATION")
1..($flags.length) | ? {$value -band [math]::Pow(2,$_)} | % { $flags[$_] }


  • Who has 1,450 login scripts (For 3,500 users at that)?
  • Who puts account passwords in the account’s description field? (for anyone who doesn’t know why this is appallingly bad, try this PowerShell as a regular domain user from a box with the Win7/2008R2 RSAT tools installed: ipmo activedirectory;get-aduser -filter * -Properties description, other scripting languages are available)
  • Who doesn’t use Domain Admins for their domain administrators and instead uses Account Operators, Administrators, Network Configuration Operators, Remote Desktop Users & Server Operators? (Not for granular permissions, all of them as a replacement for Domain Admins membership)
  • Who leaves 5,500 computer accounts, including servers, in the Computers container in AD? (ProTip: You can’t link GPOs to the “Computers” container in AD)
  • Who doesn’t have any actual DHCP servers in their Authorised DHCP Servers list? (Not even sure how you manage this one)

This isn’t just bad practice, this is years of dedicated training and substantial investment in bad practice…

I’m going to go curl up in a corner and cry for a while now.