Functions/Export-Migration.ps1


function Export-Migration
{
    <#
    .SYNOPSIS
    Exports objects from a database as Rivet migrations.
 
    .DESCRIPTION
    The `Export-Migration` function exports database objects, schemas, and data types as a Rivet migration. By default, it exports *all* non-system, non-Rivet objects, data types, and schemas. You can filter specific objects by passing their full name to the `Include` parameter. Wildcards are supported. Objects are matched on their schema *and* name.
 
    Extended properties are *not* exported, except table and column descriptions.
 
    .EXAMPLE
    Export-Migration -SqlServerName 'some\instance' -Database 'database'
 
    Demonstrates how to export an entire database.
    #>

    [CmdletBinding()]
    param(
        [Parameter(Mandatory)]
        [string]
        # The connection string for the database to connect to.
        $SqlServerName,

        [Parameter(Mandatory)]
        [string]
        # The database to connect to.
        $Database,

        [string[]]
        # The names of the objects to export. Must include the schema if exporting a specific object. Wildcards supported.
        #
        # The default behavior is to export all non-system objects.
        $Include,

        [string[]]
        # The names of any objects *not* to export. Matches the object name *and* its schema name, i.e. `schema.name`. Wildcards supported.
        $Exclude,

        [string[]]
        [ValidateSet('CheckConstraint','DataType','DefaultConstraint','ForeignKey','Function','Index','PrimaryKey','Schema','StoredProcedure','Synonym','Table','Trigger','UniqueKey','View','XmlSchema')]
        # Any object types to exclude.
        $ExcludeType,

        [Switch]
        $NoProgress
    )

    Set-StrictMode -Version 'Latest'
    Use-CallerPreference -Cmdlet $PSCmdlet -SessionState $ExecutionContext.SessionState

    $pops = New-Object 'Collections.Generic.Stack[string]'
    $popsHash = @{}
    $exportedObjects = @{ }
    $exportedSchemas = @{ 
                            'dbo' = $true;
                            'guest' = $true;
                            'sys' = $true;
                            'INFORMATION_SCHEMA' = $true;
                        }
    $exportedTypes = @{ }
    $exportedIndexes = @{ }
    $exportedXmlSchemas = @{ }
    $rivetColumnTypes = Get-Alias | 
                            Where-Object { $_.Source -eq 'Rivet' } | 
                            Where-Object { $_.ReferencedCommand -like 'New-*Column' } | 
                            Select-Object -ExpandProperty 'Name'

    $dependencies = @{ }
    $externalDependencies = @{ }
    $indentLevel = 0

    $timer = New-Object 'Timers.Timer' 100

    $checkConstraints = @()
    $checkConstraintsByID = @{}
    $columns = @()
    $columnsByTable = @{}
    $dataTypes = @()
    $defaultConstraints = @()
    $defaultConstraintsByID = @{}
    $foreignKeys = @()
    $foreignKeysByID = @{}
    $foreignKeyColumns = @()
    $foreignKeyColumnsByObjectID = @{}
    $indexes = @()
    $indexesByObjectID = @{}
    $indexColumns = @()
    $indexColumnsByObjectID = @{}
    $objects = @()
    $objectsByID = @{}
    $objectsByParentID = @{}
    $primaryKeys = @()
    $primaryKeysByID = @{}
    $primaryKeyColumns = @()
    $primaryKeyColumnsByObjectID = @{}
    $schemas = @()
    $schemasByName = @{}
    $modules = @()
    $modulesByID = @{}
    $storedProcedures = @()
    $storedProceduresByID = @{}
    $synonyms = @()
    $synonymsByID = @{}
    $triggers = @()
    $triggersByID = @{}
    $triggersByTable = @{}
    $uniqueKeys = @()
    $uniqueKeysByID = @{}
    $uniqueKeysByTable = @{}
    $uniqueKeyColumnsByObjectID = @()
    $uniqueKeyColumnsByObjectID = @{}
    $functions = @()
    $functionsByID = @{}
    $views = @()
    $viewByID = @{}
    $xmlSchemaDependencies = @{ }
    $xmlSchemasByID = @{ }

    $exclusionTypeMap = @{ 
        'CheckConstraint' = 'CHECK_CONSTRAINT';
        'DefaultConstraint' = 'DEFAULT_CONSTRAINT';
        'ForeignKey' = 'FOREIGN_KEY_CONSTRAINT';
        'Function' = @('SQL_INLINE_TABLE_VALUED_FUNCTION','SQL_SCALAR_FUNCTION','SQL_TABLE_VALUED_FUNCTION');
        'PrimaryKey' = 'PRIMARY_KEY_CONSTRAINT';
        'StoredProcedure' = 'SQL_STORED_PROCEDURE';
        'Synonym' = 'SYNONYM';
        'Table' = 'USER_TABLE';
        'Trigger' = 'SQL_TRIGGER';
        'UniqueKey' = 'UNIQUE_CONSTRAINT';
        'View' = 'VIEW';
    }


    function ConvertTo-SchemaParameter
    {
        param(
            [Parameter(Mandatory)]
            [AllowNull()]
            [AllowEmptyString()]
            [string]
            $SchemaName,

            [string]
            $ParameterName = 'SchemaName'
        )

        $parameter = ''
        if( $SchemaName -and $SchemaName -ne 'dbo' )
        {
            $parameter = ' -{0} ''{1}''' -f $ParameterName,$SchemaName
        }
        return $parameter
    }

    function Get-ChildObject
    {
        param(
            [Parameter(Mandatory)]
            [int]
            $TableID,

            [Parameter(Mandatory)]
            [string]
            $Type
        )

        if( $objectsByParentID.ContainsKey($TableID) )
        {
            $objectsByParentID[$TableID] | Where-Object { $_.type -eq $Type }
        }
    }

    $checkConstraintsQuery = '
-- CHECK CONSTRAINTS
select
    sys.check_constraints.object_id,
    schema_name(sys.tables.schema_id) as schema_name,
    sys.tables.name as table_name,
    sys.check_constraints.name as name,
    sys.check_constraints.is_not_trusted,
    sys.check_constraints.is_not_for_replication,
    sys.check_constraints.is_disabled,
    sys.check_constraints.definition
from
    sys.check_constraints
        join
    sys.tables
            on sys.check_constraints.parent_object_id = sys.tables.object_id
--where
-- sys.check_constraints.object_id = @object_id'

    function Export-CheckConstraint
    {
        param(
            [Parameter(Mandatory,ParameterSetName='ByObject')]
            [object]
            $Object,

            [Parameter(Mandatory,ParameterSetName='ByTableID')]
            [int]
            $TableID,

            [Switch]
            $ForTable
        )

        if( $TableID )
        {
            $objects = Get-ChildObject -TableID $TableID -Type 'C'
            foreach( $object in $objects )
            {
                Export-CheckConstraint -Object $object -ForTable:$ForTable
            }
            return
        }

        $constraint = $checkConstraintsByID[$Object.object_id]
        if( -not $ForTable )
        {
            Export-Object -ObjectID $Object.parent_object_id
        }

        if( $exportedObjects.ContainsKey($Object.object_id) )
        {
            continue
        }
            
        Export-DependentObject -ObjectID $constraint.object_id

        Write-ExportingMessage -Schema $constraint.schema_name -Name $constraint.name -Type CheckConstraint

        $notChecked = ''
        if( $constraint.is_not_trusted )
        {
            $notChecked = ' -NoCheck'
        }

        $notForReplication = ''
        if( $constraint.is_not_for_replication )
        {
            $notForReplication = ' -NotForReplication'
        }

        $schema = ConvertTo-SchemaParameter -SchemaName $constraint.schema_name
        ' Add-CheckConstraint{0} -TableName ''{1}'' -Name ''{2}'' -Expression ''{3}''{4}{5}' -f $schema,$constraint.table_name,$constraint.name,($constraint.definition -replace '''',''''''),$notForReplication,$notChecked
        if( $constraint.is_disabled )
        {
            ' Disable-Constraint{0} -TableName ''{1}'' -Name ''{2}''' -f $schema,$constraint.table_name,$constraint.name
        }
        if( -not $ForTable )
        {
            Push-PopOperation ('Remove-CheckConstraint{0} -TableName ''{1}'' -Name ''{2}''' -f $schema,$constraint.table_name,$constraint.name)
        }
        $exportedObjects[$constraint.object_id] = $true
    }

    $columnsQuery = '
-- COLUMNS
select
    sys.columns.object_id,
    sys.columns.is_nullable,
    sys.types.name as type_name,
    sys.columns.name as column_name,
    sys.types.collation_name as type_collation_name,
    sys.columns.max_length as max_length,
    sys.extended_properties.value as description,
    sys.columns.is_identity,
    sys.identity_columns.increment_value,
    sys.identity_columns.seed_value,
    sys.columns.precision,
    sys.columns.scale,
    sys.types.precision as default_precision,
    sys.types.scale as default_scale,
    sys.columns.is_sparse,
    sys.columns.collation_name,
    serverproperty(''collation'') as default_collation_name,
    sys.columns.is_rowguidcol,
    sys.types.system_type_id,
    sys.types.user_type_id,
    isnull(sys.identity_columns.is_not_for_replication, 0) as is_not_for_replication,
    sys.columns.column_id,
    sys.columns.is_xml_document,
    sys.columns.xml_collection_id,
    sys.xml_schema_collections.name as xml_schema_name,
    sys.types.max_length as default_max_length
from
    sys.columns
        inner join
    sys.types
            on columns.user_type_id = sys.types.user_type_id
        left join
    sys.extended_properties
            on sys.columns.object_id = sys.extended_properties.major_id
            and sys.columns.column_id = sys.extended_properties.minor_id
            and sys.extended_properties.name = ''MS_Description''
        left join
    sys.identity_columns
            on sys.columns.object_id = sys.identity_columns.object_id
            and sys.columns.column_id = sys.identity_columns.column_id
        left join
    sys.xml_schema_collections
            on sys.columns.xml_collection_id=sys.xml_schema_collections.xml_collection_id
'

    function Export-Column
    {
        param(
            [Parameter(Mandatory,ParameterSetName='ForTable')]
            [int]
            $TableID
        )

        foreach( $column in ($columnsByTable[$TableID] | Sort-Object -Property 'column_id') )
        {
            $notNull = ''
            $parameters = & {
                $isBinaryVarColumn = $column.type_name -in @( 'varbinary', 'binary' )
                if( $column.type_collation_name -or $isBinaryVarColumn )
                {
                    $isSizable = $column.type_name -in @( 'binary', 'char', 'nchar', 'nvarchar', 'varbinary', 'varchar' )
                    if( $isSizable )
                    {
                        $maxLength = $column.max_length
                        if( $maxLength -eq -1 )
                        {
                            '-Max'
                        }
                        else
                        {
                            if( $column.type_name -like 'n*' )
                            {
                                $maxLength = $maxLength / 2
                            }
                            '-Size {0}' -f $maxLength
                        }
                    }

                    if( $column.collation_name -ne $column.default_collation_name -and -not $isBinaryVarColumn )
                    {
                        '-Collation'
                        '''{0}''' -f $column.collation_name
                    }
                }

                if( $column.type_name -eq 'xml' )
                {
                    if( $column.xml_schema_name )
                    {
                        if( $column.is_xml_document )
                        {
                            '-Document'
                        }
                        '-XmlSchemaCollection'
                        '''{0}''' -f $column.xml_schema_name
                    }
                }

                if( $column.is_rowguidcol )
                {
                    '-RowGuidCol'
                }

                $scaleOnlyTypes = @( 'time','datetime2', 'datetimeoffset' )
                if( $column.precision -ne $column.default_precision -and $column.type_name -notin $scaleOnlyTypes )
                {
                    '-Precision'
                    $column.precision
                }
                if( $column.scale -ne $column.default_scale )
                {
                    '-Scale'
                    $column.scale
                }

                if( $column.is_identity )
                {
                    '-Identity'
                    if( $column.seed_value -ne 1 -or $column.increment_value -ne 1 )
                    {
                        '-Seed'
                        $column.seed_value
                        '-Increment'
                        $column.increment_value
                    }
                }
                if( $column.is_not_for_replication )
                {
                    '-NotForReplication'
                }
                if( -not $column.is_nullable )
                {
                    if( -not $column.is_identity )
                    {
                        '-NotNull'
                    }
                }
                if( $column.is_sparse )
                {
                    '-Sparse'
                }
                if( $column.description )
                {
                    '-Description ''{0}''' -f ($column.description -replace '''','''''')
                }
            }

            if( $parameters )
            {
                $parameters = $parameters -join ' '
                $parameters = ' {0}' -f $parameters
            }

            if( $rivetColumnTypes -contains $column.type_name )
            {
                ' {0} ''{1}''{2}' -f $column.type_name,$column.column_name,$parameters
            }
            else
            {
                ' New-Column -DataType ''{0}'' -Name ''{1}''{2}' -f $column.type_name,$column.column_name,$parameters
            }
        }
    }

    $dataTypesQuery = '
-- DATA TYPES
select
    schema_name(sys.types.schema_id) as schema_name,
    sys.types.name,
    sys.types.max_length,
    sys.types.precision,
    sys.types.scale,
    sys.types.collation_name,
    sys.types.is_nullable,
    systype.name as from_name,
    systype.max_length as from_max_length,
    systype.precision as from_precision,
    systype.scale as from_scale,
    systype.collation_name as from_collation_name,
    sys.types.is_table_type,
    sys.table_types.type_table_object_id
from
    sys.types
        left join
    sys.types systype
            on sys.types.system_type_id = systype.system_type_id
            and sys.types.system_type_id = systype.user_type_id
        left join
    sys.table_types
            on sys.types.user_type_id = sys.table_types.user_type_id
where
    sys.types.is_user_defined = 1'

    function Export-DataType
    {
        [CmdletBinding(DefaultParameterSetName='All')]
        param(
            [Parameter(Mandatory,ParameterSetName='ByDataType')]
            [object]
            $Object
        )

        if( $ExcludeType -contains 'DataType' )
        {
            return
        }

        if( $PSCmdlet.ParameterSetName -eq 'All' )
        {
            foreach( $object in $dataTypes )
            {
                if( (Test-SkipObject -SchemaName $object.schema_name -Name $object.name) )
                {
                    continue
                }
                Export-DataType -Object $object
            }
            return
        }

        if( $exportedTypes.ContainsKey($Object.name) )
        {
            Write-Debug ('Skipping ALREADY EXPORTED {0}' -f $Object.name)
            continue
        }
        
        Export-Schema -Name $Object.schema_name

        Write-ExportingMessage -SchemaName $Object.schema_name -Name $Object.name -Type DataType

        $schema = ConvertTo-SchemaParameter -SchemaName $Object.schema_name
        if( $Object.is_table_type )
        {
            ' Add-DataType{0} -Name ''{1}'' -AsTable {{' -f $schema,$Object.name
            Export-Column -TableID $Object.type_table_object_id
            ' }'
        }
        else
        {
            $typeDef = $object.from_name
            if( $object.from_collation_name )
            {
                if( $object.max_length -ne $object.from_max_length )
                {
                    $maxLength = $object.max_length
                    if( $maxLength -eq -1 )
                    {
                        $maxLength = 'max'
                    }
                    $typeDef = '{0}({1})' -f $typeDef,$maxLength
                }
            }
            else
            {
                if( ($object.precision -ne $object.from_precision) -or ($object.scale -ne $object.from_scale) )
                {
                    $typeDef = '{0}({1},{2})' -f $typeDef,$object.precision,$object.scale
                }
            }

            if( -not $object.is_nullable )
            {
                $typeDef = '{0} not null' -f $typeDef
            }

            ' Add-DataType{0} -Name ''{1}'' -From ''{2}''' -F $schema,$Object.name,$typeDef
        }
        Push-PopOperation ('Remove-DataType{0} -Name ''{1}''' -f $schema,$Object.name)
        $exportedtypes[$object.name] = $true
    }

    $defaultConstraintsQuery = '
-- DEFAULT CONSTRAINTS
select
    schema_name(sys.tables.schema_id) as schema_name,
    sys.tables.name as table_name,
    sys.default_constraints.name as name,
    sys.columns.name as column_name,
    definition,
    sys.default_constraints.object_id,
    sys.default_constraints.parent_object_id
from
    sys.objects
        join
    sys.default_constraints
            on sys.default_constraints.object_id = sys.objects.object_id
        join
    sys.columns
            on sys.columns.object_id = sys.default_constraints.parent_object_id
            and sys.columns.column_id = sys.default_constraints.parent_column_id
        left join
    sys.tables
            on sys.objects.parent_object_id = sys.tables.object_id
        left join
    sys.schemas
            on sys.schemas.schema_id = sys.tables.schema_id
-- where
-- sys.default_constraints.object_id = @object_id'

    function Export-DefaultConstraint
    {
        param(
            [Parameter(Mandatory,ParameterSetName='ByObject')]
            [object]
            $Object,

            [Parameter(Mandatory,ParameterSetName='ByTableID')]
            [int]
            $TableID,

            [Switch]
            $ForTable
        )

        if( $TableID )
        {
            $objects = Get-ChildObject -TableID $TableID -Type 'D'
            foreach( $item in $objects )
            {
                Export-DefaultConstraint -Object $item -ForTable:$ForTable
            }
            return
        }

        $constraint = $defaultConstraintsByID[$Object.object_id]
        if( -not $constraint )
        {
            Write-Warning -Message ('Unable to export default constraint [{0}].[{1}] ({2}): its metadata is missing from the databse.' -f $Object.schema_name,$Object.name,$Object.object_id)
            $exportedObjects[$Object.object_id] = $true
            return
        }

        # Default constraint isn't on a table
        if( $constraint.table_name -eq $null )
        {
            $exportedObjects[$Object.object_id] = $true
            return
        }

        if( -not $ForTable )
        {
            Export-Object -ObjectID $constraint.parent_object_id
        }

        if( $exportedObjects.ContainsKey($constraint.object_id) )
        {
            continue
        }

        Export-DependentObject -ObjectID $constraint.object_id

        Write-ExportingMessage -Schema $Object.schema_name -Name $constraint.name -Type DefaultConstraint
        $schema = ConvertTo-SchemaParameter -SchemaName $constraint.schema_name
        ' Add-DefaultConstraint{0} -TableName ''{1}'' -ColumnName ''{2}'' -Name ''{3}'' -Expression ''{4}''' -f $schema,$Object.parent_object_name,$constraint.column_name,$constraint.name,($constraint.definition -replace '''','''''')
        if( -not $ForTable )
        {
            Push-PopOperation ('Remove-DefaultConstraint{0} -TableName ''{1}'' -Name ''{2}''' -f $schema,$Object.parent_object_name,$constraint.name)
        }
        $exportedObjects[$constraint.object_id] = $true
    }

    function Export-DependentObject
    {
        param(
            [Parameter(Mandatory)]
            [int]
            $ObjectID
        )

        $indentLevel += 1
        try
        {
            if( $dependencies.ContainsKey($ObjectID) )
            {
                foreach( $dependencyID in $dependencies[$ObjectID].Keys )
                {
                    Export-Object -ObjectID $dependencyID
                }
            }

            if( $xmlSchemaDependencies.ContainsKey($ObjectID) )
            {
                foreach( $xmlSchemaID in $xmlSchemaDependencies[$ObjectID] )
                {
                    Export-XmlSchema -ID $xmlSchemaID
                }
            }
        }
        finally
        {
            $indentLevel -= 1
        }
    }

    $foreignKeysQuery = '
-- FOREIGN KEYS
select
    sys.foreign_keys.object_id,
    is_not_trusted,
    is_not_for_replication,
    delete_referential_action_desc,
    update_referential_action_desc,
    schema_name(sys.objects.schema_id) as references_schema_name,
    sys.objects.name as references_table_name,
    sys.foreign_keys.referenced_object_id,
    is_disabled
from
    sys.foreign_keys
        join
    sys.objects
        on sys.foreign_keys.referenced_object_id = sys.objects.object_id
'

    $foreignKeyColumnsQuery = '
-- FOREIGN KEY COLUMNS
select
    sys.foreign_key_columns.constraint_object_id,
    sys.columns.name as name,
    referenced_columns.name as referenced_name,
    sys.foreign_key_columns.constraint_column_id
from
    sys.foreign_key_columns
        join
    sys.columns
            on sys.foreign_key_columns.parent_object_id = sys.columns.object_id
            and sys.foreign_key_columns.parent_column_id = sys.columns.column_id
        join
    sys.columns as referenced_columns
            on sys.foreign_key_columns.referenced_object_id = referenced_columns.object_id
            and sys.foreign_key_columns.referenced_column_id = referenced_columns.column_id
'

    function Export-ForeignKey
    {
        param(
            [Parameter(Mandatory)]
            [object]
            $Object
        )

        # Make sure the key's table is exported.
        Export-Object -ObjectID $Object.parent_object_id

        $schema = ConvertTo-SchemaParameter -SchemaName $Object.schema_name
        $foreignKey = $foreignKeysByID[$Object.object_id]

        # Make sure the key's referenced table is exported.
        Export-Object -ObjectID $foreignKey.referenced_object_id

        $referencesSchema = ConvertTo-SchemaParameter -SchemaName $foreignKey.references_schema_name -ParameterName 'ReferencesSchema'
        $referencesTableName = $foreignKey.references_table_name

        $columns = $foreignKeyColumnsByObjectID[$Object.object_id] | Sort-Object -Property 'constraint_column_id'

        $columnNames = $columns | Select-Object -ExpandProperty 'name'
        $referencesColumnNames = $columns | Select-Object -ExpandProperty 'referenced_name'

        $onDelete = ''
        if( $foreignKey.delete_referential_action_desc -ne 'NO_ACTION' )
        {
            $onDelete = ' -OnDelete ''{0}''' -f $foreignKey.delete_referential_action_desc
        }

        $onUpdate = ''
        if( $foreignKey.update_referential_action_desc -ne 'NO_ACTION' )
        {
            $onUpdate = ' -OnUpdate ''{0}''' -f $foreignKey.update_referential_action_desc
        }

        $notForReplication = ''
        if( $foreignKey.is_not_for_replication )
        {
            $notForReplication = ' -NotForReplication'
        }

        $noCheck = ''
        if( $foreignKey.is_not_trusted )
        {
            $noCheck = ' -NoCheck'
        }

        Write-ExportingMessage -Schema $Object.schema_name -Name $Object.name -Type ForeignKey

        ' Add-ForeignKey{0} -TableName ''{1}'' -ColumnName ''{2}''{3} -References ''{4}'' -ReferencedColumn ''{5}'' -Name ''{6}''{7}{8}{9}{10}' -f $schema,$Object.parent_object_name,($columnNames -join ''','''),$referencesSchema,$referencesTableName,($referencesColumnNames -join ''','''),$Object.name,$onDelete,$onUpdate,$notForReplication,$noCheck
        if( $foreignKey.is_disabled )
        {
            ' Disable-Constraint{0} -TableName ''{1}'' -Name ''{2}''' -f $schema,$Object.parent_object_name,$Object.name
        }
        Push-PopOperation ('Remove-ForeignKey{0} -TableName ''{1}'' -Name ''{2}''' -f $schema,$Object.parent_object_name,$Object.name)
        $exportedObjects[$Object.object_id] = $true
    }

    $indexesQuery = '
-- INDEXES
select
    sys.indexes.object_id,
    schema_name(sys.tables.schema_id) as schema_name,
    sys.indexes.name,
    sys.tables.name as table_name,
    sys.indexes.is_unique,
    sys.indexes.type_desc,
    sys.indexes.has_filter,
    sys.indexes.filter_definition,
    sys.indexes.index_id
from
    sys.indexes
        join
    sys.tables
            on sys.indexes.object_id = sys.tables.object_id
where
    is_primary_key = 0 and
    sys.indexes.type != 0 and
    sys.indexes.is_unique_constraint != 1 and
    sys.tables.is_ms_shipped = 0'


    $indexesColumnsQuery = '
-- INDEX COLUMNS
select
    sys.indexes.object_id,
    sys.indexes.index_id,
    sys.columns.name,
    sys.index_columns.key_ordinal,
    sys.index_columns.is_included_column,
    sys.index_columns.is_descending_key
from
    sys.indexes
        join
    sys.index_columns
            on sys.indexes.object_id = sys.index_columns.object_id
            and sys.indexes.index_id = sys.index_columns.index_id
        join
    sys.columns
            on sys.indexes.object_id = sys.columns.object_id
            and sys.index_columns.column_id = sys.columns.column_id
-- where
-- sys.indexes.object_id = @object_id and
-- sys.indexes.index_id = @index_id
'

    function Export-Index
    {
        [CmdletBinding(DefaultParameterSetName='All')]
        param(
            [Parameter(Mandatory,ParameterSetName='ByIndex')]
            [object]
            $Object,

            [Parameter(Mandatory,ParameterSetName='ByTable')]
            [int]
            $TableID,

            [Switch]
            $ForTable
        )

        if( $PSCmdlet.ParameterSetName -eq 'All' )
        {
            foreach( $object in $indexes )
            {
                if( (Test-SkipObject -SchemaName $Object.schema_name -Name $Object.name) -or $ExcludeType -contains 'Index' )
                {
                    continue
                }

                Export-Index -Object $object -ForTable:$ForTable
            }
            return
        }
        elseif( $PSCmdlet.ParameterSetName -eq 'ByTable' )
        {
            foreach( $object in $indexesByObjectID[$TableID] )
            {
                Export-Index -Object $object -ForTable:$ForTable
            }
            return
        }

        if( -not $ForTable )
        {
            Export-Object -ObjectID $Object.object_id
        }

        $indexKey = '{0}_{1}' -f $Object.object_id,$Object.index_id
        if( $exportedIndexes.ContainsKey($indexKey) )
        {
            return
        }

        Export-DependentObject -ObjectID $Object.object_id

        $unique = ''
        if( $Object.is_unique )
        {
            $unique = ' -Unique'
        }
        $clustered = ''
        if( $Object.type_desc -eq 'CLUSTERED' )
        {
            $clustered = ' -Clustered'
        }
        $where = ''
        if( $Object.has_filter )
        {
            $where = ' -Where ''{0}''' -f $Object.filter_definition
        }

        $allColumns = $indexColumnsByObjectID[$Object.object_id] | Where-Object { $_.index_id -eq $Object.index_id }

        $includedColumns = $allColumns | Where-Object { $_.is_included_column } | Sort-Object -Property 'name' # I don't think order matters so order them discretely.
        $include = ''
        if( $includedColumns )
        {
            $include = ' -Include ''{0}''' -f (($includedColumns | Select-Object -ExpandProperty 'name') -join ''',''')
        }

        $columns = $allColumns | Where-Object { -not $_.is_included_column } | Sort-Object -Property 'key_ordinal'

        $descending = ''
        if( $columns | Where-Object { $_.is_descending_key } )
        {
            $descending = $columns | Select-Object -ExpandProperty 'is_descending_key' | ForEach-Object { if( $_ ) { '$true' } else { '$false' } }
            $descending = ' -Descending {0}' -f ($descending -join ',')
        }

        $columnNames = $columns | Select-Object -ExpandProperty 'name'
        Write-ExportingMessage -Schema $Object.schema_name -Name $Object.name -Type Index
        $schema = ConvertTo-SchemaParameter -SchemaName $Object.schema_name
        ' Add-Index{0} -TableName ''{1}'' -ColumnName ''{2}'' -Name ''{3}''{4}{5}{6}{7}{8}' -f $schema,$Object.table_name,($columnNames -join ''','''),$Object.name,$clustered,$unique,$include,$descending,$where
        if( -not $ForTable )
        {
            Push-PopOperation ('Remove-Index{0} -TableName ''{1}'' -Name ''{2}''' -f $schema,$Object.table_name,$Object.name)
        }
        $exportedIndexes[$indexKey] = $true
    }

    function Get-ModuleDefinition
    {
        param(
            [Parameter(Mandatory)]
            [int]
            $ObjectID
        )

        $modulesByID[$ObjectID].definition
    }

    $objectsQuery = '
-- OBJECTS
select
    sys.schemas.name as schema_name,
    sys.objects.name as object_name,
    sys.objects.name as name,
    sys.schemas.name + ''.'' + sys.objects.name as full_name,
    sys.extended_properties.value as description,
    parent_objects.name as parent_object_name,
    sys.objects.object_id as object_id,
    RTRIM(sys.objects.type) as type,
    sys.objects.type_desc,
    sys.objects.parent_object_id
from
    sys.objects
        join
    sys.schemas
            on sys.objects.schema_id = sys.schemas.schema_id
        left join
    sys.extended_properties
            on sys.objects.object_id = sys.extended_properties.major_id
            and sys.extended_properties.minor_id = 0
            and sys.extended_properties.name = ''MS_Description''
        left join
    sys.objects parent_objects
        on sys.objects.parent_object_id = parent_objects.object_id
where
    sys.objects.is_ms_shipped = 0 and
    (parent_objects.is_ms_shipped is null or parent_objects.is_ms_shipped = 0) and
    sys.schemas.name != ''rivet'''

    function Export-Object
    {
        [CmdletBinding(DefaultParameterSetName='All')]
        param(
            [Parameter(Mandatory,ParameterSetName='ByObjectID')]
            [int[]]
            $ObjectID = @()
        )

        $filteredObjects = $objects
        if( $PSCmdlet.ParameterSetName -eq 'ByObjectID' )
        {
            $filteredObjects = $ObjectID | ForEach-Object { $objectsByID[$_] }
        }

        foreach( $object in $filteredObjects )
        {
            if( $exportedObjects.ContainsKey($object.object_id) )
            {
                Write-Debug ('Skipping ALREADY EXPORTED {0}' -f $object.full_name)
                continue
            }

            if( (Test-SkipObject -SchemaName $object.schema_name -Name $object.object_name -Type $object.type_desc) )
            {
                continue
            }

            if( $object.schema_name -eq 'rivet' )
            {
                continue
            }

            Export-Schema -Name $object.schema_name

            Export-DependentObject -ObjectID $object.object_id

            if( $exportedObjects.ContainsKey($object.object_id) )
            {
                continue
            }

            if( $externalDependencies.ContainsKey($object.object_id) )
            {
                Write-Warning -Message ('Unable to export {0} {1}: it depends on external object {2}.' -f $object.type_desc,$object.full_name,$externalDependencies[$object.object_id])
                $exportedObjects[$object.object_id] = $true
                continue
            }

            switch ($object.type_desc)
            {
                'CHECK_CONSTRAINT'
                {
                    Export-CheckConstraint -Object $object
                    break
                }
                'DEFAULT_CONSTRAINT'
                {
                    Export-DefaultConstraint -Object $object
                    break
                }
                'FOREIGN_KEY_CONSTRAINT'
                {
                    Export-ForeignKey -Object $object
                    break
                }
                'PRIMARY_KEY_CONSTRAINT'
                {
                    Export-PrimaryKey -Object $object
                    break
                }
                'SQL_INLINE_TABLE_VALUED_FUNCTION'
                {
                    Export-UserDefinedFunction -Object $object
                    break
                }
                'SQL_SCALAR_FUNCTION'
                {
                    Export-UserDefinedFunction -Object $object
                    break
                }
                'SQL_STORED_PROCEDURE'
                {
                    Export-StoredProcedure -Object $object
                    break
                }
                'SQL_TABLE_VALUED_FUNCTION'
                {
                    Export-UserDefinedFunction -Object $object
                    break
                }
                'SQL_TRIGGER'
                {
                    Export-Trigger -Object $object
                    break
                }
                'SYNONYM'
                {
                    Export-Synonym -Object $object
                    break
                }
                'UNIQUE_CONSTRAINT'
                {
                    Export-UniqueKey -Object $object
                    break
                }
                'USER_TABLE'
                {
                    Export-Table -Object $object
                    break
                }
                'VIEW'
                {
                    Export-View -Object $object
                    break
                }

                default
                {
                    Write-Error -Message ('Unable to export object "{0}": unsupported object type "{1}".' -f $object.full_name,$object.type_desc)
                }
            }
            $exportedObjects[$object.object_id] = $true
        }
    }

    # PRIMARY KEYS
    $primaryKeysQuery = '
-- PRIMARY KEYS
select
    sys.key_constraints.object_id,
    sys.indexes.type_desc
from
    sys.key_constraints
        join
    sys.indexes
            on sys.key_constraints.parent_object_id = sys.indexes.object_id
            and sys.key_constraints.unique_index_id = sys.indexes.index_id
where
    sys.key_constraints.type = ''PK''
    and sys.key_constraints.is_ms_shipped = 0'


    # PRIMARY KEY COLUMNS
    $primaryKeyColumnsQuery = '
-- PRIMARY KEY COLUMNS
select
    sys.objects.object_id,
    sys.schemas.name as schema_name,
    sys.tables.name as table_name,
    sys.columns.name as column_name,
    sys.indexes.type_desc,
    sys.index_columns.key_ordinal
from
    sys.objects
    join sys.tables
        on sys.objects.parent_object_id = sys.tables.object_id
    join sys.schemas
        on sys.schemas.schema_id = sys.tables.schema_id
    join sys.indexes
        on sys.indexes.object_id = sys.tables.object_id
    join sys.index_columns
        on sys.indexes.object_id = sys.index_columns.object_id
        and sys.indexes.index_id = sys.index_columns.index_id
    join sys.columns
        on sys.indexes.object_id = sys.columns.object_id
        and sys.columns.column_id = sys.index_columns.column_id
where
-- sys.objects.object_id = @object_id and
    sys.objects.type = ''PK'' and
    sys.indexes.is_primary_key = 1'


    function Export-PrimaryKey
    {
        param(
            [Parameter(Mandatory,ParameterSetName='ByObject')]
            [object]
            $Object,

            [Parameter(Mandatory,ParameterSetName='ByTableID')]
            [int]
            $TableID,

            [Switch]
            $ForTable
        )

        if( $TableID )
        {
            $Object = Get-ChildObject -TableID $TableID -Type 'PK'
            if( -not $Object )
            {
                return
            }
        }

        if( -not $ForTable )
        {
            Export-Object -ObjectID $Object.parent_object_id
        }

        if( $exportedObjects.ContainsKey($Object.object_id) )
        {
            return
        }

        Export-DependentObject -ObjectID $Object.object_id

        $primaryKey = $primaryKeysByID[$Object.object_id]
        $columns = $primaryKeyColumnsByObjectID[$Object.object_id]
        if( -not $columns )
        {
            # PK on a table-valued function.
            $exportedObjects[$Object.object_id] = $true
            return        
        }

        $schema = ConvertTo-SchemaParameter -SchemaName $Object.schema_name
        $columnNames = $columns | Sort-Object -Property 'key_ordinal' | Select-Object -ExpandProperty 'column_name'
        $nonClustered = ''
        if( $primaryKey.type_desc -eq 'NONCLUSTERED' )
        {
            $nonClustered = ' -NonClustered'
        }
        Write-ExportingMessage -Schema $Object.schema_name -Name $Object.name -Type PrimaryKey
        ' Add-PrimaryKey{0} -TableName ''{1}'' -ColumnName ''{2}'' -Name ''{3}''{4}' -f $schema,$Object.parent_object_name,($columnNames -join ''','''),$object.object_name,$nonClustered
        if( -not $ForTable )
        {
            Push-PopOperation ('Remove-PrimaryKey{0} -TableName ''{1}'' -Name ''{2}''' -f $schema,$Object.parent_object_name,$object.object_name)
        }
        $exportedObjects[$Object.object_id] = $true
    }

    $schemasQuery = '
