Since users are familiar with the tree hierarchy I wanted to be able to output something to them that would maintain this look and feel. I also wanted them to be able to hide sections of the output. If a large part of the structure is just inheriting permissions, doesn't apply, have been dealt with, or whatever, I wanted them to be able to shut that section down.
PowerShell doesn't really have a good output for tree views. Sure, tree is around, but it wouldn't serve my needs. Maybe I just don't know it well enough yet...
I didn't want an Excel output, because it really doesn't have a great tree view. Again, maybe I just don't know the product well enough.
I chose to create a simple HTML file as the output and use some simple CSS and JavaScript to get the functionality I wanted. One of the cleanest tree views I have seen comes from W3Schools.com. They have a great site for this kind of thing and their tree view was exactly what I was looking for.
Once I figured out how to layout the HTML and how the nested unorganized lists would work, the whole thing came together pretty quickly. PowerShell has some out of the box cmdlets that do the heavy lifting. I made use of the normal Get-Item and Get-ChildItem cmdlets to get collections of the folder objects, then simply called the Get-Acl cmdlet to get the security of the folder.
The ACL return object of the Get-Acl cmdlet has a handy property, isinherited, that will let you know if the folder inherits security or has custom security.
The only tricky part of all of this was getting the HTML set up. The nesting works by having the child items contained in an unorganized list with a "nested" class designation enclosed within the parent's line item. The parent line item is surrounded by a caret class to attach the JavaScript click functionality.
In deep folder taxonomies, the list nesting and closing can get pretty complex. I solved this by using three functions... One that set the parent folder HTML structure, one that closed the parent HTML structure, and one that created the childless folder structure.
When all of the shouting was over, I had a reasonably elegant script that determined if a folder had child items. If it did, it was designated a parent item and the parent HTML was written. The child folder collection was then sent to a loop that simply called on the function that determined if it was a parent, and so on.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
$version = $PSVersionTable | |
Write-Host -ForegroundColor Red "Ensure that your Powershell Version is greater than 5.1" | |
Write-Host -ForegroundColor Green "Your Powershell version: " + $version.PSVersion.ToString() | |
$shares = Read-Host -prompt "P Drive Share Path" | |
$outputPath = Read-Host -prompt "Full path and filename to output HTML file" | |
function letsGo(){ | |
$htmlStuff = @' | |
<html> | |
<head> | |
<style> | |
/* Remove default bullets */ | |
ul, #myUL { | |
list-style-type: none; | |
} | |
/* Remove margins and padding from the parent ul */ | |
#myUL { | |
margin: 0; | |
padding: 0; | |
} | |
/* Style the caret/arrow */ | |
.caret { | |
cursor: pointer; | |
user-select: none; /* Prevent text selection */ | |
} | |
/* Create the caret/arrow with a unicode, and style it */ | |
.caret::before { | |
content: "\25B6"; | |
color: black; | |
display: inline-block; | |
margin-right: 6px; | |
transform: rotate(90deg); | |
} | |
/* Rotate the caret/arrow icon when clicked on (using JavaScript) */ | |
.caret-down::before { | |
content: "\25B6"; | |
color: black; | |
display: inline-block; | |
margin-right: 6px; | |
transform: rotate(270deg); | |
} | |
/* Hide the nested list */ | |
.nested { | |
display: block; | |
} | |
/* Show the nested list when the user clicks on the caret/arrow (with JavaScript) */ | |
.active { | |
display: none; | |
} | |
</style> | |
</head> | |
<body> | |
<ul id="myUL"> | |
'@ | |
$javaScriptTag = @' | |
</ul> | |
<script> | |
var toggler = document.getElementsByClassName("caret"); | |
var i; | |
for (i = 0; i < toggler.length; i++) { | |
toggler[i].addEventListener("click", function() { | |
this.parentElement.querySelector(".nested").classList.toggle("active"); | |
this.classList.toggle("caret-down"); | |
}); | |
} | |
</script> | |
'@ | |
Write-Output $htmlStuff | |
resolveTop $safeRoot | |
Write-Output $javaScriptTag | |
Write-Output "</body></html>" | |
} | |
# Sets list structure for parent folders | |
function setParentStructure($parentFolder){ | |
Write-Output "<li><span class='caret'>$parentFolder</span>" | |
getAcl $parentFolder | |
Write-Output "<ul class='nested'>" | |
} | |
# Parent list items must enclose the entirety of the nested unorgnized lists this closes that structure | |
function setCloseParentStructure(){ | |
Write-Output "</ul>" | |
Write-Output "</li>" | |
} | |
# Folders with no children are set as singular list items | |
function setSingleStructure($singleFolder){ | |
Write-Output "<li>" | |
Write-Output $singleFolder.Name | |
getAcl $singleFolder | |
Write-Output "</li>" | |
} | |
# Determines if folder has children, sets structure for both single and parent folders | |
function iterateStructure($folders){ | |
$childFolders = Get-ChildItem -LiteralPath $folders.FullName -Directory | |
if($childFolders.Length -gt 0){ | |
setParentStructure $folders | |
resolveChildren $childFolders | |
setCloseParentStructure | |
}else{ | |
setSingleStructure $folders | |
} | |
} | |
# Resolves top most folder and kicks off the iterations | |
function resolveTop($topFolderPath){ | |
$topFolder = Get-Item $topFolderPath | |
iterateStructure $topFolder | |
} | |
# Iterates through item collection for each child item | |
function resolveChildren($folderCollection){ | |
foreach($childFolder in $folderCollection){ | |
iterateStructure $childFolder | |
} | |
} | |
#Checks the ACL and writes them out as unorgnized lists | |
function getAcl($aclFolder){ | |
$theAcl = Get-Acl -LiteralPath $aclFolder.FullName | |
if($theAcl -eq $null){ | |
$theAcl = Get-Acl -Path ([Management.Automation.WildcardPattern]::Escape($aclFolder.FullName)) | |
} | |
Write-Output "<ul><ul>" | |
try{ | |
$inherit = $theAcl.access.isinherited[0] | |
}catch{ | |
Write-Output "error" | |
} | |
#if ( $inherit -eq $false -or $space -eq "") { | |
if ( $inherit -eq $false){ | |
foreach ($access in $theAcl.access) { | |
Write-Output "<li> User: $($access.identityreference) Rights: $($access.FileSystemRights)</li>" | |
} | |
}else{ | |
Write-Output "<li> Inherited from Parent</li>" | |
} | |
Write-Output "</ul></ul>" | |
} | |
# Add suffix to use Get-Item and Get-ChildItem on file paths greater the 256 charicters | |
$uncCheck = $shares.Substring(0,2) | |
if($uncCheck -eq "\\"){ | |
$safeRoot = $shares.Replace("\\","\\?\UNC\") | |
}else{ | |
$safeRoot = "\\?\$shares" | |
} | |
letsGo | Out-File $outputPath |
No comments:
Post a Comment