I have the following code that returns the ntfs permissions for a particular folder path.
$folders = Get-ChildItem -path d:\test" -recurse -force | ?{ $_.psiscontainer }
$output = #()
foreach($folder in $folders)
{
$rights = Get-Acl -path $folder.fullname
foreach($right in $rights.Access)
{
$properties = [ordered]#{'foldername'=$folder.fullname;'username'=$right.identityreference;'permissions'=$right.filesystemrights}
$output += New-Object -TypeName psobject -Property $properties
}
}
$output | export-csv d:\output\folders_temp1.csv -NoTypeInformation -Encoding UTF8
Though it displays the username along with its respective permissions, I would like to display the first and last name associated to that user from the active directory.
Any ideas on how can that be achieved?
Thank you for your help.
Here is how I would approach this, using Group-Object so that you're not querying Active Directory for the same user over and over for each Access Control List.
It's important to note that #() and += is inefficient and that PSCustomObject can be casted which is also, more efficient than using an ordered hashtable and then converting it to a New-Object.
Another efficiency improvement, thanks to Mathias R. Jessen for his helpful feedback, is to implement a hash table ($map) to have a reference of the IdentityReference already queried, by doing so we would only be querying Active Directory only once per unique user.
# $_.PSIsContainer => Can be replaced with -Directory
$folders = Get-ChildItem -Path "D:\test" -Recurse -Force -Directory
$map = #{}
$output = foreach($folder in $folders)
{
$rights = Get-Acl -Path $folder.fullname
$groups = $rights.Access | Group-Object IdentityReference
foreach($group in $groups)
{
if(-not $map.ContainsKey($group.Name))
{
$ref = Split-Path $group.Name -Leaf
$user = Get-ADUser -LDAPFilter "(name=$ref)"
$map[$group.Name] = $user
}
$aduser = $map[$group.Name]
foreach($acl in $group.Group)
{
[pscustomobject]#{
GivenName = $aduser.GivenName
Surname = $aduser.Surname
Foldername = $folder.Fullname
UserName = $acl.IdentityReference
Permissions = $acl.FilesystemRights
}
}
}
}
$output | Export-Csv D:\output\folders_temp1.csv -NoTypeInformation -Encoding UTF8
Related
I am trying to match the samaccountname to the current folder patch, and it would worked fine if the folderpatch was "d:\profile\username" but instead of username its states username_S-1-5-21*....
I use the below code, but is it possible to filter everything behind the _S-1.... so it can match the username to the samaccountname ?
I use the below code, any help would be appreciated
[EDIT: added complete script]
Below is the complete script, so the issue is that i have several user folder with _S-1-5-21* behind the username in the folder of our FXLogic profile folder and need to match the samaccountname with the folder ( username - _S-1-5-21* )
I hope this explanation is more clear, and yes its a SID not a GUID always get them mixed up.
param(
[Parameter(Mandatory=$true)]
$FXLogicFolderPath,
$MoveFolderPath,
$SearchBase,
[string[]]$ExcludePath,
[switch]$FolderSize,
[switch]$MoveDisabled,
[switch]$DisplayAll,
[switch]$UseRobocopy,
[switch]$RegExExclude,
[switch]$CheckFXLogicDirectory)
Check if FXLogicFolderPath is found, exit with warning message if path is incorrect
if (!(Test-Path -LiteralPath $FXLogicFolderPath)){
Write-Warning "FXLogicFolderPath not found: $FXLogicFolderPath"
Check if MoveFolderPath is found, exit with warning message if path is incorrect
if ($MoveFolderPath) {
if (!(Test-Path -LiteralPath $MoveFolderPath)){
Write-Warning "MoveFolderPath not found: $MoveFolderPath"
exit
}}
exit
Main loop, for each folder found under FXLogic folder path AD is queried to find a matching samaccountname
$ListOfFolders = Get-ChildItem -LiteralPath "$FXLogicFolderPath" -Force | Where-Object {$_.PSIsContainer}
Exclude folders if the ExcludePath parameter is given
if ($ExcludePath) {
$ExcludePath | ForEach-Object {
$CurrentExcludePath = $_
if ($RegExExclude) {
$ListOfFolders = $ListOfFolders | Where-Object {$_.FullName -notmatch $CurrentExcludePath}
} else {
$ListOfFolders = $ListOfFolders | Where-Object {$_.FullName -ne $CurrentExcludePath}
}
}}
$ListOfFolders | ForEach-Object {
$CurrentPath = Split-Path -Path $_ -Leaf
Construct AD Searcher, add SearchRoot attribute if SearchBase parameter is specified
$ADSearcher = New-Object DirectoryServices.DirectorySearcher -Property #{
Filter = "(samaccountname=$CurrentPath)"
}
if ($SearchBase) {
$ADSearcher.SearchRoot = [adsi]$SearchBase
}
Use the FullName path to look for a FXLogicdirectory attribute and replace the backslash by the \5C LDAP escape character
if ($CheckFXLogicDirectory) {
$ADSearcher.Filter = "(FXLogicdirectory=$($_.FullName -replace '\\','\5C')*)"
}
Execute AD Query and store in $ADResult
$ADResult = $ADSearcher.Findone()
If no matching samaccountname is found this code is executed and displayed
if (!($ADResult)) {
$HashProps = #{
'Error' = 'Account does not exist and has a FXLogic folder'
'FullPath' = $_.FullName
}
if ($FolderSize) {
$HashProps.SizeinBytes = [long](Get-ChildItem -LiteralPath $_.Fullname -Recurse -Force -ErrorAction SilentlyContinue |
Measure-Object -Property Length -Sum -ErrorAction SilentlyContinue | Select-Object -Exp Sum)
$HashProps.SizeinMegaBytes = "{0:n2}" -f ($HashProps.SizeinBytes/1MB)
}
if ($MoveFolderPath) {
$HashProps.DestinationFullPath = Join-Path -Path $MoveFolderPath -ChildPath (Split-Path -Path $_.FullName -Leaf)
if ($UseRobocopy) {
robocopy $($HashProps.FullPath) $($HashProps.DestinationFullPath) /E /MOVE /R:2 /W:1 /XJD /XJF | Out-Null
} else {
Move-Item -LiteralPath $HashProps.FullPath -Destination $HashProps.DestinationFullPath -Force
}
}
Output the object
New-Object -TypeName PSCustomObject -Property $HashProps
If samaccountname is found but the account is disabled this information is displayed
} elseif (([boolean]((-join $ADResult.Properties.useraccountcontrol) -band 2))) {
$HashProps = #{
'Error' = 'Account is disabled and has a FXLogic folder'
'FullPath' = $_.FullName
}
if ($FolderSize) {
$HashProps.SizeinBytes = [long](Get-ChildItem -LiteralPath $_.Fullname -Recurse -Force -ErrorAction SilentlyContinue |
Measure-Object -Property Length -Sum -ErrorAction SilentlyContinue | Select-Object -Exp Sum)
$HashProps.SizeinMegaBytes = "{0:n2}" -f ($HashProps.SizeinBytes/1MB)
}
if ($MoveFolderPath -and $MoveDisabled) {
$HashProps.DestinationFullPath = Join-Path -Path $MoveFolderPath -ChildPath (Split-Path -Path $_.FullName -Leaf)
Move-Item -LiteralPath $HashProps.FullPath -Destination $HashProps.DestinationFullPath -Force
}
Output the object
New-Object -TypeName PSCustomObject -Property $HashProps
Folders that do have active user accounts are displayed if -DisplayAll switch is set
} elseif ($ADResult -and $DisplayAll) {
$HashProps = #{
'Error' = $null
'FullPath' = $_.FullName
}
if ($FolderSize) {
$HashProps.SizeinBytes = [long](Get-ChildItem -LiteralPath $_.Fullname -Recurse -Force -ErrorAction SilentlyContinue |
Measure-Object -Property Length -Sum -ErrorAction SilentlyContinue | Select-Object -Exp Sum)
$HashProps.SizeinMegaBytes = "{0:n2}" -f ($HashProps.SizeinBytes/1MB)
}
Output the object
New-Object -TypeName PSCustomObject -Property $HashProps
}}
# Construct AD Searcher, add SearchRoot attribute if SearchBase parameter is specified
$ADSearcher = New-Object DirectoryServices.DirectorySearcher -Property #{
Filter = "(samaccountname=$CurrentPath)"
}
if ($SearchBase) {
$ADSearcher.SearchRoot = [adsi]$SearchBase
The S-1-5-21... part is not a GUID, it's a SID (the principal's Security Identifier).
You can use the -replace operator to remove that part of the folder name:
$folderName = 'username_S-1-5-21-2855571654-3033049851-1520320983-9328'
$userName = $folderName -replace '_S-1-5.*$'
After which you can construct the desired LDAP query filter:
$ADSearcher = New-Object DirectoryServices.DirectorySearcher -Property #{
Filter = "(samaccountname=$userName)"
}
Not sure where your username is being derived from but you could do something simple with a regex replace on the $CurrentPath
$currentPath = 'username_S-1-5-21-2855571654-3033049851-1520320983-9328'
$currentPath = $currentPath -replace '_.+'
# AD Searcher here
This will replace everything after the underscore but all usernames will have to be in a consistent format or you'll have to take into account all possible formats that could be encountered.
Okay so we are setting up a card access system that looks at the Active Directory Users thumbnailPhoto attribute. I am creating an audit system that exports the Users and compares them with the JPG images. If the image exists but there isn't a correlating user, it moves the image into an archive to be reviewed. The goal is to remove old employee photos into a folder incase of later hire. I can't get the image to move into another folder if it matches a name in the CSV. Here is the entire code:
<#Write Users to a CSV File #>
$adUsers = get-aduser -filter * -properties displayname | select displayname | export-csv -path PATHWAY.CSV -notypeinformation -encoding unicode
$keepImages = #()
$removeImages = #()
[System.Collections.ArrayList]$arrA = (Get-Childitem -Filter * -path PATHWAY).Basename
[System.Collections.ArrayList]$arrB = Get-Content PATHWAY.CSV
foreach ($itemA in $arrA) {
if ($arrB -ne $itemA) {
$arrB.Remove($itemA)
$removeImages += $itemA }}
$removeImages |out-file -FilePath PATH.csv
<# PUT THE FILES INTO AN ARCHIVE #>
--Cant get it to move here, note I am brand new to Powershell, its not like python at all--
You can try this. I have added inline comments to hopefully explain how it works:
$ImagesFolder = 'D:\UserImages'
$OldUserImages = 'D:\UserImages\OldUsers'
# test if the path to move old images exists and if not create it
if (!(Test-Path -Path $OldUserImages -PathType Container)) {
$null = New-Item -Path $OldUserImages -ItemType Directory
}
# get a list of ADUser display names
$adUsers = Get-ADUser -Filter * -Properties DisplayName | Select-Object -ExpandProperty DisplayName
# get an array of FileInfo objects of the user images currently in the $ImagesFolder.
# filter out only those that do not have a basename that correlates to any of the users DisplayName
# and move these to the $OldUserImages folder.
# Tip: if for instance all are of type JPG, add -Filter '*.jpg' to the Get-ChildItem cmdlet.
Get-ChildItem -Path $ImagesFolder -File |
Where-Object { $adUsers -notcontains $_.BaseName } |
Move-Item -Destination $OldUserImages -Force
If you want to keep track of the images you have moved, you can extend the above like:
$moved = Get-ChildItem -Path $ImagesFolder -File |
Where-Object { $adUsers -notcontains $_.BaseName } |
ForEach-Object {
$file = $_.FullName
$_ | Move-Item -Destination $OldUserImages -Force
[PsCustomObject]#{
'File' = $file
'MovedTo' = $OldUserImages
}
}
# show result on screen
$moved | Format-Table -AutoSize
# write to CSV file
$out = '{0:yyyy-MM-dd}_MovedImages.csv' -f (Get-Date)
$moved | Export-Csv -Path (Join-Path -Path $ImagesFolder -ChildPath $out) -NoTypeInformation
I've created a script that will read a list of usernames in a file named Users.csv and then search a network share for their user profile. It then reports on the shortcut target paths of said user profile and exports that to another .csv.
It works great when I use it per user but when I get it to report the data to the .csv file I can only seem to get it to report on the last user in my Users.csv and not each one. I was thinking it is because the export part of the script overwrites the report.csv for each user it runs and I need it to create a unique report.csv for each username. Anyone have any ideas?
$Users = (Get-Content C:\temp\Users.csv) -notmatch '^\s*$'
foreach ($User in $Users) {
$Shortcuts = Get-ChildItem -Recurse \\UNCPATHTOUSERPROFILES\$User -Include *.lnk
$Shell = New-Object -ComObject WScript.Shell
$data = foreach ($Shortcut in $Shortcuts) {
[PSCustomObject]#{
ShortcutName = $Shortcut.Name;
Target = $Shell.CreateShortcut($Shortcut).targetpath
User = $User
}
}
[Runtime.InteropServices.Marshal]::ReleaseComObject($Shell) | Out-Null
}
foreach ($User in $Users) {
$data | Export-Csv c:\temp\report.csv -NoTypeInformation
}
You should combine your two outer foreach loops, not only since they're iterating the same collection, but because the second loop is trying to use a variable, $data, created in the first loop. Further, since Export-Csv is being called in a loop, you will need to pass the -Append parameter to prevent the output file from being overwritten each time.
$Users = (Get-Content C:\temp\Users.csv) -notmatch '^\s*$'
foreach ($User in $Users) {
$Shortcuts = Get-ChildItem -Recurse \\UNCPATHTOUSERPROFILES\$User -Include *.lnk
$Shell = New-Object -ComObject WScript.Shell
$data = foreach ($Shortcut in $Shortcuts) {
[PSCustomObject]#{
ShortcutName = $Shortcut.Name;
Target = $Shell.CreateShortcut($Shortcut).targetpath
User = $User
}
}
$data | Export-Csv c:\temp\report.csv -Append -NoTypeInformation
[Runtime.InteropServices.Marshal]::ReleaseComObject($Shell) | Out-Null
}
You could also eliminate the $Shortcuts and $data variables in favor of using the pipeline...
$Users = (Get-Content C:\temp\Users.csv) -notmatch '^\s*$'
foreach ($User in $Users) {
$Shell = New-Object -ComObject WScript.Shell
Get-ChildItem -Recurse \\UNCPATHTOUSERPROFILES\$User -Include *.lnk `
| ForEach-Object -Process {
[PSCustomObject]#{
ShortcutName = $_.Name;
Target = $Shell.CreateShortcut($_).targetpath
User = $User
}
} `
| Export-Csv c:\temp\report.csv -Append -NoTypeInformation
[Runtime.InteropServices.Marshal]::ReleaseComObject($Shell) | Out-Null
}
...but note that -Append is still required. Finally, you could rewrite the whole thing using the pipeline...
$Shell = New-Object -ComObject WScript.Shell
try
{
Get-Content C:\temp\Users.csv `
| Where-Object { $_ -notmatch '^\s*$' } -PipelineVariable 'User' `
| ForEach-Object -Process { "\\UNCPATHTOUSERPROFILES\$User" } `
| Get-ChildItem -Recurse -Include *.lnk `
| ForEach-Object -Process {
[PSCustomObject]#{
ShortcutName = $_.Name;
Target = $Shell.CreateShortcut($_).targetpath
User = $User
} `
} `
| Export-Csv c:\temp\report.csv -NoTypeInformation
}
finally
{
[Runtime.InteropServices.Marshal]::ReleaseComObject($Shell) | Out-Null
}
...so that report.csv is only opened for writing once, with no -Append needed. Note that I am creating a single WScript.Shell instance and using try/finally to ensure it gets released.
In the event a user in Users.csv has no directory under \\UNCPATHTOUSERPROFILES, any of the above solutions and the code in the question will throw an error when Get-ChildItem tries to enumerate that directory. You could fix that by checking that the directory exists first or passing -ErrorAction SilentlyContinue to Get-ChildItem, or you could enumerate \\UNCPATHTOUSERPROFILES and filter on those that occur in Users.csv...
$Shell = New-Object -ComObject WScript.Shell
try
{
# Performs faster filtering and eliminates duplicate user rows
$UsersTable = Get-Content C:\temp\Users.csv `
| Where-Object { $_ -notmatch '^\s*$' } `
| Group-Object -AsHashTable
# Get immediate child directories of \\UNCPATHTOUSERPROFILES
Get-ChildItem -Path \\UNCPATHTOUSERPROFILES -Directory -PipelineVariable 'UserDirectory' `
<# Filter for user directories specified in Users.csv #> `
| Where-Object { $UsersTable.ContainsKey($UserDirectory.Name) } `
<# Get *.lnk descendant files of the user directory #> `
| Get-ChildItem -Recurse -Include *.lnk -File `
| ForEach-Object -Process {
[PSCustomObject]#{
ShortcutName = $_.Name;
Target = $Shell.CreateShortcut($_).targetpath
User = $UserDirectory.Name
} `
} `
| Export-Csv c:\temp\report.csv -NoTypeInformation
}
finally
{
[Runtime.InteropServices.Marshal]::ReleaseComObject($Shell) | Out-Null
}
this script works for finding a particular user who has any explicit permissions on a folder within a folder structure, which is good! However, is there an easier way to list the folder path and $_.Access.IdentityReference.Value without having to loop my way in? Or, is this actually okay?
$foldStruct = get-childitem "C:\temp" -recurse -Attributes D | get-acl
ForEach ($fold in $foldStruct) {
ForEach ($perm in $fold.Access.IdentityReference) {
ForEach ($user in $perm.Value) {
If ($user -like "Dom\A*" -or $user -like "Dom\B*") {
Write-Host $user
Write-Host $fold.Path
}
}
}
}
It's debatable if this IS easier.
A more PowerShell way is to have objects as output.
instead of two -like I'd use the RegEx based -match with an alternation
the nested ForEach could be replaced with a Where-Object and a Select-Object
the Path would include Microsoft.PowerShell.Core\FileSystem:: I'd remove that with a -split '::'.
## Q:\Test\2019\02\07\SO_54569198.ps1
$Base = "C:\temp"
# to use a -match instead of -like anchor at begin and escape the \ => \\
$Users = "^Dom\\A|^Dom\\B"
$folderACLs = Get-ChildItem $Base -Recurse -Directory | Get-Acl |
Where-Object {$_.Access.IdentityReference.Value -match $Users } |
Select-Object #{n='User';e={($_.Access.IdentityReference.Value|?{$_ -match $Users})}},
#{n='Path';e={($_.Path -split '::')[1] }}
The output could/would include multiple users in the column, so to seperate them:
ForEach($folderACL in $folderACLs){
ForEach($User in ($folderACL.User){
[PSCustomObject]#{
User = $User
Path = ($_.folderACL.Path -split '::')[1]
}
}
}
I was thinking somewhere around the same thing as LotPings, using a regex -match to filter the users instead of doing -like twice.
I came up with this:
$users = '^Dom\\[AB].*' # regex to find usernames beginning with 'A' or 'B'
$subfolders = Get-ChildItem -Path "C:\Temp" -Recurse -Directory
foreach ($folder in $subfolders) {
$folder | Get-Acl | ForEach-Object { $_.Access } |
Where-Object {$_.IdentityReference.Value -match $users} | ForEach-Object {
[PSCustomObject]#{
'Folder' = $folder.FullName
'User' = $_.IdentityReference
# add extra info about access type for this user if you like
# 'AccessControlType' = $_.AccessControlType
# 'IsInherited' = $_.IsInherited
# 'InheritanceFlags' = $_.InheritanceFlags
# 'PropagationFlags' = $_.PropagationFlags
}
}
}
I am currently using this to get a list of all permissions for specific folders.
`$InputFile = "C:\temp\Folders.txt"
$OutputFile = "C:\temp\FolderPermissions.txt"
$FolderList = Get-Content $InputFile
ForEach ($Folder in $FolderList)
{
Get-Acl $folder | Format-list >>$OutputFile
}
`
What i would like is for it to then remove all access apart from administrator from each of the specified folders.
I have looked at using SetAccessControl but can only manage to get it to remove all.
Could someone please point me in the right direction.
Thanks
The following code will remove any user execpt users matching 'administrator'
If you want to add more accounts add it to the Where-Object filter, for example:
Where-Object {$_.IdentityReference -match 'Administrator' -or $_.IdentityReference -eq 'NT AUTHORITY\SYSTEM'}
$InputFile = "C:\temp\Folders.txt"
$OutputFile = "C:\temp\FolderPermissions.txt"
$FolderList = Get-Content $InputFile
ForEach ($Folder in $FolderList)
{
Get-Acl $folder | Format-list >>$OutputFile
### Remove all ACL Rules exepet 'Administrator(s)'
$ACL = Get-ACL -Path $Folder
$Rules = $ACL.Access | Where-Object {$_.IdentityReference -notmatch 'Administrator'}
Foreach ($Rule in $Rules)
{
[Void]$ACL.RemoveAccessRule($Rule)
}
Set-Acl -Path $folder -AclObject $acl
}