Last active
August 20, 2025 12:02
-
-
Save r0ckbeard/f5ff2c28a01a18a000d8bb927a8a3051 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <# | |
| .SYNOPSIS | |
| Transfers members from one Active Directory group to another. | |
| .DESCRIPTION | |
| This script transfers a specified number of members from one Active Directory group to another. | |
| It can be used to stage the move of users from one system to another, facilitating gradual migrations | |
| or testing of new environments with a subset of users. | |
| The script performs the following actions: | |
| 1. Transfers specified number of members from a source group to a destination group. | |
| 2. Verifies the successful addition of members to the destination group. | |
| 3. If verification is successful, removes the transferred members from the source group. | |
| .PARAMETER SourceGroup | |
| The name of the AD group from which members will be transferred. | |
| .PARAMETER DestinationGroup | |
| The name of the AD group to which members will be added. | |
| .PARAMETER NumberOfMembers | |
| The number of members to transfer between groups. | |
| .EXAMPLE | |
| .\TransferADGroupMembers.ps1 -SourceGroup "OldGroup" -DestinationGroup "NewGroup" -NumberOfMembers 5 | |
| .NOTES | |
| If run without parameters, the script will use the default values defined in the variables section. | |
| #> | |
| param( | |
| [string]$SourceGroup, | |
| [string]$DestinationGroup, | |
| [int]$NumberOfMembers | |
| ) | |
| # Import the Active Directory module | |
| Import-Module ActiveDirectory | |
| # Default variables (used if parameters are not provided) | |
| $DefaultSourceGroup = "OldGroup" | |
| $DefaultDestinationGroup = "NewGroup" | |
| $DefaultNumberOfMembers = 5 | |
| # Logging setup | |
| $LogPath = "C:\Windows\Logs\Software" | |
| $LogFile = Join-Path -Path $LogPath -ChildPath "ADGroupTransfer_$(Get-Date -Format 'yyyyMMdd_HHmmss').log" | |
| # Create log directory if it doesn't exist | |
| if (-not (Test-Path -Path $LogPath)) { | |
| New-Item -ItemType Directory -Path $LogPath -Force | |
| } | |
| # Logging function | |
| function Write-Log { | |
| param( | |
| [string]$Message, | |
| [string]$Color = "White" | |
| ) | |
| $timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss" | |
| $logMessage = "[$timestamp] $Message" | |
| Add-Content -Path $LogFile -Value $logMessage | |
| Write-Host $logMessage -ForegroundColor $Color | |
| } | |
| # Use parameters if provided, otherwise use default variables | |
| $SourceGroup = if ($SourceGroup) { $SourceGroup } else { $DefaultSourceGroup } | |
| $DestinationGroup = if ($DestinationGroup) { $DestinationGroup } else { $DefaultDestinationGroup } | |
| $NumberOfMembers = if ($NumberOfMembers) { $NumberOfMembers } else { $DefaultNumberOfMembers } | |
| Write-Log "Starting group member transfer operation" | |
| Write-Log "Source Group: $SourceGroup" | |
| Write-Log "Destination Group: $DestinationGroup" | |
| Write-Log "Number of Members: $NumberOfMembers" | |
| # Get all members from the source group and randomly select the specified number | |
| $allMembers = Get-ADGroupMember -Identity $SourceGroup | |
| if ($allMembers.Count -lt $NumberOfMembers) { | |
| Write-Log "Warning: Source group only has $($allMembers.Count) members, selecting all available members" -Color Yellow | |
| $members = $allMembers | |
| } else { | |
| $members = $allMembers | Get-Random -Count $NumberOfMembers | |
| } | |
| Write-Log "Adding $($members.Count) randomly selected members to $DestinationGroup" -Color Cyan | |
| # Add members to the destination group | |
| $addedCount = 0 | |
| foreach ($member in $members) { | |
| try { | |
| Add-ADGroupMember -Identity $DestinationGroup -Members $member.DistinguishedName | |
| $addedCount++ | |
| } | |
| catch { | |
| Write-Log "Failed to add $($member.Name): $($_.Exception.Message)" | |
| } | |
| } | |
| Write-Log "Successfully added $addedCount of $($members.Count) members to $DestinationGroup" -Color Green | |
| # Verify membership in the new group | |
| Write-Log "Verifying membership..." -Color Cyan | |
| $verifiedCount = 0 | |
| $verifiedMembers = @() | |
| $failedMembers = @() | |
| # Get destination group membership once for performance | |
| $destinationMembers = Get-ADGroupMember -Identity $DestinationGroup | Select-Object DistinguishedName | |
| $destinationDNs = $destinationMembers.DistinguishedName | |
| foreach ($member in $members) { | |
| if ($destinationDNs -contains $member.DistinguishedName) { | |
| $verifiedCount++ | |
| $verifiedMembers += $member | |
| } else { | |
| $failedMembers += $member | |
| Write-Log "Failed to verify $($member.Name) in $DestinationGroup" | |
| } | |
| } | |
| Write-Log "Verified $verifiedCount of $($members.Count) members in $DestinationGroup" -Color Green | |
| if ($failedMembers.Count -gt 0) { | |
| Write-Log "Failed to verify the following $($failedMembers.Count) member(s):" -Color Yellow | |
| foreach ($failedMember in $failedMembers) { | |
| Write-Log " - $($failedMember.Name)" -Color Yellow | |
| } | |
| } | |
| if ($verifiedMembers.Count -gt 0) { | |
| Write-Log "Removing $($verifiedMembers.Count) verified members from source group" | |
| # Remove only the successfully verified members from the source group | |
| $removedCount = 0 | |
| foreach ($member in $verifiedMembers) { | |
| try { | |
| Remove-ADGroupMember -Identity $SourceGroup -Members $member.DistinguishedName -Confirm:$false | |
| $removedCount++ | |
| } | |
| catch { | |
| Write-Log "Failed to remove $($member.Name): $($_.Exception.Message)" | |
| } | |
| } | |
| Write-Log "Successfully removed $removedCount of $($verifiedMembers.Count) members from source group" -Color Green | |
| if ($failedMembers.Count -gt 0) { | |
| Write-Log "Note: $($failedMembers.Count) members were NOT removed from source group due to verification failure" | |
| } | |
| Write-Log "Transfer operation completed" -Color Green | |
| } else { | |
| Write-Log "No members were successfully verified. No members removed from source group" | |
| } | |
| # ============================================== | |
| # CSV Export (Uncomment the section you want) | |
| # ============================================== | |
| # Option 1: Detailed CSV - One row per transferred member | |
| <# | |
| if ($verifiedMembers.Count -gt 0) { | |
| $csvPath = Join-Path -Path $LogPath -ChildPath "ADGroupTransfer_Details_$(Get-Date -Format 'yyyyMMdd_HHmmss').csv" | |
| $csvData = @() | |
| foreach ($member in $verifiedMembers) { | |
| $csvData += [PSCustomObject]@{ | |
| MemberName = $member.Name | |
| MemberType = $member.ObjectClass | |
| SourceGroup = $SourceGroup | |
| DestinationGroup = $DestinationGroup | |
| TransferDate = Get-Date -Format "yyyy-MM-dd HH:mm:ss" | |
| } | |
| } | |
| $csvData | Export-Csv -Path $csvPath -NoTypeInformation | |
| Write-Log "Detailed transfer results exported to: $csvPath" -Color Green | |
| } | |
| #> | |
| # Option 2: Summary CSV - One row per transfer operation | |
| <# | |
| $csvPath = Join-Path -Path $LogPath -ChildPath "ADGroupTransfer_Summary_$(Get-Date -Format 'yyyyMMdd_HHmmss').csv" | |
| $summaryData = [PSCustomObject]@{ | |
| SourceGroup = $SourceGroup | |
| DestinationGroup = $DestinationGroup | |
| MembersRequested = $NumberOfMembers | |
| MembersAdded = $addedCount | |
| MembersVerified = $verifiedCount | |
| MembersRemoved = $removedCount | |
| FailedVerifications = $failedMembers.Count | |
| TransferDate = Get-Date -Format "yyyy-MM-dd HH:mm:ss" | |
| } | |
| $summaryData | Export-Csv -Path $csvPath -NoTypeInformation | |
| Write-Log "Summary transfer results exported to: $csvPath" -Color Green | |
| #> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment