AdminBox.psm1

<#
.SYNOPSIS
    Sets the permissions for a user home directory.
.DESCRIPTION
    Sets the permissions for a user home directory by inheriting the rights
    of the parent folder and in addition granting the user full control explicitly.
    All subdirectories and files of the user home directory are set to inherit permissions from the parent.
.PARAMETER Path
    The path to a user home directory.
.PARAMETER User
    Specifies the Domain\Name of the object that will be granted full control
    and become the owner of all subdirectories and files in the home path
.EXAMPLE
    C:\PS> Set-UserDirPermissions -Path 'C:\Users\Norman.Mann' -User 'Norman.Mann'
.EXAMPLE
    Get-ChildItem -Path 'C:\Users' | foreach-object { Set-UserDirPermission -Path $_.FullName -User $_.Name }
#>

function Set-UserDirPermission {   
    [CmdletBinding()]
    param (
    [Parameter(Position=0, Mandatory=$true, ValueFromPipeline=$true)]
    [ValidateNotNullOrEmpty()]
    [string[]]$Path,
    
    [Parameter(Position=1, Mandatory=$true)]
    [string]$User,

    [Parameter(Mandatory=$false)]
    [switch]$Force=$false
    )
    
    Begin {
        Grant-Privilege -Name SeRestorePrivilege
        $owner = new-object System.Security.Principal.NTAccount($User)
    }
    
        
    Process {
        # Take over ownership this directory and all subdirectory and files
        Reset-OwnerShip -path $Path -Recurse
        # Get permissions
        # Get-Acl and Set-Acl do not support -LiteralPath. A workaround was necessary
        # $acl = Get-Acl -path $Path
        
        $item = Get-Item -LiteralPath $Path
        Write-Verbose $item.FullName
        $acl = $item.GetAccessControl()
            
        # Do not protect against inheritance
        $acl.SetAccessRuleProtection($false, $true);
        # Remove all explicit permissions
        $acl.GetAccessRules($true, $false, [System.Security.Principal.SecurityIdentifier]) | foreach-object { [void]$acl.RemoveAccessRule($_)}
        # Add explicit permission for user and set owner
        $accessRule = new-object System.Security.AccessControl.FileSystemAccessRule($User, "FullControl", "ContainerInherit,ObjectInherit", "None", "Allow")
        $acl.SetAccessRule($accessRule)
        $acl.SetOwner($owner)
        # Save permissions
        # Set-Acl -path $Path -AclObject $acl
        $item.SetAccessControl($acl)
            
        # Get all child items, remove all explicit permissions

        Get-ChildItem -path $Path -Recurse -Force:$Force | ForEach-Object {
        
            Write-Verbose $_.PSPath
            # Get permissions
            # $acl = Get-Acl -path $_.PSPath
            
            $item = Get-Item -LiteralPath $_.PSPath -Force
            $acl = $item.GetAccessControl()
            
            # Do not protect against inheritance
            $acl.SetAccessRuleProtection($false, $true);
            # Remove all explicit permissions
            $acl.GetAccessRules($true, $false, [System.Security.Principal.SecurityIdentifier]) | foreach-object { [void]$acl.RemoveAccessRule($_)}
            $acl.SetOwner($owner)
            # Save permissions
            # Set-Acl -path $_.PSPath -AclObject $acl
            $item.SetAccessControl($acl)
        }
    }
}

<#
.SYNOPSIS
    Generates a random password.
.DESCRIPTION
    Generates a random password by randomly taking an entry from the list of
    provided words and decorating it with a random number.
.PARAMETER InputObject
    Specifies a collection of objects. Get-RandomPassword gets randomly selected objects in random order from the collection.
    Enter the objects, a variable that contains the objects, or a command or expression that gets the objects.
    You can also pipe a collection of objects to Get-RandomPassword.
.EXAMPLE
    $words = Get-Content -Path 'C:\Password.txt'
    Get-RandomPassword -InputObject $words
#>


function Get-RandomPassword {   
    [CmdletBinding()]
    param (

    [Parameter(Position=0, ParameterSetName="InputObject", Mandatory=$true, ValueFromPipeline=$true)]
    [ValidateNotNullOrEmpty()]
    [PSObject[]]$InputObject,

    [Parameter(ParameterSetName = "Path", Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)]
    [string[]]$Path,

    [Parameter(Mandatory=$false)]
    [switch]$CopyToClipboard=$true

    )

    Process {
    
        if($Path) {
            $InputObject = Get-Content -Path $Path
        }
        
        $password = $InputObject | Get-Random 
        [Int32]$pos = $password.IndexOf(' ')
        if($pos -eq -1) {$pos = $password.Length / 2}
        
        $number = Get-Random -Min 1 -Max 99
        $password = $password.Insert($pos, "!" + $number).Replace(' ', '')

        if($CopyToClipboard) { 
            Set-Clipboard -Value $password 
        }

        Write-Output $password
    }
}

