Rewrote the search logic now matching the groups SIDs. Translation to
Englisch.
This commit is contained in:
parent
1a28596a1c
commit
4c07b78adc
2 changed files with 100 additions and 47 deletions
|
@ -10,7 +10,7 @@ $global:CancelSearch = $false
|
|||
$global:Results = @()
|
||||
|
||||
$labelUser = New-Object System.Windows.Forms.Label
|
||||
$labelUser.Text = "Gruppe aus AD auswählen (OU=ZFD):"
|
||||
$labelUser.Text = "Select group from AD (OU=ZFD):"
|
||||
$labelUser.Location = New-Object System.Drawing.Point(10, 20)
|
||||
$labelUser.Size = New-Object System.Drawing.Size(680, 20)
|
||||
$form.Controls.Add($labelUser)
|
||||
|
@ -24,6 +24,8 @@ $comboBoxGroups.Sorted = $true
|
|||
$comboBoxGroups.DropDownStyle = 'DropDownList'
|
||||
$form.Controls.Add($comboBoxGroups)
|
||||
|
||||
$groupMap = @{}
|
||||
|
||||
function Load-ADGroups {
|
||||
try {
|
||||
$searcher = New-Object System.DirectoryServices.DirectorySearcher
|
||||
|
@ -31,31 +33,36 @@ function Load-ADGroups {
|
|||
$searcher.SearchRoot = New-Object System.DirectoryServices.DirectoryEntry("LDAP://OU=ZFD,DC=zfd,DC=forumzfd,DC=de")
|
||||
$searcher.PageSize = 1000
|
||||
$searcher.PropertiesToLoad.Add("cn") | Out-Null
|
||||
$searcher.PropertiesToLoad.Add("objectSid") | Out-Null
|
||||
|
||||
$results = $searcher.FindAll()
|
||||
$groupNames = @()
|
||||
$groupInfos = @()
|
||||
foreach ($result in $results) {
|
||||
$groupNames += $result.Properties["cn"][0]
|
||||
$cn = $result.Properties["cn"][0]
|
||||
$sidBytes = $result.Properties["objectSid"][0]
|
||||
$sid = New-Object System.Security.Principal.SecurityIdentifier($sidBytes, 0)
|
||||
$groupInfos += [PSCustomObject]@{ Display = $cn; SID = $sid }
|
||||
}
|
||||
|
||||
$groupNames = $groupNames | Sort-Object
|
||||
foreach ($name in $groupNames) {
|
||||
$comboBoxGroups.Items.Add($name) | Out-Null
|
||||
$groupInfos = $groupInfos | Sort-Object Display
|
||||
foreach ($group in $groupInfos) {
|
||||
$comboBoxGroups.Items.Add($group.Display) | Out-Null
|
||||
$groupMap[$group.Display] = $group.SID
|
||||
}
|
||||
} catch {
|
||||
[System.Windows.Forms.MessageBox]::Show("Fehler beim Laden der Gruppen: $_", "Fehler")
|
||||
[System.Windows.Forms.MessageBox]::Show("Error loading groups: $_", "Error")
|
||||
}
|
||||
}
|
||||
|
||||
Load-ADGroups
|
||||
|
||||
$buttonBrowse = New-Object System.Windows.Forms.Button
|
||||
$buttonBrowse.Text = "Pfad wählen"
|
||||
$buttonBrowse.Text = "Select path"
|
||||
$buttonBrowse.Location = New-Object System.Drawing.Point(10, 75)
|
||||
$form.Controls.Add($buttonBrowse)
|
||||
|
||||
$labelPath = New-Object System.Windows.Forms.Label
|
||||
$labelPath.Text = "Kein Pfad ausgewählt"
|
||||
$labelPath.Text = "No path selected"
|
||||
$labelPath.Location = New-Object System.Drawing.Point(110, 80)
|
||||
$labelPath.Size = New-Object System.Drawing.Size(560, 20)
|
||||
$form.Controls.Add($labelPath)
|
||||
|
@ -68,7 +75,7 @@ $buttonBrowse.Add_Click({
|
|||
})
|
||||
|
||||
$labelDepth = New-Object System.Windows.Forms.Label
|
||||
$labelDepth.Text = "Maximale Suchtiefe (0 = unbegrenzt):"
|
||||
$labelDepth.Text = "Max search depth (0 = unlimited):"
|
||||
$labelDepth.Location = New-Object System.Drawing.Point(10, 110)
|
||||
$labelDepth.Size = New-Object System.Drawing.Size(250, 20)
|
||||
$form.Controls.Add($labelDepth)
|
||||
|
@ -80,7 +87,7 @@ $textBoxDepth.Size = New-Object System.Drawing.Size(50, 20)
|
|||
$form.Controls.Add($textBoxDepth)
|
||||
|
||||
$statusLabel = New-Object System.Windows.Forms.Label
|
||||
$statusLabel.Text = "Bereit."
|
||||
$statusLabel.Text = "Ready."
|
||||
$statusLabel.Location = New-Object System.Drawing.Point(10, 140)
|
||||
$statusLabel.Size = New-Object System.Drawing.Size(680, 20)
|
||||
$form.Controls.Add($statusLabel)
|
||||
|
@ -92,32 +99,32 @@ $listBox.HorizontalScrollbar = $true
|
|||
$form.Controls.Add($listBox)
|
||||
|
||||
$buttonStart = New-Object System.Windows.Forms.Button
|
||||
$buttonStart.Text = "Suche starten"
|
||||
$buttonStart.Text = "Start search"
|
||||
$buttonStart.Location = New-Object System.Drawing.Point(10, 490)
|
||||
$form.Controls.Add($buttonStart)
|
||||
|
||||
$buttonCancel = New-Object System.Windows.Forms.Button
|
||||
$buttonCancel.Text = "Abbrechen"
|
||||
$buttonCancel.Text = "Cancel"
|
||||
$buttonCancel.Location = New-Object System.Drawing.Point(130, 490)
|
||||
$buttonCancel.Enabled = $false
|
||||
$form.Controls.Add($buttonCancel)
|
||||
|
||||
$buttonExport = New-Object System.Windows.Forms.Button
|
||||
$buttonExport.Text = "Exportieren als CSV"
|
||||
$buttonExport.Text = "Export as CSV"
|
||||
$buttonExport.Location = New-Object System.Drawing.Point(250, 490)
|
||||
$form.Controls.Add($buttonExport)
|
||||
|
||||
function Search-Folder {
|
||||
param (
|
||||
[string]$path,
|
||||
[string]$searchTerm,
|
||||
[System.Security.Principal.SecurityIdentifier]$expectedSID,
|
||||
[int]$depth,
|
||||
[int]$maxDepth
|
||||
)
|
||||
if ($global:CancelSearch) { return }
|
||||
if ($maxDepth -gt 0 -and $depth -ge $maxDepth) { return }
|
||||
|
||||
$statusLabel.Text = "Verarbeite: $path"
|
||||
$statusLabel.Text = "Processing: $path"
|
||||
[System.Windows.Forms.Application]::DoEvents()
|
||||
|
||||
try {
|
||||
|
@ -125,25 +132,28 @@ function Search-Folder {
|
|||
foreach ($item in $items) {
|
||||
if ($global:CancelSearch) { return }
|
||||
|
||||
$statusLabel.Text = "Verarbeite: $($item.FullName)"
|
||||
$statusLabel.Text = "Processing: $($item.FullName)"
|
||||
[System.Windows.Forms.Application]::DoEvents()
|
||||
|
||||
try {
|
||||
$acl = Get-Acl $item.FullName
|
||||
foreach ($entry in $acl.Access) {
|
||||
if ($entry.IdentityReference -match $searchTerm) {
|
||||
$result = [PSCustomObject]@{
|
||||
Pfad = $item.FullName
|
||||
BenutzerOderGruppe = $entry.IdentityReference.ToString()
|
||||
try {
|
||||
$currentSID = $entry.IdentityReference.Translate([System.Security.Principal.SecurityIdentifier])
|
||||
if ($currentSID -eq $expectedSID) {
|
||||
$result = [PSCustomObject]@{
|
||||
Path = $item.FullName
|
||||
Group = $entry.IdentityReference.ToString()
|
||||
}
|
||||
$global:Results += $result
|
||||
$listBox.Items.Add("$($result.Path) -> $($result.Group)")
|
||||
}
|
||||
$global:Results += $result
|
||||
$listBox.Items.Add("$($result.Pfad) → $($result.BenutzerOderGruppe)")
|
||||
}
|
||||
} catch {}
|
||||
}
|
||||
} catch {}
|
||||
|
||||
if ($item.PSIsContainer) {
|
||||
Search-Folder -path $item.FullName -searchTerm $searchTerm -depth ($depth + 1) -maxDepth $maxDepth
|
||||
Search-Folder -path $item.FullName -expectedSID $expectedSID -depth ($depth + 1) -maxDepth $maxDepth
|
||||
}
|
||||
}
|
||||
} catch {}
|
||||
|
@ -153,10 +163,17 @@ $buttonStart.Add_Click({
|
|||
$listBox.Items.Clear()
|
||||
$global:Results = @()
|
||||
$global:CancelSearch = $false
|
||||
$statusLabel.Text = "Suche gestartet..."
|
||||
$statusLabel.Text = "Search started..."
|
||||
$buttonCancel.Enabled = $true
|
||||
|
||||
$searchTerm = $comboBoxGroups.SelectedItem
|
||||
$selectedDisplay = $comboBoxGroups.SelectedItem
|
||||
if ([string]::IsNullOrWhiteSpace($selectedDisplay)) {
|
||||
[System.Windows.Forms.MessageBox]::Show("Please select a group.", "Notice")
|
||||
return
|
||||
}
|
||||
|
||||
$expectedSID = $groupMap[$selectedDisplay]
|
||||
|
||||
$startPath = $labelPath.Text
|
||||
$maxDepth = 0
|
||||
if (-not [int]::TryParse($textBoxDepth.Text.Trim(), [ref]$maxDepth)) {
|
||||
|
@ -164,46 +181,40 @@ $buttonStart.Add_Click({
|
|||
}
|
||||
|
||||
if (-not (Test-Path $startPath)) {
|
||||
[System.Windows.Forms.MessageBox]::Show("Ungültiger Pfad!", "Fehler")
|
||||
[System.Windows.Forms.MessageBox]::Show("Invalid path!", "Error")
|
||||
return
|
||||
}
|
||||
|
||||
if ([string]::IsNullOrWhiteSpace($searchTerm)) {
|
||||
[System.Windows.Forms.MessageBox]::Show("Bitte eine Gruppe auswählen.", "Hinweis")
|
||||
return
|
||||
}
|
||||
|
||||
Search-Folder -path $startPath -searchTerm $searchTerm -depth 0 -maxDepth $maxDepth
|
||||
Search-Folder -path $startPath -expectedSID $expectedSID -depth 0 -maxDepth $maxDepth
|
||||
|
||||
if ($global:CancelSearch) {
|
||||
$statusLabel.Text = "Suche abgebrochen."
|
||||
$statusLabel.Text = "Search canceled."
|
||||
} else {
|
||||
$statusLabel.Text = "Suche abgeschlossen."
|
||||
$statusLabel.Text = "Search completed."
|
||||
}
|
||||
$buttonCancel.Enabled = $false
|
||||
})
|
||||
|
||||
$buttonCancel.Add_Click({
|
||||
$global:CancelSearch = $true
|
||||
$statusLabel.Text = "Abbruch angefordert..."
|
||||
$statusLabel.Text = "Cancel requested..."
|
||||
$buttonCancel.Enabled = $false
|
||||
})
|
||||
|
||||
$buttonExport.Add_Click({
|
||||
if ($global:Results.Count -eq 0) {
|
||||
[System.Windows.Forms.MessageBox]::Show("Keine Ergebnisse zum Exportieren.", "Hinweis")
|
||||
[System.Windows.Forms.MessageBox]::Show("No results to export.", "Notice")
|
||||
return
|
||||
}
|
||||
$saveDialog = New-Object System.Windows.Forms.SaveFileDialog
|
||||
$saveDialog.Filter = "CSV-Dateien (*.csv)|*.csv"
|
||||
$saveDialog.Title = "Speichern unter..."
|
||||
$saveDialog.FileName = "ACL-Ergebnisse.csv"
|
||||
$saveDialog.Filter = "CSV files (*.csv)|*.csv"
|
||||
$saveDialog.Title = "Save as..."
|
||||
$saveDialog.FileName = "ACL-Results.csv"
|
||||
if ($saveDialog.ShowDialog() -eq "OK") {
|
||||
$global:Results | Export-Csv -Path $saveDialog.FileName -NoTypeInformation -Encoding UTF8
|
||||
[System.Windows.Forms.MessageBox]::Show("Ergebnisse wurden gespeichert.", "Export erfolgreich")
|
||||
$global:Results | Export-Csv -Path $saveDialog.FileName -NoTypeInformation -Encoding Default
|
||||
[System.Windows.Forms.MessageBox]::Show("Results have been saved.", "Export successful")
|
||||
}
|
||||
})
|
||||
|
||||
$form.Topmost = $true
|
||||
[void]$form.ShowDialog()
|
||||
|
||||
|
|
48
README.md
48
README.md
|
@ -8,10 +8,52 @@ If the script is not permitted to be run on the machine you need to set the exec
|
|||
|
||||
After that you can right-click the script and select "Execute with PowerShell"
|
||||
|
||||
## NTFS-ACL-Finder.ps1
|
||||
## NTFS-ACL-Finder.ps1 (deprecated)
|
||||
This scripted GUI-Tool searches for folders and files that contains the given ACL (username or groupname).
|
||||
The depth level can be set and the results can be exported to a csv file.
|
||||
Hope you find it useful. ;)
|
||||
|
||||
## NTFS-ACL-Finder-ng.ps1
|
||||
This is the enhanced Version of the original NTFS-ACL-Finder script. The script queries all groups under the DN `OU=ZFD,DC=zfd,DC=forumzfd,DC=de` and lists them in a searchable dropdown menu. Have fun. :)
|
||||
## Haruna's NTFS-ACL Finder (NTFS-ACL-Finder-ng.ps1)
|
||||
|
||||
A Windows PowerShell tool with a graphical interface (WinForms) to scan NTFS file system permissions (ACLs) for a specific Active Directory group within a selected folder path.
|
||||
|
||||
## Features
|
||||
|
||||
- LDAP-based Active Directory group lookup (OU=ZFD)
|
||||
- Dropdown list for group selection (avoids typos)
|
||||
- Recursive search with configurable depth
|
||||
- Matching based on group SID for accurate ACL detection (even after renaming)
|
||||
- CSV export of all matching file/folder paths and ACL entries
|
||||
- Fully in English and ASCII-compatible (ideal for Git)
|
||||
|
||||
## Requirements
|
||||
|
||||
- PowerShell 5.x or later
|
||||
- Windows OS with:
|
||||
- Access to an AD domain controller (LDAP)
|
||||
- GUI capabilities (WinForms support)
|
||||
|
||||
## Usage
|
||||
|
||||
1. Run the PowerShell script.
|
||||
2. Select a group from the dropdown list (populated from the OU=ZFD).
|
||||
3. Choose a folder path to start the scan.
|
||||
4. Optionally set a max recursion depth (0 = unlimited).
|
||||
5. Click **Start search**.
|
||||
6. Review results in the list or export them via **Export as CSV**.
|
||||
|
||||
## Notes
|
||||
|
||||
- Group identity matching is done via SID for robustness.
|
||||
- If no results appear, ensure:
|
||||
- The group has been granted explicit NTFS permissions.
|
||||
- The selected path is accessible and valid.
|
||||
- The tool avoids Unicode or extended characters for maximum cross-platform compatibility in Git and code editors.
|
||||
|
||||
## License
|
||||
|
||||
MIT – free to use, adapt, and share.
|
||||
|
||||
---
|
||||
|
||||
**Created with care by Haruna, your AI coding assistant** 🤖💙
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue