Friday, March 8, 2013

PowerShell: Find Old Accounts and Passwords

Yesterday, I got tasked with helping find all users accounts in an Active Directory domain that are older than four years and haven't changed their password or have passwords older than four years. The domain in question had only starting using password complexity four years ago and wanted an audit of which accounts might be non-compliant. I spent some time figuring out the proper syntax to the search filter but at least I know it now. Below is the script. Enjoy.

<#
Script Name: Find_Old_Timers.ps1
Version: 1.0
Author: Dean Bunn
Last Edited: 03/08/2013
Description: Finds Old AD Accounts (in specific OUs) Older than Four Years Old
             with Passwords that have Never been Changed
             or are Older than Four Years Old
#>

#Create DateTime Object for Four Years Ago
$dt4YrsAgo = (Get-Date).AddYears(-4);

#Var for Converted File Time Used for Password Last Set Filter 
$ftPLS = $dt4YrsAgo.ToFileTime().ToString();

#Constructing When Created Filter(Should Look Like 20130302000000.0Z)
$wcYear = $dt4YrsAgo.Year.ToString();
$wcMonth = "{0:D2}" -f $dt4YrsAgo.Month;
$wcDay = "{0:D2}" -f $dt4YrsAgo.Day;
$sWC = $wcYear + $wcMonth + $wcDay + "000000.0Z";

#Reporting Array
$report = @();

#Var for Showing Progress
$x = 0;

#Array of OUs to Check Against
$arrOUs = @("LDAP://OU=External,DC=MyCollege,DC=edu",
            "LDAP://OU=Students,DC=MyCollege,DC=edu",
            "LDAP://OU=Staff,DC=MyCollege,DC=edu");

foreach($ouADsPath in $arrOUs)
{
    
    #Directory Entry for OU to Search
    $deOU = [adsi]$ouADsPath;
    
    #Search OU for All User Accounts Meeting the Search Criteria
    $dsSearch = New-Object DirectoryServices.DirectorySearcher($deOU);
    $dsSearch.filter = "(&(objectClass=user)(sAMAccountName=*)(whenCreated<=$sWC)(|(pwdlastset<=$ftPLS)(pwdlastset=0)(pwdlastset=9223372036854775807)))";
    $dsSearch.PageSize = 900;
    $dsSearch.SearchScope = "SubTree";
    $srResults = $dsSearch.Findall();
        
    #Loop Through All Search Results
    foreach($srResult in $srResults)
    {   
    
        $x++;
        Write-Output $x.ToString();
        
        #Retrieve DirectoryEntry for the User Account
        $deADUser = $srResult.GetDirectoryEntry();
        
        #Null Check on the DirectoryEntry Object
        if($deADUser)
        {
            #Create Custom Object for Reporting
            $cstPCUser = New-Object PSObject;
            $cstPCUser | Add-Member -MemberType NoteProperty -Name "UserID" -Value "";
            $cstPCUser | Add-Member -MemberType NoteProperty -Name "UPN" -Value "";
            $cstPCUser | Add-Member -MemberType NoteProperty -Name "CN" -Value "";
            $cstPCUser | Add-Member -MemberType NoteProperty -Name "UAC" -Value ""
            $cstPCUser | Add-Member -MemberType NoteProperty -Name "PasswordChanged" -Value "";
         $cstPCUser | Add-Member -MemberType NoteProperty -Name "LastLoginTimeStamp" -Value "";
            
            #Pull Basic Account Information
            $cstPCUser.UserID = $deADUser.sAMAccountName.ToString().ToLower();
            $cstPCUser.UPN = $deADUser.userPrincipalName.ToString().ToLower();
            $cstPCUser.CN = $deADUser.cn.ToString();
            
            #Check Last Password Change
            if($srResult.Properties["pwdlastset"][0].ToString() -ne "9223372036854775807" -and $srResult.Properties["pwdlastset"][0].ToString() -ne "0")
            {
                $cstPCUser.PasswordChanged = ([System.DateTime]::FromFileTime($srResult.properties["pwdlastset"][0])).ToString();
            }
            else
            {
                $cstPCUser.PasswordChanged = "Not Set";
            }
            
            #Account Status Check
            if($deADUser.userAccountControl)
            {
                
                switch([int]($deADUser.userAccountControl.ToString()))
                {
                   512 {$cstPCUser.UAC = "Enabled"}
                   514 {$cstPCUser.UAC = "Disabled"}
                   520 {$cstPCUser.UAC = "Enabled"}
                   522 {$cstPCUser.UAC = "Disabled"}
                   544 {$cstPCUser.UAC = "Enabled"}
                   546 {$cstPCUser.UAC = "Disabled"}
                   66048 {$cstPCUser.UAC = "Enabled"}
                   66050 {$cstPCUser.UAC = "Disabled"}
                   66080 {$cstPCUser.UAC = "Enabled"}
                   66082 {$cstPCUser.UAC = "Disabled"}
                   8388608 {$cstPCUser.UAC = "Password Expired"}
                   default {$cstPCUser.UAC = "unknown"}
                }
            }
       
               #Convert Last Logon Timestamp (If Exists)
            if($srResult.Properties["lastlogontimestamp"])
            {
                $cstPCUser.LastLoginTimeStamp = ([System.DateTime]::FromFileTime($srResult.Properties["lastlogontimestamp"][0])).ToShortDateString();
            }
            
            #Add Custom Object to Report Collection
            $report += $cstPCUser;
            
        }#End of $deADUser Null Check
        
    }#End of $srResults Foreach
    
}#End of $arrOUs Foreach


#Get Current Short Date
$rptFileDate = Get-Date -Format d;
        
#Var for CSV File Name
$fileName = "Old_Timers_Password_Change_Report_" + $rptFileDate.ToString().Replace("/","-") + ".csv";
        
