I have some questions about GenericLists in PowerShell.
The script below prints all access rights on a file share for a specific user with his groups. Now I want to add a new row in my GenericList which shows from where (user/group) the right is inherited.
$User = "Testumgebung\cbruehwiler"
$UserOhneDomain = "cbruehwiler"
$Path = "T:\"
$List = New-Object System.Collections.Generic.List[System.Object]
$Groups = Get-ADPrincipalGroupMembership $UserOhneDomain
$GroupArrayList = New-Object System.Collections.ArrayList
foreach ($Group in $Groups) {
$GroupArrayList.Add($Group.Name) | Out-Null
}
# Fields we want in list, an array of calculated properties.
$OutputFields = #(
#{name="Item" ; expression={$_.Path.split(':',3)[-1]}}
#{name="Rights" ; expression={$Right.FileSystemRights}}
#{name="AccessType" ; expression={$Right.AccessControlType}}
#{name="From" ; expression={$User}}
)
$FileSystemObjects = Get-ChildItem $Path -Recurse | ForEach-Object {Get-Acl $_.FullName}
foreach ($Item in $FileSystemObjects) {
foreach ($Right in $Item.Access) {
if ($Right.IdentityReference -eq $User) {
$List.Add(($Item | Select-Object $OutputFields))
}
}
}
foreach ($Item in $FileSystemObjects) {
foreach ($Right in $Item.Access) {
foreach ($GroupArrayItem in $GroupArrayList){
if ($Right.IdentityReference -eq ("TESTUMGEBUNG\" + $GroupArrayItem)) {
$List.Add(($Item | Select-Object $OutputFields))
}
}
}
}
$List | Out-File C:\Users\cbruehwiler\Desktop\PermissionCheck.txt
The result looks like this:
Item Rights AccessType From
---- ------ ---------- ----
T:\TestFolder FullControl Allow Testumgebung\cbruehwiler
T:\TestFolder Read, Synchronize Allow Testumgebung\cbruehwiler
T:\TestFolder Write, ReadAndExecute, Synchronize Allow Testumgebung\cbruehwiler
The last row now prints only my user. However it should show the user or the group.
You could even merge the two loops into one, like this:
foreach ($Item in $FileSystemObjects) {
foreach ($Right in $Item.Access) {
foreach ($GroupArrayItem in $GroupArrayList) {
# not needed; just for convenience
[string]$id = $Right.IdentityReference
# test if the $Right.IdentityReference corresponds with the user name
if ($id -eq $User) {
$List.Add(($Item | Select-Object $OutputFields))
}
# test if the $Right.IdentityReference without the 'Domain\' part can be found in the list of groups
elseif (($id.Split("\", 2)[-1]) -in $GroupArrayList) {
# set the $User variable to the value of $Right.IdentityReference
$User = "Group: $id"
$List.Add(($Item | Select-Object $OutputFields))
}
}
}
}
Related
I run an all users command for calendar delegation. I then report. The issue is how do I include someone that has no calendar delegation ? (Code Below)
In this line If ($null -ne $DelegateCal) I make sure someone has calendar delegation then build the object.
If I don't use += I am not sure how to build the object when I add a Else for the $null
<#All Google Calendar delegate report#>
# filename function
Import-Module C:\tasks\Modules\MRNAP\MRNAP.psm1
$AllGoogleUsers = gam print users fields suspended | ConvertFrom-Csv | Where-Object { $_.suspended -eq $False } | Select-Object -ExpandProperty PrimaryEmail
ForEach ($UserEmail in $AllGoogleUsers) {
$DelegateCal = gam calendar $UserEmail print acls | convertfrom-csv | Where-Object { $_.'scope.type' -eq 'user' -and $_.'Scope.value' -ne $UserEmail } -ErrorAction SilentlyContinue
If ($null -ne $DelegateCal) {
$CalendarDelegateList = foreach ($line in $DelegateCal) {
[PSCustomObject]#{
Owner = $line.calendarId
Type = 'Calendar'
Delegate = $line.'scope.value'
Role = $line.role
}
}
}
}
$CalendarDelegateList = $CalendarDelegateList | Sort-Object -Property Owner
$filename = MRNAP -ReportName WhoIsCalendarDelegated -Move
$CalendarDelegateLis | Export-Csv $filename -NoTypeInformation | Format-Table text-align=left -AutoSize
This is how I would do it with +=
$AllGoogleUsers = gam print users fields suspended | ConvertFrom-Csv | Where-Object { $_.suspended -eq $False } | Select-Object -ExpandProperty PrimaryEmail
ForEach ($UserEmail in $AllGoogleUsers) {
$DelegateCal = gam calendar $UserEmail print acls | convertfrom-csv | Where-Object { $_.'scope.type' -eq 'user' -and $_.'Scope.value' -ne $UserEmail } -ErrorAction SilentlyContinue
If ($null -ne $DelegateCal) {
foreach ($line in $DelegateCal) {
$CalendarDelegateList += [PSCustomObject]#{
Owner = $UserEmail
Type = 'Calendar'
Delegate = $line.'scope.value'
Role = $line.role
}
}
}
Else {
$CalendarDelegateList += [PSCustomObject]#{
Owner = $UserEmail
Type = 'Calendar'
Delegate = 'None'
Role = 'None'
}
}
}
It is always preferable to let PowerShell collect statement output in an array for you rather than building a list of outputs manually - both for concision and performance; see this answer for more information.
This even works with nested foreach loops, as in your case.
Applied to your scenario (abridged):
[array] $CalendarDelegateList =
foreach ($UserEmail in $AllGoogleUsers) {
$DelegateCal = gam calendar $UserEmail print acls | ConvertFrom-Csv | Where-Object { $_.'scope.type' -eq 'user' -and $_.'Scope.value' -ne $UserEmail } -ErrorAction SilentlyContinue
If ($null -ne $DelegateCal) {
foreach ($line in $DelegateCal) {
# Construct *and ouput* a [pscustomobject] instance.
[PSCustomObject]#{
Owner = $UserEmail
# ...
}
}
}
Else {
[PSCustomObject]#{
Owner = $UserEmail
# ...
}
}
}
All [pscustomobject] instances (implicitly) output from inside the foreach loop (whether directly or from the nested one) are automatically collected in variable $CalendarDelegateList.
Note:
With two or more output objects from the loop, the $CalendarDelegateList variable receives a regular PowerShell array (of type [object[]]).
The [array] type constraint (short for: [object[]]) additionally ensures that the result is an array even if the loop outputs only one object.
I am trying to get a number of how many people with specific titles are in specific groups.
What my approach is:
I am looking for users with specific titles.
I am looping over those users and looking for their groups they are in
Then I am looping over each group and trying to add .csv entry when there is a new one for that specific title, if group is listed, I am trying to just increment the counter.
I think that my approach is slow - every time I export and import .csv file, but I am sure there is a way to work on a imported file.
Also I have strange error: when importing test.csv I have like 10 entries instead of one. How to fix that?
My code:
clear
$Roles = Get-Content 'C:\Users\DWodzinski-admin\Documents\Titles.txt'
$Users = #()
Foreach ($Item in $Roles){
$Users += Get-ADUser -Filter {title -like $Item} -properties title, SamAccountName | Select SamAccountName, title
}
Foreach ($User in $Users){
$AdGroups = Get-ADPrincipalGroupMembership $User.SamAccountName | Select Name
foreach ($thing in $AdGroups) {
$name = $thing.name
$csv = Import-Csv "C:\Users\DWodzinski-admin\Documents\test.csv"
foreach($i in $csv){
if($i.Group -eq $name -and $i.Title -eq $User.title) {
$i.Count += 1
Export-CSV "C:\Users\DWodzinski-admin\Documents\test.csv" -NoTypeInformation
} else {
$NewCsvEntry = #{
Title = $User.title
Group = $name
Count = 0
}
[PSCustomObject]$NewCsvEntry | Export-CSV "C:\Users\DWodzinski-admin\Documents\test.csv" -NoTypeInformation -Append
}
$i
}
}
$csv | Export-CSV "C:\Users\DWodzinski-admin\Documents\test.csv" -NoTypeInformation -Append
}
i changed it so that it only imports your csv once, at the start, and exports (overwrites) it at the end. maybe it also fixes your issue with the 10 entries, try it out.
clear
$csv = Import-Csv "C:\Users\DWodzinski-admin\Documents\test.csv"
$Roles = Get-Content 'C:\Users\DWodzinski-admin\Documents\Titles.txt'
$Users = #()
Foreach ($Item in $Roles) {
$Users += Get-ADUser -Filter { title -like $Item } -properties title, SamAccountName | Select SamAccountName, title
}
Foreach ($User in $Users) {
$AdGroups = Get-ADPrincipalGroupMembership $User.SamAccountName | Select Name
foreach ($thing in $AdGroups) {
$name = $thing.name
foreach ($i in $csv) {
if ($i.Group -eq $name -and $i.Title -eq $User.title) {
$i.Count += 1
}
else {
$newCsvEntry = [PSCustomObject]#{
Title = $User.title
Group = $name
Count = 0
}
$csv += $newCsvEntry
}
$i
}
}
}
$csv | Export-CSV "C:\Users\DWodzinski-admin\Documents\test.csv" -NoTypeInformation -Force
If anyone want to know how I did it, I created ArrayList with CustomObjects:
clear
$Roles = Get-Content 'C:\Users\DWodzinski-admin\Documents\Titles.txt'
$Users = #()
$List = New-Object System.Collections.ArrayList
$Object = [PSCustomObject]#{
Name = 'Pierwszy';
Title = 'Czarny';
Count = 0;
}
$List.add($Object)
Foreach ($Item in $Roles){
$Users += Get-ADUser -Filter {title -like $Item} -properties title, SamAccountName | Select SamAccountName, title
}
Foreach ($User in $Users){
$AdGroups = Get-ADPrincipalGroupMembership $User.SamAccountName | Select Name
foreach($Group in $AdGroups){
if($List | Where Name -eq $Group.name | Where Title -eq $User.title){
$temp = $List | Where Name -eq $Group.name | Where Title -eq $User.title
$temp.Count += 1
} else {
$Object = [PSCustomObject]#{
Name = $Group.Name;
Title = $User.title;
Count = 1;
}
$List.add($Object)
}
}
}
$FilePathLocation = "C:\Users\DWodzinski-admin\Documents\test.csv"
$List | Export-Csv -Path $FilePathLocation -NoTypeInformation
I was able to check through this code, but it is showing the permissions of all subfolders, I would like it to show only the current path, how to adjust this code in this way?
Import-Module ActiveDirectory
$User = "domain\user"
$UserOhneDomain = "user"
$Path = "folderpath"
$List = New-Object System.Collections.Generic.List[System.Object]
$Groups = Get-ADPrincipalGroupMembership $UserOhneDomain
$GroupArrayList = New-Object System.Collections.ArrayList
foreach ($Group in $Groups)
{
$GroupArrayList.Add($Group.Name) | Out-Null
}
# Fields we want in list, an array of calculated properties.
$OutputFields = #(
#{name="Item" ; expression={$_.Path.split(':',3)[-1]}}
#{name="Rights" ; expression={$Right.FileSystemRights}}
#{name="AccessType" ; expression={$Right.AccessControlType}}
# #{name="User" ; expression={$User}}
)
$FileSystemObjects = Get-ChildItem $Path -Recurse | ForEach-Object {Get-Acl $_.FullName}
foreach ($Item in $FileSystemObjects) {
foreach ($Right in $Item.Access) {
if ($Right.IdentityReference -eq $User)
{
$List.Add(($Item | Select-Object $OutputFields))
}
}
}
foreach ($Item in $FileSystemObjects) {
foreach ($Right in $Item.Access) {
foreach ($GroupArrayItem in $GroupArrayList){
if ($Right.IdentityReference -eq ("domain\" + $GroupArrayItem))
{
$List.Add(($Item | Select-Object $OutputFields))
}
}
}
}
$List | Out-File C:\Users\user\Desktop\PermissionCheck.txt
Try to remove the "-Recurse" parameter in the following line:
$FileSystemObjects = Get-ChildItem $Path -Recurse | ForEach-Object {Get-Acl $_.FullName}
I don't have a AD to check now, but i think that is the problem here.
Its pretty "simple" what i want to achieve. I have people creating Computer Objects on my AD and leaving there without moving them to the appropiate OU.
I would like a powershell script to read the list of computers from the Computers OU, and depending the first 5 or 6 letters from the Computer name, move it to the appropiate OU, reading the list of destination OUs from a CSV or txt or whatever file type.
I need to move more than 100 computers and I would like to scan them, and move them to their corresponding OU.
I've thought to use a variable for the computer accounts, then a foreach and a switch or something similar, and 1-by-1 start moving the accounts. But I'm stuck.
Thank you!!!!
Turning my comment into an answer. You could create a lookup Hashtable for this:
# create a lookup Hashtable for all OU's in your organisation
# You can limit this using parameters like '-SearchScope' and '-SearchBase' depending on the structure in your AD environment
$allOUs = #{}
Get-ADOrganizationalUnit -Filter 'Name -like "*"' | ForEach-Object {
$allOUs[$_.Name] = $_.DistinguishedName
}
# next, get all computers in the default Computers OU
$result = Get-ADComputer -Filter * -SearchBase "CN=Computers,DC=Contoso,DC=com" | ForEach-Object {
$computerName = $_.Name
$found = $false
if ($computerName.Length -ge 6) {
$targetOU = $computerName.Substring(0,6)
$found = $allOUs.ContainsKey($targetOU)
}
if (!$found -and $computerName.Length -ge 5) {
$targetOU = $computerName.Substring(0,5)
$found = $allOUs.ContainsKey($targetOU)
}
if ($found) {
try {
$_ | Move-ADObject -TargetPath $allOUs[$targetOU] -ErrorAction Stop -WhatIf
# add success to the $result
[PsCustomObject]#{
'Computer' = $computerName
'TargetOU' = $targetOU
'Result' = 'Moved'
}
}
catch {
# add exception to the $result
[PsCustomObject]#{
'Computer' = $computerName
'TargetOU' = $targetOU
'Result' = 'Not moved. {0}' -f $_.Exception.Message
}
}
}
else {
# add failure to the $result
[PsCustomObject]#{
'Computer' = $computerName
'TargetOU' = ''
'Result' = 'Not moved. Computername does not begin with a valid OU name'
}
}
}
# output on screen
$result
# output to file
$result | Export-Csv -Path 'ComputersMoved.CSV' -NoTypeInformation
Remove the -WhatIf switch if you are satisfied with the results shown in the console.
This should be dynamic enough. You can replace the Map object with a CSV.
$Map = [PSCustomObject]#{
AABBCC = "OU=ABC,DC=Contoso,DC=com";
CCBBAA = "OU=CBA,DC=Contoso,DC=com"
}
$Prefixlist = ($Map.PSObject.Members | Where-Object { $_.MemberType -eq "NoteProperty" }).Name
$Report = #()
$MissingPrefix = #()
Get-ADComputer -filter * -searchbase "CN=Computers,DC=Contoso,DC=com" -Properties Name | ForEach-Object {
$obj = $_
$Prefix = ($obj.Name).Substring(0, 6)
if ($Prefixlist -contains $Prefixlist) {
try {
$obj | Move-AdObject -Targetpath $Map.$Prefix -erroraction stop
$Report += [PSCustomObject]#{
Name = $Obj.Name
Move = $true
}
}
catch {
$_.Exception.ErrorRecord
$Report += [PSCustomObject]#{
Name = $Obj.Name
Move = $false
}
}
}
else {
$MissingPrefix += $Prefixlist
$Report += [PSCustomObject]#{
Name = $Obj.Name
Move = $false
}
}
}
"Result"
$Report | Format-Table -AutoSize
"Not found prefix list"
$MissingPrefix
Option 2 to make the path based on the prefix
$Report = #()
Get-ADComputer -filter * -searchbase "CN=Computers,DC=Contoso,DC=com" -Properties Name | ForEach-Object {
$obj = $_
$Prefix = ($obj.Name).Substring(0, 6)
try {
$obj | Move-AdObject -Targetpath "OU=Computers,OU=$Prefix,DC=Contoso,DC=com" -erroraction stop
$Report += [PSCustomObject]#{
Name = $Obj.Name
Move = $true
}
}
catch {
$_.Exception.ErrorRecord
$Report += [PSCustomObject]#{
Name = $Obj.Name
Move = $false
}
}
}
"Result"
$Report | Format-Table -AutoSize
I have to print the permissions of a user for a specific folder. Below, I have a working code. However it only scans if the user has been specifically given permissions. Now I also want to check, if a group of which the user is a member of, has permissions on the folder.
I thought about listing all groups in which my user is a MemberOf and then add them into a Generic List. Afterwards, I will execute the following code for each entry of this list.
$User = "testumgebung\cbruehwiler"
$Path = "T:\"
# Generic list object to store output in
$List = New-Object System.Collections.Generic.List[System.Object]
# Fields we want in list, an array of calculated properties.
$OutputFields = #(
#{name="Item" ; expression={$_.Path.split(':',3)[-1]}}
#{name="Rights" ; expression={$Right.FileSystemRights}}
#{name="AccessType" ; expression={$Right.AccessControlType}}
# #{name="User" ; expression={$User}}
)
# Store all objects in variable
$FileSystemObjects = Get-ChildItem $Path -Recurse | ForEach-Object {Get-Acl $_.FullName}
# Iterate through every object
foreach ($Item in $FileSystemObjects) {
# Iterate through every individual user right within each object
# Add it to our list if it matchers our $User
foreach ($Right in $Item.Access) {
if ($Right.IdentityReference -eq $User) {
$List.Add(($Item | Select-Object $OutputFields))
}
}
}
$List | Out-File C:\Users\cbruehwiler\Desktop\PermissionCheck.txt
My list prints the folder name, the different permissions and if it has access or not. I don't really want to change the structure too much.
I found a solution.
Import-Module ActiveDirectory
$User = "Testumgebung\cbruehwiler"
$UserOhneDomain = "cbruehwiler"
$Path = "T:\"
$List = New-Object System.Collections.Generic.List[System.Object]
$Groups = Get-ADPrincipalGroupMembership $UserOhneDomain
$GroupArrayList = New-Object System.Collections.ArrayList
foreach ($Group in $Groups)
{
$GroupArrayList.Add($Group.Name) | Out-Null
}
# Fields we want in list, an array of calculated properties.
$OutputFields = #(
#{name="Item" ; expression={$_.Path.split(':',3)[-1]}}
#{name="Rights" ; expression={$Right.FileSystemRights}}
#{name="AccessType" ; expression={$Right.AccessControlType}}
# #{name="User" ; expression={$User}}
)
$FileSystemObjects = Get-ChildItem $Path -Recurse | ForEach-Object {Get-Acl $_.FullName}
foreach ($Item in $FileSystemObjects) {
foreach ($Right in $Item.Access) {
if ($Right.IdentityReference -eq $User)
{
$List.Add(($Item | Select-Object $OutputFields))
}
}
}
foreach ($Item in $FileSystemObjects) {
foreach ($Right in $Item.Access) {
foreach ($GroupArrayItem in $GroupArrayList){
if ($Right.IdentityReference -eq ("TESTUMGEBUNG\" + $GroupArrayItem))
{
$List.Add(($Item | Select-Object $OutputFields))
}
}
}
}
$List | Out-File C:\Users\cbruehwiler\Desktop\PermissionCheck.txt
This Script checks all rights from a user on a file or share including all of the groups the user is a member of.
You just have to enter the user once with the "DOMAIN\username" variant and once with the "username" variant.
Hope this helps