User exists script across multiple forest/domain

Answered User exists script across multiple forest/domain

  • Thursday, September 27, 2012 6:03 PM
     
      Has Code

    Due to numerous mergers and acquisitions there are a number of domains on our networks. Since consolidation doesn't go as quickly as one would like we have a number which we continuously managed. There are trusts in place. When there is a termination of an employee for security reasons we go through all the domains to verify if accounts for the employee exist, and then delete them.

    With my beyond basic scripting ability have been trying to create a powershell script that will connect to each domain, search for the user, and dump results to a file. It works when checking the domain which I am currently on, but doesn't seem to work when connecting to other domains.

    function VerifyUser ([string]$FirstName,[string]$LastName,[string]$Domain,[string]$DomainUser,[string]$DomainPW){
        # Filter results based on user
        $strFilter = "(&(objectcategory=user))"
    
        # Connect to LDAP using provided credntials
        $objDomain = New-Object System.DirectoryServices.DirectoryEntry $Domain,$DomainUser,$DomainPW
    
        $objSearcher = New-Object System.DirectoryServices.DirectorySearcher
        $objSearcher.SearchRoot = $objDomain
        $objSearcher.PageSize = 1000
        $objSearcher.Filter = $strFilter
        $objSearcher.SearchScope = "Subtree"
    
        $colProplist = "name"
        foreach ($i in $colPropList){$objSearcher.PropertiesToLoad.Add($i)}
    
        $colResults = $objSearcher.FindAll()
    
        foreach ($objResult in $colResults)
            {$objItem = $objResult.Properties;
            
            # Split the name information
            $sSplit = $objItem.name
            $sSplit = $sSplit -split " "
            
            # Run through array of split variables
            foreach ($anArray in $sSplit){
                if (($anArray -eq $FirstName) -OR ($anArray -eq $LastName)){
                    # $found = $true
                    $Output = $FirstName + "," + $LastName + "," + $Domain
                    
                    # Define report file name and location
                    $sFilePath = "c:\term_user_report.csv"
                    
                    # Verify if report exists
                    if ([IO.File]::Exists($sFilePath)) #-ne $true)
                    # Write $Output to file
                    {Add-Content $sFilePath $Output}
                    # Create file if it doesn't exist, and add content
                    else
                    {$file = new-item -type file $sFilePath
                    Add-Content $sFilePath $Output}    
                  }   
                }
            }    
    }
    
    # domain connection details
    $server1 = "LDAP://DC=domain,DC=corp,DC=companyname,DC=com"
    $server2 = "LDAP://DC=domain,DC=local"
    $server3 = "LDAP://DC=domain"
    
    $DomainArray = @($server1,$server2,$server3)
    
    # Grab user list
    $FileLocation = Read-Host "Please provide the location of the user list .csv file"
    $UserList = Import-CSV $FileLocation
    
    Foreach ($Domain in $DomainArray){
        # Prompt for user who will have permissions to access the noted domain
        $DomainUser = Read-Host "Please enter a user ID who has domain polling ability for 
        " $Domain
    
        # Prompt user for password to access domain, but encrypt the text for security purposes
        $DomainPW = Read-Host -assecurestring "Please enter the password"
        $DomainPW = [System.Runtime.InteropServices.Marshal]::PtrToStringAuto([System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($DomainPW))
       
        # Call verify function and cycle through list of users
        Foreach($Person in $UserList){
        $Person.FirstName
        $Person.LastName
        VerifyUser $Person.Firstname $Person.LastName $Domain $DomainUser $DomainPW
        }
    }

All Replies

  • Friday, September 28, 2012 5:17 AM
     
     Answered

    Haven't delved into this, but at work we have same issue and are frequently forced to manage computers that our root domain admin do not (yet) have rights on.  To combat this, we use a legacy admin account from each sub-domain to connect. 


    Don't forget to mark your posts as answered so they drop off the unanswered post filter. If I've helped you and you want to show your gratitude, just click that green thingy.


  • Friday, September 28, 2012 3:26 PM
     
     

    Sounds like we are experiencing the same problem. We have admin accounts on each domain similar to what you've mentioned. When I stated we have trusts configured I might have been a little too vague. Although we have trusts setup I don't believe it is configured between all the domains. For that I have the script prompt for login information.

    I realize I didn't clarify exactly what my script is built to do so I'll try to do a better job of that.

    The script will prompt the user for the location of a csv file which contains the first and last name of the users which will be verified. The csv file has a header of firstname,lastname.

    Then it will prompt for login credentials which will be used to access the domain and list the users.

    Once it has the list of users it will verify if there are any matches for the first name or last name and print a notice that a match has been found to file

    The script is expected to provide false positives as typically the user account doesn't exist on the domains. I plan on changing the output results to be more helpful in the future.

    Right now I'm having issues where I can scan the domain I am currently in, but get errors when trying to scan the other domains.

    Error received:

    Exception calling "FindAll" with "0" argument(s): "A referral was returned from
     the server.
    "
    At U:\@Systems Admin\Projects\TermUser Script\Term User.ps1:17 char:39
    +     $colResults = $objSearcher.FindAll <<<< ()
        + CategoryInfo          : NotSpecified: (:) [], MethodInvocationException
        + FullyQualifiedErrorId : DotNetMethodException

  • Friday, September 28, 2012 4:58 PM
     
     Answered Has Code

    Start by ignoring the script and just try to get one item from the remote domain.

    $user=[adsi]'LDAP://cn=some user,ou=comeeou...,dc=subdom,dc=dom,dc=com'

    Can you run this using the DN of a known user account.  If 'guest' has not been renamed that is a good one to try.

    Example:

    PS>$user=[adsi]'LDAP://cn=guest,cn=users,dc=dom1,dc=local'
    PS>$user.name
    Guest

    Do this for the guest account in each domain.

    This will tell you if you have connectivity.  If there is a trust you should have no issues querying the guest account.

    If you have issues then I will post a two line test for protocol connectivity.  Workstations will not be able to gain remote authentication if you cannot use Kerberos to connect.  Workstations and domain member servers are allowed to authenticate within the domain using NTLM.  To authenticate on a remote domain with a remote account you must be able to authenticate using Kerberos unless AD has been modified.

    You could also do a Global Catalog query for all matches on the user name.

    [adsisearcher]'(&(sn=smith)(given=joe))'

    Here is how to search the Global Catalog for all accounts matching certain criteria:

    $searcher=[adsisearcher]'(&(sn=user2)(givenName=test))'
    # use either forest root or local domain
    # the key is to use 'GC' instead of 'LDAP'
    $searcher.SearchRoot='GC://dc=myforest,dc=com'
    $searcher.FindAll()

    Now you can just update the filter and rerun FindAll.


    ¯\_(ツ)_/¯




  • Monday, October 08, 2012 4:41 PM
    Moderator
     
     Answered Has Code

    jv makes some good points:

    • You should be able to query a GC in each domain.
    • You should query for users that have the specified first and last names, rather than query repeatedly for all users and enumerate then all.

    In addition, it can be difficult (or impossible) to find users by first and last names. I assume you have no alternative (like the "pre-Windows 2000 logon" name, which is unique in each domain). Also, you assume the first and last names appear in the Common Name, but if cn is in the format "LastName, FirstName", then when you parse the names you will get "LastName," with the trialing comma.

    If you know the givenName and sn attributes are populated (unlikely), you can use the filter:


    (&(givenName=$FirstName)(sn=$LastName))

    -----

    Or, if you know that Common Names are in the format of either "FirstName LastName" or "LastName, FirstName", you can filter on either possibility using:


    (|(cn=$FirstName $LastName)(cn=$LastName, $FirstName))

    -----

    But I really recommend you use Ambiguous Name Resolution, which I document here:

    http://www.rlmueller.net/AmbiguousNameResolution.htm

    Then you can use the following filter:


    (anr=$FirstName $LastName)

    -----

    However, I think your problem is the inability to run any query on the remote domains. I find it often helps to specify a domain controller in the remote domain in the base of the query. I would select a DC that is also a GC. For example:


    $server1 = "GC://Server1/DC=domain,DC=corp,DC=companyname,DC=com"
    $server2 = "GC://Server2/DC=domain,DC=local"
    $server3 = "GC://Server3/DC=domain"

    -----

    In my example above I specified the NetBIOS names of the DC's. You might need to specify the dns names, such as "Server1.domain.corp.companyname.com". I hope this helps.


    Richard Mueller - MVP Directory Services

  • Monday, October 08, 2012 5:30 PM
     
     

    Thank you both Richard, and JRV. I am toying with this in my down time, and am very thankful for the help being provided. I did simplify and try to just connect to the domains and found that was problematic.

    I can't wait to test out the ARN & putting the FQDN in the search string (really think it will work this time!). I'll post the code if all starts to work. Thank you again!

  • Tuesday, January 29, 2013 9:18 PM
     
      Has Code

    Update as I have made numerous changes to the code which wouldn't have happened without everyone's help. I was able to get the original script running, but realized there was a flaw in how I was searching for users. The original script would verify if the user existed if the name was exactly as provided, and given the environments that I'm dealing with I can't expect that to be correct.

    My goal here is to have a script that will end up providing more false positives than one would typically like. The script is only meant to verify and not take action. Action will be taken manually for the time being.

    Here is what I'm having trouble with. I would like to search the domain for matches to the first name OR the last name. Figuring if I match either or both I'll state they have been found, and if neither are found they must not exist. Now I know the way I'm doing this isn't working because the value returned is null, but I'm not quite sure how to get the desired result. I've included the search code which works, and the search code that doesn't. I could split the searching up but I'm concerned about running so many searches... Or am I already doubling up on the searching as is?

                

    #Code below works, but only if 100% match

    Foreach($Person in $UserList)
        {
            $Person.FirstName
            $Person.LastName

            $aSearch = get-qaduser -anr $FirstName $LastName

            if($aSearch)
                {$Output = $aSearch.name + " found in " + $Domain}
            else 
                {$Output =  $FirstName + $LastName + "not found in " + $Domain}

        }

    #Code below doesn't work due to get-qaduser returning null value

    Foreach($Person in $UserList) { if((get-qaduser -Anr $Person.FirstName) -or (get-qaduser -Anr $Person.LastName)) { $Output += $Person.FirstName + " " + $Person.LastName + ",found in," + $Domain + "`n" } else { $Output += $Person.FirstName + " " + $Person.LastName + ",not found in," + $Domain + "`n"} }




    • Edited by OT_Menafro Tuesday, January 29, 2013 9:33 PM Output statements were flipped
    •  
  • Tuesday, January 29, 2013 10:51 PM
     
      Has Code

    Try it thsi way:

    $results=Foreach($Person in $UserList){        
       get-qaduser -Anr $Person.FirstName 
       get-qaduser -Anr $Person.LastName
    } |
       ForEach-Object{
            '{0} {1} ,found in, {2}' -f $Person.FirstName, $Person.LastName, $Domain
        }


    ¯\_(ツ)_/¯


  • Tuesday, January 29, 2013 10:55 PM
     
      Has Code
    This too:
    Foreach($Person in $UserList){        
       if((get-qaduser -Anr $Person.FirstName) -or (get-qaduser -Anr $Person.LastName)){
            Write-Host ('{0} {1} was found in, {2}' -f $Person.FirstName, $Person.LastName,$Domain) -fore green
       }else{
            
            Write-Host ('{0} {1} was NOT found in, {2}' -f $Person.FirstName, $Person.LastName, $Domain) -fore red
       }
    }

    ¯\_(ツ)_/¯


  • Tuesday, January 29, 2013 11:16 PM
     
      Has Code

    Sorry - I had a mistake in that last poost which I fixed.

    Foreach($Person in $UserList){        
       if((get-qaduser -Anr $Person.FirstName) -or (get-qaduser -Anr $Person.LastName)){
            Write-Host ('{0} {1} was found in, {2}' -f $Person.FirstName, $Person.LastName,$Domain) -fore green
       }else{
            
            Write-Host ('{0} {1} was NOT found in, {2}' -f $Person.FirstName, $Person.LastName, $Domain) -fore red
       }
    }
    This was tested repeatedly and works as needed.

    ¯\_(ツ)_/¯

  • Wednesday, January 30, 2013 2:45 PM
     
     
    Thank you! For some reason I thought I wasn't able to verify if the get-qaduser request was true/false unless it was in the if statement. I made a slight change to what you provided and I've got a script that does enough to make our lives just a little easier. I've learned a good bit during this as I am by no means a programmer. Ok, time to post the full code!
  • Wednesday, January 30, 2013 3:01 PM
     
     Answered Has Code

    Here is my completed (for now) code. Special Thanks to jrv and Richard Mueller for your time and patience!

    What does it do?

    Opens up a windows dialog box allowing the user to select a file (suggests CSV) which contains the list of users who need to be searched.

    Prompts for user credentials for each domain (hard coded in the script), and verifies them against the domain (* Domain verification code taken from David Martin serverfault(dot)com/a/286003 (sorry, but it wouldn't let me link?))

    Searches the domain and verifies if some, all, or none of the name matches and outputs the results to a csv file

    function Test-Credential {
    <#
        .SYNOPSIS
            Takes a PSCredential object and validates it against the domain (or local machine, or ADAM instance).
    
        .PARAMETER cred
            A PScredential object with the username/password you wish to test. Typically this is generated using the Get-Credential cmdlet. Accepts pipeline input.
    
        .PARAMETER context
            An optional parameter specifying what type of credential this is. Possible values are 'Domain','Machine',and 'ApplicationDirectory.' The default is 'Domain.'
    
        .OUTPUTS
            A boolean, indicating whether the credentials were successfully validated.
    
        #>
        param(
            [parameter(Mandatory=$true,ValueFromPipeline=$true)]
            [System.Management.Automation.PSCredential]$credential,
            [parameter()][validateset('Domain','Machine','ApplicationDirectory')]
            [string]$context = 'Domain'
        )
        begin {
            Add-Type -assemblyname system.DirectoryServices.accountmanagement
            $DS = New-Object System.DirectoryServices.AccountManagement.PrincipalContext($context) 
        }
        process {
            $DS.ValidateCredentials($credential.UserName, $credential.GetNetworkCredential().password)
        }
    }# End Function Test-Credential
    
    $yourdomain = @("yourdomain\","x.x.x.x") # x.x.x.x = IP
    $otherdomain = @("otherdomain\","x.x.x.x")
    
    # Create two array's using the domain information
    # DomainConStr array contains the domain name which will be populated on the login prompt
    # aDomain array contains the IP for the domain controller in the specified domain
    $DomainConStr = @($yourdomain[0],$otherdomain[0])
    $aDomain = @($yourdomain[1],$otherdomain[1])
    
    # GUI file name selection function
    Function Get-FileName($initialDirectory){   
      
     [System.Reflection.Assembly]::LoadWithPartialName("System.windows.forms") | Out-Null
     
     [System.Windows.Forms.MessageBox]::Show("Instructions:
     This script expects a .csv file with a header and details configured as follows:
     Firstname,Lastname
     
     You will then be prompted to provide your login credentials for each domain being verified", "Domain User Checker")
     
     $OpenFileDialog = New-Object System.Windows.Forms.OpenFileDialog
     $OpenFileDialog.initialDirectory = "c:\"
     $OpenFileDialog.filter = "CSV Files (*.csv)|*.csv"
     $Show = $OpenFileDialog.ShowDialog() #| Out-Null
     $OpenFileDialog.filename
    
    
    If ($Show -eq "Cancel"){
    	Write-Host "Operation cancelled by user."
    	exit
    }
    } #end function Get-FileName
    
    # *** GUI file name function Entry Point to Script ***
    $FileLocation = Get-FileName
    $UserList = Import-CSV $FileLocation[1]
    
    # Count value used to step through domain array
    $Count = 0
    $Output = "First Name,Last Name,Status,Domain" + "`n"
    
    # Load Quest cmdlets. Will error if already loaded
    Add-PSSnapin Quest.ActiveRoles.ADManagement
    
    Foreach ($Domain in $DomainConStr)
    {	# Get credentials
    	$Attempts = 0
        Do {$MyCreds = $host.ui.PromptForCredential("Need credentials", "Please enter domain\username and password for the following domain", "$Domain" + [Environment]::UserName, "NetBiosUserName")
    	$Attempts += 1}
    	
    	# Check credentials against domain to ensure they are valid
    	Until(($Access = Test-Credential $MyCreds) -or ($Attempts >=2))
    	
    	# Notify the user after two failed login attempts
    	If ($Attempts >=2)
    	{
    	Write-Host "You have failed to login correctly twice. You might want to check that your account isn't locked on $Domain
    	The script is now quitting"
    	exit
    	}# End credential collection/verification
        
        # Write domain (IP) and connection credentials to terminal
    	Write-Host $aDomain[$Count]
        Write-Host $MyCreds
        
        # Open connection to the domain using the provided credentials
        connect-qadservice -service $aDomain[$Count] -credential $MyCreds
        $Count = $Count + 1
        
    	# Search the domain for the first or last name of the person specified
        Foreach($Person in $UserList)
        {        
    		$FirstName = get-qaduser -Anr $Person.FirstName
    		$LastName = get-qaduser -Anr $Person.LastName
       		if(($FirstName) -and ($LastName)){
            	$Output += ('{0},{1},100% match found in,{2}
    			' -f $Person.FirstName, $Person.LastName,$Domain)
       		}elseif(($FirstName) -or ($LastName)){
            	$Output += ('{0},{1},50% match in,{2}
    			' -f $Person.FirstName, $Person.LastName, $Domain)
    	   	}else{
    			$Output += ('{0},{1},No match in,{2}
    			' -f $Person.FirstName, $Person.LastName, $Domain)
    		}	
        }   
    }
    	# Define report file name and location
    	$sRunDate = Get-Date -Format yyyy_MM_dd
        $sFilePath = "c:\term_user_report" + $sRunDate + ".csv"
    
        # Verify if report exists
        if ([IO.File]::Exists($sFilePath))
    		# Write $Output to file
        	{Add-Content -Path $sFilePath $Output
    		[System.Windows.Forms.MessageBox]::Show("$sFilePath already existed. 
    		Results have been appended to the end of the file")}
        
    	# Create file if it doesn't exist, and add content
        else
    	    {new-item -type file $sFilePath
    		Add-Content $sFilePath $Output}
    

    • Marked As Answer by OT_Menafro Wednesday, January 30, 2013 3:06 PM
    •