HostUtilities.psm1

$script:EsentDllPath = "$env:SYSTEMROOT\Microsoft.NET\assembly\GAC_MSIL\microsoft.isam.esent.interop\v4.0_10.0.0.0__31bf3856ad364e35\Microsoft.Isam.Esent.Interop.dll"

Function Reset-WindowsUpdate {
    <#
        .SYNOPSIS
            The cmdlet resets all of the windows update components and re-registers the dlls.
 
        .DESCRIPTION
            Several services are stopped, the log files and directories are renamed, several dlls are re-registered, and then the services are restarted.
 
        .PARAMETER AutomaticReboot
            Specify whether the computer should automatically reboot after completing the reset.
 
        .INPUTS
            None
 
        .OUTPUTS
            None
 
        .EXAMPLE
            Reset-WindwsUpdate
 
            Resets windows update and does not automatically reboot.
 
        .EXAMPLE
            Reset-WindowsUpdate -AutomaticReboot
 
            Resets windows update and automatically reboots the machine.
 
        .NOTES
            AUTHOR: Michael Haken
            LAST UPDATED: 11/14/2016
 
            The command should be run with administrative credentials.
    #>


    [CmdletBinding()]
    Param(
        [Parameter(Position=0)]
        [switch]$AutomaticReboot = $false
    )

    Begin {
        if(!(Test-IsLocalAdmin)) {
            throw "This cmdlet must be run with admin credentials."
        }
    }

    Process
    {
        try
        {
            Stop-Service -Name BITS -ErrorAction Stop
        }
        catch [Exception]
        {
            Write-Warning -Message "Could not stop the BITS service"
            Exit 1
        }

        try
        {
            Stop-Service -Name wuauserv -ErrorAction Stop
        }
        catch [Exception]
        {
            Write-Warning -Message "Could not stop the wuauserv service"
            Exit 1
        }

        try
        {
            Stop-Service -Name AppIDSvc -ErrorAction Stop
        }
        catch [Exception]
        {
            Write-Warning -Message "Could not stop the AppIDSvc service"
            Exit 1
        }

        try
        {
            Stop-Service -Name CryptSvc -ErrorAction Stop
        }
        catch [Exception]
        {
            Write-Warning -Message "Could not stop the CryptSvc service"
            Exit 1
        }

        try
        {
            Clear-DnsClientCache -ErrorAction Stop
        }
        catch [Exception]
        {
            Write-Warning -Message "Could not clear the dns client cache"
        }

        Remove-Item -Path "$env:ALLUSERSPROFILE\Application Data\Microsoft\Network\Downloader\qmgr*.dat"

        if (Test-Path -Path "$env:SYSTEMROOT\winsxs\pending.xml.bak")
        {
            Remove-Item -Path "$env:SYSTEMROOT\winsxs\pending.xml.bak" -Recurse -Force
        }

        if (Test-Path -Path "$env:SYSTEMROOT\winsxs\pending.xml")
        {
            Rename-Item -Path "$env:SYSTEMROOT\winsxs\pending.xml" -NewName "$env:SYSTEMROOT\winsxs\pending.xml.bak"
        }

        if (Test-Path -Path "$env:SYSTEMROOT\SoftwareDistribution.bak")
        {
            Remove-Item -Path "$env:SYSTEMROOT\SoftwareDistribution.bak" -Recurse -Force
        }

        if (Test-Path -Path "$env:SYSTEMROOT\SoftwareDistribution") 
        {
            Rename-Item -Path "$env:SYSTEMROOT\SoftwareDistribution" -NewName "$env:SYSTEMROOT\SoftwareDistribution.bak"
        }

        if (Test-Path -Path "$env:SYSTEMROOT\system32\Catroot2.bak") 
        {
            Remove-Item -Path "$env:SYSTEMROOT\system32\Catroot2.bak" -Recurse -Force
        }

        if (Test-Path -Path "$env:SYSTEMROOT\system32\Catroot2") 
        {
            Rename-Item -Path "$env:SYSTEMROOT\system32\Catroot2" -NewName "$env:SYSTEMROOT\system32\Catroot2.bak"
        }

        if (Test-Path -Path "$env:SYSTEMROOT\WindowsUpdate.log.bak")
        {
            Remove-Item -Path "$env:SYSTEMROOT\WindowsUpdate.log.bak" -Recurse -Force
        }

        if (Test-Path -Path "$env:SYSTEMROOT\WindowsUpdate.log")
        {
            Rename-Item -Path "$env:SYSTEMROOT\WindowsUpdate.log" -NewName "$env:SYSTEMROOT\WindowsUpdate.log.bak"
        }

        & "$env:SYSTEMROOT\system32\sc.exe" sdset "BITS" "D:(A;;CCLCSWRPWPDTLOCRRC;;;SY)(A;;CCDCLCSWRPWPDTLOCRSDRCWDWO;;;BA)(A;;CCLCSWLOCRRC;;;AU)(A;;CCLCSWRPWPDTLOCRRC;;;PU)" | Out-Null
        & "$env:SYSTEMROOT\system32\sc.exe" sdset "wuauserv" "D:(A;;CCLCSWRPWPDTLOCRRC;;;SY)(A;;CCDCLCSWRPWPDTLOCRSDRCWDWO;;;BA)(A;;CCLCSWLOCRRC;;;AU)(A;;CCLCSWRPWPDTLOCRRC;;;PU)" | Out-Null

        regsvr32.exe /s atl.dll 
        regsvr32.exe /s urlmon.dll 
        regsvr32.exe /s mshtml.dll 
        regsvr32.exe /s shdocvw.dll 
        regsvr32.exe /s browseui.dll 
        regsvr32.exe /s jscript.dll 
        regsvr32.exe /s vbscript.dll 
        regsvr32.exe /s scrrun.dll 
        regsvr32.exe /s msxml.dll 
        regsvr32.exe /s msxml3.dll 
        regsvr32.exe /s msxml6.dll 
        regsvr32.exe /s actxprxy.dll 
        regsvr32.exe /s softpub.dll 
        regsvr32.exe /s wintrust.dll 
        regsvr32.exe /s dssenh.dll 
        regsvr32.exe /s rsaenh.dll 
        regsvr32.exe /s gpkcsp.dll 
        regsvr32.exe /s sccbase.dll 
        regsvr32.exe /s slbcsp.dll 
        regsvr32.exe /s cryptdlg.dll 
        regsvr32.exe /s oleaut32.dll 
        regsvr32.exe /s ole32.dll 
        regsvr32.exe /s shell32.dll 
        regsvr32.exe /s initpki.dll 
        regsvr32.exe /s wuapi.dll 
        regsvr32.exe /s wuaueng.dll 
        regsvr32.exe /s wuaueng1.dll 
        regsvr32.exe /s wucltui.dll 
        regsvr32.exe /s wups.dll 
        regsvr32.exe /s wups2.dll 
        regsvr32.exe /s wuweb.dll 
        regsvr32.exe /s qmgr.dll 
        regsvr32.exe /s qmgrprxy.dll 
        regsvr32.exe /s wucltux.dll 
        regsvr32.exe /s muweb.dll 
        regsvr32.exe /s wuwebv.dll
        regsvr32 /s wudriver.dll
        netsh winsock reset | Out-Null
        netsh winsock reset proxy | Out-Null

        Start-Service -Name BITS
        Start-Service -Name wuauserv
        Start-Service -Name AppIDSvc
        Start-Service -Name CryptSvc

        Write-Host "Successfully reset Windows Update" -ForegroundColor Green
    }

    End
    {

        if ($AutomaticReboot) 
        {
            Restart-Computer -Force
        }
        else 
        {
            $Title = "Reboot Now"
            $Message = "A reboot is required to complete the reset, reboot now?"

            $Yes = New-Object System.Management.Automation.Host.ChoiceDescription "&Yes", `
            "Reboots the computer immediately."

            $No = New-Object System.Management.Automation.Host.ChoiceDescription "&No", `
            "Does not reboot the computer."

            $Options = [System.Management.Automation.Host.ChoiceDescription[]]($Yes, $No)

            $Result = $host.ui.PromptForChoice($Title, $Message, $Options, 0) 

            if ($Result -eq 0)
            {
                Restart-Computer -Force
            }
        }
    }
}

Function Get-GroupsFromToken {
    <#
        .SYNOPSIS
            Enumerates the SIDs that are maintained in a user's access token issued at logon and translates the SIDs to group names.
 
        .DESCRIPTION
            The function gets the access token for the user that was issued at their logon. It reads the TOKEN_GROUPS from the access token and retrieves their SIDs from unmanaged memory. It then attempts to translate these SIDs to group names. The function includes all group memberships inherited from nested grouping.
 
            The function is run as a job so it executes in a new user context.
 
        .INPUTS
            None
 
        .OUTPUTS
            System.String[]
 
        .EXAMPLE
            Get-GroupsFromToken
 
            Returns an array of group names and/or SIDs in the access token for the current user.
 
        .NOTES
            AUTHOR: Michael Haken
            LAST UPDATED: 11/14/2016
    #>

    [CmdletBinding()]
    Param(
    )

    Begin {}

    Process
    {
        $Job = Start-Job -ScriptBlock {

            Add-Type -Assembly System.ComponentModel
    
            $Signatures = @"
        [DllImport("advapi32.dll", SetLastError=true)]
        public static extern bool GetTokenInformation(
                                                IntPtr TokenHandle,
                                                int TokenInformationClass,
                                                IntPtr TokenInformation,
                                                uint TokenInformationLength,
                                                out uint ReturnLength
                                                     );
        [DllImport("advapi32", SetLastError=true, CharSet=CharSet.Auto)]
        public static extern bool ConvertSidToStringSid(
                                                IntPtr pSID,
                                                [In,Out,MarshalAs(UnmanagedType.LPTStr)] ref string pStringSid
                                                );
"@


            $AdvApi32 = Add-Type -MemberDefinition $Signatures -Name "AdvApi32" -Namespace "PsInvoke.NativeMethods" -PassThru -ErrorAction SilentlyContinue

            $TokenClasses = @"
        using System;
        using System.Runtime.InteropServices;
 
        namespace TokenServices
        {
            public enum TOKEN_INFORMATION_CLASS
            {
                 TokenUser = 1,
                 TokenGroups,
                 TokenPrivileges,
                 TokenOwner,
                 TokenPrimaryGroup,
                 TokenDefaultDacl,
                 TokenSource,
                 TokenType,
                 TokenImpersonationLevel,
                 TokenStatistics,
                 TokenRestrictedSids,
                 TokenSessionId,
                 TokenGroupsAndPrivileges,
                 TokenSessionReference,
                 TokenSandBoxInert,
                 TokenAuditPolicy,
                 TokenOrigin,
                 TokenElevationType,
                 TokenLinkedToken,
                 TokenElevation,
                 TokenHasRestrictions,
                 TokenAccessInformation,
                 TokenVirtualizationAllowed,
                 TokenVirtualizationEnabled,
                 TokenIntegrityLevel,
                 TokenUiAccess,
                 TokenMandatoryPolicy,
                 TokenLogonSid,
                 MaxTokenInfoClass
            }
 
            public enum TOKEN_ELEVATION_TYPE
            {
                TokenElevationTypeDefault = 1,
                TokenElevationTypeFull,
                TokenElevationTypeLimited
            }
 
            public struct TOKEN_USER
            {
                public SID_AND_ATTRIBUTES User;
            }
  
            [StructLayout(LayoutKind.Sequential)]
            public struct SID_AND_ATTRIBUTES
            {
                public IntPtr Sid;
                public UInt32 Attributes;
            }
 
            [StructLayout(LayoutKind.Sequential)]
            public struct TOKEN_GROUPS
            {
                public UInt32 GroupCount;
                [MarshalAs(UnmanagedType.ByValArray)]
                public SID_AND_ATTRIBUTES[] Groups;
            }
        }
"@

    
            Add-Type $TokenClasses -ErrorAction SilentlyContinue

            $CloseHandleSignature = @"
        [DllImport( "kernel32.dll", CharSet = CharSet.Auto )]
        public static extern bool CloseHandle( IntPtr handle );
"@


            $Kernel32 = Add-Type -MemberDefinition $CloseHandleSignature -Name "Kernel32" -Namespace "PsInvoke.NativeMethods" -PassThru -ErrorAction SilentlyContinue

            [UInt32]$TokenInformationLength = 0

            $Success = $AdvApi32::GetTokenInformation( [System.Security.Principal.WindowsIdentity]::GetCurrent().Token,
                                                       [TokenServices.TOKEN_INFORMATION_CLASS]::TokenGroups,
                                                       [IntPtr]::Zero,
                                                       $TokenInformationLength, 
                                                       [Ref]$TokenInformationLength)


            if ($TokenInformationLength -gt 0)
            {
                [IntPtr]$TokenInformation = [System.Runtime.InteropServices.Marshal]::AllocHGlobal($TokenInformationLength)

                $Success = $AdvApi32::GetTokenInformation(  
                                                            [System.Security.Principal.WindowsIdentity]::GetCurrent().Token,
                                                            [TokenServices.TOKEN_INFORMATION_CLASS]::TokenGroups,
                                                            $TokenInformation,
                                                            $TokenInformationLength, 
                                                            [Ref]$TokenInformationLength
                                                         )

                if ($TokenInformationLength -gt 0) 
                {
                    $GroupArray = @()

                    try
                    {
                        [TokenServices.TOKEN_GROUPS]$Groups = [System.Runtime.InteropServices.Marshal]::PtrToStructure($TokenInformation, [System.Type][TokenServices.TOKEN_GROUPS])
                        $SidAndAttrs = New-Object -TypeName TokenServices.SID_AND_ATTRIBUTES
                        [int]$SidAndAttrsSize = [System.Runtime.InteropServices.Marshal]::SizeOf($SidAndAttrs)

                        for ($i = 0; $i -lt $Groups.GroupCount; $i++) 
                        {
                            [TokenServices.SID_AND_ATTRIBUTES]$SidAndAttrsGroup = [System.Runtime.InteropServices.Marshal]::PtrToStructure([IntPtr]($TokenInformation.ToInt64() + ($i * $SidAndAttrsSize) + [IntPtr]::Size), [System.Type][TokenServices.SID_AND_ATTRIBUTES]);
                        
                            [string]$SidString = ""
                            $Success = $AdvApi32::ConvertSidToStringSid($SidAndAttrsGroup.Sid, [Ref]$SidString)
                            try
                            {
                                $Group = (New-Object System.Security.Principal.SecurityIdentifier($SidString)).Translate([System.Security.Principal.NTAccount]) | Select-Object -ExpandProperty Value
                                $GroupArray += $Group
                            }
                            catch [Exception]
                            {
                                $GroupArray += $SidString
                                Write-Warning -Message $_.Exception.Message
                            }
                        }

                        Write-Output -InputObject $GroupArray
                    }
                    catch [Exception]
                    {
                        Write-Warning -Message $_.Exception.Message
                    }
                    finally
                    {
                        $Kernel32::CloseHandle($TokenInformation) | Out-Null
                    }
                }
                else
                {
                    $Kernel32::CloseHandle($TokenInformation) | Out-Null
                    Write-Warning -Message (New-Object System.ComponentModel.Win32Exception([System.Runtime.InteropServices.Marshal]::GetLastWin32Error())).Message
                }
            }
            else 
            {
                Write-Warning -Message (New-Object System.ComponentModel.Win32Exception([System.Runtime.InteropServices.Marshal]::GetLastWin32Error())).Message
            }
        }

        Wait-Job -Job $Job | Out-Null
        Write-Output -InputObject (Receive-Job $Job)
    }

    End {}
}

