How can I output the screen results to a txt file when I run this code?
#removes disabled clinical or corp accounts from SGs in the I-Drive OU
$searchOU = "OU=I-Drive,OU=SAS,OU=Application Security Groups,OU=Groups,OU=Enterprise,DC=z,DC=x,DC=y"
Get-ADGroup -Filter 'GroupCategory -eq "Security"' -SearchBase $searchOU | ForEach-Object{ $group = $_
Get-ADGroupMember -Identity $group | Get-ADUser | Where-Object {$_.Enabled -eq $false} | ForEach-Object{ $user = $_
$uname = $user.Name
$gname = $group.Name
Write-Host "Removing $uname from $gname" -Foreground Yellow
Remove-ADGroupMember -Identity $group -Member $user -Confirm:$false
}
}
Pipe the output of Get-ADGroup to Set-Content like so:
Get-ADGroup -Filter 'GroupCategory -eq "Security"' -SearchBase $searchOU | ForEach-Object{
$group = $_
Get-ADGroupMember -Identity $group | Get-ADUser | Where-Object { $_.Enabled -eq $false} | ForEach-Object{
$user = $_
$uname = $user.Name
$gname = $group.Name
Write-Host "Removing $uname from $gname" -Foreground Yellow
Remove-ADGroupMember -Identity $group -Member $user -Confirm:$false
}
} | Set-Content filename.txt
If you want any additional output (warnings, verbose, errors) change the last line a bit to redirect the other streams:
} *>&1 | Set-Content filename.txt
Alternatively, you can also use the built-in transcript logging to log everything to a file as well just call one of the following from within your script:
Start-Transcript
or if you want the log to go to a specific place:
Start-Transcript -Path "\Path\To\LogFile.log"
Note that transcript logging is more useful in scripts than during an interactive session.
Store the value you'd like to log in a variable say $abc
Write to log file and keep appending $abc | Out-File -FilePath "C:\Somewhere\log.txt" -Append -Encoding UTF8
Refer - Log output of ForEach loop
Related
I am trying to find out the Active Directory groups all our active users are in and want to export it to a CSV file. However the following command presents garbage in the related CSV file.
This is my code failing:
Import-Module ActiveDirectory
Get-ADUser -SearchBase "CN=Users,DC=Mycompany,DC=de" -Filter * | where { $_.enabled -eq "true" } | foreach-object {
write-host "User:" $_.Name
Get-ADPrincipalGroupMembership $_.SamAccountName | foreach-object {
write-host "Member of:" $_.name | export-csv "C:\scripts\output\ad-user-with-group-memberhip.csv" -NoTypeInformation -Encoding UTF8
}
}
Any idea what am I doing wrong here?
Write-Host only writes text to the console window. It doesn't output anything useful to pipe through to Export-Csv.
Also, unless you add switch -Append, you should set the Export-Csv cmdlet as last line in the code, otherwise you will overwrite it in every iteration.
Try
with Select-Object
Import-Module ActiveDirectory
Get-ADUser -SearchBase "CN=Users,DC=Mycompany,DC=de" -Filter "Enabled -eq $true" |
Select-Object Name, #{Name = 'Groups'; Expression = {($_ | Get-ADPrincipalGroupMembership).Name -join '; '}} |
Export-Csv -Path "C:\scripts\output\ad-user-with-group-memberhip.csv" -NoTypeInformation -Encoding UTF8
or with ForEach-Object
Import-Module ActiveDirectory
$result = Get-ADUser -SearchBase "CN=Users,DC=Mycompany,DC=de" -Filter "Enabled -eq $true" |
ForEach-Object {
[PsCustomObject]#{
Name = $_.Name
Groups = ($_ | Get-ADPrincipalGroupMembership).Name -join '; '
}
}
$result | Export-Csv -Path "C:\scripts\output\ad-user-with-group-memberhip.csv" -NoTypeInformation -Encoding UTF8
My code:
$searchOU = "OU=a,OU=b,OU=c,OU=d,OU=e,DC=f,DC=g,DC=com"
Get-ADGroup -Filter 'GroupCategory -eq "Security"' -SearchBase $searchOU | sort name | ForEach- Object{
$group = $_
Get-ADGroupMember -Identity $group | Get-ADUser | Where-Object { $_.Enabled -eq $false} | ForEach-Object{
$user = $_
$uname = $user.Name
$gname = $group.Name
Write-Host "Removing $uname from $gname" -Foreground Yellow
Remove-ADGroupMember -Identity $group -Member $user -Confirm:$false #-whatif
}
}
It runs, but it's dog slow. Any suggestions on ways to make it run faster?
You need to remember that Get-ADGroupMember can return users, groups, and computers, not just user objects..
If you want to search for user objects only, you need to add a Where-Object clause there.
Unfortunately, while Get-ADUser has a -Filter parameter that enables you to find disabled users much more quickly than filtering afterwards on the collection of users, using a filter while piping user DN's to it will totally ignore the pipeline and collect all users that are disabled..
So, in this case, we're stuck with appending a Where-Object clause.
You could change your code to rule out all objects from Get-ADGroupMember that are not uders:
$searchOU = "OU=a,OU=b,OU=c,OU=d,OU=e,DC=f,DC=g,DC=com"
Get-ADGroup -Filter "GroupCategory -eq 'Security'" -SearchBase $searchOU | Sort-Object Name | ForEach-Object {
$group = $_
Get-ADGroupMember -Identity $group | Where-Object { $_.objectClass -eq 'user' } |
Get-ADUser | Where-Object { $_.Enabled -eq $false} | ForEach-Object {
Write-Host "Removing $($_.Name) from $($group.Name)" -Foreground Yellow
Remove-ADGroupMember -Identity $group -Member $_ -Confirm:$false #-whatif
}
}
The above removes one disabled user at a time and for each of them writes a line on the console.
You could make it work faster if you can cope with getting a different output on screen like this:
$searchOU = "OU=a,OU=b,OU=c,OU=d,OU=e,DC=f,DC=g,DC=com"
$result = foreach ($group in (Get-ADGroup -Filter "GroupCategory -eq 'Security'" -SearchBase $searchOU)) {
$users = Get-ADGroupMember -Identity $group | Where-Object { $_.objectClass -eq 'user' } |
Get-ADUser | Where-Object { $_.Enabled -eq $false}
if ($users) {
# the Remove-ADGroupMember cmdlet can take an array of users to remove at once
Remove-ADGroupMember -Identity $group -Member $users -Confirm:$false #-whatif
# output an object that gets collected in variable $result
[PsCustomObject]#{Group = $group.Name; RemovedUsers = ($users.Name -join '; ')}
}
}
# if you like, output to console as table
$result | Sort-Object Group | Format-Table -AutoSize -Wrap
# or write to CSV file
$result | Export-Csv -Path 'D:\Test\RemovedUsers.csv' -NoTypeInformation
I've got a simple powershell script that searches for users that are in a group containing 'www*'. I'd like the output if possible to be
samAccountName,Group
How would I go about doing that in powershell?
$groups = Get-AdGroup -Properties * -filter * | Where {$_.name -like "www*"}
$output =
Foreach($g in $groups)
{
write-output $g.Name
write-output "----------"
get-adgroupmember -Identity $g | select-object -Property SamAccountName
}
$output | out-file -FilePath C:\Users\myuser\desktop\test3.txt -Append
Try this:
$groups = Get-AdGroup -Properties * -filter * | Where {$_.name -like "www*"}
$output =
Foreach($g in $groups)
{
write-output $g.Name
write-output "----------"
get-adgroupmember -Identity $g | select-object SamAccountName,#{Name="Group";Expression={$g.Name}}
}
$output | Export-Csv -NoTypeInformation -Path C:\Users\myuser\desktop\test3.txt -Append
I need to extract all the members of a group and then format that into Names, so I only get the Full Names like "Don Joe" is member of my-domain\UNCShareDrive
I need that to appear in my CSV after I have exported the ACL lists.
As an example:
Path FileSystemRights AccessControlType IdentityReference
---- ---------------- ----------------- -----------------
Microsoft.PowerShell.Core\FileSystem::\\fileshare\management\DK FullControl Allow MY-DOMAIN\Drev Management
Microsoft.PowerShell.Core\FileSystem::\\fileshare\management\ FullControl Allow BUILTIN\Administratorer
Microsoft.PowerShell.Core\FileSystem::\\fileshare\management\ FullControl Allow MY-DOMAIN\Drev Management
Microsoft.PowerShell.Core\FileSystem::\\fileshare\management\ FullControl Allow BUILTIN\Administratorer
**These persons has access to the drive:**
**Finding group: MY-DOMAIN\Administrators**
Name SamAccountName Mail
---- -------------- ----
Administrator Administrator Administrator#my-domain.dk
This is what I have so far done in Powershell:
$Searching = Get-ADGroup -Filter * -SearchBase "DC=my-domain,DC=local"
$mypath = "U:\mytest.csv"
if((Test-Path -Path "U:\mytest.csv" -pathtype Container) -eq $True){
Write-Host -ForegroundColor red "We found a file: $mypath --> REMOVING!`r`n"
Remove-Item $mypath -Force
}
$ShareName = "\\\sharename\D$"
$shares = Get-Childitem -path $ShareName |
Where-Object {$_.PSIsContainer} |
Get-ACL |
Select-Object Path -ExpandProperty Access |
Select Path, FileSystemRights,AccessControlType,IdentityReference |
export-csv $mypath -Delimiter ';' -NoTypeInformation -Encoding UTF8
$foldertotal = 0
Add-Content $mypath ""
Add-Content $mypath ""
Add-Content $mypath "Disse personer har adgang til share navnet"
Add-Content $mypath ""
Add-Content $mypath ""
$myLoop = ''
foreach ($testLoop in $myLoop) {
$Group = Get-ADGroup -Filter * -SearchBase "DC=my-domain,DC=local" -Properties IdentityReference |
Select-Object Name # Need only groups in $Shares - who are displayed
$myLoop += Write-Host -ForegroundColor Yellow "Finding group: $($Group.name).....`n"
$myLoop += ForEach ($Group in $share) {
Get-ADGroupMember -identity $($Group.name) -recursive |
Get-ADUser -Properties Name,SamAccountName,Mail |
Select-Object Name,SamAccountName,Mail
}
$testLoop | Out-File -filePath $mypath -append -encoding utf8
}
Write-Host -ForegroundColor Cyan "$($Group.name) is now exported to: $mypath`n"
I have also a hard time to filter out only the share path:
$shares.Replace('Microsoft.PowerShell.Core\FileSystem::', ' ') |
Out-File -filePath $mypath -append -encoding utf8
How come that this thing Works when I use User Input and not just automatically loop through it without the console prompt:
$Searching = Get-ADGroup -Filter * -SearchBase "DC=MY-DOMAIN,DC=local"
$mypath = "U:\mytest.csv"
$networkPath = "\\ShareName\D$"
$acl = Get-acl -path $networkPath
if((Test-Path -Path "U:\mytest.csv" -pathtype Container) -eq $True){
Write-Host -ForegroundColor red "We found a file: $mypath --> REMOVING!`r`n"
Remove-Item $mypath -Force
}
Write-Host -ForegroundColor Yellow "Eksempel på share: "`r`n\\ShareName\D$`r`n\\ShareTwo\\E$`r`n\\ShareName\management`r`n""
$ShareName = Read-host "Hvilket sharenavn vil du finde Access Control List fra?"
if($ShareName -eq "1"){
$Sharename = "\\ShareName\D$"
}
if($ShareName -eq "2"){
$Sharename = "\\ShareTwo\\E$"
}
if($ShareName -eq "3"){
$Sharename = "\\ShareName\management"
}
Get-Childitem -path $ShareName |
Where-Object {$_.PSIsContainer} |
Get-ACL |
Select-Object Path -ExpandProperty Access |
Select Path, FileSystemRights,AccessControlType,IdentityReference |
FT -AutoSize |
Out-File -Encoding utf8 $mypath
Add-Content $mypath ""
Add-Content $mypath ""
Add-Content $mypath "Disse personer har adgang til share navnet"
Add-Content $mypath ""
$users = get-aduser -Filter {Name -Like "*"} -Searchbase "dc=MY-DOMAIN,dc=local" -Properties MemberOf |
Where-Object { $_.Enabled -eq 'True' }
Write-Host -ForegroundColor red "Example: Drev Management`r`n"
$myGroups = Read-Host "Hvilken Gruppe vil du bruge?"
if($myGroups -eq "1"){
$myGroups = " Drev Management"
}
$Groups = Get-ADGroup -filter {Name -like $myGroups} | Select-Object Name
Add-Content $mypath "Finding group: $($group.name)"
Write-Host -ForegroundColor Yellow "Finding group: $($Group.name).....`n"
Add-Content -Path $mypath $result
ForEach ($Group in $Groups) {
Get-ADGroupMember -identity $($Group.name) -recursive |
Get-ADUser -Properties Name,SamAccountName,Mail |
Select-Object Name,SamAccountName,Mail |
FT -AutoSize |
Out-File -Append -encoding utf8 $mypath
}
Write-Host -ForegroundColor Cyan "$($Group.name) is now exported to: $mypath`n"
Your second code sample works, because it doesn't make the same mistakes as the first one.
$myLoop = ''
foreach ($testLoop in $myLoop) {
$Group = Get-ADGroup -Filter * ...
$myLoop += Write-Host ...
$myLoop += ForEach ($Group in $share) {
Get-ADGroupMember ...
}
$testLoop | Out-File -filePath $mypath -append -encoding utf8
}
You make $myLoop an empty string, then iterate over each element of $myLoop (one empty string), so you get a single loop cycle, no matter what you append to $myLoop inside the loop.
Instead of the group members you write the content of $testLoop (an empty string) to the output file.
You read AD groups into the variable $Group, and then overwrite its content when you use $Group as the loop variable in foreach ($Group in $share).
$share is emtpy, because it's never initialized anywhere. Even $shares is empty, because when you write the ACL information to the CSV, no output is left that could be assigned to the variable.
And as a side-note: you can't append Write-Host output to anything, because the cmdlet writes to the host process (i.e. the console), not a stream that could be captured or redirected.
You need to capture the ACL information in $shares before writing it to a file:
$shares = Get-Childitem -Path $ShareName |
Where-Object {$_.PSIsContainer} |
Get-ACL |
Select-Object Path -ExpandProperty Access |
Select Path, FileSystemRights,AccessControlType,IdentityReference
$shares | Export-Csv $mypath -Delimiter ';' -NoTypeInformation -Encoding UTF8
Then you can determine the members of the groups having access to shares like this:
$shares | Select-Object -Expand IdentityReference |
Select-Object -Expand Value |
ForEach-Object {
$name = $_ -replace '^DOMAIN\\' # <-- replace with actual domain name
Get-ADObject -Filter { Name -eq $name }
} |
Where-Object { $_.ObjectClass -eq 'group' } |
Get-ADGroupMember |
Get-ADUser -Properties * |
Select-Object Name, SamAccountName, Mail |
Out-File $mypath -Append -Encoding UTF8
Note that this will not resolve nested groups, and also won't distinguish between shares.
If you want to store both the group name and its members you need to add a loop to the pipeline:
$shares | Select-Object -Expand IdentityReference |
Select-Object -Expand Value |
ForEach-Object {
$name = $_ -replace '^DOMAIN\\' # <-- replace with actual domain name
Get-ADObject -Filter { Name -eq $name }
} |
Where-Object { $_.ObjectClass -eq 'group' } |
ForEach-Object {
$_
Get-ADGroupMember -Identity $_ |
Get-ADUser -Properties * |
Select-Object Name, SamAccountName, Mail
} |
Out-File $mypath -Append -Encoding UTF8
I'm trying to make a PS script which would list all Active Directory user group membership (recursive).
I already have working script:
import-module activedirectory
$users = get-aduser -Filter {Name -Like "*"} -Searchbase "ou=Users, dc=Domain" | Where-Object { $_.Enabled -eq 'True' }
$targetFile = "D:\users.csv"
rm $targetFile
Add-Content $targetFile "User;Group"
foreach ($user in $users)
{
$groups = Get-ADPrincipalGroupMembership $user
foreach ($group in $groups)
{
$username = $user.samaccountname
$groupname = $group.name
$line = "$username;$groupname"
Add-Content $targetFile $line
}
}
But script doesn't list groups recursively, i.e., if group listed in the output file is part of another group.
Example:
Group1: User
Group2: Group3: User
Script shows only Group1 and 3 but not 2.
What should I add to the first script that it writes group membership recursively?
Sorry I am publishing an answer for a question from 3 years ago but if someone will see it, it can help.
Credit to:
How to get ALL AD user groups (recursively) with Powershell or other tools?
You can use the LDAP_MATCHING_RULE_IN_CHAIN:
Get-ADGroup -LDAPFilter "(member:1.2.840.113556.1.4.1941:=CN=User,CN=USers,DC=x)"
You can use it anywahere that you can use an LDAP filter.
Example:
$username = 'myUsername'
$dn = (Get-ADUser $username).DistinguishedName
Get-ADGroup -LDAPFilter ("(member:1.2.840.113556.1.4.1941:={0})" -f $dn) | select -expand Name | sort Name
Fix in your script:
import-module activedirectory
$users = get-aduser -Filter {Name -Like "*"} -Searchbase "ou=Users, dc=Domain" | Where-Object { $_.Enabled -eq 'True' }
$targetFile = "D:\users.csv"
rm $targetFile
Add-Content $targetFile "User;Group"
foreach ($user in $users)
{
$dn = $user.DistinguishedName
$groups = Get-ADGroup -LDAPFilter ("(member:1.2.840.113556.1.4.1941:={0})" -f $dn) | select -expand Name | sort Name
foreach ($group in $groups)
{
$username = $user.samaccountname
$groupname = $group.name
$line = "$username;$groupname"
Add-Content $targetFile $line
}
}
If you make it a function you can call it recursively. Check this out, I think you'll be pleased with the results:
Function Get-ADGroupsRecursive{
Param([String[]]$Groups)
Begin{
$Results = #()
}
Process{
ForEach($Group in $Groups){
$Results+=$Group
ForEach($Object in (Get-ADGroupMember $Group|?{$_.objectClass -eq "Group"})){
$Results += Get-ADGroupsRecursive $Object
}
}
}
End{
$Results | Select -Unique
}
}
Toss that at the top of your script, and then call it for each user. Something like:
import-module activedirectory
$users = get-aduser -Filter {Name -Like "*"} -Searchbase "ou=Users, dc=Domain" -Properties MemberOf | Where-Object { $_.Enabled -eq 'True' }
$targetFile = "D:\users.csv"
rm $targetFile
Add-Content $targetFile "User;Group"
foreach ($user in $users)
{
$Groups = $User.MemberOf
$Groups += $Groups | %{Get-ADGroupsRecursive $_}
$Groups | %{New-Object PSObject -Property #{User=$User;Group=$_}}|Export-CSV $targetfile -notype -append
}
Now, depending on the size of your AD structure that may take quite a while, but it will get you what you were looking for.
It is very easy. Just use ActiveRoles Management Shell for Active Directory. Cmdlet Get-QADMemberOf with parameter Indirect is the one you are looking for. Example:
Get-QADMemberOf john.smith -Indirect
The Quest object returned already include All Recursive groupes (and first level users) in properties $_.AllMembers
Add-PSSnapin Quest.ActiveRoles.ADManagement
$UsersFirstLevel = ($Members | Get-QADObject -Type Group -DontUseDefaultIncludedProperties | Get-QADGroupMember -DontUseDefaultIncludedProperties | ?{$_.type -eq 'user'})
$UsersSubGroup = ($Members | Get-QADObject -Type Group -DontUseDefaultIncludedProperties | Get-QADGroupMember -DontUseDefaultIncludedProperties | ?{$_.type -eq 'group'}).Allmembers | Get-QADObject -DontUseDefaultIncludedProperties | ?{$_.type -eq 'user'}
$RecursiveUsers = $UsersFirstLevel
$RecursiveUsers += $UsersSubGroup
$RecursiveUsers = $RecursiveUsers | Sort-Object -Unique
Newer versions of PowerShell (AD Module) do have -Recursive switch. So you can easily use Get-ADGroupMember.
Example: Get-ADGroupMember -Identity My_Group -Recursive