ESENT.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 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 Credential
            An optional credential used to connect to the database.
 
        .PARAMETER Force
            Bypasses the check to confirm the operation since it may modify the database causing data loss.
 
        .INPUTS
            System.String
 
        .OUTPUTS
            System.Management.Automation.PSCustomObject[]
 
        .NOTES
            AUTHOR: Michael Haken
            LAST UPDATE: 6/27/2017
    #>

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

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

        [Parameter()]
        [System.TimeSpan]$FutureTimeLimit = [System.TimeSpan]::FromDays(36500), #100 years

        [Parameter()]
        [ValidateScript({($_ % 1024) -eq 0})]
        [System.Int32]$PageSize = -1,

        [Parameter()] 
        [System.String[]]$ProcessesToStop = @(),

        [Parameter()]
        [System.Boolean]$Recovery = $false,

        [Parameter()]
        [System.Boolean]$CircularLogging = $true,

        [Parameter()]
        [ValidateNotNull()]
        [System.Management.Automation.Credential()]
        [System.Management.Automation.PSCredential]$Credential = [System.Management.Automation.PSCredential]::Empty,

        [Parameter()]
        [switch]$Force
    )

    Begin {
    }

    Process {
        [System.Int32]$private:Result = 0

        if (-not $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) 
        }

        [PSCustomObject[]]$Tables = @()

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

            $DBSession = New-ESEDatabaseSession -Path $Path -LogPrefix $LogPrefix -PageSize $PageSize -ProcessesToStop $ProcessesToStop -Recovery $Recovery -CircularLogging $CircularLogging -Credential $Credential -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"
                [System.String[]]$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
            }

            Write-Output -InputObject $Tables
        }
    }

    End {    
    }
}

