• Get User Profiles and Last User that Logged on to Desktop Computers

    Another Powershell script that helps with a migration. In this instance the requirement was for a way of understanding the accounts that had previously logged onto desktop systems and the credentials of the last logged on user, for either all desktop systems in a Domain\OU structure or all desktop systems listed in a text file. Additionally, in this instance, there was a requirement to add a delay between each iteration of the data gathering to minimise impact on the script running system and to the network.

    The challenge was that this information is stored differently in XP and Vista\Win7 in the registry and the Profile information is accessible on Vista\Win7 systems using Get-Profiles, but needs some registry scripting jiggery-pokery for XP\Win2k3 systems. Additionally, I could not find any script out there that did both these things, so I decided to use functions from other peoples work and stitch them together to get what is attached.

    To understand what can be done with the script, read the header. If the script is run without parameters it will just get Profile information for all desktop systems in the currently logged on domain. The output is stored in 3 csv files that are suffixed with Date\Time stamps in the same location that the script is run from.

     The Syntax for running the script is this and none of the parameters are required.

    .\Get-PCUserProfilePath.ps1 -srcDomainPath <SearchPathdnOrDNSName> -IterationDelay <TimeInSeconds> -ComputerType <ServerOrDesktop> -ComputerListFile <sAMAccountNameTextList>
     

    I have not included a listing of the script here because it is too large, but the script can be downloaded at the bottom of this posting.

    HTH

     

  • Compare AD Object Direct Group Membership

    Who can guess I am working on an AD migration project at the moment? And it presents an opportunity to cut my teeth further on Powershell scripting. Yes the AD Powershell scripts I have produced so far could have been done much easier if I had used the AD module provided with AD in Windows Server 2008 R2, but unfortunately not everyone is in a situation where they can take advantage of that module or even the Quest Modules that are also available. Hence some of the long winded scripts I have been producing.

    Back to this problemette I was presented with. Following a user migration process\procedure the AD support team had discovered that a number of users were not able to access some resources and as one of the checks during remediation, wanted a quick way of comparing a users Group Membership with that of the source account. The following script is what I came up with for them. Basically, it gets all the direct Group memberships for the source object and destination objects (as specified at the command line) and runs a compare operation. The on screen output is of a table that displays the Group Memberships that are different between the objects with an arrow indicator showing what side of the evaluation the Group Membership exists. So for example if you ran the following command :

    .\Compare-UserGroups.ps1 -srcDomain domain1.local -destDomain woodgrovebank.com -srcsAMAccountName carlh1 -destsAMAccountName carlh2

    You would get 2 files that list each objects Group Membership and something like the table below, which shows that the entry for destsAMAccountName (carlh2) is a member of a group named "Password Policy Group", which the entry for srcsAMAccountName (carlh) is not a member of and vice versa for the other groups.

     InputObject                                       SideIndicator                                                                       
    -----------                                            -------------                                                                       
    Password Policy Group                     =>                                                                                  
    EventLogAccess                                <=                                                                                  
    DnsAdmins                                        <=                                                                                  
    Backup Operators                             <=                                                                                  

    It is true this is just a "like for like" sAMAccountName comparison and not a true SID to SID\SIDHistory comparison and it only compares Direct membership, but the requirement was for a quick check to ensure nothing was awry for odd troubleshooting instances and suits the needs where Groups have been migrated wholesale. If you need anything more funky that does Group Nesting then I advise you pop over to here http://www.rlmueller.net/freecode1.htm . Richards scripts are awesome.

    <#
    ####################################################################
     Compare-UserGroups.ps1

     Syntax:  Compare-UserGroups.ps1 -srcDomain <SourceDomain> -destDomain <DestinationDomain> -srcsAMAccountName <UserNetbiosName> -destsAMAccountName <UserNetbiosName>

     Example: Compare-UserGroups.ps1 -srcDomain domain1.local -destDomain woodgrovebank.com -srcsAMAccountName carlh -destsAMAccountName carlh

     Purpose: Compares the DIRECT Group Membership of 2 user accounts.
              Be aware that it compares the Netbios names (sAMAccountName) of the groups
              and is only useful either within a domain\forest or after a migration
              of a user account between domains where the group names have not been changed
              as a consequence of the migration.

     Params:  As shown in syntax above or by typing the script name at the command prompt

     Req:     Windows 2003 SP2 or above, Powershell V2.
              run "set-executionpolicy remotesigned" in Powershell

              http://blogs.technet.com/b/carlh

     Author:  Carl Harrison

        This script is provided "AS IS" with no warranties, confers no rights and
        is not supported by the authors or authors employer.
        Use of this script sample is subject to the terms specified at
        http://www.microsoft.com/info/copyright.mspx.

     Version: 1.0 - First cut

    ####################################################################
    #>

    Param (
     [Parameter()][string]$srcDomain='',
     [Parameter()][String]$destDomain='',
        [Parameter()][String]$srcsAMAccountName='',
        [Parameter()][String]$destsAMAccountName='')

    Function Compare-GroupsHelp () {
    $helptext=@"
    NAME: Compare-UserGroups.ps1

    Compares the DIRECT Group Membership of 2 user accounts
    Be aware that it compares the Netbios names (sAMAccountName) of the groups
    and is only useful either within a domain\forest or after a migration
    of a user account between domains where the group names have not been changed
    as a consequence of the migration.

    PARAMETERS:
    -srcDomain          Source Domain (Required)
    -destDomain         Destination Domain (Required)
    -srcsAMAccountName  Netbios name of the user account in the source domain (Required)
    -destsAMAccountName Netbios name of the user account in the destination domain (Required)

    SYNTAX:
    Compare-UserGroups.ps1 -srcDomain domain1.local -destDomain woodgrovebank.com -srcsAMAccountName carlh -destsAMAccountName carlh2

    Thsi compares the group memberships that carlh from domain1.local has in domain1.local
    with the group memberships that carlh from woodgrovebank.com has in woodgrovebank.com

    "@
    $helptext
    exit
    }

    Function Get-LDAPUser ($UserName, $SourceDomain) {
        $domain1 = new-object DirectoryServices.DirectoryEntry ("LDAP://$SourceDomain")
        $searcher = new-object DirectoryServices.DirectorySearcher($domain1)
        $searcher.filter = "(&(objectClass=user)(sAMAccountName= $UserName))"
        $searcher.findone().getDirectoryEntry()
        $domain1 =""
    }

    if(!($srcDomain)) {"Source Domain Required";Compare-GroupsHelp}
    if(!($destDomain)) {"Destination Domain Required";Compare-GroupsHelp}
    if(!($srcsAMAccountName)) {"Netbios Name or Source Account Required";Compare-GroupsHelp}
    if(!($destsAMAccountName)) {"Netbios Name or Destination Account Required";Compare-GroupsHelp}

    $srcUserGroupsFile = '.\srcUserGroupsFile.txt'
    $destUserGroupsFile = '.\destUserGroupsFile.txt'

    Write-Host
    $srcUser = get-ldapuser $srcsAMAccountName $srcDomain
    Write-Host $srcUser.displayName "is a member of" $srcUser.memberOf.Count " groups in domain $srcDomain. The groups are:"
    $srcUser.memberOf | ft
    Write-Host
    $destUser = get-ldapuser $destsAMAccountName $destDomain
    Write-Host $destUser.displayName "is a member of" $destUser.memberOf.Count " groups in domain $destDomain. The groups are:"
    $destUser.memberOf | ft

    Write-Host

    $srcUserGroups = @()
    $srcGroupsDN = @()
    $destUserGroups = @()
    $destGroupsDN = @()

    Foreach($Group in $srcUser.memberOf)
        {
        $GroupsAMAccountName = ([ADSI]"LDAP://$Group").sAMAccountName.value
        #$GroupsAMAccountName
        $srcUserGroups += "$GroupsAMAccountName"
        $srcGroupsDN += $Group.tostring()
        }
    Foreach($Group in $destUser.memberOf)
        {
        $GroupsAMAccountName = ([ADSI]"LDAP://$Group").sAMAccountName.value
        #$GroupsAMAccountName
        $destUserGroups += "$GroupsAMAccountName"
        $destGroupsDN += $Group.tostring()
        }

    $srcGroupsDN | Out-File $srcUserGroupsFile
    $destGroupsDN | Out-File $destUserGroupsFile

    Compare-Object $srcUserGroups $destUserGroups -SyncWindow 100

    $destUser = ""
    $srcUser = ""

     

  • Copy AD User Profile Path etc between Domain User Objects

    Here's a weird one, you are migrating users from multiple domains to a single domain and your migration process\procedure had determined that the primary domain of a user was not what it actually is, so the settings (Profile Path, Home Drive\Directory, and Script Path) for the new user are for the wrong account. For various reasons you don't want or cannot merge the user objects attributes. This was one of the queries posed to me recently by a customer.

     

    The script below gives a remedy to this by taking as a command line parameter the name and domain of one domain user and copying profile settings (the Profile Path, Home Drive & Directory, and Script Path) to relevant attributes of another command line parameter specified user name (in another domain). I even included the ability to prefix the ScriptPath Attribute (in case you use a different folder tree in the new domain).

     

    Before using the script read the header to give you a clue as to the syntax. I have attached a copy of the code in a text file at the bottom of this post.

    <#
    ####################################################################
    Copy-UserProfile.ps1

    Syntax:  Copy-UserProfile.ps1 -srcDomain <SourceDomain> -destDomain <DestinationDomain> -scriptPathPrefix <ScriptPrefix> -srcsAMAccountName <UserNetbiosName> -

    destsAMAccountName <UserNetbiosName>

    Example: Copy-UserProfile.ps1 -srcDomain domain1.local -destDomain woodgrovebank.com -scriptPathPrefix domain1\ -srcsAMAccountName carlh -destsAMAccountName carlh2

    Purpose: This Sets the ProfilePath, ScriptPath, HomeDrive and HomeDirectory of the user carlh2 in the
              Woodgrovebank.com domain to the same settings as those for carlh in domain.local. Additionally,
              the scriptPath attribute content is prefixed with the word domin1\

    Params:  As shown in syntax above or by typing the script name at the command prompt

    Req:     Windows 2003 SP2 or above, Powershell V2.
      run "set-executionpolicy remotesigned" in Powershell

      http://blogs.technet.com/b/carlh

    Author:  Carl Harrison

        This script is provided "AS IS" with no warranties, confers no rights and
        is not supported by the authors or Microsoft Corporation.
        Use of this script sample is subject to the terms specified at
        http://www.microsoft.com/info/copyright.mspx.

    Version: 1.0 - First cut

    ####################################################################
    #>

    Param (
     [Parameter()][string]$srcDomain='',
     [Parameter()][String]$destDomain='',
            [Parameter()][String]$scriptPathPrefix='',
            [Parameter()][String]$srcsAMAccountName='',
            [Parameter()][String]$destsAMAccountName='')

    Function GetSetUserHelp () {
    $helptext=@"
    NAME: Copy-UserPofile.ps1
    Used to copy User Profile, Logon Script and Home details of user
    from one domain to the next.

    PARAMETERS:
    -srcDomain          Source Domain (Required)
    -destDomain         Destination Domain (Required)
    -scriptPathPrefix   Prefix to add to Script Path attribute (include any back slashes or forward slashes as required)
    -srcsAMAccountName  Netbios name of the user account in the source domain (Required)
    -destsAMAccountName Netbios name of the user account in the destination domain (Required)

    SYNTAX:
    Copy-UserProfile.ps1 -srcDomain domain1.local -destDomain woodgrovebank.com -scriptPathPrefix domain1\ -srcsAMAccountName carlh -destsAMAccountName carlh2

    This Sets the ProfilePath, ScriptPath, HomeDrive and HomeDirectory of the user carlh2 in the
    Woodgrovebank.com domain to the same settings as those for carlh in domain.local. Additionally,
    the scriptPath attribute content is prefixed with the word domin1\

    "@
    $helptext
    exit
    }

    Function Get-LDAPUser ($UserName, $SourceDomain) {
        $domain1 = new-object DirectoryServices.DirectoryEntry ("LDAP://$SourceDomain")
        $searcher = new-object DirectoryServices.DirectorySearcher($domain1)
        $searcher.filter = "(&(objectClass=user)(sAMAccountName= $UserName))"
        $searcher.findone().getDirectoryEntry()
        $domain1 =""
    }

    Function Set-LDAPUser ($UserName2, $DestinationDomain) {
        $domain2 = new-object DirectoryServices.DirectoryEntry ("LDAP://$DestinationDomain")
        $searcher = new-object DirectoryServices.DirectorySearcher($domain2)
        $searcher.filter = "(&(objectClass=user)(sAMAccountName= $UserName2))"
        $destUser = $searcher.findone().getDirectoryEntry()
        $destUser.scriptPath = "$Global:ScriptPathPrefix" + $Global:srcUser.scriptPath
        $destUser.profilePath = $Global:srcUser.profilePath
        $destUser.homeDrive = $Global:srcUser.homeDrive
        $destUser.homeDirectory = $Global:srcUser.homeDirectory
        $destUser.setinfo()
        $domain2 = ""
    }

    if(!($srcDomain)) {"Source Domain Required";GetSetUserHelp}
    if(!($destDomain)) {"Destination Domain Required";GetSetUserHelp}
    if(!($srcsAMAccountName)) {"Netbios Name or Source Account Required";GetSetUserHelp}
    if(!($destsAMAccountName)) {"Netbios Name or Destination Account Required";GetSetUserHelp}

    $Global:ScriptPathPrefix = $ScriptPathPrefix
    $Global:srcUser = get-ldapuser $srcsAMAccountName $srcDomain
    Write-Host $Global:srcUser.displayName "in domain $srcDomain settings are:"
    $Global:srcUser.scriptPath
    $Global:srcUser.profilePath
    $Global:srcUser.homeDrive
    $Global:srcUser.homeDirectory
    set-ldapuser $destsAMAccountName $destDomain
    $Global:destUser = get-ldapuser $destsAMAccountName $destDomain
    Write-Host ""
    Write-Host $Global:destUser.displayName "in domain $destDomain settings are now:"
    $Global:destUser.scriptPath
    $Global:destUser.profilePath
    $Global:destUser.homeDrive
    $Global:destUser.homeDirectory

    $Global:destUser = ""
    $Global:srcUser = ""
    $ScriptPathPrefix = ""

     

     

  • Powershell Script to find if Domain Admins is a member of Computer Local Administrators

    So here's another script that I quickly knocked up. Basically, I was asked whether there was a way of finding out whether the Domain Admins group was a member of the Local Administrators group on a list of computers. Powershell to the rescue; I'm really getting into this Powershell Malarkey.

    It is rather rudimentary and could actually be made a bit more usable by getting it to search AD and even specific OU structures in AD. Examples of this follow later in my TechNet Blog.

    Script attached to blog below (requires removal of txt extension to work).

     

    <#
    #####################################################################
    SCRIPT IsADMemberOfLocalAdmins.ps1

    SYNTAX
    .\IsADMemberOfLocalAdmins.ps1 -InputFile <.\ComputerList.txt> -OutputFile <.\OutPutFile.txt>

    -InputFile          Text file containing list of Computers to query
                       
    -OutputFile         Text File containing results from script

    SYNOPSIS
    Queries the Local Administrators group on the computers listed in the
    text file provided as a parameter, to determine if Domain Admins is
    listed as a member.

    NOTE
        Script requires no parameters or arguments, but does have some.
        I recommend you have the relevant permissions in the domain and
        on the computers being queried for optimal results.

        This script is provided "AS IS" with no warranties, confers no rights and
        is not supported by the authors or employer.

    AUTHOR
        Carl Harrison

    VERSION: 1.0 - First cut
    #####################################################################
    #>

    # Change these two to suit your needs
    Param (
      [Parameter()][string]$InputFile='.\computers.txt',
      [Parameter()][String]$OutputFile='.\IsDAMemberOfAdminsOutput.txt')

    $ChildGroups = "Domain Admins"
    $LocalGroup = "Administrators"

    $MemberNames = @()
    $OutPutResults = @()
    $Computers = Get-Content $InputFile
    foreach ( $Computer in $Computers ) {
     $Group= [ADSI]"WinNT://$Computer/$LocalGroup,group"
     $Members = @($Group.psbase.Invoke("Members"))
     $Members | ForEach-Object {
      $MemberNames += $_.GetType().InvokeMember("Name", 'GetProperty', $null, $_, $null)
     }
     $ChildGroups | ForEach-Object {
      $output = "" | Select-Object Computer, Group, InLocalAdmin
      $output.Computer = $Computer
      $output.Group = $_
      $output.InLocalAdmin = $MemberNames -contains $_
      Write-Output $output
            $OutputResults += $output
        }
        $MemberNames = @()
    }
    $OutputResults | Export-CSV -NoTypeInformation $OutputFile

     

  • Delete Stuff in AD

    Once again another script to help one of my colleagues in need of a method of bulk deleting objects in AD taken from a list in CSV file. In this instance he need it for deletion of groups that they had determined as no longer useful. Bizarrely, this type of script did not exist when he searched for it (I would have thought someone would have written something like this previously). Actually I had some written some of this code already over 8 years ago and decided to repurpose it for my colleague.

    Below is a listing of the VBScript. It reads in a file named Groups.csv that contains a list of all groups (sAMAccountName's) to be deleted (the original CSV file also had a second column that had the group type integer, but the script strips this). The script works in the domain of the currently logged on credentials, so you need the necessary permissions in AD for it to work.

    Normally, I comment my scripts a lot more, but this was a rush order :-) and I haven't had the time to revisit it (and I an trying to move from VBScript now).

     

    My colleague has proven, the script is easily altered to enable it t delete any type of object and these scripts have been posted to Microsoft Script Center.

     

    'Script deletes security groups from a csv file.
    'csv format is strsAMGroupName,Whatever
    'This script is offered with no warranty
    'On Error Resume Next 'used in case group not found
    Option Explicit

    Const ForReading = 1

    Dim strL, spl1, strOU, strGroupCN, strGroupName
    Dim objFSO, objInputFile

    Set objFSO = CreateObject("Scripting.FileSystemObject")

    Set objInputFile = objFSO.OpenTextFile(".\groups.csv", ForReading) 'your csv file

    wscript.echo "script started"

    'extract from csv file
    Do until objInputFile.AtEndOfStream
     strL = objInputFile.ReadLine
     spl1 = Split(strL, ",")
     strGroupName = (spl1(0))
     If GroupExists(strGroupName) = True Then
      'WScript.Echo strGroupName & " exists."
      DelGroup
     End If   
    Loop

    Set objFSO = Nothing
    Set objInputFile = Nothing

    wscript.echo "script finished"

    'group exist check
    Function GroupExists(strsAMGroupName)

    Dim strDNSDomain, strFilter, strQuery
    Dim objConnection, objCommand, objRootLDAP, objLDAPGroup, objRecordSet

    GroupExists = False
    Set objConnection = CreateObject("ADODB.Connection")
    Set objCommand =   CreateObject("ADODB.Command")
    Set objRootLDAP = GetObject("LDAP://RootDSE")
    objConnection.Provider = "ADsDSOObject"
    objConnection.Open "Active Directory Provider"
    Set objCommand.ActiveConnection = objConnection
    objCommand.Properties("Page Size") = 1000
    'objCommand.Properties("Searchscope") = ADS_SCOPE_SUBTREE

    strDNSDomain = objRootLDAP.Get("DefaultNamingContext")
    strFilter = "(&(objectCategory=group)(sAMAccountName=" & strsAMGroupName & "))"

    strQuery = "<LDAP://" & strDNSDomain & ">;" & strFilter & ";sAMAccountName,adspath,CN;subTree"

    objCommand.CommandText = strQuery
    'WScript.Echo strFilter
    'WScript.Echo strQuery
    Set objRecordSet = objCommand.Execute

    If objRecordSet.RecordCount = 1 Then

    objRecordSet.MoveFirst
        'WScript.Echo "We got here " & strsAMGroupName     
     'WScript.Echo objRecordSet.Fields("sAMAccountname").Value
     'WScript.Echo objRecordSet.Fields("adspath").Value
     If objRecordSet.Fields("sAMAccountname").Value = strsAMGroupName Then
      GroupExists = True
      Set objLDAPGroup = GetObject(objRecordSet.Fields("adspath").Value)
      strOU = objLDAPGroup.Parent
      strGroupCN = objRecordSet.Fields("CN").Value
     End If
    Else
     WScript.Echo strsAMGroupName & " Group doesn't exist or Duplicate sAMAccountName"
     GroupExists = False
     strGroupCN = ""
     strOU = ""
    End If

    objRecordSet.Close
    Set objConnection = Nothing
    Set objCommand = Nothing
    Set objRootLDAP = Nothing
    Set objLDAPGroup = Nothing
    Set objRecordSet = Nothing

    end function

    Sub DelGroup

    Dim objOU

    'WScript.Echo strOU
    'WScript.Echo strGroupCN
    Set objOU = GetObject(strOU)
    objOU.Delete "Group", "cn=" & strGroupCN & ""
    WScript.Echo strGroupName & " (CN=" & strGroupCN & ") has been deleted."

    Set ObjOU = Nothing
    strGroupCN = ""

    End Sub