function Get-RandomPasswordSample {
    Get-RandomPassword -Path (Join-Path -Path $PSScriptRoot -ChildPath "Password.txt") -CopyToClipboard
}
New-Alias -Name grp -Value Get-RandomPasswordSample


function Compress-Path {
    [CmdletBinding()]
    param (
    [Parameter(Position=0, Mandatory=$true, ValueFromPipeline=$true)]
    [ValidateNotNullOrEmpty()]
    [string]$Path,    
    [Parameter(Position=1)]
    [int]$MaxLength = 64
    )

$definition = @'
[DllImport("shlwapi.dll", CharSet = CharSet.Auto, SetLastError = true)] public static extern bool PathCompactPathEx( System.Text.StringBuilder pszOut, string pszSrc, Int32 cchMax, Int32 dwFlags);
'@

    Add-Type -MemberDefinition $definition -name StringFunctions -namespace Win32 
    $sb = New-Object System.Text.StringBuilder(260)
    $result = [Win32.StringFunctions]::PathCompactPathEx($sb , $Path , $MaxLength+1, 0)
    $sb.ToString()
}


<#
.SYNOPSIS
    Grants minimum permissions for browsing a folder hierarchy.
.DESCRIPTION
    Grants minimum permissions required for browsing a folder hierarchy
    to parent folders of the specified directory.
.PARAMETER Path
    Specifies a path to one or more locations. Wildcards are permitted. The default location is the current directory (.).
.PARAMETER Group
    Specifies the Domain\Name of the object that will be granted permissions.
.PARAMETER LevelsUp
    Determines the number of parent directories that that will processed while climbing up the hierarchy.
.PARAMETER ToLevel
    Specifies the hierarchy level where assigment of permissions stops.
.EXAMPLE
    C:\PS> Grant-BrowsePermissions -Path '\\domain.local\FS\Departments\IT\Help' -Group 'DOMAIN\IT' -LevelsUp 3
.EXAMPLE
    C:\PS> Grant-BrowsePermissions -Path '\\domain.local\FS\Departments\IT\Help' -Group 'DOMAIN\IT' -ToLevel 2
#>


function Grant-BrowsePermissions {
    [CmdletBinding(SupportsShouldProcess=$true)]
    param (
        [Parameter(ParameterSetName="LevelsUp", Position=0, Mandatory=$true, ValueFromPipeline=$true)]
        [Parameter(ParameterSetName="ToLevel", Position=0, Mandatory=$true, ValueFromPipeline=$true)]
        [ValidateNotNullOrEmpty()]
        [string[]]$Path,
        
        [Parameter(ParameterSetName="LevelsUp", Position=1, Mandatory=$true)]
        [Parameter(ParameterSetName="ToLevel", Position=1, Mandatory=$true)]
        [ValidateNotNullOrEmpty()]
        [string]$Group,
        
        [Parameter(ParameterSetName="LevelsUp", Position=2, Mandatory=$true)]
        [ValidateRange(1,30)]
        [int]$LevelsUp = 1,

        [Parameter(ParameterSetName="ToLevel", Position=2, Mandatory=$true)]
        [ValidateRange(1,100)]
        [int]$ToLevel
               
    )
    
    Process {
       
        Get-Acl -Path $Path | Foreach-Object {
            $acl = $_
            Write-Verbose $acl.Path
            
            $accessRules = $acl.GetAccessRules($true, $false, [System.Security.Principal.NTAccount]) #| Where-Object { $_.IdentityReference.Value -eq $Group }
            if($accessRules.Count -eq 0) { throw "No explicit access rules could be retrieved for the specified path." }
            
            $accessRule = $accessRules | Where-Object { $_.IdentityReference.Value -eq $Group }
            if($accessRule -eq $null) { throw "No access rules could be found for the specified identity." }
            
            $identityReference = $accessRule.IdentityReference 
            #$identityReference.Value
            
            $item = Get-Item -Path $acl.Path
            
            $regExResult = Select-String -InputObject $item.FullName -Pattern "([A-Z]:\\|\\\\[^\\]+\\)([^\\]*\\*)*"
            if($regExResult -eq $null) { throw "unexpected" }
            $depth = $regExResult.Matches[0].Groups[2].Captures.Count-1
            Write-Debug "$($item.FullName) [Path Depth: $depth]"
            switch($PSCmdLet.ParameterSetName) {
                "LevelsUp" {
                    
                    if($depth -lt $LevelsUp) { throw "not possible" }
                    $traversals = $LevelsUp
                    break;
                }
                
                "ToLevel" {
                  
                    if($ToLevel -ge $depth) { throw "nonsense" }
                    $traversals = $depth - $ToLevel                
                    break;
                }   
            }
            
            for($i=0; $i -lt $traversals; $i++) {
                $item = $item.Parent
                $acl = Get-Acl -Path $item.FullName
                
                
                $accessRules = $acl.GetAccessRules($true, $false, [System.Security.Principal.NTAccount]) | Where-Object { $_.IdentityReference.Value -eq $Group }
                if($accessRules -eq $null) { 
                    $accessRule = new-object System.Security.AccessControl.FileSystemAccessRule($identityReference, "Read", "None", "None", "Allow")
                    $acl.SetAccessRule($accessRule)
                    Set-Acl -Path $item.FullName -AclObject $acl
                }
                else {
                    Write-Warning "$($item.FullName) already contains access rules for the specified identity." 
                }            
            }       
        }
    }
}