Function New-ESEDatabaseSession {
    <#
        .SYNOPSIS
            Builds a new session with an ESE database and opens the database for ReadOnly operations.
 
        .DESCRIPTION
            The New-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
            New-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 Credential
            An optional credential used to connect to the database.
 
        .PARAMETER Force
            Bypasses the check to confirm the operation since it may modify the database causing data loss.
 
        .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: 6/27/2017
    #>

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

        [Parameter()]
        [System.String]$LogPrefix = "V01",

        [Parameter()]
        [System.Int32]$PageSize = -1,

        [Parameter()] 
        [System.String[]]$ProcessesToStop = @(),

        [Parameter()]
        [System.Boolean]$Recovery = $false,

        [Parameter()]
        [System.Boolean]$CircularLogging = $true,

        [Parameter()]
        [ValidateNotNull()]
        [System.Management.Automation.Credential()]
        [System.Management.Automation.PSCredential]$Credential = [System.Management.Automation.PSCredential]::Empty,

        [Parameter()]
        [switch]$Force,

        [Parameter()]
        [ValidateScript({Test-Path -Path $_})]
        [string]$EsentDllPath = $script:EsentDllPath
    )

    Begin {
    }

    Process {
        if(-not $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) 
        {
            $Tables = @()
            $Connect = [System.String]::Empty
            [System.String]$Password = [System.String]::Empty
            [System.String]$UserName = [System.String]::Empty

            if ($Credential -ne [System.Management.Automation.PSCredential]::Empty) 
            {
                $UserName = $Credential.UserName

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

                try 
                {     
                    $IntPtr = [System.Runtime.InteropServices.Marshal]::SecureStringToGlobalAllocUnicode($SecureString)     
                    $Password = [System.Runtime.InteropServices.Marshal]::PtrToStringUni($IntPtr)   
                }   
                finally 
                {     
                    if ($IntPtr -ne $null -and $IntPtr -ne [System.IntPtr]::Zero) 
                    {       
                        [System.Runtime.InteropServices.Marshal]::ZeroFreeGlobalAllocUnicode($IntPtr)     
                    }   
                }
            }

            if ($ProcessesToStop -ne $null)
            {
                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 | Out-Null
                        Write-Verbose -Message "Process stopped"
                    }
                    else 
                    {
                        Write-Verbose -Message "Process $Process does not exist."
                    }
                }
            }

            [System.Int32]$FileType = -1
            [Microsoft.Isam.Esent.Interop.Api]::JetGetDatabaseFileInfo($Path, [ref]$FileType, [Microsoft.Isam.Esent.Interop.JET_DbInfo]::FileType)
            [Microsoft.Isam.Esent.Interop.JET_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, $Password)

                [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 $_.Exception
                    }
                }
            }
            else 
            {
                throw "The path must be to a database, the selected path was a $DBType."
            }

            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 = New-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.
 
        .INPUTS
            None
 
        .OUTPUTS
            System.String[]
 
        .NOTES
            AUTHOR: Michael Haken
            LAST UPDATE: 4/25/2016
    #>

    [CmdletBinding()]
    Param(
        [Parameter(Position = 0, Mandatory = $true)]
        [ValidateNotNull()]
        [Microsoft.Isam.Esent.Interop.JET_SESID]$Session,

        [Parameter(Position = 1, Mandatory = $true)]
        [ValidateNotNull()]
        [Microsoft.Isam.Esent.Interop.JET_DBID]$DatabaseId
    )

    Begin {
    }

    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 = New-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.
 
        .INPUTS
            None
 
        .OUTPUTS
             Microsoft.Isam.Esent.Interop.ColumnInfo[]
 
        .NOTES
            AUTHOR: Michael Haken
            LAST UPDATE: 6/27/2017
    #>

    [CmdletBinding()]
    Param(
        [Parameter(Position = 0, Mandatory = $true)]
        [ValidateNotNull()]
        [Microsoft.Isam.Esent.Interop.JET_SESID]$Session,

        [Parameter(Position = 1, Mandatory = $true)]
        [ValidateNotNull()]
        [Microsoft.Isam.Esent.Interop.JET_DBID]$DatabaseId,

        [Parameter(Position = 2, Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [System.String]$TableName
    )

    Begin {
    }

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

    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 = New-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.
 
        .INPUTS
            None
 
        .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)]
        [ValidateNotNull()]
        [Microsoft.Isam.Esent.Interop.JET_SESID]$Session,

        [Parameter(Position = 1, Mandatory = $true)]
        [ValidateNotNull()]
        [Microsoft.Isam.Esent.Interop.JET_DBID]$DatabaseId,

        [Parameter(Position = 2, Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [System.String]$TableName,

        [Parameter(Position = 3)]
        [System.TimeSpan]$FutureTimeLimit = [System.TimeSpan]::FromDays(36500)
    )

    Begin {
    }

    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=@()}
            
            [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.
 
        .INPUTS
            None
 
        .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: 6/27/2017
    #>

    [CmdletBinding()]
    Param(
        [Parameter(Position = 0, Mandatory = $true)]
        [ValidateNotNull()]
        [Microsoft.Isam.Esent.Interop.JET_SESID]$Session,

        [Parameter(Position = 1, Mandatory = $true)]
        [ValidateNotNull()]
        [Microsoft.Isam.Esent.Interop.JET_TABLEID]$TableId,

        [Parameter(Position = 2, Mandatory = $true)]
        [Microsoft.Isam.Esent.Interop.ColumnInfo[]]$Columns = $null,

        [Parameter(Position = 3)]
        [System.TimeSpan]$FutureTimeLimit = [System.TimeSpan]::FromDays(36500)
    )

    Begin {
    }

    Process {
        $Row = @{}

        if ($Columns -eq $null -or $Columns.Length -eq 0) 
        {
            $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 = New-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.
 
        .INPUTS
            None
 
        .OUTPUTS
            None
 
        .NOTES
            AUTHOR: Michael Haken
            LAST UPDATE: 6/27/2017
    #>

    [CmdletBinding()] 
    Param(
        [Parameter(Position = 0, Mandatory = $true)]
        [ValidateNotNull()]
        [Microsoft.Isam.Esent.Interop.JET_INSTANCE]$Instance,

        [Parameter(Position = 1, Mandatory = $true)]
        [ValidateNotNull()]
        [Microsoft.Isam.Esent.Interop.JET_SESID]$Session,

        [Parameter(Position = 2, Mandatory = $true)]
        [ValidateNotNull()]
        [Microsoft.Isam.Esent.Interop.JET_DBID]$DatabaseId,

        [Parameter(Position = 3,Mandatory  =$true)]
        [ValidateScript({Test-Path -Path $_})]
        [System.String]$Path
    )

    Begin {
    }

    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 {
    }
}