Auto Disabling Inactive Users with PowerShell

A number of years ago I posted some vbscript for auto disabling inactive users.  My system before was a two part system where I found the inactive users, wrote them to a text file and then my proxy system handled them.  Recently, I have been reviewing all of my scripts and moving them over to PowerShell.  (Note that I am now always expecting PowerShell 3.0 at minimum due to the improvement in native AD cmdlets.)

In addition, we have had to make sure that we are complying with PCI audit requirements so that inactive user accounts are disabled on a timely basis.  In the below PowerShell script, I have done a few different things:

  • Create an array of OU’s to look into for inactive accounts.
  • Look for users who have never logged on and were created 45 days or more ago.
  • Next, I look for users whose accounts have expired.
  • I send each user I find to my Disable-ADProxyUsers function.  This lets me do everything I want to when I disable a user account.

Here is that part of the script:

Import-Module ADProxy             
#First run just does regular accounts.            
$SearchBaseArray = "OU=User Accounts,DC=contoso,DC=org", "OU=Specialized Workstations and Users,DC=contoso,DC=org"            
foreach ($SearchBase in $SearchBaseArray) {               
 Write-Verbose "First, let's get any user who has never logged on and the creation date is more than 45 days"            
  $a = Get-Date            
  $b = $a.AddDays(-45)            
  $Justification = "Disabled: Auto Disabled by Proxy - User Never Logged On."            
  Get-ADUser -f {(lastlogontimestamp -notlike "*") -and (enabled -eq $true) -and (whencreated -lt $b) } `
     -searchbase $SearchBase |           
  Disable-ADProxyUser -Requestor "Proxy System" -ProcessType "AutoNeverLoggedOn" `
     -Justification $Justification -Notes "Yes"            
 Write-Verbose "Now let's look for inactive users.  Search AD accountinactive automatically adds 15 days to the timespan"            
  $NbDays = 104            
  Write-Debug "Get the current date"            
  $currentDate = [System.DateTime]::Now            
  # Convert the local time to UTC format because all dates are expressed in UTC (GMT) format in Active Directory            
  $currentDateUtc = $currentDate.ToUniversalTime()            
  # Calculate the time stamp in Large Integer/Interval format using the $NbDays specified on the command line            
  $lastLogonTimeStampLimit = $currentDateUtc.AddDays(- $NbDays)            
  $lastLogonIntervalLimit = $lastLogonTimeStampLimit.ToFileTime()            
  $Justification = "Disabled: Disabled by Proxy - User Account Inactive for more than 90 days."            
  Get-ADUser -f { (lastlogontimestamp -lt $lastLogonIntervalLimit) -and (enabled -eq $true) `
     -and (whencreated -lt $b)} -searchbase $SearchBase |            
  Disable-ADProxyUser -Requestor "Proxy System" -ProcessType "AutoInactive" `
     -Justification $Justification -Notes "Yes"            
 Write-Verbose "Finally let's look for accounts that have expired"            
  $Justification = "Account expired.  If you reenable, update the Account Expiration date."            
  Search-ADAccount -accountexpired -usersonly -searchbase $SearchBase |             
   Disable-ADProxyUser -Requestor "Proxy System" -ProcessType "AutoExpired" `
     -Justification $Justification -Notes "Yes"            

Finally, I send any users I find to a custom AD module to disable the user. I have made this into a function that is part of a larger module that I use to manage most changes I make in AD.  This custom function handles the disable by doing the following:

  • Disables the account
  • Updates the description with information on when the account was disabled and who disabled it.
  • Puts the disabled date into a custom schema field called contosoDisabledDate.  I then use the date in this field to auto delete accounts after they have been disabled for 60 days.
  • Set msnpAllowDialin to false.
  • Move to a Pending Delete OU.
  • Record information about this work in a SQL table so I can produce a monthly report on all AD changes.  I’ll share some of my code on this in a future blog post.

One of the benefits of this function (as part of my module) is that I can send users to disable using the PowerShell pipeline.  If you aren’t familiar with running functions, you can save below as a file and dot source it.  (You can find more information about dot sourcing by reading this blog article: )

    function Disable-ADProxyUser {            
      Disables Users in AD, moves them to pending delete and updates them with the correct information.
      This will disable a user using our processes in place here in USW.  This includes disabling the account,
      moving it to Pending Delete OU and updating appropriate fields (description - contains date user was disabled
      and who disabled it, info - has the reason the user was disabled, division - old location for placing disabled
      date, and TSADisableDate - new location for storing the disable date using a Large Integer).  Finally it 
      reports all of these updates to the ADProxy reporting database.  
      This command accepts the user name via either direct parameter input or via pipeline.
     .Parameter Requestor
      The name of the person requesting the disable.
     .Parameter ProcessType
      This lets us know if it was done manually or through some other process.
     .Parameter Justification
      This is the reason that the account is being disabled.
     .Parameter Notes
      If we also want to disable the users Notes account, this field needs to be marked with a "Y".
     .Parameter Admin
      Setting this to "Y" will disable the user account and move it to the Pending Delete IT Admin OU.
     .Parameter WorkOrder
      The Service Desk Work Order number related to this disable.
       # Disable a user and record the disable information to the ADProxy report DB.
       $LDAPFilter = "(&(objectClass=user)(!(userAccountControl:1.2.840.113556.1.4.803:=2))(lastLogonTimeStamp<=" + $lastLogonIntervalLimit + "))"
       Get-ADUser -SearchBase "OU=User Accounts,DC=usw,DC=ad,DC=salvationarmy,DC=org" -LDAPFilter $LDAPFilter -Properties * | Disable-ADProxyUser -Requestor "Proxy System" -ProcessType "Auto" -Justification "Disabled: Auto Disabled by Proxy - Inactive 75 days."
       # Disable an individual user
       Disable-ADProxyUser -UserName "Joe.Bob" -Requestor "Proxy System" -ProcessType "Auto" -Justification "Disabled: Auto Disabled by Proxy - Inactive 75 days." -Notes "Y"
      	#Disable a user with pipeline from a text file containing one user name per line
      	Get-Content names.txt | Disable-ADProxyUser -Requestor "Doug Neely" -ProcessType "ADCleanup" -Justification "User is inactive."
     [string] $UserName,            
        [string] $Requestor,            
        [string] $ProcessType,            
        [string] $Justification,            
        [string] $Notes,            
        [string] $Admin,            
        [string] $WorkOrder            
        BEGIN {            
        "Disabling User Accounts:"             
     #Set the termination date format used in the AD Description            
     $TerminationDate = Get-Date -Format "MMMM dd yyyy"            
     $TerminationDate = [string]$TerminationDate            
     $TermDateDivision = Get-Date -Format d            
     #The ContosoDisableDate field in AD is Large Integer.  Converting todays date to the Large Interval format.            
     $ContosoDisableDate = (Get-Date).ToFileTime()            
     PROCESS {            
      foreach ($User in $UserName) {            
          $ADAccount = Get-ADUser -identity $User -properties *            
          $ADSAM = $ADAccount.SamAccountName            
          $ADCompany = $ADAccount.Company            
          $ADDepartment = $ADAccount.Department            
        $ADEmployeeNumber = $ADAccount.EmployeeNumber            
        $ADEmployeeType = $ADAccount.EmployeeType            
       $ADLastLogon = $ADAccount.LastLogonTimestamp            
       If ($ADLastLogon -ne "") {            
        #$ConvertedLastLogin = [DateTime]::FromFileTime( [Int64]::Parse($ADLastLogon) )            
       $ADDescription = "Disabled on " + $TerminationDate + " by " + $Requestor            
       $DBReason = $Justification            
       #$Justification = "Inactive computer - Last logon: " + $ConvertedLastLogin            
       If ($ProcessType -eq "HR"){            
        $Justification = "User terminated by HR. Do not reenable unless HR rehires this user. $DBReason"            
       If ($ProcessType -eq "AutoInactive") {            
        $WorkOrder = " "            
        $Notes = "Yes"            
       #Disable Account            
       Get-ADUser -identity $ADSAM -properties *| Disable-ADAccount            
       #Set AD Fields            
       Get-ADUser -identity $ADSAM -properties *|             
         Set-ADUser -Description $ADDescription            
        Get-ADUser -identity $ADSAM -properties *|             
         Set-ADUser -clear info,division,ContosoDisabledDate            
        Get-ADUser -identity $ADSAM -properties *|             
         Set-ADObject -ADD @{info=$Justification;division="$TermDateDivision";ContosoDisabledDate=$ContosoDisableDate}             
        Get-ADUser -identity $ADSAM -properties *|            
         Set-ADuser -replace @{msnpallowdialin=$false}            
        #Move to Pending Delete            
         Get-ADUser -identity $ADSAM -properties *|             
          move-ADObject -TargetPath 'ou=Pending Delete,dc=contoso,dc=org'            
        #ProcessType, Username, Company, Department, Reason, LotusNotes, WorkOrder, Requestor, EmployeeNumber, EmployeeType, DateTime            
        $DBColumns = "ProcessType, Username, Company, Department, Reason, LotusNotes, WorkOrder, Requestor, EmployeeNumber, EmployeeType, DateTime"            
        $DBValues = "'$ProcessType','$ADSAM','$ADCompany','$ADDepartment','$DBReason','$Notes','$WorkOrder','$Requestor','$ADEmployeeNumber','$ADEmployeeType'"            
        Edit-ADProxyDBReport -Table "dbo.DisableUsersReport" -DBColumns $DBColumns -DBValues $DBValues            
        #Clear values at the end            
        $ADAccount = $null            
        $ADSAM = $null            
        $ADCompany = $null            
        $ADDepartment = $null            
        $ADEmployeeNumber = $null            
        $ADEmployeeType = $null            
        $ADLastLogon = $null            
     END { "Complete Disable ADProxy User"}            
    Export-ModuleMember -function Disable-ADProxyUser

    PowerShell Editors

    ​One of the things to consider when working with PowerShell is what editor do you want to use?

    Since I spend a fair amount of time in a script editor on a day to day basis, I have definitely found some things that I prefer.  Here are a few things that I really want in any script editor:

    • Color coding (this is a requirement, but really, any script editor includes this).
    • Auto-complete – this is when it will auto complete something you are typing with the correct information.  Usually using a tab or some other method, it will finish the code properly.
    • Quick and easy commenting – I often play around with different things in my scripts. This means that I may want to quickly select a number of lines and comment them out for testing purposes.
    • Open the editor and have the last scripts I was working on open automatically.  This is really just a convenience for me as I often return to the same scripts and want to keep editing them between sessions.
    • Auto signing of scripts.  I feel it is important to have all of my scripts signed automatically when I save them.  This gives me the ability to make sure that scripts aren’t modified by someone else before running them.  I am in and out of various scripts all the time and don’t want to have to think about the signing process.

    These are just a few of the things that are important to me when it comes to an editor.  I have tried out a number of editors over the years.  My primary one has been PrimalScript 2011 by Sapien Technologies.  I have really loved using it, but upgrading to the latest version is very expensive. 

    I am now starting to play with PowerShell ISE (which comes free from MS and is “in the box”).  Unfortunately, it doesn’t do everything on my must have list for script editors.  This has started me on a quest to see if I can get it to do everything I need.  The nice thing about PowerShell ISE is that it allows you to have Add-Ins to fill missing gaps.  One of the first Add-Ins I am using allows multi-line comment and uncomment and the ability to save state and exit.

    Another piece of the puzzle for me is signing the scripts automatically.  It isn’t quite the built-in ability that I like with PrimalScript, but I think it will do the job for me.

    Performance Testing Slow Startups

    On occasion, I will have users complain that their computer is starting up slow.  Usually the first thing that gets blamed is group policy, but I know that it usually isn’t the primary cause (just the symptom of some other issue). Often, it indicates some issue specific to that machine.  How do I know that?  We have spent time over the years doing some regular performance testing and timing our startups.  This used to involve using a stopwatch, but it can always be difficult to get an accurate time with that.

    Today, we use a tool from the Windows Performance Toolkit (and specifically XPerf) to do our testing.  It will give us an accurate reading of our startup times, as well as helps us troubleshoot those systems with a slow startup.  Here are some example startup times in our environment:


    *WTG is Windows To Go running from a USB key

    You can see from this that if a computer is at a location without a domain controller (one of our corps) the startup time increases but is still typically under 2 minutes.  We do see a startup time delay increase a bit when going over DirectAccess, but it is still well under our three minute mark that we like to see.

    So, how do I use XPerf to do my testing?  Well, rather than retype it all, I want to share an article (that has some good additional links in it) that covers setup and testing scenarios:

    Sending Emails via IBM Notes and PowerShell

    I have been working for awhile now to move a lot of my code over to PowerShell.  I have found it to be very efficient and easy to read (just took a bit of a mentality change to switch over from VBScript).

    In our environment, I have always found it much more reliable to send emails (usually notification emails to end users about expiring passwords or accounts) directly through IBM Lotus Notes instead of just using our SMTP server.  Previously, I worked with my boss and we created a project in Visual Studio calling some of the Notes COM objects to send emails.  I decided to move all that code over and send the email directly from my PowerShell scripts.

    In searching the web, though, I didn’t find too many examples that covered what I wanted to do including sending HTML emails.  Today my boss and I finished working up this code that I have created as a PowerShell module (save it as a .psm1 file) that I can now call from my other scripts. 

    An important note to get this working.  First, you must have IBM Notes client installed on the server where you run the script.  Second, since it is making a COM call, you must also be running PowerShell in x86.  (In newer versions of Windows such as 2012, it does default to x64.)

    Here is the code:

    Function Send-IBMNotesMail 



      Sends Email using IBM Notes.



      This uses a local install of Notes to send emails.  It seems that this is a bit more 

      reliable in our environment than using a regular SMTP server for some reason.  It requires

      Notes to be installed on the computer that this script is being run on.  It calls the 

      Lotus.NotesSession comobject which requires PowerShell to be running in x86 mode.


     .Parameter NotesInstallDir

      The directory where Notes is installed.  It defaults to C:\Program Files (x86)\IBM\Notes.


     .Parameter Password

      The password of the ID file to send email. 


     .Parameter MailFileServer

      The server where the mail file resides.  


     .Parameter MailFile

      The mail file to use to send the email.  


      .Parameter To

      The email address you are sending an email to.


     .Parameter From

      The email address of the sender.


     .Parameter Subject

      The subject line of the email.


     .Parameter Body

      The body of the email in HTML format.


     .Parameter BCC

      (Optional) A BCC recepient.


     .Parameter Attachment

      (Optional) File to attach.



       # Send an email.

       Send-IBMNotesEmail -SenderEmail "" -DestinationEmail "" -EmailSubject "Hello" -EmailBody "Body of the email" -BCC ""









        $NotesInstallDir = "C:\Program Files (x86)\IBM\Notes", 



        $Password = "password", 




        $MailFileServer = "mailserver", 




        $MailFile = "mailfile", 

        [Parameter(Position=0, Mandatory=$true)]  








        [Parameter(Position=2, Mandatory=$true)]  



        [Parameter(Position=3, Mandatory=$true)]  











    #For information on the GetDatabase option:


    $Notes = New-Object -ComObject Lotus.NotesSession 


    $MailDB = $Notes.GetDatabase("$MailFileServer", "$MailFile") 


    If (-not $MailDB.isopen) {

        Write-Host "Couldn't open the Mail Databse...Trying the Cluster Failover Server"

        $dbMail = $notes.GetDatabase("ClusterFailoverServer","$MailFile")




        $doc = $MailDB.CreateDocument()



        $stream = $notes.CreateStream()

        $notes.ConvertMime = $false


        $body = $doc.CreateMIMEEntity()


        $header = $body.CreateHeader("Subject")



        $header = $body.CreateHeader("To")



        $header = $body.CreateHeader("From")



        $header = $body.CreateHeader("Principal")



        If ($BCC -ne "") {

            $header = $body.CreateHeader("BCC")



        #This is to get it so it shows up in sent items properly.

        $Date = Get-Date



        $stream.WriteText( $BodyContent) 




        if ($Attachment -ne "") { 

            $($doc.CreateRichTextItem("Attachment")).EmbedObject(1454, "", "$Attachment", "Attachment")









    Export-ModuleMember -Function Send-IBMNotesMail

    I hope you find this as a useful way to send emails from your scripts.  I’ll come back in a few days with some of my other PowerShell modules that I am working on and how I integrate with this function.


    I guess it is confession time.  I am a recent convert to PowerShell.  After having used VBS scripts for years now, I have recently discovered that I can do so much more using PowerShell using a lot less code.  I have a long ways to go, especially to learn best practices for writing scripts and functions. 

    I thought I would share with you a few scripts that I am using and how they have helped me.  I am assuming the use of PowerShell 3.0 in this case.  (Note, to load a function so you can call it from the PowerShell prompt, you want to “dot-source” it. 


    Have you ever had service accounts that have been around for ages yet no idea what servers they are running on or what they are actually doing?  I stumbled across Get-ServiceAccountUsage and am very impressed with it. Using the following command, I can have it search our Servers OU and tell me every place (whether it is a scheduled task, running a service or even running an IIS Application Pool) this account is in use:

    (Get-AdComputer –Filter * –SearchBase “OU=Servers,DC…”).name | Get-ServiceAccountUsage –UserAccount ‘domain\service.account’


    As user accounts have been moved around through the years and been in various groups, I found that some groups used to be members of protected Admin groups (such as Account Operator or Print Operator).  When this happens, a special flag is set on the account that ensures that security inheritance is turned off on that account.  Just removing a user from those protected groups doesn’t automatically remove this adminCount flag.  Using Set-AdminUser enables you to reset the flag and turn on inheritance.  Note that if the user account should still be protected, it will refresh the adminCount flag at the next security refresh (every 90 minutes or so) so that the accounts that need to be protected are still protected.

    Password Not Required field

    On occasion, I have found some accounts where they are set to not require a password.  It isn’t a major issue as the GUI won’t allow these users to have a blank password, but auditors definitely don’t like to see this.  Here is a one liner to reset any user accounts with the pwdNotRequired AD field set improperly:

    Get-ADUser -searchscope subtree -ldapfilter "(&(objectCategory=User)(userAccountControl:1.2.840.113556.1.4.803:=32))" | Set-ADAccountControl -PasswordNotRequired $false

    and a similar one to do the same for computer accounts:

    Get-ADComputer -searchscope subtree -ldapfilter "(&(objectCategory=Computer)(userAccountControl:1.2.840.113556.1.4.803:=32))" | Set-ADAccountControl -PasswordNotRequired $false

    Disable Computers Using CSV File Input

    On occasion, you might find it necessary to disable a list of computers.  In our environment, when we disable a computer, we do a few things:

    • Disable account
    • Set the description to note when we disabled this computer
    • Mark two fields (info and a custom schema object) with the reason we disable the account and the date we disabled the account.  (The date is used as we have another scheduled task that runs and will automatically delete disabled objects after a certain period of time.)
    • Move the account to a Pending Delete OU.
    • Finally, we also record all of this info in a database for future reference, as well as some of our regular reports on what work is done in AD.
    # ==============================================================================================


    # Microsoft PowerShell Source File -- Created with SAPIEN Technologies PrimalScript 2011


    # NAME: DisableADComputerfromCSV.ps1


    # AUTHOR: Doug Neely , TSA

    # DATE  : 3/1/2013


    # COMMENT: 


    # ==============================================================================================

    Import-Module ActiveDirectory 

    $datetime = Get-Date -Format MM_dd_yy_HH_mm

    $csv = "R:\Scripts\ADPowershell\OldServers.csv"

    $TerminationDate = Get-Date -Format M/d/yyyy

    $TerminationDate = [string]$TerminationDate


    #Import CSV File

    $TerminateCSV = Import-Csv $csv 


    foreach ($Computer in $TerminateCSV) {

        $ADSAM = $


        $ADAccount = Get-ADComputer -identity $ADSAM -properties *

        $ADLastLogon = $ADAccount.LastLogonTimestamp

        $ConvertedLastLogin = [DateTime]::FromFileTime( [Int64]::Parse($ADLastLogon) )

        $ADDescription = "Disabled on " + $TerminationDate + " by This.User"

        $Justification = "Inactive computer - Last logon: " + $ConvertedLastLogin

        $ADOS = $ADAccount.OperatingSystem

        $ADOSSP = $ADAccount.OperatingSystemServicePack

        $ADFullOS = $ADOS + " " + $ADOSSP

        $Requestor = "This.UserSA"

        $DBDateTime = Get-Date


        #Disable Account

        Get-ADComputer -identity $ADSAM -properties *| Disable-ADAccount

        #Set AD Fields

        Get-ADComputer -identity $ADSAM -properties *| 

            Set-ADComputer -Description $ADDescription

        Get-ADComputer -identity $ADSAM -properties *| 

            Set-ADObject -clear info,tsaDisabledDate

        Get-ADComputer -identity $ADSAM -properties *| 

            Set-ADObject -ADD @{info=$Justification;tsaDisabledDate=$TerminationDate} 

        #Move to Pending Delete

        Get-ADComputer -identity $ADSAM -properties *| 

            move-ADObject -TargetPath 'ou=Pending Delete,dc=yourdomain,dc=org'


         #SQL Database Record information

        #Datafields: ProcessType, ComputerName, OS, Reason, Requestor, DateTime

        $Server = "ServerName"

        $Database = "DBName"

        $UserID = "User"

        $UserPassword = "Love really long passwords for others to use."

        $Table = "dbo.DisableComputersReport"

        $DateTimeRequest = (Get-Date).ToString("yyyy-MM-dd HH:mm:ss.sss")

        $Connection = New-Object System.Data.SQLClient.SQLConnection

        $Connection.ConnectionString = "server=$Server;Initial Catalog=$Database;User ID=$UserID;Password=$UserPassword;"


        $Command = New-Object System.Data.SQLClient.SQLCommand

        $Command.Connection = $Connection

        $Command.CommandText = "INSERT INTO $Table (ProcessType, ComputerName, OS, Reason, Requestor, DateTime) VALUES ('ADCleanup','$ADSAM','$ADFullOS','$Justification','Test.UserSA','$DBDateTime')"





    These are a few PowerShell scripts/codes that I have found useful recently.  Hope you find some of these useful as well.


    One of the exciting new technologies we are now in the process of testing is called DirectAccess.  DirectAccess allows a user/computer to be actively connected to the domain network no matter where in the world that user is.  It doesn’t matter if the user is at Starbucks on their wireless network, at a hotel or even at home.  This secure connectivity works from the moment the computer is turned on allowing it to receive all the latest group policies, as well as a user to connect to mapped drives, printers or other internal resources.

    In order for DirectAccess to work, it requires a number of different technologies.  Here are the technologies we are using.

    • IPv6 – all traffic for DA uses IPv6
    • Certificate Authority
    • Windows 7 Enterprise/Ultimate
    • Windows 2008 R2 – This is a server with two nics – one on the internet and one on the intranet
    • Group Policy
    • IPSEC
    • Unified Access Gateway – This is an add-on product from Microsoft allowing us to connect to IPv4 resources.

    Windows Server 8 is coming out with some changes to this, but for now, this is what we have running.

    I have found that once a user has this, it is one of those technologies that you just can’t live without.  It becomes such a part of your normal routine to be able to connect to every resource, that when it isn’t working it is very noticeable.

    Microsoft has a downloadable client to make it easy to verify if access is working.  This is called the DirectAccess Connectivity Assistant (DCA) (downloadable here: ).  This is a great tool that allows you to see if DA is working and generate logs for troubleshooting.  (I have used these logs often to find issues with DA.)

    Here are a few issues that we have found as we have implemented this:

    • Computers newly joined to the domain may not have their computer certificate yet.  (In our environment, it requires group membership and workstations aren’t added to the necessary group until a process runs every night.)
    • For some of our HP laptops, we have found that when they wake from sleep they are unable to connect to the DA server.  Usually running an ipconfig /flushdns takes care of the issue.
    • One other issue we have run across is when some of our users play with the setting on the DCA to change it to use local DNS only.  We have found that sometimes this setting gets “stuck” and they cannot connect to internal resources.  (Making the change to only use local DNS is unnecessary in our environment as our DA is already setup to do split tunneling and only send traffic & DNS to our servers for internal resources).  In this case, we have found that it is necessary to do the following. 

    If you haven’t had a chance to play with DA, I would highly recommend it.  Windows 8 is going to make it even easier by changing some of the requirements (including the need for a certificate authority server and an external NIC with two sequential IP addresses).  Stick around, as it will be a good thing!

    They say a picture is worth a thousand words, so here is a diagram of how DA works:


    Migrating SYSVOL to DFS-R Replication

    When working with Active Directory, there are a few different replication systems in place.  One of the key replications is that of the SYSVOL as this replicates the group policy settings throughout a domain.  SYSVOL replication is a bit different from regular AD replication as it replicates files used by SYSVOL (as opposed to individual items contained in the AD database).

    Originally with Windows 2000, Microsoft developed the File Replication System (FRS) as a method to replicate the SYSVOL.  For anyone who has used FRS replication over the years, though, you know that it can have problems.  For example, there is the dreaded Journal Wrap error (  Also, the monitoring tools with FRS are limited.  There are some free tools developed by MS such as Ultrasound and Sonar, but these tools are limited and don’t always seem to work well.

    With the release of 2003 R2, MS developed a new replication model using Distributed File System Replication (DFS-R).  DFS-R has a number of advantages over FRS replication:

    • Uses block-level replication – only replicates blocks of a file that has changed rather than the entire file.
    • Better reporting and troubleshooting tools – using DFS Management console, it is easy to produce reports detailing the replication and any errors/warnings.
    • Bandwidth throttling – is sensitive to bandwidth and throttles replication based upon available bandwidth.

    Now that we are running Windows 2008 R2 on all of our DCs (and are at domain functional level of 2008 R2 – although it would work fine with 2008, as well), we are now able to take advantage of DFS-R for replicating our SYSVOL.  Besides the above advantages, it is also important to note that

    Prior to making any changes to our replication model, we first want to verify that FRS replication is working properly.  In our environment, I have Ultrasound running and found a few errors.  Once those errors were resolved, I was ready to begin the process of moving to DFS-R replication for the SYSVOL.  To complete this process, we complete three steps:

    • Prepared State – This is where it will initially replicate the SYSVOL to all of your DCs using DFS-R replication.  It creates a new folder on your DC (SYSVOL_DFSR).  Once at this stage, you can verify that replication is working properly by running diagnostic reports.
    • Redirected State – When moving to this state, your SYSVOL share now points to the new SYSVOL_DFSR folder so your clients will now retrieve their group policies from the new share.  This step can be reversed if you run into issues.
    • Eliminated State – This step eliminates the dependency of the NTDS service and the FRS service and deletes the SYSVOL folder.  This step cannot be reversed!

    I recommend reading the resources below for more information and steps to complete at each state change. 

    Once you have completed your migration, you can now have an email sent to you on a schedule about the health of your DFS-R environment.  See for details on setting this up.



    —————————————————————————————– – Technet Guide to SYSVOL Migration – Ask DS FAQ on SYSVOL migration – MS Windows: Make the Move to DFS

    Find Sites with No Site-Links

    With the large environment we have (with 574 sites), it is easy to have items that are misconfigured.  One of the things that I have always wondered how to do is find sites that don’t have any site-links.  Today, the Ask the Directory Services Team blog addressed this very issue.  In the blog, they provide details on how to run a series of powershell commands to find sites with no site-links.

    I ran this in our forest and found seven sites with no site links!  Now I know what I need to fix!

    Windows Fine Grained Password Policy

    One of the great things that is introduced in Windows 2008 is the introduction of Fine Grained Password Policies.  What is FGP? With a Windows 2000/2003 domain, it has only been possible to have a single password policy that applied to everyone in your domain.  Most domains, though, do have some accounts that have higher privileges that they might want to have a stricter password policy for those specific accounts.  That is what FGP does is allow you to have different password policies for different users.  In our domain, we have an established policy requiring a stricter password policy (minimum 15 characters for length), but have never been able to enforce this policy.  FGP now allows us to enforce this policy.  (NOTE:  FGP can only be implemented when your domain functional level is at 2008 or later.)

    So, how does this work?  Well, you need to create a Password Settings object (PSO) in the System container of the domain.  There are a couple of different ways to do so:

    • Powershell – Use a script to create and set the PSO.
    • Specops Password Policy Basic – Use a free GUI to create the PSO and verify that it is setup properly.

    I chose to use Specops free utility to set this policy.  After installing the application, it is easy to open up the tool to change and view any policies.


    The Specops tool also allows you to lookup the password policy for an individual user to verify that it is applied properly.


    References – Specops Password Policy Basic information – AD DS Fine Grained Password Policy information

    Domain is Now 2008 R2 Functional Level!

    After a lot of work in upgrading our domain controllers, I have finally completed upgrading all of our DCs to Windows Server 2008 R2!  Now that I have done so, I am ready to begin enjoying some of the benefits of having all 2008 R2 DCs.  So, for me, the first step is to turn on Windows Server 2008 R2 Domain Functional Level.  This is an easy process of going into Active Directory Domains and Trusts, selecting my domain and Raise Domain Functional Level. 

    What benefits do I get by upgrading my Domain Functional Level?  Here are some of the highlights (note that I differentiate which benefit comes from the different Domain Functional Levels – DFL):

    • DFS Replication for SYSVOL – Currently, my domain uses the older File Replication System (FRS) to replicate the SYSVOL between DCs.  Microsoft has invested a lot of research in upgrading the Distributed File System starting in 2003 R2 and newer operating systems.  Now, I can begin to take advantage of these new technologies to begin to do bit level replication of files and hopefully never see a journal wrap error again!  In a later blog article, I will detail the steps that I go through to make this change (as it is about three major steps to complete this process).  (DFL 2008)
    • Access Based Enumeration on DFS File servers running Windows Server 2008 (DFL 2008)
    • New encryption standards support for Kerberos (AES 128 and AES 256) (DFL 2008)
    • Last Interactive Logon to display who last interactively logged on with your account and where. (DFL 2008)
    • Fine-grained Password Policies – This is one of the things I have been looking forward to as part of this upgrade.  We have, for a long time, required by policy that our administrative accounts must have passwords 15 characters in length.  Unfortunately, there has been no way to enforce this.  Now, I will be able to enforce this written policy with policy in AD by having a unique password policy for those specialized accounts.  (DFL 2008)
    • Personal Virtual Desktops – Not sure that we will be using this anytime soon.  (DFL 2008)
    • Authentication mechanism assurance – Allows you to know whether a user logged on with a smart card or user name/password. (DFL 2008 R2)
    • Automatic SPN management for Managed Service Accounts (DFL 2008 R2)

    Another thing that is not generally listed in any of this documentation is that you can now convert your DFS namespaces from Windows 2000 namespaces to 2008 namespaces.  There are a number of enhancements that come with increasing the namespace to 2008 including better performance for a large number of targets (over 3,000 targets in a 2000 namespaces begins to see performance issues).

    As I begin to implement some of these various features in our domain, I will continue to blog about these changes and some of my experiences to implement them.


    Resources: – Domain and Forest Functional Levels – Information about DFS changes in 2008