function New-ProtectedItemGroupName {
    [CmdletBinding(SupportsShouldProcess=$true)]
    param (
        [Parameter(Position=0, Mandatory=$true, ValueFromPipeline=$true)]
        [ValidateNotNullOrEmpty()]
        [string[]]$Path,

        [Parameter(Position=1, Mandatory=$true)]
        [ValidateNotNullOrEmpty()]
        [string[]]$Permission,
        
        [Parameter(Position=2, Mandatory=$false)]
        [string]$SiteCode,
        
        [Parameter(Mandatory=$false)]        
        [switch]$NoServerName=$false,

        [Parameter(Mandatory=$false)]
        [string]$Server
    )

    Process {

        $siteCodeWithSeparator = $SiteCode + $(if(-Not [String]::IsNullOrWhiteSpace($SiteCode)) { " " })
        
        foreach($directory in $Path) {
            Write-Verbose $directory 

            if($NoServerName) {
                $corePath = $directory -replace "^\\\\[^\\]+\\"               
            }
            else
            {
                # Replace site code if it is part of the server name or just leading backslashes
                $corePath = "$($directory -replace "^\\\\$SiteCode|\\\\")" 
            }
            
            foreach($fileSystemRight in $Permission) {
                $fileSystemRightText = $fileSystemRight.Replace("ReadAndExecute", "Read")
                $groupName = Compress-Path -Path $corePath -MaxLength (64 - $siteCodeWithSeparator.Length - $fileSystemRightText.Length - 3) # additional 3 character for <blank> and () of permission suffix
                $groupName = "$siteCodeWithSeparator$($groupName -replace "\\", "-") ($fileSystemRightText)"
                $groupName = $groupName.Trim() -replace "[\\,+]", "-"
                Write-Debug "$groupName [$($groupName.Length)]"
                Write-Output $groupName
            }
        }
    }
}