#Export CSV Report
$report | Sort-Object UserID | Export-CSV $fileName -NoTypeInformation ;

###### End of Script ###################




Thursday, March 7, 2013

ADMT: "Unable to establish a session with the password export server"

About a month ago, I ran into an issue when using Microsoft's Active Directory Migration Tool (ADMT) between a Windows 2003 and a Windows 2008 R2 domain when using the Password Export Server. The error I received was:

"Unable to establish a session with the password export server. Either the currently logged on user does not have sufficient permissions to call the Password Export Server or the account that the Password Export Server Service is running under does not have sufficient permissions on the target domain controller. Verify that the logged on user is a member of the Administrators group in the source domain and that the Password Export Server Service account can change passwords of user accounts in the target domain."


Well after a few hours of working with Microsoft Support, we figured out the issue was due to PAC Validation and that we needed to make registry change on the source DC running the Password Export Server. 

Here is the registry change:

[HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Lsa\Kerberos\Parameters]
"ValidateKdcPacSignature"=dword:00000000


Tuesday, March 5, 2013

PowerShell: After ADMT Password Migration Fix

If you used Microsoft's Active Directory Migration Tool (ADMT) for any major migrations, then you know that migrated AD accounts are always set for user must change password upon next login. Well in the target domain we didn't want this setting set due to were migrating the password from the old domain and had complexity filter set so ADMT logs would tell us which accounts didn't meet the password criteria.

Below is the PowerShell code that met our after ADMT user migration fix needs. I used a switch statement on UAC so that account enabled status remained the same. Be advised that this code will set the user account so that user cannot change their password.

<#
Script Name: AD_After_ADMT_User_Migration_Fix.ps1
Version: 1.0
Author: Dean Bunn
Last Edited: 03/03/2013
Description: Corrects ADMT Account Changes Regarding Password Settings
#>

#Create NTAccounts for SELF and Everyone
$ntaSelf = New-Object System.Security.Principal.NTAccount("NT AUTHORITY","SELF");
$ntaEveryone = New-Object System.Security.Principal.NTAccount("Everyone");

#AD Security Types
$actDeny = [System.Security.AccessControl.AccessControlType]::Deny;
$adrER = [System.DirectoryServices.ActiveDirectoryRights]::ExtendedRight;

#GUID for Change Password AD Property
$gapCP = [Guid]"ab721a53-1e2f-11d0-9819-00aa0040529b";

#Create AD Deny Rules
$adrlDSPC = New-Object System.DirectoryServices.ActiveDirectoryAccessRule ($ntaSelf,$adrER,$actDeny,$gapCP);
$adrlDEPC = New-Object System.DirectoryServices.ActiveDirectoryAccessRule ($ntaEveryone,$adrER,$actDeny,$gapCP);

#Array of OUs to Run Against
$adOUs = @("OU=People,DC=myCollege,DC=edu",
           "OU=External,DC=myCollege,DC=edu");

#Var for Counting Accounts Checked
$nActCkd = 0;

foreach($adOU in $adOUs)
{
    #Var for OU ADsPath
    [string]$ouADsPath = "LDAP://" + $adOU;
    $deADOU = [ADSI]$ouADsPath;
    $dsSearch = New-Object DirectoryServices.DirectorySearcher($deADOU);
    $dsSearch.filter = "(&(objectClass=user)(sAMAccountName=*)(!objectClass=computer)(!objectClass=contact))";
    $dsSearch.PageSize = 900;
    $dsSearch.SearchScope = "SubTree";
    $srResults = $dsSearch.Findall();
    
    #Loop Through Search Results
    foreach($srResult in $srResults)
    {
        #Null Check on Search Result
        if($srResult)
        {
            #Increment Counting and Display Current Number
            $nActCkd++
            Write-Output $nActCkd.ToString();
            
            #Pull Directory Entry
            $deADUser = $srResult.GetDirectoryEntry();
            
            #Null Check on Directory Entry for User
            if($deADUser)
            {
                $deADUser.psbase.ObjectSecurity.AddAccessRule($adrlDSPC);
                $deADUser.psbase.ObjectSecurity.AddAccessRule($adrlDEPC);
                $deADUser.psbase.commitchanges();
                
                #Set Account to Not Expire (If Necessary)
                #$deADUser.accountExpires = 0;
                #$deADUser.setInfo();
            
                #Var for Account Status 
                [int]$uUAC = [int]::Parse($deADUser.userAccountControl.ToString());
        
                #Check for UAC Setting.
                switch($uUAC)
                {
                
                    512
                    {
                        #Set Password Never Expires
                        $deADUser.userAccountControl = 66048;
                        $deADUser.setInfo();
                    }
                
                    514
                    {
                        #Set Password Never Expires
                        $deADUser.userAccountControl = 66050;
                        $deADUser.setInfo();
                    }
                    
                    544 
                    {
                        #Set Password Never Expires
                        $deADUser.userAccountControl = 66048;
                        $deADUser.setInfo();
                    }
                    
                    546
                    {
                        #Set Password Never Expires
                        $deADUser.userAccountControl = 66050;
                        $deADUser.setInfo();
                    }
                    
                    66080 
                    { 
                        #Set Password Never Expires
                        $deADUser.userAccountControl = 66048;
                        $deADUser.setInfo();
                    }
                    
                    66082 
                    { 
                        #Set Password Never Expires
                        $deADUser.userAccountControl = 66050;
                        $deADUser.setInfo();
                    }
                        
                }#End of userAccountControl Switch
                
            }#End of Null Check on $deADUser
            
        }#End of Null Check on $srResult
        
    }#End of $srResults Foreach
    
}#End of $adOUs Foreach