Monday, November 19, 2018

PowerShell Security Scan With Tree View Output

As I started my work to migrate files from file shares to SharePoint, I realized that I needed to have some sort of scan of the existing structure and security as it is.  I needed this to be friendly enough to hand over to regular users so that they could have an idea of what kind of work needed to be done for migration preparation. 

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.
$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