function Protect-Item {
    [CmdletBinding(SupportsShouldProcess=$true)]
    param (
        [Parameter(Position=0, Mandatory=$true, ValueFromPipeline=$true)]
        [ValidateNotNullOrEmpty()]
        [string[]]$Path,
        
        [Parameter(Position=1, Mandatory=$true)]
        [ValidateNotNullOrEmpty()]
        [string]$GroupContainer,
        
        [Parameter(Position=2, Mandatory=$true)]
        [ValidateNotNullOrEmpty()]
        [string[]]$Permission,
        
        [Parameter(Position=3, Mandatory=$false)]
        [string]$SiteCode,
        
        [Parameter(Mandatory=$false)]        
        [switch]$NoServerName=$false,

        [Parameter(Mandatory=$false)]
        [string]$Server
     
    )
    Process {
    
        foreach($p in $Path) {
            if((Test-Path -Path $p -PathType Container) -eq $false) {
                Write-Verbose $p 
                throw "Path does not exist."
            }
        }
       
        
        $siteCodeWithSeparator = $SiteCode + $(if(-Not [String]::IsNullOrWhiteSpace($SiteCode)) { " " })
        
        foreach($directory in $Path) {
            Write-Verbose $directory 
            if($NoServerName) {
                $corePath = $directory -replace "^\\\\[^\\]+\\"               
            }
            else
            {
                # Replace site code if it is part of the server name or just leading backslashes
                $corePath = "$($directory -replace "^\\\\$SiteCode|\\\\")" 
            }

            foreach($fileSystemRight in $Permission) {
                $fileSystemRightText = $fileSystemRight.Replace("ReadAndExecute", "Read")
                $groupName = Compress-Path -Path $corePath -MaxLength (64 - $siteCodeWithSeparator.Length - $fileSystemRightText.Length - 3) # additional 3 character for <blank> and () of permission suffix
                $groupName = "$siteCodeWithSeparator$($groupName -replace "\\", "-") ($fileSystemRightText)"
                $groupName = $groupName.Trim() -replace "[\\,+]", "-"
                Write-Debug "$groupName [$($groupName.Length)]"
                $group = $null
                if([String]::IsNullOrEmpty($Server)) {                    
                    $group = Get-ADGroup -Filter {(name -eq $groupName)}
                    if($group -eq $null) {
                        $group = New-ADGroup -Name $groupName -Path $GroupContainer -GroupScope DomainLocal -Description $directory -PassThru
                    }
                }
                else {
                    $group = Get-ADGroup -Filter {(name -eq $groupName)} -Server $Server 
                    if($group -eq $null) {
                        $group = New-ADGroup -Name $groupName -Path $GroupContainer -GroupScope DomainLocal -Description $directory -PassThru -Server $Server 
                    }
                }
                if($group -ne $null) {
                    $acl = Get-Acl -Path $directory
                    $accessRule = new-object System.Security.AccessControl.FileSystemAccessRule($group.SID, $fileSystemRight, "ObjectInherit, ContainerInherit", "None", "Allow")                
                    $acl.SetAccessRule($accessRule)
                    #$acl.AddAccessRule($accessRule)
                    Set-Acl -Path $directory -AclObject $acl
                }

                Write-Output $group
            }
        }
    }  
}


<#
.SYNOPSIS
    Determines whether a file is locked.
.DESCRIPTION
    The Test-Lock cmdlet determines whether all a file is locked.
    It returns TRUE ($true) if the file is locked and otherwise FALSE ($false)
.PARAMETER Path
    Specifies a path to be tested. Wildcards are permitted. If the path includes spaces, enclose it in quotation marks.
.EXAMPLE
    C:\PS> Test-Lock -Path 'C:\Users\Norman.Mann\Flag.txt'
#>

function Test-Lock {   
    [CmdletBinding()]
    param (
        [Parameter(Position=0, Mandatory=$true, ValueFromPipeline=$true)]
        [ValidateNotNullOrEmpty()]
        [string[]]$Path
    )

    Process {

        foreach($p in $Path) {
            
            $sendToPipeline = $false
            
            if((Test-Path -Path $p -PathType Leaf) -eq $false) {
                Write-Verbose $p 
                throw "File does not exist."
            }

            try{
                $stream = [System.IO.File]::Open($p,[System.IO.FileMode]::Open, [System.IO.FileAccess]::Read, [System.IO.FileShare]::None)
            }
            catch [System.IO.IOException] {    
                $sendToPipeline = $true
            }
            finally {
                if($stream -ne $null) {
                    $stream.Dispose()
                }
            }

            $sendToPipeline
        }
    }
}


<#
.SYNOPSIS
    Writes a message to a log files
.DESCRIPTION
    Writes a message to a log files
.EXAMPLE
    C:\PS> Write-Log -Path 'C:\Users\Norman.Mann\Flag.txt'
#>

function Write-Log {   
    [CmdletBinding()]
    param (
        [Parameter(Position=0, Mandatory=$true, ValueFromPipeline=$true)]
        [ValidateNotNullOrEmpty()]
        [string]$Message,

        [Parameter(Position=1)]
        [IO.FileInfo] $Path = ”$env:temp\PowerShellLog.txt”,
                
        [Parameter()]
        [ValidateSet("Error", "Warning", "Information")]
        [string] $Level = "Information",

        [Parameter()] 
        [ValidateRange(1,30)]
        [Int16] $Indent = 0

    )

    Begin {}

    Process {
        $messageText = '{0}{1} : {2} : {3}' -f (" " * $Indent), (Get-Date -Format “yyyy-MM-dd HH:mm:ss”), $Level.ToUpper(), $Message

        switch ($Level) {
            'Error' { Write-Error $Message }
            'Warning' { Write-Warning $Message }
            'Information' { Write-Host ('{0}{1}' -f (" " * $Indent), $Message) -ForegroundColor White}
        }
        $messageText| Out-File -FilePath $Path -Append
    }
    End {}
}

#Export-ModuleMember -Alias grp -Function Write-Log, Test-Lock, Protect-Item, New-ProtectedItemGroupName, Grant-BrowsePermissions, Compress-Path, Get-RandomPassword, Set-UserDirPermission