Zoho Banner September 2011

Archive for the ‘Scripting’ Category

Today’s post is another short one.  It’s a Powershell one-liner to find all the Primary SMTP address suffixes in use by the mailboxes in your Exchange Org.

In this example I know that my default suffix is “contoso.com”, but I want to find out what others are being used as primary:

get-mailbox -ResultSize unlimited | ? {$_.primarySMTPaddress -notmatch "@contoso.com" } `
| select @{l="SMTPSuffix";expression={$_.primarysmtpaddress.tostring().Split("@")[1]}} –unique

The output (which will take a while as there is a lot of post-processing in the pipeline) looks like this:

SMTPSuffix
_______
fabrikam.com
northwindtraders.com
fouthcoffee.com

Enjoy!

 

Today’s blog entry is short and sweet.  You want to quickly find out whether the AD Recycle Bin feature is enabled or not.   There are a few ways to do this, but this is a quick Powershell method.

 

# Is the AD Recycle Bin enabled in my forest?
Import-Module ActiveDirectory
If ((Get-ADOptionalFeature -Identity "Recycle Bin Feature").EnabledScopes) {
    write-host "AD Recycle Bin is enabled"
    } # end if
Else {
    write-host "AD Recyle Bin is not yet enabled"
} # end else

I quite regularly come across Active Directory environments where users have been mistakenly added to groups protected by the AdminSDHolder and subsequently removed when the mistake has been realised.  This process creates “orphans” because the AdminSDHolder process doesn’t tidy up after itself.  Here’s what happens:

  1. User added to group protected by the AdminSDHolder (e.g. Account Operators)
  2. The AdminSDHolder process (actually a thread within LSASS.EXE on the PDC Emulator) sets the adminCount attribute value to 1 on the user object and replaces the user’s security descriptor with that of the AdminSDHolder container object.
  3. User is then removed from Account Operators.
  4. The adminCount value of 1 is not cleared and the security descriptor is not changed (i.e. remains the same as that of the AdminSDHolder).

It doesn’t make sense to leave these orphans as they are.  For one, it plays havoc with any delegation model that is in place.  It also makes it difficult to determine what objects are actually protected.  For example, searching for objects that have an adminCount of 1 will potentially give you misleading results.

So what can you do about it?  One option is to manually look at each object to determine whether it is genuinely a protected object and then clear the adminCount value and re-set the inheritance flag for any objects no longer protected.  This is likely to be incredibly time consuming as you will need to factor in nested group membership into your investigation.  Another option is to run a script that clears the adminCount value and resets the inheritance flag for *all* protected objects.  Any objects that should genuinely be protected will be re-protected (I just made that word up) when the AdminSDHolder next cycles (within 1 hour by default).

The second option sounds like the clear winner.  There are some small risks that you should be aware of with this approach.  Firstly, there might be some malware “lurking” in your environment that will take the opportunity to compromise privileged accounts when they are in an unprotected state (even for a short period).  Secondly, re-setting the inheritance flag on objects might cause unforeseen problems.  For example, a user object might have had the inheritance flag unchecked prior to being protected by the AdminSDHolder.

Basically, there is no silver bullet here.  Personally, I think the small risk associated with the scripted option generally outweighs the negative impact of AdminSDHolder orphans.  Here’s the script that I use:

#########################################################
#
# Name: Cleanup-AdminSDHolder.ps1
# Author: Tony Murray
# Version: 1.0
# Date: 28/08/2013
# Comment: PowerShell 2.0 script to re-set inheritance
# flag for objects no longer protected by the 
# AdminSDHolder
#
########################################################### 

# Import the AD module
Import-Module ActiveDirectory

# Find objects that appear to be protected in AD
$probjs = Get-ADObject -LDAPFilter "(admincount=1)"
$bcount = $probjs.count
if ($bcount) {
    write-host "Protected object count before change is $bcount"
} # end if
else {
    write-host "No objects are currently protected - nothing to do"
    exit
} # end else

# Change to the AD drive
cd AD:

# Loop through protected objets to set inheritance flag
# and clear the adminCount value
foreach ($probj in $probjs) {
    $dn = $probj.distinguishedname
    $acl = get-acl $dn
    if ($acl.AreAccessRulesProtected) {
        write-host "$dn has inheritance blocked - we will remove the block"
        $acl.SetAccessRuleProtection($false, $false);
        set-acl -aclobject $acl $dn
        # We need to clear the adminCount attribute too
        Set-ADObject -Identity $dn -Clear adminCount
    } # end if
} # end foreach    

# Count the number of protected objects
$acount = (Get-ADObject -LDAPFilter "(admincount=1)").count
if ($acount) {
    write-host "Protected object count after change is $acount"
} # end if
else {
    write-host "No objects are currently protected"
} # end else

Please leave a comment if you have any thoughts on how to improve the script, or if you are aware of a better method of handling AdminSDHolder orphans.

You can download a copy of the script here: Cleanup-AdminSDHolder

 

Back in March 2010 when Powershell and I were on somewhat less friendly terms, I wrote an OU shadow script to populate group membership based on the contents of an OU. Since then, Powershell and I now at least acknowledge eachother when we pass in the corridor and I have updated the script with some improvements.

One common use for the script is populating group memberships for use with Fine-Grained Password Policy (FGPP).

Please leave a comment if you see any scope for improvement.  You can download a copy of the script here: OUBasedGroupMembership ps1

#########################################################
#
# Name: OUBasedGroupMembership.ps1
# Author: Tony Murray
# Version: 1.0
# Date: 03/09/2013
# Comment: PowerShell 2.0 script to
# manage group membership based on OU contents
#
#########################################################

# Import the AD module
ipmo ActiveDirectory

# Define arrays to be used for matching
$arrou = @()
$arrgp = @()

# Domain controller to be used
$dc = (Get-ADRootDSE).dnshostname
write-host "Using DC $dc for all AD reads/writes"

# Specify the OU where the accounts are located
$OUdn = "OU=Admin Accounts,OU=AD Administration,DC=contoso,DC=com"
$OuUsrs = Get-ADUser -Filter * -SearchBase $oudn -Server $dc

# Specify the group to use
$grp = "de42112f-81d2-4849-900c-d6907c77d3f5" # "Service Accounts"
$grpusers = Get-ADGroupMember -Identity $grp -Server $dc

# Build arrays using the DN attribute value
$OuUsrs | % {$arrou += $_.distinguishedname}
$grpusers | % {$arrgp += $_.distinguishedname}

# Add to group membership (new user in OU)
foreach ($usr in $arrou) {
    if ($arrgp -contains $usr) {
        write-host "User $usr is a member of the group"
    }
    else {
        write-host "User $usr is not a member of the group - adding..."
        #Add-ADGroupMember -Identity $grp -Members $usr -Server $dc
    } # end else
    Remove-Variable -ErrorAction SilentlyContinue -Name usr    
} # end foreach

write-host "`n"

