Powershell Shadow Groups - Inconsistent Group Membership - powershell

I am using Powershell to maintain several shadow groups (a group that mirrors all the users in a specific OU.)
I have three OUs, Staff, Faculty, and Administration. Each has a group associated with it, with the Staff group encompassing all three groups.
Here is the code:
$server="win-ad1.example.com"
#Staff
$AdministrationOU="OU=Administration,OU=Accounts,DC=example,DC=com"
$FacultyOU="OU=Faculty,OU=Accounts,DC=example,DC=com"
$StaffOU="OU=Staff,OU=Accounts,DC=example,DC=com"
$ShadowGroup="CN=Staff,OU=User Groups,OU=Accounts,DC=example,DC=com"
Get-ADGroupMember -Server $server –Identity $ShadowGroup `
| Where-Object {($_.distinguishedName –NotMatch $AdministrationOU) `
-OR ($_.distinguishedName –NotMatch $FacultyOU) `
-OR ($_.distinguishedName –NotMatch $StaffOU)} `
| ForEach-Object {Remove-ADPrincipalGroupMembership -Server $server –Identity $_ –MemberOf $ShadowGroup –Confirm:$false}
Sleep -Seconds 2
Get-ADUser -Server $server –SearchBase $AdministrationOU –SearchScope OneLevel –LDAPFilter "(!(memberOf=$ShadowGroup))" `
| ForEach-Object {Add-ADPrincipalGroupMembership -Server $server –Identity $_ –MemberOf $ShadowGroup}
Get-ADUser -Server $server –SearchBase $FacultyOU –SearchScope OneLevel –LDAPFilter "(!(memberOf=$ShadowGroup))" `
| ForEach-Object {Add-ADPrincipalGroupMembership -Server $server –Identity $_ –MemberOf $ShadowGroup}
Get-ADUser -Server $server –SearchBase $StaffOU –SearchScope OneLevel –LDAPFilter "(!(memberOf=$ShadowGroup))" `
| ForEach-Object {Add-ADPrincipalGroupMembership -Server $server –Identity $_ –MemberOf $ShadowGroup}
#Administration
$ShadowGroup="CN=Administration,OU=User Groups,OU=Accounts,DC=example,DC=com"
Get-ADGroupMember -Server $server –Identity $ShadowGroup `
| Where-Object {$_.distinguishedName –NotMatch $AdministrationOU} `
| ForEach-Object {Remove-ADPrincipalGroupMembership -Server $server –Identity $_ –MemberOf $ShadowGroup –Confirm:$false}
Sleep -Seconds 2
Get-ADUser -Server $server –SearchBase $AdministrationOU –SearchScope OneLevel –LDAPFilter "(!(memberOf=$ShadowGroup))" `
| ForEach-Object {Add-ADPrincipalGroupMembership -Server $server –Identity $_ –MemberOf $ShadowGroup}
Get-ADGroupMember -Server $server –Identity $ShadowGroup `
| Enable-ADAccount -Server $server
....
#same code for Faculty
This is (I think) pretty standard code, but I admit to writing very little powershell. The code should remove all the users in the group that aren't in the OU, then add all the users (not already in the group) that are in the OU to the group.
In theory, this works great, however in practice adding/removing users to the Staff group can go massively wrong. In one run of the script, it will suddenly remove swathes of (random) users from the group, despite them obviously being in one of the OUs. A run of the script again may add them back or take out other users. Many time the script runs perfectly.
I had initially chalked it up to the script talking to different domain controllers, so I added the -Server argument to each Active Directory call, so that it would hit the same (primary) domain controller each time. The problem still occurs, and in all of my searching I can't find anyone with the same issue (probably because I'm not really sure how to phrase what is happening.)
Any help would be appreciated.
Note, for scale here are the size of the OUs:
Staff: 199
Faculty: 194
Administration: 32

Related

Issue With Powershell Loop Iteration

When attempting to run the below code it appears that it's running through my initial foreach loop twice. What am I not seeing? I appreciate any help.
$DC = Get-ADDomainController
$OUs = Get-ADOrganizationalUnit -Filter 'Name -eq "test"'
$TimeStamp = get-date -format D
$description = "Disabled on " + $TimeStamp
$canNotDisableUser = Get-ADGroupMember -Identity DoNotDisableUsers -Recursive | Select -ExpandProperty Name
$accounts = $null
# Search for User Accounts inactive for XX Days and Disable if not in DoNotDisable Security Group
$accounts = Search-ADAccount -SearchBase $OU -AccountInactive -TimeSpan ([timespan]90d) -UsersOnly
foreach($account in $accounts){
If ($canNotDisableUser -notmatch $account.Name){
Disable-ADAccount -Identity $account.DistinguishedName -Verbose
}
# Disable Protected from Accidental Deletion from OU
Get-ADOrganizationalUnit -LDAPFilter '(name=*)' -SearchBase $OU.DistinguishedName -Server $DC | Set-ADObject -ProtectedFromAccidentalDeletion:$false -Verbose -WhatIf
# Move Disabled Users to Disabled Users OU & Add Timestamp to Description
Search-ADAccount –AccountDisabled –UsersOnly –SearchBase $OU.DistinguishedName | Foreach-object {
Set-ADUser $_ -Description $description -Verbose -WhatIf
Move-ADObject $_ –TargetPath “OU=Disabled Users, DC=xxx,DC=net” -Verbose -WhatIf
}
# Enable Protected from Accidental Deletion from OU
Get-ADOrganizationalUnit -LDAPFilter '(name=*)' -SearchBase $OU.DistinguishedName -Server $DC | Set-ADObject -ProtectedFromAccidentalDeletion:$true -Verbose -WhatIf
}
One of the things that will save you LOTS of time in troubleshooting these kinds of issues is "indentation". Make it a habbit of always making sure they are indented correctly.
# Search for User Accounts inactive for XX Days and Disable if not in DoNotDisable Security Group
$accounts = Search-ADAccount -SearchBase $OU -AccountInactive -TimeSpan ([timespan]90d) -UsersOnly
foreach($account in $accounts){
If ($canNotDisableUser -notmatch $account.Name){
Disable-ADAccount -Identity $account.DistinguishedName -Verbose
}
### YOU probably intend to close the foreach loop here. If so, Move the LAST brace to this place.
# Disable Protected from Accidental Deletion from OU
Get-ADOrganizationalUnit -LDAPFilter '(name=*)' -SearchBase $OU.DistinguishedName -Server $DC | Set-ADObject -ProtectedFromAccidentalDeletion:$false -Verbose -WhatIf
# Move Disabled Users to Disabled Users OU & Add Timestamp to Description
Search-ADAccount –AccountDisabled –UsersOnly –SearchBase $OU.DistinguishedName | Foreach-object {
Set-ADUser $_ -Description $description -Verbose -WhatIf
Move-ADObject $_ –TargetPath “OU=Disabled Users, DC=xxx,DC=net” -Verbose -WhatIf
}
# Enable Protected from Accidental Deletion from OU
Get-ADOrganizationalUnit -LDAPFilter '(name=*)' -SearchBase $OU.DistinguishedName -Server $DC | Set-ADObject -ProtectedFromAccidentalDeletion:$true -Verbose -WhatIf
}
Corrected
# Search for User Accounts inactive for XX Days and Disable if not in DoNotDisable Security Group
$accounts = Search-ADAccount -SearchBase $OU -AccountInactive -TimeSpan ([timespan]90d) -UsersOnly
foreach($account in $accounts){
If ($canNotDisableUser -notmatch $account.Name){
Disable-ADAccount -Identity $account.DistinguishedName -Verbose
}
}
# Disable Protected from Accidental Deletion from OU
Get-ADOrganizationalUnit -LDAPFilter '(name=*)' -SearchBase $OU.DistinguishedName -Server $DC | Set-ADObject -ProtectedFromAccidentalDeletion:$false -Verbose -WhatIf
# Move Disabled Users to Disabled Users OU & Add Timestamp to Description
Search-ADAccount –AccountDisabled –UsersOnly –SearchBase $OU.DistinguishedName | Foreach-object {
Set-ADUser $_ -Description $description -Verbose -WhatIf
Move-ADObject $_ –TargetPath “OU=Disabled Users, DC=xxx,DC=net” -Verbose -WhatIf
}
# Enable Protected from Accidental Deletion from OU
Get-ADOrganizationalUnit -LDAPFilter '(name=*)' -SearchBase $OU.DistinguishedName -Server $DC | Set-ADObject -ProtectedFromAccidentalDeletion:$true -Verbose -WhatIf

Add domain to excel name

I'm currently connecting to a domain controller that is on a different domain. I'm trying to automate a report that tells us if a user is enabled and the last time they logged in. I'm looking to either add the domain name to the excel output or even rename the tabs inside the workbook. Currently I just name the excel sheet by the IP of the domain controller. I have a total of 8 domains Thank you for any help.
$domainserver = "ipaddress1" , "ipaddress2" , "ipaddress3"
foreach ($s in $domainserver){
Get-ADUser -Credential $Credential -Server $s -Filter {Enabled -eq $TRUE} -Properties Name,SamAccountName,LastLogonDate |
Where {($_.LastLogonDate -lt (Get-Date).AddDays(-30)) -and ($_.LastLogonDate -ne $NULL)} |
Select Name,SamAccountName,LastLogonDate | Sort-Object LastLogonDate |
Export-Csv C:\temp\$s.csv -NoTypeInformation
}
Run Get-AdDomain against the same server, to find domain details:
$domainserver = "ipaddress1" , "ipaddress2" , "ipaddress3"
foreach ($s in $domainserver)
{
$domain = Get-AdDomain -Server $s -Credential $Credential
Get-ADUser -Credential $Credential -Server $s -Filter {Enabled -eq $TRUE} -Properties Name,SamAccountName,LastLogonDate |
Where {($_.LastLogonDate -lt (Get-Date).AddDays(-30)) -and ($_.LastLogonDate -ne $NULL)} |
Select-Object -Property Name,SamAccountName,LastLogonDate,#{Label='Domain'; Expression={$domain.DnsRoot}} |
Sort-Object LastLogonDate |
Export-Csv C:\temp\$s.csv -NoTypeInformation
}

Powershell Adding users within groups to cross forest groups

This script works without error now, but the problem is that when several groups in the searchbase are found, the script will add all users from all groups to the cross forest target groups.
So for example:
ForestAGroup1 = contains 2 users
ForestAGroup2 = contains 2 users
::runs script::
now...
ForestBGroup1 = contains 4 users
ForestBGroup2 = contains 4 users
The ForestBGroup1/2 needs to contain the same identical users as ForestAGroup1/2.
Here is the script for reference:
$creds = Get-Credential
$Groups = Get-ADGroup -Properties * -Filter * -SearchBase "OU=TEST,OU=Shop Print Groups,OU=User,OU=domain Groups,DC=domainA,DC=com" | export-csv c:\temp\test.csv
$Groups = Get-ADGroup -Properties * -Filter * -SearchBase "OU=TEST,OU=Shop Print Groups,OU=User,OU=domain Groups,DC=domainA,DC=com"
Foreach($G In $Groups)
{
#Display group members and group name
Write-Host $G.Name
Write-Host "-------------"
$G.Members
#Add members to domainB group
$domainGMembers = import-csv C:\temp\test.csv | ForEach-Object -Process {Get-ADGroupMember -Identity $_.CN} | Select-Object samaccountname | export-csv c:\temp\gmembers.csv
$domainDNUser = import-csv C:\temp\gmembers.csv | ForEach-Object -Process {Get-ADUser $_.samaccountname -Server "domainA.com" -properties:Distinguishedname}
import-csv C:\temp\gmembers.csv | ForEach-Object -Process {Add-ADGroupMember -Server "domainB.com" -Identity $G.Name -Members $domainDNUser -Credential $creds -Verbose}
}
What are you doing?
You export to csv, but still try to save it to a variable
You search twice
You add all members from ALL groups in TEST-OU to every group in domainB
You waste time on saving and reading data that you already have in memory
You search for the user-object to get SamAccountName when you already have something ten times better, the DN. Then you use that SamAccountName to find the DN.
Try this (untested):
$creds = Get-Credential
$Groups = Get-ADGroup -Properties Members -Filter * -SearchBase "OU=TEST,OU=Shop Print Groups,OU=User,OU=domain Groups,DC=domain,DC=com"
Foreach($G In $Groups)
{
#Display group members and group name
Write-Host $G.Name
Write-Host "-------------"
$G.Members
#Add members to domainB group
$G.Members |
Get-ADUser -Server fairfieldmfg.com |
ForEach-Object { Add-ADGroupMember -Server "domainB.com" -Identity $G.Name -Members $_ -Credential $creds -Verbose }
}
I used a foreach-loop to run the Add-ADGroupMember because it usually fails in the middle of a group of members if it finds on the already is a member, but if we add them one at a time you get around that (or you could do a search and exclude those already in the group).
You may want to add -ErrorAction SilentlyContinue to Add-ADGroupMember to ignore those errors when you know the script works as it should.

How can I clone a GroupOfNames Active Directory Object?

I'm writing a script to take one GroupOfNames object and create a second group with the first groups members. It seems like it would be a very simple piece of code:
$obj = Get-ADObject -Server "$server" -SearchBase $SearchBase -Filter "name -eq '$GroupName'" -Properties Member
New-ADObject -Server "$server" -Path $SearchBase -Type 'GroupOfNames' -name "$NewGroupName" -OtherAttributes #{'Member'= ($($obj.Member))}
When I run this the $obj gets created and I can display both the GroupOfNames information, as well as the list of members. But when it calls the New-ADObject cmdlet, I get the following error:
New-ADObject : Unable to contact the server. This may be because this server does not exist, it is currently down, or it does not have the Active Directory Web Services running.
I've tried multiple variations of the code and all fail with similar errors.
Interestingly, if I loop through the list of members and add them to the group one at a time, it works, but it just takes way too long (an hour+ vs seconds).
Try this, it casts the results of your first query, ADPropertyValueCollection, to a string array(untested!):
$obj = Get-ADObject -Server "$server" -SearchBase $SearchBase
-Filter "name -eq '$GroupName'" -Properties Member
$m = [string[]]$obj.Member
New-ADObject -Server "$server" -Path $SearchBase -Type 'GroupOfNames'
-name "$NewGroupName" -OtherAttributes #{'Member'= $m}
After some trial and error, I found that I could reliably add about 6000 members at a time. Here's the basic flow:
(Get-ADObject -Server "$server" -SearchBase $SearchBase -Filter { name -eq "$GroupName" } -Properties Member).Member | %{
$SubList += [string] $_
if($SubList.count -ge 6000) {
Set-ADObject -Server "$server" -Identity $NewGroup -Add #{'Member'= ($SubList)}
$SubList = #()
}
}
if($SubList.count -gt 0) {
Set-ADObject -Server "$server" -Identity $NewGroup -Add #{'Member'= ($SubList)}
}
Thank you #Raf for the push in the right direction.

Logging output of user disable and group removal - Powershell no Quest

I have some existing code that I've pieced together from my google searches.
Hi All,
I spent some time googling to find the code needed and this is what I have below.
What I'm wanting to do is log the groups being removed from a user either.
a) Into a local text (.log) file
or
b) Into the users "Notes" field.
I've found code on how to do logging if I use quest addin, but cannot fathom how to make it fit WITHOUT using quest.
Doing this as a learning + work functional task to make it speedier to disable users.
Import-Module activedirectory -ErrorAction silentlycontinue
Add-PSSnapin Microsoft.Exchange.Management.PowerShell.Admin -ErrorAction silentlycontinue
$username = read-host "Username"
$users= get-aduser $username
Function RemoveMemberships
{
param([string]$SAMAccountName)
$user = Get-ADUser $SAMAccountName -properties memberof
$userGroups | %{get-adgroup $_ | Remove-ADGroupMember -confirm:$false -member $SAMAccountName}
$userGroups | %{get-adgroup $_ | Remove-ADGroupMember -confirm:$false -member $SAMAccountName} $userGroups = $user.memberof
$userGroups = $null
}
$users | %{RemoveMemberships $_.SAMAccountName}
Move-ADObject $users -TargetPath "OU=Disabled Users,DC=contoso,DC=com" -PassThru | Disable-ADAccount
edit: The inclusion of the Exchange PSSnapin is for enhancing with disabling on GAL. I have code, but didn't include it in this.
A simple on-screen answer using Write-Host
$userGroups | %{Write-Host "Removing user from group $_"; get-adgroup $_ | Remove-ADGroupMember -confirm:$false -member $SAMAccountName}