Jerry Seinfield
asked on
powershell script to monitor any changes to AD groups
Hello Experts,
Can you help me to write a report that would be emailed to an AD user weekly to monitor these AD groups?
SQLPRODAdmins
SQLReadOnly
The idea to know any changes to membership, updates, removes, any task performed over those groups and generate a report weekly that is sent via email to a user
Can you help me to write a report that would be emailed to an AD user weekly to monitor these AD groups?
SQLPRODAdmins
SQLReadOnly
The idea to know any changes to membership, updates, removes, any task performed over those groups and generate a report weekly that is sent via email to a user
ASKER
Thanks Sir,
Will the script above monitor for every single change to each of the groups defined? i.e, membership, additions, removals, and so on? What line is supposed to do that?
Thanks in advance
Will the script above monitor for every single change to each of the groups defined? i.e, membership, additions, removals, and so on? What line is supposed to do that?
Thanks in advance
No, it would produce an output of members only.
If you want to produce a report on changes only, you could alter it slightly:
First, create a baseline for both:
get-adgroupmember SQLPRODAdmins | sort-object Name | select-object Name | out-file c:\folder\ProdAdmins.txt
get-adgroupmember SQLReadOnly | sort-object Name | select-object Name | out-file c:\folder\SQLRO.txt
Then you could perform a comparison. Do you want to show only those added/removed?
If you want to produce a report on changes only, you could alter it slightly:
First, create a baseline for both:
get-adgroupmember SQLPRODAdmins | sort-object Name | select-object Name | out-file c:\folder\ProdAdmins.txt
get-adgroupmember SQLReadOnly | sort-object Name | select-object Name | out-file c:\folder\SQLRO.txt
Then you could perform a comparison. Do you want to show only those added/removed?
$SQLAdmins = (get-adgroupmember SQLPRODAdmins | sort-object Name | select-object Name )
$SQLRO = (get-adgroupmember SQLReadOnly | sort-object Name | select-object Name )
$AdminDiff = compare-object $SQLAdmins c:\folder\SQLAdmins.txt
$RODiff = compare-object $SQLRO c:\folder\SQLRO.txt
$body = $AdminDiff | select @{label='Admin differences';expression={$_.inputobject.anem + ' ' + $_.sideindicator}}
$body += $RODiff | select @{label='Read only differences';expression={$_.inputobject.anem + ' ' + $_.sideindicator}}
send-mailmessage -smtp mailserver.domain.com -from Admin@domain.com -to User@domain.com -subject 'SQL Group report' -body $body
ASKER
I want to show everything added, removed, and any other change that you might think of
The above should indicate members in the original that are not in the new query, with a <= symbol, and those members in the new query that were not in the original with a => symbol. You can include those that are in both, but I think that's not going to help based on what you've described.
The only thing left would be to refresh the file each time it launches, otherwise all updates you get will be based off of that original output.
Let me know if that sorts it for you and we can easily make that adjustment...
The only thing left would be to refresh the file each time it launches, otherwise all updates you get will be based off of that original output.
Let me know if that sorts it for you and we can easily make that adjustment...
ASKER
That works for me.
how would the final script look?
how would the final script look?
Again, remember - this needs to be run first to identify the current state of both groups: (changing the folder/file location as desired)
Then you simply target that same file to be overwritten each time the script executes. With the count property, you will only get notified if there was a change in membership - otherwise, no news is good news...
get-adgroupmember SQLPRODAdmins | sort-object Name | select-object Name | out-file c:\folder\ProdAdmins.txt
get-adgroupmember SQLReadOnly | sort-object Name | select-object Name | out-file c:\folder\SQLRO.txt
Then you simply target that same file to be overwritten each time the script executes. With the count property, you will only get notified if there was a change in membership - otherwise, no news is good news...
$SQLAdmins = (get-adgroupmember SQLPRODAdmins | sort-object Name | select-object Name )
$SQLRO = (get-adgroupmember SQLReadOnly | sort-object Name | select-object Name )
$AdminDiff = compare-object $SQLAdmins c:\folder\SQLAdmins.txt
$RODiff = compare-object $SQLRO c:\folder\SQLRO.txt
if ($AdminDiff.count -gt 0) {$body = $AdminDiff | select @{label='Admin differences';expression={$_.inputobject.anem + ' ' + $_.sideindicator}}}
if ($RODiff.count -gt 0) {$body += $RODiff | select @{label='Read only differences';expression={$_.inputobject.anem + ' ' + $_.sideindicator}}}
send-mailmessage -smtp mailserver.domain.com -from Admin@domain.com -to User@domain.com -subject 'SQL Group report' -body $body
$SQLAdmins | out-file c:\folder\ProdAdmins.txt
$SQLRO | out-file c:\folder\SQLRO.txt
ASKER
Thank you Sir,
Did you have a chance to test the script?
Did you have a chance to test the script?
So far as I can, sure. But I don't have those specific groups in my environment. :^)
ASKER
the first part of the script runs, the second part did not
see error on the attached screenshot
and the final code with my variables, patch and groups names in AD
$SQLAdmins = (get-adgroupmember 'SQL Admins' | sort-object Name | select-object Name )
$SQLRO = (get-adgroupmember SQLReadOnly | sort-object Name | select-object Name )
$NonSQLAdmins = (get-adgroupmember 'SQL Admins NonProd' | sort-object Name | select-object Name )
$AdminDiff = compare-object $SQLAdmins c:\temp\ProdAdmins.txt
$RODiff = compare-object $SQLRO c:\temp\SQLRO.txt
$NonAdminDiff = compare-object $NonSQLAdmins c:\temp\NonProdAdmins.txt
if ($AdminDiff.count -gt 0) {$body = $AdminDiff | select @{label='Admin differences';expression={$ _.inputobj ect.anem + ' ' + $_.sideindicator}}}
if ($RODiff.count -gt 0) {$body += $RODiff | select @{label='Read only differences';expression={$ _.inputobj ect.anem + ' ' + $_.sideindicator}}}
if ($NonAdminDiff.count -gt 0) {$body = $NonAdminDiff | select @{label='Non Admin differences';expression={$ _.inputobj ect.anem + ' ' + $_.sideindicator}}}
send-mailmessage -smtp relayprod.DOMAIN.com -from donetreply@DOMAIN.com -to XXXX@DOMAIN.com -subject 'SQL Group report' -body $body
$SQLAdmins | out-file c:\temp\ProdAdmins.txt
$SQLRO | out-file c:\temp\SQLRO.txt
$NonSQLAdmins | out-file c:\temp\NonProdAdmins.txt
TrackADGroup.jpg
see error on the attached screenshot
and the final code with my variables, patch and groups names in AD
$SQLAdmins = (get-adgroupmember 'SQL Admins' | sort-object Name | select-object Name )
$SQLRO = (get-adgroupmember SQLReadOnly | sort-object Name | select-object Name )
$NonSQLAdmins = (get-adgroupmember 'SQL Admins NonProd' | sort-object Name | select-object Name )
$AdminDiff = compare-object $SQLAdmins c:\temp\ProdAdmins.txt
$RODiff = compare-object $SQLRO c:\temp\SQLRO.txt
$NonAdminDiff = compare-object $NonSQLAdmins c:\temp\NonProdAdmins.txt
if ($AdminDiff.count -gt 0) {$body = $AdminDiff | select @{label='Admin differences';expression={$
if ($RODiff.count -gt 0) {$body += $RODiff | select @{label='Read only differences';expression={$
if ($NonAdminDiff.count -gt 0) {$body = $NonAdminDiff | select @{label='Non Admin differences';expression={$
send-mailmessage -smtp relayprod.DOMAIN.com -from donetreply@DOMAIN.com -to XXXX@DOMAIN.com -subject 'SQL Group report' -body $body
$SQLAdmins | out-file c:\temp\ProdAdmins.txt
$SQLRO | out-file c:\temp\SQLRO.txt
$NonSQLAdmins | out-file c:\temp\NonProdAdmins.txt
TrackADGroup.jpg
Ah - need to read in the file, not reference - my mistake:
$AdminDiff = compare-object $SQLAdmins (get-content c:\temp\ProdAdmins.txt)
$RODiff = compare-object $SQLRO (get-content c:\temp\SQLRO.txt)
$NonAdminDiff = compare-object (get-content $NonSQLAdmins c:\temp\NonProdAdmins.txt)
ASKER
how would look the final second script? can you please paste the final code here? see my updates above,
Use the code block - it helps with copy/pasting the script sections:
$SQLAdmins = (get-adgroupmember 'SQL Admins' | sort-object Name | select-object Name )
$SQLRO = (get-adgroupmember SQLReadOnly | sort-object Name | select-object Name )
$NonSQLAdmins = (get-adgroupmember 'SQL Admins NonProd' | sort-object Name | select-object Name )
$AdminDiff = compare-object $SQLAdmins (get-content c:\temp\ProdAdmins.txt)
$RODiff = compare-object $SQLRO (get-content c:\temp\SQLRO.txt)
$NonAdminDiff = compare-object $NonSQLAdmins (get-content c:\temp\NonProdAdmins.txt)
if ($AdminDiff.count -gt 0) {$body = $AdminDiff | select @{label='Admin differences';expression={$_.inputobject.anem + ' ' + $_.sideindicator}}}
if ($RODiff.count -gt 0) {$body += $RODiff | select @{label='Read only differences';expression={$_.inputobject.anem + ' ' + $_.sideindicator}}}
if ($NonAdminDiff.count -gt 0) {$body = $NonAdminDiff | select @{label='Non Admin differences';expression={$_.inputobject.anem + ' ' + $_.sideindicator}}}
send-mailmessage -smtp relayprod.DOMAIN.com -from donetreply@DOMAIN.com -to XXXX@DOMAIN.com -subject 'SQL Group report' -body $body
$SQLAdmins | out-file c:\temp\ProdAdmins.txt
$SQLRO | out-file c:\temp\SQLRO.txt
$NonSQLAdmins | out-file c:\temp\NonProdAdmins.txt
ASKER
no luck,
see attached error
see attached error
no attachment?
Try this slight adjustment - condenses some of the code since we have a 3rd group...you'll need to re-run the initial file creation piece, here, to have the files appropriately named:
Then launch this code - of course, you would need to manually manipulate the source file(s) to get an indication of the differences:
foreach ($group in @('SQL Admins', 'SQLReadOnly', 'SQL Admins NonProd') {get-adgroupmember $group | sort-object name | select-object -expand name | out-file "c:\temp\$group.txt")
Then launch this code - of course, you would need to manually manipulate the source file(s) to get an indication of the differences:
$SQLAdmins = (get-adgroupmember 'SQL Admins' | sort-object Name | select-object -expand Name )
$SQLRO = (get-adgroupmember SQLReadOnly | sort-object Name | select-object -expand Name )
$NonSQLAdmins = (get-adgroupmember 'SQL Admins NonProd' | sort-object Name | select-object -expand Name )
foreach ($group in @('SQL Admins', 'SQLReadOnly', 'SQL Admins NonProd') {
$groupMembers = get-adgroupmember $group | sort-object name | select-object -expand name
$difference = compare-object $groupMembers (get-content "c:\temp\$group.txt")
if ($difference.count -gt 0) {
$notify=$true
$body += ($difference | select-object @{label="$group differences";expression={$_.inputobject.anem + ' ' + $_.sideindicator}}})
$groupMembers | out-file "c:\temp\$group.txt"
}
if ($notify) {
send-mailmessage -smtp relayprod.DOMAIN.com -from donetreply@DOMAIN.com -to XXXX@DOMAIN.com -subject 'SQL Group report' -body $body
}
ASKER
Try this version:
$SQLAdmins = (get-adgroupmember 'SQL Admins' | sort-object Name | select-object -expand Name )
$SQLRO = (get-adgroupmember SQLReadOnly | sort-object Name | select-object -expand Name )
$NonSQLAdmins = (get-adgroupmember 'SQL Admins NonProd' | sort-object Name | select-object -expand Name )
foreach ($group in @('SQL Admins', 'SQLReadOnly', 'SQL Admins NonProd') {
$groupMembers = get-adgroupmember $group | sort-object name | select-object -expand name
$difference = compare-object $groupMembers (get-content "c:\temp\$group.txt")
if ($difference.count -gt 0) {
$notify=$true
$body += ($difference | select-object @{label="$group differences";expression={$_.inputobject.name + ' ' + $_.sideindicator}})
$groupMembers | out-file "c:\temp\$group.txt"
}
}
if ($notify) {send-mailmessage -smtp relayprod.DOMAIN.com -from donetreply@DOMAIN.com -to XXXX@DOMAIN.com -subject 'SQL Group report' -body $body}
ASKER
no luck, but I believe we almost there, see error attached
GROUpError3.jpg
GROUpError3.jpg
Stupid type-o's - I swear I dropped this into the ISE and no errors were raised...ugh. o.O
Try this...
Try this...
$SQLAdmins = (get-adgroupmember 'SQL Admins' | sort-object Name | select-object -expand Name )
$SQLRO = (get-adgroupmember SQLReadOnly | sort-object Name | select-object -expand Name )
$NonSQLAdmins = (get-adgroupmember 'SQL Admins NonProd' | sort-object Name | select-object -expand Name )
foreach ($group in @('SQL Admins', 'SQLReadOnly', 'SQL Admins NonProd')) {
$groupMembers = get-adgroupmember $group | sort-object name | select-object -expand name
$difference = compare-object $groupMembers (get-content "c:\temp\$group.txt")
if ($difference.count -gt 0) {
$notify=$true
$body += ($difference | select-object @{label="$group differences";expression={$_.inputobject.name + ' ' + $_.sideindicator}})
$groupMembers | out-file "c:\temp\$group.txt"
}
}
if ($notify) {send-mailmessage -smtp relayprod.DOMAIN.com -from donetreply@DOMAIN.com -to XXXX@DOMAIN.com -subject 'SQL Group report' -body $body}
ASKER
no luck Sir
see attached, different error. it seems like is unable to find a file in a path
groupError5.jpg
see attached, different error. it seems like is unable to find a file in a path
groupError5.jpg
Yes, did you re-run the initial creation process? Do you see those files present in c:\temp?
ASKER
Yep, I ran the following and I can see the files on same path
get-adgroupmember 'SQL Admins' | sort-object Name | select-object Name | out-file c:\temp\ProdAdmins.txt
get-adgroupmember SQLReadOnly | sort-object Name | select-object Name | out-file c:\temp\SQLRO.txt
get-adgroupmember 'SQL Admins NonProd' | sort-object Name | select-object Name | out-file c:\temp\NonProdAdmins.txt
then I ran the other script
foreach ($group in @('SQL Admins', 'SQLReadOnly', 'SQL Admins NonProd') {get-adgroupmember $group | sort-object name | select-object -expand name | out-file "c:\temp\$group.txt")
and finally the problematic one
$SQLAdmins = (get-adgroupmember 'SQL Admins' | sort-object Name | select-object -expand Name )
$SQLRO = (get-adgroupmember SQLReadOnly | sort-object Name | select-object -expand Name )
$NonSQLAdmins = (get-adgroupmember 'SQL Admins NonProd' | sort-object Name | select-object -expand Name )
foreach ($group in @('SQL Admins', 'SQLReadOnly', 'SQL Admins NonProd')) {
$groupMembers = get-adgroupmember $group | sort-object name | select-object -expand name
$difference = compare-object $groupMembers (get-content "c:\temp\$group.txt")
if ($difference.count -gt 0) {
$notify=$true
$body += ($difference | select-object @{label="$group differences";expression={$ _.inputobj ect.name + ' ' + $_.sideindicator}})
$groupMembers | out-file "c:\temp\$group.txt"
}
}
if ($notify) {send-mailmessage -smtp relayprod.domain.com -from donetreply@domain.com -to xxx@domain.com -subject 'SQL Group report' -body $body}
is that the logical sequence of running the scripts?
get-adgroupmember 'SQL Admins' | sort-object Name | select-object Name | out-file c:\temp\ProdAdmins.txt
get-adgroupmember SQLReadOnly | sort-object Name | select-object Name | out-file c:\temp\SQLRO.txt
get-adgroupmember 'SQL Admins NonProd' | sort-object Name | select-object Name | out-file c:\temp\NonProdAdmins.txt
then I ran the other script
foreach ($group in @('SQL Admins', 'SQLReadOnly', 'SQL Admins NonProd') {get-adgroupmember $group | sort-object name | select-object -expand name | out-file "c:\temp\$group.txt")
and finally the problematic one
$SQLAdmins = (get-adgroupmember 'SQL Admins' | sort-object Name | select-object -expand Name )
$SQLRO = (get-adgroupmember SQLReadOnly | sort-object Name | select-object -expand Name )
$NonSQLAdmins = (get-adgroupmember 'SQL Admins NonProd' | sort-object Name | select-object -expand Name )
foreach ($group in @('SQL Admins', 'SQLReadOnly', 'SQL Admins NonProd')) {
$groupMembers = get-adgroupmember $group | sort-object name | select-object -expand name
$difference = compare-object $groupMembers (get-content "c:\temp\$group.txt")
if ($difference.count -gt 0) {
$notify=$true
$body += ($difference | select-object @{label="$group differences";expression={$
$groupMembers | out-file "c:\temp\$group.txt"
}
}
if ($notify) {send-mailmessage -smtp relayprod.domain.com -from donetreply@domain.com -to xxx@domain.com -subject 'SQL Group report' -body $body}
is that the logical sequence of running the scripts?
This is no longer needed...
What's in C:\temp?
get-adgroupmember 'SQL Admins' | sort-object Name | select-object Name | out-file c:\temp\ProdAdmins.txt
get-adgroupmember SQLReadOnly | sort-object Name | select-object Name | out-file c:\temp\SQLRO.txt
get-adgroupmember 'SQL Admins NonProd' | sort-object Name | select-object Name | out-file c:\temp\NonProdAdmins.txt
We replaced it with this loop:foreach ($group in @('SQL Admins', 'SQLReadOnly', 'SQL Admins NonProd') {get-adgroupmember $group | sort-object name | select-object -expand name | out-file "c:\temp\$group.txt")
If those files are being created, then the last code block, which is the script you'd need to schedule, is all that's needed.What's in C:\temp?
ASKER
C:\Temp is a folder where i am placing all files
ASKER CERTIFIED SOLUTION
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
While most of the solutions here are good solutions they have one flaw. They don't tell you when the change happend and who did that change. If you want that information (and you need to be precise) you need to dive into Event logs. I've written such script. It's still work in progress but it does send email daily with summary of group membership changes https://evotec.xyz/monitoring-active-directory-changes-on-users-and-groups-with-powershell/ hope it helps.
Open in new window