# Remove from group membership (no longer in OU or has been manually added to group)
# The assumption here is that the OU is authoritative for the group's membership
foreach ($mem in $arrgp) {
    if ($arrou -contains $mem) {
        write-host "User $mem is located in the OU.  Nothing to do"
    } # end if
    else {
        write-host "User $mem is not present in the OU.  Removing from membership..."
        #Remove-ADGroupMember -Identity $grp -Members $mem -Server $dc -Confirm:$false
    } # end else
    Remove-Variable -ErrorAction SilentlyContinue -Name mem
} # end foreach

Occasionally, I have a need to merge the attributes of one AD user into another.  This requirement is typically the result of a migration where some users have had accounts informally created in the target environment in advance of the formal migration process.  In other words, a user ends up with two accounts and needs to merge the authoritative attribute values from one of user object to another.   The Powershell script below shows an example of how selected attributes on one object can be replaced with those from another object.  

#########################################################
#
# Name: Merge-UserAttributes.ps1
# Author: Tony Murray
# Version: 1.0
# Date: 12/06/2013
# Comment: PowerShell 2.0 script to copy a fixed set of
# attributes from one user object to another
#
#########################################################

# Import the AD module
ipmo activedirectory

#### Function to test the existence of an AD user
function Test-ADUser() {
   [CmdletBinding(ConfirmImpact="Low")]
   Param (
      [Parameter(Mandatory=$true,
                 Position=0,
                 ValueFromPipeline=$true,
                 HelpMessage="Identity of the AD object to verify if exists or not."
                )]
      [Object] $Identity
   )
   trap [Exception] {
      return $false
   }
   $auxObject = Get-ADUser -Identity $Identity -Server $dc
   return $true
}
####

#### Global variables
$dc = (Get-ADDomainController).hostname

# Create an array of the attributes we want to copy
$atts2copy = @("ipphone","mobile","facsimileTelephoneNumber",
"telephonenumber","streetAddress","st","l","c","physicalDeliveryOfficeName",
"description","department","postofficebox","thumbnailPhoto","manager")

# Users to merge 
$source = "User1"
$target = "User2"

write-host "Working on source target pair: $source --> $target"
if ( (Test-ADUser $source) -and (Test-ADUser $target) ) { $greenlight = $true }
if ($greenlight) {
    $srcatts = Get-ADUser -Identity $source -pr * -Server $dc
    # Create a hashtable to store the attributes
    $replaceHashTable = New-Object HashTable
    # Add the attributes one-by-one to the hashtable
    foreach ($att2copy in $atts2copy){
        $attvalue = $srcatts.$att2copy
        if ($attvalue) {
            if ( ($attvalue.gettype()).name -eq "ADPropertyValueCollection") {
                # We have a collection which need to convert to an array
                # otherwise the set-aduser cmdlet doesn't set the value
                $attarray = @($attvalue)
                $replaceHashTable.Add($att2copy,$attarray)
            } # end if
            else {
                $replaceHashTable.Add($att2copy,$attvalue)
            } # end else
        } # end if
        Remove-Variable -ErrorAction SilentlyContinue -Name att2copy
        Remove-Variable -ErrorAction SilentlyContinue -Name attvalue
        Remove-Variable -ErrorAction SilentlyContinue -Name attarray
    } #end foreach
    # Set the attributes on the target user
    write-host "Setting attributes on target"
    $replaceHashTable
    Set-ADUser -Identity $target -Replace $replaceHashTable -Server $dc
    } # end if
Else {
    Write-Host "No match found for either $source or $target - please check CSV"
} # end else
Remove-Variable -ErrorAction SilentlyContinue -Name source
Remove-Variable -ErrorAction SilentlyContinue -Name target
Remove-Variable -ErrorAction SilentlyContinue -Name greenlight
Remove-Variable -ErrorAction SilentlyContinue -Name srceatts
$replaceHashTable = @{}

Another scenario where this script might be useful is merging attributes from an AD Snapshot (taken with NTDSUtil) into a “live” object, e.g. following the corruption of attribute values in the production environment.  In this case you would need to modify or remove the test for the existence of the user objects.

You can download a copy of the script here: Merge-UserAttributes

I recently came across an old blog post by fellow MVP Joe Richards.  In the post Joe points out that whenChanged is not a replicated attribute, which makes it a poor candidate for accurately determining when an object was last modified.  He does however indicate that the whenChanged attribute provides a handy way to report when your Domain Controllers were promoted.  This is possible because the whenChanged attribute is stamped with the date and time each object is initiated on that specific DC as part of DCPROMO.  It means we can query the whenChanged attribute on, for example, any object in the default AD schema to determine the date on which that DC was promoted.  Cool, eh?  Here’s a Powershell sample using the adminDescription attribute class object in the schema partition.

$admind = "CN=Admin-Description," + (Get-ADRootDSE).schemanamingcontext
$dcs = Get-ADDomainController -Filter * | sort name
foreach ($dc in $dcs) {
    $name = $dc.name
    $wc = (Get-ADObject $admind -Server $name -Properties whenchanged).whenchanged.ToShortDateString()
    write-host "Domain Controller $name was created on $wc `n"
} # end foreach

Here’s something I put together to handle bulk certificate requests for submission to an Enterprise CA using certreq.exe.  Enjoy!

#########################################################
#
# Name: Request-Certificates.ps1
# Author: Tony Murray
# Version: 1.0
# Date: 4/12/2012
# Comment: PowerShell script to submit certificate 
# requests in bulk using certreq.exe
#
#########################################################

