Public/Get-AdoSchemaMetaData.ps1

function Get-AdoSchemaMetaData {
    <#
    .SYNOPSIS
    Retrieves a list schema type from an ADO data source
 
    .DESCRIPTION
    Retrieves a list schema type from an ADO data source
 
    .PARAMETER Provider
    What describes the provider? Often this, is the guts that describe a "dsn-less" connection. It is used to choose the drivers that will be used to do to work of retrieving the data.
 
    .PARAMETER Datasource
    This describes the source of the data.
 
    .PARAMETER ExtendedProperties
    This allows the caller to provide extended property information, when required.
 
    .PARAMETER SchemaType
    What type of Schema 'report' do we want?
 
    All providers do not support all Schema types.
 
    There are many available, this cmdlet has been tested against only a few, so we ValidateSet() on this parameter.
    See the Microsoft link for full documentation.
 
    .NOTES
    Get-AdoSchemaMetaDataColumn, Get-AdoSchemaMetaDataIndex and Get-AdoSchemaMetaDataTable call this cmdlet. There is a fair amount
    of complicated code in here, with all of the looping over column names and rows, so it seems smarter to
    concentrate all of that in one function. If you look at the corresponding Get-OleDb* cmdlets, the code is
    much simpler so there is no Get-OleDbSchema.
 
    .EXAMPLE
    Get-AdoSchemaMetaData -Datasource "c:\temp\presidents.mdb" -provider "Microsoft.Ace.OLEDB.12.0" -SchemaType 20
 
    .LINK
    http://www.carlprothman.net/Technology/ConnectionStrings/ODBCDSNLess/tabid/90/Default.aspx
 
    .LINK
    https://docs.microsoft.com/en-us/sql/ado/reference/ado-api/schemaenum
 
    .LINK
    Get-AdoColumnMetaData
 
    .LINK
    Get-AdoIndexMetaData
 
    .LINK
    Get-AdoTableMetaData
 
    #>


    param (
        [CmdletBinding()]
        [Parameter(Mandatory = $true)]
        # (adSchemaColumns = 4, adSchemaTables = 20, adSchemaKeyColumnUsage = 8, adSchemaPrimaryKeys = 28, adSchemaIndexes = 12)
        [ValidateSet(4, 8, 12, 20)]
        [int] $SchemaType,
        [Parameter(Mandatory = $true)]
        [string] $Provider,
        [Parameter(Mandatory = $True, ValueFromPipeline = $True, ValueFromPipelinebyPropertyName = $True)]
        [string] $Datasource,
        [string] $ExtendedProperties
    )

    process {
        $objConnection = New-Object -comobject ADODB.Connection
        $objRecordset = New-Object -comobject ADODB.RecordSet

        $connectionString = "Provider=$provider;data source=$datasource;"
        if ($ExtendedProperties) {
            $connectionString += "Extended Properties=$ExtendedProperties"
        }


        $objConnection.Open($connectionString)
        trap [System.Runtime.InteropServices.COMException] {
            Write-Error "ERROR: $($_.Exception.Message)"
            # to keep processing, I'd use continue here
            break
        }

        $objRecordset = $objConnection.OpenSchema($SchemaType)
        $objRecordset.MoveFirst()

        # We need an object with appropriate properties to hold the data from the fields.
        # I am initializing the value here (even though we are going to overwrite in shortly)
        # so I get the correct data type associated with the property.
        $record = New-Object PSObject
        foreach ($field in $objRecordset.Fields) {
            $record | Add-Member -MemberType:noteproperty -Name:$field.Name -Value:$field.Value
    }

    do {
        # For whatevever reason, ADO returns a numeric constant rather than a string
        # for a data type, at least for Access files. That is translated here, for the
        # convenience of the caller.
        foreach ($field in $objRecordset.Fields) {
            if ($field.Name -eq "DATA_TYPE") {
                $record.$($field.Name) = [enum]::Parse("system.data.oledb.oledbtype", $field.Value)
            }
            else {
                $record.$($field.Name) = $field.Value
            }
        }
        # emit the working object to the pipeline, then move to the next record and loop
        $record
        $objRecordset.MoveNext()
    }
    until ($objRecordSet.EOF -eq $True)

    $objRecordset.Close()
    $objConnection.Close()

    [gc]::Collect()
    [gc]::WaitForPendingFinalizers()

}

}