-- SCHEMAS
select
    sys.schemas.name,
    sys.sysusers.name as owner
from
    sys.schemas
        join
    sys.sysusers
        on sys.schemas.principal_id = sys.sysusers.uid'

    function Export-Schema
    {
        param(
            [Parameter(Mandatory)]
            [string]
            $Name
        )

        if( $exportedSchemas.ContainsKey($Name) )
        {
            return
        }

        $schema = $schemasByName[$Name]
        if( -not $schema )
        {
            return
        }

        Write-ExportingMessage -Schema $Object.schema_name -Type Schema
        ' Add-Schema -Name ''{0}'' -Owner ''{1}''' -f $schema.name,$schema.owner
        $exportedSchemas[$schema.name] = $true
        Push-PopOperation ('Remove-Schema -Name ''{0}''' -f $schema.name)
    }

    function Export-StoredProcedure
    {
        param(
            [Parameter(Mandatory)]
            [object]
            $Object
        )

        Export-DependentObject -ObjectID $Object.object_id

        $query = 'select definition from sys.sql_modules where object_id = @object_id'
        $definition = Get-ModuleDefinition -ObjectID $Object.object_id

        try
        {
            if( -not $definition )
            {
                Write-Warning -Message ('Unable to export stored procedure [{0}].[{1}]: definition not readable.' -f $Object.schema_name,$Object.name)
                return
            }

            $schema = ConvertTo-SchemaParameter -SchemaName $Object.schema_name
            $createPreambleRegex = '^CREATE\s+procedure\s+\[{0}\]\.\[{1}\]\s+' -f [regex]::Escape($Object.schema_name),[regex]::Escape($Object.object_name)
            Write-ExportingMessage -Schema $Object.schema_name -Name $Object.name -Type StoredProcedure
            if( $definition -match $createPreambleRegex )
            {
                $definition = $definition -replace $createPreambleRegex,''
                ' Add-StoredProcedure{0} -Name ''{1}'' -Definition @''{2}{3}{2}''@' -f $schema,$Object.object_name,[Environment]::NewLine,$definition
            }
            else
            {
                ' Invoke-Ddl -Query @''{0}{1}{0}''@' -f [Environment]::NewLine,$definition
            }
            Push-PopOperation ('Remove-StoredProcedure{0} -Name ''{1}''' -f $schema,$Object.object_name)
        }
        finally
        {
            $exportedObjects[$Object.object_id] = $true
        }
    }

    $synonymsQuery = '