Function Update-TokenGroupMembership {
    <#
        .SYNOPSIS
            The command refreshes the user's token and clears their current Kerberos tickets in order to pick up Active Directory group membership changes since their last logon.
 
        .DESCRIPTION
            The current group membership of the user is recorded. Then, the user's Kerberos tickets are purged. After that, the explorer.exe process is stopped and restarted, which refreshes the logon token for the user. The user will be required to enter a set of credentials and then required to enter their password to restart the explorer.exe process.
 
        .PARAMETER Credential
            The credentials of the current user. These are used to launch a new powershell process to get the updated token group membership. Without using credentials, the new process won't be started with the new token and won't reflect the updates in group membership.
 
        .PARAMETER UseSmartcard
            If the user only has a Smartcard and does not know their windows password, utilize this switch to enable prompting for Smartcard credentials when explorer.exe restarts. However, they will need to specify a credential object to start a new process to check the token changes.
 
        .INPUTS
            None
 
        .OUTPUTS
            None
             
        .EXAMPLE
            Update-TokenGroupMembership -Credential (Get-Credential)
 
            Updates the group membership for the current user.
 
        .EXAMPLE
            Update-TokenGroupMembership -UseSmartcard
 
            Updates the groups membership for the current user, but prompts for Smartcard credentials to restart explorer.exe. Because the Credential parameter was not specified, the changes in the group membership in the token are not displayed.
 
        .NOTES
            AUTHOR: Michael Haken
            LAST UPDATED: 11/14/2016
 
    #>

    [CmdletBinding()]
    Param(
        [Parameter(Position=0)]
        [PSCredential]$Credential = [PSCredential]::Empty,
        [Parameter(Position=1)]
        [switch]$UseSmartcard = $false
    )

    Begin {
        if ($Credential -eq $null) {
            $Credential = [System.Management.Automation.PSCredential]::Empty
        }
    }

    Process
    {
        $CurrentGroups = @()
    
        [System.Security.Principal.WindowsIdentity]::GetCurrent().Groups.Translate([System.Security.Principal.NTAccount]) | Select-Object -ExpandProperty Value | ForEach-Object {
            if ($_ -ne $null -and $_ -ne "") {
                $CurrentGroups += $_
            }
        }    

        #The ampersand signifies to execute the following scriptblock and treat each value as a parameter
        & "$env:SYSTEMROOT\system32\klist.exe" purge | Out-Null
        & "$env:SYSTEMROOT\system32\klist.exe" tgt | Out-Null

        & "$env:SYSTEMROOT\system32\taskkill.exe" "/F" "/IM" "explorer.exe" | Out-Null

        if (!$UseSmartcard)
        {
            & "$env:SYSTEMROOT\system32\runas.exe" "/user:$env:USERDOMAIN\$env:USERNAME" "explorer.exe" 
        }
        else
        {
            & "$env:SYSTEMROOT\system32\runas.exe" "/user:$env:USERDOMAIN\$env:USERNAME" "/smartcard" "explorer.exe" 
        }

        if ($Credential -ne [PSCredential]::Empty) {

            $Command = @"
        `$Groups = whoami /groups /FO CSV | ConvertFrom-Csv | Select-Object -ExpandProperty "Group Name"
        `$Groups2 = [System.Security.Principal.WindowsIdentity]::GetCurrent().Groups.Translate([System.Security.Principal.NTAccount]) | Select-Object -ExpandProperty Value
        `$Groups += `$Groups2
        `$Groups | Select-Object -Unique
"@


            #Encode the command because it does not like the Open and Close parentheses
    
            $Bytes = [System.Text.Encoding]::Unicode.GetBytes($Command)
            $EncodedCommand = [Convert]::ToBase64String($Bytes)

            #Because Start-Process does not capture the standard out as part of the object, it can only be redirected to a file
            #Use the .NET object in order to capture the standard out without writing to file

            $ProcessInfo = New-Object -TypeName System.Diagnostics.ProcessStartInfo
            $ProcessInfo.FileName = "$env:SYSTEMROOT\System32\windowspowershell\v1.0\powershell.exe"
            $ProcessInfo.CreateNoWindow = $true
            $ProcessInfo.Verb = "runas"
            $ProcessInfo.RedirectStandardError = $true
            $ProcessInfo.RedirectStandardOutput = $true
            $ProcessInfo.UseShellExecute = $false
            $ProcessInfo.LoadUserProfile = $false
            $ProcessInfo.Domain = $Credential.UserName.Substring(0, $Credential.UserName.IndexOf("\"))
            $ProcessInfo.UserName = $Credential.UserName.Substring($Credential.UserName.IndexOf("\") + 1)
            $ProcessInfo.Password = $Credential.Password
            $ProcessInfo.Arguments = "-EncodedCommand $EncodedCommand"
            $Process = New-Object -TypeName System.Diagnostics.Process
            $Process.StartInfo = $ProcessInfo
            $Process.Start() | Out-Null
            $Process.WaitForExit()

            if ($Process.ExitCode -eq 0)
            {
                $NewGroups = @()
                $Process.StandardOutput.ReadToEnd().Split("`r`n") | ForEach-Object {
                    if ($_ -ne $null -and $_ -ne [System.String]::Empty) {
                        $NewGroups += $_
                    }
                }

                Write-Host ""

                foreach ($OldGroup in $CurrentGroups) {
                    if (!$NewGroups.Contains($OldGroup) -and $OldGroup -ne "CONSOLE LOGON") {
                        Write-Host "REMOVED : $OldGroup" -ForegroundColor Red
                    }
                }

                Write-Host ""

                foreach ($NewGroup in $NewGroups) {
                    if (!($CurrentGroups.Contains($NewGroup)) -and !$NewGroup.StartsWith("Mandatory Label\")) {
                        Write-Host "ADDED : $NewGroup" -ForegroundColor Green
                    }
                }
            }
            else
            {
                throw $Process.StandardError.ReadToEnd()
            }
        }
    }

    End {}
}

Function Start-WithImpersonation {
    <#
        .SYNOPSIS
            Runs a scriptblock while impersonating another user.
 
        .DESCRIPTION
            The user enters credentials and a scriptblock to execute. The scriptblock is executed while impersonating the entered credentials.
 
        .PARAMETER Credential
            The credentials that will be impersonated.
 
        .PARAMETER Scriptblock
            The scriptblock that will be executed with the impersonated credentials
 
        .PARAMETER LogonType
            The type of logon that will be used for impersonation. This parameter defaults to "INTERACTIVE"
 
        .INPUTS
            System.Management.Automation.Scriptblock, System.String, System.Management.Automation.PSCredential
 
        .OUTPUTS
            System.Management.Automation.PSObject
     
                The object returned is whatever the scriptblock from the input returns.
 
        .EXAMPLE
            Start-WithImpersonation -Credential (Get-Credential) -Scriptblock {Get-Service} -LogonType INTERACTIVE
 
            Runs the get-service command using the impersonated credentials received from the Credential parameter.
 
        .NOTES
            AUTHOR: Michael Haken
            LAST UPDATED: 2/27/2016
    #>


    [CmdletBinding()]
    Param(
        [Parameter(Position=1,Mandatory=$true)]
        [PSCredential]$Credential,
        [Parameter(Position=0,Mandatory=$true)]
        [Scriptblock]$Scriptblock,
        [Parameter(Position=2)]
        [ValidateSet("INTERACTIVE","NETWORK","NETWORK_CLEARTEXT","NEW_CREDENTIALS","SERVICE","BATCH","UNLOCK")]
        [string]$LogonType = "INTERACTIVE"
    )

    Begin {}

    Process
    {
    
        $Job = Start-Job -ArgumentList @($Credential, $Scriptblock) -ScriptBlock {
            Add-Type -AssemblyName System.ComponentModel

            [PSCredential]$Credential = $args[0]
            [Scriptblock]$Scriptblock = [Scriptblock]::Create($args[1])

            $Signatures = @"
        [DllImport( "advapi32.dll" )]
        public static extern bool LogonUser( String lpszUserName,
                                             String lpszDomain,
                                             String lpszPassword,
                                             int dwLogonType,
                                             int dwLogonProvider,
                                             ref IntPtr phToken );
"@


            $AdvApi32 = Add-Type -MemberDefinition $Signatures -Name "AdvApi32" -Namespace "PsInvoke.NativeMethods" -PassThru

            $CloseHandleSignature = @"
        [DllImport( "kernel32.dll", CharSet = CharSet.Auto )]
        public static extern bool CloseHandle( IntPtr handle );
"@


            $Kernel32 = Add-Type -MemberDefinition $CloseHandleSignature -Name "Kernel32" -Namespace "PsInvoke.NativeMethods" -PassThru

            try
            {
                #Logon Types
                [int]$LOGON32_LOGON_INTERACTIVE = 2
                [int]$LOGON32_LOGON_NETWORK = 3
                [int]$LOGON32_LOGON_BATCH = 4
                [int]$LOGON32_LOGON_SERVICE = 5
                [int]$LOGON32_LOGON_UNLOCK = 7
                [int]$LOGON32_LOGON_NETWORK_CLEARTEXT = 8 #Win2K or higher
                [int]$LOGON32_LOGON_NEW_CREDENTIALS = 9 #Win2K or higher

                #Logon Providers
                [int]$LOGON32_PROVIDER_DEFAULT = 0
                [int]$LOGON32_PROVIDER_WINNT35 = 1
                [int]$LOGON32_PROVIDER_WINNT40 = 2
                [int]$LOGON32_PROVIDER_WINNT50 = 3

                [int]$Logon 
                [int]$Provider = $LOGON32_PROVIDER_DEFAULT

                switch ($LogonType)
                {
                    "INTERACTIVE" {
                        $Logon = $LOGON32_LOGON_INTERACTIVE
                        break
                    }
                    "NETWORK" {
                        $Logon = $LOGON32_LOGON_NETWORK
                        break
                    }
                    "NETWORK_CLEARTEXT" {
                        $Logon = $LOGON32_LOGON_NETWORK_CLEARTEXT
                        break
                    }
                    "NEW_CREDENTIALS" {
                        $Logon = $LOGON32_LOGON_NEW_CREDENTIALS
                        $Provider = $LOGON32_PROVIDER_WINNT50
                        break
                    }
                    "SERVICE" {
                        $Logon = $LOGON32_LOGON_SERVICE
                        break
                    }
                    "BATCH" {
                        $Logon = $LOGON32_LOGON_BATCH
                        break
                    }
                    "UNLOCK" {
                        $Logon = $LOGON32_LOGON_UNLOCK
                        break
                    }
                    default {
                        $Logon = $LOGON32_LOGON_INTERACTIVE
                        break
                    }
                }

                $TokenHandle = [IntPtr]::Zero

                if ($Credential.UserName.Contains("\"))
                {
                    $UserName = $Credential.UserName.Substring($Credential.UserName.IndexOf("\") + 1)
                    $Domain = $Credential.UserName.Substring(0, $Credential.UserName.IndexOf("\"))
                }
                else
                {
                    $UserName = $Credential.UserName
                    $Domain = $env:COMPUTERNAME
                }

                $Success = $AdvApi32::LogonUser($UserName, $Domain, $Credential.Password, $Logon, $Provider, [Ref]$TokenHandle)
    
                if (!$Success)
                {
                    $ReturnValue = [System.Runtime.InteropServices.Marshal]::GetLastWin32Error()
                    $Message = (New-Object -TypeName System.ComponentModel.Win32Exception($ReturnValue)).Message
                    Write-Warning -Message "LogonUser was unsuccessful. Error code: $ReturnValue - $Message"
                    return
                }

                $NewIdentity = New-Object System.Security.Principal.WindowsIdentity($TokenHandle)

                $IdentityName = [System.Security.Principal.WindowsIdentity]::GetCurrent().Name
                Write-Host "Current Identity: $IdentityName"
    
                $Context = $NewIdentity.Impersonate()

                $IdentityName = [System.Security.Principal.WindowsIdentity]::GetCurrent().Name
                Write-Host "Impersonating: $IdentityName"

                Write-Host "Executing custom script"
                $Result = & $Scriptblock
                return $Result
            }
            catch [System.Exception]
            {
                Write-Warning -Message $_.Exception.ToString()
            }
            finally
            {
                if ($Context -ne $null)
                {
                    $Context.Undo()
                }

                if ($TokenHandle -ne [System.IntPtr]::Zero)
                {
                    $Kernel32::CloseHandle($TokenHandle) | Out-Null
                }
            }
        }

        Wait-Job -Job $Job | Out-Null
        Write-Output -InputObject (Receive-Job -Job $Job)
    }

    End
    {        
    }
}

Function Enable-WinRM {
    <#
        .SYNOPSIS
            Enables WinRM on a host.
 
        .DESCRIPTION
            The function enables PowerShell remoting, sets WinRM to automatically start, adds the provided to trusted hosts (which defaults to all hosts), and creates the firewall rule to allow inbound WinRM.
 
        .PARAMETER TrustedHosts
            The hosts that are trusted for remote mamangement. This can be an IP range, a subnet, or a wildcard. This defaults to all hosts: "*".
 
        .INPUTS
            System.String
 
                The value can be piped to Enable-WinRM.
 
        .OUTPUTS
            None
 
        .EXAMPLE
            Enable-WinRM -TrustedHosts "192.168.100.0-192.168.100.255"
 
        .NOTES
            This command should be run with administrative credentials
 
            AUTHOR: Michael Haken
            LAST UPDATED: 2/27/2016
    #>

    [CmdletBinding()]
    Param(
        [Parameter(Position=0,ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true)]
        [string]$TrustedHosts = "*"
    )

    Begin {
        if (!(Test-IsLocalAdmin)) {
            throw "This cmdlet must be run with admin credentials."
        }
    }

    Process
    {
        Set-NetConnectionProfile -NetworkCategory Private
        Enable-PSRemoting -Force 
        Set-Service -Name WinRM -StartupType Automatic
        Start-Service -Name WinRM
        Set-Item WSMan:\localhost\Client\TrustedHosts -Value $TrustedHosts -Force
        Restart-Service -Name WinRM
        New-NetFirewallRule -Name "Allow_WinRM" -DisplayName "Windows Remote Management (WinRM)" -Description "Allows WinRM ports 5985-5986 inbound." -Protocol TCP -LocalPort 5985,5986 -Enabled True -Action Allow -Profile Any
        Write-Host "WinRM Enabled" -ForegroundColor Green
    }
    
    End {}
}

Function New-EmptyTestFile {
    <#
        .SYNOPSIS
            Creates an empty file of the specified size.
 
        .DESCRIPTION
            Creates a file of the provided size in the provided location to test against.
 
        .PARAMETER FilePath
            The location the file should be created. This defaults to the user's desktop with a filename of Test.txt.
 
        .PARAMETER Size
            The size of the file to be created. Can be specified in bytes or with units, such as 64GB or 32MB.
 
        .INPUTS
            None
 
        .OUTPUTS
            None
 
        .EXAMPLE
            New-EmptyTestFile -FilePath "c:\test.cab" -Size 15MB
 
            Creates an empty 15MB cab file at c:\test.cab.
 
        .NOTES
            AUTHOR: Michael Haken
            LAST UPDATED: 11/14/2016
 
        .FUNCTIONALITY
            This cmdlet is used to create empty test files to perform tests on.
    #>

    [CmdletBinding()]
    Param(
        [Parameter(Position=1)]
        [string]$FilePath = "$env:USERPROFILE\Desktop\Test.txt",
        [Parameter(Position=0,Mandatory=$true)]
        [UInt64]$Size
    )

    Begin {}

    Process
    {
        $Writer = [System.IO.File]::Create($FilePath)

        $Bytes = New-Object Byte[] ($Size)
        $Writer.Write($Bytes, 0, $Bytes.Length)

        $Writer.Close()

        Write-Host "$Size file created at $FilePath"
    }

    End {}
}

Function Start-PortScan {
    <#
        .SYNOPSIS
            Conducts a port scan on the selected computer.
 
        .DESCRIPTION
            Tries to connect to common ports on a targetted system and reports back the port status of each. Each connection is scheduled as a job; the function waits for all jobs to exit the running status before returning scan information.
 
        .PARAMETER ComputerName
            The name of the computer to scan. The parameter defaults to "localhost"
 
        .INPUTS
            System.String
 
                The input can be piped to Start-PortScan
 
        .OUTPUTS
            System.Management.Automation.PSCustomObject[]
 
                Each custom object has a property of Service, Port, and Status. Status is either Open or Closed.
 
        .EXAMPLE
            Start-PortScan -ComputerName remotecomputer.net
 
            Returns an array of open and closed ports on remotecomputer.net
 
        .NOTES
            AUTHOR: Michael Haken
            LAST UPDATE: 2/27/2016
 
        .FUNCTIONALITY
            The intended use of this cmdlet is to conduct a security scan of ports on a computer.
 
    #>

    [CmdletBinding()]
    Param(
        [Parameter(Position=0,ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true)]
        [string]$ComputerName = "localhost"
    )
    
    Begin
    {
        $Ports = $script:Ports | Sort-Object -Property Port
    }

    Process
    {
        $Jobs = @()

        $i = 1

        foreach ($Item in $Ports)    
        {
            Write-Progress -Activity "Running Port Scan" -Status "Scanning Port $($Item.Port) $($Item.Service)" -PercentComplete (($i++ / $Ports.Count) * 100)
        
            $Jobs += Start-Job -ArgumentList @($ComputerName,$Item) -ScriptBlock {
                $ComputerName = $args[0]
                $Service = $args[1].Service
                $Port = $args[1].Port

                $Socket = New-Object Net.Sockets.TcpClient
                $ErrorActionPreference = 'SilentlyContinue'
                $Socket.Connect($ComputerName, $Port)
                $ErrorActionPreference = 'Continue' 
        
                if ($Socket.Connected) 
                {
                    $Socket.Close()
                    return [PSCustomObject]@{"Service"="$Service";"Port"=$Port;"Status"="Open"}
                }
                else 
                {
                    return [PSCustomObject]@{"Service"="$Service";"Port"=$Port;"Status"="Closed"}
                }
            }
        }

        Write-Progress -Completed -Activity "Running Port Scan"

        Write-Host "Waiting for jobs to complete..."

        $RunningJobs = @()

        $RunningJobs = Get-Job | Where-Object {$_.Id -in ($Jobs | Select-Object -ExpandProperty Id)}

        while (($RunningJobs | Where {$_.State -eq "Running"}).Length -gt 0) {
            $Completed = ($RunningJobs | Where {$_.State -eq "Completed"}).Length

            Write-Progress -Activity "Completing Jobs" -Status ("Waiting for connections to complete: " + (($Completed / $RunningJobs.Length) * 100) + "% Complete") -PercentComplete (($Completed / $RunningJobs.Length) * 100)
            Start-Sleep -Milliseconds 500
        }

        Wait-Job -Job $Jobs | Out-Null
        $Data = @()
        Receive-Job -Job $Jobs | ForEach-Object {
            $Data += $_
        }

        Remove-Job -Job $Jobs

        Write-Output -InputObject ($Data | Select-Object -Property * -ExcludeProperty RunspaceId)
    }

    End
    {    
    }
}

Function Remove-JavaInstallations {
    <#
        .SYNOPSIS
            Removes old versions of Java JRE or does a complete removal.
 
        .DESCRIPTION
            The function identifies well-known directories, registry keys, and registry key entries. Then based on the type of cleanup and architecture targetted, it removes those files, directories, registry keys, and registry key entries. During a cleanup, the current version of Java is specified so that it is not removed.
 
        .PARAMETER MajorVersion
            The current major version of Java, for example 7 or 8.
 
        .PARAMETER MinorVersion
            The current minor version of Java, this is almost always 0.
 
        .PARAMETER ReleaseVersion
            The current release version of Java, this is the update number, for example 15, 45, or 73.
 
        .PARAMETER PluginVersion
            The major version of the Java web plugin, for example 10 or 11.
     
        .PARAMETER Architecture
            The architecture to target, either x86, x64, or All. This defaults to All.
 
        .PARAMETER FullRemoval
            Specifies that a full removal of Java should be conducted.
 
        .INPUTS
            None
 
        .OUTPUTS
            None
 
        .EXAMPLE
            Remove-JavaInstallations -MajorVersion 8 -ReleaseVersion 15 -PluginVersion 11 -Architecture All
 
            Removes all versions previous to JRE 8u15.
 
        .EXAMPLE
            Remove-JavaInstallations -MajorVersion 8 -ReleaseVersion 15 -PluginVersion 11 -Architecture x64
 
            Removes all versions previous to JRE 8u15 that are x64 installations.
 
        .EXAMPLE
            Remove-JavaInstallations -FullRemoval
 
            Removes all versions of JRE from the system.
 
        .NOTES
            AUTHOR: Michael Haken
            LAST UPDATE: 11/14/2016
 
        .FUNCTIONALITY
            The intended use of this cmdlet is to conduct complete removals of the Java JRE software.
    #>


    [CmdletBinding(DefaultParameterSetName="Cleanup")]
    Param(
        [Parameter(Position=0,ParameterSetName="Cleanup",Mandatory=$true)]
        [int]$MajorVersion,
        [Parameter(ParameterSetName="Cleanup")]
        [int]$MinorVersion = 0,
        [Parameter(Position=1,ParameterSetName="Cleanup",Mandatory=$true)]
        [int]$ReleaseVersion,
        [Parameter(Position=2,ParameterSetName="Cleanup",Mandatory=$true)]
        [int]$PluginVersion,
        [Parameter(ParameterSetname="Cleanup")]
        [ValidateSet("x86","x64","All")]
        [string]$Architecture = "All",
        [Parameter(ParameterSetName="Removal",Mandatory=$true)]
        [switch]$FullRemoval    
    )

    Begin
    {
        if ((Get-PSDrive | Where-Object {$_.Root -eq "HKEY_CLASSES_ROOT"}))
        {
            Get-PSDrive | Where-Object {$_.Root -eq "HKEY_CLASSES_ROOT"} | Remove-PSDrive
        }

        New-PSDrive -Name HKCR -PSProvider Registry -Root HKEY_CLASSES_ROOT | Out-Null

        #These keys are used to cleanup HKLM:\\SOFTWARE\MICROSOFT\WINDOWS\CURRENTVERSION\UNINSTALL
        $64BIT_REGISTRY_KEY = "*26A24AE4-039D-4CA4-87B4-2F8641*FF*" # 26A24AE4-039D-4CA4-87B4-2F46417015FF - Java 7u15 x64
        $32BIT_REGISTRY_KEY = "*26A24AE4-039D-4CA4-87B4-2F8321*FF*"
        $GENERIC_REGISTRY_KEY = "*26A24AE4-039D-4CA4-87B4-2F8*1*FF*"

        #These keys are used to cleanup HKCR:\\Installer\Products
        $64BIT_HKCR_INSTALLER_PRODUCTS_KEY = "*4EA42A62D9304AC4784BF23812*FF*" # 4EA42A62D9304AC4784BF238120683FF - Java 6u38 x86
        $32BIT_HKCR_INSTALLER_PRODUCTS_KEY = "*4EA42A62D9304AC4784BF26814*FF*"
        $GENERIC_HKCR_INSTALLER_PRODUCTS_KEY = "*4EA42A62D9304AC4784BF2*81*FF*"

        #Java AutoUpdate
        $HKCR_JAVA_AUTOUPDATER = "F60730A4A66673047777F5728467D401"
        $HKLM_JAVA_AUTOUPDATER = "F60730A4A66673047777F5728467D401"

        #Build the software version
        [string]$LONG_PUNCTUATED_VERSION = ""
        [string]$NON_PUNCTUATED_VERSION = ""
        [string]$SHORT_VERSION = "1." + $MajorVersion # 1.7
        [string]$BASE_VERSION = "1." + $MajorVersion + "." + $MinorVersion + ".0" # 1.7.0
        [string]$FULL_VERSION = ""
        [string]$PLUGIN_VERSION = $PluginVersion.ToString()        
    }

    Process
    {
        $Temp = $ReleaseVersion.ToString().ToCharArray()
        [System.Array]::Reverse($Temp)
        [string]$REVERSE_RELEASE = $Temp.ToString()

        $Temp = ($MajorVersion.ToString() + $MinorVersion.ToString()).ToCharArray()
        [System.Array]::Reverse($Temp)
        [string]$REVERSE_VERSION = $Temp.ToString()

        #Make the current release string two characters long
        if ($ReleaseVersion.ToString().Length -eq 1) 
        {
            $ReleaseVersion = "0" + $ReleaseVersion.ToString()
        }

        switch ($ReleaseVersion) 
        {
            "00" {
                $FULL_VERSION = "1." + $MajorVersion + (& if($MinorVersion -gt 0) {"." + $MinorVersion } else {""}) # 1.7 or 1.7.1
                $NON_PUNCTUATED_VERSION = "1" + $MajorVersion + $MinorVersion # 170
                $LONG_PUNCTUATED_VERSION = "1." + $MajorVersion + "." + $MinorVersion # 1.7.0
                break
            }
            default {
                $FULL_VERSION = "1." + $MajorVersion + "." + $MinorVersion + "_" + $ReleaseVersion # 1.7.0_15
                $NON_PUNCTUATED_VERSION = "1" + $MajorVersion + $MinorVersion + "_" + $ReleaseVersion # 170_15
                $LONG_PUNCTUATED_VERSION = $FULL_VERSION # 1.7.0_15
                break
            }
        }

        $REVERSE_VERSION_REGISTRY_KEY = $REVERSE_VERSION + $REVERSE_RELEASE + "FF*"
        $NON_PUNCTUATED_REGISTRY_KEY = $MajorVersion.ToString() + $MinorVersion.ToString() + $ReleaseVersion.ToString() + "FF*"
        
        #Create the registry strings to match Java in HKCR and HKLM
        $UNINSTALL_REGISTRY_KEY = ""
        $HKCR_REGISTRY_KEY = ""

        switch ($Architecture)
        {
            # HKLM:\SOFTWARE\Wow6432Node\
            "x86" {
                $UNINSTALL_REGISTRY_KEY = "*26A24AE4-039D-4CA4-87B4-2F8321" + $NON_PUNCTUATED_REGISTRY_KEY # 3217000 or 3217015
                $HKCR_REGISTRY_KEY = "*4EA42A62D9304AC4784BF23812" + $REVERSE_VERSION_REGISTRY_KEY + "*" #38120751
                break
            }
            # HKLM:\SOFTWARE\
            "x64" {
                $UNINSTALL_REGISTRY_KEY = "*26A24AE4-039D-4CA4-87B4-2F8641" + $NON_PUNCTUATED_REGISTRY_KEY # 6417000 or 6417015
                $HKCR_REGISTRY_KEY = "*4EA42A62D9304AC4784BF26814" + $REVERSE_VERSION_REGISTRY_KEY +"*" #68140751
                break
            }
            "All" {
                $UNINSTALL_REGISTRY_KEY = "*26A24AE4-039D-4CA4-87B4-2F8*1" + $NON_PUNCTUATED_REGISTRY_KEY # *17000 or *17015
                $HKCR_REGISTRY_KEY = "*4EA42A62D9304AC4784BF2*81*" + $REVERSE_VERSION_REGISTRY_KEY + "*" #*81*0751
                break
            }
        }

        $FilePaths = @()
        $UserProfiles = Get-ChildItem -Path "$env:SystemDrive\Users"

        Write-Verbose -Message "[INFO] Getting All User Profiles"

        foreach ($Profile in $UserProfiles)
        {
            $FilePaths += "$env:SystemDrive\Users\" + $Profile.Name + "\AppData\LocalLow\Sun"
            $FilePaths += "$env:SystemDrive\Users\" + $Profile.Name + "\AppData\Local\Temp\java_install_reg.log"
            $FilePaths += "$env:SystemDrive\Users\" + $Profile.Name + "\AppData\Local\Temp\java_install.log"  
        }

        Write-Verbose -Message "[INFO] Adding file paths"

        $FilePaths += "$env:SYSTEMROOT\Temp\java_install.log"
        $FilePaths += "$env:SYSTEMROOT\Temp\java_install_reg.log"

        if ($PSCmdlet.ParameterSetName -eq "Removal")
        {
            $FilePaths += "$env:ALLUSERSPROFILE\Sun"

            if ($Architecture -eq "x86" -or $Architecture -eq "All")
            {
                $FilePaths += "$env:SystemDrive\Program Files (x86)\Java"
                $FilePaths += "$env:SYSTEMROOT\System32\java.exe"
                $FilePaths += "$env:SYSTEMROOT\System32\javaw.exe"
                $FilePaths += "$env:SYSTEMROOT\System32\javaws.exe"
            }
            if ($Architecture -eq "x64" -or $Architecture -eq "All")
            {
                $FilePaths += "$env:SystemDrive\Program Files\Java"
                $FilePaths += "$env:SYSTEMROOT\SysWow64\java.exe"
                $FilePaths += "$env:SYSTEMROOT\SysWow64\javaw.exe"
                $FilePaths += "$env:SYSTEMROOT\SysWow64\javaws.exe"
            }
        }

        if ($PSCmdlet.ParameterSetName -eq "Cleanup")
        {
            if ($Architecture -eq "x86" -or $Architecture -eq "All")
            {
                $FilePaths += @(Get-ChildItem "$env:SystemDrive\program files (x86)\Java" | Where-Object {$_.name -notlike "jre" + $MajorVersion})
            }
            if ($Architecture -eq "x64" -or $Architecture -eq "All")
            {
                $FilePaths += @(Get-ChildItem "$env:SystemDrive\program files\Java" | Where-Object {$_.name -notlike "jre" + $MajorVersion})
            }
        }
        
        Write-Verbose -Message "[INFO] Getting Registry Keys"
        $ErrorActionPreference = "SilentlyContinue"
        $RegistryKeys = @()

        $RegistryKeys += 'HKCU:\Software\AppDataLow\Software\Javasoft'
        $RegistryKeys += 'HKCU:\Software\Javasoft\Java Update'
        $RegistryKeys += 'HKCU:\Software\Microsoft\Protected Storage System Provider\S-1-5-21-1292428093-1275210071-839522115-1003\Data'
        $RegistryKeys += 'HKLM:\SOFTWARE\MozillaPlugins\@java.com'
        $RegistryKeys += 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Installer\UserData\S-1-5-18\Components\0357E4991DA5FF14F9615B3312070F06'
        $RegistryKeys += 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Installer\UserData\S-1-5-18\Components\0357E4991DA5FF14F9615B3512070F06'
        $RegistryKeys += 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Installer\UserData\S-1-5-18\Products\4EA42A62D9304AC4784BF238120652FF'
        $RegistryKeys += 'HKLM:\SOFTWARE\Classes\JavaSoft.JavaBeansBridge'
        $RegistryKeys += 'HKLM:\SOFTWARE\Classes\JavaSoft.JavaBeansBridge.1'
        $RegistryKeys += 'HKLM:\Software\Microsoft\Windows\CurrentVersion\Installer\UserData\S-1-5-18\Components\0357E4991DA5FF14F9615B3312070F07'
        $RegistryKeys += 'HKLM:\Software\Microsoft\Windows\CurrentVersion\Installer\UserData\S-1-5-18\Components\0357E4991DA5FF14F9615B3312070F08'
        $RegistryKeys += 'HKLM:\Software\Microsoft\Windows\CurrentVersion\Installer\UserData\S-1-5-18\Components\0357E4991DA5FF14F9615B3312070F09'
        $RegistryKeys += 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Installer\UserData\S-1-5-18\Products\4EA42A62D9304AC4784BF2381206220F'

        if ($PSCmdlet.ParameterSetName -eq "Cleanup")
        {
            $RegistryKeys += @((Get-ChildItem -Path "HKCR:\" | Where-Object {($_.Name -like "*JavaPlugin*") -and ($_.Name -notlike "*JavaPlugin." + $NON_PUNCTUATED_VERSION + "*")}).PSPath)
            $RegistryKeys += @((Get-ChildItem -Path "HKCR:\" | Where-Object {($_.name -like "*JavaWebStart.isInstalled.*") -and ($_.Name -notlike "*JavaWebStart.isInstalled." + $BASE_VERSION +"*")}).PSPath)
            $RegistryKeys += @((Get-ChildItem -Path "HKCR:\Installer\Products" | Where-Object {($_.Name -like $GENERIC_HKCR_INSTALLER_PRODUCTS_KEY) -and ($_.Name -notlike $HKCR_REGISTRY_KEY)}).PSPath)
            $RegistryKeys += @((Get-ChildItem -Path "HKCU:\Software\JavaSoft\Java Runtime Environment" | Where-Object {($_.Name -notlike "*" + $FULL_VERSION +"*") -and ($_.name -notlike "*" + $LONG_PUNCTUATED_VERSION +"*")}).PSPath)
            $RegistryKeys += @((Get-ChildItem -Path "HKCU:\Software\JavaSoft\Java2D" | Where-Object {($_.Name -notlike  "*" + $LONG_PUNCTUATED_VERSION + "*")}).PSPath) 
            $RegistryKeys += @((Get-ChildItem -Path "HKCU:\Software\Classes" | Where-Object {($_.Name -like "*JavaPlugin*") -and ($_.Name -notlike "*JavaPlugin." + $NON_PUNCTUATED_VERSION + "*")}).PSPath)
            $RegistryKeys += @((Get-ChildItem -Path "HKLM:\SOFTWARE\Classes\Installer\Products" | Where-Object {($_.Name -like $GENERIC_HKCR_INSTALLER_PRODUCTS_KEY) -and  ($_.Name -notlike $HKCR_REGISTRY_KEY) }).PSPath)

            if ($Architecture -eq "x86" -or $Architecture -eq "All")
            {
                $RegistryKeys += @((Get-ChildItem -Path "HKLM:\SOFTWARE\Classes\Installer\Features\" | Where-Object {$_.Name -like $32BIT_REGISTRY_KEY}).PSPath)
                $RegistryKeys += @((Get-ChildItem -Path "HKLM:\SOFTWARE\wow6432node\Microsoft\Windows\CurrentVersion\Uninstall\" | Where-Object {($_.Name -like $32BIT_REGISTRY_KEY) -and ($_.Name -notlike $UNINSTALL_REGISTRY_KEY)}).PSPath)
                $RegistryKeys += @((Get-ChildItem -Path "HKLM:\SOFTWARE\Classes" | Where-Object {($_.Name -notlike "*JavaPlugin." + $NON_PUNCTUATED_VERSION + "*") -and ($_.Name -like "*JavaPlugin*")}).PSPath) 
                $RegistryKeys += @((Get-ChildItem -Path "HKLM:\SOFTWARE\Classes" | Where-Object {($_.Name -notlike "*JavaWebStart.isInstalled." + $BASE_VERSION + "*") -and ($_.Name -like "*JavaWebStart.isInstalled.*")}).PSPath) 
                $RegistryKeys += @((Get-ChildItem -Path "HKLM:\SOFTWARE\Wow6432Node\JavaSoft\Java Runtime Environemt" | Where-Object {($_.Name -notlike  "*" + $FULL_VERSION + "*") -and ($_.Name -notlike  "*" + $SHORT_VERSION + "*")}).PSPath)
                $RegistryKeys += @((Get-ChildItem -Path "HKLM:\SOFTWARE\Wow6432Node\JavaSoft\Java Plug-in" | Where-Object {($_.Name -notlike  "*" + $PLUGIN_VERSION + "*")}).PSPath)
                $RegistryKeys += @((Get-ChildItem -Path "HKLM:\SOFTWARE\Wow6432Node\JavaSoft\Java Web Start" | Where-Object {($_.Name -notlike "*" + $FULL_VERSION +"*") -and ($_.name -notlike "*" + $SHORT_VERSION +"*")}).PSPath)            
            }

            if ($Architecture -eq "x64" -or $Architecture -eq "All")
            {
                $RegistryKeys += @((Get-ChildItem -Path "HKLM:\SOFTWARE\Classes\Installer\Features\" | Where-Object {$_.Name -like $64BIT_REGISTRY_KEY}).PSPath)
                $RegistryKeys += @((Get-ChildItem -Path "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\" | Where-Object {($_.Name -like $64BIT_REGISTRY_KEY) -and ($_.Name -notlike $UNINSTALL_REGISTRY_KEY)}).PSPath) 
                $RegistryKeys += @((Get-ChildItem -Path "HKLM:\SOFTWARE\Classes" | Where-Object {($_.Name -notlike "*JavaWebStart.isInstalled." + $BASE_VERSION + "*") -and ($_.Name -like "*JavaWebStart.isInstalled.*")}).PSPath)
                $RegistryKeys += @((Get-ChildItem -Path "HKLM:\SOFTWARE\Classes" | Where-Object {($_.Name -notlike "*JavaPlugin." + $NON_PUNCTUATED_VERSION + "*") -and ($_.Name -like "*JavaPlugin*")}).PSPath)
                $RegistryKeys += @((Get-ChildItem -Path "HKLM:\SOFTWARE\JavaSoft\Java Runtime Environemt" | Where-Object {($_.Name -notlike  "*" + $FULL_VERSION + "*") -and ($_.Name -notlike  "*" + $SHORT_VERSION + "*")}).PSPath)
                $RegistryKeys += @((Get-ChildItem -Path "HKLM:\SOFTWARE\JavaSoft\Java Plug-in" | Where-Object {($_.Name -notlike  "*" + $PLUGIN_VERSION + "*")}).PSPath)
                $RegistryKeys += @((Get-ChildItem -Path "HKLM:\SOFTWARE\JavaSoft\Java Web Start" | Where-Object {($_.Name -notlike "*" + $FULL_VERSION +"*") -and ($_.name -notlike "*" + $SHORT_VERSION +"*")}).PSPath)    
            }
        }

        if ($PSCmdlet.ParameterSetName -eq "Removal")
        {            
            $RegistryKeys += "HKLM:\SOFTWARE\Classes\jarfile"
            $RegistryKeys += @((Get-ChildItem -Path "HKCR:\" | Where-Object {($_.Name -like "*JavaPlugin*")}).PSPath)
            $RegistryKeys += @((Get-ChildItem -Path "HKCR:\" | Where-Object {($_.Name -like "*JavaScript*")}).PSPath)
            $RegistryKeys += @((Get-ChildItem -Path "HKCR:\" | Where-Object {($_.Name -like "*JavaWebStart*")}).PSPath)
            $RegistryKeys += @((Get-ChildItem -Path "HKCR:\Installer\Products" | Where-Object {($_.Name -like $GENERIC_HKCR_INSTALLER_PRODUCTS_KEY)}).PSPath)
            $RegistryKeys += "HKCU:\Software\JavaSoft\Java Runtime Environment"
            $RegistryKeys += "HKCU:\Software\JavaSoft\Java2D"
            $RegistryKeys += "HKCR:\Installer\Products\$HKCR_JAVA_AUTOUPDATER"

            if ($Architecture -eq "x86" -or $Architecture -eq "All")
            {
                $RegistryKeys += @((Get-ChildItem -Path "HKLM:\SOFTWARE\Classes\Installer\Features\" | Where-Object {$_.Name -like $32BIT_REGISTRY_KEY -or $_.Name -like $HKLM_JAVA_AUTOUPDATER}).PSPath)
                $RegistryKeys += @((Get-ChildItem -Path "HKLM:\SOFTWARE\wow6432node\Microsoft\Windows\CurrentVersion\Uninstall\" | Where-Object {$_.Name -like $32BIT_REGISTRY_KEY}).PSPath) 
                $RegistryKeys += "HKLM:\SOFTWARE\Wow6432Node\JavaSoft"
                $RegistryKeys += @((Get-ChildItem -Path "HKLM:\SOFTWARE\Classes" | Where-Object {$_.Name -like "*JavaWebStart*"}).PSPath)
                $RegistryKeys += @((Get-ChildItem -Path "HKLM:\SOFTWARE\Classes" | Where-Object {$_.Name -like "*JavaPlugin*"}).PSPath)
                $RegistryKeys += @((Get-ChildItem -Path "HKLM:\SOFTWARE\Classes\Installer\Products" | Where-Object {$_.Name -like $32BIT_HKCR_INSTALLER_PRODUCTS_KEY}).PSPath)
                $RegistryKeys += "HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\App Paths\javaws.exe"
            }

            if ($Architecture -eq "x64" -or $Architecture -eq "All")
            {
                $RegistryKeys += @((Get-ChildItem -Path "HKLM:\SOFTWARE\Classes\Installer\Features\" | Where-Object {$_.Name -like $64BIT_REGISTRY_KEY -or $_.Name -like $HKLM_JAVA_AUTOUPDATER}).PSPath)
                $RegistryKeys += @((Get-ChildItem -Path "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\" | Where-Object {$_.Name -like $64BIT_REGISTRY_KEY -or $_.Name -like $HKLM_JAVA_AUTOUPDATER}).PSPath) 
                $RegistryKeys += "HKLM:\SOFTWARE\JavaSoft"
                $RegistryKeys += @((Get-ChildItem -Path "HKLM:\SOFTWARE\Classes" | Where-Object {$_.Name -like "*JavaWebStart*"}).PSPath)
                $RegistryKeys += @((Get-ChildItem -Path "HKLM:\Software\Classes" | Where-Object {$_.Name -like "*JavaPlugin*"}).PSPath)
                $RegistryKeys += @((Get-ChildItem -Path "HKLM:\SOFTWARE\Classes\Installer\Products" | Where-Object {$_.Name -like $64BIT_HKCR_INSTALLER_PRODUCTS_KEY}).PSPath)
                $RegistryKeys += "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\App Paths\javaws.exe"
            }
        }

        Write-Verbose -Message "[INFO] Getting Registry Key Properties"

        $RegistryKeyProperties = @()

        $RegistryKeyProperties += @(Get-RegistryKeyEntries -Path "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Installer\Folders") 
        $RegistryKeyProperties += @(Get-RegistryKeyEntries -Path "HKLM:\System\ControlSet001\Control\Session Manager\Environment")
        $RegistryKeyProperties += @(Get-RegistryKeyEntries -Path "HKLM:\System\ControlSet002\Control\Session Manager\Environment")
        $RegistryKeyProperties += @(Get-RegistryKeyEntries -Path "HKLM:\System\ControlSet003\Control\Session Manager\Environment")
        $RegistryKeyProperties += @(Get-RegistryKeyEntries -Path "HKLM:\System\CurrentControlSet\Control\Session Manager\Environment")
        $RegistryKeyProperties += @(Get-RegistryKeyEntries -Path "HKLM:\SOFTWARE\Classes\jarfile\shell\open\command")

        $EntriesToKeep = @()

        if ($PSCmdlet.ParameterSetName -eq "Cleanup")
        {
            switch ($Architecture)
            {
                "x86" {
                    $RegistryKeyProperties += @(Get-RegistryKeyEntries -Path "HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\App Paths\javaws.exe")
                    $NOT_LIKE_1 = "$env:SystemDrive\program files (x86)\*\jre" + $majorbuild + "\*"
                    $NOT_LIKE_2 = "$env:SystemDrive\program files (x86)\*\jre" + $shortversion + "\*"
                    $LIKE = "$env:SystemDrive\program files (x86)\*\jre*"
                    break
                }
                "x64" {
                    $RegistryKeyProperties += @(Get-RegistryKeyEntries -Path "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\App Paths\javaws.exe")
                    $NOT_LIKE_1 = "$env:SystemDrive\program files\*\jre" + $majorbuild + "\*"
                    $NOT_LIKE_2 = "$env:SystemDrive\program files\*\jre" + $shortversion + "\*"
                    $LIKE = "$env:SystemDrive\program files\*\jre*"
                    break
                }
                "All" {
                    $RegistryKeyProperties += @(Get-RegistryKeyEntries -Path "HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\App Paths\javaws.exe")
                    $RegistryKeyProperties += @(Get-RegistryKeyEntries -Path "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\App Paths\javaws.exe")
                    $NOT_LIKE_1 = "$env:SystemDrive\program files*\*\jre" + $majorbuild + "\*"
                    $NOT_LIKE_2 = "$env:SystemDrive\program files*\*\jre" + $shortversion + "\*"
                    $LIKE = "$env:SystemDrive\program files*\*\jre*"
                    break
                }
            }

            foreach ($Property in $RegistryKeyProperties)
            {
                if ((($Property.Property -like $LIKE) -and ($Property.Property -notlike $NOT_LIKE_1) -and ($Property.Property -notlike $NOT_LIKE_2)) -or
                    (($Property.Value -like $LIKE) -and ($Property.Value -notlike $NOT_LIKE_1) -and ($Property.Value -notlike $NOT_LIKE_2)))
                {
                    $EntriesToKeep += $Property
                }
            }
        }

        if ($PSCmdlet.ParameterSetName -eq "Removal")
        {
            switch ($Architecture)
            {
                "x86" {
                    $RegistryKeyProperties += @(Get-RegistryKeyEntries -Path "HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\App Paths\javaws.exe")
                    $LIKE = "$env:SystemDrive\program files (x86)\*\jre*"
                    break
                }
                "x64" {
                    $RegistryKeyProperties += @(Get-RegistryKeyEntries -Path "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\App Paths\javaws.exe")
                    $LIKE = "$env:SystemDrive\program files\*\jre*"
                    break
                }
                "All" {
                    $RegistryKeyProperties += @(Get-RegistryKeyEntries -Path "HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\App Paths\javaws.exe")
                    $RegistryKeyProperties += @(Get-RegistryKeyEntries -Path "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\App Paths\javaws.exe")
                    $LIKE = "$env:SystemDrive\program files*\*\jre*"
                    break
                }
            }

            foreach ($Property in $RegistryKeyProperties)
            {
                if (($Property.Property -like $LIKE) -or ($Property.Value -like $LIKE))
                {
                    $EntriesToKepp += $Property
                }
            }
        }

        $RegistryKeyProperties = $EntriesToKeep

        $ErrorActionPreference = "Continue"

        [int]$DirectoryCount = 0
        [int]$RegistryKeyCount = 0
        [int]$RegistryEntryCount = 0

        Write-Verbose -Message "[INFO] Removing Directories and Files"

        foreach ($Item in $FilePaths)
        {
            if (Test-Path -Path $Item)
            {
                $DirectoryCount++
                Remove-Item -Path $Item -Force -Recurse
            }
        }

        Write-Verbose -Message "[INFO] Removing Registry Keys"

        foreach ($Item in $RegistryKeys)
        {
            if (Test-Path -Path $Item)
            {
                $RegistryKeyCount++
                Remove-Item -Path $Item -Force -Recurse
            }
        }

        Write-Verbose -Message "[INFO] Removing Registry Key Entries"

        foreach ($Item in $RegistryKeyProperties)
        {
            if (Test-Path -Path $Item.Path)
            {
                $RegistryEntryCount++
                Remove-ItemProperty -Path $Item.Path -Name $Item.Property -Force
            }
        }

        Write-Host "[INFO] Java cleanup removed $DirectoryCount directories, $RegistryKeyCount registry keys, and $RegistryEntryCount registry key entries."
    }

    End
    {        
    }
}

Function Get-RegistryKeyEntries {
    <#
        .SYNOPSIS
            Gets all of the properties and their values associated with a registry key.
 
        .DESCRIPTION
            The Get-RegistryKeyEntries cmdlet gets each entry and its value for a specified registry key.
 
        .PARAMETER Path
            The registry key path in the format that PowerShell can process, such as HKLM:\Software\Microsoft or Registry::HKEY_LOCAL_MACHINE\Software\Microsoft
 
        .INPUTS
            System.String
 
                You can pipe a registry path to Get-RegistryKeyEntries.
 
        .OUTPUTS
            System.Management.Automation.PSCustomObject[]
 
        .EXAMPLE
            Get-RegistryEntries -Path HKLM:\Software\Microsoft\Windows\CurrentVersion\Uninstall
 
            Gets all of the entries associated with the registry key. It does not get any information about subkeys.
 
        .NOTES
            AUTHOR: Michael Haken
            LAST UPDATE: 2/27/2016
 
        .FUNCTIONALITY
            The intended use of this cmdlet is to supplement the Get-ItemProperty cmdlet to get the values for every entry in a registry key.
    #>

    [CmdletBinding()]
    Param(
        [Parameter(Position=0,ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true,Mandatory=$true)]
        [ValidateScript({Test-Path -Path $_})]
        [string]$Path
    )

    Begin {}

    Process
    {
        Get-Item -Path $Path | Select-Object -ExpandProperty Property | ForEach-Object {
            Write-Output -InputObject ([PSCustomObject]@{"Path"=$Path;"Property"="$_";"Value"=(Get-ItemProperty -Path $Path -Name $_ | Select-Object -ExpandProperty $_)})
        }
    }

    End {}
}

Function Start-TaskSchedulerHistory {
    <#
        .SYNOPSIS
            Enables the Task Scheduler log history.
 
        .DESCRIPTION
            The Start-TaskSchedulerHistory cmdlet enables the windows event logs for the Task Scheduler. The command should be used to correct the issue of Scheduled Tasks' history showing as "Disabled" in Task Scheduler.
 
        .INPUTS
            None
 
        .OUTPUTS
            None
 
        .EXAMPLE
            Start-TaskSchedulerHistory
            This command starts the collection of scheduled task events.
 
        .NOTES
            AUTHOR: Michael Haken
            LAST UPDATE: 2/27/2016
 
        .FUNCTIONALITY
            The intended use of this cmdlet is to turn on history for Scheduled Tasks.
    #>

    [CmdletBinding()]
    Param ()

    Begin {}

    Process {
        $LogName = 'Microsoft-Windows-TaskScheduler/Operational'
        $EventLog = New-Object -TypeName System.Diagnostics.Eventing.Reader.EventLogConfiguration($LogName)
        $EventLog.IsEnabled = $true
        $EventLog.SaveChanges()
    }

    End{}
 }

Function Start-KerberosTraceLog {
    <#
        .SYNOPSIS
            Starts a trace to troubleshoot Kerberos authentication issues.
 
        .DESCRIPTION
            The Start-KerberosTraceLog cmdlet starts a trace of logs and netsh to capture all Kerberos, NTLM, SSL, and Negotiation traffic.
 
        .PARAMETER Path
            Specify the directory to store the log files during the trace. This defaults to the module root. The directory is created if it does not already exist.
 
        .INPUTS
            System.String
 
                You can pipe a directory path string to Start-KerberosTraceLog.
 
        .OUTPUTS
            None
 
        .EXAMPLE
            Start-KerberosTraceLog
 
            This command starts the trace log and logs to $PSScriptRoot\Logs.
 
        .EXAMPLE
            Start-KerberosTraceLog -Path C:\Logs
 
            This command starts the trace log and logs to C:\Logs. The directory is created if it doesn't already exist.
 
        .NOTES
            This command must be run with local administrator credentials.
 
            The output from the individual logman.exe, nltest.exe, and netsh.exe commands are written to $PSScriptRoot\StartOutput\.
     
            AUTHOR: Michael Haken
            LAST UPDATE: 11/14/2016
 
        .FUNCTIONALITY
            The intended use of this cmdlet is to assist in troubleshooting Kerberos authentication issues.
    #>

    [CmdletBinding()]
    Param(
        [Parameter(Position=0,ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true)]
        [Alias("DirectoryPath","LogDirectory")]
        [string]$Path = "$PSScriptRoot\Logs"
    )

    Begin {
        if (!(Test-IsLocalAdmin)) {
            throw "This cmdlet must be run with admin credentials."
        }

        $KerberosbDebugFlags = "0x40243"
        $NtlmDebugFlags = "0x15003"
        $NegoExtsDebugFlags = "0xFFFF"
        $Pku2uDebugFlags = "0xFFFF"
        $SslDebugFlags= "0x0000FDFF"

        $OutputPath = "$PSScriptRoot\StartOutput"

        if (!(Test-Path -Path $OutputPath)) {
             New-Item -Path $OutputPath -ItemType Directory -Force | Out-Null
        }
    }

    Process {
        if (!(Test-Path -Path $Path)) {
             New-Item -Path $Path -ItemType Directory | Out-Null
        }

        $Process = Start-Process -FilePath "$env:SYSTEMROOT\System32\logman.exe" -ArgumentList @("start","kerb","-p {6B510852-3583-4e2d-AFFE-A67F9F223438}",$KerberosbDebugFlags,"-o `"$Path\kerb.etl`"","-ets") -NoNewWindow -RedirectStandardOutput "$OutputPath\kerb.txt" -RedirectStandardError "$OutputPath\kerb_error.txt"
        $Process = Start-Process -FilePath "$env:SYSTEMROOT\System32\logman.exe" -ArgumentList @("start","ntlm","-p {5BBB6C18-AA45-49b1-A15F-085F7ED0AA90}",$NtlmDebugFlags,"-o `"$Path\ntlm.etl`"","-ets")-NoNewWindow -RedirectStandardOutput "$OutputPath\ntlm.txt" -RedirectStandardError "$OutputPath\ntlm_error.txt"
        $Process = Start-Process -FilePath "$env:SYSTEMROOT\System32\logman.exe" -ArgumentList @("start","negoexts","-p {5AF52B0D-E633-4ead-828A-4B85B8DAAC2B}",$NegoExtsDebugFlags,"-o `"$Path\negoexts.etl`"","-ets") -NoNewWindow -RedirectStandardOutput "$OutputPath\negoexts.txt" -RedirectStandardError "$OutputPath\negoexts_error.txt"

        $NegoExtender = "HKLM:\SYSTEM\CurrentControlSet\Control\Lsa\NegoExtender\Parameters"
        if (!(Test-Path -Path $NegoExtender )) {
            New-Item -Path $NegoExtender -Force | Out-Null

            $Counter = 0
            while (!(Test-Path -Path $NegoExtender)) {
                Start-Sleep -Seconds 1
                $Counter++

                if ($Counter -gt 30) {
                    throw "Timeout waiting for registry key $NegoExtender to be created."
                }
            }
        }

        Set-ItemProperty -Path "HKLM:\SYSTEM\CurrentControlSet\Control\Lsa\NegoExtender\Parameters" -Name InfoLevel -Value ([System.Convert]::ToInt32($NegoExtsDebugFlags, 16)) -Type ([Microsoft.Win32.RegistryValueKind]::DWord) -Force | Out-Null
        
        $Process = Start-Process -FilePath "$env:SYSTEMROOT\System32\logman.exe" -ArgumentList @("start","pku2u","-p {2A6FAF47-5449-4805-89A3-A504F3E221A6}",$Pku2uDebugFlags,"-o `"$Path\pku2u.etl`"","-ets") -NoNewWindow -RedirectStandardOutput "$OutputPath\pku2u.txt" -RedirectStandardError "$OutputPath\pku2u_error.txt"
        
        $Pku2u = "HKLM:\SYSTEM\CurrentControlSet\Control\Lsa\Pku2u\Parameters"

        if (!(Test-Path -Path $Pku2u)) {
            New-Item -Path $Pku2u -Force | Out-Null

            $Counter = 0
            while (!(Test-Path -Path $Pku2u)) {
                Start-Sleep -Seconds 1
                $Counter++

                if ($Counter -gt 30) {
                    throw "Timeout waiting for registry key $Pku2u to be created."
                }
            }
        }

        Set-ItemProperty -Path "HKLM:\SYSTEM\CurrentControlSet\Control\Lsa\Pku2u\Parameters" -Name InfoLevel -Value ([System.Convert]::ToInt32($Pku2uDebugFlags, 16)) -Type ([Microsoft.Win32.RegistryValueKind]::DWord) -Force | Out-Null

        $Process = Start-Process -FilePath "$env:SYSTEMROOT\System32\logman.exe" -ArgumentList @("start","ssl","-p {37D2C3CD-C5D4-4587-8531-4696C44244C8}",$SslDebugFlags,"-o `"$Path\ssl.etl`"","-ets") -NoNewWindow -RedirectStandardOutput "$OutputPath\ssl.txt" -RedirectStandardError "$OutputPath\ssl_error.txt"

        Set-ItemProperty -Path "HKLM:\SYSTEM\CurrentControlSet\Control\Lsa" -Name SPMInfoLevel -Value ([System.Convert]::ToInt32("0x101F", 16)) -Type ([Microsoft.Win32.RegistryValueKind]::DWord) -Force | Out-Null
        Set-ItemProperty -Path "HKLM:\SYSTEM\CurrentControlSet\Control\Lsa" -Name LogToFile -Value 1 -Type ([Microsoft.Win32.RegistryValueKind]::DWord) -Force | Out-Null
        Set-ItemProperty -Path "HKLM:\SYSTEM\CurrentControlSet\Control\Lsa" -Name NegEventMask -Value ([System.Convert]::ToInt32("0xF", 16)) -Type ([Microsoft.Win32.RegistryValueKind]::DWord) -Force | Out-Null

        $Process = Start-Process -FilePath "$env:SYSTEMROOT\System32\nltest.exe" -ArgumentList @("/dbflag:0x2080FFFF") -NoNewWindow -RedirectStandardOutput "$OutputPath\nltest.txt" -RedirectStandardError "$OutputPath\nltest_error.txt"        
        $Process = Start-Process -FilePath "$env:SYSTEMROOT\System32\netsh.exe" -ArgumentList @("trace","stop") -NoNewWindow -RedirectStandardOutput "$OutputPath\netshstop.txt" -RedirectStandardError "$OutputPath\netshstop_error.txt"
        $Process = Start-Process -FilePath "$env:SYSTEMROOT\System32\netsh.exe" -ArgumentList @("trace","start","scenario=NetConnection","capture=yes","persistent=no","traceFile=`"$Path\Tracefile.ETL`"","overwrite=yes") -NoNewWindow -RedirectStandardOutput "$OutputPath\netsh.txt" -RedirectStandardError "$OutputPath\netsh_error.txt"

        Write-Host "Kerberos trace log started. Stdout logged to $OutputPath and logs written to $Path" -ForegroundColor Green
    }

    End {        
    }
}

Function Stop-KerberosTraceLog {
    <#
        .SYNOPSIS
            Stops a trace that was started to troubleshoot Kerberos authentication issues.
 
        .DESCRIPTION
            The Stop-KerberosTraceLog cmdlet stops the trace of logs and netsh to capture all Kerberos, NTLM, SSL, and Negotiation traffic. The required remaining logs are copied to the specified directory and then compressed into a zip file.
 
        .PARAMETER Path
            Specify the directory that was used during the Start-KerberosTraceLog to collect logs. This defaults to the module root.
 
        .INPUTS
            System.String
 
                You can pipe a directory path string to Stop-KerberosTraceLog.
 
        .OUTPUTS
            None
 
        .EXAMPLE
            Stop-KerberosTraceLog
 
            This command stops the trace log.
 
        .EXAMPLE
            Stop-KerberosTraceLog -Path C:\Logs
 
            This command stops the trace log and and copies additional required information to C:\Logs. Then, a zip file is written to C:\Logs containing the logs files.
 
        .NOTES
            This command must be run with local administrator credentials.
 
            The output from the individual logman.exe, nltest.exe, and netsh.exe commands are written to $PSScriptRoot\StopOutput\.
     
            AUTHOR: Michael Haken
            LAST UPDATE: 2/27/2016
 
        .FUNCTIONALITY
            The intended use of this cmdlet is to assist in troubleshooting Kerberos authentication issues.
    #>

    [CmdletBinding()]
    Param(
        [Parameter(Position=0,ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true)]
        [ValidateScript({Test-Path -Path $_})]
        [string]$Path = "$PSScriptRoot\Logs"
    )

    Begin {
        if (!(Test-IsLocalAdmin)) {
            throw "This cmdlet must be run with admin credentials."
        }

        $OutputPath = "$PSScriptRoot\StopOutput"

        if (!(Test-Path -Path $OutputPath)) {
            New-Item -Path $OutputPath -ItemType Directory | Out-Null
        }

        Add-Type -AssemblyName System.IO.Compression.FileSystem
    }

    Process {
        $Process = Start-Process -FilePath "$env:SYSTEMROOT\System32\logman.exe" -ArgumentList @("stop","kerb","-ets") -NoNewWindow -RedirectStandardOutput "$OutputPath\kerb.txt" -RedirectStandardError "$OutputPath\kerb_error.txt"
        $Process = Start-Process -FilePath "$env:SYSTEMROOT\System32\logman.exe" -ArgumentList @("stop","ntlm","-ets") -NoNewWindow -RedirectStandardOutput "$OutputPath\ntlm.txt" -RedirectStandardError "$OutputPath\ntlm_error.txt"
        $Process = Start-Process -FilePath "$env:SYSTEMROOT\System32\logman.exe" -ArgumentList @("stop","negoexts","-ets") -NoNewWindow -RedirectStandardOutput "$OutputPath\negoexts.txt" -RedirectStandardError "$OutputPath\negoexts_error.txt"

        Remove-ItemProperty -Path "HKLM:\SYSTEM\CurrentControlSet\Control\Lsa\NegoExtender\Parameters" -Name "InfoLevel" -Force | Out-Null
         
        $Process = Start-Process -FilePath "$env:SYSTEMROOT\System32\logman.exe" -ArgumentList @("stop","pku2u","-ets") -NoNewWindow -RedirectStandardOutput "$OutputPath\pku2u.txt" -RedirectStandardError "$OutputPath\pku2u_error.txt"

        Remove-ItemProperty -Path "HKLM:\SYSTEM\CurrentControlSet\Control\Lsa\Pku2u\Parameters" -Name "InfoLevel" -Force | Out-Null

        $Process = Start-Process -FilePath "$env:SYSTEMROOT\System32\logman.exe" -ArgumentList @("stop","ssl","-ets") -NoNewWindow -RedirectStandardOutput "$OutputPath\ssl.txt" -RedirectStandardError "$OutputPath\ssl_error.txt"

        Remove-ItemProperty -Path "HKLM:\SYSTEM\CurrentControlSet\Control\Lsa" -Name "SPMInfoLevel" -Force | Out-Null
        Remove-ItemProperty -Path "HKLM:\SYSTEM\CurrentControlSet\Control\Lsa" -Name "LogToFile" -Force | Out-Null
        Remove-ItemProperty -Path "HKLM:\SYSTEM\CurrentControlSet\Control\Lsa" -Name "NegEventMask" -Force | Out-Null

        $Process = Start-Process -FilePath "$env:SYSTEMROOT\System32\nltest.exe" -ArgumentList @("/dbflag:0x0") -NoNewWindow -RedirectStandardOutput "$OutputPath\nltest.txt" -RedirectStandardError "$OutputPath\nltest_error.txt"
        $Process = Start-Process -FilePath "$env:SYSTEMROOT\System32\netsh.exe" -ArgumentList @("wfp","capture","stop") -NoNewWindow -RedirectStandardOutput "$OutputPath\netsh_wfp.txt" -RedirectStandardError "$OutputPath\netsh_wfp_error.txt"
        $Process = Start-Process -FilePath "$env:SYSTEMROOT\System32\netsh.exe" -ArgumentList @("trace","stop") -NoNewWindow -RedirectStandardOutput "$OutputPath\netsh_tracestop.txt" -RedirectStandardError "$OutputPath\netsh_tracestop_error.txt"

        if (Test-Path -Path "$env:SYSTEMROOT\debug\netlogon.log") {
            try {
                Copy-Item -Path "$env:SYSTEMROOT\debug\netlogon.log" -Destination $Path -Force | Out-Null
            }
            catch [Exception] {
                Write-Warning -Message $_.Exception.Message
            }
        }
        
        if (Test-Path -Path "$env:SYSTEMROOT\system32\lsass.log") {
            try {
                Copy-Item -Path "$env:SYSTEMROOT\system32\lsass.log" -Destination $Path -Force | Out-Null
            }
            catch [Exception] {
                Write-Warning -Message $_.Exception.Message
            }
        }

        Get-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion" -Name "BuildLabEx" | Select-Object -ExpandProperty BuildLabEx | Out-File -FilePath "$Path\build.txt"

        Add-Type -AssemblyName System.IO.Compression.FileSystem

        $CompressionLevel = [System.IO.Compression.CompressionLevel]::Optimal

        try {
            $FileName = ("$Path\Logs_" + (Get-Date).ToString("yyyy-MM-dd-HH-mm") + ".zip")
            [System.IO.Compression.ZipFile]::CreateFromDirectory($Path,$FileName,$CompressionLevel,$false)
            $Path = $FileName
        }
        catch [Exception] {
            Write-Warning -Message "Possible error creating zip file at $FileName : $($_.Exception.Message) The zip file may still have been created."
        }

        Write-Host "Kerberos trace logs collected at $Path. Please share these for analysis." -ForegroundColor Green
    }

    End {        
    }
}

Function Test-IsLocalAdmin {
    <#
        .SYNOPSIS
            Tests is the current user has local administrator privileges.
 
        .DESCRIPTION
            The Test-IsLocalAdmin cmdlet tests the user's current Windows Identity for inclusion in the BUILTIN\Administrators role.
 
        .INPUTS
            None
 
        .OUTPUTS
            None
 
        .EXAMPLE
            Test-IsLocalAdmin
 
            This command returns true if the current is running the session with local admin credentials and false if not.
 
        .NOTES
            AUTHOR: Michael Haken
            LAST UPDATE: 2/27/2016
 
        .FUNCTIONALITY
            The intended use of this cmdlet is to test for administrative credentials before running other commands that require them.
    #>

    [CmdletBinding()]
    Param()

    Begin {}

    Process {
        Write-Output -InputObject ([System.Security.Principal.WindowsPrincipal][System.Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([System.Security.Principal.WindowsBuiltInRole]::Administrator)
    }

    End {}
 }

Function Write-CCMLogFormat {
    <#
        .SYNOPSIS
            Writes a log file formatted to be read by the CMTrace tool.
 
        .DESCRIPTION
            The Write-CCMLogFormat cmdlet takes a message and writes it to a file in the format that can be read by CMTrace.
 
        .PARAMETER Message
            The message to be written to the file.
 
        .PARAMETER FilePath
            The path of the file to write the log information.
 
        .PARAMETER LogLevel
            The log level of the message. 1 is Informational, 2 is Warning, and 3 is Error. This defaults to Informational.
 
        .PARAMETER Component
            The component generating the log file.
 
        .PARAMETER Thread
            The thread ID of the process running the task. This defaults to the current managed thread ID.
 
        .EXAMPLE
            Write-CCMLogFormat -Message "Test Warning Message" -FilePath "c:\logpath.log" -LogLevel 2 -Component "PowerShell"
 
            This command writes "Test Warning Message" to c:\logpath.log and sets it as a Warning message in the CMTrace log viewer tool.
 
        .INPUTS
            System.String, System.String, System.Int32, System.String, System.Int32
 
        .OUTPUTS
            None
         
        .NOTES
            AUTHOR: Michael Haken
            LAST UPDATE: 11/14/2016
 
        .FUNCTIONALITY
            The intended use of this cmdlet is to write CMTrace formatted log files to be used with the viewer tool.
    #>


    [CmdletBinding()]
    Param(
        [Parameter(Position=0,ValueFromPipeline=$true,Mandatory=$true)]
        [string]$Message,
        [Parameter(Position=1,Mandatory=$true)]
        [string]$FilePath,
        [Parameter(Position=2)]
        [ValidateSet(1,2,3)]
        [Int32]$LogLevel = 1,
        [Parameter(Position=3)]
        [string]$Component = [System.String]::Empty,
        [Parameter(Position=4)]
        [Int32]$Thread = 0
    )

    Begin {        
    }

    Process {
        if ($Thread -eq 0) {
            $Thread = [System.Threading.Thread]::CurrentThread.ManagedThreadId
        }

        $Date = Get-Date
        $Time = ($Date.ToString("HH:mm:ss.fff") + "+" + ([System.TimeZone]::CurrentTimeZone.GetUtcOffset((Get-Date)).TotalMinutes * -1))
        $Day = $Date.ToString("MM-dd-yyyy")

        $File = $FilePath.Substring($FilePath.LastIndexOf("\") + 1)
        [string]$Log = "<![LOG[" + $Message + "]LOG]!><time=`"$Time`" date=`"$Day`" component=`"$Component`" context=`"`" type=`"$LogLevel`" thread=`"$Thread`" file=`"$File`">`r`n"
        Add-Content -Path $FilePath -Value $Log -Force
    }

    End {        
    }
}

Function Get-IPv6ConfigurationOptions {
    <#
        .SYNOPSIS
            Writes the HKLM:\SYSTEM\CurrentControlSet\Services\Tcpip6\Parameters DisabledComponents key property possible options.
 
        .DESCRIPTION
            The Get-IPv6ConfigurationOptions cmdlet writes the HKLM:\SYSTEM\CurrentControlSet\Services\Tcpip6\Parameters DisabledComponents key property possible options. This registry key entry determines which components of IPv6 are enabled or disabled.
 
            The cmdlet writes the possible values to enter in this key entry.
 
        .EXAMPLE
            Get-IPv6ConfigurationOptions
 
            This command returns the possible registry key settings as an array of PSCustomObjects.
 
        .INPUTS
            None
 
        .OUTPUTS
            System.Management.Automation.PSCustomObject[]
         
        .NOTES
            AUTHOR: Michael Haken
            LAST UPDATE: 2/28/2016
    #>

    [CmdletBinding()]
    Param()

    Begin {}

    Process {
        Write-Output -InputObject $script:IPv6Configs
    }

    End {}
}

Function Get-ProcessToken {
    <#
        .SYNOPSIS
            Gets the token handle for a specified process.
 
        .DESCRIPTION
            The Get-ProcessToken cmdlet gets a token handle pointer for a specified process.
             
            The CmdLet must be run with elevated permissions.
 
        .PARAMETER ProcessName
            The name of the process to get a token handle for.
 
        .PARAMETER ProcessId
            The Id of the process to get a token handle for.
 
        .PARAMETER CloseHandle
            Specifies if the handle to the token should be closed. Do not close the handle if you want to duplicate the token in another process.
 
        .EXAMPLE
            Get-ProcessToken -ProcessName lsass
 
            Gets the token handle for the lsass process.
 
        .INPUTS
            System.String, System.Int32
 
        .OUTPUTS
            System.IntPtr
 
        .NOTES
            AUTHOR: Michael Haken
            LAST UPDATE: 3/25/2016
    #>


    [CmdletBinding()]
    Param(
        [Parameter(Position=1)]
        [switch]$CloseHandle
    )

    DynamicParam {
        # Create the dictionary
        $RuntimeParameterDictionary = New-Object -TypeName System.Management.Automation.RuntimeDefinedParameterDictionary

        #region Name
        # Create the collection of attributes
        $AttributeCollection = New-Object -TypeName System.Collections.ObjectModel.Collection[System.Attribute]
            
        # Create and set the parameters' attributes
        $ParameterAttribute = New-Object -TypeName System.Management.Automation.PARAMETERAttribute
        $ParameterAttribute.Mandatory = $true
        $ParameterAttribute.Position = 0
        $ParameterAttribute.ParameterSetName ="Name"

        # Add the attributes to the attributes collection
        $AttributeCollection.Add($ParameterAttribute)

        # Generate and set the ValidateSet
        $Set = Get-Process | Select-Object -ExpandProperty Name 
        $ValidateSetAttribute = New-Object -TypeName System.Management.Automation.ValidateSetAttribute($Set)
        $AttributeCollection.Add($ValidateSetAttribute)

        #Add Alias
        $AliasAttribute = New-Object -TypeName System.Management.Automation.AliasAttribute("Name")
        $AttributeCollection.Add($AliasAttribute)

        # Create and return the dynamic parameter
        $RuntimeParameter = New-Object -TypeName System.Management.Automation.RuntimeDefinedParameter("ProcessName", [string], $AttributeCollection)
        $RuntimeParameterDictionary.Add("ProcessName", $RuntimeParameter)
        
        #endregion

        #region Id

        # Create the collection of attributes
        $AttributeCollection = New-Object -TypeName System.Collections.ObjectModel.Collection[System.Attribute]
            
        # Create and set the parameters' attributes
        $ParameterAttribute = New-Object -TypeName System.Management.Automation.PARAMETERAttribute
        $ParameterAttribute.Mandatory = $true
        $ParameterAttribute.Position = 0
        $ParameterAttribute.ParameterSetName ="Id"
        
        # Add the attributes to the attributes collection
        $AttributeCollection.Add($ParameterAttribute)

        # Generate and set the ValidateSet
        $Set = Get-Process | Select-Object -ExpandProperty Id 
        $ValidateSetAttribute = New-Object -TypeName System.Management.Automation.ValidateSetAttribute($Set)
        $AttributeCollection.Add($ValidateSetAttribute)

        #Add Alias
        $AliasAttribute = New-Object -TypeName System.Management.Automation.AliasAttribute("Id")
        $AttributeCollection.Add($AliasAttribute)

        # Create and return the dynamic parameter
        $RuntimeParameter = New-Object -TypeName System.Management.Automation.RuntimeDefinedParameter("ProcessId", [Int32], $AttributeCollection)
        $RuntimeParameterDictionary.Add("ProcessId", $RuntimeParameter)

        #endregion
        
        return $RuntimeParameterDictionary
    }

    Begin {

        if (!([System.Security.Principal.WindowsPrincipal][System.Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([System.Security.Principal.WindowsBuiltInRole]::Administrator)) {
            throw "Run the cmdlet with elevated credentials."
        }
    }

    Process {

        [IntPtr]$DulicateTokenHandle = [IntPtr]::Zero
        [IntPtr]$ProcessTokenHandle = [IntPtr]::Zero
        
        if (!([System.Management.Automation.PSTypeName]"AdjPriv").Type) {
            Add-Type -MemberDefinition $script:TokenSignature -Name AdjPriv -Namespace AdjPriv
        }

        $AdjPriv = [AdjPriv.AdjPriv]

        try {
            switch ($PSCmdlet.ParameterSetName) {
                "Name" {
                    $Process = Get-Process -Name $PSBoundParameters["ProcessName"]
                    break
                }
                "Id" {
                    $Process = Get-Process -Id $PSBoundParameters["ProcessId"]
                    break
                }
                default {
                    throw "Cannot determine parameter set."
                }
            }

            $ReturnValue = $AdjPriv::OpenProcessToken($Process.Handle, ([AdjPriv.AdjPriv]::TOKEN_IMPERSONATE -BOR [AdjPriv.AdjPriv]::TOKEN_DUPLICATE), [ref]$ProcessTokenHandle)
            $ReturnValue = $AdjPriv::DuplicateToken($ProcessTokenHandle, [AdjPriv.AdjPriv+SECURITY_IMPERSONATION_LEVEL]::SecurityImpersonation, [ref]$DulicateTokenHandle)
        
            if($ReturnValue -eq $null -or $ReturnValue -eq $false) {
                throw (New-Object -TypeName System.Exception([System.ComponentModel.Win32Exception][System.Runtime.InteropServices.marshal]::GetLastWin32Error()))
            }
        }
        finally {
            $AdjPriv::CloseHandle($ProcessTokenHandle) | Out-Null

            if ($CloseHandle) {
                $AdjPriv::CloseHandle($DulicateTokenHandle) | Out-Null
            }
        }

        Write-Output -InputObject $DulicateTokenHandle
    }

    End {        
    }
}

Function Set-ProcessToken {
    <#
        .SYNOPSIS
            Replaces the process token for the current process thread with a token from another process.
 
        .DESCRIPTION
            The Set-ProcessToken cmdlet takes a token handle from another process and then sets the process thread to use that token. Then it closes the token handle.
 
            The passed token handle must not be closed before it is passed.
             
            The CmdLet must be run with elevated permissions.
 
        .PARAMETER TokenHandle
            The Token Handle pointer that will replace the current process thread token.
 
        .PARAMETER ElevatePrivileges
            Adds the SeDebugPrivilege to the current process thread, which may be needed to replace the current process thread token.
 
        .EXAMPLE
            Get-ProcessToken -ProcessName lsass | Set-ProcessToken
 
            Gets the token handle for the lsass process and replaces the current process thread token.
 
        .INPUTS
            System.IntPtr
 
        .OUTPUTS
            None
 
        .NOTES
            AUTHOR: Michael Haken
            LAST UPDATE: 3/25/2016
    #>

    [CmdletBinding()]
    Param(
        [Parameter(Mandatory=$true,Position=0,ValueFromPipeline=$true)]
        [IntPtr]$TokenHandle,
        [Parameter(Position=1)]
        [switch]$ElevatePrivileges
    )

    Begin {
        if (!([System.Security.Principal.WindowsPrincipal][System.Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([System.Security.Principal.WindowsBuiltInRole]::Administrator)) {
            throw "Run the cmdlet with elevated credentials."
        }
    }

    Process {
        if (!([System.Management.Automation.PSTypeName]"AdjPriv").Type) {
            Add-Type -MemberDefinition $script:TokenSignature -Name AdjPriv -Namespace AdjPriv
        }

        $AdjPriv = [AdjPriv.AdjPriv]

        if ($ElevatePrivileges) {

            $TokenPrivilege1Luid = New-Object AdjPriv.AdjPriv+TokPriv1Luid
            $TokenPrivilege1Luid.Count = 1
            $TokenPrivilege1Luid.Luid = 0
            $TokenPrivilege1Luid.Attr = [AdjPriv.AdjPriv]::SE_PRIVILEGE_ENABLED

            [System.IntPtr]$TempToken = [System.IntPtr]::Zero

            $ReturnValue = $AdjPriv::LookupPrivilegeValue($null, "SeDebugPrivilege", [ref]$TokenPrivilege1Luid.Luid)
            $ReturnValue = $AdjPriv::OpenProcessToken($AdjPriv::GetCurrentProcess(), [AdjPriv.AdjPriv]::TOKEN_ALL_ACCESS, [ref]$TempToken)
  
            $TokenPrivileges = New-Object -TypeName AdjPriv.AdjPriv+TOKEN_PRIVILEGES
        
            $DisableAllPrivileges = $false
            $BufferLength = 12
            $ReturnValue = $AdjPriv::AdjustTokenPrivileges($TempToken, $DisableAllPrivileges, [ref]$TokenPrivilege1Luid, $BufferLength, [IntPtr]::Zero, [IntPtr]::Zero)

            if($ReturnValue -eq $null -or $ReturnValue -eq $false) {
                throw (New-Object -TypeName System.Exception([System.ComponentModel.Win32Exception][System.Runtime.InteropServices.Marrshal]::GetLastWin32Error()))
            }
        }

        try {
            $ReturnValue = $AdjPriv::SetThreadToken([IntPtr]::Zero, $TokenHandle)

            if($ReturnValue -eq $null -or $ReturnValue -eq $false) {
                throw (New-Object -TypeName System.Exception([System.ComponentModel.Win32Exception][System.Runtime.InteropServices.Marshal]::GetLastWin32Error()))
            }
        }
        finally {
            $AdjPriv::CloseHandle($TokenHandle) | Out-Null
        }

        Write-Host "Successfully duplicated token to current process thread." -ForegroundColor Green
    }

    End {        
    }
}

Function Reset-ProcessToken {
    <#
        .SYNOPSIS
            Reverts to the process thread token to the current user.
 
        .DESCRIPTION
            The Reset-ProcessToken cmdlet needs to be called to end any process impersonation called through DdeImpersonateClient, ImpersonateDdeClientWindow, ImpersonateLoggedOnUser, ImpersonateNamedPipeClient, ImpersonateSelf, ImpersonateAnonymousToken or SetThreadToken.
             
            Underlying the cmdlet is a P/Invoke call to RevertToSelf() in AdvApi32.dll.
 
            The CmdLet must be run with elevated permissions.
 
        .EXAMPLE
            Reset-ProcessToken
 
            Reverts the process thread to use the token of the current user.
 
        .INPUTS
            None
 
        .OUTPUTS
            None
 
        .NOTES
            AUTHOR: Michael Haken
            LAST UPDATE: 3/25/2016
    #>


    [CmdletBinding()]
    Param()

    Begin {
        if (!([System.Security.Principal.WindowsPrincipal][System.Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([System.Security.Principal.WindowsBuiltInRole]::Administrator)) {
            throw "Run the cmdlet with elevated credentials."
        }
    }

    Process {
        if (!([System.Management.Automation.PSTypeName]"AdjPriv").Type) {
            Add-Type -MemberDefinition $script:TokenSignature -Name AdjPriv -Namespace AdjPriv
        }

        $AdjPriv = [AdjPriv.AdjPriv]

        #RevertToSelf is equivalent to SetThreadToken([System.IntPtr]::Zero, [System.IntPtr]::Zero)
        $ReturnValue = $AdjPriv::RevertToSelf()

        if($ReturnValue -eq $null -or $ReturnValue -eq $false) {
            throw (New-Object -TypeName System.Exception([System.ComponentModel.Win32Exception][System.Runtime.InteropServices.Marshal]::GetLastWin32Error()))
        }

        Write-Host "Successfully executed RevertToSelf() and reset the process thread token."
    }

    End {        
    }
}

Function Get-LsaSecret {
    <#
        .SYNOPSIS
            Enumerates the content of the LSA Secrets registry hive.
 
        .DESCRIPTION
            The cmdlet first duplicates the lsass process token and sets it to the current process thread. Then it copies each secret stored in HKLM:\SECURITY\Policy\Secrets to a temporary location.
            After the content is copied over, Lsa functions from AdvApi32.dll are called to decrypt the content. When the cmdlet finishes, it leaves the registry area unchanged and reverts the process thread token.
 
            The CmdLet must be run with elevated permissions.
 
        .EXAMPLE
            Get-LsaSecret
 
            Retrieves all of the stored secrets in the registry using HKLM:\SECURITY\Policy\Secrets\<Generated GUID> to store the temporary information.
 
        .INPUTS
            None
 
        .OUTPUTS
            None
 
        .NOTES
            AUTHOR: Michael Haken
            LAST UPDATE: 3/27/2016
    #>


    [CmdletBinding()]
    Param()

    Begin {
        if (!([System.Management.Automation.PSTypeName]"Bamcis.Lsa.LSAUtil").Type) {
            Add-Type -TypeDefinition $script:LsaSignature
        }
    }

    Process {
        Get-ProcessToken -ProcessName lsass | Set-ProcessToken    

        $TempKey = [System.Guid]::NewGuid().ToString()

        #Set up a temporary location to copy the registry keys over so that we can enumerate them, we have to be the owner to get the unencrypted values
        $Destination = "HKLM:\SECURITY\Policy\Secrets\$TempKey"

        if ((Get-Item -Path $Destination -ErrorAction SilentlyContinue) -ne $null) {
            Remove-Item -Path $Destination -Recurse -Force | Out-Null
        }

        New-Item -Path $Destination | Out-Null
        $Secrets = @()

        #Get all sub keys in secrets, these are the accounts
        Get-ChildItem -Path "HKLM:\SECURITY\Policy\Secrets" | Where-Object  {$_.Name -notmatch $TempKey -and $_.Property -ne $null} | ForEach-Object {
            $AccountName = $_.PSChildName
            
            #Get all the sub keys of the accounts, these are keys like CurrVal, OldVal, CupdTime, etc
            Get-ChildItem -Path $_.PSPath | ForEach-Object {
                $ItemName = $_.PSChildName

                #If the sub key exists at the temp destination, delete it
                if ((Test-Path -Path "$Destination\$ItemName")) {
                    Remove-Item -Path "$Destination\$ItemName" -Recurse -Force | Out-Null
                }

                #Copy the value over to the new registry location
                [System.Byte[]]$Property = Get-ItemProperty -Path $_.PSPath | Select-Object -ExpandProperty "(Default)"
                New-Item -Path "$Destination\$ItemName" | Out-Null
                Set-ItemProperty -Path "$Destination\$ItemName" -Name '(Default)' -Value $Property
            }

            $LsaUtil = New-Object -TypeName Bamcis.Lsa.LSAUtil -ArgumentList @($TempKey)

            try {
                $Value = $LsaUtil.GetSecret()
            }
            catch [Exception] {
                $Value = [System.String]::Empty
            }

            if ($AccountName -match "^_SC_") {
                # Get Service Account
                $Service = $AccountName -Replace "^_SC_"
                Try {
                    # Get Service Account
                    $Service = Get-WmiObject -Query "SELECT StartName FROM Win32_Service WHERE Name = '$Service'" -ErrorAction Stop
                    $Account = $Service.StartName
                }
                catch [Exception] {
                    $Account = [System.String]::Empty
                }
            } else {
                $Account = [System.String]::Empty
            }

            $Hex = [System.Text.Encoding]::Unicode.GetBytes($Value) | ForEach-Object {
                Write-Output -InputObject $_.ToString("X2")
            }

            $EncryptedBinary = [System.Byte[]](Get-ItemProperty -Path "$Destination\CurrVal" -Name "(Default)" | Select-Object -ExpandProperty "(Default)")

            $Temp = Set-ItemProperty -Path "$Destination\CurrVal" -Name "(Default)" -Value (Get-ItemProperty -Path "$Destination\OldVal" -Name "(Default)" | Select-Object -ExpandProperty "(Default)") -PassThru

            try {
                $OldSecret = $LsaUtil.GetSecret()
            }
            catch [Exception] {
                $OldSecret = [System.String]::Empty
            }

            $Secrets += (New-Object -TypeName PSObject -Property @{Name = $AccountName; Secret = $Value; OldSecret = $OldSecret; SecretHex = ($Hex -join " "); Account = $Account; EncryptedBinary = $EncryptedBinary})  
        }

        Remove-Item -Path "$Destination" -Force -Recurse
        Reset-ProcessToken
        Write-Output -InputObject $Secrets
    }

    End {        
    }
}

Function ConvertFrom-Xml {
    <#
        .SYNOPSIS
            Converts an Xml object to as PSObject.
 
        .DESCRIPTION
            The ConvertFrom-Xml recursively goes through an Xml object and enumerates the properties of each inputted element. Those properties are accessed and added to the returned object.
 
            An XmlElement that has attributes and XmlText will end up with the XmlText value represented as a "#name" property in the resulting object.
 
        .EXAMPLE
            ConvertFrom-Xml -InputObject $XmlObj
 
            Returns an PSObject constructed from the $XmlObj variable
 
        .PARAMETER InputObject
            The InputObject is an Xml type in the System.Xml namespace. It could be an XmlDocument, XmlElement, or XmlNode for example. It cannot be a collection of Xml objects.
 
        .INPUTS
            System.Xml
 
        .OUTPUTS
            System.Management.Automation.PSObject
 
        .NOTES
            AUTHOR: Michael Haken
            LAST UPDATE: 3/31/2015
    #>

    [CmdletBinding()]
    Param(
        [Parameter(Position=0,ValueFromPipeline=$true,Mandatory=$true)]
        [ValidateScript({$_.GetType().Namespace -eq "System.Xml"})]
        $InputObject
    )

    Begin {       
    }

    Process {
        $private:Hash = @{}
        
        Get-Member -InputObject $InputObject -MemberType Property | Where-Object {$_.Name -ne "xml" -and (![System.String]::IsNullOrEmpty($_.Name))} | ForEach-Object {
            $PropertyName = $_.Name
            $InputItem = $InputObject.($PropertyName)

            #There are multiple items with the same tag name
            if ($InputItem.GetType() -eq [System.Object[]]) {
                
                #Make the tag name an array
                $private:Hash.($PropertyName) = @()

                #Go through each item in the array
                $InputItem | Where-Object {$_ -ne $null} | ForEach-Object {
                    
                    #Item is an object in the array
                    $Item = $_
                    [System.Type]$Type = $Item.GetType()

                    if ($Type.IsPrimitive -or $Type -eq [System.String]) {                   
                        $private:Hash.($PropertyName) = $Item
                    }
                    else {
                        #Create a temp variable to hold the new object that will be added to the array
                        $Temp = @{}  
                                
                        #Make attributes properties of the object
                        $Item.Attributes | ForEach-Object {
                            $Temp.($_.Name) = $_.Value
                        }

                        #As an XmlElement, the element will have at least 1 childnode, it's value
                        $Item.ChildNodes | Where-Object {$_ -ne $null -and ![System.String]::IsNullOrEmpty($_.Name)} | ForEach-Object {
                            $ChildNode = $_
   
                            if ($ChildNode.HasChildNodes) {
                                #If the item has 1 childnode and the childnode is XmlText, then the child is this type of element,
                                #<Name>ValueText</Name>, so its child is just the value
                                if ($ChildNode.ChildNodes.Count -eq 1 -and $ChildNode.ChildNodes[0].GetType() -eq [System.Xml.XmlText] -and !($ChildNode.HasAttributes)) {
                                    $Temp.($ChildNode.ToString()) = $ChildNode.ChildNodes[0].Value
                                }
                                else {
                                    $Temp.($ChildNode.ToString()) = ConvertFrom-Xml -InputObject $ChildNode
                                }
                            }
                            else {
                                $Temp.($ChildNode.ToString()) = $ChildNode.Value
                            }
                        }
                    
                        $private:Hash.($PropertyName) += $Temp
                    }
                }
            }
            else {
                if ($InputItem -ne $null) {
                    $Item = $InputItem
                    [System.Type]$Type = $InputItem.GetType()
                    
                    if ($Type.IsPrimitive -or $Type -eq [System.String]) {                   
                        $private:Hash.($PropertyName) = $Item
                    }
                    else {

                        $private:Hash.($PropertyName) = @{}  
                                
                        $Item.Attributes | ForEach-Object {
                            $private:Hash.($PropertyName).($_.Name) = $_.Value
                        }

                        $Item.ChildNodes | Where-Object {$_ -ne $null -and ![System.String]::IsNullOrEmpty($_.Name)} | ForEach-Object {
                            $ChildNode = $_
                            
                            if ($ChildNode.HasChildNodes) {
                                if ($ChildNode.ChildNodes.Count -eq 1 -and $ChildNode.ChildNodes[0].GetType() -eq [System.Xml.XmlText] -and !($ChildNode.HasAttributes)) {      
                                    $private:Hash.($PropertyName).($ChildNode.ToString()) = $ChildNode.ChildNodes[0].Value
                                }
                                else {
                                    $private:Hash.($PropertyName).($ChildNode.ToString()) = ConvertFrom-Xml -InputObject $ChildNode
                                }
                            }
                            else {
                                $private:Hash.($PropertyName).($ChildNode.ToString()) = $ChildNode.Value
                            }
                        }
                    }
                }
            }                  
        }

         Write-Output -InputObject (New-Object -TypeName System.Management.Automation.PSObject -Property $private:Hash)
    }

    End {      
    }
}

Function Get-ESEDatabase {
    <#
        .SYNOPSIS
            Enumerates a Extensible Storage Engine (ESE) database, providing all tables and data contained within those tables.
 
        .DESCRIPTION
            The Get-ESEDatabase cmdlet starts a new sessions with the given ESE database. If the database is in a dirty shutdown state, the cmdlet will run a repair or restore operation.
 
            It is recommended that an offline copy of the database is used for enumeration so that no data in an active database is lost. The database is opened with the recovery option set to false to stop errors with page size conflicts.
 
        .EXAMPLE
            Get-ESEDatabase -Path C:\Users\Administrator\AppData\Local\Microsoft\Windows\WebCache\WebCacheV01.dat -LogPrefix "V01" -ProcessesToStop @("dllhost","taskhostw")
 
            Gets an array of PSCustomObjects where each item in the array is a complete set of table data. The array represents the tables in the WebCacheV01.dat database. The processes dllhost and taskhostw are stopped to free the database from use. The log prefix is set to V01 to be used with the esentutl utility for repair operations.
 
        .PARAMETER Path
            The path to the ESE database.
 
        .PARAMETER LogPrefix
            The prefix of the logs files for the database to be used with esentutl repair operations.
 
        .PARAMETER FutureTimeLimit
            Two column data types, 14 and 15 are not specifically defined in the ESE documentation. Sometimes these are datetime objects, and sometimes they are Int64. In order to properly translate these data types, a future limit is set on converting them to a DateTime.
 
            Input is converted to a DateTime if it is between 1 Jan 1970 and a TimeSpan defined by this parameter added to the current date. This defaults to 100 years.
 
        .PARAMETER PageSize
            The page size to be used in reading the database. This information can be specified or defaults to being read from the database file. The value must be a multiple of 1024.
 
        .PARAMETER ProcessesToStop
            Specify any processes that will be stopped to free the database from exclusive locks, even for readonly operations.
 
        .PARAMETER Recovery
            Sets the Microsoft.Isam.Esent.Interop.JET_param.Recovery option in the JetSetSystemParameter object when opening the database. This defaults to false to prevent errors with the PageSize setting.
 
            This parameter is the master switch that controls crash recovery for an instance. If this parameter is set to "On" then ARIES style recovery will be used to bring all databases in the instance to a consistent state in the event of a process or machine crash. If this parameter is set to "Off" then all databases in the instance will be managed without the benefit of crash recovery. That is to say, that if the instance is not shut down cleanly using JetTerm prior to the process exiting or machine shutdown then the contents of all databases in that instance will be corrupted.
 
            https://msdn.microsoft.com/en-us/library/microsoft.isam.esent.interop.jet_param(v=exchg.10).aspx
 
        .PARAMETER CircularLogging
            This parameter configures how transaction log files are managed by the database engine. When circular logging is off, all transaction log files that are generated are retained on disk until they are no longer needed because a full backup of the database has been performed. When circular logging is on, only transaction log files that are younger than the current checkpoint are retained on disk. The benefit of this mode is that backups are not required to retire old transaction log files.
 
            This defaults to true.
 
            https://msdn.microsoft.com/en-us/library/microsoft.isam.esent.interop.jet_param(v=exchg.10).aspx
 
        .PARAMETER UserName
            An optional username used to connect to the database
 
        .PARAMETER Password
            A SecureString password object used to connect to the database.
 
        .PARAMETER Force
            Bypasses the check to confirm the operation since it may modify the database causing data loss.
 
        .PARAMETER EsentDllPath
            The path to the Esent library. This defaults to $env:SYSTEMDRIVE\Microsoft.NET\assembly\GAC_MSIL\microsoft.isam.esent.interop\v4.0_10.0.0.0__31bf3856ad364e35\Microsoft.Isam.Esent.Interop.dll.
 
        .INPUTS
            System.String
 
        .OUTPUTS
            System.Management.Automation.PSCustomObject[]
 
        .NOTES
            AUTHOR: Michael Haken
            LAST UPDATE: 4/25/2016
    #>

    [CmdletBinding()]
    Param(
        [Parameter(Position=0,Mandatory=$true,ValueFromPipeline=$true)]
        [ValidateScript({Test-Path -Path $_})]
        [string]$Path,
        [Parameter()]
        [string]$LogPrefix = [System.String]::Empty,
        [Parameter()]
        [System.TimeSpan]$FutureTimeLimit = [System.TimeSpan]::FromDays(36500),
        [Parameter()]
        [ValidateScript({($_ % 1024) -eq 0})]
        [int]$PageSize = -1,
        [Parameter()] 
        [string[]]$ProcessesToStop = @(),
        [Parameter()]
        [bool]$Recovery = $false,
        [Parameter()]
        [bool]$CircularLogging = $true,
        [Parameter()]
        [string]$UserName = [System.String]::Empty,
        [Parameter()]
        [System.Security.SecureString]$Password = $null,
        [Parameter()]
        [switch]$Force,
        [Parameter()]
        [ValidateScript({Test-Path -Path $_})]
        [string]$EsentDllPath = $script:EsentDllPath
    )

    Begin {
    }

    Process {
        if(!$Force) {
            $Title = "Confirm action."
            $Message = "This command may modify the database by running a repair if it is in a dirty shutdown state. You may lose data. It is recommended that you use an offline copy. Are you sure you want to continue?"

            $Yes = New-Object System.Management.Automation.Host.ChoiceDescription("&Yes","Executes database query.")
            $No = New-Object System.Management.Automation.Host.ChoiceDescription("&No", "Quits the cmdlet.")
            $Options = [System.Management.Automation.Host.ChoiceDescription[]]($Yes, $No)

            $private:Result = $host.ui.PromptForChoice($Title, $Message, $Options, 0) 
        }
        else {
            $private:Result = 0
        }

        if ($private:Result -eq 0) {
            $Temp = Add-Type -Path $EsentDllPath -ErrorAction Stop
        }

        $Tables = @()
        if ($private:Result -eq 0) {
            Write-Verbose -Message "Initiating new session to $Path."
            $Force = $true

            $DBSession = Get-ESEDatabaseSession -Path $Path -LogPrefix $LogPrefix -PageSize $PageSize -ProcessesToStop $ProcessesToStop -Recovery $Recovery -CircularLogging $CircularLogging -UserName $UserName -Password $Password -Force:$Force -ErrorAction Stop
            Write-Verbose -Message "Successfully initiated new session to $Path."

            $Session = $DBSession.Session
            $DatabaseId = $DBSession.DatabaseId
            $Instance = $DBSession.Instance

            try {
                Write-Verbose -Message "Getting table names"
                $TableNames = Get-ESEDatabaseTableNames -Session $Session -DatabaseId $DatabaseId

                Write-Verbose -Message "Iterating Tables"
                foreach ($TableName in $TableNames) {
                    Write-Verbose -Message "Processing table $TableName."
                    try {
                        $Tables += Get-ESEDatabaseTableData -Session $Session -DatabaseId $DatabaseId -TableName $TableName -FutureTimeLimit $FutureTimeLimit
                    }
                    catch [Exception] {
                        Write-Warning -Message $_.Exception.Message
                    }
                }
            }
            finally {
                Write-Verbose -Message "Closing database connection as the final step."
                Close-ESEDatabase -Instance $Instance -Session $Session -DatabaseId $DatabaseId -Path $Path -ErrorAction SilentlyContinue
            }
        }

        if ($private:Result -eq 0) {
            Write-Output -InputObject $Tables
        }
    }

    End {    
    }
}

Function Get-ESEDatabaseSession {
    <#
        .SYNOPSIS
            Builds a new session with an ESE database and opens the database for ReadOnly operations.
 
        .DESCRIPTION
            The Get-ESEDatabaseSession cmdlet starts a new sessions with the given ESE database. If the database is in a dirty shutdown state, the cmdlet will run a repair or restore operation.
 
            The cmdlet adds the ESENT library from $env:SYSTEMDRIVE\Microsoft.NET\assembly\GAC_MSIL\microsoft.isam.esent.interop\v4.0_10.0.0.0__31bf3856ad364e35\Microsoft.Isam.Esent.Interop.dll
 
            It is recommended that an offline copy of the database is used for enumeration so that no data in an active database is lost. The database is opened with the recovery option set to false to stop errors with page size conflicts.
 
            The database should be closed with the Close-ESEDatabase cmdlet after all operations are complete using this Session and Instance.
 
        .EXAMPLE
            Get-ESEDatabaseSession -Path C:\Users\Administrator\AppData\Local\Microsoft\Windows\WebCache\WebCacheV01.dat -LogPrefix "V01" -ProcessesToStop @("dllhost","taskhostw")
 
            Gets a PSCustomObject with the database instance, database id, database object, and path information of the database. This information can be used with additional cmdlets to read data in the database.
     
            The processes dllhost and taskhostw are stopped to free the database from use. The log prefix is set to V01 to be used with the esentutl utility for repair operations.
 
        .PARAMETER Path
            The path to the ESE database.
 
        .PARAMETER LogPrefix
            The prefix of the logs files for the database to be used with esentutl repair operations.
 
        .PARAMETER PageSize
            The page size to be used in reading the database. This information can be specified or defaults to being read from the database file. The value must be a multiple of 1024.
 
        .PARAMETER ProcessesToStop
            Specify any processes that will be stopped to free the database from exclusive locks, even for readonly operations.
 
        .PARAMETER Recovery
            Sets the Microsoft.Isam.Esent.Interop.JET_param.Recovery option in the JetSetSystemParameter object when opening the database. This defaults to false to prevent errors with the PageSize setting.
 
            This parameter is the master switch that controls crash recovery for an instance. If this parameter is set to "On" then ARIES style recovery will be used to bring all databases in the instance to a consistent state in the event of a process or machine crash. If this parameter is set to "Off" then all databases in the instance will be managed without the benefit of crash recovery. That is to say, that if the instance is not shut down cleanly using JetTerm prior to the process exiting or machine shutdown then the contents of all databases in that instance will be corrupted.
 
            https://msdn.microsoft.com/en-us/library/microsoft.isam.esent.interop.jet_param(v=exchg.10).aspx
 
        .PARAMETER CircularLogging
            This parameter configures how transaction log files are managed by the database engine. When circular logging is off, all transaction log files that are generated are retained on disk until they are no longer needed because a full backup of the database has been performed. When circular logging is on, only transaction log files that are younger than the current checkpoint are retained on disk. The benefit of this mode is that backups are not required to retire old transaction log files.
 
            This defaults to true.
 
            https://msdn.microsoft.com/en-us/library/microsoft.isam.esent.interop.jet_param(v=exchg.10).aspx
 
        .PARAMETER UserName
            An optional username used to connect to the database
 
        .PARAMETER Password
            A SecureString password object used to connect to the database.
 
        .PARAMETER Force
            Bypasses the check to confirm the operation since it may modify the database causing data loss.
 
        .PARAMETER EsentDllPath
            The path to the Esent library. This defaults to $env:SYSTEMDRIVE\Microsoft.NET\assembly\GAC_MSIL\microsoft.isam.esent.interop\v4.0_10.0.0.0__31bf3856ad364e35\Microsoft.Isam.Esent.Interop.dll.
 
        .INPUTS
            System.String
 
        .OUTPUTS
            System.Management.Automation.PSCustomObject
 
            This object contains the following members:
 
            [Microsoft.Isam.Esent.Interop.JET_INSTANCE] - The database instance being opened
            [Microsoft.Isam.Esent.Interop.JET_SESID] - The session being used to access the database, the session could have multiple instances opened in it, but in this case it is just the one
            [Microsoft.Isam.Esent.Interop.JET_DBID] - The database ID of the database instance being opened
            [System.String] - The specified path to the database
 
        .NOTES
            AUTHOR: Michael Haken
            LAST UPDATE: 4/25/2016
    #>

    [CmdletBinding()]
    Param(
        [Parameter(Position=0,Mandatory=$true,ValueFromPipeline=$true)]
        [string]$Path = "$env:USERPROFILE\AppData\Local\Microsoft\Windows\WebCache\WebCacheV01.dat",
        [Parameter()]
        [string]$LogPrefix = "V01",
        [Parameter()]
        [int]$PageSize = -1,
        [Parameter()] 
        [string[]]$ProcessesToStop = @(),
        [Parameter()]
        [bool]$Recovery = $false,
        [Parameter()]
        [bool]$CircularLogging = $true,
        [Parameter()]
        [string]$UserName = [System.String]::Empty,
        [Parameter()]
        [System.Security.SecureString]$Password = $null,
        [Parameter()]
        [switch]$Force,
        [Parameter()]
        [ValidateScript({Test-Path -Path $_})]
        [string]$EsentDllPath = $script:EsentDllPath
    )

    Begin {
    }

    Process {
        if(!$Force) {
            $Title = "Confirm action."
            $Message = "This command may modify the database by running a repair if it is in a dirty shutdown state. You may lose data. It is recommended that you use an offline copy. Are you sure you want to continue?"

            $Yes = New-Object System.Management.Automation.Host.ChoiceDescription("&Yes","Executes database query.")
            $No = New-Object System.Management.Automation.Host.ChoiceDescription("&No", "Quits the cmdlet.")
            $Options = [System.Management.Automation.Host.ChoiceDescription[]]($Yes, $No)

            $private:Result = $host.ui.PromptForChoice($Title, $Message, $Options, 0) 
        } 
        else {
            $private:Result = 0
        }

        if ($private:Result -eq 0) {

            $Temp = Add-Type -Path $EsentDllPath -ErrorAction Stop

            $Tables = @()
            $Connect = [System.String]::Empty
            $Pass = [System.String]::Empty

            if ($Password -ne $null) {
                $Marshal = [System.Runtime.InteropServices.Marshal]   
                try 
                {     
                    $IntPtr = $Marshal::SecureStringToBSTR($SecureString)     
                    $Pass = $Marshal::PtrToStringAuto($IntPtr)   
                }   
                finally 
                {     
                    if ($IntPtr) {       
                        $Marshal::ZeroFreeBSTR($IntPtr)     
                    }   
                }
            }
        }    

        if ($private:Result -eq 0) {
            foreach ($Process in $ProcessesToStop) {
            
                if ((Get-Process -Name $Process -ErrorAction SilentlyContinue) -ne $null) {
                    Write-Verbose -Message "Stopping process $Process"
                    Stop-Process -Name $Process -ErrorAction SilentlyContinue
                    Write-Verbose -Message "Process stopped"
                }
                else {
                    Write-Verbose -Message "Process $Process does not exist."
                }
            }

            [int]$FileType = -1
            [Microsoft.Isam.Esent.Interop.Api]::JetGetDatabaseFileInfo($Path, [ref]$FileType, [Microsoft.Isam.Esent.Interop.JET_DbInfo]::FileType)
            $DBType = [Microsoft.Isam.Esent.Interop.JET_filetype]($FileType)
            Write-Verbose -Message "File type $DBType."
        
            if ($DBType -eq [Microsoft.Isam.Esent.Interop.JET_filetype]::Database) {

                if ($PageSize -eq -1 -or ($PageSize % 1024 -ne 0)) {
                    [Microsoft.Isam.Esent.Interop.Api]::JetGetDatabaseFileInfo($Path, [ref]$PageSize, [Microsoft.Isam.Esent.Interop.JET_DbInfo]::PageSize)
                }
                Write-Verbose -Message "Page size $PageSize."

                [Microsoft.Isam.Esent.Interop.JET_INSTANCE]$Instance = New-Object -TypeName Microsoft.Isam.Esent.Interop.JET_INSTANCE
                [Microsoft.Isam.Esent.Interop.JET_SESID]$Session = New-Object -TypeName Microsoft.Isam.Esent.Interop.JET_SESID

                $Temp = [Microsoft.Isam.Esent.Interop.Api]::JetSetSystemParameter($Instance, [Microsoft.Isam.Esent.Interop.JET_SESID]::Nil, [Microsoft.Isam.Esent.Interop.JET_param]::DatabasePageSize, $PageSize, $null)
                $Temp = [Microsoft.Isam.Esent.Interop.Api]::JetSetSystemParameter($Instance, [Microsoft.Isam.Esent.Interop.JET_SESID]::Nil, [Microsoft.Isam.Esent.Interop.JET_param]::Recovery, [int]$Recovery, $null)
                $Temp = [Microsoft.Isam.Esent.Interop.Api]::JetSetSystemParameter($Instance, [Microsoft.Isam.Esent.Interop.JET_SESID]::Nil, [Microsoft.Isam.Esent.Interop.JET_param]::CircularLog, [int]$CircularLogging, $null)

                [Microsoft.Isam.Esent.Interop.Api]::JetCreateInstance2([ref]$Instance, "Instance", "Instance", [Microsoft.Isam.Esent.Interop.CreateInstanceGrbit]::None)
                $Temp = [Microsoft.Isam.Esent.Interop.Api]::JetInit2([ref]$Instance, [Microsoft.Isam.Esent.Interop.InitGrbit]::None)
                [Microsoft.Isam.Esent.Interop.Api]::JetBeginSession($Instance, [ref]$Session, $UserName, $Pass)

                [Microsoft.Isam.Esent.Interop.JET_DBID]$DatabaseId = New-Object -TypeName Microsoft.Isam.Esent.Interop.JET_DBID

                try {
                    try {
                        $Temp = [Microsoft.Isam.Esent.Interop.Api]::JetAttachDatabase($Session, $Path, [Microsoft.Isam.Esent.Interop.AttachDatabaseGrbit]::ReadOnly)
                        $Temp = [Microsoft.Isam.Esent.Interop.Api]::JetOpenDatabase($Session, $Path, $Connect, [ref]$DatabaseId, [Microsoft.Isam.Esent.Interop.OpenDatabaseGrbit]::ReadOnly)
                    }
                    catch [Exception] {
                        Write-Verbose -Message $_.Exception.Message
                        Write-Verbose -Message "Running recovery on $Path with log prefix $LogPrefix."
                        & "$env:SystemRoot\System32\esentutl.exe" "/r" "$LogPrefix"
                        try {
                            $Temp = [Microsoft.Isam.Esent.Interop.Api]::JetAttachDatabase($Session, $Path, [Microsoft.Isam.Esent.Interop.AttachDatabaseGrbit]::ReadOnly)
                            $Temp = [Microsoft.Isam.Esent.Interop.Api]::JetOpenDatabase($Session, $Path, $Connect, [ref]$DatabaseId, [Microsoft.Isam.Esent.Interop.OpenDatabaseGrbit]::ReadOnly)
                        }
                        catch [Exception] {
                            Write-Verbose -Message "Recovery failed, running repair on $Path."
                            & "$env:SystemRoot\System32\esentutl.exe" "/p" "$Path" "/o"
                            $Temp = [Microsoft.Isam.Esent.Interop.Api]::JetAttachDatabase($Session, $Path, [Microsoft.Isam.Esent.Interop.AttachDatabaseGrbit]::ReadOnly)
                            $Temp = [Microsoft.Isam.Esent.Interop.Api]::JetOpenDatabase($Session, $Path, $Connect, [ref]$DatabaseId, [Microsoft.Isam.Esent.Interop.OpenDatabaseGrbit]::ReadOnly)
                        }
                    }
                }
                catch [Exception] {
                    Write-Verbose -Message $_.Exception.Message
                    Write-Verbose -Message "Shutting down database due to exception."
                    try {
                        [Microsoft.Isam.Esent.Interop.Api]::JetDetachDatabase($Session, $Path)                    
                    }
                    finally {
                        [Microsoft.Isam.Esent.Interop.Api]::JetEndSession($Session, [Microsoft.Isam.Esent.Interop.EndSessionGrbit]::None)
                        [Microsoft.Isam.Esent.Interop.Api]::JetTerm($Instance)
                        Write-Verbose -Message "Completed shut down successfully."
                        throw $_
                    }
                }
            }
            else {
                throw "The path must be to a database, the selected path was a $DBType."
            }
        }

        if ($private:Result -eq 0) {
            Write-Output -InputObject ([PSCustomObject]@{Instance=$Instance;Session=$Session;DatabaseId=$DatabaseId;Path=$Path})
        }
    }

    End {        
    }
}

Function Get-ESEDatabaseTableNames {
    <#
        .SYNOPSIS
            Gets the table names from an ESE database.
 
        .DESCRIPTION
            The Get-ESEDatabaseTableNames cmdlet uses an existing session to a database and reads all of the table names.
 
        .EXAMPLE
            $Session = Get-ESEDatabaseSession -Path C:\Users\Administrator\AppData\Local\Microsoft\Windows\WebCache\WebCacheV01.dat -LogPrefix "V01" -ProcessesToStop @("dllhost","taskhostw")
            Get-ESEDatabaseTableNames -Session $Session.Session -DatabaseId $Session.DatabaseId
 
            Gets an array of table names in the database.
 
        .PARAMETER Session
            The Microsoft.Isam.Esent.Interop.JET_SESID session object.
 
        .PARAMETER DatabaseId
            The Microsoft.Isam.Esent.Interop.JET_DBID database Id object.
 
        .PARAMETER EsentDllPath
            The path to the Esent library. This defaults to $env:SYSTEMDRIVE\Microsoft.NET\assembly\GAC_MSIL\microsoft.isam.esent.interop\v4.0_10.0.0.0__31bf3856ad364e35\Microsoft.Isam.Esent.Interop.dll.
 
        .INPUTS
            Microsoft.Isam.Esent.Interop.JET_SESID, Microsoft.Isam.Esent.Interop.JET_DBID
 
        .OUTPUTS
            System.String[]
 
        .NOTES
            AUTHOR: Michael Haken
            LAST UPDATE: 4/25/2016
    #>

    [CmdletBinding()]
    Param(
        [Parameter(Position=0,Mandatory=$true)]
        $Session,
        [Parameter(Position=1,Mandatory=$true)]
        $DatabaseId,
        [Parameter()]
        [ValidateScript({Test-Path -Path $_})]
        [string]$EsentDllPath = $script:EsentDllPath
    )

    Begin {
        Add-Type -Path $EsentDllPath -ErrorAction Stop | Out-Null
    }

    Process {
        Write-Output -InputObject ([Microsoft.Isam.Esent.Interop.Api]::GetTableNames($Session, $DatabaseId))
    }

    End {        
    }
}

Function Get-ESEDatabaseTableColumns {
    <#
        .SYNOPSIS
            Gets the column information for a specific table.
 
        .DESCRIPTION
            The Get-ESEDatabaseTableColumns cmdlet uses an existing session to a database and reads the columns of a specified table.
 
        .EXAMPLE
            $Session = Get-ESEDatabaseSession -Path C:\Users\Administrator\AppData\Local\Microsoft\Windows\WebCache\WebCacheV01.dat -LogPrefix "V01" -ProcessesToStop @("dllhost","taskhostw")
            Get-ESEDatabaseTableNames -Session $Session.Session -DatabaseId $Session.DatabaseId | ForEach-Object {
                Get-ESEDatabaseTableColumns -Session $Session.Session -DatabaseId $Session.DatabaseId -TableName $_
            }
 
            Gets a List of ColumnInfo for each table in the specified database.
 
        .PARAMETER Session
            The Microsoft.Isam.Esent.Interop.JET_SESID session object.
 
        .PARAMETER DatabaseId
            The Microsoft.Isam.Esent.Interop.JET_DBID database Id object.
 
        .PARAMETER TableName
            The name of the table to get the column information from.
 
        .PARAMETER EsentDllPath
            The path to the Esent library. This defaults to $env:SYSTEMDRIVE\Microsoft.NET\assembly\GAC_MSIL\microsoft.isam.esent.interop\v4.0_10.0.0.0__31bf3856ad364e35\Microsoft.Isam.Esent.Interop.dll.
 
        .INPUTS
            Microsoft.Isam.Esent.Interop.JET_SESID, Microsoft.Isam.Esent.Interop.JET_DBID, System.String
 
        .OUTPUTS
             System.Collections.Generic.List[Microsoft.Isam.Esent.Interop.ColumnInfo]
 
        .NOTES
            AUTHOR: Michael Haken
            LAST UPDATE: 4/25/2016
    #>

    [CmdletBinding()]
    Param(
        [Parameter(Position=0,Mandatory=$true)]
        $Session,
        [Parameter(Position=1,Mandatory=$true)]
        $DatabaseId,
        [Parameter(Position=2,Mandatory=$true)]
        $TableName,
        [Parameter()]
        [ValidateScript({Test-Path -Path $_})]
        [string]$EsentDllPath = $script:EsentDllPath
    )

    Begin {
        $Temp = Add-Type -Path $EsentDllPath -ErrorAction Stop
    }

    Process {
        [Microsoft.Isam.Esent.Interop.Table]$Table = New-Object -TypeName Microsoft.Isam.Esent.Interop.Table($Session, $DatabaseId, $TableName, [Microsoft.Isam.Esent.Interop.OpenTableGrbit]::None)   
        $Columns = New-Object -TypeName System.Collections.Generic.List[Microsoft.Isam.Esent.Interop.ColumnInfo]
        $Columns += [Microsoft.Isam.Esent.Interop.Api]::GetTableColumns($Session, $Table.JetTableid)
        Write-Output -InputObject $Columns
    }

    End {        
    }
}

Function Get-ESEDatabaseTableData {
    <#
        .SYNOPSIS
            Gets all of the row information for a specified table.
 
        .DESCRIPTION
            The Get-ESEDatabaseTableData cmdlet uses an existing session to a database and reads all of the rows in a table.
 
        .EXAMPLE
            $Session = Get-ESEDatabaseSession -Path C:\Users\Administrator\AppData\Local\Microsoft\Windows\WebCache\WebCacheV01.dat -LogPrefix "V01" -ProcessesToStop @("dllhost","taskhostw")
            Get-ESEDatabaseTableNames -Session $Session.Session -DatabaseId $Session.DatabaseId | ForEach-Object {
                Get-ESEDatabaseTableData -Session $Session.Session -DatabaseId $Session.DatabaseId -TableName $_
            }
 
            Gets all of the table data for each table in the database.
 
        .PARAMETER Session
            The Microsoft.Isam.Esent.Interop.JET_SESID session object.
 
        .PARAMETER DatabaseId
            The Microsoft.Isam.Esent.Interop.JET_DBID database Id object.
 
        .PARAMETER TableName
            The name of the table to get the column information from.
 
        .PARAMETER FutureTimeLimit
            Two column data types, 14 and 15 are not specifically defined in the ESE documentation. Sometimes these are datetime objects, and sometimes they are Int64. In order to properly translate these data types, a future limit is set on converting them to a DateTime.
 
            Input is converted to a DateTime if it is between 1 Jan 1970 and a TimeSpan defined by this parameter added to the current date. This defaults to 100 years.
 
        .PARAMETER EsentDllPath
            The path to the Esent library. This defaults to $env:SYSTEMDRIVE\Microsoft.NET\assembly\GAC_MSIL\microsoft.isam.esent.interop\v4.0_10.0.0.0__31bf3856ad364e35\Microsoft.Isam.Esent.Interop.dll.
 
        .INPUTS
            Microsoft.Isam.Esent.Interop.JET_SESID, Microsoft.Isam.Esent.Interop.JET_DBID, System.String
 
        .OUTPUTS
            System.Management.Automation.PSCustomObject
 
            The custom object contains the TableName, TableId, and an array of row data that are PSCustomObjects. The row data objects have properties corresponding to the table columns.
 
        .NOTES
            AUTHOR: Michael Haken
            LAST UPDATE: 4/25/2016
    #>

    [CmdletBinding()]
    Param(
        [Parameter(Position=0,Mandatory=$true)]
        $Session,
        [Parameter(Position=1,Mandatory=$true)]
        $DatabaseId,
        [Parameter(Position=2,Mandatory=$true)]
        $TableName,
        [Parameter(Position=3)]
        [System.TimeSpan]$FutureTimeLimit = [System.TimeSpan]::FromDays(36500),
        [Parameter()]
        [ValidateScript({Test-Path -Path $_})]
        [string]$EsentDllPath = $script:EsentDllPath
    )

    Begin {
        Add-Type -Path $EsentDllPath -ErrorAction Stop | Out-Null
    }

    Process {
        Write-Verbose -Message "Getting table data for $TableName."
        
        try {
            [Microsoft.Isam.Esent.Interop.Table]$Table = New-Object -TypeName Microsoft.Isam.Esent.Interop.Table($Session, $DatabaseId, $TableName, [Microsoft.Isam.Esent.Interop.OpenTableGrbit]::None)   

            $NewTable = @{Name=$Table.Name;Id=$Table.JetTableid;Rows=@()}
            
            $Columns = New-Object -TypeName System.Collections.Generic.List[Microsoft.Isam.Esent.Interop.ColumnInfo]
            $Columns += [Microsoft.Isam.Esent.Interop.Api]::GetTableColumns($Session, $Table.JetTableid)

            if ([Microsoft.Isam.Esent.Interop.Api]::TryMoveFirst($Session, $Table.JetTableid)) {
                do {
                    $NewTable.Rows += Get-ESEDatabaseTableRowData -Session $Session -TableId $Table.JetTableid -Columns $Columns -FutureTimeLimit $FutureTimeLimit
                } while ([Microsoft.Isam.Esent.Interop.Api]::TryMoveNext($Session, $Table.JetTableid))               
            }

            Write-Output -InputObject ([PSCustomObject]$NewTable)
        }
        catch [Exception] { 
            Write-Warning -Message $_.Exception.Message
        }
    }

    End {        
    }
}

Function Get-ESEDatabaseTableRowData {
    <#
        .SYNOPSIS
            Gets all the current row information.
 
        .DESCRIPTION
            The Get-ESEDatabaseTableRowData cmdlet uses an existing session to a database and reads the current row information.
 
            This cmdlet should be used in combination with the [Microsoft.Isam.Esent.Interop.Api]::TryMoveNext($Session, $Table.JetTableid)) command to iterate over the rows in the table.
 
        .EXAMPLE
            if ([Microsoft.Isam.Esent.Interop.Api]::TryMoveFirst($Session, $Table.JetTableid)) {
                do {
                    Get-ESEDatabaseTableRowData -Session $Session -TableId $Table.JetTableid -Columns $Columns -FutureTimeLimit $FutureTimeLimit
                } while ([Microsoft.Isam.Esent.Interop.Api]::TryMoveNext($Session, $Table.JetTableid))
            }
 
            Gets all of the table data for given table by iterating over each row and retrieving that data.
 
        .PARAMETER Session
            The Microsoft.Isam.Esent.Interop.JET_SESID session object.
 
        .PARAMETER TableId
            The Microsoft.Isam.Esent.Interop.JET_TABLEID table Id object.
 
        .PARAMETER Columns
            The set of columns to get information for in the row as System.Collections.Generic.List[Microsoft.Isam.Esent.Interop.ColumnInfo].
 
            If this input is $null or the default, the cmdlet enumerates the column information and uses all columns.
 
        .PARAMETER FutureTimeLimit
            Two column data types, 14 and 15 are not specifically defined in the ESE documentation. Sometimes these are datetime objects, and sometimes they are Int64. In order to properly translate these data types, a future limit is set on converting them to a DateTime.
 
            Input is converted to a DateTime if it is between 1 Jan 1970 and a TimeSpan defined by this parameter added to the current date. This defaults to 100 years.
 
        .PARAMETER EsentDllPath
            The path to the Esent library. This defaults to $env:SYSTEMDRIVE\Microsoft.NET\assembly\GAC_MSIL\microsoft.isam.esent.interop\v4.0_10.0.0.0__31bf3856ad364e35\Microsoft.Isam.Esent.Interop.dll.
 
        .INPUTS
            Microsoft.Isam.Esent.Interop.JET_SESID, Microsoft.Isam.Esent.Interop.JET_TABLEID, System.Collections.Generic.List[Microsoft.Isam.Esent.Interop.ColumnInfo]
 
        .OUTPUTS
            System.Management.Automation.PSCustomObject
 
            The custom object contains a property and value for each column defined in the table and represents one row of data.
 
        .NOTES
            AUTHOR: Michael Haken
            LAST UPDATE: 4/25/2016
    #>

    [CmdletBinding()]
    Param(
        [Parameter(Position=0,Mandatory=$true)]
        $Session,
        [Parameter(Position=1,Mandatory=$true)]
        $TableId,
        [Parameter(Position=2,Mandatory=$true)]
        $Columns = $null,
        [Parameter(Position=3)]
        [System.TimeSpan]$FutureTimeLimit = [System.TimeSpan]::FromDays(36500),
        [Parameter()]
        [ValidateScript({Test-Path -Path $_})]
        [string]$EsentDllPath = $script:EsentDllPath
    )

    Begin {
        $Temp = Add-Type -Path $EsentDllPath -ErrorAction Stop
    }

    Process {
        $Row = @{}

        if ($Columns -eq $null) {
            $Columns = New-Object -TypeName System.Collections.Generic.List[Microsoft.Isam.Esent.Interop.ColumnInfo]
            $Columns += [Microsoft.Isam.Esent.Interop.Api]::GetTableColumns($Session, $Table.JetTableid)
        }

        foreach ($Column in $Columns) { 
            switch ($Column.Coltyp) {
                ([Microsoft.Isam.Esent.Interop.JET_coltyp]::Bit) {
                    $Buffer = [Microsoft.Isam.Esent.Interop.Api]::RetrieveColumnAsBoolean($Session, $Table.JetTableid, $Column.Columnid)
                    break
                }
                ([Microsoft.Isam.Esent.Interop.JET_coltyp]::DateTime) {
                    $Buffer = [Microsoft.Isam.Esent.Interop.Api]::RetrieveColumnAsDateTime($Session, $Table.JetTableid, $Column.Columnid)
                    break
                }
                ([Microsoft.Isam.Esent.Interop.JET_coltyp]::IEEEDouble) {
                    $Buffer = [Microsoft.Isam.Esent.Interop.Api]::RetrieveColumnAsDouble($Session, $Table.JetTableid, $Column.Columnid)
                    break
                }
                ([Microsoft.Isam.Esent.Interop.JET_coltyp]::IEEESingle) {
                    $Buffer = [Microsoft.Isam.Esent.Interop.Api]::RetrieveColumnAsFloat($Session, $Table.JetTableid, $Column.Columnid)
                    break
                }
                ([Microsoft.Isam.Esent.Interop.JET_coltyp]::Long) {
                    $Buffer = [Microsoft.Isam.Esent.Interop.Api]::RetrieveColumnAsInt32($Session, $Table.JetTableid, $Column.Columnid)
                    break
                }
                ([Microsoft.Isam.Esent.Interop.JET_coltyp]::Binary) {
                    $Buffer = [Microsoft.Isam.Esent.Interop.Api]::RetrieveColumnAsString($Session, $Table.JetTableid, $Column.Columnid, [System.Text.Encoding]::UTF8)
                    break
                }
                ([Microsoft.Isam.Esent.Interop.JET_coltyp]::LongBinary) {
                    $Buffer = [Microsoft.Isam.Esent.Interop.Api]::RetrieveColumnAsString($Session, $Table.JetTableid, $Column.Columnid, [System.Text.Encoding]::UTF8)
                    break
                }
                ([Microsoft.Isam.Esent.Interop.JET_coltyp]::LongText) {
                    $Buffer = [Microsoft.Isam.Esent.Interop.Api]::RetrieveColumnAsString($Session, $Table.JetTableid, $Column.Columnid, [System.Text.Encoding]::UTF8)
                            
                    #Replace null characters which are 0x0000 unicode
                    if (![System.String]::IsNullOrEmpty($Buffer)) {
                        $Buffer = $Buffer.Replace("`0", "")
                    }
                    break
                }
                ([Microsoft.Isam.Esent.Interop.JET_coltyp]::Text) {
                    $Buffer = [Microsoft.Isam.Esent.Interop.Api]::RetrieveColumnAsString($Session, $Table.JetTableid, $Column.Columnid, [System.Text.Encoding]::UTF8)
                                
                    #Replace null characters which are 0x0000 unicode
                    if (![System.String]::IsNullOrEmpty($Buffer)) {
                        $Buffer = $Buffer.Replace("`0", "")
                    }
                    break
                }
                ([Microsoft.Isam.Esent.Interop.JET_coltyp]::Currency) {
                    $Buffer = [Microsoft.Isam.Esent.Interop.Api]::RetrieveColumnAsString($Session, $Table.JetTableid, $Column.Columnid, [System.Text.Encoding]::UTF8)
                              
                    #Replace null characters which are 0x0000 unicode
                    if (![System.String]::IsNullOrEmpty($Buffer)) {
                        $Buffer = $Buffer.Replace("`0", "")
                    }
                    break
                }
                ([Microsoft.Isam.Esent.Interop.JET_coltyp]::Short) {
                    $Buffer = [Microsoft.Isam.Esent.Interop.Api]::RetrieveColumnAsInt16($Session, $Table.JetTableid, $Column.Columnid)
                    break
                }
                ([Microsoft.Isam.Esent.Interop.JET_coltyp]::UnsignedByte) {
                    $Buffer = [Microsoft.Isam.Esent.Interop.Api]::RetrieveColumnAsByte($Session, $Table.JetTableid, $Column.Columnid)
                    break
                }
                (14) {
                    $Buffer = [Microsoft.Isam.Esent.Interop.Api]::RetrieveColumnAsInt32($Session, $Table.JetTableid, $Column.Columnid)
                    break
                }
                (15) {
                    $Buffer = [Microsoft.Isam.Esent.Interop.Api]::RetrieveColumnAsInt64($Session, $Table.JetTableid, $Column.Columnid)
                            
                    try {
                        $DateTime = [System.DateTime]::FromBinary($Buffer)
                        $DateTime = $DateTime.AddYears(1600)
                               
                        if ($DateTime -gt (Get-Date -Year 1970 -Month 1 -Day 1) -and $DateTime -lt ([System.DateTime]::UtcNow.Add($FutureTimeLimit))) {
                            $Buffer = $DateTime
                        }
                    }
                    catch {}
                            
                    break                            
                }
                default {
                    Write-Warning -Message "Did not match column type to $_"
                    $Buffer = [System.String]::Empty
                    break
                }
            }

            $Row.Add($Column.Name, $Buffer)                               
        }

        Write-Output -InputObject ([PSCustomObject]$Row)
    }

    End {    
    }
}

Function Close-ESEDatabase {
    <#
        .SYNOPSIS
            Closes an open ESE database session.
 
        .DESCRIPTION
            The Close-ESEDatabase cmdlet closes and detaches the database. Then it closes the session and terminates the JET instance with the open session.
 
        .EXAMPLE
            $Session = Get-ESEDatabaseSession -Path C:\Users\Administrator\AppData\Local\Microsoft\Windows\WebCache\WebCacheV01.dat -LogPrefix "V01" -ProcessesToStop @("dllhost","taskhostw")
            Close-ESEDatabase -Instance $Session.Instance -Session $Session.Session -DatabaseId $Session.DatabaseId -Path $Session.Path
 
            Closes the open database.
 
        .PARAMETER Instance
            The Microsoft.Isam.Esent.Interop.JET_INSTANCE instance object.
 
        .PARAMETER Session
            The Microsoft.Isam.Esent.Interop.JET_SESID session object.
 
        .PARAMETER DatabaseId
            The Microsoft.Isam.Esent.Interop.JET_DBID database Id object.
 
        .PARAMETER Path
            The path to the database file.
 
        .PARAMETER EsentDllPath
            The path to the Esent library. This defaults to $env:SYSTEMDRIVE\Microsoft.NET\assembly\GAC_MSIL\microsoft.isam.esent.interop\v4.0_10.0.0.0__31bf3856ad364e35\Microsoft.Isam.Esent.Interop.dll.
 
        .INPUTS
            The Microsoft.Isam.Esent.Interop.JET_INSTANCE, Microsoft.Isam.Esent.Interop.JET_SESID, Microsoft.Isam.Esent.Interop.JET_DBID, System.String
 
        .OUTPUTS
            None
 
        .NOTES
            AUTHOR: Michael Haken
            LAST UPDATE: 4/25/2016
    #>

    [CmdletBinding()] 
    Param(
        [Parameter(Position=0,Mandatory=$true)]
        $Instance,
        [Parameter(Position=1,Mandatory=$true)]
        $Session,
        [Parameter(Position=2,Mandatory=$true)]
        $DatabaseId,
        [Parameter(Position=3,Mandatory=$true)]
        [ValidateScript({Test-Path -Path $_})]
        $Path,
        [Parameter()]
        [ValidateScript({Test-Path -Path $_})]
        [string]$EsentDllPath = $script:EsentDllPath
    )

    Begin {
        $Temp = Add-Type -Path $EsentDllPath -ErrorAction Stop
    }

    Process {
        Write-Verbose -Message "Shutting down database $Path due to normal close operation."
        [Microsoft.Isam.Esent.Interop.Api]::JetCloseDatabase($Session, $DatabaseId, [Microsoft.Isam.Esent.Interop.CloseDatabaseGrbit]::None)
        [Microsoft.Isam.Esent.Interop.Api]::JetDetachDatabase($Session, $Path)
        [Microsoft.Isam.Esent.Interop.Api]::JetEndSession($Session, [Microsoft.Isam.Esent.Interop.EndSessionGrbit]::None)
        [Microsoft.Isam.Esent.Interop.Api]::JetTerm($Instance)
        Write-Verbose -Message "Completed shut down successfully."
    }

    End {
    }
}

Function Get-WebHistory {
    <#
        .SYNOPSIS
            Reads the Internet Explorer web history of a user from the WebCacheV01.dat file.
 
        .DESCRIPTION
            The Get-WebHistory cmdlet is a forensic tools that reads the actual web history of a given user. It uses the ESE database functions to read the WebCacheV01.dat file. This works in IE10+.
 
            It is recommended that you use a copy of the database and logs so that the original database is not modified.
 
        .EXAMPLE
            Get-WebHistory
 
            Gets the web history of all users on the local computer.
 
        .PARAMETER UserName
            The user name to get the web history for. This defaults to all users.
 
        .INPUTS
            System.String
 
        .OUTPUTS
            System.Management.Automation.PSObject[]
 
            The array of objects contain Url, AccessedTime, and UserName information
 
        .NOTES
            AUTHOR: Michael Haken
            LAST UPDATE: 4/25/2016
    #>

    [CmdletBinding()]
    Param(
        [Parameter(Position=0,ValueFromPipeline=$true)]
        $UserName = [System.String]::Empty
    )

    Begin {
        $Verbose = $PSBoundParameters.ContainsKey("Verbose").IsPresent
    }

    Process {
        $Data = @()

        $Profiles = Get-UserProfiles
        if (![System.String]::IsNullOrEmpty($UserName)) {
            $Profiles = $Profiles | Where-Object {$_ -like "*$UserName*"}
        }

        foreach ($Profile in $Profiles) {            
            $Parts = $Profile.Split("\")
            $CurrentUser = $Parts[$Parts.Length - 1]
            $Path = Join-Path -Path $Profile -ChildPath "AppData\Local\Microsoft\Windows\WebCache"
            $Destination = "$env:USERPROFILE\AppData\Local\Temp"

            Write-Verbose -Message "Processing user $CurrentUser at path $Path"

            if ((Test-Path -Path $Path) -and (Test-Path -Path "$Path\WebCacheV01.dat")) {
                Stop-Process -Name dllhost -Force -ErrorAction SilentlyContinue
                Stop-Process -Name taskhostw -Force -ErrorAction SilentlyContinue
                Write-Verbose -Message "Copying WebCache folder."
                Copy-Item -Path $Path -Destination $Destination -Recurse -Force
                Write-Verbose -Message "Finished copy."
                $DB = $null
                $DB = Get-ESEDatabase -Path "$Destination\WebCache\WebCacheV01.dat" -LogPrefix "V01" -ProcessesToStop @("dllhost","taskhostw") -Recovery $false -CircularLogging $true -Force
                Remove-Item -Path "$Destination\WebCache" -Force -Recurse
                foreach ($Table in $DB) {
                    if ($Table.Rows.Count -gt 0 -and (Get-Member -InputObject $Table.Rows[0] -Name "Url" -MemberType Properties) -ne $null) {
                        $Data += ($Table.Rows | Select-Object -Property AccessedTime,Url,@{Name="UserName";Expression = {$CurrentUser}})
                    }
                }
            }
        }

        Write-Output -InputObject $Data
    }

    End {
    }
}

Function Get-UserProfiles {
    <#
        .SYNOPSIS
            Gets all of the user profiles on the system.
 
        .DESCRIPTION
            The Get-UserProfiles cmdlet uses the Win32_UserProfile WMI class to get user profile paths. It ignores special profiles like the local system.
 
        .EXAMPLE
            Get-UserProfiles
 
            Gets all of the user profiles on the system as an array of path strings.
 
        .INPUTS
            None
 
        .OUTPUTS
            System.String[]
 
        .NOTES
            AUTHOR: Michael Haken
            LAST UPDATE: 4/25/2016
    #>

    [CmdletBinding()]
    Param(
    )

    Begin {}

    Process {
        Write-Output -InputObject (Get-WmiObject -Class Win32_UserProfile | Where-Object {$_.Special -eq $false} | Select-Object -ExpandProperty LocalPath)
    }

    End {        
    }
}

Function ConvertTo-Html {
    <#
        .SYNOPSIS
            Converts an object to an HTML table.
 
        .DESCRIPTION
            The ConvertTo-Html cmdlet takes an input object and converts it to an HTML document containing a table.
 
        .EXAMPLE
            ConvertTo-Html -CsvPath c:\test.csv -Title "Test Import File" -Destination c:\test.html
 
            Converts the csv file to an html file and saves the html to the specified destination.
 
        .PARAMETER CsvPath
            The path to the CSV file that will be converted to HTML. Currently, this is the only supported input format.
 
        .PARAMETER Title
            An optional title to display on the HTML.
 
        .PARAMETER Destination
            An optional parameter to save the HTML content to a file. If this parameter is not specified or is Null or Empty, the HTML will be written to the pipeline.
 
        .PARAMETER IgnoreHeaders
            An array of any headers in the CSV file to ignore when creating the HTML table. Data in these columns will not be added to the table.
 
        .INPUTS
            None
 
        .OUTPUTS
            System.String
 
        .NOTES
            AUTHOR: Michael Haken
            LAST UPDATE: 11/14/2016
    #>

    [CmdletBinding()]
    Param(
        [Parameter(Mandatory=$true,ParameterSetName="Csv",Position=0)]
        [string]$CsvPath,
        [Parameter()]
        [string]$Title = [System.String]::Empty,
        [Parameter(Position=1)]
        [string]$Destination = [System.String]::Empty,
        [Parameter(ParameterSetName="Csv")]
        [string[]]$IgnoreHeaders = @()
    )

    Begin {
    }

    Process {
        switch ($PSCmdlet.ParameterSetName) {
            "Csv" {
                $Data = Import-Csv -Path $CsvPath
                if ($Data.Count -gt 0) {
                    $Headers = Get-Member -InputObject $Data[0] -MemberType NoteProperty | Select-Object -ExpandProperty Name | Where-Object {$_.ToString().ToLower() -notin $IgnoreHeaders.ToLower()}
                }
                else {
                    $Headers = @()
                    Write-Verbose -Message "No content in the CSV."
                }
            }
            default {
                throw "Could not determine parameter set."
            }
        }

        $Html = @"
<!DOCTYPE html>
<html>
    <head>
        <meta name="viewport" content="width=device-width" />
        <title>$Title</title>
    </head>
    <style>
        .logtable {
            width:100%;
            table-layout:fixed;
            border:1px solid black;
        }
         
        .logtable td {
            word-break:break-all;
            word-wrap:break-word;
            vertical-align:top;
            text-align:left;
        }
 
        .logtable th {
            text-align:center;
        }
    </style>
    <body style=`"width:1200px;margin-left:auto;margin-right:auto;`">
        <H1 style=`"text-align:center;`">$Title</H1>
        <div>
             <table class=`"logtable`">
                <thead>
 
"@


        foreach ($Header in $Headers) {
            $Html += "<th>$Header</th>"
        }

        $Html += "</thead><tbody>"

        foreach ($Obj in $Data) {
            $Html += "<tr>"

            $Props = Get-Member -InputObject $Obj -MemberType NoteProperty | Select-Object -ExpandProperty Name | Where-Object {$_.ToString().ToLower() -notin $IgnoreHeaders.ToLower()}

            foreach ($Prop in $Props) {
                $Html += "<td>" + $Obj.$Prop + "</td>"
            }

            $Html += "</tr>"
        }

        $Html += "</tbody></table></div></body></html>"
    }

    End {
        if (![System.String]::IsNullOrEmpty($Destination)) {
            Set-Content -Path $Destination -Value $Html -Force
        }
        else {
            Write-Output -InputObject $Html
        }
    }
}

Function Test-Port {
    <#
        .SYNOPSIS
            Tests if a TCP or UDP is listening on a computer.
 
        .DESCRIPTION
            The Test-Port cmdlet tests for the availability of a TCP or UDP port on a local or remote server.
 
        .EXAMPLE
            Test-Port -Port 443 -ComputerName RemoteServer.test.local -TCP
 
            Tests for the availability of port 443 via TCP on RemoteServer.test.local
 
        .PARAMETER Port
            The port number to test. This must be between 1 and 65535.
 
        .PARAMETER ComputerName
            The IP or DNS name of the computer to test. This defaults to "localhost".
 
        .PARAMETER ReceiveTimeout
            The timeout in milliseconds to wait for a response. This defaults to 1000.
 
        .PARAMETER Tcp
            Indicates that TCP should be used. This is the default
 
        .PARAMETER Udp
            Indicates that UDP should be used.
 
        .INPUTS
            None
 
        .OUTPUTS
            System.Boolean
 
        .NOTES
            AUTHOR: Michael Haken
            LAST UPDATE: 11/14/2016
    #>

    [CmdletBinding(DefaultParameterSetName = "tcp")]
    Param(
        [Parameter(Position=0,Mandatory=$true)]
        [ValidateRange(1,65535)]
        [int]$Port,
        [Parameter(Position=1)]
        [string]$ComputerName = "localhost",
        [Parameter(Position=2)]
        [int]$ReceiveTimeout = 1000,
        [Parameter(ParameterSetName="tcp")]
        [switch]$Tcp,
        [Parameter(ParameterSetName="udp")]
        [switch]$Udp
    )

    Begin {     
    }

    Process {
        $Success = $false

        if ($Tcp) {
            [System.Net.Sockets.TcpClient]$TcpObj = New-Object -TypeName System.Net.Sockets.TcpClient
            Write-Verbose -Message "Beginning tcp connection to $ComputerName."
            $Connection = $TcpObj.BeginConnect($ComputerName, $Port, $null, $null)
            $Wait = $Connection.AsyncWaitHandle.WaitOne($ReceiveTimeout, $false)

            if ($Wait) {
                $Error.Clear()
                Write-Verbose -Message "Ending connection."
                $TcpObj.EndConnect($Connection) | Out-Null

                if ($Error[0]) {
                    Write-Verbose -Message ($Error[0].Exception.Message)
                }
                else {
                    Write-Verbose -Message "Connection successful."
                    $Success = $true
                }
            }
            else {
                Write-Verbose -Message "Connection timeout."
            }

            $TcpObj.Close()
        }

        if ($Udp) {
            [System.Net.Sockets.UdpClient]$UdpObj = New-Object -TypeName System.Net.Sockets.UdpClient
            $UdpObj.Client.ReceiveTimeout = $ReceiveTimeout
            Write-Verbose -Message "Connected to $ComputerName."
            $UdpObj.Connect($ComputerName, $Port)
            $TestData = New-Object System.Text.ASCIIEncoding
            $Bytes = $TestData.GetBytes("$(Get-Date)")
            Write-Verbose -Message "Sending data."
            [void]$UdpObj.Send($Bytes, $Bytes.Length)
            Write-Verbose -Message "Creating remote endpoint."
            $RemoteEndpoint = New-Object System.Net.IPEndPoint([System.Net.IPAddress]::Any, 0)

            try {
                Write-Verbose -Message "Waiting for message return"
                $ReceivedBytes = $UdpObj.Receive([ref]$RemoteEndpoint)
                [string]$ReturnData = $TestData.GetString($ReceivedBytes)

                if (![System.String]::IsNullOrEmpty($ReturnData)) {
                    Write-Verbose -Message "Connection successful"
                    $Success = $true
                }
            }
            catch [Exception] {
                if ($_.Exception.Message -match "\brespond after a period of time\b") {
                    Write-Verbose -Message "Testing ICMP connection for false positive."
                    if (Test-Connection -ComputerName $ComputerName -Count 1 -Quiet) {
                        Write-Verbose -Message "Connection successful."
                        $Success = $true
                    }        
                }
                else {
                    Write-Verbose -Message $_.Exception.Message
                }
            }
            finally {
                $UdpObj.Close()
            } 
        }
        
        Write-Output -InputObject $Success                
    }

    End {
    }
}

Function Add-DomainUserToLocalGroup {
    <#
        .SYNOPSIS
            Adds a domain user to a local group.
 
        .DESCRIPTION
            The cmdlet adds a domain user to an existing local group on the local machine.
 
        .EXAMPLE
            Add-DomainUserToLocalGroup -Username "contoso\john.smith" -Group Administrators
 
            Adds john.smith to the local Administrators group.
 
        .PARAMETER Username
            The name of the domain user, this must be in the domain\username format.
 
        .PARAMETER Group
            The name of the local group to add the user to.
 
        .PARAMETER PassThru
            Indicates that details of the localgroup are passed back as output.
 
        .INPUTS
            None
 
        .OUTPUTS
            None or System.DirectoryServices.DirectoryEntry
 
        .NOTES
            AUTHOR: Michael Haken
            LAST UPDATE: 11/14/2016
    #>

    [CmdletBinding()]
    Param(
        [Parameter(Mandatory=$true,Position=0,ValueFromPipeline=$true)]
        [ValidateScript({
            $_.Split("\").Length -eq 2
        })]
        [System.String]$Username,
        [Parameter(Mandatory=$true,Position=1)]
        [System.String]$Group,
        [Parameter()]
        [switch]$PassThru
    )

    Begin {}

    Process {
        $Parts = $Username.Split("\")
        $Domain = $Parts[0]
        $Name = $Parts[1]
        $LocalGroup = ([ADSI]("WinNT://$env:COMPUTERNAME/$Group,group"))
        if ($LocalGroup -ne $null) {
            $LocalGroup.Add("WinNT://$Domain/$Name")
        }
        else {
            throw "$Group could not be found locally."
        }

        if ($PassThru) {
            Write-Output -InputObject ($LocalGroup | Select-Object -Property *)
        }
    }

    End {
    }
}

Function Set-AutoLogon {
    <#
        .SYNOPSIS
            Enables or disables automatic logon for a user.
 
        .DESCRIPTION
            The cmdlet enables automatic logon for a specified user.
 
        .EXAMPLE
            Set-AutoLogon -Enable -Username "contoso\john.smith" -Password "MySecureP@$$W0rd"
 
            Creates an automatic logon for john.smith. The next time the server boots, this user will be automatically logged on.
 
        .EXAMPLE
            Set-AutoLogon -Disable
 
        .PARAMETER Enable
            Specifies that auto logon should be enabled. This is the default.
 
        .PARAMETER Username
            The user that should be automatically logged in.
 
        .PARAMETER Password
            The password for the user. The password is stored in plain text in the registry.
 
        .PARAMETER Disable
            Disables auto logon and clears any stored password.
 
        .INPUTS
            None
 
        .OUTPUTS
            None
 
        .NOTES
            AUTHOR: Michael Haken
            LAST UPDATE: 11/14/2016
    #>

    [CmdletBinding()]
    Param(
        [Parameter(Mandatory=$true, ParameterSetName="Enable")]
        [switch]$Enable,
        [Parameter(Mandatory=$true, ParameterSetName="Enable")]
        [System.String]$UserName,
        [Parameter(Mandatory=$true, ParameterSetName="Enable")]
        [System.String]$Password,
        [Parameter(Mandatory=$true, ParameterSetName="Disable")]
        [switch]$Disable    
    )

    Begin {}

    Process {
        if ($Enable) {
            Write-Log "Enabling automatic logon." -Level VERBOSE
            New-ItemProperty -Path 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon' -Name AutoAdminLogon -Value 1 -ErrorAction SilentlyContinue | Out-Null
            New-ItemProperty -Path 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon' -Name DefaultUserName -Value $UserName -ErrorAction SilentlyContinue | Out-Null
            New-ItemProperty -Path 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon' -Name DefaultPassword -Value $Password -ErrorAction SilentlyContinue | Out-Null
        }
        elseif ($Disable) {
            Write-Log "Disabling automatic logon." -Level VERBOSE
            Set-ItemProperty -Path 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon' -Name AutoAdminLogon -Value 0 -ErrorAction SilentlyContinue | Out-Null
            Set-ItemProperty -Path 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon' -Name DefaultUserName -Value "" -ErrorAction SilentlyContinue | Out-Null
            Remove-ItemProperty -Path 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon' -Name DefaultPassword -ErrorAction SilentlyContinue | Out-Null
        }
    }

    End {}
}

Function Set-FilePermissions {
    <#
        .SYNOPSIS
            Sets permissions on a file or directory.
 
        .DESCRIPTION
            Will add or replace the supplied rules to the specified file or directory. The default behavior is that the rules are just added to the current ACL of the object.
 
        .PARAMETER Path
            The path to the file to set permissions on.
 
        .PARAMETER Rules
            An array of File Access Rules to apply to the path.
 
        .PARAMETER ReplaceAllRules
            Indictates if all permissions on the path should be replaced with these.
 
        .PARAMETER ReplaceRulesForUser
            Indicates if the supplied rules should replace existing rules for matching users. For example, if the Rules parameter has a Full Control rule for System and a Read rules for
            Administrators, existing rules for System and Administrators would be removed and replaced with the new rules.
 
        .PARAMETER ForceChildInheritance
            Indicates if all permissions of child items should have their permissions replaced with the parent if the target is a directory.
 
        .EXAMPLE
            PS C:\>Set-Permissions -Path "c:\test.txt" -ComputerName -Rules $Rules
 
            Creates the rule set on the test.txt file.
 
        .INPUTS
            None
 
        .OUTPUTS
            None
 
        .NOTES
            AUTHOR: Michael Haken
            LAST UPDATE: 11/13/2016
    #>


    [CmdletBinding(DefaultParameterSetName = "Add")]
    Param 
    (
        [Parameter(Position=0,Mandatory=$true)]
        [string]$Path,
        [Parameter(Position=1,Mandatory=$true)]
        [System.Security.AccessControl.FileSystemAccessRule[]]$Rules,
        [Parameter(ParameterSetName="ReplaceAll")]
        [switch]$ReplaceAllRules,
        [Parameter(ParameterSetName="Replace")]
        [switch]$ReplaceRulesForUser,
        [Parameter()]
        [switch]$ForceChildInheritance = $false
    )

    Begin 
    {               
    }

    Process
    {
        Write-Verbose -Message "Setting permissions on $Path"

        try
        {
            $Acl = Get-Acl -Path $Path

            if ($Acl -ne $null)
            {

                switch ($PSCmdlet.ParameterSetName) {
                    "ReplaceAll" {
                        $OldAcls = $Acl.Access

                        foreach ($Rule in $OldAcls)
                        {
                            $Acl.RemoveAccessRule($Rule) | Out-Null
                        }

                        break
                    }
                    "Replace" {
                        
                        [System.Security.Principal.SecurityIdentifier[]]$Identities = $Rules | Select-Object -Property @{Name = "ID"; Expression = { $_.IdentityReference.Translate([System.Security.Principal.SecurityIdentifier]) } } | Select-Object -ExpandProperty ID
                        $OldAcls = $Acl.Access | Where-Object {$_.IdentityReference.Translate([System.Security.Principal.SecurityIdentifier]) -in $Identities}

                        foreach ($Rule in $OldAcls)
                        {
                            $Acl.RemoveAccessRule($Rule) | Out-Null
                        }

                        break
                    }
                    "Add" {
                        #Do Nothing
                        break
                    }
                    default {
                        throw "Could not determine parameter set name"
                    }
                }
                
                #Add the new rules
                foreach ($Rule in $Rules) 
                {
                    $Acl.AddAccessRule($Rule)
                }

                Set-Acl -Path $Path -AclObject $Acl

                #If child permissions should be forced to inherit
                if ($ForceChildInheritance -and [System.IO.Directory]::Exists($Path))
                {
                    Get-ChildItem -Path $Path -Recurse -Force | ForEach-Object {

                        $ChildAcl = Get-Acl -Path $_.FullName 
                        $ChildPath = $_.FullName

                        Write-Verbose -Message "Forcing inheritance on $ChildPath"

                        foreach ($ChildRule in $ChildAcl.Access)
                        {
                            try
                            {
                                $ChildAcl.RemoveAccessRule($ChildRule) | Out-Null
                            }
                            catch [Exception]
                            {
                                Write-Warning -Message "Error removing ACL from $ChildPath`: $($_.ToString())"
                            }
                        }

                        $ChildAcl.SetAccessRuleProtection($false,$false)

                        Set-Acl -Path $_.FullName -AclObject $ChildAcl | Out-Null
                    }
                }                   
            }
            else
            {
                Write-Warning -Message "Could not retrieve the ACL for $Path"
            }
        }
        catch [System.Exception]
        {
            Write-Warning -Message $_.Exception.Message
        }
    }
    
    End {}
}

Function Set-Owner {
    <#
        .SYNOPSIS
            Changes owner of a file or folder to another user or group.
 
        .DESCRIPTION
            Changes owner of a file or folder to another user or group.
 
        .PARAMETER Path
            The folder or file that will have the owner changed.
 
        .PARAMETER Account
            Optional parameter to change owner of a file or folder to specified account.
 
            Default value is 'Builtin\Administrators'
 
        .PARAMETER Recurse
            Recursively set ownership on subfolders and files beneath given folder.
 
        .EXAMPLE
            PS C:\>Set-Owner -Path C:\temp\test.txt
 
            Changes the owner of test.txt to Builtin\Administrators
 
        .EXAMPLE
            PS C:\>Set-Owner -Path C:\temp\test.txt -Account 'Domain\bprox
 
            Changes the owner of test.txt to Domain\bprox
 
        .EXAMPLE
            PS C:\>Set-Owner -Path C:\temp -Recurse
 
            Changes the owner of all files and folders under C:\Temp to Builtin\Administrators
 
        .EXAMPLE
            PS C:\>Get-ChildItem C:\Temp | Set-Owner -Recurse -Account 'Domain\Administrator'
 
            Changes the owner of all files and folders under C:\Temp to Domain\Administrator
 
        .NOTES
            AUTHOR: Michael Haken
            LAST UPDATE: 2/28/2016
    #>

    [CmdletBinding(SupportsShouldProcess = $true)]
    Param (
        [parameter(Position=0,ValueFromPipeline=$true)]
        [Alias("FullName")]
        [string[]]$Path,
        [parameter(Position=1)]
        [string]$Account = 'BUILTIN\Administrators',
        [parameter()]
        [switch]$Recurse
    )
    Begin {
        #Prevent Confirmation on each Write-Debug command when using -Debug
        If ($PSBoundParameters['Debug']) {
            $DebugPreference = 'Continue'
        }

        try {
            [void][TokenAdjuster]
        } catch {
            $AdjustTokenPrivileges = @"
            using System;
            using System.Runtime.InteropServices;
 
             public class TokenAdjuster
             {
              [DllImport("advapi32.dll", ExactSpelling = true, SetLastError = true)]
              internal static extern bool AdjustTokenPrivileges(IntPtr htok, bool disall,
              ref TokPriv1Luid newst, int len, IntPtr prev, IntPtr relen);
              [DllImport("kernel32.dll", ExactSpelling = true)]
              internal static extern IntPtr GetCurrentProcess();
              [DllImport("advapi32.dll", ExactSpelling = true, SetLastError = true)]
              internal static extern bool OpenProcessToken(IntPtr h, int acc, ref IntPtr
              phtok);
              [DllImport("advapi32.dll", SetLastError = true)]
              internal static extern bool LookupPrivilegeValue(string host, string name,
              ref long pluid);
              [StructLayout(LayoutKind.Sequential, Pack = 1)]
              internal struct TokPriv1Luid
              {
               public int Count;
               public long Luid;
               public int Attr;
              }
              internal const int SE_PRIVILEGE_DISABLED = 0x00000000;
              internal const int SE_PRIVILEGE_ENABLED = 0x00000002;
              internal const int TOKEN_QUERY = 0x00000008;
              internal const int TOKEN_ADJUST_PRIVILEGES = 0x00000020;
              public static bool AddPrivilege(string privilege)
              {
               try
               {
                bool retVal;
                TokPriv1Luid tp;
                IntPtr hproc = GetCurrentProcess();
                IntPtr htok = IntPtr.Zero;
                retVal = OpenProcessToken(hproc, TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, ref htok);
                tp.Count = 1;
                tp.Luid = 0;
                tp.Attr = SE_PRIVILEGE_ENABLED;
                retVal = LookupPrivilegeValue(null, privilege, ref tp.Luid);
                retVal = AdjustTokenPrivileges(htok, false, ref tp, 0, IntPtr.Zero, IntPtr.Zero);
                return retVal;
               }
               catch (Exception ex)
               {
                throw ex;
               }
              }
              public static bool RemovePrivilege(string privilege)
              {
               try
               {
                bool retVal;
                TokPriv1Luid tp;
                IntPtr hproc = GetCurrentProcess();
                IntPtr htok = IntPtr.Zero;
                retVal = OpenProcessToken(hproc, TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, ref htok);
                tp.Count = 1;
                tp.Luid = 0;
                tp.Attr = SE_PRIVILEGE_DISABLED;
                retVal = LookupPrivilegeValue(null, privilege, ref tp.Luid);
                retVal = AdjustTokenPrivileges(htok, false, ref tp, 0, IntPtr.Zero, IntPtr.Zero);
                return retVal;
               }
               catch (Exception ex)
               {
                throw ex;
               }
              }
             }
"@

            Add-Type $AdjustTokenPrivileges
        }

        #Activate necessary admin privileges to make changes without NTFS perms
        [void][TokenAdjuster]::AddPrivilege("SeRestorePrivilege") #Necessary to set Owner Permissions
        [void][TokenAdjuster]::AddPrivilege("SeBackupPrivilege") #Necessary to bypass Traverse Checking
        [void][TokenAdjuster]::AddPrivilege("SeTakeOwnershipPrivilege") #Necessary to override FilePermissions
    }
    Process {
        ForEach ($Item in $Path) {
            Write-Verbose -Message "FullName: $Item"
            #The ACL objects do not like being used more than once, so re-create them on the Process block
            $DirOwner = New-Object System.Security.AccessControl.DirectorySecurity
            $DirOwner.SetOwner([System.Security.Principal.NTAccount]$Account)
            $FileOwner = New-Object System.Security.AccessControl.FileSecurity
            $FileOwner.SetOwner([System.Security.Principal.NTAccount]$Account)
            $DirAdminAcl = New-Object System.Security.AccessControl.DirectorySecurity
            $FileAdminAcl = New-Object System.Security.AccessControl.DirectorySecurity
            $AdminACL = New-Object System.Security.AccessControl.FileSystemAccessRule('Builtin\Administrators','FullControl','ContainerInherit,ObjectInherit','InheritOnly','Allow')
            $FileAdminAcl.AddAccessRule($AdminACL)
            $DirAdminAcl.AddAccessRule($AdminACL)
            Try {
                $Item = Get-Item -LiteralPath $Item -Force -ErrorAction Stop
                If (-NOT $Item.PSIsContainer) {
                    If ($PSCmdlet.ShouldProcess($Item, 'Set File Owner')) {
                        Try {
                            $Item.SetAccessControl($FileOwner)
                        } Catch {
                            Write-Warning -Message "Couldn't take ownership of $($Item.FullName)! Taking FullControl of $($Item.Directory.FullName)"
                            $Item.Directory.SetAccessControl($FileAdminAcl)
                            $Item.SetAccessControl($FileOwner)
                        }
                    }
                } Else {
                    If ($PSCmdlet.ShouldProcess($Item, 'Set Directory Owner')) {                        
                        Try {
                            $Item.SetAccessControl($DirOwner)
                        } Catch {
                            Write-Warning -Message "Couldn't take ownership of $($Item.FullName)! Taking FullControl of $($Item.Parent.FullName)"
                            $Item.Parent.SetAccessControl($DirAdminAcl) 
                            $Item.SetAccessControl($DirOwner)
                        }
                    }
                    If ($Recurse) {
                        [void]$PSBoundParameters.Remove('Path')
                        Get-ChildItem $Item -Force | Set-Owner @PSBoundParameters
                    }
                }
            } Catch {
                Write-Warning -Message "$($Item): $($_.Exception.Message)"
            }
        }
    }
    End {  
        #Remove priviledges that had been granted
        [void][TokenAdjuster]::RemovePrivilege("SeRestorePrivilege") 
        [void][TokenAdjuster]::RemovePrivilege("SeBackupPrivilege") 
        [void][TokenAdjuster]::RemovePrivilege("SeTakeOwnershipPrivilege")     
    }
}

Function Test-RegistryKeyProperty {
    <#
        .SYNOPSIS
            Tests the existence of a registry value
 
        .DESCRIPTION
            The Test-RegistryKeyProperty cmdlet test the extistence of a registry value (property of a key).
 
        .PARAMETER Key
            The registry key to test for containing the property.
 
        .PARAMETER PropertyName
            The property name to test for.
 
        .EXAMPLE
            Test-RegistryKeyProperty -Key "HKLM:\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Component Based Servicing" -PropertyName PendingFileRenameOperations
             
            Returns true or false depending on the existence of the property
 
        .INPUTS
            None
 
        .OUTPUTS
            System.Boolean
 
        .NOTES
            AUTHOR: Michael Haken
            LAST UPDATE: 2/28/2016
    #>


    Param (
        [Parameter(Position=0, Mandatory=$true)]
        [string]$Key,
        [Parameter(Position=1, Mandatory=$true)]
        [string]$PropertyName
    )

    Begin {
    }

    Process {
        Get-ItemProperty -Path $Key -Name $PropertyName -ErrorAction SilentlyContinue | Out-Null
        Write-Output -InputObject $?
    }

    End {
    }
}

Function ForEach-ObjectParallel {
    <#
        .SYNOPSIS
            Runs a script in a multi-threaded foreach.
 
        .DESCRIPTION
            The ForEach-ObjectParallel cmdlet runs through each input value and executes the script in a new thread.
 
        .PARAMETER ScriptBlock
            The script to execute on each input object.
 
        .PARAMETER InputObject
            The array of items to provide as input to the foreach.
 
        .PARAMETER Parameters
            A hashtable of additional parameters to provide to the script. For example @{Name = "MyService", Priority = 1} could be used by a scriptblock that looked like
 
            {
                Param(
                    $Name,
                    $Priority
                )
 
                Write-Host $Name
                Write-Host $Priority
            }
 
        .PARAMETER InputParamName
            If the input object needs to be associated with a parameter in the script, define its parameter name with this parameter. For example, consider the following Windows services:
 
            @("Winmgmt", "WinRM") | ForEach-ObjectParallel {
                Param(
                    $Type
                    $Name
                )
                Get-Service $Name
            } -InputParamName "Name"
 
            This will ensure that Winmgmt and WinRM are provided to the $Name parameter and not $Type
 
        .PARAMETER MinimumThreads
            The minimum number of threads to use, this defaults to 1.
 
        .PARAMETER MaximumThreads
            The maximum number of threads to use, this defaults to 4. This must be greater than or equal to the minimum threads.
 
        .PARAMETER WaitTime
            The amount of time, in milliseconds, the function waits in between checking the status of each task. For long running tasks
            you can increase this time to utilize less resources during execution.
 
        .EXAMPLE
            @("Winmgmt", "WinRM") | ForEach-ObjectParallel {
                Param(
                    $Name
                )
                Get-Service $Name
            }
 
            This will return the service objects for the Winmgmt and WinRM services.
 
        .EXAMPLE
            $Results = ForEach-ObjectParallel -InputObject ("Hello", "Goodbye") -ScriptBlock {
                Param(
                    $Greeting,
                    $FirstName,
                    $LastName
                )
 
                Write-Output -InputObject ($Greeting $FirstName $LastName)
 
            } -Parameters @{FirstName = "John", LastName = "Smith"}
 
            The example would execute two tasks, one outputing "Hello John Smith" and the other outputing "Goodbye John Smith", but not
            necessarily in that order. The InputObject items are mapped against the parameter in the first position of the script, $Greeting,
            while the additional parameters are mapped by matching their name.
 
        .INPUTS
            System.Object[]
 
        .OUTPUTS
            System.Object[]
 
        .NOTES
            AUTHOR: Michael Haken
            LAST UPDATE: 10/26/2016
    #>

    [CmdletBinding()]
    Param(           
        [Parameter(Mandatory = $true, Position = 0, ParameterSetName = "ScriptBlock")]
        [System.Management.Automation.ScriptBlock]$ScriptBlock,

        [Parameter(Mandatory = $true, Position = 0, ParameterSetName = "Cmdlet")]
        [ValidateScript({
            Get-Command -Name $_
        })]
        [System.String]$Cmdlet,

        [Parameter(Mandatory = $true, ValueFromPipeline = $true, Position = 1)]
        [System.Object[]]$InputObject,

        [Parameter()]
        [System.Collections.Hashtable]$Parameters,

        [Parameter()]
        $InputParamName = [System.String]::Empty,

        [Parameter()]
        [System.UInt32]$MinimumThreads = 1,

        [Parameter()]
        [ValidateScript({
            $_ -ge $MinimumThreads
        })]
        [System.UInt32]$MaximumThreads = 4,

        [Parameter()]
        [System.UInt32]$WaitTime = 100
    )

    Begin {
    }

    Process {
        $Jobs = New-Object -TypeName System.Collections.ArrayList
        $SessionState = [System.Management.Automation.Runspaces.InitialSessionState]::CreateDefault2()
        $RunspacePool = [System.Management.Automation.Runspaces.RunspaceFactory]::CreateRunspacePool($MinimumThreads, $MaximumThreads, $SessionState, $Host)
        $RunspacePool.Open()

        foreach ($Item in $InputObject) {
            $Pipeline = [System.Management.Automation.PowerShell]::Create()


            if ($PSCmdlet.ParameterSetName -eq "ScriptBlock")
            {
                $Pipeline.AddScript($ScriptBlock) | Out-Null
            }
            elseif ($PSCmdlet.ParameterSetName -eq "Cmdlet")
            {
                $Pipeline.AddCommand($Cmdlet) | Out-Null
            }
            else
            {
                throw New-Object -TypeName System.ArgumentException("The parameter set name could not be determined from the given parameters.")
            }

            if ($Parameters.Length -gt 0)
            {
                $Pipeline.AddParameters($Parameters) | Out-Null
            }

            if (![System.String]::IsNullOrEmpty($InputParamName))
            {
                $Pipeline.AddParameter($InputParamName, $Item) | Out-Null
            }
            else
            {
                $Pipeline.AddArgument($Item) | Out-Null
            }

            $Pipeline.RunspacePool = $RunspacePool
            $AsyncHandle = $Pipeline.BeginInvoke()

            $Jobs.Add(@{Handle = $AsyncHandle; Pipeline = $Pipeline}) | Out-Null
        }

        $Results = @()
        $TotalJobs = $Jobs.Count

        while ($Jobs.Count -gt 0)
        {
            Write-Progress -Activity "Waiting for async tasks" `
                        -PercentComplete ((($TotalJobs - $Jobs.Count) / $TotalJobs) * 100) `
                        -Status ( ($TotalJobs - $Jobs.Count).ToString()  + " of " + $Jobs.Count.ToString() + " completed")

            foreach($Job in ($Jobs | Where-Object {$_.Handle.IsCompleted -eq $true}))
            {
                $Results += $Job.Pipeline.EndInvoke($Job.Handle)
                $Job.Pipeline.Dispose() | Out-Null
                $Jobs.Remove($Job)
            }

            Start-Sleep -Milliseconds $WaitTime
        }

        $RunspacePool.Close() | Out-Null
        $RunspacePool.Dispose() | Out-Null

        Write-Output -InputObject $Results
    }

    End {
    }
}

Function Invoke-CommandInNewRunspace {
    <#
        .SYNOPSIS
            Runs a scriptblock in a new powershell runspace.
 
        .DESCRIPTION
            The Invoke-CommandInNewRunspace cmdlet uses a clean PowerShell runspace to execute the provided script block.
 
        .PARAMETER ScriptBlock
            The script to execute on each input object.
 
        .PARAMETER Parameters
            A hashtable of additional parameters to provide to the script. For example @{Name = "MyService", Priority = 1} could be used by a scriptblock that looked like
 
            {
                Param(
                    $Name,
                    $Priority
                )
 
                Write-Host $Name
                Write-Host $Priority
            }
 
        .EXAMPLE
            Invoke-CommandInNewRunspace -ScriptBlock {Get-Service}
             
            Invokes the Get-Service cmdlet in a new runspace.
 
        .EXAMPLE
            Invoke-CommandInNewRunspace -ScriptBlock {
                Param(
                    $Name
                )
             
                Get-Process $Name
            } -Parameters @{Name = "winlogon"}
 
            Performs a Get-Process for the winlogon process in a new runspace
 
        .INPUTS
            None
 
        .OUTPUTS
            System.Object
 
            This depends on what is returned from the ScriptBlock
 
        .NOTES
            AUTHOR: Michael Haken
            LAST UPDATE: 10/26/2016
    #>

    [CmdletBinding()]
    Param(
        
        [Parameter(Mandatory = $true, Position = 0, ParameterSetName = "ScriptBlock")]
        [System.Management.Automation.ScriptBlock]$ScriptBlock,

        [Parameter(Mandatory = $true, Position = 0, ParameterSetName = "Cmdlet")]
        [ValidateScript({
            Get-Command -Name $_
        })]
        [System.String]$Cmdlet,

        [Parameter(Position = 1)]
        [System.Collections.Hashtable]$Parameters
    )

    Begin {
    }

    Process {
        $Results = $null
        $Runspace = [System.Management.Automation.Runspaces.RunspaceFactory]::CreateRunspace()

        #Create a new PowerShell instance
        $Pipeline = [System.Management.Automation.PowerShell]::Create()

        try {
            #Assign the PowerShell instance to the new RunspacePool we created
            $Pipeline.Runspace = $Runspace

            #Open the runspace
            $Runspace.Open()

            #If the cmdlet was run using a script block, add it
            if ($PSCmdlet.ParameterSetName -eq "ScriptBlock")
            {
                $Pipeline.AddScript($ScriptBlock) | Out-Null
            }
            elseif ($PSCmdlet.ParameterSetName -eq "Cmdlet")
            {
                $Pipeline.AddCommand($Cmdlet) | Out-Null
            }
            else
            {
                throw New-Object -TypeName System.ArgumentException("The parameter set name could not be determined from the given parameters.")
            }

            #Add parameters if they are defined
            if ($Parameters.Length -gt 0)
            {
                $Pipeline.AddParameters($Parameters) | Out-Null
            }

            #Invoke the command synchronously
            $Results = $Pipeline.Invoke()
        }
        finally {
            #Dispose the powershell instance
            $Pipeline.Dispose() | Out-Null
        
            #Terminate the runspace
            $Runspace.Close() | Out-Null
            $Runspace.Dispose() | Out-Null
        }

        Write-Output -InputObject $Results
    }

    End {
        
    }
}

Function Get-WindowsActivationInformation {
    <#
        .SYNOPSIS
            Gets information about the Windows Activation.
 
        .DESCRIPTION
            The cmdlet gets the Product Key, Product Id, the OEM Product Key stored in the BIOS, and OS version.
 
        .EXAMPLE
            Get-WindowsActivationInformation
 
            Gets the activation information from the local computer.
 
        .INPUTS
            None
 
        .OUTPUTS
            System.Management.Automation.PSObject
 
        .NOTES
            AUTHOR: Michael Haken
            LAST UPDATE: 11/14/2016
    #>

    [CmdletBinding()]
    Param(
    )

    Begin {
        $RegPath = "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion"
        $Namespace = "root\cimv2"
        $SWService = "SoftwareLicensingService"
        $OEMProdKey = "OA3xOriginalProductKey"
        $RegKey = "DigitalProductId"
        $ByteArrayStart = 52
        $ArrayLength = 15
        $ProductKey = ""

        #These are the valid chars for a product key
        $CharArray = @("B", "C", "D", "F", "G", "H", "J", "K", "M", 
            "P", "Q", "R", "T", "V", "W", "X", "Y", "2", "3", "4", "6", "7", "8", "9")
    }

    Process {
        $OS = Get-CimInstance -ClassName Win32_OperatingSystem | Select-Object -ExpandProperty Version
        $Major = [System.Int32]::Parse($OS.Split(".")[0])

        #Product Id is also part of they DigitalProductId byte array from position 8 to 30, it can be converted back using
        #[System.Text.Encoding]::ASCII.GetString($Bytes) by converting the bytes to ASCII or Unicode
        $ProductId = Get-ItemProperty -Path $RegPath -Name "ProductId" | Select-Object -ExpandProperty "ProductId"

        #If the OS is Server 2008 or later, the registry key is DigitalProductId4
        if ($Major -gt 5) {
            $RegKey += "4"
        }
    
        $Bytes = Get-ItemProperty -Path $RegPath -Name $RegKey | Select-Object -ExpandProperty $RegKey

        for ($i = 24; $i -ge 0; $i--) 
        {
            $k = 0
        
            for ($j = $ByteArrayStart + $ArrayLength - 1; $j -ge $ByteArrayStart; $j--) 
            {
                $k = $k * 256 -bxor $Bytes[$j]
                $Bytes[$j] = [math]::truncate($k / 24)
                $k = $k % 24
            }
    
            $ProductKey = $CharArray[$k] + $ProductKey

            if (($i % 5 -eq 0) -and ($i -ne 0)) 
            {
                $ProductKey = "-" + $ProductKey
            }
        }

        $BiosOEMKey = Get-CimInstance -Namespace $Namespace -ClassName $SWService | Select-Object -ExpandProperty $OEMProdKey

        Write-Output -InputObject (New-Object -TypeName System.Management.Automation.PSObject -Property @{
            "BIOSOEMKey" = $BiosOEMKey
            "ProductKey" = $ProductKey
            "ProductId" = $ProductId
            "OSVersion" = $OS
            "ComputerName" = $env:COMPUTERNAME
        })
    }

    End {        
    }
}

Function Set-CertificatePrivateKeyAccess {
    <#
        .SYNOPSIS
            Provides access to certificates for a specific user.
 
        .DESCRIPTION
            The cmdlet grants access to certificates stored in $env:ProgramData\Microsoft\Crypto\RSA\MachineKeys. The cmdlet can grant
            Read, Read/Write, and Full control to either a specific certificate or the entire directory. The credentials used to run the cmdlet
            must have the ability to set permissions on the files or directory.
 
        .EXAMPLE
            Set-CertificatePrivateKeyAccess -User "contoso\john.smith" -All
 
            Grants john.smith full control access to all certificates.
 
        .EXAMPLE
            Set-CertificatePrivateKeyAccess -User "contoso\john.smith" -Thumbprint 00E811CCE0444D23A9A055F0FB6CEA576F880B89 -AccessLevel READ_WRITE
 
            Grants john.smith read/write access to the certificate specified by the thumbprint.
 
        .EXAMPLE
            Set-CertificatePrivateKeyAccess -User "contoso\john.smith" -Subject CN=f366ac78-22c8-427e-9a4e-f5ffab31725e -AccessLevel READ
 
            Grants john.smith read access to the certificate specified by the subject.
 
        .PARAMETER User
            The username that should have access.
 
        .PARAMETER All
            Specifies that the user should be granted access to all of the machine keys stored on the computer.
 
        .PARAMETER Replace
            Specifies that existing permissions for the user on the machine keys should be replaced with only the specified permissions.
 
        .PARAMETER AccessLevel
            The level of access the user should receive. This is either FULL_CONTROL, READ_WRITE, or READ.
 
        .INPUTS
            None
 
        .OUTPUTS
            None
 
        .NOTES
            AUTHOR: Michael Haken
            LAST UPDATE: 11/14/2016
    #>

    [CmdletBinding(DefaultParameterSetName="Thumbprint")]
    Param(
        [Parameter(Mandatory=$true)]
        [System.String]$User,
        [Parameter(ParameterSetName="All",Mandatory=$true)]
        [switch]$All,
        [Parameter(ParameterSetName="All")]
        [switch]$Replace,
        [Parameter()]
        [ValidateSet("FULL_CONTROL", "READ", "READ_WRITE")]
        [System.String]$AccessLevel = "FULL_CONTROL"
    )

    DynamicParam {
        [System.Management.Automation.RuntimeDefinedParameterDictionary]$ParamDictionary = New-Object -TypeName System.Management.Automation.RuntimeDefinedParameterDictionary

        [System.Management.Automation.ParameterAttribute]$Attributes = New-Object -TypeName System.Management.Automation.ParameterAttribute
        $Attributes.ParameterSetName = "Thumbprint"
        $Attributes.ValueFromPipeline = $true
        $Attributes.Mandatory = $true

        $Prints = Get-ChildItem -Path "Cert:\LocalMachine\My" | Where-Object { $_.HasPrivateKey -eq $true } | Select-Object -ExpandProperty Thumbprint

        [System.Management.Automation.ValidateSetAttribute]$ValidateSet = New-Object -TypeName System.Management.Automation.ValidateSetAttribute($Prints)
        
        $AttributeCollection = New-Object -TypeName System.Collections.ObjectModel.Collection[System.Attribute]
        $AttributeCollection.Add($Attributes)
        $AttributeCollection.Add($ValidateSet)

        [System.Management.Automation.RuntimeDefinedParameter]$DynParam = New-Object -TypeName System.Management.Automation.RuntimeDefinedParameter("Thumbprint", [System.String], $AttributeCollection)
        $ParamDictionary.Add("Thumbprint", $DynParam)

        [System.Management.Automation.ParameterAttribute]$Attributes = New-Object -TypeName System.Management.Automation.ParameterAttribute
        $Attributes.ParameterSetName = "Subject"
        $Attributes.ValueFromPipeline = $true
        $Attributes.Mandatory = $true

        $Subjects = Get-ChildItem -Path "Cert:\LocalMachine\My" | Where-Object { $_.HasPrivateKey -eq $true } | Select-Object -ExpandProperty Subject

        [System.Management.Automation.ValidateSetAttribute]$ValidateSet = New-Object -TypeName System.Management.Automation.ValidateSetAttribute($Subjects)
        
          
        $AttributeCollection = New-Object -TypeName System.Collections.ObjectModel.Collection[System.Attribute]
        $AttributeCollection.Add($Attributes)
        $AttributeCollection.Add($ValidateSet)

        [System.Management.Automation.RuntimeDefinedParameter]$DynParam = New-Object -TypeName System.Management.Automation.RuntimeDefinedParameter("Subject", [System.String], $AttributeCollection)
        $ParamDictionary.Add("Subject", $DynParam)

        Write-Output -InputObject $ParamDictionary
    }

    Begin {
    }

    Process {

        $Account = New-Object -TypeName System.Security.Principal.NTAccount($User)

        switch ($AccessLevel) {
            "FULL_CONTROL" {
                [System.Security.AccessControl.FileSystemRights]$Level = [System.Security.AccessControl.FileSystemRights]::FullControl
            }
            "READ_WRITE" {
                [System.Security.AccessControl.FileSystemRights]$Level = ([System.Security.AccessControl.FileSystemRights]::Read -bor [System.Security.AccessControl.FileSystemRights]::Write )
            }
            "READ" {
                [System.Security.AccessControl.FileSystemRights]$Level = [System.Security.AccessControl.FileSystemRights]::Read
            }
            default {
                throw "Invalid access level specified."
            }
        }

        switch ($PSCmdlet.ParameterSetName) {
            "Thumbprint" {
                [System.Security.Cryptography.X509Certificates.X509Certificate2]$Cert = Get-Item -Path "Cert:\LocalMachine\My\$($PSBoundParameters["Thumbprint"])"

                if ($Cert.HasPrivateKey()) {
                    $Path = "$($env:ProgramData)\Microsoft\Crypto\RSA\MachineKeys\$($Cert.PrivateKey.CspKeyContainerInfo.UniqueKeyContainerName)"
                    [System.Security.AccessControl.InheritanceFlags[]]$Inheritance = @([System.Security.AccessControl.InheritanceFlags]::None)
                }
                else {
                    throw "A certificate without a private key was selected."
                }
                break
            }
            "Subject" {
                [System.Security.Cryptography.X509Certificates.X509Certificate2]$Cert = Get-ChildItem -Path "Cert:\LocalMachine\My" | Where-Object {$_.Subject -eq $PSBoundParameters["Subject"]} | Select-Object -First 1
                if ($Cert.HasPrivateKey()) {
                    $Path = "$($env:ProgramData)\Microsoft\Crypto\RSA\MachineKeys\$($Cert.PrivateKey.CspKeyContainerInfo.UniqueKeyContainerName)"
                    [System.Security.AccessControl.InheritanceFlags[]]$Inheritance = @([System.Security.AccessControl.InheritanceFlags]::None)
                }
                else {
                    throw "A certificate without a private key was selected."
                }
                break
            }
            "All" {
                $Path = "$($env:ProgramData)\Microsoft\Crypto\RSA\MachineKeys"
                [System.Security.AccessControl.InheritanceFlags[]]$Inheritance = @([System.Security.AccessControl.InheritanceFlags]::ContainerInherit, [System.Security.AccessControl.InheritanceFlags]::ObjectInherit)
                break
            }
            default {
                throw "Could not determine parameter set name"
            }
        }

        #Provide access to Subfolders and files
        [System.Security.AccessControl.FileSystemAccessRule]$AccessRule = New-Object -TypeName System.Security.AccessControl.FileSystemAccessRule(
            $Account.Translate([System.Security.Principal.SecurityIdentifier]),
            $Level,
            $Inheritance,
            [System.Security.AccessControl.PropagationFlags]::InheritOnly,
            [System.Security.AccessControl.AccessControlType]::Allow
        )

        Set-FilePermissions -Rules @($AccessRule) -Path $Path -ReplaceRulesForUser:$Replace -ForceChildInheritance:$All
    }
    
    End {
    }
}

$script:IPv6Configs = @(
    [PSCustomObject]@{Name="IPv6 Disabled On All Interfaces";Value="0xFFFFFFFF"},
    [PSCustomObject]@{Name="IPv6 Enabled only on tunnel interfaces";Value="0xFFFFFFFE"}, 
    [PSCustomObject]@{Name="IPv6 Disabled On Tunnel Interfaces, Enabled On All Others";Value="0xFFFFFFEF"},
    [PSCustomObject]@{Name="IPv6 Disabled On Loopback Interface, Enabled On All Others";Value="0xFFFFFFEE"},
    [PSCustomObject]@{Name="IPv6 Disabled, Prefer IPv6 over IPv4";Value="0xFFFFFFDF"},
    [PSCustomObject]@{Name="IPv6 Enabled Only On Tunnel Interfaces, Prefer IPv6 of IPv4";Value="0xFFFFFFDE"},
    [PSCustomObject]@{Name="IPv6 Enabled On All Non Tunnel Interfaces, Prefer IPv6 over IPv4";Value="0xFFFFFFCF"},
    [PSCustomObject]@{Name="IPv6 Disabled On Loopback Interface, Prefer IPv6 over IPv4";Value="0xFFFFFFCE"},
    [PSCustomObject]@{Name="IPv6 Disabled On All Interfaces";Value="0x000000FF"},
    [PSCustomObject]@{Name="IPv6 Prefer IPv4 over IPv6 by changing entries in prefix policy table";Value="0x00000020"},
    [PSCustomObject]@{Name="IPv6 Disabled on LAN and PPP interfaces ";Value="0x00000010"},
    [PSCustomObject]@{Name="Disable Teredo";Value="0x00000008"},
    [PSCustomObject]@{Name="Disable ISATAP";Value="0x00000004"},
    [PSCustomObject]@{Name="Disable 6to4";Value="0x00000002"},
    [PSCustomObject]@{Name="IPv6 Disabled on Tunnel Interfaces including ISATAP, 6to4 and Teredo";Value="0x00000001"}
)

$script:Ports = @(
    [PSCustomObject]@{"Service"="FTP Data";"Port"=20},
    [PSCustomObject]@{"Service"="FTP Command";"Port"=21},
    [PSCustomObject]@{"Service"="SSH";"Port"=22},
    [PSCustomObject]@{"Service"="TelNet";"Port"=23},
    [PSCustomObject]@{"Service"="SMTP";"Port"=25},
    [PSCustomObject]@{"Service"="WINS";"Port"=42},
    [PSCustomObject]@{"Service"="DNS";"Port"=53},
    [PSCustomObject]@{"Service"="DHCP Server";"Port"=67},
    [PSCustomObject]@{"Service"="DHCP Client";"Port"=68},
    [PSCustomObject]@{"Service"="TFTP";"Port"=69},
    [PSCustomObject]@{"Service"="HTTP";"Port"=80},
    [PSCustomObject]@{"Service"="Kerberos";"Port"=88},
    [PSCustomObject]@{"Service"="POP3";"Port"=110},
    [PSCustomObject]@{"Service"="SFTP";"Port"=115},
    [PSCustomObject]@{"Service"="NetBIOS Name Service";"Port"=137},
    [PSCustomObject]@{"Service"="NetBIOS Datagram Service";"Port"=138},
    [PSCustomObject]@{"Service"="NetBIOS Session Service";"Port"=139},
    [PSCustomObject]@{"Service"="SNMP";"Port"=161},
    [PSCustomObject]@{"Service"="LDAP";"Port"=389},
    [PSCustomObject]@{"Service"="SSL";"Port"=443},
    [PSCustomObject]@{"Service"="SMB";"Port"=445},
    [PSCustomObject]@{"Service"="Syslog";"Port"=514},
    [PSCustomObject]@{"Service"="RPC";"Port"=135},
    [PSCustomObject]@{"Service"="LDAPS";"Port"=636},
    [PSCustomObject]@{"Service"="SOCKS";"Port"=1080},
    [PSCustomObject]@{"Service"="MSSQL";"Port"=1433},
    [PSCustomObject]@{"Service"="SQL Browser";"Port"=1434},
    [PSCustomObject]@{"Service"="Oracle DB";"Port"=1521},
    [PSCustomObject]@{"Service"="NFS";"Port"=2049},
    [PSCustomObject]@{"Service"="RDP";"Port"=3389},
    [PSCustomObject]@{"Service"="XMPP";"Port"=5222},
    [PSCustomObject]@{"Service"="HTTP Proxy";"Port"=8080},
    [PSCustomObject]@{"Service"="Global Catalog";"Port"=3268},
    [PSCustomObject]@{"Service"="Global Catalog/SSL";"Port"=3269},
    [PSCustomObject]@{"Service"="POP3/SSL";"Port"=995},
    [PSCustomObject]@{"Service"="IMAP/SSL";"Port"=993},
    [PSCustomObject]@{"Service"="IMAP";"Port"=143}
)

$script:TokenSignature = @"
public enum SECURITY_IMPERSONATION_LEVEL
{
    SecurityAnonymous = 0,
    SecurityIdentification = 1,
    SecurityImpersonation = 2,
    SecurityDelegation = 3
}
 
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct TokPriv1Luid
{
    public int Count;
    public long Luid;
    public int Attr;
}
 
public const int SE_PRIVILEGE_ENABLED = 0x00000002;
public const int TOKEN_QUERY = 0x00000008;
public const int TOKEN_ADJUST_PRIVILEGES = 0x00000020;
public const UInt32 STANDARD_RIGHTS_REQUIRED = 0x000F0000;
 
public const UInt32 STANDARD_RIGHTS_READ = 0x00020000;
public const UInt32 TOKEN_ASSIGN_PRIMARY = 0x0001;
public const UInt32 TOKEN_DUPLICATE = 0x0002;
public const UInt32 TOKEN_IMPERSONATE = 0x0004;
public const UInt32 TOKEN_QUERY_SOURCE = 0x0010;
public const UInt32 TOKEN_ADJUST_GROUPS = 0x0040;
public const UInt32 TOKEN_ADJUST_DEFAULT = 0x0080;
public const UInt32 TOKEN_ADJUST_SESSIONID = 0x0100;
public const UInt32 TOKEN_READ = (STANDARD_RIGHTS_READ | TOKEN_QUERY);
public const UInt32 TOKEN_ALL_ACCESS = (STANDARD_RIGHTS_REQUIRED | TOKEN_ASSIGN_PRIMARY |
    TOKEN_DUPLICATE | TOKEN_IMPERSONATE | TOKEN_QUERY | TOKEN_QUERY_SOURCE |
    TOKEN_ADJUST_PRIVILEGES | TOKEN_ADJUST_GROUPS | TOKEN_ADJUST_DEFAULT |
    TOKEN_ADJUST_SESSIONID);
 
public const string SE_TIME_ZONE_NAMETEXT = "SeTimeZonePrivilege";
public const int ANYSIZE_ARRAY = 1;
 
[StructLayout(LayoutKind.Sequential)]
public struct LUID
{
    public UInt32 LowPart;
    public UInt32 HighPart;
}
 
[StructLayout(LayoutKind.Sequential)]
public struct LUID_AND_ATTRIBUTES {
    public LUID Luid;
    public UInt32 Attributes;
}
 
public struct TOKEN_PRIVILEGES {
    public UInt32 PrivilegeCount;
    [MarshalAs(UnmanagedType.ByValArray, SizeConst=ANYSIZE_ARRAY)]
    public LUID_AND_ATTRIBUTES [] Privileges;
}
 
[DllImport("advapi32.dll", SetLastError=true)]
public extern static bool DuplicateToken(IntPtr ExistingTokenHandle, int SECURITY_IMPERSONATION_LEVEL, out IntPtr DuplicateTokenHandle);
 
[DllImport("advapi32.dll", SetLastError=true)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool SetThreadToken(
    IntPtr PHThread,
    IntPtr Token
);
 
[DllImport("advapi32.dll", SetLastError=true)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool OpenProcessToken(IntPtr ProcessHandle, UInt32 DesiredAccess, out IntPtr TokenHandle);
 
[DllImport("advapi32.dll", SetLastError = true)]
public static extern bool LookupPrivilegeValue(string host, string name, ref long pluid);
 
[DllImport("kernel32.dll", ExactSpelling = true)]
public static extern IntPtr GetCurrentProcess();
 
[DllImport("advapi32.dll", ExactSpelling = true, SetLastError = true)]
public static extern bool AdjustTokenPrivileges(IntPtr htok, bool disall, ref TokPriv1Luid newst, int len, IntPtr prev, IntPtr relen);
 
[DllImport( "kernel32.dll", CharSet = CharSet.Auto )]
public static extern bool CloseHandle( IntPtr handle );
 
[DllImport("advapi32.dll", SetLastError = true)]
public static extern bool RevertToSelf();
"@


$script:LsaSignature = @"
using System;
using System.Text;
using System.Runtime.InteropServices;
using System.ComponentModel;
 
namespace Bamcis.Lsa
{
    public class LSAUtil
    {
 
        [StructLayout(LayoutKind.Sequential)]
        private struct LSA_UNICODE_STRING
        {
            public UInt16 Length;
            public UInt16 MaximumLength;
            public IntPtr Buffer;
        }
 
        [StructLayout(LayoutKind.Sequential)]
        private struct LSA_OBJECT_ATTRIBUTES
        {
            public int Length;
            public IntPtr RootDirectory;
            public LSA_UNICODE_STRING ObjectName;
            public uint Attributes;
            public IntPtr SecurityDescriptor;
            public IntPtr SecurityQualityOfService;
        }
 
        private enum LSA_AccessPolicy : long
        {
            POLICY_VIEW_LOCAL_INFORMATION = 0x00000001L,
            POLICY_VIEW_AUDIT_INFORMATION = 0x00000002L,
            POLICY_GET_PRIVATE_INFORMATION = 0x00000004L,
            POLICY_TRUST_ADMIN = 0x00000008L,
            POLICY_CREATE_ACCOUNT = 0x00000010L,
            POLICY_CREATE_SECRET = 0x00000020L,
            POLICY_CREATE_PRIVILEGE = 0x00000040L,
            POLICY_SET_DEFAULT_QUOTA_LIMITS = 0x00000080L,
            POLICY_SET_AUDIT_REQUIREMENTS = 0x00000100L,
            POLICY_AUDIT_LOG_ADMIN = 0x00000200L,
            POLICY_SERVER_ADMIN = 0x00000400L,
            POLICY_LOOKUP_NAMES = 0x00000800L,
            POLICY_NOTIFICATION = 0x00001000L
        }
 
        [DllImport("advapi32.dll", SetLastError = true, PreserveSig = true)]
        private static extern uint LsaRetrievePrivateData(
            IntPtr PolicyHandle,
            ref LSA_UNICODE_STRING KeyName,
            out IntPtr PrivateData
        );
 
        [DllImport("advapi32.dll", SetLastError = true, PreserveSig = true)]
        private static extern uint LsaStorePrivateData(
             IntPtr policyHandle,
             ref LSA_UNICODE_STRING KeyName,
             ref LSA_UNICODE_STRING PrivateData
        );
 
        [DllImport("advapi32.dll", SetLastError = true, PreserveSig = true)]
        private static extern uint LsaOpenPolicy(
            ref LSA_UNICODE_STRING SystemName,
            ref LSA_OBJECT_ATTRIBUTES ObjectAttributes,
            uint DesiredAccess,
            out IntPtr PolicyHandle
        );
 
        [DllImport("advapi32.dll", SetLastError = true, PreserveSig = true)]
        private static extern uint LsaNtStatusToWinError(
            uint status
        );
 
        [DllImport("advapi32.dll", SetLastError = true, PreserveSig = true)]
        private static extern uint LsaClose(
            IntPtr policyHandle
        );
 
        [DllImport("advapi32.dll", SetLastError = true, PreserveSig = true)]
        private static extern uint LsaFreeMemory(
            IntPtr buffer
        );
 
        private LSA_OBJECT_ATTRIBUTES ObjectAttributes;
        private LSA_UNICODE_STRING LocalSystem;
        private LSA_UNICODE_STRING SecretName;
 
        public LSAUtil(string Key)
        {
            if (Key.Length == 0)
            {
                throw new ArgumentException("Key length zero");
            }
 
            this.ObjectAttributes = new LSA_OBJECT_ATTRIBUTES();
            this.ObjectAttributes.Length = 0;
            this.ObjectAttributes.RootDirectory = IntPtr.Zero;
            this.ObjectAttributes.Attributes = 0;
            this.ObjectAttributes.SecurityDescriptor = IntPtr.Zero;
            this.ObjectAttributes.SecurityQualityOfService = IntPtr.Zero;
 
            this.LocalSystem = new LSA_UNICODE_STRING();
            this.LocalSystem.Buffer = IntPtr.Zero;
            this.LocalSystem.Length = 0;
            this.LocalSystem.MaximumLength = 0;
 
            this.SecretName = new LSA_UNICODE_STRING();
            this.SecretName.Buffer = Marshal.StringToHGlobalUni(Key);
            this.SecretName.Length = (UInt16)(Key.Length * UnicodeEncoding.CharSize);
            this.SecretName.MaximumLength = (UInt16)((Key.Length + 1) * UnicodeEncoding.CharSize);
        }
 
        private IntPtr GetLsaPolicy(LSA_AccessPolicy Access)
        {
            IntPtr LsaPolicyHandle;
 
            uint NtsResult = LsaOpenPolicy(ref this.LocalSystem, ref this.ObjectAttributes, (uint)Access, out LsaPolicyHandle);
 
            uint WinErrorCode = LsaNtStatusToWinError(NtsResult);
            if (WinErrorCode != 0)
            {
                throw new Win32Exception(Convert.ToInt32(WinErrorCode));
            }
 
            return LsaPolicyHandle;
        }
 
        private static void ReleaseLsaPolicy(IntPtr LsaPolicyHandle)
        {
            uint NtsResult = LsaClose(LsaPolicyHandle);
            uint WinErrorCode = LsaNtStatusToWinError(NtsResult);
 
            if (WinErrorCode != 0)
            {
                throw new Win32Exception(Convert.ToInt32(WinErrorCode));
            }
        }
 
        private static void FreeMemory(IntPtr Buffer)
        {
            uint NtsResult = LsaFreeMemory(Buffer);
            uint WinErrorCode = LsaNtStatusToWinError(NtsResult);
            if (WinErrorCode != 0)
            {
                throw new Win32Exception(Convert.ToInt32(WinErrorCode));
            }
        }
 
        public void SetSecret(string Value)
        {
            LSA_UNICODE_STRING LusSecretData = new LSA_UNICODE_STRING();
 
            if (Value.Length > 0)
            {
                //Create data and key
                LusSecretData.Buffer = Marshal.StringToHGlobalUni(Value);
                LusSecretData.Length = (UInt16)(Value.Length * UnicodeEncoding.CharSize);
                LusSecretData.MaximumLength = (UInt16)((Value.Length + 1) * UnicodeEncoding.CharSize);
            }
            else
            {
                //Delete data and key
                LusSecretData.Buffer = IntPtr.Zero;
                LusSecretData.Length = 0;
                LusSecretData.MaximumLength = 0;
            }
 
            IntPtr LsaPolicyHandle = GetLsaPolicy(LSA_AccessPolicy.POLICY_CREATE_SECRET);
            uint Result = LsaStorePrivateData(LsaPolicyHandle, ref this.SecretName, ref LusSecretData);
            LSAUtil.ReleaseLsaPolicy(LsaPolicyHandle);
 
            uint WinErrorCode = LsaNtStatusToWinError(Result);
            if (WinErrorCode != 0)
            {
                throw new Win32Exception(Convert.ToInt32(WinErrorCode));
            }
        }
 
        public string GetSecret()
        {
            IntPtr PrivateData = IntPtr.Zero;
 
            IntPtr LsaPolicyHandle = GetLsaPolicy(LSA_AccessPolicy.POLICY_GET_PRIVATE_INFORMATION);
            uint NtsResult = LsaRetrievePrivateData(LsaPolicyHandle, ref this.SecretName, out PrivateData);
            LSAUtil.ReleaseLsaPolicy(LsaPolicyHandle);
 
            uint WinErrorCode = LsaNtStatusToWinError(NtsResult);
 
            if (WinErrorCode != 0)
            {
                throw new Win32Exception(Convert.ToInt32(WinErrorCode));
            }
 
            LSA_UNICODE_STRING LusSecretData = (LSA_UNICODE_STRING)Marshal.PtrToStructure(PrivateData, typeof(LSA_UNICODE_STRING));
            string Value = Marshal.PtrToStringAuto(LusSecretData.Buffer).Substring(0, LusSecretData.Length / 2);
 
            LSAUtil.FreeMemory(PrivateData);
 
            return Value;
        }
    }
}
"@