# Specify the location of the request files
$csrdir = "C:\Certs\Requests\"
###

$files = Get-ChildItem $csrdir
$csrs = $files | ? {$_.extension -eq ".csr"}

# Parameters
$template = "WebServer" # must always use concatenated name format
$CA = "MyCAServer.mydomain.com\MyCAName"

foreach ($csr in $csrs)
{
    write-host "Requesting certificate $csr ..."
    $basename = $csr.basename
    # Specify the command line parameters for certreq.exe
    $parameters = "-config $CA -submit -attrib CertificateTemplate:$template " `
    + "$csrdir" + "$basename" + ".csr " +  "$csrdir" + "$basename" + ".cer " `
    +  "$csrdir" + "$basename" + ".p7b"
    # Start certreq.exe and pass in the parameters
    $request = [System.Diagnostics.Process]::Start( "certreq",$parameters )
    $request.WaitForExit()
    write-host "Finished request $csr"
    #sleep 10
} # end foreach

If you’re familiar with LDAP searches you will probably at some point have been frustrated at the inability to exclude objects in a specific Organisational Unit, i.e “Give me all User objects in the domain, except those in the Sales OU”.   To workaround the problem you typically need to do some scripting. There are several methods by which you exclude objects using Powershell, but I really like the one published by fellow MVP Ilya Sazonov.

Here’s an example using Ilya’s method. In this scenario the goal is to move all Contact objects not currently in the Contacts OU to the Contacts OU. To do this we have to first find all Contacts excluding those in the Contacts OU.

$conou = "OU=Contacts,DC=mydomain,dc=com"

$exclcons = Get-ADObject -LDAPFilter "(objectclass=contact)" -SearchBase $conou `
| select -ExpandProperty distinguishedname 

$tomove = Get-ADObject -LDAPFilter "(objectclass=contact)" `
| ? {$exclcons -notcontains $_.DistinguishedName}

foreach ($con in $tomove) {
    Move-ADObject -Identity $con -TargetPath $conou -Confirm:$false
} # end foreach

If you come across this error when using Powershell to delete an object, it is most likely because the object has child objects associated with it.  The most obvious example is computer objects that have print queue, service connection point, RRAS or various other types of child objects.  The workaround is to determine the child object (to see if it might be required) and then to delete the objects recursively as shown below.

In this example we have a computer object named “Foo”.  If we try and delete it using the Powershell AD cmdlet Remove-ADComputer we see the “leaf object: error.

leaf1

We can then use some other Powershell goodness to determine the type of child object that we have.  In this case a service connection point object.

leaf2

Once we’re happy that deleting the child object won’t cause any other issues, we can use the Remove-ADObject cmdlet together with the –Recursive switch to delete both the computer and the service connection point objects.

leaf3

For a number of years now I have been using OldCmp  to find and remove inactive user and computer accounts.  The other day I thought I would have a crack at using the AD Powershell cmdlets to at least do the finding part.  It wasn’t as difficult as I thought.  Here’s an example looking for enabled accounts that have been inactive for 90 days or more:

# Find inactive user accounts

$now = Get-Date
$old = $now.AddDays(-90)
Get-ADUser -Filter * -Properties lastlogondate `
| ? {($_.enabled -eq $true) -and ($_.lastlogondate -le $old)} `
| select samaccountname, lastlogondate `
| Export-Csv .\inactive_users.csv -NoTypeInformation

# Find inactive computer accounts

$now = Get-Date
$old = $now.AddDays(-90)
Get-ADComputer -Filter * -Properties lastlogondate `
| ? {($_.enabled -eq $true) -and ($_.lastlogondate -le $old)} `
| select name, lastlogondate `
| Export-Csv .\inactive_computers.csv -NoTypeInformation

I normally use LDAP filters for all searches, but in this case I used a standard Powershell filter.  Why? Well, because the cmdlets expose two pseudo attributes: “enabled” and “lastlogondate”.  I call these pseudo attributes because you won’t find them anywhere in the AD schema.  They are provided to make life easier.   The alternative would be to query userAccountControl with a bitwise filter  to find the enabled/disabled state and to do some formatting with lastLogonTimestamp, which is stored in AD as a large integer value.

I hope you find these useful.