-- SYNONYMS
select
    sys.synonyms.object_id,
    parsename(base_object_name,3) as database_name,
    parsename(base_object_name,2) as schema_name,
    parsename(base_object_name,1) as object_name,
    sys.objects.object_id as target_object_id
from
    sys.synonyms
        left join
    sys.objects
            on parsename(sys.synonyms.base_object_name,2) = schema_name(sys.objects.schema_id)
            and parsename(sys.synonyms.base_object_name,1) = sys.objects.name
'

    function Export-Synonym
    {
        param(
            [Parameter(Mandatory)]
            [object]
            $Object
        )
        
        $schema = ConvertTo-SchemaParameter -SchemaName $Object.schema_name
        $synonym = $synonymsByID[$Object.object_id]

        if( $synonym.target_object_id -and $synonym.target_object_id -ne $synonym.object_id )
        {
            Export-Object -ObjectID $synonym.target_object_id
        }

        if( $synonym.database_name -and $synonym.database_name -ne $Database )
        {
            Write-Warning -Message ('Unable to export SYNONYM {0}.{1}: it depends on external object [{2}].[{3}].[{4}].' -f $Object.schema_name,$Object.name,$synonym.database_name,$synonym.schema_name,$synonym.object_name)
            $exportedObjects[$Object.object_id] = $true
            return
        }

        $targetDBName = ''
        if( $synonym.database_name )
        {
            $targetDBName = ' -TargetDatabaseName ''{0}''' -f $synonym.database_name
        }

        $targetSchemaName = ''
        if( $synonym.schema_name )
        {
            $targetSchemaName = ' -TargetSchemaName ''{0}''' -f $synonym.schema_name
        }

        Write-ExportingMessage -Schema $Object.schema_name -Name $Object.name -Type Synonym
        ' Add-Synonym{0} -Name ''{1}''{2}{3} -TargetObjectName ''{4}''' -f $schema,$Object.name,$targetDBName,$targetSchemaName,$synonym.object_name
        Push-PopOperation ('Remove-Synonym{0} -Name ''{1}''' -f $schema,$Object.name)
        $exportedObjects[$Object.object_id] = $true
    }

    function Export-Table
    {
        param(
            [Parameter(Mandatory)]
            [object]
            $Object
        )

        $schema = ConvertTo-SchemaParameter -SchemaName $Object.schema_name

        $description = $Object.description
        if( $description )
        {
            $description = ' -Description ''{0}''' -f ($description -replace '''','''''')
        }

        Write-ExportingMessage -Schema $Object.schema_name -Name $Object.name -Type Table
        ' Add-Table{0} -Name ''{1}''{2} -Column {{' -f $schema,$object.object_name,$description
        Export-Column -TableID $object.object_id
        ' }'

        $exportedObjects[$object.object_id] = $true

        Export-PrimaryKey -TableID $Object.object_id -ForTable
        Export-DefaultConstraint -TableID $Object.object_id -ForTable
        Export-CheckConstraint -TableID $Object.object_id -ForTable
        Export-Index -TableID $Object.object_id -ForTable
        Export-UniqueKey -TableID $Object.object_id -ForTable
        Export-Trigger -TableID $Object.object_id -ForTable

        # Do this last because table objects can reference other objects and those would need to get removed before the table
        Push-PopOperation ('Remove-Table{0} -Name ''{1}''' -f $schema,$object.object_name)
    }

    $triggersQuery = '
-- TRIGGERS
select
    sys.triggers.name,
    schema_name(sys.objects.schema_id) as schema_name,
    sys.triggers.object_id,
    sys.triggers.parent_id
from
    sys.triggers
        join
    sys.objects
        on sys.triggers.object_id = sys.objects.object_id'

    function Export-Trigger
    {
        param(
            [Parameter(Mandatory,ParameterSetName='ByTrigger')]
            [object]
            $Object,

            [Parameter(Mandatory,ParameterSetName='ByTable')]
            [int]
            $TableID,

            [Switch]
            $ForTable
        )

        if( $PSCmdlet.ParameterSetName -eq 'ByTable' )
        {
            foreach( $object in $triggersByTable[$TableID] )
            {
                Export-Trigger -Object $object -ForTable:$ForTable
            }
            return
        }

        if( -not $ForTable )
        {
            Export-Object -ObjectID $Object.parent_object_id
        }

        if( $exportedObjects.ContainsKey($Object.object_id) )
        {
            return
        }

        Export-DependentObject -ObjectID $Object.object_id

        $schema = ConvertTo-SchemaParameter -SchemaName $object.schema_name
        $trigger = Get-ModuleDefinition -ObjectID $Object.object_id
        $createPreambleRegex = '^create\s+trigger\s+\[{0}\]\.\[{1}\]\s+' -f [regex]::Escape($Object.schema_name),[regex]::Escape($Object.name)
        Write-ExportingMessage -Schema $Object.schema_name -Name $Object.name -Type Trigger
        if( $trigger -match $createPreambleRegex )
        {
            $trigger = $trigger -replace $createPreambleRegex,''
            ' Add-Trigger{0} -Name ''{1}'' -Definition @''{2}{3}{2}''@' -f $schema,$Object.name,[Environment]::NewLine,$trigger
        }
        else
        {
            ' Invoke-Ddl -Query @''{0}{1}{0}''@' -f [Environment]::NewLine,$trigger
        }
        if( -not $ForTable )
        {
            Push-PopOperation ('Remove-Trigger{0} -Name ''{1}''' -f $schema,$Object.name)
        }
        $exportedObjects[$Object.object_id] = $true
    }

    $uniqueKeysQuery = '
-- UNIQUE KEYS
select
    sys.key_constraints.name,
    schema_name(sys.key_constraints.schema_id) as schema_name,
    sys.key_constraints.object_id,
    sys.tables.name as parent_object_name,
    sys.key_constraints.parent_object_id,
    sys.indexes.type_desc
from
    sys.key_constraints
        join
    sys.tables
            on sys.key_constraints.parent_object_id = sys.tables.object_id
        join
    sys.indexes
            on sys.indexes.object_id = sys.tables.object_id
            and sys.key_constraints.unique_index_id = sys.indexes.index_id
where
    sys.key_constraints.type = ''UQ'''


    $uniqueKeysColumnsQuery = '
-- UNIQUE KEY COLUMNS
select
    sys.key_constraints.object_id,
    sys.columns.name
from
    sys.key_constraints
        join
    sys.indexes
            on sys.key_constraints.parent_object_id = sys.indexes.object_id
            and sys.key_constraints.unique_index_id = sys.indexes.index_id
        join
    sys.index_columns
            on sys.indexes.object_id = sys.index_columns.object_id
            and sys.indexes.index_id = sys.index_columns.index_id
        join
    sys.columns
            on sys.indexes.object_id = sys.columns.object_id
            and sys.index_columns.column_id = sys.columns.column_id
where
    sys.key_constraints.type = ''UQ'''

    function Export-UniqueKey
    {
        param(
            [Parameter(Mandatory,ParameterSetName='ByKey')]
            [object]
            $Object,

            [Parameter(Mandatory,ParameterSetName='ForTable')]
            [int]
            $TableID,

            [Switch]
            $ForTable
        )

        if( $PSCmdlet.ParameterSetName -eq 'ForTable' )
        {
            foreach( $object in $uniqueKeysByTable[$TableID] )
            {
                Export-UniqueKey -Object $object -ForTable:$ForTable
            }
            return
        }

        if( -not $ForTable )
        {
            Export-Object -ObjectID $Object.parent_object_id
        }

        if( $exportedObjects.ContainsKey($Object.object_id) )
        {
            return
        }

        Export-DependentObject -ObjectID $Object.object_id

        $uniqueKey = $uniqueKeysByID[$Object.object_id]

        $columns = $uniqueKeyColumnsByObjectID[$Object.object_id]
        $columnNames = $columns | Select-Object -ExpandProperty 'name'
        $clustered = ''
        if( $uniqueKey.type_desc -eq 'CLUSTERED' )
        {
            $clustered = ' -Clustered'
        }
        $schema = ConvertTo-SchemaParameter -SchemaName $Object.schema_name
        Write-ExportingMessage -Schema $Object.schema_name -Name $Object.name -Type UniqueKey
        ' Add-UniqueKey{0} -TableName ''{1}'' -ColumnName ''{2}''{3} -Name ''{4}''' -f $schema,$Object.parent_object_name,($columnNames -join ''','''),$clustered,$Object.name
        if( -not $ForTable )
        {
            Push-PopOperation ('Remove-UniqueKey{0} -TableName ''{1}'' -Name ''{2}''' -f $schema,$Object.parent_object_name,$Object.name)
        }
        $exportedObjects[$Object.object_id] = $true
    }

    function Export-UserDefinedFunction
    {
        param(
            [Parameter(Mandatory)]
            [object]
            $Object
        )

        Export-DependentObject -ObjectID $Object.object_id

        $schema = ConvertTo-SchemaParameter -SchemaName $object.schema_name
        $function = Get-ModuleDefinition -ObjectID $Object.object_id
        $createPreambleRegex = '^create\s+function\s+\[{0}\]\.\[{1}\]\s+' -f [regex]::Escape($Object.schema_name),[regex]::Escape($Object.name)
        Write-ExportingMessage -Schema $Object.schema_name -Name $Object.name -Type Function
        if( $function -match $createPreambleRegex )
        {
            $function = $function -replace $createPreambleRegex,''
            ' Add-UserDefinedFunction{0} -Name ''{1}'' -Definition @''{2}{3}{2}''@' -f $schema,$Object.name,[Environment]::NewLine,$function
        }
        else
        {
            ' Invoke-Ddl -Query @''{0}{1}{0}''@' -f [Environment]::NewLine,$function
        }
        Push-PopOperation ('Remove-UserDefinedFunction{0} -Name ''{1}''' -f $schema,$Object.name)
        $exportedObjects[$Object.object_id] = $true
    }

    function Export-View
    {
        param(
            [Parameter(Mandatory)]
            [object]
            $Object
        )

        Export-DependentObject -ObjectID $Object.object_id

        $schema = ConvertTo-SchemaParameter -SchemaName $object.schema_name
        $query = 'select definition from sys.sql_modules where object_id = @view_id'
        $view = Get-ModuleDefinition -ObjectID $Object.object_id
        $createPreambleRegex = '^CREATE\s+view\s+\[{0}\]\.\[{1}\]\s+' -f [regex]::Escape($Object.schema_name),[regex]::Escape($Object.name)
        Write-ExportingMessage -Schema $Object.schema_name -Name $Object.name -Type View
        if( $view -match $createPreambleRegex )
        {
            $view = $view -replace $createPreambleRegex,''
            ' Add-View{0} -Name ''{1}'' -Definition @''{2}{3}{2}''@' -f $schema,$Object.name,[Environment]::NewLine,$view
        }
        else
        {
            ' Invoke-Ddl -Query @''{0}{1}{0}''@' -f [Environment]::NewLine,$view
        }
        Push-PopOperation ('Remove-View{0} -Name ''{1}''' -f $schema,$Object.name)
        $exportedObjects[$Object.object_id] = $true
    }

    $xmlSchemaQuery = '
select
    schema_name(schema_id) as schema_name,
    name,
    xml_collection_id,
    XML_SCHEMA_NAMESPACE(schema_name(schema_id),sys.xml_schema_collections.name) as xml_schema
from
    sys.xml_schema_collections
where
    sys.xml_schema_collections.name != ''sys'''

    function Export-XmlSchema
    {
        param(
            [Parameter(Mandatory)]
            [int]
            $ID
        )

        if( $exportedXmlSchemas.ContainsKey($ID) )
        {
            return
        }

        if( $ExcludeType -contains 'XmlSchema' )
        {
            return
        }

        $xmlSchema = $xmlSchemasByID[$ID]

        Write-ExportingMessage -SchemaName $xmlSchema.schema_name -Name $xmlSchema.name -Type XmlSchema
        ' Invoke-Ddl @'''
        'create xml schema collection [{0}].[{1}] as' -f $xmlSchema.schema_name,$xmlSchema.name
        'N'''
        $xmlschema.xml_schema
        ''''
        '''@'
        Push-PopOperation ('Invoke-Ddl ''drop xml schema collection [{0}].[{1}]''' -f $xmlSchema.schema_name,$xmlSchema.name)
        $exportedXmlSchemas[$ID] = $true
    }

    function Push-PopOperation
    {
        param(
            [Parameter(Mandatory)]
            $InputObject
        )

        if( -not ($popsHash.ContainsKey($InputObject)) )
        {
            $pops.Push($InputObject)
            $popsHash[$InputObject] = $true
        }
    }

    $objectTypesToExclude = @()
    if( $ExcludeType )
    {
        $objectTypesToExclude = $ExcludeType | ForEach-Object { $exclusionTypeMap[$_] }
    }
    function Test-SkipObject
    {
        param(
            [Parameter(Mandatory)]
            [string]
            $SchemaName,

            [Parameter(Mandatory)]
            [string]
            $Name,

            [string]
            $Type
        )

        if( -not $Include -and -not $ExcludeType -and -not $Exclude )
        {
            return $false
        }

        $fullName = '{0}.{1}' -f $SchemaName,$Name

        if( $Type )
        {
            if( $objectTypesToExclude -contains $Type )
            {
                Write-Debug ('Skipping EXCLUDED TYPE {0} {1}' -f $fullName,$Type)
                return $true
            }
        }

        if( $Include )
        {
            $skip = $true
            foreach( $filter in $Include )
            {
                if( $fullName -like $filter )
                {
                    $skip = $false
                    break
                }
            }
            if( $skip )
            {
                return $true
            }
        }

        if( $Exclude )
        {
            foreach( $filter in $Exclude )
            {
                if( $fullName -like $filter )
                {
                    return $true
                }
            }
        }

        return $false
    }

    function Write-ExportingMessage
    {
        [CmdletBinding(DefaultParameterSetName='Schema')]
        param(
            [Parameter(Mandatory)]
            [string]
            $SchemaName,

            [Parameter(Mandatory,ParameterSetName='NotSchema')]
            [string]
            $Name,

            [Parameter(Mandatory)]
            [ValidateSet('Table','View','DefaultConstraint','StoredProcedure','Synonym','ForeignKey','CheckConstraint','PrimaryKey','Trigger','Function','Index','DataType','Schema','UniqueKey','XmlSchema')]
            [string]
            $Type
        )

        $objectName = $SchemaName
        if( $Name )
        {
            $objectName = '{0}.{1}' -f $objectName,$Name
        }

        $message = '{0,-17} {1}{2}' -f $Type,(' ' * $indentLevel),$objectName
        $timer.CurrentOperation = $message
        $timer.ExportCount += 1
        Write-Verbose -Message $message
    }

    $activity = 'Exporting migrations from {0}.{1}' -f $SqlServerName,$Database
    $writeProgress = [Environment]::UserInteractive
    if( $NoProgress )
    {
        $writeProgress = $false
    }
    $event = $null

    Connect-Database -SqlServerName $SqlServerName -Database $Database -ErrorAction Stop | Out-Null
    try
    {
        #region QUERIES
        # OBJECTS
        $objects = Invoke-Query -Query $objectsQuery
        $objects | ForEach-Object { $objectsByID[$_.object_id] = $_ }
        $objects | Group-Object -Property 'parent_object_id' | ForEach-Object { $objectsByParentID[[int]$_.Name] = $_.Group }
        $objectTypes = $objects | Select-Object -ExpandProperty 'type_desc' | Select-Object -Unique

        # CHECK CONSTRAINTS
        if( $objectTypes -contains 'CHECK_CONSTRAINT' )
        {
            $checkConstraints = Invoke-Query -Query $checkConstraintsQuery
            $checkConstraints | ForEach-Object { $checkConstraintsByID[$_.object_id] = $_ }
        }

        # DATA TYPES
        $dataTypes = Invoke-Query -Query $dataTypesQuery

        # COLUMNS
        if( $objectTypes -contains 'USER_TABLE' -or $dataTypes )
        {
            $columns = Invoke-Query -Query $columnsQuery
            $columns | Group-Object -Property 'object_id' | ForEach-Object { $columnsByTable[[int]$_.Name] = $_.Group }
        }

        # DEFAULT CONSTRAINTS
        if( $objectTypes -contains 'DEFAULT_CONSTRAINT' )
        {
            $defaultConstraints = Invoke-Query -Query $defaultConstraintsQuery #-Parameter @{ '@object_id' = $constraintObject.object_id }
            $defaultConstraints | ForEach-Object { $defaultConstraintsByID[$_.object_id] = $_ }
        }

        # FOREIGN KEYS
        if( $objectTypes -contains 'FOREIGN_KEY_CONSTRAINT' )
        {
            $foreignKeys = Invoke-Query -Query $foreignKeysQuery
            $foreignKeys | ForEach-Object { $foreignKeysByID[$_.object_id] = $_ }

            # FOREIGN KEY COLUMNS
            $foreignKeyColumns = Invoke-Query -Query $foreignKeyColumnsQuery
            $foreignKeyColumns | Group-Object -Property 'constraint_object_id' | ForEach-Object { $foreignKeyColumnsByObjectID[[int]$_.Name] = $_.Group }
        }

        # INDEXES
        if( $objectTypes -contains 'USER_TABLE' )
        {
            $indexes = Invoke-Query -Query $indexesQuery
            $indexes | Group-Object -Property 'object_id' | ForEach-Object { $indexesByObjectID[[int]$_.Name] = $_.Group }

            # INDEX COLUMNS
            $indexColumns = Invoke-Query -Query $indexesColumnsQuery
            $indexColumns | Group-Object -Property 'object_id' | ForEach-Object { $indexColumnsByObjectID[[int]$_.Name] = $_.Group }
        }

        if( $objectTypes -contains 'PRIMARY_KEY_CONSTRAINT' )
        {
            $primaryKeys = Invoke-Query -Query $primaryKeysQuery
            $primaryKeys | ForEach-Object { $primaryKeysByID[$_.object_id] = $_ }

            $primaryKeyColumns = Invoke-Query -Query $primaryKeyColumnsQuery
            $primaryKeyColumns | Group-Object -Property 'object_id' | ForEach-Object { $primaryKeyColumnsByObjectID[[int]$_.Name] = $_.Group }
        }

        # SCHEMAS
        if( ($objects | Where-Object { $_.schema_name -ne 'dbo' }) -or ($dataTypes | Where-Object { $_.schema_name -ne 'dbo' }) )
        {
            $schemas = Invoke-Query -Query $schemasQuery
            $schemas | ForEach-Object { $schemasByName[$_.name] = $_ }
        }

        # MODULES/PROGRAMMABILITY
        if( $objectTypes -contains 'SQL_INLINE_TABLE_VALUED_FUNCTION' -or $objectTypes -contains 'SQL_SCALAR_FUNCTION' -or $objectTypes -contains 'SQL_STORED_PROCEDURE' -or $objectTypes -contains 'SQL_TABLE_VALUED_FUNCTION' -or $objectTypes -contains 'SQL_TRIGGER' -or $objectTypes -contains 'VIEW' )
        {
            $query = 'select object_id, definition from sys.sql_modules'
            $modules = Invoke-Query -Query $query
            $modules | ForEach-Object { $modulesByID[$_.object_id] = $_ }
        }

        # SYNONYMS
        if( $objectTypes -contains 'SYNONYM' )
        {
            $synonyms = Invoke-Query -Query $synonymsQuery
            $synonyms | ForEach-Object { $synonymsByID[$_.object_id] = $_ }
        }

        # TRIGGERS
        if( $objectTypes -contains 'SQL_TRIGGER' )
        {
            $triggers = Invoke-Query -Query $triggersQuery
            $triggers | ForEach-Object { $triggersByID[$_.object_id] = $_ }        
            $triggers | Group-Object -Property 'parent_id' | ForEach-Object { $triggersByTable[[int]$_.Name] = $_.Group }
        }

        if( $objectTypes -contains 'UNIQUE_CONSTRAINT' )
        {
            # UNIQUE KEYS
            $uniqueKeys =  Invoke-Query -Query $uniqueKeysQuery
            $uniqueKeys | ForEach-Object { $uniqueKeysByID[$_.object_id] = $_ }
            $uniqueKeys | Group-Object -Property 'parent_object_id' | ForEach-Object { $uniqueKeysByTable[[int]$_.Name] = $_.Group }
        
            # UNIQUE KEY COLUMNS
            $uniqueKeyColumns = Invoke-Query -Query $uniqueKeysColumnsQuery
            $uniqueKeyColumns | Group-Object -Property 'object_id' | ForEach-Object { $uniqueKeyColumnsByObjectID[[int]$_.Name] = $_.Group }
        }

        if( $columns | Where-Object { $_.xml_collection_id } )
        {
            $query = '
select
    sys.columns.object_id,
    sys.columns.xml_collection_id
from
    sys.columns
        join
    sys.types
            on sys.columns.user_type_id=sys.types.user_type_id
            and sys.columns.system_type_id=sys.types.system_type_id
where
    sys.types.name = ''xml'' and
    sys.columns.xml_collection_id != 0
'

            $objectsWithXmlSchemas = Invoke-Query -Query $query
            $objectsWithXmlSchemas | Group-Object -Property 'object_id' | ForEach-Object { $xmlSchemaDependencies[[int]$_.Name] = $_.Group | Select-Object -ExpandProperty 'xml_collection_id' | Select-Object -Unique }

            $xmlSchemas = Invoke-Query -Query $xmlSchemaQuery
            $xmlSchemas | ForEach-Object { $xmlSchemasByID[$_.xml_collection_id] = $_ }
        }
        #endregion

        $sysDatabases = @( 'master', 'model', 'msdb', 'tempdb' )
        $query = 'select * from sys.sql_expression_dependencies'
        foreach( $row in (Invoke-Query -Query $query) )
        {
            $externalName = '[{0}]' -f $row.referenced_entity_name
            if( $row.referenced_schema_name )
            {
                $externalName = '[{0}].{1}' -f $row.referenced_schema_name,$externalName
            }
            if( $row.referenced_database_name )
            {
                # Allow references to system databases.
                if( $row.referenced_database_name -in $sysDatabases )
                {
                    continue
                }

                $externalName = '[{0}].{1}' -f $row.referenced_database_name,$externalName
            }
            if( $row.referenced_server_name )
            {
                $externalName = '[{0}].{1}' -f $row.referenced_server_name,$externalName
            }

            if( $row.referenced_server_name -or ($row.referenced_database_name -ne $null -and $row.referenced_database_name -ne $Database) )
            {
                $externalDependencies[$row.referencing_id] = $externalName
            }
            else
            {
                if( -not $dependencies.ContainsKey($row.referencing_id) )
                {
                    $dependencies[$row.referencing_id] = @{}
                }
                if( $row.referenced_id -ne $null -and $row.referenced_id -ne $row.referencing_id )
                {
                    $dependencies[$row.referencing_id][$row.referenced_id] = $externalName
                }
            }
        }

        $totalOperationCount = & {
                                    $objects
                                    $schemas
                                    $indexes
                                    $dataTypes
                                } | 
            Measure-Object | 
            Select-Object -ExpandProperty 'Count'

        if( $writeProgress )
        {
            Write-Progress -Activity $activity 
        }

        $timer | 
            Add-Member -Name 'ExportCount' -Value 0 -MemberType NoteProperty -PassThru |
            Add-Member -MemberType NoteProperty -Name 'Activity' -Value $activity -PassThru |
            Add-Member -MemberType NoteProperty -Name 'CurrentOperation' -Value '' -PassThru |
            Add-Member -MemberType NoteProperty -Name 'TotalCount' -Value $totalOperationCount
        
        if( $writeProgress )
        {
            # Write-Progress is *expensive*. Only do it if the user is interactive and only every 1/10th of a second.
            $event = Register-ObjectEvent -InputObject $timer -EventName 'Elapsed' -Action {
                param(
                    $Timer,
                    $EventArgs
                )
                Write-Progress -Activity $Timer.Activity -CurrentOperation $Timer.CurrentOperation -PercentComplete (($Timer.ExportCount/$Timer.TotalCount) * 100)
            }
            $timer.Enabled = $true
            $timer.Start()
        }

        'function Push-Migration'
        '{'
            Export-DataType
            Export-Object
            Export-Index
        '}'
        ''
        'function Pop-Migration'
        '{'
            $pops | ForEach-Object { ' {0}' -f $_ }
        '}'
    }
    finally
    {
        if( $writeProgress )
        {
            $timer.Stop()
            if( $event )
            {
                Unregister-Event -SourceIdentifier $event.Name
            }
            Write-Progress -Activity $activity -PercentComplete 99
            Write-Progress -Activity $activity -Completed
        }
        Disconnect-Database
    }
}